from modules.app_events import socketio from threading import Thread, Lock import serial import time # SIM868 GNSS/GSM/GPRS HAT is wired to the Pi's GPIO UART (jumper set to "B: Pi-SIM868"), # exposed as /dev/serial0 once the Pi's onboard Bluetooth has been disabled/freed. SERIAL_PORT = "/dev/serial0" BAUDRATE = 115200 POLL_INTERVAL = 2 # seconds between AT+CGNSINF polls LocationX = None LocationY = None _serial_lock = Lock() _serial = None _gps_thread = None class GPS: def __init__(self): self.LocationX = None self.LocationY = None def _get_serial(): global _serial if _serial is None or not _serial.is_open: _serial = serial.Serial(SERIAL_PORT, BAUDRATE, timeout=1) time.sleep(0.3) _serial.reset_input_buffer() return _serial def _send_at(cmd, wait=0.5): """Send an AT command to the SIM868 module and return the raw response.""" ser = _get_serial() ser.write((cmd + "\r\n").encode()) time.sleep(wait) return ser.read(1000).decode(errors="replace") def _ensure_gnss_powered(): resp = _send_at("AT+CGNSPWR?") if "+CGNSPWR: 0" in resp: print("GNSS was off, powering it on...") _send_at("AT+CGNSPWR=1", wait=1) def _parse_cgnsinf(resp): """Parse a +CGNSINF response line into a dict, or None if there's no fix yet. Format: +CGNSINF: run,fix,utc,lat,lon,alt,speed,course,fixmode,reserved1, hdop,pdop,vdop,reserved2,gps_sat_view,gnss_sat_used,gnss_sat_view,cn0,hpa,vpa """ for line in resp.splitlines(): line = line.strip() if line.startswith("+CGNSINF:"): fields = line.split(":", 1)[1].strip().split(",") if len(fields) < 8 or fields[1] != "1": return None lat, lon = fields[3], fields[4] if not lat or not lon: return None return { "lat": lat, "lon": lon, "alt": fields[5], "speed": fields[6], "course": fields[7], } return None def _poll_loop(): global LocationX, LocationY try: with _serial_lock: _ensure_gnss_powered() except Exception as e: print("Failed to initialize GNSS:", str(e)) while True: try: with _serial_lock: resp = _send_at("AT+CGNSINF") fix = _parse_cgnsinf(resp) except Exception as e: print("GPS read error:", str(e)) fix = None if fix: LocationX = fix["lat"] LocationY = fix["lon"] socketio.emit("gps_location", [LocationX, LocationY]) time.sleep(POLL_INTERVAL) def start_gps(): """Start the background thread that continuously polls the SIM868 for a GNSS fix.""" global _gps_thread if _gps_thread is None or not _gps_thread.is_alive(): _gps_thread = Thread(target=_poll_loop) _gps_thread.daemon = True _gps_thread.start() print(f"GPS thread started, alive: {_gps_thread.is_alive()}") else: print(f"GPS thread already running, alive: {_gps_thread.is_alive()}") return GPS() @socketio.on("gps") def get_gps(): """Return the last known GPS location and emit it over the 'gps_location' event.""" Location = [LocationX, LocationY] socketio.emit("gps_location", Location) print("gps_location emitted:", Location) return Location