mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2026-03-12 21:02:52 -07:00
Merge pull request #504 from CoderFX/feature/pisugar3-scripts
Add PiSugar 3 shutdown/startup scripts and watchdog
This commit is contained in:
77
scripts/pisugar/epd-shutdown.py
Normal file
77
scripts/pisugar/epd-shutdown.py
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/home/pi/.pwn/bin/python3
|
||||
"""Draw a sleeping face on the Waveshare V4 e-ink before shutdown.
|
||||
|
||||
Requires: Waveshare 2.13" V4 display (250x122).
|
||||
On non-V4 displays the import will fail and the script exits silently.
|
||||
|
||||
Install: sudo cp epd-shutdown.py /usr/local/bin/ && sudo chmod +x /usr/local/bin/epd-shutdown.py
|
||||
"""
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, '/home/pi/.pwn/lib/python3.13/site-packages')
|
||||
|
||||
try:
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper.v2in13_V4.epd2in13_V4 import EPD
|
||||
except ImportError:
|
||||
sys.exit(0) # not a V4 display — nothing to do
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
WIDTH = 250
|
||||
HEIGHT = 122
|
||||
|
||||
FONT_NAME = 'DejaVuSansMono'
|
||||
FONT_BOLD = 'DejaVuSansMono-Bold'
|
||||
|
||||
|
||||
def load_font(name, size):
|
||||
try:
|
||||
return ImageFont.truetype(name, size)
|
||||
except (IOError, OSError):
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
def main():
|
||||
epd = EPD()
|
||||
epd.init()
|
||||
|
||||
img = Image.new('1', (WIDTH, HEIGHT), 255)
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Sleeping face ( -_- ) Zzz
|
||||
face_font = load_font(FONT_BOLD, 28)
|
||||
face = "( -_- )"
|
||||
face_bbox = draw.textbbox((0, 0), face, font=face_font)
|
||||
face_w = face_bbox[2] - face_bbox[0]
|
||||
face_h = face_bbox[3] - face_bbox[1]
|
||||
face_x = (WIDTH - face_w) // 2 - 15
|
||||
face_y = (HEIGHT - face_h) // 2 - 12
|
||||
|
||||
draw.text((face_x, face_y), face, font=face_font, fill=0)
|
||||
|
||||
z1_font = load_font(FONT_BOLD, 16)
|
||||
z2_font = load_font(FONT_BOLD, 12)
|
||||
z3_font = load_font(FONT_BOLD, 9)
|
||||
zx = face_x + face_w + 4
|
||||
draw.text((zx, face_y - 2), "Z", font=z1_font, fill=0)
|
||||
draw.text((zx + 12, face_y + 12), "z", font=z2_font, fill=0)
|
||||
draw.text((zx + 20, face_y + 22), "z", font=z3_font, fill=0)
|
||||
|
||||
sm_font = load_font(FONT_NAME, 10)
|
||||
off_text = "powered off"
|
||||
off_bbox = draw.textbbox((0, 0), off_text, font=sm_font)
|
||||
off_w = off_bbox[2] - off_bbox[0]
|
||||
off_x = (WIDTH - off_w) // 2
|
||||
draw.text((off_x, face_y + face_h + 16), off_text, font=sm_font, fill=0)
|
||||
|
||||
# Full refresh persists after power cut
|
||||
img = img.rotate(180)
|
||||
epd.display(epd.getbuffer(img))
|
||||
epd.sleep()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
print(f"epd-shutdown: {e}", file=sys.stderr)
|
||||
68
scripts/pisugar/epd-startup.py
Normal file
68
scripts/pisugar/epd-startup.py
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/home/pi/.pwn/bin/python3
|
||||
"""Draw a boot indicator on the Waveshare V4 e-ink at startup.
|
||||
|
||||
Requires: Waveshare 2.13" V4 display (250x122).
|
||||
On non-V4 displays the import will fail and the script exits silently.
|
||||
|
||||
Install: sudo cp epd-startup.py /usr/local/bin/ && sudo chmod +x /usr/local/bin/epd-startup.py
|
||||
"""
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, '/home/pi/.pwn/lib/python3.13/site-packages')
|
||||
|
||||
try:
|
||||
from pwnagotchi.ui.hw.libs.waveshare.epaper.v2in13_V4.epd2in13_V4 import EPD
|
||||
except ImportError:
|
||||
sys.exit(0) # not a V4 display
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
WIDTH = 250
|
||||
HEIGHT = 122
|
||||
|
||||
FONT_NAME = 'DejaVuSansMono'
|
||||
FONT_BOLD = 'DejaVuSansMono-Bold'
|
||||
|
||||
|
||||
def load_font(name, size):
|
||||
try:
|
||||
return ImageFont.truetype(name, size)
|
||||
except (IOError, OSError):
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
def main():
|
||||
epd = EPD()
|
||||
epd.init()
|
||||
|
||||
img = Image.new('1', (WIDTH, HEIGHT), 255)
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Waking face ( O_O )
|
||||
face_font = load_font(FONT_BOLD, 28)
|
||||
face = "( O_O )"
|
||||
face_bbox = draw.textbbox((0, 0), face, font=face_font)
|
||||
face_w = face_bbox[2] - face_bbox[0]
|
||||
face_h = face_bbox[3] - face_bbox[1]
|
||||
face_x = (WIDTH - face_w) // 2
|
||||
face_y = (HEIGHT - face_h) // 2 - 12
|
||||
|
||||
draw.text((face_x, face_y), face, font=face_font, fill=0)
|
||||
|
||||
sm_font = load_font(FONT_NAME, 10)
|
||||
boot_text = "booting ..."
|
||||
boot_bbox = draw.textbbox((0, 0), boot_text, font=sm_font)
|
||||
boot_w = boot_bbox[2] - boot_bbox[0]
|
||||
boot_x = (WIDTH - boot_w) // 2
|
||||
draw.text((boot_x, face_y + face_h + 16), boot_text, font=sm_font, fill=0)
|
||||
|
||||
img = img.rotate(180)
|
||||
epd.display(epd.getbuffer(img))
|
||||
epd.sleep()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
print(f"epd-startup: {e}", file=sys.stderr)
|
||||
38
scripts/pisugar/pisugar-watchdog.sh
Normal file
38
scripts/pisugar/pisugar-watchdog.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# PiSugar 3 MCU watchdog — detects when the MCU wakes from deep sleep
|
||||
# and restarts pisugar-server so it reconnects via I2C.
|
||||
#
|
||||
# Requires: PiSugar 3 (I2C address 0x57 on bus 1, pisugar-server daemon).
|
||||
# On non-PiSugar setups the I2C probe fails — script does nothing.
|
||||
#
|
||||
# Install:
|
||||
# sudo cp pisugar-watchdog.sh /usr/local/bin/ && sudo chmod +x /usr/local/bin/pisugar-watchdog.sh
|
||||
# sudo cp systemd/pisugar-watchdog.service /etc/systemd/system/
|
||||
# sudo cp systemd/pisugar-watchdog.timer /etc/systemd/system/
|
||||
# sudo systemctl daemon-reload && sudo systemctl enable --now pisugar-watchdog.timer
|
||||
|
||||
PISUGAR_ADDR=0x57
|
||||
|
||||
i2c_found() {
|
||||
python3 -c "
|
||||
import fcntl, os
|
||||
try:
|
||||
bus = os.open('/dev/i2c-1', os.O_RDWR)
|
||||
fcntl.ioctl(bus, 0x0703, $PISUGAR_ADDR)
|
||||
os.read(bus, 1)
|
||||
os.close(bus)
|
||||
exit(0)
|
||||
except:
|
||||
exit(1)
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
pisugar_connected() {
|
||||
result=$(echo "get battery" | nc -q 1 127.0.0.1 8423 2>/dev/null)
|
||||
! echo "$result" | grep -q "not connected"
|
||||
}
|
||||
|
||||
if i2c_found && ! pisugar_connected; then
|
||||
logger "pisugar-watchdog: PiSugar MCU detected on I2C, restarting pisugar-server"
|
||||
systemctl restart pisugar-server
|
||||
fi
|
||||
42
scripts/pisugar/safe-shutdown.sh
Normal file
42
scripts/pisugar/safe-shutdown.sh
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
# Show shutdown face on e-ink display, then power off.
|
||||
# Called by PiSugar soft_poweroff_shell (power button or battery protect).
|
||||
#
|
||||
# Requires: PiSugar 3 (pisugar-server on port 8423).
|
||||
# On non-PiSugar setups the nc commands return empty — shutdown proceeds normally.
|
||||
#
|
||||
# Boot-loop guard: if battery is low AND charging (USB-C plugged in),
|
||||
# skip shutdown — the Pi just auto-started to charge, don't kill it.
|
||||
#
|
||||
# Install:
|
||||
# sudo cp safe-shutdown.sh /usr/local/bin/ && sudo chmod +x /usr/local/bin/safe-shutdown.sh
|
||||
# Then set in PiSugar config: "soft_poweroff_shell": "sudo /usr/local/bin/safe-shutdown.sh"
|
||||
|
||||
PISUGAR="127.0.0.1 8423"
|
||||
|
||||
# Query PiSugar battery level and charging status
|
||||
BATTERY=$(echo "get battery" | nc -q 1 $PISUGAR 2>/dev/null | grep -oP '[\d.]+' | head -1)
|
||||
CHARGING=$(echo "get battery_power_plugged" | nc -q 1 $PISUGAR 2>/dev/null)
|
||||
|
||||
# Guard: low battery + charging = skip shutdown (charging from dead)
|
||||
if echo "$CHARGING" | grep -q "true"; then
|
||||
BAT_INT=${BATTERY%.*}
|
||||
BAT_INT=${BAT_INT:-0}
|
||||
if [ "$BAT_INT" -lt 10 ] 2>/dev/null; then
|
||||
echo "Charging at ${BAT_INT}% — skipping shutdown" > /tmp/.pwnagotchi-button-msg
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Normal shutdown flow
|
||||
echo "Shutting down..." > /tmp/.pwnagotchi-button-msg
|
||||
|
||||
# Stop pwnagotchi first to release the SPI bus
|
||||
systemctl stop pwnagotchi 2>/dev/null
|
||||
sleep 2
|
||||
|
||||
# Draw sleeping face on e-ink (full refresh, persists after power cut)
|
||||
/home/pi/.pwn/bin/python3 /usr/local/bin/epd-shutdown.py 2>/dev/null
|
||||
|
||||
# Power off
|
||||
shutdown -h now
|
||||
14
scripts/pisugar/systemd/epd-startup.service
Normal file
14
scripts/pisugar/systemd/epd-startup.service
Normal file
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=E-ink boot indicator (Waveshare V4)
|
||||
DefaultDependencies=no
|
||||
After=local-fs.target
|
||||
Before=pwnagotchi.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/home/pi/.pwn/bin/python3 /usr/local/bin/epd-startup.py
|
||||
TimeoutStartSec=30
|
||||
RemainAfterExit=no
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
7
scripts/pisugar/systemd/pisugar-watchdog.service
Normal file
7
scripts/pisugar/systemd/pisugar-watchdog.service
Normal file
@@ -0,0 +1,7 @@
|
||||
[Unit]
|
||||
Description=PiSugar 3 MCU watchdog
|
||||
After=pisugar-server.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/pisugar-watchdog.sh
|
||||
10
scripts/pisugar/systemd/pisugar-watchdog.timer
Normal file
10
scripts/pisugar/systemd/pisugar-watchdog.timer
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Run PiSugar 3 MCU watchdog every 15 seconds
|
||||
|
||||
[Timer]
|
||||
OnBootSec=30s
|
||||
OnUnitActiveSec=15s
|
||||
Unit=pisugar-watchdog.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user