Files
blackroad/roadnet/power-optimize.sh
Alexa Amundson 78fbe80f2a Initial monorepo — everything BlackRoad in one place
bin/       230 CLI tools (ask-*, br-*, agent-*, roadid, carpool)
scripts/   99 automation scripts
fleet/     Node configs and deployment
workers/   Cloudflare Worker sources (roadpay, road-search, squad webhooks)
roadc/     RoadC programming language
roadnet/   Mesh network (5 APs, WireGuard)
operator/  Memory system scripts
config/    System configs
dotfiles/  Shell configs
docs/      Documentation

BlackRoad OS — Pave Tomorrow.

RoadChain-SHA2048: d1a24f55318d338b
RoadChain-Identity: alexa@sovereign
RoadChain-Full: d1a24f55318d338b24b60bad7be39286379c76ae5470817482100cb0ddbbcb97e147d07ac7243da0a9f0363e4e5c833d612b9c0df3a3cd20802465420278ef74875a5b77f55af6fe42a931b8b635b3d0d0b6bde9abf33dc42eea52bc03c951406d8cbe49f1a3d29b26a94dade05e9477f34a7d4d4c6ec4005c3c2ac54e73a68440c512c8e83fd9b1fe234750b898ef8f4032c23db173961fe225e67a0432b5293a9714f76c5c57ed5fdf35b9fb40fd73c03ebf88b7253c6a0575f5afb6a6b49b3bda310602fb1ef676859962dad2aebbb2875814b30eee0a8ba195e482d4cbc91d8819e7f38f6db53e8063401649c77bb994371473cabfb917fb53e8cbe73d60
2026-03-14 17:08:41 -05:00

251 lines
9.3 KiB
Bash

#!/bin/bash
# BlackRoad Fleet Power Optimizer
# Reduces power draw on Pi 5s with Hailo-8 accelerators
# Deployed to: Cecilia, Octavia, Lucidia (+ Alice as baseline)
# Usage: sudo ./power-optimize.sh [--apply]
set -e
PINK='\033[38;5;205m'
AMBER='\033[38;5;214m'
GREEN='\033[38;5;82m'
RED='\033[38;5;196m'
RESET='\033[0m'
HOSTNAME=$(hostname)
DRY_RUN=true
[[ "$1" == "--apply" ]] && DRY_RUN=false
echo -e "${PINK}━━━ BlackRoad Power Optimizer ━━━${RESET}"
echo -e "Node: ${AMBER}${HOSTNAME}${RESET} | Mode: $(${DRY_RUN} && echo 'DRY RUN' || echo 'APPLYING')"
echo ""
# Detect platform
CONFIG="/boot/firmware/config.txt"
[[ ! -f "$CONFIG" ]] && CONFIG="/boot/config.txt"
[[ ! -f "$CONFIG" ]] && { echo -e "${RED}No config.txt found${RESET}"; exit 1; }
HAS_HAILO=false
[[ -e /dev/hailo0 ]] && HAS_HAILO=true
HAS_NVME=false
lsblk -d | grep -q nvme && HAS_NVME=true
# Current state
echo -e "${AMBER}Current State:${RESET}"
GOVERNOR=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)
CUR_FREQ=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq)
MAX_FREQ=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq)
TEMP=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo "?")
echo " Governor: ${GOVERNOR} | Freq: $((CUR_FREQ/1000))MHz / $((MAX_FREQ/1000))MHz max"
echo " Temp: $((TEMP/1000))°C | Hailo: ${HAS_HAILO} | NVMe: ${HAS_NVME}"
if command -v vcgencmd &>/dev/null; then
VOLTS=$(vcgencmd measure_volts core 2>/dev/null || echo "unknown")
THROTTLE=$(vcgencmd get_throttled 2>/dev/null || echo "unknown")
GPU_MEM=$(vcgencmd get_mem gpu 2>/dev/null || echo "unknown")
echo " Voltage: ${VOLTS} | Throttle: ${THROTTLE} | GPU: ${GPU_MEM}"
fi
echo ""
CHANGES=0
# === FIX 1: CPU Governor → conservative (saves power, still responsive) ===
if [[ "$GOVERNOR" != "conservative" ]]; then
echo -e "${GREEN}[1] CPU governor: ${GOVERNOR} → conservative${RESET}"
echo " (scales freq based on load, slower ramp-up = less power spikes)"
if ! $DRY_RUN; then
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo "conservative" > "$cpu"
done
# Make persistent
if ! grep -q "cpufreq.default_governor=conservative" /boot/firmware/cmdline.txt 2>/dev/null && \
! grep -q "cpufreq.default_governor=conservative" /boot/cmdline.txt 2>/dev/null; then
CMDLINE="/boot/firmware/cmdline.txt"
[[ ! -f "$CMDLINE" ]] && CMDLINE="/boot/cmdline.txt"
if [[ -f "$CMDLINE" ]]; then
sed -i 's/$/ cpufreq.default_governor=conservative/' "$CMDLINE"
echo " Persisted in cmdline.txt"
fi
fi
fi
((CHANGES++))
else
echo "[1] CPU governor already conservative ✓"
fi
# === FIX 2: Remove overclock if present ===
if grep -q "^over_voltage=" "$CONFIG" || grep -q "^arm_freq=" "$CONFIG"; then
OV=$(grep "^over_voltage=" "$CONFIG" | tail -1 | cut -d= -f2)
AF=$(grep "^arm_freq=" "$CONFIG" | tail -1 | cut -d= -f2)
echo -e "${GREEN}[2] Remove overclock: over_voltage=${OV:-none}, arm_freq=${AF:-none}${RESET}"
echo " (overclock + Hailo PCIe = guaranteed undervoltage)"
if ! $DRY_RUN; then
sed -i '/^over_voltage=/d' "$CONFIG"
sed -i '/^arm_freq=/d' "$CONFIG"
echo " Removed from config.txt (reboot required)"
fi
((CHANGES++))
else
echo "[2] No overclock settings ✓"
fi
# === FIX 3: Reduce GPU memory (Hailo does AI, not GPU) ===
CURRENT_GPU=$(grep "^gpu_mem=" "$CONFIG" | tail -1 | cut -d= -f2)
CURRENT_GPU=${CURRENT_GPU:-76} # default is 76MB on Pi 5
if [[ "$CURRENT_GPU" -gt 16 ]]; then
echo -e "${GREEN}[3] GPU memory: ${CURRENT_GPU}MB → 16MB${RESET}"
echo " (headless servers don't need GPU RAM, Hailo handles AI inference)"
if ! $DRY_RUN; then
sed -i '/^gpu_mem=/d' "$CONFIG"
# Add under [all] section
if grep -q "^\[all\]" "$CONFIG"; then
sed -i '/^\[all\]/a gpu_mem=16' "$CONFIG"
else
echo -e "\n[all]\ngpu_mem=16" >> "$CONFIG"
fi
echo " Set in config.txt (reboot required)"
fi
((CHANGES++))
else
echo "[3] GPU memory already minimal (${CURRENT_GPU}MB) ✓"
fi
# === FIX 4: Disable HDMI if no display attached ===
HDMI_STATUS=$(cat /sys/class/drm/card1-HDMI-A-1/status 2>/dev/null || echo "unknown")
HDMI2_STATUS=$(cat /sys/class/drm/card1-HDMI-A-2/status 2>/dev/null || echo "unknown")
if [[ "$HDMI_STATUS" == "disconnected" && "$HDMI2_STATUS" == "disconnected" ]]; then
echo -e "${GREEN}[4] Both HDMI ports disconnected — can save ~25mA each${RESET}"
echo " (add hdmi_blanking=1 to config.txt for power savings)"
if ! $DRY_RUN; then
if ! grep -q "^hdmi_blanking=" "$CONFIG"; then
if grep -q "^\[all\]" "$CONFIG"; then
sed -i '/^\[all\]/a hdmi_blanking=1' "$CONFIG"
else
echo -e "\n[all]\nhdmi_blanking=1" >> "$CONFIG"
fi
echo " Added hdmi_blanking=1 (reboot required)"
fi
fi
((CHANGES++))
elif [[ "$HDMI_STATUS" == "connected" || "$HDMI2_STATUS" == "connected" ]]; then
echo "[4] HDMI display connected — skipping blanking"
else
echo "[4] HDMI status unknown — skipping"
fi
# === FIX 5: Set kernel power params for SD card longevity ===
SWAPPINESS=$(cat /proc/sys/vm/swappiness)
if [[ "$SWAPPINESS" -gt 10 ]]; then
echo -e "${GREEN}[5] Swappiness: ${SWAPPINESS} → 10 (reduce SD writes)${RESET}"
if ! $DRY_RUN; then
sysctl -w vm.swappiness=10 >/dev/null
sysctl -w vm.dirty_ratio=40 >/dev/null
sysctl -w vm.dirty_background_ratio=5 >/dev/null
sysctl -w vm.dirty_expire_centisecs=6000 >/dev/null
# Persist
cat > /etc/sysctl.d/99-blackroad-power.conf <<SYSCTL
# BlackRoad Power + SD longevity optimizations
vm.swappiness=10
vm.dirty_ratio=40
vm.dirty_background_ratio=5
vm.dirty_expire_centisecs=6000
vm.dirty_writeback_centisecs=3000
SYSCTL
echo " Persisted in /etc/sysctl.d/99-blackroad-power.conf"
fi
((CHANGES++))
else
echo "[5] Swappiness already low (${SWAPPINESS}) ✓"
fi
# === FIX 6: Disable unnecessary services eating power ===
DISABLE_LIST=""
# lightdm on headless servers
systemctl is-active lightdm &>/dev/null && DISABLE_LIST+="lightdm "
# cups if no printer
systemctl is-active cups &>/dev/null && DISABLE_LIST+="cups "
systemctl is-active cups-browsed &>/dev/null && DISABLE_LIST+="cups-browsed "
# rpcbind if no NFS clients
systemctl is-active rpcbind &>/dev/null && DISABLE_LIST+="rpcbind "
# nfs-blkmap if no NFS
systemctl is-active nfs-blkmap &>/dev/null && DISABLE_LIST+="nfs-blkmap "
# bluetooth if unused
if ! bluetoothctl devices 2>/dev/null | grep -q Device; then
systemctl is-active bluetooth &>/dev/null && DISABLE_LIST+="bluetooth "
fi
if [[ -n "$DISABLE_LIST" ]]; then
echo -e "${GREEN}[6] Disable unused services: ${DISABLE_LIST}${RESET}"
echo " (each idle service = ~5-50mA wasted)"
if ! $DRY_RUN; then
for svc in $DISABLE_LIST; do
systemctl stop "$svc" 2>/dev/null || true
systemctl disable "$svc" 2>/dev/null || true
echo " Stopped + disabled: $svc"
done
fi
((CHANGES++))
else
echo "[6] No unnecessary services found ✓"
fi
# === FIX 7: WiFi power management ===
WIFI_PM=$(iwconfig wlan0 2>/dev/null | grep -o "Power Management:.*" || echo "unknown")
if echo "$WIFI_PM" | grep -q "off"; then
echo -e "${GREEN}[7] Enable WiFi power management (saves ~40mA)${RESET}"
if ! $DRY_RUN; then
iwconfig wlan0 power on 2>/dev/null || true
# Persist
cat > /etc/NetworkManager/conf.d/wifi-powersave.conf 2>/dev/null <<WIFI || true
[connection]
wifi.powersave = 3
WIFI
echo " WiFi power save enabled"
fi
((CHANGES++))
else
echo "[7] WiFi power management already on ✓"
fi
# === FIX 8: CPU frequency cap for Hailo nodes ===
if $HAS_HAILO; then
if [[ "$MAX_FREQ" -gt 2000000 ]]; then
echo -e "${GREEN}[8] Cap CPU to 2.0GHz for Hailo nodes (PCIe shares power rail)${RESET}"
echo " (Hailo-8 draws ~2.5W on PCIe, CPU must yield headroom)"
if ! $DRY_RUN; then
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq; do
echo 2000000 > "$cpu"
done
# Persist via config.txt
if ! grep -q "^arm_freq=2000" "$CONFIG"; then
if grep -q "^\[all\]" "$CONFIG"; then
sed -i '/^\[all\]/a arm_freq=2000' "$CONFIG"
else
echo -e "\n[all]\narm_freq=2000" >> "$CONFIG"
fi
fi
echo " CPU capped to 2.0GHz (immediate + config.txt)"
fi
((CHANGES++))
else
echo "[8] CPU already capped for Hailo ✓"
fi
else
echo "[8] No Hailo — skipping CPU cap"
fi
echo ""
echo -e "${PINK}━━━ Summary ━━━${RESET}"
echo -e "Changes: ${CHANGES}"
if $DRY_RUN; then
echo -e "${AMBER}Run with --apply to make changes${RESET}"
else
echo -e "${GREEN}Applied! Some changes need reboot.${RESET}"
# Show new state
echo ""
echo "New governor: $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)"
echo "New max freq: $(($(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq)/1000))MHz"
echo "Temp: $(($(cat /sys/class/thermal/thermal_zone0/temp)/1000))°C"
fi