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 :

Catégories
Recherche technologique

openThread et GreenPower – GreenPower

Pour le dernier article de cette étude, nous allons voir comment recevoir une trame green power issue d’une commande Philips Tap (https://zigbeealliance.org/zigbee_products/philips-hue-tap/) puis la transformer pour la renvoyer sous forme d’un message DotDot.

Méthodologie:

  • L’actionneur sera le leader et conserve le code développé précédemment.
  • Le nœud enfant contiendra le proxy Green Power et sera également modifier pour rejoindre automatiquement le réseau créé par le leader.
  • Pour simplifier l’adresse ipv6 du leader sera codée en dur dans l’enfant pour l’envoie du message CoAP.
  • La sécurité Green Power est ignorée pour cette étude. La trame issue de la commande Philips Tap contien l’ordre en clair.

La trame Green Power

Sans chercher à décoder entièrement la trame Green Power pour notre étude, nous utiliserons les filtres suivants:

  • Au moins 24 octets de long.
  • Le frame control 802.15.4 doit valoir 0x0801.
  • Le frame control du header network doit être de type data et la version du protocole doit être 3 (green power).
  • Le frame counter de la trame reçue doit être supérieur au frame counter de la trame précédente.
  • L’ordre green power doit être 0x22 (Toggle)

Le code du nœud enfant

Rejoindre le réseau et activer le handler de réception 802.15.4 à placer dans la fonction main avant la boucle infinie.

    // enable promiscuous mode for capture Grenn Power Frame
    // set callback
    otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "Set Pcap callback.");
    otLinkSetPcapCallback(instance, &PromicuousHandleLinkPcapReceive, 0);
    
    // join existing network
    // set master key
    memset(&aDataset, 0, sizeof(aDataset));
    memcpy(aDataset.mMasterKey.m8, demoMasterKey, sizeof(aDataset.mMasterKey));
    aDataset.mComponents.mIsMasterKeyPresent = true;
    otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "Commit dataset : ");
    lError = otDatasetSetActive(instance, &aDataset);
    if( OT_ERROR_NONE == lError )
    {
        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 router ");
            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]));
                }                    

                // Initialize coap service
                otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "CoAP start : ");
                lError = otCoapStart(instance, OT_DEFAULT_COAP_PORT);
                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 %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 %s\n", otThreadErrorToString(lError)); }    

Filtrer les messages 802.15.4 pour n’être réactif qu’à la trame Green Power qui nous intéresse.

void PromicuousHandleLinkPcapReceive(const otRadioFrame *aFrame, bool aIsTx, void *aContext)
{
    OT_UNUSED_VARIABLE(aContext);

    // receive frame only
    if( !aIsTx )
    {
        otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "Rx %d bytes on channel %d, at time %d",
                                                                aFrame->mLength,aFrame->mChannel,aFrame->mInfo.mRxInfo.mTimestamp);
        // get only frame starting 0x01 0x08 : frame type data without security and short addressing mode fot destination, minimal length shall be 24
        if( (aFrame->mLength>=24) && (aFrame->mPsdu[0]==0x01) && (aFrame->mPsdu[1]==0x08) )
        {
            // green power use data frame with protocol 3, use it only
            if( (aFrame->mPsdu[7]&0x0F)==0x0C )
            {
                static uint32_t ls_previous_frm_counter = 0;
                otError lError;
                otMessage *   message = NULL;
                otMessageInfo messageInfo;  
                otIp6Address coapDestinationIp;              
                uint32_t l_src_id = aFrame->mPsdu[9] + (aFrame->mPsdu[10]<<8) + (aFrame->mPsdu[11]<<16) + (aFrame->mPsdu[12]<<24);
                uint32_t l_frm_counter = aFrame->mPsdu[13] + (aFrame->mPsdu[14]<<8) + (aFrame->mPsdu[15]<<16) + (aFrame->mPsdu[16]<<24);
                uint8_t l_cmd_id = aFrame->mPsdu[17];
                
                otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "GPF from %08X, frame counter %d, command %02X", l_src_id, l_frm_counter, l_cmd_id);

                // send CoAP message to leader only if frame counter is higher than previous message and only toggle (0x22) GP order
                if( (0x22 == l_cmd_id) && (l_frm_counter > ls_previous_frm_counter) )
                {
                    ls_previous_frm_counter = l_frm_counter;
                    message = otCoapNewMessage(instance, NULL);
                    otCoapMessageInit(message, OT_COAP_TYPE_NON_CONFIRMABLE, OT_COAP_CODE_POST);
                    otCoapMessageGenerateToken(message, 2);
                    otCoapMessageAppendUriPathOptions(message, "zcl/e/1/s6/c/2");
                    otIp6AddressFromString("fd85:74ee:7560:9626:dd0e:3508:db6:44c9", &coapDestinationIp);
                    memset(&messageInfo, 0, sizeof(messageInfo));
                    messageInfo.mPeerAddr = coapDestinationIp;
                    messageInfo.mPeerPort = OT_DEFAULT_COAP_PORT;                

                    otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_PLATFORM, "Send CoAP message : ");
                    lError = otCoapSendRequestWithParameters(instance, message, &messageInfo, NULL, NULL,NULL);
                    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)); }                   
                }
            }
        }
    }
}

Le résultat

Conclusion

Tout d’abord POURQUOI ? Pourquoi vouloir utilisé des devices Green Power dans un réseau thread ? Pour plusieurs raison :

  • Thread possède un type de device Low Energy MAIS il est encore trop consommateur par rapport à un device Green Power
  • Une commande Green Power peut devenir indépendante du type de réseau mesh 802.15.4 en place (Zigbee ou Thread).

Et la sécurité ? En effet Thread est un réseau sécurisé dans toutes les étape de sa vie. Pour cela il oblige l’utilisation d’un border routeur et d’un outils commissioneur, généralement un smartphone. Green Power contient bien des niveaux de sécurités dont un qui permet d’obtenir le même résultat que la sécurité mise en place dans Thread. Grâce à l’outil on peut venir entrer la clé de sécurité du device Green Power que l’on souhaite utiliser directement dans les proxy/actionneur concerné.

Pour résumer seul les devices Green Power Mono directionnel avec une sécurité de type install code et un frame counter incrémentiel présente un intérêt d’utilisation dans un réseau Thread. En effet ils permettent de combler le manque de spécification d’un device vraiment low power sans dégrader le niveau de sécurité du système.