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