From 85d069044a988b5d2d5ae1b2c160e82869d92524 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 13 Jun 2024 16:53:16 +0000 Subject: [PATCH 1/3] ui: sunnypilot Terms of Service --- common/params.cc | 2 ++ selfdrive/assets/offroad/sp_tc.html | 44 +++++++++++++++++++++++++++ selfdrive/ui/qt/offroad/onboarding.cc | 20 ++++++++++-- selfdrive/ui/qt/offroad/onboarding.h | 7 +++-- system/hardware/hardwared.py | 3 +- system/manager/manager.py | 4 ++- system/version.py | 2 ++ 7 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 selfdrive/assets/offroad/sp_tc.html diff --git a/common/params.cc b/common/params.cc index 7762c5cfec..73906d35f4 100644 --- a/common/params.cc +++ b/common/params.cc @@ -258,6 +258,7 @@ std::unordered_map keys = { {"ForceOffroad", CLEAR_ON_MANAGER_START}, {"GmapKey", PERSISTENT | BACKUP}, {"HandsOnWheelMonitoring", PERSISTENT | BACKUP}, + {"HasAcceptedTermsSP", PERSISTENT}, {"HideVEgoUi", PERSISTENT | BACKUP}, {"HkgSmoothStop", PERSISTENT | BACKUP}, {"HotspotOnBoot", PERSISTENT}, @@ -311,6 +312,7 @@ std::unordered_map keys = { {"SunnylinkDongleId", PERSISTENT}, {"SunnylinkEnabled", PERSISTENT}, {"SunnylinkdPid", PERSISTENT}, + {"TermsVersionSunnypilot", PERSISTENT}, {"TorqueDeadzoneDeg", PERSISTENT | BACKUP}, {"TorqueFriction", PERSISTENT | BACKUP}, {"TorqueMaxLatAccel", PERSISTENT | BACKUP}, diff --git a/selfdrive/assets/offroad/sp_tc.html b/selfdrive/assets/offroad/sp_tc.html new file mode 100644 index 0000000000..7bed194516 --- /dev/null +++ b/selfdrive/assets/offroad/sp_tc.html @@ -0,0 +1,44 @@ + + + + + sunnypilot Terms of Service + + + + +

The Terms and Conditions below are effective for all users

+

Last Updated on June 13, 2024

+

Please read these Terms of Use (“Terms”) carefully before using sunnypilot which is software developed by SUNNYPILOT LLC, a Limited Liability Company organized under the laws of Virginia ("sunnypilot," “us,” “we,” or “our”). sunnypilot is a software forked from openpilot which is open-sourced software developed by Comma.ai, Inc., a corporation organized under the laws of Delaware (“comma”). sunnypilot builds upon the foundation of openpilot by incorporating additional features and improvements. openpilot can be found on https://github.com/commaai/openpilot

+

Before using and by accessing sunnypilot, you indicate that you have read, understood, and agree to these Terms. These Terms apply to all users and others who access or use sunnypilot. If others use sunnypilot through your user account or vehicle, you are responsible to ensure that they only use sunnypilot when it is safe to do so, and in compliance with these Terms and with applicable law. If you disagree with any part of the Terms, you should not access or use sunnypilot.

+

Communications

+

You agree that sunnypilot may contact you by email or telephone in connection with sunnypilot or for other business purposes. You may opt out of receiving email messages at any time by contacting us at support@sunnypilot.ai.

+

We collect, use, and share information from and about you and your vehicle in connection with sunnypilot. You consent to sunnypilot accessing the systems associated with sunnypilot, without additional notice or consent, for the purposes of providing sunnypilot, data collection, software updates, safety and cybersecurity, suspension or removal of your account, and as disclosed in the Privacy Policy (available at https://sunnypilot.ai/legal/privacy/).

+

Safety

+

sunnypilot performs the functions of Adaptive Cruise Control (ACC) and Lane Keeping Assist System (LKAS) designed for use in compatible motor vehicles. While using sunnypilot, it is your responsibility to obey all laws, traffic rules, and traffic regulations governing your vehicle and its operation. Access to and use of sunnypilot is at your own risk and responsibility, and sunnypilot should be accessed and/or used only when you can do so safely.

+

sunnypilot does not make your vehicle “autonomous” or capable of operation without the active monitoring of a licensed driver. It is designed to assist a licensed driver. A licensed driver must pay attention to the road, remain aware of navigation at all times, and be prepared to take immediate action. Failure to do so can cause damage, injury, or death.

+

Supported Locations and Models

+

sunnypilot is compatible only with particular makes and models of vehicles. For a complete list of currently supported vehicles, visit https://comma.ai. sunnypilot will not function properly when installed in an incompatible vehicle. sunnypilot is compatible only within the geographical boundaries of the United States of America.

+

Indemnification

+

To the maximum extent allowable by law, you agree to defend, indemnify and hold harmless sunnypilot, and its employees, partners, suppliers, contractors, investors, agents, officers, directors, and affiliates, from and against any and all claims, damages, causes of action, penalties, interest, demands, obligations, losses, liabilities, costs or debt, additional taxes, and expenses (including but not limited to attorneys’ fees), resulting from or arising out of (i) your use and access of, or inability to use or access, sunnypilot, (ii) your breach of these Terms, (iii) the inaccuracy of any information, representation or warranty made by you, (iv) activities of anyone other than you in connection with sunnypilot conducted through your comma or other compatible device or account, (v) any other of your activities under or in connection with these Terms or sunnypilot.

+

Limitation of Liability

+

In no event shall sunnypilot, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable for any indirect, incidental, special, consequential or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from (i) your access to or use of or inability to access or use of the Software; or (ii) any conduct or content of any third party on the Software whether based on warranty, contract, tort (including negligence) or any other legal theory, whether or not we have been informed of the possibility of such damage, and even if a remedy set forth herein is found to have failed of its essential purpose.

+

No Warranty or Obligations to Maintain or Service

+

sunnypilot provides sunnypilot without representations, conditions, or warranties of any kind. sunnypilot is provided on an “AS IS” and “AS AVAILABLE” basis, including with all faults and errors as may occur. To the extent permitted by law and unless prohibited by law, sunnypilot on behalf of itself and all persons and parties acting by, through, or for sunnypilot, explicitly disclaims all warranties or conditions, express, implied, or collateral, including any implied warranties of merchantability, satisfactory quality, and fitness for a particular purpose in respect of sunnypilot.

+

To the extent permitted by law, sunnypilot does not warrant the operation, performance, or availability of sunnypilot under all conditions. sunnypilot is not responsible for any failures caused by server errors, misdirected or redirected transmissions, failed internet connections, interruptions or failures in the transmission of data, any computer virus, or any acts or omissions of third parties that damage the network or impair wireless service.

+

We undertake reasonable measures to preserve and secure information collected through our sunnypilot. However, no data collection, transmission or storage system is 100% secure, and there is always a risk that your information may be intercepted without our consent. In using sunnypilot, you acknowledge that sunnypilot is not responsible for intercepted information, and you hereby release us from any and all claims arising out of or related to the use of intercepted information in any unauthorized manner.

+

By providing sunnypilot, sunnypilot does not transfer or license its intellectual property or grant rights in its brand names, nor does sunnypilot make representations with respect to third-party intellectual property rights.

+

We are not obligated to provide any maintenance or support for sunnypilot, technical or otherwise. If we voluntarily provide any maintenance or support for sunnypilot, we may stop any such maintenance, support, or services at any time in our sole discretion.

+

Modification of Software

+

In no event shall sunnypilot, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable if you choose to modify the software.

+

Changes

+

We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material we will provide at least 15 days’ notice prior to any new terms taking effect. What constitutes a material change will be determined at our sole discretion.

+

By continuing to access or use our Software after any revisions become effective, you agree to be bound by the revised terms. If you do not agree to the new terms, you are no longer authorized to use the Software.

+

Contact Us

+

If you have any questions about these Terms, please contact us at support@sunnypilot.ai.

+ + diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index b1219055fd..014e9a6f05 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -106,7 +106,8 @@ void TermsPage::showEvent(QShowEvent *event) { text->setAttribute(Qt::WA_AlwaysStackOnTop); text->setClearColor(QColor("#1B1B1B")); - QString text_view = util::read_file("../assets/offroad/tc.html").c_str(); + std::string tc_text = sunnypilot_tc ? "../assets/offroad/sp_tc.html" : "../assets/offroad/tc.html"; + QString text_view = util::read_file(tc_text).c_str(); text->rootContext()->setContextProperty("text_view", text_view); text->setSource(QUrl::fromLocalFile("qt/offroad/text_view.qml")); @@ -158,7 +159,7 @@ void DeclinePage::showEvent(QShowEvent *event) { main_layout->setSpacing(40); QLabel *text = new QLabel(this); - text->setText(tr("You must accept the Terms and Conditions in order to use openpilot.")); + text->setText(tr("You must accept the Terms and Conditions in order to use sunnypilot.")); text->setStyleSheet(R"(font-size: 80px; font-weight: 300; margin: 200px;)"); text->setWordWrap(true); main_layout->addWidget(text, 0, Qt::AlignCenter); @@ -185,6 +186,8 @@ void OnboardingWindow::updateActiveScreen() { setCurrentIndex(0); } else if (!training_done) { setCurrentIndex(1); + } else if (!accepted_terms_sp) { + setCurrentIndex(3); } else { emit onboardingDone(); } @@ -192,11 +195,13 @@ void OnboardingWindow::updateActiveScreen() { OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { std::string current_terms_version = params.get("TermsVersion"); + std::string current_terms_version_sp = params.get("TermsVersionSunnypilot"); std::string current_training_version = params.get("TrainingVersion"); accepted_terms = params.get("HasAcceptedTerms") == current_terms_version; + accepted_terms_sp = params.get("HasAcceptedTermsSP") == current_terms_version_sp; training_done = params.get("CompletedTrainingVersion") == current_training_version; - TermsPage* terms = new TermsPage(this); + TermsPage* terms = new TermsPage(false, this); addWidget(terms); connect(terms, &TermsPage::acceptedTerms, [=]() { params.put("HasAcceptedTerms", current_terms_version); @@ -217,6 +222,15 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { addWidget(declinePage); connect(declinePage, &DeclinePage::getBack, [=]() { updateActiveScreen(); }); + TermsPage* terms_sp = new TermsPage(true, this); + addWidget(terms_sp); // index = 3 + connect(terms_sp, &TermsPage::acceptedTerms, [=]() { + params.put("HasAcceptedTermsSP", current_terms_version_sp); + accepted_terms_sp = true; + updateActiveScreen(); + }); + connect(terms_sp, &TermsPage::declinedTerms, [=]() { setCurrentIndex(2); }); + setStyleSheet(R"( * { color: white; diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index a1b6895ba0..008d86032c 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -63,7 +63,7 @@ class TermsPage : public QFrame { Q_OBJECT public: - explicit TermsPage(QWidget *parent = 0) : QFrame(parent) {} + explicit TermsPage(bool sunnypilot = false, QWidget *parent = 0) : QFrame(parent), sunnypilot_tc(sunnypilot) {} public slots: void enableAccept(); @@ -72,6 +72,7 @@ private: void showEvent(QShowEvent *event) override; QPushButton *accept_btn; + bool sunnypilot_tc = false; signals: void acceptedTerms(); @@ -97,13 +98,13 @@ class OnboardingWindow : public QStackedWidget { public: explicit OnboardingWindow(QWidget *parent = 0); inline void showTrainingGuide() { setCurrentIndex(1); } - inline bool completed() const { return accepted_terms && training_done; } + inline bool completed() const { return accepted_terms && accepted_terms_sp && training_done; } private: void updateActiveScreen(); Params params; - bool accepted_terms = false, training_done = false; + bool accepted_terms = false, accepted_terms_sp = false, training_done = false; signals: void onboardingDone(); diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 4379aa7acd..dd03692770 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -23,7 +23,7 @@ from openpilot.system.statsd import statlog from openpilot.common.swaglog import cloudlog from openpilot.system.hardware.power_monitoring import PowerMonitoring from openpilot.system.hardware.fan_controller import TiciFanController -from openpilot.system.version import terms_version, training_version +from openpilot.system.version import terms_version, terms_version_sp, training_version ThermalStatus = log.DeviceState.ThermalStatus NetworkType = log.DeviceState.NetworkType @@ -298,6 +298,7 @@ def hardware_thread(end_event, hw_queue) -> None: startup_conditions["up_to_date"] = params.get("Offroad_ConnectivityNeeded") is None or params.get_bool("DisableUpdates") or params.get_bool("SnoozeUpdate") startup_conditions["not_uninstalling"] = not params.get_bool("DoUninstall") startup_conditions["accepted_terms"] = params.get("HasAcceptedTerms") == terms_version + startup_conditions["accepted_terms"] = params.get("HasAcceptedTermsSP") == terms_version_sp # with 2% left, we killall, otherwise the phone will take a long time to boot startup_conditions["free_space"] = msg.deviceState.freeSpacePercent > 2 diff --git a/system/manager/manager.py b/system/manager/manager.py index 6d1a2ce95f..a48440f846 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -18,7 +18,7 @@ from openpilot.system.manager.process_config import managed_processes from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_ID, is_registered_device from openpilot.common.swaglog import cloudlog, add_file_handler from openpilot.system.hardware.hw import Paths -from openpilot.system.version import get_build_metadata, terms_version, training_version +from openpilot.system.version import get_build_metadata, terms_version, terms_version_sp, training_version def manager_init() -> None: @@ -67,6 +67,7 @@ def manager_init() -> None: ("EnhancedScc", "0"), ("FeatureStatus", "1"), ("HandsOnWheelMonitoring", "0"), + ("HasAcceptedTermsSP", "0"), ("HideVEgoUi", "0"), ("LastSpeedLimitSignTap", "0"), ("LkasToggle", "0"), @@ -135,6 +136,7 @@ def manager_init() -> None: # set version params params.put("Version", build_metadata.openpilot.version) params.put("TermsVersion", terms_version) + params.put("TermsVersionSunnypilot", terms_version_sp) params.put("TrainingVersion", training_version) params.put("GitCommit", build_metadata.openpilot.git_commit) params.put("GitCommitDate", build_metadata.openpilot.git_commit_date) diff --git a/system/version.py b/system/version.py index 9873b50511..05b5ab9361 100755 --- a/system/version.py +++ b/system/version.py @@ -18,6 +18,7 @@ BUILD_METADATA_FILENAME = "build.json" training_version: bytes = b"0.2.0" terms_version: bytes = b"2" +terms_version_sp: bytes = b"1.0" def get_version(path: str = BASEDIR) -> str: @@ -179,6 +180,7 @@ if __name__ == "__main__": params = Params() params.put("TermsVersion", terms_version) + params.put("TermsVersionSunnypilot", terms_version_sp) params.put("TrainingVersion", training_version) print(get_build_metadata()) From 40d9b8c333dff601a2aa2f8a165b11ee8967ab1f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 13 Jun 2024 16:57:34 +0000 Subject: [PATCH 2/3] ui: Disallow pairing QR code generation with empty sunnylink dongle ID --- selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc index 6c55bd1655..118aa42159 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnylink_settings.cc @@ -70,7 +70,11 @@ SunnylinkPanel::SunnylinkPanel(QWidget* parent) : QFrame(parent) { ); list->addItem(pairSponsorBtn); connect(pairSponsorBtn, &ButtonControl::clicked, [=]() { - pair_popup->exec(); + if (getSunnylinkDongleId().value_or(tr("N/A")) == "N/A") { + ConfirmationDialog::alert(tr("sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again."), this); + } else { + pair_popup->exec(); + } }); list->addItem(horizontal_line()); @@ -410,7 +414,7 @@ void SunnylinkSponsorQRWidget::refresh() { if (sponsor_pair) { QString token = CommaApi::create_jwt({}, 3600, true); auto sl_dongle_id = getSunnylinkDongleId(); - QByteArray payload = QString("1|" + *sl_dongle_id + "|" + token).toUtf8().toBase64(); + QByteArray payload = QString("1|" + sl_dongle_id.value_or("") + "|" + token).toUtf8().toBase64(); qrString = SUNNYLINK_BASE_URL + "/sso?state=" + payload; } else { qrString = "https://github.com/sponsors/sunnyhaibin"; From 82c67c87adc8206178fbafeb182053f2f47f7342 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Thu, 13 Jun 2024 17:01:39 +0000 Subject: [PATCH 3/3] =?UTF-8?q?[sunnylink]=C2=A0Better=20network=20managem?= =?UTF-8?q?ent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- system/athena/athenad.py | 6 ++---- system/athena/sunnylinkd.py | 36 ++++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/system/athena/athenad.py b/system/athena/athenad.py index e8ef682026..71678d534e 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -634,6 +634,7 @@ def log_handler(end_event: threading.Event, log_attr_name=LOG_ATTR_NAME) -> None is_sunnylink = log_attr_name != LOG_ATTR_NAME if PC: cloudlog.debug("athena.log_handler: Not supported on PC") + time.sleep(1) return log_files = [] @@ -808,7 +809,7 @@ def ws_manage(ws: WebSocket, end_event: threading.Event) -> None: onroad_prev = None sock = ws.sock - while True: + while not end_event.wait(5): onroad = params.get_bool("IsOnroad") if onroad != onroad_prev: onroad_prev = onroad @@ -826,9 +827,6 @@ def ws_manage(ws: WebSocket, end_event: threading.Event) -> None: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 7 if onroad else 10) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 2 if onroad else 3) - if end_event.wait(5): - break - def backoff(retries: int) -> int: return random.randrange(0, min(128, int(2 ** retries))) diff --git a/system/athena/sunnylinkd.py b/system/athena/sunnylinkd.py index 4150e8a475..67ae8a95af 100755 --- a/system/athena/sunnylinkd.py +++ b/system/athena/sunnylinkd.py @@ -1,17 +1,15 @@ #!/usr/bin/env python3 -#TODO: Add this to files_common to allow release to public from __future__ import annotations import base64 import gzip -import json import os import threading import time from openpilot.system.athena.athenad import ws_send, jsonrpc_handler, \ - recv_queue, RECONNECT_TIMEOUT_S, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler + recv_queue, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler from jsonrpc import dispatcher from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException, create_connection) @@ -26,6 +24,7 @@ SUNNYLINK_ATHENA_HOST = os.getenv('SUNNYLINK_ATHENA_HOST', 'wss://ws.stg.api.sun HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) LOCAL_PORT_WHITELIST = {8022} SUNNYLINK_LOG_ATTR_NAME = "user.sunny.upload" +SUNNYLINK_RECONNECT_TIMEOUT_S = 70 # FYI changing this will also would require a change on sidebar.cc params = Params() sunnylink_api = SunnylinkApi(params.get("SunnylinkDongleId", encoding='utf-8')) @@ -58,24 +57,22 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: end_event.set() comma_prime_cellular_end_event.set() - prime_type = params.get("PrimeType", encoding='utf-8') + prime_type = params.get("PrimeType", encoding='utf-8') or 0 metered = sm['deviceState'].networkMetered - if int(prime_type) > 2 and metered: + if metered and int(prime_type) > 2: cloudlog.debug(f"sunnylinkd.handle_long_poll: PrimeType({prime_type}) > 2 and networkMetered({metered})") comma_prime_cellular_end_event.set() elif comma_prime_cellular_end_event.is_set(): cloudlog.debug(f"sunnylinkd.handle_long_poll: comma_prime_cellular_end_event is set and not PrimeType({prime_type}) > 2 or not networkMetered({metered})") comma_prime_cellular_end_event.clear() - - except (KeyboardInterrupt, SystemExit): + finally: end_event.set() comma_prime_cellular_end_event.set() - raise - finally: for thread in threads: cloudlog.debug(f"sunnylinkd athena.joining {thread.name}") thread.join() + cloudlog.debug(f"sunnylinkd athena.joined {thread.name}") def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: @@ -89,12 +86,12 @@ def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: recv_queue.put_nowait(data) cloudlog.debug(f"sunnylinkd.ws_recv.recv {data}") elif opcode in (ABNF.OPCODE_PING, ABNF.OPCODE_PONG): - cloudlog.debug(f"sunnylinkd.ws_recv.pong {opcode}") + cloudlog.debug(f"sunnylinkd.ws_recv.pong") last_ping = int(time.monotonic() * 1e9) Params().put("LastSunnylinkPingTime", str(last_ping)) except WebSocketTimeoutException: ns_since_last_ping = int(time.monotonic() * 1e9) - last_ping - if ns_since_last_ping > RECONNECT_TIMEOUT_S * 1e9: + if ns_since_last_ping > SUNNYLINK_RECONNECT_TIMEOUT_S * 1e9: cloudlog.exception("sunnylinkd.ws_recv.timeout") end_event.set() except Exception: @@ -103,15 +100,15 @@ def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: def ws_ping(ws: WebSocket, end_event: threading.Event) -> None: - # last_ping = int(time.monotonic() * 1e9) - while not end_event.is_set(): + ws.ping() # Send the first ping + while not end_event.wait(SUNNYLINK_RECONNECT_TIMEOUT_S * 0.7): # Sleep about 70% before a timeout try: ws.ping() cloudlog.debug(f"sunnylinkd.ws_recv.ws_ping: Pinging") except Exception: cloudlog.exception("sunnylinkd.ws_ping.exception") end_event.set() - time.sleep(RECONNECT_TIMEOUT_S * 0.7) # Sleep about 70% before a timeout + cloudlog.debug(f"sunnylinkd.ws_ping.end_event is set, exiting ws_ping thread") def ws_queue(end_event: threading.Event) -> None: resume_requested = False @@ -129,7 +126,13 @@ def ws_queue(end_event: threading.Event) -> None: resume_requested = False tries += 1 time.sleep(backoff(tries)) # Wait for the backoff time before the next attempt - cloudlog.debug("Resume requested or end_event is set, exiting ws_queue thread") + + if end_event.is_set(): + cloudlog.debug("end_event is set, exiting ws_queue thread") + elif resume_requested: + cloudlog.debug(f"Resume requested to server after {tries} tries") + else: + cloudlog.error(f"Reached end of ws_queue while end_event is not set and resume_requested is {resume_requested}") def sunny_log_handler(end_event: threading.Event, comma_prime_cellular_end_event: threading.Event) -> None: @@ -195,7 +198,8 @@ def main(exit_event: threading.Event = None): cloudlog.event("sunnylinkd.main.connecting_ws", ws_uri=ws_uri, retries=conn_retries) ws = create_connection(ws_uri, cookie="jwt=" + sunnylink_api.get_token(), - enable_multithread=True) + enable_multithread=True, + timeout=SUNNYLINK_RECONNECT_TIMEOUT_S) cloudlog.event("sunnylinkd.main.connected_ws", ws_uri=ws_uri, retries=conn_retries, duration=time.monotonic() - conn_start) conn_start = None