firmware-base/docs/pressure-profile.md

333 lines
10 KiB
Markdown

# Pressure Profile Integration
## Overview
Pressure profiles (`PressureProfile`) work as **child plots** of temperature profiles (`TemperatureProfile`) in the polymech framework. When a temperature profile is configured with a pressure profile slot ID, the pressure profile becomes a subordinate that follows the lifecycle of its parent temperature profile.
## Parent-Child Relationship
### Linking Mechanism
Temperature profiles can be linked to pressure profiles through the `pressureProfile` field in the temperature profile JSON configuration:
```json
{
"id": 1,
"name": "Temperature Profile with Pressure",
"pressureProfile": 0,
// ... other temperature profile fields
}
```
The linking is established in `PHApp::load()` and `PHApp::updateProfile()`:
```cpp
// During profile loading
if (pressureProfileSlotId >= 0 && pressureProfileSlotId < PROFILE_PRESSURE_COUNT) {
if (pressureProfiles[pressureProfileSlotId]) {
tempProfiles[i]->addPlot(pressureProfiles[pressureProfileSlotId]);
}
}
```
### Duration Inheritance
When a pressure profile is added as a child plot via `PlotBase::addPlot()`:
- The child pressure profile inherits the parent's duration: `plot->setDuration(_durationMs)`
- The parent-child relationship is established: `plot->setParent(this)`
## Lifecycle Management
### State Synchronization
The pressure profile's lifecycle is controlled by its parent temperature profile through the `PlotBase` event system:
#### 1. Starting (onStart)
When the temperature profile starts:
- **Normal Start**: If the temperature profile transitions directly to `RUNNING`, child pressure profiles start immediately
- **Warmup Start**: If the temperature profile enters `INITIALIZING` state (warmup mode), child pressure profiles are **deferred** until warmup completes
```cpp
// In PlotBase::onStart()
if (getCurrentStatus() == PlotStatus::INITIALIZING) {
L_INFO("Profile ID %d is INITIALIZING, deferring child plot start.", id);
return; // Child plots NOT started yet
}
// Start all child plots when ready
for (int i = 0; i < MAX_PLOTS; ++i) {
if (_plots[i] != nullptr) {
_plots[i]->start();
}
}
```
#### 2. Stopping (onStop)
When the temperature profile stops, all child pressure profiles are stopped:
```cpp
for (int i = 0; i < MAX_PLOTS; ++i) {
if (_plots[i] != nullptr) {
_plots[i]->stop();
}
}
```
#### 3. Pausing (onPause)
When the temperature profile pauses, all child pressure profiles are paused:
```cpp
for (int i = 0; i < MAX_PLOTS; ++i) {
if (_plots[i] != nullptr) {
_plots[i]->pause();
}
}
```
#### 4. Resuming (onResume)
When the temperature profile resumes, all child pressure profiles are resumed:
```cpp
for (int i = 0; i < MAX_PLOTS; ++i) {
if (_plots[i] != nullptr) {
_plots[i]->resume();
}
}
```
#### 5. Finishing (onFinished)
When the temperature profile finishes, the event is propagated but child plots are not explicitly stopped (they should finish naturally based on duration).
## Value Normalization
### Max Value Enforcement
Pressure profiles automatically enforce `max = 100` regardless of JSON configuration to ensure proper percentage scaling for PressCylinder SP values:
```cpp
// In PressureProfile constructor and load()
max = 100; // Always enforced for percentage scaling
```
This ensures that:
- Control point `y: 1000` (100% of PROFILE_SCALE) → outputs 100 (100% pressure)
- Control point `y: 500` (50% of PROFILE_SCALE) → outputs 50 (50% pressure)
- Values are properly interpreted by PressCylinder as percentages (0-100%)
### Temperature vs Pressure Profile Max Values
| Profile Type | Max Value | Purpose |
|-------------|-----------|---------|
| Temperature | User-defined (e.g., 150°C) | Actual temperature units |
| Pressure | Always 100 | Percentage for PressCylinder SP |
## Target Register Integration
### PressCylinder SP Control
Pressure profiles can control PressCylinder components by targeting their setpoint (SP) registers through the `targetRegisters` array:
```json
{
"id": 0,
"name": "Press Cylinder Pressure Control",
"targetRegisters": [1002], // PressCylinder SP register address
"controlPoints": [
{"x": 0, "y": 0}, // Start at 0% pressure
{"x": 300, "y": 500}, // Ramp to 50% by 30% time
{"x": 700, "y": 500}, // Hold 50% until 70% time
{"x": 1000, "y": 0} // Return to 0% by end
]
// ... other fields
}
```
### Modbus Register Mapping
The pressure profile writes to target registers via Modbus TCP:
```cpp
// In PressureProfile::loop() -> applyPressure()
for (uint16_t targetRegAddr : _targetRegisters) {
if (targetRegAddr == 0) continue;
ModbusMessage req;
req.setMessage(1, FN_WRITE_HOLD_REGISTER, targetRegAddr, pressureValue);
resp = modbusTCP->modbusServer->localRequest(req);
}
```
For PressCylinder, the SP register is located at:
- **Base Address**: Component's Modbus base address
- **SP Offset**: `MB_OFS_HR_TARGET_SP = E_NVC_USER + PRESS_CYLINDER_MAX_PAIRS`
- **Full Address**: `baseAddress + MB_OFS_HR_TARGET_SP`
## State Machine Integration
### Temperature Profile States
The pressure profile responds to these temperature profile states:
| Temperature State | Pressure Profile Action |
|------------------|-------------------------|
| `IDLE` | Not started/remains idle, SP reset to 0 |
| `INITIALIZING` | Waits for parent to complete warmup, SP reset to 0 |
| `RUNNING` | Actively executes pressure curve |
| `PAUSED` | Pauses execution, **maintains current SP** |
| `STOPPED` | Stops and resets SP to 0 |
| `FINISHED` | Completes execution, **maintains final SP** |
### PressCylinder Integration
When the pressure profile targets a PressCylinder:
1. **Pressure Values**: The profile calculates pressure percentages (0-100%) based on control points
2. **SP Conversion**: Values are written directly to the PressCylinder's `m_targetSP` register
3. **Mode Compatibility**: Works with PressCylinder auto modes (`MODE_AUTO`, `MODE_AUTO_MULTI`, etc.)
4. **Safety Integration**: PressCylinder safety systems (overload, balance, timeouts) remain active
### Loop Timing
- **Pressure Profile Loop**: Executes every `PRESSURE_PROFILE_LOOP_INTERVAL_MS` (150ms)
- **PressCylinder Loop**: Executes every `PRESSCYLINDER_INTERVAL` (20ms)
- **Coordination**: PressCylinder reads SP changes and responds within its next loop cycle
## Configuration Example
Complete example showing temperature profile with linked pressure profile:
### Temperature Profile (`profile_defaults.json`)
```json
[
{
"id": 1,
"name": "Heating with Pressure Control",
"pressureProfile": 0,
"targetRegisters": [2001, 2002],
"controlPoints": [
{"x": 0, "y": 200}, // Start at 20°C
{"x": 500, "y": 800}, // Heat to 80°C
{"x": 1000, "y": 800} // Hold at 80°C
],
"duration": 300000, // 5 minutes
"enabled": true
}
]
```
### Pressure Profile (`pressure_profiles.json`)
```json
[
{
"id": 0,
"name": "Synchronized Pressure Ramp",
"targetRegisters": [1002],
"controlPoints": [
{"x": 0, "y": 0}, // Start at 0% pressure
{"x": 200, "y": 0}, // Hold 0% during initial heating
{"x": 400, "y": 600}, // Ramp to 60% pressure
{"x": 800, "y": 600}, // Hold 60% pressure
{"x": 1000, "y": 200} // Reduce to 20% at end
],
"enabled": true
}
]
```
## Event Callbacks
The system provides event callbacks for pressure profile lifecycle events:
```cpp
// In PHApp (or other IPlotEvents implementer)
void onPressureProfileStarted(PlotBase *profile);
void onPressureProfileStopped(PlotBase *profile);
void onPressureProfilePaused(PlotBase *profile);
void onPressureProfileResumed(PlotBase *profile);
void onPressureProfileFinished(PlotBase *profile);
```
These callbacks can be used to implement additional logic like:
- Safety interlocks
- Data logging
- UI status updates
- Integration with other systems
## Best Practices
1. **Duration Matching**: Ensure pressure profile control points span the full time range (0-1000) to match the parent temperature profile duration
2. **Safety First**: Always consider PressCylinder safety limits when designing pressure curves
3. **Smooth Transitions**: Use gradual pressure changes to avoid system shock and ensure stable operation
4. **Testing**: Test pressure profiles independently before linking to temperature profiles
5. **Monitoring**: Monitor both temperature and pressure values during operation to ensure proper coordination
## Pressure Holding Behavior
### State-Specific Pressure Management
The pressure profile uses intelligent pressure management based on its current state:
#### Reset Pressure to 0:
- **IDLE**: Profile not started or reset
- **INITIALIZING**: Waiting for parent warmup
- **STOPPED**: Explicitly stopped by user/system
#### Hold Current Pressure:
- **PAUSED**: Maintains current setpoint for process stability
- **FINISHED**: Maintains final setpoint for controlled completion
```cpp
// Implementation logic
switch (currentStatus) {
case PlotStatus::PAUSED:
case PlotStatus::FINISHED:
holdCurrentPressure(); // Keep current SP
break;
case PlotStatus::IDLE:
case PlotStatus::STOPPED:
resetOutputs(); // Set SP to 0
break;
}
```
This behavior ensures:
- **Process Stability**: No sudden pressure drops during pause/finish
- **Safety**: Controlled shutdown only when explicitly stopped
- **Operator Control**: Predictable behavior during manual interventions
## Troubleshooting
### Common Issues
1. **Pressure Profile Not Starting**
- Check that the temperature profile has the correct `pressureProfile` slot ID
- Verify the pressure profile slot is not null
- Ensure the temperature profile successfully exits `INITIALIZING` state
2. **SP Not Updating**
- Verify `targetRegisters` contains the correct PressCylinder SP address
- Check Modbus TCP connectivity
- Ensure PressCylinder is in an auto mode (`MODE_AUTO`, etc.)
3. **Timing Issues**
- Check loop intervals and ensure adequate system performance
- Monitor for Modbus communication delays
- Verify control point timing alignment
### Debug Information
Enable logging to see pressure profile lifecycle events:
```cpp
L_INFO("PressureProfile: Status changed from %d to %d", oldStatus, newStatus);
L_INFO("PressureProfile: Applying pressure %d to register %d", pressureValue, targetRegAddr);
```