wip
22
selfdrive/clearpilot/buttons.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import cereal.messaging as messaging
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from openpilot.common.conversions import Conversions as CV
|
||||||
|
from openpilot.common.numpy_fast import clip, interp
|
||||||
|
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, V_CRUISE_MAX
|
||||||
|
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
|
||||||
|
from openpilot.selfdrive.controls.lib.longitudinal_planner import A_CRUISE_MIN, A_CRUISE_MAX_VALS, A_CRUISE_MAX_BP, get_max_accel
|
||||||
|
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||||
|
|
||||||
|
from openpilot.selfdrive.frogpilot.functions.conditional_experimental_mode import ConditionalExperimentalMode
|
||||||
|
from openpilot.selfdrive.frogpilot.functions.map_turn_speed_controller import MapTurnSpeedController
|
||||||
|
from openpilot.selfdrive.frogpilot.functions.speed_limit_controller import SpeedLimitController
|
||||||
|
|
||||||
|
# Class for intercepting when buttons are pressed, or dispatching buttons to be pressed
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class OscarPilotButtons:
|
||||||
|
def __init__(self, params, params_memory):
|
||||||
|
self.params_memory = params_memory
|
||||||
|
|
||||||
40
selfdrive/clearpilot/events.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import cereal.messaging as messaging
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from openpilot.common.conversions import Conversions as CV
|
||||||
|
from openpilot.common.numpy_fast import clip, interp
|
||||||
|
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, V_CRUISE_MAX
|
||||||
|
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
|
||||||
|
from openpilot.selfdrive.controls.lib.longitudinal_planner import A_CRUISE_MIN, A_CRUISE_MAX_VALS, A_CRUISE_MAX_BP, get_max_accel
|
||||||
|
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||||
|
|
||||||
|
from openpilot.selfdrive.frogpilot.functions.conditional_experimental_mode import ConditionalExperimentalMode
|
||||||
|
from openpilot.selfdrive.frogpilot.functions.map_turn_speed_controller import MapTurnSpeedController
|
||||||
|
from openpilot.selfdrive.frogpilot.functions.speed_limit_controller import SpeedLimitController
|
||||||
|
|
||||||
|
# class FrogPilotPlanner:
|
||||||
|
# def __init__(self, params, params_memory):
|
||||||
|
# self.params_memory = params_memory
|
||||||
|
# self.cem = ConditionalExperimentalMode()
|
||||||
|
# self.mtsc = MapTurnSpeedController()
|
||||||
|
|
||||||
|
# self.override_slc = False
|
||||||
|
|
||||||
|
# self.overridden_speed = 0
|
||||||
|
# self.mtsc_target = 0
|
||||||
|
# self.slc_target = 0
|
||||||
|
# self.v_cruise = 0
|
||||||
|
# self.vtsc_target = 0
|
||||||
|
|
||||||
|
# self.x_desired_trajectory = np.zeros(CONTROL_N)
|
||||||
|
|
||||||
|
# self.update_frogpilot_params(params, params_memory)
|
||||||
|
|
||||||
|
# def update(self, sm, mpc):
|
||||||
|
# carState, controlsState, modelData = sm['carState'], sm['controlsState'], sm['modelV2']
|
||||||
|
|
||||||
|
# enabled = controlsState.enabled
|
||||||
|
|
||||||
|
# v_cruise_kph = min(controlsState.vCruise, V_CRUISE_MAX)
|
||||||
|
# v_cruise = v_cruise_kph * CV.KPH_TO_MS
|
||||||
|
# v_ego = carState.vEgo
|
||||||
1
selfdrive/clearpilot/settings/advanced.cc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
// Should show a UI showing the settings menu for stock frogpilot
|
||||||
0
selfdrive/clearpilot/settings/advanced.h
Normal file
149
selfdrive/clearpilot/settings/basic.cc
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#include "selfdrive/oscarpilot/settings/basic.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
|
OscarPilotBasicPanel::OscarPilotBasicPanel(OscarSettingsWindow *parent) : FrogPilotListWidget(parent) {
|
||||||
|
|
||||||
|
const std::vector<std::tuple<QString, QString, QString, QString>> visualToggles {
|
||||||
|
// {"HelloWorld", "Hello World", "Hi!", "../frogpilot/assets/wheel_images/frog.png"},
|
||||||
|
{
|
||||||
|
"OpenpilotEnabledToggle","Enable OpenPilot",
|
||||||
|
"Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off.",
|
||||||
|
"../assets/offroad/icon_openpilot.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"AlwaysOnLateral", "Always on Lateral",
|
||||||
|
"Maintain openpilot lateral control when the brake pedal is used.\n\nDeactivation occurs only through the 'Cruise Control' button.", "../frogpilot/assets/toggle_icons/icon_always_on_lateral.png"
|
||||||
|
},
|
||||||
|
{"VisionTurnControl", "Vision Turn Speed Controller", "Slow down for detected road curvature for smoother curve handling.", "../frogpilot/assets/toggle_icons/icon_vtc.png"},
|
||||||
|
|
||||||
|
// {
|
||||||
|
// "ConditionalExperimental", "Slow for curves",
|
||||||
|
// "Engages 'experimental mode' when a curve is detected, temporairly reducing max speed.", "../frogpilot/assets/toggle_icons/icon_conditional.png"
|
||||||
|
// },
|
||||||
|
// Alert on stopsign / stoplight
|
||||||
|
|
||||||
|
{"LaneChangeAssist", "Lane Change Assist", "Automatically change lanes on turn signal and wheel nudge at highway speeds.", "../frogpilot/assets/toggle_icons/icon_lane.png"},
|
||||||
|
{"HyundaiLKAS", "Use Hyundai LKAS when available", "Let the Hyundai LKAS feature steer the car when lanes are detected and HDA mode is available.", ""},
|
||||||
|
{"FireTheBabysitter", "Relaxed Driver Monitoring", "Increases driver attention warning timeout from 6 to 15 seconds during the daytime and when no lead car is detected.", ""},
|
||||||
|
// - Hands On Wheel: Always / At Dusk / 2+ hrs driving
|
||||||
|
|
||||||
|
// Move this to dashcam subgroup
|
||||||
|
//{"DashCam", "Dash Cam Recording", "Record video and gps data for all drives automatically.", ""}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &[param, title, desc, icon] : visualToggles) {
|
||||||
|
ParamControl *toggle;
|
||||||
|
|
||||||
|
toggle = new ParamControl(param, title, desc, icon, this);
|
||||||
|
|
||||||
|
addItem(toggle);
|
||||||
|
toggles[param.toStdString()] = toggle;
|
||||||
|
|
||||||
|
QObject::connect(toggle, &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamValueControl*>(toggle), &FrogPilotParamValueControl::buttonPressed, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(toggle, &AbstractControl::showDescriptionEvent, [this]() {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamManageControl*>(toggle), &FrogPilotParamManageControl::manageButtonClicked, [this]() {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// std::set<std::string> rebootKeys = {"DriveStats"};
|
||||||
|
// for (const std::string &key : rebootKeys) {
|
||||||
|
// QObject::connect(toggles[key], &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
// if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) {
|
||||||
|
// Hardware::reboot();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// power buttons
|
||||||
|
QHBoxLayout *power_layout = new QHBoxLayout();
|
||||||
|
power_layout->setSpacing(30);
|
||||||
|
|
||||||
|
QPushButton *reboot_btn = new QPushButton(tr("Reboot"));
|
||||||
|
reboot_btn->setObjectName("reboot_btn");
|
||||||
|
power_layout->addWidget(reboot_btn);
|
||||||
|
QObject::connect(reboot_btn, &QPushButton::clicked, this, &OscarPilotBasicPanel::reboot);
|
||||||
|
|
||||||
|
QPushButton *poweroff_btn = new QPushButton(tr("Power Off"));
|
||||||
|
poweroff_btn->setObjectName("poweroff_btn");
|
||||||
|
power_layout->addWidget(poweroff_btn);
|
||||||
|
QObject::connect(poweroff_btn, &QPushButton::clicked, this, &OscarPilotBasicPanel::poweroff);
|
||||||
|
|
||||||
|
if (!Hardware::PC()) {
|
||||||
|
connect(uiState(), &UIState::offroadTransition, poweroff_btn, &QPushButton::setVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
setStyleSheet(R"(
|
||||||
|
#reboot_btn { height: 120px; border-radius: 15px; background-color: #393939; }
|
||||||
|
#reboot_btn:pressed { background-color: #4a4a4a; }
|
||||||
|
#poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; }
|
||||||
|
#poweroff_btn:pressed { background-color: #FF2424; }
|
||||||
|
)");
|
||||||
|
addItem(power_layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OscarPilotBasicPanel::updateToggles() {
|
||||||
|
std::thread([this]() {
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", true);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", false);
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OscarPilotBasicPanel::parentToggleClicked() {
|
||||||
|
this->openParentToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OscarPilotBasicPanel::hideSubToggles() {
|
||||||
|
// for (auto &[key, toggle] : toggles) {
|
||||||
|
// bool subToggles = modelUIKeys.find(key.c_str()) != modelUIKeys.end() ||
|
||||||
|
// customOnroadUIKeys.find(key.c_str()) != customOnroadUIKeys.end() ||
|
||||||
|
// customThemeKeys.find(key.c_str()) != customThemeKeys.end() ||
|
||||||
|
// qolKeys.find(key.c_str()) != qolKeys.end();
|
||||||
|
// toggle->setVisible(!subToggles);
|
||||||
|
// }
|
||||||
|
|
||||||
|
this->closeParentToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OscarPilotBasicPanel::hideEvent(QHideEvent *event) {
|
||||||
|
hideSubToggles();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void OscarPilotBasicPanel::reboot() {
|
||||||
|
if (!uiState()->engaged()) {
|
||||||
|
if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot?"), tr("Reboot"), this)) {
|
||||||
|
// Check engaged again in case it changed while the dialog was open
|
||||||
|
if (!uiState()->engaged()) {
|
||||||
|
params.putBool("DoReboot", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ConfirmationDialog::alert(tr("Disengage to Reboot"), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OscarPilotBasicPanel::poweroff() {
|
||||||
|
if (!uiState()->engaged()) {
|
||||||
|
if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), tr("Power Off"), this)) {
|
||||||
|
// Check engaged again in case it changed while the dialog was open
|
||||||
|
if (!uiState()->engaged()) {
|
||||||
|
params.putBool("DoShutdown", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ConfirmationDialog::alert(tr("Disengage to Power Off"), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
280
selfdrive/clearpilot/settings/basic.example
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
#include "selfdrive/oscarpilot/settings/basic.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
|
OscarPilotBasicPanel::OscarPilotBasicPanel(OscarSettingsWindow *parent) : FrogPilotListWidget(parent) {
|
||||||
|
const std::vector<std::tuple<QString, QString, QString, QString>> visualToggles {
|
||||||
|
{"CustomTheme", "Custom Themes", "Enable the ability to use custom themes.", "../frogpilot/assets/wheel_images/frog.png"},
|
||||||
|
{"CustomColors", "Color Theme", "Switch out the standard openpilot color scheme with a custom color scheme.\n\nWant to submit your own color scheme? Post it in the 'feature-request' channel in the FrogPilot Discord!", ""},
|
||||||
|
{"CustomIcons", "Icon Pack", "Switch out the standard openpilot icons with a set of custom icons.\n\nWant to submit your own icon pack? Post it in the 'feature-request' channel in the FrogPilot Discord!", ""},
|
||||||
|
{"CustomSignals", "Turn Signals", "Add custom animations for your turn signals for a personal touch!\n\nWant to submit your own turn signal animation? Post it in the 'feature-request' channel in the FrogPilot Discord!", ""},
|
||||||
|
{"CustomSounds", "Sound Pack", "Switch out the standard openpilot sounds with a set of custom sounds.\n\nWant to submit your own sound pack? Post it in the 'feature-request' channel in the FrogPilot Discord!", ""},
|
||||||
|
|
||||||
|
{"CameraView", "Camera View", "Choose your preferred camera view for the onroad UI. This is a visual change only and doesn't impact openpilot.", "../frogpilot/assets/toggle_icons/icon_camera.png"},
|
||||||
|
{"Compass", "Compass", "Add a compass to your onroad UI.", "../frogpilot/assets/toggle_icons/icon_compass.png"},
|
||||||
|
|
||||||
|
{"CustomUI", "Custom Onroad UI", "Customize the Onroad UI with some additional visual functions.", "../assets/offroad/icon_road.png"},
|
||||||
|
{"AdjacentPath", "Adjacent Paths", "Display paths to the left and right of your car, visualizing where the model detects lanes.", ""},
|
||||||
|
{"BlindSpotPath", "Blind Spot Path", "Visualize your blind spots with a red path when another vehicle is detected nearby.", ""},
|
||||||
|
{"ShowFPS", "FPS Counter", "Display the Frames Per Second (FPS) of your onroad UI for monitoring system performance.", ""},
|
||||||
|
{"LeadInfo", "Lead Info and Logics", "Get detailed information about the vehicle ahead, including speed and distance, and the logic behind your following distance.", ""},
|
||||||
|
{"RoadNameUI", "Road Name", "See the name of the road you're on at the bottom of your screen. Sourced from OpenStreetMap.", ""},
|
||||||
|
{"UseVienna", "Use Vienna Speed Limit Signs", "Use the Vienna (EU) speed limit style signs as opposed to MUTCD (US).", ""},
|
||||||
|
|
||||||
|
{"DriverCamera", "Driver Camera On Reverse", "Show the driver's camera feed when you shift to reverse.", "../assets/img_driver_face_static.png"},
|
||||||
|
{"GreenLightAlert", "Green Light Alert", "Get an alert when a traffic light changes from red to green.", "../frogpilot/assets/toggle_icons/icon_green_light.png"},
|
||||||
|
|
||||||
|
{"ModelUI", "Model UI", "Personalize how the model's visualizations appear on your screen.", "../assets/offroad/icon_calibration.png"},
|
||||||
|
{"AccelerationPath", "Acceleration Path", "Visualize the car's intended acceleration or deceleration with a color-coded path.", ""},
|
||||||
|
{"LaneLinesWidth", "Lane Lines", "Adjust the visual thickness of lane lines on your display.\n\nDefault matches the MUTCD average of 4 inches.", ""},
|
||||||
|
{"PathEdgeWidth", "Path Edges", "Adjust the width of the path edges shown on your UI to represent different driving modes and statuses.\n\nDefault is 20% of the total path.\n\nBlue = Navigation\nLight Blue = Always On Lateral\nGreen = Default with 'FrogPilot Colors'\nLight Green = Default with stock colors\nOrange = Experimental Mode Active\nYellow = Conditional Overriden", ""},
|
||||||
|
{"PathWidth", "Path Width", "Customize the width of the driving path shown on your UI.\n\nDefault matches the width of a 2019 Lexus ES 350.", ""},
|
||||||
|
{"RoadEdgesWidth", "Road Edges", "Adjust the visual thickness of road edges on your display.\n\nDefault is 1/2 of the MUTCD average lane line width of 4 inches.", ""},
|
||||||
|
{"UnlimitedLength", "'Unlimited' Road UI Length", "Extend the display of the path, lane lines, and road edges as far as the system can detect, providing a more expansive view of the road ahead.", ""},
|
||||||
|
|
||||||
|
{"QOLVisuals", "Quality of Life", "Miscellaneous quality of life changes to improve your overall openpilot experience.", "../frogpilot/assets/toggle_icons/quality_of_life.png"},
|
||||||
|
{"DriveStats", "Drive Stats In Home Screen", "Display your device's drive stats in the home screen.", ""},
|
||||||
|
{"HideSpeed", "Hide Speed", "Hide the speed indicator in the onroad UI.", ""},
|
||||||
|
{"ShowSLCOffset", "Show Speed Limit Offset", "Show the speed limit offset seperated from the speed limit in the onroad UI when using 'Speed Limit Controller'.", ""},
|
||||||
|
|
||||||
|
{"RandomEvents", "Random Events", "Enjoy a bit of unpredictability with random events that can occur during certain driving conditions.", "../frogpilot/assets/toggle_icons/icon_random.png"},
|
||||||
|
{"ScreenBrightness", "Screen Brightness", "Customize your screen brightness.", "../frogpilot/assets/toggle_icons/icon_light.png"},
|
||||||
|
{"SilentMode", "Silent Mode", "Mute openpilot sounds for a quieter driving experience.", "../frogpilot/assets/toggle_icons/icon_mute.png"},
|
||||||
|
{"WheelIcon", "Steering Wheel Icon", "Replace the default steering wheel icon with a custom design, adding a unique touch to your interface.", "../assets/offroad/icon_openpilot.png"},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &[param, title, desc, icon] : visualToggles) {
|
||||||
|
ParamControl *toggle;
|
||||||
|
|
||||||
|
if (param == "CameraView") {
|
||||||
|
std::vector<QString> cameraOptions{tr("Auto"), tr("Standard"), tr("Wide"), tr("Driver")};
|
||||||
|
FrogPilotButtonParamControl *preferredCamera = new FrogPilotButtonParamControl(param, title, desc, icon, cameraOptions);
|
||||||
|
toggle = preferredCamera;
|
||||||
|
|
||||||
|
} else if (param == "CustomTheme") {
|
||||||
|
FrogPilotParamManageControl *customThemeToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(customThemeToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(customThemeKeys.find(key.c_str()) != customThemeKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = customThemeToggle;
|
||||||
|
} else if (param == "CustomColors" || param == "CustomIcons" || param == "CustomSignals" || param == "CustomSounds") {
|
||||||
|
std::vector<QString> themeOptions{tr("Stock"), tr("Frog"), tr("Tesla"), tr("Stalin")};
|
||||||
|
FrogPilotButtonParamControl *themeSelection = new FrogPilotButtonParamControl(param, title, desc, icon, themeOptions);
|
||||||
|
toggle = themeSelection;
|
||||||
|
|
||||||
|
if (param == "CustomSounds") {
|
||||||
|
QObject::connect(static_cast<FrogPilotButtonParamControl*>(toggle), &FrogPilotButtonParamControl::buttonClicked, [this](int id) {
|
||||||
|
if (id == 1) {
|
||||||
|
if (FrogPilotConfirmationDialog::yesorno("Do you want to enable the bonus 'Goat' sound effect?", this)) {
|
||||||
|
params.putBool("GoatScream", true);
|
||||||
|
} else {
|
||||||
|
params.putBool("GoatScream", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (param == "CustomUI") {
|
||||||
|
FrogPilotParamManageControl *customUIToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(customUIToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(customOnroadUIKeys.find(key.c_str()) != customOnroadUIKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = customUIToggle;
|
||||||
|
} else if (param == "LeadInfo") {
|
||||||
|
std::vector<QString> leadInfoToggles{tr("UseSI")};
|
||||||
|
std::vector<QString> leadInfoToggleNames{tr("Use SI Values")};
|
||||||
|
toggle = new FrogPilotParamToggleControl(param, title, desc, icon, leadInfoToggles, leadInfoToggleNames);
|
||||||
|
|
||||||
|
} else if (param == "ModelUI") {
|
||||||
|
FrogPilotParamManageControl *modelUIToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(modelUIToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(modelUIKeys.find(key.c_str()) != modelUIKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = modelUIToggle;
|
||||||
|
} else if (param == "LaneLinesWidth" || param == "RoadEdgesWidth") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 24, std::map<int, QString>(), this, false, " inches");
|
||||||
|
} else if (param == "PathEdgeWidth") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 100, std::map<int, QString>(), this, false, "%");
|
||||||
|
} else if (param == "PathWidth") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 100, std::map<int, QString>(), this, false, " feet", 10);
|
||||||
|
|
||||||
|
} else if (param == "QOLVisuals") {
|
||||||
|
FrogPilotParamManageControl *qolToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(qolToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(qolKeys.find(key.c_str()) != qolKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = qolToggle;
|
||||||
|
|
||||||
|
} else if (param == "ScreenBrightness") {
|
||||||
|
std::map<int, QString> brightnessLabels;
|
||||||
|
for (int i = 0; i <= 101; ++i) {
|
||||||
|
brightnessLabels[i] = i == 0 ? "Screen Off" : i == 101 ? "Auto" : QString::number(i) + "%";
|
||||||
|
}
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 101, brightnessLabels, this, false);
|
||||||
|
|
||||||
|
} else if (param == "WheelIcon") {
|
||||||
|
std::vector<QString> wheelToggles{"RotatingWheel"};
|
||||||
|
std::vector<QString> wheelToggleNames{tr("Rotating")};
|
||||||
|
std::map<int, QString> steeringWheelLabels = {{0, "Stock"}, {1, "Lexus"}, {2, "Toyota"}, {3, "Frog"}, {4, "Rocket"}, {5, "Hyundai"}, {6, "Stalin"}};
|
||||||
|
toggle = new FrogPilotParamValueToggleControl(param, title, desc, icon, 0, 6, steeringWheelLabels, this, true, "", 1, wheelToggles, wheelToggleNames);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
toggle = new ParamControl(param, title, desc, icon, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
addItem(toggle);
|
||||||
|
toggles[param.toStdString()] = toggle;
|
||||||
|
|
||||||
|
QObject::connect(toggle, &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamValueControl*>(toggle), &FrogPilotParamValueControl::buttonPressed, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(toggle, &AbstractControl::showDescriptionEvent, [this]() {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamManageControl*>(toggle), &FrogPilotParamManageControl::manageButtonClicked, [this]() {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string> rebootKeys = {"DriveStats"};
|
||||||
|
for (const std::string &key : rebootKeys) {
|
||||||
|
QObject::connect(toggles[key], &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) {
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
customOnroadUIKeys = {"AdjacentPath", "BlindSpotPath", "ShowFPS", "LeadInfo", "RoadNameUI", "UseVienna"};
|
||||||
|
customThemeKeys = {"CustomColors", "CustomIcons", "CustomSignals", "CustomSounds"};
|
||||||
|
modelUIKeys = {"AccelerationPath", "LaneLinesWidth", "PathEdgeWidth", "PathWidth", "RoadEdgesWidth", "UnlimitedLength"};
|
||||||
|
qolKeys = {"DriveStats", "HideSpeed", "ShowSLCOffset"};
|
||||||
|
|
||||||
|
QObject::connect(device(), &Device::interactiveTimeout, this, &OscarPilotBasicPanel::hideSubToggles);
|
||||||
|
QObject::connect(parent, &OscarSettingsWindow::closeParentToggle, this, &OscarPilotBasicPanel::hideSubToggles);
|
||||||
|
// QObject::connect(parent, &OscarSettingsWindow::updateMetric, this, &OscarPilotBasicPanel::updateMetric);
|
||||||
|
|
||||||
|
hideSubToggles();
|
||||||
|
// updateMetric();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OscarPilotBasicPanel::updateToggles() {
|
||||||
|
std::thread([this]() {
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", true);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", false);
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
// void OscarPilotBasicPanel::updateMetric() {
|
||||||
|
// bool previousIsMetric = isMetric;
|
||||||
|
// isMetric = params.getBool("IsMetric");
|
||||||
|
|
||||||
|
// if (isMetric != previousIsMetric) {
|
||||||
|
// double distanceConversion = isMetric ? INCH_TO_CM : CM_TO_INCH;
|
||||||
|
// double speedConversion = isMetric ? FOOT_TO_METER : METER_TO_FOOT;
|
||||||
|
// params.putInt("LaneLinesWidth", std::nearbyint(params.getInt("LaneLinesWidth") * distanceConversion));
|
||||||
|
// params.putInt("RoadEdgesWidth", std::nearbyint(params.getInt("RoadEdgesWidth") * distanceConversion));
|
||||||
|
// params.putInt("PathWidth", std::nearbyint(params.getInt("PathWidth") * speedConversion));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// FrogPilotParamValueControl *laneLinesWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["LaneLinesWidth"]);
|
||||||
|
// FrogPilotParamValueControl *roadEdgesWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["RoadEdgesWidth"]);
|
||||||
|
// FrogPilotParamValueControl *pathWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["PathWidth"]);
|
||||||
|
|
||||||
|
// if (isMetric) {
|
||||||
|
// laneLinesWidthToggle->setDescription("Customize the lane line width.\n\nDefault matches the Vienna average of 10 centimeters.");
|
||||||
|
// roadEdgesWidthToggle->setDescription("Customize the road edges width.\n\nDefault is 1/2 of the Vienna average lane line width of 10 centimeters.");
|
||||||
|
|
||||||
|
// laneLinesWidthToggle->updateControl(0, 60, " centimeters");
|
||||||
|
// roadEdgesWidthToggle->updateControl(0, 60, " centimeters");
|
||||||
|
// pathWidthToggle->updateControl(0, 30, " meters");
|
||||||
|
// } else {
|
||||||
|
// laneLinesWidthToggle->setDescription("Customize the lane line width.\n\nDefault matches the MUTCD average of 4 inches.");
|
||||||
|
// roadEdgesWidthToggle->setDescription("Customize the road edges width.\n\nDefault is 1/2 of the MUTCD average lane line width of 4 inches.");
|
||||||
|
|
||||||
|
// laneLinesWidthToggle->updateControl(0, 24, " inches");
|
||||||
|
// roadEdgesWidthToggle->updateControl(0, 24, " inches");
|
||||||
|
// pathWidthToggle->updateControl(0, 100, " feet");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// laneLinesWidthToggle->refresh();
|
||||||
|
// roadEdgesWidthToggle->refresh();
|
||||||
|
|
||||||
|
// previousIsMetric = isMetric;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void OscarPilotBasicPanel::updateMetric() {
|
||||||
|
// bool previousIsMetric = isMetric;
|
||||||
|
// isMetric = params.getBool("IsMetric");
|
||||||
|
|
||||||
|
// if (isMetric != previousIsMetric) {
|
||||||
|
// double distanceConversion = isMetric ? INCH_TO_CM : CM_TO_INCH;
|
||||||
|
// double speedConversion = isMetric ? FOOT_TO_METER : METER_TO_FOOT;
|
||||||
|
// params.putInt("LaneLinesWidth", std::nearbyint(params.getInt("LaneLinesWidth") * distanceConversion));
|
||||||
|
// params.putInt("RoadEdgesWidth", std::nearbyint(params.getInt("RoadEdgesWidth") * distanceConversion));
|
||||||
|
// params.putInt("PathWidth", std::nearbyint(params.getInt("PathWidth") * speedConversion));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// FrogPilotParamValueControl *laneLinesWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["LaneLinesWidth"]);
|
||||||
|
// FrogPilotParamValueControl *roadEdgesWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["RoadEdgesWidth"]);
|
||||||
|
// FrogPilotParamValueControl *pathWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["PathWidth"]);
|
||||||
|
|
||||||
|
// if (isMetric) {
|
||||||
|
// laneLinesWidthToggle->setDescription("Customize the lane line width.\n\nDefault matches the Vienna average of 10 centimeters.");
|
||||||
|
// roadEdgesWidthToggle->setDescription("Customize the road edges width.\n\nDefault is 1/2 of the Vienna average lane line width of 10 centimeters.");
|
||||||
|
|
||||||
|
// laneLinesWidthToggle->updateControl(0, 60, " centimeters");
|
||||||
|
// roadEdgesWidthToggle->updateControl(0, 60, " centimeters");
|
||||||
|
// pathWidthToggle->updateControl(0, 30, " meters");
|
||||||
|
// } else {
|
||||||
|
// laneLinesWidthToggle->setDescription("Customize the lane line width.\n\nDefault matches the MUTCD average of 4 inches.");
|
||||||
|
// roadEdgesWidthToggle->setDescription("Customize the road edges width.\n\nDefault is 1/2 of the MUTCD average lane line width of 4 inches.");
|
||||||
|
|
||||||
|
// laneLinesWidthToggle->updateControl(0, 24, " inches");
|
||||||
|
// roadEdgesWidthToggle->updateControl(0, 24, " inches");
|
||||||
|
// pathWidthToggle->updateControl(0, 100, " feet");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// laneLinesWidthToggle->refresh();
|
||||||
|
// roadEdgesWidthToggle->refresh();
|
||||||
|
|
||||||
|
// previousIsMetric = isMetric;
|
||||||
|
// }
|
||||||
|
|
||||||
|
void OscarPilotBasicPanel::parentToggleClicked() {
|
||||||
|
this->openParentToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OscarPilotBasicPanel::hideSubToggles() {
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
bool subToggles = modelUIKeys.find(key.c_str()) != modelUIKeys.end() ||
|
||||||
|
customOnroadUIKeys.find(key.c_str()) != customOnroadUIKeys.end() ||
|
||||||
|
customThemeKeys.find(key.c_str()) != customThemeKeys.end() ||
|
||||||
|
qolKeys.find(key.c_str()) != qolKeys.end();
|
||||||
|
toggle->setVisible(!subToggles);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->closeParentToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OscarPilotBasicPanel::hideEvent(QHideEvent *event) {
|
||||||
|
hideSubToggles();
|
||||||
|
}
|
||||||
34
selfdrive/clearpilot/settings/basic.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/frogpilot_functions.h"
|
||||||
|
#include "selfdrive/oscarpilot/settings/settings.h"
|
||||||
|
|
||||||
|
class OscarPilotBasicPanel : public FrogPilotListWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit OscarPilotBasicPanel(OscarSettingsWindow *parent);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void closeParentToggle();
|
||||||
|
void openParentToggle();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void hideEvent(QHideEvent *event);
|
||||||
|
void hideSubToggles();
|
||||||
|
void parentToggleClicked();
|
||||||
|
void updateToggles();
|
||||||
|
void poweroff();
|
||||||
|
void reboot();
|
||||||
|
|
||||||
|
std::set<QString> customOnroadUIKeys;
|
||||||
|
|
||||||
|
std::map<std::string, ParamControl*> toggles;
|
||||||
|
|
||||||
|
Params params;
|
||||||
|
Params paramsMemory{"/dev/shm/params"};
|
||||||
|
|
||||||
|
bool isMetric = params.getBool("IsMetric");
|
||||||
|
};
|
||||||
36
selfdrive/clearpilot/settings/basic.h.example
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/frogpilot_functions.h"
|
||||||
|
#include "selfdrive/oscarpilot/settings/settings.h"
|
||||||
|
|
||||||
|
class OscarPilotBasicPanel : public FrogPilotListWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit OscarPilotBasicPanel(OscarSettingsWindow *parent);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void closeParentToggle();
|
||||||
|
void openParentToggle();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void hideEvent(QHideEvent *event);
|
||||||
|
void hideSubToggles();
|
||||||
|
void parentToggleClicked();
|
||||||
|
void updateMetric();
|
||||||
|
void updateToggles();
|
||||||
|
|
||||||
|
std::set<QString> customOnroadUIKeys;
|
||||||
|
std::set<QString> customThemeKeys;
|
||||||
|
std::set<QString> modelUIKeys;
|
||||||
|
std::set<QString> qolKeys;
|
||||||
|
|
||||||
|
std::map<std::string, ParamControl*> toggles;
|
||||||
|
|
||||||
|
Params params;
|
||||||
|
Params paramsMemory{"/dev/shm/params"};
|
||||||
|
|
||||||
|
bool isMetric = params.getBool("IsMetric");
|
||||||
|
};
|
||||||
177
selfdrive/clearpilot/settings/defaults.cc
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/frogpilot_functions.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
|
bool FrogPilotConfirmationDialog::toggle(const QString &prompt_text, const QString &confirm_text, QWidget *parent) {
|
||||||
|
ConfirmationDialog d = ConfirmationDialog(prompt_text, confirm_text, tr("Reboot Later"), false, parent);
|
||||||
|
return d.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FrogPilotConfirmationDialog::toggleAlert(const QString &prompt_text, const QString &button_text, QWidget *parent) {
|
||||||
|
ConfirmationDialog d = ConfirmationDialog(prompt_text, button_text, "", false, parent);
|
||||||
|
return d.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FrogPilotConfirmationDialog::yesorno(const QString &prompt_text, QWidget *parent) {
|
||||||
|
ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Yes"), tr("No"), false, parent);
|
||||||
|
return d.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
FrogPilotButtonIconControl::FrogPilotButtonIconControl(const QString &title, const QString &text, const QString &desc, const QString &icon, QWidget *parent) : AbstractControl(title, desc, icon, parent) {
|
||||||
|
btn.setText(text);
|
||||||
|
btn.setStyleSheet(R"(
|
||||||
|
QPushButton {
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 35px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
color: #33E4E4E4;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
btn.setFixedSize(250, 100);
|
||||||
|
QObject::connect(&btn, &QPushButton::clicked, this, &FrogPilotButtonIconControl::clicked);
|
||||||
|
hlayout->addWidget(&btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDefaultParams() {
|
||||||
|
Params params = Params();
|
||||||
|
bool FrogsGoMoo = params.get("DongleId").substr(0, 3) == "be6";
|
||||||
|
|
||||||
|
bool brianbot = params.get("DongleId").substr(0, 6) == "90bb71";
|
||||||
|
|
||||||
|
std::map<std::string, std::string> defaultValues {
|
||||||
|
{"AccelerationPath", brianbot ? "1" : "0"},
|
||||||
|
{"AccelerationProfile", brianbot ? "2" : "2"},
|
||||||
|
{"AdjacentPath", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"AdjustablePersonalities", "0"},
|
||||||
|
{"AggressiveAcceleration", "1"},
|
||||||
|
{"AggressiveFollow", FrogsGoMoo ? "10" : "12"},
|
||||||
|
{"AggressiveJerk", FrogsGoMoo ? "6" : "5"},
|
||||||
|
{"AlwaysOnLateral", "1"},
|
||||||
|
{"AlwaysOnLateralMain", brianbot ? "1" : "0"},
|
||||||
|
{"AverageCurvature", brianbot ? "1" : "0"},
|
||||||
|
{"BlindSpotPath", "1"},
|
||||||
|
{"CameraView", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"CECurves", "1"},
|
||||||
|
{"CECurvesLead", "0"},
|
||||||
|
{"CENavigation", "1"},
|
||||||
|
{"CESignal", "1"},
|
||||||
|
{"CESlowerLead", "0"},
|
||||||
|
{"CESpeed", "0"},
|
||||||
|
{"CESpeedLead", "0"},
|
||||||
|
{"CEStopLights", "0"},
|
||||||
|
{"CEStopLightsLead", FrogsGoMoo ? "0" : "0"},
|
||||||
|
{"Compass", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"ConditionalExperimental", "1"},
|
||||||
|
{"CurveSensitivity", FrogsGoMoo ? "125" : "100"},
|
||||||
|
{"CustomColors", "1"},
|
||||||
|
{"CustomIcons", "1"},
|
||||||
|
{"CustomPersonalities", "0"},
|
||||||
|
{"CustomSignals", "0"},
|
||||||
|
{"CustomSounds", "1"},
|
||||||
|
{"CustomTheme", "1"},
|
||||||
|
{"CustomUI", "0"},
|
||||||
|
{"DeviceShutdown", "1"},
|
||||||
|
{"DisableOnroadUploads", "1"},
|
||||||
|
{"DriverCamera", "0"},
|
||||||
|
{"DriveStats", "1"},
|
||||||
|
{"EVTable", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"ExperimentalModeActivation", "1"},
|
||||||
|
{"ExperimentalModeViaLKAS", "0"},
|
||||||
|
{"ExperimentalModeViaScreen", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"Fahrenheit", "0"},
|
||||||
|
{"FireTheBabysitter", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"FullMap", "0"},
|
||||||
|
{"GasRegenCmd", "0"},
|
||||||
|
{"GoatScream", "0"},
|
||||||
|
{"GreenLightAlert", "0"},
|
||||||
|
{"HideSpeed", "0"},
|
||||||
|
{"HigherBitrate", "0"},
|
||||||
|
{"LaneChangeTime", "0"},
|
||||||
|
{"LaneDetection", "1"},
|
||||||
|
{"LaneLinesWidth", "4"},
|
||||||
|
{"LateralTune", "1"},
|
||||||
|
{"LeadInfo", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"LockDoors", "0"},
|
||||||
|
{"LongitudinalTune", "1"},
|
||||||
|
{"LongPitch", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"LowerVolt", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"Model", "3"},
|
||||||
|
{"ModelUI", "1"},
|
||||||
|
{"MTSCEnabled", "1"},
|
||||||
|
{"MuteDM", FrogsGoMoo ? "1" : "1"},
|
||||||
|
{"MuteDoor", FrogsGoMoo ? "1" : "1"},
|
||||||
|
{"MuteOverheated", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"MuteSeatbelt", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"NNFF", FrogsGoMoo ? "1" : "1"},
|
||||||
|
{"NoLogging", "1"},
|
||||||
|
{"NudgelessLaneChange", "0"},
|
||||||
|
{"NumericalTemp", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"Offset1", "3"},
|
||||||
|
{"Offset2", FrogsGoMoo ? "7" : "5"},
|
||||||
|
{"Offset3", "7"},
|
||||||
|
{"Offset4", FrogsGoMoo ? "20" : "7"},
|
||||||
|
{"OneLaneChange", "1"},
|
||||||
|
{"PathEdgeWidth", "20"},
|
||||||
|
{"PathWidth", "61"},
|
||||||
|
{"PauseLateralOnSignal", "20"},
|
||||||
|
{"PreferredSchedule", "0"},
|
||||||
|
{"QOLControls", "1"},
|
||||||
|
{"QOLVisuals", "1"},
|
||||||
|
{"RandomEvents", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"RelaxedFollow", "30"},
|
||||||
|
{"RelaxedJerk", "50"},
|
||||||
|
{"ReverseCruise", "0"},
|
||||||
|
{"RoadEdgesWidth", "2"},
|
||||||
|
{"RoadNameUI", "1"},
|
||||||
|
{"RotatingWheel", "1"},
|
||||||
|
{"ScreenBrightness", "101"},
|
||||||
|
{"SearchInput", "0"},
|
||||||
|
{"ShowCPU", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"ShowFPS", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"ShowGPU", "0"},
|
||||||
|
{"ShowMemoryUsage", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"Sidebar", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"SilentMode", "0"},
|
||||||
|
{"SLCFallback", "2"},
|
||||||
|
{"SLCOverride", FrogsGoMoo ? "2" : "1"},
|
||||||
|
{"SLCPriority", "1"},
|
||||||
|
{"SmoothBraking", "1"},
|
||||||
|
{"SNGHack", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"SpeedLimitController", "1"},
|
||||||
|
{"StandardFollow", "15"},
|
||||||
|
{"StandardJerk", "10"},
|
||||||
|
{"StoppingDistance", FrogsGoMoo ? "6" : "0"},
|
||||||
|
{"TSS2Tune", "1"},
|
||||||
|
{"TurnAggressiveness", FrogsGoMoo ? "150" : "100"}, // Test 90?
|
||||||
|
{"TurnDesires", "0"},
|
||||||
|
{"UnlimitedLength", "1"},
|
||||||
|
{"UseSI", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"UseVienna", "0"},
|
||||||
|
{"VisionTurnControl", "1"},
|
||||||
|
{"WheelIcon", FrogsGoMoo ? "1" : "0"}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool rebootRequired = false;
|
||||||
|
for (const auto &[key, value] : defaultValues) {
|
||||||
|
if (params.get(key).empty()) {
|
||||||
|
params.put(key, value);
|
||||||
|
rebootRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rebootRequired) {
|
||||||
|
while (!std::filesystem::exists("/data/openpilot/prebuilt")) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
}
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
}
|
||||||
0
selfdrive/clearpilot/settings/defaults.h
Normal file
38
selfdrive/clearpilot/settings/newmenus
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
Device
|
||||||
|
Network & Services
|
||||||
|
- Standard Wifi
|
||||||
|
- Metered Wifi
|
||||||
|
- SSH
|
||||||
|
- Web companion
|
||||||
|
- Route logging
|
||||||
|
- Dashcam upload
|
||||||
|
- Live driver monitoring / fleet mgr
|
||||||
|
- Weather
|
||||||
|
- Display alerts
|
||||||
|
- API key
|
||||||
|
- Random facts
|
||||||
|
- Get random fact about area driver is in (spoken, ai)
|
||||||
|
Basic
|
||||||
|
- Enable openpilot / Dashcam only
|
||||||
|
- Always on Lateral: True / False
|
||||||
|
- Longitudial Control scheme: Stock / Stock+SLC / OpenPilot
|
||||||
|
- Experimental Mode: never / on curves / always
|
||||||
|
- Longitudial Control scheme: Stock / Stock+SLC / Experimental
|
||||||
|
- Speed Limit offset customization:
|
||||||
|
- Lane Change Assist: Disabled / Nudge Wheel
|
||||||
|
- Driver Attention Monitoring: Strict / Relaxed
|
||||||
|
- Hands On Wheel: Always / At Dusk / 2+ hrs driving
|
||||||
|
Advanced
|
||||||
|
Navigation
|
||||||
|
Customization
|
||||||
|
Extras
|
||||||
|
- Window down notification
|
||||||
|
- Set climate on drive mode
|
||||||
|
|
||||||
|
Fleet Mgr Notes:
|
||||||
|
- Seperate fork for fleet mgr
|
||||||
|
- Self drive preferences
|
||||||
|
- Distance traveled
|
||||||
|
- SLC
|
||||||
|
- Driver camera
|
||||||
|
|
||||||
20
selfdrive/clearpilot/settings/notes
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Goals:
|
||||||
|
|
||||||
|
- slow down on experimental mode
|
||||||
|
- LKAS button - should toggle between drive, weather, weather radar. Hold for screen off
|
||||||
|
- car dashboard should have enough indicators that it is able to represent drive state without screen on
|
||||||
|
- set speed limit on engage cruise control
|
||||||
|
|
||||||
|
qol:
|
||||||
|
- customize babysitter - driver awareness timeouts daytime, nighttime, require steering wheel
|
||||||
|
- set climate on start, roll up windows on stop
|
||||||
|
- upload park location on stop
|
||||||
|
- low bandwidth / high bandwidth wifi, upload recordings on high bandwidth
|
||||||
|
- list drives w/ image on stop
|
||||||
|
- stop light / stop sign warning alert
|
||||||
|
|
||||||
|
release:
|
||||||
|
- reenable training screen
|
||||||
|
- - disregard for my device id
|
||||||
|
- cleanup code
|
||||||
|
- rename to diamondpilot
|
||||||
176
selfdrive/clearpilot/settings/settings.cc
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QScrollBar>
|
||||||
|
|
||||||
|
#include "selfdrive/ui/qt/network/networking.h"
|
||||||
|
|
||||||
|
#include "common/params.h"
|
||||||
|
#include "common/watchdog.h"
|
||||||
|
#include "common/util.h"
|
||||||
|
#include "system/hardware/hw.h"
|
||||||
|
#include "selfdrive/ui/qt/widgets/controls.h"
|
||||||
|
#include "selfdrive/ui/qt/widgets/input.h"
|
||||||
|
#include "selfdrive/ui/qt/widgets/scrollview.h"
|
||||||
|
#include "selfdrive/ui/qt/widgets/ssh_keys.h"
|
||||||
|
#include "selfdrive/ui/qt/widgets/toggle.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
#include "selfdrive/ui/qt/util.h"
|
||||||
|
#include "selfdrive/ui/qt/qt_window.h"
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/navigation/ui/navigation_settings.h"
|
||||||
|
#include "selfdrive/frogpilot/ui/control_settings.h"
|
||||||
|
#include "selfdrive/oscarpilot/settings/basic.h"
|
||||||
|
#include "selfdrive/frogpilot/ui/visual_settings.h"
|
||||||
|
|
||||||
|
#include "selfdrive/oscarpilot/settings/settings.h"
|
||||||
|
|
||||||
|
|
||||||
|
void OscarSettingsWindow::showEvent(QShowEvent *event) {
|
||||||
|
setCurrentPanel(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OscarSettingsWindow::setCurrentPanel(int index, const QString ¶m) {
|
||||||
|
panel_widget->setCurrentIndex(index);
|
||||||
|
nav_btns->buttons()[index]->setChecked(true);
|
||||||
|
if (!param.isEmpty()) {
|
||||||
|
emit expandToggleDescription(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OscarSettingsWindow::OscarSettingsWindow(QWidget *parent) : QFrame(parent) {
|
||||||
|
|
||||||
|
// setup two main layouts
|
||||||
|
sidebar_widget = new QWidget;
|
||||||
|
QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget);
|
||||||
|
sidebar_layout->setMargin(0);
|
||||||
|
panel_widget = new QStackedWidget();
|
||||||
|
|
||||||
|
// close button
|
||||||
|
QPushButton *close_btn = new QPushButton(tr("← Back"));
|
||||||
|
close_btn->setStyleSheet(R"(
|
||||||
|
QPushButton {
|
||||||
|
font-size: 50px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
border 1px grey solid;
|
||||||
|
border-radius: 25px;
|
||||||
|
background-color: #292929;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #3B3B3B;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
close_btn->setFixedSize(300, 125);
|
||||||
|
sidebar_layout->addSpacing(10);
|
||||||
|
sidebar_layout->addWidget(close_btn, 0, Qt::AlignRight);
|
||||||
|
QObject::connect(close_btn, &QPushButton::clicked, [this]() {
|
||||||
|
if (frogPilotTogglesOpen) {
|
||||||
|
frogPilotTogglesOpen = false;
|
||||||
|
this->closeParentToggle();
|
||||||
|
} else {
|
||||||
|
this->closeSettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
FrogPilotControlsPanel *frogpilotControls = new FrogPilotControlsPanel(this);
|
||||||
|
QObject::connect(frogpilotControls, &FrogPilotControlsPanel::closeParentToggle, this, [this]() {frogPilotTogglesOpen = false;});
|
||||||
|
QObject::connect(frogpilotControls, &FrogPilotControlsPanel::openParentToggle, this, [this]() {frogPilotTogglesOpen = true;});
|
||||||
|
|
||||||
|
// DevicePanel *device = new DevicePanel(this);
|
||||||
|
// QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide);
|
||||||
|
// QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView);
|
||||||
|
|
||||||
|
QList<QPair<QString, QWidget *>> panels = {
|
||||||
|
{tr("Basic"), new OscarPilotBasicPanel(this)},
|
||||||
|
{tr("Advanced"), frogpilotControls},
|
||||||
|
{tr("Network"), new Networking(this)},
|
||||||
|
{tr("Software"), new SoftwarePanel(this)},
|
||||||
|
// {tr("Device"), new DevicePanel(this)},
|
||||||
|
// FrogPilotControlsPanel
|
||||||
|
// {tr("OscarCloud"), basic},
|
||||||
|
// {tr("Logging"), basic}, // Log / Upload driver cam, Routes
|
||||||
|
// {tr("System"), new OscarPilotBasicPanel(this)}, // Debugging
|
||||||
|
// {tr("Status"), basic}, // Report on stuff like connectivity, free space, detected features
|
||||||
|
// {tr("Extra"), basic}, // Custom cloud services, QOL automations
|
||||||
|
};
|
||||||
|
|
||||||
|
nav_btns = new QButtonGroup(this);
|
||||||
|
for (auto &[name, panel] : panels) {
|
||||||
|
QPushButton *btn = new QPushButton(name);
|
||||||
|
btn->setCheckable(true);
|
||||||
|
btn->setChecked(nav_btns->buttons().size() == 0);
|
||||||
|
btn->setStyleSheet(R"(
|
||||||
|
QPushButton {
|
||||||
|
color: grey;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
font-size: 65px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
QPushButton:checked {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
color: #ADADAD;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||||
|
nav_btns->addButton(btn);
|
||||||
|
sidebar_layout->addWidget(btn, 0, Qt::AlignRight);
|
||||||
|
|
||||||
|
const int lr_margin = name != tr("Network") ? 50 : 0; // Network panel handles its own margins
|
||||||
|
panel->setContentsMargins(lr_margin, 25, lr_margin, 25);
|
||||||
|
|
||||||
|
ScrollView *panel_frame = new ScrollView(panel, this);
|
||||||
|
panel_widget->addWidget(panel_frame);
|
||||||
|
|
||||||
|
if (name == tr("Controls") || name == tr("Visuals")) {
|
||||||
|
QScrollBar *scrollbar = panel_frame->verticalScrollBar();
|
||||||
|
|
||||||
|
QObject::connect(scrollbar, &QScrollBar::valueChanged, this, [this](int value) {
|
||||||
|
if (!frogPilotTogglesOpen) {
|
||||||
|
previousScrollPosition = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(scrollbar, &QScrollBar::rangeChanged, this, [this, panel_frame]() {
|
||||||
|
panel_frame->restorePosition(previousScrollPosition);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() {
|
||||||
|
previousScrollPosition = 0;
|
||||||
|
btn->setChecked(true);
|
||||||
|
panel_widget->setCurrentWidget(w);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sidebar_layout->setContentsMargins(50, 50, 100, 50);
|
||||||
|
|
||||||
|
// main settings layout, sidebar + main panel
|
||||||
|
QHBoxLayout *main_layout = new QHBoxLayout(this);
|
||||||
|
|
||||||
|
sidebar_widget->setFixedWidth(500);
|
||||||
|
main_layout->addWidget(sidebar_widget);
|
||||||
|
main_layout->addWidget(panel_widget);
|
||||||
|
|
||||||
|
setStyleSheet(R"(
|
||||||
|
* {
|
||||||
|
color: white;
|
||||||
|
font-size: 50px;
|
||||||
|
}
|
||||||
|
OscarSettingsWindow {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
QStackedWidget, ScrollView {
|
||||||
|
background-color: #292929;
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
}
|
||||||
43
selfdrive/clearpilot/settings/settings.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <QButtonGroup>
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QStackedWidget>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "selfdrive/ui/qt/util.h"
|
||||||
|
#include "selfdrive/ui/qt/widgets/controls.h"
|
||||||
|
|
||||||
|
// ********** settings window + top-level panels **********
|
||||||
|
class OscarSettingsWindow : public QFrame {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit OscarSettingsWindow(QWidget *parent = 0);
|
||||||
|
void setCurrentPanel(int index, const QString ¶m = "");
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void closeSettings();
|
||||||
|
void showDriverView();
|
||||||
|
void expandToggleDescription(const QString ¶m);
|
||||||
|
|
||||||
|
// FrogPilot signals
|
||||||
|
void closeParentToggle();
|
||||||
|
private:
|
||||||
|
QWidget *sidebar_widget;
|
||||||
|
QButtonGroup *nav_btns;
|
||||||
|
QStackedWidget *panel_widget;
|
||||||
|
|
||||||
|
// FrogPilot variables
|
||||||
|
bool frogPilotTogglesOpen;
|
||||||
|
int previousScrollPosition;
|
||||||
|
};
|
||||||
|
|
||||||
42
selfdrive/clearpilot/settings/style.cc
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/frogpilot_functions.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
|
bool FrogPilotConfirmationDialog::toggle(const QString &prompt_text, const QString &confirm_text, QWidget *parent) {
|
||||||
|
ConfirmationDialog d = ConfirmationDialog(prompt_text, confirm_text, tr("Reboot Later"), false, parent);
|
||||||
|
return d.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FrogPilotConfirmationDialog::toggleAlert(const QString &prompt_text, const QString &button_text, QWidget *parent) {
|
||||||
|
ConfirmationDialog d = ConfirmationDialog(prompt_text, button_text, "", false, parent);
|
||||||
|
return d.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FrogPilotConfirmationDialog::yesorno(const QString &prompt_text, QWidget *parent) {
|
||||||
|
ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Yes"), tr("No"), false, parent);
|
||||||
|
return d.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
FrogPilotButtonIconControl::FrogPilotButtonIconControl(const QString &title, const QString &text, const QString &desc, const QString &icon, QWidget *parent) : AbstractControl(title, desc, icon, parent) {
|
||||||
|
btn.setText(text);
|
||||||
|
btn.setStyleSheet(R"(
|
||||||
|
QPushButton {
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 35px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
color: #33E4E4E4;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
btn.setFixedSize(250, 100);
|
||||||
|
QObject::connect(&btn, &QPushButton::clicked, this, &FrogPilotButtonIconControl::clicked);
|
||||||
|
hlayout->addWidget(&btn);
|
||||||
|
}
|
||||||
16
selfdrive/clearpilot/settings/style.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
const button_style = R"(
|
||||||
|
QPushButton {
|
||||||
|
color: grey;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
font-size: 65px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
QPushButton:checked {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
color: #ADADAD;
|
||||||
|
}
|
||||||
|
)";
|
||||||
0
selfdrive/clearpilot/settings_sync.py
Normal file
0
selfdrive/clearpilot/stocklong.py
Normal file
BIN
selfdrive/clearpilot/theme/images/button_flag.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
selfdrive/clearpilot/theme/images/button_home.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
selfdrive/clearpilot/theme/images/button_settings.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
selfdrive/clearpilot/theme/images/turn_signal_1.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
selfdrive/clearpilot/theme/images/turn_signal_1_red.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
selfdrive/clearpilot/theme/images/turn_signal_2.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
selfdrive/clearpilot/theme/images/turn_signal_3.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
selfdrive/clearpilot/theme/images/turn_signal_4.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
selfdrive/clearpilot/theme/sounds/disengage.wav
Normal file
BIN
selfdrive/clearpilot/theme/sounds/engage.wav
Normal file
BIN
selfdrive/clearpilot/theme/sounds/firefox.wav
Normal file
BIN
selfdrive/clearpilot/theme/sounds/prompt.wav
Normal file
BIN
selfdrive/clearpilot/theme/sounds/prompt_distracted.wav
Normal file
BIN
selfdrive/clearpilot/theme/sounds/refuse.wav
Normal file
BIN
selfdrive/clearpilot/theme/sounds/warning_immediate.wav
Normal file
BIN
selfdrive/clearpilot/theme/sounds/warning_soft.wav
Normal file
559
selfdrive/clearpilot/ui/control_settings.cc
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
#include "selfdrive/frogpilot/ui/control_settings.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
|
FrogPilotControlsPanel::FrogPilotControlsPanel(OscarSettingsWindow *parent) : FrogPilotListWidget(parent) {
|
||||||
|
const std::vector<std::tuple<QString, QString, QString, QString>> controlToggles {
|
||||||
|
{"AdjustablePersonalities", "Adjustable Personalities", "Use the 'Distance' button on the steering wheel or the onroad UI to switch between openpilot's driving personalities.\n\n1 bar = Aggressive\n2 bars = Standard\n3 bars = Relaxed", "../frogpilot/assets/toggle_icons/icon_distance.png"},
|
||||||
|
{"AlwaysOnLateral", "Always on Lateral", "Maintain openpilot lateral control when the brake or gas pedals are used.\n\nDeactivation occurs only through the 'Cruise Control' button.", "../frogpilot/assets/toggle_icons/icon_always_on_lateral.png"},
|
||||||
|
|
||||||
|
{"ConditionalExperimental", "Conditional Experimental Mode", "Automatically switches to 'Experimental Mode' under predefined conditions.", "../frogpilot/assets/toggle_icons/icon_conditional.png"},
|
||||||
|
{"CECurves", "Curve Detected Ahead", "Switch to 'Experimental Mode' when a curve is detected.", ""},
|
||||||
|
{"CENavigation", "Navigation Based", "Switch to 'Experimental Mode' based on navigation data. (i.e. Intersections, stop signs, etc.)", ""},
|
||||||
|
{"CESlowerLead", "Slower Lead Detected Ahead", "Switch to 'Experimental Mode' when a slower lead vehicle is detected ahead.", ""},
|
||||||
|
// {"CEStopLights", "Stop Lights and Stop Signs", "Switch to 'Experimental Mode' when a stop light or stop sign is detected.", ""},
|
||||||
|
{"CESignal", "Turn Signal When Below Highway Speeds", "Switch to 'Experimental Mode' when using turn signals below highway speeds to help assit with turns.", ""},
|
||||||
|
{"CSLCEnabled", "Custom Stock Longitudinal Control", "Use cruise control button spamming to adjust cruise set speed based on MTSC, VTSC, and SLC", "../frogpilot/assets/toggle_icons/icon_custom.png"},
|
||||||
|
{"CustomPersonalities", "Custom Driving Personalities", "Customize the driving personality profiles to your driving style.", "../frogpilot/assets/toggle_icons/icon_custom.png"},
|
||||||
|
{"DeviceShutdown", "Device Shutdown Timer", "Configure the timer for automatic device shutdown when offroad conserving energy and preventing battery drain.", "../frogpilot/assets/toggle_icons/icon_time.png"},
|
||||||
|
{"ExperimentalModeActivation", "Experimental Mode Via", "Toggle Experimental Mode by double-clicking the 'Lane Departure'/'LKAS' button or double tapping screen.\n\nOverrides 'Conditional Experimental Mode'.", "../assets/img_experimental_white.svg"},
|
||||||
|
|
||||||
|
{"FireTheBabysitter", "Fire the Babysitter", "Deactivate some of openpilot's 'Babysitter' protocols for more user autonomy.", "../frogpilot/assets/toggle_icons/icon_babysitter.png"},
|
||||||
|
{"NoLogging", "Disable All Logging", "Turn off all data tracking to enhance privacy or reduce thermal load.\n\nWARNING: This action will prevent drive recording and data cannot be recovered!", ""},
|
||||||
|
{"MuteDoor", "Mute Door Open Alert", "Disable alerts for open doors.", ""},
|
||||||
|
{"MuteDM", "Mute Driver Monitoring", "Disable driver monitoring.", ""},
|
||||||
|
{"MuteOverheated", "Mute Overheated System Alert", "Disable alerts for the device being overheated.", ""},
|
||||||
|
{"MuteSeatbelt", "Mute Seatbelt Unlatched Alert", "Disable alerts for unlatched seatbelts.", ""},
|
||||||
|
{"OfflineMode", "Offline Mode", "Allow the device to be offline indefinitely.", ""},
|
||||||
|
|
||||||
|
{"LateralTune", "Lateral Tuning", "Modify openpilot's steering behavior.", "../frogpilot/assets/toggle_icons/icon_lateral_tune.png"},
|
||||||
|
{"AverageCurvature", "Average Desired Curvature", "Use Pfeiferj's distance-based curvature adjustment for improved curve handling.", ""},
|
||||||
|
{"NNFF", "NNFF - Neural Network Feedforward", "Use Twilsonco's Neural Network Feedforward for enhanced precision in lateral control.", ""},
|
||||||
|
{"SteerRatio", steerRatioStock != 0 ? QString("Steer Ratio (Default: %1)").arg(steerRatioStock, 0, 'f', 2) : "Steer Ratio", "Set a custom steer ratio for your vehicle controls.", ""},
|
||||||
|
|
||||||
|
{"LongitudinalTune", "Longitudinal Tuning", "Modify openpilot's acceleration and braking behavior.", "../frogpilot/assets/toggle_icons/icon_longitudinal_tune.png"},
|
||||||
|
{"AccelerationProfile", "Acceleration Profile", "Change the acceleration rate to be either sporty or eco-friendly.", ""},
|
||||||
|
{"AggressiveAcceleration", "Aggressive Acceleration With Lead", "Increase acceleration aggressiveness when following a lead vehicle from a stop.", ""},
|
||||||
|
{"StoppingDistance", "Increased Stopping Distance", "Increase the stopping distance for a more comfortable stop.", ""},
|
||||||
|
{"SmoothBraking", "Smoother Braking Behind Lead", "Smoothen out the braking behavior when approaching slower vehicles.", ""},
|
||||||
|
|
||||||
|
{"Model", "Model Selector", "Choose your preferred openpilot model.", "../assets/offroad/icon_calibration.png"},
|
||||||
|
|
||||||
|
{"MTSCEnabled", "Map Turn Speed Control", "Slow down for anticipated curves detected by your downloaded maps.", "../frogpilot/assets/toggle_icons/icon_speed_map.png"},
|
||||||
|
|
||||||
|
{"NudgelessLaneChange", "Nudgeless Lane Change", "Enable lane changes without manual steering input.", "../frogpilot/assets/toggle_icons/icon_lane.png"},
|
||||||
|
{"LaneChangeTime", "Lane Change Timer", "Specify a delay before executing a nudgeless lane change.", ""},
|
||||||
|
{"LaneDetection", "Lane Detection", "Block nudgeless lane changes when a lane isn't detected.", ""},
|
||||||
|
{"OneLaneChange", "One Lane Change Per Signal", "Limit to one nudgeless lane change per turn signal activation.", ""},
|
||||||
|
|
||||||
|
{"QOLControls", "Quality of Life", "Miscellaneous quality of life changes to improve your overall openpilot experience.", "../frogpilot/assets/toggle_icons/quality_of_life.png"},
|
||||||
|
{"DisableOnroadUploads", "Disable Onroad Uploads", "Prevent large data uploads when onroad.", ""},
|
||||||
|
{"HigherBitrate", "Higher Bitrate Recording", "Increases the quality of the footage uploaded to comma connect.", ""},
|
||||||
|
{"PauseLateralOnSignal", "Pause Lateral On Turn Signal Below", "Temporarily disable lateral control during turn signal use below the set speed.", ""},
|
||||||
|
{"ReverseCruise", "Reverse Cruise Increase", "Reverses the 'long press' functionality when increasing the max set speed. Useful to increase the max speed quickly.", ""},
|
||||||
|
{"SetSpeedOffset", "Set Speed Offset", "Set an offset for your desired set speed.", ""},
|
||||||
|
|
||||||
|
{"SpeedLimitController", "Speed Limit Controller", "Automatically adjust vehicle speed to match speed limits using 'Open Street Map's, 'Navigate On openpilot', or your car's dashboard (TSS2 Toyotas only).", "../assets/offroad/icon_speed_limit.png"},
|
||||||
|
{"Offset1", "Speed Limit Offset (0-34 mph)", "Speed limit offset for speed limits between 0-34 mph.", ""},
|
||||||
|
{"Offset2", "Speed Limit Offset (35-54 mph)", "Speed limit offset for speed limits between 35-54 mph.", ""},
|
||||||
|
{"Offset3", "Speed Limit Offset (55-64 mph)", "Speed limit offset for speed limits between 55-64 mph.", ""},
|
||||||
|
{"Offset4", "Speed Limit Offset (65-99 mph)", "Speed limit offset for speed limits between 65-99 mph.", ""},
|
||||||
|
{"SLCFallback", "Fallback Method", "Choose your fallback method for when there are no speed limits currently being obtained from Navigation, OSM, and the car's dashboard.", ""},
|
||||||
|
{"SLCOverride", "Override Method", "Choose your preferred method to override the current speed limit.", ""},
|
||||||
|
{"SLCPriority", "Priority Order", "Determine the priority order for what speed limits to use.", ""},
|
||||||
|
|
||||||
|
{"TurnDesires", "Use Turn Desires", "Use turn desires for enhanced precision in turns below the minimum lane change speed.", "../assets/navigation/direction_continue_right.png"},
|
||||||
|
|
||||||
|
{"VisionTurnControl", "Vision Turn Speed Controller", "Slow down for detected road curvature for smoother curve handling.", "../frogpilot/assets/toggle_icons/icon_vtc.png"},
|
||||||
|
{"CurveSensitivity", "Curve Detection Sensitivity", "Set curve detection sensitivity. Higher values prompt earlier responses, lower values lead to smoother but later reactions.", ""},
|
||||||
|
{"TurnAggressiveness", "Turn Speed Aggressiveness", "Set turn speed aggressiveness. Higher values result in faster turns, lower values yield gentler turns.", ""},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &[param, title, desc, icon] : controlToggles) {
|
||||||
|
ParamControl *toggle;
|
||||||
|
|
||||||
|
if (param == "AdjustablePersonalities") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 3, {{0, "None"}, {1, "Steering Wheel"}, {2, "Onroad UI Button"}, {3, "Wheel + Button"}}, this, true);
|
||||||
|
|
||||||
|
} else if (param == "AlwaysOnLateral") {
|
||||||
|
std::vector<QString> aolToggles{"AlwaysOnLateralMain"};
|
||||||
|
std::vector<QString> aolToggleNames{tr("Enable On Cruise Main")};
|
||||||
|
toggle = new FrogPilotParamToggleControl(param, title, desc, icon, aolToggles, aolToggleNames);
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamToggleControl*>(toggle), &FrogPilotParamToggleControl::buttonClicked, [this](const bool checked) {
|
||||||
|
if (checked) {
|
||||||
|
FrogPilotConfirmationDialog::toggleAlert("WARNING: This is very experimental and isn't guaranteed to work. If you run into any issues, please report it in the FrogPilot Discord!",
|
||||||
|
"I understand the risks.", this);
|
||||||
|
}
|
||||||
|
if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) {
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (param == "ConditionalExperimental") {
|
||||||
|
FrogPilotParamManageControl *conditionalExperimentalToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(conditionalExperimentalToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
conditionalSpeedsImperial->setVisible(!isMetric);
|
||||||
|
conditionalSpeedsMetric->setVisible(isMetric);
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(conditionalExperimentalKeys.find(key.c_str()) != conditionalExperimentalKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = conditionalExperimentalToggle;
|
||||||
|
} else if (param == "CECurves") {
|
||||||
|
FrogPilotParamValueControl *CESpeedImperial = new FrogPilotParamValueControl("CESpeed", "Below", "Switch to 'Experimental Mode' below this speed in absence of a lead vehicle.", "", 0, 99,
|
||||||
|
std::map<int, QString>(), this, false, " mph");
|
||||||
|
FrogPilotParamValueControl *CESpeedLeadImperial = new FrogPilotParamValueControl("CESpeedLead", " w/Lead", "Switch to 'Experimental Mode' below this speed when following a lead vehicle.", "", 0, 99,
|
||||||
|
std::map<int, QString>(), this, false, " mph");
|
||||||
|
conditionalSpeedsImperial = new FrogPilotDualParamControl(CESpeedImperial, CESpeedLeadImperial, this);
|
||||||
|
addItem(conditionalSpeedsImperial);
|
||||||
|
|
||||||
|
FrogPilotParamValueControl *CESpeedMetric = new FrogPilotParamValueControl("CESpeed", "Below", "Switch to 'Experimental Mode' below this speed in absence of a lead vehicle.", "", 0, 150,
|
||||||
|
std::map<int, QString>(), this, false, " kph");
|
||||||
|
FrogPilotParamValueControl *CESpeedLeadMetric = new FrogPilotParamValueControl("CESpeedLead", " w/Lead", "Switch to 'Experimental Mode' below this speed when following a lead vehicle.", "", 0, 150,
|
||||||
|
std::map<int, QString>(), this, false, " kph");
|
||||||
|
conditionalSpeedsMetric = new FrogPilotDualParamControl(CESpeedMetric, CESpeedLeadMetric, this);
|
||||||
|
addItem(conditionalSpeedsMetric);
|
||||||
|
|
||||||
|
std::vector<QString> curveToggles{"CECurvesLead"};
|
||||||
|
std::vector<QString> curveToggleNames{tr("With Lead")};
|
||||||
|
toggle = new FrogPilotParamToggleControl(param, title, desc, icon, curveToggles, curveToggleNames);
|
||||||
|
} else if (param == "CEStopLights") {
|
||||||
|
std::vector<QString> stopLightToggles{"CEStopLightsLead"};
|
||||||
|
std::vector<QString> stopLightToggleNames{tr("With Lead")};
|
||||||
|
toggle = new FrogPilotParamToggleControl(param, title, desc, icon, stopLightToggles, stopLightToggleNames);
|
||||||
|
|
||||||
|
} else if (param == "CustomPersonalities") {
|
||||||
|
FrogPilotParamManageControl *customPersonalitiesToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(customPersonalitiesToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(false);
|
||||||
|
}
|
||||||
|
aggressiveProfile->setVisible(true);
|
||||||
|
standardProfile->setVisible(true);
|
||||||
|
relaxedProfile->setVisible(true);
|
||||||
|
});
|
||||||
|
toggle = customPersonalitiesToggle;
|
||||||
|
|
||||||
|
FrogPilotParamValueControl *aggressiveFollow = new FrogPilotParamValueControl("AggressiveFollow", "Follow",
|
||||||
|
"Set the 'Aggressive' personality' following distance. Represents seconds to follow behind the lead vehicle.\n\nStock: 1.25 seconds.", "../frogpilot/assets/other_images/aggressive.png",
|
||||||
|
10, 50, std::map<int, QString>(), this, false, " sec", 10);
|
||||||
|
FrogPilotParamValueControl *aggressiveJerk = new FrogPilotParamValueControl("AggressiveJerk", " Jerk",
|
||||||
|
"Configure brake/gas pedal responsiveness for the 'Aggressive' personality. Higher values yield a more 'relaxed' response.\n\nStock: 0.5.", "", 1, 50,
|
||||||
|
std::map<int, QString>(), this, false, "", 10);
|
||||||
|
aggressiveProfile = new FrogPilotDualParamControl(aggressiveFollow, aggressiveJerk, this, true);
|
||||||
|
addItem(aggressiveProfile);
|
||||||
|
|
||||||
|
FrogPilotParamValueControl *standardFollow = new FrogPilotParamValueControl("StandardFollow", "Follow",
|
||||||
|
"Set the 'Standard' personality following distance. Represents seconds to follow behind the lead vehicle.\n\nStock: 1.45 seconds.", "../frogpilot/assets/other_images/standard.png",
|
||||||
|
10, 50, std::map<int, QString>(), this, false, " sec", 10);
|
||||||
|
FrogPilotParamValueControl *standardJerk = new FrogPilotParamValueControl("StandardJerk", " Jerk",
|
||||||
|
"Adjust brake/gas pedal responsiveness for the 'Standard' personality. Higher values yield a more 'relaxed' response.\n\nStock: 1.0.", "", 1, 50,
|
||||||
|
std::map<int, QString>(), this, false, "", 10);
|
||||||
|
standardProfile = new FrogPilotDualParamControl(standardFollow, standardJerk, this, true);
|
||||||
|
addItem(standardProfile);
|
||||||
|
|
||||||
|
FrogPilotParamValueControl *relaxedFollow = new FrogPilotParamValueControl("RelaxedFollow", "Follow",
|
||||||
|
"Set the 'Relaxed' personality following distance. Represents seconds to follow behind the lead vehicle.\n\nStock: 1.75 seconds.", "../frogpilot/assets/other_images/relaxed.png",
|
||||||
|
10, 50, std::map<int, QString>(), this, false, " sec", 10);
|
||||||
|
FrogPilotParamValueControl *relaxedJerk = new FrogPilotParamValueControl("RelaxedJerk", " Jerk",
|
||||||
|
"Set brake/gas pedal responsiveness for the 'Relaxed' personality. Higher values yield a more 'relaxed' response.\n\nStock: 1.0.", "", 1, 50,
|
||||||
|
std::map<int, QString>(), this, false, "", 10);
|
||||||
|
relaxedProfile = new FrogPilotDualParamControl(relaxedFollow, relaxedJerk, this, true);
|
||||||
|
addItem(relaxedProfile);
|
||||||
|
|
||||||
|
} else if (param == "DeviceShutdown") {
|
||||||
|
std::map<int, QString> shutdownLabels;
|
||||||
|
for (int i = 0; i <= 33; ++i) {
|
||||||
|
shutdownLabels[i] = i == 0 ? "Instant" : i <= 3 ? QString::number(i * 15) + " mins" : QString::number(i - 3) + (i == 4 ? " hour" : " hours");
|
||||||
|
}
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 33, shutdownLabels, this, false);
|
||||||
|
|
||||||
|
} else if (param == "ExperimentalModeActivation") {
|
||||||
|
std::vector<QString> experimentalModeToggles{"ExperimentalModeViaLKAS", "ExperimentalModeViaScreen"};
|
||||||
|
std::vector<QString> experimentalModeNames{tr("LKAS Button"), tr("Screen")};
|
||||||
|
toggle = new FrogPilotParamToggleControl(param, title, desc, icon, experimentalModeToggles, experimentalModeNames);
|
||||||
|
|
||||||
|
} else if (param == "FireTheBabysitter") {
|
||||||
|
FrogPilotParamManageControl *fireTheBabysitterToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(fireTheBabysitterToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(fireTheBabysitterKeys.find(key.c_str()) != fireTheBabysitterKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = fireTheBabysitterToggle;
|
||||||
|
|
||||||
|
} else if (param == "LateralTune") {
|
||||||
|
FrogPilotParamManageControl *lateralTuneToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(lateralTuneToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(lateralTuneKeys.find(key.c_str()) != lateralTuneKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = lateralTuneToggle;
|
||||||
|
} else if (param == "SteerRatio") {
|
||||||
|
toggle = new FrogPilotParamValueControlFloat(param, title, desc, icon, steerRatioStock * 0.75, steerRatioStock * 1.25, std::map<int, QString>(), this, false, "", 10.0);
|
||||||
|
|
||||||
|
} else if (param == "LongitudinalTune") {
|
||||||
|
FrogPilotParamManageControl *longitudinalTuneToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(longitudinalTuneToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(longitudinalTuneKeys.find(key.c_str()) != longitudinalTuneKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = longitudinalTuneToggle;
|
||||||
|
} else if (param == "AccelerationProfile") {
|
||||||
|
std::vector<QString> profileOptions{tr("Standard"), tr("Eco"), tr("Sport"), tr("Sport+")};
|
||||||
|
FrogPilotButtonParamControl *profileSelection = new FrogPilotButtonParamControl(param, title, desc, icon, profileOptions);
|
||||||
|
toggle = profileSelection;
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotButtonParamControl*>(toggle), &FrogPilotButtonParamControl::buttonClicked, [this](int id) {
|
||||||
|
if (id == 3) {
|
||||||
|
FrogPilotConfirmationDialog::toggleAlert("WARNING: This maxes out openpilot's acceleration from 2.0 m/s to 4.0 m/s and may cause oscillations when accelerating!",
|
||||||
|
"I understand the risks.", this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (param == "StoppingDistance") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 10, std::map<int, QString>(), this, false, " feet");
|
||||||
|
|
||||||
|
} else if (param == "Model") {
|
||||||
|
modelSelectorButton = new FrogPilotButtonIconControl(title, tr("SELECT"), desc, icon);
|
||||||
|
QStringList models = {"New Delhi (Default)", "Blue Diamond V1", "Blue Diamond V2", "Farmville", "New Lemon Pie"};
|
||||||
|
QObject::connect(modelSelectorButton, &FrogPilotButtonIconControl::clicked, this, [this, models]() {
|
||||||
|
int currentModel = params.getInt("Model");
|
||||||
|
QString currentModelLabel = models[currentModel];
|
||||||
|
|
||||||
|
QString selection = MultiOptionDialog::getSelection(tr("Select a driving model"), models, currentModelLabel, this);
|
||||||
|
if (!selection.isEmpty()) {
|
||||||
|
int selectedModel = models.indexOf(selection);
|
||||||
|
params.putInt("Model", selectedModel);
|
||||||
|
params.remove("CalibrationParams");
|
||||||
|
params.remove("LiveTorqueParameters");
|
||||||
|
modelSelectorButton->setValue(selection);
|
||||||
|
if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) {
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
modelSelectorButton->setValue(models[params.getInt("Model")]);
|
||||||
|
addItem(modelSelectorButton);
|
||||||
|
|
||||||
|
} else if (param == "NudgelessLaneChange") {
|
||||||
|
FrogPilotParamManageControl *laneChangeToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(laneChangeToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(laneChangeKeys.find(key.c_str()) != laneChangeKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = laneChangeToggle;
|
||||||
|
} else if (param == "LaneChangeTime") {
|
||||||
|
std::map<int, QString> laneChangeTimeLabels;
|
||||||
|
for (int i = 0; i <= 10; ++i) {
|
||||||
|
laneChangeTimeLabels[i] = i == 0 ? "Instant" : QString::number(i / 2.0) + " seconds";
|
||||||
|
}
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 10, laneChangeTimeLabels, this, false);
|
||||||
|
|
||||||
|
} else if (param == "QOLControls") {
|
||||||
|
FrogPilotParamManageControl *qolToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(qolToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(qolKeys.find(key.c_str()) != qolKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = qolToggle;
|
||||||
|
} else if (param == "PauseLateralOnSignal") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, std::map<int, QString>(), this, false, " mph");
|
||||||
|
} else if (param == "SetSpeedOffset") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, std::map<int, QString>(), this, false, " mph");
|
||||||
|
|
||||||
|
} else if (param == "SpeedLimitController") {
|
||||||
|
FrogPilotParamManageControl *speedLimitControllerToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(speedLimitControllerToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(speedLimitControllerKeys.find(key.c_str()) != speedLimitControllerKeys.end());
|
||||||
|
}
|
||||||
|
slscPriorityButton->setVisible(true);
|
||||||
|
});
|
||||||
|
toggle = speedLimitControllerToggle;
|
||||||
|
} else if (param == "Offset1" || param == "Offset2" || param == "Offset3" || param == "Offset4") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, std::map<int, QString>(), this, false, " mph");
|
||||||
|
} else if (param == "SLCFallback") {
|
||||||
|
std::vector<QString> fallbackOptions{tr("None"), tr("Experimental Mode"), tr("Previous Limit")};
|
||||||
|
FrogPilotButtonParamControl *fallbackSelection = new FrogPilotButtonParamControl(param, title, desc, icon, fallbackOptions);
|
||||||
|
toggle = fallbackSelection;
|
||||||
|
} else if (param == "SLCOverride") {
|
||||||
|
std::vector<QString> overrideOptions{tr("None"), tr("Manual Set Speed"), tr("Max Set Speed")};
|
||||||
|
FrogPilotButtonParamControl *overrideSelection = new FrogPilotButtonParamControl(param, title, desc, icon, overrideOptions);
|
||||||
|
toggle = overrideSelection;
|
||||||
|
} else if (param == "SLCPriority") {
|
||||||
|
const QStringList priorities {
|
||||||
|
"Navigation, Dashboard, Offline Maps",
|
||||||
|
"Navigation, Offline Maps, Dashboard",
|
||||||
|
"Navigation, Offline Maps",
|
||||||
|
"Navigation, Dashboard",
|
||||||
|
"Navigation",
|
||||||
|
"Offline Maps, Dashboard, Navigation",
|
||||||
|
"Offline Maps, Navigation, Dashboard",
|
||||||
|
"Offline Maps, Navigation",
|
||||||
|
"Offline Maps, Dashboard",
|
||||||
|
"Offline Maps",
|
||||||
|
"Dashboard, Navigation, Offline Maps",
|
||||||
|
"Dashboard, Offline Maps, Navigation",
|
||||||
|
"Dashboard, Offline Maps",
|
||||||
|
"Dashboard, Navigation",
|
||||||
|
"Dashboard",
|
||||||
|
"Highest",
|
||||||
|
"Lowest",
|
||||||
|
"",
|
||||||
|
};
|
||||||
|
|
||||||
|
slscPriorityButton = new ButtonControl(title, tr("SELECT"), desc);
|
||||||
|
QObject::connect(slscPriorityButton, &ButtonControl::clicked, this, [this, priorities]() {
|
||||||
|
QStringList availablePriorities = {"Dashboard", "Navigation", "Offline Maps", "Highest", "Lowest", "None"};
|
||||||
|
QStringList selectedPriorities;
|
||||||
|
int priorityValue = -1;
|
||||||
|
|
||||||
|
QStringList priorityPrompts = {tr("Select your primary priority"), tr("Select your secondary priority"), tr("Select your tertiary priority")};
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
QString selection = MultiOptionDialog::getSelection(priorityPrompts[i], availablePriorities, "", this);
|
||||||
|
if (selection.isEmpty()) break;
|
||||||
|
|
||||||
|
if (selection == "None") {
|
||||||
|
priorityValue = 17;
|
||||||
|
break;
|
||||||
|
} else if (selection == "Highest") {
|
||||||
|
priorityValue = 15;
|
||||||
|
break;
|
||||||
|
} else if (selection == "Lowest") {
|
||||||
|
priorityValue = 16;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
selectedPriorities.append(selection);
|
||||||
|
availablePriorities.removeAll(selection);
|
||||||
|
availablePriorities.removeAll("Highest");
|
||||||
|
availablePriorities.removeAll("Lowest");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priorityValue == -1 && !selectedPriorities.isEmpty()) {
|
||||||
|
QString priorityString = selectedPriorities.join(", ");
|
||||||
|
priorityValue = priorities.indexOf(priorityString);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priorityValue != -1) {
|
||||||
|
slscPriorityButton->setValue(priorities[priorityValue]);
|
||||||
|
params.putInt("SLCPriority", priorityValue);
|
||||||
|
updateToggles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
slscPriorityButton->setValue(priorities[params.getInt("SLCPriority")]);
|
||||||
|
addItem(slscPriorityButton);
|
||||||
|
|
||||||
|
} else if (param == "VisionTurnControl") {
|
||||||
|
FrogPilotParamManageControl *visionTurnControlToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(visionTurnControlToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(visionTurnControlKeys.find(key.c_str()) != visionTurnControlKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = visionTurnControlToggle;
|
||||||
|
} else if (param == "CurveSensitivity" || param == "TurnAggressiveness") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 1, 200, std::map<int, QString>(), this, false, "%");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
toggle = new ParamControl(param, title, desc, icon, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
addItem(toggle);
|
||||||
|
toggles[param.toStdString()] = toggle;
|
||||||
|
|
||||||
|
QObject::connect(toggle, &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamValueControl*>(toggle), &FrogPilotParamValueControl::buttonPressed, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(toggle, &AbstractControl::showDescriptionEvent, [this]() {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamManageControl*>(toggle), &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string> rebootKeys = {"AlwaysOnLateral", "FireTheBabysitter", "HigherBitrate", "MuteDM", "NNFF"};
|
||||||
|
for (const std::string &key : rebootKeys) {
|
||||||
|
QObject::connect(toggles[key], &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) {
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
conditionalExperimentalKeys = {"CECurves", "CECurvesLead", "CESlowerLead", "CENavigation", "CEStopLights", "CESignal"};
|
||||||
|
fireTheBabysitterKeys = {"NoLogging", "MuteDM", "MuteDoor", "MuteOverheated", "MuteSeatbelt", "OfflineMode"};
|
||||||
|
laneChangeKeys = {"LaneChangeTime", "LaneDetection", "OneLaneChange"};
|
||||||
|
lateralTuneKeys = {"AverageCurvature", "NNFF", "SteerRatio"};
|
||||||
|
longitudinalTuneKeys = {"AccelerationProfile", "AggressiveAcceleration", "SmoothBraking", "StoppingDistance"};
|
||||||
|
mtscKeys = {"MTSCAggressiveness"};
|
||||||
|
qolKeys = {"DisableOnroadUploads", "HigherBitrate", "PauseLateralOnSignal", "ReverseCruise", "SetSpeedOffset"};
|
||||||
|
speedLimitControllerKeys = {"Offset1", "Offset2", "Offset3", "Offset4", "SLCFallback", "SLCOverride", "SLCPriority"};
|
||||||
|
visionTurnControlKeys = {"CurveSensitivity", "TurnAggressiveness"};
|
||||||
|
|
||||||
|
QObject::connect(uiState(), &UIState::offroadTransition, this, [this](bool offroad) {
|
||||||
|
if (!offroad) {
|
||||||
|
updateCarToggles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(device(), &Device::interactiveTimeout, this, &FrogPilotControlsPanel::hideSubToggles);
|
||||||
|
QObject::connect(parent, &OscarSettingsWindow::closeParentToggle, this, &FrogPilotControlsPanel::hideSubToggles);
|
||||||
|
// QObject::connect(parent, &OscarSettingsWindow::updateMetric, this, &FrogPilotControlsPanel::updateMetric);
|
||||||
|
|
||||||
|
hideSubToggles();
|
||||||
|
// updateMetric();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotControlsPanel::updateCarToggles() {
|
||||||
|
FrogPilotParamValueControlFloat *steerRatioToggle = static_cast<FrogPilotParamValueControlFloat*>(toggles["SteerRatio"]);
|
||||||
|
steerRatioStock = params.getFloat("SteerRatioStock");
|
||||||
|
steerRatioToggle->setTitle(steerRatioStock != 0 ? QString("Steer Ratio (Default: %1)").arg(steerRatioStock, 0, 'f', 2) : QString("Steer Ratio"));
|
||||||
|
steerRatioToggle->updateControl(steerRatioStock * 0.75, steerRatioStock * 1.25, "", 10.0);
|
||||||
|
steerRatioToggle->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotControlsPanel::updateToggles() {
|
||||||
|
std::thread([this]() {
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", true);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", false);
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotControlsPanel::updateMetric() {
|
||||||
|
bool previousIsMetric = isMetric;
|
||||||
|
isMetric = params.getBool("IsMetric");
|
||||||
|
|
||||||
|
if (isMetric != previousIsMetric) {
|
||||||
|
double distanceConversion = isMetric ? FOOT_TO_METER : METER_TO_FOOT;
|
||||||
|
double speedConversion = isMetric ? MILE_TO_KM : KM_TO_MILE;
|
||||||
|
params.putInt("CESpeed", std::nearbyint(params.getInt("CESpeed") * speedConversion));
|
||||||
|
params.putInt("CESpeedLead", std::nearbyint(params.getInt("CESpeedLead") * speedConversion));
|
||||||
|
params.putInt("Offset1", std::nearbyint(params.getInt("Offset1") * speedConversion));
|
||||||
|
params.putInt("Offset2", std::nearbyint(params.getInt("Offset2") * speedConversion));
|
||||||
|
params.putInt("Offset3", std::nearbyint(params.getInt("Offset3") * speedConversion));
|
||||||
|
params.putInt("Offset4", std::nearbyint(params.getInt("Offset4") * speedConversion));
|
||||||
|
params.putInt("PauseLateralOnSignal", std::nearbyint(params.getInt("PauseLateralOnSignal") * speedConversion));
|
||||||
|
params.putInt("SetSpeedOffset", std::nearbyint(params.getInt("SetSpeedOffset") * speedConversion));
|
||||||
|
params.putInt("StoppingDistance", std::nearbyint(params.getInt("StoppingDistance") * distanceConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
FrogPilotParamValueControl *offset1Toggle = static_cast<FrogPilotParamValueControl*>(toggles["Offset1"]);
|
||||||
|
FrogPilotParamValueControl *offset2Toggle = static_cast<FrogPilotParamValueControl*>(toggles["Offset2"]);
|
||||||
|
FrogPilotParamValueControl *offset3Toggle = static_cast<FrogPilotParamValueControl*>(toggles["Offset3"]);
|
||||||
|
FrogPilotParamValueControl *offset4Toggle = static_cast<FrogPilotParamValueControl*>(toggles["Offset4"]);
|
||||||
|
FrogPilotParamValueControl *pauseLateralToggle = static_cast<FrogPilotParamValueControl*>(toggles["PauseLateralOnSignal"]);
|
||||||
|
FrogPilotParamValueControl *setSpeedOffsetToggle = static_cast<FrogPilotParamValueControl*>(toggles["SetSpeedOffset"]);
|
||||||
|
FrogPilotParamValueControl *stoppingDistanceToggle = static_cast<FrogPilotParamValueControl*>(toggles["StoppingDistance"]);
|
||||||
|
|
||||||
|
if (isMetric) {
|
||||||
|
offset1Toggle->setTitle("Speed Limit Offset (0-34 kph)");
|
||||||
|
offset2Toggle->setTitle("Speed Limit Offset (35-54 kph)");
|
||||||
|
offset3Toggle->setTitle("Speed Limit Offset (55-64 kph)");
|
||||||
|
offset4Toggle->setTitle("Speed Limit Offset (65-99 kph)");
|
||||||
|
|
||||||
|
offset1Toggle->setDescription("Set speed limit offset for limits between 0-34 kph.");
|
||||||
|
offset2Toggle->setDescription("Set speed limit offset for limits between 35-54 kph.");
|
||||||
|
offset3Toggle->setDescription("Set speed limit offset for limits between 55-64 kph.");
|
||||||
|
offset4Toggle->setDescription("Set speed limit offset for limits between 65-99 kph.");
|
||||||
|
|
||||||
|
offset1Toggle->updateControl(0, 150, " kph");
|
||||||
|
offset2Toggle->updateControl(0, 150, " kph");
|
||||||
|
offset3Toggle->updateControl(0, 150, " kph");
|
||||||
|
offset4Toggle->updateControl(0, 150, " kph");
|
||||||
|
pauseLateralToggle->updateControl(0, 150, " kph");
|
||||||
|
setSpeedOffsetToggle->updateControl(0, 150, " kph");
|
||||||
|
stoppingDistanceToggle->updateControl(0, 5, " meters");
|
||||||
|
} else {
|
||||||
|
offset1Toggle->setTitle("Speed Limit Offset (0-34 mph)");
|
||||||
|
offset2Toggle->setTitle("Speed Limit Offset (35-54 mph)");
|
||||||
|
offset3Toggle->setTitle("Speed Limit Offset (55-64 mph)");
|
||||||
|
offset4Toggle->setTitle("Speed Limit Offset (65-99 mph)");
|
||||||
|
|
||||||
|
offset1Toggle->setDescription("Set speed limit offset for limits between 0-34 mph.");
|
||||||
|
offset2Toggle->setDescription("Set speed limit offset for limits between 35-54 mph.");
|
||||||
|
offset3Toggle->setDescription("Set speed limit offset for limits between 55-64 mph.");
|
||||||
|
offset4Toggle->setDescription("Set speed limit offset for limits between 65-99 mph.");
|
||||||
|
|
||||||
|
offset1Toggle->updateControl(0, 99, " mph");
|
||||||
|
offset2Toggle->updateControl(0, 99, " mph");
|
||||||
|
offset3Toggle->updateControl(0, 99, " mph");
|
||||||
|
offset4Toggle->updateControl(0, 99, " mph");
|
||||||
|
pauseLateralToggle->updateControl(0, 99, " mph");
|
||||||
|
setSpeedOffsetToggle->updateControl(0, 99, " mph");
|
||||||
|
stoppingDistanceToggle->updateControl(0, 10, " feet");
|
||||||
|
}
|
||||||
|
|
||||||
|
offset1Toggle->refresh();
|
||||||
|
offset2Toggle->refresh();
|
||||||
|
offset3Toggle->refresh();
|
||||||
|
offset4Toggle->refresh();
|
||||||
|
pauseLateralToggle->refresh();
|
||||||
|
setSpeedOffsetToggle->refresh();
|
||||||
|
stoppingDistanceToggle->refresh();
|
||||||
|
|
||||||
|
previousIsMetric = isMetric;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotControlsPanel::parentToggleClicked() {
|
||||||
|
aggressiveProfile->setVisible(false);
|
||||||
|
conditionalSpeedsImperial->setVisible(false);
|
||||||
|
conditionalSpeedsMetric->setVisible(false);
|
||||||
|
modelSelectorButton->setVisible(false);
|
||||||
|
slscPriorityButton->setVisible(false);
|
||||||
|
standardProfile->setVisible(false);
|
||||||
|
relaxedProfile->setVisible(false);
|
||||||
|
|
||||||
|
this->openParentToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotControlsPanel::hideSubToggles() {
|
||||||
|
aggressiveProfile->setVisible(false);
|
||||||
|
conditionalSpeedsImperial->setVisible(false);
|
||||||
|
conditionalSpeedsMetric->setVisible(false);
|
||||||
|
modelSelectorButton->setVisible(true);
|
||||||
|
slscPriorityButton->setVisible(false);
|
||||||
|
standardProfile->setVisible(false);
|
||||||
|
relaxedProfile->setVisible(false);
|
||||||
|
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
bool subToggles = conditionalExperimentalKeys.find(key.c_str()) != conditionalExperimentalKeys.end() ||
|
||||||
|
fireTheBabysitterKeys.find(key.c_str()) != fireTheBabysitterKeys.end() ||
|
||||||
|
laneChangeKeys.find(key.c_str()) != laneChangeKeys.end() ||
|
||||||
|
lateralTuneKeys.find(key.c_str()) != lateralTuneKeys.end() ||
|
||||||
|
longitudinalTuneKeys.find(key.c_str()) != longitudinalTuneKeys.end() ||
|
||||||
|
mtscKeys.find(key.c_str()) != mtscKeys.end() ||
|
||||||
|
qolKeys.find(key.c_str()) != qolKeys.end() ||
|
||||||
|
speedLimitControllerKeys.find(key.c_str()) != speedLimitControllerKeys.end() ||
|
||||||
|
visionTurnControlKeys.find(key.c_str()) != visionTurnControlKeys.end();
|
||||||
|
toggle->setVisible(!subToggles);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->closeParentToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotControlsPanel::hideEvent(QHideEvent *event) {
|
||||||
|
hideSubToggles();
|
||||||
|
}
|
||||||
559
selfdrive/clearpilot/ui/control_settings.cc.org
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
#include "selfdrive/frogpilot/ui/control_settings.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
|
FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPilotListWidget(parent) {
|
||||||
|
const std::vector<std::tuple<QString, QString, QString, QString>> controlToggles {
|
||||||
|
{"AdjustablePersonalities", "Adjustable Personalities", "Use the 'Distance' button on the steering wheel or the onroad UI to switch between openpilot's driving personalities.\n\n1 bar = Aggressive\n2 bars = Standard\n3 bars = Relaxed", "../frogpilot/assets/toggle_icons/icon_distance.png"},
|
||||||
|
{"AlwaysOnLateral", "Always on Lateral", "Maintain openpilot lateral control when the brake or gas pedals are used.\n\nDeactivation occurs only through the 'Cruise Control' button.", "../frogpilot/assets/toggle_icons/icon_always_on_lateral.png"},
|
||||||
|
|
||||||
|
{"ConditionalExperimental", "Conditional Experimental Mode", "Automatically switches to 'Experimental Mode' under predefined conditions.", "../frogpilot/assets/toggle_icons/icon_conditional.png"},
|
||||||
|
{"CECurves", "Curve Detected Ahead", "Switch to 'Experimental Mode' when a curve is detected.", ""},
|
||||||
|
{"CENavigation", "Navigation Based", "Switch to 'Experimental Mode' based on navigation data. (i.e. Intersections, stop signs, etc.)", ""},
|
||||||
|
{"CESlowerLead", "Slower Lead Detected Ahead", "Switch to 'Experimental Mode' when a slower lead vehicle is detected ahead.", ""},
|
||||||
|
// {"CEStopLights", "Stop Lights and Stop Signs", "Switch to 'Experimental Mode' when a stop light or stop sign is detected.", ""},
|
||||||
|
{"CESignal", "Turn Signal When Below Highway Speeds", "Switch to 'Experimental Mode' when using turn signals below highway speeds to help assit with turns.", ""},
|
||||||
|
{"CSLCEnabled", "Custom Stock Longitudinal Control", "Use cruise control button spamming to adjust cruise set speed based on MTSC, VTSC, and SLC", "../frogpilot/assets/toggle_icons/icon_custom.png"},
|
||||||
|
{"CustomPersonalities", "Custom Driving Personalities", "Customize the driving personality profiles to your driving style.", "../frogpilot/assets/toggle_icons/icon_custom.png"},
|
||||||
|
{"DeviceShutdown", "Device Shutdown Timer", "Configure the timer for automatic device shutdown when offroad conserving energy and preventing battery drain.", "../frogpilot/assets/toggle_icons/icon_time.png"},
|
||||||
|
{"ExperimentalModeActivation", "Experimental Mode Via", "Toggle Experimental Mode by double-clicking the 'Lane Departure'/'LKAS' button or double tapping screen.\n\nOverrides 'Conditional Experimental Mode'.", "../assets/img_experimental_white.svg"},
|
||||||
|
|
||||||
|
{"FireTheBabysitter", "Fire the Babysitter", "Deactivate some of openpilot's 'Babysitter' protocols for more user autonomy.", "../frogpilot/assets/toggle_icons/icon_babysitter.png"},
|
||||||
|
{"NoLogging", "Disable All Logging", "Turn off all data tracking to enhance privacy or reduce thermal load.\n\nWARNING: This action will prevent drive recording and data cannot be recovered!", ""},
|
||||||
|
{"MuteDoor", "Mute Door Open Alert", "Disable alerts for open doors.", ""},
|
||||||
|
{"MuteDM", "Mute Driver Monitoring", "Disable driver monitoring.", ""},
|
||||||
|
{"MuteOverheated", "Mute Overheated System Alert", "Disable alerts for the device being overheated.", ""},
|
||||||
|
{"MuteSeatbelt", "Mute Seatbelt Unlatched Alert", "Disable alerts for unlatched seatbelts.", ""},
|
||||||
|
{"OfflineMode", "Offline Mode", "Allow the device to be offline indefinitely.", ""},
|
||||||
|
|
||||||
|
{"LateralTune", "Lateral Tuning", "Modify openpilot's steering behavior.", "../frogpilot/assets/toggle_icons/icon_lateral_tune.png"},
|
||||||
|
{"AverageCurvature", "Average Desired Curvature", "Use Pfeiferj's distance-based curvature adjustment for improved curve handling.", ""},
|
||||||
|
{"NNFF", "NNFF - Neural Network Feedforward", "Use Twilsonco's Neural Network Feedforward for enhanced precision in lateral control.", ""},
|
||||||
|
{"SteerRatio", steerRatioStock != 0 ? QString("Steer Ratio (Default: %1)").arg(steerRatioStock, 0, 'f', 2) : "Steer Ratio", "Set a custom steer ratio for your vehicle controls.", ""},
|
||||||
|
|
||||||
|
{"LongitudinalTune", "Longitudinal Tuning", "Modify openpilot's acceleration and braking behavior.", "../frogpilot/assets/toggle_icons/icon_longitudinal_tune.png"},
|
||||||
|
{"AccelerationProfile", "Acceleration Profile", "Change the acceleration rate to be either sporty or eco-friendly.", ""},
|
||||||
|
{"AggressiveAcceleration", "Aggressive Acceleration With Lead", "Increase acceleration aggressiveness when following a lead vehicle from a stop.", ""},
|
||||||
|
{"StoppingDistance", "Increased Stopping Distance", "Increase the stopping distance for a more comfortable stop.", ""},
|
||||||
|
{"SmoothBraking", "Smoother Braking Behind Lead", "Smoothen out the braking behavior when approaching slower vehicles.", ""},
|
||||||
|
|
||||||
|
{"Model", "Model Selector", "Choose your preferred openpilot model.", "../assets/offroad/icon_calibration.png"},
|
||||||
|
|
||||||
|
{"MTSCEnabled", "Map Turn Speed Control", "Slow down for anticipated curves detected by your downloaded maps.", "../frogpilot/assets/toggle_icons/icon_speed_map.png"},
|
||||||
|
|
||||||
|
{"NudgelessLaneChange", "Nudgeless Lane Change", "Enable lane changes without manual steering input.", "../frogpilot/assets/toggle_icons/icon_lane.png"},
|
||||||
|
{"LaneChangeTime", "Lane Change Timer", "Specify a delay before executing a nudgeless lane change.", ""},
|
||||||
|
{"LaneDetection", "Lane Detection", "Block nudgeless lane changes when a lane isn't detected.", ""},
|
||||||
|
{"OneLaneChange", "One Lane Change Per Signal", "Limit to one nudgeless lane change per turn signal activation.", ""},
|
||||||
|
|
||||||
|
{"QOLControls", "Quality of Life", "Miscellaneous quality of life changes to improve your overall openpilot experience.", "../frogpilot/assets/toggle_icons/quality_of_life.png"},
|
||||||
|
{"DisableOnroadUploads", "Disable Onroad Uploads", "Prevent large data uploads when onroad.", ""},
|
||||||
|
{"HigherBitrate", "Higher Bitrate Recording", "Increases the quality of the footage uploaded to comma connect.", ""},
|
||||||
|
{"PauseLateralOnSignal", "Pause Lateral On Turn Signal Below", "Temporarily disable lateral control during turn signal use below the set speed.", ""},
|
||||||
|
{"ReverseCruise", "Reverse Cruise Increase", "Reverses the 'long press' functionality when increasing the max set speed. Useful to increase the max speed quickly.", ""},
|
||||||
|
{"SetSpeedOffset", "Set Speed Offset", "Set an offset for your desired set speed.", ""},
|
||||||
|
|
||||||
|
{"SpeedLimitController", "Speed Limit Controller", "Automatically adjust vehicle speed to match speed limits using 'Open Street Map's, 'Navigate On openpilot', or your car's dashboard (TSS2 Toyotas only).", "../assets/offroad/icon_speed_limit.png"},
|
||||||
|
{"Offset1", "Speed Limit Offset (0-34 mph)", "Speed limit offset for speed limits between 0-34 mph.", ""},
|
||||||
|
{"Offset2", "Speed Limit Offset (35-54 mph)", "Speed limit offset for speed limits between 35-54 mph.", ""},
|
||||||
|
{"Offset3", "Speed Limit Offset (55-64 mph)", "Speed limit offset for speed limits between 55-64 mph.", ""},
|
||||||
|
{"Offset4", "Speed Limit Offset (65-99 mph)", "Speed limit offset for speed limits between 65-99 mph.", ""},
|
||||||
|
{"SLCFallback", "Fallback Method", "Choose your fallback method for when there are no speed limits currently being obtained from Navigation, OSM, and the car's dashboard.", ""},
|
||||||
|
{"SLCOverride", "Override Method", "Choose your preferred method to override the current speed limit.", ""},
|
||||||
|
{"SLCPriority", "Priority Order", "Determine the priority order for what speed limits to use.", ""},
|
||||||
|
|
||||||
|
{"TurnDesires", "Use Turn Desires", "Use turn desires for enhanced precision in turns below the minimum lane change speed.", "../assets/navigation/direction_continue_right.png"},
|
||||||
|
|
||||||
|
{"VisionTurnControl", "Vision Turn Speed Controller", "Slow down for detected road curvature for smoother curve handling.", "../frogpilot/assets/toggle_icons/icon_vtc.png"},
|
||||||
|
{"CurveSensitivity", "Curve Detection Sensitivity", "Set curve detection sensitivity. Higher values prompt earlier responses, lower values lead to smoother but later reactions.", ""},
|
||||||
|
{"TurnAggressiveness", "Turn Speed Aggressiveness", "Set turn speed aggressiveness. Higher values result in faster turns, lower values yield gentler turns.", ""},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &[param, title, desc, icon] : controlToggles) {
|
||||||
|
ParamControl *toggle;
|
||||||
|
|
||||||
|
if (param == "AdjustablePersonalities") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 3, {{0, "None"}, {1, "Steering Wheel"}, {2, "Onroad UI Button"}, {3, "Wheel + Button"}}, this, true);
|
||||||
|
|
||||||
|
} else if (param == "AlwaysOnLateral") {
|
||||||
|
std::vector<QString> aolToggles{"AlwaysOnLateralMain"};
|
||||||
|
std::vector<QString> aolToggleNames{tr("Enable On Cruise Main")};
|
||||||
|
toggle = new FrogPilotParamToggleControl(param, title, desc, icon, aolToggles, aolToggleNames);
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamToggleControl*>(toggle), &FrogPilotParamToggleControl::buttonClicked, [this](const bool checked) {
|
||||||
|
if (checked) {
|
||||||
|
FrogPilotConfirmationDialog::toggleAlert("WARNING: This is very experimental and isn't guaranteed to work. If you run into any issues, please report it in the FrogPilot Discord!",
|
||||||
|
"I understand the risks.", this);
|
||||||
|
}
|
||||||
|
if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) {
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (param == "ConditionalExperimental") {
|
||||||
|
FrogPilotParamManageControl *conditionalExperimentalToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(conditionalExperimentalToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
conditionalSpeedsImperial->setVisible(!isMetric);
|
||||||
|
conditionalSpeedsMetric->setVisible(isMetric);
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(conditionalExperimentalKeys.find(key.c_str()) != conditionalExperimentalKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = conditionalExperimentalToggle;
|
||||||
|
} else if (param == "CECurves") {
|
||||||
|
FrogPilotParamValueControl *CESpeedImperial = new FrogPilotParamValueControl("CESpeed", "Below", "Switch to 'Experimental Mode' below this speed in absence of a lead vehicle.", "", 0, 99,
|
||||||
|
std::map<int, QString>(), this, false, " mph");
|
||||||
|
FrogPilotParamValueControl *CESpeedLeadImperial = new FrogPilotParamValueControl("CESpeedLead", " w/Lead", "Switch to 'Experimental Mode' below this speed when following a lead vehicle.", "", 0, 99,
|
||||||
|
std::map<int, QString>(), this, false, " mph");
|
||||||
|
conditionalSpeedsImperial = new FrogPilotDualParamControl(CESpeedImperial, CESpeedLeadImperial, this);
|
||||||
|
addItem(conditionalSpeedsImperial);
|
||||||
|
|
||||||
|
FrogPilotParamValueControl *CESpeedMetric = new FrogPilotParamValueControl("CESpeed", "Below", "Switch to 'Experimental Mode' below this speed in absence of a lead vehicle.", "", 0, 150,
|
||||||
|
std::map<int, QString>(), this, false, " kph");
|
||||||
|
FrogPilotParamValueControl *CESpeedLeadMetric = new FrogPilotParamValueControl("CESpeedLead", " w/Lead", "Switch to 'Experimental Mode' below this speed when following a lead vehicle.", "", 0, 150,
|
||||||
|
std::map<int, QString>(), this, false, " kph");
|
||||||
|
conditionalSpeedsMetric = new FrogPilotDualParamControl(CESpeedMetric, CESpeedLeadMetric, this);
|
||||||
|
addItem(conditionalSpeedsMetric);
|
||||||
|
|
||||||
|
std::vector<QString> curveToggles{"CECurvesLead"};
|
||||||
|
std::vector<QString> curveToggleNames{tr("With Lead")};
|
||||||
|
toggle = new FrogPilotParamToggleControl(param, title, desc, icon, curveToggles, curveToggleNames);
|
||||||
|
} else if (param == "CEStopLights") {
|
||||||
|
std::vector<QString> stopLightToggles{"CEStopLightsLead"};
|
||||||
|
std::vector<QString> stopLightToggleNames{tr("With Lead")};
|
||||||
|
toggle = new FrogPilotParamToggleControl(param, title, desc, icon, stopLightToggles, stopLightToggleNames);
|
||||||
|
|
||||||
|
} else if (param == "CustomPersonalities") {
|
||||||
|
FrogPilotParamManageControl *customPersonalitiesToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(customPersonalitiesToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(false);
|
||||||
|
}
|
||||||
|
aggressiveProfile->setVisible(true);
|
||||||
|
standardProfile->setVisible(true);
|
||||||
|
relaxedProfile->setVisible(true);
|
||||||
|
});
|
||||||
|
toggle = customPersonalitiesToggle;
|
||||||
|
|
||||||
|
FrogPilotParamValueControl *aggressiveFollow = new FrogPilotParamValueControl("AggressiveFollow", "Follow",
|
||||||
|
"Set the 'Aggressive' personality' following distance. Represents seconds to follow behind the lead vehicle.\n\nStock: 1.25 seconds.", "../frogpilot/assets/other_images/aggressive.png",
|
||||||
|
10, 50, std::map<int, QString>(), this, false, " sec", 10);
|
||||||
|
FrogPilotParamValueControl *aggressiveJerk = new FrogPilotParamValueControl("AggressiveJerk", " Jerk",
|
||||||
|
"Configure brake/gas pedal responsiveness for the 'Aggressive' personality. Higher values yield a more 'relaxed' response.\n\nStock: 0.5.", "", 1, 50,
|
||||||
|
std::map<int, QString>(), this, false, "", 10);
|
||||||
|
aggressiveProfile = new FrogPilotDualParamControl(aggressiveFollow, aggressiveJerk, this, true);
|
||||||
|
addItem(aggressiveProfile);
|
||||||
|
|
||||||
|
FrogPilotParamValueControl *standardFollow = new FrogPilotParamValueControl("StandardFollow", "Follow",
|
||||||
|
"Set the 'Standard' personality following distance. Represents seconds to follow behind the lead vehicle.\n\nStock: 1.45 seconds.", "../frogpilot/assets/other_images/standard.png",
|
||||||
|
10, 50, std::map<int, QString>(), this, false, " sec", 10);
|
||||||
|
FrogPilotParamValueControl *standardJerk = new FrogPilotParamValueControl("StandardJerk", " Jerk",
|
||||||
|
"Adjust brake/gas pedal responsiveness for the 'Standard' personality. Higher values yield a more 'relaxed' response.\n\nStock: 1.0.", "", 1, 50,
|
||||||
|
std::map<int, QString>(), this, false, "", 10);
|
||||||
|
standardProfile = new FrogPilotDualParamControl(standardFollow, standardJerk, this, true);
|
||||||
|
addItem(standardProfile);
|
||||||
|
|
||||||
|
FrogPilotParamValueControl *relaxedFollow = new FrogPilotParamValueControl("RelaxedFollow", "Follow",
|
||||||
|
"Set the 'Relaxed' personality following distance. Represents seconds to follow behind the lead vehicle.\n\nStock: 1.75 seconds.", "../frogpilot/assets/other_images/relaxed.png",
|
||||||
|
10, 50, std::map<int, QString>(), this, false, " sec", 10);
|
||||||
|
FrogPilotParamValueControl *relaxedJerk = new FrogPilotParamValueControl("RelaxedJerk", " Jerk",
|
||||||
|
"Set brake/gas pedal responsiveness for the 'Relaxed' personality. Higher values yield a more 'relaxed' response.\n\nStock: 1.0.", "", 1, 50,
|
||||||
|
std::map<int, QString>(), this, false, "", 10);
|
||||||
|
relaxedProfile = new FrogPilotDualParamControl(relaxedFollow, relaxedJerk, this, true);
|
||||||
|
addItem(relaxedProfile);
|
||||||
|
|
||||||
|
} else if (param == "DeviceShutdown") {
|
||||||
|
std::map<int, QString> shutdownLabels;
|
||||||
|
for (int i = 0; i <= 33; ++i) {
|
||||||
|
shutdownLabels[i] = i == 0 ? "Instant" : i <= 3 ? QString::number(i * 15) + " mins" : QString::number(i - 3) + (i == 4 ? " hour" : " hours");
|
||||||
|
}
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 33, shutdownLabels, this, false);
|
||||||
|
|
||||||
|
} else if (param == "ExperimentalModeActivation") {
|
||||||
|
std::vector<QString> experimentalModeToggles{"ExperimentalModeViaLKAS", "ExperimentalModeViaScreen"};
|
||||||
|
std::vector<QString> experimentalModeNames{tr("LKAS Button"), tr("Screen")};
|
||||||
|
toggle = new FrogPilotParamToggleControl(param, title, desc, icon, experimentalModeToggles, experimentalModeNames);
|
||||||
|
|
||||||
|
} else if (param == "FireTheBabysitter") {
|
||||||
|
FrogPilotParamManageControl *fireTheBabysitterToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(fireTheBabysitterToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(fireTheBabysitterKeys.find(key.c_str()) != fireTheBabysitterKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = fireTheBabysitterToggle;
|
||||||
|
|
||||||
|
} else if (param == "LateralTune") {
|
||||||
|
FrogPilotParamManageControl *lateralTuneToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(lateralTuneToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(lateralTuneKeys.find(key.c_str()) != lateralTuneKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = lateralTuneToggle;
|
||||||
|
} else if (param == "SteerRatio") {
|
||||||
|
toggle = new FrogPilotParamValueControlFloat(param, title, desc, icon, steerRatioStock * 0.75, steerRatioStock * 1.25, std::map<int, QString>(), this, false, "", 10.0);
|
||||||
|
|
||||||
|
} else if (param == "LongitudinalTune") {
|
||||||
|
FrogPilotParamManageControl *longitudinalTuneToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(longitudinalTuneToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(longitudinalTuneKeys.find(key.c_str()) != longitudinalTuneKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = longitudinalTuneToggle;
|
||||||
|
} else if (param == "AccelerationProfile") {
|
||||||
|
std::vector<QString> profileOptions{tr("Standard"), tr("Eco"), tr("Sport"), tr("Sport+")};
|
||||||
|
FrogPilotButtonParamControl *profileSelection = new FrogPilotButtonParamControl(param, title, desc, icon, profileOptions);
|
||||||
|
toggle = profileSelection;
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotButtonParamControl*>(toggle), &FrogPilotButtonParamControl::buttonClicked, [this](int id) {
|
||||||
|
if (id == 3) {
|
||||||
|
FrogPilotConfirmationDialog::toggleAlert("WARNING: This maxes out openpilot's acceleration from 2.0 m/s to 4.0 m/s and may cause oscillations when accelerating!",
|
||||||
|
"I understand the risks.", this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (param == "StoppingDistance") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 10, std::map<int, QString>(), this, false, " feet");
|
||||||
|
|
||||||
|
} else if (param == "Model") {
|
||||||
|
modelSelectorButton = new FrogPilotButtonIconControl(title, tr("SELECT"), desc, icon);
|
||||||
|
QStringList models = {"New Delhi (Default)", "Blue Diamond V1", "Blue Diamond V2", "Farmville", "New Lemon Pie"};
|
||||||
|
QObject::connect(modelSelectorButton, &FrogPilotButtonIconControl::clicked, this, [this, models]() {
|
||||||
|
int currentModel = params.getInt("Model");
|
||||||
|
QString currentModelLabel = models[currentModel];
|
||||||
|
|
||||||
|
QString selection = MultiOptionDialog::getSelection(tr("Select a driving model"), models, currentModelLabel, this);
|
||||||
|
if (!selection.isEmpty()) {
|
||||||
|
int selectedModel = models.indexOf(selection);
|
||||||
|
params.putInt("Model", selectedModel);
|
||||||
|
params.remove("CalibrationParams");
|
||||||
|
params.remove("LiveTorqueParameters");
|
||||||
|
modelSelectorButton->setValue(selection);
|
||||||
|
if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) {
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
modelSelectorButton->setValue(models[params.getInt("Model")]);
|
||||||
|
addItem(modelSelectorButton);
|
||||||
|
|
||||||
|
} else if (param == "NudgelessLaneChange") {
|
||||||
|
FrogPilotParamManageControl *laneChangeToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(laneChangeToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(laneChangeKeys.find(key.c_str()) != laneChangeKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = laneChangeToggle;
|
||||||
|
} else if (param == "LaneChangeTime") {
|
||||||
|
std::map<int, QString> laneChangeTimeLabels;
|
||||||
|
for (int i = 0; i <= 10; ++i) {
|
||||||
|
laneChangeTimeLabels[i] = i == 0 ? "Instant" : QString::number(i / 2.0) + " seconds";
|
||||||
|
}
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 10, laneChangeTimeLabels, this, false);
|
||||||
|
|
||||||
|
} else if (param == "QOLControls") {
|
||||||
|
FrogPilotParamManageControl *qolToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(qolToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(qolKeys.find(key.c_str()) != qolKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = qolToggle;
|
||||||
|
} else if (param == "PauseLateralOnSignal") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, std::map<int, QString>(), this, false, " mph");
|
||||||
|
} else if (param == "SetSpeedOffset") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, std::map<int, QString>(), this, false, " mph");
|
||||||
|
|
||||||
|
} else if (param == "SpeedLimitController") {
|
||||||
|
FrogPilotParamManageControl *speedLimitControllerToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(speedLimitControllerToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(speedLimitControllerKeys.find(key.c_str()) != speedLimitControllerKeys.end());
|
||||||
|
}
|
||||||
|
slscPriorityButton->setVisible(true);
|
||||||
|
});
|
||||||
|
toggle = speedLimitControllerToggle;
|
||||||
|
} else if (param == "Offset1" || param == "Offset2" || param == "Offset3" || param == "Offset4") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, std::map<int, QString>(), this, false, " mph");
|
||||||
|
} else if (param == "SLCFallback") {
|
||||||
|
std::vector<QString> fallbackOptions{tr("None"), tr("Experimental Mode"), tr("Previous Limit")};
|
||||||
|
FrogPilotButtonParamControl *fallbackSelection = new FrogPilotButtonParamControl(param, title, desc, icon, fallbackOptions);
|
||||||
|
toggle = fallbackSelection;
|
||||||
|
} else if (param == "SLCOverride") {
|
||||||
|
std::vector<QString> overrideOptions{tr("None"), tr("Manual Set Speed"), tr("Max Set Speed")};
|
||||||
|
FrogPilotButtonParamControl *overrideSelection = new FrogPilotButtonParamControl(param, title, desc, icon, overrideOptions);
|
||||||
|
toggle = overrideSelection;
|
||||||
|
} else if (param == "SLCPriority") {
|
||||||
|
const QStringList priorities {
|
||||||
|
"Navigation, Dashboard, Offline Maps",
|
||||||
|
"Navigation, Offline Maps, Dashboard",
|
||||||
|
"Navigation, Offline Maps",
|
||||||
|
"Navigation, Dashboard",
|
||||||
|
"Navigation",
|
||||||
|
"Offline Maps, Dashboard, Navigation",
|
||||||
|
"Offline Maps, Navigation, Dashboard",
|
||||||
|
"Offline Maps, Navigation",
|
||||||
|
"Offline Maps, Dashboard",
|
||||||
|
"Offline Maps",
|
||||||
|
"Dashboard, Navigation, Offline Maps",
|
||||||
|
"Dashboard, Offline Maps, Navigation",
|
||||||
|
"Dashboard, Offline Maps",
|
||||||
|
"Dashboard, Navigation",
|
||||||
|
"Dashboard",
|
||||||
|
"Highest",
|
||||||
|
"Lowest",
|
||||||
|
"",
|
||||||
|
};
|
||||||
|
|
||||||
|
slscPriorityButton = new ButtonControl(title, tr("SELECT"), desc);
|
||||||
|
QObject::connect(slscPriorityButton, &ButtonControl::clicked, this, [this, priorities]() {
|
||||||
|
QStringList availablePriorities = {"Dashboard", "Navigation", "Offline Maps", "Highest", "Lowest", "None"};
|
||||||
|
QStringList selectedPriorities;
|
||||||
|
int priorityValue = -1;
|
||||||
|
|
||||||
|
QStringList priorityPrompts = {tr("Select your primary priority"), tr("Select your secondary priority"), tr("Select your tertiary priority")};
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
QString selection = MultiOptionDialog::getSelection(priorityPrompts[i], availablePriorities, "", this);
|
||||||
|
if (selection.isEmpty()) break;
|
||||||
|
|
||||||
|
if (selection == "None") {
|
||||||
|
priorityValue = 17;
|
||||||
|
break;
|
||||||
|
} else if (selection == "Highest") {
|
||||||
|
priorityValue = 15;
|
||||||
|
break;
|
||||||
|
} else if (selection == "Lowest") {
|
||||||
|
priorityValue = 16;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
selectedPriorities.append(selection);
|
||||||
|
availablePriorities.removeAll(selection);
|
||||||
|
availablePriorities.removeAll("Highest");
|
||||||
|
availablePriorities.removeAll("Lowest");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priorityValue == -1 && !selectedPriorities.isEmpty()) {
|
||||||
|
QString priorityString = selectedPriorities.join(", ");
|
||||||
|
priorityValue = priorities.indexOf(priorityString);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priorityValue != -1) {
|
||||||
|
slscPriorityButton->setValue(priorities[priorityValue]);
|
||||||
|
params.putInt("SLCPriority", priorityValue);
|
||||||
|
updateToggles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
slscPriorityButton->setValue(priorities[params.getInt("SLCPriority")]);
|
||||||
|
addItem(slscPriorityButton);
|
||||||
|
|
||||||
|
} else if (param == "VisionTurnControl") {
|
||||||
|
FrogPilotParamManageControl *visionTurnControlToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(visionTurnControlToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(visionTurnControlKeys.find(key.c_str()) != visionTurnControlKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = visionTurnControlToggle;
|
||||||
|
} else if (param == "CurveSensitivity" || param == "TurnAggressiveness") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 1, 200, std::map<int, QString>(), this, false, "%");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
toggle = new ParamControl(param, title, desc, icon, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
addItem(toggle);
|
||||||
|
toggles[param.toStdString()] = toggle;
|
||||||
|
|
||||||
|
QObject::connect(toggle, &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamValueControl*>(toggle), &FrogPilotParamValueControl::buttonPressed, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(toggle, &AbstractControl::showDescriptionEvent, [this]() {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamManageControl*>(toggle), &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string> rebootKeys = {"AlwaysOnLateral", "FireTheBabysitter", "HigherBitrate", "MuteDM", "NNFF"};
|
||||||
|
for (const std::string &key : rebootKeys) {
|
||||||
|
QObject::connect(toggles[key], &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) {
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
conditionalExperimentalKeys = {"CECurves", "CECurvesLead", "CESlowerLead", "CENavigation", "CEStopLights", "CESignal"};
|
||||||
|
fireTheBabysitterKeys = {"NoLogging", "MuteDM", "MuteDoor", "MuteOverheated", "MuteSeatbelt", "OfflineMode"};
|
||||||
|
laneChangeKeys = {"LaneChangeTime", "LaneDetection", "OneLaneChange"};
|
||||||
|
lateralTuneKeys = {"AverageCurvature", "NNFF", "SteerRatio"};
|
||||||
|
longitudinalTuneKeys = {"AccelerationProfile", "AggressiveAcceleration", "SmoothBraking", "StoppingDistance"};
|
||||||
|
mtscKeys = {"MTSCAggressiveness"};
|
||||||
|
qolKeys = {"DisableOnroadUploads", "HigherBitrate", "PauseLateralOnSignal", "ReverseCruise", "SetSpeedOffset"};
|
||||||
|
speedLimitControllerKeys = {"Offset1", "Offset2", "Offset3", "Offset4", "SLCFallback", "SLCOverride", "SLCPriority"};
|
||||||
|
visionTurnControlKeys = {"CurveSensitivity", "TurnAggressiveness"};
|
||||||
|
|
||||||
|
QObject::connect(uiState(), &UIState::offroadTransition, this, [this](bool offroad) {
|
||||||
|
if (!offroad) {
|
||||||
|
updateCarToggles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(device(), &Device::interactiveTimeout, this, &FrogPilotControlsPanel::hideSubToggles);
|
||||||
|
QObject::connect(parent, &SettingsWindow::closeParentToggle, this, &FrogPilotControlsPanel::hideSubToggles);
|
||||||
|
QObject::connect(parent, &SettingsWindow::updateMetric, this, &FrogPilotControlsPanel::updateMetric);
|
||||||
|
|
||||||
|
hideSubToggles();
|
||||||
|
updateMetric();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotControlsPanel::updateCarToggles() {
|
||||||
|
FrogPilotParamValueControlFloat *steerRatioToggle = static_cast<FrogPilotParamValueControlFloat*>(toggles["SteerRatio"]);
|
||||||
|
steerRatioStock = params.getFloat("SteerRatioStock");
|
||||||
|
steerRatioToggle->setTitle(steerRatioStock != 0 ? QString("Steer Ratio (Default: %1)").arg(steerRatioStock, 0, 'f', 2) : QString("Steer Ratio"));
|
||||||
|
steerRatioToggle->updateControl(steerRatioStock * 0.75, steerRatioStock * 1.25, "", 10.0);
|
||||||
|
steerRatioToggle->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotControlsPanel::updateToggles() {
|
||||||
|
std::thread([this]() {
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", true);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", false);
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotControlsPanel::updateMetric() {
|
||||||
|
bool previousIsMetric = isMetric;
|
||||||
|
isMetric = params.getBool("IsMetric");
|
||||||
|
|
||||||
|
if (isMetric != previousIsMetric) {
|
||||||
|
double distanceConversion = isMetric ? FOOT_TO_METER : METER_TO_FOOT;
|
||||||
|
double speedConversion = isMetric ? MILE_TO_KM : KM_TO_MILE;
|
||||||
|
params.putInt("CESpeed", std::nearbyint(params.getInt("CESpeed") * speedConversion));
|
||||||
|
params.putInt("CESpeedLead", std::nearbyint(params.getInt("CESpeedLead") * speedConversion));
|
||||||
|
params.putInt("Offset1", std::nearbyint(params.getInt("Offset1") * speedConversion));
|
||||||
|
params.putInt("Offset2", std::nearbyint(params.getInt("Offset2") * speedConversion));
|
||||||
|
params.putInt("Offset3", std::nearbyint(params.getInt("Offset3") * speedConversion));
|
||||||
|
params.putInt("Offset4", std::nearbyint(params.getInt("Offset4") * speedConversion));
|
||||||
|
params.putInt("PauseLateralOnSignal", std::nearbyint(params.getInt("PauseLateralOnSignal") * speedConversion));
|
||||||
|
params.putInt("SetSpeedOffset", std::nearbyint(params.getInt("SetSpeedOffset") * speedConversion));
|
||||||
|
params.putInt("StoppingDistance", std::nearbyint(params.getInt("StoppingDistance") * distanceConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
FrogPilotParamValueControl *offset1Toggle = static_cast<FrogPilotParamValueControl*>(toggles["Offset1"]);
|
||||||
|
FrogPilotParamValueControl *offset2Toggle = static_cast<FrogPilotParamValueControl*>(toggles["Offset2"]);
|
||||||
|
FrogPilotParamValueControl *offset3Toggle = static_cast<FrogPilotParamValueControl*>(toggles["Offset3"]);
|
||||||
|
FrogPilotParamValueControl *offset4Toggle = static_cast<FrogPilotParamValueControl*>(toggles["Offset4"]);
|
||||||
|
FrogPilotParamValueControl *pauseLateralToggle = static_cast<FrogPilotParamValueControl*>(toggles["PauseLateralOnSignal"]);
|
||||||
|
FrogPilotParamValueControl *setSpeedOffsetToggle = static_cast<FrogPilotParamValueControl*>(toggles["SetSpeedOffset"]);
|
||||||
|
FrogPilotParamValueControl *stoppingDistanceToggle = static_cast<FrogPilotParamValueControl*>(toggles["StoppingDistance"]);
|
||||||
|
|
||||||
|
if (isMetric) {
|
||||||
|
offset1Toggle->setTitle("Speed Limit Offset (0-34 kph)");
|
||||||
|
offset2Toggle->setTitle("Speed Limit Offset (35-54 kph)");
|
||||||
|
offset3Toggle->setTitle("Speed Limit Offset (55-64 kph)");
|
||||||
|
offset4Toggle->setTitle("Speed Limit Offset (65-99 kph)");
|
||||||
|
|
||||||
|
offset1Toggle->setDescription("Set speed limit offset for limits between 0-34 kph.");
|
||||||
|
offset2Toggle->setDescription("Set speed limit offset for limits between 35-54 kph.");
|
||||||
|
offset3Toggle->setDescription("Set speed limit offset for limits between 55-64 kph.");
|
||||||
|
offset4Toggle->setDescription("Set speed limit offset for limits between 65-99 kph.");
|
||||||
|
|
||||||
|
offset1Toggle->updateControl(0, 150, " kph");
|
||||||
|
offset2Toggle->updateControl(0, 150, " kph");
|
||||||
|
offset3Toggle->updateControl(0, 150, " kph");
|
||||||
|
offset4Toggle->updateControl(0, 150, " kph");
|
||||||
|
pauseLateralToggle->updateControl(0, 150, " kph");
|
||||||
|
setSpeedOffsetToggle->updateControl(0, 150, " kph");
|
||||||
|
stoppingDistanceToggle->updateControl(0, 5, " meters");
|
||||||
|
} else {
|
||||||
|
offset1Toggle->setTitle("Speed Limit Offset (0-34 mph)");
|
||||||
|
offset2Toggle->setTitle("Speed Limit Offset (35-54 mph)");
|
||||||
|
offset3Toggle->setTitle("Speed Limit Offset (55-64 mph)");
|
||||||
|
offset4Toggle->setTitle("Speed Limit Offset (65-99 mph)");
|
||||||
|
|
||||||
|
offset1Toggle->setDescription("Set speed limit offset for limits between 0-34 mph.");
|
||||||
|
offset2Toggle->setDescription("Set speed limit offset for limits between 35-54 mph.");
|
||||||
|
offset3Toggle->setDescription("Set speed limit offset for limits between 55-64 mph.");
|
||||||
|
offset4Toggle->setDescription("Set speed limit offset for limits between 65-99 mph.");
|
||||||
|
|
||||||
|
offset1Toggle->updateControl(0, 99, " mph");
|
||||||
|
offset2Toggle->updateControl(0, 99, " mph");
|
||||||
|
offset3Toggle->updateControl(0, 99, " mph");
|
||||||
|
offset4Toggle->updateControl(0, 99, " mph");
|
||||||
|
pauseLateralToggle->updateControl(0, 99, " mph");
|
||||||
|
setSpeedOffsetToggle->updateControl(0, 99, " mph");
|
||||||
|
stoppingDistanceToggle->updateControl(0, 10, " feet");
|
||||||
|
}
|
||||||
|
|
||||||
|
offset1Toggle->refresh();
|
||||||
|
offset2Toggle->refresh();
|
||||||
|
offset3Toggle->refresh();
|
||||||
|
offset4Toggle->refresh();
|
||||||
|
pauseLateralToggle->refresh();
|
||||||
|
setSpeedOffsetToggle->refresh();
|
||||||
|
stoppingDistanceToggle->refresh();
|
||||||
|
|
||||||
|
previousIsMetric = isMetric;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotControlsPanel::parentToggleClicked() {
|
||||||
|
aggressiveProfile->setVisible(false);
|
||||||
|
conditionalSpeedsImperial->setVisible(false);
|
||||||
|
conditionalSpeedsMetric->setVisible(false);
|
||||||
|
modelSelectorButton->setVisible(false);
|
||||||
|
slscPriorityButton->setVisible(false);
|
||||||
|
standardProfile->setVisible(false);
|
||||||
|
relaxedProfile->setVisible(false);
|
||||||
|
|
||||||
|
this->openParentToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotControlsPanel::hideSubToggles() {
|
||||||
|
aggressiveProfile->setVisible(false);
|
||||||
|
conditionalSpeedsImperial->setVisible(false);
|
||||||
|
conditionalSpeedsMetric->setVisible(false);
|
||||||
|
modelSelectorButton->setVisible(true);
|
||||||
|
slscPriorityButton->setVisible(false);
|
||||||
|
standardProfile->setVisible(false);
|
||||||
|
relaxedProfile->setVisible(false);
|
||||||
|
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
bool subToggles = conditionalExperimentalKeys.find(key.c_str()) != conditionalExperimentalKeys.end() ||
|
||||||
|
fireTheBabysitterKeys.find(key.c_str()) != fireTheBabysitterKeys.end() ||
|
||||||
|
laneChangeKeys.find(key.c_str()) != laneChangeKeys.end() ||
|
||||||
|
lateralTuneKeys.find(key.c_str()) != lateralTuneKeys.end() ||
|
||||||
|
longitudinalTuneKeys.find(key.c_str()) != longitudinalTuneKeys.end() ||
|
||||||
|
mtscKeys.find(key.c_str()) != mtscKeys.end() ||
|
||||||
|
qolKeys.find(key.c_str()) != qolKeys.end() ||
|
||||||
|
speedLimitControllerKeys.find(key.c_str()) != speedLimitControllerKeys.end() ||
|
||||||
|
visionTurnControlKeys.find(key.c_str()) != visionTurnControlKeys.end();
|
||||||
|
toggle->setVisible(!subToggles);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->closeParentToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotControlsPanel::hideEvent(QHideEvent *event) {
|
||||||
|
hideSubToggles();
|
||||||
|
}
|
||||||
54
selfdrive/clearpilot/ui/control_settings.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "selfdrive/oscarpilot/settings/settings.h"
|
||||||
|
#include "selfdrive/frogpilot/ui/frogpilot_functions.h"
|
||||||
|
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||||
|
|
||||||
|
class FrogPilotControlsPanel : public FrogPilotListWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FrogPilotControlsPanel(OscarSettingsWindow *parent);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void closeParentToggle();
|
||||||
|
void openParentToggle();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void hideEvent(QHideEvent *event);
|
||||||
|
void hideSubToggles();
|
||||||
|
void parentToggleClicked();
|
||||||
|
void updateCarToggles();
|
||||||
|
void updateMetric();
|
||||||
|
void updateToggles();
|
||||||
|
|
||||||
|
ButtonControl *slscPriorityButton;
|
||||||
|
|
||||||
|
FrogPilotButtonIconControl *modelSelectorButton;
|
||||||
|
|
||||||
|
FrogPilotDualParamControl *aggressiveProfile;
|
||||||
|
FrogPilotDualParamControl *conditionalSpeedsImperial;
|
||||||
|
FrogPilotDualParamControl *conditionalSpeedsMetric;
|
||||||
|
FrogPilotDualParamControl *standardProfile;
|
||||||
|
FrogPilotDualParamControl *relaxedProfile;
|
||||||
|
|
||||||
|
std::set<QString> conditionalExperimentalKeys;
|
||||||
|
std::set<QString> fireTheBabysitterKeys;
|
||||||
|
std::set<QString> laneChangeKeys;
|
||||||
|
std::set<QString> lateralTuneKeys;
|
||||||
|
std::set<QString> longitudinalTuneKeys;
|
||||||
|
std::set<QString> mtscKeys;
|
||||||
|
std::set<QString> qolKeys;
|
||||||
|
std::set<QString> speedLimitControllerKeys;
|
||||||
|
std::set<QString> visionTurnControlKeys;
|
||||||
|
|
||||||
|
std::map<std::string, ParamControl*> toggles;
|
||||||
|
|
||||||
|
Params params;
|
||||||
|
Params paramsMemory{"/dev/shm/params"};
|
||||||
|
|
||||||
|
bool isMetric = params.getBool("IsMetric");
|
||||||
|
float steerRatioStock = params.getFloat("SteerRatioStock");
|
||||||
|
};
|
||||||
53
selfdrive/clearpilot/ui/control_settings.h.org
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/frogpilot_functions.h"
|
||||||
|
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||||
|
|
||||||
|
class FrogPilotControlsPanel : public FrogPilotListWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FrogPilotControlsPanel(SettingsWindow *parent);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void closeParentToggle();
|
||||||
|
void openParentToggle();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void hideEvent(QHideEvent *event);
|
||||||
|
void hideSubToggles();
|
||||||
|
void parentToggleClicked();
|
||||||
|
void updateCarToggles();
|
||||||
|
void updateMetric();
|
||||||
|
void updateToggles();
|
||||||
|
|
||||||
|
ButtonControl *slscPriorityButton;
|
||||||
|
|
||||||
|
FrogPilotButtonIconControl *modelSelectorButton;
|
||||||
|
|
||||||
|
FrogPilotDualParamControl *aggressiveProfile;
|
||||||
|
FrogPilotDualParamControl *conditionalSpeedsImperial;
|
||||||
|
FrogPilotDualParamControl *conditionalSpeedsMetric;
|
||||||
|
FrogPilotDualParamControl *standardProfile;
|
||||||
|
FrogPilotDualParamControl *relaxedProfile;
|
||||||
|
|
||||||
|
std::set<QString> conditionalExperimentalKeys;
|
||||||
|
std::set<QString> fireTheBabysitterKeys;
|
||||||
|
std::set<QString> laneChangeKeys;
|
||||||
|
std::set<QString> lateralTuneKeys;
|
||||||
|
std::set<QString> longitudinalTuneKeys;
|
||||||
|
std::set<QString> mtscKeys;
|
||||||
|
std::set<QString> qolKeys;
|
||||||
|
std::set<QString> speedLimitControllerKeys;
|
||||||
|
std::set<QString> visionTurnControlKeys;
|
||||||
|
|
||||||
|
std::map<std::string, ParamControl*> toggles;
|
||||||
|
|
||||||
|
Params params;
|
||||||
|
Params paramsMemory{"/dev/shm/params"};
|
||||||
|
|
||||||
|
bool isMetric = params.getBool("IsMetric");
|
||||||
|
float steerRatioStock = params.getFloat("SteerRatioStock");
|
||||||
|
};
|
||||||
177
selfdrive/clearpilot/ui/frogpilot_functions.cc
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/frogpilot_functions.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
|
bool FrogPilotConfirmationDialog::toggle(const QString &prompt_text, const QString &confirm_text, QWidget *parent) {
|
||||||
|
ConfirmationDialog d = ConfirmationDialog(prompt_text, confirm_text, tr("Reboot Later"), false, parent);
|
||||||
|
return d.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FrogPilotConfirmationDialog::toggleAlert(const QString &prompt_text, const QString &button_text, QWidget *parent) {
|
||||||
|
ConfirmationDialog d = ConfirmationDialog(prompt_text, button_text, "", false, parent);
|
||||||
|
return d.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FrogPilotConfirmationDialog::yesorno(const QString &prompt_text, QWidget *parent) {
|
||||||
|
ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Yes"), tr("No"), false, parent);
|
||||||
|
return d.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
FrogPilotButtonIconControl::FrogPilotButtonIconControl(const QString &title, const QString &text, const QString &desc, const QString &icon, QWidget *parent) : AbstractControl(title, desc, icon, parent) {
|
||||||
|
btn.setText(text);
|
||||||
|
btn.setStyleSheet(R"(
|
||||||
|
QPushButton {
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 35px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
color: #33E4E4E4;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
btn.setFixedSize(250, 100);
|
||||||
|
QObject::connect(&btn, &QPushButton::clicked, this, &FrogPilotButtonIconControl::clicked);
|
||||||
|
hlayout->addWidget(&btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDefaultParams() {
|
||||||
|
Params params = Params();
|
||||||
|
bool FrogsGoMoo = params.get("DongleId").substr(0, 3) == "be6";
|
||||||
|
|
||||||
|
bool brianbot = params.get("DongleId").substr(0, 6) == "90bb71";
|
||||||
|
|
||||||
|
std::map<std::string, std::string> defaultValues {
|
||||||
|
{"AccelerationPath", brianbot ? "1" : "0"},
|
||||||
|
{"AccelerationProfile", brianbot ? "2" : "2"},
|
||||||
|
{"AdjacentPath", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"AdjustablePersonalities", "0"},
|
||||||
|
{"AggressiveAcceleration", "1"},
|
||||||
|
{"AggressiveFollow", FrogsGoMoo ? "10" : "12"},
|
||||||
|
{"AggressiveJerk", FrogsGoMoo ? "6" : "5"},
|
||||||
|
{"AlwaysOnLateral", "1"},
|
||||||
|
{"AlwaysOnLateralMain", brianbot ? "1" : "0"},
|
||||||
|
{"AverageCurvature", brianbot ? "1" : "0"},
|
||||||
|
{"BlindSpotPath", "1"},
|
||||||
|
{"CameraView", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"CECurves", "1"},
|
||||||
|
{"CECurvesLead", "0"},
|
||||||
|
{"CENavigation", "1"},
|
||||||
|
{"CESignal", "1"},
|
||||||
|
{"CESlowerLead", "0"},
|
||||||
|
{"CESpeed", "0"},
|
||||||
|
{"CESpeedLead", "0"},
|
||||||
|
{"CEStopLights", "0"},
|
||||||
|
{"CEStopLightsLead", FrogsGoMoo ? "0" : "0"},
|
||||||
|
{"Compass", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"ConditionalExperimental", "1"},
|
||||||
|
{"CurveSensitivity", FrogsGoMoo ? "125" : "100"},
|
||||||
|
{"CustomColors", "1"},
|
||||||
|
{"CustomIcons", "1"},
|
||||||
|
{"CustomPersonalities", "0"},
|
||||||
|
{"CustomSignals", "0"},
|
||||||
|
{"CustomSounds", "1"},
|
||||||
|
{"CustomTheme", "1"},
|
||||||
|
{"CustomUI", "0"},
|
||||||
|
{"DeviceShutdown", "1"},
|
||||||
|
{"DisableOnroadUploads", "1"},
|
||||||
|
{"DriverCamera", "0"},
|
||||||
|
{"DriveStats", "1"},
|
||||||
|
{"EVTable", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"ExperimentalModeActivation", "1"},
|
||||||
|
{"ExperimentalModeViaLKAS", "0"},
|
||||||
|
{"ExperimentalModeViaScreen", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"Fahrenheit", "0"},
|
||||||
|
{"FireTheBabysitter", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"FullMap", "0"},
|
||||||
|
{"GasRegenCmd", "0"},
|
||||||
|
{"GoatScream", "0"},
|
||||||
|
{"GreenLightAlert", "0"},
|
||||||
|
{"HideSpeed", "0"},
|
||||||
|
{"HigherBitrate", "0"},
|
||||||
|
{"LaneChangeTime", "0"},
|
||||||
|
{"LaneDetection", "1"},
|
||||||
|
{"LaneLinesWidth", "4"},
|
||||||
|
{"LateralTune", "1"},
|
||||||
|
{"LeadInfo", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"LockDoors", "0"},
|
||||||
|
{"LongitudinalTune", "1"},
|
||||||
|
{"LongPitch", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"LowerVolt", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"Model", "3"},
|
||||||
|
{"ModelUI", "1"},
|
||||||
|
{"MTSCEnabled", "1"},
|
||||||
|
{"MuteDM", FrogsGoMoo ? "1" : "1"},
|
||||||
|
{"MuteDoor", FrogsGoMoo ? "1" : "1"},
|
||||||
|
{"MuteOverheated", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"MuteSeatbelt", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"NNFF", FrogsGoMoo ? "1" : "1"},
|
||||||
|
{"NoLogging", "1"},
|
||||||
|
{"NudgelessLaneChange", "0"},
|
||||||
|
{"NumericalTemp", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"Offset1", "3"},
|
||||||
|
{"Offset2", FrogsGoMoo ? "7" : "5"},
|
||||||
|
{"Offset3", "7"},
|
||||||
|
{"Offset4", FrogsGoMoo ? "20" : "7"},
|
||||||
|
{"OneLaneChange", "1"},
|
||||||
|
{"PathEdgeWidth", "20"},
|
||||||
|
{"PathWidth", "61"},
|
||||||
|
{"PauseLateralOnSignal", "20"},
|
||||||
|
{"PreferredSchedule", "0"},
|
||||||
|
{"QOLControls", "1"},
|
||||||
|
{"QOLVisuals", "1"},
|
||||||
|
{"RandomEvents", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"RelaxedFollow", "30"},
|
||||||
|
{"RelaxedJerk", "50"},
|
||||||
|
{"ReverseCruise", "0"},
|
||||||
|
{"RoadEdgesWidth", "2"},
|
||||||
|
{"RoadNameUI", "1"},
|
||||||
|
{"RotatingWheel", "1"},
|
||||||
|
{"ScreenBrightness", "101"},
|
||||||
|
{"SearchInput", "0"},
|
||||||
|
{"ShowCPU", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"ShowFPS", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"ShowGPU", "0"},
|
||||||
|
{"ShowMemoryUsage", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"Sidebar", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"SilentMode", "0"},
|
||||||
|
{"SLCFallback", "2"},
|
||||||
|
{"SLCOverride", FrogsGoMoo ? "2" : "1"},
|
||||||
|
{"SLCPriority", "1"},
|
||||||
|
{"SmoothBraking", "1"},
|
||||||
|
{"SNGHack", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"SpeedLimitController", "1"},
|
||||||
|
{"StandardFollow", "15"},
|
||||||
|
{"StandardJerk", "10"},
|
||||||
|
{"StoppingDistance", FrogsGoMoo ? "6" : "0"},
|
||||||
|
{"TSS2Tune", "1"},
|
||||||
|
{"TurnAggressiveness", FrogsGoMoo ? "150" : "100"}, // Test 90?
|
||||||
|
{"TurnDesires", "0"},
|
||||||
|
{"UnlimitedLength", "1"},
|
||||||
|
{"UseSI", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"UseVienna", "0"},
|
||||||
|
{"VisionTurnControl", "1"},
|
||||||
|
{"WheelIcon", FrogsGoMoo ? "1" : "0"}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool rebootRequired = false;
|
||||||
|
for (const auto &[key, value] : defaultValues) {
|
||||||
|
if (params.get(key).empty()) {
|
||||||
|
params.put(key, value);
|
||||||
|
rebootRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rebootRequired) {
|
||||||
|
while (!std::filesystem::exists("/data/openpilot/prebuilt")) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
}
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
}
|
||||||
177
selfdrive/clearpilot/ui/frogpilot_functions.cc.org
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/frogpilot_functions.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
|
bool FrogPilotConfirmationDialog::toggle(const QString &prompt_text, const QString &confirm_text, QWidget *parent) {
|
||||||
|
ConfirmationDialog d = ConfirmationDialog(prompt_text, confirm_text, tr("Reboot Later"), false, parent);
|
||||||
|
return d.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FrogPilotConfirmationDialog::toggleAlert(const QString &prompt_text, const QString &button_text, QWidget *parent) {
|
||||||
|
ConfirmationDialog d = ConfirmationDialog(prompt_text, button_text, "", false, parent);
|
||||||
|
return d.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FrogPilotConfirmationDialog::yesorno(const QString &prompt_text, QWidget *parent) {
|
||||||
|
ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Yes"), tr("No"), false, parent);
|
||||||
|
return d.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
FrogPilotButtonIconControl::FrogPilotButtonIconControl(const QString &title, const QString &text, const QString &desc, const QString &icon, QWidget *parent) : AbstractControl(title, desc, icon, parent) {
|
||||||
|
btn.setText(text);
|
||||||
|
btn.setStyleSheet(R"(
|
||||||
|
QPushButton {
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 35px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
color: #33E4E4E4;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
btn.setFixedSize(250, 100);
|
||||||
|
QObject::connect(&btn, &QPushButton::clicked, this, &FrogPilotButtonIconControl::clicked);
|
||||||
|
hlayout->addWidget(&btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDefaultParams() {
|
||||||
|
Params params = Params();
|
||||||
|
bool FrogsGoMoo = params.get("DongleId").substr(0, 3) == "be6";
|
||||||
|
|
||||||
|
bool brianbot = params.get("DongleId").substr(0, 6) == "90bb71";
|
||||||
|
|
||||||
|
std::map<std::string, std::string> defaultValues {
|
||||||
|
{"AccelerationPath", brianbot ? "1" : "0"},
|
||||||
|
{"AccelerationProfile", brianbot ? "2" : "2"},
|
||||||
|
{"AdjacentPath", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"AdjustablePersonalities", "0"},
|
||||||
|
{"AggressiveAcceleration", "1"},
|
||||||
|
{"AggressiveFollow", FrogsGoMoo ? "10" : "12"},
|
||||||
|
{"AggressiveJerk", FrogsGoMoo ? "6" : "5"},
|
||||||
|
{"AlwaysOnLateral", "1"},
|
||||||
|
{"AlwaysOnLateralMain", brianbot ? "1" : "0"},
|
||||||
|
{"AverageCurvature", brianbot ? "1" : "0"},
|
||||||
|
{"BlindSpotPath", "1"},
|
||||||
|
{"CameraView", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"CECurves", "1"},
|
||||||
|
{"CECurvesLead", "0"},
|
||||||
|
{"CENavigation", "1"},
|
||||||
|
{"CESignal", "1"},
|
||||||
|
{"CESlowerLead", "0"},
|
||||||
|
{"CESpeed", "0"},
|
||||||
|
{"CESpeedLead", "0"},
|
||||||
|
{"CEStopLights", "0"},
|
||||||
|
{"CEStopLightsLead", FrogsGoMoo ? "0" : "0"},
|
||||||
|
{"Compass", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"ConditionalExperimental", "1"},
|
||||||
|
{"CurveSensitivity", FrogsGoMoo ? "125" : "100"},
|
||||||
|
{"CustomColors", "1"},
|
||||||
|
{"CustomIcons", "1"},
|
||||||
|
{"CustomPersonalities", "0"},
|
||||||
|
{"CustomSignals", "0"},
|
||||||
|
{"CustomSounds", "1"},
|
||||||
|
{"CustomTheme", "1"},
|
||||||
|
{"CustomUI", "0"},
|
||||||
|
{"DeviceShutdown", "1"},
|
||||||
|
{"DisableOnroadUploads", "1"},
|
||||||
|
{"DriverCamera", "0"},
|
||||||
|
{"DriveStats", "1"},
|
||||||
|
{"EVTable", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"ExperimentalModeActivation", "1"},
|
||||||
|
{"ExperimentalModeViaLKAS", "0"},
|
||||||
|
{"ExperimentalModeViaScreen", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"Fahrenheit", "0"},
|
||||||
|
{"FireTheBabysitter", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"FullMap", "0"},
|
||||||
|
{"GasRegenCmd", "0"},
|
||||||
|
{"GoatScream", "0"},
|
||||||
|
{"GreenLightAlert", "0"},
|
||||||
|
{"HideSpeed", "0"},
|
||||||
|
{"HigherBitrate", "0"},
|
||||||
|
{"LaneChangeTime", "0"},
|
||||||
|
{"LaneDetection", "1"},
|
||||||
|
{"LaneLinesWidth", "4"},
|
||||||
|
{"LateralTune", "1"},
|
||||||
|
{"LeadInfo", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"LockDoors", "0"},
|
||||||
|
{"LongitudinalTune", "1"},
|
||||||
|
{"LongPitch", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"LowerVolt", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"Model", "3"},
|
||||||
|
{"ModelUI", "1"},
|
||||||
|
{"MTSCEnabled", "1"},
|
||||||
|
{"MuteDM", FrogsGoMoo ? "1" : "1"},
|
||||||
|
{"MuteDoor", FrogsGoMoo ? "1" : "1"},
|
||||||
|
{"MuteOverheated", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"MuteSeatbelt", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"NNFF", FrogsGoMoo ? "1" : "1"},
|
||||||
|
{"NoLogging", "1"},
|
||||||
|
{"NudgelessLaneChange", "0"},
|
||||||
|
{"NumericalTemp", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"Offset1", "3"},
|
||||||
|
{"Offset2", FrogsGoMoo ? "7" : "5"},
|
||||||
|
{"Offset3", "7"},
|
||||||
|
{"Offset4", FrogsGoMoo ? "20" : "7"},
|
||||||
|
{"OneLaneChange", "1"},
|
||||||
|
{"PathEdgeWidth", "20"},
|
||||||
|
{"PathWidth", "61"},
|
||||||
|
{"PauseLateralOnSignal", "20"},
|
||||||
|
{"PreferredSchedule", "0"},
|
||||||
|
{"QOLControls", "1"},
|
||||||
|
{"QOLVisuals", "1"},
|
||||||
|
{"RandomEvents", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"RelaxedFollow", "30"},
|
||||||
|
{"RelaxedJerk", "50"},
|
||||||
|
{"ReverseCruise", "0"},
|
||||||
|
{"RoadEdgesWidth", "2"},
|
||||||
|
{"RoadNameUI", "1"},
|
||||||
|
{"RotatingWheel", "1"},
|
||||||
|
{"ScreenBrightness", "101"},
|
||||||
|
{"SearchInput", "0"},
|
||||||
|
{"ShowCPU", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"ShowFPS", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"ShowGPU", "0"},
|
||||||
|
{"ShowMemoryUsage", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"Sidebar", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"SilentMode", "0"},
|
||||||
|
{"SLCFallback", "2"},
|
||||||
|
{"SLCOverride", FrogsGoMoo ? "2" : "1"},
|
||||||
|
{"SLCPriority", "1"},
|
||||||
|
{"SmoothBraking", "1"},
|
||||||
|
{"SNGHack", FrogsGoMoo ? "0" : "1"},
|
||||||
|
{"SpeedLimitController", "1"},
|
||||||
|
{"StandardFollow", "15"},
|
||||||
|
{"StandardJerk", "10"},
|
||||||
|
{"StoppingDistance", FrogsGoMoo ? "6" : "0"},
|
||||||
|
{"TSS2Tune", "1"},
|
||||||
|
{"TurnAggressiveness", FrogsGoMoo ? "150" : "100"}, // Test 90?
|
||||||
|
{"TurnDesires", "0"},
|
||||||
|
{"UnlimitedLength", "1"},
|
||||||
|
{"UseSI", FrogsGoMoo ? "1" : "0"},
|
||||||
|
{"UseVienna", "0"},
|
||||||
|
{"VisionTurnControl", "1"},
|
||||||
|
{"WheelIcon", FrogsGoMoo ? "1" : "0"}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool rebootRequired = false;
|
||||||
|
for (const auto &[key, value] : defaultValues) {
|
||||||
|
if (params.get(key).empty()) {
|
||||||
|
params.put(key, value);
|
||||||
|
rebootRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rebootRequired) {
|
||||||
|
while (!std::filesystem::exists("/data/openpilot/prebuilt")) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
}
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
}
|
||||||
661
selfdrive/clearpilot/ui/frogpilot_functions.h
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "selfdrive/ui/qt/widgets/controls.h"
|
||||||
|
|
||||||
|
class FrogPilotConfirmationDialog : public ConfirmationDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FrogPilotConfirmationDialog(const QString &prompt_text, const QString &confirm_text,
|
||||||
|
const QString &cancel_text, const bool rich, QWidget* parent);
|
||||||
|
static bool toggle(const QString &prompt_text, const QString &confirm_text, QWidget *parent);
|
||||||
|
static bool toggleAlert(const QString &prompt_text, const QString &button_text, QWidget *parent);
|
||||||
|
static bool yesorno(const QString &prompt_text, QWidget *parent);
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotListWidget : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit FrogPilotListWidget(QWidget *parent = 0) : QWidget(parent), outer_layout(this) {
|
||||||
|
outer_layout.setMargin(0);
|
||||||
|
outer_layout.setSpacing(0);
|
||||||
|
outer_layout.addLayout(&inner_layout);
|
||||||
|
inner_layout.setMargin(0);
|
||||||
|
inner_layout.setSpacing(25); // default spacing is 25
|
||||||
|
outer_layout.addStretch();
|
||||||
|
}
|
||||||
|
inline void addItem(QWidget *w) { inner_layout.addWidget(w); }
|
||||||
|
inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); }
|
||||||
|
inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void paintEvent(QPaintEvent *) override {
|
||||||
|
QPainter p(this);
|
||||||
|
p.setPen(Qt::gray);
|
||||||
|
|
||||||
|
int visibleWidgetCount = 0;
|
||||||
|
std::vector<QRect> visibleRects;
|
||||||
|
|
||||||
|
for (int i = 0; i < inner_layout.count(); ++i) {
|
||||||
|
QWidget *widget = inner_layout.itemAt(i)->widget();
|
||||||
|
if (widget && widget->isVisible()) {
|
||||||
|
visibleWidgetCount++;
|
||||||
|
visibleRects.push_back(inner_layout.itemAt(i)->geometry());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < visibleWidgetCount - 1; ++i) {
|
||||||
|
int bottom = visibleRects[i].bottom() + inner_layout.spacing() / 2;
|
||||||
|
p.drawLine(visibleRects[i].left() + 40, bottom, visibleRects[i].right() - 40, bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QVBoxLayout outer_layout;
|
||||||
|
QVBoxLayout inner_layout;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotButtonIconControl : public AbstractControl {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrogPilotButtonIconControl(const QString &title, const QString &text, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
|
||||||
|
inline void setText(const QString &text) { btn.setText(text); }
|
||||||
|
inline QString text() const { return btn.text(); }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void clicked();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setEnabled(bool enabled) { btn.setEnabled(enabled); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPushButton btn;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotButtonParamControl : public ParamControl {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
FrogPilotButtonParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||||
|
const std::vector<QString> &button_texts, const int minimum_button_width = 225)
|
||||||
|
: ParamControl(param, title, desc, icon) {
|
||||||
|
const QString style = R"(
|
||||||
|
QPushButton {
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 500;
|
||||||
|
height:100px;
|
||||||
|
padding: 0 25 0 25;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
QPushButton:checked:enabled {
|
||||||
|
background-color: #0048FF;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
color: #33E4E4E4;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
key = param.toStdString();
|
||||||
|
int value = atoi(params.get(key).c_str());
|
||||||
|
|
||||||
|
button_group = new QButtonGroup(this);
|
||||||
|
button_group->setExclusive(true);
|
||||||
|
for (size_t i = 0; i < button_texts.size(); i++) {
|
||||||
|
QPushButton *button = new QPushButton(button_texts[i], this);
|
||||||
|
button->setCheckable(true);
|
||||||
|
button->setChecked(i == value);
|
||||||
|
button->setStyleSheet(style);
|
||||||
|
button->setMinimumWidth(minimum_button_width);
|
||||||
|
hlayout->addWidget(button);
|
||||||
|
button_group->addButton(button, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject::connect(button_group, QOverload<int, bool>::of(&QButtonGroup::buttonToggled), [=](int id, bool checked) {
|
||||||
|
if (checked) {
|
||||||
|
params.put(key, std::to_string(id));
|
||||||
|
refresh();
|
||||||
|
emit buttonClicked(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
toggle.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setEnabled(bool enable) {
|
||||||
|
for (auto btn : button_group->buttons()) {
|
||||||
|
btn->setEnabled(enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void buttonClicked(int id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string key;
|
||||||
|
Params params;
|
||||||
|
QButtonGroup *button_group;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotParamManageControl : public ParamControl {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrogPilotParamManageControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr)
|
||||||
|
: ParamControl(param, title, desc, icon, parent),
|
||||||
|
key(param.toStdString()),
|
||||||
|
manageButton(new ButtonControl(tr(""), tr("MANAGE"), tr(""))) {
|
||||||
|
hlayout->insertWidget(hlayout->indexOf(&toggle) - 1, manageButton);
|
||||||
|
|
||||||
|
connect(this, &ToggleControl::toggleFlipped, this, [this](bool state) {
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(manageButton, &ButtonControl::clicked, this, &FrogPilotParamManageControl::manageButtonClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
ParamControl::refresh();
|
||||||
|
manageButton->setVisible(params.getBool(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void showEvent(QShowEvent *event) override {
|
||||||
|
ParamControl::showEvent(event);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void manageButtonClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string key;
|
||||||
|
Params params;
|
||||||
|
ButtonControl *manageButton;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotParamToggleControl : public ParamControl {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
FrogPilotParamToggleControl(const QString ¶m, const QString &title, const QString &desc,
|
||||||
|
const QString &icon, const std::vector<QString> &button_params,
|
||||||
|
const std::vector<QString> &button_texts, QWidget *parent = nullptr,
|
||||||
|
const int minimum_button_width = 225)
|
||||||
|
: ParamControl(param, title, desc, icon, parent) {
|
||||||
|
|
||||||
|
connect(this, &ToggleControl::toggleFlipped, this, [this](bool state) {
|
||||||
|
refreshButtons(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
const QString style = R"(
|
||||||
|
QPushButton {
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 500;
|
||||||
|
height:100px;
|
||||||
|
padding: 0 25 0 25;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
QPushButton:checked:enabled {
|
||||||
|
background-color: #0048FF;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
color: #33E4E4E4;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
button_group = new QButtonGroup(this);
|
||||||
|
button_group->setExclusive(false);
|
||||||
|
|
||||||
|
std::map<QString, bool> paramState;
|
||||||
|
for (const QString &button_param : button_params) {
|
||||||
|
paramState[button_param] = params.getBool(button_param.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < button_texts.size(); ++i) {
|
||||||
|
QPushButton *button = new QPushButton(button_texts[i], this);
|
||||||
|
button->setCheckable(true);
|
||||||
|
button->setChecked(paramState[button_params[i]]);
|
||||||
|
button->setStyleSheet(style);
|
||||||
|
button->setMinimumWidth(minimum_button_width);
|
||||||
|
button_group->addButton(button, i);
|
||||||
|
|
||||||
|
connect(button, &QPushButton::clicked, [this, button_params, i](bool checked) {
|
||||||
|
params.putBool(button_params[i].toStdString(), checked);
|
||||||
|
button_group->button(i)->setChecked(checked);
|
||||||
|
emit buttonClicked(checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
hlayout->insertWidget(hlayout->indexOf(&toggle), button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void refreshButtons(bool state) {
|
||||||
|
for (QAbstractButton *button : button_group->buttons()) {
|
||||||
|
button->setVisible(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void buttonClicked(const bool checked);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Params params;
|
||||||
|
QButtonGroup *button_group;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotParamValueControl : public ParamControl {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrogPilotParamValueControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||||
|
const int &minValue, const int &maxValue, const std::map<int, QString> &valueLabels,
|
||||||
|
QWidget *parent = nullptr, const bool &loop = true, const QString &label = "", const int &division = 1)
|
||||||
|
: ParamControl(param, title, desc, icon, parent),
|
||||||
|
minValue(minValue), maxValue(maxValue), valueLabelMappings(valueLabels), loop(loop), labelText(label), division(division) {
|
||||||
|
key = param.toStdString();
|
||||||
|
|
||||||
|
valueLabel = new QLabel(this);
|
||||||
|
hlayout->addWidget(valueLabel);
|
||||||
|
|
||||||
|
QPushButton *decrementButton = createButton("-", this);
|
||||||
|
QPushButton *incrementButton = createButton("+", this);
|
||||||
|
|
||||||
|
hlayout->addWidget(decrementButton);
|
||||||
|
hlayout->addWidget(incrementButton);
|
||||||
|
|
||||||
|
connect(decrementButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
updateValue(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(incrementButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
updateValue(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
toggle.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateValue(int increment) {
|
||||||
|
value = value + increment;
|
||||||
|
|
||||||
|
if (loop) {
|
||||||
|
if (value < minValue) value = maxValue;
|
||||||
|
else if (value > maxValue) value = minValue;
|
||||||
|
} else {
|
||||||
|
value = std::max(minValue, std::min(maxValue, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
params.putInt(key, value);
|
||||||
|
refresh();
|
||||||
|
emit buttonPressed();
|
||||||
|
emit valueChanged(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
value = params.getInt(key);
|
||||||
|
|
||||||
|
QString text;
|
||||||
|
auto it = valueLabelMappings.find(value);
|
||||||
|
if (division > 1) {
|
||||||
|
text = QString::number(value / (division * 1.0), 'g');
|
||||||
|
} else {
|
||||||
|
text = it != valueLabelMappings.end() ? it->second : QString::number(value);
|
||||||
|
}
|
||||||
|
if (!labelText.isEmpty()) {
|
||||||
|
text += labelText;
|
||||||
|
}
|
||||||
|
valueLabel->setText(text);
|
||||||
|
valueLabel->setStyleSheet("QLabel { color: #E0E879; }");
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateControl(int newMinValue, int newMaxValue, const QString &newLabel, int newDivision = 1) {
|
||||||
|
minValue = newMinValue;
|
||||||
|
maxValue = newMaxValue;
|
||||||
|
labelText = newLabel;
|
||||||
|
division = newDivision;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showEvent(QShowEvent *event) override {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void buttonPressed();
|
||||||
|
void valueChanged(int value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool loop;
|
||||||
|
int division;
|
||||||
|
int maxValue;
|
||||||
|
int minValue;
|
||||||
|
int value;
|
||||||
|
QLabel *valueLabel;
|
||||||
|
QString labelText;
|
||||||
|
std::map<int, QString> valueLabelMappings;
|
||||||
|
std::string key;
|
||||||
|
Params params;
|
||||||
|
|
||||||
|
QPushButton *createButton(const QString &text, QWidget *parent) {
|
||||||
|
QPushButton *button = new QPushButton(text, parent);
|
||||||
|
button->setFixedSize(150, 100);
|
||||||
|
button->setAutoRepeat(true);
|
||||||
|
button->setAutoRepeatInterval(150);
|
||||||
|
button->setStyleSheet(R"(
|
||||||
|
QPushButton {
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 50px;
|
||||||
|
font-weight: 500;
|
||||||
|
height: 100px;
|
||||||
|
padding: 0 25 0 25;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotParamValueControlFloat : public ParamControl {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrogPilotParamValueControlFloat(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||||
|
const float &minValue, const float &maxValue, const std::map<int, QString> &valueLabels,
|
||||||
|
QWidget *parent = nullptr, const bool &loop = true, const QString &label = "", const float &division = 1.0f)
|
||||||
|
: ParamControl(param, title, desc, icon, parent),
|
||||||
|
minValue(minValue), maxValue(maxValue), valueLabelMappings(valueLabels), loop(loop), labelText(label), division(division) {
|
||||||
|
key = param.toStdString();
|
||||||
|
|
||||||
|
valueLabel = new QLabel(this);
|
||||||
|
hlayout->addWidget(valueLabel);
|
||||||
|
|
||||||
|
QPushButton *decrementButton = createButton("-", this);
|
||||||
|
QPushButton *incrementButton = createButton("+", this);
|
||||||
|
|
||||||
|
hlayout->addWidget(decrementButton);
|
||||||
|
hlayout->addWidget(incrementButton);
|
||||||
|
|
||||||
|
connect(decrementButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
updateValue(-1.0f);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(incrementButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
updateValue(1.0f);
|
||||||
|
});
|
||||||
|
|
||||||
|
toggle.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateValue(float increment) {
|
||||||
|
value += increment * 0.1f;
|
||||||
|
|
||||||
|
if (loop) {
|
||||||
|
if (value < minValue) value = maxValue;
|
||||||
|
else if (value > maxValue) value = minValue;
|
||||||
|
} else {
|
||||||
|
value = std::max(minValue, std::min(maxValue, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
params.putFloat(key, value);
|
||||||
|
refresh();
|
||||||
|
emit buttonPressed();
|
||||||
|
emit valueChanged(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
value = params.getFloat(key);
|
||||||
|
|
||||||
|
QString text;
|
||||||
|
auto it = valueLabelMappings.find(value);
|
||||||
|
if (division > 0.1f) {
|
||||||
|
text = QString::number(value, 'f', 1);
|
||||||
|
} else {
|
||||||
|
text = it != valueLabelMappings.end() ? it->second : QString::number(value, 'f', 1);
|
||||||
|
}
|
||||||
|
if (!labelText.isEmpty()) {
|
||||||
|
text += labelText;
|
||||||
|
}
|
||||||
|
valueLabel->setText(text);
|
||||||
|
valueLabel->setStyleSheet("QLabel { color: #E0E879; }");
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateControl(float newMinValue, float newMaxValue, const QString &newLabel, float newDivision = 1.0f) {
|
||||||
|
minValue = newMinValue;
|
||||||
|
maxValue = newMaxValue;
|
||||||
|
labelText = newLabel;
|
||||||
|
division = newDivision;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showEvent(QShowEvent *event) override {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void buttonPressed();
|
||||||
|
void valueChanged(float value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool loop;
|
||||||
|
float division;
|
||||||
|
float maxValue;
|
||||||
|
float minValue;
|
||||||
|
float value;
|
||||||
|
QLabel *valueLabel;
|
||||||
|
QString labelText;
|
||||||
|
std::map<int, QString> valueLabelMappings;
|
||||||
|
std::string key;
|
||||||
|
Params params;
|
||||||
|
|
||||||
|
QPushButton *createButton(const QString &text, QWidget *parent) {
|
||||||
|
QPushButton *button = new QPushButton(text, parent);
|
||||||
|
button->setFixedSize(150, 100);
|
||||||
|
button->setAutoRepeat(true);
|
||||||
|
button->setAutoRepeatInterval(150);
|
||||||
|
button->setStyleSheet(R"(
|
||||||
|
QPushButton {
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 50px;
|
||||||
|
font-weight: 500;
|
||||||
|
height: 100px;
|
||||||
|
padding: 0 25 0 25;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotDualParamControl : public QFrame {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrogPilotDualParamControl(ParamControl *control1, ParamControl *control2, QWidget *parent = nullptr, bool split=false)
|
||||||
|
: QFrame(parent) {
|
||||||
|
QHBoxLayout *hlayout = new QHBoxLayout(this);
|
||||||
|
|
||||||
|
control1->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
|
||||||
|
control1->setMaximumWidth(split ? 800 : 700);
|
||||||
|
|
||||||
|
control2->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||||
|
control2->setMaximumWidth(800);
|
||||||
|
|
||||||
|
hlayout->addWidget(control1);
|
||||||
|
hlayout->addWidget(control2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotParamValueToggleControl : public ParamControl {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrogPilotParamValueToggleControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||||
|
const int &minValue, const int &maxValue, const std::map<int, QString> &valueLabels,
|
||||||
|
QWidget *parent = nullptr, const bool &loop = true, const QString &label = "", const int &division = 1,
|
||||||
|
const std::vector<QString> &button_params = std::vector<QString>(), const std::vector<QString> &button_texts = std::vector<QString>(),
|
||||||
|
const int minimum_button_width = 225)
|
||||||
|
: ParamControl(param, title, desc, icon, parent),
|
||||||
|
minValue(minValue), maxValue(maxValue), valueLabelMappings(valueLabels), loop(loop), labelText(label), division(division) {
|
||||||
|
key = param.toStdString();
|
||||||
|
|
||||||
|
const QString style = R"(
|
||||||
|
QPushButton {
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 500;
|
||||||
|
height:100px;
|
||||||
|
padding: 0 25 0 25;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
QPushButton:checked:enabled {
|
||||||
|
background-color: #0048FF;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
color: #33E4E4E4;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
button_group = new QButtonGroup(this);
|
||||||
|
button_group->setExclusive(false);
|
||||||
|
|
||||||
|
std::map<QString, bool> paramState;
|
||||||
|
for (const QString &button_param : button_params) {
|
||||||
|
paramState[button_param] = params.getBool(button_param.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < button_texts.size(); ++i) {
|
||||||
|
QPushButton *button = new QPushButton(button_texts[i], this);
|
||||||
|
button->setCheckable(true);
|
||||||
|
button->setChecked(paramState[button_params[i]]);
|
||||||
|
button->setStyleSheet(style);
|
||||||
|
button->setMinimumWidth(minimum_button_width);
|
||||||
|
button_group->addButton(button, i);
|
||||||
|
|
||||||
|
connect(button, &QPushButton::clicked, [this, button_params, i](bool checked) {
|
||||||
|
params.putBool(button_params[i].toStdString(), checked);
|
||||||
|
button_group->button(i)->setChecked(checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
hlayout->addWidget(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
valueLabel = new QLabel(this);
|
||||||
|
hlayout->addWidget(valueLabel);
|
||||||
|
|
||||||
|
QPushButton *decrementButton = createButton("-", this);
|
||||||
|
QPushButton *incrementButton = createButton("+", this);
|
||||||
|
|
||||||
|
hlayout->addWidget(decrementButton);
|
||||||
|
hlayout->addWidget(incrementButton);
|
||||||
|
|
||||||
|
connect(decrementButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
updateValue(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(incrementButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
updateValue(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
toggle.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateValue(int increment) {
|
||||||
|
value = value + increment;
|
||||||
|
|
||||||
|
if (loop) {
|
||||||
|
if (value < minValue) value = maxValue;
|
||||||
|
else if (value > maxValue) value = minValue;
|
||||||
|
} else {
|
||||||
|
value = std::max(minValue, std::min(maxValue, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
params.putInt(key, value);
|
||||||
|
refresh();
|
||||||
|
emit buttonPressed();
|
||||||
|
emit valueChanged(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
value = params.getInt(key);
|
||||||
|
|
||||||
|
QString text;
|
||||||
|
auto it = valueLabelMappings.find(value);
|
||||||
|
if (division > 1) {
|
||||||
|
text = QString::number(value / (division * 1.0), 'g');
|
||||||
|
} else {
|
||||||
|
text = it != valueLabelMappings.end() ? it->second : QString::number(value);
|
||||||
|
}
|
||||||
|
if (!labelText.isEmpty()) {
|
||||||
|
text += labelText;
|
||||||
|
}
|
||||||
|
valueLabel->setText(text);
|
||||||
|
valueLabel->setStyleSheet("QLabel { color: #E0E879; }");
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateControl(int newMinValue, int newMaxValue, const QString &newLabel, int newDivision) {
|
||||||
|
minValue = newMinValue;
|
||||||
|
maxValue = newMaxValue;
|
||||||
|
labelText = newLabel;
|
||||||
|
division = newDivision;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showEvent(QShowEvent *event) override {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void buttonPressed();
|
||||||
|
void valueChanged(int value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool loop;
|
||||||
|
int division;
|
||||||
|
int maxValue;
|
||||||
|
int minValue;
|
||||||
|
int value;
|
||||||
|
QButtonGroup *button_group;
|
||||||
|
QLabel *valueLabel;
|
||||||
|
QString labelText;
|
||||||
|
std::map<int, QString> valueLabelMappings;
|
||||||
|
std::string key;
|
||||||
|
Params params;
|
||||||
|
|
||||||
|
QPushButton *createButton(const QString &text, QWidget *parent) {
|
||||||
|
QPushButton *button = new QPushButton(text, parent);
|
||||||
|
button->setFixedSize(150, 100);
|
||||||
|
button->setAutoRepeat(true);
|
||||||
|
button->setAutoRepeatInterval(150);
|
||||||
|
button->setStyleSheet(R"(
|
||||||
|
QPushButton {
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 50px;
|
||||||
|
font-weight: 500;
|
||||||
|
height: 100px;
|
||||||
|
padding: 0 25 0 25;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void setDefaultParams();
|
||||||
661
selfdrive/clearpilot/ui/frogpilot_functions.h.org
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "selfdrive/ui/qt/widgets/controls.h"
|
||||||
|
|
||||||
|
class FrogPilotConfirmationDialog : public ConfirmationDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FrogPilotConfirmationDialog(const QString &prompt_text, const QString &confirm_text,
|
||||||
|
const QString &cancel_text, const bool rich, QWidget* parent);
|
||||||
|
static bool toggle(const QString &prompt_text, const QString &confirm_text, QWidget *parent);
|
||||||
|
static bool toggleAlert(const QString &prompt_text, const QString &button_text, QWidget *parent);
|
||||||
|
static bool yesorno(const QString &prompt_text, QWidget *parent);
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotListWidget : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit FrogPilotListWidget(QWidget *parent = 0) : QWidget(parent), outer_layout(this) {
|
||||||
|
outer_layout.setMargin(0);
|
||||||
|
outer_layout.setSpacing(0);
|
||||||
|
outer_layout.addLayout(&inner_layout);
|
||||||
|
inner_layout.setMargin(0);
|
||||||
|
inner_layout.setSpacing(25); // default spacing is 25
|
||||||
|
outer_layout.addStretch();
|
||||||
|
}
|
||||||
|
inline void addItem(QWidget *w) { inner_layout.addWidget(w); }
|
||||||
|
inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); }
|
||||||
|
inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void paintEvent(QPaintEvent *) override {
|
||||||
|
QPainter p(this);
|
||||||
|
p.setPen(Qt::gray);
|
||||||
|
|
||||||
|
int visibleWidgetCount = 0;
|
||||||
|
std::vector<QRect> visibleRects;
|
||||||
|
|
||||||
|
for (int i = 0; i < inner_layout.count(); ++i) {
|
||||||
|
QWidget *widget = inner_layout.itemAt(i)->widget();
|
||||||
|
if (widget && widget->isVisible()) {
|
||||||
|
visibleWidgetCount++;
|
||||||
|
visibleRects.push_back(inner_layout.itemAt(i)->geometry());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < visibleWidgetCount - 1; ++i) {
|
||||||
|
int bottom = visibleRects[i].bottom() + inner_layout.spacing() / 2;
|
||||||
|
p.drawLine(visibleRects[i].left() + 40, bottom, visibleRects[i].right() - 40, bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QVBoxLayout outer_layout;
|
||||||
|
QVBoxLayout inner_layout;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotButtonIconControl : public AbstractControl {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrogPilotButtonIconControl(const QString &title, const QString &text, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
|
||||||
|
inline void setText(const QString &text) { btn.setText(text); }
|
||||||
|
inline QString text() const { return btn.text(); }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void clicked();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setEnabled(bool enabled) { btn.setEnabled(enabled); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPushButton btn;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotButtonParamControl : public ParamControl {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
FrogPilotButtonParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||||
|
const std::vector<QString> &button_texts, const int minimum_button_width = 225)
|
||||||
|
: ParamControl(param, title, desc, icon) {
|
||||||
|
const QString style = R"(
|
||||||
|
QPushButton {
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 500;
|
||||||
|
height:100px;
|
||||||
|
padding: 0 25 0 25;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
QPushButton:checked:enabled {
|
||||||
|
background-color: #0048FF;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
color: #33E4E4E4;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
key = param.toStdString();
|
||||||
|
int value = atoi(params.get(key).c_str());
|
||||||
|
|
||||||
|
button_group = new QButtonGroup(this);
|
||||||
|
button_group->setExclusive(true);
|
||||||
|
for (size_t i = 0; i < button_texts.size(); i++) {
|
||||||
|
QPushButton *button = new QPushButton(button_texts[i], this);
|
||||||
|
button->setCheckable(true);
|
||||||
|
button->setChecked(i == value);
|
||||||
|
button->setStyleSheet(style);
|
||||||
|
button->setMinimumWidth(minimum_button_width);
|
||||||
|
hlayout->addWidget(button);
|
||||||
|
button_group->addButton(button, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject::connect(button_group, QOverload<int, bool>::of(&QButtonGroup::buttonToggled), [=](int id, bool checked) {
|
||||||
|
if (checked) {
|
||||||
|
params.put(key, std::to_string(id));
|
||||||
|
refresh();
|
||||||
|
emit buttonClicked(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
toggle.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setEnabled(bool enable) {
|
||||||
|
for (auto btn : button_group->buttons()) {
|
||||||
|
btn->setEnabled(enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void buttonClicked(int id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string key;
|
||||||
|
Params params;
|
||||||
|
QButtonGroup *button_group;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotParamManageControl : public ParamControl {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrogPilotParamManageControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr)
|
||||||
|
: ParamControl(param, title, desc, icon, parent),
|
||||||
|
key(param.toStdString()),
|
||||||
|
manageButton(new ButtonControl(tr(""), tr("MANAGE"), tr(""))) {
|
||||||
|
hlayout->insertWidget(hlayout->indexOf(&toggle) - 1, manageButton);
|
||||||
|
|
||||||
|
connect(this, &ToggleControl::toggleFlipped, this, [this](bool state) {
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(manageButton, &ButtonControl::clicked, this, &FrogPilotParamManageControl::manageButtonClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
ParamControl::refresh();
|
||||||
|
manageButton->setVisible(params.getBool(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void showEvent(QShowEvent *event) override {
|
||||||
|
ParamControl::showEvent(event);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void manageButtonClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string key;
|
||||||
|
Params params;
|
||||||
|
ButtonControl *manageButton;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotParamToggleControl : public ParamControl {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
FrogPilotParamToggleControl(const QString ¶m, const QString &title, const QString &desc,
|
||||||
|
const QString &icon, const std::vector<QString> &button_params,
|
||||||
|
const std::vector<QString> &button_texts, QWidget *parent = nullptr,
|
||||||
|
const int minimum_button_width = 225)
|
||||||
|
: ParamControl(param, title, desc, icon, parent) {
|
||||||
|
|
||||||
|
connect(this, &ToggleControl::toggleFlipped, this, [this](bool state) {
|
||||||
|
refreshButtons(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
const QString style = R"(
|
||||||
|
QPushButton {
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 500;
|
||||||
|
height:100px;
|
||||||
|
padding: 0 25 0 25;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
QPushButton:checked:enabled {
|
||||||
|
background-color: #0048FF;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
color: #33E4E4E4;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
button_group = new QButtonGroup(this);
|
||||||
|
button_group->setExclusive(false);
|
||||||
|
|
||||||
|
std::map<QString, bool> paramState;
|
||||||
|
for (const QString &button_param : button_params) {
|
||||||
|
paramState[button_param] = params.getBool(button_param.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < button_texts.size(); ++i) {
|
||||||
|
QPushButton *button = new QPushButton(button_texts[i], this);
|
||||||
|
button->setCheckable(true);
|
||||||
|
button->setChecked(paramState[button_params[i]]);
|
||||||
|
button->setStyleSheet(style);
|
||||||
|
button->setMinimumWidth(minimum_button_width);
|
||||||
|
button_group->addButton(button, i);
|
||||||
|
|
||||||
|
connect(button, &QPushButton::clicked, [this, button_params, i](bool checked) {
|
||||||
|
params.putBool(button_params[i].toStdString(), checked);
|
||||||
|
button_group->button(i)->setChecked(checked);
|
||||||
|
emit buttonClicked(checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
hlayout->insertWidget(hlayout->indexOf(&toggle), button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void refreshButtons(bool state) {
|
||||||
|
for (QAbstractButton *button : button_group->buttons()) {
|
||||||
|
button->setVisible(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void buttonClicked(const bool checked);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Params params;
|
||||||
|
QButtonGroup *button_group;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotParamValueControl : public ParamControl {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrogPilotParamValueControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||||
|
const int &minValue, const int &maxValue, const std::map<int, QString> &valueLabels,
|
||||||
|
QWidget *parent = nullptr, const bool &loop = true, const QString &label = "", const int &division = 1)
|
||||||
|
: ParamControl(param, title, desc, icon, parent),
|
||||||
|
minValue(minValue), maxValue(maxValue), valueLabelMappings(valueLabels), loop(loop), labelText(label), division(division) {
|
||||||
|
key = param.toStdString();
|
||||||
|
|
||||||
|
valueLabel = new QLabel(this);
|
||||||
|
hlayout->addWidget(valueLabel);
|
||||||
|
|
||||||
|
QPushButton *decrementButton = createButton("-", this);
|
||||||
|
QPushButton *incrementButton = createButton("+", this);
|
||||||
|
|
||||||
|
hlayout->addWidget(decrementButton);
|
||||||
|
hlayout->addWidget(incrementButton);
|
||||||
|
|
||||||
|
connect(decrementButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
updateValue(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(incrementButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
updateValue(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
toggle.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateValue(int increment) {
|
||||||
|
value = value + increment;
|
||||||
|
|
||||||
|
if (loop) {
|
||||||
|
if (value < minValue) value = maxValue;
|
||||||
|
else if (value > maxValue) value = minValue;
|
||||||
|
} else {
|
||||||
|
value = std::max(minValue, std::min(maxValue, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
params.putInt(key, value);
|
||||||
|
refresh();
|
||||||
|
emit buttonPressed();
|
||||||
|
emit valueChanged(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
value = params.getInt(key);
|
||||||
|
|
||||||
|
QString text;
|
||||||
|
auto it = valueLabelMappings.find(value);
|
||||||
|
if (division > 1) {
|
||||||
|
text = QString::number(value / (division * 1.0), 'g');
|
||||||
|
} else {
|
||||||
|
text = it != valueLabelMappings.end() ? it->second : QString::number(value);
|
||||||
|
}
|
||||||
|
if (!labelText.isEmpty()) {
|
||||||
|
text += labelText;
|
||||||
|
}
|
||||||
|
valueLabel->setText(text);
|
||||||
|
valueLabel->setStyleSheet("QLabel { color: #E0E879; }");
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateControl(int newMinValue, int newMaxValue, const QString &newLabel, int newDivision = 1) {
|
||||||
|
minValue = newMinValue;
|
||||||
|
maxValue = newMaxValue;
|
||||||
|
labelText = newLabel;
|
||||||
|
division = newDivision;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showEvent(QShowEvent *event) override {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void buttonPressed();
|
||||||
|
void valueChanged(int value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool loop;
|
||||||
|
int division;
|
||||||
|
int maxValue;
|
||||||
|
int minValue;
|
||||||
|
int value;
|
||||||
|
QLabel *valueLabel;
|
||||||
|
QString labelText;
|
||||||
|
std::map<int, QString> valueLabelMappings;
|
||||||
|
std::string key;
|
||||||
|
Params params;
|
||||||
|
|
||||||
|
QPushButton *createButton(const QString &text, QWidget *parent) {
|
||||||
|
QPushButton *button = new QPushButton(text, parent);
|
||||||
|
button->setFixedSize(150, 100);
|
||||||
|
button->setAutoRepeat(true);
|
||||||
|
button->setAutoRepeatInterval(150);
|
||||||
|
button->setStyleSheet(R"(
|
||||||
|
QPushButton {
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 50px;
|
||||||
|
font-weight: 500;
|
||||||
|
height: 100px;
|
||||||
|
padding: 0 25 0 25;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotParamValueControlFloat : public ParamControl {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrogPilotParamValueControlFloat(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||||
|
const float &minValue, const float &maxValue, const std::map<int, QString> &valueLabels,
|
||||||
|
QWidget *parent = nullptr, const bool &loop = true, const QString &label = "", const float &division = 1.0f)
|
||||||
|
: ParamControl(param, title, desc, icon, parent),
|
||||||
|
minValue(minValue), maxValue(maxValue), valueLabelMappings(valueLabels), loop(loop), labelText(label), division(division) {
|
||||||
|
key = param.toStdString();
|
||||||
|
|
||||||
|
valueLabel = new QLabel(this);
|
||||||
|
hlayout->addWidget(valueLabel);
|
||||||
|
|
||||||
|
QPushButton *decrementButton = createButton("-", this);
|
||||||
|
QPushButton *incrementButton = createButton("+", this);
|
||||||
|
|
||||||
|
hlayout->addWidget(decrementButton);
|
||||||
|
hlayout->addWidget(incrementButton);
|
||||||
|
|
||||||
|
connect(decrementButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
updateValue(-1.0f);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(incrementButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
updateValue(1.0f);
|
||||||
|
});
|
||||||
|
|
||||||
|
toggle.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateValue(float increment) {
|
||||||
|
value += increment * 0.1f;
|
||||||
|
|
||||||
|
if (loop) {
|
||||||
|
if (value < minValue) value = maxValue;
|
||||||
|
else if (value > maxValue) value = minValue;
|
||||||
|
} else {
|
||||||
|
value = std::max(minValue, std::min(maxValue, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
params.putFloat(key, value);
|
||||||
|
refresh();
|
||||||
|
emit buttonPressed();
|
||||||
|
emit valueChanged(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
value = params.getFloat(key);
|
||||||
|
|
||||||
|
QString text;
|
||||||
|
auto it = valueLabelMappings.find(value);
|
||||||
|
if (division > 0.1f) {
|
||||||
|
text = QString::number(value, 'f', 1);
|
||||||
|
} else {
|
||||||
|
text = it != valueLabelMappings.end() ? it->second : QString::number(value, 'f', 1);
|
||||||
|
}
|
||||||
|
if (!labelText.isEmpty()) {
|
||||||
|
text += labelText;
|
||||||
|
}
|
||||||
|
valueLabel->setText(text);
|
||||||
|
valueLabel->setStyleSheet("QLabel { color: #E0E879; }");
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateControl(float newMinValue, float newMaxValue, const QString &newLabel, float newDivision = 1.0f) {
|
||||||
|
minValue = newMinValue;
|
||||||
|
maxValue = newMaxValue;
|
||||||
|
labelText = newLabel;
|
||||||
|
division = newDivision;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showEvent(QShowEvent *event) override {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void buttonPressed();
|
||||||
|
void valueChanged(float value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool loop;
|
||||||
|
float division;
|
||||||
|
float maxValue;
|
||||||
|
float minValue;
|
||||||
|
float value;
|
||||||
|
QLabel *valueLabel;
|
||||||
|
QString labelText;
|
||||||
|
std::map<int, QString> valueLabelMappings;
|
||||||
|
std::string key;
|
||||||
|
Params params;
|
||||||
|
|
||||||
|
QPushButton *createButton(const QString &text, QWidget *parent) {
|
||||||
|
QPushButton *button = new QPushButton(text, parent);
|
||||||
|
button->setFixedSize(150, 100);
|
||||||
|
button->setAutoRepeat(true);
|
||||||
|
button->setAutoRepeatInterval(150);
|
||||||
|
button->setStyleSheet(R"(
|
||||||
|
QPushButton {
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 50px;
|
||||||
|
font-weight: 500;
|
||||||
|
height: 100px;
|
||||||
|
padding: 0 25 0 25;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotDualParamControl : public QFrame {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrogPilotDualParamControl(ParamControl *control1, ParamControl *control2, QWidget *parent = nullptr, bool split=false)
|
||||||
|
: QFrame(parent) {
|
||||||
|
QHBoxLayout *hlayout = new QHBoxLayout(this);
|
||||||
|
|
||||||
|
control1->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
|
||||||
|
control1->setMaximumWidth(split ? 800 : 700);
|
||||||
|
|
||||||
|
control2->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||||
|
control2->setMaximumWidth(800);
|
||||||
|
|
||||||
|
hlayout->addWidget(control1);
|
||||||
|
hlayout->addWidget(control2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrogPilotParamValueToggleControl : public ParamControl {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrogPilotParamValueToggleControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||||
|
const int &minValue, const int &maxValue, const std::map<int, QString> &valueLabels,
|
||||||
|
QWidget *parent = nullptr, const bool &loop = true, const QString &label = "", const int &division = 1,
|
||||||
|
const std::vector<QString> &button_params = std::vector<QString>(), const std::vector<QString> &button_texts = std::vector<QString>(),
|
||||||
|
const int minimum_button_width = 225)
|
||||||
|
: ParamControl(param, title, desc, icon, parent),
|
||||||
|
minValue(minValue), maxValue(maxValue), valueLabelMappings(valueLabels), loop(loop), labelText(label), division(division) {
|
||||||
|
key = param.toStdString();
|
||||||
|
|
||||||
|
const QString style = R"(
|
||||||
|
QPushButton {
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 500;
|
||||||
|
height:100px;
|
||||||
|
padding: 0 25 0 25;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
QPushButton:checked:enabled {
|
||||||
|
background-color: #0048FF;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
color: #33E4E4E4;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
button_group = new QButtonGroup(this);
|
||||||
|
button_group->setExclusive(false);
|
||||||
|
|
||||||
|
std::map<QString, bool> paramState;
|
||||||
|
for (const QString &button_param : button_params) {
|
||||||
|
paramState[button_param] = params.getBool(button_param.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < button_texts.size(); ++i) {
|
||||||
|
QPushButton *button = new QPushButton(button_texts[i], this);
|
||||||
|
button->setCheckable(true);
|
||||||
|
button->setChecked(paramState[button_params[i]]);
|
||||||
|
button->setStyleSheet(style);
|
||||||
|
button->setMinimumWidth(minimum_button_width);
|
||||||
|
button_group->addButton(button, i);
|
||||||
|
|
||||||
|
connect(button, &QPushButton::clicked, [this, button_params, i](bool checked) {
|
||||||
|
params.putBool(button_params[i].toStdString(), checked);
|
||||||
|
button_group->button(i)->setChecked(checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
hlayout->addWidget(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
valueLabel = new QLabel(this);
|
||||||
|
hlayout->addWidget(valueLabel);
|
||||||
|
|
||||||
|
QPushButton *decrementButton = createButton("-", this);
|
||||||
|
QPushButton *incrementButton = createButton("+", this);
|
||||||
|
|
||||||
|
hlayout->addWidget(decrementButton);
|
||||||
|
hlayout->addWidget(incrementButton);
|
||||||
|
|
||||||
|
connect(decrementButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
updateValue(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(incrementButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
updateValue(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
toggle.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateValue(int increment) {
|
||||||
|
value = value + increment;
|
||||||
|
|
||||||
|
if (loop) {
|
||||||
|
if (value < minValue) value = maxValue;
|
||||||
|
else if (value > maxValue) value = minValue;
|
||||||
|
} else {
|
||||||
|
value = std::max(minValue, std::min(maxValue, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
params.putInt(key, value);
|
||||||
|
refresh();
|
||||||
|
emit buttonPressed();
|
||||||
|
emit valueChanged(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
value = params.getInt(key);
|
||||||
|
|
||||||
|
QString text;
|
||||||
|
auto it = valueLabelMappings.find(value);
|
||||||
|
if (division > 1) {
|
||||||
|
text = QString::number(value / (division * 1.0), 'g');
|
||||||
|
} else {
|
||||||
|
text = it != valueLabelMappings.end() ? it->second : QString::number(value);
|
||||||
|
}
|
||||||
|
if (!labelText.isEmpty()) {
|
||||||
|
text += labelText;
|
||||||
|
}
|
||||||
|
valueLabel->setText(text);
|
||||||
|
valueLabel->setStyleSheet("QLabel { color: #E0E879; }");
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateControl(int newMinValue, int newMaxValue, const QString &newLabel, int newDivision) {
|
||||||
|
minValue = newMinValue;
|
||||||
|
maxValue = newMaxValue;
|
||||||
|
labelText = newLabel;
|
||||||
|
division = newDivision;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showEvent(QShowEvent *event) override {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void buttonPressed();
|
||||||
|
void valueChanged(int value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool loop;
|
||||||
|
int division;
|
||||||
|
int maxValue;
|
||||||
|
int minValue;
|
||||||
|
int value;
|
||||||
|
QButtonGroup *button_group;
|
||||||
|
QLabel *valueLabel;
|
||||||
|
QString labelText;
|
||||||
|
std::map<int, QString> valueLabelMappings;
|
||||||
|
std::string key;
|
||||||
|
Params params;
|
||||||
|
|
||||||
|
QPushButton *createButton(const QString &text, QWidget *parent) {
|
||||||
|
QPushButton *button = new QPushButton(text, parent);
|
||||||
|
button->setFixedSize(150, 100);
|
||||||
|
button->setAutoRepeat(true);
|
||||||
|
button->setAutoRepeatInterval(150);
|
||||||
|
button->setStyleSheet(R"(
|
||||||
|
QPushButton {
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 50px;
|
||||||
|
font-weight: 500;
|
||||||
|
height: 100px;
|
||||||
|
padding: 0 25 0 25;
|
||||||
|
color: #E4E4E4;
|
||||||
|
background-color: #393939;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void setDefaultParams();
|
||||||
185
selfdrive/clearpilot/ui/vehicle_settings.cc
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
#include <QDir>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/vehicle_settings.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
|
QStringList getCarNames(const QString &carMake) {
|
||||||
|
QMap<QString, QString> makeMap;
|
||||||
|
makeMap["acura"] = "honda";
|
||||||
|
makeMap["audi"] = "volkswagen";
|
||||||
|
makeMap["buick"] = "gm";
|
||||||
|
makeMap["cadillac"] = "gm";
|
||||||
|
makeMap["chevrolet"] = "gm";
|
||||||
|
makeMap["chrysler"] = "chrysler";
|
||||||
|
makeMap["dodge"] = "chrysler";
|
||||||
|
makeMap["ford"] = "ford";
|
||||||
|
makeMap["gm"] = "gm";
|
||||||
|
makeMap["gmc"] = "gm";
|
||||||
|
makeMap["genesis"] = "hyundai";
|
||||||
|
makeMap["honda"] = "honda";
|
||||||
|
makeMap["hyundai"] = "hyundai";
|
||||||
|
makeMap["infiniti"] = "nissan";
|
||||||
|
makeMap["jeep"] = "chrysler";
|
||||||
|
makeMap["kia"] = "hyundai";
|
||||||
|
makeMap["lexus"] = "toyota";
|
||||||
|
makeMap["lincoln"] = "ford";
|
||||||
|
makeMap["man"] = "volkswagen";
|
||||||
|
makeMap["mazda"] = "mazda";
|
||||||
|
makeMap["nissan"] = "nissan";
|
||||||
|
makeMap["ram"] = "chrysler";
|
||||||
|
makeMap["seat"] = "volkswagen";
|
||||||
|
makeMap["subaru"] = "subaru";
|
||||||
|
makeMap["tesla"] = "tesla";
|
||||||
|
makeMap["toyota"] = "toyota";
|
||||||
|
makeMap["volkswagen"] = "volkswagen";
|
||||||
|
makeMap["skoda"] = "volkswagen";
|
||||||
|
|
||||||
|
QString dirPath = "../../selfdrive/car";
|
||||||
|
QDir dir(dirPath);
|
||||||
|
QString targetFolder = makeMap.value(carMake, carMake);
|
||||||
|
QStringList names;
|
||||||
|
|
||||||
|
foreach (const QString &folder, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
||||||
|
if (folder == targetFolder) {
|
||||||
|
QFile file(dirPath + "/" + folder + "/values.py");
|
||||||
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
QRegularExpression regex("class CAR\\(StrEnum\\):([\\s\\S]*?)(?=^\\w)", QRegularExpression::MultilineOption);
|
||||||
|
QRegularExpressionMatch match = regex.match(QTextStream(&file).readAll());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (match.hasMatch()) {
|
||||||
|
QRegularExpression nameRegex("=\\s*\"([^\"]+)\"");
|
||||||
|
QRegularExpressionMatchIterator it = nameRegex.globalMatch(match.captured(1));
|
||||||
|
while (it.hasNext()) {
|
||||||
|
names << it.next().captured(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(names.begin(), names.end());
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
FrogPilotVehiclesPanel::FrogPilotVehiclesPanel(SettingsWindow *parent) : FrogPilotListWidget(parent) {
|
||||||
|
selectMakeButton = new ButtonControl(tr("Select Make"), tr("SELECT"));
|
||||||
|
QObject::connect(selectMakeButton, &ButtonControl::clicked, [this]() {
|
||||||
|
std::string currentMake = params.get("CarMake");
|
||||||
|
QStringList makes = {
|
||||||
|
"Acura", "Audi", "BMW", "Buick", "Cadillac", "Chevrolet", "Chrysler", "Dodge", "Ford", "GM", "GMC",
|
||||||
|
"Genesis", "Honda", "Hyundai", "Infiniti", "Jeep", "Kia", "Lexus", "Lincoln", "MAN", "Mazda",
|
||||||
|
"Mercedes", "Nissan", "Ram", "SEAT", "Subaru", "Tesla", "Toyota", "Volkswagen", "Volvo", "Škoda",
|
||||||
|
};
|
||||||
|
|
||||||
|
QString newMakeSelection = MultiOptionDialog::getSelection(tr("Select a Make"), makes, QString::fromStdString(currentMake), this);
|
||||||
|
if (!newMakeSelection.isEmpty()) {
|
||||||
|
carMake = newMakeSelection;
|
||||||
|
params.put("CarMake", carMake.toStdString());
|
||||||
|
selectMakeButton->setValue(newMakeSelection);
|
||||||
|
setModels();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addItem(selectMakeButton);
|
||||||
|
|
||||||
|
selectModelButton = new ButtonControl(tr("Select Model"), tr("SELECT"));
|
||||||
|
QString modelSelection = QString::fromStdString(params.get("CarModel"));
|
||||||
|
QObject::connect(selectModelButton, &ButtonControl::clicked, [this]() {
|
||||||
|
std::string currentModel = params.get("CarModel");
|
||||||
|
QString newModelSelection = MultiOptionDialog::getSelection(tr("Select a Model"), models, QString::fromStdString(currentModel), this);
|
||||||
|
if (!newModelSelection.isEmpty()) {
|
||||||
|
params.put("CarModel", newModelSelection.toStdString());
|
||||||
|
selectModelButton->setValue(newModelSelection);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
selectModelButton->setValue(modelSelection);
|
||||||
|
addItem(selectModelButton);
|
||||||
|
selectModelButton->setVisible(false);
|
||||||
|
|
||||||
|
std::vector<std::tuple<QString, QString, QString, QString>> vehicleToggles {
|
||||||
|
{"EVTable", "EV Lookup Tables", "Smoothen out the gas and brake controls for EV vehicles.", ""},
|
||||||
|
{"GasRegenCmd", "Gas Regen Cmd", "", ""},
|
||||||
|
{"LongPitch", "Long Pitch Compensation", "Reduce speed and acceleration error for greater passenger comfort and improved vehicle efficiency.", ""},
|
||||||
|
{"LowerVolt", "Lower Volt Enable Speed", "Lower the Volt's minimum enable speed to enable openpilot at any speed.", ""},
|
||||||
|
|
||||||
|
{"LockDoors", "Lock Doors In Drive", "Automatically lock the doors when in drive and unlock when in park.", ""},
|
||||||
|
{"SNGHack", "Stop and Go Hack", "Enable the 'Stop and Go' hack for vehicles without stock stop and go functionality.", ""},
|
||||||
|
{"TSS2Tune", "TSS2 Tune", "Tuning profile based on the tuning profile from DragonPilot for TSS2 vehicles.", ""}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto &[param, title, desc, icon] : vehicleToggles) {
|
||||||
|
ParamControl *toggle = new ParamControl(param, title, desc, icon, this);
|
||||||
|
|
||||||
|
addItem(toggle);
|
||||||
|
toggle->setVisible(false);
|
||||||
|
toggles[param.toStdString()] = toggle;
|
||||||
|
|
||||||
|
QObject::connect(toggle, &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
gmKeys = {"EVTable", "GasRegenCmd", "LongPitch", "LowerVolt"};
|
||||||
|
toyotaKeys = {"LockDoors", "SNGHack", "TSS2Tune"};
|
||||||
|
|
||||||
|
std::set<std::string> rebootKeys = {"EVTable", "GasRegenCmd", "LongPitch", "LowerVolt", "TSS2Tune"};
|
||||||
|
for (const std::string &key : rebootKeys) {
|
||||||
|
QObject::connect(toggles[key], &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) {
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject::connect(uiState(), &UIState::offroadTransition, this, [this](bool offroad) {
|
||||||
|
if (!offroad) {
|
||||||
|
std::thread([this]() {
|
||||||
|
while (carMake.isEmpty()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
carMake = QString::fromStdString(params.get("CarMake"));
|
||||||
|
}
|
||||||
|
setModels();
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
carMake = QString::fromStdString(params.get("CarMake"));
|
||||||
|
if (!carMake.isEmpty()) {
|
||||||
|
setModels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVehiclesPanel::updateToggles() {
|
||||||
|
std::thread([this]() {
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", true);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", false);
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVehiclesPanel::setModels() {
|
||||||
|
models = getCarNames(carMake.toLower());
|
||||||
|
setToggles();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVehiclesPanel::setToggles() {
|
||||||
|
selectMakeButton->setValue(carMake);
|
||||||
|
selectModelButton->setVisible(!carMake.isEmpty());
|
||||||
|
|
||||||
|
bool gm = carMake == "Buick" || carMake == "Cadillac" || carMake == "Chevrolet" || carMake == "GM" || carMake == "GMC";
|
||||||
|
bool toyota = carMake == "Lexus" || carMake == "Toyota";
|
||||||
|
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(false);
|
||||||
|
|
||||||
|
if (gm) {
|
||||||
|
toggle->setVisible(gmKeys.find(key.c_str()) != gmKeys.end());
|
||||||
|
} else if (toyota) {
|
||||||
|
toggle->setVisible(toyotaKeys.find(key.c_str()) != toyotaKeys.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
185
selfdrive/clearpilot/ui/vehicle_settings.cc.org
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
#include <QDir>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/vehicle_settings.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
|
QStringList getCarNames(const QString &carMake) {
|
||||||
|
QMap<QString, QString> makeMap;
|
||||||
|
makeMap["acura"] = "honda";
|
||||||
|
makeMap["audi"] = "volkswagen";
|
||||||
|
makeMap["buick"] = "gm";
|
||||||
|
makeMap["cadillac"] = "gm";
|
||||||
|
makeMap["chevrolet"] = "gm";
|
||||||
|
makeMap["chrysler"] = "chrysler";
|
||||||
|
makeMap["dodge"] = "chrysler";
|
||||||
|
makeMap["ford"] = "ford";
|
||||||
|
makeMap["gm"] = "gm";
|
||||||
|
makeMap["gmc"] = "gm";
|
||||||
|
makeMap["genesis"] = "hyundai";
|
||||||
|
makeMap["honda"] = "honda";
|
||||||
|
makeMap["hyundai"] = "hyundai";
|
||||||
|
makeMap["infiniti"] = "nissan";
|
||||||
|
makeMap["jeep"] = "chrysler";
|
||||||
|
makeMap["kia"] = "hyundai";
|
||||||
|
makeMap["lexus"] = "toyota";
|
||||||
|
makeMap["lincoln"] = "ford";
|
||||||
|
makeMap["man"] = "volkswagen";
|
||||||
|
makeMap["mazda"] = "mazda";
|
||||||
|
makeMap["nissan"] = "nissan";
|
||||||
|
makeMap["ram"] = "chrysler";
|
||||||
|
makeMap["seat"] = "volkswagen";
|
||||||
|
makeMap["subaru"] = "subaru";
|
||||||
|
makeMap["tesla"] = "tesla";
|
||||||
|
makeMap["toyota"] = "toyota";
|
||||||
|
makeMap["volkswagen"] = "volkswagen";
|
||||||
|
makeMap["skoda"] = "volkswagen";
|
||||||
|
|
||||||
|
QString dirPath = "../../selfdrive/car";
|
||||||
|
QDir dir(dirPath);
|
||||||
|
QString targetFolder = makeMap.value(carMake, carMake);
|
||||||
|
QStringList names;
|
||||||
|
|
||||||
|
foreach (const QString &folder, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
||||||
|
if (folder == targetFolder) {
|
||||||
|
QFile file(dirPath + "/" + folder + "/values.py");
|
||||||
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
QRegularExpression regex("class CAR\\(StrEnum\\):([\\s\\S]*?)(?=^\\w)", QRegularExpression::MultilineOption);
|
||||||
|
QRegularExpressionMatch match = regex.match(QTextStream(&file).readAll());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (match.hasMatch()) {
|
||||||
|
QRegularExpression nameRegex("=\\s*\"([^\"]+)\"");
|
||||||
|
QRegularExpressionMatchIterator it = nameRegex.globalMatch(match.captured(1));
|
||||||
|
while (it.hasNext()) {
|
||||||
|
names << it.next().captured(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(names.begin(), names.end());
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
FrogPilotVehiclesPanel::FrogPilotVehiclesPanel(SettingsWindow *parent) : FrogPilotListWidget(parent) {
|
||||||
|
selectMakeButton = new ButtonControl(tr("Select Make"), tr("SELECT"));
|
||||||
|
QObject::connect(selectMakeButton, &ButtonControl::clicked, [this]() {
|
||||||
|
std::string currentMake = params.get("CarMake");
|
||||||
|
QStringList makes = {
|
||||||
|
"Acura", "Audi", "BMW", "Buick", "Cadillac", "Chevrolet", "Chrysler", "Dodge", "Ford", "GM", "GMC",
|
||||||
|
"Genesis", "Honda", "Hyundai", "Infiniti", "Jeep", "Kia", "Lexus", "Lincoln", "MAN", "Mazda",
|
||||||
|
"Mercedes", "Nissan", "Ram", "SEAT", "Subaru", "Tesla", "Toyota", "Volkswagen", "Volvo", "Škoda",
|
||||||
|
};
|
||||||
|
|
||||||
|
QString newMakeSelection = MultiOptionDialog::getSelection(tr("Select a Make"), makes, QString::fromStdString(currentMake), this);
|
||||||
|
if (!newMakeSelection.isEmpty()) {
|
||||||
|
carMake = newMakeSelection;
|
||||||
|
params.put("CarMake", carMake.toStdString());
|
||||||
|
selectMakeButton->setValue(newMakeSelection);
|
||||||
|
setModels();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addItem(selectMakeButton);
|
||||||
|
|
||||||
|
selectModelButton = new ButtonControl(tr("Select Model"), tr("SELECT"));
|
||||||
|
QString modelSelection = QString::fromStdString(params.get("CarModel"));
|
||||||
|
QObject::connect(selectModelButton, &ButtonControl::clicked, [this]() {
|
||||||
|
std::string currentModel = params.get("CarModel");
|
||||||
|
QString newModelSelection = MultiOptionDialog::getSelection(tr("Select a Model"), models, QString::fromStdString(currentModel), this);
|
||||||
|
if (!newModelSelection.isEmpty()) {
|
||||||
|
params.put("CarModel", newModelSelection.toStdString());
|
||||||
|
selectModelButton->setValue(newModelSelection);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
selectModelButton->setValue(modelSelection);
|
||||||
|
addItem(selectModelButton);
|
||||||
|
selectModelButton->setVisible(false);
|
||||||
|
|
||||||
|
std::vector<std::tuple<QString, QString, QString, QString>> vehicleToggles {
|
||||||
|
{"EVTable", "EV Lookup Tables", "Smoothen out the gas and brake controls for EV vehicles.", ""},
|
||||||
|
{"GasRegenCmd", "Gas Regen Cmd", "", ""},
|
||||||
|
{"LongPitch", "Long Pitch Compensation", "Reduce speed and acceleration error for greater passenger comfort and improved vehicle efficiency.", ""},
|
||||||
|
{"LowerVolt", "Lower Volt Enable Speed", "Lower the Volt's minimum enable speed to enable openpilot at any speed.", ""},
|
||||||
|
|
||||||
|
{"LockDoors", "Lock Doors In Drive", "Automatically lock the doors when in drive and unlock when in park.", ""},
|
||||||
|
{"SNGHack", "Stop and Go Hack", "Enable the 'Stop and Go' hack for vehicles without stock stop and go functionality.", ""},
|
||||||
|
{"TSS2Tune", "TSS2 Tune", "Tuning profile based on the tuning profile from DragonPilot for TSS2 vehicles.", ""}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto &[param, title, desc, icon] : vehicleToggles) {
|
||||||
|
ParamControl *toggle = new ParamControl(param, title, desc, icon, this);
|
||||||
|
|
||||||
|
addItem(toggle);
|
||||||
|
toggle->setVisible(false);
|
||||||
|
toggles[param.toStdString()] = toggle;
|
||||||
|
|
||||||
|
QObject::connect(toggle, &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
gmKeys = {"EVTable", "GasRegenCmd", "LongPitch", "LowerVolt"};
|
||||||
|
toyotaKeys = {"LockDoors", "SNGHack", "TSS2Tune"};
|
||||||
|
|
||||||
|
std::set<std::string> rebootKeys = {"EVTable", "GasRegenCmd", "LongPitch", "LowerVolt", "TSS2Tune"};
|
||||||
|
for (const std::string &key : rebootKeys) {
|
||||||
|
QObject::connect(toggles[key], &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) {
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject::connect(uiState(), &UIState::offroadTransition, this, [this](bool offroad) {
|
||||||
|
if (!offroad) {
|
||||||
|
std::thread([this]() {
|
||||||
|
while (carMake.isEmpty()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
carMake = QString::fromStdString(params.get("CarMake"));
|
||||||
|
}
|
||||||
|
setModels();
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
carMake = QString::fromStdString(params.get("CarMake"));
|
||||||
|
if (!carMake.isEmpty()) {
|
||||||
|
setModels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVehiclesPanel::updateToggles() {
|
||||||
|
std::thread([this]() {
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", true);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", false);
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVehiclesPanel::setModels() {
|
||||||
|
models = getCarNames(carMake.toLower());
|
||||||
|
setToggles();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVehiclesPanel::setToggles() {
|
||||||
|
selectMakeButton->setValue(carMake);
|
||||||
|
selectModelButton->setVisible(!carMake.isEmpty());
|
||||||
|
|
||||||
|
bool gm = carMake == "Buick" || carMake == "Cadillac" || carMake == "Chevrolet" || carMake == "GM" || carMake == "GMC";
|
||||||
|
bool toyota = carMake == "Lexus" || carMake == "Toyota";
|
||||||
|
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(false);
|
||||||
|
|
||||||
|
if (gm) {
|
||||||
|
toggle->setVisible(gmKeys.find(key.c_str()) != gmKeys.end());
|
||||||
|
} else if (toyota) {
|
||||||
|
toggle->setVisible(toyotaKeys.find(key.c_str()) != toyotaKeys.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
34
selfdrive/clearpilot/ui/vehicle_settings.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/frogpilot_functions.h"
|
||||||
|
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||||
|
|
||||||
|
class FrogPilotVehiclesPanel : public FrogPilotListWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FrogPilotVehiclesPanel(SettingsWindow *parent);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setModels();
|
||||||
|
void setToggles();
|
||||||
|
void updateToggles();
|
||||||
|
|
||||||
|
ButtonControl *selectMakeButton;
|
||||||
|
ButtonControl *selectModelButton;
|
||||||
|
|
||||||
|
QString carMake;
|
||||||
|
QStringList models;
|
||||||
|
|
||||||
|
std::map<std::string, ParamControl*> toggles;
|
||||||
|
|
||||||
|
std::set<QString> gmKeys;
|
||||||
|
std::set<QString> toyotaKeys;
|
||||||
|
|
||||||
|
Params params;
|
||||||
|
Params paramsMemory{"/dev/shm/params"};
|
||||||
|
};
|
||||||
34
selfdrive/clearpilot/ui/vehicle_settings.h.org
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/frogpilot_functions.h"
|
||||||
|
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||||
|
|
||||||
|
class FrogPilotVehiclesPanel : public FrogPilotListWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FrogPilotVehiclesPanel(SettingsWindow *parent);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setModels();
|
||||||
|
void setToggles();
|
||||||
|
void updateToggles();
|
||||||
|
|
||||||
|
ButtonControl *selectMakeButton;
|
||||||
|
ButtonControl *selectModelButton;
|
||||||
|
|
||||||
|
QString carMake;
|
||||||
|
QStringList models;
|
||||||
|
|
||||||
|
std::map<std::string, ParamControl*> toggles;
|
||||||
|
|
||||||
|
std::set<QString> gmKeys;
|
||||||
|
std::set<QString> toyotaKeys;
|
||||||
|
|
||||||
|
Params params;
|
||||||
|
Params paramsMemory{"/dev/shm/params"};
|
||||||
|
};
|
||||||
242
selfdrive/clearpilot/ui/visual_settings.cc
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
#include "selfdrive/frogpilot/ui/visual_settings.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
|
FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilotListWidget(parent) {
|
||||||
|
const std::vector<std::tuple<QString, QString, QString, QString>> visualToggles {
|
||||||
|
{"CustomTheme", "Custom Themes", "Enable the ability to use custom themes.", "../frogpilot/assets/wheel_images/frog.png"},
|
||||||
|
{"CustomColors", "Color Theme", "Switch out the standard openpilot color scheme with a custom color scheme.\n\nWant to submit your own color scheme? Post it in the 'feature-request' channel in the FrogPilot Discord!", ""},
|
||||||
|
{"CustomIcons", "Icon Pack", "Switch out the standard openpilot icons with a set of custom icons.\n\nWant to submit your own icon pack? Post it in the 'feature-request' channel in the FrogPilot Discord!", ""},
|
||||||
|
{"CustomSignals", "Turn Signals", "Add custom animations for your turn signals for a personal touch!\n\nWant to submit your own turn signal animation? Post it in the 'feature-request' channel in the FrogPilot Discord!", ""},
|
||||||
|
{"CustomSounds", "Sound Pack", "Switch out the standard openpilot sounds with a set of custom sounds.\n\nWant to submit your own sound pack? Post it in the 'feature-request' channel in the FrogPilot Discord!", ""},
|
||||||
|
|
||||||
|
{"CameraView", "Camera View", "Choose your preferred camera view for the onroad UI. This is a visual change only and doesn't impact openpilot.", "../frogpilot/assets/toggle_icons/icon_camera.png"},
|
||||||
|
{"Compass", "Compass", "Add a compass to your onroad UI.", "../frogpilot/assets/toggle_icons/icon_compass.png"},
|
||||||
|
|
||||||
|
{"CustomUI", "Custom Onroad UI", "Customize the Onroad UI with some additional visual functions.", "../assets/offroad/icon_road.png"},
|
||||||
|
{"AdjacentPath", "Adjacent Paths", "Display paths to the left and right of your car, visualizing where the model detects lanes.", ""},
|
||||||
|
{"BlindSpotPath", "Blind Spot Path", "Visualize your blind spots with a red path when another vehicle is detected nearby.", ""},
|
||||||
|
{"ShowFPS", "FPS Counter", "Display the Frames Per Second (FPS) of your onroad UI for monitoring system performance.", ""},
|
||||||
|
{"LeadInfo", "Lead Info and Logics", "Get detailed information about the vehicle ahead, including speed and distance, and the logic behind your following distance.", ""},
|
||||||
|
{"RoadNameUI", "Road Name", "See the name of the road you're on at the bottom of your screen. Sourced from OpenStreetMap.", ""},
|
||||||
|
{"UseVienna", "Use Vienna Speed Limit Signs", "Use the Vienna (EU) speed limit style signs as opposed to MUTCD (US).", ""},
|
||||||
|
|
||||||
|
{"DriverCamera", "Driver Camera On Reverse", "Show the driver's camera feed when you shift to reverse.", "../assets/img_driver_face_static.png"},
|
||||||
|
{"GreenLightAlert", "Green Light Alert", "Get an alert when a traffic light changes from red to green.", "../frogpilot/assets/toggle_icons/icon_green_light.png"},
|
||||||
|
|
||||||
|
{"ModelUI", "Model UI", "Personalize how the model's visualizations appear on your screen.", "../assets/offroad/icon_calibration.png"},
|
||||||
|
{"AccelerationPath", "Acceleration Path", "Visualize the car's intended acceleration or deceleration with a color-coded path.", ""},
|
||||||
|
{"LaneLinesWidth", "Lane Lines", "Adjust the visual thickness of lane lines on your display.\n\nDefault matches the MUTCD average of 4 inches.", ""},
|
||||||
|
{"PathEdgeWidth", "Path Edges", "Adjust the width of the path edges shown on your UI to represent different driving modes and statuses.\n\nDefault is 20% of the total path.\n\nBlue = Navigation\nLight Blue = Always On Lateral\nGreen = Default with 'FrogPilot Colors'\nLight Green = Default with stock colors\nOrange = Experimental Mode Active\nYellow = Conditional Overriden", ""},
|
||||||
|
{"PathWidth", "Path Width", "Customize the width of the driving path shown on your UI.\n\nDefault matches the width of a 2019 Lexus ES 350.", ""},
|
||||||
|
{"RoadEdgesWidth", "Road Edges", "Adjust the visual thickness of road edges on your display.\n\nDefault is 1/2 of the MUTCD average lane line width of 4 inches.", ""},
|
||||||
|
{"UnlimitedLength", "'Unlimited' Road UI Length", "Extend the display of the path, lane lines, and road edges as far as the system can detect, providing a more expansive view of the road ahead.", ""},
|
||||||
|
|
||||||
|
{"QOLVisuals", "Quality of Life", "Miscellaneous quality of life changes to improve your overall openpilot experience.", "../frogpilot/assets/toggle_icons/quality_of_life.png"},
|
||||||
|
{"DriveStats", "Drive Stats In Home Screen", "Display your device's drive stats in the home screen.", ""},
|
||||||
|
{"HideSpeed", "Hide Speed", "Hide the speed indicator in the onroad UI.", ""},
|
||||||
|
{"ShowSLCOffset", "Show Speed Limit Offset", "Show the speed limit offset seperated from the speed limit in the onroad UI when using 'Speed Limit Controller'.", ""},
|
||||||
|
|
||||||
|
{"RandomEvents", "Random Events", "Enjoy a bit of unpredictability with random events that can occur during certain driving conditions.", "../frogpilot/assets/toggle_icons/icon_random.png"},
|
||||||
|
{"ScreenBrightness", "Screen Brightness", "Customize your screen brightness.", "../frogpilot/assets/toggle_icons/icon_light.png"},
|
||||||
|
{"SilentMode", "Silent Mode", "Mute openpilot sounds for a quieter driving experience.", "../frogpilot/assets/toggle_icons/icon_mute.png"},
|
||||||
|
{"WheelIcon", "Steering Wheel Icon", "Replace the default steering wheel icon with a custom design, adding a unique touch to your interface.", "../assets/offroad/icon_openpilot.png"},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &[param, title, desc, icon] : visualToggles) {
|
||||||
|
ParamControl *toggle;
|
||||||
|
|
||||||
|
if (param == "CameraView") {
|
||||||
|
std::vector<QString> cameraOptions{tr("Auto"), tr("Standard"), tr("Wide"), tr("Driver")};
|
||||||
|
FrogPilotButtonParamControl *preferredCamera = new FrogPilotButtonParamControl(param, title, desc, icon, cameraOptions);
|
||||||
|
toggle = preferredCamera;
|
||||||
|
|
||||||
|
} else if (param == "CustomTheme") {
|
||||||
|
FrogPilotParamManageControl *customThemeToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(customThemeToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(customThemeKeys.find(key.c_str()) != customThemeKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = customThemeToggle;
|
||||||
|
} else if (param == "CustomColors" || param == "CustomIcons" || param == "CustomSignals" || param == "CustomSounds") {
|
||||||
|
std::vector<QString> themeOptions{tr("Stock"), tr("Frog"), tr("Tesla"), tr("Stalin")};
|
||||||
|
FrogPilotButtonParamControl *themeSelection = new FrogPilotButtonParamControl(param, title, desc, icon, themeOptions);
|
||||||
|
toggle = themeSelection;
|
||||||
|
|
||||||
|
if (param == "CustomSounds") {
|
||||||
|
QObject::connect(static_cast<FrogPilotButtonParamControl*>(toggle), &FrogPilotButtonParamControl::buttonClicked, [this](int id) {
|
||||||
|
if (id == 1) {
|
||||||
|
if (FrogPilotConfirmationDialog::yesorno("Do you want to enable the bonus 'Goat' sound effect?", this)) {
|
||||||
|
params.putBool("GoatScream", true);
|
||||||
|
} else {
|
||||||
|
params.putBool("GoatScream", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (param == "CustomUI") {
|
||||||
|
FrogPilotParamManageControl *customUIToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(customUIToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(customOnroadUIKeys.find(key.c_str()) != customOnroadUIKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = customUIToggle;
|
||||||
|
} else if (param == "LeadInfo") {
|
||||||
|
std::vector<QString> leadInfoToggles{tr("UseSI")};
|
||||||
|
std::vector<QString> leadInfoToggleNames{tr("Use SI Values")};
|
||||||
|
toggle = new FrogPilotParamToggleControl(param, title, desc, icon, leadInfoToggles, leadInfoToggleNames);
|
||||||
|
|
||||||
|
} else if (param == "ModelUI") {
|
||||||
|
FrogPilotParamManageControl *modelUIToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(modelUIToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(modelUIKeys.find(key.c_str()) != modelUIKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = modelUIToggle;
|
||||||
|
} else if (param == "LaneLinesWidth" || param == "RoadEdgesWidth") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 24, std::map<int, QString>(), this, false, " inches");
|
||||||
|
} else if (param == "PathEdgeWidth") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 100, std::map<int, QString>(), this, false, "%");
|
||||||
|
} else if (param == "PathWidth") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 100, std::map<int, QString>(), this, false, " feet", 10);
|
||||||
|
|
||||||
|
} else if (param == "QOLVisuals") {
|
||||||
|
FrogPilotParamManageControl *qolToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(qolToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(qolKeys.find(key.c_str()) != qolKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = qolToggle;
|
||||||
|
|
||||||
|
} else if (param == "ScreenBrightness") {
|
||||||
|
std::map<int, QString> brightnessLabels;
|
||||||
|
for (int i = 0; i <= 101; ++i) {
|
||||||
|
brightnessLabels[i] = i == 0 ? "Screen Off" : i == 101 ? "Auto" : QString::number(i) + "%";
|
||||||
|
}
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 101, brightnessLabels, this, false);
|
||||||
|
|
||||||
|
} else if (param == "WheelIcon") {
|
||||||
|
std::vector<QString> wheelToggles{"RotatingWheel"};
|
||||||
|
std::vector<QString> wheelToggleNames{tr("Rotating")};
|
||||||
|
std::map<int, QString> steeringWheelLabels = {{0, "Stock"}, {1, "Lexus"}, {2, "Toyota"}, {3, "Frog"}, {4, "Rocket"}, {5, "Hyundai"}, {6, "Stalin"}};
|
||||||
|
toggle = new FrogPilotParamValueToggleControl(param, title, desc, icon, 0, 6, steeringWheelLabels, this, true, "", 1, wheelToggles, wheelToggleNames);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
toggle = new ParamControl(param, title, desc, icon, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
addItem(toggle);
|
||||||
|
toggles[param.toStdString()] = toggle;
|
||||||
|
|
||||||
|
QObject::connect(toggle, &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamValueControl*>(toggle), &FrogPilotParamValueControl::buttonPressed, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(toggle, &AbstractControl::showDescriptionEvent, [this]() {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamManageControl*>(toggle), &FrogPilotParamManageControl::manageButtonClicked, [this]() {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string> rebootKeys = {"DriveStats"};
|
||||||
|
for (const std::string &key : rebootKeys) {
|
||||||
|
QObject::connect(toggles[key], &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) {
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
customOnroadUIKeys = {"AdjacentPath", "BlindSpotPath", "ShowFPS", "LeadInfo", "RoadNameUI", "UseVienna"};
|
||||||
|
customThemeKeys = {"CustomColors", "CustomIcons", "CustomSignals", "CustomSounds"};
|
||||||
|
modelUIKeys = {"AccelerationPath", "LaneLinesWidth", "PathEdgeWidth", "PathWidth", "RoadEdgesWidth", "UnlimitedLength"};
|
||||||
|
qolKeys = {"DriveStats", "HideSpeed", "ShowSLCOffset"};
|
||||||
|
|
||||||
|
QObject::connect(device(), &Device::interactiveTimeout, this, &FrogPilotVisualsPanel::hideSubToggles);
|
||||||
|
QObject::connect(parent, &SettingsWindow::closeParentToggle, this, &FrogPilotVisualsPanel::hideSubToggles);
|
||||||
|
QObject::connect(parent, &SettingsWindow::updateMetric, this, &FrogPilotVisualsPanel::updateMetric);
|
||||||
|
|
||||||
|
hideSubToggles();
|
||||||
|
updateMetric();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVisualsPanel::updateToggles() {
|
||||||
|
std::thread([this]() {
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", true);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", false);
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVisualsPanel::updateMetric() {
|
||||||
|
bool previousIsMetric = isMetric;
|
||||||
|
isMetric = params.getBool("IsMetric");
|
||||||
|
|
||||||
|
if (isMetric != previousIsMetric) {
|
||||||
|
double distanceConversion = isMetric ? INCH_TO_CM : CM_TO_INCH;
|
||||||
|
double speedConversion = isMetric ? FOOT_TO_METER : METER_TO_FOOT;
|
||||||
|
params.putInt("LaneLinesWidth", std::nearbyint(params.getInt("LaneLinesWidth") * distanceConversion));
|
||||||
|
params.putInt("RoadEdgesWidth", std::nearbyint(params.getInt("RoadEdgesWidth") * distanceConversion));
|
||||||
|
params.putInt("PathWidth", std::nearbyint(params.getInt("PathWidth") * speedConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
FrogPilotParamValueControl *laneLinesWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["LaneLinesWidth"]);
|
||||||
|
FrogPilotParamValueControl *roadEdgesWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["RoadEdgesWidth"]);
|
||||||
|
FrogPilotParamValueControl *pathWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["PathWidth"]);
|
||||||
|
|
||||||
|
if (isMetric) {
|
||||||
|
laneLinesWidthToggle->setDescription("Customize the lane line width.\n\nDefault matches the Vienna average of 10 centimeters.");
|
||||||
|
roadEdgesWidthToggle->setDescription("Customize the road edges width.\n\nDefault is 1/2 of the Vienna average lane line width of 10 centimeters.");
|
||||||
|
|
||||||
|
laneLinesWidthToggle->updateControl(0, 60, " centimeters");
|
||||||
|
roadEdgesWidthToggle->updateControl(0, 60, " centimeters");
|
||||||
|
pathWidthToggle->updateControl(0, 30, " meters");
|
||||||
|
} else {
|
||||||
|
laneLinesWidthToggle->setDescription("Customize the lane line width.\n\nDefault matches the MUTCD average of 4 inches.");
|
||||||
|
roadEdgesWidthToggle->setDescription("Customize the road edges width.\n\nDefault is 1/2 of the MUTCD average lane line width of 4 inches.");
|
||||||
|
|
||||||
|
laneLinesWidthToggle->updateControl(0, 24, " inches");
|
||||||
|
roadEdgesWidthToggle->updateControl(0, 24, " inches");
|
||||||
|
pathWidthToggle->updateControl(0, 100, " feet");
|
||||||
|
}
|
||||||
|
|
||||||
|
laneLinesWidthToggle->refresh();
|
||||||
|
roadEdgesWidthToggle->refresh();
|
||||||
|
|
||||||
|
previousIsMetric = isMetric;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVisualsPanel::parentToggleClicked() {
|
||||||
|
this->openParentToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVisualsPanel::hideSubToggles() {
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
bool subToggles = modelUIKeys.find(key.c_str()) != modelUIKeys.end() ||
|
||||||
|
customOnroadUIKeys.find(key.c_str()) != customOnroadUIKeys.end() ||
|
||||||
|
customThemeKeys.find(key.c_str()) != customThemeKeys.end() ||
|
||||||
|
qolKeys.find(key.c_str()) != qolKeys.end();
|
||||||
|
toggle->setVisible(!subToggles);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->closeParentToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVisualsPanel::hideEvent(QHideEvent *event) {
|
||||||
|
hideSubToggles();
|
||||||
|
}
|
||||||
242
selfdrive/clearpilot/ui/visual_settings.cc.org
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
#include "selfdrive/frogpilot/ui/visual_settings.h"
|
||||||
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
|
FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilotListWidget(parent) {
|
||||||
|
const std::vector<std::tuple<QString, QString, QString, QString>> visualToggles {
|
||||||
|
{"CustomTheme", "Custom Themes", "Enable the ability to use custom themes.", "../frogpilot/assets/wheel_images/frog.png"},
|
||||||
|
{"CustomColors", "Color Theme", "Switch out the standard openpilot color scheme with a custom color scheme.\n\nWant to submit your own color scheme? Post it in the 'feature-request' channel in the FrogPilot Discord!", ""},
|
||||||
|
{"CustomIcons", "Icon Pack", "Switch out the standard openpilot icons with a set of custom icons.\n\nWant to submit your own icon pack? Post it in the 'feature-request' channel in the FrogPilot Discord!", ""},
|
||||||
|
{"CustomSignals", "Turn Signals", "Add custom animations for your turn signals for a personal touch!\n\nWant to submit your own turn signal animation? Post it in the 'feature-request' channel in the FrogPilot Discord!", ""},
|
||||||
|
{"CustomSounds", "Sound Pack", "Switch out the standard openpilot sounds with a set of custom sounds.\n\nWant to submit your own sound pack? Post it in the 'feature-request' channel in the FrogPilot Discord!", ""},
|
||||||
|
|
||||||
|
{"CameraView", "Camera View", "Choose your preferred camera view for the onroad UI. This is a visual change only and doesn't impact openpilot.", "../frogpilot/assets/toggle_icons/icon_camera.png"},
|
||||||
|
{"Compass", "Compass", "Add a compass to your onroad UI.", "../frogpilot/assets/toggle_icons/icon_compass.png"},
|
||||||
|
|
||||||
|
{"CustomUI", "Custom Onroad UI", "Customize the Onroad UI with some additional visual functions.", "../assets/offroad/icon_road.png"},
|
||||||
|
{"AdjacentPath", "Adjacent Paths", "Display paths to the left and right of your car, visualizing where the model detects lanes.", ""},
|
||||||
|
{"BlindSpotPath", "Blind Spot Path", "Visualize your blind spots with a red path when another vehicle is detected nearby.", ""},
|
||||||
|
{"ShowFPS", "FPS Counter", "Display the Frames Per Second (FPS) of your onroad UI for monitoring system performance.", ""},
|
||||||
|
{"LeadInfo", "Lead Info and Logics", "Get detailed information about the vehicle ahead, including speed and distance, and the logic behind your following distance.", ""},
|
||||||
|
{"RoadNameUI", "Road Name", "See the name of the road you're on at the bottom of your screen. Sourced from OpenStreetMap.", ""},
|
||||||
|
{"UseVienna", "Use Vienna Speed Limit Signs", "Use the Vienna (EU) speed limit style signs as opposed to MUTCD (US).", ""},
|
||||||
|
|
||||||
|
{"DriverCamera", "Driver Camera On Reverse", "Show the driver's camera feed when you shift to reverse.", "../assets/img_driver_face_static.png"},
|
||||||
|
{"GreenLightAlert", "Green Light Alert", "Get an alert when a traffic light changes from red to green.", "../frogpilot/assets/toggle_icons/icon_green_light.png"},
|
||||||
|
|
||||||
|
{"ModelUI", "Model UI", "Personalize how the model's visualizations appear on your screen.", "../assets/offroad/icon_calibration.png"},
|
||||||
|
{"AccelerationPath", "Acceleration Path", "Visualize the car's intended acceleration or deceleration with a color-coded path.", ""},
|
||||||
|
{"LaneLinesWidth", "Lane Lines", "Adjust the visual thickness of lane lines on your display.\n\nDefault matches the MUTCD average of 4 inches.", ""},
|
||||||
|
{"PathEdgeWidth", "Path Edges", "Adjust the width of the path edges shown on your UI to represent different driving modes and statuses.\n\nDefault is 20% of the total path.\n\nBlue = Navigation\nLight Blue = Always On Lateral\nGreen = Default with 'FrogPilot Colors'\nLight Green = Default with stock colors\nOrange = Experimental Mode Active\nYellow = Conditional Overriden", ""},
|
||||||
|
{"PathWidth", "Path Width", "Customize the width of the driving path shown on your UI.\n\nDefault matches the width of a 2019 Lexus ES 350.", ""},
|
||||||
|
{"RoadEdgesWidth", "Road Edges", "Adjust the visual thickness of road edges on your display.\n\nDefault is 1/2 of the MUTCD average lane line width of 4 inches.", ""},
|
||||||
|
{"UnlimitedLength", "'Unlimited' Road UI Length", "Extend the display of the path, lane lines, and road edges as far as the system can detect, providing a more expansive view of the road ahead.", ""},
|
||||||
|
|
||||||
|
{"QOLVisuals", "Quality of Life", "Miscellaneous quality of life changes to improve your overall openpilot experience.", "../frogpilot/assets/toggle_icons/quality_of_life.png"},
|
||||||
|
{"DriveStats", "Drive Stats In Home Screen", "Display your device's drive stats in the home screen.", ""},
|
||||||
|
{"HideSpeed", "Hide Speed", "Hide the speed indicator in the onroad UI.", ""},
|
||||||
|
{"ShowSLCOffset", "Show Speed Limit Offset", "Show the speed limit offset seperated from the speed limit in the onroad UI when using 'Speed Limit Controller'.", ""},
|
||||||
|
|
||||||
|
{"RandomEvents", "Random Events", "Enjoy a bit of unpredictability with random events that can occur during certain driving conditions.", "../frogpilot/assets/toggle_icons/icon_random.png"},
|
||||||
|
{"ScreenBrightness", "Screen Brightness", "Customize your screen brightness.", "../frogpilot/assets/toggle_icons/icon_light.png"},
|
||||||
|
{"SilentMode", "Silent Mode", "Mute openpilot sounds for a quieter driving experience.", "../frogpilot/assets/toggle_icons/icon_mute.png"},
|
||||||
|
{"WheelIcon", "Steering Wheel Icon", "Replace the default steering wheel icon with a custom design, adding a unique touch to your interface.", "../assets/offroad/icon_openpilot.png"},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &[param, title, desc, icon] : visualToggles) {
|
||||||
|
ParamControl *toggle;
|
||||||
|
|
||||||
|
if (param == "CameraView") {
|
||||||
|
std::vector<QString> cameraOptions{tr("Auto"), tr("Standard"), tr("Wide"), tr("Driver")};
|
||||||
|
FrogPilotButtonParamControl *preferredCamera = new FrogPilotButtonParamControl(param, title, desc, icon, cameraOptions);
|
||||||
|
toggle = preferredCamera;
|
||||||
|
|
||||||
|
} else if (param == "CustomTheme") {
|
||||||
|
FrogPilotParamManageControl *customThemeToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(customThemeToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(customThemeKeys.find(key.c_str()) != customThemeKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = customThemeToggle;
|
||||||
|
} else if (param == "CustomColors" || param == "CustomIcons" || param == "CustomSignals" || param == "CustomSounds") {
|
||||||
|
std::vector<QString> themeOptions{tr("Stock"), tr("Frog"), tr("Tesla"), tr("Stalin")};
|
||||||
|
FrogPilotButtonParamControl *themeSelection = new FrogPilotButtonParamControl(param, title, desc, icon, themeOptions);
|
||||||
|
toggle = themeSelection;
|
||||||
|
|
||||||
|
if (param == "CustomSounds") {
|
||||||
|
QObject::connect(static_cast<FrogPilotButtonParamControl*>(toggle), &FrogPilotButtonParamControl::buttonClicked, [this](int id) {
|
||||||
|
if (id == 1) {
|
||||||
|
if (FrogPilotConfirmationDialog::yesorno("Do you want to enable the bonus 'Goat' sound effect?", this)) {
|
||||||
|
params.putBool("GoatScream", true);
|
||||||
|
} else {
|
||||||
|
params.putBool("GoatScream", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (param == "CustomUI") {
|
||||||
|
FrogPilotParamManageControl *customUIToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(customUIToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(customOnroadUIKeys.find(key.c_str()) != customOnroadUIKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = customUIToggle;
|
||||||
|
} else if (param == "LeadInfo") {
|
||||||
|
std::vector<QString> leadInfoToggles{tr("UseSI")};
|
||||||
|
std::vector<QString> leadInfoToggleNames{tr("Use SI Values")};
|
||||||
|
toggle = new FrogPilotParamToggleControl(param, title, desc, icon, leadInfoToggles, leadInfoToggleNames);
|
||||||
|
|
||||||
|
} else if (param == "ModelUI") {
|
||||||
|
FrogPilotParamManageControl *modelUIToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(modelUIToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(modelUIKeys.find(key.c_str()) != modelUIKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = modelUIToggle;
|
||||||
|
} else if (param == "LaneLinesWidth" || param == "RoadEdgesWidth") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 24, std::map<int, QString>(), this, false, " inches");
|
||||||
|
} else if (param == "PathEdgeWidth") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 100, std::map<int, QString>(), this, false, "%");
|
||||||
|
} else if (param == "PathWidth") {
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 100, std::map<int, QString>(), this, false, " feet", 10);
|
||||||
|
|
||||||
|
} else if (param == "QOLVisuals") {
|
||||||
|
FrogPilotParamManageControl *qolToggle = new FrogPilotParamManageControl(param, title, desc, icon, this);
|
||||||
|
QObject::connect(qolToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() {
|
||||||
|
parentToggleClicked();
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
toggle->setVisible(qolKeys.find(key.c_str()) != qolKeys.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggle = qolToggle;
|
||||||
|
|
||||||
|
} else if (param == "ScreenBrightness") {
|
||||||
|
std::map<int, QString> brightnessLabels;
|
||||||
|
for (int i = 0; i <= 101; ++i) {
|
||||||
|
brightnessLabels[i] = i == 0 ? "Screen Off" : i == 101 ? "Auto" : QString::number(i) + "%";
|
||||||
|
}
|
||||||
|
toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 101, brightnessLabels, this, false);
|
||||||
|
|
||||||
|
} else if (param == "WheelIcon") {
|
||||||
|
std::vector<QString> wheelToggles{"RotatingWheel"};
|
||||||
|
std::vector<QString> wheelToggleNames{tr("Rotating")};
|
||||||
|
std::map<int, QString> steeringWheelLabels = {{0, "Stock"}, {1, "Lexus"}, {2, "Toyota"}, {3, "Frog"}, {4, "Rocket"}, {5, "Hyundai"}, {6, "Stalin"}};
|
||||||
|
toggle = new FrogPilotParamValueToggleControl(param, title, desc, icon, 0, 6, steeringWheelLabels, this, true, "", 1, wheelToggles, wheelToggleNames);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
toggle = new ParamControl(param, title, desc, icon, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
addItem(toggle);
|
||||||
|
toggles[param.toStdString()] = toggle;
|
||||||
|
|
||||||
|
QObject::connect(toggle, &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamValueControl*>(toggle), &FrogPilotParamValueControl::buttonPressed, [this]() {
|
||||||
|
updateToggles();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(toggle, &AbstractControl::showDescriptionEvent, [this]() {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(static_cast<FrogPilotParamManageControl*>(toggle), &FrogPilotParamManageControl::manageButtonClicked, [this]() {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string> rebootKeys = {"DriveStats"};
|
||||||
|
for (const std::string &key : rebootKeys) {
|
||||||
|
QObject::connect(toggles[key], &ToggleControl::toggleFlipped, [this]() {
|
||||||
|
if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) {
|
||||||
|
Hardware::reboot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
customOnroadUIKeys = {"AdjacentPath", "BlindSpotPath", "ShowFPS", "LeadInfo", "RoadNameUI", "UseVienna"};
|
||||||
|
customThemeKeys = {"CustomColors", "CustomIcons", "CustomSignals", "CustomSounds"};
|
||||||
|
modelUIKeys = {"AccelerationPath", "LaneLinesWidth", "PathEdgeWidth", "PathWidth", "RoadEdgesWidth", "UnlimitedLength"};
|
||||||
|
qolKeys = {"DriveStats", "HideSpeed", "ShowSLCOffset"};
|
||||||
|
|
||||||
|
QObject::connect(device(), &Device::interactiveTimeout, this, &FrogPilotVisualsPanel::hideSubToggles);
|
||||||
|
QObject::connect(parent, &SettingsWindow::closeParentToggle, this, &FrogPilotVisualsPanel::hideSubToggles);
|
||||||
|
QObject::connect(parent, &SettingsWindow::updateMetric, this, &FrogPilotVisualsPanel::updateMetric);
|
||||||
|
|
||||||
|
hideSubToggles();
|
||||||
|
updateMetric();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVisualsPanel::updateToggles() {
|
||||||
|
std::thread([this]() {
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", true);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
paramsMemory.putBool("FrogPilotTogglesUpdated", false);
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVisualsPanel::updateMetric() {
|
||||||
|
bool previousIsMetric = isMetric;
|
||||||
|
isMetric = params.getBool("IsMetric");
|
||||||
|
|
||||||
|
if (isMetric != previousIsMetric) {
|
||||||
|
double distanceConversion = isMetric ? INCH_TO_CM : CM_TO_INCH;
|
||||||
|
double speedConversion = isMetric ? FOOT_TO_METER : METER_TO_FOOT;
|
||||||
|
params.putInt("LaneLinesWidth", std::nearbyint(params.getInt("LaneLinesWidth") * distanceConversion));
|
||||||
|
params.putInt("RoadEdgesWidth", std::nearbyint(params.getInt("RoadEdgesWidth") * distanceConversion));
|
||||||
|
params.putInt("PathWidth", std::nearbyint(params.getInt("PathWidth") * speedConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
FrogPilotParamValueControl *laneLinesWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["LaneLinesWidth"]);
|
||||||
|
FrogPilotParamValueControl *roadEdgesWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["RoadEdgesWidth"]);
|
||||||
|
FrogPilotParamValueControl *pathWidthToggle = static_cast<FrogPilotParamValueControl*>(toggles["PathWidth"]);
|
||||||
|
|
||||||
|
if (isMetric) {
|
||||||
|
laneLinesWidthToggle->setDescription("Customize the lane line width.\n\nDefault matches the Vienna average of 10 centimeters.");
|
||||||
|
roadEdgesWidthToggle->setDescription("Customize the road edges width.\n\nDefault is 1/2 of the Vienna average lane line width of 10 centimeters.");
|
||||||
|
|
||||||
|
laneLinesWidthToggle->updateControl(0, 60, " centimeters");
|
||||||
|
roadEdgesWidthToggle->updateControl(0, 60, " centimeters");
|
||||||
|
pathWidthToggle->updateControl(0, 30, " meters");
|
||||||
|
} else {
|
||||||
|
laneLinesWidthToggle->setDescription("Customize the lane line width.\n\nDefault matches the MUTCD average of 4 inches.");
|
||||||
|
roadEdgesWidthToggle->setDescription("Customize the road edges width.\n\nDefault is 1/2 of the MUTCD average lane line width of 4 inches.");
|
||||||
|
|
||||||
|
laneLinesWidthToggle->updateControl(0, 24, " inches");
|
||||||
|
roadEdgesWidthToggle->updateControl(0, 24, " inches");
|
||||||
|
pathWidthToggle->updateControl(0, 100, " feet");
|
||||||
|
}
|
||||||
|
|
||||||
|
laneLinesWidthToggle->refresh();
|
||||||
|
roadEdgesWidthToggle->refresh();
|
||||||
|
|
||||||
|
previousIsMetric = isMetric;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVisualsPanel::parentToggleClicked() {
|
||||||
|
this->openParentToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVisualsPanel::hideSubToggles() {
|
||||||
|
for (auto &[key, toggle] : toggles) {
|
||||||
|
bool subToggles = modelUIKeys.find(key.c_str()) != modelUIKeys.end() ||
|
||||||
|
customOnroadUIKeys.find(key.c_str()) != customOnroadUIKeys.end() ||
|
||||||
|
customThemeKeys.find(key.c_str()) != customThemeKeys.end() ||
|
||||||
|
qolKeys.find(key.c_str()) != qolKeys.end();
|
||||||
|
toggle->setVisible(!subToggles);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->closeParentToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrogPilotVisualsPanel::hideEvent(QHideEvent *event) {
|
||||||
|
hideSubToggles();
|
||||||
|
}
|
||||||
36
selfdrive/clearpilot/ui/visual_settings.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/frogpilot_functions.h"
|
||||||
|
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||||
|
|
||||||
|
class FrogPilotVisualsPanel : public FrogPilotListWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FrogPilotVisualsPanel(SettingsWindow *parent);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void closeParentToggle();
|
||||||
|
void openParentToggle();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void hideEvent(QHideEvent *event);
|
||||||
|
void hideSubToggles();
|
||||||
|
void parentToggleClicked();
|
||||||
|
void updateMetric();
|
||||||
|
void updateToggles();
|
||||||
|
|
||||||
|
std::set<QString> customOnroadUIKeys;
|
||||||
|
std::set<QString> customThemeKeys;
|
||||||
|
std::set<QString> modelUIKeys;
|
||||||
|
std::set<QString> qolKeys;
|
||||||
|
|
||||||
|
std::map<std::string, ParamControl*> toggles;
|
||||||
|
|
||||||
|
Params params;
|
||||||
|
Params paramsMemory{"/dev/shm/params"};
|
||||||
|
|
||||||
|
bool isMetric = params.getBool("IsMetric");
|
||||||
|
};
|
||||||
36
selfdrive/clearpilot/ui/visual_settings.h.org
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "selfdrive/frogpilot/ui/frogpilot_functions.h"
|
||||||
|
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||||
|
|
||||||
|
class FrogPilotVisualsPanel : public FrogPilotListWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FrogPilotVisualsPanel(SettingsWindow *parent);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void closeParentToggle();
|
||||||
|
void openParentToggle();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void hideEvent(QHideEvent *event);
|
||||||
|
void hideSubToggles();
|
||||||
|
void parentToggleClicked();
|
||||||
|
void updateMetric();
|
||||||
|
void updateToggles();
|
||||||
|
|
||||||
|
std::set<QString> customOnroadUIKeys;
|
||||||
|
std::set<QString> customThemeKeys;
|
||||||
|
std::set<QString> modelUIKeys;
|
||||||
|
std::set<QString> qolKeys;
|
||||||
|
|
||||||
|
std::map<std::string, ParamControl*> toggles;
|
||||||
|
|
||||||
|
Params params;
|
||||||
|
Params paramsMemory{"/dev/shm/params"};
|
||||||
|
|
||||||
|
bool isMetric = params.getBool("IsMetric");
|
||||||
|
};
|
||||||
0
selfdrive/clearpilot/weather.cc
Normal file
0
selfdrive/clearpilot/weather.h
Normal file
21
system/clearpilot/configure/dependencies.sh
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if nodejs is installed
|
||||||
|
if ! which nodejs > /dev/null; then
|
||||||
|
#sudo mount -o remount,rw /
|
||||||
|
|
||||||
|
# Node.js is not installed, setting up the NodeSource Node.js 21.x repo
|
||||||
|
echo "Node.js not found. Setting up NodeSource Node.js 21.x repository..."
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_21.x | sudo -E bash -
|
||||||
|
|
||||||
|
# Install Node.js from the NodeSource repository
|
||||||
|
echo "Installing Node.js..."
|
||||||
|
sudo apt-get install -y nodejs
|
||||||
|
fi
|
||||||
|
|
||||||
|
# apt-get update
|
||||||
|
# apt-get install -y nodejs npm
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y python3-pyqt5
|
||||||
|
pip3 install termqt
|
||||||
47
system/clearpilot/notes_wayland.txt
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
From the information you've provided, particularly the configuration in the systemd service file for Weston and related directories, we can deduce several important environmental setup aspects for running applications using the Wayland display server (Weston) on your device.
|
||||||
|
|
||||||
|
Key Takeaways
|
||||||
|
Weston Configuration and Runtime Directory:
|
||||||
|
Weston is run with specific configurations specified in /usr/comma/weston.ini.
|
||||||
|
The XDG_RUNTIME_DIR environment variable is set to /var/tmp/weston. This is crucial because Wayland clients locate the Wayland server (Weston in this case) through the WAYLAND_DISPLAY environment variable, which typically points to a socket file within XDG_RUNTIME_DIR.
|
||||||
|
Service Setup:
|
||||||
|
Pre-execution steps in the Weston service ensure that the runtime directory exists and has the correct permissions. This directory is used by Weston to store its runtime data, including the socket file (wayland-0), which clients use to communicate with Weston.
|
||||||
|
Execution Parameters:
|
||||||
|
Weston is started with the --config flag pointing to a custom configuration file and on a specific TTY. This setup is essential for correctly initializing the display server on your device.
|
||||||
|
Making Your PyQt Application Work
|
||||||
|
Given the above setup, you should ensure your PyQt application is configured to correctly interact with this environment. Here's what you need to do:
|
||||||
|
|
||||||
|
Set the XDG_RUNTIME_DIR Environment Variable: This should match the configuration of the Weston service to ensure that your application can find the Wayland display server.
|
||||||
|
Direct PyQt to use the Wayland Platform:
|
||||||
|
Explicitly set the platform to Wayland in your PyQt application.
|
||||||
|
Ensure the WAYLAND_DISPLAY environment variable points to the correct socket file, usually wayland-0 inside your runtime directory.
|
||||||
|
Here’s how you can modify your Python script to accommodate these settings:
|
||||||
|
|
||||||
|
python
|
||||||
|
Copy code
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QScrollBar
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
|
# Set environment variables to match the Weston configuration
|
||||||
|
os.environ["XDG_RUNTIME_DIR"] = "/var/tmp/weston"
|
||||||
|
os.environ["WAYLAND_DISPLAY"] = "wayland-0" # Typically the socket is named wayland-0
|
||||||
|
os.environ["QT_QPA_PLATFORM"] = "wayland"
|
||||||
|
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
window = QWidget()
|
||||||
|
window.setWindowTitle("PyQt on Wayland")
|
||||||
|
layout = QHBoxLayout(window)
|
||||||
|
scroll = QScrollBar(Qt.Vertical)
|
||||||
|
layout.addWidget(scroll)
|
||||||
|
window.setLayout(layout)
|
||||||
|
window.show()
|
||||||
|
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
Summary
|
||||||
|
By aligning your PyQt environment with the established Weston configuration:
|
||||||
|
|
||||||
|
You replicate the environmental setup that native applications use, ensuring compatibility.
|
||||||
|
You directly address the issue of the PyQt application failing to find or connect to the Weston display server, which is indicated by errors related to the xcb platform plugin.
|
||||||
|
This setup should resolve the issues you've been facing with launching your PyQt application on the device. Ensure that Weston is actively running when you attempt to start your application, as it must be running to accept connections from Wayland clients.
|
||||||
0
system/clearpilot/on_boot.sh
Normal file
0
system/clearpilot/on_start.sh
Normal file
4
system/clearpilot/provision_shell.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apt-get update
|
||||||
|
apt-get install -y nodejs npm
|
||||||
|
|
||||||
|
pip install pyqt5
|
||||||
3
system/clearpilot/shell/authorized_keys
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQtHTzkeRlOXDWyK/IvO2RgjSdoq6V81u3YtcyIxBZVX2zCj1xzE9zWcUcVxloe63rB/DBasChODIRBtp1vGnWb/EkLWAuOqS2V5rzhlcSfM103++TI81e04A7HDspWSNUXRh5OD/mUvwtYIH7S4QAkBiCro5lAgSToXNAOR4b4cXgNQecf+RhPc0Nm3K8Is1wEeQajmlC1E22YWBDDV+uoB3Uagl90e58Psbp8PunCdbeY9EfqQsymyloiTeqzKwHnmHnMXSlZluh7A+ifoKgohDsarT1FixAgxT0LSIxxINORhE4P6em/7y3xpgubPhNpbuQSzDlb3op3fwMoFcAEEYKWg+d9OGOrdiWa13aV0g7UNdW/XmmF/BAaBdSOZeomVNnxmftmmJWfu3jtFdwTDRQpZn7nDYC+aZ1R3Q0Xd4lLuqkA/9smUXLZuiBDJXwM5nDyWQR9tESIwlTLcdKAUpj0gQqpcozVehksNksTekZBAg/mYb6DKyYCTY0ti0=
|
||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCm/Vq50kqf94allqGq9luBGjDh2Do/rOCA719CRlDOErCvdY+ZaYNumQZ5AbFfU5KcPZwirJLBvhEoH/G0lEAg9TUaUgH/VvqBBztlpcmA1eplZHzEFLnTDn0oO4Tk46bXwjL0anOZfNaUGhbaO4Th7m+9+o16WUduEabPiyVbnqD6P44CANsvBJNKlyUDBzsdkE9z5gULp06i1+JqqXiGV81HoFWZe5YCFv4j4QUPvfmFhcBHViVrOFs87hS4Eu0gWNxQmQBhh6R1ZbjaBlGdE5GyDZQZwlofjfuO06e0HvCDuIAELSYqlGFCmUhlM/LZ6YkF79/HFrg5sS3gsuY5
|
||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHbrOZrByUb2Ci21DdJkhWv/4Bz4oghL9joraQYFq4Om
|
||||||
BIN
system/clearpilot/shell/bg.jpg
Normal file
|
After Width: | Height: | Size: 44 KiB |
5
system/clearpilot/shell/configure_ssh.sh
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
cat /data/openpilot/shell/authorized_keys >/data/params/d/GithubSshKeys
|
||||||
|
chown comma:comma /data/params/d/GithubSshKeys
|
||||||
|
chmod 600 /data/params/d/GithubSshKeys
|
||||||
|
# systemctl restart ssh
|
||||||
|
|
||||||
14
system/clearpilot/shell/dependencies.sh
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if nodejs is installed
|
||||||
|
if ! which nodejs > /dev/null; then
|
||||||
|
#sudo mount -o remount,rw /
|
||||||
|
|
||||||
|
# Node.js is not installed, setting up the NodeSource Node.js 21.x repo
|
||||||
|
echo "Node.js not found. Setting up NodeSource Node.js 21.x repository..."
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_21.x | sudo -E bash -
|
||||||
|
|
||||||
|
# Install Node.js from the NodeSource repository
|
||||||
|
echo "Installing Node.js..."
|
||||||
|
sudo apt-get install -y nodejs
|
||||||
|
fi
|
||||||
16
system/clearpilot/shell/init_shell.sh
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
sudo chown -R comma:comma /data/openpilot &
|
||||||
|
|
||||||
|
cd /data/openpilot/shell/
|
||||||
|
|
||||||
|
sudo bash ./configure_ssh.sh
|
||||||
|
sudo bash ./set_logo.sh
|
||||||
|
|
||||||
|
cd /data/openpilot/logs
|
||||||
|
|
||||||
|
rm -f watcher.log.gz
|
||||||
|
ls watcher.log && gzip watcher.log
|
||||||
|
|
||||||
|
chmod +x /data/openpilot/shell/watcher_run_loop.sh
|
||||||
|
sudo pkill -f "/bin/sh /data/openpilot/shell/watcher_run_loop.sh"
|
||||||
|
sudo rm -rf /run/screen/S-comma
|
||||||
|
screen -dmS "watcher" /data/openpilot/shell/watcher_run_loop.sh
|
||||||
32
system/clearpilot/shell/node_modules/.package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "shell",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"node_modules/http": {
|
||||||
|
"version": "0.0.1-security",
|
||||||
|
"resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz",
|
||||||
|
"integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g=="
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
|
||||||
|
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
system/clearpilot/shell/node_modules/http/README.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Security holding package
|
||||||
|
|
||||||
|
This package name is not currently in use, but was formerly occupied
|
||||||
|
by another package. To avoid malicious use, npm is hanging on to the
|
||||||
|
package name, but loosely, and we'll probably give it to you if you
|
||||||
|
want it.
|
||||||
|
|
||||||
|
You may adopt this package by contacting support@npmjs.com and
|
||||||
|
requesting the name.
|
||||||
6
system/clearpilot/shell/node_modules/http/package.json
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "http",
|
||||||
|
"version": "0.0.1-security",
|
||||||
|
"description": "security holding package",
|
||||||
|
"repository": "npm/security-holder"
|
||||||
|
}
|
||||||
20
system/clearpilot/shell/node_modules/ws/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||||
|
Copyright (c) 2013 Arnout Kazemier and contributors
|
||||||
|
Copyright (c) 2016 Luigi Pinca and contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
549
system/clearpilot/shell/node_modules/ws/README.md
generated
vendored
Normal file
@@ -0,0 +1,549 @@
|
|||||||
|
# ws: a Node.js WebSocket library
|
||||||
|
|
||||||
|
[](https://www.npmjs.com/package/ws)
|
||||||
|
[](https://github.com/websockets/ws/actions?query=workflow%3ACI+branch%3Amaster)
|
||||||
|
[](https://coveralls.io/github/websockets/ws)
|
||||||
|
|
||||||
|
ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and
|
||||||
|
server implementation.
|
||||||
|
|
||||||
|
Passes the quite extensive Autobahn test suite: [server][server-report],
|
||||||
|
[client][client-report].
|
||||||
|
|
||||||
|
**Note**: This module does not work in the browser. The client in the docs is a
|
||||||
|
reference to a back end with the role of a client in the WebSocket
|
||||||
|
communication. Browser clients must use the native
|
||||||
|
[`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
|
||||||
|
object. To make the same code work seamlessly on Node.js and the browser, you
|
||||||
|
can use one of the many wrappers available on npm, like
|
||||||
|
[isomorphic-ws](https://github.com/heineiuo/isomorphic-ws).
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Protocol support](#protocol-support)
|
||||||
|
- [Installing](#installing)
|
||||||
|
- [Opt-in for performance](#opt-in-for-performance)
|
||||||
|
- [Legacy opt-in for performance](#legacy-opt-in-for-performance)
|
||||||
|
- [API docs](#api-docs)
|
||||||
|
- [WebSocket compression](#websocket-compression)
|
||||||
|
- [Usage examples](#usage-examples)
|
||||||
|
- [Sending and receiving text data](#sending-and-receiving-text-data)
|
||||||
|
- [Sending binary data](#sending-binary-data)
|
||||||
|
- [Simple server](#simple-server)
|
||||||
|
- [External HTTP/S server](#external-https-server)
|
||||||
|
- [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server)
|
||||||
|
- [Client authentication](#client-authentication)
|
||||||
|
- [Server broadcast](#server-broadcast)
|
||||||
|
- [Round-trip time](#round-trip-time)
|
||||||
|
- [Use the Node.js streams API](#use-the-nodejs-streams-api)
|
||||||
|
- [Other examples](#other-examples)
|
||||||
|
- [FAQ](#faq)
|
||||||
|
- [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client)
|
||||||
|
- [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections)
|
||||||
|
- [How to connect via a proxy?](#how-to-connect-via-a-proxy)
|
||||||
|
- [Changelog](#changelog)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
## Protocol support
|
||||||
|
|
||||||
|
- **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)
|
||||||
|
- **HyBi drafts 13-17** (Current default, alternatively option
|
||||||
|
`protocolVersion: 13`)
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install ws
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opt-in for performance
|
||||||
|
|
||||||
|
[bufferutil][] is an optional module that can be installed alongside the ws
|
||||||
|
module:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install --save-optional bufferutil
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a binary addon that improves the performance of certain operations such
|
||||||
|
as masking and unmasking the data payload of the WebSocket frames. Prebuilt
|
||||||
|
binaries are available for the most popular platforms, so you don't necessarily
|
||||||
|
need to have a C++ compiler installed on your machine.
|
||||||
|
|
||||||
|
To force ws to not use bufferutil, use the
|
||||||
|
[`WS_NO_BUFFER_UTIL`](./doc/ws.md#ws_no_buffer_util) environment variable. This
|
||||||
|
can be useful to enhance security in systems where a user can put a package in
|
||||||
|
the package search path of an application of another user, due to how the
|
||||||
|
Node.js resolver algorithm works.
|
||||||
|
|
||||||
|
#### Legacy opt-in for performance
|
||||||
|
|
||||||
|
If you are running on an old version of Node.js (prior to v18.14.0), ws also
|
||||||
|
supports the [utf-8-validate][] module:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install --save-optional utf-8-validate
|
||||||
|
```
|
||||||
|
|
||||||
|
This contains a binary polyfill for [`buffer.isUtf8()`][].
|
||||||
|
|
||||||
|
To force ws to not use utf-8-validate, use the
|
||||||
|
[`WS_NO_UTF_8_VALIDATE`](./doc/ws.md#ws_no_utf_8_validate) environment variable.
|
||||||
|
|
||||||
|
## API docs
|
||||||
|
|
||||||
|
See [`/doc/ws.md`](./doc/ws.md) for Node.js-like documentation of ws classes and
|
||||||
|
utility functions.
|
||||||
|
|
||||||
|
## WebSocket compression
|
||||||
|
|
||||||
|
ws supports the [permessage-deflate extension][permessage-deflate] which enables
|
||||||
|
the client and server to negotiate a compression algorithm and its parameters,
|
||||||
|
and then selectively apply it to the data payloads of each WebSocket message.
|
||||||
|
|
||||||
|
The extension is disabled by default on the server and enabled by default on the
|
||||||
|
client. It adds a significant overhead in terms of performance and memory
|
||||||
|
consumption so we suggest to enable it only if it is really needed.
|
||||||
|
|
||||||
|
Note that Node.js has a variety of issues with high-performance compression,
|
||||||
|
where increased concurrency, especially on Linux, can lead to [catastrophic
|
||||||
|
memory fragmentation][node-zlib-bug] and slow performance. If you intend to use
|
||||||
|
permessage-deflate in production, it is worthwhile to set up a test
|
||||||
|
representative of your workload and ensure Node.js/zlib will handle it with
|
||||||
|
acceptable performance and memory usage.
|
||||||
|
|
||||||
|
Tuning of permessage-deflate can be done via the options defined below. You can
|
||||||
|
also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed directly
|
||||||
|
into the creation of [raw deflate/inflate streams][node-zlib-deflaterawdocs].
|
||||||
|
|
||||||
|
See [the docs][ws-server-options] for more options.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import WebSocket, { WebSocketServer } from 'ws';
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({
|
||||||
|
port: 8080,
|
||||||
|
perMessageDeflate: {
|
||||||
|
zlibDeflateOptions: {
|
||||||
|
// See zlib defaults.
|
||||||
|
chunkSize: 1024,
|
||||||
|
memLevel: 7,
|
||||||
|
level: 3
|
||||||
|
},
|
||||||
|
zlibInflateOptions: {
|
||||||
|
chunkSize: 10 * 1024
|
||||||
|
},
|
||||||
|
// Other options settable:
|
||||||
|
clientNoContextTakeover: true, // Defaults to negotiated value.
|
||||||
|
serverNoContextTakeover: true, // Defaults to negotiated value.
|
||||||
|
serverMaxWindowBits: 10, // Defaults to negotiated value.
|
||||||
|
// Below options specified as default values.
|
||||||
|
concurrencyLimit: 10, // Limits zlib concurrency for perf.
|
||||||
|
threshold: 1024 // Size (in bytes) below which messages
|
||||||
|
// should not be compressed if context takeover is disabled.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The client will only use the extension if it is supported and enabled on the
|
||||||
|
server. To always disable the extension on the client set the
|
||||||
|
`perMessageDeflate` option to `false`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import WebSocket from 'ws';
|
||||||
|
|
||||||
|
const ws = new WebSocket('ws://www.host.com/path', {
|
||||||
|
perMessageDeflate: false
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage examples
|
||||||
|
|
||||||
|
### Sending and receiving text data
|
||||||
|
|
||||||
|
```js
|
||||||
|
import WebSocket from 'ws';
|
||||||
|
|
||||||
|
const ws = new WebSocket('ws://www.host.com/path');
|
||||||
|
|
||||||
|
ws.on('error', console.error);
|
||||||
|
|
||||||
|
ws.on('open', function open() {
|
||||||
|
ws.send('something');
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('message', function message(data) {
|
||||||
|
console.log('received: %s', data);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sending binary data
|
||||||
|
|
||||||
|
```js
|
||||||
|
import WebSocket from 'ws';
|
||||||
|
|
||||||
|
const ws = new WebSocket('ws://www.host.com/path');
|
||||||
|
|
||||||
|
ws.on('error', console.error);
|
||||||
|
|
||||||
|
ws.on('open', function open() {
|
||||||
|
const array = new Float32Array(5);
|
||||||
|
|
||||||
|
for (var i = 0; i < array.length; ++i) {
|
||||||
|
array[i] = i / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.send(array);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Simple server
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { WebSocketServer } from 'ws';
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({ port: 8080 });
|
||||||
|
|
||||||
|
wss.on('connection', function connection(ws) {
|
||||||
|
ws.on('error', console.error);
|
||||||
|
|
||||||
|
ws.on('message', function message(data) {
|
||||||
|
console.log('received: %s', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.send('something');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### External HTTP/S server
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { createServer } from 'https';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { WebSocketServer } from 'ws';
|
||||||
|
|
||||||
|
const server = createServer({
|
||||||
|
cert: readFileSync('/path/to/cert.pem'),
|
||||||
|
key: readFileSync('/path/to/key.pem')
|
||||||
|
});
|
||||||
|
const wss = new WebSocketServer({ server });
|
||||||
|
|
||||||
|
wss.on('connection', function connection(ws) {
|
||||||
|
ws.on('error', console.error);
|
||||||
|
|
||||||
|
ws.on('message', function message(data) {
|
||||||
|
console.log('received: %s', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.send('something');
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(8080);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple servers sharing a single HTTP/S server
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { createServer } from 'http';
|
||||||
|
import { parse } from 'url';
|
||||||
|
import { WebSocketServer } from 'ws';
|
||||||
|
|
||||||
|
const server = createServer();
|
||||||
|
const wss1 = new WebSocketServer({ noServer: true });
|
||||||
|
const wss2 = new WebSocketServer({ noServer: true });
|
||||||
|
|
||||||
|
wss1.on('connection', function connection(ws) {
|
||||||
|
ws.on('error', console.error);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
wss2.on('connection', function connection(ws) {
|
||||||
|
ws.on('error', console.error);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('upgrade', function upgrade(request, socket, head) {
|
||||||
|
const { pathname } = parse(request.url);
|
||||||
|
|
||||||
|
if (pathname === '/foo') {
|
||||||
|
wss1.handleUpgrade(request, socket, head, function done(ws) {
|
||||||
|
wss1.emit('connection', ws, request);
|
||||||
|
});
|
||||||
|
} else if (pathname === '/bar') {
|
||||||
|
wss2.handleUpgrade(request, socket, head, function done(ws) {
|
||||||
|
wss2.emit('connection', ws, request);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(8080);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client authentication
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { createServer } from 'http';
|
||||||
|
import { WebSocketServer } from 'ws';
|
||||||
|
|
||||||
|
function onSocketError(err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = createServer();
|
||||||
|
const wss = new WebSocketServer({ noServer: true });
|
||||||
|
|
||||||
|
wss.on('connection', function connection(ws, request, client) {
|
||||||
|
ws.on('error', console.error);
|
||||||
|
|
||||||
|
ws.on('message', function message(data) {
|
||||||
|
console.log(`Received message ${data} from user ${client}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('upgrade', function upgrade(request, socket, head) {
|
||||||
|
socket.on('error', onSocketError);
|
||||||
|
|
||||||
|
// This function is not defined on purpose. Implement it with your own logic.
|
||||||
|
authenticate(request, function next(err, client) {
|
||||||
|
if (err || !client) {
|
||||||
|
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||||
|
socket.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.removeListener('error', onSocketError);
|
||||||
|
|
||||||
|
wss.handleUpgrade(request, socket, head, function done(ws) {
|
||||||
|
wss.emit('connection', ws, request, client);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(8080);
|
||||||
|
```
|
||||||
|
|
||||||
|
Also see the provided [example][session-parse-example] using `express-session`.
|
||||||
|
|
||||||
|
### Server broadcast
|
||||||
|
|
||||||
|
A client WebSocket broadcasting to all connected WebSocket clients, including
|
||||||
|
itself.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import WebSocket, { WebSocketServer } from 'ws';
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({ port: 8080 });
|
||||||
|
|
||||||
|
wss.on('connection', function connection(ws) {
|
||||||
|
ws.on('error', console.error);
|
||||||
|
|
||||||
|
ws.on('message', function message(data, isBinary) {
|
||||||
|
wss.clients.forEach(function each(client) {
|
||||||
|
if (client.readyState === WebSocket.OPEN) {
|
||||||
|
client.send(data, { binary: isBinary });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
A client WebSocket broadcasting to every other connected WebSocket clients,
|
||||||
|
excluding itself.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import WebSocket, { WebSocketServer } from 'ws';
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({ port: 8080 });
|
||||||
|
|
||||||
|
wss.on('connection', function connection(ws) {
|
||||||
|
ws.on('error', console.error);
|
||||||
|
|
||||||
|
ws.on('message', function message(data, isBinary) {
|
||||||
|
wss.clients.forEach(function each(client) {
|
||||||
|
if (client !== ws && client.readyState === WebSocket.OPEN) {
|
||||||
|
client.send(data, { binary: isBinary });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Round-trip time
|
||||||
|
|
||||||
|
```js
|
||||||
|
import WebSocket from 'ws';
|
||||||
|
|
||||||
|
const ws = new WebSocket('wss://websocket-echo.com/');
|
||||||
|
|
||||||
|
ws.on('error', console.error);
|
||||||
|
|
||||||
|
ws.on('open', function open() {
|
||||||
|
console.log('connected');
|
||||||
|
ws.send(Date.now());
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', function close() {
|
||||||
|
console.log('disconnected');
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('message', function message(data) {
|
||||||
|
console.log(`Round-trip time: ${Date.now() - data} ms`);
|
||||||
|
|
||||||
|
setTimeout(function timeout() {
|
||||||
|
ws.send(Date.now());
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use the Node.js streams API
|
||||||
|
|
||||||
|
```js
|
||||||
|
import WebSocket, { createWebSocketStream } from 'ws';
|
||||||
|
|
||||||
|
const ws = new WebSocket('wss://websocket-echo.com/');
|
||||||
|
|
||||||
|
const duplex = createWebSocketStream(ws, { encoding: 'utf8' });
|
||||||
|
|
||||||
|
duplex.on('error', console.error);
|
||||||
|
|
||||||
|
duplex.pipe(process.stdout);
|
||||||
|
process.stdin.pipe(duplex);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Other examples
|
||||||
|
|
||||||
|
For a full example with a browser client communicating with a ws server, see the
|
||||||
|
examples folder.
|
||||||
|
|
||||||
|
Otherwise, see the test cases.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### How to get the IP address of the client?
|
||||||
|
|
||||||
|
The remote IP address can be obtained from the raw socket.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { WebSocketServer } from 'ws';
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({ port: 8080 });
|
||||||
|
|
||||||
|
wss.on('connection', function connection(ws, req) {
|
||||||
|
const ip = req.socket.remoteAddress;
|
||||||
|
|
||||||
|
ws.on('error', console.error);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
When the server runs behind a proxy like NGINX, the de-facto standard is to use
|
||||||
|
the `X-Forwarded-For` header.
|
||||||
|
|
||||||
|
```js
|
||||||
|
wss.on('connection', function connection(ws, req) {
|
||||||
|
const ip = req.headers['x-forwarded-for'].split(',')[0].trim();
|
||||||
|
|
||||||
|
ws.on('error', console.error);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### How to detect and close broken connections?
|
||||||
|
|
||||||
|
Sometimes the link between the server and the client can be interrupted in a way
|
||||||
|
that keeps both the server and the client unaware of the broken state of the
|
||||||
|
connection (e.g. when pulling the cord).
|
||||||
|
|
||||||
|
In these cases ping messages can be used as a means to verify that the remote
|
||||||
|
endpoint is still responsive.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { WebSocketServer } from 'ws';
|
||||||
|
|
||||||
|
function heartbeat() {
|
||||||
|
this.isAlive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({ port: 8080 });
|
||||||
|
|
||||||
|
wss.on('connection', function connection(ws) {
|
||||||
|
ws.isAlive = true;
|
||||||
|
ws.on('error', console.error);
|
||||||
|
ws.on('pong', heartbeat);
|
||||||
|
});
|
||||||
|
|
||||||
|
const interval = setInterval(function ping() {
|
||||||
|
wss.clients.forEach(function each(ws) {
|
||||||
|
if (ws.isAlive === false) return ws.terminate();
|
||||||
|
|
||||||
|
ws.isAlive = false;
|
||||||
|
ws.ping();
|
||||||
|
});
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
wss.on('close', function close() {
|
||||||
|
clearInterval(interval);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Pong messages are automatically sent in response to ping messages as required by
|
||||||
|
the spec.
|
||||||
|
|
||||||
|
Just like the server example above your clients might as well lose connection
|
||||||
|
without knowing it. You might want to add a ping listener on your clients to
|
||||||
|
prevent that. A simple implementation would be:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import WebSocket from 'ws';
|
||||||
|
|
||||||
|
function heartbeat() {
|
||||||
|
clearTimeout(this.pingTimeout);
|
||||||
|
|
||||||
|
// Use `WebSocket#terminate()`, which immediately destroys the connection,
|
||||||
|
// instead of `WebSocket#close()`, which waits for the close timer.
|
||||||
|
// Delay should be equal to the interval at which your server
|
||||||
|
// sends out pings plus a conservative assumption of the latency.
|
||||||
|
this.pingTimeout = setTimeout(() => {
|
||||||
|
this.terminate();
|
||||||
|
}, 30000 + 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new WebSocket('wss://websocket-echo.com/');
|
||||||
|
|
||||||
|
client.on('error', console.error);
|
||||||
|
client.on('open', heartbeat);
|
||||||
|
client.on('ping', heartbeat);
|
||||||
|
client.on('close', function clear() {
|
||||||
|
clearTimeout(this.pingTimeout);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### How to connect via a proxy?
|
||||||
|
|
||||||
|
Use a custom `http.Agent` implementation like [https-proxy-agent][] or
|
||||||
|
[socks-proxy-agent][].
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
We're using the GitHub [releases][changelog] for changelog entries.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](LICENSE)
|
||||||
|
|
||||||
|
[`buffer.isutf8()`]: https://nodejs.org/api/buffer.html#bufferisutf8input
|
||||||
|
[bufferutil]: https://github.com/websockets/bufferutil
|
||||||
|
[changelog]: https://github.com/websockets/ws/releases
|
||||||
|
[client-report]: http://websockets.github.io/ws/autobahn/clients/
|
||||||
|
[https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent
|
||||||
|
[node-zlib-bug]: https://github.com/nodejs/node/issues/8871
|
||||||
|
[node-zlib-deflaterawdocs]:
|
||||||
|
https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options
|
||||||
|
[permessage-deflate]: https://tools.ietf.org/html/rfc7692
|
||||||
|
[server-report]: http://websockets.github.io/ws/autobahn/servers/
|
||||||
|
[session-parse-example]: ./examples/express-session-parse
|
||||||
|
[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent
|
||||||
|
[utf-8-validate]: https://github.com/websockets/utf-8-validate
|
||||||
|
[ws-server-options]: ./doc/ws.md#new-websocketserveroptions-callback
|
||||||
8
system/clearpilot/shell/node_modules/ws/browser.js
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = function () {
|
||||||
|
throw new Error(
|
||||||
|
'ws does not work in the browser. Browser clients must use the native ' +
|
||||||
|
'WebSocket object'
|
||||||
|
);
|
||||||
|
};
|
||||||
13
system/clearpilot/shell/node_modules/ws/index.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const WebSocket = require('./lib/websocket');
|
||||||
|
|
||||||
|
WebSocket.createWebSocketStream = require('./lib/stream');
|
||||||
|
WebSocket.Server = require('./lib/websocket-server');
|
||||||
|
WebSocket.Receiver = require('./lib/receiver');
|
||||||
|
WebSocket.Sender = require('./lib/sender');
|
||||||
|
|
||||||
|
WebSocket.WebSocket = WebSocket;
|
||||||
|
WebSocket.WebSocketServer = WebSocket.Server;
|
||||||
|
|
||||||
|
module.exports = WebSocket;
|
||||||
131
system/clearpilot/shell/node_modules/ws/lib/buffer-util.js
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { EMPTY_BUFFER } = require('./constants');
|
||||||
|
|
||||||
|
const FastBuffer = Buffer[Symbol.species];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges an array of buffers into a new buffer.
|
||||||
|
*
|
||||||
|
* @param {Buffer[]} list The array of buffers to concat
|
||||||
|
* @param {Number} totalLength The total length of buffers in the list
|
||||||
|
* @return {Buffer} The resulting buffer
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
function concat(list, totalLength) {
|
||||||
|
if (list.length === 0) return EMPTY_BUFFER;
|
||||||
|
if (list.length === 1) return list[0];
|
||||||
|
|
||||||
|
const target = Buffer.allocUnsafe(totalLength);
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
const buf = list[i];
|
||||||
|
target.set(buf, offset);
|
||||||
|
offset += buf.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < totalLength) {
|
||||||
|
return new FastBuffer(target.buffer, target.byteOffset, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Masks a buffer using the given mask.
|
||||||
|
*
|
||||||
|
* @param {Buffer} source The buffer to mask
|
||||||
|
* @param {Buffer} mask The mask to use
|
||||||
|
* @param {Buffer} output The buffer where to store the result
|
||||||
|
* @param {Number} offset The offset at which to start writing
|
||||||
|
* @param {Number} length The number of bytes to mask.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
function _mask(source, mask, output, offset, length) {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
output[offset + i] = source[i] ^ mask[i & 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unmasks a buffer using the given mask.
|
||||||
|
*
|
||||||
|
* @param {Buffer} buffer The buffer to unmask
|
||||||
|
* @param {Buffer} mask The mask to use
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
function _unmask(buffer, mask) {
|
||||||
|
for (let i = 0; i < buffer.length; i++) {
|
||||||
|
buffer[i] ^= mask[i & 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a buffer to an `ArrayBuffer`.
|
||||||
|
*
|
||||||
|
* @param {Buffer} buf The buffer to convert
|
||||||
|
* @return {ArrayBuffer} Converted buffer
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
function toArrayBuffer(buf) {
|
||||||
|
if (buf.length === buf.buffer.byteLength) {
|
||||||
|
return buf.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts `data` to a `Buffer`.
|
||||||
|
*
|
||||||
|
* @param {*} data The data to convert
|
||||||
|
* @return {Buffer} The buffer
|
||||||
|
* @throws {TypeError}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
function toBuffer(data) {
|
||||||
|
toBuffer.readOnly = true;
|
||||||
|
|
||||||
|
if (Buffer.isBuffer(data)) return data;
|
||||||
|
|
||||||
|
let buf;
|
||||||
|
|
||||||
|
if (data instanceof ArrayBuffer) {
|
||||||
|
buf = new FastBuffer(data);
|
||||||
|
} else if (ArrayBuffer.isView(data)) {
|
||||||
|
buf = new FastBuffer(data.buffer, data.byteOffset, data.byteLength);
|
||||||
|
} else {
|
||||||
|
buf = Buffer.from(data);
|
||||||
|
toBuffer.readOnly = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
concat,
|
||||||
|
mask: _mask,
|
||||||
|
toArrayBuffer,
|
||||||
|
toBuffer,
|
||||||
|
unmask: _unmask
|
||||||
|
};
|
||||||
|
|
||||||
|
/* istanbul ignore else */
|
||||||
|
if (!process.env.WS_NO_BUFFER_UTIL) {
|
||||||
|
try {
|
||||||
|
const bufferUtil = require('bufferutil');
|
||||||
|
|
||||||
|
module.exports.mask = function (source, mask, output, offset, length) {
|
||||||
|
if (length < 48) _mask(source, mask, output, offset, length);
|
||||||
|
else bufferUtil.mask(source, mask, output, offset, length);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.unmask = function (buffer, mask) {
|
||||||
|
if (buffer.length < 32) _unmask(buffer, mask);
|
||||||
|
else bufferUtil.unmask(buffer, mask);
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
// Continue regardless of the error.
|
||||||
|
}
|
||||||
|
}
|
||||||
12
system/clearpilot/shell/node_modules/ws/lib/constants.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'],
|
||||||
|
EMPTY_BUFFER: Buffer.alloc(0),
|
||||||
|
GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
|
||||||
|
kForOnEventAttribute: Symbol('kIsForOnEventAttribute'),
|
||||||
|
kListener: Symbol('kListener'),
|
||||||
|
kStatusCode: Symbol('status-code'),
|
||||||
|
kWebSocket: Symbol('websocket'),
|
||||||
|
NOOP: () => {}
|
||||||
|
};
|
||||||
292
system/clearpilot/shell/node_modules/ws/lib/event-target.js
generated
vendored
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { kForOnEventAttribute, kListener } = require('./constants');
|
||||||
|
|
||||||
|
const kCode = Symbol('kCode');
|
||||||
|
const kData = Symbol('kData');
|
||||||
|
const kError = Symbol('kError');
|
||||||
|
const kMessage = Symbol('kMessage');
|
||||||
|
const kReason = Symbol('kReason');
|
||||||
|
const kTarget = Symbol('kTarget');
|
||||||
|
const kType = Symbol('kType');
|
||||||
|
const kWasClean = Symbol('kWasClean');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing an event.
|
||||||
|
*/
|
||||||
|
class Event {
|
||||||
|
/**
|
||||||
|
* Create a new `Event`.
|
||||||
|
*
|
||||||
|
* @param {String} type The name of the event
|
||||||
|
* @throws {TypeError} If the `type` argument is not specified
|
||||||
|
*/
|
||||||
|
constructor(type) {
|
||||||
|
this[kTarget] = null;
|
||||||
|
this[kType] = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {*}
|
||||||
|
*/
|
||||||
|
get target() {
|
||||||
|
return this[kTarget];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
get type() {
|
||||||
|
return this[kType];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(Event.prototype, 'target', { enumerable: true });
|
||||||
|
Object.defineProperty(Event.prototype, 'type', { enumerable: true });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing a close event.
|
||||||
|
*
|
||||||
|
* @extends Event
|
||||||
|
*/
|
||||||
|
class CloseEvent extends Event {
|
||||||
|
/**
|
||||||
|
* Create a new `CloseEvent`.
|
||||||
|
*
|
||||||
|
* @param {String} type The name of the event
|
||||||
|
* @param {Object} [options] A dictionary object that allows for setting
|
||||||
|
* attributes via object members of the same name
|
||||||
|
* @param {Number} [options.code=0] The status code explaining why the
|
||||||
|
* connection was closed
|
||||||
|
* @param {String} [options.reason=''] A human-readable string explaining why
|
||||||
|
* the connection was closed
|
||||||
|
* @param {Boolean} [options.wasClean=false] Indicates whether or not the
|
||||||
|
* connection was cleanly closed
|
||||||
|
*/
|
||||||
|
constructor(type, options = {}) {
|
||||||
|
super(type);
|
||||||
|
|
||||||
|
this[kCode] = options.code === undefined ? 0 : options.code;
|
||||||
|
this[kReason] = options.reason === undefined ? '' : options.reason;
|
||||||
|
this[kWasClean] = options.wasClean === undefined ? false : options.wasClean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
get code() {
|
||||||
|
return this[kCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
get reason() {
|
||||||
|
return this[kReason];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
get wasClean() {
|
||||||
|
return this[kWasClean];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(CloseEvent.prototype, 'code', { enumerable: true });
|
||||||
|
Object.defineProperty(CloseEvent.prototype, 'reason', { enumerable: true });
|
||||||
|
Object.defineProperty(CloseEvent.prototype, 'wasClean', { enumerable: true });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing an error event.
|
||||||
|
*
|
||||||
|
* @extends Event
|
||||||
|
*/
|
||||||
|
class ErrorEvent extends Event {
|
||||||
|
/**
|
||||||
|
* Create a new `ErrorEvent`.
|
||||||
|
*
|
||||||
|
* @param {String} type The name of the event
|
||||||
|
* @param {Object} [options] A dictionary object that allows for setting
|
||||||
|
* attributes via object members of the same name
|
||||||
|
* @param {*} [options.error=null] The error that generated this event
|
||||||
|
* @param {String} [options.message=''] The error message
|
||||||
|
*/
|
||||||
|
constructor(type, options = {}) {
|
||||||
|
super(type);
|
||||||
|
|
||||||
|
this[kError] = options.error === undefined ? null : options.error;
|
||||||
|
this[kMessage] = options.message === undefined ? '' : options.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {*}
|
||||||
|
*/
|
||||||
|
get error() {
|
||||||
|
return this[kError];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
get message() {
|
||||||
|
return this[kMessage];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(ErrorEvent.prototype, 'error', { enumerable: true });
|
||||||
|
Object.defineProperty(ErrorEvent.prototype, 'message', { enumerable: true });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing a message event.
|
||||||
|
*
|
||||||
|
* @extends Event
|
||||||
|
*/
|
||||||
|
class MessageEvent extends Event {
|
||||||
|
/**
|
||||||
|
* Create a new `MessageEvent`.
|
||||||
|
*
|
||||||
|
* @param {String} type The name of the event
|
||||||
|
* @param {Object} [options] A dictionary object that allows for setting
|
||||||
|
* attributes via object members of the same name
|
||||||
|
* @param {*} [options.data=null] The message content
|
||||||
|
*/
|
||||||
|
constructor(type, options = {}) {
|
||||||
|
super(type);
|
||||||
|
|
||||||
|
this[kData] = options.data === undefined ? null : options.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {*}
|
||||||
|
*/
|
||||||
|
get data() {
|
||||||
|
return this[kData];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(MessageEvent.prototype, 'data', { enumerable: true });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This provides methods for emulating the `EventTarget` interface. It's not
|
||||||
|
* meant to be used directly.
|
||||||
|
*
|
||||||
|
* @mixin
|
||||||
|
*/
|
||||||
|
const EventTarget = {
|
||||||
|
/**
|
||||||
|
* Register an event listener.
|
||||||
|
*
|
||||||
|
* @param {String} type A string representing the event type to listen for
|
||||||
|
* @param {(Function|Object)} handler The listener to add
|
||||||
|
* @param {Object} [options] An options object specifies characteristics about
|
||||||
|
* the event listener
|
||||||
|
* @param {Boolean} [options.once=false] A `Boolean` indicating that the
|
||||||
|
* listener should be invoked at most once after being added. If `true`,
|
||||||
|
* the listener would be automatically removed when invoked.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
addEventListener(type, handler, options = {}) {
|
||||||
|
for (const listener of this.listeners(type)) {
|
||||||
|
if (
|
||||||
|
!options[kForOnEventAttribute] &&
|
||||||
|
listener[kListener] === handler &&
|
||||||
|
!listener[kForOnEventAttribute]
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
if (type === 'message') {
|
||||||
|
wrapper = function onMessage(data, isBinary) {
|
||||||
|
const event = new MessageEvent('message', {
|
||||||
|
data: isBinary ? data : data.toString()
|
||||||
|
});
|
||||||
|
|
||||||
|
event[kTarget] = this;
|
||||||
|
callListener(handler, this, event);
|
||||||
|
};
|
||||||
|
} else if (type === 'close') {
|
||||||
|
wrapper = function onClose(code, message) {
|
||||||
|
const event = new CloseEvent('close', {
|
||||||
|
code,
|
||||||
|
reason: message.toString(),
|
||||||
|
wasClean: this._closeFrameReceived && this._closeFrameSent
|
||||||
|
});
|
||||||
|
|
||||||
|
event[kTarget] = this;
|
||||||
|
callListener(handler, this, event);
|
||||||
|
};
|
||||||
|
} else if (type === 'error') {
|
||||||
|
wrapper = function onError(error) {
|
||||||
|
const event = new ErrorEvent('error', {
|
||||||
|
error,
|
||||||
|
message: error.message
|
||||||
|
});
|
||||||
|
|
||||||
|
event[kTarget] = this;
|
||||||
|
callListener(handler, this, event);
|
||||||
|
};
|
||||||
|
} else if (type === 'open') {
|
||||||
|
wrapper = function onOpen() {
|
||||||
|
const event = new Event('open');
|
||||||
|
|
||||||
|
event[kTarget] = this;
|
||||||
|
callListener(handler, this, event);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper[kForOnEventAttribute] = !!options[kForOnEventAttribute];
|
||||||
|
wrapper[kListener] = handler;
|
||||||
|
|
||||||
|
if (options.once) {
|
||||||
|
this.once(type, wrapper);
|
||||||
|
} else {
|
||||||
|
this.on(type, wrapper);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an event listener.
|
||||||
|
*
|
||||||
|
* @param {String} type A string representing the event type to remove
|
||||||
|
* @param {(Function|Object)} handler The listener to remove
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
removeEventListener(type, handler) {
|
||||||
|
for (const listener of this.listeners(type)) {
|
||||||
|
if (listener[kListener] === handler && !listener[kForOnEventAttribute]) {
|
||||||
|
this.removeListener(type, listener);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
CloseEvent,
|
||||||
|
ErrorEvent,
|
||||||
|
Event,
|
||||||
|
EventTarget,
|
||||||
|
MessageEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call an event listener
|
||||||
|
*
|
||||||
|
* @param {(Function|Object)} listener The listener to call
|
||||||
|
* @param {*} thisArg The value to use as `this`` when calling the listener
|
||||||
|
* @param {Event} event The event to pass to the listener
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function callListener(listener, thisArg, event) {
|
||||||
|
if (typeof listener === 'object' && listener.handleEvent) {
|
||||||
|
listener.handleEvent.call(listener, event);
|
||||||
|
} else {
|
||||||
|
listener.call(thisArg, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
203
system/clearpilot/shell/node_modules/ws/lib/extension.js
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { tokenChars } = require('./validation');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an offer to the map of extension offers or a parameter to the map of
|
||||||
|
* parameters.
|
||||||
|
*
|
||||||
|
* @param {Object} dest The map of extension offers or parameters
|
||||||
|
* @param {String} name The extension or parameter name
|
||||||
|
* @param {(Object|Boolean|String)} elem The extension parameters or the
|
||||||
|
* parameter value
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function push(dest, name, elem) {
|
||||||
|
if (dest[name] === undefined) dest[name] = [elem];
|
||||||
|
else dest[name].push(elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the `Sec-WebSocket-Extensions` header into an object.
|
||||||
|
*
|
||||||
|
* @param {String} header The field value of the header
|
||||||
|
* @return {Object} The parsed object
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
function parse(header) {
|
||||||
|
const offers = Object.create(null);
|
||||||
|
let params = Object.create(null);
|
||||||
|
let mustUnescape = false;
|
||||||
|
let isEscaping = false;
|
||||||
|
let inQuotes = false;
|
||||||
|
let extensionName;
|
||||||
|
let paramName;
|
||||||
|
let start = -1;
|
||||||
|
let code = -1;
|
||||||
|
let end = -1;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
for (; i < header.length; i++) {
|
||||||
|
code = header.charCodeAt(i);
|
||||||
|
|
||||||
|
if (extensionName === undefined) {
|
||||||
|
if (end === -1 && tokenChars[code] === 1) {
|
||||||
|
if (start === -1) start = i;
|
||||||
|
} else if (
|
||||||
|
i !== 0 &&
|
||||||
|
(code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */
|
||||||
|
) {
|
||||||
|
if (end === -1 && start !== -1) end = i;
|
||||||
|
} else if (code === 0x3b /* ';' */ || code === 0x2c /* ',' */) {
|
||||||
|
if (start === -1) {
|
||||||
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end === -1) end = i;
|
||||||
|
const name = header.slice(start, end);
|
||||||
|
if (code === 0x2c) {
|
||||||
|
push(offers, name, params);
|
||||||
|
params = Object.create(null);
|
||||||
|
} else {
|
||||||
|
extensionName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
start = end = -1;
|
||||||
|
} else {
|
||||||
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||||
|
}
|
||||||
|
} else if (paramName === undefined) {
|
||||||
|
if (end === -1 && tokenChars[code] === 1) {
|
||||||
|
if (start === -1) start = i;
|
||||||
|
} else if (code === 0x20 || code === 0x09) {
|
||||||
|
if (end === -1 && start !== -1) end = i;
|
||||||
|
} else if (code === 0x3b || code === 0x2c) {
|
||||||
|
if (start === -1) {
|
||||||
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end === -1) end = i;
|
||||||
|
push(params, header.slice(start, end), true);
|
||||||
|
if (code === 0x2c) {
|
||||||
|
push(offers, extensionName, params);
|
||||||
|
params = Object.create(null);
|
||||||
|
extensionName = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
start = end = -1;
|
||||||
|
} else if (code === 0x3d /* '=' */ && start !== -1 && end === -1) {
|
||||||
|
paramName = header.slice(start, i);
|
||||||
|
start = end = -1;
|
||||||
|
} else {
|
||||||
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//
|
||||||
|
// The value of a quoted-string after unescaping must conform to the
|
||||||
|
// token ABNF, so only token characters are valid.
|
||||||
|
// Ref: https://tools.ietf.org/html/rfc6455#section-9.1
|
||||||
|
//
|
||||||
|
if (isEscaping) {
|
||||||
|
if (tokenChars[code] !== 1) {
|
||||||
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||||
|
}
|
||||||
|
if (start === -1) start = i;
|
||||||
|
else if (!mustUnescape) mustUnescape = true;
|
||||||
|
isEscaping = false;
|
||||||
|
} else if (inQuotes) {
|
||||||
|
if (tokenChars[code] === 1) {
|
||||||
|
if (start === -1) start = i;
|
||||||
|
} else if (code === 0x22 /* '"' */ && start !== -1) {
|
||||||
|
inQuotes = false;
|
||||||
|
end = i;
|
||||||
|
} else if (code === 0x5c /* '\' */) {
|
||||||
|
isEscaping = true;
|
||||||
|
} else {
|
||||||
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||||
|
}
|
||||||
|
} else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) {
|
||||||
|
inQuotes = true;
|
||||||
|
} else if (end === -1 && tokenChars[code] === 1) {
|
||||||
|
if (start === -1) start = i;
|
||||||
|
} else if (start !== -1 && (code === 0x20 || code === 0x09)) {
|
||||||
|
if (end === -1) end = i;
|
||||||
|
} else if (code === 0x3b || code === 0x2c) {
|
||||||
|
if (start === -1) {
|
||||||
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end === -1) end = i;
|
||||||
|
let value = header.slice(start, end);
|
||||||
|
if (mustUnescape) {
|
||||||
|
value = value.replace(/\\/g, '');
|
||||||
|
mustUnescape = false;
|
||||||
|
}
|
||||||
|
push(params, paramName, value);
|
||||||
|
if (code === 0x2c) {
|
||||||
|
push(offers, extensionName, params);
|
||||||
|
params = Object.create(null);
|
||||||
|
extensionName = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
paramName = undefined;
|
||||||
|
start = end = -1;
|
||||||
|
} else {
|
||||||
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start === -1 || inQuotes || code === 0x20 || code === 0x09) {
|
||||||
|
throw new SyntaxError('Unexpected end of input');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end === -1) end = i;
|
||||||
|
const token = header.slice(start, end);
|
||||||
|
if (extensionName === undefined) {
|
||||||
|
push(offers, token, params);
|
||||||
|
} else {
|
||||||
|
if (paramName === undefined) {
|
||||||
|
push(params, token, true);
|
||||||
|
} else if (mustUnescape) {
|
||||||
|
push(params, paramName, token.replace(/\\/g, ''));
|
||||||
|
} else {
|
||||||
|
push(params, paramName, token);
|
||||||
|
}
|
||||||
|
push(offers, extensionName, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return offers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the `Sec-WebSocket-Extensions` header field value.
|
||||||
|
*
|
||||||
|
* @param {Object} extensions The map of extensions and parameters to format
|
||||||
|
* @return {String} A string representing the given object
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
function format(extensions) {
|
||||||
|
return Object.keys(extensions)
|
||||||
|
.map((extension) => {
|
||||||
|
let configurations = extensions[extension];
|
||||||
|
if (!Array.isArray(configurations)) configurations = [configurations];
|
||||||
|
return configurations
|
||||||
|
.map((params) => {
|
||||||
|
return [extension]
|
||||||
|
.concat(
|
||||||
|
Object.keys(params).map((k) => {
|
||||||
|
let values = params[k];
|
||||||
|
if (!Array.isArray(values)) values = [values];
|
||||||
|
return values
|
||||||
|
.map((v) => (v === true ? k : `${k}=${v}`))
|
||||||
|
.join('; ');
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.join('; ');
|
||||||
|
})
|
||||||
|
.join(', ');
|
||||||
|
})
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { format, parse };
|
||||||
55
system/clearpilot/shell/node_modules/ws/lib/limiter.js
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const kDone = Symbol('kDone');
|
||||||
|
const kRun = Symbol('kRun');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A very simple job queue with adjustable concurrency. Adapted from
|
||||||
|
* https://github.com/STRML/async-limiter
|
||||||
|
*/
|
||||||
|
class Limiter {
|
||||||
|
/**
|
||||||
|
* Creates a new `Limiter`.
|
||||||
|
*
|
||||||
|
* @param {Number} [concurrency=Infinity] The maximum number of jobs allowed
|
||||||
|
* to run concurrently
|
||||||
|
*/
|
||||||
|
constructor(concurrency) {
|
||||||
|
this[kDone] = () => {
|
||||||
|
this.pending--;
|
||||||
|
this[kRun]();
|
||||||
|
};
|
||||||
|
this.concurrency = concurrency || Infinity;
|
||||||
|
this.jobs = [];
|
||||||
|
this.pending = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a job to the queue.
|
||||||
|
*
|
||||||
|
* @param {Function} job The job to run
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
add(job) {
|
||||||
|
this.jobs.push(job);
|
||||||
|
this[kRun]();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a job from the queue and runs it if possible.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
[kRun]() {
|
||||||
|
if (this.pending === this.concurrency) return;
|
||||||
|
|
||||||
|
if (this.jobs.length) {
|
||||||
|
const job = this.jobs.shift();
|
||||||
|
|
||||||
|
this.pending++;
|
||||||
|
job(this[kDone]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Limiter;
|
||||||
514
system/clearpilot/shell/node_modules/ws/lib/permessage-deflate.js
generated
vendored
Normal file
@@ -0,0 +1,514 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const zlib = require('zlib');
|
||||||
|
|
||||||
|
const bufferUtil = require('./buffer-util');
|
||||||
|
const Limiter = require('./limiter');
|
||||||
|
const { kStatusCode } = require('./constants');
|
||||||
|
|
||||||
|
const FastBuffer = Buffer[Symbol.species];
|
||||||
|
const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
|
||||||
|
const kPerMessageDeflate = Symbol('permessage-deflate');
|
||||||
|
const kTotalLength = Symbol('total-length');
|
||||||
|
const kCallback = Symbol('callback');
|
||||||
|
const kBuffers = Symbol('buffers');
|
||||||
|
const kError = Symbol('error');
|
||||||
|
|
||||||
|
//
|
||||||
|
// We limit zlib concurrency, which prevents severe memory fragmentation
|
||||||
|
// as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913
|
||||||
|
// and https://github.com/websockets/ws/issues/1202
|
||||||
|
//
|
||||||
|
// Intentionally global; it's the global thread pool that's an issue.
|
||||||
|
//
|
||||||
|
let zlibLimiter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* permessage-deflate implementation.
|
||||||
|
*/
|
||||||
|
class PerMessageDeflate {
|
||||||
|
/**
|
||||||
|
* Creates a PerMessageDeflate instance.
|
||||||
|
*
|
||||||
|
* @param {Object} [options] Configuration options
|
||||||
|
* @param {(Boolean|Number)} [options.clientMaxWindowBits] Advertise support
|
||||||
|
* for, or request, a custom client window size
|
||||||
|
* @param {Boolean} [options.clientNoContextTakeover=false] Advertise/
|
||||||
|
* acknowledge disabling of client context takeover
|
||||||
|
* @param {Number} [options.concurrencyLimit=10] The number of concurrent
|
||||||
|
* calls to zlib
|
||||||
|
* @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the
|
||||||
|
* use of a custom server window size
|
||||||
|
* @param {Boolean} [options.serverNoContextTakeover=false] Request/accept
|
||||||
|
* disabling of server context takeover
|
||||||
|
* @param {Number} [options.threshold=1024] Size (in bytes) below which
|
||||||
|
* messages should not be compressed if context takeover is disabled
|
||||||
|
* @param {Object} [options.zlibDeflateOptions] Options to pass to zlib on
|
||||||
|
* deflate
|
||||||
|
* @param {Object} [options.zlibInflateOptions] Options to pass to zlib on
|
||||||
|
* inflate
|
||||||
|
* @param {Boolean} [isServer=false] Create the instance in either server or
|
||||||
|
* client mode
|
||||||
|
* @param {Number} [maxPayload=0] The maximum allowed message length
|
||||||
|
*/
|
||||||
|
constructor(options, isServer, maxPayload) {
|
||||||
|
this._maxPayload = maxPayload | 0;
|
||||||
|
this._options = options || {};
|
||||||
|
this._threshold =
|
||||||
|
this._options.threshold !== undefined ? this._options.threshold : 1024;
|
||||||
|
this._isServer = !!isServer;
|
||||||
|
this._deflate = null;
|
||||||
|
this._inflate = null;
|
||||||
|
|
||||||
|
this.params = null;
|
||||||
|
|
||||||
|
if (!zlibLimiter) {
|
||||||
|
const concurrency =
|
||||||
|
this._options.concurrencyLimit !== undefined
|
||||||
|
? this._options.concurrencyLimit
|
||||||
|
: 10;
|
||||||
|
zlibLimiter = new Limiter(concurrency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
static get extensionName() {
|
||||||
|
return 'permessage-deflate';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an extension negotiation offer.
|
||||||
|
*
|
||||||
|
* @return {Object} Extension parameters
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
offer() {
|
||||||
|
const params = {};
|
||||||
|
|
||||||
|
if (this._options.serverNoContextTakeover) {
|
||||||
|
params.server_no_context_takeover = true;
|
||||||
|
}
|
||||||
|
if (this._options.clientNoContextTakeover) {
|
||||||
|
params.client_no_context_takeover = true;
|
||||||
|
}
|
||||||
|
if (this._options.serverMaxWindowBits) {
|
||||||
|
params.server_max_window_bits = this._options.serverMaxWindowBits;
|
||||||
|
}
|
||||||
|
if (this._options.clientMaxWindowBits) {
|
||||||
|
params.client_max_window_bits = this._options.clientMaxWindowBits;
|
||||||
|
} else if (this._options.clientMaxWindowBits == null) {
|
||||||
|
params.client_max_window_bits = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept an extension negotiation offer/response.
|
||||||
|
*
|
||||||
|
* @param {Array} configurations The extension negotiation offers/reponse
|
||||||
|
* @return {Object} Accepted configuration
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
accept(configurations) {
|
||||||
|
configurations = this.normalizeParams(configurations);
|
||||||
|
|
||||||
|
this.params = this._isServer
|
||||||
|
? this.acceptAsServer(configurations)
|
||||||
|
: this.acceptAsClient(configurations);
|
||||||
|
|
||||||
|
return this.params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases all resources used by the extension.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
cleanup() {
|
||||||
|
if (this._inflate) {
|
||||||
|
this._inflate.close();
|
||||||
|
this._inflate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._deflate) {
|
||||||
|
const callback = this._deflate[kCallback];
|
||||||
|
|
||||||
|
this._deflate.close();
|
||||||
|
this._deflate = null;
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback(
|
||||||
|
new Error(
|
||||||
|
'The deflate stream was closed while data was being processed'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept an extension negotiation offer.
|
||||||
|
*
|
||||||
|
* @param {Array} offers The extension negotiation offers
|
||||||
|
* @return {Object} Accepted configuration
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
acceptAsServer(offers) {
|
||||||
|
const opts = this._options;
|
||||||
|
const accepted = offers.find((params) => {
|
||||||
|
if (
|
||||||
|
(opts.serverNoContextTakeover === false &&
|
||||||
|
params.server_no_context_takeover) ||
|
||||||
|
(params.server_max_window_bits &&
|
||||||
|
(opts.serverMaxWindowBits === false ||
|
||||||
|
(typeof opts.serverMaxWindowBits === 'number' &&
|
||||||
|
opts.serverMaxWindowBits > params.server_max_window_bits))) ||
|
||||||
|
(typeof opts.clientMaxWindowBits === 'number' &&
|
||||||
|
!params.client_max_window_bits)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!accepted) {
|
||||||
|
throw new Error('None of the extension offers can be accepted');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.serverNoContextTakeover) {
|
||||||
|
accepted.server_no_context_takeover = true;
|
||||||
|
}
|
||||||
|
if (opts.clientNoContextTakeover) {
|
||||||
|
accepted.client_no_context_takeover = true;
|
||||||
|
}
|
||||||
|
if (typeof opts.serverMaxWindowBits === 'number') {
|
||||||
|
accepted.server_max_window_bits = opts.serverMaxWindowBits;
|
||||||
|
}
|
||||||
|
if (typeof opts.clientMaxWindowBits === 'number') {
|
||||||
|
accepted.client_max_window_bits = opts.clientMaxWindowBits;
|
||||||
|
} else if (
|
||||||
|
accepted.client_max_window_bits === true ||
|
||||||
|
opts.clientMaxWindowBits === false
|
||||||
|
) {
|
||||||
|
delete accepted.client_max_window_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accepted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept the extension negotiation response.
|
||||||
|
*
|
||||||
|
* @param {Array} response The extension negotiation response
|
||||||
|
* @return {Object} Accepted configuration
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
acceptAsClient(response) {
|
||||||
|
const params = response[0];
|
||||||
|
|
||||||
|
if (
|
||||||
|
this._options.clientNoContextTakeover === false &&
|
||||||
|
params.client_no_context_takeover
|
||||||
|
) {
|
||||||
|
throw new Error('Unexpected parameter "client_no_context_takeover"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params.client_max_window_bits) {
|
||||||
|
if (typeof this._options.clientMaxWindowBits === 'number') {
|
||||||
|
params.client_max_window_bits = this._options.clientMaxWindowBits;
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
this._options.clientMaxWindowBits === false ||
|
||||||
|
(typeof this._options.clientMaxWindowBits === 'number' &&
|
||||||
|
params.client_max_window_bits > this._options.clientMaxWindowBits)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'Unexpected or invalid parameter "client_max_window_bits"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize parameters.
|
||||||
|
*
|
||||||
|
* @param {Array} configurations The extension negotiation offers/reponse
|
||||||
|
* @return {Array} The offers/response with normalized parameters
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
normalizeParams(configurations) {
|
||||||
|
configurations.forEach((params) => {
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
let value = params[key];
|
||||||
|
|
||||||
|
if (value.length > 1) {
|
||||||
|
throw new Error(`Parameter "${key}" must have only a single value`);
|
||||||
|
}
|
||||||
|
|
||||||
|
value = value[0];
|
||||||
|
|
||||||
|
if (key === 'client_max_window_bits') {
|
||||||
|
if (value !== true) {
|
||||||
|
const num = +value;
|
||||||
|
if (!Number.isInteger(num) || num < 8 || num > 15) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Invalid value for parameter "${key}": ${value}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
value = num;
|
||||||
|
} else if (!this._isServer) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Invalid value for parameter "${key}": ${value}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (key === 'server_max_window_bits') {
|
||||||
|
const num = +value;
|
||||||
|
if (!Number.isInteger(num) || num < 8 || num > 15) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Invalid value for parameter "${key}": ${value}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
value = num;
|
||||||
|
} else if (
|
||||||
|
key === 'client_no_context_takeover' ||
|
||||||
|
key === 'server_no_context_takeover'
|
||||||
|
) {
|
||||||
|
if (value !== true) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Invalid value for parameter "${key}": ${value}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown parameter "${key}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
params[key] = value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return configurations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decompress data. Concurrency limited.
|
||||||
|
*
|
||||||
|
* @param {Buffer} data Compressed data
|
||||||
|
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||||
|
* @param {Function} callback Callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
decompress(data, fin, callback) {
|
||||||
|
zlibLimiter.add((done) => {
|
||||||
|
this._decompress(data, fin, (err, result) => {
|
||||||
|
done();
|
||||||
|
callback(err, result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress data. Concurrency limited.
|
||||||
|
*
|
||||||
|
* @param {(Buffer|String)} data Data to compress
|
||||||
|
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||||
|
* @param {Function} callback Callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
compress(data, fin, callback) {
|
||||||
|
zlibLimiter.add((done) => {
|
||||||
|
this._compress(data, fin, (err, result) => {
|
||||||
|
done();
|
||||||
|
callback(err, result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decompress data.
|
||||||
|
*
|
||||||
|
* @param {Buffer} data Compressed data
|
||||||
|
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||||
|
* @param {Function} callback Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_decompress(data, fin, callback) {
|
||||||
|
const endpoint = this._isServer ? 'client' : 'server';
|
||||||
|
|
||||||
|
if (!this._inflate) {
|
||||||
|
const key = `${endpoint}_max_window_bits`;
|
||||||
|
const windowBits =
|
||||||
|
typeof this.params[key] !== 'number'
|
||||||
|
? zlib.Z_DEFAULT_WINDOWBITS
|
||||||
|
: this.params[key];
|
||||||
|
|
||||||
|
this._inflate = zlib.createInflateRaw({
|
||||||
|
...this._options.zlibInflateOptions,
|
||||||
|
windowBits
|
||||||
|
});
|
||||||
|
this._inflate[kPerMessageDeflate] = this;
|
||||||
|
this._inflate[kTotalLength] = 0;
|
||||||
|
this._inflate[kBuffers] = [];
|
||||||
|
this._inflate.on('error', inflateOnError);
|
||||||
|
this._inflate.on('data', inflateOnData);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._inflate[kCallback] = callback;
|
||||||
|
|
||||||
|
this._inflate.write(data);
|
||||||
|
if (fin) this._inflate.write(TRAILER);
|
||||||
|
|
||||||
|
this._inflate.flush(() => {
|
||||||
|
const err = this._inflate[kError];
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
this._inflate.close();
|
||||||
|
this._inflate = null;
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = bufferUtil.concat(
|
||||||
|
this._inflate[kBuffers],
|
||||||
|
this._inflate[kTotalLength]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this._inflate._readableState.endEmitted) {
|
||||||
|
this._inflate.close();
|
||||||
|
this._inflate = null;
|
||||||
|
} else {
|
||||||
|
this._inflate[kTotalLength] = 0;
|
||||||
|
this._inflate[kBuffers] = [];
|
||||||
|
|
||||||
|
if (fin && this.params[`${endpoint}_no_context_takeover`]) {
|
||||||
|
this._inflate.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress data.
|
||||||
|
*
|
||||||
|
* @param {(Buffer|String)} data Data to compress
|
||||||
|
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||||
|
* @param {Function} callback Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_compress(data, fin, callback) {
|
||||||
|
const endpoint = this._isServer ? 'server' : 'client';
|
||||||
|
|
||||||
|
if (!this._deflate) {
|
||||||
|
const key = `${endpoint}_max_window_bits`;
|
||||||
|
const windowBits =
|
||||||
|
typeof this.params[key] !== 'number'
|
||||||
|
? zlib.Z_DEFAULT_WINDOWBITS
|
||||||
|
: this.params[key];
|
||||||
|
|
||||||
|
this._deflate = zlib.createDeflateRaw({
|
||||||
|
...this._options.zlibDeflateOptions,
|
||||||
|
windowBits
|
||||||
|
});
|
||||||
|
|
||||||
|
this._deflate[kTotalLength] = 0;
|
||||||
|
this._deflate[kBuffers] = [];
|
||||||
|
|
||||||
|
this._deflate.on('data', deflateOnData);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._deflate[kCallback] = callback;
|
||||||
|
|
||||||
|
this._deflate.write(data);
|
||||||
|
this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
|
||||||
|
if (!this._deflate) {
|
||||||
|
//
|
||||||
|
// The deflate stream was closed while data was being processed.
|
||||||
|
//
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = bufferUtil.concat(
|
||||||
|
this._deflate[kBuffers],
|
||||||
|
this._deflate[kTotalLength]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fin) {
|
||||||
|
data = new FastBuffer(data.buffer, data.byteOffset, data.length - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Ensure that the callback will not be called again in
|
||||||
|
// `PerMessageDeflate#cleanup()`.
|
||||||
|
//
|
||||||
|
this._deflate[kCallback] = null;
|
||||||
|
|
||||||
|
this._deflate[kTotalLength] = 0;
|
||||||
|
this._deflate[kBuffers] = [];
|
||||||
|
|
||||||
|
if (fin && this.params[`${endpoint}_no_context_takeover`]) {
|
||||||
|
this._deflate.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PerMessageDeflate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The listener of the `zlib.DeflateRaw` stream `'data'` event.
|
||||||
|
*
|
||||||
|
* @param {Buffer} chunk A chunk of data
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function deflateOnData(chunk) {
|
||||||
|
this[kBuffers].push(chunk);
|
||||||
|
this[kTotalLength] += chunk.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The listener of the `zlib.InflateRaw` stream `'data'` event.
|
||||||
|
*
|
||||||
|
* @param {Buffer} chunk A chunk of data
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function inflateOnData(chunk) {
|
||||||
|
this[kTotalLength] += chunk.length;
|
||||||
|
|
||||||
|
if (
|
||||||
|
this[kPerMessageDeflate]._maxPayload < 1 ||
|
||||||
|
this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload
|
||||||
|
) {
|
||||||
|
this[kBuffers].push(chunk);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this[kError] = new RangeError('Max payload size exceeded');
|
||||||
|
this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
|
||||||
|
this[kError][kStatusCode] = 1009;
|
||||||
|
this.removeListener('data', inflateOnData);
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The listener of the `zlib.InflateRaw` stream `'error'` event.
|
||||||
|
*
|
||||||
|
* @param {Error} err The emitted error
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function inflateOnError(err) {
|
||||||
|
//
|
||||||
|
// There is no need to call `Zlib#close()` as the handle is automatically
|
||||||
|
// closed when an error is emitted.
|
||||||
|
//
|
||||||
|
this[kPerMessageDeflate]._inflate = null;
|
||||||
|
err[kStatusCode] = 1007;
|
||||||
|
this[kCallback](err);
|
||||||
|
}
|
||||||
742
system/clearpilot/shell/node_modules/ws/lib/receiver.js
generated
vendored
Normal file
@@ -0,0 +1,742 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Writable } = require('stream');
|
||||||
|
|
||||||
|
const PerMessageDeflate = require('./permessage-deflate');
|
||||||
|
const {
|
||||||
|
BINARY_TYPES,
|
||||||
|
EMPTY_BUFFER,
|
||||||
|
kStatusCode,
|
||||||
|
kWebSocket
|
||||||
|
} = require('./constants');
|
||||||
|
const { concat, toArrayBuffer, unmask } = require('./buffer-util');
|
||||||
|
const { isValidStatusCode, isValidUTF8 } = require('./validation');
|
||||||
|
|
||||||
|
const FastBuffer = Buffer[Symbol.species];
|
||||||
|
const promise = Promise.resolve();
|
||||||
|
|
||||||
|
//
|
||||||
|
// `queueMicrotask()` is not available in Node.js < 11.
|
||||||
|
//
|
||||||
|
const queueTask =
|
||||||
|
typeof queueMicrotask === 'function' ? queueMicrotask : queueMicrotaskShim;
|
||||||
|
|
||||||
|
const GET_INFO = 0;
|
||||||
|
const GET_PAYLOAD_LENGTH_16 = 1;
|
||||||
|
const GET_PAYLOAD_LENGTH_64 = 2;
|
||||||
|
const GET_MASK = 3;
|
||||||
|
const GET_DATA = 4;
|
||||||
|
const INFLATING = 5;
|
||||||
|
const DEFER_EVENT = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HyBi Receiver implementation.
|
||||||
|
*
|
||||||
|
* @extends Writable
|
||||||
|
*/
|
||||||
|
class Receiver extends Writable {
|
||||||
|
/**
|
||||||
|
* Creates a Receiver instance.
|
||||||
|
*
|
||||||
|
* @param {Object} [options] Options object
|
||||||
|
* @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether
|
||||||
|
* any of the `'message'`, `'ping'`, and `'pong'` events can be emitted
|
||||||
|
* multiple times in the same tick
|
||||||
|
* @param {String} [options.binaryType=nodebuffer] The type for binary data
|
||||||
|
* @param {Object} [options.extensions] An object containing the negotiated
|
||||||
|
* extensions
|
||||||
|
* @param {Boolean} [options.isServer=false] Specifies whether to operate in
|
||||||
|
* client or server mode
|
||||||
|
* @param {Number} [options.maxPayload=0] The maximum allowed message length
|
||||||
|
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
||||||
|
* not to skip UTF-8 validation for text and close messages
|
||||||
|
*/
|
||||||
|
constructor(options = {}) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._allowSynchronousEvents = !!options.allowSynchronousEvents;
|
||||||
|
this._binaryType = options.binaryType || BINARY_TYPES[0];
|
||||||
|
this._extensions = options.extensions || {};
|
||||||
|
this._isServer = !!options.isServer;
|
||||||
|
this._maxPayload = options.maxPayload | 0;
|
||||||
|
this._skipUTF8Validation = !!options.skipUTF8Validation;
|
||||||
|
this[kWebSocket] = undefined;
|
||||||
|
|
||||||
|
this._bufferedBytes = 0;
|
||||||
|
this._buffers = [];
|
||||||
|
|
||||||
|
this._compressed = false;
|
||||||
|
this._payloadLength = 0;
|
||||||
|
this._mask = undefined;
|
||||||
|
this._fragmented = 0;
|
||||||
|
this._masked = false;
|
||||||
|
this._fin = false;
|
||||||
|
this._opcode = 0;
|
||||||
|
|
||||||
|
this._totalPayloadLength = 0;
|
||||||
|
this._messageLength = 0;
|
||||||
|
this._fragments = [];
|
||||||
|
|
||||||
|
this._errored = false;
|
||||||
|
this._loop = false;
|
||||||
|
this._state = GET_INFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements `Writable.prototype._write()`.
|
||||||
|
*
|
||||||
|
* @param {Buffer} chunk The chunk of data to write
|
||||||
|
* @param {String} encoding The character encoding of `chunk`
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_write(chunk, encoding, cb) {
|
||||||
|
if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
|
||||||
|
|
||||||
|
this._bufferedBytes += chunk.length;
|
||||||
|
this._buffers.push(chunk);
|
||||||
|
this.startLoop(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consumes `n` bytes from the buffered data.
|
||||||
|
*
|
||||||
|
* @param {Number} n The number of bytes to consume
|
||||||
|
* @return {Buffer} The consumed bytes
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
consume(n) {
|
||||||
|
this._bufferedBytes -= n;
|
||||||
|
|
||||||
|
if (n === this._buffers[0].length) return this._buffers.shift();
|
||||||
|
|
||||||
|
if (n < this._buffers[0].length) {
|
||||||
|
const buf = this._buffers[0];
|
||||||
|
this._buffers[0] = new FastBuffer(
|
||||||
|
buf.buffer,
|
||||||
|
buf.byteOffset + n,
|
||||||
|
buf.length - n
|
||||||
|
);
|
||||||
|
|
||||||
|
return new FastBuffer(buf.buffer, buf.byteOffset, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dst = Buffer.allocUnsafe(n);
|
||||||
|
|
||||||
|
do {
|
||||||
|
const buf = this._buffers[0];
|
||||||
|
const offset = dst.length - n;
|
||||||
|
|
||||||
|
if (n >= buf.length) {
|
||||||
|
dst.set(this._buffers.shift(), offset);
|
||||||
|
} else {
|
||||||
|
dst.set(new Uint8Array(buf.buffer, buf.byteOffset, n), offset);
|
||||||
|
this._buffers[0] = new FastBuffer(
|
||||||
|
buf.buffer,
|
||||||
|
buf.byteOffset + n,
|
||||||
|
buf.length - n
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
n -= buf.length;
|
||||||
|
} while (n > 0);
|
||||||
|
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the parsing loop.
|
||||||
|
*
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
startLoop(cb) {
|
||||||
|
this._loop = true;
|
||||||
|
|
||||||
|
do {
|
||||||
|
switch (this._state) {
|
||||||
|
case GET_INFO:
|
||||||
|
this.getInfo(cb);
|
||||||
|
break;
|
||||||
|
case GET_PAYLOAD_LENGTH_16:
|
||||||
|
this.getPayloadLength16(cb);
|
||||||
|
break;
|
||||||
|
case GET_PAYLOAD_LENGTH_64:
|
||||||
|
this.getPayloadLength64(cb);
|
||||||
|
break;
|
||||||
|
case GET_MASK:
|
||||||
|
this.getMask();
|
||||||
|
break;
|
||||||
|
case GET_DATA:
|
||||||
|
this.getData(cb);
|
||||||
|
break;
|
||||||
|
case INFLATING:
|
||||||
|
case DEFER_EVENT:
|
||||||
|
this._loop = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} while (this._loop);
|
||||||
|
|
||||||
|
if (!this._errored) cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the first two bytes of a frame.
|
||||||
|
*
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
getInfo(cb) {
|
||||||
|
if (this._bufferedBytes < 2) {
|
||||||
|
this._loop = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buf = this.consume(2);
|
||||||
|
|
||||||
|
if ((buf[0] & 0x30) !== 0x00) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
'RSV2 and RSV3 must be clear',
|
||||||
|
true,
|
||||||
|
1002,
|
||||||
|
'WS_ERR_UNEXPECTED_RSV_2_3'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const compressed = (buf[0] & 0x40) === 0x40;
|
||||||
|
|
||||||
|
if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
'RSV1 must be clear',
|
||||||
|
true,
|
||||||
|
1002,
|
||||||
|
'WS_ERR_UNEXPECTED_RSV_1'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._fin = (buf[0] & 0x80) === 0x80;
|
||||||
|
this._opcode = buf[0] & 0x0f;
|
||||||
|
this._payloadLength = buf[1] & 0x7f;
|
||||||
|
|
||||||
|
if (this._opcode === 0x00) {
|
||||||
|
if (compressed) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
'RSV1 must be clear',
|
||||||
|
true,
|
||||||
|
1002,
|
||||||
|
'WS_ERR_UNEXPECTED_RSV_1'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._fragmented) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
'invalid opcode 0',
|
||||||
|
true,
|
||||||
|
1002,
|
||||||
|
'WS_ERR_INVALID_OPCODE'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._opcode = this._fragmented;
|
||||||
|
} else if (this._opcode === 0x01 || this._opcode === 0x02) {
|
||||||
|
if (this._fragmented) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
`invalid opcode ${this._opcode}`,
|
||||||
|
true,
|
||||||
|
1002,
|
||||||
|
'WS_ERR_INVALID_OPCODE'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._compressed = compressed;
|
||||||
|
} else if (this._opcode > 0x07 && this._opcode < 0x0b) {
|
||||||
|
if (!this._fin) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
'FIN must be set',
|
||||||
|
true,
|
||||||
|
1002,
|
||||||
|
'WS_ERR_EXPECTED_FIN'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compressed) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
'RSV1 must be clear',
|
||||||
|
true,
|
||||||
|
1002,
|
||||||
|
'WS_ERR_UNEXPECTED_RSV_1'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this._payloadLength > 0x7d ||
|
||||||
|
(this._opcode === 0x08 && this._payloadLength === 1)
|
||||||
|
) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
`invalid payload length ${this._payloadLength}`,
|
||||||
|
true,
|
||||||
|
1002,
|
||||||
|
'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
`invalid opcode ${this._opcode}`,
|
||||||
|
true,
|
||||||
|
1002,
|
||||||
|
'WS_ERR_INVALID_OPCODE'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._fin && !this._fragmented) this._fragmented = this._opcode;
|
||||||
|
this._masked = (buf[1] & 0x80) === 0x80;
|
||||||
|
|
||||||
|
if (this._isServer) {
|
||||||
|
if (!this._masked) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
'MASK must be set',
|
||||||
|
true,
|
||||||
|
1002,
|
||||||
|
'WS_ERR_EXPECTED_MASK'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (this._masked) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
'MASK must be clear',
|
||||||
|
true,
|
||||||
|
1002,
|
||||||
|
'WS_ERR_UNEXPECTED_MASK'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16;
|
||||||
|
else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64;
|
||||||
|
else this.haveLength(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets extended payload length (7+16).
|
||||||
|
*
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
getPayloadLength16(cb) {
|
||||||
|
if (this._bufferedBytes < 2) {
|
||||||
|
this._loop = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._payloadLength = this.consume(2).readUInt16BE(0);
|
||||||
|
this.haveLength(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets extended payload length (7+64).
|
||||||
|
*
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
getPayloadLength64(cb) {
|
||||||
|
if (this._bufferedBytes < 8) {
|
||||||
|
this._loop = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buf = this.consume(8);
|
||||||
|
const num = buf.readUInt32BE(0);
|
||||||
|
|
||||||
|
//
|
||||||
|
// The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
|
||||||
|
// if payload length is greater than this number.
|
||||||
|
//
|
||||||
|
if (num > Math.pow(2, 53 - 32) - 1) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
'Unsupported WebSocket frame: payload length > 2^53 - 1',
|
||||||
|
false,
|
||||||
|
1009,
|
||||||
|
'WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4);
|
||||||
|
this.haveLength(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload length has been read.
|
||||||
|
*
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
haveLength(cb) {
|
||||||
|
if (this._payloadLength && this._opcode < 0x08) {
|
||||||
|
this._totalPayloadLength += this._payloadLength;
|
||||||
|
if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
'Max payload size exceeded',
|
||||||
|
false,
|
||||||
|
1009,
|
||||||
|
'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._masked) this._state = GET_MASK;
|
||||||
|
else this._state = GET_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads mask bytes.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
getMask() {
|
||||||
|
if (this._bufferedBytes < 4) {
|
||||||
|
this._loop = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._mask = this.consume(4);
|
||||||
|
this._state = GET_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads data bytes.
|
||||||
|
*
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
getData(cb) {
|
||||||
|
let data = EMPTY_BUFFER;
|
||||||
|
|
||||||
|
if (this._payloadLength) {
|
||||||
|
if (this._bufferedBytes < this._payloadLength) {
|
||||||
|
this._loop = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = this.consume(this._payloadLength);
|
||||||
|
|
||||||
|
if (
|
||||||
|
this._masked &&
|
||||||
|
(this._mask[0] | this._mask[1] | this._mask[2] | this._mask[3]) !== 0
|
||||||
|
) {
|
||||||
|
unmask(data, this._mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._opcode > 0x07) {
|
||||||
|
this.controlMessage(data, cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._compressed) {
|
||||||
|
this._state = INFLATING;
|
||||||
|
this.decompress(data, cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length) {
|
||||||
|
//
|
||||||
|
// This message is not compressed so its length is the sum of the payload
|
||||||
|
// length of all fragments.
|
||||||
|
//
|
||||||
|
this._messageLength = this._totalPayloadLength;
|
||||||
|
this._fragments.push(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataMessage(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decompresses data.
|
||||||
|
*
|
||||||
|
* @param {Buffer} data Compressed data
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
decompress(data, cb) {
|
||||||
|
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
|
||||||
|
|
||||||
|
perMessageDeflate.decompress(data, this._fin, (err, buf) => {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
if (buf.length) {
|
||||||
|
this._messageLength += buf.length;
|
||||||
|
if (this._messageLength > this._maxPayload && this._maxPayload > 0) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
'Max payload size exceeded',
|
||||||
|
false,
|
||||||
|
1009,
|
||||||
|
'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._fragments.push(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataMessage(cb);
|
||||||
|
if (this._state === GET_INFO) this.startLoop(cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a data message.
|
||||||
|
*
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
dataMessage(cb) {
|
||||||
|
if (!this._fin) {
|
||||||
|
this._state = GET_INFO;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageLength = this._messageLength;
|
||||||
|
const fragments = this._fragments;
|
||||||
|
|
||||||
|
this._totalPayloadLength = 0;
|
||||||
|
this._messageLength = 0;
|
||||||
|
this._fragmented = 0;
|
||||||
|
this._fragments = [];
|
||||||
|
|
||||||
|
if (this._opcode === 2) {
|
||||||
|
let data;
|
||||||
|
|
||||||
|
if (this._binaryType === 'nodebuffer') {
|
||||||
|
data = concat(fragments, messageLength);
|
||||||
|
} else if (this._binaryType === 'arraybuffer') {
|
||||||
|
data = toArrayBuffer(concat(fragments, messageLength));
|
||||||
|
} else {
|
||||||
|
data = fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// If the state is `INFLATING`, it means that the frame data was
|
||||||
|
// decompressed asynchronously, so there is no need to defer the event
|
||||||
|
// as it will be emitted asynchronously anyway.
|
||||||
|
//
|
||||||
|
if (this._state === INFLATING || this._allowSynchronousEvents) {
|
||||||
|
this.emit('message', data, true);
|
||||||
|
this._state = GET_INFO;
|
||||||
|
} else {
|
||||||
|
this._state = DEFER_EVENT;
|
||||||
|
queueTask(() => {
|
||||||
|
this.emit('message', data, true);
|
||||||
|
this._state = GET_INFO;
|
||||||
|
this.startLoop(cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const buf = concat(fragments, messageLength);
|
||||||
|
|
||||||
|
if (!this._skipUTF8Validation && !isValidUTF8(buf)) {
|
||||||
|
const error = this.createError(
|
||||||
|
Error,
|
||||||
|
'invalid UTF-8 sequence',
|
||||||
|
true,
|
||||||
|
1007,
|
||||||
|
'WS_ERR_INVALID_UTF8'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._state === INFLATING || this._allowSynchronousEvents) {
|
||||||
|
this.emit('message', buf, false);
|
||||||
|
this._state = GET_INFO;
|
||||||
|
} else {
|
||||||
|
this._state = DEFER_EVENT;
|
||||||
|
queueTask(() => {
|
||||||
|
this.emit('message', buf, false);
|
||||||
|
this._state = GET_INFO;
|
||||||
|
this.startLoop(cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a control message.
|
||||||
|
*
|
||||||
|
* @param {Buffer} data Data to handle
|
||||||
|
* @return {(Error|RangeError|undefined)} A possible error
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
controlMessage(data, cb) {
|
||||||
|
if (this._opcode === 0x08) {
|
||||||
|
if (data.length === 0) {
|
||||||
|
this._loop = false;
|
||||||
|
this.emit('conclude', 1005, EMPTY_BUFFER);
|
||||||
|
this.end();
|
||||||
|
} else {
|
||||||
|
const code = data.readUInt16BE(0);
|
||||||
|
|
||||||
|
if (!isValidStatusCode(code)) {
|
||||||
|
const error = this.createError(
|
||||||
|
RangeError,
|
||||||
|
`invalid status code ${code}`,
|
||||||
|
true,
|
||||||
|
1002,
|
||||||
|
'WS_ERR_INVALID_CLOSE_CODE'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buf = new FastBuffer(
|
||||||
|
data.buffer,
|
||||||
|
data.byteOffset + 2,
|
||||||
|
data.length - 2
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!this._skipUTF8Validation && !isValidUTF8(buf)) {
|
||||||
|
const error = this.createError(
|
||||||
|
Error,
|
||||||
|
'invalid UTF-8 sequence',
|
||||||
|
true,
|
||||||
|
1007,
|
||||||
|
'WS_ERR_INVALID_UTF8'
|
||||||
|
);
|
||||||
|
|
||||||
|
cb(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loop = false;
|
||||||
|
this.emit('conclude', code, buf);
|
||||||
|
this.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._state = GET_INFO;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._allowSynchronousEvents) {
|
||||||
|
this.emit(this._opcode === 0x09 ? 'ping' : 'pong', data);
|
||||||
|
this._state = GET_INFO;
|
||||||
|
} else {
|
||||||
|
this._state = DEFER_EVENT;
|
||||||
|
queueTask(() => {
|
||||||
|
this.emit(this._opcode === 0x09 ? 'ping' : 'pong', data);
|
||||||
|
this._state = GET_INFO;
|
||||||
|
this.startLoop(cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an error object.
|
||||||
|
*
|
||||||
|
* @param {function(new:Error|RangeError)} ErrorCtor The error constructor
|
||||||
|
* @param {String} message The error message
|
||||||
|
* @param {Boolean} prefix Specifies whether or not to add a default prefix to
|
||||||
|
* `message`
|
||||||
|
* @param {Number} statusCode The status code
|
||||||
|
* @param {String} errorCode The exposed error code
|
||||||
|
* @return {(Error|RangeError)} The error
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
createError(ErrorCtor, message, prefix, statusCode, errorCode) {
|
||||||
|
this._loop = false;
|
||||||
|
this._errored = true;
|
||||||
|
|
||||||
|
const err = new ErrorCtor(
|
||||||
|
prefix ? `Invalid WebSocket frame: ${message}` : message
|
||||||
|
);
|
||||||
|
|
||||||
|
Error.captureStackTrace(err, this.createError);
|
||||||
|
err.code = errorCode;
|
||||||
|
err[kStatusCode] = statusCode;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Receiver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A shim for `queueMicrotask()`.
|
||||||
|
*
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
*/
|
||||||
|
function queueMicrotaskShim(cb) {
|
||||||
|
promise.then(cb).catch(throwErrorNextTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws an error.
|
||||||
|
*
|
||||||
|
* @param {Error} err The error to throw
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function throwError(err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws an error in the next tick.
|
||||||
|
*
|
||||||
|
* @param {Error} err The error to throw
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function throwErrorNextTick(err) {
|
||||||
|
process.nextTick(throwError, err);
|
||||||
|
}
|
||||||
477
system/clearpilot/shell/node_modules/ws/lib/sender.js
generated
vendored
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex" }] */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Duplex } = require('stream');
|
||||||
|
const { randomFillSync } = require('crypto');
|
||||||
|
|
||||||
|
const PerMessageDeflate = require('./permessage-deflate');
|
||||||
|
const { EMPTY_BUFFER } = require('./constants');
|
||||||
|
const { isValidStatusCode } = require('./validation');
|
||||||
|
const { mask: applyMask, toBuffer } = require('./buffer-util');
|
||||||
|
|
||||||
|
const kByteLength = Symbol('kByteLength');
|
||||||
|
const maskBuffer = Buffer.alloc(4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HyBi Sender implementation.
|
||||||
|
*/
|
||||||
|
class Sender {
|
||||||
|
/**
|
||||||
|
* Creates a Sender instance.
|
||||||
|
*
|
||||||
|
* @param {Duplex} socket The connection socket
|
||||||
|
* @param {Object} [extensions] An object containing the negotiated extensions
|
||||||
|
* @param {Function} [generateMask] The function used to generate the masking
|
||||||
|
* key
|
||||||
|
*/
|
||||||
|
constructor(socket, extensions, generateMask) {
|
||||||
|
this._extensions = extensions || {};
|
||||||
|
|
||||||
|
if (generateMask) {
|
||||||
|
this._generateMask = generateMask;
|
||||||
|
this._maskBuffer = Buffer.alloc(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._socket = socket;
|
||||||
|
|
||||||
|
this._firstFragment = true;
|
||||||
|
this._compress = false;
|
||||||
|
|
||||||
|
this._bufferedBytes = 0;
|
||||||
|
this._deflating = false;
|
||||||
|
this._queue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frames a piece of data according to the HyBi WebSocket protocol.
|
||||||
|
*
|
||||||
|
* @param {(Buffer|String)} data The data to frame
|
||||||
|
* @param {Object} options Options object
|
||||||
|
* @param {Boolean} [options.fin=false] Specifies whether or not to set the
|
||||||
|
* FIN bit
|
||||||
|
* @param {Function} [options.generateMask] The function used to generate the
|
||||||
|
* masking key
|
||||||
|
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
|
||||||
|
* `data`
|
||||||
|
* @param {Buffer} [options.maskBuffer] The buffer used to store the masking
|
||||||
|
* key
|
||||||
|
* @param {Number} options.opcode The opcode
|
||||||
|
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
|
||||||
|
* modified
|
||||||
|
* @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
|
||||||
|
* RSV1 bit
|
||||||
|
* @return {(Buffer|String)[]} The framed data
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
static frame(data, options) {
|
||||||
|
let mask;
|
||||||
|
let merge = false;
|
||||||
|
let offset = 2;
|
||||||
|
let skipMasking = false;
|
||||||
|
|
||||||
|
if (options.mask) {
|
||||||
|
mask = options.maskBuffer || maskBuffer;
|
||||||
|
|
||||||
|
if (options.generateMask) {
|
||||||
|
options.generateMask(mask);
|
||||||
|
} else {
|
||||||
|
randomFillSync(mask, 0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
skipMasking = (mask[0] | mask[1] | mask[2] | mask[3]) === 0;
|
||||||
|
offset = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dataLength;
|
||||||
|
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
if (
|
||||||
|
(!options.mask || skipMasking) &&
|
||||||
|
options[kByteLength] !== undefined
|
||||||
|
) {
|
||||||
|
dataLength = options[kByteLength];
|
||||||
|
} else {
|
||||||
|
data = Buffer.from(data);
|
||||||
|
dataLength = data.length;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dataLength = data.length;
|
||||||
|
merge = options.mask && options.readOnly && !skipMasking;
|
||||||
|
}
|
||||||
|
|
||||||
|
let payloadLength = dataLength;
|
||||||
|
|
||||||
|
if (dataLength >= 65536) {
|
||||||
|
offset += 8;
|
||||||
|
payloadLength = 127;
|
||||||
|
} else if (dataLength > 125) {
|
||||||
|
offset += 2;
|
||||||
|
payloadLength = 126;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = Buffer.allocUnsafe(merge ? dataLength + offset : offset);
|
||||||
|
|
||||||
|
target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
|
||||||
|
if (options.rsv1) target[0] |= 0x40;
|
||||||
|
|
||||||
|
target[1] = payloadLength;
|
||||||
|
|
||||||
|
if (payloadLength === 126) {
|
||||||
|
target.writeUInt16BE(dataLength, 2);
|
||||||
|
} else if (payloadLength === 127) {
|
||||||
|
target[2] = target[3] = 0;
|
||||||
|
target.writeUIntBE(dataLength, 4, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.mask) return [target, data];
|
||||||
|
|
||||||
|
target[1] |= 0x80;
|
||||||
|
target[offset - 4] = mask[0];
|
||||||
|
target[offset - 3] = mask[1];
|
||||||
|
target[offset - 2] = mask[2];
|
||||||
|
target[offset - 1] = mask[3];
|
||||||
|
|
||||||
|
if (skipMasking) return [target, data];
|
||||||
|
|
||||||
|
if (merge) {
|
||||||
|
applyMask(data, mask, target, offset, dataLength);
|
||||||
|
return [target];
|
||||||
|
}
|
||||||
|
|
||||||
|
applyMask(data, mask, data, 0, dataLength);
|
||||||
|
return [target, data];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a close message to the other peer.
|
||||||
|
*
|
||||||
|
* @param {Number} [code] The status code component of the body
|
||||||
|
* @param {(String|Buffer)} [data] The message component of the body
|
||||||
|
* @param {Boolean} [mask=false] Specifies whether or not to mask the message
|
||||||
|
* @param {Function} [cb] Callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
close(code, data, mask, cb) {
|
||||||
|
let buf;
|
||||||
|
|
||||||
|
if (code === undefined) {
|
||||||
|
buf = EMPTY_BUFFER;
|
||||||
|
} else if (typeof code !== 'number' || !isValidStatusCode(code)) {
|
||||||
|
throw new TypeError('First argument must be a valid error code number');
|
||||||
|
} else if (data === undefined || !data.length) {
|
||||||
|
buf = Buffer.allocUnsafe(2);
|
||||||
|
buf.writeUInt16BE(code, 0);
|
||||||
|
} else {
|
||||||
|
const length = Buffer.byteLength(data);
|
||||||
|
|
||||||
|
if (length > 123) {
|
||||||
|
throw new RangeError('The message must not be greater than 123 bytes');
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = Buffer.allocUnsafe(2 + length);
|
||||||
|
buf.writeUInt16BE(code, 0);
|
||||||
|
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
buf.write(data, 2);
|
||||||
|
} else {
|
||||||
|
buf.set(data, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
[kByteLength]: buf.length,
|
||||||
|
fin: true,
|
||||||
|
generateMask: this._generateMask,
|
||||||
|
mask,
|
||||||
|
maskBuffer: this._maskBuffer,
|
||||||
|
opcode: 0x08,
|
||||||
|
readOnly: false,
|
||||||
|
rsv1: false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this._deflating) {
|
||||||
|
this.enqueue([this.dispatch, buf, false, options, cb]);
|
||||||
|
} else {
|
||||||
|
this.sendFrame(Sender.frame(buf, options), cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a ping message to the other peer.
|
||||||
|
*
|
||||||
|
* @param {*} data The message to send
|
||||||
|
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
|
||||||
|
* @param {Function} [cb] Callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
ping(data, mask, cb) {
|
||||||
|
let byteLength;
|
||||||
|
let readOnly;
|
||||||
|
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
byteLength = Buffer.byteLength(data);
|
||||||
|
readOnly = false;
|
||||||
|
} else {
|
||||||
|
data = toBuffer(data);
|
||||||
|
byteLength = data.length;
|
||||||
|
readOnly = toBuffer.readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byteLength > 125) {
|
||||||
|
throw new RangeError('The data size must not be greater than 125 bytes');
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
[kByteLength]: byteLength,
|
||||||
|
fin: true,
|
||||||
|
generateMask: this._generateMask,
|
||||||
|
mask,
|
||||||
|
maskBuffer: this._maskBuffer,
|
||||||
|
opcode: 0x09,
|
||||||
|
readOnly,
|
||||||
|
rsv1: false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this._deflating) {
|
||||||
|
this.enqueue([this.dispatch, data, false, options, cb]);
|
||||||
|
} else {
|
||||||
|
this.sendFrame(Sender.frame(data, options), cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a pong message to the other peer.
|
||||||
|
*
|
||||||
|
* @param {*} data The message to send
|
||||||
|
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
|
||||||
|
* @param {Function} [cb] Callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
pong(data, mask, cb) {
|
||||||
|
let byteLength;
|
||||||
|
let readOnly;
|
||||||
|
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
byteLength = Buffer.byteLength(data);
|
||||||
|
readOnly = false;
|
||||||
|
} else {
|
||||||
|
data = toBuffer(data);
|
||||||
|
byteLength = data.length;
|
||||||
|
readOnly = toBuffer.readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byteLength > 125) {
|
||||||
|
throw new RangeError('The data size must not be greater than 125 bytes');
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
[kByteLength]: byteLength,
|
||||||
|
fin: true,
|
||||||
|
generateMask: this._generateMask,
|
||||||
|
mask,
|
||||||
|
maskBuffer: this._maskBuffer,
|
||||||
|
opcode: 0x0a,
|
||||||
|
readOnly,
|
||||||
|
rsv1: false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this._deflating) {
|
||||||
|
this.enqueue([this.dispatch, data, false, options, cb]);
|
||||||
|
} else {
|
||||||
|
this.sendFrame(Sender.frame(data, options), cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a data message to the other peer.
|
||||||
|
*
|
||||||
|
* @param {*} data The message to send
|
||||||
|
* @param {Object} options Options object
|
||||||
|
* @param {Boolean} [options.binary=false] Specifies whether `data` is binary
|
||||||
|
* or text
|
||||||
|
* @param {Boolean} [options.compress=false] Specifies whether or not to
|
||||||
|
* compress `data`
|
||||||
|
* @param {Boolean} [options.fin=false] Specifies whether the fragment is the
|
||||||
|
* last one
|
||||||
|
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
|
||||||
|
* `data`
|
||||||
|
* @param {Function} [cb] Callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
send(data, options, cb) {
|
||||||
|
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
|
||||||
|
let opcode = options.binary ? 2 : 1;
|
||||||
|
let rsv1 = options.compress;
|
||||||
|
|
||||||
|
let byteLength;
|
||||||
|
let readOnly;
|
||||||
|
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
byteLength = Buffer.byteLength(data);
|
||||||
|
readOnly = false;
|
||||||
|
} else {
|
||||||
|
data = toBuffer(data);
|
||||||
|
byteLength = data.length;
|
||||||
|
readOnly = toBuffer.readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._firstFragment) {
|
||||||
|
this._firstFragment = false;
|
||||||
|
if (
|
||||||
|
rsv1 &&
|
||||||
|
perMessageDeflate &&
|
||||||
|
perMessageDeflate.params[
|
||||||
|
perMessageDeflate._isServer
|
||||||
|
? 'server_no_context_takeover'
|
||||||
|
: 'client_no_context_takeover'
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
rsv1 = byteLength >= perMessageDeflate._threshold;
|
||||||
|
}
|
||||||
|
this._compress = rsv1;
|
||||||
|
} else {
|
||||||
|
rsv1 = false;
|
||||||
|
opcode = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.fin) this._firstFragment = true;
|
||||||
|
|
||||||
|
if (perMessageDeflate) {
|
||||||
|
const opts = {
|
||||||
|
[kByteLength]: byteLength,
|
||||||
|
fin: options.fin,
|
||||||
|
generateMask: this._generateMask,
|
||||||
|
mask: options.mask,
|
||||||
|
maskBuffer: this._maskBuffer,
|
||||||
|
opcode,
|
||||||
|
readOnly,
|
||||||
|
rsv1
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this._deflating) {
|
||||||
|
this.enqueue([this.dispatch, data, this._compress, opts, cb]);
|
||||||
|
} else {
|
||||||
|
this.dispatch(data, this._compress, opts, cb);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.sendFrame(
|
||||||
|
Sender.frame(data, {
|
||||||
|
[kByteLength]: byteLength,
|
||||||
|
fin: options.fin,
|
||||||
|
generateMask: this._generateMask,
|
||||||
|
mask: options.mask,
|
||||||
|
maskBuffer: this._maskBuffer,
|
||||||
|
opcode,
|
||||||
|
readOnly,
|
||||||
|
rsv1: false
|
||||||
|
}),
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a message.
|
||||||
|
*
|
||||||
|
* @param {(Buffer|String)} data The message to send
|
||||||
|
* @param {Boolean} [compress=false] Specifies whether or not to compress
|
||||||
|
* `data`
|
||||||
|
* @param {Object} options Options object
|
||||||
|
* @param {Boolean} [options.fin=false] Specifies whether or not to set the
|
||||||
|
* FIN bit
|
||||||
|
* @param {Function} [options.generateMask] The function used to generate the
|
||||||
|
* masking key
|
||||||
|
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
|
||||||
|
* `data`
|
||||||
|
* @param {Buffer} [options.maskBuffer] The buffer used to store the masking
|
||||||
|
* key
|
||||||
|
* @param {Number} options.opcode The opcode
|
||||||
|
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
|
||||||
|
* modified
|
||||||
|
* @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
|
||||||
|
* RSV1 bit
|
||||||
|
* @param {Function} [cb] Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
dispatch(data, compress, options, cb) {
|
||||||
|
if (!compress) {
|
||||||
|
this.sendFrame(Sender.frame(data, options), cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
|
||||||
|
|
||||||
|
this._bufferedBytes += options[kByteLength];
|
||||||
|
this._deflating = true;
|
||||||
|
perMessageDeflate.compress(data, options.fin, (_, buf) => {
|
||||||
|
if (this._socket.destroyed) {
|
||||||
|
const err = new Error(
|
||||||
|
'The socket was closed while data was being compressed'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (typeof cb === 'function') cb(err);
|
||||||
|
|
||||||
|
for (let i = 0; i < this._queue.length; i++) {
|
||||||
|
const params = this._queue[i];
|
||||||
|
const callback = params[params.length - 1];
|
||||||
|
|
||||||
|
if (typeof callback === 'function') callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._bufferedBytes -= options[kByteLength];
|
||||||
|
this._deflating = false;
|
||||||
|
options.readOnly = false;
|
||||||
|
this.sendFrame(Sender.frame(buf, options), cb);
|
||||||
|
this.dequeue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes queued send operations.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
dequeue() {
|
||||||
|
while (!this._deflating && this._queue.length) {
|
||||||
|
const params = this._queue.shift();
|
||||||
|
|
||||||
|
this._bufferedBytes -= params[3][kByteLength];
|
||||||
|
Reflect.apply(params[0], this, params.slice(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueues a send operation.
|
||||||
|
*
|
||||||
|
* @param {Array} params Send operation parameters.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
enqueue(params) {
|
||||||
|
this._bufferedBytes += params[3][kByteLength];
|
||||||
|
this._queue.push(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a frame.
|
||||||
|
*
|
||||||
|
* @param {Buffer[]} list The frame to send
|
||||||
|
* @param {Function} [cb] Callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
sendFrame(list, cb) {
|
||||||
|
if (list.length === 2) {
|
||||||
|
this._socket.cork();
|
||||||
|
this._socket.write(list[0]);
|
||||||
|
this._socket.write(list[1], cb);
|
||||||
|
this._socket.uncork();
|
||||||
|
} else {
|
||||||
|
this._socket.write(list[0], cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Sender;
|
||||||
159
system/clearpilot/shell/node_modules/ws/lib/stream.js
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Duplex } = require('stream');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the `'close'` event on a stream.
|
||||||
|
*
|
||||||
|
* @param {Duplex} stream The stream.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function emitClose(stream) {
|
||||||
|
stream.emit('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The listener of the `'end'` event.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function duplexOnEnd() {
|
||||||
|
if (!this.destroyed && this._writableState.finished) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The listener of the `'error'` event.
|
||||||
|
*
|
||||||
|
* @param {Error} err The error
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function duplexOnError(err) {
|
||||||
|
this.removeListener('error', duplexOnError);
|
||||||
|
this.destroy();
|
||||||
|
if (this.listenerCount('error') === 0) {
|
||||||
|
// Do not suppress the throwing behavior.
|
||||||
|
this.emit('error', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a `WebSocket` in a duplex stream.
|
||||||
|
*
|
||||||
|
* @param {WebSocket} ws The `WebSocket` to wrap
|
||||||
|
* @param {Object} [options] The options for the `Duplex` constructor
|
||||||
|
* @return {Duplex} The duplex stream
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
function createWebSocketStream(ws, options) {
|
||||||
|
let terminateOnDestroy = true;
|
||||||
|
|
||||||
|
const duplex = new Duplex({
|
||||||
|
...options,
|
||||||
|
autoDestroy: false,
|
||||||
|
emitClose: false,
|
||||||
|
objectMode: false,
|
||||||
|
writableObjectMode: false
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('message', function message(msg, isBinary) {
|
||||||
|
const data =
|
||||||
|
!isBinary && duplex._readableState.objectMode ? msg.toString() : msg;
|
||||||
|
|
||||||
|
if (!duplex.push(data)) ws.pause();
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.once('error', function error(err) {
|
||||||
|
if (duplex.destroyed) return;
|
||||||
|
|
||||||
|
// Prevent `ws.terminate()` from being called by `duplex._destroy()`.
|
||||||
|
//
|
||||||
|
// - If the `'error'` event is emitted before the `'open'` event, then
|
||||||
|
// `ws.terminate()` is a noop as no socket is assigned.
|
||||||
|
// - Otherwise, the error is re-emitted by the listener of the `'error'`
|
||||||
|
// event of the `Receiver` object. The listener already closes the
|
||||||
|
// connection by calling `ws.close()`. This allows a close frame to be
|
||||||
|
// sent to the other peer. If `ws.terminate()` is called right after this,
|
||||||
|
// then the close frame might not be sent.
|
||||||
|
terminateOnDestroy = false;
|
||||||
|
duplex.destroy(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.once('close', function close() {
|
||||||
|
if (duplex.destroyed) return;
|
||||||
|
|
||||||
|
duplex.push(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
duplex._destroy = function (err, callback) {
|
||||||
|
if (ws.readyState === ws.CLOSED) {
|
||||||
|
callback(err);
|
||||||
|
process.nextTick(emitClose, duplex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let called = false;
|
||||||
|
|
||||||
|
ws.once('error', function error(err) {
|
||||||
|
called = true;
|
||||||
|
callback(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.once('close', function close() {
|
||||||
|
if (!called) callback(err);
|
||||||
|
process.nextTick(emitClose, duplex);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (terminateOnDestroy) ws.terminate();
|
||||||
|
};
|
||||||
|
|
||||||
|
duplex._final = function (callback) {
|
||||||
|
if (ws.readyState === ws.CONNECTING) {
|
||||||
|
ws.once('open', function open() {
|
||||||
|
duplex._final(callback);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the value of the `_socket` property is `null` it means that `ws` is a
|
||||||
|
// client websocket and the handshake failed. In fact, when this happens, a
|
||||||
|
// socket is never assigned to the websocket. Wait for the `'error'` event
|
||||||
|
// that will be emitted by the websocket.
|
||||||
|
if (ws._socket === null) return;
|
||||||
|
|
||||||
|
if (ws._socket._writableState.finished) {
|
||||||
|
callback();
|
||||||
|
if (duplex._readableState.endEmitted) duplex.destroy();
|
||||||
|
} else {
|
||||||
|
ws._socket.once('finish', function finish() {
|
||||||
|
// `duplex` is not destroyed here because the `'end'` event will be
|
||||||
|
// emitted on `duplex` after this `'finish'` event. The EOF signaling
|
||||||
|
// `null` chunk is, in fact, pushed when the websocket emits `'close'`.
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
duplex._read = function () {
|
||||||
|
if (ws.isPaused) ws.resume();
|
||||||
|
};
|
||||||
|
|
||||||
|
duplex._write = function (chunk, encoding, callback) {
|
||||||
|
if (ws.readyState === ws.CONNECTING) {
|
||||||
|
ws.once('open', function open() {
|
||||||
|
duplex._write(chunk, encoding, callback);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.send(chunk, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
duplex.on('end', duplexOnEnd);
|
||||||
|
duplex.on('error', duplexOnError);
|
||||||
|
return duplex;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = createWebSocketStream;
|
||||||
62
system/clearpilot/shell/node_modules/ws/lib/subprotocol.js
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { tokenChars } = require('./validation');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the `Sec-WebSocket-Protocol` header into a set of subprotocol names.
|
||||||
|
*
|
||||||
|
* @param {String} header The field value of the header
|
||||||
|
* @return {Set} The subprotocol names
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
function parse(header) {
|
||||||
|
const protocols = new Set();
|
||||||
|
let start = -1;
|
||||||
|
let end = -1;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
for (i; i < header.length; i++) {
|
||||||
|
const code = header.charCodeAt(i);
|
||||||
|
|
||||||
|
if (end === -1 && tokenChars[code] === 1) {
|
||||||
|
if (start === -1) start = i;
|
||||||
|
} else if (
|
||||||
|
i !== 0 &&
|
||||||
|
(code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */
|
||||||
|
) {
|
||||||
|
if (end === -1 && start !== -1) end = i;
|
||||||
|
} else if (code === 0x2c /* ',' */) {
|
||||||
|
if (start === -1) {
|
||||||
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end === -1) end = i;
|
||||||
|
|
||||||
|
const protocol = header.slice(start, end);
|
||||||
|
|
||||||
|
if (protocols.has(protocol)) {
|
||||||
|
throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protocols.add(protocol);
|
||||||
|
start = end = -1;
|
||||||
|
} else {
|
||||||
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start === -1 || end !== -1) {
|
||||||
|
throw new SyntaxError('Unexpected end of input');
|
||||||
|
}
|
||||||
|
|
||||||
|
const protocol = header.slice(start, i);
|
||||||
|
|
||||||
|
if (protocols.has(protocol)) {
|
||||||
|
throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protocols.add(protocol);
|
||||||
|
return protocols;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { parse };
|
||||||
130
system/clearpilot/shell/node_modules/ws/lib/validation.js
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { isUtf8 } = require('buffer');
|
||||||
|
|
||||||
|
//
|
||||||
|
// Allowed token characters:
|
||||||
|
//
|
||||||
|
// '!', '#', '$', '%', '&', ''', '*', '+', '-',
|
||||||
|
// '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~'
|
||||||
|
//
|
||||||
|
// tokenChars[32] === 0 // ' '
|
||||||
|
// tokenChars[33] === 1 // '!'
|
||||||
|
// tokenChars[34] === 0 // '"'
|
||||||
|
// ...
|
||||||
|
//
|
||||||
|
// prettier-ignore
|
||||||
|
const tokenChars = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
|
||||||
|
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a status code is allowed in a close frame.
|
||||||
|
*
|
||||||
|
* @param {Number} code The status code
|
||||||
|
* @return {Boolean} `true` if the status code is valid, else `false`
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
function isValidStatusCode(code) {
|
||||||
|
return (
|
||||||
|
(code >= 1000 &&
|
||||||
|
code <= 1014 &&
|
||||||
|
code !== 1004 &&
|
||||||
|
code !== 1005 &&
|
||||||
|
code !== 1006) ||
|
||||||
|
(code >= 3000 && code <= 4999)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given buffer contains only correct UTF-8.
|
||||||
|
* Ported from https://www.cl.cam.ac.uk/%7Emgk25/ucs/utf8_check.c by
|
||||||
|
* Markus Kuhn.
|
||||||
|
*
|
||||||
|
* @param {Buffer} buf The buffer to check
|
||||||
|
* @return {Boolean} `true` if `buf` contains only correct UTF-8, else `false`
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
function _isValidUTF8(buf) {
|
||||||
|
const len = buf.length;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
if ((buf[i] & 0x80) === 0) {
|
||||||
|
// 0xxxxxxx
|
||||||
|
i++;
|
||||||
|
} else if ((buf[i] & 0xe0) === 0xc0) {
|
||||||
|
// 110xxxxx 10xxxxxx
|
||||||
|
if (
|
||||||
|
i + 1 === len ||
|
||||||
|
(buf[i + 1] & 0xc0) !== 0x80 ||
|
||||||
|
(buf[i] & 0xfe) === 0xc0 // Overlong
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 2;
|
||||||
|
} else if ((buf[i] & 0xf0) === 0xe0) {
|
||||||
|
// 1110xxxx 10xxxxxx 10xxxxxx
|
||||||
|
if (
|
||||||
|
i + 2 >= len ||
|
||||||
|
(buf[i + 1] & 0xc0) !== 0x80 ||
|
||||||
|
(buf[i + 2] & 0xc0) !== 0x80 ||
|
||||||
|
(buf[i] === 0xe0 && (buf[i + 1] & 0xe0) === 0x80) || // Overlong
|
||||||
|
(buf[i] === 0xed && (buf[i + 1] & 0xe0) === 0xa0) // Surrogate (U+D800 - U+DFFF)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 3;
|
||||||
|
} else if ((buf[i] & 0xf8) === 0xf0) {
|
||||||
|
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||||
|
if (
|
||||||
|
i + 3 >= len ||
|
||||||
|
(buf[i + 1] & 0xc0) !== 0x80 ||
|
||||||
|
(buf[i + 2] & 0xc0) !== 0x80 ||
|
||||||
|
(buf[i + 3] & 0xc0) !== 0x80 ||
|
||||||
|
(buf[i] === 0xf0 && (buf[i + 1] & 0xf0) === 0x80) || // Overlong
|
||||||
|
(buf[i] === 0xf4 && buf[i + 1] > 0x8f) ||
|
||||||
|
buf[i] > 0xf4 // > U+10FFFF
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 4;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isValidStatusCode,
|
||||||
|
isValidUTF8: _isValidUTF8,
|
||||||
|
tokenChars
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isUtf8) {
|
||||||
|
module.exports.isValidUTF8 = function (buf) {
|
||||||
|
return buf.length < 24 ? _isValidUTF8(buf) : isUtf8(buf);
|
||||||
|
};
|
||||||
|
} /* istanbul ignore else */ else if (!process.env.WS_NO_UTF_8_VALIDATE) {
|
||||||
|
try {
|
||||||
|
const isValidUTF8 = require('utf-8-validate');
|
||||||
|
|
||||||
|
module.exports.isValidUTF8 = function (buf) {
|
||||||
|
return buf.length < 32 ? _isValidUTF8(buf) : isValidUTF8(buf);
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
// Continue regardless of the error.
|
||||||
|
}
|
||||||
|
}
|
||||||
539
system/clearpilot/shell/node_modules/ws/lib/websocket-server.js
generated
vendored
Normal file
@@ -0,0 +1,539 @@
|
|||||||
|
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex$" }] */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const EventEmitter = require('events');
|
||||||
|
const http = require('http');
|
||||||
|
const { Duplex } = require('stream');
|
||||||
|
const { createHash } = require('crypto');
|
||||||
|
|
||||||
|
const extension = require('./extension');
|
||||||
|
const PerMessageDeflate = require('./permessage-deflate');
|
||||||
|
const subprotocol = require('./subprotocol');
|
||||||
|
const WebSocket = require('./websocket');
|
||||||
|
const { GUID, kWebSocket } = require('./constants');
|
||||||
|
|
||||||
|
const keyRegex = /^[+/0-9A-Za-z]{22}==$/;
|
||||||
|
|
||||||
|
const RUNNING = 0;
|
||||||
|
const CLOSING = 1;
|
||||||
|
const CLOSED = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing a WebSocket server.
|
||||||
|
*
|
||||||
|
* @extends EventEmitter
|
||||||
|
*/
|
||||||
|
class WebSocketServer extends EventEmitter {
|
||||||
|
/**
|
||||||
|
* Create a `WebSocketServer` instance.
|
||||||
|
*
|
||||||
|
* @param {Object} options Configuration options
|
||||||
|
* @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether
|
||||||
|
* any of the `'message'`, `'ping'`, and `'pong'` events can be emitted
|
||||||
|
* multiple times in the same tick
|
||||||
|
* @param {Boolean} [options.autoPong=true] Specifies whether or not to
|
||||||
|
* automatically send a pong in response to a ping
|
||||||
|
* @param {Number} [options.backlog=511] The maximum length of the queue of
|
||||||
|
* pending connections
|
||||||
|
* @param {Boolean} [options.clientTracking=true] Specifies whether or not to
|
||||||
|
* track clients
|
||||||
|
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
||||||
|
* @param {String} [options.host] The hostname where to bind the server
|
||||||
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
||||||
|
* size
|
||||||
|
* @param {Boolean} [options.noServer=false] Enable no server mode
|
||||||
|
* @param {String} [options.path] Accept only connections matching this path
|
||||||
|
* @param {(Boolean|Object)} [options.perMessageDeflate=false] Enable/disable
|
||||||
|
* permessage-deflate
|
||||||
|
* @param {Number} [options.port] The port where to bind the server
|
||||||
|
* @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S
|
||||||
|
* server to use
|
||||||
|
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
||||||
|
* not to skip UTF-8 validation for text and close messages
|
||||||
|
* @param {Function} [options.verifyClient] A hook to reject connections
|
||||||
|
* @param {Function} [options.WebSocket=WebSocket] Specifies the `WebSocket`
|
||||||
|
* class to use. It must be the `WebSocket` class or class that extends it
|
||||||
|
* @param {Function} [callback] A listener for the `listening` event
|
||||||
|
*/
|
||||||
|
constructor(options, callback) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
options = {
|
||||||
|
allowSynchronousEvents: false,
|
||||||
|
autoPong: true,
|
||||||
|
maxPayload: 100 * 1024 * 1024,
|
||||||
|
skipUTF8Validation: false,
|
||||||
|
perMessageDeflate: false,
|
||||||
|
handleProtocols: null,
|
||||||
|
clientTracking: true,
|
||||||
|
verifyClient: null,
|
||||||
|
noServer: false,
|
||||||
|
backlog: null, // use default (511 as implemented in net.js)
|
||||||
|
server: null,
|
||||||
|
host: null,
|
||||||
|
path: null,
|
||||||
|
port: null,
|
||||||
|
WebSocket,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
(options.port == null && !options.server && !options.noServer) ||
|
||||||
|
(options.port != null && (options.server || options.noServer)) ||
|
||||||
|
(options.server && options.noServer)
|
||||||
|
) {
|
||||||
|
throw new TypeError(
|
||||||
|
'One and only one of the "port", "server", or "noServer" options ' +
|
||||||
|
'must be specified'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.port != null) {
|
||||||
|
this._server = http.createServer((req, res) => {
|
||||||
|
const body = http.STATUS_CODES[426];
|
||||||
|
|
||||||
|
res.writeHead(426, {
|
||||||
|
'Content-Length': body.length,
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
});
|
||||||
|
res.end(body);
|
||||||
|
});
|
||||||
|
this._server.listen(
|
||||||
|
options.port,
|
||||||
|
options.host,
|
||||||
|
options.backlog,
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
} else if (options.server) {
|
||||||
|
this._server = options.server;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._server) {
|
||||||
|
const emitConnection = this.emit.bind(this, 'connection');
|
||||||
|
|
||||||
|
this._removeListeners = addListeners(this._server, {
|
||||||
|
listening: this.emit.bind(this, 'listening'),
|
||||||
|
error: this.emit.bind(this, 'error'),
|
||||||
|
upgrade: (req, socket, head) => {
|
||||||
|
this.handleUpgrade(req, socket, head, emitConnection);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.perMessageDeflate === true) options.perMessageDeflate = {};
|
||||||
|
if (options.clientTracking) {
|
||||||
|
this.clients = new Set();
|
||||||
|
this._shouldEmitClose = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options = options;
|
||||||
|
this._state = RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the bound address, the address family name, and port of the server
|
||||||
|
* as reported by the operating system if listening on an IP socket.
|
||||||
|
* If the server is listening on a pipe or UNIX domain socket, the name is
|
||||||
|
* returned as a string.
|
||||||
|
*
|
||||||
|
* @return {(Object|String|null)} The address of the server
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
address() {
|
||||||
|
if (this.options.noServer) {
|
||||||
|
throw new Error('The server is operating in "noServer" mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._server) return null;
|
||||||
|
return this._server.address();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the server from accepting new connections and emit the `'close'` event
|
||||||
|
* when all existing connections are closed.
|
||||||
|
*
|
||||||
|
* @param {Function} [cb] A one-time listener for the `'close'` event
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
close(cb) {
|
||||||
|
if (this._state === CLOSED) {
|
||||||
|
if (cb) {
|
||||||
|
this.once('close', () => {
|
||||||
|
cb(new Error('The server is not running'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
process.nextTick(emitClose, this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cb) this.once('close', cb);
|
||||||
|
|
||||||
|
if (this._state === CLOSING) return;
|
||||||
|
this._state = CLOSING;
|
||||||
|
|
||||||
|
if (this.options.noServer || this.options.server) {
|
||||||
|
if (this._server) {
|
||||||
|
this._removeListeners();
|
||||||
|
this._removeListeners = this._server = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.clients) {
|
||||||
|
if (!this.clients.size) {
|
||||||
|
process.nextTick(emitClose, this);
|
||||||
|
} else {
|
||||||
|
this._shouldEmitClose = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
process.nextTick(emitClose, this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const server = this._server;
|
||||||
|
|
||||||
|
this._removeListeners();
|
||||||
|
this._removeListeners = this._server = null;
|
||||||
|
|
||||||
|
//
|
||||||
|
// The HTTP/S server was created internally. Close it, and rely on its
|
||||||
|
// `'close'` event.
|
||||||
|
//
|
||||||
|
server.close(() => {
|
||||||
|
emitClose(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See if a given request should be handled by this server instance.
|
||||||
|
*
|
||||||
|
* @param {http.IncomingMessage} req Request object to inspect
|
||||||
|
* @return {Boolean} `true` if the request is valid, else `false`
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
shouldHandle(req) {
|
||||||
|
if (this.options.path) {
|
||||||
|
const index = req.url.indexOf('?');
|
||||||
|
const pathname = index !== -1 ? req.url.slice(0, index) : req.url;
|
||||||
|
|
||||||
|
if (pathname !== this.options.path) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a HTTP Upgrade request.
|
||||||
|
*
|
||||||
|
* @param {http.IncomingMessage} req The request object
|
||||||
|
* @param {Duplex} socket The network socket between the server and client
|
||||||
|
* @param {Buffer} head The first packet of the upgraded stream
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
handleUpgrade(req, socket, head, cb) {
|
||||||
|
socket.on('error', socketOnError);
|
||||||
|
|
||||||
|
const key = req.headers['sec-websocket-key'];
|
||||||
|
const version = +req.headers['sec-websocket-version'];
|
||||||
|
|
||||||
|
if (req.method !== 'GET') {
|
||||||
|
const message = 'Invalid HTTP method';
|
||||||
|
abortHandshakeOrEmitwsClientError(this, req, socket, 405, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.headers.upgrade.toLowerCase() !== 'websocket') {
|
||||||
|
const message = 'Invalid Upgrade header';
|
||||||
|
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!key || !keyRegex.test(key)) {
|
||||||
|
const message = 'Missing or invalid Sec-WebSocket-Key header';
|
||||||
|
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version !== 8 && version !== 13) {
|
||||||
|
const message = 'Missing or invalid Sec-WebSocket-Version header';
|
||||||
|
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.shouldHandle(req)) {
|
||||||
|
abortHandshake(socket, 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const secWebSocketProtocol = req.headers['sec-websocket-protocol'];
|
||||||
|
let protocols = new Set();
|
||||||
|
|
||||||
|
if (secWebSocketProtocol !== undefined) {
|
||||||
|
try {
|
||||||
|
protocols = subprotocol.parse(secWebSocketProtocol);
|
||||||
|
} catch (err) {
|
||||||
|
const message = 'Invalid Sec-WebSocket-Protocol header';
|
||||||
|
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const secWebSocketExtensions = req.headers['sec-websocket-extensions'];
|
||||||
|
const extensions = {};
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.options.perMessageDeflate &&
|
||||||
|
secWebSocketExtensions !== undefined
|
||||||
|
) {
|
||||||
|
const perMessageDeflate = new PerMessageDeflate(
|
||||||
|
this.options.perMessageDeflate,
|
||||||
|
true,
|
||||||
|
this.options.maxPayload
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const offers = extension.parse(secWebSocketExtensions);
|
||||||
|
|
||||||
|
if (offers[PerMessageDeflate.extensionName]) {
|
||||||
|
perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
|
||||||
|
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const message =
|
||||||
|
'Invalid or unacceptable Sec-WebSocket-Extensions header';
|
||||||
|
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Optionally call external client verification handler.
|
||||||
|
//
|
||||||
|
if (this.options.verifyClient) {
|
||||||
|
const info = {
|
||||||
|
origin:
|
||||||
|
req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
|
||||||
|
secure: !!(req.socket.authorized || req.socket.encrypted),
|
||||||
|
req
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.options.verifyClient.length === 2) {
|
||||||
|
this.options.verifyClient(info, (verified, code, message, headers) => {
|
||||||
|
if (!verified) {
|
||||||
|
return abortHandshake(socket, code || 401, message, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.completeUpgrade(
|
||||||
|
extensions,
|
||||||
|
key,
|
||||||
|
protocols,
|
||||||
|
req,
|
||||||
|
socket,
|
||||||
|
head,
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.completeUpgrade(extensions, key, protocols, req, socket, head, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade the connection to WebSocket.
|
||||||
|
*
|
||||||
|
* @param {Object} extensions The accepted extensions
|
||||||
|
* @param {String} key The value of the `Sec-WebSocket-Key` header
|
||||||
|
* @param {Set} protocols The subprotocols
|
||||||
|
* @param {http.IncomingMessage} req The request object
|
||||||
|
* @param {Duplex} socket The network socket between the server and client
|
||||||
|
* @param {Buffer} head The first packet of the upgraded stream
|
||||||
|
* @param {Function} cb Callback
|
||||||
|
* @throws {Error} If called more than once with the same socket
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
completeUpgrade(extensions, key, protocols, req, socket, head, cb) {
|
||||||
|
//
|
||||||
|
// Destroy the socket if the client has already sent a FIN packet.
|
||||||
|
//
|
||||||
|
if (!socket.readable || !socket.writable) return socket.destroy();
|
||||||
|
|
||||||
|
if (socket[kWebSocket]) {
|
||||||
|
throw new Error(
|
||||||
|
'server.handleUpgrade() was called more than once with the same ' +
|
||||||
|
'socket, possibly due to a misconfiguration'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
||||||
|
|
||||||
|
const digest = createHash('sha1')
|
||||||
|
.update(key + GUID)
|
||||||
|
.digest('base64');
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
'HTTP/1.1 101 Switching Protocols',
|
||||||
|
'Upgrade: websocket',
|
||||||
|
'Connection: Upgrade',
|
||||||
|
`Sec-WebSocket-Accept: ${digest}`
|
||||||
|
];
|
||||||
|
|
||||||
|
const ws = new this.options.WebSocket(null, undefined, this.options);
|
||||||
|
|
||||||
|
if (protocols.size) {
|
||||||
|
//
|
||||||
|
// Optionally call external protocol selection handler.
|
||||||
|
//
|
||||||
|
const protocol = this.options.handleProtocols
|
||||||
|
? this.options.handleProtocols(protocols, req)
|
||||||
|
: protocols.values().next().value;
|
||||||
|
|
||||||
|
if (protocol) {
|
||||||
|
headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
|
||||||
|
ws._protocol = protocol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extensions[PerMessageDeflate.extensionName]) {
|
||||||
|
const params = extensions[PerMessageDeflate.extensionName].params;
|
||||||
|
const value = extension.format({
|
||||||
|
[PerMessageDeflate.extensionName]: [params]
|
||||||
|
});
|
||||||
|
headers.push(`Sec-WebSocket-Extensions: ${value}`);
|
||||||
|
ws._extensions = extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Allow external modification/inspection of handshake headers.
|
||||||
|
//
|
||||||
|
this.emit('headers', headers, req);
|
||||||
|
|
||||||
|
socket.write(headers.concat('\r\n').join('\r\n'));
|
||||||
|
socket.removeListener('error', socketOnError);
|
||||||
|
|
||||||
|
ws.setSocket(socket, head, {
|
||||||
|
allowSynchronousEvents: this.options.allowSynchronousEvents,
|
||||||
|
maxPayload: this.options.maxPayload,
|
||||||
|
skipUTF8Validation: this.options.skipUTF8Validation
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.clients) {
|
||||||
|
this.clients.add(ws);
|
||||||
|
ws.on('close', () => {
|
||||||
|
this.clients.delete(ws);
|
||||||
|
|
||||||
|
if (this._shouldEmitClose && !this.clients.size) {
|
||||||
|
process.nextTick(emitClose, this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(ws, req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WebSocketServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add event listeners on an `EventEmitter` using a map of <event, listener>
|
||||||
|
* pairs.
|
||||||
|
*
|
||||||
|
* @param {EventEmitter} server The event emitter
|
||||||
|
* @param {Object.<String, Function>} map The listeners to add
|
||||||
|
* @return {Function} A function that will remove the added listeners when
|
||||||
|
* called
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function addListeners(server, map) {
|
||||||
|
for (const event of Object.keys(map)) server.on(event, map[event]);
|
||||||
|
|
||||||
|
return function removeListeners() {
|
||||||
|
for (const event of Object.keys(map)) {
|
||||||
|
server.removeListener(event, map[event]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a `'close'` event on an `EventEmitter`.
|
||||||
|
*
|
||||||
|
* @param {EventEmitter} server The event emitter
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function emitClose(server) {
|
||||||
|
server._state = CLOSED;
|
||||||
|
server.emit('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle socket errors.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function socketOnError() {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the connection when preconditions are not fulfilled.
|
||||||
|
*
|
||||||
|
* @param {Duplex} socket The socket of the upgrade request
|
||||||
|
* @param {Number} code The HTTP response status code
|
||||||
|
* @param {String} [message] The HTTP response body
|
||||||
|
* @param {Object} [headers] Additional HTTP response headers
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function abortHandshake(socket, code, message, headers) {
|
||||||
|
//
|
||||||
|
// The socket is writable unless the user destroyed or ended it before calling
|
||||||
|
// `server.handleUpgrade()` or in the `verifyClient` function, which is a user
|
||||||
|
// error. Handling this does not make much sense as the worst that can happen
|
||||||
|
// is that some of the data written by the user might be discarded due to the
|
||||||
|
// call to `socket.end()` below, which triggers an `'error'` event that in
|
||||||
|
// turn causes the socket to be destroyed.
|
||||||
|
//
|
||||||
|
message = message || http.STATUS_CODES[code];
|
||||||
|
headers = {
|
||||||
|
Connection: 'close',
|
||||||
|
'Content-Type': 'text/html',
|
||||||
|
'Content-Length': Buffer.byteLength(message),
|
||||||
|
...headers
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.once('finish', socket.destroy);
|
||||||
|
|
||||||
|
socket.end(
|
||||||
|
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
|
||||||
|
Object.keys(headers)
|
||||||
|
.map((h) => `${h}: ${headers[h]}`)
|
||||||
|
.join('\r\n') +
|
||||||
|
'\r\n\r\n' +
|
||||||
|
message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a `'wsClientError'` event on a `WebSocketServer` if there is at least
|
||||||
|
* one listener for it, otherwise call `abortHandshake()`.
|
||||||
|
*
|
||||||
|
* @param {WebSocketServer} server The WebSocket server
|
||||||
|
* @param {http.IncomingMessage} req The request object
|
||||||
|
* @param {Duplex} socket The socket of the upgrade request
|
||||||
|
* @param {Number} code The HTTP response status code
|
||||||
|
* @param {String} message The HTTP response body
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function abortHandshakeOrEmitwsClientError(server, req, socket, code, message) {
|
||||||
|
if (server.listenerCount('wsClientError')) {
|
||||||
|
const err = new Error(message);
|
||||||
|
Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError);
|
||||||
|
|
||||||
|
server.emit('wsClientError', err, socket, req);
|
||||||
|
} else {
|
||||||
|
abortHandshake(socket, code, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
1336
system/clearpilot/shell/node_modules/ws/lib/websocket.js
generated
vendored
Normal file
68
system/clearpilot/shell/node_modules/ws/package.json
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"name": "ws",
|
||||||
|
"version": "8.16.0",
|
||||||
|
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
|
||||||
|
"keywords": [
|
||||||
|
"HyBi",
|
||||||
|
"Push",
|
||||||
|
"RFC-6455",
|
||||||
|
"WebSocket",
|
||||||
|
"WebSockets",
|
||||||
|
"real-time"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/websockets/ws",
|
||||||
|
"bugs": "https://github.com/websockets/ws/issues",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/websockets/ws.git"
|
||||||
|
},
|
||||||
|
"author": "Einar Otto Stangvik <einaros@gmail.com> (http://2x.io)",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "index.js",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"browser": "./browser.js",
|
||||||
|
"import": "./wrapper.mjs",
|
||||||
|
"require": "./index.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
|
"browser": "browser.js",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"browser.js",
|
||||||
|
"index.js",
|
||||||
|
"lib/*.js",
|
||||||
|
"wrapper.mjs"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "nyc --reporter=lcov --reporter=text mocha --throw-deprecation test/*.test.js",
|
||||||
|
"integration": "mocha --throw-deprecation test/*.integration.js",
|
||||||
|
"lint": "eslint --ignore-path .gitignore . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\""
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"benchmark": "^2.1.4",
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"eslint": "^8.0.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
|
"mocha": "^8.4.0",
|
||||||
|
"nyc": "^15.0.0",
|
||||||
|
"prettier": "^3.0.0",
|
||||||
|
"utf-8-validate": "^6.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
system/clearpilot/shell/node_modules/ws/wrapper.mjs
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import createWebSocketStream from './lib/stream.js';
|
||||||
|
import Receiver from './lib/receiver.js';
|
||||||
|
import Sender from './lib/sender.js';
|
||||||
|
import WebSocket from './lib/websocket.js';
|
||||||
|
import WebSocketServer from './lib/websocket-server.js';
|
||||||
|
|
||||||
|
export { createWebSocketStream, Receiver, Sender, WebSocket, WebSocketServer };
|
||||||
|
export default WebSocket;
|
||||||
38
system/clearpilot/shell/package-lock.json
generated
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "shell",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"http": "^0.0.1-security",
|
||||||
|
"ws": "^8.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http": {
|
||||||
|
"version": "0.0.1-security",
|
||||||
|
"resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz",
|
||||||
|
"integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g=="
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
|
||||||
|
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
system/clearpilot/shell/package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"http": "^0.0.1-security",
|
||||||
|
"ws": "^8.16.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
23
system/clearpilot/shell/revert_logo.sh
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if /data/openpilot/shell/bg.jpg does not exist
|
||||||
|
if [ ! -f /data/openpilot/shell/bg.jpg ]; then
|
||||||
|
# Check if /usr/comma/bg.org does exist
|
||||||
|
if [ -f /usr/comma/bg.org ]; then
|
||||||
|
sudo mount -o remount,rw /
|
||||||
|
# Copy /usr/comma/bg.org to /usr/comma/bg.jpg
|
||||||
|
sudo mv -f /usr/comma/bg.org /usr/comma/bg.jpg
|
||||||
|
# Remove /usr/comma/bg.org
|
||||||
|
# sudo rm /usr/comma/bg.org
|
||||||
|
if [ -f /usr/comma/comma.org ]; then
|
||||||
|
sudo mv -f /usr/comma/comma.org /usr/comma/comma.sh
|
||||||
|
fi
|
||||||
|
echo Reverted custom logo for comma boot sequence
|
||||||
|
sudo sync
|
||||||
|
sleep 2
|
||||||
|
sudo reboot
|
||||||
|
while true; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
54
system/clearpilot/shell/set_logo.sh
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
# Test: sudo mount -o remount,rw /; cp /usr/comma/bg.org /usr/comma/bg.jpg; bash shell/set_logo.sh
|
||||||
|
|
||||||
|
# Check if md5sum of /usr/comma/bg.jpg is not equal to md5sum of /data/openpilot/shell/bg.jpg
|
||||||
|
if [ "$(md5sum /usr/comma/bg.jpg | awk '{print $1}')" != "$(md5sum /data/openpilot/shell/bg.jpg | awk '{print $1}')" ]; then
|
||||||
|
|
||||||
|
# If /usr/comma/bg.org does not exist
|
||||||
|
if [ ! -f /usr/comma/bg.org ]; then
|
||||||
|
if [[ "$(md5sum /usr/comma/bg.jpg | awk '{print $1}')" == "642380ba4c0f00b16e9cf6e613f43eec" ]]; then
|
||||||
|
sudo mount -o remount,rw /
|
||||||
|
sudo cp /usr/comma/bg.jpg /usr/comma/bg.org
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$(md5sum /usr/comma/bg.org | awk '{print $1}')" != "642380ba4c0f00b16e9cf6e613f43eec" ]]; then
|
||||||
|
sudo mount -o remount,ro /
|
||||||
|
echo failed to backup the correct picture
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo mount -o remount,rw /
|
||||||
|
|
||||||
|
# If /usr/comma/bg.org does exist
|
||||||
|
if [ -f /usr/comma/bg.org ]; then
|
||||||
|
sudo cp -f /data/openpilot/shell/bg.jpg /usr/comma/bg.jpg
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If file /usr/comma/revert_logo.sh does not exist
|
||||||
|
if [ "$(md5sum /data/openpilot/shell/revert_logo.sh | awk '{print $1}')" != "$(md5sum /usr/comma/revert_logo.sh | awk '{print $1}')" ]; then
|
||||||
|
sudo cp /data/openpilot/shell/revert_logo.sh /usr/comma/revert_logo.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(md5sum md5sum /usr/comma/comma.sh | awk '{print $1}')" != "$(md5sum /data/openpilot/shell/usr_comma_comma.sh | awk '{print $1}')" ]; then
|
||||||
|
if [[ "$(md5sum /usr/comma/comma.sh | awk '{print $1}')" == "ddbac0b46dd02efd672a0ef31ca426cf" ]]; then
|
||||||
|
if [ ! -f /usr/comma/comma.org ]; then
|
||||||
|
sudo cp /usr/comma/comma.sh /usr/comma/comma.org
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [-f /usr/comma/comma.org ]; then
|
||||||
|
sudo cp /data/openpilot/shell/usr_comma_comma.sh /usr/comma/comma.sh
|
||||||
|
echo updated comma.sh
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo Applied custom logo for comma boot sequence
|
||||||
|
sudo sync
|
||||||
|
sleep 2
|
||||||
|
sudo mount -o remount,ro /
|
||||||
|
sudo sync
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
95
system/clearpilot/shell/usr_comma_comma.sh
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#/usr/bin/env bash
|
||||||
|
|
||||||
|
source /etc/profile
|
||||||
|
|
||||||
|
SETUP="/usr/comma/setup"
|
||||||
|
RESET="/usr/comma/reset"
|
||||||
|
CONTINUE="/data/continue.sh"
|
||||||
|
INSTALLER="/tmp/installer"
|
||||||
|
RESET_TRIGGER="/data/__system_reset__"
|
||||||
|
|
||||||
|
echo "waiting for weston"
|
||||||
|
for i in {1..200}; do
|
||||||
|
if systemctl is-active --quiet weston-ready; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
if systemctl is-active --quiet weston-ready; then
|
||||||
|
echo "weston ready after ${SECONDS}s"
|
||||||
|
else
|
||||||
|
echo "timed out waiting for weston, ${SECONDS}s"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo chown comma: /data
|
||||||
|
sudo chown comma: /data/media
|
||||||
|
|
||||||
|
handle_setup_keys () {
|
||||||
|
# install default SSH key while still in setup
|
||||||
|
if [[ ! -e /data/params/d/GithubSshKeys && ! -e /data/continue.sh ]]; then
|
||||||
|
if [ ! -e /data/params/d ]; then
|
||||||
|
mkdir -p /data/params/d_tmp
|
||||||
|
ln -s /data/params/d_tmp /data/params/d
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n 1 > /data/params/d/SshEnabled
|
||||||
|
cp /usr/comma/setup_keys /data/params/d/GithubSshKeys
|
||||||
|
elif [[ -e /data/params/d/GithubSshKeys && -e /data/continue.sh ]]; then
|
||||||
|
if cmp -s /data/params/d/GithubSshKeys /usr/comma/setup_keys; then
|
||||||
|
rm /data/params/d/SshEnabled
|
||||||
|
rm /data/params/d/GithubSshKeys
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# factory reset handling
|
||||||
|
if [ -f "$RESET_TRIGGER" ]; then
|
||||||
|
echo "launching system reset, reset trigger present"
|
||||||
|
rm -f $RESET_TRIGGER
|
||||||
|
$RESET
|
||||||
|
elif ! mountpoint -q /data; then
|
||||||
|
echo "userdata not mounted. loading system reset"
|
||||||
|
if [ "$(head -c 15 /dev/disk/by-partlabel/userdata)" == "COMMA_ABL_RESET" ]; then
|
||||||
|
$RESET --format
|
||||||
|
else
|
||||||
|
$RESET --recover
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# symlink vscode to userdata
|
||||||
|
mkdir -p /data/tmp/vscode-server
|
||||||
|
ln -s /data/tmp/vscode-server ~/.vscode-server
|
||||||
|
|
||||||
|
# Check to see if the comma logo should be reverted
|
||||||
|
bash /usr/comma/revert_logo.sh
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
pkill -f "$SETUP"
|
||||||
|
handle_setup_keys
|
||||||
|
|
||||||
|
if [ -f $CONTINUE ]; then
|
||||||
|
exec "$CONTINUE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo abctl --set_success
|
||||||
|
|
||||||
|
# cleanup installers from previous runs
|
||||||
|
rm -f $INSTALLER
|
||||||
|
pkill -f $INSTALLER
|
||||||
|
|
||||||
|
# run setup and wait for installer
|
||||||
|
$SETUP &
|
||||||
|
echo "waiting for installer"
|
||||||
|
while [ ! -f $INSTALLER ]; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# run installer and wait for continue.sh
|
||||||
|
chmod +x $INSTALLER
|
||||||
|
$INSTALLER &
|
||||||
|
echo "running installer"
|
||||||
|
while [ ! -f $CONTINUE ] && ps -p $! > /dev/null; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
done
|
||||||
32
system/clearpilot/shell/watcher.example.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import socket
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Initialize the socket without connecting
|
||||||
|
sock = None
|
||||||
|
|
||||||
|
def ensure_socket_connected():
|
||||||
|
global sock
|
||||||
|
if sock is None or sock.fileno() == -1: # Checks if socket is not initialized or closed
|
||||||
|
try:
|
||||||
|
# Attempt to initialize and connect the socket
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
sock.connect("/tmp/oscar_watcher.sock")
|
||||||
|
except socket.error:
|
||||||
|
# If connection fails, set sock to None and do nothing
|
||||||
|
sock = None
|
||||||
|
|
||||||
|
def log_watch(var_name, message):
|
||||||
|
ensure_socket_connected() # Ensure the socket is connected before attempting to log
|
||||||
|
if sock: # Proceed only if sock is not None (i.e., is connected)
|
||||||
|
message_json = json.dumps(message)
|
||||||
|
try:
|
||||||
|
sock.sendall(message_json.encode('utf-8') + b'\n')
|
||||||
|
except socket.error:
|
||||||
|
# Handle potential error in sending (e.g., if connection was lost)
|
||||||
|
global sock
|
||||||
|
sock.close() # Close the current socket to clean up resources
|
||||||
|
sock = None # Reset sock to ensure reconnection attempt on next call
|
||||||
|
|
||||||
|
#message = {"variable": "car_stats", "value": {"car_lights_on": [3, 5, 7], "car_state": "on"}}
|
||||||
|
#log_watch(sock, message)
|
||||||
|
|
||||||
42
system/clearpilot/shell/watcher.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>WebSocket Test</title>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||||
|
<script>
|
||||||
|
let connect = function() {
|
||||||
|
let host = window.location.host;
|
||||||
|
|
||||||
|
if (host.indexOf(":") != -1) {
|
||||||
|
host = host.substr(0,host.indexOf(":"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace 'ws://localhost/ws' with your WebSocket server address
|
||||||
|
var ws = new WebSocket('ws://'+host+':1025/ws');
|
||||||
|
|
||||||
|
ws.onopen = function() {
|
||||||
|
console.log('WebSocket connection established');
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = function(event) {
|
||||||
|
console.log('Message received: ' + event.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function() {
|
||||||
|
console.log('WebSocket connection closed');
|
||||||
|
setTimeout(function() {
|
||||||
|
connect();
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
connect();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>WebSocket Test Page</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
247
system/clearpilot/shell/watcher.js
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
const net = require('net');
|
||||||
|
const fs = require('fs');
|
||||||
|
const socketPath = '/tmp/oscar_watcher.sock';
|
||||||
|
|
||||||
|
// from openpilot.common.watcher import Watcher
|
||||||
|
// Watcher.log_watch("LastGPSPosition", location)
|
||||||
|
|
||||||
|
//// UNIXSOCKET SERVER ////
|
||||||
|
|
||||||
|
let progvars = {};
|
||||||
|
|
||||||
|
// Remove the socket file if it already exists
|
||||||
|
if (fs.existsSync( socketPath )) {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(socketPath);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== 'ENOENT') {
|
||||||
|
console.error('Error removing existing socket file:', err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unix_socket_server = net.createServer((connection) => {
|
||||||
|
// console.log('UnixSocket Client connected.');
|
||||||
|
|
||||||
|
let buffer = '';
|
||||||
|
|
||||||
|
connection.on('data', (data) => {
|
||||||
|
buffer += data.toString();
|
||||||
|
let boundary = buffer.indexOf('\n');
|
||||||
|
while (boundary !== -1) {
|
||||||
|
const message = buffer.substring(0, boundary);
|
||||||
|
buffer = buffer.substring(boundary + 1);
|
||||||
|
processMessage(message);
|
||||||
|
boundary = buffer.indexOf('\n');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on('end', () => {
|
||||||
|
// console.log('UnixSocket Client disconnected.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
unix_socket_server.listen(socketPath, () => {
|
||||||
|
console.log(`UnixSocket Server listening on ${socketPath}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
function processMessage(message) {
|
||||||
|
console.log(message);
|
||||||
|
try {
|
||||||
|
msg = JSON.parse(message);
|
||||||
|
console.log(msg);
|
||||||
|
let variable = msg[0];
|
||||||
|
let value = msg[1];
|
||||||
|
console.log({var: variable, val: value});
|
||||||
|
const diff = calculateDiff(variable, progvars[variable], value);
|
||||||
|
console.log({diff: diff})
|
||||||
|
send_ws_message(diff);
|
||||||
|
progvars[variable] = value;
|
||||||
|
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const logEntry = `log_time="${timestamp}";\n${diff}\n`;
|
||||||
|
|
||||||
|
fs.writeFile('/data/openpilot/logs/watcher.log', logEntry, { flag: 'a' }, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error writing to log file:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error processing message:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateDiff(provVarsVariable, oldValue, newValue) {
|
||||||
|
const changes = [];
|
||||||
|
|
||||||
|
function findChanges(path, oldVal, newVal) {
|
||||||
|
// Check if both values are objects (and not null), otherwise compare directly
|
||||||
|
if (!(typeof oldVal === 'object' && oldVal !== null) ||
|
||||||
|
!(typeof newVal === 'object' && newVal !== null)) {
|
||||||
|
if (oldVal !== newVal) {
|
||||||
|
changes.push(`${path} = ${JSON.stringify(newVal)};`);
|
||||||
|
}
|
||||||
|
return oldVal !== newVal ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldKeys = Object.keys(oldVal);
|
||||||
|
const newKeys = Object.keys(newVal);
|
||||||
|
const allKeys = new Set([...oldKeys, ...newKeys]);
|
||||||
|
let changedCount = 0;
|
||||||
|
|
||||||
|
allKeys.forEach(key => {
|
||||||
|
const oldKeyValue = oldVal[key];
|
||||||
|
const newKeyValue = newVal[key];
|
||||||
|
const currentPath = path ? `${path}[${JSON.stringify(key)}]` : `[${JSON.stringify(key)}]`;
|
||||||
|
|
||||||
|
if (!oldVal || !(key in oldVal)) {
|
||||||
|
// New key added
|
||||||
|
changes.push(`${currentPath} = ${JSON.stringify(newKeyValue)};`);
|
||||||
|
changedCount++;
|
||||||
|
} else if (typeof oldKeyValue === 'object' && oldKeyValue !== null && typeof newKeyValue === 'object' && newKeyValue !== null) {
|
||||||
|
// Recursive diff for objects
|
||||||
|
const subChanges = findChanges(currentPath, oldKeyValue, newKeyValue);
|
||||||
|
if (subChanges > 0) changedCount += subChanges;
|
||||||
|
} else if (oldKeyValue !== newKeyValue) {
|
||||||
|
// Direct value change
|
||||||
|
changes.push(`${currentPath} = ${JSON.stringify(newKeyValue)};`);
|
||||||
|
changedCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If more than 1/3 of the properties have been changed, replace the entire node
|
||||||
|
if (changedCount > 0 && changedCount > oldKeys.length / 3) {
|
||||||
|
// Clear individual changes as we replace the entire node
|
||||||
|
changes.splice(-changedCount);
|
||||||
|
// Replace the node
|
||||||
|
changes.push(`${path} = ${JSON.stringify(newVal)};`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust initial call to handle non-object types
|
||||||
|
if ((typeof oldValue === 'object' && oldValue !== null) && (typeof newValue === 'object' && newValue !== null)) {
|
||||||
|
findChanges('progvars['+JSON.stringify(provVarsVariable)+']', oldValue, newValue);
|
||||||
|
} else if (oldValue !== newValue) {
|
||||||
|
changes.push(`progvars[${JSON.stringify(provVarsVariable)}] = ${JSON.stringify(newValue)};`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//// HTTP SERVER ////
|
||||||
|
|
||||||
|
const WebSocket = require('ws');
|
||||||
|
const http = require('http');
|
||||||
|
const url = require('url');
|
||||||
|
const path = require('path');
|
||||||
|
const hostname = '0.0.0.0';
|
||||||
|
const port = 1024;
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
if(req.url === '/') {
|
||||||
|
// Serve the HTML file
|
||||||
|
fs.readFile(path.join(__dirname, 'watcher.html'), (err, data) => {
|
||||||
|
if(err) {
|
||||||
|
res.writeHead(500);
|
||||||
|
res.end('Error loading watcher.html');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.writeHead(200, {'Content-Type': 'text/html'});
|
||||||
|
res.end(data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Handle 404
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end('Not found');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(port, hostname, () => {
|
||||||
|
console.log(`Web Server running at http://${hostname}:${port}/`);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//// WEBSOCKET SERVER ////
|
||||||
|
ws_port = port + 1;
|
||||||
|
|
||||||
|
// Attach WebSocket server to the HTTP server
|
||||||
|
const wss = new WebSocket.Server({ port: ws_port });
|
||||||
|
|
||||||
|
console.log(`WebSocket Server running at http://${hostname}:${ws_port}/`);
|
||||||
|
|
||||||
|
wss.on('connection', function connection(ws) {
|
||||||
|
console.log('WebSocket client connected');
|
||||||
|
|
||||||
|
// Send initial state
|
||||||
|
ws.send(calculateDiff({}, progvars));
|
||||||
|
ws.send("progvars = "+JSON.stringify(progvars)+";");
|
||||||
|
|
||||||
|
ws.on('message', function incoming(message) {
|
||||||
|
console.log('received: %s', message);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', function close() {
|
||||||
|
console.log('WebSocket client disconnected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle upgrade of the request
|
||||||
|
wss.on('upgrade', function upgrade(request, socket, head) {
|
||||||
|
const { pathname } = url.parse(request.url);
|
||||||
|
|
||||||
|
if (pathname === '/ws') {
|
||||||
|
wss.handleUpgrade(request, socket, head, function done(ws) {
|
||||||
|
wss.emit('connection', ws, request);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to send a message to all connected WebSocket clients
|
||||||
|
function send_ws_message(message) {
|
||||||
|
wss.clients.forEach(function each(client) {
|
||||||
|
if (client.readyState === WebSocket.OPEN) {
|
||||||
|
client.send(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//// Exit when low disk space /////
|
||||||
|
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
|
||||||
|
// Function to check disk space
|
||||||
|
function checkDiskSpace() {
|
||||||
|
exec('df -BG /data | tail -n 1 | awk \'{print $4}\'', (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
console.error(`exec error: ${error}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
console.error(`stderr: ${stderr}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the number of GBs available
|
||||||
|
const freeSpaceGB = parseInt(stdout.trim().replace('G', ''));
|
||||||
|
|
||||||
|
// Check if free space is less than 10GB
|
||||||
|
if (freeSpaceGB < 9) {
|
||||||
|
console.log('Less than 9GB of free space on /data. Exiting.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check disk space every 10 seconds
|
||||||
|
setInterval(checkDiskSpace, 10000);
|
||||||
|
|
||||||
|
// Perform an initial check
|
||||||
|
checkDiskSpace();
|
||||||
20
system/clearpilot/shell/watcher_run_loop.sh
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
cd /data/openpilot/shell
|
||||||
|
|
||||||
|
while true
|
||||||
|
do
|
||||||
|
|
||||||
|
nodejs watcher.js
|
||||||
|
|
||||||
|
# Get free space on /data in GB
|
||||||
|
free_space=$(df -BG /data | tail -n 1 | awk '{print $4}' | sed 's/G//')
|
||||||
|
|
||||||
|
# Check if free space is less than 10GB
|
||||||
|
if [ "$free_space" -lt 10 ]; then
|
||||||
|
echo "Less than 10GB of free space on /data. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo crashed, waiting 30 seconds
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
done
|
||||||
BIN
system/clearpilot/startup_logo/bg.jpg
Normal file
|
After Width: | Height: | Size: 44 KiB |
23
system/clearpilot/startup_logo/revert_logo.sh
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if /data/openpilot/shell/bg.jpg does not exist
|
||||||
|
if [ ! -f /data/openpilot/shell/bg.jpg ]; then
|
||||||
|
# Check if /usr/comma/bg.org does exist
|
||||||
|
if [ -f /usr/comma/bg.org ]; then
|
||||||
|
sudo mount -o remount,rw /
|
||||||
|
# Copy /usr/comma/bg.org to /usr/comma/bg.jpg
|
||||||
|
sudo mv -f /usr/comma/bg.org /usr/comma/bg.jpg
|
||||||
|
# Remove /usr/comma/bg.org
|
||||||
|
# sudo rm /usr/comma/bg.org
|
||||||
|
if [ -f /usr/comma/comma.org ]; then
|
||||||
|
sudo mv -f /usr/comma/comma.org /usr/comma/comma.sh
|
||||||
|
fi
|
||||||
|
echo Reverted custom logo for comma boot sequence
|
||||||
|
sudo sync
|
||||||
|
sleep 2
|
||||||
|
sudo reboot
|
||||||
|
while true; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
54
system/clearpilot/startup_logo/set_logo.sh
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
# Test: sudo mount -o remount,rw /; cp /usr/comma/bg.org /usr/comma/bg.jpg; bash shell/set_logo.sh
|
||||||
|
|
||||||
|
# Check if md5sum of /usr/comma/bg.jpg is not equal to md5sum of /data/openpilot/shell/bg.jpg
|
||||||
|
if [ "$(md5sum /usr/comma/bg.jpg | awk '{print $1}')" != "$(md5sum /data/openpilot/shell/bg.jpg | awk '{print $1}')" ]; then
|
||||||
|
|
||||||
|
# If /usr/comma/bg.org does not exist
|
||||||
|
if [ ! -f /usr/comma/bg.org ]; then
|
||||||
|
if [[ "$(md5sum /usr/comma/bg.jpg | awk '{print $1}')" == "642380ba4c0f00b16e9cf6e613f43eec" ]]; then
|
||||||
|
sudo mount -o remount,rw /
|
||||||
|
sudo cp /usr/comma/bg.jpg /usr/comma/bg.org
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$(md5sum /usr/comma/bg.org | awk '{print $1}')" != "642380ba4c0f00b16e9cf6e613f43eec" ]]; then
|
||||||
|
sudo mount -o remount,ro /
|
||||||
|
echo failed to backup the correct picture
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo mount -o remount,rw /
|
||||||
|
|
||||||
|
# If /usr/comma/bg.org does exist
|
||||||
|
if [ -f /usr/comma/bg.org ]; then
|
||||||
|
sudo cp -f /data/openpilot/shell/bg.jpg /usr/comma/bg.jpg
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If file /usr/comma/revert_logo.sh does not exist
|
||||||
|
if [ "$(md5sum /data/openpilot/shell/revert_logo.sh | awk '{print $1}')" != "$(md5sum /usr/comma/revert_logo.sh | awk '{print $1}')" ]; then
|
||||||
|
sudo cp /data/openpilot/shell/revert_logo.sh /usr/comma/revert_logo.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(md5sum md5sum /usr/comma/comma.sh | awk '{print $1}')" != "$(md5sum /data/openpilot/shell/usr_comma_comma.sh | awk '{print $1}')" ]; then
|
||||||
|
if [[ "$(md5sum /usr/comma/comma.sh | awk '{print $1}')" == "ddbac0b46dd02efd672a0ef31ca426cf" ]]; then
|
||||||
|
if [ ! -f /usr/comma/comma.org ]; then
|
||||||
|
sudo cp /usr/comma/comma.sh /usr/comma/comma.org
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [-f /usr/comma/comma.org ]; then
|
||||||
|
sudo cp /data/openpilot/shell/usr_comma_comma.sh /usr/comma/comma.sh
|
||||||
|
echo updated comma.sh
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo Applied custom logo for comma boot sequence
|
||||||
|
sudo sync
|
||||||
|
sleep 2
|
||||||
|
sudo mount -o remount,ro /
|
||||||
|
sudo sync
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
1
system/clearpilot/tools/remount_ro.sh
Normal file
@@ -0,0 +1 @@
|
|||||||
|
sudo mount -o remount,ro /
|
||||||
1
system/clearpilot/tools/remount_rw.sh
Normal file
@@ -0,0 +1 @@
|
|||||||
|
sudo mount -o remount,rw /
|
||||||
90
system/clearpilot/tools/shell_wayland.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
import logging
|
||||||
|
import platform
|
||||||
|
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QScrollBar, QGraphicsView, QGraphicsScene, QGraphicsProxyWidget
|
||||||
|
from PyQt5.QtCore import Qt, QCoreApplication
|
||||||
|
from PyQt5.QtGui import QFont, QScreen
|
||||||
|
|
||||||
|
import termqt
|
||||||
|
from termqt import Terminal, TerminalPOSIXExecIO
|
||||||
|
|
||||||
|
class ExitOnMessageHandler(logging.Handler):
|
||||||
|
def emit(self, record):
|
||||||
|
if "Spawned process has been killed" in record.getMessage():
|
||||||
|
QApplication.quit() # Exit the application gracefully
|
||||||
|
|
||||||
|
def setup_logger():
|
||||||
|
logger = logging.getLogger()
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
formatter = logging.Formatter("[%(asctime)s] > [%(filename)s:%(lineno)d] %(message)s")
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
logger.addHandler(handler)
|
||||||
|
handler2 = ExitOnMessageHandler()
|
||||||
|
logger.addHandler(handler2)
|
||||||
|
return logger
|
||||||
|
|
||||||
|
def create_terminal_app():
|
||||||
|
os.environ["XDG_RUNTIME_DIR"] = "/var/tmp/weston"
|
||||||
|
os.environ["WAYLAND_DISPLAY"] = "wayland-0"
|
||||||
|
os.environ["QT_QPA_PLATFORM"] = "wayland"
|
||||||
|
os.environ["QT_WAYLAND_SHELL_INTEGRATION"] = "wl-shell"
|
||||||
|
|
||||||
|
|
||||||
|
QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
desktop = QApplication.desktop()
|
||||||
|
ag = desktop.availableGeometry(desktop.primaryScreen())
|
||||||
|
print (ag.width())
|
||||||
|
print (ag.height())
|
||||||
|
|
||||||
|
window = QWidget()
|
||||||
|
window.setWindowTitle("termqt on {}".format(platform.system()))
|
||||||
|
window.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
|
||||||
|
window.setGeometry(0,0, ag.width(), ag.height())
|
||||||
|
window.setStyleSheet("background-color: black;")
|
||||||
|
window.showFullScreen()
|
||||||
|
|
||||||
|
scene = QGraphicsScene()
|
||||||
|
view = QGraphicsView(scene, window)
|
||||||
|
print (window.width())
|
||||||
|
print (window.height())
|
||||||
|
view.setGeometry(0, 0, window.width(), window.height())
|
||||||
|
view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||||
|
view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||||
|
|
||||||
|
layout = QHBoxLayout()
|
||||||
|
terminal = Terminal(window.width(), window.height(), logger=setup_logger(), font_size=32)
|
||||||
|
|
||||||
|
proxy_terminal = scene.addWidget(terminal)
|
||||||
|
view.setScene(scene)
|
||||||
|
view.rotate(90) # Rotate the view by 90 degrees clockwise
|
||||||
|
|
||||||
|
window_layout = QHBoxLayout(window)
|
||||||
|
window_layout.addWidget(view)
|
||||||
|
window_layout.setContentsMargins(0,0,0,0)
|
||||||
|
window.setLayout(window_layout)
|
||||||
|
|
||||||
|
return app, window, terminal
|
||||||
|
|
||||||
|
def main():
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL) # Enable Ctrl+C
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: python start.py '<command>'")
|
||||||
|
return
|
||||||
|
|
||||||
|
command = "bash -c '{}'".format(sys.argv[1])
|
||||||
|
app, window, terminal = create_terminal_app()
|
||||||
|
platform_name = platform.system()
|
||||||
|
terminal_io = TerminalPOSIXExecIO(terminal.col_len, terminal.row_len, command, os.environ, setup_logger())
|
||||||
|
terminal_io.stdout_callback = terminal.stdout
|
||||||
|
terminal.stdin_callback = terminal_io.write
|
||||||
|
terminal.resize_callback = terminal_io.resize
|
||||||
|
terminal_io.spawn()
|
||||||
|
exit_code = app.exec_()
|
||||||
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||