feat: add multi-device provisioning and local remote-config UI
- add multi-device portal device management, key rotation, and sync request APIs - add Pi settings UI for remote server URL, device name, and API key - add local test-connection and sync-now actions - make upload and config sync reuse dynamic shared credentials - add Phase 25.5c migration and docs updates Co-authored-by: Copilot <[email protected]>
This commit is contained in:
+27
-19
@@ -41,17 +41,9 @@ import time
|
||||
from threading import Thread
|
||||
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
from requests_toolbelt import MultipartEncoder
|
||||
|
||||
from modules import app_sql, app_dash, app_cellular, app_video
|
||||
|
||||
_BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
load_dotenv(os.path.join(_BASE_DIR, "db", "upload_creds.env"))
|
||||
|
||||
SERVER_URL = os.getenv("YARIS_SERVER_URL", "").rstrip("/")
|
||||
DEVICE_NAME = os.getenv("YARIS_DEVICE_NAME", "")
|
||||
API_KEY = os.getenv("YARIS_API_KEY", "")
|
||||
from modules import app_sql, app_dash, app_cellular, app_remote_config, app_video
|
||||
|
||||
UPLOAD_INTERVAL = 30 # seconds between upload attempts once caught up
|
||||
CATCHUP_INTERVAL = 1 # seconds between batches while draining a backlog
|
||||
@@ -75,12 +67,24 @@ _last_success_at = None
|
||||
_last_network_path = None # "wifi" or "cellular" - last path a batch actually went out on
|
||||
|
||||
|
||||
def _headers():
|
||||
return {"X-Device-Name": DEVICE_NAME, "X-Api-Key": API_KEY}
|
||||
def _credentials():
|
||||
return app_remote_config.server_credentials()
|
||||
|
||||
|
||||
def _headers(credentials):
|
||||
return {
|
||||
"X-Device-Name": credentials["device_name"],
|
||||
"X-Api-Key": credentials["api_key"],
|
||||
}
|
||||
|
||||
|
||||
def is_configured():
|
||||
return bool(SERVER_URL and DEVICE_NAME and API_KEY)
|
||||
credentials = _credentials()
|
||||
return bool(
|
||||
credentials["server_url"]
|
||||
and credentials["device_name"]
|
||||
and credentials["api_key"]
|
||||
)
|
||||
|
||||
|
||||
def is_dashcam_upload_enabled():
|
||||
@@ -151,6 +155,7 @@ def _upload_tracking_batch():
|
||||
rows = app_sql.get_unsent_tracking_rows(limit=TRACKING_BATCH_SIZE)
|
||||
if not rows:
|
||||
return 0
|
||||
credentials = _credentials()
|
||||
|
||||
points = [
|
||||
{
|
||||
@@ -168,9 +173,9 @@ def _upload_tracking_batch():
|
||||
|
||||
try:
|
||||
resp = requests.post(
|
||||
f"{SERVER_URL}/api/v1/tracking/batch",
|
||||
f"{credentials['server_url']}/api/v1/tracking/batch",
|
||||
json={"points": points},
|
||||
headers=_headers(),
|
||||
headers=_headers(credentials),
|
||||
timeout=15,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
@@ -182,7 +187,9 @@ def _upload_tracking_batch():
|
||||
body_bytes = json.dumps({"points": points}).encode()
|
||||
try:
|
||||
status_code, _body = app_cellular.http_post_json(
|
||||
f"{SERVER_URL}/api/v1/tracking/batch", body_bytes, _headers()
|
||||
f"{credentials['server_url']}/api/v1/tracking/batch",
|
||||
body_bytes,
|
||||
_headers(credentials),
|
||||
)
|
||||
except app_cellular.CellularError as cell_err:
|
||||
# Neither path worked - raise the original WiFi error so the
|
||||
@@ -207,10 +214,11 @@ def _upload_video_chunked(abs_path, rel_path, sha256):
|
||||
re-query /status and resume from the first missing chunk instead of
|
||||
starting over.
|
||||
"""
|
||||
credentials = _credentials()
|
||||
status_resp = requests.get(
|
||||
f"{SERVER_URL}/api/v1/dashcam/upload/status",
|
||||
f"{credentials['server_url']}/api/v1/dashcam/upload/status",
|
||||
params={"relative_path": rel_path, "sha256": sha256},
|
||||
headers=_headers(),
|
||||
headers=_headers(credentials),
|
||||
timeout=15,
|
||||
)
|
||||
status_resp.raise_for_status()
|
||||
@@ -241,9 +249,9 @@ def _upload_video_chunked(abs_path, rel_path, sha256):
|
||||
}
|
||||
)
|
||||
resp = requests.post(
|
||||
f"{SERVER_URL}/api/v1/dashcam/upload/chunk",
|
||||
f"{credentials['server_url']}/api/v1/dashcam/upload/chunk",
|
||||
data=encoder,
|
||||
headers={**_headers(), "Content-Type": encoder.content_type},
|
||||
headers={**_headers(credentials), "Content-Type": encoder.content_type},
|
||||
timeout=120,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
Reference in New Issue
Block a user