pw_hdlc: add on_read_error callback to read_and_process_data

 - this allows to handle reconnection of serial ports
   automatically

Change-Id: I780933ebaa67fbf94f47b052fe708234ed30a05f
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/31542
Commit-Queue: Maksim Shmukler <shmukler@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
diff --git a/pw_hdlc/py/pw_hdlc/rpc.py b/pw_hdlc/py/pw_hdlc/rpc.py
index 0146c9f..557690a 100644
--- a/pw_hdlc/py/pw_hdlc/rpc.py
+++ b/pw_hdlc/py/pw_hdlc/rpc.py
@@ -66,6 +66,7 @@
 
 
 def read_and_process_data(read: Callable[[], bytes],
+                          on_read_error: Callable[[Exception], Any],
                           frame_handlers: FrameHandlers,
                           error_handler: Callable[[Frame],
                                                   Any] = _handle_error,
@@ -96,7 +97,12 @@
     # long time or crashes, this reading thread is not interrupted.
     with ThreadPoolExecutor(max_workers=handler_threads) as executor:
         while True:
-            data = read()
+            try:
+                data = read()
+            except Exception as exc:  # pylint: disable=broad-except
+                on_read_error(exc)
+                continue
+
             if data:
                 _LOG.debug('Read %2d B: %s', len(data), data)
 
@@ -148,7 +154,7 @@
         # Start background thread that reads and processes RPC packets.
         threading.Thread(target=read_and_process_data,
                          daemon=True,
-                         args=(read, frame_handlers)).start()
+                         args=(read, lambda: None, frame_handlers)).start()
 
     def rpcs(self, channel_id: int = None) -> Any:
         """Returns object for accessing services on the specified channel.