firmware-base/docs/pressure-profile.md

10 KiB

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:

{
  "id": 1,
  "name": "Temperature Profile with Pressure",
  "pressureProfile": 0,
  // ... other temperature profile fields
}

The linking is established in PHApp::load() and PHApp::updateProfile():

// 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
// 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:

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:

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:

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:

// 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:

{
  "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:

// 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)

[
  {
    "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)

[
  {
    "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:

// 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
// 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:

L_INFO("PressureProfile: Status changed from %d to %d", oldStatus, newStatus);
L_INFO("PressureProfile: Applying pressure %d to register %d", pressureValue, targetRegAddr);