C

Qt Quick Ultralite Automotive Cluster Demo

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

namespace {
const float cmPerMinuteToKmhRatio = 0.0006;
const uint32_t crusingDownshiftDelay = 1500;
} // namespace

namespace Simulation {
Drivetrain::Drivetrain(const Config &config)
    : _config(config)
{
    assert(_config.gearNum <= Config::MAX_GEARS);
    reset();
}

void Drivetrain::update(uint32_t tick, float acceleration)
{
    const float prevRpm = updateRpm(tick, acceleration);
    updateGearShift(tick, prevRpm, acceleration);
    updateSpeed();
    applySpeedLimit();
    updateOdo(tick);
    updateRange();
    updateBattery(tick, acceleration);
    updateFuelLevel();
    updateCoolantTemp(tick, acceleration);
}

void Drivetrain::applySpeedLimit()
{
    if (_data.speed > _config.maxSpeed) {
        _data.speed = _config.maxSpeed;
        _data.rpm = getRpm(_data.speed, _data.gear);
    }
}

void Drivetrain::updateCruiseControl(uint32_t tick, float targetSpeed)
{
    const float maxSpeed = _config.maxSpeed;
    targetSpeed = std::min(targetSpeed, maxSpeed);

    float acceleration = 0;
    if ((int) targetSpeed != (int) _data.speed) {
        const int dir = targetSpeed - _data.speed > 0 ? 1 : -1;
        const float speedFactor = std::abs(targetSpeed - _data.speed) / _config.maxSpeed;
        if (dir > 0) {
            acceleration = 0.05f + std::sqrt(std::min(std::max(speedFactor * 2, 0.f), 1.f)) * 0.95f;
        } else {
            acceleration = -(0.05f + std::min(std::max(speedFactor * 4, 0.f), 1.f) * 0.95f);
        }
    }
    update(tick, acceleration);
}

float Drivetrain::updateRpm(uint32_t tick, float acceleration)
{
    const float currentRatio = (_config.gearRatios[_data.gear]);
    const float speedFactor = _data.speed / _config.maxSpeed;
    const float accVariation = acceleration > 0 ? 0.02f + (1 - speedFactor) * 0.98f
                                                : 2 * (0.2 + speedFactor * speedFactor * speedFactor * 0.8f);

    const float prevValue = _data.rpm;
    _data.rpm += 2 * tick * acceleration * currentRatio * accVariation;
    _data.rpm = std::min(std::max(_data.rpm, _config.minRpm), _config.maxRpm);
    return prevValue;
}

void Drivetrain::updateGearShift(uint32_t tick, float prevRpm, float acceleration)
{
    const float shiftUpAtRpm = acceleration > 0.25f
                                   ? _config.shiftUpAtRpm
                                   : _config.shiftDownAtRpm + (_config.shiftUpAtRpm - _config.shiftDownAtRpm) * 0.5f;
    if (_data.rpm >= shiftUpAtRpm && _data.gear < (_config.gearNum - 1)) {
        shiftGear(1);
    } else if (_data.rpm <= _config.shiftDownAtRpm && _data.gear > 0) {
        shiftGear(-1);
    } else if (std::abs((int) prevRpm - (int) _data.rpm) < 10) {
        _constSpeedTime += tick;
        if (_constSpeedTime > crusingDownshiftDelay && _data.gear < (_config.gearNum - 1)) {
            // Consider shifting up while crusing
            const float possibleRpm = getRpm(_data.speed, _data.gear + 1);
            if (possibleRpm > _config.shiftDownAtRpm) {
                shiftGear(1);
            }
            _constSpeedTime = 0;
        }
    } else {
        _constSpeedTime = 0;
    }
}

void Drivetrain::shiftGear(int delta)
{
    _data.gear += delta;
    assert(_data.gear >= 0 && _data.gear < _config.gearNum);
    _data.rpm = getRpm(_data.speed, _data.gear);
}

void Drivetrain::updateSpeed()
{
    _data.speed = getSpeed(_data.rpm, _data.gear);
}

void Drivetrain::updateOdo(uint32_t tick)
{
    _data.odo += _data.speed * tick / 3600000.f;
}

void Drivetrain::updateRange()
{
    // Just flip to max range when hitting 0
    _data.range = _config.maxRange - (int) _data.odo % (int) _config.maxRange;
}

void Drivetrain::updateBattery(uint32_t tick, float acceleration)
{
    float battery = _data.battery;
    const float ratio = acceleration > 0 ? _config.batteryDrainRatio : _config.batteryFillRatio;
    battery -= acceleration * tick * ratio;
    _data.battery = std::min(std::max(battery, 0.f), 1.f);
}

void Drivetrain::updateFuelLevel()
{
    _data.fuel = _data.range / _config.maxRange;
}

void Drivetrain::updateCoolantTemp(uint32_t tick, float acceleration)
{
    if (_data.coolantTemp < _config.optimalTemp
        || (_data.rpm > _config.maxRpm * 0.75f && _data.coolantTemp < _config.maxTemp)) {
        const float accHeatFactor = acceleration > 0 ? (0.1f + acceleration * 0.2f) : 0.1f;
        _data.coolantTemp += _config.maxTemp * tick * _config.coolantHeatRatio * accHeatFactor;
    } else if (_data.coolantTemp > _config.optimalTemp) {
        const float rpmCoolFactor = acceleration <= 0.15 ? 1 - _data.rpm / _config.maxRpm : 0;
        _data.coolantTemp -= _config.maxTemp * tick * _config.coolantCooldownRatio * rpmCoolFactor;
    }
}

void Drivetrain::reset()
{
    _data.speed = 0;
    _data.rpm = 0;
    _data.gear = 0;
    _data.coolantTemp = 0;
    resetOdo();
    resetBattery();
}

void Drivetrain::resetOdo(float value)
{
    _data.odo = value;
}

void Drivetrain::resetBattery(float value)
{
    _data.battery = value;
}

float Drivetrain::getSpeed(float rpm, int gear) const
{
    if (gear < 0 || gear >= _config.gearNum) {
        assert(!"Wrong gear!");
        return 0;
    }
    return (_config.tireCircumference * rpm) / (_config.gearRatios[gear] * _config.diffRatio) * cmPerMinuteToKmhRatio;
}

float Drivetrain::getRpm(float speed, int gear) const
{
    if (gear < 0 || gear >= _config.gearNum) {
        assert(!"Wrong gear!");
        return 0;
    }
    return (speed * _config.gearRatios[gear] * _config.diffRatio) / (_config.tireCircumference * cmPerMinuteToKmhRatio);
}
} // namespace Simulation