From ac8cd6d5aee83d413b8261bf69a56d9b439bc4b7 Mon Sep 17 00:00:00 2001 From: bartvanbos Date: Sun, 8 Nov 2026 14:39:45 +0100 Subject: [PATCH 1/2] chore(deps): go-canbus v0.9.0 Co-Authored-By: Claude Opus 5.5 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 918277c..d8465d5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/otfabric/canopenctl go 1.26.0 require ( - github.com/otfabric/go-canbus v0.8.0 + github.com/otfabric/go-canbus v0.9.0 github.com/otfabric/go-canopen v0.4.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index f4ff138..66cd136 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxK github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/otfabric/go-canbus v0.8.0 h1:z0etLlCxHqyQf17MB4/joDrVisNj+gzh1Zxih00xRZc= -github.com/otfabric/go-canbus v0.8.0/go.mod h1:/+y8PRSu7tTGdoh2f7NTxaRAML86ePZSXLfjehaFDgc= +github.com/otfabric/go-canbus v0.9.0 h1:DzRDY3eLzNAFxcqrnls1x2k7TFHDQzxOkQsUCT3N/xc= +github.com/otfabric/go-canbus v0.9.0/go.mod h1:/+y8PRSu7tTGdoh2f7NTxaRAML86ePZSXLfjehaFDgc= github.com/otfabric/go-canopen v0.4.1 h1:SG9cJCQjCD7ZReE00EpTA5irfu2ueB8kH8HfnZIHm9c= github.com/otfabric/go-canopen v0.4.1/go.mod h1:mnQwOvdhOfyB10pK1JkiUQdS7jnv58Urbt7ly6eFsDE= github.com/otfabric/go-serial v0.2.1 h1:PCOdnCDAHIHFaxTBNmisC/4SlD+NVd74d+EJPnXrCOw= From 4ca84f99b695654d4372c50c810655b11ad5f904 Mon Sep 17 00:00:00 2001 From: bartvanbos Date: Sun, 8 Nov 2026 14:42:37 +0100 Subject: [PATCH 2/2] feat: go-canopen v0.5.0 decoder fixes in monitor and decode go-canopen v0.5.0: guard answers with the toggle bit clear are guard responses (they were heartbeats); PDOs on COB-IDs set over SDO are decoded as that PDO ("configured"). Test with frames recorded from a Copley drive, record schema, json-output, LIMITATIONS, v0.7.0 release section. Co-Authored-By: Claude Opus 5.5 --- LIMITATIONS.md | 7 ++++-- RELEASE.md | 40 +++++++++++++++++++++++++++++++++++ cmd/monitor_test.go | 28 ++++++++++++++++++++++++ docs/json-output.md | 2 +- go.mod | 2 +- go.sum | 4 ++-- schemas/record-v1.schema.json | 2 +- 7 files changed, 78 insertions(+), 7 deletions(-) diff --git a/LIMITATIONS.md b/LIMITATIONS.md index bb0bb29..e143016 100644 --- a/LIMITATIONS.md +++ b/LIMITATIONS.md @@ -17,8 +17,11 @@ are opened without a data phase bit rate. `monitor` and `decode` skip error frames (counted in `frames`). - **Predefined connection set.** `monitor` and `decode` interpret frames by - their default identifiers (as Wireshark does); a device that moved a PDO or - SDO channel elsewhere is decoded by the identifier's default meaning. The + their default identifiers (as Wireshark does), except PDO COB-IDs they see + set or read over SDO (0x1400..0x15FF and 0x1800..0x19FF sub-index 1, + expedited): frames on those decode as that node's PDO (`"configured": + true`). A PDO moved before the trace starts, or an SDO channel moved + elsewhere, is decoded by the identifier's default meaning. The SDO commands use the default SDO channel (0x600/0x580 + node-ID); other channels (0x1200 sub 1/2, 0x1280..) are not selectable. - **LSS** (`lss`) acts on one network with one LSS master: canopenctl does not diff --git a/RELEASE.md b/RELEASE.md index a84643d..48ac95e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -6,6 +6,46 @@ with the version bump). It computes the tag, builds the binaries for linux publishes the GitHub release with the section of this file that matches the version as its notes. Add the section before running it. +## v0.7.0 + +**Date:** 2026-11-08 +**Previous release:** v0.6.1 + +### Summary + +go-canopen v0.5.0 and go-canbus v0.9.0. go-canopen is now tested against +recorded traffic of real Copley Controls drives; two of the decoder fixes +it brought show in `monitor` and `decode`. + +### Fixed + +- `monitor`, `decode`: a node guarding answer with the toggle bit clear + (`701#05` after `701#R`) was shown as a heartbeat; it is now a + `guard-response`, as the answers with the toggle bit set already were + (go-canopen's `dissect.Tracker`). **JSON**: such frames change `kind` + from `heartbeat` to `guard-response`; `--service heartbeat` shows both + as before. + +### Added + +- `monitor`, `decode`: PDOs on COB-IDs set or read over SDO earlier in the + trace are decoded as that node's PDO, with `"configured": true` in the + `pdo` object (record schema updated). A recorded Copley drive sends its + TPDO5 on 0x180, which was `unknown`. + +### Dependencies + +- go-canopen v0.5.0 (from v0.4.1), go-canbus v0.9.0 (from v0.8.0). canopenctl + only reads traces; go-canbus's writer and ISO-TP fixes do not change its + behaviour. + +### Tests + +- `decode` of frames recorded from a Copley drive (go-canopen's + testdata/captures): both guard answers, the learned TPDO5. + +--- + ## v0.6.1 **Date:** 2026-11-08 diff --git a/cmd/monitor_test.go b/cmd/monitor_test.go index 39b69c0..3dc4f04 100644 --- a/cmd/monitor_test.go +++ b/cmd/monitor_test.go @@ -103,3 +103,31 @@ func TestDecode(t *testing.T) { want(t, run(t, "", "decode", bad), 3, "") want(t, run(t, "", "decode", path, "--service", "x"), 3, "unknown service") } + +// copleyTrace holds frames recorded from a Copley Controls drive (go-canopen +// testdata/captures/copley/unknown_copley-1100_absolute-move.log, lines +// 426-427, 449-460 and 466-468), written as a candump log: node guarding +// answers with both toggle values, the TPDO5 COB-ID set to 0x180 over SDO, +// and a TPDO5 frame (the drive's microsecond time stamp 0x1013). +const copleyTrace = `(3383.105884) can0 701#R +(3383.105958) can0 701#85 +(3383.338300) can0 701#R +(3383.338350) can0 701#05 +(3383.132009) can0 601#2304180180010000 +(3383.132223) can0 581#6004180100000000 +(3383.213675) can0 180#B71FE879 +` + +// TestDecodeRecordedCopley: decode labels both guard answers as guard +// responses (the one with the toggle bit clear was a heartbeat before +// go-canopen v0.5.0) and the frame on 0x180 as the drive's TPDO5, learned +// from the SDO transfer ("configured"). +func TestDecodeRecordedCopley(t *testing.T) { + fresh(t) + r := run(t, copleyTrace, "decode", "-", "--format", "json") + want(t, r, 0, `"kind":"guard-response"`, `"summary":"TPDO5 node 1: B7 1F E8 79"`, `"configured":true`) + if strings.Contains(r.out, `"kind":"heartbeat"`) || strings.Count(r.out, `"kind":"guard-response"`) != 2 { + t.Fatalf("guard answers: %s", r.out) + } + want(t, run(t, copleyTrace, "decode", "-", "--service", "tpdo"), 0, "TPDO5 node 1", "1 shown") +} diff --git a/docs/json-output.md b/docs/json-output.md index b1270a9..6361c50 100644 --- a/docs/json-output.md +++ b/docs/json-output.md @@ -87,7 +87,7 @@ Streaming commands write JSON Lines: one compact object per line with | Command | `type` | Fields | |---------|--------|--------| -| `monitor`, `decode` | `message` | the frame (`timestamp`, `interface`, `id`, `idHex`, `extended`, `remote`, `len` (up to 64), `fd` and `brs` (CAN FD), `data`), `kind` (`nmt`, `sync`, `time`, `emcy`, `tpdo`, `rpdo`, `sdo-request`, `sdo-response`, `usdo-request`, `usdo-response`, `heartbeat`, `boot-up`, `guard-request`, `guard-response`, `lss-master`, `lss-slave`, `unknown`), `node` (for a USDO message its sender), `summary` (one line), `error` (malformed), and one object for the service: `nmt`, `sync`, `timeStamp`, `emcy` (`code`, `codeText`, `register`, `registerNames`, `msef`, `reset`, and `fd` for a CANopen FD emergency: `logicalDevice`, `ciaSpec`, `status`, `ms`, `days`, `time`), `pdo`, `sdo` (command, phase, object, data, abort code and text, block transfer fields; segments carry the object of their transfer), `usdo` (`client`, `dest`, `destination`, `cs`, `command`, `session`, `index`, `subIndex`, `hasIndex`, `dataType`, `size`, `counter`, `tProc`, `abortCode`, `abortText`, `data`), `errorControl`, `lss` | +| `monitor`, `decode` | `message` | the frame (`timestamp`, `interface`, `id`, `idHex`, `extended`, `remote`, `len` (up to 64), `fd` and `brs` (CAN FD), `data`), `kind` (`nmt`, `sync`, `time`, `emcy`, `tpdo`, `rpdo`, `sdo-request`, `sdo-response`, `usdo-request`, `usdo-response`, `heartbeat`, `boot-up`, `guard-request`, `guard-response`, `lss-master`, `lss-slave`, `unknown`), `node` (for a USDO message its sender), `summary` (one line), `error` (malformed), and one object for the service: `nmt`, `sync`, `timeStamp`, `emcy` (`code`, `codeText`, `register`, `registerNames`, `msef`, `reset`, and `fd` for a CANopen FD emergency: `logicalDevice`, `ciaSpec`, `status`, `ms`, `days`, `time`), `pdo`, `sdo` (command, phase, object, data, abort code and text, block transfer fields; segments carry the object of their transfer), `usdo` (`client`, `dest`, `destination`, `cs`, `command`, `session`, `index`, `subIndex`, `hasIndex`, `dataType`, `size`, `counter`, `tProc`, `abortCode`, `abortText`, `data`), `errorControl`, `lss`. `pdo` is `number`, `transmit` and `configured` (true when the PDO was identified by a COB-ID set or read over SDO earlier in the trace, not by the predefined connection set). A node guarding answer that follows a request is `guard-response` whatever its toggle bit | | | `summary` | `frames`, `shown`, `malformed`, `dropped`, `kinds` (count per kind) | | `heartbeat watch` | `heartbeat` | `timestamp`, `node`, `event` (`boot-up`, `state-change`, `timeout`, `resumed`), `state`, `previous` | | | `summary` | `events`, `timeouts`, `bootUps`, `nodes` (`node`, `state`, `bootUps`, `lost`, `lastSeen`, `timeoutMs`) | diff --git a/go.mod b/go.mod index d8465d5..487e25e 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/otfabric/go-canbus v0.9.0 - github.com/otfabric/go-canopen v0.4.1 + github.com/otfabric/go-canopen v0.5.0 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 diff --git a/go.sum b/go.sum index 66cd136..7d8f085 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/otfabric/go-canbus v0.9.0 h1:DzRDY3eLzNAFxcqrnls1x2k7TFHDQzxOkQsUCT3N/xc= github.com/otfabric/go-canbus v0.9.0/go.mod h1:/+y8PRSu7tTGdoh2f7NTxaRAML86ePZSXLfjehaFDgc= -github.com/otfabric/go-canopen v0.4.1 h1:SG9cJCQjCD7ZReE00EpTA5irfu2ueB8kH8HfnZIHm9c= -github.com/otfabric/go-canopen v0.4.1/go.mod h1:mnQwOvdhOfyB10pK1JkiUQdS7jnv58Urbt7ly6eFsDE= +github.com/otfabric/go-canopen v0.5.0 h1:bQJearjMs7yL6lKQgMXHZJd6r2RtTijJr8JWT6J8Ak4= +github.com/otfabric/go-canopen v0.5.0/go.mod h1:kIC0XAM59pii2korvrptJ0O9ucIQSvGimAzQx64eDd4= github.com/otfabric/go-serial v0.2.1 h1:PCOdnCDAHIHFaxTBNmisC/4SlD+NVd74d+EJPnXrCOw= github.com/otfabric/go-serial v0.2.1/go.mod h1:TqbYpWWg6W+IuCLs8YM2cndlMAEaTyaKyUX6Z08gtq8= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/schemas/record-v1.schema.json b/schemas/record-v1.schema.json index c215087..81a531b 100644 --- a/schemas/record-v1.schema.json +++ b/schemas/record-v1.schema.json @@ -54,7 +54,7 @@ "sync": {"type": "object", "required": ["hasCounter"]}, "timeStamp": {"type": "object", "required": ["ms", "days", "time"]}, "emcy": {"$ref": "#/$defs/emcy"}, - "pdo": {"type": "object", "required": ["number", "transmit"]}, + "pdo": {"type": "object", "required": ["number", "transmit"], "properties": {"number": {"type": "integer", "minimum": 1, "maximum": 512}, "transmit": {"type": "boolean"}, "configured": {"type": "boolean"}}}, "sdo": {"type": "object", "required": ["client", "cs", "command", "phase", "index", "subIndex", "hasIndex"], "properties": {"phase": {"enum": ["initiate", "segment", "block", "sub-block", "end", "abort", "unknown"]}}}, "usdo": {"type": "object", "required": ["client", "dest", "destination", "cs", "command", "session", "index", "subIndex", "hasIndex"]},