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