Skip to content

Advanced Topics

cziter15 edited this page Feb 14, 2026 · 1 revision

Advanced Topics

Deep dive into advanced features and internals of ksIotFrameworkLib.


🎯 Overview

This section covers:

  • Custom RTTI system
  • Event system
  • Power management
  • Memory optimization
  • Custom component development
  • Event-driven architecture

🔮 Custom RTTI System

The framework implements a custom Run-Time Type Information system for type-safe component casting.

Why Custom RTTI?

Arduino C++ environments often disable standard C++ RTTI to reduce code size. The framework provides a lightweight alternative.

Required Macro

Every component MUST use this macro:

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

What the Macro Does

Generates type identification methods:

  • isA<T>() — Check if component is type T
  • asA<T>() — Cast component to type T
  • getTypeName() — Get type name string

Type-Safe Casting

// Get generic component
auto comp = findComponent<ksComponent>();

// Check type
if (comp && comp->isA<MyComponent>()) {
    // Safe cast
    auto myComp = comp->asA<MyComponent>();
    myComp->doSomething();
}

Casting from Base

void processComponent(ksComponent* comp)
{
    if (comp->isA<ksLed>()) {
        auto led = comp->asA<ksLed>();
        led->on();
    }
    else if (comp->isA<ksMqttConnector>()) {
        auto mqtt = comp->asA<ksMqttConnector>();
        mqtt->publish("status", "online");
    }
}

🔔 Event System

The framework provides a publish-subscribe event mechanism for loose coupling.

Event Classes

ksEvent — Core event implementation ksEventHandle — Event subscription handle ksEventInterface — Event interface definition

Publishing Events

class SensorComponent : public ksComponent
{
private:
    ksEvent* buttonEvent;
    
protected:
    bool init() override {
        // Create event
        buttonEvent = new ksEvent("button_pressed");
        return true;
    }
    
    bool loop() override {
        if (buttonPressed()) {
            // Publish event
            buttonEvent->publish();
        }
        return true;
    }
};

Subscribing to Events

class ActuatorComponent : public ksComponent
{
private:
    ksEventHandle* subscription;
    
protected:
    bool postInit() override {
        // Find event publisher
        auto sensor = findComponent<SensorComponent>();
        if (!sensor) return false;
        
        auto event = sensor->getEvent("button_pressed");
        
        // Subscribe with callback
        subscription = event->subscribe([]() {
            activateActuator();
        });
        
        return true;
    }
    
    ~ActuatorComponent() {
        // Cleanup subscription
        if (subscription) {
            delete subscription;
        }
    }
};

Event with Data

// Publish with data
struct SensorData {
    float temperature;
    float humidity;
};

SensorData data{22.5, 45.0};
event->publish(&data);

// Subscribe with data
event->subscribe([](void* data) {
    SensorData* sensorData = (SensorData*)data;
    Serial.println(sensorData->temperature);
});

Event Benefits

  • Loose coupling — Components don't need direct references
  • Multiple subscribers — Many components can react to one event
  • Dynamic — Subscribe/unsubscribe at runtime

🔋 Power Management

Modem Sleep

ESP32 supports modem sleep to reduce power consumption when idle.

Enable in ksWifiConnector:

bool init() override {
    // Enable modem sleep
    addComponent<ksWifiConnector>("Device", true);  // 2nd param = enable sleep
    return true;
}

Power Consumption:

  • Active (no sleep): ~100mA
  • Modem sleep: ~20mA
  • Deep sleep: ~10µA (app-controlled)

DTIM Configuration

For optimal modem sleep, configure your access point:

DTIM Interval: 3

This allows ESP32 to wake periodically and still receive messages.

Manual Deep Sleep

For battery-powered devices:

bool loop() override {
    // Do work
    readSensor();
    sendData();
    
    // Sleep for 5 minutes
    ESP.deepSleep(300e6);  // microseconds
    
    return true;
}

Note: Deep sleep resets the device.


🧠 Memory Optimization

Reduce Memory Footprint

1. Disable Unused Features:

bool init() override {
    #ifndef ENABLE_MQTT
        // Don't add MQTT if not needed
        addComponent<ksMqttConnector>();
    #endif
    
    return true;
}

2. Use String Literals Carefully:

// ❌ Bad - Duplicates strings
mqtt->publish("topic", "message");
mqtt->publish("topic", "message");

// ✅ Good - Reuse strings
const char* TOPIC = "topic";
const char* MSG = "message";
mqtt->publish(TOPIC, MSG);
mqtt->publish(TOPIC, MSG);

3. Prefer Local Variables:

bool loop() override {
    // ✅ Stack allocated
    char buffer[64];
    
    // ❌ Heap allocation
    char* buffer = new char[64];
    delete buffer;
    
    return true;
}

Monitoring Memory

bool loop() override {
    static unsigned long lastCheck = 0;
    
    if (millis() - lastCheck > 60000) {
        Serial.printf("Free heap: %u bytes\n", ESP.getFreeHeap());
        lastCheck = millis();
    }
    return true;
}

🎨 Custom Component Development

Complete Example: Temperature Sensor

#pragma once
#include <ksf/ksComponent.h>
#include <DHT.h>

class TempSensor : public ksComponent
{
    KSF_RTTI_DECLARATIONS(TempSensor, ksComponent)
    
private:
    DHT* dht;
    uint8_t pin;
    
    std::shared_ptr<ksMqttConnector> mqtt;
    
protected:
    bool init() override {
        // Initialize sensor
        dht = new DHT(pin, DHT22);
        dht->begin();
        return true;
    }
    
    bool postInit() override {
        // Find MQTT (optional)
        mqtt = findComponent<ksMqttConnector>();
        return true;
    }
    
    bool loop() override {
        // Read every 5 seconds
        static unsigned long lastRead = 0;
        const unsigned long interval = 5000;
        
        if (millis() - lastRead >= interval) {
            float temp = dht->readTemperature();
            float hum = dht->readHumidity();
            
            // Publish via MQTT if available
            if (mqtt && mqtt->isConnected()) {
                mqtt->publish("sensor/temperature", String(temp));
                mqtt->publish("sensor/humidity", String(hum));
            }
            
            lastRead = millis();
        }
        
        return true;
    }
    
public:
    TempSensor(uint8_t sensorPin) : pin(sensorPin) {}
    
    ~TempSensor() override {
        if (dht) {
            delete dht;
        }
    }
};

Usage:

bool init() override {
    addComponent<TempSensor>(4);  // DHT on GPIO 4
    return true;
}

🔄 Event-Driven Architecture

Pattern: Publisher-Subscriber

class ButtonComponent : public ksComponent
{
private:
    ksEvent* clickEvent;
    
protected:
    bool init() override {
        clickEvent = new ksEvent("button_click");
        return true;
    }
    
    bool loop() override {
        if (buttonClicked()) {
            clickEvent->publish();
        }
        return true;
    }
};

class RelayComponent : public ksComponent
{
private:
    ksEventHandle* handle;
    
protected:
    bool postInit() override {
        auto button = findComponent<ButtonComponent>();
        if (!button) return true;
        
        auto event = button->getEvent("button_click");
        handle = event->subscribe([this]() {
            toggleRelay();
        });
        
        return true;
    }
};

🔌 Advanced Configuration

JSON Configuration Parser

class AdvancedConfig : public ksConfigProvider
{
public:
    int getSensorInterval() {
        String val = getParam("sensor_interval");
        return val.toInt();
    }
    
    std::vector<String> getMqttTopics() {
        String topics = getParam("mqtt_topics");
        std::vector<String> result;
        
        // Parse comma-separated
        char* token = strtok((char*)topics.c_str(), ",");
        while (token) {
            result.push_back(String(token));
            token = strtok(nullptr, ",");
        }
        return result;
    }
};

🌐 Network Advanced

Custom MQTT Callbacks

bool postInit() override {
    auto mqtt = findComponent<ksMqttConnector>();
    if (!mqtt) return false;
    
    // Subscribe with pattern
    mqtt->subscribe("sensor/#");
    
    // Advanced message handler
    mqtt->onMessage([this](const char* topic, const char* payload) {
        Serial.printf("Topic: %s, Payload: %s\n", topic, payload);
        
        // Parse topic
        if (strcmp(topic, "sensor/command") == 0) {
            handleCommand(payload);
        }
        else if (strncmp(topic, "sensor/config/", 14) == 0) {
            const char* key = topic + 14;
            updateConfig(key, payload);
        }
    });
    
    return true;
}

WiFi Events

class WiFiMonitor : public ksComponent
{
protected:
    bool loop() override {
        static WiFiClass::Status lastStatus = 
            WiFiClass::Status::DISCONNECTED;
        
        auto currentStatus = WiFi.status();
        
        if (currentStatus != lastStatus) {
            onWiFiStatusChange(currentStatus);
            lastStatus = currentStatus;
        }
        
        return true;
    }
    
    void onWiFiStatusChange(WiFiClass::Status status) {
        switch (status) {
            case WiFiClass::Status::CONNECTED:
                Serial.println("WiFi connected");
                break;
            case WiFiClass::Status::DISCONNECTED:
                Serial.println("WiFi disconnected");
                break;
        }
    }
};

🐛 Debugging Techniques

Component State Logging

class DebugComponent : public ksComponent
{
protected:
    bool init() override {
        Serial.println("[DebugComponent] init()");
        return true;
    }
    
    bool postInit() override {
        Serial.println("[DebugComponent] postInit()");
        return true;
    }
    
    bool loop() override {
        static unsigned long counter = 0;
        if (counter++ % 1000 == 0) {
            Serial.printf("[DebugComponent] loop() count: %u\n", counter);
        }
        return true;
    }
};

Memory Tracking

bool loop() override {
    static unsigned long lastHeap = ESP.getFreeHeap();
    unsigned long currentHeap = ESP.getFreeHeap();
    
    if (currentHeap < lastHeap - 1024) {
        Serial.printf("Memory drop: %u -> %u\n", lastHeap, currentHeap);
    }
    
    lastHeap = currentHeap;
    return true;
}

📖 Advanced Examples

Example 1: State Machine Component

class StateMachineComponent : public ksComponent
{
private:
    enum State { IDLE, RUNNING, ERROR, SHUTDOWN };
    State state = IDLE;
    
protected:
    bool loop() override {
        State nextState = state;
        
        switch (state) {
            case IDLE:
                if (shouldStart()) nextState = RUNNING;
                break;
            case RUNNING:
                if (errorDetected()) nextState = ERROR;
                if (shouldStop()) nextState = SHUTDOWN;
                break;
            case ERROR:
                if (recovered()) nextState = IDLE;
                break;
            case SHUTDOWN:
                return false;  // Stop component
        }
        
        if (nextState != state) {
            onStateChange(state, nextState);
            state = nextState;
        }
        
        return true;
    }
    
    void onStateChange(State from, State to) {
        Serial.printf("State: %d -> %d\n", from, to);
    }
};

Example 2: Watchdog Timer

class WatchdogComponent : public ksComponent
{
private:
    unsigned long lastFeed = 0;
    const unsigned long timeout = 30000;  // 30 seconds
    
protected:
    bool init() override {
        lastFeed = millis();
        return true;
    }
    
    bool loop() override {
        // Feed watchdog
        if (millis() - lastFeed > timeout) {
            Serial.println("Watchdog timeout!");
            return false;  // Trigger failover
        }
        return true;
    }
    
    void feed() {
        lastFeed = millis();
    }
};

📖 Related Topics

📘 Getting Started

🏗️ Core Concepts

📦 Components Reference

⚙️ Configuration & Management

🔬 Advanced Topics

💡 Examples

🔗 External Resources

Clone this wiki locally