diff --git a/apk/ai.comma.plus.black.apk b/apk/ai.comma.plus.black.apk deleted file mode 100644 index e0d0df3d96..0000000000 --- a/apk/ai.comma.plus.black.apk +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:309b46b7c38f10da52b18b0340eb3c57b633558a9a27c3ca4116474969ebb456 -size 84675 diff --git a/apk/ai.comma.plus.frame.apk b/apk/ai.comma.plus.frame.apk deleted file mode 100644 index 362a7cc747..0000000000 --- a/apk/ai.comma.plus.frame.apk +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d563bc9d43408f78d859538b50999cd7703487bdbe1fba1b11a3810205cd26c4 -size 2856327 diff --git a/apk/ai.comma.plus.offroad.apk b/apk/ai.comma.plus.offroad.apk index 172c99394c..3338981432 100644 --- a/apk/ai.comma.plus.offroad.apk +++ b/apk/ai.comma.plus.offroad.apk @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf9cdba9b1f10d7b76573d1f78d208bc58a7273ae5d5e6f171be6c00c67863a6 -size 13678787 +oid sha256:deb70c5a5b284e73d4960b60ac7a6dcc1263597762554749adf675c23efc383e +size 13688133 diff --git a/common/apk.py b/common/apk.py index d77b83f141..5716ed452a 100644 --- a/common/apk.py +++ b/common/apk.py @@ -6,7 +6,7 @@ import shutil from common.basedir import BASEDIR from selfdrive.swaglog import cloudlog -android_packages = ("ai.comma.plus.offroad", "ai.comma.plus.frame") +android_packages = ("ai.comma.plus.offroad",) def get_installed_apks(): dat = subprocess.check_output(["pm", "list", "packages", "-f"], encoding='utf8').strip().split("\n") @@ -26,9 +26,9 @@ def install_apk(path): os.remove(install_path) return ret == 0 -def start_frame(): +def start_offroad(): set_package_permissions() - system("am start -n ai.comma.plus.frame/.MainActivity") + system("am start -n ai.comma.plus.offroad/.MainActivity") def set_package_permissions(): pm_grant("ai.comma.plus.offroad", "android.permission.ACCESS_FINE_LOCATION") @@ -95,4 +95,3 @@ def pm_apply_packages(cmd): if __name__ == "__main__": update_apks() - diff --git a/selfdrive/assets/images/battery.png b/selfdrive/assets/images/battery.png new file mode 100644 index 0000000000..1709278945 --- /dev/null +++ b/selfdrive/assets/images/battery.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2996f1558686dec3ea09b73b9a4bc5819c5f10a17611ccd267a61eab03e3d55b +size 1704 diff --git a/selfdrive/assets/images/battery_charging.png b/selfdrive/assets/images/battery_charging.png new file mode 100644 index 0000000000..155caade20 --- /dev/null +++ b/selfdrive/assets/images/battery_charging.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1825c86dab73661002f8895c4bddf43c45aa50e0c5899ddc1b8cfde52d6942ec +size 2131 diff --git a/selfdrive/assets/images/button_home.png b/selfdrive/assets/images/button_home.png new file mode 100644 index 0000000000..dd3f97f44b --- /dev/null +++ b/selfdrive/assets/images/button_home.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fca2a1f675cad4f454f3ba56d6e7f090a352321b61f241fc2ffcbede7a7593a +size 1501 diff --git a/selfdrive/assets/images/button_settings.png b/selfdrive/assets/images/button_settings.png new file mode 100644 index 0000000000..4bbfe581e6 --- /dev/null +++ b/selfdrive/assets/images/button_settings.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ec603a5e8f08eb46512c8f0fbb684ff93533964b13a8a8ce45aec95b85e3800 +size 957 diff --git a/selfdrive/assets/images/network_0.png b/selfdrive/assets/images/network_0.png new file mode 100644 index 0000000000..656789f3e1 --- /dev/null +++ b/selfdrive/assets/images/network_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bafba8ccc7166a430c4147159da31c989b4c1343585df90a6d60b6ca53fd3da +size 416 diff --git a/selfdrive/assets/images/network_1.png b/selfdrive/assets/images/network_1.png new file mode 100644 index 0000000000..62506a532e --- /dev/null +++ b/selfdrive/assets/images/network_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a202db2a98d2564926d45fa1b890a62ed902a4246359ff4a96df1336d0d7569e +size 462 diff --git a/selfdrive/assets/images/network_2.png b/selfdrive/assets/images/network_2.png new file mode 100644 index 0000000000..9fd19216b2 --- /dev/null +++ b/selfdrive/assets/images/network_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b2f5efdcb4c4d0fa3620e12c4aec2a4bf250ce72d2cf93ee34e754c7e055c71 +size 593 diff --git a/selfdrive/assets/images/network_3.png b/selfdrive/assets/images/network_3.png new file mode 100644 index 0000000000..de54d353cf --- /dev/null +++ b/selfdrive/assets/images/network_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74b690ab1f789f7a3ad0182e79f47c40f3145e586efb82582842dc3e4d375274 +size 524 diff --git a/selfdrive/assets/images/network_4.png b/selfdrive/assets/images/network_4.png new file mode 100644 index 0000000000..d4a28b8720 --- /dev/null +++ b/selfdrive/assets/images/network_4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a0109067d5ba99087d0678b1b2d5e9b90ae63b2750b06c8c497a073d111ca15 +size 541 diff --git a/selfdrive/assets/images/network_5.png b/selfdrive/assets/images/network_5.png new file mode 100644 index 0000000000..fc2b67475e --- /dev/null +++ b/selfdrive/assets/images/network_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:827996c0c1f975dde1818ad66155c7564aa37a6019b841d3070b038c3e6d2860 +size 503 diff --git a/selfdrive/manager.py b/selfdrive/manager.py index 73347af872..d39c8701b9 100755 --- a/selfdrive/manager.py +++ b/selfdrive/manager.py @@ -129,7 +129,7 @@ from selfdrive.version import version, dirty from selfdrive.loggerd.config import ROOT from selfdrive.launcher import launcher from common import android -from common.apk import update_apks, pm_apply_packages, start_frame +from common.apk import update_apks, pm_apply_packages, start_offroad from common.manager_helpers import print_cpu_usage ThermalStatus = cereal.log.ThermalData.ThermalStatus @@ -406,10 +406,10 @@ def manager_thread(): for p in persistent_processes: start_managed_process(p) - # start frame + # start offroad if ANDROID: pm_apply_packages('enable') - start_frame() + start_offroad() if os.getenv("NOBOARD") is None: start_managed_process("pandad") diff --git a/selfdrive/test/test_openpilot.py b/selfdrive/test/test_openpilot.py index e5ea327369..3e9a317582 100644 --- a/selfdrive/test/test_openpilot.py +++ b/selfdrive/test/test_openpilot.py @@ -2,7 +2,7 @@ import os os.environ['FAKEUPLOAD'] = "1" -from common.apk import update_apks, start_frame, pm_apply_packages, android_packages +from common.apk import update_apks, start_offroad, pm_apply_packages, android_packages from common.params import Params from common.testing import phone_only from selfdrive.manager import manager_init, manager_prepare @@ -57,7 +57,7 @@ def with_apks(): update_apks() pm_apply_packages('enable') - start_frame() + start_offroad() func() diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 44ce4e332d..e0c353b43e 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,6 +1,6 @@ Import('env', 'arch', 'common', 'messaging', 'gpucommon', 'visionipc', 'cereal') -src = ['ui.cc', 'paint.cc', '#phonelibs/nanovg/nanovg.c'] +src = ['ui.cc', 'paint.cc', 'sidebar.cc', '#phonelibs/nanovg/nanovg.c'] libs = [common, 'zmq', 'czmq', 'capnp', 'capnp_c', 'm', cereal, messaging, gpucommon, visionipc] if arch == "aarch64": diff --git a/selfdrive/ui/paint.cc b/selfdrive/ui/paint.cc index 7e8fda20ac..ce854ecbe1 100644 --- a/selfdrive/ui/paint.cc +++ b/selfdrive/ui/paint.cc @@ -877,6 +877,7 @@ static void ui_draw_blank(UIState *s) { } void ui_draw(UIState *s) { + ui_draw_sidebar(s); if (s->vision_connected && s->active_app == cereal_UiLayoutState_App_home && s->status != STATUS_STOPPED) { ui_draw_vision(s); } else { @@ -985,6 +986,25 @@ void ui_nvg_init(UIState *s) { assert(s->img_map >= 0); s->img_map = nvgCreateImage(s->vg, "../assets/img_map.png", 1); + assert(s->img_button_settings >= 0); + s->img_button_settings = nvgCreateImage(s->vg, "../assets/images/button_settings.png", 1); + + assert(s->img_button_home >= 0); + s->img_button_home = nvgCreateImage(s->vg, "../assets/images/button_home.png", 1); + + assert(s->img_battery >= 0); + s->img_battery = nvgCreateImage(s->vg, "../assets/images/battery.png", 1); + + assert(s->img_battery_charging >= 0); + s->img_battery_charging = nvgCreateImage(s->vg, "../assets/images/battery_charging.png", 1); + + for(int i=0;i<=5;++i) { + assert(s->img_network[i] >= 0); + char network_asset[32]; + snprintf(network_asset, sizeof(network_asset), "../assets/images/network_%d.png", i); + s->img_network[i] = nvgCreateImage(s->vg, network_asset, 1); + } + // init gl s->frame_program = load_program(frame_vertex_shader, frame_fragment_shader); assert(s->frame_program); diff --git a/selfdrive/ui/sidebar.cc b/selfdrive/ui/sidebar.cc new file mode 100644 index 0000000000..717449dee9 --- /dev/null +++ b/selfdrive/ui/sidebar.cc @@ -0,0 +1,234 @@ +#include +#include +#include +#include "ui.hpp" + +static void ui_draw_sidebar_background(UIState *s, bool hasSidebar) { + int sbr_x = hasSidebar ? 0 : -(sbr_w) + bdr_s * 2; + + nvgBeginPath(s->vg); + nvgRect(s->vg, sbr_x, 0, sbr_w, vwp_h); + nvgFillColor(s->vg, COLOR_BLACK_ALPHA); + nvgFill(s->vg); +} + +static void ui_draw_sidebar_settings_button(UIState *s, bool hasSidebar) { + bool settingsActive = s->active_app == cereal_UiLayoutState_App_settings; + const int settings_btn_xr = hasSidebar ? settings_btn_x : -(sbr_w); + + nvgBeginPath(s->vg); + NVGpaint imgPaint = nvgImagePattern(s->vg, settings_btn_xr, settings_btn_y, + settings_btn_w, settings_btn_h, 0, s->img_button_settings, settingsActive ? 1.0f : 0.65f); + nvgRect(s->vg, settings_btn_xr, settings_btn_y, settings_btn_w, settings_btn_h); + nvgFillPaint(s->vg, imgPaint); + nvgFill(s->vg); +} + +static void ui_draw_sidebar_home_button(UIState *s, bool hasSidebar) { + bool homeActive = s->active_app == cereal_UiLayoutState_App_home; + const int home_btn_xr = hasSidebar ? home_btn_x : -(sbr_w); + + nvgBeginPath(s->vg); + NVGpaint imgPaint = nvgImagePattern(s->vg, home_btn_xr, home_btn_y, + home_btn_w, home_btn_h, 0, s->img_button_home, homeActive ? 1.0f : 0.65f); + nvgRect(s->vg, home_btn_xr, home_btn_y, home_btn_w, home_btn_h); + nvgFillPaint(s->vg, imgPaint); + nvgFill(s->vg); +} + +static void ui_draw_sidebar_network_strength(UIState *s, bool hasSidebar) { + const int network_img_h = 27; + const int network_img_w = 176; + const int network_img_x = hasSidebar ? 58 : -(sbr_w); + const int network_img_y = 196; + const int network_img = s->scene.networkType == cereal_ThermalData_NetworkType_none ? + s->img_network[0] : s->img_network[s->scene.networkStrength + 1]; + + nvgBeginPath(s->vg); + NVGpaint imgPaint = nvgImagePattern(s->vg, network_img_x, network_img_y, + network_img_w, network_img_h, 0, network_img, 1.0f); + nvgRect(s->vg, network_img_x, network_img_y, network_img_w, network_img_h); + nvgFillPaint(s->vg, imgPaint); + nvgFill(s->vg); +} + +static void ui_draw_sidebar_battery_icon(UIState *s, bool hasSidebar) { + const int battery_img_h = 36; + const int battery_img_w = 76; + const int battery_img_x = hasSidebar ? 160 : -(sbr_w); + const int battery_img_y = 255; + + int battery_img = strcmp(s->scene.batteryStatus, "Charging") == 0 ? + s->img_battery_charging : s->img_battery; + + nvgBeginPath(s->vg); + nvgRect(s->vg, battery_img_x + 6, battery_img_y + 5, + ((battery_img_w - 19) * (s->scene.batteryPercent * 0.01)), battery_img_h - 11); + nvgFillColor(s->vg, COLOR_WHITE); + nvgFill(s->vg); + + nvgBeginPath(s->vg); + NVGpaint imgPaint = nvgImagePattern(s->vg, battery_img_x, battery_img_y, + battery_img_w, battery_img_h, 0, battery_img, 1.0f); + nvgRect(s->vg, battery_img_x, battery_img_y, battery_img_w, battery_img_h); + nvgFillPaint(s->vg, imgPaint); + nvgFill(s->vg); +} + +static void ui_draw_sidebar_network_type(UIState *s, bool hasSidebar) { + const int network_x = hasSidebar ? 50 : -(sbr_w); + const int network_y = 273; + const int network_w = 100; + const int network_h = 100; + const char *network_types[6] = {"--", "WiFi", "2G", "3G", "4G", "5G"}; + char network_type_str[32]; + + if (s->scene.networkType <= 5) { + snprintf(network_type_str, sizeof(network_type_str), "%s", network_types[s->scene.networkType]); + } + + nvgFillColor(s->vg, COLOR_WHITE); + nvgFontSize(s->vg, 48); + nvgFontFace(s->vg, "sans-regular"); + nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); + nvgTextBox(s->vg, network_x, network_y, network_w, network_type_str, NULL); +} + +static void ui_draw_sidebar_metric(UIState *s, const char* label_str, const char* value_str, const int severity, const int y_offset, const char* message_str, bool hasSidebar) { + const int metric_x = hasSidebar ? 30 : -(sbr_w); + const int metric_y = 338 + y_offset; + const int metric_w = 240; + const int metric_h = message_str ? strlen(message_str) > 8 ? 124 : 100 : 148; + NVGcolor status_color; + + if (severity == 0) { + status_color = COLOR_WHITE; + } else if (severity == 1) { + status_color = COLOR_YELLOW; + } else if (severity > 1) { + status_color = COLOR_RED; + } + + nvgBeginPath(s->vg); + nvgRoundedRect(s->vg, metric_x, metric_y, metric_w, metric_h, 20); + nvgStrokeColor(s->vg, severity > 0 ? COLOR_WHITE : COLOR_WHITE_ALPHA); + nvgStrokeWidth(s->vg, 2); + nvgStroke(s->vg); + + nvgBeginPath(s->vg); + nvgRoundedRectVarying(s->vg, metric_x + 6, metric_y + 6, 18, metric_h - 12, 25, 0, 0, 25); + nvgFillColor(s->vg, status_color); + nvgFill(s->vg); + + if (!message_str) { + nvgFillColor(s->vg, COLOR_WHITE); + nvgFontSize(s->vg, 78); + nvgFontFace(s->vg, "sans-bold"); + nvgTextAlign(s->vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + nvgTextBox(s->vg, metric_x + 50, metric_y + 50, metric_w - 60, value_str, NULL); + + nvgFillColor(s->vg, COLOR_WHITE); + nvgFontSize(s->vg, 48); + nvgFontFace(s->vg, "sans-regular"); + nvgTextAlign(s->vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + nvgTextBox(s->vg, metric_x + 50, metric_y + 50 + 66, metric_w - 60, label_str, NULL); + } else { + nvgFillColor(s->vg, COLOR_WHITE); + nvgFontSize(s->vg, 48); + nvgFontFace(s->vg, "sans-bold"); + nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); + nvgTextBox(s->vg, metric_x + 35, metric_y + (strlen(message_str) > 8 ? 40 : 50), metric_w - 50, message_str, NULL); + } +} + +static void ui_draw_sidebar_storage_metric(UIState *s, bool hasSidebar) { + int storage_severity; + char storage_label_str[32]; + char storage_value_str[32]; + char storage_value_unit[32]; + const int storage_y_offset = 0; + const float storage_pct = ceilf((1.0 - s->scene.freeSpace) * 100); + + if (storage_pct < 75.0) { + storage_severity = 0; + } else if (storage_pct >= 75.0 && storage_pct < 87.0) { + storage_severity = 1; + } else if (storage_pct >= 87.0) { + storage_severity = 2; + } + + snprintf(storage_value_str, sizeof(storage_value_str), "%d", (int)storage_pct); + snprintf(storage_value_unit, sizeof(storage_value_unit), "%s", "%"); + snprintf(storage_label_str, sizeof(storage_label_str), "%s", "STORAGE"); + strcat(storage_value_str, storage_value_unit); + + ui_draw_sidebar_metric(s, storage_label_str, storage_value_str, storage_severity, storage_y_offset, NULL, hasSidebar); +} + +static void ui_draw_sidebar_temp_metric(UIState *s, bool hasSidebar) { + int temp_severity; + char temp_label_str[32]; + char temp_value_str[32]; + char temp_value_unit[32]; + const int temp_y_offset = 148 + 32; + + if (s->scene.thermalStatus == cereal_ThermalData_ThermalStatus_green) { + temp_severity = 0; + } else if (s->scene.thermalStatus == cereal_ThermalData_ThermalStatus_yellow) { + temp_severity = 1; + } else if (s->scene.thermalStatus == cereal_ThermalData_ThermalStatus_red) { + temp_severity = 2; + } else if (s->scene.thermalStatus == cereal_ThermalData_ThermalStatus_danger) { + temp_severity = 3; + } + + snprintf(temp_value_str, sizeof(temp_value_str), "%d", s->scene.paTemp); + snprintf(temp_value_unit, sizeof(temp_value_unit), "%s", "°C"); + snprintf(temp_label_str, sizeof(temp_label_str), "%s", "TEMP"); + strcat(temp_value_str, temp_value_unit); + + ui_draw_sidebar_metric(s, temp_label_str, temp_value_str, temp_severity, temp_y_offset, NULL, hasSidebar); +} + +static void ui_draw_sidebar_panda_metric(UIState *s, bool hasSidebar) { + int panda_severity; + char panda_message_str[32]; + const int panda_y_offset = (148 + 32) * 2; + + if (s->scene.hwType == cereal_HealthData_HwType_unknown) { + panda_severity = 2; + snprintf(panda_message_str, sizeof(panda_message_str), "%s", "NO PANDA"); + } else if (s->scene.hwType == cereal_HealthData_HwType_whitePanda) { + panda_severity = 0; + snprintf(panda_message_str, sizeof(panda_message_str), "%s", "PANDA ACTIVE"); + } else if ( + (s->scene.hwType == cereal_HealthData_HwType_greyPanda) || + (s->scene.hwType == cereal_HealthData_HwType_blackPanda) || + (s->scene.hwType == cereal_HealthData_HwType_uno)) { + if (s->scene.satelliteCount == -1) { + panda_severity = 0; + snprintf(panda_message_str, sizeof(panda_message_str), "%s", "PANDA ACTIVE"); + } else if (s->scene.satelliteCount < 6) { + panda_severity = 1; + snprintf(panda_message_str, sizeof(panda_message_str), "%s", "PANDA\nNO GPS"); + } else if (s->scene.satelliteCount >= 6) { + panda_severity = 0; + snprintf(panda_message_str, sizeof(panda_message_str), "%s", "PANDA GOOD GPS"); + } + } + + ui_draw_sidebar_metric(s, NULL, NULL, panda_severity, panda_y_offset, panda_message_str, hasSidebar); +} + +void ui_draw_sidebar(UIState *s) { + bool hasSidebar = !s->scene.uilayout_sidebarcollapsed; + ui_draw_sidebar_background(s, hasSidebar); + ui_draw_sidebar_settings_button(s, hasSidebar); + ui_draw_sidebar_home_button(s, hasSidebar); + ui_draw_sidebar_network_strength(s, hasSidebar); + ui_draw_sidebar_battery_icon(s, hasSidebar); + ui_draw_sidebar_network_type(s, hasSidebar); + ui_draw_sidebar_storage_metric(s, hasSidebar); + ui_draw_sidebar_temp_metric(s, hasSidebar); + ui_draw_sidebar_panda_metric(s, hasSidebar); +} diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index da566a1e8a..7b00117921 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -57,6 +57,45 @@ static void set_awake(UIState *s, bool awake) { #endif } +static void navigate_to_settings(UIState *s) { +#ifdef QCOM + system("am broadcast -a 'ai.comma.plus.SidebarSettingsTouchUpInside'"); +#else + // computer UI doesn't have offroad settings +#endif +} + +static void navigate_to_home(UIState *s) { +#ifdef QCOM + system("am broadcast -a 'ai.comma.plus.HomeButtonTouchUpInside'"); +#else + // computer UI doesn't have offroad home +#endif +} + +static void handle_sidebar_touch(UIState *s, int touch_x, int touch_y) { + if (!s->scene.uilayout_sidebarcollapsed && touch_x <= sbr_w) { + if (touch_x >= settings_btn_x && touch_x < (settings_btn_x + settings_btn_w) + && touch_y >= settings_btn_y && touch_y < (settings_btn_y + settings_btn_h)) { + navigate_to_settings(s); + } + if (touch_x >= home_btn_x && touch_x < (home_btn_x + home_btn_w) + && touch_y >= home_btn_y && touch_y < (home_btn_y + home_btn_h)) { + navigate_to_home(s); + if (s->vision_connected) { + s->scene.uilayout_sidebarcollapsed = true; + } + } + } +} + +static void handle_vision_touch(UIState *s, int touch_x, int touch_y) { + if (s->vision_connected && (touch_x >= s->scene.ui_viz_rx - bdr_s) + && (s->active_app != cereal_UiLayoutState_App_settings)) { + s->scene.uilayout_sidebarcollapsed = !s->scene.uilayout_sidebarcollapsed; + } +} + volatile sig_atomic_t do_exit = 0; static void set_do_exit(int sig) { do_exit = 1; @@ -110,19 +149,28 @@ static void ui_init(UIState *s) { s->uilayout_sock = SubSocket::create(s->ctx, "uiLayoutState"); s->livecalibration_sock = SubSocket::create(s->ctx, "liveCalibration"); s->radarstate_sock = SubSocket::create(s->ctx, "radarState"); + s->thermal_sock = SubSocket::create(s->ctx, "thermal"); + s->health_sock = SubSocket::create(s->ctx, "health"); + s->ubloxgnss_sock = SubSocket::create(s->ctx, "ubloxGnss"); assert(s->model_sock != NULL); assert(s->controlsstate_sock != NULL); assert(s->uilayout_sock != NULL); assert(s->livecalibration_sock != NULL); assert(s->radarstate_sock != NULL); + assert(s->thermal_sock != NULL); + assert(s->health_sock != NULL); + assert(s->ubloxgnss_sock != NULL); s->poller = Poller::create({ s->model_sock, s->controlsstate_sock, s->uilayout_sock, s->livecalibration_sock, - s->radarstate_sock + s->radarstate_sock, + s->thermal_sock, + s->health_sock, + s->ubloxgnss_sock }); #ifdef SHOW_SPEEDLIMIT @@ -404,27 +452,56 @@ void handle_message(UIState *s, Message * msg) { cereal_read_UiLayoutState(&datad, eventd.uiLayoutState); s->active_app = datad.activeApp; s->scene.uilayout_sidebarcollapsed = datad.sidebarCollapsed; - s->scene.uilayout_mapenabled = datad.mapEnabled; - - bool hasSidebar = !s->scene.uilayout_sidebarcollapsed; - bool mapEnabled = s->scene.uilayout_mapenabled; - if (mapEnabled) { - s->scene.ui_viz_rx = hasSidebar ? (box_x+nav_w) : (box_x+nav_w-(bdr_s*4)); - s->scene.ui_viz_rw = hasSidebar ? (box_w-nav_w) : (box_w-nav_w+(bdr_s*4)); - s->scene.ui_viz_ro = -(sbr_w + 4*bdr_s); - } else { - s->scene.ui_viz_rx = hasSidebar ? box_x : (box_x-sbr_w+bdr_s*2); - s->scene.ui_viz_rw = hasSidebar ? box_w : (box_w+sbr_w-(bdr_s*2)); - s->scene.ui_viz_ro = hasSidebar ? -(sbr_w - 6*bdr_s) : 0; - } } else if (eventd.which == cereal_Event_liveMapData) { struct cereal_LiveMapData datad; cereal_read_LiveMapData(&datad, eventd.liveMapData); s->scene.map_valid = datad.mapValid; + } else if (eventd.which == cereal_Event_thermal) { + struct cereal_ThermalData datad; + cereal_read_ThermalData(&datad, eventd.thermal); + + s->scene.networkType = datad.networkType; + s->scene.networkStrength = datad.networkStrength; + s->scene.batteryPercent = datad.batteryPercent; + snprintf(s->scene.batteryStatus, sizeof(s->scene.batteryStatus), "%s", datad.batteryStatus.str); + s->scene.freeSpace = datad.freeSpace; + s->scene.thermalStatus = datad.thermalStatus; + s->scene.paTemp = datad.pa0; + } else if (eventd.which == cereal_Event_ubloxGnss) { + struct cereal_UbloxGnss datad; + cereal_read_UbloxGnss(&datad, eventd.ubloxGnss); + struct cereal_UbloxGnss_MeasurementReport reportdatad; + cereal_read_UbloxGnss_MeasurementReport(&reportdatad, datad.measurementReport); + + s->scene.satelliteCount = reportdatad.numMeas; + } else if (eventd.which == cereal_Event_health) { + struct cereal_HealthData datad; + cereal_read_HealthData(&datad, eventd.health); + + s->scene.hwType = datad.hwType; + s->hardware_timeout = 5*30; // 5 seconds at 30 fps } capn_free(&ctx); } +static void check_messages(UIState *s) { + while(true) { + auto polls = s->poller->poll(0); + + if (polls.size() == 0) + break; + + for (auto sock : polls){ + Message * msg = sock->receive(); + if (msg == NULL) continue; + + handle_message(s, msg); + + delete msg; + } + } +} + static void ui_update(UIState *s) { int err; @@ -494,7 +571,7 @@ static void ui_update(UIState *s) { assert(glGetError() == GL_NO_ERROR); - // Default UI Measurements (Assumes sidebar collapsed) + s->scene.uilayout_sidebarcollapsed = true; s->scene.ui_viz_rx = (box_x-sbr_w+bdr_s*2); s->scene.ui_viz_rw = (box_w+sbr_w-(bdr_s*2)); s->scene.ui_viz_ro = 0; @@ -575,23 +652,7 @@ static void ui_update(UIState *s) { break; } // peek and consume all events in the zmq queue, then return. - while(true) { - auto polls = s->poller->poll(0); - - if (polls.size() == 0) - return; - - for (auto sock : polls){ - Message * msg = sock->receive(); - if (msg == NULL) continue; - - set_awake(s, true); - - handle_message(s, msg); - - delete msg; - } - } + check_messages(s); } static int vision_subscribe(int fd, VisionPacket *rp, VisionStreamType type) { @@ -732,7 +793,6 @@ fail: return NULL; } - static void* bg_thread(void* args) { UIState *s = (UIState*)args; set_thread_name("bg"); @@ -813,7 +873,6 @@ int main(int argc, char* argv[]) { TouchState touch = {0}; touch_init(&touch); s->touch_fd = touch.fd; - ui_sound_init(); // light sensor scaling params @@ -830,6 +889,9 @@ int main(int argc, char* argv[]) { set_volume(MIN_VOLUME); s->volume_timeout = 5 * UI_FREQ; int draws = 0; + + s->scene.satelliteCount = -1; + while (!do_exit) { bool should_swap = false; if (!s->vision_connected) { @@ -847,34 +909,37 @@ int main(int argc, char* argv[]) { if (smooth_brightness > 255) smooth_brightness = 255; set_brightness(s, (int)smooth_brightness); + // resize vision for collapsing sidebar + const bool hasSidebar = !s->scene.uilayout_sidebarcollapsed; + s->scene.ui_viz_rx = hasSidebar ? box_x : (box_x - sbr_w + (bdr_s * 2)); + s->scene.ui_viz_rw = hasSidebar ? box_w : (box_w + sbr_w - (bdr_s * 2)); + s->scene.ui_viz_ro = hasSidebar ? -(sbr_w - 6 * bdr_s) : 0; + + // poll for touch events + int touch_x = -1, touch_y = -1; + int touched = touch_poll(&touch, &touch_x, &touch_y, 0); + if (touched == 1) { + set_awake(s, true); + handle_sidebar_touch(s, touch_x, touch_y); + handle_vision_touch(s, touch_x, touch_y); + } + if (!s->vision_connected) { - // Car is not started, keep in idle state and awake on touch events - zmq_pollitem_t polls[1] = {{0}}; - polls[0].fd = s->touch_fd; - polls[0].events = ZMQ_POLLIN; - int ret = zmq_poll(polls, 1, 0); - if (ret < 0){ - if (errno == EINTR) continue; - LOGW("poll failed (%d)", ret); - } else if (ret > 0) { - // awake on any touch - int touch_x = -1, touch_y = -1; - int touched = touch_read(&touch, &touch_x, &touch_y); - if (touched == 1) { - set_awake(s, true); - } - } if (s->status != STATUS_STOPPED) { update_status(s, STATUS_STOPPED); } + check_messages(s); } else { + set_awake(s, true); if (s->status == STATUS_STOPPED) { update_status(s, STATUS_DISENGAGED); } // Car started, fetch a new rgb image from ipc and peek for zmq events. ui_update(s); - if(!s->vision_connected) { + if (!s->vision_connected) { // Visiond process is just stopped, force a redraw to make screen blank again. + s->scene.satelliteCount = -1; + s->scene.uilayout_sidebarcollapsed = false; ui_draw(s); glFinish(); should_swap = true; @@ -888,8 +953,15 @@ int main(int argc, char* argv[]) { set_awake(s, false); } - // Don't waste resources on drawing in case screen is off or car is not started. - if (s->awake && s->vision_connected) { + // manage hardware disconnect + if (s->hardware_timeout > 0) { + s->hardware_timeout--; + } else { + s->scene.hwType = cereal_HealthData_HwType_unknown; + } + + // Don't waste resources on drawing in case screen is off + if (s->awake) { ui_draw(s); glFinish(); should_swap = true; diff --git a/selfdrive/ui/ui.hpp b/selfdrive/ui/ui.hpp index 64d32c65c1..ee431a4496 100644 --- a/selfdrive/ui/ui.hpp +++ b/selfdrive/ui/ui.hpp @@ -38,6 +38,12 @@ #define ALERTSIZE_MID 2 #define ALERTSIZE_FULL 3 +#define COLOR_BLACK_ALPHA nvgRGBA(0, 0, 0, 85) +#define COLOR_WHITE nvgRGBA(255, 255, 255, 255) +#define COLOR_WHITE_ALPHA nvgRGBA(255, 255, 255, 85) +#define COLOR_YELLOW nvgRGBA(218, 202, 37, 255) +#define COLOR_RED nvgRGBA(201, 34, 49, 255) + #ifndef QCOM #define UI_60FPS #endif @@ -60,6 +66,14 @@ const int viz_w = vwp_w-(bdr_s*2); const int header_h = 420; const int footer_h = 280; const int footer_y = vwp_h-bdr_s-footer_h; +const int settings_btn_h = 117; +const int settings_btn_w = 200; +const int settings_btn_x = 50; +const int settings_btn_y = 35; +const int home_btn_h = 180; +const int home_btn_w = 180; +const int home_btn_x = 60; +const int home_btn_y = vwp_h - home_btn_h - 40; const int UI_FREQ = 30; // Hz @@ -115,7 +129,7 @@ typedef struct UIScene { int lead_status; float lead_d_rel, lead_y_rel, lead_v_rel; - + int lead_status2; float lead_d_rel2, lead_y_rel2, lead_v_rel2; @@ -131,6 +145,16 @@ typedef struct UIScene { // Used to show gps planner status bool gps_planner_active; + + uint8_t networkType; + uint8_t networkStrength; + int batteryPercent; + char batteryStatus[64]; + float freeSpace; + uint8_t thermalStatus; + int paTemp; + int hwType; + int satelliteCount; } UIScene; typedef struct { @@ -168,6 +192,11 @@ typedef struct UIState { int img_turn; int img_face; int img_map; + int img_button_settings; + int img_button_home; + int img_battery; + int img_battery_charging; + int img_network[6]; // sockets Context *ctx; @@ -177,7 +206,11 @@ typedef struct UIState { SubSocket *radarstate_sock; SubSocket *map_data_sock; SubSocket *uilayout_sock; + SubSocket *thermal_sock; + SubSocket *health_sock; + SubSocket *ubloxgnss_sock; Poller * poller; + Poller * ublox_poller; int active_app; @@ -221,6 +254,7 @@ typedef struct UIState { int is_metric_timeout; int longitudinal_control_timeout; int limit_set_speed_timeout; + int hardware_timeout; bool controls_seen; @@ -254,8 +288,9 @@ typedef struct UIState { // API void ui_draw_vision_alert(UIState *s, int va_size, int va_color, - const char* va_text1, const char* va_text2); + const char* va_text1, const char* va_text2); void ui_draw(UIState *s); +void ui_draw_sidebar(UIState *s); void ui_nvg_init(UIState *s); #endif