Compare commits

...

5 Commits
main ... edit

Author SHA1 Message Date
tanlinxing
4b57e95def 柱状图 2024-08-01 18:17:57 +08:00
Holy
f1c566b008 修改Signal结构 2024-08-01 15:13:18 +08:00
tanlinxing
9a38c279ab 天空图修改 2024-08-01 14:43:57 +08:00
tanlinxing
99e56d5207 天空图修改 2024-08-01 14:08:34 +08:00
Holy
dcfd9c6268 调试 2024-07-31 23:37:51 +08:00
63 changed files with 879 additions and 3992 deletions

View File

@ -1,26 +1,37 @@
import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:gnss/gnss.dart'; import 'package:gnss/gnss.dart';
class GnssController extends GetxController { class GnssController extends GetxController {
late Gnss gnss; late final Gnss gnss;
LocationData? locationData; LocationData? locationData;
SignalData? signalData; SignalData? signalData;
var locationUpdate = 0.obs; var locationUpdate = 0.obs;
var singnalUpdate = 0.obs; var singnalUpdate = 0.obs;
// var _selectedSignal = []; final startIndex = 0.obs;
Map<String, bool> selectedSignal = {}; updateSlider(double value) {
// List<String> get selectedSignal => _selectedSignal; startIndex.value = value.ceil();
// set selectedSignal(Set<String> value) {
// _selectedSignal = value; update();
// 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 @override
void onInit() async { void onInit() async {
super.onInit(); super.onInit();
gnss = Gnss(port: "/dev/ttysWK2", baudrate: 115200); gnss = Gnss(port: "/dev/ttysWK2", baudrate: 115200);
// gnss = Gnss(port: "COM1", baudrate: 115200);
gnss.start(); gnss.start();
gnss.locationStream.listen((location) { gnss.locationStream.listen((location) {
locationData = location; locationData = location;
@ -34,7 +45,6 @@ class GnssController extends GetxController {
@override @override
void dispose() { void dispose() {
// TODO: implement dispose
gnss.dispose(); gnss.dispose();
super.dispose(); super.dispose();
} }

View File

@ -1,50 +1,14 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.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 'sky/sky_plot.dart';
import 'Controller/gnssController.dart'; // Import the correct file location for GnssController 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');
// 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')));
// }
// }
void main() { void main() {
Get.lazyPut<GnssController>(() => GnssController(), tag: 'gnss'); Get.put<GnssController>(GnssController());
runApp(MyApp()); runApp(MyApp());
} }
@ -71,13 +35,13 @@ class _MyAppState extends State<MyApp> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final con = Get.find<GnssController>(tag: 'gnss'); // final con = Get.find<GnssController>();
return MaterialApp( return MaterialApp(
home: Scaffold( home: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('天空图'), title: Text('天空图'),
), ),
body: SkyPlotPage(), body: ChartPart(),
)); ));
} }
} }

View File

@ -1,89 +1,302 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:get/get.dart';
import 'package:gnss/gnss.dart';
class ChartPart extends StatelessWidget { import '../Controller/gnss_controller.dart';
final List<List<Color>> groupColors; //
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 @override
Widget build(BuildContext context) { 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++) {}
// 使 return SizedBox(
List<BarChartGroupData> sampleData = []; width: size.width,
for (int i = 0; i < groupColors.length; i++) { // decoration: const BoxDecoration(color: Colors.white),
// child: AspectRatio(
if (groupColors[i].isNotEmpty && aspectRatio: 1,
groupColors[i][0] != Colors.transparent) { child: Padding(
sampleData.add( padding: const EdgeInsets.all(10),
BarChartGroupData( child: Stack(
x: i, children: [
barRods: [ Column(
BarChartRodData( crossAxisAlignment: CrossAxisAlignment.stretch,
toY: (4 + i).toDouble(), color: groupColors[i][0]), 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;
// });
} }
} },
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( titlesData: FlTitlesData(
show: true, show: true,
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles( bottomTitles: AxisTitles(
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: true,
getTitlesWidget: bottomTitles,
reservedSize: 42, reservedSize: 42,
getTitlesWidget: (value, meta) {
//
return Text(orientation == Orientation.portrait
? 'Group $value'
: 'Group $value'); //
},
),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
), ),
), ),
leftTitles: AxisTitles( leftTitles: AxisTitles(
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: true,
reservedSize: 40, reservedSize: 40,
getTitlesWidget: (value, meta) { interval: 1,
return RotatedBox( getTitlesWidget: leftTitles,
quarterTurns: 0, // 270
child: Text(value.toString()),
);
},
), ),
), ),
), ),
borderData: FlBorderData(show: false), borderData: FlBorderData(
barGroups: sampleData, show: false,
alignment: BarChartAlignment.spaceAround,
),
), ),
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),
), ),
); );
}),
),
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,
),
),
),
],
),
),
),
); );
}); });
} }
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);
}
} }

View File

@ -18,7 +18,8 @@ class _SignalQualityState extends State<SignalQuality> {
super.initState(); super.initState();
// //
for (int i = 0; i < 4; i++) { 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(() { setState(() {
for (int i = 0; i < groupColors.length; i++) { 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) { String colorToString(Color color) {
// //
if (color == Colors.blue) return 'GPS'; if (color == Color.fromARGB(255, 255, 0, 0)) return 'GPS(G)';
if (color == Colors.red) return 'BDS'; if (color == Color.fromARGB(255, 0, 255, 0)) return 'GLONASS(R)';
if (color == Colors.green) return 'GLO'; if (color == Color.fromARGB(255, 0, 0, 255)) return 'GALILEO(E)';
if (color == Colors.orange) return 'ALS'; 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'; return 'Unknown Color';
} }
@ -50,7 +53,7 @@ class _SignalQualityState extends State<SignalQuality> {
return Column( return Column(
children: [ children: [
SingleButton( SingleButton(
colors: colors, // colors: colors,
onSelectionChanged: (color) { onSelectionChanged: (color) {
setState(() { setState(() {
selectedColor = color; // selectedColor = color; //
@ -60,10 +63,11 @@ class _SignalQualityState extends State<SignalQuality> {
} }
}); });
}, },
signalColorList: [],
), ),
Expanded( Expanded(
child: Center( child: Center(
child: ChartPart(groupColors: groupColors), // ChartPart child: ChartPart(), // ChartPart
), ),
), ),
], ],

View File

@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
class SingleButton extends StatefulWidget { class SingleButton extends StatefulWidget {
final controller = Get.find(tag: 'gnss'); // final controller = Get.find(tag: 'gnss');
final List<Color> colors; final List<Color> signalColorList;
final Function(Color) onSelectionChanged; final Function(Color) onSelectionChanged;
SingleButton({ SingleButton({
required this.colors, required this.signalColorList,
required this.onSelectionChanged, required this.onSelectionChanged,
}); });
@ -16,17 +16,17 @@ class SingleButton extends StatefulWidget {
} }
class _SingleButtonState extends State<SingleButton> { class _SingleButtonState extends State<SingleButton> {
late Color selectedColor; late Color QselectedSignal;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
selectedColor = widget.colors[0]; // QselectedSignal = widget.signalColorList[0]; //
} }
void updateSelection(Color color) { void updateSelection(Color color) {
setState(() { setState(() {
selectedColor = color; QselectedSignal = color;
widget.onSelectionChanged(color); // widget.onSelectionChanged(color); //
}); });
} }
@ -35,23 +35,23 @@ class _SingleButtonState extends State<SingleButton> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(widget.colors.length, (index) { children: List.generate(widget.signalColorList.length, (index) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Radio<Color>( Radio<Color>(
value: widget.colors[index], value: widget.signalColorList[index],
groupValue: selectedColor, groupValue: QselectedSignal,
onChanged: (Color? value) { onChanged: (Color? value) {
if (value != null) { if (value != null) {
updateSelection(value); updateSelection(value);
} }
}, },
activeColor: widget.colors[index], activeColor: widget.signalColorList[index],
), ),
Text( Text(
colorToString(widget.colors[index]), colorToString(widget.signalColorList[index]),
style: TextStyle(color: widget.colors[index]), style: TextStyle(color: widget.signalColorList[index]),
), ),
], ],
); );
@ -60,10 +60,11 @@ class _SingleButtonState extends State<SingleButton> {
} }
String colorToString(Color color) { String colorToString(Color color) {
if (color == Colors.blue) return 'GPS'; if (color == Color.fromARGB(255, 255, 0, 0)) return 'GPS(G)';
if (color == Colors.red) return 'BDS'; if (color == Color.fromARGB(255, 0, 255, 0)) return 'GLONASS(R)';
if (color == Colors.green) return 'GLO'; if (color == Color.fromARGB(255, 0, 0, 255)) return 'GALILEO(E)';
if (color == Colors.orange) return 'ALS'; 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'; return 'Unknown Color';
} }
} }

View File

@ -2,12 +2,11 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
class MulButton extends StatefulWidget { class MulButton extends StatefulWidget {
final controller = Get.find(tag: 'gnss'); final List<Color> signalColorList;
final List<Color> colors; final Function(int, bool) onSelectionChanged;
final Function(List<Color>) onSelectionChanged;
MulButton({ MulButton({
required this.colors, required this.signalColorList,
required this.onSelectionChanged, required this.onSelectionChanged,
}); });
@ -16,24 +15,25 @@ class MulButton extends StatefulWidget {
} }
class _MulButtonState extends State<MulButton> { class _MulButtonState extends State<MulButton> {
late List<bool> selectedColors; late List<bool> selectedSignal;
@override @override
void initState() { void initState() {
super.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) { void updateSelection(int index, bool value) {
setState(() { setState(() {
selectedColors[index] = value; selectedSignal[index] = value;
List<Color> selected = []; // List<Color> selected = [];
for (int i = 0; i < selectedColors.length; i++) { // for (int i = 0; i < selectedColors.length; i++) {
if (selectedColors[i]) { // if (selectedColors[i]) {
selected.add(widget.colors[i]); // selected.add(widget.colors[i]);
} // }
} // }
widget.onSelectionChanged(selected); // widget.onSelectionChanged(index, value); //
}); });
} }
@ -43,25 +43,25 @@ class _MulButtonState extends State<MulButton> {
spacing: 8.0, // spacing: 8.0, //
runSpacing: 4.0, // runSpacing: 4.0, //
alignment: WrapAlignment.start, alignment: WrapAlignment.start,
children: List.generate(widget.colors.length, (index) { children: List.generate(widget.signalColorList.length, (index) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Checkbox( Checkbox(
value: selectedColors[index], value: selectedSignal[index],
onChanged: (bool? value) { onChanged: (bool? value) {
if (value != null) { if (value != null) {
updateSelection(index, value); updateSelection(index, value);
} }
}, },
activeColor: widget.colors[index], activeColor: widget.signalColorList[index],
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4), // borderRadius: BorderRadius.circular(4), //
), ),
), ),
Text( Text(
colorToString(widget.colors[index]), colorToString(widget.signalColorList[index]),
style: TextStyle(color: widget.colors[index]), style: TextStyle(color: widget.signalColorList[index]),
), ),
], ],
); );
@ -70,10 +70,11 @@ class _MulButtonState extends State<MulButton> {
} }
String colorToString(Color color) { String colorToString(Color color) {
if (color == Colors.blue) return 'GPS'; if (color == Color.fromARGB(255, 255, 0, 0)) return 'GPS(G)';
if (color == Colors.red) return 'BDS'; if (color == Color.fromARGB(255, 0, 255, 0)) return 'GLONASS(R)';
if (color == Colors.green) return 'GLO'; if (color == Color.fromARGB(255, 0, 0, 255)) return 'GALILEO(E)';
if (color == Colors.orange) return 'ALS'; 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'; return 'Unknown Color';
} }
} }

View File

@ -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,
),
);
}
}

View File

@ -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
View 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
View 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;
}
}

View File

@ -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/

View File

@ -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

View File

@ -1,3 +0,0 @@
## 0.0.1
* TODO: Describe initial release.

View File

@ -1 +0,0 @@
TODO: Add your license here.

View File

@ -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.

View File

@ -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

View File

@ -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
}

View File

@ -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"),
);
}
}

View File

@ -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'),
);
}
}

View File

@ -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'),
);
}
}

View File

@ -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'),
);
}
}

View File

@ -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"),
);
}
}

View File

@ -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,
});
}

View File

@ -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"),
);
}
}

View File

@ -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"),
);
}
}

View File

@ -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
);
}
}

View File

@ -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
]),
);
}
}

View File

@ -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"),
);
}
}

View File

@ -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"),
);
}
}

View File

@ -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"));
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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"),
);
}
}

View File

@ -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]),
);
}
}

View File

@ -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]),
);
}
}

View File

@ -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"),
);
}
}

View File

@ -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;

View File

@ -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"),
);
}
}

View File

@ -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);
}
}

View File

@ -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"),
);
}
}

View File

@ -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);
}

View File

@ -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) {}
}

View File

@ -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"),
);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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});
}

View File

@ -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",
);
}
}

View File

@ -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"),
);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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]),
);
}
}

View File

@ -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;
}
}

View File

@ -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');
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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)"),
);
}
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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);
});
}

View File

@ -120,10 +120,18 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "6e4c9b35dc86d027a0f0bfbc65747a4813d92a2c" resolved-ref: "13bca7191808a98707e61f119994a91ebc35f11a"
url: "https://git.mcxa.cn:89/flutter/gnss.git" url: "https://git.mcxa.cn:89/flutter/gnss.git"
source: git source: git
version: "0.0.1" 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: leak_tracker:
dependency: transitive dependency: transitive
description: description:

View File

@ -34,7 +34,7 @@ dependencies:
git: git:
url: https://git.mcxa.cn:89/flutter/gnss.git url: https://git.mcxa.cn:89/flutter/gnss.git
ref: main ref: main
intl: ^0.17.0
# libserialport: # libserialport:
# path: plugins/libserialport # path: plugins/libserialport