C

Qt Quick Ultralite Motorcycle Cluster Demo

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
#include "simulation/normaldrivestate.h"
#include "simulation/drivetrain.h"

#include "MotorCluster/TellTalesModel.h"
#include "MotorCluster/MainModel.h"
#include "MotorCluster/NavigationModel.h"

using namespace MotorCluster;

namespace {
const uint8_t StateUpdatePeriod = 100;
const uint16_t ExtraTimeToFinishState = 1000;
const uint16_t LongExtraTimeToFinishState = 3000;

MainModel &mainModel = MainModel::instance();
NavigationModel &navigationModel = NavigationModel::instance();
TellTalesModel &tellTalesModel = TellTalesModel::instance();

static Simulation::Drivetrain::Config drivetrainConfig = {17000,  // float maxRpm;
                                                          0,      // float minRpm;
                                                          14000,  // float shiftUpAtRpm;
                                                          5500,   // float shiftDownAtRpm;
                                                          207.3f, // float tireCircumference; // cm for 225/45/R18
                                                          12.f,   // float diffRatio;
                                                          7,      // int gearNum;
                                                          {10000.f, 2.97f, 2.07f, 1.43f, 1.00f, 0.84f, 0.56f}};

Simulation::Drivetrain drivetrain(drivetrainConfig);

template<class T, class U>
void updateIfChanged(T &modelElement, const U &newValue)
{
    if (modelElement.value() != newValue) {
        modelElement.setValue(newValue);
    }
}
} // namespace

namespace Simulation {

NormalDriveState::NormalDriveState(StateId nextState)
    : _step(Step_Start)
    , _timeToFinishStepWhenTargetSpeedWasMet(0)
    , _wasSpeedToFinishStepMet(true)
    , _nextState(nextState)
{}

void NormalDriveState::onEnter(const StateId &, const Layer &layer, Machine &sm)
{
    _step = Step_Start;

    drivetrain.reset();
    updateModel();

    mainModel.rpmAnimationTime.setValue(500);
    setNavigation(NavigationModel::Navigating, NavigationModel::LeftTurn, 0.55f);

    sm.requestUpdate(StateUpdatePeriod, true, layer);
}

void NormalDriveState::onLeave(const StateId &, const Layer &layer, Machine &sm)
{
    sm.dismissUpdate(layer);

    tellTalesModel.engineFailure.setValue(false);
    tellTalesModel.engineOil.setValue(false);
    tellTalesModel.battery.setValue(false);
    tellTalesModel.highBeamsActive.setValue(false);

    setNavigation(NavigationModel::Hidden, NavigationModel::RightTurn, 7.2f);
}

void NormalDriveState::onUpdate(const uint32_t tick, const Layer &layer, Machine &sm)
{
    if (_timeToFinishStepWhenTargetSpeedWasMet <= 0 && _wasSpeedToFinishStepMet) {
        moveToNextStep(layer, sm);
    } else if (_wasSpeedToFinishStepMet) {
        _timeToFinishStepWhenTargetSpeedWasMet -= tick;
    } else if (drivetrain.isTargetSpeedReached()) {
        _wasSpeedToFinishStepMet = true;
    }

    calculateDriveData(tick);

    updateModel();
}

void NormalDriveState::moveToNextStep(const Layer &layer, Machine &sm)
{
    _step = static_cast<Step>(_step + 1);

    _wasSpeedToFinishStepMet = false;
    _timeToFinishStepWhenTargetSpeedWasMet = ExtraTimeToFinishState;

    switch (_step) {
    case Step_GoingOut:
        setSpeedTarget(60.f);
        setFuelTarget(27.f, 0.02f);
        break;
    case Step_FirstCorner:
        setSpeedTarget(30.f);
        break;
    case Step_FirstStright:
        mainModel.isDayMode.setValue(mainModel.isDayMode.value() ? false : true);
        setSpeedTarget(70.f);
        break;
    case Step_SecondCorner:
        setSpeedTarget(40.f);
        _timeToFinishStepWhenTargetSpeedWasMet = LongExtraTimeToFinishState;
        mainModel.leftTurnSignal.setValue(true);
        break;
    case Step_BeginnigOfLongStright:
        setSpeedTarget(200.f);
        setFuelTarget(25.f, 0.3f);
        setNavigation(NavigationModel::Navigating, NavigationModel::RightTurn, 2.7f);
        mainModel.leftTurnSignal.setValue(false);
        break;
    case Step_OilProblems:
        setSpeedTarget(210.f);
        _timeToFinishStepWhenTargetSpeedWasMet = LongExtraTimeToFinishState;
        tellTalesModel.engineOil.setValue(true);
        break;
    case Step_SlowingDownToPetrolStation:
        setSpeedTarget(110.f);
        setFuelTarget(2.f, 0.3f);
        break;
    case Step_TurningToPetrolStation:
        setSpeedTarget(20.f);
        _timeToFinishStepWhenTargetSpeedWasMet = LongExtraTimeToFinishState;
        mainModel.rightTurnSignal.setValue(true);
        break;
    case Step_PetrolStation:
        setSpeedTarget(0.f, false);
        mainModel.rightTurnSignal.setValue(false);
        setNavigation(NavigationModel::Paused, NavigationModel::TwoStright, 0.f);
        break;
    case Step_FillingTheTank:
        drivetrain.forceNeutraulGear(true);
        setFuelTarget(80.f, 5.f);
        _timeToFinishStepWhenTargetSpeedWasMet = LongExtraTimeToFinishState;
        mainModel.rightTurnSignal.setValue(false);
        tellTalesModel.engineOil.setValue(false);
        break;

    case Step_ShowStatistics:
        mainModel.showStatistics.setValue(true);
        setNavigation(NavigationModel::Paused, NavigationModel::TwoStright, 0.f);
        _timeToFinishStepWhenTargetSpeedWasMet = LongExtraTimeToFinishState;
        break;
    case Step_TurnOffNavigation:
        setNavigation(NavigationModel::Off, NavigationModel::TwoStright, 0.f);
        _timeToFinishStepWhenTargetSpeedWasMet = LongExtraTimeToFinishState;
        break;
    case Step_TypeDestination:
        mainModel.showKeyboard.setValue(true);
        _timeToFinishStepWhenTargetSpeedWasMet = LongExtraTimeToFinishState * 2;
        break;
    case Step_backToDriveMode:
        mainModel.showKeyboard.setValue(false);
        mainModel.showStatistics.setValue(false);
        _timeToFinishStepWhenTargetSpeedWasMet = LongExtraTimeToFinishState;
        setNavigation(NavigationModel::Navigating, NavigationModel::RightTurn, 42.f);
        break;

    case Step_LeavingPetrolStation:
        drivetrain.forceNeutraulGear(false);
        setSpeedTarget(110.f);
        setFuelTarget(80.f, .0f);
        tellTalesModel.highBeamsActive.setValue(true);
        break;
    case Step_SecondStright:
        setSpeedTarget(280.f);
        break;
    case Step_BatteryProblems:
        tellTalesModel.battery.setValue(true);
        break;
    case Step_EngineProblems:
        tellTalesModel.engineFailure.setValue(true);
        break;
    case Step_EndOfSecondStright:
        mainModel.rightTurnSignal.setValue(true);
        mainModel.leftTurnSignal.setValue(true);
        setSpeedTarget(200.f);
        setFuelTarget(20.f, .15f);
        break;
    case Step_MoveToNextState:
        mainModel.rightTurnSignal.setValue(false);
        mainModel.leftTurnSignal.setValue(false);
        sm.changeState(_nextState, layer, 10);
        break;
    default:
        break;
    }
}

void NormalDriveState::updateModel()
{
    const Drivetrain::DriveData &data = drivetrain.getDriveData();

    updateIfChanged(mainModel.rpm, std::max(data.rpm - data.rpm % 1000, 1000));
    updateIfChanged(mainModel.speedValue, data.speed);
    updateIfChanged(mainModel.gear, data.gear);

    updateIfChanged(mainModel.fuelLevel, int(data.fuelLevel));
    updateIfChanged(mainModel.odoValue, int(data.odometer));
    updateIfChanged(mainModel.tripValue, int(data.tripDistance));

    updateIfChanged(navigationModel.distanceToTarget, int(data.distanceToTarget * 1000));
}

void NormalDriveState::calculateDriveData(const uint32_t tick)
{
    drivetrain.update(tick);
}

void NormalDriveState::setSpeedTarget(const int targetSpeed, const bool withRandomAccChanges)
{
    drivetrain.setSpeedTarget(targetSpeed, withRandomAccChanges);
}

void NormalDriveState::setFuelTarget(const float fuelTarget, const float fuelSpeed)
{
    drivetrain.setFuelTarget(fuelTarget, fuelSpeed);
}

void NormalDriveState::setDistanceToTarget(const float distance)
{
    drivetrain.setDistanceToTarget(distance);
}

void NormalDriveState::setNavigation(const NavigationModel::NaviState state,
                                     const NavigationModel::ArrowsState arrowsConfiguration,
                                     const float distance)
{
    updateIfChanged(navigationModel.naviState, state);
    updateIfChanged(navigationModel.arrowsState, arrowsConfiguration);
    setDistanceToTarget(distance);
}

} // namespace Simulation