Tau.Acuvim/docs/acuvim-spec-01.md
Renier Forster 84a0668c54 Initial commit: Tau Acuvim IoT monitoring system
Complete IoT monitoring platform for Acuvim II power meters via ESP32.

Firmware (Phases 1-7):
- ESP32-WROVER-B (TTGO T-Call v1.4) with RS485 Modbus RTU
- WiFi STA+AP concurrent mode with GSM/GPRS failover
- Transport abstraction layer with 4 priority modes
- MQTT protocol with 20 commands, LWT, QoS, exponential backoff
- SD card offline buffering with JSONL rotation and non-blocking drain
- OTA firmware updates with dual partition rollback protection
- Watchdog timer, crash loop detection, Acuvim health monitoring
- Captive portal provisioning with AP mode

Console backend (Phase 8):
- .NET 10 minimal API with PostgreSQL + EF Core
- JWT authentication, SignalR real-time updates
- MQTTnet 5.x bridge service with health monitoring
- Device, telemetry, firmware, alert, group management
- Rate limiting, security headers, Swagger/OpenAPI

Frontend (Phase 9):
- React 18 + TypeScript + Vite with Ant Design 5
- ECharts telemetry visualization, TanStack Query
- SignalR live updates, device management UI
- Dashboard, fleet management, firmware deployment

Testing & Production (Phase 10):
- 28 firmware unit tests (Modbus, JSON, config, version)
- 23 xUnit backend tests (device, telemetry, command, alert)
- Docker Compose with nginx, TLS MQTT, PostgreSQL
- Production deployment, commissioning, and troubleshooting docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-16 19:05:32 +02:00

392 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 1: Project Setup, Hardware Abstraction & RS485 Modbus Communication
## Objective
Establish the PlatformIO project structure, configure the LILYGO TTGO T-Call v1.4 board, build the hardware abstraction layer, and implement RS485 Modbus RTU communication with the Acuvim II power meter.
## Prerequisites
- LILYGO TTGO T-Call v1.4 board
- Acuvim II power meter with RS485 interface
- RS485-to-TTL transceiver module (e.g., MAX485 or SP3485)
- Acuvim II Modbus register map documentation
- USB-C cable for programming
- PlatformIO IDE (VS Code extension)
## Deliverables
1. PlatformIO project with correct board and library configuration
2. Pin mapping header for TTGO T-Call v1.4
3. RS485 Modbus RTU driver for Acuvim II
4. Acuvim II register definitions and data structures
5. Polling task that reads all inverter parameters
6. Serial debug output confirming successful reads
---
## 1.1 PlatformIO Project Structure
```
Tau.Acuvim/
├── firmware/
│ ├── platformio.ini
│ ├── src/
│ │ └── main.cpp
│ ├── include/
│ │ ├── pin_config.h
│ │ ├── acuvim_registers.h
│ │ ├── acuvim_reader.h
│ │ └── config_manager.h
│ ├── lib/
│ │ └── README.md
│ ├── data/ # LittleFS web files (Phase 3)
│ └── test/
├── console/ # Console app (Phase 8-9)
├── docs/
│ └── acuvim-spec-xx.md
└── README.md
```
## 1.2 PlatformIO Configuration
### `platformio.ini`
```ini
[env:ttgo-t-call]
platform = espressif32
board = esp-wrover-kit
framework = arduino
monitor_speed = 115200
upload_speed = 921600
board_build.partitions = min_spiffs.csv
board_build.f_cpu = 240000000L
board_build.f_flash = 80000000L
board_build.flash_mode = qio
lib_deps =
4-20ma/ModbusMaster@^2.0.1
bblanchon/ArduinoJson@^7.0.0
build_flags =
-DBOARD_HAS_PSRAM
-DCORE_DEBUG_LEVEL=3
```
**Notes:**
- `esp-wrover-kit` is the closest PlatformIO board definition for the ESP32-WROVER-B module
- Partition table will be changed in Phase 6 (OTA) to support two app partitions
- Additional libraries will be added in subsequent phases
## 1.3 Pin Configuration
### `pin_config.h`
Define all hardware pin assignments for the TTGO T-Call v1.4 in a single header. This is the hardware abstraction layer that will be updated if the board changes.
```
TTGO T-Call v1.4 Pin Assignments
================================
Modem (SIM800L) Pins:
MODEM_RST = GPIO 5
MODEM_PWRKEY = GPIO 4
MODEM_POWER_ON = GPIO 23
MODEM_TX = GPIO 27
MODEM_RX = GPIO 26
LED:
LED_PIN = GPIO 13
I2C (IP5306 Power Management):
I2C_SDA = GPIO 21
I2C_SCL = GPIO 22
RS485 (Acuvim II) - User-assigned:
RS485_RX = GPIO 32
RS485_TX = GPIO 33
RS485_DE_RE = GPIO 25 # Direction control (DE+RE tied together)
SD Card (SPI) - User-assigned (Phase 5):
SD_CS = GPIO 15
SD_MOSI = GPIO 2
SD_MISO = GPIO 14
SD_CLK = GPIO 12
Available GPIOs (unused):
GPIO 0 (BOOT - avoid for general use)
GPIO 34 (input only)
GPIO 35 (input only)
GPIO 36 (input only, VP)
GPIO 39 (input only, VN)
```
**Important constraints:**
- GPIO 6-11 are used by internal flash (do not use)
- GPIO 34-39 are input-only (no pull-up/pull-down)
- GPIO 2 must be LOW during boot (SD MOSI — acceptable since SPI is idle at boot)
- GPIO 12 (MTDI) controls flash voltage on WROVER — if using SD on GPIO 12, set efuse or use `board_build.flash_mode = qio` to avoid boot issues
**RS485 wiring to MAX485/SP3485:**
```
ESP32 GPIO 33 (TX) ───→ DI (Driver Input)
ESP32 GPIO 32 (RX) ←─── RO (Receiver Output)
ESP32 GPIO 25 (DE) ───→ DE + RE (tied together)
MAX485 A ────────────→ Acuvim II RS485 A (+ / Data+)
MAX485 B ────────────→ Acuvim II RS485 B (- / Data-)
GND ─────────────────→ Acuvim II GND (common ground)
```
## 1.4 Acuvim II Modbus Register Map
The Acuvim II uses Modbus RTU over RS485 with the following defaults:
- Baud rate: 9600 (configurable: 120019200)
- Data bits: 8
- Parity: None
- Stop bits: 1
- Slave address: 1 (configurable: 1247)
### Key Register Groups
Define these in `acuvim_registers.h`:
```
Register Address | Length | Parameter | Unit | Data Type | Scale
------------------|--------|------------------------|---------|--------------|-------
0x0000 | 2 | Phase A Voltage (Ua) | V | Float32 | -
0x0002 | 2 | Phase B Voltage (Ub) | V | Float32 | -
0x0004 | 2 | Phase C Voltage (Uc) | V | Float32 | -
0x0006 | 2 | Phase A Current (Ia) | A | Float32 | -
0x0008 | 2 | Phase B Current (Ib) | A | Float32 | -
0x000A | 2 | Phase C Current (Ic) | A | Float32 | -
0x000C | 2 | Active Power (P) | kW | Float32 | -
0x000E | 2 | Reactive Power (Q) | kVAR | Float32 | -
0x0010 | 2 | Apparent Power (S) | kVA | Float32 | -
0x0012 | 2 | Power Factor (PF) | - | Float32 | -
0x0014 | 2 | Frequency (F) | Hz | Float32 | -
0x0016 | 2 | Phase A Power (Pa) | kW | Float32 | -
0x0018 | 2 | Phase B Power (Pb) | kW | Float32 | -
0x001A | 2 | Phase C Power (Pc) | kW | Float32 | -
0x001C | 2 | Line Voltage AB (Uab) | V | Float32 | -
0x001E | 2 | Line Voltage BC (Ubc) | V | Float32 | -
0x0020 | 2 | Line Voltage CA (Uca) | V | Float32 | -
Energy Registers (Accumulated):
0x0100 | 2 | Import Active Energy | kWh | Float32 | -
0x0102 | 2 | Export Active Energy | kWh | Float32 | -
0x0104 | 2 | Import Reactive Energy | kVARh | Float32 | -
0x0106 | 2 | Export Reactive Energy | kVARh | Float32 | -
Demand Registers:
0x0200 | 2 | Active Power Demand | kW | Float32 | -
0x0202 | 2 | Max Active Demand | kW | Float32 | -
0x0204 | 2 | Reactive Power Demand | kVAR | Float32 | -
THD Registers:
0x0300 | 2 | Voltage THD A | % | Float32 | -
0x0302 | 2 | Voltage THD B | % | Float32 | -
0x0304 | 2 | Voltage THD C | % | Float32 | -
0x0306 | 2 | Current THD A | % | Float32 | -
0x0308 | 2 | Current THD B | % | Float32 | -
0x030A | 2 | Current THD C | % | Float32 | -
```
**Note:** Register addresses and data types must be verified against the actual Acuvim II documentation for the specific model (Acuvim II, IIR, IIE, IIW). The above is representative — the actual register map from Accuenergy should be used as the source of truth.
### Data Structure
```cpp
struct AcuvimData {
// Phase Voltages
float voltage_a; // V
float voltage_b; // V
float voltage_c; // V
// Phase Currents
float current_a; // A
float current_b; // A
float current_c; // A
// Power
float active_power; // kW (total)
float reactive_power; // kVAR (total)
float apparent_power; // kVA (total)
float power_factor; // -1.0 to 1.0
// Frequency
float frequency; // Hz
// Per-Phase Power
float power_a; // kW
float power_b; // kW
float power_c; // kW
// Line Voltages
float voltage_ab; // V
float voltage_bc; // V
float voltage_ca; // V
// Energy (Accumulated)
float import_active_energy; // kWh
float export_active_energy; // kWh
float import_reactive_energy; // kVARh
float export_reactive_energy; // kVARh
// Demand
float active_demand; // kW
float max_active_demand; // kW
float reactive_demand; // kVAR
// THD
float thd_voltage_a; // %
float thd_voltage_b; // %
float thd_voltage_c; // %
float thd_current_a; // %
float thd_current_b; // %
float thd_current_c; // %
// Metadata
uint32_t timestamp; // epoch seconds
bool valid; // true if read succeeded
uint8_t error_code; // 0 = success, else Modbus exception
};
```
## 1.5 Modbus RTU Driver Implementation
### `acuvim_reader.h` / `acuvim_reader.cpp`
**Class: `AcuvimReader`**
Responsibilities:
- Initialize HardwareSerial (Serial2) for RS485 at configured baud rate
- Control RS485 direction pin (DE/RE) for half-duplex operation
- Read register blocks using ModbusMaster library
- Parse Float32 values from Modbus register pairs (big-endian IEEE 754)
- Populate `AcuvimData` struct
- Handle communication errors with retry logic
- Report read success/failure statistics
**Key methods:**
```cpp
class AcuvimReader {
public:
bool begin(uint8_t slaveAddress = 1, uint32_t baudRate = 9600);
bool readAll(AcuvimData& data);
bool readVoltages(AcuvimData& data);
bool readCurrents(AcuvimData& data);
bool readPower(AcuvimData& data);
bool readEnergy(AcuvimData& data);
bool readDemand(AcuvimData& data);
bool readTHD(AcuvimData& data);
uint32_t getSuccessCount() const;
uint32_t getErrorCount() const;
uint8_t getLastError() const;
private:
ModbusMaster modbus;
uint8_t slaveAddr;
float readFloat32(uint16_t reg);
bool readRegisterBlock(uint16_t startReg, uint16_t count, uint16_t* buffer);
static void preTransmission();
static void postTransmission();
};
```
**RS485 direction control callbacks:**
```cpp
// Called before Modbus transmission — set DE/RE HIGH to enable transmit
void AcuvimReader::preTransmission() {
digitalWrite(RS485_DE_RE, HIGH);
}
// Called after Modbus transmission — set DE/RE LOW to enable receive
void AcuvimReader::postTransmission() {
digitalWrite(RS485_DE_RE, LOW);
}
```
**Float32 parsing from Modbus registers:**
The Acuvim II stores 32-bit floats across two consecutive 16-bit registers (big-endian). The ModbusMaster library returns registers in order, so:
```cpp
float AcuvimReader::readFloat32(uint16_t reg) {
uint32_t raw = ((uint32_t)modbus.getResponseBuffer(reg) << 16)
| (uint32_t)modbus.getResponseBuffer(reg + 1);
float value;
memcpy(&value, &raw, sizeof(float));
return value;
}
```
### Error Handling
- Max 3 retries per register block read with 50ms delay between retries
- On persistent failure, mark `AcuvimData.valid = false` and set `error_code`
- Track cumulative success/error counts for health reporting (Phase 7)
- Log errors to Serial for debugging
## 1.6 Main Application (Phase 1 Scope)
### `main.cpp`
Phase 1 main loop is minimal — it initializes the RS485 interface, polls the Acuvim II at a configurable interval (default: 5 seconds), and prints results to Serial for verification.
```cpp
// Pseudocode for Phase 1 main.cpp
void setup() {
Serial.begin(115200);
acuvimReader.begin(SLAVE_ADDRESS, BAUD_RATE);
}
void loop() {
AcuvimData data;
if (acuvimReader.readAll(data)) {
printAcuvimData(data); // Serial debug output
} else {
Serial.printf("Read failed, error: %d\n", data.error_code);
}
delay(pollInterval);
}
```
## 1.7 Testing & Validation
| Test | Method | Pass Criteria |
|------|--------|---------------|
| Build succeeds | `pio run` | Zero errors, zero warnings |
| Upload to board | `pio run -t upload` | Firmware flashes successfully |
| Serial output | `pio device monitor` | Prints structured data at poll interval |
| Modbus read | Connect to Acuvim II | Valid voltage/current/power readings |
| Error handling | Disconnect RS485 | Error reported, no crash, recovery on reconnect |
| Baud rate mismatch | Set wrong baud | Error reported, no hang |
| Slave address mismatch | Set wrong address | Timeout reported, no hang |
| Continuous operation | Run for 1 hour | No memory leaks, stable read rate |
## 1.8 Dependencies
| Library | Version | Purpose |
|---------|---------|---------|
| ModbusMaster | ^2.0.1 | Modbus RTU master protocol |
| ArduinoJson | ^7.0.0 | JSON serialization (used from Phase 2 onward) |
## 1.9 Phase 1 Completion Criteria
- [ ] PlatformIO project builds and uploads to TTGO T-Call v1.4
- [ ] RS485 communication established with Acuvim II
- [ ] All register groups read successfully (voltages, currents, power, energy, demand, THD)
- [ ] Float32 values parsed correctly from Modbus register pairs
- [ ] Error handling tested (disconnection, timeout, wrong address)
- [ ] Data printed to Serial in human-readable format
- [ ] Pin configuration documented and verified against hardware
---
**Next Phase:** [Phase 2 — WiFi, MQTT & NVS Configuration](acuvim-spec-02.md)