C

Qt Cluster: Rendering and Recovery from Main UI Failure

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

// This file is part of the Qt Safe Renderer module

pragma Singleton
import QtQuick
// comment ifndef QTIVIVEHICLEFUNCTIONS
//import QtIVIVehicleFunctions 1.0
import ClusterDemoData

Item {
    id: valueSource
    property real kph: 0
    property real consumeKW: 0
    property real maxConsumeKWValue: 90
    property real maxChargeKWValue: 40
    property real chargeKW: 0
    property real maxRange: 600
    property real range: (batteryLevel / 100) * maxRange

    property bool runningInDesigner: false

    property var consumption: [300, 600, 700, 800, 900, 700, 600, 300, 50, 50, -100, 50, -100, -150,
        -200, 50, 150, 200, 300, 200, 300, 200, 500, 50, -100, -100, -150, -80, 50, 300, 600, 700, 800,
        600,  700, 300, 50, 50]

    property var turnSignal
    property var currentDate: new Date()
    //property string date: currentDate.toLocaleDateString(Qt.locale("fi_FI"), "ddd d. MMM")
    //property string time: currentDate.toLocaleTimeString(Qt.locale("fi_FI"), "hh:mm")
    property string date: currentDate.toLocaleDateString(Qt.locale("en_GB"))
    property string time: currentDate.toLocaleTimeString(Qt.locale("en_GB"), "hh:mm")

    ClusterData {
        id: clusterDataSource

        onVehicleSpeedChanged: {
            kph = vehicleSpeed
            if (carId === 0 && !fastBootDemo) {
                oldSpeed.shift()
                oldSpeed.push(vehicleSpeed)
                speedChanged()
            }
        }
        property int notLeft: ~Qt.LeftArrow
        property int notRight: ~Qt.RightArrow
        onLeftTurnLightChanged: leftTurnLight ? turnSignal |= Qt.LeftArrow
                                              : turnSignal &= notLeft
        onRightTurnLightChanged: rightTurnLight ? turnSignal |= Qt.RightArrow
                                                : turnSignal &= notRight
    }

    // comment ifndef QTIVIVEHICLEFUNCTIONS
    property real latitude: clusterDataSource.latitude
    property real longitude: clusterDataSource.longitude
    property real direction: clusterDataSource.direction
    property bool lowBeam: clusterDataSource.headLight
    property int carId: clusterDataSource.carId
    property bool lightFailure: clusterDataSource.lightFailure
    property bool flatTire: clusterDataSource.flatTire

    property bool frontLeftOpen: false
    property bool frontRightOpen: false
    property bool rearLeftDoorOpen: false
    property bool rearRighDoorOpen: false
    property bool hoodOpen: false
    property bool trunkOpen: false

    property double batteryLevel: clusterDataSource.batteryPotential
    property double fuelLevel: clusterDataSource.gasLevel
    property int gear: clusterDataSource.gear
    property bool parkingBrake: clusterDataSource.brake
    // TODO: These two are hacks. View change messages might not come through CAN.
    property bool viewChange: clusterDataSource.oilTemp
    property bool rightViewChange: clusterDataSource.oilPressure

    //
    // ENABLE FOR FAST BOOT DEMO (or otherwise with no CanController)
    //
    property bool fastBootDemo: false

    // TODO: Park light used for automatic demo mode for now
    property bool automaticDemoMode: fastBootDemo ? true : clusterDataSource.parkLight

    //
    // Speed animations for fast boot demo
    //
    Timer {
        running: fastBootDemo
        interval: 4000
        onTriggered: animation.start()
    }

    Timer {
        running: fastBootDemo
        property bool turnLeft: true
        repeat: true
        interval: 5000
        onTriggered: {
            turnLeft = !turnLeft
            if (turnLeft)
                turnSignal = Qt.LeftArrow
            else
                turnSignal = Qt.RightArrow
            stopSignaling.start()
        }
    }

    Timer {
        id: stopSignaling
        running: false
        interval: 2100
        onTriggered: turnSignal = Qt.NoArrow
    }

    Behavior on fuelLevel {
        enabled: fastBootDemo
        PropertyAnimation {
            duration: 18000
        }
    }

    Behavior on batteryLevel {
        enabled: fastBootDemo
        PropertyAnimation {
            duration: 18000
        }
    }

    onFuelLevelChanged: {
        if (fastBootDemo && fuelLevel <= 5)
            fuelLevel = 100
    }

    onBatteryLevelChanged: {
        if (fastBootDemo && batteryLevel <= 5)
            batteryLevel = 100
    }

    SequentialAnimation {
        id: animation
        running: false
        loops: Animation.Infinite

        ScriptAction {
            script: {
                gear = 0
                parkingBrake = true
                consumeKW = 0
                chargeKW = 0
            }
        }
        PauseAnimation { duration: 2000 }
        ScriptAction {
            script: {
                parkingBrake = false
                gear = 1
                fuelLevel -= 10.
                batteryLevel -= 10.
            }
        }
        ParallelAnimation {
            PropertyAnimation {
                target: valueSource
                property: "kph"
                from: 0
                to: 150
                duration: 10000
            }
            PropertyAnimation {
                target: valueSource
                property: "consumeKW"
                from: 0
                to: 75
                duration: 10000
            }
        }
        ParallelAnimation {
            PropertyAnimation {
                target: valueSource
                property: "kph"
                from: 150
                to: 120
                duration: 500
            }
            PropertyAnimation {
                target: valueSource
                property: "consumeKW"
                from: 75
                to: 0
                duration: 100
            }
            PropertyAnimation {
                target: valueSource
                property: "chargeKW"
                from: 0
                to: 40
                duration: 500
            }
        }
        ParallelAnimation {
            PropertyAnimation {
                target: valueSource
                property: "kph"
                from: 120
                to: 200
                duration: 1500
            }
            PropertyAnimation {
                target: valueSource
                property: "consumeKW"
                from: 0
                to: 90
                duration: 1500
            }
            PropertyAnimation {
                target: valueSource
                property: "chargeKW"
                from: 40
                to: 0
                duration: 100
            }
        }
        ParallelAnimation {
            PropertyAnimation {
                target: valueSource
                property: "kph"
                from: 200
                to: 0
                duration: 6000
            }
            PropertyAnimation {
                target: valueSource
                property: "consumeKW"
                from: 90
                to: 0
                duration: 600
            }
            PropertyAnimation {
                target: valueSource
                property: "chargeKW"
                from: 0
                to: 40
                duration: 3000
            }
        }
    }

    property int simuRpm: fastBootDemo ? kph * 40 : kph * 150
    property double simuTemperature: fastBootDemo ? kph * .25 + 60. : kph * .5 + 50.

    // In normal Car UI mode only speed is animated based on gps data
    // In automatic demo mode rpm, turbo, consumption and engine temperature are based on speed
    property int rpm: automaticDemoMode ? simuRpm : clusterDataSource.rpm
    property double engineTemperature: automaticDemoMode ? simuTemperature
                                                         : clusterDataSource.engineTemp

    property int totalDistance: 42300
    property int kmSinceCharge: 8
    property int avRangePerCharge: 425
    property int energyPerKm: 324

    property real totalDistanceSince: 0.

    property string gearString: {
        var g
        if (gear === 0 || gear < -1)
            return "N"
        else if (gear === -1)
            return "R"
        else if (carId === 1) //sports car
            return gear.toString()
        else
            return "D"
    }

    Timer {
        id: timeTimer
        interval: 15000
        repeat: true
        running: true
        onTriggered: {
            currentDate = new Date()
            //date = currentDate.toLocaleDateString(Qt.locale("fi_FI"), "ddd d. MMM")
            //time = currentDate.toLocaleTimeString(Qt.locale("fi_FI"), "hh:mm")
            date = currentDate.toLocaleDateString(Qt.locale("en_GB"))
            time = currentDate.toLocaleTimeString(Qt.locale("en_GB"), "hh:mm")
            // Approximate total distance based on current speed
            totalDistanceSince += kph / 240. // = km / 15 min
            if (totalDistanceSince > 1.) {
                var totalInt = Math.floor(totalDistanceSince)
                totalDistance += totalInt
                kmSinceCharge += totalInt
                totalDistanceSince -= totalInt
            }
        }
    }

    Timer {
        id: backCutTimer
        interval: 1000
        repeat: true
        running: true
        onTriggered: {
            backCut = kph
        }
    }

    property real temperature: 0.6
    property alias musicTimer: musicTimer
    property real backCut: 0 //For needle tail gradient
    property real musicElapsed: 0

    Timer {
        id: musicTimer
        interval: 2000
        running: false
        repeat: true
        onTriggered: {
            if (musicElapsed < 100)
            musicElapsed++
            else
            musicElapsed = 0
        }
    }

    Behavior on kph {
        enabled: !fastBootDemo
        PropertyAnimation { duration: 2000 }
    }

    //
    // For electric car KwGauge animation
    //
    property var oldSpeed: [0, 0, 0]
    signal speedChanged

    SequentialAnimation {
        id: reduceSpeedAnim
        running: (carId === 0 && !fastBootDemo)
        property alias chargeTo: charge.to
        NumberAnimation {
            target: valueSource
            property: "consumeKW"
            duration: 600
            to: 0
        }
        NumberAnimation {
            id: charge
            target: valueSource
            property: "chargeKW"
            duration: 600
        }
    }

    SequentialAnimation {
        id: addSpeedAnim
        running: (carId === 0 && !fastBootDemo)
        property alias consumeTo: consume.to
        NumberAnimation {
            target: valueSource
            property: "chargeKW"
            duration: 600
            to: 0

        }
        NumberAnimation {
            id: consume
            target: valueSource
            property: "consumeKW"
            duration: 600
        }
    }

    onSpeedChanged: {
        var speedChange = oldSpeed[1] - oldSpeed[0]
        if (speedChange > 2) {
            //"adding speed"
            var newKW = Math.min(maxConsumeKWValue * 0.8, 10 * speedChange)
            addSpeedAnim.consumeTo = newKW
            addSpeedAnim.restart()
        } else if (speedChange < -2) {
            //"reducing speed"
            newKW = Math.min(maxChargeKWValue * 0.8, 2 * Math.abs(speedChange))
            reduceSpeedAnim.chargeTo = newKW
            reduceSpeedAnim.restart()
        } else if (Math.abs(speedChange) >= 0 && oldSpeed[1] !== 0) {
            //Speed just about the same but still moving
            addSpeedAnim.consumeTo = Math.min(maxConsumeKWValue * (kph / 100),
                                              maxConsumeKWValue * 0.5)
            addSpeedAnim.restart()
        }
        if (kph <= 0.1) {
            addSpeedAnim.consumeTo = 0
            reduceSpeedAnim.chargeTo = 0
            addSpeedAnim.restart()
            reduceSpeedAnim.restart()
        }
    }
}