Connecting Stuff

Décodage des protocoles Oregon Scientific sur Arduino (1/3)

Cet article est le premier d’une petite série. Voulant me passer de mon Rfxcom, je cherche à implémenter les protocoles qu’il décode, du moins ceux que j’utilise, dans un Arduino.
J’ai commencé par les protocoles Oregon Scientific v2 (et v3) qui permettent de recevoir les informations des sondes (température, humidité, vent, etc) du même nom.

Un Décodeur Oregon Scientific pour Arduino

Je n’ai pas implémenté le décodeur moi même, j’ai utilisé celui-ci : http://jeelabs.net/projects/cafe/wiki/Decoding_the_Oregon_Scientific_V2_protocol (Vous y trouverez un sketch Arduino qui contient d’autres décodeurs, dont celui du protocole v3 que je n’ai pas testé, n’ayant pas de sonde v3 pour le moment).
Malheureusement, lors de mes premiers tests, impossible de recevoir la moindre donnée de mes sondes THN132N (protocole Oregon v2) :/

Deux problèmes

Du coup j’ai lu les specs des protocoles Oregon Scientific ici : http://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf . J’y ai trouvé deux problèmes à l’origine de mes soucis. Le premier, c’est que le décodeur ne décode que les trames de 80bits de longueur … or la spec indique que les trames sont de taille non fixe. La plupart des sondes renvoient effectivement 80bits mais d’autres non. Les THN132N ne renvoient par exemple que 68bits.

Le deuxième problème c’est que le décodeur attends 32bits de préambule (et c’est bien ce que la spec indique) mais avec mes sondes, je n’en reçois que 31 ? :/ … sur ce coup là aucune explications. J’ai contacté l’auteur du décodeur qui m’a répondu très vite (merci à lui :) et m’explique qu’il arrive que le récepteur « loupe » les premiers bits (si j’ai bien compris) mais que l’on peu considérer qu’au bout d’au moins 24bits de préambule la trame qui suit est bien une trame Oregon v2. Je modifie (en surbrillance voir ci-dessous) donc le décodeur et tout marche maintenant :)

Le code du décodeur Oregon Scientific modifié

class OregonDecoderV2 : public DecodeOOK {
  public:   

    OregonDecoderV2() {}

    // add one bit to the packet data buffer
    virtual void gotBit (char value) {
        if(!(total_bits & 0x01))
        {
            data[pos] = (data[pos] >> 1) | (value ? 0x80 : 00);
        }
        total_bits++;
        pos = total_bits >> 4;
        if (pos >= sizeof data) {
            Serial.println("sizeof data");
            resetDecoder();
            return;
        }
        state = OK;
    }

    virtual char decode (word width) {
       if (200 <= width && width < 1200) {
            //Serial.println(width);
            byte w = width >= 700;

            switch (state) {
                case UNKNOWN:
                    if (w != 0) {
                        // Long pulse
                        ++flip;
                    } else if (w == 0 && 24 <= flip) {
                        // Short pulse, start bit
                        flip = 0;
                        state = T0;
                    } else {
                        // Reset decoder
                        return -1;
                    }
                    break;
                case OK:
                    if (w == 0) {
                        // Short pulse
                        state = T0;
                    } else {
                        // Long pulse
                        manchester(1);
                    }
                    break;
                case T0:
                    if (w == 0) {
                      // Second short pulse
                        manchester(0);
                    } else {
                        // Reset decoder
                        return -1;
                    }
                    break;
              }
        } else if (width >= 2500  && pos >= 8) {
            return 1;
        } else {
            return -1;
        }
        return 0;
    }
};

Le hardware

Coté hardware, il suffit de connecter un récepteur RF433 sur l’Arduino. J’utilise celui-ci pour mes tests : http://www.watterott.com/en/RF-Link-2400bps-Receiver-434MHz mais l’auteur du décodeur qui en a testé plusieurs m’a conseillé celui ci : http://www.lextronic.fr/P865-recepteur-43392-mhz-rrq3-433.html (moins sensible au bruit).

Vcc sur le 5v de l’Arduino, Gnd sur gnd Arduino, data sur la pin 3 (interruption 1) et une antenne de 17.3 cm.

La portée n’est pas terrible lors de mes tests mais nous nous occuperons de cela dans le prochain article.

Exemple

le sketch complet de réception et affichage de la trame reçue :

// Oregon V2 decoder modfied - Olivier Lebrun
// Oregon V2 decoder added - Dominique Pierre
// New code to decode OOK signals from weather sensors, etc.
// 2010-04-11 <jcw@equi4.com> http://opensource.org/licenses/mit-license.php
// $Id: ookDecoder.pde 5331 2010-04-17 10:45:17Z jcw $

class DecodeOOK {
protected:
    byte total_bits, bits, flip, state, pos, data[25];

    virtual char decode (word width) =0;

public:

    enum { UNKNOWN, T0, T1, T2, T3, OK, DONE };

    DecodeOOK () { resetDecoder(); }

    bool nextPulse (word width) {
        if (state != DONE)

            switch (decode(width)) {
                case -1: resetDecoder(); break;
                case 1:  done(); break;
            }
        return isDone();
    }

    bool isDone () const { return state == DONE; }

    const byte* getData (byte& count) const {
        count = pos;
        return data; 
    }

    void resetDecoder () {
        total_bits = bits = pos = flip = 0;
        state = UNKNOWN;
    }

    // add one bit to the packet data buffer

    virtual void gotBit (char value) {
        total_bits++;
        byte *ptr = data + pos;
        *ptr = (*ptr >> 1) | (value << 7);

        if (++bits >= 8) {
            bits = 0;
            if (++pos >= sizeof data) {
                resetDecoder();
                return;
            }
        }
        state = OK;
    }

    // store a bit using Manchester encoding
    void manchester (char value) {
        flip ^= value; // manchester code, long pulse flips the bit
        gotBit(flip);
    }

    // move bits to the front so that all the bits are aligned to the end
    void alignTail (byte max =0) {
        // align bits
        if (bits != 0) {
            data[pos] >>= 8 - bits;
            for (byte i = 0; i < pos; ++i)
                data[i] = (data[i] >> bits) | (data[i+1] << (8 - bits));
            bits = 0;
        }
        // optionally shift bytes down if there are too many of 'em
        if (max > 0 && pos > max) {
            byte n = pos - max;
            pos = max;
            for (byte i = 0; i < pos; ++i)
                data[i] = data[i+n];
        }
    }

    void reverseBits () {
        for (byte i = 0; i < pos; ++i) {
            byte b = data[i];
            for (byte j = 0; j < 8; ++j) {
                data[i] = (data[i] << 1) | (b & 1);
                b >>= 1;
            }
        }
    }

    void reverseNibbles () {
        for (byte i = 0; i < pos; ++i)
            data[i] = (data[i] << 4) | (data[i] >> 4);
    }

    void done () {
        while (bits)
            gotBit(0); // padding
        state = DONE;
    }
};

class OregonDecoderV2 : public DecodeOOK {
  public:   

    OregonDecoderV2() {}

    // add one bit to the packet data buffer
    virtual void gotBit (char value) {
        if(!(total_bits & 0x01))
        {
            data[pos] = (data[pos] >> 1) | (value ? 0x80 : 00);
        }
        total_bits++;
        pos = total_bits >> 4;
        if (pos >= sizeof data) {
            Serial.println("sizeof data");
            resetDecoder();
            return;
        }
        state = OK;
    }

    virtual char decode (word width) {
       if (200 <= width && width < 1200) {
            //Serial.println(width);
            byte w = width >= 700;

            switch (state) {
                case UNKNOWN:
                    if (w != 0) {
                        // Long pulse
                        ++flip;
                    } else if (w == 0 && 24 <= flip) {
                        // Short pulse, start bit
                        flip = 0;
                        state = T0;
                    } else {
                        // Reset decoder
                        return -1;
                    }
                    break;
                case OK:
                    if (w == 0) {
                        // Short pulse
                        state = T0;
                    } else {
                        // Long pulse
                        manchester(1);
                    }
                    break;
                case T0:
                    if (w == 0) {
                      // Second short pulse
                        manchester(0);
                    } else {
                        // Reset decoder
                        return -1;
                    }
                    break;
              }
        } else if (width >= 2500  && pos >= 8) {
            return 1;
        } else {
            return -1;
        }
        return 0;
    }
};

OregonDecoderV2 orscV2;

volatile word pulse;

void ext_int_1(void)
{
    static word last;
    // determine the pulse length in microseconds, for either polarity
    pulse = micros() - last;
    last += pulse;
}

void reportSerial (const char* s, class DecodeOOK& decoder) {
    byte pos;
    const byte* data = decoder.getData(pos);
    Serial.print(s);
    Serial.print(' ');
    for (byte i = 0; i < pos; ++i)
    {
        Serial.print(data[i] >> 4, HEX);
        Serial.print(data[i] & 0x0F, HEX);
    }

    Serial.println();

    decoder.resetDecoder();
}

void setup ()
{
    Serial.begin(115200);
    Serial.println("\n[ookDecoder]");
    attachInterrupt(1, ext_int_1, CHANGE);

    //DDRE  &= ~_BV(PE5); //input with pull-up 
    //PORTE &= ~_BV(PE5);
}

void loop () {
    static int i = 0;
    cli();
    word p = pulse;

    pulse = 0;
    sei();

    if (p != 0)
    {
        if (orscV2.nextPulse(p))
            reportSerial("OSV2", orscV2);  
    }
}

Un exemple de trames émisent par mes sondes THN132N :

OSV2 EA4C20725C21D083
OSV2 EA4C40F85C21D0D4

Conclusion

Nous avons le décodeur qui nous affiche les trames brutes reçues. Dans le prochain article nous ajouterons le décodage de ces trames (ça en fait des décodeurs…) afin de récupérer les informations qui nous intéresse à savoir : l’id de la sonde, son type, le type d’information reçue, sa valeur, le niveau de batterie, etc.

Je n’ai pour l’instant testé le décodeur qu’avec des sondes THN132N. Si vous possédez d’autres sondes, faites moi un petit retour sur le fonctionnement! (vous pouvez coller les trames reçues en commentaire). Merci à vous.

La suite c’est ici -> Decodeur Oregon Scientific Partie 2

, ,

36 thoughts on “Décodage des protocoles Oregon Scientific sur Arduino (1/3)

  • Michael067 dit :

    Information supplementaire, ce code est pour un arduino mega, ne fonctionne pas sur un arduino uno.
    je reprend le code de jeelabs pour le faire marcher sur un arduino uno mais je n’arrive pas a comprendre quel est le port d’entrée…. je reposte mes tests ensuite :-)

    • Olivier dit :

      Effectivement, je n’ai testé que sur un mega mais normalement l’interruption 1 est branché sur la meme pin (la 3)

      Je test des que j’ai le temps

    • Olivier dit :

      Tu aurais moyen de réessayer le stech ci dessus sur ton UNO en utilisant la pin digital 3 (c’est l’interruption 1) comme sortie du récepteur (pin data) ?

      car je ne suis pas sur que ma condition de sortie fonctionne pour toutes les sondes : le « else if (width >= 2500 && pos >= 8) »

      • Michael067 dit :

        dans le setup tu utilises le port E du mega , pas de port E sur uno.
        j’ai modifié les lignes du decodage comme toi, ca decrypte toujours si en ligne 168 j’ai return total_bits == 160 ? 1: 0; sinon ca decode pas.

  • Michael067 dit :

    le code de jeelabs est bon sur arduino uno sur la patte ADC1 (AnalogIn 2)
    voici la reception avec une sonde THGR228N (thermo/hydro)

    OSV2 1A2D20BB1C26008348A7
    OSV2 1A2D20BB8C2500834EE0
    OSV2 1A2D20BB6C2500834C24
    OSV2 1A2D20BB4C2500834A08

    Encore Merci Olivier :-)
    vivement la suite de l’article….

  • Michael067 dit :

    pour le recepteur, c’est un premier prix comme celui que tu utilises, j’en ai deux autres a tester ….

  • BennyBoom dit :

    MERRRRRRRRRRCIIIII !!!
    Un grand merci à toi ! Je m’avance un peu, peut-être, effectivement je viens de lire ton article mais je ne pourrai tester que ce soir si cela fonctionne (pas de raison j’ai tout à l’identique je crois) .
    Quelle explication ! J’avais imprimé la doc Oregon mais elle ne me parlait pas du tout v___v
    Comme je vois que tu as dans le bain des décodages de sondes sur du 433Mhz, j’en profite pour t’exposé un autre de mes hacks en cours (enfin qui est au point O et qui risque pas de bougé car les informations sont rare) .
    il s’agit d’une sonde de courant , je m’explique , c’est une prise qui se met en serie avec un appareillage electrique (frigo…etc) afin de connaitre sa consommation . C’est appelé : un consomètre sans fil, de marque Otio , modèle 12BB10T et cela émet entre 433.05 et 434.79Mhz, je l’ai démonté je pourrais joindre la photo plus tard, il semble que le MicroControlleur soit un : SONIX SN8P2604A Doc Constructeur .
    Je sais qu’il est possible de capturer les trames emises via un arduino mais je n’ai pas le schema de branchement. Merci encore pour ton travail! ++Benny

    • Olivier dit :

      Salut BennyBoom,
      Content que ça puisse aider :), n’hésite pas à faire un retour si ça marche (ou pas:/ ).

      Pour le wattmètre Otio, on doit pouvoir choper des données mais ils faut ensuite trouver quel codage est utilisé pour reconstituer les trames puis décoder les données :/ … Skywodd t’en parlait dans une de ses réponses à ton commentaire (sur skyduino). Il a réaliser ce genre de chose avec d’autres membres du forum fr arduino pour le matériel blyss : Reverse engineering des interrupteurs domotique Blyss.
      A moins qu’il y ai déjà des infos à propos du protocole utilisé, je pense que c’est au dessus de mes compétences :/

  • BennyBoom dit :

    Salut Olivier,

    Je viens de rentrer ton sketch dans mon Arduino UNO , et après avoir changé l’entrée Data qui était souvent sur les pin11/12/13 (selon les sketch trouvé sur internet), sur la pin3, j’ai des TRAMES!!!! VICTOIRE!
    Voici ce que je chope :

    [ookDecoder]
    OSV2 1A2D103742197005378E
    OSV2 1A2D103742197005378E
    OSV2 1A2D103742197005378E
    OSV2 1A2D103742197005378E
    OSV2 1A2D103742197005378E
    OSV2 1A2D103742197005378E

    Par contre pour compiler j’ai du commenter les lignes suivantes :

    //DDRE &= ~_BV(PE5);
    //PORTE &= ~_BV(PE5);

    qui me remontaient l’erreur :

    In function ‘void setup()’:
    error: ‘DDRE’ was not declared in this scope
    error: ‘PE5′ was not declared in this scope
    error: ‘PORTE’ was not declared in this scope

    Ceux sont des variables à définir sur une arduino mega?

    Autre question, j’ai flingué un récepteur Oregon (tombé du bar, les cristaux liquide ont pleuré xD) donc j’ai du en racheter un et donc j’ai à présent 2 sondes THN132N , un switch permet de choisir le numéro de la sonde entre 1 , 2 et 3, sais tu si dans la trame l’identifiant est stipulé ? (Je suppose que oui mais je voulais savoir si tu avais d’autres infos sur ca)

    Merci également pour ta réponse sur le wattmètre, j’ai vu le tuto de Skyduino, effectivement le travail réalisé est monstrueux , je me disais que peut etre tu avais déjà planché dessus :)

    Je viens de voir que tu viens de publier la suite du tuto sur oregonV2 je vais de ce pas lire ton billet, j’ai pour but de transmettre les infos de mes sondes via ethernet à un nas qui stockera les métriques et les publiera via des graphes RRD servit par une interfaceweb.

    Je te souhaite bonne continuation , la domotique m’intéresse beaucoup surtout que tout comme toi j’ai le projet d’un homecinema dans les cartons :-)

    ++Benny

    • Olivier dit :

      Super :), je t’ai répondu sur l’autre billet.

      Le but final cette série d’articles est semblable au tiens: Réaliser un matériel autonome connecté au réseau qui envoi les infos des sondes oregon via xPL sur le réseau :) (et intérogeable en HTML)
      Ensuite tu peux récupérer ça comme tu veux. Dans une interface perso (c’est ce que je fais, avec RRD justement), dans domogik, etc …

      Bonne chance pour ta salle HC, c’est vraiment un chouette projet :)

    • Olivier dit :

      un switch permet de choisir le numéro de la sonde entre 1 , 2 et 3, sais tu si dans la trame l’identifiant est stipulé ?

      Il permet de choisir le canal. Il est bien dans la trame mais n’est pas l’identifiant de la sonde. L’identifiant est généré aléatoirement à chaque changement de pile (qui est aussi dans la trame).

  • BennyBoom dit :

    Hello,

    Pour la salle HomeCinema j’ai pas trouvé de post sur ton site qui parle de la tienne? Si tu as des billes a me filer coté domotique ou meme matos Hifivideo je suis preneur! Merci encore
    ++

    Benny

    • Olivier dit :

      Salut,
      J’ai vendu ma maison début juin, la salle ciné et tout le matériel :/ Peut etre que j’en ferai un petit post (à titre posthume :’)

      Si tu cherche des sources d’inspiration je te conseil l’excellent forum de homecinema-fr.com notamment la section Installations dédiées . J’y est trouvé à l’époque tout ce dont j’avais besoin (j’y ai un post sur ma salle de l’époque).

      Pour le matériel je ne saurais pas te guider je ne suis pas vraiment connaisseur.

      Quelques coneils en vrac quand meme :

      Quand j’ai commencé la salle je n’avais au début d’un ancien kit de salon JBL 5.1 et un vieil ampli HK AVR2000, et bien le son était déjà vraiment bon (pour mon niveau en tout cas).

      Pour l’écran je te conseille la toile que vend l’un des membres du forum. C’est une toile transsonore (elle te permet de positionner les enceintes cachées derrière l’écran sans perdre en qualité d’écoute. En plus de ça, elle n’est pas chère.

      Je ne sais pas où se trouve la pièce que tu as choisis ? moi elle se trouvait sous ma terrasse. Je n’ai pas eu besoin de forcer sur l’isolation phonique, on entendait rien dans la maison meme à fort niveau sonore. Si elle se trouve dans la maison, n’hésite pas à mettre le paquet sur l’isolation pour pouvoir en profiter (surtout le soir qd les enfants font dodo :). La meilleure technique est de faire de nouveaux murs,sol et plafond découplés des anciens (tu as des exemples sur le forum).

      Placer les élements électroniques hors du champ de vision.

      Mettre un canapé plutot que des sièges ciné, c’est moins classe mais tellement plus confortable :)

      Pour la domotique, un tableau élec à part rien que pour la salle. Les lumières étaient pilotées par un arduino. J’utilisais un PCHC linux/XBMC qui envoyait des ordres HTTP à l’arduino sur réception des ordres de la télecommande (ex : sur play, il ordonnait à l’arduino d’éteindre progressivement les lumières, etc)

      voila voila. Si tu veux plus de détails hésite pas à me contacter par mail, ça sera plus simple :)

  • Julien dit :

    Article sitôt lu, sitôt mis en application !

    Je suis reparti du sketch original de Jeelabs, adapté pour une Arduino Uno (PD2, INT0). Le récepteur est un ST‐RX07A‐ASK de chez Summitech Technology.

    La sonde est une Oregon Scientific THGR122NX qui renvoie ce genre de trames (avec décodage) :
    OSV2 1A2D1072512080E73F2C[THGR228N,...] Id:72 ,Channel:1 ,temp:20.50 ,hum:78 ,bat:90
    OSV2 1A2D1072412080E73E3A[THGR228N,...] Id:72 ,Channel:1 ,temp:20.40 ,hum:78 ,bat:90
    OSV2 1A2D1072312080E73D58[THGR228N,...] Id:72 ,Channel:1 ,temp:20.30 ,hum:78 ,bat:90
    OSV2 1A2D1072212080E73C4E[THGR228N,...] Id:72 ,Channel:1 ,temp:20.20 ,hum:78 ,bat:90
    OSV2 1A2D1072112060E739A2[THGR228N,...] Id:72 ,Channel:1 ,temp:20.10 ,hum:76 ,bat:90

    • Olivier dit :

      Super merci pour ton retour :)

      Sur le conseils de l’auteur du sketch original (très sympa cela dit en passant), j’ai ajouté la détection du type de sonde pendant la réception des trames de manière à connaitre la longueur de la trame. Ca fonctionne bien et du coup toutes les sondes devraient être détectées sans problèmes.

      J’ai aussi ajouté le calcul de la checksum pour vérifier la cohérence des trames reçues et le décodage des infos de toutes les sondes reconnues … bientôt en ligne :)

  • Olivier dit :

    J’ai modifié (et testé) le sketch pour qu’il fonctionne indifféremment sur mega et duemilanove, je n’ai pas de UNO pour tester.

    Il suffit effectivement de supprimer/commenter :

    DDRE &= ~_BV(PE5);
    PORTE &= ~_BV(PE5);

    qui sont des commandes de manipulation de port (du mega en l’occurrence). Si je ne dis pas de conneries, c’est redondant avec la fonction attachInterrupt()

  • Julien dit :

    La nouvelle version fonctionne aussi sur Arduino Uno.

    Par contre les lignes commentées ne sont pas redondantes avec la fonction attachInterrupt. Même si elles ne sont pas obligatoires dans ce cas précis…

    La ligne DDRE &= ~_BV(PE5); configure le port PE5 en entrée. Par défaut, elle l’est déjà au démarrage de l’ATmega. De plus le flag d’interruption sera levé à n’importe quel changement d’état de la broche configurée en interruption, qu’elle soit en entrée ou en sortie.

    La ligne PORTE &= ~_BV(PE5); désactive la résistance de pull-up de l’entrée PE5. Par défaut, elle l’est déjà au démarrage de l’ATmega.

    Cependant sur certains projets, cette entrée sera peut-être utilisée d’une manière différente et il me semble préférable de prendre la peine de la configurer en conservant les deux lignes. Quitte à les adapter ou à gérer les différents cas avec une compilation conditionnelle…

  • Julien dit :

    De rien Olivier ;-)

    Pour les documentations, la bible est la documentation technique de chaque microcontrôleur, plus particulièrement les chapitres « External Interrupts » et ‘I/O Ports ». Mais ce n’est pas forcément accessible et peut rapidement devenir indigeste…

    Sinon il y aussi la note d’application AVR1200: Using External Interrupts for megaAVR Devices.

    Enfin on trouve pas mal d’autres ressources mais pas aussi complètes…

  • francois dit :

    Bonjour

    Merci pour ce super travail

    De mon coté j ai reussi à avoir les trames oregonV3 d une thgr810.
    J ai mis la data de la carte reception sur A1
    Voici un exemple de trame recue sur arduino :
    OSV3 FA2814A93022304443BE

    Maintenant reste la partie emission vers un rfxtrx433 à partir de l arduino !!

  • [...] des protocoles Oregon Scientific sur Arduino (2/3) | Connecting Stuff Décodage des protocoles Oregon Scientific sur Arduino (1/3) | Connecting Stuff Le senseur à effet Hall US5881LUA Un senseur à effet Hall est sensible au champ magnétique. [...]

  • Abalam dit :

    Hello,

    Quelqu’un a essayé avec un RRQ3 ?
    Moi il semble soit crasher, soit ne rien retourner…

  • yann dit :

    Salut,
    Je viens de faire le test avec une arduino uno, et le sketch tel qu’il est aujourd’hui.
    Le data sur la patte 3 et je n’obtiens qu’un pauvre « [ookDecoder]« …

    :(
    des idées ?

    Merci pour ton travail.
    yann

  • yann dit :

    j’ai oublié de préciser le capteur : THGR1222NX
    ;)

  • Renaud dit :

    Bonjour,

    Je n’arrive pas à télécharger la librairie du décodeur. Il semble qu’elle ne soit plus accessible.

    Cordialement,

  • yann dit :

    Salut,
    Je viens de faire le test avec une arduino uno, et le sketch tel qu’il est aujourd’hui.
    Le data sur la patte 3 et je n’obtiens qu’un pauvre « [ookDecoder]« …

    :(
    des idées ?
    j’ai oublié de préciser le capteur : THGR1222NX

    Merci pour ton travail.
    yann

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>