Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for custom sentences #3

Merged
merged 9 commits into from
Aug 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

Version 2.1.0
-------------

* Adds support for custom sentence types

Version 2.0.0
-------------

Expand Down Expand Up @@ -41,4 +46,4 @@ Version 1.0.0+1
Initial release.

* Added `NmeaSentence`, `NmeaSentenceType`, `ChecksumSentence`, `TalkerSentence`, `QuerySentence`,
`ProprietarySentence`, `NmeaUtils` and `NmeaDecoder` classes.
`ProprietarySentence`, `NmeaUtils` and `NmeaDecoder` classes.
2 changes: 2 additions & 0 deletions lib/nmea.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export 'src/nmea_sentence.dart'
show NmeaSentence, nmeaPrefix, nmeaSuffix, nmeaFieldSeparator;
export 'src/proprietary_sentence.dart'
show ProprietarySentence, nmeaProprietaryDenominator, nmeaProprietaryPrefix;
export 'src/custom_sentence.dart' show CustomSentence;
export 'src/talker_sentence.dart' show TalkerSentence;
export 'src/multipart_sentence.dart' show MultipartSentence;
export 'src/query_sentence.dart' show QuerySentence, nmeaQueryDenominator;
Expand All @@ -20,6 +21,7 @@ export 'src/checksum_sentence.dart'
export 'src/nmea_decoder.dart'
show
NmeaDecoder,
CustomSentenceFactory,
ProprietarySentenceFactory,
TalkerSentenceFactory,
OptionalProprietarySentenceFactory,
Expand Down
8 changes: 3 additions & 5 deletions lib/src/checksum_sentence.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,9 @@ class ChecksumSentence extends NmeaSentence {
/// This is the actual sentence, without any fixtures, which is used to
/// calculate the checksum.
@override
String get rawWithoutFixtures =>
_rawWithoutFixtures ??= super.rawWithoutFixtures.substring(
0,
super.raw.length -
4); // -4 to remove the checksum + separator character
String get rawWithoutFixtures => _rawWithoutFixtures ??= (hasChecksum
? super.rawWithoutFixtures.split(nmeaChecksumSeparator).first
: super.rawWithoutFixtures); // remove the checksum + separator character

/// The [ChecksumSentence] constructor has the same parameters as the parent
/// [NmeaSentence] does.
Expand Down
24 changes: 24 additions & 0 deletions lib/src/custom_sentence.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:nmea/src/checksum_sentence.dart';
import 'package:nmea/src/nmea_sentence.dart';
import 'package:nmea/src/nmea_sentence_type.dart';

/// The data format is completely customizable, as long as it fits in the general
/// envelope of an NMEA0183 sentence (begins with '$' and ends with windows-
/// style newline characters).
class CustomSentence extends ChecksumSentence {
ricardoboss marked this conversation as resolved.
Show resolved Hide resolved
/// Returns the identifier used to identify this sentence type
final String identifier;
final bool validateChecksums;

CustomSentence(
{required this.identifier,
required super.raw,
this.validateChecksums = true})
: super(
type: NmeaSentenceType.unknown,
prefix: nmeaPrefix,
);

@override
bool get hasValidChecksum => super.hasValidChecksum || !validateChecksums;
}
32 changes: 30 additions & 2 deletions lib/src/nmea_decoder.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import 'dart:async';
import 'dart:convert';

import 'package:nmea/src/custom_sentence.dart';
import 'package:nmea/src/multipart_sentence.dart';
import 'package:nmea/src/nmea_sentence.dart';
import 'package:nmea/src/proprietary_sentence.dart';
import 'package:nmea/src/query_sentence.dart';
import 'package:nmea/src/talker_sentence.dart';

/// A function to create a [CustomSentence] from a raw string and an
/// identifier.
typedef CustomSentenceFactory = CustomSentence Function(String line);

/// A function to create a [ProprietarySentence] from a raw string and a
/// manufacturer id.
typedef ProprietarySentenceFactory = ProprietarySentence Function(String line);
Expand Down Expand Up @@ -34,6 +39,7 @@ typedef OptionalNmeaSentenceFactory = NmeaSentence? Function(String line);
/// this transformer doesn't buffer the input and tries to parse every piece of
/// data it receives as a complete NMEA sentence.
class NmeaDecoder extends StreamTransformerBase<String, NmeaSentence> {
final Map<String, CustomSentenceFactory> _customGenerators = {};
final Map<String, ProprietarySentenceFactory> _proprietaryGenerators = {};
final Map<String, TalkerSentenceFactory> _talkerGenerators = {};
final List<MultipartSentence<dynamic>> _incompleteSentences = [];
Expand Down Expand Up @@ -68,6 +74,14 @@ class NmeaDecoder extends StreamTransformerBase<String, NmeaSentence> {
this.onlyAllowValid = false,
});

/// Registers a [CustomSentenceFactory] for a given identifier.
void registerCustomSentence(
String identifier,
CustomSentenceFactory factory,
) {
_customGenerators[identifier] = factory;
}

/// Registers a [ProprietarySentenceFactory] for a given manufacturer id.
void registerProprietarySentence(
String manufacturer,
Expand Down Expand Up @@ -143,7 +157,21 @@ class NmeaDecoder extends StreamTransformerBase<String, NmeaSentence> {
return decodeQuery(line);
}

return decodeTalker(line);
return decodeTalker(line) ?? decodeCustom(line);
}

/// Tries to decode the given line as a custom sentence.
/// The identifier is extracted from the line and the corresponding
/// [CustomSentenceFactory] is used to create the sentence.
/// If none is found `null` is returned.
CustomSentence? decodeCustom(String line) {
Wackymax marked this conversation as resolved.
Show resolved Hide resolved
for (final identifier in _customGenerators.keys) {
if (line.startsWith(nmeaPrefix + identifier)) {
return _customGenerators[identifier]!(line);
}
}

return null;
}

/// Tries to decode the given line as a proprietary sentence.
Expand Down Expand Up @@ -175,7 +203,7 @@ class NmeaDecoder extends StreamTransformerBase<String, NmeaSentence> {
/// If no fallback is registered, `null` is returned.
TalkerSentence? decodeTalker(String line) {
final separatorIndex = line.indexOf(nmeaFieldSeparator);
if (separatorIndex < 0 || line.length < 6) {
if (separatorIndex < 3 || line.length < 6) {
return null;
}

Expand Down
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ homepage: https://github.com/ricardoboss/dart_nmea
repository: https://github.com/ricardoboss/dart_nmea
issue_tracker: https://github.com/ricardoboss/dart_nmea/issues

version: 2.0.0
version: 2.1.0

environment:
sdk: ">=2.17.1 <3.0.0"
Expand All @@ -16,4 +16,4 @@ dev_dependencies:
lints: ^2.0.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# following page: https://dart.dev/tools/pub/pubspec
Wackymax marked this conversation as resolved.
Show resolved Hide resolved
54 changes: 54 additions & 0 deletions test/nmea_decoder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,54 @@ void main() {
expect(decoded.raw, equals("\$--TES,123,345*56"));
});

test("decodes custom sentences with invalid checksums", () {
final decoder = NmeaDecoder()
..registerCustomSentence(
TestCustomSentence.id, (line) => TestCustomSentence(raw: line));
final decoded = decoder.decodeCustom("\$CST,123,345*56");

expect(decoded, isNotNull);
expect(decoded!.fields, equals(["CST", "123", "345"]));
expect(decoded.rawWithoutFixtures, "CST,123,345");
expect(decoded.hasChecksum, isTrue);
expect(decoded.checksum, equals("56"));
expect(decoded.actualChecksum, equals("46"));
expect(decoded.valid, isFalse);
expect(decoded.raw, equals("\$CST,123,345*56"));
});

Wackymax marked this conversation as resolved.
Show resolved Hide resolved
test("decodes custom sentences with valid checksums", () {
final decoder = NmeaDecoder()
..registerCustomSentence(
TestCustomSentence.id, (line) => TestCustomSentence(raw: line));
final decoded = decoder.decodeCustom("\$CST,123,345*46");

expect(decoded, isNotNull);
expect(decoded!.fields, equals(["CST", "123", "345"]));
expect(decoded.rawWithoutFixtures, "CST,123,345");
expect(decoded.hasChecksum, isTrue);
expect(decoded.checksum, equals("46"));
expect(decoded.actualChecksum, equals("46"));
expect(decoded.valid, isTrue);
expect(decoded.raw, equals("\$CST,123,345*46"));
});

test("decodes custom sentences with skipped checksums", () {
final decoder = NmeaDecoder()
..registerCustomSentence(TestCustomSentence.id,
(line) => TestCustomSentence(raw: line, validateChecksums: false));
final decoded = decoder.decodeCustom("\$CST,123,345*56");

expect(decoded, isNotNull);
expect(decoded!.fields, equals(["CST", "123", "345"]));
expect(decoded.rawWithoutFixtures, "CST,123,345");
expect(decoded.hasChecksum, isTrue);
expect(decoded.checksum, equals("56"));
expect(decoded.actualChecksum, equals("46"));
expect(decoded.valid, isTrue);
expect(decoded.raw, equals("\$CST,123,345*56"));
});

test("decodes query sentences", () {
final decoder = NmeaDecoder();
final decoded = decoder.decodeQuery("\$GPECQ,RMC");
Expand All @@ -45,3 +93,9 @@ class TestTalkerSentence extends TalkerSentence {

TestTalkerSentence({required super.raw});
}

class TestCustomSentence extends CustomSentence {
static const String id = "CST";
TestCustomSentence({required super.raw, super.validateChecksums = true})
: super(identifier: id);
}