# 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); ```