SYSTEMD-GPIO(5)
===============

NAME
----
systemd-gpio - run commands when a button on a GPIO pin is pressed

DESCRIPTION
-----------

Use systemd to configure commands to run when a button attached to a GPIO pin is pressed, released, or held.

This is a C++20 implementation that provides the same functionality as the original Python version but with better performance and fewer dependencies.

BUILDING
--------

Requirements:
- C++20 compatible compiler (g++ >= 9 or clang++ >= 10)
- CMake >= 3.20 (recommended) and Make
- Linux system with GPIO support
- libgpiod development library

**Installing Dependencies:**

On Debian/Ubuntu/Raspberry Pi OS::

    sudo apt update
    sudo apt install libgpiod-dev libgpiod2 cmake g++ pandoc

On Fedora::

    sudo dnf install libgpiod-devel cmake gcc-c++ pandoc

On CentOS/RHEL::

    sudo yum install libgpiod-devel cmake gcc-c++ pandoc

**Security Hardening**: This project is built with OpenSSF security hardening by default, including stack protection, FORTIFY_SOURCE, Position Independent Executable (PIE), RELRO, and other security mitigations. Security features are automatically adapted for different architectures (x86_64 gets Control Flow Integrity, ARM64 gets architecture-appropriate protections).

Using CMake (recommended)::

    mkdir build && cd build
    cmake .. -DCMAKE_BUILD_TYPE=Release
    make -j$(nproc)
    sudo make install

Using the build script::

    ./build.sh                    # Build with security hardening
    ./build.sh install            # Build and install
    ./build.sh --debug install    # Debug build with sanitizers
    ./build.sh --no-security      # Build without security hardening

Security Options:
- Security hardening is enabled by default
- Debug builds include Address Sanitizer and Undefined Behavior Sanitizer
- Use ``-DENABLE_SECURITY_HARDENING=OFF`` with CMake to disable hardening

TESTING
-------

This project includes comprehensive tests using the Catch2 framework that can run on any system without GPIO hardware.

**Running Tests:**

Using the build script::

    ./build.sh test                 # Build and run tests
    ./build.sh --debug test         # Debug build with sanitizers and tests
    ./build.sh --no-security test   # Test without security hardening

Using CMake directly::

    mkdir build && cd build
    cmake .. -DCMAKE_BUILD_TYPE=Debug
    make -j$(nproc)
    ctest --verbose

**Test Coverage:**

The test suite covers:

- **GPIO Logic:** All 4 configurations (Active Low/High × NO/NC)
- **Button Events:** Press, release, and hold detection
- **Timing:** Debouncing and hold timing
- **Edge Cases:** Rapid changes, missing callbacks, zero hold times
- **Argument Parsing:** Command line and environment variable handling
- **Mock System:** Complete GPIO hardware simulation

**Test Options:**

- ``-DBUILD_TESTING=OFF`` - Disable tests entirely
- ``--no-tests`` with build script - Skip test compilation
- Tests automatically use software GPIO simulation (no hardware required)

To configure a GPIO pin, create a file called /etc/gpio/*N*, where *N* is an integer pin number following the `gpiozero pin numbering`_ convention. Once you have populated the configuration file, activate the button by running the following command, replacing *N* with the pin number::

    sudo systemctl enable --now gpio@N.service

ENVIRONMENT
-----------

| GPIO_PULL_UP
| GPIO_ACTIVE_STATE
| GPIO_BOUNCE_TIME
| GPIO_HOLD_TIME
| GPIO_HOLD_REPEAT

    Button behavior. These correspond to the same parameters as the original gpiozero.Button implementation.

| GPIO_WHEN_HELD
| GPIO_WHEN_PRESSED
| GPIO_WHEN_RELEASED

    Commands to run when the button is held, pressed, or released, respectively.

FILES
-----

/etc/gpio/*N*

    Configuration file for GPIO pin *N*, where *N* corresponds to the `BCM GPIO pin numbering`_. The file should be in the `systemd EnvironmentFile syntax`_ and may define any of the environment variables listed above.

HISTORY
-------

This project is inspired by https://github.com/ali1234/systemd-gpio and https://github.com/nasa-gcn/systemd-gpio. It was originally implemented in Python using gpiozero_. This version has been rewritten in C++20 for better performance and reduced dependencies while maintaining the same functionality and API. 

GPIO BUTTON CONFIGURATIONS
--------------------------

This implementation supports both **normally open (NO)** and **normally closed (NC)** button configurations through the ``GPIO_PULL_UP`` and ``GPIO_ACTIVE_STATE`` settings.

**Configuration Table:**

+------------------------+-------------+---------------+------------------+------------------------+
| Configuration          | PULL_UP     | ACTIVE_STATE  | Hardware Setup   | Pressed Condition      |
+========================+=============+===============+==================+========================+
| **Active Low (NO)**    | 1 (default) | 0 (default)   | Button to GND    | GPIO reads LOW         |
+------------------------+-------------+---------------+------------------+------------------------+
| **Active Low (NC)**    | 1           | 1             | Button to GND    | GPIO reads HIGH        |
+------------------------+-------------+---------------+------------------+------------------------+
| **Active High (NO)**   | 0           | 0             | Button to 3.3V   | GPIO reads HIGH        |
+------------------------+-------------+---------------+------------------+------------------------+
| **Active High (NC)**   | 0           | 1             | Button to 3.3V   | GPIO reads LOW         |
+------------------------+-------------+---------------+------------------+------------------------+

**Common Configurations:**

**Active Low with Normally Open (NO) - Default and Most Common:**
    - Standard momentary push button
    - One side to GPIO pin, other side to ground
    - Internal pull-up keeps GPIO HIGH when not pressed
    - Pressing button pulls GPIO LOW → triggers "pressed" event

**Active Low with Normally Closed (NC) - Emergency Stops:**
    - Emergency stop or safety button
    - One side to GPIO pin, other side to ground  
    - When button is released (circuit closed): GPIO LOW → not pressed
    - When button is pressed (circuit open): GPIO HIGH → triggers "pressed" event

**Active High with Normally Open (NO) - External Pull-down:**
    - Button connects GPIO to 3.3V when pressed
    - External pull-down resistor keeps GPIO LOW when not pressed
    - Pressing button pulls GPIO HIGH → triggers "pressed" event

**Active High with Normally Closed (NC) - Inverted Logic:**
    - Button disconnects GPIO from 3.3V when pressed
    - External pull-down resistor pulls GPIO LOW when pressed
    - When button is pressed (circuit open): GPIO LOW → triggers "pressed" event

EXAMPLES
--------

**Example 1: Active Low + Normally Open (Default Configuration)**

Standard momentary push button connected between GPIO 23 and ground::

    # /etc/gpio/23 - Active Low, Normally Open
    GPIO_PULL_UP=1          # Use internal pull-up resistor
    GPIO_ACTIVE_STATE=0     # Active when GPIO reads LOW
    GPIO_BOUNCE_TIME=0.05
    GPIO_HOLD_TIME=5
    GPIO_WHEN_PRESSED="wall 'Power button pressed - hold for 5 seconds to shutdown'"
    GPIO_WHEN_HELD=poweroff

**Example 2: Active Low + Normally Closed (Emergency Stop)**

Emergency stop button that is normally closed, opens when pressed::

    # /etc/gpio/18 - Active Low, Normally Closed
    GPIO_PULL_UP=1          # Use internal pull-up resistor
    GPIO_ACTIVE_STATE=1     # Active when GPIO reads HIGH (button open)
    GPIO_BOUNCE_TIME=0.02
    GPIO_WHEN_PRESSED="systemctl stop critical-service && wall 'EMERGENCY STOP ACTIVATED'"
    GPIO_WHEN_RELEASED="wall 'Emergency stop released - service stopped'"

**Example 3: Active High + Normally Open (External Pull-down)**

Button with external pull-down resistor, connects to 3.3V when pressed::

    # /etc/gpio/24 - Active High, Normally Open
    GPIO_PULL_UP=0          # No internal pull-up (using external pull-down)
    GPIO_ACTIVE_STATE=0     # Active when GPIO reads HIGH
    GPIO_BOUNCE_TIME=0.1
    GPIO_WHEN_PRESSED="echo 'LED ON' && echo 1 > /sys/class/gpio/gpio25/value"
    GPIO_WHEN_RELEASED="echo 'LED OFF' && echo 0 > /sys/class/gpio/gpio25/value"

**Example 4: Active High + Normally Closed (Inverted Logic)**

Button with external pull-down, normally connects to 3.3V, opens when pressed::

    # /etc/gpio/19 - Active High, Normally Closed
    GPIO_PULL_UP=0          # No internal pull-up (using external pull-down)
    GPIO_ACTIVE_STATE=1     # Active when GPIO reads LOW (button open)
    GPIO_BOUNCE_TIME=0.05
    GPIO_WHEN_PRESSED="wall 'Safety cover opened!'"
    GPIO_WHEN_RELEASED="wall 'Safety cover closed'"

**Example 5: Multi-function Active Low Button with Hold Repeat**

Power button with different actions for press, hold, and release::

    # /etc/gpio/3 - Active Low, Multi-function
    GPIO_PULL_UP=1
    GPIO_ACTIVE_STATE=0
    GPIO_BOUNCE_TIME=0.05
    GPIO_HOLD_TIME=2
    GPIO_HOLD_REPEAT=1
    GPIO_WHEN_PRESSED="wall 'Power button pressed'"
    GPIO_WHEN_HELD="wall 'Holding power button - release to cancel shutdown'"
    GPIO_WHEN_RELEASED="wall 'Power button released'"

**Example 6: Reset Button (Active Low, Short Press)**

Reset button that only responds to quick presses::

    # /etc/gpio/2 - Active Low, Reset Button
    GPIO_PULL_UP=1
    GPIO_ACTIVE_STATE=0
    GPIO_BOUNCE_TIME=0.02
    GPIO_HOLD_TIME=0.5
    GPIO_WHEN_PRESSED="systemctl restart my-application"
    GPIO_WHEN_HELD="wall 'Hold detected - ignoring reset to prevent accidental restarts'"

To activate any of these configurations, run::

    sudo systemctl enable --now gpio@PIN.service

Replace PIN with your GPIO pin number (e.g., ``gpio@23.service`` for GPIO 23).

TROUBLESHOOTING
---------------

**Button not responding:**
1. Check GPIO pin number is correct using ``pinout`` command
2. Verify button wiring matches your configuration settings
3. Check systemd service status: ``systemctl status gpio@PIN.service``
4. View logs: ``journalctl -u gpio@PIN.service -f``

**Button triggering incorrectly:**
1. Try different ``GPIO_BOUNCE_TIME`` values (0.02 to 0.1 seconds)
2. Verify ``GPIO_PULL_UP`` and ``GPIO_ACTIVE_STATE`` match your hardware:
   
   - **Active Low + Normally Open:** ``GPIO_PULL_UP=1, GPIO_ACTIVE_STATE=0`` (default)
   - **Active Low + Normally Closed:** ``GPIO_PULL_UP=1, GPIO_ACTIVE_STATE=1``
   - **Active High + Normally Open:** ``GPIO_PULL_UP=0, GPIO_ACTIVE_STATE=0``
   - **Active High + Normally Closed:** ``GPIO_PULL_UP=0, GPIO_ACTIVE_STATE=1``

3. Test with simple commands first (e.g., ``echo "pressed" > /tmp/test``)
4. Use ``cat /sys/class/gpio/gpio23/value`` to check raw GPIO state

**Permission issues:**
- Ensure the service has permission to access GPIO and run commands
- Check that target commands exist and are executable

.. _gpiozero: https://github.com/gpiozero/gpiozero
.. _WiringPi: https://web.archive.org/web/20220405225008/http://wiringpi.com/wiringpi-deprecated/
.. _`BCM GPIO pin numbering`: https://pinout.xyz/
.. _`systemd EnvironmentFile syntax`: https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#EnvironmentFile=

SEE ALSO
--------

systemctl(1), systemd.exec(5)
# Changelog improvements
