Fix intermittent reconnect failures and missing charge point ID extraction#752
Conversation
|
I like your thoughts generally! I just have couple of comments:
The idea of better connection error handling I think is great. However there is couple of issues with the implementation. First of all a bit of bad practice catching all exceptions. Of course one could argue that it makes sense for logging, but I think this should be more specific. Even if we improve on this points I think we might end up creating some limitations. This library is currently coupled with the Just my quick thoughts. |
- Remove bare `except Exception` in ChargePoint.start() so exceptions propagate to consumers, enabling custom reconnection logic and error-specific handling without coupling the library to websockets - Use `logging.warning` instead of `logging.info` for disconnections in examples, and log the exception details - Update tests to verify exceptions propagate rather than being silenced Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks for the thoughtful review, @a-alak! All great points. I've pushed a commit addressing each one: On On exception handling in
On logging levels — Updated the examples to use The examples still demonstrate All 181 tests pass. |
|
Great, I think the PR looks good now! |
|
Thank you for the thorough review and feedback, @a-alak! I really appreciate you taking the time to help improve the implementation. Your suggestions made the PR much cleaner and more focused. 🙏 |
|
Hi! Just checking in — this PR was approved by @a-alak on Feb 17. Is there anything else needed before it can be merged? Happy to address any additional feedback. Thanks! |
|
Thanks for the thorough review @a-alak! Glad the PR looks good now. Let me know if there's anything else needed for merging. |
|
I am not a maintainer, just a consumer (and have minimal contributions). I can't approve or merge the PR. |
mdwcrft
left a comment
There was a problem hiding this comment.
Small comments otherwise looks great!
- Use generic Exception instead of ConnectionError in tests, since websockets.exceptions.ConnectionClosed inherits from WebSocketException → Exception, not ConnectionError - Add extract_charge_point_id() and ConnectionClosed handling to examples/v21/central_system.py, matching v16 and v201 examples Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@rishabhvaish ready to approve once the formatting failure is fixed |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Reduce duplicated code across v16/v201/v21 examples by simplifying the charge point ID validation block (fixes >3% duplication threshold) - Fix implicit string concatenation warnings in error messages - Fix type hint: accept Optional[str] to match None input handling Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Rishabh Vaish <rishabhvaish.904@gmail.com>
Move the duplicated charge point ID validation, logging, and start logic from all 3 example files into a new `create_and_start_charge_point` helper in `ocpp/charge_point.py`. This reduces the examples to a single function call, bringing duplication well under SonarCloud's 3% threshold. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Rishabh Vaish <rishabhvaish.904@gmail.com>
a-alak
left a comment
There was a problem hiding this comment.
Sorry, I know this is merged now, but I have to push back. I think at least some parts need to be reverted. I missed this in my last review (or maybe it was added later?). But the create_and_start_charge_point function is broken. If you are implementing a CSMS that is only handling incoming messages, I guess it is fine. But if you need to send central system initiated messages, you will loose the ChargePoint instance and not be able to interact with it. I really think this function does not belong here. It will create unnecessary friction for new users, that will spend a lot of time figuring out how to use and realising it is unusable if they need to track the ChargePoint instances.
| LOGGER.info("Charge point %s connected", charge_point_id) | ||
| cp = charge_point_cls(charge_point_id, websocket) | ||
| await cp.start() | ||
| return cp |
There was a problem hiding this comment.
This is essentially dead code. cp.start() is an infinite loop, so it only returns on exceptions/closed connections and the cp instance will not have a valid connection (essentially unusable).
| Returns: | ||
| The ``ChargePoint`` instance, or ``None`` if the path was invalid. | ||
| """ | ||
| charge_point_id = extract_charge_point_id(websocket.request.path) |
There was a problem hiding this comment.
Increases coupling to the websockets library and specifically v >15 I think.
@a-alak do you have capacity to open a PR with fixes that I can review? |
Summary
extract_charge_point_id()utility for robust WebSocket path parsing — handles nested paths, query strings, empty/malformed pathsChargePoint.start()catch connection exceptions gracefully instead of crashing, with informative log outputConnectionClosedcleanlyProblem
When chargers intermittently disconnect and reconnect, two issues arise:
websocket.request.path.strip("/"), which breaks for nested paths like/ocpp/CP001(returnsocpp/CP001instead ofCP001) and silently produces empty strings for root pathsChargePoint.start()callsawait self._connection.recv()in a bare loop — if the WebSocket closes, the exception propagates unhandled, leaving no diagnostic informationChanges
ocpp/charge_point.pyextract_charge_point_id(path): Safely extracts the last path segment as the charge point ID, handling edge cases (empty paths, query strings, fragments, multiple slashes)ChargePoint.start()resilience: Wrapsrecv()in a try/except that logs the disconnection reason and exits the loop cleanlyexamples/v16/central_system.pyandexamples/v201/central_system.pyextract_charge_point_id()instead ofpath.strip("/")None)cp.start()in aConnectionClosedhandlertests/test_charge_point_connection.py(new)extract_charge_point_id()covering normal paths, nested paths, empty/None inputs, query strings, special charactersChargePoint.start()verifying clean exit on connection close, message processing before close, disconnection logging, and reconnection with new instancesTest plan