Initial commit: ESP32-C5-EVB and ESP32-C6-EVB Arduino firmware + HA custom components

This commit is contained in:
ske087
2026-06-11 00:42:59 +03:00
commit e5fd3645d1
41 changed files with 6637 additions and 0 deletions
+161
View File
@@ -0,0 +1,161 @@
# NFC (PN532) Debugging Guide
## Current Status
- **Hardware**: GPIO26(TX) → PN532 RXD, GPIO25(RX) ← PN532 TXD (UEXT pins 3-4)
- **DIP Switches**: Both OFF (HSU mode) ✓
- **Power**: UEXT pin 1 = 3.3V (verified), Pin 2 = GND (verified)
- **Wiring**: Confirmed correct
- **Problem**: PN532 module not responding to auto-baud probe
## Why NFC Probe Fails
The firmware attempts to communicate with PN532 at various baud rates:
- 115200, 9600, 57600, 38400 baud
- Tries both GPIO26/GPIO25 and GPIO25/GPIO26 (swapped)
- At each combination: sends `getFirmwareVersion()` command
- **Issue**: No response from PN532 → returns 0x00
## Prerequisites for Debugging
### 1. Enable Serial Output
**Arduino IDE Settings (REQUIRED):**
```
Tools → USB CDC On Boot: ENABLED
Tools → Port: /dev/ttyACM0
```
**Rebuild and flash the firmware:**
```bash
cd ~/arduino/olimex_ESP32-C5-EVB/esp32_arduino
arduino --verify --board esp32:esp32:esp32c5 esp32_arduino.ino
esptool.py --chip esp32c5 --port /dev/ttyACM0 --baud 921600 erase-flash
esptool.py --chip esp32c5 --port /dev/ttyACM0 --baud 921600 write-flash 0x0 build_output/esp32_arduino.ino.merged.bin
```
**Open Serial Monitor:**
- Arduino IDE → Tools → Serial Monitor
- Baud rate: **115200**
- Wait for boot messages
## Diagnostic Steps
### Step 1: Verify Serial Output
Expected output during boot:
```
=================================
ESP32-C5 Home Assistant Device
Arduino Framework
=================================
...
--- NFC (PN532 HSU) Initialization with Debug ---
Hardware: GPIO26(TX)→PN532-RX, GPIO25(RX)←PN532-TX, DIP:HSU mode
[1/8] Baud=115200, RX=GPIO25, TX=GPIO26 ... ✗
[2/8] Baud=9600, RX=GPIO25, TX=GPIO26 ... ✗
...
```
**If you see:**
- ✓ All "✗" symbols → PN532 not responding at any baud rate
- Nothing after "Initialization" → Serial output not working (re-check USB CDC setting)
### Step 2: Physical Verification Checklist
```
□ PN532 powered: Measure 3.3V on VCC pin (should be 3.0-3.3V)
□ PN532 GND: Verify continuity to board GND with multimeter
□ UEXT Pin 1: Should be 3.3V (measure with multimeter)
□ UEXT Pin 2: Should be GND (continuity check)
□ UEXT Pin 3 (GPIO26): No continuity to Pin 4 (should be separate signals)
□ UEXT Pin 4 (GPIO25): No continuity to Pin 3 (should be separate signals)
□ PN532 Module: Try re-seating in connector or using different PN532 board
```
### Step 3: Signal Analysis (Advanced)
If serial output shows all "✗" but hardware checks pass:
**Option A: Oscilloscope Check**
```
Connect oscilloscope probe to GPIO26 (TX line)
- Should see UART signal transitions during probe
- If flat line: ESP32 not transmitting
- If signal present: PN532 not responding
```
**Option B: Loopback Test**
```
Connect GPIO26 (TX) directly to GPIO25 (RX) with a jumper wire
Rebuild with loopback test code (at end of this file)
If loopback works: GPIO pins OK, PN532 module is issue
If loopback fails: GPIO configuration issue
```
## Possible Issues & Solutions
### Issue 1: PN532 Module Defective
**Symptoms:** All baud rates fail, hardware verified correct
**Solution:**
- Try different PN532 board if available
- Test with I2C mode (different DIP setting) if supported
- Replace PN532 module
### Issue 2: UEXT Connector Loose
**Symptoms:** Intermittent detection or complete failure
**Solution:**
- Fully remove and re-seat PN532 in UEXT connector
- Ensure connector is fully inserted until it clicks
- Check for bent pins on UEXT connector
### Issue 3: Incorrect Pin Mapping
**Symptoms:** Detection works with swapped pins
**Solution:**
- Edit `esp32_arduino.ino`:
- Find: `#define NFC_TX_PIN 26` and `#define NFC_RX_PIN 25`
- If swapped RX/TX works: reverse these definitions
- Rebuild and test
### Issue 4: Baud Rate Mismatch
**Symptoms:** Serial output shows some attempts as "✓ FOUND!" but then fails to init
**Solution:**
- Firmware will re-init with confirmed baud rate
- If fails at SAMConfig() step: PN532 module may be in wrong mode
## Loopback Test Code
(If GPIO pins might be misconfigured):
```cpp
// Add to setup() after WiFi init, BEFORE NFC init:
void setup() {
// ... existing code ...
// LOOPBACK TEST
Serial.println("--- GPIO Loopback Test ---");
Serial.println("Jumper GPIO26 to GPIO25 to test UART");
HardwareSerial testSerial(1); // UART1
testSerial.begin(115200, SERIAL_8N1, 25, 26); // RX=GPIO25, TX=GPIO26
delay(100);
testSerial.print("TEST_LOOPBACK");
delay(100);
String received = "";
while (testSerial.available()) {
received += (char)testSerial.read();
}
if (received.indexOf("TEST") >= 0) {
Serial.println("✓ GPIO26/GPIO25 loopback OK");
} else {
Serial.println("✗ GPIO loopback FAILED - pin configuration issue");
}
}
```
## Next Steps
1. **Enable Serial Output**: Change USB CDC On Boot to ENABLED
2. **Rebuild & Flash**: Use updated Arduino IDE settings
3. **Monitor Serial**: Watch boot output during NFC probe
4. **Share Output**: Post the serial output here for analysis
**Expected Timeline:**
- Checking serial output: ~5 minutes
- Physical verification: ~10 minutes
- Analyzing results: ~5 minutes
- **Total**: ~20 minutes to identify root cause
+186
View File
@@ -0,0 +1,186 @@
# Olimex ESP32-C5-EVB — Home Assistant Integration
This repository contains two components:
## `custom_components/olimex_esp32_c5`
Home Assistant custom integration for the **Olimex ESP32-C5-EVB** board.
- 2 relay switches (250V / 10A, controlled via HTTP POST to the board)
- 2 opto-isolated digital inputs (110VAC-240VAC, state pushed from board to HA via webhook)
- NFC/PN532 card reader for access control (optional, via UEXT connector)
- No polling — fully event-driven for inputs, command-driven for relays
### Installation
Copy `custom_components/olimex_esp32_c5` into your Home Assistant `config/custom_components/` directory and restart HA.
## `esp32_arduino`
Arduino sketch for the ESP32-C5-EVB board.
- Hosts a REST API on port 80
- Registers a callback URL with HA on startup
- POSTs input state changes to HA webhook in real time
- Optional NFC card reader integration for relay trigger automation
### Arduino IDE Settings
| Setting | Value |
|---|---|
| Board | ESP32C5 Dev Module |
| Flash Size | 4MB |
| USB CDC On Boot | Enabled |
See [`esp32_arduino/DEPLOYMENT_GUIDE.md`](esp32_arduino/DEPLOYMENT_GUIDE.md) for full flashing instructions.
## Key Differences from C6-EVB
| Feature | C6-EVB | C5-EVB |
|---------|--------|--------|
| **Relays** | 4 | 2 (250V / 10A) |
| **Inputs** | 4 | 2 (opto-isolated 110VAC-240VAC) |
| **Relay 1** | GPIO 10 | GPIO 24 |
| **Relay 2** | GPIO 11 | GPIO 23 |
| **Input 1** | GPIO 1 | GPIO 4 |
| **Input 2** | GPIO 2 | GPIO 5 |
| **LED** | GPIO 8 | GPIO 27 |
| **Button** | GPIO 9 | GPIO 28 |
| **NFC UART** | GPIO 4/5 (UEXT1) | GPIO 26/25 (UEXT) |
---
## Hardware Overview
### Pin Configuration
**Relays** (250V / 10A AC/DC)
- Relay 1: GPIO24
- Relay 2: GPIO23
**Opto-Isolated Inputs** (110VAC-240VAC)
- Opto Input 1: GPIO4
- Opto Input 2: GPIO5
**LED & Button**
- User LED: GPIO27 (active LOW)
- User Button: GPIO28 (active LOW, internal pull-up)
**UEXT Connector** (for NFC reader / additional peripherals)
- I2C: SDA=GPIO9, SCL=GPIO8
- UART: TX=GPIO26, RX=GPIO25
- SPI: CLK=GPIO2, MOSI=GPIO7, MISO=GPIO6
### Power & Connectivity
- USB-C power input
- WiFi 6 (802.11ax) dual-band (2.4 & 5 GHz)
- Bluetooth 5.0 LE
- Zigbee & Thread capable
---
## API Endpoints
### Status & Control
- `GET /api/status` — Returns all relay and input states (JSON)
- `POST /relay/on?relay=1-2` — Turn relay on
- `POST /relay/off?relay=1-2` — Turn relay off
- `GET /relay/status?relay=1-2` — Get relay state
### Inputs
- `GET /input/status?input=1-2` — Get input state
### LED
- `POST /led/on` — Turn LED on
- `POST /led/off` — Turn LED off
### Home Assistant Integration
- `POST /register?callback_url=...` — Register HA webhook (no auth required for bootstrap)
### NFC Access Control
- `GET /nfc/status` — Get NFC module status
- `GET /nfc/config` — Get NFC access control settings
- `POST /nfc/config?auth_uid=...&relay=1-2&pulse_ms=5000` — Set NFC access config
- `POST /nfc/enable?state=0|1` — Enable/disable NFC module
### Debug
- `GET /debug` — System info (requires basic auth)
All endpoints except `/register` require **Basic HTTP Authentication** (default: admin/admin) or HMAC-SHA256 request signing.
---
## Setup Instructions
1. **Flash the firmware**
- See [DEPLOYMENT_GUIDE.md](esp32_arduino/DEPLOYMENT_GUIDE.md)
2. **Configure WiFi**
- Edit WiFi credentials in `esp32_arduino.ino` (lines ~58-59)
- Set a static IP (currently 192.168.0.240)
- Flash the board
3. **Add to Home Assistant**
- Copy `custom_components/olimex_esp32_c5/` to `~/.homeassistant/custom_components/`
- Restart Home Assistant
- Settings → Devices & Services → Create Integration → Olimex ESP32-C5-EVB
- Enter board IP, port (80), and Home Assistant callback IP
4. **Optional: NFC Card Reader Setup**
- Attach PN532 breakout to UEXT connector (TX=GPIO26, RX=GPIO25)
- Open board web UI (`http://<ip>/`)
- Enable NFC module
- Scan a card to authorize it
- Configure trigger relay and timeout
---
## Home Assistant Integration
Once configured, entities appear automatically:
**Switches**
- `switch.olimex_esp32_c5_relay_1`
- `switch.olimex_esp32_c5_relay_2`
**Binary Sensors**
- `binary_sensor.olimex_esp32_c5_input_1` (opto-isolated input)
- `binary_sensor.olimex_esp32_c5_input_2` (opto-isolated input)
- `binary_sensor.olimex_esp32_c5_nfc_card` (NFC card present, if enabled)
**Automations**
Create automations to trigger relays on input events, NFC cards, time schedules, etc.
---
## Troubleshooting
### Board not reachable
1. Check WiFi SSID and password in code
2. Verify board IP with serial console output
3. Check firewall allows port 80
### Inputs not triggering
1. Verify wiring to opto-isolated inputs (GPIO4, GPIO5)
2. Use `/debug` endpoint to check input state
3. Ensure Home Assistant callback URL is registered
### NFC not detecting cards
1. Check PN532 wiring (TX=GPIO26, RX=GPIO25)
2. Set PN532 DIP switches to HSU mode (both = 0)
3. Check serial output for PN532 detection message
4. Try different baud rates (115200, 9600, 57600, 38400)
---
## Security Notes
- Default credentials: admin / admin
- Change WEB_PASSWORD in `secrets.h` before deployment
- Enable API_SECRET for HMAC-SHA256 request signing (optional)
- Only expose board on trusted local networks
- NFC cards trigger relays when authenticated card is present
---
## License & Attribution
- Hardware: [Olimex](https://www.olimex.com/Products/IoT/ESP32-C5/ESP32-C5-EVB/open-source-hardware)
- Firmware: Arduino & ESP-IDF
- Integration: Home Assistant Custom Component
+235
View File
@@ -0,0 +1,235 @@
#!/usr/bin/env python3
"""
Olimex ESP32-C5-EVB — Remote Board Verification Script
=========================================================
Queries the board's REST API and verifies every subsystem.
Usage:
python3 board_verify.py # uses default IP 192.168.0.240
python3 board_verify.py 192.168.0.200 # custom IP
python3 board_verify.py --json # machine-readable output
Requirements: pip install requests (already in location_managemet requirements)
What it tests:
1. Board reachability (GET /api/status)
2. Both relays (POST /relay/on, GET /relay/status, POST /relay/off)
3. Both digital inputs (GET /input/status)
4. LED (POST /led/on + /led/off)
5. NFC reader (GET /nfc/status)
6. NFC config API (GET /nfc/config)
NOTE: Relay tests cycle each relay ON→verify→OFF→verify.
You should hear/see the relay click during the test.
"""
import sys
import json
import time
import argparse
import requests
TIMEOUT = 5 # seconds per HTTP request
RELAY_DLY = 0.4 # seconds to wait between relay on/status/off
# ─────────────────────────────────────────────────────────────────────────────
# Result tracking
# ─────────────────────────────────────────────────────────────────────────────
results = []
def record(name: str, ok: bool, detail: str = "") -> bool:
results.append({"name": name, "pass": ok, "detail": detail})
icon = "\033[32m[PASS]\033[0m" if ok else "\033[31m[FAIL]\033[0m"
print(f" {icon} {name}" + (f"{detail}" if detail else ""))
return ok
def _get(url: str):
try:
r = requests.get(url, timeout=TIMEOUT)
return r.status_code, r.text, None
except requests.RequestException as e:
return None, None, str(e)
def _post(url: str, json_data=None):
try:
r = requests.post(url, json=json_data, timeout=TIMEOUT)
return r.status_code, r.text, None
except requests.RequestException as e:
return None, None, str(e)
# ─────────────────────────────────────────────────────────────────────────────
# Main verification
# ─────────────────────────────────────────────────────────────────────────────
def verify_board(ip: str) -> int:
"""Test all board subsystems. Returns count of failures."""
base = f"http://{ip}"
failures = 0
print(f"\n{'='*70}")
print(f"Olimex ESP32-C5-EVB Verification")
print(f"{'='*70}")
print(f"Target IP: {ip}\n")
# ─ 1. Board Reachability ──────────────────────────────────────────────────
print("1. Board Status")
status_code, resp_text, err = _get(f"{base}/api/status")
if err:
record("Board reachable", False, err)
return 1 # Can't proceed without board connectivity
if not (status_code and status_code == 200):
record("Board reachable", False, f"HTTP {status_code}")
return 1
try:
status_json = json.loads(resp_text)
record("Board reachable", True, f"HTTP 200")
except json.JSONDecodeError:
record("Board reachable (partial)", False, "Response not JSON")
status_json = {}
# ─ 2. Relays ──────────────────────────────────────────────────────────────
print("\n2. Relays (2x 250V/10A)")
for relay_num in [1, 2]:
relay_name = f"Relay {relay_num}"
# Turn ON
_, _, err = _post(f"{base}/relay/on?relay={relay_num}")
if err:
record(f"{relay_name} ON", False, err)
failures += 1
continue
time.sleep(RELAY_DLY)
status_code, resp_text, err = _get(f"{base}/relay/status?relay={relay_num}")
if err or status_code != 200:
record(f"{relay_name} ON verify", False, err or f"HTTP {status_code}")
failures += 1
else:
try:
r = json.loads(resp_text)
if r.get("state") == True:
record(f"{relay_name} ON", True)
else:
record(f"{relay_name} ON verify", False, "State mismatch")
failures += 1
except:
record(f"{relay_name} ON verify", False, "Invalid response")
failures += 1
time.sleep(RELAY_DLY)
# Turn OFF
_, _, err = _post(f"{base}/relay/off?relay={relay_num}")
if err:
record(f"{relay_name} OFF", False, err)
failures += 1
continue
time.sleep(RELAY_DLY)
status_code, resp_text, err = _get(f"{base}/relay/status?relay={relay_num}")
if err or status_code != 200:
record(f"{relay_name} OFF verify", False, err or f"HTTP {status_code}")
failures += 1
else:
try:
r = json.loads(resp_text)
if r.get("state") == False:
record(f"{relay_name} OFF", True)
else:
record(f"{relay_name} OFF verify", False, "State mismatch")
failures += 1
except:
record(f"{relay_name} OFF verify", False, "Invalid response")
failures += 1
# ─ 3. Digital Inputs ──────────────────────────────────────────────────────
print("\n3. Opto-Isolated Inputs (110VAC-240VAC)")
for input_num in [1, 2]:
status_code, resp_text, err = _get(f"{base}/input/status?input={input_num}")
if err or status_code != 200:
record(f"Input {input_num}", False, err or f"HTTP {status_code}")
failures += 1
else:
try:
r = json.loads(resp_text)
state = "HIGH" if r.get("state") else "LOW"
record(f"Input {input_num}", True, f"State: {state}")
except:
record(f"Input {input_num}", False, "Invalid response")
failures += 1
# ─ 4. LED ─────────────────────────────────────────────────────────────────
print("\n4. LED Control")
_, _, err = _post(f"{base}/led/on")
if err:
record("LED ON", False, err)
failures += 1
else:
record("LED ON", True)
time.sleep(0.2)
_, _, err = _post(f"{base}/led/off")
if err:
record("LED OFF", False, err)
failures += 1
else:
record("LED OFF", True)
# ─ 5. NFC Status ──────────────────────────────────────────────────────────
print("\n5. NFC Module (PN532 via UEXT, optional)")
status_code, resp_text, err = _get(f"{base}/nfc/status")
if err or status_code != 200:
record("NFC Status API", False, err or f"HTTP {status_code}")
else:
try:
nfc_status = json.loads(resp_text)
init = nfc_status.get("initialized", False)
record("NFC Status API", True, f"Init: {init}")
except:
record("NFC Status API", False, "Invalid response")
# ─ 6. NFC Config ──────────────────────────────────────────────────────────
status_code, resp_text, err = _get(f"{base}/nfc/config")
if err or status_code != 200:
record("NFC Config API", False, err or f"HTTP {status_code}")
else:
try:
config = json.loads(resp_text)
record("NFC Config API", True, f"Relay: {config.get('relay_num')}, Pulse: {config.get('pulse_ms')}ms")
except:
record("NFC Config API", False, "Invalid response")
# ─────────────────────────────────────────────────────────────────────────
# Summary
# ─────────────────────────────────────────────────────────────────────────
print(f"\n{'='*70}")
passed = len([r for r in results if r["pass"]])
total = len(results)
print(f"Result: {passed}/{total} tests passed")
if failures == 0:
print("✓ All systems functional!")
else:
print(f"{failures} failures detected")
print(f"{'='*70}\n")
return failures
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("ip", nargs="?", default="192.168.0.240", help="Board IP (default: 192.168.0.240)")
parser.add_argument("--json", action="store_true", help="Output results as JSON")
args = parser.parse_args()
failures = verify_board(args.ip)
if args.json:
print(json.dumps(results, indent=2))
sys.exit(min(failures, 1))
+133
View File
@@ -0,0 +1,133 @@
#!/bin/bash
# Build and Deploy Script for ESP32-C5-EVB
# Usage: ./build_and_deploy.sh [--flash-only]
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKETCH_DIR="$SCRIPT_DIR/esp32_arduino"
SKETCH_FILE="$SKETCH_DIR/esp32_arduino.ino"
DEVICE_PORT="${DEVICE_PORT:-/dev/ttyACM0}"
DEVICE_BAUD="${DEVICE_BAUD:-921600}"
DEVICE_IP="${DEVICE_IP:-192.168.0.241}"
# ─ Colors ─────────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# ─ Functions ──────────────────────────────────────────────────────────────────
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
log_error() { echo -e "${RED}[✗]${NC} $1"; }
# ─ Check Prerequisites ────────────────────────────────────────────────────────
log_info "Checking prerequisites..."
if [ ! -f "$SKETCH_FILE" ]; then
log_error "Sketch not found: $SKETCH_FILE"
exit 1
fi
if ! command -v esptool.py &> /dev/null; then
log_error "esptool.py not found. Install: pip install esptool"
exit 1
fi
if [ ! -e "$DEVICE_PORT" ]; then
log_error "Device port not found: $DEVICE_PORT"
log_info "Available ports:"
ls -la /dev/ttyUSB* /dev/ttyACM* 2>/dev/null || echo " (none found)"
exit 1
fi
log_success "Prerequisites OK"
# ─ Skip compilation if --flash-only ───────────────────────────────────────────
if [ "$1" == "--flash-only" ]; then
log_warn "Skipping compilation (--flash-only)"
LATEST_BUILD=$(find /tmp -name "arduino_build_*" -type d 2>/dev/null | sort -V | tail -1)
else
# ─ Build ──────────────────────────────────────────────────────────────────
log_info "Compiling sketch (this may take 30-60 seconds)..."
log_warn "Please click 'Sketch → Verify' in Arduino IDE or use: arduino --verify --board esp32:esp32:esp32c5 '$SKETCH_FILE'"
log_info ""
log_info "Waiting for build to complete..."
# Find the latest build directory created
BUILD_BEFORE=$(find /tmp -name "arduino_build_*" -type d 2>/dev/null | sort -V | tail -1)
START_TIME=$(date +%s)
# Wait for new build or changes to existing build (max 120 seconds)
while true; do
LATEST_BUILD=$(find /tmp -name "arduino_build_*" -type d 2>/dev/null | sort -V | tail -1)
if [ -n "$LATEST_BUILD" ] && [ -f "$LATEST_BUILD/esp32_arduino.ino.merged.bin" ]; then
MODIFIED=$(stat -f%m "$LATEST_BUILD/esp32_arduino.ino.merged.bin" 2>/dev/null || stat -c%Y "$LATEST_BUILD/esp32_arduino.ino.merged.bin")
CURRENT_TIME=$(date +%s)
if [ $((CURRENT_TIME - MODIFIED)) -lt 10 ]; then
log_success "Build found and appears recent"
break
fi
fi
CURRENT_TIME=$(date +%s)
if [ $((CURRENT_TIME - START_TIME)) -gt 120 ]; then
log_error "Timeout waiting for build. Please compile in Arduino IDE manually."
exit 1
fi
sleep 2
done
fi
# ─ Verify binary exists ───────────────────────────────────────────────────────
if [ -z "$LATEST_BUILD" ] || [ ! -f "$LATEST_BUILD/esp32_arduino.ino.merged.bin" ]; then
log_error "Compiled binary not found"
exit 1
fi
BINARY_PATH="$LATEST_BUILD/esp32_arduino.ino.merged.bin"
BINARY_SIZE=$(ls -lh "$BINARY_PATH" | awk '{print $5}')
log_success "Binary found: $BINARY_PATH ($BINARY_SIZE)"
# ─ Flash ──────────────────────────────────────────────────────────────────────
log_info "Erasing flash on $DEVICE_PORT..."
esptool.py --chip esp32c5 --port "$DEVICE_PORT" --baud "$DEVICE_BAUD" erase-flash
log_info "Flashing firmware..."
cd "$LATEST_BUILD"
esptool.py --chip esp32c5 --port "$DEVICE_PORT" --baud "$DEVICE_BAUD" write-flash 0x0 esp32_arduino.ino.merged.bin
log_success "Firmware flashed successfully!"
# ─ Verify ─────────────────────────────────────────────────────────────────────
log_info "Waiting for device to boot (10 seconds)..."
sleep 10
log_info "Testing connectivity to $DEVICE_IP..."
if curl -s -f -u Ske087:Matei@123 "http://$DEVICE_IP/relay/status?relay=1" &> /dev/null; then
log_success "Device is online and responding!"
# Test all endpoints
log_info "Testing API endpoints..."
echo -n " Relay 1: "
curl -s -u Ske087:Matei@123 "http://$DEVICE_IP/relay/status?relay=1"
echo -n " Relay 2: "
curl -s -u Ske087:Matei@123 "http://$DEVICE_IP/relay/status?relay=2"
echo -n " Input 1: "
curl -s -u Ske087:Matei@123 "http://$DEVICE_IP/input/status?input=1"
echo -n " Input 2: "
curl -s -u Ske087:Matei@123 "http://$DEVICE_IP/input/status?input=2"
log_success "All tests passed!"
else
log_warn "Device not yet responding. This is normal if WiFi connection is in progress."
log_info "Check with: curl -u Ske087:Matei@123 http://$DEVICE_IP/relay/status?relay=1"
fi
log_success "Deployment complete!"
@@ -0,0 +1,117 @@
"""Olimex ESP32-C5-EVB Integration for Home Assistant."""
import logging
import aiohttp
from homeassistant.config_entries import ConfigEntry, SOURCE_IMPORT
from homeassistant.core import HomeAssistant
from homeassistant.const import CONF_HOST, CONF_PORT, Platform
from homeassistant.components.webhook import (
async_register as webhook_register,
async_unregister as webhook_unregister,
)
from .const import DOMAIN, CONF_CALLBACK_IP, DEFAULT_CALLBACK_IP
from .webhook import handle_input_event
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SWITCH]
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
"""Set up the Olimex ESP32-C5-EVB component."""
hass.data.setdefault(DOMAIN, {})
# Handle YAML configuration
if DOMAIN in config:
for device_config in config[DOMAIN]:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=device_config,
)
)
_LOGGER.debug("Olimex ESP32-C5-EVB integration initialized")
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Olimex ESP32-C5-EVB from a config entry."""
_LOGGER.info("Setting up Olimex ESP32-C5-EVB entry: %s", entry.entry_id)
host = entry.data.get(CONF_HOST, "192.168.0.240")
port = entry.data.get(CONF_PORT, 80)
callback_ip = entry.data.get(CONF_CALLBACK_IP, DEFAULT_CALLBACK_IP)
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
"host": host,
"port": port,
}
# Tell the board where to POST input events
try:
callback_url = f"http://{callback_ip}:8123/api/webhook/{entry.entry_id}"
register_url = f"http://{host}:{port}/register?callback_url={callback_url}"
_LOGGER.info("Registering webhook with board at %s:%d (callback %s)", host, port, callback_url)
async with aiohttp.ClientSession() as session:
async with session.post(register_url, timeout=aiohttp.ClientTimeout(total=5)) as response:
if response.status == 200:
_LOGGER.info("Board webhook registered successfully")
else:
_LOGGER.warning("Board registration returned status %d", response.status)
except Exception as err:
_LOGGER.warning("Failed to register webhook with board: %s", err)
# Register HA webhook handler to receive input events from the board
# Unregister first in case a previous failed setup left it registered
try:
webhook_unregister(hass, entry.entry_id)
except Exception:
pass
webhook_register(
hass,
DOMAIN,
"Olimex Input Event",
entry.entry_id,
handle_input_event,
)
_LOGGER.info("HA webhook handler registered for entry %s", entry.entry_id)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
_LOGGER.info("Olimex ESP32-C5-EVB configured for %s:%d", host, port)
return True
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Clean up when integration is fully removed (called after unload)."""
_LOGGER.info("Removing Olimex ESP32-C5-EVB entry: %s", entry.entry_id)
# Remove any leftover domain data bucket if it's now empty
if DOMAIN in hass.data and not hass.data[DOMAIN]:
hass.data.pop(DOMAIN)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
try:
_LOGGER.info("Unloading Olimex ESP32-C5-EVB entry: %s", entry.entry_id)
# Unregister webhook handler
webhook_unregister(hass, entry.entry_id)
# Unload all platforms
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
# Clean up data
if entry.entry_id in hass.data.get(DOMAIN, {}):
hass.data[DOMAIN].pop(entry.entry_id)
_LOGGER.debug("Cleaned up data for entry %s", entry.entry_id)
_LOGGER.info("Successfully unloaded Olimex ESP32-C5-EVB entry: %s", entry.entry_id)
return unload_ok
except Exception as err:
_LOGGER.error("Error unloading Olimex ESP32-C5-EVB entry: %s", err, exc_info=True)
return False
@@ -0,0 +1,90 @@
"""Binary sensor platform for Olimex ESP32-C5-EVB."""
import logging
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import DOMAIN, NUM_INPUTS
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up binary sensor entities for inputs."""
data = hass.data[DOMAIN][entry.entry_id]
sensors = [
OlimexInputSensor(hass, entry, input_num)
for input_num in range(1, NUM_INPUTS + 1)
]
async_add_entities(sensors, update_before_add=False)
class OlimexInputSensor(BinarySensorEntity):
"""Binary sensor for Olimex opto-isolated input.
State is driven exclusively by webhook POSTs from the board.
No polling is performed.
"""
_attr_should_poll = False
def __init__(self, hass: HomeAssistant, entry: ConfigEntry, input_num: int):
"""Initialize the sensor."""
self.hass = hass
self._entry = entry
self._input_num = input_num
self._attr_name = f"Opto Input {input_num}"
self._attr_unique_id = f"{entry.entry_id}_input_{input_num}"
self._state = False
async def async_added_to_hass(self):
"""Subscribe to webhook dispatcher when entity is added."""
signal = f"{DOMAIN}_input_{self._input_num}_event"
self.async_on_remove(
async_dispatcher_connect(self.hass, signal, self._handle_webhook_event)
)
await super().async_added_to_hass()
@callback
def _handle_webhook_event(self, state):
"""Handle real-time input event received via webhook from the board."""
# Board already inverts opto logic before sending:
# state=True means triggered, state=False means not triggered
self._state = state
_LOGGER.debug(
"Input %d webhook event: state=%s (sensor is_on=%s)",
self._input_num, state, self._state
)
self.async_write_ha_state()
@property
def is_on(self):
"""Return True if opto-isolated input is triggered."""
return self._state
@property
def device_info(self):
"""Return device information."""
try:
host = self._entry.data.get('host', 'unknown')
return {
"identifiers": {(DOMAIN, self._entry.entry_id)},
"name": f"Olimex ESP32-C5 ({host})",
"manufacturer": "Olimex",
"model": "ESP32-C5-EVB",
}
except Exception as err:
_LOGGER.debug("Error getting device info: %s", err)
return {
"identifiers": {(DOMAIN, self._entry.entry_id)},
"name": "Olimex ESP32-C5",
"manufacturer": "Olimex",
"model": "ESP32-C5-EVB",
}
@@ -0,0 +1,112 @@
"""Config flow for Olimex ESP32-C5-EVB integration."""
import logging
import voluptuous as vol
import aiohttp
from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.helpers import config_validation as cv
from .const import DOMAIN, DEFAULT_PORT, CONF_CALLBACK_IP, DEFAULT_CALLBACK_IP
_LOGGER = logging.getLogger(__name__)
class OlimexESP32C5ConfigFlow(config_entries.ConfigFlow, domain="olimex_esp32_c5"):
"""Handle a config flow for Olimex ESP32-C5-EVB."""
VERSION = 1
async def async_step_user(self, user_input=None):
"""Handle the initial step."""
errors = {}
if user_input is not None:
host = user_input.get(CONF_HOST, "192.168.0.240")
port = user_input.get(CONF_PORT, DEFAULT_PORT)
# Validate connection to the board
try:
async with aiohttp.ClientSession() as session:
url = f"http://{host}:{port}/api/status"
async with session.get(
url,
timeout=aiohttp.ClientTimeout(total=10)
) as resp:
if resp.status != 200:
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(f"{host}:{port}")
self._abort_if_unique_id_configured()
_LOGGER.info("Successfully connected to Olimex ESP32-C5 at %s", host)
return self.async_create_entry(
title=f"Olimex ESP32-C5 ({host})",
data={
CONF_HOST: host,
CONF_PORT: port,
CONF_CALLBACK_IP: user_input.get(
CONF_CALLBACK_IP, DEFAULT_CALLBACK_IP
),
},
)
except aiohttp.ClientError:
errors["base"] = "cannot_connect"
_LOGGER.error("Failed to connect to Olimex ESP32-C5 at %s", host)
except Exception as err:
errors["base"] = "unknown"
_LOGGER.error("Error in config flow: %s", err)
data_schema = vol.Schema({
vol.Required(CONF_HOST, default="192.168.0.240"): str,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_CALLBACK_IP, default=DEFAULT_CALLBACK_IP): str,
})
return self.async_show_form(
step_id="user",
data_schema=data_schema,
errors=errors,
)
async def async_step_import(self, import_data):
"""Handle import from YAML configuration."""
_LOGGER.debug("Importing Olimex ESP32-C5 from YAML: %s", import_data)
host = import_data.get(CONF_HOST, "192.168.0.240")
port = import_data.get(CONF_PORT, DEFAULT_PORT)
# Check if already configured
await self.async_set_unique_id(f"{host}:{port}")
self._abort_if_unique_id_configured()
# Validate connection
try:
async with aiohttp.ClientSession() as session:
url = f"http://{host}:{port}/api/status"
async with session.get(
url,
timeout=aiohttp.ClientTimeout(total=10)
) as resp:
if resp.status == 200:
_LOGGER.info("Successfully imported Olimex ESP32-C5 from YAML at %s", host)
return self.async_create_entry(
title=f"Olimex ESP32-C5 ({host})",
data={
CONF_HOST: host,
CONF_PORT: port,
CONF_CALLBACK_IP: import_data.get(CONF_CALLBACK_IP, DEFAULT_CALLBACK_IP),
},
)
except Exception as err:
_LOGGER.error("Failed to import Olimex ESP32-C5 from YAML: %s", err)
# If validation fails, still create entry but log warning
_LOGGER.warning("Could not validate Olimex ESP32-C5 at %s, creating entry anyway", host)
return self.async_create_entry(
title=f"Olimex ESP32-C5 ({host})",
data={
CONF_HOST: host,
CONF_PORT: port,
CONF_CALLBACK_IP: import_data.get(CONF_CALLBACK_IP, DEFAULT_CALLBACK_IP),
},
)
@@ -0,0 +1,28 @@
"""Constants for the Olimex ESP32-C5-EVB integration."""
DOMAIN = "olimex_esp32_c5"
MANUFACTURER = "Olimex"
MODEL = "ESP32-C5-EVB"
CHIP = "ESP32-C5"
# Configuration
CONF_HOST = "host"
CONF_PORT = "port"
CONF_SCAN_INTERVAL = "scan_interval"
CONF_CALLBACK_IP = "callback_ip"
# Default values
DEFAULT_PORT = 80
DEFAULT_SCAN_INTERVAL = 5
DEFAULT_CALLBACK_IP = "192.168.0.1"
# Relay and Input info
NUM_RELAYS = 2
NUM_INPUTS = 2
# Device info
DEVICE_INFO = {
"manufacturer": MANUFACTURER,
"model": MODEL,
"chip": CHIP,
}
@@ -0,0 +1,11 @@
{
"domain": "olimex_esp32_c5",
"name": "Olimex ESP32-C5-EVB",
"codeowners": [],
"config_flow": true,
"dependencies": [],
"documentation": "https://github.com/yourusername/olimex-esp32-c5-ha",
"iot_class": "local_polling",
"requirements": ["aiohttp>=3.8.0"],
"version": "0.1.0"
}
@@ -0,0 +1,19 @@
{
"config": {
"step": {
"user": {
"title": "Configure Olimex ESP32-C5-EVB",
"description": "Enter the IP address or hostname of your Olimex ESP32-C5-EVB board",
"data": {
"host": "Host",
"port": "Port",
"callback_ip": "Home Assistant IP"
}
}
},
"error": {
"cannot_connect": "Failed to connect to the board. Check IP address and port.",
"unknown": "An unknown error occurred"
}
}
}
@@ -0,0 +1,117 @@
"""Switch platform for Olimex ESP32-C5-EVB."""
import logging
import aiohttp
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, NUM_RELAYS
_LOGGER = logging.getLogger(__name__)
# Tight timeout for a local LAN device
_TIMEOUT = aiohttp.ClientTimeout(total=3)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up switch entities for relays."""
data = hass.data[DOMAIN][entry.entry_id]
host = data["host"]
port = data["port"]
switches = [
OlimexRelaySwitch(entry, host, port, relay_num)
for relay_num in range(1, NUM_RELAYS + 1)
]
async_add_entities(switches, update_before_add=False)
class OlimexRelaySwitch(SwitchEntity):
"""Switch for Olimex relay.
State is set on load via a single GET and on every toggle via the
state value returned directly in the POST response — no extra round-trip.
A single persistent aiohttp session is reused for all requests.
"""
_attr_should_poll = False
def __init__(self, entry: ConfigEntry, host: str, port: int, relay_num: int):
"""Initialize the switch."""
self._entry = entry
self._host = host
self._port = port
self._relay_num = relay_num
self._attr_name = f"Relay {relay_num}"
self._attr_unique_id = f"{entry.entry_id}_relay_{relay_num}"
self._is_on = False
self._session: aiohttp.ClientSession | None = None
async def async_added_to_hass(self):
"""Open a persistent HTTP session and fetch initial relay state."""
self._session = aiohttp.ClientSession()
url = f"http://{self._host}:{self._port}/relay/status?relay={self._relay_num}"
try:
async with self._session.get(url, timeout=_TIMEOUT) as resp:
if resp.status == 200:
data = await resp.json()
self._is_on = data.get("state", False)
except Exception as err:
_LOGGER.debug("Relay %d initial fetch failed: %s", self._relay_num, err)
self.async_write_ha_state()
async def async_will_remove_from_hass(self):
"""Close the HTTP session when entity is removed."""
if self._session:
await self._session.close()
self._session = None
# ------------------------------------------------------------------
# SwitchEntity interface
# ------------------------------------------------------------------
async def async_turn_on(self, **kwargs):
"""Turn the relay on."""
await self._async_set_relay(True)
async def async_turn_off(self, **kwargs):
"""Turn the relay off."""
await self._async_set_relay(False)
async def _async_set_relay(self, on: bool):
"""POST on/off to the board; read state from the response body directly."""
action = "on" if on else "off"
url = f"http://{self._host}:{self._port}/relay/{action}?relay={self._relay_num}"
try:
async with self._session.post(url, timeout=_TIMEOUT) as resp:
if resp.status == 200:
data = await resp.json()
# Board returns {"status":"ok","state":true/false} — use it directly
self._is_on = data.get("state", on)
_LOGGER.debug("Relay %d -> %s (board confirmed: %s)", self._relay_num, action, self._is_on)
else:
_LOGGER.error("Relay %d %s failed: HTTP %d", self._relay_num, action, resp.status)
except Exception as err:
_LOGGER.error("Relay %d %s error: %s", self._relay_num, action, err)
self.async_write_ha_state()
@property
def is_on(self):
"""Return True if relay is on."""
return self._is_on
@property
def device_info(self):
"""Return device information."""
host = self._entry.data.get("host", "unknown")
return {
"identifiers": {(DOMAIN, self._entry.entry_id)},
"name": f"Olimex ESP32-C5 ({host})",
"manufacturer": "Olimex",
"model": "ESP32-C5-EVB",
}
@@ -0,0 +1,29 @@
"""Webhook handler for Olimex ESP32-C5-EVB input events."""
import logging
from aiohttp import web
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_send
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def handle_input_event(hass: HomeAssistant, webhook_id: str, request) -> web.Response:
"""Handle input event webhook from the board."""
try:
data = await request.json()
input_num = data.get("input")
state = data.get("state")
_LOGGER.info("Received input event: input=%s state=%s", input_num, state)
# Dispatch signal to update binary sensors immediately
signal = f"{DOMAIN}_input_{input_num}_event"
async_dispatcher_send(hass, signal, state)
return web.json_response({"status": "ok"})
except Exception as err:
_LOGGER.error("Error handling webhook: %s", err)
return web.json_response({"error": str(err)}, status=400)
@@ -0,0 +1,299 @@
# ESP32-C5 Arduino Deployment Guide
Complete guide to compile and deploy the firmware to your Olimex ESP32-C5-EVB board.
---
## ✓ Pre-Deployment Checklist
- [ ] Arduino IDE installed (version 2.0+)
- [ ] ESP32 board package installed (version 3.0.0+)
- [ ] Olimex ESP32-C5-EVB board connected via USB
- [ ] USB drivers installed for your OS
- [ ] WiFi credentials available
---
## Step 1: Install Arduino IDE
1. Download from: https://www.arduino.cc/en/software
2. Install and launch **Arduino IDE 2.0 or later**
**⚠️ Arduino IDE 1.x (including the system package `arduino` on Linux) will NOT work with ESP32 core 3.x.**
Core 3.x uses nested variable references in its build system that only Arduino IDE 2.x resolves correctly.
Installing `arduino` via `apt` gives you IDE 1.8.19 — download the AppImage/zip directly from arduino.cc instead.
---
## Step 2: Add ESP32 Board Support
### Windows/Mac/Linux (same process):
1. **Open Preferences**
- File → Preferences (or Arduino IDE → Settings on Mac)
2. **Add Board URL**
- Find "Additional Boards Manager URLs" field
- Add this URL:
```
https://espressif.github.io/arduino-esp32/package_esp32_index.json
```
- Click OK
3. **Install ESP32 Board Package**
- Tools → Board → Boards Manager
- Search: "esp32"
- Install "esp32 by Espressif Systems" (version 3.0.0+)
- Wait for installation to complete
---
## Step 3: Configure Board Settings
After installation, configure these exact settings in Arduino IDE:
### Tools Menu Settings:
| Setting | Value |
|---------|-------|
| Board | **ESP32C5 Dev Module** |
| USB CDC On Boot | **Enabled** ⚠️ **REQUIRED for Serial Monitor** |
| Flash Size | **4MB** |
| Flash Frequency | **80 MHz** |
| Flash Mode | **QIO** (default for C5) |
| PSRAM | Disabled |
| CPU Frequency | 240 MHz (WiFi) |
| Core Debug Level | None |
| Partition Scheme | **No OTA (2MB APP/2MB SPIFFS)** |
| Port | `/dev/ttyACM0` (Linux) or `/dev/cu.usbserial-*` (Mac) |
> **Note:** The ESP32-C5 does not have a "USB Mode" menu. Its USB hardware is fixed (Hardware CDC).
> The correct partition scheme for 4 MB flash is **No OTA (2MB APP/2MB SPIFFS)** — this gives 2 MB app space vs 1 MB with `noota_3g`.
**⚠️ IMPORTANT:**
- **USB CDC On Boot MUST be ENABLED** for Serial output to work in Arduino IDE Serial Monitor
- The device will show 2 serial ports when USB CDC is enabled:
- First port: USB CDC (for Serial.print output)
- Second port: USB-UART (legacy, can be ignored)
**⚠️ Important:** USB CDC On Boot must be **Enabled** for serial output to work!
---
## Step 4: Install Required Libraries
The sketch uses these libraries. Install them via Sketch → Include Library → Manage Libraries:
1. **PN532** (by Elechouse)
- Search: "PN532"
- Install the official PN532 library by Elechouse
- Version: 1.32 or later
2. Other libraries (WiFi, WebServer, Preferences) are built-in to ESP32 core
---
## Step 5: Configure Secrets
1. Copy `secrets.h.example` to `secrets.h` in the same directory:
```bash
cp esp32_arduino/secrets.h.example esp32_arduino/secrets.h
```
2. Edit `secrets.h` with your credentials:
```cpp
const char* WEB_USER = "admin";
const char* WEB_PASSWORD = "admin"; // Change this!
const char* API_SECRET = ""; // Optional HMAC secret
```
3. Also update WiFi credentials in `esp32_arduino.ino` (around line 58):
```cpp
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
```
**⚠️ Never commit `secrets.h` to Git!**
---
## Step 6: Flash the Board
### Connect the Board:
1. Plug the Olimex ESP32-C5-EVB into your computer via USB
2. Wait for USB drivers to load (Windows: ~30 seconds)
### In Arduino IDE:
1. Open `esp32_arduino.ino`
2. Tools → Port → Select your board's port
- **Linux:** `/dev/ttyUSB0`, `/dev/ttyUSB1`, or `/dev/ACM0`
- **macOS:** `/dev/cu.usbserial-*` or `/dev/cu.SLAB_USBtoUART`
- **Windows:** `COM3`, `COM4`, etc.
3. Sketch → Upload (or press Ctrl+U / Cmd+U)
4. Wait for compilation and flashing to complete (~30 seconds)
### Expected Output:
```
Connecting........___....._____
esptool.py v4.5.1
Serial port COM3
Chip is ESP32-C5...
Uploading stub...
...
Leaving...
Hard resetting via RTS pin...
```
### If Upload Fails:
**Problem:** "Failed to connect to ESP32"
- **Solution 1:** Try different baud rates (Tools → Upload Speed)
- **Solution 2:** Hold USER button during upload
- **Solution 3:** Different USB cable or port
- **Solution 4:** Update board drivers for your OS
**Problem:** "No module named 'serial'"
- **Solution:** Update Arduino IDE to latest version
---
## Step 7: Verify Installation
After successful upload, the board will reboot.
### Check Serial Monitor:
1. Tools → Serial Monitor (or Ctrl+Shift+M / Cmd+Shift+M)
2. Set baud rate to **115200**
3. Should see:
```
=================================
ESP32-C5 Home Assistant Device
Arduino Framework
=================================
GPIO initialized
Connecting to WiFi: YOUR_SSID (attempt 1/3)
...............................
✓ WiFi connected!
IP : 192.168.0.240
RSSI : -42 dBm
MAC : xx:xx:xx:xx:xx:xx
✓ HTTP server started on port 80
=================================
Ready! Try these endpoints:
http://192.168.0.240/api/status
=================================
```
### Test in Browser:
1. Open browser and go to: `http://192.168.0.240/`
2. Login with credentials from `secrets.h` (default: admin/admin)
3. Should see interactive control panel with relays and inputs
### Test API:
```bash
curl http://192.168.0.240/api/status
# Expected response:
# {"input1":true,"input2":true,"relay1":false,"relay2":false,...}
```
---
## Step 8: Add to Home Assistant
Once the board is verified and WiFi works:
1. In Home Assistant: Settings → Devices & Services
2. Click "Create Automation" → Integration
3. Search for "Olimex ESP32-C5"
4. Click "Olimex ESP32-C5-EVB"
5. Enter:
- **Host:** 192.168.0.240
- **Port:** 80
- **Callback IP:** Your Home Assistant server IP (usually 192.168.0.1 or 192.168.0.100)
6. Click "Create Entry"
Home Assistant will automatically:
- Discover relays and inputs
- Register a webhook for input events
- Create switches and binary sensors
---
## Troubleshooting
### Board doesn't show in Port menu:
- Install drivers: https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers
- Try different USB cable
- Check Device Manager (Windows) or System Report (Mac)
### Serial output garbled:
- Check baud rate is **115200** in Serial Monitor
- Try different baud rates (9600, 57600)
### WiFi connection fails:
- Verify SSID and password in `esp32_arduino.ino`
- Check 2.4 GHz WiFi is enabled (C5 may not support 5 GHz yet)
- Power cycle board and router
### NFC not detected:
- Check PN532 is soldered correctly to UEXT connector
- Verify TX=GPIO26, RX=GPIO25 wiring
- Set PN532 DIP switches to HSU mode (both = 0)
- Check serial output for baud detection attempts
### Can't reach board from Home Assistant:
- Verify board IP: check serial monitor output
- Ping from HA machine: `ping 192.168.0.240`
- Check firewall allows port 80
- Ensure HA and board are on same network
---
## Updating Firmware
To update code after initial deployment:
1. Make changes in Arduino IDE
2. Edit WiFi credentials if needed
3. Sketch → Upload
4. Monitor serial output to verify successful flash
---
## Protecting Your Device
**Before deployment to production:**
1. Change default credentials in `secrets.h`:
```cpp
const char* WEB_USER = "secure_user";
const char* WEB_PASSWORD = "strong_password_123";
```
2. Set an API secret for request signing:
```cpp
const char* API_SECRET = "my_random_secret_key_xyz123";
```
3. Only expose board on trusted networks (private LAN)
4. Consider firewall rules to restrict access
---
## Next Steps
- Check [README.md](../README.md) for API endpoints and features
- Configure Home Assistant automations with the new switches/sensors
- Optional: Add NFC card reader to UEXT connector for access control
- Explore [custom_components](../custom_components/) for integration details
---
## Support
- Olimex Board: https://github.com/OLIMEX/ESP32-C5-EVB
- ESP32 Arduino: https://github.com/espressif/arduino-esp32
- PN532 Library: https://github.com/elechouse/PN532
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true,
"**/secrets.h": true,
"**/*.o": true
},
"search.exclude": {
"**/.git": true,
"**/node_modules": true,
"**/.venv": true
}
}
}
@@ -0,0 +1,40 @@
/**
* secrets.h — Credentials & API Keys (NOT Version Controlled)
*
* Instructions:
* 1. Copy this file to secrets.h (same directory)
* 2. Edit secrets.h with your WiFi credentials and API secret
* 3. DO NOT commit secrets.h to Git
* 4. Add secrets.h to .gitignore if not already present
* 5. Flash the board with Arduino IDE
*/
#ifndef SECRETS_H
#define SECRETS_H
// ─ WiFi Credentials ───────────────────────────────────────────────────────
const char* WIFI_SSID = "YourWiFiSSID";
const char* WIFI_PASSWORD = "YourWiFiPassword";
// ─ Static IP Configuration ────────────────────────────────────────────────
// Set USE_STATIC_IP = true to use a fixed IP address
// Otherwise, board will request IP via DHCP
const bool USE_STATIC_IP = false; // true = static IP; false = DHCP
const char* STATIC_IP_ADDR = "192.168.0.240"; // e.g., "192.168.0.240"
const char* STATIC_GATEWAY = "192.168.0.1"; // e.g., "192.168.0.1"
const char* STATIC_SUBNET = "255.255.255.0"; // e.g., "255.255.255.0"
const char* STATIC_DNS1 = "8.8.8.8"; // e.g., "8.8.8.8"
const char* STATIC_DNS2 = "8.8.4.4"; // e.g., "8.8.4.4"
// ─ Web Server Credentials ──────────────────────────────────────────────────
const char* WEB_USER = "admin";
const char* WEB_PASSWORD = "admin";
// ─ API Authentication Secret ──────────────────────────────────────────────
// Used for HMAC-SHA256 request signing (optional)
// If empty, API requests do not require authentication.
// To enable, set a random string and include X-Request-Time/X-Request-Sig headers.
// Example: "my_super_secret_api_key_12345"
const char* API_SECRET = "";
#endif
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""
NFC Pin Mapping Diagnostic for ESP32-C5-EVB
Verifies the UEXT connector GPIO assignments
"""
import requests
import json
import sys
DEVICE_IP = sys.argv[1] if len(sys.argv) > 1 else "192.168.0.240"
WEB_USER = "Ske087"
WEB_PASS = "Matei@123"
print("=" * 70)
print("ESP32-C5-EVB UEXT Pin Mapping Diagnostic")
print("=" * 70)
# Try to get device info
try:
resp = requests.get(f"http://{DEVICE_IP}/",
auth=(WEB_USER, WEB_PASS),
timeout=2)
if resp.status_code == 200:
print(f"\n✓ Device reachable at {DEVICE_IP}\n")
else:
print(f"\n✗ Device responded with {resp.status_code}")
except Exception as e:
print(f"\n✗ Cannot reach device: {e}")
sys.exit(1)
# Check NFC status
try:
resp = requests.get(f"http://{DEVICE_IP}/nfc/status",
auth=(WEB_USER, WEB_PASS),
timeout=2)
nfc_status = resp.json()
print("NFC Module Status:")
print(f" Initialized: {nfc_status.get('initialized', False)}")
print(f" Enabled: {nfc_status.get('nfc_enabled', False)}")
print(f" Access State: {nfc_status.get('access_state', 'unknown')}")
if not nfc_status.get('initialized'):
print("\n⚠️ NFC module NOT initialized!")
print("\nDiagnostic Checklist:")
print(" 1. Is PN532 physically connected to UEXT connector?")
print(" 2. Are DIP switches set to HSU mode (DIP1=0, DIP2=0)?")
print(" 3. Check 3.3V power on UEXT Pin 2 (multimeter)")
print(" 4. Verify TX/RX wiring:")
print(" - UEXT Pin 3 (GPIO26) → PN532 RXD")
print(" - UEXT Pin 4 (GPIO25) ← PN532 TXD")
print(" 5. Some PN532 boards need 100Ω resistor on TX line")
except Exception as e:
print(f"✗ Cannot get NFC status: {e}")
print("\n" + "=" * 70)
print("C5-EVB vs C6-EVB UEXT Pin Mapping Reference:")
print("=" * 70)
print("""
C6-EVB (for reference):
UEXT Pin 3 → GPIO4 (UART1 TX)
UEXT Pin 4 → GPIO5 (UART1 RX)
C5-EVB (current board):
UEXT Pin 3 → GPIO26 (UART1 TX)
UEXT Pin 4 → GPIO25 (UART1 RX)
UEXT Pin 1 → GND
UEXT Pin 2 → +3V3 (power)
If you previously worked with C6 and connected PN532 to GPIO 4/5,
you MUST move it to GPIO 26/25 on the C5!
""")
print("=" * 70)