2017-10-05 00:38:45 -04:00
/*
* Copyright ( C ) 2015 Jared Boone , ShareBrained Technology , Inc .
* Copyright ( C ) 2017 Furrtek
2023-05-02 23:37:31 -04:00
* Early 2023 joyel24 added meteomodem M20 support
2017-10-05 00:38:45 -04:00
*
* This file is part of PortaPack .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 , or ( at your option )
* any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; see the file COPYING . If not , write to
* the Free Software Foundation , Inc . , 51 Franklin Street ,
* Boston , MA 02110 - 1301 , USA .
*/
# include "sonde_packet.hpp"
2017-10-28 13:16:06 -04:00
# include "string_format.hpp"
2020-08-14 14:51:12 -04:00
# include <cstring>
2023-05-18 16:16:05 -04:00
// #include <complex>
2017-10-05 00:38:45 -04:00
namespace sonde {
2023-05-18 16:16:05 -04:00
static uint8_t calibytes [ 51 * 16 ] ; // need these vars to survive
static uint8_t calfrchk [ 51 ] ; // so subframes are preserved while populated
2021-01-27 09:09:02 -05:00
2023-05-18 16:16:05 -04:00
// Defines for Vaisala RS41, from https://github.com/rs1729/RS/blob/master/rs41/rs41sg.c
2020-08-14 14:51:12 -04:00
# define MASK_LEN 64
2020-08-24 16:31:27 -04:00
2023-05-18 16:16:05 -04:00
// Following values include the 4 bytes less shift, consumed in detecting the header on proc_sonde
# define block_status 0x35 // 0x039 // 40 bytes
# define block_gpspos 0x10E // 0x112 // 21 bytes
# define block_meas 0x61 // 0x65 // 42 bytes
# 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_temp 0x063 // 0x067 // 3 bytes (uint24_t)
# 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)
2020-08-14 14:51:12 -04:00
2017-11-09 21:20:44 -05:00
Packet : : Packet (
2023-05-18 16:16:05 -04:00
const baseband : : Packet & packet ,
const Type type )
: packet_ { packet } ,
decoder_ { packet_ } ,
reader_bi_m { decoder_ } ,
type_ { type } {
if ( type_ = = Type : : Meteomodem_unknown ) {
// Right now we're just sure that the sync is from a Meteomodem sonde, differentiate between models now
const uint32_t id_byte = reader_bi_m . read ( 0 * 8 , 16 ) ;
if ( id_byte = = 0x649F )
type_ = Type : : Meteomodem_M10 ;
else if ( id_byte = = 0x648F )
type_ = Type : : Meteomodem_M2K2 ;
else if ( id_byte = = 0x4520 ) // https://raw.githubusercontent.com/projecthorus/radiosonde_auto_rx/master/demod/mod/m20mod.c
type_ = Type : : Meteomodem_M20 ;
}
2017-11-09 21:20:44 -05:00
}
2017-10-05 00:38:45 -04:00
size_t Packet : : length ( ) const {
2023-05-18 16:16:05 -04:00
return decoder_ . symbols_count ( ) ;
2017-10-05 00:38:45 -04:00
}
Timestamp Packet : : received_at ( ) const {
2023-05-18 16:16:05 -04:00
return packet_ . timestamp ( ) ;
2017-10-05 00:38:45 -04:00
}
Packet : : Type Packet : : type ( ) const {
2023-05-18 16:16:05 -04:00
return type_ ;
2017-10-05 00:38:45 -04:00
}
2023-05-18 16:16:05 -04:00
// euquiq here:
// 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)
// from 0x008 to 0x037 (48 bytes reed-solomon error correction data)
uint8_t Packet : : vaisala_descramble ( const uint32_t pos ) const { // vaisala_descramble(const uint32_t pos) const {
// packet_[i]; its a bit; packet_.size the total (should be 2560 bits)
uint8_t value = 0 ;
for ( uint8_t i = 0 ; i < 8 ; i + + )
value = ( value < < 1 ) | packet_ [ ( pos * 8 ) + ( 7 - i ) ] ; // get the byte from the bits collection
// packetReader reader { packet_ }; //This works just as above.
// value = reader.read(pos * 8,8);
// shift pos because first 4 bytes are consumed by proc_sonde in finding the vaisala signature
uint32_t mask_pos = pos + 4 ;
value = value ^ vaisala_mask [ mask_pos % MASK_LEN ] ; // descramble with the xor pseudorandom table
return value ;
2020-08-14 14:51:12 -04:00
} ;
2023-05-18 16:16:05 -04:00
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 : : Meteomodem_M20 ) {
result . alt = reader_bi_m . read ( 8 * 8 , 24 ) / 100.0 ; // <|
result . lat = reader_bi_m . read ( 28 * 8 , 32 ) / 1000000.0 ; // <| Inspired by https://raw.githubusercontent.com/projecthorus/radiosonde_auto_rx/master/demod/mod/m20mod.c
result . lon = reader_bi_m . read ( 32 * 8 , 32 ) / 1000000.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 ;
2017-10-27 12:54:50 -04:00
}
2023-05-18 16:16:05 -04:00
uint32_t Packet : : battery_voltage ( ) const {
if ( type_ = = Type : : Meteomodem_M10 )
return ( reader_bi_m . read ( 69 * 8 , 8 ) + ( reader_bi_m . read ( 70 * 8 , 8 ) < < 8 ) ) * 1000 / 150 ;
else if ( type_ = = Type : : Meteomodem_M20 ) {
return 0 ; // NOT SUPPPORTED YET
} else if ( type_ = = Type : : Meteomodem_M2K2 )
return reader_bi_m . read ( 69 * 8 , 8 ) * 66 ; // Actually 65.8
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
}
2017-10-28 13:16:06 -04:00
}
2023-05-18 16:16:05 -04:00
uint32_t Packet : : frame ( ) const {
if ( type_ = = Type : : Vaisala_RS41_SG ) {
uint32_t frame_number = vaisala_descramble ( pos_FrameNb ) | ( vaisala_descramble ( pos_FrameNb + 1 ) < < 8 ) ;
return frame_number ;
} else {
return 0 ; // Unknown
}
2017-10-27 12:54:50 -04:00
}
2023-05-18 16:16:05 -04:00
temp_humid Packet : : get_temp_humid ( ) const {
temp_humid result ;
result . humid = 0 ;
result . temp = 0 ;
if ( type_ = = Type : : Vaisala_RS41_SG & & crc_ok_RS41 ( ) ) // Only process if packet is healthy
{
// memset(calfrchk, 0, 51); // is this necessary ? only if the sondeID changes (new sonde)
// original code from https://github.com/rs1729/RS/blob/master/rs41/rs41ptu.c
float Rf1 , // ref-resistor f1 (750 Ohm)
Rf2 , // ref-resistor f2 (1100 Ohm)
co1 [ 3 ] , // { -243.911 , 0.187654 , 8.2e-06 }
calT1 [ 3 ] , // calibration T1
co2 [ 3 ] , // { -243.911 , 0.187654 , 8.2e-06 }
calT2 [ 3 ] , // calibration T2-Hum
calH [ 2 ] ; // calibration Hum
uint32_t meas [ 12 ] , i ;
//-------------- get_CalData
//-------------- populate calibytes (from getFrameConf)
uint8_t calfr = vaisala_descramble ( pos_CalData ) ; // get subframe #slot
for ( i = 0 ; i < 16 ; i + + ) // Load subrfame calibration page (16 bytes) into #slot
calibytes [ calfr * 16 + i ] = vaisala_descramble ( pos_CalData + 1 + i ) ; // pos = pos_CalData + 1 + i ; vaisala_descramble(pos)
calfrchk [ calfr ] = 1 ; // flag this #slot as populated
memcpy ( & Rf1 , calibytes + 61 , 4 ) ; // 0x03*0x10+13
memcpy ( & Rf2 , calibytes + 65 , 4 ) ; // 0x04*0x10+ 1
memcpy ( co1 + 0 , calibytes + 77 , 4 ) ; // 0x04*0x10+13
memcpy ( co1 + 1 , calibytes + 81 , 4 ) ; // 0x05*0x10+ 1
memcpy ( co1 + 2 , calibytes + 85 , 4 ) ; // 0x05*0x10+ 5
memcpy ( calT1 + 0 , calibytes + 89 , 4 ) ; // 0x05*0x10+ 9
memcpy ( calT1 + 1 , calibytes + 93 , 4 ) ; // 0x05*0x10+13
memcpy ( calT1 + 2 , calibytes + 97 , 4 ) ; // 0x06*0x10+ 1
memcpy ( calH + 0 , calibytes + 117 , 4 ) ; // 0x07*0x10+ 5
memcpy ( calH + 1 , calibytes + 121 , 4 ) ; // 0x07*0x10+ 9
memcpy ( co2 + 0 , calibytes + 293 , 4 ) ; // 0x12*0x10+ 5
memcpy ( co2 + 1 , calibytes + 297 , 4 ) ; // 0x12*0x10+ 9
memcpy ( co2 + 2 , calibytes + 301 , 4 ) ; // 0x12*0x10+13
memcpy ( calT2 + 0 , calibytes + 305 , 4 ) ; // 0x13*0x10+ 1
memcpy ( calT2 + 1 , calibytes + 309 , 4 ) ; // 0x13*0x10+ 5
memcpy ( calT2 + 2 , calibytes + 313 , 4 ) ; // 0x13*0x10+ 9
//---------------------------------------
for ( i = 0 ; i < 12 ; i + + )
meas [ i ] = vaisala_descramble ( pos_temp + ( 3 * i ) ) |
( vaisala_descramble ( pos_temp + ( 3 * i ) + 1 ) < < 8 ) |
( vaisala_descramble ( pos_temp + ( 3 * i ) + 2 ) < < 16 ) ;
//----Check if necessary calibytes are already present for calculation
if ( calfrchk [ 0x03 ] & & calfrchk [ 0x04 ] & & calfrchk [ 0x04 ] & & calfrchk [ 0x05 ] & & calfrchk [ 0x05 ] & & calfrchk [ 0x06 ] ) // Calibites OK for Temperature
{
//----------get_Tc------------------------
float * p = co1 ;
float * c = calT1 ;
float g = ( float ) ( meas [ 2 ] - meas [ 1 ] ) / ( Rf2 - Rf1 ) , // gain
Rb = ( meas [ 1 ] * Rf2 - meas [ 2 ] * Rf1 ) / ( float ) ( meas [ 2 ] - meas [ 1 ] ) , // ofs
Rc = meas [ 0 ] / g - Rb ,
R = Rc * c [ 0 ] ,
T = ( p [ 0 ] + p [ 1 ] * R + p [ 2 ] * R * R + c [ 1 ] ) * ( 1.0 + c [ 2 ] ) ;
result . temp = T ;
}
if ( calfrchk [ 0x07 ] ) {
//----------get_RH------------------------
float a0 = 7.5 ; // empirical
float a1 = 350.0 / calH [ 0 ] ; // empirical
float fh = ( meas [ 3 ] - meas [ 4 ] ) / ( float ) ( meas [ 5 ] - meas [ 4 ] ) ;
float rh = 100.0 * ( a1 * fh - a0 ) ;
float T0 = 0.0 , T1 = - 25.0 ; // T/C
rh + = T0 - result . temp / 5.5 ; // empir. temperature compensation
if ( result . temp < T1 )
rh * = 1.0 + ( T1 - result . temp ) / 90.0 ; // empir. temperature compensation
if ( rh < 0.0 )
rh = 0.0 ;
if ( rh > 100.0 )
rh = 100.0 ;
if ( result . temp < - 273.0 )
rh = - 1.0 ;
result . humid = rh ;
}
}
return result ;
2020-08-24 16:31:27 -04:00
}
2023-05-18 16:16:05 -04:00
std : : string Packet : : type_string ( ) const {
switch ( type_ ) {
case Type : : Unknown :
return " Unknown " ;
case Type : : Meteomodem_unknown :
return " Meteomodem ??? " ;
case Type : : Meteomodem_M10 :
return " Meteomodem M10 " ;
case Type : : Meteomodem_M20 :
return " Meteomodem M20 " ;
case Type : : Meteomodem_M2K2 :
return " Meteomodem M2K2 " ;
case Type : : Vaisala_RS41_SG :
return " Vaisala RS41-SG " ;
default :
return " ? 0x " + symbols_formatted ( ) . data . substr ( 0 , 6 ) ;
}
2020-08-24 16:31:27 -04:00
}
2023-05-18 16:16:05 -04:00
std : : string Packet : : serial_number ( ) const {
if ( type_ = = Type : : Meteomodem_M10 ) {
// See https://github.com/rs1729/RS/blob/master/m10/m10x.c line 606
// Starting at byte #93: 00000000 11111111 22222222 33333333 44444444
// CCCC AAAABBBB
// 44444444 33333333
// DDDEEEEE EEEEEEEE
return to_string_hex ( reader_bi_m . read ( 93 * 8 + 16 , 4 ) , 1 ) +
to_string_dec_uint ( reader_bi_m . read ( 93 * 8 + 20 , 4 ) , 2 , ' 0 ' ) + " " +
to_string_hex ( reader_bi_m . read ( 93 * 8 + 4 , 4 ) , 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 ' ) ;
} 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 {
return " ? " ;
}
2017-10-05 00:38:45 -04:00
}
2023-05-18 16:16:05 -04:00
FormattedSymbols Packet : : symbols_formatted ( ) const {
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_ ) ;
}
2017-10-05 00:38:45 -04:00
}
2023-05-18 16:16:05 -04:00
bool Packet : : crc_ok ( ) const {
switch ( type_ ) {
case Type : : Meteomodem_M10 :
return crc_ok_M10 ( ) ;
case Type : : Vaisala_RS41_SG :
return crc_ok_RS41 ( ) ;
default :
return true ; // euquiq: it was false, but if no crc routine, then no way to check
}
2020-08-24 16:31:27 -04:00
}
2023-05-18 16:16:05 -04:00
// each data block has a 2 byte header, data, and 2 byte tail:
// 1st byte: block ID
// 2nd byte: data length (without header or tail)
// <data>
// 2 bytes CRC16 over the data.
bool Packet : : crc_ok_RS41 ( ) const // check CRC for the data blocks we need
2020-08-24 16:31:27 -04:00
{
2023-05-18 16:16:05 -04:00
if ( ! crc16rs41 ( block_status ) )
return false ;
if ( ! crc16rs41 ( block_gpspos ) )
return false ;
2020-08-24 16:31:27 -04:00
2023-05-18 16:16:05 -04:00
if ( ! crc16rs41 ( block_meas ) )
return false ;
2020-08-24 16:31:27 -04:00
2023-05-18 16:16:05 -04:00
return true ;
2020-08-24 16:31:27 -04:00
}
2023-05-18 16:16:05 -04:00
// Checks CRC16 on a RS41 field:
bool Packet : : crc16rs41 ( uint32_t field_start ) const {
int crc16poly = 0x1021 ;
int rem = 0xFFFF , b , j ;
int xbyte ;
uint32_t pos = field_start + 1 ;
uint8_t length = vaisala_descramble ( pos ) ;
if ( pos + length + 2 > packet_ . size ( ) / 8 )
return false ; // Out of packet!
for ( b = 0 ; b < length ; b + + ) {
pos + + ;
xbyte = vaisala_descramble ( pos ) ;
rem = rem ^ ( xbyte < < 8 ) ;
for ( j = 0 ; j < 8 ; j + + ) {
if ( rem & 0x8000 ) {
rem = ( rem < < 1 ) ^ crc16poly ;
} else {
rem = ( rem < < 1 ) ;
}
rem & = 0xFFFF ;
}
}
// Check calculated CRC against packet's one
pos + + ;
int crcok = vaisala_descramble ( pos ) | ( vaisala_descramble ( pos + 1 ) < < 8 ) ;
if ( crcok ! = rem )
return false ;
return true ;
2017-10-05 00:38:45 -04:00
}
2023-05-18 16:16:05 -04:00
bool Packet : : crc_ok_M10 ( ) const {
uint16_t cs { 0 } ;
uint32_t c0 , c1 , t , t6 , t7 , s , b ;
for ( size_t i = 0 ; i < packet_ . size ( ) ; i + + ) {
b = packet_ [ i ] ;
c1 = cs & 0xFF ;
2020-08-24 16:31:27 -04:00
2023-05-18 16:16:05 -04:00
// B
b = ( b > > 1 ) | ( ( b & 1 ) < < 7 ) ;
b ^ = ( b > > 2 ) & 0xFF ;
2017-10-05 00:38:45 -04:00
2023-05-18 16:16:05 -04:00
// A1
t6 = ( cs & 1 ) ^ ( ( cs > > 2 ) & 1 ) ^ ( ( cs > > 4 ) & 1 ) ;
t7 = ( ( cs > > 1 ) & 1 ) ^ ( ( cs > > 3 ) & 1 ) ^ ( ( cs > > 5 ) & 1 ) ;
t = ( cs & 0x3F ) | ( t6 < < 6 ) | ( t7 < < 7 ) ;
2017-10-05 00:38:45 -04:00
2023-05-18 16:16:05 -04:00
// A2
s = ( cs > > 7 ) & 0xFF ;
s ^ = ( s > > 2 ) & 0xFF ;
2017-10-05 00:38:45 -04:00
2023-05-18 16:16:05 -04:00
c0 = b ^ t ^ s ;
2017-10-05 00:38:45 -04:00
2023-05-18 16:16:05 -04:00
cs = ( ( c1 < < 8 ) | c0 ) & 0xFFFF ;
}
2017-10-05 00:38:45 -04:00
2023-05-18 16:16:05 -04:00
return ( ( cs & 0xFFFF ) = = ( ( packet_ [ 0x63 ] < < 8 ) | ( packet_ [ 0x63 + 1 ] ) ) ) ;
2017-10-05 00:38:45 -04:00
}
} /* namespace sonde */