firmware-base/docs/manual.md

5.5 KiB

Manual Mode Refactor Proposal

Status: Implemented Feature: Enhanced Manual Mode Safety & Interlock Support

Problem Statement

Currently, PressCylinder::onJoystickUp implements a simplified manual control loop that bypasses safety checks (CFlags) and complex logic available in loopSingle and loopMulti. It directly enables solenoids based on Joystick Position, ignoring:

  • Load balancing (in multi-cylinder setups)
  • Stall detection
  • Interlock status (implicitly, though safety overrides exist)
  • Operation timeouts

The goal is to unify the logic so that "Manual" operation is simply a special case of the robust loop* functions, but controlled by the Joystick/PushButton rather than a fixed "Auto" process.

Proposed Strategy

We will treat Manual operation as "Auto operation towards a Virtual Maximum Target" that only persists while the operator holds the input (Joystick or PushButton).

1. Unified Control Logic

Instead of direct SOLENOID_ON/OFF in onJoystickUp, we will:

  1. Detect "Active Input" (Joystick UP OR PushButton Pressed).
  2. If Active:
    • Temporarily override m_targetSP to 100% (or a manual_override_sp).
    • Set effective mode to DEFAULT_AUTO_MODE_NORMAL (if single/unlocked) or DEFAULT_AUTO_MODE_INTERLOCKED (if interlocked).
    • Call loopSingle() or loopMulti() respectively.
  3. If Inactive:
    • Reset m_targetSP to 0 (or restore original).
    • Enforce SOLENOIDS_OFF.

2. Implementation Plan

A. Define Helper: getVirtualTargetSP()

For manual moves, we want to press as long as the operator commands, up to the absolute hardware limit (maxload_threshold or 100%).

uint32_t PressCylinder::getVirtualTargetSP() {
    // In Manual mode, "Target" is effectively "Go until Max or Stop"
    return 70; // 70% of maxload_threshold (Virtual SP Max)
}

B. Refactor onJoystickUp / Main Loop Integration

We will stop using onJoystickUp as a direct actuator. Instead, it becomes an input state detector.

Modified loop() Logic:

// 1. Determine "Effective Mode" and "Effective Target"
E_Mode effective_mode = (E_Mode)m_mode.getValue();
uint32_t effective_sp = m_targetSP.getValue();
bool manual_input_active = false;

// Check Inputs (Joystick or PushButton)
    // Check Inputs (Joystick or PushButton)
    if (_pushButton->getState() == PushButton::State::PRESSED || _pushButton->getState() == PushButton::State::HELD) {
        // Condition 1: PushButton Held -> Force Manual Single Cylinder
        manual_input_active = true;
        effective_mode = MODE_MANUAL;
        effective_sp = 70; 
    }
    else if (_joystick->getPosition() == Joystick::E_POSITION::UP) {
        // Condition 2: Joystick UP -> Smart Auto / Interlock Dependent
        manual_input_active = true;
        effective_sp = 70;
        
        if (effective_mode == MODE_MANUAL || effective_mode == MODE_MANUAL_MULTI) {
             if (m_interlocked.getValue()) {
                  effective_mode = DEFAULT_AUTO_MODE_INTERLOCKED; // e.g. AUTO_MULTI_BALANCED
             } else {
                  effective_mode = DEFAULT_AUTO_MODE_NORMAL; // e.g. AUTO
             }
        }
    }

// 2. Dispatch to Control Loops
if (manual_input_active || isAutoRunning()) {
    // These functions now use 'effective_mode' and 'effective_sp' 
    // instead of reading m_targetSP/m_mode directly if we pass them as args,
    // OR we temporarily override member vars (simpler but riskier state).
    
    // BETTER APPROACH: Refactor loopSingle/loopMulti to take args
    if (isMultiMode(effective_mode)) {
        loopMulti(effective_mode, effective_sp);
    } else {
        loopSingle(effective_mode, effective_sp);
    }
} else {
    // Idle / Stop
    SOLENOIDS_OFF();
}

3. Modifications Required

  1. Refactor loopSingle / loopMulti:

    • Change signature to accept E_Mode mode and uint16_t target_sp arguments.
    • Remove internal calls to m_mode.getValue() and m_targetSP.getValue() in favor of these arguments.
  2. Update onJoystickUp:

    • Actually, we might deprecate onJoystickUp direct logic entirely and move it into the main loop() structure as shown above.
    • Retain onJoystickDoubleUp for the HOLD feature (capturing Load to SP).
  3. PushButton Integration:

    • Map _pushButton->getState() == PushButton::State::PRESSED (or HELD) to manual_input_active.

4. Safety Considerations

  • Deadman Switch: The moment the Joystick/Button is released, manual_input_active becomes false, and the else block triggers SOLENOIDS_OFF().
  • Existing Safety: loopSingle/Multi already contain CheckMinLoad, Stall, Balance, and MaxTime checks. By routing manual control through them, we gain all these features automatically.
  • Virtual SP: Setting SP to 100% is safe because loop* logic checks canPress() which respects maxload_threshold.

Diagram: New Flow

graph TD
    Input[Inputs: Joystick / Button] --> Check{Active?}
    Check -- Yes --> MapMode[Map to Effective Auto Mode]
    MapMode --> SetSP[Set Virtual SP = 70%]
    SetSP --> Dispatch{Is Multi?}
    
    Dispatch -- Single --> LoopSingle[loopSingle(SafeMode, 70%)]
    Dispatch -- Multi --> LoopMulti[loopMulti(SafeMode, 70%)]
    
    LoopSingle --> Actuators
    LoopMulti --> Actuators
    
    Check -- No --> Stop[SOLENOIDS_OFF]

References

Data

cs-1150 - interlocked

min load : 50-150