mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-20 09:12:05 +08:00
0668513d13
Removed the SunnylinkApi call from the registration process as it was no longer in use. Improved error logging by adding directory checks and more descriptive exception handling in the sunnylink API.
156 lines
6.2 KiB
Python
156 lines
6.2 KiB
Python
import os
|
|
import time
|
|
import jwt
|
|
import json
|
|
from pathlib import Path
|
|
from datetime import datetime, timedelta
|
|
from openpilot.common.params import Params
|
|
from openpilot.system.hardware import HARDWARE
|
|
from openpilot.system.hardware.hw import Paths
|
|
|
|
from openpilot.common.api.base import BaseApi
|
|
|
|
API_HOST = os.getenv('SUNNYLINK_API_HOST', 'https://stg.api.sunnypilot.ai')
|
|
UNREGISTERED_SUNNYLINK_DONGLE_ID = "UnregisteredDevice"
|
|
MAX_RETRIES = 6
|
|
CRASH_LOG_DIR = '/data/community/crashes'
|
|
|
|
class SunnylinkApi(BaseApi):
|
|
def __init__(self, dongle_id):
|
|
super().__init__(dongle_id, API_HOST)
|
|
self.user_agent = "sunnypilot-"
|
|
self.spinner = None
|
|
self.params = Params()
|
|
|
|
def api_get(self, endpoint, method='GET', timeout=10, access_token=None, **kwargs):
|
|
if not self.params.get_bool("SunnylinkEnabled"):
|
|
return None
|
|
|
|
return super().api_get(endpoint, method, timeout, access_token, **kwargs)
|
|
|
|
def resume_queued(self, timeout=10, **kwargs):
|
|
sunnylinkId, commaId = self._resolve_dongle_ids()
|
|
return self.api_get(f"ws/{sunnylinkId}/resume_queued", "POST", timeout, access_token=self.get_token(), **kwargs)
|
|
|
|
def get_token(self, expiry_hours=1):
|
|
# Add your additional data here
|
|
additional_data = {}
|
|
return super()._get_token(expiry_hours, **additional_data)
|
|
|
|
def _status_update(self, message):
|
|
print(message)
|
|
if self.spinner:
|
|
self.spinner.update(message)
|
|
time.sleep(0.5)
|
|
|
|
def _resolve_dongle_ids(self):
|
|
sunnylink_dongle_id = self.params.get("SunnylinkDongleId", encoding='utf-8')
|
|
comma_dongle_id = self.dongle_id or self.params.get("DongleId", encoding='utf-8')
|
|
return sunnylink_dongle_id, comma_dongle_id
|
|
|
|
def _resolve_imeis(self):
|
|
imei1, imei2 = None, None
|
|
imei_try = 0
|
|
while imei1 is None and imei2 is None and imei_try < MAX_RETRIES:
|
|
try:
|
|
imei1, imei2 = self.params.get("IMEI", encoding='utf8') or HARDWARE.get_imei(0), HARDWARE.get_imei(1)
|
|
except Exception:
|
|
self._status_update(f"Error getting imei, trying again... [{imei_try+1}/{MAX_RETRIES}]")
|
|
time.sleep(1)
|
|
imei_try += 1
|
|
return imei1, imei2
|
|
|
|
def _resolve_serial(self):
|
|
serial = self.params.get("HardwareSerial", encoding='utf8') or HARDWARE.get_serial()
|
|
return serial
|
|
|
|
def register_device(self, spinner=None, timeout=60, verbose=False):
|
|
self.spinner = spinner
|
|
|
|
sunnylink_dongle_id, comma_dongle_id = self._resolve_dongle_ids()
|
|
|
|
if comma_dongle_id is None:
|
|
self._status_update("Comma dongle ID not found, deferring sunnylink's registration to comma's registration process.")
|
|
return None
|
|
|
|
imei1, imei2 = self._resolve_imeis()
|
|
serial = self._resolve_serial()
|
|
|
|
if sunnylink_dongle_id not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID):
|
|
return sunnylink_dongle_id
|
|
|
|
privkey_path = Path(Paths.persist_root()+"/comma/id_rsa")
|
|
pubkey_path = Path(Paths.persist_root()+"/comma/id_rsa.pub")
|
|
|
|
start_time = time.monotonic()
|
|
successful_registration = False
|
|
if not pubkey_path.is_file():
|
|
sunnylink_dongle_id = UNREGISTERED_SUNNYLINK_DONGLE_ID
|
|
self._status_update("Public key not found, setting dongle ID to unregistered.")
|
|
else:
|
|
Params().put("LastSunnylinkPingTime", "0") # Reset the last ping time to 0 if we are trying to register
|
|
with pubkey_path.open() as f1, privkey_path.open() as f2:
|
|
public_key = f1.read()
|
|
private_key = f2.read()
|
|
|
|
backoff = 1
|
|
while True:
|
|
register_token = jwt.encode({'register': True, 'exp': datetime.utcnow() + timedelta(hours=1)}, private_key, algorithm='RS256')
|
|
try:
|
|
if verbose or time.monotonic() - start_time < timeout / 2:
|
|
self._status_update("Registering device to sunnylink...")
|
|
elif time.monotonic() - start_time >= timeout / 2:
|
|
self._status_update("Still registering device to sunnylink...")
|
|
|
|
resp = self.api_get("v2/pilotauth/", method='POST', timeout=15, imei=imei1, imei2=imei2, serial=serial, comma_dongle_id=comma_dongle_id, public_key=public_key, register_token=register_token)
|
|
|
|
if resp is None:
|
|
raise Exception("Unable to register device, request was None")
|
|
|
|
if resp.status_code in (409, 412):
|
|
timeout = time.monotonic() - start_time # Don't retry if the public key is already in use
|
|
key_in_use = "Public key is already in use, is your key unique? Contact your vendor for a new key."
|
|
unsafe_key = "Public key is known to not be unique and it's unsafe. Contact your vendor for a new key."
|
|
error_message = key_in_use if resp.status_code == 409 else unsafe_key
|
|
raise Exception(error_message)
|
|
|
|
if resp.status_code != 200:
|
|
raise Exception(f"Failed to register with sunnylink. Status code: {resp.status_code}\nData\n:{resp.text}")
|
|
|
|
dongleauth = json.loads(resp.text)
|
|
sunnylink_dongle_id = dongleauth["device_id"]
|
|
if sunnylink_dongle_id:
|
|
self._status_update("Device registered successfully.")
|
|
successful_registration = True
|
|
break
|
|
except Exception as e:
|
|
if verbose:
|
|
self._status_update(f"Waiting {backoff}s before retry, Exception occurred during registration: [{str(e)}]")
|
|
|
|
if not os.path.exists(CRASH_LOG_DIR):
|
|
os.makedirs(CRASH_LOG_DIR)
|
|
|
|
with open(f'{CRASH_LOG_DIR}/error.txt', 'a') as f:
|
|
f.write(f"[{datetime.now()}] sunnylink: {str(e)}\n")
|
|
|
|
backoff = min(backoff * 2, 60)
|
|
time.sleep(backoff)
|
|
|
|
if time.monotonic() - start_time > timeout:
|
|
self._status_update(f"Giving up on sunnylink's registration after {timeout}s. Will retry on next boot.")
|
|
time.sleep(3)
|
|
break
|
|
|
|
self.params.put("SunnylinkDongleId", sunnylink_dongle_id or UNREGISTERED_SUNNYLINK_DONGLE_ID)
|
|
|
|
# Set the last ping time to the current time since we were just talking to the API
|
|
last_ping = int(time.monotonic() * 1e9) if successful_registration else start_time
|
|
Params().put("LastSunnylinkPingTime", str(last_ping))
|
|
|
|
# Disable sunnylink if registration was not successful
|
|
if not successful_registration:
|
|
Params().put_bool("SunnylinkEnabled", False)
|
|
|
|
self.spinner = None
|
|
return sunnylink_dongle_id
|