Skip to content

Component

cziter15 edited this page Feb 14, 2026 · 7 revisions

Component

A Component is the fundamental building block in ksIotFrameworkLib, encapsulating specific functionality in a reusable, composable unit.


📖 Overview

Components are modular, self-contained units that provide specific functionality like WiFi connectivity, MQTT communication, LED control, or any custom behavior. They are managed by the Application and follow a well-defined lifecycle.

Key Characteristics

  • Modular — Each component has a single, focused responsibility
  • Reusable — Can be used across multiple applications
  • Composable — Multiple components work together seamlessly
  • Self-contained — Manages its own state and resources

🎨 Creating a Component

Basic Template

Every component must:

  1. Inherit from ksComponent
  2. Use the RTTI macro
  3. Override lifecycle methods
#pragma once
#include <ksf/ksComponent.h>

class MyComponent : public ksf::ksComponent
{
    // Required: RTTI macro
    KSF_RTTI_DECLARATIONS(MyComponent, ksComponent)
    
protected:
    bool init() override {
        // One-time initialization
        return true;  // false = component fails
    }
    
    bool postInit() override {
        // Setup dependencies, find other components
        return true;
    }
    
    bool loop() override {
        // Called repeatedly
        return true;  // false = component fails
    }
    
public:
    ~MyComponent() override {
        // Cleanup
    }
};

🔄 Component Lifecycle

┌─────────────────────────────────────────────────────────┐
│ CREATED                                                │
│ Component instantiated via addComponent<T>()             │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│ NOT_INITIALIZED                                       │
│ init() is called                                       │
│ ✓ Setup hardware                                       │
│ ✓ Initialize state                                     │
└─────────────────────┬───────────────────────────────────┘
                      │ return true
                      ▼
┌─────────────────────────────────────────────────────────┐
│ INITIALIZED                                            │
│ Component ready, other components can find it           │
│ postInit() is called                                  │
│ ✓ Find dependent components                           │
│ ✓ Setup interactions                                  │
└─────────────────────┬───────────────────────────────────┘
                      │ return true
                      ▼
┌─────────────────────────────────────────────────────────┐
│ ACTIVE                                                │
│ loop() called repeatedly                              │
│ ✓ Perform ongoing operations                           │
│ ✓ Process events                                      │
└─────────────────────┬───────────────────────────────────┘
                      │
                      │ return false or remove()
                      ▼
┌─────────────────────────────────────────────────────────┐
│ TO_REMOVE                                            │
│ Marked for removal                                    │
│ ✓ Cleanup in progress                                │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│ DELETED                                              │
│ Memory freed, resources released                     │
└─────────────────────────────────────────────────────────┘

📋 Lifecycle Methods

init()

Called once when component is created. Use for:

  • Hardware initialization (GPIO, peripherals)
  • Creating objects and resources
  • Initial configuration
  • Validation of prerequisites

Return Value:

  • true — Success, proceed to INITIALIZED state
  • false — Failure, application will fail

Example:

bool init() override {
    // Initialize serial
    Serial.begin(115200);
    
    // Setup pin
    pinMode(buttonPin, INPUT_PULLUP);
    
    // Create timer
    lastCheck = millis();
    
    return true;
}

postInit()

Called after ALL components are initialized. Use for:

  • Finding references to other components
  • Setting up component interactions
  • Registering event callbacks
  • Any operation requiring other components to exist

Return Value:

  • true — Success, proceed to ACTIVE state
  • false — Failure, application will fail

Example:

bool postInit() override {
    // Find MQTT component (must exist by now)
    mqtt = findComponent<ksMqttConnector>();
    if (!mqtt) {
        return false;  // Dependency not found
    }
    
    // Subscribe to topics
    mqtt->subscribe("device/control");
    
    // Find LED for feedback
    led = findComponent<ksLed>();
    
    return true;
}

loop()

Called repeatedly by the framework. Use for:

  • Polling sensors
  • Processing data
  • Implementing component logic
  • Checking time-based conditions

Return Value:

  • true — Continue running
  • false — Component failed, triggers app rotation

Important: Must return quickly, no blocking calls!

Example:

bool loop() override {
    // Check timer (non-blocking)
    if (millis() - lastCheck > interval) {
        
        // Read sensor
        float value = readSensor();
        
        // Send via MQTT
        if (mqtt && mqtt->isConnected()) {
            mqtt->publish("sensor", String(value));
        }
        
        lastCheck = millis();
    }
    
    return true;
}

🔌 Component Communication

Method 1: Direct References

Get reference to other component in postInit():

class MyComponent : public ksComponent
{
private:
    std::shared_ptr<ksMqttConnector> mqtt;
    std::shared_ptr<ksLed> led;
    
protected:
    bool postInit() override {
        mqtt = findComponent<ksMqttConnector>();
        led = findComponent<ksLed>();
        
        if (!mqtt || !led) {
            return false;
        }
        
        // Now use them
        mqtt->onMessage([this, led](auto topic, auto payload) {
            led->toggle();
        });
        
        return true;
    }
};

Method 2: Event System

Use events for loose coupling:

// Publisher
bool loop() override {
    if (eventDetected()) {
        publishEvent("sensor_triggered", data);
    }
    return true;
}

// Subscriber
bool postInit() override {
    subscribe("sensor_triggered", [](auto data) {
        // Handle event
        activateActuator();
    });
    return true;
}

Method 3: Shared State

Store data in application:

// Save state
getApplication()->setData("sensorValue", value);

// Retrieve state
auto value = getApplication()->getData("sensorValue");

📝 RTTI System

The framework uses a custom RTTI (Run-Time Type Information) system to enable:

  • Type-safe component discovery
  • Safe component casting
  • Dynamic type checking

Required Macro

Every component MUST use this macro at the top of the class:

class MyComponent : public ksComponent
{
    KSF_RTTI_DECLARATIONS(MyComponent, ksComponent)
    // ...
};

Type Casting

// Safe cast with check
auto comp = findComponent<ksComponent>();
if (comp && comp->isA<MyComponent>()) {
    auto myComp = comp->asA<MyComponent>();
    // Use myComp
}

💡 Best Practices

1. Keep Components Focused

// ✅ Good - Single responsibility
class TemperatureSensor : public ksComponent {
    bool loop() override {
        float temp = readTemp();
        publish("temp", temp);
        return true;
    }
};

// ❌ Bad - Multiple responsibilities
class KitchenSink : public ksComponent {
    bool loop() override {
        readTemp();
        connectWiFi();
        updateDisplay();
        sendEmail();
        return true;
    }
};

2. Non-Blocking Loop

bool loop() override {
    // ✅ Good - Non-blocking
    if (millis() - lastTime > interval) {
        doWork();
        lastTime = millis();
    }
    return true;
}

// ❌ Bad - Blocking delay
bool loop() override {
    doWork();
    delay(1000);  // Don't use delay()!
    return true;
}

3. Clean Up Resources

~MyComponent() override {
    // Clean up
    if (sensorActive) {
        sensor.end();
    }
    
    if (buffer) {
        free(buffer);
    }
}

4. Validate Dependencies

bool postInit() override {
    mqtt = findComponent<ksMqttConnector>();
    
    // Validate required dependency
    if (!mqtt) {
        return false;  // Can't work without it
    }
    
    // Optional dependency
    led = findComponent<ksLed>();
    // Can work with or without LED
    
    return true;
}

5. Use Member Variables for State

class SensorComponent : public ksComponent
{
private:
    float lastValue;
    unsigned long lastRead;
    int errorCount;
    
protected:
    bool loop() override {
        // Access member state
        if (errorCount > maxErrors) {
            return false;
        }
        return true;
    }
};

🎨 Component Examples

Example 1: Simple Sensor

class TempSensor : public ksComponent
{
    KSF_RTTI_DECLARATIONS(TempSensor, ksComponent)
    
private:
    std::shared_ptr<ksMqttConnector> mqtt;
    unsigned long lastRead = 0;
    const unsigned long interval = 5000;
    
protected:
    bool init() override {
        lastRead = millis();
        return true;
    }
    
    bool postInit() override {
        mqtt = findComponent<ksMqttConnector>();
        return true;  // MQTT optional
    }
    
    bool loop() override {
        if (millis() - lastRead >= interval) {
            float temp = readTemperature();
            
            if (mqtt && mqtt->isConnected()) {
                mqtt->publish("sensor/temperature", String(temp));
            }
            
            lastRead = millis();
        }
        return true;
    }
    
    float readTemperature() {
        // Read actual sensor here
        return 22.5;
    }
};

Example 2: Event-Driven Actuator

class RelayController : public ksComponent
{
    KSF_RTTI_DECLARATIONS(RelayController, ksComponent)
    
private:
    int relayPin;
    
protected:
    bool init() override {
        pinMode(relayPin, OUTPUT);
        digitalWrite(relayPin, LOW);
        return true;
    }
    
    bool postInit() override {
        auto mqtt = findComponent<ksMqttConnector>();
        if (mqtt) {
            mqtt->onMessage([this](auto topic, auto payload) {
                if (strcmp(topic, "relay/set") == 0) {
                    if (payload == "ON") {
                        digitalWrite(relayPin, HIGH);
                    } else if (payload == "OFF") {
                        digitalWrite(relayPin, LOW);
                    }
                }
            });
        }
        return true;
    }
    
public:
    RelayController(int pin) : relayPin(pin) {}
};

Example 3: Complex Component with State Machine

class PumpController : public ksComponent
{
    KSF_RTTI_DECLARATIONS(PumpController, ksComponent)
    
private:
    enum State { IDLE, PRIMING, RUNNING, SHUTDOWN };
    State state = IDLE;
    
    std::shared_ptr<ksMqttConnector> mqtt;
    unsigned long stateTime = 0;
    
protected:
    bool postInit() override {
        mqtt = findComponent<ksMqttConnector>();
        return true;
    }
    
    bool loop() override {
        switch (state) {
            case IDLE:
                if (shouldStart()) {
                    state = PRIMING;
                    stateTime = millis();
                    mqtt->publish("pump/state", "PRIMING");
                }
                break;
                
            case PRIMING:
                if (millis() - stateTime > 5000) {
                    state = RUNNING;
                    mqtt->publish("pump/state", "RUNNING");
                }
                break;
                
            case RUNNING:
                if (shouldStop()) {
                    state = SHUTDOWN;
                    mqtt->publish("pump/state", "SHUTDOWN");
                }
                break;
                
            case SHUTDOWN:
                if (millis() - stateTime > 3000) {
                    state = IDLE;
                    mqtt->publish("pump/state", "IDLE");
                }
                break;
        }
        return true;
    }
};

⚠️ Common Mistakes

Don't: Use delay()

bool loop() override {
    doSomething();
    delay(1000);  // ❌ Blocks entire framework
    return true;
}

Don't: Block in init()

bool init() override {
    while (!wifiConnected()) {  // ❌ Never blocks
        delay(100);
    }
    return true;
}

Don't: Forget RTTI Macro

class MyComponent : public ksComponent
{
    // ❌ Missing: KSF_RTTI_DECLARATIONS(MyComponent, ksComponent)
    bool init() override { return true; }
};

Don't: Store Raw Pointers

private:
    ksMqttConnector* mqtt;  // ❌ Unsafe

bool postInit() override {
    mqtt = findComponent<ksMqttConnector>().get();  // Don't
    return true;
}

📖 Related Topics

📘 Getting Started

🏗️ Core Concepts

📦 Components Reference

⚙️ Configuration & Management

🔬 Advanced Topics

💡 Examples

🔗 External Resources

Clone this wiki locally