C

Qt Quick Ultralite Automotive Cluster Demo

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
#include "simulation/states.h"
#include "Automotive/TellTalesModel.h"
#include "Automotive/NormalModeModel.h"
#include "Automotive/PhoneModel.h"
#include "Automotive/MediaPlayerModel.h"
#include "Automotive/SettingsMenuModel.h"
#include "Automotive/SportModeModel.h"
#include "mathutils.h"

using namespace Automotive;

namespace {
MainModel &mainModel = MainModel::instance();
TellTalesModel &tellTalesModel = TellTalesModel::instance();
NormalModeModel &normalModeModel = NormalModeModel::instance();
PhoneModel &phoneModel = PhoneModel::instance();
MediaPlayerModel &mediaPlayerModel = MediaPlayerModel::instance();
SportModeModel &sportModeModel = SportModeModel::instance();
SettingsMenuModel &settingsMenuModel = SettingsMenuModel::instance();
} // namespace

namespace IntroStateConstants {
#ifdef __ghs__
// Additional delay for Renesas to compensate HDMI initialization time
const uint32_t InitialDelay = 1500;
#else
const uint32_t InitialDelay = 0;
#endif
const uint32_t TellTalesResetParkDelay = 500;
const uint32_t TellTalesResetAirbagDelay = 300;
const uint32_t FinalDelay = 500;
} // namespace IntroStateConstants

namespace SettingsMenuStateConsts {
const unsigned SwitchOptionDelay = 1500;
const unsigned ChangeDriveModeDelay = 2000;
const unsigned SportModeDurationMin = 15000;
const unsigned SportModeDurationMax = 25000;
const unsigned DriveModeStateFinalizeDelay = 1500;
const unsigned SwitchToCarStatusDelay = 3000;
const unsigned ReDisplayMenuDelay = 0;
const unsigned MenuPresentationDelayMin = 3000;
const unsigned MenuPresentationDelayMax = 6000;
} // namespace SettingsMenuStateConsts

namespace MediaPlayerConstants {
const uint16_t ChangeTracksDurationMin = 10000;
const uint16_t ChangeTracksDurationMax = 20000;
const uint16_t InitialPlayDurationMin = 2000;
const uint16_t InitialPlayDurationMax = 7500;
const uint16_t FinalPlayDurationMin = 5000;
const uint16_t FinalPlayDurationMax = 10000;
const uint16_t ChangeTrackDelayMin = 1000;
const uint16_t ChangeTrackDelayMax = 3000;
} // namespace MediaPlayerConstants

namespace PhoneStateConstants {
const uint16_t InitialListDisplay = 3000;
const uint16_t ChangeTabToContactScrollDelay = 800;
const uint16_t StartCallDelay = 2000;
const uint16_t EndCallToStateSwitchDelay = 2800;
} // namespace PhoneStateConstants

namespace NaviStateConstants {
const uint16_t DurationMin = 7500;
const uint16_t DurationMax = 15000;
} // namespace NaviStateConstants

namespace Simulation {
MenuState::MenuState(const StateId &nextState)
    : _nextState(nextState)
{}

// -- ClusterModeState
ClusterModeState::ClusterModeState(MainModel::ClusterMode mode_)
    : mode(mode_)
{}

void ClusterModeState::onEnter(const StateId &, const Layer &, Machine &)
{
    MainModel::instance().clusterMode.setValue(mode);
}

// --- Intro state
IntroState::IntroState(IntroFinishedCallback finishedCallback)
    : ClusterModeState(MainModel::ModeNormal)
    , _finishedCallback(finishedCallback)
{}

void IntroState::onEnter(const StateId &prevState, const Layer &layer, Machine &sm)
{
    ClusterModeState::onEnter(prevState, layer, sm);

    _step = Step_Blank;

    mainModel.telltalesVisible.setValue(false);
    mainModel.clusterVisible.setValue(false);

    mainModel.clusterOpacity.setValue(0);
    mainModel.gaugesOpacity.setValue(0);

    tellTalesModel.turnLeftActive.setValue(true);
    tellTalesModel.turnRightActive.setValue(true);
    tellTalesModel.parkingLightsActive.setValue(true);
    tellTalesModel.lowBeamHeadlightsActive.setValue(true);
    tellTalesModel.parkedActive.setValue(true);
    tellTalesModel.airbagActive.setValue(true);

    tellTalesModel.qtLogoOpacity.setValue(0);
    tellTalesModel.indicatorOpacity.setValue(0);

    mainModel.speed.setValue(0);
    mainModel.rpm.setValue(0);
    mainModel.fuelLevel.setValue(0);
    mainModel.batteryLevel.setValue(0);

    mainModel.gaugesValueChangeDuration.setValue(mainModel.gaugesValueChangeDurationSlow.value());
    mainModel.introSequenceStarted.setValue(true);

    sm.requestUpdate(IntroStateConstants::InitialDelay, false, layer);
}

void IntroState::onUpdate(uint32_t, const Layer &layer, Machine &sm)
{
    using namespace IntroStateConstants;
    _step = static_cast<Step>(_step + 1);
    switch (_step) {
    case Step_ShowQtLogo:
        mainModel.telltalesVisible.setValue(true);
        tellTalesModel.qtLogoOpacity.setValue(1);
        sm.requestUpdate(int(tellTalesModel.opacityChangeDuration.value() * 1.25), false, layer);
        break;

    case Step_ShowTellTales:
        tellTalesModel.indicatorOpacity.setValue(1);
        sm.requestUpdate(int(tellTalesModel.opacityChangeDuration.value() * 1.25), false, layer);
        break;

    case Step_ShowCluster:
        mainModel.clusterVisible.setValue(true);
        mainModel.clusterOpacity.setValue(1);
        sm.requestUpdate(int(mainModel.clusterOpacityChangeDuration.value() * 0.5), false, layer);
        break;

    case Step_ShowGauges:
        mainModel.gaugesOpacity.setValue(1);
        sm.requestUpdate(mainModel.gaugesOpacityChangeDuration.value(), false, layer);
        break;

    case Step_MaxGauges:
        mainModel.speed.setValue(mainModel.maxSpeed.value());
        mainModel.rpm.setValue(mainModel.maxRpm.value());
        mainModel.fuelLevel.setValue(mainModel.initialFuelLevel.value());
        mainModel.batteryLevel.setValue(mainModel.initialBatteryLevel.value());
        sm.requestUpdate(mainModel.gaugesValueChangeDuration.value(), false, layer);
        break;

    case Step_ResetGauges:
        mainModel.speed.setValue(0);
        mainModel.rpm.setValue(0);
        sm.requestUpdate(int(mainModel.gaugesValueChangeDuration.value() * 0.1), false, layer);
        break;

    case Step_TellTalesResetLights:
        tellTalesModel.turnLeftActive.setValue(false);
        tellTalesModel.turnRightActive.setValue(false);
        tellTalesModel.parkingLightsActive.setValue(false);
        tellTalesModel.lowBeamHeadlightsActive.setValue(false);
        sm.requestUpdate(TellTalesResetParkDelay, false, layer);
        break;

    case Step_TellTalesResetPark:
        tellTalesModel.parkedActive.setValue(false);
        sm.requestUpdate(TellTalesResetAirbagDelay, false, layer);
        break;

    case Step_TellTalesResetAirbag:
        tellTalesModel.airbagActive.setValue(false);
        sm.requestUpdate(FinalDelay, false, layer);
        break;

    case Step_Done:
        if (_finishedCallback) {
            _finishedCallback(sm);
        }
        break;
    case Step_Blank:
        break;
    }
}

void IntroState::onLeave(const StateId &, const Layer &, Machine &)
{
    mainModel.gaugesValueChangeDuration.setValue(mainModel.gaugesValueChangeDurationNormal.value());
    mainModel.introSequenceCompleted.setValue(true);
}

// --- EndSate
EndState::EndState(const StateId &nextState)
    : _nextState(nextState)
{}

void EndState::onEnter(const StateId &, const Layer &layer, Machine &sm)
{
    tellTalesModel.qtLogoOpacity.setValue(0);
    tellTalesModel.indicatorOpacity.setValue(0);
    mainModel.clusterOpacity.setValue(0);
    mainModel.gaugesOpacity.setValue(0);

    sm.changeState(_nextState, layer, mainModel.clusterOpacityChangeDuration.value() * 3);
}

PhoneState::PhoneState(const StateId &nextState)
    : MenuState(nextState)
{}

void PhoneState::onEnter(const StateId &, const Layer &layer, Machine &sm)
{
    _step = Step_Blank;
    _contactsToScroll = -1;
    phoneModel.contactTabIndex.setValue(1);
    phoneModel.inCall.setValue(false);
    sm.requestUpdate(1, false, layer);
}

void PhoneState::onUpdate(uint32_t, const Layer &layer, Machine &sm)
{
    if (_step != Step_ScrollToNextContact || _contactsToScroll == -1) {
        _step = static_cast<Step>(_step + 1);
    }
    using namespace PhoneStateConstants;

    switch (_step) {
    case Step_ShowMenu:
        normalModeModel.menu.setValue(NormalModeModel::PhoneMenu);
        sm.requestUpdate(InitialListDisplay, false, layer);
        break;
    case Step_ChangeTab:
        phoneModel.contactTabIndex.setValue(randomChoice(phoneModel.phoneTabCount.value() - 1));
        sm.requestUpdate(phoneModel.contactTabSwitchDuration.value() + ChangeTabToContactScrollDelay, false, layer);
        break;
    case Step_ChooseNextContact:
        switch (phoneModel.contactTabIndex.value()) {
        case 0:
            _contactsToScroll = randomChoice(phoneModel.favContactsCount.value() - 1, 1);
            QUL_FALLTHROUGH();
        case 1:
            _contactsToScroll = randomChoice(phoneModel.recentContactsCount.value() - 1, 1);
            QUL_FALLTHROUGH();
        case 2:
            _contactsToScroll = randomChoice(phoneModel.allContactsCount.value() - 1, 1);
            QUL_FALLTHROUGH();
        default:
            break;
        }
        sm.requestUpdate(0, false, layer);
        break;
    case Step_ScrollToNextContact:
        _contactsToScroll--;
        if (_contactsToScroll >= 0) {
            phoneModel.nextContact();
            sm.requestUpdate(phoneModel.contactScrollDuration.value(), false, layer);
        } else {
            sm.requestUpdate(StartCallDelay, false, layer);
        }
        break;
    case Step_StartCall:
        phoneModel.inCall.setValue(true);
        sm.requestUpdate(randomize(phoneModel.minCallDuration.value(), phoneModel.maxCallDuration.value()),
                         false,
                         layer);
        break;
    case Step_EndCall:
        phoneModel.inCall.setValue(false);
        sm.requestUpdate(EndCallToStateSwitchDelay, false, layer);
        break;
    case Step_Done:
        sm.changeState(_nextState, layer);
        break;
    case Step_Blank:
        break;
    }
}

MediaPlayerState::MediaPlayerState(const StateId &nextState)
    : MenuState(nextState)
    , _cumulatedTime(0)
    , _changeTracksDuration(0)
{}

void MediaPlayerState::onEnter(const StateId &, const Layer &layer, Machine &sm)
{
    using namespace MediaPlayerConstants;
    nextStep(Step_Play);
    _changeTracksDuration = randomize(ChangeTracksDurationMin, ChangeTracksDurationMax);
    normalModeModel.menu.setValue(NormalModeModel::MediaPlayerMenu);
    sm.requestUpdate(1, false, layer);
}

void MediaPlayerState::onUpdate(uint32_t tick, const Layer &layer, Machine &sm)
{
    using namespace MediaPlayerConstants;
    _cumulatedTime += tick;

    switch (_step) {
    case Step_Play:
        mediaPlayerModel.play();
        nextStep(Step_ChangeTracks);
        sm.requestUpdate(randomize(InitialPlayDurationMin, InitialPlayDurationMax), false, layer);
        break;
    case Step_ChangeTracks:
        randomChoice(1u) == 0u ? mediaPlayerModel.nextSong() : mediaPlayerModel.previousSong();
        if (_cumulatedTime > _changeTracksDuration) {
            sm.requestUpdate(mediaPlayerModel.changeSongDuration.value()
                                 + randomize(FinalPlayDurationMin, FinalPlayDurationMax),
                             false,
                             layer);
            nextStep(Step_Done);
        } else {
            sm.requestUpdate(mediaPlayerModel.changeSongDuration.value()
                                 + randomize(ChangeTrackDelayMin, ChangeTrackDelayMax),
                             false,
                             layer);
        }
        break;
    case Step_Done:
        sm.changeState(_nextState, layer);
        break;
    }
}

void MediaPlayerState::nextStep(Step step)
{
    _step = step;
    _cumulatedTime = 0;
}

NaviState::NaviState(const StateId &nextState)
    : MenuState(nextState)
{}

void NaviState::onEnter(const StateId &, const Layer &layer, Machine &sm)
{
    _cumulatedTime = 0;
    _step = Step_Navi;
    normalModeModel.menu.setValue(NormalModeModel::NavigationMenu);
    sm.requestUpdate(100, false, layer);
}

void NaviState::onUpdate(uint32_t, const Layer &layer, Machine &sm)
{
    switch (_step) {
    case Step_Navi:
        sm.requestUpdate(randomize(NaviStateConstants::DurationMin, NaviStateConstants::DurationMax), false, layer);
        break;
    case Step_Done:
        sm.changeState(_nextState, layer);
        break;
    }
    _step = static_cast<Step>(_step + 1);
}

void DriveModeMenuState::onEnter(const StateId &, const Layer &layer, Machine &sm)
{
    _step = Step_Blank;
    sm.requestUpdate(0, false, layer);
}

void DriveModeMenuState::onUpdate(uint32_t, const Layer &layer, Machine &sm)
{
    using namespace SettingsMenuStateConsts;
    _step = static_cast<Step>(_step + 1);
    switch (_step) {
    case Step_DisplayDriveModeMenu:
        if (sm.getCurrentStateId(Layer_Gauges) == State_NormalDrive) {
            normalModeModel.menu.setValue(NormalModeModel::CarStatusMenu);
            settingsMenuModel.currentDriveModeSelected.setValue(SettingsMenuModel::NormalDrive);
            settingsMenuModel.currentTab.setValue(SettingsMenuModel::DriveModeTab);
        } else {
            sportModeModel.menuActive.setValue(true);
        }
        sm.requestUpdate(SwitchOptionDelay, false, layer);
        break;
    case Step_SwitchDriveModeOption:
        if (sm.getCurrentStateId(Layer_Gauges) == State_NormalDrive) {
            settingsMenuModel.currentDriveModeSelected.setValue(SettingsMenuModel::SportDrive);
        } else {
            settingsMenuModel.currentDriveModeSelected.setValue(SettingsMenuModel::NormalDrive);
        }
        sm.requestUpdate(ChangeDriveModeDelay, false, layer);
        break;
    case Step_ChangeDriveMode:
        if (sm.getCurrentStateId(Layer_Gauges) == State_NormalDrive) {
            sm.changeState(State_SportDrive, Layer_Gauges);
            sm.requestUpdate(randomize(SportModeDurationMin, SportModeDurationMax), false, layer);
        } else {
            sm.changeState(State_NormalDrive, Layer_Gauges);
            sm.requestUpdate(DriveModeStateFinalizeDelay, false, layer);
        }
        break;
    case Step_Done:
        if (sm.getCurrentStateId(Layer_Gauges) == State_NormalDrive) {
            sm.changeState(State_CarStatus, layer, SwitchToCarStatusDelay);
        } else {
            _step = Step_Blank;
            sm.requestUpdate(ReDisplayMenuDelay, false, layer);
        }
        break;
    default:
        break;
    }
}

void CarStatusMenuState::onEnter(const StateId &, const Layer &layer, Machine &sm)
{
    using namespace SettingsMenuStateConsts;
    settingsMenuModel.currentTab.setValue(SettingsMenuModel::CarStatusTab);
    sm.changeState(State_LastTrip, layer, randomize(MenuPresentationDelayMin, MenuPresentationDelayMax));
}

void LastTripMenuState::onEnter(const StateId &, const Layer &layer, Machine &sm)
{
    using namespace SettingsMenuStateConsts;
    settingsMenuModel.currentTab.setValue(SettingsMenuModel::LastTripTab);
    sm.changeState(State_MediaPlayer, layer, randomize(MenuPresentationDelayMin, MenuPresentationDelayMax));
}

void InteractiveModeState::onUpdate(uint32_t, const Layer &layer, Machine &sm)
{
    if (mainModel.clusterMode.value() == MainModel::ModeNormal) {
        switch (normalModeModel.menu.value()) {
        case NormalModeModel::MediaPlayerMenu:
            sm.changeState(State_MediaPlayer, layer);
            break;
        case NormalModeModel::PhoneMenu:
            sm.changeState(State_Phone, layer);
            break;
        case NormalModeModel::NavigationMenu:
            sm.changeState(State_Navi, layer);
            break;
        case NormalModeModel::CarStatusMenu: {
            switch (settingsMenuModel.currentTab.value()) {
            case SettingsMenuModel::DriveModeTab:
                sm.changeState(State_DriveModeMenu, layer);
                break;
            case SettingsMenuModel::CarStatusTab:
                sm.changeState(State_CarStatus, layer);
                break;
            case SettingsMenuModel::LastTripTab:
                sm.changeState(State_LastTrip, layer);
                break;
            default:
                break;
            }
        } break;
        default:
            break;
        }
    } else {
        sm.changeState(State_DriveModeMenu, layer);
    }
}

} // namespace Simulation