Compare commits
No commits in common. "edit" and "main" have entirely different histories.
@ -1,37 +1,26 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/rendering.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 final Gnss gnss;
|
late 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;
|
||||||
final startIndex = 0.obs;
|
// var _selectedSignal = [];
|
||||||
updateSlider(double value) {
|
Map<String, bool> selectedSignal = {};
|
||||||
startIndex.value = value.ceil();
|
// List<String> get selectedSignal => _selectedSignal;
|
||||||
|
// set selectedSignal(Set<String> value) {
|
||||||
update();
|
// _selectedSignal = value;
|
||||||
}
|
// 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;
|
||||||
@ -45,6 +34,7 @@ class GnssController extends GetxController {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
// TODO: implement dispose
|
||||||
gnss.dispose();
|
gnss.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
@ -1,14 +1,50 @@
|
|||||||
|
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:gnssview/quality/chartpart.dart';
|
import 'package:gnss/gnss.dart';
|
||||||
// import 'package:gnss/gnss.dart';
|
|
||||||
|
|
||||||
import 'sky/sky_plot.dart';
|
import 'sky/skyplot.dart';
|
||||||
import 'sky/sky_info.dart';
|
import 'Controller/gnssController.dart'; // Import the correct file location for GnssController
|
||||||
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.put<GnssController>(GnssController());
|
Get.lazyPut<GnssController>(() => GnssController(), tag: 'gnss');
|
||||||
runApp(MyApp());
|
runApp(MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,13 +71,13 @@ class _MyAppState extends State<MyApp> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// final con = Get.find<GnssController>();
|
final con = Get.find<GnssController>(tag: 'gnss');
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('天空图'),
|
title: Text('天空图'),
|
||||||
),
|
),
|
||||||
body: ChartPart(),
|
body: SkyPlotPage(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,302 +1,89 @@
|
|||||||
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';
|
|
||||||
|
|
||||||
import '../Controller/gnss_controller.dart';
|
class ChartPart extends StatelessWidget {
|
||||||
|
final List<List<Color>> groupColors; // 接收每个组的颜色
|
||||||
|
|
||||||
const signalPrefixList = <String>["G", "R", "E", "B", "Q"];
|
ChartPart({required this.groupColors}); // 构造函数
|
||||||
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) {
|
||||||
final size = MediaQuery.of(context).size;
|
return OrientationBuilder(builder: (context, orientation) {
|
||||||
final orientation =
|
final size = MediaQuery.of(context).size;
|
||||||
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(
|
// 使用静态示例数据来绘制图表框架
|
||||||
width: size.width,
|
List<BarChartGroupData> sampleData = [];
|
||||||
// decoration: const BoxDecoration(color: Colors.white),
|
for (int i = 0; i < groupColors.length; i++) {
|
||||||
child: AspectRatio(
|
// 确保每个组至少有两个颜色
|
||||||
aspectRatio: 1,
|
if (groupColors[i].isNotEmpty &&
|
||||||
child: Padding(
|
groupColors[i][0] != Colors.transparent) {
|
||||||
padding: const EdgeInsets.all(10),
|
sampleData.add(
|
||||||
child: Stack(
|
BarChartGroupData(
|
||||||
children: [
|
x: i,
|
||||||
Column(
|
barRods: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
BarChartRodData(
|
||||||
children: [
|
toY: (4 + i).toDouble(), color: groupColors[i][0]),
|
||||||
Expanded(
|
|
||||||
child: Obx(() {
|
|
||||||
controller.startIndex.value;
|
|
||||||
return BarChart(
|
|
||||||
swapAnimationDuration:
|
|
||||||
const Duration(milliseconds: 0),
|
|
||||||
BarChartData(
|
|
||||||
maxY: 100, // 提示需要+ 20 因为 提示是根据柱状图位置进行计算的
|
|
||||||
barTouchData: BarTouchData(
|
|
||||||
enabled: false,
|
|
||||||
touchTooltipData: BarTouchTooltipData(
|
|
||||||
// tooltipBgColor: Colors.blueGrey,
|
|
||||||
getTooltipItem: (
|
|
||||||
BarChartGroupData group,
|
|
||||||
int groupIndex,
|
|
||||||
BarChartRodData rod,
|
|
||||||
int rodIndex,
|
|
||||||
) {
|
|
||||||
return BarTooltipItem(
|
|
||||||
'L1:${group.barRods[0].toY}\n',
|
|
||||||
const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
decoration: TextDecoration.none,
|
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 18,
|
|
||||||
shadows: [
|
|
||||||
Shadow(
|
|
||||||
color: Colors.black26,
|
|
||||||
blurRadius: 12,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
touchCallback: (event, response) {
|
|
||||||
if (event.isInterestedForInteractions &&
|
|
||||||
response != null &&
|
|
||||||
response.spot != null) {
|
|
||||||
// setState(() {
|
|
||||||
// touchedGroupIndex =
|
|
||||||
// response.spot!.touchedBarGroupIndex;
|
|
||||||
// });
|
|
||||||
} else {
|
|
||||||
// setState(() {
|
|
||||||
// touchedGroupIndex = -100;
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
titlesData: FlTitlesData(
|
|
||||||
show: true,
|
|
||||||
rightTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(showTitles: false),
|
|
||||||
),
|
|
||||||
topTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(showTitles: false),
|
|
||||||
),
|
|
||||||
bottomTitles: AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: true,
|
|
||||||
getTitlesWidget: bottomTitles,
|
|
||||||
reservedSize: 42,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
leftTitles: AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: true,
|
|
||||||
reservedSize: 40,
|
|
||||||
interval: 1,
|
|
||||||
getTitlesWidget: leftTitles,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
borderData: FlBorderData(
|
|
||||||
show: false,
|
|
||||||
),
|
|
||||||
barGroups:
|
|
||||||
List.generate(signalGNSS.length, (index) {
|
|
||||||
List<int> listy = [];
|
|
||||||
Map snr = signalGNSS[index].snr;
|
|
||||||
|
|
||||||
snr.forEach((key, value) {
|
|
||||||
listy.add(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return makeGroupData(
|
|
||||||
index, listy, signalColorList[0]);
|
|
||||||
}),
|
|
||||||
gridData: const FlGridData(show: true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
orientation
|
|
||||||
? const SizedBox(
|
|
||||||
height: 50,
|
|
||||||
)
|
|
||||||
: const Text(""),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
height: size.height,
|
|
||||||
child: Opacity(
|
|
||||||
opacity: 0, // 设置 Slider 组件的透明度为 0,使其不可见
|
|
||||||
child: Slider(
|
|
||||||
value: controller.startIndex.toDouble(),
|
|
||||||
onChanged: (newvalue) {
|
|
||||||
controller.updateSlider(newvalue);
|
|
||||||
},
|
|
||||||
min: 0,
|
|
||||||
max: xLength <= 5 ? 0 : xLength - 5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
}
|
||||||
|
}
|
||||||
|
return OrientationBuilder(
|
||||||
|
builder: (context, orientation) {
|
||||||
|
return SizedBox(
|
||||||
|
width: size.width,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: BarChart(
|
||||||
|
BarChartData(
|
||||||
|
maxY: 10,
|
||||||
|
barTouchData: BarTouchData(enabled: false),
|
||||||
|
titlesData: FlTitlesData(
|
||||||
|
show: true,
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 42,
|
||||||
|
getTitlesWidget: (value, meta) {
|
||||||
|
// 根据方向设置标题
|
||||||
|
return Text(orientation == Orientation.portrait
|
||||||
|
? 'Group $value'
|
||||||
|
: 'Group $value'); // 可以根据需要调整
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
topTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rightTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leftTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 40,
|
||||||
|
getTitlesWidget: (value, meta) {
|
||||||
|
return RotatedBox(
|
||||||
|
quarterTurns: 0, // 旋转 270 度
|
||||||
|
child: Text(value.toString()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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,8 +18,7 @@ class _SignalQualityState extends State<SignalQuality> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
// 初始化每个组的颜色
|
// 初始化每个组的颜色
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
groupColors
|
groupColors.add([colors[i % colors.length], colors[(i + 1) % colors.length]]);
|
||||||
.add([colors[i % colors.length], colors[(i + 1) % colors.length]]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,19 +26,17 @@ 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] = groupColors[i].first == color ? [color] : [Colors.transparent];
|
||||||
groupColors[i].first == color ? [color] : [Colors.transparent];
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String colorToString(Color color) {
|
String colorToString(Color color) {
|
||||||
// 将颜色转换为字符串
|
// 将颜色转换为字符串
|
||||||
if (color == Color.fromARGB(255, 255, 0, 0)) return 'GPS(G)';
|
if (color == Colors.blue) return 'GPS';
|
||||||
if (color == Color.fromARGB(255, 0, 255, 0)) return 'GLONASS(R)';
|
if (color == Colors.red) return 'BDS';
|
||||||
if (color == Color.fromARGB(255, 0, 0, 255)) return 'GALILEO(E)';
|
if (color == Colors.green) return 'GLO';
|
||||||
if (color == Color.fromARGB(255, 255, 255, 0)) return 'BEIDOU(B)';
|
if (color == Colors.orange) return 'ALS';
|
||||||
if (color == Color.fromARGB(255, 0, 255, 255)) return 'QZSS(Q)';
|
|
||||||
return 'Unknown Color';
|
return 'Unknown Color';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +50,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; // 更新选中的颜色
|
||||||
@ -63,11 +60,10 @@ class _SignalQualityState extends State<SignalQuality> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
signalColorList: [],
|
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: ChartPart(), // 将组颜色传递给 ChartPart
|
child: ChartPart(groupColors: groupColors), // 将组颜色传递给 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> signalColorList;
|
final List<Color> colors;
|
||||||
final Function(Color) onSelectionChanged;
|
final Function(Color) onSelectionChanged;
|
||||||
|
|
||||||
SingleButton({
|
SingleButton({
|
||||||
required this.signalColorList,
|
required this.colors,
|
||||||
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 QselectedSignal;
|
late Color selectedColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
QselectedSignal = widget.signalColorList[0]; // 默认选中第一个颜色
|
selectedColor = widget.colors[0]; // 默认选中第一个颜色
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateSelection(Color color) {
|
void updateSelection(Color color) {
|
||||||
setState(() {
|
setState(() {
|
||||||
QselectedSignal = color;
|
selectedColor = 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.signalColorList.length, (index) {
|
children: List.generate(widget.colors.length, (index) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Radio<Color>(
|
Radio<Color>(
|
||||||
value: widget.signalColorList[index],
|
value: widget.colors[index],
|
||||||
groupValue: QselectedSignal,
|
groupValue: selectedColor,
|
||||||
onChanged: (Color? value) {
|
onChanged: (Color? value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
updateSelection(value);
|
updateSelection(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
activeColor: widget.signalColorList[index],
|
activeColor: widget.colors[index],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
colorToString(widget.signalColorList[index]),
|
colorToString(widget.colors[index]),
|
||||||
style: TextStyle(color: widget.signalColorList[index]),
|
style: TextStyle(color: widget.colors[index]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -60,11 +60,10 @@ class _SingleButtonState extends State<SingleButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String colorToString(Color color) {
|
String colorToString(Color color) {
|
||||||
if (color == Color.fromARGB(255, 255, 0, 0)) return 'GPS(G)';
|
if (color == Colors.blue) return 'GPS';
|
||||||
if (color == Color.fromARGB(255, 0, 255, 0)) return 'GLONASS(R)';
|
if (color == Colors.red) return 'BDS';
|
||||||
if (color == Color.fromARGB(255, 0, 0, 255)) return 'GALILEO(E)';
|
if (color == Colors.green) return 'GLO';
|
||||||
if (color == Color.fromARGB(255, 255, 255, 0)) return 'BEIDOU(B)';
|
if (color == Colors.orange) return 'ALS';
|
||||||
if (color == Color.fromARGB(255, 0, 255, 255)) return 'QZSS(Q)';
|
|
||||||
return 'Unknown Color';
|
return 'Unknown Color';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,12 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class MulButton extends StatefulWidget {
|
class MulButton extends StatefulWidget {
|
||||||
final List<Color> signalColorList;
|
final controller = Get.find(tag: 'gnss');
|
||||||
final Function(int, bool) onSelectionChanged;
|
final List<Color> colors;
|
||||||
|
final Function(List<Color>) onSelectionChanged;
|
||||||
|
|
||||||
MulButton({
|
MulButton({
|
||||||
required this.signalColorList,
|
required this.colors,
|
||||||
required this.onSelectionChanged,
|
required this.onSelectionChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -15,25 +16,24 @@ class MulButton extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MulButtonState extends State<MulButton> {
|
class _MulButtonState extends State<MulButton> {
|
||||||
late List<bool> selectedSignal;
|
late List<bool> selectedColors;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
selectedSignal =
|
selectedColors = List.generate(widget.colors.length, (index) => false);
|
||||||
List.generate(widget.signalColorList.length, (index) => false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateSelection(int index, bool value) {
|
void updateSelection(int index, bool value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedSignal[index] = value;
|
selectedColors[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(index, value); // 通知父组件
|
widget.onSelectionChanged(selected); // 通知父组件
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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.signalColorList.length, (index) {
|
children: List.generate(widget.colors.length, (index) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
value: selectedSignal[index],
|
value: selectedColors[index],
|
||||||
onChanged: (bool? value) {
|
onChanged: (bool? value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
updateSelection(index, value);
|
updateSelection(index, value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
activeColor: widget.signalColorList[index],
|
activeColor: widget.colors[index],
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(4), // 设置边角
|
borderRadius: BorderRadius.circular(4), // 设置边角
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
colorToString(widget.signalColorList[index]),
|
colorToString(widget.colors[index]),
|
||||||
style: TextStyle(color: widget.signalColorList[index]),
|
style: TextStyle(color: widget.colors[index]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -70,11 +70,10 @@ class _MulButtonState extends State<MulButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String colorToString(Color color) {
|
String colorToString(Color color) {
|
||||||
if (color == Color.fromARGB(255, 255, 0, 0)) return 'GPS(G)';
|
if (color == Colors.blue) return 'GPS';
|
||||||
if (color == Color.fromARGB(255, 0, 255, 0)) return 'GLONASS(R)';
|
if (color == Colors.red) return 'BDS';
|
||||||
if (color == Color.fromARGB(255, 0, 0, 255)) return 'GALILEO(E)';
|
if (color == Colors.green) return 'GLO';
|
||||||
if (color == Color.fromARGB(255, 255, 255, 0)) return 'BEIDOU(B)';
|
if (color == Colors.orange) return 'ALS';
|
||||||
if (color == Color.fromARGB(255, 0, 255, 255)) return 'QZSS(Q)';
|
|
||||||
return 'Unknown Color';
|
return 'Unknown Color';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
120
lib/sky/skyInfo.dart
Normal file
120
lib/sky/skyInfo.dart
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
250
lib/sky/skyPlot.dart
Normal file
250
lib/sky/skyPlot.dart
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
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;
|
||||||
|
}
|
@ -1,155 +0,0 @@
|
|||||||
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(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,348 +0,0 @@
|
|||||||
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
Normal file
29
plugins/gnss/.gitignore
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# 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/
|
10
plugins/gnss/.metadata
Normal file
10
plugins/gnss/.metadata
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# 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
|
3
plugins/gnss/CHANGELOG.md
Normal file
3
plugins/gnss/CHANGELOG.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## 0.0.1
|
||||||
|
|
||||||
|
* TODO: Describe initial release.
|
1
plugins/gnss/LICENSE
Normal file
1
plugins/gnss/LICENSE
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO: Add your license here.
|
39
plugins/gnss/README.md
Normal file
39
plugins/gnss/README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
7
plugins/gnss/analysis_options.yaml
Normal file
7
plugins/gnss/analysis_options.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
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
|
380
plugins/gnss/lib/gnss.dart
Normal file
380
plugins/gnss/lib/gnss.dart
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
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
|
||||||
|
}
|
46
plugins/gnss/lib/nmea/aam.dart
Normal file
46
plugins/gnss/lib/nmea/aam.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
40
plugins/gnss/lib/nmea/abm.dart
Normal file
40
plugins/gnss/lib/nmea/abm.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// 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'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
20
plugins/gnss/lib/nmea/ack.dart
Normal file
20
plugins/gnss/lib/nmea/ack.dart
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 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'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
40
plugins/gnss/lib/nmea/acn.dart
Normal file
40
plugins/gnss/lib/nmea/acn.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// 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'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
42
plugins/gnss/lib/nmea/ala.dart
Normal file
42
plugins/gnss/lib/nmea/ala.dart
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
66
plugins/gnss/lib/nmea/alc.dart
Normal file
66
plugins/gnss/lib/nmea/alc.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// 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,
|
||||||
|
});
|
||||||
|
}
|
55
plugins/gnss/lib/nmea/alf.dart
Normal file
55
plugins/gnss/lib/nmea/alf.dart
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
35
plugins/gnss/lib/nmea/alr.dart
Normal file
35
plugins/gnss/lib/nmea/alr.dart
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
108
plugins/gnss/lib/nmea/apb.dart
Normal file
108
plugins/gnss/lib/nmea/apb.dart
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
44
plugins/gnss/lib/nmea/arc.dart
Normal file
44
plugins/gnss/lib/nmea/arc.dart
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// 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
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
35
plugins/gnss/lib/nmea/bbm.dart
Normal file
35
plugins/gnss/lib/nmea/bbm.dart
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
58
plugins/gnss/lib/nmea/bec.dart
Normal file
58
plugins/gnss/lib/nmea/bec.dart
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
50
plugins/gnss/lib/nmea/bestpos.dart
Normal file
50
plugins/gnss/lib/nmea/bestpos.dart
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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"));
|
||||||
|
}
|
||||||
|
}
|
42
plugins/gnss/lib/nmea/bod.dart
Normal file
42
plugins/gnss/lib/nmea/bod.dart
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
61
plugins/gnss/lib/nmea/bwc.dart
Normal file
61
plugins/gnss/lib/nmea/bwc.dart
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
61
plugins/gnss/lib/nmea/bwr.dart
Normal file
61
plugins/gnss/lib/nmea/bwr.dart
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
37
plugins/gnss/lib/nmea/bww.dart
Normal file
37
plugins/gnss/lib/nmea/bww.dart
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
38
plugins/gnss/lib/nmea/dbk.dart
Normal file
38
plugins/gnss/lib/nmea/dbk.dart
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
38
plugins/gnss/lib/nmea/dbs.dart
Normal file
38
plugins/gnss/lib/nmea/dbs.dart
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
25
plugins/gnss/lib/nmea/dbt.dart
Normal file
25
plugins/gnss/lib/nmea/dbt.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
68
plugins/gnss/lib/nmea/deprecated.dart
Normal file
68
plugins/gnss/lib/nmea/deprecated.dart
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// 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;
|
66
plugins/gnss/lib/nmea/dor.dart
Normal file
66
plugins/gnss/lib/nmea/dor.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
25
plugins/gnss/lib/nmea/dpt.dart
Normal file
25
plugins/gnss/lib/nmea/dpt.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
66
plugins/gnss/lib/nmea/dsc.dart
Normal file
66
plugins/gnss/lib/nmea/dsc.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
32
plugins/gnss/lib/nmea/dse.dart
Normal file
32
plugins/gnss/lib/nmea/dse.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
23
plugins/gnss/lib/nmea/dtm.dart
Normal file
23
plugins/gnss/lib/nmea/dtm.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// 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) {}
|
||||||
|
}
|
75
plugins/gnss/lib/nmea/gga.dart
Normal file
75
plugins/gnss/lib/nmea/gga.dart
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
39
plugins/gnss/lib/nmea/gll.dart
Normal file
39
plugins/gnss/lib/nmea/gll.dart
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
88
plugins/gnss/lib/nmea/gns.dart
Normal file
88
plugins/gnss/lib/nmea/gns.dart
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
62
plugins/gnss/lib/nmea/gsa.dart
Normal file
62
plugins/gnss/lib/nmea/gsa.dart
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
69
plugins/gnss/lib/nmea/gsv.dart
Normal file
69
plugins/gnss/lib/nmea/gsv.dart
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// 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});
|
||||||
|
}
|
20
plugins/gnss/lib/nmea/hdt.dart
Normal file
20
plugins/gnss/lib/nmea/hdt.dart
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 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",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
31
plugins/gnss/lib/nmea/heading.dart
Normal file
31
plugins/gnss/lib/nmea/heading.dart
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
262
plugins/gnss/lib/nmea/parser.dart
Normal file
262
plugins/gnss/lib/nmea/parser.dart
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
71
plugins/gnss/lib/nmea/rmc.dart
Normal file
71
plugins/gnss/lib/nmea/rmc.dart
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
34
plugins/gnss/lib/nmea/rsa.dart
Normal file
34
plugins/gnss/lib/nmea/rsa.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
447
plugins/gnss/lib/nmea/sentence.dart
Normal file
447
plugins/gnss/lib/nmea/sentence.dart
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
78
plugins/gnss/lib/nmea/tagblock.dart
Normal file
78
plugins/gnss/lib/nmea/tagblock.dart
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
|
||||||
|
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');
|
||||||
|
}
|
234
plugins/gnss/lib/nmea/types.dart
Normal file
234
plugins/gnss/lib/nmea/types.dart
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
34
plugins/gnss/lib/nmea/vtg.dart
Normal file
34
plugins/gnss/lib/nmea/vtg.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
36
plugins/gnss/lib/nmea/zda.dart
Normal file
36
plugins/gnss/lib/nmea/zda.dart
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// 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)"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
97
plugins/gnss/lib/rtk_base.dart
Normal file
97
plugins/gnss/lib/rtk_base.dart
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
plugins/gnss/pubspec.yaml
Normal file
61
plugins/gnss/pubspec.yaml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
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
|
19
plugins/gnss/test/gnss_test.dart
Normal file
19
plugins/gnss/test/gnss_test.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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,18 +120,10 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: "13bca7191808a98707e61f119994a91ebc35f11a"
|
resolved-ref: "6e4c9b35dc86d027a0f0bfbc65747a4813d92a2c"
|
||||||
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