Krótkie wprowadzenie
Artykułem tym chciałbym się z Wami podzielić tym jak udało mi się odczytać przy pomocy Arduino UNO R3 wartości temperatury i wilgotności wysyłane przez bezprzewodową sondę.
Od jakiegoś czasu zajmuję się monitorowaniem warunków (temperatura, wilgotność) w mieszkaniu i na zewnątrz. Celem moim jest zbudowanie jeszcze przed tą zimą inteligentnego systemu sterowania ogrzewaniem i wentylacją w mieszkaniu.
Jestem w posiadaniu stacji pogodowej (mniej więcej takiej) z bezprzewodową sondą temperatury i wilgotności. Postanowiłem nauczyć się odczytywać jej wskazania i przesyłać je do komputera.
Zapytanie do producenta
Pierwszą rzeczą jaką postanowiłem zrobić to zapytać producenta o protokół jakim przesyłane są te wartości. Początek był obiecujący. Miły pan wprawdzie nie wiedział nic więcej ponad to, że sonda nadaje na częstotliwości 433 MHz, ale obiecał się dowiedzieć. Skończyło się jednak na mailowej odpowiedzi o treści “Tak, pytałem u źródła, niestety, nie da się tak zrobić, aby czujka sczytywała do innego urządzenia niż nasza stacja bazowa.”
Oczywiście nie uwierzyłem :)
Pytanie na majsterkowo.pl
Postanowiłem poszukać pomocy gdzie indziej, a mianowicie na portalu https://majsterkowo.pl. Zadałem pytanie w wątku https://majsterkowo.pl/forum/arduino-i-bezprzewodowy-czujnik-temperatury-i-wilgotnosci-t816.html licząc na pomoc społeczności. Jednak jedyna odpowiedź jaką dostałem brzmiała “W opisie jest napisane z jaką stacją to współpracuje. Do pracy z Arduino tego raczej nie zmusisz, producent na pewno zadbał o odpowiednie kodowanie sygnału.”
Nie zabrzmiało to zbyt zachęcająco, ale oczywiście tym bardziej w to nie uwierzyłem ;)
Kupno odbiornika RF 433
W międzyczasie zakupiłem między innymi nadajnik i odbiornik RF 433 MHz.
Analiza sygnału
Kupno analizatora
Buszując gdzieś po sieci natrafiłem na pojęcie “analizatora stanów logicznych”. Pomyślałem, że właśnie tego potrzebuję.
W sieci znalazłem bardzo bogatą ofertę tego typu urządzeń od takich za wiele tysięcy złotych (np http://www.conrad.pl/websale7/?Ctx={ver%2f7%2fver}{st%2f3ec%2fst}{cmd%2f0%2fcmd}{m%2fwebsale%2fm}{s%2fconrad%2fs}{l%2f01-aa%2fl}{p1%2fd2f6e56f669660582c6750d86eaf57b8%2fp1}{md5%2f5ffac58ff15c6a24fcf953c797b8c9b3%2fmd5}&act=product&prod_index=123271&cat_index=SHOP_AREA_17630_1114018&otp1=tablink2) do bardzo prostych i tanich podłączanych bezpośrednio do portu LPT (drukarkowego) w komputerze.
Ostatecznie zdecydowałem się na takie urządzenie:
Analizator ten ma 8 kanałów, częstotliwość odczytu do 24 MHz i … kosztuje tylko 57 PLN :)
Znalazłem go na Allegro.
Do tego analizatora należy pobrać oprogramowanie ze strony http://www.saleae.com/.
Odczyt sygnałów
Aby odczytać nadawane przez sondę sygnały postanowiłem podłączyć analizator bezpośrednio do nadajnika RF wewnątrz sondy. Po otwarciu sondy wnętrze przedstawia się następująco
Następnie rozpocząłem pomiar. Ustawiłem częstotliwość próbkowania na 1 MHz, wyłączyłem wszystkie kanały poza pierwszym. Włączyłem dla niego opcję “Require rising edge transition”, która powoduje, że pomiar danych rozpocznie się dopiero kiedy sygnał na wejściu zmieni się z niskiego na wysoki i nacisnąłem “Start”.
Po jakimś czasie (sonda nadaje co ok 50-56 sekund – więcej o tym później) na ekranie ujrzałem taki obrazek:
Widać na nim, że odczytane zostały dane, że nadawanie trwało trochę ponad 0,8s.
Po powiększeniu początku danych zobaczyłem:
Widać, że sonda nadaje krótki “piki” (stan wysoki), a pomiędzy nimi przerwy.
Stan wysoki zawsze trwa ok 560μs, a przerwy pomiędzy nimi 8640, 3800 i 1900μs. Przerwa 8640 μs występuje na samym początku transmisji, na samym końcu oraz tylko kilka razy w środku. Przerwy 3800 i 1900 o wiele częściej i są pomieszane. W tym momencie wydawało mi się, że 8640 to jakiś sygnał synchronizująco-rozdzielający, a te dwie pozostałe przerwy oznaczają bity o wartościach 0 i 1.
Czas to sprawdzić.
Analiza w arkuszu kalkulacyjnym
Analizę danych postanowiłem przeprowadzić w LibreOffice Calc (dla niewtajemniczonych to taki darmowy odpowiednik Excela ;) ).
Po wybraniu w oprogramowaniu analizatora opcji “Options/Export Data” (Ctrl+E) pojawia się okienko
Po wybraniu opcji jak na obrazku na dysku utworzony został plik z następującymi danymi – poniżej początkowy fragment:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Time[s], czujnik 13.539262, 0 23.539262, 1 23.53983, 0 23.548476, 1 23.549044, 0 23.552861, 1 23.553429, 0 23.555343, 1 23.555911, 0 23.557816, 1 23.558384, 0 23.562232, 1 ... |
W pierwszej kolumnie jest czas mierzony w sekundach, w drugiej kolumnie jest wartość sygnału jaka się pojawiła na wejściu w tym czasie.
Zaimportowałem te dane do arkusza i dodałem dwie kolejne kolumny: czas i stan.
W kolumnie czas wpisałem wszędzie formułę (dla komórki C3) =1000*(A4-A3). Dzięki temu otrzymałem czas w milisekundach trwania danego stanu.
Widać ponownie, że stan wysoki zawsze trwa ok 560μs, a stan niski 8640, 3800 i 1900μs. W czwartej kolumnie wpisuję regułę, które dla przerwy równej 3800μs wylicza, że to 1, a dla 1900μs wylicza, że to 0. Dla 8640μs wypisuje wartość “dystans”. Oto przykładowe dane:
Time[s] | SCIO | czas | analiza |
0 | 0 | ||
11,35824 | 1 | 0,56 | |
11,3588 | 0 | 8,64 | dystans |
11,36744 | 1 | 0,56 | |
11,368 | 0 | 3,8 | 1 |
11,3718 | 1 | 0,56 | |
11,37236 | 0 | 1,88 | 0 |
11,37424 | 1 | 0,56 | |
11,3748 | 0 | 1,92 | 0 |
11,37672 | 1 | 0,56 | |
11,37728 | 0 | 3,84 | 1 |
Na tym poziomie analizy zauważyłem, że podczas pojedynczej transmisji 6 razy przesyłanych jest 37 bitów. Za każdym razem takich samych. Serie 37 bitów rozdzielane są “dystansami”.
Aby przeanalizować wartości tych bitów wykonałem następujące czynności. Najpierw wykorzystując autofiltr przeniosłem do drugiego arkusza taką paczkę 37 bitów do pierwszej kolumny. W kolejnych kolumnach zapisałem następujące formuły, które miały następujące znaczenie.
Kol. | formuła | znaczenie |
B | =1-A2 | odwrotny bit (nie wiem czy długa przerwa to bit czy krótka) |
C | =A2+2(A3+2(A4+2(A5+2(A6+2(A7+2(A8+2*A9)))))) | wartość bajtu zaczynającego się od tego bitu przy założeniu, że: długa przerwa to 1, a krótka to 0, najpierw jest najmłodszy bit a potem coraz starsze. |
D | =B2+2(B3+2(B4+2(B5+2(B6+2(B7+2(B8+2*B9)))))) | wartość bajtu zaczynającego się od tego bitu przy założeniu, że: długa przerwa to 0, a krótka to 1, najpierw jest najmłodszy bit a potem coraz starsze. |
E | =A9+2(A8+2(A7+2(A6+2(A5+2(A4+2(A3+2*A2)))))) | wartość bajtu zaczynającego się od tego bitu przy założeniu, że: długa przerwa to 1, a krótka to 0, najpierw jest najstarszy bit a potem coraz młodsze. |
F | =B9+2(B8+2(B7+2(B6+2(B5+2(B4+2(B3+2*B2)))))) | wartość bajtu zaczynającego się od tego bitu przy założeniu, że: długa przerwa to 0, a krótka to 1, najpierw jest najstarszy bit a potem coraz młodsze. |
Dla następujących danych:
- kanału nr 3
- temperatury 24,1 stopnia Celsjusza
- wilgotności 43%
tabela przedstawiała się następująco:
A | B | C | D | E | F |
1 | 0 | 137 | 118 | 145 | 110 |
0 | 1 | 68 | 187 | 34 | 221 |
0 | 1 | 162 | 93 | 69 | 186 |
1 | 0 | 209 | 46 | 139 | 116 |
0 | 1 | 104 | 151 | 22 | 233 |
0 | 1 | 52 | 203 | 44 | 211 |
0 | 1 | 26 | 229 | 88 | 167 |
1 | 0 | 141 | 114 | 177 | 78 |
0 | 1 | 70 | 185 | 98 | 157 |
1 | 0 | 35 | 220 | 196 | 59 |
1 | 0 | 17 | 238 | 136 | 119 |
0 | 1 | 8 | 247 | 16 | 239 |
0 | 1 | 4 | 251 | 32 | 223 |
0 | 1 | 130 | 125 | 65 | 190 |
1 | 0 | 193 | 62 | 131 | 124 |
0 | 1 | 224 | 31 | 7 | 248 |
0 | 1 | 240 | 15 | 15 | 240 |
0 | 1 | 120 | 135 | 30 | 225 |
0 | 1 | 60 | 195 | 60 | 195 |
0 | 1 | 30 | 225 | 120 | 135 |
1 | 0 | 143 | 112 | 241 | 14 |
1 | 0 | 71 | 184 | 226 | 29 |
1 | 0 | 35 | 220 | 196 | 59 |
1 | 0 | 145 | 110 | 137 | 118 |
0 | 1 | 72 | 183 | 18 | 237 |
0 | 1 | 164 | 91 | 37 | 218 |
0 | 1 | 82 | 173 | 74 | 181 |
1 | 0 | 169 | 86 | 149 | 106 |
0 | 1 | 212 | 43 | 43 | 212 |
0 | 1 | 106 | 149 | 86 | 169 |
1 | 0 | ||||
0 | 1 | ||||
1 | 0 | ||||
0 | 1 | ||||
1 | 0 | ||||
1 | 0 | ||||
0 | 1 |
Na żółto zaznaczyłem te bajty gdzie widać pewne interesujące dane. W ostatniej żółtej komórce widać wilgotność, a w przedostatniej temperaturę pomnożoną przez 10. Pierwsze dwie zaznaczyłem na żółto bo to po prostu wcześniejsze dwa bajty. Że to ta kolumna to utwierdziłem się po kilku kolejnych pomiarach dla różnych wartości temperatury i wilgotności.
Powstały oczywiście pytania:
- czy na wilgotność potrzeba aż 8 bitów? skoro jej wartości są od 0-99 to wystarczy 7.
- na ilu bitach i w jaki sposób kodowana jest temperatura, ten jeden bajt wystarczy na temperatury od 0.0 do 25.5 stopni Celsjusza, a ma wystarczyć na jakieś -50.0 do 70.0 czyli 1200 wartości więc co najmniej 11 (w praktycznie 12) bitów
Po kolejnych pomiarach i próbach, których nie będę tu szczegółowo opisywał (było ich ok 10) doszedłem do następujących wniosków:
licznik | bity | wartości | komentarz |
1 | 1 | 9 | jakieś 4 bity |
2 | 0 | ||
3 | 0 | ||
4 | 1 | ||
5 | 1 | 219 | losowe id |
6 | 1 | ||
7 | 0 | ||
8 | 1 | ||
9 | 1 | ||
10 | 0 | ||
11 | 1 | ||
12 | 1 | ||
13 | 0 | 0 | jakieś 2 bity |
14 | 0 | ||
15 | 0 | 1 | kanał (od 0) |
16 | 1 | ||
17 | 0 | 332 | temperatura |
18 | 0 | ||
19 | 0 | ||
20 | 1 | ||
21 | 0 | ||
22 | 1 | ||
23 | 0 | ||
24 | 0 | ||
25 | 1 | ||
26 | 1 | ||
27 | 0 | ||
28 | 0 | ||
29 | 0 | 25 | wilgotność |
30 | 0 | ||
31 | 0 | ||
32 | 1 | ||
33 | 1 | ||
34 | 0 | ||
35 | 0 | ||
36 | 1 | ||
37 | 0 | 0 | jakiś bit |
Oto znaczenie kolejnych sekcji:
- jakieś 4 bity – te bity ani razu nie zmieniły swojej wartości
- losowe id – wg dokumentacji po każdej zmianie baterii konieczne jest ponowne synchronizacja stacji bazowej z sondą ponieważ sonda zaczyna wysyłać nowe, losowe id – to prawdopodobnie te bity
- jakieś 2 bity – nigdy się nie zmieniły
- kanał (od 0) – w sondzie można wybrać kanał, jego wartość wysyłana jest właśnie na tych dwóch bitach
- temperatura – zakładam, że wysyłana jest na tych 12 bitach jako liczba całkowita będąca wartością temperatury pomnożoną przez 10. Podejrzewam, że najstarszy bit to bit znaku, ale nie robiłem jeszcze testów dla temperatur ujemnych
- wilgotność – jest przechowywana jako liczba całkowita na tych bitach. Możliwe, że nie na 8 od 29 bitu, a tylko na 7 od 30 bitu, a bit 29 jest do czegoś innego.
- jakiś bit – jego wartość nigdy się nie zmieniła.
Na tym etapie postanowiłem przerwać analizę danych i zająć się budową odbiornika, który będzie bezprzewodowo je odczytywał. W przyszłości podłączę go mojego systemu monitoringu temperatury zacznę prowadzić dalsze badania.
Wysłanie samemu danych do odbiornika
Aby przetestować poprawność rozpracowanych danych postanowiłem zmontować układ do nadawania i przeprowadzić próbę nadania temperatury i wilgotności do stacji bazowej czyli udawanie sondy.
Napisałem następujący programik:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 #include <LiquidCrystal.h>#define RF_DATA_PIN 9#define LED_PIN 10#define UP_TIME 560#define SEPARATOR_TIME 8640#define ZERO_TIME 1900#define ONE_TIME 3800LiquidCrystal lcd(2, 3, 4, 5, 6, 7);int BITS [37] ={1,0,0,1,0,0,0,1,0,1,1,0,0,0,1,0,0,0,0,0,1,1,1,1,0,0,0,1,0,0,1,0,1,0,1,1,0};void <b>setup</b> (){pinMode(RF_DATA_PIN, OUTPUT);pinMode(LED_PIN, OUTPUT);lcd.begin(16, 2);}// nadaje wysoki poziom przez określony czasvoid writeOne(){digitalWrite(RF_DATA_PIN, HIGH);delayMicroseconds(UP_TIME);digitalWrite(RF_DATA_PIN, LOW);}// nadaje wysoki poziom, a potem czeka określony czas// z niskim poziomemvoid writeOneTick(unsigned int microsTime){writeOne();delayMicroseconds(microsTime);}void writeAllData (){// sonda tego nie nadaje,// ale to dla odbiornika żeby zdążył się wygrzaćfor (int i = 0; i < 30; i++)writeOneTick(UP_TIME);writeOneTick(SEPARATOR_TIME);// 6 razy nadajemyfor (int i = 0; i < 6; i++){// paczkę 37 bitówfor (int j = 0; j < 37; j++){if (BITS [j])writeOneTick(ONE_TIME);elsewriteOneTick(ZERO_TIME);}writeOneTick(SEPARATOR_TIME);}writeOne();}// za każdym razem nadaję inną wilgotność// ta zmienna przechowuje jej wartośćint humidity = 50;void <b>loop</b> (){// zapalam diodę aby widzieć kiedy trwa nadawaniedigitalWrite(LED_PIN, HIGH);// na wyświetlaczu LCD wypisuję// wartość wilgotnościlcd.setCursor(0, 1);lcd.print(humidity);lcd.print(" ");// ustawienie bitów w tablicy aby// aby wilgotność była wysłanaint h = humidity;for (int i = 0; i < 8; i++){BITS [35 - i] = h % 2;h = h / 2;}// wysłanie danychwriteAllData();// zwiększenie wilgotnościhumidity = humidity == 99 ? 0 : humidity + 1;// zgaszenie diodydigitalWrite(LED_PIN, LOW);// odczekanie odpowiedniego czasu do następnej// transmisji// kanał 1 - 50 s w sumie// kanał 2 - 53 s w sumie// kanał 3 - 56 s w sumiedelay(55300);}
Wszystko zadziałało właściwie od razu. Jedyny problem jaki napotkałem to odstępy pomiędzy nadawaniem kolejnych pomiarów. Okazało się, że odstęp ten jest różny na różnych kanałach i tak:
- na kanale 1 wynosi on 50s
- na kanale 2 wynosi on 53s
- na kanale 3 wynosi on 56s
Dokładnie co taki okres czasu stacja bazowa spodziewa się kolejnego nadawania i oczekuje na niego przez krótki okres czasu. Prawdopodobnie aby oszczędzać baterię.
Odczyt danych
Odczyt danych okazał się najtrudniejszą częścią, a to za sprawą dużej ilości błędów.
Po pierwsze kiedy sonda nie nadaje to odbiornik i tak wychwytuje “coś” z powietrza i ciągle na jego wyjściu stan się zmienia. Kiedy sonda zaczyna nadawać to odbiornik chwilę potrzebuje żeby się “dostroić” do mocy sygnału i pierwsze bity są uszkodzone często.
Poza tym okazało się, że czułość odbiornika jest niewielka i już przy odległości rzędu metra liczba niedokładnych odczytów mocno rośnie.
Pierwsze podejście
Pierwsza wersja programu do odczytu wyglądała następująco:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 // ustawienie tej definicji powoduje, że program// wypisuje na rs232 różne informacje diagnostyczne#define DEBUG9// ustawienie tej definicji powoduje, że program// po dokonaniu pomiaru ustawia na chwilę stan wysoki// na jednym z wyjść, podłączony tam analizator// stanów logicznych zaczyna wtedy pomiar// ponieważ posiada on bufor na jakiś czas przed// rozpoczęciem pomiwaru więc można przeanalizować// co pojawiło się na wyjściu z odbiornika RDF#define DEBUG_SALAE1// wyjśącie odbiornika RDF#define RF_DATA_PIN 11// wyjście z diodą świecącą#define LED_PIN 13// czasy stanu wyokiego i przerw#define UP_TIME 560#define SEPARATOR_TIME 8640#define ZERO_TIME 1900#define ONE_TIME 3800// maksymalny czas transmisji w ms// obliczony przy założeniu, że są same jedynki wysylane// (czyli długie stany niskie)#define MAX_TRANSMISION_TIME ((6L * (37L * (UP_TIME + ONE_TIME) + UP_TIME + SEPARATOR_TIME) + UP_TIME) / 1000L)// makra sprawdzające czy czas mieści się w buforze#define CHK_T(CT,T) (((CT) >= ((T) - 50)) && ((CT) <= ((T) + 50)))#define CHK_T2(CT,T) (((CT) >= ((T) - 250)) && ((CT) <= ((T) + 250)))// liczba bitów jednej wiadomości i liczba// paczek, które są nadawane#define NUM_OF_BITS 37#define NUM_OF_SERIES 6// specjalna wartość oznaczająca, że odczyt był błędny#define BAD_VALUE 0b1000000000000000// bufor na bityint8_t bits [NUM_OF_BITS];// za każdym z 6 pomiarów na określonej pozycji mogę zmierzyć// 0 lub 1 (na skutek błędów transmisji)// jeżeli zmierzę 1 to zwiększam tę wartość,// a jeśli 0 to zmniejszamint8_t bit_adds [NUM_OF_BITS];// za każdym poprawnym pomiarem zwiększam tę wartość o 1uint8_t bit_measures [NUM_OF_BITS];// licznik pomiarów całej paczkiint8_t measure_number;uint32_t last_report_time = 0;uint32_t last_pulse_time;#ifdef DEBUG9uint16_t arr_bit_times [NUM_OF_SERIES][NUM_OF_BITS][3];#define PRE_DEBUG_BUF_SIZE 10uint16_t arr_pre_buf_times [PRE_DEBUG_BUF_SIZE][3];int pre_buf_counter;#endif#ifdef DEBUG_SALAE1#define DEBUG_SALAE1_PIN 6#endifuint32_t getvalue(int bitStart, int bitNum){uint32_t ret = 0;for (int i = 0; i < bitNum; i++){if (bits [bitStart + i] > 1)return BAD_VALUE;ret *= 2;ret += bits [bitStart + i];}return ret;}void setup() {Serial.begin(115200);pinMode(RF_DATA_PIN, INPUT);pinMode(LED_PIN, OUTPUT);#ifdef DEBUG_SALAE1pinMode(DEBUG_SALAE1_PIN, OUTPUT);#endifSerial.print(F("Odczyt sondy v1 - max transmision time = "));Serial.println(MAX_TRANSMISION_TIME);}void loop(){// czyszczę wszystkie zmienne na początkufor (int i = 0; i < NUM_OF_BITS; i++){bit_adds [i] = 0;bit_measures [i] = 0;#ifdef DEBUG9for (int j = 0; j < NUM_OF_SERIES; j++){arr_bit_times [j][i][0] = 0;arr_bit_times [j][i][1] = 0;arr_bit_times [j][i][2] = 0;}#endif}measure_number = 0;#ifdef DEBUG9for (int i = 0; i < PRE_DEBUG_BUF_SIZE; i++){arr_pre_buf_times [i][0] = 0;arr_pre_buf_times [i][1] = 0;arr_pre_buf_times [i][2] = 0;}pre_buf_counter = 0;#endifuint32_t highTime, time, lowTime;// czekam na prawidłowy pierwszy pulse highlast_pulse_time = micros();while (true){highTime = pulseIn(RF_DATA_PIN, HIGH);// ignoruję takie krótkie pulsy tak jakby ich nie było// wynika to ze sprzętowych błędów odczytuif (highTime < 350) continue;time = micros();lowTime = time - last_pulse_time - highTime;last_pulse_time = time;#ifdef DEBUG9arr_pre_buf_times [pre_buf_counter][0] = lowTime;arr_pre_buf_times [pre_buf_counter][1] = highTime;arr_pre_buf_times [pre_buf_counter][2] = millis();pre_buf_counter = (pre_buf_counter + 1) % PRE_DEBUG_BUF_SIZE;#endifif (CHK_T (highTime, UP_TIME) && CHK_T2 (lowTime, SEPARATOR_TIME)){break;}}int bit_number = 0;// wykonuję sześć pomiarówwhile (measure_number < NUM_OF_SERIES){// odczytuję pulsehighTime = pulseIn(RF_DATA_PIN, HIGH);// ignoruję takie krótkie pulsy tak jakby ich nie było// wynika to ze sprzętowych błędów odczytuif (highTime < 350) continue;time = micros();// zbyt długie traktuję jako krótki który się zlał z normalnymif (highTime > UP_TIME){highTime = UP_TIME;}lowTime = time - last_pulse_time - highTime;last_pulse_time = time;#ifdef DEBUG9if (bit_number < NUM_OF_BITS){arr_bit_times [measure_number][bit_number][0] = lowTime;arr_bit_times [measure_number][bit_number][1] = highTime;arr_bit_times [measure_number][bit_number][2] = millis();}#endif// sprawdzam czy pulse ma dobry czasif (!CHK_T (highTime, UP_TIME)){// i jeśli nie to koniec//Serial.println(T("1"));break;}// sprawdzam czy to znacznik końca seriiif (CHK_T2 (lowTime, SEPARATOR_TIME)){// sprawdzam czy została odpowiednia liczba bitów odczytanaif (bit_number <= NUM_OF_BITS){measure_number++;bit_number = 0;}else{// została odczytana zła liczba bitówSerial.print(F("2 - "));Serial.println(bit_number);break;}}// sprawdzam czy kolejny bit został odczytanyelse if (CHK_T2 (lowTime, ZERO_TIME)){if (bit_number >= NUM_OF_BITS){break;}bit_adds [bit_number]--;bit_measures [bit_number]++;bit_number++;}else if (CHK_T2 (lowTime, ONE_TIME)){if (bit_number >= NUM_OF_BITS){break;}bit_adds [bit_number]++;bit_measures [bit_number]++;bit_number++;}else{// to nieprawidłowy bitbreak;}}// jeżeli choć jeden pomiar się udał wypisuję raportif (measure_number > 0){#ifdef DEBUG_SALAE1digitalWrite(DEBUG_SALAE1_PIN, HIGH);delay(10);digitalWrite(DEBUG_SALAE1_PIN, LOW);#endiffor (int i = 0; i < NUM_OF_BITS; i++){if (bit_adds[i] > (bit_measures[i] / 2)){bits[i] = 1;}else if (bit_adds[i] < (-bit_measures[i] / 2)){bits[i] = 0;}else{bits[i] = 2;}}uint32_t nChannel = getvalue (14, 2);uint32_t nTemp = getvalue (16, 12);uint32_t nHumidity = getvalue (28, 8);Serial.print(F("soft: v1 channel: "));if (nChannel != BAD_VALUE)Serial.print(nChannel + 1);elseSerial.print(F("???"));Serial.print (F(", temperature: "));if (nTemp != BAD_VALUE)Serial.print(nTemp / 10.0);elseSerial.print(F("???"));Serial.print (F(" *C, humidity: "));if (nHumidity != BAD_VALUE)Serial.print(nHumidity);elseSerial.print(F("???"));Serial.print(F(" %, Num of measures: "));Serial.println(measure_number);//wypisuję liczbę pomiarów dla bitówSerial.print(F("lb: "));for (int i = 0; i < NUM_OF_BITS; i++){Serial.print(F(" "));Serial.print(bit_measures[i]);Serial.print(F(","));}Serial.println();Serial.print(F("sb: "));for (int i = 0; i < NUM_OF_BITS; i++){if (bit_adds[i] >= 0){Serial.print(F(" "));}Serial.print(bit_adds[i]);Serial.print(F(","));}Serial.println();Serial.print(F("b: "));for (int i = 0; i < NUM_OF_BITS; i++){Serial.print(F(" "));if (bits[i] > 1)Serial.print(F("?"));elseSerial.print(bits[i]);Serial.print(F(","));}Serial.println();#ifdef DEBUG9for (int i = 0; i < PRE_DEBUG_BUF_SIZE; i++){Serial.print(F("pre "));Serial.print(i);Serial.print(F("-"));Serial.print(pre_buf_counter);Serial.print(F(": "));Serial.print(F("("));Serial.print(arr_pre_buf_times [pre_buf_counter][2]);Serial.print(F(", "));Serial.print(arr_pre_buf_times [pre_buf_counter][0]);Serial.print(F(", "));Serial.print(arr_pre_buf_times [pre_buf_counter][1]);Serial.print(F("), "));Serial.println();pre_buf_counter = (pre_buf_counter + 1) % PRE_DEBUG_BUF_SIZE;}#endif#ifdef DEBUG9for (int i = 0; i < NUM_OF_BITS; i++){Serial.print(F("bit "));Serial.print(i);Serial.print(F(": "));for (int j = 0; j < NUM_OF_SERIES; j++){Serial.print(F("("));Serial.print(arr_bit_times [j][i][2]);Serial.print(F(", "));Serial.print(arr_bit_times [j][i][0]);Serial.print(F(", "));Serial.print(arr_bit_times [j][i][1]);Serial.print(F("), "));}Serial.println();}#endifSerial.println(F("---------------------------------"));}}
Nie działała ona najlepiej, o tym co poprawiłem w dalszej części artykułu.
Problemy z zasięgiem
Jak już pisałem kiedy żaden nadajnik nie nadawał odbiornik “łapał coś” i stan na wyjściu co chwila się zmieniał. Założyłem jednak, że kiedy nadajnik zacznie nadawać to odbiornik zacznie reagować prawidłowo.
Pierwsza wersja programu została napisana tak, że program czeka na stan wysoki o odpowiedniej długości, a następnie odczytuje dane spodziewając się prawidłowych czasów zarówno stanów wysokich jak i niskich. Po pierwszym błędzie program przerywał odczyt i próbował zanalizować odczytane dane, a mianowicie jeżeli udało się odczytać choć jedną serię danych to następowało porównywanie odczytanych serii.
Pierwsze próby robiłem nadając mój testowy sygnał z nadajnika, który zakupiłem razem z odbiornikiem. Sygnał nadawany był z niewielkiej odległości. Zauważyłem, że dane nie odczytują się do końca poprawnie. Często zamiast sześciu serii danych odczytywane było pięć serii.
Przyczyną okazało się to, że kiedy nadajnik zaczynał nadawać to odbiornik tak jakby potrzebował chwili aby się do niego dostroić.
Dużo gorzej było kiedy próbowałem odczytać sygnał z oryginalnej sondy, tu już przy około metrowej odległości zaczynały się spore problemy. Często nie udawało się odczytać w ogóle danych lub najwyżej jedną, dwie serie.
Przyczyną jak się okazało było pojawianie się na wyjściu stanów wysokich mimo tego, że sonda nadawała stan niski.
Próbowałem poprawić czułość sondy przylutowując antenkę w postaci kawałka drutu o długości około 15 cm, ale niewiele to pomogło. Wywiązała się nawet dyskusja na forum majsterkowo.pl. Jak tylko znajdę moment to popracuję nad tą antenką.
Kolejnym sposobem było ignorowanie krótkich stanów wysokich, to poprawiło nieco jakość odbioru.
Oto przykład wadliwie odczytanych danych:
W tym momencie postanowiłem zrobić co następuje:
- jeszcze bardziej uniewrażliwić oprogramowanie na sporadyczne błędy
- nie przerywać pomiaru po pierwszym poważnym błędzie tylko czekać na kolejną serię danych
- być może pewną inteligencję wprowadzić
Poprawianie jakości odczytu
Omówienie sytuacji aktualnej
Tak wygląda log z pierwszej wersji programu, który za chwilę omówię
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
channel: 2, temperature: 20.90 *C, humidity: 62 %, Num of measures: 3 lb: 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, sb: 4,-4,-4, 4, 4, 4,-4, 4, 4,-4, 4, 4,-4,-4,-4, 4,-4,-4,-4,-4, 4, 4,-4, 4,-4,-3,-3, 3,-3,-3, 3, 3, 3, 3, 3,-3,-3, b: 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, pre 0-0: (49049, 1944, 540), pre 1-1: (49051, 1948, 544), pre 2-2: (49055, 3833, 539), pre 3-3: (49060, 3910, 514), pre 4-4: (49064, 3849, 547), pre 5-5: (49069, 3608, 780), pre 6-6: (49073, 3817, 547), pre 7-7: (49076, 1944, 544), pre 8-8: (49078, 1937, 531), pre 9-9: (49087, 8644, 548), bit 0: (49091, 3812, 544), (49226, 3807, 553), (49361, 3823, 545), (49497, 3806, 558), (0, 0, 0), (0, 0, 0), bit 1: (49094, 1925, 523), (49229, 1911, 533), (49364, 1941, 527), (49500, 1908, 560), (0, 0, 0), (0, 0, 0), bit 2: (49096, 1932, 532), (49231, 1916, 544), (49367, 1915, 533), (49502, 1888, 560), (0, 0, 0), (0, 0, 0), bit 3: (49101, 3851, 537), (49235, 3848, 532), (49372, 3848, 548), (49507, 3830, 550), (0, 0, 0), (0, 0, 0), bit 4: (49105, 3862, 522), (49241, 3860, 520), (49376, 3851, 529), (49511, 3841, 547), (0, 0, 0), (0, 0, 0), bit 5: (49110, 3836, 536), (49245, 3848, 540), (49380, 3843, 537), (49515, 3827, 545), (0, 0, 0), (0, 0, 0), bit 6: (49112, 1944, 532), (49247, 1927, 549), (49382, 1953, 527), (49518, 1955, 533), (0, 0, 0), (0, 0, 0), bit 7: (49117, 3839, 545), (49252, 3845, 531), (49387, 3845, 539), (49522, 3857, 515), (0, 0, 0), (0, 0, 0), bit 8: (49121, 3843, 533), (49256, 3856, 516), (49391, 3835, 545), (49526, 3857, 535), (0, 0, 0), (0, 0, 0), bit 9: (49123, 1964, 528), (49258, 1971, 533), (49393, 1955, 541), (49529, 1940, 560), (0, 0, 0), (0, 0, 0), bit 10: (49128, 3856, 524), (49263, 3831, 545), (49398, 3848, 520), (49533, 3833, 531), (0, 0, 0), (0, 0, 0), bit 11: (49132, 3857, 535), (49267, 3867, 533), (49402, 3856, 552), (49538, 3861, 523), (0, 0, 0), (0, 0, 0), bit 12: (49134, 1955, 533), (49269, 1936, 540), (49404, 1937, 547), (49541, 1957, 543), (0, 0, 0), (0, 0, 0), bit 13: (49137, 1953, 527), (49272, 1942, 522), (49408, 1956, 524), (49543, 1957, 519), (0, 0, 0), (0, 0, 0), bit 14: (49139, 1948, 532), (49274, 1934, 558), (49410, 1950, 526), (49545, 1952, 524), (0, 0, 0), (0, 0, 0), bit 15: (49144, 3853, 527), (49280, 3832, 544), (49415, 3837, 547), (49550, 3858, 526), (0, 0, 0), (0, 0, 0), bit 16: (49146, 1944, 528), (49282, 1940, 528), (49417, 1944, 520), (49552, 1959, 529), (0, 0, 0), (0, 0, 0), bit 17: (49148, 1933, 535), (49284, 1959, 533), (49419, 1948, 560), (49554, 1953, 535), (0, 0, 0), (0, 0, 0), bit 18: (49152, 1973, 515), (49287, 1954, 518), (49422, 1946, 518), (49557, 1935, 533), (0, 0, 0), (0, 0, 0), bit 19: (49154, 1964, 552), (49289, 1956, 548), (49424, 1968, 560), (49559, 1965, 531), (0, 0, 0), (0, 0, 0), bit 20: (49159, 3849, 527), (49294, 3855, 529), (49429, 3819, 529), (49564, 3850, 522), (0, 0, 0), (0, 0, 0), bit 21: (49163, 3854, 522), (49298, 3842, 526), (49433, 3899, 529), (49568, 3862, 510), (0, 0, 0), (0, 0, 0), bit 22: (49165, 1943, 545), (49300, 1941, 535), (49435, 1941, 543), (49570, 1951, 533), (0, 0, 0), (0, 0, 0), bit 23: (49170, 3849, 535), (49305, 3839, 545), (49440, 3848, 528), (49575, 3838, 554), (0, 0, 0), (0, 0, 0), bit 24: (49172, 1940, 524), (49307, 1997, 519), (49442, 1954, 522), (49577, 1943, 545), (0, 0, 0), (0, 0, 0), bit 25: (49174, 1959, 533), (49309, 1945, 547), (49444, 1945, 531), (49580, 1292, 560), (0, 0, 0), (0, 0, 0), bit 26: (49177, 1958, 526), (49312, 1947, 533), (49447, 1943, 533), (0, 0, 0), (0, 0, 0), (0, 0, 0), bit 27: (49181, 3865, 527), (49316, 3861, 543), (49452, 3857, 535), (0, 0, 0), (0, 0, 0), (0, 0, 0), bit 28: (49184, 1965, 523), (49319, 1953, 523), (49455, 1953, 531), (0, 0, 0), (0, 0, 0), (0, 0, 0), bit 29: (49186, 1936, 560), (49321, 1936, 560), (49457, 1960, 532), (0, 0, 0), (0, 0, 0), (0, 0, 0), bit 30: (49190, 3817, 547), (49326, 3828, 536), (49461, 3833, 543), (0, 0, 0), (0, 0, 0), (0, 0, 0), bit 31: (49196, 3848, 532), (49331, 3844, 540), (49466, 3857, 523), (0, 0, 0), (0, 0, 0), (0, 0, 0), bit 32: (49200, 3853, 527), (49335, 3857, 515), (49470, 3852, 528), (0, 0, 0), (0, 0, 0), (0, 0, 0), bit 33: (49204, 3849, 535), (49339, 3832, 560), (49474, 3837, 547), (0, 0, 0), (0, 0, 0), (0, 0, 0), bit 34: (49208, 3833, 543), (49343, 3831, 537), (49478, 3844, 560), (0, 0, 0), (0, 0, 0), (0, 0, 0), bit 35: (49211, 1928, 548), (49346, 1949, 527), (49481, 1916, 548), (0, 0, 0), (0, 0, 0), (0, 0, 0), bit 36: (49213, 1929, 539), (49348, 1939, 529), (49483, 1919, 537), (0, 0, 0), (0, 0, 0), (0, 0, 0), --------------------------------- |
W pierwszej linii znajdują się informacje o odczycie, czyli:
- kanał na którym nastąpił odczyt
- odczytana temperatura
- odczytana wilgotność
- liczba pełnych udanych odczytów wszystkich bitów – jak pisałem wcześniej sonda nadaje 6 razy ten sam zestaw bitów w trakcie pojedynczej transmisji
Kolejne trzy wiersze zawierają informację o odczytach kolejnych bitów i tak:
- pierwszy wiersz (lb:) zawiera informację o tym ile razy bit został prawidłowo odczytany
- kolejny wiersz (sb:) zawiera informację o tym jakie informacje zostały odczytane – kiedy program odczytuje zero to zmniejsza tę wartość o 1, a kiedy odczytuje 1 zwiększa ją – w idealnym przypadku ta wartość jest równa tej z pierwszego wiersza lub ujemnej wartości z pierwszego wiersza
- ostatni wiersz (b:) zawiera interpretację odczytu czyli 0, 1 lub ? jeśli interpretacja jest niejednoznaczna
W tym przypadku widać, że bity od 0 do 24 zostały odczytane 4 razy, a pozostałe bity 3 razy. Każdy bit za każdym razem został odczytany tak samo. Gdyby tak nie było i np bit 0 nie został 4 razy odczytany jako 1 tylko np raz jako 0 to wartość (sb:) nie wynosiłaby 4 tylko 2, a gdyby dwa razy jak 0 a dwa razy jako 1 to ta wartość wynosiłaby 0 i nie dałoby się powiedzieć jaką właściwie ten bit ma wartość.
Kolejnych 10 wierszy, tych zaczynających się od “pre”, zawiera informacje o czasach 10 pomiarów niskich i wysokich stanów zanim rozpoczął się właściwy pomiar. Liczby w nawiasach oznaczają:
- moment w którym zmiana została zmierzona, jest to technicznie millis() % 65536 – obcinam wartość millis() tylko do 16 bitów bo ze względu na małą pamięć nie mam gdzie przechowywać danych, ledwo się mieszczą
- czas w jakim stan był niski
- czas w jakim stan był wysoki
W tym przypadku wyraźnie widać, że te odczyty nie są szumem, a stanowią poprawne wartości odczytywanych bitów. Nie zostały jednak zaliczone do pomiaru. Stało się tak prawdopodobnie dlatego, że w momencie jak sonda zaczęła nadawać to odbiornik potrzebował chwili aby się dostosować do nadawanego sygnału.
Na obrazie poniżej widać jak wygląda odczyt początku transmisji. Stan wysoki na lewo od zielonego znacznika “1” to pierwszy stan wysoki nadany przez sondę. Między znacznikami “1” a “2” sonda nadawała stan niski, jednak odbiornik wychwycił kilka stanów wysokich. Są to zakłócenia z którymi mój algorytm sobie nie poradził i uznał całość za szumy.
Widać też zakłócenia w dalszej części i dopiero potem (od mniej więcej 87ms) czyste dane.
Kolejnych 37 wierszy zawiera informacje o odczytach kolejnych bitów w kolejnych 6 transmisjach. Liczby w nawiasach mają takie samo znaczenie jak w wierszach “pre”.
Tu widać, że podczas odczytu 4 serii bitów nagle nastąpiła przerwa podczas odczytu bitu nr 25. Czas stanu wysokiego jest ok, jednak czas stanu niskiego jest zbyt krótki, wynosi tylko 1292μs. Oto co się stało:
Pomiędzy znacznikami “1” i “2” powinien być stan niski, jednak widać, że odbiornik wychwycił jakieś zakłócenia jedno o długości 103μs, a drugie o długości 1028μs. Aktualny algorytm zignorował to pierwsze, jednak tego drugiego już nie i uznał odczyt za błędny i przerwał działanie.
Oto co postanowiłem zrobić aby algorytm lepiej działał:
- nie przerywać całkowicie działania po pierwszym błędnym odczycie, ale czekać na kolejną transmisję (czyli przerwę o czasie 8640μs) i zaprzestać czekania dopiero kiedy zbyt długo ona nie nastąpiła.
- jeszcze bardziej uniewrażliwić algorytm na takie pojedyncze zakłócenia.
Nie przerywanie działania
Pisząc program, który po pierwszym błędzie nie przerywa działania tylko czeka na kolejną porcję danych praktycznie główną pętlę napisałem od nowa. Oto ten program.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
//#define DEBUG_ADHOC // ustawienie tej definicji powoduje, że program // wypisuje na rs232 różne informacje diagnostyczne #define DEBUG9 // ustawienie tej definicji powoduje, że program // po dokonaniu pomiaru ustawia na chwilę stan wysoki // na jednym z wyjść, podłączony tam analizator // stanów logicznych zaczyna wtedy pomiar // ponieważ posiada on bufor na jakiś czas przed // rozpoczęciem pomiwaru więc można przeanalizować // co pojawiło się na wyjściu z odbiornika RDF #define DEBUG_SALAE1 // wyjśącie odbiornika RDF #define RF_DATA_PIN 11 // wyjście z diodą świecącą #define LED_PIN 13 // czasy stanu wyokiego i przerw #define UP_TIME 560 #define SEPARATOR_TIME 8640 #define ZERO_TIME 1900 #define ONE_TIME 3800 #define MODE_WAITING_FOR_SEPARATOR 1 #define MODE_READING_BITS 2 #define MODE_STOP_READING 3 // maksymalny czas transmisji w ms // obliczony przy założeniu, że są same jedynki wysylane // (czyli długie stany niskie) #define MAX_TRANSMISION_TIME ((6L * (37L * (UP_TIME + ONE_TIME) + UP_TIME + SEPARATOR_TIME) + UP_TIME) / 1000L) // makra sprawdzające czy czas mieści się w buforze #define CHK_UT(CT,T) (((CT) >= ((T) - 80)) && ((CT) <= ((T) + 80))) #define CHK_FT(CT,T) (((CT) >= ((T) - 250)) && ((CT) <= ((T) + 250))) // liczba bitów jednej wiadomości i liczba // paczek, które są nadawane #define NUM_OF_BITS 37 #define NUM_OF_SERIES 6 // specjalna wartość oznaczająca, że odczyt był błędny #define BAD_VALUE 0b1000000000000000 // bufor na bity int8_t bits [NUM_OF_BITS]; // za każdym z 6 pomiarów na określonej pozycji mogę zmierzyć // 0 lub 1 (na skutek błędów transmisji) // jeżeli zmierzę 1 to zwiększam tę wartość, // a jeśli 0 to zmniejszam int8_t bit_adds [NUM_OF_BITS]; // za każdym poprawnym pomiarem zwiększam tę wartość o 1 uint8_t bit_measures [NUM_OF_BITS]; uint32_t last_report_time = 0; #ifdef DEBUG9 uint16_t arr_bit_times [NUM_OF_SERIES][NUM_OF_BITS][3]; #define PRE_DEBUG_BUF_SIZE 10 uint16_t arr_pre_buf_times [PRE_DEBUG_BUF_SIZE][3]; int pre_buf_counter; #endif #ifdef DEBUG_SALAE1 #define DEBUG_SALAE1_PIN 6 #endif uint32_t getvalue(int bitStart, int bitNum) { uint32_t ret = 0; for (int i = 0; i < bitNum; i++) { if (bits [bitStart + i] > 1) return BAD_VALUE; ret *= 2; ret += bits [bitStart + i]; } return ret; } void setup() { Serial.begin(115200); pinMode(RF_DATA_PIN, INPUT); pinMode(LED_PIN, OUTPUT); #ifdef DEBUG_SALAE1 pinMode(DEBUG_SALAE1_PIN, OUTPUT); #endif Serial.print(F("Odczyt sondy v2 - max transmision time = ")); Serial.println(MAX_TRANSMISION_TIME); } void loop() { // czyszczę wszystkie zmienne na początku for (int i = 0; i < NUM_OF_BITS; i++) { bit_adds [i] = 0; bit_measures [i] = 0; #ifdef DEBUG9 for (int j = 0; j < NUM_OF_SERIES; j++) { arr_bit_times [j][i][0] = 0; arr_bit_times [j][i][1] = 0; arr_bit_times [j][i][2] = 0; } #endif } #ifdef DEBUG9 for (int i = 0; i < PRE_DEBUG_BUF_SIZE; i++) { arr_pre_buf_times [i][0] = 0; arr_pre_buf_times [i][1] = 0; arr_pre_buf_times [i][2] = 0; } pre_buf_counter = 0; #endif int8_t mode = MODE_WAITING_FOR_SEPARATOR; // czas stanu wysokiego w microsekundach uint32_t highTime; // czas aktualnego pomiaru w microsekundach uint32_t time; // czas stanu niskiego i wysokiego razem uint32_t tickTime; // czas ostatniego pełnego pomiaru w microsecundach uint32_t last_pulse_time = micros(); // aktualna seria danych int8_t measure_number = -1; // liczba pełnych pomiarów (wszystkie bity) int8_t num_of_full_measures = 0; // czas rozpoczęcia pełnego pomiaru // używany to sprawdzania czy timeout nie upłynął uint32_t full_measure_start_time; // licznik odczytywanych bitów int8_t bit_number; do { while (true) { highTime = pulseIn(RF_DATA_PIN, HIGH); time = micros(); #ifdef DEBUG9 uint32_t lowTime = time - last_pulse_time - highTime; // do prebufora tylko przed właściwymi pomiarami if (measure_number < 0) { arr_pre_buf_times [pre_buf_counter][0] = lowTime; arr_pre_buf_times [pre_buf_counter][1] = highTime; arr_pre_buf_times [pre_buf_counter][2] = millis(); pre_buf_counter = (pre_buf_counter + 1) % PRE_DEBUG_BUF_SIZE; } #endif // sprawdzenie czy timeout if ((measure_number >= 0) && ((millis () - full_measure_start_time) > MAX_TRANSMISION_TIME)) { mode = MODE_STOP_READING; break; } if (highTime > UP_TIME) highTime = UP_TIME; tickTime = time - last_pulse_time; // poprawny, wystarczająco długi stan wysoki // i wystarczająco długi niski wcześniej if (CHK_UT(highTime, UP_TIME) && (tickTime > (ZERO_TIME + UP_TIME - 250))) break; } #ifdef DEBUG9 if ((measure_number >= 0) && (bit_number < NUM_OF_BITS) && (mode != MODE_WAITING_FOR_SEPARATOR)) { arr_bit_times [measure_number][bit_number][0] = tickTime - highTime; arr_bit_times [measure_number][bit_number][1] = highTime; arr_bit_times [measure_number][bit_number][2] = millis(); } #endif // ta zmienna określa czy "skonsumować" ten odczyt boolean bConsumeThisTick = false; // sprawdzamy czy udało się odczytać prawidłowy czas switch (mode) { case MODE_READING_BITS: if (CHK_FT(tickTime, UP_TIME + ZERO_TIME)) { bit_adds [bit_number]--; bit_measures [bit_number]++; bit_number++; bConsumeThisTick = true; } else if (CHK_FT(tickTime, UP_TIME + ONE_TIME)) { bit_adds [bit_number]++; bit_measures [bit_number]++; bit_number++; bConsumeThisTick = true; } if (bit_number >= NUM_OF_BITS) { mode = MODE_WAITING_FOR_SEPARATOR; num_of_full_measures++; } case MODE_WAITING_FOR_SEPARATOR: if (CHK_FT(tickTime, UP_TIME + SEPARATOR_TIME)) { measure_number++; if (measure_number == 0) { full_measure_start_time = millis(); } bit_number = 0; if (measure_number >= NUM_OF_SERIES) { mode = MODE_STOP_READING; } else { mode = MODE_READING_BITS; } bConsumeThisTick = true; } } if ((mode != MODE_STOP_READING) && !bConsumeThisTick) { mode = MODE_WAITING_FOR_SEPARATOR; bConsumeThisTick = true; } if (bConsumeThisTick) { last_pulse_time = time; } } while (mode != MODE_STOP_READING); // jeżeli choć jeden pełny pomiar się udał wypisuję raport if (num_of_full_measures > 0) { #ifdef DEBUG_SALAE1 digitalWrite(DEBUG_SALAE1_PIN, HIGH); delay(10); digitalWrite(DEBUG_SALAE1_PIN, LOW); #endif for (int i = 0; i < NUM_OF_BITS; i++) { if (bit_adds[i] >= (bit_measures[i] / 2)) { bits[i] = 1; } else if (bit_adds[i] <= (-bit_measures[i] / 2)) { bits[i] = 0; } else { bits[i] = 2; } } uint32_t nChannel = getvalue (14, 2); uint32_t nTemp = getvalue (16, 12); uint32_t nHumidity = getvalue (28, 8); Serial.print(F("soft: v2 channel: ")); if (nChannel != BAD_VALUE) Serial.print(nChannel + 1); else Serial.print(F("???")); Serial.print (F(", temperature: ")); if (nTemp != BAD_VALUE) Serial.print(nTemp / 10.0); else Serial.print(F("???")); Serial.print (F(" *C, humidity: ")); if (nHumidity != BAD_VALUE) Serial.print(nHumidity); else Serial.print(F("???")); Serial.print(F(" %, Num of measures: ")); Serial.println(num_of_full_measures); //wypisuję liczbę pomiarów dla bitów Serial.print(F("lb: ")); for (int i = 0; i < NUM_OF_BITS; i++) { Serial.print(F(" ")); Serial.print(bit_measures[i]); Serial.print(F(",")); } Serial.println(); Serial.print(F("sb: ")); for (int i = 0; i < NUM_OF_BITS; i++) { if (bit_adds[i] >= 0) { Serial.print(F(" ")); } Serial.print(bit_adds[i]); Serial.print(F(",")); } Serial.println(); Serial.print(F("b: ")); for (int i = 0; i < NUM_OF_BITS; i++) { Serial.print(F(" ")); if (bits[i] > 1) Serial.print(F("?")); else Serial.print(bits[i]); Serial.print(F(",")); } Serial.println(); #ifdef DEBUG9 for (int i = 0; i < PRE_DEBUG_BUF_SIZE; i++) { Serial.print(F("pre ")); Serial.print(i); Serial.print(F("-")); Serial.print(pre_buf_counter); Serial.print(F(": ")); Serial.print(F("(")); Serial.print(arr_pre_buf_times [pre_buf_counter][2]); Serial.print(F(", ")); Serial.print(arr_pre_buf_times [pre_buf_counter][0]); Serial.print(F(", ")); Serial.print(arr_pre_buf_times [pre_buf_counter][1]); Serial.print(F("), ")); Serial.println(); pre_buf_counter = (pre_buf_counter + 1) % PRE_DEBUG_BUF_SIZE; } #endif #ifdef DEBUG9 for (int i = 0; i < NUM_OF_BITS; i++) { Serial.print(F("bit ")); Serial.print(i); Serial.print(F(": ")); if (bits[i] > 1) Serial.print(F("?")); else Serial.print(bits[i]); Serial.print(F(":")); for (int j = 0; j < NUM_OF_SERIES; j++) { if ((bits[i] == 0) && !CHK_FT(arr_bit_times [j][i][0] + arr_bit_times [j][i][1], UP_TIME + ZERO_TIME)) Serial.print(F("#")); else if ((bits[i] == 1) && !CHK_FT(arr_bit_times [j][i][0] + arr_bit_times [j][i][1], UP_TIME + ONE_TIME)) Serial.print(F("#")); else Serial.print(F(" ")); Serial.print(F("(")); Serial.print(arr_bit_times [j][i][2]); Serial.print(F(", ")); Serial.print(arr_bit_times [j][i][0]); Serial.print(F(", ")); Serial.print(arr_bit_times [j][i][1]); Serial.print(F("),")); } Serial.println(); } #endif Serial.println(F("---------------------------------")); } } |
Uniewrażliwienie na zakłócenia
Za to jeszcze się nie zabrałem, na poważnie, ale już całkiem nieźle to działa. Analizując program widać co już zrobiłem
Zestaw użytych elementów
Do przeprowadzenia tej pracu użyłem:
- Arduino UNO R3 – zakupione na w sklepie internetowym nettigo.pl w zestawie startowym – oprócz samego arduino z zestawu użyłem
- kabelki
- płytkę stykową
- analizator stanów logicznych saleae – jest to podróbka, ale działa
Co dalej
Następne kroki jakie zamierzam zrobić w tym temacie:
- podpięcie układu do moje stacji monitorowania warunków w mieszkaniu i na zewnątrz. Stacja już całkiem nieźle działa, ale nie jest jeszcze gotowa na artykuł. Jeżeli interesuje Was ten temat już teraz to zapraszam na mój blog
- Poprawienie jakości filtrowania zakłóceń
- Zakup na nettigo innego odbiornika – może będzie bardziej czuły
- Przerobienie programu aby na przerwaniach chodził i oszczędzał energię
Podsumowanie
Mam nadzieję, że temat Wam się spodobał i nie był zbyt długi i nużący.
Jest to mój pierwszy artykuł więc będę wdzięczny za wszelkie konstruktywne uwagi na jego temat.
Jeżeli ktoś miałby ochotę wspólnie ze mną rozwijać ten temat to zapraszam do współpracy. Cel na jesień/zimę to automatyka do sterowania ogrzewaniem w domu oparta między innymi o technologię z-wave.
Myślę, że opieranie automatyki domowej na podstawie bezprzewodowego nadajnika i odbiornika tego typu może odbić się czkawką ;)
Wystarczy dowcipny sąsiad lub jakieś warunki zakłócające sygnał z czujnika i już masz w domu lodówkę albo piekarnik ;)
mam jeszcze za oknem ds18b20 ;)
a tak poważnie to myślę, że ryzyko jest niewielkie i warte swojej ceny :) a pewna dbałość w warstwie softu dodatkowo to ryzyko ograniczy
jacek: Po pierwsze wielki szacunek za profesjonalne i wyczerpujące podejście do tematu, myślę, że wiesz teraz o tych układzikach więcej, niż dyrekcja fabryki, która je składa ;-)
Natomiast ryzyka nie nazwałbym niewielkim – te stacje (w tym chyba Twoja) zwykle nie mają żadnego parowania tylko jednostronną “adaptację” do nadajnika a pewnie jakieś 50% tych zabawek pochodzi z jednej fabryki i ma identyczne kodowanie. Ja już nie raz łapałem pomiary na różnych kanałach mimo, że mam tylko jedną sondę. A sam czasami jej używałem np. w zamrażalniku żeby zweryfikować poprawność termostatu w lodówce. Nie chciałbyś raczej w słoneczny dzień +25 podejmować jakiejś decyzji na podstawie odczytu -26st.C/wilg. 100%… Nawet uśredniony z drugim rzeczywistym odczytem daje temperaturę ujemną w środku lata.
@tomee: dzięki za komplement :)
wiesz, ja jednak zaryzykuję, a z tego typu błędnymi odczytami spróbuję sobie poradzić, myślę, że uda mi się zrobić nieco bardziej inteligentny system jak taka prosta stacja ;)
Super analiza. Coś podobnego chodzi mi po głowie od jakiegoś czasu i Twój opis tylko utwierdza mnie w przekonaniu, że to będzie ciekawe wyzwanie ale chyba jednak z dużą szansą na powodzenie. Czekam na opis kolejnych działań!
bardzo dziękuję :)
jak tylko coś będę miał gotowego dam znać, a myślę, że coraz częściej coś będę miał bo jak to mówią “pierwsze koty za płoty”
Dla mnie czytelne i zrozumiałe – życzę powodzenia w dalszych badaniach
B.
dziękuję :)
Gratuluję projektu i rezultatów – jestem wręcz zaskoczony, jak prosto jest to kodowane. Przecież wystarczyłoby żeby temperatura była inaczej kodowana, np. od 15 bitu, i już byłoby trochę trudniej.
Zastanawia mnie jednak co innego – w większości tego typu komunikacji gdzieś zostaje zaszyta suma kontrolna – właśnie po to, żeby wiedzieć czy wszystko jest prawidłowo odebrane. Czy sprawdzałeś, że te bity, które uznałeś za ID rzeczywiście zmieniają się tylko przy resecie baterii? Podałeś dane tylko z jednego testu, więc muszę wierzyć na słowo, ale naprawdę powinna tam gdzieś być suma kontrolna.
Co do odbiornika – to normalne, że muszą złapać poziom. Zwykle robi się to jakąś preambułą (szczególnie jeśli ‘1’ i ‘0’ są kodowane różnymi poziomami sygnału, a nie długością pauzy); tutaj jak widać poradzili sobie powtarzając transmisję 6 razu. Pierwszy 1-2 są do wyrzucenia z definicji.
I jeszcze jedno: zegary w tych urządzeniach nie mają szansy być precyzyjne, obstawiałbym drift rzędu kilku sekund dziennie na nadajniku, może trochę mniej na na odbiorniku – ciekaw jestem, na czym jest wykonywana resynchronizacja? Może na końcu transmisji…
I na deser – rzuć okiem tutaj, chyba coś podobnego: http://forum.arduino.cc/index.php?topic=145341.0
dziękuję :)
Brak sumy kontrolnej bardzo mnie zdziwił, ale naprawdę jej nie ma. Podczas prac nad programowym filtrowaniem błędów odczytu widziałem już chyba ze dwie setki odczytów i żaden bit nie zmieniał się w taki sposób, że mógłby być tą sumą lub jej częścią. W szczególności bity na początku i końcu w ogóle się nie zmieniają.
Co do “łapania poziomu” to zauważyłem, że już po kilku(nastu max) bitach poziom jest łapany. Te bity zauważ wolno są nadawane. Szkoda, że nie pomyśleli o tym, ja w swojej implementacji nadajnika po prostu przez chwilę nadawałem krótkie sygnały.
Ta synchronizacja jest bardzo prosta, odbiornik zaczyna nasłuchiwać już ok sekundy przed czasem, odbiera komunikację i następnym razem wie kiedy znowu zacząć słuchać.
Ta analiza do której linka mi wysłałeś to bardzo ciekawy materiał, chodzi mi po głowie aby rozwijać bibliotekę potrafiącą wiele różnych sond tego typu odczytywać. Tak żeby nie być skazanym na jeden model.
Na tym niskim poziomie często będzie podobnie, kilka transmisji (u mnie 6, w przykładzie, który podałeś 3) takiej samej paczki bitów (u mnie 37, w Twoim przykładzie 57 bodajże), którą potem trzeba zinterpretować. Różnica tylko w czasach transmisji.
Zdecydowana większość kodu dla takich stacji mogłaby być wspólna.
Gratuluje świetnego rozgryzienia problemu ;) Najbardziej podobała mi się sytuacja, kiedy usłyszałeś od “producenta”, że się nie da, a Ty tu ładnie pokazałeś, że się da ;P
A tak swoją drogą to problem z zakłóceniami mógłbyś rozwiązać stosując częstotliwość 868 MHz, jest u nas mniej popularna. Ewentualnie Bluetooth bo skacze po kanałach i tym sposobem minimalizowane jest ryzyko zakłócenia.
Rozmawiałem ostatnio z ludźmi z ST i z naszych rozmów właśnie wyszło, że do sterowania urządzeniami mobilnymi obecnie nadaje się albo Bluetooth, albo RF 868 MHz. Z kolei 2,4GHz i wszystkie WiFi’ki podatne są na zakłócenia choćby nawet przez mikrofalówkę. No i podobno częstotliwość ta nie jest legalna w Polsce ;P Osobiście mam aparaturkę RC właśnie 2,4GHz z pełną syntezą sygnału i nigdy nie miałem problemu z sterowaniem. Może dlatego, że o ile dobrze mi wiadomo to aparatura RC w technologii 2,4GHz podobnie jak Bluetooth skacze po wstęgach i tym sposobem eliminuje możliwość podłączenia się do tego samego kanału na którym akurat nadajemy.
dziękuję :)
Też nie rozumiem postawy producenta, może osobie z którą rozmawiałem nie udało/chciało się zdobyć informacji i mnie zbył.
Co do sterowania to zainteresowała mnie mocno technologia z-wave, mam zamiar niedługo w to wejść . Jest sporo urządzeń na rynku, które w tej technologii się komunikują. Sama nośna w tej technologii to właśnie 868 MHz.
Może ktoś ma już jakieś doświadczenia i podzieliłby się nimi.
A pod czym pracuje stacja odbiorcza? Jaki µC? Bo możnaby odessać z niej program.
Nie wiem, nawet jej nie rozbierałem. Wydaje mi się jednak, że nawet jakby to się udało to dużo zachodu byłoby z tym później.
Świetna robota, bardzo ciekawie się czytało. Zastanawia mnie tylko, czy na pewno nie lepiej zrobić całości od podstaw, montując odpowiednie czujniki dedykowane Arduino? Z mojego doświadczenia z takimi stacjami pogodowymi wynika, że nie są wzorem stabilności ani jakości – a przecież nie zrobiłeś tego researchu po to, aby oszczędzić 50 zł.
PS. Synchronizacja może w ogóle nie korzystać z zegara, który się może rozjechać tylko z timera – po synchronizacji ustawiany jest licznik na 56 sekund i do następnego odczytu. W ten sposób szansa na rozjechanie synchronizacji jest niewielka, bo w 56 sekund nawet kiepski zegar będzie “trzymał”
Ta moja ma już jakieś 2 lata i nieźle działa.
Zrobiłem to dokładnie po to żeby zaoszczędzić te 50 zł :) a nawet sporo więcej niż 50 zł bo takie “wypasione” czujniki np systemu KNX to potrafią ponad 500 PLN kosztować, a nie 30.
Moja właśnie padła po 2 latach. Kupiłem drugą (droższą) i przeżyła również 2 lata :) Dlatego trzeciej już nie kupiłem, mam tylko motocyklowy termometr (odporny na warunki pogodowe).
Patrzę na ceny czujników wilgotności i (szczególnie) temperatury: w sumie oba nie kosztują tyle, ile wydałeś na analizator stanów :) Ale rozumiem, że rozrywka umysłowa – bezcenna :) Jeszcze raz: bardzo ciekawy post, przyda się innym majsterkowiczom na pewno.
Analizator stanów w wersji produkcyjnej to powiedzmy 20 zł (bo arduino do testów przecież tylko używam) i można go podłączyć do np 6 sond po 30 zł każda co daje 200 zł za taki zestaw.
Mam pomysl – raczej nie odnosnie dalszej analizy obecnego sposobu zczytywania danych – a nie latwiej byloby odczytywac sygnal wyslany juz do wyswietlacza stacji bazowej i tam odkodowany? Rozkrecamy stacje bazowa, podlaczamy sie do sygnalu idacego na wyswietlacz czy wrecz do wyswietlacza (mozliwe, ze ciezko bedzie mechanicznie) i mamy dokladny odczyt temperatury i wilgotnosci. Taki pomysl tylko. Odpornosc na zaklocenia maksymalna, bo odczytujemy dokladnie to co mamy na wyswietlaczu. Ale o szczegoly prosze nie pytac – az tak dobrze sie nie znam na cyfrowce.
Albo wrecz analizator optyczny wyswietlacza – pare czujnikow optycznych, zeby z siedmiosegmentowych wyswietlaczy odczytac dane… lub wrecz analizator obrazu z kamerka
to napisal Bratzacieszyciela
mam pytanie:
czy zamiast analizatora logicznego, nie wystarczyłby odbiornik 433 i analiza wejścia na konsoli arduino?
@bimki: myślę, że byłoby o wiele trudniej,
analizator potrafi zebrać DUŻO próbek i ładnie je pokazać,
potrafi też czysty sygnał zebrać z nadajnika,
odbiornik mnóstwo szumów łapie i w tej sieczce trudniej byłoby się połapać,
oczywiście dałoby się, ale tak było łatwiej
poza tym ja akurat jestem na etapie poznawania tego wszystkiego i takie doświadczenie z analizatorem jest dla mnie cenne
pozdrawiam
Jacek Szpyrka
Trzeba przyznać że jestem pod wrażeniem zdeterminowania w dążeniu do celu. Dobrze, że nie zaufałeś w to co Ci napisał producent. Jak widać, na pomoc producenta często nie ma co liczyć bo albo sami nie wiedzą, albo celowo nie chcą powiedzieć, sam ostatnio kupiłem dekoder dvb-t który powinien odtwarzać napisy, a nie odtwarza, napisałem do producenta i pewnie na odpowiedzi się nie doczekam.
Ile taka przeróbka Cię kosztowała? Długo czasu na to poświęciłeś?
Pozdrawiam!
dzięki,
ale o jaką przeróbkę pytasz?
a ja mam pomysł o czym mogą mówić pierwsze 4 bity (odczytana wartość 9) może to podzielony przez 10 wskaźnik naładowania baterii w nadajniku, ów stacja ma ikonkę rozładowania baterii nadajnika sondy, ale to takie tam dywagacje:)
Gratuluję determinacji, jednak mam wrażenie, że to daremny wysiłek. Jak już ktoś wspomniał chyba lepiej było by zrobić własną stację pogodową. Ale co kto lubi.
Słówko na temat pasma 433 MHz: podobnież jest to pasmo krótkofalarskie i wykorzystanie tego typu urządzeń jest nie do końca legalne. A co do tych tanich odbiorniczków – te superreakcyjne odbiorniki są nie tylko mało niezawodne ale też emitują zaklócenia.
Super artykul. Kochani, blagam, nie wstawiajcie usmieszkow. To strasznie meczy oczy. Zwlasza w tresci artykulu. Nie mowiac o recenzjach. Na elektrodzie ich nie zobaczycie. Tak jest najwygodniej.
Rozmawiając z kimś z cztery oczy też w ogóle nie używasz mimiki twarzy?
Rozmowa to jednak cos innego
To jest dokładnie to samo. W czasie rozmowy druga osoba właśnie za pomocą mimiki twarzy może poznać, czy mówisz całkiem poważnie, czy sobie żartujesz. Pisząc nie da się przekazać tego samego bez użycia emotikon.
Zobacz na takie zdanie:
Weź stąd spadaj.
A teraz to samo zdanie z emotikoną nadającą żartobliwego tonu całej wypowiedzi:
Weź stąd spadaj :P
Te same słowa, ale odbiór zupełnie inny.
Witam!
Domyślam się że gotowy kod podany jest dla Arduino lecz niestety nie posiadam tego ale czy można ten kod wstawić jako gotowy hex ale w wersji dla Attiny2313 i pytanie czy zmieścił by się w tym procesorze ??
Pozdrawiam
P.S. bardzo ciekawy artykuł :)
Świetny artykuł!
Jakiś czas temu zakupiłem prosty termometr z biedronki z zaokienną, bezprzewodową sondą. No i właśnie od jakiegoś czasu zastanawiam się jak się dobrać do tego sygnału by wprowadzić go do komputera. Teraz już wiem jak podejść do tematu ;) Myślę, że będę obserwował na bieżąco.
pozdrawiam
Ps. Piszesz o problemach z zasięgiem, czy w oryginale (czyli zestaw baza + sonda) zasięg też jest tak mały? (1m), czy uwidacznia się to tylko w modelu Twój odbiornik + fabryczna sonda z zestawu?
@Siutek:
Sprawdź czy nie taka: http://blog.omegastar.eu – tam masz mój reverse stacji z biedronki.
Przeczytałem artykuł na Twojej stronie, gdyż mam tą samą stację z Biedronki. U mnie sprawa jest jakby odwrotna:) Mianowicie sonda mi się uszkodziła i chciał bym wysyłać temperaturę stacji. Mam trochę różnych czujników w domu i szkoda by było wyrzucać tę stację. Nie wiesz jak można by za pomocą kodu arduino wysłać temperaturę do stacji? Przerobiłem nieco ten kod do wysyłania z artykułu wg. protokołu Ariol(ktoś w komentarzach u Ciebie napisał), ale niestety nie chce działać.
UPDATE – udało się:) Jakoś obliczyłem sobie wartości czasów impulsów z Twoich wykresów i mogę wysyłać temperaturę na tę stację:) Jednak protokół auriol jest nieco inny.
No i jeszcze informacja jak by ktoś szukał. Temperatura jest tam zapisana w postaci signed binary i zajmuje 12 bitów, a jest w kodzie uzupełnień do dwóch.
Cześć,
Zmień odbiornik.
Ten ma fatalny filtr i łapie dużo śmieci. Zrobiłem reverse engineering podobnej stacji ale z wykorzystaniem STM32F103x. Polecam odbiornik ZZ-RS10-433MHz (19pln).
Do pomiaru czasu pomiędzy impulsami używaj funkcjonalności capture timera ustawionego na wyzwalanie narastającym zboczem, to hardware zrobi za ciebie cały pomiar a tobie pozostanie tylko interpretacja długości impuslów.
TL;DR
nie warto czytać – i tak nie działa
Witam , jeśli chodzi o długość kawałka drutu jako antenę – 165 mm i będzie o wiele lepiej z zasięgiem i czułością (to dokładnie półfalowa antenka) – bardzo rzeczowy projekt – pozdrawiam
Wiesz może jaki czujnik temperatury tam siedzi? Mam taką stację i mój ewidentnie przekłamuje o 3C. Chciałbym go wymienić na jakiś inny.
Znalazłem kiedyś odbiorniki dedykowane do odczytywania sygnału, ale nie wiem gdzie można je kupić. Trzeba szukać Rxb1 433mhz. A tu coś na ten temat :
http://www.practicalarduino.com/projects/weather-station-receiver
Przydatne informacje, szczególnie na temat wilgotności te parametry. Warto to analizować. Taki poziom wilgotności w mieszkaniu jest bardzo istotny, tak samo jak temperatura.