|
|
@ -29,7 +29,7 @@ from selfdrive.loggerd.config import ROOT |
|
|
|
from selfdrive.swaglog import cloudlog |
|
|
|
from selfdrive.swaglog import cloudlog |
|
|
|
|
|
|
|
|
|
|
|
ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai') |
|
|
|
ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai') |
|
|
|
HANDLER_THREADS = os.getenv('HANDLER_THREADS', 4) |
|
|
|
HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) |
|
|
|
LOCAL_PORT_WHITELIST = set([8022]) |
|
|
|
LOCAL_PORT_WHITELIST = set([8022]) |
|
|
|
|
|
|
|
|
|
|
|
dispatcher["echo"] = lambda s: s |
|
|
|
dispatcher["echo"] = lambda s: s |
|
|
@ -39,6 +39,7 @@ upload_queue: Any = queue.Queue() |
|
|
|
cancelled_uploads: Any = set() |
|
|
|
cancelled_uploads: Any = set() |
|
|
|
UploadItem = namedtuple('UploadItem', ['path', 'url', 'headers', 'created_at', 'id']) |
|
|
|
UploadItem = namedtuple('UploadItem', ['path', 'url', 'headers', 'created_at', 'id']) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_long_poll(ws): |
|
|
|
def handle_long_poll(ws): |
|
|
|
end_event = threading.Event() |
|
|
|
end_event = threading.Event() |
|
|
|
|
|
|
|
|
|
|
@ -60,9 +61,10 @@ def handle_long_poll(ws): |
|
|
|
end_event.set() |
|
|
|
end_event.set() |
|
|
|
raise |
|
|
|
raise |
|
|
|
finally: |
|
|
|
finally: |
|
|
|
for i, thread in enumerate(threads): |
|
|
|
for thread in threads: |
|
|
|
thread.join() |
|
|
|
thread.join() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def jsonrpc_handler(end_event): |
|
|
|
def jsonrpc_handler(end_event): |
|
|
|
dispatcher["startLocalProxy"] = partial(startLocalProxy, end_event) |
|
|
|
dispatcher["startLocalProxy"] = partial(startLocalProxy, end_event) |
|
|
|
while not end_event.is_set(): |
|
|
|
while not end_event.is_set(): |
|
|
@ -76,6 +78,7 @@ def jsonrpc_handler(end_event): |
|
|
|
cloudlog.exception("athena jsonrpc handler failed") |
|
|
|
cloudlog.exception("athena jsonrpc handler failed") |
|
|
|
response_queue.put_nowait(json.dumps({"error": str(e)})) |
|
|
|
response_queue.put_nowait(json.dumps({"error": str(e)})) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def upload_handler(end_event): |
|
|
|
def upload_handler(end_event): |
|
|
|
while not end_event.is_set(): |
|
|
|
while not end_event.is_set(): |
|
|
|
try: |
|
|
|
try: |
|
|
@ -89,6 +92,7 @@ def upload_handler(end_event): |
|
|
|
except Exception: |
|
|
|
except Exception: |
|
|
|
cloudlog.exception("athena.upload_handler.exception") |
|
|
|
cloudlog.exception("athena.upload_handler.exception") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _do_upload(upload_item): |
|
|
|
def _do_upload(upload_item): |
|
|
|
with open(upload_item.path, "rb") as f: |
|
|
|
with open(upload_item.path, "rb") as f: |
|
|
|
size = os.fstat(f.fileno()).st_size |
|
|
|
size = os.fstat(f.fileno()).st_size |
|
|
@ -97,6 +101,7 @@ def _do_upload(upload_item): |
|
|
|
headers={**upload_item.headers, 'Content-Length': str(size)}, |
|
|
|
headers={**upload_item.headers, 'Content-Length': str(size)}, |
|
|
|
timeout=10) |
|
|
|
timeout=10) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# security: user should be able to request any message from their car |
|
|
|
# security: user should be able to request any message from their car |
|
|
|
@dispatcher.add_method |
|
|
|
@dispatcher.add_method |
|
|
|
def getMessage(service=None, timeout=1000): |
|
|
|
def getMessage(service=None, timeout=1000): |
|
|
@ -111,11 +116,13 @@ def getMessage(service=None, timeout=1000): |
|
|
|
|
|
|
|
|
|
|
|
return ret.to_dict() |
|
|
|
return ret.to_dict() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dispatcher.add_method |
|
|
|
@dispatcher.add_method |
|
|
|
def listDataDirectory(): |
|
|
|
def listDataDirectory(): |
|
|
|
files = [os.path.relpath(os.path.join(dp, f), ROOT) for dp, dn, fn in os.walk(ROOT) for f in fn] |
|
|
|
files = [os.path.relpath(os.path.join(dp, f), ROOT) for dp, dn, fn in os.walk(ROOT) for f in fn] |
|
|
|
return files |
|
|
|
return files |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dispatcher.add_method |
|
|
|
@dispatcher.add_method |
|
|
|
def reboot(): |
|
|
|
def reboot(): |
|
|
|
thermal_sock = messaging.sub_sock("thermal", timeout=1000) |
|
|
|
thermal_sock = messaging.sub_sock("thermal", timeout=1000) |
|
|
@ -131,6 +138,7 @@ def reboot(): |
|
|
|
|
|
|
|
|
|
|
|
return {"success": 1} |
|
|
|
return {"success": 1} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dispatcher.add_method |
|
|
|
@dispatcher.add_method |
|
|
|
def uploadFileToUrl(fn, url, headers): |
|
|
|
def uploadFileToUrl(fn, url, headers): |
|
|
|
if len(fn) == 0 or fn[0] == '/' or '..' in fn: |
|
|
|
if len(fn) == 0 or fn[0] == '/' or '..' in fn: |
|
|
@ -139,7 +147,7 @@ def uploadFileToUrl(fn, url, headers): |
|
|
|
if not os.path.exists(path): |
|
|
|
if not os.path.exists(path): |
|
|
|
return 404 |
|
|
|
return 404 |
|
|
|
|
|
|
|
|
|
|
|
item = UploadItem(path=path, url=url, headers=headers, created_at=int(time.time()*1000), id=None) |
|
|
|
item = UploadItem(path=path, url=url, headers=headers, created_at=int(time.time() * 1000), id=None) |
|
|
|
upload_id = hashlib.sha1(str(item).encode()).hexdigest() |
|
|
|
upload_id = hashlib.sha1(str(item).encode()).hexdigest() |
|
|
|
item = item._replace(id=upload_id) |
|
|
|
item = item._replace(id=upload_id) |
|
|
|
|
|
|
|
|
|
|
@ -147,10 +155,12 @@ def uploadFileToUrl(fn, url, headers): |
|
|
|
|
|
|
|
|
|
|
|
return {"enqueued": 1, "item": item._asdict()} |
|
|
|
return {"enqueued": 1, "item": item._asdict()} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dispatcher.add_method |
|
|
|
@dispatcher.add_method |
|
|
|
def listUploadQueue(): |
|
|
|
def listUploadQueue(): |
|
|
|
return [item._asdict() for item in list(upload_queue.queue)] |
|
|
|
return [item._asdict() for item in list(upload_queue.queue)] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dispatcher.add_method |
|
|
|
@dispatcher.add_method |
|
|
|
def cancelUpload(upload_id): |
|
|
|
def cancelUpload(upload_id): |
|
|
|
upload_ids = set(item.id for item in list(upload_queue.queue)) |
|
|
|
upload_ids = set(item.id for item in list(upload_queue.queue)) |
|
|
@ -160,6 +170,7 @@ def cancelUpload(upload_id): |
|
|
|
cancelled_uploads.add(upload_id) |
|
|
|
cancelled_uploads.add(upload_id) |
|
|
|
return {"success": 1} |
|
|
|
return {"success": 1} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def startLocalProxy(global_end_event, remote_ws_uri, local_port): |
|
|
|
def startLocalProxy(global_end_event, remote_ws_uri, local_port): |
|
|
|
try: |
|
|
|
try: |
|
|
|
if local_port not in LOCAL_PORT_WHITELIST: |
|
|
|
if local_port not in LOCAL_PORT_WHITELIST: |
|
|
@ -190,18 +201,21 @@ def startLocalProxy(global_end_event, remote_ws_uri, local_port): |
|
|
|
cloudlog.exception("athenad.startLocalProxy.exception") |
|
|
|
cloudlog.exception("athenad.startLocalProxy.exception") |
|
|
|
raise e |
|
|
|
raise e |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dispatcher.add_method |
|
|
|
@dispatcher.add_method |
|
|
|
def getPublicKey(): |
|
|
|
def getPublicKey(): |
|
|
|
if not os.path.isfile(PERSIST+'/comma/id_rsa.pub'): |
|
|
|
if not os.path.isfile(PERSIST + '/comma/id_rsa.pub'): |
|
|
|
return None |
|
|
|
return None |
|
|
|
|
|
|
|
|
|
|
|
with open(PERSIST+'/comma/id_rsa.pub', 'r') as f: |
|
|
|
with open(PERSIST + '/comma/id_rsa.pub', 'r') as f: |
|
|
|
return f.read() |
|
|
|
return f.read() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dispatcher.add_method |
|
|
|
@dispatcher.add_method |
|
|
|
def getSshAuthorizedKeys(): |
|
|
|
def getSshAuthorizedKeys(): |
|
|
|
return Params().get("GithubSshKeys", encoding='utf8') or '' |
|
|
|
return Params().get("GithubSshKeys", encoding='utf8') or '' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dispatcher.add_method |
|
|
|
@dispatcher.add_method |
|
|
|
def getSimInfo(): |
|
|
|
def getSimInfo(): |
|
|
|
sim_state = android.getprop("gsm.sim.state").split(",") |
|
|
|
sim_state = android.getprop("gsm.sim.state").split(",") |
|
|
@ -220,6 +234,7 @@ def getSimInfo(): |
|
|
|
'data_connected': cell_data_connected |
|
|
|
'data_connected': cell_data_connected |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dispatcher.add_method |
|
|
|
@dispatcher.add_method |
|
|
|
def takeSnapshot(): |
|
|
|
def takeSnapshot(): |
|
|
|
from selfdrive.camerad.snapshot.snapshot import snapshot, jpeg_write |
|
|
|
from selfdrive.camerad.snapshot.snapshot import snapshot, jpeg_write |
|
|
@ -237,6 +252,7 @@ def takeSnapshot(): |
|
|
|
else: |
|
|
|
else: |
|
|
|
raise Exception("not available while camerad is started") |
|
|
|
raise Exception("not available while camerad is started") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ws_proxy_recv(ws, local_sock, ssock, end_event, global_end_event): |
|
|
|
def ws_proxy_recv(ws, local_sock, ssock, end_event, global_end_event): |
|
|
|
while not (end_event.is_set() or global_end_event.is_set()): |
|
|
|
while not (end_event.is_set() or global_end_event.is_set()): |
|
|
|
try: |
|
|
|
try: |
|
|
@ -252,6 +268,7 @@ def ws_proxy_recv(ws, local_sock, ssock, end_event, global_end_event): |
|
|
|
local_sock.close() |
|
|
|
local_sock.close() |
|
|
|
end_event.set() |
|
|
|
end_event.set() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ws_proxy_send(ws, local_sock, signal_sock, end_event): |
|
|
|
def ws_proxy_send(ws, local_sock, signal_sock, end_event): |
|
|
|
while not end_event.is_set(): |
|
|
|
while not end_event.is_set(): |
|
|
|
try: |
|
|
|
try: |
|
|
@ -272,6 +289,7 @@ def ws_proxy_send(ws, local_sock, signal_sock, end_event): |
|
|
|
cloudlog.exception("athenad.ws_proxy_send.exception") |
|
|
|
cloudlog.exception("athenad.ws_proxy_send.exception") |
|
|
|
end_event.set() |
|
|
|
end_event.set() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ws_recv(ws, end_event): |
|
|
|
def ws_recv(ws, end_event): |
|
|
|
while not end_event.is_set(): |
|
|
|
while not end_event.is_set(): |
|
|
|
try: |
|
|
|
try: |
|
|
@ -281,13 +299,14 @@ def ws_recv(ws, end_event): |
|
|
|
data = data.decode("utf-8") |
|
|
|
data = data.decode("utf-8") |
|
|
|
payload_queue.put_nowait(data) |
|
|
|
payload_queue.put_nowait(data) |
|
|
|
elif opcode == ABNF.OPCODE_PING: |
|
|
|
elif opcode == ABNF.OPCODE_PING: |
|
|
|
Params().put("LastAthenaPingTime", str(int(sec_since_boot()*1e9))) |
|
|
|
Params().put("LastAthenaPingTime", str(int(sec_since_boot() * 1e9))) |
|
|
|
except WebSocketTimeoutException: |
|
|
|
except WebSocketTimeoutException: |
|
|
|
pass |
|
|
|
pass |
|
|
|
except Exception: |
|
|
|
except Exception: |
|
|
|
cloudlog.exception("athenad.ws_recv.exception") |
|
|
|
cloudlog.exception("athenad.ws_recv.exception") |
|
|
|
end_event.set() |
|
|
|
end_event.set() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ws_send(ws, end_event): |
|
|
|
def ws_send(ws, end_event): |
|
|
|
while not end_event.is_set(): |
|
|
|
while not end_event.is_set(): |
|
|
|
try: |
|
|
|
try: |
|
|
@ -299,9 +318,11 @@ def ws_send(ws, end_event): |
|
|
|
cloudlog.exception("athenad.ws_send.exception") |
|
|
|
cloudlog.exception("athenad.ws_send.exception") |
|
|
|
end_event.set() |
|
|
|
end_event.set() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def backoff(retries): |
|
|
|
def backoff(retries): |
|
|
|
return random.randrange(0, min(128, int(2 ** retries))) |
|
|
|
return random.randrange(0, min(128, int(2 ** retries))) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(): |
|
|
|
def main(): |
|
|
|
params = Params() |
|
|
|
params = Params() |
|
|
|
dongle_id = params.get("DongleId").decode('utf-8') |
|
|
|
dongle_id = params.get("DongleId").decode('utf-8') |
|
|
@ -328,5 +349,6 @@ def main(): |
|
|
|
|
|
|
|
|
|
|
|
time.sleep(backoff(conn_retries)) |
|
|
|
time.sleep(backoff(conn_retries)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
if __name__ == "__main__": |
|
|
|
main() |
|
|
|
main() |
|
|
|