eddy_em: (Default)
eddy_em ([personal profile] eddy_em) wrote2022-06-29 11:44 pm
Entry tags:

Датчик дождя Hydreon RG-11

[profile] sergepl был так любезен, что прислал мне данную железяку, за что ему grand mercy!!!
Документация у этой железяки весьма скудная: куча описания того, как его подключить к дворникам (странно было бы видеть на крыше легковушки такую "дуру") или просто к релюхе, которая кричит "ахтунг" при начале дождя или ливня (можно переключателями настроить пороговые уровни). Но вот выдача данных по последовательному порту совсем уж малодокументирована. В основном мануале сказано, что, мол, читай вспомогательный. Во вспомогательном немного написано, а что до протокола, мол, читай файлик RG-11_12.ser (в котором в непонятной форме тупо описана структура данных и битовые поля, вообще без пояснений, что какое поле знает, мол, сам по названию поймешь).

Первые попытки подключиться к датчику окончились неудачей: валилась всякая откровенная дрянь. В мануале все было четко: судя по картинке, на выходе МК стоит обычный биполярный транзистор-повторитель, подтягивающий шину к +5В, когда МК выдает высокий уровень. И что-то сразу я не подумал посмотреть на осциллографе, как выглядит разделитель между пакетами (оказалось, что там был низкий уровень, но это было позже). Нагуглил, что, оказывается, сигнал-то там инвертированный (а в документации про это — ни слова)! ОК, смонтировал на макетке, используя мосфет, которым я включал питание для ИК-датчика.
Стендик

И таки пошли нормальные данные!!!
ОК, набросал код:
#include <stdio.h>
#include <usefull_macros.h>

typedef struct{
    uint8_t PeakRS;
    uint8_t SPeakRS;
    uint8_t RainAD8;
    uint8_t LRA;
    uint8_t TransRat;
    uint8_t AmbLNoise;
    uint8_t RGBits;
    uint8_t SlowRegIngex;
    uint8_t SlowRegValue;
} rg11;

typedef struct{
    uint8_t RevLevel;     // 12
    uint8_t EmLevel;      // 30..80
    uint8_t RecEmStr;     // 60..66
    uint8_t ABLevel;      // 10
    uint8_t TmprtrF;      // 70..100
    uint8_t PUGain;       // 34..39
    uint8_t ClearTR;      // 60..170
    uint8_t AmbLight;
    uint8_t Bucket;
    uint8_t Barrel;
    uint8_t RGConfig;
    uint8_t DwellT;
    uint8_t SinceRn;
    uint8_t MonoStb;
    uint8_t LightAD;      // 120..136
    uint8_t RainThr;
} slowregs;

// RGBits values:
#define PkOverThr   (1<<0)
#define Raining     (1<<1)
#define Out1On      (1<<2)
#define HtrOn       (1<<3)
#define IsDark      (1<<4)
#define Cndnstn     (1<<5)
#define Freeze      (1<<6)
#define Storm       (1<<7)

static const char* rgbitnames[8] = {
    "PkOverThr",
    "Raining",
    "Out1On",
    "HtrOn",
    "IsDark",
    "Cndnstn",
    "Freeze",
    "Storm"
};

static const char *slowregnames[16] = {
    "RevLevel",
    "EmLevel",
    "RecEmStr",
    "ABLevel",
    "TmprtrF",
    "PUGain",
    "ClearTR",
    "AmbLight",
    "Bucket",
    "Barrel",
    "RGConfig",
    "DwellT",
    "SinceRn",
    "MonoStb",
    "LightAD",
    "RainThr",
};

// minimal packet length (without slow registers)
#define REGMINLEN   (14)
// standard packet length
#define REGLEN      (18)
#define BUFLEN      (32)

static int getv(char s, uint8_t *v){
    if(s >= '0' && s <= '9'){
        *v = s - '0';
        return 1;
    }else if(s >= 'a' && s <= 'f'){
        *v = 10 + s - 'a';
        return 1;
    }
    return 0;
}

static int encodepacket(const char *buf, int len){
    uint8_t databuf[REGLEN/2] = {0};
    static slowregs slow = {0};
    if(len != REGMINLEN && len != REGLEN) return 0;
    for(int i = 0; i < len; ++i){
        int l = i&1; // low part
        int idx = i/2; // data index
        uint8_t v;
        if(!getv(buf[i], &v)) return 0;
        if(l) databuf[idx] |= v;
        else databuf[idx] |= v << 4;
    }
    rg11 r = *((rg11*)databuf);
    printf("PeakRS=%d\n", r.PeakRS);        // 0..5
    printf("SPeakRS=%d\n", r.SPeakRS);      // 0..5
    printf("RainAD8=%d\n", r.RainAD8);      // 64..192
    printf("LRA=%d\n", r.LRA);              // ?
    printf("TransRat=%d\n", r.TransRat);    // 60..170
    printf("AmbLNoise=%d\n", r.AmbLNoise);  // ?
    if(r.RGBits){
        for(int i = 0; i < 8; ++i){
            if(r.RGBits & 1<<i) printf("%s=1\n", rgbitnames[i]);
        }
    }
    uint8_t *s = (uint8_t*) &slow;
    if(len == REGLEN){
        if(r.SlowRegIngex < 16){
            s[r.SlowRegIngex] = r.SlowRegValue;
        }
    }
    for(int i = 0; i < 16; ++i){
        printf("%s=%u\n", slowregnames[i], s[i]);
    }
    printf("\n\n");
    return 1;
}

static int getpacket(TTY_descr *d){
    static int startpacket = 0, buflen = 0;
    static char strbuf[BUFLEN];
    int l = read_tty(d);
    if(l < 1) return 0;
    char s = d->buf[0];
    if(s == 's'){
        startpacket = 1;
        if(buflen){
            l = encodepacket(strbuf, buflen);
            buflen = 0;
            return l;
        }
    }else{
        startpacket = 0;
        strbuf[buflen++] = s;
        if(buflen >= BUFLEN){
            WARNX("Buffer overfull");
            buflen = 0;
        }
    }
    return 0;
}

int main(){
    TTY_descr *x = new_tty("/dev/ttyUSB0", 1200, 1);
    if(!x) return 1;
    x = tty_open(x, 1);
    if(!x) return 2;
    double t0 = dtime();
    while(dtime() - t0 < 10.){
        if(getpacket(x)) t0 = dtime();
    }
    close_tty(&x);
    return 0;
}

И на выходе стал получать нечто вроде
PeakRS=0
SPeakRS=0
RainAD8=112
LRA=122
TransRat=148
AmbLNoise=0
Raining=1
IsDark=1
Storm=1
RevLevel=14
EmLevel=38
RecEmStr=65
ABLevel=10
TmprtrF=85
PUGain=36
ClearTR=143
AmbLight=0
Bucket=12
Barrel=7
RGConfig=0
DwellT=5
SinceRn=0
MonoStb=15
LightAD=127
RainThr=12

Остается лишь разобраться, что там придумали авторы, т.к. их волшебный файлик 'RG-11_12.ser' вообще ни хрена никакой информации не содержит, кроме грубого описания битовых полей некоторых регистров, да имен регистров с их порядковыми номерами!
Разве что стало понятно, что PeakRS и SPeakRS отражают информацию о "каплях", LRA - счетчик от последнего детектирования "дождя", а остальное — да хрен бы и знал!.. Но, по крайней мере, уже с этой информацией можно как-то жить.
Гугол насчет значения полей датчика ничего не подсказал. Народ обычно использует лишь его релюшку, которая срабатывает, в зависимости от настроек переключателями на плате. Но мы же идем другим путем! И путь этот оказался вполне себе правильным. В общем, следующей стадией испытаний будет подключить провода, загерметизировать датчик обратно, да провести испытания в тазике. Жена предложила множество вариантов: от покапать пипеткой и побрызгать из пульверизатора до "обоссать" ☺