Разбор протокола передачи с метеостанции MiSOL WH2310

mixael

★✩✩✩✩✩✩
1 Авг 2018
8
11
Доброго времени суток, год назад приобрёл на Али метеостанцию MiSOL WH2310, у неё имеются выносные датчики дождя, освещённости, уровня ультрафиолетового излучения, скорости и направления ветра, а так же стандартные датчики температуры и влажности, барометр находится в приёмной части, как и в большинстве метеостанций(барометр соберу отдельно).
IMG_2978.jpegIMG_2982.jpeg
Она стабильно работает и почти нормально записывает показания в память, раз в неделю, если нужна статистика, приходится подключать её к компьютеру, скачивать данные и обнулять память. И всё бы ничего, но примерно через полгода при достижении уровня памяти более 80% станция начала удалять почти все показания, записать ничего нормально не получалось, а записывать данные каждый день было не очень хорошей идеей, поэтому начал изучать что можно с ней сделать. А предложений было не много, в основном станцию подключали к RaspberryPI по USB и периодически считывали данные, но такой подход мне не подходит, поскольку станция должна быть автономной.
Поэтому я пошёл другим путём - купил на Али приёмник RXB6(изначально пробовал другие, но этот оказался самый стабильный, так же пробовал считывать с Si4432 в режиме RAW, но так и не вышло отфильтровать полезный сигнал). Поскольку осциллограф Hantek 6104BC у меня нормально не смог принять пакетную передачу и буфер у него всего 4кб, пришлось купить китайский логический анализатор, клон Saeale Logic pro, с помощью него и, сначала штатной программы, а затем PulseView, удалось вычислить биты передаваемой информации, где, скорее всего, первые 7 бит это преамбула, здесь единица это импульс 500микросекунд, а ноль 1500микросекунд.
IMG_3160.jpegMiSOL_protocol_light.png
После этого начались поиски примера кода, который учитывает преамбулу, или который даже мог бы расшифровать этот протокол, но он не подходил ни к одному по описанию, RCSwitch его не видел, поскольку тут нет бита синхронизации - вместо него преамбула для подстройки приёмника. В итоге нашёл похожий код, подстроил под себя количество бит преамбулы и данные пошли.

C++:
static int pin = 2;
int data[81];
unsigned long sec;
int i1;

void setup() {
  Serial.begin(9600);
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
}

boolean HCS301_message(){

  int duration[7];
  byte PreamblePulseCount;

  int rs = digitalRead(pin);

  if(rs == 0){
    return false;
  }

  // что то поймали, будем анализировать

  //Serial.println("Ok");

  unsigned long TimeStartReading = micros();
  unsigned long PulseStart = TimeStartReading;
  unsigned long curpoz = TimeStartReading;

  int stat = 1;
  int tdur = 0;
  PreamblePulseCount = 0;

  int i,j;

  // ловим преамбулу //////////////////////////////////////////////////////////////////////

  // Преамбула состоит из 12 импульсов, типичная длительность 9200 мкс
  // Длительность преамбулы 23 базовых импульсов Те, типичный импульс Те 400 мкс
  while((curpoz - TimeStartReading) < 20000){

    rs = digitalRead(pin);

    if (stat != rs) { // состояние изменилось
      if(stat == 1 && rs == 0){
        // окончание импульса
        tdur = curpoz - PulseStart;
        if(tdur < 300 || tdur > 600){
          break;
        }
        duration[PreamblePulseCount] =  tdur;
        PreamblePulseCount ++;
        stat = 0;

        if(PreamblePulseCount == 7){
          break;
        }

      } else {
        // начало импульса
        stat = 1;
        PulseStart = curpoz;
      }
    }
    curpoz = micros();
  }


  if(PreamblePulseCount != 7){
    return false;
  }

  Serial.println();
  Serial.println("Catch preamble");
  // Найдена преамбула/////

  int PreambleDuration = micros() - TimeStartReading;
  int Te = PreambleDuration / 23;

  int mass[81];
  bool Reading;
  bool Success;
  int dur;

  // Начинаем читать данные
  for(i = 80;i >= 0; i--){

    Reading = false;
    TimeStartReading = 0;
    Success = false;

    for(int j=0;j<1000;j++){

      rs = digitalRead(pin);

      if (rs == 1 && Reading == false){
        // начали чтение бита
        TimeStartReading = micros();
        Reading = true;
      };
      if (rs == 0  && Reading == true){
        // окончили чтение бита
        dur = micros() - TimeStartReading;
        mass[i] = (dur > (Te / 2 * 3)) ? 0 : 1;
        data[i]=mass[i];
        Success = true;
        Serial.print(mass[i]);
        break;
      };
    };

    if(Success == false){

      return true;
    };

  };
  }

char *MakeCRC(char *BitString) //не работает, не считает правильно
   {
   static char Res[9];                                 // CRC Result
   char CRC[8];
   int  i;
   char DoInvert;

   for (i=0; i<8; ++i)  CRC[i] = 0;                    // Init before calculation

   for (i=0; i<strlen(BitString); ++i)
      {
      DoInvert = ('1'==BitString[i]) ^ CRC[7];         // XOR required?

      CRC[7] = CRC[6];
      CRC[6] = CRC[5];
      CRC[5] = CRC[4] ^ DoInvert;
      CRC[4] = CRC[3] ^ DoInvert;
      CRC[3] = CRC[2];
      CRC[2] = CRC[1];
      CRC[1] = CRC[0];
      CRC[0] = DoInvert;
      }

   for (i=0; i<8; ++i)  Res[7-i] = CRC[i] ? '1' : '0'; // Convert binary to ASCII
   Res[8] = 0;                                         // Set string terminator

   return(Res);
   }



void loop() {
//    if (millis() - sec > 10000){
//      sec = millis();
//      char *Data, *Result;
//      Data = "01110011010000000101010100000000000000000000000010111101"; //Это пример ночных данных, CRC=BD
//      Result = MakeCRC(Data);
// Serial.println(Result);
//    }


if (HCS301_message() == true){
//    Serial.println(); ////////////////писать обработку данных с датчиков тут
//    Serial.println();
//    Serial.println("Data:");
//    for (i1=80;i1>0;i1--) {Serial.print(data[i1]); data[i1]=0; }
//    Serial.println();
//    memset(data, 0, 80);

    }
}
Данные приходили в таком виде: через каждые 59-60 секунд шли 3 пакета, вида:
01110011010001110101010100001011010001011001010001111101 - 56бит данных
каждые 42-43 секунды шел либо один пакет, либо 2, вида:
10101110111100101000100000101001000000100000001100000001001011000000101000110110 - 80 бит данных
____________________________________________________
Вооружившись калькулятором, начал изучать приходящие данные и вычислил следующее - сначала идёт тип датчика, затем его ID, которое меняется при каждой перезагрузке внешнего блока, затем данные отличаются в разных пакетах, приведу их так:
C++:
1010|11101111|001010001000|00101001|00000010|00000011|0000000100101100|00001010|00110110
тип^--ID^--температура^--влажность^----ветер^--порыв^ветра-дождь^--направление^ветра----^CRC(контрольная сумма)
Тип: 10(какой-то внутренний тип)
ID: EF(на самом приёмнике отображается зеркально-FE)
Температура: 648 - надо из суммы вычесть цифру 400(предел измерений от -40 до +50) и разделить на 10, получим +24,8°C
Влажность: 41%
Ветер: 2 - тут сложнее, по формуле надо это значение умножить на 0.34 и затем округлить 2*0.34=0.68=~0.7м/с
Порыв ветра: 3 это 3*0,34=1.02=~1.0м/с
Дождь: 300 - просто счётчик, при достижении предела 0,3мм увеличивается на 1, если за какой-то промежуток времени прошёл дождь, то значение увеличится, если было значение 300, а через час значение 303, значит выпало 0,9мм осадков
Направление ветра: 10 - SW-ЮЗ(Юго-Западный ветер) - пишем таблицу соответствия направления ветра, поскольку индикаторов 16:
IMG_3130.jpeg
0° - 0 n
23° - 1 n-ne
45° - 2 ne
68° - 3 ne-e
90° - 4 e
113° - 5 e-se
135° - 6 se
158° - 7 se-s
180° - 8 s
203° - 9 s-sw
225° - 10 sw
248° - 11 sw-w
270° - 12 w
293° - 13 w-nw
315° - 14 nw
338° - 15 nw-n
CRC: 00110110 = 36 HEX

Следующий пакет:

C++:
0111|00110100|0111|01010101|000010110100010110010100|01111101
Тип^--ID^-Индекс^UV--???^????---Освещённость^----------------^CRC(Контрольная сумма)
Тип - 7; ID - 34(HEX)(на самом приёмнике отображается зеркально-43);
Индекс ультрафиолетового освещения: 7, просто показывает индекс, по разным таблицам в интернете разные степени высокого и низкого излучения
??????? - пока непонятная последовательность, всегда одинаковая даже после перезагрузки, возможно, батарейка, но в мануале не заявлена функция отображения разряженной батареи, хоть на экране и имеется соответствующий символ
Освещенность: 738708(Lux) - значение в люксах, на экране отображаются килолюксы,есть внутреннее преобразование в w/m2,но оно не совпадает с формулами
CRC: 01111101 - 7D HEX
____________________________________________________

С данными разобрались, теперь самое интересное - помогите высчитывать CRC, до сих пор ни нашёл ни одного калькулятора, который правильно может это сделать, нашёл документацию, где анализировался похожий датчик, там предлагалась функция CRC8, и, судя по всему, CRC-8-Dallas/Maxim, поскольку там говорилось "The CRC-8 polynomial used is x8 + x5 + x4+ 1". Но почти ни один калькулятор не выдал правильный ответ. И только на единственном cайте при заведении полинома результат совпадает

Исходные данные:
01110011010000000101010100000000000000000000000010111101 > 734055000000 BD последние 2 - CRC

Откидываем CRC, вставляем полином x8 + x5 + x4+ 1 > 100110001(131 в HEX, но как я понял первый бит откидываем, получаем 31, как раз CRC-8-Dallas/Maxim по википедии)

011100110100000001010101000000000000000000000000 > 734055000000, подставляем 16ричное число в онлайн калькулятор и он нам выдаёт BD

Пытаемся повторить в других калькуляторах, получаем всё что угодно, но не BD...............

Помогите разобраться, без CRC из-за помех периодически приходит информация от каких-то левых устройств между посылками, и информация получается неверная
 
Изменено:
  • Лойс +1
Реакции: savenkoffcom

mixael

★✩✩✩✩✩✩
1 Авг 2018
8
11
Оставлю несколько двоичных данных для примера:

C++:
011100110100001101010101000001100101011100010000|01010110 > 734355065710 56     
011100110100001101010101000001001010100010010000|00100001 > 73435504A890 21
011100110100011101010101000010110100010110010100|01111101 > 7347550B4594 7D
011100110100010001010101000000111011110101001100|11111001 > 73445503BD4C F9
011100110100011001010101000010001001101011000000|10000011 > 734655089AC0 83
011100110100010001010101000001000101010101110000|11100011 > 734455045570 E3
C++:
101011101111001010000100001001010000011000001010000000010010110000001000|01010011 > AEF28425060A012C08 53
101011101111001001111011001111100000001000000011000000010001001100001000|00100110 > AEF27B3E0203011308 26
101011101111001010001000001010010000001000000011000000010010110000001010|00110110 > AEF288290203012C0A 36
101011101111001000001110010101100000000000000000000000010010111000001110|00001110 > AEF20E560000012E0E 0E
101011101111001000001100010110100000000000000001000000010010111000001100|10100011 > AEF20C5A0001012E0C A3
101011101111001000001100010110100000000000000000000000010010111000001100|00111000 > AEF20C5A0000012E0C 38
 
  • Лойс +1
Реакции: savenkoffcom

savenkoffcom

✩✩✩✩✩✩✩
6 Июл 2019
8
0
Оставлю несколько двоичных.....
Собрался заказывать себе аналогичную метеостанцию для проекта. Вычитывать тоже буду с Ардуино с последующей передачей на сайт, поэтому ваша информация очень полезна. Как только будет что дополнить по данной теме - я дам знать, а пока - очень хорошее начало, я не знал даже, как подойти к этому вопросу.
 

mixael

★✩✩✩✩✩✩
1 Авг 2018
8
11
IMG_3243(1).jpg
Всё заработало, набросал скетч, добавил файл с комментариями, в прошлый раз у меня было задвоение переменной, которую я не заметил, и поэтому CRC считал неправильно, теперь проблема ушла и данные удалось считать.

Чтобы начать писать код нужно опытным путём(с помощью любого анализатора, да хоть с помощью рации и микрофона) изучить что прилетает в эфир и разобрать какие биты, сколько их и какой они величины. В моём случае частота импульсов примерно равна 500микросекундам, единица это 1 импульс высокого уровня и 2 низкого, а ноль - 3 импульса высокого уровня, и 2 низкого, от этого и отталкиваемся, если за определённое время пришёл импульс 500микросекунд, значит это единица, если 1500микросекунд, значит ноль.

На данный момент работает так: постоянный цикл, который проверяет ногу, к которой подключен приёмник, если на нём появилась логическая единица, то начинается отсчёт преамбулы, если высокий уровень продержался дольше, или меньше предела, то цикл завершается и это означает, что пришёл мусор, если всё нормально и тайминги соблюдены, то начинаем считать преамбулу, если она пришла и состоит из нескольких(можно задать) одинаковых импульсов, то начинаем считать и записывать в массив биты данных. Считаем до тех пор, пока не придёт импульс, не соответствующий стандартной длине импульса(больше или меньше), либо пока не закончится счётчик(можно изменить значение). Биты записываем в массив в обратном порядке от последнего элемента массива к первому, поскольку иначе код отрабатывает некорректно и биты съезжают.

После этого, если количество совпало с неким числом бит(в моём случае 56 или 80), которое было вычислено заранее опытным путём, то продолжаем обрабатывать пришедшие данные - это второй этап отсеивания неправильных данных, после преамбулы, он позволяет отсеять большинство неверных данных. Причём тут есть хитрость - иногда, при плохом приёме данных, задваивается ноль или единица, и чтобы не допустить ненужный пакет к обработке был создан массив на 1 бит больше фактически верного числа(81 вместо 80), при этом, если данные пришли с ошибкой, такой пакет отсеется уже на этапе приёма, даже не попав на обработку(поскольку такой пакет обычно приходит либо больше нужного числа бит, либо меньше), что влекло бы лишнее время работы контроллера. После этого, если длина совпала, начинаем высчитывать CRC(контрольную сумму) - записываем весь пришедший пакет, кроме последних 8 бит в отдельную переменную, и по функции вычисляем CRC, так же в другую переменную записываем только последние 8 бит, и, если вычисленная сумма будет равна фактической сумме, записанной в последних 8ми битах, то значит к нам пришёл верный пакет, продолжаем обработку.

Далее разбиваем пакет данных всё тем же опытным путём(включая логику и калькулятор записываем значения пакета в двоичном виде, пытаемся понять сколько бит подо что идут, конвертируем в десятичную систему счисления, сверяемся(если есть) с источником принятых данных - в моём случае это база метеостанции) на блоки, в которых записаны некоторые данные и уже обрабатываем каждое значение, которое удалось вычислить этим опытным путём, переводим блоки данных из двоичной системы в десятичную(причём нюанс - в функцию опять надо послать инвертированные данные, если надо перевести цифру 7(0111), то сначала надо её записать в обратном порядке(1110), и только потом передавать на конвертацию, иначе результат непредсказуем) и записываем в общий массив данных, из которого можно будет их взять в любое время до следующего прихода пакета(обычно за это время можно успеть их вывести, или передать в нужное место).

В итоге если пакет приходит верный, то, в моём случае, данные считываются, отправляются в Serial и выводятся на LCD дисплей, в противном случае пришедший пакет не обрабатывается(отбрасывается).

При нажатии первой кнопки слева выводится расшифрованный урезанный пакет 56бит(насколько хватило дисплея, можно расширить показания при необходимости), а так же значение барометра в миллиметрах ртутного столба, при двойном нажатии включается подсветка экрана
Аналогично при нажатии второй кнопки слева выводится расшифрованный урезанный пакет 80бит, при двойном нажатии подсветка экрана выключается.
В обычном, и единственном, режиме данные на экране отображаются через пару секунд после получения пакета и обновляются в соответствии какой пакет пришёл.

Ещё столкнулся с некоторыми проблемами - барометр BME280 запустить удалось не с первого раза, один попался бракованный - ножкаCSB коротила на землю и тем самым замыкала линию 3.3v, я не сразу понял в чём дело, поскольку пытался просканировать шину i2c, но на неё ничего не выводилось, понял только когда где-то начало пахнуть палёным, начал щупать - всё холодное, а оказалось модуль с AMS1117 на макетке раскалился, но, к счастью, остался рабочим, пришлось брать второй датчик BME280. Этот датчик имеет напряжение питания 3.3v, поэтому напрямую к Ардуино его подключать нельзя - сразу сгорит, имейте это ввиду, поэтому подключение необходимо производить только через согласователь уровней, который может быть выполнен как угодно, хоть делитель на резисторах(с ESP8266 он не понадобится, так как там и так напряжение везде 3.3v), при этом на модуле макетки на одном краю сейчас стоит переключатель в положении 5v, а с другой 3.3. Так же с ним была проблема подключения в скетче, поскольку по ошибке сначала использовал библиотеку bmP280, а надо было bmE280(они разные). С этого датчика мной считывается только давление(остальное мне не нужно - температура и влажность кладовки, в которой будет стоять модуль, меня мало интересует =) , если будет нужно, соберу отдельную плату, к тому же данные собираюсь отправлять в интернет, а там уж точно никому не интересна комнатная температура), но если нужно считывать влажность и температуру, то надо дописать пару строк, комментарии в скетче по этому поводу есть.

В данном виде это почти финальная стадия, далее следует переход на ESP8266, его настройка для работы с MQTT и подключение к шлюзу MajorDoMo с последующей отправкой данных в сеть.
IMG_3290.jpegMeteo_data_arduino.png

Если есть какие-нибудь предложения по улучшению или оптимизации кода, буду благодарен.
 

Вложения

Изменено:

savenkoffcom

✩✩✩✩✩✩✩
6 Июл 2019
8
0
Всё заработало, набросал скетч, добавил файл с комментариями, в прошлый раз у меня было задвоение переменной...
Спасибо, дорогой друг! Внешний комплект вылетел ко мне, дисплея, к сожалению, не будет, поэтому я очень, очень, надеюсь, что Ваши наработки спасут меня. Делать приемник собираюсь на основе железки LoRa v.2, брал из-за наличия комплекта 433 Mhz + WiFi. Как будет результат - напишу.
 

savenkoffcom

✩✩✩✩✩✩✩
6 Июл 2019
8
0
Ребята, комплект прибыл. А вот настроить LoRa под него не получается. А RXB6 пока не прибыл, "сырой" сигнал не вижу. Может есть возможность считать сырой сигнал с LoRa? - Просто если не указывать размер ожидаемого пакета - она вообще ничего читать не хочет, все API перерыл. Может у кого получилось?
 

savenkoffcom

✩✩✩✩✩✩✩
6 Июл 2019
8
0
Всё заработало, набросал скетч, добавил файл с комментариями, в прошлый раз у меня было задвоение переменной, которую я не заметил, и поэтому CRC считал неправильно, теперь проблема ушла и данные удалось считать.
Перешел на NodeMCU с RXB6, преамбулу смог поймать только при двойном импульсе, ровно 7 бит, при единичном - тишина в эфире. Анализатора нет, к сожалению, посмотреть не могу что фактически ходит в эти периоды. Данные, кстати, после успешной преамбулы у меня тоже не пошли...
 

mixael

★✩✩✩✩✩✩
1 Авг 2018
8
11
Ребята, комплект прибыл. А вот настроить LoRa под него не получается. А RXB6 пока не прибыл, "сырой" сигнал не вижу. Может есть возможность считать сырой сигнал с LoRa? - Просто если не указывать размер ожидаемого пакета - она вообще ничего читать не хочет, все API перерыл. Может у кого получилось?
У LoRa свой протокол передачи данных, и он зашит в чипе, поэтому с помощью неё можно передавать данные только между собой, а вот RBX6 "видит" сырой эфир без фильтрации, с помощью него в теории можно и посмотреть, что пришло, но без анализатора сложно.
Перешел на NodeMCU с RXB6, преамбулу смог поймать только при двойном импульсе, ровно 7 бит, при единичном - тишина в эфире. Анализатора нет, к сожалению, посмотреть не могу что фактически ходит в эти периоды. Данные, кстати, после успешной преамбулы у меня тоже не пошли...
Как вариант у меня только идея с рацией, либо с RTL-SDR приёмником, на крайний случай можно попробовать понизить напряжение с выхода RBX6 до 0,75-1 вольта и записать сигнал входом микрофона, и ещё как вариант поставить светодиод через резистор, он будет мигать в такт эфиру, только вот записать его будет не просто, поскольку там микросекунды, поэтому только вариант с микрофоном, я думаю
 

Alex_Doctor

✩✩✩✩✩✩✩
14 Дек 2019
1
0
ветер 0
дождь 0


110101101000100100110001100110000000000000000000000000000000000000000000100001100
48 вл
21.2 темп
N

110101101000100100110011000101111000000000000000000000000000000000000000011101000
N 21.4 47 hum

--------------------------------------------------------
110101101000100100110010000110000000000000000000000000000000000000000100101001000
S
21.2 48hum

110101101000100100110011000101111000000000000000000000000000000000000100101100000
S
21.4 48 hum
---------------------------------------------------------
110101101000100100110010000110000000000000000000000000000000000000000100101001000
W
21.2 48 hum
110101101000100100110010100110000000000000000000000000000000000000000110101100101
W
21.3 48 hum

110101101000100100110010100101111000000000000000000000000000000000000110110101110
110101101000100100110010100101111000000000000000000000000000000000000110110101110
W 21.3 47hum

-----------------------------------------------------------
110101101000100100110010000110000000000000000000000000000000000000000010100110101
E
21.3 48 hum
110101101000100100110010100110000000000000000000000000000000000000000110101100101
E
21.3 47Hum
-------------------

101011010001001001100111001100010000001000010001000000000000001000001100000011011
0,7 m/s
49 hum
21.5C
0,6 mm
W


ewsn - направление ветра
21.3-21.4 температура
47-48 влажность


помогите, пожалуйста, разобрать пакет (
станция - WN14CN, на передатчике написано
MISOL WN5300CA На коробке


неизм часть--------- темп-------------- влаж ---------- сила------ порыв---- счетчик дождя ----- напр ветра------crc

1101 011010001 001001101001 00110110 00000000 00000000 0000000000000010 00000101 11101110



эксперименты с датчиком ветра
110101101000100 1001101101 00 110111 00001001 00010100 0000000000000010 0000 0110 11000110

2
2*0.34=0.68=~0.7м/с

20
20*0,34=6,8


и с датчиком дождя

1101011010001001001000000010010110000000000000000 0000000010111000 00000100 10010001

51.6мм
184 счетчик

1101 011010001001000111101010011000000000000000000 0000000011001011 00000100 00101100

57.3мм
203 счетчик
 
Изменено:

efko

✩✩✩✩✩✩✩
22 Июн 2019
2
3
@Molodoi7772, у меня такая же плата на метеостанции https://mysku.ru/blog/china-stores/75406.html
Протокол удалось расшифровать
C++:
Север                                  Нап  Темп х 10   Влажность Скорость ветра                            CRC(сумма           Повтор пакета
    ID (0xF5)                          рав                       (делить на 588                             предыдущ 12
                                      ление                       для перев. в м/с)                         байт&0xFF)
001 11110101 010110111100010100000000 0000 000100010100 00101001 0000000000000010 0000000000000001 00000000 01010110 11110100 00111110101010110111100010100000000 0000 000100010100 00101001 0000000000000010000000000000000100000000010101100
СевероВосток
001 11110101 010110111100010100000000 0010 000100010000 00101010 0000000000000001 0000000000000001 00000000 01110010 10101000 00111110101010110111100010100000000 0010 000100010000 00101010 0000000000000001000000000000000100000000011100100
Восток
001 11110101 010110111100010100000000 0100 000100010100 00101001 0000000000000001 0000000000000001 00000000 10010101 00101110 00111110101010110111100010100000000 0100 000100010100 00101001 0000000000000001000000000000000100000000100101010
Юго-Восток
001 11110101 010110111100010100000000 0110 000100010100 00101001 0000000000000001 0000000000000001 00000000 10110101 11101101 00111110101010110111100010100000000 0110 000100010100 00101001 0000000000000001000000000000000100000000101101010
ЮГ
001 11110101 010110111100010100000000 1000 000100010000 00101010 0000000000000011 0000000000000001 00000000 11010100 00000101 00111110101010110111100010100000000 1000 000100010000 00101010 0000000000000011000000000000000100000000110101000
Юго-Запад
001 11110101 010110111100010100000000 1010 000100010100 00101001 0000000000000000 0000000000000001 00000000 11110100 10011101 00111110101010110111100010100000000 1010 000100010100 00101001 0000000000000000000000000000000100000000111101000
ЗАПАД
001 11110101 010110111100010100000000 1100 000100010100 00101010 0000000000000000 0000000000000001 00000000 00010101 10101000 00111110101010110111100010100000000 1100 000100010100 00101010 0000000000000000000000000000000100000000000101010
Северо-запад
001 11110101 010110111100010100000000 1110 000100010100 00101010 0000000000000011 0000000000000001 00000000 00111000 01100011 00111110101010110111100010100000000 1110 000100010100 00101010 0000000000000011000000000000000100000000001110000
CRC - это просто сумма 12 байт до CRC (начиная с 11110101 по 00000000) по маске &0XFF (берем младшие 8 бит)

Код ардуино:
C++:
uint8_t count = 0;
volatile uint8_t res[225];
volatile uint8_t flag = 0;
uint8_t head_flag = 0;
volatile unsigned long prevtime;
volatile unsigned int lolen, hilen, state;

#define rxPin 2
#define MAX_DELTA 140

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  ;
  pinMode(rxPin, INPUT_PULLUP);
  attachInterrupt(0, read_input, CHANGE);

}

void loop() {
  // put your main code here, to run repeatedly:
  if (flag == 1) //если были данные
  {
    //Serial.println("\n");

    uint16_t tmp = 0;
    for (uint8_t i = 0; i < 8; i++)
    {
      tmp <<= 1;
      tmp |= res[i] & 1;
    };
    //Serial.println(tmp);

uint8_t crc = 0;
    for (uint8_t i = 96; i < 104; i++)
    {
      crc <<= 1;
      crc |= res[i] & 1;
    };
//    Serial.print("\ncalc_CRC: ");
//    Serial.println(calc_CRC());
//    Serial.print("received CRC: ");
//    Serial.println(crc);


    if (tmp == 0xF5 && crc == calc_CRC()) { //первый байт должен быть 0xF5 (ID станции, может варьироваться от модели и верный CRC
      //Температура
      float temp = 0;
      int16_t temp_tmp = 0;
      int16_t temp_tmp1 = 0;
      for (uint8_t i = 36; i < 48; i++) //биты температуры
      {
        temp_tmp <<= 1; //смещаем влево
        temp_tmp |= res[i] & 1; //прибавляем младший бит
        temp_tmp1 <<= 1;
        temp_tmp1 |= res[i + 112] & 1; //прибавляем младший бит
      };
      //сверяем значение из двух посылок, если не ок, то выставляем в 255
      if (temp_tmp == temp_tmp1) {
        temp = (float)(temp_tmp) / 10.0;
      }
      else temp = 255;


      //Влажность
      uint8_t hum = 0;
      uint8_t hum1 = 0;
      for (uint8_t i = 48; i < 56; i++) //биты влажности
      {
        hum <<= 1;
        hum |= res[i] & 1;
        hum1 <<= 1;
        hum1 |= res[i + 112] & 1;
      };
      if (hum == hum1) {
        hum = hum1;
      }
      else hum = 255;

      //скорость ветра
      uint16_t ch_wind = 0;
      uint16_t ch_wind1 = 0;
      float wind = 0;
      for (uint8_t i = 56; i < 72; i++) //скорость ветра
      {
        ch_wind <<= 1;
        ch_wind |= res[i] & 1;
        ch_wind1 <<= 1;
        ch_wind1 |= res[i + 112] & 1;
      };
      if (ch_wind == ch_wind1) {
        wind = (float)ch_wind / 588;
      }
      else wind = 255;

      //направление ветра
      uint8_t wind_dir = 0;
      uint8_t wind_dir1 = 0;
      for (uint8_t i = 32; i < 36; i++)
      {
        wind_dir <<= 1;
        wind_dir |= res[i] & 1;
        wind_dir1 <<= 1;
        wind_dir1 |= res[i + 112] & 1;
      };
      if (wind_dir1 == wind_dir) {
        wind_dir = wind_dir1;
      }
      else wind_dir = 255;

      Serial.print("Temperature: ");
      Serial.print((temp), 1);
      Serial.print("C; Humidity: ");
      Serial.print(hum);
      Serial.print("%; Wind: ");
      Serial.print(wind);
      Serial.print("m/s; Direction: ");
      switch (wind_dir) {
        case 0: Serial.print("N"); break;
        case 2: Serial.print("NE"); break;
        case 4: Serial.print("E"); break;
        case 6: Serial.print("SE"); break;
        case 8: Serial.print("S"); break;
        case 10: Serial.print("SW"); break;
        case 12: Serial.print("W"); break;
        case 14: Serial.print("NW"); break;
        default: Serial.print("Error"); break;
      }

      //Батарея
      if (res[21] == res[21 + 112])
      {
        Serial.print("; Low battery!!!");
      }
      Serial.print("\n");

    }
    res[0] = 0;
    flag = 0;
    count = 0;
    sei();
  }
}

boolean CheckValue(unsigned int base, unsigned int value) {
  return ((value == base) || ((value > base) && ((value - base) < MAX_DELTA)) || ((value < base) && ((base - value) < MAX_DELTA)));
}

void read_input() {
  state = digitalRead(rxPin);
  if (state == HIGH)
    lolen = micros() - prevtime;
  else
    hilen = micros() - prevtime;
  prevtime = micros();


  if (state == LOW)  {
    // по спаду начинаем анализ
    if (CheckValue(240, hilen) && CheckValue(1520, lolen) && head_flag != 2) { //нашли короткий импульс
      head_flag = 1;
      } else if (head_flag != 2) head_flag = 0; //если нет - все заново
    if (CheckValue(2000, hilen) && CheckValue(1520, lolen) && head_flag != 2) { //нашли длинный импульс
      head_flag = 2;
      } else if (head_flag != 2) head_flag = 0; //если нет - все заново
      // голова посылки найдена начинаем прием полезных 224 бит данных
    if (CheckValue(960, hilen) && head_flag == 2) //бит 1 - 960 мс
    {
      res[count] = 1;
      count++;
      //Serial.print("1");
    }
    else if (CheckValue(480, hilen) && head_flag == 2) // бит 0 - 480 мс
    {
      res[count] = 0;
      count++;
      //Serial.print("0");
    }
    if (count == 223) { // отсчитали нужное число бит, все сбрасываем и запрещаем прерывания
      //Serial.print("\n");
      cli();
      head_flag = 0;
      flag = 1; //уходим в парсинг пакета
      count = 0;
      lolen = 0;
      hilen = 0;
      _delay_ms(250);
    }
  }
}

int calc_CRC() {
  uint8_t tmp_byte = 0;
  uint16_t tmp_result = 0;
  for (uint8_t j = 0; j < 12; j++) {
    tmp_byte = 0;
    for (uint8_t i = 0; i < 8; i++) {
      tmp_byte <<= 1; //смещаем влево
      tmp_byte |= res[8 * j + i] & 1; //прибавляем младший бит
    }
    tmp_result = tmp_result + tmp_byte;
  }
  return (tmp_result & 0xFF);
}
 
Изменено:
  • Лойс +1
Реакции: gex64 и zeaxm

Gex7772

✩✩✩✩✩✩✩
21 Май 2019
22
1
CRC - это просто сумма 12 байт до CRC (начиная с 11110101 по 00000000) по маске &0XFF (берем младшие 8 бит)

Код ардуино:
привет, а библиотека для приёмника какая? каторая в самой ардуино проге найти можно пойдёт? для китайских обычных приёмноков? или под каждую библиотеку код изменять надо?
 

Gex7772

✩✩✩✩✩✩✩
21 Май 2019
22
1
Efko а можно код для использования с дисплеяем? а данные осадков твоя плата не передаёт?
 

Gex7772

✩✩✩✩✩✩✩
21 Май 2019
22
1
вот такое вот по коду выше)) только вот температура "мудрит" хж почему
 

Вложения

Gex7772

✩✩✩✩✩✩✩
21 Май 2019
22
1
всё показывает, разобрался, приёмник к ардуинке лучше ловит чем базовая станция, но в прямой видимости метров 80-85 ловит, на металл крыше уличный блок стоит на высоте 10 метром, как я понял большая часть сигнала отражается от металла, и ещё экранирует.
 
Изменено:

zeaxm

✩✩✩✩✩✩✩
31 Май 2020
3
0
@efko, спасибо за расшифровку. Только вот не пойму или батареи все дохлые, или не верно определяет Low battery!!!
 

savenkoffcom

✩✩✩✩✩✩✩
6 Июл 2019
8
0
Ребята, вот обещанный протокол передачи данных для моей метиостанции MiSol. Вкидываю вам, может поможет, код себе сделал по нему, позже дам. У меня совпадает, добавил колонку с порядковым номером бита в пакете, мне так удобно парсить было. Делал на прерываниях.
 

Вложения

Изменено:

savenkoffcom

✩✩✩✩✩✩✩
6 Июл 2019
8
0
CRC рассчитал, используемый полином 0x131, мой вариант кода:
C++:
// Савенков Даниил Викторович, https://www.savenkoff.com/, (с) 2020
// Преамбула: 8 импульсов, 500 мкс. - HIGH, 1000 - LOW
// Хейдер: 2950 - HIGH, только не в основном пакете. Увидел по данным только в пакете с одной вспышкой, не обрабатываю, полезного там мало
#define RXB_DATA        2   //  PIN данных датчика RXB6
#define PREAMBLE_COUNT  8   //  Количество символов преамбулы
#define PREAMBLE_HIGH   500 //  Среднее HIGH в преамбуле, мкс
#define PRECISION       50  //  Точность при отклонении от длинысигнала в мкс., чем меньше число, тем строже фильтр, не реккомендуется выставлять менее 50 мкс.
#define BIT_COUNT       80  //  Количество ожидаемых бит данных

#include <Wire.h>             // Библиотека для работы с шиной I2C
#include <Adafruit_BMP280.h>  // Библиотека для работы с датчиком BMP280

class WH14 {
public:
    byte TxType;            //  Тип передатчика
    byte SecurityCode;      //  Код безопасности
    byte TempBitError;      //  Бит(-ы, почему-то 2) ошибки показаний температуры
    word Temperatue;        //  Температура
    byte Humidity;          //  Влажность
    byte WindSpeed;         //  Скорость ветра
    byte Gust;              //  Порывы ветра
    word RainCounter;       //  Счетчик осадков
    byte WindDirBitError;   //  Бит ошибки показаний направления ветра
    byte LowBattBit;        //  Бит разряженной батареи передатчика
    byte WindDir;           //  Направление ветра
    byte Crc;               //  Контрольная сумма
    
    byte writeByte(int StartBit, int DescCount, bool reverse = true);  // Функция установки значения байту
    word writeWord(int StartBit, int DescCount);  // Функция установки значений слову
    void parseData();       //  Функция разбора данных метеостанции
    void printSerial();     //  Функция вывода сообщения в канал
    double getTemperature(); //  Функция получения температуры в гр. С
    double getWindSpeed(byte wind);   //  Функция получения скорости ветра в м/с
    String getWindDir();  // Функция получения направления ветра
    int getRain(byte rain); //  Функция получения кол-ва осадков
    word calcCRC(); // Функция расчёта CRC8
};

// через volatile, так как изменяется через обработчик прерываний и должна быть в ОЗУ
volatile boolean  meteoListening = true;      //  Переменная - флаг получения пакета
volatile uint32_t meteoStartPulse = 0;        //  Метка времени, на которой был пойман HIGH
volatile uint8_t  meteoPreambleCount = 0;     //  Количество считанных бит преамбулы
volatile uint8_t  meteoBitCounter;            //  Счетчик считанных бит данных
volatile boolean  meteoBitArray[BIT_COUNT];   //  Массив считанных бит данных
volatile uint32_t meteoStartPreamble = 0;     //  Метка времени, старт преамбулы
volatile uint32_t Te;
volatile uint32_t Te2_3;

bool meteoCorrentData;                        //  Признак получения корректных данных с метеостанции

WH14 wh14;
Adafruit_BMP280 bmp;

void setup() {
  Serial.begin(115200);
  pinMode(RXB_DATA, INPUT_PULLUP);          //  Маркируем порт, как порт для прерываний
  attachInterrupt(digitalPinToInterrupt(RXB_DATA), meteoInterrupt, CHANGE);  //  Подключаем обработку прерываний
  if(!bmp.begin(0x76)) {
    // Если датчик BMP280 не найден
    Serial.println("BMP280 Sensor Error"); // Выводим сообщение об ошибке
  } else {
    // Устанавливаем настройки датчика BMP280
    bmp.setSampling(Adafruit_BMP280::MODE_FORCED,       // Режим работы
                    Adafruit_BMP280::SAMPLING_X2,       // Точность изм. температуры
                    Adafruit_BMP280::SAMPLING_X16,      // Точность изм. давления
                    Adafruit_BMP280::FILTER_X16,        // Уровень фильтрации
                    Adafruit_BMP280::STANDBY_MS_1000);  // Период просыпания, мСек
  }
  Serial.println("Init OK");
}
// Основной цикл
void loop() {
  if (meteoListening == false) {
    // Атмосферное давление с датчика BMP280
    Serial.print(F("Pressure = "));
    Serial.print(getPressure());
    Serial.println(" мм рт. ст.");
    // Включаем обработку показаний с метеостанции
    if (meteoCorrentData) {
      // Готовим пакет для передачи по Wi-Fi

      meteoCorrentData = false; //  Обнуляем признак получения данных с метеостанции
    }
    meteoListening = true;
  }
}

//  Проверка корректности длины битов преамбулы
boolean checkLenPreamblePulse(uint32_t p_duration) {
  if (p_duration >= (PREAMBLE_HIGH-PRECISION) && (p_duration <= (PREAMBLE_HIGH+PRECISION))) {
    return true;
  }
  return false;
}

// Функция записи бита в протокол
byte WH14::writeByte(int StartBit, int DescCount, bool reverse) {
    int   Count = 0;
    int   BitPos;
    byte  ResultByte = 0;
    if (reverse) {
      while (Count < DescCount) {
        BitPos = StartBit-Count;
        bitWrite(ResultByte, Count, meteoBitArray[BitPos]);
        Count++;
      }
    } else {
      while (Count < DescCount) {
        BitPos = StartBit+Count;
        bitWrite(ResultByte, Count, meteoBitArray[BitPos]);
        Count++;
      }
    }
    return ResultByte;
}

// Функция записи пары байт в протокол
word WH14::writeWord(int StartBit, int DescCount) {
    int   Count = 0;
    int   BitPos;
    word  ResultWord = 0;
    while (Count < DescCount) {
      BitPos = StartBit-Count;
      bitWrite(ResultWord, Count, meteoBitArray[BitPos]);
      Count++;
    }
    return ResultWord;
}

// Функция вывода показаний в канал
void WH14::printSerial() {
    // Покажем весь принятый пакет
    int i = 0;
    Serial.print("Data:  ");
    while(i < BIT_COUNT){
      Serial.print(String(meteoBitArray[i]));
      i++;
    }
    Serial.println("");
    // Тип передатчика
    Serial.print("TX Type: ");
    Serial.print(TxType, HEX);
    // Код безопасности
    Serial.print(" | Security Code: ");
    Serial.print(SecurityCode, HEX);
    //  Бит разряженной батареи передатчика
    Serial.print(" | Low Battary: ");
    Serial.print(LowBattBit, DEC);
    // Температура
    Serial.print(" | Temperature: ");
    Serial.print(getTemperature(),1);
    Serial.print("*C ");
    // Влажность
    Serial.print( "| Humidity: ");
    Serial.print(Humidity, DEC);
    Serial.print("%");
    // Скорость ветра
    Serial.print(" | Wind ");
    Serial.print(getWindDir());
    Serial.print(": speed: ");
    Serial.print(getWindSpeed(wh14.WindSpeed),1);
    Serial.print(" m/s");
    // Порывы ветра
    Serial.print(", gust: ");
    Serial.print(getWindSpeed(wh14.Gust),1);
    Serial.print(" m/s");
    // Счётчик осадков
    Serial.print(" | Rain: ");
    Serial.print(getRain(wh14.RainCounter),1);
    // Crc
    Serial.print(" | CRC: ");
    Serial.print(Crc, BIN);
    Serial.print(" -> 0x");
    Serial.println(Crc, HEX);
}

// Функция получения температуры в гр. С
double WH14::getTemperature() {
  if (TempBitError == 0) {
    return ((double)Temperatue-400)/10.0;
  }
}

// Функция получения атмосферного давления в мм.рт.ст
int getPressure() {
  return bmp.readPressure()*0.00750062;
}

// Функция получения направления ветра
String WH14::getWindDir() {
  if (wh14.WindDirBitError == 0)  {
      switch (wh14.WindDir) {
        case 0x0: return "N";    break;
        case 0x1: return "NNE";  break;
        case 0x2: return "NE";   break;
        case 0x3: return "NEE";  break;
        case 0x4: return "E";    break;
        case 0x5: return "EES";  break;
        case 0x6: return "ES";   break;
        case 0x7: return "ESS";  break;
        case 0x8: return "S";    break;
        case 0x9: return "SSW";  break;
        case 0xA: return "SW";   break;
        case 0xB: return "SWW";  break;
        case 0xC: return "W";    break;
        case 0xD: return "WWN";  break;
        case 0xE: return "WN";   break;
        case 0xF: return "WNN";  break;
      }
  }
  return "NULL";
}

// Функция получения силы ветра
double WH14::getWindSpeed(byte wind) {
  return (wind*0.34);
}

// Функция получения кол-ва осадков
// Значение - счётчик, накапливается. 
// Для определения дифференциальной величины, например, за час, необходимо в начале каждого часа находить разницу между предшествующим и текущим значением
int WH14::getRain(byte rain) {
  return (rain*0.3);
}

// Функция разбора данных метеостанции
void WH14::parseData() {
    // Тип передатчика
    TxType = writeByte(3,4);
    // Код безопасности
    SecurityCode = writeByte(11,8);
    //  Бит(-ы, почему-то 2) ошибки показаний температуры
    TempBitError = writeByte(13,2);
    // Температура в форенгейтах
    Temperatue = writeWord(23,10);
    // Влажность
    Humidity = writeByte(31,8);
    // Скорость ветра
    WindSpeed = writeByte(39,8);
    // Порывы ветра
    Gust = writeByte(47,8);
    // Счётчик осадков
    RainCounter = writeWord(63,16);
    // Бит ошибки показаний направления ветра
    WindDirBitError = writeByte(64,1);
    //  Бит разряженной батареи передатчика
    LowBattBit = writeByte(67,1);
    // Направление ветра
    WindDir = writeByte(71,4);
    // Crc
    Crc = writeByte(79,8);
}

word WH14::calcCRC() {
  word polynom = 0x131; //  Полином расчитан алгоритмом https://sourceforge.net/projects/reveng/ для массива данных с обнулёнными последними 8-и битами сообщения: "CRC-8/MAXIM-DWO"
  word crcValue;
  boolean  calcArray[BIT_COUNT];   //  Свой массив
  // Копируем массив
  for (int i = 0; i < BIT_COUNT; i++) {
    calcArray[i] = meteoBitArray[i];
  }
  // Обнуляем хвост
  for (int i = 0; i < 9; i++) {
    calcArray[(BIT_COUNT-i)] = 0;
  }
  // Установки
  crcValue = 0x00;
  // Читаем биты до конца
  for (int i = 0; i < BIT_COUNT; i++) {
    bitWrite(crcValue, 0, calcArray[i]);
    if bitRead(crcValue,8) {
      crcValue = crcValue ^ polynom;
    }
    crcValue <<= 1;
  }
  crcValue >>= 1;
  return crcValue;
}

// Функция обработки прерываний
void meteoInterrupt() {
  // Если пока не читаем, значит не читаем
  if(meteoListening == false){
    return;
  }
  // Фиксирум/обновляем время изменения импульса
  uint32_t cur_timestamp  = micros();                         //  Метка времени
  uint8_t  cur_status     = digitalRead(RXB_DATA);            //  Тип импульса
  uint32_t pulse_duration = cur_timestamp - meteoStartPulse;  //  Длина последнего импульса

  // Начало по восхождению
  if (cur_status == HIGH) {
    meteoStartPulse = cur_timestamp;  
  }
  
  // Анализ по спаду
  if (cur_status == LOW) {
    // Ловим преамбулу
    if (meteoPreambleCount < PREAMBLE_COUNT) {
      // Отметим время старта преамбулы
      if (meteoPreambleCount == 0) {
        meteoStartPreamble = cur_timestamp;
      }
      // Проверяем длину HIGH импульса
      if (checkLenPreamblePulse(pulse_duration)) {
        meteoPreambleCount ++;
        if(meteoPreambleCount == PREAMBLE_COUNT){
          // Словили преамбулу
          meteoBitCounter = 0;
          // Расчёт длительности Te
          Te = (cur_timestamp - meteoStartPreamble) / 23; // вычисляем длительность базового импульса Te
          Te2_3 = Te * 3 / 2;
        }
      } else {
        // Не то пальто, обнуляем
        meteoPreambleCount = 0;
      }
    }
    // Ловим битЫ
    if (meteoPreambleCount == PREAMBLE_COUNT && meteoBitCounter < BIT_COUNT) {
      meteoBitArray[meteoBitCounter] = (pulse_duration > Te2_3) ? 0 : 1;
      meteoBitCounter++;
    }
  }
  // Кажется всё поймали
  if (meteoBitCounter == BIT_COUNT) {
    // Перестаём читать, парсим
    meteoListening = false;
    wh14.parseData();
    if (wh14.TxType == 0xA && wh14.calcCRC() == wh14.Crc) {
      wh14.printSerial();
      meteoCorrentData = true;
    }
    // Обнулим счётчики бит
    meteoBitCounter = 0;
    meteoPreambleCount = 0;
  }
}
Предложения по улучшению кода только приветствуются!
 
Изменено:

zeaxm

✩✩✩✩✩✩✩
31 Май 2020
3
0
@Molodoi7772, у меня такая же плата на метеостанции https://mysku.ru/blog/china-stores/75406.html
Протокол удалось расшифровать
C++:
Север                                  Нап  Темп х 10   Влажность Скорость ветра                            CRC(сумма           Повтор пакета
    ID (0xF5)                          рав                       (делить на 588                             предыдущ 12
                                      ление                       для перев. в м/с)                         байт&0xFF)
001 11110101 010110111100010100000000 0000 000100010100 00101001 0000000000000010 0000000000000001 00000000 01010110 11110100 00111110101010110111100010100000000 0000 000100010100 00101001 0000000000000010000000000000000100000000010101100
СевероВосток
001 11110101 010110111100010100000000 0010 000100010000 00101010 0000000000000001 0000000000000001 00000000 01110010 10101000 00111110101010110111100010100000000 0010 000100010000 00101010 0000000000000001000000000000000100000000011100100
Восток
001 11110101 010110111100010100000000 0100 000100010100 00101001 0000000000000001 0000000000000001 00000000 10010101 00101110 00111110101010110111100010100000000 0100 000100010100 00101001 0000000000000001000000000000000100000000100101010
Юго-Восток
001 11110101 010110111100010100000000 0110 000100010100 00101001 0000000000000001 0000000000000001 00000000 10110101 11101101 00111110101010110111100010100000000 0110 000100010100 00101001 0000000000000001000000000000000100000000101101010
ЮГ
001 11110101 010110111100010100000000 1000 000100010000 00101010 0000000000000011 0000000000000001 00000000 11010100 00000101 00111110101010110111100010100000000 1000 000100010000 00101010 0000000000000011000000000000000100000000110101000
Юго-Запад
001 11110101 010110111100010100000000 1010 000100010100 00101001 0000000000000000 0000000000000001 00000000 11110100 10011101 00111110101010110111100010100000000 1010 000100010100 00101001 0000000000000000000000000000000100000000111101000
ЗАПАД
001 11110101 010110111100010100000000 1100 000100010100 00101010 0000000000000000 0000000000000001 00000000 00010101 10101000 00111110101010110111100010100000000 1100 000100010100 00101010 0000000000000000000000000000000100000000000101010
Северо-запад
001 11110101 010110111100010100000000 1110 000100010100 00101010 0000000000000011 0000000000000001 00000000 00111000 01100011 00111110101010110111100010100000000 1110 000100010100 00101010 0000000000000011000000000000000100000000001110000
CRC - это просто сумма 12 байт до CRC (начиная с 11110101 по 00000000) по маске &0XFF (берем младшие 8 бит)

Код ардуино:
C++:
uint8_t count = 0;
volatile uint8_t res[225];
volatile uint8_t flag = 0;
uint8_t head_flag = 0;
volatile unsigned long prevtime;
volatile unsigned int lolen, hilen, state;

#define rxPin 2
#define MAX_DELTA 140

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  ;
  pinMode(rxPin, INPUT_PULLUP);
  attachInterrupt(0, read_input, CHANGE);

}

void loop() {
  // put your main code here, to run repeatedly:
  if (flag == 1) //если были данные
  {
    //Serial.println("\n");

    uint16_t tmp = 0;
    for (uint8_t i = 0; i < 8; i++)
    {
      tmp <<= 1;
      tmp |= res[i] & 1;
    };
    //Serial.println(tmp);

uint8_t crc = 0;
    for (uint8_t i = 96; i < 104; i++)
    {
      crc <<= 1;
      crc |= res[i] & 1;
    };
//    Serial.print("\ncalc_CRC: ");
//    Serial.println(calc_CRC());
//    Serial.print("received CRC: ");
//    Serial.println(crc);


    if (tmp == 0xF5 && crc == calc_CRC()) { //первый байт должен быть 0xF5 (ID станции, может варьироваться от модели и верный CRC
      //Температура
      float temp = 0;
      int16_t temp_tmp = 0;
      int16_t temp_tmp1 = 0;
      for (uint8_t i = 36; i < 48; i++) //биты температуры
      {
        temp_tmp <<= 1; //смещаем влево
        temp_tmp |= res[i] & 1; //прибавляем младший бит
        temp_tmp1 <<= 1;
        temp_tmp1 |= res[i + 112] & 1; //прибавляем младший бит
      };
      //сверяем значение из двух посылок, если не ок, то выставляем в 255
      if (temp_tmp == temp_tmp1) {
        temp = (float)(temp_tmp) / 10.0;
      }
      else temp = 255;


      //Влажность
      uint8_t hum = 0;
      uint8_t hum1 = 0;
      for (uint8_t i = 48; i < 56; i++) //биты влажности
      {
        hum <<= 1;
        hum |= res[i] & 1;
        hum1 <<= 1;
        hum1 |= res[i + 112] & 1;
      };
      if (hum == hum1) {
        hum = hum1;
      }
      else hum = 255;

      //скорость ветра
      uint16_t ch_wind = 0;
      uint16_t ch_wind1 = 0;
      float wind = 0;
      for (uint8_t i = 56; i < 72; i++) //скорость ветра
      {
        ch_wind <<= 1;
        ch_wind |= res[i] & 1;
        ch_wind1 <<= 1;
        ch_wind1 |= res[i + 112] & 1;
      };
      if (ch_wind == ch_wind1) {
        wind = (float)ch_wind / 588;
      }
      else wind = 255;

      //направление ветра
      uint8_t wind_dir = 0;
      uint8_t wind_dir1 = 0;
      for (uint8_t i = 32; i < 36; i++)
      {
        wind_dir <<= 1;
        wind_dir |= res[i] & 1;
        wind_dir1 <<= 1;
        wind_dir1 |= res[i + 112] & 1;
      };
      if (wind_dir1 == wind_dir) {
        wind_dir = wind_dir1;
      }
      else wind_dir = 255;

      Serial.print("Temperature: ");
      Serial.print((temp), 1);
      Serial.print("C; Humidity: ");
      Serial.print(hum);
      Serial.print("%; Wind: ");
      Serial.print(wind);
      Serial.print("m/s; Direction: ");
      switch (wind_dir) {
        case 0: Serial.print("N"); break;
        case 2: Serial.print("NE"); break;
        case 4: Serial.print("E"); break;
        case 6: Serial.print("SE"); break;
        case 8: Serial.print("S"); break;
        case 10: Serial.print("SW"); break;
        case 12: Serial.print("W"); break;
        case 14: Serial.print("NW"); break;
        default: Serial.print("Error"); break;
      }

      //Батарея
      if (res[21] == res[21 + 112])
      {
        Serial.print("; Low battery!!!");
      }
      Serial.print("\n");

    }
    res[0] = 0;
    flag = 0;
    count = 0;
    sei();
  }
}

boolean CheckValue(unsigned int base, unsigned int value) {
  return ((value == base) || ((value > base) && ((value - base) < MAX_DELTA)) || ((value < base) && ((base - value) < MAX_DELTA)));
}

void read_input() {
  state = digitalRead(rxPin);
  if (state == HIGH)
    lolen = micros() - prevtime;
  else
    hilen = micros() - prevtime;
  prevtime = micros();


  if (state == LOW)  {
    // по спаду начинаем анализ
    if (CheckValue(240, hilen) && CheckValue(1520, lolen) && head_flag != 2) { //нашли короткий импульс
      head_flag = 1;
      } else if (head_flag != 2) head_flag = 0; //если нет - все заново
    if (CheckValue(2000, hilen) && CheckValue(1520, lolen) && head_flag != 2) { //нашли длинный импульс
      head_flag = 2;
      } else if (head_flag != 2) head_flag = 0; //если нет - все заново
      // голова посылки найдена начинаем прием полезных 224 бит данных
    if (CheckValue(960, hilen) && head_flag == 2) //бит 1 - 960 мс
    {
      res[count] = 1;
      count++;
      //Serial.print("1");
    }
    else if (CheckValue(480, hilen) && head_flag == 2) // бит 0 - 480 мс
    {
      res[count] = 0;
      count++;
      //Serial.print("0");
    }
    if (count == 223) { // отсчитали нужное число бит, все сбрасываем и запрещаем прерывания
      //Serial.print("\n");
      cli();
      head_flag = 0;
      flag = 1; //уходим в парсинг пакета
      count = 0;
      lolen = 0;
      hilen = 0;
      _delay_ms(250);
    }
  }
}

int calc_CRC() {
  uint8_t tmp_byte = 0;
  uint16_t tmp_result = 0;
  for (uint8_t j = 0; j < 12; j++) {
    tmp_byte = 0;
    for (uint8_t i = 0; i < 8; i++) {
      tmp_byte <<= 1; //смещаем влево
      tmp_byte |= res[8 * j + i] & 1; //прибавляем младший бит
    }
    tmp_result = tmp_result + tmp_byte;
  }
  return (tmp_result & 0xFF);
}
всё нормально работало, а тут холода пошли и на отрицательных значениях муть показывает
 

savenkoffcom

✩✩✩✩✩✩✩
6 Июл 2019
8
0
всё нормально работало, а тут холода пошли и на отрицательных значениях муть показывает
решил свою прошивку проверить на отрицательных температурах, тоже наткнулся на косяк с отображением, оказывается не привел переменную температуры к необходимому для корректного вычисления типу данных. У себя выше в коде поправил, строку
C++:
return (Temperatue-400)/10.0;
заменил на
C++:
return ((double)Temperatue-400)/10.0;
Теперь все ОК:
21:32:58.462 -> Data: 10100011110100010111110100110011000000000000000000000000000000000000100011110001
21:32:58.462 -> TX Type: A | Security Code: 3D | Low Battary: 0 | Temperature: -1.9*C | Humidity: 51% | Wind S: speed: 0.0 m/s, gust: 0.0 m/s | Rain: 0 | CRC: 11110001 -> 0xF1
В используемую Вами версию прошивки не полез, но полагаю, истина где-то рядом ;)