mirror of
https://github.com/alexhopeoconnor/DFTE.git
synced 2026-10-04 04:28:10 +10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aa53c08d9 | ||
|
|
85d34e60c5 | ||
|
|
ec4c32db32 | ||
|
|
42cb8f2324 |
@@ -20,10 +20,15 @@ void setup() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint8_t chunk[128];
|
||||
if (!TemplateRenderer::isComplete(context) && !TemplateRenderer::hasError(context)) {
|
||||
Serial.write(chunk, TemplateRenderer::renderNextChunk(context, chunk, sizeof(chunk)));
|
||||
if (TemplateRenderer::hasError(context)) {
|
||||
// Stop or report the invalid root/placeholder; completion alone is not success.
|
||||
return;
|
||||
}
|
||||
if (TemplateRenderer::isComplete(context)) return;
|
||||
|
||||
uint8_t chunk[128];
|
||||
const size_t written = TemplateRenderer::renderNextChunk(context, chunk, sizeof(chunk));
|
||||
if (written > 0) Serial.write(chunk, written);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+9
-5
@@ -1,6 +1,6 @@
|
||||
# Async web responses
|
||||
|
||||
Build a `PlaceholderRegistry` once during setup, but allocate a fresh `TemplateContext` for each request. A shared context would mix rendering state when clients overlap.
|
||||
Build a `PlaceholderRegistry` once during setup, but allocate a fresh `TemplateContext` for each request. A shared context would mix rendering state when clients overlap. The handler below assumes setup has populated the long-lived `registry`; see [StreamingAsync](../examples/StreamingAsync/) for the complete project.
|
||||
|
||||
| Need | Use |
|
||||
| --- | --- |
|
||||
@@ -17,8 +17,8 @@ void sendTemplate(AsyncWebServerRequest* request, const char* root) {
|
||||
auto context = std::make_shared<TemplateContext>();
|
||||
context->setRegistry(registry.get());
|
||||
TemplateRenderer::initializeContext(*context, root);
|
||||
request->onDisconnect([context]() mutable { context.reset(); });
|
||||
|
||||
// The helper retains the request-owned context until the response completes or disconnects.
|
||||
AsyncWebServerResponse* response =
|
||||
TemplateEngineAsyncWeb::beginSafeTemplateResponse(
|
||||
request, "text/html; charset=utf-8", context, 128
|
||||
@@ -36,12 +36,15 @@ Use `beginBorrowedChunkedResponse()` when the application already owns a small,
|
||||
```cpp
|
||||
struct ResponseSlot {
|
||||
bool busy = false;
|
||||
uint32_t lease = 0;
|
||||
TemplateContext context;
|
||||
};
|
||||
|
||||
ResponseSlot slots[2];
|
||||
|
||||
void releaseSlot(ResponseSlot& slot) {
|
||||
void releaseSlot(ResponseSlot& slot, uint32_t lease) {
|
||||
// A completed response can disconnect after this slot has been reused.
|
||||
if (!slot.busy || slot.lease != lease) return;
|
||||
slot.context.reset();
|
||||
slot.busy = false;
|
||||
}
|
||||
@@ -60,6 +63,7 @@ void sendBoundedTemplate(AsyncWebServerRequest* request, const char* root) {
|
||||
}
|
||||
|
||||
slot->busy = true;
|
||||
const uint32_t lease = ++slot->lease;
|
||||
slot->context.setRegistry(registry.get());
|
||||
TemplateRenderer::initializeContext(slot->context, root);
|
||||
request->send(TemplateEngineAsyncWeb::beginBorrowedChunkedResponse(
|
||||
@@ -71,11 +75,11 @@ void sendBoundedTemplate(AsyncWebServerRequest* request, const char* root) {
|
||||
[](const ResponseSlot& state) {
|
||||
return TemplateEngineAsyncWeb::isTemplateTerminal(state.context);
|
||||
},
|
||||
releaseSlot));
|
||||
[lease](ResponseSlot& state) { releaseSlot(state, lease); }));
|
||||
}
|
||||
```
|
||||
|
||||
Use this form only while the slot itself has static or otherwise guaranteed lifetime. Use the `shared_ptr` form above for a request-owned dynamic context.
|
||||
Use this form only while the slot itself has static or otherwise guaranteed lifetime. The lease makes a late disconnect from an old response a no-op after the slot has been reused. Use the `shared_ptr` form above for a request-owned dynamic context.
|
||||
|
||||
Use [StreamingAsync](../examples/StreamingAsync/) for a complete SoftAP/captive-portal project.
|
||||
|
||||
|
||||
@@ -5,10 +5,14 @@ stack and read buffer at runtime, so choosing capacities cannot make a consumer
|
||||
and the compiled library disagree about object size.
|
||||
|
||||
```cpp
|
||||
TemplateContext standard; // default: 16 stack frames, 512-byte buffer
|
||||
TemplateContext pageContext(6, 128); // known shallow response, smaller allocation
|
||||
if (!pageContext.isReady()) {
|
||||
// Allocation failed; do not start a response with this context.
|
||||
// Keep each context alive for the full response that uses it.
|
||||
TemplateContext standard; // Default: 16 stack frames, 512-byte buffer.
|
||||
TemplateContext pageContext(6, 128); // Known shallow response, smaller allocation.
|
||||
|
||||
void setup() {
|
||||
if (!standard.isReady() || !pageContext.isReady()) {
|
||||
// Allocation failed; do not start a response with either context.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -2,6 +2,36 @@
|
||||
|
||||
Released applications should use a public Git tag. While changing DFTE with a sibling library, select a local `symlink://` or `file://` dependency from an ignored PlatformIO override rather than changing tracked application dependencies.
|
||||
|
||||
## Target pins
|
||||
|
||||
DFTE uses one maintained ESP32 fixture baseline:
|
||||
|
||||
| Test selector | pioarduino platform | Framework stack | Purpose |
|
||||
| --- | --- | --- | --- |
|
||||
| `esp32` | `55.03.311` | Arduino-ESP32 3.3.11 / ESP-IDF 5.5.5 | Maintained baseline |
|
||||
|
||||
These are test-harness fixture lanes, not DFTE package dependencies: a consuming
|
||||
application owns its `platform` pin and tests the complete framework/toolchain
|
||||
stack. Do not copy a compiler or toolchain package between lanes; each pinned
|
||||
pioarduino platform resolves its matched framework, uploader, and toolchain.
|
||||
|
||||
All DFTE ESP8266 test and example environments pin framework commit `521ae60`
|
||||
for the upstream Postmortem large-jump linker fix. The exact rationale and
|
||||
update rule are in the shared [ESP8266 linker-workaround
|
||||
note](https://github.com/alexhopeoconnor/arduino-home-assistant/blob/main/docs/ESP8266-LINKER-WORKAROUND.md).
|
||||
|
||||
For the pioarduino release-to-Core mapping and cache-collision diagnosis, see
|
||||
[DeviceFramework's toolchain guide](https://github.com/alexhopeoconnor/DeviceFramework/blob/main/docs/TOOLCHAINS.md).
|
||||
|
||||
`./scripts/test.sh` uses the persistent PlatformIO Core/cache shared by the
|
||||
maintained framework repositories:
|
||||
`${XDG_CACHE_HOME:-$HOME/.cache}/arduino-framework-platformio/core-3.3.11`.
|
||||
All maintained ESP32 lanes pin this exact graph, avoiding repeated downloads
|
||||
while keeping stale global `tool-esptoolpy` metadata from shadowing the current
|
||||
pioarduino uploader. Override it with `DFTE_PLATFORMIO_CORE_DIR`,
|
||||
`DFTE_PLATFORMIO_PACKAGES_DIR`, and `DFTE_PLATFORMIO_CACHE_DIR` for another
|
||||
disk; the script never clears that cache or pins one compiler separately.
|
||||
|
||||
Start a release with `bump-version.sh`. It updates package metadata and canonical installation snippets, then creates the changelog section. Replace its generated TODO with the release summary and update any behavioural documentation before running:
|
||||
|
||||
```bash
|
||||
@@ -10,6 +40,8 @@ Start a release with `bump-version.sh`. It updates package metadata and canonica
|
||||
./scripts/check-docs.sh
|
||||
./scripts/test.sh compile --platform esp8266
|
||||
./scripts/test.sh compile --platform esp32
|
||||
./scripts/test.sh examples --platform esp8266
|
||||
./scripts/test.sh examples --platform esp32
|
||||
./scripts/prepare-release.sh vMAJOR.MINOR.PATCH --tag
|
||||
```
|
||||
|
||||
|
||||
@@ -27,11 +27,15 @@ void setup() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint8_t chunk[128];
|
||||
if (!TemplateRenderer::isComplete(context) && !TemplateRenderer::hasError(context)) {
|
||||
const size_t written = TemplateRenderer::renderNextChunk(context, chunk, sizeof(chunk));
|
||||
Serial.write(chunk, written);
|
||||
if (TemplateRenderer::hasError(context)) {
|
||||
// Report the invalid root or placeholder once in production, then stop this response.
|
||||
return;
|
||||
}
|
||||
if (TemplateRenderer::isComplete(context)) return;
|
||||
|
||||
uint8_t chunk[128];
|
||||
const size_t written = TemplateRenderer::renderNextChunk(context, chunk, sizeof(chunk));
|
||||
if (written > 0) Serial.write(chunk, written);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+12
-2
@@ -1,13 +1,23 @@
|
||||
# Testing
|
||||
|
||||
The two PlatformIO Unity commands compile the complete DFTE test suites without uploading or executing them, so they require no attached board.
|
||||
The PlatformIO Unity commands compile the complete DFTE test suites without uploading or executing them, so they require no attached board.
|
||||
|
||||
```bash
|
||||
./scripts/test.sh compile --platform esp8266
|
||||
./scripts/test.sh compile --platform esp32
|
||||
./scripts/test.sh examples --platform esp8266
|
||||
./scripts/test.sh examples --platform esp32
|
||||
```
|
||||
|
||||
The test environments include the library sources with `test_build_src = yes`. CI runs both target checks on the maintained branch and pull requests.
|
||||
`esp32` uses pioarduino `55.03.311` (Arduino-ESP32 3.3.11 / ESP-IDF 5.5.5).
|
||||
The `compile` commands compile the complete suites with `test_build_src = yes`; the example commands
|
||||
compile every standalone project on each target, protecting the code that the
|
||||
documentation links users to. CI runs every listed target lane on the
|
||||
maintained branch and pull requests.
|
||||
|
||||
The ESP32 lane uses a persistent dedicated Core/cache directory so the
|
||||
package-form Arduino-ESP32 uploader cannot inherit stale global Python
|
||||
metadata. The test script never clears that cache.
|
||||
|
||||
The standalone examples are buildable PlatformIO projects; see [examples](../examples/README.md).
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ build_flags =
|
||||
-I$PROJECT_LIBDEPS_DIR/$PIOENV/ESPAsyncTCP/src
|
||||
|
||||
[env:dashboard_esp32]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip
|
||||
board = esp32dev
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
|
||||
@@ -15,6 +15,5 @@ platform_packages =
|
||||
platformio/framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#521ae60a89e64bb0d1eb7a0b7addf620ced5cad3
|
||||
|
||||
[env:example_esp32]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip
|
||||
board = esp32dev
|
||||
|
||||
|
||||
@@ -37,15 +37,19 @@ void setup() {
|
||||
while (!TemplateRenderer::isComplete(ctx) && !TemplateRenderer::hasError(ctx)) {
|
||||
size_t written = TemplateRenderer::renderNextChunk(ctx, buffer, sizeof(buffer));
|
||||
if (!written) {
|
||||
Serial.println(F("\nRendering stalled before completion."));
|
||||
break;
|
||||
}
|
||||
Serial.write(buffer, written);
|
||||
}
|
||||
|
||||
Serial.println(F("\nRendering complete."));
|
||||
if (TemplateRenderer::hasError(ctx)) {
|
||||
Serial.println(F("\nRendering failed."));
|
||||
} else if (TemplateRenderer::isComplete(ctx)) {
|
||||
Serial.println(F("\nRendering complete."));
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Nothing else to do in the basic example.
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,5 @@ platform_packages =
|
||||
platformio/framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#521ae60a89e64bb0d1eb7a0b7addf620ced5cad3
|
||||
|
||||
[env:example_esp32]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip
|
||||
board = esp32dev
|
||||
|
||||
|
||||
@@ -196,11 +196,16 @@ void renderToSerial() {
|
||||
while (!TemplateRenderer::isComplete(ctx) && !TemplateRenderer::hasError(ctx)) {
|
||||
size_t written = TemplateRenderer::renderNextChunk(ctx, buffer, sizeof(buffer));
|
||||
if (!written) {
|
||||
Serial.println(F("\nRendering stalled before completion."));
|
||||
break;
|
||||
}
|
||||
Serial.write(buffer, written);
|
||||
}
|
||||
Serial.println();
|
||||
if (TemplateRenderer::hasError(ctx)) {
|
||||
Serial.println(F("\nRendering failed."));
|
||||
} else if (TemplateRenderer::isComplete(ctx)) {
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -218,4 +223,3 @@ void setup() {
|
||||
void loop() {
|
||||
// Nothing to do in loop for this example.
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ pio device monitor -d examples/HelloPlaceholder -e example_esp8266
|
||||
|
||||
Choose the corresponding ESP32 environment where provided. The examples use the checked-out DFTE source, so they double as practical integration checks for this repository.
|
||||
|
||||
The `example_esp32` / `dashboard_esp32` environments use Arduino-ESP32 3.3.11.
|
||||
A consuming application should select and pin its own complete PlatformIO
|
||||
platform stack.
|
||||
|
||||
| Example | Start here when you want to… |
|
||||
| --- | --- |
|
||||
| [HelloPlaceholder](HelloPlaceholder/) | understand the smallest registry/context/chunk flow over serial |
|
||||
|
||||
@@ -24,7 +24,7 @@ build_flags =
|
||||
-I$PROJECT_LIBDEPS_DIR/$PIOENV/ESPAsyncTCP/src
|
||||
|
||||
[env:example_esp32]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip
|
||||
board = esp32dev
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
@@ -34,4 +34,3 @@ build_flags =
|
||||
-DSOC_WIFI_SUPPORTED=1
|
||||
-I${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network/src
|
||||
-I$PROJECT_LIBDEPS_DIR/$PIOENV/AsyncTCP/src
|
||||
|
||||
|
||||
+4
-1
@@ -9,7 +9,10 @@ test_framework = unity
|
||||
test_build_src = yes
|
||||
|
||||
[env:test_template_engine_esp32]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
; Arduino-ESP32 3.3.11 / ESP-IDF 5.5.5 is the maintained ESP32 baseline.
|
||||
; Pin the complete pioarduino platform so its framework and toolchain stay
|
||||
; matched; PlatformIO must not select one from an unrelated package cache.
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip
|
||||
board = nodemcu-32s
|
||||
framework = arduino
|
||||
test_framework = unity
|
||||
|
||||
@@ -9,6 +9,32 @@ required=(
|
||||
examples/README.md
|
||||
)
|
||||
|
||||
check_cpp_fence_scope() {
|
||||
local markdown="$1"
|
||||
awk '
|
||||
function brace_delta(line, copy) {
|
||||
copy = line
|
||||
return gsub(/\{/, "{", copy) - gsub(/\}/, "}", copy)
|
||||
}
|
||||
/^```cpp[[:space:]]*$/ { in_cpp = 1; depth = 0; next }
|
||||
in_cpp && /^```[[:space:]]*$/ { in_cpp = 0; next }
|
||||
in_cpp {
|
||||
line = $0
|
||||
sub(/^[[:space:]]+/, "", line)
|
||||
if (depth == 0 &&
|
||||
(line ~ /^(if|for|while|switch)[[:space:]]*\(/ ||
|
||||
line ~ /^[A-Za-z_][A-Za-z0-9_:]*::[A-Za-z0-9_]+[[:space:]]*\(/ ||
|
||||
line ~ /^[A-Za-z_][A-Za-z0-9_]*\./ ||
|
||||
line ~ /^[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(/)) {
|
||||
printf "%s:%d: C++ expression appears at namespace scope; wrap it in a function.\n", FILENAME, FNR > "/dev/stderr"
|
||||
failed = 1
|
||||
}
|
||||
depth += brace_delta($0)
|
||||
}
|
||||
END { exit failed }
|
||||
' "$markdown"
|
||||
}
|
||||
|
||||
for path in "${required[@]}"; do
|
||||
[[ -f "$root/$path" ]] || { echo "Missing required documentation: $path" >&2; exit 1; }
|
||||
done
|
||||
@@ -25,6 +51,10 @@ while IFS= read -r -d '' markdown; do
|
||||
done < <(sed -nE 's/.*\]\(([^ )]+)( "[^"]*")?\).*/\1/p' "$markdown")
|
||||
done < <(find "$root" -path "$root/.git" -prune -o -path '*/.pio' -prune -o -name '*.md' -type f -print0)
|
||||
|
||||
while IFS= read -r markdown; do
|
||||
check_cpp_fence_scope "$markdown"
|
||||
done < <(find "$root" -path "$root/.git" -prune -o -path '*/.pio' -prune -o -name '*.md' -type f -print)
|
||||
|
||||
while IFS= read -r example; do
|
||||
for required in README.md platformio.ini; do
|
||||
[[ -f "$example/$required" ]] || { echo "Incomplete example: ${example#$root/} is missing $required" >&2; exit 1; }
|
||||
|
||||
+24
-4
@@ -13,15 +13,35 @@ case "${3:-}" in
|
||||
esac
|
||||
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
if [[ "$1" == "compile" ]]; then
|
||||
pio_for_platform() {
|
||||
if [[ "$platform" != "esp32" ]]; then
|
||||
pio "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
# Keep the maintained Core 3.3.11 package-form uploader in one persistent,
|
||||
# repository-owned cache. This avoids unrelated global package metadata;
|
||||
# it is never cleared by this script.
|
||||
local core_dir packages_dir cache_dir
|
||||
core_dir="${DFTE_PLATFORMIO_CORE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/arduino-framework-platformio/core-3.3.11}"
|
||||
packages_dir="${DFTE_PLATFORMIO_PACKAGES_DIR:-$core_dir/packages}"
|
||||
cache_dir="${DFTE_PLATFORMIO_CACHE_DIR:-$core_dir/cache}"
|
||||
install -d -m 700 "$core_dir" "$packages_dir" "$cache_dir"
|
||||
PLATFORMIO_CORE_DIR="$core_dir" PLATFORMIO_PACKAGES_DIR="$packages_dir" \
|
||||
PLATFORMIO_CACHE_DIR="$cache_dir" pio "$@"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
compile)
|
||||
case "$platform" in
|
||||
esp8266) test_environment="test_template_engine_8266" ;;
|
||||
esp32) test_environment="test_template_engine_esp32" ;;
|
||||
esac
|
||||
pio test -d "$root" -e "$test_environment" --without-uploading --without-testing
|
||||
pio_for_platform test -d "$root" -e "$test_environment" --without-uploading --without-testing
|
||||
echo "DFTE compile check passed for $platform"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
suffix="$platform"
|
||||
mapfile -t examples < <(find "$root/examples" -mindepth 2 -maxdepth 2 -type f -name platformio.ini -printf '%h\n' | sort)
|
||||
@@ -32,6 +52,6 @@ fi
|
||||
for example in "${examples[@]}"; do
|
||||
env_name="example_$suffix"
|
||||
[[ "$(basename "$example")" == "AsyncDashboardDemo" ]] && env_name="dashboard_$suffix"
|
||||
pio run -d "$example" -e "$env_name" </dev/null
|
||||
pio_for_platform run -d "$example" -e "$env_name" </dev/null
|
||||
done
|
||||
echo "DFTE examples compile check passed for $platform"
|
||||
|
||||
Reference in New Issue
Block a user