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

01.08.2018
7
9
3
#1
Доброго времени суток, год назад приобрёл на Али метеостанцию MiSOL WH2310, у неё имеются выносные датчики дождя, освещённости, уровня ультрафиолетового излучения, скорости и направления ветра, а так же стандартные датчики температуры и влажности, барометр находится в приёмной части, как и в большинстве метеостанций(барометр соберу отдельно).
IMG_2978.jpeg IMG_2982.jpeg
Она стабильно работает и почти нормально записывает показания в память, раз в неделю, если нужна статистика, приходится подключать её к компьютеру, скачивать данные и обнулять память. И всё бы ничего, но примерно через полгода при достижении уровня памяти более 80% станция начала удалять почти все показания, записать ничего нормально не получалось, а записывать данные каждый день было не очень хорошей идеей, поэтому начал изучать что можно с ней сделать. А предложений было не много, в основном станцию подключали к RaspberryPI по USB и периодически считывали данные, но такой подход мне не подходит, поскольку станция должна быть автономной.
Поэтому я пошёл другим путём - купил на Али приёмник RXB6(изначально пробовал другие, но этот оказался самый стабильный, так же пробовал считывать с Si4432 в режиме RAW, но так и не вышло отфильтровать полезный сигнал). Поскольку осциллограф Hantek 6104BC у меня нормально не смог принять пакетную передачу и буфер у него всего 4кб, пришлось купить китайский логический анализатор, клон Saeale Logic pro, с помощью него и, сначала штатной программы, а затем PulseView, удалось вычислить биты передаваемой информации, где, скорее всего, первые 7 бит это преамбула, здесь единица это импульс 500микросекунд, а ноль 1500микросекунд.
IMG_3160.jpeg MiSOL_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, которое меняется при каждой перезагрузке внешнего блока, затем данные отличаются в разных пакетах, приведу их так:
Код:
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

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

Код:
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 из-за помех периодически приходит информация от каких-то левых устройств между посылками, и информация получается неверная
 
Последнее редактирование:
01.08.2018
7
9
3
#2
Оставлю несколько двоичных данных для примера:

Код:
011100110100001101010101000001100101011100010000|01010110 > 734355065710 56     
011100110100001101010101000001001010100010010000|00100001 > 73435504A890 21
011100110100011101010101000010110100010110010100|01111101 > 7347550B4594 7D
011100110100010001010101000000111011110101001100|11111001 > 73445503BD4C F9
011100110100011001010101000010001001101011000000|10000011 > 734655089AC0 83
011100110100010001010101000001000101010101110000|11100011 > 734455045570 E3
Код:
101011101111001010000100001001010000011000001010000000010010110000001000|01010011 > AEF28425060A012C08 53
101011101111001001111011001111100000001000000011000000010001001100001000|00100110 > AEF27B3E0203011308 26
101011101111001010001000001010010000001000000011000000010010110000001010|00110110 > AEF288290203012C0A 36
101011101111001000001110010101100000000000000000000000010010111000001110|00001110 > AEF20E560000012E0E 0E
101011101111001000001100010110100000000000000001000000010010111000001100|10100011 > AEF20C5A0001012E0C A3
101011101111001000001100010110100000000000000000000000010010111000001100|00111000 > AEF20C5A0000012E0C 38
 
06.07.2019
2
0
1
#3
Оставлю несколько двоичных.....
Собрался заказывать себе аналогичную метеостанцию для проекта. Вычитывать тоже буду с Ардуино с последующей передачей на сайт, поэтому ваша информация очень полезна. Как только будет что дополнить по данной теме - я дам знать, а пока - очень хорошее начало, я не знал даже, как подойти к этому вопросу.
 
01.08.2018
7
9
3
#4
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.jpeg Meteo_data_arduino.png

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

Вложения

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