#!/usr/bin/env python3
import time
import json
import jwt
from pathlib import Path
from datetime import datetime , timedelta , UTC
from openpilot . common . api import api_get
from openpilot . common . params import Params
from openpilot . selfdrive . selfdrived . alertmanager import set_offroad_alert
from openpilot . system . hardware import HARDWARE , PC
from openpilot . system . hardware . hw import Paths
from openpilot . system . ui . spinner import Spinner
from openpilot . common . swaglog import cloudlog
UNREGISTERED_DONGLE_ID = " UnregisteredDevice "
def is_registered_device ( ) - > bool :
dongle = Params ( ) . get ( " DongleId " , encoding = ' utf-8 ' )
return dongle not in ( None , UNREGISTERED_DONGLE_ID )
def register ( show_spinner = False ) - > str | None :
"""
All devices built since March 2024 come with all
info stored in / persist / . This is kept around
only for devices built before then .
With a backend update to take serial number instead
of dongle ID to some endpoints , this can be removed
entirely .
"""
params = Params ( )
dongle_id : str | None = params . get ( " DongleId " , encoding = ' utf8 ' )
if dongle_id is None and Path ( Paths . persist_root ( ) + " /comma/dongle_id " ) . is_file ( ) :
# not all devices will have this; added early in comma 3X production (2/28/24)
with open ( Paths . persist_root ( ) + " /comma/dongle_id " ) as f :
dongle_id = f . read ( ) . strip ( )
pubkey = Path ( Paths . persist_root ( ) + " /comma/id_rsa.pub " )
if not pubkey . is_file ( ) :
dongle_id = UNREGISTERED_DONGLE_ID
cloudlog . warning ( f " missing public key: { pubkey } " )
elif dongle_id is None :
if show_spinner :
spinner = Spinner ( )
spinner . update ( " registering device " )
# Create registration token, in the future, this key will make JWTs directly
with open ( Paths . persist_root ( ) + " /comma/id_rsa.pub " ) as f1 , open ( Paths . persist_root ( ) + " /comma/id_rsa " ) as f2 :
public_key = f1 . read ( )
private_key = f2 . read ( )
# Block until we get the imei
serial = HARDWARE . get_serial ( )
start_time = time . monotonic ( )
imei1 : str | None = None
imei2 : str | None = None
while imei1 is None and imei2 is None :
try :
imei1 , imei2 = HARDWARE . get_imei ( 0 ) , HARDWARE . get_imei ( 1 )
except Exception :
cloudlog . exception ( " Error getting imei, trying again... " )
time . sleep ( 1 )
if time . monotonic ( ) - start_time > 60 and show_spinner :
spinner . update ( f " registering device - serial: { serial } , IMEI: ( { imei1 } , { imei2 } ) " )
backoff = 0
start_time = time . monotonic ( )
while True :
try :
register_token = jwt . encode ( { ' register ' : True , ' exp ' : datetime . now ( UTC ) . replace ( tzinfo = None ) + timedelta ( hours = 1 ) } , private_key , algorithm = ' RS256 ' )
cloudlog . info ( " getting pilotauth " )
resp = api_get ( " v2/pilotauth/ " , method = ' POST ' , timeout = 15 ,
imei = imei1 , imei2 = imei2 , serial = serial , public_key = public_key , register_token = register_token )
if resp . status_code in ( 402 , 403 ) :
cloudlog . info ( f " Unable to register device, got { resp . status_code } " )
dongle_id = UNREGISTERED_DONGLE_ID
else :
dongleauth = json . loads ( resp . text )
dongle_id = dongleauth [ " dongle_id " ]
break
except Exception :
cloudlog . exception ( " failed to authenticate " )
backoff = min ( backoff + 1 , 15 )
time . sleep ( backoff )
if time . monotonic ( ) - start_time > 60 and show_spinner :
spinner . update ( f " registering device - serial: { serial } , IMEI: ( { imei1 } , { imei2 } ) " )
if show_spinner :
spinner . close ( )
if dongle_id :
params . put ( " DongleId " , dongle_id )
set_offroad_alert ( " Offroad_UnofficialHardware " , ( dongle_id == UNREGISTERED_DONGLE_ID ) and not PC )
return dongle_id
if __name__ == " __main__ " :
print ( register ( ) )