# Carlo Gavazzi EM580 Power Meter Driver Driver module for the **Carlo Gavazzi EM580** power meter using Modbus via EVerest's `serial_communication_hub` interface. It implements the standardized EVerest `powermeter` interface and supports **OCMF/Eichrecht** transaction flows. ## Overview This is an **EVerest Hardware Driver** module that: - **Implements**: `powermeter` interface - **Communicates**: Modbus RTU (through `SerialCommHub`) - **Provides**: Live meter values, OCMF transaction start/stop handling, public key publishing ## Features - **Live measurements**: Publishes `powermeter` readings periodically (`live_measurement_interval_ms`) - **OCMF transactions**: - `start_transaction`: writes OCMF identification fields + tariff text (TT) + start command - `stop_transaction`: ends transaction, waits for READY, reads OCMF file, confirms file read - **Modbus protocol compliance**: transport splits writes into chunks (max 123 registers per request) - **Resilience / retries**: - Separate initial connection retry settings vs. normal operation retry settings - Communication-fault raise/clear hooks - **Device state monitoring**: periodic read of device state bitfield (`device_state_read_interval_ms`) - **Signature key readout**: reads signature type and public keys; publishes public key (hex) via `public_key_ocmf` ## Hardware requirements & compatibility ### Supported devices - **Carlo Gavazzi EM580** with **Modbus RTU** enabled/available. - **OCMF/Eichrecht flow**: requires a meter variant/firmware that supports the OCMF register set used by this driver. If you are unsure which EM580 variant you have, check the device documentation/ordering code and confirm: - Modbus RTU via RS-485 is supported and enabled - The meter is configured for a known Modbus **unit id** (device address) - **Carlo Gavazzi EM300** with **Modbus RTU** enabled/available. These models do not support OCMF/Eichrecht and can only be used as usual power meter. All transaction related configuration etc. does not apply for such devices. ### Bus / physical layer - **RS-485 (2-wire, half duplex)**: correct A/B wiring is essential. - **Termination**: enable 120Ω termination at the ends of the RS-485 bus (and only at the ends). - **Biasing**: ensure the bus has proper bias resistors (often provided by the adapter/master or by dedicated biasing). ### Host requirements - A Linux host running EVerest with access to a serial device (e.g. `/dev/ttyUSB0`). - A **USB-to-RS485** adapter (or equivalent RS-485 interface) supported by the OS. - Permissions to access the serial device node (group membership / udev rules). ## Configuration ### Required connections The module requires a `serial_communication_hub` implementation (typically `SerialCommHub`) via its `modbus` requirement. `SerialCommHub` encapsulates the serial port settings (port, baudrate, parity, timeouts). The EM580 module only needs the hub connection plus its Modbus unit id (`powermeter_device_id`). ### Example configuration (bringup) See `config/bringup/config-bringup-CGEM580.yaml`: ```yaml active_modules: cgem580: module: CarloGavazzi_EM580 config_implementation: main: powermeter_device_id: 1 communication_retry_count: 3 communication_retry_delay_ms: 500 communication_error_pause_delay_s: 10 initial_connection_retry_count: 10 initial_connection_retry_delay_ms: 2000 timezone_offset_minutes: 60 live_measurement_interval_ms: 1000 device_state_read_interval_ms: 10000 connections: modbus: - module_id: comm_hub implementation_id: main ``` ### Multiple EM580 devices on one RS-485 bus You can run multiple EM580 devices on the same RS-485 line by: - Creating multiple `CarloGavazzi_EM580` module instances - Pointing them all to the same `SerialCommHub` - Giving each instance a unique `powermeter_device_id` (Modbus unit id) ### Configuration parameters All parameters are defined in `modules/HardwareDrivers/PowerMeters/CarloGavazzi_EM580/manifest.yaml`: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `powermeter_device_id` | integer | `1` | Modbus device ID on the bus | | `communication_retry_count` | integer | `3` | Retries for regular Modbus operations | | `communication_retry_delay_ms` | integer | `500` | Delay between regular retries | | `communication_error_pause_delay_s` | integer | `10` | Pause after a communication failure in the live measurement thread before retrying (also applies to initial communication) | | `initial_connection_retry_count` | integer | `10` (0 = infinite) | Retries during initial device setup/signature config reads | | `initial_connection_retry_delay_ms` | integer | `2000` | Delay between initialization retries | | `timezone_offset_minutes` | integer | `0` | Timezone offset from UTC (minutes) | | `live_measurement_interval_ms` | integer | `1000` | Interval for reading/publishing live measurements | | `device_state_read_interval_ms` | integer | `10000` | Interval for reading device-state bitfield (VendorError reporting) | | `public_key_format` | enum | `binary` | The key format to use for the public key. ### Parameter tuning notes - **`initial_connection_*`**: - Used during module startup for device setup / signature config reads. - `initial_connection_retry_count: 0` means **retry forever**. - `initial_connection_retry_delay_ms` has a **minimum of 100ms** (see `manifest.yaml`). - **`communication_*`**: - Used for regular Modbus operations during runtime. - `communication_retry_delay_ms` has a **minimum of 10ms** (see `manifest.yaml`). - **`communication_error_pause_delay_s`**: - After a communication exception in the live thread, the module waits this long before retrying. - If the line is physically broken (wrong wiring / adapter unplugged), increasing this value reduces log spam. - **`live_measurement_interval_ms` / `device_state_read_interval_ms`**: - Keep live measurements reasonable for your bus speed and number of devices. - For multi-drop RS-485, consider increasing intervals if you see bus contention/timeouts. ## Interfaces ### Provides - `main`: `powermeter` ### Requires - `modbus`: `serial_communication_hub` ## Transaction flow (OCMF) ### `start_transaction` High-level flow: 1. Read OCMF state register and ensure it is `NOT_READY` before starting. 2. Write OCMF transaction registers: - Identification status/level/flags/type - Identification data (ID) - Charging point identifier type + value (EVSE ID) - Tariff text (TT) as `tariff_text + "<=>" + transaction_id` - Written as **0-terminated** and only the **used** portion (no full padding). 3. Write session modality (charging vehicle). 4. Write the start command (`'B'`). ### `stop_transaction` High-level flow: 1. Write end command (`'E'`) if stopping the currently tracked transaction. 2. Wait for OCMF state `READY`. 3. Read OCMF file (size + content). 4. Confirm file read by writing `NOT_READY` to OCMF state. 5. Return OCMF report in `signed_meter_value` (with `public_key` attached). ## Signature validation (recommended) For troubleshooting and integration testing, this module ships a small signature validation tool under: `modules/HardwareDrivers/PowerMeters/CarloGavazzi_EM580/ocmf_validation/`. It can validate EM580 OCMF signatures (`ECDSA-brainpoolP384r1-SHA256`) against the public key read from the meter. See `ocmf_validation/README.md` for usage details. ## Troubleshooting ### No communication / timeouts Common causes and checks: - **Wrong unit id**: - Verify `powermeter_device_id` matches the meter’s Modbus address. - If you have multiple meters, ensure each has a unique id (1..247 are typical). - **Wrong serial settings**: - Ensure `SerialCommHub` settings match the meter configuration (baudrate, parity). - If you are unsure, start with conservative settings and increase once stable. - **RS-485 wiring / termination**: - Swap A/B if you see only timeouts. - Ensure termination is correct (only at bus ends). - Keep cables short / twisted pair; avoid star topologies where possible. - **Adapter / permissions**: - Confirm the serial device path exists and is stable (`/dev/ttyUSB0` can change between boots). - Ensure the EVerest process has permissions to open the device node. ### Repeated `CommunicationFault` raises The module raises a communication fault when Modbus operations fail and clears it once communication is restored. If you see frequent toggling: - Reduce bus load (increase `live_measurement_interval_ms`, especially with multiple meters) - Increase `communication_retry_count` modestly (and keep `communication_retry_delay_ms` ≥ 10ms) - Consider increasing `communication_error_pause_delay_s` to reduce retry storms on hard faults ### OCMF transaction does not complete / stuck waiting for READY - Check that the meter is in the expected OCMF state and is not holding a previous transaction open. - Ensure the transaction ids passed to `start_transaction` / `stop_transaction` match your intended flow. - Inspect logs around OCMF state transitions and file readout to see which step fails. ### Tariff text (TT) is truncated The EM580 TT field is limited to `CHAR[252]`. The driver logs a warning and truncates overlong strings. Shorten `tariff_text` and/or the appended data (e.g. transaction id formatting). ### Signature verification fails Use the validation tool in `ocmf_validation/` to validate the produced OCMF string against the published public key. If it fails: - Ensure public key and OCMF data come from the **same device** and **same transaction** - Ensure the OCMF data is not modified (OCMF signatures are over compact JSON) ## Notes / Limitations - **Write-multiple-registers limit**: the Modbus transport enforces the protocol limit by chunking into max 123 registers. - **Tariff text length**: TT is a `CHAR[252]` field (126 words). The driver logs a warning and truncates if needed. ## References - Module docs: `modules/HardwareDrivers/PowerMeters/CarloGavazzi_EM580/docs/index.rst` - OCMF spec: see [SAFE-eV OCMF specification](https://github.com/SAFE-eV/OCMF-Open-Charge-Metering-Format) ## Unit tests Unit tests live under `modules/HardwareDrivers/PowerMeters/CarloGavazzi_EM580/tests/` and include: - Helper-level tests (`helper.hpp`) - `powermeterImpl` behavior tests using a fake Modbus transport and small test hooks Build/run example (target name may vary by build system settings): ```bash ninja -C build everest-core_carlo_gavazzi_em580_helper_tests ./build/modules/HardwareDrivers/PowerMeters/CarloGavazzi_EM580/tests/everest-core_carlo_gavazzi_em580_helper_tests ```