66from typing import Any
77
88from homeassistant .core import HomeAssistant
9+ from homeassistant .util import dt as dt_util
910
1011_LOGGER = logging .getLogger (__name__ )
1112
1213
14+ async def async_execute_at_command (
15+ client : Any , port : str , at_command : str , timeout : int = 2
16+ ) -> str :
17+ """Send an AT command to the serial port using microcom or a universal fallback."""
18+ # Construct a shell command that tries:
19+ # 1. microcom
20+ # 2. stty + echo + timeout/cat
21+ cmd = (
22+ f"if command -v microcom >/dev/null 2>&1; then "
23+ f'echo -e "{ at_command } \\ r" | microcom -t { timeout } 000 { port } 2>/dev/null; '
24+ f"elif command -v stty >/dev/null 2>&1; then "
25+ f"stty -F { port } 9600 -echo igncr icanon onlcr 2>/dev/null; "
26+ f'(sleep 0.2; echo -e "{ at_command } \\ r" > { port } ) & '
27+ f"timeout { timeout } cat { port } 2>/dev/null; "
28+ f"else "
29+ f'(sleep 0.2; echo -e "{ at_command } \\ r" > { port } ) & '
30+ f"timeout { timeout } cat { port } 2>/dev/null; "
31+ f"fi"
32+ )
33+ return await client .execute_command (cmd )
34+
35+
1336def parse_nmea_coordinate (coord_str : str ) -> float | None :
1437 """Parse NMEA coordinate string (ddmm.mmmmN/S or dddmm.mmmmE/W) into decimal degrees."""
1538 if not coord_str or len (coord_str ) < 3 :
@@ -39,7 +62,7 @@ def parse_qgpsloc_response(response: str) -> tuple[float, float] | None:
3962 for line in response .splitlines ():
4063 line = line .strip ()
4164 if "+QGPSLOC:" in line :
42- # Strip anything before "+QGPSLOC: " to handle echoes or prefixes
65+ # Strip anything before "+QGPSLOC:" to handle echoes or prefixes
4366 start = line .find ("+QGPSLOC:" )
4467 content = line [start + len ("+QGPSLOC:" ) :].strip ()
4568 parts = content .split ("," )
@@ -57,22 +80,19 @@ async def async_update_gps_location(
5780 hass : HomeAssistant ,
5881 client : Any ,
5982 port : str ,
60- ) -> bool :
61- """Query GPS coordinates from modem and update HA location."""
83+ ) -> tuple [ float , float , str ] | None :
84+ """Query GPS coordinates from modem, update HA location and return coordinates ."""
6285 try :
6386 # Check if GPS is enabled
64- check_cmd = f'echo -e "AT+QGPS?\\ r" | microcom -t 1000 { port } '
65- check_res = await client .execute_command (check_cmd )
87+ check_res = await async_execute_at_command (client , port , "AT+QGPS?" , timeout = 1 )
6688
6789 # If QGPS is disabled or query returned error, try enabling it
6890 if "+QGPS: 0" in check_res or "ERROR" in check_res :
6991 _LOGGER .debug ("GPS is disabled, enabling via AT+QGPS=1" )
70- enable_cmd = f'echo -e "AT+QGPS=1\\ r" | microcom -t 1000 { port } '
71- await client .execute_command (enable_cmd )
92+ await async_execute_at_command (client , port , "AT+QGPS=1" , timeout = 1 )
7293
7394 # Poll the GPS location
74- loc_cmd = f'echo -e "AT+QGPSLOC?\\ r" | microcom -t 2000 { port } '
75- loc_res = await client .execute_command (loc_cmd )
95+ loc_res = await async_execute_at_command (client , port , "AT+QGPSLOC?" , timeout = 2 )
7696
7797 coords = parse_qgpsloc_response (loc_res )
7898 if coords :
@@ -86,10 +106,11 @@ async def async_update_gps_location(
86106 "longitude" : lon ,
87107 },
88108 )
89- return True
109+ last_update = dt_util .now ().isoformat ()
110+ return lat , lon , last_update
90111 _LOGGER .debug (
91112 "Failed to get valid GPS fix or coordinates from %s: %s" , port , loc_res
92113 )
93114 except Exception as err :
94115 _LOGGER .warning ("Failed to update GPS location from Quectel modem: %s" , err )
95- return False
116+ return None
0 commit comments