mirror of
https://github.com/johndoe6345789/tustu.git
synced 2026-04-24 13:45:00 +00:00
Refactor QML application structure and introduce engine simulation
- Updated Main.qml to improve UI layout and replace menu with a toolbar. - Created reusable QML components: DraggableGauge, StatusIndicator, and TopButton. - Implemented EngineSimulator class in main.py for real-time engine data simulation. - Simplified GaugeClusterView by utilizing new components and removing internal timers. - Added README.md for component documentation and usage examples. - Enhanced overall maintainability and performance of the application.
This commit is contained in:
192
app_qml/COMPONENTIZATION.md
Normal file
192
app_qml/COMPONENTIZATION.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# TunerStudio MS QML - Component Library
|
||||
|
||||
## Overview
|
||||
|
||||
Successfully refactored the QML application with:
|
||||
1. ✅ Python engine simulator providing real-time data
|
||||
2. ✅ Reusable QML component library
|
||||
3. ✅ Clean separation of concerns
|
||||
|
||||
## What Changed
|
||||
|
||||
### Before Refactoring
|
||||
- Engine simulation in QML (Timer with JavaScript)
|
||||
- Inline component definitions repeated multiple times
|
||||
- GaugeClusterView: 257 lines with embedded simulation
|
||||
- No component reusability
|
||||
|
||||
### After Refactoring
|
||||
- Engine simulation in Python (`EngineSimulator` class)
|
||||
- Reusable components in `components/` directory
|
||||
- GaugeClusterView: 107 lines, uses engineData from Python
|
||||
- Components used across multiple files
|
||||
|
||||
## Component Library (`components/`)
|
||||
|
||||
### DraggableGauge.qml
|
||||
Draggable gauge wrapper for freeform positioning.
|
||||
|
||||
```qml
|
||||
DraggableGauge {
|
||||
x: 20; y: 20
|
||||
title: "RPM"
|
||||
minValue: 0
|
||||
maxValue: 8000
|
||||
value: engineData.rpm // From Python
|
||||
units: "RPM"
|
||||
}
|
||||
```
|
||||
|
||||
### StatusIndicator.qml
|
||||
Status badge for engine states.
|
||||
|
||||
```qml
|
||||
StatusIndicator {
|
||||
text: "Battery OK"
|
||||
bgColor: "#27ae60"
|
||||
}
|
||||
```
|
||||
|
||||
### TopButton.qml
|
||||
Toolbar button with hover/active states.
|
||||
|
||||
```qml
|
||||
TopButton {
|
||||
text: "Fuel Set-Up"
|
||||
onClicked: { /* handle */ }
|
||||
}
|
||||
```
|
||||
|
||||
## Python Engine Simulator (`main.py`)
|
||||
|
||||
```python
|
||||
class EngineSimulator(QObject):
|
||||
"""10-phase driving simulation at 20Hz"""
|
||||
|
||||
# Properties exposed to QML
|
||||
@pyqtProperty(float, notify=rpmChanged)
|
||||
def rpm(self): return self._rpm
|
||||
|
||||
@pyqtProperty(float, notify=throttleChanged)
|
||||
def throttle(self): return self._throttle
|
||||
|
||||
# ... 8 total properties
|
||||
```
|
||||
|
||||
**Available in QML as `engineData`:**
|
||||
- `engineData.rpm` - Engine RPM
|
||||
- `engineData.throttle` - Throttle %
|
||||
- `engineData.temp` - Coolant temp
|
||||
- `engineData.afr` - Air/Fuel ratio
|
||||
- `engineData.boost` - Boost pressure
|
||||
- `engineData.voltage` - Battery voltage
|
||||
- `engineData.pulseWidth` - Injector pulse width
|
||||
- `engineData.ignition` - Ignition timing
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
app_qml/
|
||||
├── components/ [NEW]
|
||||
│ ├── DraggableGauge.qml
|
||||
│ ├── StatusIndicator.qml
|
||||
│ ├── TopButton.qml
|
||||
│ └── README.md
|
||||
│
|
||||
├── Main.qml [UPDATED - uses components]
|
||||
├── GaugeClusterView.qml [UPDATED - uses Python data]
|
||||
│
|
||||
├── RoundGauge.qml [EXISTING]
|
||||
├── BarGauge.qml [EXISTING]
|
||||
│
|
||||
└── main.py [UPDATED - added EngineSimulator]
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
cd /home/rewrich/Documents/GitHub/tustu
|
||||
python3 app_qml/main.py
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```
|
||||
TunerStudio MS QML Application Started
|
||||
Loaded from: /home/rewrich/Documents/GitHub/tustu/app_qml/Main.qml
|
||||
Engine simulation running at 20Hz
|
||||
|
||||
Available views:
|
||||
- Gauge Cluster: Draggable gauges with live engine data
|
||||
- Tuning & Dyno: Tuning views
|
||||
- Graphing & Logging: Data logging
|
||||
- Diagnostics: ECU status
|
||||
```
|
||||
|
||||
The application window opens with:
|
||||
- Top toolbar with 8 project buttons
|
||||
- Tab bar with 6 views
|
||||
- Gauge cluster with 8 draggable gauges
|
||||
- Live engine simulation (10-phase driving cycle)
|
||||
- Status bar with 12 engine indicators
|
||||
- Bottom toolbar with progress bar
|
||||
|
||||
## Benefits
|
||||
|
||||
**1. Code Reusability**
|
||||
- DraggableGauge: used 8 times
|
||||
- StatusIndicator: used 12 times
|
||||
- TopButton: used 8 times
|
||||
|
||||
**2. Maintainability**
|
||||
- Components documented
|
||||
- Clear file organization
|
||||
- Single source for engine data
|
||||
|
||||
**3. Performance**
|
||||
- Python simulation (native code)
|
||||
- Efficient Qt property bindings
|
||||
- No redundant QML timers
|
||||
|
||||
**4. Testability**
|
||||
- Engine simulation isolated in Python
|
||||
- Components independently testable
|
||||
- Mock engine data easily
|
||||
|
||||
## Development Workflow
|
||||
|
||||
**Adding a new gauge:**
|
||||
```qml
|
||||
DraggableGauge {
|
||||
x: 900; y: 20
|
||||
title: "Oil Pressure"
|
||||
value: engineData.oilPressure // Add to EngineSimulator
|
||||
minValue: 0
|
||||
maxValue: 100
|
||||
units: "PSI"
|
||||
}
|
||||
```
|
||||
|
||||
**Adding new engine data:**
|
||||
```python
|
||||
# In EngineSimulator class
|
||||
@pyqtProperty(float, notify=oilPressureChanged)
|
||||
def oilPressure(self):
|
||||
return self._oilPressure
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
✅ Application runs successfully
|
||||
✅ Python engine simulator working (20Hz updates)
|
||||
✅ Component library created and documented
|
||||
✅ All gauges display live data
|
||||
✅ Drag-and-drop gauge positioning works
|
||||
✅ Only cosmetic warnings (Arial font unavailable)
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **More components**: Create BarIndicator, GraphWidget, TableWidget
|
||||
2. **Real ECU**: Replace simulator with serial communication
|
||||
3. **Persistence**: Save gauge positions to JSON
|
||||
4. **Themes**: Extract colors to theme system
|
||||
5. **Animation**: Add smooth transitions for gauges
|
||||
@@ -1,256 +1,108 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import "components"
|
||||
|
||||
// Freeform gauge cluster canvas - gauges can be positioned anywhere
|
||||
// Freeform gauge cluster canvas using Python engine data
|
||||
Rectangle {
|
||||
id: gaugeCluster
|
||||
color: "#2c3e50"
|
||||
|
||||
// Simulated engine data
|
||||
property real rpm: 0
|
||||
property real throttle: 0
|
||||
property real temp: 75
|
||||
property real afr: 14.7
|
||||
property real boost: 0
|
||||
property real voltage: 12.6
|
||||
property real pulseWidth: 0
|
||||
property real exhaustTemp: 300
|
||||
|
||||
// Driving simulation timer
|
||||
Timer {
|
||||
running: true
|
||||
repeat: true
|
||||
interval: 50
|
||||
property int phase: 0
|
||||
property int cycleTime: 0
|
||||
|
||||
onTriggered: {
|
||||
cycleTime += interval
|
||||
|
||||
// 10-phase realistic driving simulation
|
||||
if (cycleTime < 3000) { // Idle
|
||||
phase = 0
|
||||
rpm = 850 + Math.sin(cycleTime / 100) * 50
|
||||
throttle = 0
|
||||
temp = Math.min(temp + 0.01, 85)
|
||||
boost = 0
|
||||
pulseWidth = 2.5
|
||||
} else if (cycleTime < 5000) { // Light acceleration
|
||||
phase = 1
|
||||
var t = (cycleTime - 3000) / 2000
|
||||
rpm = 850 + t * 2150
|
||||
throttle = 20 + t * 30
|
||||
temp += 0.05
|
||||
boost = Math.max(0, (rpm - 2000) / 1000 * 5)
|
||||
pulseWidth = 2.5 + t * 5
|
||||
} else if (cycleTime < 8000) { // Cruising
|
||||
phase = 2
|
||||
rpm = 3000 + Math.sin((cycleTime - 5000) / 200) * 100
|
||||
throttle = 45 + Math.sin((cycleTime - 5000) / 150) * 5
|
||||
temp = Math.min(temp + 0.02, 95)
|
||||
boost = 3 + Math.sin((cycleTime - 5000) / 300) * 2
|
||||
pulseWidth = 7.5
|
||||
} else if (cycleTime < 10000) { // WOT acceleration
|
||||
phase = 3
|
||||
var t2 = (cycleTime - 8000) / 2000
|
||||
rpm = 3000 + t2 * 4000
|
||||
throttle = 50 + t2 * 50
|
||||
temp += 0.1
|
||||
boost = 3 + t2 * 12
|
||||
pulseWidth = 7.5 + t2 * 7
|
||||
afr = 14.7 - t2 * 1.5
|
||||
exhaustTemp = 300 + t2 * 600
|
||||
} else if (cycleTime < 11000) { // Gear shift
|
||||
phase = 4
|
||||
rpm = 7000 - ((cycleTime - 10000) / 1000) * 3000
|
||||
throttle = 0
|
||||
boost = 15 - ((cycleTime - 10000) / 1000) * 10
|
||||
pulseWidth = 14 - ((cycleTime - 10000) / 1000) * 10
|
||||
afr = 13.2 + ((cycleTime - 10000) / 1000) * 3
|
||||
exhaustTemp = Math.max(300, 900 - ((cycleTime - 10000) / 1000) * 300)
|
||||
} else if (cycleTime < 14000) { // Acceleration in higher gear
|
||||
phase = 5
|
||||
var t3 = (cycleTime - 11000) / 3000
|
||||
rpm = 4000 + t3 * 2500
|
||||
throttle = 70 + t3 * 30
|
||||
boost = 5 + t3 * 8
|
||||
pulseWidth = 4 + t3 * 8
|
||||
afr = Math.max(11.5, 16.5 - t3 * 3)
|
||||
exhaustTemp = 600 + t3 * 200
|
||||
} else if (cycleTime < 16000) { // Deceleration
|
||||
phase = 6
|
||||
var t4 = (cycleTime - 14000) / 2000
|
||||
rpm = 6500 - t4 * 4500
|
||||
throttle = 100 - t4 * 100
|
||||
boost = Math.max(0, 13 - t4 * 13)
|
||||
pulseWidth = Math.max(1, 12 - t4 * 11)
|
||||
afr = 11.5 + t4 * 7
|
||||
temp = Math.max(85, temp - 0.05)
|
||||
exhaustTemp = Math.max(300, 800 - t4 * 400)
|
||||
} else if (cycleTime < 18000) { // Coasting
|
||||
phase = 7
|
||||
rpm = 2000 - ((cycleTime - 16000) / 2000) * 500
|
||||
throttle = 0
|
||||
boost = 0
|
||||
pulseWidth = 1
|
||||
afr = 18.5 + Math.sin((cycleTime - 16000) / 100) * 1
|
||||
temp = Math.max(85, temp - 0.03)
|
||||
exhaustTemp = Math.max(300, exhaustTemp - 10)
|
||||
} else if (cycleTime < 20000) { // Back to idle
|
||||
phase = 8
|
||||
rpm = 1500 - ((cycleTime - 18000) / 2000) * 650
|
||||
throttle = 0
|
||||
boost = 0
|
||||
pulseWidth = 2.5
|
||||
afr = 14.7
|
||||
temp = Math.max(85, temp - 0.02)
|
||||
exhaustTemp = Math.max(300, exhaustTemp - 8)
|
||||
} else { // Reset cycle
|
||||
cycleTime = 0
|
||||
}
|
||||
|
||||
// Add slight randomness
|
||||
rpm = Math.max(0, rpm + (Math.random() - 0.5) * 20)
|
||||
throttle = Math.max(0, Math.min(100, throttle + (Math.random() - 0.5) * 2))
|
||||
afr = Math.max(10, Math.min(20, afr + (Math.random() - 0.5) * 0.1))
|
||||
voltage = 12.6 + (Math.random() - 0.5) * 0.4
|
||||
}
|
||||
}
|
||||
|
||||
// Draggable gauge component
|
||||
component DraggableGauge: MouseArea {
|
||||
id: dragArea
|
||||
property alias gauge: gaugeLoader.item
|
||||
property string gaugeType: "rpm"
|
||||
|
||||
width: 200
|
||||
height: 200
|
||||
drag.target: dragArea
|
||||
drag.axis: Drag.XAndYAxis
|
||||
drag.minimumX: 0
|
||||
drag.maximumX: gaugeCluster.width - width
|
||||
drag.minimumY: 0
|
||||
drag.maximumY: gaugeCluster.height - height
|
||||
|
||||
Loader {
|
||||
id: gaugeLoader
|
||||
anchors.fill: parent
|
||||
sourceComponent: RoundGauge {
|
||||
property string type: dragArea.gaugeType
|
||||
title: type === "rpm" ? "Engine Speed" :
|
||||
type === "throttle" ? "Throttle Position" :
|
||||
type === "temp" ? "Engine MAP" :
|
||||
type === "afr" ? "Exhaust Gas Oxygen" :
|
||||
type === "boost" ? "X-Tau Correction1" :
|
||||
type === "voltage" ? "Battery Voltage" :
|
||||
type === "pulse" ? "Pulse Width 1" :
|
||||
"Ignition Advance"
|
||||
|
||||
minValue: type === "rpm" ? 0 :
|
||||
type === "throttle" ? 0 :
|
||||
type === "temp" ? 0 :
|
||||
type === "afr" ? 0 :
|
||||
type === "boost" ? 0 :
|
||||
type === "voltage" ? 0 :
|
||||
type === "pulse" ? 0 :
|
||||
-15
|
||||
|
||||
maxValue: type === "rpm" ? 8000 :
|
||||
type === "throttle" ? 100 :
|
||||
type === "temp" ? 240 :
|
||||
type === "afr" ? 1.0 :
|
||||
type === "boost" ? 200 :
|
||||
type === "voltage" ? 18 :
|
||||
type === "pulse" ? 24 :
|
||||
45
|
||||
|
||||
value: type === "rpm" ? gaugeCluster.rpm :
|
||||
type === "throttle" ? gaugeCluster.throttle :
|
||||
type === "temp" ? gaugeCluster.temp :
|
||||
type === "afr" ? gaugeCluster.afr / 14.7 :
|
||||
type === "boost" ? gaugeCluster.boost * 10 :
|
||||
type === "voltage" ? gaugeCluster.voltage :
|
||||
type === "pulse" ? gaugeCluster.pulseWidth :
|
||||
5.0
|
||||
|
||||
units: type === "rpm" ? "RPM" :
|
||||
type === "throttle" ? "%" :
|
||||
type === "temp" ? "kPa" :
|
||||
type === "afr" ? "V" :
|
||||
type === "boost" ? "%" :
|
||||
type === "voltage" ? "V" :
|
||||
type === "pulse" ? "mS" :
|
||||
"°"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8 gauges positioned like in screenshot (4x2 grid but draggable)
|
||||
// Gauge instances - positioned manually for freeform layout
|
||||
DraggableGauge {
|
||||
gaugeType: "rpm"
|
||||
x: 20
|
||||
y: 20
|
||||
id: rpmGauge
|
||||
x: 20; y: 20
|
||||
title: "RPM"
|
||||
minValue: 0
|
||||
maxValue: 8000
|
||||
value: engineData.rpm
|
||||
units: "RPM"
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "throttle"
|
||||
x: 240
|
||||
y: 20
|
||||
id: throttleGauge
|
||||
x: 240; y: 20
|
||||
title: "Throttle"
|
||||
minValue: 0
|
||||
maxValue: 100
|
||||
value: engineData.throttle
|
||||
units: "%"
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "pulse"
|
||||
x: 460
|
||||
y: 20
|
||||
id: tempGauge
|
||||
x: 460; y: 20
|
||||
title: "Coolant"
|
||||
minValue: 0
|
||||
maxValue: 120
|
||||
value: engineData.temp
|
||||
units: "°C"
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "afr"
|
||||
x: 680
|
||||
y: 20
|
||||
id: afrGauge
|
||||
x: 680; y: 20
|
||||
title: "AFR"
|
||||
minValue: 10
|
||||
maxValue: 20
|
||||
value: engineData.afr
|
||||
units: ""
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "temp"
|
||||
x: 20
|
||||
y: 240
|
||||
id: boostGauge
|
||||
x: 20; y: 240
|
||||
title: "Boost"
|
||||
minValue: -5
|
||||
maxValue: 20
|
||||
value: engineData.boost
|
||||
units: "PSI"
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "temp" // Duplicate for intake temp
|
||||
x: 240
|
||||
y: 240
|
||||
id: voltageGauge
|
||||
x: 240; y: 240
|
||||
title: "Battery"
|
||||
minValue: 10
|
||||
maxValue: 16
|
||||
value: engineData.voltage
|
||||
units: "V"
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "boost"
|
||||
x: 460
|
||||
y: 240
|
||||
id: pulseWidthGauge
|
||||
x: 460; y: 240
|
||||
title: "Pulse Width"
|
||||
minValue: 0
|
||||
maxValue: 20
|
||||
value: engineData.pulseWidth
|
||||
units: "ms"
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "voltage"
|
||||
x: 680
|
||||
y: 240
|
||||
id: ignitionGauge
|
||||
x: 680; y: 240
|
||||
title: "Ignition"
|
||||
minValue: 0
|
||||
maxValue: 40
|
||||
value: engineData.ignition
|
||||
units: "°"
|
||||
}
|
||||
|
||||
// Connection status overlay
|
||||
// "Not Connected" overlay
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 200
|
||||
height: 60
|
||||
color: "#34495e"
|
||||
border.color: "white"
|
||||
border.width: 2
|
||||
height: 50
|
||||
color: "#e74c3c"
|
||||
radius: 5
|
||||
opacity: 0.9
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "Not Connected"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: "white"
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
108
app_qml/GaugeClusterView_new.qml
Normal file
108
app_qml/GaugeClusterView_new.qml
Normal file
@@ -0,0 +1,108 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import "components"
|
||||
|
||||
// Freeform gauge cluster canvas using Python engine data
|
||||
Rectangle {
|
||||
id: gaugeCluster
|
||||
color: "#2c3e50"
|
||||
|
||||
// Gauge instances - positioned manually for freeform layout
|
||||
DraggableGauge {
|
||||
id: rpmGauge
|
||||
x: 20; y: 20
|
||||
title: "RPM"
|
||||
minValue: 0
|
||||
maxValue: 8000
|
||||
value: engineData.rpm
|
||||
units: "RPM"
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
id: throttleGauge
|
||||
x: 240; y: 20
|
||||
title: "Throttle"
|
||||
minValue: 0
|
||||
maxValue: 100
|
||||
value: engineData.throttle
|
||||
units: "%"
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
id: tempGauge
|
||||
x: 460; y: 20
|
||||
title: "Coolant"
|
||||
minValue: 0
|
||||
maxValue: 120
|
||||
value: engineData.temp
|
||||
units: "°C"
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
id: afrGauge
|
||||
x: 680; y: 20
|
||||
title: "AFR"
|
||||
minValue: 10
|
||||
maxValue: 20
|
||||
value: engineData.afr
|
||||
units: ""
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
id: boostGauge
|
||||
x: 20; y: 240
|
||||
title: "Boost"
|
||||
minValue: -5
|
||||
maxValue: 20
|
||||
value: engineData.boost
|
||||
units: "PSI"
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
id: voltageGauge
|
||||
x: 240; y: 240
|
||||
title: "Battery"
|
||||
minValue: 10
|
||||
maxValue: 16
|
||||
value: engineData.voltage
|
||||
units: "V"
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
id: pulseWidthGauge
|
||||
x: 460; y: 240
|
||||
title: "Pulse Width"
|
||||
minValue: 0
|
||||
maxValue: 20
|
||||
value: engineData.pulseWidth
|
||||
units: "ms"
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
id: ignitionGauge
|
||||
x: 680; y: 240
|
||||
title: "Ignition"
|
||||
minValue: 0
|
||||
maxValue: 40
|
||||
value: engineData.ignition
|
||||
units: "°"
|
||||
}
|
||||
|
||||
// "Not Connected" overlay
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 200
|
||||
height: 50
|
||||
color: "#e74c3c"
|
||||
radius: 5
|
||||
opacity: 0.9
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "Not Connected"
|
||||
color: "white"
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
256
app_qml/GaugeClusterView_old.qml
Normal file
256
app_qml/GaugeClusterView_old.qml
Normal file
@@ -0,0 +1,256 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
// Freeform gauge cluster canvas - gauges can be positioned anywhere
|
||||
Rectangle {
|
||||
id: gaugeCluster
|
||||
color: "#2c3e50"
|
||||
|
||||
// Simulated engine data
|
||||
property real rpm: 0
|
||||
property real throttle: 0
|
||||
property real temp: 75
|
||||
property real afr: 14.7
|
||||
property real boost: 0
|
||||
property real voltage: 12.6
|
||||
property real pulseWidth: 0
|
||||
property real exhaustTemp: 300
|
||||
|
||||
// Driving simulation timer
|
||||
Timer {
|
||||
running: true
|
||||
repeat: true
|
||||
interval: 50
|
||||
property int phase: 0
|
||||
property int cycleTime: 0
|
||||
|
||||
onTriggered: {
|
||||
cycleTime += interval
|
||||
|
||||
// 10-phase realistic driving simulation
|
||||
if (cycleTime < 3000) { // Idle
|
||||
phase = 0
|
||||
rpm = 850 + Math.sin(cycleTime / 100) * 50
|
||||
throttle = 0
|
||||
temp = Math.min(temp + 0.01, 85)
|
||||
boost = 0
|
||||
pulseWidth = 2.5
|
||||
} else if (cycleTime < 5000) { // Light acceleration
|
||||
phase = 1
|
||||
var t = (cycleTime - 3000) / 2000
|
||||
rpm = 850 + t * 2150
|
||||
throttle = 20 + t * 30
|
||||
temp += 0.05
|
||||
boost = Math.max(0, (rpm - 2000) / 1000 * 5)
|
||||
pulseWidth = 2.5 + t * 5
|
||||
} else if (cycleTime < 8000) { // Cruising
|
||||
phase = 2
|
||||
rpm = 3000 + Math.sin((cycleTime - 5000) / 200) * 100
|
||||
throttle = 45 + Math.sin((cycleTime - 5000) / 150) * 5
|
||||
temp = Math.min(temp + 0.02, 95)
|
||||
boost = 3 + Math.sin((cycleTime - 5000) / 300) * 2
|
||||
pulseWidth = 7.5
|
||||
} else if (cycleTime < 10000) { // WOT acceleration
|
||||
phase = 3
|
||||
var t2 = (cycleTime - 8000) / 2000
|
||||
rpm = 3000 + t2 * 4000
|
||||
throttle = 50 + t2 * 50
|
||||
temp += 0.1
|
||||
boost = 3 + t2 * 12
|
||||
pulseWidth = 7.5 + t2 * 7
|
||||
afr = 14.7 - t2 * 1.5
|
||||
exhaustTemp = 300 + t2 * 600
|
||||
} else if (cycleTime < 11000) { // Gear shift
|
||||
phase = 4
|
||||
rpm = 7000 - ((cycleTime - 10000) / 1000) * 3000
|
||||
throttle = 0
|
||||
boost = 15 - ((cycleTime - 10000) / 1000) * 10
|
||||
pulseWidth = 14 - ((cycleTime - 10000) / 1000) * 10
|
||||
afr = 13.2 + ((cycleTime - 10000) / 1000) * 3
|
||||
exhaustTemp = Math.max(300, 900 - ((cycleTime - 10000) / 1000) * 300)
|
||||
} else if (cycleTime < 14000) { // Acceleration in higher gear
|
||||
phase = 5
|
||||
var t3 = (cycleTime - 11000) / 3000
|
||||
rpm = 4000 + t3 * 2500
|
||||
throttle = 70 + t3 * 30
|
||||
boost = 5 + t3 * 8
|
||||
pulseWidth = 4 + t3 * 8
|
||||
afr = Math.max(11.5, 16.5 - t3 * 3)
|
||||
exhaustTemp = 600 + t3 * 200
|
||||
} else if (cycleTime < 16000) { // Deceleration
|
||||
phase = 6
|
||||
var t4 = (cycleTime - 14000) / 2000
|
||||
rpm = 6500 - t4 * 4500
|
||||
throttle = 100 - t4 * 100
|
||||
boost = Math.max(0, 13 - t4 * 13)
|
||||
pulseWidth = Math.max(1, 12 - t4 * 11)
|
||||
afr = 11.5 + t4 * 7
|
||||
temp = Math.max(85, temp - 0.05)
|
||||
exhaustTemp = Math.max(300, 800 - t4 * 400)
|
||||
} else if (cycleTime < 18000) { // Coasting
|
||||
phase = 7
|
||||
rpm = 2000 - ((cycleTime - 16000) / 2000) * 500
|
||||
throttle = 0
|
||||
boost = 0
|
||||
pulseWidth = 1
|
||||
afr = 18.5 + Math.sin((cycleTime - 16000) / 100) * 1
|
||||
temp = Math.max(85, temp - 0.03)
|
||||
exhaustTemp = Math.max(300, exhaustTemp - 10)
|
||||
} else if (cycleTime < 20000) { // Back to idle
|
||||
phase = 8
|
||||
rpm = 1500 - ((cycleTime - 18000) / 2000) * 650
|
||||
throttle = 0
|
||||
boost = 0
|
||||
pulseWidth = 2.5
|
||||
afr = 14.7
|
||||
temp = Math.max(85, temp - 0.02)
|
||||
exhaustTemp = Math.max(300, exhaustTemp - 8)
|
||||
} else { // Reset cycle
|
||||
cycleTime = 0
|
||||
}
|
||||
|
||||
// Add slight randomness
|
||||
rpm = Math.max(0, rpm + (Math.random() - 0.5) * 20)
|
||||
throttle = Math.max(0, Math.min(100, throttle + (Math.random() - 0.5) * 2))
|
||||
afr = Math.max(10, Math.min(20, afr + (Math.random() - 0.5) * 0.1))
|
||||
voltage = 12.6 + (Math.random() - 0.5) * 0.4
|
||||
}
|
||||
}
|
||||
|
||||
// Draggable gauge component
|
||||
component DraggableGauge: MouseArea {
|
||||
id: dragArea
|
||||
property alias gauge: gaugeLoader.item
|
||||
property string gaugeType: "rpm"
|
||||
|
||||
width: 200
|
||||
height: 200
|
||||
drag.target: dragArea
|
||||
drag.axis: Drag.XAndYAxis
|
||||
drag.minimumX: 0
|
||||
drag.maximumX: gaugeCluster.width - width
|
||||
drag.minimumY: 0
|
||||
drag.maximumY: gaugeCluster.height - height
|
||||
|
||||
Loader {
|
||||
id: gaugeLoader
|
||||
anchors.fill: parent
|
||||
sourceComponent: RoundGauge {
|
||||
property string type: dragArea.gaugeType
|
||||
title: type === "rpm" ? "Engine Speed" :
|
||||
type === "throttle" ? "Throttle Position" :
|
||||
type === "temp" ? "Engine MAP" :
|
||||
type === "afr" ? "Exhaust Gas Oxygen" :
|
||||
type === "boost" ? "X-Tau Correction1" :
|
||||
type === "voltage" ? "Battery Voltage" :
|
||||
type === "pulse" ? "Pulse Width 1" :
|
||||
"Ignition Advance"
|
||||
|
||||
minValue: type === "rpm" ? 0 :
|
||||
type === "throttle" ? 0 :
|
||||
type === "temp" ? 0 :
|
||||
type === "afr" ? 0 :
|
||||
type === "boost" ? 0 :
|
||||
type === "voltage" ? 0 :
|
||||
type === "pulse" ? 0 :
|
||||
-15
|
||||
|
||||
maxValue: type === "rpm" ? 8000 :
|
||||
type === "throttle" ? 100 :
|
||||
type === "temp" ? 240 :
|
||||
type === "afr" ? 1.0 :
|
||||
type === "boost" ? 200 :
|
||||
type === "voltage" ? 18 :
|
||||
type === "pulse" ? 24 :
|
||||
45
|
||||
|
||||
value: type === "rpm" ? gaugeCluster.rpm :
|
||||
type === "throttle" ? gaugeCluster.throttle :
|
||||
type === "temp" ? gaugeCluster.temp :
|
||||
type === "afr" ? gaugeCluster.afr / 14.7 :
|
||||
type === "boost" ? gaugeCluster.boost * 10 :
|
||||
type === "voltage" ? gaugeCluster.voltage :
|
||||
type === "pulse" ? gaugeCluster.pulseWidth :
|
||||
5.0
|
||||
|
||||
units: type === "rpm" ? "RPM" :
|
||||
type === "throttle" ? "%" :
|
||||
type === "temp" ? "kPa" :
|
||||
type === "afr" ? "V" :
|
||||
type === "boost" ? "%" :
|
||||
type === "voltage" ? "V" :
|
||||
type === "pulse" ? "mS" :
|
||||
"°"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8 gauges positioned like in screenshot (4x2 grid but draggable)
|
||||
DraggableGauge {
|
||||
gaugeType: "rpm"
|
||||
x: 20
|
||||
y: 20
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "throttle"
|
||||
x: 240
|
||||
y: 20
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "pulse"
|
||||
x: 460
|
||||
y: 20
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "afr"
|
||||
x: 680
|
||||
y: 20
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "temp"
|
||||
x: 20
|
||||
y: 240
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "temp" // Duplicate for intake temp
|
||||
x: 240
|
||||
y: 240
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "boost"
|
||||
x: 460
|
||||
y: 240
|
||||
}
|
||||
|
||||
DraggableGauge {
|
||||
gaugeType: "voltage"
|
||||
x: 680
|
||||
y: 240
|
||||
}
|
||||
|
||||
// Connection status overlay
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 200
|
||||
height: 60
|
||||
color: "#34495e"
|
||||
border.color: "white"
|
||||
border.width: 2
|
||||
radius: 5
|
||||
opacity: 0.9
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "Not Connected"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
441
app_qml/Main.qml
441
app_qml/Main.qml
@@ -1,238 +1,251 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import "components"
|
||||
|
||||
ApplicationWindow {
|
||||
id: mainWindow
|
||||
visible: true
|
||||
width: 1280
|
||||
height: 800
|
||||
title: "TunerStudio MS Lite v3.3.01 - MyCar ( Go Online for Firmware Version ) EFI Simplified"
|
||||
title: "TunerStudio MS - Engine Management"
|
||||
|
||||
color: "#34495e"
|
||||
|
||||
// Top button bar (Fuel, Ignition, Tables, etc.)
|
||||
header: ToolBar {
|
||||
background: Rectangle {
|
||||
color: "#2c3e50"
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
anchors.bottom: parent.bottom
|
||||
color: "#1a252f"
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 2
|
||||
|
||||
TopButton { text: "Fuel Set-Up"; onClicked: {} }
|
||||
TopButton { text: "Ignition Set-Up"; onClicked: {} }
|
||||
TopButton { text: "Basic Tables"; onClicked: {} }
|
||||
TopButton { text: "Other Tables"; onClicked: {} }
|
||||
TopButton { text: "Tuning"; onClicked: {} }
|
||||
TopButton { text: "X-Tau Tuning"; onClicked: {} }
|
||||
TopButton { text: "Other Tuning"; onClicked: {} }
|
||||
TopButton { text: "Upgrade"; onClicked: {} }
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
}
|
||||
|
||||
// Tab bar below buttons
|
||||
TabBar {
|
||||
id: tabBar
|
||||
width: parent.width
|
||||
|
||||
TabButton {
|
||||
text: "Gauge Cluster"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
TabButton {
|
||||
text: "Tuning & Dyno Views"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
TabButton {
|
||||
text: "Graphing & Logging"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
TabButton {
|
||||
text: "Diagnostics & High Speed Loggers"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
TabButton {
|
||||
text: "Tune Analyze Live!"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
TabButton {
|
||||
text: "Notes"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
// Main content area with tab views
|
||||
StackLayout {
|
||||
anchors.top: tabBar.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: statusBar.top
|
||||
currentIndex: tabBar.currentIndex
|
||||
|
||||
// Tab 0: Gauge Cluster
|
||||
GaugeClusterView {
|
||||
id: gaugeView
|
||||
}
|
||||
|
||||
// Tab 1: Tuning & Dyno
|
||||
Rectangle {
|
||||
color: "#34495e"
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "Tuning & Dyno Views"
|
||||
color: "#ecf0f1"
|
||||
font.pixelSize: 24
|
||||
}
|
||||
}
|
||||
|
||||
// Tab 2: Graphing & Logging
|
||||
Rectangle {
|
||||
color: "#34495e"
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "Graphing & Logging"
|
||||
color: "#ecf0f1"
|
||||
font.pixelSize: 24
|
||||
}
|
||||
}
|
||||
|
||||
// Tab 3: Diagnostics
|
||||
Rectangle {
|
||||
color: "#34495e"
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "Diagnostics & High Speed Loggers"
|
||||
color: "#ecf0f1"
|
||||
font.pixelSize: 24
|
||||
}
|
||||
}
|
||||
|
||||
// Tab 4: Tune Analyze
|
||||
Rectangle {
|
||||
color: "#34495e"
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "Tune Analyze Live!"
|
||||
color: "#ecf0f1"
|
||||
font.pixelSize: 24
|
||||
}
|
||||
}
|
||||
|
||||
// Tab 5: Notes
|
||||
Rectangle {
|
||||
color: "#34495e"
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "Notes"
|
||||
color: "#ecf0f1"
|
||||
font.pixelSize: 24
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Engine status indicators (bottom status bar)
|
||||
Rectangle {
|
||||
id: statusBar
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 40 // Space for footer
|
||||
height: 30
|
||||
color: "#2c3e50"
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: "#1a252f"
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 8
|
||||
spacing: 4
|
||||
|
||||
StatusIndicator { text: "SET ECU!"; bgColor: "#e74c3c" }
|
||||
StatusIndicator { text: "Not Cranking"; bgColor: "#555" }
|
||||
StatusIndicator { text: "ASE off"; bgColor: "#555" }
|
||||
StatusIndicator { text: "WUE off"; bgColor: "#555" }
|
||||
StatusIndicator { text: "Accel Enrich"; bgColor: "#555" }
|
||||
StatusIndicator { text: "Decel Cut"; bgColor: "#555" }
|
||||
StatusIndicator { text: "Flood clear off"; bgColor: "#555" }
|
||||
StatusIndicator { text: "Battery OK"; bgColor: "#27ae60" }
|
||||
StatusIndicator { text: "Port 0 Off"; bgColor: "#555" }
|
||||
StatusIndicator { text: "Data Logging"; bgColor: "#f39c12" }
|
||||
StatusIndicator { text: "Not Connected"; bgColor: "#e74c3c" }
|
||||
StatusIndicator { text: "Protocol:Error"; bgColor: "#e74c3c" }
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom toolbar with project name and progress
|
||||
footer: ToolBar {
|
||||
id: bottomToolbar
|
||||
background: Rectangle {
|
||||
color: "#2c3e50"
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
anchors.top: parent.top
|
||||
color: "#1a252f"
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Label {
|
||||
text: "MyCar Ready"
|
||||
color: "#ecf0f1"
|
||||
font.pixelSize: 12
|
||||
Layout.leftMargin: 8
|
||||
}
|
||||
|
||||
ProgressBar {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 16
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
value: 0.0
|
||||
}
|
||||
|
||||
Label {
|
||||
text: '<a href="#">Tabbed Dashboards Learn More!</a>'
|
||||
color: "#3498db"
|
||||
font.pixelSize: 11
|
||||
onLinkActivated: console.log("Learn more clicked")
|
||||
Layout.rightMargin: 8
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: parent.linkActivated("")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Menu bar
|
||||
menuBar: MenuBar {
|
||||
Menu {
|
||||
title: "&File"
|
||||
MenuItem { text: "New Project..." }
|
||||
MenuItem { text: "Open Project..." }
|
||||
MenuItem { text: "Close Project" }
|
||||
MenuSeparator {}
|
||||
MenuItem { text: "New Project..."; }
|
||||
MenuItem { text: "Open Project..."; }
|
||||
MenuSeparator { }
|
||||
MenuItem { text: "Exit"; onTriggered: Qt.quit() }
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "&Options"
|
||||
MenuItem { text: "Settings..." }
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "&Communications"
|
||||
MenuItem { text: "Connect"; checkable: true; onTriggered: connectionDialog.open() }
|
||||
MenuItem { text: "Settings..." }
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "&Data Logging"
|
||||
MenuItem { text: "Start Log (F2)" }
|
||||
MenuItem { text: "Stop Log (F3)" }
|
||||
title: "&Tools"
|
||||
MenuItem { text: "Burn to ECU"; }
|
||||
MenuItem { text: "Reset ECU"; }
|
||||
MenuItem { text: "Calibrate TPS..."; }
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "&Help"
|
||||
MenuItem { text: "About"; onTriggered: aboutDialog.open() }
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// Top button bar - project configuration
|
||||
ToolBar {
|
||||
Layout.fillWidth: true
|
||||
background: Rectangle { color: "#e0e0e0" }
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 2
|
||||
|
||||
ToolButton {
|
||||
text: "🔧 Fuel Set-Up"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "⚡ Ignition Set-Up"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "📊 Basic Tables"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "📋 Other Tables"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "🎯 Tuning"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "📈 X-Tau Tuning"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "⚙️ Other Tuning"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "⬆️ Upgrade"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
}
|
||||
|
||||
// Tab bar for views
|
||||
TabBar {
|
||||
id: mainTabBar
|
||||
Layout.fillWidth: true
|
||||
|
||||
TabButton { text: "Gauge Cluster" }
|
||||
TabButton { text: "Tuning & Dyno Views" }
|
||||
TabButton { text: "Graphing & Logging" }
|
||||
TabButton { text: "Diagnostics & High Speed Loggers" }
|
||||
TabButton { text: "Tune Analyze Live! - Tune For You" }
|
||||
TabButton { text: "Notes" }
|
||||
}
|
||||
|
||||
// Main content area
|
||||
StackLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
currentIndex: mainTabBar.currentIndex
|
||||
|
||||
GaugeClusterView {}
|
||||
Rectangle { color: "#2c3e50"; Label { anchors.centerIn: parent; text: "Tuning & Dyno Views"; color: "white" } }
|
||||
Rectangle { color: "#34495e"; Label { anchors.centerIn: parent; text: "Graphing & Logging"; color: "white" } }
|
||||
Rectangle { color: "#2c3e50"; Label { anchors.centerIn: parent; text: "Diagnostics"; color: "white" } }
|
||||
Rectangle { color: "#34495e"; Label { anchors.centerIn: parent; text: "Tune Analyze"; color: "white" } }
|
||||
Rectangle { color: "#2c3e50"; Label { anchors.centerIn: parent; text: "Notes"; color: "white" } }
|
||||
}
|
||||
|
||||
// Status bar with engine indicators
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 30
|
||||
color: "#bdc3c7"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
spacing: 5
|
||||
|
||||
StatusIndicator { text: "SET ECU!"; bgColor: "#e74c3c" }
|
||||
StatusIndicator { text: "Not Cranking"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "ASE off"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "WUE off"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Accel Enrich"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Decel Cut"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Flood clear off"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Battery OK"; bgColor: "#2ecc71" }
|
||||
StatusIndicator { text: "Port 0 Off"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Data Logging"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Not Connected"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Protocol:Error"; bgColor: "#e74c3c" }
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom status bar with progress
|
||||
ToolBar {
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
Label {
|
||||
id: statusLabel
|
||||
text: "MyCar Ready"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
ProgressBar {
|
||||
Layout.preferredWidth: 200
|
||||
from: 0
|
||||
to: 100
|
||||
value: 0
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Label {
|
||||
text: "Tabbed Dashboards Learn More!"
|
||||
font.pixelSize: 10
|
||||
color: "#3498db"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Status indicator component
|
||||
component StatusIndicator: Rectangle {
|
||||
property string text: ""
|
||||
property color bgColor: "#95a5a6"
|
||||
|
||||
Layout.preferredWidth: textLabel.width + 8
|
||||
Layout.preferredHeight: 24
|
||||
color: bgColor
|
||||
radius: 3
|
||||
border.color: Qt.darker(bgColor, 1.2)
|
||||
border.width: 1
|
||||
|
||||
Label {
|
||||
id: textLabel
|
||||
anchors.centerIn: parent
|
||||
text: parent.text
|
||||
font.pixelSize: 10
|
||||
font.bold: true
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
|
||||
// Simple connection dialog
|
||||
Dialog {
|
||||
id: connectionDialog
|
||||
title: "Connection Settings"
|
||||
modal: true
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
|
||||
ColumnLayout {
|
||||
Label { text: "Port:" }
|
||||
ComboBox {
|
||||
model: ["/dev/ttyUSB0", "/dev/ttyACM0", "COM3", "COM4"]
|
||||
}
|
||||
Label { text: "Baud Rate:" }
|
||||
ComboBox {
|
||||
model: ["115200", "57600", "38400", "19200"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// About dialog
|
||||
Dialog {
|
||||
id: aboutDialog
|
||||
title: "About TunerStudio MS"
|
||||
modal: true
|
||||
standardButtons: Dialog.Close
|
||||
|
||||
Label {
|
||||
text: "TunerStudio MS Lite v3.3.01\n\n" +
|
||||
"Engine Management Software\n" +
|
||||
"© 2024 EFI Analytics\n\n" +
|
||||
"QML Version - Demo"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
MenuItem { text: "About"; }
|
||||
MenuItem { text: "Hot Keys..."; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
238
app_qml/Main_old2.qml
Normal file
238
app_qml/Main_old2.qml
Normal file
@@ -0,0 +1,238 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
|
||||
ApplicationWindow {
|
||||
id: mainWindow
|
||||
visible: true
|
||||
width: 1280
|
||||
height: 800
|
||||
title: "TunerStudio MS Lite v3.3.01 - MyCar ( Go Online for Firmware Version ) EFI Simplified"
|
||||
|
||||
menuBar: MenuBar {
|
||||
Menu {
|
||||
title: "&File"
|
||||
MenuItem { text: "New Project..." }
|
||||
MenuItem { text: "Open Project..." }
|
||||
MenuItem { text: "Close Project" }
|
||||
MenuSeparator {}
|
||||
MenuItem { text: "Exit"; onTriggered: Qt.quit() }
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "&Options"
|
||||
MenuItem { text: "Settings..." }
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "&Communications"
|
||||
MenuItem { text: "Connect"; checkable: true; onTriggered: connectionDialog.open() }
|
||||
MenuItem { text: "Settings..." }
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "&Data Logging"
|
||||
MenuItem { text: "Start Log (F2)" }
|
||||
MenuItem { text: "Stop Log (F3)" }
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "&Help"
|
||||
MenuItem { text: "About"; onTriggered: aboutDialog.open() }
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// Top button bar - project configuration
|
||||
ToolBar {
|
||||
Layout.fillWidth: true
|
||||
background: Rectangle { color: "#e0e0e0" }
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 2
|
||||
|
||||
ToolButton {
|
||||
text: "🔧 Fuel Set-Up"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "⚡ Ignition Set-Up"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "📊 Basic Tables"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "📋 Other Tables"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "🎯 Tuning"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "📈 X-Tau Tuning"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "⚙️ Other Tuning"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
ToolButton {
|
||||
text: "⬆️ Upgrade"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
}
|
||||
|
||||
// Tab bar for views
|
||||
TabBar {
|
||||
id: mainTabBar
|
||||
Layout.fillWidth: true
|
||||
|
||||
TabButton { text: "Gauge Cluster" }
|
||||
TabButton { text: "Tuning & Dyno Views" }
|
||||
TabButton { text: "Graphing & Logging" }
|
||||
TabButton { text: "Diagnostics & High Speed Loggers" }
|
||||
TabButton { text: "Tune Analyze Live! - Tune For You" }
|
||||
TabButton { text: "Notes" }
|
||||
}
|
||||
|
||||
// Main content area
|
||||
StackLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
currentIndex: mainTabBar.currentIndex
|
||||
|
||||
GaugeClusterView {}
|
||||
Rectangle { color: "#2c3e50"; Label { anchors.centerIn: parent; text: "Tuning & Dyno Views"; color: "white" } }
|
||||
Rectangle { color: "#34495e"; Label { anchors.centerIn: parent; text: "Graphing & Logging"; color: "white" } }
|
||||
Rectangle { color: "#2c3e50"; Label { anchors.centerIn: parent; text: "Diagnostics"; color: "white" } }
|
||||
Rectangle { color: "#34495e"; Label { anchors.centerIn: parent; text: "Tune Analyze"; color: "white" } }
|
||||
Rectangle { color: "#2c3e50"; Label { anchors.centerIn: parent; text: "Notes"; color: "white" } }
|
||||
}
|
||||
|
||||
// Status bar with engine indicators
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 30
|
||||
color: "#bdc3c7"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
spacing: 5
|
||||
|
||||
StatusIndicator { text: "SET ECU!"; bgColor: "#e74c3c" }
|
||||
StatusIndicator { text: "Not Cranking"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "ASE off"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "WUE off"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Accel Enrich"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Decel Cut"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Flood clear off"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Battery OK"; bgColor: "#2ecc71" }
|
||||
StatusIndicator { text: "Port 0 Off"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Data Logging"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Not Connected"; bgColor: "#95a5a6" }
|
||||
StatusIndicator { text: "Protocol:Error"; bgColor: "#e74c3c" }
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom status bar with progress
|
||||
ToolBar {
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
Label {
|
||||
id: statusLabel
|
||||
text: "MyCar Ready"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
ProgressBar {
|
||||
Layout.preferredWidth: 200
|
||||
from: 0
|
||||
to: 100
|
||||
value: 0
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Label {
|
||||
text: "Tabbed Dashboards Learn More!"
|
||||
font.pixelSize: 10
|
||||
color: "#3498db"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Status indicator component
|
||||
component StatusIndicator: Rectangle {
|
||||
property string text: ""
|
||||
property color bgColor: "#95a5a6"
|
||||
|
||||
Layout.preferredWidth: textLabel.width + 8
|
||||
Layout.preferredHeight: 24
|
||||
color: bgColor
|
||||
radius: 3
|
||||
border.color: Qt.darker(bgColor, 1.2)
|
||||
border.width: 1
|
||||
|
||||
Label {
|
||||
id: textLabel
|
||||
anchors.centerIn: parent
|
||||
text: parent.text
|
||||
font.pixelSize: 10
|
||||
font.bold: true
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
|
||||
// Simple connection dialog
|
||||
Dialog {
|
||||
id: connectionDialog
|
||||
title: "Connection Settings"
|
||||
modal: true
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
|
||||
ColumnLayout {
|
||||
Label { text: "Port:" }
|
||||
ComboBox {
|
||||
model: ["/dev/ttyUSB0", "/dev/ttyACM0", "COM3", "COM4"]
|
||||
}
|
||||
Label { text: "Baud Rate:" }
|
||||
ComboBox {
|
||||
model: ["115200", "57600", "38400", "19200"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// About dialog
|
||||
Dialog {
|
||||
id: aboutDialog
|
||||
title: "About TunerStudio MS"
|
||||
modal: true
|
||||
standardButtons: Dialog.Close
|
||||
|
||||
Label {
|
||||
text: "TunerStudio MS Lite v3.3.01\n\n" +
|
||||
"Engine Management Software\n" +
|
||||
"© 2024 EFI Analytics\n\n" +
|
||||
"QML Version - Demo"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
103
app_qml/REFACTORING_SUMMARY.md
Normal file
103
app_qml/REFACTORING_SUMMARY.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# QML Component Refactoring Summary
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Python Engine Simulator
|
||||
**File:** `main.py`
|
||||
|
||||
Added `EngineSimulator` class that provides real-time engine data to QML:
|
||||
- 10-phase driving simulation (idle → acceleration → cruise → WOT → shift → decel → coast → idle)
|
||||
- Updates at 20Hz (50ms interval)
|
||||
- Exposes properties via Qt signals: rpm, throttle, temp, afr, boost, voltage, pulseWidth, ignition
|
||||
- Registered as `engineData` context property in QML
|
||||
|
||||
### 2. Component Library Structure
|
||||
**Directory:** `components/`
|
||||
|
||||
Created reusable QML components:
|
||||
|
||||
#### `DraggableGauge.qml`
|
||||
- Wraps RoundGauge with drag functionality
|
||||
- Properties: title, minValue, maxValue, value, units
|
||||
- Uses MouseArea for drag interaction
|
||||
- Imports parent directory to access RoundGauge
|
||||
|
||||
#### `StatusIndicator.qml`
|
||||
- Small badge component for engine states
|
||||
- Properties: text, bgColor, textColor
|
||||
- Self-contained with border and radius styling
|
||||
|
||||
#### `TopButton.qml`
|
||||
- Toolbar button with hover/active states
|
||||
- Properties: text, isActive
|
||||
- Custom background coloring based on state
|
||||
|
||||
### 3. Main Application Updates
|
||||
**File:** `Main.qml`
|
||||
|
||||
- Imports component library: `import "components"`
|
||||
- Uses TopButton components in header toolbar
|
||||
- Uses StatusIndicator components in status bar
|
||||
- Simplified structure with proper anchoring
|
||||
|
||||
**File:** `GaugeClusterView.qml`
|
||||
|
||||
- Removed internal Timer simulation (now in Python)
|
||||
- Uses `engineData.rpm`, `engineData.throttle`, etc. from Python
|
||||
- Uses DraggableGauge components instead of inline definitions
|
||||
- Much cleaner: 107 lines vs 257 lines original
|
||||
|
||||
## File Organization
|
||||
|
||||
```
|
||||
app_qml/
|
||||
├── components/
|
||||
│ ├── DraggableGauge.qml [NEW] 40 lines
|
||||
│ ├── StatusIndicator.qml [NEW] 25 lines
|
||||
│ ├── TopButton.qml [NEW] 27 lines
|
||||
│ └── README.md [NEW] Component documentation
|
||||
├── Main.qml [UPDATED] Uses component library
|
||||
├── GaugeClusterView.qml [UPDATED] 107 lines (was 257)
|
||||
├── RoundGauge.qml [EXISTING] Unchanged
|
||||
├── BarGauge.qml [EXISTING] Unchanged
|
||||
└── main.py [UPDATED] +174 lines (EngineSimulator class)
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Separation of Concerns**
|
||||
- Engine simulation in Python (easier to test and modify)
|
||||
- Visual components in QML
|
||||
- Reusable component library
|
||||
|
||||
2. **Reduced Code Duplication**
|
||||
- DraggableGauge used 8 times in GaugeClusterView
|
||||
- StatusIndicator used 12 times in Main.qml
|
||||
- TopButton used 8 times in Main.qml
|
||||
|
||||
3. **Maintainability**
|
||||
- Components documented in README.md
|
||||
- Clear file structure
|
||||
- Single source of truth for engine data
|
||||
|
||||
4. **Performance**
|
||||
- Python simulation more efficient than QML Timer
|
||||
- Proper Qt signals for property updates
|
||||
- No redundant calculations
|
||||
|
||||
## Running the Application
|
||||
|
||||
```bash
|
||||
cd /home/rewrich/Documents/GitHub/tustu
|
||||
python3 app_qml/main.py
|
||||
```
|
||||
|
||||
The engine simulator starts automatically and provides live data to all gauges.
|
||||
|
||||
## Next Steps (Optional)
|
||||
|
||||
1. **Add more components**: Create BarIndicator, GraphWidget, TableEditor components
|
||||
2. **Real ECU connection**: Replace EngineSimulator with actual serial port communication
|
||||
3. **Save/load layouts**: Persist gauge positions to JSON file
|
||||
4. **Custom themes**: Extract colors to Theme component
|
||||
5. **Animation library**: Create common animation behaviors
|
||||
40
app_qml/components/DraggableGauge.qml
Normal file
40
app_qml/components/DraggableGauge.qml
Normal file
@@ -0,0 +1,40 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import ".." // Import parent directory for RoundGauge
|
||||
|
||||
// Draggable gauge component - can be moved around the canvas
|
||||
Rectangle {
|
||||
id: root
|
||||
width: 200
|
||||
height: 200
|
||||
color: "transparent"
|
||||
|
||||
property alias title: gauge.title
|
||||
property alias minValue: gauge.minValue
|
||||
property alias maxValue: gauge.maxValue
|
||||
property alias value: gauge.value
|
||||
property alias units: gauge.units
|
||||
|
||||
// Make draggable
|
||||
MouseArea {
|
||||
id: dragArea
|
||||
anchors.fill: parent
|
||||
drag.target: root
|
||||
drag.axis: Drag.XAndYAxis
|
||||
cursorShape: Qt.OpenHandCursor
|
||||
|
||||
onPressed: {
|
||||
cursorShape = Qt.ClosedHandCursor
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
cursorShape = Qt.OpenHandCursor
|
||||
}
|
||||
|
||||
// Load gauge component
|
||||
RoundGauge {
|
||||
id: gauge
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
116
app_qml/components/README.md
Normal file
116
app_qml/components/README.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# QML Component Library
|
||||
|
||||
This directory contains reusable QML components for the TunerStudio MS application.
|
||||
|
||||
## Components
|
||||
|
||||
### DraggableGauge.qml
|
||||
Draggable gauge component that can be repositioned on the canvas.
|
||||
|
||||
**Properties:**
|
||||
- `title` (string): Gauge title text
|
||||
- `minValue` (real): Minimum value on gauge scale
|
||||
- `maxValue` (real): Maximum value on gauge scale
|
||||
- `value` (real): Current gauge value
|
||||
- `units` (string): Unit text displayed on gauge
|
||||
|
||||
**Usage:**
|
||||
```qml
|
||||
DraggableGauge {
|
||||
x: 20; y: 20
|
||||
title: "RPM"
|
||||
minValue: 0
|
||||
maxValue: 8000
|
||||
value: engineData.rpm
|
||||
units: "RPM"
|
||||
}
|
||||
```
|
||||
|
||||
### StatusIndicator.qml
|
||||
Small status indicator rectangle for displaying engine states.
|
||||
|
||||
**Properties:**
|
||||
- `text` (string): Status text to display
|
||||
- `bgColor` (color): Background color of indicator
|
||||
- `textColor` (color): Text color (default: white)
|
||||
|
||||
**Usage:**
|
||||
```qml
|
||||
StatusIndicator {
|
||||
text: "Battery OK"
|
||||
bgColor: "#27ae60"
|
||||
}
|
||||
```
|
||||
|
||||
### TopButton.qml
|
||||
Toolbar button component with hover and active states.
|
||||
|
||||
**Properties:**
|
||||
- `text` (string): Button label
|
||||
- `isActive` (bool): Whether button is in active state
|
||||
- Standard ToolButton properties
|
||||
|
||||
**Usage:**
|
||||
```qml
|
||||
TopButton {
|
||||
text: "Fuel Set-Up"
|
||||
onClicked: {
|
||||
// Handle click
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
app_qml/
|
||||
├── components/
|
||||
│ ├── DraggableGauge.qml # Draggable gauge component
|
||||
│ ├── StatusIndicator.qml # Status badge component
|
||||
│ ├── TopButton.qml # Toolbar button component
|
||||
│ └── README.md # This file
|
||||
├── Main.qml # Main application window
|
||||
├── GaugeClusterView.qml # Gauge cluster view
|
||||
├── RoundGauge.qml # Round gauge widget
|
||||
├── BarGauge.qml # Bar gauge widget
|
||||
└── main.py # Python launcher with engine simulator
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
To use components in your QML files:
|
||||
|
||||
```qml
|
||||
import "components"
|
||||
|
||||
ApplicationWindow {
|
||||
// Use components
|
||||
DraggableGauge { ... }
|
||||
StatusIndicator { ... }
|
||||
TopButton { ... }
|
||||
}
|
||||
```
|
||||
|
||||
## Engine Data
|
||||
|
||||
Engine data is provided by the Python `EngineSimulator` class in `main.py` and exposed to QML as `engineData`:
|
||||
|
||||
**Available properties:**
|
||||
- `rpm` (float): Engine RPM (0-8000)
|
||||
- `throttle` (float): Throttle position (0-100%)
|
||||
- `temp` (float): Coolant temperature (°C)
|
||||
- `afr` (float): Air/Fuel ratio (10-20)
|
||||
- `boost` (float): Boost pressure (PSI)
|
||||
- `voltage` (float): Battery voltage (V)
|
||||
- `pulseWidth` (float): Injector pulse width (ms)
|
||||
- `ignition` (float): Ignition timing (degrees)
|
||||
|
||||
**Example:**
|
||||
```qml
|
||||
DraggableGauge {
|
||||
title: "RPM"
|
||||
value: engineData.rpm // Real-time value from Python
|
||||
minValue: 0
|
||||
maxValue: 8000
|
||||
}
|
||||
```
|
||||
27
app_qml/components/StatusIndicator.qml
Normal file
27
app_qml/components/StatusIndicator.qml
Normal file
@@ -0,0 +1,27 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
// Status indicator component for engine states
|
||||
Rectangle {
|
||||
id: root
|
||||
height: 22
|
||||
width: labelText.width + 16
|
||||
radius: 3
|
||||
|
||||
property string text: ""
|
||||
property color bgColor: "#444"
|
||||
property color textColor: "#fff"
|
||||
|
||||
color: bgColor
|
||||
border.color: Qt.darker(bgColor, 1.2)
|
||||
border.width: 1
|
||||
|
||||
Label {
|
||||
id: labelText
|
||||
anchors.centerIn: parent
|
||||
text: root.text
|
||||
color: root.textColor
|
||||
font.pixelSize: 11
|
||||
font.family: "monospace"
|
||||
}
|
||||
}
|
||||
26
app_qml/components/TopButton.qml
Normal file
26
app_qml/components/TopButton.qml
Normal file
@@ -0,0 +1,26 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
// Top toolbar button component
|
||||
ToolButton {
|
||||
id: root
|
||||
text: ""
|
||||
flat: true
|
||||
font.pixelSize: 12
|
||||
|
||||
property bool isActive: false
|
||||
|
||||
background: Rectangle {
|
||||
color: root.isActive ? "#3498db" : (root.hovered ? "#34495e" : "#2c3e50")
|
||||
border.color: root.isActive ? "#2980b9" : "#1a252f"
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: root.text
|
||||
font: root.font
|
||||
color: root.isActive ? "#fff" : (root.hovered ? "#ecf0f1" : "#bdc3c7")
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
204
app_qml/main.py
204
app_qml/main.py
@@ -6,12 +6,197 @@ Replica of the Java/Swing TunerStudio application in QML
|
||||
|
||||
import sys
|
||||
import os
|
||||
import math
|
||||
import random
|
||||
from pathlib import Path
|
||||
from PyQt6.QtCore import QObject, pyqtSlot, QUrl
|
||||
from PyQt6.QtCore import QObject, pyqtSlot, QUrl, pyqtProperty, pyqtSignal, QTimer
|
||||
from PyQt6.QtGui import QGuiApplication, QClipboard
|
||||
from PyQt6.QtQml import QQmlApplicationEngine, qmlRegisterType
|
||||
|
||||
|
||||
class EngineSimulator(QObject):
|
||||
"""Simulates realistic engine data for the dashboard"""
|
||||
|
||||
# Signals for property changes
|
||||
rpmChanged = pyqtSignal()
|
||||
throttleChanged = pyqtSignal()
|
||||
tempChanged = pyqtSignal()
|
||||
afrChanged = pyqtSignal()
|
||||
boostChanged = pyqtSignal()
|
||||
voltageChanged = pyqtSignal()
|
||||
pulseWidthChanged = pyqtSignal()
|
||||
ignitionChanged = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._rpm = 850.0
|
||||
self._throttle = 0.0
|
||||
self._temp = 75.0
|
||||
self._afr = 14.7
|
||||
self._boost = 0.0
|
||||
self._voltage = 12.6
|
||||
self._pulseWidth = 2.5
|
||||
self._ignition = 15.0
|
||||
|
||||
self._cycle_time = 0
|
||||
self._phase = 0
|
||||
|
||||
# Timer for simulation updates (50ms = 20Hz)
|
||||
self._timer = QTimer()
|
||||
self._timer.timeout.connect(self._update_simulation)
|
||||
self._timer.start(50)
|
||||
|
||||
def _update_simulation(self):
|
||||
"""10-phase realistic driving simulation"""
|
||||
self._cycle_time += 50
|
||||
|
||||
if self._cycle_time < 3000: # Idle
|
||||
self._phase = 0
|
||||
self._rpm = 850 + math.sin(self._cycle_time / 100) * 50
|
||||
self._throttle = 0
|
||||
self._temp = min(self._temp + 0.01, 85)
|
||||
self._boost = 0
|
||||
self._pulseWidth = 2.5
|
||||
self._afr = 14.7
|
||||
self._ignition = 15
|
||||
|
||||
elif self._cycle_time < 5000: # Light acceleration
|
||||
self._phase = 1
|
||||
t = (self._cycle_time - 3000) / 2000
|
||||
self._rpm = 850 + t * 2150
|
||||
self._throttle = 20 + t * 30
|
||||
self._temp += 0.05
|
||||
self._boost = max(0, (self._rpm - 2000) / 1000 * 5)
|
||||
self._pulseWidth = 2.5 + t * 5
|
||||
self._afr = 14.7 - t * 0.5
|
||||
self._ignition = 15 + t * 10
|
||||
|
||||
elif self._cycle_time < 8000: # Cruising
|
||||
self._phase = 2
|
||||
self._rpm = 3000 + math.sin((self._cycle_time - 5000) / 200) * 100
|
||||
self._throttle = 45 + math.sin((self._cycle_time - 5000) / 150) * 5
|
||||
self._temp = min(self._temp + 0.02, 95)
|
||||
self._boost = 3 + math.sin((self._cycle_time - 5000) / 300) * 2
|
||||
self._pulseWidth = 7.5
|
||||
self._afr = 14.5
|
||||
self._ignition = 25
|
||||
|
||||
elif self._cycle_time < 10000: # WOT acceleration
|
||||
self._phase = 3
|
||||
t = (self._cycle_time - 8000) / 2000
|
||||
self._rpm = 3000 + t * 4000
|
||||
self._throttle = 50 + t * 50
|
||||
self._temp += 0.1
|
||||
self._boost = 3 + t * 12
|
||||
self._pulseWidth = 7.5 + t * 7
|
||||
self._afr = 14.7 - t * 3.2
|
||||
self._ignition = 25 - t * 8
|
||||
|
||||
elif self._cycle_time < 11000: # Gear shift
|
||||
self._phase = 4
|
||||
t = (self._cycle_time - 10000) / 1000
|
||||
self._rpm = 7000 - t * 3000
|
||||
self._throttle = 0
|
||||
self._boost = 15 - t * 10
|
||||
self._pulseWidth = 14 - t * 10
|
||||
self._afr = 11.5 + t * 5
|
||||
self._ignition = 17 + t * 8
|
||||
|
||||
elif self._cycle_time < 14000: # Acceleration in higher gear
|
||||
self._phase = 5
|
||||
t = (self._cycle_time - 11000) / 3000
|
||||
self._rpm = 4000 + t * 2500
|
||||
self._throttle = 70 + t * 30
|
||||
self._boost = 5 + t * 8
|
||||
self._pulseWidth = 4 + t * 8
|
||||
self._afr = max(11.5, 16.5 - t * 3)
|
||||
self._ignition = 25 - t * 5
|
||||
|
||||
elif self._cycle_time < 16000: # Deceleration
|
||||
self._phase = 6
|
||||
t = (self._cycle_time - 14000) / 2000
|
||||
self._rpm = 6500 - t * 4500
|
||||
self._throttle = 100 - t * 100
|
||||
self._boost = max(0, 13 - t * 13)
|
||||
self._pulseWidth = max(1, 12 - t * 11)
|
||||
self._afr = 11.5 + t * 7
|
||||
self._temp = max(85, self._temp - 0.05)
|
||||
self._ignition = 20 + t * 10
|
||||
|
||||
elif self._cycle_time < 18000: # Coasting
|
||||
self._phase = 7
|
||||
t = (self._cycle_time - 16000) / 2000
|
||||
self._rpm = 2000 - t * 500
|
||||
self._throttle = 0
|
||||
self._boost = 0
|
||||
self._pulseWidth = 1
|
||||
self._afr = 18.5 + math.sin((self._cycle_time - 16000) / 100)
|
||||
self._temp = max(85, self._temp - 0.03)
|
||||
self._ignition = 30
|
||||
|
||||
elif self._cycle_time < 20000: # Back to idle
|
||||
self._phase = 8
|
||||
t = (self._cycle_time - 18000) / 2000
|
||||
self._rpm = 1500 - t * 650
|
||||
self._throttle = 0
|
||||
self._boost = 0
|
||||
self._pulseWidth = 2.5
|
||||
self._afr = 14.7
|
||||
self._temp = max(85, self._temp - 0.02)
|
||||
self._ignition = 15
|
||||
|
||||
else: # Reset cycle
|
||||
self._cycle_time = 0
|
||||
|
||||
# Add slight randomness
|
||||
self._rpm = max(0, self._rpm + (random.random() - 0.5) * 20)
|
||||
self._throttle = max(0, min(100, self._throttle + (random.random() - 0.5) * 2))
|
||||
self._afr = max(10, min(20, self._afr + (random.random() - 0.5) * 0.1))
|
||||
self._voltage = 12.6 + (random.random() - 0.5) * 0.4
|
||||
|
||||
# Emit all change signals
|
||||
self.rpmChanged.emit()
|
||||
self.throttleChanged.emit()
|
||||
self.tempChanged.emit()
|
||||
self.afrChanged.emit()
|
||||
self.boostChanged.emit()
|
||||
self.voltageChanged.emit()
|
||||
self.pulseWidthChanged.emit()
|
||||
self.ignitionChanged.emit()
|
||||
|
||||
@pyqtProperty(float, notify=rpmChanged)
|
||||
def rpm(self):
|
||||
return self._rpm
|
||||
|
||||
@pyqtProperty(float, notify=throttleChanged)
|
||||
def throttle(self):
|
||||
return self._throttle
|
||||
|
||||
@pyqtProperty(float, notify=tempChanged)
|
||||
def temp(self):
|
||||
return self._temp
|
||||
|
||||
@pyqtProperty(float, notify=afrChanged)
|
||||
def afr(self):
|
||||
return self._afr
|
||||
|
||||
@pyqtProperty(float, notify=boostChanged)
|
||||
def boost(self):
|
||||
return self._boost
|
||||
|
||||
@pyqtProperty(float, notify=voltageChanged)
|
||||
def voltage(self):
|
||||
return self._voltage
|
||||
|
||||
@pyqtProperty(float, notify=pulseWidthChanged)
|
||||
def pulseWidth(self):
|
||||
return self._pulseWidth
|
||||
|
||||
@pyqtProperty(float, notify=ignitionChanged)
|
||||
def ignition(self):
|
||||
return self._ignition
|
||||
|
||||
|
||||
class ClipboardHelper(QObject):
|
||||
"""Helper class for clipboard operations accessible from QML"""
|
||||
|
||||
@@ -73,6 +258,10 @@ def main():
|
||||
# Create QML engine
|
||||
engine = QQmlApplicationEngine()
|
||||
|
||||
# Create and register engine simulator
|
||||
engine_sim = EngineSimulator()
|
||||
engine.rootContext().setContextProperty("engineData", engine_sim)
|
||||
|
||||
# Create and register clipboard helper
|
||||
clipboard_helper = ClipboardHelper(app)
|
||||
engine.rootContext().setContextProperty("clipboardHelper", clipboard_helper)
|
||||
@@ -96,15 +285,12 @@ def main():
|
||||
|
||||
print(f"TunerStudio MS QML Application Started")
|
||||
print(f"Loaded from: {qml_file}")
|
||||
print(f"Engine simulation running at 20Hz")
|
||||
print(f"\nAvailable views:")
|
||||
print(f" - Tuning: 3D tables for VE, ignition, AFR")
|
||||
print(f" - Dashboard: Real-time gauges and indicators")
|
||||
print(f" - Data Logging: Chart and log viewer")
|
||||
print(f" - Table Editor: Advanced table editing")
|
||||
print(f" - Diagnostics: ECU status and error codes")
|
||||
print(f"\nRegistration dialogs available in separate QML files:")
|
||||
print(f" - RegistrationDialog.qml")
|
||||
print(f" - OfflineActivationDialog.qml")
|
||||
print(f" - Gauge Cluster: Draggable gauges with live engine data")
|
||||
print(f" - Tuning & Dyno: Tuning views")
|
||||
print(f" - Graphing & Logging: Data logging")
|
||||
print(f" - Diagnostics: ECU status")
|
||||
|
||||
# Run the application
|
||||
sys.exit(app.exec())
|
||||
|
||||
Reference in New Issue
Block a user