2017-10-05 05:38:45 +01:00
/*
* Copyright ( C ) 2015 Jared Boone , ShareBrained Technology , Inc .
* Copyright ( C ) 2017 Furrtek
2023-05-03 05:37:31 +02:00
* Early 2023 joyel24 added meteomodem M20 support
2017-10-05 05:38:45 +01: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 19:16:06 +02:00
# include "string_format.hpp"
2020-08-14 15:51:12 -03:00
# include <cstring>
//#include <complex>
2017-10-05 05:38:45 +01:00
namespace sonde {
2021-01-27 15:09:02 +01:00
static uint8_t calibytes [ 51 * 16 ] ; //need these vars to survive
static uint8_t calfrchk [ 51 ] ; //so subframes are preserved while populated
2020-08-14 15:51:12 -03:00
//Defines for Vaisala RS41, from https://github.com/rs1729/RS/blob/master/rs41/rs41sg.c
# define MASK_LEN 64
2020-08-24 17:31:27 -03: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
2020-08-14 15:51:12 -03:00
# 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
2020-08-24 17:31:27 -03:00
# define pos_temp 0x063 //0x067 // 3 bytes (uint24_t)
2020-08-14 15:51:12 -03:00
# 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)
2017-11-10 02:20:44 +00:00
Packet : : Packet (
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
2017-12-08 18:58:46 +00:00
const uint32_t id_byte = reader_bi_m . read ( 0 * 8 , 16 ) ;
2017-11-10 02:20:44 +00:00
if ( id_byte = = 0x649F )
type_ = Type : : Meteomodem_M10 ;
else if ( id_byte = = 0x648F )
type_ = Type : : Meteomodem_M2K2 ;
2023-04-29 14:00:28 +02:00
else if ( id_byte = = 0x4520 ) //https://raw.githubusercontent.com/projecthorus/radiosonde_auto_rx/master/demod/mod/m20mod.c
type_ = Type : : Meteomodem_M20 ;
2017-11-10 02:20:44 +00:00
}
}
2017-10-05 05:38:45 +01:00
size_t Packet : : length ( ) const {
return decoder_ . symbols_count ( ) ;
}
Timestamp Packet : : received_at ( ) const {
return packet_ . timestamp ( ) ;
}
Packet : : Type Packet : : type ( ) const {
return type_ ;
}
2020-08-14 15:51:12 -03: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)
2020-08-24 17:31:27 -03:00
//from 0x008 to 0x037 (48 bytes reed-solomon error correction data)
2020-08-14 15:51:12 -03:00
2020-08-24 17:31:27 -03:00
uint8_t Packet : : vaisala_descramble ( const uint32_t pos ) const
{ //vaisala_descramble(const uint32_t pos) const {
2020-08-14 15:51:12 -03:00
// packet_[i]; its a bit; packet_.size the total (should be 2560 bits)
uint8_t value = 0 ;
2020-08-24 17:31:27 -03:00
for ( uint8_t i = 0 ; i < 8 ; i + + )
value = ( value < < 1 ) | packet_ [ ( pos * 8 ) + ( 7 - i ) ] ; //get the byte from the bits collection
2020-08-14 15:51:12 -03:00
//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 ;
2020-08-24 17:31:27 -03:00
value = value ^ vaisala_mask [ mask_pos % MASK_LEN ] ; //descramble with the xor pseudorandom table
2020-08-14 15:51:12 -03:00
return value ;
} ;
2020-08-24 17:31:27 -03:00
GPS_data Packet : : get_GPS_data ( ) const
{
2020-08-14 15:51:12 -03:00
GPS_data result ;
2020-08-24 17:31:27 -03:00
if ( ( type_ = = Type : : Meteomodem_M10 ) | | ( type_ = = Type : : Meteomodem_M2K2 ) )
{
2020-08-14 15:51:12 -03:00
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 ) ;
2020-08-24 17:31:27 -03:00
}
2023-04-29 14:00:28 +02:00
else if ( type_ = = Type : : Meteomodem_M20 )
{
2023-05-03 05:37:31 +02:00
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 ; // <|
2023-04-29 14:00:28 +02:00
}
2020-08-24 17:31:27 -03:00
else if ( type_ = = Type : : Vaisala_RS41_SG )
{
2020-08-14 15:51:12 -03:00
uint8_t XYZ_bytes [ 4 ] ;
int32_t XYZ ; // 32bit
double_t X [ 3 ] ;
2020-08-24 17:31:27 -03:00
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 ) ;
2020-08-14 15:51:12 -03:00
memcpy ( & XYZ , XYZ_bytes , 4 ) ;
X [ k ] = XYZ / 100.0 ;
}
double_t a = 6378137.0 ;
double_t b = 6356752.31424518 ;
2020-08-24 17:31:27 -03:00
double_t e = sqrt ( ( a * a - b * b ) / ( a * a ) ) ;
double_t ee = sqrt ( ( a * a - b * b ) / ( b * b ) ) ;
2020-08-14 15:51:12 -03:00
2020-08-24 17:31:27 -03:00
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 ) ) ;
2020-08-14 15:51:12 -03:00
2020-08-24 17:31:27 -03:00
double_t R = a / sqrt ( 1 - e * e * sin ( phi ) * sin ( phi ) ) ;
2020-08-14 15:51:12 -03:00
result . alt = p / cos ( phi ) - R ;
2020-08-24 17:31:27 -03:00
result . lat = phi * 180 / PI ;
result . lon = lam * 180 / PI ;
2020-08-14 15:51:12 -03:00
}
return result ;
2017-10-27 18:54:50 +02:00
}
2020-08-24 17:31:27 -03:00
uint32_t Packet : : battery_voltage ( ) const
{
2017-11-10 02:20:44 +00:00
if ( type_ = = Type : : Meteomodem_M10 )
return ( reader_bi_m . read ( 69 * 8 , 8 ) + ( reader_bi_m . read ( 70 * 8 , 8 ) < < 8 ) ) * 1000 / 150 ;
2023-04-29 14:00:28 +02:00
else if ( type_ = = Type : : Meteomodem_M20 )
{
return 0 ; //NOT SUPPPORTED YET
}
2017-11-10 02:20:44 +00:00
else if ( type_ = = Type : : Meteomodem_M2K2 )
2020-08-24 17:31:27 -03:00
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)
2020-08-14 15:51:12 -03:00
return voltage ;
2020-08-24 17:31:27 -03:00
}
else
{
return 0 ; // Unknown
}
2017-10-28 19:16:06 +02:00
}
2020-08-24 17:31:27 -03: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-11-10 02:20:44 +00:00
}
2017-10-27 18:54:50 +02:00
}
2020-08-24 17:31:27 -03: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 ;
}
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 " ;
2023-04-29 14:00:28 +02:00
case Type : : Meteomodem_M20 :
return " Meteomodem M20 " ;
2020-08-24 17:31:27 -03:00
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 ) ;
}
}
std : : string Packet : : serial_number ( ) const
{
if ( type_ = = Type : : Meteomodem_M10 )
{
2017-10-05 05:38:45 +01:00
// See https://github.com/rs1729/RS/blob/master/m10/m10x.c line 606
2017-10-28 19:16:06 +02:00
// Starting at byte #93: 00000000 11111111 22222222 33333333 44444444
// CCCC AAAABBBB
// 44444444 33333333
// DDDEEEEE EEEEEEEE
2020-08-24 17:31:27 -03:00
2017-11-10 02:20:44 +00:00
return to_string_hex ( reader_bi_m . read ( 93 * 8 + 16 , 4 ) , 1 ) +
2020-08-24 17:31:27 -03:00
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 )
{
2020-08-14 15:51:12 -03:00
std : : string serial_id = " " ;
uint8_t achar ;
2020-08-24 17:31:27 -03:00
for ( uint8_t i = 0 ; i < 8 ; i + + )
{ //euquiq: Serial ID is 8 bytes long, each byte a char
2020-08-14 15:51:12 -03:00
achar = vaisala_descramble ( pos_SondeID + i ) ;
2020-08-24 17:31:27 -03:00
if ( achar < 32 | | achar > 126 )
return " ? " ; //Maybe there are ids with less than 8 bytes and this is not OK.
2020-08-14 15:51:12 -03:00
serial_id + = ( char ) achar ;
}
return serial_id ;
2020-08-24 17:31:27 -03:00
}
else
{
2017-12-08 18:58:46 +00:00
return " ? " ;
2020-08-24 17:31:27 -03:00
}
2017-10-05 05:38:45 +01:00
}
2020-08-24 17:31:27 -03: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
2020-08-14 15:51:12 -03:00
std : : string hex_data ;
std : : string hex_error ;
hex_data . reserve ( bytes * 2 ) ; //2 hexa chars per byte
2020-08-24 17:31:27 -03:00
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
{
2020-08-14 15:51:12 -03:00
return format_symbols ( decoder_ ) ;
}
2017-10-05 05:38:45 +01:00
}
2020-08-24 17:31:27 -03: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
}
}
//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
{
if ( ! crc16rs41 ( block_status ) )
return false ;
if ( ! crc16rs41 ( block_gpspos ) )
return false ;
if ( ! crc16rs41 ( block_meas ) )
return false ;
return true ;
}
//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 ;
}
2017-10-05 05:38:45 +01:00
}
2020-08-24 17:31:27 -03:00
//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 05:38:45 +01:00
}
2020-08-24 17:31:27 -03: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 + + )
{
2017-10-05 05:38:45 +01:00
b = packet_ [ i ] ;
c1 = cs & 0xFF ;
// B
b = ( b > > 1 ) | ( ( b & 1 ) < < 7 ) ;
b ^ = ( b > > 2 ) & 0xFF ;
// 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 ) ;
// A2
s = ( cs > > 7 ) & 0xFF ;
s ^ = ( s > > 2 ) & 0xFF ;
c0 = b ^ t ^ s ;
2020-08-24 17:31:27 -03:00
cs = ( ( c1 < < 8 ) | c0 ) & 0xFFFF ;
}
2017-10-05 05:38:45 +01:00
return ( ( cs & 0xFFFF ) = = ( ( packet_ [ 0x63 ] < < 8 ) | ( packet_ [ 0x63 + 1 ] ) ) ) ;
}
} /* namespace sonde */