Catégories
Recherche technologique

openThread et GreenPower – Réseau à deux noeuds

L’objectif est d’avoir un réseau composé d’un leader et d’un routeur, de pouvoir effectué un ping du routeur sur le leader et une communication de type coap. Pour cela nous allons générer un firmware de type Full Thread Device pilotable grâce à la CLIdepuis un terminal série. Le matériel utilisé sera deux cartes Silicon Labs EFR32MG12.

Générer le firmware

Pour pouvoir générer un firmware Silicon Labs il est nécessaire de charger et installer simplicity studio pour pouvoir récupérer la stack flex contenue dans le sdk 2.7.

Lien vers la page de chargement de simplicity studio : https://www.silabs.com/products/development-tools/software/simplicity-studio

Une fois simplicity studio installé et la stack Flex SDK v2.7 téléchargée, il faut la copier du répertoire ou elle se trouve (C:\SiliconLabs\SimplicityStudio\v4\developer\sdks\gecko_sdk_suite) dans le repertoire third party de la stack openthread (C:_DEV\docker_share\openthread\third_party\silabs\gecko_sdk_suite).

Depuis notre terminal docker :

make -f examples/Makefile-efr32mg12 COMMISSIONER=1 COAP=1 JOINER=1 DHCP6_CLIENT=1 DHCP6_SERVER=1 BOARD=BRD4161A

Puis on transforme notre binaire au format hex :

arm-none-eabi-objcopy -O ihex output/efr32mg12/bin/ot-cli-ftd output/efr32mg12/bin/ot-cli-ftd.hex

Depuis Windows on éxecute l’outils Silicon Labs pour charger les firmware dans la cible : commander (C:\SiliconLabs\SimplicityStudio\v4\developer\adapter_packs\commander) et l’on charge nos deux cartes avec notre firmware fraichement compilé.

Construire le réseau et configurer le sniffer

A l’aide du gestionnaire de périphérique on note les ports séries associés à nos cartes. Dans mon cas les port COM11 et COM12. On ouvre un terminal série par port avec la configuration suivante : 115200 8-N-1

Configuration de la première carte (COM11) pour devenir le leader du réseau.

Initialisation d’un dataset et affichage des valeurs :

dataset init new

Le canal radio choisi est le 20, or nous avons configurer notre sniffer pour écouter sur le canal 15, on change la valeur du canal dans notre dataset pour se positionner sur le canal 15.

dataset channel 15

Maintenant que le dataset nous conviens, on le rend actif, puis on démarre l’interface réseau et la stack thread. au bout d’un quelque secondes lorsque l’on demande son état elle nous informe qu’elle est le leader.

Configuration du sniffer

Plusieurs protocoles sont à configurer en utilisant les valeurs fournies par le dataset du leader.

6LoWPAN

CoAP

IEEE 802.15.4

Thread

Configuration de la deuxième carte (COM12) en tant que routeur

Contrairement à la première carte, on ne va renseigner que la « master key » dans le dataset avant d’activer l’interface et de démarrer la stack.

dataset masterkey 9d7ddcb4408be470e2240256feaf9cf2

La trace wireshark de l’entrée réseau :

Test – Ping

Un premier test consiste à envoyer un ping en ICMPv6 du routeur vers le leader et d’observer la transcation :

ping fdad:574c:3814:8442:9c44:e832:a11e:4939

Test – UDP

Le routeur va envoyer un message « hello » en udp sur un port du leader qui aura été ouvert :

Leader – Ouverture du port en écoute

udp open

udp bind :: 1234

Routeur – Envoie du message « hello »

udp open

udp send fdad:574c:3814:8442:9c44:e832:a11e:4939 1234 hello

Leader – Résultat

Wireshark – Trace

Test – CoAP

L’exemple fournit ne pose plus de problème à présent et peut être suivi sans modification : https://github.com/openthread/openthread/blob/master/src/cli/README_COAP.md

Liens

Liste des liens qui ont permis de réaliser cet article :

Catégories
Recherche technologique

openThread et GreenPower – DotDot

Et pourquoi pas CHIP ? (https://www.connectedhomeip.com/) Et bien tout simplement car aucune spécification n’est encore publique actuellement !

Les documents de référence

L’objectif

L’objectif n’est pas d’implémenter l’ensemble du protocole dotdot mais le strict minimum pour passer à l’étape suivante : le pilotage depuis une commande green power. Pour se faire seul la commande toggle du cluster on/off sera implémentée sur le premier endpoint de l’actionneur. Seront donc ignoré :

  • La découverte des produits et des ressources depuis le /.well-known et de fait la gestion de l’UID produit.
  • Tous les mécanismes de sécurité.
  • La gestion du multicast, seul un post unicast sera géré par le serveur.
  • Les bindings et autres tokens

On peut donc résumé que le client enverra un post coap depuis la cli, le serveur quand à implémentera ce post sur la ressource « /zcl/e/1/s6/c/2 » qui correspond a la commande toggle du cluster on/off sur l’enpoint 1 du produit. Commande qui n’a pas besoin de payload.

Le code !

Jusqu’à présent nous avons compilé le projet exemple qui donne accès à une ligne de commande. Pour réaliser notre objectif on définira le leader comme l’actionneur, on ne touchera pas au code du routeur. Pour simplifier l’on va modifier le projet example cli pour :

  • créé automatiquement le réseau en tant que leader
  • activé coap et lui ajouter la ressource zcl/e/1/s6/c/2
  • ajouter un handler sur cette ressource
  • imprimer sur la sortie RTT les informations permettant de s’assurer que toute les étapes ce sont bien passée
  • a chaque fois que le handler de la ressource coap est appelée changé d’état une variable représentant la charge et l’afficher.
void CoAPHandleRequest(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
 {
     static bool light_on_state = false;
     OT_UNUSED_VARIABLE(aContext);
     OT_UNUSED_VARIABLE(aMessage);
     OT_UNUSED_VARIABLE(aMessageInfo);

     otPlatLog(OT_LOG_LEVEL_NONE, 
               OT_LOG_REGION_PLATFORM, 
               "Light %s",light_on_state?"ON":"OFF");
     light_on_state = !light_on_state;
}
int main(int argc, char *argv[])
 {
     otInstance *instance;
...

// configure node as leader
otError lError;

otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "Create new network : dataset ");
otOperationalDataset aDataset;
uint8_t demoMasterKey[OT_MASTER_KEY_SIZE] = {0x9d,0x7d,0xdc,0xb4,0x40,0x8b,0xe4,0x70,0xe2,0x24,0x02,0x56,0xfe,0xaf,0x9c,0xf2};

if( OT_ERROR_NONE == otDatasetCreateNewNetwork(instance, &aDataset) )
{
    otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "SUCCESS, set active ");
    aDataset.mChannel = 15;
    memcpy(aDataset.mMasterKey.m8, demoMasterKey, OT_MASTER_KEY_SIZE);
    if( OT_ERROR_NONE == otDatasetSetActive(instance, &aDataset) )
    {
        otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "SUCCESS, enable IPv6 ");
        if( OT_ERROR_NONE == otIp6SetEnabled(instance, true) )
        {
            otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "SUCCESS, start as leader ");
            lError = otThreadSetEnabled(instance,true);
            if( OT_ERROR_NONE == lError )
            {
                otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "SUCCESS\n");
                const otNetifAddress *unicastAddrs = otIp6GetUnicastAddresses(instance);

                for (const otNetifAddress *addr = unicastAddrs; addr; addr = addr->mNext)
                {
                    otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM,
                        "%x:%x:%x:%x:%x:%x:%x:%x", tSwap16(addr->mAddress.mFields.m16[0]), tSwap16(addr->mAddress.mFields.m16[1]),
                        tSwap16(addr->mAddress.mFields.m16[2]), tSwap16(addr->mAddress.mFields.m16[3]), tSwap16(addr->mAddress.mFields.m16[4]),
                        tSwap16(addr->mAddress.mFields.m16[5]), tSwap16(addr->mAddress.mFields.m16[6]), tSwap16(addr->mAddress.mFields.m16[7]));
                }                    
            }
            else { otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "FAILED %s\n", otThreadErrorToString(lError)); }    
        }
        else { otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "FAILED\n"); }    
    }
    else { otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "FAILED\n"); }    
}
else { otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "FAILED\n"); }    


// Initialize coap service
otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "CoAP start : ");
if( OT_ERROR_NONE == otCoapStart(instance, OT_DEFAULT_COAP_PORT) )
{
    otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "SUCCESS\n");

    // add ressources
    otCoapResource mResource;
    char zclToggleRscr[] = "zcl/e/1/s6/c/2";
    memset(&mResource, 0, sizeof(mResource));
    mResource.mUriPath = zclToggleRscr;
    mResource.mHandler = CoAPHandleRequest;

    otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "CoAP add ressource : ");
    lError = otCoapAddResource(instance, &mResource);
    if( OT_ERROR_NONE == lError )
    {
        otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "SUCCESS\n");
    }
    else { otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "FAILED %s\n", otThreadErrorToString(lError)); }    
}
else
{
    otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "FAILED\n");
}


while (!otSysPseudoResetWasRequested())
{
    ...

return 0;
}

Et le résultat :