library gnss; import 'dart:developer'; import 'dart:io'; import 'dart:typed_data'; import 'dart:async'; import "package:libserialport/libserialport.dart"; import "nmea/sentence.dart"; import 'nmea/gga.dart'; import 'nmea/rmc.dart'; import 'nmea/vtg.dart'; import 'nmea/gsa.dart'; import 'nmea/gsv.dart'; import 'nmea/heading.dart'; import 'nmea/bestpos.dart'; import "rtk_base.dart"; class LocationData { double latitude = 0; //纬度 double longitude = 0; //经度 double speed = 0; //速度 double baseLength = 0; //基线长度(两天线距离) double heading = 0; // double pitch = 0; //俯仰角 double altitude = 0; //海拔 double adop = 0; //高程定位精度 double hdop = 0; //水平定位精度 double vdop = 0; //垂直定位精度 double course = 0; //航向 double diffAge = 0; //差分数据年龄 int numberSv = 0; //可见卫星数 int numberSa = 0; //使用卫星数 String fixQuality = ""; //定位质量(字符串) int quality = 0; //定位质量(数字) @override String toString() { return "LocationData{latitude=$latitude,longitude=$longitude,speed=$speed,baseLength=$baseLength,heading=$heading,pitch=$pitch,altitude=$altitude,adop=$adop,hdop=$hdop,vdop=$vdop,course=$course,diffAge=$diffAge,numberSv=$numberSv,numberSa=$numberSa,fixQuality=$fixQuality,quality=$quality}"; } } const um982Init = [ "version\r\n", "UNLOGALL\r\n", "GPRMC 1\r\n", //0.2 "GPGGA 1\r\n", "GPVTG 1\r\n", "LOG HEADINGA ONTIME 0.2\r\n", "LOG BESTPOSA ONTIME 0.2\r\n", "GPGSV 1\r\n", "GPGSA 1\r\n", "SAVECONFIG\r\n" ]; class SignalGPS { int prn; double elevation; double azimuth; int snrL1; int snrL2; int snrL5; SignalGPS( {required this.prn, required this.elevation, required this.azimuth, required this.snrL1, required this.snrL2, required this.snrL5}); } class SignalBDS { int prn; double elevation; double azimuth; int snrB1I; int snrB2I; int snrB1C; int snrB2a; int snrB3I; SignalBDS( {required this.prn, required this.elevation, required this.azimuth, required this.snrB1I, required this.snrB2I, required this.snrB1C, required this.snrB2a, required this.snrB3I}); } class SignalGLO { int prn; double elevation; double azimuth; int snrR1; int snrR2; SignalGLO( {required this.prn, required this.elevation, required this.azimuth, required this.snrR1, required this.snrR2}); } class SignalGAL { int prn; double elevation; double azimuth; int snrE1; int snrE5a; int snrE5b; SignalGAL( {required this.prn, required this.elevation, required this.azimuth, required this.snrE1, required this.snrE5a, required this.snrE5b}); } class SignalData { List signalsGPS = []; List signalsBDS = []; List signalsGLO = []; List signalsALS = []; } class Gnss { SerialPort? _serialPort; SerialPortReader? _serialPortReader; Socket? _socket; // tcp RtkBase? rtkBase; LineSplitter lineSplitter = LineSplitter(); SentenceParser sentence = SentenceParser({}, null, null, null); var locationStreamController = StreamController.broadcast(); var signalStreamController = StreamController.broadcast(); Stream get locationStream => locationStreamController.stream; Stream get signalStream => signalStreamController.stream; final LocationData _locationData = LocationData(); final SignalData _signalData = SignalData(); SignalData get signalData => _signalData; bool locationUpdate = false; bool signalUpdate = false; bool _isTCP = false; String _port = ""; int _baudrate = 0; String _host = ""; int _portTcp = 0; Gnss({port = "", baudrate = 115200, host = "", portTCP = 0, isTCP = false}) { _port = port; _baudrate = baudrate; _host = host; _portTcp = portTCP; _isTCP = isTCP; } start() { if (_isTCP) { _connectTcp(); } else { _connectSerialPort(); } rtkBase ??= RtkBase(); rtkBase!.connect(); rtkBase!.rtcmStream.listen((rtcmData) { if (_serialPort != null) { _serialPort!.write(Uint8List.fromList(rtcmData)); } }); } void dispose() { if (_isTCP) { if (_socket != null) { _socket!.close(); _socket = null; } } else { if (_serialPort != null) { _serialPortReader?.close(); _serialPort!.close(); _serialPort = null; } } } _connectSerialPort() { _serialPort = SerialPort(_port); if (!_serialPort!.openReadWrite()) { throw Exception(SerialPort.lastError); } var config = _serialPort!.config; config.baudRate = _baudrate; config.bits = 8; config.parity = 0; config.stopBits = 1; config.setFlowControl(0); _serialPort!.config = config; _serialPortReader = SerialPortReader(_serialPort!); _serialPortReader!.stream.listen(_onData); if (_serialPort != null) { int i = 0; _serialPort!.write(Uint8List.fromList(um982Init[i].codeUnits)); Timer.periodic(const Duration(milliseconds: 100), (timer) { if (_serialPort != null) { _serialPort!.write(Uint8List.fromList(um982Init[i].codeUnits)); } i++; if (i == um982Init.length - 1) { timer.cancel(); } }); } } void _connectTcp() { if (_host == "" || _portTcp == 0) { return; } log("尝试连接$_host:$_port"); Socket.connect(_host, _portTcp).then((socket) { _socket = socket; log("连接成功"); socket.listen(_onData); socket.handleError((error) { log("error=$error"); }); socket.timeout(const Duration(seconds: 15), onTimeout: (sink) { log("timeout"); socket.destroy(); _reconnect(); }); }).onError((error, stackTrace) { log("error=$error"); _socket = null; _reconnect(); }); } void _reconnect() { log("尝试重新连接..."); Future.delayed(const Duration(seconds: 5), () { _connectTcp(); }); } _onData(Uint8List data) { var lines = lineSplitter.pushData(data); for (int i = 0; i < lines.length; i++) { try { var s = sentence.parse(lines[i]); switch (s.type) { case TypeBESTPOSA: var bestpos = BESTPOSA.newBESTPOSA(s); // log("bestposa=${bestposa.lat},${bestposa.lon}"); _locationData.latitude = bestpos.lat; _locationData.longitude = bestpos.lon; _locationData.altitude = bestpos.hgt; _locationData.adop = bestpos.adop; _locationData.hdop = bestpos.hdop; _locationData.vdop = bestpos.vdop; _locationData.diffAge = bestpos.diffAge; _locationData.fixQuality = bestpos.fixQuality; // _locationData.pdop = bestpos.pdop; break; case TypeHEADINGA: var h = HEADINGA.newHEADINGA(s); // log("heading=${h.heading}"); _locationData.baseLength = h.length; _locationData.heading = h.heading; _locationData.pitch = h.pitch; _locationData.numberSv = h.numberSv; _locationData.numberSa = h.numberSa; locationUpdate = true; break; case TypeRMC: var rmc = RMC.newRMC(s); _locationData.course = rmc.course; // log("rmc=${rmc.latitude},${rmc.longitude},${rmc.speed}"); break; case TypeGGA: var gga = GGA.newGGA(s); if (rtkBase != null) { rtkBase!.sendGGA(s.raw); } // log("gga=${gga.latitude},${gga.longitude}"); _locationData.quality = gga.quality; break; case TypeVTG: // 速度 var vtg = VTG.newVTG(s); // log("vtg=${vtg.groundSpeedKnots}"); _locationData.speed = vtg.groundSpeedKnots; break; case TypeGSA: // 已使用 var gsa = GSA.newGSA(s); // log("gsa=${gsa.pdop}"); // SV 可用 break; case TypeGSV: // 可见 var gsv = GSV.newGSV(s); // log("gsv=${gsv.numberSVsInView}"); signalUpdate = true; break; default: log('Unhandled sentence type: ${s.type}'); } } catch (e) { log("error=$e"); } } if (locationUpdate) { locationStreamController.add(_locationData); locationUpdate = false; } if (signalUpdate) { signalStreamController.add(_signalData); signalUpdate = false; } } } class LineSplitter { pushData(Uint8List data) { List sentences = []; if (endIndex + data.length > dataBuf.length) { endIndex = 0; sentenceStart = -1; } dataBuf.setAll(endIndex, data); endIndex = endIndex += data.length; int index = 0; while (index < endIndex) { if (sentenceStart < 0) { if (startDelimiter.contains(dataBuf[index]) == false) { index++; } else { sentenceStart = index; seekIndex = index + 1; } } else { for (; seekIndex < endIndex; seekIndex++) { if (dataBuf[seekIndex] == 0x0a) { sentences .add(String.fromCharCodes(dataBuf, sentenceStart, seekIndex)); sentenceStart = -1; index = seekIndex + 1; break; } } if (seekIndex == endIndex) { break; } } } // log("index=$index,endIndex=$endIndex, data.length=${data.length}"); if (index == endIndex) { endIndex = 0; } else if (index > 0) { dataBuf.setAll(0, dataBuf.sublist(index, endIndex)); sentenceStart = -1; endIndex -= index; } return sentences; } int endIndex = 0; int seekIndex = 0; int sentenceStart = -1; Uint8List dataBuf = Uint8List(4096); static const startDelimiter = [0x24, 0x23]; //$23 #23 }