mirror of
https://github.com/alexhopeoconnor/DFTE.git
synced 2026-10-04 04:28:10 +10:00
222 lines
6.3 KiB
C++
222 lines
6.3 KiB
C++
#include <Arduino.h>
|
|
#include <TemplateEngine.h>
|
|
#include <memory>
|
|
#include <cstring>
|
|
|
|
#include "../../common/templates/LayoutSnippets.h"
|
|
|
|
namespace {
|
|
|
|
struct SubsystemStatus {
|
|
const char* name;
|
|
const char* detail;
|
|
const char* severityClass;
|
|
};
|
|
|
|
constexpr SubsystemStatus SUBSYSTEMS[] = {
|
|
{"Wi-Fi", "Connected", "ok"},
|
|
{"MQTT", "Disconnected", "warn"},
|
|
{"Storage", "Healthy", "ok"},
|
|
{"OTA", "Idle", "info"},
|
|
};
|
|
constexpr size_t SUBSYSTEM_COUNT = sizeof(SUBSYSTEMS) / sizeof(SubsystemStatus);
|
|
|
|
bool maintenanceMode = false;
|
|
|
|
// Templates
|
|
inline const char PROGMEM BASE_TEMPLATE[] = R"HTML(
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>%PAGE_TITLE%</title>
|
|
<style>%CSS%</style>
|
|
</head>
|
|
<body>
|
|
%HEADER%
|
|
<section>
|
|
<h2>Environment</h2>
|
|
<p><strong>Firmware:</strong> %FIRMWARE_VERSION%</p>
|
|
<p><strong>Boot Count:</strong> %BOOT_COUNT%</p>
|
|
</section>
|
|
<section>
|
|
<h2>Maintenance</h2>
|
|
%MAINTENANCE_BANNER%
|
|
</section>
|
|
<section>
|
|
<h2>Subsystems</h2>
|
|
<ul class="subsystems">
|
|
%SUBSYSTEM_LIST%
|
|
</ul>
|
|
</section>
|
|
%FOOTER%
|
|
</body>
|
|
</html>
|
|
)HTML";
|
|
|
|
inline const char PROGMEM SUBSYSTEM_ITEM_TEMPLATE[] = R"HTML(
|
|
<li class="%SEVERITY%">
|
|
<strong>%NAME%</strong>
|
|
<span>%DETAIL%</span>
|
|
</li>
|
|
)HTML";
|
|
|
|
inline const char PROGMEM MAINTENANCE_TRUE_TEMPLATE[] = R"HTML(
|
|
<div class="notice warn">System in maintenance mode. Automations disabled.</div>
|
|
)HTML";
|
|
|
|
inline const char PROGMEM MAINTENANCE_FALSE_TEMPLATE[] = R"HTML(
|
|
<div class="notice ok">All services operating normally.</div>
|
|
)HTML";
|
|
|
|
// RAM data
|
|
static String bootCountBuffer;
|
|
static uint32_t bootCount = 42;
|
|
|
|
const char* getFirmwareVersion() { return "2.3.1"; }
|
|
|
|
const char* getBootCount() {
|
|
bootCountBuffer = String(bootCount);
|
|
return bootCountBuffer.c_str();
|
|
}
|
|
|
|
// Conditional descriptor
|
|
struct MaintenanceState {
|
|
bool enabled;
|
|
} maintenanceState{maintenanceMode};
|
|
|
|
ConditionalBranchResult evaluateMaintenance(void* userData) {
|
|
auto* state = static_cast<MaintenanceState*>(userData);
|
|
return state->enabled ? ConditionalBranchResult::TRUE_BRANCH : ConditionalBranchResult::FALSE_BRANCH;
|
|
}
|
|
|
|
inline const ConditionalDescriptor MAINTENANCE_DESCRIPTOR = {
|
|
evaluateMaintenance,
|
|
"%MAINTENANCE_TRUE%",
|
|
"%MAINTENANCE_FALSE%",
|
|
(void*)&maintenanceState};
|
|
|
|
// Iterator plumbing
|
|
struct SubsystemIteratorState {
|
|
size_t index = 0;
|
|
};
|
|
|
|
SubsystemIteratorState iteratorState;
|
|
PlaceholderEntry subsystemOverrides[SUBSYSTEM_COUNT][3];
|
|
DynamicDataDescriptor subsystemOverrideData[SUBSYSTEM_COUNT][3];
|
|
|
|
const char* getStaticRamValue(void* userData) {
|
|
return static_cast<const char*>(userData);
|
|
}
|
|
|
|
void initialiseOverrides() {
|
|
for (size_t i = 0; i < SUBSYSTEM_COUNT; ++i) {
|
|
const SubsystemStatus& status = SUBSYSTEMS[i];
|
|
|
|
PlaceholderEntry& name = subsystemOverrides[i][0];
|
|
strncpy(name.name, "%NAME%", sizeof(name.name) - 1);
|
|
subsystemOverrideData[i][0] = {getStaticRamValue, nullptr, const_cast<char*>(status.name)};
|
|
name.type = PlaceholderType::DYNAMIC_DATA;
|
|
name.data = &subsystemOverrideData[i][0];
|
|
name.getLength = nullptr;
|
|
|
|
PlaceholderEntry& detail = subsystemOverrides[i][1];
|
|
strncpy(detail.name, "%DETAIL%", sizeof(detail.name) - 1);
|
|
subsystemOverrideData[i][1] = {getStaticRamValue, nullptr, const_cast<char*>(status.detail)};
|
|
detail.type = PlaceholderType::DYNAMIC_DATA;
|
|
detail.data = &subsystemOverrideData[i][1];
|
|
detail.getLength = nullptr;
|
|
|
|
PlaceholderEntry& severity = subsystemOverrides[i][2];
|
|
strncpy(severity.name, "%SEVERITY%", sizeof(severity.name) - 1);
|
|
subsystemOverrideData[i][2] = {getStaticRamValue, nullptr, const_cast<char*>(status.severityClass)};
|
|
severity.type = PlaceholderType::DYNAMIC_DATA;
|
|
severity.data = &subsystemOverrideData[i][2];
|
|
severity.getLength = nullptr;
|
|
}
|
|
}
|
|
|
|
void* iteratorOpen(void* userData) {
|
|
initialiseOverrides();
|
|
auto* state = static_cast<SubsystemIteratorState*>(userData);
|
|
state->index = 0;
|
|
return state;
|
|
}
|
|
|
|
IteratorStepResult iteratorNext(void* handle, IteratorItemView& view) {
|
|
auto* state = static_cast<SubsystemIteratorState*>(handle);
|
|
if (state->index >= SUBSYSTEM_COUNT) {
|
|
return IteratorStepResult::COMPLETE;
|
|
}
|
|
|
|
view.templateData = SUBSYSTEM_ITEM_TEMPLATE;
|
|
view.templateLength = strlen_P(SUBSYSTEM_ITEM_TEMPLATE);
|
|
view.templateIsProgmem = true;
|
|
view.placeholders = subsystemOverrides[state->index];
|
|
view.placeholderCount = 3;
|
|
state->index++;
|
|
return IteratorStepResult::ITEM_READY;
|
|
}
|
|
|
|
void iteratorClose(void*) {}
|
|
|
|
IteratorDescriptor iteratorDescriptor = {
|
|
iteratorOpen,
|
|
iteratorNext,
|
|
iteratorClose,
|
|
&iteratorState};
|
|
|
|
std::shared_ptr<PlaceholderRegistry> buildRegistry() {
|
|
auto registry = std::make_shared<PlaceholderRegistry>();
|
|
registry->registerProgmemData("%CSS%", DFTEExamples::SHARED_CSS);
|
|
registry->registerProgmemTemplate("%HEADER%", DFTEExamples::SHARED_HEADER);
|
|
registry->registerProgmemTemplate("%FOOTER%", DFTEExamples::SHARED_FOOTER);
|
|
|
|
registry->registerRamData("%PAGE_TITLE%", []() -> const char* { return "DFTE Nested Layouts"; });
|
|
registry->registerRamData("%TAGLINE%", []() -> const char* { return "Composing templates with conditionals and iterators"; });
|
|
registry->registerRamData("%FIRMWARE_VERSION%", getFirmwareVersion);
|
|
registry->registerRamData("%BOOT_COUNT%", getBootCount);
|
|
|
|
registry->registerConditional("%MAINTENANCE_BANNER%", &MAINTENANCE_DESCRIPTOR);
|
|
registry->registerProgmemTemplate("%MAINTENANCE_TRUE%", MAINTENANCE_TRUE_TEMPLATE);
|
|
registry->registerProgmemTemplate("%MAINTENANCE_FALSE%", MAINTENANCE_FALSE_TEMPLATE);
|
|
|
|
registry->registerIterator("%SUBSYSTEM_LIST%", &iteratorDescriptor);
|
|
|
|
return registry;
|
|
}
|
|
|
|
void renderToSerial() {
|
|
auto registry = buildRegistry();
|
|
TemplateContext ctx;
|
|
ctx.setRegistry(registry.get());
|
|
TemplateRenderer::initializeContext(ctx, BASE_TEMPLATE);
|
|
|
|
uint8_t buffer[128];
|
|
while (!TemplateRenderer::isComplete(ctx) && !TemplateRenderer::hasError(ctx)) {
|
|
size_t written = TemplateRenderer::renderNextChunk(ctx, buffer, sizeof(buffer));
|
|
if (!written) {
|
|
break;
|
|
}
|
|
Serial.write(buffer, written);
|
|
}
|
|
Serial.println();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
while (!Serial) { /* wait */ }
|
|
|
|
Serial.println();
|
|
Serial.println(F("=== DFTE Nested Layouts Example ==="));
|
|
|
|
renderToSerial();
|
|
}
|
|
|
|
void loop() {
|
|
// Nothing to do in loop for this example.
|
|
}
|
|
|