ui: add sunnylink settings and sidebar status (#503)

* Add Sunnylink integration for improved device communication

This commit introduces Sunnylink support, including modules for API interactions, device registration, logging, and uploader processes. Key changes involve adding Sunnylink-related components, such as sunnylinkd, manage_sunnylinkd, and associated utilities, along with seamless integration into process management.

* Refactor Sunnylink modules and update import paths

Standardize parameter handling in Sunnylink functions by initializing Params within functions as needed. Update imports to use fully-qualified paths for better clarity and consistency. Also, refactor logging messages for improved readability and maintainability.

* Add Sunnylink support and improve log handling

Introduced Sunnylink-specific functionality, including compression for oversized logs and platform-specific socket handling for macOS. Improved logging mechanisms, refactored log queue management, and fixed exception handling in sunnylinkd.

* Refactor and fix minor coding style inconsistencies

Remove unnecessary string concatenation, adjust spacing for better readability, and ensure cleaner code in `athenad.py` and `sunnylink.py`. Added a macOS-specific comment for TCP_KEEPALIVE configuration to improve code clarity.

* Replace platform system check with sys platform in athenad.py

To check for macOS platform, the code in athenad.py has been altered. Originally, the platform.system() function was used. However, the function has been replaced with sys.platform for a more consistent and preferable syntax. Particularly, this has been modified in the context of setting socket options.

* Apply suggestions from code review

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* Simplify imports and reformat API function.

Removed unused `platform` import for cleanup in `athenad.py`. Improved readability of `api_get` in `__init__.py` by reformatting the long return statement into multiple lines.

* Adjust backoff logic and refactor API call formatting.

Introduce randomness to backoff calculation in Sunnylink API to reduce synchronization issues. Minor code refactoring improves readability in the API call logic.

* Refactor Sunnylink network check logic.

Removed hardware-based network check due to performance concerns and replaced it with a real-time device state monitoring loop. This improves efficiency and ensures accurate online status before proceeding with Sunnylink registration.

* Apply suggestions from code review

* `Refactor saveParams error handling and simplify logic`

Removed redundant try-except block wrapping the entire method for clarity. Moved error logging directly inside the loop to handle individual parameter exceptions more effectively. Simplified dictionary construction and improved error logging format.

* Add BACKUP flag to select persistent parameters

This commit introduces a new BACKUP flag and applies it to specific persistent parameters in `params.cc` and `params.h`. The BACKUP flag enhances data retention by designating parameters for inclusion in backups, ensuring crucial information is preserved across sessions.

* Simplify Sunnypilot params formatting

Removed unnecessary blank lines and adjusted the Sunnypilot comment format for better readability and consistency. No functional changes were made.

* SP: Move Sunnypilot-related code to sunnypilot/sunnylink (#504)

* Refactor and relocate sunnylink-related modules

sunnylink components have been reorganized for better modularity and clarity, with files moved under `sunnypilot/sunnylink`. Unused code was removed, and reusable utilities were separated for easier maintenance. Adjusted references across the project to reflect these changes.

* Permissions

* adding init py

* Add sunnylink toggle to developer panel and translations

This commit introduces a new toggle for enabling or disabling sunnylink in the developer panel. Corresponding translation entries have been added for all supported languages to ensure compatibility across the UI.

* Add SunnyLink integration and multi-language support updates

Enhanced SidebarSP with SunnyLink connection status and temperature display. Extended translations for multiple languages, including new strings. Updated build scripts and added utility functions for SunnyPilot-specific features.

* Need it this way as it's intentionally shortened. Sorry

* format

* only block drawing

* format

* format

* fix path

* cleanup translations

* sunnylink panel

* offroad transition

* remove stretch

* add panel to ui preview

* Translating to spanish

* just reorder params

* Refactor sidebar drawing method names and remove unused code.

Renamed `paintSidebar` to `drawSidebar` for better clarity across both `Sidebar` and `SidebarSP` classes. Removed unused utility functions `drawRoundedRect` and `interpColor` to streamline the codebase and improve maintainability.

* Updating translations

---------

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
This commit is contained in:
DevTekVE
2025-01-05 10:47:28 +01:00
committed by GitHub
parent 0b1d97fc72
commit 5b1bfc26db
26 changed files with 881 additions and 21 deletions
+1
View File
@@ -1 +1,2 @@
Wen
REGIST
+1 -1
View File
@@ -86,7 +86,7 @@ jobs:
run: >-
sudo apt-get install -y imagemagick
scenes="homescreen settings_device settings_software settings_toggles settings_sunnypilot settings_sunnypilot_mads settings_developer offroad_alert update_available prime onroad onroad_disengaged onroad_override onroad_sidebar onroad_wide onroad_wide_sidebar onroad_alert_small onroad_alert_mid onroad_alert_full driver_camera body keyboard"
scenes="homescreen settings_device settings_software settings_sunnylink settings_toggles settings_sunnypilot settings_sunnypilot_mads settings_developer offroad_alert update_available prime onroad onroad_disengaged onroad_override onroad_sidebar onroad_wide onroad_wide_sidebar onroad_alert_small onroad_alert_mid onroad_alert_full driver_camera body keyboard"
A=($scenes)
DIFF=""
+9 -3
View File
@@ -201,18 +201,24 @@ std::unordered_map<std::string, uint32_t> keys = {
{"UpdaterLastFetchTime", PERSISTENT},
{"Version", PERSISTENT},
// sunnypilot params
// --- sunnypilot params --- //
{"EnableGithubRunner", PERSISTENT | BACKUP},
{"EnableSunnylinkUploader", PERSISTENT | BACKUP},
{"LastSunnylinkPingTime", CLEAR_ON_MANAGER_START},
// MADS params
{"Mads", PERSISTENT | BACKUP},
{"MadsMainCruiseAllowed", PERSISTENT | BACKUP},
{"MadsPauseLateralOnBrake", PERSISTENT | BACKUP},
{"MadsUnifiedEngagementMode", PERSISTENT | BACKUP},
// Model Manager params
{"ModelManager_ActiveBundle", PERSISTENT},
{"ModelManager_DownloadIndex", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_ONROAD_TRANSITION},
{"ModelManager_LastSyncTime", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"ModelManager_ModelsCache", PERSISTENT | BACKUP},
// sunnylink params
{"EnableSunnylinkUploader", PERSISTENT | BACKUP},
{"LastSunnylinkPingTime", CLEAR_ON_MANAGER_START},
{"SunnylinkDongleId", PERSISTENT},
{"SunnylinkdPid", PERSISTENT},
{"SunnylinkEnabled", PERSISTENT},
+3 -2
View File
@@ -14,14 +14,15 @@ if arch == "Darwin":
sp_widgets_src = []
sp_qt_src = []
sp_qt_util = []
if not GetOption('stock_ui'):
SConscript(['sunnypilot/SConscript'])
Import('sp_widgets_src', 'sp_qt_src')
Import('sp_widgets_src', 'sp_qt_src', 'sp_qt_util')
# FIXME: remove this once we're on 5.15 (24.04)
qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"]
qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs)
qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"] + sp_qt_util, LIBS=base_libs)
widgets_src = ["qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/prime_state.cc",
"qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc",
"qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc",
+4 -2
View File
@@ -110,10 +110,10 @@ void Sidebar::updateState(const UIState &s) {
void Sidebar::paintEvent(QPaintEvent *event) {
QPainter p(this);
paintSidebar(p);
drawSidebar(p);
}
void Sidebar::paintSidebar(QPainter &p) {
void Sidebar::drawSidebar(QPainter &p) {
p.setPen(Qt::NoPen);
p.setRenderHint(QPainter::Antialiasing);
@@ -140,8 +140,10 @@ void Sidebar::paintSidebar(QPainter &p) {
const QRect r = QRect(58, 247, width() - 100, 50);
p.drawText(r, Qt::AlignLeft | Qt::AlignVCenter, net_type);
#ifndef SUNNYPILOT
// metrics
drawMetric(p, temp_status.first, temp_status.second, 338);
drawMetric(p, panda_status.first, panda_status.second, 496);
drawMetric(p, connect_status.first, connect_status.second, 654);
#endif
}
+1 -1
View File
@@ -40,7 +40,7 @@ protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void drawMetric(QPainter &p, const QPair<QString, QString> &label, QColor c, int y);
virtual void paintSidebar(QPainter &p);
virtual void drawSidebar(QPainter &p);
QPixmap home_img, flag_img, settings_img;
bool onroad, flag_pressed, settings_pressed;
+7 -1
View File
@@ -4,6 +4,10 @@ widgets_src = [
"sunnypilot/qt/widgets/scrollview.cc",
]
qt_util = [
"sunnypilot/qt/util.cc",
]
qt_src = [
"sunnypilot/ui.cc",
"sunnypilot/qt/sidebar.cc",
@@ -11,6 +15,7 @@ qt_src = [
"sunnypilot/qt/home.cc",
"sunnypilot/qt/offroad/settings/settings.cc",
"sunnypilot/qt/offroad/settings/software_panel.cc",
"sunnypilot/qt/offroad/settings/sunnylink_panel.cc",
"sunnypilot/qt/offroad/settings/sunnypilot_panel.cc",
"sunnypilot/qt/onroad/onroad_home.cc",
]
@@ -21,5 +26,6 @@ sunnypilot_panel_qt_src = [
sp_widgets_src = widgets_src
sp_qt_src = qt_src + sunnypilot_panel_qt_src
sp_qt_util = qt_util
Export('sp_widgets_src', 'sp_qt_src')
Export('sp_widgets_src', 'sp_qt_src', "sp_qt_util")
@@ -12,6 +12,7 @@
#include "selfdrive/ui/qt/offroad/developer_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot_panel.h"
TogglesPanelSP::TogglesPanelSP(SettingsWindow *parent) : TogglesPanel(parent) {
@@ -69,6 +70,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) {
QList<PanelInfo> panels = {
PanelInfo(" " + tr("Device"), device, "../../sunnypilot/selfdrive/assets/offroad/icon_home.svg"),
PanelInfo(" " + tr("Network"), networking, "../assets/offroad/icon_network.png"),
PanelInfo(" " + tr("sunnylink"), new SunnylinkPanel(this), "../assets/offroad/icon_wifi_strength_full.svg"),
PanelInfo(" " + tr("Toggles"), toggles, "../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"),
PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../../sunnypilot/selfdrive/assets/offroad/icon_software.png"),
PanelInfo(" " + tr("sunnypilot"), new SunnypilotPanel(this), "../assets/images/button_home.png"),
@@ -0,0 +1,72 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
main_layout = new QStackedLayout(this);
ListWidget *list = new ListWidget(this, false);
sunnylinkScreen = new QWidget(this);
QVBoxLayout *vlayout = new QVBoxLayout(sunnylinkScreen);
vlayout->setContentsMargins(50, 20, 50, 20);
QString sunnylinkEnabledBtnDesc = tr("This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.");
sunnylinkEnabledBtn = new ParamControl(
"SunnylinkEnabled",
tr("Enable sunnylink"),
sunnylinkEnabledBtnDesc,
"");
list->addItem(sunnylinkEnabledBtn);
connect(sunnylinkEnabledBtn, &ParamControl::showDescriptionEvent, [=]() {
// resets the description to the default one for the Easter egg
sunnylinkEnabledBtn->setDescription(sunnylinkEnabledBtnDesc);
});
connect(sunnylinkEnabledBtn, &ParamControl::toggleFlipped, [=](bool enabled) {
if (enabled) {
auto proud_description = "<font color='SeaGreen'>"+ tr("🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀")+ "</font>";
sunnylinkEnabledBtn->showDescription();
sunnylinkEnabledBtn->setDescription(proud_description);
} else {
auto shame_description = "<font color='orange'>"+ tr("👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉.")+ "</font>";
sunnylinkEnabledBtn->showDescription();
sunnylinkEnabledBtn->setDescription(shame_description);
}
updatePanel(offroad);
});
QObject::connect(uiState(), &UIState::offroadTransition, this, &SunnylinkPanel::updatePanel);
sunnylinkScroller = new ScrollViewSP(list, this);
vlayout->addWidget(sunnylinkScroller);
main_layout->addWidget(sunnylinkScreen);
}
void SunnylinkPanel::showEvent(QShowEvent *event) {
updatePanel(offroad);
}
void SunnylinkPanel::updatePanel(bool _offroad) {
QString sunnylink_device_id = tr("Device ID") + " " + getSunnylinkDongleId().value_or(tr("N/A"));
sunnylinkEnabledBtn->setEnabled(_offroad);
if (sunnylinkEnabledBtn->isToggled()) {
sunnylinkEnabledBtn->setValue(sunnylink_device_id);
} else {
sunnylinkEnabledBtn->setValue("");
}
offroad = _offroad;
}
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
class SunnylinkPanel : public QFrame {
Q_OBJECT
public:
explicit SunnylinkPanel(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
public slots:
void updatePanel(bool _offroad);
private:
Params params;
QStackedLayout *main_layout = nullptr;
QWidget *sunnylinkScreen = nullptr;
ScrollViewSP *sunnylinkScroller = nullptr;
bool offroad;
ParamControl *sunnylinkEnabledBtn;
};
+32 -5
View File
@@ -7,12 +7,12 @@
#include "selfdrive/ui/sunnypilot/qt/sidebar.h"
#include <cmath>
#include <QMouseEvent>
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
#include "common/params.h"
SidebarSP::SidebarSP(QWidget *parent) : Sidebar(parent) {
// Redirect uiUpdate signal to SidebarSP::updateState instead of Sidebar::updateState
QObject::disconnect(uiState(), &UIState::uiUpdate, this, &Sidebar::updateState);
QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &SidebarSP::updateState);
}
@@ -20,8 +20,35 @@ SidebarSP::SidebarSP(QWidget *parent) : Sidebar(parent) {
void SidebarSP::updateState(const UIStateSP &s) {
if (!isVisible()) return;
Sidebar::updateState(s);
ItemStatus sunnylinkStatus;
auto sl_dongle_id = getSunnylinkDongleId();
auto last_sunnylink_ping_str = params.get("LastSunnylinkPingTime");
auto last_sunnylink_ping = std::stoull(last_sunnylink_ping_str.empty() ? "0" : last_sunnylink_ping_str);
auto elapsed_sunnylink_ping = nanos_since_boot() - last_sunnylink_ping;
auto sunnylink_enabled = params.getBool("SunnylinkEnabled");
QString status = tr("DISABLED");
QColor color = disabled_color;
if (sunnylink_enabled && last_sunnylink_ping == 0) {
// If sunnylink is enabled, but we don't have a dongle id, and we haven't received a ping yet, we are registering
status = sl_dongle_id.has_value() ? tr("OFFLINE") : tr("REGIST...");
color = sl_dongle_id.has_value() ? warning_color : progress_color;
} else if (sunnylink_enabled) {
// If sunnylink is enabled, we are considered online if we have received a ping in the last 80 seconds, else error.
status = elapsed_sunnylink_ping < 80000000000ULL ? tr("ONLINE") : tr("ERROR");
color = elapsed_sunnylink_ping < 80000000000ULL ? good_color : danger_color;
}
sunnylinkStatus = ItemStatus{{tr("SUNNYLINK"), status}, color};
setProperty("sunnylinkStatus", QVariant::fromValue(sunnylinkStatus));
}
void SidebarSP::paintSidebar(QPainter &p) {
Sidebar::paintSidebar(p);
void SidebarSP::drawSidebar(QPainter &p) {
Sidebar::drawSidebar(p);
// metrics
drawMetric(p, temp_status.first, temp_status.second, 310);
drawMetric(p, panda_status.first, panda_status.second, 440);
drawMetric(p, connect_status.first, connect_status.second, 570);
drawMetric(p, sunnylink_status.first, sunnylink_status.second, 700);
}
+13 -1
View File
@@ -15,6 +15,8 @@
class SidebarSP : public Sidebar {
Q_OBJECT
Q_PROPERTY(ItemStatus sunnylinkStatus MEMBER sunnylink_status NOTIFY valueChanged);
Q_PROPERTY(QString sidebarTemp MEMBER sidebar_temp_str NOTIFY valueChanged);
public slots:
void updateState(const UIStateSP &s);
@@ -23,5 +25,15 @@ public:
explicit SidebarSP(QWidget *parent = 0);
private:
void paintSidebar(QPainter &p) override;
void drawSidebar(QPainter &p) override;
Params params;
QString sidebar_temp = "0";
QString sidebar_temp_str = "0";
protected:
const QColor progress_color = QColor(3, 132, 252);
const QColor disabled_color = QColor(128, 128, 128);
ItemStatus sunnylink_status;
};
+35
View File
@@ -0,0 +1,35 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/util.h"
#include "selfdrive/ui/qt/util.h"
#include <string>
#include <vector>
#include <QDir>
#include <QLayoutItem>
#include <QPainterPath>
#include "system/hardware/hw.h"
std::optional<QString> getParamIgnoringDefault(const std::string &param_name, const std::string &default_value) {
std::string value = Params().get(param_name);
if (!value.empty() && value != default_value)
return QString::fromStdString(value);
return {};
}
QString getUserAgent(bool sunnylink) {
return (sunnylink ? "sunnypilot-" : "openpilot-") + getVersion();
}
std::optional<QString> getSunnylinkDongleId() {
return getParamIgnoringDefault("SunnylinkDongleId", "UnregisteredDevice");
}
+18
View File
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <optional>
#include <vector>
#include <QPainter>
#include <QWidget>
QString getUserAgent(bool sunnylink = false);
std::optional<QString> getSunnylinkDongleId();
std::optional<QString> getParamIgnoringDefault(const std::string &param_name, const std::string &default_value);
+14 -5
View File
@@ -42,17 +42,17 @@ def setup_settings_device(click, pm: PubMaster):
def setup_settings_toggles(click, pm: PubMaster):
setup_settings_device(click, pm)
click(278, 586)
click(278, 640)
time.sleep(UI_DELAY)
def setup_settings_software(click, pm: PubMaster):
setup_settings_device(click, pm)
click(278, 716)
click(278, 750)
time.sleep(UI_DELAY)
def setup_settings_developer(click, pm: PubMaster):
setup_settings_device(click, pm)
click(278, 976)
click(278, 970)
time.sleep(UI_DELAY)
def setup_onroad(click, pm: PubMaster):
@@ -176,16 +176,23 @@ def setup_pair_device(click, pm: PubMaster):
click(1950, 435)
click(1800, 900)
def setup_settings_sunnylink(click, pm: PubMaster):
Params().put_bool("SunnylinkEnabled", True)
setup_settings_device(click, pm)
click(278, 530)
time.sleep(UI_DELAY)
def setup_settings_sunnypilot(click, pm: PubMaster):
setup_settings_device(click, pm)
click(278, 846)
click(278, 860)
time.sleep(UI_DELAY)
def setup_settings_sunnypilot_mads(click, pm: PubMaster):
Params().put_bool("Mads", True)
setup_settings_device(click, pm)
click(278, 846)
click(278, 860)
click(970, 455)
time.sleep(UI_DELAY)
@@ -214,6 +221,7 @@ CASES = {
}
CASES.update({
"settings_sunnylink": setup_settings_sunnylink,
"settings_sunnypilot": setup_settings_sunnypilot,
"settings_sunnypilot_mads": setup_settings_sunnypilot_mads,
})
@@ -302,6 +310,7 @@ def create_screenshots():
with OpenpilotPrefix():
params = Params()
params.put("DongleId", "123456789012345")
params.put("SunnylinkDongleId", "123456789012345")
if name == 'prime':
params.put('PrimeType', '1')
elif name == 'pair_device':
+58
View File
@@ -725,6 +725,10 @@ This may take up to a minute.</source>
<source>sunnypilot</source>
<translation>sunnypilot</translation>
</message>
<message>
<source>sunnylink</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Setup</name>
@@ -911,6 +915,33 @@ This may take up to a minute.</source>
<translation>5G</translation>
</message>
</context>
<context>
<name>SidebarSP</name>
<message>
<source>DISABLED</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>OFFLINE</source>
<translation type="unfinished">غير متصل</translation>
</message>
<message>
<source>REGIST...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ONLINE</source>
<translation type="unfinished">متصل</translation>
</message>
<message>
<source>ERROR</source>
<translation type="unfinished">خطأ</translation>
</message>
<message>
<source>SUNNYLINK</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SoftwarePanel</name>
<message>
@@ -1107,6 +1138,33 @@ This may take up to a minute.</source>
<translation>تمكين SSH</translation>
</message>
</context>
<context>
<name>SunnylinkPanel</name>
<message>
<source>This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>N/A</source>
<translation type="unfinished">غير متاح</translation>
</message>
<message>
<source>🎉Welcome back! We&apos;re excited to see you&apos;ve enabled sunnylink again! 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>👋Not going to lie, it&apos;s sad to see you disabled sunnylink 😢, but we&apos;ll be here when you&apos;re ready to come back 🎉.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnypilotPanel</name>
<message>
+58
View File
@@ -707,6 +707,10 @@ This may take up to a minute.</source>
<source>sunnypilot</source>
<translation>sunnypilot</translation>
</message>
<message>
<source>sunnylink</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Setup</name>
@@ -894,6 +898,33 @@ This may take up to a minute.</source>
<translation>5G</translation>
</message>
</context>
<context>
<name>SidebarSP</name>
<message>
<source>DISABLED</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>OFFLINE</source>
<translation type="unfinished">OFFLINE</translation>
</message>
<message>
<source>REGIST...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ONLINE</source>
<translation type="unfinished">ONLINE</translation>
</message>
<message>
<source>ERROR</source>
<translation type="unfinished">FEHLER</translation>
</message>
<message>
<source>SUNNYLINK</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SoftwarePanel</name>
<message>
@@ -1091,6 +1122,33 @@ This may take up to a minute.</source>
<translation>SSH aktivieren</translation>
</message>
</context>
<context>
<name>SunnylinkPanel</name>
<message>
<source>This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>N/A</source>
<translation type="unfinished">Nicht verfügbar</translation>
</message>
<message>
<source>🎉Welcome back! We&apos;re excited to see you&apos;ve enabled sunnylink again! 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>👋Not going to lie, it&apos;s sad to see you disabled sunnylink 😢, but we&apos;ll be here when you&apos;re ready to come back 🎉.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnypilotPanel</name>
<message>
+58
View File
@@ -709,6 +709,10 @@ Esto puede tardar un minuto.</translation>
<source>sunnypilot</source>
<translation>sunnypilot</translation>
</message>
<message>
<source>sunnylink</source>
<translation>sunnylink</translation>
</message>
</context>
<context>
<name>Setup</name>
@@ -895,6 +899,33 @@ Esto puede tardar un minuto.</translation>
<translation>5G</translation>
</message>
</context>
<context>
<name>SidebarSP</name>
<message>
<source>DISABLED</source>
<translation>DESHABILITADO</translation>
</message>
<message>
<source>OFFLINE</source>
<translation type="unfinished">OFFLINE</translation>
</message>
<message>
<source>REGIST...</source>
<translation>REGIST...</translation>
</message>
<message>
<source>ONLINE</source>
<translation>EN LÍNEA</translation>
</message>
<message>
<source>ERROR</source>
<translation>ERROR</translation>
</message>
<message>
<source>SUNNYLINK</source>
<translation>SUNNYLINK</translation>
</message>
</context>
<context>
<name>SoftwarePanel</name>
<message>
@@ -1091,6 +1122,33 @@ Esto puede tardar un minuto.</translation>
<translation>Habilitar SSH</translation>
</message>
</context>
<context>
<name>SunnylinkPanel</name>
<message>
<source>This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.</source>
<translation>Este es el interruptor principal; te permitirá desactivar completamente cualquier connectividad de sunnylink si así lo deseas.</translation>
</message>
<message>
<source>Enable sunnylink</source>
<translation>Habilitar sunnylink</translation>
</message>
<message>
<source>N/A</source>
<translation type="unfinished">N/A</translation>
</message>
<message>
<source>🎉Welcome back! We&apos;re excited to see you&apos;ve enabled sunnylink again! 🚀</source>
<translation>🎉¡Bienvenido de vuelta! Nos alegra que hayas reactivado sunnylink. 🚀</translation>
</message>
<message>
<source>Device ID</source>
<translation>ID del dispositivo</translation>
</message>
<message>
<source>👋Not going to lie, it&apos;s sad to see you disabled sunnylink 😢, but we&apos;ll be here when you&apos;re ready to come back 🎉.</source>
<translation>👋No te mentiremos, nos entristece que hayas desactivado sunnylink 😢. Estaremos aquí cuando estes listo para volver 🎉.</translation>
</message>
</context>
<context>
<name>SunnypilotPanel</name>
<message>
+58
View File
@@ -709,6 +709,10 @@ Cela peut prendre jusqu&apos;à une minute.</translation>
<source>sunnypilot</source>
<translation>sunnypilot</translation>
</message>
<message>
<source>sunnylink</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Setup</name>
@@ -895,6 +899,33 @@ Cela peut prendre jusqu&apos;à une minute.</translation>
<translation>5G</translation>
</message>
</context>
<context>
<name>SidebarSP</name>
<message>
<source>DISABLED</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>OFFLINE</source>
<translation type="unfinished">HORS LIGNE</translation>
</message>
<message>
<source>REGIST...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ONLINE</source>
<translation type="unfinished">EN LIGNE</translation>
</message>
<message>
<source>ERROR</source>
<translation type="unfinished">ERREUR</translation>
</message>
<message>
<source>SUNNYLINK</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SoftwarePanel</name>
<message>
@@ -1091,6 +1122,33 @@ Cela peut prendre jusqu&apos;à une minute.</translation>
<translation>Activer SSH</translation>
</message>
</context>
<context>
<name>SunnylinkPanel</name>
<message>
<source>This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>N/A</source>
<translation type="unfinished">N/A</translation>
</message>
<message>
<source>🎉Welcome back! We&apos;re excited to see you&apos;ve enabled sunnylink again! 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>👋Not going to lie, it&apos;s sad to see you disabled sunnylink 😢, but we&apos;ll be here when you&apos;re ready to come back 🎉.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnypilotPanel</name>
<message>
+58
View File
@@ -703,6 +703,10 @@ This may take up to a minute.</source>
<source>sunnypilot</source>
<translation>sunnypilot</translation>
</message>
<message>
<source>sunnylink</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Setup</name>
@@ -889,6 +893,33 @@ This may take up to a minute.</source>
<translation>5G</translation>
</message>
</context>
<context>
<name>SidebarSP</name>
<message>
<source>DISABLED</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>OFFLINE</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REGIST...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ONLINE</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ERROR</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SUNNYLINK</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SoftwarePanel</name>
<message>
@@ -1085,6 +1116,33 @@ This may take up to a minute.</source>
<translation>SSH </translation>
</message>
</context>
<context>
<name>SunnylinkPanel</name>
<message>
<source>This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>N/A</source>
<translation type="unfinished">N/A</translation>
</message>
<message>
<source>🎉Welcome back! We&apos;re excited to see you&apos;ve enabled sunnylink again! 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>👋Not going to lie, it&apos;s sad to see you disabled sunnylink 😢, but we&apos;ll be here when you&apos;re ready to come back 🎉.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnypilotPanel</name>
<message>
+58
View File
@@ -705,6 +705,10 @@ This may take up to a minute.</source>
<source>sunnypilot</source>
<translation>sunnypilot</translation>
</message>
<message>
<source>sunnylink</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Setup</name>
@@ -891,6 +895,33 @@ This may take up to a minute.</source>
<translation>5G</translation>
</message>
</context>
<context>
<name>SidebarSP</name>
<message>
<source>DISABLED</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>OFFLINE</source>
<translation type="unfinished"> </translation>
</message>
<message>
<source>REGIST...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ONLINE</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ERROR</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SUNNYLINK</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SoftwarePanel</name>
<message>
@@ -1087,6 +1118,33 @@ This may take up to a minute.</source>
<translation>SSH </translation>
</message>
</context>
<context>
<name>SunnylinkPanel</name>
<message>
<source>This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>N/A</source>
<translation type="unfinished">N/A</translation>
</message>
<message>
<source>🎉Welcome back! We&apos;re excited to see you&apos;ve enabled sunnylink again! 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>👋Not going to lie, it&apos;s sad to see you disabled sunnylink 😢, but we&apos;ll be here when you&apos;re ready to come back 🎉.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnypilotPanel</name>
<message>
+58
View File
@@ -709,6 +709,10 @@ Isso pode levar até um minuto.</translation>
<source>sunnypilot</source>
<translation>sunnypilot</translation>
</message>
<message>
<source>sunnylink</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Setup</name>
@@ -895,6 +899,33 @@ Isso pode levar até um minuto.</translation>
<translation>5G</translation>
</message>
</context>
<context>
<name>SidebarSP</name>
<message>
<source>DISABLED</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>OFFLINE</source>
<translation type="unfinished">OFFLINE</translation>
</message>
<message>
<source>REGIST...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ONLINE</source>
<translation type="unfinished">ONLINE</translation>
</message>
<message>
<source>ERROR</source>
<translation type="unfinished">ERRO</translation>
</message>
<message>
<source>SUNNYLINK</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SoftwarePanel</name>
<message>
@@ -1091,6 +1122,33 @@ Isso pode levar até um minuto.</translation>
<translation>Habilitar SSH</translation>
</message>
</context>
<context>
<name>SunnylinkPanel</name>
<message>
<source>This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>N/A</source>
<translation type="unfinished">N/A</translation>
</message>
<message>
<source>🎉Welcome back! We&apos;re excited to see you&apos;ve enabled sunnylink again! 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>👋Not going to lie, it&apos;s sad to see you disabled sunnylink 😢, but we&apos;ll be here when you&apos;re ready to come back 🎉.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnypilotPanel</name>
<message>
+58
View File
@@ -705,6 +705,10 @@ This may take up to a minute.</source>
<source>sunnypilot</source>
<translation>sunnypilot</translation>
</message>
<message>
<source>sunnylink</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Setup</name>
@@ -891,6 +895,33 @@ This may take up to a minute.</source>
<translation>5G</translation>
</message>
</context>
<context>
<name>SidebarSP</name>
<message>
<source>DISABLED</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>OFFLINE</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REGIST...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ONLINE</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ERROR</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SUNNYLINK</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SoftwarePanel</name>
<message>
@@ -1087,6 +1118,33 @@ This may take up to a minute.</source>
<translation> SSH</translation>
</message>
</context>
<context>
<name>SunnylinkPanel</name>
<message>
<source>This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>N/A</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>🎉Welcome back! We&apos;re excited to see you&apos;ve enabled sunnylink again! 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>👋Not going to lie, it&apos;s sad to see you disabled sunnylink 😢, but we&apos;ll be here when you&apos;re ready to come back 🎉.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnypilotPanel</name>
<message>
+58
View File
@@ -703,6 +703,10 @@ This may take up to a minute.</source>
<source>sunnypilot</source>
<translation>sunnypilot</translation>
</message>
<message>
<source>sunnylink</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Setup</name>
@@ -889,6 +893,33 @@ This may take up to a minute.</source>
<translation>5G</translation>
</message>
</context>
<context>
<name>SidebarSP</name>
<message>
<source>DISABLED</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>OFFLINE</source>
<translation type="unfinished">ÇEVRİMDIŞI</translation>
</message>
<message>
<source>REGIST...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ONLINE</source>
<translation type="unfinished">ÇEVRİMİÇİ</translation>
</message>
<message>
<source>ERROR</source>
<translation type="unfinished">HATA</translation>
</message>
<message>
<source>SUNNYLINK</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SoftwarePanel</name>
<message>
@@ -1085,6 +1116,33 @@ This may take up to a minute.</source>
<translation>SSH </translation>
</message>
</context>
<context>
<name>SunnylinkPanel</name>
<message>
<source>This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>N/A</source>
<translation type="unfinished">N/A</translation>
</message>
<message>
<source>🎉Welcome back! We&apos;re excited to see you&apos;ve enabled sunnylink again! 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>👋Not going to lie, it&apos;s sad to see you disabled sunnylink 😢, but we&apos;ll be here when you&apos;re ready to come back 🎉.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnypilotPanel</name>
<message>
+58
View File
@@ -705,6 +705,10 @@ This may take up to a minute.</source>
<source>sunnypilot</source>
<translation>sunnypilot</translation>
</message>
<message>
<source>sunnylink</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Setup</name>
@@ -891,6 +895,33 @@ This may take up to a minute.</source>
<translation>5G</translation>
</message>
</context>
<context>
<name>SidebarSP</name>
<message>
<source>DISABLED</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>OFFLINE</source>
<translation type="unfinished">线</translation>
</message>
<message>
<source>REGIST...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ONLINE</source>
<translation type="unfinished">线</translation>
</message>
<message>
<source>ERROR</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SUNNYLINK</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SoftwarePanel</name>
<message>
@@ -1087,6 +1118,33 @@ This may take up to a minute.</source>
<translation>SSH</translation>
</message>
</context>
<context>
<name>SunnylinkPanel</name>
<message>
<source>This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>N/A</source>
<translation type="unfinished">N/A</translation>
</message>
<message>
<source>🎉Welcome back! We&apos;re excited to see you&apos;ve enabled sunnylink again! 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>👋Not going to lie, it&apos;s sad to see you disabled sunnylink 😢, but we&apos;ll be here when you&apos;re ready to come back 🎉.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnypilotPanel</name>
<message>
+58
View File
@@ -705,6 +705,10 @@ This may take up to a minute.</source>
<source>sunnypilot</source>
<translation>sunnypilot</translation>
</message>
<message>
<source>sunnylink</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Setup</name>
@@ -891,6 +895,33 @@ This may take up to a minute.</source>
<translation>5G</translation>
</message>
</context>
<context>
<name>SidebarSP</name>
<message>
<source>DISABLED</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>OFFLINE</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>REGIST...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ONLINE</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ERROR</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SUNNYLINK</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SoftwarePanel</name>
<message>
@@ -1087,6 +1118,33 @@ This may take up to a minute.</source>
<translation> SSH </translation>
</message>
</context>
<context>
<name>SunnylinkPanel</name>
<message>
<source>This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enable sunnylink</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>N/A</source>
<translation type="unfinished">使</translation>
</message>
<message>
<source>🎉Welcome back! We&apos;re excited to see you&apos;ve enabled sunnylink again! 🚀</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>👋Not going to lie, it&apos;s sad to see you disabled sunnylink 😢, but we&apos;ll be here when you&apos;re ready to come back 🎉.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SunnypilotPanel</name>
<message>