mirror of
https://github.com/commaai/agnos-builder.git
synced 2026-10-31 02:54:02 +08:00
Move rootfs files into userspace/root overlay (#646)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
*.ko
|
||||
Executable
BIN
Binary file not shown.
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
sudo mount -o rw,remount /
|
||||
sudo resize2fs $(findmnt -n -o SOURCE /) &>/dev/null || sudo resize2fs $(findmnt -n -o SOURCE /)
|
||||
sudo mount -o remount,size=1500M /var
|
||||
sudo cp -r /usr/default/var/lib/dpkg /var/lib/
|
||||
sudo sed -i '/bionic/s/^/#/' /etc/apt/sources.list
|
||||
sudo sed -i '/hirsute/s/^/#/' /etc/apt/sources.list
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo mount -o ro,remount / || true
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Read the raw device model
|
||||
RAW_MODEL=$(tr -d '\0' < /sys/firmware/devicetree/base/model)
|
||||
|
||||
# Extract the device model (e.g., tici, tizi, mici)
|
||||
MODEL=$(echo "$RAW_MODEL" | sed 's/comma //g' | awk '{print $1}')
|
||||
|
||||
[ -z "$MODEL" ] && MODEL="unknown"
|
||||
|
||||
HOSTNAME=$(hostname)
|
||||
SERVICE_NAME="comma SSH - $MODEL - [$HOSTNAME]"
|
||||
|
||||
ALIAS_FILE="/data/params/d_tmp/ApiCache_Device"
|
||||
ALIAS=$(jq -e -r '.alias // empty' "$ALIAS_FILE" 2>/dev/null | grep -E '\S' || echo "$RAW_MODEL")
|
||||
|
||||
# Publish avahi service
|
||||
exec avahi-publish -s "$SERVICE_NAME" _ssh._tcp 22 "vendor=comma" "device=comma" "model=$MODEL" "alias=$ALIAS"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
|
||||
# limit display brightness with display uptime to prevent burn in
|
||||
|
||||
BL_OFF = 4
|
||||
BL_POWER_PATH = "/sys/class/backlight/panel0-backlight/bl_power"
|
||||
BRIGHTNESS_PATH = "/sys/class/backlight/panel0-backlight/brightness"
|
||||
MAX_BRIGHTNESS_PATH= "/sys/devices/platform/soc/soc:qcom,dsi-display@0/max_brightness_percent"
|
||||
|
||||
MAX_PERCENT = 90
|
||||
MIN_PERCENT = 30
|
||||
HOURLY_PERC_DECREASE = 5
|
||||
|
||||
def read(path: str) -> int:
|
||||
try:
|
||||
with open(path) as f:
|
||||
return int(f.read())
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
last_perc = None
|
||||
last_off_ts = time.monotonic()
|
||||
while True:
|
||||
try:
|
||||
# sample brightness and backlight power
|
||||
bl_power = read(BL_POWER_PATH)
|
||||
brightness = read(BRIGHTNESS_PATH)
|
||||
if bl_power == BL_OFF or brightness == 0:
|
||||
last_off_ts = time.monotonic()
|
||||
|
||||
# calculate new max
|
||||
uptime_hours = (time.monotonic() - last_off_ts) / (60*60)
|
||||
clipped_perc = MAX_PERCENT - (HOURLY_PERC_DECREASE*uptime_hours)
|
||||
clipped_perc = int(max(min(clipped_perc, MAX_PERCENT), MIN_PERCENT))
|
||||
|
||||
if clipped_perc != last_perc:
|
||||
with open(MAX_BRIGHTNESS_PATH, 'w') as f:
|
||||
f.write(f"{int(clipped_perc)}\n")
|
||||
last_perc = clipped_perc
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
time.sleep(5)
|
||||
Executable
+111
@@ -0,0 +1,111 @@
|
||||
#/usr/bin/env bash
|
||||
|
||||
source /etc/profile
|
||||
|
||||
SETUP="/usr/comma/setup"
|
||||
RESET="/usr/comma/reset"
|
||||
CONTINUE="/data/continue.sh"
|
||||
INSTALLER="/tmp/installer"
|
||||
RESET_TRIGGER="/data/__system_reset__"
|
||||
|
||||
# blip power to ~5W to see if the PSU is stable
|
||||
if grep -q mici /sys/firmware/devicetree/base/model; then
|
||||
sudo timeout --kill-after=2 5 /home/comma/power_burn_max 0.3 2
|
||||
fi
|
||||
|
||||
# use max freq to boot up quickly, then limit
|
||||
echo 1689600 | sudo tee /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq
|
||||
echo 1689600 | sudo tee /sys/devices/system/cpu/cpufreq/policy4/scaling_max_freq
|
||||
|
||||
echo "waiting for magic"
|
||||
for i in {1..200}; do
|
||||
if systemctl is-active --quiet magic && [ -S /tmp/drmfd.sock ]; then
|
||||
break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
if systemctl is-active --quiet magic && [ -S /tmp/drmfd.sock ]; then
|
||||
echo "magic ready after ${SECONDS}s"
|
||||
else
|
||||
echo "timed out waiting for magic, ${SECONDS}s"
|
||||
fi
|
||||
|
||||
sudo chown comma: /data
|
||||
sudo chown comma: /data/media
|
||||
|
||||
handle_setup_keys () {
|
||||
# install default SSH key while still in setup
|
||||
if [[ ! -e /data/params/d/GithubSshKeys && ! -e /data/continue.sh ]]; then
|
||||
if [ ! -e /data/params/d ]; then
|
||||
mkdir -p /data/params/d_tmp
|
||||
ln -s /data/params/d_tmp /data/params/d
|
||||
fi
|
||||
|
||||
echo -n 1 > /data/params/d/AdbEnabled
|
||||
echo -n 1 > /data/params/d/SshEnabled
|
||||
cp /usr/comma/setup_keys /data/params/d/GithubSshKeys
|
||||
elif [[ -e /data/params/d/GithubSshKeys && -e /data/continue.sh ]]; then
|
||||
if cmp -s /data/params/d/GithubSshKeys /usr/comma/setup_keys; then
|
||||
rm /data/params/d/AdbEnabled
|
||||
rm /data/params/d/SshEnabled
|
||||
rm /data/params/d/GithubSshKeys
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# factory reset handling
|
||||
if [ ! -f /run/comma-booted ]; then
|
||||
sudo touch /run/comma-booted
|
||||
if [ -f "$RESET_TRIGGER" ]; then
|
||||
echo "launching system reset, reset trigger present"
|
||||
rm -f $RESET_TRIGGER
|
||||
$RESET
|
||||
elif (( "$(cat /sys/class/input/input2/device/touch_count)" > 4 )); then
|
||||
echo "launching system reset, got taps"
|
||||
$RESET --tap-reset
|
||||
elif ! mountpoint -q /data; then
|
||||
echo "userdata not mounted. loading system reset"
|
||||
$RESET --recover
|
||||
fi
|
||||
fi
|
||||
|
||||
# setup /data/tmp
|
||||
rm -rf /data/tmp
|
||||
mkdir -p /data/tmp
|
||||
|
||||
# symlink vscode to userdata
|
||||
mkdir -p /data/tmp/vscode-server
|
||||
ln -s /data/tmp/vscode-server ~/.vscode-server
|
||||
ln -s /data/tmp/vscode-server ~/.cursor-server
|
||||
ln -s /data/tmp/vscode-server ~/.windsurf-server
|
||||
|
||||
while true; do
|
||||
pkill -f "$SETUP"
|
||||
handle_setup_keys
|
||||
|
||||
if [ -f $CONTINUE ]; then
|
||||
exec "$CONTINUE"
|
||||
fi
|
||||
|
||||
sudo abctl --set_success
|
||||
|
||||
# cleanup installers from previous runs
|
||||
rm -f $INSTALLER
|
||||
pkill -f $INSTALLER
|
||||
|
||||
# run setup and wait for installer
|
||||
$SETUP &
|
||||
echo "waiting for installer"
|
||||
while [ ! -f $INSTALLER ]; do
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
# run installer and wait for continue.sh
|
||||
chmod +x $INSTALLER
|
||||
$INSTALLER &
|
||||
echo "running installer"
|
||||
while [ ! -f $CONTINUE ] && ps -p $! > /dev/null; do
|
||||
sleep 0.1
|
||||
done
|
||||
done
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path("/cache/debug/")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.makedirs(ROOT, exist_ok=True)
|
||||
|
||||
boots = sorted(list(ROOT.iterdir()), key=lambda p: int(p.stem, 16), reverse=True)
|
||||
|
||||
# limit to 100 boots
|
||||
for d in boots[100:]:
|
||||
print("cleaning up", d)
|
||||
shutil.rmtree(d)
|
||||
|
||||
# make a directory for this boot
|
||||
n = 0
|
||||
if len(boots):
|
||||
n = int(boots[0].stem, 16) + 1
|
||||
boot_dir = ROOT / f"{n:08x}"
|
||||
boot_dir.mkdir(parents=True)
|
||||
|
||||
# log some stuff
|
||||
pstore = boot_dir / "pstore"
|
||||
pstore.mkdir()
|
||||
os.system(f"cp /sys/fs/pstore/* {pstore} 2>/dev/null")
|
||||
|
||||
os.system(f"uname -a > {boot_dir / 'uname'}")
|
||||
shutil.copyfile("/VERSION", boot_dir / "VERSION")
|
||||
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Ensure the symlinks in the read only rootfs are
|
||||
# backed by real files and directories on userdata.
|
||||
|
||||
# tmpfiles
|
||||
systemd-tmpfiles --create /usr/comma/tmpfiles.conf
|
||||
|
||||
# /var/log/ tmpfs
|
||||
mkdir -p /var/log/
|
||||
chown root:syslog /var/log
|
||||
mount -t tmpfs -o rw,nosuid,nodev,size=128M,mode=755 tmpfs /var/log
|
||||
|
||||
# setup /home
|
||||
mkdir -p /rwtmp/home_work
|
||||
mkdir -p /rwtmp/home_upper
|
||||
chmod 755 /rwtmp/*
|
||||
mount -t overlay overlay -o lowerdir=/usr/default/home,upperdir=/rwtmp/home_upper,workdir=/rwtmp/home_work /home
|
||||
|
||||
# /etc
|
||||
mkdir -p /data/etc
|
||||
touch /data/etc/timezone
|
||||
touch /data/etc/localtime
|
||||
mkdir -p /data/etc/netplan
|
||||
mkdir -p /data/etc/NetworkManager/system-connections
|
||||
|
||||
# /cache
|
||||
chown -R comma:comma /cache/
|
||||
|
||||
# /data/ssh
|
||||
mkdir -p /data/ssh
|
||||
chown comma: /data/ssh
|
||||
|
||||
# /data/tmp - clear out
|
||||
rm -rf /data/tmp/
|
||||
mkdir -p /data/tmp/
|
||||
|
||||
# /data/persist
|
||||
if [[ ! -d /data/persist ]]; then
|
||||
sudo cp -r /system/persist /data
|
||||
fi
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
function gpio {
|
||||
echo "out" > /sys/class/gpio/gpio$1/direction
|
||||
echo $2 > /sys/class/gpio/gpio$1/value
|
||||
}
|
||||
|
||||
pins=(
|
||||
# 27 # SW_3V3_EN
|
||||
# 25 # SW_5V_EN
|
||||
30 # HUB_RST_N
|
||||
49 # SOM_ST_IO
|
||||
134 # ST_BOOT0
|
||||
41 # PANDA_1V8_EN_N
|
||||
50 # LTE_RST_N
|
||||
116 # LTE_PWRKEY
|
||||
124 # ST_RST_N
|
||||
34 # GPS_PWR_EN
|
||||
33 # GPS_SAFEBOOT_N
|
||||
32 # GPS_RST_N
|
||||
52 # LTE_BOOT
|
||||
1264 # POWER ALERT
|
||||
)
|
||||
|
||||
for p in ${pins[@]}; do
|
||||
echo $p
|
||||
|
||||
# this is SSD_3v3 EN on tici
|
||||
if [ "$p" -eq 41 ] && grep -q "comma tici" /sys/firmware/devicetree/base/model; then
|
||||
echo "Skipping $p"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo $p > /sys/class/gpio/export
|
||||
until [ -d /sys/class/gpio/gpio$p ]
|
||||
do
|
||||
sleep .05
|
||||
done
|
||||
done
|
||||
|
||||
|
||||
HUB_RST_N=30
|
||||
gpio $HUB_RST_N 1
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
# don't restart whole SoC on subsystem crash
|
||||
for i in {0..7}; do
|
||||
echo "related" | sudo tee /sys/bus/msm_subsys/devices/subsys${i}/restart_level
|
||||
done
|
||||
|
||||
# raise scaling_max so policy=performance can reach the BOOST top step
|
||||
echo 2649600 | sudo tee /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq
|
||||
echo 2649600 | sudo tee /sys/devices/system/cpu/cpufreq/policy4/scaling_max_freq
|
||||
|
||||
# boot wifi
|
||||
echo 1 | sudo tee /sys/kernel/boot_wlan/boot_wlan
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:85f6d9e54286a3842920d6967b187478b4e43d6171c331d72d3fb3102106e101
|
||||
size 1401224
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
|
||||
function gpio {
|
||||
echo "out" > /sys/class/gpio/gpio$1/direction
|
||||
echo $2 > /sys/class/gpio/gpio$1/value
|
||||
}
|
||||
|
||||
HUB_RST_N=30
|
||||
LTE_RST_N=50
|
||||
LTE_BOOT=52
|
||||
LTE_PWRKEY=116
|
||||
|
||||
function is_modem_up {
|
||||
if lsusb -d "0x2c7c:" >/dev/null 2>&1; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function reset {
|
||||
echo " Resetting..."
|
||||
gpio $LTE_RST_N 1
|
||||
sleep 1
|
||||
gpio $LTE_RST_N 0
|
||||
}
|
||||
|
||||
function power_button {
|
||||
echo " Pulsing power button..."
|
||||
gpio $LTE_PWRKEY 1
|
||||
sleep 1
|
||||
gpio $LTE_PWRKEY 0
|
||||
}
|
||||
|
||||
function is_online {
|
||||
echo " Waiting until the modem comes online..."
|
||||
|
||||
for i in {0..30}; do
|
||||
if is_modem_up; then
|
||||
echo " Modem is online!"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo " Waiting..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
function is_offline {
|
||||
echo " Waiting until the modem goes offline..."
|
||||
|
||||
for i in {0..30}; do
|
||||
if ! is_modem_up; then
|
||||
echo " Modem is offline!"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo " Waiting..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Boot into the regular mode
|
||||
gpio $LTE_BOOT 0
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
echo "Starting LTE..."
|
||||
|
||||
reset
|
||||
power_button
|
||||
until is_online; do
|
||||
reset
|
||||
power_button
|
||||
done
|
||||
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping LTE..."
|
||||
|
||||
if is_online; then
|
||||
power_button
|
||||
fi
|
||||
|
||||
;;
|
||||
stop_blocking)
|
||||
echo "Stopping LTE..."
|
||||
|
||||
if is_online; then
|
||||
power_button
|
||||
fi
|
||||
|
||||
until is_offline; do
|
||||
power_button
|
||||
done
|
||||
|
||||
;;
|
||||
*)
|
||||
echo "Specify either start or stop as first argument!"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
import os, socket, threading, time
|
||||
from array import array
|
||||
|
||||
import pyray as rl
|
||||
|
||||
SOCK_PATH = "/tmp/drmfd.sock"
|
||||
DRM_DEVICE = "/dev/dri/card0"
|
||||
BACKGROUND = "/usr/comma/bg.png"
|
||||
|
||||
def power_screen():
|
||||
try:
|
||||
with open("/sys/class/backlight/panel0-backlight/bl_power", "w") as f:
|
||||
f.write("0")
|
||||
with open("/sys/class/backlight/panel0-backlight/max_brightness") as f:
|
||||
max_brightness = int((int(f.read().strip()) / 100) * 65)
|
||||
with open("/sys/class/backlight/panel0-backlight/brightness", "w") as f:
|
||||
f.write(str(max_brightness))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def show_background(tex, pos):
|
||||
rl.begin_drawing()
|
||||
rl.draw_texture(tex, int(pos.x), int(pos.y), rl.WHITE)
|
||||
rl.end_drawing()
|
||||
power_screen()
|
||||
|
||||
def handle_client(client, drm_master):
|
||||
try:
|
||||
drm_master_dup = os.dup(drm_master)
|
||||
client.sendmsg([b"x"], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, array("i", [drm_master_dup]).tobytes())])
|
||||
client.recv(1)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
os.close(drm_master_dup)
|
||||
client.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def main():
|
||||
while True:
|
||||
try:
|
||||
drm_master = os.open(DRM_DEVICE, os.O_RDWR | os.O_CLOEXEC)
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
time.sleep(0.1)
|
||||
|
||||
os.environ['DRM_FD'] = str(drm_master)
|
||||
rl.init_window(0, 0, "not weston")
|
||||
img = rl.load_image(BACKGROUND)
|
||||
rl.image_resize(img, rl.get_screen_width(), rl.get_screen_width()//2)
|
||||
tex = rl.load_texture_from_image(img)
|
||||
rl.set_texture_filter(tex, rl.TextureFilter.TEXTURE_FILTER_BILINEAR)
|
||||
pos = rl.Vector2((rl.get_screen_width() - tex.width)/2.0, (rl.get_screen_height() - tex.height)/2.0)
|
||||
rl.unload_image(img)
|
||||
show_background(tex, pos)
|
||||
|
||||
try:
|
||||
os.unlink(SOCK_PATH)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
server.bind(SOCK_PATH)
|
||||
server.settimeout(0.1)
|
||||
server.listen(1)
|
||||
|
||||
clients = set()
|
||||
need_background = False
|
||||
|
||||
while True:
|
||||
dead = [t for t in list(clients) if not t.is_alive()]
|
||||
for t in dead:
|
||||
t.join()
|
||||
clients.discard(t)
|
||||
if not clients and need_background:
|
||||
need_background = False
|
||||
show_background(tex, pos)
|
||||
|
||||
try:
|
||||
client, _ = server.accept()
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
need_background = True
|
||||
t = threading.Thread(target=handle_client, args=(client, drm_master), daemon=True)
|
||||
t.start()
|
||||
clients.add(t)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,641 @@
|
||||
#!/usr/bin/env python3
|
||||
# Source: commaai/openpilot@6b592ae2ccf68d1b3b89a8c544b12ecb074f3a8f
|
||||
# openpilot/common/hardware/comma/modem.py; Webbing ICCID prefix inlined.
|
||||
# Copyright (c) 2018, Comma.ai, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
import fcntl
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import select
|
||||
import signal
|
||||
import string
|
||||
import struct
|
||||
import subprocess
|
||||
import tempfile
|
||||
import termios
|
||||
import time
|
||||
|
||||
from contextlib import contextmanager
|
||||
from ipaddress import IPv4Address, AddressValueError
|
||||
|
||||
from enum import Enum
|
||||
|
||||
WEBBING_ICCID_PREFIX = "8985235"
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s.%(msecs)03d %(levelname)-7s modem: %(message)s",
|
||||
datefmt="%H:%M:%S",
|
||||
)
|
||||
|
||||
AT_PORT = "/dev/modem_at0"
|
||||
PPP_PORT = "/dev/modem_at1"
|
||||
STATE_PATH = "/dev/shm/modem"
|
||||
AT_LOCK = "/dev/shm/modem.lock" # shared with LPA
|
||||
AT_INIT = [
|
||||
"ATE0", # disable command echo
|
||||
"ATV1", # verbose result codes (CONNECT/BUSY/NO CARRIER, not numeric)
|
||||
"AT+CMEE=1", # numeric +CME ERROR codes on failures (per 3GPP 27.007)
|
||||
"ATX4", # extended result codes: busy + dial tone detection, line speed in CONNECT
|
||||
"AT&C1", # DCD pin follows carrier state (V.250 default)
|
||||
"AT+CREG=2", # registration URCs include location info
|
||||
"AT+CGREG=2", # GPRS registration URCs include location info
|
||||
]
|
||||
CREG = {0: "not_registered", 1: "home", 2: "searching", 3: "denied", 4: "unknown", 5: "roaming"}
|
||||
# 3GPP TS 27.007 +COPS <AcT> -> network type
|
||||
NETWORK_TYPE = {0: "gsm", 1: "gsm", 3: "gsm", 8: "gsm",
|
||||
2: "utran", 4: "utran", 5: "utran", 6: "utran",
|
||||
7: "lte", 9: "lte", 10: "lte",
|
||||
11: "nr", 12: "nr", 13: "nr"}
|
||||
|
||||
DIAL_CID = 1
|
||||
|
||||
PPPD_CMD = [
|
||||
"sudo", "pppd", PPP_PORT, "460800", "noauth", "nodetach", "noipdefault", "usepeerdns",
|
||||
"nodefaultroute", "connect",
|
||||
"/usr/sbin/chat -v ABORT 'NO CARRIER' ABORT 'NO DIALTONE' ABORT 'BUSY' " +
|
||||
f"ABORT 'NO ANSWER' ABORT 'ERROR' TIMEOUT 5 '' AT OK ATD*99***{DIAL_CID}# CONNECT ''",
|
||||
"lcp-echo-interval", "30", "lcp-echo-failure", "4", "mtu", "1500", "mru", "1500",
|
||||
"novj", "novjccomp", "ipcp-accept-local", "ipcp-accept-remote", "nomagic",
|
||||
"user", '""', "password", '""',
|
||||
]
|
||||
INITIAL_STATE: dict[str, object] = {
|
||||
"seconds_since_boot": 0,
|
||||
"state": "INITIALIZING",
|
||||
"connected": False, "ip_address": "",
|
||||
"iccid": "", "mcc_mnc": "", "imei": "", "modem_version": "",
|
||||
"signal_strength": 0, "signal_quality": 0,
|
||||
"network_type": "unknown", "operator": "", "band": "", "channel": 0,
|
||||
"registration": "unknown", "temperatures": [], "extra": "",
|
||||
"tx_bytes": 0, "rx_bytes": 0,
|
||||
}
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _serial_port(port: str, baudrate: int):
|
||||
fd = os.open(port, os.O_RDWR | os.O_NOCTTY)
|
||||
try:
|
||||
attrs = termios.tcgetattr(fd)
|
||||
attrs[0] = 0
|
||||
attrs[1] = 0
|
||||
attrs[2] = termios.CLOCAL | termios.CREAD | termios.CS8
|
||||
attrs[3] = 0
|
||||
attrs[4] = attrs[5] = getattr(termios, f"B{baudrate}")
|
||||
attrs[6][termios.VMIN] = 0
|
||||
attrs[6][termios.VTIME] = 0
|
||||
termios.tcsetattr(fd, termios.TCSANOW, attrs)
|
||||
yield fd
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def _read_line(fd: int, timeout: float) -> bytes:
|
||||
data = bytearray()
|
||||
deadline = time.monotonic() + timeout
|
||||
while True:
|
||||
readable, _, _ = select.select([fd], [], [], max(0.0, deadline - time.monotonic()))
|
||||
if not readable:
|
||||
return bytes(data)
|
||||
byte = os.read(fd, 1)
|
||||
if not byte:
|
||||
return bytes(data)
|
||||
data.extend(byte)
|
||||
if byte == b"\n":
|
||||
return bytes(data)
|
||||
|
||||
|
||||
class State(Enum):
|
||||
INITIALIZING = "INITIALIZING"
|
||||
SEARCHING = "SEARCHING"
|
||||
CONNECTING = "CONNECTING"
|
||||
CONNECTED = "CONNECTED"
|
||||
DISCONNECTING = "DISCONNECTING"
|
||||
|
||||
|
||||
STATE_WAIT = 1.0 # seconds to wait after each state handler returns
|
||||
|
||||
|
||||
class PPPSession:
|
||||
"""Owns pppd lifecycle, fail tracking, and PPP routing."""
|
||||
MAX_FAILS = 3
|
||||
|
||||
def __init__(self):
|
||||
self._proc: subprocess.Popen | None = None
|
||||
self._fails = 0
|
||||
self._peer = ""
|
||||
|
||||
def start(self):
|
||||
self._proc = subprocess.Popen(PPPD_CMD, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
self._peer = ""
|
||||
logging.info(f"PPP dialing CID {DIAL_CID}")
|
||||
|
||||
def kill(self):
|
||||
subprocess.run(["sudo", "killall", "-9", "pppd"], capture_output=True)
|
||||
self._peer = ""
|
||||
|
||||
@staticmethod
|
||||
def reset_data_port():
|
||||
"""Drop DTR on PPP_PORT so the modem terminates any stuck PPP session."""
|
||||
try:
|
||||
with _serial_port(PPP_PORT, 460800) as fd:
|
||||
dtr = struct.pack("I", termios.TIOCM_DTR)
|
||||
fcntl.ioctl(fd, termios.TIOCMBIC, dtr)
|
||||
time.sleep(0.2)
|
||||
fcntl.ioctl(fd, termios.TIOCMBIS, dtr)
|
||||
except Exception as e:
|
||||
logging.warning(f"data port reset failed: {e}")
|
||||
|
||||
def has_exited(self) -> bool:
|
||||
return self._proc is not None and self._proc.poll() is not None
|
||||
|
||||
def reset_fail_counter(self):
|
||||
self._fails = 0
|
||||
|
||||
def record_fail(self) -> bool:
|
||||
"""Bump fail counter; return True if at the give-up limit."""
|
||||
self._fails += 1
|
||||
return self._fails >= self.MAX_FAILS
|
||||
|
||||
@property
|
||||
def fails(self) -> int:
|
||||
return self._fails
|
||||
|
||||
def maybe_install_routes(self, ip: str, peer: str) -> bool:
|
||||
"""Install routes if peer changed; kill the session on failure so the state machine reconnects."""
|
||||
if not peer or peer == self._peer:
|
||||
return False
|
||||
try:
|
||||
IPv4Address(ip)
|
||||
IPv4Address(peer)
|
||||
except AddressValueError:
|
||||
logging.warning(f"refusing route install with non-IPv4 ip={ip!r} peer={peer!r}")
|
||||
self.kill()
|
||||
return False
|
||||
self.cleanup_routes()
|
||||
cmds = [
|
||||
["sudo", "ip", "route", "add", "default", "via", peer, "dev", "ppp0", "metric", "1000"],
|
||||
["sudo", "ip", "route", "add", "default", "via", peer, "dev", "ppp0", "table", "1000"],
|
||||
["sudo", "ip", "rule", "add", "from", ip, "table", "1000"],
|
||||
]
|
||||
for cmd in cmds:
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
logging.warning(f"route install failed ({' '.join(cmd[1:])}): {r.stderr.strip()}")
|
||||
self.cleanup_routes()
|
||||
self.kill()
|
||||
return False
|
||||
logging.info(f"route set up for {ip} via {peer}")
|
||||
self._peer = peer
|
||||
return True
|
||||
|
||||
def maybe_install_dns(self, dns_servers: list[str]) -> bool:
|
||||
"""Register DNS servers with systemd-resolved; kill the session on failure to force a retry."""
|
||||
if not dns_servers:
|
||||
return False
|
||||
for cmd in (["sudo", "resolvectl", "dns", "ppp0", *dns_servers],
|
||||
["sudo", "resolvectl", "default-route", "ppp0", "yes"]):
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
logging.warning(f"resolvectl failed ({' '.join(cmd[1:])}): {r.stderr.strip()}")
|
||||
self.kill()
|
||||
return False
|
||||
logging.info(f"resolvectl: ppp0 DNS = {dns_servers}")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def cleanup_routes():
|
||||
subprocess.run(["sudo", "ip", "route", "del", "default", "dev", "ppp0"], capture_output=True)
|
||||
subprocess.run(["sudo", "ip", "route", "flush", "table", "1000"], capture_output=True)
|
||||
# rules don't have a flush; delete until none remain
|
||||
while subprocess.run(["sudo", "ip", "rule", "del", "table", "1000"], capture_output=True).returncode == 0:
|
||||
pass
|
||||
subprocess.run(["sudo", "resolvectl", "revert", "ppp0"], capture_output=True)
|
||||
|
||||
|
||||
class Modem:
|
||||
def __init__(self):
|
||||
self._ppp = PPPSession()
|
||||
self._sim_change = False
|
||||
self._apn = "" # blank = network-provided via PCO
|
||||
self._roaming_allowed = True
|
||||
self.running = True
|
||||
self.S = INITIAL_STATE.copy()
|
||||
|
||||
@staticmethod
|
||||
def _read_param(key):
|
||||
try:
|
||||
with open(f"/data/params/d/{key}") as f:
|
||||
return f.read().strip()
|
||||
except FileNotFoundError:
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def _parse_reg(v: str) -> str:
|
||||
try:
|
||||
return CREG.get(int(v.split(",")[1].strip('"')), "unknown")
|
||||
except (ValueError, IndexError):
|
||||
return "unknown"
|
||||
|
||||
@staticmethod
|
||||
def _has_modem_manager() -> bool:
|
||||
return os.path.isfile("/lib/systemd/system/ModemManager.service")
|
||||
|
||||
def _is_roaming_allowed(self) -> bool:
|
||||
if self.S["iccid"].startswith(WEBBING_ICCID_PREFIX):
|
||||
return True
|
||||
return self._read_param("GsmRoaming") == "1"
|
||||
|
||||
def _publish_state(self, **kwargs):
|
||||
self.S.update(kwargs)
|
||||
self.S["seconds_since_boot"] = time.monotonic()
|
||||
with tempfile.NamedTemporaryFile(mode="w", dir="/dev/shm", delete=False) as f:
|
||||
json.dump(self.S, f, indent=2)
|
||||
os.chmod(f.name, 0o644)
|
||||
os.replace(f.name, STATE_PATH)
|
||||
|
||||
def _at(self, cmd):
|
||||
"""Send AT command, return response lines. [] on error or if LPA holds port."""
|
||||
fd = os.open(AT_LOCK, os.O_CREAT | os.O_RDWR, 0o666)
|
||||
try:
|
||||
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except OSError:
|
||||
os.close(fd)
|
||||
return []
|
||||
try:
|
||||
with _serial_port(AT_PORT, 9600) as serial_fd:
|
||||
termios.tcflush(serial_fd, termios.TCIFLUSH)
|
||||
command = (cmd + "\r").encode()
|
||||
while command:
|
||||
command = command[os.write(serial_fd, command):]
|
||||
lines = []
|
||||
while True:
|
||||
raw = _read_line(serial_fd, 5)
|
||||
if not raw:
|
||||
raise TimeoutError("AT timeout")
|
||||
line = raw.decode(errors="ignore").strip()
|
||||
if not line:
|
||||
continue
|
||||
if line == "OK":
|
||||
break
|
||||
if line == "ERROR" or line.startswith("+CME ERROR"):
|
||||
raise RuntimeError(line)
|
||||
lines.append(line)
|
||||
return lines
|
||||
except (RuntimeError, TimeoutError, OSError) as e:
|
||||
logging.info(f"AT {cmd} failed: {e}")
|
||||
return []
|
||||
finally:
|
||||
fcntl.flock(fd, fcntl.LOCK_UN)
|
||||
os.close(fd)
|
||||
|
||||
def _atv(self, cmd, pfx):
|
||||
for line in self._at(cmd):
|
||||
if pfx in line and ":" in line:
|
||||
return line.split(":", 1)[1].strip()
|
||||
return None
|
||||
|
||||
def _init_at_channel(self) -> bool:
|
||||
"""Run AT_INIT and confirm ATE0 took effect. Returns False if echo is still on."""
|
||||
for c in AT_INIT:
|
||||
self._at(c)
|
||||
r = self._at("AT+CGMI")
|
||||
return bool(r) and not r[0].startswith("AT")
|
||||
|
||||
def _configure_modem(self, modem_version: str):
|
||||
if not modem_version.startswith("EG25"):
|
||||
return
|
||||
cmds = [
|
||||
# clear initial EPS bearer APN (some carriers reject the default)
|
||||
'AT+CGDCONT=0,"IP",""',
|
||||
|
||||
# SIM hot swap
|
||||
'AT+QSIMDET=1,0',
|
||||
'AT+QSIMSTAT=1',
|
||||
|
||||
# configure modem as data-centric
|
||||
'AT+QNVW=5280,0,"0102000000000000"',
|
||||
'AT+QNVFW="/nv/item_files/ims/IMS_enable",00',
|
||||
'AT+QNVFW="/nv/item_files/modem/mmode/ue_usage_setting",01',
|
||||
]
|
||||
for c in cmds:
|
||||
self._at(c)
|
||||
|
||||
def _do_initializing(self):
|
||||
if not os.path.exists(AT_PORT):
|
||||
return State.INITIALIZING
|
||||
logging.info("port found, initializing")
|
||||
self._ppp.kill()
|
||||
self._ppp.cleanup_routes()
|
||||
|
||||
if not self._init_at_channel():
|
||||
logging.warning("AT echo still on, retrying")
|
||||
return State.INITIALIZING
|
||||
|
||||
identity = self._read_identity()
|
||||
if not identity["iccid"] or not identity["imei"]:
|
||||
logging.warning(f"identity read incomplete: {identity}, retrying")
|
||||
return State.INITIALIZING
|
||||
|
||||
self._configure_modem(identity["modem_version"])
|
||||
|
||||
self.S.update(identity)
|
||||
self._apn = self._read_param("GsmApn")
|
||||
self._roaming_allowed = self._is_roaming_allowed()
|
||||
# blank APN lets the carrier supply one via PCO
|
||||
self._at(f'AT+CGDCONT={DIAL_CID},"IP","{self._apn}"')
|
||||
logging.info(f"APN '{self._apn or '(network-provided)'}' written to CID {DIAL_CID}, roaming={'on' if self._roaming_allowed else 'off'}")
|
||||
|
||||
self._sim_change = False # clear since we just re-read identity with the new SIM
|
||||
self._publish_state(**identity)
|
||||
return State.SEARCHING
|
||||
|
||||
def _read_identity(self):
|
||||
def first_line(cmd):
|
||||
r = self._at(cmd)
|
||||
return r[0].strip() if r else ""
|
||||
|
||||
imei = first_line("AT+CGSN")
|
||||
if not (imei.isdigit() and 14 <= len(imei) <= 17): # 3GPP TS 23.003
|
||||
imei = ""
|
||||
|
||||
iccid = (self._atv("AT+QCCID", "+QCCID:") or "").rstrip("F")
|
||||
if not all(c in string.hexdigits for c in iccid):
|
||||
iccid = ""
|
||||
|
||||
imsi = first_line("AT+CIMI")
|
||||
mcc_mnc = imsi[:6] if imsi.isdigit() and len(imsi) >= 6 else ""
|
||||
|
||||
modem_version = first_line("AT+GMR")
|
||||
|
||||
logging.info(f"imei={imei} iccid={iccid} mcc_mnc={mcc_mnc} ver={modem_version}")
|
||||
return {"imei": imei, "iccid": iccid, "mcc_mnc": mcc_mnc, "modem_version": modem_version}
|
||||
|
||||
def _do_searching(self):
|
||||
new_roaming = self._is_roaming_allowed()
|
||||
if new_roaming != self._roaming_allowed:
|
||||
logging.info(f"roaming changed: {self._roaming_allowed} -> {new_roaming}")
|
||||
self._roaming_allowed = new_roaming
|
||||
|
||||
v = self._atv("AT+CREG?", "+CREG:")
|
||||
if not v:
|
||||
return self._searching_idle()
|
||||
|
||||
reg = self._parse_reg(v)
|
||||
greg = self._parse_reg(self._atv("AT+CGREG?", "+CGREG:") or "")
|
||||
logging.debug(f"creg={reg} cgreg={greg} roaming_allowed={self._roaming_allowed}")
|
||||
|
||||
if reg == "roaming" and not self._roaming_allowed:
|
||||
self._publish_state(registration=reg)
|
||||
return State.SEARCHING
|
||||
|
||||
if reg in ("home", "roaming") and greg in ("home", "roaming"):
|
||||
self._publish_state(registration=reg)
|
||||
return State.CONNECTING
|
||||
|
||||
if reg != self.S.get("registration"):
|
||||
self._publish_state(registration=reg)
|
||||
return self._searching_idle()
|
||||
|
||||
def _searching_idle(self):
|
||||
if self._sim_change or not os.path.exists(AT_PORT):
|
||||
logging.info(f"-> reconnecting (sim_change={self._sim_change} port={os.path.exists(AT_PORT)})")
|
||||
return State.DISCONNECTING
|
||||
return State.SEARCHING
|
||||
|
||||
def _do_connecting(self):
|
||||
logging.info("starting pppd")
|
||||
self._ppp.reset_fail_counter()
|
||||
self._sim_change = False
|
||||
self._ppp.start()
|
||||
return State.CONNECTED
|
||||
|
||||
def _handle_pppd_exit(self):
|
||||
if self._sim_change or not os.path.exists(AT_PORT):
|
||||
return State.DISCONNECTING
|
||||
give_up = self._ppp.record_fail()
|
||||
if give_up:
|
||||
logging.warning(f"PPP fail {self._ppp.fails}/{self._ppp.MAX_FAILS}, reconnecting")
|
||||
return State.DISCONNECTING
|
||||
logging.warning(f"PPP fail {self._ppp.fails}/{self._ppp.MAX_FAILS}, retrying")
|
||||
self._ppp.reset_data_port()
|
||||
if not os.path.exists(AT_PORT):
|
||||
return State.DISCONNECTING
|
||||
self._ppp.start()
|
||||
return State.CONNECTED
|
||||
|
||||
def _params_changed(self) -> bool:
|
||||
new_apn = self._read_param("GsmApn")
|
||||
if new_apn != self._apn:
|
||||
logging.info(f"GsmApn changed: '{self._apn}' -> '{new_apn}'")
|
||||
return True
|
||||
new_roaming = self._is_roaming_allowed()
|
||||
if new_roaming != self._roaming_allowed:
|
||||
logging.info(f"roaming changed: {self._roaming_allowed} -> {new_roaming}")
|
||||
return True
|
||||
return False
|
||||
|
||||
def _check_iccid(self, state):
|
||||
if state in (State.INITIALIZING, State.DISCONNECTING) or not self.S["iccid"]:
|
||||
return
|
||||
iccid = (self._atv("AT+QCCID", "+QCCID:") or "").rstrip("F")
|
||||
if iccid and iccid != self.S["iccid"]:
|
||||
logging.warning(f"iccid changed: {self.S['iccid']} -> {iccid}")
|
||||
self._sim_change = True
|
||||
|
||||
def _do_connected(self):
|
||||
if self._ppp.has_exited():
|
||||
return self._handle_pppd_exit()
|
||||
|
||||
if self._sim_change or not os.path.exists(AT_PORT) or self._params_changed():
|
||||
return State.DISCONNECTING
|
||||
|
||||
self._poll()
|
||||
return State.CONNECTED
|
||||
|
||||
def _do_disconnecting(self):
|
||||
logging.warning("reconnecting")
|
||||
self._publish_state(**INITIAL_STATE)
|
||||
self._ppp.kill()
|
||||
self._ppp.cleanup_routes()
|
||||
self._ppp.reset_data_port()
|
||||
self._sim_change = False
|
||||
return State.INITIALIZING
|
||||
|
||||
def _poll_signal(self) -> dict:
|
||||
v = self._atv("AT+CSQ", "+CSQ:")
|
||||
if not v:
|
||||
return {}
|
||||
try:
|
||||
rssi = int(v.split(",")[0])
|
||||
if rssi == 99:
|
||||
return {}
|
||||
return {"signal_strength": rssi, "signal_quality": min(100, int(rssi / 31 * 100))}
|
||||
except (ValueError, IndexError):
|
||||
return {}
|
||||
|
||||
def _poll_operator(self) -> dict:
|
||||
v = self._atv("AT+COPS?", "+COPS:")
|
||||
if not v:
|
||||
return {}
|
||||
p = v.split(",")
|
||||
out: dict = {}
|
||||
try:
|
||||
if len(p) >= 3:
|
||||
out["operator"] = p[2].strip('"')
|
||||
if len(p) >= 4:
|
||||
out["network_type"] = NETWORK_TYPE.get(int(p[3]), "unknown")
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
return out
|
||||
|
||||
def _poll_band(self) -> dict:
|
||||
v = self._atv("AT+QNWINFO", "+QNWINFO:")
|
||||
if not v:
|
||||
return {}
|
||||
info = v.replace('"', '').split(",")
|
||||
try:
|
||||
if len(info) >= 4:
|
||||
return {"band": info[2], "channel": int(info[3])}
|
||||
except ValueError:
|
||||
pass
|
||||
return {}
|
||||
|
||||
def _poll_extra(self) -> dict:
|
||||
v = self._atv('AT+QENG="servingcell"', "+QENG:")
|
||||
return {"extra": v.replace('"', '')} if v else {}
|
||||
|
||||
def _poll_temps(self) -> dict:
|
||||
v = self._atv("AT+QTEMP", "+QTEMP:")
|
||||
if not v:
|
||||
return {}
|
||||
try:
|
||||
return {"temperatures": [t for t in (int(x) for x in v.split(",") if x.strip()) if t != 255]}
|
||||
except (ValueError, IndexError):
|
||||
return {}
|
||||
|
||||
def _poll_iface(self) -> dict:
|
||||
try:
|
||||
r = subprocess.run(["ip", "-4", "addr", "show", "ppp0"], capture_output=True, text=True, timeout=2)
|
||||
ip, peer = "", ""
|
||||
for line in r.stdout.splitlines():
|
||||
# `inet 10.x.x.x peer 10.64.64.64/32 ...`
|
||||
parts = line.strip().split()
|
||||
if "inet" in parts:
|
||||
i = parts.index("inet")
|
||||
ip = parts[i + 1].split("/")[0]
|
||||
if "peer" in parts:
|
||||
peer = parts[parts.index("peer") + 1].split("/")[0]
|
||||
break
|
||||
if ip:
|
||||
if self._ppp.maybe_install_routes(ip, peer):
|
||||
self._ppp.maybe_install_dns(self._read_cellular_dns())
|
||||
return {"ip_address": ip, "connected": True}
|
||||
if self.S["connected"]:
|
||||
return {"connected": False, "ip_address": ""}
|
||||
except Exception:
|
||||
pass
|
||||
return {}
|
||||
|
||||
def _read_cellular_dns(self) -> list[str]:
|
||||
v = self._atv(f"AT+CGCONTRDP={DIAL_CID}", "+CGCONTRDP:")
|
||||
if not v:
|
||||
return []
|
||||
# +CGCONTRDP: <cid>,<bearer_id>,<apn>,<local_addr>,<gw_addr>,<dns_prim>,<dns_sec>,...
|
||||
fields = [f.strip().strip('"') for f in v.split(",")]
|
||||
dns_servers = []
|
||||
for d in fields[5:7]:
|
||||
try:
|
||||
dns_servers.append(str(IPv4Address(d)))
|
||||
except (AddressValueError, ValueError):
|
||||
pass
|
||||
if not dns_servers:
|
||||
dns_servers = [
|
||||
"8.8.8.8", # Google
|
||||
"1.1.1.1", # Cloudflare
|
||||
]
|
||||
logging.warning(f"no cellular DNS servers reported by modem: {v!r}; using fallback {dns_servers}")
|
||||
return dns_servers
|
||||
|
||||
def _poll_byte_counters(self) -> dict:
|
||||
try:
|
||||
with open("/sys/class/net/ppp0/statistics/tx_bytes") as f:
|
||||
tx = int(f.read().strip())
|
||||
with open("/sys/class/net/ppp0/statistics/rx_bytes") as f:
|
||||
rx = int(f.read().strip())
|
||||
except Exception:
|
||||
return {}
|
||||
return {"tx_bytes": tx, "rx_bytes": rx}
|
||||
|
||||
def _poll(self):
|
||||
s: dict = {}
|
||||
for fn in (self._poll_signal, self._poll_operator, self._poll_band,
|
||||
self._poll_extra, self._poll_temps, self._poll_iface,
|
||||
self._poll_byte_counters):
|
||||
s.update(fn())
|
||||
if s:
|
||||
self._publish_state(**s)
|
||||
|
||||
def run(self):
|
||||
logging.info("starting")
|
||||
self._publish_state(state=State.INITIALIZING.value)
|
||||
if self._has_modem_manager():
|
||||
subprocess.run(["sudo", "systemctl", "mask", "--runtime", "ModemManager"], capture_output=True)
|
||||
subprocess.run(["sudo", "systemctl", "stop", "ModemManager"], capture_output=True)
|
||||
self._ppp.kill()
|
||||
|
||||
state = State.INITIALIZING
|
||||
|
||||
handlers = {
|
||||
State.INITIALIZING: self._do_initializing,
|
||||
State.SEARCHING: self._do_searching,
|
||||
State.CONNECTING: self._do_connecting,
|
||||
State.CONNECTED: self._do_connected,
|
||||
State.DISCONNECTING: self._do_disconnecting,
|
||||
}
|
||||
|
||||
while self.running:
|
||||
try:
|
||||
self._check_iccid(state)
|
||||
prev = state
|
||||
state = handlers[state]()
|
||||
if state != prev:
|
||||
self._publish_state(state=state.value)
|
||||
logging.info(f"{prev.value} -> {state.value}")
|
||||
except Exception:
|
||||
logging.exception(f"error in {state.value}")
|
||||
state = State.DISCONNECTING
|
||||
time.sleep(STATE_WAIT)
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
self._ppp.kill()
|
||||
self._ppp.cleanup_routes()
|
||||
try:
|
||||
os.remove(STATE_PATH)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
if self._has_modem_manager():
|
||||
subprocess.run(["sudo", "systemctl", "unmask", "--runtime", "ModemManager"], capture_output=True)
|
||||
subprocess.run(["sudo", "systemctl", "start", "ModemManager"], capture_output=True)
|
||||
|
||||
|
||||
def main():
|
||||
m = Modem()
|
||||
|
||||
def _sig(*_):
|
||||
m.running = False
|
||||
|
||||
signal.signal(signal.SIGINT, _sig)
|
||||
signal.signal(signal.SIGTERM, _sig)
|
||||
m.run()
|
||||
m.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import time
|
||||
import smbus2
|
||||
import select
|
||||
import datetime
|
||||
import subprocess
|
||||
|
||||
ALERT_VOLTAGE_THRESHOLD_mV = 4000
|
||||
|
||||
POWER_ALERT_GPIO_PIN = 1264
|
||||
INA231_BUS = 0
|
||||
INA231_ADDRESS = 0x40
|
||||
INA231_MASK_REG = 0x06
|
||||
INA231_LIMIT_REG = 0x07
|
||||
INA231_MASK_CONFIG = (1 << 12) # Bus undervoltage, not latching
|
||||
INA231_BUS_VOLTAGE_LSB_mV = 1.25
|
||||
VOLTAGE_FILE = "/sys/class/hwmon/hwmon1/in1_input"
|
||||
PARAM_FILE = "/data/params/d/LastPowerDropDetected"
|
||||
|
||||
alert_pin_base = f"/sys/class/gpio/gpio{POWER_ALERT_GPIO_PIN}/"
|
||||
|
||||
def set_screen_power(on):
|
||||
with open("/sys/class/backlight/panel0-backlight/bl_power", "w") as f:
|
||||
f.write("0\n" if on else "4\n")
|
||||
|
||||
def get_screen_power():
|
||||
with open("/sys/class/backlight/panel0-backlight/bl_power", "r") as f:
|
||||
return f.read(1) == "0"
|
||||
|
||||
def swap_word_bytes(val):
|
||||
return ((val & 0xFF) << 8) | ((val & 0xFF00) >> 8)
|
||||
|
||||
def write_once(path, value):
|
||||
with open(path, 'w') as f:
|
||||
f.write(value)
|
||||
|
||||
def init_alert_pin():
|
||||
write_once(os.path.join(alert_pin_base, 'direction'), 'in')
|
||||
write_once(os.path.join(alert_pin_base, 'edge'), 'falling')
|
||||
|
||||
def init_voltage_alert():
|
||||
with smbus2.SMBus(INA231_BUS) as bus:
|
||||
bus.write_word_data(INA231_ADDRESS, INA231_MASK_REG, swap_word_bytes(INA231_MASK_CONFIG), force=True)
|
||||
bus.write_word_data(INA231_ADDRESS, INA231_LIMIT_REG, swap_word_bytes(int(ALERT_VOLTAGE_THRESHOLD_mV / INA231_BUS_VOLTAGE_LSB_mV)), force=True)
|
||||
|
||||
def read_voltage_mV():
|
||||
with open(VOLTAGE_FILE, "r") as f:
|
||||
return int(f.read().strip())
|
||||
|
||||
def read_current_mA():
|
||||
with open("/sys/class/hwmon/hwmon1/curr1_input", "r") as f:
|
||||
return int(f.read().strip())
|
||||
|
||||
def update_param(stage, v_initial, i_initial, v_final, i_final):
|
||||
try:
|
||||
os.umask(0)
|
||||
with open(os.open(PARAM_FILE, os.O_CREAT | os.O_WRONLY, 0o777), 'a') as f:
|
||||
f.write(f"{stage} {datetime.datetime.now()}, ({v_initial=} mV, {i_initial=} mA) -> ({v_final=} mV, {i_final=} mA)\n")
|
||||
f.flush()
|
||||
os.fdatasync(f.fileno())
|
||||
os.fsync(f.fileno())
|
||||
except Exception:
|
||||
print("Failed to update LastControlledShutdown param")
|
||||
|
||||
def printk(msg):
|
||||
with open('/dev/kmsg', 'w') as kmsg:
|
||||
kmsg.write(f"<3>[power drop monitor] {msg}\n")
|
||||
print(msg)
|
||||
|
||||
def perform_controlled_shutdown():
|
||||
printk("Power alert received!")
|
||||
|
||||
prev_screen_power = get_screen_power()
|
||||
set_screen_power(False)
|
||||
|
||||
v_initial = read_voltage_mV()
|
||||
i_initial = read_current_mA()
|
||||
update_param("PREP", v_initial, i_initial, None, None)
|
||||
|
||||
# Wait 100ms before checking voltage level again
|
||||
t = time.monotonic()
|
||||
while time.monotonic() - t < 0.1:
|
||||
time.sleep(0.01)
|
||||
|
||||
v_now = read_voltage_mV()
|
||||
i_now = read_current_mA()
|
||||
if v_now > ALERT_VOLTAGE_THRESHOLD_mV:
|
||||
printk("Voltage restored. Not shutting down!")
|
||||
update_param("ABORT", v_initial, i_initial, v_now, i_now)
|
||||
set_screen_power(prev_screen_power)
|
||||
return
|
||||
|
||||
update_param("SHUTDOWN", v_initial, i_initial, v_now, i_now)
|
||||
|
||||
# Kill services that draw a lot of power
|
||||
printk("Killing services")
|
||||
subprocess.call(["/usr/bin/systemctl", "kill", "--signal=9", "magic", "comma"])
|
||||
set_screen_power(False)
|
||||
|
||||
printk("Halt")
|
||||
subprocess.call(["/usr/sbin/halt", "-f"])
|
||||
|
||||
if __name__ == '__main__':
|
||||
init_alert_pin()
|
||||
init_voltage_alert()
|
||||
|
||||
# Setup interrupt
|
||||
f = open(os.path.join(alert_pin_base, 'value'), 'r')
|
||||
po = select.poll()
|
||||
po.register(f, select.POLLPRI)
|
||||
|
||||
# Interrupt loop
|
||||
f.read()
|
||||
while True:
|
||||
try:
|
||||
events = po.poll(60000)
|
||||
if events:
|
||||
f.seek(0)
|
||||
state_last = f.read().strip()
|
||||
if int(state_last) == 0:
|
||||
perform_controlled_shutdown()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
import fcntl
|
||||
import struct
|
||||
import threading
|
||||
import subprocess
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
FN = "/var/tmp/power_watchdog"
|
||||
PARAM_FILE = "/data/params/d/LastAgnosPowerMonitorShutdown"
|
||||
THRESHOLD = timedelta(hours=1.0)
|
||||
timestamps = {}
|
||||
|
||||
|
||||
def read(path: str, num: bool = False):
|
||||
try:
|
||||
with open(path) as f:
|
||||
if num:
|
||||
return float(f.read())
|
||||
return f.read()
|
||||
except Exception:
|
||||
return 0 if num else ""
|
||||
|
||||
|
||||
def check_touches():
|
||||
global timestamps
|
||||
|
||||
event_format = "llHHi"
|
||||
event_size = struct.calcsize(event_format)
|
||||
|
||||
with open("/dev/input/by-path/platform-894000.i2c-event", "rb") as event_file:
|
||||
fcntl.fcntl(event_file, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
while True:
|
||||
while (event := event_file.read(event_size)):
|
||||
(sec, usec, etype, code, value) = struct.unpack(event_format, event)
|
||||
if etype != 0 or code != 0 or value != 0:
|
||||
timestamps['touch'] = time.monotonic()
|
||||
time.sleep(60)
|
||||
|
||||
def ssh_active():
|
||||
p = subprocess.run("ss | grep ssh", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
return p.returncode == 0
|
||||
|
||||
def write_param():
|
||||
try:
|
||||
os.umask(0)
|
||||
with open(os.open(PARAM_FILE, os.O_CREAT | os.O_WRONLY, 0o777), 'a') as f:
|
||||
f.write(f"SHUTDOWN datetime={datetime.now()}, time.monotonic={time.monotonic()}, {timestamps=}\n")
|
||||
f.flush()
|
||||
os.fdatasync(f.fileno())
|
||||
os.fsync(f.fileno())
|
||||
except Exception as e:
|
||||
print(f"Failed to write param: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# we limit worst-case power usage when openpilot isn't managing it,
|
||||
# e.g. while building or during setup.
|
||||
|
||||
threading.Thread(target=check_touches, daemon=True).start()
|
||||
|
||||
timestamps['startup'] = time.monotonic()
|
||||
while True:
|
||||
timestamps['watchdog'] = read(FN, True)
|
||||
if ssh_active():
|
||||
timestamps['ssh'] = time.monotonic()
|
||||
if read("/data/params/d/IsEngaged").startswith("1"):
|
||||
timestamps['engaged'] = time.monotonic()
|
||||
|
||||
# store a max such that we can't go backwards
|
||||
timestamps['max'] = max(timestamps.values())
|
||||
|
||||
# time to shutoff?
|
||||
dt = timedelta(seconds=time.monotonic() - max(timestamps.values()))
|
||||
if dt > THRESHOLD:
|
||||
write_param()
|
||||
os.system("sudo poweroff")
|
||||
|
||||
print((THRESHOLD - dt), "until shutdown", f"/ {timestamps=}")
|
||||
time.sleep(60)
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:97ed6413515d0674442c42ae6e20baccf66dd6bb4ec382ee4cf0cc5ebe84e739
|
||||
size 23027473
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import struct
|
||||
|
||||
GAMMA_CURVES_FILE = "/persist/comma/dwo_gamma_curves"
|
||||
COMMAND_FILE = "/sys/kernel/debug/dsi_dwo_video_display/mipi_command"
|
||||
|
||||
GAMMA_ADDR = [
|
||||
[(0x00, 0x01), (0x02, 0x03), (0x04, 0x05), (0x06, 0x07), (0x08, 0x09), (0x0A, 0x0B), (0x0C, 0x0D), (0x0E, 0x0F), (0x10, 0x11), (0x12, 0x13), (0x14, 0x15), (0x16, 0x17), (0x18, 0x19), (0x1A, 0x1B), (0x1C, 0x1D), (0x1E, 0x1F), (0x20, 0x21), (0x22, 0x23), (0x24, 0x25), (0x26, 0x27), (0x28, 0x29), (0x2A, 0x2B), (0x2D, 0x2F), (0x30, 0x31), (0x32, 0x33)],
|
||||
[(0x34, 0x35), (0x36, 0x37), (0x38, 0x39), (0x3A, 0x3B), (0x3D, 0x3F), (0x40, 0x41), (0x42, 0x43), (0x44, 0x45), (0x46, 0x47), (0x48, 0x49), (0x4A, 0x4B), (0x4C, 0x4D), (0x4E, 0x4F), (0x50, 0x51), (0x52, 0x53), (0x54, 0x55), (0x56, 0x58), (0x59, 0x5A), (0x5B, 0x5C), (0x5D, 0x5E), (0x5F, 0x60), (0x61, 0x62), (0x63, 0x64), (0x65, 0x66), (0x67, 0x68)],
|
||||
[(0x69, 0x6A), (0x6B, 0x6C), (0x6D, 0x6E), (0x6F, 0x70), (0x71, 0x72), (0x73, 0x74), (0x75, 0x76), (0x77, 0x78), (0x79, 0x7A), (0x7B, 0x7C), (0x7D, 0x7E), (0x7F, 0x80), (0x81, 0x82), (0x83, 0x84), (0x85, 0x86), (0x87, 0x88), (0x89, 0x8A), (0x8B, 0x8C), (0x8D, 0x8E), (0x8F, 0x90), (0x91, 0x92), (0x93, 0x94), (0x95, 0x96), (0x97, 0x98), (0x99, 0x9A)],
|
||||
]
|
||||
|
||||
def command_mode(mode):
|
||||
return f"39 01 00 00 0A 00 02 FE {((0b1010 << 4) | mode):02X}"
|
||||
|
||||
def pack_command(command_str):
|
||||
command_buf = bytes(map(lambda x: int(x, base=16), command_str.strip().split()))
|
||||
return struct.pack(f"i{len(command_buf)}s", len(command_buf), command_buf)
|
||||
|
||||
def set_gamma(gamma):
|
||||
cmds = [command_mode(2)]
|
||||
for color_idx in range(3):
|
||||
for i, val in reversed(list(enumerate(gamma[color_idx]))):
|
||||
msb, lsb = GAMMA_ADDR[color_idx][i]
|
||||
cmds.append(f"39 0F 00 00 0A 00 02 {msb:02X} {(val >> 8) & 0xFF:02X}")
|
||||
cmds.append(f"39 {'01' if i % 10 == 0 else '0F'} 00 00 0A 00 02 {lsb:02X} {val & 0xFF:02X}")
|
||||
cmds.append(command_mode(0))
|
||||
|
||||
data = b"".join(pack_command(c) for c in cmds)
|
||||
with open(COMMAND_FILE, "wb") as f:
|
||||
f.write(data)
|
||||
print("Successfully setup screen calibration")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not os.path.exists(GAMMA_CURVES_FILE):
|
||||
print("No gamma curves found")
|
||||
exit(0)
|
||||
|
||||
assert os.path.exists(COMMAND_FILE), "MIPI command interface not found"
|
||||
|
||||
with open(GAMMA_CURVES_FILE, "r") as f:
|
||||
gamma = [[int(x, 16) for x in l.strip().split()] for l in f]
|
||||
|
||||
set_gamma(gamma)
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SERIAL="$(cat /proc/cmdline | sed -e 's/^.*androidboot.serialno=//' -e 's/ .*$//')"
|
||||
echo "serial: '$SERIAL'"
|
||||
sysctl kernel.hostname="comma-$SERIAL"
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
setup() {
|
||||
# Check if /config is already mounted
|
||||
if ! mountpoint -q /config; then
|
||||
sudo mount -t configfs none /config
|
||||
else
|
||||
echo "/config is already mounted."
|
||||
fi
|
||||
|
||||
# Create USB gadget directory structure
|
||||
sudo mkdir -p /config/usb_gadget/g1
|
||||
cd /config/usb_gadget/g1
|
||||
sudo mkdir -p strings/0x409
|
||||
sudo mkdir -p configs/c.1/strings/0x409
|
||||
sudo mkdir -p functions/ncm.0
|
||||
|
||||
# Set Vendor and Product ID
|
||||
echo 0x04D8 | sudo tee idVendor
|
||||
echo 0x1234 | sudo tee idProduct
|
||||
|
||||
# Set strings
|
||||
echo "$(cat /proc/cmdline | sed -e 's/^.*androidboot.serialno=//' -e 's/ .*$//')" | sudo tee strings/0x409/serialnumber
|
||||
echo "comma.ai" | sudo tee strings/0x409/manufacturer
|
||||
echo "Linux USB Gadget" | sudo tee strings/0x409/product
|
||||
echo 250 | sudo tee configs/c.1/MaxPower
|
||||
|
||||
# Create ADB function
|
||||
sudo mkdir -p functions/ffs.adb
|
||||
sudo mkdir -p /dev/usb-ffs/adb
|
||||
if ! mountpoint -q /dev/usb-ffs/adb; then
|
||||
sudo mount -t functionfs adb /dev/usb-ffs/adb
|
||||
else
|
||||
echo "/dev/usb-ffs/adb is already mounted"
|
||||
fi
|
||||
|
||||
# Link both functions to configuration
|
||||
echo "NCM+ADB" | sudo tee configs/c.1/strings/0x409/configuration
|
||||
sudo rm -f configs/c.1/ncm.0
|
||||
sudo rm -f configs/c.1/ffs.adb
|
||||
sudo ln -s functions/ncm.0 configs/c.1/
|
||||
sudo ln -s functions/ffs.adb configs/c.1/
|
||||
}
|
||||
|
||||
start() {
|
||||
setprop service.adb.tcp.port -1
|
||||
|
||||
cd /config/usb_gadget/g1
|
||||
echo "a600000.dwc3" | sudo tee UDC
|
||||
}
|
||||
|
||||
stop() {
|
||||
if [ -d "/config/usb_gadget/g1" ]; then
|
||||
cd /config/usb_gadget/g1
|
||||
echo "" | sudo tee UDC
|
||||
fi
|
||||
}
|
||||
|
||||
ADB_PARAM="/data/params/d/AdbEnabled"
|
||||
if [ -f "$ADB_PARAM" ] && [ "$(< $ADB_PARAM)" == "1" ]; then
|
||||
echo "Enabling ADB"
|
||||
|
||||
setup
|
||||
systemctl start adbd
|
||||
sleep 1 # adbd does some setup before we can enable the gadget
|
||||
start
|
||||
else
|
||||
echo "Disabling ADB"
|
||||
|
||||
systemctl stop adbd
|
||||
stop
|
||||
fi
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
SSH_PARAM="/data/params/d/SshEnabled"
|
||||
if [ -f "$SSH_PARAM" ] && [ "$(< $SSH_PARAM)" == "1" ]; then
|
||||
echo "Enabling SSH"
|
||||
systemctl start ssh
|
||||
else
|
||||
echo "Disabling SSH"
|
||||
systemctl stop ssh
|
||||
fi
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c382ce266653bad781c25e403ddea4af508aa6f3ea2eef3f568d964982fad9d6
|
||||
size 22996245
|
||||
@@ -0,0 +1 @@
|
||||
from="127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+iXXq30Tq+J5NKat3KWHCzcmwZ55nGh6WggAqECa5CasBlM9VeROpVu3beA+5h0MibRgbD4DMtVXBt6gEvZ8nd04E7eLA9LTZyFDZ7SkSOVj4oXOQsT0GnJmKrASW5KslTWqVzTfo2XCtZ+004ikLxmyFeBO8NOcErW1pa8gFdQDToH9FrA7kgysic/XVESTOoe7XlzRoe/eZacEQ+jtnmFd21A4aEADkk00Ahjr0uKaJiLUAPatxs2icIXWpgYtfqqtaKF23wSt61OTu6cAwXbOWr3m+IUSRUO0IRzEIQS3z1jfd1svgzSgSSwZ1Lhj4AoKxIEAIc8qJrO4uymCJ public
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
PIP_PATH=/usr/local/venv/bin/pip
|
||||
export TMPDIR=/tmp/pip-tmp
|
||||
|
||||
# setup large tmp for install
|
||||
mkdir -p $TMPDIR
|
||||
sudo mount -o remount,size=2G /tmp
|
||||
sudo mount -o rw,remount /
|
||||
sudo resize2fs $(findmnt -n -o SOURCE /) &>/dev/null || sudo resize2fs $(findmnt -n -o SOURCE /)
|
||||
|
||||
# run command
|
||||
sudo TMPDIR=$TMPDIR PIP_NO_CACHE_DIR=1 $PIP_PATH "$@"
|
||||
|
||||
# cleanup
|
||||
sudo mount -o ro,remount /
|
||||
sudo rm -rf $TMPDIR
|
||||
sudo mount -o remount /tmp
|
||||
+1
@@ -0,0 +1 @@
|
||||
pip
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
UV_PATH=/usr/bin/uv
|
||||
export TMPDIR=/tmp/uv-tmp
|
||||
|
||||
# setup large tmp for install
|
||||
mkdir -p $TMPDIR
|
||||
sudo mount -o remount,size=2G /tmp
|
||||
sudo mount -o rw,remount /
|
||||
sudo resize2fs $(findmnt -n -o SOURCE /) &>/dev/null || sudo resize2fs $(findmnt -n -o SOURCE /)
|
||||
|
||||
# run command
|
||||
sudo -E TMPDIR=$TMPDIR UV_NO_CACHE=1 $UV_PATH "$@"
|
||||
|
||||
# cleanup
|
||||
sudo mount -o ro,remount /
|
||||
sudo rm -rf $TMPDIR
|
||||
sudo mount -o remount /tmp
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/bin/sh
|
||||
###############################################################################
|
||||
#
|
||||
# This script is used for administration of the Hexagon DSP
|
||||
#
|
||||
# Copyright (c) 2012-2016 Qualcomm Technologies, Inc.
|
||||
# All Rights Reserved.
|
||||
# Confidential and Proprietary - Qualcomm Technologies, Inc.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
KEEP_ALIVE=0
|
||||
subsys_name=""
|
||||
|
||||
echo -n "/firmware/image" > /sys/module/firmware_class/parameters/path
|
||||
|
||||
# Wait for adsp.mdt to show up
|
||||
count=0
|
||||
while [ ! -s /firmware/image/adsp.mdt ]; do
|
||||
sleep 0.1
|
||||
# wait 10s for /firmware mounted
|
||||
count=$(( $count + 1 ))
|
||||
if [ $count -ge 100 ]; then
|
||||
echo "[ERROR] Can not find the adsp's firmware"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
for subsys in `ls /sys/bus/msm_subsys/devices`; do
|
||||
name=`cat /sys/bus/msm_subsys/devices/${subsys}/name`
|
||||
if [ "`cat /sys/bus/msm_subsys/devices/${subsys}/name`" = "adsp" ]; then
|
||||
subsys_name="${subsys}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$KEEP_ALIVE" = "1" ]; then
|
||||
if [ -n "${subsys_name}" ]; then
|
||||
sysctl -w kernel.panic=0
|
||||
echo 1 > /sys/bus/msm_subsys/devices/${subsys_name}/keep_alive
|
||||
else
|
||||
echo "[ERROR] Can not keep adsp alive"
|
||||
fi
|
||||
fi
|
||||
|
||||
# FIXME: See ATL-3054
|
||||
echo 1 > /sys/module/subsystem_restart/parameters/enable_debug
|
||||
# Bring adsp out of reset
|
||||
echo "[INFO] Bringing adsp out of reset"
|
||||
echo "${subsys_name}"
|
||||
echo 1 > /sys/kernel/boot_adsp/boot
|
||||
|
||||
# wait boot finished
|
||||
if [ -n "${subsys_name}" ]; then
|
||||
count=0
|
||||
state=`cat /sys/bus/msm_subsys/devices/${subsys_name}/state`
|
||||
while [ "${state}" != "ONLINE" ]; do
|
||||
# wait 2s for subsys boot finished
|
||||
count=$(( $count + 1 ))
|
||||
if [ $count -ge 200 ]; then
|
||||
echo "[ERROR] adsp fail to boot"
|
||||
exit 1
|
||||
fi
|
||||
state=`cat /sys/bus/msm_subsys/devices/${subsys_name}/state`
|
||||
sleep 0.1
|
||||
done
|
||||
fi
|
||||
|
||||
Executable
+161
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
from smbus2 import SMBus
|
||||
from collections import namedtuple
|
||||
from typing import List
|
||||
|
||||
# https://datasheets.maximintegrated.com/en/ds/MAX98089.pdf
|
||||
|
||||
AmpConfig = namedtuple('AmpConfig', ['name', 'value', 'register', 'offset', 'mask'])
|
||||
EQParams = namedtuple('EQParams', ['K', 'k1', 'k2', 'c1', 'c2'])
|
||||
|
||||
def configs_from_eq_params(base, eq_params):
|
||||
return [
|
||||
AmpConfig("K (high)", (eq_params.K >> 8), base, 0, 0xFF),
|
||||
AmpConfig("K (low)", (eq_params.K & 0xFF), base + 1, 0, 0xFF),
|
||||
AmpConfig("k1 (high)", (eq_params.k1 >> 8), base + 2, 0, 0xFF),
|
||||
AmpConfig("k1 (low)", (eq_params.k1 & 0xFF), base + 3, 0, 0xFF),
|
||||
AmpConfig("k2 (high)", (eq_params.k2 >> 8), base + 4, 0, 0xFF),
|
||||
AmpConfig("k2 (low)", (eq_params.k2 & 0xFF), base + 5, 0, 0xFF),
|
||||
AmpConfig("c1 (high)", (eq_params.c1 >> 8), base + 6, 0, 0xFF),
|
||||
AmpConfig("c1 (low)", (eq_params.c1 & 0xFF), base + 7, 0, 0xFF),
|
||||
AmpConfig("c2 (high)", (eq_params.c2 >> 8), base + 8, 0, 0xFF),
|
||||
AmpConfig("c2 (low)", (eq_params.c2 & 0xFF), base + 9, 0, 0xFF),
|
||||
]
|
||||
|
||||
BASE_CONFIG = [
|
||||
AmpConfig("MCLK prescaler", 0b01, 0x10, 4, 0b00110000),
|
||||
AmpConfig("PM: enable speakers", 0b11, 0x4D, 4, 0b00110000),
|
||||
AmpConfig("PM: enable DACs", 0b11, 0x4D, 0, 0b00000011),
|
||||
AmpConfig("Enable PLL1", 0b1, 0x12, 7, 0b10000000),
|
||||
AmpConfig("Enable PLL2", 0b1, 0x1A, 7, 0b10000000),
|
||||
AmpConfig("DAI1: I2S mode", 0b00100, 0x14, 2, 0b01111100),
|
||||
AmpConfig("DAI2: I2S mode", 0b00100, 0x1C, 2, 0b01111100),
|
||||
AmpConfig("DAI1 Passband filtering: music mode", 0b1, 0x18, 7, 0b10000000),
|
||||
AmpConfig("DAI1 voice mode gain (DV1G)", 0b00, 0x2F, 4, 0b00110000),
|
||||
AmpConfig("DAI1 attenuation (DV1)", 0x0, 0x2F, 0, 0b00001111),
|
||||
AmpConfig("DAI2 attenuation (DV2)", 0x0, 0x31, 0, 0b00001111),
|
||||
AmpConfig("DAI2: DC blocking", 0b1, 0x20, 0, 0b00000001),
|
||||
AmpConfig("DAI2: High sample rate", 0b0, 0x20, 3, 0b00001000),
|
||||
AmpConfig("ALC enable", 0b1, 0x43, 7, 0b10000000),
|
||||
AmpConfig("ALC/excursion limiter release time", 0b101, 0x43, 4, 0b01110000),
|
||||
AmpConfig("ALC multiband enable", 0b1, 0x43, 3, 0b00001000),
|
||||
AmpConfig("DAI1 EQ enable", 0b0, 0x49, 0, 0b00000001),
|
||||
AmpConfig("DAI2 EQ clip detection disabled", 0b1, 0x32, 4, 0b00010000),
|
||||
AmpConfig("DAI2 EQ attenuation", 0x5, 0x32, 0, 0b00001111),
|
||||
AmpConfig("Excursion limiter upper corner freq", 0b100, 0x41, 4, 0b01110000),
|
||||
AmpConfig("Excursion limiter lower corner freq", 0b00, 0x41, 0, 0b00000011),
|
||||
AmpConfig("Excursion limiter threshold", 0b000, 0x42, 0, 0b00001111),
|
||||
AmpConfig("Distortion limit (THDCLP)", 0x6, 0x46, 4, 0b11110000),
|
||||
AmpConfig("Distortion limiter release time constant", 0b0, 0x46, 0, 0b00000001),
|
||||
AmpConfig("Right DAC input mixer: DAI1 left", 0b0, 0x22, 3, 0b00001000),
|
||||
AmpConfig("Right DAC input mixer: DAI1 right", 0b0, 0x22, 2, 0b00000100),
|
||||
AmpConfig("Right DAC input mixer: DAI2 left", 0b1, 0x22, 1, 0b00000010),
|
||||
AmpConfig("Right DAC input mixer: DAI2 right", 0b0, 0x22, 0, 0b00000001),
|
||||
AmpConfig("DAI1 audio port selector", 0b10, 0x16, 6, 0b11000000),
|
||||
AmpConfig("DAI1 in/output enable", 0b11, 0x16, 0, 0b11),
|
||||
AmpConfig("DAI2 audio port selector", 0b01, 0x1E, 6, 0b11000000),
|
||||
AmpConfig("DAI2 in/output enable", 0b11, 0x1E, 0, 0b11),
|
||||
AmpConfig("Enable left digital microphone", 0b1, 0x48, 5, 0b00100000),
|
||||
AmpConfig("Enable right digital microphone", 0b1, 0x48, 4, 0b00010000),
|
||||
AmpConfig("Enhanced volume smoothing disabled", 0b0, 0x49, 7, 0b10000000),
|
||||
AmpConfig("Volume adjustment smoothing disabled", 0b0, 0x49, 6, 0b01000000),
|
||||
AmpConfig("Zero-crossing detection disabled", 0b0, 0x49, 5, 0b00100000),
|
||||
]
|
||||
|
||||
CONFIGS = {
|
||||
"tici": [
|
||||
AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111),
|
||||
AmpConfig("Right Speaker Mixer Gain", 0b00, 0x2D, 2, 0b00001100),
|
||||
AmpConfig("Right speaker output volume", 0x1c, 0x3E, 0, 0b00011111),
|
||||
AmpConfig("DAI2 EQ enable", 0b1, 0x49, 1, 0b00000010),
|
||||
|
||||
*configs_from_eq_params(0x84, EQParams(0x274F, 0xC0FF, 0x3BF9, 0x0B3C, 0x1656)),
|
||||
*configs_from_eq_params(0x8E, EQParams(0x1009, 0xC6BF, 0x2952, 0x1C97, 0x30DF)),
|
||||
*configs_from_eq_params(0x98, EQParams(0x0F75, 0xCBE5, 0x0ED2, 0x2528, 0x3E42)),
|
||||
*configs_from_eq_params(0xA2, EQParams(0x091F, 0x3D4C, 0xCE11, 0x1266, 0x2807)),
|
||||
*configs_from_eq_params(0xAC, EQParams(0x0A9E, 0x3F20, 0xE573, 0x0A8B, 0x3A3B)),
|
||||
],
|
||||
"tizi": [
|
||||
AmpConfig("Left speaker output from left DAC", 0b1, 0x2B, 0, 0b11111111),
|
||||
AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111),
|
||||
AmpConfig("Left Speaker Mixer Gain", 0b00, 0x2D, 0, 0b00000011),
|
||||
AmpConfig("Right Speaker Mixer Gain", 0b00, 0x2D, 2, 0b00001100),
|
||||
AmpConfig("Left speaker output volume", 0x17, 0x3D, 0, 0b00011111),
|
||||
AmpConfig("Right speaker output volume", 0x17, 0x3E, 0, 0b00011111),
|
||||
|
||||
AmpConfig("DAI2 EQ enable", 0b0, 0x49, 1, 0b00000010),
|
||||
AmpConfig("DAI2: DC blocking", 0b0, 0x20, 0, 0b00000001),
|
||||
AmpConfig("ALC enable", 0b0, 0x43, 7, 0b10000000),
|
||||
AmpConfig("DAI2 EQ attenuation", 0x2, 0x32, 0, 0b00001111),
|
||||
AmpConfig("Excursion limiter upper corner freq", 0b001, 0x41, 4, 0b01110000),
|
||||
AmpConfig("Excursion limiter threshold", 0b100, 0x42, 0, 0b00001111),
|
||||
AmpConfig("Distortion limit (THDCLP)", 0x0, 0x46, 4, 0b11110000),
|
||||
AmpConfig("Distortion limiter release time constant", 0b1, 0x46, 0, 0b00000001),
|
||||
AmpConfig("Left DAC input mixer: DAI1 left", 0b0, 0x22, 7, 0b10000000),
|
||||
AmpConfig("Left DAC input mixer: DAI1 right", 0b0, 0x22, 6, 0b01000000),
|
||||
AmpConfig("Left DAC input mixer: DAI2 left", 0b1, 0x22, 5, 0b00100000),
|
||||
AmpConfig("Left DAC input mixer: DAI2 right", 0b0, 0x22, 4, 0b00010000),
|
||||
AmpConfig("Right DAC input mixer: DAI2 left", 0b0, 0x22, 1, 0b00000010),
|
||||
AmpConfig("Right DAC input mixer: DAI2 right", 0b1, 0x22, 0, 0b00000001),
|
||||
AmpConfig("Volume adjustment smoothing disabled", 0b1, 0x49, 6, 0b01000000),
|
||||
],
|
||||
}
|
||||
|
||||
class Amplifier:
|
||||
AMP_I2C_BUS = 0
|
||||
AMP_ADDRESS = 0x10
|
||||
|
||||
def __init__(self, debug=False):
|
||||
self.debug = debug
|
||||
|
||||
def _get_shutdown_config(self, amp_disabled: bool) -> AmpConfig:
|
||||
return AmpConfig("Global shutdown", 0b0 if amp_disabled else 0b1, 0x51, 7, 0b10000000)
|
||||
|
||||
def _set_configs(self, configs: List[AmpConfig]) -> None:
|
||||
with SMBus(self.AMP_I2C_BUS) as bus:
|
||||
for config in configs:
|
||||
if self.debug:
|
||||
print(f"Setting \"{config.name}\" to {config.value}:")
|
||||
|
||||
old_value = bus.read_byte_data(self.AMP_ADDRESS, config.register, force=True)
|
||||
new_value = (old_value & (~config.mask)) | ((config.value << config.offset) & config.mask)
|
||||
bus.write_byte_data(self.AMP_ADDRESS, config.register, new_value, force=True)
|
||||
|
||||
if self.debug:
|
||||
print(f" Changed {hex(config.register)}: {hex(old_value)} -> {hex(new_value)}")
|
||||
|
||||
def set_configs(self, configs: List[AmpConfig]) -> bool:
|
||||
# retry in case panda is using the amp
|
||||
for _ in range(10):
|
||||
try:
|
||||
self._set_configs(configs)
|
||||
return True
|
||||
except OSError:
|
||||
print("Failed to set amp config, retrying...")
|
||||
time.sleep(0.02)
|
||||
return False
|
||||
|
||||
def set_global_shutdown(self, amp_disabled: bool) -> bool:
|
||||
return self.set_configs([self._get_shutdown_config(amp_disabled), ])
|
||||
|
||||
def initialize_configuration(self, model: str) -> bool:
|
||||
cfgs = [
|
||||
self._get_shutdown_config(True),
|
||||
*BASE_CONFIG,
|
||||
*CONFIGS[model],
|
||||
self._get_shutdown_config(False),
|
||||
]
|
||||
return self.set_configs(cfgs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("/sys/firmware/devicetree/base/model") as f:
|
||||
model = f.read().strip('\x00')
|
||||
model = model.split('comma ')[-1]
|
||||
|
||||
if model in CONFIGS:
|
||||
amp = Amplifier()
|
||||
amp.initialize_configuration(model)
|
||||
else:
|
||||
print(f"Skipping, no config for {model}")
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
/usr/comma/sound/adsp-start.sh
|
||||
|
||||
echo "waiting for sound card to come online"
|
||||
while [ ! -d /proc/asound/sdm845tavilsndc ] || [ "$(cat /proc/asound/card0/state 2> /dev/null)" != "ONLINE" ] ; do
|
||||
sleep 0.01
|
||||
done
|
||||
echo "sound card online"
|
||||
|
||||
while ! /usr/comma/sound/tinymix controls | grep -q "SEC_MI2S_RX Audio Mixer MultiMedia1"; do
|
||||
sleep 0.01
|
||||
done
|
||||
echo "tinymix controls ready"
|
||||
|
||||
/usr/comma/sound/tinymix set "SEC_MI2S_RX Audio Mixer MultiMedia1" 1
|
||||
if grep -q mici /sys/firmware/devicetree/base/model; then
|
||||
/usr/comma/sound/tinymix set "MultiMedia1 Mixer SEC_MI2S_TX" 1
|
||||
else
|
||||
/usr/comma/sound/tinymix set "MultiMedia1 Mixer TERT_MI2S_TX" 1
|
||||
/usr/comma/sound/tinymix set "TERT_MI2S_TX Channels" Two
|
||||
fi
|
||||
|
||||
# setup the amplifier registers
|
||||
/usr/local/venv/bin/python /usr/comma/sound/amplifier.py
|
||||
Executable
BIN
Binary file not shown.
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
from tabulate import tabulate
|
||||
|
||||
# serial with timestamps:
|
||||
# grabserial -d /dev/serial/by-id/usb-FTDI_FT230X* -t
|
||||
|
||||
# systemd-analyze critical-chain comma.service
|
||||
|
||||
# boot chart:
|
||||
# systemd-analyze plot > /tmp/bootup.svg
|
||||
|
||||
def get_journal_time(x):
|
||||
out = subprocess.check_output("journalctl -x -o short-monotonic", shell=True, encoding='utf8')
|
||||
jlines = out.strip().splitlines()
|
||||
for l in jlines:
|
||||
if x in l:
|
||||
t = l.split('[')[1].split(']')[0]
|
||||
return float(t)
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
ts = {
|
||||
"PON": 1.5, # estimate from powering on to XBL
|
||||
|
||||
# these are dumped over serial, use the ones from the XBL
|
||||
"XBL": 2.4,
|
||||
"ABL": 3.7, # 3s of this is waiting for fastboot + factory reset tapping
|
||||
}
|
||||
def tot_since_kern():
|
||||
return sum(ts.values()) - (ts['PON'] + ts['XBL'] + ts['ABL'])
|
||||
|
||||
out = subprocess.check_output("systemd-analyze", shell=True, encoding='utf8')
|
||||
l = out.splitlines()[0].split(' ')
|
||||
ts['kernel'] = float(l[3][:-1])
|
||||
#ts['systemd'] = float(l[6][:-1])
|
||||
|
||||
ts['comma'] = get_journal_time("Starting comma.service") - tot_since_kern()
|
||||
|
||||
# print
|
||||
tot = 0
|
||||
total = sum(ts.values())
|
||||
tab = []
|
||||
for s, t in ts.items():
|
||||
tot += t
|
||||
tab.append([
|
||||
s,
|
||||
round(t, 2),
|
||||
round(tot, 2),
|
||||
str(round(t/total * 100)) + "%",
|
||||
])
|
||||
|
||||
# TODO: add openpilot time-to-onroad
|
||||
tab.append(['onroad', '?', '?', '-'])
|
||||
print(tabulate(tab))
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
def run(cmd):
|
||||
return subprocess.check_output(cmd, shell=True, encoding='utf8')
|
||||
|
||||
|
||||
def test_setup():
|
||||
# TODO: write some real unit tests for this
|
||||
proc = subprocess.Popen('/usr/comma/setup', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
time.sleep(5)
|
||||
assert proc.poll() is None
|
||||
proc.terminate()
|
||||
|
||||
def test_reset():
|
||||
proc = subprocess.Popen('/usr/comma/reset', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
time.sleep(5)
|
||||
assert proc.poll() is None
|
||||
proc.terminate()
|
||||
|
||||
def test_modem():
|
||||
from test_modem import test_modem_setup, test_modem as check_modem, test_cellular_connection
|
||||
test_modem_setup()
|
||||
check_modem()
|
||||
test_cellular_connection()
|
||||
|
||||
def test_wifi():
|
||||
out = run("nmcli dev wifi")
|
||||
networks = out.strip().splitlines()[1:]
|
||||
assert len(networks) > 5
|
||||
|
||||
def test_color_calibration():
|
||||
out = run("journalctl -b -u magic")
|
||||
assert out.count("Successfully setup color correction") == 3
|
||||
|
||||
def test_python_shims():
|
||||
subprocess.check_call("cd /data/openpilot && scons -h", shell=True, stdout=subprocess.DEVNULL)
|
||||
|
||||
def test_dmesg():
|
||||
# TODO: ensure no new errors in dmesg. would catch things like the brightness setting bug
|
||||
pass
|
||||
|
||||
def test_systemd_services():
|
||||
assert run("systemctl is-active magic").strip() == "active"
|
||||
assert os.path.exists("/tmp/drmfd.sock")
|
||||
|
||||
def test_boot_time():
|
||||
out = subprocess.check_output("systemd-analyze", shell=True, encoding='utf8')
|
||||
|
||||
a = out.splitlines()[0]
|
||||
print(out, a)
|
||||
|
||||
# check magic service
|
||||
|
||||
# check comma service
|
||||
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import string
|
||||
import subprocess
|
||||
import time
|
||||
from ipaddress import IPv4Address
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def modem_state(timeout=120):
|
||||
deadline = time.monotonic() + timeout
|
||||
while True:
|
||||
try:
|
||||
state = json.loads(Path('/dev/shm/modem').read_text())
|
||||
if state.get('imei') and state.get('iccid'):
|
||||
return state
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
pass
|
||||
assert time.monotonic() < deadline, "modem.service did not publish modem/SIM identity"
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def test_modem_setup():
|
||||
assert not Path('/lib/systemd/system/ModemManager.service').exists()
|
||||
assert subprocess.check_output(['systemctl', 'is-enabled', 'modem.service'], text=True).strip() == 'enabled'
|
||||
assert subprocess.check_output(['systemctl', 'is-active', 'modem.service'], text=True).strip() == 'active'
|
||||
for port in ('/dev/modem_at0', '/dev/modem_at1'):
|
||||
assert os.access(port, os.R_OK | os.W_OK), f"comma needs access to {port}"
|
||||
for command in ('pppd', 'chat', 'ip', 'resolvectl', 'killall'):
|
||||
assert shutil.which(command), f"modem.py requires {command}"
|
||||
rules = subprocess.check_output(['sudo', '-n', 'iptables-save', '-t', 'filter'], text=True)
|
||||
assert '-A INPUT -i ppp0 -j DROP' in rules
|
||||
|
||||
|
||||
def test_modem():
|
||||
state = modem_state()
|
||||
assert state['imei'].isdigit() and 14 <= len(state['imei']) <= 17
|
||||
assert all(c in string.hexdigits for c in state['iccid']) and 18 <= len(state['iccid']) <= 22
|
||||
assert state['modem_version'].startswith(('EG25', 'EG91'))
|
||||
assert state['state'] in ('SEARCHING', 'CONNECTING', 'CONNECTED')
|
||||
|
||||
from openpilot.common.hardware import HARDWARE
|
||||
assert HARDWARE.get_imei() == state['imei']
|
||||
assert HARDWARE.get_sim_info()['sim_id'] == state['iccid']
|
||||
assert HARDWARE.get_sim_info()['sim_state'] == ['READY']
|
||||
|
||||
|
||||
def test_cellular_connection():
|
||||
# Requires a SIM with service and cellular coverage, even when Wi-Fi is connected.
|
||||
state = modem_state()
|
||||
assert state['connected'], f"Cellular not connected: {state['state']} ({state['registration']})"
|
||||
ip = str(IPv4Address(state['ip_address']))
|
||||
routes = subprocess.check_output(['ip', '-4', 'route', 'show', 'table', '1000'], text=True)
|
||||
assert 'default' in routes and 'dev ppp0' in routes
|
||||
rules = subprocess.check_output(['ip', '-4', 'rule', 'show'], text=True)
|
||||
assert any(f'from {ip}' in rule and 'lookup 1000' in rule for rule in rules.splitlines())
|
||||
dns = subprocess.check_output(['resolvectl', 'dns', 'ppp0'], text=True)
|
||||
assert len(dns.split(':', 1)[-1].split()) > 0
|
||||
subprocess.check_call(['ping', '-I', 'ppp0', '-c', '3', '-W', '10', '1.1.1.1'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_modem_setup()
|
||||
test_modem()
|
||||
test_cellular_connection()
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
good = """
|
||||
Starting Sound...
|
||||
[INFO] Bringing adsp out of reset
|
||||
subsys4
|
||||
waiting for sound card to come online
|
||||
sound card online
|
||||
sound.service: Succeeded.
|
||||
Finished Sound.
|
||||
""".strip()
|
||||
|
||||
if __name__ == "__main__":
|
||||
time.sleep(3)
|
||||
log = subprocess.check_output("journalctl -o cat -u sound", shell=True, encoding='utf8').strip()
|
||||
passed = log == good
|
||||
with open('/data/tmp/sound_log', 'a') as f:
|
||||
f.write(f"{passed}\n")
|
||||
|
||||
os.system("journalctl -u sound.service -o short-monotonic >> /data/tmp/sound_service_log")
|
||||
os.system("sudo su -c 'tail /data/tmp/sound_log > /dev/console'")
|
||||
os.system("sudo su -c 'wc -l /data/tmp/sound_log > /dev/console'")
|
||||
os.sync()
|
||||
|
||||
if passed:
|
||||
time.sleep(2)
|
||||
os.system("sudo reboot")
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
RATE=${1:-1}
|
||||
|
||||
log_message() {
|
||||
# /var/log/syslog
|
||||
cat /usr/include/sqlite3.h | systemd-cat -t SPAM_TEST
|
||||
|
||||
# /var/log/kern.log
|
||||
#cat /usr/include/sqlite3.h | sudo tee /dev/kmsg > /dev/null
|
||||
}
|
||||
|
||||
# verify config is good
|
||||
sudo logrotate -d /etc/logrotate.conf
|
||||
|
||||
sudo rm -rf /var/log/*
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart rsyslog
|
||||
sudo systemctl restart systemd-journald
|
||||
sudo systemctl restart logrotate-hourly.timer
|
||||
|
||||
while true; do
|
||||
for i in $(seq 1 $RATE); do
|
||||
log_message
|
||||
done
|
||||
|
||||
echo
|
||||
df -h /var/
|
||||
sudo du -hs /var/log/* || true
|
||||
|
||||
sleep 1
|
||||
done
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
# silences a ruby warning
|
||||
# https://bugs.launchpad.net/ubuntu/+source/iptables-netflow/+bug/1907932
|
||||
export RUBYOPT="-W0"
|
||||
|
||||
irqtop
|
||||
@@ -0,0 +1,24 @@
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# See tmpfiles.d(5) for details
|
||||
|
||||
# /var: rebuild enough for services to be happy
|
||||
d /var/crash 0755 - - -
|
||||
q /var/tmp 1777 root root 30d
|
||||
d /var/tmp/xdg 0700 comma comma -
|
||||
|
||||
d /var/lib/dpkg 0755 - - -
|
||||
f /var/lib/dpkg/lock-frontend 0755 - - -
|
||||
f /var/lib/dpkg/status 0755 - - -
|
||||
|
||||
d /var/lib/logrotate 0755 - - -
|
||||
|
||||
d /var/spool 0755 - - -
|
||||
d /var/spool/cron/atjobs 0755 - - -
|
||||
|
||||
d /var/cache/pollinate 0755 pollinate daemon -
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9df4edbeb5849de03f9c2d691d04646af84a3ef74c2f33be8e73d9281daebe99
|
||||
size 23027477
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
|
||||
if __name__ == "__main__":
|
||||
# we are the last line of defense for /var/log filling up
|
||||
while True:
|
||||
usage = os.statvfs('/var/log')
|
||||
percent = (usage.f_blocks - usage.f_bavail) / usage.f_blocks * 100
|
||||
if percent > 70:
|
||||
for fn in os.listdir('/var/log'):
|
||||
file_path = os.path.join('/var/log', fn)
|
||||
if os.path.isfile(file_path):
|
||||
with open(file_path, 'w'):
|
||||
pass
|
||||
time.sleep(1)
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
until [ -e $1 ]
|
||||
do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user