From 93268f9a8ee1d08b8cd1bda274cdfca1975cee30 Mon Sep 17 00:00:00 2001 From: jarau-de Date: Tue, 14 Feb 2023 12:10:39 +0100 Subject: [PATCH 1/5] added WHB18 barometric pressure sender (MA10238) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit geändert: decoder.cpp geändert: decoder.h geändert: whb.cpp geändert: whb.h --- decoder.cpp | 10 +- decoder.cpp~ | 128 ++++++++++ decoder.h | 1 + decoder.h~ | 75 ++++++ whb.cpp | 64 ++++- whb.cpp~ | 708 +++++++++++++++++++++++++++++++++++++++++++++++++++ whb.h | 1 + whb.h~ | 62 +++++ 8 files changed, 1035 insertions(+), 14 deletions(-) create mode 100644 decoder.cpp~ create mode 100644 decoder.h~ create mode 100644 whb.cpp~ create mode 100644 whb.h~ diff --git a/decoder.cpp b/decoder.cpp index 4dca9e4..31eab64 100644 --- a/decoder.cpp +++ b/decoder.cpp @@ -24,7 +24,7 @@ void decoder::set_params(char *_handler, int _mode, int _dbg) { handler=_handler; mode=_mode; - dbg=_dbg; + dbg=_dbg; } //------------------------------------------------------------------------- void decoder::store_bit(int bit) @@ -71,7 +71,7 @@ void decoder::execute_handler(sensordata_t &d) uint64_t nid; if (type!=TFA_WHB) { nid=d.id|(d.type<<24); - // t h s a r f ts + // t h s a r f ts snprintf(cmd,sizeof(cmd),"%s %04" PRIx64 " %+.1f %g %i %i %i %i %li", handler, nid, d.temp, d.humidity, @@ -81,10 +81,10 @@ void decoder::execute_handler(sensordata_t &d) } else { // WHB has really long IDs... nid=d.id; - // t h s a r f ts - snprintf(cmd,sizeof(cmd),"%s %013" PRIx64 " %+.1f %g %i %i %i %i %li", + // t h p s a r f ts + snprintf(cmd,sizeof(cmd),"%s %013" PRIx64 " %+.1f %g %.1f %i %i %i %i %li", handler, - nid, d.temp, d.humidity, + nid, d.temp, d.humidity, d.pressure, d.sequence, d.alarm, d.rssi, d.flags, d.ts); diff --git a/decoder.cpp~ b/decoder.cpp~ new file mode 100644 index 0000000..4dca9e4 --- /dev/null +++ b/decoder.cpp~ @@ -0,0 +1,128 @@ +#include +#include +#include + +#include "decoder.h" +#define __STDC_FORMAT_MACROS +#include + +//------------------------------------------------------------------------- +// mode=0 -> handle individual messages +// mode=1 -> handle summary (only one message per id) + +decoder::decoder(sensor_e _type) +{ + type=_type; + handler=NULL; + mode=0; + dbg=0; + bad=0; + synced=0; +} +//------------------------------------------------------------------------- +void decoder::set_params(char *_handler, int _mode, int _dbg) +{ + handler=_handler; + mode=_mode; + dbg=_dbg; +} +//------------------------------------------------------------------------- +void decoder::store_bit(int bit) +{ +} +//------------------------------------------------------------------------- +// Shortcut for testing +void decoder::store_bytes(uint8_t *d, int len) +{ + memcpy(rdata, d, len); + byte_cnt=len; + synced=1; +} +//------------------------------------------------------------------------- +void decoder::flush(int rssi, int offset) +{ +} +//------------------------------------------------------------------------- +void decoder::store_data(sensordata_t &d) +{ + int found=0; + /* only first appearance of id message is stored + also check for identical sequence for WHB (suppress double exec) + */ + auto ret=data.find(d.id); + + if (ret==data.end()) + data.insert(std::pair(d.id,d)); + else if (ret->second.type==TFA_WHB) { + if (ret->second.sequence==d.sequence) + found=1; + else + ret->second.sequence=d.sequence; // track sequence + } + + if (!mode && !found) + execute_handler(d); +} +//------------------------------------------------------------------------- +void decoder::execute_handler(sensordata_t &d) +{ + if (handler && strlen(handler)) { + char cmd[512]; + uint64_t nid; + if (type!=TFA_WHB) { + nid=d.id|(d.type<<24); + // t h s a r f ts + snprintf(cmd,sizeof(cmd),"%s %04" PRIx64 " %+.1f %g %i %i %i %i %li", + handler, + nid, d.temp, d.humidity, + d.sequence, d.alarm, d.rssi, + d.flags, + d.ts); + } + else { // WHB has really long IDs... + nid=d.id; + // t h s a r f ts + snprintf(cmd,sizeof(cmd),"%s %013" PRIx64 " %+.1f %g %i %i %i %i %li", + handler, + nid, d.temp, d.humidity, + d.sequence, d.alarm, d.rssi, + d.flags, + d.ts); + } + if (dbg>=1) + printf("EXEC %s\n",cmd); + (void)system(cmd); + } +} +//------------------------------------------------------------------------- +void decoder::flush_storage(void) +{ + if (!mode) + return; + map::iterator it; + it=data.begin(); + while(it!=data.end()) { + execute_handler(it->second); + it++; + } + data.clear(); +} +//------------------------------------------------------------------------- +//------------------------------------------------------------------------- +demodulator::demodulator(decoder *_dec) +{ + dec=_dec; + last_bit_idx=0; +} +//------------------------------------------------------------------------- +void demodulator::start(int len) +{ + if (last_bit_idx) // handle data wrap from last block processing + last_bit_idx-=len; +} +//------------------------------------------------------------------------- +int demodulator::demod(int thresh, int pwr, int index, int16_t *iq) +{ + return 0; +} +//------------------------------------------------------------------------- diff --git a/decoder.h b/decoder.h index dc3d34b..c0ce120 100644 --- a/decoder.h +++ b/decoder.h @@ -23,6 +23,7 @@ typedef struct { uint64_t id; double temp; double humidity; + double pressure; int alarm; int flags; int sequence; diff --git a/decoder.h~ b/decoder.h~ new file mode 100644 index 0000000..dc3d34b --- /dev/null +++ b/decoder.h~ @@ -0,0 +1,75 @@ +#ifndef _INCLUDE_DECODER_H +#define _INCLUDE_DECODER_H + +#include +#include +#include + +using std::string; +using std::map; + +enum sensor_e { + TFA_1=0, // IT+ Klimalogg Pro, 30.3180, 30.3181, 30.3199(?) + TFA_2, // IT+ 30.3143, 30.3146(?), 30.3144 + TFA_3, // 30.3155 + TX22, // LaCrosse TX22 + TFA_WHP, // 30.3306 (rain meter), pulse data + TFA_WHB, // TFA WeatherHub + FIREANGEL=0x20 // ST-630+W2 +}; + +typedef struct { + sensor_e type; + uint64_t id; + double temp; + double humidity; + int alarm; + int flags; + int sequence; + time_t ts; + int rssi; +} sensordata_t; + +class decoder +{ + public: + decoder(sensor_e _type); + void set_params(char *_handler, int _mode, int _dbg); + virtual void store_bit(int bit); + virtual void flush(int rssi, int offset=0); + virtual void store_data(sensordata_t &d); + virtual void execute_handler(sensordata_t &d); + virtual void flush_storage(void); + virtual int has_sync(void) {return synced;}; + int count(void) {return data.size();} + sensor_e get_type(void) {return type;} + virtual void store_bytes(uint8_t *d, int len); + protected: + int dbg; + int bad; + int synced; + sensor_e type; + uint8_t rdata[256]; + int byte_cnt; + private: + + char *handler; + int mode; + map data; +}; + +class demodulator +{ + public: + demodulator(decoder *_dec); + virtual void start(int len); + virtual void reset(void){}; + virtual int demod(int thresh, int pwr, int index, int16_t *iq); + + decoder *dec; + protected: + + int last_bit_idx; +}; + +#endif diff --git a/whb.cpp b/whb.cpp index 3e1d3dc..e870479 100644 --- a/whb.cpp +++ b/whb.cpp @@ -59,6 +59,7 @@ map crc_initvals = { { 0x10, 0x62d0afc1}, // Door sensor { 0x11, 0x8cba0708}, // 4 Thermo-hygro-sensors (TFA 30.3060.01) { 0x12, 0x5a9e30ae}, // Humidity guard (TFA 30.5043.01) + { 0x18, 0xca0abb08}, // Air pressure sensor }; // Translates time units in seconds multiplier @@ -94,8 +95,6 @@ whb_decoder::whb_decoder(sensor_e _type) : decoder(_type) msg[2]=i>>8; msg[3]=i; uint32_t crc_calc=crc->calc(msg, 4, 0); - if (crc_calc== 0x83f50b46) - printf("%x\n",i); } exit(0); } @@ -266,9 +265,10 @@ void whb_decoder::decode_07(uint8_t *msg, uint64_t id, int rssi, int offset) hum[n]=BE16(msg+4+4*n)&0x0ff; } if (dbg>=0) { - printf("WHB07 ID %" PRIx64 " TEMP_IN %g HUM_IN %i TEMP_OUT %g HUM_OUT %i",id, cvt_temp(temp[0]), hum[0], cvt_temp(temp[1]), hum[1]); + printf("WHB07 ID %012" PRIX64 " TEMP_IN %.1f HUM_IN %i TEMP_OUT %.1f HUM_OUT %i BAT %d", + id, cvt_temp(temp[0]), hum[0], cvt_temp(temp[1]), hum[1], seq&0x4000); if (dbg>1) - printf(" PTEMP_IN %g PHUM_IN %i PTEMP_OUT %g PHUM_OUT %i", cvt_temp(temp[2]), hum[2], cvt_temp(temp[3]), hum[3]); + printf(" PTEMP_IN %.1f PHUM_IN %i PTEMP_OUT %.1f PHUM_OUT %i", cvt_temp(temp[2]), hum[2], cvt_temp(temp[3]), hum[3]); puts(""); fflush(stdout); } @@ -473,6 +473,49 @@ void whb_decoder::decode_12(uint8_t *msg, uint64_t id, int rssi, int offset) store_data(sd); } } + + +//------------------------------------------------------------------------- +// Temp/hum/air pressure + +void whb_decoder::decode_18(uint8_t *msg, uint64_t id, int rssi, int offset) +{ + uint32_t seq = BE32(msg)&0x0fffff; + uint16_t temp = BE16(msg+3)&0x7ff; + uint16_t hum = msg[5]&0xff; + uint16_t bp = BE16(msg+6)&0xffff; + + uint16_t ptemp = BE16(msg+8)&0x7ff; + uint16_t phum = msg[10]&0xff; + uint16_t pbp = BE16(msg+11)&0xffff; + + if (dbg>=0) { + printf("WHB18 ID %012" PRIX64 " TEMP %.1f HUM %i, BP %.1f BAT %d", + id, cvt_temp(temp), hum, cvt_temp(bp), seq&0x400000); + if (dbg>1) + printf(" PTEMP %.1f PHUM %i, PBP %.1f", cvt_temp(ptemp), phum, cvt_temp(pbp)); + puts(""); + fflush(stdout); + } + + sensordata_t sd = {}; + //printf("pressure: %f %f\n", sd.pressure, sd.temp); + sd.type=type; + sd.id=(id<<4LL); + sd.temp=cvt_temp(temp); + sd.humidity=hum; + sd.pressure=cvt_temp(bp); + sd.sequence=seq; + sd.alarm=0; + sd.rssi=rssi; + sd.flags=0; + sd.ts=time(0); + store_data(sd); + sd.pressure=-9999.9; + store_data(sd); +} + + //------------------------------------------------------------------------- void whb_decoder::flush(int rssi, int offset) { @@ -485,11 +528,11 @@ void whb_decoder::flush(int rssi, int offset) goto reset; // FIXME: byte count usually 2-3 bytes longer than real payload - if (dbg && byte_cnt) { - printf("#%03i %u L=%i ",snum++,(uint32_t)time(0), byte_cnt); - for(int n=0;n -1 && byte_cnt) { + printf("#%03i %u L=%i ", snum++,(uint32_t)time(0), byte_cnt); + for(int n=0;n +#include +#define __STDC_FORMAT_MACROS +#include + + +#include "whb.h" +#include "dsp_stuff.h" + +/* + TFA WeatherHub + 868.250MHz + 6000 bits/second + PSK-NRZS-G3RUH-scrambled + + RF layer: + + 4b 2d d4 2b LL II II II II II II CC CC CC CC + + 4b 2d d4 2b: Sync word + + LL: Packet length from LL to last CC (depends on type) + + II: 6 Byte ID (printed on the sensor, first byte type) + + Thanks to CRC reverse-engineering tool "reveng": http://reveng.sourceforge.net/ + CC: CRC-32, poly=0x04c11db7 init= refin=false refout=false xorout=0x00000000 + Init-value depends on type (see crc_initvals)! + + See https://github.com/sarnau/MMMMobileAlerts/blob/master/MobileAlertsGatewayBinaryUpload.markdown + for more details on the sensor payload! + + ID-Mapping: Sensor-ID gets an appended nibble like TX22 (-> 6.5byte ID!) + ID.0: temperature, humidity (0 if not available) (indoor) + ID.2: Rain sensor: tempval=rain-counter, hum=time since last pulse + ID.3: Wind sensor: tempval=speed (m/s), hum=direction (degree) + ID.4: Wind sensor: tempval=gust speed (m/s) + ID.5: Door/water-sensor: tempval=state, hum=time since last event + ID.c: temperature, humidity (outdoor, sensor #1) + ID.d: temperature, humidity (sensor #2) + ID.e: temperature, humidity (sensor #3) + + Notes: + WHB-Type 7 (MA10410/TFA 35.1147.01) has indoor and outdoor values -> mapped to ID.0 and ID.c + WHB-Type 11 (TFA 30.3060.01) has indoor and 3 other sensor values -> mapped to ID.0 and ID.c to ID.e + */ +#include +using std::map; + +map crc_initvals = { + { 0x02, 0x97d97a26}, // Only temp + { 0x03, 0xf59c5a1e}, // Temp/hum + { 0x04, 0x98e1d11f}, // Temp/hum/water + { 0x06, 0xa7a41254}, // Temp/hum + temp2 + { 0x07, 0x3303fb1d}, // Station MA10410 (TFA 35.1147.01) + { 0x08, 0x29f0f49b}, // Rain sensor (+ temp) + { 0x09, 0xa7a41254}, // Temp/hum + temp2 (TFA 30.3302.02), extended temperature range + { 0x0b, 0xe7720ae4}, // Wind sensor + { 0x10, 0x62d0afc1}, // Door sensor + { 0x11, 0x8cba0708}, // 4 Thermo-hygro-sensors (TFA 30.3060.01) + { 0x12, 0x5a9e30ae}, // Humidity guard (TFA 30.5043.01) +}; + +// Translates time units in seconds multiplier +uint32_t timeunit_tab[4]= { + 24*60*60, // day + 60*60, // hour + 60, // minutes + 1 // seconds +}; + +#define BE16(x) ( ((*(x))<<8) | (*(x+1)) ) +#define BE24(x) ( ((*(x))<<16) | ((*(x+1))<<8) | (*(x+2)) ) +#define BE32(x) ( ((*(x))<<24) | ((*(x+1))<<16) | ((*(x+2))<<8) | (*(x+3)) ) + +#define BE48(x) ( (((uint64_t)*(x))<<40) | (((uint64_t)*(x+1))<<32) | (((uint64_t)*(x+2))<<24) | \ + (((uint64_t)*(x+3))<<16) | (((uint64_t)*(x+4))<<8) | (((uint64_t)*(x+5))) ) + +//------------------------------------------------------------------------- +whb_decoder::whb_decoder(sensor_e _type) : decoder(_type) +{ + sr=0; + sr_cnt=-1; + byte_cnt=0; + snum=0; + bad=0; + crc=new crc32(0x04c11db7); +#if 0 + { + uint8_t msg[4]; + for(uint32_t i=0;i<0xffffffff;i++) { + msg[0]=i>>24; + msg[1]=i>>16; + msg[2]=i>>8; + msg[3]=i; + uint32_t crc_calc=crc->calc(msg, 4, 0); + if (crc_calc== 0x83f50b46) + printf("%x\n",i); + } + exit(0); + } +#endif + last_bit=0; + lfsr=0; + psk=last_psk=0; + nrzs=0; +} +//------------------------------------------------------------------------- +double whb_decoder::cvt_temp(uint16_t raw, int extended) +{ + if (extended==1) { + // extened range, bit 11 is actually the sign bit (range -204.7 to 204.7) + if (raw&0x800) + return -((raw^0xfff)+1)/10.0; + else + return raw/10.0; + } else { + if (raw&0x400) + return -((raw^0x7ff)+1)/10.0; + else + return raw/10.0; + } +} +//------------------------------------------------------------------------- +// Temp/hum +void whb_decoder::decode_02(uint8_t *msg, uint64_t id, int rssi, int offset) +{ + uint16_t seq=BE16(msg)&0x3fff; + uint16_t temp=BE16(msg+2)&0x7ff; + uint16_t temp_prev=BE16(msg+4)&0x7ff; + + if (dbg>=0) { + printf("WHB02 ID %" PRIx64 " TEMP %g, PTEMP %g\n", + id, cvt_temp(temp), cvt_temp(temp_prev)); + fflush(stdout); + } + sensordata_t sd; + sd.type=type; + sd.id=(id<<4LL); + sd.temp=cvt_temp(temp); + sd.humidity=0; + sd.sequence=seq; + sd.alarm=0; + sd.rssi=rssi; + sd.flags=0; + sd.ts=time(0); + store_data(sd); +} +//------------------------------------------------------------------------- +// Temp/hum +void whb_decoder::decode_03(uint8_t *msg, uint64_t id, int rssi, int offset) +{ + uint16_t seq=BE16(msg)&0x3fff; + uint16_t temp=BE16(msg+2)&0x7ff; + uint16_t hum=BE16(msg+4)&0xff; + + uint16_t temp_prev=BE16(msg+6)&0x7ff; + uint16_t hum_prev=BE16(msg+8)&0xff; + + uint16_t unknown=msg[10]; + if (dbg>=0) { + printf("WHB03 ID %" PRIx64 " TEMP %g HUM %i, PTEMP %g PHUM %i\n", + id, cvt_temp(temp), hum, cvt_temp(temp_prev), hum_prev); + fflush(stdout); + } + sensordata_t sd; + sd.type=type; + sd.id=(id<<4LL); + sd.temp=cvt_temp(temp); + sd.humidity=hum; + sd.sequence=seq; + sd.alarm=0; + sd.rssi=rssi; + sd.flags=0; + sd.ts=time(0); + store_data(sd); +} +//------------------------------------------------------------------------- +// Temp/hum/water +void whb_decoder::decode_04(uint8_t *msg, uint64_t id, int rssi, int offset) +{ + uint16_t seq=BE16(msg)&0x3fff; + uint16_t temp=BE16(msg+2)&0x7ff; + uint16_t hum=BE16(msg+4)&0xff; + uint8_t wet=*(msg+6); + + uint16_t temp_prev=BE16(msg+7)&0x7ff; + uint16_t hum_prev=BE16(msg+9)&0xff; + uint16_t wet_prev=*(msg+11); + + if (dbg>=0) { + printf("WHB04 ID %" PRIx64 " TEMP %g HUM %i WET %i, PTEMP %g PHUM %i PWET %i\n", + id, cvt_temp(temp),hum,(wet&1)^1, cvt_temp(temp_prev), hum_prev, (wet_prev&1)^1); + fflush(stdout); + } + sensordata_t sd; + sd.type=type; + sd.id=(id<<4LL); + sd.temp=cvt_temp(temp); + sd.humidity=hum; + sd.sequence=seq; + sd.alarm=0; + sd.rssi=rssi; + sd.flags=0; + sd.ts=time(0); + store_data(sd); + + sd.id=(id<<4LL)|5; + sd.temp=(wet&1)^1; + sd.humidity=0; + store_data(sd); +} +//------------------------------------------------------------------------- +// Temp/hum + temp2 +// decodes also version 9 with extended temperature range +void whb_decoder::decode_06_09(uint8_t *msg, uint64_t id, int rssi, int offset, int extended) +{ + uint16_t seq=BE16(msg)&0x3fff;; + uint16_t temp=BE16(msg+2)&0x7ff; + uint16_t temp2, temp2_prev; + int whb_type=6; + if (extended==1) { + temp2=BE16(msg+4)&0xfff; + temp2_prev=BE16(msg+10)&0xfff; + whb_type=9; + + } else { + temp2=BE16(msg+4)&0x7ff; + temp2_prev=BE16(msg+10)&0x7ff; + } + uint16_t hum=BE16(msg+6)&0xff; + uint16_t temp_prev=BE16(msg+8)&0x7ff; + uint16_t hum_prev=BE16(msg+12)&0xff; + + if (dbg>=0) { + printf("WHB0%i ID %" PRIx64 "TEMP %g HUM %i TEMP2 %g, PTEMP %g PHUM %i PTEMP2 %g\n", + whb_type, id, cvt_temp(temp),hum,cvt_temp(temp2,extended), cvt_temp(temp_prev), hum_prev, cvt_temp(temp2_prev,extended)); + fflush(stdout); + } + sensordata_t sd; + sd.type=type; + sd.id=(id<<4LL); + sd.temp=cvt_temp(temp); + sd.humidity=hum; + sd.sequence=seq; + sd.alarm=0; + sd.rssi=rssi; + sd.flags=0; + sd.ts=time(0); + store_data(sd); + + sd.id=(id<<4LL)|1; + sd.temp=cvt_temp(temp2, extended); + sd.humidity=0; + store_data(sd); +} +//------------------------------------------------------------------------- +// Station MA10410 (TFA 35.1147.01) +void whb_decoder::decode_07(uint8_t *msg, uint64_t id, int rssi, int offset) +{ + uint16_t seq=BE16(msg)&0x3fff; + uint16_t temp[4]; // 0=indoor, 2/3: previous + uint16_t hum[4]; + for(int n=0;n<4;n++) { + temp[n]=BE16(msg+2+4*n)&0x07ff; + hum[n]=BE16(msg+4+4*n)&0x0ff; + } + if (dbg>=0) { + printf("WHB07 ID %" PRIx64 " TEMP_IN %g HUM_IN %i TEMP_OUT %g HUM_OUT %i",id, cvt_temp(temp[0]), hum[0], cvt_temp(temp[1]), hum[1]); + if (dbg>1) + printf(" PTEMP_IN %g PHUM_IN %i PTEMP_OUT %g PHUM_OUT %i", cvt_temp(temp[2]), hum[2], cvt_temp(temp[3]), hum[3]); + puts(""); + fflush(stdout); + } + + sensordata_t sd; + sd.type=type; + sd.id=(id<<4LL); // ID.0 = indoor + sd.temp=cvt_temp(temp[0]); + sd.humidity=hum[0]; + sd.sequence=seq; + sd.alarm=0; + sd.rssi=rssi; + sd.flags=0; + sd.ts=time(0); + store_data(sd); + + sd.id=(id<<4LL)|0xc; // ID.c = outdoor + sd.temp=cvt_temp(temp[1]); + sd.humidity=hum[1]; + store_data(sd); +} +//------------------------------------------------------------------------- +// Rain sensor, store counter and temperature +void whb_decoder::decode_08(uint8_t *msg, uint64_t id, int rssi, int offset) +{ + uint16_t seq=BE16(msg)&0x3fff; + uint8_t event=msg[2]>>6; + uint16_t temp=BE16(msg+2)&0x07ff; // 11Bit signed, 0.1steps + double temp_real=cvt_temp(temp); + uint16_t cnt=BE16(msg+4); + uint32_t times[10]; + if (dbg>=0) { + printf("WHB08 ID %" PRIx64 " cnt %i\n",id, cnt); + fflush(stdout); + } + for(int i=0;i<10;i++) { + uint16_t x=BE16(msg+6+2*i); + times[i]=timeunit_tab[(x>>14)&3] * (x&0x3fff); + if (dbg>1) + printf("WHB08 ID %" PRIx64 " #%i time %i\n",id,i,times[i]); + } + + sensordata_t sd; + sd.type=type; + sd.id=(id<<4LL)|2; + sd.temp=cnt; + sd.humidity=times[1]; + sd.sequence=seq; + sd.alarm=0; + sd.rssi=rssi; + sd.flags=0; + sd.ts=time(0); + store_data(sd); + + sd.id=(id<<4); + sd.temp=temp_real; + sd.humidity=0; + store_data(sd); +} +//------------------------------------------------------------------------- +// Wind sensor +void whb_decoder::decode_0b(uint8_t *msg, uint64_t id, int rssi, int offset) +{ + uint32_t seq=BE24(msg); + float dir[6],speed[6],gust[6]; + uint32_t times[6]; + for(int i=0;i<6;i++) { + uint32_t v=BE32(msg+3+4*i); + dir[i]=22.5*(v>>28); // 0: N 90:E 180:S + speed[i]= (((v>>16)&0xff) + 256*((v>>25)&1))/10.0; + gust[i]= (((v>>8)&0xff) + 256*((v>>24)&1))/10.0; + times[i]=(v&0xff)*2; + if (dbg>=0 && (i==0 || dbg>0)) { + printf("WHB0b ID %" PRIx64 " #%i DIR %f SPEED %f GUST %f time %i\n", + id, i,dir[i],speed[i],gust[i],times[i]); + fflush(stdout); + } + } + sensordata_t sd; + sd.type=type; + sd.id=(id<<4LL)|3; + sd.temp=speed[0]; + sd.humidity=dir[0]; + sd.sequence=seq; + sd.alarm=0; + sd.rssi=rssi; + sd.flags=0; + sd.ts=time(0); + store_data(sd); + + sd.id=(id<<4)|4; + sd.temp=gust[0]; + sd.humidity=0; + store_data(sd); +} +//------------------------------------------------------------------------- +// Door sensor +void whb_decoder::decode_10(uint8_t *msg, uint64_t id, int rssi, int offset) +{ + uint16_t seq=BE16(msg)&0x3fff; + uint8_t state[4]; + uint32_t times[4]; + for(int i=0;i<4;i++) { + uint16_t x=BE16(msg+2+2*i); + state[i]=x>>15; // 0=closed + times[i]=timeunit_tab[(x>>13)&3] * (x&0x1fff); + if (dbg>=0 && (i==0 || dbg>0)) { + printf("WHB10 ID %" PRIx64 " #%i %i %i\n",id,i,state[i],times[i]); + fflush(stdout); + } + } + sensordata_t sd; + sd.type=type; + sd.id=(id<<4LL)|5; + sd.temp=state[0]; + sd.humidity=times[1]; + sd.sequence=seq; + sd.alarm=0; + sd.rssi=rssi; + sd.flags=0; + sd.ts=time(0); + store_data(sd); +} +//------------------------------------------------------------------------- +// 4 Thermo-hygro-sensors (TFA 30.3060.01) +void whb_decoder::decode_11(uint8_t *msg, uint64_t id, int rssi, int offset) +{ + uint16_t seq=BE16(msg)&0x3fff; + uint16_t temp[8]; // 3 = indoor, >3: previous values + uint16_t hum[8]; + for(int n=0;n<8;n++) { + temp[n]=BE16(msg+2+4*n)&0x07ff; + hum[n] =BE16(msg+4+4*n)&0xff; + } + + if (dbg>=0) { + printf("WHB11 %" PRIx64 " TEMP1 %g HUM1 %i TEMP2 %g HUM2 %i TEMP3 %g HUM3 %i TEMP_IN %g HUM_IN %i", + id, cvt_temp(temp[0]),hum[0], cvt_temp(temp[1]),hum[1], cvt_temp(temp[2]),hum[2], cvt_temp(temp[3]),hum[3]); + if (dbg>1) + printf(" PTEMP1 %g PHUM1 %i PTEMP2 %g PHUM2 %i PTEMP3 %g PHUM3 %i PTEMP_IN %g PHUM_IN %i", + cvt_temp(temp[4]),hum[4], cvt_temp(temp[5]),hum[5], cvt_temp(temp[6]),hum[6], cvt_temp(temp[7]),hum[7]); + puts(""); + fflush(stdout); + } + sensordata_t sd; + sd.type=type; + sd.id=(id<<4LL); + sd.temp=cvt_temp(temp[3]); + sd.humidity=hum[3]; + sd.sequence=seq; + sd.alarm=0; + sd.rssi=rssi; + sd.flags=0; + sd.ts=time(0); + store_data(sd); + + for(int n=0;n<3;n++) { + sd.id=(id<<4LL)|(0xc+n); + sd.temp=cvt_temp(temp[n]); + sd.humidity=hum[n]; + store_data(sd); + } +} +//------------------------------------------------------------------------- +// Humidity guard +void whb_decoder::decode_12(uint8_t *msg, uint64_t id, int rssi, int offset) +{ + uint16_t seq=BE16(msg)&0x3fff; + uint16_t hum[5]; + hum[0]=msg[8]&0x7f; + hum[1]=msg[2]&0x7f; + hum[2]=msg[3]&0x7f; + hum[3]=msg[4]&0x7f; + hum[4]=msg[5]&0x7f; + uint16_t temp=BE16(msg+6)&0x7ff; + + if (dbg>=0) { + printf("WHB12 %" PRIx64 " TEMP %g HUM %i HUM3h %i HUM24h %i HUM7d %i HUM30d %i\n", + id, cvt_temp(temp), hum[0], hum[1], hum[2], hum[3], hum[4]); + fflush(stdout); + } + sensordata_t sd; + sd.type=type; + sd.id=(id<<4LL); + sd.temp=cvt_temp(temp); + sd.humidity=hum[0]; + sd.sequence=seq; + sd.alarm=0; + sd.rssi=rssi; + sd.flags=0; + sd.ts=time(0); + store_data(sd); + + // Store average humidity as sub ids 1 c d e + sd.id=(id<<4LL)+1; + sd.temp=0; + sd.humidity=hum[1]; + store_data(sd); + for(int n=0;n<3;n++) { + sd.id=(id<<4LL)+0xc+n; + sd.humidity=hum[2+n]; + store_data(sd); + } +} +//------------------------------------------------------------------------- +void whb_decoder::flush(int rssi, int offset) +{ + uint32_t crc_calc=0; + uint32_t crc_val=0; + int plen; + uint32_t stype; + + if (byte_cnt<11 || byte_cnt>60) // Sanity + goto reset; + + // FIXME: byte count usually 2-3 bytes longer than real payload + if (dbg && byte_cnt) { + printf("#%03i %u L=%i ",snum++,(uint32_t)time(0), byte_cnt); + for(int n=0;n60) + goto bad; + + stype=rdata[5]; // sensor type + + if (crc_initvals.find(stype)==crc_initvals.end()) { + if (dbg>=0) + printf("WHB: Probably unsupported sensor type %02x! Please report\n",stype); + goto bad; + } + + crc_calc=crc->calc(&rdata[4], plen-4, crc_initvals[stype]); + crc_val=(rdata[plen]<<24) | (rdata[plen+1]<<16) | (rdata[plen+2]<<8) | rdata[plen+3]; + + if (crc_calc==crc_val) { + uint64_t id=BE48(&rdata[5]); + uint8_t *msg=&rdata[4+1+6]; + switch(stype) { + case 0x02: + decode_02(msg, id, rssi, offset); + break; + case 0x03: + decode_03(msg, id, rssi, offset); + break; + case 0x04: + decode_04(msg, id, rssi, offset); + break; + case 0x06: + decode_06_09(msg, id, rssi, offset); + break; + case 0x07: + decode_07(msg, id, rssi, offset); + break; + case 0x08: + decode_08(msg, id, rssi, offset); + break; + case 0x09: + decode_06_09(msg, id, rssi, offset, 1); + break; + case 0x0b: + decode_0b(msg, id, rssi, offset); + break; + case 0x10: + decode_10(msg, id, rssi, offset); + break; + case 0x11: + decode_11(msg, id, rssi, offset); + break; + case 0x12: + decode_12(msg, id, rssi, offset); + break; + } + goto reset; + } + bad: + bad++; + if (dbg) { + if (crc_val!=crc_calc) + printf("\nWHB BAD %i RSSI %i (CRC is %08x, should be %08x, len %i, plen %i)\n", + bad, rssi, crc_val,crc_calc, byte_cnt,plen); + else + printf("\nWHB BAD %i RSSI %i (SANITY)\n",bad,rssi); + } + reset: + sr_cnt=-1; + sr=0; + byte_cnt=0; + synced=0; +} +//------------------------------------------------------------------------- +void whb_decoder::store_bit(int bit) +{ + // De-PSK + if (bit == last_bit) + psk=1-psk; + // De-NRZS + if (psk == last_psk) + nrzs=1-nrzs; + + last_bit=bit; + last_psk=psk; + + // G3RUH descrambler + int bit_descrambled=nrzs ^ ((lfsr>>16)&1) ^ ((lfsr>>11)&1); + lfsr=(lfsr<<1)|nrzs; + + sr=(sr>>1)|(bit_descrambled<<31); + + if ( ((sr&0xffffffff)==0x2bd42d4b) ) { // FIXME 3 or 4 bytes? + //printf("######################### SYNC\n"); + synced=1; + sr_cnt=0; + rdata[0]=sr&0xff; + rdata[1]=(sr>>8)&0xff; + rdata[2]=(sr>>16)&0xff; + byte_cnt=3; + } + + if (sr_cnt==0) { + if (byte_cnt<(int)sizeof(rdata)) { + rdata[byte_cnt]=(sr>>24)&0xff; + } + //printf(" %i %02x\n",byte_cnt,rdata[byte_cnt]); + byte_cnt++; + } + if (sr_cnt>=0) + sr_cnt=(sr_cnt+1)&7; +} +//------------------------------------------------------------------------- +whb_demod::whb_demod(decoder *_dec, double _spb) : demodulator( _dec) +{ + spb=_spb; + timeout_cnt=0; + reset(); + iir=new iir2(2.0/spb); // Pulse filter + iir_avg=new iir2(0.0025/spb); // Phase discriminator filter + printf("WHB: Samples per bit: %.1f\n",spb); + last_dev=0; +} +//------------------------------------------------------------------------- +void whb_demod::reset(void) +{ + offset=0; + bitcnt=0; + last_peak=0; + rssi=0; + step=last_peak=0; +} +//------------------------------------------------------------------------- +//#define DBG_DUMP +#if DBG_DUMP +// More debugging +static FILE *fx=NULL; +static FILE *fy=NULL; +static int fc=0; +#endif +int whb_demod::demod(int thresh, int pwr, int index, int16_t *iq) +{ + int triggered=0; + + if (pwr>thresh) { + if (!timeout_cnt) { + reset(); + } + + timeout_cnt=8*spb; + } + + if (timeout_cnt) { + triggered++; + int dev; + + /* Shaped PSK of AX5031 causes hard drop at phase changes for fm_dev_nrzs() + -> detected minima are 0s, fillup with 1s since last 0 + */ + dev=fm_dev_nrzs(iq[0],iq[1],last_i,last_q); + dev=iir->step(dev); // reduce noise + if (!dec->has_sync()) + avg_of=iir_avg->step(0.5*dev); // decision value for phase change + + int bit=0; + timeout_cnt--; + + int tdiff=step-last_peak; + + // Phase change? + if (devlast_dev && + (tdiff>3*spb/4) ) { + bit=avg_of; + dec->store_bit(0); + bitcnt++; + int bit0=(tdiff+spb/2)/spb; + for(int n=1;nstore_bit(1); + bitcnt++; + } + last_peak=step; + } + last_dev=dev; + + if (dec->has_sync()) + rssi+=(iq[0]*iq[0]+iq[1]*iq[1]); + +#ifdef DBG_DUMP + // plot "blub" using 1:2 with lines,"blub" using 1:3 with boxes + if (!fx) + fx=fopen("blub","w"); + if (!fy) + fy=fopen("blub1","w"); + if (fx) + fprintf(fx,"%i %i %i %i\n",fc,dev, bit, tdiff_mod*10); + fc++; +#endif + + if (!timeout_cnt) { + // Flush descrambler + if (dec->has_sync()) { + for(int n=0;n<16;n++) + dec->store_bit(0); + dec->flush(10*log10(1+rssi/4000),offset); // scale to rougly match with TFA_1-RSSI + } + reset(); + rssi=0; + } + } + last_i=iq[0]; + last_q=iq[1]; + + step++; + return triggered; +} +//------------------------------------------------------------------------- diff --git a/whb.h b/whb.h index bdb5953..a608f83 100644 --- a/whb.h +++ b/whb.h @@ -26,6 +26,7 @@ class whb_decoder: public decoder void decode_10(uint8_t *msg, uint64_t id, int rssi, int offset); // door void decode_11(uint8_t *msg, uint64_t id, int rssi, int offset); // 4 Thermo-hygro-sensors (TFA 30.3060.01) void decode_12(uint8_t *msg, uint64_t id, int rssi, int offset); // Humidity guard/cosy radar (TFA 30.5043.01) + void decode_18(uint8_t *msg, uint64_t id, int rssi, int offset); // Air pressure uint32_t sr; int sr_cnt; diff --git a/whb.h~ b/whb.h~ new file mode 100644 index 0000000..bdb5953 --- /dev/null +++ b/whb.h~ @@ -0,0 +1,62 @@ +#ifndef _INCLUDE_TFA5H +#define _INCLUDE_TFA5H + +#include +#include "dsp_stuff.h" +#include "decoder.h" +#include "crc32.h" + +using std::string; + +class whb_decoder: public decoder +{ + public: + whb_decoder(sensor_e type=TFA_WHB); + void store_bit(int bit); + void flush(int rssi,int offset=0); + private: + double cvt_temp(uint16_t raw, int extended=0); + void decode_02(uint8_t *msg, uint64_t id, int rssi, int offset); // temp + void decode_03(uint8_t *msg, uint64_t id, int rssi, int offset); // temp/hum + void decode_04(uint8_t *msg, uint64_t id, int rssi, int offset); // temp/hum/water + void decode_06_09(uint8_t *msg, uint64_t id, int rssi, int offset, int extended=0); // temp/hum + temp (TFA 30.3304.02, extended range 30.3302.02) + void decode_07(uint8_t *msg, uint64_t id, int rssi, int offset); // Station MA10410 (TFA 35.1147.01) + void decode_08(uint8_t *msg, uint64_t id, int rssi, int offset); // rain + void decode_0b(uint8_t *msg, uint64_t id, int rssi, int offset); // wind + void decode_10(uint8_t *msg, uint64_t id, int rssi, int offset); // door + void decode_11(uint8_t *msg, uint64_t id, int rssi, int offset); // 4 Thermo-hygro-sensors (TFA 30.3060.01) + void decode_12(uint8_t *msg, uint64_t id, int rssi, int offset); // Humidity guard/cosy radar (TFA 30.5043.01) + + uint32_t sr; + int sr_cnt; + int snum; + crc32 *crc; + + // Decoding + int last_bit; + int psk,last_psk; + int nrzs; + uint32_t lfsr; +}; + +class whb_demod: public demodulator { + public: + whb_demod(decoder *_dec, double spb); + void reset(void); + int demod(int thresh, int pwr, int index, int16_t *iq); + + private: + double spb; + int bitcnt; + int offset; + int timeout_cnt; + int last_i, last_q; + int last_dev; + uint64_t step; + uint64_t last_peak; + double rssi; + iir2 *iir; + iir2 *iir_avg; + int avg_of; +}; +#endif From c73b3a0d8c027ad49325d06cd1c6437a1c8d4cb6 Mon Sep 17 00:00:00 2001 From: jarau-de Date: Tue, 14 Feb 2023 12:43:15 +0100 Subject: [PATCH 2/5] Delete whb.h~ --- whb.h~ | 62 ---------------------------------------------------------- 1 file changed, 62 deletions(-) delete mode 100644 whb.h~ diff --git a/whb.h~ b/whb.h~ deleted file mode 100644 index bdb5953..0000000 --- a/whb.h~ +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef _INCLUDE_TFA5H -#define _INCLUDE_TFA5H - -#include -#include "dsp_stuff.h" -#include "decoder.h" -#include "crc32.h" - -using std::string; - -class whb_decoder: public decoder -{ - public: - whb_decoder(sensor_e type=TFA_WHB); - void store_bit(int bit); - void flush(int rssi,int offset=0); - private: - double cvt_temp(uint16_t raw, int extended=0); - void decode_02(uint8_t *msg, uint64_t id, int rssi, int offset); // temp - void decode_03(uint8_t *msg, uint64_t id, int rssi, int offset); // temp/hum - void decode_04(uint8_t *msg, uint64_t id, int rssi, int offset); // temp/hum/water - void decode_06_09(uint8_t *msg, uint64_t id, int rssi, int offset, int extended=0); // temp/hum + temp (TFA 30.3304.02, extended range 30.3302.02) - void decode_07(uint8_t *msg, uint64_t id, int rssi, int offset); // Station MA10410 (TFA 35.1147.01) - void decode_08(uint8_t *msg, uint64_t id, int rssi, int offset); // rain - void decode_0b(uint8_t *msg, uint64_t id, int rssi, int offset); // wind - void decode_10(uint8_t *msg, uint64_t id, int rssi, int offset); // door - void decode_11(uint8_t *msg, uint64_t id, int rssi, int offset); // 4 Thermo-hygro-sensors (TFA 30.3060.01) - void decode_12(uint8_t *msg, uint64_t id, int rssi, int offset); // Humidity guard/cosy radar (TFA 30.5043.01) - - uint32_t sr; - int sr_cnt; - int snum; - crc32 *crc; - - // Decoding - int last_bit; - int psk,last_psk; - int nrzs; - uint32_t lfsr; -}; - -class whb_demod: public demodulator { - public: - whb_demod(decoder *_dec, double spb); - void reset(void); - int demod(int thresh, int pwr, int index, int16_t *iq); - - private: - double spb; - int bitcnt; - int offset; - int timeout_cnt; - int last_i, last_q; - int last_dev; - uint64_t step; - uint64_t last_peak; - double rssi; - iir2 *iir; - iir2 *iir_avg; - int avg_of; -}; -#endif From 0d10fed548f9030eff13797f829b2c2f3b37a012 Mon Sep 17 00:00:00 2001 From: jarau-de Date: Tue, 14 Feb 2023 12:43:28 +0100 Subject: [PATCH 3/5] Delete decoder.cpp~ --- decoder.cpp~ | 128 --------------------------------------------------- 1 file changed, 128 deletions(-) delete mode 100644 decoder.cpp~ diff --git a/decoder.cpp~ b/decoder.cpp~ deleted file mode 100644 index 4dca9e4..0000000 --- a/decoder.cpp~ +++ /dev/null @@ -1,128 +0,0 @@ -#include -#include -#include - -#include "decoder.h" -#define __STDC_FORMAT_MACROS -#include - -//------------------------------------------------------------------------- -// mode=0 -> handle individual messages -// mode=1 -> handle summary (only one message per id) - -decoder::decoder(sensor_e _type) -{ - type=_type; - handler=NULL; - mode=0; - dbg=0; - bad=0; - synced=0; -} -//------------------------------------------------------------------------- -void decoder::set_params(char *_handler, int _mode, int _dbg) -{ - handler=_handler; - mode=_mode; - dbg=_dbg; -} -//------------------------------------------------------------------------- -void decoder::store_bit(int bit) -{ -} -//------------------------------------------------------------------------- -// Shortcut for testing -void decoder::store_bytes(uint8_t *d, int len) -{ - memcpy(rdata, d, len); - byte_cnt=len; - synced=1; -} -//------------------------------------------------------------------------- -void decoder::flush(int rssi, int offset) -{ -} -//------------------------------------------------------------------------- -void decoder::store_data(sensordata_t &d) -{ - int found=0; - /* only first appearance of id message is stored - also check for identical sequence for WHB (suppress double exec) - */ - auto ret=data.find(d.id); - - if (ret==data.end()) - data.insert(std::pair(d.id,d)); - else if (ret->second.type==TFA_WHB) { - if (ret->second.sequence==d.sequence) - found=1; - else - ret->second.sequence=d.sequence; // track sequence - } - - if (!mode && !found) - execute_handler(d); -} -//------------------------------------------------------------------------- -void decoder::execute_handler(sensordata_t &d) -{ - if (handler && strlen(handler)) { - char cmd[512]; - uint64_t nid; - if (type!=TFA_WHB) { - nid=d.id|(d.type<<24); - // t h s a r f ts - snprintf(cmd,sizeof(cmd),"%s %04" PRIx64 " %+.1f %g %i %i %i %i %li", - handler, - nid, d.temp, d.humidity, - d.sequence, d.alarm, d.rssi, - d.flags, - d.ts); - } - else { // WHB has really long IDs... - nid=d.id; - // t h s a r f ts - snprintf(cmd,sizeof(cmd),"%s %013" PRIx64 " %+.1f %g %i %i %i %i %li", - handler, - nid, d.temp, d.humidity, - d.sequence, d.alarm, d.rssi, - d.flags, - d.ts); - } - if (dbg>=1) - printf("EXEC %s\n",cmd); - (void)system(cmd); - } -} -//------------------------------------------------------------------------- -void decoder::flush_storage(void) -{ - if (!mode) - return; - map::iterator it; - it=data.begin(); - while(it!=data.end()) { - execute_handler(it->second); - it++; - } - data.clear(); -} -//------------------------------------------------------------------------- -//------------------------------------------------------------------------- -demodulator::demodulator(decoder *_dec) -{ - dec=_dec; - last_bit_idx=0; -} -//------------------------------------------------------------------------- -void demodulator::start(int len) -{ - if (last_bit_idx) // handle data wrap from last block processing - last_bit_idx-=len; -} -//------------------------------------------------------------------------- -int demodulator::demod(int thresh, int pwr, int index, int16_t *iq) -{ - return 0; -} -//------------------------------------------------------------------------- From 3c065e7b26ecbda584f0b6aed35b0ad3f69ec1e6 Mon Sep 17 00:00:00 2001 From: jarau-de Date: Tue, 14 Feb 2023 12:43:41 +0100 Subject: [PATCH 4/5] Delete decoder.h~ --- decoder.h~ | 75 ------------------------------------------------------ 1 file changed, 75 deletions(-) delete mode 100644 decoder.h~ diff --git a/decoder.h~ b/decoder.h~ deleted file mode 100644 index dc3d34b..0000000 --- a/decoder.h~ +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef _INCLUDE_DECODER_H -#define _INCLUDE_DECODER_H - -#include -#include -#include - -using std::string; -using std::map; - -enum sensor_e { - TFA_1=0, // IT+ Klimalogg Pro, 30.3180, 30.3181, 30.3199(?) - TFA_2, // IT+ 30.3143, 30.3146(?), 30.3144 - TFA_3, // 30.3155 - TX22, // LaCrosse TX22 - TFA_WHP, // 30.3306 (rain meter), pulse data - TFA_WHB, // TFA WeatherHub - FIREANGEL=0x20 // ST-630+W2 -}; - -typedef struct { - sensor_e type; - uint64_t id; - double temp; - double humidity; - int alarm; - int flags; - int sequence; - time_t ts; - int rssi; -} sensordata_t; - -class decoder -{ - public: - decoder(sensor_e _type); - void set_params(char *_handler, int _mode, int _dbg); - virtual void store_bit(int bit); - virtual void flush(int rssi, int offset=0); - virtual void store_data(sensordata_t &d); - virtual void execute_handler(sensordata_t &d); - virtual void flush_storage(void); - virtual int has_sync(void) {return synced;}; - int count(void) {return data.size();} - sensor_e get_type(void) {return type;} - virtual void store_bytes(uint8_t *d, int len); - protected: - int dbg; - int bad; - int synced; - sensor_e type; - uint8_t rdata[256]; - int byte_cnt; - private: - - char *handler; - int mode; - map data; -}; - -class demodulator -{ - public: - demodulator(decoder *_dec); - virtual void start(int len); - virtual void reset(void){}; - virtual int demod(int thresh, int pwr, int index, int16_t *iq); - - decoder *dec; - protected: - - int last_bit_idx; -}; - -#endif From 3f741710384ee38888f0526923fd1f0e44c42802 Mon Sep 17 00:00:00 2001 From: jarau-de Date: Tue, 14 Feb 2023 12:43:54 +0100 Subject: [PATCH 5/5] Delete whb.cpp~ --- whb.cpp~ | 708 ------------------------------------------------------- 1 file changed, 708 deletions(-) delete mode 100644 whb.cpp~ diff --git a/whb.cpp~ b/whb.cpp~ deleted file mode 100644 index 3e1d3dc..0000000 --- a/whb.cpp~ +++ /dev/null @@ -1,708 +0,0 @@ -#include -#include -#define __STDC_FORMAT_MACROS -#include - - -#include "whb.h" -#include "dsp_stuff.h" - -/* - TFA WeatherHub - 868.250MHz - 6000 bits/second - PSK-NRZS-G3RUH-scrambled - - RF layer: - - 4b 2d d4 2b LL II II II II II II CC CC CC CC - - 4b 2d d4 2b: Sync word - - LL: Packet length from LL to last CC (depends on type) - - II: 6 Byte ID (printed on the sensor, first byte type) - - Thanks to CRC reverse-engineering tool "reveng": http://reveng.sourceforge.net/ - CC: CRC-32, poly=0x04c11db7 init= refin=false refout=false xorout=0x00000000 - Init-value depends on type (see crc_initvals)! - - See https://github.com/sarnau/MMMMobileAlerts/blob/master/MobileAlertsGatewayBinaryUpload.markdown - for more details on the sensor payload! - - ID-Mapping: Sensor-ID gets an appended nibble like TX22 (-> 6.5byte ID!) - ID.0: temperature, humidity (0 if not available) (indoor) - ID.2: Rain sensor: tempval=rain-counter, hum=time since last pulse - ID.3: Wind sensor: tempval=speed (m/s), hum=direction (degree) - ID.4: Wind sensor: tempval=gust speed (m/s) - ID.5: Door/water-sensor: tempval=state, hum=time since last event - ID.c: temperature, humidity (outdoor, sensor #1) - ID.d: temperature, humidity (sensor #2) - ID.e: temperature, humidity (sensor #3) - - Notes: - WHB-Type 7 (MA10410/TFA 35.1147.01) has indoor and outdoor values -> mapped to ID.0 and ID.c - WHB-Type 11 (TFA 30.3060.01) has indoor and 3 other sensor values -> mapped to ID.0 and ID.c to ID.e - */ -#include -using std::map; - -map crc_initvals = { - { 0x02, 0x97d97a26}, // Only temp - { 0x03, 0xf59c5a1e}, // Temp/hum - { 0x04, 0x98e1d11f}, // Temp/hum/water - { 0x06, 0xa7a41254}, // Temp/hum + temp2 - { 0x07, 0x3303fb1d}, // Station MA10410 (TFA 35.1147.01) - { 0x08, 0x29f0f49b}, // Rain sensor (+ temp) - { 0x09, 0xa7a41254}, // Temp/hum + temp2 (TFA 30.3302.02), extended temperature range - { 0x0b, 0xe7720ae4}, // Wind sensor - { 0x10, 0x62d0afc1}, // Door sensor - { 0x11, 0x8cba0708}, // 4 Thermo-hygro-sensors (TFA 30.3060.01) - { 0x12, 0x5a9e30ae}, // Humidity guard (TFA 30.5043.01) -}; - -// Translates time units in seconds multiplier -uint32_t timeunit_tab[4]= { - 24*60*60, // day - 60*60, // hour - 60, // minutes - 1 // seconds -}; - -#define BE16(x) ( ((*(x))<<8) | (*(x+1)) ) -#define BE24(x) ( ((*(x))<<16) | ((*(x+1))<<8) | (*(x+2)) ) -#define BE32(x) ( ((*(x))<<24) | ((*(x+1))<<16) | ((*(x+2))<<8) | (*(x+3)) ) - -#define BE48(x) ( (((uint64_t)*(x))<<40) | (((uint64_t)*(x+1))<<32) | (((uint64_t)*(x+2))<<24) | \ - (((uint64_t)*(x+3))<<16) | (((uint64_t)*(x+4))<<8) | (((uint64_t)*(x+5))) ) - -//------------------------------------------------------------------------- -whb_decoder::whb_decoder(sensor_e _type) : decoder(_type) -{ - sr=0; - sr_cnt=-1; - byte_cnt=0; - snum=0; - bad=0; - crc=new crc32(0x04c11db7); -#if 0 - { - uint8_t msg[4]; - for(uint32_t i=0;i<0xffffffff;i++) { - msg[0]=i>>24; - msg[1]=i>>16; - msg[2]=i>>8; - msg[3]=i; - uint32_t crc_calc=crc->calc(msg, 4, 0); - if (crc_calc== 0x83f50b46) - printf("%x\n",i); - } - exit(0); - } -#endif - last_bit=0; - lfsr=0; - psk=last_psk=0; - nrzs=0; -} -//------------------------------------------------------------------------- -double whb_decoder::cvt_temp(uint16_t raw, int extended) -{ - if (extended==1) { - // extened range, bit 11 is actually the sign bit (range -204.7 to 204.7) - if (raw&0x800) - return -((raw^0xfff)+1)/10.0; - else - return raw/10.0; - } else { - if (raw&0x400) - return -((raw^0x7ff)+1)/10.0; - else - return raw/10.0; - } -} -//------------------------------------------------------------------------- -// Temp/hum -void whb_decoder::decode_02(uint8_t *msg, uint64_t id, int rssi, int offset) -{ - uint16_t seq=BE16(msg)&0x3fff; - uint16_t temp=BE16(msg+2)&0x7ff; - uint16_t temp_prev=BE16(msg+4)&0x7ff; - - if (dbg>=0) { - printf("WHB02 ID %" PRIx64 " TEMP %g, PTEMP %g\n", - id, cvt_temp(temp), cvt_temp(temp_prev)); - fflush(stdout); - } - sensordata_t sd; - sd.type=type; - sd.id=(id<<4LL); - sd.temp=cvt_temp(temp); - sd.humidity=0; - sd.sequence=seq; - sd.alarm=0; - sd.rssi=rssi; - sd.flags=0; - sd.ts=time(0); - store_data(sd); -} -//------------------------------------------------------------------------- -// Temp/hum -void whb_decoder::decode_03(uint8_t *msg, uint64_t id, int rssi, int offset) -{ - uint16_t seq=BE16(msg)&0x3fff; - uint16_t temp=BE16(msg+2)&0x7ff; - uint16_t hum=BE16(msg+4)&0xff; - - uint16_t temp_prev=BE16(msg+6)&0x7ff; - uint16_t hum_prev=BE16(msg+8)&0xff; - - uint16_t unknown=msg[10]; - if (dbg>=0) { - printf("WHB03 ID %" PRIx64 " TEMP %g HUM %i, PTEMP %g PHUM %i\n", - id, cvt_temp(temp), hum, cvt_temp(temp_prev), hum_prev); - fflush(stdout); - } - sensordata_t sd; - sd.type=type; - sd.id=(id<<4LL); - sd.temp=cvt_temp(temp); - sd.humidity=hum; - sd.sequence=seq; - sd.alarm=0; - sd.rssi=rssi; - sd.flags=0; - sd.ts=time(0); - store_data(sd); -} -//------------------------------------------------------------------------- -// Temp/hum/water -void whb_decoder::decode_04(uint8_t *msg, uint64_t id, int rssi, int offset) -{ - uint16_t seq=BE16(msg)&0x3fff; - uint16_t temp=BE16(msg+2)&0x7ff; - uint16_t hum=BE16(msg+4)&0xff; - uint8_t wet=*(msg+6); - - uint16_t temp_prev=BE16(msg+7)&0x7ff; - uint16_t hum_prev=BE16(msg+9)&0xff; - uint16_t wet_prev=*(msg+11); - - if (dbg>=0) { - printf("WHB04 ID %" PRIx64 " TEMP %g HUM %i WET %i, PTEMP %g PHUM %i PWET %i\n", - id, cvt_temp(temp),hum,(wet&1)^1, cvt_temp(temp_prev), hum_prev, (wet_prev&1)^1); - fflush(stdout); - } - sensordata_t sd; - sd.type=type; - sd.id=(id<<4LL); - sd.temp=cvt_temp(temp); - sd.humidity=hum; - sd.sequence=seq; - sd.alarm=0; - sd.rssi=rssi; - sd.flags=0; - sd.ts=time(0); - store_data(sd); - - sd.id=(id<<4LL)|5; - sd.temp=(wet&1)^1; - sd.humidity=0; - store_data(sd); -} -//------------------------------------------------------------------------- -// Temp/hum + temp2 -// decodes also version 9 with extended temperature range -void whb_decoder::decode_06_09(uint8_t *msg, uint64_t id, int rssi, int offset, int extended) -{ - uint16_t seq=BE16(msg)&0x3fff;; - uint16_t temp=BE16(msg+2)&0x7ff; - uint16_t temp2, temp2_prev; - int whb_type=6; - if (extended==1) { - temp2=BE16(msg+4)&0xfff; - temp2_prev=BE16(msg+10)&0xfff; - whb_type=9; - - } else { - temp2=BE16(msg+4)&0x7ff; - temp2_prev=BE16(msg+10)&0x7ff; - } - uint16_t hum=BE16(msg+6)&0xff; - uint16_t temp_prev=BE16(msg+8)&0x7ff; - uint16_t hum_prev=BE16(msg+12)&0xff; - - if (dbg>=0) { - printf("WHB0%i ID %" PRIx64 "TEMP %g HUM %i TEMP2 %g, PTEMP %g PHUM %i PTEMP2 %g\n", - whb_type, id, cvt_temp(temp),hum,cvt_temp(temp2,extended), cvt_temp(temp_prev), hum_prev, cvt_temp(temp2_prev,extended)); - fflush(stdout); - } - sensordata_t sd; - sd.type=type; - sd.id=(id<<4LL); - sd.temp=cvt_temp(temp); - sd.humidity=hum; - sd.sequence=seq; - sd.alarm=0; - sd.rssi=rssi; - sd.flags=0; - sd.ts=time(0); - store_data(sd); - - sd.id=(id<<4LL)|1; - sd.temp=cvt_temp(temp2, extended); - sd.humidity=0; - store_data(sd); -} -//------------------------------------------------------------------------- -// Station MA10410 (TFA 35.1147.01) -void whb_decoder::decode_07(uint8_t *msg, uint64_t id, int rssi, int offset) -{ - uint16_t seq=BE16(msg)&0x3fff; - uint16_t temp[4]; // 0=indoor, 2/3: previous - uint16_t hum[4]; - for(int n=0;n<4;n++) { - temp[n]=BE16(msg+2+4*n)&0x07ff; - hum[n]=BE16(msg+4+4*n)&0x0ff; - } - if (dbg>=0) { - printf("WHB07 ID %" PRIx64 " TEMP_IN %g HUM_IN %i TEMP_OUT %g HUM_OUT %i",id, cvt_temp(temp[0]), hum[0], cvt_temp(temp[1]), hum[1]); - if (dbg>1) - printf(" PTEMP_IN %g PHUM_IN %i PTEMP_OUT %g PHUM_OUT %i", cvt_temp(temp[2]), hum[2], cvt_temp(temp[3]), hum[3]); - puts(""); - fflush(stdout); - } - - sensordata_t sd; - sd.type=type; - sd.id=(id<<4LL); // ID.0 = indoor - sd.temp=cvt_temp(temp[0]); - sd.humidity=hum[0]; - sd.sequence=seq; - sd.alarm=0; - sd.rssi=rssi; - sd.flags=0; - sd.ts=time(0); - store_data(sd); - - sd.id=(id<<4LL)|0xc; // ID.c = outdoor - sd.temp=cvt_temp(temp[1]); - sd.humidity=hum[1]; - store_data(sd); -} -//------------------------------------------------------------------------- -// Rain sensor, store counter and temperature -void whb_decoder::decode_08(uint8_t *msg, uint64_t id, int rssi, int offset) -{ - uint16_t seq=BE16(msg)&0x3fff; - uint8_t event=msg[2]>>6; - uint16_t temp=BE16(msg+2)&0x07ff; // 11Bit signed, 0.1steps - double temp_real=cvt_temp(temp); - uint16_t cnt=BE16(msg+4); - uint32_t times[10]; - if (dbg>=0) { - printf("WHB08 ID %" PRIx64 " cnt %i\n",id, cnt); - fflush(stdout); - } - for(int i=0;i<10;i++) { - uint16_t x=BE16(msg+6+2*i); - times[i]=timeunit_tab[(x>>14)&3] * (x&0x3fff); - if (dbg>1) - printf("WHB08 ID %" PRIx64 " #%i time %i\n",id,i,times[i]); - } - - sensordata_t sd; - sd.type=type; - sd.id=(id<<4LL)|2; - sd.temp=cnt; - sd.humidity=times[1]; - sd.sequence=seq; - sd.alarm=0; - sd.rssi=rssi; - sd.flags=0; - sd.ts=time(0); - store_data(sd); - - sd.id=(id<<4); - sd.temp=temp_real; - sd.humidity=0; - store_data(sd); -} -//------------------------------------------------------------------------- -// Wind sensor -void whb_decoder::decode_0b(uint8_t *msg, uint64_t id, int rssi, int offset) -{ - uint32_t seq=BE24(msg); - float dir[6],speed[6],gust[6]; - uint32_t times[6]; - for(int i=0;i<6;i++) { - uint32_t v=BE32(msg+3+4*i); - dir[i]=22.5*(v>>28); // 0: N 90:E 180:S - speed[i]= (((v>>16)&0xff) + 256*((v>>25)&1))/10.0; - gust[i]= (((v>>8)&0xff) + 256*((v>>24)&1))/10.0; - times[i]=(v&0xff)*2; - if (dbg>=0 && (i==0 || dbg>0)) { - printf("WHB0b ID %" PRIx64 " #%i DIR %f SPEED %f GUST %f time %i\n", - id, i,dir[i],speed[i],gust[i],times[i]); - fflush(stdout); - } - } - sensordata_t sd; - sd.type=type; - sd.id=(id<<4LL)|3; - sd.temp=speed[0]; - sd.humidity=dir[0]; - sd.sequence=seq; - sd.alarm=0; - sd.rssi=rssi; - sd.flags=0; - sd.ts=time(0); - store_data(sd); - - sd.id=(id<<4)|4; - sd.temp=gust[0]; - sd.humidity=0; - store_data(sd); -} -//------------------------------------------------------------------------- -// Door sensor -void whb_decoder::decode_10(uint8_t *msg, uint64_t id, int rssi, int offset) -{ - uint16_t seq=BE16(msg)&0x3fff; - uint8_t state[4]; - uint32_t times[4]; - for(int i=0;i<4;i++) { - uint16_t x=BE16(msg+2+2*i); - state[i]=x>>15; // 0=closed - times[i]=timeunit_tab[(x>>13)&3] * (x&0x1fff); - if (dbg>=0 && (i==0 || dbg>0)) { - printf("WHB10 ID %" PRIx64 " #%i %i %i\n",id,i,state[i],times[i]); - fflush(stdout); - } - } - sensordata_t sd; - sd.type=type; - sd.id=(id<<4LL)|5; - sd.temp=state[0]; - sd.humidity=times[1]; - sd.sequence=seq; - sd.alarm=0; - sd.rssi=rssi; - sd.flags=0; - sd.ts=time(0); - store_data(sd); -} -//------------------------------------------------------------------------- -// 4 Thermo-hygro-sensors (TFA 30.3060.01) -void whb_decoder::decode_11(uint8_t *msg, uint64_t id, int rssi, int offset) -{ - uint16_t seq=BE16(msg)&0x3fff; - uint16_t temp[8]; // 3 = indoor, >3: previous values - uint16_t hum[8]; - for(int n=0;n<8;n++) { - temp[n]=BE16(msg+2+4*n)&0x07ff; - hum[n] =BE16(msg+4+4*n)&0xff; - } - - if (dbg>=0) { - printf("WHB11 %" PRIx64 " TEMP1 %g HUM1 %i TEMP2 %g HUM2 %i TEMP3 %g HUM3 %i TEMP_IN %g HUM_IN %i", - id, cvt_temp(temp[0]),hum[0], cvt_temp(temp[1]),hum[1], cvt_temp(temp[2]),hum[2], cvt_temp(temp[3]),hum[3]); - if (dbg>1) - printf(" PTEMP1 %g PHUM1 %i PTEMP2 %g PHUM2 %i PTEMP3 %g PHUM3 %i PTEMP_IN %g PHUM_IN %i", - cvt_temp(temp[4]),hum[4], cvt_temp(temp[5]),hum[5], cvt_temp(temp[6]),hum[6], cvt_temp(temp[7]),hum[7]); - puts(""); - fflush(stdout); - } - sensordata_t sd; - sd.type=type; - sd.id=(id<<4LL); - sd.temp=cvt_temp(temp[3]); - sd.humidity=hum[3]; - sd.sequence=seq; - sd.alarm=0; - sd.rssi=rssi; - sd.flags=0; - sd.ts=time(0); - store_data(sd); - - for(int n=0;n<3;n++) { - sd.id=(id<<4LL)|(0xc+n); - sd.temp=cvt_temp(temp[n]); - sd.humidity=hum[n]; - store_data(sd); - } -} -//------------------------------------------------------------------------- -// Humidity guard -void whb_decoder::decode_12(uint8_t *msg, uint64_t id, int rssi, int offset) -{ - uint16_t seq=BE16(msg)&0x3fff; - uint16_t hum[5]; - hum[0]=msg[8]&0x7f; - hum[1]=msg[2]&0x7f; - hum[2]=msg[3]&0x7f; - hum[3]=msg[4]&0x7f; - hum[4]=msg[5]&0x7f; - uint16_t temp=BE16(msg+6)&0x7ff; - - if (dbg>=0) { - printf("WHB12 %" PRIx64 " TEMP %g HUM %i HUM3h %i HUM24h %i HUM7d %i HUM30d %i\n", - id, cvt_temp(temp), hum[0], hum[1], hum[2], hum[3], hum[4]); - fflush(stdout); - } - sensordata_t sd; - sd.type=type; - sd.id=(id<<4LL); - sd.temp=cvt_temp(temp); - sd.humidity=hum[0]; - sd.sequence=seq; - sd.alarm=0; - sd.rssi=rssi; - sd.flags=0; - sd.ts=time(0); - store_data(sd); - - // Store average humidity as sub ids 1 c d e - sd.id=(id<<4LL)+1; - sd.temp=0; - sd.humidity=hum[1]; - store_data(sd); - for(int n=0;n<3;n++) { - sd.id=(id<<4LL)+0xc+n; - sd.humidity=hum[2+n]; - store_data(sd); - } -} -//------------------------------------------------------------------------- -void whb_decoder::flush(int rssi, int offset) -{ - uint32_t crc_calc=0; - uint32_t crc_val=0; - int plen; - uint32_t stype; - - if (byte_cnt<11 || byte_cnt>60) // Sanity - goto reset; - - // FIXME: byte count usually 2-3 bytes longer than real payload - if (dbg && byte_cnt) { - printf("#%03i %u L=%i ",snum++,(uint32_t)time(0), byte_cnt); - for(int n=0;n60) - goto bad; - - stype=rdata[5]; // sensor type - - if (crc_initvals.find(stype)==crc_initvals.end()) { - if (dbg>=0) - printf("WHB: Probably unsupported sensor type %02x! Please report\n",stype); - goto bad; - } - - crc_calc=crc->calc(&rdata[4], plen-4, crc_initvals[stype]); - crc_val=(rdata[plen]<<24) | (rdata[plen+1]<<16) | (rdata[plen+2]<<8) | rdata[plen+3]; - - if (crc_calc==crc_val) { - uint64_t id=BE48(&rdata[5]); - uint8_t *msg=&rdata[4+1+6]; - switch(stype) { - case 0x02: - decode_02(msg, id, rssi, offset); - break; - case 0x03: - decode_03(msg, id, rssi, offset); - break; - case 0x04: - decode_04(msg, id, rssi, offset); - break; - case 0x06: - decode_06_09(msg, id, rssi, offset); - break; - case 0x07: - decode_07(msg, id, rssi, offset); - break; - case 0x08: - decode_08(msg, id, rssi, offset); - break; - case 0x09: - decode_06_09(msg, id, rssi, offset, 1); - break; - case 0x0b: - decode_0b(msg, id, rssi, offset); - break; - case 0x10: - decode_10(msg, id, rssi, offset); - break; - case 0x11: - decode_11(msg, id, rssi, offset); - break; - case 0x12: - decode_12(msg, id, rssi, offset); - break; - } - goto reset; - } - bad: - bad++; - if (dbg) { - if (crc_val!=crc_calc) - printf("\nWHB BAD %i RSSI %i (CRC is %08x, should be %08x, len %i, plen %i)\n", - bad, rssi, crc_val,crc_calc, byte_cnt,plen); - else - printf("\nWHB BAD %i RSSI %i (SANITY)\n",bad,rssi); - } - reset: - sr_cnt=-1; - sr=0; - byte_cnt=0; - synced=0; -} -//------------------------------------------------------------------------- -void whb_decoder::store_bit(int bit) -{ - // De-PSK - if (bit == last_bit) - psk=1-psk; - // De-NRZS - if (psk == last_psk) - nrzs=1-nrzs; - - last_bit=bit; - last_psk=psk; - - // G3RUH descrambler - int bit_descrambled=nrzs ^ ((lfsr>>16)&1) ^ ((lfsr>>11)&1); - lfsr=(lfsr<<1)|nrzs; - - sr=(sr>>1)|(bit_descrambled<<31); - - if ( ((sr&0xffffffff)==0x2bd42d4b) ) { // FIXME 3 or 4 bytes? - //printf("######################### SYNC\n"); - synced=1; - sr_cnt=0; - rdata[0]=sr&0xff; - rdata[1]=(sr>>8)&0xff; - rdata[2]=(sr>>16)&0xff; - byte_cnt=3; - } - - if (sr_cnt==0) { - if (byte_cnt<(int)sizeof(rdata)) { - rdata[byte_cnt]=(sr>>24)&0xff; - } - //printf(" %i %02x\n",byte_cnt,rdata[byte_cnt]); - byte_cnt++; - } - if (sr_cnt>=0) - sr_cnt=(sr_cnt+1)&7; -} -//------------------------------------------------------------------------- -whb_demod::whb_demod(decoder *_dec, double _spb) : demodulator( _dec) -{ - spb=_spb; - timeout_cnt=0; - reset(); - iir=new iir2(2.0/spb); // Pulse filter - iir_avg=new iir2(0.0025/spb); // Phase discriminator filter - printf("WHB: Samples per bit: %.1f\n",spb); - last_dev=0; -} -//------------------------------------------------------------------------- -void whb_demod::reset(void) -{ - offset=0; - bitcnt=0; - last_peak=0; - rssi=0; - step=last_peak=0; -} -//------------------------------------------------------------------------- -//#define DBG_DUMP -#if DBG_DUMP -// More debugging -static FILE *fx=NULL; -static FILE *fy=NULL; -static int fc=0; -#endif -int whb_demod::demod(int thresh, int pwr, int index, int16_t *iq) -{ - int triggered=0; - - if (pwr>thresh) { - if (!timeout_cnt) { - reset(); - } - - timeout_cnt=8*spb; - } - - if (timeout_cnt) { - triggered++; - int dev; - - /* Shaped PSK of AX5031 causes hard drop at phase changes for fm_dev_nrzs() - -> detected minima are 0s, fillup with 1s since last 0 - */ - dev=fm_dev_nrzs(iq[0],iq[1],last_i,last_q); - dev=iir->step(dev); // reduce noise - if (!dec->has_sync()) - avg_of=iir_avg->step(0.5*dev); // decision value for phase change - - int bit=0; - timeout_cnt--; - - int tdiff=step-last_peak; - - // Phase change? - if (devlast_dev && - (tdiff>3*spb/4) ) { - bit=avg_of; - dec->store_bit(0); - bitcnt++; - int bit0=(tdiff+spb/2)/spb; - for(int n=1;nstore_bit(1); - bitcnt++; - } - last_peak=step; - } - last_dev=dev; - - if (dec->has_sync()) - rssi+=(iq[0]*iq[0]+iq[1]*iq[1]); - -#ifdef DBG_DUMP - // plot "blub" using 1:2 with lines,"blub" using 1:3 with boxes - if (!fx) - fx=fopen("blub","w"); - if (!fy) - fy=fopen("blub1","w"); - if (fx) - fprintf(fx,"%i %i %i %i\n",fc,dev, bit, tdiff_mod*10); - fc++; -#endif - - if (!timeout_cnt) { - // Flush descrambler - if (dec->has_sync()) { - for(int n=0;n<16;n++) - dec->store_bit(0); - dec->flush(10*log10(1+rssi/4000),offset); // scale to rougly match with TFA_1-RSSI - } - reset(); - rssi=0; - } - } - last_i=iq[0]; - last_q=iq[1]; - - step++; - return triggered; -} -//-------------------------------------------------------------------------