Auto power-on the SIM868 module via its PWRKEY GPIO line

Root-caused a "GPS not working" report to the SIM868 module chip being
fully powered off (the HAT's 5V rail can be live while the chip itself
stays off until PWRKEY is triggered) - confirmed by getting zero AT
response at any baud rate while the app's GPS thread had the serial
port open normally.

Discovered this specific HAT revision has the "P4" GPIO power-on mod
wire installed (Waveshare FAQ / GSM_PWRKEY.rar script), letting PWRKEY
be triggered via physical pin 7 (BCM GPIO4) instead of a manual button
press. Verified live: pulsing it low for 4s then high brought the
module from zero AT response to responding immediately.

modules/app_gps.py: added _module_responds()/_pulse_pwrkey()/
_ensure_module_powered(), wired into _poll_loop() before the existing
GNSS power-on check. Checks AT first and only pulses PWRKEY if the
module isn't already responding, since PWRKEY is a toggle (pulsing an
already-on module would turn it back off).

Also fixed a related logging gap found while debugging: print()
diagnostics from app_gps.py were invisible in yaris-obd.log for
months because Python fully-buffers stdout when it isn't a TTY.
deploy/yaris-obd.service now sets PYTHONUNBUFFERED=1.

requirements.txt: added RPi.GPIO.
PLAN.md: documented both findings and the Pi deploy gotchas (systemd
unit file changes need a manual copy + daemon-reload; requirements.txt
changes need a manual venv pip install - neither is picked up by a
plain git pull + restart).

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-09 15:14:28 -04:00
co-authored by Copilot
parent e4b0823ec2
commit fc18f71cfe
4 changed files with 102 additions and 4 deletions
+20 -3
View File
@@ -30,8 +30,14 @@ pick up the project without needing prior chat history.
- **The SIM868 module chip itself needs its PWRKEY button pressed and held (~1-2s) to power on**
— this is separate from the board's 5V/PWR LED, which can be lit while the module chip is
fully off. AT commands get zero response if the module chip isn't powered, even with perfect
UART wiring/config. This is a manual step needed on every power cycle unless a P4
GPIO-power-on hardware mod (per Waveshare FAQ) is done — not pursued so far.
UART wiring/config. **Update 2026-08-09: this HAT revision DOES have the "P4" GPIO-power-on
mod wire installed** (per the Waveshare FAQ/`GSM_PWRKEY.rar` script) — confirmed by pulsing
physical header pin 7 (BCM GPIO4) LOW for 4s then HIGH while the module was unresponsive, after
which it immediately answered `AT`. This is now automated in `modules/app_gps.py`
(`_ensure_module_powered()`), which checks `AT` first and only pulses PWRKEY if the module
isn't already responding (PWRKEY is a toggle — pulsing an already-on module would turn it back
off, so this check-first behavior is required). No more manual button-pressing needed after a
Pi power cycle.
- Confirmed AT command baud rate: **115200**.
- GNSS workflow: `AT+CGNSPWR?` to check GNSS power state, `AT+CGNSPWR=1` to power on,
`AT+CGNSINF` to poll for a fix. Response format:
@@ -577,7 +583,18 @@ optimization. Not scoped/designed yet.
`~/app/yaris-obd/yaris-obd.log` (not journald - the systemd unit redirects both streams there),
so use `tail`/`grep` on that file, not `journalctl`, to see application-level prints (upload
progress, OBD/GNSS errors, etc.) - `journalctl -u yaris-obd.service` only shows service
start/stop/crash lifecycle events.
start/stop/crash lifecycle events. **Update 2026-08-09**: plain `print()` calls were silently
invisible in that log for months because Python fully-buffers stdout when it's not a TTY (a
file, in this case) — `logging`-based lines (e.g. the `obd` library's own logger, which uses
stderr) showed up fine, but bare `print()` diagnostics from `app_gps.py` never did. Fixed by
adding `Environment=PYTHONUNBUFFERED=1` to the systemd unit
(`deploy/yaris-obd.service`) — **remember to also copy the updated unit file to
`/etc/systemd/system/yaris-obd.service` and run `sudo systemctl daemon-reload`** on the Pi, a
plain `git pull` + restart does NOT pick up systemd unit file changes (the live
`/etc/systemd/system/` copy is independent of the repo's `deploy/` copy).
- **Pi deploy loop, requirements.txt changes**: after any `requirements.txt` change (e.g. adding
`RPi.GPIO`), also run `~/app/yaris-obd/venv/bin/pip install -r requirements.txt` on the Pi — a
`git pull` alone doesn't install new dependencies into the venv.
- **Pi memory budget**: ~900MB usable RAM, no swap. Any code path that might buffer a whole
dashcam-sized file (hundreds of MB) in memory is unsafe on this hardware - stream, don't buffer
(see Phase 10).
+4
View File
@@ -8,6 +8,10 @@ Type=simple
User=yaris
Group=yaris
WorkingDirectory=/home/yaris/app/yaris-obd
# Unbuffered stdout so print() diagnostics (GPS/GNSS status, etc.) actually reach
# the log file promptly instead of sitting in Python's stdout buffer indefinitely
# when it's redirected to a file instead of a TTY.
Environment=PYTHONUNBUFFERED=1
ExecStart=/home/yaris/app/yaris-obd/venv/bin/python app.py
Restart=on-failure
RestartSec=5
+76
View File
@@ -3,12 +3,28 @@ from threading import Thread, Lock
import serial
import time
try:
import RPi.GPIO as GPIO
except (ImportError, RuntimeError):
# Not running on a Pi (e.g. local dev machine) - PWRKEY auto power-on is
# simply unavailable there; _pulse_pwrkey() below degrades gracefully.
GPIO = None
# SIM868 GNSS/GSM/GPRS HAT is wired to the Pi's GPIO UART (jumper set to "B: Pi-SIM868"),
# exposed as /dev/serial0 once the Pi's onboard Bluetooth has been disabled/freed.
SERIAL_PORT = "/dev/serial0"
BAUDRATE = 115200
POLL_INTERVAL = 2 # seconds between AT+CGNSINF polls
# This HAT revision has the "P4" GPIO power-on mod wire connected to the SIM868's
# PWRKEY line (confirmed 2026-08-09 by pulsing it and seeing the module respond to
# AT afterward) - physical header pin 7 (BCM GPIO4), same pin Waveshare's own
# GSM_PWRKEY.py reference script uses. This lets us auto power-on the module chip
# on every app start instead of requiring someone to press its PWRKEY button by
# hand after every Pi power cycle.
PWRKEY_PIN = 7 # BOARD numbering
PWRKEY_HOLD_SECONDS = 4 # matches Waveshare's reference script
LocationX = None
LocationY = None
@@ -40,6 +56,65 @@ def _send_at(cmd, wait=0.5):
return ser.read(1000).decode(errors="replace")
def _module_responds():
"""Check whether the SIM868 module chip is powered on and answering AT commands."""
try:
return "OK" in _send_at("AT", wait=0.3)
except Exception:
return False
def _pulse_pwrkey():
"""Pulse the PWRKEY line low then high, same sequence as Waveshare's own
GSM_PWRKEY.py reference script.
PWRKEY is a toggle, not a simple "power on" switch: pressing it while the
module is off powers it on, but pressing it again while already on powers
it back off. Callers must only invoke this after confirming via
`_module_responds()` that the module is NOT currently responding - never
unconditionally on every app start, or a normal restart while the module
is already up would accidentally shut it back down.
"""
if GPIO is None:
print("RPi.GPIO not available, cannot auto-power-on the SIM868 module - "
"press its PWRKEY button by hand.")
return
GPIO.setmode(GPIO.BOARD)
GPIO.setup(PWRKEY_PIN, GPIO.OUT)
GPIO.output(PWRKEY_PIN, GPIO.LOW)
time.sleep(PWRKEY_HOLD_SECONDS)
GPIO.output(PWRKEY_PIN, GPIO.HIGH)
GPIO.cleanup(PWRKEY_PIN)
def _ensure_module_powered():
"""Power on the SIM868 module chip via its PWRKEY line if it isn't already on.
The HAT's 5V rail can be live (board powered) while the module chip itself
stays fully off until PWRKEY is pulsed - this is what previously required a
manual button press after every Pi power cycle. Returns True once the
module is confirmed responding to AT commands, False if it still isn't
after attempting the power-on pulse (e.g. GPIO unavailable, or this board
doesn't have the P4 mod wire).
"""
if _module_responds():
return True
print("SIM868 module not responding to AT, pulsing PWRKEY to power it on...")
_pulse_pwrkey()
time.sleep(3) # give the module a few seconds to boot after PWRKEY release
for _ in range(5):
if _module_responds():
print("SIM868 module powered on and responding.")
return True
time.sleep(1)
print("SIM868 module still not responding after PWRKEY pulse - check the "
"P4 wiring or press the physical PWRKEY button.")
return False
def _ensure_gnss_powered():
resp = _send_at("AT+CGNSPWR?")
if "+CGNSPWR: 0" in resp:
@@ -76,6 +151,7 @@ def _poll_loop():
global LocationX, LocationY
try:
with _serial_lock:
if _ensure_module_powered():
_ensure_gnss_powered()
except Exception as e:
print("Failed to initialize GNSS:", str(e))
+1
View File
@@ -4,6 +4,7 @@ gevent
pymongo
python-dotenv
pyserial
RPi.GPIO
obd
requests
requests-toolbelt