/* Copyright (c) 2016-2020 Peter Antypas This file is part of the MAIANAâ„¢ transponder firmware. The firmware is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see */ #include #include "AISMessages.hpp" #include "Utils.hpp" #include "_assert.h" #include #include #include using namespace std; // A good quick reference for AIS message format: http://www.navcen.uscg.gov/?pageName=AISMessages AISMessage::AISMessage () { mMMSI = 0; mRI = 0; mType = 0; } AISMessage::~AISMessage () { } uint8_t AISMessage::type() const { return mType; } uint8_t AISMessage::repeatIndicator() const { return mRI; } uint32_t AISMessage::mmsi() const { return mMMSI; } bool AISMessage::decode(const RXPacket &) { // The base class method should never be called ASSERT(false); // This will never execute but the compiler is not smart enough to know it return false; } void AISMessage::encode(const StationData &station, TXPacket &) { mMMSI = station.mmsi; } void AISMessage::addBits(uint8_t *bitVector, uint16_t &size, uint32_t value, uint8_t numBits) { ASSERT(numBits > 0 && numBits <= 32); uint16_t pos = size; for ( uint8_t bit = 0; bit < numBits; ++bit, value >>= 1 ) { bitVector[pos + numBits-bit-1] = value & 1; } size += numBits; } void AISMessage::putBits(uint8_t *bitVector, uint32_t value, uint8_t numBits) { // This is used for HDLC framing uint16_t pos = 0; for ( uint8_t bit = 0; bit < numBits; ++bit, value >>= 1 ) { bitVector[pos++] = value & 0x01; } } void AISMessage::reverseEachByte(uint8_t *bitVector, uint16_t size) { ASSERT(size % 8 == 0); for ( uint16_t i = 0; i < size; i += 8 ) { for ( uint8_t j = 0; j < 4; ++j ) { swap(bitVector[i+j], bitVector[i+7-j]); } } } void AISMessage::addString(uint8_t *bitVector, uint16_t &size, const string &value, uint8_t maxChars) { ASSERT(value.length() <= maxChars); ASSERT(maxChars < 30); // There should be no application for such long strings here char s[30]; memset(s, 0, sizeof s); strncpy(s, value.c_str(), value.length()); uint8_t buffer[32]; for ( uint8_t c = 0; c < maxChars; ++c ) { uint8_t byte = s[c] >= 64 ? s[c]-64 : s[c]; buffer[c] = byte; } for ( uint8_t c = 0; c < maxChars; ++c ) addBits(bitVector, size, buffer[c], 6); } void AISMessage::payloadToBytes(uint8_t *bitVector, uint16_t numBits, uint8_t *byteArray) { for ( uint16_t i = 0; i < numBits; i += 8 ) { uint8_t byte = 0; for ( uint8_t b = 0; b < 8; ++b ) { byte |= (bitVector[i+b] << b); } byteArray[i/8] = byte; } } void AISMessage::finalize(uint8_t *payload, uint16_t &size, TXPacket &packet) { // Nothing we send exceeds 256 bits, including preambles and such. 40 bytes is more than enough. uint8_t bytes[40]; // CRC-CCITT calculation payloadToBytes(payload, size, bytes); uint16_t crc = Utils::crc16(bytes, size/8); uint8_t crcL = crc & 0x00ff; uint8_t crcH = (crc & 0xff00) >> 8; addBits(payload, size, crcL, 8); addBits(payload, size, crcH, 8); payloadToBytes(payload, size, bytes); // Encoding for transmission reverseEachByte(payload, size); bitStuff(payload, size); constructHDLCFrame(payload, size); nrziEncode(payload, size, packet); packet.pad(); } void AISMessage::bitStuff(uint8_t *buff, uint16_t &size) { uint16_t numOnes = 0; for ( uint16_t i = 0; i < size; ++i ) { switch(buff[i]) { case 0: numOnes = 0; break; case 1: ++numOnes; if ( numOnes == 5 ) { // Insert a 0 right after this one memmove(buff + i + 2, buff + i + 1, size-i-1); buff[i+1] = 0; ++size; } break; default: ASSERT(false); } } } void AISMessage::constructHDLCFrame(uint8_t *buff, uint16_t &size) { /* * As a class B "CS" transponder, we don't transmit a full ramp byte because * we have to listen for a few bits into each slot for Clear Channel Assessment. * Also, this implementation only adds 3 bits for ramp down, just in case * our TX bit clock is a little lazy. Not what the ITU standard says, but no * reasonable receiver should care about ramp-down bits. It's only what goes * between the 0x7E markers that counts. */ // Make room for 35 bits at the front memmove(buff+35, buff, size); size += 35; putBits(buff, 0xFF, 3); // 3 ramp bits. That's all we can afford. putBits(buff+3, 0b010101010101010101010101, 24); // 24 training bits (ramp will actually continue during the first 1-2) putBits(buff+27, 0x7e, 8); // HDLC start flag // Now append the end marker and ramp-down bits addBits(buff, size, 0x7e, 8); // HDLC stop flag addBits(buff, size, 0x00, 3); // Ramp down } void AISMessage::nrziEncode(uint8_t *buff, uint16_t &size, TXPacket &packet) { uint8_t prevBit = 1; // Arbitrarily starting with 1 packet.addBit(prevBit); for ( uint16_t i = 0; i < size; ++i ) { if ( buff[i] == 0 ) { packet.addBit(!prevBit); prevBit = !prevBit; } else { packet.addBit(prevBit); } } // The TXPacket is now populated with the sequence of bits that need to be sent } #if 0 /////////////////////////////////////////////////////////////////////////////// // // AISMessage123 // /////////////////////////////////////////////////////////////////////////////// AISMessage123::AISMessage123() { } bool AISMessage123::decode(const RXPacket &packet) { mType = packet.messageType(); mRI = packet.repeatIndicator(); mMMSI = packet.mmsi(); sog = packet.bits(50, 10) / 10.0f; longitude = Utils::coordinateFromUINT32(packet.bits(61, 28), 28); latitude = Utils::coordinateFromUINT32(packet.bits(89, 27), 27); return true; } #endif /////////////////////////////////////////////////////////////////////////////// // // AISMessage15 // /////////////////////////////////////////////////////////////////////////////// AISMessage15::AISMessage15() { memset(&targets, 0, sizeof targets); } bool AISMessage15::decode(const RXPacket &packet) { mType = packet.messageType(); mRI = packet.repeatIndicator(); mMMSI = packet.mmsi(); uint8_t bit = 40; // A message 15 has up to 3 targets for ( uint8_t i = 0; i < 3; ++i ) { if ( bit >= packet.size()-16 ) break; targets[i].mmsi = packet.bits(bit, 30); bit += 30; targets[i].messageType = (uint8_t)packet.bits(bit, 6); bit += 6; // Ignore slot offset plus 2 spare bits per record bit += 14; } return true; // Would we ever return false? } /////////////////////////////////////////////////////////////////////////////// // // AISMessage18 // /////////////////////////////////////////////////////////////////////////////// AISMessage18::AISMessage18() { mType = 18; } void AISMessage18::encode(const StationData &station, TXPacket &packet) { AISMessage::encode(station, packet); // TODO: Perhaps this shouldn't live on the stack? uint8_t payload[MAX_AIS_TX_PACKET_SIZE]; uint16_t size = 0; uint32_t value; packet.setMessageType("18"); value = mType; addBits(payload, size, value, 6); // Message type value = mRI; addBits(payload, size, value, 2); // Repeat Indicator value = mMMSI; addBits(payload, size, value, 30); // MMSI value = 0; addBits(payload, size, value, 8); // Spare bits value = (uint32_t)(sog * 10); addBits(payload, size, value, 10); // Speed (knots x 10) value = 1; addBits(payload, size, value, 1); // Position accuracy is high value = Utils::coordinateToUINT32(longitude); addBits(payload, size, value, 28); // Longitude value = Utils::coordinateToUINT32(latitude); addBits(payload, size, value, 27); // Latitude value = (uint32_t)(cog * 10); addBits(payload, size, value, 12); // COG value = 511; addBits(payload, size, value, 9); // We don't know our heading value = utc % 60; addBits(payload, size, value, 6); // UTC second. value = 0; addBits(payload, size, value, 2); // Spare value = 1; addBits(payload, size, value, 1); // We are a "CS" unit value = 0; addBits(payload, size, value, 1); // We have no display value = 0; addBits(payload, size, value, 1); // We have no DSC value = 0; addBits(payload, size, value, 1); // We don't switch frequencies so this doesn't matter value = 0; addBits(payload, size, value, 1); // We do not respond to message 22 to switch frequency value = 0; addBits(payload, size, value, 1); // We operate in autonomous and continuous mode value = 0; addBits(payload, size, value, 1); // No RAIM value = 1; addBits(payload, size, value, 1); // We use ITDMA (as a CS unit) value = DEFAULT_COMM_STATE; addBits(payload, size, value, 19); // Standard communication state flag for CS units #if DEV_MODE ASSERT(size == 168); #endif finalize(payload, size, packet); } #if 0 bool AISMessage18::decode(const RXPacket &packet) { mType = packet.messageType(); mRI = packet.repeatIndicator(); mMMSI = packet.mmsi(); sog = packet.bits(46, 10) / 10.0f; longitude = Utils::coordinateFromUINT32(packet.bits(57, 28), 28); latitude = Utils::coordinateFromUINT32(packet.bits(85, 27), 27); return true; } #endif /////////////////////////////////////////////////////////////////////////////// // // AISMessage24A // /////////////////////////////////////////////////////////////////////////////// AISMessage24A::AISMessage24A() { mType = 24; } void AISMessage24A::encode(const StationData &station, TXPacket &packet) { AISMessage::encode(station, packet); packet.setMessageType("24A"); uint8_t payload[MAX_AIS_TX_PACKET_SIZE]; uint16_t size = 0; uint32_t value; value = mType; addBits(payload, size, value, 6); // Message type value = mRI; addBits(payload, size, value, 2); // Repeat Indicator value = mMMSI; addBits(payload, size, value, 30); // MMSI value = 0; addBits(payload, size, value, 2); // Part number (0 for 24A) addString(payload, size, station.name, 20); // Station name finalize(payload, size, packet); } /////////////////////////////////////////////////////////////////////////////// // // AISMessage24B // /////////////////////////////////////////////////////////////////////////////// AISMessage24B::AISMessage24B() { mType = 24; } void AISMessage24B::encode(const StationData &station, TXPacket &packet) { AISMessage::encode(station, packet); packet.setMessageType("24B"); uint8_t payload[MAX_AIS_TX_PACKET_SIZE]; uint16_t size = 0; uint32_t value; value = mType; addBits(payload, size, value, 6); // Message type value = mRI; addBits(payload, size, value, 2); // Repeat Indicator value = mMMSI; addBits(payload, size, value, 30); // MMSI value = 1; addBits(payload, size, value, 2); // Part number (1 for 24B) value = station.type; addBits(payload, size, value, 8); // Type of ship addString(payload, size, "", 7); // Vendor ID -- not available addString(payload, size, station.callsign, 7); if ( station.len == 0 || station.beam == 0 ) { value = 0; } else { uint16_t A,B,C,D; C = station.portOffset; D = station.beam - C; A = station.bowOffset; B = station.len - A; if ( D > 63 ) D = 63; if ( C > 63 ) C = 63; if ( B > 511 ) B = 511; if ( A > 511 ) A = 511; value = D | (C << 6) | (B << 12) | (A << 21); } addBits(payload, size, value, 30); // Dimension information value = 3; addBits(payload, size, value, 4); // Using GPS/GLONASS value = 0; addBits(payload, size, value, 2); // Spare bits finalize(payload, size, packet); }