#include #include #include #include #include #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 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); 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> 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(); }