having loading CarPiHat software issues

XZIVR
Posts: 4
Joined: Tue Jun 16, 2026 6:33 am

Re: having loading CarPiHat software issues

Post by XZIVR »

Alright, fair enough. Well I am not a programmer by any stretch so evaluate and use at your own risk. All I can say is it worked on my setup, which is a Pi 4B, CarPiHat Pro hardware rev 1.1, fresh install of Pi OS Trixie and Hudiy.


The guide I had it generate:
# CarPiHat Pro Graceful Shutdown — Fresh Install Setup Guide

This sets up a systemd service that watches the CarPiHat's ignition-sense
line (BCM12) and shuts the Pi down ~10 seconds after the key is turned
off, then relies on a kernel-level `gpio-poweroff` overlay to drop the
power latch (BCM25) once the Pi finishes halting.

This is independent of Hudiy — it works the same whether Hudiy is
installed yet or not, so you can do this step before or after installing
Hudiy itself.

## Before you start

- SSH into the Pi (or use a terminal on the Pi directly).
- You'll need your CarPiHat board revision to pick the right RTC overlay
in Step 3. Check the silkscreen on the board, or the chip marking next
to the I2C section — V0.3 boards use a **DS3231MZ**, V0.4–V0.6 use a
**DS1307**. If you're not sure, it's fine to guess and revisit later;
getting it wrong won't break the shutdown function, only RTC time-sync.

## Step 1: Confirm your config.txt location

Raspberry Pi OS Bookworm and later moved the boot config file. Check
which one exists on your install:

```
ls /boot/firmware/config.txt 2>/dev/null && echo "Use /boot/firmware/config.txt"
ls /boot/config.txt 2>/dev/null && echo "Use /boot/config.txt"
```

Use whichever path it reports for all the `config.txt` edits below.
I'll refer to it as `CONFIG_PATH` — substitute your actual path.

## Step 2: Enable I2C (only needed if you want RTC time-sync)

```
sudo raspi-config
```

Go to **Interface Options → I2C → Enable**, then exit. This isn't
required for the shutdown function itself — only for the real-time
clock. The script will run fine without it; it just logs a message and
moves on if I2C isn't available.

## Step 3: Add the required overlays to config.txt

Open the file:

```
sudo nano CONFIG_PATH
```

Add these lines at the bottom (pick the RTC line matching your board
revision from "Before you start" — or omit the RTC line entirely if you
don't care about clock sync):

```
# CarPiHat: drop the power latch after the kernel finishes halting
dtoverlay=gpio-poweroff,gpiopin=25,active_low

# CarPiHat: RTC (pick one, matching your board revision)
dtoverlay=i2c-rtc,ds1307
# dtoverlay=i2c-rtc,ds3231
```

Save and exit (Ctrl+O, Enter, Ctrl+X).

## Step 4: Install dependencies

```
sudo apt update
sudo apt install -y python3-gpiozero
```

(Usually preinstalled on full Raspberry Pi OS images, but harmless to
run regardless.)

## Step 5: Create the monitor script

```
sudo tee /usr/local/bin/pi-shutdown.py > /dev/null << 'EOF'
#!/usr/bin/env python3
"""
CarPiHat ignition monitor / graceful shutdown service.

Watches BCM12 (12V switched / ignition sense) and shuts the Pi down
after IGN_LOW_TIME seconds of the ignition line being off. BCM25 holds
the power latch high; the actual power cut happens via the
"dtoverlay=gpio-poweroff,gpiopin=25,active_low" line in config.txt
once the kernel finishes halting.
"""

import sys
from gpiozero import DigitalInputDevice, DigitalOutputDevice
from subprocess import run, CalledProcessError
from time import sleep

IGN_PIN = 12
EN_POWER_PIN = 25
IGN_LOW_TIME = 10


def log(msg):
# systemd captures stdout/stderr into the journal automatically
print(msg, flush=True)


def init_rtc():
"""
Best-effort RTC bring-up. Any failure here must NEVER stop the
ignition monitor from starting, so every exception is caught here
and only logged.
"""
rtc_path = '/sys/class/i2c-adapter/i2c-1/new_device'
try:
with open(rtc_path, 'w') as f:
f.write('ds1307 0x68')
except FileNotFoundError:
log("RTC: I2C bus not available, skipping.")
return
except OSError as e:
log(f"RTC: could not register device ({e}); probably already registered, continuing.")

try:
run(['hwclock', '-s'], check=True)
run(['date'])
except (CalledProcessError, OSError) as e:
log(f"RTC: hwclock sync failed ({e}); continuing without it.")


def main():
log("pi-shutdown: starting up")
init_rtc()

power_latch = DigitalOutputDevice(EN_POWER_PIN, initial_value=True)
ignition = DigitalInputDevice(IGN_PIN, pull_up=None, active_state=True)

log("pi-shutdown: monitoring ignition (currently %s)" % ("ON" if ignition.is_active else "OFF"))

while True:
if ignition.is_active:
sleep(1)
elif not ignition.wait_for_active(timeout=IGN_LOW_TIME):
log(f"pi-shutdown: ignition off for {IGN_LOW_TIME}s, shutting down")
run(['shutdown', '-h', 'now'], check=True)
break
else:
log("pi-shutdown: ignition back on, resuming monitoring")


if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass
except Exception as e:
log(f"pi-shutdown: fatal error: {e}")
sys.exit(1)
EOF
sudo chmod +x /usr/local/bin/pi-shutdown.py
```

Note: if your board's RTC chip is a DS3231 rather than a DS1307, you can
change `'ds1307 0x68'` to `'ds3231 0x68'` in the script above — but since
this manual registration is now non-fatal either way, it's also fine to
leave it as-is and just rely on the config.txt overlay from Step 3 to
bring the RTC up correctly at boot.

## Step 6: Create the systemd service

```
sudo tee /etc/systemd/system/pi-shutdown.service > /dev/null << 'EOF'
[Unit]
Description=Pi Shutdown monitor
After=multi-user.target

[Service]
Type=simple
User=root
ExecStart=/usr/bin/python3 -u /usr/local/bin/pi-shutdown.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
```

## Step 7: Enable and start the service

```
sudo systemctl daemon-reload
sudo systemctl enable pi-shutdown.service
sudo systemctl start pi-shutdown.service
```

## Step 8: Verify it's actually running (don't skip this)

```
sudo systemctl status pi-shutdown.service
journalctl -u pi-shutdown.service -f
```

You should see `pi-shutdown: starting up` followed by
`pi-shutdown: monitoring ignition (currently ON)`. If you see it
restarting every few seconds instead, something is throwing an
exception — the journal output will tell you what.

Leave that `journalctl -f` running, then turn the key off. After about
10 seconds you should see the "shutting down" log line, and the Pi
should begin its halt sequence. Once it finishes, the CarPiHat's "5V"
LED should go dark, confirming the latch actually dropped (this part is
handled entirely by the `gpio-poweroff` overlay from Step 3, not the
script).

## Troubleshooting

**Service keeps restarting in a loop.** Check `journalctl -u
pi-shutdown.service -n 50 --no-pager` for the actual exception. Most
likely candidate is the RTC bring-up failing in a way that's not fully
suppressed — worth confirming I2C is enabled and the RTC overlay in
Step 3 matches your board revision.

**Shutdown triggers but the board never fully powers off (LED stays
lit).** That's not the script's job — double check the
`dtoverlay=gpio-poweroff,gpiopin=25,active_low` line made it into the
correct config.txt path from Step 1, and reboot after adding it (overlay
changes only take effect on boot).

**Nothing happens at all, ever, even in the journal.** Confirm the
service is actually enabled (`systemctl is-enabled pi-shutdown.service`)
and that BCM12 is genuinely your board's ignition-sense pin — older
board revisions occasionally differ; cross-check against the schematic/
pinout image on the CarPiHat GitHub wiki if you're unsure.
Contents of pi-shutdown.py:

Code: Select all

#!/usr/bin/env python3
"""
CarPiHat ignition monitor / graceful shutdown service.

Watches BCM12 (12V switched / ignition sense) and shuts the Pi down
after IGN_LOW_TIME seconds of the ignition line being off. BCM25 holds
the power latch high; the actual power cut happens via the
"dtoverlay=gpio-poweroff,gpiopin=25,active_low" line in /boot/config.txt
once the kernel finishes halting.
"""

import sys
from gpiozero import DigitalInputDevice, DigitalOutputDevice
from subprocess import run, CalledProcessError
from time import sleep

IGN_PIN = 12
EN_POWER_PIN = 25
IGN_LOW_TIME = 10


def log(msg):
    # systemd captures stdout/stderr into the journal automatically
    print(msg, flush=True)


def init_rtc():
    """
    Best-effort RTC bring-up. Any failure here must NEVER stop the
    ignition monitor from starting, so every exception is caught here
    and only logged.
    """
    rtc_path = '/sys/class/i2c-adapter/i2c-1/new_device'
    try:
        with open(rtc_path, 'w') as f:
            f.write('ds1307 0x68')
    except FileNotFoundError:
        log("RTC: I2C bus not available, skipping.")
        return
    except OSError as e:
        log(f"RTC: could not register device ({e}); probably already registered, continuing.")

    try:
        run(['hwclock', '-s'], check=True)
        run(['date'])
    except (CalledProcessError, OSError) as e:
        log(f"RTC: hwclock sync failed ({e}); continuing without it.")


def main():
    log("pi-shutdown: starting up")
    init_rtc()

    power_latch = DigitalOutputDevice(EN_POWER_PIN, initial_value=True)
    ignition = DigitalInputDevice(IGN_PIN, pull_up=None, active_state=True)

    log("pi-shutdown: monitoring ignition (currently %s)" % ("ON" if ignition.is_active else "OFF"))

    while True:
        if ignition.is_active:
            sleep(1)
        elif not ignition.wait_for_active(timeout=IGN_LOW_TIME):
            log(f"pi-shutdown: ignition off for {IGN_LOW_TIME}s, shutting down")
            run(['shutdown', '-h', 'now'], check=True)
            break
        else:
            log("pi-shutdown: ignition back on, resuming monitoring")


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        pass
    except Exception as e:
        log(f"pi-shutdown: fatal error: {e}")
        sys.exit(1)
contents of pi.shutdown.service:

Code: Select all

[Unit]
Description=Pi Shutdown monitor
After=multi-user.target

[Service]
Type=simple
User=root
ExecStart=/usr/bin/python3 -u /usr/local/bin/pi-shutdown.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Post Reply