Skip to content

Comments

Add hardware button support (part 1)#532

Open
v-murygin wants to merge 6 commits intofatihak:mainfrom
v-murygin:pr/hardware-buttons
Open

Add hardware button support (part 1)#532
v-murygin wants to merge 6 commits intofatihak:mainfrom
v-murygin:pr/hardware-buttons

Conversation

@v-murygin
Copy link
Contributor

@v-murygin v-murygin commented Jan 19, 2026

Note: This PR is part 1 of the split from #512 (as requested). Contains hardware button support only. Part 2 (UI improvements & API keys) will follow separately.

This PR adds hardware button support for e-ink displays with GPIO controls.

Features

🔘 Hardware Button Support

  • Physical GPIO button handling for Raspberry Pi (buttons A, B, C, D)
  • Virtual button controls in web UI with visual feedback
  • Support for short press, double press, and long press actions
  • Configurable button actions via Settings → Button Actions
  • Enable/disable toggle in Settings → Hardware Settings
  • Configurable GPIO pins (default: A=5, B=6, C=16, D=24 for Inky Impression)

Default button mapping:

Button Short Press Long Press
A Refresh display -
B Plugin action Previous plugin
C Plugin action Next plugin
D - Shutdown

Settings Improvements

  • Collapsible sections for better organization
  • Hardware Settings: GPIO pin configuration, button visibility toggle
  • Button Actions: configure actions for each button/press type

Plugin Button API

Plugins can implement on_button_press() to handle button events:

def on_button_press(self, button_id, press_type, device_config) -> bool:
    if button_id == ButtonID.C and press_type == PressType.SHORT:
        self.next_item()
        return True  # Handled, triggers refresh
    return False  # Falls through to configured action

Photo Frame Plugin

Test plugin demonstrating button navigation (B/C for prev/next photo).

Technical Details

New Module: src/buttons/

  • abstract_button_handler.py - Base class and enums
  • button_manager.py - Event routing and action execution
  • gpio_button_handler.py - GPIO implementation using gpiozero
  • mock_button_handler.py - Development/testing stub

Dependencies

  • gpiozero>=2.0.1
  • lgpio>=0.2.2.0

📸 UI Screenshots


Web UI Settings
Mobile View Settings

Copy link
Owner

@fatihak fatihak left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! This is a really nice implementation overall and very close to how I was thinking about adding button support, especially with the settings flow and GPIO handling.

A few items of feedback:

  • Plugin button API: I don’t think this is really necessary. For most cases (for example, “next” image in an album or library), a refresh already achieves the same result. Exposing button handling inside plugins will likely lead to requests for plugin-specific button settings which adds complexity when defining a separate plugin instance with different settings is the expected use case.
  • Button actions: I think the most useful/requested behavior will be mapping buttons to show a specific plugin. For example, button A shows a calendar, button B shows a clock, etc. This doesn’t need to be added as part of this PR since it should be straightforward to layer on later with the existing structure.
  • Added plugins: I’m guessing the added plugins were to demonstrate the button actions/testing but they can be removed. You can add them as third plugin plugins which were recently added: https://github.com/fatihak/InkyPi/wiki
  • Concurrent button actions: Right now, pressing a button multiple times with some delay can trigger multiple actions before the first one finishes. I think we should prevent new button actions from executing while another is in progress. Otherwise this can lead to race conditions especially with resource-heavy plugins like refreshing weather or calendar plugins at the same time.

status = "enabled" if self._auto_cycle_enabled else "disabled"
logger.info(f"Auto-cycle {status}")

def _show_info(self):
Copy link
Owner

Choose a reason for hiding this comment

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

This action doesn't update the screen for me, likely bc it instantiates a new DisplayManager instance. I dont think we should be instantiating a new display manager here but instead pass it into the button manager in inkypi.py


return take_screenshot_html(rendered_html, dimensions)

def on_button_press(self, button_id, press_type, device_config) -> bool:
Copy link
Owner

Choose a reason for hiding this comment

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

I don't think this feature is really necessary for custom button actions on plugins. The "next" image for an album/ library can already be achieved by refreshing that plugin. People will likely ask to switch between settings for a specific plugin, which will add additional complexity to plugins when it should just be defined as a separate plugin instance with a different setting.

}
}

function startStatusPolling() {
Copy link
Owner

Choose a reason for hiding this comment

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

The status polling only starts when using the buttons in the home page. I think we can run this all the time so it works with regular refreshes and hardware button actions as well.

@v-murygin v-murygin force-pushed the pr/hardware-buttons branch from 7e1e687 to fc55288 Compare February 6, 2026 21:07
@v-murygin
Copy link
Contributor Author

v-murygin commented Feb 6, 2026

@fatihak Thanks for the detailed feedback! I've addressed almost all points in the latest commits.

Re: Plugin button API - I respectfully disagree here. The API solves a problem that refresh alone can't handle.
Example (Photo Frame plugin): User has 10 photos in one instance, buttons B/C navigate between them. Simple refresh won't work - it would show the same photo since there's no state change. Creating 10 separate plugin instances for 10 photos would be poor UX.
The API is simple (plugin returns True if handled, False to fall through), opt-in, and enables interactive plugins like galleries and slideshows without forcing multiple instances.

Note: I've also added concurrent action protection at both client and server levels to prevent race conditions when buttons are pressed rapidly.
PS: I tweaked the button UI - users can now choose where to display buttons on the home page: bottom, top, left, or right side.

@RobinWts
Copy link

RobinWts commented Feb 11, 2026

@v-murygin is absolutely right here. If you expect people to contribute by building plugins, you need APIs, regs, hooks, and callbacks for them to interact with as much as possible. Of course, it adds complexity, but it does so in a controlled way.
Expose more functionality to the plugins, and you will probably be surprised how people use that. If you don’t, this project will be dependent on core integration, which is inherently slow. If you want plugins, plugins, plugins (as stated somewhere), you need to grant them the possibilities to do stuff…

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.

3 participants