@@ -11,10 +11,11 @@ def __init__(self, host: str, port: int) -> None:
1111 self .port = port
1212 self ._auto_reconnect = True
1313 self ._reconnect_delay = 5.0
14- self ._on_message : list [Callable [[str ], None ]] = []
15- self ._on_connected : list [Callable [[], None ]] = []
16- self ._on_disconnected : list [Callable [[], None ]] = []
17- self ._on_error : list [Callable [[Exception ], None ]] = []
14+ self ._on_message : list [Callable [[str ], None ]] = []
15+ self ._on_connected : list [Callable [[], None ]] = []
16+ self ._on_disconnected : list [Callable [[], None ]] = []
17+ self ._on_error : list [Callable [[Exception ], None ]] = []
18+ self ._on_device_status : list [Callable [[bool , str ], None ]] = []
1819 self ._queue : deque [str ] = deque ()
1920 self ._writer : asyncio .StreamWriter | None = None
2021 self ._task : asyncio .Task | None = None
@@ -38,6 +39,10 @@ def on_disconnected(self, cb: Callable[[], None]) -> None:
3839 def on_error (self , cb : Callable [[Exception ], None ]) -> None :
3940 self ._on_error .append (cb )
4041
42+ def on_device_status (self , cb : Callable [[bool , str ], None ]) -> None :
43+ """cb(is_connected: bool, endpoint: str) — fired when an upstream device connects/disconnects."""
44+ self ._on_device_status .append (cb )
45+
4146 # ── public API ─────────────────────────────────────────────────────────────
4247
4348 @property
@@ -125,6 +130,15 @@ def _handle_line(self, line: str) -> None:
125130 if self ._writer and not self ._writer .is_closing ():
126131 self ._writer .write (f"EDGELINK_PONG:{ hex_val } \n " .encode ())
127132 return
133+ if line .startswith ("EDGELINK_STATUS:" ):
134+ body = line [16 :]
135+ sep = body .find (":" )
136+ status = body [:sep ] if sep >= 0 else body
137+ endpoint = body [sep + 1 :] if sep >= 0 else ""
138+ connected = status .upper () == "CONNECTED"
139+ for cb in self ._on_device_status :
140+ cb (connected , endpoint )
141+ return
128142 if line .startswith ("EDGELINK_" ):
129143 return
130144
@@ -138,10 +152,11 @@ class EdgeLinkTcpListener:
138152
139153 def __init__ (self , local_port : int ) -> None :
140154 self .local_port = local_port
141- self ._on_message : list [Callable [[str ], None ]] = []
142- self ._on_connected : list [Callable [[], None ]] = []
143- self ._on_disconnected : list [Callable [[], None ]] = []
144- self ._on_error : list [Callable [[Exception ], None ]] = []
155+ self ._on_message : list [Callable [[str ], None ]] = []
156+ self ._on_connected : list [Callable [[], None ]] = []
157+ self ._on_disconnected : list [Callable [[], None ]] = []
158+ self ._on_error : list [Callable [[Exception ], None ]] = []
159+ self ._on_device_status : list [Callable [[bool , str ], None ]] = []
145160 self ._queue : deque [str ] = deque ()
146161 self ._server : asyncio .Server | None = None
147162 self .is_running = False
@@ -158,6 +173,10 @@ def on_disconnected(self, cb: Callable[[], None]) -> None:
158173 def on_error (self , cb : Callable [[Exception ], None ]) -> None :
159174 self ._on_error .append (cb )
160175
176+ def on_device_status (self , cb : Callable [[bool , str ], None ]) -> None :
177+ """cb(is_connected: bool, endpoint: str) — fired when an upstream device connects/disconnects."""
178+ self ._on_device_status .append (cb )
179+
161180 async def start (self ) -> None :
162181 self ._server = await asyncio .start_server (self ._handle_client , "0.0.0.0" , self .local_port )
163182 self .is_running = True
@@ -204,6 +223,15 @@ async def _handle_line(self, line: str, writer: asyncio.StreamWriter) -> None:
204223 except Exception :
205224 pass
206225 return
226+ if line .startswith ("EDGELINK_STATUS:" ):
227+ body = line [16 :]
228+ sep = body .find (":" )
229+ status = body [:sep ] if sep >= 0 else body
230+ endpoint = body [sep + 1 :] if sep >= 0 else ""
231+ connected = status .upper () == "CONNECTED"
232+ for cb in self ._on_device_status :
233+ cb (connected , endpoint )
234+ return
207235 if line .startswith ("EDGELINK_" ):
208236 return
209237
0 commit comments