mirror of
https://github.com/pe1hvh/meshcore-gui.git
synced 2026-10-31 01:42:04 +01:00
feat(repeater): poll repeater statistics on a configurable interval Configured repeaters are logged into, queried for their status and logged out again on a configurable interval. Raw values are archived with the moment of each poll, so battery voltage can be tracked over multiple days. New modules - services/repeater_config_store.py — per-device configuration in ~/.meshcore-gui/repeaters/<device>_repeaters.json (directory 0700, file 0600). Holds the full 32-byte public key, display name, login password, poll interval and enabled flag. A key that is not 64 hex characters is rejected: the meshcore library refuses a prefix for login, status and logout requests. An absent password means "not configured", an empty string is a valid credential. - services/repeater_stats_archive.py — append-only JSONL at ~/.meshcore-gui/archive/<device>_repeater_stats.jsonl, one record per poll, retention through the existing daily cleanup task. - services/repeater_poller.py — send_login_sync, req_status_sync, send_logout. The sync variants are used because plain send_login and send_statusreq return as soon as the frame is on the wire and say nothing about the outcome. - gui/panels/repeater_stats_panel.py — read-only REPEATERS panel showing the latest values, the age of the last successful poll and the last error per repeater. Changed - config.py: VERSION 1.23.0, REPEATERS_DIR, REPEATER_POLL_ENABLED, REPEATER_POLL_INTERVAL (900 s), REPEATER_POLL_CHECK_INTERVAL, REPEATER_LOGIN_TIMEOUT, REPEATER_STATUS_TIMEOUT, REPEATER_STATS_RETENTION_DAYS. - ble/worker.py: _main_loop runs the repeater poll alongside the existing periodic tasks, following the same interval-guarded pattern; _cleanup_old_data applies retention to the new archive. - __main__.py, gui/dashboard.py, gui/panels/__init__.py: wiring. Behaviour - At most one repeater is polled per check, each with its own start offset within the interval, so two repeaters are never queried back to back. Two repeaters on the default interval land ~7.5 minutes apart. - A failed login, an absent status response or an exception is recorded as a poll attempt with ok=false and a reason, and blocks neither the other repeater nor the application. Logout is always sent once a login has been attempted, including after a timeout — a login that returned no confirmation may still have succeeded on the far side. - Values are stored exactly as reported: no scaling, rounding or averaging. Battery voltage keeps the unit the firmware returns. - The password is reachable through a single method that only the poller calls. Objects handed to the GUI have no password field, and no password reaches the archive, the log or any error message. - Polling stays idle until a repeater is configured, so an installation without a configuration file behaves exactly as before. No change to the archive JSON schema, the public REST API or any existing public method signature. Worker and dashboard constructors gained optional keyword arguments only.
56 lines
1.7 KiB
Python
56 lines
1.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Direct port of the PHP fetchWeatherData() method.
|
|
|
|
Behaviour matches the original: same URL construction, same disabled TLS
|
|
verification, prints the URL and the decoded payload, and returns the raw
|
|
response body (or False on error). The API key is passed in as a parameter.
|
|
"""
|
|
|
|
import json
|
|
import ssl
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
|
|
def fetch_weather_data(locatie, datum, api_key):
|
|
"""Haal de weerdata op van de API.
|
|
|
|
:param locatie: location query (lat,lon or place name)
|
|
:param datum: date as YYYY-MM-DD
|
|
:param api_key: WeatherAPI key
|
|
:return: raw response body as str, or False on error
|
|
"""
|
|
url = f"https://api.weatherapi.com/v1/history.json?q={locatie}&dt={datum}&key={api_key}"
|
|
print(f"{url}")
|
|
|
|
# CURLOPT_SSL_VERIFYPEER = false / CURLOPT_SSL_VERIFYHOST = 0
|
|
context = ssl.create_default_context()
|
|
context.check_hostname = False
|
|
context.verify_mode = ssl.CERT_NONE
|
|
|
|
request = urllib.request.Request(url, headers={"accept": "application/json"})
|
|
|
|
try:
|
|
with urllib.request.urlopen(request, context=context) as handle:
|
|
response = handle.read().decode("utf-8")
|
|
except urllib.error.URLError as exc:
|
|
print(f"cURL Error: {exc}")
|
|
return False
|
|
|
|
# JSON netjes weergeven
|
|
try:
|
|
data = json.loads(response)
|
|
print(json.dumps(data, indent=4, ensure_ascii=False))
|
|
except json.JSONDecodeError as exc:
|
|
print(f"JSON Error: {exc}")
|
|
|
|
return response
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) != 4:
|
|
print(f"usage: {sys.argv[0]} <locatie> <datum> <api_key>")
|
|
sys.exit(1)
|
|
fetch_weather_data(sys.argv[1], sys.argv[2], sys.argv[3])
|