-
-
Notifications
You must be signed in to change notification settings - Fork 3
Advanced Topics
Deep dive into advanced features and internals of ksIotFrameworkLib.
This section covers:
- Custom RTTI system
- Event system
- Power management
- Memory optimization
- Custom component development
- Event-driven architecture
The framework implements a custom Run-Time Type Information system for type-safe component casting.
Arduino C++ environments often disable standard C++ RTTI to reduce code size. The framework provides a lightweight alternative.
Every component MUST use this macro:
class MyComponent : public ksComponent
{
KSF_RTTI_DECLARATIONS(MyComponent, ksComponent)
// ... rest of class
};Generates type identification methods:
-
isA<T>()— Check if component is type T -
asA<T>()— Cast component to type T -
getTypeName()— Get type name string
// Get generic component
auto comp = findComponent<ksComponent>();
// Check type
if (comp && comp->isA<MyComponent>()) {
// Safe cast
auto myComp = comp->asA<MyComponent>();
myComp->doSomething();
}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");
}
}The framework provides a publish-subscribe event mechanism for loose coupling.
ksEvent — Core event implementation ksEventHandle — Event subscription handle ksEventInterface — Event interface definition
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;
}
};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;
}
}
};// 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);
});- Loose coupling — Components don't need direct references
- Multiple subscribers — Many components can react to one event
- Dynamic — Subscribe/unsubscribe at runtime
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)
For optimal modem sleep, configure your access point:
DTIM Interval: 3
This allows ESP32 to wake periodically and still receive messages.
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.
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;
}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;
}#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;
}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;
}
};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;
}
};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;
}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;
}
}
};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;
}
};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;
}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);
}
};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();
}
};- Component — Component lifecycle
- Architecture — Framework design
- Examples — Practical implementations
🤖 This wiki is automatically generated and may contain errors.
Please report any issues here:
👉 https://github.com/cziter15/ksIotFrameworkLib/issues