gps manipulations tailored to sim7600h hat

This commit is contained in:
2026-02-09 02:11:55 +09:00
parent 992270ed00
commit 629c735eec
20 changed files with 2503 additions and 2355 deletions

View File

@@ -1,88 +1,88 @@
#!/usr/bin/env python3
"""One-click build and deploy for Smart Serow.
Combines build.py and deploy.py with sensible defaults.
Defaults to --restart since that's usually what you want.
"""
import argparse
import sys
from pathlib import Path
# Import sibling modules
sys.path.insert(0, str(Path(__file__).parent))
from build import build
from deploy import deploy
from deploy_backend import deploy as deploy_backend
def main():
parser = argparse.ArgumentParser(
description="Build and deploy Smart Serow in one step",
)
parser.add_argument(
"--clean", "-c",
action="store_true",
help="Clean CMake cache before building",
)
parser.add_argument(
"--no-restart",
action="store_true",
help="Don't restart service after deploy (default: restart)",
)
parser.add_argument(
"--build-only",
action="store_true",
help="Only build, don't deploy",
)
parser.add_argument(
"--deploy-only",
action="store_true",
help="Only deploy, don't build",
)
parser.add_argument(
"--ui",
action="store_true",
help="Build/deploy UI only (no backend)",
)
parser.add_argument(
"--backend",
action="store_true",
help="Deploy backend only (no UI, no build)",
)
args = parser.parse_args()
# Default: both UI and backend if neither flag specified
do_ui = args.ui or not args.backend
do_backend = args.backend or not args.ui
restart = not args.no_restart
# Build UI (only if doing UI and not deploy-only)
if do_ui and not args.deploy_only:
print()
if not build(clean=args.clean):
print("UI build failed!")
sys.exit(1)
# Deploy backend FIRST (no build step needed - it's Python)
# Backend must be up before UI connects to WebSocket
if do_backend and not args.build_only:
print()
if not deploy_backend(restart=restart):
print("Backend deploy failed!")
sys.exit(1)
# Deploy UI after backend is ready
if do_ui and not args.build_only:
print()
if not deploy(restart=restart):
print("UI deploy failed!")
sys.exit(1)
print()
print("=== All done! ===")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""One-click build and deploy for Smart Serow.
Combines build.py and deploy.py with sensible defaults.
Defaults to --restart since that's usually what you want.
"""
import argparse
import sys
from pathlib import Path
# Import sibling modules
sys.path.insert(0, str(Path(__file__).parent))
from build import build
from deploy import deploy
from deploy_backend import deploy as deploy_backend
def main():
parser = argparse.ArgumentParser(
description="Build and deploy Smart Serow in one step",
)
parser.add_argument(
"--clean", "-c",
action="store_true",
help="Clean CMake cache before building",
)
parser.add_argument(
"--no-restart",
action="store_true",
help="Don't restart service after deploy (default: restart)",
)
parser.add_argument(
"--build-only",
action="store_true",
help="Only build, don't deploy",
)
parser.add_argument(
"--deploy-only",
action="store_true",
help="Only deploy, don't build",
)
parser.add_argument(
"--ui",
action="store_true",
help="Build/deploy UI only (no backend)",
)
parser.add_argument(
"--backend",
action="store_true",
help="Deploy backend only (no UI, no build)",
)
args = parser.parse_args()
# Default: both UI and backend if neither flag specified
do_ui = args.ui or not args.backend
do_backend = args.backend or not args.ui
restart = not args.no_restart
# Build UI (only if doing UI and not deploy-only)
if do_ui and not args.deploy_only:
print()
if not build(clean=args.clean):
print("UI build failed!")
sys.exit(1)
# Deploy backend FIRST (no build step needed - it's Python)
# Backend must be up before UI connects to WebSocket
if do_backend and not args.build_only:
print()
if not deploy_backend(restart=restart):
print("Backend deploy failed!")
sys.exit(1)
# Deploy UI after backend is ready
if do_ui and not args.build_only:
print()
if not deploy(restart=restart):
print("UI deploy failed!")
sys.exit(1)
print()
print("=== All done! ===")
if __name__ == "__main__":
main()

View File

@@ -1,157 +1,157 @@
#!/usr/bin/env python3
"""Deploy script for Smart Serow Python backend.
Pushes backend source to Pi and optionally restarts service.
Completely independent from UI deploy.
"""
import argparse
import json
import subprocess
import sys
import time
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent.resolve()
PROJECT_ROOT = SCRIPT_DIR.parent
CONFIG_FILE = SCRIPT_DIR / "deploy_target.json"
BACKEND_DIR = PROJECT_ROOT / "pi" / "backend"
SERVICE_FILE = SCRIPT_DIR / "smartserow-backend.service"
def run(cmd: list[str], check: bool = True, **kwargs) -> subprocess.CompletedProcess:
"""Run a command."""
print(f"{' '.join(cmd)}")
return subprocess.run(cmd, check=check, **kwargs)
def load_config() -> dict:
"""Load deploy target configuration."""
if not CONFIG_FILE.exists():
print(f"ERROR: Config file not found: {CONFIG_FILE}")
print("Create it based on deploy_target.sample.json")
sys.exit(1)
with open(CONFIG_FILE) as f:
return json.load(f)
def deploy(restart: bool = False) -> bool:
"""Deploy backend to Pi. Returns True on success."""
config = load_config()
pi_user = config["user"]
pi_host = config["host"]
# Backend-specific config (with defaults)
remote_path = config.get("backend_path", "/opt/smartserow-backend")
service_name = config.get("backend_service", "smartserow-backend")
ssh_target = f"{pi_user}@{pi_host}"
print("=== Smart Serow Backend Deploy ===")
print(f"Target: {ssh_target}:{remote_path}")
print(f"Source: {BACKEND_DIR}")
if not BACKEND_DIR.exists():
print(f"ERROR: Backend directory not found: {BACKEND_DIR}")
return False
# Ensure remote directory exists
print()
print("Ensuring remote directory...")
run(["ssh", ssh_target, f"mkdir -p {remote_path}"])
# Sync backend source to Pi
# Exclude __pycache__, .venv, etc.
print()
print("Syncing files...")
run([
"rsync", "-avz", "--delete",
"--exclude", "__pycache__",
"--exclude", "*.pyc",
"--exclude", ".venv",
"--exclude", ".ruff_cache",
"--exclude", "uv.lock", # Let Pi generate its own lockfile
f"{BACKEND_DIR}/",
f"{ssh_target}:{remote_path}/",
])
# Ensure system GPIO package is installed (pip version needs compilation)
print()
print("Ensuring system GPIO package...")
run(
["ssh", ssh_target, "dpkg -s python3-rpi.gpio >/dev/null 2>&1 || sudo apt install -y python3-rpi.gpio"],
check=False,
)
# Create venv with system-site-packages if it doesn't exist
# This allows access to apt-installed packages like python3-rpi.gpio
print()
print("Ensuring venv with system-site-packages...")
run(
["ssh", ssh_target, f"cd {remote_path} && [ -d .venv ] || ~/.local/bin/uv venv --system-site-packages"],
check=False,
)
# Run uv sync to install/update dependencies
# Use full path since non-interactive SSH doesn't load .bashrc
print()
print("Running uv sync...")
result = run(
["ssh", ssh_target, f"cd {remote_path} && ~/.local/bin/uv sync"],
check=False,
)
if result.returncode != 0:
print("WARNING: uv sync failed - dependencies may be out of date")
print("Make sure uv is installed on Pi: curl -LsSf https://astral.sh/uv/install.sh | sh")
# Deploy service file if it exists
if SERVICE_FILE.exists():
print()
print("Deploying systemd service file...")
run(["scp", str(SERVICE_FILE), f"{ssh_target}:/tmp/"])
run([
"ssh", ssh_target,
f"sudo mv /tmp/{SERVICE_FILE.name} /etc/systemd/system/ && sudo systemctl daemon-reload"
])
# Restart service if requested
if restart:
print()
print(f"Restarting service: {service_name}")
run(["ssh", ssh_target, f"sudo systemctl restart {service_name}"], check=False)
time.sleep(2)
run(["ssh", ssh_target, f"systemctl status {service_name} --no-pager"], check=False)
else:
print()
print("Deploy complete. To restart service, run:")
print(f" ssh {ssh_target} 'sudo systemctl restart {service_name}'")
print()
print("Or run this script with --restart flag")
print()
print("Note: First-time setup on Pi requires:")
print(f" ssh {ssh_target}")
print(" curl -LsSf https://astral.sh/uv/install.sh | sh # Install uv")
print(" sudo apt install python3-rpi.gpio # GPIO support")
return True
def main():
parser = argparse.ArgumentParser(description="Deploy Smart Serow backend to Pi")
parser.add_argument(
"--restart", "-r",
action="store_true",
help="Restart the systemd service after deploy",
)
args = parser.parse_args()
success = deploy(restart=args.restart)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Deploy script for Smart Serow Python backend.
Pushes backend source to Pi and optionally restarts service.
Completely independent from UI deploy.
"""
import argparse
import json
import subprocess
import sys
import time
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent.resolve()
PROJECT_ROOT = SCRIPT_DIR.parent
CONFIG_FILE = SCRIPT_DIR / "deploy_target.json"
BACKEND_DIR = PROJECT_ROOT / "pi" / "backend"
SERVICE_FILE = SCRIPT_DIR / "smartserow-backend.service"
def run(cmd: list[str], check: bool = True, **kwargs) -> subprocess.CompletedProcess:
"""Run a command."""
print(f"{' '.join(cmd)}")
return subprocess.run(cmd, check=check, **kwargs)
def load_config() -> dict:
"""Load deploy target configuration."""
if not CONFIG_FILE.exists():
print(f"ERROR: Config file not found: {CONFIG_FILE}")
print("Create it based on deploy_target.sample.json")
sys.exit(1)
with open(CONFIG_FILE) as f:
return json.load(f)
def deploy(restart: bool = False) -> bool:
"""Deploy backend to Pi. Returns True on success."""
config = load_config()
pi_user = config["user"]
pi_host = config["host"]
# Backend-specific config (with defaults)
remote_path = config.get("backend_path", "/opt/smartserow-backend")
service_name = config.get("backend_service", "smartserow-backend")
ssh_target = f"{pi_user}@{pi_host}"
print("=== Smart Serow Backend Deploy ===")
print(f"Target: {ssh_target}:{remote_path}")
print(f"Source: {BACKEND_DIR}")
if not BACKEND_DIR.exists():
print(f"ERROR: Backend directory not found: {BACKEND_DIR}")
return False
# Ensure remote directory exists
print()
print("Ensuring remote directory...")
run(["ssh", ssh_target, f"mkdir -p {remote_path}"])
# Sync backend source to Pi
# Exclude __pycache__, .venv, etc.
print()
print("Syncing files...")
run([
"rsync", "-avz", "--delete",
"--exclude", "__pycache__",
"--exclude", "*.pyc",
"--exclude", ".venv",
"--exclude", ".ruff_cache",
"--exclude", "uv.lock", # Let Pi generate its own lockfile
f"{BACKEND_DIR}/",
f"{ssh_target}:{remote_path}/",
])
# Ensure system GPIO package is installed (pip version needs compilation)
print()
print("Ensuring system GPIO package...")
run(
["ssh", ssh_target, "dpkg -s python3-rpi.gpio >/dev/null 2>&1 || sudo apt install -y python3-rpi.gpio"],
check=False,
)
# Create venv with system-site-packages if it doesn't exist
# This allows access to apt-installed packages like python3-rpi.gpio
print()
print("Ensuring venv with system-site-packages...")
run(
["ssh", ssh_target, f"cd {remote_path} && [ -d .venv ] || ~/.local/bin/uv venv --system-site-packages"],
check=False,
)
# Run uv sync to install/update dependencies
# Use full path since non-interactive SSH doesn't load .bashrc
print()
print("Running uv sync...")
result = run(
["ssh", ssh_target, f"cd {remote_path} && ~/.local/bin/uv sync"],
check=False,
)
if result.returncode != 0:
print("WARNING: uv sync failed - dependencies may be out of date")
print("Make sure uv is installed on Pi: curl -LsSf https://astral.sh/uv/install.sh | sh")
# Deploy service file if it exists
if SERVICE_FILE.exists():
print()
print("Deploying systemd service file...")
run(["scp", str(SERVICE_FILE), f"{ssh_target}:/tmp/"])
run([
"ssh", ssh_target,
f"sudo mv /tmp/{SERVICE_FILE.name} /etc/systemd/system/ && sudo systemctl daemon-reload"
])
# Restart service if requested
if restart:
print()
print(f"Restarting service: {service_name}")
run(["ssh", ssh_target, f"sudo systemctl restart {service_name}"], check=False)
time.sleep(2)
run(["ssh", ssh_target, f"systemctl status {service_name} --no-pager"], check=False)
else:
print()
print("Deploy complete. To restart service, run:")
print(f" ssh {ssh_target} 'sudo systemctl restart {service_name}'")
print()
print("Or run this script with --restart flag")
print()
print("Note: First-time setup on Pi requires:")
print(f" ssh {ssh_target}")
print(" curl -LsSf https://astral.sh/uv/install.sh | sh # Install uv")
print(" sudo apt install python3-rpi.gpio # GPIO support")
return True
def main():
parser = argparse.ArgumentParser(description="Deploy Smart Serow backend to Pi")
parser.add_argument(
"--restart", "-r",
action="store_true",
help="Restart the systemd service after deploy",
)
args = parser.parse_args()
success = deploy(restart=args.restart)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()