Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4b57e95def | ||
|
f1c566b008 | ||
|
9a38c279ab | ||
|
99e56d5207 | ||
|
dcfd9c6268 |
@ -1,26 +1,37 @@
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gnss/gnss.dart';
|
||||
|
||||
class GnssController extends GetxController {
|
||||
late Gnss gnss;
|
||||
late final Gnss gnss;
|
||||
LocationData? locationData;
|
||||
SignalData? signalData;
|
||||
var locationUpdate = 0.obs;
|
||||
|
||||
var singnalUpdate = 0.obs;
|
||||
// var _selectedSignal = [];
|
||||
Map<String, bool> selectedSignal = {};
|
||||
// List<String> get selectedSignal => _selectedSignal;
|
||||
// set selectedSignal(Set<String> value) {
|
||||
// _selectedSignal = value;
|
||||
// update();
|
||||
// }
|
||||
final startIndex = 0.obs;
|
||||
updateSlider(double value) {
|
||||
startIndex.value = value.ceil();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
Map<String, bool> selectedSignal = {
|
||||
"GPS": true,
|
||||
"GLONASS": true,
|
||||
"GALILEO": true,
|
||||
"BEIDOU": true,
|
||||
"QZSS": true,
|
||||
"SBAS": true,
|
||||
}.obs;
|
||||
// final selectedSignal = Map<String,bool>{false, false, false, false, false};
|
||||
final QselectedSignal = <bool>[false, false, false, false, false].obs; //信号质量
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
gnss = Gnss(port: "/dev/ttysWK2", baudrate: 115200);
|
||||
// gnss = Gnss(port: "COM1", baudrate: 115200);
|
||||
gnss.start();
|
||||
gnss.locationStream.listen((location) {
|
||||
locationData = location;
|
||||
@ -34,7 +45,6 @@ class GnssController extends GetxController {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// TODO: implement dispose
|
||||
gnss.dispose();
|
||||
super.dispose();
|
||||
}
|
@ -1,50 +1,14 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gnss/gnss.dart';
|
||||
import 'package:gnssview/quality/chartpart.dart';
|
||||
// import 'package:gnss/gnss.dart';
|
||||
|
||||
import 'sky/skyplot.dart';
|
||||
import 'Controller/gnssController.dart'; // Import the correct file location for GnssController
|
||||
|
||||
// void main() {
|
||||
// Get.lazyPut<GnssController>(() => GnssController(), tag: 'gnss');
|
||||
// runApp(MyApp());
|
||||
// }
|
||||
|
||||
// Gnss gnss = Gnss(port: "/dev/ttysWK2", baudrate: 115200);
|
||||
|
||||
// class MyApp extends StatefulWidget {
|
||||
// @override
|
||||
// _MyAppState createState() => _MyAppState();
|
||||
// }
|
||||
|
||||
// class _MyAppState extends State<MyApp> {
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// // 初始化每个组的颜色
|
||||
// gnss.start();
|
||||
// gnss.locationStream.listen((location) {
|
||||
// log(location.toString() as num);
|
||||
// });
|
||||
// gnss.signalStream.listen((location) {
|
||||
// log(location.toString() as num);
|
||||
// });
|
||||
// }
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Scaffold(
|
||||
// appBar: AppBar(
|
||||
// title: Text('天空图'),
|
||||
// ),
|
||||
// body: SkyPlotPage(controller: Get.find(tag: 'gnss')));
|
||||
// }
|
||||
// }
|
||||
import 'sky/sky_plot.dart';
|
||||
import 'sky/sky_info.dart';
|
||||
import 'Controller/gnss_controller.dart'; // Import the correct file location for GnssController
|
||||
|
||||
void main() {
|
||||
Get.lazyPut<GnssController>(() => GnssController(), tag: 'gnss');
|
||||
Get.put<GnssController>(GnssController());
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
@ -71,13 +35,13 @@ class _MyAppState extends State<MyApp> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final con = Get.find<GnssController>(tag: 'gnss');
|
||||
// final con = Get.find<GnssController>();
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('天空图'),
|
||||
),
|
||||
body: SkyPlotPage(),
|
||||
body: ChartPart(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -1,89 +1,302 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gnss/gnss.dart';
|
||||
|
||||
class ChartPart extends StatelessWidget {
|
||||
final List<List<Color>> groupColors; // 接收每个组的颜色
|
||||
import '../Controller/gnss_controller.dart';
|
||||
|
||||
ChartPart({required this.groupColors}); // 构造函数
|
||||
const signalPrefixList = <String>["G", "R", "E", "B", "Q"];
|
||||
const List<Color> signalColorList = [
|
||||
Color.fromARGB(255, 255, 0, 0),
|
||||
Color.fromARGB(255, 0, 255, 0),
|
||||
Color.fromARGB(255, 0, 0, 255),
|
||||
Color.fromARGB(255, 255, 255, 0),
|
||||
Color.fromARGB(255, 0, 255, 255)
|
||||
];
|
||||
|
||||
class ChartPart extends GetView<GnssController> {
|
||||
ChartPart({super.key});
|
||||
|
||||
List<BarChartGroupData> checkSVData = [];
|
||||
|
||||
double maxY = 0;
|
||||
int maxX = 10;
|
||||
double xLength = 0;
|
||||
|
||||
// void drawBarChart(
|
||||
// Canvas canvas,
|
||||
// Size size,
|
||||
// int index,
|
||||
// Paint paint,
|
||||
// ) {
|
||||
// for (int index = 0; index < QselectedSignal.length; index++) {
|
||||
// if (QselectedSignal[index]) {
|
||||
// var snr = signalGNSS[index];
|
||||
// List<int> signal = snr.values.toList(); // 从 snr Map 中提取值并生成整数数组
|
||||
// print('signal= $signal');
|
||||
// for (int i = 0; i < signal.length; i++) {
|
||||
// if (signal[0] == 0 &&
|
||||
// signal[1] == 0 &&
|
||||
// signal[2] == 0 &&
|
||||
// signal[3] == 0 &&
|
||||
// signal[4] == 0) {
|
||||
// continue;
|
||||
// }
|
||||
// maxX = max(maxX, 16);
|
||||
// List<int> listy = [
|
||||
// signal[0],
|
||||
// signal[1],
|
||||
// signal[2],
|
||||
// signal[3],
|
||||
// signal[4]
|
||||
// ];
|
||||
// int maxItem = listy.reduce(max);
|
||||
// maxY = max(maxY, maxItem.toDouble());
|
||||
// checkSVData.add(makeGroupData(i + 1, listy, signalColorList[0]));
|
||||
// xLength++;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (checkSVData.isNotEmpty) {
|
||||
// if (controller.startIndex.value < checkSVData.length - 8) {
|
||||
// checkSVData = checkSVData.sublist(
|
||||
// controller.startIndex.value, controller.startIndex.value + 8);
|
||||
// } else {
|
||||
// checkSVData = checkSVData.sublist(
|
||||
// controller.startIndex.value, checkSVData.length - 1);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return OrientationBuilder(builder: (context, orientation) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final size = MediaQuery.of(context).size;
|
||||
final orientation =
|
||||
MediaQuery.of(context).orientation == Orientation.portrait;
|
||||
return Obx(() {
|
||||
controller.singnalUpdate.value;
|
||||
List<SignalGNSS> signalGNSS = controller.signalData == null
|
||||
? []
|
||||
: (controller.signalData!['BDS'] ?? []);
|
||||
//
|
||||
for (var i = 0; i < signalGNSS.length; i++) {}
|
||||
|
||||
// 使用静态示例数据来绘制图表框架
|
||||
List<BarChartGroupData> sampleData = [];
|
||||
for (int i = 0; i < groupColors.length; i++) {
|
||||
// 确保每个组至少有两个颜色
|
||||
if (groupColors[i].isNotEmpty &&
|
||||
groupColors[i][0] != Colors.transparent) {
|
||||
sampleData.add(
|
||||
BarChartGroupData(
|
||||
x: i,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: (4 + i).toDouble(), color: groupColors[i][0]),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return OrientationBuilder(
|
||||
builder: (context, orientation) {
|
||||
return SizedBox(
|
||||
width: size.width,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: BarChart(
|
||||
BarChartData(
|
||||
maxY: 10,
|
||||
barTouchData: BarTouchData(enabled: false),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 42,
|
||||
getTitlesWidget: (value, meta) {
|
||||
// 根据方向设置标题
|
||||
return Text(orientation == Orientation.portrait
|
||||
? 'Group $value'
|
||||
: 'Group $value'); // 可以根据需要调整
|
||||
},
|
||||
),
|
||||
return SizedBox(
|
||||
width: size.width,
|
||||
// decoration: const BoxDecoration(color: Colors.white),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Obx(() {
|
||||
controller.startIndex.value;
|
||||
return BarChart(
|
||||
swapAnimationDuration:
|
||||
const Duration(milliseconds: 0),
|
||||
BarChartData(
|
||||
maxY: 100, // 提示需要+ 20 因为 提示是根据柱状图位置进行计算的
|
||||
barTouchData: BarTouchData(
|
||||
enabled: false,
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
// tooltipBgColor: Colors.blueGrey,
|
||||
getTooltipItem: (
|
||||
BarChartGroupData group,
|
||||
int groupIndex,
|
||||
BarChartRodData rod,
|
||||
int rodIndex,
|
||||
) {
|
||||
return BarTooltipItem(
|
||||
'L1:${group.barRods[0].toY}\n',
|
||||
const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none,
|
||||
color: Colors.black,
|
||||
fontSize: 18,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Colors.black26,
|
||||
blurRadius: 12,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
touchCallback: (event, response) {
|
||||
if (event.isInterestedForInteractions &&
|
||||
response != null &&
|
||||
response.spot != null) {
|
||||
// setState(() {
|
||||
// touchedGroupIndex =
|
||||
// response.spot!.touchedBarGroupIndex;
|
||||
// });
|
||||
} else {
|
||||
// setState(() {
|
||||
// touchedGroupIndex = -100;
|
||||
// });
|
||||
}
|
||||
},
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: bottomTitles,
|
||||
reservedSize: 42,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 40,
|
||||
interval: 1,
|
||||
getTitlesWidget: leftTitles,
|
||||
),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
barGroups:
|
||||
List.generate(signalGNSS.length, (index) {
|
||||
List<int> listy = [];
|
||||
Map snr = signalGNSS[index].snr;
|
||||
|
||||
snr.forEach((key, value) {
|
||||
listy.add(value);
|
||||
});
|
||||
|
||||
return makeGroupData(
|
||||
index, listy, signalColorList[0]);
|
||||
}),
|
||||
gridData: const FlGridData(show: true),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 40,
|
||||
getTitlesWidget: (value, meta) {
|
||||
return RotatedBox(
|
||||
quarterTurns: 0, // 旋转 270 度
|
||||
child: Text(value.toString()),
|
||||
);
|
||||
},
|
||||
),
|
||||
orientation
|
||||
? const SizedBox(
|
||||
height: 50,
|
||||
)
|
||||
: const Text(""),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: size.height,
|
||||
child: Opacity(
|
||||
opacity: 0, // 设置 Slider 组件的透明度为 0,使其不可见
|
||||
child: Slider(
|
||||
value: controller.startIndex.toDouble(),
|
||||
onChanged: (newvalue) {
|
||||
controller.updateSlider(newvalue);
|
||||
},
|
||||
min: 0,
|
||||
max: xLength <= 5 ? 0 : xLength - 5,
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(show: false),
|
||||
barGroups: sampleData,
|
||||
alignment: BarChartAlignment.spaceAround,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget leftTitles(double value, TitleMeta meta) {
|
||||
const style = TextStyle(
|
||||
color: Color(0xff7589a2),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.none);
|
||||
String text;
|
||||
int intValue = (value).ceil();
|
||||
if (intValue % 10 == 0 && intValue <= maxY) {
|
||||
text = intValue.toString();
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
return SideTitleWidget(
|
||||
axisSide: meta.axisSide,
|
||||
space: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 20),
|
||||
child: Text(text, style: style)),
|
||||
);
|
||||
}
|
||||
|
||||
Map svMap = {
|
||||
"BEIDOU": "BSV",
|
||||
"GLONASS": "ESV",
|
||||
"GPS": "GSV",
|
||||
"GALILEO": "RSV",
|
||||
};
|
||||
Widget bottomTitles(double index, TitleMeta meta) {
|
||||
String text = "";
|
||||
int intValue = (index - 1).ceil();
|
||||
|
||||
// if (signal[].isNotEmpty) {
|
||||
// var data = svData[intValue];
|
||||
// String? name = svMap[data.name];
|
||||
// if (name != null) {
|
||||
// text = "${name.substring(0, 1)}${data.sn}";
|
||||
// }
|
||||
// }
|
||||
|
||||
return SideTitleWidget(
|
||||
axisSide: meta.axisSide,
|
||||
space: 10, //margin top
|
||||
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
color: Color(0xff7589a2),
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none,
|
||||
fontSize: 14,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final double width = 15;
|
||||
final Color leftBarColor = const Color.fromARGB(95, 101, 98, 98);
|
||||
final Color rightBarColor = const Color.fromRGBO(33, 150, 243, 1);
|
||||
|
||||
BarChartGroupData makeGroupData(int x, List<int> listy,
|
||||
[Color color = const Color.fromRGBO(33, 150, 243, 1)]) {
|
||||
List<BarChartRodData> list = [];
|
||||
for (var i = 0; i < listy.length; i++) {
|
||||
list.add(BarChartRodData(
|
||||
toY: listy[i].toDouble(),
|
||||
color: color.withOpacity((i + 1) * .2),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(2),
|
||||
),
|
||||
width: width,
|
||||
));
|
||||
}
|
||||
return BarChartGroupData(
|
||||
barsSpace: 0, x: x, showingTooltipIndicators: [], barRods: list);
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,8 @@ class _SignalQualityState extends State<SignalQuality> {
|
||||
super.initState();
|
||||
// 初始化每个组的颜色
|
||||
for (int i = 0; i < 4; i++) {
|
||||
groupColors.add([colors[i % colors.length], colors[(i + 1) % colors.length]]);
|
||||
groupColors
|
||||
.add([colors[i % colors.length], colors[(i + 1) % colors.length]]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,17 +27,19 @@ class _SignalQualityState extends State<SignalQuality> {
|
||||
setState(() {
|
||||
for (int i = 0; i < groupColors.length; i++) {
|
||||
// 只保留与选中颜色相同的颜色
|
||||
groupColors[i] = groupColors[i].first == color ? [color] : [Colors.transparent];
|
||||
groupColors[i] =
|
||||
groupColors[i].first == color ? [color] : [Colors.transparent];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String colorToString(Color color) {
|
||||
// 将颜色转换为字符串
|
||||
if (color == Colors.blue) return 'GPS';
|
||||
if (color == Colors.red) return 'BDS';
|
||||
if (color == Colors.green) return 'GLO';
|
||||
if (color == Colors.orange) return 'ALS';
|
||||
if (color == Color.fromARGB(255, 255, 0, 0)) return 'GPS(G)';
|
||||
if (color == Color.fromARGB(255, 0, 255, 0)) return 'GLONASS(R)';
|
||||
if (color == Color.fromARGB(255, 0, 0, 255)) return 'GALILEO(E)';
|
||||
if (color == Color.fromARGB(255, 255, 255, 0)) return 'BEIDOU(B)';
|
||||
if (color == Color.fromARGB(255, 0, 255, 255)) return 'QZSS(Q)';
|
||||
return 'Unknown Color';
|
||||
}
|
||||
|
||||
@ -50,7 +53,7 @@ class _SignalQualityState extends State<SignalQuality> {
|
||||
return Column(
|
||||
children: [
|
||||
SingleButton(
|
||||
colors: colors,
|
||||
// colors: colors,
|
||||
onSelectionChanged: (color) {
|
||||
setState(() {
|
||||
selectedColor = color; // 更新选中的颜色
|
||||
@ -60,10 +63,11 @@ class _SignalQualityState extends State<SignalQuality> {
|
||||
}
|
||||
});
|
||||
},
|
||||
signalColorList: [],
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: ChartPart(groupColors: groupColors), // 将组颜色传递给 ChartPart
|
||||
child: ChartPart(), // 将组颜色传递给 ChartPart
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class SingleButton extends StatefulWidget {
|
||||
final controller = Get.find(tag: 'gnss');
|
||||
final List<Color> colors;
|
||||
// final controller = Get.find(tag: 'gnss');
|
||||
final List<Color> signalColorList;
|
||||
final Function(Color) onSelectionChanged;
|
||||
|
||||
SingleButton({
|
||||
required this.colors,
|
||||
required this.signalColorList,
|
||||
required this.onSelectionChanged,
|
||||
});
|
||||
|
||||
@ -16,17 +16,17 @@ class SingleButton extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SingleButtonState extends State<SingleButton> {
|
||||
late Color selectedColor;
|
||||
late Color QselectedSignal;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedColor = widget.colors[0]; // 默认选中第一个颜色
|
||||
QselectedSignal = widget.signalColorList[0]; // 默认选中第一个颜色
|
||||
}
|
||||
|
||||
void updateSelection(Color color) {
|
||||
setState(() {
|
||||
selectedColor = color;
|
||||
QselectedSignal = color;
|
||||
widget.onSelectionChanged(color); // 通知父组件
|
||||
});
|
||||
}
|
||||
@ -35,23 +35,23 @@ class _SingleButtonState extends State<SingleButton> {
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(widget.colors.length, (index) {
|
||||
children: List.generate(widget.signalColorList.length, (index) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Radio<Color>(
|
||||
value: widget.colors[index],
|
||||
groupValue: selectedColor,
|
||||
value: widget.signalColorList[index],
|
||||
groupValue: QselectedSignal,
|
||||
onChanged: (Color? value) {
|
||||
if (value != null) {
|
||||
updateSelection(value);
|
||||
}
|
||||
},
|
||||
activeColor: widget.colors[index],
|
||||
activeColor: widget.signalColorList[index],
|
||||
),
|
||||
Text(
|
||||
colorToString(widget.colors[index]),
|
||||
style: TextStyle(color: widget.colors[index]),
|
||||
colorToString(widget.signalColorList[index]),
|
||||
style: TextStyle(color: widget.signalColorList[index]),
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -60,10 +60,11 @@ class _SingleButtonState extends State<SingleButton> {
|
||||
}
|
||||
|
||||
String colorToString(Color color) {
|
||||
if (color == Colors.blue) return 'GPS';
|
||||
if (color == Colors.red) return 'BDS';
|
||||
if (color == Colors.green) return 'GLO';
|
||||
if (color == Colors.orange) return 'ALS';
|
||||
if (color == Color.fromARGB(255, 255, 0, 0)) return 'GPS(G)';
|
||||
if (color == Color.fromARGB(255, 0, 255, 0)) return 'GLONASS(R)';
|
||||
if (color == Color.fromARGB(255, 0, 0, 255)) return 'GALILEO(E)';
|
||||
if (color == Color.fromARGB(255, 255, 255, 0)) return 'BEIDOU(B)';
|
||||
if (color == Color.fromARGB(255, 0, 255, 255)) return 'QZSS(Q)';
|
||||
return 'Unknown Color';
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MulButton extends StatefulWidget {
|
||||
final controller = Get.find(tag: 'gnss');
|
||||
final List<Color> colors;
|
||||
final Function(List<Color>) onSelectionChanged;
|
||||
final List<Color> signalColorList;
|
||||
final Function(int, bool) onSelectionChanged;
|
||||
|
||||
MulButton({
|
||||
required this.colors,
|
||||
required this.signalColorList,
|
||||
required this.onSelectionChanged,
|
||||
});
|
||||
|
||||
@ -16,24 +15,25 @@ class MulButton extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MulButtonState extends State<MulButton> {
|
||||
late List<bool> selectedColors;
|
||||
late List<bool> selectedSignal;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedColors = List.generate(widget.colors.length, (index) => false);
|
||||
selectedSignal =
|
||||
List.generate(widget.signalColorList.length, (index) => false);
|
||||
}
|
||||
|
||||
void updateSelection(int index, bool value) {
|
||||
setState(() {
|
||||
selectedColors[index] = value;
|
||||
List<Color> selected = [];
|
||||
for (int i = 0; i < selectedColors.length; i++) {
|
||||
if (selectedColors[i]) {
|
||||
selected.add(widget.colors[i]);
|
||||
}
|
||||
}
|
||||
widget.onSelectionChanged(selected); // 通知父组件
|
||||
selectedSignal[index] = value;
|
||||
// List<Color> selected = [];
|
||||
// for (int i = 0; i < selectedColors.length; i++) {
|
||||
// if (selectedColors[i]) {
|
||||
// selected.add(widget.colors[i]);
|
||||
// }
|
||||
// }
|
||||
widget.onSelectionChanged(index, value); // 通知父组件
|
||||
});
|
||||
}
|
||||
|
||||
@ -43,25 +43,25 @@ class _MulButtonState extends State<MulButton> {
|
||||
spacing: 8.0, // 水平间距
|
||||
runSpacing: 4.0, // 垂直间距
|
||||
alignment: WrapAlignment.start,
|
||||
children: List.generate(widget.colors.length, (index) {
|
||||
children: List.generate(widget.signalColorList.length, (index) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Checkbox(
|
||||
value: selectedColors[index],
|
||||
value: selectedSignal[index],
|
||||
onChanged: (bool? value) {
|
||||
if (value != null) {
|
||||
updateSelection(index, value);
|
||||
}
|
||||
},
|
||||
activeColor: widget.colors[index],
|
||||
activeColor: widget.signalColorList[index],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4), // 设置边角
|
||||
),
|
||||
),
|
||||
Text(
|
||||
colorToString(widget.colors[index]),
|
||||
style: TextStyle(color: widget.colors[index]),
|
||||
colorToString(widget.signalColorList[index]),
|
||||
style: TextStyle(color: widget.signalColorList[index]),
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -70,10 +70,11 @@ class _MulButtonState extends State<MulButton> {
|
||||
}
|
||||
|
||||
String colorToString(Color color) {
|
||||
if (color == Colors.blue) return 'GPS';
|
||||
if (color == Colors.red) return 'BDS';
|
||||
if (color == Colors.green) return 'GLO';
|
||||
if (color == Colors.orange) return 'ALS';
|
||||
if (color == Color.fromARGB(255, 255, 0, 0)) return 'GPS(G)';
|
||||
if (color == Color.fromARGB(255, 0, 255, 0)) return 'GLONASS(R)';
|
||||
if (color == Color.fromARGB(255, 0, 0, 255)) return 'GALILEO(E)';
|
||||
if (color == Color.fromARGB(255, 255, 255, 0)) return 'BEIDOU(B)';
|
||||
if (color == Color.fromARGB(255, 0, 255, 255)) return 'QZSS(Q)';
|
||||
return 'Unknown Color';
|
||||
}
|
||||
}
|
||||
|
@ -1,120 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gnssview/sky/device_type.dart';
|
||||
import '../Controller/gnssController.dart';
|
||||
import 'mulbutton.dart';
|
||||
|
||||
class SkyInfo extends StatelessWidget {
|
||||
late GnssController controller;
|
||||
// final String lat; // 纬度
|
||||
// final String lon; // 经度
|
||||
// final String hdop; // 高程
|
||||
// final int status; // 定位状态
|
||||
// final int view; // 可见
|
||||
// final int use; // 使用
|
||||
// final String time; // 时间
|
||||
|
||||
SkyInfo() {
|
||||
controller = Get.find<GnssController>(tag: 'gnss');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isPortrait =
|
||||
MediaQuery.of(context).orientation == Orientation.portrait;
|
||||
|
||||
return Theme(
|
||||
data: ThemeData(
|
||||
textTheme: const TextTheme(
|
||||
titleLarge: TextStyle(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 5),
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
alignment: Alignment.centerLeft,
|
||||
child:
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
// 创建行项的函数
|
||||
rowItem(context, "北纬:", '${controller.locationData?.latitude} '),
|
||||
rowItem(context, "东经:", '${controller.locationData?.longitude}'),
|
||||
rowItem(context, "高程:", '${controller.locationData?.altitude}'),
|
||||
rowItem(context, "水平定位精度:", '${controller.locationData?.hdop}'),
|
||||
rowItem(context, "垂直定位精度:", '${controller.locationData?.vdop}'),
|
||||
rowItem(context, "定位状态:", '${controller.locationData?.fixQuality}'),
|
||||
rowItem(context, "可见卫星数:", '${controller.locationData?.numberSv}'),
|
||||
rowItem(context, "使用卫星数:", '${controller.locationData?.numberSa}'),
|
||||
rowItem(context, "时间:", '${controller.locationData?.time}'),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
MulButton(
|
||||
colors: const [
|
||||
Colors.blue,
|
||||
Colors.red,
|
||||
Colors.green,
|
||||
Colors.orange
|
||||
],
|
||||
onSelectionChanged: (selectedColors) {
|
||||
// 处理选中的颜色
|
||||
print('选中的颜色: $selectedColors');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
Widget rowItem(BuildContext context, String title, String text) => Row(
|
||||
children: [
|
||||
FixedWidthTextWidget(
|
||||
width: 80,
|
||||
text: title,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
Text(
|
||||
text,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class FixedWidthTextWidget extends StatelessWidget {
|
||||
final String text;
|
||||
final double width;
|
||||
final TextStyle? style;
|
||||
|
||||
FixedWidthTextWidget({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.width = 80,
|
||||
this.style,
|
||||
});
|
||||
TextStyle? textStyle;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final deviceType = getDeviceType(context);
|
||||
double dynamicWidth = deviceType == DeviceType.mobile ? 0 : 50;
|
||||
|
||||
if (style != null) {
|
||||
textStyle =
|
||||
style!.copyWith(fontSize: deviceType == DeviceType.mobile ? 16 : 20);
|
||||
} else {
|
||||
textStyle =
|
||||
TextStyle(fontSize: deviceType == DeviceType.mobile ? 16 : 20);
|
||||
}
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||
width: width + dynamicWidth,
|
||||
child: Text(
|
||||
text,
|
||||
style: textStyle,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,250 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gnss/gnss.dart';
|
||||
|
||||
import '../Controller/gnssController.dart';
|
||||
|
||||
class DrawSkyPlot {
|
||||
double width = 500;
|
||||
double height = 500;
|
||||
late Canvas ctx;
|
||||
Map legend = {};
|
||||
final Paint _paint = Paint();
|
||||
Path path = Path();
|
||||
bool isPortrait = false;
|
||||
late LocationData locationData;
|
||||
late SignalData signalData;
|
||||
late GnssController controller;
|
||||
// final GnssController controller;
|
||||
|
||||
final bool isDarkMode;
|
||||
Color color = Colors.black;
|
||||
DrawSkyPlot(op, this.isDarkMode, this.controller) {
|
||||
// controller = Get.find<GnssController>(tag: 'gnss');
|
||||
ctx = op['ctx']; // canvas dom 对象
|
||||
height = op['height']; // 画布高
|
||||
width = op['width']; // 画布宽
|
||||
if (isDarkMode) {
|
||||
color = Colors.white;
|
||||
} else {
|
||||
color = Colors.black;
|
||||
}
|
||||
_paint
|
||||
..color = color
|
||||
..strokeWidth = 3
|
||||
..style = PaintingStyle.stroke;
|
||||
MediaQueryData mediaQueryData =
|
||||
MediaQueryData.fromView(WidgetsBinding.instance.window);
|
||||
final orientation = mediaQueryData.orientation;
|
||||
// 横屏竖屏宽高重新设置
|
||||
if (orientation == Orientation.portrait) {
|
||||
height = op['width'];
|
||||
isPortrait = true;
|
||||
} else {
|
||||
width = op['height'];
|
||||
isPortrait = false;
|
||||
}
|
||||
}
|
||||
|
||||
//外圈
|
||||
drawSkyPlotCircle() {
|
||||
double x = (width / 2) + 50;
|
||||
double y = (height / 2) - 15;
|
||||
double r = height / 2.5;
|
||||
ctx.drawCircle(Offset(x, y), r, _paint);
|
||||
ctx.drawCircle(Offset(x, y), r * 2 / 3, _paint);
|
||||
ctx.drawCircle(Offset(x, y), r / 3, _paint);
|
||||
path.moveTo(x - r, y);
|
||||
path.lineTo(x + r, y);
|
||||
path.moveTo(x, y - r);
|
||||
path.lineTo(x, y + r);
|
||||
path.moveTo(x + cos(30 * (pi / 180)) * r, y + sin(30 * (pi / 180)) * r);
|
||||
path.lineTo(x - cos(30 * (pi / 180)) * r, y - sin(30 * (pi / 180)) * r);
|
||||
path.moveTo(x + cos(60 * (pi / 180)) * r, y + sin(60 * (pi / 180)) * r);
|
||||
path.lineTo(x - cos(60 * (pi / 180)) * r, y - sin(60 * (pi / 180)) * r);
|
||||
path.moveTo(x + cos(60 * (pi / 180)) * r, y - sin(60 * (pi / 180)) * r);
|
||||
path.lineTo(x - cos(60 * (pi / 180)) * r, y + sin(60 * (pi / 180)) * r);
|
||||
path.moveTo(x - cos(60 * (pi / 180)) * r, y + sin(60 * (pi / 180)) * r);
|
||||
path.lineTo(x + cos(60 * (pi / 180)) * r, y - sin(60 * (pi / 180)) * r);
|
||||
path.moveTo(x + cos(30 * (pi / 180)) * r, y - sin(30 * (pi / 180)) * r);
|
||||
path.lineTo(x - cos(30 * (pi / 180)) * r, y + sin(30 * (pi / 180)) * r);
|
||||
path.moveTo(x - cos(30 * (pi / 180)) * r, y + sin(30 * (pi / 180)) * r);
|
||||
path.lineTo(x + cos(30 * (pi / 180)) * r, y - sin(30 * (pi / 180)) * r);
|
||||
ctx.drawPath(path, _paint);
|
||||
|
||||
Map textList = {
|
||||
'0': Offset(x, y - r - 25),
|
||||
'30': Offset(
|
||||
x + cos(60 * (pi / 180)) * r, y - sin(60 * (pi / 180)) * r - 25),
|
||||
'60': Offset(
|
||||
x + cos(30 * (pi / 180)) * r + 10, y - sin(30 * (pi / 180)) * r - 10),
|
||||
'90': Offset(x + r + 10, y - 5),
|
||||
'120': Offset(
|
||||
x + cos(30 * (pi / 180)) * r, y + sin(30 * (pi / 180)) * r + 5),
|
||||
'150': Offset(
|
||||
x + cos(60 * (pi / 180)) * r, y + sin(60 * (pi / 180)) * r + 5),
|
||||
'180': Offset(x - 10, y + r),
|
||||
'210': Offset(
|
||||
x - cos(60 * (pi / 180)) * r - 20, y + sin(60 * (pi / 180)) * r + 10),
|
||||
'240': Offset(
|
||||
x - cos(30 * (pi / 180)) * r - 30, y + sin(30 * (pi / 180)) * r + 5),
|
||||
'270': Offset(x - r - 35, y - 5),
|
||||
'300': Offset(
|
||||
x - cos(30 * (pi / 180)) * r - 37, y - sin(30 * (pi / 180)) * r - 20),
|
||||
'330': Offset(
|
||||
x - cos(60 * (pi / 180)) * r - 30, y - sin(60 * (pi / 180)) * r - 25)
|
||||
};
|
||||
textList.forEach((key, offset) {
|
||||
TextPainter(
|
||||
text: TextSpan(text: key, style: TextStyle(color: color, fontSize: 20)),
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
..layout()
|
||||
..paint(ctx, offset);
|
||||
});
|
||||
}
|
||||
|
||||
getCircles() {
|
||||
legend = {};
|
||||
double radius = height / 2.5;
|
||||
double r = 15;
|
||||
if (controller.signalData == null) {
|
||||
return;
|
||||
}
|
||||
var signalData = controller.signalData!;
|
||||
if (controller.selectedSignal['GPS'] == true) {
|
||||
for (int i = 0; i < signalData.GPS.length; i++) {
|
||||
SignalGPS item = controller.signalData!.GPS![i];
|
||||
double elev = item.elevation * pi / 180; // 将仰角转换为弧度
|
||||
double maxr = radius * cos(elev); // 计算该点到圆心的距离
|
||||
|
||||
double azi = (90 - item.azimuth) * pi / 180; // 将方位角转换为弧度
|
||||
double cx = (maxr) * cos(azi); // 计算 x 坐标
|
||||
double cy = -(maxr) * sin(azi); // 计算 y
|
||||
|
||||
_paint.color = Colors.blue;
|
||||
_paint.style = PaintingStyle.fill;
|
||||
|
||||
// 绘制卫星位置
|
||||
ctx.drawCircle(Offset(cx, cy), r, _paint);
|
||||
}
|
||||
} else if (controller.selectedSignal['BDS'] == true) {
|
||||
for (int i = 0; i < signalData.BDS.length; i++) {
|
||||
SignalBDS item = controller.signalData!.BDS![i];
|
||||
double elev = item.elevation * pi / 180; // 将仰角转换为弧度
|
||||
double maxr = radius * cos(elev); // 计算该点到圆心的距离
|
||||
|
||||
double azi = (90 - item.azimuth) * pi / 180; // 将方位角转换为弧度
|
||||
double cx = (maxr) * cos(azi); // 计算 x 坐标
|
||||
double cy = -(maxr) * sin(azi); // 计算 y
|
||||
|
||||
_paint.color = Colors.red;
|
||||
_paint.style = PaintingStyle.fill;
|
||||
// 绘制卫星位置
|
||||
ctx.drawCircle(Offset(cx, cy), r, _paint);
|
||||
}
|
||||
} else if (controller.selectedSignal['GLO'] == true) {
|
||||
for (int i = 0; i < signalData.GLO.length; i++) {
|
||||
SignalGLO item = controller.signalData!.GLO![i];
|
||||
double elev = item.elevation * pi / 180; // 将仰角转换为弧度
|
||||
double maxr = radius * cos(elev); // 计算该点到圆心的距离
|
||||
|
||||
double azi = (90 - item.azimuth) * pi / 180; // 将方位角转换为弧度
|
||||
double cx = (maxr) * cos(azi); // 计算 x 坐标
|
||||
double cy = -(maxr) * sin(azi); // 计算 y
|
||||
|
||||
_paint.color = Colors.green;
|
||||
_paint.style = PaintingStyle.fill;
|
||||
// 绘制卫星位置
|
||||
ctx.drawCircle(Offset(cx, cy), r, _paint);
|
||||
}
|
||||
} else if (controller.selectedSignal['ALS'] == true) {
|
||||
for (int i = 0; i < signalData.GAL.length; i++) {
|
||||
SignalGAL item = controller.signalData!.GAL![i];
|
||||
double elev = item.elevation * pi / 180; // 将仰角转换为弧度
|
||||
double maxr = radius * cos(elev); // 计算该点到圆心的距离
|
||||
|
||||
double azi = (90 - item.azimuth) * pi / 180; // 将方位角转换为弧度
|
||||
double cx = (maxr) * cos(azi); // 计算 x 坐标
|
||||
double cy = -(maxr) * sin(azi); // 计算 y
|
||||
|
||||
_paint.color = Colors.orange;
|
||||
_paint.style = PaintingStyle.fill;
|
||||
// 绘制卫星位置
|
||||
ctx.drawCircle(Offset(cx, cy), r, _paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SkyPlotPage extends StatelessWidget {
|
||||
late final GnssController controller;
|
||||
// Map<String, ui.Image> imageList = {};
|
||||
@override
|
||||
SkyPlotPage() {
|
||||
controller = Get.find<GnssController>(tag: 'gnss');
|
||||
}
|
||||
Future<ui.Image> loadImage(String path) async {
|
||||
ByteData data = await rootBundle.load(path);
|
||||
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
|
||||
ui.FrameInfo fi = await codec.getNextFrame();
|
||||
return fi.image;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Obx(() {
|
||||
controller.singnalUpdate;
|
||||
return CustomPaint(
|
||||
painter: DrawSky(controller, context, isDarkMode),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class DrawSky extends CustomPainter {
|
||||
final BuildContext context;
|
||||
final GnssController controller;
|
||||
final bool isDarkMode;
|
||||
// late SignalData signalData;
|
||||
DrawSky(this.controller, this.context, this.isDarkMode);
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
canvas.translate(0, -15);
|
||||
final bool isPortrait =
|
||||
MediaQuery.of(context).orientation == Orientation.portrait;
|
||||
DrawSkyPlot skyPlot = DrawSkyPlot({
|
||||
'ctx': canvas,
|
||||
'height': size.height,
|
||||
'width': size.width,
|
||||
}, isDarkMode, controller);
|
||||
if (!isPortrait) {
|
||||
canvas.translate(-30, 0);
|
||||
}
|
||||
skyPlot.drawSkyPlotCircle();
|
||||
// if (controller.chartData.value.time != 0) {
|
||||
if (isPortrait) {
|
||||
canvas.translate(size.width / 2, size.height / 2);
|
||||
} else {
|
||||
canvas.translate(size.width / 2, size.height / 2);
|
||||
}
|
||||
|
||||
skyPlot.getCircles();
|
||||
if (isPortrait) {
|
||||
canvas.translate(-size.width / 2, -size.height / 2);
|
||||
} else {
|
||||
canvas.translate(-size.width / 2, -size.height / 2);
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||
}
|
155
lib/sky/sky_info.dart
Normal file
155
lib/sky/sky_info.dart
Normal file
@ -0,0 +1,155 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:gnssview/sky/device_type.dart';
|
||||
import '../Controller/gnss_controller.dart';
|
||||
import 'mulbutton.dart';
|
||||
import 'sky_plot.dart';
|
||||
|
||||
class SkyInfo extends StatelessWidget {
|
||||
late final GnssController controller;
|
||||
|
||||
SkyInfo({super.key}) {
|
||||
controller = Get.find<GnssController>();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isPortrait =
|
||||
MediaQuery.of(context).orientation == Orientation.portrait;
|
||||
|
||||
return Theme(
|
||||
data: ThemeData(
|
||||
textTheme: const TextTheme(
|
||||
titleLarge: TextStyle(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 5),
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Obx(() {
|
||||
controller.locationUpdate.value;
|
||||
final location = controller.locationData;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 创建行项的函数
|
||||
rowItem(context, "纬度:", '${location?.latitude}'),
|
||||
rowItem(context, "经度:", '${location?.longitude}'),
|
||||
rowItem(context, "高程:", '${location?.altitude}'),
|
||||
rowItem(context, "水平精度:", '${location?.hdop}'),
|
||||
rowItem(context, "垂直精度:", '${location?.vdop}'),
|
||||
rowItem(context, "定位状态:", '${location?.fixQuality}'),
|
||||
rowItem(context, "可见卫星数:", '${location?.numberSv}'),
|
||||
rowItem(context, "使用卫星数:", '${location?.numberSa}'),
|
||||
rowItem(
|
||||
context,
|
||||
"时间:",
|
||||
location == null
|
||||
? 'null'
|
||||
: DateFormat('yyyy-MM-dd HH:mm:ss')
|
||||
.format(location.time)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
MulButton(
|
||||
signalColorList: const [
|
||||
Color.fromARGB(255, 255, 0, 0),
|
||||
Color.fromARGB(255, 0, 255, 0),
|
||||
Color.fromARGB(255, 0, 0, 255),
|
||||
Color.fromARGB(255, 255, 255, 0),
|
||||
Color.fromARGB(255, 0, 255, 255)
|
||||
],
|
||||
onSelectionChanged: (int index, bool value) {
|
||||
// controller.selectedSignal[index] = value;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
]);
|
||||
})));
|
||||
}
|
||||
|
||||
Widget rowItem(BuildContext context, String title, String text) => Row(
|
||||
children: [
|
||||
FixedWidthText(
|
||||
width: 80,
|
||||
text: title,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
Text(
|
||||
text,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class FixedWidthText extends StatelessWidget {
|
||||
final String text;
|
||||
final double width;
|
||||
final TextStyle? style;
|
||||
const FixedWidthText({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.width = 80,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final deviceType = getDeviceType(context);
|
||||
double dynamicWidth = deviceType == DeviceType.mobile ? 0 : 50;
|
||||
TextStyle textStyle;
|
||||
if (style != null) {
|
||||
textStyle =
|
||||
style!.copyWith(fontSize: deviceType == DeviceType.mobile ? 16 : 20);
|
||||
} else {
|
||||
textStyle =
|
||||
TextStyle(fontSize: deviceType == DeviceType.mobile ? 16 : 20);
|
||||
}
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||
width: width + dynamicWidth,
|
||||
child: Text(
|
||||
text,
|
||||
style: textStyle,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SkyInfoPlotPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final orientation = MediaQuery.of(context).orientation;
|
||||
bool isPortrait = orientation == Orientation.portrait;
|
||||
return isPortrait
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: SkyInfo(),
|
||||
),
|
||||
Expanded(
|
||||
child: SkyPlotPage(),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: SkyInfo(),
|
||||
),
|
||||
Expanded(
|
||||
child: SkyPlotPage(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
348
lib/sky/sky_plot.dart
Normal file
348
lib/sky/sky_plot.dart
Normal file
@ -0,0 +1,348 @@
|
||||
import 'dart:math';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gnss/gnss.dart';
|
||||
|
||||
import '../Controller/gnss_controller.dart';
|
||||
|
||||
// const signalNameList = <String>["GPS", "GLONASS", "GALILEO", "BEIDOU", "QZSS"];
|
||||
const Map<String, String> signalPrefixList = {
|
||||
"GPS": "G",
|
||||
"GLONASS": "N",
|
||||
"GALILEO": "E",
|
||||
"BEIDOU": "B",
|
||||
"QZSS": "Q"
|
||||
};
|
||||
const Map<String, Color> signalColorMap = {
|
||||
"GPS": Color.fromARGB(255, 255, 0, 0),
|
||||
"GLONASS": Color.fromARGB(255, 0, 255, 0),
|
||||
"GALILEO": Color.fromARGB(255, 0, 0, 255),
|
||||
"BEIDOU": Color.fromARGB(255, 255, 255, 0),
|
||||
"QZSS": Color.fromARGB(255, 0, 255, 255),
|
||||
};
|
||||
|
||||
// class DrawSkyPlot {
|
||||
// double width = 500;
|
||||
// double height = 500;
|
||||
// late Canvas ctx;
|
||||
// Map legend = {};
|
||||
// final Paint _paint = Paint();
|
||||
// Path path = Path();
|
||||
// bool isPortrait = false;
|
||||
// late LocationData locationData;
|
||||
// late SignalData signalData;
|
||||
// late GnssController controller;
|
||||
// // final GnssController controller;
|
||||
|
||||
// final bool isDarkMode;
|
||||
// Color color = Colors.black;
|
||||
// DrawSkyPlot(op, this.isDarkMode, this.controller) {
|
||||
// // controller = Get.find<GnssController>(tag: 'gnss');
|
||||
// ctx = op['ctx']; // canvas dom 对象
|
||||
// height = op['height']; // 画布高
|
||||
// width = op['width']; // 画布宽
|
||||
// if (isDarkMode) {
|
||||
// color = Colors.white;
|
||||
// } else {
|
||||
// color = Colors.black;
|
||||
// }
|
||||
// _paint
|
||||
// ..color = color
|
||||
// ..strokeWidth = 3
|
||||
// ..style = PaintingStyle.stroke;
|
||||
// MediaQueryData mediaQueryData =
|
||||
// MediaQueryData.fromView(WidgetsBinding.instance.window);
|
||||
// final orientation = mediaQueryData.orientation;
|
||||
// // 横屏竖屏宽高重新设置
|
||||
// if (orientation == Orientation.portrait) {
|
||||
// height = op['width'];
|
||||
// isPortrait = true;
|
||||
// } else {
|
||||
// width = op['height'];
|
||||
// isPortrait = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// //外圈
|
||||
// drawSkyPlotCircle() {
|
||||
// double x = (width / 2) + 50;
|
||||
// double y = (height / 2) - 15;
|
||||
// double r = height / 2.5;
|
||||
// ctx.drawCircle(Offset(x, y), r, _paint);
|
||||
// ctx.drawCircle(Offset(x, y), r * 2 / 3, _paint);
|
||||
// ctx.drawCircle(Offset(x, y), r / 3, _paint);
|
||||
// path.moveTo(x - r, y);
|
||||
// path.lineTo(x + r, y);
|
||||
// path.moveTo(x, y - r);
|
||||
// path.lineTo(x, y + r);
|
||||
// path.moveTo(x + cos(30 * (pi / 180)) * r, y + sin(30 * (pi / 180)) * r);
|
||||
// path.lineTo(x - cos(30 * (pi / 180)) * r, y - sin(30 * (pi / 180)) * r);
|
||||
// path.moveTo(x + cos(60 * (pi / 180)) * r, y + sin(60 * (pi / 180)) * r);
|
||||
// path.lineTo(x - cos(60 * (pi / 180)) * r, y - sin(60 * (pi / 180)) * r);
|
||||
// path.moveTo(x + cos(60 * (pi / 180)) * r, y - sin(60 * (pi / 180)) * r);
|
||||
// path.lineTo(x - cos(60 * (pi / 180)) * r, y + sin(60 * (pi / 180)) * r);
|
||||
// path.moveTo(x - cos(60 * (pi / 180)) * r, y + sin(60 * (pi / 180)) * r);
|
||||
// path.lineTo(x + cos(60 * (pi / 180)) * r, y - sin(60 * (pi / 180)) * r);
|
||||
// path.moveTo(x + cos(30 * (pi / 180)) * r, y - sin(30 * (pi / 180)) * r);
|
||||
// path.lineTo(x - cos(30 * (pi / 180)) * r, y + sin(30 * (pi / 180)) * r);
|
||||
// path.moveTo(x - cos(30 * (pi / 180)) * r, y + sin(30 * (pi / 180)) * r);
|
||||
// path.lineTo(x + cos(30 * (pi / 180)) * r, y - sin(30 * (pi / 180)) * r);
|
||||
// ctx.drawPath(path, _paint);
|
||||
|
||||
// Map textList = {
|
||||
// '0': Offset(x, y - r - 25),
|
||||
// '30': Offset(
|
||||
// x + cos(60 * (pi / 180)) * r, y - sin(60 * (pi / 180)) * r - 25),
|
||||
// '60': Offset(
|
||||
// x + cos(30 * (pi / 180)) * r + 10, y - sin(30 * (pi / 180)) * r - 10),
|
||||
// '90': Offset(x + r + 10, y - 5),
|
||||
// '120': Offset(
|
||||
// x + cos(30 * (pi / 180)) * r, y + sin(30 * (pi / 180)) * r + 5),
|
||||
// '150': Offset(
|
||||
// x + cos(60 * (pi / 180)) * r, y + sin(60 * (pi / 180)) * r + 5),
|
||||
// '180': Offset(x - 10, y + r),
|
||||
// '210': Offset(
|
||||
// x - cos(60 * (pi / 180)) * r - 20, y + sin(60 * (pi / 180)) * r + 10),
|
||||
// '240': Offset(
|
||||
// x - cos(30 * (pi / 180)) * r - 30, y + sin(30 * (pi / 180)) * r + 5),
|
||||
// '270': Offset(x - r - 35, y - 5),
|
||||
// '300': Offset(
|
||||
// x - cos(30 * (pi / 180)) * r - 37, y - sin(30 * (pi / 180)) * r - 20),
|
||||
// '330': Offset(
|
||||
// x - cos(60 * (pi / 180)) * r - 30, y - sin(60 * (pi / 180)) * r - 25)
|
||||
// };
|
||||
// textList.forEach((key, offset) {
|
||||
// TextPainter(
|
||||
// text: TextSpan(text: key, style: TextStyle(color: color, fontSize: 20)),
|
||||
// textDirection: TextDirection.ltr,
|
||||
// )
|
||||
// ..layout()
|
||||
// ..paint(ctx, offset);
|
||||
// });
|
||||
// }
|
||||
|
||||
// getCircles() {
|
||||
// legend = {};
|
||||
// double radius = height / 2.5;
|
||||
// double r = 15;
|
||||
// if (controller.signalData == null) {
|
||||
// return;
|
||||
// }
|
||||
// var signalData = controller.signalData!;
|
||||
// if (controller.selectedSignal['GPS'] == true) {
|
||||
// for (int i = 0; i < signalData.GPS.length; i++) {
|
||||
// SignalGPS item = controller.signalData.GPS[i];
|
||||
// double elev = item.elevation * pi / 180; // 将仰角转换为弧度
|
||||
// double maxr = radius * cos(elev); // 计算该点到圆心的距离
|
||||
|
||||
// double azi = (90 - item.azimuth) * pi / 180; // 将方位角转换为弧度
|
||||
// double cx = (maxr) * cos(azi); // 计算 x 坐标
|
||||
// double cy = -(maxr) * sin(azi); // 计算 y
|
||||
|
||||
// _paint.color = Colors.blue;
|
||||
// _paint.style = PaintingStyle.fill;
|
||||
|
||||
// // 绘制卫星位置
|
||||
// ctx.drawCircle(Offset(cx, cy), r, _paint);
|
||||
// }
|
||||
// } else if (controller.selectedSignal['BDS'] == true) {
|
||||
// for (int i = 0; i < signalData.BDS.length; i++) {
|
||||
// SignalBDS item = controller.signalData!.BDS![i];
|
||||
// double elev = item.elevation * pi / 180; // 将仰角转换为弧度
|
||||
// double maxr = radius * cos(elev); // 计算该点到圆心的距离
|
||||
|
||||
// double azi = (90 - item.azimuth) * pi / 180; // 将方位角转换为弧度
|
||||
// double cx = (maxr) * cos(azi); // 计算 x 坐标
|
||||
// double cy = -(maxr) * sin(azi); // 计算 y
|
||||
|
||||
// _paint.color = Colors.red;
|
||||
// _paint.style = PaintingStyle.fill;
|
||||
// // 绘制卫星位置
|
||||
// ctx.drawCircle(Offset(cx, cy), r, _paint);
|
||||
// }
|
||||
// } else if (controller.selectedSignal['GLO'] == true) {
|
||||
// for (int i = 0; i < signalData.GLO.length; i++) {
|
||||
// SignalGLO item = controller.signalData!.GLO![i];
|
||||
// double elev = item.elevation * pi / 180; // 将仰角转换为弧度
|
||||
// double maxr = radius * cos(elev); // 计算该点到圆心的距离
|
||||
|
||||
// double azi = (90 - item.azimuth) * pi / 180; // 将方位角转换为弧度
|
||||
// double cx = (maxr) * cos(azi); // 计算 x 坐标
|
||||
// double cy = -(maxr) * sin(azi); // 计算 y
|
||||
|
||||
// _paint.color = Colors.green;
|
||||
// _paint.style = PaintingStyle.fill;
|
||||
// // 绘制卫星位置
|
||||
// ctx.drawCircle(Offset(cx, cy), r, _paint);
|
||||
// }
|
||||
// } else if (controller.selectedSignal['ALS'] == true) {
|
||||
// for (int i = 0; i < signalData.GAL.length; i++) {
|
||||
// SignalGAL item = controller.signalData!.GAL![i];
|
||||
// double elev = item.elevation * pi / 180; // 将仰角转换为弧度
|
||||
// double maxr = radius * cos(elev); // 计算该点到圆心的距离
|
||||
|
||||
// double azi = (90 - item.azimuth) * pi / 180; // 将方位角转换为弧度
|
||||
// double cx = (maxr) * cos(azi); // 计算 x 坐标
|
||||
// double cy = -(maxr) * sin(azi); // 计算 y
|
||||
|
||||
// _paint.color = Colors.orange;
|
||||
// _paint.style = PaintingStyle.fill;
|
||||
// // 绘制卫星位置
|
||||
// ctx.drawCircle(Offset(cx, cy), r, _paint);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
class SkyPlotPage extends StatelessWidget {
|
||||
late final GnssController controller;
|
||||
// Map<String, ui.Image> imageList = {};
|
||||
@override
|
||||
SkyPlotPage({super.key}) {
|
||||
controller = Get.find<GnssController>();
|
||||
}
|
||||
Future<ui.Image> loadImage(String path) async {
|
||||
ByteData data = await rootBundle.load(path);
|
||||
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
|
||||
ui.FrameInfo fi = await codec.getNextFrame();
|
||||
return fi.image;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
final size = MediaQuery.of(context).size;
|
||||
return Obx(() {
|
||||
controller.singnalUpdate.value;
|
||||
return SizedBox(
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
child: CustomPaint(
|
||||
painter:
|
||||
SkyPlotPainter(controller.signalData, controller.selectedSignal),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// class DrawSky extends CustomPainter {
|
||||
// final BuildContext context;
|
||||
// final GnssController controller;
|
||||
// final radius = 20.0;
|
||||
|
||||
// final bool isDarkMode;
|
||||
// // late SignalData signalData;
|
||||
// DrawSky(this.controller, this.context, this.isDarkMode);
|
||||
// @override
|
||||
// void paint(Canvas canvas, Size size) {
|
||||
// canvas.translate(0, -15);
|
||||
// final bool isPortrait =
|
||||
// MediaQuery.of(context).orientation == Orientation.portrait;
|
||||
// DrawSkyPlot skyPlot = DrawSkyPlot({
|
||||
// 'ctx': canvas,
|
||||
// 'height': size.height,
|
||||
// 'width': size.width,
|
||||
// }, isDarkMode, controller);
|
||||
// if (!isPortrait) {
|
||||
// canvas.translate(-30, 0);
|
||||
// }
|
||||
// skyPlot.drawSkyPlotCircle();
|
||||
// // if (controller.chartData.value.time != 0) {
|
||||
// if (isPortrait) {
|
||||
// canvas.translate(size.width / 2, size.height / 2);
|
||||
// } else {
|
||||
// canvas.translate(size.width / 2, size.height / 2);
|
||||
// }
|
||||
|
||||
// skyPlot.getCircles();
|
||||
// if (isPortrait) {
|
||||
// canvas.translate(-size.width / 2, -size.height / 2);
|
||||
// } else {
|
||||
// canvas.translate(-size.width / 2, -size.height / 2);
|
||||
// }
|
||||
// // }
|
||||
// }
|
||||
|
||||
// @override
|
||||
// bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||
// }
|
||||
|
||||
class SkyPlotPainter extends CustomPainter {
|
||||
final SignalData? signalData;
|
||||
final Map<String, bool> selectedSignal;
|
||||
final satelliteRadius = 12.0;
|
||||
SkyPlotPainter(this.signalData, this.selectedSignal);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = const ui.Color.fromARGB(255, 0, 0, 0)
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
final double radius = min(size.width / 2, size.height / 2);
|
||||
final Offset center = Offset(size.width / 2, size.height / 2);
|
||||
|
||||
// Draw concentric circles for different elevation angles
|
||||
for (int i = 0; i <= 90; i += 30) {
|
||||
double r = radius * (90 - i) / 90;
|
||||
canvas.drawCircle(center, r, paint);
|
||||
}
|
||||
|
||||
// Draw lines for different azimuth angles
|
||||
for (int i = 0; i < 360; i += 30) {
|
||||
double radians = i * pi / 180;
|
||||
double x = center.dx + radius * cos(radians);
|
||||
double y = center.dy + radius * sin(radians);
|
||||
canvas.drawLine(center, Offset(x, y), paint);
|
||||
}
|
||||
if (signalData == null) {
|
||||
return;
|
||||
}
|
||||
// Draw satellite positions
|
||||
final satellitePaint = Paint()..style = PaintingStyle.fill;
|
||||
signalData!.forEach((key, value) {
|
||||
if (selectedSignal[key] == true) {
|
||||
satellitePaint.color = signalColorMap[key] ?? Colors.yellow;
|
||||
drawGnssSignal(canvas, center, radius, value, satellitePaint, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void drawGnssSignal(
|
||||
Canvas canvas,
|
||||
Offset center,
|
||||
double radius,
|
||||
List<SignalGNSS> signals,
|
||||
Paint paint,
|
||||
String neme,
|
||||
) {
|
||||
for (final signal in signals) {
|
||||
final int el = signal.elevation;
|
||||
|
||||
final double az = (360 - signal.azimuth + 90) * pi / 180;
|
||||
|
||||
final double r = radius * (90 - el) / 90;
|
||||
final double x = center.dx + r * cos(az);
|
||||
final double y = center.dy + r * sin(az);
|
||||
|
||||
canvas.drawCircle(Offset(x, y), satelliteRadius, paint);
|
||||
TextPainter(
|
||||
text: TextSpan(
|
||||
text: signalPrefixList[neme]! + signal.prn.toString().padLeft(2, '0'),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 12),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
..layout()
|
||||
..paint(canvas, Offset(x - 9, y - 7));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
29
plugins/gnss/.gitignore
vendored
29
plugins/gnss/.gitignore
vendored
@ -1,29 +0,0 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
build/
|
@ -1,10 +0,0 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "b0850beeb25f6d5b10426284f506557f66181b36"
|
||||
channel: "stable"
|
||||
|
||||
project_type: package
|
@ -1,3 +0,0 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
@ -1 +0,0 @@
|
||||
TODO: Add your license here.
|
@ -1,39 +0,0 @@
|
||||
<!--
|
||||
This README describes the package. If you publish this package to pub.dev,
|
||||
this README's contents appear on the landing page for your package.
|
||||
|
||||
For information about how to write a good package README, see the guide for
|
||||
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
||||
|
||||
For general information about developing packages, see the Dart guide for
|
||||
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
||||
and the Flutter guide for
|
||||
[developing packages and plugins](https://flutter.dev/developing-packages).
|
||||
-->
|
||||
|
||||
TODO: Put a short description of the package here that helps potential users
|
||||
know whether this package might be useful for them.
|
||||
|
||||
## Features
|
||||
|
||||
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
||||
|
||||
## Getting started
|
||||
|
||||
TODO: List prerequisites and provide or point to information on how to
|
||||
start using the package.
|
||||
|
||||
## Usage
|
||||
|
||||
TODO: Include short and useful examples for package users. Add longer examples
|
||||
to `/example` folder.
|
||||
|
||||
```dart
|
||||
const like = 'sample';
|
||||
```
|
||||
|
||||
## Additional information
|
||||
|
||||
TODO: Tell users more about the package: where to find more information, how to
|
||||
contribute to the package, how to file issues, what response they can expect
|
||||
from the package authors, and more.
|
@ -1,7 +0,0 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
linter:
|
||||
rules:
|
||||
- avoid_print
|
@ -1,380 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'apb.dart';
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
import 'types.dart';
|
||||
|
||||
const String TypeAAM = 'AAM';
|
||||
|
||||
class AAM {
|
||||
String statusArrivalCircleEntered;
|
||||
String statusPerpendicularPassed;
|
||||
double arrivalCircleRadius;
|
||||
String arrivalCircleRadiusUnit;
|
||||
String destinationWaypointID;
|
||||
|
||||
AAM(
|
||||
{required this.statusArrivalCircleEntered,
|
||||
required this.statusPerpendicularPassed,
|
||||
required this.arrivalCircleRadius,
|
||||
required this.arrivalCircleRadiusUnit,
|
||||
required this.destinationWaypointID});
|
||||
|
||||
static AAM newAAM(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
p.assertType(TypeAAM);
|
||||
return AAM(
|
||||
statusArrivalCircleEntered: p.enumString(
|
||||
0,
|
||||
"arrival circle entered status",
|
||||
[WPStatusArrivalCircleEnteredA, WPStatusArrivalCircleEnteredV]),
|
||||
statusPerpendicularPassed: p.enumString(
|
||||
1,
|
||||
"perpendicularly passed status",
|
||||
[WPStatusPerpendicularPassedA, WPStatusPerpendicularPassedV]),
|
||||
arrivalCircleRadius: p.float64(2, "arrival circle radius"),
|
||||
arrivalCircleRadiusUnit: p.enumString(3, "arrival circle radius units", [
|
||||
DistanceUnitKilometre,
|
||||
DistanceUnitNauticalMile,
|
||||
DistanceUnitStatuteMile,
|
||||
DistanceUnitMetre
|
||||
]),
|
||||
destinationWaypointID: p.string(4, "destination waypoint ID"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeABM = "ABM";
|
||||
|
||||
class ABM {
|
||||
int numFragments;
|
||||
int fragmentNumber;
|
||||
int messageID;
|
||||
String mmsi;
|
||||
String channel;
|
||||
int vdlMessageNumber;
|
||||
List<int>
|
||||
payload; // Assuming the byte array is represented as a list of integers
|
||||
|
||||
ABM({
|
||||
required this.numFragments,
|
||||
required this.fragmentNumber,
|
||||
required this.messageID,
|
||||
required this.mmsi,
|
||||
required this.channel,
|
||||
required this.vdlMessageNumber,
|
||||
required this.payload,
|
||||
});
|
||||
static ABM newABM(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return ABM(
|
||||
numFragments: p.int64(0, 'number of fragments'),
|
||||
fragmentNumber: p.int64(1, 'fragment number'),
|
||||
messageID: p.int64(2, 'message ID'),
|
||||
mmsi: p.string(3, 'MMSI'),
|
||||
channel: p.string(4, 'channel'),
|
||||
vdlMessageNumber: p.int64(5, 'VDL message number'),
|
||||
payload: p.sixBitASCIIArmour(
|
||||
6, p.int64(7, 'number of padding bits'), 'payload'),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const String TypeACK = "ACK";
|
||||
|
||||
class ACK {
|
||||
int alertIdentifier;
|
||||
|
||||
ACK({ required this.alertIdentifier});
|
||||
|
||||
static ACK newACK(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return ACK(
|
||||
|
||||
alertIdentifier: p.int64(0, 'alert identifier'),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeACN = "ACN";
|
||||
|
||||
class ACN {
|
||||
Object time; // Assuming Time is a custom type
|
||||
String manufacturerMnemonicCode;
|
||||
int alertIdentifier;
|
||||
int alertInstance;
|
||||
String command;
|
||||
String state;
|
||||
|
||||
ACN(
|
||||
{required this.time,
|
||||
required this.manufacturerMnemonicCode,
|
||||
required this.alertIdentifier,
|
||||
required this.alertInstance,
|
||||
required this.command,
|
||||
required this.state});
|
||||
|
||||
static ACN newACN(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return ACN(
|
||||
time: p.time(0, 'time'),
|
||||
manufacturerMnemonicCode: p.string(1, 'manufacturer mnemonic code'),
|
||||
alertIdentifier: p.int64(2, 'alert identifier'),
|
||||
alertInstance: p.int64(3, 'alert instance'),
|
||||
command: p.enumString(4, 'alert command', [
|
||||
'A',
|
||||
'Q',
|
||||
'O',
|
||||
'S'
|
||||
]), // Assuming AlertCommandAcknowledge, AlertCommandRequestRepeatInformation, AlertCommandResponsibilityTransfer, AlertCommandSilence are represented as 'A', 'Q', 'O', 'S' respectively
|
||||
state: p.string(5, 'alarm state'),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeALA = "ALA";
|
||||
|
||||
class ALA {
|
||||
Time time;
|
||||
String systemIndicator;
|
||||
String subSystemIndicator;
|
||||
int instanceNumber;
|
||||
int type;
|
||||
String condition;
|
||||
String alarmAckState;
|
||||
String message;
|
||||
|
||||
ALA({
|
||||
required this.time,
|
||||
required this.systemIndicator,
|
||||
required this.subSystemIndicator,
|
||||
required this.instanceNumber,
|
||||
required this.type,
|
||||
required this.condition,
|
||||
required this.alarmAckState,
|
||||
required this.message,
|
||||
});
|
||||
static ALA newALA(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return ALA(
|
||||
time: p.time(0, "time"),
|
||||
systemIndicator: p.string(1, "system indicator"),
|
||||
subSystemIndicator: p.string(2, "subsystem indicator"),
|
||||
instanceNumber: p.int64(3, "instance number"),
|
||||
type: p.int64(4, "type"),
|
||||
condition: p.string(5, "condition"), // string as there could be more
|
||||
alarmAckState: p.string(
|
||||
6, "alarm acknowledgement state"), // string as there could be more
|
||||
message: p.string(7, "message"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeALC = "ALC";
|
||||
|
||||
class ALC {
|
||||
int numFragments; // 0
|
||||
int fragmentNumber; // 1
|
||||
int messageID; // 2
|
||||
int entriesNumber; // 3
|
||||
List<ALCAlertEntry> alertEntries;
|
||||
|
||||
ALC({
|
||||
required this.numFragments,
|
||||
required this.fragmentNumber,
|
||||
required this.messageID,
|
||||
required this.entriesNumber,
|
||||
required this.alertEntries,
|
||||
});
|
||||
|
||||
static ALC newALC(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
var alc = ALC(
|
||||
numFragments: p.int64(0, "number of fragments"),
|
||||
fragmentNumber: p.int64(1, "fragment number"),
|
||||
messageID: p.int64(2, "message ID"),
|
||||
entriesNumber: p.int64(3, "entries number"),
|
||||
alertEntries: []);
|
||||
|
||||
int fieldCount = s.fields.length;
|
||||
if (fieldCount == 4) {
|
||||
return alc;
|
||||
}
|
||||
if (fieldCount % 4 != 0) {
|
||||
return alc;
|
||||
}
|
||||
for (int i = 4; i < fieldCount; i = i + 4) {
|
||||
var tmp = ALCAlertEntry(
|
||||
manufacturerMnemonicCode:
|
||||
p.string(i * 4 + 4, "manufacturer mnemonic code"),
|
||||
alertIdentifier: p.int64(i * 4 + 5, "alert identifier"),
|
||||
alertInstance: p.int64(i * 4 + 6, "alert instance"),
|
||||
revisionCounter: p.int64(i * 4 + 7, "revision counter"),
|
||||
);
|
||||
alc.alertEntries.add(tmp);
|
||||
}
|
||||
return alc;
|
||||
}
|
||||
}
|
||||
|
||||
class ALCAlertEntry {
|
||||
String manufacturerMnemonicCode; // i+4
|
||||
int alertIdentifier; // i+5
|
||||
int alertInstance; // i+6
|
||||
int revisionCounter; // i+7
|
||||
|
||||
ALCAlertEntry({
|
||||
required this.manufacturerMnemonicCode,
|
||||
required this.alertIdentifier,
|
||||
required this.alertInstance,
|
||||
required this.revisionCounter,
|
||||
});
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeALF = "ALF";
|
||||
|
||||
class ALF {
|
||||
int numFragments; // 0
|
||||
int fragmentNumber; // 1
|
||||
int messageID; // 2
|
||||
Time time; // 3
|
||||
String category; // 4
|
||||
String priority; // 5
|
||||
String state; // 6
|
||||
String manufacturerMnemonicCode; // 7
|
||||
int alertIdentifier; // 8
|
||||
int alertInstance; // 9
|
||||
int revisionCounter; // 10
|
||||
int escalationCounter; // 11
|
||||
String text; // 12
|
||||
ALF(
|
||||
{required this.numFragments,
|
||||
required this.fragmentNumber,
|
||||
required this.messageID,
|
||||
required this.time,
|
||||
required this.category,
|
||||
required this.priority,
|
||||
required this.state,
|
||||
required this.manufacturerMnemonicCode,
|
||||
required this.alertIdentifier,
|
||||
required this.alertInstance,
|
||||
required this.revisionCounter,
|
||||
required this.escalationCounter,
|
||||
required this.text});
|
||||
static ALF newALF(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
|
||||
return ALF(
|
||||
numFragments: p.int64(0, "number of fragments"),
|
||||
fragmentNumber: p.int64(1, "fragment number"),
|
||||
messageID: p.int64(2, "message ID"),
|
||||
time: p.time(3, "time"),
|
||||
category: p.enumString(4, "alarm category", ["A", "B", "C"]),
|
||||
priority: p.enumString(5, "alarm priority", ["E", "A", "C", "W"]),
|
||||
state: p.enumString(6, "alarm state", ["A", "S", "O", "U", "V", "N"]),
|
||||
manufacturerMnemonicCode: p.string(7, "manufacturer mnemonic code"),
|
||||
alertIdentifier: p.int64(8, "alert identifier"),
|
||||
alertInstance: p.int64(9, "alert instance"),
|
||||
revisionCounter: p.int64(10, "revision counter"),
|
||||
escalationCounter: p.int64(11, "escalation counter"),
|
||||
text: p.string(12, "alert text"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'parser.dart';
|
||||
import 'types.dart';
|
||||
|
||||
const TypeALR = "ALR";
|
||||
|
||||
class ALR {
|
||||
Time time; // 0
|
||||
int alarmIdentifier; // 1
|
||||
String condition; // 2
|
||||
String state; // 3
|
||||
String description; // 4
|
||||
|
||||
ALR(
|
||||
{required this.time,
|
||||
required this.alarmIdentifier,
|
||||
required this.condition,
|
||||
required this.state,
|
||||
required this.description});
|
||||
static ALR newALR(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
|
||||
return ALR(
|
||||
time: p.time(0, "time"),
|
||||
alarmIdentifier: p.int64(1, "unique alarm number"),
|
||||
condition:
|
||||
p.enumString(2, "alarm condition", [StatusValid, StatusInvalid]),
|
||||
state: p.enumString(3, "alarm state", [StatusValid, StatusInvalid]),
|
||||
description: p.string(4, "description"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
import 'types.dart';
|
||||
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// Type constants
|
||||
const String TypeAPB = "APB"; // type of APB sentence for Autopilot Sentence "B"
|
||||
|
||||
// Status constants for APB
|
||||
const String StatusWarningASetAPB = "V"; // LORAN-C Blink or SNR warning
|
||||
const String StatusWarningAClearORNotUsedAPB =
|
||||
"A"; // general warning flag or other navigation systems when a reliable fix is not available
|
||||
const String StatusWarningBSetAPB =
|
||||
"A"; // Loran-C Cycle Lock warning OK or not used
|
||||
const String StatusWarningBClearAPB = "V"; // Loran-C Cycle Lock warning flag
|
||||
|
||||
// Autopilot related constants (used in APB, APA, AAM)
|
||||
const String WPStatusPerpendicularPassedA =
|
||||
"A"; // warning for passing the perpendicular of the course line of waypoint
|
||||
const String WPStatusPerpendicularPassedV =
|
||||
"V"; // indicates for not passing of the perpendicular of the course line of waypoint
|
||||
const String WPStatusArrivalCircleEnteredA =
|
||||
"A"; // warning of entering to waypoint circle
|
||||
const String WPStatusArrivalCircleEnteredV =
|
||||
"V"; // indicates of not yet entered into waypoint circle
|
||||
|
||||
class APB {
|
||||
String statusGeneralWarning;
|
||||
String statusLockWarning;
|
||||
double crossTrackErrorMagnitude;
|
||||
String directionToSteer;
|
||||
String crossTrackUnits;
|
||||
String statusArrivalCircleEntered;
|
||||
String statusPerpendicularPassed;
|
||||
double bearingOriginToDest;
|
||||
String bearingOriginToDestType;
|
||||
String destinationWaypointID;
|
||||
double bearingPresentToDest;
|
||||
String bearingPresentToDestType;
|
||||
double heading;
|
||||
String headingType;
|
||||
String ffaMode;
|
||||
|
||||
APB({
|
||||
required this.statusGeneralWarning,
|
||||
required this.statusLockWarning,
|
||||
required this.crossTrackErrorMagnitude,
|
||||
required this.directionToSteer,
|
||||
required this.crossTrackUnits,
|
||||
required this.statusArrivalCircleEntered,
|
||||
required this.statusPerpendicularPassed,
|
||||
required this.bearingOriginToDest,
|
||||
required this.bearingOriginToDestType,
|
||||
required this.destinationWaypointID,
|
||||
required this.bearingPresentToDest,
|
||||
required this.bearingPresentToDestType,
|
||||
required this.heading,
|
||||
required this.headingType,
|
||||
this.ffaMode = "",
|
||||
});
|
||||
|
||||
static APB newAPB(BaseSentence s) {
|
||||
Parser p = Parser(s);
|
||||
return APB(
|
||||
statusGeneralWarning: p.enumString(0, "general warning",
|
||||
[StatusWarningAClearORNotUsedAPB, StatusWarningASetAPB]),
|
||||
statusLockWarning: p.enumString(
|
||||
1, "lock warning", [StatusWarningBSetAPB, StatusWarningBClearAPB]),
|
||||
crossTrackErrorMagnitude: p.float64(2, "cross track error magnitude"),
|
||||
directionToSteer:
|
||||
p.enumString(3, "direction to steer", ["Left", "Right"]),
|
||||
crossTrackUnits: p.enumString(4, "cross track units", [
|
||||
DistanceUnitKilometre,
|
||||
DistanceUnitNauticalMile,
|
||||
DistanceUnitStatuteMile,
|
||||
DistanceUnitMetre
|
||||
]),
|
||||
statusArrivalCircleEntered: p.enumString(
|
||||
5,
|
||||
"arrival circle entered status",
|
||||
[WPStatusArrivalCircleEnteredA, WPStatusArrivalCircleEnteredV]),
|
||||
statusPerpendicularPassed: p.enumString(
|
||||
6,
|
||||
"perpendicularly passed status",
|
||||
[WPStatusPerpendicularPassedA,
|
||||
WPStatusPerpendicularPassedV]),
|
||||
bearingOriginToDest: p.float64(7, "origin bearing to destination"),
|
||||
bearingOriginToDestType: p.enumString(
|
||||
8,
|
||||
"origin bearing to destination type",
|
||||
["HeadingMagnetic",
|
||||
"HeadingTrue"]),
|
||||
destinationWaypointID: p.string(9, "destination waypoint ID"),
|
||||
bearingPresentToDest: p.float64(10, "present bearing to destination"),
|
||||
bearingPresentToDestType: p.enumString(
|
||||
11,
|
||||
"present bearing to destination type",
|
||||
["HeadingMagnetic",
|
||||
"HeadingTrue"]),
|
||||
heading: p.float64(12, "heading"),
|
||||
headingType:
|
||||
p.enumString(13, "heading type", ["HeadingMagnetic", "HeadingTrue"]),
|
||||
ffaMode: p.string(14,
|
||||
"FAA mode"), // not enum because some devices have proprietary "non-nmea" values
|
||||
);
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'parser.dart';
|
||||
|
||||
const TypeARC = "ARC";
|
||||
const AlertCommandAcknowledge = "A";
|
||||
// AlertCommandRequestRepeatInformation means request/repeat information
|
||||
const AlertCommandRequestRepeatInformation = "Q";
|
||||
// AlertCommandResponsibilityTransfer means responsibility transfer
|
||||
const AlertCommandResponsibilityTransfer = "O";
|
||||
// AlertCommandSilence means silence
|
||||
const AlertCommandSilence = "S";
|
||||
|
||||
class ARC {
|
||||
Time time;
|
||||
String manufacturerMnemonicCode;
|
||||
int alertIdentifier;
|
||||
int alertInstance;
|
||||
String command;
|
||||
|
||||
ARC(
|
||||
{required this.time,
|
||||
required this.manufacturerMnemonicCode,
|
||||
required this.alertIdentifier,
|
||||
required this.alertInstance,
|
||||
required this.command});
|
||||
static ARC newARC(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return ARC(
|
||||
time: p.time(0, "time"),
|
||||
manufacturerMnemonicCode: p.string(1, "manufacturer mnemonic code"),
|
||||
alertIdentifier: p.int64(2, "alert identifier"),
|
||||
alertInstance: p.int64(3, "alert instance"),
|
||||
command: p.enumString(4, "refused alert command", [
|
||||
AlertCommandAcknowledge,
|
||||
AlertCommandRequestRepeatInformation,
|
||||
AlertCommandResponsibilityTransfer,
|
||||
AlertCommandSilence
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeBBM = "BBM";
|
||||
|
||||
class BBM {
|
||||
int numFragments;
|
||||
int fragmentNumber;
|
||||
int messageID;
|
||||
String channel; // Assuming default value is an empty string
|
||||
int vDLMessageNumber;
|
||||
List<int> payload;
|
||||
BBM({
|
||||
required this.numFragments,
|
||||
required this.fragmentNumber,
|
||||
required this.messageID,
|
||||
required this.channel,
|
||||
required this.vDLMessageNumber,
|
||||
required this.payload,
|
||||
});
|
||||
static BBM newBBM(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return BBM(
|
||||
numFragments: p.int64(0, "number of fragments"),
|
||||
fragmentNumber: p.int64(1, "fragment number"),
|
||||
messageID: p.int64(2, "message ID"),
|
||||
channel: p.string(3, "channel"),
|
||||
vDLMessageNumber: p.int64(4, "VDL message number"),
|
||||
payload: p.sixBitASCIIArmour(
|
||||
5, p.int64(6, "number of padding bits"), "payload"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'types.dart';
|
||||
|
||||
const TypeBEC = "BEC";
|
||||
|
||||
class BEC {
|
||||
Time time; // UTC Time
|
||||
double latitude; // latitude of waypoint
|
||||
double longitude; // longitude of waypoint
|
||||
double bearingTrue; // true bearing in degrees
|
||||
bool bearingTrueValid; // is unit of true bearing valid
|
||||
double bearingMagnetic; // magnetic bearing in degrees
|
||||
bool bearingMagneticValid; // is unit of magnetic bearing valid
|
||||
double distanceNauticalMiles; // distance to waypoint in nautical miles
|
||||
bool
|
||||
distanceNauticalMilesValid; // is unit of distance to waypoint nautical miles valid
|
||||
String destinationWaypointID; // destination waypoint ID
|
||||
BEC({
|
||||
required this.time,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
required this.bearingTrue,
|
||||
required this.bearingTrueValid,
|
||||
required this.bearingMagnetic,
|
||||
required this.bearingMagneticValid,
|
||||
required this.distanceNauticalMiles,
|
||||
required this.distanceNauticalMilesValid,
|
||||
required this.destinationWaypointID,
|
||||
});
|
||||
static BEC newBEC(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return BEC(
|
||||
time: p.time(0, "time"),
|
||||
latitude: p.latLong(1, 2, "latitude"),
|
||||
longitude: p.latLong(3, 4, "longitude"),
|
||||
bearingTrue: p.float64(5, "true bearing"),
|
||||
bearingTrueValid:
|
||||
p.enumString(6, "true bearing unit valid", [BearingTrue]) ==
|
||||
BearingTrue,
|
||||
bearingMagnetic: p.float64(7, "magnetic bearing"),
|
||||
bearingMagneticValid:
|
||||
p.enumString(8, "magnetic bearing unit valid", [BearingMagnetic]) ==
|
||||
BearingMagnetic,
|
||||
distanceNauticalMiles:
|
||||
p.float64(9, "distance to waypoint is nautical miles"),
|
||||
distanceNauticalMilesValid: p.enumString(
|
||||
10,
|
||||
"is distance to waypoint nautical miles valid",
|
||||
[DistanceUnitNauticalMile]) ==
|
||||
DistanceUnitNauticalMile,
|
||||
destinationWaypointID: p.string(11, "destination waypoint ID"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeBESTPOSA = "BESTPOSA";
|
||||
|
||||
class BESTPOSA {
|
||||
double lat;
|
||||
double lon;
|
||||
double hgt;
|
||||
String fixQuality;
|
||||
double undulation;
|
||||
String datum;
|
||||
double diffAge;
|
||||
double solAge;
|
||||
double hdop;
|
||||
double vdop;
|
||||
double adop;
|
||||
String baseId;
|
||||
BESTPOSA(
|
||||
{required this.lat,
|
||||
required this.lon,
|
||||
required this.hgt,
|
||||
required this.undulation,
|
||||
required this.fixQuality,
|
||||
required this.datum,
|
||||
required this.diffAge,
|
||||
required this.solAge,
|
||||
required this.hdop,
|
||||
required this.vdop,
|
||||
required this.adop,
|
||||
required this.baseId});
|
||||
static BESTPOSA newBESTPOSA(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return BESTPOSA(
|
||||
fixQuality: p.string(9, "fix_quality"),
|
||||
lat: p.float64(10, "lat"),
|
||||
lon: p.float64(11, "lon"),
|
||||
hgt: p.float64(12, "hgt"),
|
||||
undulation: p.float64(13, "undulation"),
|
||||
datum: p.string(14, "datum"),
|
||||
vdop: p.float64(15, "vdop"),
|
||||
hdop: p.float64(16, "hdop"),
|
||||
adop: p.float64(17, "adop"),
|
||||
baseId: p.string(18, "baseId"),
|
||||
diffAge: p.float64(19, "diff_age"),
|
||||
solAge: p.float64(20, "sol_age"));
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'types.dart';
|
||||
|
||||
const TypeBOD = "BOD";
|
||||
|
||||
class BOD {
|
||||
double bearingTrue; // true bearing in degrees
|
||||
String bearingTrueType; // is type of true bearing
|
||||
double bearingMagnetic; // magnetic bearing in degrees
|
||||
String bearingMagneticType; // is type of magnetic bearing
|
||||
String destinationWaypointID; // destination waypoint ID
|
||||
String originWaypointID; // origin waypoint ID
|
||||
BOD(
|
||||
{required this.bearingTrue,
|
||||
required this.bearingTrueType,
|
||||
required this.bearingMagnetic,
|
||||
required this.bearingMagneticType,
|
||||
required this.destinationWaypointID,
|
||||
required this.originWaypointID});
|
||||
|
||||
static BOD newBOD(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
|
||||
var bod = BOD(
|
||||
bearingTrue: p.float64(0, "true bearing"),
|
||||
bearingTrueType: p.enumString(1, "true bearing type", [BearingTrue]),
|
||||
bearingMagnetic: p.float64(2, "magnetic bearing"),
|
||||
bearingMagneticType:
|
||||
p.enumString(3, "magnetic bearing type", [BearingMagnetic]),
|
||||
destinationWaypointID: p.string(4, "destination waypoint ID"),
|
||||
originWaypointID: "",
|
||||
);
|
||||
if (s.fields.length > 5) {
|
||||
bod.originWaypointID = p.string(5, "origin waypoint ID");
|
||||
}
|
||||
return bod;
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'types.dart';
|
||||
|
||||
const TypeBWC = "BWC";
|
||||
|
||||
class BWC {
|
||||
Time time; // UTC Time
|
||||
double latitude; // latitude of waypoint
|
||||
double longitude; // longitude of waypoint
|
||||
double bearingTrue; // true bearing in degrees
|
||||
String bearingTrueType; // is type of true bearing
|
||||
double bearingMagnetic; // magnetic bearing in degrees
|
||||
String bearingMagneticType; // is type of magnetic bearing
|
||||
double distanceNauticalMiles; // distance to waypoint in nautical miles
|
||||
String
|
||||
distanceNauticalMilesUnit; // is unit of distance to waypoint nautical miles
|
||||
String destinationWaypointID; // destination waypoint ID
|
||||
String ffaMode; // FAA mode indicator (filled in NMEA 2.3 and later)
|
||||
|
||||
BWC({
|
||||
required this.time,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
required this.bearingTrue,
|
||||
required this.bearingTrueType,
|
||||
required this.bearingMagnetic,
|
||||
required this.bearingMagneticType,
|
||||
required this.distanceNauticalMiles,
|
||||
required this.distanceNauticalMilesUnit,
|
||||
required this.destinationWaypointID,
|
||||
required this.ffaMode,
|
||||
});
|
||||
static BWC newBWC(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
var bwc = BWC(
|
||||
time: p.time(0, "time"),
|
||||
latitude: p.latLong(1, 2, "latitude"),
|
||||
longitude: p.latLong(3, 4, "longitude"),
|
||||
bearingTrue: p.float64(5, "true bearing"),
|
||||
bearingTrueType: p.enumString(6, "true bearing type", [BearingTrue]),
|
||||
bearingMagnetic: p.float64(7, "magnetic bearing"),
|
||||
bearingMagneticType:
|
||||
p.enumString(8, "magnetic bearing type", [BearingMagnetic]),
|
||||
distanceNauticalMiles:
|
||||
p.float64(9, "distance to waypoint is nautical miles"),
|
||||
distanceNauticalMilesUnit: p.enumString(
|
||||
10,
|
||||
"is distance to waypoint nautical miles unit",
|
||||
[DistanceUnitNauticalMile]),
|
||||
destinationWaypointID: p.string(11, "destination waypoint ID"),
|
||||
ffaMode: "");
|
||||
if (s.fields.length > 12) {
|
||||
bwc.ffaMode = p.string(12, "FFA mode");
|
||||
}
|
||||
return bwc;
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'parser.dart';
|
||||
import 'types.dart';
|
||||
|
||||
const TypeBWR = "BWR";
|
||||
|
||||
class BWR {
|
||||
Time time; // UTC Time
|
||||
double latitude; // latitude of waypoint
|
||||
double longitude; // longitude of waypoint
|
||||
double bearingTrue; // true bearing in degrees
|
||||
String bearingTrueType; // is type of true bearing
|
||||
double bearingMagnetic; // magnetic bearing in degrees
|
||||
String bearingMagneticType; // is type of magnetic bearing
|
||||
double distanceNauticalMiles; // distance to waypoint in nautical miles
|
||||
String
|
||||
distanceNauticalMilesUnit; // is unit of distance to waypoint nautical miles
|
||||
String destinationWaypointID; // destination waypoint ID
|
||||
String ffaMode; // FAA mode indicator (filled in NMEA 2.3 and later)
|
||||
BWR({
|
||||
required this.time,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
required this.bearingTrue,
|
||||
required this.bearingTrueType,
|
||||
required this.bearingMagnetic,
|
||||
required this.bearingMagneticType,
|
||||
required this.distanceNauticalMiles,
|
||||
required this.distanceNauticalMilesUnit,
|
||||
required this.destinationWaypointID,
|
||||
required this.ffaMode,
|
||||
});
|
||||
static BWR newBWR(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
var bwr = BWR(
|
||||
time: p.time(0, "time"),
|
||||
latitude: p.latLong(1, 2, "latitude"),
|
||||
longitude: p.latLong(3, 4, "longitude"),
|
||||
bearingTrue: p.float64(5, "true bearing"),
|
||||
bearingTrueType: p.enumString(6, "true bearing type", [BearingTrue]),
|
||||
bearingMagnetic: p.float64(7, "magnetic bearing"),
|
||||
bearingMagneticType:
|
||||
p.enumString(8, "magnetic bearing type", [BearingMagnetic]),
|
||||
distanceNauticalMiles:
|
||||
p.float64(9, "distance to waypoint is nautical miles"),
|
||||
distanceNauticalMilesUnit: p.enumString(
|
||||
10,
|
||||
"is distance to waypoint nautical miles unit",
|
||||
[DistanceUnitNauticalMile]),
|
||||
destinationWaypointID: p.string(11, "destination waypoint ID"),
|
||||
ffaMode: "");
|
||||
if (s.fields.length > 12) {
|
||||
bwr.ffaMode = p.string(12,
|
||||
"FAA mode"); // not enum because some devices have proprietary "non-nmea" values
|
||||
}
|
||||
return bwr;
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'types.dart';
|
||||
|
||||
const TypeBWW = "BWW";
|
||||
|
||||
class BWW {
|
||||
double bearingTrue; // true bearing in degrees
|
||||
String bearingTrueType; // is type of true bearing
|
||||
double bearingMagnetic; // magnetic bearing in degrees
|
||||
String bearingMagneticType; // is type of magnetic bearing
|
||||
String destinationWaypointID; // destination waypoint ID
|
||||
String originWaypointID; // origin waypoint ID
|
||||
BWW({
|
||||
required this.bearingTrue,
|
||||
required this.bearingTrueType,
|
||||
required this.bearingMagnetic,
|
||||
required this.bearingMagneticType,
|
||||
required this.destinationWaypointID,
|
||||
required this.originWaypointID,
|
||||
});
|
||||
static BWW newBWW(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return BWW(
|
||||
bearingTrue: p.float64(0, "true bearing"),
|
||||
bearingTrueType: p.enumString(1, "true bearing type", [BearingTrue]),
|
||||
bearingMagnetic: p.float64(2, "magnetic bearing"),
|
||||
bearingMagneticType:
|
||||
p.enumString(3, "magnetic bearing type", [BearingMagnetic]),
|
||||
destinationWaypointID: p.string(4, "destination waypoint ID"),
|
||||
originWaypointID: p.string(5, "origin waypoint ID"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'types.dart';
|
||||
|
||||
const TypeDBK = "DBK";
|
||||
|
||||
class DBK {
|
||||
double depthFeet; // Depth, feet
|
||||
String depthFeetUnit; // f = feet
|
||||
double depthMeters; // Depth, meters
|
||||
String depthMetersUnit; // M = meters
|
||||
double depthFathoms; // Depth, Fathoms
|
||||
String depthFathomsUnit; // F = Fathoms
|
||||
DBK({
|
||||
required this.depthFeet,
|
||||
required this.depthFeetUnit,
|
||||
required this.depthMeters,
|
||||
required this.depthMetersUnit,
|
||||
required this.depthFathoms,
|
||||
required this.depthFathomsUnit,
|
||||
});
|
||||
static DBK newDBK(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return DBK(
|
||||
depthFeet: p.float64(0, "depth feet"),
|
||||
depthFeetUnit: p.enumString(1, "depth feet unit", [DistanceUnitFeet]),
|
||||
depthMeters: p.float64(2, "depth meters"),
|
||||
depthMetersUnit:
|
||||
p.enumString(3, "depth meters unit", [DistanceUnitMetre]),
|
||||
depthFathoms: p.float64(4, "depth fathom"),
|
||||
depthFathomsUnit:
|
||||
p.enumString(5, "depth fathom unit", [DistanceUnitFathom]),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'types.dart';
|
||||
|
||||
var TypeDBS = "DBS";
|
||||
|
||||
class DBS {
|
||||
double depthFeet; // Depth, feet
|
||||
String depthFeetUnit; // f = feet
|
||||
double depthMeters; // Depth, meters
|
||||
String depthMeterUnit; // M = meters
|
||||
double depthFathoms; // Depth, Fathoms
|
||||
String depthFathomUnit; // F = Fathoms
|
||||
|
||||
DBS({
|
||||
required this.depthFathomUnit,
|
||||
required this.depthFathoms,
|
||||
required this.depthFeet,
|
||||
required this.depthFeetUnit,
|
||||
required this.depthMeterUnit,
|
||||
required this.depthMeters,
|
||||
});
|
||||
static DBS newDBS(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return DBS(
|
||||
depthFeet: p.float64(0, "depth feet"),
|
||||
depthFeetUnit: p.enumString(1, "depth feet unit", [DistanceUnitFeet]),
|
||||
depthMeters: p.float64(2, "depth meters"),
|
||||
depthMeterUnit: p.enumString(3, "depth feet unit", [DistanceUnitMetre]),
|
||||
depthFathoms: p.float64(4, "depth fathoms"),
|
||||
depthFathomUnit:
|
||||
p.enumString(5, "depth fathom unit", [DistanceUnitFathom]),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeDBT = "DBT";
|
||||
|
||||
class DBT {
|
||||
double depthFeet;
|
||||
double depthMeters;
|
||||
double depthFathoms;
|
||||
DBT({
|
||||
required this.depthFeet,
|
||||
required this.depthMeters,
|
||||
required this.depthFathoms,
|
||||
});
|
||||
static DBT newDBT(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return DBT(
|
||||
depthFeet: p.float64(0, "depth_feet"),
|
||||
depthMeters: p.float64(2, "depth_meters"),
|
||||
depthFathoms: p.float64(4, "depth_fathoms"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'gga.dart';
|
||||
import 'gsa.dart';
|
||||
|
||||
import 'gll.dart';
|
||||
import 'gns.dart';
|
||||
import 'gsv.dart';
|
||||
import 'hdt.dart';
|
||||
import 'rmc.dart';
|
||||
import 'vtg.dart';
|
||||
import 'zda.dart';
|
||||
|
||||
const PrefixGNGNS = "GNGNS";
|
||||
const PrefixGPGGA = "GPGGA";
|
||||
const PrefixGPGLL = "GPGLL";
|
||||
const PrefixGPGSA = "GPGSA";
|
||||
const PrefixGPRMC = "GPRMC";
|
||||
const PrefixPGRME = "PGRME";
|
||||
const PrefixGLGSV = "GLGSV";
|
||||
const PrefixGNGGA = "GNGGA";
|
||||
const PrefixGNRMC = "GNRMC";
|
||||
const PrefixGPGSV = "GPGSV";
|
||||
const PrefixGPHDT = "GPHDT";
|
||||
const PrefixGPVTG = "GPVTG";
|
||||
const PrefixGPZDA = "GPZDA";
|
||||
|
||||
// Deprecated: Use GSV instead
|
||||
typedef GLGSV = GSV;
|
||||
|
||||
// Deprecated: Use GSVInfo instead
|
||||
typedef GLGSVInfo = GSVInfo;
|
||||
|
||||
// Deprecated: Use GGA instead
|
||||
typedef GNGGA = GGA;
|
||||
|
||||
// Deprecated: Use GNS instead
|
||||
typedef GNGNS = GNS;
|
||||
|
||||
// Deprecated: Use RCM instead
|
||||
typedef GNRMC = RMC;
|
||||
|
||||
// Deprecated: Use GGA instead
|
||||
typedef GPGGA = GGA;
|
||||
|
||||
// Deprecated: Use GLL instead
|
||||
typedef GPGLL = GLL;
|
||||
|
||||
// Deprecated: Use GSA instead
|
||||
typedef GPGSA = GSA;
|
||||
|
||||
// Deprecated: Use GSV instead
|
||||
typedef GPGSV = GSV;
|
||||
|
||||
// Deprecated: Use GSVInfo instead
|
||||
typedef GPGSVInfo = GSVInfo;
|
||||
|
||||
// Deprecated: Use HDT instead
|
||||
typedef GPHDT = HDT;
|
||||
|
||||
// Deprecated: Use RMC instead
|
||||
typedef GPRMC = RMC;
|
||||
|
||||
// Deprecated: Use VTG instead
|
||||
typedef GPVTG = VTG;
|
||||
|
||||
// Deprecated: Use ZDA instead
|
||||
typedef GPZDA = ZDA;
|
@ -1,66 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeDOR = "DOR";
|
||||
|
||||
// TypeSingleDoorDOR is type for single door related event
|
||||
const TypeSingleDoorDOR = "E";
|
||||
// TypeFaultDOR is type for fault with door
|
||||
const TypeFaultDOR = "F";
|
||||
// TypeSectionDOR is type for section of doors related event
|
||||
const TypeSectionDOR = "S";
|
||||
|
||||
// DoorStatusOpenDOR is status for open door
|
||||
const DoorStatusOpenDOR = "O";
|
||||
// DoorStatusClosedDOR is status for closed door
|
||||
const DoorStatusClosedDOR = "C";
|
||||
// DoorStatusFaultDOR is status for fault with door
|
||||
const DoorStatusFaultDOR = "X";
|
||||
|
||||
// SwitchSettingHarbourModeDOR is setting for Harbour mode (allowed open)
|
||||
const SwitchSettingHarbourModeDOR = "O";
|
||||
// SwitchSettingSeaModeDOR is setting for Sea mode (ordered closed)
|
||||
const SwitchSettingSeaModeDOR = "C";
|
||||
|
||||
class DOR {
|
||||
String type;
|
||||
Time time;
|
||||
String systemIndicator;
|
||||
String divisionIndicator1;
|
||||
int divisionIndicator2;
|
||||
int doorNumberOrCount;
|
||||
String doorStatus;
|
||||
String switchSetting;
|
||||
String message;
|
||||
|
||||
DOR(
|
||||
{required this.type,
|
||||
required this.time,
|
||||
required this.systemIndicator,
|
||||
required this.divisionIndicator1,
|
||||
required this.divisionIndicator2,
|
||||
required this.doorNumberOrCount,
|
||||
required this.doorStatus,
|
||||
required this.switchSetting,
|
||||
required this.message});
|
||||
|
||||
static DOR newDOR(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return DOR(
|
||||
type: p.enumString(
|
||||
0, "message type", [TypeSingleDoorDOR, TypeFaultDOR, TypeSectionDOR]),
|
||||
time: p.time(1, "time"),
|
||||
systemIndicator: p.string(2, "system indicator"),
|
||||
divisionIndicator1: p.string(3, "division indicator 1"),
|
||||
divisionIndicator2: p.int64(4, "division indicator 2"),
|
||||
doorNumberOrCount: p.int64(5, "door number or count"),
|
||||
doorStatus: p.enumString(6, "door state",
|
||||
[DoorStatusOpenDOR, DoorStatusClosedDOR, DoorStatusFaultDOR]),
|
||||
switchSetting: p.enumString(7, "switch setting mode",
|
||||
[SwitchSettingHarbourModeDOR, SwitchSettingSeaModeDOR]),
|
||||
message: p.string(8, "message"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeDPT = "DPT";
|
||||
|
||||
class DPT {
|
||||
double depth;
|
||||
double offset;
|
||||
double? rangeScale; // Optional property
|
||||
|
||||
DPT({required this.depth, required this.offset, this.rangeScale});
|
||||
static DPT newDPT(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
double rangeScale = 0;
|
||||
if (s.fields.length > 2) {
|
||||
rangeScale = p.float64(2, "range scale");
|
||||
}
|
||||
return DPT(
|
||||
depth: p.float64(0, "depth"),
|
||||
offset: p.float64(1, "offset"),
|
||||
rangeScale: rangeScale);
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
// TypeDSC type of DSC sentence for Digital Selective Calling Information
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'sentence.dart';
|
||||
import 'parser.dart';
|
||||
|
||||
const TypeDSC = "DSC";
|
||||
|
||||
// AcknowledgementRequestDSC is type for Acknowledge request
|
||||
const AcknowledgementRequestDSC = "R";
|
||||
// AcknowledgementDSC is type for Acknowledgement
|
||||
const AcknowledgementDSC = "B";
|
||||
// AcknowledgementNeitherDSC is type for Neither (end of sequence)
|
||||
const AcknowledgementNeitherDSC = "S";
|
||||
|
||||
class DSC {
|
||||
String formatSpecifier;
|
||||
String address;
|
||||
String category;
|
||||
String distressCauseOrTeleCommand1;
|
||||
String commandTypeOrTeleCommand2;
|
||||
String positionOrCanal;
|
||||
String timeOrTelephoneNumber;
|
||||
String mmsi;
|
||||
String distressCause;
|
||||
String acknowledgement;
|
||||
String expansionIndicator;
|
||||
DSC({
|
||||
required this.formatSpecifier,
|
||||
required this.address,
|
||||
required this.category,
|
||||
required this.distressCauseOrTeleCommand1,
|
||||
required this.commandTypeOrTeleCommand2,
|
||||
required this.positionOrCanal,
|
||||
required this.timeOrTelephoneNumber,
|
||||
required this.mmsi,
|
||||
required this.distressCause,
|
||||
required this.acknowledgement,
|
||||
required this.expansionIndicator,
|
||||
});
|
||||
static DSC newDSC(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return DSC(
|
||||
formatSpecifier: p.string(0, "format specifier"),
|
||||
address: p.string(1, "address"),
|
||||
category: p.string(2, "category"),
|
||||
distressCauseOrTeleCommand1:
|
||||
p.string(3, "cause of the distress or first telecommand"),
|
||||
commandTypeOrTeleCommand2:
|
||||
p.string(4, "type of communication or second telecommand"),
|
||||
positionOrCanal: p.string(5, "position or canal"),
|
||||
timeOrTelephoneNumber: p.string(6, "time or telephone"),
|
||||
mmsi: p.string(7, "MMSI"),
|
||||
distressCause: p.string(8, "distress cause"),
|
||||
acknowledgement: p.enumString(9, "acknowledgement", [
|
||||
AcknowledgementRequestDSC,
|
||||
" $AcknowledgementRequestDSC",
|
||||
AcknowledgementDSC,
|
||||
" $AcknowledgementDSC",
|
||||
AcknowledgementNeitherDSC,
|
||||
" $AcknowledgementNeitherDSC",
|
||||
]).trim(),
|
||||
expansionIndicator: p.string(10, "expansion indicator"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
const TypeDSE = "DSE";
|
||||
|
||||
// AcknowledgementAutomaticDSE is type for automatic
|
||||
const AcknowledgementAutomaticDSE = "A";
|
||||
// AcknowledgementRequestDSE is type for request
|
||||
const AcknowledgementRequestDSE = "R";
|
||||
// AcknowledgementQueryDSE is type for query
|
||||
const AcknowledgementQueryDSE = "Q";
|
||||
|
||||
class DSE {
|
||||
int totalNumber;
|
||||
int number;
|
||||
String acknowledgement;
|
||||
String mmsi;
|
||||
List<DSEDataSet> dataSets;
|
||||
|
||||
DSE(
|
||||
{required this.totalNumber,
|
||||
required this.number,
|
||||
required this.acknowledgement,
|
||||
required this.mmsi,
|
||||
required this.dataSets});
|
||||
}
|
||||
|
||||
class DSEDataSet {
|
||||
String code;
|
||||
String data;
|
||||
|
||||
DSEDataSet(this.code, this.data);
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeDTM = "DTM";
|
||||
|
||||
class DTM {
|
||||
String localDatumCode;
|
||||
String localDatumSubcode;
|
||||
double latitudeOffsetMinute;
|
||||
double longitudeOffsetMinute;
|
||||
double altitudeOffsetMeters;
|
||||
String datumName;
|
||||
|
||||
DTM(
|
||||
{required this.localDatumCode,
|
||||
required this.localDatumSubcode,
|
||||
required this.latitudeOffsetMinute,
|
||||
required this.longitudeOffsetMinute,
|
||||
required this.altitudeOffsetMeters,
|
||||
required this.datumName});
|
||||
static newDTM(BaseSentence s) {}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeGGA = "GGA";
|
||||
// Invalid fix quality.
|
||||
const Invalid = "0";
|
||||
// GPS fix quality
|
||||
const GPS = "1";
|
||||
// DGPS fix quality
|
||||
const DGPS = "2";
|
||||
// PPS fix
|
||||
const PPS = "3";
|
||||
// RTK real time kinematic fix
|
||||
const RTK = "4";
|
||||
// FRTK float RTK fix
|
||||
const FRTK = "5";
|
||||
// EST estimated fix.
|
||||
const EST = "6";
|
||||
|
||||
class GGA {
|
||||
// Time of fix.
|
||||
Time time;
|
||||
// Latitude.
|
||||
double latitude;
|
||||
// Longitude.
|
||||
double longitude;
|
||||
// Quality of fix.
|
||||
int quality;
|
||||
// Number of satellites in use.
|
||||
int numSatellites;
|
||||
// Horizontal dilution of precision.
|
||||
double hdop;
|
||||
// Altitude.
|
||||
double altitude;
|
||||
// Geoidal separation.
|
||||
double separation;
|
||||
// Age of differential GPD data.
|
||||
String dgpsAge;
|
||||
// DGPS reference station ID.
|
||||
String dgpsId;
|
||||
|
||||
// Constructor
|
||||
GGA(
|
||||
{required this.time,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
required this.quality,
|
||||
required this.numSatellites,
|
||||
required this.hdop,
|
||||
required this.altitude,
|
||||
required this.separation,
|
||||
required this.dgpsAge,
|
||||
required this.dgpsId});
|
||||
|
||||
// Factory method to create GGA from parser
|
||||
static GGA newGGA(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return GGA(
|
||||
time: p.time(0, "time"),
|
||||
latitude: p.latLong(1, 2, "latitude"),
|
||||
longitude: p.latLong(3, 4, "longitude"),
|
||||
// fixQuality: p.enumString(
|
||||
// 5, "fix quality", [Invalid, GPS, DGPS, PPS, RTK, FRTK, EST]),
|
||||
quality: p.int64(5, "quality"),
|
||||
numSatellites: p.int64(6, "number of satellites"),
|
||||
hdop: p.float64(7, "hdop"),
|
||||
altitude: p.float64(8, "altitude"),
|
||||
separation: p.float64(10, "separation"),
|
||||
dgpsAge: p.string(12, "dgps age"),
|
||||
dgpsId: p.string(13, "dgps id"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'parser.dart';
|
||||
|
||||
const TypeGLL = "GLL";
|
||||
// ValidGLL character
|
||||
const ValidGLL = "A";
|
||||
// InvalidGLL character
|
||||
const InvalidGLL = "V";
|
||||
|
||||
class GLL {
|
||||
double latitude;
|
||||
double longitude;
|
||||
Time time;
|
||||
String validity;
|
||||
String ffaMode;
|
||||
|
||||
GLL(
|
||||
{required this.latitude,
|
||||
required this.longitude,
|
||||
required this.time,
|
||||
required this.validity,
|
||||
required this.ffaMode});
|
||||
static GLL newGLL(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
var ffaMode = "";
|
||||
if (s.fields.length > 6) {
|
||||
ffaMode = p.string(6, "FAA mode");
|
||||
}
|
||||
return GLL(
|
||||
latitude: p.latLong(0, 1, "latitude"),
|
||||
longitude: p.latLong(2, 3, "longitude"),
|
||||
time: p.time(4, "time"),
|
||||
validity: p.enumString(5, "validity", [ValidGLL, InvalidGLL]),
|
||||
ffaMode: ffaMode);
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'parser.dart';
|
||||
import 'types.dart';
|
||||
|
||||
const TypeGNS = "GNS";
|
||||
// NoFixGNS Character
|
||||
const NoFixGNS = "N";
|
||||
// AutonomousGNS Character
|
||||
const AutonomousGNS = "A";
|
||||
// DifferentialGNS Character
|
||||
const DifferentialGNS = "D";
|
||||
// PreciseGNS Character
|
||||
const PreciseGNS = "P";
|
||||
// RealTimeKinematicGNS Character
|
||||
const RealTimeKinematicGNS = "R";
|
||||
// FloatRTKGNS RealTime Kinematic Character
|
||||
const FloatRTKGNS = "F";
|
||||
// EstimatedGNS Fix Character
|
||||
const EstimatedGNS = "E";
|
||||
// ManualGNS Fix Character
|
||||
const ManualGNS = "M";
|
||||
// SimulatorGNS Character
|
||||
const SimulatorGNS = "S";
|
||||
|
||||
class GNS {
|
||||
Time time;
|
||||
double latitude;
|
||||
double longitude;
|
||||
List<String> mode;
|
||||
int svs;
|
||||
double hdop;
|
||||
double altitude;
|
||||
double separation;
|
||||
double age;
|
||||
int station;
|
||||
String navStatus;
|
||||
|
||||
GNS(
|
||||
{required this.time,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
required this.mode,
|
||||
required this.svs,
|
||||
required this.hdop,
|
||||
required this.altitude,
|
||||
required this.separation,
|
||||
required this.age,
|
||||
required this.station,
|
||||
required this.navStatus});
|
||||
static GNS newGNS(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
var gns = GNS(
|
||||
time: p.time(0, "time"),
|
||||
latitude: p.latLong(1, 2, "latitude"),
|
||||
longitude: p.latLong(3, 4, "longitude"),
|
||||
mode: p.enumChars(5, "mode", [
|
||||
NoFixGNS,
|
||||
AutonomousGNS,
|
||||
DifferentialGNS,
|
||||
PreciseGNS,
|
||||
RealTimeKinematicGNS,
|
||||
FloatRTKGNS,
|
||||
EstimatedGNS,
|
||||
ManualGNS,
|
||||
SimulatorGNS
|
||||
]),
|
||||
svs: p.int64(6, "SVs"),
|
||||
hdop: p.float64(7, "HDOP"),
|
||||
altitude: p.float64(8, "altitude"),
|
||||
separation: p.float64(9, "separation"),
|
||||
age: p.float64(10, "age"),
|
||||
station: p.int64(11, "station"),
|
||||
navStatus: '',
|
||||
);
|
||||
if (s.fields.length >= 13) {
|
||||
gns.navStatus = p.enumString(12, "navigation status", [
|
||||
NavStatusSafe,
|
||||
NavStatusCaution,
|
||||
NavStatusUnsafe,
|
||||
NavStatusNotValid,
|
||||
]);
|
||||
}
|
||||
return gns;
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeGSA = "GSA";
|
||||
// Auto - Field 1, auto or manual fix.
|
||||
const Auto = "A";
|
||||
// Manual - Field 1, auto or manual fix.
|
||||
const Manual = "M";
|
||||
// FixNone - Field 2, fix type.
|
||||
const FixNone = "1";
|
||||
// Fix2D - Field 2, fix type.
|
||||
const Fix2D = "2";
|
||||
// Fix3D - Field 2, fix type.
|
||||
const Fix3D = "3";
|
||||
|
||||
class GSA {
|
||||
String mode;
|
||||
String fixType;
|
||||
List<String> sv;
|
||||
double pdop;
|
||||
double hdop;
|
||||
double vdop;
|
||||
int systemID;
|
||||
|
||||
GSA(
|
||||
{required this.mode,
|
||||
required this.fixType,
|
||||
required this.sv,
|
||||
required this.pdop,
|
||||
required this.hdop,
|
||||
required this.vdop,
|
||||
required this.systemID});
|
||||
static GSA newGSA(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
var m = GSA(
|
||||
mode: p.enumString(0, "selection mode", [Auto, Manual]),
|
||||
fixType: p.enumString(1, "fix type", [FixNone, Fix2D, Fix3D]),
|
||||
sv: [],
|
||||
pdop: 0,
|
||||
hdop: 0,
|
||||
vdop: 0,
|
||||
systemID: 0,
|
||||
);
|
||||
for (int i = 2; i < 14; i++) {
|
||||
var v = p.string(i, "satellite in view");
|
||||
if (v != "") {
|
||||
m.sv.add(v);
|
||||
}
|
||||
}
|
||||
|
||||
m.pdop = p.float64(14, "pdop");
|
||||
m.hdop = p.float64(15, "hdop");
|
||||
m.vdop = p.float64(16, "vdop");
|
||||
if (s.fields.length > 17) {
|
||||
m.systemID = p.int64(17, "system ID");
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeGSV = "GSV";
|
||||
|
||||
class GSV {
|
||||
int totalMessages;
|
||||
int messageNumber;
|
||||
int numberSVsInView;
|
||||
List<GSVInfo> info;
|
||||
int systemID;
|
||||
int signalID;
|
||||
GSV(
|
||||
{required this.totalMessages,
|
||||
required this.messageNumber,
|
||||
required this.numberSVsInView,
|
||||
required this.info,
|
||||
required this.systemID,
|
||||
required this.signalID});
|
||||
static GSV newGSV(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
var gsv = GSV(
|
||||
totalMessages: p.int64(0, "total number of messages"),
|
||||
messageNumber: p.int64(1, "message number"),
|
||||
numberSVsInView: p.int64(2, "number of SVs in view"),
|
||||
info: [],
|
||||
systemID: 0,
|
||||
signalID: 0,
|
||||
);
|
||||
|
||||
int i = 0;
|
||||
for (; i < 4; i++) {
|
||||
if (6 + i * 4 >= s.fields.length) {
|
||||
break;
|
||||
}
|
||||
gsv.info.add(GSVInfo(
|
||||
svprnNumber: p.int64(3 + i * 4, "SV prn number"),
|
||||
elevation: p.int64(4 + i * 4, "elevation"),
|
||||
azimuth: p.int64(5 + i * 4, "azimuth"),
|
||||
snr: p.int64(6 + i * 4, "SNR"),
|
||||
));
|
||||
}
|
||||
var idxSID = (6 + (i - 1) * 4) + 1;
|
||||
if (s.fields.length == idxSID + 2) {
|
||||
gsv.systemID = p.int64(idxSID, "system ID");
|
||||
gsv.signalID = p.int64(idxSID + 1, "signal ID");
|
||||
}
|
||||
if (s.fields.length == idxSID + 1) {
|
||||
gsv.signalID = p.int64(idxSID, "signal ID");
|
||||
}
|
||||
|
||||
return gsv;
|
||||
}
|
||||
}
|
||||
|
||||
class GSVInfo {
|
||||
int svprnNumber;
|
||||
int elevation;
|
||||
int azimuth;
|
||||
int snr;
|
||||
|
||||
GSVInfo(
|
||||
{required this.svprnNumber,
|
||||
required this.elevation,
|
||||
required this.azimuth,
|
||||
required this.snr});
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeHDT = "HDT";
|
||||
|
||||
class HDT {
|
||||
double heading;
|
||||
bool isTrue;
|
||||
|
||||
HDT({required this.heading, required this.isTrue});
|
||||
static HDT newHDT(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return HDT(
|
||||
heading: p.float64(0, "heading"),
|
||||
isTrue: p.enumString(1, "true", ["T"]) == "T",
|
||||
);
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeHEADINGA = "HEADINGA";
|
||||
|
||||
class HEADINGA {
|
||||
double length;
|
||||
double heading;
|
||||
double pitch;
|
||||
int numberSv;
|
||||
int numberSa;
|
||||
HEADINGA(
|
||||
{required this.length,
|
||||
required this.heading,
|
||||
required this.pitch,
|
||||
required this.numberSv,
|
||||
required this.numberSa});
|
||||
// HEADINGA({required this.heading, required this.isTrue});
|
||||
static HEADINGA newHEADINGA(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return HEADINGA(
|
||||
length: p.float64(10, "length"),
|
||||
heading: p.float64(11, "heading"),
|
||||
pitch: p.float64(12, "pitch"),
|
||||
numberSv: p.int64(17, "number of satellites"),
|
||||
numberSa: p.int64(18, "number of satellites used"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,262 +0,0 @@
|
||||
|
||||
|
||||
import 'sentence.dart';
|
||||
import 'types.dart';
|
||||
|
||||
class Parser {
|
||||
BaseSentence baseSentence;
|
||||
String? _err;
|
||||
|
||||
Parser(this.baseSentence);
|
||||
|
||||
void assertType(String typ) {
|
||||
if (baseSentence.type != typ) {
|
||||
setErr('type', baseSentence.type);
|
||||
}
|
||||
}
|
||||
|
||||
String? err() {
|
||||
return _err;
|
||||
}
|
||||
|
||||
void setErr(String context, String value) {
|
||||
_err ??= 'nmea: ${baseSentence.prefix()} invalid $context: $value';
|
||||
}
|
||||
|
||||
String string(int i, String context) {
|
||||
if (_err != null) {
|
||||
return '';
|
||||
}
|
||||
if (i < 0 || i >= baseSentence.fields.length) {
|
||||
setErr(context, 'index out of range');
|
||||
return '';
|
||||
}
|
||||
return baseSentence.fields[i];
|
||||
}
|
||||
|
||||
List<String> listString(int from, String context) {
|
||||
if (_err != null) {
|
||||
return [];
|
||||
}
|
||||
if (from < 0 || from >= baseSentence.fields.length) {
|
||||
setErr(context, 'index out of range');
|
||||
return [];
|
||||
}
|
||||
return baseSentence.fields.sublist(from);
|
||||
}
|
||||
|
||||
String enumString(
|
||||
int i,
|
||||
String context,
|
||||
List<String> options,
|
||||
) {
|
||||
String? s = string(i, context);
|
||||
if (_err != null || s == '') {
|
||||
return '';
|
||||
}
|
||||
if (options.contains(s)) {
|
||||
return s;
|
||||
} else {
|
||||
setErr(context, s);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
List<String> enumChars(int i, String context, List<String> options) {
|
||||
String? s = string(i, context);
|
||||
if (_err != null || s == '') {
|
||||
return [];
|
||||
}
|
||||
List<String> strs = [];
|
||||
for (int j = 0; j < s.length; j++) {
|
||||
String rs = s[j];
|
||||
if (options.contains(rs)) {
|
||||
strs.add(rs);
|
||||
}
|
||||
}
|
||||
if (strs.length != s.length) {
|
||||
setErr(context, s);
|
||||
return [];
|
||||
}
|
||||
return strs;
|
||||
}
|
||||
|
||||
int hexInt64(int i, String context) {
|
||||
String? s = string(i, context);
|
||||
if (_err != null) {
|
||||
return 0;
|
||||
}
|
||||
if (s == '') {
|
||||
return 0;
|
||||
}
|
||||
int value = int.parse(s, radix: 16);
|
||||
return value;
|
||||
}
|
||||
|
||||
int int64(int i, String context) {
|
||||
return nullInt64(i, context).value;
|
||||
}
|
||||
|
||||
Int64 nullInt64(int i, String context) {
|
||||
String? s = string(i, context);
|
||||
if (_err != null) {
|
||||
return Int64(0, false);
|
||||
}
|
||||
if (s == '') {
|
||||
return Int64(0, false);
|
||||
}
|
||||
int? v = int.tryParse(s);
|
||||
if (v == null) {
|
||||
setErr(context, s);
|
||||
return Int64(0, false);
|
||||
}
|
||||
return Int64(v, true);
|
||||
}
|
||||
|
||||
double float64(int i, String context) {
|
||||
return nullFloat64(i, context).value;
|
||||
}
|
||||
|
||||
Float64 nullFloat64(int i, String context) {
|
||||
String? s = string(i, context);
|
||||
if (_err != null) {
|
||||
return Float64(0, false);
|
||||
}
|
||||
if (s == '') {
|
||||
return Float64(0, false);
|
||||
}
|
||||
double? v = double.tryParse(s);
|
||||
if (v == null) {
|
||||
setErr(context, s);
|
||||
return Float64(0, false);
|
||||
}
|
||||
return Float64(v, true);
|
||||
}
|
||||
|
||||
Time time(int i, String context) {
|
||||
String? s = string(i, context);
|
||||
if (_err != null) {
|
||||
return Time(false, 0, 0, 0, 0);
|
||||
}
|
||||
try {
|
||||
return parseTime(s);
|
||||
} catch (e) {
|
||||
setErr(context, s);
|
||||
return Time(false, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Date date(int i, String context) {
|
||||
String? s = string(i, context);
|
||||
if (_err != null) {
|
||||
return Date(false, 0, 0, 0);
|
||||
}
|
||||
try {
|
||||
return parseDate(s);
|
||||
} catch (e) {
|
||||
setErr(context, s);
|
||||
return Date(false, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
double latLong(int i, int j, String context) {
|
||||
String a = string(i, context);
|
||||
String b = string(j, context);
|
||||
if (_err != null) {
|
||||
return 0;
|
||||
}
|
||||
String s = '$a $b';
|
||||
double? v = parseLatLong(s);
|
||||
if ((b == 'North' || b == 'South') && (v < -90.0 || v > 90.0)) {
|
||||
setErr(context, 'latitude is not in range (-90, 90)');
|
||||
return 0;
|
||||
} else if ((b == 'West' || b == 'East') && (v < -180.0 || v > 180.0)) {
|
||||
setErr(context, 'longitude is not in range (-180, 180)');
|
||||
return 0;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
List<int> sixBitASCIIArmour(int i, int fillBits, String context) {
|
||||
if (_err != null) {
|
||||
return [];
|
||||
}
|
||||
if (fillBits < 0 || fillBits >= 6) {
|
||||
setErr(context, 'fill bits');
|
||||
return [];
|
||||
}
|
||||
|
||||
List<int> payload = string(i, 'encoded payload').codeUnits;
|
||||
int numBits = payload.length * 6 - fillBits;
|
||||
|
||||
if (numBits < 0) {
|
||||
setErr(context, 'num bits');
|
||||
return [];
|
||||
}
|
||||
|
||||
List<int> result = List.filled(numBits, 0);
|
||||
int resultIndex = 0;
|
||||
|
||||
for (int v in payload) {
|
||||
if (v < 48 || v >= 120) {
|
||||
setErr(context, 'data byte');
|
||||
return [];
|
||||
}
|
||||
|
||||
int d = v - 48;
|
||||
if (d > 40) {
|
||||
d -= 8;
|
||||
}
|
||||
|
||||
for (int i = 5; i >= 0 && resultIndex < result.length; i--) {
|
||||
result[resultIndex] = (d >> i) & 1;
|
||||
resultIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class Time {
|
||||
bool isValid;
|
||||
int hour;
|
||||
int minute;
|
||||
int second;
|
||||
int millisecond;
|
||||
|
||||
Time(this.isValid, this.hour, this.minute, this.second, this.millisecond);
|
||||
}
|
||||
|
||||
Time parseTime(String s) {
|
||||
if (s == "") {
|
||||
return Time(false, 0, 0, 0, 0);
|
||||
}
|
||||
RegExp timeRe = RegExp(r"^\d{6}(\.\d*)?$");
|
||||
if (!timeRe.hasMatch(s)) {
|
||||
throw Exception("parse time: expected hhmmss.ss format, got '$s'");
|
||||
}
|
||||
int hour = int.parse(s.substring(0, 2));
|
||||
int minute = int.parse(s.substring(2, 4));
|
||||
double second = double.parse(s.substring(4));
|
||||
|
||||
int whole = second.floor(); // 获取整数部分
|
||||
double frac = second - whole; // 获取小数部分
|
||||
return Time(true, hour, minute, whole.toInt(), (frac * 1000).round());
|
||||
}
|
||||
|
||||
mixin math {}
|
||||
|
||||
class Int64 {
|
||||
int value;
|
||||
bool valid;
|
||||
|
||||
Int64(this.value, this.valid);
|
||||
}
|
||||
|
||||
class Float64 {
|
||||
double value;
|
||||
bool valid;
|
||||
|
||||
Float64(this.value, this.valid);
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
import 'types.dart';
|
||||
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
const TypeRMC = "RMC";
|
||||
// ValidRMC character
|
||||
const ValidRMC = "A";
|
||||
// InvalidRMC character
|
||||
const InvalidRMC = "V";
|
||||
|
||||
class RMC {
|
||||
BaseSentence sentence; // sentence
|
||||
Time time; // Time Stamp
|
||||
String validity; // validity - A-ok, V-invalid
|
||||
double latitude; // Latitude
|
||||
double longitude; // Longitude
|
||||
double speed; // Speed in knots
|
||||
double course; // True course
|
||||
Date date; // Date
|
||||
double variation; // Magnetic variation
|
||||
String? ffaMode; // FAA mode indicator (filled in NMEA 2.3 and later)
|
||||
String? navStatus; // Nav Status (NMEA 4.1 and later)
|
||||
|
||||
RMC({
|
||||
required this.sentence,
|
||||
required this.time,
|
||||
required this.validity,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
required this.speed,
|
||||
required this.course,
|
||||
required this.date,
|
||||
required this.variation,
|
||||
this.ffaMode,
|
||||
this.navStatus,
|
||||
}) ;
|
||||
|
||||
static RMC newRMC(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
var m = RMC(
|
||||
sentence: s,
|
||||
time: p.time(0, "time"),
|
||||
validity: p.enumString(1, "validity", [ValidRMC, InvalidRMC]),
|
||||
latitude: p.latLong(2, 3, "latitude"),
|
||||
longitude: p.latLong(4, 5, "longitude"),
|
||||
speed: p.float64(6, "speed"),
|
||||
course: p.float64(7, "course"),
|
||||
date: p.date(8, "date"),
|
||||
variation: p.float64(9, "variation"),//是否有效
|
||||
);
|
||||
if (p.enumString(10, "direction", [West, East]) == West) {
|
||||
m.variation = 0 - m.variation;
|
||||
}
|
||||
if (s.fields.length > 11) {
|
||||
m.ffaMode = p.string(11,
|
||||
"FAA mode"); // not enum because some devices have proprietary "non-nmea" values
|
||||
}
|
||||
if (s.fields.length > 12) {
|
||||
m.navStatus = p.enumString(12, "navigation status", [
|
||||
NavStatusSafe,
|
||||
NavStatusCaution,
|
||||
NavStatusUnsafe,
|
||||
NavStatusNotValid,
|
||||
]);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'types.dart';
|
||||
|
||||
const TypeRSA = "RSA";
|
||||
|
||||
class RSA {
|
||||
double starboardRudderAngle;
|
||||
String starboardRudderAngleStatus;
|
||||
double portRudderAngle;
|
||||
String portRudderAngleStatus;
|
||||
|
||||
RSA({
|
||||
required this.starboardRudderAngle,
|
||||
required this.starboardRudderAngleStatus,
|
||||
required this.portRudderAngle,
|
||||
required this.portRudderAngleStatus,
|
||||
});
|
||||
|
||||
static RSA newRSA(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return RSA(
|
||||
starboardRudderAngle: p.float64(0, "starboard rudder angle"),
|
||||
starboardRudderAngleStatus: p.enumString(
|
||||
1, "starboard rudder angle status", [StatusValid, StatusInvalid]),
|
||||
portRudderAngle: p.float64(2, "port rudder angle"),
|
||||
portRudderAngleStatus: p.enumString(
|
||||
3, "port rudder angle status", [StatusValid, StatusInvalid]),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,447 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'dart:core';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'tagblock.dart';
|
||||
|
||||
// TagBlockSep is the separator (slash `\`) that indicates start and end of tag block
|
||||
const TagBlockSep = '\\';
|
||||
|
||||
// SentenceStart is the token to indicate the start of a sentence.
|
||||
const SentenceStart = "\$";
|
||||
|
||||
// SentenceStartEncapsulated is the token to indicate the start of encapsulated data.
|
||||
const SentenceStartEncapsulated = "!";
|
||||
|
||||
// ProprietarySentencePrefix is the token to indicate the start of parametric sentences.
|
||||
const ProprietarySentencePrefix = 'P';
|
||||
|
||||
// QuerySentencePostfix is the suffix token to indicate the Query sentences.
|
||||
const QuerySentencePostfix = 'Q';
|
||||
|
||||
// FieldSep is the token to delimit fields of a sentence.
|
||||
const FieldSep = ",";
|
||||
|
||||
// ChecksumSep is the token to delimit the checksum of a sentence.
|
||||
const ChecksumSep = "*";
|
||||
|
||||
// Define the interface for all NMEA sentences
|
||||
abstract class Sentence {
|
||||
String prefix();
|
||||
String dataType();
|
||||
String talkerID();
|
||||
}
|
||||
|
||||
// Define the structure for the NMEA sentence
|
||||
class BaseSentence implements Sentence {
|
||||
String talker; // The talker id (e.g GP)
|
||||
String type; // The data type (e.g GSA)
|
||||
List<String> fields; // Array of fields
|
||||
int checksum; // The (raw) Checksum
|
||||
String raw; // The raw NMEA sentence received
|
||||
TagBlock tagBlock; // NMEA tagblock
|
||||
|
||||
BaseSentence(this.talker, this.type, this.fields, this.checksum, this.raw,
|
||||
this.tagBlock);
|
||||
|
||||
@override
|
||||
String prefix() {
|
||||
return talker + type;
|
||||
}
|
||||
|
||||
@override
|
||||
String dataType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@override
|
||||
String talkerID() {
|
||||
return talker;
|
||||
}
|
||||
|
||||
String string() {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
// Define the callback type used to parse specific sentence variants
|
||||
typedef ParserFunc = BaseSentence Function(BaseSentence s);
|
||||
|
||||
// Define the error returned when a parsed sentence is not supported
|
||||
class NotSupportedError {
|
||||
String prefix;
|
||||
|
||||
NotSupportedError(this.prefix);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'nmea: sentence prefix \'$prefix\' not supported';
|
||||
}
|
||||
}
|
||||
|
||||
const List<int> aulCrcTable = [
|
||||
0x00000000,
|
||||
0x77073096,
|
||||
0xee0e612c,
|
||||
0x990951ba,
|
||||
0x076dc419,
|
||||
0x706af48f,
|
||||
0xe963a535,
|
||||
0x9e6495a3,
|
||||
0x0edb8832,
|
||||
0x79dcb8a4,
|
||||
0xe0d5e91e,
|
||||
0x97d2d988,
|
||||
0x09b64c2b,
|
||||
0x7eb17cbd,
|
||||
0xe7b82d07,
|
||||
0x90bf1d91,
|
||||
0x1db71064,
|
||||
0x6ab020f2,
|
||||
0xf3b97148,
|
||||
0x84be41de,
|
||||
0x1adad47d,
|
||||
0x6ddde4eb,
|
||||
0xf4d4b551,
|
||||
0x83d385c7,
|
||||
0x136c9856,
|
||||
0x646ba8c0,
|
||||
0xfd62f97a,
|
||||
0x8a65c9ec,
|
||||
0x14015c4f,
|
||||
0x63066cd9,
|
||||
0xfa0f3d63,
|
||||
0x8d080df5,
|
||||
0x3b6e20c8,
|
||||
0x4c69105e,
|
||||
0xd56041e4,
|
||||
0xa2677172,
|
||||
0x3c03e4d1,
|
||||
0x4b04d447,
|
||||
0xd20d85fd,
|
||||
0xa50ab56b,
|
||||
0x35b5a8fa,
|
||||
0x42b2986c,
|
||||
0xdbbbc9d6,
|
||||
0xacbcf940,
|
||||
0x32d86ce3,
|
||||
0x45df5c75,
|
||||
0xdcd60dcf,
|
||||
0xabd13d59,
|
||||
0x26d930ac,
|
||||
0x51de003a,
|
||||
0xc8d75180,
|
||||
0xbfd06116,
|
||||
0x21b4f4b5,
|
||||
0x56b3c423,
|
||||
0xcfba9599,
|
||||
0xb8bda50f,
|
||||
0x2802b89e,
|
||||
0x5f058808,
|
||||
0xc60cd9b2,
|
||||
0xb10be924,
|
||||
0x2f6f7c87,
|
||||
0x58684c11,
|
||||
0xc1611dab,
|
||||
0xb6662d3d,
|
||||
0x76dc4190,
|
||||
0x01db7106,
|
||||
0x98d220bc,
|
||||
0xefd5102a,
|
||||
0x71b18589,
|
||||
0x06b6b51f,
|
||||
0x9fbfe4a5,
|
||||
0xe8b8d433,
|
||||
0x7807c9a2,
|
||||
0x0f00f934,
|
||||
0x9609a88e,
|
||||
0xe10e9818,
|
||||
0x7f6a0dbb,
|
||||
0x086d3d2d,
|
||||
0x91646c97,
|
||||
0xe6635c01,
|
||||
0x6b6b51f4,
|
||||
0x1c6c6162,
|
||||
0x856530d8,
|
||||
0xf262004e,
|
||||
0x6c0695ed,
|
||||
0x1b01a57b,
|
||||
0x8208f4c1,
|
||||
0xf50fc457,
|
||||
0x65b0d9c6,
|
||||
0x12b7e950,
|
||||
0x8bbeb8ea,
|
||||
0xfcb9887c,
|
||||
0x62dd1ddf,
|
||||
0x15da2d49,
|
||||
0x8cd37cf3,
|
||||
0xfbd44c65,
|
||||
0x4db26158,
|
||||
0x3ab551ce,
|
||||
0xa3bc0074,
|
||||
0xd4bb30e2,
|
||||
0x4adfa541,
|
||||
0x3dd895d7,
|
||||
0xa4d1c46d,
|
||||
0xd3d6f4fb,
|
||||
0x4369e96a,
|
||||
0x346ed9fc,
|
||||
0xad678846,
|
||||
0xda60b8d0,
|
||||
0x44042d73,
|
||||
0x33031de5,
|
||||
0xaa0a4c5f,
|
||||
0xdd0d7cc9,
|
||||
0x5005713c,
|
||||
0x270241aa,
|
||||
0xbe0b1010,
|
||||
0xc90c2086,
|
||||
0x5768b525,
|
||||
0x206f85b3,
|
||||
0xb966d409,
|
||||
0xce61e49f,
|
||||
0x5edef90e,
|
||||
0x29d9c998,
|
||||
0xb0d09822,
|
||||
0xc7d7a8b4,
|
||||
0x59b33d17,
|
||||
0x2eb40d81,
|
||||
0xb7bd5c3b,
|
||||
0xc0ba6cad,
|
||||
0xedb88320,
|
||||
0x9abfb3b6,
|
||||
0x03b6e20c,
|
||||
0x74b1d29a,
|
||||
0xead54739,
|
||||
0x9dd277af,
|
||||
0x04db2615,
|
||||
0x73dc1683,
|
||||
0xe3630b12,
|
||||
0x94643b84,
|
||||
0x0d6d6a3e,
|
||||
0x7a6a5aa8,
|
||||
0xe40ecf0b,
|
||||
0x9309ff9d,
|
||||
0x0a00ae27,
|
||||
0x7d079eb1,
|
||||
0xf00f9344,
|
||||
0x8708a3d2,
|
||||
0x1e01f268,
|
||||
0x6906c2fe,
|
||||
0xf762575d,
|
||||
0x806567cb,
|
||||
0x196c3671,
|
||||
0x6e6b06e7,
|
||||
0xfed41b76,
|
||||
0x89d32be0,
|
||||
0x10da7a5a,
|
||||
0x67dd4acc,
|
||||
0xf9b9df6f,
|
||||
0x8ebeeff9,
|
||||
0x17b7be43,
|
||||
0x60b08ed5,
|
||||
0xd6d6a3e8,
|
||||
0xa1d1937e,
|
||||
0x38d8c2c4,
|
||||
0x4fdff252,
|
||||
0xd1bb67f1,
|
||||
0xa6bc5767,
|
||||
0x3fb506dd,
|
||||
0x48b2364b,
|
||||
0xd80d2bda,
|
||||
0xaf0a1b4c,
|
||||
0x36034af6,
|
||||
0x41047a60,
|
||||
0xdf60efc3,
|
||||
0xa867df55,
|
||||
0x316e8eef,
|
||||
0x4669be79,
|
||||
0xcb61b38c,
|
||||
0xbc66831a,
|
||||
0x256fd2a0,
|
||||
0x5268e236,
|
||||
0xcc0c7795,
|
||||
0xbb0b4703,
|
||||
0x220216b9,
|
||||
0x5505262f,
|
||||
0xc5ba3bbe,
|
||||
0xb2bd0b28,
|
||||
0x2bb45a92,
|
||||
0x5cb36a04,
|
||||
0xc2d7ffa7,
|
||||
0xb5d0cf31,
|
||||
0x2cd99e8b,
|
||||
0x5bdeae1d,
|
||||
0x9b64c2b0,
|
||||
0xec63f226,
|
||||
0x756aa39c,
|
||||
0x026d930a,
|
||||
0x9c0906a9,
|
||||
0xeb0e363f,
|
||||
0x72076785,
|
||||
0x05005713,
|
||||
0x95bf4a82,
|
||||
0xe2b87a14,
|
||||
0x7bb12bae,
|
||||
0x0cb61b38,
|
||||
0x92d28e9b,
|
||||
0xe5d5be0d,
|
||||
0x7cdcefb7,
|
||||
0x0bdbdf21,
|
||||
0x86d3d2d4,
|
||||
0xf1d4e242,
|
||||
0x68ddb3f8,
|
||||
0x1fda836e,
|
||||
0x81be16cd,
|
||||
0xf6b9265b,
|
||||
0x6fb077e1,
|
||||
0x18b74777,
|
||||
0x88085ae6,
|
||||
0xff0f6a70,
|
||||
0x66063bca,
|
||||
0x11010b5c,
|
||||
0x8f659eff,
|
||||
0xf862ae69,
|
||||
0x616bffd3,
|
||||
0x166ccf45,
|
||||
0xa00ae278,
|
||||
0xd70dd2ee,
|
||||
0x4e048354,
|
||||
0x3903b3c2,
|
||||
0xa7672661,
|
||||
0xd06016f7,
|
||||
0x4969474d,
|
||||
0x3e6e77db,
|
||||
0xaed16a4a,
|
||||
0xd9d65adc,
|
||||
0x40df0b66,
|
||||
0x37d83bf0,
|
||||
0xa9bcae53,
|
||||
0xdebb9ec5,
|
||||
0x47b2cf7f,
|
||||
0x30b5ffe9,
|
||||
0xbdbdf21c,
|
||||
0xcabac28a,
|
||||
0x53b39330,
|
||||
0x24b4a3a6,
|
||||
0xbad03605,
|
||||
0xcdd70693,
|
||||
0x54de5729,
|
||||
0x23d967bf,
|
||||
0xb3667a2e,
|
||||
0xc4614ab8,
|
||||
0x5d681b02,
|
||||
0x2a6f2b94,
|
||||
0xb40bbe37,
|
||||
0xc30c8ea1,
|
||||
0x5a05df1b,
|
||||
0x2d02ef8d
|
||||
];
|
||||
|
||||
class SentenceParser {
|
||||
Map<String, ParserFunc> customParsers;
|
||||
Function(String)? parsePrefix;
|
||||
Function(BaseSentence, String)? checkCRC;
|
||||
Function(TagBlock)? onTagBlock;
|
||||
|
||||
SentenceParser(
|
||||
this.customParsers, this.parsePrefix, this.checkCRC, this.onTagBlock);
|
||||
|
||||
BaseSentence parseBaseSentence(String raw) {
|
||||
// raw = raw.trim();
|
||||
if (raw.isEmpty) {
|
||||
throw ArgumentError('nmea: can not parse empty input');
|
||||
}
|
||||
|
||||
TagBlock tagBlock = TagBlock(0, 0, "", "", 0, "", "");
|
||||
var startIndex = raw.indexOf(RegExp(r'\$|!|#'));
|
||||
if (startIndex != 0) {
|
||||
raw;
|
||||
log("NMEA sentence does not start with a '\$' or '!' or '#'");
|
||||
}
|
||||
|
||||
var checksumSepIndex = raw.indexOf('*');
|
||||
var rawFields = raw.substring(
|
||||
startIndex + 1, checksumSepIndex != -1 ? checksumSepIndex : raw.length);
|
||||
var checksumRaw = -1;
|
||||
if (checksumSepIndex != -1) {
|
||||
rawFields = raw.substring(startIndex + 1, checksumSepIndex);
|
||||
checksumRaw = int.parse(raw.substring(checksumSepIndex + 1), radix: 16);
|
||||
}
|
||||
List<String> fields = rawFields.split(',');
|
||||
|
||||
String talkerID;
|
||||
String type;
|
||||
List<String> result;
|
||||
if (parsePrefix != null) {
|
||||
result = parsePrefix!(fields[0]);
|
||||
} else {
|
||||
result = _parsePrefix(fields[0]);
|
||||
}
|
||||
|
||||
talkerID = result[0];
|
||||
type = result[1];
|
||||
var sentence = BaseSentence(
|
||||
talkerID, type, fields.sublist(1), checksumRaw, raw, tagBlock);
|
||||
if (checkCRC != null) {
|
||||
checkCRC!(sentence, rawFields);
|
||||
} else {
|
||||
int checksum;
|
||||
if (raw[0] == "#") {
|
||||
//#HEADING,#BESTPOSA
|
||||
checksum = _calculateCRC32(rawFields);
|
||||
} else {
|
||||
checksum = _calculateChecksum(rawFields);
|
||||
}
|
||||
if (checksum != sentence.checksum) {
|
||||
throw ArgumentError(
|
||||
'nmea: sentence checksum mismatch [$checksum != ${sentence.checksum}]');
|
||||
}
|
||||
}
|
||||
return sentence;
|
||||
}
|
||||
|
||||
int _calculateChecksum(String s) {
|
||||
var checksum = 0;
|
||||
for (var i = 0; i < s.length; i++) {
|
||||
checksum ^= s.codeUnitAt(i);
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
// Calculate and return the CRC for String
|
||||
int _calculateCRC32(String str) {
|
||||
int crc = 0;
|
||||
for (int i = 0; i < str.length; i++) {
|
||||
crc = (aulCrcTable[(crc ^ str.codeUnitAt(i)) & 0xff] ^ (crc >> 8));
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
List<String> _parsePrefix(String prefix) {
|
||||
if (prefix.isEmpty) {
|
||||
log('nmea: sentence prefix is empty');
|
||||
// throw ArgumentError('nmea: sentence prefix is empty');
|
||||
return ['', prefix];
|
||||
}
|
||||
if (prefix[0] == 'P') {
|
||||
return ['P', prefix.substring(1)];
|
||||
}
|
||||
if (prefix.length == 5) {
|
||||
if (prefix[4] == 'Q') {
|
||||
return [prefix.substring(0, 2), 'Q'];
|
||||
}
|
||||
return [prefix.substring(0, 2), prefix.substring(2)];
|
||||
}
|
||||
return ['', prefix];
|
||||
}
|
||||
|
||||
BaseSentence parse(String raw) {
|
||||
var s = parseBaseSentence(raw);
|
||||
if (customParsers.containsKey(s.type)) {
|
||||
return customParsers[s.type]!(s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
|
||||
import 'dart:core';
|
||||
|
||||
class TagBlock {
|
||||
int time; // TypeUnixTime unix timestamp (unit is likely to be s, but might be ms, YMMV), parameter: -c
|
||||
int relativeTime; // TypeRelativeTime relative time, parameter: -r
|
||||
String destination; // TypeDestinationID destination identification 15 char max, parameter: -d
|
||||
String grouping; // TypeGrouping sentence grouping, parameter: -g
|
||||
int lineCount; // TypeLineCount line count, parameter: -n
|
||||
String source; // TypeSourceID source identification 15 char max, parameter: -s
|
||||
String text; // TypeTextString valid character string, parameter -t
|
||||
|
||||
TagBlock(this.time, this.relativeTime, this.destination, this.grouping, this.lineCount, this.source, this.text);
|
||||
}
|
||||
|
||||
int parseInt64(String raw) {
|
||||
return int.parse(raw);
|
||||
}
|
||||
|
||||
// Parse the tag block to create a TagBlock instance
|
||||
TagBlock parseTagBlock(String tags) {
|
||||
int sumSepIndex = tags.indexOf('*');
|
||||
if (sumSepIndex == -1) {
|
||||
throw ArgumentError('nmea: tagblock does not contain checksum separator');
|
||||
}
|
||||
|
||||
String fieldsRaw = tags.substring(0, sumSepIndex);
|
||||
String checksumRaw = tags.substring(sumSepIndex + 1).toUpperCase();
|
||||
String checksum = _calculateChecksum(fieldsRaw);
|
||||
TagBlock tagBlock = TagBlock(0, 0, "", "", 0, "", "");
|
||||
|
||||
if (checksum != checksumRaw) {
|
||||
throw ArgumentError('nmea: tagblock checksum mismatch [$checksum != $checksumRaw]');
|
||||
}
|
||||
|
||||
List<String> items = tags.substring(0, sumSepIndex).split(',');
|
||||
for (String item in items) {
|
||||
List<String> parts = item.split(':');
|
||||
if (parts.length != 2) {
|
||||
throw ArgumentError('nmea: tagblock field is malformed (should be <key>:<value>) [$item]');
|
||||
}
|
||||
String key = parts[0];
|
||||
String value = parts[1];
|
||||
|
||||
switch (key) {
|
||||
case 'c': // UNIX timestamp
|
||||
tagBlock.time = parseInt64(value);
|
||||
break;
|
||||
case 'd': // Destination ID
|
||||
tagBlock.destination = value;
|
||||
break;
|
||||
case 'g': // Grouping
|
||||
tagBlock.grouping = value;
|
||||
break;
|
||||
case 'n': // Line count
|
||||
tagBlock.lineCount = parseInt64(value);
|
||||
break;
|
||||
case 'r': // Relative time
|
||||
tagBlock.relativeTime = parseInt64(value);
|
||||
break;
|
||||
case 's': // Source ID
|
||||
tagBlock.source = value;
|
||||
break;
|
||||
case 't': // Text string
|
||||
tagBlock.text = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return tagBlock;
|
||||
}
|
||||
|
||||
String _calculateChecksum(String s) {
|
||||
int checksum = 0;
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
checksum ^= s.codeUnitAt(i);
|
||||
}
|
||||
return checksum.toRadixString(16).toUpperCase().padLeft(2, '0');
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
// Status constants
|
||||
const String StatusValid = "A";
|
||||
const String StatusInvalid = "V";
|
||||
|
||||
// Unit constants
|
||||
const String UnitAmpere = "A"; // unit for current in Amperes
|
||||
const String UnitBars = "B"; // unit for pressure in Bars
|
||||
const String UnitBinary = "B"; // unit for binary data
|
||||
const String UnitCelsius = "Celsius"; // unit for temperature in Celsius
|
||||
const String UnitFahrenheit =
|
||||
"Fahrenheit"; // unit for temperature in Fahrenheit
|
||||
const String UnitDegrees = "D"; // unit for angular displacement in Degrees
|
||||
const String UnitHertz = "H"; // unit for frequency in Hertz
|
||||
const String UnitLitresPerSecond =
|
||||
"I"; // unit for volumetric flow in Litres per second
|
||||
const String UnitKelvin = "Kelvin"; // unit of temperature in Kelvin
|
||||
const String UnitKilogramPerCubicMetre =
|
||||
"K"; // unit of density in kilogram per cubic metre
|
||||
const String UnitMeters = "Meters"; // unit of distance in Meters
|
||||
const String UnitNewtons = "N"; // unit of force in Newtons (1 kg*m/s2)
|
||||
const String UnitCubicMeters = "M"; // unit of volume in cubic meters
|
||||
const String UnitRevolutionsPerMinute =
|
||||
"R"; // unit of rotational speed or the frequency of rotation around a fixed axis in revolutions per minute (RPM)
|
||||
const String UnitPercent = "P"; // percent of full range
|
||||
const String UnitPascal = "P"; // unit of pressure in Pascals
|
||||
const String UnitPartsPerThousand =
|
||||
"S"; // in parts-per notation set of pseudo-unit to describe small values of miscellaneous dimensionless quantities, e.g. mole fraction or mass fraction.
|
||||
const String UnitVolts = "V"; // unit of voltage in Volts
|
||||
|
||||
// Speed constants
|
||||
const String SpeedKnots =
|
||||
"N"; // unit of speed equal to one nautical mile per hour, approximately 1.852 km/h
|
||||
const String SpeedMeterPerSecond = "M"; // unit of speed of 1 meter per second
|
||||
const String SpeedKilometerPerHour =
|
||||
"K"; // unit of speed of 1 kilometer per hour
|
||||
|
||||
// Temperature constants
|
||||
const String TemperatureCelsius =
|
||||
"C"; // unit of temperature measured in Celsius
|
||||
const String TemperatureFahrenheit =
|
||||
"F"; // unit of temperature measured in Fahrenheit
|
||||
const String TemperatureKelvin = "K"; // unit of temperature measured in Kelvin
|
||||
|
||||
// Heading constants
|
||||
const String HeadingMagnetic = "M"; // Magnetic heading
|
||||
const String HeadingTrue = "T"; // True heading
|
||||
|
||||
// Bearing constants
|
||||
const String BearingMagnetic =
|
||||
"M"; // Angle between Earth's magnetic north and an object observed from the vessel
|
||||
const String BearingTrue =
|
||||
"T"; // Angle between Earth's true (geographical) north and an object observed from the vessel
|
||||
|
||||
// FAAMode constants
|
||||
const String FAAModeAutonomous = "A"; // Autonomous mode
|
||||
const String FAAModeDifferential = "D"; // Differential Mode
|
||||
const String FAAModeEstimated = "E"; // Estimated (dead-reckoning) mode
|
||||
const String FAAModeRTKFloat = "F"; // RTK Float mode
|
||||
const String FAAModeManualInput = "M"; // Manual Input Mode
|
||||
const String FAAModeDataNotValid = "N"; // Data Not Valid
|
||||
const String FAAModePrecise = "P"; // Precise (NMEA4.00+)
|
||||
const String FAAModeRTKInteger = "R"; // RTK Integer mode
|
||||
const String FAAModeSimulated = "S"; // Simulated Mode
|
||||
|
||||
// Navigation Status constants
|
||||
const String NavStatusSimulated = "S"; // Deprecated: use NavStatusSafe
|
||||
const String NavStatusDataValid = "V"; // Deprecated: use NavStatusNotValid
|
||||
const String NavStatusSafe = "S"; // Safe (within selected accuracy level)
|
||||
const String NavStatusCaution = "C"; // Caution (integrity not available)
|
||||
const String NavStatusUnsafe = "U"; // Unsafe (outside selected accuracy level)
|
||||
const String NavStatusNotValid =
|
||||
"V"; // Not Valid (equipment does not provide navigation status information)
|
||||
|
||||
// DistanceUnit constants
|
||||
const String DistanceUnitKilometre = "K"; // unit for distance in kilometres
|
||||
const String DistanceUnitNauticalMile =
|
||||
"N"; // unit for distance in nautical miles
|
||||
const String DistanceUnitStatuteMile =
|
||||
"S"; // unit for distance in statute miles
|
||||
const String DistanceUnitMetre = "M"; // unit for distance in metres
|
||||
const String DistanceUnitFeet = "f"; // unit for distance in feet
|
||||
const String DistanceUnitFathom = "F"; // unit for distance in fathoms
|
||||
|
||||
// Other constants
|
||||
const String Degrees = "\u00B0";
|
||||
const String Minutes = "'";
|
||||
const String Seconds = "\"";
|
||||
const String Point = ".";
|
||||
const String North = "N";
|
||||
const String South = "S";
|
||||
const String East = "E";
|
||||
const String West = "W";
|
||||
const String Left = "L";
|
||||
const String Right = "R";
|
||||
|
||||
double parseLatLong(String s) {
|
||||
double l = 0;
|
||||
|
||||
// try {
|
||||
// double v = parseDMS(s);
|
||||
// l = v;
|
||||
// } catch (e) {
|
||||
try {
|
||||
double v = parseGPS(s);
|
||||
l = v;
|
||||
} catch (e) {
|
||||
try {
|
||||
double v = parseDecimal(s);
|
||||
l = v;
|
||||
} catch (e) {
|
||||
print("cannot parse [$s], unknown format");
|
||||
}
|
||||
}
|
||||
// }
|
||||
return l;
|
||||
}
|
||||
|
||||
double parseGPS(String s) {
|
||||
List<String> parts = s.split(" ");
|
||||
if (parts.length != 2) {
|
||||
throw FormatException("invalid format: $s");
|
||||
}
|
||||
String dir = parts[1];
|
||||
double value = double.parse(parts[0]);
|
||||
|
||||
int degrees = value ~/ 100;
|
||||
double minutes = value - (degrees * 100);
|
||||
value = degrees + (minutes / 60);
|
||||
|
||||
if (dir == 'N' || dir == 'E') {
|
||||
return value;
|
||||
} else if (dir == 'S' || dir == 'W') {
|
||||
return -value;
|
||||
} else {
|
||||
throw FormatException("invalid direction [$dir]");
|
||||
}
|
||||
}
|
||||
|
||||
String formatGPS(double l) {
|
||||
String padding = "";
|
||||
int degrees = l.floor().abs();
|
||||
double fraction = (l.abs() - degrees) * 60;
|
||||
if (fraction < 10) {
|
||||
padding = "0";
|
||||
}
|
||||
return "$degrees$padding${fraction.toStringAsFixed(4)}";
|
||||
}
|
||||
|
||||
double parseDecimal(String s) {
|
||||
double l = double.parse(s);
|
||||
if (l.isNaN || (s[0] != '-' && s.split(".")[0].length > 3)) {
|
||||
throw const FormatException("parse error (not decimal coordinate)");
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
double parseDMS(String s) {
|
||||
int degrees = 0;
|
||||
int minutes = 0;
|
||||
double seconds = 0.0;
|
||||
// 是否数字解析已完成(即在其后有空格)
|
||||
bool endNumber = false;
|
||||
// 临时解析缓冲区。
|
||||
List<int> tmpBytes = [];
|
||||
String? error;
|
||||
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
String char = s[i];
|
||||
if (RegExp(r'[0-9.]').hasMatch(char)) {
|
||||
if (!endNumber) {
|
||||
tmpBytes.add(int.parse(s[i]));
|
||||
} else {
|
||||
return 0.0;
|
||||
}
|
||||
} else if (RegExp(r'\s').hasMatch(char) && tmpBytes.isNotEmpty) {
|
||||
endNumber = true;
|
||||
} else if (char == '°') {
|
||||
degrees = int.parse(String.fromCharCodes(tmpBytes));
|
||||
tmpBytes.clear();
|
||||
endNumber = false;
|
||||
} else if (char == "'") {
|
||||
minutes = int.parse(String.fromCharCodes(tmpBytes));
|
||||
tmpBytes.clear();
|
||||
endNumber = false;
|
||||
} else if (char == '"') {
|
||||
seconds = double.parse(String.fromCharCodes(tmpBytes));
|
||||
tmpBytes.clear();
|
||||
endNumber = false;
|
||||
} else if (RegExp(r'\s').hasMatch(char) && tmpBytes.isEmpty) {
|
||||
continue;
|
||||
} else {
|
||||
error = "parse error (unknown symbol [$char])";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tmpBytes.isNotEmpty) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double val = degrees + (minutes / 60) + (seconds / 3600);
|
||||
if (error != null) {
|
||||
throw FormatException(error);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
Date parseDate(String ddmmyy) {
|
||||
if (ddmmyy == "") {
|
||||
return Date(false, 0, 0, 0);
|
||||
}
|
||||
if (ddmmyy.length != 6) {
|
||||
throw Exception("parse date: expected ddmmyy format, got '$ddmmyy'");
|
||||
}
|
||||
int dd = int.parse(ddmmyy.substring(0, 2));
|
||||
int mm = int.parse(
|
||||
ddmmyy.substring(2, 4),
|
||||
);
|
||||
int yy = int.parse(
|
||||
ddmmyy.substring(4, 6),
|
||||
);
|
||||
return Date(true, dd, mm, yy);
|
||||
}
|
||||
|
||||
class Date {
|
||||
bool isValid;
|
||||
int dd;
|
||||
int mm;
|
||||
int yy;
|
||||
|
||||
Date(this.isValid, this.dd, this.mm, this.yy);
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'parser.dart';
|
||||
import 'sentence.dart';
|
||||
|
||||
const TypeVTG = "VTG";
|
||||
|
||||
class VTG {
|
||||
double trueTrack;
|
||||
double magneticTrack;
|
||||
double groundSpeedKnots;
|
||||
double groundSpeedKPH;
|
||||
String ffaMode;
|
||||
|
||||
VTG(
|
||||
{required this.trueTrack,
|
||||
required this.magneticTrack,
|
||||
required this.groundSpeedKnots,
|
||||
required this.groundSpeedKPH,
|
||||
required this.ffaMode});
|
||||
static VTG newVTG(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
String ffaMode = "";
|
||||
if (s.fields.length > 8) {
|
||||
ffaMode = p.string(8, "FAA mode");
|
||||
}
|
||||
return VTG(
|
||||
trueTrack: p.float64(0, "true track"),
|
||||
magneticTrack: p.float64(2, "magnetic track"),
|
||||
groundSpeedKnots: p.float64(4, "ground speed (knots)"),
|
||||
groundSpeedKPH: p.float64(6, "ground speed (km/h)"),
|
||||
ffaMode: ffaMode);
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'sentence.dart';
|
||||
|
||||
import 'parser.dart';
|
||||
|
||||
const TypeZDA = "ZDA";
|
||||
|
||||
class ZDA {
|
||||
Time time;
|
||||
int day;
|
||||
int month;
|
||||
int year;
|
||||
int offsetHours; // Local time zone offset from GMT, hours
|
||||
int offsetMinutes; // Local time zone offset from GMT, minutes
|
||||
|
||||
ZDA({
|
||||
required this.time,
|
||||
required this.day,
|
||||
required this.month,
|
||||
required this.year,
|
||||
required this.offsetHours,
|
||||
required this.offsetMinutes,
|
||||
});
|
||||
static ZDA newZDA(BaseSentence s) {
|
||||
var p = Parser(s);
|
||||
return ZDA(
|
||||
time: p.time(0, "time"),
|
||||
day: p.int64(1, "day"),
|
||||
month: p.int64(2, "month"),
|
||||
year: p.int64(3, "year"),
|
||||
offsetHours: p.int64(4, "offset (hours)"),
|
||||
offsetMinutes: p.int64(5, "offset (minutes)"),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class RtkBase {
|
||||
// String host = "192.168.1.6";
|
||||
// String host = "120.253.239.161"; //ip地址
|
||||
// int port = 8001; //端口号
|
||||
// String account = "csar6309"; //用户名
|
||||
// String password = "9kd6kmd3"; //密码
|
||||
// String mountPoint = "RTCM33_GRCE"; //挂载点
|
||||
String host = "vrs.sixents.com";
|
||||
int port = 8005; //端口号
|
||||
String account = "uhjns21929"; //用户名
|
||||
String password = "UpqbKp4Q"; //密码
|
||||
String mountPoint = "RTCM32_GRECJ2"; //挂载点
|
||||
String lastGGA = ""; //最后一次发送的GGA
|
||||
Socket? _client;
|
||||
bool isConnected = false;
|
||||
int lastSendGGATime = 0;
|
||||
var rtcmStreamController = StreamController<Uint8List>.broadcast();
|
||||
Stream<Uint8List> get rtcmStream => rtcmStreamController.stream;
|
||||
var updateCount = 0; //
|
||||
dispose() {
|
||||
close();
|
||||
}
|
||||
|
||||
connect() {
|
||||
var headers = [
|
||||
"GET /$mountPoint HTTP/1.0",
|
||||
"Host: $host",
|
||||
"Ntrip-Version: Ntrip/2.0",
|
||||
"User-Agent: NTRIP u-blox",
|
||||
"Accept: */*",
|
||||
"Authorization: Basic ${base64Encode(('$account:$password').codeUnits)}",
|
||||
"Connection: close",
|
||||
"\r\n"
|
||||
];
|
||||
Socket.connect(host, port).then((Socket client) {
|
||||
_client = client;
|
||||
log('Connected to: ${client.remoteAddress.address}:${client.remotePort}');
|
||||
var reqStr = headers.join("\r\n");
|
||||
client.write(reqStr);
|
||||
log(reqStr);
|
||||
|
||||
client.listen(
|
||||
(Uint8List data) {
|
||||
if (data[0] == 211) {
|
||||
rtcmStreamController.add(data);
|
||||
} else {
|
||||
String response = String.fromCharCodes(data);
|
||||
log("Connected to NTRIP caster: $response");
|
||||
if (response.contains("ICY 200 OK")) {
|
||||
if (lastGGA.isNotEmpty) {
|
||||
_client!.write(lastGGA);
|
||||
lastSendGGATime = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
} else {
|
||||
log("Failed to connect to NTRIP caster", error: response);
|
||||
client.close();
|
||||
_client = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (error) {
|
||||
log("Error reading from response body: $error");
|
||||
// client.close();
|
||||
},
|
||||
);
|
||||
client.done.then((_) {
|
||||
log('Connection to NTRIP caster closed');
|
||||
_client = null;
|
||||
});
|
||||
}).catchError((error) {
|
||||
log("Error connecting to server: $error");
|
||||
_client = null;
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
if (_client != null) {
|
||||
_client!.close();
|
||||
_client = null;
|
||||
}
|
||||
}
|
||||
|
||||
sendGGA(String gga) {
|
||||
var now = DateTime.now().millisecondsSinceEpoch;
|
||||
if (now - lastSendGGATime > 10000 && _client != null) {
|
||||
lastGGA = "$gga\r\n";
|
||||
_client!.write(lastGGA);
|
||||
lastSendGGATime = now;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
name: gnss
|
||||
description: "A new Flutter package project."
|
||||
version: 0.0.1
|
||||
homepage:
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=3.4.4 <4.0.0'
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
libserialport :
|
||||
git:
|
||||
url: https://git.mcxa.cn:89/flutter/libserialport.git
|
||||
ref: main
|
||||
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# To add assets to your package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
||||
# To add custom fonts to your package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/custom-fonts/#from-packages
|
@ -1,19 +0,0 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:gnss/gnss.dart';
|
||||
|
||||
void main() {
|
||||
test('adds one to input values', () {
|
||||
Gnss gnss = Gnss();
|
||||
// gnss.connectSerialPort("COM25", 115200);
|
||||
// gnss.locationStream.listen((event) {
|
||||
// log("Location: $event");
|
||||
// });
|
||||
// final calculator = Calculator();
|
||||
// expect(calculator.addOne(2), 3);
|
||||
// expect(calculator.addOne(-7), -6);
|
||||
// expect(calculator.addOne(0), 1);
|
||||
});
|
||||
}
|
10
pubspec.lock
10
pubspec.lock
@ -120,10 +120,18 @@ packages:
|
||||
description:
|
||||
path: "."
|
||||
ref: main
|
||||
resolved-ref: "6e4c9b35dc86d027a0f0bfbc65747a4813d92a2c"
|
||||
resolved-ref: "13bca7191808a98707e61f119994a91ebc35f11a"
|
||||
url: "https://git.mcxa.cn:89/flutter/gnss.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -34,7 +34,7 @@ dependencies:
|
||||
git:
|
||||
url: https://git.mcxa.cn:89/flutter/gnss.git
|
||||
ref: main
|
||||
|
||||
intl: ^0.17.0
|
||||
# libserialport:
|
||||
# path: plugins/libserialport
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user