Skip to content

fix(docs): rename canopen CAN console to avoid apps/ basename collision - #753

Merged
finger563 merged 1 commit into
mainfrom
fix/docs-apps-can-console-collision
Sep 2, 2026
Merged

fix(docs): rename canopen CAN console to avoid apps/ basename collision#753
finger563 merged 1 commit into
mainfrom
fix/docs-apps-can-console-collision

Conversation

@finger563

Copy link
Copy Markdown
Contributor

The docs build (build_and_publish_docs.yml) hosts every components/*/web/*.html by flattening them into docs/apps/ with cp -L. #748 added components/canopen/web/can_console.html, but components/twai/web/can_console.html (“CAN Bus Console (slcan)”) already existed — so the copy collides and fails the docs job:

cp: will not overwrite just-created ../docs/apps/can_console.html with ../components/twai/web/can_console.html
=== BUILD FAILED ===

Fix

Rename the newer canopen bridge console → can_bridge_console.html (the twai console keeps its incumbent apps/can_console.html URL). Updated its three references:

  • example README.md link
  • the WebUSB landing_page_url baked into the firmware descriptor (can_bridge_example.cpp)
  • the canopen.rst doc link

The apps index globs the directory, so the renamed file is listed automatically. Verified there are no remaining duplicate basenames under components/*/web/.

🤖 Generated with Claude Code

The docs build hosts every components/*/web/*.html by flattening them into
docs/apps/ (cp -L in build_and_publish_docs.yml). #748 added a second
can_console.html (components/canopen/web/) while components/twai/web/ already had
one ("CAN Bus Console (slcan)"), so the copy failed:

  cp: will not overwrite just-created ../docs/apps/can_console.html
      with ../components/twai/web/can_console.html

Rename the newer canopen bridge console to can_bridge_console.html (the twai
console keeps its published apps/can_console.html URL) and update its three
references: the example README link, the WebUSB landing_page_url baked into the
firmware descriptor, and the canopen.rst doc link. The apps index globs the
directory, so the renamed file is listed automatically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 2, 2026 14:06
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

✅Static analysis result - no issues found! ✅

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The rename removes the apps/ basename collision and all updated links consistently point to the new hosted filename.

Pull request overview

This PR fixes a documentation-site build failure caused by two web apps sharing the same basename (can_console.html) when the docs workflow flattens components/*/web/*.html into docs/apps/. It resolves the collision by renaming the CANopen bridge console HTML file and updating the in-repo references to point at the new hosted URL.

Changes:

  • Rename the CANopen-hosted console web app to can_bridge_console.html to avoid apps/ basename collisions.
  • Update documentation and example README link to the new apps/can_bridge_console.html URL.
  • Update the WebUSB landing_page_url in the CAN bridge firmware example to match the new hosted page.
File summaries
File Description
doc/en/buses/canopen.rst Updates the hosted web app link to the renamed CAN bridge console.
components/canopen/web/can_bridge_console.html Adds the renamed single-file CAN bridge console web app under a unique basename.
components/canopen/can_bridge_example/README.md Updates the README hyperlink to the new hosted console URL.
components/canopen/can_bridge_example/main/can_bridge_example.cpp Updates the WebUSB landing page URL baked into the USB descriptor.
Review details
  • Files reviewed: 3/4 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@finger563
finger563 merged commit 3916271 into main Sep 2, 2026
148 of 155 checks passed
@finger563
finger563 deleted the fix/docs-apps-can-console-collision branch September 2, 2026 14:37
finger563 added a commit that referenced this pull request Sep 2, 2026
…e logging

- setStatusLine() now reflects the bus run state in the controls (only while
  connected): Apply + Start are disabled while the bus is running (SET_CONFIG is
  rejected by the firmware then) and Stop is disabled while stopped - matching the
  CAN bridge console, so Apply is not clickable with its "only while stopped"
  tooltip during a run.
- onIncoming() logged every non-SDO CAN frame to the DOM unconditionally; on a
  live CANopen bus (heartbeats / PDOs / EMCY) that floods the DOM and freezes the
  UI. Gate it behind a new "log bus frames" toggle (default off), mirroring the
  SDO-frame toggle.
- Update the can_console.html references to can_bridge_console.html (renamed on
  main in #753).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
finger563 added a commit that referenced this pull request Sep 2, 2026
… bridge) (#752)

* feat(canopen): add DS402 drive panel web app (in-browser SDO over the CAN bridge)

A single-file, offline, dependency-free browser front-end that commissions a
CANopen CiA 402 (DS402) drive through the existing USB<->CAN bridge
(can_bridge_example). It reuses the CAN console's transport + stream_frame
framing to move raw CAN frames and layers all CANopen/DS402 logic in the browser
("architecture option A") - the firmware stays a dumb raw-CAN pipe, no firmware
change needed.

Includes:
- An in-browser CANopen SDO client: expedited + segmented upload (read) and
  download (write) on the default SDO channel (0x600/0x580 + nodeId), with abort
  decoding and one-transaction-at-a-time serialization.
- A DS402 panel: statusword (0x6041) -> power-drive-system state decode,
  controlword (0x6040) command buttons + enable sequence / quick-stop /
  fault-reset, mode of operation (0x6060/0x6061), target and actual
  velocity/position/torque, and an opt-in live poll.
- Node identity (0x1000/0x1008/0x1018) and NMT controls.
- A generic object-dictionary panel to read/write ANY index:subindex with a
  chosen data type (u8..i32/string/raw hex), for vendor-specific objects.

Because it drives the node over SDO (not cyclic PDOs) it is a commissioning /
bring-up tool; the bus must be in Normal mode so the bridge ACKs the node.

The apps index auto-lists it from its <title>/<meta description>; canopen.rst
links it next to the CAN console. Verified: node --check clean, and a runtime
test of the SDO expedited/segmented up/download codec + DS402 state decode
against a mock CANopen server (13/13 round-trips).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(canopen): escape invalid < in ds402_panel meta description

The <meta name="description"> content had a literal "USB<->CAN"; an unescaped
< makes the tag invalid HTML. Reword to "USB-to-CAN" (same fix as can_console).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(canopen): DS402 panel review - SDO channel cleanup, toggle check, poll re-entrancy

Address the review on the DS402 web app:

- Disconnect / link-loss now abort any outstanding SDO transaction: new
  abortSdo() clears the waiter timer, rejects its pending Promise, and resets
  sdoTxnActive. Without this a link drop mid-SDO left the channel wedged ("SDO
  channel busy" / "another SDO transaction in progress") after reconnect and a
  Promise pending until timeout. Called from disconnect() and onLinkLost().
- Segmented SDO download now validates the server echoed the expected toggle bit
  (like the upload path) and throws on mismatch, so a desynced channel cannot
  silently corrupt a write.
- refreshDs402() is guarded by a refreshInFlight flag around the whole multi-read
  sequence: sdoTxnActive is released between individual reads, so a poll tick or
  manual refresh could otherwise start a second refresh mid-sequence and
  interleave reads. The manual button no longer needs its own sdoTxnActive check.
- Clarified the NMT comment: the UI always targets the selected node (1-127,
  shared with the SDO client); it does not expose CANopen node-0 broadcast.

Verified: node --check clean, and the SDO codec round-trip test still passes
(segmented upload + download with the new toggle check).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(canopen): DS402 panel review - run-state buttons + gated bus-frame logging

- setStatusLine() now reflects the bus run state in the controls (only while
  connected): Apply + Start are disabled while the bus is running (SET_CONFIG is
  rejected by the firmware then) and Stop is disabled while stopped - matching the
  CAN bridge console, so Apply is not clickable with its "only while stopped"
  tooltip during a run.
- onIncoming() logged every non-SDO CAN frame to the DOM unconditionally; on a
  live CANopen bus (heartbeats / PDOs / EMCY) that floods the DOM and freezes the
  UI. Gate it behind a new "log bus frames" toggle (default off), mirroring the
  SDO-frame toggle.
- Update the can_console.html references to can_bridge_console.html (renamed on
  main in #753).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(canopen): DS402 panel review round 3 - SDO robustness + real enable sequence

Address the SDO/DS402 correctness review on the DS402 web app:

- SDO response matching: sdoDeliver() now requires an EXACT 8-byte standard
  (non-RTR, non-extended) frame and, for phases whose response echoes the
  multiplexer (expedited/initiate/abort), that index:subindex matches the pending
  transfer. A late response for an earlier index can no longer complete the next
  transaction with stale data / a false ack, and stray bus traffic on the
  response COB-ID is no longer zero-padded into a fake response. Segment phases
  carry no multiplexer and stay matched by phase + toggle.
- Node snapshot: withSdo() captures the node id ONCE and threads it through the
  whole transaction, so a node-field change mid segmented transfer cannot send
  the initiate and later segments to different servers.
- Segmented upload rejects a short transfer (declared size > delivered bytes)
  instead of returning truncated data as a successful read.
- Scalar writes are range-validated before encoding (u8=-1 / i16=65535 / oversized
  32-bit no longer silently wrap); scalar reads reject an undersized object
  instead of zero-extending it into a valid-looking value. Zero-length writes are
  rejected (no valid SDO expedited encoding). Target/controlword handlers only
  log the value as sent when the write actually succeeded.
- The "Enable operation" button now performs the real state-aware enable sequence
  (Shutdown -> Switch On -> Enable Operation, with fault reset and quick-stop
  exit), polling the statusword between transitions - it no longer just writes
  0x000F, which fails from Switch-on-disabled. Relabeled "Enable sequence".

Verified: node --check clean + a runtime test of the range/undersized/truncation/
empty-write/segmented-codec logic (15/15).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(canopen): DS402 panel review round 4 - node pinning, cancellation, address bounds

Address the remaining SDO/DS402 robustness review:

- Node pinning across multi-transaction actions: withSdo() accepts an explicit
  node (default = the field value). The enable sequence and the identity read now
  snapshot the node ONCE and pass it to every readObject/sdoDownload, so editing
  the node field mid-action can no longer read state from one drive and command
  another. The enable sequence also locks the node field while it runs.
- Enable sequence is cancellable: Quick stop sets a cancel flag (and waits for the
  sequence to release the SDO channel) before issuing quick stop, checked after
  every awaited SDO op - so quick stop can no longer be undone by the sequence
  driving the state machine back to Operation enabled.
- Fault reset performs both controlword writes (clear -> 0x80) DIRECTLY instead of
  via sendControlword, whose trailing refreshDs402() would grab the SDO channel
  and fail the second write, so bit 7 now gets its rising edge.
- Object address validation: sdoUpload/sdoDownload reject index outside
  0x0000..0xFFFF and subindex outside 0..255 before packing the low bits, so a
  typo like hex 16040 (0x16040) can no longer be silently sent as 0x6040.
- Segmented uploads are bounded (SDO_MAX_TRANSFER = 64 KiB) for both a declared
  oversized total and a node that never sets the last-segment bit; on overflow we
  send a client SDO abort and fail, so a faulty node cannot grow browser memory.
- Serial read loop treats a clean EOF while still reading as a link failure (not a
  silent normal return), so a port that closes under us tears the UI down instead
  of leaving it "Connected". Normal cancellation during close() is unaffected.
- Mode-of-operation set logs success only inside the successful write path (no
  more success message after an SDO abort/timeout/busy error).

Verified: node --check clean + runtime tests of the address-validation and
upload-bound logic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(canopen): DS402 panel self-review - serialize SDO via a queue + related fixes

A focused self-review (plus an independent adversarial pass) surfaced one real bug
and several hardening items:

- HIGH: Quick stop could be silently dropped. The enable sequence's finally ran
  refreshDs402() which re-acquired the SDO channel exactly as the cancelled
  sequence handed control back to Quick stop, so the quick-stop write failed with
  "another SDO transaction in progress" and was never sent. Root cause: the SDO
  channel failed fast on contention. Replace the fail-fast mutex with a QUEUE
  (withSdo enqueues; pumpSdoQueue runs one transaction at a time in FIFO order),
  so a poll tick, the enable sequence and manual commands serialize instead of the
  loser being dropped. abortSdo() now also drains the queue on disconnect.
  Additionally the enable sequence skips its trailing refresh when cancelled, so
  the quick-stop write runs immediately.
- Manual commands issued during a live-poll burst (or a trailing refresh) are no
  longer dropped with a "busy" error - they queue and run in order.
- The enable sequence now locks the competing command controls (controlword /
  mode / target / OD-write / NMT / node field) while it runs; Quick stop stays
  enabled (it cancels the sequence) and reads stay enabled.
- currentNode() rejects an out-of-range node id (1..127) instead of silently
  clamping to 1, so a command / identity read can no longer target a different
  drive than the field shows. The enable-sequence and identity handlers surface
  the error.
- disconnect()/onLinkLost() set the sequence cancel flag (matching the comment)
  and the sequence's finally no longer refreshes after a disconnect (was spamming
  "not connected").
- OD read shows scalar hex at the object's own width (i16 -1 -> 0xffff, not
  0xffffffff). Connecting with Live poll pre-ticked now actually starts polling.

Verified: node --check clean + a runtime test of the queue (serialization, FIFO
order, abort-drain), currentNode validation, and hex width (13/13).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants