381 lines
10 KiB
Dart
381 lines
10 KiB
Dart
|
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<SignalGPS> signalsGPS = [];
|
||
|
List<SignalBDS> signalsBDS = [];
|
||
|
List<SignalGLO> signalsGLO = [];
|
||
|
List<SignalGAL> signalsALS = [];
|
||
|
}
|
||
|
|
||
|
class Gnss {
|
||
|
SerialPort? _serialPort;
|
||
|
SerialPortReader? _serialPortReader;
|
||
|
Socket? _socket; // tcp
|
||
|
RtkBase? rtkBase;
|
||
|
LineSplitter lineSplitter = LineSplitter();
|
||
|
SentenceParser sentence = SentenceParser({}, null, null, null);
|
||
|
var locationStreamController = StreamController<LocationData>.broadcast();
|
||
|
var signalStreamController = StreamController<SignalData>.broadcast();
|
||
|
Stream<LocationData> get locationStream => locationStreamController.stream;
|
||
|
Stream<SignalData> 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<String> 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
|
||
|
}
|