Skip to content

Commit 7a7dd4f

Browse files
fix: stabilize gpspipe polling and timeout handling
1 parent 6e89a9a commit 7a7dd4f

1 file changed

Lines changed: 22 additions & 9 deletions

File tree

app.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ def save_config(config):
6868
log.info('Configuration saved; mode=%r host=%r', config.get('mode'), config.get('host') or 'local')
6969

7070
# --- Command Execution ---
71-
def run_commands_local(cmds):
71+
def run_commands_local(cmds, timeout_seconds=5):
7272
results =[]
7373
for cmd in cmds:
7474
try:
75-
proc = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=5)
75+
proc = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=timeout_seconds)
7676
if proc.returncode != 0:
7777
log.warning('Local command failed (rc=%s): %s :: %s', proc.returncode, cmd, proc.stdout.strip())
7878
results.append(f"Error: {proc.stdout.strip()}")
@@ -83,7 +83,7 @@ def run_commands_local(cmds):
8383
results.append("Error: An internal error occurred while executing a local command.")
8484
return results
8585

86-
def run_commands_remote(cmds, config):
86+
def run_commands_remote(cmds, config, timeout_seconds=5):
8787
ssh = paramiko.SSHClient()
8888
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
8989
results =[]
@@ -109,7 +109,7 @@ def run_commands_remote(cmds, config):
109109
ssh.connect(config.get('host'), username=config.get('user'), password=pwd, key_filename=key_filepath, timeout=10, banner_timeout=15, auth_timeout=15, look_for_keys=False)
110110

111111
for cmd in cmds:
112-
stdin, stdout, stderr = ssh.exec_command(cmd, timeout=5)
112+
stdin, stdout, stderr = ssh.exec_command(cmd, timeout=timeout_seconds)
113113
err_out = stderr.read().decode('utf-8').strip()
114114
std_out = stdout.read().decode('utf-8').strip()
115115

@@ -193,39 +193,52 @@ def get_ntp():
193193
@app.route('/api/gps')
194194
def get_gps():
195195
config = load_config()
196-
cmd = ["timeout 3 gpspipe -w -n 12"]
196+
# Keep sample collection long enough to gather TPV/SKY reliably on slower receivers.
197+
cmd = ["timeout 8 gpspipe -w -n 8"]
197198

198199
if config.get("mode") == "local":
199-
gps_out = run_commands_local(cmd)[0]
200+
gps_out = run_commands_local(cmd, timeout_seconds=10)[0]
200201
else:
201-
gps_out = run_commands_remote(cmd, config)[0]
202+
gps_out = run_commands_remote(cmd, config, timeout_seconds=10)[0]
202203

203204
satellites =[]
204205
gps_time = "Waiting for lock..."
205206
error = None
207+
parse_source = gps_out or ""
208+
if gps_out and gps_out.startswith('Error:'):
209+
# Non-zero exit (often timeout) may still include usable JSON output.
210+
parse_source = gps_out[len('Error:'):].lstrip()
211+
206212
if gps_out and (gps_out.startswith('Error:') or "command not found" in gps_out.lower()):
207213
error = gps_out
208214
if config.get("mode") == "local" and "gpspipe" in gps_out and "not found" in gps_out.lower():
209215
error = "Local GPS support is not installed in this image. Rebuild with INSTALL_GPSD_CLIENTS=true to enable gpspipe, or switch to Remote mode."
210216
gps_time = "Local GPS support not installed"
211217

212-
if gps_out and not error:
213-
for line in gps_out.strip().split('\n'):
218+
if parse_source:
219+
parsed_any = False
220+
for line in parse_source.strip().split('\n'):
214221
if not line:
215222
continue
216223
try:
217224
data = json.loads(line)
218225
if data.get("class") == "SKY":
219226
if "satellites" in data:
220227
satellites = data["satellites"]
228+
parsed_any = True
221229
elif data.get("class") == "TPV" and "time" in data:
222230
gps_time = data.get("time")
231+
parsed_any = True
223232
except json.JSONDecodeError as e:
224233
log.debug('GPS: could not parse line as JSON: %s', e)
225234
except Exception as e:
226235
log.exception('GPS: unexpected error parsing line: %s', e)
227236
error = "GPS parsing error occurred"
228237

238+
# If we recovered useful data from a timeout-wrapped command, do not surface an error.
239+
if parsed_any and error and isinstance(error, str) and error.startswith('Error:'):
240+
error = None
241+
229242
if error:
230243
log.warning('GPS API returned error in %s mode: %s', config.get('mode'), error)
231244
return jsonify({"satellites": satellites, "gps_time": gps_time, "error": error})

0 commit comments

Comments
 (0)