| // Copyright (C) 2017 The Android Open Source Project |
| // |
| // This software is licensed under the terms of the GNU General Public |
| // License version 2, as published by the Free Software Foundation, and |
| // may be copied, distributed, and modified under those terms. |
| // |
| // This program is distributed in the hope that it will be useful, |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| // GNU General Public License for more details. |
| #include "android/skin/qt/extended-pages/car-data-emulation/car-sensor-data.h" |
| |
| #include <stdint.h> // for int32_t |
| |
| #include <QByteArray> // for QByteArray |
| #include <QCheckBox> // for QCheckBox |
| #include <QComboBox> // for QComboBox |
| #include <QFileDialog> // for QFileDialog |
| #include <QJsonArray> // for QJsonArray |
| #include <QJsonDocument> // for QJsonDocument |
| #include <QJsonObject> // for QJsonObject |
| #include <QJsonValue> // for QJsonValue |
| #include <QLabel> // for QLabel |
| #include <QSlider> // for QSlider |
| #include <QTextStream> // for QSlider |
| #include <utility> // for move |
| |
| #include "android/base/Log.h" |
| #include "android/featurecontrol/feature_control.h" |
| #include "android/skin/qt/error-dialog.h" // for showErrorDialog |
| #include "android/utils/debug.h" // for VERBOSE_PRINT |
| #include "ui_car-sensor-data.h" // for CarSensorData |
| #include "vehicle_constants_generated.h" // for VehicleIgnit... |
| |
| class QWidget; |
| |
| #define D(...) VERBOSE_PRINT(car, __VA_ARGS__) |
| |
| using std::string; |
| |
| using emulator::EmulatorMessage; |
| using emulator::MsgType; |
| using emulator::Status; |
| using emulator::TimedEmulatorMessages; |
| using emulator::VehicleGear; |
| using emulator::VehicleIgnitionState; |
| using emulator::VehicleProperty; |
| using emulator::VehiclePropValue; |
| using emulator::VhalEventLoaderThread; |
| |
| static constexpr int64_t VHAL_REPLAY_INTERVAL = 1000; |
| |
| CarSensorData::CarSensorData(QWidget* parent) |
| : QWidget(parent), mUi(new Ui::CarSensorData) { |
| mUi->setupUi(this); |
| |
| if (!feature_is_enabled(kFeature_CarVhalReplay)) { |
| mUi->button_loadrecord->setVisible(false); |
| mUi->button_playrecord->setVisible(false); |
| QObject::connect(&mTimer, &QTimer::timeout, this, |
| &CarSensorData::VhalTimeout); |
| prepareVhalLoader(); |
| } |
| } |
| |
| static const enum VehicleGear sComboBoxGearValues[] = { |
| VehicleGear::GEAR_NEUTRAL, VehicleGear::GEAR_REVERSE, |
| VehicleGear::GEAR_PARK, VehicleGear::GEAR_DRIVE}; |
| |
| static const enum VehicleIgnitionState sComboBoxIgnitionStates[] = { |
| VehicleIgnitionState::UNDEFINED, VehicleIgnitionState::LOCK, |
| VehicleIgnitionState::OFF, VehicleIgnitionState::ACC, |
| VehicleIgnitionState::ON, VehicleIgnitionState::START}; |
| |
| static EmulatorMessage makeSetPropMsg() { |
| EmulatorMessage emulatorMsg; |
| emulatorMsg.set_msg_type(MsgType::SET_PROPERTY_CMD); |
| emulatorMsg.set_status(Status::RESULT_OK); |
| return emulatorMsg; |
| } |
| |
| void CarSensorData::VhalTimeout() { |
| if (mTimedEmulatorMessages.getStatus() != TimedEmulatorMessages::START) { |
| mTimer.stop(); |
| return; |
| } |
| std::vector<emulator::EmulatorMessage> events = |
| mTimedEmulatorMessages.getEvents(VHAL_REPLAY_INTERVAL); |
| int realIndex = mTimedEmulatorMessages.getCurrentIndex() - events.size(); |
| for (auto event : events) { |
| string log = "Send event from json index: " + std::to_string(realIndex); |
| realIndex++; |
| mSendEmulatorMsg(event, log); |
| } |
| } |
| |
| void CarSensorData::sendGearChangeMsg(const int gear, const string& gearName) { |
| // TODO: Grey out the buttons when callback is not set or vehicle hal is |
| // not connected. |
| if (mSendEmulatorMsg == nullptr) { |
| return; |
| } |
| |
| EmulatorMessage emulatorMsg = makeSetPropMsg(); |
| VehiclePropValue* value = emulatorMsg.add_value(); |
| value->set_prop(static_cast<int32_t>(VehicleProperty::GEAR_SELECTION)); |
| value->add_int32_values(gear); |
| string log = "Gear changed to " + gearName; |
| mSendEmulatorMsg(emulatorMsg, log); |
| } |
| |
| void CarSensorData::sendIgnitionChangeMsg(const int ignition, |
| const string& ignitionName) { |
| if (mSendEmulatorMsg == nullptr) { |
| return; |
| } |
| |
| EmulatorMessage emulatorMsg = makeSetPropMsg(); |
| VehiclePropValue* value = emulatorMsg.add_value(); |
| value->set_prop(static_cast<int32_t>(VehicleProperty::IGNITION_STATE)); |
| value->add_int32_values(ignition); |
| string log = "Ignition state: " + ignitionName; |
| mSendEmulatorMsg(emulatorMsg, log); |
| } |
| |
| void CarSensorData::on_car_speedSlider_valueChanged(int speed) { |
| mUi->car_speedLabel->setText(QString::number(speed)); |
| float speedMetersPerSecond = (float)speed * |
| ((mUi->comboBox_speedUnit->currentIndex() == MILES_PER_HOUR) |
| ? MILES_PER_HOUR_TO_METERS_PER_SEC |
| : KILOMETERS_PER_HOUR_TO_METERS_PER_SEC); |
| |
| if (mSendEmulatorMsg != nullptr) { |
| EmulatorMessage emulatorMsg = makeSetPropMsg(); |
| VehiclePropValue* value = emulatorMsg.add_value(); |
| value->set_prop( |
| static_cast<int32_t>(VehicleProperty::PERF_VEHICLE_SPEED)); |
| value->add_float_values(speedMetersPerSecond); |
| string log = "Speed changed to " + std::to_string(speedMetersPerSecond); |
| mSendEmulatorMsg(emulatorMsg, log); |
| } |
| } |
| |
| void CarSensorData::setSendEmulatorMsgCallback(EmulatorMsgCallback&& func) { |
| mSendEmulatorMsg = std::move(func); |
| } |
| |
| void CarSensorData::on_checkBox_night_toggled() { |
| bool night = mUi->checkBox_night->isChecked(); |
| EmulatorMessage emulatorMsg = makeSetPropMsg(); |
| VehiclePropValue* value = emulatorMsg.add_value(); |
| value->set_prop(static_cast<int32_t>(VehicleProperty::NIGHT_MODE)); |
| value->add_int32_values(night ? 1 : 0); |
| string log = "Night mode: " + std::to_string(night); |
| mSendEmulatorMsg(emulatorMsg, log); |
| } |
| |
| void CarSensorData::on_checkBox_park_toggled() { |
| bool parkBrakeOn = mUi->checkBox_park->isChecked(); |
| EmulatorMessage emulatorMsg = makeSetPropMsg(); |
| VehiclePropValue* value = emulatorMsg.add_value(); |
| value->set_prop(static_cast<int32_t>(VehicleProperty::PARKING_BRAKE_ON)); |
| value->add_int32_values(parkBrakeOn ? 1 : 0); |
| string log = "Park brake: " + std::to_string(parkBrakeOn); |
| mSendEmulatorMsg(emulatorMsg, log); |
| } |
| |
| void CarSensorData::on_checkBox_fuel_low_toggled() { |
| bool fuelLow = mUi->checkBox_fuel_low->isChecked(); |
| EmulatorMessage emulatorMsg = makeSetPropMsg(); |
| VehiclePropValue* value = emulatorMsg.add_value(); |
| value->set_prop(static_cast<int32_t>(VehicleProperty::FUEL_LEVEL_LOW)); |
| value->add_int32_values(fuelLow ? 1 : 0); |
| string log = "Fuel low: " + std::to_string(fuelLow); |
| mSendEmulatorMsg(emulatorMsg, log); |
| } |
| |
| void CarSensorData::on_comboBox_ignition_currentIndexChanged(int index) { |
| sendIgnitionChangeMsg(static_cast<int32_t>(sComboBoxIgnitionStates[index]), |
| mUi->comboBox_ignition->currentText().toStdString()); |
| } |
| |
| void CarSensorData::on_comboBox_gear_currentIndexChanged(int index) { |
| sendGearChangeMsg(static_cast<int32_t>(sComboBoxGearValues[index]), |
| mUi->comboBox_gear->currentText().toStdString()); |
| } |
| |
| void CarSensorData::on_button_loadrecord_clicked() { |
| prepareVhalLoader(); |
| |
| QString fileName = QFileDialog::getOpenFileName( |
| this, tr("Open Json File"), ".", tr("Json files (*.json)")); |
| |
| if (fileName.isNull()) |
| return; |
| parseEventsFromJsonFile(fileName); |
| } |
| |
| void CarSensorData::prepareVhalLoader() { |
| mVhalEventLoader.reset(VhalEventLoaderThread::newInstance()); |
| |
| connect(mVhalEventLoader.get(), &VhalEventLoaderThread::started, this, |
| &CarSensorData::vhalEventThreadStarted); |
| |
| connect(mVhalEventLoader.get(), &VhalEventLoaderThread::loadingFinished, |
| this, &CarSensorData::startupVhalEventThreadFinished); |
| |
| // Make sure new_instance gets cleaned up after the thread exits. |
| connect(mVhalEventLoader.get(), &VhalEventLoaderThread::finished, |
| mVhalEventLoader.get(), &QObject::deleteLater); |
| } |
| |
| void CarSensorData::on_button_playrecord_clicked() { |
| mTimedEmulatorMessages.setStatus(TimedEmulatorMessages::START); |
| mTimer.start(VHAL_REPLAY_INTERVAL); |
| } |
| |
| void CarSensorData::parseEventsFromJsonFile(QString jsonPath) { |
| mVhalEventLoader->loadVhalEventFromFile(jsonPath, &mTimedEmulatorMessages); |
| } |
| |
| void CarSensorData::processMsg(emulator::EmulatorMessage emulatorMsg) { |
| if (emulatorMsg.prop_size() == 0 && emulatorMsg.value_size() == 0) { |
| return; |
| } |
| for (int valIndex = 0; valIndex < emulatorMsg.value_size(); valIndex++) { |
| VehiclePropValue val = emulatorMsg.value(valIndex); |
| |
| switch (val.prop()) { |
| case static_cast<int32_t>(VehicleProperty::GEAR_SELECTION): { |
| int gear_vhal = getIndexFromVehicleGear(val.int32_values(0)); |
| int gear_state = mUi->comboBox_gear->currentIndex(); |
| if (gear_state != gear_vhal) { |
| mUi->comboBox_gear->setCurrentIndex(gear_vhal); |
| } |
| break; |
| } |
| case static_cast<int32_t>(VehicleProperty::FUEL_LEVEL_LOW): { |
| bool fuelLow_vhal = val.int32_values(0) == 1; |
| bool fuelLow = mUi->checkBox_fuel_low->isChecked(); |
| if (fuelLow != fuelLow_vhal) { |
| mUi->checkBox_fuel_low->setChecked(fuelLow_vhal); |
| } |
| break; |
| } |
| case static_cast<int32_t>(VehicleProperty::PARKING_BRAKE_ON): { |
| bool parkingBreak_vhal = val.int32_values(0) == 1; |
| bool parkingBreak = mUi->checkBox_park->isChecked(); |
| if (parkingBreak != parkingBreak_vhal) { |
| mUi->checkBox_park->setChecked(parkingBreak_vhal); |
| } |
| break; |
| } |
| case static_cast<int32_t>(VehicleProperty::NIGHT_MODE): { |
| bool night_vhal = val.int32_values(0) == 1; |
| bool night = mUi->checkBox_night->isChecked(); |
| if (night != night_vhal) { |
| mUi->checkBox_night->setChecked(night_vhal); |
| } |
| break; |
| } |
| case static_cast<int32_t>(VehicleProperty::PERF_VEHICLE_SPEED): { |
| float speedMeterPerSecond = val.float_values(0); |
| int speed = static_cast<int32_t>( |
| speedMeterPerSecond / |
| ((mUi->comboBox_speedUnit->currentIndex() == |
| MILES_PER_HOUR) |
| ? MILES_PER_HOUR_TO_METERS_PER_SEC |
| : KILOMETERS_PER_HOUR_TO_METERS_PER_SEC)); |
| if (speed != mUi->car_speedSlider->value()) { |
| mUi->car_speedSlider->setValue(speed); |
| } |
| break; |
| } |
| case static_cast<int32_t>(VehicleProperty::IGNITION_STATE): { |
| int ignition_state_vhal = val.int32_values(0); |
| int ignition_state = mUi->comboBox_ignition->currentIndex(); |
| if (ignition_state != ignition_state_vhal) { |
| mUi->comboBox_ignition->setCurrentIndex( |
| ignition_state_vhal); |
| } |
| break; |
| } |
| default: { |
| break; |
| } |
| } |
| } |
| } |
| |
| int CarSensorData::getIndexFromVehicleGear(int gear) { |
| int len = sizeof(sComboBoxGearValues) / sizeof(sComboBoxGearValues[0]); |
| for (int index = 0; index < len; index++) { |
| if (static_cast<int>(sComboBoxGearValues[index]) == gear) { |
| return index; |
| } |
| } |
| return len - 1; |
| } |
| |
| void CarSensorData::vhalEventThreadStarted() { |
| // Prevent the user from initiating a load json while another load is |
| // already in progress |
| mUi->button_loadrecord->setEnabled(false); |
| mUi->button_playrecord->setEnabled(false); |
| |
| mNowLoadingVhalEvent = true; |
| } |
| |
| void CarSensorData::startupVhalEventThreadFinished(QString file_name, |
| bool ok, |
| QString error_message) { |
| // on startup, we silently ignore the previously remebered event data file |
| // being missing or malformed. |
| finishVhalEventLoading(file_name, ok, error_message, true); |
| } |
| |
| void CarSensorData::vhalEventThreadFinished(QString file_name, |
| bool ok, |
| QString error_message) { |
| // on startup, we silently ignore the previously remebered event data file |
| // being missing or malformed. |
| finishVhalEventLoading(file_name, ok, error_message, false); |
| } |
| |
| void CarSensorData::finishVhalEventLoading(const QString& file_name, |
| bool ok, |
| const QString& error_message, |
| bool ignore_error) { |
| mVhalEventLoader.reset(); |
| updateControlsAfterLoading(); |
| |
| if (!ok) { |
| if (!ignore_error) { |
| showErrorDialog(error_message, tr("Vhal EVENT Parser")); |
| } |
| return; |
| } |
| } |
| |
| void CarSensorData::updateControlsAfterLoading() { |
| mUi->button_loadrecord->setEnabled(true); |
| mUi->button_playrecord->setEnabled(true); |
| |
| mNowLoadingVhalEvent = false; |
| } |
| |
| void VhalEventLoaderThread::loadVhalEventFromFile( |
| const QString& file_name, |
| TimedEmulatorMessages* events) { |
| mFileName = file_name; |
| mTimedEmulatorMessages = events; |
| start(); |
| } |
| |
| void VhalEventLoaderThread::run() { |
| if (mFileName.isEmpty() || mTimedEmulatorMessages == nullptr) { |
| emit(loadingFinished(mFileName, false, tr("No file to load"))); |
| return; |
| } |
| bool ok = false; |
| std::string err_str; |
| |
| QFileInfo file_info(mFileName); |
| mTimedEmulatorMessages->clear(); |
| auto suffix = file_info.suffix().toLower(); |
| if (suffix == "json") { |
| ok = parseJsonFile(mFileName.toStdString().c_str(), |
| mTimedEmulatorMessages); |
| } else { |
| err_str = tr("Unknown file type").toStdString(); |
| } |
| |
| auto err_qstring = QString::fromStdString(err_str); |
| emit(loadingFinished(mFileName, ok, err_qstring)); |
| } |
| |
| bool VhalEventLoaderThread::parseJsonFile( |
| const char* filePath, |
| TimedEmulatorMessages* timedEmulatorMessages) { |
| QString jsonString; |
| if (filePath) { |
| jsonString = readJsonStringFromFile(filePath); |
| } |
| |
| QJsonDocument eventDoc = QJsonDocument::fromJson(jsonString.toUtf8()); |
| if (eventDoc.isNull()) { |
| return false; |
| } else { |
| return loadEmulatorEvents(eventDoc, timedEmulatorMessages); |
| } |
| return false; |
| } |
| |
| bool VhalEventLoaderThread::loadEmulatorEvents( |
| const QJsonDocument& eventDoc, |
| TimedEmulatorMessages* timedEmulatorMessages) { |
| if (eventDoc.isNull()) { |
| return false; |
| } |
| QJsonObject eventsJson = eventDoc.object(); |
| QJsonArray eventArray = eventsJson.value("events").toArray(); |
| |
| for (int eventIdx = 0; eventIdx < eventArray.size(); eventIdx++) { |
| QJsonObject eventObject = eventArray.at(eventIdx).toObject(); |
| QJsonDocument Doc(eventObject); |
| QByteArray ba = Doc.toJson(); |
| QJsonObject sensorrecord = |
| eventObject.value("sensor_records").toObject(); |
| QJsonObject carPropertyValues = |
| sensorrecord.value("car_property_values").toObject(); |
| int propID = carPropertyValues.value("key").toInt(); |
| |
| EmulatorMessage emulatorMsg = makeSetPropMsg(); |
| VehiclePropValue* value = emulatorMsg.add_value(); |
| value->set_prop(static_cast<int32_t>(propID)); |
| |
| int prop = carPropertyValues.value("key").toInt(); |
| int areaId = carPropertyValues.value("value") |
| .toObject() |
| .value("area_id") |
| .toInt(); |
| QJsonObject propertyValue = carPropertyValues.value("value").toObject(); |
| int type = 0; |
| if (propertyValue.contains("int32_values")) { |
| value->add_int32_values( |
| propertyValue.value("int32_values").toInt()); |
| } else if (propertyValue.contains("float_values")) { |
| value->add_float_values( |
| (float)propertyValue.value("float_values").toDouble()); |
| } |
| |
| char* error = nullptr; |
| timedEmulatorMessages->addEvents( |
| strtol(sensorrecord.value("timestamp_ns") |
| .toString() |
| .toStdString() |
| .c_str(), |
| &error, 0), |
| emulatorMsg); |
| } |
| |
| return true; |
| } |
| |
| QString VhalEventLoaderThread::readJsonStringFromFile(const char* filePath) { |
| QString fullContents; |
| if (filePath) { |
| QFile jsonFile(filePath); |
| if (jsonFile.open(QFile::ReadOnly | QFile::Text)) { |
| QTextStream jsonStream(&jsonFile); |
| fullContents = jsonStream.readAll(); |
| jsonFile.close(); |
| } |
| } |
| return fullContents; |
| } |
| |
| VhalEventLoaderThread* VhalEventLoaderThread::newInstance() { |
| VhalEventLoaderThread* new_instance = new VhalEventLoaderThread(); |
| return new_instance; |
| } |
| |
| std::vector<emulator::EmulatorMessage> TimedEmulatorMessages::getEvents( |
| int64_t interval) { |
| std::vector<emulator::EmulatorMessage> res; |
| |
| if (mTimestampes.size() != mEmulatorMessages.size()) { |
| clear(); |
| return res; |
| } |
| if (mCurrIndex == mEmulatorMessages.size()) { |
| mCurrIndex = 0; |
| mBaseTimeStamp = -1; |
| mStatus = STOP; |
| return res; |
| } |
| |
| int64_t nextTimeStamp = mBaseTimeStamp + (int64_t)interval; |
| |
| for (; mCurrIndex < mEmulatorMessages.size(); mCurrIndex++) { |
| int64_t timestamp = mTimestampes.at(mCurrIndex); |
| if (timestamp >= mBaseTimeStamp && timestamp < nextTimeStamp) { |
| res.push_back(mEmulatorMessages.at(mCurrIndex)); |
| } else { |
| break; |
| } |
| } |
| mBaseTimeStamp = nextTimeStamp; |
| return res; |
| } |
| |
| void TimedEmulatorMessages::addEvents(int64_t timestamp, |
| emulator::EmulatorMessage& msg) { |
| if (mBaseTimeStamp == -1) { |
| mBaseTimeStamp = timestamp; |
| } |
| if (mTimestampes.size() != mEmulatorMessages.size()) { |
| return; |
| } |
| mEmulatorMessages.push_back(msg); |
| mTimestampes.push_back(timestamp); |
| } |
| |
| void TimedEmulatorMessages::clear() { |
| mEmulatorMessages.clear(); |
| mTimestampes.clear(); |
| mCurrIndex = 0; |
| mBaseTimeStamp = -1; |
| } |
| |
| TimedEmulatorMessages::PlayStatus TimedEmulatorMessages::getStatus() { |
| return mStatus; |
| } |
| |
| void TimedEmulatorMessages::setStatus(PlayStatus status) { |
| mStatus = status; |
| } |
| |
| int TimedEmulatorMessages::getCurrentIndex() { |
| return mCurrIndex; |
| } |