From ad4d4f302a360af642eb37ef28c1bd4e7e77b28d Mon Sep 17 00:00:00 2001 From: brian-r-calder Date: Tue, 11 Aug 2026 18:42:25 -0400 Subject: [PATCH 1/2] fw: add N2K sender ID to output packets for v1.4 of the file format. --- LogConvert/src/SerialisableFactory.cpp | 30 ++++-- LogConvert/src/logconvert.cpp | 6 +- LogConvert/src/serialisation.h | 2 +- LoggerFirmware/ReleaseNotes.md | 3 + LoggerFirmware/include/serialisation.h | 2 +- LoggerFirmware/src/N2kLogger.cpp | 30 ++++-- wibl-python/src/wibl/__init__.py | 2 +- wibl-python/src/wibl/core/logger_file.py | 124 +++++++++++++++-------- 8 files changed, 130 insertions(+), 69 deletions(-) diff --git a/LogConvert/src/SerialisableFactory.cpp b/LogConvert/src/SerialisableFactory.cpp index 87192859..519564ec 100644 --- a/LogConvert/src/SerialisableFactory.cpp +++ b/LogConvert/src/SerialisableFactory.cpp @@ -106,10 +106,11 @@ std::shared_ptr HandleSystemTime(tN2kMsg& msg, bool& no_data_detec no_data_detected = true; } - rtn = std::shared_ptr(new Serialisable(sizeof(uint16_t) + sizeof(double) + sizeof(unsigned long) + 1)); + rtn = std::shared_ptr(new Serialisable(sizeof(uint16_t) + sizeof(double) + sizeof(unsigned long) + 2)); *rtn += date; *rtn += timestamp; *rtn += static_cast(msg.MsgTime); + *rtn += (uint8_t)msg.Source; *rtn += (uint8_t)source; } } @@ -136,8 +137,9 @@ std::shared_ptr HandleAttitude(tN2kMsg& msg, bool& no_data_detecte no_data_detected = true; } - rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 3*sizeof(double))); + rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 1 + 3*sizeof(double))); t.Serialise(rtn); + *rtn += (uint8_t)msg.Source; *rtn += yaw; *rtn += pitch; *rtn += roll; @@ -164,8 +166,9 @@ std::shared_ptr HandleDepth(tN2kMsg& msg, bool& no_data_detected) no_data_detected = true; } - rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 3*sizeof(double))); + rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 1 + 3*sizeof(double))); t.Serialise(rtn); + *rtn += (uint8_t)msg.Source; *rtn += depth; *rtn += offset; *rtn += range; @@ -194,8 +197,9 @@ std::shared_ptr HandleCOG(tN2kMsg& msg, bool& no_data_detected) no_data_detected = true; } - rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 2*sizeof(double))); + rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 1 + 2*sizeof(double))); t.Serialise(rtn); + *rtn += (uint8_t)msg.Source; *rtn += cog; *rtn += sog; } @@ -235,8 +239,9 @@ std::shared_ptr HandleGNSS(tN2kMsg& msg, bool& no_data_detected) || N2kIsNA(sep) || N2kIsNA(nRefStations) || N2kIsNA((uint8_t)refStationType) || N2kIsNA(refStationID) || N2kIsNA(correctionAge)) { no_data_detected = true; } - rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 2*sizeof(uint16_t) + 8*sizeof(double) + 5)); + rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 1 + 2*sizeof(uint16_t) + 8*sizeof(double) + 5)); t.Serialise(rtn); // Put in the standard timestamp, as well as the in-message one. + *rtn += (uint8_t)msg.Source; *rtn += datestamp; *rtn += timestamp; *rtn += latitude; *rtn += longitude; *rtn += altitude; *rtn += (uint8_t)rec_type; *rtn += (uint8_t)rec_method; @@ -272,8 +277,9 @@ std::shared_ptr HandleEnvironment(tN2kMsg& msg, bool& no_data_dete no_data_detected = true; } - rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 3*sizeof(double) + 2)); + rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 1 + 3*sizeof(double) + 2)); t.Serialise(rtn); + *rtn += (uint8_t)msg.Source; *rtn += (uint8_t)t_source; *rtn += temp; *rtn += (uint8_t)h_source; @@ -305,8 +311,9 @@ std::shared_ptr HandleTemperature(tN2kMsg& msg, bool& no_data_dete no_data_detected = true; } - rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 1 + sizeof(double))); + rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 2 + sizeof(double))); t.Serialise(rtn); + *rtn += (uint8_t)msg.Source; *rtn += (uint8_t)t_source; *rtn += temp; } @@ -336,8 +343,9 @@ std::shared_ptr HandleHumidity(tN2kMsg& msg, bool& no_data_detecte no_data_detected = true; } - rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 1 + sizeof(double))); + rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 2 + sizeof(double))); t.Serialise(rtn); + *rtn += (uint8_t)msg.Source; *rtn += (uint8_t)h_source; *rtn += humidity; } @@ -367,8 +375,9 @@ std::shared_ptr HandlePressure(tN2kMsg& msg, bool& no_data_detecte no_data_detected = true; } - rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 1 + sizeof(double))); + rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 2 + sizeof(double))); t.Serialise(rtn); + *rtn += (uint8_t)msg.Source; *rtn += (uint8_t)p_source; *rtn += pressure; } @@ -398,8 +407,9 @@ std::shared_ptr HandleExtTemperature(tN2kMsg& msg, bool& no_data_d no_data_detected = true; } - rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 1 + sizeof(double))); + rtn = std::shared_ptr(new Serialisable(t.SerialisationSize() + 2 + sizeof(double))); t.Serialise(rtn); + *rtn += (uint8_t)msg.Source; *rtn += (uint8_t)t_source; *rtn += temp; } diff --git a/LogConvert/src/logconvert.cpp b/LogConvert/src/logconvert.cpp index eb373ee2..d58f5294 100644 --- a/LogConvert/src/logconvert.cpp +++ b/LogConvert/src/logconvert.cpp @@ -42,6 +42,8 @@ namespace po = boost::program_options; #include "serialisation.h" #include "SerialisableFactory.h" +const char *version = "1.2.0"; + /// Dummy code to allow the system to pretend that there's a millisecond counter /// /// \return Nominally the millisecond counter since boot; there, uniformly zero @@ -157,7 +159,9 @@ std::string NameOutputPacket(uint32_t packet_id) void Syntax(po::options_description const& cmdopt) { - std::cout << "logconvert [" << __DATE__ << ", " << __TIME__ << "] - Convert VGI log output to WIBL for upload." << std::endl; + std::cout << "logconvert [" << __DATE__ << ", " << __TIME__ << "] v." << version + << " - Convert VGI log output to WIBL for upload." << std::endl; + std::cout << " Serialiser Version: " << SerialiserVersionMajor << "." << SerialiserVersionMinor << std::endl; std::cout << "Syntax: logconvert [opt] " << std::endl; std::cout << cmdopt << std::endl; } diff --git a/LogConvert/src/serialisation.h b/LogConvert/src/serialisation.h index aeac00fc..fbe33ef3 100644 --- a/LogConvert/src/serialisation.h +++ b/LogConvert/src/serialisation.h @@ -38,7 +38,7 @@ #undef minor const int SerialiserVersionMajor = 1; ///< Major version number for the serialiser -const int SerialiserVersionMinor = 3; ///< Minor version number for the serialiser +const int SerialiserVersionMinor = 4; ///< Minor version number for the serialiser /// \class Serialisable /// \brief Provide encapsulation for data to be written to store diff --git a/LoggerFirmware/ReleaseNotes.md b/LoggerFirmware/ReleaseNotes.md index 4189d9aa..6540a546 100644 --- a/LoggerFirmware/ReleaseNotes.md +++ b/LoggerFirmware/ReleaseNotes.md @@ -3,8 +3,11 @@ ## Firmware 1.7.0 Firmware 1.7.0 addresses multiple issues in the repository, including: + * [Issue 110](https://github.com/CCOMJHC/WIBL/issues/110), which changes the filename structure for raw files to include a fixed extension (e.g., "raw00000.wbl") rather than encoding the filename in the extension. In addition to allowing for more files, this avoids problems in processing later. +* [Issue 81](https://github.com/CCOMJHC/WIBL/issues/81), which adds source (speaker) information to NMEA2000 output packets in the raw file, stepping the serialiser version to 1.4. This also updates the Python code for reading files to be compatible, and updates logconvert to write version 1.4 data. + ## Firmware 1.6.2 Firmware 1.6.2 addresses [issue 107](https://github.com/CCOMJHC/WIBL/issues/107) in the repository, where the size of the JSON documents being constructed in the firmware were getting to the size where they took up enough memory that they caused problems when generating SSL connections during upload. This also seemed to have significantly slowed down the webserver, so this version of the firmware should show much snappier connections on the server as a (very welcome) side effect. diff --git a/LoggerFirmware/include/serialisation.h b/LoggerFirmware/include/serialisation.h index f2af8410..92d53c72 100644 --- a/LoggerFirmware/include/serialisation.h +++ b/LoggerFirmware/include/serialisation.h @@ -35,7 +35,7 @@ #include "FS.h" const int SerialiserVersionMajor = 1; ///< Major version number for the serialiser -const int SerialiserVersionMinor = 3; ///< Minor version number for the serialiser +const int SerialiserVersionMinor = 4; ///< Minor version number for the serialiser /// \class Serialisable /// \brief Provide encapsulation for data to be written to store diff --git a/LoggerFirmware/src/N2kLogger.cpp b/LoggerFirmware/src/N2kLogger.cpp index 4511b6c9..a86522f5 100644 --- a/LoggerFirmware/src/N2kLogger.cpp +++ b/LoggerFirmware/src/N2kLogger.cpp @@ -277,10 +277,11 @@ void Logger::HandleSystemTime(Timestamp::TimeDatum const& t, const tN2kMsg& msg) logger::DataObs obs(t.RawElapsed(), date, timestamp); logger::Metrics.RegisterObs(obs); - Serialisable s(sizeof(uint16_t) + sizeof(double) + sizeof(unsigned long) + 1); + Serialisable s(sizeof(uint16_t) + sizeof(double) + sizeof(unsigned long) + 2); s += date; s += timestamp; s += t.RawElapsed(); + s += (uint8_t)msg.Source; s += (uint8_t)source; m_logManager->Record(logger::Manager::PacketIDs::Pkt_SystemTime, s); m_logManager->Syslog(String("INF: Time update to: ") + m_timeReference.printable()); @@ -306,8 +307,9 @@ void Logger::HandleAttitude(Timestamp::TimeDatum const& t, tN2kMsg const& msg) if (IsNA(t) || N2kIsNA(yaw) || N2kIsNA(pitch) || N2kIsNA(roll)) m_logManager->EmitNoDataReject(); - Serialisable s(t.SerialisationSize() + 3*sizeof(double)); + Serialisable s(t.SerialisationSize() + 1 + 3*sizeof(double)); t.Serialise(s); + s += (uint8_t)msg.Source; s += yaw; s += pitch; s += roll; @@ -338,8 +340,9 @@ void Logger::HandleDepth(Timestamp::TimeDatum const& t, tN2kMsg const& msg) if (IsNA(t) || N2kIsNA(depth) || N2kIsNA(offset) || N2kIsNA(range)) m_logManager->EmitNoDataReject(); - Serialisable s(t.SerialisationSize() + 3*sizeof(double)); + Serialisable s(t.SerialisationSize() + 1 + 3*sizeof(double)); t.Serialise(s); + s += (uint8_t)msg.Source; s += depth; s += offset; s += range; @@ -370,8 +373,9 @@ void Logger::HandleCOG(Timestamp::TimeDatum const& t, tN2kMsg const& msg) if (IsNA(t) || N2kIsNA(cog) || N2kIsNA(sog)) m_logManager->EmitNoDataReject(); - Serialisable s(t.SerialisationSize() + 2*sizeof(double)); + Serialisable s(t.SerialisationSize() + 1 + 2*sizeof(double)); t.Serialise(s); + s += (uint8_t)msg.Source; s += cog; s += sog; m_logManager->Record(logger::Manager::PacketIDs::Pkt_COG, s); @@ -420,8 +424,9 @@ void Logger::HandleGNSS(Timestamp::TimeDatum const& t, tN2kMsg const& msg) N2kIsNA(refStationID) || N2kIsNA(correctionAge)) m_logManager->EmitNoDataReject(); - Serialisable s(t.SerialisationSize() + 2*sizeof(uint16_t) + 8*sizeof(double) + 5); + Serialisable s(t.SerialisationSize() + 1 + 2*sizeof(uint16_t) + 8*sizeof(double) + 5); t.Serialise(s); // Put in the standard timestamp, as well as the in-message one. + s += (uint8_t)msg.Source; s += datestamp; s += timestamp; s += latitude; s += longitude; s += altitude; s += (uint8_t)rec_type; s += (uint8_t)rec_method; @@ -470,8 +475,9 @@ void Logger::HandleEnvironment(Timestamp::TimeDatum const& t, tN2kMsg const& msg N2kIsNA((uint8_t)h_source) || N2kIsNA(humidity) || N2kIsNA(pressure)) m_logManager->EmitNoDataReject(); - Serialisable s(t.SerialisationSize() + 3*sizeof(double) + 2); + Serialisable s(t.SerialisationSize() + 1 + 3*sizeof(double) + 2); t.Serialise(s); + s += (uint8_t)msg.Source; s += (uint8_t)t_source; s += temp; s += (uint8_t)h_source; @@ -506,8 +512,9 @@ void Logger::HandleTemperature(Timestamp::TimeDatum const& t, tN2kMsg const& msg if (IsNA(t) || N2kIsNA((uint8_t)t_source) || N2kIsNA(temp)) m_logManager->EmitNoDataReject(); - Serialisable s(t.SerialisationSize() + 1 + sizeof(double)); + Serialisable s(t.SerialisationSize() + 2 + sizeof(double)); t.Serialise(s); + s += (uint8_t)msg.Source; s += (uint8_t)t_source; s += temp; m_logManager->Record(logger::Manager::PacketIDs::Pkt_Temperature, s); @@ -539,8 +546,9 @@ void Logger::HandleHumidity(Timestamp::TimeDatum const& t, tN2kMsg const& msg) if (IsNA(t) || N2kIsNA((uint8_t)h_source) || N2kIsNA(humidity)) m_logManager->EmitNoDataReject(); - Serialisable s(t.SerialisationSize() + 1 + sizeof(double)); + Serialisable s(t.SerialisationSize() + 2 + sizeof(double)); t.Serialise(s); + s += (uint8_t)msg.Source; s += (uint8_t)h_source; s += humidity; m_logManager->Record(logger::Manager::PacketIDs::Pkt_Humidity, s); @@ -572,8 +580,9 @@ void Logger::HandlePressure(Timestamp::TimeDatum const& t, tN2kMsg const& msg) if (IsNA(t) || N2kIsNA((uint8_t)p_source) || N2kIsNA(pressure)) m_logManager->EmitNoDataReject(); - Serialisable s(t.SerialisationSize() + 1 + sizeof(double)); + Serialisable s(t.SerialisationSize() + 2 + sizeof(double)); t.Serialise(s); + s += (uint8_t)msg.Source; s += (uint8_t)p_source; s += pressure; m_logManager->Record(logger::Manager::PacketIDs::Pkt_Pressure, s); @@ -605,8 +614,9 @@ void Logger::HandleExtTemperature(Timestamp::TimeDatum const& t, tN2kMsg const& if (IsNA(t) || N2kIsNA((uint8_t)t_source) || N2kIsNA(temp)) m_logManager->EmitNoDataReject(); - Serialisable s(t.SerialisationSize() + 1 + sizeof(double)); + Serialisable s(t.SerialisationSize() + 2 + sizeof(double)); t.Serialise(s); + s += (uint8_t)msg.Source; s += (uint8_t)t_source; s += temp; m_logManager->Record(logger::Manager::PacketIDs::Pkt_Temperature, s); diff --git a/wibl-python/src/wibl/__init__.py b/wibl-python/src/wibl/__init__.py index f6c956a7..aea98a82 100644 --- a/wibl-python/src/wibl/__init__.py +++ b/wibl-python/src/wibl/__init__.py @@ -8,7 +8,7 @@ import argparse import logging -__version__ = '1.2.0' +__version__ = '1.3.0' LOGGER_NAME: str = 'wibl-python' diff --git a/wibl-python/src/wibl/core/logger_file.py b/wibl-python/src/wibl/core/logger_file.py index a0068200..9f856650 100644 --- a/wibl-python/src/wibl/core/logger_file.py +++ b/wibl-python/src/wibl/core/logger_file.py @@ -45,7 +45,7 @@ class PacketTranscriptionError(Exception): ## Definition of major version of the file format represented by this description wibl_file_version_major = 1 ## Definition of minor version of the file format represented by this description -wibl_file_version_minor = 3 +wibl_file_version_minor = 4 def wibl_file_version() -> str: return f'{wibl_file_version_major}.{wibl_file_version_minor}' @@ -170,7 +170,7 @@ def id(self) -> int: # \param self Pointer to the object # \return String with the human-readable name of the packet @abstractmethod - def name(self): + def name(self) -> str: pass ## Serialise the data in the current packet into the given file @@ -224,9 +224,10 @@ def __init__(self, **kwargs): # \param self Pointer to the object # \param buffer Bytes buffer from which to unpack binary data def buffer_constructor(self, buffer: bytes) -> None: - (date, timestamp, elapsed_time, data_source) = struct.unpack(' None: # 'elapsed_time': Elapsed time (ms) since logger boot # 'date': Estimated real-world date string for packet (yyyy-mm-dd) # 'timestamp': Estimated real-world timestasmp for packet (seconds since midnight) + # 'talker_id': Talker ID number for the message (as if on a NMEA2000 bus) # 'data_source': Source for time information (see Wiki for details) def data_constructor(self, **kwargs) -> None: try: self.data_source = kwargs['data_source'] + self.talker_id = kwargs['talker_id'] super().__init__(kwargs['date'], kwargs['timestamp'], kwargs['elapsed_time']) except KeyError as e: raise SpecificationError('Bad packet parameters') from e def payload(self) -> bytes: - buffer = struct.pack(' int: @@ -256,7 +259,7 @@ def id(self) -> int: # # \param self Pointer to the object # \return String with the human-readable name of the packet - def name(self): + def name(self) -> str: return 'SystemTime' ## Implement the printable interface for this class, allowing it to be streamed @@ -266,7 +269,7 @@ def name(self): # \param self Pointer to the object # \return String representation of the object def __str__(self): - rtn = super().__str__() + f' {self.name()}: source = {self.data_source}' + rtn = super().__str__() + f' talker: {self.talker_id}, {self.name()}: source = {self.data_source}' return rtn ## Implementation of the Attitude NMEA2000 packet @@ -296,7 +299,8 @@ def __init__(self, **kwargs): # \param self Pointer to the object # \param buffer Bytes byffer from which to unpack binary data def buffer_constructor(self, buffer: bytes) -> None: - (date, timestamp, elapsed_time, yaw, pitch, roll) = struct.unpack(" None: DataPacket.__init__(self, date, timestamp, elapsed_time) def payload(self) -> bytes: - buffer = struct.pack(' bytes: # 'elapsed_time': Elapsed time (ms) since logger boot # 'date': Estimated real-world date string for packet (yyyy-mm-dd) # 'timestamp': Estimated real-world timestasmp for packet (seconds since midnight) + # 'talker_id': Talker ID number for the message (as if on a NMEA2000 bus) # 'yaw': Yaw angle (radians, +ve clockwise from north) # 'pitch': Pitch angle (radians, +ve bow up) # 'roll': Roll angle (radians, +ve port up) def data_constructor(self, **kwargs) -> None: try: + self.talker_id = kwargs['talker_id'] self.yaw = kwargs['yaw'] self.pitch = kwargs['pitch'] self.roll = kwargs['roll'] @@ -336,8 +343,8 @@ def id(self) -> int: # # \param self Pointer to the object # \return String with the human-readable name of the packet - def name(self): - return "Attitude" + def name(self) -> str: + return 'Attitude' ## Implement the printable interface for this class, allowing it to be streamed # @@ -346,7 +353,7 @@ def name(self): # \param self Pointer to the object # \return String representation of the object def __str__(self): - rtn = super().__str__() + f' {self.name()}: yaw = {angle_to_degs(self.yaw)} deg, pitch = {angle_to_degs(self.pitch)} deg, roll = {angle_to_degs(self.roll)} deg' + rtn = super().__str__() + f' talker: {self.talker_id}, {self.name()}: yaw = {angle_to_degs(self.yaw)} deg, pitch = {angle_to_degs(self.pitch)} deg, roll = {angle_to_degs(self.roll)} deg' return rtn ## Implement the Observed Depth NMEA2000 message @@ -376,7 +383,8 @@ def __init__(self, **kwargs): # \param self Pointer to the object # \param buffer Bytes buffer from which to unpack binary data def buffer_constructor(self, buffer: bytes) -> None: - (date, timestamp, elapsed_time, depth, offset, range) = struct.unpack(' None: super().__init__(date, timestamp, elapsed_time) def payload(self) -> bytes: - buffer = struct.pack(' int: @@ -400,11 +409,13 @@ def id(self) -> int: # 'elapsed_time': Elapsed time (ms) since logger boot # 'date': Estimated real-world date string for packet (yyyy-mm-dd) # 'timestamp': Estimated real-world timestasmp for packet (seconds since midnight) + # 'talker_id': Talker ID number for the message (as if on a NMEA2000 bus) # 'depth': Depth indicated (m) # 'offset': Offset applied to the depth before reporting (m) # 'range': Maximum range of the echosounder (m) def data_constructor(self, **kwargs) -> None: try: + self.talker_id = kwargs['talker_id'] self.depth = kwargs['depth'] self.offset = kwargs['offset'] self.range = kwargs['range'] @@ -418,8 +429,8 @@ def data_constructor(self, **kwargs) -> None: # # \param self Pointer to the object # \return String with the human-readable name of the packet - def name(self): - return "Depth" + def name(self) -> str: + return 'Depth' ## Implement the printable interface for this class, allowing it to be streamed # @@ -428,7 +439,7 @@ def name(self): # \param self Pointer to the object # \return String representation of the object def __str__(self): - rtn = super().__str__() + f' {self.name()}: depth = {self.depth}m, offset = {self.offset}m, range = {self.range}m' + rtn = super().__str__() + f' talker: {self.talker_id}, {self.name()}: depth = {self.depth}m, offset = {self.offset}m, range = {self.range}m' return rtn ## Implement the Course-over-Ground Rapid NMEA2000 message @@ -458,7 +469,8 @@ def __init__(self, **kwargs): # \param self Pointer to the objet # \param buffer Bytes buffer from which to unpack binary data def buffer_constructor(self, buffer: bytes) -> None: - (date, timestamp, elapsed_time, courseOverGround, speedOverGround) = struct.unpack(' None: super().__init__(date, timestamp, elapsed_time) def payload(self) -> bytes: - buffer = struct.pack(' int: @@ -478,10 +490,12 @@ def id(self) -> int: # 'elapsed_time': Elapsed time (ms) since logger boot # 'date': Estimated real-world date string for packet (yyyy-mm-dd) # 'timestamp': Estimated real-world timestasmp for packet (seconds since midnight) + # 'talker_id': Talker ID number for the message (as if on a NMEA2000 bus) # 'cog': Course over ground (CW from north), radians # 'sog': Speed over ground (m/s) def data_constructor(self, **kwargs) -> None: try: + self.talker_id = kwargs['talker_id'] self.courseOverGround = kwargs['cog'] self.speedOverGround = kwargs['sog'] super().__init__(kwargs['date'], kwargs['timestamp'], kwargs['elapsed_time']) @@ -494,7 +508,7 @@ def data_constructor(self, **kwargs) -> None: # # \param self Pointer to the object # \return String with the human-readable name of the packet - def name(self): + def name(self) -> str: return 'Course Over Ground' ## Implement the printable interface for this class, allowing it to be streamed @@ -504,7 +518,7 @@ def name(self): # \param self Pointer to the object # \return String representation of the object def __str__(self): - rtn = super().__str__() + f' {self.name()}: course over ground = {angle_to_degs(self.courseOverGround)} deg, speed over ground = {self.speedOverGround} m/s' + rtn = super().__str__() + f' talker: {self.talker_id}, {self.name()}: course over ground = {angle_to_degs(self.courseOverGround)} deg, speed over ground = {self.speedOverGround} m/s' return rtn ## Implement the GNSS observation NMEA2000 message @@ -539,9 +553,10 @@ def __init__(self, **kwargs): # \param self Pointer to the object # \param buffer Bytes buffer from which to unpack binary data def buffer_constructor(self, buffer: bytes) -> None: - (sys_date, sys_timestamp, sys_elapsed, date, timestamp, latitude, longitude, altitude, + (sys_date, sys_timestamp, sys_elapsed, talker_id, date, timestamp, latitude, longitude, altitude, receiverType, receiverMethod, numSVs, horizontalDOP, positionDOP, separation, numRefStations, refStationType, - refStationID, correctionAge) = struct.unpack(' None: super().__init__(sys_date, sys_timestamp, sys_elapsed) def payload(self) -> bytes: - buffer = struct.pack(' int: # 'elapsed_time': Elapsed time (ms) since logger boot # 'date': Estimated real-world date string for packet (days since epoch)) # 'timestamp': Estimated real-world timestasmp for packet (seconds since midnight) + # 'talker_id': Talker ID number for the message (as if on a NMEA2000 bus) # 'msg_date': Message's claimed date string for data (days since epoch) # 'msg_timestamp': Message's claimed timestasmp for data (seconds since midnight) # 'latitude': Latitude of antenna phase centre (degrees) @@ -611,6 +628,7 @@ def id(self) -> int: # which one you reported, or how you'd report more than one. def data_constructor(self, **kwargs) -> None: try: + self.talker_id = kwargs['talker_id'] self.msg_date = kwargs['msg_date'] self.msg_timestamp = kwargs['msg_timestamp'] self.latitude = kwargs['latitude'] @@ -636,8 +654,8 @@ def data_constructor(self, **kwargs) -> None: # # \param self Pointer to the object # \return String with the human-readable name of the packet - def name(self): - return "GNSS" + def name(self) -> str: + return 'GNSS' ## Implement the printable interface for this class, allowing it to be streamed # @@ -648,7 +666,7 @@ def name(self): def __str__(self): summary = io.StringIO() summary.write(super().__str__()) - summary.write(f' {self.name()}: in-message date = {self.msg_date} days, in-message time = {self.msg_timestamp} s., ') + summary.write(f' talker: {self.talker_id}, {self.name()}: in-message date = {self.msg_date} days, in-message time = {self.msg_timestamp} s., ') summary.write(f'latitude = {self.latitude} deg, longitude = {self.longitude} deg, altitude = {self.altitude} m, ') summary.write(f'GNSS type = {self.receiverType}, GNSS method = {self.receiverMethod}, ') summary.write(f'num. SVs = {self.numSVs}, ') @@ -689,8 +707,9 @@ def __init__(self, **kwargs): # \param self Pointer to the object # \param buffer Bytes buffer from which to unpack binary data def buffer_constructor(self, buffer: bytes) -> None: - (date, timestamp, elapsed_time, tempSource, temperature, humiditySource, humidity, pressure) = \ - struct.unpack(' None: super().__init__(date, timestamp, elapsed_time) def payload(self) -> bytes: - buffer = struct.pack(' int: @@ -718,6 +738,7 @@ def id(self) -> int: # 'elapsed_time': Elapsed time (ms) since logger boot # 'date': Real-world date associated with the data (days since epoch) # 'timestamp': Real-world time associated with the data (seconds since midnight) + # 'talker_id': Talker ID number for the message (as if on a NMEA2000 bus) # 'temp': Temperature (C) # 'temp_source': Source of temperature (see Wiki for details) # 'humidity': Relative humidity (%) @@ -725,6 +746,7 @@ def id(self) -> int: # 'pressure': Pressure (Pa) def data_constructor(self, **kwargs) -> None: try: + self.talker_id = kwargs['talker_id'] self.tempSource = kwargs['temp_source'] self.temperature = kwargs['temp'] self.humiditySource = kwargs['humid_source'] @@ -740,7 +762,7 @@ def data_constructor(self, **kwargs) -> None: # # \param self Pointer to the object # \return String with the human-readable name of the packet - def name(self): + def name(self) -> str: return 'Environment' ## Implement the printable interface for this class, allowing it to be streamed @@ -750,7 +772,7 @@ def name(self): # \param self Pointer to the object # \return String representation of the object def __str__(self): - rtn = super().__str__() + f' {self.name()}: temperature = {temp_to_celsius(self.temperature)} ºC (source {self.tempSource}), humidity = {self.humidity}% (source {self.humiditySource}), pressure = {pressure_to_mbar(self.pressure)} mBar' + rtn = super().__str__() + f' talker: {self.talker_id}, {self.name()}: temperature = {temp_to_celsius(self.temperature)} ºC (source {self.tempSource}), humidity = {self.humidity}% (source {self.humiditySource}), pressure = {pressure_to_mbar(self.pressure)} mBar' return rtn ## Implement the Temperature NMEA2000 message @@ -783,7 +805,8 @@ def __init__(self, **kwargs): # \param self Pointer to the object # \param buffer Bytes object from which to unpack binary data def buffer_constructor(self, buffer: bytes) -> None: - (date, timestamp, elapsed_time, tempSource, temperature) = struct.unpack(' None: super().__init__(date, timestamp, elapsed_time) def payload(self) -> bytes: - buffer = struct.pack(' int: @@ -803,10 +827,12 @@ def id(self) -> int: # 'elapsed_time': Elapsed time (ms) since logger boot # 'date': Real-world date associated with the data # 'timestamp': Real-world time associated with the data + # 'talker_id': Talker ID number for the message (as if on a NMEA2000 bus) # 'temp': Temperature (C) # 'temp_source': Source of temperature (see Wiki for details) def data_constructor(self, **kwargs) -> None: try: + self.talker_id = kwargs['talker_id'] self.tempSource = kwargs['temp_source'] self.temperature = kwargs['temp'] super().__init__(kwargs['date'], kwargs['timestamp'], kwargs['elapsed_time']) @@ -819,7 +845,7 @@ def data_constructor(self, **kwargs) -> None: # # \param self Pointer to the object # \return String with the human-readable name of the packet - def name(self): + def name(self) -> str: return 'Temperature' ## Implement the printable interface for this class, allowing it to be streamed @@ -829,7 +855,7 @@ def name(self): # \param self Pointer to the object # \return String representation of the object def __str__(self): - rtn = super().__str__() + f' {self.name()}: temperature = {temp_to_celsius(self.temperature)} ºC (source {self.tempSource})' + rtn = super().__str__() + f' talker: {self.talker_id}, {self.name()}: temperature = {temp_to_celsius(self.temperature)} ºC (source {self.tempSource})' return rtn ## Implement the Humidity NMEA2000 message @@ -862,7 +888,8 @@ def __init__(self, **kwargs): # \param self Pointer to the object # \param buffer Bytes object from which to unpack the binary data def buffer_constructor(self, buffer: bytes) -> None: - (date, timestamp, elapsed_time, humiditySource, humidity) = struct.unpack(' None: super().__init__(date, timestamp, elapsed_time) def payload(self) -> bytes: - buffer = struct.pack(' int: @@ -882,10 +910,12 @@ def id(self) -> int: # 'elapsed_time': Elapsed time (ms) since logger boot # 'date': Real-world date associated with the data # 'timestamp': Real-world time associated with the data + # 'talker_id': Talker ID number for the message (as if on a NMEA2000 bus) # 'humidity': Relative humidity (%) # 'humid_source': Source of humidity (see Wiki for details) def data_constructor(self, **kwargs): try: + self.talker_id = kwargs['talker_id'] self.humiditySource = kwargs['humid_source'] self.humidity = kwargs['humidity'] super().__init__(kwargs['date'], kwargs['timestamp'], kwargs['elapsed_time']) @@ -898,7 +928,7 @@ def data_constructor(self, **kwargs): # # \param self Pointer to the object # \return String with the human-readable name of the packet - def name(self): + def name(self) -> str: return 'Humidity' ## Implement the printable interface for this class, allowing it to be streamed @@ -908,7 +938,7 @@ def name(self): # \param self Pointer to the object # \return String representation of the object def __str__(self): - rtn = super().__str__() + f' {self.name()}: humidity = {self.humidity} % (source {self.humiditySource})' + rtn = super().__str__() + f' talker: {self.talker_id}, {self.name()}: humidity = {self.humidity} % (source {self.humiditySource})' return rtn ## Implement the Pressure NMEA2000 message @@ -941,7 +971,8 @@ def __init__(self, **kwargs): # \param self Pointer to the object # \param buffer Bytes object from which to unpack the information def buffer_constructor(self, buffer: bytes) -> None: - (date, timestamp, elapsed_time, pressureSource, pressure) = struct.unpack(' None: super().__init__(date, timestamp, elapsed_time) def payload(self) -> bytes: - buffer = struct.pack(' int: @@ -961,10 +993,12 @@ def id(self) -> int: # 'elapsed_time': Elapsed time (ms) since logger boot # 'date': Real-world date associated with the data # 'timestamp': Real-world time associated with the data + # 'talker_id': Talker ID number for the message (as if on a NMEA2000 bus) # 'pressure': Pressure in Pascals # 'press_source': Source of pressure (see Wiki for details) def data_constructor(self, **kwargs): try: + self.talker_id = kwargs['talker_id'] self.pressureSource = kwargs['press_source'] self.pressure = kwargs['pressure'] super().__init__(kwargs['date'], kwargs['timestamp'], kwargs['elapsed_time']) @@ -977,8 +1011,8 @@ def data_constructor(self, **kwargs): # # \param self Pointer to the object # \return String with the human-readable name of the packet - def name(self): - return "Pressure" + def name(self) -> str: + return 'Pressure' ## Implement the printable interface for this class, allowing it to be streamed # @@ -987,7 +1021,7 @@ def name(self): # \param self Pointer to the object # \return String representation of the object def __str__(self): - rtn = super().__str__() + f' {self.name()}: pressure = {pressure_to_mbar(self.pressure)} mBar (source {self.pressureSource})' + rtn = super().__str__() + f' talker: {self.talker_id}, {self.name()}: pressure = {pressure_to_mbar(self.pressure)} mBar (source {self.pressureSource})' return rtn ## Implement the NMEA0183 serial data message @@ -1361,7 +1395,7 @@ def data_constructor(self, **kwargs) -> None: self.parameters = kwargs['params'].encode('UTF-8') super().__init__(0, 0.0, 0) except KeyError as e: - raise SerialiserVersion('Bad packet parameters') from e + raise SpecificationError('Bad packet parameters') from e ## Provide the fixed-text string name for this data packet # From 0246d17202b647c52e01ad1d2ec9f2b50ca6b085 Mon Sep 17 00:00:00 2001 From: brian-r-calder Date: Wed, 12 Aug 2026 14:40:13 -0400 Subject: [PATCH 2/2] Added build dependency directory to gitignore for LogConvert build. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 30d20e8e..185d9e71 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ xcshareddata # Build products build/ +build-deps/ *.o *.LinkFileList *.hmap @@ -56,4 +57,4 @@ AWS/Repo-information.eml **/local/ **/tmp/ fp-info-cache -WIBL-backups/ \ No newline at end of file +WIBL-backups/