Szukaj na tym blogu

czwartek, 30 maja 2013

Odczyt bezprzewodowej sondy - programowa poprawa jakości odczytu

Dzisiaj zabrałem się za ostateczną poprawę algorytmu do odczytu danych.


Omówienie sytuacji aktualnej


Tak wygląda log, który będę omawiał

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ę 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 odczytam zero zmniejszam tę wartość o 1, a kiedy odczytam 1 zwiększam 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 lub 1
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 potrzebuje 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. Zobaczmy 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.
Co zamierzam 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.

//#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) - 50)) && ((CT) <= ((T) + 50)))
#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("Startujemy - 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;

  // 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;
      }
      
      // poprawny, wystarczająco długi stan wysoki
      if (CHK_UT(highTime, UP_TIME))
        break;
    }
    
    tickTime = time - last_pulse_time;
    

#ifdef DEBUG9
    if ((measure_number >= 0) && (bit_number < NUM_OF_BITS))
    {
      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;
        }      

      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 pomiar się udał wypisuję raport

  if (measure_number > 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("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(measure_number);
    
    //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.

1 komentarz:

  1. Ale zawiłości... dobrze, że z moją stacją pogodą zakupioną na https://www.oleole.pl/stacje-pogody,_Meteo.bhtml nie mam aż tyle problemów. Wyświetlacz jest bardzo czytelny i wszystkie potrzebne parametry można sprawdzić. I są w pełni wiarygodne. I na zewnątrz i wewnątrz można taką stację trzymać.

    OdpowiedzUsuń