FrogPilot community - Open Street Map integration

Credit goes to Pfeiferj!

https: //github.com/pfeiferj
Co-Authored-By: Jacob Pfeifer <jacob@pfeifer.dev>
This commit is contained in:
FrogAi 2024-05-24 06:57:37 -07:00
parent 233e777bc5
commit 77ed408861
8 changed files with 876 additions and 4 deletions

View File

@ -570,3 +570,4 @@ selfdrive/frogpilot/controls/frogpilot_planner.py
selfdrive/frogpilot/controls/lib/frogpilot_functions.py
selfdrive/frogpilot/controls/lib/frogpilot_variables.py
selfdrive/frogpilot/fleet_manager/fleet_manager.py
selfdrive/frogpilot/navigation/mapd.py

View File

@ -39,14 +39,36 @@ def automatic_update_check(params):
elif update_state_idle:
os.system("pkill -SIGUSR1 -f selfdrive.updated.updated")
def time_checks(automatic_updates, deviceState, now, params, params_memory):
def time_checks(automatic_updates, deviceState, maps_downloaded, now, params, params_memory):
screen_off = deviceState.screenBrightnessPercent == 0
wifi_connection = deviceState.networkType == WIFI
if screen_off and wifi_connection:
if screen_off and wifi_connection or not maps_downloaded:
if automatic_updates:
automatic_update_check(params)
update_maps(maps_downloaded, now, params, params_memory)
def update_maps(maps_downloaded, now, params, params_memory):
day = now.day
is_first = day == 1
is_Sunday = now.weekday() == 6
maps_selected = params.get("MapsSelected")
schedule = params.get_int("PreferredSchedule")
if maps_downloaded and (maps_selected is None or schedule == 0 or (schedule == 1 and not is_Sunday) or (schedule == 2 and not is_first)):
return
suffix = "th" if 4 <= day <= 20 or 24 <= day <= 30 else ["st", "nd", "rd"][day % 10 - 1]
todays_date = now.strftime(f"%B {day}{suffix}, %Y")
if params.get("LastMapsUpdate") == todays_date:
return
if params.get("OSMDownloadProgress") is None:
params_memory.put("OSMDownloadLocations", params.get("MapsSelected"))
params.put("LastMapsUpdate", todays_date)
def frogpilot_thread(frogpilot_toggles):
config_realtime_process(5, Priority.CTRL_LOW)
@ -59,6 +81,7 @@ def frogpilot_thread(frogpilot_toggles):
current_day = None
first_run = True
maps_downloaded = os.path.exists('/data/media/0/osm/offline') or params.get("MapsSelected") is None
time_validated = system_time_valid()
pm = messaging.PubMaster(['frogpilotPlan'])
@ -91,8 +114,9 @@ def frogpilot_thread(frogpilot_toggles):
continue
if now.second == 0 or first_run or params_memory.get_bool("ManualUpdateInitiated"):
if not started and github_pinged():
time_checks(frogpilot_toggles.automatic_updates, deviceState, now, params, params_memory)
if (not started or not maps_downloaded) and github_pinged():
time_checks(frogpilot_toggles.automatic_updates, deviceState, maps_downloaded, now, params, params_memory)
maps_downloaded = os.path.exists('/data/media/0/osm/offline') or params.get("OSMDownloadProgress") is not None or params.get("MapsSelected") is None
if now.day != current_day:
params.remove("FingerprintLogged")

View File

@ -0,0 +1,102 @@
# 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'
GITHUB_VERSION_URL = f"https://github.com/FrogAi/FrogPilot-Resources/raw/Versions/mapd_version_{VERSION}.txt"
GITLAB_VERSION_URL = f"https://gitlab.com/FrogAi/FrogPilot-Resources/-/raw/Versions/mapd_version_{VERSION}.txt"
MAPD_PATH = '/data/media/0/osm/mapd'
VERSION_PATH = '/data/media/0/osm/mapd_version'
def ping_url(url, 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():
urls = [GITHUB_VERSION_URL, GITLAB_VERSION_URL]
for url in urls:
try:
with urllib.request.urlopen(url) as response:
return response.read().decode('utf-8').strip()
except Exception as e:
print(f"Failed to get latest version from {url}. Error: {e}")
print("Failed to get latest version from both sources.")
return None
def download(current_version):
urls = [
f"https://github.com/pfeiferj/openpilot-mapd/releases/download/{current_version}/mapd",
f"https://gitlab.com/FrogAi/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)
for url in urls:
try:
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)
os.chmod(MAPD_PATH, current_permissions | stat.S_IEXEC)
with open(VERSION_PATH, 'w') as output:
output.write(current_version)
os.fsync(output)
print(f"Successfully downloaded mapd from {url}")
return
except Exception as e:
print(f"Failed to download from {url}. Error: {e}")
print(f"Failed to download mapd for version {current_version} from both sources.")
def mapd_thread(sm=None, pm=None):
rk = Ratekeeper(0.05, print_delay_threshold=None)
while True:
try:
if ping_url("https://github.com"):
current_version = get_latest_version()
if current_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()

View File

@ -0,0 +1,311 @@
#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";
}
}

View File

@ -1,5 +1,6 @@
#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) {
@ -19,6 +20,375 @@ FrogPilotNavigationPanel::FrogPilotNavigationPanel(QWidget *parent) : QFrame(par
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);
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);
}
void FrogPilotNavigationPanel::hideEvent(QHideEvent *event) {
QWidget::hideEvent(event);
mainLayout->setCurrentWidget(navigationWidget);
}
void FrogPilotNavigationPanel::showEvent(QShowEvent *event) {
downloadActive = !paramsMemory.get("OSMDownloadLocations").empty();
lastMapsDownload->setText(QString::fromStdString(params.get("LastMapsUpdate")));
qint64 fileSize = calculateDirectorySize(offlineFolderPath);
offlineMapsSize->setText(formatSize(fileSize));
removeOfflineMapsButton->setVisible(QDir(offlineFolderPath).exists() && !downloadActive);
}
void FrogPilotNavigationPanel::updateState() {
if (!isVisible() && downloadActive) updateVisibility(downloadActive);
if (downloadActive) updateStatuses();
}
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::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::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::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) {

View File

@ -1,5 +1,6 @@
#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"
@ -44,6 +45,33 @@ 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
@ -51,9 +79,38 @@ public:
explicit FrogPilotNavigationPanel(QWidget *parent = 0);
private:
void cancelDownload(QWidget *parent);
void downloadMaps();
void hideEvent(QHideEvent *event);
void removeMaps(QWidget *parent);
void setMaps();
void showEvent(QShowEvent *event);
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;
QString elapsedTime;
QString offlineFolderPath = "/data/media/0/osm/offline";
std::string osmDownloadProgress;
std::string previousOSMDownloadProgress;
Params params;
Params paramsMemory{"/dev/shm/params"};
UIScene &scene;

View File

@ -192,6 +192,12 @@ def main():
learner.handle_log(t, which, sm[which])
if sm.updated['liveLocationKalman']:
location = sm['liveLocationKalman']
if (location.status == log.LiveLocationKalman.Status.valid) and location.positionGeodetic.valid and location.gpsOK:
bearing = math.degrees(location.calibratedOrientationNED.value[2])
lat = location.positionGeodetic.value[0]
lon = location.positionGeodetic.value[1]
params_memory.put("LastGPSPosition", json.dumps({"latitude": lat, "longitude": lon, "bearing": bearing}))
x = learner.kf.x
P = np.sqrt(learner.kf.P.diagonal())
if not all(map(math.isfinite, x)):

View File

@ -91,6 +91,7 @@ procs = [
# FrogPilot processes
PythonProcess("fleet_manager", "selfdrive.frogpilot.fleetmanager.fleet_manager", always_run),
PythonProcess("frogpilot_process", "selfdrive.frogpilot.frogpilot_process", always_run),
PythonProcess("mapd", "selfdrive.frogpilot.navigation.mapd", always_run),
]
managed_processes = {p.name: p for p in procs}