use crate::decode::nmea::NmeaAisMessage;
use crate::dsp::*;
use crate::prelude::Message;
use deku::reader::Reader;
use deku::DekuReader;
use num_complex::Complex;
use std::collections::HashSet;
use std::hash::Hash;
use std::io::Cursor;
use std::time::{SystemTime, UNIX_EPOCH};
pub const AIS_FREQ_A: f64 = 161.975e6;
pub const AIS_FREQ_B: f64 = 162.025e6;
pub const AIS_BAUD_RATE: f64 = 9600.0;
pub const AIS_SAMPLE_RATE_96K: u32 = 96000;
pub const AIS_SAMPLE_RATE_288K: u32 = 288000;
const MAX_AIS_LENGTH: usize = 128 * 8; const MIN_TRAINING_BITS: usize = 18;
const EXPECTED_CRC: u16 = 0xF0B8;
#[derive(Debug, Clone)]
pub struct AisDemodulatedMessage {
pub bits: Vec<u8>,
pub signal_level: f32,
pub channel: char,
pub timestamp: u64,
pub nmea_sentences: Vec<String>,
}
impl PartialEq for AisDemodulatedMessage {
fn eq(&self, other: &Self) -> bool {
self.bits == other.bits && self.channel == other.channel
}
}
impl Eq for AisDemodulatedMessage {}
impl Hash for AisDemodulatedMessage {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.bits.hash(state);
self.channel.hash(state);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum State {
Training,
StartFlag,
DataFcs,
}
#[derive(Debug, Clone)]
struct DecoderState {
state: State,
position: usize,
one_seq_count: u8,
prev_d: u8,
last_bit: u8,
msg_bits: Vec<u8>,
level_accumulator: f32,
level_count: usize,
}
impl Default for DecoderState {
fn default() -> Self {
Self {
state: State::Training,
position: 0,
one_seq_count: 0,
prev_d: 0,
last_bit: 0,
msg_bits: Vec::with_capacity(MAX_AIS_LENGTH),
level_accumulator: 0.0,
level_count: 0,
}
}
}
pub struct AisDemodulator {
dsk: fir::DownsampleKFilter,
rotate: rotate::Rotate,
ds2a: cic5::Downsample2CIC5,
ds2b: cic5::Downsample2CIC5,
fcic5a: cic5::FilterCIC5,
fcic5b: cic5::FilterCIC5,
cgf_a: afc::SquareFreqOffsetCorrection,
cgf_b: afc::SquareFreqOffsetCorrection,
fc_a: fir::FilterComplex,
fc_b: fir::FilterComplex,
s_a: scatter::ScatterPLL,
s_b: scatter::ScatterPLL,
cd_ema_a: Vec<ema::PhaseSearchEMA>,
cd_ema_b: Vec<ema::PhaseSearchEMA>,
decoder_states_a: Vec<DecoderState>,
decoder_states_b: Vec<DecoderState>,
sample_rate: u32,
}
impl AisDemodulator {
pub fn new(sample_rate: u32) -> Self {
Self {
dsk: fir::DownsampleKFilter::with_params(3, fir::BLACKMAN_HARRIS_28_3),
rotate: rotate::Rotate::new(std::f32::consts::PI * 25000.0 / 48000.0),
ds2a: cic5::Downsample2CIC5::new(),
ds2b: cic5::Downsample2CIC5::new(),
fcic5a: cic5::FilterCIC5::new(),
fcic5b: cic5::FilterCIC5::new(),
cgf_a: afc::SquareFreqOffsetCorrection::with_params(512, 187, false),
cgf_b: afc::SquareFreqOffsetCorrection::with_params(512, 187, false),
fc_a: fir::FilterComplex::with_taps(fir::COHERENT_TAPS),
fc_b: fir::FilterComplex::with_taps(fir::COHERENT_TAPS),
s_a: scatter::ScatterPLL::new(5), s_b: scatter::ScatterPLL::new(5), cd_ema_a: (0..5)
.map(|_| ema::PhaseSearchEMA::with_params(3))
.collect(),
cd_ema_b: (0..5)
.map(|_| ema::PhaseSearchEMA::with_params(3))
.collect(),
decoder_states_a: vec![DecoderState::default(); 5],
decoder_states_b: vec![DecoderState::default(); 5],
sample_rate,
}
}
pub fn demodulate(&mut self, iq_samples: &[Complex<f32>]) -> HashSet<AisDemodulatedMessage> {
if (self.sample_rate != AIS_SAMPLE_RATE_96K) & (self.sample_rate != AIS_SAMPLE_RATE_288K) {
panic!("This simplified demodulator only supports 96ks/s or 288ks/s sample rate.");
}
if iq_samples.is_empty() {
return HashSet::new();
}
let mut tag = Tag::default();
let iq_samples = if self.sample_rate == AIS_SAMPLE_RATE_288K {
self.dsk.receive(iq_samples, &mut tag)
} else {
iq_samples.to_vec()
};
let (channel_a, channel_b) = self.rotate.receive_dual(&iq_samples, &mut tag);
let ds2a = self.ds2a.receive(&channel_a, &mut tag);
let ds2b = self.ds2b.receive(&channel_b, &mut tag);
let fcic5a = self.fcic5a.receive(&ds2a, &mut tag);
let fcic5b = self.fcic5b.receive(&ds2b, &mut tag);
let cgf_a = self.cgf_a.receive(&fcic5a, &mut tag);
let cgf_b = self.cgf_b.receive(&fcic5b, &mut tag);
let fc_a = self.fc_a.receive(&cgf_a, &mut tag);
let fc_b = self.fc_b.receive(&cgf_b, &mut tag);
let s_a = self.s_a.receive_scatter(&fc_a, &mut tag);
let s_b = self.s_b.receive_scatter(&fc_b, &mut tag);
let mut messages = HashSet::new();
for (i, samples) in s_a.iter().enumerate() {
let syms = self.cd_ema_a[i].receive(samples, &mut tag);
let msgs = self.decode_ais_message_stateful(&syms, 'A', i, &mut tag);
for msg in msgs {
messages.insert(msg);
}
}
for (i, samples) in s_b.iter().enumerate() {
let syms = self.cd_ema_b[i].receive(samples, &mut tag);
let msgs = self.decode_ais_message_stateful(&syms, 'B', i, &mut tag);
for msg in msgs {
messages.insert(msg);
}
}
messages
}
fn decode_ais_message_stateful(
&mut self,
symbols: &[f32],
channel: char,
index: usize,
tag: &mut Tag,
) -> Vec<AisDemodulatedMessage> {
let mut accumulator = vec![];
let decoder_state = match channel {
'A' => &mut self.decoder_states_a[index],
'B' => &mut self.decoder_states_b[index],
_ => panic!("Invalid channel"),
};
if symbols.is_empty() {
return accumulator;
}
if decoder_state.state == State::Training && decoder_state.position == 0 {
if let Some(&first) = symbols.first() {
decoder_state.prev_d = if first > 0.0 { 1 } else { 0 };
}
}
for &s in symbols.iter() {
let d: u8 = if s > 0.0 { 1 } else { 0 };
let bit: u8 = if d == decoder_state.prev_d { 1 } else { 0 };
decoder_state.prev_d = d;
match decoder_state.state {
State::Training => {
if bit != decoder_state.last_bit {
decoder_state.position += 1;
} else if decoder_state.position > MIN_TRAINING_BITS {
decoder_state.state = State::StartFlag;
decoder_state.position = if bit == 1 { 3 } else { 1 };
} else {
decoder_state.position = 0;
}
}
State::StartFlag => {
if decoder_state.position == 7 {
if bit == 0 {
decoder_state.state = State::DataFcs;
decoder_state.msg_bits.clear();
decoder_state.one_seq_count = 0;
decoder_state.position = 0;
decoder_state.level_accumulator = 0.0;
decoder_state.level_count = 0;
} else {
decoder_state.state = State::Training;
decoder_state.position = 0;
}
} else if bit == 1 {
decoder_state.position += 1;
} else {
decoder_state.state = State::Training;
decoder_state.position = 0;
}
}
State::DataFcs => {
decoder_state.msg_bits.push(bit);
decoder_state.position += 1;
decoder_state.level_accumulator += tag.sample_lvl;
decoder_state.level_count += 1;
if bit == 1 {
if decoder_state.one_seq_count == 5 {
let level = if decoder_state.level_count > 0 {
decoder_state.level_accumulator / decoder_state.level_count as f32
} else {
0.0
};
for _ in 1..=7 {
if !decoder_state.msg_bits.is_empty() {
decoder_state.msg_bits.pop();
}
}
let bytes = Self::pack_bits_to_bytes_lsb(&decoder_state.msg_bits);
if bytes.len() >= 3 {
let calc_crc = Self::crc16(&bytes);
if calc_crc == EXPECTED_CRC {
let signal_level = if level != 0.0 {
10.0f32 + f32::log10(level)
} else {
0.0
};
let data = &bytes[..bytes.len() - 2];
let rxtime = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let msg = AisDemodulatedMessage {
bits: data.to_vec(),
signal_level,
channel,
timestamp: rxtime,
nmea_sentences: vec![],
};
if msg.validate() {
accumulator.push(msg);
}
}
}
decoder_state.state = State::Training;
decoder_state.position = 0;
} else {
decoder_state.one_seq_count += 1;
}
} else {
if decoder_state.one_seq_count == 5 {
if !decoder_state.msg_bits.is_empty() {
decoder_state.msg_bits.pop();
decoder_state.position -= 1;
}
}
decoder_state.one_seq_count = 0;
}
if decoder_state.msg_bits.len() >= MAX_AIS_LENGTH {
decoder_state.state = State::Training;
decoder_state.position = 0;
decoder_state.one_seq_count = 0;
decoder_state.msg_bits.clear();
}
}
}
decoder_state.last_bit = bit;
}
accumulator
}
fn crc16(data: &[u8]) -> u16 {
let mut crc: u16 = 0xFFFF;
let poly: u16 = 0x8408;
for &byte in data {
let mut b = byte;
for _ in 0..8 {
let mix = (crc ^ (b as u16)) & 0x01;
crc >>= 1;
if mix != 0 {
crc ^= poly;
}
b >>= 1;
}
}
crc
}
fn pack_bits_to_bytes_lsb(bits: &[u8]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(bits.len().div_ceil(8));
for chunk in bits.chunks(8) {
let mut byte = 0u8;
for (i, &bit) in chunk.iter().enumerate() {
if bit != 0 {
byte |= 1 << i; }
}
bytes.push(byte);
}
bytes
}
}
impl AisDemodulatedMessage {
pub fn validate(&self) -> bool {
let bit_len = self.bits.len() * 8;
if bit_len == 0 {
return true;
}
let msg_type = (self.bits[0] >> 2) & 0x3F;
if !(1..=27).contains(&msg_type) {
return false;
}
const ML: [usize; 27] = [
149, 149, 149, 168, 418, 88, 72, 56, 168, 70, 168, 72, 40, 40, 88, 92, 80, 168, 312,
70, 271, 145, 154, 160, 72, 60, 96,
];
if bit_len < ML[msg_type as usize - 1] {
return false;
}
true
}
pub fn encode_nmea(&self) -> Self {
let payload = encode_ais_6bit_ascii(&self.bits);
let max_payload_len = 56;
let mut sentences = Vec::new();
let total_fragments = payload.len().div_ceil(max_payload_len) as u8;
for i in 0..total_fragments {
let start = (i as usize) * max_payload_len;
let end = ((i as usize + 1) * max_payload_len).min(payload.len());
let frag_payload = &payload[start..end];
let fill_bits = if i == total_fragments - 1 {
let total_bits = self.bits.len() * 8;
(6 - (total_bits % 6)) % 6
} else {
0
};
let nmea_msg = NmeaAisMessage {
message_type: "AIVDM".to_string(),
fragment_count: total_fragments,
fragment_number: i + 1,
message_id: if total_fragments > 1 {
Some("1".to_string())
} else {
None
},
channel: self.channel,
payload: frag_payload.to_string(),
fill_bits: fill_bits as u8,
checksum: 0, };
let fields = [
nmea_msg.message_type.clone(),
nmea_msg.fragment_count.to_string(),
nmea_msg.fragment_number.to_string(),
nmea_msg.message_id.clone().unwrap_or_default(),
nmea_msg.channel.to_string(),
nmea_msg.payload.clone(),
nmea_msg.fill_bits.to_string(),
];
let data_part = fields.join(",");
let checksum = data_part.bytes().fold(0u8, |acc, b| acc ^ b);
let sentence = format!("!{}*{:02X}", data_part, checksum);
sentences.push(sentence);
}
Self {
bits: self.bits.clone(),
signal_level: self.signal_level,
channel: self.channel,
timestamp: self.timestamp,
nmea_sentences: sentences,
}
}
pub fn decode(&self) -> Option<Message> {
if self.bits.len() < 14 {
return None;
}
let cursor = Cursor::new(&self.bits);
let mut reader = Reader::new(cursor);
Message::from_reader_with_ctx(&mut reader, ()).ok()
}
}
fn encode_ais_6bit_ascii(bytes: &[u8]) -> String {
let mut result = String::new();
let mut bit_buffer = 0u32;
let mut bits_in_buffer = 0;
for &byte in bytes {
bit_buffer = (bit_buffer << 8) | (byte as u32);
bits_in_buffer += 8;
while bits_in_buffer >= 6 {
let six_bit_val = ((bit_buffer >> (bits_in_buffer - 6)) & 0x3F) as u8;
result.push(ais_6bit_to_char(six_bit_val));
bits_in_buffer -= 6;
}
}
if bits_in_buffer > 0 {
let six_bit_val = ((bit_buffer << (6 - bits_in_buffer)) & 0x3F) as u8;
result.push(ais_6bit_to_char(six_bit_val));
}
result
}
fn ais_6bit_to_char(val: u8) -> char {
match val {
0..=39 => (val + 48) as char,
40..=63 => (val + 56) as char,
_ => '?',
}
}
#[cfg(test)]
mod tests {
use crate::prelude::Message;
use super::*;
#[test]
fn test_encode_nmea() {
let bits = vec![
20, 58, 86, 192, 110, 0, 0, 0, 1, 1, 96, 231, 124, 181, 32, 20, 212, 6, 3, 21, 20, 115,
192, 0, 0, 0, 0, 0, 0, 99, 3, 4, 70, 15, 192, 24, 240, 0, 193, 96, 32, 21, 146, 20, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
let msg = AisDemodulatedMessage {
bits: bits.clone(),
signal_level: 42.0,
channel: 'B',
timestamp: 1_700_000_000,
nmea_sentences: vec![],
};
assert!(msg.validate(), "AIS message should be valid");
let nmea_encoded = msg.encode_nmea();
assert!(
!nmea_encoded.nmea_sentences.is_empty(),
"Should produce at least one NMEA sentence"
);
assert_eq!(
nmea_encoded.nmea_sentences[0],
"!AIVDM,2,1,1,B,53aFh6p000010F3WO;DP5=@60iDDLt000000001S0hA63t0Ht031H20E,0*7F"
);
assert_eq!(
nmea_encoded.nmea_sentences[1],
"!AIVDM,2,2,1,B,TQ@000000000000,2*53"
);
let sentence_refs: Vec<&str> = nmea_encoded
.nmea_sentences
.iter()
.map(|s| s.as_str())
.collect();
let message = Message::from_nmea(&sentence_refs).unwrap();
if let Message::StaticAndVoyageData(msg) = message {
assert_eq!(msg.mmsi, 244690971);
assert_eq!(msg.destination, "LE HAVRE");
assert_eq!(msg.shipname, "HASTA LUEGO");
assert_eq!(msg.callsign, "PE 9725");
} else {
panic!("Decoded message is not StaticAndVoyageData");
}
}
}