chore: baseline existing CI-V suite before RepeaterBook integration
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Web control panel server for the ICOM ID-5100.
|
||||
|
||||
Holds one CI-V serial connection (so don't run cli.py at the same time),
|
||||
serves the instrument-panel UI, and exposes a tiny JSON API on top of radio.py.
|
||||
|
||||
python3 server.py [--port 8550] [--serial /dev/ttyUSB0] [--baud 9600]
|
||||
|
||||
Then browse to http://<host>:8550/ (binds 0.0.0.0 -> reachable from phone).
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
from radio import Radio, CIVError
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
PANEL = os.path.join(HERE, "panel.html")
|
||||
|
||||
radio = None
|
||||
lock = threading.Lock()
|
||||
|
||||
|
||||
def read_status():
|
||||
with lock:
|
||||
freq = radio.get_frequency()
|
||||
mode, code = radio.get_mode()
|
||||
return {
|
||||
"ok": True,
|
||||
"freq_mhz": round(freq / 1e6, 5),
|
||||
"mode": mode,
|
||||
"mode_code": code,
|
||||
"volume": radio.get_volume(),
|
||||
"squelch_level": radio.get_squelch_level(),
|
||||
"squelch_open": radio.squelch_open(),
|
||||
"smeter": radio.get_smeter(),
|
||||
}
|
||||
|
||||
|
||||
def apply_control(path, body):
|
||||
with lock:
|
||||
if path == "/api/freq":
|
||||
radio.set_frequency(int(round(float(body["mhz"]) * 1e6)))
|
||||
elif path == "/api/mode":
|
||||
radio.set_mode(str(body["mode"]))
|
||||
elif path == "/api/vol":
|
||||
radio.set_volume(int(body["value"]))
|
||||
elif path == "/api/sql":
|
||||
radio.set_squelch_level(int(body["value"]))
|
||||
else:
|
||||
raise CIVError("unknown control")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, *a):
|
||||
pass # quiet
|
||||
|
||||
def _json(self, obj, code=200):
|
||||
payload = json.dumps(obj).encode()
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(payload)))
|
||||
self.end_headers()
|
||||
self.wfile.write(payload)
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == "/" or self.path == "/index.html":
|
||||
with open(PANEL, "rb") as f:
|
||||
body = f.read()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
elif self.path == "/api/status":
|
||||
try:
|
||||
self._json(read_status())
|
||||
except (CIVError, OSError) as e:
|
||||
self._json({"ok": False, "error": str(e)}, 200)
|
||||
else:
|
||||
self._json({"ok": False, "error": "not found"}, 404)
|
||||
|
||||
def do_POST(self):
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
try:
|
||||
body = json.loads(self.rfile.read(length) or b"{}")
|
||||
self._json(apply_control(self.path, body))
|
||||
except (CIVError, OSError, ValueError, KeyError) as e:
|
||||
self._json({"ok": False, "error": str(e)}, 200)
|
||||
|
||||
|
||||
def main():
|
||||
global radio
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--port", type=int, default=8550)
|
||||
ap.add_argument("--serial", default="/dev/ttyUSB0")
|
||||
ap.add_argument("--baud", type=int, default=9600)
|
||||
args = ap.parse_args()
|
||||
|
||||
radio = Radio(args.serial, args.baud).open()
|
||||
httpd = ThreadingHTTPServer(("0.0.0.0", args.port), Handler)
|
||||
print(f"ID-5100 panel on http://0.0.0.0:{args.port}/ "
|
||||
f"(serial {args.serial} @ {args.baud})")
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
radio.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user