C
Qt Quick Ultralite Automotive Cluster Demo
/****************************************************************************** ** ** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Quick Ultralite module. ** ** $QT_BEGIN_LICENSE:COMM$ ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** $QT_END_LICENSE$ ** ******************************************************************************/#include "simulation/drivestates.h" #include "simulation/drivetrain.h" #include "simulation/speedlimits.h" #include "simulation/lights.h" #include "mathutils.h" #include "Automotive/TellTalesModel.h" #include "Automotive/NaviModel.h" using namespace Automotive; namespace { MainModel &mainModel = MainModel::instance(); TellTalesModel &tellTalesModel = TellTalesModel::instance(); NaviModel &naviModel = NaviModel::instance(); static Simulation::Drivetrain::Config drivetrainConfig = { 200, // float maxSpeed; 7000, // float maxRpm; 800, // float minRpm; 6000, // float shiftUpAtRpm; 2500, // float shiftDownAtRpm; 207.3f, // float tireCircumference; // cm for 225/45/R18 7.5f, // float diffRatio; 6, // int gearNum; {2.97f, 2.07f, 1.43f, 1.00f, 0.84f, 0.56f}, // float gearRatios[MAX_GEARS]; 0.000025f, // float batteryFillRatio; 0.00002f, // float batteryDrainRatio; 0.000055f, // float coolantHeatRatio; 0.00002f, // float coolantCooldownRatio; 90.f, // float optimalTemp; 180.f, // float maxTemp; (float) mainModel.fullRange.value() // float maxRange; }; Simulation::Drivetrain drivetrain(drivetrainConfig); Simulation::SpeedLimits speedLimits(20000, 10000); Simulation::Lights lights; } // namespace namespace CommonDriveConstants { const uint16_t DelayDriveAfterModeChange = 1500; const float AcceChangeFilteringFactor = 0.15f; const uint8_t StateUpdatePeriod = 100; } // namespace CommonDriveConstants namespace LightConstants { const uint8_t HighBeamMinimalSpeed = 100; const uint16_t HighBeamRetryPeriod = 5000; const uint16_t HighBeamTurnOnTimeMin = 5000; const uint16_t HighBeamTurnOnTimeMax = 15000; const uint16_t TurnSignalShort = 1500; const uint16_t TurnSignalLong = 5000; const uint16_t TurnSingalTurnOnTimeScaleMin = 1; const uint16_t TurnSingalTurnOnTimeScaleMax = 2; const uint8_t TurnSignalShortMinSpeed = 70; const uint16_t TurnSignalRetryPeriodMin = 5000; const uint16_t TurnSignalRetryPeriodMax = 25000; const uint16_t TurnSignalInitialDelay = 10000; } // namespace LightConstants namespace LaneAssistConstants { const uint16_t InitialDelay = 10000; const uint16_t RetryPeriodMin = 5000; const uint16_t RetryPeriodMax = 15000; } // namespace LaneAssistConstants namespace ArrowGuidesConstants { const uint8_t HighSpeedForwardOnly = 80; const uint8_t MediumSpeedLightTurns = 50; const float HighSpeedDistanceMin = 0.5f; // 500m const float HighSpeedDistanceMax = 1.f; // 1km const float DistanceMin = 0.1f; // 100m const float DistanceMax = 0.3f; // 300m const float InitialDistance = 0.05f; } // namespace ArrowGuidesConstants namespace NormalDriveConstants { const uint16_t AccChangeRandomizePeriod = 3000; const float AccChangeMin = -0.1f; const float AccChangeMax = 1.0f; const float AccMin = -0.3f; const float AccMax = 0.4f; const float AccInitial = AccMax; const float AccChangeSpeedFactor = 10000.f; const uint8_t DefaultSpeedLimit = 160; const uint8_t ExcessiveSpeedingThreshold = 50; const float ExcessiveSpeedingBraking = -0.5f; const uint8_t LightSpeedingThreshold = 15; const float LightSpeedingBraking = -0.2f; const float LightSpeedingBrakingRelaxValue = -0.4f; } // namespace NormalDriveConstants namespace SportDriveConstants { const float MinSpeed = 10.f; const float SpeedBoostFactor = 20.f; const float AccMin = 0.2f; const float AccMax = 2.0f; const float CooldownSpeedBoostFactor = 2.f; const float CooldownSpeedReductionFactor = 5.f; const float CooldownAccMin = 0.05f; const float CooldownAccMax = 0.15f; const float CooldownAccReductionMin = 0.1f; const float CooldownAccReductionMax = 1.f; const float CooldownStartMaxTempRatio = 0.9f; const float CooldownStopOptimalTempRatio = 1.1f; const float SpeedFactorMin = 0.f; const float SpeedFactorMax = 1.f; } // namespace SportDriveConstants namespace Simulation { bool isDriveState(const StateId &state) { return state == State_NormalDrive || state == State_SportDrive; } DriveState::DriveState(MainModel::ClusterMode mode_, char gearLabel) : ClusterModeState(mode_) , _gearLabel(gearLabel) , _cumulatedTime(0) , _lastGear(INT32_MIN) , _highBeamTimestamp(0) , _turnSignalTimestamp(0) , _laneAssistTimestamp(0) , _guideArrowNextChange(0) , _acceleration(0) , _comesFromDriveState(false) {} void DriveState::onEnter(const StateId &prevState, const Layer &layer, Machine &sm) { ClusterModeState::onEnter(prevState, layer, sm); _lastGear = INT32_MIN; _cumulatedTime = 0; _comesFromDriveState = isDriveState(prevState); if (!_comesFromDriveState) { mainModel.laneAssistCarMoving.setValue(true); drivetrain.reset(); drivetrain.resetOdo(mainModel.odo.value()); drivetrain.resetBattery(mainModel.batteryLevel.value()); speedLimits.reset((SpeedLimitValues::Limit) mainModel.speedLimitWarning.value()); lights.setLightState(Lights::LowBeam, true); naviModel.triggerInstruction(NaviModel::ArrowForward); sm.requestUpdate(CommonDriveConstants::StateUpdatePeriod, true, layer); } _turnSignalTimestamp = LightConstants::TurnSignalInitialDelay; // Don't turn on turn signals for at least 10s from state begin _laneAssistTimestamp = LaneAssistConstants::InitialDelay; // Same for lane assist _guideArrowNextChange = drivetrain.getDriveData().odo + ArrowGuidesConstants::InitialDistance; _highBeamTimestamp = 0; } void DriveState::onUpdate(uint32_t tick, const Layer &, Machine &) { _cumulatedTime += tick; // When switching drive states, don't start to drive immediately, // keep prev acc value for a while if (getStateTime() > CommonDriveConstants::DelayDriveAfterModeChange || !_comesFromDriveState) { drive(tick); } else { updateDrivetrain(tick, _acceleration); } updateLights(tick); updateLaneAssist(); updateGuides(); updateModels(); } void DriveState::updateModels() { const Drivetrain::DriveData &data = drivetrain.getDriveData(); mainModel.speed.setValue(data.speed); mainModel.rpm.setValue(data.rpm); mainModel.odo.setValue(int(data.odo)); mainModel.batteryLevel.setValue(data.battery); mainModel.temp.setValue(data.coolantTemp); mainModel.range.setValue(int(data.range)); mainModel.fuelLevel.setValue(data.fuel); tellTalesModel.turnLeftBlinking.setValue(lights.getLightState(Lights::TurnLeft)); tellTalesModel.turnRightBlinking.setValue(lights.getLightState(Lights::TurnRight)); tellTalesModel.beamActive.setValue(lights.getLightState(Lights::LowBeam)); tellTalesModel.highBeamsActive.setValue(lights.getLightState(Lights::HighBeam)); const int currentGear = data.gear; if (currentGear != _lastGear) { // This is workaround for QUL string using static buffer underneath // (no value change could be detected then) static char gearText[3] = {'\0', '\0', '\0'}; gearText[0] = _gearLabel; gearText[1] = '1' + currentGear; mainModel.gearShiftText.setValue(gearText); mainModel.gearShiftText.markDependencyDirty(); _lastGear = currentGear; } } void DriveState::onLeave(const StateId &nextState, const Layer &layer, Machine &sm) { if (!isDriveState(nextState)) { sm.dismissUpdate(layer); } } void DriveState::updateLights(uint32_t tick) { using namespace LightConstants; lights.update(tick); if (drivetrain.getDriveData().speed > HighBeamMinimalSpeed && lights.getLightState(Lights::HighBeam) == false && int64_t(getStateTime() - _highBeamTimestamp) > HighBeamRetryPeriod) { const bool turnOn = randomChoice(1u) == 0; if (turnOn) { const uint32_t turnOnTime = randomize(HighBeamTurnOnTimeMin, HighBeamTurnOnTimeMax); lights.setLightState(Lights::HighBeam, turnOn, turnOnTime); _highBeamTimestamp = getStateTime() + turnOnTime; } else { _highBeamTimestamp = getStateTime(); } } if (int64_t(getStateTime() - _turnSignalTimestamp) > 0) { // Choices: // 0 - turn left // 1 - turn right // 2 - off uint8_t choice = randomChoice(2u); if (choice == 0 || choice == 1) { // When going fast simulate lane changes by shorter period of signal enabled const uint16_t turnOnBaseTime = (drivetrain.getDriveData().speed > TurnSignalShortMinSpeed ? TurnSignalShort : TurnSignalLong); const uint16_t turnOnTime = randomScale(turnOnBaseTime, TurnSingalTurnOnTimeScaleMin, TurnSingalTurnOnTimeScaleMax); lights.setLightState(choice == 0 ? Lights::TurnLeft : Lights::TurnRight, true, turnOnTime); } else { lights.setLightState(Lights::TurnLeft, false); lights.setLightState(Lights::TurnRight, false); } _turnSignalTimestamp = getStateTime() + randomize(TurnSignalRetryPeriodMin, TurnSignalRetryPeriodMax); } } void DriveState::updateLaneAssist() { using namespace LaneAssistConstants; if (int64_t(getStateTime() - _laneAssistTimestamp) > 0) { // Choices: // 0 - left // 1 - right // 2 - off uint8_t choice = randomChoice(2u); // Don't signal same side as current turn indicator if ((choice == 0 && lights.getLightState(Lights::TurnLeft)) || (choice == 1 && lights.getLightState(Lights::TurnRight))) { choice = (choice + 1) % 2; } mainModel.triggerLaneAssist(choice); _laneAssistTimestamp = getStateTime() + randomize(RetryPeriodMin, RetryPeriodMax); } } void DriveState::updateGuides() { using namespace ArrowGuidesConstants; if (drivetrain.getDriveData().odo >= _guideArrowNextChange) { // Tight turns and roundabuts possible only at low speeds NaviModel::ArrowType maxAllowedManoeuver = NaviModel::ArrowNone; const bool highSpeed = drivetrain.getDriveData().speed > HighSpeedForwardOnly; if (highSpeed) { maxAllowedManoeuver = NaviModel::ArrowForward; } else if (drivetrain.getDriveData().speed > MediumSpeedLightTurns) { maxAllowedManoeuver = NaviModel::ArrowRight; } else { maxAllowedManoeuver = NaviModel::ArrowRound; } NaviModel::ArrowType choice = randomChoice(maxAllowedManoeuver, NaviModel::ArrowForward); naviModel.triggerInstruction(choice); // All manoeuvers should change quickly, just forward direction should stay longer float distanceToChange = highSpeed ? randomize(HighSpeedDistanceMin, HighSpeedDistanceMax) : randomize(DistanceMin, DistanceMax); _guideArrowNextChange = drivetrain.getDriveData().odo + distanceToChange; } const uint32_t distanceInMeters = uint32_t((_guideArrowNextChange - drivetrain.getDriveData().odo) * 1000); naviModel.distanceToNextManoeuver.setValue(distanceInMeters); } void DriveState::updateDrivetrain(uint32_t tick, float acceleration) { using namespace CommonDriveConstants; _acceleration = AcceChangeFilteringFactor * acceleration + (1 - AcceChangeFilteringFactor) * _acceleration; drivetrain.udpate(tick, _acceleration); } NormalDriveState::NormalDriveState() : DriveState(MainModel::ModeNormal, 'D') , _accChangeTimestamp(0) , _targetAcc(0) , _accChange(0) {} void NormalDriveState::onEnter(const StateId &prevState, const Layer &layer, Machine &sm) { DriveState::onEnter(prevState, layer, sm); _targetAcc = NormalDriveConstants::AccInitial; randomizeAccChange(); } void NormalDriveState::onUpdate(uint32_t tick, const Layer &layer, Machine &sm) { speedLimits.update(tick); DriveState::onUpdate(tick, layer, sm); } void NormalDriveState::randomizeAccChange() { using namespace NormalDriveConstants; _accChange = randomize(AccChangeMin, AccChangeMax); _accChangeTimestamp = 0; } void NormalDriveState::drive(uint32_t tick) { using namespace NormalDriveConstants; _accChangeTimestamp += tick; float limit = speedLimits.getCurrentSpeedLimitInKmh(); if (limit <= 10e-5f) { // There is not speed limit, but in normal mode don't go to fast limit = DefaultSpeedLimit; } if (limit > 0 && drivetrain.getDriveData().speed > limit) { if (drivetrain.getDriveData().speed - limit > ExcessiveSpeedingThreshold) { // Excessive speeding slow down fast _targetAcc = ExcessiveSpeedingBraking; } else if (_targetAcc < LightSpeedingBrakingRelaxValue && drivetrain.getDriveData().speed - limit < LightSpeedingThreshold) { // Lot of braking, but not so much speeding any more, relax _targetAcc = LightSpeedingBraking; } else if (_targetAcc > LightSpeedingBraking) { // Slight speeding, slow down gently _targetAcc -= tick / AccChangeSpeedFactor; } } else { // Just randomize acc change if (_accChangeTimestamp >= AccChangeRandomizePeriod) { randomizeAccChange(); } _targetAcc += tick / AccChangeSpeedFactor * _accChange; _targetAcc = clamp(_targetAcc, AccMin, AccMax); } DriveState::updateDrivetrain(tick, _targetAcc); } void NormalDriveState::updateModels() { DriveState::updateModels(); mainModel.speedLimitWarning.setValue(speedLimits.getCurrentSpeedLimit()); } SportDriveState::SportDriveState() : DriveState(MainModel::ModeSport, 'S') , _targetSpeed(0) , _cooldown(false) {} void SportDriveState::onEnter(const StateId &prevState, const Layer &layer, Machine &sm) { DriveState::onEnter(prevState, layer, sm); mainModel.speedLimitWarning.setValue(SpeedLimitValues::None); randomizeTargetSpeed(); } void SportDriveState::onUpdate(uint32_t tick, const Layer &layer, Machine &sm) { updateCooldown(); DriveState::onUpdate(tick, layer, sm); } void SportDriveState::drive(uint32_t tick) { using namespace SportDriveConstants; const Drivetrain::DriveData &data = drivetrain.getDriveData(); const int dir = _targetSpeed - data.speed > 0 ? 1 : -1; const float speedFactor = std::abs(_targetSpeed - data.speed) / drivetrainConfig.maxSpeed; float targetAcc = 0; if (_cooldown) { if (dir > 0) { targetAcc = linearPartitionSelect(clamp(speedFactor * CooldownSpeedBoostFactor, SpeedFactorMin, SpeedFactorMax), CooldownAccMin, CooldownAccMax); } else { targetAcc = linearPartitionSelect(clamp(speedFactor * CooldownSpeedReductionFactor, SpeedFactorMin, SpeedFactorMax), CooldownAccReductionMin, CooldownAccReductionMax); } } else { targetAcc = sqrtPartitionSelect(clamp(speedFactor * SpeedBoostFactor, SpeedFactorMin, SpeedFactorMax), AccMin, AccMax); } targetAcc *= dir; DriveState::updateDrivetrain(tick, targetAcc); if ((dir > 0 && data.speed >= _targetSpeed) || (dir < 0 && data.speed <= _targetSpeed)) { // Target speed boundary just crossed randomizeTargetSpeed(); } } void SportDriveState::updateCooldown() { using namespace SportDriveConstants; if (_cooldown && drivetrain.getDriveData().coolantTemp <= drivetrainConfig.optimalTemp * CooldownStopOptimalTempRatio) { _cooldown = false; } else if (!_cooldown && drivetrain.getDriveData().coolantTemp > drivetrainConfig.maxTemp * CooldownStartMaxTempRatio) { _cooldown = true; randomizeTargetSpeed(); } } void SportDriveState::randomizeTargetSpeed() { const float halfSpeed = drivetrainConfig.maxSpeed * 0.5f; if (drivetrain.getDriveData().speed > halfSpeed || _cooldown) { _targetSpeed = randomize(SportDriveConstants::MinSpeed, halfSpeed); } else { _targetSpeed = randomize(halfSpeed, drivetrainConfig.maxSpeed); } } } // namespace Simulation