Использование внешнего беспроводного термометра Buro H999 совместно с самодельными устройствами

    Всем хороша погодная станция Buro H146G с внешним беспроводным термометром H999. Но вот только чтобы увидеть показания на её блеклом ЖК-дисплее требуется хорошее освещение. А мне было бы лучше, если бы вывод температуры и влажности за окном отображался на достаточно ярких индикаторах (например, совместив отображение температуры и влажности с часами на газоразрядных индикаторах ИН-12). Сделать такую поделку несложно, но нужно знать протокол обмена с беспроводным термометром. Здесь уже были статьи про использование беспроводного термометра метеостанций для получения температуры и влажности по радиоканалу. Но для станций Buro протокол обмена ещё не был описан. Значит, надо это исправить: возможно, кому-то он может пригодиться.

    В интернете описания протокола обмена станций BURO я не нашёл. А это значит, что придётся вскрывать протокол обмена этого беспроводного датчика.

    Мой внешний термометр выглядит так:



    Подключив к осциллографу китайский сверхрегенеративный приёмник на 433,92 МГц и нажав кнопку TEST на термометре, было отчётливо видно, как бегут импульсы передачи. Ну а так как частота там небольшая, выход приёмника был подключён к входу звуковой карты через резистивный делитель. После обработки записанного звукового файла компаратором получилась следующая картинка:



    Как и у других погодных станций модуляция осуществляется изменением скважности. Начинается передача с блока синхросигналов, потом идёт ещё один синхросигнал, а потом идут данные, после которых идёт завершающий синхросигнал. Два ноля после синхросигнала, по-видимому, являются идентификатором начала данных – во всяком случае, я ни разу не заметил их изменения. Данные с начальным и конечным синхросигналом дублируются шесть раз. Обмен данными ведётся нибблами.

    Для декодирования я решил начинать приём по первому синхросигналу и двум нулям, а завершать по последнему синхросигналу.

    Чтобы такой сигнал декодировать, достаточно посчитать длительности между перепадами сигнала.

    Я для этого написал простенькую тестовую программу для контроллера Atmega8:

    Программа для Atmega8
    //----------------------------------------------------------------------------------------------------
    //библиотеки
    //----------------------------------------------------------------------------------------------------
    #include <avr/io.h>
    #include <util/delay.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <stdio.h>
    #include <avr/interrupt.h>
    #include <avr/pgmspace.h>
    #include <string.h>
    #include <stdbool.h>
    #include <stdint.h>
     
    //----------------------------------------------------------------------------------------------------
    //частота контроллера
    //----------------------------------------------------------------------------------------------------
    #define F_CPU 8000000UL
    
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    //макроопределения
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     
    //скорость передачи данных UART, бит/с
    #define UART_SPEED 9600UL
    
    //----------------------------------------------------------------------------------------------------
    //перечисления
    //----------------------------------------------------------------------------------------------------
    
    //тип блока
    enum BLOCK_TYPE
    {
     BLOCK_TYPE_UNKNOW,//неизвестный блок
     BLOCK_TYPE_DIVIDER,//разделитель
     BLOCK_TYPE_SYNCHRO,//синхросигнал
     BLOCK_TYPE_ONE,//единица
     BLOCK_TYPE_ZERO//ноль
    };
    
    //режим декодирования
    enum MODE
    {
     MODE_WAIT_SYNCHRO,//ожидание синхросигнала
     MODE_WAIT_ZERO_FIRST,//ожидание первого нуля
     MODE_WAIT_ZERO_SECOND,//ожидание второго нуля
     MODE_RECEIVE_DATA//приём данных
    }; 
     
    //----------------------------------------------------------------------------------------------------
    //глобальные переменные
    //----------------------------------------------------------------------------------------------------
    static const uint16_t MAX_TIMER_INTERVAL_VALUE=0xFFFF;//максимальное значение интервала таймера
    
    static volatile bool TimerOverflow=false;//было ли переполнение таймера
    
    static uint8_t Buffer[20];//буфер сборки полубайта
    static uint8_t BitSize=0;//количество принятых бит
    static uint8_t Byte=0;//собираемый байт
    
    //----------------------------------------------------------------------------------------------------
    //прототипы функций
    //----------------------------------------------------------------------------------------------------
     
    void InitAVR(void);//инициализация контроллера
    void UART_Write(unsigned char byte);//передача символа в COM-порт
    void SendText(const char *text);//отправить текст в COM-порт
    
    void RF_Init(void);//инициализация
    void RF_SetTimerOverflow(void);//установить флаг переполнения таймера
    void RF_ResetTimerOverflow(void);//сбросить флаг переполнения таймера
    bool RF_IsTimerOverflow(void);//получить, есть ли переполнение таймера
    uint16_t RF_GetTimerValue(void);//получить значение таймера
    void RF_ResetTimerValue(void);//сбросить значение таймера 
    BLOCK_TYPE RF_GetBlockType(uint32_t counter,bool value);//получить тип блока
    void RF_AddBit(bool state);//добавить бит данных
    void RF_ResetData(void);//начать сборку данных заново
    void RF_AnalizeCounter(uint32_t counter,bool value,MODE &mode);//анализ блока
    
    //----------------------------------------------------------------------------------------------------
    //основная функция программы
    //----------------------------------------------------------------------------------------------------
    int main(void)
    {
     InitAVR();  
     _delay_ms(200); 
     SendText("Thermo unit\r\n");
     _delay_ms(200);  
     sei();
     while(1);
     cli();  
    }
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    //общие функции
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     
    //----------------------------------------------------------------------------------------------------
    //инициализация контроллера
    //----------------------------------------------------------------------------------------------------
    void InitAVR(void)
    {
     //настраиваем порты
     DDRB=0;
     DDRD=0;
     DDRC=0; 
     //задаём состояние портов
     PORTB=0;
     PORTD=0;
     PORTC=0;
     
     //устанавливаем режим передачи данных UART
     UCSRB=(1<<RXEN)|(1<<TXEN)|(0<<RXCIE);  
     //RXCIE=1 и прерывания разрешены (бит I=1 в регистре SREG) : прерывание по завершению приёма по UART разрешено
     //TXCIE=1 и прерывания разрешены (бит I=1 в регистре SREG) : прерывание по завершению передачи по UART разрешено
     //UDRIE=1 и прерывания разрешены (бит I=1 в регистре SREG) : прерывание по опустошению регистра данных UART разрешено
     //RXEN=1 : активация приёмника, вывод D0 становится входом UART.
     //TXEN=1 : активация передатчика, вывод D1 становится выходом UART.
     //CHR9=1 : длина передаваемой посылки с становится равной 11 бит (9 бит данных + старт-стоповый бит + стоп-бит).
     //RXB8-расширенный стоп-бит
     //TXB8-расширенный стоп-бит
     //вычисляем значение регистра скорости передачи данных
     unsigned long speed=F_CPU/(16UL);
     speed=(speed/UART_SPEED)-1UL;
     UBRRH=(speed>>8)&0xff;
     UBRRL=speed&0xFF;
     
     RF_Init();  
    }
    //----------------------------------------------------------------------------------------------------
    //передача символа в COM-порт
    //----------------------------------------------------------------------------------------------------
    void UART_Write(unsigned char byte)
    { 
     while(!(UCSRA&(1<<UDRE)));
     UDR=byte;
    }
    
    //----------------------------------------------------------------------------------------------------
    //отправить текст в COM-порт
    //----------------------------------------------------------------------------------------------------
    void SendText(const char *text)
    {
     while((*text))
     {
      UART_Write(*text);
      text++;
     }
    }
    
    //----------------------------------------------------------------------------------------------------
    //инициализация
    //----------------------------------------------------------------------------------------------------
    void RF_Init(void)
    {
     //настраиваем аналоговый компаратор
     ACSR=(0<<ACD)|(1<<ACBG)|(0<<ACO)|(0<<ACI)|(1<<ACIE)|(0<<ACIC)|(0<<ACIS1)|(0<<ACIS0);
     //ACD - включение компаратора (0 - ВКЛЮЧЁН!)
     //ACBG - подключение к неинвертрирующему входу компаратора внутрреннего ИОН'а
     //ACO - результат сравнения (выход компаратора)
     //ACI - флаг прерывания от компаратора
     //ACIE - разрешение прерываний от компаратора
     //ACIC - подключение компаратора к схеме захвата таймера T1
     //ACIS1,ACID0 - условие генерации прерывания от компаратора
      
     //настраиваем таймер T1 на частоту 31250 Гц
     TCCR1A=(0<<WGM11)|(0<<WGM10)|(0<<COM1A1)|(0<<COM1A0)|(0<<COM1B1)|(0<<COM1B0);
     //COM1A1-COM1A0 - состояние вывода OC1A
     //COM1B1-COM1B0 - состояние вывода OC1B 
     //WGM11-WGM10 - режим работы таймера
     TCCR1B=(0<<WGM13)|(0<<WGM12)|(1<<CS12)|(0<<CS11)|(0<<CS10)|(0<<ICES1)|(0<<ICNC1);
     //WGM13-WGM12 - режим работы таймера
     //CS12-CS10 - управление тактовым сигналом (выбран режим деления тактовых импульсов на 256 (частота таймера 31250 Гц))
     //ICNC1 - управление схемой подавления помех блока захвата
     //ICES1 - выбор активного фронта сигнала захвата
     TCNT1=0;//начальное значение таймера
     TIMSK|=(1<<TOIE1);//прерывание по переполнению таймера (таймер T1 шестнадцатибитный) 
    } 
    //----------------------------------------------------------------------------------------------------
    //установить флаг переполнения таймера
    //----------------------------------------------------------------------------------------------------
    void RF_SetTimerOverflow(void)
    {
     cli();
     TimerOverflow=true;
     sei();
    }
    //----------------------------------------------------------------------------------------------------
    //сбросить флаг переполнения таймера
    //----------------------------------------------------------------------------------------------------
    void RF_ResetTimerOverflow(void)
    {
     cli();
     TimerOverflow=false;
     sei();
    }
    //----------------------------------------------------------------------------------------------------
    //получить, есть ли переполнение таймера
    //----------------------------------------------------------------------------------------------------
    bool RF_IsTimerOverflow(void)
    {
     cli();
     bool ret=TimerOverflow;
     sei();
     return(ret);
    }
    
    //----------------------------------------------------------------------------------------------------
    //получить значение таймера 
    //----------------------------------------------------------------------------------------------------
    uint16_t RF_GetTimerValue(void)
    {
     cli();
     uint16_t ret=TCNT1;
     sei(); 
     return(ret);
    } 
    
    
    //----------------------------------------------------------------------------------------------------
    //сбросить значение таймера 
    //----------------------------------------------------------------------------------------------------
    void RF_ResetTimerValue(void)
    {
     cli();
     TCNT1=0;
     sei();
     RF_ResetTimerOverflow();
    } 
    //----------------------------------------------------------------------------------------------------
    //получить тип блока
    //----------------------------------------------------------------------------------------------------
    BLOCK_TYPE RF_GetBlockType(uint32_t counter,bool value)
    { 
     static const uint32_t DIVIDER_MIN=(31250UL*12)/44100UL;
     static const uint32_t DIVIDER_MAX=(31250UL*25)/44100UL;
     static const uint32_t ZERO_MIN=(31250UL*80)/44100UL;
     static const uint32_t ZERO_MAX=(31250UL*100)/44100UL;
     static const uint32_t ONE_MIN=(31250UL*160)/44100UL;
     static const uint32_t ONE_MAX=(31250UL*200)/44100UL;
     static const uint32_t SYNCHRO_MIN=(31250UL*320)/44100UL;
     static const uint32_t SYNCHRO_MAX=(31250UL*400)/44100UL;
     
    
     if (counter>DIVIDER_MIN && counter<DIVIDER_MAX) return(BLOCK_TYPE_DIVIDER);//разделитель
     if (counter>ZERO_MIN && counter<ZERO_MAX) return(BLOCK_TYPE_ZERO);//ноль
     if (counter>ONE_MIN && counter<ONE_MAX) return(BLOCK_TYPE_ONE);//один
     if (counter>SYNCHRO_MIN && counter<SYNCHRO_MAX) return(BLOCK_TYPE_SYNCHRO);//синхросигнал
     return(BLOCK_TYPE_UNKNOW);//неизвестный блок
    }
    //----------------------------------------------------------------------------------------------------
    //добавить бит данных
    //----------------------------------------------------------------------------------------------------
    void RF_AddBit(bool state)
    {
     if ((BitSize>>2)>=19) return;//буфер заполнен
     Byte<<=1;
     if (state==true) Byte|=1;
     BitSize++; 
     if ((BitSize&0x03)==0)
     {
      Buffer[(BitSize>>2)-1]=Byte;
      Byte=0;
     }
    }
    //----------------------------------------------------------------------------------------------------
    //начать сборку данных заново
    //----------------------------------------------------------------------------------------------------
    void RF_ResetData(void)
    {
     BitSize=0;
     Byte=0;
    }
    
    //----------------------------------------------------------------------------------------------------
    //анализ блока
    //----------------------------------------------------------------------------------------------------
    void RF_AnalizeCounter(uint32_t counter,bool value,MODE &mode)
    {
     //узнаем тип блока
     BLOCK_TYPE type=RF_GetBlockType(counter,value);
    
     if (type==BLOCK_TYPE_UNKNOW)
     {
      mode=MODE_WAIT_SYNCHRO;
      RF_ResetData();
      return;
     } 
     if (type==BLOCK_TYPE_DIVIDER) return;//разделитель бесполезен для анализа 
     //посылка должна начинаться и завершаться синхросигналом
     if (mode==MODE_WAIT_SYNCHRO)//ждём синхросигнала
     {
      if (type==BLOCK_TYPE_SYNCHRO)
      {
       mode=MODE_WAIT_ZERO_FIRST;
       return;
      }
      mode=MODE_WAIT_SYNCHRO;
      RF_ResetData();
      return;
     }
     if (mode==MODE_WAIT_ZERO_FIRST || mode==MODE_WAIT_ZERO_SECOND)//ждём два нуля
     {
      if (type==BLOCK_TYPE_SYNCHRO && mode==MODE_WAIT_ZERO_FIRST) return;//продолжается синхросигнал
      if (type==BLOCK_TYPE_ZERO && mode==MODE_WAIT_ZERO_FIRST)
      {
       mode=MODE_WAIT_ZERO_SECOND;
       return;
      }
      if (type==BLOCK_TYPE_ZERO && mode==MODE_WAIT_ZERO_SECOND)
      {
       mode=MODE_RECEIVE_DATA;
       return;
      }
      mode=MODE_WAIT_SYNCHRO;
      RF_ResetData();
      return;
     }
     //принимаем данные
     if (type==BLOCK_TYPE_SYNCHRO)//приём окончен
     {
      uint8_t size=(BitSize>>2);
      char str[30];
      if  (size!=10)
      {
       mode=MODE_WAIT_SYNCHRO;
       RF_ResetData();
       return; 
      }
      //выдаём блок
      for(uint8_t n=0;n<size;n++)
      {
       uint8_t b=Buffer[n];  
       uint8_t mask=(1<<3);
       for(uint8_t m=0;m<4;m++,mask>>=1)
       {
        if (b&mask) SendText("1");
    	       else SendText("0");
       }
       SendText(" "); 
      }
      
      uint8_t channel=Buffer[2]&0x03;
      uint8_t key=(Buffer[8]>>3)&0x01;
      uint8_t h=(Buffer[7]<<4)|(Buffer[6]);//влажность
      int16_t temp=(Buffer[5]<<8)|(Buffer[4]<<4)|(Buffer[3]);//температура 
      int16_t k=18;
      int16_t t=(10*(temp-1220))/k;
      sprintf(str,"%i",key);
      SendText("Key:");
      SendText(str);  
      
      sprintf(str,"%i",channel+1);  
      SendText(" Ch:");
      SendText(str);  
      sprintf(str,"%i",h);  
      SendText(" H:");
      SendText(str);
      SendText("%, T:");
      if (t<0)
      {
       t=-t;
       sprintf(str,"-%i.%i",(int)(t/10),(int)(t%10));
      }
      else
      {
       sprintf(str,"%i.%i",(int)(t/10),(int)(t%10));
      }  
      SendText(str);
      SendText(" C\r\n");
      mode=MODE_WAIT_SYNCHRO;
      RF_ResetData();
      return;
     }
     //приём данных
     if (type==BLOCK_TYPE_ONE)
     {
      RF_AddBit(true);
      return;
     }
     if (type==BLOCK_TYPE_ZERO)
     {
      RF_AddBit(false);
      return;
     }
     mode=MODE_WAIT_SYNCHRO;
     RF_ResetData();
    }
    
    
     
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    //обработчики векторов прерываний
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
      
    //----------------------------------------------------------------------------------------------------
    //обработчик вектора прерывания таймера T1 (16-ми разрядный таймер) по переполнению
    //----------------------------------------------------------------------------------------------------
    ISR(TIMER1_OVF_vect)
    {   
     RF_SetTimerOverflow();
    } 
     
    //----------------------------------------------------------------------------------------------------
    //обработчик вектора прерывания от компаратора
    //----------------------------------------------------------------------------------------------------
    ISR(ANA_COMP_vect)
    {
     ACSR&=0xFF^(1<<ACIE);//запрещаем прерывания
     ACSR|=(1<<ACI);//сбрасываем флаг прерывания компаратора
     
     static MODE mode=MODE_WAIT_SYNCHRO;
     
     //узнаём длительность интервала
     uint16_t length=RF_GetTimerValue();
     if (RF_IsTimerOverflow()==true) length=MAX_TIMER_INTERVAL_VALUE;//было переполнение, считаем интервал максимальным
     RF_ResetTimerValue();
     //отправляем на анализ
     bool value=true;
     if (ACSR&(1<<ACO)) value=false;
     RF_AnalizeCounter(length,value,mode);
     ACSR|=(1<<ACIE);//разрешаем прерывания
    }
    


    Выход приёмника подключается к выводу 13 (AIN1). Atmega через max232 подключается к COM-порту компьютера (ну или к переходнику USB-COM). Скорость работы порта 9600 бод.

    После декодирования получится следующий поток данных (два начальных ноля я выбрасываю):

    //без кнопки, канал 1
    1100 1100 0000 1110 1000 0110 1100 0001 0000 1001 Влажность:28% Температура:25.4
    //без кнопки, канал 2
    1100 1100 0001 1110 1000 0110 1101 0001 0000 0110 Влажность:29% Температура:25.4

    Итого, пакет выглядит так:



    I0-I7 – идентификатор термометра. При каждом новом включении термометра идентификатор меняется.

    C0-C1 — канал (всего их возможно 3). Каналы нумеруются с нуля.

    H0-H7 — влажность. Влажность в процентах считывается как есть, а вот температура (T0-T11) почему-то задана в необычном для метеостанций формате. Судя по найденным мной описаниям протоколов обмена различных метеостанций, можно было бы ожидать температуру в десятых долях градуса и со смещением нижнего предела измерения термометра. Так вот, нет. Эксперименты показали, что код температуры данной метеостанции переводится в градусы Цельсия как (T-1220)/18. Откуда эти магические числа знают только китайцы, придумавшие этот протокол обмена.

    Как подсказал wolowizard в комментариях, станция передаёт температуру в десятых долях градусов Фаренгейта, поэтому осмысленный перевод в градусы Цельсия будет 0.1*(T-320)*5/9-500=0.1*(T-1220)/1.8.

    Бит K соответствует нажатию на кнопку TEST.

    Назначение остальных полей установить не удалось, но выяснилось, что значение переключателя Фаренгейты/Цельсии на термометре в протокол обмена не попадает. Предположительно так же последний ниббл (а может, и часть предпоследнего) является CRC, но вычислить алгоритм мне пока не удалось (есть подозрение, что в вычислении участвуют строки и столбцы нибблов). Если кто-нибудь сумеет разгадать эту загадку, сообщите, пожалуйста, алгоритм вычисления.
    Для желающих поломать голову, но не имеющих такого термометра, привожу таблицу принятых данных.

    Таблица
    1001 0110 0101 1011 1000 0110 1000 0010 0001 1111 Key:0 Ch:2 H:40%, T:25.2 C
    1001 1001 0000 1101 1010 0100 0101 0101 0000 0110 Key:0 Ch:1 H:85%, T:-1.2 C
    1001 0110 0101 1100 1000 0110 1010 0010 0001 0100 Key:0 Ch:2 H:42%, T:25.3 C
    1001 0110 1001 0110 0111 0110 1101 0001 0010 1111 Key:0 Ch:2 H:29%, T:24.1 C
    1001 0110 1001 0000 0111 0110 1101 0001 0010 1000 Key:0 Ch:2 H:29%, T:23.7 C
    1001 0110 1001 0010 0101 0110 1110 0001 0010 1111 Key:0 Ch:2 H:30%, T:22.1 C
    1001 0110 1001 1001 0011 0110 1110 0001 0010 1100 Key:0 Ch:2 H:30%, T:20.7 C
    1001 0110 1001 1111 0001 0110 1111 0001 0010 1010 Key:0 Ch:2 H:31%, T:19.2 C
    1001 0110 0101 1001 0000 0110 0001 0010 0010 1000 Key:0 Ch:2 H:33%, T:18.0 C
    1001 0110 0101 0010 1111 0101 0010 0010 0010 0111 Key:0 Ch:2 H:34%, T:16.7 C
    1001 0110 0101 0100 1110 0101 0010 0010 0010 0010 Key:0 Ch:2 H:34%, T:16.0 C
    1001 0110 0101 0100 1101 0101 0011 0010 0010 0001 Key:0 Ch:2 H:35%, T:15.1 C
    1001 0110 0101 1100 1100 0101 0100 0010 0010 1110 Key:0 Ch:2 H:36%, T:14.6 C
    1001 0110 0101 1111 1011 0101 0101 0010 0010 1111 Key:0 Ch:2 H:37%, T:13.9 C
    1001 0110 0101 0011 1011 0101 0101 0010 0010 0001 Key:0 Ch:2 H:37%, T:13.2 C
    1001 0110 0101 1001 1010 0101 0110 0010 0010 0101 Key:0 Ch:2 H:38%, T:12.7 C
    1001 0110 0101 0100 1010 0101 0111 0010 0010 1000 Key:0 Ch:2 H:39%, T:12.4 C
    1001 0110 0101 1011 1001 0101 0111 0010 0010 1010 Key:0 Ch:2 H:39%, T:11.9 C
    1001 0110 0101 0011 1001 0101 1000 0010 0010 1001 Key:0 Ch:2 H:40%, T:11.5 C
    1001 0110 0101 1011 1000 0101 1000 0010 0010 1110 Key:0 Ch:2 H:40%, T:11.0 C
    1001 0110 0101 0111 1000 0101 1001 0010 0010 0101 Key:0 Ch:2 H:41%, T:10.8 C
    1001 0110 0101 1111 0111 0101 1001 0010 0010 1101 Key:0 Ch:2 H:41%, T:10.3 C
    1001 0110 0101 0111 0111 0101 1010 0010 0010 0111 Key:0 Ch:2 H:42%, T:9.9 C
    1001 0110 0101 0001 0111 0101 1011 0010 0010 0101 Key:0 Ch:2 H:43%, T:9.6 C
    1001 0110 0101 1011 0110 0101 1100 0010 0010 0110 Key:0 Ch:2 H:44%, T:9.2 C
    1001 0110 0101 1000 0110 0101 1100 0010 0010 1100 Key:0 Ch:2 H:44%, T:9.1 C
    1001 0110 0101 0011 0110 0101 1101 0010 0010 0110 Key:0 Ch:2 H:45%, T:8.8 C
    1001 0110 0101 1001 0101 0101 1110 0010 0010 0110 Key:0 Ch:2 H:46%, T:8.2 C
    1001 0110 0101 0101 0101 0101 1111 0010 0010 1101 Key:0 Ch:2 H:47%, T:8.0 C
    1001 0110 0101 0010 0101 0101 1111 0010 0010 1100 Key:0 Ch:2 H:47%, T:7.8 C
    1001 0110 0101 1110 0100 0101 1111 0010 0010 0000 Key:0 Ch:2 H:47%, T:7.6 C
    1001 0110 0101 1100 0100 0101 1111 0010 0010 1100 Key:0 Ch:2 H:47%, T:7.5 C

    Поделиться публикацией

    Комментарии 15

      +1
      При одинаковых показателях пакеты полностью одинаковые? Я к тому что может там не crc, а таймер?
        0
        Да, полностью одинаковые. Там очень похоже на CRC.
          0
          CRC там может быть просто арифметическая сумма всех байтов в посылке. А температура — в десятых долях Фаренгейта. Как раз недавно разбирался с подобной.
            0
            Так ведь нет, в том-то и дело. CRC несколько хитрее. И температура практически в кодах АЦП.
              +1
              у вас какой-то странный порядок бит в таблице, подозреваю что при разборе пакета полубайты перемешиваются. Например, если поменять местами полубайты в первой посылке:
              1001 0110 0101 1011 1000 0110 1000 0010 0001 1111 Key:0 Ch:2 H:40%, T:25.2 C
              на
              1001 0110 0101 0110 1000 1011 0010 1000 0001 1111

              то получим:
              0010 1000 = 40, это влажность. Дальше
              0110 1000 1011 = 1675 — температура. Переводим по формуле
              (1675-320)*5/9-500=252, или 25,2 градуса Цельсия.
              Буквально несколько дней назад разбирался с подобным датчиком. Тоже довольно быстро нашел биты канала, биты поля влажности, и столкнулся со странным форматом поля температуры. Помог хабраюзер ValeriVP — он прямо указал что температура в таких датчиках передается в долях Фаренгейта и CRC рассчитывается как младший байт арифметической суммы всех байт пакета.
              Кстати, я смотрел сигнал прямо со входа передатчика цифровым осциллографом и увидел там типичный манчестерский код. Соответственно алгоритм приема у меня реализован по-другому. Сначала идет четыре импульса по 700 мкс, потом 40 бит данных в формате MSB first. Если последовательно записать эти 40 бит, то разбирать их можно будет в привычном формате.
                0
                Они в таком виде и приходят. Просто там little-endian по нибблам. Я в таблице это ведь и указал. А CRC точно не арифметическая сумма (и не xor), но что-то к ней близкое, так как иногда совпадает с ней. Я делал перебор различных операций (сумма, разность, xor, or,and, пропуск ниббла, инверсия, вычитание из 0x0f), так вот, мне не удалось найти такую последовательность операций, чтобы CRC всегда совпадало на всех пакетах. Но я заметил, что если третий ниббл нулевой, то в предпоследнем ниббле последний бит тоже всегда ноль и наоборот. Почему так — я не знаю. Поэтому у меня есть предположение, что CRC больше 4 бит и может быть связано с какой-либо операцией на строках и столбцах выписанных блоком нибблов.

                (1675-320)*5/9-500=252, или 25,2 градуса Цельсия.


                А, вот как. Кстати, очень похоже, что так и сделано. Спасибо за подсказку! Вот откуда 1220 = 320+500*9/5 и 18=10*9/5. Понятно. :)

                и увидел там типичный манчестерский код.


                Нет, там не похоже на манчестер, потому что ширина высокого уровня всегда одинакова и меньшая из всех.

                Если последовательно записать эти 40 бит,


                Так я их и записал последовательно. Там ровно 40 бит. У вас какой датчик?
                  0
                  датчик у меня вот такой.
                  А если сделать перестановки для всех байт пакета и просуммировать, может тогда CRC совпадет с суммой? Вряд ли там слишком много модификаций электроники.
                  Пакет у меня выглядел вот так:

                  Но вы смотрели уже с выхода приемника, может это повлияло на длительность импульсов
                    0
                    Да, у вас похоже на манчестерское кодирование.
                    А приёмник у меня самый обычный китайский RF-модуль (сверхрегенератор с выходным компаратором). Он ничего не умеет декодировать. Просто производители станций придумывают протоколы обмена и модуляцию кто во что горазд. :)

                    А если сделать перестановки для всех байт пакета и просуммировать, может тогда CRC совпадет с суммой?


                    А почему вы предлагаете суммировать байты пакета (их там 4.5), а не нибблы (при суммировании байт старший ниббл практически не участвует в младшем ниббле CRC)? А от перестановки нибблов сумма ожидаемо не изменится.
                    Я видел в одном из протоколов обмена со станциями такую CRC: 0x0f-n1-n2-n3… Это я тоже делал — бесполезно.
                      0
                      просто формат данных очень похож на тот, что в моих датчиках — предположил, что там используется один и тот же чип. В моих датчиках все было логично — 5 байт: 4 данных и 5й — сумма. Все сошлось с первого раза, другие варианты даже не пробовал. Но у вас с передачей нибблами, возможно, все совсем иначе
        +1
        Возможно, напряжение батарейки еще где-то кодируется.
          0
          Да, это возможно. Такой бит описывается в протоколах обмена других станций.
          +2

          rtl_433 этот протокол не понимает?

            0
            Не знаю. Я такую штуку никогда не использовал. :)
              +1
              Скорее всего он знает протокол и выдаст что-то в этом роде:
              image
                0
                Посмотрел список устройств в папке device у rtl_433. Похоже, моего термометра она не знает.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое