Skip to content

App Rotator

cziter15 edited this page Feb 14, 2026 · 8 revisions

App Rotator

The App Rotator provides automatic failover and multi-application support, allowing your device to seamlessly switch between different operational modes.


πŸ“– Overview

The App Rotator (ksAppRotator) acts as a carousel manager for your applications. It attempts to run applications in sequence, automatically switching to the next one if the current application fails or requests termination.

Key Benefits

  • Automatic Failover β€” Switch to config app on errors
  • Multi-Mode Operation β€” Main app + config assistant
  • Resilience β€” Device never gets stuck in broken state
  • Easy Setup β€” First boot provisioning flow

🎯 How It Works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Application Rotator Loop                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚
                      β–Ό
           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”‚ Try Application #1 β”‚
           β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”˜
                β”‚          β”‚
          Success β”‚          β”‚ Failed
                β”‚          β–Ό
                β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                β”‚   β”‚ Try App #2   β”‚
                β”‚   β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                β”‚         β”‚
                β”‚   Successβ”‚
                β”‚         β”‚
                β–Ό         β–Ό
           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”‚ Run App Loop Forever   β”‚
           β”‚ Until app returns falseβ”‚
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“ Implementing App Rotator

Using the Macro

The framework provides a convenient macro to implement the rotator:

#include <ksf/ksAppRotator.h>
#include "MyMainApp.h"
#include "MyConfigApp.h"

// In main.cpp
KSF_IMPLEMENT_APP_ROTATOR(
    MyMainApp,      // First application
    MyConfigApp     // Fallback application
)

What the Macro Does

  1. Implements Arduino's setup() function
  2. Implements Arduino's loop() function
  3. Creates application instances
  4. Manages rotation between applications
  5. Handles cleanup and error recovery

🎨 Common Patterns

Pattern 1: Main App + Config App

The most common use case:

class MainApp : public ksApplication
{
protected:
    bool init() override {
        // Check if configured
        if (!hasWiFiCredentials()) {
            return false;  // Go to config app
        }
        
        // Normal operation
        addComponent<ksWifiConnector>("MyDevice");
        addComponent<ksMqttConnector>();
        addComponent<DeviceLogic>();
        return true;
    }
};

class ConfigApp : public ksApplication
{
protected:
    bool init() override {
        // Setup mode components
        addComponent<ksWifiConfigurator>();
        addComponent<ksDevicePortal>();
        addComponent<ksLed>(LED_BUILTIN);
        return true;
    }
    
    bool loop() override {
        // Auto-restart when configured
        if (isWiFiConfigured()) {
            return false;  // Back to main app
        }
        return true;
    }
};

Pattern 2: Multiple Fallbacks

You can have multiple fallback applications:

KSF_IMPLEMENT_APP_ROTATOR(
    MainApp,        // Try main first
    DiagnosticApp,  // Falls back to diagnostics
    ConfigApp       // Last resort: full setup
)

Pattern 3: Error Recovery

Main app can detect errors and trigger config mode:

class MainApp : public ksApplication
{
private:
    int connectionFailures = 0;
    
protected:
    bool loop() override {
        auto wifi = findComponent<ksWifiConnector>();
        
        if (!wifi->isConnected()) {
            connectionFailures++;
            
            // Too many failures, go to config
            if (connectionFailures > 10) {
                return false;  // Trigger rotator
            }
        } else {
            connectionFailures = 0;  // Reset on success
        }
        
        return true;
    }
};

πŸ”„ Rotation Conditions

Applications are rotated when any of these occur:

1. init() Returns False

bool init() override {
    if (missingConfig) {
        return false;  // Rotate immediately
    }
    return true;
}

2. postInit() Returns False

bool postInit() override {
    auto mqtt = findComponent<ksMqttConnector>();
    if (!mqtt) {
        return false;  // Required component missing
    }
    return true;
}

3. loop() Returns False

bool loop() override {
    if (fatalError) {
        return false;  // Stop and rotate
    }
    return true;
}

4. Constructor Throws Exception

class MyApp : public ksApplication
{
    MyComponent* comp;
    
    MyApp() {
        // If this throws, rotator catches it
        comp = new MyComponent();
    }
};

πŸ’‘ Best Practices

1. Validate Prerequisites in init()

bool init() override {
    // Check configuration exists
    if (!configExists()) {
        return false;  // Let config app handle it
    }
    
    // Validate required hardware
    if (!sensorPresent()) {
        return false;  // Go to diagnostics
    }
    
    // All good, add components
    addComponent<ksWifiConnector>();
    return true;
}

2. Use Config App for Setup

class ConfigApp : public ksApplication
{
protected:
    bool init() override {
        // WiFi configurator creates AP
        addComponent<ksWifiConfigurator>();
        
        // Device portal for web setup
        addComponent<ksDevicePortal>();
        
        // LED indicates setup mode
        addComponent<ksLed>(LED_BUILTIN);
        
        return true;
    }
    
    bool loop() override {
        auto led = findComponent<ksLed>();
        
        // Blink fast in config mode
        led->pattern(0b10101010);
        
        // Check if setup complete
        if (hasWiFiCredentials() && hasMqttSettings()) {
            saveConfiguration();
            return false;  // Exit config mode
        }
        
        return true;
    }
};

3. Clear State Transitions

class MainApp : public ksApplication
{
protected:
    bool postInit() override {
        // Get references
        auto led = findComponent<ksLed>();
        led->pattern(0b11001100);  // Slow blink = active
        return true;
    }
};

class ConfigApp : public ksApplication
{
protected:
    bool postInit() override {
        auto led = findComponent<ksLed>();
        led->pattern(0b10101010);  // Fast blink = config
        return true;
    }
};

4. Graceful Degradation

class MainApp : public ksApplication
{
private:
    bool degradedMode = false;
    
protected:
    bool loop() override {
        auto wifi = findComponent<ksWifiConnector>();
        
        if (!wifi->isConnected()) {
            if (!degradedMode) {
                enterDegradedMode();
                degradedMode = true;
            }
            
            // Check for too long offline
            if (offlineTime() > 3600000) {  // 1 hour
                return false;  // Go to config app
            }
        } else {
            degradedMode = false;
        }
        
        return true;
    }
};

🎨 Complete Example

Two-Application System

// MainApp.h
#pragma once
#include <ksf/ksApplication.h>
#include <ksf/comp/ksWifiConnector.h>
#include <ksf/comp/ksMqttConnector.h>
#include <ksf/comp/ksLed.h>

class MainApp : public ksApplication
{
protected:
    bool init() override {
        // No configuration? Go to setup
        if (!hasWiFiConfig() || !hasMqttConfig()) {
            return false;
        }
        
        // Add main application components
        addComponent<ksWifiConnector>("IoTDevice");
        addComponent<ksMqttConnector>();
        addComponent<ksLed>(LED_BUILTIN);
        
        return true;
    }
    
    bool postInit() override {
        auto led = findComponent<ksLed>();
        if (led) {
            led->pattern(0b11001100);  // Running indicator
        }
        return true;
    }
};

// ConfigApp.h
#pragma once
#include <ksf/ksApplication.h>
#include <ksf/comp/ksWifiConfigurator.h>
#include <ksf/comp/ksDevicePortal.h>
#include <ksf/comp/ksLed.h>

class ConfigApp : public ksApplication
{
protected:
    bool init() override {
        // Setup mode components
        addComponent<ksWifiConfigurator>();
        addComponent<ksDevicePortal>();
        addComponent<ksLed>(LED_BUILTIN);
        return true;
    }
    
    bool postInit() override {
        auto led = findComponent<ksLed>();
        if (led) {
            led->pattern(0b10101010);  // Config mode indicator
        }
        return true;
    }
    
    bool loop() override {
        // Auto-exit when configured
        if (hasWiFiConfig() && hasMqttConfig()) {
            return false;  // Go back to main app
        }
        return true;
    }
};

// main.cpp
#include <ksf/ksAppRotator.h>
#include "MainApp.h"
#include "ConfigApp.h"

using namespace ksf;

// Implement application rotation
KSF_IMPLEMENT_APP_ROTATOR(
    MainApp,    // Main application
    ConfigApp   // Configuration application
)

πŸ”§ Advanced Usage

Custom Rotation Logic

For complex scenarios, implement custom logic:

class SmartRotator : public ksAppRotator
{
protected:
    bool shouldRestartApp() override {
        // Custom logic to decide if app should restart
        auto lastErrorTime = getLastApplicationErrorTime();
        auto timeSinceError = millis() - lastErrorTime;
        
        // Wait 5 minutes before retrying failed app
        if (timeSinceError < 300000) {
            return false;  // Skip to next app
        }
        
        return true;
    }
};

Application State Persistence

Persist state between applications:

class ConfigApp : public ksApplication
{
protected:
    bool loop() override {
        if (configurationComplete()) {
            // Save completion flag
            saveFlag("config_complete", true);
            return false;
        }
        return true;
    }
};

class MainApp : public ksApplication
{
protected:
    bool init() override {
        if (!loadFlag("config_complete")) {
            return false;  // Go to config
        }
        
        // Clear flag for next time
        saveFlag("config_complete", false);
        
        addComponent<ksWifiConnector>();
        return true;
    }
};

πŸ“Š Behavior Characteristics

Restart Order

Boot β†’ App 1 β†’ (fails) β†’ App 2 β†’ (fails) β†’ App 3 β†’ (succeeds) β†’ Run
                                      ↑
                                 App 3 fails
                                      ↓
Boot β†’ App 1 β†’ (succeeds) β†’ Run

Application Lifecycle

Each rotation:

  1. Destroys previous application completely
  2. Creates new application instance
  3. Calls init() β†’ postInit() β†’ loop()
  4. Runs until failure or manual termination

Memory Management

  • Old application fully destroyed before new one created
  • All components and resources released
  • Fresh start on each rotation

⚠️ Common Mistakes

Don't: Infinite Rotation

bool loop() override {
    if (somethingMinor) {
        return false;  // ❌ Too sensitive, causes loop
    }
    return true;
}

Don't: Forget Reset Logic

bool init() override {
    if (needConfig) {
        return false;
    }
    
    // ❌ Forgets to check again next time
    addComponent<ksWifiConnector>();
    return true;
}

Don't: Assume Global State

int globalCounter = 0;  // ❌ Survives rotation

class MyApp : public ksApplication {
    bool loop() override {
        globalCounter++;  // Wrong! App may rotate
        return true;
    }
};

πŸ“– Related Topics

πŸ“˜ Getting Started

πŸ—οΈ Core Concepts

πŸ“¦ Components Reference

βš™οΈ Configuration & Management

πŸ”¬ Advanced Topics

πŸ’‘ Examples

πŸ”— External Resources

Clone this wiki locally