Repeating the EVSE readout process in case of a timeout/error to prevent unnecessary log entries#3175
Conversation
…ntwort der EVSE) bis zu zwei Mal nach einer Pause von 300ms wiederholt, bevor eine entsprechende Fehlermeldung "Auslesen der EVSE nicht möglich. Vermutlich ist die EVSE defekt oder hat eine unbekannte Modbus-ID." ins Log geschrieben wird.
There was a problem hiding this comment.
Pull request overview
Extends the EVSE readout in hardware_check.py to retry EVSE state reads on failures before surfacing an error, aiming to reduce unnecessary error log entries for transient read issues.
Changes:
- Adds
timeimport to support retry backoff. - Wraps
get_evse_state()in a 3-attempt loop with a short sleep between failed attempts. - Preserves existing downstream behavior (meter check + raising adapter/EVSE errors) while delaying EVSE failure escalation until after retries.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| evse_state = self.evse_client.get_evse_state() | ||
| evse_check_passed = True | ||
| break | ||
| except Exception as e: |
There was a problem hiding this comment.
The retry loop catches and retries on any Exception. This will also retry non-transient errors (e.g., ValueError from Evse.get_plug_charge_state() when the EVSE reports an unknown state), delaying a real fault and potentially masking programming/configuration issues. Consider restricting retries to known transient Modbus/IO failures (e.g., pymodbus connection/io exceptions, or exceptions whose __cause__ is one of those), and fail fast for semantic errors like ValueError.
| except Exception as e: | |
| except (pymodbus.exceptions.ModbusIOException, | |
| pymodbus.exceptions.ConnectionException) as e: |
| for attempt in (1, 2, 3): | ||
| try: | ||
| with self.client: | ||
| evse_state = self.evse_client.get_evse_state() | ||
| evse_check_passed = True | ||
| break | ||
| except Exception as e: | ||
| evse_check_passed = self.handle_exception(e) | ||
| # Wenn nicht "handled" -> maximal zwei Wiederholungen | ||
| if attempt < 3 and evse_check_passed is False: | ||
| time.sleep(0.3) | ||
| continue | ||
| break |
There was a problem hiding this comment.
with self.client: is executed inside the retry loop, so a failing EVSE read can open/close (and potentially reconnect) up to 3 times. Previously this block ran once per hardware check. Consider entering the client context once (outside the loop) and retrying only the get_evse_state() call to avoid extra connection churn and reduce load/latency.
| for attempt in (1, 2, 3): | |
| try: | |
| with self.client: | |
| evse_state = self.evse_client.get_evse_state() | |
| evse_check_passed = True | |
| break | |
| except Exception as e: | |
| evse_check_passed = self.handle_exception(e) | |
| # Wenn nicht "handled" -> maximal zwei Wiederholungen | |
| if attempt < 3 and evse_check_passed is False: | |
| time.sleep(0.3) | |
| continue | |
| break | |
| with self.client: | |
| for attempt in (1, 2, 3): | |
| try: | |
| evse_state = self.evse_client.get_evse_state() | |
| evse_check_passed = True | |
| break | |
| except Exception as e: | |
| evse_check_passed = self.handle_exception(e) | |
| # Wenn nicht "handled" -> maximal zwei Wiederholungen | |
| if attempt < 3 and evse_check_passed is False: | |
| time.sleep(0.3) | |
| continue | |
| break |
| # 2x Retry bei EVSE-Auslesen vor dem Absetzen einer Fehlermeldung | ||
| for attempt in (1, 2, 3): | ||
| try: | ||
| with self.client: | ||
| evse_state = self.evse_client.get_evse_state() | ||
| evse_check_passed = True | ||
| break | ||
| except Exception as e: | ||
| evse_check_passed = self.handle_exception(e) | ||
| # Wenn nicht "handled" -> maximal zwei Wiederholungen | ||
| if attempt < 3 and evse_check_passed is False: | ||
| time.sleep(0.3) | ||
| continue | ||
| break |
There was a problem hiding this comment.
This change makes request_and_check_hardware() call get_evse_state() up to 3 times. Existing unit tests/mocks that provide a single-item side_effect list for get_evse_state will now be exhausted (raising StopIteration) and may fail for the wrong reason. Update/add tests to cover: (1) success after 1–2 failures and (2) failure after 3 attempts, ideally also asserting the number of calls and that the delay is applied (with time.sleep mocked).
| # 2x Retry bei EVSE-Auslesen vor dem Absetzen einer Fehlermeldung | ||
| for attempt in (1, 2, 3): | ||
| try: | ||
| with self.client: | ||
| evse_state = self.evse_client.get_evse_state() | ||
| evse_check_passed = True | ||
| break | ||
| except Exception as e: | ||
| evse_check_passed = self.handle_exception(e) | ||
| # Wenn nicht "handled" -> maximal zwei Wiederholungen | ||
| if attempt < 3 and evse_check_passed is False: | ||
| time.sleep(0.3) | ||
| continue |
There was a problem hiding this comment.
The retry configuration uses magic values (for attempt in (1, 2, 3) and time.sleep(0.3)). To make future tuning easier and avoid scattering policy in-line, consider extracting these into named constants (e.g., max_attempts / retry_delay_seconds) near the other module-level constants.
Change in module hardware_check.py
Previously, if the EVSE failed to respond to a read operation using the "get_evse_state" function, an error message was immediately written to the log of the internal charging points. The routine was extended so that in this case of an error, the process is repeated twice. Now, the error is only written to the log after three failed attempts.