Radiosonde-app-Vaisala-rs41-decoding

Added the Vaisala RS41 data packet decoding.

Changed the default freq from 402.0 to 402.7 Mhz, since it is more popular freq.

Lowered the frequency stepping, so it is easier to fine-tune the exact freq center, if needed.

Sonde's Serial ID is passed into the VIEW MAP, so now the sonde is labelled on the map.
This commit is contained in:
euquiq 2020-08-14 15:51:12 -03:00
parent 6eabd9f1bc
commit c7b0fbc359
5 changed files with 132 additions and 56 deletions

View File

@ -54,7 +54,7 @@ SondeView::SondeView(NavigationView& nav) {
}); });
field_frequency.set_value(target_frequency_); field_frequency.set_value(target_frequency_);
field_frequency.set_step(10000); field_frequency.set_step(500); //euquiq: was 10000, but we are using this for fine-tunning
field_frequency.on_change = [this](rf::Frequency f) { field_frequency.on_change = [this](rf::Frequency f) {
set_target_frequency(f); set_target_frequency(f);
field_frequency.set_value(f); field_frequency.set_value(f);
@ -86,12 +86,12 @@ SondeView::SondeView(NavigationView& nav) {
button_see_map.on_select = [this, &nav](Button&) { button_see_map.on_select = [this, &nav](Button&) {
nav.push<GeoMapView>( nav.push<GeoMapView>(
"", sonde_id,
altitude, gps_info.alt,
GeoPos::alt_unit::METERS, GeoPos::alt_unit::METERS,
latitude, gps_info.lat,
longitude, gps_info.lon,
0); 999); //set a dummy heading out of range to draw a cross...probably not ideal?
}; };
logger = std::make_unique<SondeLogger>(); logger = std::make_unique<SondeLogger>();
@ -113,16 +113,15 @@ void SondeView::on_packet(const sonde::Packet& packet) {
//const auto hex_formatted = packet.symbols_formatted(); //const auto hex_formatted = packet.symbols_formatted();
text_signature.set(packet.type_string()); text_signature.set(packet.type_string());
text_serial.set(packet.serial_number()); sonde_id = packet.serial_number(); //used also as tag on the geomap
text_serial.set(sonde_id);
text_voltage.set(unit_auto_scale(packet.battery_voltage(), 2, 3) + "V"); text_voltage.set(unit_auto_scale(packet.battery_voltage(), 2, 3) + "V");
altitude = packet.GPS_altitude(); gps_info = packet.get_GPS_data();
latitude = packet.GPS_latitude();
longitude = packet.GPS_longitude();
geopos.set_altitude(altitude); geopos.set_altitude(gps_info.alt);
geopos.set_lat(latitude); geopos.set_lat(gps_info.lat);
geopos.set_lon(longitude); geopos.set_lon(gps_info.lon);
if (logger && logging) { if (logger && logging) {
logger->on_packet(packet); logger->on_packet(packet);

View File

@ -65,11 +65,10 @@ public:
private: private:
std::unique_ptr<SondeLogger> logger { }; std::unique_ptr<SondeLogger> logger { };
uint32_t target_frequency_ { 402000000 }; uint32_t target_frequency_ { 402700000 };
bool logging { false }; bool logging { false };
int32_t altitude { 0 }; sonde::GPS_data gps_info;
float latitude { 0 }; std::string sonde_id;
float longitude { 0 };
Labels labels { Labels labels {
{ { 0 * 8, 2 * 16 }, "Signature:", Color::light_grey() }, { { 0 * 8, 2 * 16 }, "Signature:", Color::light_grey() },

View File

@ -140,7 +140,8 @@ private:
} }
}; };
PacketBuilder<BitPattern, NeverMatch, FixedLength> packet_builder_fsk_4800_Vaisala { PacketBuilder<BitPattern, NeverMatch, FixedLength> packet_builder_fsk_4800_Vaisala {
{ 0b00001000011011010101001110001000, 32, 1 }, { 0b00001000011011010101001110001000, 32, 1 }, //euquiq Header detects 4 of 8 bytes 0x10B6CA11 /this is in raw format) (these bits are not passed at the beginning of packet)
//{ 0b0000100001101101010100111000100001000100011010010100100000011111, 64, 1 }, //euquiq whole header detection would be 8 bytes.
{ }, { },
{ 320 * 8 }, { 320 * 8 },
[this](const baseband::Packet& packet) { [this](const baseband::Packet& packet) {

View File

@ -22,9 +22,25 @@
#include "sonde_packet.hpp" #include "sonde_packet.hpp"
#include "string_format.hpp" #include "string_format.hpp"
#include <cstring>
//#include <complex>
namespace sonde { namespace sonde {
//Defines for Vaisala RS41, from https://github.com/rs1729/RS/blob/master/rs41/rs41sg.c
#define MASK_LEN 64
#define pos_FrameNb 0x37 //0x03B // 2 byte
#define pos_SondeID 0x39 //0x03D // 8 byte
#define pos_Voltage 0x041 //0x045 // 3 bytes (but first one is the important one) voltage x 10 ie: 26 = 2.6v
#define pos_CalData 0x04E //0x052 // 1 byte, counter 0x00..0x32
#define pos_GPSweek 0x091 //0x095 // 2 byte
#define pos_GPSTOW 0x093 //0x097 // 4 byte
#define pos_GPSecefX 0x110 //0x114 // 4 byte
#define pos_GPSecefY 0x114 //0x118 // 4 byte (not actually used since Y and Z are following X, and grabbed in that same loop)
#define pos_GPSecefZ 0x118 //0x11C // 4 byte (same as Y)
#define PI 3.1415926535897932384626433832795 //3.1416 //(3.1415926535897932384626433832795)
Packet::Packet( Packet::Packet(
const baseband::Packet& packet, const baseband::Packet& packet,
const Type type const Type type
@ -60,37 +76,65 @@ Packet::Type Packet::type() const {
return type_; return type_;
} }
/*uint8_t Packet::vaisala_descramble(const uint32_t pos) { //euquiq here:
return reader_raw.read(pos * 8, 8) ^ vaisala_mask[pos & 63]; //RS41SG 320 bits header, 320bytes frame (or more if it is an "extended frame")
};*/ //The raw data is xor-scrambled with the values in the 64 bytes vaisala_mask (see.hpp)
uint32_t Packet::GPS_altitude() const {
if ((type_ == Type::Meteomodem_M10) || (type_ == Type::Meteomodem_M2K2))
return (reader_bi_m.read(22 * 8, 32) / 1000) - 48;
else if (type_ == Type::Vaisala_RS41_SG) {
/*uint32_t altitude_ecef = 0;
for (uint32_t i = 0; i < 4; i++)
altitude_ecef = (altitude_ecef << 8) + vaisala_descramble(0x11C + i);*/
// TODO: and a bunch of maths (see ecef2elli() from RS1729)
return 0;
} else
return 0; // Unknown
}
float Packet::GPS_latitude() const { uint8_t Packet::vaisala_descramble(const uint32_t pos) const {
if ((type_ == Type::Meteomodem_M10) || (type_ == Type::Meteomodem_M2K2)) //return reader_raw.read(pos * 8, 8) ^ vaisala_mask[pos & 63];
return reader_bi_m.read(14 * 8, 32) / ((1ULL << 32) / 360.0); // packet_[i]; its a bit; packet_.size the total (should be 2560 bits)
//else if (type_ == Type::Vaisala_RS41_SG) uint8_t value = 0;
// return vaisala_descramble(); for (uint8_t i = 0; i < 8; i++)
else value = (value << 1) | packet_[(pos * 8) + (7 -i)]; //get the byte from the bits collection
return 0; // Unknown
}
float Packet::GPS_longitude() const { //packetReader reader { packet_ }; //This works just as above.
if ((type_ == Type::Meteomodem_M10) || (type_ == Type::Meteomodem_M2K2)) //value = reader.read(pos * 8,8);
return reader_bi_m.read(18 * 8, 32) / ((1ULL << 32) / 360.0); //shift pos because first 4 bytes are consumed by proc_sonde in finding the vaisala signature
else uint32_t mask_pos = pos + 4;
return 0; // Unknown value = value ^ vaisala_mask[mask_pos % MASK_LEN]; //descramble with the xor pseudorandom table
return value;
};
GPS_data Packet::get_GPS_data() const {
GPS_data result;
if ((type_ == Type::Meteomodem_M10) || (type_ == Type::Meteomodem_M2K2)) {
result.alt = (reader_bi_m.read(22 * 8, 32) / 1000) - 48;
result.lat = reader_bi_m.read(14 * 8, 32) / ((1ULL << 32) / 360.0);
result.lon = reader_bi_m.read(18 * 8, 32) / ((1ULL << 32) / 360.0);
} else if (type_ == Type::Vaisala_RS41_SG) {
uint8_t XYZ_bytes[4];
int32_t XYZ; // 32bit
double_t X[3];
for (int32_t k = 0; k < 3; k++) { //Get X,Y,Z ECEF position from GPS
for (int32_t i = 0; i < 4; i++) //each one is 4 bytes (32 bits)
XYZ_bytes[i] = vaisala_descramble(pos_GPSecefX + (4*k) + i);
memcpy(&XYZ, XYZ_bytes, 4);
X[k] = XYZ / 100.0;
}
double_t a = 6378137.0;
double_t b = 6356752.31424518;
double_t e = sqrt( (a*a - b*b) / (a*a) );
double_t ee = sqrt( (a*a - b*b) / (b*b) );
double_t lam = atan2( X[1] , X[0] );
double_t p = sqrt( X[0]*X[0] + X[1]*X[1] );
double_t t = atan2( X[2]*a , p*b );
double_t phi = atan2( X[2] + ee*ee * b * sin(t)*sin(t)*sin(t) ,
p - e*e * a * cos(t)*cos(t)*cos(t) );
double_t R = a / sqrt( 1 - e*e*sin(phi)*sin(phi) );
result.alt = p / cos(phi) - R;
result.lat = phi*180/PI;
result.lon = lam*180/PI;
}
return result;
} }
uint32_t Packet::battery_voltage() const { uint32_t Packet::battery_voltage() const {
@ -98,8 +142,13 @@ uint32_t Packet::battery_voltage() const {
return (reader_bi_m.read(69 * 8, 8) + (reader_bi_m.read(70 * 8, 8) << 8)) * 1000 / 150; return (reader_bi_m.read(69 * 8, 8) + (reader_bi_m.read(70 * 8, 8) << 8)) * 1000 / 150;
else if (type_ == Type::Meteomodem_M2K2) else if (type_ == Type::Meteomodem_M2K2)
return reader_bi_m.read(69 * 8, 8) * 66; // Actually 65.8 return reader_bi_m.read(69 * 8, 8) * 66; // Actually 65.8
else else if (type_ == Type::Vaisala_RS41_SG) {
uint32_t voltage = vaisala_descramble(pos_Voltage) * 100; //byte 69 = voltage * 10 (check if this value needs to be multiplied)
return voltage;
}
else {
return 0; // Unknown return 0; // Unknown
}
} }
std::string Packet::type_string() const { std::string Packet::type_string() const {
@ -127,12 +176,33 @@ std::string Packet::serial_number() const {
to_string_dec_uint(reader_bi_m.read(93 * 8 + 24, 3), 1) + to_string_dec_uint(reader_bi_m.read(93 * 8 + 24, 3), 1) +
to_string_dec_uint(reader_bi_m.read(93 * 8 + 27, 13), 4, '0'); to_string_dec_uint(reader_bi_m.read(93 * 8 + 27, 13), 4, '0');
} else if(type() == Type::Vaisala_RS41_SG) {
std::string serial_id = "";
uint8_t achar;
for (uint8_t i=0; i<8; i++) { //euquiq: Serial ID is 8 bytes long, each byte a char
achar = vaisala_descramble(pos_SondeID + i);
if (achar < 32 || achar > 126) return "?"; //Maybe there are ids with less than 8 bytes and this is not OK.
serial_id += (char)achar;
}
return serial_id;
} else } else
return "?"; return "?";
} }
FormattedSymbols Packet::symbols_formatted() const { FormattedSymbols Packet::symbols_formatted() const {
return format_symbols(decoder_); if (type() == Type::Vaisala_RS41_SG) { //Euquiq: now we distinguish different types
uint32_t bytes = packet_.size() / 8; //Need the byte amount, which if full, it SHOULD be 320 size() should return 2560
std::string hex_data;
std::string hex_error;
hex_data.reserve(bytes * 2); //2 hexa chars per byte
hex_error.reserve(1);
for (uint32_t i=0; i < bytes; i++) //log will show the packet starting on the last 4 bytes from signature 93DF1A60
hex_data += to_string_hex(vaisala_descramble(i),2);
return { hex_data, hex_error };
} else {
return format_symbols(decoder_);
}
} }
bool Packet::crc_ok() const { bool Packet::crc_ok() const {

View File

@ -32,6 +32,12 @@
namespace sonde { namespace sonde {
struct GPS_data {
uint32_t alt { 0 };
float lat { 0 };
float lon { 0 };
};
class Packet { class Packet {
public: public:
enum class Type : uint32_t { enum class Type : uint32_t {
@ -56,9 +62,7 @@ public:
std::string serial_number() const; std::string serial_number() const;
uint32_t battery_voltage() const; uint32_t battery_voltage() const;
uint32_t GPS_altitude() const; GPS_data get_GPS_data() const;
float GPS_latitude() const;
float GPS_longitude() const;
FormattedSymbols symbols_formatted() const; FormattedSymbols symbols_formatted() const;
@ -76,13 +80,16 @@ private:
0x78, 0x6E, 0x3B, 0xAE, 0xBF, 0x7B, 0x4C, 0xC1 0x78, 0x6E, 0x3B, 0xAE, 0xBF, 0x7B, 0x4C, 0xC1
}; };
//uint8_t vaisala_descramble(const uint32_t pos); GPS_data ecef_to_gps() const;
uint8_t vaisala_descramble(uint32_t pos) const;
const baseband::Packet packet_; const baseband::Packet packet_;
const BiphaseMDecoder decoder_; const BiphaseMDecoder decoder_;
const FieldReader<BiphaseMDecoder, BitRemapNone> reader_bi_m; const FieldReader<BiphaseMDecoder, BitRemapNone> reader_bi_m;
Type type_; Type type_;
using packetReader = FieldReader<baseband::Packet, BitRemapByteReverse>; //baseband::Packet instead of BiphaseMDecoder
bool crc_ok_M10() const; bool crc_ok_M10() const;
}; };