From ca7d9468ee2157138fc329e153265a00fe6f0b3f Mon Sep 17 00:00:00 2001 From: Andy Van Pelt Date: Wed, 8 Sep 2021 09:47:46 -0700 Subject: [PATCH 1/3] Add parsing support for 8:367:23, 8:367:24, 8:367:25, 8:367:33. --- setup.py | 1 + src/libais/CMakeLists.txt | 1 + src/libais/Makefile-custom | 1 + src/libais/Makefile.am | 1 + src/libais/ais.h | 329 +++++++++++++++++++++++++++++++++++ src/libais/ais8.cpp | 96 ++++++++++ src/libais/ais8_367_33.cpp | 288 ++++++++++++++++++++++++++++++ src/libais/ais_py.cpp | 347 +++++++++++++++++++++++++++++++++++++ src/libais/decode_body.cpp | 2 + test/test_data.py | 81 +++++++++ 10 files changed, 1147 insertions(+) create mode 100644 src/libais/ais8_367_33.cpp diff --git a/setup.py b/setup.py index ef7e9a77..b2693736 100755 --- a/setup.py +++ b/setup.py @@ -42,6 +42,7 @@ 'ais8_200.cpp', 'ais8_366.cpp', 'ais8_367.cpp', + 'ais8_367_33.cpp', 'ais9.cpp', 'ais10.cpp', # : # 11 See 4 - ; diff --git a/src/libais/CMakeLists.txt b/src/libais/CMakeLists.txt index 6862ed84..f63bbd07 100644 --- a/src/libais/CMakeLists.txt +++ b/src/libais/CMakeLists.txt @@ -13,6 +13,7 @@ ais8_200.cpp ais8_366.cpp ais8_366_22.cpp ais8_367.cpp +ais8_367_33.cpp ais9.cpp ais10.cpp ais12.cpp diff --git a/src/libais/Makefile-custom b/src/libais/Makefile-custom index 5cbcc2b9..0ba7ee33 100644 --- a/src/libais/Makefile-custom +++ b/src/libais/Makefile-custom @@ -67,6 +67,7 @@ SRCS += ais8_200.cpp # River Information System (RIS) SRCS += ais8_366.cpp SRCS += ais8_366_22.cpp SRCS += ais8_367.cpp +SRCS += ais8_367_33.cpp SRCS += ais9.cpp SRCS += ais10.cpp diff --git a/src/libais/Makefile.am b/src/libais/Makefile.am index 68cbeb5c..2c90e2ac 100644 --- a/src/libais/Makefile.am +++ b/src/libais/Makefile.am @@ -20,6 +20,7 @@ libais_la_SOURCES = \ ais8_366.cpp \ ais8_366_22.cpp \ ais8_367.cpp \ + ais8_367_33.cpp \ ais9.cpp \ ais10.cpp \ ais12.cpp \ diff --git a/src/libais/ais.h b/src/libais/ais.h index 4619db19..bfef4a3e 100644 --- a/src/libais/ais.h +++ b/src/libais/ais.h @@ -2433,6 +2433,335 @@ class Ais27 : public AisMsg { }; ostream& operator<< (ostream &o, const Ais27 &msg); + +// SSW Satellite Ship Weather 1-Slot Version +class Ais8_367_23 : public Ais8 { + public: + int version; + + int utc_day; + int utc_hour; + int utc_min; + + AisPoint position; // 25, 24 bits + int pressure; // hPa + float air_temp; // C + int wind_speed; // knots + int wind_gust; // knots + int wind_dir; // degrees + int spare; + + Ais8_367_23(const char *nmea_payload, const size_t pad); +}; +ostream& operator<< (ostream &o, const Ais8_367_23 &msg); + +// +// SSW Satellite Ship Weather 1-Slot Version +class Ais8_367_24 : public Ais8 { + public: + int version; + + int utc_day; + int utc_hour; + int utc_min; + + AisPoint position; // 25, 24 bits + int pressure; // hPa + + Ais8_367_24(const char *nmea_payload, const size_t pad); +}; +ostream& operator<< (ostream &o, const Ais8_367_24 &msg); + +// +// SSW Satellite Ship Weather Tiny Version +class Ais8_367_25 : public Ais8 { + public: + int version; + + int utc_day; + int utc_hour; + int utc_min; + + int pressure; // hPa + int wind_speed; // Knots + int wind_dir; // Degrees + + Ais8_367_25(const char *nmea_payload, const size_t pad); +}; +ostream& operator<< (ostream &o, const Ais8_367_25 &msg); + +const size_t AIS8_367_33_REPORT_SIZE = 112; + +enum Ais8_367_33_SensorEnum { + AIS8_367_33_SENSOR_ERROR = -1, + AIS8_367_33_SENSOR_LOCATION = 0, + AIS8_367_33_SENSOR_STATION = 1, + AIS8_367_33_SENSOR_WIND = 2, + AIS8_367_33_SENSOR_WATER_LEVEL = 3, + AIS8_367_33_SENSOR_CURR_2D = 4, + AIS8_367_33_SENSOR_CURR_3D = 5, + AIS8_367_33_SENSOR_HORZ_FLOW = 6, + AIS8_367_33_SENSOR_SEA_STATE = 7, + AIS8_367_33_SENSOR_SALINITY = 8, + AIS8_367_33_SENSOR_WX = 9, + AIS8_367_33_SENSOR_AIR_GAP = 10, + AIS8_367_33_SENSOR_WIND_REPORT_2 = 11, + AIS8_367_33_SENSOR_RESERVED_12 = 12, + AIS8_367_33_SENSOR_RESERVED_13 = 13, + AIS8_367_33_SENSOR_RESERVED_14 = 14, + AIS8_367_33_SENSOR_RESERVED_15 = 15, +}; + + +class Ais8_367_33_SensorReport { + public: + int report_type; + int utc_day; + int utc_hr; + int utc_min; + int site_id; // aka link_id + + virtual Ais8_367_33_SensorEnum getType() const = 0; + virtual ~Ais8_367_33_SensorReport() {} +}; + +Ais8_367_33_SensorReport* +ais8_367_33_sensor_report_factory(const AisBitset &bs, + const size_t offset); + +class Ais8_367_33_Location : public Ais8_367_33_SensorReport { + public: + int version; + AisPoint position; + int precision; + float altitude; + int owner; + int timeout; + int spare2; + + Ais8_367_33_Location(const AisBitset &bs, const size_t offset); + Ais8_367_33_Location() {} + Ais8_367_33_SensorEnum getType() const { return AIS8_367_33_SENSOR_LOCATION; } +}; + +class Ais8_367_33_Station : public Ais8_367_33_SensorReport { + public: + string name; + int spare2; + + Ais8_367_33_Station(const AisBitset &bs, const size_t offset); + Ais8_367_33_Station() {} + Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_STATION;} +}; + +class Ais8_367_33_Wind : public Ais8_367_33_SensorReport { + public: + int wind_speed; // knots + int wind_gust; // knots + int wind_dir; + int wind_gust_dir; + int sensor_type; + int wind_forecast; // knots + int wind_gust_forecast; // knots + int wind_dir_forecast; + int utc_day_forecast; + int utc_hour_forecast; + int utc_min_forecast; + int duration; + int spare2; + + Ais8_367_33_Wind(const AisBitset &bs, const size_t offset); + Ais8_367_33_Wind() {} + Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_WIND;} +}; + +class Ais8_367_33_WaterLevel : public Ais8_367_33_SensorReport { + public: + int type; + float level; // m. assuming it is being stored at 0.01 m inc. + int trend; + int vdatum; + int sensor_type; + int forecast_type; + float level_forecast; + int utc_day_forecast; + int utc_hour_forecast; + int utc_min_forecast; + int duration; // minutes + int spare2; + + Ais8_367_33_WaterLevel(const AisBitset &bs, const size_t offset); + Ais8_367_33_WaterLevel() {} + Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_WATER_LEVEL;} +}; + +class Ais8_367_33_Curr2D_Current { + public: + float speed; // knots + int dir; + int depth; // m +}; + +class Ais8_367_33_Curr2D : public Ais8_367_33_SensorReport { + public: + Ais8_367_33_Curr2D_Current currents[3]; + int type; + int spare2; + + Ais8_367_33_Curr2D(const AisBitset &bs, const size_t offset); + Ais8_367_33_Curr2D() {} + Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_CURR_2D;} +}; + +class Ais8_367_33_Curr3D_Current { + public: + float north; + float east; + float up; + int depth; // m +}; + +class Ais8_367_33_Curr3D : public Ais8_367_33_SensorReport { + public: + Ais8_367_33_Curr3D_Current currents[2]; + int type; + int spare2; + + Ais8_367_33_Curr3D(const AisBitset &bs, const size_t offset); + Ais8_367_33_Curr3D() {} + Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_CURR_3D;} +}; + +class Ais8_367_33_HorzFlow_Current { + public: + int dist; // m + float speed; // knots + int dir; // deg + int level; // m +}; + +class Ais8_367_33_HorzFlow : public Ais8_367_33_SensorReport { + public: + Ais8_367_33_HorzFlow_Current currents[2]; + int bearing; // deg + int type; + int spare2; + + Ais8_367_33_HorzFlow(const AisBitset &bs, const size_t offset); + Ais8_367_33_HorzFlow() {} + Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_HORZ_FLOW;} +}; + +class Ais8_367_33_SeaState : public Ais8_367_33_SensorReport { + public: + float swell_height; + int swell_period; // seconds + int swell_dir; // deg + int sea_state; + int swell_sensor_type; + float water_temp; // C + float water_temp_depth; // m + int water_sensor_type; + float wave_height; + int wave_period; // seconds + int wave_dir; // deg + int wave_sensor_type; + float salinity; + + Ais8_367_33_SeaState(const AisBitset &bs, const size_t offset); + Ais8_367_33_SeaState() {} + Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_SEA_STATE;} +}; + +class Ais8_367_33_Salinity : public Ais8_367_33_SensorReport { + public: + float water_temp; // C + float conductivity; // siemens/m + float pressure; // decibars + float salinity; // 0/00 ppt + int salinity_type; + int sensor_type; + int spare2[2]; + + Ais8_367_33_Salinity(const AisBitset &bs, const size_t offset); + Ais8_367_33_Salinity() {} + Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_SALINITY;} +}; + +class Ais8_367_33_Wx : public Ais8_367_33_SensorReport { + public: + float air_temp; // C + int air_temp_sensor_type; + int precip; + float horz_vis; // nm + float dew_point; // C + int dew_point_type; + float air_pressure; // Pascals (Pa). + int air_pressure_trend; + int air_pressor_type; + float salinity; // 0/00 ppt + int spare2; + + Ais8_367_33_Wx(const AisBitset &bs, const size_t offset); + Ais8_367_33_Wx() {} + Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_WX;} +}; + +class Ais8_367_33_AirGap : public Ais8_367_33_SensorReport { + public: + float draught; + float gap; + int trend; + float forecast_gap; + int utc_day_forecast; + int utc_hour_forecast; + int utc_min_forecast; + int type; + int spare2; + + Ais8_367_33_AirGap(const AisBitset &bs, const size_t offset); + Ais8_367_33_AirGap() {} + Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_AIR_GAP;} +}; + +class Ais8_367_33_Wind_V2 : public Ais8_367_33_SensorReport { + public: + int wind_speed; // knots + int wind_gust; // knots + int wind_dir; + int averaging_time; + int sensor_type; + int wind_speed_forecast; // knots + int wind_gust_forecast; // knots + int wind_dir_forecast; + int utc_hour_forecast; + int utc_min_forecast; + int duration; + int spare2; + + Ais8_367_33_Wind_V2(const AisBitset &bs, const size_t offset); + Ais8_367_33_Wind_V2() {} + Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_WIND_REPORT_2;} +}; + +// +// Environmental Message +class Ais8_367_33 : public Ais8 { + public: + int report_type; + + int utc_day; + int utc_hour; + int utc_min; + int site_id; + + vector reports; + + Ais8_367_33(const char *nmea_payload, const size_t pad); + ~Ais8_367_33(); +}; +ostream& operator<< (ostream &o, const Ais8_367_33 &msg); + } // namespace libais #endif // LIBAIS_AIS_H_ diff --git a/src/libais/ais8.cpp b/src/libais/ais8.cpp index 2e960deb..6ef9f106 100644 --- a/src/libais/ais8.cpp +++ b/src/libais/ais8.cpp @@ -626,8 +626,104 @@ Ais8_1_31::Ais8_1_31(const char *nmea_payload, const size_t pad) spare2 = bits.ToUnsignedInt(350, 10); assert(bits.GetRemaining() == 0); +} + +// SSW FI23 Satellite Ship Weather 1-Slot Version +Ais8_367_23::Ais8_367_23(const char *nmea_payload, const size_t pad) + : Ais8(nmea_payload, pad), version(0), utc_day(0), + utc_hour(), utc_min(), position(), pressure(0.0), air_temp(0.0), + wind_speed(0), wind_gust(0.0), wind_dir(0) { + assert(dac == 367); + assert(fi == 23); + + if (!CheckStatus()) { + return; + } + + if ((num_bits != 168) && (num_bits != 192)) { + status = AIS_ERR_BAD_BIT_COUNT; + return; + } + + bits.SeekTo(56); + + version = bits.ToUnsignedInt(56, 3); + utc_day = bits.ToUnsignedInt(59, 5); + utc_hour = bits.ToUnsignedInt(64, 5); + utc_min = bits.ToUnsignedInt(69, 6); + position = bits.ToAisPoint(75, 49); + pressure = bits.ToUnsignedInt(124, 9) + 800; // hPa + air_temp = bits.ToInt(133, 11) / 10.; // C + wind_speed = bits.ToUnsignedInt(144, 7); // ave knots + wind_gust = bits.ToUnsignedInt(151, 7); // ave knots + wind_dir = bits.ToUnsignedInt(158, 9); + + spare = bits.ToUnsignedInt(167, 1); + + // NOTE: I'm seeing 192-bit messages come in, which means 24 bits left. + // assert(bits.GetRemaining() == 0); + status = AIS_OK; +} + +// SSW FI24 Satellite Ship Weather Small - Less than 1-Slot Version +Ais8_367_24::Ais8_367_24(const char *nmea_payload, const size_t pad) + : Ais8(nmea_payload, pad), version(0), + utc_hour(), utc_min(), position(), pressure(0.0) { + assert(dac == 367); + assert(fi == 24); + + if (!CheckStatus()) { + return; + } + + if ((num_bits != 128) && (num_bits != 144)) { + status = AIS_ERR_BAD_BIT_COUNT; + return; + } + + bits.SeekTo(56); + + version = bits.ToUnsignedInt(56, 3); + utc_hour = bits.ToUnsignedInt(59, 5); + utc_min = bits.ToUnsignedInt(64, 6); + position = bits.ToAisPoint(70, 49); + pressure = bits.ToUnsignedInt(119, 9) + 800; // hPa + + // NOTE: If message comes in at 144, then there will be 16 bits remaining. + // assert(bits.GetRemaining() == 0); status = AIS_OK; } +// SSW FI25 Satellite Ship Weather Tiny Version +Ais8_367_25::Ais8_367_25(const char *nmea_payload, const size_t pad) + : Ais8(nmea_payload, pad), version(0), + utc_hour(), utc_min(), pressure(0.0) , wind_speed(0), wind_dir(0) { + assert(dac == 367); + assert(fi == 25); + + if (!CheckStatus()) { + return; + } + + if ((num_bits != 96) && (num_bits != 120)) { + status = AIS_ERR_BAD_BIT_COUNT; + return; + } + + bits.SeekTo(56); + + version = bits.ToUnsignedInt(56, 3); + utc_hour = bits.ToUnsignedInt(59, 5); + utc_min = bits.ToUnsignedInt(64, 6); + pressure = bits.ToUnsignedInt(70, 9) + 800; // hPa + wind_speed = bits.ToUnsignedInt(79, 7); // Knots + wind_dir = bits.ToUnsignedInt(86, 9); // Degrees + + spare = bits.ToUnsignedInt(95, 1); + + // NOTE: if num_bits is 120, then there will be 24 bits left. + // assert(bits.GetRemaining() == 0); + status = AIS_OK; +} } // namespace libais diff --git a/src/libais/ais8_367_33.cpp b/src/libais/ais8_367_33.cpp new file mode 100644 index 00000000..17e20221 --- /dev/null +++ b/src/libais/ais8_367_33.cpp @@ -0,0 +1,288 @@ +// 8:367:33 Defined by EM Version 3-23MAR15.pdf. +// The USCG Environmental Message + +#include "ais.h" + +namespace libais { + +Ais8_367_33_Location::Ais8_367_33_Location(const AisBitset &bits, + const size_t offset) { + version = bits.ToUnsignedInt(offset, 6); + position = bits.ToAisPoint(offset + 6, 55); + + precision = bits.ToUnsignedInt(offset + 61, 3); + altitude = bits.ToUnsignedInt(offset + 64, 12) / 10.; // meters + owner = bits.ToUnsignedInt(offset + 76, 4); + timeout = bits.ToUnsignedInt(offset + 80, 3); + spare2 = bits.ToUnsignedInt(offset + 83, 2); +} + +Ais8_367_33_Station::Ais8_367_33_Station(const AisBitset &bits, + const size_t offset) { + name = bits.ToString(offset, 84); + spare2 = bits.ToUnsignedInt(offset + 84, 1); +} + +Ais8_367_33_Wind::Ais8_367_33_Wind(const AisBitset &bits, + const size_t offset) { + wind_speed = bits.ToUnsignedInt(offset, 7); + wind_gust = bits.ToUnsignedInt(offset + 7, 7); // knots + wind_dir = bits.ToUnsignedInt(offset + 14, 9); + wind_gust_dir = bits.ToUnsignedInt(offset + 23, 9); + sensor_type = bits.ToUnsignedInt(offset + 32, 3); + wind_forecast = bits.ToUnsignedInt(offset + 35, 7); + wind_gust_forecast = bits.ToUnsignedInt(offset + 42, 7); // knots + wind_dir_forecast = bits.ToUnsignedInt(offset + 49, 9); + utc_day_forecast = bits.ToUnsignedInt(offset + 58, 5); + utc_hour_forecast = bits.ToUnsignedInt(offset + 63, 5); + utc_min_forecast = bits.ToUnsignedInt(offset + 68, 6); + duration = bits.ToUnsignedInt(offset + 74, 8); + spare2 = bits.ToUnsignedInt(offset + 82, 3); +} + +Ais8_367_33_WaterLevel::Ais8_367_33_WaterLevel(const AisBitset &bits, + const size_t offset) { + type = bits[offset]; + level = bits.ToInt(offset + 1, 16); + trend = bits.ToUnsignedInt(offset + 17, 2); + vdatum = bits.ToUnsignedInt(offset + 19, 5); + sensor_type = bits.ToUnsignedInt(offset + 24, 3); + forecast_type = bits[offset + 27]; + level_forecast = bits.ToInt(offset + 28, 16); + utc_day_forecast = bits.ToUnsignedInt(offset + 44, 5); + utc_hour_forecast = bits.ToUnsignedInt(offset + 49, 5); + utc_min_forecast = bits.ToUnsignedInt(offset + 54, 6); + duration = bits.ToUnsignedInt(offset + 60, 8); + spare2 = bits.ToUnsignedInt(offset + 68, 17); +} + +Ais8_367_33_Curr2D::Ais8_367_33_Curr2D(const AisBitset &bits, + const size_t offset) { + for (size_t idx = 0; idx < 3; idx++) { + size_t start = offset + idx * 26; + currents[idx].speed = bits.ToUnsignedInt(start, 8) / 10.; + currents[idx].dir = bits.ToUnsignedInt(start + 8, 9); + currents[idx].depth = bits.ToUnsignedInt(start + 17, 9); + } + type = bits.ToUnsignedInt(offset + 78, 3); + spare2 = bits.ToUnsignedInt(offset + 81, 4); +} + +Ais8_367_33_Curr3D::Ais8_367_33_Curr3D(const AisBitset &bits, + const size_t offset) { + for (size_t idx = 0; idx < 2; idx++) { + size_t start = offset + idx * 36; + currents[idx].north = bits.ToUnsignedInt(start, 9) / 10.; + currents[idx].east = bits.ToUnsignedInt(start + 9, 9) / 10.; + currents[idx].up = bits.ToUnsignedInt(start + 18, 9) / 10.; + currents[idx].depth = bits.ToUnsignedInt(start + 27, 9); + } + type = bits.ToUnsignedInt(offset + 72, 3); + spare2 = bits.ToUnsignedInt(offset + 75, 10); +} + +Ais8_367_33_HorzFlow::Ais8_367_33_HorzFlow(const AisBitset &bits, + const size_t offset) { + bearing = bits.ToUnsignedInt(offset, 9); + for (size_t idx = 0; idx < 2; idx++) { + size_t start = offset + 9 + (idx * 35); + currents[idx].dist = bits.ToUnsignedInt(start, 9); + currents[idx].speed = bits.ToUnsignedInt(start + 9, 8) / 10.; + currents[idx].dir = bits.ToUnsignedInt(start + 17, 9); + currents[idx].level = bits.ToUnsignedInt(start + 26, 9); + } + type = bits.ToUnsignedInt(offset + 79, 3); + spare2 = bits.ToUnsignedInt(offset + 82, 3); +} + +Ais8_367_33_SeaState::Ais8_367_33_SeaState(const AisBitset &bits, + const size_t offset) { + swell_height = bits.ToUnsignedInt(offset, 8) / 10.; + swell_period = bits.ToUnsignedInt(offset + 8, 6); + swell_dir = bits.ToUnsignedInt(offset + 14, 9); + sea_state = bits.ToUnsignedInt(offset + 23, 4); + swell_sensor_type = bits.ToUnsignedInt(offset + 27, 3); + water_temp = bits.ToInt(offset + 30, 10) / 10.; + water_temp_depth = bits.ToUnsignedInt(offset + 40, 7) / 10.; + water_sensor_type = bits.ToUnsignedInt(offset + 47, 3); + wave_height = bits.ToUnsignedInt(offset + 50, 8) / 10.; + wave_period = bits.ToUnsignedInt(offset + 58, 6); + wave_dir = bits.ToUnsignedInt(offset + 64, 9); + wave_sensor_type = bits.ToUnsignedInt(offset + 73, 3); + salinity = bits.ToUnsignedInt(offset + 76, 9) / 10.; +} + + +Ais8_367_33_Salinity::Ais8_367_33_Salinity(const AisBitset &bits, + const size_t offset) { + water_temp = bits.ToUnsignedInt(offset, 10) / 10. - 10; + conductivity = bits.ToUnsignedInt(offset + 10, 10) / 100.; + pressure = bits.ToUnsignedInt(offset + 20, 16) / 10.; + salinity = bits.ToUnsignedInt(offset + 36, 9) / 10.; + salinity_type = bits.ToUnsignedInt(offset + 45, 2); + sensor_type = bits.ToUnsignedInt(offset + 47, 3); + spare2[0] = bits.ToUnsignedInt(offset + 50, 32); + spare2[1] = bits.ToUnsignedInt(offset + 82, 3); +} + +Ais8_367_33_Wx::Ais8_367_33_Wx(const AisBitset &bits, + const size_t offset) { + air_temp = bits.ToInt(offset, 11) / 10.; + air_temp_sensor_type = bits.ToUnsignedInt(offset + 11, 3); + precip = bits.ToUnsignedInt(offset + 14, 2); + horz_vis = bits.ToUnsignedInt(offset + 16, 8) / 10.; + dew_point = (bits.ToUnsignedInt(offset + 24, 10) / 10.) - 20.0; + dew_point_type = bits.ToUnsignedInt(offset + 34, 3); + air_pressure = bits.ToUnsignedInt(offset + 37, 9) + 800; // hPa. + air_pressure_trend = bits.ToUnsignedInt(offset + 46, 2); + air_pressor_type = bits.ToUnsignedInt(offset + 48, 3); + salinity = bits.ToUnsignedInt(offset + 51, 9) / 10.; + spare2 = bits.ToUnsignedInt(offset + 60, 25); +} + +Ais8_367_33_AirGap::Ais8_367_33_AirGap(const AisBitset &bits, + const size_t offset) { + draught = bits.ToUnsignedInt(offset, 13) / 100.; + gap = bits.ToUnsignedInt(offset + 13, 13) / 10.; + trend = bits.ToUnsignedInt(offset + 26, 2); + forecast_gap = bits.ToUnsignedInt(offset + 28, 13) / 10.; + utc_day_forecast = bits.ToUnsignedInt(offset + 41, 5); + utc_hour_forecast = bits.ToUnsignedInt(offset + 46, 5); + utc_min_forecast = bits.ToUnsignedInt(offset + 51, 6); + type = bits.ToUnsignedInt(offset + 57, 3); + spare2 = bits.ToUnsignedInt(offset + 60, 25); +} + +Ais8_367_33_Wind_V2::Ais8_367_33_Wind_V2(const AisBitset &bits, + const size_t offset) { + wind_speed = bits.ToUnsignedInt(offset, 7); + wind_gust = bits.ToUnsignedInt(offset + 7, 7); + wind_dir = bits.ToUnsignedInt(offset + 14, 7); + averaging_time = bits.ToUnsignedInt(offset + 23, 6); + sensor_type = bits.ToUnsignedInt(offset + 29, 3); + wind_speed_forecast = bits.ToUnsignedInt(offset + 32, 7); + wind_gust_forecast = bits.ToUnsignedInt(offset + 39, 7); + wind_dir_forecast = bits.ToUnsignedInt(offset + 46, 9); + utc_hour_forecast = bits.ToUnsignedInt(offset + 55, 5); + utc_min_forecast = bits.ToUnsignedInt(offset + 60, 6); + duration = bits.ToUnsignedInt(offset + 66, 8); + spare2 = bits.ToUnsignedInt(offset + 74, 11); +} + + +Ais8_367_33_SensorReport* +ais8_367_33_sensor_report_factory(const AisBitset &bits, + const size_t offset) { + const Ais8_367_33_SensorEnum rpt_type = + (Ais8_367_33_SensorEnum)bits.ToUnsignedInt(offset, 4); + + // WARNING: out of order decoding + // Only get the report header if we can decode the type + const size_t rpt_start = offset + 27; // skip tp after site_id + bits.SeekTo(rpt_start); + Ais8_367_33_SensorReport *rpt = nullptr; + switch (rpt_type) { + case AIS8_367_33_SENSOR_LOCATION: + rpt = new Ais8_367_33_Location(bits, rpt_start); + break; + case AIS8_367_33_SENSOR_STATION: + rpt = new Ais8_367_33_Station(bits, rpt_start); + break; + case AIS8_367_33_SENSOR_WIND: + rpt = new Ais8_367_33_Wind(bits, rpt_start); + break; + case AIS8_367_33_SENSOR_WATER_LEVEL: + rpt = new Ais8_367_33_WaterLevel(bits, rpt_start); + break; + case AIS8_367_33_SENSOR_CURR_2D: + rpt = new Ais8_367_33_Curr2D(bits, rpt_start); + break; + case AIS8_367_33_SENSOR_CURR_3D: + rpt = new Ais8_367_33_Curr3D(bits, rpt_start); + break; + case AIS8_367_33_SENSOR_HORZ_FLOW: + rpt = new Ais8_367_33_HorzFlow(bits, rpt_start); + break; + case AIS8_367_33_SENSOR_SEA_STATE: + rpt = new Ais8_367_33_SeaState(bits, rpt_start); + break; + case AIS8_367_33_SENSOR_SALINITY: + rpt = new Ais8_367_33_Salinity(bits, rpt_start); + break; + case AIS8_367_33_SENSOR_WX: + rpt = new Ais8_367_33_Wx(bits, rpt_start); + break; + case AIS8_367_33_SENSOR_AIR_GAP: + rpt = new Ais8_367_33_AirGap(bits, rpt_start); + break; + case AIS8_367_33_SENSOR_WIND_REPORT_2: + rpt = new Ais8_367_33_Wind_V2(bits, rpt_start); + break; + // Leave rpt == 0 to indicate error + case AIS8_367_33_SENSOR_RESERVED_12: break; + case AIS8_367_33_SENSOR_RESERVED_13: break; + case AIS8_367_33_SENSOR_RESERVED_14: break; + case AIS8_367_33_SENSOR_RESERVED_15: break; + default: + {} // Leave rpt == 0 to indicate error + } + + if (!rpt) { + return rpt; + } + + rpt->report_type = rpt_type; + bits.SeekTo(offset + 4); + rpt->utc_day = bits.ToUnsignedInt(offset + 4, 5); + rpt->utc_hr = bits.ToUnsignedInt(offset + 9, 5); + rpt->utc_min = bits.ToUnsignedInt(offset + 14, 6); + rpt->site_id = bits.ToUnsignedInt(offset + 20, 7); + return rpt; +} + +Ais8_367_33::Ais8_367_33(const char *nmea_payload, const size_t pad) + : Ais8(nmea_payload, pad) { + assert(dac == 33); + assert(fi == 33); + + if (!CheckStatus()) { + return; + } + if (168 > num_bits || num_bits > 952) { + status = AIS_ERR_BAD_BIT_COUNT; + return; + } + + const size_t num_sensor_reports = (num_bits - 56) / AIS8_367_33_REPORT_SIZE; + + if (num_sensor_reports > 8) { + return; + } + + for (size_t report_idx = 0; report_idx < num_sensor_reports; report_idx++) { + const size_t start = 56 + report_idx * AIS8_367_33_REPORT_SIZE; + bits.SeekTo(start); + Ais8_367_33_SensorReport *sensor = + ais8_367_33_sensor_report_factory(bits, start); + if (sensor) { + reports.push_back(sensor); + } else { + status = AIS_ERR_BAD_SUB_SUB_MSG; + return; + } + } + + // NOTE: Enable this assert after fixing the message. + // assert(bits.GetRemaining() == 0); + status = AIS_OK; +} + +// TODO(schwehr): Use unique_ptr to manage memory. +Ais8_367_33::~Ais8_367_33() { + for (size_t i = 0; i < reports.size(); i++) { + delete reports[i]; + reports[i] = nullptr; + } +} + +} // namespace libais diff --git a/src/libais/ais_py.cpp b/src/libais/ais_py.cpp index a0fcce67..fa30ce48 100644 --- a/src/libais/ais_py.cpp +++ b/src/libais/ais_py.cpp @@ -61,6 +61,10 @@ enum AIS_FI { AIS_FI_8_200_55_RIS_PERSONS_ON_BOARD = 50, AIS_FI_8_366_22_AREA_NOTICE = 22, // USCG. AIS_FI_8_367_22_AREA_NOTICE = 22, // USCG. + AIS_FI_8_367_23_SSW = 23, // USCG Satellite Ship Weather + AIS_FI_8_367_24_SSW_SMALL = 24, // USCG Satellite Ship Weather Small + AIS_FI_8_367_25_SSW_TINY = 25, // USCG Satellite Ship Weather Tiny + AIS_FI_8_367_33_ENVIRONMENTAL = 33, // USCG Environmental Report }; void @@ -1988,6 +1992,337 @@ ais8_367_22_append_pydict(const char *nmea_payload, PyObject *dict, DictSafeSetItem(dict, "sub_areas", sub_area_list); } +AIS_STATUS +ais8_367_23_append_pydict(const char *nmea_payload, PyObject *dict, + const size_t pad) { + assert(nmea_payload); + assert(dict); + assert(pad < 6); + Ais8_367_23 msg(nmea_payload, pad); + if (msg.had_error()) { + return msg.get_error(); + } + + DictSafeSetItem(dict, "version", msg.version); + DictSafeSetItem(dict, "utc_day", msg.utc_day); + DictSafeSetItem(dict, "utc_hour", msg.utc_hour); + DictSafeSetItem(dict, "utc_min", msg.utc_min); + DictSafeSetItem(dict, "x", "y", msg.position); + + DictSafeSetItem(dict, "pressure", msg.pressure); + DictSafeSetItem(dict, "air_temp", msg.air_temp); + DictSafeSetItem(dict, "wind_speed", msg.wind_speed); + DictSafeSetItem(dict, "wind_gust", msg.wind_gust); + DictSafeSetItem(dict, "wind_dir", msg.wind_dir); + + return AIS_OK; +} + +AIS_STATUS +ais8_367_24_append_pydict(const char *nmea_payload, PyObject *dict, + const size_t pad) { + assert(nmea_payload); + assert(dict); + assert(pad < 6); + Ais8_367_24 msg(nmea_payload, pad); + if (msg.had_error()) { + return msg.get_error(); + } + + DictSafeSetItem(dict, "version", msg.version); + DictSafeSetItem(dict, "utc_hour", msg.utc_hour); + DictSafeSetItem(dict, "utc_min", msg.utc_min); + DictSafeSetItem(dict, "x", "y", msg.position); + DictSafeSetItem(dict, "pressure", msg.pressure); + + return AIS_OK; +} + +AIS_STATUS +ais8_367_25_append_pydict(const char *nmea_payload, PyObject *dict, + const size_t pad) { + assert(nmea_payload); + assert(dict); + assert(pad < 6); + Ais8_367_25 msg(nmea_payload, pad); + if (msg.had_error()) { + return msg.get_error(); + } + + DictSafeSetItem(dict, "version", msg.version); + DictSafeSetItem(dict, "utc_hour", msg.utc_hour); + DictSafeSetItem(dict, "utc_min", msg.utc_min); + DictSafeSetItem(dict, "pressure", msg.pressure); + DictSafeSetItem(dict, "wind_speed", msg.wind_speed); + DictSafeSetItem(dict, "wind_dir", msg.wind_dir); + + return AIS_OK; +} + +AIS_STATUS +ais8_367_33_append_pydict_sensor_hdr(PyObject *dict, + Ais8_367_33_SensorReport* rpt) { + assert(dict); + assert(rpt); + DictSafeSetItem(dict, "report_type", rpt->report_type); + DictSafeSetItem(dict, "utc_day", rpt->utc_day); + DictSafeSetItem(dict, "utc_hr", rpt->utc_hr); + DictSafeSetItem(dict, "utc_min", rpt->utc_min); + DictSafeSetItem(dict, "site_id", rpt->site_id); + + return AIS_OK; +} + +// +// IMO Circ 289 - Environmental +AIS_STATUS +ais8_367_33_append_pydict(const char *nmea_payload, PyObject *dict, + const size_t pad) { + assert(nmea_payload); + assert(dict); + assert(pad < 6); + Ais8_367_33 msg(nmea_payload, pad); + if (msg.had_error()) { + return msg.get_error(); + } + + PyObject *rpt_list = PyList_New(msg.reports.size()); + DictSafeSetItem(dict, "reports", rpt_list); + + for (size_t rpt_num = 0; rpt_num < msg.reports.size(); rpt_num++) { + PyObject *rpt_dict = PyDict_New(); + PyList_SetItem(rpt_list, rpt_num, rpt_dict); + + switch (msg.reports[rpt_num]->report_type) { + // case AIS8_367_33_SENSOR_ERROR: + case AIS8_367_33_SENSOR_LOCATION: + { + Ais8_367_33_Location *rpt = + reinterpret_cast(msg.reports[rpt_num]); + ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); + DictSafeSetItem(rpt_dict, "x", "y", rpt->position); + DictSafeSetItem(rpt_dict, "z", rpt->altitude); + DictSafeSetItem(rpt_dict, "owner", rpt->owner); + DictSafeSetItem(rpt_dict, "timeout", rpt->timeout); + DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); + } + break; + case AIS8_367_33_SENSOR_STATION: + { + Ais8_367_33_Station *rpt = + reinterpret_cast(msg.reports[rpt_num]); + ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); + DictSafeSetItem(rpt_dict, "name", rpt->name); + DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); + } + break; + case AIS8_367_33_SENSOR_WIND: + { + Ais8_367_33_Wind *rpt = + reinterpret_cast(msg.reports[rpt_num]); + ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); + DictSafeSetItem(rpt_dict, "wind_speed", rpt->wind_speed); + DictSafeSetItem(rpt_dict, "wind_gust", rpt->wind_gust); + DictSafeSetItem(rpt_dict, "wind_dir", rpt->wind_dir); + DictSafeSetItem(rpt_dict, "wind_gust_dir", rpt->wind_gust_dir); + DictSafeSetItem(rpt_dict, "sensor_type", rpt->sensor_type); + DictSafeSetItem(rpt_dict, "wind_forecast", rpt->wind_forecast); + DictSafeSetItem( + rpt_dict, "wind_gust_forecast", rpt->wind_gust_forecast); + DictSafeSetItem(rpt_dict, "wind_dir_forecast", rpt->wind_dir_forecast); + DictSafeSetItem(rpt_dict, "utc_day_forecast", rpt->utc_day_forecast); + DictSafeSetItem(rpt_dict, "utc_hour_forecast", rpt->utc_hour_forecast); + DictSafeSetItem(rpt_dict, "utc_min_forecast", rpt->utc_min_forecast); + DictSafeSetItem(rpt_dict, "duration", rpt->duration); + DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); + } + break; + case AIS8_367_33_SENSOR_WATER_LEVEL: + { + Ais8_367_33_WaterLevel *rpt = + reinterpret_cast(msg.reports[rpt_num]); + ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); + DictSafeSetItem(rpt_dict, "type", rpt->type); + DictSafeSetItem(rpt_dict, "level", rpt->level); + DictSafeSetItem(rpt_dict, "trend", rpt->trend); + DictSafeSetItem(rpt_dict, "vdatum", rpt->vdatum); + DictSafeSetItem(rpt_dict, "sensor_type", rpt->sensor_type); + DictSafeSetItem(rpt_dict, "forecast_type", rpt->forecast_type); + DictSafeSetItem(rpt_dict, "level_forecast", rpt->level_forecast); + DictSafeSetItem(rpt_dict, "utc_day_forecast", rpt->utc_day_forecast); + DictSafeSetItem(rpt_dict, "utc_hour_forecast", rpt->utc_hour_forecast); + DictSafeSetItem(rpt_dict, "utc_min_forecast", rpt->utc_min_forecast); + DictSafeSetItem(rpt_dict, "duration", rpt->duration); + DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); + } + break; + case AIS8_367_33_SENSOR_CURR_2D: + { + Ais8_367_33_Curr2D *rpt = + reinterpret_cast(msg.reports[rpt_num]); + ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); + DictSafeSetItem(rpt_dict, "type", rpt->type); + DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); + + PyObject *curr_list = PyList_New(3); + DictSafeSetItem(dict, "currents", curr_list); + for (size_t idx = 0; idx < 3; idx++) { + PyObject *curr_dict = PyDict_New(); + DictSafeSetItem(curr_dict, "speed", rpt->currents[idx].speed); + DictSafeSetItem(curr_dict, "dir", rpt->currents[idx].dir); + DictSafeSetItem(curr_dict, "depth", rpt->currents[idx].depth); + PyList_SetItem(curr_list, idx, curr_dict); + } + } + break; + case AIS8_367_33_SENSOR_CURR_3D: + { + Ais8_367_33_Curr3D *rpt = + reinterpret_cast(msg.reports[rpt_num]); + ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); + DictSafeSetItem(rpt_dict, "type", rpt->type); + DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); + + PyObject *curr_list = PyList_New(2); + DictSafeSetItem(dict, "currents", curr_list); + for (size_t idx = 0; idx < 2; idx++) { + // ERROR: no way to specify negative direction + PyObject *curr_dict = PyDict_New(); + PyList_SetItem(curr_list, idx, curr_dict); + DictSafeSetItem(curr_dict, "north", rpt->currents[idx].north); + DictSafeSetItem(curr_dict, "east", rpt->currents[idx].east); + DictSafeSetItem(curr_dict, "up", rpt->currents[idx].up); + DictSafeSetItem(curr_dict, "depth", rpt->currents[idx].depth); + } + } + break; + case AIS8_367_33_SENSOR_HORZ_FLOW: + { + Ais8_367_33_HorzFlow *rpt = + reinterpret_cast(msg.reports[rpt_num]); + ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); + DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); + DictSafeSetItem(rpt_dict, "bearing", rpt->bearing); + DictSafeSetItem(rpt_dict, "type", rpt->type); + + PyObject *curr_list = PyList_New(2); + DictSafeSetItem(dict, "currents", curr_list); + for (size_t idx = 0; idx < 2; idx++) { + PyObject *curr_dict = PyDict_New(); + PyList_SetItem(curr_list, idx, curr_dict); + DictSafeSetItem(curr_dict, "dist", rpt->currents[idx].dist); + DictSafeSetItem(curr_dict, "speed", rpt->currents[idx].speed); + DictSafeSetItem(curr_dict, "dir", rpt->currents[idx].dir); + DictSafeSetItem(curr_dict, "level", rpt->currents[idx].level); + } + } + break; + case AIS8_367_33_SENSOR_SEA_STATE: + { + Ais8_367_33_SeaState *rpt = + reinterpret_cast(msg.reports[rpt_num]); + ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); + DictSafeSetItem(rpt_dict, "swell_height", rpt->swell_height); + DictSafeSetItem(rpt_dict, "swell_period", rpt->swell_period); + DictSafeSetItem(rpt_dict, "swell_dir", rpt->swell_dir); + DictSafeSetItem(rpt_dict, "sea_state", rpt->sea_state); + DictSafeSetItem(rpt_dict, "swell_sensor_type", rpt->swell_sensor_type); + DictSafeSetItem(rpt_dict, "water_temp", rpt->water_temp); + DictSafeSetItem(rpt_dict, "water_temp_depth", rpt->water_temp_depth); + DictSafeSetItem(rpt_dict, "water_sensor_type", rpt->water_sensor_type); + DictSafeSetItem(rpt_dict, "wave_height", rpt->wave_height); + DictSafeSetItem(rpt_dict, "wave_period", rpt->wave_period); + DictSafeSetItem(rpt_dict, "wave_dir", rpt->wave_dir); + DictSafeSetItem(rpt_dict, "wave_sensor_type", rpt->wave_sensor_type); + DictSafeSetItem(rpt_dict, "salinity", rpt->salinity); + } + break; + case AIS8_367_33_SENSOR_SALINITY: + { + Ais8_367_33_Salinity *rpt = + reinterpret_cast(msg.reports[rpt_num]); + ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); + DictSafeSetItem(rpt_dict, "water_temp", rpt->water_temp); + DictSafeSetItem(rpt_dict, "conductivity", rpt->conductivity); + DictSafeSetItem(rpt_dict, "pressure", rpt->pressure); + DictSafeSetItem(rpt_dict, "salinity", rpt->salinity); + DictSafeSetItem(rpt_dict, "salinity_type", rpt->salinity_type); + DictSafeSetItem(rpt_dict, "sensor_type", rpt->sensor_type); + DictSafeSetItem(rpt_dict, "spare0", rpt->spare2[0]); + DictSafeSetItem(rpt_dict, "spare1", rpt->spare2[1]); + } + break; + case AIS8_367_33_SENSOR_WX: + { + Ais8_367_33_Wx *rpt = + reinterpret_cast(msg.reports[rpt_num]); + ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); + DictSafeSetItem(rpt_dict, "air_temp", rpt->air_temp); + DictSafeSetItem(rpt_dict, "air_temp_sensor_type", + rpt->air_temp_sensor_type); + DictSafeSetItem(rpt_dict, "precip", rpt->precip); + DictSafeSetItem(rpt_dict, "horz_vis", rpt->horz_vis); + DictSafeSetItem(rpt_dict, "dew_point", rpt->dew_point); + DictSafeSetItem(rpt_dict, "dew_point_type", rpt->dew_point_type); + DictSafeSetItem(rpt_dict, "air_pressure", rpt->air_pressure); + DictSafeSetItem(rpt_dict, "air_pressure_trend", + rpt->air_pressure_trend); + DictSafeSetItem(rpt_dict, "air_pressor_type", rpt->air_pressor_type); + DictSafeSetItem(rpt_dict, "salinity", rpt->salinity); + DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); + } + break; + case AIS8_367_33_SENSOR_AIR_GAP: + { + Ais8_367_33_AirGap *rpt = + reinterpret_cast(msg.reports[rpt_num]); + ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); + DictSafeSetItem(rpt_dict, "draught", rpt->draught); + DictSafeSetItem(rpt_dict, "gap", rpt->gap); + DictSafeSetItem(rpt_dict, "forecast_gap", rpt->forecast_gap); + DictSafeSetItem(rpt_dict, "trend", rpt->trend); + DictSafeSetItem(rpt_dict, "utc_day_forecast", rpt->utc_day_forecast); + DictSafeSetItem(rpt_dict, "utc_hour_forecast", rpt->utc_hour_forecast); + DictSafeSetItem(rpt_dict, "utc_min_forecast", rpt->utc_min_forecast); + DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); + } + break; + case AIS8_367_33_SENSOR_WIND_REPORT_2: + { + Ais8_367_33_Wind_V2 *rpt = + reinterpret_cast(msg.reports[rpt_num]); + ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); + + DictSafeSetItem(rpt_dict, "wind_speed", rpt->wind_speed); + + DictSafeSetItem(rpt_dict, "wind_gust", rpt->wind_gust); + DictSafeSetItem(rpt_dict, "wind_dir", rpt->wind_dir); + DictSafeSetItem(rpt_dict, "averaging_time", rpt->averaging_time); + DictSafeSetItem(rpt_dict, "sensor_type", rpt->sensor_type); + DictSafeSetItem(rpt_dict, "wind_speed_forecast", + rpt->wind_speed_forecast); + DictSafeSetItem(rpt_dict, "wind_gust_forecast", + rpt->wind_gust_forecast); + DictSafeSetItem(rpt_dict, "wind_dir_forecast", rpt->wind_dir_forecast); + DictSafeSetItem(rpt_dict, "utc_hour_forecast", rpt->utc_hour_forecast); + DictSafeSetItem(rpt_dict, "utc_min_forecast", rpt->utc_min_forecast); + DictSafeSetItem(rpt_dict, "duration", rpt->duration); + DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); + } + break; + case AIS8_367_33_SENSOR_RESERVED_12: + case AIS8_367_33_SENSOR_RESERVED_13: + case AIS8_367_33_SENSOR_RESERVED_14: + case AIS8_367_33_SENSOR_RESERVED_15: + default: + {} // TODO(schwehr): mark a bad sensor type or raise exception + } + } + + return AIS_OK; +} + + // AIS Binary broadcast messages. There will be a huge number of subtypes // If we don't know how to decode it, just return the dac, fi PyObject* @@ -2106,6 +2441,18 @@ ais8_to_pydict(const char *nmea_payload, const size_t pad) { case 22: // USCG Area Notice 2012 (v5?). ais8_367_22_append_pydict(nmea_payload, dict, pad); break; + case AIS_FI_8_367_23_SSW: + status = ais8_367_23_append_pydict(nmea_payload, dict, pad); + break; + case AIS_FI_8_367_24_SSW_SMALL: + status = ais8_367_24_append_pydict(nmea_payload, dict, pad); + break; + case AIS_FI_8_367_25_SSW_TINY: + status = ais8_367_25_append_pydict(nmea_payload, dict, pad); + break; + case AIS_FI_8_367_33_ENVIRONMENTAL: + status = ais8_367_33_append_pydict(nmea_payload, dict, pad); + break; default: DictSafeSetItem(dict, "parsed", false); break; diff --git a/src/libais/decode_body.cpp b/src/libais/decode_body.cpp index 0e6a1c9d..64a78f66 100644 --- a/src/libais/decode_body.cpp +++ b/src/libais/decode_body.cpp @@ -139,6 +139,8 @@ unique_ptr CreateAisMsg8(const string &body, const int fill_bits) { switch (msg.fi) { case 22: return MakeUnique(body.c_str(), fill_bits); + case 33: + return MakeUnique(body.c_str(), fill_bits); } // FI not handled. break; diff --git a/test/test_data.py b/test/test_data.py index 30f06a7a..b229e1ac 100644 --- a/test/test_data.py +++ b/test/test_data.py @@ -673,4 +673,85 @@ 'y': -2.5533333333333332} }, + { + 'nmea': ['!SAVDM,1,1,7,B,85Mr6<1KmhB:VgtfPV9lIf4M31PR000P,0*71'], + 'result': {'id': 8, + 'repeat_indicator': 0, + 'mmsi': 366904880, + 'spare': 0, + 'dac': 367, + 'fi': 23, + 'version': 0, + 'utc_day': 4, + 'utc_hour': 17, + 'utc_min': 20, + 'x': -87.4372, + 'y': 41.6737, + 'pressure': 1020, + 'air_temp': 28.5, + 'wind_speed': 6, + 'wind_gust': 6, + 'wind_dir': 17} + }, + + { + 'nmea': ['!SAVDM,1,1,3,A,85Mr6<1Kn15CGvG@C4rk1oFAKpB95?AruFRl7mre0 Date: Fri, 10 Sep 2021 16:32:15 -0700 Subject: [PATCH 2/3] Remove 8:367:23,25,33 move 8:367:24 to correct file, add test Added message reference to ais8_367.cpp. Added missing operator<< 8:367:24: https://www.e-navigation.nl/content/satellite-ship-weather-small --- setup.py | 1 - src/libais/CMakeLists.txt | 1 - src/libais/Makefile-custom | 1 - src/libais/Makefile.am | 1 - src/libais/ais.h | 345 ++----------------------------------- src/libais/ais8.cpp | 98 ----------- src/libais/ais8_367.cpp | 36 ++++ src/libais/ais_py.cpp | 323 ---------------------------------- src/libais/decode_body.cpp | 4 +- src/test/ais8_367_test.cc | 31 ++++ test/test_data.py | 67 +------ 11 files changed, 86 insertions(+), 822 deletions(-) diff --git a/setup.py b/setup.py index b2693736..ef7e9a77 100755 --- a/setup.py +++ b/setup.py @@ -42,7 +42,6 @@ 'ais8_200.cpp', 'ais8_366.cpp', 'ais8_367.cpp', - 'ais8_367_33.cpp', 'ais9.cpp', 'ais10.cpp', # : # 11 See 4 - ; diff --git a/src/libais/CMakeLists.txt b/src/libais/CMakeLists.txt index f63bbd07..6862ed84 100644 --- a/src/libais/CMakeLists.txt +++ b/src/libais/CMakeLists.txt @@ -13,7 +13,6 @@ ais8_200.cpp ais8_366.cpp ais8_366_22.cpp ais8_367.cpp -ais8_367_33.cpp ais9.cpp ais10.cpp ais12.cpp diff --git a/src/libais/Makefile-custom b/src/libais/Makefile-custom index 0ba7ee33..5cbcc2b9 100644 --- a/src/libais/Makefile-custom +++ b/src/libais/Makefile-custom @@ -67,7 +67,6 @@ SRCS += ais8_200.cpp # River Information System (RIS) SRCS += ais8_366.cpp SRCS += ais8_366_22.cpp SRCS += ais8_367.cpp -SRCS += ais8_367_33.cpp SRCS += ais9.cpp SRCS += ais10.cpp diff --git a/src/libais/Makefile.am b/src/libais/Makefile.am index 2c90e2ac..68cbeb5c 100644 --- a/src/libais/Makefile.am +++ b/src/libais/Makefile.am @@ -20,7 +20,6 @@ libais_la_SOURCES = \ ais8_366.cpp \ ais8_366_22.cpp \ ais8_367.cpp \ - ais8_367_33.cpp \ ais9.cpp \ ais10.cpp \ ais12.cpp \ diff --git a/src/libais/ais.h b/src/libais/ais.h index bfef4a3e..a2e72fcb 100644 --- a/src/libais/ais.h +++ b/src/libais/ais.h @@ -1979,6 +1979,22 @@ class Ais8_367_22 : public Ais8 { }; ostream& operator<< (ostream& o, Ais8_367_22 const& msg); +// +// SSW Satellite Ship Weather 1-Slot Version +class Ais8_367_24 : public Ais8 { + public: + int version; + + int utc_hour; + int utc_min; + + AisPoint position; // 25, 24 bits + int pressure; // hPa + + Ais8_367_24(const char *nmea_payload, const size_t pad); +}; +ostream& operator<< (ostream &o, const Ais8_367_24 &msg); + class Ais9 : public AisMsg { public: int alt; // m above sea level @@ -2433,335 +2449,6 @@ class Ais27 : public AisMsg { }; ostream& operator<< (ostream &o, const Ais27 &msg); - -// SSW Satellite Ship Weather 1-Slot Version -class Ais8_367_23 : public Ais8 { - public: - int version; - - int utc_day; - int utc_hour; - int utc_min; - - AisPoint position; // 25, 24 bits - int pressure; // hPa - float air_temp; // C - int wind_speed; // knots - int wind_gust; // knots - int wind_dir; // degrees - int spare; - - Ais8_367_23(const char *nmea_payload, const size_t pad); -}; -ostream& operator<< (ostream &o, const Ais8_367_23 &msg); - -// -// SSW Satellite Ship Weather 1-Slot Version -class Ais8_367_24 : public Ais8 { - public: - int version; - - int utc_day; - int utc_hour; - int utc_min; - - AisPoint position; // 25, 24 bits - int pressure; // hPa - - Ais8_367_24(const char *nmea_payload, const size_t pad); -}; -ostream& operator<< (ostream &o, const Ais8_367_24 &msg); - -// -// SSW Satellite Ship Weather Tiny Version -class Ais8_367_25 : public Ais8 { - public: - int version; - - int utc_day; - int utc_hour; - int utc_min; - - int pressure; // hPa - int wind_speed; // Knots - int wind_dir; // Degrees - - Ais8_367_25(const char *nmea_payload, const size_t pad); -}; -ostream& operator<< (ostream &o, const Ais8_367_25 &msg); - -const size_t AIS8_367_33_REPORT_SIZE = 112; - -enum Ais8_367_33_SensorEnum { - AIS8_367_33_SENSOR_ERROR = -1, - AIS8_367_33_SENSOR_LOCATION = 0, - AIS8_367_33_SENSOR_STATION = 1, - AIS8_367_33_SENSOR_WIND = 2, - AIS8_367_33_SENSOR_WATER_LEVEL = 3, - AIS8_367_33_SENSOR_CURR_2D = 4, - AIS8_367_33_SENSOR_CURR_3D = 5, - AIS8_367_33_SENSOR_HORZ_FLOW = 6, - AIS8_367_33_SENSOR_SEA_STATE = 7, - AIS8_367_33_SENSOR_SALINITY = 8, - AIS8_367_33_SENSOR_WX = 9, - AIS8_367_33_SENSOR_AIR_GAP = 10, - AIS8_367_33_SENSOR_WIND_REPORT_2 = 11, - AIS8_367_33_SENSOR_RESERVED_12 = 12, - AIS8_367_33_SENSOR_RESERVED_13 = 13, - AIS8_367_33_SENSOR_RESERVED_14 = 14, - AIS8_367_33_SENSOR_RESERVED_15 = 15, -}; - - -class Ais8_367_33_SensorReport { - public: - int report_type; - int utc_day; - int utc_hr; - int utc_min; - int site_id; // aka link_id - - virtual Ais8_367_33_SensorEnum getType() const = 0; - virtual ~Ais8_367_33_SensorReport() {} -}; - -Ais8_367_33_SensorReport* -ais8_367_33_sensor_report_factory(const AisBitset &bs, - const size_t offset); - -class Ais8_367_33_Location : public Ais8_367_33_SensorReport { - public: - int version; - AisPoint position; - int precision; - float altitude; - int owner; - int timeout; - int spare2; - - Ais8_367_33_Location(const AisBitset &bs, const size_t offset); - Ais8_367_33_Location() {} - Ais8_367_33_SensorEnum getType() const { return AIS8_367_33_SENSOR_LOCATION; } -}; - -class Ais8_367_33_Station : public Ais8_367_33_SensorReport { - public: - string name; - int spare2; - - Ais8_367_33_Station(const AisBitset &bs, const size_t offset); - Ais8_367_33_Station() {} - Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_STATION;} -}; - -class Ais8_367_33_Wind : public Ais8_367_33_SensorReport { - public: - int wind_speed; // knots - int wind_gust; // knots - int wind_dir; - int wind_gust_dir; - int sensor_type; - int wind_forecast; // knots - int wind_gust_forecast; // knots - int wind_dir_forecast; - int utc_day_forecast; - int utc_hour_forecast; - int utc_min_forecast; - int duration; - int spare2; - - Ais8_367_33_Wind(const AisBitset &bs, const size_t offset); - Ais8_367_33_Wind() {} - Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_WIND;} -}; - -class Ais8_367_33_WaterLevel : public Ais8_367_33_SensorReport { - public: - int type; - float level; // m. assuming it is being stored at 0.01 m inc. - int trend; - int vdatum; - int sensor_type; - int forecast_type; - float level_forecast; - int utc_day_forecast; - int utc_hour_forecast; - int utc_min_forecast; - int duration; // minutes - int spare2; - - Ais8_367_33_WaterLevel(const AisBitset &bs, const size_t offset); - Ais8_367_33_WaterLevel() {} - Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_WATER_LEVEL;} -}; - -class Ais8_367_33_Curr2D_Current { - public: - float speed; // knots - int dir; - int depth; // m -}; - -class Ais8_367_33_Curr2D : public Ais8_367_33_SensorReport { - public: - Ais8_367_33_Curr2D_Current currents[3]; - int type; - int spare2; - - Ais8_367_33_Curr2D(const AisBitset &bs, const size_t offset); - Ais8_367_33_Curr2D() {} - Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_CURR_2D;} -}; - -class Ais8_367_33_Curr3D_Current { - public: - float north; - float east; - float up; - int depth; // m -}; - -class Ais8_367_33_Curr3D : public Ais8_367_33_SensorReport { - public: - Ais8_367_33_Curr3D_Current currents[2]; - int type; - int spare2; - - Ais8_367_33_Curr3D(const AisBitset &bs, const size_t offset); - Ais8_367_33_Curr3D() {} - Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_CURR_3D;} -}; - -class Ais8_367_33_HorzFlow_Current { - public: - int dist; // m - float speed; // knots - int dir; // deg - int level; // m -}; - -class Ais8_367_33_HorzFlow : public Ais8_367_33_SensorReport { - public: - Ais8_367_33_HorzFlow_Current currents[2]; - int bearing; // deg - int type; - int spare2; - - Ais8_367_33_HorzFlow(const AisBitset &bs, const size_t offset); - Ais8_367_33_HorzFlow() {} - Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_HORZ_FLOW;} -}; - -class Ais8_367_33_SeaState : public Ais8_367_33_SensorReport { - public: - float swell_height; - int swell_period; // seconds - int swell_dir; // deg - int sea_state; - int swell_sensor_type; - float water_temp; // C - float water_temp_depth; // m - int water_sensor_type; - float wave_height; - int wave_period; // seconds - int wave_dir; // deg - int wave_sensor_type; - float salinity; - - Ais8_367_33_SeaState(const AisBitset &bs, const size_t offset); - Ais8_367_33_SeaState() {} - Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_SEA_STATE;} -}; - -class Ais8_367_33_Salinity : public Ais8_367_33_SensorReport { - public: - float water_temp; // C - float conductivity; // siemens/m - float pressure; // decibars - float salinity; // 0/00 ppt - int salinity_type; - int sensor_type; - int spare2[2]; - - Ais8_367_33_Salinity(const AisBitset &bs, const size_t offset); - Ais8_367_33_Salinity() {} - Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_SALINITY;} -}; - -class Ais8_367_33_Wx : public Ais8_367_33_SensorReport { - public: - float air_temp; // C - int air_temp_sensor_type; - int precip; - float horz_vis; // nm - float dew_point; // C - int dew_point_type; - float air_pressure; // Pascals (Pa). - int air_pressure_trend; - int air_pressor_type; - float salinity; // 0/00 ppt - int spare2; - - Ais8_367_33_Wx(const AisBitset &bs, const size_t offset); - Ais8_367_33_Wx() {} - Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_WX;} -}; - -class Ais8_367_33_AirGap : public Ais8_367_33_SensorReport { - public: - float draught; - float gap; - int trend; - float forecast_gap; - int utc_day_forecast; - int utc_hour_forecast; - int utc_min_forecast; - int type; - int spare2; - - Ais8_367_33_AirGap(const AisBitset &bs, const size_t offset); - Ais8_367_33_AirGap() {} - Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_AIR_GAP;} -}; - -class Ais8_367_33_Wind_V2 : public Ais8_367_33_SensorReport { - public: - int wind_speed; // knots - int wind_gust; // knots - int wind_dir; - int averaging_time; - int sensor_type; - int wind_speed_forecast; // knots - int wind_gust_forecast; // knots - int wind_dir_forecast; - int utc_hour_forecast; - int utc_min_forecast; - int duration; - int spare2; - - Ais8_367_33_Wind_V2(const AisBitset &bs, const size_t offset); - Ais8_367_33_Wind_V2() {} - Ais8_367_33_SensorEnum getType() const {return AIS8_367_33_SENSOR_WIND_REPORT_2;} -}; - -// -// Environmental Message -class Ais8_367_33 : public Ais8 { - public: - int report_type; - - int utc_day; - int utc_hour; - int utc_min; - int site_id; - - vector reports; - - Ais8_367_33(const char *nmea_payload, const size_t pad); - ~Ais8_367_33(); -}; -ostream& operator<< (ostream &o, const Ais8_367_33 &msg); - } // namespace libais #endif // LIBAIS_AIS_H_ diff --git a/src/libais/ais8.cpp b/src/libais/ais8.cpp index 6ef9f106..b28727a2 100644 --- a/src/libais/ais8.cpp +++ b/src/libais/ais8.cpp @@ -628,102 +628,4 @@ Ais8_1_31::Ais8_1_31(const char *nmea_payload, const size_t pad) assert(bits.GetRemaining() == 0); } -// SSW FI23 Satellite Ship Weather 1-Slot Version -Ais8_367_23::Ais8_367_23(const char *nmea_payload, const size_t pad) - : Ais8(nmea_payload, pad), version(0), utc_day(0), - utc_hour(), utc_min(), position(), pressure(0.0), air_temp(0.0), - wind_speed(0), wind_gust(0.0), wind_dir(0) { - assert(dac == 367); - assert(fi == 23); - - if (!CheckStatus()) { - return; - } - - if ((num_bits != 168) && (num_bits != 192)) { - status = AIS_ERR_BAD_BIT_COUNT; - return; - } - - bits.SeekTo(56); - - version = bits.ToUnsignedInt(56, 3); - utc_day = bits.ToUnsignedInt(59, 5); - utc_hour = bits.ToUnsignedInt(64, 5); - utc_min = bits.ToUnsignedInt(69, 6); - position = bits.ToAisPoint(75, 49); - pressure = bits.ToUnsignedInt(124, 9) + 800; // hPa - air_temp = bits.ToInt(133, 11) / 10.; // C - wind_speed = bits.ToUnsignedInt(144, 7); // ave knots - wind_gust = bits.ToUnsignedInt(151, 7); // ave knots - wind_dir = bits.ToUnsignedInt(158, 9); - - spare = bits.ToUnsignedInt(167, 1); - - // NOTE: I'm seeing 192-bit messages come in, which means 24 bits left. - // assert(bits.GetRemaining() == 0); - status = AIS_OK; -} - -// SSW FI24 Satellite Ship Weather Small - Less than 1-Slot Version -Ais8_367_24::Ais8_367_24(const char *nmea_payload, const size_t pad) - : Ais8(nmea_payload, pad), version(0), - utc_hour(), utc_min(), position(), pressure(0.0) { - assert(dac == 367); - assert(fi == 24); - - if (!CheckStatus()) { - return; - } - - if ((num_bits != 128) && (num_bits != 144)) { - status = AIS_ERR_BAD_BIT_COUNT; - return; - } - - bits.SeekTo(56); - - version = bits.ToUnsignedInt(56, 3); - utc_hour = bits.ToUnsignedInt(59, 5); - utc_min = bits.ToUnsignedInt(64, 6); - position = bits.ToAisPoint(70, 49); - pressure = bits.ToUnsignedInt(119, 9) + 800; // hPa - - // NOTE: If message comes in at 144, then there will be 16 bits remaining. - // assert(bits.GetRemaining() == 0); - status = AIS_OK; -} - -// SSW FI25 Satellite Ship Weather Tiny Version -Ais8_367_25::Ais8_367_25(const char *nmea_payload, const size_t pad) - : Ais8(nmea_payload, pad), version(0), - utc_hour(), utc_min(), pressure(0.0) , wind_speed(0), wind_dir(0) { - assert(dac == 367); - assert(fi == 25); - - if (!CheckStatus()) { - return; - } - - if ((num_bits != 96) && (num_bits != 120)) { - status = AIS_ERR_BAD_BIT_COUNT; - return; - } - - bits.SeekTo(56); - - version = bits.ToUnsignedInt(56, 3); - utc_hour = bits.ToUnsignedInt(59, 5); - utc_min = bits.ToUnsignedInt(64, 6); - pressure = bits.ToUnsignedInt(70, 9) + 800; // hPa - wind_speed = bits.ToUnsignedInt(79, 7); // Knots - wind_dir = bits.ToUnsignedInt(86, 9); // Degrees - - spare = bits.ToUnsignedInt(95, 1); - - // NOTE: if num_bits is 120, then there will be 24 bits left. - // assert(bits.GetRemaining() == 0); - status = AIS_OK; -} - } // namespace libais diff --git a/src/libais/ais8_367.cpp b/src/libais/ais8_367.cpp index 647c100e..0e70a18f 100644 --- a/src/libais/ais8_367.cpp +++ b/src/libais/ais8_367.cpp @@ -4,6 +4,9 @@ // // http://www.e-navigation.nl/content/geographic-notice // http://www.e-navigation.nl/sites/default/files/asm_files/GN%20Release%20Version%201b.pdf +// +// 8:367:24 +// https://www.e-navigation.nl/content/satellite-ship-weather-small #include @@ -169,4 +172,37 @@ Ais8_367_22::~Ais8_367_22() { } } +// SSW FI24 Satellite Ship Weather Small - Less than 1-Slot Version +Ais8_367_24::Ais8_367_24(const char *nmea_payload, const size_t pad) + : Ais8(nmea_payload, pad), version(0), + utc_hour(), utc_min(), position(), pressure(0.0) { + assert(dac == 367); + assert(fi == 24); + + if (!CheckStatus()) { + return; + } + + if ((num_bits != 128) && (num_bits != 144)) { + status = AIS_ERR_BAD_BIT_COUNT; + return; + } + + bits.SeekTo(56); + + version = bits.ToUnsignedInt(56, 3); + utc_hour = bits.ToUnsignedInt(59, 5); + utc_min = bits.ToUnsignedInt(64, 6); + position = bits.ToAisPoint(70, 49); + pressure = bits.ToUnsignedInt(119, 9) + 799; // hPa + + // NOTE: If message comes in at 144, then there will be 16 bits remaining. + // assert(bits.GetRemaining() == 0); + status = AIS_OK; +} + +ostream& operator<< (ostream &o, const Ais8_367_24 &msg) { + return o << msg.mmsi << ": " << msg.version << ": " << msg.utc_hour << ": " << msg.utc_min << ": " << msg.pressure; +} + } // namespace libais diff --git a/src/libais/ais_py.cpp b/src/libais/ais_py.cpp index fa30ce48..54330ea3 100644 --- a/src/libais/ais_py.cpp +++ b/src/libais/ais_py.cpp @@ -61,10 +61,7 @@ enum AIS_FI { AIS_FI_8_200_55_RIS_PERSONS_ON_BOARD = 50, AIS_FI_8_366_22_AREA_NOTICE = 22, // USCG. AIS_FI_8_367_22_AREA_NOTICE = 22, // USCG. - AIS_FI_8_367_23_SSW = 23, // USCG Satellite Ship Weather AIS_FI_8_367_24_SSW_SMALL = 24, // USCG Satellite Ship Weather Small - AIS_FI_8_367_25_SSW_TINY = 25, // USCG Satellite Ship Weather Tiny - AIS_FI_8_367_33_ENVIRONMENTAL = 33, // USCG Environmental Report }; void @@ -1992,32 +1989,6 @@ ais8_367_22_append_pydict(const char *nmea_payload, PyObject *dict, DictSafeSetItem(dict, "sub_areas", sub_area_list); } -AIS_STATUS -ais8_367_23_append_pydict(const char *nmea_payload, PyObject *dict, - const size_t pad) { - assert(nmea_payload); - assert(dict); - assert(pad < 6); - Ais8_367_23 msg(nmea_payload, pad); - if (msg.had_error()) { - return msg.get_error(); - } - - DictSafeSetItem(dict, "version", msg.version); - DictSafeSetItem(dict, "utc_day", msg.utc_day); - DictSafeSetItem(dict, "utc_hour", msg.utc_hour); - DictSafeSetItem(dict, "utc_min", msg.utc_min); - DictSafeSetItem(dict, "x", "y", msg.position); - - DictSafeSetItem(dict, "pressure", msg.pressure); - DictSafeSetItem(dict, "air_temp", msg.air_temp); - DictSafeSetItem(dict, "wind_speed", msg.wind_speed); - DictSafeSetItem(dict, "wind_gust", msg.wind_gust); - DictSafeSetItem(dict, "wind_dir", msg.wind_dir); - - return AIS_OK; -} - AIS_STATUS ais8_367_24_append_pydict(const char *nmea_payload, PyObject *dict, const size_t pad) { @@ -2038,291 +2009,6 @@ ais8_367_24_append_pydict(const char *nmea_payload, PyObject *dict, return AIS_OK; } -AIS_STATUS -ais8_367_25_append_pydict(const char *nmea_payload, PyObject *dict, - const size_t pad) { - assert(nmea_payload); - assert(dict); - assert(pad < 6); - Ais8_367_25 msg(nmea_payload, pad); - if (msg.had_error()) { - return msg.get_error(); - } - - DictSafeSetItem(dict, "version", msg.version); - DictSafeSetItem(dict, "utc_hour", msg.utc_hour); - DictSafeSetItem(dict, "utc_min", msg.utc_min); - DictSafeSetItem(dict, "pressure", msg.pressure); - DictSafeSetItem(dict, "wind_speed", msg.wind_speed); - DictSafeSetItem(dict, "wind_dir", msg.wind_dir); - - return AIS_OK; -} - -AIS_STATUS -ais8_367_33_append_pydict_sensor_hdr(PyObject *dict, - Ais8_367_33_SensorReport* rpt) { - assert(dict); - assert(rpt); - DictSafeSetItem(dict, "report_type", rpt->report_type); - DictSafeSetItem(dict, "utc_day", rpt->utc_day); - DictSafeSetItem(dict, "utc_hr", rpt->utc_hr); - DictSafeSetItem(dict, "utc_min", rpt->utc_min); - DictSafeSetItem(dict, "site_id", rpt->site_id); - - return AIS_OK; -} - -// -// IMO Circ 289 - Environmental -AIS_STATUS -ais8_367_33_append_pydict(const char *nmea_payload, PyObject *dict, - const size_t pad) { - assert(nmea_payload); - assert(dict); - assert(pad < 6); - Ais8_367_33 msg(nmea_payload, pad); - if (msg.had_error()) { - return msg.get_error(); - } - - PyObject *rpt_list = PyList_New(msg.reports.size()); - DictSafeSetItem(dict, "reports", rpt_list); - - for (size_t rpt_num = 0; rpt_num < msg.reports.size(); rpt_num++) { - PyObject *rpt_dict = PyDict_New(); - PyList_SetItem(rpt_list, rpt_num, rpt_dict); - - switch (msg.reports[rpt_num]->report_type) { - // case AIS8_367_33_SENSOR_ERROR: - case AIS8_367_33_SENSOR_LOCATION: - { - Ais8_367_33_Location *rpt = - reinterpret_cast(msg.reports[rpt_num]); - ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); - DictSafeSetItem(rpt_dict, "x", "y", rpt->position); - DictSafeSetItem(rpt_dict, "z", rpt->altitude); - DictSafeSetItem(rpt_dict, "owner", rpt->owner); - DictSafeSetItem(rpt_dict, "timeout", rpt->timeout); - DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); - } - break; - case AIS8_367_33_SENSOR_STATION: - { - Ais8_367_33_Station *rpt = - reinterpret_cast(msg.reports[rpt_num]); - ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); - DictSafeSetItem(rpt_dict, "name", rpt->name); - DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); - } - break; - case AIS8_367_33_SENSOR_WIND: - { - Ais8_367_33_Wind *rpt = - reinterpret_cast(msg.reports[rpt_num]); - ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); - DictSafeSetItem(rpt_dict, "wind_speed", rpt->wind_speed); - DictSafeSetItem(rpt_dict, "wind_gust", rpt->wind_gust); - DictSafeSetItem(rpt_dict, "wind_dir", rpt->wind_dir); - DictSafeSetItem(rpt_dict, "wind_gust_dir", rpt->wind_gust_dir); - DictSafeSetItem(rpt_dict, "sensor_type", rpt->sensor_type); - DictSafeSetItem(rpt_dict, "wind_forecast", rpt->wind_forecast); - DictSafeSetItem( - rpt_dict, "wind_gust_forecast", rpt->wind_gust_forecast); - DictSafeSetItem(rpt_dict, "wind_dir_forecast", rpt->wind_dir_forecast); - DictSafeSetItem(rpt_dict, "utc_day_forecast", rpt->utc_day_forecast); - DictSafeSetItem(rpt_dict, "utc_hour_forecast", rpt->utc_hour_forecast); - DictSafeSetItem(rpt_dict, "utc_min_forecast", rpt->utc_min_forecast); - DictSafeSetItem(rpt_dict, "duration", rpt->duration); - DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); - } - break; - case AIS8_367_33_SENSOR_WATER_LEVEL: - { - Ais8_367_33_WaterLevel *rpt = - reinterpret_cast(msg.reports[rpt_num]); - ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); - DictSafeSetItem(rpt_dict, "type", rpt->type); - DictSafeSetItem(rpt_dict, "level", rpt->level); - DictSafeSetItem(rpt_dict, "trend", rpt->trend); - DictSafeSetItem(rpt_dict, "vdatum", rpt->vdatum); - DictSafeSetItem(rpt_dict, "sensor_type", rpt->sensor_type); - DictSafeSetItem(rpt_dict, "forecast_type", rpt->forecast_type); - DictSafeSetItem(rpt_dict, "level_forecast", rpt->level_forecast); - DictSafeSetItem(rpt_dict, "utc_day_forecast", rpt->utc_day_forecast); - DictSafeSetItem(rpt_dict, "utc_hour_forecast", rpt->utc_hour_forecast); - DictSafeSetItem(rpt_dict, "utc_min_forecast", rpt->utc_min_forecast); - DictSafeSetItem(rpt_dict, "duration", rpt->duration); - DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); - } - break; - case AIS8_367_33_SENSOR_CURR_2D: - { - Ais8_367_33_Curr2D *rpt = - reinterpret_cast(msg.reports[rpt_num]); - ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); - DictSafeSetItem(rpt_dict, "type", rpt->type); - DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); - - PyObject *curr_list = PyList_New(3); - DictSafeSetItem(dict, "currents", curr_list); - for (size_t idx = 0; idx < 3; idx++) { - PyObject *curr_dict = PyDict_New(); - DictSafeSetItem(curr_dict, "speed", rpt->currents[idx].speed); - DictSafeSetItem(curr_dict, "dir", rpt->currents[idx].dir); - DictSafeSetItem(curr_dict, "depth", rpt->currents[idx].depth); - PyList_SetItem(curr_list, idx, curr_dict); - } - } - break; - case AIS8_367_33_SENSOR_CURR_3D: - { - Ais8_367_33_Curr3D *rpt = - reinterpret_cast(msg.reports[rpt_num]); - ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); - DictSafeSetItem(rpt_dict, "type", rpt->type); - DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); - - PyObject *curr_list = PyList_New(2); - DictSafeSetItem(dict, "currents", curr_list); - for (size_t idx = 0; idx < 2; idx++) { - // ERROR: no way to specify negative direction - PyObject *curr_dict = PyDict_New(); - PyList_SetItem(curr_list, idx, curr_dict); - DictSafeSetItem(curr_dict, "north", rpt->currents[idx].north); - DictSafeSetItem(curr_dict, "east", rpt->currents[idx].east); - DictSafeSetItem(curr_dict, "up", rpt->currents[idx].up); - DictSafeSetItem(curr_dict, "depth", rpt->currents[idx].depth); - } - } - break; - case AIS8_367_33_SENSOR_HORZ_FLOW: - { - Ais8_367_33_HorzFlow *rpt = - reinterpret_cast(msg.reports[rpt_num]); - ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); - DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); - DictSafeSetItem(rpt_dict, "bearing", rpt->bearing); - DictSafeSetItem(rpt_dict, "type", rpt->type); - - PyObject *curr_list = PyList_New(2); - DictSafeSetItem(dict, "currents", curr_list); - for (size_t idx = 0; idx < 2; idx++) { - PyObject *curr_dict = PyDict_New(); - PyList_SetItem(curr_list, idx, curr_dict); - DictSafeSetItem(curr_dict, "dist", rpt->currents[idx].dist); - DictSafeSetItem(curr_dict, "speed", rpt->currents[idx].speed); - DictSafeSetItem(curr_dict, "dir", rpt->currents[idx].dir); - DictSafeSetItem(curr_dict, "level", rpt->currents[idx].level); - } - } - break; - case AIS8_367_33_SENSOR_SEA_STATE: - { - Ais8_367_33_SeaState *rpt = - reinterpret_cast(msg.reports[rpt_num]); - ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); - DictSafeSetItem(rpt_dict, "swell_height", rpt->swell_height); - DictSafeSetItem(rpt_dict, "swell_period", rpt->swell_period); - DictSafeSetItem(rpt_dict, "swell_dir", rpt->swell_dir); - DictSafeSetItem(rpt_dict, "sea_state", rpt->sea_state); - DictSafeSetItem(rpt_dict, "swell_sensor_type", rpt->swell_sensor_type); - DictSafeSetItem(rpt_dict, "water_temp", rpt->water_temp); - DictSafeSetItem(rpt_dict, "water_temp_depth", rpt->water_temp_depth); - DictSafeSetItem(rpt_dict, "water_sensor_type", rpt->water_sensor_type); - DictSafeSetItem(rpt_dict, "wave_height", rpt->wave_height); - DictSafeSetItem(rpt_dict, "wave_period", rpt->wave_period); - DictSafeSetItem(rpt_dict, "wave_dir", rpt->wave_dir); - DictSafeSetItem(rpt_dict, "wave_sensor_type", rpt->wave_sensor_type); - DictSafeSetItem(rpt_dict, "salinity", rpt->salinity); - } - break; - case AIS8_367_33_SENSOR_SALINITY: - { - Ais8_367_33_Salinity *rpt = - reinterpret_cast(msg.reports[rpt_num]); - ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); - DictSafeSetItem(rpt_dict, "water_temp", rpt->water_temp); - DictSafeSetItem(rpt_dict, "conductivity", rpt->conductivity); - DictSafeSetItem(rpt_dict, "pressure", rpt->pressure); - DictSafeSetItem(rpt_dict, "salinity", rpt->salinity); - DictSafeSetItem(rpt_dict, "salinity_type", rpt->salinity_type); - DictSafeSetItem(rpt_dict, "sensor_type", rpt->sensor_type); - DictSafeSetItem(rpt_dict, "spare0", rpt->spare2[0]); - DictSafeSetItem(rpt_dict, "spare1", rpt->spare2[1]); - } - break; - case AIS8_367_33_SENSOR_WX: - { - Ais8_367_33_Wx *rpt = - reinterpret_cast(msg.reports[rpt_num]); - ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); - DictSafeSetItem(rpt_dict, "air_temp", rpt->air_temp); - DictSafeSetItem(rpt_dict, "air_temp_sensor_type", - rpt->air_temp_sensor_type); - DictSafeSetItem(rpt_dict, "precip", rpt->precip); - DictSafeSetItem(rpt_dict, "horz_vis", rpt->horz_vis); - DictSafeSetItem(rpt_dict, "dew_point", rpt->dew_point); - DictSafeSetItem(rpt_dict, "dew_point_type", rpt->dew_point_type); - DictSafeSetItem(rpt_dict, "air_pressure", rpt->air_pressure); - DictSafeSetItem(rpt_dict, "air_pressure_trend", - rpt->air_pressure_trend); - DictSafeSetItem(rpt_dict, "air_pressor_type", rpt->air_pressor_type); - DictSafeSetItem(rpt_dict, "salinity", rpt->salinity); - DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); - } - break; - case AIS8_367_33_SENSOR_AIR_GAP: - { - Ais8_367_33_AirGap *rpt = - reinterpret_cast(msg.reports[rpt_num]); - ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); - DictSafeSetItem(rpt_dict, "draught", rpt->draught); - DictSafeSetItem(rpt_dict, "gap", rpt->gap); - DictSafeSetItem(rpt_dict, "forecast_gap", rpt->forecast_gap); - DictSafeSetItem(rpt_dict, "trend", rpt->trend); - DictSafeSetItem(rpt_dict, "utc_day_forecast", rpt->utc_day_forecast); - DictSafeSetItem(rpt_dict, "utc_hour_forecast", rpt->utc_hour_forecast); - DictSafeSetItem(rpt_dict, "utc_min_forecast", rpt->utc_min_forecast); - DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); - } - break; - case AIS8_367_33_SENSOR_WIND_REPORT_2: - { - Ais8_367_33_Wind_V2 *rpt = - reinterpret_cast(msg.reports[rpt_num]); - ais8_367_33_append_pydict_sensor_hdr(rpt_dict, rpt); - - DictSafeSetItem(rpt_dict, "wind_speed", rpt->wind_speed); - - DictSafeSetItem(rpt_dict, "wind_gust", rpt->wind_gust); - DictSafeSetItem(rpt_dict, "wind_dir", rpt->wind_dir); - DictSafeSetItem(rpt_dict, "averaging_time", rpt->averaging_time); - DictSafeSetItem(rpt_dict, "sensor_type", rpt->sensor_type); - DictSafeSetItem(rpt_dict, "wind_speed_forecast", - rpt->wind_speed_forecast); - DictSafeSetItem(rpt_dict, "wind_gust_forecast", - rpt->wind_gust_forecast); - DictSafeSetItem(rpt_dict, "wind_dir_forecast", rpt->wind_dir_forecast); - DictSafeSetItem(rpt_dict, "utc_hour_forecast", rpt->utc_hour_forecast); - DictSafeSetItem(rpt_dict, "utc_min_forecast", rpt->utc_min_forecast); - DictSafeSetItem(rpt_dict, "duration", rpt->duration); - DictSafeSetItem(rpt_dict, "spare2", rpt->spare2); - } - break; - case AIS8_367_33_SENSOR_RESERVED_12: - case AIS8_367_33_SENSOR_RESERVED_13: - case AIS8_367_33_SENSOR_RESERVED_14: - case AIS8_367_33_SENSOR_RESERVED_15: - default: - {} // TODO(schwehr): mark a bad sensor type or raise exception - } - } - - return AIS_OK; -} - - // AIS Binary broadcast messages. There will be a huge number of subtypes // If we don't know how to decode it, just return the dac, fi PyObject* @@ -2441,18 +2127,9 @@ ais8_to_pydict(const char *nmea_payload, const size_t pad) { case 22: // USCG Area Notice 2012 (v5?). ais8_367_22_append_pydict(nmea_payload, dict, pad); break; - case AIS_FI_8_367_23_SSW: - status = ais8_367_23_append_pydict(nmea_payload, dict, pad); - break; case AIS_FI_8_367_24_SSW_SMALL: status = ais8_367_24_append_pydict(nmea_payload, dict, pad); break; - case AIS_FI_8_367_25_SSW_TINY: - status = ais8_367_25_append_pydict(nmea_payload, dict, pad); - break; - case AIS_FI_8_367_33_ENVIRONMENTAL: - status = ais8_367_33_append_pydict(nmea_payload, dict, pad); - break; default: DictSafeSetItem(dict, "parsed", false); break; diff --git a/src/libais/decode_body.cpp b/src/libais/decode_body.cpp index 64a78f66..d7727f79 100644 --- a/src/libais/decode_body.cpp +++ b/src/libais/decode_body.cpp @@ -139,8 +139,8 @@ unique_ptr CreateAisMsg8(const string &body, const int fill_bits) { switch (msg.fi) { case 22: return MakeUnique(body.c_str(), fill_bits); - case 33: - return MakeUnique(body.c_str(), fill_bits); + case 24: + return MakeUnique(body.c_str(), fill_bits); } // FI not handled. break; diff --git a/src/test/ais8_367_test.cc b/src/test/ais8_367_test.cc index 4b91df3c..6252aaa5 100644 --- a/src/test/ais8_367_test.cc +++ b/src/test/ais8_367_test.cc @@ -178,5 +178,36 @@ TEST(Ais8_367_22Test, DecodeUscgWhaleBouyTest2) { } #endif +std::unique_ptr Init_24(const string &nmea_string) { + const string body(GetBody(nmea_string)); + const int pad = GetPad(nmea_string); + + // TODO(schwehr): Switch to c++14 make_unique. + std::unique_ptr msg(new Ais8_367_24(body.c_str(), pad)); + if (!msg || msg->had_error()) { + return nullptr; + } + + return msg; +} + +TEST(Ais8_367_24Test, DecodeSingleTest_24) { + std::unique_ptr msg = + Init_24("!SAVDM,1,1,3,A,85Mr6<1Kn15CGvG@C4rmessage_id); + ASSERT_EQ(0, msg->repeat_indicator); + ASSERT_EQ(366904880, msg->mmsi); + ASSERT_EQ(0, msg->spare); + ASSERT_EQ(367, msg->dac); + ASSERT_EQ(24, msg->fi); + + ASSERT_EQ(0, msg->version); + ASSERT_EQ(17, msg->utc_hour); + ASSERT_EQ(20, msg->utc_min); + + ASSERT_EQ(1019, msg->pressure); +} + } // namespace } // namespace libais diff --git a/test/test_data.py b/test/test_data.py index b229e1ac..47d7e1d0 100644 --- a/test/test_data.py +++ b/test/test_data.py @@ -673,27 +673,6 @@ 'y': -2.5533333333333332} }, - { - 'nmea': ['!SAVDM,1,1,7,B,85Mr6<1KmhB:VgtfPV9lIf4M31PR000P,0*71'], - 'result': {'id': 8, - 'repeat_indicator': 0, - 'mmsi': 366904880, - 'spare': 0, - 'dac': 367, - 'fi': 23, - 'version': 0, - 'utc_day': 4, - 'utc_hour': 17, - 'utc_min': 20, - 'x': -87.4372, - 'y': 41.6737, - 'pressure': 1020, - 'air_temp': 28.5, - 'wind_speed': 6, - 'wind_gust': 6, - 'wind_dir': 17} - }, - { 'nmea': ['!SAVDM,1,1,3,A,85Mr6<1Kn15CGvG@C4rk1oFAKpB95?AruFRl7mre0 Date: Mon, 13 Sep 2021 16:26:50 -0700 Subject: [PATCH 3/3] fix NA processing for 367_24 Set pressure to Py_None if not a real pressure value. --- src/libais/ais_py.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libais/ais_py.cpp b/src/libais/ais_py.cpp index 54330ea3..f6f08cd0 100644 --- a/src/libais/ais_py.cpp +++ b/src/libais/ais_py.cpp @@ -2004,7 +2004,12 @@ ais8_367_24_append_pydict(const char *nmea_payload, PyObject *dict, DictSafeSetItem(dict, "utc_hour", msg.utc_hour); DictSafeSetItem(dict, "utc_min", msg.utc_min); DictSafeSetItem(dict, "x", "y", msg.position); - DictSafeSetItem(dict, "pressure", msg.pressure); + if (msg.pressure <= 1201) { + DictSafeSetItem(dict, "pressure", msg.pressure); + } else { + // Raw values was 403 (N/A), or reserved value. + DictSafeSetItem(dict, "pressure", Py_None); + } return AIS_OK; }