diff --git a/.gitignore b/.gitignore index f4932ed4f..14ba0bd05 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ selfdrive/logcatd/logcatd selfdrive/mapd/default_speeds_by_region.json selfdrive/proclogd/proclogd selfdrive/ui/_ui +selfdrive/ui/_soundd selfdrive/test/longitudinal_maneuvers/out selfdrive/visiond/visiond selfdrive/loggerd/loggerd diff --git a/Jenkinsfile b/Jenkinsfile index dbcc11066..20bfcccf6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -71,7 +71,24 @@ pipeline { } steps { phone_steps("eon-build", [ - ["build release2-staging and dashcam-staging", "cd release && PUSH=1 ./build_release2.sh"], + ["build release2-staging & dashcam-staging", "cd release && PUSH=1 ./build_release2.sh"], + ]) + } + } + + stage('Build release3') { + agent { + docker { + image 'python:3.7.3' + args '--user=root' + } + } + when { + branch 'devel-staging' + } + steps { + phone_steps("tici", [ + ["build release3-staging & dashcam3-staging", "PUSH=1 $SOURCE_DIR/release/build_release3.sh"], ]) } } @@ -80,7 +97,10 @@ pipeline { when { not { anyOf { - branch 'master-ci'; branch 'devel'; branch 'devel-staging'; branch 'release2'; branch 'release2-staging'; branch 'dashcam'; branch 'dashcam-staging'; branch 'testing-closet*'; branch 'hotfix-*' + branch 'master-ci'; branch 'devel'; branch 'devel-staging'; + branch 'release2'; branch 'release2-staging'; branch 'dashcam'; branch 'dashcam-staging'; + branch 'release3'; branch 'release3-staging'; branch 'dashcam3'; branch 'dashcam3-staging'; + branch 'testing-closet*'; branch 'hotfix-*' } } } @@ -152,7 +172,7 @@ pipeline { phone_steps("eon", [ ["build", "cd selfdrive/manager && ./build.py"], ["test athena", "nosetests -s selfdrive/athena/tests/test_athenad_old.py"], - ["test sounds", "nosetests -s selfdrive/test/test_sounds.py"], + ["test sounds", "nosetests -s selfdrive/ui/tests/test_sounds.py"], ["test boardd loopback", "nosetests -s selfdrive/boardd/tests/test_boardd_loopback.py"], ["test loggerd", "python selfdrive/loggerd/tests/test_loggerd.py"], ["test encoder", "python selfdrive/loggerd/tests/test_encoder.py"], @@ -191,7 +211,6 @@ pipeline { phone_steps("tici", [ ["build", "cd selfdrive/manager && ./build.py"], ["onroad tests", "cd selfdrive/test/ && ./test_onroad.py"], - //["build release3-staging", "cd release && PUSH=${env.R3_PUSH} ./build_release3.sh"], ]) } } diff --git a/README.md b/README.md index ff1cde33e..aeaeb5613 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,7 @@ Community Maintained Cars and Features | Volkswagen| Jetta 2018-20 | Driver Assistance | Stock | 0mph | 0mph | | Volkswagen| Jetta GLI 2021 | Driver Assistance | Stock | 0mph | 0mph | | Volkswagen| Passat 2016-172 | Driver Assistance | Stock | 0mph | 0mph | +| Volkswagen| T-Cross 2021 | Driver Assistance | Stock | 0mph | 0mph | | Volkswagen| Tiguan 2020 | Driver Assistance | Stock | 0mph | 0mph | | Volkswagen| Touran 2017 | Driver Assistance | Stock | 0mph | 0mph | @@ -298,13 +299,13 @@ openpilot is open source software: the user is free to disable data collection i openpilot logs the road facing camera, CAN, GPS, IMU, magnetometer, thermal sensors, crashes, and operating system logs. The driver facing camera is only logged if you explicitly opt-in in settings. The microphone is not recorded. -By using openpilot, you agree to [our Privacy Policy](https://my.comma.ai/privacy). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data. +By using openpilot, you agree to [our Privacy Policy](https://connect.comma.ai/privacy). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data. Safety and Testing ---- * openpilot observes ISO26262 guidelines, see [SAFETY.md](SAFETY.md) for more details. -* openpilot has software in the loop [tests](.github/workflows/test.yaml) that run on every commit. +* openpilot has software in the loop [tests](.github/workflows/selfdrive_tests.yaml) that run on every commit. * The safety model code lives in panda and is written in C, see [code rigor](https://github.com/commaai/panda#code-rigor) for more details. * panda has software in the loop [safety tests](https://github.com/commaai/panda/tree/master/tests/safety). * Internally, we have a hardware in the loop Jenkins test suite that builds and unit tests the various processes. diff --git a/RELEASES.md b/RELEASES.md index 767d6331e..59d9ee474 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,7 @@ +Version 0.8.7 (2021-07-31) +======================== + * Volkswagen T-Cross 2021 support thanks to jyoung8607! + Version 0.8.6 (2021-07-21) ======================== * Revamp lateral and longitudinal planners diff --git a/cereal/log.capnp b/cereal/log.capnp index 4f21fcf98..79c1fd9b2 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -432,6 +432,7 @@ struct PandaState @0xa7649e2575e4591e { pedal @4; uno @5; dos @6; + red @7; } enum UsbPowerMode { diff --git a/cereal/visionipc/visionbuf.h b/cereal/visionipc/visionbuf.h index f6d84f4c1..fad60f7ab 100644 --- a/cereal/visionipc/visionbuf.h +++ b/cereal/visionipc/visionbuf.h @@ -49,7 +49,6 @@ class VisionBuf { // ion int handle = 0; - bool owner = false; void allocate(size_t len); void import(); diff --git a/cereal/visionipc/visionbuf_ion.cc b/cereal/visionipc/visionbuf_ion.cc index 31448daed..92b72b04d 100644 --- a/cereal/visionipc/visionbuf_ion.cc +++ b/cereal/visionipc/visionbuf_ion.cc @@ -62,7 +62,6 @@ void VisionBuf::allocate(size_t len) { memset(addr, 0, ion_alloc.len); - this->owner = true; this->len = len; this->mmap_len = ion_alloc.len; this->addr = addr; @@ -82,7 +81,6 @@ void VisionBuf::import(){ err = ioctl(ion_fd, ION_IOC_IMPORT, &fd_data); assert(err == 0); - this->owner = false; this->handle = fd_data.handle; this->addr = mmap(NULL, this->mmap_len, PROT_READ | PROT_WRITE, MAP_SHARED, this->fd, 0); assert(this->addr != MAP_FAILED); @@ -139,10 +137,6 @@ void VisionBuf::free() { munmap(this->addr, this->mmap_len); close(this->fd); - // Free the ION buffer if we also shared it - if (this->owner){ - struct ion_handle_data handle_data = {.handle = this->handle}; - int ret = ioctl(ion_fd, ION_IOC_FREE, &handle_data); - assert(ret == 0); - } + struct ion_handle_data handle_data = {.handle = this->handle}; + ioctl(ion_fd, ION_IOC_FREE, &handle_data); } diff --git a/cereal/visionipc/visionipc_server.cc b/cereal/visionipc/visionipc_server.cc index 4aeb40d6e..adfc23190 100644 --- a/cereal/visionipc/visionipc_server.cc +++ b/cereal/visionipc/visionipc_server.cc @@ -122,7 +122,6 @@ void VisionIpcServer::listener(){ bufs[i].buf_cl = 0; bufs[i].copy_q = 0; bufs[i].handle = 0; - bufs[i].owner = false; bufs[i].server_id = server_id; } diff --git a/common/logging_extra.py b/common/logging_extra.py index 9471b4419..d3a75d0f4 100644 --- a/common/logging_extra.py +++ b/common/logging_extra.py @@ -158,7 +158,7 @@ class SwagLogger(logging.Logger): evt.update(kwargs) if 'error' in kwargs: self.error(evt) - if 'debug' in kwargs: + elif 'debug' in kwargs: self.debug(evt) else: self.info(evt) diff --git a/installer/continue_openpilot.sh b/installer/continue_openpilot.sh new file mode 100755 index 000000000..3da67313e --- /dev/null +++ b/installer/continue_openpilot.sh @@ -0,0 +1,4 @@ +#!/usr/bin/bash + +cd /data/openpilot +exec ./launch_openpilot.sh diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index d76dd9c18..d8bf6da5c 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -93,6 +93,11 @@ function two_init { } function tici_init { + # wait longer for weston to come up + if [ -f "$BASEDIR/prebuilt" ]; then + sleep 3 + fi + sudo su -c 'echo "performance" > /sys/class/devfreq/soc:qcom,memlat-cpu0/governor' sudo su -c 'echo "performance" > /sys/class/devfreq/soc:qcom,memlat-cpu4/governor' nmcli connection modify --temporary lte gsm.auto-config yes @@ -103,11 +108,12 @@ function tici_init { # Check if AGNOS update is required if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then + AGNOS_PY="$DIR/selfdrive/hardware/tici/agnos.py" MANIFEST="$DIR/selfdrive/hardware/tici/agnos.json" - $DIR/selfdrive/hardware/tici/agnos.py --swap $MANIFEST - - sleep 1 - sudo reboot + if $AGNOS_PY --verify $MANIFEST; then + sudo reboot + fi + $DIR/selfdrive/hardware/tici/updater $AGNOS_PY $MANIFEST fi } diff --git a/launch_env.sh b/launch_env.sh index 3a3fef74e..128d3d687 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -11,7 +11,7 @@ if [ -z "$REQUIRED_NEOS_VERSION" ]; then fi if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="0.21" + export AGNOS_VERSION="1.1" fi if [ -z "$PASSIVE" ]; then diff --git a/phonelibs/mapbox-gl-native-qt/include/QMapbox b/phonelibs/mapbox-gl-native-qt/include/QMapbox new file mode 100644 index 000000000..a8479c09a --- /dev/null +++ b/phonelibs/mapbox-gl-native-qt/include/QMapbox @@ -0,0 +1 @@ +#include "qmapbox.hpp" diff --git a/phonelibs/mapbox-gl-native-qt/include/QMapboxGL b/phonelibs/mapbox-gl-native-qt/include/QMapboxGL new file mode 100644 index 000000000..15b55a9ab --- /dev/null +++ b/phonelibs/mapbox-gl-native-qt/include/QMapboxGL @@ -0,0 +1 @@ +#include "qmapboxgl.hpp" diff --git a/phonelibs/mapbox-gl-native-qt/include/qmapbox.hpp b/phonelibs/mapbox-gl-native-qt/include/qmapbox.hpp new file mode 100644 index 000000000..3acc9d55e --- /dev/null +++ b/phonelibs/mapbox-gl-native-qt/include/qmapbox.hpp @@ -0,0 +1,147 @@ +#ifndef QMAPBOX_H +#define QMAPBOX_H + +#include +#include +#include +#include +#include + +// This header follows the Qt coding style: https://wiki.qt.io/Qt_Coding_Style + +#if !defined(QT_MAPBOXGL_STATIC) +# if defined(QT_BUILD_MAPBOXGL_LIB) +# define Q_MAPBOXGL_EXPORT Q_DECL_EXPORT +# else +# define Q_MAPBOXGL_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_MAPBOXGL_EXPORT +#endif + +namespace QMapbox { + +typedef QPair Coordinate; +typedef QPair CoordinateZoom; +typedef QPair ProjectedMeters; + +typedef QVector Coordinates; +typedef QVector CoordinatesCollection; + +typedef QVector CoordinatesCollections; + +struct Q_MAPBOXGL_EXPORT Feature { + enum Type { + PointType = 1, + LineStringType, + PolygonType + }; + + /*! Class constructor. */ + Feature(Type type_ = PointType, const CoordinatesCollections& geometry_ = CoordinatesCollections(), + const QVariantMap& properties_ = QVariantMap(), const QVariant& id_ = QVariant()) + : type(type_), geometry(geometry_), properties(properties_), id(id_) {} + + Type type; + CoordinatesCollections geometry; + QVariantMap properties; + QVariant id; +}; + +struct Q_MAPBOXGL_EXPORT ShapeAnnotationGeometry { + enum Type { + LineStringType = 1, + PolygonType, + MultiLineStringType, + MultiPolygonType + }; + + /*! Class constructor. */ + ShapeAnnotationGeometry(Type type_ = LineStringType, const CoordinatesCollections& geometry_ = CoordinatesCollections()) + : type(type_), geometry(geometry_) {} + + Type type; + CoordinatesCollections geometry; +}; + +struct Q_MAPBOXGL_EXPORT SymbolAnnotation { + Coordinate geometry; + QString icon; +}; + +struct Q_MAPBOXGL_EXPORT LineAnnotation { + /*! Class constructor. */ + LineAnnotation(const ShapeAnnotationGeometry& geometry_ = ShapeAnnotationGeometry(), float opacity_ = 1.0f, + float width_ = 1.0f, const QColor& color_ = Qt::black) + : geometry(geometry_), opacity(opacity_), width(width_), color(color_) {} + + ShapeAnnotationGeometry geometry; + float opacity; + float width; + QColor color; +}; + +struct Q_MAPBOXGL_EXPORT FillAnnotation { + /*! Class constructor. */ + FillAnnotation(const ShapeAnnotationGeometry& geometry_ = ShapeAnnotationGeometry(), float opacity_ = 1.0f, + const QColor& color_ = Qt::black, const QVariant& outlineColor_ = QVariant()) + : geometry(geometry_), opacity(opacity_), color(color_), outlineColor(outlineColor_) {} + + ShapeAnnotationGeometry geometry; + float opacity; + QColor color; + QVariant outlineColor; +}; + +typedef QVariant Annotation; +typedef quint32 AnnotationID; +typedef QVector AnnotationIDs; + +enum NetworkMode { + Online, // Default + Offline, +}; + +Q_MAPBOXGL_EXPORT QVector >& defaultStyles(); + +Q_MAPBOXGL_EXPORT NetworkMode networkMode(); +Q_MAPBOXGL_EXPORT void setNetworkMode(NetworkMode); + +// This struct is a 1:1 copy of mbgl::CustomLayerRenderParameters. +struct Q_MAPBOXGL_EXPORT CustomLayerRenderParameters { + double width; + double height; + double latitude; + double longitude; + double zoom; + double bearing; + double pitch; + double fieldOfView; +}; + +class Q_MAPBOXGL_EXPORT CustomLayerHostInterface { +public: + virtual ~CustomLayerHostInterface() = default; + virtual void initialize() = 0; + virtual void render(const CustomLayerRenderParameters&) = 0; + virtual void deinitialize() = 0; +}; + +Q_MAPBOXGL_EXPORT double metersPerPixelAtLatitude(double latitude, double zoom); +Q_MAPBOXGL_EXPORT ProjectedMeters projectedMetersForCoordinate(const Coordinate &); +Q_MAPBOXGL_EXPORT Coordinate coordinateForProjectedMeters(const ProjectedMeters &); + +} // namespace QMapbox + +Q_DECLARE_METATYPE(QMapbox::Coordinate); +Q_DECLARE_METATYPE(QMapbox::Coordinates); +Q_DECLARE_METATYPE(QMapbox::CoordinatesCollection); +Q_DECLARE_METATYPE(QMapbox::CoordinatesCollections); +Q_DECLARE_METATYPE(QMapbox::Feature); + +Q_DECLARE_METATYPE(QMapbox::SymbolAnnotation); +Q_DECLARE_METATYPE(QMapbox::ShapeAnnotationGeometry); +Q_DECLARE_METATYPE(QMapbox::LineAnnotation); +Q_DECLARE_METATYPE(QMapbox::FillAnnotation); + +#endif // QMAPBOX_H diff --git a/phonelibs/mapbox-gl-native-qt/include/qmapboxgl.hpp b/phonelibs/mapbox-gl-native-qt/include/qmapboxgl.hpp new file mode 100644 index 000000000..337991aa1 --- /dev/null +++ b/phonelibs/mapbox-gl-native-qt/include/qmapboxgl.hpp @@ -0,0 +1,277 @@ +#ifndef QMAPBOXGL_H +#define QMAPBOXGL_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class QMapboxGLPrivate; + +// This header follows the Qt coding style: https://wiki.qt.io/Qt_Coding_Style + +class Q_MAPBOXGL_EXPORT QMapboxGLSettings +{ +public: + QMapboxGLSettings(); + + enum GLContextMode { + UniqueGLContext = 0, + SharedGLContext + }; + + enum MapMode { + Continuous = 0, + Static + }; + + enum ConstrainMode { + NoConstrain = 0, + ConstrainHeightOnly, + ConstrainWidthAndHeight + }; + + enum ViewportMode { + DefaultViewport = 0, + FlippedYViewport + }; + + GLContextMode contextMode() const; + void setContextMode(GLContextMode); + + MapMode mapMode() const; + void setMapMode(MapMode); + + ConstrainMode constrainMode() const; + void setConstrainMode(ConstrainMode); + + ViewportMode viewportMode() const; + void setViewportMode(ViewportMode); + + unsigned cacheDatabaseMaximumSize() const; + void setCacheDatabaseMaximumSize(unsigned); + + QString cacheDatabasePath() const; + void setCacheDatabasePath(const QString &); + + QString assetPath() const; + void setAssetPath(const QString &); + + QString accessToken() const; + void setAccessToken(const QString &); + + QString apiBaseUrl() const; + void setApiBaseUrl(const QString &); + + QString localFontFamily() const; + void setLocalFontFamily(const QString &); + + std::function resourceTransform() const; + void setResourceTransform(const std::function &); + +private: + GLContextMode m_contextMode; + MapMode m_mapMode; + ConstrainMode m_constrainMode; + ViewportMode m_viewportMode; + + unsigned m_cacheMaximumSize; + QString m_cacheDatabasePath; + QString m_assetPath; + QString m_accessToken; + QString m_apiBaseUrl; + QString m_localFontFamily; + std::function m_resourceTransform; +}; + +struct Q_MAPBOXGL_EXPORT QMapboxGLCameraOptions { + QVariant center; // Coordinate + QVariant anchor; // QPointF + QVariant zoom; // double + QVariant bearing; // double + QVariant pitch; // double +}; + +class Q_MAPBOXGL_EXPORT QMapboxGL : public QObject +{ + Q_OBJECT + Q_PROPERTY(double latitude READ latitude WRITE setLatitude) + Q_PROPERTY(double longitude READ longitude WRITE setLongitude) + Q_PROPERTY(double zoom READ zoom WRITE setZoom) + Q_PROPERTY(double bearing READ bearing WRITE setBearing) + Q_PROPERTY(double pitch READ pitch WRITE setPitch) + Q_PROPERTY(QString styleJson READ styleJson WRITE setStyleJson) + Q_PROPERTY(QString styleUrl READ styleUrl WRITE setStyleUrl) + Q_PROPERTY(double scale READ scale WRITE setScale) + Q_PROPERTY(QMapbox::Coordinate coordinate READ coordinate WRITE setCoordinate) + Q_PROPERTY(QMargins margins READ margins WRITE setMargins) + +public: + enum MapChange { + MapChangeRegionWillChange = 0, + MapChangeRegionWillChangeAnimated, + MapChangeRegionIsChanging, + MapChangeRegionDidChange, + MapChangeRegionDidChangeAnimated, + MapChangeWillStartLoadingMap, + MapChangeDidFinishLoadingMap, + MapChangeDidFailLoadingMap, + MapChangeWillStartRenderingFrame, + MapChangeDidFinishRenderingFrame, + MapChangeDidFinishRenderingFrameFullyRendered, + MapChangeWillStartRenderingMap, + MapChangeDidFinishRenderingMap, + MapChangeDidFinishRenderingMapFullyRendered, + MapChangeDidFinishLoadingStyle, + MapChangeSourceDidChange + }; + + enum MapLoadingFailure { + StyleParseFailure, + StyleLoadFailure, + NotFoundFailure, + UnknownFailure + }; + + // Determines the orientation of the map. + enum NorthOrientation { + NorthUpwards, // Default + NorthRightwards, + NorthDownwards, + NorthLeftwards, + }; + + QMapboxGL(QObject* parent = 0, + const QMapboxGLSettings& = QMapboxGLSettings(), + const QSize& size = QSize(), + qreal pixelRatio = 1); + virtual ~QMapboxGL(); + + QString styleJson() const; + QString styleUrl() const; + + void setStyleJson(const QString &); + void setStyleUrl(const QString &); + + double latitude() const; + void setLatitude(double latitude); + + double longitude() const; + void setLongitude(double longitude); + + double scale() const; + void setScale(double scale, const QPointF ¢er = QPointF()); + + double zoom() const; + void setZoom(double zoom); + + double minimumZoom() const; + double maximumZoom() const; + + double bearing() const; + void setBearing(double degrees); + void setBearing(double degrees, const QPointF ¢er); + + double pitch() const; + void setPitch(double pitch); + void pitchBy(double pitch); + + NorthOrientation northOrientation() const; + void setNorthOrientation(NorthOrientation); + + QMapbox::Coordinate coordinate() const; + void setCoordinate(const QMapbox::Coordinate &); + void setCoordinateZoom(const QMapbox::Coordinate &, double zoom); + + void jumpTo(const QMapboxGLCameraOptions&); + + void setGestureInProgress(bool inProgress); + + void setTransitionOptions(qint64 duration, qint64 delay = 0); + + void addAnnotationIcon(const QString &name, const QImage &sprite); + + QMapbox::AnnotationID addAnnotation(const QMapbox::Annotation &); + void updateAnnotation(QMapbox::AnnotationID, const QMapbox::Annotation &); + void removeAnnotation(QMapbox::AnnotationID); + + bool setLayoutProperty(const QString &layer, const QString &property, const QVariant &value); + bool setPaintProperty(const QString &layer, const QString &property, const QVariant &value); + + bool isFullyLoaded() const; + + void moveBy(const QPointF &offset); + void scaleBy(double scale, const QPointF ¢er = QPointF()); + void rotateBy(const QPointF &first, const QPointF &second); + + void resize(const QSize &size); + + double metersPerPixelAtLatitude(double latitude, double zoom) const; + QMapbox::ProjectedMeters projectedMetersForCoordinate(const QMapbox::Coordinate &) const; + QMapbox::Coordinate coordinateForProjectedMeters(const QMapbox::ProjectedMeters &) const; + QPointF pixelForCoordinate(const QMapbox::Coordinate &) const; + QMapbox::Coordinate coordinateForPixel(const QPointF &) const; + + QMapbox::CoordinateZoom coordinateZoomForBounds(const QMapbox::Coordinate &sw, QMapbox::Coordinate &ne) const; + QMapbox::CoordinateZoom coordinateZoomForBounds(const QMapbox::Coordinate &sw, QMapbox::Coordinate &ne, double bearing, double pitch); + + void setMargins(const QMargins &margins); + QMargins margins() const; + + void addSource(const QString &sourceID, const QVariantMap& params); + bool sourceExists(const QString &sourceID); + void updateSource(const QString &sourceID, const QVariantMap& params); + void removeSource(const QString &sourceID); + + void addImage(const QString &name, const QImage &sprite); + void removeImage(const QString &name); + + void addCustomLayer(const QString &id, + QScopedPointer& host, + const QString& before = QString()); + void addLayer(const QVariantMap ¶ms, const QString& before = QString()); + bool layerExists(const QString &id); + void removeLayer(const QString &id); + + QVector layerIds() const; + + void setFilter(const QString &layer, const QVariant &filter); + QVariant getFilter(const QString &layer) const; + // When rendering on a different thread, + // should be called on the render thread. + void createRenderer(); + void destroyRenderer(); + void setFramebufferObject(quint32 fbo, const QSize &size); + +public slots: + void render(); + void connectionEstablished(); + + // Commit changes, load all the resources + // and renders the map when completed. + void startStaticRender(); + +signals: + void needsRendering(); + void mapChanged(QMapboxGL::MapChange); + void mapLoadingFailed(QMapboxGL::MapLoadingFailure, const QString &reason); + void copyrightsChanged(const QString ©rightsHtml); + + void staticRenderFinished(const QString &error); + +private: + Q_DISABLE_COPY(QMapboxGL) + + QMapboxGLPrivate *d_ptr; +}; + +Q_DECLARE_METATYPE(QMapboxGL::MapChange); +Q_DECLARE_METATYPE(QMapboxGL::MapLoadingFailure); + +#endif // QMAPBOXGL_H diff --git a/release/build_devel.sh b/release/build_devel.sh new file mode 100755 index 000000000..db8c69bd7 --- /dev/null +++ b/release/build_devel.sh @@ -0,0 +1,70 @@ +#!/usr/bin/bash -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" + +TARGET_DIR=/data/openpilot +SOURCE_DIR="$(git rev-parse --show-toplevel)" + +# set git identity +source $DIR/identity.sh + +echo "[-] Setting up repo T=$SECONDS" +if [ ! -d "$TARGET_DIR" ]; then + mkdir -p $TARGET_DIR + cd $TARGET_DIR + git init + git remote add origin git@github.com:commaai/openpilot.git +fi + +echo "[-] bringing master-ci and devel in sync T=$SECONDS" +cd $TARGET_DIR +git prune || true +git remote prune origin || true +git fetch origin master-ci +git fetch origin devel + +git checkout -f --track origin/master-ci +git reset --hard master-ci +git checkout master-ci +git reset --hard origin/devel +git clean -xdf + +# remove everything except .git +echo "[-] erasing old openpilot T=$SECONDS" +find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \; + +# reset source tree +cd $SOURCE_DIR +git clean -xdf + +# do the files copy +echo "[-] copying files T=$SECONDS" +cd $SOURCE_DIR +cp -pR --parents $(cat release/files_common) $TARGET_DIR/ +cp -pR --parents $(cat release/files_tici) $TARGET_DIR/ +if [ ! -z "$EXTRA_FILES" ]; then + cp -pR --parents $EXTRA_FILES $TARGET_DIR/ +fi + +# append source commit hash and build date to version +GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse --short HEAD) +DATETIME=$(date '+%Y-%m-%dT%H:%M:%S') +VERSION=$(cat selfdrive/common/version.h | awk -F\" '{print $2}') +echo "#define COMMA_VERSION \"$VERSION-$GIT_HASH-$DATETIME\"" > selfdrive/common/version.h + +# in the directory +cd $TARGET_DIR +rm -f panda/board/obj/panda.bin.signed + +echo "[-] committing version $VERSION T=$SECONDS" +git add -f . +git status +git commit -a -m "openpilot v$VERSION release" + +if [ ! -z "$PUSH" ]; then + echo "[-] Pushing to $PUSH T=$SECONDS" + git remote set-url origin git@github.com:commaai/openpilot.git + git push -f origin master-ci:$PUSH +fi + +echo "[-] done T=$SECONDS" diff --git a/release/build_release2.sh b/release/build_release2.sh index ee3c3f465..c607aa820 100755 --- a/release/build_release2.sh +++ b/release/build_release2.sh @@ -55,6 +55,14 @@ scons -j3 python selfdrive/manager/test/test_manager.py selfdrive/car/tests/test_car_interfaces.py +# Ensure no submodules in release +if test "$(git submodule--helper list | wc -l)" -gt "0"; then + echo "submodules found:" + git submodule--helper list + exit 1 +fi +git submodule status + # Cleanup find . -name '*.a' -delete find . -name '*.o' -delete diff --git a/release/build_release3.sh b/release/build_release3.sh new file mode 100755 index 000000000..8c5b8b63b --- /dev/null +++ b/release/build_release3.sh @@ -0,0 +1,91 @@ +#!/usr/bin/bash -e + +# git diff --name-status origin/release3-staging | grep "^A" | less + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" + +cd $DIR + +BUILD_DIR=/data/openpilot +SOURCE_DIR="$(git rev-parse --show-toplevel)" + +BRANCH=release3-staging + +# set git identity +source $DIR/identity.sh + +echo "[-] Setting up repo T=$SECONDS" +rm -rf $BUILD_DIR +mkdir -p $BUILD_DIR +cd $BUILD_DIR +git init +git remote add origin git@github.com:commaai/openpilot.git +git checkout -f -B $BRANCH + +# do the files copy +echo "[-] copying files T=$SECONDS" +cd $SOURCE_DIR +cp -pR --parents $(cat release/files_common) $BUILD_DIR/ +cp -pR --parents $(cat release/files_tici) $BUILD_DIR/ + +# in the directory +cd $BUILD_DIR + +rm -f panda/board/obj/panda.bin.signed + +VERSION=$(cat selfdrive/common/version.h | awk -F[\"-] '{print $2}') +echo "#define COMMA_VERSION \"$VERSION-release\"" > selfdrive/common/version.h + +echo "[-] committing version $VERSION T=$SECONDS" +git add -f . +git commit -a -m "openpilot v$VERSION release" + +# Build panda firmware +pushd panda/ +CERT=/data/pandaextra/certs/release RELEASE=1 scons -u . +mv board/obj/panda.bin.signed /tmp/panda.bin.signed +popd + +# Build +export PYTHONPATH="$BUILD_DIR" +scons -j$(nproc) + +# Run tests +#python selfdrive/manager/test/test_manager.py +selfdrive/car/tests/test_car_interfaces.py + +# Cleanup +find . -name '*.a' -delete +find . -name '*.o' -delete +find . -name '*.os' -delete +find . -name '*.pyc' -delete +find . -name '__pycache__' -delete +rm -rf panda/board panda/certs panda/crypto +rm -rf .sconsign.dblite Jenkinsfile release/ + +# Move back signed panda fw +mkdir -p panda/board/obj +mv /tmp/panda.bin.signed panda/board/obj/panda.bin.signed + +# Restore phonelibs +git checkout phonelibs/ + +# Mark as prebuilt release +touch prebuilt + +# Add built files to git +git add -f . +git commit --amend -m "openpilot v$VERSION" + +if [ ! -z "$PUSH" ]; then + echo "[-] pushing T=$SECONDS" + git remote set-url origin git@github.com:commaai/openpilot.git + git push -f origin $BRANCH + + # Create dashcam + git rm selfdrive/car/*/carcontroller.py + git commit -m "create dashcam release from release" + git push -f origin $BRANCH:dashcam3-staging +fi + +echo "[-] done T=$SECONDS" diff --git a/release/check-submodules.sh b/release/check-submodules.sh new file mode 100755 index 000000000..182042e6b --- /dev/null +++ b/release/check-submodules.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +while read hash submodule ref; do + git -C $submodule fetch --depth 100 origin master + git -C $submodule branch -r --contains $hash | grep "origin/master" + if [ "$?" -eq 0 ]; then + echo "$submodule ok" + else + echo "$submodule: $hash is not on master" + exit 1 + fi +done <<< $(git submodule status --recursive) diff --git a/release/files_common b/release/files_common new file mode 100644 index 000000000..c227e5a64 --- /dev/null +++ b/release/files_common @@ -0,0 +1,622 @@ +.gitignore +LICENSE +launch_env.sh +launch_chffrplus.sh +launch_openpilot.sh + +Jenkinsfile +SConstruct + +CONTRIBUTING.md +README.md +RELEASES.md +SAFETY.md +site_scons/site_tools/cython.py + +common/.gitignore +common/__init__.py +common/gpio.py +common/realtime.py +common/clock.pyx +common/timeout.py +common/ffi_wrapper.py +common/file_helpers.py +common/logging_extra.py +common/numpy_fast.py +common/params.py +common/params_pxd.pxd +common/params_pyx.pyx +common/xattr.py +common/profiler.py +common/basedir.py +common/dict_helpers.py +common/filter_simple.py +common/stat_live.py +common/spinner.py +common/text_window.py +common/cython_hacks.py +common/SConscript + +common/kalman/.gitignore +common/kalman/* + +common/transformations/__init__.py +common/transformations/camera.py +common/transformations/model.py + +common/transformations/SConscript +common/transformations/coordinates.py +common/transformations/coordinates.cc +common/transformations/coordinates.hpp +common/transformations/orientation.py +common/transformations/orientation.cc +common/transformations/orientation.hpp +common/transformations/transformations.pxd +common/transformations/transformations.pyx + +common/api/__init__.py + +models/supercombo.dlc +models/dmonitoring_model_q.dlc + +release/* + +selfdrive/version.py + +selfdrive/__init__.py +selfdrive/config.py +selfdrive/crash.py +selfdrive/swaglog.py +selfdrive/logmessaged.py +selfdrive/tombstoned.py +selfdrive/pandad.py +selfdrive/updated.py +selfdrive/rtshield.py + +selfdrive/athena/__init__.py +selfdrive/athena/athenad.py +selfdrive/athena/manage_athenad.py +selfdrive/athena/registration.py + +selfdrive/boardd/.gitignore +selfdrive/boardd/SConscript +selfdrive/boardd/__init__.py +selfdrive/boardd/boardd.cc +selfdrive/boardd/boardd.py +selfdrive/boardd/boardd_api_impl.pyx +selfdrive/boardd/can_list_to_can_capnp.cc +selfdrive/boardd/panda.cc +selfdrive/boardd/panda.h +selfdrive/boardd/pigeon.cc +selfdrive/boardd/pigeon.h +selfdrive/boardd/set_time.py + +selfdrive/car/__init__.py +selfdrive/car/car_helpers.py +selfdrive/car/fingerprints.py +selfdrive/car/interfaces.py +selfdrive/car/vin.py +selfdrive/car/fw_versions.py +selfdrive/car/isotp_parallel_query.py +selfdrive/car/tests/__init__.py +selfdrive/car/tests/test_car_interfaces.py +selfdrive/car/chrysler/__init__.py +selfdrive/car/chrysler/carstate.py +selfdrive/car/chrysler/interface.py +selfdrive/car/chrysler/radar_interface.py +selfdrive/car/chrysler/values.py +selfdrive/car/chrysler/carcontroller.py +selfdrive/car/chrysler/chryslercan.py +selfdrive/car/honda/__init__.py +selfdrive/car/honda/carstate.py +selfdrive/car/honda/interface.py +selfdrive/car/honda/radar_interface.py +selfdrive/car/honda/values.py +selfdrive/car/honda/carcontroller.py +selfdrive/car/honda/hondacan.py +selfdrive/car/hyundai/__init__.py +selfdrive/car/hyundai/carstate.py +selfdrive/car/hyundai/interface.py +selfdrive/car/hyundai/radar_interface.py +selfdrive/car/hyundai/values.py +selfdrive/car/hyundai/carcontroller.py +selfdrive/car/hyundai/hyundaican.py +selfdrive/car/toyota/__init__.py +selfdrive/car/toyota/carstate.py +selfdrive/car/toyota/interface.py +selfdrive/car/toyota/radar_interface.py +selfdrive/car/toyota/values.py +selfdrive/car/toyota/carcontroller.py +selfdrive/car/toyota/toyotacan.py +selfdrive/car/nissan/__init__.py +selfdrive/car/nissan/carcontroller.py +selfdrive/car/nissan/carstate.py +selfdrive/car/nissan/interface.py +selfdrive/car/nissan/nissancan.py +selfdrive/car/nissan/radar_interface.py +selfdrive/car/nissan/values.py +selfdrive/car/volkswagen/__init__.py +selfdrive/car/volkswagen/carstate.py +selfdrive/car/volkswagen/interface.py +selfdrive/car/volkswagen/radar_interface.py +selfdrive/car/volkswagen/values.py +selfdrive/car/volkswagen/carcontroller.py +selfdrive/car/volkswagen/volkswagencan.py +selfdrive/car/gm/__init__.py +selfdrive/car/gm/carstate.py +selfdrive/car/gm/interface.py +selfdrive/car/gm/radar_interface.py +selfdrive/car/gm/values.py +selfdrive/car/gm/carcontroller.py +selfdrive/car/gm/gmcan.py +selfdrive/car/ford/__init__.py +selfdrive/car/ford/carstate.py +selfdrive/car/ford/interface.py +selfdrive/car/ford/radar_interface.py +selfdrive/car/ford/values.py +selfdrive/car/ford/carcontroller.py +selfdrive/car/ford/fordcan.py +selfdrive/car/subaru/__init__.py +selfdrive/car/subaru/carstate.py +selfdrive/car/subaru/interface.py +selfdrive/car/subaru/radar_interface.py +selfdrive/car/subaru/values.py +selfdrive/car/subaru/carcontroller.py +selfdrive/car/subaru/subarucan.py +selfdrive/car/mazda/__init__.py +selfdrive/car/mazda/carstate.py +selfdrive/car/mazda/interface.py +selfdrive/car/mazda/radar_interface.py +selfdrive/car/mazda/values.py +selfdrive/car/mazda/carcontroller.py +selfdrive/car/mazda/mazdacan.py +selfdrive/car/tesla/__init__.py +selfdrive/car/tesla/teslacan.py +selfdrive/car/tesla/carcontroller.py +selfdrive/car/tesla/radar_interface.py +selfdrive/car/tesla/values.py +selfdrive/car/tesla/carstate.py +selfdrive/car/tesla/interface.py +selfdrive/car/mock/*.py + +selfdrive/clocksd/.gitignore +selfdrive/clocksd/SConscript +selfdrive/clocksd/clocksd.cc + +selfdrive/debug/*.py + +selfdrive/common/SConscript +selfdrive/common/version.h + +selfdrive/common/framebuffer.h +selfdrive/common/framebuffer.cc +selfdrive/common/glutil.cc +selfdrive/common/glutil.h +selfdrive/common/touch.[c,h] +selfdrive/common/swaglog.h +selfdrive/common/swaglog.cc +selfdrive/common/util.cc +selfdrive/common/util.h +selfdrive/common/queue.h +selfdrive/common/clutil.cc +selfdrive/common/clutil.h +selfdrive/common/params.h +selfdrive/common/params.cc +selfdrive/common/watchdog.cc +selfdrive/common/watchdog.h + +selfdrive/common/modeldata.h +selfdrive/common/mat.h +selfdrive/common/timing.h + +selfdrive/common/visionimg.cc +selfdrive/common/visionimg.h + +selfdrive/common/gpio.cc +selfdrive/common/gpio.h +selfdrive/common/i2c.cc +selfdrive/common/i2c.h + + +selfdrive/controls/__init__.py +selfdrive/controls/controlsd.py +selfdrive/controls/plannerd.py +selfdrive/controls/radard.py +selfdrive/controls/lib/__init__.py +selfdrive/controls/lib/alertmanager.py +selfdrive/controls/lib/alerts_offroad.json +selfdrive/controls/lib/events.py +selfdrive/controls/lib/drive_helpers.py +selfdrive/controls/lib/latcontrol_pid.py +selfdrive/controls/lib/latcontrol_indi.py +selfdrive/controls/lib/latcontrol_lqr.py +selfdrive/controls/lib/latcontrol_angle.py +selfdrive/controls/lib/longcontrol.py +selfdrive/controls/lib/lateral_planner.py +selfdrive/controls/lib/lane_planner.py +selfdrive/controls/lib/pid.py +selfdrive/controls/lib/longitudinal_planner.py +selfdrive/controls/lib/radar_helpers.py +selfdrive/controls/lib/vehicle_model.py +selfdrive/controls/lib/fcw.py +selfdrive/controls/lib/long_mpc.py +selfdrive/controls/lib/lead_mpc.py + +selfdrive/controls/lib/cluster/* + +selfdrive/controls/lib/lateral_mpc/lib_mpc_export/* +selfdrive/controls/lib/lateral_mpc/.gitignore +selfdrive/controls/lib/lateral_mpc/SConscript +selfdrive/controls/lib/lateral_mpc/__init__.py +selfdrive/controls/lib/lateral_mpc/generator.cpp +selfdrive/controls/lib/lateral_mpc/libmpc_py.py +selfdrive/controls/lib/lateral_mpc/lateral_mpc.c + +selfdrive/controls/lib/lead_mpc_lib/lib_mpc_export/* +selfdrive/controls/lib/lead_mpc_lib/.gitignore +selfdrive/controls/lib/lead_mpc_lib/SConscript +selfdrive/controls/lib/lead_mpc_lib/__init__.py +selfdrive/controls/lib/lead_mpc_lib/generator.cpp +selfdrive/controls/lib/lead_mpc_lib/libmpc_py.py +selfdrive/controls/lib/lead_mpc_lib/longitudinal_mpc.c + +selfdrive/controls/lib/longitudinal_mpc_lib/lib_mpc_export/* +selfdrive/controls/lib/longitudinal_mpc_lib/.gitignore +selfdrive/controls/lib/longitudinal_mpc_lib/SConscript +selfdrive/controls/lib/longitudinal_mpc_lib/__init__.py +selfdrive/controls/lib/longitudinal_mpc_lib/generator.cpp +selfdrive/controls/lib/longitudinal_mpc_lib/libmpc_py.py +selfdrive/controls/lib/longitudinal_mpc_lib/longitudinal_mpc.c + +selfdrive/hardware/__init__.py +selfdrive/hardware/base.h +selfdrive/hardware/base.py +selfdrive/hardware/hw.h +selfdrive/hardware/eon/__init__.py +selfdrive/hardware/eon/hardware.h +selfdrive/hardware/eon/hardware.py +selfdrive/hardware/tici/__init__.py +selfdrive/hardware/tici/hardware.py +selfdrive/hardware/tici/amplifier.py +selfdrive/hardware/tici/iwlist.py +selfdrive/hardware/pc/__init__.py +selfdrive/hardware/pc/hardware.py + +selfdrive/locationd/__init__.py +selfdrive/locationd/.gitignore +selfdrive/locationd/SConscript +selfdrive/locationd/ubloxd.cc +selfdrive/locationd/ublox_msg.cc +selfdrive/locationd/ublox_msg.h +selfdrive/locationd/generated/ubx.cpp +selfdrive/locationd/generated/ubx.h +selfdrive/locationd/generated/gps.cpp +selfdrive/locationd/generated/gps.h + +selfdrive/locationd/locationd.h +selfdrive/locationd/locationd.cc +selfdrive/locationd/paramsd.py +selfdrive/locationd/models/.gitignore +selfdrive/locationd/models/live_kf.py +selfdrive/locationd/models/car_kf.py +selfdrive/locationd/models/constants.py +selfdrive/locationd/models/live_kf.h +selfdrive/locationd/models/live_kf.cc + +selfdrive/locationd/calibrationd.py + +selfdrive/logcatd/SConscript +selfdrive/logcatd/logcatd_android.cc +selfdrive/logcatd/logcatd_systemd.cc + +selfdrive/proclogd/SConscript +selfdrive/proclogd/proclogd.cc + +selfdrive/loggerd/SConscript +selfdrive/loggerd/encoder.h +selfdrive/loggerd/omx_encoder.cc +selfdrive/loggerd/omx_encoder.h +selfdrive/loggerd/logger.cc +selfdrive/loggerd/logger.h +selfdrive/loggerd/loggerd.cc +selfdrive/loggerd/bootlog.cc +selfdrive/loggerd/raw_logger.cc +selfdrive/loggerd/raw_logger.h +selfdrive/loggerd/include/msm_media_info.h + +selfdrive/loggerd/__init__.py +selfdrive/loggerd/config.py +selfdrive/loggerd/uploader.py +selfdrive/loggerd/deleter.py +selfdrive/loggerd/xattr_cache.py + +selfdrive/sensord/SConscript +selfdrive/sensord/libdiag.h +selfdrive/sensord/sensors_qcom.cc +selfdrive/sensord/sensors_qcom2.cc +selfdrive/sensord/sensors/*.cc +selfdrive/sensord/sensors/*.h +selfdrive/sensord/sensord + +selfdrive/thermald/thermald.py +selfdrive/thermald/power_monitoring.py + +selfdrive/test/__init__.py +selfdrive/test/helpers.py +selfdrive/test/setup_device_ci.sh +selfdrive/test/test_fingerprints.py +selfdrive/test/test_onroad.py + +selfdrive/ui/.gitignore +selfdrive/ui/SConscript +selfdrive/ui/*.cc +selfdrive/ui/*.h +selfdrive/ui/ui +selfdrive/ui/text +selfdrive/ui/spinner +selfdrive/ui/soundd + +selfdrive/ui/qt/*.cc +selfdrive/ui/qt/*.h +selfdrive/ui/qt/offroad/*.cc +selfdrive/ui/qt/offroad/*.h +selfdrive/ui/qt/offroad/*.qml +selfdrive/ui/qt/widgets/*.cc +selfdrive/ui/qt/widgets/*.h +selfdrive/ui/qt/spinner_aarch64 +selfdrive/ui/qt/text_aarch64 + +selfdrive/camerad/SConscript +selfdrive/camerad/main.cc + +selfdrive/camerad/snapshot/* +selfdrive/camerad/include/* +selfdrive/camerad/cameras/camera_common.h +selfdrive/camerad/cameras/camera_common.cc +selfdrive/camerad/cameras/camera_frame_stream.cc +selfdrive/camerad/cameras/camera_frame_stream.h +selfdrive/camerad/cameras/camera_qcom.cc +selfdrive/camerad/cameras/camera_qcom.h +selfdrive/camerad/cameras/debayer.cl +selfdrive/camerad/cameras/sensor_i2c.h +selfdrive/camerad/cameras/sensor2_i2c.h + +selfdrive/camerad/transforms/rgb_to_yuv.cc +selfdrive/camerad/transforms/rgb_to_yuv.h +selfdrive/camerad/transforms/rgb_to_yuv.cl +selfdrive/camerad/transforms/rgb_to_yuv_test.cc + +selfdrive/camerad/imgproc/conv.cl +selfdrive/camerad/imgproc/pool.cl +selfdrive/camerad/imgproc/utils.cc +selfdrive/camerad/imgproc/utils.h + +selfdrive/manager/__init__.py +selfdrive/manager/build.py +selfdrive/manager/helpers.py +selfdrive/manager/manager.py +selfdrive/manager/process_config.py +selfdrive/manager/process.py +selfdrive/manager/test/__init__.py +selfdrive/manager/test/test_manager.py + +selfdrive/modeld/SConscript +selfdrive/modeld/modeld.cc +selfdrive/modeld/dmonitoringmodeld.cc +selfdrive/modeld/constants.py +selfdrive/modeld/modeld +selfdrive/modeld/dmonitoringmodeld + +selfdrive/modeld/models/commonmodel.cc +selfdrive/modeld/models/commonmodel.h +selfdrive/modeld/models/driving.cc +selfdrive/modeld/models/driving.h +selfdrive/modeld/models/dmonitoring.cc +selfdrive/modeld/models/dmonitoring.h + +selfdrive/modeld/transforms/loadyuv.cc +selfdrive/modeld/transforms/loadyuv.h +selfdrive/modeld/transforms/loadyuv.cl +selfdrive/modeld/transforms/transform.cc +selfdrive/modeld/transforms/transform.h +selfdrive/modeld/transforms/transform.cl + +selfdrive/modeld/thneed/thneed.* +selfdrive/modeld/thneed/serialize.cc +selfdrive/modeld/thneed/compile.cc +selfdrive/modeld/thneed/include/* + +selfdrive/modeld/runners/snpemodel.cc +selfdrive/modeld/runners/snpemodel.h +selfdrive/modeld/runners/thneedmodel.cc +selfdrive/modeld/runners/thneedmodel.h +selfdrive/modeld/runners/runmodel.h +selfdrive/modeld/runners/run.h + +selfdrive/monitoring/dmonitoringd.py +selfdrive/monitoring/driver_monitor.py + +selfdrive/assets/.gitignore +selfdrive/assets/assets.qrc +selfdrive/assets/*.png +selfdrive/assets/*.svg +selfdrive/assets/fonts/*.ttf +selfdrive/assets/images/* +selfdrive/assets/offroad/* +selfdrive/assets/sounds/* +selfdrive/assets/training/* + +phonelibs/SConscript + +phonelibs/nanovg/*.c +phonelibs/nanovg/*.h + +phonelibs/libgralloc/** +phonelibs/linux/** +phonelibs/opencl/** +phonelibs/zlib/* +phonelibs/bzip2/* +phonelibs/openmax/** + +phonelibs/json11/json11.cpp +phonelibs/json11/json11.hpp + +phonelibs/qpoases/** + +phonelibs/qrcode/*.cc +phonelibs/qrcode/*.hpp + +phonelibs/kaitai/*.h +phonelibs/kaitai/*.cpp + +phonelibs/libyuv/include/** +phonelibs/libyuv/lib/** +phonelibs/libyuv/larch64/** + +phonelibs/snpe/include/** +phonelibs/snpe/aarch64** +phonelibs/snpe/larch64** +phonelibs/snpe/dsp** + +phonelibs/android_frameworks_native/** +phonelibs/android_hardware_libhardware/** +phonelibs/android_system_core/** + +installer/updater/updater +installer/updater/updater.cc +installer/updater/update.json +installer/updater/Makefile + +scripts/update_now.sh +scripts/stop_updater.sh + +pyextra/.gitignore + +rednose/** + +cereal/.gitignore +cereal/__init__.py +cereal/car.capnp +cereal/legacy.capnp +cereal/log.capnp +cereal/services.py +cereal/SConscript +cereal/include/** +cereal/messaging/.gitignore +cereal/messaging/__init__.py +cereal/messaging/bridge.cc +cereal/messaging/impl_msgq.cc +cereal/messaging/impl_msgq.h +cereal/messaging/impl_zmq.cc +cereal/messaging/impl_zmq.h +cereal/messaging/messaging.cc +cereal/messaging/messaging.h +cereal/messaging/messaging.pxd +cereal/messaging/messaging_pyx.pyx +cereal/messaging/msgq.cc +cereal/messaging/msgq.h +cereal/messaging/socketmaster.cc +cereal/visionipc/.gitignore +cereal/visionipc/__init__.py +cereal/visionipc/*.cc +cereal/visionipc/*.h +cereal/visionipc/*.pyx +cereal/visionipc/*.pxd + +panda/.gitignore +panda/__init__.py +panda/VERSION +panda/board/** +panda/certs/** +panda/crypto/** +panda/examples/query_fw_versions.py +panda/python/** + +opendbc/.gitignore +opendbc/__init__.py +opendbc/can/__init__.py +opendbc/can/SConscript +opendbc/can/can_define.py +opendbc/can/common.cc +opendbc/can/common.h +opendbc/can/common.pxd +opendbc/can/common_dbc.h +opendbc/can/dbc.cc +opendbc/can/dbc.py +opendbc/can/dbc_template.cc +opendbc/can/packer.cc +opendbc/can/packer.py +opendbc/can/packer_pyx.pyx +opendbc/can/parser.cc +opendbc/can/parser.py +opendbc/can/parser_pyx.pyx +opendbc/can/process_dbc.py +opendbc/can/dbc_out/.gitkeep +opendbc/can/dbc_out/.gitignore + +opendbc/chrysler_pacifica_2017_hybrid.dbc +opendbc/chrysler_pacifica_2017_hybrid_private_fusion.dbc + +opendbc/gm_global_a_powertrain.dbc +opendbc/gm_global_a_object.dbc +opendbc/gm_global_a_chassis.dbc + +opendbc/ford_fusion_2018_pt.dbc +opendbc/ford_fusion_2018_adas.dbc + +opendbc/honda_accord_2018_can_generated.dbc +opendbc/acura_ilx_2016_can_generated.dbc +opendbc/acura_rdx_2018_can_generated.dbc +opendbc/acura_rdx_2020_can_generated.dbc +opendbc/honda_civic_touring_2016_can_generated.dbc +opendbc/honda_civic_hatchback_ex_2017_can_generated.dbc +opendbc/honda_civic_sedan_16_diesel_2019_can_generated.dbc +opendbc/honda_crv_touring_2016_can_generated.dbc +opendbc/honda_crv_ex_2017_can_generated.dbc +opendbc/honda_crv_ex_2017_body_generated.dbc +opendbc/honda_crv_executive_2016_can_generated.dbc +opendbc/honda_crv_hybrid_2019_can_generated.dbc +opendbc/honda_fit_ex_2018_can_generated.dbc +opendbc/honda_hrv_touring_2019_can_generated.dbc +opendbc/honda_odyssey_exl_2018_generated.dbc +opendbc/honda_odyssey_extreme_edition_2018_china_can_generated.dbc +opendbc/honda_pilot_touring_2017_can_generated.dbc +opendbc/honda_ridgeline_black_edition_2017_can_generated.dbc +opendbc/honda_insight_ex_2019_can_generated.dbc +opendbc/acura_ilx_2016_nidec.dbc + +opendbc/hyundai_kia_generic.dbc + +opendbc/mazda_2017.dbc + +opendbc/nissan_x_trail_2017.dbc +opendbc/nissan_leaf_2018.dbc + +opendbc/subaru_global_2017_generated.dbc +opendbc/subaru_outback_2015_generated.dbc +opendbc/subaru_outback_2019_generated.dbc +opendbc/subaru_forester_2017_generated.dbc + +opendbc/toyota_rav4_hybrid_2017_pt_generated.dbc +opendbc/toyota_rav4_2017_pt_generated.dbc +opendbc/toyota_prius_2017_pt_generated.dbc +opendbc/toyota_corolla_2017_pt_generated.dbc +opendbc/lexus_rx_350_2016_pt_generated.dbc +opendbc/lexus_rx_hybrid_2017_pt_generated.dbc +opendbc/toyota_nodsu_pt_generated.dbc +opendbc/toyota_nodsu_hybrid_pt_generated.dbc +opendbc/toyota_camry_hybrid_2018_pt_generated.dbc +opendbc/toyota_highlander_2017_pt_generated.dbc +opendbc/toyota_highlander_hybrid_2018_pt_generated.dbc +opendbc/toyota_avalon_2017_pt_generated.dbc +opendbc/toyota_sienna_xle_2018_pt_generated.dbc +opendbc/lexus_is_2018_pt_generated.dbc +opendbc/lexus_ct200h_2018_pt_generated.dbc +opendbc/lexus_nx300h_2018_pt_generated.dbc +opendbc/lexus_nx300_2018_pt_generated.dbc +opendbc/toyota_adas.dbc +opendbc/toyota_tss2_adas.dbc + +opendbc/vw_mqb_2010.dbc + +opendbc/tesla_can.dbc diff --git a/release/files_pc b/release/files_pc new file mode 100644 index 000000000..1b57f97a7 --- /dev/null +++ b/release/files_pc @@ -0,0 +1,3 @@ +phonelibs/mapbox-gl-native-qt/x86_64/** + +phonelibs/qt-plugins/x86_64/** diff --git a/release/files_tici b/release/files_tici new file mode 100644 index 000000000..f3037a1d4 --- /dev/null +++ b/release/files_tici @@ -0,0 +1,27 @@ +installer/continue_openpilot.sh + +phonelibs/mapbox-gl-native-qt/include/* + +selfdrive/timezoned.py + +selfdrive/assets/navigation/* +selfdrive/assets/training_wide/* +selfdrive/assets/sounds_tici/* + +selfdrive/camerad/cameras/camera_qcom2.cc +selfdrive/camerad/cameras/camera_qcom2.h +selfdrive/camerad/cameras/real_debayer.cl + +selfdrive/hardware/tici/__init__.py +selfdrive/hardware/tici/hardware.h +selfdrive/hardware/tici/hardware.py +selfdrive/hardware/tici/pins.py +selfdrive/hardware/tici/agnos.py +selfdrive/hardware/tici/agnos.json +selfdrive/hardware/tici/amplifier.py +selfdrive/hardware/tici/updater + +selfdrive/ui/qt/spinner_larch64 +selfdrive/ui/qt/text_larch64 +selfdrive/ui/qt/maps/*.cc +selfdrive/ui/qt/maps/*.h diff --git a/release/identity.sh b/release/identity.sh new file mode 100644 index 000000000..b90372d82 --- /dev/null +++ b/release/identity.sh @@ -0,0 +1,5 @@ +export GIT_COMMITTER_NAME="Vehicle Researcher" +export GIT_COMMITTER_EMAIL="user@comma.ai" +export GIT_AUTHOR_NAME="Vehicle Researcher" +export GIT_AUTHOR_EMAIL="user@comma.ai" +export GIT_SSH_COMMAND="ssh -i /data/gitkey" diff --git a/selfdrive/assets/.gitignore b/selfdrive/assets/.gitignore new file mode 100644 index 000000000..283034ca8 --- /dev/null +++ b/selfdrive/assets/.gitignore @@ -0,0 +1 @@ +*.cc diff --git a/selfdrive/assets/assets.qrc b/selfdrive/assets/assets.qrc new file mode 100644 index 000000000..1fc6d3591 --- /dev/null +++ b/selfdrive/assets/assets.qrc @@ -0,0 +1,15 @@ + + + img_continue_triangle.svg + img_circled_check.svg + img_circled_slash.svg + img_eye_open.svg + img_eye_closed.svg + offroad/icon_lock_closed.svg + offroad/icon_checkmark.svg + offroad/icon_wifi_strength_low.svg + offroad/icon_wifi_strength_medium.svg + offroad/icon_wifi_strength_high.svg + offroad/icon_wifi_strength_full.svg + + diff --git a/selfdrive/assets/img_circled_check.svg b/selfdrive/assets/img_circled_check.svg new file mode 100644 index 000000000..27c37395b --- /dev/null +++ b/selfdrive/assets/img_circled_check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/selfdrive/assets/img_circled_slash.svg b/selfdrive/assets/img_circled_slash.svg new file mode 100644 index 000000000..b10a3938d --- /dev/null +++ b/selfdrive/assets/img_circled_slash.svg @@ -0,0 +1,4 @@ + + + + diff --git a/selfdrive/assets/img_continue_triangle.svg b/selfdrive/assets/img_continue_triangle.svg new file mode 100644 index 000000000..20f9e45dc --- /dev/null +++ b/selfdrive/assets/img_continue_triangle.svg @@ -0,0 +1,3 @@ + + + diff --git a/selfdrive/assets/img_eye_closed.svg b/selfdrive/assets/img_eye_closed.svg new file mode 100644 index 000000000..91b229e91 --- /dev/null +++ b/selfdrive/assets/img_eye_closed.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/selfdrive/assets/img_eye_open.svg b/selfdrive/assets/img_eye_open.svg new file mode 100644 index 000000000..ea6e41ac5 --- /dev/null +++ b/selfdrive/assets/img_eye_open.svg @@ -0,0 +1,4 @@ + + + + diff --git a/selfdrive/assets/navigation/direction_arrive.png b/selfdrive/assets/navigation/direction_arrive.png new file mode 100644 index 000000000..733c12909 Binary files /dev/null and b/selfdrive/assets/navigation/direction_arrive.png differ diff --git a/selfdrive/assets/navigation/direction_arrive_left.png b/selfdrive/assets/navigation/direction_arrive_left.png new file mode 100644 index 000000000..92ff8e034 Binary files /dev/null and b/selfdrive/assets/navigation/direction_arrive_left.png differ diff --git a/selfdrive/assets/navigation/direction_arrive_right.png b/selfdrive/assets/navigation/direction_arrive_right.png new file mode 100644 index 000000000..f5983bfe6 Binary files /dev/null and b/selfdrive/assets/navigation/direction_arrive_right.png differ diff --git a/selfdrive/assets/navigation/direction_arrive_straight.png b/selfdrive/assets/navigation/direction_arrive_straight.png new file mode 100644 index 000000000..733c12909 Binary files /dev/null and b/selfdrive/assets/navigation/direction_arrive_straight.png differ diff --git a/selfdrive/assets/navigation/direction_close.png b/selfdrive/assets/navigation/direction_close.png new file mode 100644 index 000000000..4fdb5d195 Binary files /dev/null and b/selfdrive/assets/navigation/direction_close.png differ diff --git a/selfdrive/assets/navigation/direction_continue.png b/selfdrive/assets/navigation/direction_continue.png new file mode 100644 index 000000000..a01045ae6 Binary files /dev/null and b/selfdrive/assets/navigation/direction_continue.png differ diff --git a/selfdrive/assets/navigation/direction_continue_left.png b/selfdrive/assets/navigation/direction_continue_left.png new file mode 100644 index 000000000..9a618026f Binary files /dev/null and b/selfdrive/assets/navigation/direction_continue_left.png differ diff --git a/selfdrive/assets/navigation/direction_continue_right.png b/selfdrive/assets/navigation/direction_continue_right.png new file mode 100644 index 000000000..0fbaa3f25 Binary files /dev/null and b/selfdrive/assets/navigation/direction_continue_right.png differ diff --git a/selfdrive/assets/navigation/direction_continue_slight_left.png b/selfdrive/assets/navigation/direction_continue_slight_left.png new file mode 100644 index 000000000..08e964dbd Binary files /dev/null and b/selfdrive/assets/navigation/direction_continue_slight_left.png differ diff --git a/selfdrive/assets/navigation/direction_continue_slight_right.png b/selfdrive/assets/navigation/direction_continue_slight_right.png new file mode 100644 index 000000000..3e21cae11 Binary files /dev/null and b/selfdrive/assets/navigation/direction_continue_slight_right.png differ diff --git a/selfdrive/assets/navigation/direction_continue_straight.png b/selfdrive/assets/navigation/direction_continue_straight.png new file mode 100644 index 000000000..a01045ae6 Binary files /dev/null and b/selfdrive/assets/navigation/direction_continue_straight.png differ diff --git a/selfdrive/assets/navigation/direction_continue_uturn.png b/selfdrive/assets/navigation/direction_continue_uturn.png new file mode 100644 index 000000000..0bd1b9177 Binary files /dev/null and b/selfdrive/assets/navigation/direction_continue_uturn.png differ diff --git a/selfdrive/assets/navigation/direction_depart.png b/selfdrive/assets/navigation/direction_depart.png new file mode 100644 index 000000000..4bf32c870 Binary files /dev/null and b/selfdrive/assets/navigation/direction_depart.png differ diff --git a/selfdrive/assets/navigation/direction_depart_left.png b/selfdrive/assets/navigation/direction_depart_left.png new file mode 100644 index 000000000..1f8d72691 Binary files /dev/null and b/selfdrive/assets/navigation/direction_depart_left.png differ diff --git a/selfdrive/assets/navigation/direction_depart_right.png b/selfdrive/assets/navigation/direction_depart_right.png new file mode 100644 index 000000000..f359a685f Binary files /dev/null and b/selfdrive/assets/navigation/direction_depart_right.png differ diff --git a/selfdrive/assets/navigation/direction_depart_straight.png b/selfdrive/assets/navigation/direction_depart_straight.png new file mode 100644 index 000000000..4bf32c870 Binary files /dev/null and b/selfdrive/assets/navigation/direction_depart_straight.png differ diff --git a/selfdrive/assets/navigation/direction_end_of_road_left.png b/selfdrive/assets/navigation/direction_end_of_road_left.png new file mode 100644 index 000000000..5c0a24e7c Binary files /dev/null and b/selfdrive/assets/navigation/direction_end_of_road_left.png differ diff --git a/selfdrive/assets/navigation/direction_end_of_road_right.png b/selfdrive/assets/navigation/direction_end_of_road_right.png new file mode 100644 index 000000000..8d9b89d36 Binary files /dev/null and b/selfdrive/assets/navigation/direction_end_of_road_right.png differ diff --git a/selfdrive/assets/navigation/direction_flag.png b/selfdrive/assets/navigation/direction_flag.png new file mode 100644 index 000000000..bad12ec66 Binary files /dev/null and b/selfdrive/assets/navigation/direction_flag.png differ diff --git a/selfdrive/assets/navigation/direction_fork.png b/selfdrive/assets/navigation/direction_fork.png new file mode 100644 index 000000000..3e0c262e2 Binary files /dev/null and b/selfdrive/assets/navigation/direction_fork.png differ diff --git a/selfdrive/assets/navigation/direction_fork_left.png b/selfdrive/assets/navigation/direction_fork_left.png new file mode 100644 index 000000000..b244b42b5 Binary files /dev/null and b/selfdrive/assets/navigation/direction_fork_left.png differ diff --git a/selfdrive/assets/navigation/direction_fork_right.png b/selfdrive/assets/navigation/direction_fork_right.png new file mode 100644 index 000000000..aa3efaabc Binary files /dev/null and b/selfdrive/assets/navigation/direction_fork_right.png differ diff --git a/selfdrive/assets/navigation/direction_fork_slight_left.png b/selfdrive/assets/navigation/direction_fork_slight_left.png new file mode 100644 index 000000000..82fa59859 Binary files /dev/null and b/selfdrive/assets/navigation/direction_fork_slight_left.png differ diff --git a/selfdrive/assets/navigation/direction_fork_slight_right.png b/selfdrive/assets/navigation/direction_fork_slight_right.png new file mode 100644 index 000000000..3596a2fbf Binary files /dev/null and b/selfdrive/assets/navigation/direction_fork_slight_right.png differ diff --git a/selfdrive/assets/navigation/direction_fork_straight.png b/selfdrive/assets/navigation/direction_fork_straight.png new file mode 100644 index 000000000..86f30ab9b Binary files /dev/null and b/selfdrive/assets/navigation/direction_fork_straight.png differ diff --git a/selfdrive/assets/navigation/direction_invalid.png b/selfdrive/assets/navigation/direction_invalid.png new file mode 100644 index 000000000..a01045ae6 Binary files /dev/null and b/selfdrive/assets/navigation/direction_invalid.png differ diff --git a/selfdrive/assets/navigation/direction_invalid_left.png b/selfdrive/assets/navigation/direction_invalid_left.png new file mode 100644 index 000000000..9a618026f Binary files /dev/null and b/selfdrive/assets/navigation/direction_invalid_left.png differ diff --git a/selfdrive/assets/navigation/direction_invalid_right.png b/selfdrive/assets/navigation/direction_invalid_right.png new file mode 100644 index 000000000..0fbaa3f25 Binary files /dev/null and b/selfdrive/assets/navigation/direction_invalid_right.png differ diff --git a/selfdrive/assets/navigation/direction_invalid_slight_left.png b/selfdrive/assets/navigation/direction_invalid_slight_left.png new file mode 100644 index 000000000..08e964dbd Binary files /dev/null and b/selfdrive/assets/navigation/direction_invalid_slight_left.png differ diff --git a/selfdrive/assets/navigation/direction_invalid_slight_right.png b/selfdrive/assets/navigation/direction_invalid_slight_right.png new file mode 100644 index 000000000..3e21cae11 Binary files /dev/null and b/selfdrive/assets/navigation/direction_invalid_slight_right.png differ diff --git a/selfdrive/assets/navigation/direction_invalid_straight.png b/selfdrive/assets/navigation/direction_invalid_straight.png new file mode 100644 index 000000000..a01045ae6 Binary files /dev/null and b/selfdrive/assets/navigation/direction_invalid_straight.png differ diff --git a/selfdrive/assets/navigation/direction_invalid_uturn.png b/selfdrive/assets/navigation/direction_invalid_uturn.png new file mode 100644 index 000000000..0bd1b9177 Binary files /dev/null and b/selfdrive/assets/navigation/direction_invalid_uturn.png differ diff --git a/selfdrive/assets/navigation/direction_merge_left.png b/selfdrive/assets/navigation/direction_merge_left.png new file mode 100644 index 000000000..a713f52c5 Binary files /dev/null and b/selfdrive/assets/navigation/direction_merge_left.png differ diff --git a/selfdrive/assets/navigation/direction_merge_right.png b/selfdrive/assets/navigation/direction_merge_right.png new file mode 100644 index 000000000..3390b31a0 Binary files /dev/null and b/selfdrive/assets/navigation/direction_merge_right.png differ diff --git a/selfdrive/assets/navigation/direction_merge_slight_left.png b/selfdrive/assets/navigation/direction_merge_slight_left.png new file mode 100644 index 000000000..308f97b5a Binary files /dev/null and b/selfdrive/assets/navigation/direction_merge_slight_left.png differ diff --git a/selfdrive/assets/navigation/direction_merge_slight_right.png b/selfdrive/assets/navigation/direction_merge_slight_right.png new file mode 100644 index 000000000..8f5289011 Binary files /dev/null and b/selfdrive/assets/navigation/direction_merge_slight_right.png differ diff --git a/selfdrive/assets/navigation/direction_merge_straight.png b/selfdrive/assets/navigation/direction_merge_straight.png new file mode 100644 index 000000000..49c464389 Binary files /dev/null and b/selfdrive/assets/navigation/direction_merge_straight.png differ diff --git a/selfdrive/assets/navigation/direction_new_name_left.png b/selfdrive/assets/navigation/direction_new_name_left.png new file mode 100644 index 000000000..9a618026f Binary files /dev/null and b/selfdrive/assets/navigation/direction_new_name_left.png differ diff --git a/selfdrive/assets/navigation/direction_new_name_right.png b/selfdrive/assets/navigation/direction_new_name_right.png new file mode 100644 index 000000000..0fbaa3f25 Binary files /dev/null and b/selfdrive/assets/navigation/direction_new_name_right.png differ diff --git a/selfdrive/assets/navigation/direction_new_name_sharp_left.png b/selfdrive/assets/navigation/direction_new_name_sharp_left.png new file mode 100644 index 000000000..77106b493 Binary files /dev/null and b/selfdrive/assets/navigation/direction_new_name_sharp_left.png differ diff --git a/selfdrive/assets/navigation/direction_new_name_sharp_right.png b/selfdrive/assets/navigation/direction_new_name_sharp_right.png new file mode 100644 index 000000000..eb3a02f8b Binary files /dev/null and b/selfdrive/assets/navigation/direction_new_name_sharp_right.png differ diff --git a/selfdrive/assets/navigation/direction_new_name_slight_left.png b/selfdrive/assets/navigation/direction_new_name_slight_left.png new file mode 100644 index 000000000..08e964dbd Binary files /dev/null and b/selfdrive/assets/navigation/direction_new_name_slight_left.png differ diff --git a/selfdrive/assets/navigation/direction_new_name_slight_right.png b/selfdrive/assets/navigation/direction_new_name_slight_right.png new file mode 100644 index 000000000..3e21cae11 Binary files /dev/null and b/selfdrive/assets/navigation/direction_new_name_slight_right.png differ diff --git a/selfdrive/assets/navigation/direction_new_name_straight.png b/selfdrive/assets/navigation/direction_new_name_straight.png new file mode 100644 index 000000000..a01045ae6 Binary files /dev/null and b/selfdrive/assets/navigation/direction_new_name_straight.png differ diff --git a/selfdrive/assets/navigation/direction_notificaiton_right.png b/selfdrive/assets/navigation/direction_notificaiton_right.png new file mode 100644 index 000000000..0fbaa3f25 Binary files /dev/null and b/selfdrive/assets/navigation/direction_notificaiton_right.png differ diff --git a/selfdrive/assets/navigation/direction_notificaiton_sharp_right.png b/selfdrive/assets/navigation/direction_notificaiton_sharp_right.png new file mode 100644 index 000000000..a7e3c4cee Binary files /dev/null and b/selfdrive/assets/navigation/direction_notificaiton_sharp_right.png differ diff --git a/selfdrive/assets/navigation/direction_notification_left.png b/selfdrive/assets/navigation/direction_notification_left.png new file mode 100644 index 000000000..9a618026f Binary files /dev/null and b/selfdrive/assets/navigation/direction_notification_left.png differ diff --git a/selfdrive/assets/navigation/direction_notification_sharp_left.png b/selfdrive/assets/navigation/direction_notification_sharp_left.png new file mode 100644 index 000000000..dd8a4301d Binary files /dev/null and b/selfdrive/assets/navigation/direction_notification_sharp_left.png differ diff --git a/selfdrive/assets/navigation/direction_notification_slight_left.png b/selfdrive/assets/navigation/direction_notification_slight_left.png new file mode 100644 index 000000000..08e964dbd Binary files /dev/null and b/selfdrive/assets/navigation/direction_notification_slight_left.png differ diff --git a/selfdrive/assets/navigation/direction_notification_slight_right.png b/selfdrive/assets/navigation/direction_notification_slight_right.png new file mode 100644 index 000000000..3e21cae11 Binary files /dev/null and b/selfdrive/assets/navigation/direction_notification_slight_right.png differ diff --git a/selfdrive/assets/navigation/direction_notification_straight.png b/selfdrive/assets/navigation/direction_notification_straight.png new file mode 100644 index 000000000..a01045ae6 Binary files /dev/null and b/selfdrive/assets/navigation/direction_notification_straight.png differ diff --git a/selfdrive/assets/navigation/direction_off_ramp_left.png b/selfdrive/assets/navigation/direction_off_ramp_left.png new file mode 100644 index 000000000..d3fd18289 Binary files /dev/null and b/selfdrive/assets/navigation/direction_off_ramp_left.png differ diff --git a/selfdrive/assets/navigation/direction_off_ramp_right.png b/selfdrive/assets/navigation/direction_off_ramp_right.png new file mode 100644 index 000000000..722e3f808 Binary files /dev/null and b/selfdrive/assets/navigation/direction_off_ramp_right.png differ diff --git a/selfdrive/assets/navigation/direction_off_ramp_slight_left.png b/selfdrive/assets/navigation/direction_off_ramp_slight_left.png new file mode 100644 index 000000000..ddac4aad6 Binary files /dev/null and b/selfdrive/assets/navigation/direction_off_ramp_slight_left.png differ diff --git a/selfdrive/assets/navigation/direction_off_ramp_slight_right.png b/selfdrive/assets/navigation/direction_off_ramp_slight_right.png new file mode 100644 index 000000000..ed5760886 Binary files /dev/null and b/selfdrive/assets/navigation/direction_off_ramp_slight_right.png differ diff --git a/selfdrive/assets/navigation/direction_on_ramp_left.png b/selfdrive/assets/navigation/direction_on_ramp_left.png new file mode 100644 index 000000000..9a618026f Binary files /dev/null and b/selfdrive/assets/navigation/direction_on_ramp_left.png differ diff --git a/selfdrive/assets/navigation/direction_on_ramp_right.png b/selfdrive/assets/navigation/direction_on_ramp_right.png new file mode 100644 index 000000000..0fbaa3f25 Binary files /dev/null and b/selfdrive/assets/navigation/direction_on_ramp_right.png differ diff --git a/selfdrive/assets/navigation/direction_on_ramp_sharp_left.png b/selfdrive/assets/navigation/direction_on_ramp_sharp_left.png new file mode 100644 index 000000000..77106b493 Binary files /dev/null and b/selfdrive/assets/navigation/direction_on_ramp_sharp_left.png differ diff --git a/selfdrive/assets/navigation/direction_on_ramp_sharp_right.png b/selfdrive/assets/navigation/direction_on_ramp_sharp_right.png new file mode 100644 index 000000000..a7e3c4cee Binary files /dev/null and b/selfdrive/assets/navigation/direction_on_ramp_sharp_right.png differ diff --git a/selfdrive/assets/navigation/direction_on_ramp_slight_left.png b/selfdrive/assets/navigation/direction_on_ramp_slight_left.png new file mode 100644 index 000000000..a5ea8a881 Binary files /dev/null and b/selfdrive/assets/navigation/direction_on_ramp_slight_left.png differ diff --git a/selfdrive/assets/navigation/direction_on_ramp_slight_right.png b/selfdrive/assets/navigation/direction_on_ramp_slight_right.png new file mode 100644 index 000000000..f8ea3800e Binary files /dev/null and b/selfdrive/assets/navigation/direction_on_ramp_slight_right.png differ diff --git a/selfdrive/assets/navigation/direction_on_ramp_straight.png b/selfdrive/assets/navigation/direction_on_ramp_straight.png new file mode 100644 index 000000000..a01045ae6 Binary files /dev/null and b/selfdrive/assets/navigation/direction_on_ramp_straight.png differ diff --git a/selfdrive/assets/navigation/direction_rotary.png b/selfdrive/assets/navigation/direction_rotary.png new file mode 100644 index 000000000..2a5d264bd Binary files /dev/null and b/selfdrive/assets/navigation/direction_rotary.png differ diff --git a/selfdrive/assets/navigation/direction_rotary_left.png b/selfdrive/assets/navigation/direction_rotary_left.png new file mode 100644 index 000000000..0c4e4ab5e Binary files /dev/null and b/selfdrive/assets/navigation/direction_rotary_left.png differ diff --git a/selfdrive/assets/navigation/direction_rotary_right.png b/selfdrive/assets/navigation/direction_rotary_right.png new file mode 100644 index 000000000..32a6b2504 Binary files /dev/null and b/selfdrive/assets/navigation/direction_rotary_right.png differ diff --git a/selfdrive/assets/navigation/direction_rotary_sharp_left.png b/selfdrive/assets/navigation/direction_rotary_sharp_left.png new file mode 100644 index 000000000..c84a6d96c Binary files /dev/null and b/selfdrive/assets/navigation/direction_rotary_sharp_left.png differ diff --git a/selfdrive/assets/navigation/direction_rotary_sharp_right.png b/selfdrive/assets/navigation/direction_rotary_sharp_right.png new file mode 100644 index 000000000..d15cbee00 Binary files /dev/null and b/selfdrive/assets/navigation/direction_rotary_sharp_right.png differ diff --git a/selfdrive/assets/navigation/direction_rotary_slight_left.png b/selfdrive/assets/navigation/direction_rotary_slight_left.png new file mode 100644 index 000000000..3838e720a Binary files /dev/null and b/selfdrive/assets/navigation/direction_rotary_slight_left.png differ diff --git a/selfdrive/assets/navigation/direction_rotary_slight_right.png b/selfdrive/assets/navigation/direction_rotary_slight_right.png new file mode 100644 index 000000000..8cd45fe61 Binary files /dev/null and b/selfdrive/assets/navigation/direction_rotary_slight_right.png differ diff --git a/selfdrive/assets/navigation/direction_rotary_straight.png b/selfdrive/assets/navigation/direction_rotary_straight.png new file mode 100644 index 000000000..b6b0a7311 Binary files /dev/null and b/selfdrive/assets/navigation/direction_rotary_straight.png differ diff --git a/selfdrive/assets/navigation/direction_roundabout.png b/selfdrive/assets/navigation/direction_roundabout.png new file mode 100644 index 000000000..2a5d264bd Binary files /dev/null and b/selfdrive/assets/navigation/direction_roundabout.png differ diff --git a/selfdrive/assets/navigation/direction_roundabout_left.png b/selfdrive/assets/navigation/direction_roundabout_left.png new file mode 100644 index 000000000..0c4e4ab5e Binary files /dev/null and b/selfdrive/assets/navigation/direction_roundabout_left.png differ diff --git a/selfdrive/assets/navigation/direction_roundabout_right.png b/selfdrive/assets/navigation/direction_roundabout_right.png new file mode 100644 index 000000000..32a6b2504 Binary files /dev/null and b/selfdrive/assets/navigation/direction_roundabout_right.png differ diff --git a/selfdrive/assets/navigation/direction_roundabout_sharp_left.png b/selfdrive/assets/navigation/direction_roundabout_sharp_left.png new file mode 100644 index 000000000..1e8cce8c8 Binary files /dev/null and b/selfdrive/assets/navigation/direction_roundabout_sharp_left.png differ diff --git a/selfdrive/assets/navigation/direction_roundabout_sharp_right.png b/selfdrive/assets/navigation/direction_roundabout_sharp_right.png new file mode 100644 index 000000000..d15cbee00 Binary files /dev/null and b/selfdrive/assets/navigation/direction_roundabout_sharp_right.png differ diff --git a/selfdrive/assets/navigation/direction_roundabout_slight_left.png b/selfdrive/assets/navigation/direction_roundabout_slight_left.png new file mode 100644 index 000000000..da1b11270 Binary files /dev/null and b/selfdrive/assets/navigation/direction_roundabout_slight_left.png differ diff --git a/selfdrive/assets/navigation/direction_roundabout_slight_right.png b/selfdrive/assets/navigation/direction_roundabout_slight_right.png new file mode 100644 index 000000000..8cd45fe61 Binary files /dev/null and b/selfdrive/assets/navigation/direction_roundabout_slight_right.png differ diff --git a/selfdrive/assets/navigation/direction_roundabout_straight.png b/selfdrive/assets/navigation/direction_roundabout_straight.png new file mode 100644 index 000000000..b6b0a7311 Binary files /dev/null and b/selfdrive/assets/navigation/direction_roundabout_straight.png differ diff --git a/selfdrive/assets/navigation/direction_turn_left.png b/selfdrive/assets/navigation/direction_turn_left.png new file mode 100644 index 000000000..9a618026f Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_left.png differ diff --git a/selfdrive/assets/navigation/direction_turn_right.png b/selfdrive/assets/navigation/direction_turn_right.png new file mode 100644 index 000000000..0fbaa3f25 Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_right.png differ diff --git a/selfdrive/assets/navigation/direction_turn_sharp_left.png b/selfdrive/assets/navigation/direction_turn_sharp_left.png new file mode 100644 index 000000000..dd8a4301d Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_sharp_left.png differ diff --git a/selfdrive/assets/navigation/direction_turn_sharp_right.png b/selfdrive/assets/navigation/direction_turn_sharp_right.png new file mode 100644 index 000000000..a7e3c4cee Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_sharp_right.png differ diff --git a/selfdrive/assets/navigation/direction_turn_slight_left.png b/selfdrive/assets/navigation/direction_turn_slight_left.png new file mode 100644 index 000000000..08e964dbd Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_slight_left.png differ diff --git a/selfdrive/assets/navigation/direction_turn_slight_right.png b/selfdrive/assets/navigation/direction_turn_slight_right.png new file mode 100644 index 000000000..3e21cae11 Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_slight_right.png differ diff --git a/selfdrive/assets/navigation/direction_turn_straight.png b/selfdrive/assets/navigation/direction_turn_straight.png new file mode 100644 index 000000000..a01045ae6 Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_straight.png differ diff --git a/selfdrive/assets/navigation/direction_turn_uturn.png b/selfdrive/assets/navigation/direction_turn_uturn.png new file mode 100644 index 000000000..0bd1b9177 Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_uturn.png differ diff --git a/selfdrive/assets/navigation/direction_updown.png b/selfdrive/assets/navigation/direction_updown.png new file mode 100644 index 000000000..16d0979f3 Binary files /dev/null and b/selfdrive/assets/navigation/direction_updown.png differ diff --git a/selfdrive/assets/navigation/home.png b/selfdrive/assets/navigation/home.png new file mode 100644 index 000000000..8a4f65c7d Binary files /dev/null and b/selfdrive/assets/navigation/home.png differ diff --git a/selfdrive/assets/navigation/home.svg b/selfdrive/assets/navigation/home.svg new file mode 100644 index 000000000..f5d89514c --- /dev/null +++ b/selfdrive/assets/navigation/home.svg @@ -0,0 +1,65 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/selfdrive/assets/navigation/home_inactive.png b/selfdrive/assets/navigation/home_inactive.png new file mode 100644 index 000000000..a58fd3864 Binary files /dev/null and b/selfdrive/assets/navigation/home_inactive.png differ diff --git a/selfdrive/assets/navigation/screenshot.png b/selfdrive/assets/navigation/screenshot.png new file mode 100644 index 000000000..9360452a8 Binary files /dev/null and b/selfdrive/assets/navigation/screenshot.png differ diff --git a/selfdrive/assets/navigation/work.png b/selfdrive/assets/navigation/work.png new file mode 100644 index 000000000..611f9b038 Binary files /dev/null and b/selfdrive/assets/navigation/work.png differ diff --git a/selfdrive/assets/navigation/work.svg b/selfdrive/assets/navigation/work.svg new file mode 100644 index 000000000..2da7bb7d3 --- /dev/null +++ b/selfdrive/assets/navigation/work.svg @@ -0,0 +1,66 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/selfdrive/assets/navigation/work_inactive.png b/selfdrive/assets/navigation/work_inactive.png new file mode 100644 index 000000000..679e6a54b Binary files /dev/null and b/selfdrive/assets/navigation/work_inactive.png differ diff --git a/selfdrive/assets/offroad/fcc.html b/selfdrive/assets/offroad/fcc.html new file mode 100644 index 000000000..7f03a7204 --- /dev/null +++ b/selfdrive/assets/offroad/fcc.html @@ -0,0 +1,56 @@ + + +

Supplier's Declaration of Conformity: 47 CFR § 2.1077 Compliance Information

+ +

Unique Identifier

+

comma three

+ +

Authorized Components

+ +
Thundersoft TurboX D845 SOM
+

FCC ID: 2AOHHTURBOXSOMD845

+ +
Quectel/EG25-G
+

FCC ID: XMR201903EG25GM

+

+ This device complies with Part 15 of the FCC Rules. + Operation is subject to the following two conditions: + +

(1) this device may not cause harmful interference, and +

(2) this device must accept any interference received, including interference that may cause undesired operation.

+ + The following test reports are subject to this declaration: + Test report number: HR20191001605 + Issue date: 2019-2-21 + + The following manufacturer/importer/entity (located in the USA) is responsible for this declaration: + Company name: Quectel Wireless Solutions Co., Ltd. + Name/Title (legal representative): Yin JiXiong + Address: 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District, Shanghai 200233, China + Phone: +8602150086326 Extension: 800 + Fax: +862153253668 + E-mail: johnny.xiang@quectel.com +

+ +

Responsible Party - U.S. Contact Information

+

comma.ai

+

501 W Broadway St

+

STE A #403

+

San Diego, California

+

92101

+

United States

+

support@comma.ai

+ +

FCC Compliance Statement

+

This device complies with part 15 of the FCC Rules. Operation is subject to the following two conditions: (1) This device may not cause harmful interference, and (2) this device must accept any interference received, including interference that may cause undesired operation. Note: This equipment has been tested and found to comply with the limits for a Class B digital device, pursuant to part 15 of the FCC Rules. These limits are designed to provide reasonable protection against harmful interference in a residential installation.

+

This equipment generates, uses, and can radiate radio frequency energy and, if not installed and used in accordance with the instructions, may cause harmful interference to radio communications. However, there is no guarantee that interference will not occur in a particular installation.

+

If this equipment does cause harmful interference to radio or television reception, which can be determined by turning the equipment off and on, the user is encouraged to try to correct the interference by one or more of the following measures:

+

Reorient or relocate the receiving antenna.

+

Increase the separation between the equipment and receiver.

+

Connect the equipment to an outlet on a circuit different from that to which the receiver is connected.

+

Consult the dealer or an experienced radio/TV technician for help.

+

Changes or modifications to this product not expressly approved by the party responsible for compliance could void the electromagnetic compatibility (EMC) and wireless compliance and negate your authority to operate the product.

+

This product has demonstrated EMC compliance under conditions that included the use of compliant peripheral devices and shielded cables between system components. It is important that you use compliant peripheral devices and shielded cables between system components to reduce the possibility of causing interference to radios, televisions, and other electronic devices.

+

The radiated output power of this device meets the limits of FCC/IC radio frequency exposure limits. This device should be operated with a minimum separation distance of 20 cm (8 inches) between the equipment and a person's body.

+ + diff --git a/selfdrive/assets/offroad/icon_checkmark.png b/selfdrive/assets/offroad/icon_checkmark.png deleted file mode 100644 index 06efdfb0c..000000000 Binary files a/selfdrive/assets/offroad/icon_checkmark.png and /dev/null differ diff --git a/selfdrive/assets/offroad/icon_checkmark.svg b/selfdrive/assets/offroad/icon_checkmark.svg new file mode 100644 index 000000000..b024eccd9 --- /dev/null +++ b/selfdrive/assets/offroad/icon_checkmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/selfdrive/assets/offroad/icon_lock_closed.svg b/selfdrive/assets/offroad/icon_lock_closed.svg index b78709740..7dc9283c8 100644 --- a/selfdrive/assets/offroad/icon_lock_closed.svg +++ b/selfdrive/assets/offroad/icon_lock_closed.svg @@ -1,4 +1,4 @@ - - + + + - diff --git a/selfdrive/assets/offroad/icon_wifi_strength_full.svg b/selfdrive/assets/offroad/icon_wifi_strength_full.svg new file mode 100644 index 000000000..758198e97 --- /dev/null +++ b/selfdrive/assets/offroad/icon_wifi_strength_full.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/selfdrive/assets/offroad/icon_wifi_strength_high.svg b/selfdrive/assets/offroad/icon_wifi_strength_high.svg new file mode 100644 index 000000000..a8db07f91 --- /dev/null +++ b/selfdrive/assets/offroad/icon_wifi_strength_high.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/selfdrive/assets/offroad/icon_wifi_strength_low.svg b/selfdrive/assets/offroad/icon_wifi_strength_low.svg new file mode 100644 index 000000000..8963c3dbc --- /dev/null +++ b/selfdrive/assets/offroad/icon_wifi_strength_low.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/selfdrive/assets/offroad/icon_wifi_strength_medium.svg b/selfdrive/assets/offroad/icon_wifi_strength_medium.svg new file mode 100644 index 000000000..8f8d50326 --- /dev/null +++ b/selfdrive/assets/offroad/icon_wifi_strength_medium.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/selfdrive/assets/offroad/tc.html b/selfdrive/assets/offroad/tc.html index 70d08906f..f88daf08f 100644 --- a/selfdrive/assets/offroad/tc.html +++ b/selfdrive/assets/offroad/tc.html @@ -17,7 +17,7 @@

Before using and by accessing openpilot, you indicate that you have read, understood, and agree to these Terms. These Terms apply to all users and others who access or use openpilot. If others use openpilot through your user account or vehicle, you are responsible to ensure that they only use openpilot when it is safe to do so, and in compliance with these Terms and with applicable law. If you disagree with any part of the Terms, you should not access or use openpilot.

Communications

You agree that comma may contact you by email or telephone in connection with openpilot or for other business purposes. You may opt out of receiving email messages at any time by contacting us at support@comma.ai.

-

We collect, use, and share information from and about you and your vehicle in connection with openpilot. You consent to comma accessing the systems associated with openpilot, without additional notice or consent, for the purposes of providing openpilot, data collection, software updates, safety and cybersecurity, suspension or removal of your account, and as disclosed in the Privacy Policy (available at https://my.comma.ai/privacy).

+

We collect, use, and share information from and about you and your vehicle in connection with openpilot. You consent to comma accessing the systems associated with openpilot, without additional notice or consent, for the purposes of providing openpilot, data collection, software updates, safety and cybersecurity, suspension or removal of your account, and as disclosed in the Privacy Policy (available at https://connect.comma.ai/privacy).

Safety

openpilot performs the functions of Adaptive Cruise Control (ACC) and Lane Keeping Assist System (LKAS) designed for use in compatible motor vehicles. While using openpilot, it is your responsibility to obey all laws, traffic rules, and traffic regulations governing your vehicle and its operation. Access to and use of openpilot is at your own risk and responsibility, and openpilot should be accessed and/or used only when you can do so safely.

openpilot does not make your vehicle “autonomous” or capable of operation without the active monitoring of a licensed driver. It is designed to assist a licensed driver. A licensed driver must pay attention to the road, remain aware of navigation at all times, and be prepared to take immediate action. Failure to do so can cause damage, injury, or death.

diff --git a/selfdrive/assets/sounds_tici/disengaged.wav b/selfdrive/assets/sounds_tici/disengaged.wav new file mode 100644 index 000000000..182adffa2 Binary files /dev/null and b/selfdrive/assets/sounds_tici/disengaged.wav differ diff --git a/selfdrive/assets/sounds_tici/engaged.wav b/selfdrive/assets/sounds_tici/engaged.wav new file mode 100644 index 000000000..5c4c8b4d1 Binary files /dev/null and b/selfdrive/assets/sounds_tici/engaged.wav differ diff --git a/selfdrive/assets/sounds_tici/error.wav b/selfdrive/assets/sounds_tici/error.wav new file mode 100644 index 000000000..c4e280e9a Binary files /dev/null and b/selfdrive/assets/sounds_tici/error.wav differ diff --git a/selfdrive/assets/sounds_tici/warning_1.wav b/selfdrive/assets/sounds_tici/warning_1.wav new file mode 100644 index 000000000..c937b3be0 Binary files /dev/null and b/selfdrive/assets/sounds_tici/warning_1.wav differ diff --git a/selfdrive/assets/sounds_tici/warning_2.wav b/selfdrive/assets/sounds_tici/warning_2.wav new file mode 100644 index 000000000..49188db88 Binary files /dev/null and b/selfdrive/assets/sounds_tici/warning_2.wav differ diff --git a/selfdrive/assets/sounds_tici/warning_repeat.wav b/selfdrive/assets/sounds_tici/warning_repeat.wav new file mode 100644 index 000000000..bb3bfd0d5 Binary files /dev/null and b/selfdrive/assets/sounds_tici/warning_repeat.wav differ diff --git a/selfdrive/assets/training/step0.png b/selfdrive/assets/training/step0.png index c5401136e..8ea90610d 100644 Binary files a/selfdrive/assets/training/step0.png and b/selfdrive/assets/training/step0.png differ diff --git a/selfdrive/assets/training/step1.png b/selfdrive/assets/training/step1.png index 5976ce034..22a629bea 100644 Binary files a/selfdrive/assets/training/step1.png and b/selfdrive/assets/training/step1.png differ diff --git a/selfdrive/assets/training/step10.png b/selfdrive/assets/training/step10.png index 7c9c78130..96390aa32 100644 Binary files a/selfdrive/assets/training/step10.png and b/selfdrive/assets/training/step10.png differ diff --git a/selfdrive/assets/training/step11.png b/selfdrive/assets/training/step11.png index cdfd0fad1..94342a4b5 100644 Binary files a/selfdrive/assets/training/step11.png and b/selfdrive/assets/training/step11.png differ diff --git a/selfdrive/assets/training/step12.png b/selfdrive/assets/training/step12.png index 1196e0736..8b2b01af2 100644 Binary files a/selfdrive/assets/training/step12.png and b/selfdrive/assets/training/step12.png differ diff --git a/selfdrive/assets/training/step13.png b/selfdrive/assets/training/step13.png index eff7ed2e9..6593b93d7 100644 Binary files a/selfdrive/assets/training/step13.png and b/selfdrive/assets/training/step13.png differ diff --git a/selfdrive/assets/training/step14.png b/selfdrive/assets/training/step14.png index f1975cc0b..1cb8fb992 100644 Binary files a/selfdrive/assets/training/step14.png and b/selfdrive/assets/training/step14.png differ diff --git a/selfdrive/assets/training/step15.png b/selfdrive/assets/training/step15.png index 3479764e2..b4c0841b7 100644 Binary files a/selfdrive/assets/training/step15.png and b/selfdrive/assets/training/step15.png differ diff --git a/selfdrive/assets/training/step16.png b/selfdrive/assets/training/step16.png index a1f2ac263..d8518b10b 100644 Binary files a/selfdrive/assets/training/step16.png and b/selfdrive/assets/training/step16.png differ diff --git a/selfdrive/assets/training/step17.png b/selfdrive/assets/training/step17.png index b35a17374..a9ab91269 100644 Binary files a/selfdrive/assets/training/step17.png and b/selfdrive/assets/training/step17.png differ diff --git a/selfdrive/assets/training/step18.png b/selfdrive/assets/training/step18.png new file mode 100644 index 000000000..a5d8833a3 Binary files /dev/null and b/selfdrive/assets/training/step18.png differ diff --git a/selfdrive/assets/training/step2.png b/selfdrive/assets/training/step2.png index 58003315d..03a45f8de 100644 Binary files a/selfdrive/assets/training/step2.png and b/selfdrive/assets/training/step2.png differ diff --git a/selfdrive/assets/training/step3.png b/selfdrive/assets/training/step3.png index e31044b85..0712678ac 100644 Binary files a/selfdrive/assets/training/step3.png and b/selfdrive/assets/training/step3.png differ diff --git a/selfdrive/assets/training/step4.png b/selfdrive/assets/training/step4.png index 57e5e234f..60a99310f 100644 Binary files a/selfdrive/assets/training/step4.png and b/selfdrive/assets/training/step4.png differ diff --git a/selfdrive/assets/training/step5.png b/selfdrive/assets/training/step5.png index af9fbd58c..54aa049dd 100644 Binary files a/selfdrive/assets/training/step5.png and b/selfdrive/assets/training/step5.png differ diff --git a/selfdrive/assets/training/step6.png b/selfdrive/assets/training/step6.png index c8ca6b526..80cbb0a55 100644 Binary files a/selfdrive/assets/training/step6.png and b/selfdrive/assets/training/step6.png differ diff --git a/selfdrive/assets/training/step7.png b/selfdrive/assets/training/step7.png index 28af33b91..e5e403df2 100644 Binary files a/selfdrive/assets/training/step7.png and b/selfdrive/assets/training/step7.png differ diff --git a/selfdrive/assets/training/step8.png b/selfdrive/assets/training/step8.png index 615a80922..d5193ae33 100644 Binary files a/selfdrive/assets/training/step8.png and b/selfdrive/assets/training/step8.png differ diff --git a/selfdrive/assets/training/step9.png b/selfdrive/assets/training/step9.png index f8ed28da5..10dadc2ae 100644 Binary files a/selfdrive/assets/training/step9.png and b/selfdrive/assets/training/step9.png differ diff --git a/selfdrive/assets/training_wide/step0.png b/selfdrive/assets/training_wide/step0.png new file mode 100644 index 000000000..cff6d4f88 Binary files /dev/null and b/selfdrive/assets/training_wide/step0.png differ diff --git a/selfdrive/assets/training_wide/step1.png b/selfdrive/assets/training_wide/step1.png new file mode 100644 index 000000000..e81e2ce80 Binary files /dev/null and b/selfdrive/assets/training_wide/step1.png differ diff --git a/selfdrive/assets/training_wide/step10.png b/selfdrive/assets/training_wide/step10.png new file mode 100644 index 000000000..c595f602a Binary files /dev/null and b/selfdrive/assets/training_wide/step10.png differ diff --git a/selfdrive/assets/training_wide/step11.png b/selfdrive/assets/training_wide/step11.png new file mode 100644 index 000000000..26680b902 Binary files /dev/null and b/selfdrive/assets/training_wide/step11.png differ diff --git a/selfdrive/assets/training_wide/step12.png b/selfdrive/assets/training_wide/step12.png new file mode 100644 index 000000000..c93ea6b25 Binary files /dev/null and b/selfdrive/assets/training_wide/step12.png differ diff --git a/selfdrive/assets/training_wide/step13.png b/selfdrive/assets/training_wide/step13.png new file mode 100644 index 000000000..103b6db97 Binary files /dev/null and b/selfdrive/assets/training_wide/step13.png differ diff --git a/selfdrive/assets/training_wide/step14.png b/selfdrive/assets/training_wide/step14.png new file mode 100644 index 000000000..cf4f35b72 Binary files /dev/null and b/selfdrive/assets/training_wide/step14.png differ diff --git a/selfdrive/assets/training_wide/step15.png b/selfdrive/assets/training_wide/step15.png new file mode 100644 index 000000000..29999f0ad Binary files /dev/null and b/selfdrive/assets/training_wide/step15.png differ diff --git a/selfdrive/assets/training_wide/step16.png b/selfdrive/assets/training_wide/step16.png new file mode 100644 index 000000000..601ff4eb0 Binary files /dev/null and b/selfdrive/assets/training_wide/step16.png differ diff --git a/selfdrive/assets/training_wide/step17.png b/selfdrive/assets/training_wide/step17.png new file mode 100644 index 000000000..d110451af Binary files /dev/null and b/selfdrive/assets/training_wide/step17.png differ diff --git a/selfdrive/assets/training_wide/step18.png b/selfdrive/assets/training_wide/step18.png new file mode 100644 index 000000000..c1ce4ec1e Binary files /dev/null and b/selfdrive/assets/training_wide/step18.png differ diff --git a/selfdrive/assets/training_wide/step2.png b/selfdrive/assets/training_wide/step2.png new file mode 100644 index 000000000..ac03677ef Binary files /dev/null and b/selfdrive/assets/training_wide/step2.png differ diff --git a/selfdrive/assets/training_wide/step3.png b/selfdrive/assets/training_wide/step3.png new file mode 100644 index 000000000..70649b82b Binary files /dev/null and b/selfdrive/assets/training_wide/step3.png differ diff --git a/selfdrive/assets/training_wide/step4.png b/selfdrive/assets/training_wide/step4.png new file mode 100644 index 000000000..3f393ca79 Binary files /dev/null and b/selfdrive/assets/training_wide/step4.png differ diff --git a/selfdrive/assets/training_wide/step5.png b/selfdrive/assets/training_wide/step5.png new file mode 100644 index 000000000..a26ecbf1e Binary files /dev/null and b/selfdrive/assets/training_wide/step5.png differ diff --git a/selfdrive/assets/training_wide/step6.png b/selfdrive/assets/training_wide/step6.png new file mode 100644 index 000000000..bb1b11602 Binary files /dev/null and b/selfdrive/assets/training_wide/step6.png differ diff --git a/selfdrive/assets/training_wide/step7.png b/selfdrive/assets/training_wide/step7.png new file mode 100644 index 000000000..8a3d93081 Binary files /dev/null and b/selfdrive/assets/training_wide/step7.png differ diff --git a/selfdrive/assets/training_wide/step8.png b/selfdrive/assets/training_wide/step8.png new file mode 100644 index 000000000..c559d6a6c Binary files /dev/null and b/selfdrive/assets/training_wide/step8.png differ diff --git a/selfdrive/assets/training_wide/step9.png b/selfdrive/assets/training_wide/step9.png new file mode 100644 index 000000000..19b2d483d Binary files /dev/null and b/selfdrive/assets/training_wide/step9.png differ diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 720fd8926..86751fad8 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -29,7 +29,7 @@ from selfdrive.hardware import HARDWARE, PC, TICI from selfdrive.loggerd.config import ROOT from selfdrive.loggerd.xattr_cache import getxattr, setxattr from selfdrive.swaglog import cloudlog, SWAGLOG_DIR -from selfdrive.version import get_version, get_git_remote, get_git_branch, get_git_commit +from selfdrive.version import version, get_version, get_git_remote, get_git_branch, get_git_commit ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai') HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) @@ -39,6 +39,9 @@ LOG_ATTR_NAME = 'user.upload' LOG_ATTR_VALUE_MAX_UNIX_TIME = int.to_bytes(2147483647, 4, sys.byteorder) RECONNECT_TIMEOUT_S = 70 +RETRY_DELAY = 10 # seconds +MAX_RETRY_COUNT = 30 # Try for at most 5 minutes if upload fails immediately + dispatcher["echo"] = lambda s: s recv_queue: Any = queue.Queue() send_queue: Any = queue.Queue() @@ -46,7 +49,7 @@ upload_queue: Any = queue.Queue() log_send_queue: Any = queue.Queue() log_recv_queue: Any = queue.Queue() cancelled_uploads: Any = set() -UploadItem = namedtuple('UploadItem', ['path', 'url', 'headers', 'created_at', 'id']) +UploadItem = namedtuple('UploadItem', ['path', 'url', 'headers', 'created_at', 'id', 'retry_count'], defaults=(0,)) def handle_long_poll(ws): @@ -103,7 +106,20 @@ def upload_handler(end_event): if item.id in cancelled_uploads: cancelled_uploads.remove(item.id) continue - _do_upload(item) + + try: + _do_upload(item) + except (requests.exceptions.Timeout, requests.exceptions.ConnectionError, requests.exceptions.SSLError) as e: + cloudlog.warning(f"athena.upload_handler.retry {e} {item}") + + if item.retry_count < MAX_RETRY_COUNT: + item = item._replace(retry_count=item.retry_count + 1) + upload_queue.put_nowait(item) + + for _ in range(RETRY_DELAY): + time.sleep(1) + if end_event.is_set(): + break except queue.Empty: pass except Exception: @@ -209,6 +225,14 @@ def cancelUpload(upload_id): return {"success": 1} +@dispatcher.add_method +def primeActivated(activated): + dongle_id = Params().get("DongleId", encoding='utf-8') + api = Api(dongle_id) + manage_tokens(api) + return {"success": 1} + + def startLocalProxy(global_end_event, remote_ws_uri, local_port): try: if local_port not in LOCAL_PORT_WHITELIST: @@ -303,9 +327,8 @@ def get_logs_to_send_sorted(): # assume send failed and we lost the response if sent more than one hour ago if not time_sent or curr_time - time_sent > 3600: logs.append(log_entry) - # return logs in order they should be sent # excluding most recent (active) log file - return sorted(logs[:-1]) + return sorted(logs)[:-1] def log_handler(end_event): @@ -324,7 +347,7 @@ def log_handler(end_event): # send one log curr_log = None if len(log_files) > 0: - log_entry = log_files.pop() + log_entry = log_files.pop() # newest log file cloudlog.debug(f"athena.log_handler.forward_request {log_entry}") try: curr_time = int(time.time()) @@ -486,6 +509,7 @@ def main(): enable_multithread=True, timeout=30.0) cloudlog.event("athenad.main.connected_ws", ws_uri=ws_uri) + params.delete("PrimeRedirected") manage_tokens(api) @@ -495,13 +519,22 @@ def main(): break except (ConnectionError, TimeoutError, WebSocketException): conn_retries += 1 + params.delete("PrimeRedirected") + params.delete("LastAthenaPingTime") + except socket.timeout: + try: + r = requests.get("http://api.commadotai.com/v1/me", allow_redirects=False, + headers={"User-Agent": f"openpilot-{version}"}, timeout=15.0) + if r.status_code == 302 and r.headers['Location'].startswith("http://u.web2go.com"): + params.put_bool("PrimeRedirected", True) + except Exception: + cloudlog.exception("athenad.socket_timeout.exception") params.delete("LastAthenaPingTime") - if TICI: - cloudlog.exception("athenad.main.exception2") except Exception: cloudlog.exception("athenad.main.exception") conn_retries += 1 + params.delete("PrimeRedirected") params.delete("LastAthenaPingTime") time.sleep(backoff(conn_retries)) diff --git a/selfdrive/athena/registration.py b/selfdrive/athena/registration.py old mode 100644 new mode 100755 index 6bcb29e4b..39dfb3c23 --- a/selfdrive/athena/registration.py +++ b/selfdrive/athena/registration.py @@ -1,7 +1,7 @@ +#!/usr/bin/env python3 import os import time import json - import jwt from datetime import datetime, timedelta @@ -50,6 +50,8 @@ def register(show_spinner=False) -> str: private_key = f2.read() # Block until we get the imei + serial = HARDWARE.get_serial() + start_time = time.monotonic() imei1, imei2 = None, None while imei1 is None and imei2 is None: try: @@ -58,11 +60,14 @@ def register(show_spinner=False) -> str: cloudlog.exception("Error getting imei, trying again...") time.sleep(1) - serial = HARDWARE.get_serial() + if time.monotonic() - start_time > 60 and show_spinner: + spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1}, {imei2})") + params.put("IMEI", imei1) params.put("HardwareSerial", serial) backoff = 0 + start_time = time.monotonic() while True: try: register_token = jwt.encode({'register': True, 'exp': datetime.utcnow() + timedelta(hours=1)}, private_key, algorithm='RS256') @@ -82,6 +87,9 @@ def register(show_spinner=False) -> str: backoff = min(backoff + 1, 15) time.sleep(backoff) + if time.monotonic() - start_time > 60 and show_spinner: + spinner.update(f"registering device - serial: {serial}, IMEI: ({imei1}, {imei2})") + if show_spinner: spinner.close() diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index c770845af..a8e8ebecf 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -411,6 +411,8 @@ void hardware_control_thread() { bool prev_charging_disabled = false; unsigned int cnt = 0; + FirstOrderFilter integ_lines_filter(0, 30.0, 0.05); + while (!do_exit && panda->connected) { cnt++; sm.update(1000); // TODO: what happens if EINTR is sent while in sm.update? @@ -443,6 +445,10 @@ void hardware_control_thread() { if (sm.updated("driverCameraState")) { auto event = sm["driverCameraState"]; int cur_integ_lines = event.getDriverCameraState().getIntegLines(); + + if (Hardware::TICI()) { + cur_integ_lines = integ_lines_filter.update(cur_integ_lines); + } last_front_frame_t = event.getLogMonoTime(); if (cur_integ_lines <= CUTOFF_IL) { diff --git a/selfdrive/camerad/cameras/camera_common.cc b/selfdrive/camerad/cameras/camera_common.cc index 125ac67a9..4c19d1d81 100644 --- a/selfdrive/camerad/cameras/camera_common.cc +++ b/selfdrive/camerad/cameras/camera_common.cc @@ -28,6 +28,8 @@ #include "selfdrive/camerad/cameras/camera_frame_stream.h" #endif +const int YUV_COUNT = 100; + static cl_program build_debayer_program(cl_device_id device_id, cl_context context, const CameraInfo *ci, const CameraBuf *b, const CameraState *s) { char args[4096]; snprintf(args, sizeof(args), @@ -125,6 +127,8 @@ bool CameraBuf::acquire() { const size_t globalWorkSize[] = {size_t(camera_state->ci.frame_width), size_t(camera_state->ci.frame_height)}; const size_t localWorkSize[] = {DEBAYER_LOCAL_WORKSIZE, DEBAYER_LOCAL_WORKSIZE}; CL_CHECK(clSetKernelArg(krnl_debayer, 2, localMemSize, 0)); + int ggain = camera_state->analog_gain + 4*camera_state->dc_gain_enabled; + CL_CHECK(clSetKernelArg(krnl_debayer, 3, sizeof(int), &ggain)); CL_CHECK(clEnqueueNDRangeKernel(q, krnl_debayer, 2, NULL, globalWorkSize, localWorkSize, 0, 0, &debayer_event)); #else @@ -276,30 +280,39 @@ static void publish_thumbnail(PubMaster *pm, const CameraBuf *b) { free(thumbnail_buffer); } -float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip, int y_start, int y_end, int y_skip) { - int lum_med; - uint32_t lum_binning[256] = {0}; +float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip, int y_start, int y_end, int y_skip, int analog_gain, bool hist_ceil, bool hl_weighted) { const uint8_t *pix_ptr = b->cur_yuv_buf->y; - + uint32_t lum_binning[256] = {0}; unsigned int lum_total = 0; for (int y = y_start; y < y_end; y += y_skip) { for (int x = x_start; x < x_end; x += x_skip) { uint8_t lum = pix_ptr[(y * b->rgb_width) + x]; + if (hist_ceil && lum < 80 && lum_binning[lum] > HISTO_CEIL_K * (y_end - y_start) * (x_end - x_start) / x_skip / y_skip / 256) { + continue; + } lum_binning[lum]++; lum_total += 1; } } - - // Find mean lumimance value unsigned int lum_cur = 0; - for (lum_med = 255; lum_med >= 0; lum_med--) { + int lum_med = 0; + int lum_med_alt = 0; + for (lum_med=255; lum_med>=0; lum_med--) { lum_cur += lum_binning[lum_med]; - + if (hl_weighted) { + int lum_med_tmp = 0; + int hb = HLC_THRESH + (10 - analog_gain); + if (lum_cur > 0 && lum_med > hb) { + lum_med_tmp = (lum_med - hb) + 100; + } + lum_med_alt = lum_med_alt>lum_med_tmp?lum_med_alt:lum_med_tmp; + } if (lum_cur >= lum_total / 2) { break; } } + lum_med = lum_med_alt>0 ? lum_med + lum_med/32*lum_cur*abs(lum_med_alt - lum_med)/lum_total:lum_med; return lum_med / 256.0; } @@ -342,12 +355,18 @@ static void driver_cam_auto_exposure(CameraState *c, SubMaster &sm) { struct ExpRect {int x1, x2, x_skip, y1, y2, y_skip;}; const CameraBuf *b = &c->buf; + bool hist_ceil = false, hl_weighted = false; int x_offset = 0, y_offset = 0; int frame_width = b->rgb_width, frame_height = b->rgb_height; - +#ifndef QCOM2 + int analog_gain = -1; +#else + int analog_gain = c->analog_gain; +#endif ExpRect def_rect; if (Hardware::TICI()) { + hist_ceil = hl_weighted = true; x_offset = 630, y_offset = 156; frame_width = 668, frame_height = frame_width / 1.33; def_rect = {96, 1832, 2, 242, 1148, 4}; @@ -371,7 +390,7 @@ static void driver_cam_auto_exposure(CameraState *c, SubMaster &sm) { } } - camera_autoexposure(c, set_exposure_target(b, rect.x1, rect.x2, rect.x_skip, rect.y1, rect.y2, rect.y_skip)); + camera_autoexposure(c, set_exposure_target(b, rect.x1, rect.x2, rect.x_skip, rect.y1, rect.y2, rect.y_skip, analog_gain, hist_ceil, hl_weighted)); } void common_process_driver_camera(SubMaster *sm, PubMaster *pm, CameraState *c, int cnt) { diff --git a/selfdrive/camerad/cameras/camera_common.h b/selfdrive/camerad/cameras/camera_common.h index 8bf4ff336..17051de40 100644 --- a/selfdrive/camerad/cameras/camera_common.h +++ b/selfdrive/camerad/cameras/camera_common.h @@ -27,7 +27,6 @@ #define CAMERA_ID_MAX 9 #define UI_BUF_COUNT 4 -#define YUV_COUNT 100 #define LOG_CAMERA_ID_FCAMERA 0 #define LOG_CAMERA_ID_DCAMERA 1 #define LOG_CAMERA_ID_ECAMERA 2 @@ -135,7 +134,7 @@ typedef void (*process_thread_cb)(MultiCameraState *s, CameraState *c, int cnt); void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &frame_data); kj::Array get_frame_image(const CameraBuf *b); -float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip, int y_start, int y_end, int y_skip); +float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip, int y_start, int y_end, int y_skip, int analog_gain, bool hist_ceil, bool hl_weighted); std::thread start_process_thread(MultiCameraState *cameras, CameraState *cs, process_thread_cb callback); void common_process_driver_camera(SubMaster *sm, PubMaster *pm, CameraState *c, int cnt); diff --git a/selfdrive/camerad/cameras/camera_qcom.cc b/selfdrive/camerad/cameras/camera_qcom.cc index 7b350215d..5ac90e4b3 100644 --- a/selfdrive/camerad/cameras/camera_qcom.cc +++ b/selfdrive/camerad/cameras/camera_qcom.cc @@ -1113,7 +1113,7 @@ void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { if (cnt % 3 == 0) { const int x = 290, y = 322, width = 560, height = 314; const int skip = 1; - camera_autoexposure(c, set_exposure_target(b, x, x + width, skip, y, y + height, skip)); + camera_autoexposure(c, set_exposure_target(b, x, x + width, skip, y, y + height, skip, -1, false, false)); } } diff --git a/selfdrive/camerad/cameras/camera_qcom2.cc b/selfdrive/camerad/cameras/camera_qcom2.cc new file mode 100644 index 000000000..ff512e658 --- /dev/null +++ b/selfdrive/camerad/cameras/camera_qcom2.cc @@ -0,0 +1,1145 @@ +#include "selfdrive/camerad/cameras/camera_qcom2.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "media/cam_defs.h" +#include "media/cam_isp.h" +#include "media/cam_isp_ife.h" +#include "media/cam_sensor.h" +#include "media/cam_sensor_cmn_header.h" +#include "media/cam_sync.h" +#include "selfdrive/common/swaglog.h" +#include "selfdrive/camerad/cameras/sensor2_i2c.h" + +#define FRAME_WIDTH 1928 +#define FRAME_HEIGHT 1208 +//#define FRAME_STRIDE 1936 // for 8 bit output +#define FRAME_STRIDE 2416 // for 10 bit output +//#define FRAME_STRIDE 1936 // for 8 bit output + +#define MIPI_SETTLE_CNT 33 // Calculated by camera_freqs.py + +extern ExitHandler do_exit; + +// global var for AE ops +std::atomic cam_exp[3] = {{{0}}}; + +CameraInfo cameras_supported[CAMERA_ID_MAX] = { + [CAMERA_ID_AR0231] = { + .frame_width = FRAME_WIDTH, + .frame_height = FRAME_HEIGHT, + .frame_stride = FRAME_STRIDE, + .bayer = true, + .bayer_flip = 1, + .hdr = false + }, +}; + +float sensor_analog_gains[ANALOG_GAIN_MAX_IDX] = {3.0/6.0, 4.0/6.0, 4.0/5.0, 5.0/5.0, + 5.0/4.0, 6.0/4.0, 6.0/3.0, 7.0/3.0, + 7.0/2.0, 8.0/2.0}; + +// ************** low level camera helpers **************** + +int cam_control(int fd, int op_code, void *handle, int size) { + struct cam_control camcontrol = {0}; + camcontrol.op_code = op_code; + camcontrol.handle = (uint64_t)handle; + if (size == 0) { camcontrol.size = 8; + camcontrol.handle_type = CAM_HANDLE_MEM_HANDLE; + } else { + camcontrol.size = size; + camcontrol.handle_type = CAM_HANDLE_USER_POINTER; + } + + int ret = ioctl(fd, VIDIOC_CAM_CONTROL, &camcontrol); + if (ret == -1) { + printf("OP CODE ERR - %d \n", op_code); + perror("wat"); + } + return ret; +} + +int device_control(int fd, int op_code, int session_handle, int dev_handle) { + // start stop and release are all the same + static struct cam_release_dev_cmd release_dev_cmd; + release_dev_cmd.session_handle = session_handle; + release_dev_cmd.dev_handle = dev_handle; + return cam_control(fd, op_code, &release_dev_cmd, sizeof(release_dev_cmd)); +} + +void *alloc_w_mmu_hdl(int video0_fd, int len, uint32_t *handle, int align = 8, int flags = CAM_MEM_FLAG_KMD_ACCESS | CAM_MEM_FLAG_UMD_ACCESS | CAM_MEM_FLAG_CMD_BUF_TYPE, + int mmu_hdl = 0, int mmu_hdl2 = 0) { + struct cam_mem_mgr_alloc_cmd mem_mgr_alloc_cmd = {0}; + mem_mgr_alloc_cmd.len = len; + mem_mgr_alloc_cmd.align = align; + mem_mgr_alloc_cmd.flags = flags; + mem_mgr_alloc_cmd.num_hdl = 0; + if (mmu_hdl != 0) { + mem_mgr_alloc_cmd.mmu_hdls[0] = mmu_hdl; + mem_mgr_alloc_cmd.num_hdl++; + } + if (mmu_hdl2 != 0) { + mem_mgr_alloc_cmd.mmu_hdls[1] = mmu_hdl2; + mem_mgr_alloc_cmd.num_hdl++; + } + + cam_control(video0_fd, CAM_REQ_MGR_ALLOC_BUF, &mem_mgr_alloc_cmd, sizeof(mem_mgr_alloc_cmd)); + *handle = mem_mgr_alloc_cmd.out.buf_handle; + + void *ptr = NULL; + if (mem_mgr_alloc_cmd.out.fd > 0) { + ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, mem_mgr_alloc_cmd.out.fd, 0); + assert(ptr != MAP_FAILED); + } + + // LOGD("alloced: %x %d %llx mapped %p", mem_mgr_alloc_cmd.out.buf_handle, mem_mgr_alloc_cmd.out.fd, mem_mgr_alloc_cmd.out.vaddr, ptr); + + return ptr; +} + +void release(int video0_fd, uint32_t handle) { + int ret; + struct cam_mem_mgr_release_cmd mem_mgr_release_cmd = {0}; + mem_mgr_release_cmd.buf_handle = handle; + + ret = cam_control(video0_fd, CAM_REQ_MGR_RELEASE_BUF, &mem_mgr_release_cmd, sizeof(mem_mgr_release_cmd)); + assert(ret == 0); +} + +void release_fd(int video0_fd, uint32_t handle) { + // handle to fd + close(handle>>16); + release(video0_fd, handle); +} + +void clear_req_queue(int fd, int32_t session_hdl, int32_t link_hdl) { + struct cam_req_mgr_flush_info req_mgr_flush_request = {0}; + req_mgr_flush_request.session_hdl = session_hdl; + req_mgr_flush_request.link_hdl = link_hdl; + req_mgr_flush_request.flush_type = CAM_REQ_MGR_FLUSH_TYPE_ALL; + int ret; + ret = cam_control(fd, CAM_REQ_MGR_FLUSH_REQ, &req_mgr_flush_request, sizeof(req_mgr_flush_request)); + // LOGD("flushed all req: %d", ret); +} + +// ************** high level camera helpers **************** + +void sensors_poke(struct CameraState *s, int request_id) { + uint32_t cam_packet_handle = 0; + int size = sizeof(struct cam_packet); + struct cam_packet *pkt = (struct cam_packet *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, size, &cam_packet_handle); + pkt->num_cmd_buf = 0; + pkt->kmd_cmd_buf_index = -1; + pkt->header.size = size; + pkt->header.op_code = 0x7f; + pkt->header.request_id = request_id; + + struct cam_config_dev_cmd config_dev_cmd = {}; + config_dev_cmd.session_handle = s->session_handle; + config_dev_cmd.dev_handle = s->sensor_dev_handle; + config_dev_cmd.offset = 0; + config_dev_cmd.packet_handle = cam_packet_handle; + + int ret = cam_control(s->sensor_fd, CAM_CONFIG_DEV, &config_dev_cmd, sizeof(config_dev_cmd)); + assert(ret == 0); + + munmap(pkt, size); + release_fd(s->multi_cam_state->video0_fd, cam_packet_handle); +} + +void sensors_i2c(struct CameraState *s, struct i2c_random_wr_payload* dat, int len, int op_code) { + // LOGD("sensors_i2c: %d", len); + uint32_t cam_packet_handle = 0; + int size = sizeof(struct cam_packet)+sizeof(struct cam_cmd_buf_desc)*1; + struct cam_packet *pkt = (struct cam_packet *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, size, &cam_packet_handle); + pkt->num_cmd_buf = 1; + pkt->kmd_cmd_buf_index = -1; + pkt->header.size = size; + pkt->header.op_code = op_code; + struct cam_cmd_buf_desc *buf_desc = (struct cam_cmd_buf_desc *)&pkt->payload; + + buf_desc[0].size = buf_desc[0].length = sizeof(struct i2c_rdwr_header) + len*sizeof(struct i2c_random_wr_payload); + buf_desc[0].type = CAM_CMD_BUF_I2C; + + struct cam_cmd_i2c_random_wr *i2c_random_wr = (struct cam_cmd_i2c_random_wr *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, buf_desc[0].size, (uint32_t*)&buf_desc[0].mem_handle); + i2c_random_wr->header.count = len; + i2c_random_wr->header.op_code = 1; + i2c_random_wr->header.cmd_type = CAMERA_SENSOR_CMD_TYPE_I2C_RNDM_WR; + i2c_random_wr->header.data_type = CAMERA_SENSOR_I2C_TYPE_WORD; + i2c_random_wr->header.addr_type = CAMERA_SENSOR_I2C_TYPE_WORD; + memcpy(i2c_random_wr->random_wr_payload, dat, len*sizeof(struct i2c_random_wr_payload)); + + struct cam_config_dev_cmd config_dev_cmd = {}; + config_dev_cmd.session_handle = s->session_handle; + config_dev_cmd.dev_handle = s->sensor_dev_handle; + config_dev_cmd.offset = 0; + config_dev_cmd.packet_handle = cam_packet_handle; + + int ret = cam_control(s->sensor_fd, CAM_CONFIG_DEV, &config_dev_cmd, sizeof(config_dev_cmd)); + assert(ret == 0); + + munmap(i2c_random_wr, buf_desc[0].size); + release_fd(s->multi_cam_state->video0_fd, buf_desc[0].mem_handle); + munmap(pkt, size); + release_fd(s->multi_cam_state->video0_fd, cam_packet_handle); +} +static cam_cmd_power *power_set_wait(cam_cmd_power *power, int16_t delay_ms) { + cam_cmd_unconditional_wait *unconditional_wait = (cam_cmd_unconditional_wait *)((char *)power + (sizeof(struct cam_cmd_power) + (power->count - 1) * sizeof(struct cam_power_settings))); + unconditional_wait->cmd_type = CAMERA_SENSOR_CMD_TYPE_WAIT; + unconditional_wait->delay = delay_ms; + unconditional_wait->op_code = CAMERA_SENSOR_WAIT_OP_SW_UCND; + return (struct cam_cmd_power *)(unconditional_wait + 1); +}; + +void sensors_init(int video0_fd, int sensor_fd, int camera_num) { + uint32_t cam_packet_handle = 0; + int size = sizeof(struct cam_packet)+sizeof(struct cam_cmd_buf_desc)*2; + struct cam_packet *pkt = (struct cam_packet *)alloc_w_mmu_hdl(video0_fd, size, &cam_packet_handle); + pkt->num_cmd_buf = 2; + pkt->kmd_cmd_buf_index = -1; + pkt->header.op_code = 0x1000003; + pkt->header.size = size; + struct cam_cmd_buf_desc *buf_desc = (struct cam_cmd_buf_desc *)&pkt->payload; + + buf_desc[0].size = buf_desc[0].length = sizeof(struct cam_cmd_i2c_info) + sizeof(struct cam_cmd_probe); + buf_desc[0].type = CAM_CMD_BUF_LEGACY; + struct cam_cmd_i2c_info *i2c_info = (struct cam_cmd_i2c_info *)alloc_w_mmu_hdl(video0_fd, buf_desc[0].size, (uint32_t*)&buf_desc[0].mem_handle); + struct cam_cmd_probe *probe = (struct cam_cmd_probe *)((uint8_t *)i2c_info) + sizeof(struct cam_cmd_i2c_info); + + switch (camera_num) { + case 0: + // port 0 + i2c_info->slave_addr = 0x20; + probe->camera_id = 0; + break; + case 1: + // port 1 + i2c_info->slave_addr = 0x30; + probe->camera_id = 1; + break; + case 2: + // port 2 + i2c_info->slave_addr = 0x20; + probe->camera_id = 2; + break; + } + + // 0(I2C_STANDARD_MODE) = 100khz, 1(I2C_FAST_MODE) = 400khz + //i2c_info->i2c_freq_mode = I2C_STANDARD_MODE; + i2c_info->i2c_freq_mode = I2C_FAST_MODE; + i2c_info->cmd_type = CAMERA_SENSOR_CMD_TYPE_I2C_INFO; + + probe->data_type = CAMERA_SENSOR_I2C_TYPE_WORD; + probe->addr_type = CAMERA_SENSOR_I2C_TYPE_WORD; + probe->op_code = 3; // don't care? + probe->cmd_type = CAMERA_SENSOR_CMD_TYPE_PROBE; + probe->reg_addr = 0x3000; //0x300a; //0x300b; + probe->expected_data = 0x354; //0x7750; //0x885a; + probe->data_mask = 0; + + //buf_desc[1].size = buf_desc[1].length = 148; + buf_desc[1].size = buf_desc[1].length = 196; + buf_desc[1].type = CAM_CMD_BUF_I2C; + struct cam_cmd_power *power_settings = (struct cam_cmd_power *)alloc_w_mmu_hdl(video0_fd, buf_desc[1].size, (uint32_t*)&buf_desc[1].mem_handle); + memset(power_settings, 0, buf_desc[1].size); + // 7750 + /*power->count = 2; + power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_UP; + power->power_settings[0].power_seq_type = 2; + power->power_settings[1].power_seq_type = 8; + power = (void*)power + (sizeof(struct cam_cmd_power) + (power->count-1)*sizeof(struct cam_power_settings));*/ + + // 885a + struct cam_cmd_power *power = power_settings; + power->count = 4; + power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_UP; + power->power_settings[0].power_seq_type = 3; // clock?? + power->power_settings[1].power_seq_type = 1; // analog + power->power_settings[2].power_seq_type = 2; // digital + power->power_settings[3].power_seq_type = 8; // reset low + power = power_set_wait(power, 5); + + // set clock + power->count = 1; + power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_UP; + power->power_settings[0].power_seq_type = 0; + power->power_settings[0].config_val_low = 19200000; //Hz + power = power_set_wait(power, 10); + + // 8,1 is this reset? + power->count = 1; + power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_UP; + power->power_settings[0].power_seq_type = 8; + power->power_settings[0].config_val_low = 1; + power = power_set_wait(power, 100); + + // probe happens here + + // disable clock + power->count = 1; + power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_DOWN; + power->power_settings[0].power_seq_type = 0; + power->power_settings[0].config_val_low = 0; + power = power_set_wait(power, 1); + + // reset high + power->count = 1; + power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_DOWN; + power->power_settings[0].power_seq_type = 8; + power->power_settings[0].config_val_low = 1; + power = power_set_wait(power, 1); + + // reset low + power->count = 1; + power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_DOWN; + power->power_settings[0].power_seq_type = 8; + power->power_settings[0].config_val_low = 0; + power = power_set_wait(power, 1); + + // 7750 + /*power->count = 1; + power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_DOWN; + power->power_settings[0].power_seq_type = 2; + power = (void*)power + (sizeof(struct cam_cmd_power) + (power->count-1)*sizeof(struct cam_power_settings));*/ + + // 885a + power->count = 3; + power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_DOWN; + power->power_settings[0].power_seq_type = 2; + power->power_settings[1].power_seq_type = 1; + power->power_settings[2].power_seq_type = 3; + + LOGD("probing the sensor"); + int ret = cam_control(sensor_fd, CAM_SENSOR_PROBE_CMD, (void *)(uintptr_t)cam_packet_handle, 0); + assert(ret == 0); + + munmap(i2c_info, buf_desc[0].size); + release_fd(video0_fd, buf_desc[0].mem_handle); + munmap(power_settings, buf_desc[1].size); + release_fd(video0_fd, buf_desc[1].mem_handle); + munmap(pkt, size); + release_fd(video0_fd, cam_packet_handle); +} + +void config_isp(struct CameraState *s, int io_mem_handle, int fence, int request_id, int buf0_mem_handle, int buf0_offset) { + uint32_t cam_packet_handle = 0; + int size = sizeof(struct cam_packet)+sizeof(struct cam_cmd_buf_desc)*2; + if (io_mem_handle != 0) { + size += sizeof(struct cam_buf_io_cfg); + } + struct cam_packet *pkt = (struct cam_packet *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, size, &cam_packet_handle); + pkt->num_cmd_buf = 2; + pkt->kmd_cmd_buf_index = 0; + + if (io_mem_handle != 0) { + pkt->io_configs_offset = sizeof(struct cam_cmd_buf_desc)*2; + pkt->num_io_configs = 1; + } + + if (io_mem_handle != 0) { + pkt->header.op_code = 0xf000001; + pkt->header.request_id = request_id; + } else { + pkt->header.op_code = 0xf000000; + } + pkt->header.size = size; + struct cam_cmd_buf_desc *buf_desc = (struct cam_cmd_buf_desc *)&pkt->payload; + struct cam_buf_io_cfg *io_cfg = (struct cam_buf_io_cfg *)((char*)&pkt->payload + pkt->io_configs_offset); + + // TODO: support MMU + buf_desc[0].size = 65624; + buf_desc[0].length = 0; + buf_desc[0].type = CAM_CMD_BUF_DIRECT; + buf_desc[0].meta_data = 3; + buf_desc[0].mem_handle = buf0_mem_handle; + buf_desc[0].offset = buf0_offset; + + buf_desc[1].size = 324; + if (io_mem_handle != 0) { + buf_desc[1].length = 228; // 0 works here too + buf_desc[1].offset = 0x60; + } else { + buf_desc[1].length = 324; + } + buf_desc[1].type = CAM_CMD_BUF_GENERIC; + buf_desc[1].meta_data = CAM_ISP_PACKET_META_GENERIC_BLOB_COMMON; + uint32_t *buf2 = (uint32_t *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, buf_desc[1].size, (uint32_t*)&buf_desc[1].mem_handle, 0x20); + + // cam_isp_packet_generic_blob_handler + uint32_t tmp[] = { + // size is 0x20, type is 0(CAM_ISP_GENERIC_BLOB_TYPE_HFR_CONFIG) + 0x2000, + 0x1, 0x0, CAM_ISP_IFE_OUT_RES_RDI_0, 0x1, 0x0, 0x1, 0x0, 0x0, // 1 port, CAM_ISP_IFE_OUT_RES_RDI_0 + // size is 0x38, type is 1(CAM_ISP_GENERIC_BLOB_TYPE_CLOCK_CONFIG), clocks + 0x3801, + 0x1, 0x4, // Dual mode, 4 RDI wires + 0x18148d00, 0x0, 0x18148d00, 0x0, 0x18148d00, 0x0, // rdi clock + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // junk? + // offset 0x60 + // size is 0xe0, type is 2(CAM_ISP_GENERIC_BLOB_TYPE_BW_CONFIG), bandwidth + 0xe002, + 0x1, 0x4, // 4 RDI + 0x0, 0x0, 0x1ad27480, 0x0, 0x1ad27480, 0x0, // left_pix_vote + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // right_pix_vote + 0x0, 0x0, 0x6ee11c0, 0x2, 0x6ee11c0, 0x2, // rdi_vote + 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; + memcpy(buf2, tmp, sizeof(tmp)); + + if (io_mem_handle != 0) { + io_cfg[0].mem_handle[0] = io_mem_handle; + io_cfg[0].planes[0] = (struct cam_plane_cfg){ + .width = FRAME_WIDTH, + .height = FRAME_HEIGHT, + .plane_stride = FRAME_STRIDE, + .slice_height = FRAME_HEIGHT, + .meta_stride = 0x0, + .meta_size = 0x0, + .meta_offset = 0x0, + .packer_config = 0x0, + .mode_config = 0x0, + .tile_config = 0x0, + .h_init = 0x0, + .v_init = 0x0, + }; + io_cfg[0].format = CAM_FORMAT_MIPI_RAW_10; + io_cfg[0].color_pattern = 0x5; + io_cfg[0].bpp = 0xc; + io_cfg[0].resource_type = CAM_ISP_IFE_OUT_RES_RDI_0; + io_cfg[0].fence = fence; + io_cfg[0].direction = CAM_BUF_OUTPUT; + io_cfg[0].subsample_pattern = 0x1; + io_cfg[0].framedrop_pattern = 0x1; + } + + struct cam_config_dev_cmd config_dev_cmd = {}; + config_dev_cmd.session_handle = s->session_handle; + config_dev_cmd.dev_handle = s->isp_dev_handle; + config_dev_cmd.offset = 0; + config_dev_cmd.packet_handle = cam_packet_handle; + + int ret = cam_control(s->multi_cam_state->isp_fd, CAM_CONFIG_DEV, &config_dev_cmd, sizeof(config_dev_cmd)); + if (ret != 0) { + printf("ISP CONFIG FAILED\n"); + } + + munmap(buf2, buf_desc[1].size); + release_fd(s->multi_cam_state->video0_fd, buf_desc[1].mem_handle); + // release_fd(s->multi_cam_state->video0_fd, buf_desc[0].mem_handle); + munmap(pkt, size); + release_fd(s->multi_cam_state->video0_fd, cam_packet_handle); +} + +void enqueue_buffer(struct CameraState *s, int i, bool dp) { + int ret; + int request_id = s->request_ids[i]; + + if (s->buf_handle[i]) { + release(s->multi_cam_state->video0_fd, s->buf_handle[i]); + // wait + struct cam_sync_wait sync_wait = {0}; + sync_wait.sync_obj = s->sync_objs[i]; + sync_wait.timeout_ms = 50; // max dt tolerance, typical should be 23 + ret = cam_control(s->multi_cam_state->video1_fd, CAM_SYNC_WAIT, &sync_wait, sizeof(sync_wait)); + // LOGD("fence wait: %d %d", ret, sync_wait.sync_obj); + + s->buf.camera_bufs_metadata[i].timestamp_eof = (uint64_t)nanos_since_boot(); // set true eof + if (dp) s->buf.queue(i); + + // destroy old output fence + struct cam_sync_info sync_destroy = {0}; + strcpy(sync_destroy.name, "NodeOutputPortFence"); + sync_destroy.sync_obj = s->sync_objs[i]; + ret = cam_control(s->multi_cam_state->video1_fd, CAM_SYNC_DESTROY, &sync_destroy, sizeof(sync_destroy)); + // LOGD("fence destroy: %d %d", ret, sync_destroy.sync_obj); + } + + // do stuff + struct cam_req_mgr_sched_request req_mgr_sched_request = {0}; + req_mgr_sched_request.session_hdl = s->session_handle; + req_mgr_sched_request.link_hdl = s->link_handle; + req_mgr_sched_request.req_id = request_id; + ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_SCHED_REQ, &req_mgr_sched_request, sizeof(req_mgr_sched_request)); + // LOGD("sched req: %d %d", ret, request_id); + + // create output fence + struct cam_sync_info sync_create = {0}; + strcpy(sync_create.name, "NodeOutputPortFence"); + ret = cam_control(s->multi_cam_state->video1_fd, CAM_SYNC_CREATE, &sync_create, sizeof(sync_create)); + // LOGD("fence req: %d %d", ret, sync_create.sync_obj); + s->sync_objs[i] = sync_create.sync_obj; + + // configure ISP to put the image in place + struct cam_mem_mgr_map_cmd mem_mgr_map_cmd = {0}; + mem_mgr_map_cmd.mmu_hdls[0] = s->multi_cam_state->device_iommu; + mem_mgr_map_cmd.num_hdl = 1; + mem_mgr_map_cmd.flags = 1; + mem_mgr_map_cmd.fd = s->buf.camera_bufs[i].fd; + ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_MAP_BUF, &mem_mgr_map_cmd, sizeof(mem_mgr_map_cmd)); + // LOGD("map buf req: (fd: %d) 0x%x %d", s->bufs[i].fd, mem_mgr_map_cmd.out.buf_handle, ret); + s->buf_handle[i] = mem_mgr_map_cmd.out.buf_handle; + + // poke sensor + sensors_poke(s, request_id); + // LOGD("Poked sensor"); + + // push the buffer + config_isp(s, s->buf_handle[i], s->sync_objs[i], request_id, s->buf0_handle, 65632*(i+1)); +} + +void enqueue_req_multi(struct CameraState *s, int start, int n, bool dp) { + for (int i=start;irequest_ids[(i - 1) % FRAME_BUF_COUNT] = i; + enqueue_buffer(s, (i - 1) % FRAME_BUF_COUNT, dp); + } +} + +// ******************* camera ******************* + +static void camera_init(MultiCameraState *multi_cam_state, VisionIpcServer * v, CameraState *s, int camera_id, int camera_num, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType rgb_type, VisionStreamType yuv_type) { + LOGD("camera init %d", camera_num); + s->multi_cam_state = multi_cam_state; + assert(camera_id < std::size(cameras_supported)); + s->ci = cameras_supported[camera_id]; + assert(s->ci.frame_width != 0); + + s->camera_num = camera_num; + + s->dc_gain_enabled = false; + s->analog_gain = 0x5; + s->analog_gain_frac = sensor_analog_gains[s->analog_gain]; + s->exposure_time = 256; + s->exposure_time_max = 1.2 * EXPOSURE_TIME_MAX / 2; + s->exposure_time_min = 0.75 * EXPOSURE_TIME_MIN * 2; + s->request_id_last = 0; + s->skipped = true; + s->ef_filtered = 1.0; + + s->buf.init(device_id, ctx, s, v, FRAME_BUF_COUNT, rgb_type, yuv_type); +} + +// TODO: refactor this to somewhere nicer, perhaps use in camera_qcom as well +static int open_v4l_by_name_and_index(const char name[], int index, int flags) { + char nbuf[0x100]; + int v4l_index = 0; + int cnt_index = index; + while (true) { + snprintf(nbuf, sizeof(nbuf), "/sys/class/video4linux/v4l-subdev%d/name", v4l_index); + FILE *f = fopen(nbuf, "rb"); + if (f == NULL) return -1; + int len = fread(nbuf, 1, sizeof(nbuf), f); + fclose(f); + + // name ends with '\n', remove it + if (len < 1) return -1; + nbuf[len-1] = '\0'; + + if (strcmp(nbuf, name) == 0) { + if (cnt_index == 0) { + snprintf(nbuf, sizeof(nbuf), "/dev/v4l-subdev%d", v4l_index); + LOGD("open %s for %s index %d", nbuf, name, index); + return open(nbuf, flags); + } + cnt_index--; + } + v4l_index++; + } +} + +static void camera_open(CameraState *s) { + int ret; + s->sensor_fd = open_v4l_by_name_and_index("cam-sensor-driver", s->camera_num, O_RDWR | O_NONBLOCK); + assert(s->sensor_fd >= 0); + LOGD("opened sensor"); + + s->csiphy_fd = open_v4l_by_name_and_index("cam-csiphy-driver", s->camera_num, O_RDWR | O_NONBLOCK); + assert(s->csiphy_fd >= 0); + LOGD("opened csiphy"); + + // probe the sensor + LOGD("-- Probing sensor %d", s->camera_num); + sensors_init(s->multi_cam_state->video0_fd, s->sensor_fd, s->camera_num); + + memset(&s->req_mgr_session_info, 0, sizeof(s->req_mgr_session_info)); + ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_CREATE_SESSION, &s->req_mgr_session_info, sizeof(s->req_mgr_session_info)); + LOGD("get session: %d 0x%X", ret, s->req_mgr_session_info.session_hdl); + s->session_handle = s->req_mgr_session_info.session_hdl; + // access the sensor + LOGD("-- Accessing sensor"); + static struct cam_acquire_dev_cmd acquire_dev_cmd = {0}; + acquire_dev_cmd.session_handle = s->session_handle; + acquire_dev_cmd.handle_type = CAM_HANDLE_USER_POINTER; + ret = cam_control(s->sensor_fd, CAM_ACQUIRE_DEV, &acquire_dev_cmd, sizeof(acquire_dev_cmd)); + LOGD("acquire sensor dev: %d", ret); + s->sensor_dev_handle = acquire_dev_cmd.dev_handle; + + static struct cam_isp_resource isp_resource = {0}; + + acquire_dev_cmd.session_handle = s->session_handle; + acquire_dev_cmd.handle_type = CAM_HANDLE_USER_POINTER; + acquire_dev_cmd.num_resources = 1; + acquire_dev_cmd.resource_hdl = (uint64_t)&isp_resource; + + isp_resource.resource_id = CAM_ISP_RES_ID_PORT; + isp_resource.length = sizeof(struct cam_isp_in_port_info) + sizeof(struct cam_isp_out_port_info)*(1-1); + isp_resource.handle_type = CAM_HANDLE_USER_POINTER; + + struct cam_isp_in_port_info *in_port_info = (struct cam_isp_in_port_info *)malloc(isp_resource.length); + isp_resource.res_hdl = (uint64_t)in_port_info; + + switch (s->camera_num) { + case 0: + in_port_info->res_type = CAM_ISP_IFE_IN_RES_PHY_0; + break; + case 1: + in_port_info->res_type = CAM_ISP_IFE_IN_RES_PHY_1; + break; + case 2: + in_port_info->res_type = CAM_ISP_IFE_IN_RES_PHY_2; + break; + } + + in_port_info->lane_type = CAM_ISP_LANE_TYPE_DPHY; + in_port_info->lane_num = 4; + in_port_info->lane_cfg = 0x3210; + + in_port_info->vc = 0x0; + //in_port_info->dt = 0x2C; //CSI_RAW12 + //in_port_info->format = CAM_FORMAT_MIPI_RAW_12; + + in_port_info->dt = 0x2B; //CSI_RAW10 + in_port_info->format = CAM_FORMAT_MIPI_RAW_10; + + in_port_info->test_pattern = 0x2; // 0x3? + in_port_info->usage_type = 0x0; + + in_port_info->left_start = 0x0; + in_port_info->left_stop = FRAME_WIDTH - 1; + in_port_info->left_width = FRAME_WIDTH; + + in_port_info->right_start = 0x0; + in_port_info->right_stop = FRAME_WIDTH - 1; + in_port_info->right_width = FRAME_WIDTH; + + in_port_info->line_start = 0x0; + in_port_info->line_stop = FRAME_HEIGHT - 1; + in_port_info->height = FRAME_HEIGHT; + + in_port_info->pixel_clk = 0x0; + in_port_info->batch_size = 0x0; + in_port_info->dsp_mode = 0x0; + in_port_info->hbi_cnt = 0x0; + in_port_info->custom_csid = 0x0; + + in_port_info->num_out_res = 0x1; + in_port_info->data[0] = (struct cam_isp_out_port_info){ + .res_type = CAM_ISP_IFE_OUT_RES_RDI_0, + //.format = CAM_FORMAT_MIPI_RAW_12, + .format = CAM_FORMAT_MIPI_RAW_10, + .width = FRAME_WIDTH, + .height = FRAME_HEIGHT, + .comp_grp_id = 0x0, .split_point = 0x0, .secure_mode = 0x0, + }; + + ret = cam_control(s->multi_cam_state->isp_fd, CAM_ACQUIRE_DEV, &acquire_dev_cmd, sizeof(acquire_dev_cmd)); + LOGD("acquire isp dev: %d", ret); + free(in_port_info); + s->isp_dev_handle = acquire_dev_cmd.dev_handle; + + static struct cam_csiphy_acquire_dev_info csiphy_acquire_dev_info = {0}; + csiphy_acquire_dev_info.combo_mode = 0; + + acquire_dev_cmd.session_handle = s->session_handle; + acquire_dev_cmd.handle_type = CAM_HANDLE_USER_POINTER; + acquire_dev_cmd.num_resources = 1; + acquire_dev_cmd.resource_hdl = (uint64_t)&csiphy_acquire_dev_info; + + ret = cam_control(s->csiphy_fd, CAM_ACQUIRE_DEV, &acquire_dev_cmd, sizeof(acquire_dev_cmd)); + + LOGD("acquire csiphy dev: %d", ret); + s->csiphy_dev_handle = acquire_dev_cmd.dev_handle; + + // acquires done + + // config ISP + alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, 984480, (uint32_t*)&s->buf0_handle, 0x20, CAM_MEM_FLAG_HW_READ_WRITE | CAM_MEM_FLAG_KMD_ACCESS | CAM_MEM_FLAG_UMD_ACCESS | CAM_MEM_FLAG_CMD_BUF_TYPE, s->multi_cam_state->device_iommu, s->multi_cam_state->cdm_iommu); + config_isp(s, 0, 0, 1, s->buf0_handle, 0); + + LOG("-- Configuring sensor"); + sensors_i2c(s, init_array_ar0231, sizeof(init_array_ar0231)/sizeof(struct i2c_random_wr_payload), + CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG); + //sensors_i2c(s, start_reg_array, sizeof(start_reg_array)/sizeof(struct i2c_random_wr_payload), + //CAM_SENSOR_PACKET_OPCODE_SENSOR_STREAMON); + //sensors_i2c(s, stop_reg_array, sizeof(stop_reg_array)/sizeof(struct i2c_random_wr_payload), + //CAM_SENSOR_PACKET_OPCODE_SENSOR_STREAMOFF); + + // config csiphy + LOG("-- Config CSI PHY"); + { + uint32_t cam_packet_handle = 0; + int size = sizeof(struct cam_packet)+sizeof(struct cam_cmd_buf_desc)*1; + struct cam_packet *pkt = (struct cam_packet *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, size, &cam_packet_handle); + pkt->num_cmd_buf = 1; + pkt->kmd_cmd_buf_index = -1; + pkt->header.size = size; + struct cam_cmd_buf_desc *buf_desc = (struct cam_cmd_buf_desc *)&pkt->payload; + + buf_desc[0].size = buf_desc[0].length = sizeof(struct cam_csiphy_info); + buf_desc[0].type = CAM_CMD_BUF_GENERIC; + + struct cam_csiphy_info *csiphy_info = (struct cam_csiphy_info *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, buf_desc[0].size, (uint32_t*)&buf_desc[0].mem_handle); + csiphy_info->lane_mask = 0x1f; + csiphy_info->lane_assign = 0x3210;// skip clk. How is this 16 bit for 5 channels?? + csiphy_info->csiphy_3phase = 0x0; // no 3 phase, only 2 conductors per lane + csiphy_info->combo_mode = 0x0; + csiphy_info->lane_cnt = 0x4; + csiphy_info->secure_mode = 0x0; + csiphy_info->settle_time = MIPI_SETTLE_CNT * 200000000ULL; + csiphy_info->data_rate = 48000000; // Calculated by camera_freqs.py + + static struct cam_config_dev_cmd config_dev_cmd = {}; + config_dev_cmd.session_handle = s->session_handle; + config_dev_cmd.dev_handle = s->csiphy_dev_handle; + config_dev_cmd.offset = 0; + config_dev_cmd.packet_handle = cam_packet_handle; + + int ret = cam_control(s->csiphy_fd, CAM_CONFIG_DEV, &config_dev_cmd, sizeof(config_dev_cmd)); + assert(ret == 0); + + release(s->multi_cam_state->video0_fd, buf_desc[0].mem_handle); + release(s->multi_cam_state->video0_fd, cam_packet_handle); + } + + // link devices + LOG("-- Link devices"); + static struct cam_req_mgr_link_info req_mgr_link_info = {0}; + req_mgr_link_info.session_hdl = s->session_handle; + req_mgr_link_info.num_devices = 2; + req_mgr_link_info.dev_hdls[0] = s->isp_dev_handle; + req_mgr_link_info.dev_hdls[1] = s->sensor_dev_handle; + ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_LINK, &req_mgr_link_info, sizeof(req_mgr_link_info)); + LOGD("link: %d", ret); + s->link_handle = req_mgr_link_info.link_hdl; + + static struct cam_req_mgr_link_control req_mgr_link_control = {0}; + req_mgr_link_control.ops = 0; + req_mgr_link_control.session_hdl = s->session_handle; + req_mgr_link_control.num_links = 1; + req_mgr_link_control.link_hdls[0] = s->link_handle; + ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_LINK_CONTROL, &req_mgr_link_control, sizeof(req_mgr_link_control)); + LOGD("link control: %d", ret); + + LOGD("start csiphy: %d", ret); + ret = device_control(s->csiphy_fd, CAM_START_DEV, s->session_handle, s->csiphy_dev_handle); + LOGD("start isp: %d", ret); + ret = device_control(s->multi_cam_state->isp_fd, CAM_START_DEV, s->session_handle, s->isp_dev_handle); + LOGD("start sensor: %d", ret); + ret = device_control(s->sensor_fd, CAM_START_DEV, s->session_handle, s->sensor_dev_handle); + + enqueue_req_multi(s, 1, FRAME_BUF_COUNT, 0); +} + +void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { + camera_init(s, v, &s->road_cam, CAMERA_ID_AR0231, 1, 20, device_id, ctx, + VISION_STREAM_RGB_BACK, VISION_STREAM_YUV_BACK); // swap left/right + printf("road camera initted \n"); + camera_init(s, v, &s->wide_road_cam, CAMERA_ID_AR0231, 0, 20, device_id, ctx, + VISION_STREAM_RGB_WIDE, VISION_STREAM_YUV_WIDE); + printf("wide road camera initted \n"); + camera_init(s, v, &s->driver_cam, CAMERA_ID_AR0231, 2, 20, device_id, ctx, + VISION_STREAM_RGB_FRONT, VISION_STREAM_YUV_FRONT); + printf("driver camera initted \n"); + + s->sm = new SubMaster({"driverState"}); + s->pm = new PubMaster({"roadCameraState", "driverCameraState", "wideRoadCameraState", "thumbnail"}); +} + +void cameras_open(MultiCameraState *s) { + int ret; + + LOG("-- Opening devices"); + // video0 is req_mgr, the target of many ioctls + s->video0_fd = open("/dev/v4l/by-path/platform-soc:qcom_cam-req-mgr-video-index0", O_RDWR | O_NONBLOCK); + assert(s->video0_fd >= 0); + LOGD("opened video0"); + + // video1 is cam_sync, the target of some ioctls + s->video1_fd = open("/dev/v4l/by-path/platform-cam_sync-video-index0", O_RDWR | O_NONBLOCK); + assert(s->video1_fd >= 0); + LOGD("opened video1"); + + // looks like there's only one of these + s->isp_fd = open("/dev/v4l-subdev1", O_RDWR | O_NONBLOCK); + assert(s->isp_fd >= 0); + LOGD("opened isp"); + + // query icp for MMU handles + LOG("-- Query ICP for MMU handles"); + static struct cam_isp_query_cap_cmd isp_query_cap_cmd = {0}; + static struct cam_query_cap_cmd query_cap_cmd = {0}; + query_cap_cmd.handle_type = 1; + query_cap_cmd.caps_handle = (uint64_t)&isp_query_cap_cmd; + query_cap_cmd.size = sizeof(isp_query_cap_cmd); + ret = cam_control(s->isp_fd, CAM_QUERY_CAP, &query_cap_cmd, sizeof(query_cap_cmd)); + assert(ret == 0); + LOGD("using MMU handle: %x", isp_query_cap_cmd.device_iommu.non_secure); + LOGD("using MMU handle: %x", isp_query_cap_cmd.cdm_iommu.non_secure); + s->device_iommu = isp_query_cap_cmd.device_iommu.non_secure; + s->cdm_iommu = isp_query_cap_cmd.cdm_iommu.non_secure; + + // subscribe + LOG("-- Subscribing"); + static struct v4l2_event_subscription sub = {0}; + sub.type = 0x8000000; + sub.id = 2; // should use boot time for sof + ret = ioctl(s->video0_fd, VIDIOC_SUBSCRIBE_EVENT, &sub); + printf("req mgr subscribe: %d\n", ret); + + camera_open(&s->road_cam); + printf("road camera opened \n"); + camera_open(&s->wide_road_cam); + printf("wide road camera opened \n"); + camera_open(&s->driver_cam); + printf("driver camera opened \n"); +} + +static void camera_close(CameraState *s) { + int ret; + + // stop devices + LOG("-- Stop devices"); + // ret = device_control(s->sensor_fd, CAM_STOP_DEV, s->session_handle, s->sensor_dev_handle); + // LOGD("stop sensor: %d", ret); + ret = device_control(s->multi_cam_state->isp_fd, CAM_STOP_DEV, s->session_handle, s->isp_dev_handle); + LOGD("stop isp: %d", ret); + ret = device_control(s->csiphy_fd, CAM_STOP_DEV, s->session_handle, s->csiphy_dev_handle); + LOGD("stop csiphy: %d", ret); + // link control stop + LOG("-- Stop link control"); + static struct cam_req_mgr_link_control req_mgr_link_control = {0}; + req_mgr_link_control.ops = 1; + req_mgr_link_control.session_hdl = s->session_handle; + req_mgr_link_control.num_links = 1; + req_mgr_link_control.link_hdls[0] = s->link_handle; + ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_LINK_CONTROL, &req_mgr_link_control, sizeof(req_mgr_link_control)); + LOGD("link control stop: %d", ret); + + // unlink + LOG("-- Unlink"); + static struct cam_req_mgr_unlink_info req_mgr_unlink_info = {0}; + req_mgr_unlink_info.session_hdl = s->session_handle; + req_mgr_unlink_info.link_hdl = s->link_handle; + ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_UNLINK, &req_mgr_unlink_info, sizeof(req_mgr_unlink_info)); + LOGD("unlink: %d", ret); + + // release devices + LOGD("-- Release devices"); + ret = device_control(s->sensor_fd, CAM_RELEASE_DEV, s->session_handle, s->sensor_dev_handle); + LOGD("release sensor: %d", ret); + ret = device_control(s->multi_cam_state->isp_fd, CAM_RELEASE_DEV, s->session_handle, s->isp_dev_handle); + LOGD("release isp: %d", ret); + ret = device_control(s->csiphy_fd, CAM_RELEASE_DEV, s->session_handle, s->csiphy_dev_handle); + LOGD("release csiphy: %d", ret); + + ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_DESTROY_SESSION, &s->req_mgr_session_info, sizeof(s->req_mgr_session_info)); + LOGD("destroyed session: %d", ret); +} + +void cameras_close(MultiCameraState *s) { + camera_close(&s->road_cam); + camera_close(&s->wide_road_cam); + camera_close(&s->driver_cam); + + delete s->sm; + delete s->pm; +} + +// ******************* just a helper ******************* + +void handle_camera_event(CameraState *s, void *evdat) { + struct cam_req_mgr_message *event_data = (struct cam_req_mgr_message *)evdat; + + uint64_t timestamp = event_data->u.frame_msg.timestamp; + int main_id = event_data->u.frame_msg.frame_id; + int real_id = event_data->u.frame_msg.request_id; + + if (real_id != 0) { // next ready + if (real_id == 1) {s->idx_offset = main_id;} + int buf_idx = (real_id - 1) % FRAME_BUF_COUNT; + + // check for skipped frames + if (main_id > s->frame_id_last + 1 && !s->skipped) { + // realign + clear_req_queue(s->multi_cam_state->video0_fd, event_data->session_hdl, event_data->u.frame_msg.link_hdl); + enqueue_req_multi(s, real_id + 1, FRAME_BUF_COUNT - 1, 0); + s->skipped = true; + } else if (main_id == s->frame_id_last + 1) { + s->skipped = false; + } + + // check for dropped requests + if (real_id > s->request_id_last + 1) { + enqueue_req_multi(s, s->request_id_last + 1 + FRAME_BUF_COUNT, real_id - (s->request_id_last + 1), 0); + } + + // metas + s->frame_id_last = main_id; + s->request_id_last = real_id; + + auto &meta_data = s->buf.camera_bufs_metadata[buf_idx]; + meta_data.frame_id = main_id - s->idx_offset; + meta_data.timestamp_sof = timestamp; + s->exp_lock.lock(); + meta_data.gain = s->dc_gain_enabled ? s->analog_gain_frac * 2.5 : s->analog_gain_frac; + meta_data.high_conversion_gain = s->dc_gain_enabled; + meta_data.integ_lines = s->exposure_time; + meta_data.measured_grey_fraction = s->measured_grey_fraction; + meta_data.target_grey_fraction = s->target_grey_fraction; + s->exp_lock.unlock(); + + // dispatch + enqueue_req_multi(s, real_id + FRAME_BUF_COUNT, 1, 1); + } else { // not ready + // reset after half second of no response + if (main_id > s->frame_id_last + 10) { + clear_req_queue(s->multi_cam_state->video0_fd, event_data->session_hdl, event_data->u.frame_msg.link_hdl); + enqueue_req_multi(s, s->request_id_last + 1, FRAME_BUF_COUNT, 0); + s->frame_id_last = main_id; + s->skipped = true; + } + } +} + +// ******************* exposure control helpers ******************* + +void set_exposure_time_bounds(CameraState *s) { + switch (s->analog_gain) { + case 0: { + s->exposure_time_min = EXPOSURE_TIME_MIN; + s->exposure_time_max = EXPOSURE_TIME_MAX; // EXPOSURE_TIME_MIN * 4; + break; + } + case ANALOG_GAIN_MAX_IDX - 1: { + s->exposure_time_min = EXPOSURE_TIME_MIN; // EXPOSURE_TIME_MAX / 4; + s->exposure_time_max = EXPOSURE_TIME_MAX; + break; + } + default: { + // finetune margins on both ends + float k_up = sensor_analog_gains[s->analog_gain+1] / sensor_analog_gains[s->analog_gain]; + float k_down = sensor_analog_gains[s->analog_gain-1] / sensor_analog_gains[s->analog_gain]; + s->exposure_time_min = k_down * EXPOSURE_TIME_MIN * 2; + s->exposure_time_max = k_up * EXPOSURE_TIME_MAX / 2; + } + } +} + +void switch_conversion_gain(CameraState *s) { + if (!s->dc_gain_enabled) { + s->dc_gain_enabled = true; + s->analog_gain -= 4; + } else { + s->dc_gain_enabled = false; + s->analog_gain += 4; + } +} + +static void set_camera_exposure(CameraState *s, float grey_frac) { + // TODO: get stats from sensor? + float target_grey = 0.4 - ((float)(s->analog_gain + 4*s->dc_gain_enabled) / 48.0f); + float exposure_factor = 1 + 30 * pow((target_grey - grey_frac), 3); + exposure_factor = std::max(exposure_factor, 0.56f); + + if (s->camera_num != 1) { + s->ef_filtered = (1 - EF_LOWPASS_K) * s->ef_filtered + EF_LOWPASS_K * exposure_factor; + exposure_factor = s->ef_filtered; + } + + s->exp_lock.lock(); + s->measured_grey_fraction = grey_frac; + s->target_grey_fraction = target_grey; + + // always prioritize exposure time adjust + s->exposure_time *= exposure_factor; + + // switch gain if max/min exposure time is reached + // or always switch down to a lower gain when possible + bool kd = false; + if (s->analog_gain > 0) { + kd = 1.1 * s->exposure_time / (sensor_analog_gains[s->analog_gain-1] / sensor_analog_gains[s->analog_gain]) < EXPOSURE_TIME_MAX / 2; + } + + if (s->exposure_time > s->exposure_time_max) { + if (s->analog_gain < ANALOG_GAIN_MAX_IDX - 1) { + s->exposure_time = EXPOSURE_TIME_MAX / 2; + s->analog_gain += 1; + if (!s->dc_gain_enabled && sensor_analog_gains[s->analog_gain] >= 4.0) { // switch to HCG + switch_conversion_gain(s); + } + set_exposure_time_bounds(s); + } else { + s->exposure_time = s->exposure_time_max; + } + } else if (s->exposure_time < s->exposure_time_min || kd) { + if (s->analog_gain > 0) { + s->exposure_time = std::max(EXPOSURE_TIME_MIN * 2, (int)(s->exposure_time / (sensor_analog_gains[s->analog_gain-1] / sensor_analog_gains[s->analog_gain]))); + s->analog_gain -= 1; + if (s->dc_gain_enabled && sensor_analog_gains[s->analog_gain] <= 1.25) { // switch back to LCG + switch_conversion_gain(s); + } + set_exposure_time_bounds(s); + } else { + s->exposure_time = s->exposure_time_min; + } + } + + // set up config + uint16_t AG = s->analog_gain + 4; + AG = 0xFF00 + AG * 16 + AG; + s->analog_gain_frac = sensor_analog_gains[s->analog_gain]; + + s->exp_lock.unlock(); + // printf("cam %d, min %d, max %d \n", s->camera_num, s->exposure_time_min, s->exposure_time_max); + // printf("cam %d, set AG to 0x%X, S to %d, dc %d \n", s->camera_num, AG, s->exposure_time, s->dc_gain_enabled); + + struct i2c_random_wr_payload exp_reg_array[] = { + {0x3366, AG}, // analog gain + {0x3362, (uint16_t)(s->dc_gain_enabled?0x1:0x0)}, // DC_GAIN + {0x305A, 0x00F8}, // red gain + {0x3058, 0x0122}, // blue gain + {0x3056, 0x009A}, // g1 gain + {0x305C, 0x009A}, // g2 gain + {0x3012, (uint16_t)s->exposure_time}, // integ time + }; + //{0x301A, 0x091C}}; // reset + sensors_i2c(s, exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), + CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG); +} + +void camera_autoexposure(CameraState *s, float grey_frac) { + CameraExpInfo tmp = cam_exp[s->camera_num].load(); + tmp.op_id++; + tmp.grey_frac = grey_frac; + cam_exp[s->camera_num].store(tmp); +} + +static void ae_thread(MultiCameraState *s) { + CameraState *c_handles[3] = {&s->wide_road_cam, &s->road_cam, &s->driver_cam}; + + int op_id_last[3] = {0}; + CameraExpInfo cam_op[3]; + + set_thread_name("camera_settings"); + + while(!do_exit) { + for (int i=0;i<3;i++) { + cam_op[i] = cam_exp[i].load(); + if (cam_op[i].op_id != op_id_last[i]) { + set_camera_exposure(c_handles[i], cam_op[i].grey_frac); + op_id_last[i] = cam_op[i].op_id; + } + } + + util::sleep_for(50); + } +} + +void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { + common_process_driver_camera(s->sm, s->pm, c, cnt); +} + +// called by processing_thread +void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { + const CameraBuf *b = &c->buf; + + MessageBuilder msg; + auto framed = c == &s->road_cam ? msg.initEvent().initRoadCameraState() : msg.initEvent().initWideRoadCameraState(); + fill_frame_data(framed, b->cur_frame_data); + if ((c == &s->road_cam && env_send_road) || (c == &s->wide_road_cam && env_send_wide_road)) { + framed.setImage(get_frame_image(b)); + } + if (c == &s->road_cam) { + framed.setTransform(b->yuv_transform.v); + } + s->pm->send(c == &s->road_cam ? "roadCameraState" : "wideRoadCameraState", msg); + + if (cnt % 3 == 0) { + const auto [x, y, w, h] = (c == &s->wide_road_cam) ? std::tuple(96, 250, 1734, 524) : std::tuple(96, 160, 1734, 986); + const int skip = 2; + camera_autoexposure(c, set_exposure_target(b, x, x + w, skip, y, y + h, skip, (int)c->analog_gain, true, true)); + } +} + +void cameras_run(MultiCameraState *s) { + LOG("-- Starting threads"); + std::vector threads; + threads.push_back(std::thread(ae_thread, s)); + threads.push_back(start_process_thread(s, &s->road_cam, process_road_camera)); + threads.push_back(start_process_thread(s, &s->driver_cam, process_driver_camera)); + threads.push_back(start_process_thread(s, &s->wide_road_cam, process_road_camera)); + + // start devices + LOG("-- Starting devices"); + int start_reg_len = sizeof(start_reg_array) / sizeof(struct i2c_random_wr_payload); + sensors_i2c(&s->road_cam, start_reg_array, start_reg_len, CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG); + sensors_i2c(&s->wide_road_cam, start_reg_array, start_reg_len, CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG); + sensors_i2c(&s->driver_cam, start_reg_array, start_reg_len, CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG); + + // poll events + LOG("-- Dequeueing Video events"); + while (!do_exit) { + struct pollfd fds[1] = {{0}}; + + fds[0].fd = s->video0_fd; + fds[0].events = POLLPRI; + + int ret = poll(fds, std::size(fds), 1000); + if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) continue; + LOGE("poll failed (%d - %d)", ret, errno); + break; + } + + if (!fds[0].revents) continue; + + struct v4l2_event ev = {0}; + ret = ioctl(fds[0].fd, VIDIOC_DQEVENT, &ev); + if (ev.type == 0x8000000) { + struct cam_req_mgr_message *event_data = (struct cam_req_mgr_message *)ev.u.data; + // LOGD("v4l2 event: sess_hdl %d, link_hdl %d, frame_id %d, req_id %lld, timestamp 0x%llx, sof_status %d\n", event_data->session_hdl, event_data->u.frame_msg.link_hdl, event_data->u.frame_msg.frame_id, event_data->u.frame_msg.request_id, event_data->u.frame_msg.timestamp, event_data->u.frame_msg.sof_status); + // printf("sess_hdl %d, link_hdl %d, frame_id %lu, req_id %lu, timestamp 0x%lx, sof_status %d\n", event_data->session_hdl, event_data->u.frame_msg.link_hdl, event_data->u.frame_msg.frame_id, event_data->u.frame_msg.request_id, event_data->u.frame_msg.timestamp, event_data->u.frame_msg.sof_status); + + if (event_data->session_hdl == s->road_cam.req_mgr_session_info.session_hdl) { + handle_camera_event(&s->road_cam, event_data); + } else if (event_data->session_hdl == s->wide_road_cam.req_mgr_session_info.session_hdl) { + handle_camera_event(&s->wide_road_cam, event_data); + } else if (event_data->session_hdl == s->driver_cam.req_mgr_session_info.session_hdl) { + handle_camera_event(&s->driver_cam, event_data); + } else { + printf("Unknown vidioc event source\n"); + assert(false); + } + } + } + + LOG(" ************** STOPPING **************"); + + for (auto &t : threads) t.join(); + + cameras_close(s); +} diff --git a/selfdrive/camerad/cameras/camera_qcom2.h b/selfdrive/camerad/cameras/camera_qcom2.h new file mode 100644 index 000000000..469a2f858 --- /dev/null +++ b/selfdrive/camerad/cameras/camera_qcom2.h @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include + +#include + +#include "selfdrive/camerad/cameras/camera_common.h" +#include "selfdrive/common/util.h" + +#define FRAME_BUF_COUNT 4 + +#define ANALOG_GAIN_MAX_IDX 10 // 0xF is bypass +#define EXPOSURE_TIME_MIN 2 // with HDR, fastest ss +#define EXPOSURE_TIME_MAX 1904 // with HDR, slowest ss + +#define EF_LOWPASS_K 0.35 + +#define DEBAYER_LOCAL_WORKSIZE 16 + +typedef struct CameraState { + MultiCameraState *multi_cam_state; + CameraInfo ci; + + std::mutex exp_lock; + float analog_gain_frac; + uint16_t analog_gain; + bool dc_gain_enabled; + int exposure_time; + int exposure_time_min; + int exposure_time_max; + float ef_filtered; + + float measured_grey_fraction; + float target_grey_fraction; + + unique_fd sensor_fd; + unique_fd csiphy_fd; + + int camera_num; + + uint32_t session_handle; + + uint32_t sensor_dev_handle; + uint32_t isp_dev_handle; + uint32_t csiphy_dev_handle; + + int32_t link_handle; + + int buf0_handle; + int buf_handle[FRAME_BUF_COUNT]; + int sync_objs[FRAME_BUF_COUNT]; + int request_ids[FRAME_BUF_COUNT]; + int request_id_last; + int frame_id_last; + int idx_offset; + bool skipped; + + struct cam_req_mgr_session_info req_mgr_session_info; + + CameraBuf buf; +} CameraState; + +typedef struct MultiCameraState { + int device; + + unique_fd video0_fd; + unique_fd video1_fd; + unique_fd isp_fd; + int device_iommu; + int cdm_iommu; + + + CameraState road_cam; + CameraState wide_road_cam; + CameraState driver_cam; + + pthread_mutex_t isp_lock; + + SubMaster *sm; + PubMaster *pm; +} MultiCameraState; diff --git a/selfdrive/camerad/cameras/real_debayer.cl b/selfdrive/camerad/cameras/real_debayer.cl new file mode 100644 index 000000000..10cb5ae76 --- /dev/null +++ b/selfdrive/camerad/cameras/real_debayer.cl @@ -0,0 +1,209 @@ +#pragma OPENCL EXTENSION cl_khr_fp16 : enable + +const half black_level = 42.0; + +const __constant half3 color_correction[3] = { + // post wb CCM + (half3)(1.82717181, -0.31231438, 0.07307673), + (half3)(-0.5743977, 1.36858544, -0.53183455), + (half3)(-0.25277411, -0.05627105, 1.45875782), +}; + +// tone mapping params +const half cpk = 0.75; +const half cpb = 0.125; +const half cpxk = 0.0025; +const half cpxb = 0.01; + +half mf(half x, half cp) { + half rk = 9 - 100*cp; + if (x > cp) { + return (rk * (x-cp) * (1-(cpk*cp+cpb)) * (1+1/(rk*(1-cp))) / (1+rk*(x-cp))) + cpk*cp + cpb; + } else if (x < cp) { + return (rk * (x-cp) * (cpk*cp+cpb) * (1+1/(rk*cp)) / (1-rk*(x-cp))) + cpk*cp + cpb; + } else { + return x; + } +} + +half3 color_correct(half3 rgb, int ggain) { + half3 ret = (0,0,0); + half cpx = 0.01; //clamp(0.01h, 0.05h, cpxb + cpxk * min(10, ggain)); + ret += (half)rgb.x * color_correction[0]; + ret += (half)rgb.y * color_correction[1]; + ret += (half)rgb.z * color_correction[2]; + ret.x = mf(ret.x, cpx); + ret.y = mf(ret.y, cpx); + ret.z = mf(ret.z, cpx); + ret = clamp(0.0h, 255.0h, ret*255.0h); + return ret; +} + +half val_from_10(const uchar * source, int gx, int gy) { + // parse 10bit + int start = gy * FRAME_STRIDE + (5 * (gx / 4)); + int offset = gx % 4; + uint major = (uint)source[start + offset] << 2; + uint minor = (source[start + 4] >> (2 * offset)) & 3; + half pv = (half)(major + minor); + + // normalize + pv = max(0.0h, pv - black_level); + pv *= 0.00101833h; // /= (1024.0f - black_level); + + // correct vignetting + if (CAM_NUM == 1) { // fcamera + gx = (gx - RGB_WIDTH/2); + gy = (gy - RGB_HEIGHT/2); + float r = gx*gx + gy*gy; + half s; + if (r < 62500) { + s = (half)(1.0f + 0.0000008f*r); + } else if (r < 490000) { + s = (half)(0.9625f + 0.0000014f*r); + } else if (r < 1102500) { + s = (half)(1.26434f + 0.0000000000016f*r*r); + } else { + s = (half)(0.53503625f + 0.0000000000022f*r*r); + } + pv = s * pv; + } + + pv = clamp(0.0h, 1.0h, pv); + return pv; +} + +half fabs_diff(half x, half y) { + return fabs(x-y); +} + +half phi(half x) { + // detection funtion + return 2 - x; + // if (x > 1) { + // return 1 / x; + // } else { + // return 2 - x; + // } +} + +__kernel void debayer10(const __global uchar * in, + __global uchar * out, + __local half * cached, + uint ggain + ) +{ + const int x_global = get_global_id(0); + const int y_global = get_global_id(1); + + const int localRowLen = 2 + get_local_size(0); // 2 padding + const int x_local = get_local_id(0); // 0-15 + const int y_local = get_local_id(1); // 0-15 + const int localOffset = (y_local + 1) * localRowLen + x_local + 1; // max 18x18-1 + + int out_idx = 3 * x_global + 3 * y_global * RGB_WIDTH; + + half pv = val_from_10(in, x_global, y_global); + cached[localOffset] = pv; + + // don't care + if (x_global < 1 || x_global >= RGB_WIDTH - 1 || y_global < 1 || y_global >= RGB_HEIGHT - 1) { + return; + } + + // cache padding + int localColOffset = -1; + int globalColOffset = -1; + + // cache padding + if (x_local < 1) { + localColOffset = x_local; + globalColOffset = -1; + cached[(y_local + 1) * localRowLen + x_local] = val_from_10(in, x_global-1, y_global); + } else if (x_local >= get_local_size(0) - 1) { + localColOffset = x_local + 2; + globalColOffset = 1; + cached[localOffset + 1] = val_from_10(in, x_global+1, y_global); + } + + if (y_local < 1) { + cached[y_local * localRowLen + x_local + 1] = val_from_10(in, x_global, y_global-1); + if (localColOffset != -1) { + cached[y_local * localRowLen + localColOffset] = val_from_10(in, x_global+globalColOffset, y_global-1); + } + } else if (y_local >= get_local_size(1) - 1) { + cached[(y_local + 2) * localRowLen + x_local + 1] = val_from_10(in, x_global, y_global+1); + if (localColOffset != -1) { + cached[(y_local + 2) * localRowLen + localColOffset] = val_from_10(in, x_global+globalColOffset, y_global+1); + } + } + + // sync + barrier(CLK_LOCAL_MEM_FENCE); + + half d1 = cached[localOffset - localRowLen - 1]; + half d2 = cached[localOffset - localRowLen + 1]; + half d3 = cached[localOffset + localRowLen - 1]; + half d4 = cached[localOffset + localRowLen + 1]; + half n1 = cached[localOffset - localRowLen]; + half n2 = cached[localOffset + 1]; + half n3 = cached[localOffset + localRowLen]; + half n4 = cached[localOffset - 1]; + + half3 rgb; + + // a simplified version of https://opensignalprocessingjournal.com/contents/volumes/V6/TOSIGPJ-6-1/TOSIGPJ-6-1.pdf + if (x_global % 2 == 0) { + if (y_global % 2 == 0) { + rgb.y = pv; // G1(R) + half k1 = phi(fabs_diff(d1, pv) + fabs_diff(d2, pv)); + half k2 = phi(fabs_diff(d2, pv) + fabs_diff(d4, pv)); + half k3 = phi(fabs_diff(d3, pv) + fabs_diff(d4, pv)); + half k4 = phi(fabs_diff(d1, pv) + fabs_diff(d3, pv)); + // R_G1 + rgb.x = (k2*n2+k4*n4)/(k2+k4); + // B_G1 + rgb.z = (k1*n1+k3*n3)/(k1+k3); + } else { + rgb.z = pv; // B + half k1 = phi(fabs_diff(d1, d3) + fabs_diff(d2, d4)); + half k2 = phi(fabs_diff(n1, n4) + fabs_diff(n2, n3)); + half k3 = phi(fabs_diff(d1, d2) + fabs_diff(d3, d4)); + half k4 = phi(fabs_diff(n1, n2) + fabs_diff(n3, n4)); + // G_B + rgb.y = (k1*(n1+n3)*0.5+k3*(n2+n4)*0.5)/(k1+k3); + // R_B + rgb.x = (k2*(d2+d3)*0.5+k4*(d1+d4)*0.5)/(k2+k4); + } + } else { + if (y_global % 2 == 0) { + rgb.x = pv; // R + half k1 = phi(fabs_diff(d1, d3) + fabs_diff(d2, d4)); + half k2 = phi(fabs_diff(n1, n4) + fabs_diff(n2, n3)); + half k3 = phi(fabs_diff(d1, d2) + fabs_diff(d3, d4)); + half k4 = phi(fabs_diff(n1, n2) + fabs_diff(n3, n4)); + // G_R + rgb.y = (k1*(n1+n3)*0.5+k3*(n2+n4)*0.5)/(k1+k3); + // B_R + rgb.z = (k2*(d2+d3)*0.5+k4*(d1+d4)*0.5)/(k2+k4); + } else { + rgb.y = pv; // G2(B) + half k1 = phi(fabs_diff(d1, pv) + fabs_diff(d2, pv)); + half k2 = phi(fabs_diff(d2, pv) + fabs_diff(d4, pv)); + half k3 = phi(fabs_diff(d3, pv) + fabs_diff(d4, pv)); + half k4 = phi(fabs_diff(d1, pv) + fabs_diff(d3, pv)); + // R_G2 + rgb.x = (k1*n1+k3*n3)/(k1+k3); + // B_G2 + rgb.z = (k2*n2+k4*n4)/(k2+k4); + } + } + + rgb = clamp(0.0h, 1.0h, rgb); + rgb = color_correct(rgb, (int)ggain); + + out[out_idx + 0] = (uchar)(rgb.z); + out[out_idx + 1] = (uchar)(rgb.y); + out[out_idx + 2] = (uchar)(rgb.x); + +} diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 71490b1cb..0f87836ec 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -33,7 +33,7 @@ class CarController(): # STEER if (frame % P.STEER_STEP) == 0: - lkas_enabled = enabled and not CS.out.steerWarning and CS.out.vEgo > P.MIN_STEER_SPEED + lkas_enabled = enabled and not (CS.out.steerWarning or CS.out.steerError) and CS.out.vEgo > P.MIN_STEER_SPEED if lkas_enabled: new_steer = int(round(actuators.steer * P.STEER_MAX)) apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, P) diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index c9f1de8ba..f16f5e542 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -45,7 +45,8 @@ class CarState(CarStateBase): # 0 inactive, 1 active, 2 temporarily limited, 3 failed self.lkas_status = pt_cp.vl["PSCMStatus"]["LKATorqueDeliveredStatus"] - ret.steerWarning = self.lkas_status not in [0, 1] + ret.steerWarning = self.lkas_status == 2 + ret.steerError = self.lkas_status == 3 # 1 - open, 0 - closed ret.doorOpen = (pt_cp.vl["BCMDoorBeltStatus"]["FrontLeftDoor"] == 1 or diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 6f643f118..9edb1bdbf 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -73,7 +73,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kf = 0.00005 ret.mass = (2800. * CV.LB_TO_KG) + STD_CARGO_KG ret.wheelbase = 2.72 - ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable + ret.steerRatio = 12.9 tire_stiffness_factor = 0.65 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] @@ -81,7 +81,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kf = 0.00005 ret.mass = (3017. * CV.LB_TO_KG) + STD_CARGO_KG ret.wheelbase = 2.72 - ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable + ret.steerRatio = 12.9 tire_stiffness_factor = 0.65 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index dd1543246..73aa1942e 100755 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -180,7 +180,7 @@ class CarInterface(CarInterfaceBase): # 2019+ Rav4 TSS2 uses two different steering racks and specific tuning seems to be necessary. # See https://github.com/commaai/openpilot/pull/21429#issuecomment-873652891 for fw in car_fw: - if fw.ecu == "eps" and fw.fwVersion.startswith(b'\x02'): + if fw.ecu == "eps" and (fw.fwVersion.startswith(b'\x02') or fw.fwVersion in [b'8965B42181\x00\x00\x00\x00\x00\x00']): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15], [0.05]] ret.lateralTuning.pid.kf = 0.00004 break diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 92f6db919..7699a62a1 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -443,6 +443,7 @@ FW_VERSIONS = { (Ecu.engine, 0x700, None): [ b'\x018966306Q5000\x00\x00\x00\x00', b'\x018966306T3100\x00\x00\x00\x00', + b'\x018966306T3200\x00\x00\x00\x00', b'\x018966306T4100\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 15): [ @@ -847,8 +848,10 @@ FW_VERSIONS = { b'\x01F15264872300\x00\x00\x00\x00', b'\x01F15264872400\x00\x00\x00\x00', b'\x01F15264872500\x00\x00\x00\x00', + b'\x01F152648C6300\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ + b'\x01896630EA1000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630EB3000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630EB3100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', @@ -1555,6 +1558,7 @@ FW_VERSIONS = { CAR.PRIUS_TSS2: { (Ecu.engine, 0x700, None): [ b'\x028966347C8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x038966347C0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00', b'\x038966347C1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00', b'\x038966347C5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707101\x00\x00\x00\x00', b'\x038966347C5100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707101\x00\x00\x00\x00', @@ -1563,6 +1567,7 @@ FW_VERSIONS = { b'F152647500\x00\x00\x00\x00\x00\x00', b'F152647510\x00\x00\x00\x00\x00\x00', b'F152647520\x00\x00\x00\x00\x00\x00', + b'F152647521\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B47070\x00\x00\x00\x00\x00\x00', @@ -1572,6 +1577,7 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F4707000\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F4710000\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', ], }, CAR.MIRAI: { diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index d816ba397..970ba8782 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -21,7 +21,7 @@ class CarController(): self.steer_rate_limited = False - def update(self, enabled, CS, frame, actuators, visual_alert, left_lane_visible, right_lane_visible, left_lane_depart, right_lane_depart): + def update(self, enabled, CS, frame, ext_bus, actuators, visual_alert, left_lane_visible, right_lane_visible, left_lane_depart, right_lane_depart): """ Controls thread """ can_sends = [] @@ -106,7 +106,7 @@ class CarController(): if self.graMsgSentCount == 0: self.graMsgStartFramePrev = frame idx = (CS.graMsgBusCounter + 1) % 16 - can_sends.append(volkswagencan.create_mqb_acc_buttons_control(self.packer_pt, CANBUS.pt, self.graButtonStatesToSend, CS, idx)) + can_sends.append(volkswagencan.create_mqb_acc_buttons_control(self.packer_pt, ext_bus, self.graButtonStatesToSend, CS, idx)) self.graMsgSentCount += 1 if self.graMsgSentCount >= P.GRA_VBP_COUNT: self.graButtonStatesToSend = None diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index ec3fc53e3..f74484bd2 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -4,7 +4,7 @@ from selfdrive.config import Conversions as CV from selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser from opendbc.can.can_define import CANDefine -from selfdrive.car.volkswagen.values import DBC_FILES, CANBUS, TransmissionType, GearShifter, BUTTON_STATES, CarControllerParams +from selfdrive.car.volkswagen.values import DBC_FILES, CANBUS, NetworkLocation, TransmissionType, GearShifter, BUTTON_STATES, CarControllerParams class CarState(CarStateBase): def __init__(self, CP): @@ -17,7 +17,7 @@ class CarState(CarStateBase): self.hca_status_values = can_define.dv["LH_EPS_03"]["EPS_HCA_Status"] self.buttonStates = BUTTON_STATES.copy() - def update(self, pt_cp, cam_cp, trans_type): + def update(self, pt_cp, cam_cp, ext_cp, trans_type): ret = car.CarState.new_message() # Update vehicle speed and acceleration from ABS wheel speeds. ret.wheelSpeeds.fl = pt_cp.vl["ESP_19"]["ESP_VL_Radgeschw_02"] * CV.KPH_TO_MS @@ -76,18 +76,11 @@ class CarState(CarStateBase): # We use the speed preference for OP. self.displayMetricUnits = not pt_cp.vl["Einheiten_01"]["KBI_MFA_v_Einheit_02"] - # Consume blind-spot monitoring info/warning LED states, if available. The - # info signal (LED on) is enabled whenever a vehicle is detected in the - # driver's blind spot. The warning signal (LED flashing) is enabled if the - # driver shows possibly hazardous intent toward a BSM detected vehicle, by - # setting the turn signal in that direction, or (for cars with factory Lane - # Assist) approaches the lane boundary in that direction. Size of the BSM - # detection box is dynamic based on speed and road curvature. - # Refer to VW Self Study Program 890253: Volkswagen Driver Assist Systems, - # pages 32-35. + # Consume blind-spot monitoring info/warning LED states, if available. + # Infostufe: BSM LED on, Warnung: BSM LED flashing if self.CP.enableBsm: - ret.leftBlindspot = bool(pt_cp.vl["SWA_01"]["SWA_Infostufe_SWA_li"]) or bool(pt_cp.vl["SWA_01"]["SWA_Warnung_SWA_li"]) - ret.rightBlindspot = bool(pt_cp.vl["SWA_01"]["SWA_Infostufe_SWA_re"]) or bool(pt_cp.vl["SWA_01"]["SWA_Warnung_SWA_re"]) + ret.leftBlindspot = bool(ext_cp.vl["SWA_01"]["SWA_Infostufe_SWA_li"]) or bool(ext_cp.vl["SWA_01"]["SWA_Warnung_SWA_li"]) + ret.rightBlindspot = bool(ext_cp.vl["SWA_01"]["SWA_Infostufe_SWA_re"]) or bool(ext_cp.vl["SWA_01"]["SWA_Warnung_SWA_re"]) # Consume factory LDW data relevant for factory SWA (Lane Change Assist) # and capture it for forwarding to the blind spot radar controller @@ -102,8 +95,8 @@ class CarState(CarStateBase): # braking release bits are set. # Refer to VW Self Study Program 890253: Volkswagen Driver Assistance # Systems, chapter on Front Assist with Braking: Golf Family for all MQB - ret.stockFcw = bool(pt_cp.vl["ACC_10"]["AWV2_Freigabe"]) - ret.stockAeb = bool(pt_cp.vl["ACC_10"]["ANB_Teilbremsung_Freigabe"]) or bool(pt_cp.vl["ACC_10"]["ANB_Zielbremsung_Freigabe"]) + ret.stockFcw = bool(ext_cp.vl["ACC_10"]["AWV2_Freigabe"]) + ret.stockAeb = bool(ext_cp.vl["ACC_10"]["ANB_Teilbremsung_Freigabe"]) or bool(ext_cp.vl["ACC_10"]["ANB_Zielbremsung_Freigabe"]) # Update ACC radar status. accStatus = pt_cp.vl["TSK_06"]["TSK_Status"] @@ -122,7 +115,7 @@ class CarState(CarStateBase): # Update ACC setpoint. When the setpoint is zero or there's an error, the # radar sends a set-speed of ~90.69 m/s / 203mph. - ret.cruiseState.speed = pt_cp.vl["ACC_02"]["ACC_Wunschgeschw"] * CV.KPH_TO_MS + ret.cruiseState.speed = ext_cp.vl["ACC_02"]["ACC_Wunschgeschw"] * CV.KPH_TO_MS if ret.cruiseState.speed > 90: ret.cruiseState.speed = 0 @@ -180,7 +173,6 @@ class CarState(CarStateBase): ("ESP_Fahrer_bremst", "ESP_05", 0), # Brake pedal pressed ("ESP_Bremsdruck", "ESP_05", 0), # Brake pressure applied ("MO_Fahrpedalrohwert_01", "Motor_20", 0), # Accelerator pedal value - ("MO_Kuppl_schalter", "Motor_14", 0), # Clutch switch ("EPS_Lenkmoment", "LH_EPS_03", 0), # Absolute driver torque input ("EPS_VZ_Lenkmoment", "LH_EPS_03", 0), # Driver torque input sign ("EPS_HCA_Status", "LH_EPS_03", 3), # EPS HCA control status @@ -213,7 +205,6 @@ class CarState(CarStateBase): ("ESP_02", 50), # From J104 ABS/ESP controller ("GRA_ACC_01", 33), # From J533 CAN gateway (via LIN from steering wheel controls) ("Gateway_72", 10), # From J533 CAN gateway (aggregated data) - ("Motor_14", 10), # From J623 Engine control module ("Airbag_02", 5), # From J234 Airbag control module ("Kombi_01", 2), # From J285 Instrument cluster ("Blinkmodi_02", 1), # From J519 BCM (sent at 1Hz when no lights active, 50Hz when active) @@ -231,13 +222,13 @@ class CarState(CarStateBase): ("BCM1_Rueckfahrlicht_Schalter", "Gateway_72", 0)] # Reverse light from BCM checks += [("Motor_14", 10)] # From J623 Engine control module - # TODO: Detect ACC radar bus location - signals += MqbExtraSignals.fwd_radar_signals - checks += MqbExtraSignals.fwd_radar_checks - # TODO: Detect BSM radar bus location - if CP.enableBsm: - signals += MqbExtraSignals.bsm_radar_signals - checks += MqbExtraSignals.bsm_radar_checks + if CP.networkLocation == NetworkLocation.fwdCamera: + # Radars are here on CANBUS.pt + signals += MqbExtraSignals.fwd_radar_signals + checks += MqbExtraSignals.fwd_radar_checks + if CP.enableBsm: + signals += MqbExtraSignals.bsm_radar_signals + checks += MqbExtraSignals.bsm_radar_checks return CANParser(DBC_FILES.mqb, signals, checks, CANBUS.pt) @@ -258,6 +249,14 @@ class CarState(CarStateBase): ("LDW_02", 10) # From R242 Driver assistance camera ] + if CP.networkLocation == NetworkLocation.gateway: + # Radars are here on CANBUS.cam + signals += MqbExtraSignals.fwd_radar_signals + checks += MqbExtraSignals.fwd_radar_checks + if CP.enableBsm: + signals += MqbExtraSignals.bsm_radar_signals + checks += MqbExtraSignals.bsm_radar_checks + return CANParser(DBC_FILES.mqb, signals, checks, CANBUS.cam) class MqbExtraSignals: diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 1735a8dc7..802b5e27b 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,5 +1,5 @@ from cereal import car -from selfdrive.car.volkswagen.values import CAR, BUTTON_STATES, TransmissionType, GearShifter +from selfdrive.car.volkswagen.values import CAR, BUTTON_STATES, CANBUS, NetworkLocation, TransmissionType, GearShifter from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint from selfdrive.car.interfaces import CarInterfaceBase @@ -13,6 +13,13 @@ class CarInterface(CarInterfaceBase): self.displayMetricUnitsPrev = None self.buttonStatesPrev = BUTTON_STATES.copy() + if CP.networkLocation == NetworkLocation.fwdCamera: + self.ext_bus = CANBUS.pt + self.cp_ext = self.cp + else: + self.ext_bus = CANBUS.cam + self.cp_ext = self.cp_cam + @staticmethod def compute_gb(accel, speed): return float(accel) / 4.0 @@ -20,29 +27,30 @@ class CarInterface(CarInterfaceBase): @staticmethod def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None): ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - - # VW port is a community feature, since we don't own one to test + ret.carName = "volkswagen" ret.communityFeature = True + ret.radarOffCan = True if True: # pylint: disable=using-constant-test - # Set common MQB parameters that will apply globally - ret.carName = "volkswagen" - ret.radarOffCan = True + # Set global MQB parameters ret.safetyModel = car.CarParams.SafetyModel.volkswagen - ret.steerActuatorDelay = 0.05 + ret.enableBsm = 0x30F in fingerprint[0] - if 0xAD in fingerprint[0]: - # Getriebe_11 detected: traditional automatic or DSG gearbox + if 0xAD in fingerprint[0]: # Getriebe_11 ret.transmissionType = TransmissionType.automatic - elif 0x187 in fingerprint[0]: - # EV_Gearshift detected: e-Golf or similar direct-drive electric + elif 0x187 in fingerprint[0]: # EV_Gearshift ret.transmissionType = TransmissionType.direct - else: - # No trans message at all, must be a true stick-shift manual + else: # No trans message at all, must be a true stick-shift manual ret.transmissionType = TransmissionType.manual + if 0x86 in fingerprint[1]: # LWI_01 seen on bus 1, we're wired to the CAN gateway + ret.networkLocation = NetworkLocation.gateway + else: # We're wired to the LKAS camera + ret.networkLocation = NetworkLocation.fwdCamera + # Global tuning defaults, can be overridden per-vehicle + ret.steerActuatorDelay = 0.05 ret.steerRateCost = 1.0 ret.steerLimitTimer = 0.4 ret.steerRatio = 15.6 # Let the params learner figure this out @@ -56,85 +64,72 @@ class CarInterface(CarInterfaceBase): # Per-chassis tuning values, override tuning defaults here if desired if candidate == CAR.ATLAS_MK1: - # Averages of all CA Atlas variants ret.mass = 2011 + STD_CARGO_KG ret.wheelbase = 2.98 elif candidate == CAR.GOLF_MK7: - # Averages of all AU Golf variants ret.mass = 1397 + STD_CARGO_KG ret.wheelbase = 2.62 elif candidate == CAR.JETTA_MK7: - # Averages of all BU Jetta variants ret.mass = 1328 + STD_CARGO_KG ret.wheelbase = 2.71 elif candidate == CAR.PASSAT_MK8: - # Averages of all 3C Passat variants ret.mass = 1551 + STD_CARGO_KG ret.wheelbase = 2.79 + elif candidate == CAR.TCROSS_MK1: + ret.mass = 1150 + STD_CARGO_KG + ret.wheelbase = 2.60 + elif candidate == CAR.TIGUAN_MK2: - # Average of SWB and LWB variants ret.mass = 1715 + STD_CARGO_KG ret.wheelbase = 2.74 elif candidate == CAR.TOURAN_MK2: - # Average of SWB and LWB variants ret.mass = 1516 + STD_CARGO_KG ret.wheelbase = 2.79 elif candidate == CAR.AUDI_A3_MK3: - # Averages of all 8V A3 variants ret.mass = 1335 + STD_CARGO_KG ret.wheelbase = 2.61 elif candidate == CAR.AUDI_Q2_MK1: - # Averages of all GA Q2 variants ret.mass = 1205 + STD_CARGO_KG ret.wheelbase = 2.61 elif candidate == CAR.SEAT_ATECA_MK1: - # Averages of all 5F Ateca variants ret.mass = 1900 + STD_CARGO_KG ret.wheelbase = 2.64 elif candidate == CAR.SEAT_LEON_MK3: - # Averages of all 5F Leon variants ret.mass = 1227 + STD_CARGO_KG ret.wheelbase = 2.64 elif candidate == CAR.SKODA_KODIAQ_MK1: - # Averages of all 5N Kodiaq variants ret.mass = 1569 + STD_CARGO_KG ret.wheelbase = 2.79 elif candidate == CAR.SKODA_OCTAVIA_MK3: - # Averages of all 5E/NE Octavia variants ret.mass = 1388 + STD_CARGO_KG ret.wheelbase = 2.68 elif candidate == CAR.SKODA_SCALA_MK1: - # Averages of all NW Scala variants ret.mass = 1192 + STD_CARGO_KG ret.wheelbase = 2.65 elif candidate == CAR.SKODA_SUPERB_MK3: - # Averages of all 3V/NP Scala variants ret.mass = 1505 + STD_CARGO_KG ret.wheelbase = 2.84 - ret.centerToFront = ret.wheelbase * 0.45 - - ret.enableBsm = 0x30F in fingerprint[0] - # TODO: get actual value, for now starting with reasonable value for # civic and scaling by mass and wheelbase ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors + ret.centerToFront = ret.wheelbase * 0.45 ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, tire_stiffness_factor=tire_stiffness_factor) @@ -150,7 +145,7 @@ class CarInterface(CarInterfaceBase): self.cp.update_strings(can_strings) self.cp_cam.update_strings(can_strings) - ret = self.CS.update(self.cp, self.cp_cam, self.CP.transmissionType) + ret = self.CS.update(self.cp, self.cp_cam, self.cp_ext, self.CP.transmissionType) ret.canValid = self.cp.can_valid and self.cp_cam.can_valid ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False @@ -186,7 +181,7 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, + can_sends = self.CC.update(c.enabled, self.CS, self.frame, self.ext_bus, c.actuators, c.hudControl.visualAlert, c.hudControl.leftLaneVisible, c.hudControl.rightLaneVisible, diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 3bad8fc3e..63cd8d291 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -5,7 +5,11 @@ from typing import Dict from cereal import car from selfdrive.car import dbc_dict + Ecu = car.CarParams.Ecu +NetworkLocation = car.CarParams.NetworkLocation +TransmissionType = car.CarParams.TransmissionType +GearShifter = car.CarState.GearShifter class CarControllerParams: HCA_STEP = 2 # HCA_01 message frequency 50Hz @@ -34,9 +38,6 @@ class DBC_FILES: DBC = defaultdict(lambda: dbc_dict(DBC_FILES.mqb, None)) # type: Dict[str, Dict[str, str]] -TransmissionType = car.CarParams.TransmissionType -GearShifter = car.CarState.GearShifter - BUTTON_STATES = { "accelCruise": False, "decelCruise": False, @@ -68,6 +69,7 @@ class CAR: GOLF_MK7 = "VOLKSWAGEN GOLF 7TH GEN" # Chassis 5G/AU/BA/BE, Mk7 VW Golf and variants JETTA_MK7 = "VOLKSWAGEN JETTA 7TH GEN" # Chassis BU, Mk7 Jetta PASSAT_MK8 = "VOLKSWAGEN PASSAT 8TH GEN" # Chassis 3G, Mk8 Passat and variants + TCROSS_MK1 = "VOLKSWAGEN T-CROSS 1ST GEN" # Chassis C1, Mk1 VW T-Cross SWB and LWB variants TIGUAN_MK2 = "VOLKSWAGEN TIGUAN 2ND GEN" # Chassis AD/BW, Mk2 VW Tiguan and variants TOURAN_MK2 = "VOLKSWAGEN TOURAN 2ND GEN" # Chassis 1T, Mk2 VW Touran and variants AUDI_A3_MK3 = "AUDI A3 3RD GEN" # Chassis 8V/FF, Mk3 Audi A3 and variants @@ -95,6 +97,7 @@ FW_VERSIONS = { b'\xf1\x8703H906026F \xf1\x896696', b'\xf1\x8703H906026F \xf1\x899970', b'\xf1\x8703H906026S \xf1\x896693', + b'\xf1\x8703H906026S \xf1\x899970', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158A \xf1\x893387', @@ -137,6 +140,7 @@ FW_VERSIONS = { b'\xf1\x875G0906259N \xf1\x890003', b'\xf1\x875G0906259Q \xf1\x890002', b'\xf1\x875G0906259Q \xf1\x892313', + b'\xf1\x878V0906259H \xf1\x890002', b'\xf1\x878V0906259J \xf1\x890003', b'\xf1\x878V0906259K \xf1\x890001', b'\xf1\x878V0906259P \xf1\x890001', @@ -154,6 +158,7 @@ FW_VERSIONS = { b'\xf1\x870CW300045 \xf1\x894531', b'\xf1\x870CW300047D \xf1\x895261', b'\xf1\x870CW300048J \xf1\x890611', + b'\xf1\x870D9300012 \xf1\x894904', b'\xf1\x870D9300012 \xf1\x894913', b'\xf1\x870D9300012 \xf1\x894937', b'\xf1\x870D9300012 \xf1\x895045', @@ -189,6 +194,7 @@ FW_VERSIONS = { b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02324230011211200061104171724102491132111', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02324230011211200621143171724112491132111', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011211200061104171717101791132111', + b'\xf1\x875Q0959655T \xf1\x890825\xf1\x82\023271200111312--071104171837103791132111', b'\xf1\x875Q0959655T \xf1\x890830\xf1\x82\x13271100111312--071104171826102691131211', b'\xf1\x875QD959655 \xf1\x890388\xf1\x82\x111413001113120006110417121D101D9112', ], @@ -293,6 +299,23 @@ FW_VERSIONS = { b'\xf1\x875Q0907572R \xf1\x890771', ], }, + CAR.TCROSS_MK1: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704C906025AK\xf1\x897053', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300050E \xf1\x891903', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\02212130411110411--04041104141311152H14', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x872Q1909144M \xf1\x896041', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572T \xf1\x890383', + ], + }, CAR.TIGUAN_MK2: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704L906026EJ\xf1\x893661', @@ -467,15 +490,18 @@ FW_VERSIONS = { b'\xf1\x8704E906027HD\xf1\x893742', b'\xf1\x8704L906021DT\xf1\x898127', b'\xf1\x8704L906026BS\xf1\x891541', + b'\xf1\x875G0906259C \xf1\x890002', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870CW300043B \xf1\x891601', + b'\xf1\x870D9300041C \xf1\x894936', b'\xf1\x870D9300041J \xf1\x894902', b'\xf1\x870D9300041P \xf1\x894507', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AC\xf1\x890200\xf1\x82\r11120011100010022212110200', b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11120011100010022212110200', + b'\xf1\x873Q0959655AQ\xf1\x890200\xf1\x82\r11120011100010312212113100', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e3221003221002105755331052100', ], (Ecu.eps, 0x712, None): [ diff --git a/selfdrive/common/params.cc b/selfdrive/common/params.cc index a14076513..ad5121893 100644 --- a/selfdrive/common/params.cc +++ b/selfdrive/common/params.cc @@ -146,7 +146,7 @@ private: }; std::unordered_map keys = { - {"AccessToken", CLEAR_ON_MANAGER_START}, + {"AccessToken", CLEAR_ON_MANAGER_START | DONT_LOG}, {"ApiCache_DriveStats", PERSISTENT}, {"ApiCache_Device", PERSISTENT}, {"ApiCache_Owner", PERSISTENT}, @@ -165,7 +165,7 @@ std::unordered_map keys = { {"CompletedTrainingVersion", PERSISTENT}, {"DisablePowerDown", PERSISTENT}, {"DisableUpdates", PERSISTENT}, - {"EnableWideCamera", PERSISTENT}, + {"EnableWideCamera", CLEAR_ON_MANAGER_START}, {"DoUninstall", CLEAR_ON_MANAGER_START}, {"DongleId", PERSISTENT}, {"GitDiff", PERSISTENT}, @@ -186,12 +186,12 @@ std::unordered_map keys = { {"IsTakingSnapshot", CLEAR_ON_MANAGER_START}, {"IsUpdateAvailable", CLEAR_ON_MANAGER_START}, {"UploadRaw", PERSISTENT}, - {"LastAthenaPingTime", PERSISTENT}, + {"LastAthenaPingTime", CLEAR_ON_MANAGER_START}, {"LastGPSPosition", PERSISTENT}, {"LastUpdateException", PERSISTENT}, {"LastUpdateTime", PERSISTENT}, {"LiveParameters", PERSISTENT}, - {"MapboxToken", PERSISTENT}, + {"MapboxToken", PERSISTENT | DONT_LOG}, {"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"NavSettingTime24h", PERSISTENT}, {"OpenpilotEnabledToggle", PERSISTENT}, @@ -199,6 +199,7 @@ std::unordered_map keys = { {"PandaFirmwareHex", CLEAR_ON_MANAGER_START | CLEAR_ON_PANDA_DISCONNECT}, {"PandaDongleId", CLEAR_ON_MANAGER_START | CLEAR_ON_PANDA_DISCONNECT}, {"Passive", PERSISTENT}, + {"PrimeRedirected", PERSISTENT}, {"RecordFront", PERSISTENT}, {"RecordFrontLock", PERSISTENT}, // for the internal fleet {"ReleaseNotes", PERSISTENT}, @@ -245,6 +246,10 @@ bool Params::checkKey(const std::string &key) { return keys.find(key) != keys.end(); } +ParamKeyType Params::getKeyType(const std::string &key) { + return static_cast(keys[key]); +} + int Params::put(const char* key, const char* value, size_t value_size) { // Information about safely and atomically writing a file: https://lwn.net/Articles/457667/ // 1) Create temp file diff --git a/selfdrive/common/params.h b/selfdrive/common/params.h index bc1372295..a85a73b0a 100644 --- a/selfdrive/common/params.h +++ b/selfdrive/common/params.h @@ -12,7 +12,8 @@ enum ParamKeyType { CLEAR_ON_PANDA_DISCONNECT = 0x08, CLEAR_ON_IGNITION_ON = 0x10, CLEAR_ON_IGNITION_OFF = 0x20, - ALL = 0x02 | 0x04 | 0x08 | 0x10 | 0x20 + DONT_LOG = 0x40, + ALL = 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 }; class Params { @@ -24,6 +25,7 @@ public: Params(const std::string &path); bool checkKey(const std::string &key); + ParamKeyType getKeyType(const std::string &key); // Delete a value int remove(const char *key); diff --git a/selfdrive/common/version.h b/selfdrive/common/version.h index 45bf96517..617206a18 100644 --- a/selfdrive/common/version.h +++ b/selfdrive/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.8.6" +#define COMMA_VERSION "0.8.7" diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 344d1b15a..a56ac18a5 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -324,7 +324,7 @@ class Controls: self.sm.update(0) all_valid = CS.canValid and self.sm.all_alive_and_valid() - if not self.initialized and (all_valid or self.sm.frame * DT_CTRL > 2.0): + if not self.initialized and (all_valid or self.sm.frame * DT_CTRL > 3.5): self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan']) self.initialized = True Params().put_bool("ControlsReady", True) diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index ef150709a..c7af80333 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -545,7 +545,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # current GPS position. This alert is thrown when the localizer is reset # more often than expected. EventName.localizerMalfunction: { - ET.PERMANENT: NormalPermanentAlert("Localizer unstable", "Contact Support"), + ET.PERMANENT: NormalPermanentAlert("Sensor Malfunction", "Contact Support"), }, # ********** events that affect controls state transitions ********** diff --git a/selfdrive/controls/lib/lead_mpc.py b/selfdrive/controls/lib/lead_mpc.py index da5bbc25f..ee8ea98e7 100644 --- a/selfdrive/controls/lib/lead_mpc.py +++ b/selfdrive/controls/lib/lead_mpc.py @@ -95,7 +95,7 @@ class LeadMpc(): # Reset if NaN or goes through lead car crashing = any(lead - ego < -50 for (lead, ego) in zip(self.mpc_solution[0].x_l, self.mpc_solution[0].x_ego)) nans = any(math.isnan(x) for x in self.mpc_solution[0].v_ego) - backwards = min(self.mpc_solution[0].v_ego) < -0.01 + backwards = min(self.mpc_solution[0].v_ego) < -0.15 if ((backwards or crashing) and self.prev_lead_status) or nans: if t > self.last_cloudlog_t + 5.0: diff --git a/selfdrive/controls/lib/lead_mpc_lib/generator.cpp b/selfdrive/controls/lib/lead_mpc_lib/generator.cpp index f633db71e..9941846a5 100644 --- a/selfdrive/controls/lib/lead_mpc_lib/generator.cpp +++ b/selfdrive/controls/lib/lead_mpc_lib/generator.cpp @@ -69,7 +69,7 @@ int main( ) ocp.minimizeLSQ(Q, h); ocp.minimizeLSQEndTerm(QN, hN); - ocp.subjectTo( 0.0 <= v_ego); + ocp.subjectTo( -0.1 <= v_ego); ocp.setNOD(2); OCPexport mpc(ocp); diff --git a/selfdrive/controls/lib/lead_mpc_lib/lib_mpc_export/acado_solver.c b/selfdrive/controls/lib/lead_mpc_lib/lib_mpc_export/acado_solver.c index 8cfc06f3b..6dd373b8d 100644 --- a/selfdrive/controls/lib/lead_mpc_lib/lib_mpc_export/acado_solver.c +++ b/selfdrive/controls/lib/lead_mpc_lib/lib_mpc_export/acado_solver.c @@ -4227,64 +4227,64 @@ acadoWorkspace.ub[0] = acadoWorkspace.Dx0[0]; acadoWorkspace.ub[1] = acadoWorkspace.Dx0[1]; acadoWorkspace.ub[2] = acadoWorkspace.Dx0[2]; tmp = acadoVariables.x[4] + acadoWorkspace.d[1]; -acadoWorkspace.lbA[0] = - tmp; +acadoWorkspace.lbA[0] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[0] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[7] + acadoWorkspace.d[4]; -acadoWorkspace.lbA[1] = - tmp; +acadoWorkspace.lbA[1] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[1] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[10] + acadoWorkspace.d[7]; -acadoWorkspace.lbA[2] = - tmp; +acadoWorkspace.lbA[2] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[2] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[13] + acadoWorkspace.d[10]; -acadoWorkspace.lbA[3] = - tmp; +acadoWorkspace.lbA[3] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[3] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[16] + acadoWorkspace.d[13]; -acadoWorkspace.lbA[4] = - tmp; +acadoWorkspace.lbA[4] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[4] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[19] + acadoWorkspace.d[16]; -acadoWorkspace.lbA[5] = - tmp; +acadoWorkspace.lbA[5] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[5] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[22] + acadoWorkspace.d[19]; -acadoWorkspace.lbA[6] = - tmp; +acadoWorkspace.lbA[6] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[6] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[25] + acadoWorkspace.d[22]; -acadoWorkspace.lbA[7] = - tmp; +acadoWorkspace.lbA[7] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[7] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[28] + acadoWorkspace.d[25]; -acadoWorkspace.lbA[8] = - tmp; +acadoWorkspace.lbA[8] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[8] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[31] + acadoWorkspace.d[28]; -acadoWorkspace.lbA[9] = - tmp; +acadoWorkspace.lbA[9] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[9] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[34] + acadoWorkspace.d[31]; -acadoWorkspace.lbA[10] = - tmp; +acadoWorkspace.lbA[10] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[10] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[37] + acadoWorkspace.d[34]; -acadoWorkspace.lbA[11] = - tmp; +acadoWorkspace.lbA[11] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[11] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[40] + acadoWorkspace.d[37]; -acadoWorkspace.lbA[12] = - tmp; +acadoWorkspace.lbA[12] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[12] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[43] + acadoWorkspace.d[40]; -acadoWorkspace.lbA[13] = - tmp; +acadoWorkspace.lbA[13] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[13] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[46] + acadoWorkspace.d[43]; -acadoWorkspace.lbA[14] = - tmp; +acadoWorkspace.lbA[14] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[14] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[49] + acadoWorkspace.d[46]; -acadoWorkspace.lbA[15] = - tmp; +acadoWorkspace.lbA[15] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[15] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[52] + acadoWorkspace.d[49]; -acadoWorkspace.lbA[16] = - tmp; +acadoWorkspace.lbA[16] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[16] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[55] + acadoWorkspace.d[52]; -acadoWorkspace.lbA[17] = - tmp; +acadoWorkspace.lbA[17] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[17] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[58] + acadoWorkspace.d[55]; -acadoWorkspace.lbA[18] = - tmp; +acadoWorkspace.lbA[18] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[18] = (real_t)1.0000000000000000e+12 - tmp; tmp = acadoVariables.x[61] + acadoWorkspace.d[58]; -acadoWorkspace.lbA[19] = - tmp; +acadoWorkspace.lbA[19] = (real_t)-1.0000000000000001e-01 - tmp; acadoWorkspace.ubA[19] = (real_t)1.0000000000000000e+12 - tmp; } diff --git a/selfdrive/hardware/tici/agnos.json b/selfdrive/hardware/tici/agnos.json new file mode 100644 index 000000000..25eeada08 --- /dev/null +++ b/selfdrive/hardware/tici/agnos.json @@ -0,0 +1,52 @@ +[ + { + "name": "boot", + "url": "https://commadist.azureedge.net/agnosupdate/boot-54fc7edceeabff713b51dd4d4931eb920344aa3f8f8e0f153baad1fb5a15ef19.img.xz", + "hash": "54fc7edceeabff713b51dd4d4931eb920344aa3f8f8e0f153baad1fb5a15ef19", + "hash_raw": "54fc7edceeabff713b51dd4d4931eb920344aa3f8f8e0f153baad1fb5a15ef19", + "size": 14772224, + "sparse": false, + "full_check": true, + "has_ab": true + }, + { + "name": "abl", + "url": "https://commadist.azureedge.net/agnosupdate/abl-ab4068f005ed9cb7fbca55c6d658880df1abfb1a4e6afb64f3fc5e64dac6fc82.img.xz", + "hash": "ab4068f005ed9cb7fbca55c6d658880df1abfb1a4e6afb64f3fc5e64dac6fc82", + "hash_raw": "ab4068f005ed9cb7fbca55c6d658880df1abfb1a4e6afb64f3fc5e64dac6fc82", + "size": 274432, + "sparse": false, + "full_check": true, + "has_ab": true + }, + { + "name": "xbl", + "url": "https://commadist.azureedge.net/agnosupdate/xbl-2b1b67aa918cd127f2b0b4ed0a372f3a93676cf9d270bd3e56329516efdc5a35.img.xz", + "hash": "2b1b67aa918cd127f2b0b4ed0a372f3a93676cf9d270bd3e56329516efdc5a35", + "hash_raw": "2b1b67aa918cd127f2b0b4ed0a372f3a93676cf9d270bd3e56329516efdc5a35", + "size": 3670016, + "sparse": false, + "full_check": true, + "has_ab": true + }, + { + "name": "xbl_config", + "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-3aa926394b4cec464300bfc0e7ab77d50889b38041138c60cd84c397930b38ad.img.xz", + "hash": "3aa926394b4cec464300bfc0e7ab77d50889b38041138c60cd84c397930b38ad", + "hash_raw": "3aa926394b4cec464300bfc0e7ab77d50889b38041138c60cd84c397930b38ad", + "size": 364544, + "sparse": false, + "full_check": true, + "has_ab": true + }, + { + "name": "system", + "url": "https://commadist.azureedge.net/agnosupdate/system-22fc75dd29fc0a3eccf7f899f5397d61b63166c194e4888dfd59211828a81f34.img.xz", + "hash": "231c269ed636758763e3e1365b2bedaf15547b2a7ff3c07617cbe4c3aed8b3a5", + "hash_raw": "22fc75dd29fc0a3eccf7f899f5397d61b63166c194e4888dfd59211828a81f34", + "size": 10737418240, + "sparse": true, + "full_check": false, + "has_ab": true + } +] diff --git a/selfdrive/hardware/tici/agnos.py b/selfdrive/hardware/tici/agnos.py new file mode 100755 index 000000000..d3b2ca96e --- /dev/null +++ b/selfdrive/hardware/tici/agnos.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 +import json +import lzma +import hashlib +import requests +import struct +import subprocess +import os +from typing import Generator + +SPARSE_CHUNK_FMT = struct.Struct('H2xI4x') + + +class StreamingDecompressor: + def __init__(self, url: str) -> None: + self.buf = b"" + + self.req = requests.get(url, stream=True, headers={'Accept-Encoding': None}) + self.it = self.req.iter_content(chunk_size=1024 * 1024) + self.decompressor = lzma.LZMADecompressor(format=lzma.FORMAT_AUTO) + self.eof = False + self.sha256 = hashlib.sha256() + + def read(self, length: int) -> bytes: + while len(self.buf) < length: + self.req.raise_for_status() + + try: + compressed = next(self.it) + except StopIteration: + self.eof = True + break + out = self.decompressor.decompress(compressed) + self.buf += out + + result = self.buf[:length] + self.buf = self.buf[length:] + + self.sha256.update(result) + return result + + +def unsparsify(f: StreamingDecompressor) -> Generator[bytes, None, None]: + # https://source.android.com/devices/bootloader/images#sparse-format + magic = struct.unpack("I", f.read(4))[0] + assert(magic == 0xed26ff3a) + + # Version + major = struct.unpack("H", f.read(2))[0] + minor = struct.unpack("H", f.read(2))[0] + assert(major == 1 and minor == 0) + + f.read(2) # file header size + f.read(2) # chunk header size + + block_sz = struct.unpack("I", f.read(4))[0] + f.read(4) # total blocks + num_chunks = struct.unpack("I", f.read(4))[0] + f.read(4) # crc checksum + + for _ in range(num_chunks): + chunk_type, out_blocks = SPARSE_CHUNK_FMT.unpack(f.read(12)) + + if chunk_type == 0xcac1: # Raw + # TODO: yield in smaller chunks. Yielding only block_sz is too slow. Largest observed data chunk is 252 MB. + yield f.read(out_blocks * block_sz) + elif chunk_type == 0xcac2: # Fill + filler = f.read(4) * (block_sz // 4) + for _ in range(out_blocks): + yield filler + elif chunk_type == 0xcac3: # Don't care + yield b"" + else: + raise Exception("Unhandled sparse chunk type") + + +def get_target_slot_number() -> int: + current_slot = subprocess.check_output(["abctl", "--boot_slot"], encoding='utf-8').strip() + return 1 if current_slot == "_a" else 0 + + +def slot_number_to_suffix(slot_number: int) -> str: + assert slot_number in (0, 1) + return '_a' if slot_number == 0 else '_b' + + +def get_partition_path(target_slot_number: int, partition: dict) -> str: + path = f"/dev/disk/by-partlabel/{partition['name']}" + + if partition.get('has_ab', True): + path += slot_number_to_suffix(target_slot_number) + + return path + + +def verify_partition(target_slot_number: int, partition: dict) -> bool: + full_check = partition['full_check'] + path = get_partition_path(target_slot_number, partition) + partition_size = partition['size'] + + with open(path, 'rb+') as out: + if full_check: + raw_hash = hashlib.sha256() + + pos = 0 + chunk_size = 1024 * 1024 + while pos < partition_size: + n = min(chunk_size, partition_size - pos) + raw_hash.update(out.read(n)) + pos += n + + return raw_hash.hexdigest().lower() == partition['hash_raw'].lower() + else: + out.seek(partition_size) + return out.read(64) == partition['hash_raw'].lower().encode() + + +def clear_partition_hash(target_slot_number: int, partition: dict) -> None: + path = get_partition_path(target_slot_number, partition) + with open(path, 'wb+') as out: + partition_size = partition['size'] + + out.seek(partition_size) + out.write(b"\x00" * 64) + os.sync() + + +def flash_partition(target_slot_number: int, partition: dict, cloudlog): + cloudlog.info(f"Downloading and writing {partition['name']}") + + if verify_partition(target_slot_number, partition): + cloudlog.info(f"Already flashed {partition['name']}") + return + + downloader = StreamingDecompressor(partition['url']) + + # Clear hash before flashing in case we get interrupted + full_check = partition['full_check'] + if not full_check: + clear_partition_hash(target_slot_number, partition) + + path = get_partition_path(target_slot_number, partition) + with open(path, 'wb+') as out: + partition_size = partition['size'] + + # Flash partition + if partition['sparse']: + raw_hash = hashlib.sha256() + for chunk in unsparsify(downloader): + raw_hash.update(chunk) + out.write(chunk) + p = int(out.tell() / partition_size * 100) + print(f"Installing {partition['name']}: {p}") + + if raw_hash.hexdigest().lower() != partition['hash_raw'].lower(): + raise Exception(f"Unsparse hash mismatch '{raw_hash.hexdigest().lower()}'") + else: + while not downloader.eof: + out.write(downloader.read(1024 * 1024)) + + if downloader.sha256.hexdigest().lower() != partition['hash'].lower(): + raise Exception("Uncompressed hash mismatch") + + if out.tell() != partition['size']: + raise Exception("Uncompressed size mismatch") + + # Write hash after successfull flash + os.sync() + if not full_check: + out.write(partition['hash_raw'].lower().encode()) + + +def swap(manifest_path: str, target_slot_number: int, cloudlog) -> None: + update = json.load(open(manifest_path)) + for partition in update: + if not partition.get('full_check', False): + clear_partition_hash(target_slot_number, partition) + + while True: + out = subprocess.check_output(f"abctl --set_active {target_slot_number}", shell=True, stderr=subprocess.STDOUT, encoding='utf8') + if ("No such file or directory" not in out) and ("lun as boot lun" in out): + cloudlog.info(f"Swap successfull {out}") + break + else: + cloudlog.error(f"Swap failed {out}") + + +def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog) -> None: + update = json.load(open(manifest_path)) + + cloudlog.info(f"Target slot {target_slot_number}") + + # set target slot as unbootable + os.system(f"abctl --set_unbootable {target_slot_number}") + + for partition in update: + success = False + + for retries in range(10): + try: + flash_partition(target_slot_number, partition, cloudlog) + success = True + break + + except requests.exceptions.RequestException: + cloudlog.exception("Failed") + cloudlog.info(f"Failed to download {partition['name']}, retrying ({retries})") + time.sleep(10) + + if not success: + cloudlog.info(f"Failed to flash {partition['name']}, aborting") + raise Exception("Maximum retries exceeded") + + cloudlog.info(f"AGNOS ready on slot {target_slot_number}") + + +def verify_agnos_update(manifest_path: str, target_slot_number: int) -> bool: + update = json.load(open(manifest_path)) + return all(verify_partition(target_slot_number, partition) for partition in update) + + +if __name__ == "__main__": + import logging + import time + import argparse + + parser = argparse.ArgumentParser(description="Flash and verify AGNOS update", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument("--verify", action="store_true", help="Verify and perform swap if update ready") + parser.add_argument("--swap", action="store_true", help="Verify and perform swap, downloads if necessary") + parser.add_argument("manifest", help="Manifest json") + args = parser.parse_args() + + logging.basicConfig(level=logging.INFO) + + target_slot_number = get_target_slot_number() + if args.verify: + if verify_agnos_update(args.manifest, target_slot_number): + swap(args.manifest, target_slot_number, logging) + exit(0) + exit(1) + elif args.swap: + while not verify_agnos_update(args.manifest, target_slot_number): + logging.error("Verification failed. Flashing AGNOS") + flash_agnos_update(args.manifest, target_slot_number, logging) + + logging.warning(f"Verification succeeded. Swapping to slot {target_slot_number}") + swap(args.manifest, target_slot_number, logging) + else: + flash_agnos_update(args.manifest, target_slot_number, logging) diff --git a/selfdrive/hardware/tici/amplifier.py b/selfdrive/hardware/tici/amplifier.py index b0c201c07..99b6d0598 100755 --- a/selfdrive/hardware/tici/amplifier.py +++ b/selfdrive/hardware/tici/amplifier.py @@ -31,7 +31,7 @@ BASE_CONFIG = [ AmpConfig("Enable PLL2", 0b1, 0x1A, 7, 0b10000000), AmpConfig("DAI1: I2S mode", 0b00100, 0x14, 2, 0b01111100), AmpConfig("DAI2: I2S mode", 0b00100, 0x1C, 2, 0b01111100), - AmpConfig("Right speaker output volume", 0x1F, 0x3E, 0, 0b00011111), + AmpConfig("Right speaker output volume", 0x1a, 0x3E, 0, 0b00011111), AmpConfig("DAI1 Passband filtering: music mode", 0b1, 0x18, 7, 0b10000000), AmpConfig("DAI1 voice mode gain (DV1G)", 0b00, 0x2F, 4, 0b00110000), AmpConfig("DAI1 attenuation (DV1)", 0x0, 0x2F, 0, 0b00001111), @@ -41,8 +41,8 @@ BASE_CONFIG = [ AmpConfig("ALC enable", 0b0, 0x43, 7, 0b10000000), AmpConfig("ALC/excursion limiter release time", 0b101, 0x43, 4, 0b01110000), AmpConfig("DAI1 EQ enable", 0b0, 0x49, 0, 0b00000001), - AmpConfig("DAI2 EQ enable", 0b1, 0x49, 1, 0b00000010), - AmpConfig("DAI2 EQ clip detection disabled", 0b0, 0x32, 4, 0b00010000), + AmpConfig("DAI2 EQ enable", 0b0, 0x49, 1, 0b00000010), + AmpConfig("DAI2 EQ clip detection disabled", 0b1, 0x32, 4, 0b00010000), AmpConfig("DAI2 EQ attenuation", 0x5, 0x32, 0, 0b00001111), AmpConfig("Excursion limiter upper corner freq", 0b100, 0x41, 4, 0b01110000), AmpConfig("Excursion limiter lower corner freq", 0b00, 0x41, 0, 0b00000011), @@ -57,6 +57,9 @@ BASE_CONFIG = [ AmpConfig("DAI2 audio port selector", 0b01, 0x1E, 6, 0b11000000), AmpConfig("Enable left digital microphone", 0b1, 0x48, 5, 0b00100000), AmpConfig("Enable right digital microphone", 0b1, 0x48, 4, 0b00010000), + AmpConfig("Enhanced volume smoothing disabled", 0b0, 0x49, 7, 0b10000000), + AmpConfig("Volume adjustment smoothing disabled", 0b0, 0x49, 6, 0b01000000), + AmpConfig("Zero-crossing detection disabled", 0b0, 0x49, 5, 0b00100000), ] BASE_CONFIG += configs_from_eq_params(0x84, EQParams(0x65C4, 0xC07C, 0x3D66, 0x07D9, 0x120F)) @@ -88,11 +91,11 @@ class Amplifier: self.set_config(AmpConfig("Global shutdown", 0b0 if amp_disabled else 0b1, 0x51, 7, 0b10000000)) def initialize_configuration(self): + self.set_global_shutdown(amp_disabled=True) + for config in BASE_CONFIG: self.set_config(config) - # Re-init amp - self.set_global_shutdown(amp_disabled=True) self.set_global_shutdown(amp_disabled=False) diff --git a/selfdrive/hardware/tici/hardware.h b/selfdrive/hardware/tici/hardware.h new file mode 100644 index 000000000..abd7e9297 --- /dev/null +++ b/selfdrive/hardware/tici/hardware.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include "selfdrive/common/params.h" +#include "selfdrive/common/util.h" +#include "selfdrive/hardware/base.h" + +class HardwareTici : public HardwareNone { +public: + static constexpr float MAX_VOLUME = 1.0; + static constexpr float MIN_VOLUME = 0.4; + static bool TICI() { return true; } + static std::string get_os_version() { + return "AGNOS " + util::read_file("/VERSION"); + }; + + static void reboot() { std::system("sudo reboot"); }; + static void poweroff() { std::system("sudo poweroff"); }; + static void set_brightness(int percent) { + std::ofstream brightness_control("/sys/class/backlight/panel0-backlight/brightness"); + if (brightness_control.is_open()) { + brightness_control << (percent * (int)(1023/100.)) << "\n"; + brightness_control.close(); + } + }; + static void set_display_power(bool on) { + std::ofstream bl_power_control("/sys/class/backlight/panel0-backlight/bl_power"); + if (bl_power_control.is_open()) { + bl_power_control << (on ? "0" : "4") << "\n"; + bl_power_control.close(); + } + }; + + static bool get_ssh_enabled() { return Params().getBool("SshEnabled"); }; + static void set_ssh_enabled(bool enabled) { Params().putBool("SshEnabled", enabled); }; +}; diff --git a/selfdrive/hardware/tici/pins.py b/selfdrive/hardware/tici/pins.py new file mode 100644 index 000000000..7139f5e95 --- /dev/null +++ b/selfdrive/hardware/tici/pins.py @@ -0,0 +1,8 @@ +# TODO: these are also defined in a header +# GPIO pin definitions +GPIO_HUB_RST_N = 30 +GPIO_UBLOX_RST_N = 32 +GPIO_UBLOX_SAFEBOOT_N = 33 +GPIO_UBLOX_PWR_EN = 34 +GPIO_STM_RST_N = 124 +GPIO_STM_BOOT0 = 134 diff --git a/selfdrive/hardware/tici/updater b/selfdrive/hardware/tici/updater new file mode 100755 index 000000000..abea2ed17 Binary files /dev/null and b/selfdrive/hardware/tici/updater differ diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index d96246f94..3f86ecf33 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -372,7 +372,11 @@ void Localizer::time_check(double current_time) { void Localizer::update_reset_tracker() { // reset tracker is tuned to trigger when over 1reset/10s over 2min period - this->reset_tracker *= .99995; + if (this->isGpsOK()) { + this->reset_tracker *= .99995; + } else { + this->reset_tracker = 0.0; + } } void Localizer::reset_kalman(double current_time, VectorXd init_orient, VectorXd init_pos) { @@ -427,6 +431,11 @@ kj::ArrayPtr Localizer::get_message_bytes(MessageBuilder& msg_build return msg_builder.toBytes(); } + +bool Localizer::isGpsOK() { + return this->kf->get_filter_time() - this->last_gps_fix < 1.0; +} + int Localizer::locationd_thread() { const std::initializer_list service_list = { "gpsLocationExternal", "sensorEvents", "cameraOdometry", "liveCalibration", "carState" }; @@ -448,7 +457,7 @@ int Localizer::locationd_thread() { uint64_t logMonoTime = sm["cameraOdometry"].getLogMonoTime(); bool inputsOK = sm.allAliveAndValid(); bool sensorsOK = sm.alive("sensorEvents") && sm.valid("sensorEvents"); - bool gpsOK = (logMonoTime / 1e9) - this->last_gps_fix < 1.0; + bool gpsOK = this->isGpsOK(); MessageBuilder msg_builder; kj::ArrayPtr bytes = this->get_message_bytes(msg_builder, logMonoTime, inputsOK, sensorsOK, gpsOK); diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h index 82b7eb6b3..04284ba3b 100755 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -31,6 +31,7 @@ public: void finite_check(double current_time = NAN); void time_check(double current_time = NAN); void update_reset_tracker(); + bool isGpsOK(); kj::ArrayPtr get_message_bytes(MessageBuilder& msg_builder, uint64_t logMonoTime, bool inputsOK, bool sensorsOK, bool gpsOK); diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index ccf780b60..fdb9dbc5b 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -96,8 +96,8 @@ class ManagerProcess(ABC): if dt > self.watchdog_max_dt: # Only restart while offroad for now - if self.watchdog_seen and ENABLE_WATCHDOG and (not started): - cloudlog.error(f"Watchdog timeout for {self.name} (exitcode {self.proc.exitcode}) restarting") + if self.watchdog_seen and ENABLE_WATCHDOG: + cloudlog.error(f"Watchdog timeout for {self.name} (exitcode {self.proc.exitcode}) restarting ({started=})") self.restart() else: self.watchdog_seen = True diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 9ed6d9f87..91d256f24 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -17,7 +17,8 @@ procs = [ NativeProcess("proclogd", "selfdrive/proclogd", ["./proclogd"]), NativeProcess("sensord", "selfdrive/sensord", ["./sensord"], enabled=not PC, persistent=EON, sigkill=EON), NativeProcess("ubloxd", "selfdrive/locationd", ["./ubloxd"], enabled=(not PC or WEBCAM)), - NativeProcess("ui", "selfdrive/ui", ["./ui"], persistent=True, watchdog_max_dt=(10 if TICI else None)), + NativeProcess("ui", "selfdrive/ui", ["./ui"], persistent=True, watchdog_max_dt=(5 if TICI else None)), + NativeProcess("soundd", "selfdrive/ui", ["./soundd"]), NativeProcess("locationd", "selfdrive/locationd", ["./locationd"]), PythonProcess("calibrationd", "selfdrive.locationd.calibrationd"), PythonProcess("controlsd", "selfdrive.controls.controlsd"), diff --git a/selfdrive/modeld/modeld b/selfdrive/modeld/modeld index a87221dbd..1b67842ab 100755 --- a/selfdrive/modeld/modeld +++ b/selfdrive/modeld/modeld @@ -10,6 +10,6 @@ if [ -d /system ]; then fi else # PC - export LD_LIBRARY_PATH="$DIR/../../phonelibs/snpe/x86_64-linux-clang:$DIR/../..//openpilot/phonelibs/snpe/x86_64:$LD_LIBRARY_PATH" + export LD_LIBRARY_PATH="$DIR/../../phonelibs/snpe/x86_64-linux-clang:$DIR/../../openpilot/phonelibs/snpe/x86_64:$LD_LIBRARY_PATH" fi exec ./_modeld diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 8321d3734..f11d72266 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -3,7 +3,7 @@ from cereal import car from common.params import Params import cereal.messaging as messaging from selfdrive.controls.lib.events import Events -from selfdrive.monitoring.driver_monitor import DriverStatus, MAX_TERMINAL_ALERTS, MAX_TERMINAL_DURATION +from selfdrive.monitoring.driver_monitor import DriverStatus from selfdrive.locationd.calibrationd import Calibration @@ -50,7 +50,8 @@ def dmonitoringd_thread(sm=None, pm=None): driver_status.get_pose(sm['driverState'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled) # Block engaging after max number of distrations - if driver_status.terminal_alert_cnt >= MAX_TERMINAL_ALERTS or driver_status.terminal_time >= MAX_TERMINAL_DURATION: + if driver_status.terminal_alert_cnt >= driver_status.settings._MAX_TERMINAL_ALERTS or \ + driver_status.terminal_time >= driver_status.settings._MAX_TERMINAL_DURATION: events.add(car.CarEvent.EventName.tooDistracted) # Update events from driver state diff --git a/selfdrive/monitoring/driver_monitor.py b/selfdrive/monitoring/driver_monitor.py index accaf62ae..f54cd5878 100644 --- a/selfdrive/monitoring/driver_monitor.py +++ b/selfdrive/monitoring/driver_monitor.py @@ -3,6 +3,7 @@ from math import atan2, sqrt from cereal import car from common.numpy_fast import interp from common.realtime import DT_DMON +from selfdrive.hardware import TICI from common.filter_simple import FirstOrderFilter from common.stat_live import RunningStatFilter @@ -14,43 +15,48 @@ EventName = car.CarEvent.EventName # We recommend that you do not change these numbers from the defaults. # ****************************************************************************************** -_AWARENESS_TIME = 35. # passive wheel touch total timeout -_AWARENESS_PRE_TIME_TILL_TERMINAL = 12. -_AWARENESS_PROMPT_TIME_TILL_TERMINAL = 6. -_DISTRACTED_TIME = 11. -_DISTRACTED_PRE_TIME_TILL_TERMINAL = 8. -_DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 6. +class DRIVER_MONITOR_SETTINGS(): + def __init__(self, TICI=TICI, DT_DMON=DT_DMON): + self._DT_DMON = DT_DMON + self._AWARENESS_TIME = 35. # passive wheeltouch total timeout + self._AWARENESS_PRE_TIME_TILL_TERMINAL = 12. + self._AWARENESS_PROMPT_TIME_TILL_TERMINAL = 6. + self._DISTRACTED_TIME = 11. # active monitoring total timeout + self._DISTRACTED_PRE_TIME_TILL_TERMINAL = 8. + self._DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 6. -_FACE_THRESHOLD = 0.5 -_PARTIAL_FACE_THRESHOLD = 0.5 -_EYE_THRESHOLD = 0.5 -_SG_THRESHOLD = 0.5 -_BLINK_THRESHOLD = 0.5 -_BLINK_THRESHOLD_SLACK = 0.65 -_BLINK_THRESHOLD_STRICT = 0.5 -_PITCH_WEIGHT = 1.35 # pitch matters a lot more -_POSESTD_THRESHOLD = 0.14 -_E2E_POSE_THRESHOLD = 0.9 -_E2E_EYES_THRESHOLD = 0.75 -_METRIC_THRESHOLD = 0.4 -_METRIC_THRESHOLD_SLACK = 0.55 -_METRIC_THRESHOLD_STRICT = 0.4 -_PITCH_POS_ALLOWANCE = 0.12 # rad, to not be too sensitive on positive pitch -_PITCH_NATURAL_OFFSET = 0.02 # people don't seem to look straight when they drive relaxed, rather a bit up -_YAW_NATURAL_OFFSET = 0.08 # people don't seem to look straight when they drive relaxed, rather a bit to the right (center of car) + self._FACE_THRESHOLD = 0.5 + self._PARTIAL_FACE_THRESHOLD = 0.75 if TICI else 0.5 + self._EYE_THRESHOLD = 0.5 + self._SG_THRESHOLD = 0.5 + self._BLINK_THRESHOLD = 0.88 if TICI else 0.5 + self._BLINK_THRESHOLD_SLACK = 0.98 if TICI else 0.65 + self._BLINK_THRESHOLD_STRICT = 0.88 if TICI else 0.5 + self._PITCH_WEIGHT = 1.175 if TICI else 1.35 # pitch matters a lot more + self._POSESTD_THRESHOLD = 0.318 if TICI else 0.14 + self._E2E_POSE_THRESHOLD = 0.95 if TICI else 0.9 + self._E2E_EYES_THRESHOLD = 0.75 -_HI_STD_FALLBACK_TIME = 10 # fall back to wheel touch if model is uncertain for a long time -_DISTRACTED_FILTER_TS = 0.25 # 0.6Hz + self._METRIC_THRESHOLD = 0.5 if TICI else 0.4 + self._METRIC_THRESHOLD_SLACK = 0.6875 if TICI else 0.55 + self._METRIC_THRESHOLD_STRICT = 0.5 if TICI else 0.4 + self._PITCH_POS_ALLOWANCE = 0.12 # rad, to not be too sensitive on positive pitch + self._PITCH_NATURAL_OFFSET = 0.02 # people don't seem to look straight when they drive relaxed, rather a bit up + self._YAW_NATURAL_OFFSET = 0.08 # people don't seem to look straight when they drive relaxed, rather a bit to the right (center of car) -_POSE_CALIB_MIN_SPEED = 13 # 30 mph -_POSE_OFFSET_MIN_COUNT = 600 # valid data counts before calibration completes, 1 seg is 600 counts -_POSE_OFFSET_MAX_COUNT = 3600 # stop deweighting new data after 6 min, aka "short term memory" + self._HI_STD_FALLBACK_TIME = int(10 / self._DT_DMON) # fall back to wheel touch if model is uncertain for 10s + self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz -_RECOVERY_FACTOR_MAX = 5. # relative to minus step change -_RECOVERY_FACTOR_MIN = 1.25 # relative to minus step change + self._POSE_CALIB_MIN_SPEED = 13 # 30 mph + self._POSE_OFFSET_MIN_COUNT = int(60 / self._DT_DMON) # valid data counts before calibration completes, 1min cumulative + self._POSE_OFFSET_MAX_COUNT = int(360 / self._DT_DMON) # stop deweighting new data after 6 min, aka "short term memory" + + self._RECOVERY_FACTOR_MAX = 5. # relative to minus step change + self._RECOVERY_FACTOR_MIN = 1.25 # relative to minus step change + + self._MAX_TERMINAL_ALERTS = 3 # not allowed to engage after 3 terminal alerts + self._MAX_TERMINAL_DURATION = int(30 / self._DT_DMON) # not allowed to engage after 30s of terminal alerts -MAX_TERMINAL_ALERTS = 3 # not allowed to engage after 3 terminal alerts -MAX_TERMINAL_DURATION = 300 # 30s # model output refers to center of cropped image, so need to apply the x displacement offset RESIZED_FOCAL = 320.0 @@ -80,15 +86,15 @@ def face_orientation_from_net(angles_desc, pos_desc, rpy_calib, is_rhd): return roll_net, pitch, yaw class DriverPose(): - def __init__(self): + def __init__(self, max_trackable): self.yaw = 0. self.pitch = 0. self.roll = 0. self.yaw_std = 0. self.pitch_std = 0. self.roll_std = 0. - self.pitch_offseter = RunningStatFilter(max_trackable=_POSE_OFFSET_MAX_COUNT) - self.yaw_offseter = RunningStatFilter(max_trackable=_POSE_OFFSET_MAX_COUNT) + self.pitch_offseter = RunningStatFilter(max_trackable=max_trackable) + self.yaw_offseter = RunningStatFilter(max_trackable=max_trackable) self.low_std = True self.cfactor = 1. @@ -99,17 +105,20 @@ class DriverBlink(): self.cfactor = 1. class DriverStatus(): - def __init__(self, rhd=False): + def __init__(self, rhd=False, settings=DRIVER_MONITOR_SETTINGS()): + # init policy settings + self.settings = settings + + # init driver status self.is_rhd_region = rhd - self.pose = DriverPose() - self.pose_calibrated = self.pose.pitch_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT and \ - self.pose.yaw_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT + self.pose = DriverPose(self.settings._POSE_OFFSET_MAX_COUNT) + self.pose_calibrated = False self.blink = DriverBlink() self.awareness = 1. self.awareness_active = 1. self.awareness_passive = 1. self.driver_distracted = False - self.driver_distraction_filter = FirstOrderFilter(0., _DISTRACTED_FILTER_TS, DT_DMON) + self.driver_distraction_filter = FirstOrderFilter(0., self.settings._DISTRACTED_FILTER_TS, self.settings._DT_DMON) self.face_detected = False self.face_partial = False self.terminal_alert_cnt = 0 @@ -118,14 +127,15 @@ class DriverStatus(): self.active_monitoring_mode = True self.is_model_uncertain = False self.hi_stds = 0 - self.threshold_prompt = _DISTRACTED_PROMPT_TIME_TILL_TERMINAL / _DISTRACTED_TIME + self.threshold_pre = self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME + self.threshold_prompt = self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME self._set_timers(active_monitoring=True) def _set_timers(self, active_monitoring): if self.active_monitoring_mode and self.awareness <= self.threshold_prompt: if active_monitoring: - self.step_change = DT_DMON / _DISTRACTED_TIME + self.step_change = self.settings._DT_DMON / self.settings._DISTRACTED_TIME else: self.step_change = 0. return # no exploit after orange alert @@ -138,79 +148,85 @@ class DriverStatus(): self.awareness_passive = self.awareness self.awareness = self.awareness_active - self.threshold_pre = _DISTRACTED_PRE_TIME_TILL_TERMINAL / _DISTRACTED_TIME - self.threshold_prompt = _DISTRACTED_PROMPT_TIME_TILL_TERMINAL / _DISTRACTED_TIME - self.step_change = DT_DMON / _DISTRACTED_TIME + self.threshold_pre = self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME + self.threshold_prompt = self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME + self.step_change = self.settings._DT_DMON / self.settings._DISTRACTED_TIME self.active_monitoring_mode = True else: if self.active_monitoring_mode: self.awareness_active = self.awareness self.awareness = self.awareness_passive - self.threshold_pre = _AWARENESS_PRE_TIME_TILL_TERMINAL / _AWARENESS_TIME - self.threshold_prompt = _AWARENESS_PROMPT_TIME_TILL_TERMINAL / _AWARENESS_TIME - self.step_change = DT_DMON / _AWARENESS_TIME + self.threshold_pre = self.settings._AWARENESS_PRE_TIME_TILL_TERMINAL / self.settings._AWARENESS_TIME + self.threshold_prompt = self.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL / self.settings._AWARENESS_TIME + self.step_change = self.settings._DT_DMON / self.settings._AWARENESS_TIME self.active_monitoring_mode = False def _is_driver_distracted(self, pose, blink): if not self.pose_calibrated: - pitch_error = pose.pitch - _PITCH_NATURAL_OFFSET - yaw_error = pose.yaw - _YAW_NATURAL_OFFSET + pitch_error = pose.pitch - self.settings._PITCH_NATURAL_OFFSET + yaw_error = pose.yaw - self.settings._YAW_NATURAL_OFFSET else: pitch_error = pose.pitch - self.pose.pitch_offseter.filtered_stat.mean() yaw_error = pose.yaw - self.pose.yaw_offseter.filtered_stat.mean() # positive pitch allowance if pitch_error > 0.: - pitch_error = max(pitch_error - _PITCH_POS_ALLOWANCE, 0.) - pitch_error *= _PITCH_WEIGHT + pitch_error = max(pitch_error - self.settings._PITCH_POS_ALLOWANCE, 0.) + pitch_error *= self.settings._PITCH_WEIGHT pose_metric = sqrt(yaw_error**2 + pitch_error**2) - if pose_metric > _METRIC_THRESHOLD*pose.cfactor: + if pose_metric > self.settings._METRIC_THRESHOLD*pose.cfactor: return DistractedType.BAD_POSE - elif (blink.left_blink + blink.right_blink)*0.5 > _BLINK_THRESHOLD*blink.cfactor: + elif (blink.left_blink + blink.right_blink)*0.5 > self.settings._BLINK_THRESHOLD*blink.cfactor: return DistractedType.BAD_BLINK else: return DistractedType.NOT_DISTRACTED def set_policy(self, model_data): ep = min(model_data.meta.engagedProb, 0.8) / 0.8 - self.pose.cfactor = interp(ep, [0, 0.5, 1], [_METRIC_THRESHOLD_STRICT, _METRIC_THRESHOLD, _METRIC_THRESHOLD_SLACK])/_METRIC_THRESHOLD - self.blink.cfactor = interp(ep, [0, 0.5, 1], [_BLINK_THRESHOLD_STRICT, _BLINK_THRESHOLD, _BLINK_THRESHOLD_SLACK])/_BLINK_THRESHOLD + self.pose.cfactor = interp(ep, [0, 0.5, 1], + [self.settings._METRIC_THRESHOLD_STRICT, + self.settings. _METRIC_THRESHOLD, + self.settings._METRIC_THRESHOLD_SLACK]) / self.settings._METRIC_THRESHOLD + self.blink.cfactor = interp(ep, [0, 0.5, 1], + [self.settings._BLINK_THRESHOLD_STRICT, + self.settings._BLINK_THRESHOLD, + self.settings._BLINK_THRESHOLD_SLACK]) / self.settings._BLINK_THRESHOLD def get_pose(self, driver_state, cal_rpy, car_speed, op_engaged): if not all(len(x) > 0 for x in [driver_state.faceOrientation, driver_state.facePosition, driver_state.faceOrientationStd, driver_state.facePositionStd]): return - self.face_partial = driver_state.partialFace > _PARTIAL_FACE_THRESHOLD - self.face_detected = driver_state.faceProb > _FACE_THRESHOLD or self.face_partial + self.face_partial = driver_state.partialFace > self.settings._PARTIAL_FACE_THRESHOLD + self.face_detected = driver_state.faceProb > self.settings._FACE_THRESHOLD or self.face_partial self.pose.roll, self.pose.pitch, self.pose.yaw = face_orientation_from_net(driver_state.faceOrientation, driver_state.facePosition, cal_rpy, self.is_rhd_region) self.pose.pitch_std = driver_state.faceOrientationStd[0] self.pose.yaw_std = driver_state.faceOrientationStd[1] # self.pose.roll_std = driver_state.faceOrientationStd[2] model_std_max = max(self.pose.pitch_std, self.pose.yaw_std) - self.pose.low_std = model_std_max < _POSESTD_THRESHOLD and not self.face_partial - self.blink.left_blink = driver_state.leftBlinkProb * (driver_state.leftEyeProb > _EYE_THRESHOLD) * (driver_state.sunglassesProb < _SG_THRESHOLD) - self.blink.right_blink = driver_state.rightBlinkProb * (driver_state.rightEyeProb > _EYE_THRESHOLD) * (driver_state.sunglassesProb < _SG_THRESHOLD) + self.pose.low_std = model_std_max < self.settings._POSESTD_THRESHOLD and not self.face_partial + self.blink.left_blink = driver_state.leftBlinkProb * (driver_state.leftEyeProb > self.settings._EYE_THRESHOLD) * (driver_state.sunglassesProb < self.settings._SG_THRESHOLD) + self.blink.right_blink = driver_state.rightBlinkProb * (driver_state.rightEyeProb > self.settings._EYE_THRESHOLD) * (driver_state.sunglassesProb < self.settings._SG_THRESHOLD) - distracted_normal = (self._is_driver_distracted(self.pose, self.blink) > 0 and - driver_state.faceProb > _FACE_THRESHOLD and self.pose.low_std) - distracted_E2E = ((driver_state.distractedPose > _E2E_POSE_THRESHOLD or driver_state.distractedEyes > _E2E_EYES_THRESHOLD) and - (self.face_detected and not self.face_partial)) + distracted_normal = self._is_driver_distracted(self.pose, self.blink) > 0 and \ + driver_state.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std + distracted_E2E = (driver_state.distractedPose > self.settings._E2E_POSE_THRESHOLD or driver_state.distractedEyes > self.settings._E2E_EYES_THRESHOLD) and \ + (self.face_detected and not self.face_partial) self.driver_distracted = distracted_normal or distracted_E2E self.driver_distraction_filter.update(self.driver_distracted) # update offseter # only update when driver is actively driving the car above a certain speed - if self.face_detected and car_speed > _POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): + if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): self.pose.pitch_offseter.push_and_update(self.pose.pitch) self.pose.yaw_offseter.push_and_update(self.pose.yaw) - self.pose_calibrated = self.pose.pitch_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT and \ - self.pose.yaw_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT + self.pose_calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \ + self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT - self.is_model_uncertain = self.hi_stds * DT_DMON > _HI_STD_FALLBACK_TIME + self.is_model_uncertain = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME self._set_timers(self.face_detected and not self.is_model_uncertain) if self.face_detected and not self.pose.low_std and not self.driver_distracted: self.hi_stds += 1 @@ -230,7 +246,7 @@ class DriverStatus(): if (driver_attentive and self.face_detected and self.pose.low_std and self.awareness > 0): # only restore awareness when paying attention and alert is not red - self.awareness = min(self.awareness + ((_RECOVERY_FACTOR_MAX-_RECOVERY_FACTOR_MIN)*(1.-self.awareness)+_RECOVERY_FACTOR_MIN)*self.step_change, 1.) + self.awareness = min(self.awareness + ((self.settings._RECOVERY_FACTOR_MAX-self.settings._RECOVERY_FACTOR_MIN)*(1.-self.awareness)+self.settings._RECOVERY_FACTOR_MIN)*self.step_change, 1.) if self.awareness == 1.: self.awareness_passive = min(self.awareness_passive + self.step_change, 1.) # don't display alert banner when awareness is recovering and has cleared orange @@ -239,7 +255,7 @@ class DriverStatus(): standstill_exemption = standstill and self.awareness - self.step_change <= self.threshold_prompt certainly_distracted = self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected - maybe_distracted = self.hi_stds * DT_DMON > _HI_STD_FALLBACK_TIME or not self.face_detected + maybe_distracted = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME or not self.face_detected if certainly_distracted or maybe_distracted: # should always be counting if distracted unless at standstill and reaching orange if not standstill_exemption: diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 7bd228fc7..b5b7e3be0 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -34,6 +34,7 @@ PROCS = { "./_dmonitoringmodeld": 2.67, "selfdrive.thermald.thermald": 2.41, "selfdrive.locationd.calibrationd": 2.0, + "./_soundd": 2.0, "selfdrive.monitoring.dmonitoringd": 1.90, "./proclogd": 1.54, "selfdrive.logmessaged": 0.2, @@ -48,6 +49,7 @@ if TICI: "./loggerd": 60.0, "selfdrive.controls.controlsd": 26.0, "./camerad": 25.0, + "./_ui": 21.0, "selfdrive.controls.plannerd": 12.0, "selfdrive.locationd.paramsd": 5.0, "./_dmonitoringmodeld": 10.0, @@ -74,9 +76,6 @@ def check_cpu_usage(first_proc, last_proc): cpu_time = cputime_total(last) - cputime_total(first) cpu_usage = cpu_time / dt * 100. if cpu_usage > max(normal_cpu_usage * 1.1, normal_cpu_usage + 5.0): - # TODO: fix high CPU when playing sounds constantly in UI - if proc_name == "./_ui" and cpu_usage < 50.: - continue result += f"Warning {proc_name} using more CPU than normal\n" r = False elif cpu_usage < min(normal_cpu_usage * 0.65, max(normal_cpu_usage - 1.0, 0.0)): diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 9256d0345..bcd3f1888 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -21,7 +21,7 @@ from selfdrive.loggerd.config import get_available_percent from selfdrive.pandad import get_expected_signature from selfdrive.swaglog import cloudlog from selfdrive.thermald.power_monitoring import PowerMonitoring -from selfdrive.version import get_git_branch, terms_version, training_version +from selfdrive.version import tested_branch, terms_version, training_version FW_SIGNATURE = get_expected_signature() @@ -151,7 +151,6 @@ def thermald_thread(): started_seen = False thermal_status = ThermalStatus.green usb_power = True - current_branch = get_git_branch() network_type = NetworkType.none network_strength = NetworkStrength.unknown @@ -333,7 +332,7 @@ def thermald_thread(): last_update_exception = params.get("LastUpdateException", encoding='utf8') if update_failed_count > 15 and last_update_exception is not None: - if current_branch in ["release2", "dashcam"]: + if tested_branch: extra_text = "Ensure the software is correctly installed" else: extra_text = last_update_exception diff --git a/selfdrive/timezoned.py b/selfdrive/timezoned.py new file mode 100755 index 000000000..74da5fe35 --- /dev/null +++ b/selfdrive/timezoned.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +import json +import os +import time +import subprocess + +import requests +from timezonefinder import TimezoneFinder + +from common.params import Params +from selfdrive.hardware import TICI +from selfdrive.swaglog import cloudlog + + +def set_timezone(valid_timezones, timezone): + if timezone not in valid_timezones: + cloudlog.error(f"Timezone not supported {timezone}") + return + + cloudlog.debug(f"Setting timezone to {timezone}") + try: + if TICI: + tzpath = os.path.join("/usr/share/zoneinfo/", timezone) + subprocess.check_call(f'sudo su -c "ln -snf {tzpath} /data/etc/tmptime && \ + mv /data/etc/tmptime /data/etc/localtime"', shell=True) + subprocess.check_call(f'sudo su -c "echo \"{timezone}\" > /data/etc/timezone"', shell=True) + else: + subprocess.check_call(f'sudo timedatectl set-timezone {timezone}', shell=True) + except subprocess.CalledProcessError: + cloudlog.exception(f"Error setting timezone to {timezone}") + + +def main(): + params = Params() + tf = TimezoneFinder() + + # Get allowed timezones + valid_timezones = subprocess.check_output('timedatectl list-timezones', shell=True, encoding='utf8').strip().split('\n') + + while True: + time.sleep(60) + + is_onroad = not params.get_bool("IsOffroad") + if is_onroad: + continue + + # Set based on param + timezone = params.get("Timezone", encoding='utf8') + if timezone is not None: + cloudlog.debug("Setting timezone based on param") + set_timezone(valid_timezones, timezone) + continue + + location = params.get("LastGPSPosition", encoding='utf8') + + # Find timezone based on IP geolocation if no gps location is available + if location is None: + cloudlog.debug("Setting timezone based on IP lookup") + try: + r = requests.get("https://ipapi.co/timezone", timeout=10) + if r.status_code == 200: + set_timezone(valid_timezones, r.text) + else: + cloudlog.error(f"Unexpected status code from api {r.status_code}") + + time.sleep(3600) # Don't make too many API requests + except requests.exceptions.RequestException: + cloudlog.exception("Error getting timezone based on IP") + continue + + # Find timezone by reverse geocoding the last known gps location + else: + cloudlog.debug("Setting timezone based on GPS location") + try: + location = json.loads(location) + except Exception: + cloudlog.exception("Error parsing location") + continue + + timezone = tf.timezone_at(lng=location['longitude'], lat=location['latitude']) + if timezone is None: + cloudlog.error(f"No timezone found based on location, {location}") + continue + set_timezone(valid_timezones, timezone) + + +if __name__ == "__main__": + main() diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 3f5116f24..452545591 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -7,4 +7,5 @@ qt/spinner qt/setup/setup qt/setup/reset qt/setup/wifi +qt/setup/updater qt/setup/installer_* diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index df607ec7f..61ca8c868 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -5,13 +5,12 @@ Import('qt_env', 'arch', 'common', 'messaging', 'gpucommon', 'visionipc', base_libs = [gpucommon, common, messaging, cereal, visionipc, transformations, 'zmq', 'capnp', 'kj', 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] -maps = arch in ['larch64', 'x86_64'] and \ - os.path.exists(File("qt/maps/map.cc").srcnode().abspath) +maps = arch in ['larch64', 'x86_64'] if arch == 'aarch64': base_libs += ['log', 'utils', 'gui', 'ui', 'CB', 'gsl', 'adreno_utils', 'cutils', 'uuid'] -if maps and arch in ['x86_64']: +if maps and arch == 'x86_64': rpath = [Dir(f"#phonelibs/mapbox-gl-native-qt/{arch}").srcnode().abspath] qt_env["RPATH"] += rpath @@ -19,24 +18,29 @@ if arch == "Darwin": del base_libs[base_libs.index('OpenCL')] qt_env['FRAMEWORKS'] += ['OpenCL'] -widgets_src = ["qt/util.cc", - "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", +widgets_src = ["qt/util.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", - "qt/widgets/offroad_alerts.cc", "qt/widgets/setup.cc", "qt/widgets/keyboard.cc", + "qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#phonelibs/qrcode/QrCode.cc", "qt/api.cc", "qt/request_repeater.cc"] if arch != 'aarch64': widgets_src += ["qt/offroad/networking.cc", "qt/offroad/wifiManager.cc"] -if maps: +qt_env['CPPDEFINES'] = [] +if GetOption('setup'): + qt_env['CPPDEFINES'] += ["USE_QRC"] +elif maps: base_libs += ['qmapboxgl'] widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc"] - qt_env['CPPDEFINES'] = ["ENABLE_MAPS"] + qt_env['CPPDEFINES'] += ["ENABLE_MAPS"] widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) qt_libs = [widgets] + base_libs +# build soundd +qt_env.Program("_soundd", "soundd.cc", LIBS=base_libs) + # spinner and text window qt_env.Program("qt/text", ["qt/text.cc"], LIBS=qt_libs) qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=qt_libs) @@ -50,9 +54,19 @@ qt_env.Program("_ui", qt_src, LIBS=qt_libs) # setup, factory resetter, and installer if arch != 'aarch64' and GetOption('setup'): + + # TODO: do this for all resources once NEOS has rcc + assets = "#selfdrive/assets/assets.cc" + assets_src = "#selfdrive/assets/assets.qrc" + qt_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET") + qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, "#selfdrive/assets/assets.o"])) + asset_obj = qt_env.Object("assets", assets) + qt_env.Program("qt/setup/reset", ["qt/setup/reset.cc"], LIBS=qt_libs) - qt_env.Program("qt/setup/setup", ["qt/setup/setup.cc"], LIBS=qt_libs + ['curl', 'common', 'json11']) qt_env.Program("qt/setup/wifi", ["qt/setup/wifi.cc"], LIBS=qt_libs + ['common', 'json11']) + qt_env.Program("qt/setup/updater", ["qt/setup/updater.cc", asset_obj], LIBS=qt_libs) + qt_env.Program("qt/setup/setup", ["qt/setup/setup.cc", asset_obj], + LIBS=qt_libs + ['curl', 'common', 'json11']) senv = qt_env.Clone() senv['LINKFLAGS'].append('-Wl,-strip-debug') diff --git a/selfdrive/ui/main.cc b/selfdrive/ui/main.cc index 63bbffa7a..3d2411b9c 100644 --- a/selfdrive/ui/main.cc +++ b/selfdrive/ui/main.cc @@ -1,3 +1,5 @@ +#include + #include #include @@ -7,6 +9,8 @@ #include "selfdrive/ui/qt/window.h" int main(int argc, char *argv[]) { + setpriority(PRIO_PROCESS, 0, -20); + qInstallMessageHandler(swagLogMessageHandler); initApp(); diff --git a/selfdrive/ui/paint.cc b/selfdrive/ui/paint.cc index 96d635ac9..ac944fd87 100644 --- a/selfdrive/ui/paint.cc +++ b/selfdrive/ui/paint.cc @@ -85,8 +85,8 @@ static void draw_lead(UIState *s, const cereal::RadarState::LeadData::Reader &le } float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35; - x = std::clamp(x, 0.f, s->viz_rect.right() - sz / 2); - y = std::fmin(s->viz_rect.bottom() - sz * .6, y); + x = std::clamp(x, 0.f, s->fb_w - sz / 2); + y = std::fmin(s->fb_h - sz * .6, y); draw_chevron(s, x, y, sz, nvgRGBA(201, 34, 49, fillAlpha), COLOR_YELLOW); } @@ -108,7 +108,7 @@ static void ui_draw_line(UIState *s, const line_vertices_data &vd, NVGcolor *col nvgFill(s->vg); } -static void draw_frame(UIState *s) { +static void draw_vision_frame(UIState *s) { glBindVertexArray(s->frame_vao); mat4 *out_mat = &s->rear_frame_mat; glActiveTexture(GL_TEXTURE0); @@ -160,8 +160,7 @@ static void ui_draw_vision_lane_lines(UIState *s) { // Draw all world space objects. static void ui_draw_world(UIState *s) { - // Don't draw on top of sidebar - nvgScissor(s->vg, s->viz_rect.x, s->viz_rect.y, s->viz_rect.w, s->viz_rect.h); + nvgScissor(s->vg, 0, 0, s->fb_w, s->fb_h); // Draw lane edges and vision/mpc tracks ui_draw_vision_lane_lines(s); @@ -187,17 +186,17 @@ static void ui_draw_vision_maxspeed(UIState *s) { const bool is_cruise_set = maxspeed != 0 && maxspeed != SET_SPEED_NA; if (is_cruise_set && !s->scene.is_metric) { maxspeed *= 0.6225; } - const Rect rect = {s->viz_rect.x + (bdr_s * 2), int(s->viz_rect.y + (bdr_s * 1.5)), 184, 202}; + const Rect rect = {bdr_s * 2, int(bdr_s * 1.5), 184, 202}; ui_fill_rect(s->vg, rect, COLOR_BLACK_ALPHA(100), 30.); ui_draw_rect(s->vg, rect, COLOR_WHITE_ALPHA(100), 10, 20.); nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE); - ui_draw_text(s, rect.centerX(), 148, "MAX", 26 * 2.5, COLOR_WHITE_ALPHA(is_cruise_set ? 200 : 100), "sans-regular"); + ui_draw_text(s, rect.centerX(), 118, "MAX", 26 * 2.5, COLOR_WHITE_ALPHA(is_cruise_set ? 200 : 100), "sans-regular"); if (is_cruise_set) { const std::string maxspeed_str = std::to_string((int)std::nearbyint(maxspeed)); - ui_draw_text(s, rect.centerX(), 242, maxspeed_str.c_str(), 48 * 2.5, COLOR_WHITE, "sans-bold"); + ui_draw_text(s, rect.centerX(), 212, maxspeed_str.c_str(), 48 * 2.5, COLOR_WHITE, "sans-bold"); } else { - ui_draw_text(s, rect.centerX(), 242, "N/A", 42 * 2.5, COLOR_WHITE_ALPHA(100), "sans-semibold"); + ui_draw_text(s, rect.centerX(), 212, "N/A", 42 * 2.5, COLOR_WHITE_ALPHA(100), "sans-semibold"); } } @@ -205,16 +204,16 @@ static void ui_draw_vision_speed(UIState *s) { const float speed = std::max(0.0, (*s->sm)["carState"].getCarState().getVEgo() * (s->scene.is_metric ? 3.6 : 2.2369363)); const std::string speed_str = std::to_string((int)std::nearbyint(speed)); nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE); - ui_draw_text(s, s->viz_rect.centerX(), 240, speed_str.c_str(), 96 * 2.5, COLOR_WHITE, "sans-bold"); - ui_draw_text(s, s->viz_rect.centerX(), 320, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, COLOR_WHITE_ALPHA(200), "sans-regular"); + ui_draw_text(s, s->fb_w/2, 210, speed_str.c_str(), 96 * 2.5, COLOR_WHITE, "sans-bold"); + ui_draw_text(s, s->fb_w/2, 290, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, COLOR_WHITE_ALPHA(200), "sans-regular"); } static void ui_draw_vision_event(UIState *s) { if (s->scene.engageable) { // draw steering wheel const int radius = 96; - const int center_x = s->viz_rect.right() - radius - bdr_s * 2; - const int center_y = s->viz_rect.y + radius + (bdr_s * 1.5); + const int center_x = s->fb_w - radius - bdr_s * 2; + const int center_y = radius + (bdr_s * 1.5); const QColor &color = bg_colors[s->status]; NVGcolor nvg_color = nvgRGBA(color.red(), color.green(), color.blue(), color.alpha()); ui_draw_circle_image(s, center_x, center_y, radius, "wheel", nvg_color, 1.0f); @@ -223,35 +222,20 @@ static void ui_draw_vision_event(UIState *s) { static void ui_draw_vision_face(UIState *s) { const int radius = 96; - const int center_x = s->viz_rect.x + radius + (bdr_s * 2); - const int center_y = s->viz_rect.bottom() - footer_h / 2; + const int center_x = radius + (bdr_s * 2); + const int center_y = s->fb_h - footer_h / 2; ui_draw_circle_image(s, center_x, center_y, radius, "driver_face", s->scene.dm_active); } static void ui_draw_vision_header(UIState *s) { - NVGpaint gradient = nvgLinearGradient(s->vg, s->viz_rect.x, - s->viz_rect.y+(header_h-(header_h/2.5)), - s->viz_rect.x, s->viz_rect.y+header_h, - nvgRGBAf(0,0,0,0.45), nvgRGBAf(0,0,0,0)); - - ui_fill_rect(s->vg, {s->viz_rect.x, s->viz_rect.y, s->viz_rect.w, header_h}, gradient); - + NVGpaint gradient = nvgLinearGradient(s->vg, 0, header_h - (header_h / 2.5), 0, header_h, + nvgRGBAf(0, 0, 0, 0.45), nvgRGBAf(0, 0, 0, 0)); + ui_fill_rect(s->vg, {0, 0, s->fb_w , header_h}, gradient); ui_draw_vision_maxspeed(s); ui_draw_vision_speed(s); ui_draw_vision_event(s); } -static void ui_draw_vision_frame(UIState *s) { - // Draw video frames - glEnable(GL_SCISSOR_TEST); - glViewport(s->video_rect.x, s->video_rect.y, s->video_rect.w, s->video_rect.h); - glScissor(s->viz_rect.x, s->viz_rect.y, s->viz_rect.w, s->viz_rect.h); - draw_frame(s); - glDisable(GL_SCISSOR_TEST); - - glViewport(0, 0, s->fb_w, s->fb_h); -} - static void ui_draw_vision(UIState *s) { const UIScene *scene = &s->scene; // Draw augmented elements @@ -265,33 +249,20 @@ static void ui_draw_vision(UIState *s) { } } -static void ui_draw_background(UIState *s) { - const QColor &color = bg_colors[s->status]; - glClearColor(color.redF(), color.greenF(), color.blueF(), 1.0); - glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); -} - void ui_draw(UIState *s, int w, int h) { - s->viz_rect = Rect{bdr_s, bdr_s, w - 2 * bdr_s, h - 2 * bdr_s}; - const bool draw_vision = s->scene.started && s->vipc_client->connected; - // GL drawing functions - ui_draw_background(s); + glViewport(0, 0, s->fb_w, s->fb_h); if (draw_vision) { - ui_draw_vision_frame(s); + draw_vision_frame(s); } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glViewport(0, 0, s->fb_w, s->fb_h); - // NVG drawing functions - should be no GL inside NVG frame nvgBeginFrame(s->vg, s->fb_w, s->fb_h, 1.0f); - if (draw_vision) { ui_draw_vision(s); } - nvgEndFrame(s->vg); glDisable(GL_BLEND); } @@ -446,13 +417,12 @@ void ui_resize(UIState *s, int width, int height) { zoom *= 0.5; } - s->video_rect = Rect{bdr_s, bdr_s, s->fb_w - 2 * bdr_s, s->fb_h - 2 * bdr_s}; - float zx = zoom * 2 * intrinsic_matrix.v[2] / s->video_rect.w; - float zy = zoom * 2 * intrinsic_matrix.v[5] / s->video_rect.h; + float zx = zoom * 2 * intrinsic_matrix.v[2] / width; + float zy = zoom * 2 * intrinsic_matrix.v[5] / height; const mat4 frame_transform = {{ zx, 0.0, 0.0, 0.0, - 0.0, zy, 0.0, -y_offset / s->video_rect.h * 2, + 0.0, zy, 0.0, -y_offset / height * 2, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, }}; @@ -461,11 +431,9 @@ void ui_resize(UIState *s, int width, int height) { // Apply transformation such that video pixel coordinates match video // 1) Put (0, 0) in the middle of the video - nvgTranslate(s->vg, s->video_rect.x + s->video_rect.w / 2, s->video_rect.y + s->video_rect.h / 2 + y_offset); - + nvgTranslate(s->vg, width / 2, height / 2 + y_offset); // 2) Apply same scaling as video nvgScale(s->vg, zoom, zoom); - // 3) Put (0, 0) in top left corner of video nvgTranslate(s->vg, -intrinsic_matrix.v[2], -intrinsic_matrix.v[5]); diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc index e604a5c09..bfb3d8a1d 100644 --- a/selfdrive/ui/qt/api.cc +++ b/selfdrive/ui/qt/api.cc @@ -67,19 +67,24 @@ QString create_jwt(const QJsonObject &payloads, int expiry) { } // namespace CommaApi -HttpRequest::HttpRequest(QObject *parent, const QString &requestURL, bool create_jwt_) : create_jwt(create_jwt_), QObject(parent) { +HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout) : create_jwt(create_jwt), QObject(parent) { networkAccessManager = new QNetworkAccessManager(this); - reply = NULL; networkTimer = new QTimer(this); networkTimer->setSingleShot(true); - networkTimer->setInterval(20000); + networkTimer->setInterval(timeout); connect(networkTimer, &QTimer::timeout, this, &HttpRequest::requestTimeout); - - sendRequest(requestURL); } -void HttpRequest::sendRequest(const QString &requestURL) { +bool HttpRequest::active() { + return reply != nullptr; +} + +void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Method method) { + if (active()) { + qDebug() << "HttpRequest is active"; + return; + } QString token; if(create_jwt) { token = CommaApi::create_jwt(); @@ -93,7 +98,11 @@ void HttpRequest::sendRequest(const QString &requestURL) { request.setUrl(QUrl(requestURL)); request.setRawHeader(QByteArray("Authorization"), ("JWT " + token).toUtf8()); - reply = networkAccessManager->get(request); + if (method == HttpRequest::Method::GET) { + reply = networkAccessManager->get(request); + } else if (method == HttpRequest::Method::DELETE) { + reply = networkAccessManager->deleteResource(request); + } networkTimer->start(); connect(reply, &QNetworkReply::finished, this, &HttpRequest::requestFinished); @@ -105,19 +114,24 @@ void HttpRequest::requestTimeout() { // This function should always emit something void HttpRequest::requestFinished() { + bool success = false; if (reply->error() != QNetworkReply::OperationCanceledError) { networkTimer->stop(); QString response = reply->readAll(); if (reply->error() == QNetworkReply::NoError) { + success = true; emit receivedResponse(response); } else { qDebug() << reply->errorString(); emit failedResponse(reply->errorString()); } } else { + networkAccessManager->clearAccessCache(); + networkAccessManager->clearConnectionCache(); emit timeoutResponse("timeout"); } + emit requestDone(success); reply->deleteLater(); - reply = NULL; + reply = nullptr; } diff --git a/selfdrive/ui/qt/api.h b/selfdrive/ui/qt/api.h index 8aa01ae2b..624e58bd6 100644 --- a/selfdrive/ui/qt/api.h +++ b/selfdrive/ui/qt/api.h @@ -20,15 +20,18 @@ class HttpRequest : public QObject { Q_OBJECT public: - explicit HttpRequest(QObject* parent, const QString &requestURL, bool create_jwt_ = true); - void sendRequest(const QString &requestURL); + enum class Method {GET, DELETE}; + + explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000); + void sendRequest(const QString &requestURL, const Method method = Method::GET); + bool active(); protected: - QNetworkReply *reply; + QNetworkReply *reply = nullptr; private: - QNetworkAccessManager *networkAccessManager; - QTimer *networkTimer; + QNetworkAccessManager *networkAccessManager = nullptr; + QTimer *networkTimer = nullptr; bool create_jwt; private slots: @@ -36,6 +39,7 @@ private slots: void requestFinished(); signals: + void requestDone(bool success); void receivedResponse(const QString &response); void failedResponse(const QString &errorString); void timeoutResponse(const QString &errorString); diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 4800fc5c5..428b5e202 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -8,7 +8,7 @@ #include "selfdrive/common/params.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/drive_stats.h" -#include "selfdrive/ui/qt/widgets/setup.h" +#include "selfdrive/ui/qt/widgets/prime.h" // HomeWindow: the container for the offroad and onroad UIs @@ -31,7 +31,7 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { onroad = new OnroadWindow(this); slayout->addWidget(onroad); - QObject::connect(this, &HomeWindow::update, onroad, &OnroadWindow::update); + QObject::connect(this, &HomeWindow::update, onroad, &OnroadWindow::updateStateSignal); QObject::connect(this, &HomeWindow::offroadTransitionSignal, onroad, &OnroadWindow::offroadTransitionSignal); driver_view = new DriverViewWindow(this); @@ -39,6 +39,7 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { showDriverView(false); }); slayout->addWidget(driver_view); + setAttribute(Qt::WA_NoSystemBackground); } void HomeWindow::showSidebar(bool show) { @@ -46,12 +47,12 @@ void HomeWindow::showSidebar(bool show) { } void HomeWindow::offroadTransition(bool offroad) { + sidebar->setVisible(offroad); if (offroad) { slayout->setCurrentWidget(home); } else { slayout->setCurrentWidget(onroad); } - sidebar->setVisible(offroad); emit offroadTransitionSignal(offroad); } @@ -87,10 +88,11 @@ void HomeWindow::mousePressEvent(QMouseEvent* e) { OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); - main_layout->setMargin(50); + main_layout->setContentsMargins(40, 40, 40, 45); // top header QHBoxLayout* header_layout = new QHBoxLayout(); + header_layout->setContentsMargins(15, 15, 15, 0); header_layout->setSpacing(16); date = new QLabel(); @@ -99,13 +101,13 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { update_notif = new QPushButton("UPDATE"); update_notif->setVisible(false); update_notif->setStyleSheet("background-color: #364DEF;"); - QObject::connect(update_notif, &QPushButton::released, [=]() { center_layout->setCurrentIndex(1); }); + QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); }); header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignRight); alert_notif = new QPushButton(); alert_notif->setVisible(false); alert_notif->setStyleSheet("background-color: #E22C2C;"); - QObject::connect(alert_notif, &QPushButton::released, [=] { center_layout->setCurrentIndex(2); }); + QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); }); header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignRight); header_layout->addWidget(new QLabel(getBrandVersion()), 0, Qt::AlignHCenter | Qt::AlignRight); @@ -119,9 +121,8 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { QWidget* statsAndSetupWidget = new QWidget(this); QHBoxLayout* statsAndSetup = new QHBoxLayout(statsAndSetupWidget); statsAndSetup->setMargin(0); - DriveStats* drive = new DriveStats(); - drive->setFixedSize(800, 800); - statsAndSetup->addWidget(drive); + statsAndSetup->setSpacing(30); + statsAndSetup->addWidget(new DriveStats, 1); statsAndSetup->addWidget(new SetupWidget); center_layout->addWidget(statsAndSetupWidget); diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc new file mode 100644 index 000000000..4db9ce5e7 --- /dev/null +++ b/selfdrive/ui/qt/maps/map.cc @@ -0,0 +1,824 @@ +#include "selfdrive/ui/qt/maps/map.h" + +#include + +#include + +#include "selfdrive/common/swaglog.h" +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/maps/map_helpers.h" +#include "selfdrive/ui/qt/request_repeater.h" + + +const int PAN_TIMEOUT = 100; +const qreal REROUTE_DISTANCE = 25; +const float MANEUVER_TRANSITION_THRESHOLD = 10; + +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 QMapboxGLSettings &settings) : + m_settings(settings), velocity_filter(0, 10, 0.1) { + sm = new SubMaster({"liveLocationKalman"}); + + timer = new QTimer(this); + QObject::connect(timer, SIGNAL(timeout()), this, SLOT(timerUpdate())); + timer->start(100); + + recompute_timer = new QTimer(this); + QObject::connect(recompute_timer, SIGNAL(timeout()), this, SLOT(recomputeRoute())); + recompute_timer->start(1000); + + // Instructions + map_instructions = new MapInstructions(this); + QObject::connect(this, &MapWindow::instructionsChanged, map_instructions, &MapInstructions::updateInstructions); + QObject::connect(this, &MapWindow::distanceChanged, map_instructions, &MapInstructions::updateDistance); + map_instructions->setFixedWidth(width()); + map_instructions->setVisible(false); + + map_eta = new MapETA(this); + QObject::connect(this, &MapWindow::ETAChanged, map_eta, &MapETA::updateETA); + + const int h = 120; + map_eta->setFixedHeight(h); + map_eta->move(25, 1080 - h - bdr_s*2); + map_eta->setVisible(false); + + // Routing + QVariantMap parameters; + parameters["mapbox.access_token"] = m_settings.accessToken(); + + geoservice_provider = new QGeoServiceProvider("mapbox", parameters); + routing_manager = geoservice_provider->routingManager(); + if (routing_manager == nullptr) { + qDebug() << geoservice_provider->errorString(); + assert(routing_manager); + } + QObject::connect(routing_manager, &QGeoRoutingManager::finished, this, &MapWindow::routeCalculated); + + auto last_gps_position = coordinate_from_param("LastGPSPosition"); + if (last_gps_position) { + last_position = *last_gps_position; + } + + grabGesture(Qt::GestureType::PinchGesture); +} + +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(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["id"] = "navLayer"; + nav["type"] = "line"; + nav["source"] = "navSource"; + m_map->addLayer(nav, "road-intersection"); + m_map->setPaintProperty("navLayer", "line-color", QColor("#31a1ee")); + m_map->setPaintProperty("navLayer", "line-width", 7.5); + m_map->setLayoutProperty("navLayer", "line-cap", "round"); + } + if (!m_map->layerExists("carPosLayer")) { + qDebug() << "Initializing carPosLayer"; + m_map->addImage("label-arrow", QImage("../assets/images/triangle.svg")); + + QVariantMap carPos; + carPos["id"] = "carPosLayer"; + carPos["type"] = "symbol"; + carPos["source"] = "carPosSource"; + m_map->addLayer(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); + m_map->setLayoutProperty("carPosLayer", "symbol-sort-key", 0); + } +} + +void MapWindow::timerUpdate() { + if (isVisible()) { + update(); + } + + sm->update(0); + if (sm->updated("liveLocationKalman")) { + auto location = (*sm)["liveLocationKalman"].getLiveLocationKalman(); + gps_ok = location.getGpsOK(); + + localizer_valid = location.getStatus() == cereal::LiveLocationKalman::Status::VALID; + + if (localizer_valid) { + auto pos = location.getPositionGeodetic(); + auto orientation = location.getOrientationNED(); + + float velocity = location.getVelocityCalibrated().getValue()[0]; + float bearing = RAD2DEG(orientation.getValue()[2]); + auto coordinate = QMapbox::Coordinate(pos.getValue()[0], pos.getValue()[1]); + + last_position = coordinate; + last_bearing = bearing; + velocity_filter.update(velocity); + } + } + + if (m_map.isNull()) { + return; + } + + loaded_once = loaded_once || m_map->isFullyLoaded(); + if (!loaded_once) { + map_instructions->showError("Map Loading"); + return; + } + + initLayers(); + + if (!localizer_valid) { + map_instructions->showError("Waiting for GPS"); + return; + } + + if (pan_counter == 0) { + if (last_position) m_map->setCoordinate(*last_position); + if (last_bearing) m_map->setBearing(*last_bearing); + } else { + pan_counter--; + } + + if (zoom_counter == 0) { + m_map->setZoom(util::map_val(velocity_filter.x(), 0, 30, MAX_ZOOM, MIN_ZOOM)); + } else { + zoom_counter--; + } + + // Update current location marker + auto point = coordinate_to_collection(*last_position); + QMapbox::Feature feature1(QMapbox::Feature::PointType, point, {}, {}); + QVariantMap carPosSource; + carPosSource["type"] = "geojson"; + carPosSource["data"] = QVariant::fromValue(feature1); + m_map->updateSource("carPosSource", carPosSource); + + // Show route instructions + if (segment.isValid()) { + auto cur_maneuver = segment.maneuver(); + auto attrs = cur_maneuver.extendedAttributes(); + if (cur_maneuver.isValid() && attrs.contains("mapbox.banner_instructions")) { + float along_geometry = distance_along_geometry(segment.path(), to_QGeoCoordinate(*last_position)); + float distance_to_maneuver = segment.distance() - along_geometry; + emit distanceChanged(std::max(0.0f, distance_to_maneuver)); + + m_map->setPitch(MAX_PITCH); // TODO: smooth pitching based on maneuver distance + + auto banner = attrs["mapbox.banner_instructions"].toList(); + if (banner.size()) { + auto banner_0 = banner[0].toMap(); + float show_at = banner_0["distance_along_geometry"].toDouble(); + emit instructionsChanged(banner_0, distance_to_maneuver < show_at); + } + + // Transition to next route segment + if (distance_to_maneuver < -MANEUVER_TRANSITION_THRESHOLD) { + auto next_segment = segment.nextRouteSegment(); + if (next_segment.isValid()) { + segment = next_segment; + + recompute_backoff = std::max(0, recompute_backoff - 1); + recompute_countdown = 0; + } else { + qWarning() << "Destination reached"; + Params().remove("NavDestination"); + + // Clear route if driving away from destination + float d = segment.maneuver().position().distanceTo(to_QGeoCoordinate(*last_position)); + if (d > REROUTE_DISTANCE) { + clearRoute(); + } + } + } + } + } +} + +void MapWindow::resizeGL(int w, int h) { + m_map->resize(size() / MAP_SCALE); + map_instructions->setFixedWidth(width()); +} + +void MapWindow::initializeGL() { + m_map.reset(new QMapboxGL(nullptr, m_settings, size(), 1)); + + if (last_position) { + m_map->setCoordinateZoom(*last_position, MAX_ZOOM); + } else { + m_map->setCoordinateZoom(QMapbox::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/ckr64tlwp0azb17nqvr9fj13s"); + + connect(m_map.data(), SIGNAL(needsRendering()), this, SLOT(update())); + QObject::connect(m_map.data(), &QMapboxGL::mapChanged, [=](QMapboxGL::MapChange change) { + if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingMap) { + loaded_once = true; + } + }); +} + +void MapWindow::paintGL() { + if (!isVisible()) return; + m_map->render(); +} + +static float get_time_typical(const QGeoRouteSegment &segment) { + auto maneuver = segment.maneuver(); + auto attrs = maneuver.extendedAttributes(); + return attrs.contains("mapbox.duration_typical") ? attrs["mapbox.duration_typical"].toDouble() : segment.travelTime(); +} + + +void MapWindow::recomputeRoute() { + if (!QUIState::ui_state.scene.started) { + return; + } + + // Retry all timed out requests + if (!m_map.isNull()) { + m_map->connectionEstablished(); + } + + if (!last_position) { + return; + } + + auto new_destination = coordinate_from_param("NavDestination"); + if (!new_destination) { + clearRoute(); + return; + } + + bool should_recompute = shouldRecompute(); + if (*new_destination != nav_destination) { + qWarning() << "Got new destination from NavDestination param" << *new_destination; + + // Only open the map on setting destination the first time + if (allow_open) { + setVisible(true); // Show map on destination set/change + allow_open = false; + } + + // TODO: close sidebar + + should_recompute = true; + } + + if (!should_recompute) updateETA(); // ETA is updated after recompute + + if (!gps_ok && segment.isValid()) return; // Don't recompute when gps drifts in tunnels + + // Only do API request when map is fully loaded + if (loaded_once) { + if (recompute_countdown == 0 && should_recompute) { + recompute_countdown = std::pow(2, recompute_backoff); + recompute_backoff = std::min(7, recompute_backoff + 1); + calculateRoute(*new_destination); + } else { + recompute_countdown = std::max(0, recompute_countdown - 1); + } + } +} + +void MapWindow::updateETA() { + if (segment.isValid()) { + float progress = distance_along_geometry(segment.path(), to_QGeoCoordinate(*last_position)) / segment.distance(); + float total_distance = segment.distance() * (1.0 - progress); + float total_time = segment.travelTime() * (1.0 - progress); + float total_time_typical = get_time_typical(segment) * (1.0 - progress); + + auto s = segment.nextRouteSegment(); + while (s.isValid()) { + total_distance += s.distance(); + total_time += s.travelTime(); + total_time_typical += get_time_typical(s); + + s = s.nextRouteSegment(); + } + + emit ETAChanged(total_time, total_time_typical, total_distance); + } +} + +void MapWindow::calculateRoute(QMapbox::Coordinate destination) { + qWarning() << "Calculating route" << *last_position << "->" << destination; + + nav_destination = destination; + QGeoRouteRequest request(to_QGeoCoordinate(*last_position), to_QGeoCoordinate(destination)); + request.setFeatureWeight(QGeoRouteRequest::TrafficFeature, QGeoRouteRequest::AvoidFeatureWeight); + + if (last_bearing) { + QVariantMap params; + int bearing = ((int)(*last_bearing) + 360) % 360; + params["bearing"] = bearing; + request.setWaypointsMetadata({params}); + } + + routing_manager->calculateRoute(request); +} + +void MapWindow::routeCalculated(QGeoRouteReply *reply) { + bool got_route = false; + if (reply->error() == QGeoRouteReply::NoError) { + if (reply->routes().size() != 0) { + qWarning() << "Got route response"; + + route = reply->routes().at(0); + segment = route.firstRouteSegment(); + + auto route_points = coordinate_list_to_collection(route.path()); + QMapbox::Feature feature(QMapbox::Feature::LineStringType, route_points, {}, {}); + QVariantMap navSource; + navSource["type"] = "geojson"; + navSource["data"] = QVariant::fromValue(feature); + m_map->updateSource("navSource", navSource); + m_map->setLayoutProperty("navLayer", "visibility", "visible"); + got_route = true; + + updateETA(); + } else { + qWarning() << "Got empty route response"; + } + } else { + qWarning() << "Got error in route reply" << reply->errorString(); + } + + if (!got_route) { + map_instructions->showError("Failed to Route"); + } + + reply->deleteLater(); +} + +void MapWindow::clearRoute() { + segment = QGeoRouteSegment(); + nav_destination = QMapbox::Coordinate(); + + if (!m_map.isNull()) { + m_map->setLayoutProperty("navLayer", "visibility", "none"); + m_map->setPitch(MIN_PITCH); + } + + map_instructions->hideIfNoError(); + map_eta->setVisible(false); + allow_open = true; +} + + +bool MapWindow::shouldRecompute() { + if (!segment.isValid()) { + return true; + } + + // Compute closest distance to all line segments in the current path + float min_d = REROUTE_DISTANCE + 1; + auto path = segment.path(); + auto cur = to_QGeoCoordinate(*last_position); + for (size_t i = 0; i < path.size() - 1; i++) { + auto a = path[i]; + auto b = path[i+1]; + if (a.distanceTo(b) < 1.0) { + continue; + } + min_d = std::min(min_d, minimum_distance(a, b, cur)); + } + return min_d > REROUTE_DISTANCE; + + // TODO: Check for going wrong way in segment +} + +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(velocity_filter.x(), 0, 30, MAX_ZOOM, MIN_ZOOM)); + + pan_counter = 0; + zoom_counter = 0; +} + +void MapWindow::mouseMoveEvent(QMouseEvent *ev) { + QPointF delta = ev->localPos() - m_lastPos; + + if (!delta.isNull()) { + pan_counter = PAN_TIMEOUT; + m_map->moveBy(delta / MAP_SCALE); + } + + 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); + zoom_counter = PAN_TIMEOUT; + ev->accept(); +} + +bool MapWindow::event(QEvent *event) { + if (event->type() == QEvent::Gesture) { + return gestureEvent(static_cast(event)); + } + + return QWidget::event(event); +} + +bool MapWindow::gestureEvent(QGestureEvent *event) { + if (QGesture *pinch = event->gesture(Qt::PinchGesture)) { + pinchTriggered(static_cast(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}); + zoom_counter = PAN_TIMEOUT; + } +} + +void MapWindow::offroadTransition(bool offroad) { + if (!offroad) { + auto dest = coordinate_from_param("NavDestination"); + setVisible(dest.has_value()); + } + last_bearing = {}; +} + +MapInstructions::MapInstructions(QWidget * parent) : QWidget(parent) { + QHBoxLayout *main_layout = new QHBoxLayout(this); + main_layout->setContentsMargins(11, 50, 11, 11); + { + QVBoxLayout *layout = new QVBoxLayout; + icon_01 = new QLabel; + layout->addWidget(icon_01); + layout->addStretch(); + main_layout->addLayout(layout); + } + + { + QWidget *w = new QWidget; + QVBoxLayout *layout = new QVBoxLayout(w); + + distance = new QLabel; + distance->setStyleSheet(R"(font-size: 90px;)"); + layout->addWidget(distance); + + primary = new QLabel; + primary->setStyleSheet(R"(font-size: 60px;)"); + primary->setWordWrap(true); + layout->addWidget(primary); + + secondary = new QLabel; + secondary->setStyleSheet(R"(font-size: 50px;)"); + secondary->setWordWrap(true); + layout->addWidget(secondary); + + lane_layout = new QHBoxLayout; + layout->addLayout(lane_layout); + + main_layout->addWidget(w); + } + + setStyleSheet(R"( + * { + color: white; + font-family: "Inter"; + } + )"); + + QPalette pal = palette(); + pal.setColor(QPalette::Background, QColor(0, 0, 0, 150)); + setAutoFillBackground(true); + setPalette(pal); +} + +void MapInstructions::updateDistance(float d) { + QString distance_str; + + if (QUIState::ui_state.scene.is_metric) { + if (d > 500) { + distance_str.setNum(d / 1000, 'f', 1); + distance_str += " km"; + } else { + distance_str.setNum(50 * int(d / 50)); + distance_str += " m"; + } + } else { + float miles = d * METER_2_MILE; + float feet = d * METER_2_FOOT; + + if (feet > 500) { + distance_str.setNum(miles, 'f', 1); + distance_str += " mi"; + } else { + distance_str.setNum(50 * int(feet / 50)); + distance_str += " ft"; + } + } + + distance->setAlignment(Qt::AlignLeft); + distance->setText(distance_str); +} + +void MapInstructions::showError(QString error) { + primary->setText(""); + distance->setText(error); + distance->setAlignment(Qt::AlignCenter); + + secondary->setVisible(false); + icon_01->setVisible(false); + + last_banner = {}; + error = true; + + setVisible(true); + adjustSize(); +} + +void MapInstructions::updateInstructions(QMap banner, bool full) { + // Need multiple calls to adjustSize for it to properly resize + // seems like it takes a little bit of time for the images to change and + // the size can only be changed afterwards + adjustSize(); + + // Word wrap widgets need fixed width + primary->setFixedWidth(width() - 250); + secondary->setFixedWidth(width() - 250); + + if (banner == last_banner) return; + QString primary_str, secondary_str; + + auto p = banner["primary"].toMap(); + primary_str += p["text"].toString(); + + // Show arrow with direction + if (p.contains("type")) { + QString fn = "../assets/navigation/direction_" + p["type"].toString(); + if (p.contains("modifier")) { + fn += "_" + p["modifier"].toString(); + } + fn += + ".png"; + fn = fn.replace(' ', '_'); + + QPixmap pix(fn); + icon_01->setPixmap(pix.scaledToWidth(200, Qt::SmoothTransformation)); + icon_01->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + icon_01->setVisible(true); + } + + // Parse components (e.g. lanes, exit number) + auto components = p["components"].toList(); + QString icon_fn; + for (auto &c : components) { + auto cc = c.toMap(); + if (cc["type"].toString() == "icon") { + icon_fn = cc["imageBaseURL"].toString() + "@3x.png"; + } + } + + if (banner.contains("secondary") && full) { + auto s = banner["secondary"].toMap(); + secondary_str += s["text"].toString(); + } + + clearLayout(lane_layout); + bool has_lanes = false; + + if (banner.contains("sub") && full) { + auto s = banner["sub"].toMap(); + auto components = s["components"].toList(); + for (auto &c : components) { + auto cc = c.toMap(); + if (cc["type"].toString() == "lane") { + has_lanes = true; + + bool left = false; + bool straight = false; + bool right = false; + bool active = cc["active"].toBool(); + + for (auto &dir : cc["directions"].toList()) { + auto d = dir.toString(); + left |= d.contains("left"); + straight |= d.contains("straight"); + right |= d.contains("right"); + } + + // TODO: Make more images based on active direction and combined directions + QString fn = "../assets/navigation/direction_"; + if (left) { + fn += "turn_left"; + } else if (right) { + fn += "turn_right"; + } else if (straight) { + fn += "turn_straight"; + } + + QPixmap pix(fn + ".png"); + auto icon = new QLabel; + icon->setPixmap(pix.scaledToWidth(active ? 125 : 75, Qt::SmoothTransformation)); + icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + lane_layout->addWidget(icon); + } + } + } + + primary->setText(primary_str); + secondary->setVisible(secondary_str.length() > 0); + secondary->setText(secondary_str); + + last_banner = banner; + error = false; + + show(); + adjustSize(); +} + +void MapInstructions::hideIfNoError() { + if (!error) { + hide(); + } +} + +MapETA::MapETA(QWidget * parent) : QWidget(parent) { + QHBoxLayout *main_layout = new QHBoxLayout(this); + main_layout->setContentsMargins(40, 25, 40, 25); + + { + QHBoxLayout *layout = new QHBoxLayout; + eta = new QLabel; + eta->setAlignment(Qt::AlignCenter); + eta->setStyleSheet("font-weight:600"); + + eta_unit = new QLabel; + eta_unit->setAlignment(Qt::AlignCenter); + + layout->addWidget(eta); + layout->addWidget(eta_unit); + main_layout->addLayout(layout); + } + main_layout->addSpacing(40); + { + QHBoxLayout *layout = new QHBoxLayout; + time = new QLabel; + time->setAlignment(Qt::AlignCenter); + + time_unit = new QLabel; + time_unit->setAlignment(Qt::AlignCenter); + + layout->addWidget(time); + layout->addWidget(time_unit); + main_layout->addLayout(layout); + } + main_layout->addSpacing(40); + { + QHBoxLayout *layout = new QHBoxLayout; + distance = new QLabel; + distance->setAlignment(Qt::AlignCenter); + distance->setStyleSheet("font-weight:600"); + + distance_unit = new QLabel; + distance_unit->setAlignment(Qt::AlignCenter); + + layout->addWidget(distance); + layout->addWidget(distance_unit); + main_layout->addLayout(layout); + } + + setStyleSheet(R"( + * { + color: white; + font-family: "Inter"; + font-size: 70px; + } + )"); + + QPalette pal = palette(); + pal.setColor(QPalette::Background, QColor(0, 0, 0, 150)); + setAutoFillBackground(true); + setPalette(pal); +} + + +void MapETA::updateETA(float s, float s_typical, float d) { + if (d < MANEUVER_TRANSITION_THRESHOLD) { + hide(); + return; + } + + // ETA + auto eta_time = QDateTime::currentDateTime().addSecs(s).time(); + if (params.getBool("NavSettingTime24h")) { + eta->setText(eta_time.toString("HH:mm")); + eta_unit->setText("eta"); + } else { + auto t = eta_time.toString("h:mm a").split(' '); + eta->setText(t[0]); + eta_unit->setText(t[1]); + } + + // Remaining time + if (s < 3600) { + time->setText(QString::number(int(s / 60))); + time_unit->setText("min"); + } else { + int hours = int(s) / 3600; + time->setText(QString::number(hours) + ":" + QString::number(int((s - hours * 3600) / 60)).rightJustified(2, '0')); + time_unit->setText("hr"); + } + + QString color; + if (s / s_typical > 1.5) { + color = "#DA3025"; + } else if (s / s_typical > 1.2) { + color = "#DAA725"; + } else { + color = "#25DA6E"; + } + + time->setStyleSheet(QString(R"(color: %1; font-weight:600;)").arg(color)); + time_unit->setStyleSheet(QString(R"(color: %1;)").arg(color)); + + // Distance + QString distance_str; + float num = 0; + if (QUIState::ui_state.scene.is_metric) { + num = d / 1000.0; + distance_unit->setText("km"); + } else { + num = d * METER_2_MILE; + distance_unit->setText("mi"); + } + + distance_str.setNum(num, 'f', num < 100 ? 1 : 0); + distance->setText(distance_str); + + show(); + adjustSize(); + repaint(); + adjustSize(); + + // Rounded corners + const int radius = 25; + const auto r = rect(); + + // Top corners rounded + QPainterPath path; + path.setFillRule(Qt::WindingFill); + path.addRoundedRect(r, radius, radius); + + // Bottom corners not rounded + path.addRect(r.marginsRemoved(QMargins(0, radius, 0, 0))); + + // Set clipping mask + QRegion mask = QRegion(path.simplified().toFillPolygon().toPolygon()); + setMask(mask); + + // Center + move(static_cast(parent())->width() / 2 - width() / 2, 1080 - height() - bdr_s*2); +} diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h new file mode 100644 index 000000000..a796872b3 --- /dev/null +++ b/selfdrive/ui/qt/maps/map.h @@ -0,0 +1,148 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "selfdrive/common/params.h" +#include "selfdrive/common/util.h" +#include "cereal/messaging/messaging.h" + +class MapInstructions : public QWidget { + Q_OBJECT + +private: + QLabel *distance; + QLabel *primary; + QLabel *secondary; + QLabel *icon_01; + QHBoxLayout *lane_layout; + QMap last_banner; + bool error = false; + +public: + MapInstructions(QWidget * parent=nullptr); + void showError(QString error); + void hideIfNoError(); + +public slots: + void updateDistance(float d); + void updateInstructions(QMap banner, bool full); +}; + +class MapETA : public QWidget { + Q_OBJECT + +private: + QLabel *eta; + QLabel *eta_unit; + QLabel *time; + QLabel *time_unit; + QLabel *distance; + QLabel *distance_unit; + Params params; + +public: + MapETA(QWidget * parent=nullptr); + +public slots: + void updateETA(float seconds, float seconds_typical, float distance); +}; + +class MapWindow : public QOpenGLWidget { + Q_OBJECT + +public: + MapWindow(const QMapboxGLSettings &); + ~MapWindow(); + +private: + void initializeGL() final; + void paintGL() final; + void resizeGL(int w, int h) override; + + QMapboxGLSettings m_settings; + QScopedPointer 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); + + bool m_sourceAdded = false; + SubMaster *sm; + QTimer* timer; + + bool loaded_once = false; + + // Panning + QPointF m_lastPos; + int pan_counter = 0; + int zoom_counter = 0; + + // Position + std::optional last_position; + std::optional last_bearing; + FirstOrderFilter velocity_filter; + bool localizer_valid = false; + + // Route + bool allow_open = true; + bool gps_ok = false; + QGeoServiceProvider *geoservice_provider; + QGeoRoutingManager *routing_manager; + QGeoRoute route; + QGeoRouteSegment segment; + + MapInstructions* map_instructions; + MapETA* map_eta; + + QMapbox::Coordinate nav_destination; + + // Route recompute + QTimer* recompute_timer; + int recompute_backoff = 0; + int recompute_countdown = 0; + void calculateRoute(QMapbox::Coordinate destination); + void clearRoute(); + bool shouldRecompute(); + void updateETA(); + +private slots: + void timerUpdate(); + void routeCalculated(QGeoRouteReply *reply); + void recomputeRoute(); + +public slots: + void offroadTransition(bool offroad); + +signals: + void distanceChanged(float distance); + void instructionsChanged(QMap banner, bool full); + void ETAChanged(float seconds, float seconds_typical, float distance); +}; + diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc new file mode 100644 index 000000000..16730e6c5 --- /dev/null +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -0,0 +1,131 @@ +#include "selfdrive/ui/qt/maps/map_helpers.h" + +#include +#include + +#include "selfdrive/common/params.h" + + +QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in) { + return QGeoCoordinate(in.first, in.second); +} + +QMapbox::CoordinatesCollections model_to_collection( + const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF, + const cereal::LiveLocationKalman::Measurement::Reader &positionECEF, + const cereal::ModelDataV2::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).transpose(); + + QMapbox::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]}); + QMapbox::Coordinate coordinate(point_geodetic.lat, point_geodetic.lon); + coordinates.push_back(coordinate); + } + + QMapbox::CoordinatesCollection collection; + collection.push_back(coordinates); + + QMapbox::CoordinatesCollections collections; + collections.push_back(collection); + return collections; +} + +QMapbox::CoordinatesCollections coordinate_to_collection(QMapbox::Coordinate c) { + QMapbox::Coordinates coordinates; + coordinates.push_back(c); + + QMapbox::CoordinatesCollection collection; + collection.push_back(coordinates); + + QMapbox::CoordinatesCollections collections; + collections.push_back(collection); + return collections; +} + +QMapbox::CoordinatesCollections coordinate_list_to_collection(QList coordinate_list) { + QMapbox::Coordinates coordinates; + + for (auto &c : coordinate_list) { + QMapbox::Coordinate coordinate(c.latitude(), c.longitude()); + coordinates.push_back(coordinate); + } + + QMapbox::CoordinatesCollection collection; + collection.push_back(coordinates); + + QMapbox::CoordinatesCollections collections; + collections.push_back(collection); + return collections; +} + +static QGeoCoordinate sub(QGeoCoordinate v, QGeoCoordinate w) { + return QGeoCoordinate(v.latitude() - w.latitude(), v.longitude() - w.longitude()); +} + +static QGeoCoordinate add(QGeoCoordinate v, QGeoCoordinate w) { + return QGeoCoordinate(v.latitude() + w.latitude(), v.longitude() + w.longitude()); +} + +static QGeoCoordinate mul(QGeoCoordinate v, float c) { + return QGeoCoordinate(c * v.latitude(), c * v.longitude()); +} + +static float dot(QGeoCoordinate v, QGeoCoordinate w) { + return v.latitude() * w.latitude() + v.longitude() * w.longitude(); +} + +float minimum_distance(QGeoCoordinate a, QGeoCoordinate b, QGeoCoordinate p) { + const QGeoCoordinate ap = sub(p, a); + const QGeoCoordinate ab = sub(b, a); + const float t = std::clamp(dot(ap, ab) / dot(ab, ab), 0.0f, 1.0f); + const QGeoCoordinate projection = add(a, mul(ab, t)); + return projection.distanceTo(p); +} + +float distance_along_geometry(QList geometry, QGeoCoordinate pos) { + if (geometry.size() <= 2) { + return geometry[0].distanceTo(pos); + } + + // 1. Find segment that is closest to current position + // 2. Total distance is sum of distance to start of closest segment + // + all previous segments + double total_distance = 0; + double total_distance_closest = 0; + double closest_distance = std::numeric_limits::max(); + + for (int i = 0; i < geometry.size() - 1; i++) { + double d = minimum_distance(geometry[i], geometry[i+1], pos); + if (d < closest_distance) { + closest_distance = d; + total_distance_closest = total_distance + geometry[i].distanceTo(pos); + } + total_distance += geometry[i].distanceTo(geometry[i+1]); + } + + return total_distance_closest; +} + +std::optional coordinate_from_param(std::string param) { + 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()) { + QMapbox::Coordinate coord(json["latitude"].toDouble(), json["longitude"].toDouble()); + return coord; + } else { + return {}; + } +} diff --git a/selfdrive/ui/qt/maps/map_helpers.h b/selfdrive/ui/qt/maps/map_helpers.h new file mode 100644 index 000000000..a85352ff8 --- /dev/null +++ b/selfdrive/ui/qt/maps/map_helpers.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + +#include "common/transformations/coordinates.hpp" +#include "common/transformations/orientation.hpp" +#include "cereal/messaging/messaging.h" + +const float METER_2_MILE = 0.000621371; +const float METER_2_FOOT = 3.28084; +#define RAD2DEG(x) ((x) * 180.0 / M_PI) + +QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in); +QMapbox::CoordinatesCollections model_to_collection( + const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF, + const cereal::LiveLocationKalman::Measurement::Reader &positionECEF, + const cereal::ModelDataV2::XYZTData::Reader &line); +QMapbox::CoordinatesCollections coordinate_to_collection(QMapbox::Coordinate c); +QMapbox::CoordinatesCollections coordinate_list_to_collection(QList coordinate_list); + +float minimum_distance(QGeoCoordinate a, QGeoCoordinate b, QGeoCoordinate p); +std::optional coordinate_from_param(std::string param); +float distance_along_geometry(QList geometry, QGeoCoordinate pos); diff --git a/selfdrive/ui/qt/maps/map_settings.cc b/selfdrive/ui/qt/maps/map_settings.cc new file mode 100644 index 000000000..4e5d4ea48 --- /dev/null +++ b/selfdrive/ui/qt/maps/map_settings.cc @@ -0,0 +1,252 @@ +#include "map_settings.h" + +#include + +#include "selfdrive/common/util.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/qt_window.h" +#include "selfdrive/ui/qt/request_repeater.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" + +static QString shorten(const QString &str, int max_len) { + return str.size() > max_len ? str.left(max_len).trimmed() + "…" : str; +} + +MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { + stack = new QStackedWidget; + + QWidget * main_widget = new QWidget; + QVBoxLayout *main_layout = new QVBoxLayout(main_widget); + const int icon_size = 200; + + // Home + QHBoxLayout *home_layout = new QHBoxLayout; + home_button = new QPushButton; + home_button->setIconSize(QSize(icon_size, icon_size)); + home_layout->addWidget(home_button); + + home_address = new QLabel; + home_address->setWordWrap(true); + home_layout->addSpacing(30); + home_layout->addWidget(home_address); + home_layout->addStretch(); + + // Work + QHBoxLayout *work_layout = new QHBoxLayout; + work_button = new QPushButton; + work_button->setIconSize(QSize(icon_size, icon_size)); + work_layout->addWidget(work_button); + + work_address = new QLabel; + work_address->setWordWrap(true); + work_layout->addSpacing(30); + work_layout->addWidget(work_address); + work_layout->addStretch(); + + // Home & Work layout + QHBoxLayout *home_work_layout = new QHBoxLayout; + home_work_layout->addLayout(home_layout, 1); + home_work_layout->addSpacing(50); + home_work_layout->addLayout(work_layout, 1); + + main_layout->addLayout(home_work_layout); + main_layout->addSpacing(20); + main_layout->addWidget(horizontal_line()); + main_layout->addSpacing(20); + + // Recents + recent_layout = new QVBoxLayout; + QWidget *recent_widget = new LayoutWidget(recent_layout, this); + ScrollView *recent_scroller = new ScrollView(recent_widget, this); + main_layout->addWidget(recent_scroller, 1); + + QWidget * no_prime_widget = new QWidget; + QVBoxLayout *no_prime_layout = new QVBoxLayout(no_prime_widget); + QLabel *signup_header = new QLabel("Try the Navigation Beta"); + signup_header->setStyleSheet(R"(font-size: 75px; color: white; font-weight:600;)"); + signup_header->setAlignment(Qt::AlignCenter); + + no_prime_layout->addWidget(signup_header); + no_prime_layout->addSpacing(50); + + QLabel *screenshot = new QLabel; + QPixmap pm = QPixmap("../assets/navigation/screenshot.png"); + screenshot->setPixmap(pm.scaledToWidth(vwp_w * 0.5, Qt::SmoothTransformation)); + no_prime_layout->addWidget(screenshot, 0, Qt::AlignHCenter); + + QLabel *signup = new QLabel("Get turn-by-turn directions displayed and more with a comma \nprime subscription. Sign up now: https://connect.comma.ai"); + signup->setStyleSheet(R"(font-size: 45px; color: white; font-weight:300;)"); + signup->setAlignment(Qt::AlignCenter); + + no_prime_layout->addSpacing(50); + no_prime_layout->addWidget(signup); + + no_prime_layout->addStretch(); + + stack->addWidget(main_widget); + stack->addWidget(no_prime_widget); + stack->setCurrentIndex(1); + + QVBoxLayout *wrapper = new QVBoxLayout(this); + wrapper->addWidget(stack); + + clear(); + + std::string dongle_id = params.get("DongleId"); + if (util::is_valid_dongle_id(dongle_id)) { + // Fetch favorite and recent locations + { + std::string url = "https://api.commadotai.com/v1/navigation/" + dongle_id + "/locations"; + RequestRepeater* repeater = new RequestRepeater(this, QString::fromStdString(url), "ApiCache_NavDestinations", 30, true); + QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &MapPanel::parseResponse); + QObject::connect(repeater, &RequestRepeater::failedResponse, this, &MapPanel::failedResponse); + } + + // Destination set while offline + { + QString url = QString::fromStdString("https://api.commadotai.com/v1/navigation/" + dongle_id + "/next"); + RequestRepeater* repeater = new RequestRepeater(this, url, "", 10, true); + HttpRequest* deleter = new HttpRequest(this); + + QObject::connect(repeater, &RequestRepeater::receivedResponse, [=](QString resp) { + auto params = Params(); + if (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); + } + }); + } + } +} + +void MapPanel::clear() { + home_button->setIcon(QPixmap("../assets/navigation/home_inactive.png")); + home_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); + home_address->setText("No home\nlocation set"); + home_button->disconnect(); + + work_button->setIcon(QPixmap("../assets/navigation/work_inactive.png")); + work_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); + work_address->setText("No work\nlocation set"); + work_button->disconnect(); + + clearLayout(recent_layout); +} + + +void MapPanel::parseResponse(const QString &response) { + QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8()); + if (doc.isNull()) { + qDebug() << "JSON Parse failed on navigation locations"; + return; + } + + clear(); + + bool has_recents = false; + for (auto &save_type: {"favorite", "recent"}) { + for (auto location : doc.array()) { + auto obj = location.toObject(); + + auto type = obj["save_type"].toString(); + auto label = obj["label"].toString(); + auto name = obj["place_name"].toString(); + auto details = obj["place_details"].toString(); + + if (type != save_type) continue; + + if (type == "favorite" && label == "home") { + home_address->setText(name); + home_address->setStyleSheet(R"(font-size: 50px; color: white;)"); + home_button->setIcon(QPixmap("../assets/navigation/home.png")); + QObject::connect(home_button, &QPushButton::clicked, [=]() { + navigateTo(obj); + emit closeSettings(); + }); + } else if (type == "favorite" && label == "work") { + work_address->setText(name); + work_address->setStyleSheet(R"(font-size: 50px; color: white;)"); + work_button->setIcon(QPixmap("../assets/navigation/work.png")); + QObject::connect(work_button, &QPushButton::clicked, [=]() { + navigateTo(obj); + emit closeSettings(); + }); + } else { + ClickableWidget *widget = new ClickableWidget; + QHBoxLayout *layout = new QHBoxLayout(widget); + layout->setContentsMargins(15, 14, 40, 14); + + QLabel *star = new QLabel("★"); + auto sp = star->sizePolicy(); + sp.setRetainSizeWhenHidden(true); + star->setSizePolicy(sp); + + star->setVisible(type == "favorite"); + star->setStyleSheet(R"(font-size: 60px;)"); + layout->addWidget(star); + layout->addSpacing(10); + + + QLabel *recent_label = new QLabel(shorten(name + " " + details, 45)); + recent_label->setStyleSheet(R"(font-size: 50px;)"); + + layout->addWidget(recent_label); + layout->addStretch(); + + QLabel *arrow = new QLabel("→"); + arrow->setStyleSheet(R"(font-size: 60px;)"); + layout->addWidget(arrow); + + widget->setStyleSheet(R"( + .ClickableWidget { + border-radius: 10px; + border-width: 1px; + border-style: solid; + border-color: gray; + } + QWidget { + background-color: #393939; + color: #9c9c9c; + } + )"); + + QObject::connect(widget, &ClickableWidget::clicked, [=]() { + navigateTo(obj); + emit closeSettings(); + }); + + recent_layout->addWidget(widget); + recent_layout->addSpacing(10); + has_recents = true; + } + } + + } + + if (!has_recents) { + QLabel *no_recents = new QLabel("no recent destinations"); + no_recents->setStyleSheet(R"(font-size: 50px; color: #9c9c9c)"); + recent_layout->addWidget(no_recents); + } + + recent_layout->addStretch(); + stack->setCurrentIndex(0); + repaint(); +} + +void MapPanel::failedResponse(const QString &response) { + stack->setCurrentIndex(1); +} + +void MapPanel::navigateTo(const QJsonObject &place) { + QJsonDocument doc(place); + params.put("NavDestination", doc.toJson().toStdString()); +} diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h new file mode 100644 index 000000000..2eb462d67 --- /dev/null +++ b/selfdrive/ui/qt/maps/map_settings.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +#include "selfdrive/common/params.h" + +class MapPanel : public QWidget { + Q_OBJECT +public: + explicit MapPanel(QWidget* parent = nullptr); + + void navigateTo(const QJsonObject &place); + void parseResponse(const QString &response); + void failedResponse(const QString &response); + void clear(); + +private: + Params params; + QStackedWidget *stack; + QPushButton *home_button, *work_button; + QLabel *home_address, *work_address; + QVBoxLayout *recent_layout; + +signals: + void closeSettings(); +}; diff --git a/selfdrive/ui/qt/offroad/networking.cc b/selfdrive/ui/qt/offroad/networking.cc index aa3e523da..d9f819e4c 100644 --- a/selfdrive/ui/qt/offroad/networking.cc +++ b/selfdrive/ui/qt/offroad/networking.cc @@ -1,43 +1,36 @@ #include "selfdrive/ui/qt/offroad/networking.h" +#include + #include #include #include #include +#include -#include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/qt_window.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" -void NetworkStrengthWidget::paintEvent(QPaintEvent* event) { - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - p.setPen(Qt::NoPen); - const QColor gray(0x54, 0x54, 0x54); - for (int i = 0, x = 0; i < 5; ++i) { - p.setBrush(i < strength_ ? Qt::white : gray); - p.drawEllipse(x, 0, 15, 15); - x += 20; - } -} - // Networking functions -Networking::Networking(QWidget* parent, bool show_advanced) : QWidget(parent), show_advanced(show_advanced) { +Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) { main_layout = new QStackedLayout(this); wifi = new WifiManager(this); - connect(wifi, &WifiManager::wrongPassword, this, &Networking::wrongPassword); connect(wifi, &WifiManager::refreshSignal, this, &Networking::refresh); + connect(wifi, &WifiManager::wrongPassword, this, &Networking::wrongPassword); QWidget* wifiScreen = new QWidget(this); QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen); + vlayout->setContentsMargins(20, 20, 20, 20); if (show_advanced) { QPushButton* advancedSettings = new QPushButton("Advanced"); advancedSettings->setObjectName("advancedBtn"); advancedSettings->setStyleSheet("margin-right: 30px;"); advancedSettings->setFixedSize(350, 100); - connect(advancedSettings, &QPushButton::released, [=]() { main_layout->setCurrentWidget(an); }); + connect(advancedSettings, &QPushButton::clicked, [=]() { main_layout->setCurrentWidget(an); }); vlayout->addSpacing(10); vlayout->addWidget(advancedSettings, 0, Qt::AlignRight); vlayout->addSpacing(10); @@ -46,13 +39,22 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QWidget(parent), s wifiWidget = new WifiUI(this, wifi); wifiWidget->setObjectName("wifiWidget"); connect(wifiWidget, &WifiUI::connectToNetwork, this, &Networking::connectToNetwork); - vlayout->addWidget(new ScrollView(wifiWidget, this), 1); + + ScrollView *wifiScroller = new ScrollView(wifiWidget, this); + wifiScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + vlayout->addWidget(wifiScroller, 1); main_layout->addWidget(wifiScreen); an = new AdvancedNetworking(this, wifi); connect(an, &AdvancedNetworking::backPress, [=]() { main_layout->setCurrentWidget(wifiScreen); }); main_layout->addWidget(an); + QPalette pal = palette(); + pal.setColor(QPalette::Background, QColor(0x29, 0x29, 0x29)); + setAutoFillBackground(true); + setPalette(pal); + + // TODO: revisit pressed colors setStyleSheet(R"( #wifiWidget > QPushButton, #back_btn, #advancedBtn { font-size: 50px; @@ -63,10 +65,6 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QWidget(parent), s color: #dddddd; background-color: #444444; } - #wifiWidget > QPushButton:disabled { - color: #777777; - background-color: #222222; - } )"); main_layout->setCurrentWidget(wifiScreen); } @@ -79,10 +77,11 @@ void Networking::refresh() { void Networking::connectToNetwork(const Network &n) { if (wifi->isKnownConnection(n.ssid)) { wifi->activateWifiConnection(n.ssid); + wifiWidget->refresh(); } else if (n.security_type == SecurityType::OPEN) { wifi->connect(n); } else if (n.security_type == SecurityType::WPA) { - QString pass = InputDialog::getText("Enter password for \"" + n.ssid + "\"", this, 8); + QString pass = InputDialog::getText("Enter password", this, "for \"" + n.ssid + "\"", true, 8); if (!pass.isEmpty()) { wifi->connect(n, pass); } @@ -90,13 +89,11 @@ void Networking::connectToNetwork(const Network &n) { } void Networking::wrongPassword(const QString &ssid) { - for (Network n : wifi->seen_networks) { - if (n.ssid == ssid) { - QString pass = InputDialog::getText("Wrong password for \"" + n.ssid +"\"", this, 8); - if (!pass.isEmpty()) { - wifi->connect(n, pass); - } - return; + if (wifi->seenNetworks.contains(ssid)) { + const Network &n = wifi->seenNetworks.value(ssid); + QString pass = InputDialog::getText("Wrong password", this, "for \"" + n.ssid +"\"", true, 8); + if (!pass.isEmpty()) { + wifi->connect(n, pass); } } } @@ -123,19 +120,19 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid QPushButton* back = new QPushButton("Back"); back->setObjectName("back_btn"); back->setFixedSize(500, 100); - connect(back, &QPushButton::released, [=]() { emit backPress(); }); + connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); main_layout->addWidget(back, 0, Qt::AlignLeft); // Enable tethering layout - ToggleControl *tetheringToggle = new ToggleControl("Enable Tethering", "", "", wifi->isTetheringEnabled()); + tetheringToggle = new ToggleControl("Enable Tethering", "", "", wifi->isTetheringEnabled()); main_layout->addWidget(tetheringToggle); QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering); main_layout->addWidget(horizontal_line(), 0); // Change tethering password ButtonControl *editPasswordButton = new ButtonControl("Tethering Password", "EDIT"); - connect(editPasswordButton, &ButtonControl::released, [=]() { - QString pass = InputDialog::getText("Enter new tethering password", this, 8, wifi->getTetheringPassword()); + connect(editPasswordButton, &ButtonControl::clicked, [=]() { + QString pass = InputDialog::getText("Enter new tethering password", this, "", true, 8, wifi->getTetheringPassword()); if (!pass.isEmpty()) { wifi->changeTetheringPassword(pass); } @@ -158,85 +155,156 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid void AdvancedNetworking::refresh() { ipLabel->setText(wifi->ipv4_address); + tetheringToggle->setEnabled(true); update(); } void AdvancedNetworking::toggleTethering(bool enabled) { wifi->setTetheringEnabled(enabled); + tetheringToggle->setEnabled(false); } // WifiUI functions WifiUI::WifiUI(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi) { main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); + main_layout->setSpacing(0); - // Scan on startup - QLabel *scanning = new QLabel("Scanning for networks"); - scanning->setStyleSheet(R"(font-size: 65px;)"); + // load imgs + for (const auto &s : {"low", "medium", "high", "full"}) { + QPixmap pix(ASSET_PATH + "/offroad/icon_wifi_strength_" + s + ".svg"); + strengths.push_back(pix.scaledToHeight(68, Qt::SmoothTransformation)); + } + lock = QPixmap(ASSET_PATH + "offroad/icon_lock_closed.svg").scaledToWidth(49, Qt::SmoothTransformation); + checkmark = QPixmap(ASSET_PATH + "offroad/icon_checkmark.svg").scaledToWidth(49, Qt::SmoothTransformation); + circled_slash = QPixmap(ASSET_PATH + "img_circled_slash.svg").scaledToWidth(49, Qt::SmoothTransformation); + + QLabel *scanning = new QLabel("Scanning for networks..."); + scanning->setStyleSheet("font-size: 65px;"); main_layout->addWidget(scanning, 0, Qt::AlignCenter); - main_layout->setSpacing(25); + + setStyleSheet(R"( + QScrollBar::handle:vertical { + min-height: 0px; + border-radius: 4px; + background-color: #8A8A8A; + } + #forgetBtn { + font-size: 32px; + font-weight: 600; + color: #292929; + background-color: #BDBDBD; + border-width: 1px solid #828282; + border-radius: 5px; + padding: 40px; + padding-bottom: 16px; + padding-top: 16px; + } + #connecting { + font-size: 32px; + font-weight: 600; + color: white; + border-radius: 0; + padding: 27px; + padding-left: 43px; + padding-right: 43px; + background-color: black; + } + #ssidLabel { + font-size: 55px; + font-weight: 300; + text-align: left; + border: none; + padding-top: 50px; + padding-bottom: 50px; + } + #ssidLabel[disconnected=false] { + font-weight: 500; + } + #ssidLabel:disabled { + color: #696969; + } + )"); } void WifiUI::refresh() { + // TODO: don't rebuild this every time clearLayout(main_layout); - if (wifi->seen_networks.size() == 0) { - QLabel *scanning = new QLabel("No networks found. Scanning..."); - scanning->setStyleSheet(R"(font-size: 65px;)"); + + if (wifi->seenNetworks.size() == 0) { + QLabel *scanning = new QLabel("Scanning for networks..."); + scanning->setStyleSheet("font-size: 65px;"); main_layout->addWidget(scanning, 0, Qt::AlignCenter); return; } + QList sortedNetworks = wifi->seenNetworks.values(); + std::sort(sortedNetworks.begin(), sortedNetworks.end(), compare_by_strength); + // add networks int i = 0; - for (Network &network : wifi->seen_networks) { + for (Network &network : sortedNetworks) { QHBoxLayout *hlayout = new QHBoxLayout; + hlayout->setContentsMargins(44, 0, 73, 0); + hlayout->setSpacing(50); - ElidedLabel *ssid_label = new ElidedLabel(network.ssid); - ssid_label->setStyleSheet("font-size: 55px;"); - hlayout->addWidget(ssid_label, 1, Qt::AlignLeft); + // Clickable SSID label + QPushButton *ssidLabel = new QPushButton(network.ssid); + ssidLabel->setObjectName("ssidLabel"); + ssidLabel->setEnabled(network.security_type != SecurityType::UNSUPPORTED); + ssidLabel->setProperty("disconnected", network.connected == ConnectedType::DISCONNECTED); + if (network.connected == ConnectedType::DISCONNECTED) { + QObject::connect(ssidLabel, &QPushButton::clicked, this, [=]() { emit connectToNetwork(network); }); + } + hlayout->addWidget(ssidLabel, network.connected == ConnectedType::CONNECTING ? 0 : 1); + if (network.connected == ConnectedType::CONNECTING) { + QPushButton *connecting = new QPushButton("CONNECTING..."); + connecting->setObjectName("connecting"); + hlayout->addWidget(connecting, 2, Qt::AlignLeft); + } + + // Forget button if (wifi->isKnownConnection(network.ssid) && !wifi->isTetheringEnabled()) { - QPushButton *forgetBtn = new QPushButton(); - QPixmap pix("../assets/offroad/icon_close.svg"); - - forgetBtn->setIcon(QIcon(pix)); - forgetBtn->setIconSize(QSize(35, 35)); - forgetBtn->setStyleSheet("QPushButton { background-color: #E22C2C; }"); - forgetBtn->setFixedSize(100, 90); - - QObject::connect(forgetBtn, &QPushButton::released, [=]() { - if (ConfirmationDialog::confirm("Are you sure you want to forget " + QString::fromUtf8(network.ssid) + "?", this)) { + QPushButton *forgetBtn = new QPushButton("FORGET"); + forgetBtn->setObjectName("forgetBtn"); + QObject::connect(forgetBtn, &QPushButton::clicked, [=]() { + if (ConfirmationDialog::confirm("Forget WiFi Network \"" + QString::fromUtf8(network.ssid) + "\"?", this)) { wifi->forgetConnection(network.ssid); } }); - hlayout->addWidget(forgetBtn, 0, Qt::AlignRight); - } else if (network.security_type == SecurityType::WPA) { - QLabel *lockIcon = new QLabel(); - QPixmap pix("../assets/offroad/icon_lock_closed.svg"); - lockIcon->setPixmap(pix.scaledToWidth(35, Qt::SmoothTransformation)); - lockIcon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); - lockIcon->setStyleSheet("QLabel { margin: 0px; padding-left: 15px; padding-right: 15px; }"); - - hlayout->addWidget(lockIcon, 0, Qt::AlignRight); } - // strength indicator - unsigned int strength_scale = network.strength / 17; - hlayout->addWidget(new NetworkStrengthWidget(strength_scale), 0, Qt::AlignRight); + // Status icon + if (network.connected == ConnectedType::CONNECTED) { + QLabel *connectIcon = new QLabel(); + connectIcon->setPixmap(checkmark); + hlayout->addWidget(connectIcon, 0, Qt::AlignRight); + } else if (network.security_type == SecurityType::UNSUPPORTED) { + QLabel *unsupportedIcon = new QLabel(); + unsupportedIcon->setPixmap(circled_slash); + hlayout->addWidget(unsupportedIcon, 0, Qt::AlignRight); + } else if (network.security_type == SecurityType::WPA) { + QLabel *lockIcon = new QLabel(); + lockIcon->setPixmap(lock); + hlayout->addWidget(lockIcon, 0, Qt::AlignRight); + } else { + hlayout->addSpacing(lock.width() + hlayout->spacing()); + } - // connect button - QPushButton* btn = new QPushButton(network.security_type == SecurityType::UNSUPPORTED ? "Unsupported" : (network.connected == ConnectedType::CONNECTED ? "Connected" : (network.connected == ConnectedType::CONNECTING ? "Connecting" : "Connect"))); - btn->setDisabled(network.connected == ConnectedType::CONNECTED || network.connected == ConnectedType::CONNECTING || network.security_type == SecurityType::UNSUPPORTED); - btn->setFixedWidth(350); - QObject::connect(btn, &QPushButton::clicked, this, [=]() { emit connectToNetwork(network); }); + // Strength indicator + QLabel *strength = new QLabel(); + strength->setPixmap(strengths[std::clamp((int)round(network.strength / 33.), 0, 3)]); + hlayout->addWidget(strength, 0, Qt::AlignRight); + + main_layout->addLayout(hlayout); - hlayout->addWidget(btn, 0, Qt::AlignRight); - main_layout->addLayout(hlayout, 1); // Don't add the last horizontal line - if (i+1 < wifi->seen_networks.size()) { + if (i+1 < wifi->seenNetworks.size()) { main_layout->addWidget(horizontal_line(), 0); } i++; } - main_layout->addStretch(3); + main_layout->addStretch(1); } diff --git a/selfdrive/ui/qt/offroad/networking.h b/selfdrive/ui/qt/offroad/networking.h index 7c0ad319b..ed544a27c 100644 --- a/selfdrive/ui/qt/offroad/networking.h +++ b/selfdrive/ui/qt/offroad/networking.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -9,17 +10,6 @@ #include "selfdrive/ui/qt/widgets/ssh_keys.h" #include "selfdrive/ui/qt/widgets/toggle.h" -class NetworkStrengthWidget : public QWidget { - Q_OBJECT - -public: - explicit NetworkStrengthWidget(int strength, QWidget* parent = nullptr) : strength_(strength), QWidget(parent) { setFixedSize(100, 15); } - -private: - void paintEvent(QPaintEvent* event) override; - int strength_ = 0; -}; - class WifiUI : public QWidget { Q_OBJECT @@ -29,6 +19,10 @@ public: private: WifiManager *wifi = nullptr; QVBoxLayout* main_layout; + QPixmap lock; + QPixmap checkmark; + QPixmap circled_slash; + QVector strengths; signals: void connectToNetwork(const Network &n); @@ -44,6 +38,7 @@ public: private: LabelControl* ipLabel; + ToggleControl* tetheringToggle; WifiManager* wifi = nullptr; signals: @@ -54,20 +49,19 @@ public slots: void refresh(); }; -class Networking : public QWidget { +class Networking : public QFrame { Q_OBJECT public: explicit Networking(QWidget* parent = 0, bool show_advanced = true); + WifiManager* wifi = nullptr; private: - QStackedLayout* main_layout = nullptr; // nm_warning, wifiScreen, advanced + QStackedLayout* main_layout = nullptr; QWidget* wifiScreen = nullptr; AdvancedNetworking* an = nullptr; - bool show_advanced; WifiUI* wifiWidget; - WifiManager* wifi = nullptr; protected: void showEvent(QShowEvent* event) override; diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index 0034f3a39..e05b345f7 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -7,11 +7,20 @@ #include #include "selfdrive/common/util.h" +#include "selfdrive/common/params.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/input.h" +TrainingGuide::TrainingGuide(QWidget *parent) : QFrame(parent) { + setAttribute(Qt::WA_OpaquePaintEvent); +} + void TrainingGuide::mouseReleaseEvent(QMouseEvent *e) { if (boundingRect[currentIndex].contains(e->x(), e->y())) { + if (currentIndex == 9) { + const QRect yes = QRect(692, 842, 492, 148); + Params().putBool("RecordFront", yes.contains(e->x(), e->y())); + } currentIndex += 1; } else if (currentIndex == (boundingRect.size() - 2) && boundingRect.last().contains(e->x(), e->y())) { currentIndex = 0; @@ -34,12 +43,18 @@ void TrainingGuide::paintEvent(QPaintEvent *event) { QPainter painter(this); QRect bg(0, 0, painter.device()->width(), painter.device()->height()); - QBrush bgBrush("#000000"); - painter.fillRect(bg, bgBrush); + painter.fillRect(bg, QColor("#000000")); QRect rect(image.rect()); rect.moveCenter(bg.center()); painter.drawImage(rect.topLeft(), image); + + // progress bar + if (currentIndex > 0 && currentIndex < (boundingRect.size() - 2)) { + const int h = 20; + const int w = (currentIndex / (float)(boundingRect.size() - 2)) * width(); + painter.fillRect(QRect(0, height() - h, w, h), QColor("#465BEA")); + } } void TermsPage::showEvent(QShowEvent *event) { @@ -82,7 +97,7 @@ void TermsPage::showEvent(QShowEvent *event) { QPushButton *decline_btn = new QPushButton("Decline"); buttons->addWidget(decline_btn); - QObject::connect(decline_btn, &QPushButton::released, this, &TermsPage::declinedTerms); + QObject::connect(decline_btn, &QPushButton::clicked, this, &TermsPage::declinedTerms); accept_btn = new QPushButton("Scroll to accept"); accept_btn->setEnabled(false); @@ -95,7 +110,7 @@ void TermsPage::showEvent(QShowEvent *event) { } )"); buttons->addWidget(accept_btn); - QObject::connect(accept_btn, &QPushButton::released, this, &TermsPage::acceptedTerms); + QObject::connect(accept_btn, &QPushButton::clicked, this, &TermsPage::acceptedTerms); } void TermsPage::enableAccept() { @@ -125,12 +140,12 @@ void DeclinePage::showEvent(QShowEvent *event) { QPushButton *back_btn = new QPushButton("Back"); buttons->addWidget(back_btn); - QObject::connect(back_btn, &QPushButton::released, this, &DeclinePage::getBack); + QObject::connect(back_btn, &QPushButton::clicked, this, &DeclinePage::getBack); QPushButton *uninstall_btn = new QPushButton("Decline, uninstall " + getBrand()); uninstall_btn->setStyleSheet("background-color: #B73D3D"); buttons->addWidget(uninstall_btn); - QObject::connect(uninstall_btn, &QPushButton::released, [=]() { + QObject::connect(uninstall_btn, &QPushButton::clicked, [=]() { Params().putBool("DoUninstall", true); }); } diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index bcd40e157..c35ce069d 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -13,7 +13,7 @@ class TrainingGuide : public QFrame { Q_OBJECT public: - explicit TrainingGuide(QWidget *parent = 0) : QFrame(parent) {}; + explicit TrainingGuide(QWidget *parent = 0); private: void showEvent(QShowEvent *event) override; @@ -29,12 +29,13 @@ private: QRect(650, 710, 720, 190), continueBtnStandard, continueBtnStandard, - QRect(1470, 515, 235, 565), - QRect(1580, 630, 215, 130), + QRect(1442, 565, 230, 310), + QRect(1515, 562, 133, 60), continueBtnStandard, QRect(1580, 630, 215, 130), QRect(1210, 0, 485, 590), QRect(1460, 400, 375, 210), + QRect(166, 842, 1019, 148), QRect(1460, 210, 300, 310), continueBtnStandard, QRect(1375, 80, 545, 1000), @@ -52,12 +53,13 @@ private: QRect(654, 721, 718, 189), continueBtnWide, continueBtnWide, - QRect(1589, 530, 345, 555), - QRect(1660, 630, 195, 125), + QRect(1690, 570, 165, 300), + QRect(1690, 560, 133, 60), continueBtnWide, QRect(1820, 630, 180, 155), QRect(1360, 0, 460, 620), QRect(1570, 400, 375, 215), + QRect(167, 842, 1018, 148), QRect(1610, 210, 295, 310), continueBtnWide, QRect(1555, 90, 610, 990), diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 6c61166dd..66a808fd0 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -23,6 +23,7 @@ #include "selfdrive/ui/qt/widgets/toggle.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/qt_window.h" TogglesPanel::TogglesPanel(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); @@ -63,26 +64,15 @@ TogglesPanel::TogglesPanel(QWidget *parent) : QWidget(parent) { ParamControl *record_toggle = new ParamControl("RecordFront", "Record and Upload Driver Camera", - "Upload data from the driver facing camera and help improve the driver monitoring algorithm.", - "../assets/offroad/icon_monitoring.png", - this); + "Upload data from the driver facing camera and help improve the driver monitoring algorithm.", + "../assets/offroad/icon_monitoring.png", + this); toggles.append(record_toggle); toggles.append(new ParamControl("EndToEndToggle", - "\U0001f96c Disable use of lanelines (Alpha) \U0001f96c", - "In this mode openpilot will ignore lanelines and just drive how it thinks a human would.", - "../assets/offroad/icon_road.png", - this)); - - if (Hardware::TICI()) { - toggles.append(new ParamControl("EnableWideCamera", - "Enable use of Wide Angle Camera", - "Use wide angle camera for driving and ui.", - "../assets/offroad/icon_openpilot.png", - this)); - QObject::connect(toggles.back(), &ToggleControl::toggleFlipped, [=](bool state) { - Params().remove("CalibrationParams"); - }); - } + "\U0001f96c Disable use of lanelines (Alpha) \U0001f96c", + "In this mode openpilot will ignore lanelines and just drive how it thinks a human would.", + "../assets/offroad/icon_road.png", + this)); #ifdef ENABLE_MAPS toggles.append(new ParamControl("NavSettingTime24h", @@ -118,11 +108,11 @@ DevicePanel::DevicePanel(QWidget* parent) : QWidget(parent) { auto dcamBtn = new ButtonControl("Driver Camera", "PREVIEW", "Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)"); - connect(dcamBtn, &ButtonControl::released, [=]() { emit showDriverView(); }); + connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); }); QString resetCalibDesc = "openpilot requires the device to be mounted within 4° left or right and within 5° up or down. openpilot is continuously calibrating, resetting is rarely required."; auto resetCalibBtn = new ButtonControl("Reset Calibration", "RESET", resetCalibDesc); - connect(resetCalibBtn, &ButtonControl::released, [=]() { + connect(resetCalibBtn, &ButtonControl::clicked, [=]() { if (ConfirmationDialog::confirm("Are you sure you want to reset calibration?", this)) { Params().remove("CalibrationParams"); } @@ -152,7 +142,7 @@ DevicePanel::DevicePanel(QWidget* parent) : QWidget(parent) { ButtonControl *retrainingBtn = nullptr; if (!params.getBool("Passive")) { retrainingBtn = new ButtonControl("Review Training Guide", "REVIEW", "Review the rules, features, and limitations of openpilot"); - connect(retrainingBtn, &ButtonControl::released, [=]() { + connect(retrainingBtn, &ButtonControl::clicked, [=]() { if (ConfirmationDialog::confirm("Are you sure you want to review the training guide?", this)) { Params().remove("CompletedTrainingVersion"); emit reviewTrainingGuide(); @@ -160,14 +150,16 @@ DevicePanel::DevicePanel(QWidget* parent) : QWidget(parent) { }); } - auto uninstallBtn = new ButtonControl("Uninstall " + getBrand(), "UNINSTALL"); - connect(uninstallBtn, &ButtonControl::released, [=]() { - if (ConfirmationDialog::confirm("Are you sure you want to uninstall?", this)) { - Params().putBool("DoUninstall", true); - } - }); + ButtonControl *regulatoryBtn = nullptr; + if (Hardware::TICI()) { + regulatoryBtn = new ButtonControl("Regulatory", "VIEW", ""); + connect(regulatoryBtn, &ButtonControl::clicked, [=]() { + const std::string txt = util::read_file(ASSET_PATH.toStdString() + "/offroad/fcc.html"); + RichTextDialog::alert(QString::fromStdString(txt), this); + }); + } - for (auto btn : {dcamBtn, resetCalibBtn, retrainingBtn, uninstallBtn}) { + for (auto btn : {dcamBtn, resetCalibBtn, retrainingBtn, regulatoryBtn}) { if (btn) { main_layout->addWidget(horizontal_line()); connect(parent, SIGNAL(offroadTransition(bool)), btn, SLOT(setEnabled(bool))); @@ -180,23 +172,33 @@ DevicePanel::DevicePanel(QWidget* parent) : QWidget(parent) { power_layout->setSpacing(30); QPushButton *reboot_btn = new QPushButton("Reboot"); - reboot_btn->setStyleSheet("height: 120px;border-radius: 15px; background-color: #393939;"); + reboot_btn->setObjectName("reboot_btn"); power_layout->addWidget(reboot_btn); - QObject::connect(reboot_btn, &QPushButton::released, [=]() { + QObject::connect(reboot_btn, &QPushButton::clicked, [=]() { if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) { Hardware::reboot(); } }); QPushButton *poweroff_btn = new QPushButton("Power Off"); - poweroff_btn->setStyleSheet("height: 120px;border-radius: 15px; background-color: #E22C2C;"); + poweroff_btn->setObjectName("poweroff_btn"); power_layout->addWidget(poweroff_btn); - QObject::connect(poweroff_btn, &QPushButton::released, [=]() { + QObject::connect(poweroff_btn, &QPushButton::clicked, [=]() { if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) { Hardware::poweroff(); } }); + setStyleSheet(R"( + QPushButton { + height: 120px; + border-radius: 15px; + } + #reboot_btn { background-color: #393939; } + #reboot_btn:pressed { background-color: #4a4a4a; } + #poweroff_btn { background-color: #E22C2C; } + #poweroff_btn:pressed { background-color: #FF2424; } + )"); main_layout->addLayout(power_layout); } @@ -207,7 +209,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : QWidget(parent) { versionLbl = new LabelControl("Version", "", QString::fromStdString(params.get("ReleaseNotes")).trimmed()); lastUpdateLbl = new LabelControl("Last Update Check", "", "The last time openpilot successfully checked for an update. The updater only runs while the car is off."); updateBtn = new ButtonControl("Check for Update", ""); - connect(updateBtn, &ButtonControl::released, [=]() { + connect(updateBtn, &ButtonControl::clicked, [=]() { if (params.getBool("IsOffroad")) { const QString paramsPath = QString::fromStdString(params.getParamsPath()); fs_watch->addPath(paramsPath + "/d/LastUpdateTime"); @@ -222,12 +224,17 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : QWidget(parent) { QWidget *widgets[] = {versionLbl, lastUpdateLbl, updateBtn, gitBranchLbl, gitCommitLbl, osVersionLbl}; for (int i = 0; i < std::size(widgets); ++i) { main_layout->addWidget(widgets[i]); - if (i < std::size(widgets) - 1) { - main_layout->addWidget(horizontal_line()); - } + main_layout->addWidget(horizontal_line()); } - setStyleSheet(R"(QLabel {font-size: 50px;})"); + auto uninstallBtn = new ButtonControl("Uninstall " + getBrand(), "UNINSTALL"); + connect(uninstallBtn, &ButtonControl::clicked, [=]() { + if (ConfirmationDialog::confirm("Are you sure you want to uninstall?", this)) { + Params().putBool("DoUninstall", true); + } + }); + connect(parent, SIGNAL(offroadTransition(bool)), uninstallBtn, SLOT(setEnabled(bool))); + main_layout->addWidget(uninstallBtn); fs_watch = new QFileSystemWatcher(this); QObject::connect(fs_watch, &QFileSystemWatcher::fileChanged, [=](const QString path) { @@ -270,12 +277,12 @@ QWidget * network_panel(QWidget * parent) { // wifi + tethering buttons auto wifiBtn = new ButtonControl("WiFi Settings", "OPEN"); - QObject::connect(wifiBtn, &ButtonControl::released, [=]() { HardwareEon::launch_wifi(); }); + QObject::connect(wifiBtn, &ButtonControl::clicked, [=]() { HardwareEon::launch_wifi(); }); layout->addWidget(wifiBtn); layout->addWidget(horizontal_line()); auto tetheringBtn = new ButtonControl("Tethering Settings", "OPEN"); - QObject::connect(tetheringBtn, &ButtonControl::released, [=]() { HardwareEon::launch_tethering(); }); + QObject::connect(tetheringBtn, &ButtonControl::clicked, [=]() { HardwareEon::launch_tethering(); }); layout->addWidget(tetheringBtn); layout->addWidget(horizontal_line()); @@ -309,18 +316,25 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { )"); // close button - QPushButton *close_btn = new QPushButton("X"); + QPushButton *close_btn = new QPushButton("×"); close_btn->setStyleSheet(R"( - font-size: 90px; - font-weight: bold; - border 1px grey solid; - border-radius: 100px; - background-color: #292929; + QPushButton { + font-size: 140px; + padding-bottom: 20px; + font-weight: bold; + border 1px grey solid; + border-radius: 100px; + background-color: #292929; + font-weight: 400; + } + QPushButton:pressed { + background-color: #3B3B3B; + } )"); close_btn->setFixedSize(200, 200); sidebar_layout->addSpacing(45); sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter); - QObject::connect(close_btn, &QPushButton::released, this, &SettingsWindow::closeSettings); + QObject::connect(close_btn, &QPushButton::clicked, this, &SettingsWindow::closeSettings); // setup panels DevicePanel *device = new DevicePanel(this); @@ -335,12 +349,11 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { }; #ifdef ENABLE_MAPS - if (!Params().get("MapboxToken").empty()) { - auto map_panel = new MapPanel(this); - panels.push_back({"Navigation", map_panel}); - QObject::connect(map_panel, &MapPanel::closeSettings, this, &SettingsWindow::closeSettings); - } + auto map_panel = new MapPanel(this); + panels.push_back({"Navigation", map_panel}); + QObject::connect(map_panel, &MapPanel::closeSettings, this, &SettingsWindow::closeSettings); #endif + const int padding = panels.size() > 3 ? 25 : 35; nav_btns = new QButtonGroup(); @@ -361,17 +374,21 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { QPushButton:checked { color: white; } + QPushButton:pressed { + color: #ADADAD; + } )").arg(padding)); nav_btns->addButton(btn); sidebar_layout->addWidget(btn, 0, Qt::AlignRight); - panel->setContentsMargins(50, 25, 50, 25); + const int lr_margin = name != "Network" ? 50 : 0; // Network panel handles its own margins + panel->setContentsMargins(lr_margin, 25, lr_margin, 25); ScrollView *panel_frame = new ScrollView(panel, this); panel_widget->addWidget(panel_frame); - QObject::connect(btn, &QPushButton::released, [=, w = panel_frame]() { + QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() { btn->setChecked(true); panel_widget->setCurrentWidget(w); }); diff --git a/selfdrive/ui/qt/offroad/wifiManager.cc b/selfdrive/ui/qt/offroad/wifiManager.cc index 06a51990b..f0ca79a06 100644 --- a/selfdrive/ui/qt/offroad/wifiManager.cc +++ b/selfdrive/ui/qt/offroad/wifiManager.cc @@ -78,8 +78,7 @@ void WifiManager::refreshNetworks() { if (adapter.isEmpty()) { return; } - seen_networks.clear(); - seen_ssids.clear(); + seenNetworks.clear(); ipv4_address = get_ipv4_address(); QDBusInterface nm(NM_DBUS_SERVICE, adapter, NM_DBUS_INTERFACE_DEVICE_WIRELESS, bus); @@ -87,14 +86,16 @@ void WifiManager::refreshNetworks() { const QDBusReply> &response = nm.call("GetAllAccessPoints"); for (const QDBusObjectPath &path : response.value()) { - QByteArray ssid = get_property(path.path(), "Ssid"); - if (ssid.isEmpty() || seen_ssids.contains(ssid)) { + const QByteArray &ssid = get_property(path.path(), "Ssid"); + unsigned int strength = get_ap_strength(path.path()); + if (ssid.isEmpty() || (seenNetworks.contains(ssid) && + strength <= seenNetworks.value(ssid).strength)) { continue; } - unsigned int strength = get_ap_strength(path.path()); SecurityType security = getSecurityType(path.path()); ConnectedType ctype; - if (path.path() != activeAp) { + QString activeSsid = (activeAp != "" && activeAp != "/") ? get_property(activeAp, "Ssid") : ""; + if (ssid != activeSsid) { ctype = ConnectedType::DISCONNECTED; } else { if (ssid == connecting_to_network) { @@ -103,11 +104,9 @@ void WifiManager::refreshNetworks() { ctype = ConnectedType::CONNECTED; } } - Network network = {path.path(), ssid, strength, ctype, security}; - seen_ssids.push_back(ssid); - seen_networks.push_back(network); + Network network = {ssid, strength, ctype, security}; + seenNetworks[ssid] = network; } - std::sort(seen_networks.begin(), seen_networks.end(), compare_by_strength); } QString WifiManager::get_ipv4_address() { @@ -116,8 +115,7 @@ QString WifiManager::get_ipv4_address() { } QVector conns = get_active_connections(); for (auto &p : conns) { - QString active_connection = p.path(); - QDBusInterface nm(NM_DBUS_SERVICE, active_connection, NM_DBUS_INTERFACE_PROPERTIES, bus); + QDBusInterface nm(NM_DBUS_SERVICE, p.path(), NM_DBUS_INTERFACE_PROPERTIES, bus); nm.setTimeout(DBUS_TIMEOUT); QDBusObjectPath pth = get_response(nm.call("Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Ip4Config")); @@ -195,6 +193,7 @@ void WifiManager::connect(const QByteArray &ssid, const QString &username, const } connection["ipv4"]["method"] = "auto"; + connection["ipv4"]["dns-priority"] = 600; connection["ipv6"]["method"] = "ignore"; QDBusInterface nm_settings(NM_DBUS_SERVICE, NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, bus); @@ -305,7 +304,7 @@ QString WifiManager::getAdapter() { void WifiManager::stateChange(unsigned int new_state, unsigned int previous_state, unsigned int change_reason) { raw_adapter_state = new_state; if (new_state == NM_DEVICE_STATE_NEED_AUTH && change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT) { - knownConnections.remove(getConnectionPath(connecting_to_network)); + forgetConnection(connecting_to_network); emit wrongPassword(connecting_to_network); } else if (new_state == NM_DEVICE_STATE_ACTIVATED) { connecting_to_network = ""; @@ -390,6 +389,33 @@ void WifiManager::activateWifiConnection(const QString &ssid) { } } +// function matches tici/hardware.py +NetworkType WifiManager::currentNetworkType() { + QDBusInterface nm(NM_DBUS_SERVICE, NM_DBUS_PATH, NM_DBUS_INTERFACE_PROPERTIES, bus); + nm.setTimeout(DBUS_TIMEOUT); + const QDBusObjectPath &path = get_response(nm.call("Get", NM_DBUS_INTERFACE, "PrimaryConnection")); + + QDBusInterface nm2(NM_DBUS_SERVICE, path.path(), NM_DBUS_INTERFACE_PROPERTIES, bus); + nm.setTimeout(DBUS_TIMEOUT); + const QString &type = get_response(nm2.call("Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Type")); + + if (type == "802-3-ethernet") { + return NetworkType::ETHERNET; + } else if (type == "802-11-wireless" && !isTetheringEnabled()) { + return NetworkType::WIFI; + } else { + for (const QDBusObjectPath &path : get_active_connections()) { + QDBusInterface nm3(NM_DBUS_SERVICE, path.path(), NM_DBUS_INTERFACE_PROPERTIES, bus); + nm3.setTimeout(DBUS_TIMEOUT); + const QString &type = get_response(nm3.call("Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Type")); + if (type == "gsm") { + return NetworkType::CELL; + } + } + } + return NetworkType::NONE; +} + // Functions for tethering void WifiManager::addTetheringConnection() { Connection connection; diff --git a/selfdrive/ui/qt/offroad/wifiManager.h b/selfdrive/ui/qt/offroad/wifiManager.h index e6741ec8f..191a4c300 100644 --- a/selfdrive/ui/qt/offroad/wifiManager.h +++ b/selfdrive/ui/qt/offroad/wifiManager.h @@ -10,30 +10,37 @@ enum class SecurityType { WPA, UNSUPPORTED }; -enum class ConnectedType{ +enum class ConnectedType { DISCONNECTED, CONNECTING, CONNECTED }; +enum class NetworkType { + NONE, + WIFI, + CELL, + ETHERNET +}; typedef QMap> Connection; typedef QVector> IpConfig; struct Network { - QString path; QByteArray ssid; unsigned int strength; ConnectedType connected; SecurityType security_type; }; +bool compare_by_strength(const Network &a, const Network &b); class WifiManager : public QWidget { Q_OBJECT + public: explicit WifiManager(QWidget* parent); void requestScan(); - QVector seen_networks; + QMap seenNetworks; QMap knownConnections; QString ipv4_address; @@ -41,6 +48,7 @@ public: void forgetConnection(const QString &ssid); bool isKnownConnection(const QString &ssid); void activateWifiConnection(const QString &ssid); + NetworkType currentNetworkType(); void connect(const Network &ssid); void connect(const Network &ssid, const QString &password); @@ -55,7 +63,6 @@ public: QString getTetheringPassword(); private: - QVector seen_ssids; QString adapter; // Path to network manager wifi-device QDBusConnection bus = QDBusConnection::systemBus(); unsigned int raw_adapter_state; // Connection status https://developer.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NMDeviceState diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 296f063e8..ad0722058 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -7,18 +7,20 @@ #include "selfdrive/common/timing.h" #include "selfdrive/ui/paint.h" #include "selfdrive/ui/qt/util.h" - #ifdef ENABLE_MAPS #include "selfdrive/ui/qt/maps/map.h" #endif OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { - main_layout = new QStackedLayout(this); - main_layout->setStackingMode(QStackedLayout::StackAll); + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setMargin(bdr_s); + QStackedLayout *stacked_layout = new QStackedLayout; + stacked_layout->setStackingMode(QStackedLayout::StackAll); + main_layout->addLayout(stacked_layout); // old UI on bottom nvg = new NvgWindow(this); - QObject::connect(this, &OnroadWindow::update, nvg, &NvgWindow::update); + QObject::connect(this, &OnroadWindow::updateStateSignal, nvg, &NvgWindow::updateState); QWidget * split_wrapper = new QWidget; split = new QHBoxLayout(split_wrapper); @@ -26,21 +28,46 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { split->setSpacing(0); split->addWidget(nvg); - main_layout->addWidget(split_wrapper); + stacked_layout->addWidget(split_wrapper); alerts = new OnroadAlerts(this); alerts->setAttribute(Qt::WA_TransparentForMouseEvents, true); - QObject::connect(this, &OnroadWindow::update, alerts, &OnroadAlerts::updateState); - QObject::connect(this, &OnroadWindow::offroadTransitionSignal, alerts, &OnroadAlerts::offroadTransition); - QObject::connect(this, &OnroadWindow::offroadTransitionSignal, this, &OnroadWindow::offroadTransition); - main_layout->addWidget(alerts); + stacked_layout->addWidget(alerts); // setup stacking order alerts->raise(); setAttribute(Qt::WA_OpaquePaintEvent); + QObject::connect(this, &OnroadWindow::updateStateSignal, this, &OnroadWindow::updateState); + QObject::connect(this, &OnroadWindow::offroadTransitionSignal, this, &OnroadWindow::offroadTransition); } +void OnroadWindow::updateState(const UIState &s) { + SubMaster &sm = *(s.sm); + QColor bgColor = bg_colors[s.status]; + if (sm.updated("controlsState")) { + const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState(); + alerts->updateAlert({QString::fromStdString(cs.getAlertText1()), + QString::fromStdString(cs.getAlertText2()), + QString::fromStdString(cs.getAlertType()), + cs.getAlertSize(), cs.getAlertSound()}, bgColor); + } else if ((sm.frame - s.scene.started_frame) > 5 * UI_FREQ) { + // Handle controls timeout + if (sm.rcv_frame("controlsState") < s.scene.started_frame) { + // car is started, but controlsState hasn't been seen at all + alerts->updateAlert(CONTROLS_WAITING_ALERT, bgColor); + } else if ((nanos_since_boot() - sm.rcv_time("controlsState")) / 1e9 > CONTROLS_TIMEOUT) { + // car is started, but controls is lagging or died + bgColor = bg_colors[STATUS_ALERT]; + alerts->updateAlert(CONTROLS_UNRESPONSIVE_ALERT, bgColor); + } + } + if (bg != bgColor) { + // repaint border + bg = bgColor; + update(); + } +} void OnroadWindow::offroadTransition(bool offroad) { #ifdef ENABLE_MAPS @@ -55,112 +82,34 @@ void OnroadWindow::offroadTransition(bool offroad) { settings.setAccessToken(token.trimmed()); MapWindow * m = new MapWindow(settings); + m->setFixedWidth(width() / 2 - bdr_s); QObject::connect(this, &OnroadWindow::offroadTransitionSignal, m, &MapWindow::offroadTransition); - split->addWidget(m); - + split->addWidget(m, 0, Qt::AlignRight); map = m; } - } #endif + + alerts->updateAlert({}, bg); +} + +void OnroadWindow::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.fillRect(rect(), QColor(bg.red(), bg.green(), bg.blue(), 255)); } // ***** onroad widgets ***** -OnroadAlerts::OnroadAlerts(QWidget *parent) : QWidget(parent) { - std::tuple sound_list[] = { - {AudibleAlert::CHIME_DISENGAGE, "../assets/sounds/disengaged.wav", false}, - {AudibleAlert::CHIME_ENGAGE, "../assets/sounds/engaged.wav", false}, - {AudibleAlert::CHIME_WARNING1, "../assets/sounds/warning_1.wav", false}, - {AudibleAlert::CHIME_WARNING2, "../assets/sounds/warning_2.wav", false}, - {AudibleAlert::CHIME_WARNING2_REPEAT, "../assets/sounds/warning_2.wav", true}, - {AudibleAlert::CHIME_WARNING_REPEAT, "../assets/sounds/warning_repeat.wav", true}, - {AudibleAlert::CHIME_ERROR, "../assets/sounds/error.wav", false}, - {AudibleAlert::CHIME_PROMPT, "../assets/sounds/error.wav", false}}; - - for (auto &[alert, fn, loops] : sound_list) { - sounds[alert].first.setSource(QUrl::fromLocalFile(fn)); - sounds[alert].second = loops ? QSoundEffect::Infinite : 0; - } -} - -void OnroadAlerts::updateState(const UIState &s) { - SubMaster &sm = *(s.sm); - if (sm.updated("carState")) { - // scale volume with speed - volume = util::map_val(sm["carState"].getCarState().getVEgo(), 0.f, 20.f, - Hardware::MIN_VOLUME, Hardware::MAX_VOLUME); - } - if (sm["deviceState"].getDeviceState().getStarted()) { - if (sm.updated("controlsState")) { - const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState(); - updateAlert(QString::fromStdString(cs.getAlertText1()), QString::fromStdString(cs.getAlertText2()), - cs.getAlertBlinkingRate(), cs.getAlertType(), cs.getAlertSize(), cs.getAlertSound()); - } else if ((sm.frame - s.scene.started_frame) > 10 * UI_FREQ) { - // Handle controls timeout - if (sm.rcv_frame("controlsState") < s.scene.started_frame) { - // car is started, but controlsState hasn't been seen at all - updateAlert("openpilot Unavailable", "Waiting for controls to start", 0, - "controlsWaiting", cereal::ControlsState::AlertSize::MID, AudibleAlert::NONE); - } else if ((sm.frame - sm.rcv_frame("controlsState")) > 5 * UI_FREQ) { - // car is started, but controls is lagging or died - updateAlert("TAKE CONTROL IMMEDIATELY", "Controls Unresponsive", 0, - "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, AudibleAlert::CHIME_WARNING_REPEAT); - - // TODO: clean this up once Qt handles the border - QUIState::ui_state.status = STATUS_ALERT; - } - } - } - - // TODO: add blinking back if performant - //float alpha = 0.375 * cos((millis_since_boot() / 1000) * 2 * M_PI * blinking_rate) + 0.625; - bg = bg_colors[s.status]; -} - -void OnroadAlerts::offroadTransition(bool offroad) { - updateAlert("", "", 0, "", cereal::ControlsState::AlertSize::NONE, AudibleAlert::NONE); -} - -void OnroadAlerts::updateAlert(const QString &t1, const QString &t2, float blink_rate, - const std::string &type, cereal::ControlsState::AlertSize size, AudibleAlert sound) { - if (alert_type.compare(type) == 0 && text1.compare(t1) == 0 && text2.compare(t2) == 0) { - return; - } - - stopSounds(); - if (sound != AudibleAlert::NONE) { - playSound(sound); - } - - text1 = t1; - text2 = t2; - alert_type = type; - alert_size = size; - blinking_rate = blink_rate; - - update(); -} - -void OnroadAlerts::playSound(AudibleAlert alert) { - auto &[sound, loops] = sounds[alert]; - sound.setLoopCount(loops); - sound.setVolume(volume); - sound.play(); -} - -void OnroadAlerts::stopSounds() { - for (auto &kv : sounds) { - // Only stop repeating sounds - auto &[sound, loops] = kv.second; - if (sound.loopsRemaining() == QSoundEffect::Infinite) { - sound.stop(); - } +void OnroadAlerts::updateAlert(const Alert &a, const QColor &color) { + if (!alert.equal(a) || color != bg) { + alert = a; + bg = color; + update(); } } void OnroadAlerts::paintEvent(QPaintEvent *event) { - if (alert_size == cereal::ControlsState::AlertSize::NONE) { + if (alert.size == cereal::ControlsState::AlertSize::NONE) { return; } static std::map alert_sizes = { @@ -168,7 +117,7 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { {cereal::ControlsState::AlertSize::MID, 420}, {cereal::ControlsState::AlertSize::FULL, height()}, }; - int h = alert_sizes[alert_size]; + int h = alert_sizes[alert.size]; QRect r = QRect(0, height() - h, width(), h); QPainter p(this); @@ -189,27 +138,24 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { p.fillRect(r, g); p.setCompositionMode(QPainter::CompositionMode_SourceOver); - // remove bottom border - r = QRect(0, height() - h, width(), h - 30); - // text const QPoint c = r.center(); p.setPen(QColor(0xff, 0xff, 0xff)); p.setRenderHint(QPainter::TextAntialiasing); - if (alert_size == cereal::ControlsState::AlertSize::SMALL) { + if (alert.size == cereal::ControlsState::AlertSize::SMALL) { configFont(p, "Open Sans", 74, "SemiBold"); - p.drawText(r, Qt::AlignCenter, text1); - } else if (alert_size == cereal::ControlsState::AlertSize::MID) { + p.drawText(r, Qt::AlignCenter, alert.text1); + } else if (alert.size == cereal::ControlsState::AlertSize::MID) { configFont(p, "Open Sans", 88, "Bold"); - p.drawText(QRect(0, c.y() - 125, width(), 150), Qt::AlignHCenter | Qt::AlignTop, text1); + p.drawText(QRect(0, c.y() - 125, width(), 150), Qt::AlignHCenter | Qt::AlignTop, alert.text1); configFont(p, "Open Sans", 66, "Regular"); - p.drawText(QRect(0, c.y() + 21, width(), 90), Qt::AlignHCenter, text2); - } else if (alert_size == cereal::ControlsState::AlertSize::FULL) { - bool l = text1.length() > 15; + p.drawText(QRect(0, c.y() + 21, width(), 90), Qt::AlignHCenter, alert.text2); + } else if (alert.size == cereal::ControlsState::AlertSize::FULL) { + bool l = alert.text1.length() > 15; configFont(p, "Open Sans", l ? 132 : 177, "Bold"); - p.drawText(QRect(0, r.y() + (l ? 240 : 270), width(), 600), Qt::AlignHCenter | Qt::TextWordWrap, text1); + p.drawText(QRect(0, r.y() + (l ? 240 : 270), width(), 600), Qt::AlignHCenter | Qt::TextWordWrap, alert.text1); configFont(p, "Open Sans", 88, "Regular"); - p.drawText(QRect(0, r.height() - (l ? 361 : 420), width(), 300), Qt::AlignHCenter | Qt::TextWordWrap, text2); + p.drawText(QRect(0, r.height() - (l ? 361 : 420), width(), 300), Qt::AlignHCenter | Qt::TextWordWrap, alert.text2); } } @@ -234,11 +180,14 @@ void NvgWindow::initializeGL() { prev_draw_t = millis_since_boot(); } -void NvgWindow::update(const UIState &s) { +void NvgWindow::updateState(const UIState &s) { // Connecting to visionIPC requires opengl to be current if (s.vipc_client->connected) { makeCurrent(); } + if (isVisible() != s.vipc_client->connected) { + setVisible(s.vipc_client->connected); + } repaint(); } diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index ae2cfda8f..4d9881fd4 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -1,19 +1,14 @@ #pragma once -#include - #include #include -#include #include #include #include "cereal/gen/cpp/log.capnp.h" -#include "selfdrive/hardware/hw.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/ui.h" -typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert; // ***** onroad widgets ***** @@ -21,28 +16,15 @@ class OnroadAlerts : public QWidget { Q_OBJECT public: - OnroadAlerts(QWidget *parent = 0); + OnroadAlerts(QWidget *parent = 0) : QWidget(parent) {}; + void updateAlert(const Alert &a, const QColor &color); protected: void paintEvent(QPaintEvent*) override; private: - void stopSounds(); - void playSound(AudibleAlert alert); - void updateAlert(const QString &t1, const QString &t2, float blink_rate, - const std::string &type, cereal::ControlsState::AlertSize size, AudibleAlert sound); - QColor bg; - float volume = Hardware::MIN_VOLUME; - std::map> sounds; - float blinking_rate = 0; - QString text1, text2; - std::string alert_type; - cereal::ControlsState::AlertSize alert_size; - -public slots: - void updateState(const UIState &s); - void offroadTransition(bool offroad); + Alert alert = {}; }; // container window for the NVG UI @@ -63,7 +45,7 @@ private: double prev_draw_t = 0; public slots: - void update(const UIState &s); + void updateState(const UIState &s); }; // container for all onroad widgets @@ -75,15 +57,18 @@ public: QWidget *map = nullptr; private: + void paintEvent(QPaintEvent *event); + OnroadAlerts *alerts; NvgWindow *nvg; - QStackedLayout *main_layout; + QColor bg = bg_colors[STATUS_DISENGAGED]; QHBoxLayout* split; signals: - void update(const UIState &s); + void updateStateSignal(const UIState &s); void offroadTransitionSignal(bool offroad); private slots: void offroadTransition(bool offroad); + void updateState(const UIState &s); }; diff --git a/selfdrive/ui/qt/qt_window.h b/selfdrive/ui/qt/qt_window.h index 307de6b29..d17b01462 100644 --- a/selfdrive/ui/qt/qt_window.h +++ b/selfdrive/ui/qt/qt_window.h @@ -13,6 +13,12 @@ #include "selfdrive/hardware/hw.h" +#ifdef USE_QRC +const QString ASSET_PATH = ":/"; +#else +const QString ASSET_PATH = "../assets/"; +#endif + const int vwp_w = (Hardware::TICI() || (getenv("WIDE_UI") != NULL)) ? 2160 : 1920; const int vwp_h = 1080; diff --git a/selfdrive/ui/qt/request_repeater.cc b/selfdrive/ui/qt/request_repeater.cc index 7c11c0f8a..c11698d6f 100644 --- a/selfdrive/ui/qt/request_repeater.cc +++ b/selfdrive/ui/qt/request_repeater.cc @@ -1,11 +1,11 @@ #include "selfdrive/ui/qt/request_repeater.h" RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey, - int period, bool while_onroad) : HttpRequest(parent, requestURL) { + int period, bool while_onroad) : HttpRequest(parent) { timer = new QTimer(this); timer->setTimerType(Qt::VeryCoarseTimer); QObject::connect(timer, &QTimer::timeout, [=]() { - if ((!QUIState::ui_state.scene.started || while_onroad) && QUIState::ui_state.awake && reply == NULL) { + if ((!QUIState::ui_state.scene.started || while_onroad) && QUIState::ui_state.awake && !active()) { sendRequest(requestURL); } }); @@ -15,7 +15,7 @@ RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, con if (!cacheKey.isEmpty()) { prevResp = QString::fromStdString(params.get(cacheKey.toStdString())); if (!prevResp.isEmpty()) { - QTimer::singleShot(0, [=]() { emit receivedResponse(prevResp); }); + QTimer::singleShot(500, [=]() { emit receivedResponse(prevResp); }); } QObject::connect(this, &HttpRequest::receivedResponse, [=](const QString &resp) { if (resp != prevResp) { @@ -23,11 +23,5 @@ RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, con prevResp = resp; } }); - QObject::connect(this, &HttpRequest::failedResponse, [=](const QString &err) { - if (!prevResp.isEmpty()) { - params.remove(cacheKey.toStdString()); - prevResp = ""; - } - }); } } diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index ee315c142..da226b412 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -46,7 +46,7 @@ Sidebar::Sidebar(QWidget *parent) : QFrame(parent) { setStyleSheet("background-color: rgb(57, 57, 57);"); } -void Sidebar::mousePressEvent(QMouseEvent *event) { +void Sidebar::mouseReleaseEvent(QMouseEvent *event) { if (settings_btn.contains(event->pos())) { emit openSettings(); } @@ -62,11 +62,16 @@ void Sidebar::updateState(const UIState &s) { auto last_ping = deviceState.getLastAthenaPingTime(); if (last_ping == 0) { - setProperty("connectStr", "OFFLINE"); - setProperty("connectStatus", warning_color); + if (params.getBool("PrimeRedirected")) { + setProperty("connectStr", "NO\nPRIME"); + setProperty("connectStatus", danger_color); + } else { + setProperty("connectStr", "CONNECT\nOFFLINE"); + setProperty("connectStatus", warning_color); + } } else { bool online = nanos_since_boot() - last_ping < 80e9; - setProperty("connectStr", online ? "ONLINE" : "ERROR"); + setProperty("connectStr", (online ? "CONNECT\nONLINE" : "CONNECT\nERROR")); setProperty("connectStatus", online ? good_color : danger_color); } @@ -121,5 +126,5 @@ void Sidebar::paintEvent(QPaintEvent *event) { // metrics drawMetric(p, "TEMP", QString("%1°C").arg(temp_val), temp_status, 338); drawMetric(p, panda_str, "", panda_status, 518); - drawMetric(p, "CONNECT\n" + connect_str, "", connect_status, 676); + drawMetric(p, connect_str, "", connect_status, 676); } diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 9c955bb2b..7cd04519a 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -3,6 +3,7 @@ #include #include +#include "selfdrive/common/params.h" #include "selfdrive/ui/ui.h" class Sidebar : public QFrame { @@ -28,7 +29,7 @@ public slots: protected: void paintEvent(QPaintEvent *event) override; - void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; private: void drawMetric(QPainter &p, const QString &label, const QString &val, QColor c, int y); @@ -49,6 +50,7 @@ private: const QColor warning_color = QColor(218, 202, 37); const QColor danger_color = QColor(201, 34, 49); + Params params; QString connect_str = "OFFLINE"; QColor connect_status = warning_color; QString panda_str = "NO\nPANDA"; diff --git a/selfdrive/ui/qt/spinner.cc b/selfdrive/ui/qt/spinner.cc index 0dcae6d97..5323d182a 100644 --- a/selfdrive/ui/qt/spinner.cc +++ b/selfdrive/ui/qt/spinner.cc @@ -54,7 +54,9 @@ Spinner::Spinner(QWidget *parent) : QWidget(parent) { main_layout->addWidget(new TrackWidget(this), 0, 0, Qt::AlignHCenter | Qt::AlignVCenter); text = new QLabel(); + text->setWordWrap(true); text->setVisible(false); + text->setAlignment(Qt::AlignCenter); main_layout->addWidget(text, 1, 0, Qt::AlignHCenter); progress_bar = new QProgressBar(); diff --git a/selfdrive/ui/qt/spinner_aarch64 b/selfdrive/ui/qt/spinner_aarch64 index a1fa69566..77c13d81e 100755 Binary files a/selfdrive/ui/qt/spinner_aarch64 and b/selfdrive/ui/qt/spinner_aarch64 differ diff --git a/selfdrive/ui/qt/spinner_larch64 b/selfdrive/ui/qt/spinner_larch64 new file mode 100755 index 000000000..fa39f0ed5 Binary files /dev/null and b/selfdrive/ui/qt/spinner_larch64 differ diff --git a/selfdrive/ui/qt/text.cc b/selfdrive/ui/qt/text.cc index 2d4ff8826..cb69cc082 100644 --- a/selfdrive/ui/qt/text.cc +++ b/selfdrive/ui/qt/text.cc @@ -34,12 +34,12 @@ int main(int argc, char *argv[]) { QPushButton *btn = new QPushButton(); #ifdef __aarch64__ btn->setText("Reboot"); - QObject::connect(btn, &QPushButton::released, [=]() { + QObject::connect(btn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); #else btn->setText("Exit"); - QObject::connect(btn, &QPushButton::released, &a, &QApplication::quit); + QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); #endif main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); diff --git a/selfdrive/ui/qt/text_larch64 b/selfdrive/ui/qt/text_larch64 new file mode 100755 index 000000000..bdfa10daf Binary files /dev/null and b/selfdrive/ui/qt/text_larch64 differ diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 532f8e1d4..2087977d0 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -78,12 +78,15 @@ ButtonControl::ButtonControl(const QString &title, const QString &text, const QS color: #E4E4E4; background-color: #393939; } + QPushButton:pressed { + background-color: #4a4a4a; + } QPushButton:disabled { color: #33E4E4E4; } )"); btn.setFixedSize(250, 100); - QObject::connect(&btn, &QPushButton::released, this, &ButtonControl::released); + QObject::connect(&btn, &QPushButton::clicked, this, &ButtonControl::clicked); hlayout->addWidget(&btn); } diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 2e8360f0c..f2192396b 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -75,7 +75,7 @@ public: inline QString text() const { return btn.text(); } signals: - void released(); + void clicked(); public slots: void setEnabled(bool enabled) { btn.setEnabled(enabled); }; @@ -112,14 +112,19 @@ class ParamControl : public ToggleControl { public: ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { - if (params.getBool(param.toStdString().c_str())) { - toggle.togglePosition(); - } + key = param.toStdString(); QObject::connect(this, &ToggleControl::toggleFlipped, [=](bool state) { - params.putBool(param.toStdString().c_str(), state); + params.putBool(key, state); }); } + void showEvent(QShowEvent *event) override { + if (params.getBool(key) != toggle.on) { + toggle.togglePosition(); + } + }; + private: + std::string key; Params params; }; diff --git a/selfdrive/ui/qt/widgets/drive_stats.cc b/selfdrive/ui/qt/widgets/drive_stats.cc index 168633610..573e9b044 100644 --- a/selfdrive/ui/qt/widgets/drive_stats.cc +++ b/selfdrive/ui/qt/widgets/drive_stats.cc @@ -10,42 +10,40 @@ const double MILE_TO_KM = 1.60934; -namespace { - -QLabel* numberLabel() { - QLabel* label = new QLabel("0"); - label->setStyleSheet("font-size: 80px; font-weight: 600;"); +static QLabel* newLabel(const QString& text, const QString &type) { + QLabel* label = new QLabel(text); + label->setProperty("type", type); return label; } -QLabel* unitLabel(const QString& name) { - QLabel* label = new QLabel(name); - label->setStyleSheet("font-size: 45px; font-weight: 500;"); - return label; -} - -} // namespace - -DriveStats::DriveStats(QWidget* parent) : QWidget(parent) { +DriveStats::DriveStats(QWidget* parent) : QFrame(parent) { metric_ = Params().getBool("IsMetric"); - QGridLayout* main_layout = new QGridLayout(this); - main_layout->setMargin(0); + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(50, 50, 50, 60); auto add_stats_layouts = [=](const QString &title, StatsLabels& labels) { - int row = main_layout->rowCount(); - main_layout->addWidget(new QLabel(title), row++, 0, 1, 3); + QGridLayout* grid_layout = new QGridLayout; + grid_layout->setVerticalSpacing(10); + grid_layout->setContentsMargins(0, 10, 0, 10); - main_layout->addWidget(labels.routes = numberLabel(), row, 0, Qt::AlignLeft); - main_layout->addWidget(labels.distance = numberLabel(), row, 1, Qt::AlignLeft); - main_layout->addWidget(labels.hours = numberLabel(), row, 2, Qt::AlignLeft); + int row = 0; + grid_layout->addWidget(newLabel(title, "title"), row++, 0, 1, 3); + grid_layout->addItem(new QSpacerItem(0, 50), row++, 0, 1, 1); - main_layout->addWidget(unitLabel("DRIVES"), row + 1, 0, Qt::AlignLeft); - main_layout->addWidget(labels.distance_unit = unitLabel(getDistanceUnit()), row + 1, 1, Qt::AlignLeft); - main_layout->addWidget(unitLabel("HOURS"), row + 1, 2, Qt::AlignLeft); + grid_layout->addWidget(labels.routes = newLabel("0", "number"), row, 0, Qt::AlignLeft); + grid_layout->addWidget(labels.distance = newLabel("0", "number"), row, 1, Qt::AlignLeft); + grid_layout->addWidget(labels.hours = newLabel("0", "number"), row, 2, Qt::AlignLeft); + + grid_layout->addWidget(newLabel("Drives", "unit"), row + 1, 0, Qt::AlignLeft); + grid_layout->addWidget(labels.distance_unit = newLabel(getDistanceUnit(), "unit"), row + 1, 1, Qt::AlignLeft); + grid_layout->addWidget(newLabel("Hours ", "unit"), row + 1, 2, Qt::AlignLeft); + + main_layout->addLayout(grid_layout); }; add_stats_layouts("ALL TIME", all_); + main_layout->addStretch(); add_stats_layouts("PAST WEEK", week_); std::string dongle_id = Params().get("DongleId"); @@ -55,7 +53,16 @@ DriveStats::DriveStats(QWidget* parent) : QWidget(parent) { QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &DriveStats::parseResponse); } - setStyleSheet(R"(QLabel {font-size: 48px; font-weight: 500;})"); + setStyleSheet(R"( + DriveStats { + background-color: #333333; + border-radius: 10px; + } + + QLabel[type="title"] { font-size: 51px; font-weight: 500; } + QLabel[type="number"] { font-size: 78px; font-weight: 500; } + QLabel[type="unit"] { font-size: 51px; font-weight: 300; color: #A0A0A0; } + )"); } void DriveStats::updateStats() { diff --git a/selfdrive/ui/qt/widgets/drive_stats.h b/selfdrive/ui/qt/widgets/drive_stats.h index 2747487ff..40ecbdeaf 100644 --- a/selfdrive/ui/qt/widgets/drive_stats.h +++ b/selfdrive/ui/qt/widgets/drive_stats.h @@ -3,7 +3,7 @@ #include #include -class DriveStats : public QWidget { +class DriveStats : public QFrame { Q_OBJECT public: @@ -12,7 +12,7 @@ public: private: void showEvent(QShowEvent *event) override; void updateStats(); - inline QString getDistanceUnit() const { return metric_ ? "KM" : "MILES"; } + inline QString getDistanceUnit() const { return metric_ ? "KM" : "Miles"; } bool metric_; QJsonDocument stats_; diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index f3ef138ea..8e978ca33 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -2,12 +2,39 @@ #include -#include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/hardware/hw.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/qt_window.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" + QDialogBase::QDialogBase(QWidget *parent) : QDialog(parent) { Q_ASSERT(parent != nullptr); parent->installEventFilter(this); + + setWindowFlags(Qt::Popup); + setStyleSheet(R"( + * { + outline: none; + color: white; + font-family: Inter; + } + QDialogBase { + background-color: black; + } + QPushButton { + height: 160; + font-size: 55px; + font-weight: 400; + border-radius: 10px; + color: white; + background-color: #333333; + } + QPushButton:pressed { + background-color: #444444; + } + + )"); } bool QDialogBase::eventFilter(QObject *o, QEvent *e) { @@ -17,62 +44,109 @@ bool QDialogBase::eventFilter(QObject *o, QEvent *e) { return QDialog::eventFilter(o, e); } -InputDialog::InputDialog(const QString &prompt_text, QWidget *parent) : QDialogBase(parent) { +int QDialogBase::exec() { + setMainWindow(this); + return QDialog::exec(); +} + +InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &subtitle, bool secret) : QDialogBase(parent) { main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(50, 50, 50, 50); - main_layout->setSpacing(20); + main_layout->setContentsMargins(50, 55, 50, 50); + main_layout->setSpacing(0); // build header QHBoxLayout *header_layout = new QHBoxLayout(); - label = new QLabel(prompt_text, this); - label->setStyleSheet(R"(font-size: 75px; font-weight: 500;)"); - header_layout->addWidget(label, 1, Qt::AlignLeft); + QVBoxLayout *vlayout = new QVBoxLayout; + header_layout->addLayout(vlayout); + label = new QLabel(title, this); + label->setStyleSheet("font-size: 90px; font-weight: bold;"); + vlayout->addWidget(label, 1, Qt::AlignTop | Qt::AlignLeft); + + if (!subtitle.isEmpty()) { + sublabel = new QLabel(subtitle, this); + sublabel->setStyleSheet("font-size: 55px; font-weight: light; color: #BDBDBD;"); + vlayout->addWidget(sublabel, 1, Qt::AlignTop | Qt::AlignLeft); + } QPushButton* cancel_btn = new QPushButton("Cancel"); + cancel_btn->setFixedSize(386, 125); cancel_btn->setStyleSheet(R"( - padding: 30px; - padding-right: 45px; - padding-left: 45px; - border-radius: 7px; - font-size: 45px; + font-size: 48px; + border-radius: 10px; + color: #E4E4E4; background-color: #444444; )"); header_layout->addWidget(cancel_btn, 0, Qt::AlignRight); - QObject::connect(cancel_btn, &QPushButton::released, this, &InputDialog::reject); - QObject::connect(cancel_btn, &QPushButton::released, this, &InputDialog::cancel); + QObject::connect(cancel_btn, &QPushButton::clicked, this, &InputDialog::reject); + QObject::connect(cancel_btn, &QPushButton::clicked, this, &InputDialog::cancel); main_layout->addLayout(header_layout); // text box - main_layout->addSpacing(20); - line = new QLineEdit(); - line->setStyleSheet(R"( - border: none; - background-color: #444444; - font-size: 80px; - font-weight: 500; - padding: 10px; - )"); - main_layout->addWidget(line, 1, Qt::AlignTop); + main_layout->addStretch(2); - k = new Keyboard(this); - QObject::connect(k, &Keyboard::emitButton, this, &InputDialog::handleInput); - main_layout->addWidget(k, 2, Qt::AlignBottom); + QWidget *textbox_widget = new QWidget; + textbox_widget->setObjectName("textbox"); + QHBoxLayout *textbox_layout = new QHBoxLayout(textbox_widget); + textbox_layout->setContentsMargins(50, 0, 50, 0); - setStyleSheet(R"( + textbox_widget->setStyleSheet(R"( + #textbox { + margin-left: 50px; + margin-right: 50px; + border-radius: 0; + border-bottom: 3px solid #BDBDBD; + } * { - outline: none; - color: white; - font-family: Inter; - background-color: black; + border: none; + font-size: 80px; + font-weight: light; + background-color: transparent; } )"); + line = new QLineEdit(); + line->setStyleSheet("lineedit-password-character: 8226; lineedit-password-mask-delay: 1500;"); + textbox_layout->addWidget(line, 1); + + if (secret) { + eye_btn = new QPushButton(); + eye_btn->setCheckable(true); + eye_btn->setFixedSize(150, 120); + QObject::connect(eye_btn, &QPushButton::toggled, [=](bool checked) { + if (checked) { + eye_btn->setIcon(QIcon(ASSET_PATH + "img_eye_closed.svg")); + eye_btn->setIconSize(QSize(81, 54)); + line->setEchoMode(QLineEdit::Password); + } else { + eye_btn->setIcon(QIcon(ASSET_PATH + "img_eye_open.svg")); + eye_btn->setIconSize(QSize(81, 44)); + line->setEchoMode(QLineEdit::Normal); + } + }); + eye_btn->setChecked(true); + textbox_layout->addWidget(eye_btn); + } + + main_layout->addWidget(textbox_widget, 0, Qt::AlignBottom); + main_layout->addSpacing(25); + + k = new Keyboard(this); + QObject::connect(k, &Keyboard::emitEnter, this, &InputDialog::handleEnter); + QObject::connect(k, &Keyboard::emitBackspace, this, [=]() { + line->backspace(); + }); + QObject::connect(k, &Keyboard::emitKey, this, [=](const QString &key) { + line->insert(key.left(1)); + }); + + main_layout->addWidget(k, 2, Qt::AlignBottom); } -QString InputDialog::getText(const QString &prompt, QWidget *parent, int minLength, const QString &defaultText) { - InputDialog d = InputDialog(prompt, parent); +QString InputDialog::getText(const QString &prompt, QWidget *parent, const QString &subtitle, + bool secret, int minLength, const QString &defaultText) { + InputDialog d = InputDialog(prompt, parent, subtitle, secret); d.line->setText(defaultText); d.setMinLength(minLength); const int ret = d.exec(); @@ -83,27 +157,16 @@ QString InputDialog::text() { return line->text(); } -int InputDialog::exec() { - setMainWindow(this); - return QDialog::exec(); -} - void InputDialog::show() { setMainWindow(this); } -void InputDialog::handleInput(const QString &s) { - if (!QString::compare(s,"⌫")) { - line->backspace(); - } else if (!QString::compare(s,"⏎")) { - if (line->text().length() >= minLength) { - done(QDialog::Accepted); - emitText(line->text()); - } else { - setMessage("Need at least "+QString::number(minLength)+" characters!", false); - } +void InputDialog::handleEnter() { + if (line->text().length() >= minLength) { + done(QDialog::Accepted); + emitText(line->text()); } else { - line->insert(s.left(1)); + setMessage("Need at least "+QString::number(minLength)+" characters!", false); } } @@ -118,51 +181,41 @@ void InputDialog::setMinLength(int length) { minLength = length; } +// ConfirmationDialog + ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, const QString &cancel_text, QWidget *parent) : QDialogBase(parent) { - setWindowFlags(Qt::Popup); - main_layout = new QVBoxLayout(this); - main_layout->setMargin(25); + QFrame *container = new QFrame(this); + container->setStyleSheet("QFrame { border-radius: 0; background-color: #ECECEC; }"); + QVBoxLayout *main_layout = new QVBoxLayout(container); + main_layout->setContentsMargins(32, 120, 32, 32); - prompt = new QLabel(prompt_text, this); + QLabel *prompt = new QLabel(prompt_text, this); prompt->setWordWrap(true); prompt->setAlignment(Qt::AlignHCenter); - prompt->setStyleSheet(R"(font-size: 55px; font-weight: 400;)"); + prompt->setStyleSheet("font-size: 70px; font-weight: bold; color: black;"); main_layout->addWidget(prompt, 1, Qt::AlignTop | Qt::AlignHCenter); // cancel + confirm buttons QHBoxLayout *btn_layout = new QHBoxLayout(); - btn_layout->setSpacing(20); - btn_layout->addStretch(1); + btn_layout->setSpacing(30); main_layout->addLayout(btn_layout); if (cancel_text.length()) { QPushButton* cancel_btn = new QPushButton(cancel_text); - btn_layout->addWidget(cancel_btn, 0, Qt::AlignRight); - QObject::connect(cancel_btn, &QPushButton::released, this, &ConfirmationDialog::reject); + btn_layout->addWidget(cancel_btn); + QObject::connect(cancel_btn, &QPushButton::clicked, this, &ConfirmationDialog::reject); } if (confirm_text.length()) { QPushButton* confirm_btn = new QPushButton(confirm_text); - btn_layout->addWidget(confirm_btn, 0, Qt::AlignRight); - QObject::connect(confirm_btn, &QPushButton::released, this, &ConfirmationDialog::accept); + btn_layout->addWidget(confirm_btn); + QObject::connect(confirm_btn, &QPushButton::clicked, this, &ConfirmationDialog::accept); } - setFixedSize(900, 350); - setStyleSheet(R"( - * { - color: black; - background-color: white; - } - QPushButton { - font-size: 40px; - padding: 30px; - padding-right: 45px; - padding-left: 45px; - border-radius: 7px; - background-color: #44444400; - } - )"); + QVBoxLayout *outer_layout = new QVBoxLayout(this); + outer_layout->setContentsMargins(210, 170, 210, 170); + outer_layout->addWidget(container); } bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent) { @@ -175,10 +228,34 @@ bool ConfirmationDialog::confirm(const QString &prompt_text, QWidget *parent) { return d.exec(); } -int ConfirmationDialog::exec() { - // TODO: make this work without fullscreen - if (Hardware::TICI()) { - setMainWindow(this); - } - return QDialog::exec(); + +// RichTextDialog + +RichTextDialog::RichTextDialog(const QString &prompt_text, const QString &btn_text, + QWidget *parent) : QDialogBase(parent) { + QFrame *container = new QFrame(this); + container->setStyleSheet("QFrame { background-color: #1B1B1B; }"); + QVBoxLayout *main_layout = new QVBoxLayout(container); + main_layout->setContentsMargins(32, 32, 32, 32); + + QLabel *prompt = new QLabel(prompt_text, this); + prompt->setWordWrap(true); + prompt->setAlignment(Qt::AlignLeft); + prompt->setTextFormat(Qt::RichText); + prompt->setStyleSheet("font-size: 42px; font-weight: light; color: #C9C9C9; margin: 45px;"); + main_layout->addWidget(new ScrollView(prompt, this), 1, Qt::AlignTop); + + // confirm button + QPushButton* confirm_btn = new QPushButton(btn_text); + main_layout->addWidget(confirm_btn); + QObject::connect(confirm_btn, &QPushButton::clicked, this, &QDialog::accept); + + QVBoxLayout *outer_layout = new QVBoxLayout(this); + outer_layout->setContentsMargins(100, 100, 100, 100); + outer_layout->addWidget(container); +} + +bool RichTextDialog::alert(const QString &prompt_text, QWidget *parent) { + auto d = RichTextDialog(prompt_text, "Ok", parent); + return d.exec(); } diff --git a/selfdrive/ui/qt/widgets/input.h b/selfdrive/ui/qt/widgets/input.h index 287727b9f..f81211d0e 100644 --- a/selfdrive/ui/qt/widgets/input.h +++ b/selfdrive/ui/qt/widgets/input.h @@ -9,20 +9,25 @@ #include "selfdrive/ui/qt/widgets/keyboard.h" + class QDialogBase : public QDialog { Q_OBJECT protected: QDialogBase(QWidget *parent); - bool eventFilter(QObject *o, QEvent *e) override; + bool eventFilter(QObject *o, QEvent *e) override; + +public slots: + int exec() override; }; class InputDialog : public QDialogBase { Q_OBJECT public: - explicit InputDialog(const QString &prompt_text, QWidget *parent); - static QString getText(const QString &prompt, QWidget *parent, int minLength = -1, const QString &defaultText = ""); + explicit InputDialog(const QString &title, QWidget *parent, const QString &subtitle = "", bool secret = false); + static QString getText(const QString &title, QWidget *parent, const QString &substitle = "", + bool secret = false, int minLength = -1, const QString &defaultText = ""); QString text(); void setMessage(const QString &message, bool clearInputField = true); void setMinLength(int length); @@ -33,13 +38,12 @@ private: QLineEdit *line; Keyboard *k; QLabel *label; + QLabel *sublabel; QVBoxLayout *main_layout; - -public slots: - int exec() override; + QPushButton *eye_btn; private slots: - void handleInput(const QString &s); + void handleEnter(); signals: void cancel(); @@ -54,11 +58,13 @@ public: const QString &cancel_text, QWidget* parent); static bool alert(const QString &prompt_text, QWidget *parent); static bool confirm(const QString &prompt_text, QWidget *parent); - -private: - QLabel *prompt; - QVBoxLayout *main_layout; - -public slots: - int exec() override; +}; + +// larger ConfirmationDialog for rich text +class RichTextDialog : public QDialogBase { + Q_OBJECT + +public: + explicit RichTextDialog(const QString &prompt_text, const QString &btn_text, QWidget* parent); + static bool alert(const QString &prompt_text, QWidget *parent); }; diff --git a/selfdrive/ui/qt/widgets/keyboard.cc b/selfdrive/ui/qt/widgets/keyboard.cc index 9ea8d1abb..349e53054 100644 --- a/selfdrive/ui/qt/widgets/keyboard.cc +++ b/selfdrive/ui/qt/widgets/keyboard.cc @@ -1,44 +1,68 @@ #include "selfdrive/ui/qt/widgets/keyboard.h" +#include + #include #include -#include +#include +#include #include -const int DEFAULT_STRETCH = 1; -const int SPACEBAR_STRETCH = 3; - const QString BACKSPACE_KEY = "⌫"; -const QString ENTER_KEY = "⏎"; +const QString ENTER_KEY = "→"; -const QStringList CONTROL_BUTTONS = {"↑", "↓", "ABC", "#+=", "123"}; +const QMap KEY_STRETCH = {{" ", 5}, {ENTER_KEY, 2}}; + +const QStringList CONTROL_BUTTONS = {"↑", "↓", "ABC", "#+=", "123", BACKSPACE_KEY, ENTER_KEY}; + +const float key_spacing_vertical = 20; +const float key_spacing_horizontal = 15; + +KeyButton::KeyButton(const QString &text, QWidget *parent) : QPushButton(text, parent) { + setAttribute(Qt::WA_AcceptTouchEvents); + setFocusPolicy(Qt::NoFocus); +} + +bool KeyButton::event(QEvent *event) { + if (event->type() == QEvent::TouchBegin || event->type() == QEvent::TouchEnd) { + QTouchEvent *touchEvent = static_cast(event); + if (!touchEvent->touchPoints().empty()) { + const QEvent::Type mouseType = event->type() == QEvent::TouchBegin ? QEvent::MouseButtonPress : QEvent::MouseButtonRelease; + QMouseEvent mouseEvent(mouseType, touchEvent->touchPoints().front().pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QPushButton::event(&mouseEvent); + event->accept(); + return true; + } + } + return QPushButton::event(event); +} KeyboardLayout::KeyboardLayout(QWidget* parent, const std::vector>& layout) : QWidget(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); main_layout->setMargin(0); - main_layout->setSpacing(20); + main_layout->setSpacing(0); QButtonGroup* btn_group = new QButtonGroup(this); QObject::connect(btn_group, SIGNAL(buttonClicked(QAbstractButton*)), parent, SLOT(handleButton(QAbstractButton*))); for (const auto &s : layout) { QHBoxLayout *hlayout = new QHBoxLayout; - hlayout->setSpacing(15); + hlayout->setSpacing(0); if (main_layout->count() == 1) { hlayout->addSpacing(90); } for (const QString &p : s) { - QPushButton* btn = new QPushButton(p); + KeyButton* btn = new KeyButton(p); if (p == BACKSPACE_KEY) { btn->setAutoRepeat(true); } else if (p == ENTER_KEY) { btn->setStyleSheet("background-color: #465BEA;"); } - btn->setFixedHeight(135); + btn->setFixedHeight(135 + key_spacing_vertical); btn_group->addButton(btn); - hlayout->addWidget(btn, p == QString(" ") ? SPACEBAR_STRETCH : DEFAULT_STRETCH); + hlayout->addWidget(btn, KEY_STRETCH.value(p, 1)); } if (main_layout->count() == 1) { @@ -48,10 +72,13 @@ KeyboardLayout::KeyboardLayout(QWidget* parent, const std::vectoraddLayout(hlayout); } - setStyleSheet(R"( + setStyleSheet(QString(R"( QPushButton { font-size: 75px; - margin: 0px; + margin-left: %1px; + margin-right: %1px; + margin-top: %2px; + margin-bottom: %2px; padding: 0px; border-radius: 10px; color: #dddddd; @@ -60,7 +87,7 @@ KeyboardLayout::KeyboardLayout(QWidget* parent, const std::vectortext(); - if (!QString::compare(key, "↓") || !QString::compare(key, "ABC")) { - main_layout->setCurrentIndex(0); - } - if (!QString::compare(key, "↑")) { - main_layout->setCurrentIndex(1); - } - if (!QString::compare(key, "123")) { - main_layout->setCurrentIndex(2); - } - if (!QString::compare(key, "#+=")) { - main_layout->setCurrentIndex(3); - } - if (!QString::compare(key, BACKSPACE_KEY)) { - main_layout->setCurrentIndex(0); - } - if ("A" <= key && key <= "Z") { - main_layout->setCurrentIndex(0); - } - - // TODO: break up into separate signals - if (!CONTROL_BUTTONS.contains(key)) { - emit emitButton(key); + const QString &key = btn->text(); + if (CONTROL_BUTTONS.contains(key)) { + if (key == "↓" || key == "ABC") { + main_layout->setCurrentIndex(0); + } else if (key == "↑") { + main_layout->setCurrentIndex(1); + } else if (key == "123") { + main_layout->setCurrentIndex(2); + } else if (key == "#+=") { + main_layout->setCurrentIndex(3); + } else if (key == ENTER_KEY) { + main_layout->setCurrentIndex(0); + emit emitEnter(); + } else if (key == BACKSPACE_KEY) { + emit emitBackspace(); + } + } else { + if ("A" <= key && key <= "Z") { + main_layout->setCurrentIndex(0); + } + emit emitKey(key); } } diff --git a/selfdrive/ui/qt/widgets/keyboard.h b/selfdrive/ui/qt/widgets/keyboard.h index 14df6c733..516105719 100644 --- a/selfdrive/ui/qt/widgets/keyboard.h +++ b/selfdrive/ui/qt/widgets/keyboard.h @@ -1,18 +1,22 @@ #pragma once -#include - -#include #include +#include #include -#include -#include + +class KeyButton : public QPushButton { + Q_OBJECT + +public: + KeyButton(const QString &text, QWidget *parent = 0); + bool event(QEvent *event) override; +}; class KeyboardLayout : public QWidget { Q_OBJECT public: - explicit KeyboardLayout(QWidget* parent, const std::vector>& layout); + explicit KeyboardLayout(QWidget* parent, const std::vector>& layout); }; class Keyboard : public QFrame { @@ -28,5 +32,7 @@ private slots: void handleButton(QAbstractButton* m_button); signals: - void emitButton(const QString &s); + void emitKey(const QString &s); + void emitBackspace(); + void emitEnter(); }; diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.cc b/selfdrive/ui/qt/widgets/offroad_alerts.cc index 0fcdb8716..9dd9e139e 100644 --- a/selfdrive/ui/qt/widgets/offroad_alerts.cc +++ b/selfdrive/ui/qt/widgets/offroad_alerts.cc @@ -26,13 +26,13 @@ AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent QPushButton *dismiss_btn = new QPushButton("Dismiss"); dismiss_btn->setFixedSize(400, 125); footer_layout->addWidget(dismiss_btn, 0, Qt::AlignBottom | Qt::AlignLeft); - QObject::connect(dismiss_btn, &QPushButton::released, this, &AbstractAlert::dismiss); + QObject::connect(dismiss_btn, &QPushButton::clicked, this, &AbstractAlert::dismiss); if (hasRebootBtn) { QPushButton *rebootBtn = new QPushButton("Reboot and Update"); rebootBtn->setFixedSize(600, 125); footer_layout->addWidget(rebootBtn, 0, Qt::AlignBottom | Qt::AlignRight); - QObject::connect(rebootBtn, &QPushButton::released, [=]() { Hardware::reboot(); }); + QObject::connect(rebootBtn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); } setStyleSheet(R"( * { diff --git a/selfdrive/ui/qt/widgets/setup.cc b/selfdrive/ui/qt/widgets/prime.cc similarity index 50% rename from selfdrive/ui/qt/widgets/setup.cc rename to selfdrive/ui/qt/widgets/prime.cc index f16e773a7..a27e6aaa3 100644 --- a/selfdrive/ui/qt/widgets/setup.cc +++ b/selfdrive/ui/qt/widgets/prime.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/widgets/setup.h" +#include "selfdrive/ui/qt/widgets/prime.h" #include #include @@ -31,7 +31,7 @@ void PairingQRWidget::showEvent(QShowEvent *event) { void PairingQRWidget::refresh() { QString pairToken = CommaApi::create_jwt({{"pair", true}}); - QString qrString = "https://my.comma.ai/?pair=" + pairToken; + QString qrString = "https://connect.comma.ai/?pair=" + pairToken; this->updateQrCode(qrString); } @@ -61,32 +61,52 @@ void PairingQRWidget::updateQrCode(const QString &text) { PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) { mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(30); + mainLayout->setMargin(0); + mainLayout->setSpacing(30); - QLabel* commaPrime = new QLabel("COMMA PRIME"); - mainLayout->addWidget(commaPrime, 0, Qt::AlignTop); + // subscribed prime layout + QWidget *primeWidget = new QWidget; + primeWidget->setObjectName("primeWidget"); + QVBoxLayout *primeLayout = new QVBoxLayout(primeWidget); + primeLayout->setMargin(0); + primeWidget->setContentsMargins(60, 50, 60, 50); - username = new QLabel(); - username->setStyleSheet("font-size: 55px;"); // TODO: fit width - mainLayout->addWidget(username, 0, Qt::AlignTop); + QLabel* subscribed = new QLabel("✓ SUBSCRIBED"); + subscribed->setStyleSheet("font-size: 41px; font-weight: bold; color: #86FF4E;"); + primeLayout->addWidget(subscribed, 0, Qt::AlignTop); - mainLayout->addSpacing(100); + primeLayout->addSpacing(60); + + QLabel* commaPrime = new QLabel("comma prime"); + commaPrime->setStyleSheet("font-size: 75px; font-weight: bold;"); + primeLayout->addWidget(commaPrime, 0, Qt::AlignTop); + + primeLayout->addSpacing(20); + + QLabel* connectUrl = new QLabel("CONNECT.COMMA.AI"); + connectUrl->setStyleSheet("font-size: 41px; font-family: Inter SemiBold; color: #A0A0A0;"); + primeLayout->addWidget(connectUrl, 0, Qt::AlignTop); + + mainLayout->addWidget(primeWidget); + + // comma points layout + QWidget *pointsWidget = new QWidget; + pointsWidget->setObjectName("primeWidget"); + QVBoxLayout *pointsLayout = new QVBoxLayout(pointsWidget); + pointsLayout->setMargin(0); + pointsWidget->setContentsMargins(60, 50, 60, 50); QLabel* commaPoints = new QLabel("COMMA POINTS"); - commaPoints->setStyleSheet(R"( - color: #b8b8b8; - )"); - mainLayout->addWidget(commaPoints, 0, Qt::AlignTop); + commaPoints->setStyleSheet("font-size: 41px; font-family: Inter SemiBold;"); + pointsLayout->addWidget(commaPoints, 0, Qt::AlignTop); - points = new QLabel(); - mainLayout->addWidget(points, 0, Qt::AlignTop); + points = new QLabel("210"); + points->setStyleSheet("font-size: 91px; font-weight: bold;"); + pointsLayout->addWidget(points, 0, Qt::AlignTop); - setStyleSheet(R"( - QLabel { - font-size: 70px; - font-weight: 500; - } - )"); + mainLayout->addWidget(pointsWidget); + + mainLayout->addStretch(); // set up API requests std::string dongleId = Params().get("DongleId"); @@ -100,42 +120,51 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) { void PrimeUserWidget::replyFinished(const QString &response) { QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); if (doc.isNull()) { - qDebug() << "JSON Parse failed on getting username and points"; + qDebug() << "JSON Parse failed on getting points"; return; } QJsonObject json = doc.object(); - QString points_str = QString::number(json["points"].toInt()); - QString username_str = json["username"].toString(); - if (username_str.length()) { - username_str = "@" + username_str; - } - - username->setText(username_str); - points->setText(points_str); + points->setText(QString::number(json["points"].toInt())); } -PrimeAdWidget::PrimeAdWidget(QWidget* parent) : QWidget(parent) { +PrimeAdWidget::PrimeAdWidget(QWidget* parent) : QFrame(parent) { QVBoxLayout* main_layout = new QVBoxLayout(this); - main_layout->setMargin(30); - main_layout->setSpacing(15); + main_layout->setContentsMargins(80, 90, 80, 60); + main_layout->setSpacing(0); - main_layout->addWidget(new QLabel("Upgrade now"), 1, Qt::AlignTop); + QLabel *upgrade = new QLabel("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("Become a comma prime member at my.comma.ai and get premium features!"); - description->setStyleSheet(R"( - font-size: 50px; - color: #b8b8b8; - )"); + QLabel *description = new QLabel("Become a comma prime member at connect.comma.ai"); + description->setStyleSheet("font-size: 60px; font-weight: light; color: white;"); description->setWordWrap(true); - main_layout->addWidget(description, 2, Qt::AlignTop); + main_layout->addWidget(description, 0, Qt::AlignTop); - QVector features = {"✓ REMOTE ACCESS", "✓ 14 DAYS OF STORAGE", "✓ DEVELOPER PERKS"}; - for (auto &f: features) { - QLabel* feature = new QLabel(f); - feature->setStyleSheet(R"(font-size: 40px;)"); - main_layout->addWidget(feature, 0, Qt::AlignBottom); + main_layout->addStretch(); + + QLabel *features = new QLabel("PRIME FEATURES:"); + features->setStyleSheet("font-size: 41px; font-weight: bold; color: #E5E5E5;"); + main_layout->addWidget(features, 0, Qt::AlignBottom); + main_layout->addSpacing(30); + + QVector bullets = {"Remote access", "14 days of storage", "Developer perks"}; + for (auto &b: bullets) { + const QString check = " "; + 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; + } + )"); } @@ -145,48 +174,62 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) { // Unpaired, registration prompt layout QWidget* finishRegistration = new QWidget; + finishRegistration->setObjectName("primeWidget"); QVBoxLayout* finishRegistationLayout = new QVBoxLayout(finishRegistration); - finishRegistationLayout->setMargin(30); + finishRegistationLayout->setContentsMargins(30, 75, 30, 45); + finishRegistationLayout->setSpacing(0); - QLabel* registrationDescription = new QLabel("Pair your device with the comma connect app"); + QLabel* registrationTitle = new QLabel("Finish Setup"); + registrationTitle->setStyleSheet("font-size: 75px; font-weight: bold; margin-left: 55px;"); + finishRegistationLayout->addWidget(registrationTitle); + + finishRegistationLayout->addSpacing(30); + + QLabel* registrationDescription = new QLabel("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."); registrationDescription->setWordWrap(true); - registrationDescription->setAlignment(Qt::AlignCenter); - registrationDescription->setStyleSheet(R"( - font-size: 55px; - font-weight: 400; - )"); - + registrationDescription->setStyleSheet("font-size: 55px; font-weight: light; margin-left: 55px;"); finishRegistationLayout->addWidget(registrationDescription); - QPushButton* finishButton = new QPushButton("Finish setup"); - finishButton->setFixedHeight(200); + finishRegistationLayout->addStretch(); + + QPushButton* finishButton = new QPushButton("Pair device"); + finishButton->setFixedHeight(220); finishButton->setStyleSheet(R"( - border-radius: 30px; - font-size: 55px; - font-weight: 500; - background: #585858; + QPushButton { + font-size: 55px; + font-weight: 400; + border-radius: 10px; + background-color: #465BEA; + } + QPushButton:pressed { + background-color: #3049F4; + } )"); finishRegistationLayout->addWidget(finishButton); - QObject::connect(finishButton, &QPushButton::released, this, &SetupWidget::showQrCode); + QObject::connect(finishButton, &QPushButton::clicked, this, &SetupWidget::showQrCode); mainLayout->addWidget(finishRegistration); // Pairing QR code layout QWidget* q = new QWidget; + q->setObjectName("primeWidget"); QVBoxLayout* qrLayout = new QVBoxLayout(q); + qrLayout->setContentsMargins(90, 90, 90, 90); - qrLayout->addSpacing(30); - QLabel* qrLabel = new QLabel("Scan QR code to pair!"); - qrLabel->setWordWrap(true); + QLabel* qrLabel = new QLabel("Scan the QR code to pair."); qrLabel->setAlignment(Qt::AlignHCenter); - qrLabel->setStyleSheet(R"( - font-size: 55px; - font-weight: 400; - )"); - qrLayout->addWidget(qrLabel, 0, Qt::AlignTop); + qrLabel->setStyleSheet("font-size: 47px; font-weight: light;"); + qrLayout->addWidget(qrLabel); + qrLayout->addSpacing(50); - qrLayout->addWidget(new PairingQRWidget, 1); + qrLayout->addWidget(new PairingQRWidget); + qrLayout->addStretch(); + + // setup widget + QVBoxLayout *outer_layout = new QVBoxLayout(this); + outer_layout->setContentsMargins(0, 0, 0, 0); + outer_layout->addWidget(mainLayout); mainLayout->addWidget(q); @@ -198,17 +241,11 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) { mainLayout->setCurrentWidget(primeAd); - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->addWidget(mainLayout); - + setFixedWidth(750); setStyleSheet(R"( - SetupWidget { - background-color: #292929; - } - * { - font-size: 90px; - font-weight: 500; - border-radius: 40px; + #primeWidget { + border-radius: 10px; + background-color: #333333; } )"); @@ -231,8 +268,10 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) { void SetupWidget::parseError(const QString &response) { show(); - showQr = false; - mainLayout->setCurrentIndex(0); + if (mainLayout->currentIndex() == 1) { + showQr = false; + mainLayout->setCurrentIndex(0); + } } void SetupWidget::showQrCode() { @@ -249,12 +288,9 @@ void SetupWidget::replyFinished(const QString &response) { } QJsonObject json = doc.object(); - bool is_paired = json["is_paired"].toBool(); - bool is_prime = json["prime"].toBool(); - - if (!is_paired) { + if (!json["is_paired"].toBool()) { mainLayout->setCurrentIndex(showQr); - } else if (!is_prime) { + } else if (!json["prime"].toBool()) { showQr = false; mainLayout->setCurrentWidget(primeAd); } else { diff --git a/selfdrive/ui/qt/widgets/setup.h b/selfdrive/ui/qt/widgets/prime.h similarity index 94% rename from selfdrive/ui/qt/widgets/setup.h rename to selfdrive/ui/qt/widgets/prime.h index 02c47bd3e..27b2b6e54 100644 --- a/selfdrive/ui/qt/widgets/setup.h +++ b/selfdrive/ui/qt/widgets/prime.h @@ -27,14 +27,13 @@ public: private: QVBoxLayout* mainLayout; - QLabel* username; QLabel* points; private slots: void replyFinished(const QString &response); }; -class PrimeAdWidget : public QWidget { +class PrimeAdWidget : public QFrame { Q_OBJECT public: explicit PrimeAdWidget(QWidget* parent = 0); diff --git a/selfdrive/ui/qt/widgets/scrollview.cc b/selfdrive/ui/qt/widgets/scrollview.cc index 18cd6ee67..1aa05b415 100644 --- a/selfdrive/ui/qt/widgets/scrollview.cc +++ b/selfdrive/ui/qt/widgets/scrollview.cc @@ -8,18 +8,18 @@ ScrollView::ScrollView(QWidget *w, QWidget *parent) : QScrollArea(parent) { setWidgetResizable(true); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setStyleSheet("ScrollView { background-color:transparent; }"); + setStyleSheet("background-color: transparent;"); QString style = R"( QScrollBar:vertical { border: none; background: transparent; - width:10px; + width: 10px; margin: 0; } QScrollBar::handle:vertical { min-height: 0px; - border-radius: 4px; + border-radius: 5px; background-color: white; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { @@ -29,7 +29,6 @@ ScrollView::ScrollView(QWidget *w, QWidget *parent) : QScrollArea(parent) { background: none; } )"; - verticalScrollBar()->setStyleSheet(style); horizontalScrollBar()->setStyleSheet(style); diff --git a/selfdrive/ui/qt/widgets/ssh_keys.cc b/selfdrive/ui/qt/widgets/ssh_keys.cc index a9a03fbf1..c6af2f5cd 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.cc +++ b/selfdrive/ui/qt/widgets/ssh_keys.cc @@ -9,7 +9,7 @@ SshControl::SshControl() : ButtonControl("SSH Keys", "", "Warning: This grants S username_label.setStyleSheet("color: #aaaaaa"); hlayout->insertWidget(1, &username_label); - QObject::connect(this, &ButtonControl::released, [=]() { + QObject::connect(this, &ButtonControl::clicked, [=]() { if (text() == "ADD") { QString username = InputDialog::getText("Enter your GitHub username", this); if (username.length() > 0) { @@ -40,7 +40,7 @@ void SshControl::refresh() { } void SshControl::getUserKeys(const QString &username) { - HttpRequest *request = new HttpRequest(this, "https://github.com/" + username + ".keys", false); + HttpRequest *request = new HttpRequest(this, false); QObject::connect(request, &HttpRequest::receivedResponse, [=](const QString &resp) { if (!resp.isEmpty()) { params.put("GithubUsername", username.toStdString()); @@ -61,4 +61,6 @@ void SshControl::getUserKeys(const QString &username) { refresh(); request->deleteLater(); }); + + request->sendRequest("https://github.com/" + username + ".keys"); } diff --git a/selfdrive/ui/qt/widgets/toggle.cc b/selfdrive/ui/qt/widgets/toggle.cc index e319f8017..82302ad5b 100644 --- a/selfdrive/ui/qt/widgets/toggle.cc +++ b/selfdrive/ui/qt/widgets/toggle.cc @@ -37,13 +37,13 @@ void Toggle::paintEvent(QPaintEvent *e) { } void Toggle::mouseReleaseEvent(QMouseEvent *e) { - if(!enabled) { + if (!enabled) { return; } const int left = _radius; const int right = width() - _radius; - if(_x_circle != left && _x_circle != right) { - //Don't parse touch events, while the animation is running + if ((_x_circle != left && _x_circle != right) || !this->rect().contains(e->localPos().toPoint())) { + // If mouse release isn't in rect or animation is running, don't parse touch events return; } if (e->button() & Qt::LeftButton) { @@ -73,11 +73,11 @@ bool Toggle::getEnabled() { void Toggle::setEnabled(bool value) { enabled = value; - if(value) { + if (value) { circleColor.setRgb(0xfafafa); green.setRgb(0x33ab4c); - }else{ + } else { circleColor.setRgb(0x888888); green.setRgb(0x227722); } -} \ No newline at end of file +} diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 04c50684b..787fe4eba 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -59,6 +59,7 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { outline: none; } )"); + setAttribute(Qt::WA_NoSystemBackground); } void MainWindow::openSettings() { @@ -75,7 +76,7 @@ void MainWindow::closeSettings() { bool MainWindow::eventFilter(QObject *obj, QEvent *event) { // wake screen on tap - if (event->type() == QEvent::MouseButtonPress) { + if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::TouchBegin) { device.setAwake(true, true); } diff --git a/selfdrive/ui/soundd b/selfdrive/ui/soundd new file mode 100755 index 000000000..e658062b7 --- /dev/null +++ b/selfdrive/ui/soundd @@ -0,0 +1,3 @@ +#!/bin/sh +export LD_LIBRARY_PATH="/system/lib64:$LD_LIBRARY_PATH" +exec ./_soundd diff --git a/selfdrive/ui/soundd.cc b/selfdrive/ui/soundd.cc new file mode 100644 index 000000000..014ceb2c5 --- /dev/null +++ b/selfdrive/ui/soundd.cc @@ -0,0 +1,108 @@ +#include + +#include + +#include +#include +#include + +#include "cereal/messaging/messaging.h" +#include "selfdrive/common/util.h" +#include "selfdrive/hardware/hw.h" +#include "selfdrive/ui/ui.h" + +// TODO: detect when we can't play sounds +// TODO: detect when we can't display the UI + +class Sound : public QObject { +public: + explicit Sound(QObject *parent = 0) { + // TODO: merge again and add EQ in the amp config + const QString sound_asset_path = Hardware::TICI() ? "../assets/sounds_tici/" : "../assets/sounds/"; + std::tuple sound_list[] = { + {AudibleAlert::CHIME_DISENGAGE, sound_asset_path + "disengaged.wav", false}, + {AudibleAlert::CHIME_ENGAGE, sound_asset_path + "engaged.wav", false}, + {AudibleAlert::CHIME_WARNING1, sound_asset_path + "warning_1.wav", false}, + {AudibleAlert::CHIME_WARNING2, sound_asset_path + "warning_2.wav", false}, + {AudibleAlert::CHIME_WARNING2_REPEAT, sound_asset_path + "warning_2.wav", true}, + {AudibleAlert::CHIME_WARNING_REPEAT, sound_asset_path + "warning_repeat.wav", true}, + {AudibleAlert::CHIME_ERROR, sound_asset_path + "error.wav", false}, + {AudibleAlert::CHIME_PROMPT, sound_asset_path + "error.wav", false} + }; + for (auto &[alert, fn, loops] : sound_list) { + QSoundEffect *s = new QSoundEffect(this); + QObject::connect(s, &QSoundEffect::statusChanged, this, &Sound::checkStatus); + s->setSource(QUrl::fromLocalFile(fn)); + sounds[alert] = {s, loops ? QSoundEffect::Infinite : 0}; + } + + sm = new SubMaster({"carState", "controlsState"}); + + QTimer *timer = new QTimer(this); + QObject::connect(timer, &QTimer::timeout, this, &Sound::update); + timer->start(); + }; + ~Sound() { + delete sm; + }; + +private slots: + void checkStatus() { + QSoundEffect *s = qobject_cast(sender()); + assert(s->status() != QSoundEffect::Error); + } + + void update() { + sm->update(100); + if (sm->updated("carState")) { + // scale volume with speed + volume = util::map_val((*sm)["carState"].getCarState().getVEgo(), 0.f, 20.f, + Hardware::MIN_VOLUME, Hardware::MAX_VOLUME); + } + if (sm->updated("controlsState")) { + const cereal::ControlsState::Reader &cs = (*sm)["controlsState"].getControlsState(); + setAlert({QString::fromStdString(cs.getAlertText1()), + QString::fromStdString(cs.getAlertText2()), + QString::fromStdString(cs.getAlertType()), + cs.getAlertSize(), cs.getAlertSound()}); + } else if (sm->rcv_frame("controlsState") > 0 && (*sm)["controlsState"].getControlsState().getEnabled() && + ((nanos_since_boot() - sm->rcv_time("controlsState")) / 1e9 > CONTROLS_TIMEOUT)) { + setAlert(CONTROLS_UNRESPONSIVE_ALERT); + } + } + + void setAlert(Alert a) { + if (!alert.equal(a)) { + alert = a; + // stop sounds + for (auto &[s, loops] : sounds) { + // Only stop repeating sounds + if (s->loopsRemaining() == QSoundEffect::Infinite) { + s->stop(); + } + } + + // play sound + if (alert.sound != AudibleAlert::NONE) { + auto &[s, loops] = sounds[alert.sound]; + s->setLoopCount(loops); + s->setVolume(volume); + s->play(); + } + } + } + +private: + Alert alert; + float volume = Hardware::MIN_VOLUME; + QMap> sounds; + SubMaster *sm; +}; + +int main(int argc, char **argv) { + setpriority(PRIO_PROCESS, 0, -20); + + QApplication a(argc, argv); + Sound sound; + return a.exec(); +} diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 62f621de4..bb0c0c85a 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -14,8 +14,8 @@ #include "selfdrive/ui/paint.h" #include "selfdrive/ui/qt/qt_window.h" -#define BACKLIGHT_DT 0.25 -#define BACKLIGHT_TS 2.00 +#define BACKLIGHT_DT 0.05 +#define BACKLIGHT_TS 10.00 #define BACKLIGHT_OFFROAD 75 @@ -224,6 +224,8 @@ static void update_vision(UIState *s) { } else if (!Hardware::PC()) { LOGE("visionIPC receive timeout"); } + } else if (s->scene.started) { + util::sleep_for(1000. / UI_FREQ); } } diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index db782c0a8..f3229eee9 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -31,6 +31,8 @@ #define COLOR_YELLOW nvgRGBA(218, 202, 37, 255) #define COLOR_RED nvgRGBA(201, 34, 49, 255) +typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert; + // TODO: this is also hardcoded in common/transformations/camera.py // TODO: choose based on frame input size const float y_offset = Hardware::TICI() ? 150.0 : 0.0; @@ -47,6 +49,26 @@ typedef struct Rect { } } Rect; +typedef struct Alert { + QString text1; + QString text2; + QString type; + cereal::ControlsState::AlertSize size; + AudibleAlert sound; + bool equal(Alert a2) { + return text1 == a2.text1 && text2 == a2.text2 && type == a2.type; + } +} Alert; + +const Alert CONTROLS_WAITING_ALERT = {"openpilot Unavailable", "Waiting for controls to start", + "controlsWaiting", cereal::ControlsState::AlertSize::MID, + AudibleAlert::NONE}; + +const Alert CONTROLS_UNRESPONSIVE_ALERT = {"TAKE CONTROL IMMEDIATELY", "Controls Unresponsive", + "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, + AudibleAlert::CHIME_WARNING_REPEAT}; +const int CONTROLS_TIMEOUT = 5; + const int bdr_s = 30; const int header_h = 420; const int footer_h = 280; @@ -129,7 +151,6 @@ typedef struct UIState { bool awake; - Rect video_rect, viz_rect; float car_space_transform[6]; bool wide_camera; } UIState; diff --git a/selfdrive/updated.py b/selfdrive/updated.py index cfe57bee6..cad3275d7 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -167,6 +167,8 @@ def init_overlay() -> None: params.put_bool("UpdateAvailable", False) set_consistent_flag(False) dismount_overlay() + if TICI: + run(["sudo", "rm", "-rf", STAGING_ROOT]) if os.path.isdir(STAGING_ROOT): shutil.rmtree(STAGING_ROOT) diff --git a/selfdrive/version.py b/selfdrive/version.py index 4289d5ca9..d721ba61e 100644 --- a/selfdrive/version.py +++ b/selfdrive/version.py @@ -7,6 +7,9 @@ from common.basedir import BASEDIR from selfdrive.swaglog import cloudlog +TESTED_BRANCHES = ['devel', 'release2-staging', 'release3-staging', 'dashcam-staging', 'release2', 'release3', 'dashcam'] + + def run_cmd(cmd: List[str]) -> str: return subprocess.check_output(cmd, encoding='utf8').strip() @@ -60,7 +63,7 @@ commit = get_git_commit() if (origin is not None) and (branch is not None): try: comma_remote = origin.startswith('git@github.com:commaai') or origin.startswith('https://github.com/commaai') - tested_branch = get_git_branch() in ['devel', 'release2-staging', 'dashcam-staging', 'release2', 'dashcam'] + tested_branch = get_git_branch() in TESTED_BRANCHES dirty = False