Skip to content

macOS: enable CH341 LoRa-hardware path (fix serial truncation, document setup)#10320

Merged
thebentern merged 2 commits into
developfrom
macos-ch341-serial-fix
Apr 28, 2026
Merged

macOS: enable CH341 LoRa-hardware path (fix serial truncation, document setup)#10320
thebentern merged 2 commits into
developfrom
macos-ch341-serial-fix

Conversation

@thebentern

Copy link
Copy Markdown
Contributor

Summary

Verified on Apple Silicon with a CH341A USB-SPI bridge (VID 0x1A86, PID 0x5512) wired to an SX1262 Meshstick that the existing pine64/libch341-spi-userspace lib_dep works on macOS as-is — Apple's bundled CH34x driver only matches the CH340 UART variant (PID 0x7523), so the CH341A's interface 0 is left unclaimed and libusb opens / configures / claims it directly via IOUSBHostInterface.

End-to-end verification on darwin/arm64:

[libusb_claim_interface] interface 0
[darwin_claim_interface] no interface found; setting configuration: 1
[get_endpoints] interface: 0 pipe 1: dir: 1 number: 2
[get_endpoints] interface: 0 pipe 2: dir: 0 number: 2
[get_endpoints] interface: 0 pipe 3: dir: 1 number: 1
[darwin_claim_interface] interface opened
…
CH341 Serial 10000002
CH341 Product MESHSTICK aabbccddeeff
Deriving MAC address from Serial and Product String
MAC ADDRESS: F2:95:90:7A:0A:0D
…
INFO  | sx1262 init success
INFO  | API server listen on TCP port 4403
…
[ServerAPI] Received text msg from=0x0, id=0x89d853d1, msg=macos verified 2
[ServerAPI] Packet History - insert: Using new slot @uptime 12.986s TRACE NEW

meshtastic --host localhost --info returns full node info; --sendtext round-trips through ServerAPI → PacketHistory; runtime --set lora.region US reconfigures the SX1262 over SPI and the radio reflects its state back via [Router] Received routing from=….

No upstream library forks, no PR chain, no additional lib_deps. The existing pine64/libch341-spi-userspace + libusb-1.0 stack does the right thing on macOS already.

Changes

1. src/platform/portduino/PortduinoGlue.cpp:497 — fix serial truncation

Pass sizeof(serial) (= 9) instead of the literal 8 to Ch341Hal::getSerialString(). The function in USBHal.h:61-68 treats len as buffer size and reserves one slot for the null terminator:

size_t bytesCopied = (len - 1) < 8 ? (len - 1) : 8;

So calling with len=8 produced a 7-char serial ("1000000" instead of "10000002"), which then failed the strlen(serial) == 8 check at line 502 and skipped the auto-MAC derivation from serial + product string.

On Linux this was masked by the BlueZ HCI MAC fallback in getMacAddr() (PortduinoGlue.cpp:139-157) — that path runs when mac_address is empty. On macOS the BlueZ path is __linux__-guarded, so the serial-derivation path is the only MAC source for CH341 boards without an explicit MACAddress: in config.yaml. Truncation left mac_address empty and the daemon exited with *** Blank MAC Address not allowed!.

2. variants/native/portduino/platformio.ini — document macOS hardware support

Expanded the [env:native-macos] header comment with a "Real LoRa hardware on macOS" section. Covers:

  • Why no upstream library change is needed (Apple kext targets CH340/UART, not CH341A/SPI; libusb's #ifdef __linux__ skip in libpinedio-usb.c is correct for macOS in this case).
  • How to point meshtasticd at an existing platform-agnostic bin/config.d/lora-*.yaml for CH341 hardware.
  • The auto-MAC-derivation contract (now working with the fix above).
  • Diagnostic recipes for the failure mode where a third-party WCH CH34xVCPDriver would claim interface 0 (ioreg -p IOUSB -l -w 0 | grep -B2 -A30 0x5512, LIBUSB_DEBUG=4 meshtasticd, sudo kmutil unload -b <bundleID>).

Test plan

  • pio run -e native-macos builds clean on Apple Silicon (arm64)
  • No regression on Linux: bytesCopied = (len - 1) < 8 ? (len - 1) : 8 with len=9 still gives bytesCopied=8, identical behavior to the previous code path with len=8 and the intended bytesCopied semantics. Linux CI's BlueZ fallback was the actual MAC source so neither path observed the truncation in the field.
  • meshtasticd boots against bin/config.d/lora-meshstick-1262.yaml, derives MAC, initializes SX1262
  • TCP API serves meshtastic --info and --sendtext round-trips through ServerAPI + PacketHistory
  • Runtime --set lora.region US reconfigures SX1262 and a second --sendtext is accepted (no lora tx disabled warning)

🤖 Generated with Claude Code

…ent setup

Verified on Apple Silicon with a CH341A USB-SPI bridge (VID 0x1A86,
PID 0x5512) wired to an SX1262 (Meshstick variant) that the existing
`pine64/libch341-spi-userspace` lib_dep works on macOS as-is — Apple's
bundled CH34x driver only matches the CH340 *UART* variant
(PID 0x7523), so the CH341A's interface 0 is left unclaimed and
libusb opens / configures / claims it directly via IOUSBHostInterface.
End-to-end test: meshtasticd boots, libusb claim succeeds, SX1262 init
returns 0, TCP API serves the meshtastic CLI's --info / --sendtext flow.

Two changes:

1. **`PortduinoGlue.cpp:497`**: pass `sizeof(serial)` (= 9) instead of
   the literal `8` to `Ch341Hal::getSerialString()`. The function in
   `USBHal.h:61-68` treats `len` as buffer size and reserves one slot
   for the null terminator (`bytesCopied = (len - 1) < 8 ? (len - 1) : 8`),
   so passing 8 produced a 7-char serial — which then broke the
   `strlen(serial) == 8` check at line 502, skipping the auto-MAC
   derivation from serial + product string. On Linux this was masked
   by the BlueZ HCI MAC fallback in `getMacAddr()` at lines 139-157,
   but on macOS that fallback is `__linux__`-guarded so the serial path
   is mandatory and the truncation left `mac_address` empty, causing
   the daemon to exit with `*** Blank MAC Address not allowed!`.

2. **`variants/native/portduino/platformio.ini`**: expand the
   `[env:native-macos]` comment block with a "Real LoRa hardware on
   macOS" section. Documents:
   - Why no upstream library change is needed (Apple kext targets
     CH340/UART, not CH341A/SPI; libusb's `#ifdef __linux__` skip is
     correct for macOS in this case).
   - How to point `meshtasticd` at an existing platform-agnostic
     `bin/config.d/lora-*.yaml` for CH341 hardware.
   - The auto-MAC-derivation contract (now working with this fix).
   - `ioreg` and `LIBUSB_DEBUG=4` diagnostic recipes for the failure
     mode where a third-party WCH `CH34xVCPDriver` *would* claim
     interface 0 (`kmutil unload -b <bundleID>` workaround).

No upstream library forks, no PR chain, no additional lib_deps —
the existing `pine64/libch341-spi-userspace` + libusb-1.0 stack does
the right thing on macOS already.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added needs-review Needs human review bugfix Pull request that fixes bugs labels Apr 27, 2026
@thebentern thebentern requested a review from Copilot April 27, 2026 15:12

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.

Pull request overview

Enables/clarifies running Portduino (native-macos) against real LoRa hardware via a CH341 USB-SPI bridge on macOS by fixing CH341 serial-string truncation (which prevented MAC derivation) and documenting the setup/diagnostics path in the Portduino PlatformIO environment.

Changes:

  • Fix CH341 serial retrieval by passing the full buffer size to Ch341Hal::getSerialString() so 8-char serials are not truncated.
  • Document macOS CH341 real-hardware usage and troubleshooting steps in the native-macos PlatformIO environment comments.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
variants/native/portduino/platformio.ini Adds macOS-specific documentation for CH341 real LoRa hardware usage and debugging.
src/platform/portduino/PortduinoGlue.cpp Fixes serial-string truncation impacting CH341-based MAC auto-derivation on macOS.

Comment thread src/platform/portduino/PortduinoGlue.cpp Outdated
Comment thread variants/native/portduino/platformio.ini
@vidplace7 vidplace7 added enhancement New feature or request and removed bugfix Pull request that fixes bugs labels Apr 27, 2026
@vidplace7 vidplace7 requested review from vidplace7 April 27, 2026 18:06
@thebentern thebentern requested a review from jp-bennett April 27, 2026 18:36
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@thebentern thebentern merged commit 6c7ffa1 into develop Apr 28, 2026
78 checks passed
@thebentern thebentern deleted the macos-ch341-serial-fix branch April 28, 2026 13:31
thebentern added a commit that referenced this pull request Apr 28, 2026
…nt setup) (#10320)

* macOS: enable CH341 LoRa-hardware path — fix serial truncation, document setup

Verified on Apple Silicon with a CH341A USB-SPI bridge (VID 0x1A86,
PID 0x5512) wired to an SX1262 (Meshstick variant) that the existing
`pine64/libch341-spi-userspace` lib_dep works on macOS as-is — Apple's
bundled CH34x driver only matches the CH340 *UART* variant
(PID 0x7523), so the CH341A's interface 0 is left unclaimed and
libusb opens / configures / claims it directly via IOUSBHostInterface.
End-to-end test: meshtasticd boots, libusb claim succeeds, SX1262 init
returns 0, TCP API serves the meshtastic CLI's --info / --sendtext flow.

Two changes:

1. **`PortduinoGlue.cpp:497`**: pass `sizeof(serial)` (= 9) instead of
   the literal `8` to `Ch341Hal::getSerialString()`. The function in
   `USBHal.h:61-68` treats `len` as buffer size and reserves one slot
   for the null terminator (`bytesCopied = (len - 1) < 8 ? (len - 1) : 8`),
   so passing 8 produced a 7-char serial — which then broke the
   `strlen(serial) == 8` check at line 502, skipping the auto-MAC
   derivation from serial + product string. On Linux this was masked
   by the BlueZ HCI MAC fallback in `getMacAddr()` at lines 139-157,
   but on macOS that fallback is `__linux__`-guarded so the serial path
   is mandatory and the truncation left `mac_address` empty, causing
   the daemon to exit with `*** Blank MAC Address not allowed!`.

2. **`variants/native/portduino/platformio.ini`**: expand the
   `[env:native-macos]` comment block with a "Real LoRa hardware on
   macOS" section. Documents:
   - Why no upstream library change is needed (Apple kext targets
     CH340/UART, not CH341A/SPI; libusb's `#ifdef __linux__` skip is
     correct for macOS in this case).
   - How to point `meshtasticd` at an existing platform-agnostic
     `bin/config.d/lora-*.yaml` for CH341 hardware.
   - The auto-MAC-derivation contract (now working with this fix).
   - `ioreg` and `LIBUSB_DEBUG=4` diagnostic recipes for the failure
     mode where a third-party WCH `CH34xVCPDriver` *would* claim
     interface 0 (`kmutil unload -b <bundleID>` workaround).

No upstream library forks, no PR chain, no additional lib_deps —
the existing `pine64/libch341-spi-userspace` + libusb-1.0 stack does
the right thing on macOS already.

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

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
mariotti pushed a commit to mariotti/firmware that referenced this pull request May 6, 2026
…nt setup) (meshtastic#10320)

* macOS: enable CH341 LoRa-hardware path — fix serial truncation, document setup

Verified on Apple Silicon with a CH341A USB-SPI bridge (VID 0x1A86,
PID 0x5512) wired to an SX1262 (Meshstick variant) that the existing
`pine64/libch341-spi-userspace` lib_dep works on macOS as-is — Apple's
bundled CH34x driver only matches the CH340 *UART* variant
(PID 0x7523), so the CH341A's interface 0 is left unclaimed and
libusb opens / configures / claims it directly via IOUSBHostInterface.
End-to-end test: meshtasticd boots, libusb claim succeeds, SX1262 init
returns 0, TCP API serves the meshtastic CLI's --info / --sendtext flow.

Two changes:

1. **`PortduinoGlue.cpp:497`**: pass `sizeof(serial)` (= 9) instead of
   the literal `8` to `Ch341Hal::getSerialString()`. The function in
   `USBHal.h:61-68` treats `len` as buffer size and reserves one slot
   for the null terminator (`bytesCopied = (len - 1) < 8 ? (len - 1) : 8`),
   so passing 8 produced a 7-char serial — which then broke the
   `strlen(serial) == 8` check at line 502, skipping the auto-MAC
   derivation from serial + product string. On Linux this was masked
   by the BlueZ HCI MAC fallback in `getMacAddr()` at lines 139-157,
   but on macOS that fallback is `__linux__`-guarded so the serial path
   is mandatory and the truncation left `mac_address` empty, causing
   the daemon to exit with `*** Blank MAC Address not allowed!`.

2. **`variants/native/portduino/platformio.ini`**: expand the
   `[env:native-macos]` comment block with a "Real LoRa hardware on
   macOS" section. Documents:
   - Why no upstream library change is needed (Apple kext targets
     CH340/UART, not CH341A/SPI; libusb's `#ifdef __linux__` skip is
     correct for macOS in this case).
   - How to point `meshtasticd` at an existing platform-agnostic
     `bin/config.d/lora-*.yaml` for CH341 hardware.
   - The auto-MAC-derivation contract (now working with this fix).
   - `ioreg` and `LIBUSB_DEBUG=4` diagnostic recipes for the failure
     mode where a third-party WCH `CH34xVCPDriver` *would* claim
     interface 0 (`kmutil unload -b <bundleID>` workaround).

No upstream library forks, no PR chain, no additional lib_deps —
the existing `pine64/libch341-spi-userspace` + libusb-1.0 stack does
the right thing on macOS already.

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

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Evil8it pushed a commit to Evil8it/ME4TACTNK that referenced this pull request Jun 10, 2026
…nt setup) (meshtastic#10320)

* macOS: enable CH341 LoRa-hardware path — fix serial truncation, document setup

Verified on Apple Silicon with a CH341A USB-SPI bridge (VID 0x1A86,
PID 0x5512) wired to an SX1262 (Meshstick variant) that the existing
`pine64/libch341-spi-userspace` lib_dep works on macOS as-is — Apple's
bundled CH34x driver only matches the CH340 *UART* variant
(PID 0x7523), so the CH341A's interface 0 is left unclaimed and
libusb opens / configures / claims it directly via IOUSBHostInterface.
End-to-end test: meshtasticd boots, libusb claim succeeds, SX1262 init
returns 0, TCP API serves the meshtastic CLI's --info / --sendtext flow.

Two changes:

1. **`PortduinoGlue.cpp:497`**: pass `sizeof(serial)` (= 9) instead of
   the literal `8` to `Ch341Hal::getSerialString()`. The function in
   `USBHal.h:61-68` treats `len` as buffer size and reserves one slot
   for the null terminator (`bytesCopied = (len - 1) < 8 ? (len - 1) : 8`),
   so passing 8 produced a 7-char serial — which then broke the
   `strlen(serial) == 8` check at line 502, skipping the auto-MAC
   derivation from serial + product string. On Linux this was masked
   by the BlueZ HCI MAC fallback in `getMacAddr()` at lines 139-157,
   but on macOS that fallback is `__linux__`-guarded so the serial path
   is mandatory and the truncation left `mac_address` empty, causing
   the daemon to exit with `*** Blank MAC Address not allowed!`.

2. **`variants/native/portduino/platformio.ini`**: expand the
   `[env:native-macos]` comment block with a "Real LoRa hardware on
   macOS" section. Documents:
   - Why no upstream library change is needed (Apple kext targets
     CH340/UART, not CH341A/SPI; libusb's `#ifdef __linux__` skip is
     correct for macOS in this case).
   - How to point `meshtasticd` at an existing platform-agnostic
     `bin/config.d/lora-*.yaml` for CH341 hardware.
   - The auto-MAC-derivation contract (now working with this fix).
   - `ioreg` and `LIBUSB_DEBUG=4` diagnostic recipes for the failure
     mode where a third-party WCH `CH34xVCPDriver` *would* claim
     interface 0 (`kmutil unload -b <bundleID>` workaround).

No upstream library forks, no PR chain, no additional lib_deps —
the existing `pine64/libch341-spi-userspace` + libusb-1.0 stack does
the right thing on macOS already.

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

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request needs-review Needs human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants