1
0
mirror of https://github.com/peterantypas/maiana.git synced 2025-05-16 07:20:08 -07:00
2021-09-09 08:14:28 -07:00

227 lines
8.6 KiB
C++

// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Parse NMEA AIS VDM strings without extra metadata.
//
// Handles a sequence of AIS VDM NMEA messages. Messages are composed of
// 1 to 10 lines. These lines are connected by a sequence number if there
// are more than 1 line required to make a message. NMEA AIS VDM lines are of
// the following form as a pseudo regular expression, where each line has 7
// comma separated fields:
//
// ![A-Z]{5},[1-9],[1-9],[0-9]?,[AB],armored_data,[0-5]\*[0-9A-F][0-9A-F]
//
// The fields are:
//
// 0. Start bang, 2 character talker and 3 character sentence.
// 1. Total number of sentences.
// 2. Current sentence number.
// 3. Sequence number.
// 4. VHF radio channel.
// 5. 6-bit per character encoded binary data (not Base64 encoding). The
// first character maps to the AIS message type.
// 6. The number of fill bits included in the last character, a star, and
// a 2 character hex xor checksum.
//
// An example single line message:
//
// !AIVDM,1,1,,A,14VIk0002sMM04vE>V9jGimn08RP,0*0D
//
// A multi-line message:
//
// clang-format off
// !SAVDM,2,1,4,B,55NGlfP00001L@GO??0lU=>0hUaaV2222222220O1p>454wV07PhE82Dk0CQ,0*64
// // NOLINT
// !SAVDM,2,2,4,B,84i@H0SmFH0,2*54
// clang-format on
//
// The library is structured around NmeaSentence, which manages each line of
// text. It validates the checksum and separates the field components.
//
// VdmStream takes a series of sentences, converts them to NmeaScentences, and
// then assembles the parts into complete messages. When it has all of the
// constituent sentences, it uses libais to decode the armored 6-bit payload
// into AisMsg instances.
//
// The VdmStream is not thread safe.
//
// See Also:
// http://catb.org/gpsd/AIVDM.html
// http://www.itu.int/rec/R-REC-M.1371/en
#ifndef LIBAIS_VDM_H_
#define LIBAIS_VDM_H_
#include <deque>
#include <memory>
#include <string>
#include <vector>
// #include "base/logging.h"
#include "ais.h"
namespace libais {
// No more than 10 sentences per message. The total is [1..9].
// The most observed in real data has been 4.
static const int kMaxSentences = 10;
// AIS receivers use the sequence to group multi-line messages.
static const int kNumSequenceChannels = 10;
// Computes the xor checksum of a string.
uint8_t Checksum(const string &content);
// Convert a number to a two character hex string.
string ToHex2(int32_t val);
// Returns the 2 upper case character xor checksum of a string.
string ChecksumHexString(const string &base);
bool ValidateChecksum(const string &line);
// Manages single lines of NMEA AIS VDM text.
class NmeaSentence {
public:
NmeaSentence(const string &talker, const string &sentence_type,
int sentence_total, int sentence_number, int sequence_number,
char channel, const string &body, int fill_bits,
int64_t line_number)
: talker_(talker),
sentence_type_(sentence_type),
sentence_total_(sentence_total),
sentence_number_(sentence_number),
sequence_number_(sequence_number),
channel_(channel),
body_(body),
fill_bits_(fill_bits),
line_number_(line_number) {}
// Returns a smart pointer for an instance of a NmeaSentence created from a
// line of text. Returns nullptr on failure. The line_number argument
// tracks the source location in a file or the count of lines pushed through
// a channel.
static std::unique_ptr<NmeaSentence> Create(const string &line,
int64_t line_number);
// Returns a composite NmeaSentence from multiple prior sentences that make up
// a multi-line message. The resulting sentence is such that the new payload
// can be decoded as one 6-bit encoded unit. Sets the sentence total and
// sentence number both to 1 so that it looks like a single line sentence.
// Returns a nullptr if the merge failed.
// Merging sentences from different messages or out of order messages will
// fail.
std::unique_ptr<NmeaSentence> Merge(
const vector<std::unique_ptr<NmeaSentence>> &prior_sentences) const;
// Reconstructs the NMEA AIS VDM sentence representation of this instance.
string ToString() const;
// Returns the hex digest of the 6-bit encoded body. Used for detection of
// multiple instances of a line in an input stream.
// TODO(schwehr): Consider mixing in the VHF channel into the text.
string ToMd5Digest() const;
// Returns true if another sentence is a part of the same multiline message.
// The given sentence must have the same channel, sequence number and total
// number of sentences as the instance sentence to be a part of the same AIS
// NMEA multi-line message.
//
// In the same message:
// !SAVDM,2,1,1,A,54a...
// !SAVDM,2,2,1,A,888...
//
// Different channels:
// !SAVDM,2,1,1,B,54a...
// !SAVDM,2,2,1,A,888...
//
// Different sequence:
// !SAVDM,2,1,8,A,54a...
// !SAVDM,2,2,2,A,888...
//
// Different totals:
// !SAVDM,2,1,1,A,54a...
// !SAVDM,3,2,1,A,888...
//
// TODO(schwehr): What should this return when called with the same sentence?
// !SAVDM,3,2,1,A,54a...
// !SAVDM,3,3,1,A,54a...
bool VerifyInSameMessage(const NmeaSentence &sentence) const;
// Accessors to get each of the fields or sub-fields of the sentence.
string talker() const { return talker_; }
string sentence_type() const { return sentence_type_; }
int sentence_total() const { return sentence_total_; }
int sentence_number() const { return sentence_number_; }
int sequence_number() const { return sequence_number_; }
char channel() const { return channel_; }
string body() const { return body_; }
int fill_bits() const { return fill_bits_; }
int64_t line_number() const { return line_number_; }
private:
// Which device type sent the message. AI is an AIS transceiver.
const string talker_;
// This will be VDM for an AIS message received and VDO for "own ship."
const string sentence_type_;
// Total number of lines that compose the message this is a part of.
const int sentence_total_;
// Which sentence is this? Ranges from 1 to sentence_total_.
const int sentence_number_;
// Logical channel for the AIS receiver. A receiver will put a multi-line
// message on a single channel and then move to the next channel. Ranges from
// 0 to 9, but most receivers will not use 0.
const int sequence_number_; // -1 if not valid.
// VHF channel. Will be either A or B. Some receivers do not record this.
const char channel_;
// The VDM 6-bit encoded/armored payload of the message.
const string body_;
// The number of padding. Should be zero for all messages except the last.
// Must be 0, 2, or 4.
const int fill_bits_;
const int64_t line_number_;
};
// This class processes a sequence of lines to find the AIS messages across
// lines. AIS messages come in groups of 1 or more lines. Its job is
// to return decoded AIS messages as libais::AisMsg instances as they are found
// in the stream.
// Sentences are assembled into messages and output in FIFO order.
class VdmStream {
public:
VdmStream() : line_number_(0), incoming_sentences_(kNumSequenceChannels) {}
// Returns true if the sentence was used or false if the line was ignored.
// A line will be ignored if it is not a valid VDM line or if it is a later
// part of a multi-line message but missing one or more initial lines.
bool AddLine(const string &line); // Was push
// Returns nullptr if there are not decoded messages currently available.
std::unique_ptr<libais::AisMsg> PopOldestMessage();
int size() const { return messages_.size(); }
bool empty() const { return messages_.empty(); }
private:
// Line number starts at 0 and is incremented to 1 with the first line.
int64_t line_number_;
// Decoded messages ready for pickup.
std::deque<std::unique_ptr<libais::AisMsg>> messages_;
// Sentences for each sequence number that have yet to get all the required
// parts to be complete.
vector<vector<std::unique_ptr<NmeaSentence>>> incoming_sentences_;
};
} // namespace libais
#endif // LIBAIS_VDM_H_