diff --git a/RX_FSK/data/screens1.txt b/RX_FSK/data/screens1.txt index 0fe6736..a915e77 100644 --- a/RX_FSK/data/screens1.txt +++ b/RX_FSK/data/screens1.txt @@ -234,7 +234,9 @@ fonts=0,1 0,0=Is 0,9=f 1,12=t -2,0=xTelemetry Data +2,0=xSonde +3,0=xData +2,10=A 4,0=Mt°C 4,9=Mh%rH 6,0=MphPa diff --git a/RX_FSK/src/DFM.cpp b/RX_FSK/src/DFM.cpp index 613781a..49a0642 100644 --- a/RX_FSK/src/DFM.cpp +++ b/RX_FSK/src/DFM.cpp @@ -14,6 +14,18 @@ #define DFM_FRAMELEN 33 + +/* + * observed DAT patterns for DFM-9: + * A: 0+1; 2+3; 4+5; 6+7; 8+0 => keep frame in shadowFrame + * B: 1+2; 3+4; 5+6; 7+8 => all good => keep date in shadowDate + * C: 0+1; 2+3; 4+5; 6+7; 8+15 => all good => keep date in shadowDate + * D: 0+1; 2+3; 4+5; 6+7; 0+1 => use shadowDate + * not seen:5+6; 7+1 + * values: + * 0:packet counter; 1:utc-msec; 2:lat,vh; 3:lon,dir, 4:alt,vv, 8=utc-date(day-hh-mm) + */ + // single data structure, search restarts after decoder change static struct st_dfmstat { int idcnt0; @@ -24,10 +36,14 @@ static struct st_dfmstat { uint16_t dat[50*2]; uint8_t cnt[50*2]; uint16_t good; + uint32_t datesec; + uint8_t frame; uint16_t msec; uint8_t nameregok; uint8_t nameregtop; uint8_t lastdat; + uint8_t cycledone; // 0=no; 1=OK, 2=partially/with errors + float meas[5+2]; } dfmstate; int DFM::setup(float frequency, int type) @@ -160,7 +176,8 @@ int DFM::hamming(uint8_t *ham, int L, uint8_t *sym) { for (i = 0; i < L; i++) { // L bytes (4bit data, 4bit parity) if (use_ecc) { int res = check(ham+8*i); - if(ret>=0 && res>=0) ret += res; else ret=-1; + if( res<0 ) ret = -1; + else if ( ret >= 0 && res > 0 ) ret++; } // systematic Hamming code: copy bits 0..3 for (j = 0; j < 4; j++) { @@ -323,14 +340,67 @@ void DFM::finddfname(uint8_t *b) } } +static float get_Temp() { + SondeData *si = &(sonde.si()->d); + if(!si->validID) { // type not yet known, so don't try to decode + return NAN; + } + float f = dfmstate.meas[0], + f1 = dfmstate.meas[3], + f2 = dfmstate.meas[4]; + if(si->subtype >= 0x0C) { + f = dfmstate.meas[1]; + f1 = dfmstate.meas[5]; + f2 = dfmstate.meas[6]; + } + Serial.printf("Meas: %f %f %f\n", f, f1, f2); + // as in autorx / dfm + float BB0 = 3260.0; // B/Kelvin, fit -55C..+40C + float T0 = 25 + 273.15; // t0=25C + float R0 = 5.0e3; // R0=R25=5k + float Rf = 220e3; // Rf = 220k + float g = f2/Rf; + float R = (f-f1) / g; // meas[0,3,4] > 0 ? + float T = 0; // T/Kelvin + if (f*f1*f2 == 0) R = 0; + if (R > 0) T = 1/(1/T0 + 1/BB0 * log(R/R0)); + T = T - 273.15; // Celsius + if(T<-100 || T>50) { + Serial.printf("Temperature invalid: %f\n", T); + return NAN; + } + return T; +} + void DFM::decodeCFG(uint8_t *cfg) { + SondeData *si = &(sonde.si()->d); // new ID finddfname(cfg); + // get meas + uint8_t conf_id = (*cfg)>>4; + if(conf_id<=6) { + uint32_t val = (cfg[1]<<12) | (cfg[2]<<4) | cfg[3]; + uint8_t exp = cfg[0] & 0xF; + dfmstate.meas[conf_id] = val / (float)(1<validID && si->subtype>=0x0A) { + // otherwise don't try, as we might not have the right type yet... + int cid = (si->subtype >= 0x0C) ? 0x7 : 0x5; + if(conf_id == cid) { + uint16_t val = cfg[1]<<8 | cfg[2]; + si->batteryVoltage = val / 1000.0; + Serial.printf("battery: %f\n", si->batteryVoltage); + } + } // new aprs ID (dxlaprs, autorx) is now "D" + serial (8 digits) by consensus memcpy(sonde.si()->d.ser, sonde.si()->d.id+1, 9); } +#if 0 +// not used any more static int bitCount(int x) { int m4 = 0x1 | (0x1<<8) | (0x1<<16) | (0x1<<24); int m1 = 0xFF; @@ -338,6 +408,7 @@ static int bitCount(int x) { int s1 = (s4&m1) + ((s4>>8)&m1) + ((s4>>16)&m1) + ((s4>>24)&m1); return s1; } +#endif uint16_t MON[]={0,0,31,59,90,120,151,181,212,243,273,304,334}; @@ -346,24 +417,66 @@ void DFM::decodeDAT(uint8_t *dat) // TODO: Here we need to work on a shadow copy of SondeData in order to prevent concurrent changes while using data in main loop SondeData *si = &(sonde.si()->d); Serial.print(" DAT["); Serial.print(dat[6]); Serial.print("]: "); - // We can have a 8 and 0 subframe in a single frame. So do the reset only for dat>0 - if( !(dat[6]==0 && dfmstate.lastdat==8) ) { // if we have DAT8 + DAT0, don't reset before returing the 8 frame... - if(dat[6] < dfmstate.lastdat) dfmstate.good = 0; // next iteration detected - } - dfmstate.lastdat = dat[6]; + + // We handle this case already here, because we need to update dfmstate.datesec before the cycle complete handling + if( dat[6]==8 ) { + int y = (dat[0]<<4) + (dat[1]>>4); + int m = dat[1]&0x0F; + int d = dat[2]>>3; + int h = ((dat[2]&0x07)<<2) + (dat[3]>>6); + int mi = (dat[3]&0x3F); + Serial.printf("Date: %04d-%02d-%02d %02d:%02dz ", y, m, d, h, mi); + si->sats = dat[4]; + si->validPos |= 0x40; + // convert to unix time + int tt = (y-1970)*365 + (y-1969)/4; // days since 1970 + if(m<=12) { tt += MON[m]; if((y%4)==0 && m>2) tt++; } + tt = (tt+d-1)*(60*60*24) + h*3600 + mi*60; + dfmstate.datesec = tt; + dfmstate.good |= 0x100; + } + else if( dat[6]>8 ) return; // we ignore those... + + /* Here we update data that should be updated only after receiving a "complete" frame (mainly for consistent SondeHub exports) + * We do this (a) when there is a DAT8 block and (b) when there is wrap from DAT7 to DAT0, for these fields: + * => frame + * => vframe (only used for SondeHub as virtual frame number) + * => time (calculated with using date and msec) + * [assuming that if there is no DAT8, then the date value did not change.] + */ + + if( dat[6]==8 || dat[6] < dfmstate.lastdat) { // After DAT8, or after a "warp around" + if( dfmstate.good&1 ) si->frame = dfmstate.frame; + if( (dfmstate.good&0x102)==0x102 ) { + si->time = dfmstate.datesec + dfmstate.msec/1000; + // Lets be consistent with autorx: the timestamp uses the msec value truncated to seconds, + // whereas the virtual frame number for DFM uses the msec value rounded to full seconds. + // Actually, tt is real UTC, and the transformation to GPS seconds lacks adjusting for leap seconds + si->vframe = dfmstate.datesec + (dfmstate.msec+500)/1000 - 315964800; + } + // All fields updated? 1=OK, 2=with errors + Serial.printf("Cycle done: good is %x\n", dfmstate.good); + si->temperature = get_Temp(); + Serial.printf("Temp: %f\n", si->temperature); + dfmstate.cycledone = ((dfmstate.good&0x11F)==0x11F) ? 1 : 2; + dfmstate.good = 0; + dfmstate.lastdat = 0; + } else { + dfmstate.lastdat = dat[6]; + } dfmstate.good |= (1<frame = dat[3]; + dfmstate.frame = dat[3]; break; case 1: { int val = (((uint16_t)dat[4])<<8) + (uint16_t)dat[5]; Serial.print("UTC-msec: "); Serial.print(val); dfmstate.msec = val; - uint32_t tmp = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + ((uint32_t)dat[3]); - si->sats = bitCount(tmp); + //uint32_t tmp = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + ((uint32_t)dat[3]); + //si->sats = bitCount(tmp); } break; case 2: @@ -375,7 +488,7 @@ void DFM::decodeDAT(uint8_t *dat) Serial.print(", hor-V: "); Serial.print(vh*0.01); si->lat = lat*0.0000001; si->hs = vh*0.01; - si->validPos |= 0x11; + if(lat!=0 || vh!=0) si->validPos |= 0x11; else si->validPos &= ~0x11; } break; case 3: @@ -383,11 +496,11 @@ void DFM::decodeDAT(uint8_t *dat) float lon, dir; lon = (int32_t)(((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + (uint32_t)dat[3]); dir = ((uint16_t)dat[4]<<8) + dat[5]; - Serial.print("GPS-lon: "); Serial.print(lon*0.0000001); - Serial.print(", dir: "); Serial.print(dir*0.01); si->lon = lon*0.0000001; si->dir = dir*0.01; - si->validPos |= 0x42; + Serial.print("GPS-lon: "); Serial.print(si->lon); + Serial.print(", dir: "); Serial.print(si->dir); + if(lon != 0 || dir != 0) si->validPos |= 0x22; else si->validPos &= ~0x22; } break; case 4: @@ -399,32 +512,11 @@ void DFM::decodeDAT(uint8_t *dat) Serial.print(", vv: "); Serial.print(vv*0.01); si->alt = alt*0.01; si->vs = vv*0.01; - si->validPos |= 0x0C; + if(alt!=0 || vv != 0) si->validPos |= 0x0C; else si->validPos &= ~0x0C; } break; case 8: - { - int y = (dat[0]<<4) + (dat[1]>>4); - int m = dat[1]&0x0F; - int d = dat[2]>>3; - int h = ((dat[2]&0x07)<<2) + (dat[3]>>6); - int mi = (dat[3]&0x3F); - char buf[100]; - snprintf(buf, 100, "%04d-%02d-%02d %02d:%02dz", y, m, d, h, mi); - Serial.print("Date: "); Serial.print(buf); - // convert to unix time - int tt = (y-1970)*365 + (y-1969)/4; // days since 1970 - if(m<=12) { tt += MON[m]; if((y%4)==0 && m>2) tt++; } - tt = (tt+d-1)*(60*60*24) + h*3600 + mi*60; - si->time = tt + dfmstate.msec/1000; - // Lets be consistent with autorx: the timestamp uses the msec value truncated to seconds, - // whereas the virtual frame number for DFM uses the msec value rounded to full seconds. - // Actually, tt is real UTC, and the transformation to GPS seconds lacks adjusting for leap seconds - si->vframe = tt + (dfmstate.msec+500)/1000 - 315964800; - // maybe TODO: if we missed the type 0 frame, we still might caculate the right seconds from system time. - // but we only send time stamps to external servers (in particular to sondehub), if all - // required frame types have been correctly decoded, so this does not matter much. - } + // handled above break; default: Serial.print("(?)"); @@ -432,6 +524,7 @@ void DFM::decodeDAT(uint8_t *dat) } } + void DFM::bitsToBytes(uint8_t *bits, uint8_t *bytes, int len) { int i; @@ -504,6 +597,7 @@ int DFM::receive() { // tentative continuous RX version... unsigned long t0 = millis(); + dfmstate.cycledone = 0; while( ( millis() - t0 ) < 1300 ) { uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2); if ( bitRead(value, 7) ) { @@ -526,22 +620,16 @@ int DFM::receive() { } else { if(haveNewFrame) { //Serial.printf("DFM::receive(): new frame complete after %ldms\n", millis()-t0); - Serial.printf("receive newframe: %d, good: %x\n", rxframes, dfmstate.good); haveNewFrame = 0; rxframes--; - if(dfmstate.good & 0x100) { - if(dfmstate.good & 0x11E) { - dfmstate.good = 0; return RX_OK; // type 8 frame has been received - } else { - dfmstate.good = 0; return RX_ERROR; - } - } - if(rxframes==0) return RX_ERROR; + // OK: All DAT frames (0/1/2/3/4/8) have been received in the last cycle + if(dfmstate.cycledone==1) return RX_OK; + if(dfmstate.cycledone>1 || rxframes==0) return RX_ERROR; } delay(2); } } - return rxframes == 5 ? RX_TIMEOUT : RX_OK; + return rxframes == 5 ? RX_TIMEOUT : RX_ERROR; } int DFM::decodeFrameDFM(uint8_t *data) { @@ -552,6 +640,7 @@ int DFM::decodeFrameDFM(uint8_t *data) { int ret0 = hamming(hamming_conf, 7, block_conf); int ret1 = hamming(hamming_dat1, 13, block_dat1); int ret2 = hamming(hamming_dat2, 13, block_dat2); + //Serial.printf("Hamming returns %d %d %d -- %d\n", ret0, ret1, ret2, ret0|ret1|ret2); byte byte_conf[4], byte_dat1[7], byte_dat2[7]; bitsToBytes(block_conf, byte_conf, 7); @@ -561,11 +650,14 @@ int DFM::decodeFrameDFM(uint8_t *data) { printRaw("CFG", 7, ret0, byte_conf); printRaw("DAT", 13, ret1, byte_dat1); printRaw("DAT", 13, ret2, byte_dat2); - decodeCFG(byte_conf); - decodeDAT(byte_dat1); - decodeDAT(byte_dat2); + if (ret0>=0) decodeCFG(byte_conf); + if (ret1>=0 && ret1<=4) decodeDAT(byte_dat1); + if (ret2>=0 && ret2<=4) decodeDAT(byte_dat2); Serial.println(""); - return RX_OK; + // Consistent with autorx: If more than 4 corrected bit errors in DAT block, assume it is possibly corrupt and + // don't treat it as a correct frame (ttgo display shows data anyway, but it is not sent to external sites) + if(ret1>4 || ret2>4) return RX_ERROR; + return (ret0|ret1|ret2)>=0 ? RX_OK : RX_ERROR; } // moved to a single function in Sonde(). This function can be used for additional diff --git a/RX_FSK/src/Display.cpp b/RX_FSK/src/Display.cpp index 810925a..66d063f 100644 --- a/RX_FSK/src/Display.cpp +++ b/RX_FSK/src/Display.cpp @@ -1204,7 +1204,7 @@ void Display::drawString(DispEntry *de, const char *str) { void Display::drawLat(DispEntry *de) { rdis->setFont(de->fmt); - if(!sonde.si()->d.validPos) { + if(!VALIDPOS(sonde.si()->d.validPos)) { drawString(de," "); return; } @@ -1213,7 +1213,7 @@ void Display::drawLat(DispEntry *de) { } void Display::drawLon(DispEntry *de) { rdis->setFont(de->fmt); - if(!sonde.si()->d.validPos) { + if(!VALIDPOS(sonde.si()->d.validPos)) { drawString(de," "); return; } @@ -1222,7 +1222,7 @@ void Display::drawLon(DispEntry *de) { } void Display::drawAlt(DispEntry *de) { rdis->setFont(de->fmt); - if(!sonde.si()->d.validPos) { + if(!VALIDALT(sonde.si()->d.validPos)) { drawString(de," "); return; } @@ -1233,7 +1233,7 @@ void Display::drawAlt(DispEntry *de) { } void Display::drawHS(DispEntry *de) { rdis->setFont(de->fmt); - if(!sonde.si()->d.validPos) { + if(!VALIDHS(sonde.si()->d.validPos)) { drawString(de," "); return; } @@ -1248,7 +1248,7 @@ void Display::drawHS(DispEntry *de) { } void Display::drawVS(DispEntry *de) { rdis->setFont(de->fmt); - if(!sonde.si()->d.validPos) { + if(!VALIDVS(sonde.si()->d.validPos)) { drawString(de," "); return; } @@ -1459,13 +1459,13 @@ void Display::calcGPS() { valid = true; } // distance - if( valid && (sonde.si()->d.validPos&0x03)==0x03 && (layout->usegps&GPSUSE_DIST)) { + if( valid && VALIDPOS(sonde.si()->d.validPos) && (layout->usegps&GPSUSE_DIST)) { gpsDist = (int)calcLatLonDist(mylat, mylon, sonde.si()->d.lat, sonde.si()->d.lon); } else { gpsDist = -1; } // bearing - if( valid && (sonde.si()->d.validPos&0x03)==0x03 && (layout->usegps&GPSUSE_BEARING)) { + if( valid && VALIDPOS(sonde.si()->d.validPos&0x03) && (layout->usegps&GPSUSE_BEARING)) { float lat1 = radians(mylat); float lat2 = radians(sonde.si()->d.lat); float lon1 = radians(mylon); @@ -1522,7 +1522,7 @@ void Display::drawGPS(DispEntry *de) { { // distance // equirectangular approximation is good enough - if( (sonde.si()->d.validPos&0x03)!=0x03 ) { + if( !VALIDPOS(sonde.si()->d.validPos) ) { snprintf(buf, 16, "no pos "); if(de->extra && *de->extra=='5') buf[5]=0; } else if( disp.gpsDist < 0 ) { diff --git a/RX_FSK/src/Sonde.cpp b/RX_FSK/src/Sonde.cpp index 47b06b0..c4be466 100644 --- a/RX_FSK/src/Sonde.cpp +++ b/RX_FSK/src/Sonde.cpp @@ -487,14 +487,13 @@ void Sonde::receive() { } // state information for RX_TIMER / NORX_TIMER events - if(res==0) { // RX OK - flashLed(700); + if(res==RX_OK || res==RX_ERROR) { // something was received... + flashLed( (res==RX_OK)?700:100); if(si->lastState != 1) { si->rxStart = millis(); si->lastState = 1; } - } else { // RX not ok - if(res==RX_ERROR) flashLed(100); + } else { // RX Timeout //Serial.printf("Sonde::receive(): result %d (%s), laststate was %d\n", res, (res<=3)?RXstr[res]:"?", si->lastState); if(si->lastState != 0) { si->norxStart = millis(); diff --git a/RX_FSK/src/Sonde.h b/RX_FSK/src/Sonde.h index 15007f5..36a5470 100644 --- a/RX_FSK/src/Sonde.h +++ b/RX_FSK/src/Sonde.h @@ -74,6 +74,13 @@ extern const char *manufacturer_string[NSondeTypes]; #define TYPE_IS_DFM(t) ( (t)==STYPE_DFM ) #define TYPE_IS_METEO(t) ( (t)==STYPE_M10M20 || (t)==STYPE_M10 || (t)==STYPE_M20 ) +#define VALIDPOS(x) (((x)&0x03)==0x03) +#define VALIDALT(x) ((x)&0x04) +#define VALIDVS(x) ((x)&0x08) +#define VALIDHS(x) ((x)&0x10) +#define VALIDDIR(x) ((x)&0x20) +#define VALIDSATS(x) ((x)&0x40) + typedef struct st_sondedata { // decoded ID char id[10]; diff --git a/RX_FSK/version.h b/RX_FSK/version.h index f0a21ff..8d96436 100644 --- a/RX_FSK/version.h +++ b/RX_FSK/version.h @@ -1,4 +1,4 @@ const char *version_name = "rdzTTGOsonde"; -const char *version_id = "devel20210928"; +const char *version_id = "devel20210930"; const int SPIFFS_MAJOR=2; const int SPIFFS_MINOR=16;