pub mod analyzer_json;
pub mod bits;
pub mod bus_protocol;
mod charset_generated;
pub mod codec;
pub mod db;
pub mod decode;
pub mod encode;
pub mod fastpacket;
pub mod format;
pub mod frame;
pub mod from_json;
pub mod iso_tp;
pub mod os;
pub mod output;
pub mod pgn_list;
pub mod quirk;
pub mod reassembly;
pub(crate) use charset_generated::rds_g0_char;
mod schema_data;
mod schema_data_j1939;
mod schema_data_quick;
pub mod snapshot;
pub mod source;
pub mod startup;
pub mod types;
pub mod units;
pub use bus_protocol::{BusProtocol, UnknownBusProtocol};
pub use db::{PgnDatabase, Units};
pub use decode::{DecodeError, DecodedField, DecodedPgn, FieldValue};
pub use encode::{EncodeError, EncodeValue, PgnBuilder, Raw};
pub use frame::{ADDR_GLOBAL, ADDR_NULL, FASTPACKET_MAX_SIZE, RAWFRAME_MAX_SIZE, RawFrame};
pub use from_json::json_to_decoded;
pub use reassembly::{
FramePacketType, Reassembled, Reassembler, ReassemblyError, summarize_incomplete,
};
pub use schema_data::{COPYRIGHT_ID, SCHEMA_HASH, VERSION as CANBOAT_JSON_VERSION};
pub use schema_data::{field, pgn};
pub use source::{Decoder, FrameSource};
pub use startup::{CANBOAT_BEM, format_iso_ms, parse_iso_ms, startup_record};
pub use types::{
BitLookupTable, BitLookupValue, Continuation, FieldInfo, FieldRef, FieldType,
IndirectLookupTable, IndirectLookupValue, LookupTable, LookupValue, PacketType, PgnInfo,
};
#[cfg(test)]
mod smoke {
use super::*;
#[test]
fn loads_full_database() {
let db = PgnDatabase::embedded(crate::engine::Units::Metric);
assert!(!db.version.is_empty());
assert!(!db.schema_version.is_empty());
assert!(
db.pgn_count() >= 500,
"expected >=500 PGNs, got {}",
db.pgn_count()
);
}
#[test]
fn finds_iso_address_claim() {
let db = PgnDatabase::embedded(crate::engine::Units::Metric);
let pgn = db.first_pgn(60928).expect("PGN 60928 must exist");
assert_eq!(pgn.id, "isoAddressClaim");
assert_eq!(pgn.packet_type, PacketType::Single);
assert_eq!(pgn.fields.len(), 10);
let mfr = &pgn.fields[1];
assert_eq!(mfr.field_type, Some(FieldType::Lookup));
assert_eq!(mfr.lookup_enumeration, Some("MANUFACTURER_CODE"));
}
#[test]
fn resolves_manufacturer_lookup() {
let db = PgnDatabase::embedded(crate::engine::Units::Metric);
let table = db.lookup("MANUFACTURER_CODE").expect("table present");
assert!(table.values.iter().any(|v| v.name == "Navico"));
}
#[test]
fn field_handle_resolves_and_finds() {
let db = PgnDatabase::embedded(crate::engine::Units::Metric);
let h = db
.field("isoAddressClaim", "uniqueNumber")
.expect("unique number handle");
assert_eq!(h.field.order, 1);
assert!(db.field("isoAddressClaim", "noSuchField").is_none());
assert!(db.field("noSuchPgn", "uniqueNumber").is_none());
}
#[test]
fn field_handle_indexes_decoded_record() {
let db = PgnDatabase::embedded(crate::engine::Units::Metric);
let mut data: smallvec::SmallVec<[u8; 8]> = smallvec::SmallVec::new();
for b in [0xfb, 0x9b, 0x70, 0x22, 0x00, 0x9b, 0x50, 0xc0] {
data.push(b);
}
let frame = RawFrame {
timestamp: None,
prio: 6,
pgn: 60928,
src: 5,
dst: 255,
data,
};
let dec = db.decode(&frame).expect("decode");
let h = db.field("isoAddressClaim", "uniqueNumber").expect("handle");
let f = dec.field(h).expect("field present");
assert_eq!(f.value.as_i64(), Some(1_088_507));
}
#[test]
fn si_and_metric_schemas_decode_same_frame_in_their_units() {
let mut data: smallvec::SmallVec<[u8; 8]> = smallvec::SmallVec::new();
for b in [0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0xff, 0xff] {
data.push(b);
}
let frame = RawFrame {
timestamp: None,
prio: 2,
pgn: 130306,
src: 23,
dst: 255,
data,
};
let angle = |units| {
let db = PgnDatabase::embedded(units);
let dec = db.decode(&frame).expect("decode");
let h = db.field("windData", "windAngle").expect("handle");
dec.field(h).expect("field").value.as_f64().expect("f64")
};
let si = angle(crate::engine::Units::Si);
let metric = angle(crate::engine::Units::Metric);
assert!((si - 1.6384).abs() < 1e-6, "SI radians: {si}");
assert!((metric - 93.8734).abs() < 1e-3, "Metric degrees: {metric}");
assert!((si.to_degrees() - metric).abs() < 1e-3);
}
}