mirror of https://github.com/commaai/openpilot.git
soundd: misc cleanup + tests (#22796)
* pass AudibleAlert to setAlert * apply reviews * remove trailing underscore * add current_alert_type * common function get_alert * same freq as ui * small cleanup * setvolumne on change * static Alert::get * test_sounds * remove old test files * loop twice * assert loopsRemaining * assert stop * init to min_volumne * Revert "remove old test files" This reverts commit 12e8bfefae1ae7f8649d3eda1e4126ff8eb1fe17. * move to ui/soundd * move test to tests/test_sound.cc * remove old tests * cleanup * lower volume * Revert "remove old tests" This reverts commite6bb12abfa
. * return Alert * cleanup * remove #include <optional> * cleanup .gitignore * revert to std::optional * Revert "revert to std::optional" This reverts commita66291c51f
. * cleanup * remove volume check Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> old-commit-hash:797bb1acb0
This commit is contained in:
parent
1e67fc25f1
commit
c1a3683ccb
|
@ -42,7 +42,6 @@ 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
|
||||
|
|
|
@ -340,7 +340,10 @@ selfdrive/ui/*.h
|
|||
selfdrive/ui/ui
|
||||
selfdrive/ui/text
|
||||
selfdrive/ui/spinner
|
||||
selfdrive/ui/soundd
|
||||
selfdrive/ui/soundd/*.cc
|
||||
selfdrive/ui/soundd/*.h
|
||||
selfdrive/ui/soundd/soundd
|
||||
selfdrive/ui/soundd/.gitignore
|
||||
|
||||
selfdrive/ui/qt/*.cc
|
||||
selfdrive/ui/qt/*.h
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
// no-op base hw class
|
||||
class HardwareNone {
|
||||
public:
|
||||
static constexpr float MAX_VOLUME = 0;
|
||||
static constexpr float MIN_VOLUME = 0;
|
||||
static constexpr float MAX_VOLUME = 0.7;
|
||||
static constexpr float MIN_VOLUME = 0.2;
|
||||
|
||||
static std::string get_os_version() { return ""; }
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ procs = [
|
|||
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=(5 if TICI else None)),
|
||||
NativeProcess("soundd", "selfdrive/ui", ["./soundd"]),
|
||||
NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"]),
|
||||
NativeProcess("locationd", "selfdrive/locationd", ["./locationd"]),
|
||||
NativeProcess("boardd", "selfdrive/boardd", ["./boardd"], enabled=False),
|
||||
PythonProcess("calibrationd", "selfdrive.locationd.calibrationd"),
|
||||
|
|
|
@ -3,7 +3,6 @@ moc_*
|
|||
|
||||
watch3
|
||||
installer/installers/*
|
||||
tests/playsound
|
||||
replay/replay
|
||||
replay/tests/test_replay
|
||||
qt/text
|
||||
|
|
|
@ -44,9 +44,10 @@ qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src,
|
|||
asset_obj = qt_env.Object("assets", assets)
|
||||
|
||||
# build soundd
|
||||
qt_env.Program("_soundd", "soundd.cc", LIBS=qt_libs)
|
||||
qt_env.Program("soundd/_soundd", ["soundd/main.cc", "soundd/sound.cc"], LIBS=qt_libs)
|
||||
if GetOption('test'):
|
||||
qt_env.Program("tests/playsound", "tests/playsound.cc", LIBS=base_libs)
|
||||
qt_env.Program('tests/test_sound', ['tests/test_runner.cc', 'soundd/sound.cc', 'tests/test_sound.cc'], LIBS=[base_libs])
|
||||
|
||||
qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs)
|
||||
|
||||
|
|
|
@ -40,25 +40,12 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
Alert alert = Alert::get(*(s.sm), s.scene.started_frame);
|
||||
if (alert.type == "controlsUnresponsive") {
|
||||
bgColor = bg_colors[STATUS_ALERT];
|
||||
}
|
||||
alerts->updateAlert(alert, bgColor);
|
||||
if (bg != bgColor) {
|
||||
// repaint border
|
||||
bg = bgColor;
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
#include <sys/resource.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QString>
|
||||
#include <QSoundEffect>
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#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
|
||||
|
||||
void sigHandler(int s) {
|
||||
qApp->quit();
|
||||
}
|
||||
|
||||
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<AudibleAlert, QString, bool> 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<QSoundEffect*>(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);
|
||||
for (auto &[s, loops] : sounds) {
|
||||
s->setVolume(std::round(100 * volume) / 100);
|
||||
}
|
||||
}
|
||||
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->play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Alert alert;
|
||||
float volume = Hardware::MIN_VOLUME;
|
||||
QMap<AudibleAlert, QPair<QSoundEffect*, int>> sounds;
|
||||
SubMaster *sm;
|
||||
};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
qInstallMessageHandler(swagLogMessageHandler);
|
||||
setpriority(PRIO_PROCESS, 0, -20);
|
||||
|
||||
QApplication a(argc, argv);
|
||||
std::signal(SIGINT, sigHandler);
|
||||
std::signal(SIGTERM, sigHandler);
|
||||
|
||||
Sound sound;
|
||||
return a.exec();
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
_soundd
|
|
@ -0,0 +1,22 @@
|
|||
#include <sys/resource.h>
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/soundd/sound.h"
|
||||
|
||||
void sigHandler(int s) {
|
||||
qApp->quit();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
qInstallMessageHandler(swagLogMessageHandler);
|
||||
setpriority(PRIO_PROCESS, 0, -20);
|
||||
|
||||
QApplication a(argc, argv);
|
||||
std::signal(SIGINT, sigHandler);
|
||||
std::signal(SIGTERM, sigHandler);
|
||||
|
||||
Sound sound;
|
||||
return a.exec();
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
#include "selfdrive/ui/soundd/sound.h"
|
||||
|
||||
#include "cereal/messaging/messaging.h"
|
||||
#include "selfdrive/common/util.h"
|
||||
|
||||
// TODO: detect when we can't play sounds
|
||||
// TODO: detect when we can't display the UI
|
||||
|
||||
Sound::Sound(QObject *parent) : sm({"carState", "controlsState"}) {
|
||||
const QString sound_asset_path = Hardware::TICI() ? "../../assets/sounds_tici/" : "../../assets/sounds/";
|
||||
for (auto &[alert, fn, loops] : sound_list) {
|
||||
QSoundEffect *s = new QSoundEffect(this);
|
||||
QObject::connect(s, &QSoundEffect::statusChanged, [=]() {
|
||||
assert(s->status() != QSoundEffect::Error);
|
||||
});
|
||||
s->setSource(QUrl::fromLocalFile(sound_asset_path + fn));
|
||||
s->setVolume(Hardware::MIN_VOLUME);
|
||||
sounds[alert] = {s, loops ? QSoundEffect::Infinite : 0};
|
||||
}
|
||||
|
||||
QTimer *timer = new QTimer(this);
|
||||
QObject::connect(timer, &QTimer::timeout, this, &Sound::update);
|
||||
timer->start(1000 / UI_FREQ);
|
||||
};
|
||||
|
||||
void Sound::update() {
|
||||
sm.update(0);
|
||||
if (sm.updated("carState")) {
|
||||
// scale volume with speed
|
||||
float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 0.f, 20.f,
|
||||
Hardware::MIN_VOLUME, Hardware::MAX_VOLUME);
|
||||
for (auto &[s, loops] : sounds) {
|
||||
s->setVolume(std::round(100 * volume) / 100);
|
||||
}
|
||||
}
|
||||
|
||||
setAlert(Alert::get(sm, 1));
|
||||
}
|
||||
|
||||
void Sound::setAlert(const Alert &alert) {
|
||||
if (!current_alert.equal(alert)) {
|
||||
current_alert = alert;
|
||||
// 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->play();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
#include <QMap>
|
||||
#include <QSoundEffect>
|
||||
#include <QString>
|
||||
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
const std::tuple<AudibleAlert, QString, bool> sound_list[] = {
|
||||
{AudibleAlert::CHIME_DISENGAGE, "disengaged.wav", false},
|
||||
{AudibleAlert::CHIME_ENGAGE, "engaged.wav", false},
|
||||
{AudibleAlert::CHIME_WARNING1, "warning_1.wav", false},
|
||||
{AudibleAlert::CHIME_WARNING2, "warning_2.wav", false},
|
||||
{AudibleAlert::CHIME_WARNING2_REPEAT, "warning_2.wav", true},
|
||||
{AudibleAlert::CHIME_WARNING_REPEAT, "warning_repeat.wav", true},
|
||||
{AudibleAlert::CHIME_ERROR, "error.wav", false},
|
||||
{AudibleAlert::CHIME_PROMPT, "error.wav", false},
|
||||
};
|
||||
|
||||
class Sound : public QObject {
|
||||
public:
|
||||
explicit Sound(QObject *parent = 0);
|
||||
|
||||
protected:
|
||||
void update();
|
||||
void setAlert(const Alert &alert);
|
||||
|
||||
Alert current_alert = {};
|
||||
QMap<AudibleAlert, QPair<QSoundEffect *, int>> sounds;
|
||||
SubMaster sm;
|
||||
};
|
|
@ -1,2 +1,3 @@
|
|||
test
|
||||
play_sound
|
||||
playsound
|
||||
test_sound
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#define CATCH_CONFIG_RUNNER
|
||||
#include "catch2/catch.hpp"
|
||||
#include <QCoreApplication>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// unit tests for Qt
|
||||
QCoreApplication app(argc, argv);
|
||||
const int res = Catch::Session().run(argc, argv);
|
||||
return (res < 0xff ? res : 0xff);
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
#include <QEventLoop>
|
||||
#include <QMap>
|
||||
#include <QThread>
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
#include "selfdrive/ui/soundd/sound.h"
|
||||
|
||||
class TestSound : public Sound {
|
||||
public:
|
||||
TestSound() : Sound() {
|
||||
for (auto i = sounds.constBegin(); i != sounds.constEnd(); ++i) {
|
||||
QObject::connect(i.value().first, &QSoundEffect::playingChanged, [=, s = i.value().first, a = i.key()]() {
|
||||
if (s->isPlaying()) {
|
||||
bool repeat = a == AudibleAlert::CHIME_WARNING_REPEAT || a == AudibleAlert::CHIME_WARNING2_REPEAT;
|
||||
REQUIRE((s->loopsRemaining() == repeat ? QSoundEffect::Infinite : 1));
|
||||
sound_stats[a].first++;
|
||||
} else {
|
||||
sound_stats[a].second++;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QMap<AudibleAlert, std::pair<int, int>> sound_stats;
|
||||
};
|
||||
|
||||
void controls_thread(int loop_cnt) {
|
||||
PubMaster pm({"controlsState"});
|
||||
const int DT_CTRL = 10; // ms
|
||||
for (int i = 0; i < loop_cnt; ++i) {
|
||||
for (auto &[alert, fn, loops] : sound_list) {
|
||||
printf("testing %s\n", qPrintable(fn));
|
||||
for (int j = 0; j < 1000 / DT_CTRL; ++j) {
|
||||
MessageBuilder msg;
|
||||
auto cs = msg.initEvent().initControlsState();
|
||||
cs.setAlertSound(alert);
|
||||
cs.setAlertType(fn.toStdString());
|
||||
pm.send("controlsState", msg);
|
||||
QThread::msleep(DT_CTRL);
|
||||
}
|
||||
}
|
||||
}
|
||||
QThread::currentThread()->quit();
|
||||
}
|
||||
|
||||
TEST_CASE("test sound") {
|
||||
QEventLoop loop;
|
||||
|
||||
TestSound test_sound;
|
||||
|
||||
const int test_loop_cnt = 2;
|
||||
QThread t;
|
||||
QObject::connect(&t, &QThread::started, [=]() { controls_thread(test_loop_cnt); });
|
||||
QObject::connect(&t, &QThread::finished, [&]() { loop.quit(); });
|
||||
t.start();
|
||||
|
||||
loop.exec();
|
||||
|
||||
for (auto [play, stop] : test_sound.sound_stats) {
|
||||
REQUIRE(play == test_loop_cnt);
|
||||
REQUIRE(stop == test_loop_cnt);
|
||||
}
|
||||
}
|
|
@ -26,6 +26,12 @@
|
|||
#define COLOR_YELLOW nvgRGBA(218, 202, 37, 255)
|
||||
#define COLOR_RED nvgRGBA(201, 34, 49, 255)
|
||||
|
||||
const int bdr_s = 30;
|
||||
const int header_h = 420;
|
||||
const int footer_h = 280;
|
||||
|
||||
const int UI_FREQ = 20; // Hz
|
||||
|
||||
typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert;
|
||||
|
||||
// TODO: this is also hardcoded in common/transformations/camera.py
|
||||
|
@ -44,7 +50,7 @@ typedef struct Rect {
|
|||
}
|
||||
} Rect;
|
||||
|
||||
typedef struct Alert {
|
||||
struct Alert {
|
||||
QString text1;
|
||||
QString text2;
|
||||
QString type;
|
||||
|
@ -53,22 +59,31 @@ typedef struct Alert {
|
|||
bool equal(const 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;
|
||||
|
||||
const int UI_FREQ = 20; // Hz
|
||||
static Alert get(const SubMaster &sm, uint64_t started_frame) {
|
||||
if (sm.updated("controlsState")) {
|
||||
const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState();
|
||||
return {cs.getAlertText1().cStr(), cs.getAlertText2().cStr(),
|
||||
cs.getAlertType().cStr(), cs.getAlertSize(),
|
||||
cs.getAlertSound()};
|
||||
} else if ((sm.frame - started_frame) > 5 * UI_FREQ) {
|
||||
const int CONTROLS_TIMEOUT = 5;
|
||||
// Handle controls timeout
|
||||
if (sm.rcv_frame("controlsState") < started_frame) {
|
||||
// car is started, but controlsState hasn't been seen at all
|
||||
return {"openpilot Unavailable", "Waiting for controls to start",
|
||||
"controlsWaiting", cereal::ControlsState::AlertSize::MID,
|
||||
AudibleAlert::NONE};
|
||||
} else if ((nanos_since_boot() - sm.rcv_time("controlsState")) / 1e9 > CONTROLS_TIMEOUT) {
|
||||
// car is started, but controls is lagging or died
|
||||
return {"TAKE CONTROL IMMEDIATELY", "Controls Unresponsive",
|
||||
"controlsUnresponsive", cereal::ControlsState::AlertSize::FULL,
|
||||
AudibleAlert::CHIME_WARNING_REPEAT};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
typedef enum UIStatus {
|
||||
STATUS_DISENGAGED,
|
||||
|
|
Loading…
Reference in New Issue