Skip to content

fix: Make the GPIO chip, IRQ backend, and enable pins configurable (#85) - #89

Open
cwill747 wants to merge 4 commits into
mainfrom
fix-85
Open

fix: Make the GPIO chip, IRQ backend, and enable pins configurable (#85)#89
cwill747 wants to merge 4 commits into
mainfrom
fix-85

Conversation

@cwill747

@cwill747 cwill747 commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Fixes #85.

The bug

The 40-pin header sits on /dev/gpiochip0 for CM4, but on /dev/gpiochip15 for CM5 and Pi 5 kernels, where it hangs off the RP1. We hardcoded chip 0 in three places and never passed openhop_core's gpio_chip kwarg, so affected users had no supported way to point at the right chip — the only workaround was a udev symlink.

Changes

Three hardware settings, each with an env override, a Settings > Hardware control, and a passthrough in create_radio():

Setting Env var Effect
gpio_chip MESHCORE_GPIO_CHIP which /dev/gpiochipN to open
use_gpiod_backend MESHCORE_USE_GPIOD_BACKEND polled IRQ edges instead of edge interrupts
en_pins MESHCORE_EN_PINS GPIO pins driven HIGH at init to power the radio

These are deliberately not in HARDWARE_PRESETS: the chip number and IRQ backend are properties of the SoC and kernel, not of the radio board, so picking a board preset must not reset them. There's a test pinning that.

Diagnostics

So the next reporter doesn't have to guess:

  • doctor reports the configured chip and lists what the host actually has, instead of asserting /dev/gpiochip0:
    [FAIL] gpiochip: Configured GPIO chip /dev/gpiochip0 not found — available: 11, 12, 13, 14, 15. Set it in Settings > Hardware or via MESHCORE_GPIO_CHIP (CM5/Pi 5 kernels usually put the 40-pin header on 15, not 0).
    
  • Pre-flight gains a GPIO_CHIP conflict naming the available chips. A missing chip short-circuits the pin probes, which would otherwise emit one redundant conflict per pin for the same root cause.
  • Pin probes use the configured chip, and now include the configured enable pins.

Note on use_gpiod_backend

It does not actually select libgpiod while python-periphery is installed. In GPIOPinManager.__init__ the GpiodGPIO class and the globals()["GPIO"] = GpiodGPIO swap are nested inside the if backend == "auto" branch, so passing backend="gpiod" explicitly never installs the wrapper — and the [hardware] extra always pulls in periphery.

What the flag really changes is edge detection, from kernel edge interrupts to a polling thread. That's a workaround for kernels that reject the edge request and leave the radio connected but deaf, which is plausibly the more useful half here. It's labelled "Poll IRQ" in the UI and documented accordingly. We do not depend on openhop_core's gpiod extra: it would never be imported, and it needs libgpiod headers to build on the Pi.

Dependency migration (first commit)

Upstream renamed both the repo (rightup/pyMC_coreopenhop-dev/openhop_core) and the PyPI distribution (pymc-coreopenhop-core). pymc-core is frozen at 1.0.12; further releases land under the new name, starting at 1.1.1.

Comparing the wheels, the rename is near-mechanical: the module tree is identical apart from three new files, SX1262Radio.__init__ is unchanged, and protocol/identity.py is byte-identical. Internal names renamed alongside the imports (PyMCCoreSessionOpenHopCoreSession, etc.). CHANGELOG entries are left referring to pymc-core, as they were accurate when written.

Also fixes a dead entry in _RADIO_LOGGER_SUBSTRINGS: the "pyMC" substring never matched anything, since the library's module loggers were named pymc_core.* (lowercase) and no logger was literally named pyMC.

Testing

  • 136 passed / 1 skipped (17 new), ruff check + format clean, mypy unchanged at its 85-error baseline.
  • Settings panel rendered under Xvfb: the new row displays and round-trips gpio_chip=15, en_pins=16,17, use_gpiod_backend=True; natural width stays 58px, so no runaway-resize regression.
  • Not yet verified on affected hardware — neither reporter has confirmed a fix. Worth holding for that.

Follow-ups (not in scope here)

  • doctor's spidev check is still hardcoded to /dev/spidev1.0 despite now having bus_id/cs_id in hand — wrong for the waveshare preset, but doesn't affect the Change gpiochip0 to gpiochip15 #85 reporters.
  • GPIOPinManager warnings (including "Could not setup EN pin") still don't reach the radio-error path. Adding it to _RADIO_LOGGER_SUBSTRINGS would surface exactly the failures reporters hit blind, but broadens what becomes a toast.

🤖 Generated with Claude Code

cwill747 and others added 2 commits August 9, 2026 14:15
Upstream renamed both the GitHub repo (rightup/pyMC_core ->
openhop-dev/openhop_core) and the PyPI distribution (pymc-core ->
openhop-core).  pymc-core is frozen at 1.0.12; all further releases
land under the new name, starting at 1.1.1.

The rename is near-mechanical: comparing the 1.0.12 and 1.1.1 wheels,
the module tree is identical apart from three new files, SX1262Radio
has an unchanged __init__ signature, and protocol/identity.py is
byte-identical.  The [hardware] extra survives the rename.

Renamed alongside the imports:
  - PyMCCoreSession     -> OpenHopCoreSession
  - MockPyMCCoreSession -> MockOpenHopCoreSession
  - import_pymc_core    -> import_openhop_core
  - require_pymc        -> require_openhop

Also fixes a dead entry in _RADIO_LOGGER_SUBSTRINGS: the "pyMC"
substring never matched anything, since the library's module loggers
were named "pymc_core.*" (lowercase) and no logger was literally named
"pyMC".  It is now "openhop_core", which does match.

CHANGELOG entries are left referring to pymc-core, as they were
accurate when written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 40-pin header sits on /dev/gpiochip0 for CM4, but on /dev/gpiochip15
for CM5 and Pi 5 kernels, where it hangs off the RP1.  We hardcoded chip
0 in three places and never passed openhop_core's gpio_chip kwarg, so
affected users had no supported way to point at the right chip — the
only workaround was a udev symlink.

Adds three hardware settings, each with an env override, a Settings >
Hardware control, and a passthrough in create_radio():

  gpio_chip         MESHCORE_GPIO_CHIP          /dev/gpiochipN
  use_gpiod_backend MESHCORE_USE_GPIOD_BACKEND  polled IRQ edges
  en_pins           MESHCORE_EN_PINS            radio power-enable pins

These are deliberately *not* part of HARDWARE_PRESETS: the chip number
and IRQ backend are properties of the SoC and kernel, not of the radio
board, so picking a board preset must not reset them.

Note use_gpiod_backend does not actually select libgpiod while
python-periphery is installed — openhop_core only swaps in its libgpiod
wrapper when periphery is absent, and its [hardware] extra always
installs periphery.  What the flag really changes is edge detection,
from kernel edge interrupts to a polling thread, which is a workaround
for kernels that reject the edge request and leave the radio connected
but deaf.  It is documented and labelled ("Poll IRQ") accordingly, and
we do not depend on the gpiod extra, which would never be imported and
needs libgpiod headers to build on the Pi.

Diagnostics, so the next reporter does not have to guess:
  - doctor reports the *configured* chip and lists the chips the host
    actually has, instead of asserting /dev/gpiochip0.
  - pre-flight adds a GPIO_CHIP conflict naming the available chips.
    A missing chip short-circuits the pin probes, which would otherwise
    emit one redundant conflict per pin for the same root cause.
  - conflicts probe pins against the configured chip, and now include
    the configured enable pins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2bd420e0b0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +89 to +91
hardware = runtime_config_from_settings(settings).hardware
if hardware is not None:
return hardware

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor the GPIO environment override in doctor

When MESHCORE_GPIO_CHIP=15 meshcore-console doctor is run on a host with an accessible settings database, SettingsStore.load() normally succeeds and this path returns the persisted/default chip (usually 0), so the environment fallback is never reached. Doctor therefore checks /dev/gpiochip0 and may report a failure even though the radio CLI uses load_runtime_config() and will use chip 15; apply the environment override before returning the persisted configuration.

AGENTS.md reference: AGENTS.md:L160-L162

Useful? React with 👍 / 👎.

cwill747 and others added 2 commits August 11, 2026 19:43
The GTK app built its radio configuration from the settings database only,
so MESHCORE_GPIO_CHIP did nothing there. `doctor` had the same gap: it read
the persisted chip and ignored the variable. A tester on CM5 hardware could
therefore not point the app at /dev/gpiochip15 without the udev symlink.

- Add one `_HARDWARE_ENV_OVERRIDES` table in `meshcore/config.py`. The CLI,
  the GTK app and `doctor` now apply it through
  `runtime_config_from_settings()`, so all three agree on the values that
  reach the radio. The environment wins, which keeps a broken saved
  configuration recoverable from the command line.
- Say in Settings > Hardware which variables are set, because an edit there
  cannot beat them.
- Report the source of the chip number in `doctor`.

The "Settings" button on the conflict screen did nothing. `navigate_to`
switches the inner page stack, but the conflict screen sits in the outer
content stack, so the page changed behind a screen the user never left.
The button now leaves that stack first.

Add the "uConsole HG AIOv2" board preset, which is the uConsole pinout plus
LoRa enable pin 27. Every preset now states `en_pins`, so a switch between
boards clears a stale pin. `gpio_chip` stays out of the presets: it follows
the kernel, not the board.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#85)

Driving the GTK app under Xvfb found two faults that code inspection missed.

`navigate_to("settings")` switched the page and then deactivated the nav
buttons. Deactivating the last active button makes `_on_nav_button_toggled`
switch it back on, which switches the page back with it, so the settings
page never appeared. Update the buttons first, as `_on_settings_clicked`
already does. This is what made the conflict screen's "Settings" button look
dead: the outer stack fix alone landed the user back on the previous page.

`_on_hw_preset_changed` wrote every preset field back to its entry except
`en_pins`. `_collect_settings` reads the entries, so the AIOv2 preset looked
right but saved an empty enable pin.

Co-Authored-By: Claude Opus 5 <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.

Change gpiochip0 to gpiochip15

1 participant