wip
This commit is contained in:
@@ -1,82 +0,0 @@
|
||||
# PFEIFER - MAPD - Modified by FrogAi for FrogPilot to automatically update
|
||||
import os
|
||||
import socket
|
||||
import stat
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
from openpilot.common.realtime import Ratekeeper
|
||||
|
||||
VERSION = 'v1'
|
||||
VERSION_URL = f"https://github.com/FrogAi/FrogPilot-Resources/raw/master/mapd_version_{VERSION}.txt"
|
||||
|
||||
MAPD_PATH = '/data/media/0/osm/mapd'
|
||||
VERSION_PATH = '/data/media/0/osm/mapd_version'
|
||||
|
||||
def github_pinged(url="https://github.com", timeout=5):
|
||||
no_internet = 0
|
||||
while True:
|
||||
if no_internet > 5:
|
||||
break
|
||||
try:
|
||||
urllib.request.urlopen(url, timeout=timeout)
|
||||
return True
|
||||
except (urllib.error.URLError, socket.timeout):
|
||||
no_internet += 1
|
||||
time.sleep(10)
|
||||
return False
|
||||
|
||||
def get_latest_version():
|
||||
with urllib.request.urlopen(VERSION_URL) as response:
|
||||
return response.read().decode('utf-8').strip()
|
||||
|
||||
def download(current_version):
|
||||
url = f"https://github.com/pfeiferj/openpilot-mapd/releases/download/{current_version}/mapd"
|
||||
|
||||
mapd_dir = os.path.dirname(MAPD_PATH)
|
||||
if not os.path.exists(mapd_dir):
|
||||
os.makedirs(mapd_dir)
|
||||
with urllib.request.urlopen(url) as f:
|
||||
with open(MAPD_PATH, 'wb') as output:
|
||||
output.write(f.read())
|
||||
os.fsync(output)
|
||||
current_permissions = stat.S_IMODE(os.lstat(MAPD_PATH).st_mode) # <-- preserve permissions
|
||||
os.chmod(MAPD_PATH, current_permissions | stat.S_IEXEC) # <-- preserve permissions
|
||||
with open(VERSION_PATH, 'w') as output:
|
||||
output.write(current_version)
|
||||
os.fsync(output)
|
||||
|
||||
def mapd_thread(sm=None, pm=None):
|
||||
rk = Ratekeeper(0.05, print_delay_threshold=None)
|
||||
|
||||
while True:
|
||||
try:
|
||||
if github_pinged():
|
||||
current_version = get_latest_version()
|
||||
if not os.path.exists(MAPD_PATH):
|
||||
download(current_version)
|
||||
continue
|
||||
if not os.path.exists(VERSION_PATH):
|
||||
download(current_version)
|
||||
continue
|
||||
with open(VERSION_PATH) as f:
|
||||
content = f.read()
|
||||
if content != current_version:
|
||||
download(current_version)
|
||||
continue
|
||||
|
||||
process = subprocess.Popen(MAPD_PATH)
|
||||
process.wait()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
rk.keep_time()
|
||||
|
||||
|
||||
def main(sm=None, pm=None):
|
||||
mapd_thread(sm, pm)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 637 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 422 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 366 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 654 KiB |
@@ -1,329 +0,0 @@
|
||||
/****************************************************************************
|
||||
** Meta object code from reading C++ file 'navigation_settings.h'
|
||||
**
|
||||
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.8)
|
||||
**
|
||||
** WARNING! All changes made in this file will be lost!
|
||||
*****************************************************************************/
|
||||
|
||||
#include "navigation_settings.h"
|
||||
#include <QtCore/qbytearray.h>
|
||||
#include <QtCore/qmetatype.h>
|
||||
#if !defined(Q_MOC_OUTPUT_REVISION)
|
||||
#error "The header file 'navigation_settings.h' doesn't include <QObject>."
|
||||
#elif Q_MOC_OUTPUT_REVISION != 67
|
||||
#error "This file was generated using the moc from 5.12.8. It"
|
||||
#error "cannot be used with the include files from this version of Qt."
|
||||
#error "(The moc has changed too much.)"
|
||||
#endif
|
||||
|
||||
QT_BEGIN_MOC_NAMESPACE
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_DEPRECATED
|
||||
struct qt_meta_stringdata_Primeless_t {
|
||||
QByteArrayData data[3];
|
||||
char stringdata0[21];
|
||||
};
|
||||
#define QT_MOC_LITERAL(idx, ofs, len) \
|
||||
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
|
||||
qptrdiff(offsetof(qt_meta_stringdata_Primeless_t, stringdata0) + ofs \
|
||||
- idx * sizeof(QByteArrayData)) \
|
||||
)
|
||||
static const qt_meta_stringdata_Primeless_t qt_meta_stringdata_Primeless = {
|
||||
{
|
||||
QT_MOC_LITERAL(0, 0, 9), // "Primeless"
|
||||
QT_MOC_LITERAL(1, 10, 9), // "backPress"
|
||||
QT_MOC_LITERAL(2, 20, 0) // ""
|
||||
|
||||
},
|
||||
"Primeless\0backPress\0"
|
||||
};
|
||||
#undef QT_MOC_LITERAL
|
||||
|
||||
static const uint qt_meta_data_Primeless[] = {
|
||||
|
||||
// content:
|
||||
8, // revision
|
||||
0, // classname
|
||||
0, 0, // classinfo
|
||||
1, 14, // methods
|
||||
0, 0, // properties
|
||||
0, 0, // enums/sets
|
||||
0, 0, // constructors
|
||||
0, // flags
|
||||
1, // signalCount
|
||||
|
||||
// signals: name, argc, parameters, tag, flags
|
||||
1, 0, 19, 2, 0x06 /* Public */,
|
||||
|
||||
// signals: parameters
|
||||
QMetaType::Void,
|
||||
|
||||
0 // eod
|
||||
};
|
||||
|
||||
void Primeless::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
if (_c == QMetaObject::InvokeMetaMethod) {
|
||||
auto *_t = static_cast<Primeless *>(_o);
|
||||
Q_UNUSED(_t)
|
||||
switch (_id) {
|
||||
case 0: _t->backPress(); break;
|
||||
default: ;
|
||||
}
|
||||
} else if (_c == QMetaObject::IndexOfMethod) {
|
||||
int *result = reinterpret_cast<int *>(_a[0]);
|
||||
{
|
||||
using _t = void (Primeless::*)();
|
||||
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&Primeless::backPress)) {
|
||||
*result = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Q_UNUSED(_a);
|
||||
}
|
||||
|
||||
QT_INIT_METAOBJECT const QMetaObject Primeless::staticMetaObject = { {
|
||||
&QWidget::staticMetaObject,
|
||||
qt_meta_stringdata_Primeless.data,
|
||||
qt_meta_data_Primeless,
|
||||
qt_static_metacall,
|
||||
nullptr,
|
||||
nullptr
|
||||
} };
|
||||
|
||||
|
||||
const QMetaObject *Primeless::metaObject() const
|
||||
{
|
||||
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
|
||||
}
|
||||
|
||||
void *Primeless::qt_metacast(const char *_clname)
|
||||
{
|
||||
if (!_clname) return nullptr;
|
||||
if (!strcmp(_clname, qt_meta_stringdata_Primeless.stringdata0))
|
||||
return static_cast<void*>(this);
|
||||
return QWidget::qt_metacast(_clname);
|
||||
}
|
||||
|
||||
int Primeless::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
_id = QWidget::qt_metacall(_c, _id, _a);
|
||||
if (_id < 0)
|
||||
return _id;
|
||||
if (_c == QMetaObject::InvokeMetaMethod) {
|
||||
if (_id < 1)
|
||||
qt_static_metacall(this, _c, _id, _a);
|
||||
_id -= 1;
|
||||
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
|
||||
if (_id < 1)
|
||||
*reinterpret_cast<int*>(_a[0]) = -1;
|
||||
_id -= 1;
|
||||
}
|
||||
return _id;
|
||||
}
|
||||
|
||||
// SIGNAL 0
|
||||
void Primeless::backPress()
|
||||
{
|
||||
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
|
||||
}
|
||||
struct qt_meta_stringdata_SelectMaps_t {
|
||||
QByteArrayData data[4];
|
||||
char stringdata0[30];
|
||||
};
|
||||
#define QT_MOC_LITERAL(idx, ofs, len) \
|
||||
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
|
||||
qptrdiff(offsetof(qt_meta_stringdata_SelectMaps_t, stringdata0) + ofs \
|
||||
- idx * sizeof(QByteArrayData)) \
|
||||
)
|
||||
static const qt_meta_stringdata_SelectMaps_t qt_meta_stringdata_SelectMaps = {
|
||||
{
|
||||
QT_MOC_LITERAL(0, 0, 10), // "SelectMaps"
|
||||
QT_MOC_LITERAL(1, 11, 9), // "backPress"
|
||||
QT_MOC_LITERAL(2, 21, 0), // ""
|
||||
QT_MOC_LITERAL(3, 22, 7) // "setMaps"
|
||||
|
||||
},
|
||||
"SelectMaps\0backPress\0\0setMaps"
|
||||
};
|
||||
#undef QT_MOC_LITERAL
|
||||
|
||||
static const uint qt_meta_data_SelectMaps[] = {
|
||||
|
||||
// content:
|
||||
8, // revision
|
||||
0, // classname
|
||||
0, 0, // classinfo
|
||||
2, 14, // methods
|
||||
0, 0, // properties
|
||||
0, 0, // enums/sets
|
||||
0, 0, // constructors
|
||||
0, // flags
|
||||
2, // signalCount
|
||||
|
||||
// signals: name, argc, parameters, tag, flags
|
||||
1, 0, 24, 2, 0x06 /* Public */,
|
||||
3, 0, 25, 2, 0x06 /* Public */,
|
||||
|
||||
// signals: parameters
|
||||
QMetaType::Void,
|
||||
QMetaType::Void,
|
||||
|
||||
0 // eod
|
||||
};
|
||||
|
||||
void SelectMaps::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
if (_c == QMetaObject::InvokeMetaMethod) {
|
||||
auto *_t = static_cast<SelectMaps *>(_o);
|
||||
Q_UNUSED(_t)
|
||||
switch (_id) {
|
||||
case 0: _t->backPress(); break;
|
||||
case 1: _t->setMaps(); break;
|
||||
default: ;
|
||||
}
|
||||
} else if (_c == QMetaObject::IndexOfMethod) {
|
||||
int *result = reinterpret_cast<int *>(_a[0]);
|
||||
{
|
||||
using _t = void (SelectMaps::*)();
|
||||
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&SelectMaps::backPress)) {
|
||||
*result = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
{
|
||||
using _t = void (SelectMaps::*)();
|
||||
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&SelectMaps::setMaps)) {
|
||||
*result = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Q_UNUSED(_a);
|
||||
}
|
||||
|
||||
QT_INIT_METAOBJECT const QMetaObject SelectMaps::staticMetaObject = { {
|
||||
&QWidget::staticMetaObject,
|
||||
qt_meta_stringdata_SelectMaps.data,
|
||||
qt_meta_data_SelectMaps,
|
||||
qt_static_metacall,
|
||||
nullptr,
|
||||
nullptr
|
||||
} };
|
||||
|
||||
|
||||
const QMetaObject *SelectMaps::metaObject() const
|
||||
{
|
||||
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
|
||||
}
|
||||
|
||||
void *SelectMaps::qt_metacast(const char *_clname)
|
||||
{
|
||||
if (!_clname) return nullptr;
|
||||
if (!strcmp(_clname, qt_meta_stringdata_SelectMaps.stringdata0))
|
||||
return static_cast<void*>(this);
|
||||
return QWidget::qt_metacast(_clname);
|
||||
}
|
||||
|
||||
int SelectMaps::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
_id = QWidget::qt_metacall(_c, _id, _a);
|
||||
if (_id < 0)
|
||||
return _id;
|
||||
if (_c == QMetaObject::InvokeMetaMethod) {
|
||||
if (_id < 2)
|
||||
qt_static_metacall(this, _c, _id, _a);
|
||||
_id -= 2;
|
||||
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
|
||||
if (_id < 2)
|
||||
*reinterpret_cast<int*>(_a[0]) = -1;
|
||||
_id -= 2;
|
||||
}
|
||||
return _id;
|
||||
}
|
||||
|
||||
// SIGNAL 0
|
||||
void SelectMaps::backPress()
|
||||
{
|
||||
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
|
||||
}
|
||||
|
||||
// SIGNAL 1
|
||||
void SelectMaps::setMaps()
|
||||
{
|
||||
QMetaObject::activate(this, &staticMetaObject, 1, nullptr);
|
||||
}
|
||||
struct qt_meta_stringdata_FrogPilotNavigationPanel_t {
|
||||
QByteArrayData data[1];
|
||||
char stringdata0[25];
|
||||
};
|
||||
#define QT_MOC_LITERAL(idx, ofs, len) \
|
||||
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
|
||||
qptrdiff(offsetof(qt_meta_stringdata_FrogPilotNavigationPanel_t, stringdata0) + ofs \
|
||||
- idx * sizeof(QByteArrayData)) \
|
||||
)
|
||||
static const qt_meta_stringdata_FrogPilotNavigationPanel_t qt_meta_stringdata_FrogPilotNavigationPanel = {
|
||||
{
|
||||
QT_MOC_LITERAL(0, 0, 24) // "FrogPilotNavigationPanel"
|
||||
|
||||
},
|
||||
"FrogPilotNavigationPanel"
|
||||
};
|
||||
#undef QT_MOC_LITERAL
|
||||
|
||||
static const uint qt_meta_data_FrogPilotNavigationPanel[] = {
|
||||
|
||||
// content:
|
||||
8, // revision
|
||||
0, // classname
|
||||
0, 0, // classinfo
|
||||
0, 0, // methods
|
||||
0, 0, // properties
|
||||
0, 0, // enums/sets
|
||||
0, 0, // constructors
|
||||
0, // flags
|
||||
0, // signalCount
|
||||
|
||||
0 // eod
|
||||
};
|
||||
|
||||
void FrogPilotNavigationPanel::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
Q_UNUSED(_o);
|
||||
Q_UNUSED(_id);
|
||||
Q_UNUSED(_c);
|
||||
Q_UNUSED(_a);
|
||||
}
|
||||
|
||||
QT_INIT_METAOBJECT const QMetaObject FrogPilotNavigationPanel::staticMetaObject = { {
|
||||
&QFrame::staticMetaObject,
|
||||
qt_meta_stringdata_FrogPilotNavigationPanel.data,
|
||||
qt_meta_data_FrogPilotNavigationPanel,
|
||||
qt_static_metacall,
|
||||
nullptr,
|
||||
nullptr
|
||||
} };
|
||||
|
||||
|
||||
const QMetaObject *FrogPilotNavigationPanel::metaObject() const
|
||||
{
|
||||
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
|
||||
}
|
||||
|
||||
void *FrogPilotNavigationPanel::qt_metacast(const char *_clname)
|
||||
{
|
||||
if (!_clname) return nullptr;
|
||||
if (!strcmp(_clname, qt_meta_stringdata_FrogPilotNavigationPanel.stringdata0))
|
||||
return static_cast<void*>(this);
|
||||
return QFrame::qt_metacast(_clname);
|
||||
}
|
||||
|
||||
int FrogPilotNavigationPanel::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
_id = QFrame::qt_metacall(_c, _id, _a);
|
||||
return _id;
|
||||
}
|
||||
QT_WARNING_POP
|
||||
QT_END_MOC_NAMESPACE
|
||||
@@ -1,311 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||
#include "selfdrive/ui/qt/widgets/controls.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
QMap<QString, QString> northeastMap = {
|
||||
{"CT", "Connecticut"}, {"DE", "Delaware"}, {"MA", "Massachusetts"},
|
||||
{"MD", "Maryland"}, {"ME", "Maine"}, {"NH", "New Hampshire"},
|
||||
{"NJ", "New Jersey"}, {"NY", "New York"}, {"PA", "Pennsylvania"},
|
||||
{"RI", "Rhode Island"}, {"VT", "Vermont"}
|
||||
};
|
||||
|
||||
QMap<QString, QString> midwestMap = {
|
||||
{"IA", "Iowa"}, {"IL", "Illinois"}, {"IN", "Indiana"},
|
||||
{"KS", "Kansas"}, {"MI", "Michigan"}, {"MN", "Minnesota"},
|
||||
{"MO", "Missouri"}, {"ND", "North Dakota"}, {"NE", "Nebraska"},
|
||||
{"OH", "Ohio"}, {"SD", "South Dakota"}, {"WI", "Wisconsin"}
|
||||
};
|
||||
|
||||
QMap<QString, QString> southMap = {
|
||||
{"AL", "Alabama"}, {"AR", "Arkansas"}, {"FL", "Florida"},
|
||||
{"GA", "Georgia"}, {"KY", "Kentucky"}, {"LA", "Louisiana"},
|
||||
{"MS", "Mississippi"}, {"NC", "North Carolina"}, {"OK", "Oklahoma"},
|
||||
{"SC", "South Carolina"}, {"TN", "Tennessee"}, {"TX", "Texas"},
|
||||
{"VA", "Virginia"}, {"WV", "West Virginia"}
|
||||
};
|
||||
|
||||
QMap<QString, QString> westMap = {
|
||||
{"AK", "Alaska"}, {"AZ", "Arizona"}, {"CA", "California"},
|
||||
{"CO", "Colorado"}, {"HI", "Hawaii"}, {"ID", "Idaho"},
|
||||
{"MT", "Montana"}, {"NM", "New Mexico"}, {"NV", "Nevada"},
|
||||
{"OR", "Oregon"}, {"UT", "Utah"}, {"WA", "Washington"},
|
||||
{"WY", "Wyoming"}
|
||||
};
|
||||
|
||||
QMap<QString, QString> territoriesMap = {
|
||||
{"AS", "American Samoa"}, {"DC", "District of Columbia"},
|
||||
{"GM", "Guam"}, {"MP", "North Mariana Islands"},
|
||||
{"PR", "Puerto Rico"}, {"VI", "Virgin Islands"}
|
||||
};
|
||||
|
||||
QMap<QString, QString> africaMap = {
|
||||
{"DZ", "Algeria"}, {"AO", "Angola"}, {"BJ", "Benin"}, {"BW", "Botswana"},
|
||||
{"BF", "Burkina Faso"}, {"BI", "Burundi"}, {"CM", "Cameroon"}, {"CV", "Cape Verde"},
|
||||
{"CF", "Central African Republic"}, {"TD", "Chad"}, {"KM", "Comoros"}, {"CG", "Congo (Brazzaville)"},
|
||||
{"CD", "Congo (Kinshasa)"}, {"CI", "Ivory Coast"}, {"DJ", "Djibouti"}, {"EG", "Egypt"},
|
||||
{"GQ", "Equatorial Guinea"}, {"ER", "Eritrea"}, {"SZ", "Eswatini"}, {"ET", "Ethiopia"},
|
||||
{"GA", "Gabon"}, {"GM", "Gambia"}, {"GH", "Ghana"}, {"GN", "Guinea"},
|
||||
{"GW", "Guinea-Bissau"}, {"KE", "Kenya"}, {"LS", "Lesotho"}, {"LR", "Liberia"},
|
||||
{"LY", "Libya"}, {"MG", "Madagascar"}, {"MW", "Malawi"}, {"ML", "Mali"},
|
||||
{"MR", "Mauritania"}, {"MU", "Mauritius"}, {"MA", "Morocco"}, {"MZ", "Mozambique"},
|
||||
{"NA", "Namibia"}, {"NE", "Niger"}, {"NG", "Nigeria"}, {"RW", "Rwanda"},
|
||||
{"ST", "Sao Tome and Principe"}, {"SN", "Senegal"}, {"SC", "Seychelles"}, {"SL", "Sierra Leone"},
|
||||
{"SO", "Somalia"}, {"ZA", "South Africa"}, {"SS", "South Sudan"}, {"SD", "Sudan"},
|
||||
{"TZ", "Tanzania"}, {"TG", "Togo"}, {"TN", "Tunisia"}, {"UG", "Uganda"},
|
||||
{"EH", "Western Sahara"}, {"ZM", "Zambia"}, {"ZW", "Zimbabwe"}
|
||||
};
|
||||
|
||||
QMap<QString, QString> antarcticaMap = {
|
||||
{"AQ", "Antarctica"}
|
||||
};
|
||||
|
||||
QMap<QString, QString> asiaMap = {
|
||||
{"AF", "Afghanistan"}, {"AM", "Armenia"}, {"AZ", "Azerbaijan"}, {"BH", "Bahrain"},
|
||||
{"BD", "Bangladesh"}, {"BT", "Bhutan"}, {"BN", "Brunei"}, {"KH", "Cambodia"},
|
||||
{"CN", "China"}, {"CY", "Cyprus"}, {"GE", "Georgia"}, {"IN", "India"},
|
||||
{"ID", "Indonesia"}, {"IR", "Iran"}, {"IQ", "Iraq"}, {"IL", "Israel"},
|
||||
{"JP", "Japan"}, {"JO", "Jordan"}, {"KZ", "Kazakhstan"}, {"KP", "North Korea"},
|
||||
{"KR", "South Korea"}, {"KW", "Kuwait"}, {"KG", "Kyrgyzstan"}, {"LA", "Laos"},
|
||||
{"LB", "Lebanon"}, {"MY", "Malaysia"}, {"MV", "Maldives"}, {"MN", "Mongolia"},
|
||||
{"MM", "Myanmar"}, {"NP", "Nepal"}, {"OM", "Oman"}, {"PK", "Pakistan"},
|
||||
{"PS", "Palestine"}, {"PH", "Philippines"}, {"QA", "Qatar"}, {"SA", "Saudi Arabia"},
|
||||
{"SG", "Singapore"}, {"LK", "Sri Lanka"}, {"SY", "Syria"}, {"TW", "Taiwan"},
|
||||
{"TJ", "Tajikistan"}, {"TH", "Thailand"}, {"TL", "Timor-Leste"}, {"TR", "Turkey"},
|
||||
{"TM", "Turkmenistan"}, {"AE", "United Arab Emirates"}, {"UZ", "Uzbekistan"}, {"VN", "Vietnam"},
|
||||
{"YE", "Yemen"}
|
||||
};
|
||||
|
||||
QMap<QString, QString> europeMap = {
|
||||
{"AL", "Albania"}, {"AD", "Andorra"}, {"AT", "Austria"}, {"BY", "Belarus"},
|
||||
{"BE", "Belgium"}, {"BA", "Bosnia and Herzegovina"}, {"BG", "Bulgaria"}, {"HR", "Croatia"},
|
||||
{"CY", "Cyprus"}, {"CZ", "Czech Republic"}, {"DK", "Denmark"}, {"EE", "Estonia"},
|
||||
{"FI", "Finland"}, {"FR", "France"}, {"DE", "Germany"}, {"GR", "Greece"},
|
||||
{"HU", "Hungary"}, {"IS", "Iceland"}, {"IE", "Ireland"}, {"IT", "Italy"},
|
||||
{"LV", "Latvia"}, {"LI", "Liechtenstein"}, {"LT", "Lithuania"}, {"LU", "Luxembourg"},
|
||||
{"MT", "Malta"}, {"MD", "Moldova"}, {"MC", "Monaco"}, {"ME", "Montenegro"},
|
||||
{"NL", "Netherlands"}, {"MK", "North Macedonia"}, {"NO", "Norway"}, {"PL", "Poland"},
|
||||
{"PT", "Portugal"}, {"RO", "Romania"}, {"RU", "Russia"}, {"SM", "San Marino"},
|
||||
{"RS", "Serbia"}, {"SK", "Slovakia"}, {"SI", "Slovenia"}, {"ES", "Spain"},
|
||||
{"SE", "Sweden"}, {"CH", "Switzerland"}, {"TR", "Turkey"}, {"UA", "Ukraine"},
|
||||
{"GB", "United Kingdom"}, {"VA", "Vatican City"}
|
||||
};
|
||||
|
||||
QMap<QString, QString> northAmericaMap = {
|
||||
{"AG", "Antigua and Barbuda"}, {"BS", "Bahamas"}, {"BB", "Barbados"}, {"BZ", "Belize"},
|
||||
{"CA", "Canada"}, {"CR", "Costa Rica"}, {"CU", "Cuba"}, {"DM", "Dominica"},
|
||||
{"DO", "Dominican Republic"}, {"SV", "El Salvador"}, {"GD", "Grenada"}, {"GT", "Guatemala"},
|
||||
{"HT", "Haiti"}, {"HN", "Honduras"}, {"JM", "Jamaica"}, {"MX", "Mexico"},
|
||||
{"NI", "Nicaragua"}, {"PA", "Panama"}, {"KN", "Saint Kitts and Nevis"}, {"LC", "Saint Lucia"},
|
||||
{"VC", "Saint Vincent and the Grenadines"}, {"TT", "Trinidad and Tobago"}, {"US", "United States"}
|
||||
};
|
||||
|
||||
QMap<QString, QString> oceaniaMap = {
|
||||
{"AU", "Australia"}, {"FJ", "Fiji"}, {"KI", "Kiribati"}, {"MH", "Marshall Islands"},
|
||||
{"FM", "Micronesia"}, {"NR", "Nauru"}, {"NZ", "New Zealand"}, {"PW", "Palau"},
|
||||
{"PG", "Papua New Guinea"}, {"WS", "Samoa"}, {"SB", "Solomon Islands"}, {"TO", "Tonga"},
|
||||
{"TV", "Tuvalu"}, {"VU", "Vanuatu"}
|
||||
};
|
||||
|
||||
QMap<QString, QString> southAmericaMap = {
|
||||
{"AR", "Argentina"}, {"BO", "Bolivia"}, {"BR", "Brazil"}, {"CL", "Chile"},
|
||||
{"CO", "Colombia"}, {"EC", "Ecuador"}, {"GY", "Guyana"}, {"PY", "Paraguay"},
|
||||
{"PE", "Peru"}, {"SR", "Suriname"}, {"TT", "Trinidad and Tobago"}, {"UY", "Uruguay"},
|
||||
{"VE", "Venezuela"}
|
||||
};
|
||||
|
||||
class ButtonSelectionControl : public QWidget {
|
||||
public:
|
||||
static QString selectedStates;
|
||||
static QString selectedCountries;
|
||||
|
||||
explicit ButtonSelectionControl(const QString &id, const QString &title, const QString &description,
|
||||
const QMap<QString, QString> &map, bool isCountry, QWidget *parent = nullptr)
|
||||
: QWidget(parent), country(isCountry) {
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
layout->setSpacing(10);
|
||||
|
||||
QHBoxLayout *buttonsLayout = new QHBoxLayout();
|
||||
buttonsLayout->setSpacing(10);
|
||||
layout->addLayout(buttonsLayout);
|
||||
|
||||
int count = 0;
|
||||
int max = country ? 3 : 4;
|
||||
|
||||
QJsonObject mapsSelected = QJsonDocument::fromJson(QString::fromStdString(Params().get("MapsSelected")).toUtf8()).object();
|
||||
|
||||
for (const QString &stateCode : map.keys()) {
|
||||
if (count % max == 0 && count != 0) {
|
||||
buttonsLayout = new QHBoxLayout();
|
||||
buttonsLayout->setSpacing(10);
|
||||
layout->addLayout(buttonsLayout);
|
||||
}
|
||||
|
||||
QPushButton *button = createButton(buttonsLayout, map[stateCode], stateCode);
|
||||
|
||||
QString key = country ? "nations" : "states";
|
||||
if (mapsSelected.contains(key)) {
|
||||
QJsonArray selectedItems = mapsSelected.value(key).toArray();
|
||||
button->setChecked(selectedItems.contains(stateCode));
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
adjustButtonWidths(buttonsLayout);
|
||||
}
|
||||
|
||||
private:
|
||||
bool country;
|
||||
|
||||
const QString buttonStyle = 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, QPushButton:checked {
|
||||
background-color: #4a4a4a;
|
||||
}
|
||||
QPushButton:checked:enabled {
|
||||
background-color: #33Ab4C;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
color: #33E4E4E4;
|
||||
}
|
||||
)";
|
||||
|
||||
QPushButton *createButton(QHBoxLayout *layout, const QString &label, const QString &stateCode) {
|
||||
QPushButton *button = new QPushButton(label, this);
|
||||
button->setCheckable(true);
|
||||
button->setStyleSheet(buttonStyle);
|
||||
connect(button, &QPushButton::clicked, this, [this, button, stateCode] { updateState(stateCode, button); });
|
||||
layout->addWidget(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
void adjustButtonWidths(QHBoxLayout *layout) {
|
||||
if (!layout || layout->count() == (country ? 3 : 4)) return;
|
||||
|
||||
for (int i = 0; i < layout->count(); ++i) {
|
||||
QWidget *widget = layout->itemAt(i)->widget();
|
||||
QPushButton *button = qobject_cast<QPushButton *>(widget);
|
||||
if (button) {
|
||||
button->setMinimumWidth(button->sizeHint().width());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateState(const QString &newState, QPushButton *button) {
|
||||
QString &selectedList = country ? selectedCountries : selectedStates;
|
||||
QStringList tempList = selectedList.split(',');
|
||||
|
||||
if (button->isChecked()) {
|
||||
if (!selectedList.isEmpty()) selectedList += ",";
|
||||
selectedList += newState;
|
||||
} else {
|
||||
tempList.removeAll(newState);
|
||||
selectedList = tempList.join(',');
|
||||
}
|
||||
|
||||
Params("/dev/shm/params").remove("OSMDownloadLocations");
|
||||
}
|
||||
};
|
||||
|
||||
QString ButtonSelectionControl::selectedStates = "";
|
||||
QString ButtonSelectionControl::selectedCountries = "";
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
T extractFromJson(const std::string &jsonData, const std::string &key, T defaultValue = 0) {
|
||||
std::string::size_type pos = jsonData.find(key);
|
||||
return pos != std::string::npos ? std::stol(jsonData.substr(pos + key.length())) : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
QString formatTime(long timeInSeconds) {
|
||||
long minutes = timeInSeconds / 60;
|
||||
long seconds = timeInSeconds % 60;
|
||||
QString formattedTime = (minutes > 0) ? QString::number(minutes) + "m " : "";
|
||||
formattedTime += QString::number(seconds) + "s";
|
||||
return formattedTime;
|
||||
}
|
||||
|
||||
QString formatDateTime(const std::chrono::time_point<std::chrono::system_clock> &timePoint) {
|
||||
return QDateTime::fromTime_t(std::chrono::system_clock::to_time_t(timePoint)).toString("h:mm ap");
|
||||
}
|
||||
|
||||
QString calculateElapsedTime(int totalFiles, int downloadedFiles, const std::chrono::steady_clock::time_point &startTime) {
|
||||
using namespace std::chrono;
|
||||
if (totalFiles <= 0 || downloadedFiles >= totalFiles) return "Calculating...";
|
||||
|
||||
long elapsed = duration_cast<seconds>(steady_clock::now() - startTime).count();
|
||||
return formatTime(elapsed);
|
||||
}
|
||||
|
||||
QString calculateETA(int totalFiles, int downloadedFiles, const std::chrono::steady_clock::time_point &startTime) {
|
||||
using namespace std::chrono;
|
||||
if (totalFiles <= 0 || downloadedFiles >= totalFiles) return "Calculating...";
|
||||
|
||||
long elapsed = duration_cast<seconds>(steady_clock::now() - startTime).count();
|
||||
|
||||
if (downloadedFiles == 0 || elapsed <= 0) {
|
||||
return "Calculating...";
|
||||
}
|
||||
|
||||
double averageTimePerFile = static_cast<double>(elapsed) / downloadedFiles;
|
||||
int remainingFiles = totalFiles - downloadedFiles;
|
||||
long estimatedTimeRemaining = static_cast<long>(averageTimePerFile * remainingFiles);
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock> estimatedCompletionTime = system_clock::now() + seconds(estimatedTimeRemaining);
|
||||
QString estimatedTimeStr = formatDateTime(estimatedCompletionTime);
|
||||
|
||||
return formatTime(estimatedTimeRemaining) + " (" + estimatedTimeStr + ")";
|
||||
|
||||
}
|
||||
|
||||
QString formatDownloadStatus(int totalFiles, int downloadedFiles) {
|
||||
if (totalFiles <= 0) return "Calculating...";
|
||||
if (downloadedFiles >= totalFiles) return "Downloaded";
|
||||
|
||||
int percentage = static_cast<int>(100 * downloadedFiles / totalFiles);
|
||||
return QString::asprintf("Downloading: %d/%d (%d%%)", downloadedFiles, totalFiles, percentage);
|
||||
}
|
||||
|
||||
quint64 calculateDirectorySize(const QString &path) {
|
||||
quint64 totalSize = 0;
|
||||
QDirIterator it(path, QDir::Files, QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
QFileInfo fileInfo(it.filePath());
|
||||
if (fileInfo.isFile()) {
|
||||
totalSize += fileInfo.size();
|
||||
}
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
QString formatSize(qint64 size) {
|
||||
const qint64 kb = 1024;
|
||||
const qint64 mb = 1024 * kb;
|
||||
const qint64 gb = 1024 * mb;
|
||||
|
||||
if (size < gb) {
|
||||
double sizeMB = size / static_cast<double>(mb);
|
||||
return QString::number(sizeMB, 'f', 2) + " MB";
|
||||
} else {
|
||||
double sizeGB = size / static_cast<double>(gb);
|
||||
return QString::number(sizeGB, 'f', 2) + " GB";
|
||||
}
|
||||
}
|
||||
@@ -1,574 +0,0 @@
|
||||
#include <QMouseEvent>
|
||||
|
||||
#include "selfdrive/frogpilot/navigation/ui/navigation_functions.h"
|
||||
#include "selfdrive/frogpilot/navigation/ui/navigation_settings.h"
|
||||
|
||||
FrogPilotNavigationPanel::FrogPilotNavigationPanel(QWidget *parent) : QFrame(parent), scene(uiState()->scene) {
|
||||
mainLayout = new QStackedLayout(this);
|
||||
|
||||
navigationWidget = new QWidget();
|
||||
QVBoxLayout *navigationLayout = new QVBoxLayout(navigationWidget);
|
||||
navigationLayout->setMargin(40);
|
||||
|
||||
FrogPilotListWidget *list = new FrogPilotListWidget(navigationWidget);
|
||||
|
||||
Primeless *primelessPanel = new Primeless(this);
|
||||
mainLayout->addWidget(primelessPanel);
|
||||
|
||||
ButtonControl *manageNOOButton = new ButtonControl(tr("Manage Primeless Navigation Settings"), tr("MANAGE"), tr("Manage primeless navigate on openpilot settings."));
|
||||
QObject::connect(manageNOOButton, &ButtonControl::clicked, [=]() { mainLayout->setCurrentWidget(primelessPanel); });
|
||||
QObject::connect(primelessPanel, &Primeless::backPress, [=]() { mainLayout->setCurrentWidget(navigationWidget); });
|
||||
list->addItem(manageNOOButton);
|
||||
manageNOOButton->setVisible(!uiState()->hasPrime());
|
||||
|
||||
std::vector<QString> scheduleOptions{tr("Manually"), tr("Weekly"), tr("Monthly")};
|
||||
ButtonParamControl *preferredSchedule = new ButtonParamControl("PreferredSchedule", tr("Maps Scheduler"),
|
||||
tr("Choose the frequency for updating maps with the latest OpenStreetMap (OSM) changes. "
|
||||
"Weekly updates begin at midnight every Sunday, while monthly updates start at midnight on the 1st of each month. "
|
||||
"If your device is off or not connected to WiFi during a scheduled update, the download will be conducted the next "
|
||||
"time you're offroad with a WiFi connection."),
|
||||
"",
|
||||
scheduleOptions);
|
||||
schedule = params.getInt("PreferredSchedule");
|
||||
schedulePending = params.getBool("SchedulePending");
|
||||
list->addItem(preferredSchedule);
|
||||
|
||||
list->addItem(offlineMapsSize = new LabelControl(tr("Offline Maps Size"), formatSize(calculateDirectorySize(offlineFolderPath))));
|
||||
offlineMapsSize->setVisible(true);
|
||||
list->addItem(lastMapsDownload = new LabelControl(tr("Last Download"), ""));
|
||||
lastMapsDownload->setVisible(!params.get("LastMapsUpdate").empty());
|
||||
lastMapsDownload->setText(QString::fromStdString(params.get("LastMapsUpdate")));
|
||||
list->addItem(offlineMapsStatus = new LabelControl(tr("Offline Maps Status"), ""));
|
||||
offlineMapsStatus->setVisible(false);
|
||||
list->addItem(offlineMapsETA = new LabelControl(tr("Offline Maps ETA"), ""));
|
||||
offlineMapsETA->setVisible(false);
|
||||
list->addItem(offlineMapsElapsed = new LabelControl(tr("Time Elapsed"), ""));
|
||||
offlineMapsElapsed->setVisible(false);
|
||||
|
||||
cancelDownloadButton = new ButtonControl(tr("Cancel Download"), tr("CANCEL"), tr("Cancel your current download."));
|
||||
QObject::connect(cancelDownloadButton, &ButtonControl::clicked, [this] { cancelDownload(this); });
|
||||
list->addItem(cancelDownloadButton);
|
||||
cancelDownloadButton->setVisible(false);
|
||||
|
||||
downloadOfflineMapsButton = new ButtonControl(tr("Download Offline Maps"), tr("DOWNLOAD"), tr("Download your selected offline maps to use with openpilot."));
|
||||
QObject::connect(downloadOfflineMapsButton, &ButtonControl::clicked, [this] { downloadMaps(); });
|
||||
list->addItem(downloadOfflineMapsButton);
|
||||
downloadOfflineMapsButton->setVisible(!params.get("MapsSelected").empty());
|
||||
|
||||
SelectMaps *mapsPanel = new SelectMaps(this);
|
||||
mainLayout->addWidget(mapsPanel);
|
||||
|
||||
QObject::connect(mapsPanel, &SelectMaps::setMaps, [=]() { setMaps(); });
|
||||
|
||||
ButtonControl *selectMapsButton = new ButtonControl(tr("Select Offline Maps"), tr("SELECT"), tr("Select your maps to use with OSM."));
|
||||
QObject::connect(selectMapsButton, &ButtonControl::clicked, [=]() { mainLayout->setCurrentWidget(mapsPanel); });
|
||||
QObject::connect(mapsPanel, &SelectMaps::backPress, [=]() { mainLayout->setCurrentWidget(navigationWidget); });
|
||||
list->addItem(selectMapsButton);
|
||||
|
||||
removeOfflineMapsButton = new ButtonControl(tr("Remove Offline Maps"), tr("REMOVE"), tr("Remove your downloaded offline maps to clear up storage space."));
|
||||
QObject::connect(removeOfflineMapsButton, &ButtonControl::clicked, [this] { removeMaps(this); });
|
||||
list->addItem(removeOfflineMapsButton);
|
||||
removeOfflineMapsButton->setVisible(QDir(offlineFolderPath).exists());
|
||||
|
||||
navigationLayout->addWidget(new ScrollView(list, navigationWidget));
|
||||
navigationWidget->setLayout(navigationLayout);
|
||||
mainLayout->addWidget(navigationWidget);
|
||||
mainLayout->setCurrentWidget(navigationWidget);
|
||||
|
||||
QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotNavigationPanel::updateState);
|
||||
|
||||
checkIfUpdateMissed();
|
||||
}
|
||||
|
||||
void FrogPilotNavigationPanel::hideEvent(QHideEvent *event) {
|
||||
QWidget::hideEvent(event);
|
||||
mainLayout->setCurrentWidget(navigationWidget);
|
||||
}
|
||||
|
||||
void FrogPilotNavigationPanel::updateState() {
|
||||
if (!isVisible() && downloadActive) updateVisibility(downloadActive);
|
||||
if (downloadActive) updateStatuses();
|
||||
if (schedule) downloadSchedule();
|
||||
}
|
||||
|
||||
void FrogPilotNavigationPanel::updateStatuses() {
|
||||
static std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
|
||||
osmDownloadProgress = params.get("OSMDownloadProgress");
|
||||
|
||||
const int totalFiles = extractFromJson<int>(osmDownloadProgress, "\"total_files\":");
|
||||
const int downloadedFiles = extractFromJson<int>(osmDownloadProgress, "\"downloaded_files\":");
|
||||
|
||||
if (paramsMemory.get("OSMDownloadLocations").empty()) {
|
||||
downloadActive = false;
|
||||
updateDownloadedLabel();
|
||||
qint64 fileSize = calculateDirectorySize(offlineFolderPath);
|
||||
offlineMapsSize->setText(formatSize(fileSize));
|
||||
previousOSMDownloadProgress = osmDownloadProgress;
|
||||
}
|
||||
|
||||
if (osmDownloadProgress != previousOSMDownloadProgress && isVisible()) {
|
||||
qint64 fileSize = calculateDirectorySize(offlineFolderPath);
|
||||
offlineMapsSize->setText(formatSize(fileSize));
|
||||
previousOSMDownloadProgress = osmDownloadProgress;
|
||||
}
|
||||
|
||||
elapsedTime = calculateElapsedTime(totalFiles, downloadedFiles, startTime);
|
||||
|
||||
offlineMapsElapsed->setText(elapsedTime);
|
||||
offlineMapsETA->setText(calculateETA(totalFiles, downloadedFiles, startTime));
|
||||
offlineMapsStatus->setText(formatDownloadStatus(totalFiles, downloadedFiles));
|
||||
|
||||
if (downloadActive != previousDownloadActive) {
|
||||
startTime = !downloadActive ? std::chrono::steady_clock::now() : startTime;
|
||||
updateVisibility(downloadActive);
|
||||
previousDownloadActive = downloadActive;
|
||||
}
|
||||
}
|
||||
|
||||
void FrogPilotNavigationPanel::updateVisibility(bool visibility) {
|
||||
cancelDownloadButton->setVisible(visibility);
|
||||
offlineMapsElapsed->setVisible(visibility);
|
||||
offlineMapsETA->setVisible(visibility);
|
||||
offlineMapsStatus->setVisible(visibility);
|
||||
downloadOfflineMapsButton->setVisible(!visibility);
|
||||
lastMapsDownload->setVisible(QDir(offlineFolderPath).exists() && !downloadActive);
|
||||
removeOfflineMapsButton->setVisible(QDir(offlineFolderPath).exists() && !downloadActive);
|
||||
update();
|
||||
}
|
||||
|
||||
void FrogPilotNavigationPanel::checkIfUpdateMissed() {
|
||||
std::string lastMapsUpdate = params.get("LastMapsUpdate");
|
||||
|
||||
if (lastMapsUpdate.empty() || schedule == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::time_t currentTime = std::time(nullptr);
|
||||
std::tm *now = std::localtime(¤tTime);
|
||||
|
||||
std::tm lastUpdate = {};
|
||||
sscanf(lastMapsUpdate.c_str(), "%d-%d-%d", &lastUpdate.tm_year, &lastUpdate.tm_mon, &lastUpdate.tm_mday);
|
||||
lastUpdate.tm_year -= 1900;
|
||||
lastUpdate.tm_mon -= 1;
|
||||
|
||||
std::time_t lastUpdateTime = std::mktime(&lastUpdate);
|
||||
std::tm *lastUpdateDay = std::localtime(&lastUpdateTime);
|
||||
|
||||
if (schedule == 1) {
|
||||
schedulePending = (now->tm_wday == 0 && lastUpdateDay->tm_wday != 0) || (now->tm_wday > lastUpdateDay->tm_wday);
|
||||
} else if (schedule == 2) {
|
||||
schedulePending = (now->tm_mday == 1 && lastUpdate.tm_mday != 1) || (now->tm_mon != lastUpdate.tm_mon);
|
||||
}
|
||||
}
|
||||
|
||||
void FrogPilotNavigationPanel::updateDownloadedLabel() {
|
||||
std::time_t t = std::time(nullptr);
|
||||
std::tm now = *std::localtime(&t);
|
||||
char dateBuffer[11];
|
||||
std::strftime(dateBuffer, sizeof(dateBuffer), "%Y-%m-%d", &now);
|
||||
QDate date = QDate::fromString(dateBuffer, "yyyy-MM-dd");
|
||||
|
||||
int day = date.day();
|
||||
std::string suffix = (day == 1 || day == 21 || day == 31) ? "st" :
|
||||
(day == 2 || day == 22) ? "nd" :
|
||||
(day == 3 || day == 23) ? "rd" : "th";
|
||||
std::string lastMapsUpdate = date.toString("MMMM d").toStdString() + suffix + date.toString(", yyyy").toStdString();
|
||||
lastMapsDownload->setText(QString::fromStdString(lastMapsUpdate));
|
||||
params.put("LastMapsUpdate", lastMapsUpdate);
|
||||
}
|
||||
|
||||
void FrogPilotNavigationPanel::downloadSchedule() {
|
||||
const bool wifi = (*uiState()->sm)["deviceState"].getDeviceState().getNetworkType() == cereal::DeviceState::NetworkType::WIFI;
|
||||
|
||||
const std::time_t t = std::time(nullptr);
|
||||
const std::tm *now = std::localtime(&t);
|
||||
|
||||
const bool isScheduleTime = (schedule == 1 && now->tm_wday == 0) || (schedule == 2 && now->tm_mday == 1);
|
||||
|
||||
if ((isScheduleTime || schedulePending) && !(scene.started || scheduleCompleted) && wifi) {
|
||||
downloadMaps();
|
||||
scheduleCompleted = true;
|
||||
|
||||
if (schedulePending) {
|
||||
schedulePending = false;
|
||||
params.putBool("SchedulePending", false);
|
||||
}
|
||||
} else if (!isScheduleTime) {
|
||||
scheduleCompleted = false;
|
||||
} else {
|
||||
if (!schedulePending) {
|
||||
params.putBool("SchedulePending", true);
|
||||
}
|
||||
schedulePending = true;
|
||||
}
|
||||
}
|
||||
|
||||
void FrogPilotNavigationPanel::cancelDownload(QWidget *parent) {
|
||||
if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to cancel the download?"), parent)) {
|
||||
std::thread([&] {
|
||||
std::system("pkill mapd");
|
||||
}).detach();
|
||||
if (FrogPilotConfirmationDialog::toggle(tr("Reboot required to re-enable map downloads"), tr("Reboot Now"), parent)) {
|
||||
Hardware::soft_reboot();
|
||||
}
|
||||
downloadActive = false;
|
||||
updateVisibility(downloadActive);
|
||||
downloadOfflineMapsButton->setVisible(downloadActive);
|
||||
}
|
||||
}
|
||||
|
||||
void FrogPilotNavigationPanel::downloadMaps() {
|
||||
params.remove("OSMDownloadProgress");
|
||||
paramsMemory.put("OSMDownloadLocations", params.get("MapsSelected"));
|
||||
removeOfflineMapsButton->setVisible(true);
|
||||
downloadActive = true;
|
||||
}
|
||||
|
||||
void FrogPilotNavigationPanel::removeMaps(QWidget *parent) {
|
||||
if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to delete all of your downloaded maps?"), parent)) {
|
||||
std::thread([&] {
|
||||
lastMapsDownload->setVisible(false);
|
||||
removeOfflineMapsButton->setVisible(false);
|
||||
offlineMapsSize->setText(formatSize(0));
|
||||
params.remove("LastMapsUpdate");
|
||||
std::system("rm -rf /data/media/0/osm/offline");
|
||||
}).detach();
|
||||
}
|
||||
}
|
||||
|
||||
void FrogPilotNavigationPanel::setMaps() {
|
||||
std::thread([&] {
|
||||
QStringList states = ButtonSelectionControl::selectedStates.split(',', QString::SkipEmptyParts);
|
||||
QStringList countries = ButtonSelectionControl::selectedCountries.split(',', QString::SkipEmptyParts);
|
||||
|
||||
QJsonObject json;
|
||||
json.insert("states", QJsonArray::fromStringList(states));
|
||||
json.insert("nations", QJsonArray::fromStringList(countries));
|
||||
|
||||
params.put("MapsSelected", QJsonDocument(json).toJson(QJsonDocument::Compact).toStdString());
|
||||
|
||||
if (!states.isEmpty() || !countries.isEmpty()) {
|
||||
downloadOfflineMapsButton->setVisible(true);
|
||||
update();
|
||||
} else {
|
||||
params.remove("MapsSelected");
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
SelectMaps::SelectMaps(QWidget *parent) : QWidget(parent) {
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
|
||||
QHBoxLayout *buttonsLayout = new QHBoxLayout();
|
||||
buttonsLayout->setContentsMargins(20, 40, 20, 0);
|
||||
|
||||
backButton = new QPushButton(tr("Back"), this);
|
||||
statesButton = new QPushButton(tr("States"), this);
|
||||
countriesButton = new QPushButton(tr("Countries"), this);
|
||||
|
||||
backButton->setFixedSize(400, 100);
|
||||
statesButton->setFixedSize(400, 100);
|
||||
countriesButton->setFixedSize(400, 100);
|
||||
|
||||
buttonsLayout->addWidget(backButton);
|
||||
buttonsLayout->addStretch();
|
||||
buttonsLayout->addWidget(statesButton);
|
||||
buttonsLayout->addStretch();
|
||||
buttonsLayout->addWidget(countriesButton);
|
||||
mainLayout->addLayout(buttonsLayout);
|
||||
|
||||
mainLayout->addWidget(horizontalLine());
|
||||
mainLayout->setSpacing(20);
|
||||
|
||||
mapsLayout = new QStackedLayout();
|
||||
mapsLayout->setMargin(40);
|
||||
mapsLayout->setSpacing(20);
|
||||
mainLayout->addLayout(mapsLayout);
|
||||
|
||||
QObject::connect(backButton, &QPushButton::clicked, this, [this]() { emit backPress(), emit setMaps(); });
|
||||
|
||||
FrogPilotListWidget *statesList = new FrogPilotListWidget();
|
||||
|
||||
LabelControl *northeastLabel = new LabelControl(tr("United States - Northeast"), "");
|
||||
statesList->addItem(northeastLabel);
|
||||
|
||||
ButtonSelectionControl *northeastControl = new ButtonSelectionControl("", tr(""), tr(""), northeastMap, false);
|
||||
statesList->addItem(northeastControl);
|
||||
|
||||
LabelControl *midwestLabel = new LabelControl(tr("United States - Midwest"), "");
|
||||
statesList->addItem(midwestLabel);
|
||||
|
||||
ButtonSelectionControl *midwestControl = new ButtonSelectionControl("", tr(""), tr(""), midwestMap, false);
|
||||
statesList->addItem(midwestControl);
|
||||
|
||||
LabelControl *southLabel = new LabelControl(tr("United States - South"), "");
|
||||
statesList->addItem(southLabel);
|
||||
|
||||
ButtonSelectionControl *southControl = new ButtonSelectionControl("", tr(""), tr(""), southMap, false);
|
||||
statesList->addItem(southControl);
|
||||
|
||||
LabelControl *westLabel = new LabelControl(tr("United States - West"), "");
|
||||
statesList->addItem(westLabel);
|
||||
|
||||
ButtonSelectionControl *westControl = new ButtonSelectionControl("", tr(""), tr(""), westMap, false);
|
||||
statesList->addItem(westControl);
|
||||
|
||||
LabelControl *territoriesLabel = new LabelControl(tr("United States - Territories"), "");
|
||||
statesList->addItem(territoriesLabel);
|
||||
|
||||
ButtonSelectionControl *territoriesControl = new ButtonSelectionControl("", tr(""), tr(""), territoriesMap, false);
|
||||
statesList->addItem(territoriesControl);
|
||||
|
||||
statesScrollView = new ScrollView(statesList);
|
||||
mapsLayout->addWidget(statesScrollView);
|
||||
|
||||
QObject::connect(statesButton, &QPushButton::clicked, this, [this]() {
|
||||
mapsLayout->setCurrentWidget(statesScrollView);
|
||||
statesButton->setStyleSheet(activeButtonStyle);
|
||||
countriesButton->setStyleSheet(normalButtonStyle);
|
||||
});
|
||||
|
||||
FrogPilotListWidget *countriesList = new FrogPilotListWidget();
|
||||
|
||||
LabelControl *africaLabel = new LabelControl(tr("Africa"), "");
|
||||
countriesList->addItem(africaLabel);
|
||||
|
||||
ButtonSelectionControl *africaControl = new ButtonSelectionControl("", tr(""), tr(""), africaMap, true);
|
||||
countriesList->addItem(africaControl);
|
||||
|
||||
LabelControl *antarcticaLabel = new LabelControl(tr("Antarctica"), "");
|
||||
countriesList->addItem(antarcticaLabel);
|
||||
|
||||
ButtonSelectionControl *antarcticaControl = new ButtonSelectionControl("", tr(""), tr(""), antarcticaMap, true);
|
||||
countriesList->addItem(antarcticaControl);
|
||||
|
||||
LabelControl *asiaLabel = new LabelControl(tr("Asia"), "");
|
||||
countriesList->addItem(asiaLabel);
|
||||
|
||||
ButtonSelectionControl *asiaControl = new ButtonSelectionControl("", tr(""), tr(""), asiaMap, true);
|
||||
countriesList->addItem(asiaControl);
|
||||
|
||||
LabelControl *europeLabel = new LabelControl(tr("Europe"), "");
|
||||
countriesList->addItem(europeLabel);
|
||||
|
||||
ButtonSelectionControl *europeControl = new ButtonSelectionControl("", tr(""), tr(""), europeMap, true);
|
||||
countriesList->addItem(europeControl);
|
||||
|
||||
LabelControl *northAmericaLabel = new LabelControl(tr("North America"), "");
|
||||
countriesList->addItem(northAmericaLabel);
|
||||
|
||||
ButtonSelectionControl *northAmericaControl = new ButtonSelectionControl("", tr(""), tr(""), northAmericaMap, true);
|
||||
countriesList->addItem(northAmericaControl);
|
||||
|
||||
LabelControl *oceaniaLabel = new LabelControl(tr("Oceania"), "");
|
||||
countriesList->addItem(oceaniaLabel);
|
||||
|
||||
ButtonSelectionControl *oceaniaControl = new ButtonSelectionControl("", tr(""), tr(""), oceaniaMap, true);
|
||||
countriesList->addItem(oceaniaControl);
|
||||
|
||||
LabelControl *southAmericaLabel = new LabelControl(tr("South America"), "");
|
||||
countriesList->addItem(southAmericaLabel);
|
||||
|
||||
ButtonSelectionControl *southAmericaControl = new ButtonSelectionControl("", tr(""), tr(""), southAmericaMap, true);
|
||||
countriesList->addItem(southAmericaControl);
|
||||
|
||||
countriesScrollView = new ScrollView(countriesList);
|
||||
mapsLayout->addWidget(countriesScrollView);
|
||||
|
||||
QObject::connect(countriesButton, &QPushButton::clicked, this, [this]() {
|
||||
mapsLayout->setCurrentWidget(countriesScrollView);
|
||||
statesButton->setStyleSheet(normalButtonStyle);
|
||||
countriesButton->setStyleSheet(activeButtonStyle);
|
||||
});
|
||||
|
||||
mapsLayout->setCurrentWidget(statesScrollView);
|
||||
statesButton->setStyleSheet(activeButtonStyle);
|
||||
|
||||
setStyleSheet(R"(
|
||||
QPushButton {
|
||||
font-size: 50px;
|
||||
margin: 0px;
|
||||
padding: 15px;
|
||||
border-width: 0;
|
||||
border-radius: 30px;
|
||||
color: #dddddd;
|
||||
background-color: #393939;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #4a4a4a;
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
QString SelectMaps::activeButtonStyle = R"(
|
||||
font-size: 50px;
|
||||
margin: 0px;
|
||||
padding: 15px;
|
||||
border-width: 0;
|
||||
border-radius: 30px;
|
||||
color: #dddddd;
|
||||
background-color: #33Ab4C;
|
||||
)";
|
||||
|
||||
QString SelectMaps::normalButtonStyle = R"(
|
||||
font-size: 50px;
|
||||
margin: 0px;
|
||||
padding: 15px;
|
||||
border-width: 0;
|
||||
border-radius: 30px;
|
||||
color: #dddddd;
|
||||
background-color: #393939;
|
||||
)";
|
||||
|
||||
QFrame *SelectMaps::horizontalLine(QWidget *parent) const {
|
||||
QFrame *line = new QFrame(parent);
|
||||
|
||||
line->setFrameShape(QFrame::StyledPanel);
|
||||
line->setStyleSheet(R"(
|
||||
border-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
border-color: gray;
|
||||
)");
|
||||
line->setFixedHeight(2);
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
void SelectMaps::hideEvent(QHideEvent *event) {
|
||||
QWidget::hideEvent(event);
|
||||
emit setMaps();
|
||||
}
|
||||
|
||||
Primeless::Primeless(QWidget *parent) : QWidget(parent) {
|
||||
QStackedLayout *primelessLayout = new QStackedLayout(this);
|
||||
|
||||
QWidget *mainWidget = new QWidget();
|
||||
mainLayout = new QVBoxLayout(mainWidget);
|
||||
mainLayout->setMargin(40);
|
||||
|
||||
backButton = new QPushButton(tr("Back"), this);
|
||||
backButton->setObjectName("backButton");
|
||||
backButton->setFixedSize(400, 100);
|
||||
QObject::connect(backButton, &QPushButton::clicked, this, [this]() { emit backPress(); });
|
||||
mainLayout->addWidget(backButton, 0, Qt::AlignLeft);
|
||||
|
||||
list = new FrogPilotListWidget(mainWidget);
|
||||
|
||||
wifi = new WifiManager(this);
|
||||
ipLabel = new LabelControl(tr("Manage Your Settings At"), QString("%1:8082").arg(wifi->getIp4Address()));
|
||||
list->addItem(ipLabel);
|
||||
|
||||
std::vector<QString> searchOptions{tr("MapBox"), tr("Amap"), tr("Google")};
|
||||
ButtonParamControl *searchInput = new ButtonParamControl("SearchInput", tr("Destination Search Provider"),
|
||||
tr("Select a search provider for destination queries in Navigate on Openpilot. Options include MapBox (recommended), Amap, and Google Maps."),
|
||||
"", searchOptions);
|
||||
list->addItem(searchInput);
|
||||
|
||||
createMapboxKeyControl(publicMapboxKeyControl, tr("Public Mapbox Key"), "MapboxPublicKey", "pk.");
|
||||
createMapboxKeyControl(secretMapboxKeyControl, tr("Secret Mapbox Key"), "MapboxSecretKey", "sk.");
|
||||
|
||||
mapboxPublicKeySet = !params.get("MapboxPublicKey").empty();
|
||||
mapboxSecretKeySet = !params.get("MapboxSecretKey").empty();
|
||||
setupCompleted = mapboxPublicKeySet && mapboxSecretKeySet;
|
||||
|
||||
QHBoxLayout *setupLayout = new QHBoxLayout();
|
||||
setupLayout->setMargin(0);
|
||||
|
||||
imageLabel = new QLabel(this);
|
||||
pixmap.load(currentStep);
|
||||
imageLabel->setPixmap(pixmap.scaledToWidth(1500, Qt::SmoothTransformation));
|
||||
setupLayout->addWidget(imageLabel, 0, Qt::AlignCenter);
|
||||
imageLabel->hide();
|
||||
|
||||
ButtonControl *setupButton = new ButtonControl(tr("Setup Instructions"), tr("VIEW"), tr("View the instructions to set up MapBox for Primeless Navigation."), this);
|
||||
QObject::connect(setupButton, &ButtonControl::clicked, this, [this]() {
|
||||
updateStep();
|
||||
backButton->hide();
|
||||
list->setVisible(false);
|
||||
imageLabel->show();
|
||||
});
|
||||
list->addItem(setupButton);
|
||||
|
||||
QObject::connect(uiState(), &UIState::uiUpdate, this, &Primeless::updateState);
|
||||
|
||||
mainLayout->addLayout(setupLayout);
|
||||
mainLayout->addWidget(new ScrollView(list, mainWidget));
|
||||
mainWidget->setLayout(mainLayout);
|
||||
primelessLayout->addWidget(mainWidget);
|
||||
|
||||
setLayout(primelessLayout);
|
||||
|
||||
setStyleSheet(R"(
|
||||
QPushButton {
|
||||
font-size: 50px;
|
||||
margin: 0px;
|
||||
padding: 15px;
|
||||
border-width: 0;
|
||||
border-radius: 30px;
|
||||
color: #dddddd;
|
||||
background-color: #393939;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #4a4a4a;
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
void Primeless::hideEvent(QHideEvent *event) {
|
||||
QWidget::hideEvent(event);
|
||||
backButton->show();
|
||||
list->setVisible(true);
|
||||
imageLabel->hide();
|
||||
}
|
||||
|
||||
void Primeless::mousePressEvent(QMouseEvent *event) {
|
||||
backButton->show();
|
||||
list->setVisible(true);
|
||||
imageLabel->hide();
|
||||
}
|
||||
|
||||
void Primeless::updateState() {
|
||||
if (!isVisible()) return;
|
||||
|
||||
QString ipAddress = wifi->getIp4Address();
|
||||
ipLabel->setText(ipAddress.isEmpty() ? tr("Device Offline") : QString("%1:8082").arg(ipAddress));
|
||||
|
||||
mapboxPublicKeySet = !params.get("MapboxPublicKey").empty();
|
||||
mapboxSecretKeySet = !params.get("MapboxSecretKey").empty();
|
||||
setupCompleted = mapboxPublicKeySet && mapboxSecretKeySet && setupCompleted;
|
||||
|
||||
publicMapboxKeyControl->setText(mapboxPublicKeySet ? tr("REMOVE") : tr("ADD"));
|
||||
secretMapboxKeyControl->setText(mapboxSecretKeySet ? tr("REMOVE") : tr("ADD"));
|
||||
|
||||
if (imageLabel->isVisible()) {
|
||||
updateStep();
|
||||
}
|
||||
}
|
||||
|
||||
void Primeless::createMapboxKeyControl(ButtonControl *&control, const QString &label, const std::string ¶mKey, const QString &prefix) {
|
||||
control = new ButtonControl(label, "", tr("Manage your %1."), this);
|
||||
QObject::connect(control, &ButtonControl::clicked, this, [this, control, label, paramKey, prefix] {
|
||||
if (control->text() == tr("ADD")) {
|
||||
QString key = InputDialog::getText(tr("Enter your %1").arg(label), this);
|
||||
if (!key.startsWith(prefix)) {
|
||||
key = prefix + key;
|
||||
}
|
||||
if (key.length() >= 80) {
|
||||
params.put(paramKey, key.toStdString());
|
||||
}
|
||||
} else {
|
||||
params.remove(paramKey);
|
||||
}
|
||||
});
|
||||
list->addItem(control);
|
||||
control->setText(params.get(paramKey).empty() ? tr("ADD") : tr("REMOVE"));
|
||||
}
|
||||
|
||||
void Primeless::updateStep() {
|
||||
currentStep = setupCompleted ? "../frogpilot/navigation/navigation_training/setup_completed.png" :
|
||||
(mapboxPublicKeySet && mapboxSecretKeySet) ? "../frogpilot/navigation/navigation_training/both_keys_set.png" :
|
||||
mapboxPublicKeySet ? "../frogpilot/navigation/navigation_training/public_key_set.png" : "../frogpilot/navigation/navigation_training/no_keys_set.png";
|
||||
|
||||
pixmap.load(currentStep);
|
||||
imageLabel->setPixmap(pixmap.scaledToWidth(1500, Qt::SmoothTransformation));
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/frogpilot/ui/qt/widgets/frogpilot_controls.h"
|
||||
#include "selfdrive/ui/qt/network/wifi_manager.h"
|
||||
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||
#include "selfdrive/ui/qt/widgets/scrollview.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
class Primeless : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Primeless(QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
|
||||
private:
|
||||
void createMapboxKeyControl(ButtonControl *&control, const QString &label, const std::string ¶mKey, const QString &prefix);
|
||||
void updateState();
|
||||
void updateStep();
|
||||
|
||||
QVBoxLayout *mainLayout;
|
||||
FrogPilotListWidget *list;
|
||||
|
||||
QPushButton *backButton;
|
||||
QLabel *imageLabel;
|
||||
|
||||
ButtonControl *publicMapboxKeyControl;
|
||||
ButtonControl *secretMapboxKeyControl;
|
||||
LabelControl *ipLabel;
|
||||
|
||||
WifiManager *wifi;
|
||||
|
||||
bool mapboxPublicKeySet;
|
||||
bool mapboxSecretKeySet;
|
||||
bool setupCompleted;
|
||||
QPixmap pixmap;
|
||||
QString currentStep = "../assets/images/setup_completed.png";
|
||||
|
||||
Params params;
|
||||
|
||||
signals:
|
||||
void backPress();
|
||||
};
|
||||
|
||||
class SelectMaps : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SelectMaps(QWidget *parent = nullptr);
|
||||
|
||||
QFrame *horizontalLine(QWidget *parent = nullptr) const;
|
||||
|
||||
private:
|
||||
void hideEvent(QHideEvent *event);
|
||||
|
||||
ScrollView *countriesScrollView;
|
||||
ScrollView *statesScrollView;
|
||||
QStackedLayout *mapsLayout;
|
||||
|
||||
QPushButton *backButton;
|
||||
QPushButton *statesButton;
|
||||
QPushButton *countriesButton;
|
||||
|
||||
static QString activeButtonStyle;
|
||||
static QString normalButtonStyle;
|
||||
|
||||
signals:
|
||||
void backPress();
|
||||
void setMaps();
|
||||
};
|
||||
|
||||
class FrogPilotNavigationPanel : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FrogPilotNavigationPanel(QWidget *parent = 0);
|
||||
|
||||
private:
|
||||
void cancelDownload(QWidget *parent);
|
||||
void checkIfUpdateMissed();
|
||||
void downloadMaps();
|
||||
void downloadSchedule();
|
||||
void hideEvent(QHideEvent *event);
|
||||
void removeMaps(QWidget *parent);
|
||||
void setMaps();
|
||||
void updateDownloadedLabel();
|
||||
void updateState();
|
||||
void updateStatuses();
|
||||
void updateVisibility(bool visibility);
|
||||
|
||||
QStackedLayout *mainLayout;
|
||||
QWidget *navigationWidget;
|
||||
|
||||
ButtonControl *cancelDownloadButton;
|
||||
ButtonControl *downloadOfflineMapsButton;
|
||||
ButtonControl *redownloadOfflineMapsButton;
|
||||
ButtonControl *removeOfflineMapsButton;
|
||||
|
||||
LabelControl *lastMapsDownload;
|
||||
LabelControl *offlineMapsSize;
|
||||
LabelControl *offlineMapsStatus;
|
||||
LabelControl *offlineMapsETA;
|
||||
LabelControl *offlineMapsElapsed;
|
||||
|
||||
bool downloadActive;
|
||||
bool previousDownloadActive;
|
||||
bool scheduleCompleted;
|
||||
bool schedulePending;
|
||||
int schedule;
|
||||
QString elapsedTime;
|
||||
QString offlineFolderPath = "/data/media/0/osm/offline";
|
||||
std::string osmDownloadProgress;
|
||||
std::string previousOSMDownloadProgress;
|
||||
|
||||
Params params;
|
||||
Params paramsMemory{"/dev/shm/params"};
|
||||
UIScene &scene;
|
||||
};
|
||||
@@ -21,19 +21,14 @@ qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"]
|
||||
qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs)
|
||||
widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "qt/widgets/wifi.cc",
|
||||
"qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc",
|
||||
"qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc",
|
||||
"qt/widgets/offroad_alerts.cc", "qt/widgets/keyboard.cc",
|
||||
"qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc",
|
||||
"qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc",
|
||||
"../frogpilot/ui/qt/widgets/frogpilot_controls.cc", "../frogpilot/navigation/ui/navigation_settings.cc",
|
||||
"../frogpilot/ui/qt/widgets/frogpilot_controls.cc",
|
||||
"../frogpilot/ui/qt/offroad/control_settings.cc", "../frogpilot/ui/qt/offroad/vehicle_settings.cc",
|
||||
"../frogpilot/ui/qt/offroad/visual_settings.cc"]
|
||||
|
||||
qt_env['CPPDEFINES'] = []
|
||||
if maps:
|
||||
base_libs += ['QMapLibre']
|
||||
widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc",
|
||||
"qt/maps/map_eta.cc", "qt/maps/map_instructions.cc"]
|
||||
qt_env['CPPDEFINES'] += ["ENABLE_MAPS"]
|
||||
|
||||
widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs)
|
||||
Export('widgets')
|
||||
|
||||
@@ -8,15 +8,10 @@
|
||||
#include "selfdrive/ui/qt/offroad/experimental_mode.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/qt/widgets/drive_stats.h"
|
||||
#include "selfdrive/ui/qt/widgets/prime.h"
|
||||
#include "system/hardware/hw.h"
|
||||
|
||||
#include <QWebEngineView>
|
||||
|
||||
#ifdef ENABLE_MAPS
|
||||
#include "selfdrive/ui/qt/maps/map_settings.h"
|
||||
#endif
|
||||
|
||||
// HomeWindow: the container for the offroad and onroad UIs
|
||||
|
||||
HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
|
||||
@@ -40,7 +35,6 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
|
||||
slayout->addWidget(home);
|
||||
|
||||
onroad = new OnroadWindow(this);
|
||||
QObject::connect(onroad, &OnroadWindow::mapPanelRequested, this, [=] { sidebar->hide(); });
|
||||
slayout->addWidget(onroad);
|
||||
|
||||
// CLEARPILOT
|
||||
@@ -71,10 +65,6 @@ void HomeWindow::showSidebar(bool show) {
|
||||
sidebar->setVisible(show);
|
||||
}
|
||||
|
||||
void HomeWindow::showMapPanel(bool show) {
|
||||
onroad->showMapPanel(show);
|
||||
}
|
||||
|
||||
void HomeWindow::updateState(const UIState &s) {
|
||||
// const SubMaster &sm = *(s.sm);
|
||||
if (s.scene.started) {
|
||||
@@ -89,7 +79,6 @@ void HomeWindow::offroadTransition(bool offroad) {
|
||||
} else {
|
||||
sidebar->setVisible(false);
|
||||
slayout->setCurrentWidget(onroad);
|
||||
uiState()->scene.map_open = onroad->isMapVisible();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ public slots:
|
||||
void showDriverView(bool show, bool started=false);
|
||||
void showOnroad();
|
||||
void showSidebar(bool show);
|
||||
void showMapPanel(bool show);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent* e) override;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "selfdrive/ui/qt/offroad/experimental_mode.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/qt/widgets/drive_stats.h"
|
||||
#include "selfdrive/ui/qt/widgets/prime.h"
|
||||
#include "system/hardware/hw.h"
|
||||
|
||||
#ifdef ENABLE_MAPS
|
||||
|
||||
@@ -1,469 +0,0 @@
|
||||
#include "selfdrive/ui/qt/maps/map.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <eigen3/Eigen/Dense>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "selfdrive/ui/qt/maps/map_helpers.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
|
||||
const int INTERACTION_TIMEOUT = 100;
|
||||
|
||||
const float MAX_ZOOM = 17;
|
||||
const float MIN_ZOOM = 14;
|
||||
const float MAX_PITCH = 50;
|
||||
const float MIN_PITCH = 0;
|
||||
const float MAP_SCALE = 2;
|
||||
|
||||
MapWindow::MapWindow(const QMapLibre::Settings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05, false) {
|
||||
QObject::connect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState);
|
||||
|
||||
map_overlay = new QWidget (this);
|
||||
map_overlay->setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
QVBoxLayout *overlay_layout = new QVBoxLayout(map_overlay);
|
||||
overlay_layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// Instructions
|
||||
map_instructions = new MapInstructions(this);
|
||||
map_instructions->setVisible(false);
|
||||
|
||||
map_eta = new MapETA(this);
|
||||
map_eta->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
map_eta->setFixedHeight(120);
|
||||
|
||||
error = new QLabel(this);
|
||||
error->setStyleSheet(R"(color:white;padding:50px 11px;font-size: 90px; background-color:rgba(0, 0, 0, 150);)");
|
||||
error->setAlignment(Qt::AlignCenter);
|
||||
|
||||
overlay_layout->addWidget(error);
|
||||
overlay_layout->addWidget(map_instructions);
|
||||
overlay_layout->addStretch(1);
|
||||
overlay_layout->addWidget(map_eta);
|
||||
|
||||
last_position = coordinate_from_param("LastGPSPosition");
|
||||
grabGesture(Qt::GestureType::PinchGesture);
|
||||
qDebug() << "MapWindow initialized";
|
||||
}
|
||||
|
||||
MapWindow::~MapWindow() {
|
||||
makeCurrent();
|
||||
}
|
||||
|
||||
void MapWindow::initLayers() {
|
||||
// This doesn't work from initializeGL
|
||||
if (!m_map->layerExists("modelPathLayer")) {
|
||||
qDebug() << "Initializing modelPathLayer";
|
||||
QVariantMap modelPath;
|
||||
//modelPath["id"] = "modelPathLayer";
|
||||
modelPath["type"] = "line";
|
||||
modelPath["source"] = "modelPathSource";
|
||||
m_map->addLayer("modelPathLayer", modelPath);
|
||||
m_map->setPaintProperty("modelPathLayer", "line-color", QColor("red"));
|
||||
m_map->setPaintProperty("modelPathLayer", "line-width", 5.0);
|
||||
m_map->setLayoutProperty("modelPathLayer", "line-cap", "round");
|
||||
}
|
||||
if (!m_map->layerExists("navLayer")) {
|
||||
qDebug() << "Initializing navLayer";
|
||||
QVariantMap nav;
|
||||
nav["type"] = "line";
|
||||
nav["source"] = "navSource";
|
||||
m_map->addLayer("navLayer", nav, "road-intersection");
|
||||
|
||||
QVariantMap transition;
|
||||
transition["duration"] = 400; // ms
|
||||
m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(uiState()->scene.navigate_on_openpilot));
|
||||
m_map->setPaintProperty("navLayer", "line-color-transition", transition);
|
||||
m_map->setPaintProperty("navLayer", "line-width", 7.5);
|
||||
m_map->setLayoutProperty("navLayer", "line-cap", "round");
|
||||
}
|
||||
if (!m_map->layerExists("pinLayer")) {
|
||||
qDebug() << "Initializing pinLayer";
|
||||
m_map->addImage("default_marker", QImage("../assets/navigation/default_marker.svg"));
|
||||
QVariantMap pin;
|
||||
pin["type"] = "symbol";
|
||||
pin["source"] = "pinSource";
|
||||
m_map->addLayer("pinLayer", pin);
|
||||
m_map->setLayoutProperty("pinLayer", "icon-pitch-alignment", "viewport");
|
||||
m_map->setLayoutProperty("pinLayer", "icon-image", "default_marker");
|
||||
m_map->setLayoutProperty("pinLayer", "icon-ignore-placement", true);
|
||||
m_map->setLayoutProperty("pinLayer", "icon-allow-overlap", true);
|
||||
m_map->setLayoutProperty("pinLayer", "symbol-sort-key", 0);
|
||||
m_map->setLayoutProperty("pinLayer", "icon-anchor", "bottom");
|
||||
}
|
||||
if (!m_map->layerExists("carPosLayer")) {
|
||||
qDebug() << "Initializing carPosLayer";
|
||||
m_map->addImage("label-arrow", QImage("../assets/images/triangle.svg"));
|
||||
|
||||
QVariantMap carPos;
|
||||
carPos["type"] = "symbol";
|
||||
carPos["source"] = "carPosSource";
|
||||
m_map->addLayer("carPosLayer", carPos);
|
||||
m_map->setLayoutProperty("carPosLayer", "icon-pitch-alignment", "map");
|
||||
m_map->setLayoutProperty("carPosLayer", "icon-image", "label-arrow");
|
||||
m_map->setLayoutProperty("carPosLayer", "icon-size", 0.5);
|
||||
m_map->setLayoutProperty("carPosLayer", "icon-ignore-placement", true);
|
||||
m_map->setLayoutProperty("carPosLayer", "icon-allow-overlap", true);
|
||||
// TODO: remove, symbol-sort-key does not seem to matter outside of each layer
|
||||
m_map->setLayoutProperty("carPosLayer", "symbol-sort-key", 0);
|
||||
}
|
||||
if (!m_map->layerExists("buildingsLayer")) {
|
||||
qDebug() << "Initializing buildingsLayer";
|
||||
QVariantMap buildings;
|
||||
buildings["id"] = "buildingsLayer";
|
||||
buildings["source"] = "composite";
|
||||
buildings["source-layer"] = "building";
|
||||
buildings["type"] = "fill-extrusion";
|
||||
buildings["minzoom"] = 15;
|
||||
m_map->addLayer("buildingsLayer", buildings);
|
||||
m_map->setFilter("buildingsLayer", QVariantList({"==", "extrude", "true"}));
|
||||
|
||||
QVariantList fillExtrusionHight = {
|
||||
"interpolate",
|
||||
QVariantList{"linear"},
|
||||
QVariantList{"zoom"},
|
||||
15, 0,
|
||||
15.05, QVariantList{"get", "height"}
|
||||
};
|
||||
|
||||
QVariantList fillExtrusionBase = {
|
||||
"interpolate",
|
||||
QVariantList{"linear"},
|
||||
QVariantList{"zoom"},
|
||||
15, 0,
|
||||
15.05, QVariantList{"get", "min_height"}
|
||||
};
|
||||
|
||||
QVariantList fillExtrusionOpacity = {
|
||||
"interpolate",
|
||||
QVariantList{"linear"},
|
||||
QVariantList{"zoom"},
|
||||
15, 0,
|
||||
15.5, .6,
|
||||
17, .6,
|
||||
20, 0
|
||||
};
|
||||
|
||||
m_map->setPaintProperty("buildingsLayer", "fill-extrusion-color", QColor("grey"));
|
||||
m_map->setPaintProperty("buildingsLayer", "fill-extrusion-opacity", fillExtrusionOpacity);
|
||||
m_map->setPaintProperty("buildingsLayer", "fill-extrusion-height", fillExtrusionHight);
|
||||
m_map->setPaintProperty("buildingsLayer", "fill-extrusion-base", fillExtrusionBase);
|
||||
m_map->setLayoutProperty("buildingsLayer", "visibility", "visible");
|
||||
}
|
||||
}
|
||||
|
||||
void MapWindow::updateState(const UIState &s) {
|
||||
if (!uiState()->scene.started) {
|
||||
return;
|
||||
}
|
||||
const SubMaster &sm = *(s.sm);
|
||||
update();
|
||||
|
||||
if (sm.updated("modelV2")) {
|
||||
// set path color on change, and show map on rising edge of navigate on openpilot
|
||||
bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabled() &&
|
||||
(sm["controlsState"].getControlsState().getEnabled() || sm["frogpilotCarControl"].getFrogpilotCarControl().getAlwaysOnLateral());
|
||||
if (nav_enabled != uiState()->scene.navigate_on_openpilot) {
|
||||
if (loaded_once) {
|
||||
m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled));
|
||||
}
|
||||
if (nav_enabled) {
|
||||
emit requestVisible(true);
|
||||
}
|
||||
}
|
||||
uiState()->scene.navigate_on_openpilot = nav_enabled;
|
||||
}
|
||||
|
||||
if (sm.updated("liveLocationKalman")) {
|
||||
auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman();
|
||||
auto locationd_pos = locationd_location.getPositionGeodetic();
|
||||
auto locationd_orientation = locationd_location.getCalibratedOrientationNED();
|
||||
auto locationd_velocity = locationd_location.getVelocityCalibrated();
|
||||
|
||||
// Check std norm
|
||||
auto pos_ecef_std = locationd_location.getPositionECEF().getStd();
|
||||
bool pos_accurate_enough = sqrt(pow(pos_ecef_std[0], 2) + pow(pos_ecef_std[1], 2) + pow(pos_ecef_std[2], 2)) < 100;
|
||||
|
||||
locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && pos_accurate_enough);
|
||||
|
||||
if (locationd_valid) {
|
||||
last_position = QMapLibre::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]);
|
||||
last_bearing = RAD2DEG(locationd_orientation.getValue()[2]);
|
||||
velocity_filter.update(std::max(10.0, locationd_velocity.getValue()[0]));
|
||||
}
|
||||
}
|
||||
|
||||
// Credit to jakethesnake420
|
||||
if (loaded_once && (sm.rcv_frame("uiPlan") != model_rcv_frame)) {
|
||||
auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman();
|
||||
auto model_path = model_to_collection(locationd_location.getCalibratedOrientationECEF(), locationd_location.getPositionECEF(), sm["uiPlan"].getUiPlan().getPosition());
|
||||
QMapLibre::Feature model_path_feature(QMapLibre::Feature::LineStringType, model_path, {}, {});
|
||||
QVariantMap modelV2Path;
|
||||
modelV2Path["type"] = "geojson";
|
||||
modelV2Path["data"] = QVariant::fromValue<QMapLibre::Feature>(model_path_feature);
|
||||
m_map->updateSource("modelPathSource", modelV2Path);
|
||||
model_rcv_frame = sm.rcv_frame("uiPlan");
|
||||
}
|
||||
|
||||
if (sm.updated("navRoute") && sm["navRoute"].getNavRoute().getCoordinates().size()) {
|
||||
auto nav_dest = coordinate_from_param("NavDestination");
|
||||
bool allow_open = std::exchange(last_valid_nav_dest, nav_dest) != nav_dest &&
|
||||
nav_dest && !isVisible();
|
||||
qWarning() << "Got new navRoute from navd. Opening map:" << allow_open;
|
||||
|
||||
// Show map on destination set/change
|
||||
if (allow_open) {
|
||||
emit requestSettings(false);
|
||||
emit requestVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
loaded_once = loaded_once || (m_map && m_map->isFullyLoaded());
|
||||
if (!loaded_once) {
|
||||
setError(tr("Map Loading"));
|
||||
return;
|
||||
}
|
||||
initLayers();
|
||||
|
||||
if (!locationd_valid) {
|
||||
setError(tr("Waiting for GPS"));
|
||||
} else if (routing_problem) {
|
||||
setError(tr("Waiting for route"));
|
||||
} else {
|
||||
setError("");
|
||||
}
|
||||
|
||||
if (locationd_valid) {
|
||||
// Update current location marker
|
||||
auto point = coordinate_to_collection(*last_position);
|
||||
QMapLibre::Feature feature1(QMapLibre::Feature::PointType, point, {}, {});
|
||||
QVariantMap carPosSource;
|
||||
carPosSource["type"] = "geojson";
|
||||
carPosSource["data"] = QVariant::fromValue<QMapLibre::Feature>(feature1);
|
||||
m_map->updateSource("carPosSource", carPosSource);
|
||||
|
||||
// Map bearing isn't updated when interacting, keep location marker up to date
|
||||
if (last_bearing) {
|
||||
m_map->setLayoutProperty("carPosLayer", "icon-rotate", *last_bearing - m_map->bearing());
|
||||
}
|
||||
}
|
||||
|
||||
if (interaction_counter == 0) {
|
||||
if (last_position) m_map->setCoordinate(*last_position);
|
||||
if (last_bearing) m_map->setBearing(*last_bearing);
|
||||
m_map->setZoom(util::map_val<float>(velocity_filter.x(), 0, 30, MAX_ZOOM, MIN_ZOOM));
|
||||
} else {
|
||||
interaction_counter--;
|
||||
}
|
||||
|
||||
if (sm.updated("navInstruction")) {
|
||||
// an invalid navInstruction packet with a nav destination is only possible if:
|
||||
// - API exception/no internet
|
||||
// - route response is empty
|
||||
// - any time navd is waiting for recompute_countdown
|
||||
routing_problem = !sm.valid("navInstruction") && coordinate_from_param("NavDestination").has_value();
|
||||
|
||||
if (sm.valid("navInstruction")) {
|
||||
auto i = sm["navInstruction"].getNavInstruction();
|
||||
map_eta->updateETA(i.getTimeRemaining(), i.getTimeRemainingTypical(), i.getDistanceRemaining());
|
||||
|
||||
if (locationd_valid) {
|
||||
m_map->setPitch(MAX_PITCH); // TODO: smooth pitching based on maneuver distance
|
||||
map_instructions->updateInstructions(i);
|
||||
}
|
||||
} else {
|
||||
clearRoute();
|
||||
}
|
||||
}
|
||||
|
||||
if (sm.rcv_frame("navRoute") != route_rcv_frame) {
|
||||
qWarning() << "Updating navLayer with new route";
|
||||
auto route = sm["navRoute"].getNavRoute();
|
||||
auto route_points = capnp_coordinate_list_to_collection(route.getCoordinates());
|
||||
QMapLibre::Feature feature(QMapLibre::Feature::LineStringType, route_points, {}, {});
|
||||
QVariantMap navSource;
|
||||
navSource["type"] = "geojson";
|
||||
navSource["data"] = QVariant::fromValue<QMapLibre::Feature>(feature);
|
||||
m_map->updateSource("navSource", navSource);
|
||||
m_map->setLayoutProperty("navLayer", "visibility", "visible");
|
||||
|
||||
route_rcv_frame = sm.rcv_frame("navRoute");
|
||||
updateDestinationMarker();
|
||||
}
|
||||
|
||||
// Map Styling - Credit goes to OPKR!
|
||||
int map_style = uiState()->scene.map_style;
|
||||
|
||||
if (map_style != previous_map_style) {
|
||||
std::unordered_map<int, std::string> styleUrls = {
|
||||
{0, "mapbox://styles/commaai/clkqztk0f00ou01qyhsa5bzpj"}, // Stock openpilot
|
||||
{1, "mapbox://styles/mapbox/streets-v11"}, // Mapbox Streets
|
||||
{2, "mapbox://styles/mapbox/outdoors-v11"}, // Mapbox Outdoors
|
||||
{3, "mapbox://styles/mapbox/light-v10"}, // Mapbox Light
|
||||
{4, "mapbox://styles/mapbox/dark-v10"}, // Mapbox Dark
|
||||
{5, "mapbox://styles/mapbox/satellite-v9"}, // Mapbox Satellite
|
||||
{6, "mapbox://styles/mapbox/satellite-streets-v11"}, // Mapbox Satellite Streets
|
||||
{7, "mapbox://styles/mapbox/navigation-day-v1"}, // Mapbox Navigation Day
|
||||
{8, "mapbox://styles/mapbox/navigation-night-v1"}, // Mapbox Navigation Night
|
||||
{9, "mapbox://styles/mapbox/traffic-night-v2"}, // Mapbox Traffic Night
|
||||
{10, "mapbox://styles/mike854/clt0hm8mw01ok01p4blkr27jp"}, // mike854's (Satellite hybrid)
|
||||
};
|
||||
|
||||
std::unordered_map<int, std::string>::iterator it = styleUrls.find(map_style);
|
||||
m_map->setStyleUrl(QString::fromStdString(it->second));
|
||||
}
|
||||
|
||||
previous_map_style = map_style;
|
||||
}
|
||||
|
||||
void MapWindow::setError(const QString &err_str) {
|
||||
if (err_str != error->text()) {
|
||||
error->setText(err_str);
|
||||
error->setVisible(!err_str.isEmpty());
|
||||
if (!err_str.isEmpty()) map_instructions->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
void MapWindow::resizeGL(int w, int h) {
|
||||
m_map->resize(size() / MAP_SCALE);
|
||||
map_overlay->setFixedSize(width(), height());
|
||||
}
|
||||
|
||||
void MapWindow::initializeGL() {
|
||||
m_map.reset(new QMapLibre::Map(this, m_settings, size(), 1));
|
||||
|
||||
if (last_position) {
|
||||
m_map->setCoordinateZoom(*last_position, MAX_ZOOM);
|
||||
} else {
|
||||
m_map->setCoordinateZoom(QMapLibre::Coordinate(64.31990695292795, -149.79038934046247), MIN_ZOOM);
|
||||
}
|
||||
|
||||
m_map->setMargins({0, 350, 0, 50});
|
||||
m_map->setPitch(MIN_PITCH);
|
||||
m_map->setStyleUrl("mapbox://styles/commaai/clkqztk0f00ou01qyhsa5bzpj");
|
||||
|
||||
QObject::connect(m_map.data(), &QMapLibre::Map::mapChanged, [=](QMapLibre::Map::MapChange change) {
|
||||
// set global animation duration to 0 ms so visibility changes are instant
|
||||
if (change == QMapLibre::Map::MapChange::MapChangeDidFinishLoadingStyle) {
|
||||
m_map->setTransitionOptions(0, 0);
|
||||
}
|
||||
if (change == QMapLibre::Map::MapChange::MapChangeDidFinishLoadingMap) {
|
||||
loaded_once = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MapWindow::paintGL() {
|
||||
if (!isVisible() || m_map.isNull()) return;
|
||||
m_map->render();
|
||||
}
|
||||
|
||||
void MapWindow::clearRoute() {
|
||||
if (!m_map.isNull()) {
|
||||
m_map->setLayoutProperty("navLayer", "visibility", "none");
|
||||
m_map->setPitch(MIN_PITCH);
|
||||
updateDestinationMarker();
|
||||
}
|
||||
|
||||
map_instructions->setVisible(false);
|
||||
map_eta->setVisible(false);
|
||||
last_valid_nav_dest = std::nullopt;
|
||||
}
|
||||
|
||||
void MapWindow::mousePressEvent(QMouseEvent *ev) {
|
||||
m_lastPos = ev->localPos();
|
||||
ev->accept();
|
||||
}
|
||||
|
||||
void MapWindow::mouseDoubleClickEvent(QMouseEvent *ev) {
|
||||
if (last_position) m_map->setCoordinate(*last_position);
|
||||
if (last_bearing) m_map->setBearing(*last_bearing);
|
||||
m_map->setZoom(util::map_val<float>(velocity_filter.x(), 0, 30, MAX_ZOOM, MIN_ZOOM));
|
||||
update();
|
||||
|
||||
interaction_counter = 0;
|
||||
}
|
||||
|
||||
void MapWindow::mouseMoveEvent(QMouseEvent *ev) {
|
||||
QPointF delta = ev->localPos() - m_lastPos;
|
||||
|
||||
if (!delta.isNull()) {
|
||||
interaction_counter = INTERACTION_TIMEOUT;
|
||||
m_map->moveBy(delta / MAP_SCALE);
|
||||
update();
|
||||
}
|
||||
|
||||
m_lastPos = ev->localPos();
|
||||
ev->accept();
|
||||
}
|
||||
|
||||
void MapWindow::wheelEvent(QWheelEvent *ev) {
|
||||
if (ev->orientation() == Qt::Horizontal) {
|
||||
return;
|
||||
}
|
||||
|
||||
float factor = ev->delta() / 1200.;
|
||||
if (ev->delta() < 0) {
|
||||
factor = factor > -1 ? factor : 1 / factor;
|
||||
}
|
||||
|
||||
m_map->scaleBy(1 + factor, ev->pos() / MAP_SCALE);
|
||||
update();
|
||||
|
||||
interaction_counter = INTERACTION_TIMEOUT;
|
||||
ev->accept();
|
||||
}
|
||||
|
||||
bool MapWindow::event(QEvent *event) {
|
||||
if (event->type() == QEvent::Gesture) {
|
||||
return gestureEvent(static_cast<QGestureEvent*>(event));
|
||||
}
|
||||
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
bool MapWindow::gestureEvent(QGestureEvent *event) {
|
||||
if (QGesture *pinch = event->gesture(Qt::PinchGesture)) {
|
||||
pinchTriggered(static_cast<QPinchGesture *>(pinch));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MapWindow::pinchTriggered(QPinchGesture *gesture) {
|
||||
QPinchGesture::ChangeFlags changeFlags = gesture->changeFlags();
|
||||
if (changeFlags & QPinchGesture::ScaleFactorChanged) {
|
||||
// TODO: figure out why gesture centerPoint doesn't work
|
||||
m_map->scaleBy(gesture->scaleFactor(), {width() / 2.0 / MAP_SCALE, height() / 2.0 / MAP_SCALE});
|
||||
update();
|
||||
interaction_counter = INTERACTION_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
void MapWindow::offroadTransition(bool offroad) {
|
||||
if (offroad) {
|
||||
clearRoute();
|
||||
uiState()->scene.navigate_on_openpilot = false;
|
||||
routing_problem = false;
|
||||
} else {
|
||||
auto dest = coordinate_from_param("NavDestination");
|
||||
emit requestVisible(dest.has_value());
|
||||
}
|
||||
last_bearing = {};
|
||||
}
|
||||
|
||||
void MapWindow::updateDestinationMarker() {
|
||||
auto nav_dest = coordinate_from_param("NavDestination");
|
||||
if (nav_dest.has_value()) {
|
||||
auto point = coordinate_to_collection(*nav_dest);
|
||||
QMapLibre::Feature feature(QMapLibre::Feature::PointType, point, {}, {});
|
||||
QVariantMap pinSource;
|
||||
pinSource["type"] = "geojson";
|
||||
pinSource["data"] = QVariant::fromValue<QMapLibre::Feature>(feature);
|
||||
m_map->updateSource("pinSource", pinSource);
|
||||
m_map->setPaintProperty("pinLayer", "visibility", "visible");
|
||||
} else {
|
||||
m_map->setPaintProperty("pinLayer", "visibility", "none");
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QGeoCoordinate>
|
||||
#include <QGestureEvent>
|
||||
#include <QLabel>
|
||||
#include <QMap>
|
||||
#include <QMapLibre/Map>
|
||||
#include <QMapLibre/Settings>
|
||||
#include <QMouseEvent>
|
||||
#include <QOpenGLWidget>
|
||||
#include <QPixmap>
|
||||
#include <QPushButton>
|
||||
#include <QScopedPointer>
|
||||
#include <QString>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWheelEvent>
|
||||
|
||||
#include "cereal/messaging/messaging.h"
|
||||
#include "common/params.h"
|
||||
#include "common/util.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
#include "selfdrive/ui/qt/maps/map_eta.h"
|
||||
#include "selfdrive/ui/qt/maps/map_instructions.h"
|
||||
|
||||
class MapWindow : public QOpenGLWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MapWindow(const QMapLibre::Settings &);
|
||||
~MapWindow();
|
||||
|
||||
private:
|
||||
void initializeGL() final;
|
||||
void paintGL() final;
|
||||
void resizeGL(int w, int h) override;
|
||||
|
||||
QMapLibre::Settings m_settings;
|
||||
QScopedPointer<QMapLibre::Map> m_map;
|
||||
|
||||
void initLayers();
|
||||
|
||||
void mousePressEvent(QMouseEvent *ev) final;
|
||||
void mouseDoubleClickEvent(QMouseEvent *ev) final;
|
||||
void mouseMoveEvent(QMouseEvent *ev) final;
|
||||
void wheelEvent(QWheelEvent *ev) final;
|
||||
bool event(QEvent *event) final;
|
||||
bool gestureEvent(QGestureEvent *event);
|
||||
void pinchTriggered(QPinchGesture *gesture);
|
||||
void setError(const QString &err_str);
|
||||
|
||||
bool loaded_once = false;
|
||||
|
||||
// Panning
|
||||
QPointF m_lastPos;
|
||||
int interaction_counter = 0;
|
||||
|
||||
// Position
|
||||
std::optional<QMapLibre::Coordinate> last_valid_nav_dest;
|
||||
std::optional<QMapLibre::Coordinate> last_position;
|
||||
std::optional<float> last_bearing;
|
||||
FirstOrderFilter velocity_filter;
|
||||
bool locationd_valid = false;
|
||||
bool routing_problem = false;
|
||||
|
||||
QWidget *map_overlay;
|
||||
QLabel *error;
|
||||
MapInstructions* map_instructions;
|
||||
MapETA* map_eta;
|
||||
|
||||
// Blue with normal nav, green when nav is input into the model
|
||||
QColor getNavPathColor(bool nav_enabled) {
|
||||
return nav_enabled ? QColor("#31ee73") : QColor("#31a1ee");
|
||||
}
|
||||
|
||||
void clearRoute();
|
||||
void updateDestinationMarker();
|
||||
uint64_t route_rcv_frame = 0;
|
||||
|
||||
// FrogPilot variables
|
||||
Params params;
|
||||
|
||||
int previous_map_style;
|
||||
|
||||
uint64_t model_rcv_frame = 0;
|
||||
|
||||
private slots:
|
||||
void updateState(const UIState &s);
|
||||
|
||||
public slots:
|
||||
void offroadTransition(bool offroad);
|
||||
|
||||
signals:
|
||||
void requestVisible(bool visible);
|
||||
void requestSettings(bool settings);
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
#include "selfdrive/ui/qt/maps/map_eta.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QPainter>
|
||||
|
||||
#include "selfdrive/ui/qt/maps/map_helpers.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
const float MANEUVER_TRANSITION_THRESHOLD = 10;
|
||||
|
||||
MapETA::MapETA(QWidget *parent) : QWidget(parent) {
|
||||
setVisible(false);
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
eta_doc.setUndoRedoEnabled(false);
|
||||
eta_doc.setDefaultStyleSheet("body {font-family:Inter;font-size:70px;color:white;} b{font-weight:600;} td{padding:0 3px;}");
|
||||
}
|
||||
|
||||
void MapETA::paintEvent(QPaintEvent *event) {
|
||||
if (!eta_doc.isEmpty()) {
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(QColor(0, 0, 0, 255));
|
||||
QSizeF txt_size = eta_doc.size();
|
||||
p.drawRoundedRect((width() - txt_size.width()) / 2 - UI_BORDER_SIZE, 0, txt_size.width() + UI_BORDER_SIZE * 2, height() + 25, 25, 25);
|
||||
p.translate((width() - txt_size.width()) / 2, (height() - txt_size.height()) / 2);
|
||||
eta_doc.drawContents(&p);
|
||||
}
|
||||
}
|
||||
|
||||
void MapETA::updateETA(float s, float s_typical, float d) {
|
||||
// ETA
|
||||
auto eta_t = QDateTime::currentDateTime().addSecs(s).time();
|
||||
auto eta = format_24h ? std::pair{eta_t.toString("HH:mm"), tr("eta")}
|
||||
: std::pair{eta_t.toString("h:mm a").split(' ')[0], eta_t.toString("a")};
|
||||
|
||||
// Remaining time
|
||||
auto remaining = s < 3600 ? std::pair{QString::number(int(s / 60)), tr("min")}
|
||||
: std::pair{QString("%1:%2").arg((int)s / 3600).arg(((int)s % 3600) / 60, 2, 10, QLatin1Char('0')), tr("hr")};
|
||||
QString color = "#25DA6E";
|
||||
if (s / s_typical > 1.5)
|
||||
color = "#DA3025";
|
||||
else if (s / s_typical > 1.2)
|
||||
color = "#DAA725";
|
||||
|
||||
// Distance
|
||||
auto distance = map_format_distance(d, uiState()->scene.is_metric);
|
||||
|
||||
eta_doc.setHtml(QString(R"(<body><table><tr style="vertical-align:bottom;"><td><b>%1</b></td><td>%2</td>
|
||||
<td style="padding-left:40px;color:%3;"><b>%4</b></td><td style="padding-right:40px;color:%3;">%5</td>
|
||||
<td><b>%6</b></td><td>%7</td></tr></body>)")
|
||||
.arg(eta.first, eta.second, color, remaining.first, remaining.second, distance.first, distance.second));
|
||||
|
||||
setVisible(d >= MANEUVER_TRANSITION_THRESHOLD);
|
||||
update();
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QPaintEvent>
|
||||
#include <QTextDocument>
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/params.h"
|
||||
|
||||
class MapETA : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MapETA(QWidget * parent=nullptr);
|
||||
void updateETA(float seconds, float seconds_typical, float distance);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void showEvent(QShowEvent *event) override { format_24h = param.getBool("NavSettingTime24h"); }
|
||||
|
||||
bool format_24h = false;
|
||||
QTextDocument eta_doc;
|
||||
Params param;
|
||||
};
|
||||
@@ -1,153 +0,0 @@
|
||||
#include "selfdrive/ui/qt/maps/map_helpers.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "common/params.h"
|
||||
#include "system/hardware/hw.h"
|
||||
#include "selfdrive/ui/qt/api.h"
|
||||
|
||||
QString get_mapbox_token() {
|
||||
// Valid for 4 weeks since we can't swap tokens on the fly
|
||||
return MAPBOX_TOKEN.isEmpty() ? CommaApi::create_jwt({}, 4 * 7 * 24 * 3600) : MAPBOX_TOKEN;
|
||||
}
|
||||
|
||||
QMapLibre::Settings get_mapbox_settings() {
|
||||
QMapLibre::Settings settings;
|
||||
settings.setProviderTemplate(QMapLibre::Settings::ProviderTemplate::MapboxProvider);
|
||||
|
||||
if (!Hardware::PC()) {
|
||||
settings.setCacheDatabasePath(MAPS_CACHE_PATH);
|
||||
settings.setCacheDatabaseMaximumSize(100 * 1024 * 1024);
|
||||
}
|
||||
settings.setApiBaseUrl(MAPS_HOST);
|
||||
settings.setApiKey(get_mapbox_token());
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
QGeoCoordinate to_QGeoCoordinate(const QMapLibre::Coordinate &in) {
|
||||
return QGeoCoordinate(in.first, in.second);
|
||||
}
|
||||
|
||||
QMapLibre::CoordinatesCollections model_to_collection(
|
||||
const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF,
|
||||
const cereal::LiveLocationKalman::Measurement::Reader &positionECEF,
|
||||
const cereal::XYZTData::Reader &line){
|
||||
|
||||
Eigen::Vector3d ecef(positionECEF.getValue()[0], positionECEF.getValue()[1], positionECEF.getValue()[2]);
|
||||
Eigen::Vector3d orient(calibratedOrientationECEF.getValue()[0], calibratedOrientationECEF.getValue()[1], calibratedOrientationECEF.getValue()[2]);
|
||||
Eigen::Matrix3d ecef_from_local = euler2rot(orient);
|
||||
|
||||
QMapLibre::Coordinates coordinates;
|
||||
auto x = line.getX();
|
||||
auto y = line.getY();
|
||||
auto z = line.getZ();
|
||||
for (int i = 0; i < x.size(); i++) {
|
||||
Eigen::Vector3d point_ecef = ecef_from_local * Eigen::Vector3d(x[i], y[i], z[i]) + ecef;
|
||||
Geodetic point_geodetic = ecef2geodetic((ECEF){.x = point_ecef[0], .y = point_ecef[1], .z = point_ecef[2]});
|
||||
coordinates.push_back({point_geodetic.lat, point_geodetic.lon});
|
||||
}
|
||||
|
||||
return {QMapLibre::CoordinatesCollection{coordinates}};
|
||||
}
|
||||
|
||||
QMapLibre::CoordinatesCollections coordinate_to_collection(const QMapLibre::Coordinate &c) {
|
||||
QMapLibre::Coordinates coordinates{c};
|
||||
return {QMapLibre::CoordinatesCollection{coordinates}};
|
||||
}
|
||||
|
||||
QMapLibre::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List<cereal::NavRoute::Coordinate>::Reader& coordinate_list) {
|
||||
QMapLibre::Coordinates coordinates;
|
||||
for (auto const &c : coordinate_list) {
|
||||
coordinates.push_back({c.getLatitude(), c.getLongitude()});
|
||||
}
|
||||
return {QMapLibre::CoordinatesCollection{coordinates}};
|
||||
}
|
||||
|
||||
QMapLibre::CoordinatesCollections coordinate_list_to_collection(const QList<QGeoCoordinate> &coordinate_list) {
|
||||
QMapLibre::Coordinates coordinates;
|
||||
for (auto &c : coordinate_list) {
|
||||
coordinates.push_back({c.latitude(), c.longitude()});
|
||||
}
|
||||
return {QMapLibre::CoordinatesCollection{coordinates}};
|
||||
}
|
||||
|
||||
QList<QGeoCoordinate> polyline_to_coordinate_list(const QString &polylineString) {
|
||||
QList<QGeoCoordinate> path;
|
||||
if (polylineString.isEmpty())
|
||||
return path;
|
||||
|
||||
QByteArray data = polylineString.toLatin1();
|
||||
|
||||
bool parsingLatitude = true;
|
||||
|
||||
int shift = 0;
|
||||
int value = 0;
|
||||
|
||||
QGeoCoordinate coord(0, 0);
|
||||
|
||||
for (int i = 0; i < data.length(); ++i) {
|
||||
unsigned char c = data.at(i) - 63;
|
||||
|
||||
value |= (c & 0x1f) << shift;
|
||||
shift += 5;
|
||||
|
||||
// another chunk
|
||||
if (c & 0x20)
|
||||
continue;
|
||||
|
||||
int diff = (value & 1) ? ~(value >> 1) : (value >> 1);
|
||||
|
||||
if (parsingLatitude) {
|
||||
coord.setLatitude(coord.latitude() + (double)diff/1e6);
|
||||
} else {
|
||||
coord.setLongitude(coord.longitude() + (double)diff/1e6);
|
||||
path.append(coord);
|
||||
}
|
||||
|
||||
parsingLatitude = !parsingLatitude;
|
||||
|
||||
value = 0;
|
||||
shift = 0;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
std::optional<QMapLibre::Coordinate> coordinate_from_param(const std::string ¶m) {
|
||||
QString json_str = QString::fromStdString(Params().get(param));
|
||||
if (json_str.isEmpty()) return {};
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(json_str.toUtf8());
|
||||
if (doc.isNull()) return {};
|
||||
|
||||
QJsonObject json = doc.object();
|
||||
if (json["latitude"].isDouble() && json["longitude"].isDouble()) {
|
||||
QMapLibre::Coordinate coord(json["latitude"].toDouble(), json["longitude"].toDouble());
|
||||
return coord;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// return {distance, unit}
|
||||
std::pair<QString, QString> map_format_distance(float d, bool is_metric) {
|
||||
auto round_distance = [](float d) -> float {
|
||||
return (d > 10) ? std::nearbyint(d) : std::nearbyint(d * 10) / 10.0;
|
||||
};
|
||||
|
||||
d = std::max(d, 0.0f);
|
||||
if (is_metric) {
|
||||
return (d > 500) ? std::pair{QString::number(round_distance(d / 1000)), QObject::tr("km")}
|
||||
: std::pair{QString::number(50 * std::nearbyint(d / 50)), QObject::tr("m")};
|
||||
} else {
|
||||
float feet = d * METER_TO_FOOT;
|
||||
return (feet > 500) ? std::pair{QString::number(round_distance(d * METER_TO_MILE)), QObject::tr("mi")}
|
||||
: std::pair{QString::number(50 * std::nearbyint(d / 50)), QObject::tr("ft")};
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <QMapLibre/Map>
|
||||
#include <QMapLibre/Settings>
|
||||
#include <eigen3/Eigen/Dense>
|
||||
#include <QGeoCoordinate>
|
||||
|
||||
#include "common/params.h"
|
||||
#include "common/util.h"
|
||||
#include "common/transformations/coordinates.hpp"
|
||||
#include "common/transformations/orientation.hpp"
|
||||
#include "cereal/messaging/messaging.h"
|
||||
|
||||
const QString MAPBOX_TOKEN = QString::fromStdString(Params().get("MapboxPublicKey"));
|
||||
const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "https://maps.comma.ai" : "https://api.mapbox.com").c_str();
|
||||
const QString MAPS_CACHE_PATH = "/data/mbgl-cache-navd.db";
|
||||
|
||||
QString get_mapbox_token();
|
||||
QMapLibre::Settings get_mapbox_settings();
|
||||
QGeoCoordinate to_QGeoCoordinate(const QMapLibre::Coordinate &in);
|
||||
QMapLibre::CoordinatesCollections model_to_collection(
|
||||
const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF,
|
||||
const cereal::LiveLocationKalman::Measurement::Reader &positionECEF,
|
||||
const cereal::XYZTData::Reader &line);
|
||||
QMapLibre::CoordinatesCollections coordinate_to_collection(const QMapLibre::Coordinate &c);
|
||||
QMapLibre::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List<cereal::NavRoute::Coordinate>::Reader &coordinate_list);
|
||||
QMapLibre::CoordinatesCollections coordinate_list_to_collection(const QList<QGeoCoordinate> &coordinate_list);
|
||||
QList<QGeoCoordinate> polyline_to_coordinate_list(const QString &polylineString);
|
||||
std::optional<QMapLibre::Coordinate> coordinate_from_param(const std::string ¶m);
|
||||
std::pair<QString, QString> map_format_distance(float d, bool is_metric);
|
||||
@@ -1,144 +0,0 @@
|
||||
#include "selfdrive/ui/qt/maps/map_instructions.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "selfdrive/ui/qt/maps/map_helpers.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
const QString ICON_SUFFIX = ".png";
|
||||
|
||||
MapInstructions::MapInstructions(QWidget *parent) : QWidget(parent) {
|
||||
is_rhd = Params().getBool("IsRhdDetected");
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(11, UI_BORDER_SIZE, 11, 20);
|
||||
|
||||
QHBoxLayout *top_layout = new QHBoxLayout;
|
||||
top_layout->addWidget(icon_01 = new QLabel, 0, Qt::AlignTop);
|
||||
|
||||
QVBoxLayout *right_layout = new QVBoxLayout;
|
||||
right_layout->setContentsMargins(9, 9, 9, 0);
|
||||
right_layout->addWidget(distance = new QLabel);
|
||||
distance->setStyleSheet(R"(font-size: 90px;)");
|
||||
|
||||
right_layout->addWidget(primary = new QLabel);
|
||||
primary->setStyleSheet(R"(font-size: 60px;)");
|
||||
primary->setWordWrap(true);
|
||||
|
||||
right_layout->addWidget(secondary = new QLabel);
|
||||
secondary->setStyleSheet(R"(font-size: 50px;)");
|
||||
secondary->setWordWrap(true);
|
||||
|
||||
top_layout->addLayout(right_layout);
|
||||
|
||||
main_layout->addLayout(top_layout);
|
||||
main_layout->addLayout(lane_layout = new QHBoxLayout);
|
||||
lane_layout->setAlignment(Qt::AlignHCenter);
|
||||
lane_layout->setSpacing(10);
|
||||
|
||||
setStyleSheet("color:white");
|
||||
QPalette pal = palette();
|
||||
pal.setColor(QPalette::Background, QColor(0, 0, 0, 150));
|
||||
setAutoFillBackground(true);
|
||||
setPalette(pal);
|
||||
|
||||
buildPixmapCache();
|
||||
}
|
||||
|
||||
void MapInstructions::buildPixmapCache() {
|
||||
QDir dir("../assets/navigation");
|
||||
for (QString fn : dir.entryList({"*" + ICON_SUFFIX}, QDir::Files)) {
|
||||
QPixmap pm(dir.filePath(fn));
|
||||
QString key = fn.left(fn.size() - ICON_SUFFIX.length());
|
||||
pm = pm.scaledToWidth(200, Qt::SmoothTransformation);
|
||||
|
||||
// Maneuver icons
|
||||
pixmap_cache[key] = pm;
|
||||
// lane direction icons
|
||||
if (key.contains("turn_")) {
|
||||
pixmap_cache["lane_" + key] = pm.scaled({125, 125}, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
// for rhd, reflect direction and then flip
|
||||
if (key.contains("_left")) {
|
||||
pixmap_cache["rhd_" + key.replace("_left", "_right")] = pm.transformed(QTransform().scale(-1, 1));
|
||||
} else if (key.contains("_right")) {
|
||||
pixmap_cache["rhd_" + key.replace("_right", "_left")] = pm.transformed(QTransform().scale(-1, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruction) {
|
||||
setUpdatesEnabled(false);
|
||||
|
||||
// Show instruction text
|
||||
QString primary_str = QString::fromStdString(instruction.getManeuverPrimaryText());
|
||||
QString secondary_str = QString::fromStdString(instruction.getManeuverSecondaryText());
|
||||
|
||||
primary->setText(primary_str);
|
||||
secondary->setVisible(secondary_str.length() > 0);
|
||||
secondary->setText(secondary_str);
|
||||
|
||||
auto distance_str_pair = map_format_distance(instruction.getManeuverDistance(), uiState()->scene.is_metric);
|
||||
distance->setText(QString("%1 %2").arg(distance_str_pair.first, distance_str_pair.second));
|
||||
|
||||
// Show arrow with direction
|
||||
QString type = QString::fromStdString(instruction.getManeuverType());
|
||||
QString modifier = QString::fromStdString(instruction.getManeuverModifier());
|
||||
if (!type.isEmpty()) {
|
||||
QString fn = "direction_" + type;
|
||||
if (!modifier.isEmpty()) {
|
||||
fn += "_" + modifier;
|
||||
}
|
||||
fn = fn.replace(' ', '_');
|
||||
bool rhd = is_rhd && (fn.contains("_left") || fn.contains("_right"));
|
||||
icon_01->setPixmap(pixmap_cache[!rhd ? fn : "rhd_" + fn]);
|
||||
icon_01->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
|
||||
icon_01->setVisible(true);
|
||||
} else {
|
||||
icon_01->setVisible(false);
|
||||
}
|
||||
|
||||
// Hide distance after arrival
|
||||
distance->setVisible(type != "arrive" || instruction.getManeuverDistance() > 0);
|
||||
|
||||
// Show lanes
|
||||
auto lanes = instruction.getLanes();
|
||||
for (int i = 0; i < lanes.size(); ++i) {
|
||||
bool active = lanes[i].getActive();
|
||||
const auto active_direction = lanes[i].getActiveDirection();
|
||||
|
||||
// TODO: Make more images based on active direction and combined directions
|
||||
QString fn = "lane_direction_";
|
||||
|
||||
// active direction has precedence
|
||||
if (active && active_direction != cereal::NavInstruction::Direction::NONE) {
|
||||
fn += "turn_" + DIRECTIONS[active_direction];
|
||||
} else {
|
||||
for (auto const &direction : lanes[i].getDirections()) {
|
||||
if (direction != cereal::NavInstruction::Direction::NONE) {
|
||||
fn += "turn_" + DIRECTIONS[direction];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!active) {
|
||||
fn += "_inactive";
|
||||
}
|
||||
|
||||
QLabel *label = (i < lane_labels.size()) ? lane_labels[i] : lane_labels.emplace_back(new QLabel);
|
||||
if (!label->parentWidget()) {
|
||||
lane_layout->addWidget(label);
|
||||
}
|
||||
label->setPixmap(pixmap_cache[fn]);
|
||||
label->setVisible(true);
|
||||
}
|
||||
|
||||
for (int i = lanes.size(); i < lane_labels.size(); ++i) {
|
||||
lane_labels[i]->setVisible(false);
|
||||
}
|
||||
|
||||
setUpdatesEnabled(true);
|
||||
setVisible(true);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <QHash>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
|
||||
#include "cereal/gen/cpp/log.capnp.h"
|
||||
|
||||
static std::map<cereal::NavInstruction::Direction, QString> DIRECTIONS = {
|
||||
{cereal::NavInstruction::Direction::NONE, "none"},
|
||||
{cereal::NavInstruction::Direction::LEFT, "left"},
|
||||
{cereal::NavInstruction::Direction::RIGHT, "right"},
|
||||
{cereal::NavInstruction::Direction::STRAIGHT, "straight"},
|
||||
{cereal::NavInstruction::Direction::SLIGHT_LEFT, "slight_left"},
|
||||
{cereal::NavInstruction::Direction::SLIGHT_RIGHT, "slight_right"},
|
||||
};
|
||||
|
||||
class MapInstructions : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QLabel *distance;
|
||||
QLabel *primary;
|
||||
QLabel *secondary;
|
||||
QLabel *icon_01;
|
||||
QHBoxLayout *lane_layout;
|
||||
bool is_rhd = false;
|
||||
std::vector<QLabel *> lane_labels;
|
||||
QHash<QString, QPixmap> pixmap_cache;
|
||||
|
||||
public:
|
||||
MapInstructions(QWidget * parent=nullptr);
|
||||
void buildPixmapCache();
|
||||
void updateInstructions(cereal::NavInstruction::Reader instruction);
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
#include "selfdrive/ui/qt/maps/map_panel.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/ui/qt/maps/map.h"
|
||||
#include "selfdrive/ui/qt/maps/map_settings.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
MapPanel::MapPanel(const QMapLibre::Settings &mapboxSettings, QWidget *parent) : QFrame(parent) {
|
||||
content_stack = new QStackedLayout(this);
|
||||
content_stack->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto map = new MapWindow(mapboxSettings);
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, map, &MapWindow::offroadTransition);
|
||||
QObject::connect(device(), &Device::interactiveTimeout, this, [=]() {
|
||||
content_stack->setCurrentIndex(0);
|
||||
});
|
||||
QObject::connect(map, &MapWindow::requestVisible, this, [=](bool visible) {
|
||||
// when we show the map for a new route, signal HomeWindow to hide the sidebar
|
||||
if (visible) { emit mapPanelRequested(); }
|
||||
setVisible(visible);
|
||||
});
|
||||
QObject::connect(map, &MapWindow::requestSettings, this, [=](bool settings) {
|
||||
content_stack->setCurrentIndex(settings ? 1 : 0);
|
||||
});
|
||||
content_stack->addWidget(map);
|
||||
|
||||
auto settings = new MapSettings(true, parent);
|
||||
QObject::connect(settings, &MapSettings::closeSettings, this, [=]() {
|
||||
content_stack->setCurrentIndex(0);
|
||||
});
|
||||
content_stack->addWidget(settings);
|
||||
}
|
||||
|
||||
void MapPanel::toggleMapSettings() {
|
||||
// show settings if not visible, then toggle between map and settings
|
||||
int new_index = isVisible() ? (1 - content_stack->currentIndex()) : 1;
|
||||
content_stack->setCurrentIndex(new_index);
|
||||
emit mapPanelRequested();
|
||||
show();
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QFrame>
|
||||
#include <QMapLibre/Settings>
|
||||
#include <QStackedLayout>
|
||||
|
||||
class MapPanel : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MapPanel(const QMapLibre::Settings &settings, QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void mapPanelRequested();
|
||||
|
||||
public slots:
|
||||
void toggleMapSettings();
|
||||
|
||||
private:
|
||||
QStackedLayout *content_stack;
|
||||
};
|
||||
@@ -1,393 +0,0 @@
|
||||
#include "selfdrive/ui/qt/maps/map_settings.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
|
||||
#include "common/util.h"
|
||||
#include "selfdrive/ui/qt/request_repeater.h"
|
||||
#include "selfdrive/ui/qt/widgets/scrollview.h"
|
||||
|
||||
static void swap(QJsonValueRef v1, QJsonValueRef v2) { std::swap(v1, v2); }
|
||||
|
||||
static bool locationEqual(const QJsonValue &v1, const QJsonValue &v2) {
|
||||
return v1["latitude"] == v2["latitude"] && v1["longitude"] == v2["longitude"];
|
||||
}
|
||||
|
||||
static qint64 convertTimestampToEpoch(const QString ×tamp) {
|
||||
QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate);
|
||||
return dt.isValid() ? dt.toSecsSinceEpoch() : 0;
|
||||
}
|
||||
|
||||
MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) {
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
setAttribute(Qt::WA_NoMousePropagation);
|
||||
|
||||
auto *frame = new QVBoxLayout(this);
|
||||
frame->setContentsMargins(40, 40, 40, 0);
|
||||
frame->setSpacing(0);
|
||||
|
||||
auto *heading_frame = new QHBoxLayout;
|
||||
heading_frame->setContentsMargins(0, 0, 0, 0);
|
||||
heading_frame->setSpacing(32);
|
||||
{
|
||||
if (closeable) {
|
||||
auto *close_btn = new QPushButton("←");
|
||||
close_btn->setStyleSheet(R"(
|
||||
QPushButton {
|
||||
color: #FFFFFF;
|
||||
font-size: 100px;
|
||||
padding-bottom: 8px;
|
||||
border 1px grey solid;
|
||||
border-radius: 70px;
|
||||
background-color: #292929;
|
||||
font-weight: 500;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #3B3B3B;
|
||||
}
|
||||
)");
|
||||
close_btn->setFixedSize(140, 140);
|
||||
QObject::connect(close_btn, &QPushButton::clicked, [=]() { emit closeSettings(); });
|
||||
// TODO: read map_on_left from ui state
|
||||
heading_frame->addWidget(close_btn);
|
||||
}
|
||||
|
||||
auto *heading = new QVBoxLayout;
|
||||
heading->setContentsMargins(0, 0, 0, 0);
|
||||
heading->setSpacing(16);
|
||||
{
|
||||
auto *title = new QLabel(tr("NAVIGATION"), this);
|
||||
title->setStyleSheet("color: #FFFFFF; font-size: 54px; font-weight: 600;");
|
||||
heading->addWidget(title);
|
||||
|
||||
subtitle = new QLabel(tr("Manage at connect.comma.ai"), this);
|
||||
subtitle->setStyleSheet("color: #A0A0A0; font-size: 40px; font-weight: 300;");
|
||||
heading->addWidget(subtitle);
|
||||
}
|
||||
heading_frame->addLayout(heading, 1);
|
||||
}
|
||||
frame->addLayout(heading_frame);
|
||||
frame->addSpacing(32);
|
||||
|
||||
current_widget = new DestinationWidget(this);
|
||||
QObject::connect(current_widget, &DestinationWidget::actionClicked,
|
||||
[]() { NavManager::instance()->setCurrentDestination({}); });
|
||||
frame->addWidget(current_widget);
|
||||
frame->addSpacing(32);
|
||||
|
||||
QWidget *destinations_container = new QWidget(this);
|
||||
destinations_layout = new QVBoxLayout(destinations_container);
|
||||
destinations_layout->setContentsMargins(0, 32, 0, 32);
|
||||
destinations_layout->setSpacing(20);
|
||||
destinations_layout->addWidget(home_widget = new DestinationWidget(this));
|
||||
destinations_layout->addWidget(work_widget = new DestinationWidget(this));
|
||||
QObject::connect(home_widget, &DestinationWidget::navigateTo, this, &MapSettings::navigateTo);
|
||||
QObject::connect(work_widget, &DestinationWidget::navigateTo, this, &MapSettings::navigateTo);
|
||||
destinations_layout->addStretch();
|
||||
|
||||
ScrollView *destinations_scroller = new ScrollView(destinations_container, this);
|
||||
destinations_scroller->setFrameShape(QFrame::NoFrame);
|
||||
frame->addWidget(destinations_scroller);
|
||||
|
||||
setStyleSheet("MapSettings { background-color: #333333; }");
|
||||
QObject::connect(NavManager::instance(), &NavManager::updated, this, &MapSettings::refresh);
|
||||
|
||||
wifi = new WifiManager(this);
|
||||
}
|
||||
|
||||
void MapSettings::showEvent(QShowEvent *event) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
void MapSettings::refresh() {
|
||||
if (!isVisible()) return;
|
||||
|
||||
setUpdatesEnabled(false);
|
||||
|
||||
auto get_w = [this](int i) {
|
||||
auto w = i < widgets.size() ? widgets[i] : widgets.emplace_back(new DestinationWidget);
|
||||
if (!w->parentWidget()) {
|
||||
destinations_layout->insertWidget(destinations_layout->count() - 1, w);
|
||||
QObject::connect(w, &DestinationWidget::navigateTo, this, &MapSettings::navigateTo);
|
||||
}
|
||||
return w;
|
||||
};
|
||||
|
||||
const auto current_dest = NavManager::instance()->currentDestination();
|
||||
if (!current_dest.isEmpty()) {
|
||||
current_widget->set(current_dest, true);
|
||||
} else {
|
||||
current_widget->unset("", true);
|
||||
}
|
||||
home_widget->unset(NAV_FAVORITE_LABEL_HOME);
|
||||
work_widget->unset(NAV_FAVORITE_LABEL_WORK);
|
||||
|
||||
int n = 0;
|
||||
for (auto location : NavManager::instance()->currentLocations()) {
|
||||
DestinationWidget *w = nullptr;
|
||||
auto dest = location.toObject();
|
||||
if (dest["save_type"].toString() == NAV_TYPE_FAVORITE) {
|
||||
auto label = dest["label"].toString();
|
||||
if (label == NAV_FAVORITE_LABEL_HOME) w = home_widget;
|
||||
if (label == NAV_FAVORITE_LABEL_WORK) w = work_widget;
|
||||
}
|
||||
w = w ? w : get_w(n++);
|
||||
w->set(dest, false);
|
||||
w->setVisible(!locationEqual(dest, current_dest));
|
||||
}
|
||||
for (; n < widgets.size(); ++n) widgets[n]->setVisible(false);
|
||||
|
||||
setUpdatesEnabled(true);
|
||||
|
||||
// Use IP for NOO without Prime
|
||||
if (!uiState()->hasPrime()) {
|
||||
QString ipAddress = QString("%1:8082").arg(wifi->getIp4Address());
|
||||
subtitle->setText(tr("Manage at %1").arg(ipAddress));
|
||||
}
|
||||
}
|
||||
|
||||
void MapSettings::navigateTo(const QJsonObject &place) {
|
||||
NavManager::instance()->setCurrentDestination(place);
|
||||
emit closeSettings();
|
||||
}
|
||||
|
||||
DestinationWidget::DestinationWidget(QWidget *parent) : QPushButton(parent) {
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto *frame = new QHBoxLayout(this);
|
||||
frame->setContentsMargins(32, 24, 32, 24);
|
||||
frame->setSpacing(32);
|
||||
|
||||
icon = new QLabel(this);
|
||||
icon->setAlignment(Qt::AlignCenter);
|
||||
icon->setFixedSize(96, 96);
|
||||
icon->setObjectName("icon");
|
||||
frame->addWidget(icon);
|
||||
|
||||
auto *inner_frame = new QVBoxLayout;
|
||||
inner_frame->setContentsMargins(0, 0, 0, 0);
|
||||
inner_frame->setSpacing(0);
|
||||
{
|
||||
title = new ElidedLabel(this);
|
||||
title->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
inner_frame->addWidget(title);
|
||||
|
||||
subtitle = new ElidedLabel(this);
|
||||
subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
subtitle->setObjectName("subtitle");
|
||||
inner_frame->addWidget(subtitle);
|
||||
}
|
||||
frame->addLayout(inner_frame, 1);
|
||||
|
||||
action = new QPushButton(this);
|
||||
action->setFixedSize(96, 96);
|
||||
action->setObjectName("action");
|
||||
action->setStyleSheet("font-size: 65px; font-weight: 600;");
|
||||
QObject::connect(action, &QPushButton::clicked, this, &QPushButton::clicked);
|
||||
QObject::connect(action, &QPushButton::clicked, this, &DestinationWidget::actionClicked);
|
||||
frame->addWidget(action);
|
||||
|
||||
setFixedHeight(164);
|
||||
setStyleSheet(R"(
|
||||
DestinationWidget { background-color: #202123; border-radius: 10px; }
|
||||
QLabel { color: #FFFFFF; font-size: 48px; font-weight: 400; }
|
||||
#icon { background-color: #3B4356; border-radius: 48px; }
|
||||
#subtitle { color: #9BA0A5; }
|
||||
#action { border: none; border-radius: 48px; color: #FFFFFF; padding-bottom: 4px; }
|
||||
|
||||
/* current destination */
|
||||
[current="true"] { background-color: #E8E8E8; }
|
||||
[current="true"] QLabel { color: #000000; }
|
||||
[current="true"] #icon { background-color: #42906B; }
|
||||
[current="true"] #subtitle { color: #333333; }
|
||||
[current="true"] #action { color: #202123; }
|
||||
|
||||
/* no saved destination */
|
||||
[set="false"] QLabel { color: #9BA0A5; }
|
||||
[current="true"][set="false"] QLabel { color: #A0000000; }
|
||||
|
||||
/* pressed */
|
||||
[current="false"]:pressed { background-color: #18191B; }
|
||||
[current="true"] #action:pressed { background-color: #D6D6D6; }
|
||||
)");
|
||||
QObject::connect(this, &QPushButton::clicked, [this]() { if (!dest.isEmpty()) emit navigateTo(dest); });
|
||||
}
|
||||
|
||||
void DestinationWidget::set(const QJsonObject &destination, bool current) {
|
||||
if (dest == destination) return;
|
||||
|
||||
dest = destination;
|
||||
setProperty("current", current);
|
||||
setProperty("set", true);
|
||||
|
||||
auto icon_pixmap = current ? icons().directions : icons().recent;
|
||||
auto title_text = destination["place_name"].toString();
|
||||
auto subtitle_text = destination["place_details"].toString();
|
||||
|
||||
if (destination["save_type"] == NAV_TYPE_FAVORITE) {
|
||||
if (destination["label"] == NAV_FAVORITE_LABEL_HOME) {
|
||||
icon_pixmap = icons().home;
|
||||
subtitle_text = title_text + ", " + subtitle_text;
|
||||
title_text = tr("Home");
|
||||
} else if (destination["label"] == NAV_FAVORITE_LABEL_WORK) {
|
||||
icon_pixmap = icons().work;
|
||||
subtitle_text = title_text + ", " + subtitle_text;
|
||||
title_text = tr("Work");
|
||||
} else {
|
||||
icon_pixmap = icons().favorite;
|
||||
}
|
||||
}
|
||||
|
||||
icon->setPixmap(icon_pixmap);
|
||||
|
||||
title->setText(title_text);
|
||||
subtitle->setText(subtitle_text);
|
||||
subtitle->setVisible(true);
|
||||
|
||||
// TODO: use pixmap
|
||||
action->setAttribute(Qt::WA_TransparentForMouseEvents, !current);
|
||||
action->setText(current ? "×" : "→");
|
||||
action->setVisible(true);
|
||||
|
||||
setStyleSheet(styleSheet());
|
||||
}
|
||||
|
||||
void DestinationWidget::unset(const QString &label, bool current) {
|
||||
dest = {};
|
||||
setProperty("current", current);
|
||||
setProperty("set", false);
|
||||
|
||||
if (label.isEmpty()) {
|
||||
icon->setPixmap(icons().directions);
|
||||
title->setText(tr("No destination set"));
|
||||
} else {
|
||||
QString title_text = label == NAV_FAVORITE_LABEL_HOME ? tr("home") : tr("work");
|
||||
icon->setPixmap(label == NAV_FAVORITE_LABEL_HOME ? icons().home : icons().work);
|
||||
title->setText(tr("No %1 location set").arg(title_text));
|
||||
}
|
||||
|
||||
subtitle->setVisible(false);
|
||||
action->setVisible(false);
|
||||
|
||||
setStyleSheet(styleSheet());
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
// singleton NavManager
|
||||
|
||||
NavManager *NavManager::instance() {
|
||||
static NavManager *request = new NavManager(qApp);
|
||||
return request;
|
||||
}
|
||||
|
||||
NavManager::NavManager(QObject *parent) : QObject(parent) {
|
||||
locations = QJsonDocument::fromJson(params.get("NavPastDestinations").c_str()).array();
|
||||
current_dest = QJsonDocument::fromJson(params.get("NavDestination").c_str()).object();
|
||||
if (auto dongle_id = getDongleId()) {
|
||||
{
|
||||
// Fetch favorite and recent locations
|
||||
QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/locations";
|
||||
RequestRepeater *repeater = new RequestRepeater(this, url, "ApiCache_NavDestinations", 30, true);
|
||||
QObject::connect(repeater, &RequestRepeater::requestDone, this, &NavManager::parseLocationsResponse);
|
||||
}
|
||||
{
|
||||
auto param_watcher = new ParamWatcher(this);
|
||||
QObject::connect(param_watcher, &ParamWatcher::paramChanged, this, &NavManager::updated);
|
||||
|
||||
// Destination set while offline
|
||||
QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/next";
|
||||
HttpRequest *deleter = new HttpRequest(this);
|
||||
RequestRepeater *repeater = new RequestRepeater(this, url, "", 10, true);
|
||||
QObject::connect(repeater, &RequestRepeater::requestDone, [=](const QString &resp, bool success) {
|
||||
if (success && resp != "null") {
|
||||
if (params.get("NavDestination").empty()) {
|
||||
qWarning() << "Setting NavDestination from /next" << resp;
|
||||
params.put("NavDestination", resp.toStdString());
|
||||
} else {
|
||||
qWarning() << "Got location from /next, but NavDestination already set";
|
||||
}
|
||||
// Send DELETE to clear destination server side
|
||||
deleter->sendRequest(url, HttpRequest::Method::DELETE);
|
||||
}
|
||||
|
||||
// athena can set destination at any time
|
||||
param_watcher->addParam("NavDestination");
|
||||
current_dest = QJsonDocument::fromJson(params.get("NavDestination").c_str()).object();
|
||||
emit updated();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NavManager::parseLocationsResponse(const QString &response, bool success) {
|
||||
if (!success || response == prev_response) return;
|
||||
|
||||
prev_response = response;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8());
|
||||
if (doc.isNull()) {
|
||||
qWarning() << "JSON Parse failed on navigation locations" << response;
|
||||
return;
|
||||
}
|
||||
|
||||
// set last activity time.
|
||||
auto remote_locations = doc.array();
|
||||
for (QJsonValueRef loc : remote_locations) {
|
||||
auto obj = loc.toObject();
|
||||
auto serverTime = convertTimestampToEpoch(obj["modified"].toString());
|
||||
obj.insert("time", qMax(serverTime, getLastActivity(obj)));
|
||||
loc = obj;
|
||||
}
|
||||
|
||||
locations = remote_locations;
|
||||
sortLocations();
|
||||
emit updated();
|
||||
}
|
||||
|
||||
void NavManager::sortLocations() {
|
||||
// Sort: alphabetical FAVORITES, and then most recent.
|
||||
// We don't need to care about the ordering of HOME and WORK. DestinationWidget always displays them at the top.
|
||||
std::stable_sort(locations.begin(), locations.end(), [](const QJsonValue &a, const QJsonValue &b) {
|
||||
if (a["save_type"] == NAV_TYPE_FAVORITE || b["save_type"] == NAV_TYPE_FAVORITE) {
|
||||
return (std::tuple(a["save_type"].toString(), a["place_name"].toString()) <
|
||||
std::tuple(b["save_type"].toString(), b["place_name"].toString()));
|
||||
} else {
|
||||
return a["time"].toVariant().toLongLong() > b["time"].toVariant().toLongLong();
|
||||
}
|
||||
});
|
||||
|
||||
write_param_future = std::async(std::launch::async, [destinations = QJsonArray(locations)]() {
|
||||
Params().put("NavPastDestinations", QJsonDocument(destinations).toJson().toStdString());
|
||||
});
|
||||
}
|
||||
|
||||
qint64 NavManager::getLastActivity(const QJsonObject &loc) const {
|
||||
qint64 last_activity = 0;
|
||||
auto it = std::find_if(locations.begin(), locations.end(),
|
||||
[&loc](const QJsonValue &l) { return locationEqual(loc, l); });
|
||||
if (it != locations.end()) {
|
||||
auto tm = it->toObject().value("time");
|
||||
if (!tm.isUndefined() && !tm.isNull()) {
|
||||
last_activity = tm.toVariant().toLongLong();
|
||||
}
|
||||
}
|
||||
return last_activity;
|
||||
}
|
||||
|
||||
void NavManager::setCurrentDestination(const QJsonObject &loc) {
|
||||
current_dest = loc;
|
||||
if (!current_dest.isEmpty()) {
|
||||
current_dest["time"] = QDateTime::currentSecsSinceEpoch();
|
||||
auto it = std::find_if(locations.begin(), locations.end(),
|
||||
[&loc](const QJsonValue &l) { return locationEqual(loc, l); });
|
||||
if (it != locations.end()) {
|
||||
*it = current_dest;
|
||||
sortLocations();
|
||||
}
|
||||
params.put("NavDestination", QJsonDocument(current_dest).toJson().toStdString());
|
||||
} else {
|
||||
params.remove("NavDestination");
|
||||
}
|
||||
emit updated();
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <future>
|
||||
#include <vector>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "common/params.h"
|
||||
#include "selfdrive/ui/qt/network/wifi_manager.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/qt/widgets/controls.h"
|
||||
|
||||
const QString NAV_TYPE_FAVORITE = "favorite";
|
||||
const QString NAV_TYPE_RECENT = "recent";
|
||||
|
||||
const QString NAV_FAVORITE_LABEL_HOME = "home";
|
||||
const QString NAV_FAVORITE_LABEL_WORK = "work";
|
||||
|
||||
class DestinationWidget;
|
||||
|
||||
class NavManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static NavManager *instance();
|
||||
QJsonArray currentLocations() const { return locations; }
|
||||
QJsonObject currentDestination() const { return current_dest; }
|
||||
void setCurrentDestination(const QJsonObject &loc);
|
||||
qint64 getLastActivity(const QJsonObject &loc) const;
|
||||
|
||||
signals:
|
||||
void updated();
|
||||
|
||||
private:
|
||||
NavManager(QObject *parent);
|
||||
void parseLocationsResponse(const QString &response, bool success);
|
||||
void sortLocations();
|
||||
|
||||
Params params;
|
||||
QString prev_response;
|
||||
QJsonArray locations;
|
||||
QJsonObject current_dest;
|
||||
std::future<void> write_param_future;
|
||||
};
|
||||
|
||||
class MapSettings : public QFrame {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MapSettings(bool closeable = false, QWidget *parent = nullptr);
|
||||
void navigateTo(const QJsonObject &place);
|
||||
|
||||
private:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void refresh();
|
||||
|
||||
QVBoxLayout *destinations_layout;
|
||||
DestinationWidget *current_widget;
|
||||
DestinationWidget *home_widget;
|
||||
DestinationWidget *work_widget;
|
||||
std::vector<DestinationWidget *> widgets;
|
||||
|
||||
// FrogPilot variables
|
||||
QLabel *subtitle;
|
||||
|
||||
WifiManager *wifi;
|
||||
|
||||
signals:
|
||||
void closeSettings();
|
||||
};
|
||||
|
||||
class DestinationWidget : public QPushButton {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DestinationWidget(QWidget *parent = nullptr);
|
||||
void set(const QJsonObject &location, bool current = false);
|
||||
void unset(const QString &label, bool current = false);
|
||||
|
||||
signals:
|
||||
void actionClicked();
|
||||
void navigateTo(const QJsonObject &destination);
|
||||
|
||||
private:
|
||||
struct NavIcons {
|
||||
QPixmap home, work, favorite, recent, directions;
|
||||
};
|
||||
|
||||
static NavIcons icons() {
|
||||
static NavIcons nav_icons {
|
||||
loadPixmap("../assets/navigation/icon_home.svg", {48, 48}),
|
||||
loadPixmap("../assets/navigation/icon_work.svg", {48, 48}),
|
||||
loadPixmap("../assets/navigation/icon_favorite.svg", {48, 48}),
|
||||
loadPixmap("../assets/navigation/icon_recent.svg", {48, 48}),
|
||||
loadPixmap("../assets/navigation/icon_directions.svg", {48, 48}),
|
||||
};
|
||||
return nav_icons;
|
||||
}
|
||||
|
||||
private:
|
||||
QLabel *icon, *title, *subtitle;
|
||||
QPushButton *action;
|
||||
QJsonObject dest;
|
||||
};
|
||||
@@ -205,16 +205,6 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
|
||||
});
|
||||
list->addItem(hiddenNetworkButton);
|
||||
|
||||
// Set initial config
|
||||
wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")), metered);
|
||||
|
||||
connect(uiState(), &UIState::primeTypeChanged, this, [=](PrimeType prime_type) {
|
||||
bool gsmVisible = prime_type == PrimeType::NONE || prime_type == PrimeType::LITE;
|
||||
roamingToggle->setVisible(gsmVisible);
|
||||
editApnButton->setVisible(gsmVisible);
|
||||
meteredToggle->setVisible(gsmVisible);
|
||||
});
|
||||
|
||||
main_layout->addWidget(new ScrollView(list, this));
|
||||
main_layout->addStretch(1);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "selfdrive/ui/qt/network/wifi_manager.h"
|
||||
|
||||
#include "selfdrive/ui/ui.h"
|
||||
#include "selfdrive/ui/qt/widgets/prime.h"
|
||||
|
||||
#include "common/params.h"
|
||||
#include "common/swaglog.h"
|
||||
@@ -432,10 +431,8 @@ void WifiManager::addTetheringConnection() {
|
||||
}
|
||||
|
||||
void WifiManager::tetheringActivated(QDBusPendingCallWatcher *call) {
|
||||
int prime_type = uiState()->primeType();
|
||||
int ipv4_forward = (prime_type == PrimeType::NONE || prime_type == PrimeType::LITE);
|
||||
|
||||
if (!ipv4_forward) {
|
||||
// Todo: this is false if not prime. Override?
|
||||
if (!ipv4_forward && false) {
|
||||
QTimer::singleShot(5000, this, [=] {
|
||||
qWarning() << "net.ipv4.ip_forward = 0";
|
||||
std::system("sudo sysctl net.ipv4.ip_forward=0");
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#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/qt/offroad/control_settings.h"
|
||||
#include "selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.h"
|
||||
#include "selfdrive/frogpilot/ui/qt/offroad/visual_settings.h"
|
||||
@@ -79,20 +78,6 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
|
||||
tr("Display speed in km/h instead of mph."),
|
||||
"../assets/offroad/icon_metric.png",
|
||||
},
|
||||
#ifdef ENABLE_MAPS
|
||||
{
|
||||
"NavSettingTime24h",
|
||||
tr("Show ETA in 24h Format"),
|
||||
tr("Use 24h format instead of am/pm"),
|
||||
"../assets/offroad/icon_metric.png",
|
||||
},
|
||||
{
|
||||
"NavSettingLeftSide",
|
||||
tr("Show Map on Left Side of UI"),
|
||||
tr("Show map on left side when in split screen view."),
|
||||
"../assets/offroad/icon_road.png",
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -161,8 +146,6 @@ void TogglesPanel::updateToggles() {
|
||||
disengage_on_accelerator_toggle->setVisible(!params.getBool("AlwaysOnLateral"));
|
||||
auto driver_camera_toggle = toggles["RecordFront"];
|
||||
driver_camera_toggle->setVisible(!(params.getBool("DeviceManagement") && params.getBool("NoLogging") && params.getBool("NoUploads")));
|
||||
auto nav_settings_left_toggle = toggles["NavSettingLeftSide"];
|
||||
nav_settings_left_toggle->setVisible(!params.getBool("FullMap"));
|
||||
|
||||
auto experimental_mode_toggle = toggles["ExperimentalMode"];
|
||||
auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"];
|
||||
@@ -178,10 +161,6 @@ void TogglesPanel::updateToggles() {
|
||||
.arg(tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. "
|
||||
"Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; "
|
||||
"mistakes should be expected."))
|
||||
.arg(tr("Navigate on openpilot"))
|
||||
.arg(tr("When navigation has a destination, openpilot will input the map information into the model. This provides useful context for the model and allows openpilot to keep left or right "
|
||||
"appropriately at forks/exits. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected, particularly around "
|
||||
"exits and forks. These mistakes can include unintended laneline crossings, late exit taking, driving towards dividing barriers in the gore areas, etc."))
|
||||
.arg(tr("New Driving Visualization"))
|
||||
.arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. "
|
||||
"When a navigation destination is set and the driving model is using it as input, the driving path on the map will turn green."));
|
||||
@@ -651,7 +630,6 @@ void SettingsWindow::showEvent(QShowEvent *event) {
|
||||
|
||||
void SettingsWindow::setCurrentPanel(int index, const QString ¶m) {
|
||||
panel_widget->setCurrentIndex(index);
|
||||
nav_btns->buttons()[index]->setChecked(true);
|
||||
if (!param.isEmpty()) {
|
||||
emit expandToggleDescription(param);
|
||||
}
|
||||
@@ -715,16 +693,13 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
|
||||
{tr("Toggles"), toggles},
|
||||
{tr("Software"), new SoftwarePanel(this)},
|
||||
{tr("Controls"), frogpilotControls},
|
||||
{tr("Navigation"), new FrogPilotNavigationPanel(this)},
|
||||
{tr("Vehicles"), new FrogPilotVehiclesPanel(this)},
|
||||
{tr("Visuals"), frogpilotVisuals},
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -741,7 +716,6 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
|
||||
}
|
||||
)");
|
||||
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
|
||||
|
||||
@@ -43,7 +43,6 @@ signals:
|
||||
private:
|
||||
QPushButton *sidebar_alert_widget;
|
||||
QWidget *sidebar_widget;
|
||||
QButtonGroup *nav_btns;
|
||||
QStackedWidget *panel_widget;
|
||||
|
||||
// FrogPilot variables
|
||||
|
||||
@@ -13,10 +13,6 @@
|
||||
#include "common/swaglog.h"
|
||||
#include "common/timing.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#ifdef ENABLE_MAPS
|
||||
#include "selfdrive/ui/qt/maps/map_helpers.h"
|
||||
#include "selfdrive/ui/qt/maps/map_panel.h"
|
||||
#endif
|
||||
|
||||
static void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity, const int angle = 0) {
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
@@ -60,16 +56,6 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent), scene(uiState()->
|
||||
split->setSpacing(0);
|
||||
split->addWidget(nvg);
|
||||
|
||||
if (getenv("DUAL_CAMERA_VIEW")) {
|
||||
CameraWidget *arCam = new CameraWidget("camerad", VISION_STREAM_ROAD, true, this);
|
||||
split->insertWidget(0, arCam);
|
||||
}
|
||||
|
||||
if (getenv("MAP_RENDER_VIEW")) {
|
||||
CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this);
|
||||
split->insertWidget(0, map_render);
|
||||
}
|
||||
|
||||
stacked_layout->addWidget(split_wrapper);
|
||||
|
||||
alerts = new OnroadAlerts(this);
|
||||
@@ -82,7 +68,6 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent), scene(uiState()->
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
QObject::connect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState);
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition);
|
||||
QObject::connect(uiState(), &UIState::primeChanged, this, &OnroadWindow::primeChanged);
|
||||
|
||||
QObject::connect(&clickTimer, &QTimer::timeout, this, [this]() {
|
||||
clickTimer.stop();
|
||||
@@ -99,12 +84,6 @@ void OnroadWindow::updateState(const UIState &s) {
|
||||
Alert alert = Alert::get(*(s.sm), s.scene.started_frame);
|
||||
alerts->updateAlert(alert);
|
||||
|
||||
if (s.scene.map_on_left || scene.full_map) {
|
||||
split->setDirection(QBoxLayout::LeftToRight);
|
||||
} else {
|
||||
split->setDirection(QBoxLayout::RightToLeft);
|
||||
}
|
||||
|
||||
nvg->updateState(s);
|
||||
|
||||
QColor bgColor = bg_colors[s.status];
|
||||
@@ -194,19 +173,6 @@ void OnroadWindow::mousePressEvent(QMouseEvent* e) {
|
||||
// widgetClicked = true;
|
||||
// }
|
||||
|
||||
#ifdef ENABLE_MAPS
|
||||
if (map != nullptr && !widgetClicked) {
|
||||
// Switch between map and sidebar when using navigate on openpilot
|
||||
bool sidebarVisible = geometry().x() > 0;
|
||||
bool show_map = uiState()->scene.navigate_on_openpilot ? sidebarVisible : !sidebarVisible;
|
||||
map->setVisible(show_map && !map->isVisible());
|
||||
if (scene.big_map) {
|
||||
map->setFixedWidth(width());
|
||||
} else {
|
||||
map->setFixedWidth(topWidget(this)->width() / 2 - UI_BORDER_SIZE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// propagation event to parent(HomeWindow)
|
||||
if (!widgetClicked) {
|
||||
QWidget::mousePressEvent(e);
|
||||
@@ -214,39 +180,10 @@ void OnroadWindow::mousePressEvent(QMouseEvent* e) {
|
||||
}
|
||||
|
||||
void OnroadWindow::offroadTransition(bool offroad) {
|
||||
#ifdef ENABLE_MAPS
|
||||
if (!offroad) {
|
||||
if (map == nullptr && (uiState()->hasPrime() || !MAPBOX_TOKEN.isEmpty())) {
|
||||
auto m = new MapPanel(get_mapbox_settings());
|
||||
map = m;
|
||||
|
||||
QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindow::mapPanelRequested);
|
||||
QObject::connect(nvg->map_settings_btn, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings);
|
||||
QObject::connect(nvg->map_settings_btn_bottom, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings);
|
||||
nvg->map_settings_btn->setEnabled(true);
|
||||
|
||||
m->setFixedWidth(topWidget(this)->width() / 2 - UI_BORDER_SIZE);
|
||||
split->insertWidget(0, m);
|
||||
|
||||
// hidden by default, made visible when navRoute is published
|
||||
m->setVisible(false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
alerts->updateAlert({});
|
||||
}
|
||||
|
||||
void OnroadWindow::primeChanged(bool prime) {
|
||||
#ifdef ENABLE_MAPS
|
||||
if (map && (!prime && MAPBOX_TOKEN.isEmpty())) {
|
||||
nvg->map_settings_btn->setEnabled(false);
|
||||
nvg->map_settings_btn->setVisible(false);
|
||||
map->deleteLater();
|
||||
map = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OnroadWindow::paintEvent(QPaintEvent *event) {
|
||||
QPainter p(this);
|
||||
@@ -448,100 +385,85 @@ void ExperimentalButton::changeMode() {
|
||||
}
|
||||
|
||||
void ExperimentalButton::updateState(const UIState &s, bool leadInfo) {
|
||||
const auto cs = (*s.sm)["controlsState"].getControlsState();
|
||||
bool eng = cs.getEngageable() || cs.getEnabled() || scene.always_on_lateral_active;
|
||||
if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) {
|
||||
engageable = eng;
|
||||
experimental_mode = cs.getExperimentalMode();
|
||||
update();
|
||||
}
|
||||
// const auto cs = (*s.sm)["controlsState"].getControlsState();
|
||||
// bool eng = cs.getEngageable() || cs.getEnabled() || scene.always_on_lateral_active;
|
||||
// if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) {
|
||||
// engageable = eng;
|
||||
// experimental_mode = cs.getExperimentalMode();
|
||||
// update();
|
||||
// }
|
||||
|
||||
// FrogPilot variables
|
||||
int randomEvent = scene.current_random_event;
|
||||
// // FrogPilot variables
|
||||
// int randomEvent = scene.current_random_event;
|
||||
|
||||
rotatingWheel = scene.rotating_wheel;
|
||||
wheelIcon = scene.wheel_icon;
|
||||
wheelIconGif = 0;
|
||||
// rotatingWheel = scene.rotating_wheel;
|
||||
// wheelIcon = scene.wheel_icon;
|
||||
// wheelIconGif = 0;
|
||||
|
||||
y_offset = leadInfo ? 10 : 0;
|
||||
// y_offset = leadInfo ? 10 : 0;
|
||||
|
||||
if (randomEvent == 0 && gifLabel) {
|
||||
delete gifLabel;
|
||||
gifLabel = nullptr;
|
||||
} else if (randomEvent == 1) {
|
||||
static int rotationDegree = 0;
|
||||
rotationDegree = (rotationDegree + 36) % 360;
|
||||
steeringAngleDeg = rotationDegree;
|
||||
wheelIcon = 7;
|
||||
update();
|
||||
// if (randomEvent == 0 && gifLabel) {
|
||||
// delete gifLabel;
|
||||
// gifLabel = nullptr;
|
||||
// } else if (randomEvent == 1) {
|
||||
// static int rotationDegree = 0;
|
||||
// rotationDegree = (rotationDegree + 36) % 360;
|
||||
// steeringAngleDeg = rotationDegree;
|
||||
// wheelIcon = 7;
|
||||
// update();
|
||||
|
||||
} else if (randomEvent == 2 || randomEvent == 3 || randomEvent == 4) {
|
||||
if (!gifLabel) {
|
||||
gifLabel = new QLabel(this);
|
||||
QMovie *movie = wheelImagesGif[randomEvent - 1];
|
||||
if (movie) {
|
||||
gifLabel->setMovie(movie);
|
||||
gifLabel->setFixedSize(img_size, img_size);
|
||||
gifLabel->move((width() - gifLabel->width()) / 2, (height() - gifLabel->height()) / 2 + y_offset);
|
||||
gifLabel->movie()->start();
|
||||
}
|
||||
}
|
||||
gifLabel->show();
|
||||
wheelIconGif = randomEvent - 1;
|
||||
update();
|
||||
// } else if (randomEvent == 2 || randomEvent == 3 || randomEvent == 4) {
|
||||
// if (!gifLabel) {
|
||||
// gifLabel = new QLabel(this);
|
||||
// QMovie *movie = wheelImagesGif[randomEvent - 1];
|
||||
// if (movie) {
|
||||
// gifLabel->setMovie(movie);
|
||||
// gifLabel->setFixedSize(img_size, img_size);
|
||||
// gifLabel->move((width() - gifLabel->width()) / 2, (height() - gifLabel->height()) / 2 + y_offset);
|
||||
// gifLabel->movie()->start();
|
||||
// }
|
||||
// }
|
||||
// gifLabel->show();
|
||||
// wheelIconGif = randomEvent - 1;
|
||||
// update();
|
||||
|
||||
} else if (rotatingWheel && steeringAngleDeg != scene.steering_angle_deg) {
|
||||
steeringAngleDeg = scene.steering_angle_deg;
|
||||
update();
|
||||
steeringAngleDeg = scene.steering_angle_deg;
|
||||
} else if (!rotatingWheel) {
|
||||
steeringAngleDeg = 0;
|
||||
}
|
||||
// } else if (rotatingWheel && steeringAngleDeg != scene.steering_angle_deg) {
|
||||
// steeringAngleDeg = scene.steering_angle_deg;
|
||||
// update();
|
||||
// steeringAngleDeg = scene.steering_angle_deg;
|
||||
// } else if (!rotatingWheel) {
|
||||
// steeringAngleDeg = 0;
|
||||
// }
|
||||
}
|
||||
|
||||
void ExperimentalButton::paintEvent(QPaintEvent *event) {
|
||||
if (wheelIcon < 0) {
|
||||
return;
|
||||
}
|
||||
// if (wheelIcon < 0) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
QPainter p(this);
|
||||
engage_img = wheelImages[wheelIcon];
|
||||
QPixmap img = wheelIcon != 0 ? engage_img : (experimental_mode ? experimental_img : engage_img);
|
||||
QMovie *gif = wheelImagesGif[wheelIconGif];
|
||||
// QPainter p(this);
|
||||
// engage_img = wheelImages[wheelIcon];
|
||||
// QPixmap img = wheelIcon != 0 ? engage_img : (experimental_mode ? experimental_img : engage_img);
|
||||
// QMovie *gif = wheelImagesGif[wheelIconGif];
|
||||
|
||||
QColor background_color = wheelIcon != 0 && !isDown() && engageable ?
|
||||
(scene.always_on_lateral_active ? QColor(10, 186, 181, 255) :
|
||||
(scene.conditional_status == 1 || scene.conditional_status == 3 || scene.conditional_status == 5 ? QColor(255, 246, 0, 255) :
|
||||
(experimental_mode ? QColor(218, 111, 37, 241) :
|
||||
(scene.traffic_mode_active ? QColor(201, 34, 49, 255) :
|
||||
(scene.navigate_on_openpilot ? QColor(49, 161, 238, 255) : QColor(0, 0, 0, 166)))))) :
|
||||
QColor(0, 0, 0, 166);
|
||||
// QColor background_color = wheelIcon != 0 && !isDown() && engageable ?
|
||||
// (scene.always_on_lateral_active ? QColor(10, 186, 181, 255) :
|
||||
// (scene.conditional_status == 1 || scene.conditional_status == 3 || scene.conditional_status == 5 ? QColor(255, 246, 0, 255) :
|
||||
// (experimental_mode ? QColor(218, 111, 37, 241) :
|
||||
// (scene.traffic_mode_active ? QColor(201, 34, 49, 255) :
|
||||
// (scene.navigate_on_openpilot ? QColor(49, 161, 238, 255) : QColor(0, 0, 0, 166)))))) :
|
||||
// QColor(0, 0, 0, 166);
|
||||
|
||||
if (!(scene.map_open && scene.big_map)) {
|
||||
if (wheelIconGif != 0) {
|
||||
drawIconGif(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), *gif, background_color, 1.0);
|
||||
} else {
|
||||
drawIcon(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), img, background_color, (isDown() || !engageable) ? 0.6 : 1.0, steeringAngleDeg);
|
||||
}
|
||||
}
|
||||
// if (!(scene.map_open && scene.big_map)) {
|
||||
// if (wheelIconGif != 0) {
|
||||
// drawIconGif(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), *gif, background_color, 1.0);
|
||||
// } else {
|
||||
// drawIcon(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), img, background_color, (isDown() || !engageable) ? 0.6 : 1.0, steeringAngleDeg);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
// MapSettingsButton
|
||||
MapSettingsButton::MapSettingsButton(QWidget *parent) : QPushButton(parent) {
|
||||
setFixedSize(btn_size, btn_size + 20);
|
||||
settings_img = loadPixmap("../assets/navigation/icon_directions_outlined.svg", {img_size, img_size});
|
||||
|
||||
// hidden by default, made visible if map is created (has prime or mapbox token)
|
||||
setVisible(false);
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
void MapSettingsButton::paintEvent(QPaintEvent *event) {
|
||||
QPainter p(this);
|
||||
drawIcon(p, QPoint(btn_size / 2, btn_size / 2), settings_img, QColor(0, 0, 0, 166), isDown() ? 0.6 : 1.0);
|
||||
}
|
||||
|
||||
|
||||
// Window that shows camera view and variety of info drawn on top
|
||||
AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent), scene(uiState()->scene) {
|
||||
@@ -572,9 +494,6 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par
|
||||
main_layout->addLayout(top_right_layout, 0);
|
||||
main_layout->setAlignment(top_right_layout, Qt::AlignTop | Qt::AlignRight);
|
||||
|
||||
map_settings_btn = new MapSettingsButton(this);
|
||||
main_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight);
|
||||
|
||||
dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5});
|
||||
|
||||
// Initialize FrogPilot widgets
|
||||
@@ -586,10 +505,8 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
|
||||
const SubMaster &sm = *(s.sm);
|
||||
|
||||
const bool cs_alive = sm.alive("controlsState");
|
||||
const bool nav_alive = sm.alive("navInstruction") && sm["navInstruction"].getValid();
|
||||
const auto cs = sm["controlsState"].getControlsState();
|
||||
const auto car_state = sm["carState"].getCarState();
|
||||
const auto nav_instruction = sm["navInstruction"].getNavInstruction();
|
||||
|
||||
// Handle older routes where vCruiseCluster is not set
|
||||
float v_cruise = cs.getVCruiseCluster() == 0.0 ? cs.getVCruise() : cs.getVCruiseCluster();
|
||||
@@ -605,19 +522,19 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
|
||||
speed = cs_alive ? std::max<float>(0.0, v_ego) : 0.0;
|
||||
speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH;
|
||||
|
||||
auto speed_limit_sign = nav_instruction.getSpeedLimitSign();
|
||||
speedLimit = slcOverridden ? scene.speed_limit_overridden_speed : speedLimitController ? scene.speed_limit : nav_alive ? nav_instruction.getSpeedLimit() : 0.0;
|
||||
speedLimit *= (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH);
|
||||
if (speedLimitController && !slcOverridden) {
|
||||
speedLimit = speedLimit - (showSLCOffset ? slcSpeedLimitOffset : 0);
|
||||
}
|
||||
// auto speed_limit_sign = nav_instruction.getSpeedLimitSign();
|
||||
// speedLimit = slcOverridden ? scene.speed_limit_overridden_speed : speedLimitController ? scene.speed_limit : nav_alive ? nav_instruction.getSpeedLimit() : 0.0;
|
||||
// speedLimit *= (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH);
|
||||
// if (speedLimitController && !slcOverridden) {
|
||||
// speedLimit = speedLimit - (showSLCOffset ? slcSpeedLimitOffset : 0);
|
||||
// }
|
||||
|
||||
has_us_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD) || (speedLimitController && !useViennaSLCSign);
|
||||
has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA) && !(speedLimitController && !useViennaSLCSign) || (speedLimitController && useViennaSLCSign);
|
||||
is_metric = s.scene.is_metric;
|
||||
speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph");
|
||||
hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE || customSignals != 0 && (turnSignalLeft || turnSignalRight) || bigMapOpen);
|
||||
status = s.status;
|
||||
// has_us_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD) || (speedLimitController && !useViennaSLCSign);
|
||||
// has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA) && !(speedLimitController && !useViennaSLCSign) || (speedLimitController && useViennaSLCSign);
|
||||
// is_metric = s.scene.is_metric;
|
||||
// speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph");
|
||||
// hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE || customSignals != 0 && (turnSignalLeft || turnSignalRight));
|
||||
// status = s.status;
|
||||
|
||||
// update engageability/experimental mode button
|
||||
experimental_btn->updateState(s, leadInfo);
|
||||
@@ -631,12 +548,6 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
|
||||
// DM icon transition
|
||||
dm_fade_state = std::clamp(dm_fade_state+0.2*(0.5-dmActive), 0.0, 1.0);
|
||||
|
||||
// hide map settings button for alerts and flip for right hand DM
|
||||
if (map_settings_btn->isEnabled()) {
|
||||
map_settings_btn->setVisible(!hideBottomIcons && compass && !scene.hide_map_icon);
|
||||
main_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignTop);
|
||||
}
|
||||
|
||||
updateFrogPilotWidgets();
|
||||
}
|
||||
|
||||
@@ -777,7 +688,7 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) {
|
||||
}
|
||||
|
||||
// current speed
|
||||
if (!(scene.hide_speed || bigMapOpen)) {
|
||||
if (!(scene.hide_speed)) {
|
||||
// CLEARPILOT changes to 120 from ~176
|
||||
// Maybe we want to hide this?
|
||||
p.setFont(InterFont(140, QFont::Bold));
|
||||
@@ -1193,9 +1104,6 @@ void AnnotatedCameraWidget::initializeFrogPilotWidgets() {
|
||||
compass_img = new Compass(this);
|
||||
// bottom_layout->addWidget(compass_img);
|
||||
|
||||
map_settings_btn_bottom = new MapSettingsButton(this);
|
||||
bottom_layout->addWidget(map_settings_btn_bottom);
|
||||
|
||||
main_layout->addLayout(bottom_layout);
|
||||
|
||||
themeConfiguration = {
|
||||
@@ -1293,9 +1201,6 @@ void AnnotatedCameraWidget::updateFrogPilotWidgets() {
|
||||
obstacleDistance = scene.obstacle_distance;
|
||||
obstacleDistanceStock = scene.obstacle_distance_stock;
|
||||
|
||||
mapOpen = scene.map_open;
|
||||
bigMapOpen = mapOpen && scene.big_map;
|
||||
|
||||
onroadDistanceButton = scene.onroad_distance_button;
|
||||
|
||||
roadNameUI = scene.road_name_ui;
|
||||
@@ -1350,7 +1255,7 @@ void AnnotatedCameraWidget::updateFrogPilotWidgets() {
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p) {
|
||||
if ((showAlwaysOnLateralStatusBar || showConditionalExperimentalStatusBar || roadNameUI) && !bigMapOpen) {
|
||||
if ((showAlwaysOnLateralStatusBar || showConditionalExperimentalStatusBar || roadNameUI)) {
|
||||
drawStatusBar(p);
|
||||
}
|
||||
|
||||
@@ -1363,7 +1268,7 @@ void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p) {
|
||||
// animationTimer->stop();
|
||||
// }
|
||||
|
||||
if (leadInfo && !bigMapOpen) {
|
||||
if (leadInfo) {
|
||||
drawLeadInfo(p);
|
||||
}
|
||||
|
||||
@@ -1391,12 +1296,6 @@ void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p) {
|
||||
// pedal_icons->updateState();
|
||||
// }
|
||||
|
||||
map_settings_btn_bottom->setEnabled(map_settings_btn->isEnabled());
|
||||
if (map_settings_btn_bottom->isEnabled()) {
|
||||
map_settings_btn_bottom->setVisible(!hideBottomIcons && !compass && !scene.hide_map_icon);
|
||||
bottom_layout->setAlignment(map_settings_btn_bottom, rightHandDM ? Qt::AlignLeft : Qt::AlignRight);
|
||||
}
|
||||
|
||||
// recorder_btn->setVisible(scene.screen_recorder && !mapOpen);
|
||||
recorder_btn->setVisible(false);
|
||||
}
|
||||
@@ -1581,7 +1480,7 @@ void AnnotatedCameraWidget::drawLeadInfo(QPainter &p) {
|
||||
constexpr int maxAccelDuration = 5000;
|
||||
|
||||
QString accelerationUnit = tr(" m/s²");
|
||||
leadDistanceUnit = tr(mapOpen ? "m" : "meters");
|
||||
leadDistanceUnit = tr("meters");
|
||||
leadSpeedUnit = tr("m/s");
|
||||
|
||||
float accelerationConversion = 1.0f;
|
||||
@@ -1635,15 +1534,15 @@ void AnnotatedCameraWidget::drawLeadInfo(QPainter &p) {
|
||||
.arg(accelerationUnit);
|
||||
|
||||
QString maxAccSuffix;
|
||||
if (!mapOpen) {
|
||||
|
||||
maxAccSuffix = tr(" - Max: %1%2")
|
||||
.arg(maxAcceleration * accelerationConversion, 0, 'f', 2)
|
||||
.arg(accelerationUnit);
|
||||
}
|
||||
|
||||
QString obstacleText = createText(mapOpen ? tr(" | Obstacle: ") : tr(" | Obstacle Factor: "), obstacleDistance);
|
||||
QString stopText = createText(mapOpen ? tr(" - Stop: ") : tr(" - Stop Factor: "), scene.stopped_equivalence);
|
||||
QString followText = " = " + createText(mapOpen ? tr("Follow: ") : tr("Follow Distance: "), scene.desired_follow);
|
||||
|
||||
QString obstacleText = createText(tr(" | Obstacle Factor: "), obstacleDistance);
|
||||
QString stopText = createText(tr(" - Stop Factor: "), scene.stopped_equivalence);
|
||||
QString followText = " = " + createText(tr("Follow Distance: "), scene.desired_follow);
|
||||
|
||||
auto createDiffText = [&](const double data, const double stockData) {
|
||||
double difference = std::round((data - stockData) * distanceConversion);
|
||||
@@ -1784,21 +1683,21 @@ void AnnotatedCameraWidget::drawStatusBar(QPainter &p) {
|
||||
{4, tr("Experimental Mode manually activated")},
|
||||
{5, tr("Conditional Experimental overridden")},
|
||||
{6, tr("Experimental Mode manually activated")},
|
||||
{7, tr("Experimental Mode activated for") + (mapOpen ? tr(" intersection") : tr(" upcoming intersection"))},
|
||||
{8, tr("Experimental Mode activated for") + (mapOpen ? tr(" turn") : tr(" upcoming turn"))},
|
||||
{9, tr("Experimental Mode activated due to") + (mapOpen ? tr(" SLC") : tr(" no speed limit set"))},
|
||||
{10, tr("Experimental Mode activated due to") + (mapOpen ? tr(" speed") : tr(" speed being less than ") + QString::number(scene.conditional_speed_lead) + (is_metric ? tr(" kph") : tr(" mph")))},
|
||||
{11, tr("Experimental Mode activated due to") + (mapOpen ? tr(" speed") : tr(" speed being less than ") + QString::number(scene.conditional_speed) + (is_metric ? tr(" kph") : tr(" mph")))},
|
||||
{7, tr("Experimental Mode activated for") + (tr(" upcoming intersection"))},
|
||||
{8, tr("Experimental Mode activated for") + (tr(" upcoming turn"))},
|
||||
{9, tr("Experimental Mode activated due to") + (tr(" no speed limit set"))},
|
||||
{10, tr("Experimental Mode activated due to") + (tr(" speed being less than ") + QString::number(scene.conditional_speed_lead) + (is_metric ? tr(" kph") : tr(" mph")))},
|
||||
{11, tr("Experimental Mode activated due to") + (tr(" speed being less than ") + QString::number(scene.conditional_speed) + (is_metric ? tr(" kph") : tr(" mph")))},
|
||||
{12, tr("Experimental Mode activated for slower lead")},
|
||||
{13, tr("Experimental Mode activated for turn") + (mapOpen ? "" : tr(" / lane change"))},
|
||||
{13, tr("Experimental Mode activated for turn") + (tr(" / lane change"))},
|
||||
{14, tr("Experimental Mode activated for curve")},
|
||||
{15, tr("Experimental Mode activated for stop") + (mapOpen ? "" : tr(" sign / stop light"))},
|
||||
{15, tr("Experimental Mode activated for stop") + (tr(" sign / stop light"))},
|
||||
};
|
||||
|
||||
QString roadName = roadNameUI ? QString::fromStdString(paramsMemory.get("RoadName")) : QString();
|
||||
|
||||
if (alwaysOnLateralActive && showAlwaysOnLateralStatusBar) {
|
||||
newStatus = tr("Always On Lateral active") + (mapOpen ? "" : tr(". Press the \"Cruise Control\" button to disable"));
|
||||
newStatus = tr("Always On Lateral active") + (tr(". Press the \"Cruise Control\" button to disable"));
|
||||
} else if (showConditionalExperimentalStatusBar) {
|
||||
newStatus = conditionalStatusMap[status != STATUS_DISENGAGED ? conditionalStatus : 0];
|
||||
}
|
||||
@@ -1807,7 +1706,7 @@ void AnnotatedCameraWidget::drawStatusBar(QPainter &p) {
|
||||
QString lkasSuffix = tr(". Double press the \"LKAS\" button to revert");
|
||||
QString screenSuffix = tr(". Double tap the screen to revert");
|
||||
|
||||
if (!alwaysOnLateralActive && !mapOpen && status != STATUS_DISENGAGED && !newStatus.isEmpty()) {
|
||||
if (!alwaysOnLateralActive && status != STATUS_DISENGAGED && !newStatus.isEmpty()) {
|
||||
if (conditionalStatus == 1 || conditionalStatus == 2) {
|
||||
newStatus += distanceSuffix;
|
||||
} else if (conditionalStatus == 3 || conditionalStatus == 4) {
|
||||
|
||||
@@ -127,19 +127,6 @@ private:
|
||||
int y_offset;
|
||||
};
|
||||
|
||||
|
||||
class MapSettingsButton : public QPushButton {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MapSettingsButton(QWidget *parent = 0);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
QPixmap settings_img;
|
||||
};
|
||||
|
||||
class PedalIcons : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -170,9 +157,6 @@ public:
|
||||
void updateState(const UIState &s);
|
||||
void updateLaneEdgeColor(QColor &bgColor);
|
||||
|
||||
MapSettingsButton *map_settings_btn;
|
||||
MapSettingsButton *map_settings_btn_bottom;
|
||||
|
||||
private:
|
||||
void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255);
|
||||
|
||||
@@ -221,13 +205,11 @@ private:
|
||||
QHBoxLayout *bottom_layout;
|
||||
|
||||
bool alwaysOnLateralActive;
|
||||
bool bigMapOpen;
|
||||
bool blindSpotLeft;
|
||||
bool blindSpotRight;
|
||||
bool compass;
|
||||
bool experimentalMode;
|
||||
bool leadInfo;
|
||||
bool mapOpen;
|
||||
bool onroadDistanceButton;
|
||||
bool roadNameUI;
|
||||
bool showAlwaysOnLateralStatusBar;
|
||||
@@ -297,11 +279,8 @@ class OnroadWindow : public QWidget {
|
||||
|
||||
public:
|
||||
OnroadWindow(QWidget* parent = 0);
|
||||
bool isMapVisible() const { return map && map->isVisible(); }
|
||||
void showMapPanel(bool show) { if (map) map->setVisible(show); }
|
||||
|
||||
signals:
|
||||
void mapPanelRequested();
|
||||
// signals:
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *event);
|
||||
@@ -322,6 +301,5 @@ private:
|
||||
|
||||
private slots:
|
||||
void offroadTransition(bool offroad);
|
||||
void primeChanged(bool prime);
|
||||
void updateState(const UIState &s);
|
||||
};
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
#include "selfdrive/ui/qt/widgets/prime.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QStackedWidget>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <QrCode.hpp>
|
||||
|
||||
#include "selfdrive/ui/qt/request_repeater.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
#include "selfdrive/ui/qt/widgets/wifi.h"
|
||||
|
||||
using qrcodegen::QrCode;
|
||||
|
||||
PairingQRWidget::PairingQRWidget(QWidget* parent) : QWidget(parent) {
|
||||
timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &PairingQRWidget::refresh);
|
||||
}
|
||||
|
||||
void PairingQRWidget::showEvent(QShowEvent *event) {
|
||||
refresh();
|
||||
timer->start(5 * 60 * 1000);
|
||||
device()->setOffroadBrightness(100);
|
||||
}
|
||||
|
||||
void PairingQRWidget::hideEvent(QHideEvent *event) {
|
||||
timer->stop();
|
||||
device()->setOffroadBrightness(BACKLIGHT_OFFROAD);
|
||||
}
|
||||
|
||||
void PairingQRWidget::refresh() {
|
||||
QString pairToken = CommaApi::create_jwt({{"pair", true}});
|
||||
QString qrString = "https://connect.comma.ai/?pair=" + pairToken;
|
||||
this->updateQrCode(qrString);
|
||||
update();
|
||||
}
|
||||
|
||||
void PairingQRWidget::updateQrCode(const QString &text) {
|
||||
QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW);
|
||||
qint32 sz = qr.getSize();
|
||||
QImage im(sz, sz, QImage::Format_RGB32);
|
||||
|
||||
QRgb black = qRgb(0, 0, 0);
|
||||
QRgb white = qRgb(255, 255, 255);
|
||||
for (int y = 0; y < sz; y++) {
|
||||
for (int x = 0; x < sz; x++) {
|
||||
im.setPixel(x, y, qr.getModule(x, y) ? black : white);
|
||||
}
|
||||
}
|
||||
|
||||
// Integer division to prevent anti-aliasing
|
||||
int final_sz = ((width() / sz) - 1) * sz;
|
||||
img = QPixmap::fromImage(im.scaled(final_sz, final_sz, Qt::KeepAspectRatio), Qt::MonoOnly);
|
||||
}
|
||||
|
||||
void PairingQRWidget::paintEvent(QPaintEvent *e) {
|
||||
QPainter p(this);
|
||||
p.fillRect(rect(), Qt::white);
|
||||
|
||||
QSize s = (size() - img.size()) / 2;
|
||||
p.drawPixmap(s.width(), s.height(), img);
|
||||
}
|
||||
|
||||
|
||||
PairingPopup::PairingPopup(QWidget *parent) : DialogBase(parent) {
|
||||
QHBoxLayout *hlayout = new QHBoxLayout(this);
|
||||
hlayout->setContentsMargins(0, 0, 0, 0);
|
||||
hlayout->setSpacing(0);
|
||||
|
||||
setStyleSheet("PairingPopup { background-color: #E0E0E0; }");
|
||||
|
||||
// text
|
||||
QVBoxLayout *vlayout = new QVBoxLayout();
|
||||
vlayout->setContentsMargins(85, 70, 50, 70);
|
||||
vlayout->setSpacing(50);
|
||||
hlayout->addLayout(vlayout, 1);
|
||||
{
|
||||
QPushButton *close = new QPushButton(QIcon(":/icons/close.svg"), "", this);
|
||||
close->setIconSize(QSize(80, 80));
|
||||
close->setStyleSheet("border: none;");
|
||||
vlayout->addWidget(close, 0, Qt::AlignLeft);
|
||||
QObject::connect(close, &QPushButton::clicked, this, &QDialog::reject);
|
||||
|
||||
vlayout->addSpacing(30);
|
||||
|
||||
QLabel *title = new QLabel(tr("Pair your device to your comma account"), this);
|
||||
title->setStyleSheet("font-size: 75px; color: black;");
|
||||
title->setWordWrap(true);
|
||||
vlayout->addWidget(title);
|
||||
|
||||
QLabel *instructions = new QLabel(QString(R"(
|
||||
<ol type='1' style='margin-left: 15px;'>
|
||||
<li style='margin-bottom: 50px;'>%1</li>
|
||||
<li style='margin-bottom: 50px;'>%2</li>
|
||||
<li style='margin-bottom: 50px;'>%3</li>
|
||||
</ol>
|
||||
)").arg(tr("Go to https://connect.comma.ai on your phone"))
|
||||
.arg(tr("Click \"add new device\" and scan the QR code on the right"))
|
||||
.arg(tr("Bookmark connect.comma.ai to your home screen to use it like an app")), this);
|
||||
|
||||
instructions->setStyleSheet("font-size: 47px; font-weight: bold; color: black;");
|
||||
instructions->setWordWrap(true);
|
||||
vlayout->addWidget(instructions);
|
||||
|
||||
vlayout->addStretch();
|
||||
}
|
||||
|
||||
// QR code
|
||||
PairingQRWidget *qr = new PairingQRWidget(this);
|
||||
hlayout->addWidget(qr, 1);
|
||||
}
|
||||
|
||||
|
||||
PrimeUserWidget::PrimeUserWidget(QWidget *parent) : QFrame(parent) {
|
||||
setObjectName("primeWidget");
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(56, 40, 56, 40);
|
||||
mainLayout->setSpacing(20);
|
||||
|
||||
QLabel *subscribed = new QLabel(tr("✓ SUBSCRIBED"));
|
||||
subscribed->setStyleSheet("font-size: 41px; font-weight: bold; color: #86FF4E;");
|
||||
mainLayout->addWidget(subscribed);
|
||||
|
||||
QLabel *commaPrime = new QLabel(tr("comma prime"));
|
||||
commaPrime->setStyleSheet("font-size: 75px; font-weight: bold;");
|
||||
mainLayout->addWidget(commaPrime);
|
||||
}
|
||||
|
||||
|
||||
PrimeAdWidget::PrimeAdWidget(QWidget* parent) : QFrame(parent) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(80, 90, 80, 60);
|
||||
main_layout->setSpacing(0);
|
||||
|
||||
QLabel *upgrade = new QLabel(tr("Upgrade Now"));
|
||||
upgrade->setStyleSheet("font-size: 75px; font-weight: bold;");
|
||||
main_layout->addWidget(upgrade, 0, Qt::AlignTop);
|
||||
main_layout->addSpacing(50);
|
||||
|
||||
QLabel *description = new QLabel(tr("Become a comma prime member at connect.comma.ai"));
|
||||
description->setStyleSheet("font-size: 56px; font-weight: light; color: white;");
|
||||
description->setWordWrap(true);
|
||||
main_layout->addWidget(description, 0, Qt::AlignTop);
|
||||
|
||||
main_layout->addStretch();
|
||||
|
||||
QLabel *features = new QLabel(tr("PRIME FEATURES:"));
|
||||
features->setStyleSheet("font-size: 41px; font-weight: bold; color: #E5E5E5;");
|
||||
main_layout->addWidget(features, 0, Qt::AlignBottom);
|
||||
main_layout->addSpacing(30);
|
||||
|
||||
QVector<QString> bullets = {tr("Remote access"), tr("24/7 LTE connectivity"), tr("1 year of drive storage"), tr("Turn-by-turn navigation")};
|
||||
for (auto &b : bullets) {
|
||||
const QString check = "<b><font color='#465BEA'>✓</font></b> ";
|
||||
QLabel *l = new QLabel(check + b);
|
||||
l->setAlignment(Qt::AlignLeft);
|
||||
l->setStyleSheet("font-size: 50px; margin-bottom: 15px;");
|
||||
main_layout->addWidget(l, 0, Qt::AlignBottom);
|
||||
}
|
||||
|
||||
setStyleSheet(R"(
|
||||
PrimeAdWidget {
|
||||
border-radius: 10px;
|
||||
background-color: #333333;
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
|
||||
SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
|
||||
mainLayout = new QStackedWidget;
|
||||
|
||||
// Unpaired, registration prompt layout
|
||||
|
||||
QFrame* finishRegistration = new QFrame;
|
||||
finishRegistration->setObjectName("primeWidget");
|
||||
QVBoxLayout* finishRegistationLayout = new QVBoxLayout(finishRegistration);
|
||||
finishRegistationLayout->setSpacing(38);
|
||||
finishRegistationLayout->setContentsMargins(64, 48, 64, 48);
|
||||
|
||||
QLabel* registrationTitle = new QLabel(tr("Finish Setup"));
|
||||
registrationTitle->setStyleSheet("font-size: 75px; font-weight: bold;");
|
||||
finishRegistationLayout->addWidget(registrationTitle);
|
||||
|
||||
QLabel* registrationDescription = new QLabel(tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."));
|
||||
registrationDescription->setWordWrap(true);
|
||||
registrationDescription->setStyleSheet("font-size: 50px; font-weight: light;");
|
||||
finishRegistationLayout->addWidget(registrationDescription);
|
||||
|
||||
finishRegistationLayout->addStretch();
|
||||
|
||||
QPushButton* pair = new QPushButton(tr("Pair device"));
|
||||
pair->setStyleSheet(R"(
|
||||
QPushButton {
|
||||
font-size: 55px;
|
||||
font-weight: 500;
|
||||
border-radius: 10px;
|
||||
background-color: #465BEA;
|
||||
padding: 64px;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #3049F4;
|
||||
}
|
||||
)");
|
||||
finishRegistationLayout->addWidget(pair);
|
||||
|
||||
popup = new PairingPopup(this);
|
||||
QObject::connect(pair, &QPushButton::clicked, popup, &PairingPopup::exec);
|
||||
|
||||
mainLayout->addWidget(finishRegistration);
|
||||
|
||||
// build stacked layout
|
||||
QVBoxLayout *outer_layout = new QVBoxLayout(this);
|
||||
outer_layout->setContentsMargins(0, 0, 0, 0);
|
||||
outer_layout->addWidget(mainLayout);
|
||||
|
||||
QWidget *content = new QWidget;
|
||||
QVBoxLayout *content_layout = new QVBoxLayout(content);
|
||||
content_layout->setContentsMargins(0, 0, 0, 0);
|
||||
content_layout->setSpacing(30);
|
||||
|
||||
primeUser = new PrimeUserWidget;
|
||||
content_layout->addWidget(primeUser);
|
||||
|
||||
WiFiPromptWidget *wifi_prompt = new WiFiPromptWidget;
|
||||
QObject::connect(wifi_prompt, &WiFiPromptWidget::openSettings, this, &SetupWidget::openSettings);
|
||||
content_layout->addWidget(wifi_prompt);
|
||||
content_layout->addStretch();
|
||||
|
||||
mainLayout->addWidget(content);
|
||||
|
||||
primeUser->setVisible(uiState()->primeType());
|
||||
mainLayout->setCurrentIndex(1);
|
||||
|
||||
setStyleSheet(R"(
|
||||
#primeWidget {
|
||||
border-radius: 10px;
|
||||
background-color: #333333;
|
||||
}
|
||||
)");
|
||||
|
||||
// Retain size while hidden
|
||||
QSizePolicy sp_retain = sizePolicy();
|
||||
sp_retain.setRetainSizeWhenHidden(true);
|
||||
setSizePolicy(sp_retain);
|
||||
|
||||
// set up API requests
|
||||
if (auto dongleId = getDongleId()) {
|
||||
QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/";
|
||||
RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_Device", 5);
|
||||
|
||||
QObject::connect(repeater, &RequestRepeater::requestDone, this, &SetupWidget::replyFinished);
|
||||
}
|
||||
}
|
||||
|
||||
void SetupWidget::replyFinished(const QString &response, bool success) {
|
||||
if (!success) return;
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
|
||||
if (doc.isNull()) {
|
||||
qDebug() << "JSON Parse failed on getting pairing and prime status";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json = doc.object();
|
||||
PrimeType prime_type = static_cast<PrimeType>(json["prime_type"].toInt());
|
||||
uiState()->setPrimeType(prime_type);
|
||||
|
||||
if (!json["is_paired"].toBool()) {
|
||||
mainLayout->setCurrentIndex(0);
|
||||
} else {
|
||||
popup->reject();
|
||||
|
||||
primeUser->setVisible(prime_type);
|
||||
mainLayout->setCurrentIndex(1);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QStackedWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/ui/qt/widgets/input.h"
|
||||
|
||||
// pairing QR code
|
||||
class PairingQRWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PairingQRWidget(QWidget* parent = 0);
|
||||
void paintEvent(QPaintEvent*) override;
|
||||
|
||||
private:
|
||||
QPixmap img;
|
||||
QTimer *timer;
|
||||
void updateQrCode(const QString &text);
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void refresh();
|
||||
};
|
||||
|
||||
|
||||
// pairing popup widget
|
||||
class PairingPopup : public DialogBase {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PairingPopup(QWidget* parent);
|
||||
};
|
||||
|
||||
|
||||
// widget for paired users with prime
|
||||
class PrimeUserWidget : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PrimeUserWidget(QWidget* parent = 0);
|
||||
};
|
||||
|
||||
|
||||
// widget for paired users without prime
|
||||
class PrimeAdWidget : public QFrame {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PrimeAdWidget(QWidget* parent = 0);
|
||||
};
|
||||
|
||||
|
||||
// container widget
|
||||
class SetupWidget : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SetupWidget(QWidget* parent = 0);
|
||||
|
||||
signals:
|
||||
void openSettings(int index = 0, const QString ¶m = "");
|
||||
|
||||
private:
|
||||
PairingPopup *popup;
|
||||
QStackedWidget *mainLayout;
|
||||
PrimeUserWidget *primeUser;
|
||||
|
||||
private slots:
|
||||
void replyFinished(const QString &response, bool success);
|
||||
};
|
||||
@@ -74,13 +74,8 @@ void MainWindow::closeSettings() {
|
||||
main_layout->setCurrentWidget(homeWindow);
|
||||
|
||||
if (uiState()->scene.started) {
|
||||
// Map is always shown when using navigate on openpilot
|
||||
if (uiState()->scene.navigate_on_openpilot) {
|
||||
homeWindow->showMapPanel(true);
|
||||
} else {
|
||||
homeWindow->showSidebar(params.getBool("Sidebar"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
|
||||
|
||||
@@ -96,15 +96,9 @@ def setup_onroad(click, pm: PubMaster):
|
||||
server.send(cam_meta.stream, IMG_BYTES, cs.frameId, cs.timestampSof, cs.timestampEof)
|
||||
|
||||
@mock_messages(['liveLocationKalman'])
|
||||
def setup_onroad_map(click, pm: PubMaster):
|
||||
setup_onroad(click, pm)
|
||||
|
||||
click(500, 500)
|
||||
|
||||
time.sleep(UI_DELAY) # give time for the map to render
|
||||
|
||||
def setup_onroad_sidebar(click, pm: PubMaster):
|
||||
setup_onroad_map(click, pm)
|
||||
click(500, 500)
|
||||
|
||||
CASES = {
|
||||
@@ -112,7 +106,6 @@ CASES = {
|
||||
"settings_device": setup_settings_device,
|
||||
"settings_network": setup_settings_network,
|
||||
"onroad": setup_onroad,
|
||||
"onroad_map": setup_onroad_map,
|
||||
"onroad_sidebar": setup_onroad_sidebar
|
||||
}
|
||||
|
||||
|
||||
@@ -305,7 +305,6 @@ static void update_state(UIState *s) {
|
||||
void ui_update_params(UIState *s) {
|
||||
auto params = Params();
|
||||
s->scene.is_metric = params.getBool("IsMetric");
|
||||
s->scene.map_on_left = params.getBool("NavSettingLeftSide");
|
||||
}
|
||||
|
||||
void ui_update_frogpilot_params(UIState *s) {
|
||||
@@ -373,13 +372,10 @@ void ui_update_frogpilot_params(UIState *s) {
|
||||
scene.reverse_cruise_ui = scene.reverse_cruise && params.getBool("ReverseCruiseUI");
|
||||
|
||||
bool quality_of_life_visuals = params.getBool("QOLVisuals");
|
||||
scene.big_map = quality_of_life_visuals && params.getBool("BigMap");
|
||||
scene.full_map = scene.big_map && params.getBool("FullMap");
|
||||
scene.camera_view = quality_of_life_visuals ? params.getInt("CameraView") : 0;
|
||||
scene.driver_camera = quality_of_life_visuals && params.getBool("DriverCamera");
|
||||
scene.hide_speed = quality_of_life_visuals && params.getBool("HideSpeed");
|
||||
scene.hide_speed_ui = scene.hide_speed && params.getBool("HideSpeedUI");
|
||||
scene.map_style = quality_of_life_visuals ? params.getInt("MapStyle") : 0;
|
||||
scene.numerical_temp = quality_of_life_visuals && params.getBool("NumericalTemp");
|
||||
scene.fahrenheit = scene.numerical_temp && params.getBool("Fahrenheit");
|
||||
scene.wheel_speed = quality_of_life_visuals && params.getBool("WheelSpeed");
|
||||
@@ -387,7 +383,6 @@ void ui_update_frogpilot_params(UIState *s) {
|
||||
bool screen_management = params.getBool("ScreenManagement");
|
||||
bool hide_ui_elements = screen_management && params.getBool("HideUIElements");
|
||||
scene.hide_alerts = hide_ui_elements && params.getBool("HideAlerts");
|
||||
scene.hide_map_icon = hide_ui_elements && params.getBool("HideMapIcon");
|
||||
scene.hide_max_speed = hide_ui_elements && params.getBool("HideMaxSpeed");
|
||||
scene.screen_brightness = screen_management ? params.getInt("ScreenBrightness") : 101;
|
||||
scene.screen_brightness_onroad = screen_management ? params.getInt("ScreenBrightnessOnroad") : 101;
|
||||
@@ -437,16 +432,12 @@ UIState::UIState(QObject *parent) : QObject(parent) {
|
||||
sm = std::make_unique<SubMaster, const std::initializer_list<const char *>>({
|
||||
"modelV2", "controlsState", "liveCalibration", "radarState", "deviceState",
|
||||
"pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2",
|
||||
"wideRoadCameraState", "managerState", "navInstruction", "navRoute", "uiPlan", "liveTorqueParameters",
|
||||
"wideRoadCameraState", "managerState", "uiPlan", "liveTorqueParameters",
|
||||
"frogpilotCarControl", "frogpilotDeviceState", "frogpilotPlan",
|
||||
});
|
||||
|
||||
Params params;
|
||||
language = QString::fromStdString(params.get("LanguageSetting"));
|
||||
auto prime_value = params.get("PrimeType");
|
||||
if (!prime_value.empty()) {
|
||||
prime_type = static_cast<PrimeType>(std::atoi(prime_value.c_str()));
|
||||
}
|
||||
|
||||
// update timer
|
||||
timer = new QTimer(this);
|
||||
@@ -480,21 +471,6 @@ void UIState::update() {
|
||||
scene.driver_camera_timer = (scene.driver_camera && scene.reverse) ? scene.driver_camera_timer + 1 : 0;
|
||||
}
|
||||
|
||||
void UIState::setPrimeType(PrimeType type) {
|
||||
if (type != prime_type) {
|
||||
bool prev_prime = hasPrime();
|
||||
|
||||
prime_type = type;
|
||||
Params().put("PrimeType", std::to_string(prime_type));
|
||||
emit primeTypeChanged(prime_type);
|
||||
|
||||
bool prime = hasPrime();
|
||||
if (prev_prime != prime) {
|
||||
emit primeChanged(prime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) {
|
||||
setAwake(true);
|
||||
resetInteractiveTimeout();
|
||||
|
||||
@@ -155,16 +155,6 @@ struct Alert {
|
||||
}
|
||||
};
|
||||
|
||||
enum PrimeType {
|
||||
UNKNOWN = -1,
|
||||
NONE = 0,
|
||||
MAGENTA = 1,
|
||||
LITE = 2,
|
||||
BLUE = 3,
|
||||
MAGENTA_NEW = 4,
|
||||
PURPLE = 5,
|
||||
};
|
||||
|
||||
|
||||
typedef struct UIScene {
|
||||
bool calibration_valid = false;
|
||||
@@ -191,7 +181,6 @@ typedef struct UIScene {
|
||||
float driver_pose_coss[3];
|
||||
vec3 face_kpts_draw[std::size(default_face_kpts_3d)];
|
||||
|
||||
bool navigate_on_openpilot = false;
|
||||
cereal::LongitudinalPersonality personality;
|
||||
|
||||
float light_sensor;
|
||||
@@ -204,7 +193,6 @@ typedef struct UIScene {
|
||||
bool adjacent_path;
|
||||
bool adjacent_path_metrics;
|
||||
bool always_on_lateral_active;
|
||||
bool big_map;
|
||||
bool blind_spot_left;
|
||||
bool blind_spot_path;
|
||||
bool blind_spot_right;
|
||||
@@ -219,7 +207,6 @@ typedef struct UIScene {
|
||||
bool experimental_mode_via_screen;
|
||||
bool fahrenheit;
|
||||
bool fps_counter;
|
||||
bool full_map;
|
||||
bool has_auto_tune;
|
||||
bool hide_alerts;
|
||||
bool hide_lead_marker;
|
||||
@@ -230,7 +217,6 @@ typedef struct UIScene {
|
||||
bool holiday_themes;
|
||||
bool lead_info;
|
||||
bool live_valid;
|
||||
bool map_open;
|
||||
bool model_ui;
|
||||
bool numerical_temp;
|
||||
bool online;
|
||||
@@ -328,10 +314,6 @@ public:
|
||||
return scene.started && (*sm)["controlsState"].getControlsState().getEnabled();
|
||||
}
|
||||
|
||||
void setPrimeType(PrimeType type);
|
||||
inline PrimeType primeType() const { return prime_type; }
|
||||
inline bool hasPrime() const { return prime_type != PrimeType::UNKNOWN && prime_type != PrimeType::NONE; }
|
||||
|
||||
int fb_w = 0, fb_h = 0;
|
||||
|
||||
std::unique_ptr<SubMaster> sm;
|
||||
@@ -349,8 +331,6 @@ public:
|
||||
signals:
|
||||
void uiUpdate(const UIState &s);
|
||||
void offroadTransition(bool offroad);
|
||||
void primeChanged(bool prime);
|
||||
void primeTypeChanged(PrimeType prime_type);
|
||||
|
||||
private slots:
|
||||
void update();
|
||||
@@ -358,7 +338,6 @@ private slots:
|
||||
private:
|
||||
QTimer *timer;
|
||||
bool started_prev = false;
|
||||
PrimeType prime_type = PrimeType::UNKNOWN;
|
||||
|
||||
// FrogPilot variables
|
||||
Params paramsMemory{"/dev/shm/params"};
|
||||
|
||||
@@ -19,7 +19,6 @@ int main(int argc, char *argv[]) {
|
||||
{
|
||||
QHBoxLayout *hlayout = new QHBoxLayout();
|
||||
layout->addLayout(hlayout);
|
||||
hlayout->addWidget(new CameraWidget("navd", VISION_STREAM_MAP, false));
|
||||
hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_ROAD, false));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user