Comme beaucoup (trop) de développeur, mon pc de travail fonctionne sous Windows 10 or openThread fait appel à des outils Linux pour compiler. Dans la suite je vais vous décrire mon installation qui me permet de modifier le code dans un IDE digne de ce nom, de compiler ce même code comme si j’étais sous Linux et enfin de le programmer dans la cible avec les outils natif à mon os fournit par les fabricants de composants.
Environnement de compilation
Le plus simple est d’utiliser docker, d’autant qu’une image existe déjà !
Installation de docker sous Windows 10
Pas de difficulté particulière si ce n’est qu’il faut une version professionnel de Windows 10. Le lien vers la page de téléchargement de Docker Desktop : https://www.docker.com/get-started
A la fin de l’installation on ouvre une Invite de commandes Windows (pas le powershell) et l’on peut vérifier que docker est en route en lui demandant ça version :
Nous allons ensuite chercher l’image docker qui nous intéresse :
docker pull openthread/environment:latest
Pour faciliter la modification ultérieur des fichier sources depuis un IDE sous Windows nous allons activer le partage de disque sous docker. Pour cela on va dans la partie « Setting/Ressources/File sharing » de docker desktop et l’on sélectionne le disque que l’on souhaite partager :
J’ai créé un répertoire spécifique de partage sur mon disque « c » de Windows à l’emplacement « C:_DEV\docker_share » et pour simplifier la suite il sera monté à l’emplacement « /mnt » de l’image Linux. Il est temps de lancer un container :
docker run -it -v c:/_DEV/docker_share:/mnt --rm openthread/environment bash
Dans ce shell Linux nous allons effectuer un enchainement de commandes :
Installation de la toolchain et autres dépendances :
./script/bootstrap
Setup de l’environnement :
./bootstrap
Nous voilà prêt à effectuer la compilation de notre premier firmware, pour ce préparer à l’étape suivante qui est la mise en place d’un sniffer, nous allons construire le firmware nécessaire pour la carte de développement de chez Nordic « nRF52840-DK » :
make -f examples/Makefile-nrf52840 USB=1
le binaire généré ce trouve dans le répertoire « /mnt/openthread/output/nrf52840/bin » mais a encore besoin d’une transformation pour le mettre au format hex :
Dans un terminal Windows mettre installer les dépendances utiles et pyspinel :
pip3 install pyserial ipaddresspyspinel
Ce placer dans un répertoire de travail et cloner le repo openthread/pyspinel :
git clone https://github.com/openthread/pyspinel
Lancer la session de sniffing …
On commence par connecter la carte nRF52840 précédemment flashée avec le firmware ot-rcp.hex par l’usb de debug au PC. A l’aide du gestionnaire de périphérique on note le numéro du port série virtuel associé, COM3 dans mon cas.
Le mini réseau thread sera créé sur le canal 15, on décide de placer le sniffer en écoute sur celui-ci par la commande suivante :
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.
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
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.
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.
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.
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.