Added UART as a communication interface option.
This commit is contained in:
828
vesper/documentation/API_Reference.md
Normal file
828
vesper/documentation/API_Reference.md
Normal file
@@ -0,0 +1,828 @@
|
|||||||
|
# 🔔 VESPER ESP32 Communication API Reference v3.0
|
||||||
|
|
||||||
|
> **Complete command reference for Vesper Bell Automation System with Grouped Commands**
|
||||||
|
> Version: 3.0 | Updated: 2025-09-15
|
||||||
|
> Supports: MQTT + WebSocket protocols with multi-client support and batch processing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Getting Started
|
||||||
|
|
||||||
|
### Connection Protocols
|
||||||
|
- **MQTT**: `vesper/{device_id}/control` (commands) → `vesper/{device_id}/data` (responses)
|
||||||
|
- **WebSocket**: `ws://{esp_ip}/ws` (bidirectional)
|
||||||
|
- **UDP Discovery**: Broadcast on configured port for device discovery
|
||||||
|
- **UDP Port**: 32101
|
||||||
|
|
||||||
|
### WebSocket Client Identification
|
||||||
|
**Required for WebSocket clients to receive targeted messages:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "system",
|
||||||
|
"contents": {
|
||||||
|
"action": "identify",
|
||||||
|
"device_type": "master" // or "secondary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"type": "identify",
|
||||||
|
"payload": "Device identified as master"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Command Categories (NEW GROUPED ARCHITECTURE)
|
||||||
|
|
||||||
|
- [🖥️ System Commands](#️-system-commands)
|
||||||
|
- [🎵 Playback Control](#-playback-control)
|
||||||
|
- [📁 File Management](#-file-management)
|
||||||
|
- [🔧 Relay Setup](#-relay-setup)
|
||||||
|
- [🕐 Clock Setup](#-clock-setup)
|
||||||
|
- [📢 Information Messages](#-information-messages)
|
||||||
|
- [🌐 Network & Discovery](#-network--discovery)
|
||||||
|
- [🔄 Legacy Command Support](#-legacy-command-support)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖥️ System Commands
|
||||||
|
|
||||||
|
### 🏓 Ping Test
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "system",
|
||||||
|
"contents": {
|
||||||
|
"action": "ping"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"type": "pong",
|
||||||
|
"payload": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📊 System Status
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "system",
|
||||||
|
"contents": {
|
||||||
|
"action": "status"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"type": "current_status",
|
||||||
|
"payload": {
|
||||||
|
"player_status": "playing",
|
||||||
|
"time_elapsed": 45230,
|
||||||
|
"projected_run_time": 34598,
|
||||||
|
"timestamp": 1699123456789
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 👤 Device Identification (WebSocket Only)
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "system",
|
||||||
|
"contents": {
|
||||||
|
"action": "identify",
|
||||||
|
"device_type": "master"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔄 Restart Device
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "system",
|
||||||
|
"contents": {
|
||||||
|
"action": "restart"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"type": "restart",
|
||||||
|
"payload": "Device will restart in 2 seconds"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Device will reboot after sending the response.
|
||||||
|
|
||||||
|
### 🔄 Force OTA Update
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "system",
|
||||||
|
"contents": {
|
||||||
|
"action": "force_update",
|
||||||
|
"channel": "stable" // optional: "stable", "beta", or "emergency" (default: "stable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"type": "force_update",
|
||||||
|
"payload": "Starting forced OTA update from channel: stable. Device may reboot."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Response (if player is active):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ERROR",
|
||||||
|
"type": "force_update",
|
||||||
|
"payload": "Cannot update while playback is active"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** If update is successful, device will reboot automatically.
|
||||||
|
|
||||||
|
### 🔥 Custom Firmware Update
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "system",
|
||||||
|
"contents": {
|
||||||
|
"action": "custom_update",
|
||||||
|
"firmware_url": "https://example.com/path/to/firmware.bin",
|
||||||
|
"checksum": "a1b2c3d4e5f6...", // optional: SHA256 checksum for verification
|
||||||
|
"file_size": 1234567, // optional: expected file size in bytes
|
||||||
|
"version": 145 // optional: firmware version number to save in NVS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"type": "custom_update",
|
||||||
|
"payload": "Starting custom OTA update. Device may reboot."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Responses:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ERROR",
|
||||||
|
"type": "custom_update",
|
||||||
|
"payload": "Missing firmware_url parameter"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ERROR",
|
||||||
|
"type": "custom_update",
|
||||||
|
"payload": "Cannot update while playback is active"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Download firmware from any URL (bypasses configured update servers)
|
||||||
|
- Optional SHA256 checksum verification
|
||||||
|
- Optional file size validation
|
||||||
|
- Optional version number to update NVS (prevents unwanted auto-downgrades)
|
||||||
|
- Automatically blocks updates during playback
|
||||||
|
- Device reboots on successful installation
|
||||||
|
|
||||||
|
**Version Parameter Behavior:**
|
||||||
|
- If `version` is provided (> 0): NVS firmware version will be updated to this value
|
||||||
|
- If `version` is omitted or 0: NVS firmware version remains unchanged
|
||||||
|
- **Important:** Without version parameter, future OTA checks may detect your custom firmware as "outdated" and trigger auto-updates/downgrades
|
||||||
|
|
||||||
|
**Note:** If update is successful, device will reboot automatically. Use with caution!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎵 Playback Control
|
||||||
|
|
||||||
|
### ▶️ Start Playback
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "playback",
|
||||||
|
"contents": {
|
||||||
|
"action": "play",
|
||||||
|
"name": "My Melody",
|
||||||
|
"uid": "01DegzV9FA8tYbQpkIHR",
|
||||||
|
"url": "https://example.com/melody.bin",
|
||||||
|
"speed": 500,
|
||||||
|
"note_assignments": [1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"segment_duration": 15000,
|
||||||
|
"pause_duration": 5000,
|
||||||
|
"total_duration": 60000,
|
||||||
|
"continuous_loop": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⏹️ Stop Playback
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "playback",
|
||||||
|
"contents": {
|
||||||
|
"action": "stop"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 File Management
|
||||||
|
|
||||||
|
### 📋 List Available Melodies
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "file_manager",
|
||||||
|
"contents": {
|
||||||
|
"action": "list_melodies"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"type": "list_melodies",
|
||||||
|
"payload": ["melody1.bin", "melody2.bin", "melody3.bin"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📥 Download Melody
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "file_manager",
|
||||||
|
"contents": {
|
||||||
|
"action": "download_melody",
|
||||||
|
"download_url": "https://example.com/melody.bin",
|
||||||
|
"melodys_uid": "01DegzV9FA8tYbQpkIHR",
|
||||||
|
"name": "Optional Display Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🗑️ Delete Melody
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "file_manager",
|
||||||
|
"contents": {
|
||||||
|
"action": "delete_melody",
|
||||||
|
"name": "01DegzV9FA8tYbQpkIHR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Relay Setup
|
||||||
|
|
||||||
|
### ⏱️ Set Relay Timers (Single Bell)
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "relay_setup",
|
||||||
|
"contents": {
|
||||||
|
"action": "set_timers",
|
||||||
|
"b1": 100,
|
||||||
|
"b2": 200,
|
||||||
|
"b3": 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⏱️ Set Relay Timers (Batch Mode)
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "relay_setup",
|
||||||
|
"contents": {
|
||||||
|
"action": "set_timers",
|
||||||
|
"timers": {
|
||||||
|
"b1": 100,
|
||||||
|
"b2": 200,
|
||||||
|
"b3": 150,
|
||||||
|
"b4": 300,
|
||||||
|
"b5": 250,
|
||||||
|
"b6": 180
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔌 Set Relay Outputs (Single Bell)
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "relay_setup",
|
||||||
|
"contents": {
|
||||||
|
"action": "set_outputs",
|
||||||
|
"b1": 1,
|
||||||
|
"b2": 2,
|
||||||
|
"b3": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔌 Set Relay Outputs (Batch Mode)
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "relay_setup",
|
||||||
|
"contents": {
|
||||||
|
"action": "set_outputs",
|
||||||
|
"outputs": {
|
||||||
|
"b1": 1,
|
||||||
|
"b2": 2,
|
||||||
|
"b3": 3,
|
||||||
|
"b4": 4,
|
||||||
|
"b5": 5,
|
||||||
|
"b6": 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🕐 Clock Setup
|
||||||
|
|
||||||
|
### 🔌 Set Clock Outputs
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "clock_setup",
|
||||||
|
"contents": {
|
||||||
|
"action": "set_outputs",
|
||||||
|
"c1": 1,
|
||||||
|
"c2": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⏰ Set Clock Timings
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "clock_setup",
|
||||||
|
"contents": {
|
||||||
|
"action": "set_timings",
|
||||||
|
"pulseDuration": 5000,
|
||||||
|
"pauseDuration": 2000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔔 Set Clock Alerts
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "clock_setup",
|
||||||
|
"contents": {
|
||||||
|
"action": "set_alerts",
|
||||||
|
"alertType": "HOURS",
|
||||||
|
"alertRingInterval": 1000,
|
||||||
|
"hourBell": 1,
|
||||||
|
"halfBell": 2,
|
||||||
|
"quarterBell": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 💡 Set Clock Backlight
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "clock_setup",
|
||||||
|
"contents": {
|
||||||
|
"action": "set_backlight",
|
||||||
|
"enabled": true,
|
||||||
|
"output": 5,
|
||||||
|
"onTime": "18:00",
|
||||||
|
"offTime": "06:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔇 Set Clock Silence Periods
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "clock_setup",
|
||||||
|
"contents": {
|
||||||
|
"action": "set_silence",
|
||||||
|
"daytime": {
|
||||||
|
"enabled": true,
|
||||||
|
"onTime": "13:00",
|
||||||
|
"offTime": "15:00"
|
||||||
|
},
|
||||||
|
"nighttime": {
|
||||||
|
"enabled": true,
|
||||||
|
"onTime": "22:00",
|
||||||
|
"offTime": "07:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🚀 Batch Clock Setup (Multiple Settings at Once)
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "clock_setup",
|
||||||
|
"contents": {
|
||||||
|
"action": "batch_setup",
|
||||||
|
"outputs": {
|
||||||
|
"c1": 1,
|
||||||
|
"c2": 2
|
||||||
|
},
|
||||||
|
"timings": {
|
||||||
|
"pulseDuration": 5000,
|
||||||
|
"pauseDuration": 2000
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"alertType": "HOURS",
|
||||||
|
"hourBell": 1,
|
||||||
|
"halfBell": 2
|
||||||
|
},
|
||||||
|
"backlight": {
|
||||||
|
"enabled": true,
|
||||||
|
"output": 5,
|
||||||
|
"onTime": "18:00",
|
||||||
|
"offTime": "06:00"
|
||||||
|
},
|
||||||
|
"silence": {
|
||||||
|
"daytime": {
|
||||||
|
"enabled": true,
|
||||||
|
"onTime": "13:00",
|
||||||
|
"offTime": "15:00"
|
||||||
|
},
|
||||||
|
"nighttime": {
|
||||||
|
"enabled": true,
|
||||||
|
"onTime": "22:00",
|
||||||
|
"offTime": "07:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"type": "clock_setup",
|
||||||
|
"payload": "Batch clock setup updated: 5 sections"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📢 Information Messages
|
||||||
|
|
||||||
|
> **Automatic status broadcasts sent to ALL clients**
|
||||||
|
> These messages are initiated by the ESP32 system and broadcast to all connected clients without being requested.
|
||||||
|
|
||||||
|
### 🎵 Playback Status Updates
|
||||||
|
|
||||||
|
**Sent automatically during playback state changes:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "INFO",
|
||||||
|
"type": "playback",
|
||||||
|
"payload": {
|
||||||
|
"action": "playing",
|
||||||
|
"time_elapsed": 125,
|
||||||
|
"projected_run_time": 5158
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⚠️ Bell Overload Warnings
|
||||||
|
|
||||||
|
**Sent automatically when bell load monitoring detects issues:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "INFO",
|
||||||
|
"type": "bell_overload",
|
||||||
|
"payload": {
|
||||||
|
"bells": [1, 3, 5],
|
||||||
|
"loads": [85, 92, 78],
|
||||||
|
"severity": "warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Network & Discovery
|
||||||
|
|
||||||
|
### 🔍 UDP Discovery
|
||||||
|
|
||||||
|
**UDP Broadcast Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "discover",
|
||||||
|
"svc": "vesper"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**UDP Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "discover_reply",
|
||||||
|
"svc": "vesper",
|
||||||
|
"ver": 1,
|
||||||
|
"name": "Proj. Vesper v0.5",
|
||||||
|
"id": "ESP32_ABC123",
|
||||||
|
"ip": "192.168.1.100",
|
||||||
|
"ws": "ws://192.168.1.100/ws",
|
||||||
|
"port": 80,
|
||||||
|
"fw": "1.2.3",
|
||||||
|
"clients": 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Legacy Command Support
|
||||||
|
|
||||||
|
**For backward compatibility, the following legacy commands are still supported:**
|
||||||
|
|
||||||
|
### Individual Commands (Legacy)
|
||||||
|
- `cmd: "ping"` → Use `system` with `action: "ping"`
|
||||||
|
- `cmd: "report_status"` → Use `system` with `action: "status"`
|
||||||
|
- `cmd: "identify"` → Use `system` with `action: "identify"`
|
||||||
|
- `cmd: "list_melodies"` → Use `file_manager` with `action: "list_melodies"`
|
||||||
|
- `cmd: "download_melody"` → Use `file_manager` with `action: "download_melody"`
|
||||||
|
- `cmd: "delete_melody"` → Use `file_manager` with `action: "delete_melody"`
|
||||||
|
- `cmd: "set_relay_timers"` → Use `relay_setup` with `action: "set_timers"`
|
||||||
|
- `cmd: "set_relay_outputs"` → Use `relay_setup` with `action: "set_outputs"`
|
||||||
|
- `cmd: "set_clock_outputs"` → Use `clock_setup` with `action: "set_outputs"`
|
||||||
|
- `cmd: "set_clock_timings"` → Use `clock_setup` with `action: "set_timings"`
|
||||||
|
- `cmd: "set_clock_alerts"` → Use `clock_setup` with `action: "set_alerts"`
|
||||||
|
- `cmd: "set_clock_backlight"` → Use `clock_setup` with `action: "set_backlight"`
|
||||||
|
- `cmd: "set_clock_silence"` → Use `clock_setup` with `action: "set_silence"`
|
||||||
|
|
||||||
|
**Legacy commands will continue to work but are deprecated. Please migrate to the new grouped command structure for optimal performance and features.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Key Advantages of Grouped Commands
|
||||||
|
|
||||||
|
### 🚀 **Batch Processing**
|
||||||
|
- Send multiple settings in a single command
|
||||||
|
- Reduce network overhead and latency
|
||||||
|
- Atomic operations ensure consistency
|
||||||
|
|
||||||
|
### 📊 **Better Organization**
|
||||||
|
- Logical grouping of related commands
|
||||||
|
- Cleaner API structure
|
||||||
|
- Easier to understand and maintain
|
||||||
|
|
||||||
|
### ⚡ **Enhanced Performance**
|
||||||
|
- Fewer round-trips for complex configurations
|
||||||
|
- Optimized ESP32 processing
|
||||||
|
- Improved user experience
|
||||||
|
|
||||||
|
### 🔄 **Backward Compatibility**
|
||||||
|
- Legacy commands still supported
|
||||||
|
- Gradual migration path
|
||||||
|
- No breaking changes for existing implementations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Integration Examples
|
||||||
|
|
||||||
|
### Dart/Flutter App Integration
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// New grouped command approach
|
||||||
|
await ClockSetup.batchClockSetup(
|
||||||
|
c1Output: 1,
|
||||||
|
c2Output: 2,
|
||||||
|
pulseDuration: 5000,
|
||||||
|
pauseDuration: 2000,
|
||||||
|
alertType: 'HOURS',
|
||||||
|
hourBell: 1,
|
||||||
|
backlightEnabled: true,
|
||||||
|
backlightOutput: 5,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Batch relay setup
|
||||||
|
await RelaySetup.setBatchRelayOutputs({
|
||||||
|
1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Individual settings still work
|
||||||
|
await ClockSetup.setOddClockOutput(1);
|
||||||
|
await ClockSetup.setEvenClockOutput(2);
|
||||||
|
```
|
||||||
|
|
||||||
|
### JavaScript/WebSocket Integration
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Batch clock configuration
|
||||||
|
const clockConfig = {
|
||||||
|
cmd: "clock_setup",
|
||||||
|
contents: {
|
||||||
|
action: "batch_setup",
|
||||||
|
outputs: { c1: 1, c2: 2 },
|
||||||
|
timings: { pulseDuration: 5000, pauseDuration: 2000 },
|
||||||
|
alerts: { alertType: "HOURS", hourBell: 1 },
|
||||||
|
backlight: { enabled: true, output: 5 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
webSocket.send(JSON.stringify(clockConfig));
|
||||||
|
|
||||||
|
// Batch relay configuration
|
||||||
|
const relayConfig = {
|
||||||
|
cmd: "relay_setup",
|
||||||
|
contents: {
|
||||||
|
action: "set_outputs",
|
||||||
|
outputs: {
|
||||||
|
b1: 1, b2: 2, b3: 3, b4: 4, b5: 5, b6: 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
webSocket.send(JSON.stringify(relayConfig));
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 Error Handling
|
||||||
|
|
||||||
|
### Common Error Types
|
||||||
|
|
||||||
|
**Missing Action Parameter:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ERROR",
|
||||||
|
"type": "relay_setup",
|
||||||
|
"payload": "Missing action parameter"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Unknown Action:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ERROR",
|
||||||
|
"type": "clock_setup",
|
||||||
|
"payload": "Unknown action: invalid_action"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Batch Processing Errors:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ERROR",
|
||||||
|
"type": "relay_setup",
|
||||||
|
"payload": "No valid relay timers found in batch"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success with Count:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"type": "relay_setup",
|
||||||
|
"payload": "Batch relay outputs updated: 6 bells"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📡 Message Routing
|
||||||
|
|
||||||
|
### Response Routing Rules
|
||||||
|
|
||||||
|
1. **Command Responses**: Sent only to the originating client/protocol
|
||||||
|
- MQTT command → MQTT response
|
||||||
|
- WebSocket client #3 → WebSocket client #3 only
|
||||||
|
|
||||||
|
2. **Status Broadcasts**: Sent to ALL connected clients
|
||||||
|
- All WebSocket clients receive the message
|
||||||
|
- MQTT subscribers receive the message
|
||||||
|
- Used for system notifications and status updates
|
||||||
|
|
||||||
|
3. **Targeted Messages**: Based on device type
|
||||||
|
- `broadcastToMasterClients()`: Only master devices
|
||||||
|
- `broadcastToSecondaryClients()`: Only secondary devices
|
||||||
|
- `broadcastToAllWebSocketClients()`: All WebSocket clients
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ Performance Optimizations
|
||||||
|
|
||||||
|
### Batch Command Benefits
|
||||||
|
|
||||||
|
**Before (Legacy - 6 separate commands):**
|
||||||
|
```javascript
|
||||||
|
// 6 separate network calls
|
||||||
|
await setRelayOutput(1, 1);
|
||||||
|
await setRelayOutput(2, 2);
|
||||||
|
await setRelayOutput(3, 3);
|
||||||
|
await setRelayOutput(4, 4);
|
||||||
|
await setRelayOutput(5, 5);
|
||||||
|
await setRelayOutput(6, 6);
|
||||||
|
```
|
||||||
|
|
||||||
|
**After (Grouped - 1 batch command):**
|
||||||
|
```javascript
|
||||||
|
// 1 network call for all settings
|
||||||
|
await setBatchRelayOutputs({
|
||||||
|
1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance Metrics
|
||||||
|
- **Network Calls**: Reduced by up to 85%
|
||||||
|
- **Configuration Time**: 3-5x faster
|
||||||
|
- **ESP32 Processing**: More efficient batch updates
|
||||||
|
- **Error Handling**: Atomic operations ensure consistency
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Quick Reference
|
||||||
|
|
||||||
|
### Command Groups
|
||||||
|
| Group | Purpose | Batch Support |
|
||||||
|
|-------|---------|---------------|
|
||||||
|
| `system` | Device management, ping, status | No |
|
||||||
|
| `playback` | Music playback control | No |
|
||||||
|
| `file_manager` | Melody file operations | No |
|
||||||
|
| `relay_setup` | Bell configuration | ✅ Yes |
|
||||||
|
| `clock_setup` | Clock mechanism setup | ✅ Yes |
|
||||||
|
|
||||||
|
### Actions by Group
|
||||||
|
|
||||||
|
**System:** `ping`, `status`, `identify`, `restart`, `force_update`, `custom_update`
|
||||||
|
|
||||||
|
**Playback:** `play`, `stop`
|
||||||
|
|
||||||
|
**File Manager:** `list_melodies`, `download_melody`, `delete_melody`
|
||||||
|
|
||||||
|
**Relay Setup:** `set_timers`, `set_outputs`
|
||||||
|
|
||||||
|
**Clock Setup:** `set_outputs`, `set_timings`, `set_alerts`, `set_backlight`, `set_silence`, `batch_setup`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Happy Bell Automation with Grouped Commands! 🔔*
|
||||||
11
vesper/documentation/BuildProcedure.md
Normal file
11
vesper/documentation/BuildProcedure.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Device Setup Process:
|
||||||
|
|
||||||
|
1. Build device with peripherals.
|
||||||
|
2. Flash Base Firmware
|
||||||
|
3. Set Device Credentials (UID/HWID/Rev) via WebServer on device
|
||||||
|
4. Add Device to BellCloud
|
||||||
|
5. Add Device Credentials to Mosquitto
|
||||||
|
6. Reboot Device to Pull Stable Production Firmware
|
||||||
|
7. Sell the device.
|
||||||
|
- User will bind it to their account
|
||||||
|
- Factory can install App and bind user for convenience
|
||||||
0
vesper/documentation/Roadmap.md
Normal file
0
vesper/documentation/Roadmap.md
Normal file
85
vesper/documentation/features.info
Normal file
85
vesper/documentation/features.info
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
Features:
|
||||||
|
|
||||||
|
// Board Naming Schema:
|
||||||
|
|
||||||
|
eg. PV25K07BC01R01
|
||||||
|
|
||||||
|
PV 25 K 07 BC 01 R 01
|
||||||
|
PV [Y] [M] [D] [BT] [RV] R [BC]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PV25L22BP01R01
|
||||||
|
|
||||||
|
|
||||||
|
Y: (Year) 2 Digit Year. eg 25 for 2025
|
||||||
|
M: (Month) 1 Letter as Coded Month. eg B for February
|
||||||
|
D: (Day) 2 Digit Date. eg 17 for 17th of the Month
|
||||||
|
BT: (Board Type) 2 letter/digit board Type (custom) eg BC for BellCore
|
||||||
|
RV: (Revision) 2 letter/digit board revision code
|
||||||
|
R: Now, just an R for "Revision" but can change later
|
||||||
|
BC: (Batch Code) 2 digit SerialNumber starting from 01
|
||||||
|
|
||||||
|
|
||||||
|
// mqtt topics:
|
||||||
|
|
||||||
|
vesper/<DEVID>/data // Data sent from the controller
|
||||||
|
vesper/<DEVID>/control // Commands sent to the controller
|
||||||
|
vesper/<DEVID>/kiosk/event // Kiosk Mode Events
|
||||||
|
vesper/<DEVID>/kiosk/info // Kiosk Mode General Info
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- WiFi Manager (captive portal with hotspot)
|
||||||
|
- MQTT Support (Subscribing and Publishing)
|
||||||
|
- WebSocket Support (Sending and Receiving)
|
||||||
|
- JSON Format Messaging (both MQTT and WS)
|
||||||
|
- SD Card Handling and File Ops
|
||||||
|
- Stand-alone Player/BellEngine Classes, with functions to Play/Pause/Stop etc
|
||||||
|
- NoteAssignments - Effectively mapping Notes to Bells
|
||||||
|
- Independent SubSystems for all Core Functions (Networking/Comms/Scheduling/Logging/etc)
|
||||||
|
- Custom Relay Output Maps and Timings (saved on SD)
|
||||||
|
- Timekeeper with RTC/Clock/Alerts/Scheduling features
|
||||||
|
- OTA Update Functionality with Versioning/Rollbacks/Checksum/Firmware Validation/NTP Sync
|
||||||
|
- Global logger with Mode Selection (None, Error, Warning, Info, Debug, Verbose)
|
||||||
|
- UDP Listener for Auto Device Discovery
|
||||||
|
- Datalogging and Statistics:
|
||||||
|
- Counter for each bell (counts total times the bell ringed)
|
||||||
|
- Counter per bell, beats/minute for reliability and thermal protection. Warranty Void scenario.
|
||||||
|
- Ability to change Log levels (in-app)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ToDo Features:
|
||||||
|
|
||||||
|
- (optional) Add Bluetooth support
|
||||||
|
- (optional) Add WiFi Direct AP Support
|
||||||
|
- (optional) Add PCB Temperature Sensor Support
|
||||||
|
|
||||||
|
- (critical) Counters and Statistics:
|
||||||
|
- Counter per playback, to figure out which melody is the most played.
|
||||||
|
This can be implemented on the App itself. Doesn't need to be on the Device.
|
||||||
|
|
||||||
|
- Create a "humanizer" mode that randomizes delays on playback to simulate human ringing.
|
||||||
|
|
||||||
|
|
||||||
|
ToDo Fixes:
|
||||||
|
|
||||||
|
- (small significance) Fix each Log's level Correctly + Fix Log Syntax where needed
|
||||||
|
- (medium significance) BellGuard: Make the buttons functional.
|
||||||
|
|
||||||
|
- Fix IP Settings not applying. More Specifically, Variables inside the Components take long to update. Either Ditch the components, or find another way.
|
||||||
|
- On Very fast playback speeds and small programs that will run for less than a second or so, STOP isn't sent properly. Player keeps indicating "playing".
|
||||||
|
- When a new user is created, set default PINs for both Quick Settings, and Settings.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Stamna:
|
||||||
|
|
||||||
|
PV25L22BP01R01
|
||||||
|
Bell Plus
|
||||||
|
HW: 1.0
|
||||||
|
|
||||||
|
u6545309759@gmail.com
|
||||||
|
bellsystems2025
|
||||||
|
aCx!97IEfTiA073^#*Jj
|
||||||
@@ -41,7 +41,8 @@ public:
|
|||||||
// Message source identification
|
// Message source identification
|
||||||
enum class MessageSource {
|
enum class MessageSource {
|
||||||
MQTT,
|
MQTT,
|
||||||
WEBSOCKET
|
WEBSOCKET,
|
||||||
|
UART
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MessageContext {
|
struct MessageContext {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ CommunicationRouter::CommunicationRouter(ConfigManager& configManager,
|
|||||||
, _wsServer(webSocket, _clientManager)
|
, _wsServer(webSocket, _clientManager)
|
||||||
, _commandHandler(configManager, otaManager)
|
, _commandHandler(configManager, otaManager)
|
||||||
, _httpHandler(server, configManager)
|
, _httpHandler(server, configManager)
|
||||||
|
, _uartHandler()
|
||||||
, _settingsServer(server, configManager, networking) {}
|
, _settingsServer(server, configManager, networking) {}
|
||||||
|
|
||||||
CommunicationRouter::~CommunicationRouter() {}
|
CommunicationRouter::~CommunicationRouter() {}
|
||||||
@@ -106,13 +107,27 @@ void CommunicationRouter::begin() {
|
|||||||
_settingsServer.begin();
|
_settingsServer.begin();
|
||||||
LOG_INFO("✅ Settings Web Server initialized at /settings");
|
LOG_INFO("✅ Settings Web Server initialized at /settings");
|
||||||
|
|
||||||
|
// Initialize UART Command Handler
|
||||||
|
LOG_INFO("Setting up UART Command Handler...");
|
||||||
|
_uartHandler.begin();
|
||||||
|
_uartHandler.setCallback([this](JsonDocument& message) {
|
||||||
|
onUartMessage(message);
|
||||||
|
});
|
||||||
|
LOG_INFO("✅ UART Command Handler initialized (TX: GPIO12, RX: GPIO13)");
|
||||||
|
|
||||||
LOG_INFO("Communication Router initialized with modular architecture");
|
LOG_INFO("Communication Router initialized with modular architecture");
|
||||||
LOG_INFO(" • MQTT: AsyncMqttClient");
|
LOG_INFO(" • MQTT: AsyncMqttClient");
|
||||||
LOG_INFO(" • WebSocket: Multi-client support");
|
LOG_INFO(" • WebSocket: Multi-client support");
|
||||||
LOG_INFO(" • HTTP REST API: /api endpoints");
|
LOG_INFO(" • HTTP REST API: /api endpoints");
|
||||||
|
LOG_INFO(" • UART: External device control");
|
||||||
LOG_INFO(" • Settings Page: /settings");
|
LOG_INFO(" • Settings Page: /settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CommunicationRouter::loop() {
|
||||||
|
// Process UART incoming data
|
||||||
|
_uartHandler.loop();
|
||||||
|
}
|
||||||
|
|
||||||
void CommunicationRouter::setPlayerReference(Player* player) {
|
void CommunicationRouter::setPlayerReference(Player* player) {
|
||||||
_player = player;
|
_player = player;
|
||||||
_commandHandler.setPlayerReference(player);
|
_commandHandler.setPlayerReference(player);
|
||||||
@@ -327,6 +342,20 @@ void CommunicationRouter::onWebSocketMessage(uint32_t clientId, const JsonDocume
|
|||||||
LOG_DEBUG("WebSocket message from client #%u processed", clientId);
|
LOG_DEBUG("WebSocket message from client #%u processed", clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CommunicationRouter::onUartMessage(JsonDocument& message) {
|
||||||
|
// Extract command for logging
|
||||||
|
String cmd = message["cmd"] | "unknown";
|
||||||
|
LOG_INFO("🔌 UART message received: cmd=%s", cmd.c_str());
|
||||||
|
|
||||||
|
// Create message context for UART
|
||||||
|
CommandHandler::MessageContext context(CommandHandler::MessageSource::UART);
|
||||||
|
|
||||||
|
// Forward to command handler
|
||||||
|
_commandHandler.processCommand(message, context);
|
||||||
|
|
||||||
|
LOG_DEBUG("UART message processed");
|
||||||
|
}
|
||||||
|
|
||||||
void CommunicationRouter::sendResponse(const String& response, const CommandHandler::MessageContext& context) {
|
void CommunicationRouter::sendResponse(const String& response, const CommandHandler::MessageContext& context) {
|
||||||
if (context.source == CommandHandler::MessageSource::MQTT) {
|
if (context.source == CommandHandler::MessageSource::MQTT) {
|
||||||
LOG_DEBUG("↗️ Sending response via MQTT: %s", response.c_str());
|
LOG_DEBUG("↗️ Sending response via MQTT: %s", response.c_str());
|
||||||
@@ -334,6 +363,9 @@ void CommunicationRouter::sendResponse(const String& response, const CommandHand
|
|||||||
} else if (context.source == CommandHandler::MessageSource::WEBSOCKET) {
|
} else if (context.source == CommandHandler::MessageSource::WEBSOCKET) {
|
||||||
LOG_DEBUG("↗️ Sending response to WebSocket client #%u: %s", context.clientId, response.c_str());
|
LOG_DEBUG("↗️ Sending response to WebSocket client #%u: %s", context.clientId, response.c_str());
|
||||||
_wsServer.sendToClient(context.clientId, response);
|
_wsServer.sendToClient(context.clientId, response);
|
||||||
|
} else if (context.source == CommandHandler::MessageSource::UART) {
|
||||||
|
LOG_DEBUG("↗️ Sending response via UART: %s", response.c_str());
|
||||||
|
_uartHandler.send(response);
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR("❌ Unknown message source for response routing!");
|
LOG_ERROR("❌ Unknown message source for response routing!");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
#include "../CommandHandler/CommandHandler.hpp"
|
#include "../CommandHandler/CommandHandler.hpp"
|
||||||
#include "../ResponseBuilder/ResponseBuilder.hpp"
|
#include "../ResponseBuilder/ResponseBuilder.hpp"
|
||||||
#include "../HTTPRequestHandler/HTTPRequestHandler.hpp"
|
#include "../HTTPRequestHandler/HTTPRequestHandler.hpp"
|
||||||
|
#include "../UARTCommandHandler/UARTCommandHandler.hpp"
|
||||||
#include "../../ClientManager/ClientManager.hpp"
|
#include "../../ClientManager/ClientManager.hpp"
|
||||||
#include "../../SettingsWebServer/SettingsWebServer.hpp"
|
#include "../../SettingsWebServer/SettingsWebServer.hpp"
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ public:
|
|||||||
~CommunicationRouter();
|
~CommunicationRouter();
|
||||||
|
|
||||||
void begin();
|
void begin();
|
||||||
|
void loop(); // Must be called from main loop for UART processing
|
||||||
void setPlayerReference(Player* player);
|
void setPlayerReference(Player* player);
|
||||||
void setFileManagerReference(FileManager* fm);
|
void setFileManagerReference(FileManager* fm);
|
||||||
void setTimeKeeperReference(Timekeeper* tk);
|
void setTimeKeeperReference(Timekeeper* tk);
|
||||||
@@ -78,6 +80,7 @@ public:
|
|||||||
|
|
||||||
// Component accessors
|
// Component accessors
|
||||||
MQTTAsyncClient& getMQTTClient() { return _mqttClient; }
|
MQTTAsyncClient& getMQTTClient() { return _mqttClient; }
|
||||||
|
UARTCommandHandler& getUARTHandler() { return _uartHandler; }
|
||||||
|
|
||||||
// Broadcast methods
|
// Broadcast methods
|
||||||
void broadcastStatus(const String& statusMessage);
|
void broadcastStatus(const String& statusMessage);
|
||||||
@@ -116,11 +119,13 @@ private:
|
|||||||
WebSocketServer _wsServer;
|
WebSocketServer _wsServer;
|
||||||
CommandHandler _commandHandler;
|
CommandHandler _commandHandler;
|
||||||
HTTPRequestHandler _httpHandler;
|
HTTPRequestHandler _httpHandler;
|
||||||
|
UARTCommandHandler _uartHandler;
|
||||||
SettingsWebServer _settingsServer;
|
SettingsWebServer _settingsServer;
|
||||||
|
|
||||||
// Message handlers
|
// Message handlers
|
||||||
void onMqttMessage(const String& topic, const String& payload);
|
void onMqttMessage(const String& topic, const String& payload);
|
||||||
void onWebSocketMessage(uint32_t clientId, const JsonDocument& message);
|
void onWebSocketMessage(uint32_t clientId, const JsonDocument& message);
|
||||||
|
void onUartMessage(JsonDocument& message);
|
||||||
|
|
||||||
// Response routing
|
// Response routing
|
||||||
void sendResponse(const String& response, const CommandHandler::MessageContext& context);
|
void sendResponse(const String& response, const CommandHandler::MessageContext& context);
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* UARTCOMMANDHANDLER.CPP - UART Command Handler Implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "UARTCommandHandler.hpp"
|
||||||
|
#include "../../Logging/Logging.hpp"
|
||||||
|
|
||||||
|
UARTCommandHandler::UARTCommandHandler(uint8_t txPin, uint8_t rxPin, uint32_t baudRate)
|
||||||
|
: _serial(Serial2)
|
||||||
|
, _txPin(txPin)
|
||||||
|
, _rxPin(rxPin)
|
||||||
|
, _baudRate(baudRate)
|
||||||
|
, _ready(false)
|
||||||
|
, _bufferIndex(0)
|
||||||
|
, _messageCount(0)
|
||||||
|
, _errorCount(0)
|
||||||
|
, _callback(nullptr)
|
||||||
|
{
|
||||||
|
resetBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
UARTCommandHandler::~UARTCommandHandler() {
|
||||||
|
_serial.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTCommandHandler::begin() {
|
||||||
|
LOG_INFO("Initializing UART Command Handler");
|
||||||
|
LOG_INFO(" TX Pin: GPIO%d", _txPin);
|
||||||
|
LOG_INFO(" RX Pin: GPIO%d", _rxPin);
|
||||||
|
LOG_INFO(" Baud Rate: %u", _baudRate);
|
||||||
|
|
||||||
|
// Initialize Serial2 with custom pins
|
||||||
|
_serial.begin(_baudRate, SERIAL_8N1, _rxPin, _txPin);
|
||||||
|
|
||||||
|
// Clear any garbage in the buffer
|
||||||
|
while (_serial.available()) {
|
||||||
|
_serial.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ready = true;
|
||||||
|
LOG_INFO("UART Command Handler ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTCommandHandler::loop() {
|
||||||
|
if (!_ready) return;
|
||||||
|
|
||||||
|
// Process all available bytes
|
||||||
|
while (_serial.available()) {
|
||||||
|
char c = _serial.read();
|
||||||
|
|
||||||
|
// Check for message delimiter (newline)
|
||||||
|
if (c == '\n' || c == '\r') {
|
||||||
|
if (_bufferIndex > 0) {
|
||||||
|
// Null-terminate and process
|
||||||
|
_buffer[_bufferIndex] = '\0';
|
||||||
|
processLine(_buffer);
|
||||||
|
resetBuffer();
|
||||||
|
}
|
||||||
|
// Skip empty lines
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add character to buffer
|
||||||
|
if (_bufferIndex < BUFFER_SIZE - 1) {
|
||||||
|
_buffer[_bufferIndex++] = c;
|
||||||
|
} else {
|
||||||
|
// Buffer overflow - discard and reset
|
||||||
|
LOG_ERROR("UART buffer overflow, discarding message");
|
||||||
|
_errorCount++;
|
||||||
|
resetBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTCommandHandler::setCallback(MessageCallback callback) {
|
||||||
|
_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTCommandHandler::send(const String& response) {
|
||||||
|
if (!_ready) {
|
||||||
|
LOG_ERROR("UART not ready, cannot send response");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_serial.print(response);
|
||||||
|
_serial.print('\n'); // Newline delimiter
|
||||||
|
_serial.flush(); // Ensure data is sent
|
||||||
|
|
||||||
|
LOG_DEBUG("UART TX: %s", response.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTCommandHandler::processLine(const char* line) {
|
||||||
|
LOG_DEBUG("UART RX: %s", line);
|
||||||
|
|
||||||
|
// Skip empty lines or whitespace-only
|
||||||
|
if (strlen(line) == 0) return;
|
||||||
|
|
||||||
|
// Parse JSON
|
||||||
|
StaticJsonDocument<1024> doc;
|
||||||
|
DeserializationError error = deserializeJson(doc, line);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
LOG_ERROR("UART JSON parse error: %s", error.c_str());
|
||||||
|
_errorCount++;
|
||||||
|
|
||||||
|
// Send error response back
|
||||||
|
StaticJsonDocument<256> errorDoc;
|
||||||
|
errorDoc["status"] = "ERROR";
|
||||||
|
errorDoc["type"] = "parse_error";
|
||||||
|
errorDoc["payload"] = error.c_str();
|
||||||
|
|
||||||
|
String errorResponse;
|
||||||
|
serializeJson(errorDoc, errorResponse);
|
||||||
|
send(errorResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_messageCount++;
|
||||||
|
|
||||||
|
// Invoke callback if set
|
||||||
|
if (_callback) {
|
||||||
|
_callback(doc);
|
||||||
|
} else {
|
||||||
|
LOG_WARNING("UART message received but no callback set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTCommandHandler::resetBuffer() {
|
||||||
|
_bufferIndex = 0;
|
||||||
|
memset(_buffer, 0, BUFFER_SIZE);
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* ═══════════════════════════════════════════════════════════════════════════════════
|
||||||
|
* UARTCOMMANDHANDLER.HPP - UART Command Interface for External Control Devices
|
||||||
|
* ═══════════════════════════════════════════════════════════════════════════════════
|
||||||
|
*
|
||||||
|
* 🔌 UART COMMAND HANDLER 🔌
|
||||||
|
*
|
||||||
|
* Enables command input from external devices (LCD panels, button controllers)
|
||||||
|
* via UART serial communication. Uses newline-delimited JSON protocol.
|
||||||
|
*
|
||||||
|
* Pin Configuration:
|
||||||
|
* • TX: GPIO12
|
||||||
|
* • RX: GPIO13
|
||||||
|
* • Baud: 115200 (configurable)
|
||||||
|
*
|
||||||
|
* Protocol:
|
||||||
|
* • Newline-delimited JSON messages
|
||||||
|
* • Same command format as MQTT/WebSocket
|
||||||
|
* • Responses sent back on same UART
|
||||||
|
*
|
||||||
|
* 📋 VERSION: 1.0
|
||||||
|
* 📅 DATE: 2025-01-19
|
||||||
|
* 👨💻 AUTHOR: Advanced Bell Systems
|
||||||
|
* ═══════════════════════════════════════════════════════════════════════════════════
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class UARTCommandHandler {
|
||||||
|
public:
|
||||||
|
// Default pin configuration
|
||||||
|
static constexpr uint8_t DEFAULT_TX_PIN = 12;
|
||||||
|
static constexpr uint8_t DEFAULT_RX_PIN = 13;
|
||||||
|
static constexpr uint32_t DEFAULT_BAUD_RATE = 115200;
|
||||||
|
static constexpr size_t BUFFER_SIZE = 1024;
|
||||||
|
|
||||||
|
// Message callback type - called when a complete JSON message is received
|
||||||
|
using MessageCallback = std::function<void(JsonDocument& message)>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct UART handler with custom pins
|
||||||
|
* @param txPin GPIO pin for TX (default: 12)
|
||||||
|
* @param rxPin GPIO pin for RX (default: 13)
|
||||||
|
* @param baudRate Baud rate (default: 115200)
|
||||||
|
*/
|
||||||
|
explicit UARTCommandHandler(uint8_t txPin = DEFAULT_TX_PIN,
|
||||||
|
uint8_t rxPin = DEFAULT_RX_PIN,
|
||||||
|
uint32_t baudRate = DEFAULT_BAUD_RATE);
|
||||||
|
|
||||||
|
~UARTCommandHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the UART interface
|
||||||
|
*/
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Process incoming UART data (call from loop or task)
|
||||||
|
* Non-blocking - processes available bytes and returns
|
||||||
|
*/
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set callback for received messages
|
||||||
|
* @param callback Function to call with parsed JSON
|
||||||
|
*/
|
||||||
|
void setCallback(MessageCallback callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a response back over UART
|
||||||
|
* @param response JSON string to send (newline appended automatically)
|
||||||
|
*/
|
||||||
|
void send(const String& response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if UART is initialized and ready
|
||||||
|
*/
|
||||||
|
bool isReady() const { return _ready; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get number of messages received since boot
|
||||||
|
*/
|
||||||
|
uint32_t getMessageCount() const { return _messageCount; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get number of parse errors since boot
|
||||||
|
*/
|
||||||
|
uint32_t getErrorCount() const { return _errorCount; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
HardwareSerial& _serial;
|
||||||
|
uint8_t _txPin;
|
||||||
|
uint8_t _rxPin;
|
||||||
|
uint32_t _baudRate;
|
||||||
|
bool _ready;
|
||||||
|
|
||||||
|
// Receive buffer
|
||||||
|
char _buffer[BUFFER_SIZE];
|
||||||
|
size_t _bufferIndex;
|
||||||
|
|
||||||
|
// Statistics
|
||||||
|
uint32_t _messageCount;
|
||||||
|
uint32_t _errorCount;
|
||||||
|
|
||||||
|
// Callback
|
||||||
|
MessageCallback _callback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Process a complete line from the buffer
|
||||||
|
* @param line Null-terminated string containing the message
|
||||||
|
*/
|
||||||
|
void processLine(const char* line);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the receive buffer
|
||||||
|
*/
|
||||||
|
void resetBuffer();
|
||||||
|
};
|
||||||
@@ -513,6 +513,9 @@ void loop()
|
|||||||
lastWsCleanup = millis();
|
lastWsCleanup = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process UART command input from external devices (LCD panel, buttons)
|
||||||
|
communication.loop();
|
||||||
|
|
||||||
// 🔥 DEBUG: Log every 10 seconds to verify we're still running
|
// 🔥 DEBUG: Log every 10 seconds to verify we're still running
|
||||||
static unsigned long lastLog = 0;
|
static unsigned long lastLog = 0;
|
||||||
if (millis() - lastLog > 10000) {
|
if (millis() - lastLog > 10000) {
|
||||||
|
|||||||
Reference in New Issue
Block a user