import requests import aprslib import datetime import sys import json # --- CONFIGURATION --- API_URL = "http://birb.haldo.org/api/v2/detections?limit=100" CALLSIGN = "KI7OKT-10" PASSCODE = "REDACTED" COORDS = "4406.47N\\12308.14W&" CONF_THRESHOLD = 0.50 def log(msg): print(f"[{datetime.datetime.now()}] DEBUG: {msg}") def get_top_bird(): try: log(f"Requesting BirdNET detections from: {API_URL}") response = requests.get(API_URL, timeout=10) log(f"HTTP Status: {response.status_code}") response.raise_for_status() json_data = response.json() # Debug: Print the keys found in the JSON root log(f"JSON Keys found: {list(json_data.keys())}") # Try both 'detections' and 'data' keys based on different v2 versions detections = json_data.get('detections') or json_data.get('data', []) log(f"Total detections found in response: {len(detections)}") now_utc = datetime.datetime.now(datetime.timezone.utc) cutoff = now_utc - datetime.timedelta(minutes=21) log(f"Current UTC: {now_utc.isoformat()} | Cutoff: {cutoff.isoformat()}") recent_birds = {} processed_count = 0 for d in detections: ts_str = d.get('beginTime') conf = d.get('confidence', 0) name = d.get('commonName', 'Unknown') processed_count += 1 if not ts_str: continue # Parse time and handle timezone awareness dt = datetime.datetime.fromisoformat(ts_str.replace('Z', '+00:00')) dt_utc = dt.astimezone(datetime.timezone.utc) # Debug logs for first 3 items to check if time logic is hitting if processed_count <= 3: log(f"Checking: {name} | Conf: {conf} | Time: {dt_utc.isoformat()}") if conf >= CONF_THRESHOLD and dt_utc > cutoff: if name not in ["Human", "Siren", "Noise", "Dog", "Engine"]: recent_birds[name] = recent_birds.get(name, 0) + 1 log(f"Detections matching criteria (Conf > {CONF_THRESHOLD} & Recent): {len(recent_birds)}") if recent_birds: top_bird = max(recent_birds, key=recent_birds.get) res = f"Top bird: {top_bird} ({recent_birds[top_bird]} hits)" log(f"Result: {res}") return res return "Listening for birds..." except Exception as e: log(f"CRITICAL ERROR in get_top_bird: {str(e)}") # Print a snippet of the raw response if possible to see if it's HTML or wrong JSON if 'response' in locals(): log(f"Raw Response Snippet: {response.text[:200]}") return "BirdNET Monitoring Active (Error)" def send_packet(message_type): try: log(f"Connecting to APRS-IS: noam.aprs2.net:14580") ais = aprslib.IS(CALLSIGN, passwd=PASSCODE, host="noam.aprs2.net", port=14580) ais.connect() log("APRS-IS Connected successfully.") if message_type == "bird": comment = get_top_bird() packet = f"{CALLSIGN}>APRS,TCPIP*:!{COORDS} {comment}" else: packet = f"{CALLSIGN}>APRS,TCPIP*:!{COORDS}RX-Only KI7OKT i-Gate + BirdNET v2" log(f"Prepared Packet: {packet}") ais.sendall(packet) log("Packet sent.") except Exception as e: log(f"APRS ERROR: {e}") if __name__ == "__main__": mode = sys.argv[1] if len(sys.argv) > 1 else "bird" send_packet(mode)