mirror of
https://github.com/smittix/intercept.git
synced 2026-07-30 11:38:09 -07:00
style: apply ruff-format to entire codebase
First-time run of ruff-format via pre-commit hook normalises quote style, trailing commas, and whitespace across 188 Python files. No logic changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+143
-141
@@ -34,9 +34,9 @@ from utils.sdr import SDRFactory, SDRType
|
||||
from utils.sse import sse_stream_fanout
|
||||
from utils.validation import validate_device_index, validate_gain
|
||||
|
||||
logger = get_logger('intercept.ais')
|
||||
logger = get_logger("intercept.ais")
|
||||
|
||||
ais_bp = Blueprint('ais', __name__, url_prefix='/ais')
|
||||
ais_bp = Blueprint("ais", __name__, url_prefix="/ais")
|
||||
|
||||
# Track AIS state
|
||||
ais_running = False
|
||||
@@ -49,17 +49,17 @@ _ais_error_logged = True
|
||||
|
||||
# Common installation paths for AIS-catcher
|
||||
AIS_CATCHER_PATHS = [
|
||||
'/usr/local/bin/AIS-catcher',
|
||||
'/usr/bin/AIS-catcher',
|
||||
'/opt/homebrew/bin/AIS-catcher',
|
||||
'/opt/homebrew/bin/aiscatcher',
|
||||
"/usr/local/bin/AIS-catcher",
|
||||
"/usr/bin/AIS-catcher",
|
||||
"/opt/homebrew/bin/AIS-catcher",
|
||||
"/opt/homebrew/bin/aiscatcher",
|
||||
]
|
||||
|
||||
|
||||
def find_ais_catcher():
|
||||
"""Find AIS-catcher binary, checking PATH and common locations."""
|
||||
# First try PATH
|
||||
for name in ['AIS-catcher', 'aiscatcher']:
|
||||
for name in ["AIS-catcher", "aiscatcher"]:
|
||||
path = shutil.which(name)
|
||||
if path:
|
||||
return path
|
||||
@@ -84,7 +84,7 @@ def parse_ais_stream(port: int):
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(AIS_SOCKET_TIMEOUT)
|
||||
sock.connect(('localhost', port))
|
||||
sock.connect(("localhost", port))
|
||||
ais_connected = True
|
||||
_ais_error_logged = True
|
||||
logger.info("Connected to AIS-catcher TCP server")
|
||||
@@ -95,14 +95,14 @@ def parse_ais_stream(port: int):
|
||||
|
||||
while ais_running:
|
||||
try:
|
||||
data = sock.recv(SOCKET_BUFFER_SIZE).decode('utf-8', errors='ignore')
|
||||
data = sock.recv(SOCKET_BUFFER_SIZE).decode("utf-8", errors="ignore")
|
||||
if not data:
|
||||
logger.warning("AIS connection closed (no data)")
|
||||
break
|
||||
buffer += data
|
||||
|
||||
while '\n' in buffer:
|
||||
line, buffer = buffer.split('\n', 1)
|
||||
while "\n" in buffer:
|
||||
line, buffer = buffer.split("\n", 1)
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
@@ -111,7 +111,7 @@ def parse_ais_stream(port: int):
|
||||
msg = json.loads(line)
|
||||
vessel = process_ais_message(msg)
|
||||
if vessel:
|
||||
mmsi = vessel.get('mmsi')
|
||||
mmsi = vessel.get("mmsi")
|
||||
if mmsi:
|
||||
app_module.ais_vessels.set(mmsi, vessel)
|
||||
pending_updates.add(mmsi)
|
||||
@@ -128,21 +128,25 @@ def parse_ais_stream(port: int):
|
||||
if mmsi in app_module.ais_vessels:
|
||||
_vessel_snap = app_module.ais_vessels[mmsi]
|
||||
with contextlib.suppress(queue.Full):
|
||||
app_module.ais_queue.put_nowait({
|
||||
'type': 'vessel',
|
||||
**_vessel_snap
|
||||
})
|
||||
app_module.ais_queue.put_nowait({"type": "vessel", **_vessel_snap})
|
||||
# Geofence check
|
||||
_v_lat = _vessel_snap.get('lat')
|
||||
_v_lon = _vessel_snap.get('lon')
|
||||
_v_lat = _vessel_snap.get("lat")
|
||||
_v_lon = _vessel_snap.get("lon")
|
||||
if _v_lat and _v_lon:
|
||||
try:
|
||||
from utils.geofence import get_geofence_manager
|
||||
|
||||
for _gf_evt in get_geofence_manager().check_position(
|
||||
mmsi, 'vessel', _v_lat, _v_lon,
|
||||
{'name': _vessel_snap.get('name'), 'ship_type': _vessel_snap.get('ship_type_text')}
|
||||
mmsi,
|
||||
"vessel",
|
||||
_v_lat,
|
||||
_v_lon,
|
||||
{
|
||||
"name": _vessel_snap.get("name"),
|
||||
"ship_type": _vessel_snap.get("ship_type_text"),
|
||||
},
|
||||
):
|
||||
process_event('ais', _gf_evt, 'geofence')
|
||||
process_event("ais", _gf_evt, "geofence")
|
||||
except Exception:
|
||||
pass
|
||||
pending_updates.clear()
|
||||
@@ -172,139 +176,138 @@ def process_ais_message(msg: dict) -> dict | None:
|
||||
# AIS-catcher outputs different message types
|
||||
# We're interested in position reports and static data
|
||||
|
||||
mmsi = msg.get('mmsi')
|
||||
mmsi = msg.get("mmsi")
|
||||
if not mmsi:
|
||||
return None
|
||||
|
||||
mmsi = str(mmsi)
|
||||
|
||||
# Get existing vessel data or create new
|
||||
vessel = app_module.ais_vessels.get(mmsi) or {'mmsi': mmsi}
|
||||
vessel = app_module.ais_vessels.get(mmsi) or {"mmsi": mmsi}
|
||||
|
||||
# Extract common fields
|
||||
# AIS-catcher JSON_FULL uses 'longitude'/'latitude', but some versions use 'lon'/'lat'
|
||||
lat_val = msg.get('latitude') or msg.get('lat')
|
||||
lon_val = msg.get('longitude') or msg.get('lon')
|
||||
lat_val = msg.get("latitude") or msg.get("lat")
|
||||
lon_val = msg.get("longitude") or msg.get("lon")
|
||||
if lat_val is not None and lon_val is not None:
|
||||
try:
|
||||
lat = float(lat_val)
|
||||
lon = float(lon_val)
|
||||
# Validate coordinates (AIS uses 181 for unavailable)
|
||||
if -90 <= lat <= 90 and -180 <= lon <= 180:
|
||||
vessel['lat'] = lat
|
||||
vessel['lon'] = lon
|
||||
vessel["lat"] = lat
|
||||
vessel["lon"] = lon
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Speed over ground (knots)
|
||||
if 'speed' in msg:
|
||||
if "speed" in msg:
|
||||
try:
|
||||
speed = float(msg['speed'])
|
||||
speed = float(msg["speed"])
|
||||
if speed < 102.3: # 102.3 = not available
|
||||
vessel['speed'] = round(speed, 1)
|
||||
vessel["speed"] = round(speed, 1)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Course over ground (degrees)
|
||||
if 'course' in msg:
|
||||
if "course" in msg:
|
||||
try:
|
||||
course = float(msg['course'])
|
||||
course = float(msg["course"])
|
||||
if course < 360: # 360 = not available
|
||||
vessel['course'] = round(course, 1)
|
||||
vessel["course"] = round(course, 1)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# True heading (degrees)
|
||||
if 'heading' in msg:
|
||||
if "heading" in msg:
|
||||
try:
|
||||
heading = int(msg['heading'])
|
||||
heading = int(msg["heading"])
|
||||
if heading < 511: # 511 = not available
|
||||
vessel['heading'] = heading
|
||||
vessel["heading"] = heading
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Navigation status
|
||||
if 'status' in msg:
|
||||
vessel['nav_status'] = msg['status']
|
||||
if 'status_text' in msg:
|
||||
vessel['nav_status_text'] = msg['status_text']
|
||||
if "status" in msg:
|
||||
vessel["nav_status"] = msg["status"]
|
||||
if "status_text" in msg:
|
||||
vessel["nav_status_text"] = msg["status_text"]
|
||||
|
||||
# Vessel name (from Type 5 or Type 24 messages)
|
||||
if 'shipname' in msg:
|
||||
name = msg['shipname'].strip().strip('@')
|
||||
if "shipname" in msg:
|
||||
name = msg["shipname"].strip().strip("@")
|
||||
if name:
|
||||
vessel['name'] = name
|
||||
vessel["name"] = name
|
||||
|
||||
# Callsign
|
||||
if 'callsign' in msg:
|
||||
callsign = msg['callsign'].strip().strip('@')
|
||||
if "callsign" in msg:
|
||||
callsign = msg["callsign"].strip().strip("@")
|
||||
if callsign:
|
||||
vessel['callsign'] = callsign
|
||||
vessel["callsign"] = callsign
|
||||
|
||||
# Ship type
|
||||
if 'shiptype' in msg:
|
||||
vessel['ship_type'] = msg['shiptype']
|
||||
if 'shiptype_text' in msg:
|
||||
vessel['ship_type_text'] = msg['shiptype_text']
|
||||
if "shiptype" in msg:
|
||||
vessel["ship_type"] = msg["shiptype"]
|
||||
if "shiptype_text" in msg:
|
||||
vessel["ship_type_text"] = msg["shiptype_text"]
|
||||
|
||||
# Destination
|
||||
if 'destination' in msg:
|
||||
dest = msg['destination'].strip().strip('@')
|
||||
if "destination" in msg:
|
||||
dest = msg["destination"].strip().strip("@")
|
||||
if dest:
|
||||
vessel['destination'] = dest
|
||||
vessel["destination"] = dest
|
||||
|
||||
# ETA
|
||||
if 'eta' in msg:
|
||||
vessel['eta'] = msg['eta']
|
||||
if "eta" in msg:
|
||||
vessel["eta"] = msg["eta"]
|
||||
|
||||
# Dimensions
|
||||
if 'to_bow' in msg and 'to_stern' in msg:
|
||||
if "to_bow" in msg and "to_stern" in msg:
|
||||
try:
|
||||
length = int(msg['to_bow']) + int(msg['to_stern'])
|
||||
length = int(msg["to_bow"]) + int(msg["to_stern"])
|
||||
if length > 0:
|
||||
vessel['length'] = length
|
||||
vessel["length"] = length
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if 'to_port' in msg and 'to_starboard' in msg:
|
||||
if "to_port" in msg and "to_starboard" in msg:
|
||||
try:
|
||||
width = int(msg['to_port']) + int(msg['to_starboard'])
|
||||
width = int(msg["to_port"]) + int(msg["to_starboard"])
|
||||
if width > 0:
|
||||
vessel['width'] = width
|
||||
vessel["width"] = width
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Draught
|
||||
if 'draught' in msg:
|
||||
if "draught" in msg:
|
||||
try:
|
||||
draught = float(msg['draught'])
|
||||
draught = float(msg["draught"])
|
||||
if draught > 0:
|
||||
vessel['draught'] = draught
|
||||
vessel["draught"] = draught
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Rate of turn
|
||||
if 'turn' in msg:
|
||||
if "turn" in msg:
|
||||
try:
|
||||
turn = float(msg['turn'])
|
||||
turn = float(msg["turn"])
|
||||
if -127 <= turn <= 127: # Valid range
|
||||
vessel['rate_of_turn'] = turn
|
||||
vessel["rate_of_turn"] = turn
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Message type for debugging
|
||||
if 'type' in msg:
|
||||
vessel['last_msg_type'] = msg['type']
|
||||
if "type" in msg:
|
||||
vessel["last_msg_type"] = msg["type"]
|
||||
|
||||
# Timestamp
|
||||
vessel['last_seen'] = time.time()
|
||||
vessel["last_seen"] = time.time()
|
||||
|
||||
# Check for DSC DISTRESS matching this MMSI
|
||||
try:
|
||||
for _dsc_key, _dsc_msg in app_module.dsc_messages.items():
|
||||
if (str(_dsc_msg.get('source_mmsi', '')) == mmsi
|
||||
and _dsc_msg.get('category', '').upper() == 'DISTRESS'):
|
||||
vessel['dsc_distress'] = True
|
||||
if str(_dsc_msg.get("source_mmsi", "")) == mmsi and _dsc_msg.get("category", "").upper() == "DISTRESS":
|
||||
vessel["dsc_distress"] = True
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
@@ -312,7 +315,7 @@ def process_ais_message(msg: dict) -> dict | None:
|
||||
return vessel
|
||||
|
||||
|
||||
@ais_bp.route('/tools')
|
||||
@ais_bp.route("/tools")
|
||||
def check_ais_tools():
|
||||
"""Check for AIS decoding tools and hardware."""
|
||||
has_ais_catcher = find_ais_catcher() is not None
|
||||
@@ -321,60 +324,64 @@ def check_ais_tools():
|
||||
devices = SDRFactory.detect_devices()
|
||||
has_rtlsdr = any(d.sdr_type == SDRType.RTL_SDR for d in devices)
|
||||
|
||||
return jsonify({
|
||||
'ais_catcher': has_ais_catcher,
|
||||
'ais_catcher_path': find_ais_catcher(),
|
||||
'has_rtlsdr': has_rtlsdr,
|
||||
'device_count': len(devices)
|
||||
})
|
||||
return jsonify(
|
||||
{
|
||||
"ais_catcher": has_ais_catcher,
|
||||
"ais_catcher_path": find_ais_catcher(),
|
||||
"has_rtlsdr": has_rtlsdr,
|
||||
"device_count": len(devices),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ais_bp.route('/status')
|
||||
@ais_bp.route("/status")
|
||||
def ais_status():
|
||||
"""Get AIS tracking status for debugging."""
|
||||
process_running = False
|
||||
if app_module.ais_process:
|
||||
process_running = app_module.ais_process.poll() is None
|
||||
|
||||
return jsonify({
|
||||
'tracking_active': ais_running,
|
||||
'active_device': ais_active_device,
|
||||
'connected': ais_connected,
|
||||
'messages_received': ais_messages_received,
|
||||
'last_message_time': ais_last_message_time,
|
||||
'vessel_count': len(app_module.ais_vessels),
|
||||
'vessels': dict(app_module.ais_vessels),
|
||||
'queue_size': app_module.ais_queue.qsize(),
|
||||
'ais_catcher_path': find_ais_catcher(),
|
||||
'process_running': process_running
|
||||
})
|
||||
return jsonify(
|
||||
{
|
||||
"tracking_active": ais_running,
|
||||
"active_device": ais_active_device,
|
||||
"connected": ais_connected,
|
||||
"messages_received": ais_messages_received,
|
||||
"last_message_time": ais_last_message_time,
|
||||
"vessel_count": len(app_module.ais_vessels),
|
||||
"vessels": dict(app_module.ais_vessels),
|
||||
"queue_size": app_module.ais_queue.qsize(),
|
||||
"ais_catcher_path": find_ais_catcher(),
|
||||
"process_running": process_running,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ais_bp.route('/start', methods=['POST'])
|
||||
@ais_bp.route("/start", methods=["POST"])
|
||||
def start_ais():
|
||||
"""Start AIS tracking."""
|
||||
global ais_running, ais_active_device, ais_active_sdr_type
|
||||
|
||||
with app_module.ais_lock:
|
||||
if ais_running:
|
||||
return api_error('AIS tracking already active', 409)
|
||||
return api_error("AIS tracking already active", 409)
|
||||
|
||||
data = request.json or {}
|
||||
|
||||
# Validate inputs
|
||||
try:
|
||||
gain = int(validate_gain(data.get('gain', '40')))
|
||||
device = validate_device_index(data.get('device', '0'))
|
||||
gain = int(validate_gain(data.get("gain", "40")))
|
||||
device = validate_device_index(data.get("device", "0"))
|
||||
except ValueError as e:
|
||||
return api_error(str(e), 400)
|
||||
|
||||
# Find AIS-catcher
|
||||
ais_catcher_path = find_ais_catcher()
|
||||
if not ais_catcher_path:
|
||||
return api_error('AIS-catcher not found. Install from https://github.com/jvde-github/AIS-catcher/releases', 400)
|
||||
return api_error("AIS-catcher not found. Install from https://github.com/jvde-github/AIS-catcher/releases", 400)
|
||||
|
||||
# Get SDR type from request
|
||||
sdr_type_str = data.get('sdr_type', 'rtlsdr')
|
||||
sdr_type_str = data.get("sdr_type", "rtlsdr")
|
||||
try:
|
||||
sdr_type = SDRType(sdr_type_str)
|
||||
except ValueError:
|
||||
@@ -397,27 +404,27 @@ def start_ais():
|
||||
|
||||
# Check if device is available
|
||||
device_int = int(device)
|
||||
error = app_module.claim_sdr_device(device_int, 'ais', sdr_type_str)
|
||||
error = app_module.claim_sdr_device(device_int, "ais", sdr_type_str)
|
||||
if error:
|
||||
return api_error(error, 409, error_type='DEVICE_BUSY')
|
||||
return api_error(error, 409, error_type="DEVICE_BUSY")
|
||||
|
||||
# Build command using SDR abstraction
|
||||
sdr_device = SDRFactory.create_default_device(sdr_type, index=device)
|
||||
builder = SDRFactory.get_builder(sdr_type)
|
||||
|
||||
bias_t = data.get('bias_t', False)
|
||||
bias_t = data.get("bias_t", False)
|
||||
tcp_port = AIS_TCP_PORT
|
||||
|
||||
# Optional UDP NMEA forwarding (e.g. for OpenCPN on port 10110)
|
||||
udp_host = data.get('udp_host') or None
|
||||
udp_host = data.get("udp_host") or None
|
||||
udp_port = None
|
||||
if udp_host:
|
||||
try:
|
||||
udp_port = int(data.get('udp_port', 10110))
|
||||
udp_port = int(data.get("udp_port", 10110))
|
||||
if not 1 <= udp_port <= 65535:
|
||||
raise ValueError
|
||||
except (TypeError, ValueError):
|
||||
return api_error('Invalid udp_port (1-65535)', 400)
|
||||
return api_error("Invalid udp_port (1-65535)", 400)
|
||||
|
||||
cmd = builder.build_ais_command(
|
||||
device=sdr_device,
|
||||
@@ -434,10 +441,7 @@ def start_ais():
|
||||
try:
|
||||
logger.info(f"Starting AIS-catcher with device {device}: {' '.join(cmd)}")
|
||||
app_module.ais_process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
start_new_session=True
|
||||
cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, start_new_session=True
|
||||
)
|
||||
|
||||
# Wait for process to start
|
||||
@@ -446,15 +450,15 @@ def start_ais():
|
||||
if app_module.ais_process.poll() is not None:
|
||||
# Release device on failure
|
||||
app_module.release_sdr_device(device_int, sdr_type_str)
|
||||
stderr_output = ''
|
||||
stderr_output = ""
|
||||
if app_module.ais_process.stderr:
|
||||
with contextlib.suppress(Exception):
|
||||
stderr_output = app_module.ais_process.stderr.read().decode('utf-8', errors='ignore').strip()
|
||||
stderr_output = app_module.ais_process.stderr.read().decode("utf-8", errors="ignore").strip()
|
||||
if stderr_output:
|
||||
logger.error(f"AIS-catcher stderr:\n{stderr_output}")
|
||||
error_msg = 'AIS-catcher failed to start. Check SDR device connection.'
|
||||
error_msg = "AIS-catcher failed to start. Check SDR device connection."
|
||||
if stderr_output:
|
||||
error_msg += f' Error: {stderr_output[:500]}'
|
||||
error_msg += f" Error: {stderr_output[:500]}"
|
||||
return api_error(error_msg, 500)
|
||||
|
||||
ais_running = True
|
||||
@@ -465,12 +469,7 @@ def start_ais():
|
||||
thread = threading.Thread(target=parse_ais_stream, args=(tcp_port,), daemon=True)
|
||||
thread.start()
|
||||
|
||||
return jsonify({
|
||||
'status': 'started',
|
||||
'message': 'AIS tracking started',
|
||||
'device': device,
|
||||
'port': tcp_port
|
||||
})
|
||||
return jsonify({"status": "started", "message": "AIS tracking started", "device": device, "port": tcp_port})
|
||||
except Exception as e:
|
||||
# Release device on failure
|
||||
app_module.release_sdr_device(device_int, sdr_type_str)
|
||||
@@ -478,7 +477,7 @@ def start_ais():
|
||||
return api_error(str(e), 500)
|
||||
|
||||
|
||||
@ais_bp.route('/stop', methods=['POST'])
|
||||
@ais_bp.route("/stop", methods=["POST"])
|
||||
def stop_ais():
|
||||
"""Stop AIS tracking."""
|
||||
global ais_running, ais_active_device, ais_active_sdr_type
|
||||
@@ -500,55 +499,56 @@ def stop_ais():
|
||||
|
||||
# Release device from registry
|
||||
if ais_active_device is not None:
|
||||
app_module.release_sdr_device(ais_active_device, ais_active_sdr_type or 'rtlsdr')
|
||||
app_module.release_sdr_device(ais_active_device, ais_active_sdr_type or "rtlsdr")
|
||||
|
||||
ais_running = False
|
||||
ais_active_device = None
|
||||
ais_active_sdr_type = None
|
||||
|
||||
app_module.ais_vessels.clear()
|
||||
return jsonify({'status': 'stopped'})
|
||||
return jsonify({"status": "stopped"})
|
||||
|
||||
|
||||
@ais_bp.route('/stream')
|
||||
@ais_bp.route("/stream")
|
||||
def stream_ais():
|
||||
"""SSE stream for AIS vessels."""
|
||||
|
||||
def _on_msg(msg: dict[str, Any]) -> None:
|
||||
process_event('ais', msg, msg.get('type'))
|
||||
process_event("ais", msg, msg.get("type"))
|
||||
|
||||
response = Response(
|
||||
sse_stream_fanout(
|
||||
source_queue=app_module.ais_queue,
|
||||
channel_key='ais',
|
||||
channel_key="ais",
|
||||
timeout=SSE_QUEUE_TIMEOUT,
|
||||
keepalive_interval=SSE_KEEPALIVE_INTERVAL,
|
||||
on_message=_on_msg,
|
||||
),
|
||||
mimetype='text/event-stream',
|
||||
mimetype="text/event-stream",
|
||||
)
|
||||
response.headers['Cache-Control'] = 'no-cache'
|
||||
response.headers['X-Accel-Buffering'] = 'no'
|
||||
response.headers["Cache-Control"] = "no-cache"
|
||||
response.headers["X-Accel-Buffering"] = "no"
|
||||
return response
|
||||
|
||||
|
||||
@ais_bp.route('/vessel/<mmsi>/dsc')
|
||||
@ais_bp.route("/vessel/<mmsi>/dsc")
|
||||
def get_vessel_dsc(mmsi: str):
|
||||
"""Get DSC messages associated with a vessel MMSI."""
|
||||
if not mmsi or not mmsi.isdigit():
|
||||
return api_error('Invalid MMSI', 400)
|
||||
return api_error("Invalid MMSI", 400)
|
||||
|
||||
matches = []
|
||||
try:
|
||||
for _key, msg in app_module.dsc_messages.items():
|
||||
if str(msg.get('source_mmsi', '')) == mmsi:
|
||||
if str(msg.get("source_mmsi", "")) == mmsi:
|
||||
matches.append(dict(msg))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return api_success(data={'mmsi': mmsi, 'dsc_messages': matches})
|
||||
return api_success(data={"mmsi": mmsi, "dsc_messages": matches})
|
||||
|
||||
|
||||
@ais_bp.route('/vessels')
|
||||
@ais_bp.route("/vessels")
|
||||
def ais_vessels():
|
||||
"""Export current AIS vessel data as JSON.
|
||||
|
||||
@@ -563,22 +563,24 @@ def ais_vessels():
|
||||
"""
|
||||
vessels = dict(app_module.ais_vessels)
|
||||
|
||||
mmsi_filter = request.args.get('mmsi')
|
||||
mmsi_filter = request.args.get("mmsi")
|
||||
if mmsi_filter:
|
||||
vessels = {k: v for k, v in vessels.items() if str(k) == str(mmsi_filter)}
|
||||
|
||||
return jsonify({
|
||||
'count': len(vessels),
|
||||
'vessels': list(vessels.values()),
|
||||
})
|
||||
return jsonify(
|
||||
{
|
||||
"count": len(vessels),
|
||||
"vessels": list(vessels.values()),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ais_bp.route('/dashboard')
|
||||
@ais_bp.route("/dashboard")
|
||||
def ais_dashboard():
|
||||
"""Popout AIS dashboard."""
|
||||
embedded = request.args.get('embedded', 'false') == 'true'
|
||||
embedded = request.args.get("embedded", "false") == "true"
|
||||
return render_template(
|
||||
'ais_dashboard.html',
|
||||
"ais_dashboard.html",
|
||||
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
|
||||
default_latitude=DEFAULT_LATITUDE,
|
||||
default_longitude=DEFAULT_LONGITUDE,
|
||||
|
||||
Reference in New Issue
Block a user