Skip to content

Commit

Permalink
fix: added async support
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikMeinders committed Dec 19, 2024
1 parent 11da34d commit 6a5a251
Show file tree
Hide file tree
Showing 23 changed files with 657 additions and 70 deletions.
2 changes: 1 addition & 1 deletion ai/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ The journal files should be committed to the repository.

## TODO

- [ ] Transition from blocking webserver and websocket to non-blocking webserver and websocket
- [ ] Make the examples compile, build and run again.
Binary file added examples/.DS_Store
Binary file not shown.
12 changes: 11 additions & 1 deletion examples/basic/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
; https://docs.platformio.org/page/projectconf.html

[platformio]
default_envs = basic-esp8266, basic-esp32
default_envs = basic-esp8266, basic-esp32, basic-esp32s3
workspace_dir = .pio.nosync

[env]
Expand Down Expand Up @@ -46,3 +46,13 @@ build_flags =
${env.build_flags}
-DESP32
-DCORE_DEBUG_LEVEL=5

[env:basic-esp32s3]
platform = espressif32
board = esp32-s3-devkitc-1
build_type = debug
build_flags =
${env.build_flags}
-DESP32
-DESP32S3
-DCORE_DEBUG_LEVEL=5
5 changes: 5 additions & 0 deletions examples/webpage_async/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
10 changes: 10 additions & 0 deletions examples/webpage_async/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}
81 changes: 81 additions & 0 deletions examples/webpage_async/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# PlatformIO environment
PIO = source ~/.platformio/penv/bin/activate && pio

# Directories to watch
SRC_DIR = src
INCLUDE_DIR = include
DATA_DIR = data
LIB_DIR = ../..

# Files to watch for library changes
LIB_FILES = $(shell find $(LIB_DIR)/src $(LIB_DIR)/include -type f 2>/dev/null)
LIB_META = $(LIB_DIR)/library.json

# Source and header files
SRC_FILES = $(shell find $(SRC_DIR) -type f -name '*.cpp' -o -name '*.c' 2>/dev/null)
HEADER_FILES = $(shell find $(INCLUDE_DIR) -type f -name '*.h' 2>/dev/null)
DATA_FILES = $(shell find $(DATA_DIR) -type f 2>/dev/null)

# Timestamp files to track last build
FIRMWARE_TIMESTAMP = .pio.nosync/.firmware_timestamp
FILESYSTEM_TIMESTAMP = .pio.nosync/.filesystem_timestamp
UPLOAD_TIMESTAMP = .pio.nosync/.upload_timestamp

# Create directories if they don't exist
$(shell mkdir -p .pio.nosync)

# Default target
all: build-firmware build-filesystem

# Build and upload firmware
build-firmware: $(FIRMWARE_TIMESTAMP)
$(FIRMWARE_TIMESTAMP): $(SRC_FILES) $(HEADER_FILES) $(LIB_FILES) $(LIB_META)
@echo "Source files changed, rebuilding and uploading firmware..."
@$(PIO) run -t upload || (echo "Firmware build failed"; exit 1)
@touch $(FIRMWARE_TIMESTAMP)

$(UPLOAD_TIMESTAMP): $(FIRMWARE_TIMESTAMP)
@echo "Uploading firmware..."
@$(PIO) run -t upload || (echo "Firmware upload failed"; exit 1)
@touch $(UPLOAD_TIMESTAMP)

# Build and upload filesystem
build-filesystem: $(FILESYSTEM_TIMESTAMP)
$(FILESYSTEM_TIMESTAMP): $(DATA_FILES)
@echo "Data files changed, uploading filesystem..."
@$(PIO) run -t uploadfs || (echo "Filesystem upload failed"; exit 1)
@touch $(FILESYSTEM_TIMESTAMP)

# Clean only build artifacts in .pio.nosync/build
clean:
@echo "Cleaning build artifacts in .pio.nosync/build..."
@rm -f $(FIRMWARE_TIMESTAMP) $(FILESYSTEM_TIMESTAMP)
@rm -rf .pio.nosync/build
@echo "Note: Only removed build artifacts, all source files are preserved"

# Clean everything in .pio.nosync
distclean:
@echo "Deep cleaning all artifacts in .pio.nosync..."
@rm -rf .pio.nosync
@echo "Note: Only removed .pio.nosync directory, all source files are preserved"

# Helper target to show what would be rebuilt
check:
@echo "Checking for changes..."
@if [ -n "$$(find $(SRC_FILES) $(HEADER_FILES) $(LIB_FILES) $(LIB_META) -newer $(FIRMWARE_TIMESTAMP) 2>/dev/null)" ]; then \
echo "Firmware needs rebuild"; \
fi
@if [ ! -f $(FILESYSTEM_TIMESTAMP) ] || [ -n "$$(find $(DATA_FILES) -newer $(FILESYSTEM_TIMESTAMP) 2>/dev/null)" ]; then \
echo "Filesystem needs rebuild"; \
fi

# Force rebuild of specific components
force-firmware:
@rm -f $(FIRMWARE_TIMESTAMP)
@$(MAKE) build-firmware

force-filesystem:
@rm -f $(FILESYSTEM_TIMESTAMP)
@$(MAKE) build-filesystem

.PHONY: all clean distclean check build-firmware build-filesystem force-firmware force-filesystem
104 changes: 104 additions & 0 deletions examples/webpage_async/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# KNX PlatformIO Web Example

This example demonstrates how to create a web interface with real-time updates using WebSocket communication.

## Features

- Web server serving static files from LittleFS
- WebSocket server for real-time updates
- Temperature value broadcasting
- Responsive file serving with proper chunked transfer

## Setup

1. Configure platformio.ini:
```ini
[env:webpage-esp8266]
platform = espressif8266
board = d1
framework = arduino
build_flags =
-DFEATURE_WEB ; Enable web server
-DFEATURE_WEBS ; Enable WebSocket server
```

2. Create your application class:
```cpp
class knxapp : public _knxapp {
public:
void loop() override {
_knxapp::loop(); // Call base class implementation first

if (DUE(BroadcastValue)) {
static float temp = 19.0;
String json = "{\"temperature\":" + String(temp, 1) + "}";
_knxapp::webSocketServer.broadcast(json.c_str());

// Update temperature for next broadcast
temp += 0.1;
if (temp > 25.0) temp = 19.0;
}
}
};
```
3. Prepare your web files:
- Place HTML, CSS, and JavaScript files in the `data` directory
- Upload filesystem using `make force-filesystem`
## Web Interface
1. HTML (data/index.html):
```html
<!DOCTYPE html>
<html>
<head>
<title>KNX Temperature</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="temperature">--.-°C</div>
<script src="script.js"></script>
</body>
</html>
```

2. JavaScript (data/script.js):
```javascript
let ws = null;
const wsPort = 81;

function connect() {
ws = new WebSocket(`ws://${window.location.hostname}:${wsPort}`);

ws.onmessage = function(event) {
const data = JSON.parse(event.data);
document.getElementById('temperature').textContent =
`${data.temperature.toFixed(1)}°C`;
};

ws.onclose = function() {
setTimeout(connect, 1000); // Reconnect after 1 second
};
}

connect();
```

## Implementation Details

1. Web Server:
- Serves static files from LittleFS
- Uses chunked transfer encoding for efficient delivery
- Maintains system responsiveness with regular yield() calls

2. WebSocket Server:
- Runs on port 81
- Broadcasts temperature updates every second
- Handles client connections automatically

## Notes

- Enable both FEATURE_WEB and FEATURE_WEBS in your build flags
- Upload filesystem after making changes to web files
- WebSocket server runs on port 81 to avoid conflicts with web server
22 changes: 22 additions & 0 deletions examples/webpage_async/data/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KNX Temperature Monitor</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>KNX Temperature Monitor</h1>
<div class="temperature-display">
<div class="value">--.-</div>
<div class="unit">°C</div>
</div>
<div class="status">
<div class="connection-status">Connecting...</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
59 changes: 59 additions & 0 deletions examples/webpage_async/data/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
document.addEventListener('DOMContentLoaded', () => {
const temperatureValue = document.querySelector('.value');
const connectionStatus = document.querySelector('.connection-status');
let ws = null;
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;

function connect() {
// Use secure WebSocket if page is served over HTTPS
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.hostname}/ws`;

console.log('Connecting to WebSocket:', wsUrl);
ws = new WebSocket(wsUrl);

ws.onopen = () => {
console.log('WebSocket connected');
connectionStatus.textContent = 'Connected';
connectionStatus.classList.add('connected');
connectionStatus.classList.remove('disconnected');
reconnectAttempts = 0;
};

ws.onclose = () => {
console.log('WebSocket disconnected');
connectionStatus.textContent = 'Disconnected';
connectionStatus.classList.add('disconnected');
connectionStatus.classList.remove('connected');

// Attempt to reconnect
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
connectionStatus.textContent = `Reconnecting (${reconnectAttempts}/${maxReconnectAttempts})...`;
setTimeout(connect, 3000);
} else {
connectionStatus.textContent = 'Connection failed. Please refresh the page.';
}
};

ws.onerror = (error) => {
console.error('WebSocket error:', error);
};

ws.onmessage = (event) => {
console.log('WebSocket message:', event.data);
try {
const data = JSON.parse(event.data);
if (data.temperature !== undefined) {
temperatureValue.textContent = data.temperature.toFixed(1);
}
} catch (e) {
console.error('Error parsing WebSocket message:', e);
}
};
}

// Initial connection
connect();
});
63 changes: 63 additions & 0 deletions examples/webpage_async/data/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}

.container {
background-color: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
text-align: center;
}

h1 {
color: #333;
margin-bottom: 2rem;
font-size: 1.5rem;
}

.temperature-display {
display: flex;
justify-content: center;
align-items: baseline;
margin: 2rem 0;
font-size: 3rem;
color: #2196F3;
}

.value {
font-weight: bold;
}

.unit {
margin-left: 0.5rem;
font-size: 2rem;
}

.status {
margin-top: 1rem;
}

.connection-status {
font-size: 0.9rem;
color: #666;
}

.connected {
color: #4CAF50;
}

.disconnected {
color: #f44336;
}
Loading

0 comments on commit 6a5a251

Please sign in to comment.