On suppose que le lecteur a déjà une connaissance initiale du langage C, connaît quelque chose sur Zigbee, la puce cc2530, les méthodes pour le flasher et l'utiliser, et est également familier avec des projets comme zigbee2mqtt. Sinon, préparez-vous ou allez lire sur https://myzigbee.ru et https://www.zigbee2mqtt.io/ L'article est écrit d'abord en détail, mais il est progressivement accéléré et ne s'arrête pas aux détails, mais décrit le code du firmware fini. Si quelqu'un n'est pas intéressé par le raisonnement, ouvrez simplement les sources du firmware et lisez-les.Le code source du firmware finiL'approche code et développement ne prétend pas être idéale. "Je ne suis pas un magicien, j'apprends juste."objectif
L'objectif principal est de comprendre comment écrire un firmware pour Z-Stack pendant longtemps. Par conséquent, j'ai décidé d'implémenter un firmware alternatif pour l'équipement fini (le relais Sonoff BASICZBR3 a été choisi comme exemple ) et d'ajouter la possibilité de connecter le populaire capteur de température ds18b20.De plus, je voulais montrer aux développeurs débutants Zigbee un exemple de développement de firmware pour la puce TI cc2530 sur Z-Stack.1. Préparation
Pour commencer le développement, vous devez télécharger et installer Z-Stack 3.0.2 - il s'agit d'un SDK pour développer un firmware avec des exemples et de la documentation.Vous devez également télécharger et installer IAR Embeded Workbench pour 8051 - il s'agit d'un environnement de développement avec la possibilité de compiler pour les puces TI cc2530. La période d'utilisation gratuite est de 1 mois (mais le chercheur trouvera une solution).Pour le développement et le débogage, j'utilise CCDebugger - il permet non seulement de flasher les puces cc2531 / cc2530, mais aussi de déboguer l'application dans l'environnement IAR.
Pour simplifier les expériences, le prototypage et le débogage que je fais sur le devboard et le module cc2530 correspondant:
2. Création d'une nouvelle application
Nous créons un nouveau projet basé sur GenericApp. Ceci est un exemple d'une application Z-Stack de base. Il se trouve dans le dossier Z-Stack 3.0.2 \ Projects \ zstack \ HomeAutomation \ GenericApp.Nous le copions à proximité et le renommons, par exemple, en DIYRuZRT (appelons l'application pour notre appareil).Le dossier CC2530DB contient des fichiers:- GenericApp.ewd - paramètres de projet pour C-SPY
- GenericApp.ewp - fichier de projet
- GenericApp.eww - Espace de travail
Renommez les fichiers en DIYRuZRT.eww et DIYRuZRT.ewp.À l'intérieur de tous les fichiers (y compris le dossier Source), nous changeons également toutes les références à GenericApp en DIYRuZRT.Ouvrez maintenant le projet DIYRuZRT.ewp dans IAR. Nous sélectionnons la configuration de RouterEB et effectuons Tout reconstruire.
Le dossier RouterEB sera créé dans le dossier CC2530DB et le fichier DIYRuZRT.d51 apparaîtra dans le dossier EXE - ce fichier est pratique pour flasher et déboguer depuis IAR.Mais si nous devons flasher le firmware via SmartRF Flash Programmer, nous ferons de petits changements. Pour ce faire, dans les paramètres du projet dans la section Lien sous l'onglet Sortie, modifiez les paramètres du fichier de sortie et du format:
Après cela, le fichier du firmware DIYRuZRT.hex sera créé dans le dossier EXE, pratique pour flasher à partir d'autres outils et d'autres manières .Mais après avoir téléchargé ce firmware, l'appareil ne se connecte pas au réseau. Eh bien, nous comprendrons.3. Un peu de terminologie
La terminologie Zigbee comprend les concepts suivants:- Endpoint - Le point de description du périphérique final. Habituellement, dans les appareils simples, un point final. Il peut y en avoir plusieurs dans des appareils multifonctions, ainsi que dans des appareils avec différents profils d'interaction (un profil - un point final).
- Cluster (cluster) - un ensemble d'attributs et de commandes liés à une seule fonction (marche / arrêt, gradation, mesures de température, etc.). Le cluster indique les opportunités réalisées par le point de terminaison. Dans un point de terminaison, vous pouvez implémenter plusieurs clusters différents, mais pas les mêmes.
- Attribut (attribut) - une caractéristique d'un cluster dont la valeur peut être lue ou écrite. Un cluster peut avoir de nombreux attributs.
- Commande - Un message de contrôle que le cluster peut traiter. Une équipe peut avoir des paramètres. Ceci est implémenté par une fonction qui est exécutée lorsqu'une commande et des paramètres sont reçus.
Les types de clusters, d'attributs et de commandes sont standardisés dans la bibliothèque de clusters Zigbee. Mais les fabricants peuvent utiliser leurs propres clusters, avec leurs propres attributs et équipes.Certains producteurs pitoyables ne se soucient pas des normes et font quelque chose pour les normes. Ensuite, vous devez vous y adapter. Laterminologie Z-Stack a également ses propres concepts , par exemple:- OSAL (Operating System Abstraction Layer) - le niveau d'abstraction du système d'exploitation. Ici, ils fonctionnent avec des tâches (tâches), des messages (messages), des événements (événements), des temporisateurs (temporisateurs) et d'autres objets.
- HAL (Hardware Abstraction Layer) - niveau d'abstraction de l'équipement. Ici, ils fonctionnent avec des boutons (touches), des LED (leds), des interruptions (Interruption), etc.
La couche matérielle assure l'isolement du code du programme et de l'équipement qu'il contrôle. Le niveau opérationnel fournit des mécanismes pour créer et interagir entre les éléments d'application.L'utilisation de tout cela vous attend ci-dessous et, en principe, lors du développement du firmware.4. Qu'avons-nous à l'intérieur de l'application de base?
Le code d'application se trouve dans le dossier Source:- OSAL_DIYRuZRT.c —
- zcl_DIYRuZRT.h —
- zcl_DIYRuZRT.c —
- zcl_DIYRuZRT_data.c — ,
OSAL_DIYRuZRT.c - le fichier principal dans lequel le tableau des gestionnaires de tâches (tâche) pTaskEventHandlerFn tasksArr est rempli et la fonction d'initialisation osalInitTasks est implémentée .Tous les autres fichiers sont nécessaires pour implémenter ces initialiseurs et gestionnaires.La liste des gestionnaires de tâches pTaskEventHandlerFn tasksAr r est remplie de références de fonction. Certaines tâches sont connectées / déconnectées par les directives de compilation correspondantes.Vous pouvez afficher et configurer les directives de compilation dans les options du compilateur de symboles définis:
const pTaskEventHandlerFn tasksArr[] = {
macEventLoop,
nwk_event_loop,
#if !defined (DISABLE_GREENPOWER_BASIC_PROXY) && (ZG_BUILD_RTR_TYPE)
gp_event_loop,
#endif
Hal_ProcessEvent,
#if defined( MT_TASK )
MT_ProcessEvent,
#endif
APS_event_loop,
#if defined ( ZIGBEE_FRAGMENTATION )
APSF_ProcessEvent,
#endif
ZDApp_event_loop,
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
ZDNwkMgr_event_loop,
#endif
#if defined ( INTER_PAN )
StubAPS_ProcessEvent,
#endif
#if defined ( BDB_TL_INITIATOR )
touchLinkInitiator_event_loop,
#endif
#if defined ( BDB_TL_TARGET )
touchLinkTarget_event_loop,
#endif
zcl_event_loop,
bdb_event_loop,
zclDIYRuZRT_event_loop
};
osalInitTasks est la fonction de démarrage de l'application qui enregistre les tâches effectuées par l' application.L'enregistrement des tâches est effectué dans l'ordre et chaque tâche obtient son propre numéro. Il est important de suivre le même ordre que dans le tableau tasksArr , comme les gestionnaires sont appelés en fonction du numéro de tâche.void osalInitTasks( void )
{
uint8 taskID = 0;
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
macTaskInit( taskID++ );
nwk_init( taskID++ );
#if !defined (DISABLE_GREENPOWER_BASIC_PROXY) && (ZG_BUILD_RTR_TYPE)
gp_Init( taskID++ );
#endif
Hal_Init( taskID++ );
#if defined( MT_TASK )
MT_TaskInit( taskID++ );
#endif
APS_Init( taskID++ );
#if defined ( ZIGBEE_FRAGMENTATION )
APSF_Init( taskID++ );
#endif
ZDApp_Init( taskID++ );
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
ZDNwkMgr_Init( taskID++ );
#endif
#if defined ( INTER_PAN )
StubAPS_Init( taskID++ );
#endif
#if defined( BDB_TL_INITIATOR )
touchLinkInitiator_Init( taskID++ );
#endif
#if defined ( BDB_TL_TARGET )
touchLinkTarget_Init( taskID++ );
#endif
zcl_Init( taskID++ );
bdb_Init( taskID++ );
zclDIYRuZRT_Init( taskID );
}
Notre application a enregistré le gestionnaire de fonctions zclDIYRuZRT_event_loop et la fonction d'initialisation zclDIYRuZRT_Init . Ils sont ajoutés en dernier dans la liste.Ce sont les deux fonctions principales de notre application. L'implémentation de ces fonctions se trouve dans le fichier zcl_DIYRuZRT.c .zclDIYRuZRT_Init - fonction d'enregistrement de tâche.DIYRuZRT_ENDPOINT - le numéro de point final implémenté par notre application.Les étapes d'enregistrement qui décrivent notre application sont exécutées séquentiellement:- bdb_RegisterSimpleDescriptor — . SimpleDescriptionFormat_t zclDIYRuZRT_SimpleDesc — , , , . OSAL_DIYRuZRT_data.c
- zclGeneral_RegisterCmdCallbacks — zclGeneral_AppCallbacks_t zclDIYRuZRT_CmdCallbacks — , .
- zcl_registerAttrList — zclAttrRec_t zclDIYRuZRT_Attrs — , .
- zcl_registerForMsg - enregistre la réception des messages de contrôle.
- RegisterForKeys - nous signons notre tâche pour recevoir les événements de clic de bouton.
const cId_t zclDIYRuZRT_InClusterList[] =
{
ZCL_CLUSTER_ID_GEN_BASIC,
ZCL_CLUSTER_ID_GEN_IDENTIFY,
};
#define ZCLDIYRuZRT_MAX_INCLUSTERS (sizeof(zclDIYRuZRT_InClusterList) / sizeof(zclDIYRuZRT_InClusterList[0]))
const cId_t zclDIYRuZRT_OutClusterList[] =
{
ZCL_CLUSTER_ID_GEN_BASIC,
};
#define ZCLDIYRuZRT_MAX_OUTCLUSTERS (sizeof(zclDIYRuZRT_OutClusterList) / sizeof(zclDIYRuZRT_OutClusterList[0]))
SimpleDescriptionFormat_t zclDIYRuZRT_SimpleDesc =
{
DIYRuZRT_ENDPOINT,
ZCL_HA_PROFILE_ID,
ZCL_HA_DEVICEID_ON_OFF_LIGHT,
DIYRuZRT_DEVICE_VERSION,
DIYRuZRT_FLAGS,
ZCLDIYRuZRT_MAX_INCLUSTERS,
(cId_t *)zclDIYRuZRT_InClusterList,
ZCLDIYRuZRT_MAX_OUTCLUSTERS,
(cId_t *)zclDIYRuZRT_OutClusterList
};
zclDIYRuZRT_event_loop - fonction des gestionnaires d'événements de notre application.Tout d'abord, les événements système sont traités en boucle:- ZCL_INCOMING_MSG - les commandes de contrôle de périphérique sont traitées dans zclDIYRuZRT_ProcessIncomingMsg.
- KEY_CHANGE - Les événements de clic de bouton sont traités dans zclDIYRuZRT_HandleKeys .
- ZDO_STATE_CHANGE - événements de changement d'état du réseau.
if ( events & SYS_EVENT_MSG )
{
while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( zclDIYRuZRT_TaskID )) )
{
switch ( MSGpkt->hdr.event )
{
case ZCL_INCOMING_MSG:
zclDIYRuZRT_ProcessIncomingMsg( (zclIncomingMsg_t *)MSGpkt );
break;
case KEY_CHANGE:
zclDIYRuZRT_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );
break;
case ZDO_STATE_CHANGE:
zclDIYRuZRT_NwkState = (devStates_t)(MSGpkt->hdr.status);
if ( (zclDIYRuZRT_NwkState == DEV_ZB_COORD) ||
(zclDIYRuZRT_NwkState == DEV_ROUTER) ||
(zclDIYRuZRT_NwkState == DEV_END_DEVICE) )
{
giGenAppScreenMode = GENERIC_MAINMODE;
zclDIYRuZRT_LcdDisplayUpdate();
}
break;
default:
break;
}
osal_msg_deallocate( (uint8 *)MSGpkt );
}
Vient ensuite le traitement de l'événement spécial DIYRuZRT_EVT_1 , qui commute l'état de la LED HAL_LED_2 et démarre la minuterie sur 500m avec le même événement. Cela démarre le clignotement de la LED HAL_LED_2 . if ( events & DIYRuZRT_EVT_1 )
{
HalLedSet ( HAL_LED_2, HAL_LED_MODE_TOGGLE );
osal_start_timerEx( zclDIYRuZRT_TaskID, DIYRuZRT_EVT_1, 500 );
return ( events ^ DIYRuZRT_EVT_1 );
}
Le fait est qu'au démarrage du firmware, l'événement HAL_KEY_SW_1 se produit et c'est en lui que le timer et l'événement DIYRuZRT_EVT_1 sont initialisés . Et si vous appuyez sur le bouton S2, le clignotement s'arrêtera (ma LED reste allumée). Une nouvelle pression commencera à clignoter.5. HAL: LED et boutons
"Attendez, quelles LED et quels boutons?", Demandez-vous. Initialement, tous les exemples de Z-stack se concentrent sur différents types de cartes de débogage de la série SmartRF05 EB:
j'ai une carte de débogage légèrement différente et un module avec une puce.Il y a 2 boutons (+ reset) et 3 LEDs (+ indicateur d'alimentation) sur la carte. Voici l'un d'eux (D2) clignotant lorsque le firmware fonctionne correctement.Après avoir appelé les contacts, nous déterminons la correspondance des broches, diodes et boutons:- D1 - P10
- D2 - P11
- D3 - P14
- S2 - P20
- S1 - P01
Ainsi, HAL est une couche d'abstraction matérielle , un moyen d'abstraire de la mise en œuvre de l'équipement. Le code d'application utilise des macros et des fonctions qui fonctionnent avec des abstractions telles que Button 1 ou LED 2 , et la correspondance spécifique des abstractions et de l'équipement est définie séparément.Voyons de quel type de HAL_LED_2 il s'agit et comment comprendre sur quelle broche il est suspendu. Enrecherchant, nous trouvons le fichier hal_led.h , où ces constantes et la fonction HalLedSet sont décrites , où le numéro et le mode LED sont transmis. À l'intérieur, la fonction HalLedOnOff est appelée pour allumer et éteindre la LED, qui à son tour exécute HAL_TURN_ON_LED2 ouHAL_TURN_OFF_LED2 .HAL_TURN_ON_LED2 et HAL_TURN_OFF_LED2 sont les macros décrites dans hal_board_cfg.h . Les macros changent en fonction de la configuration matérielle.Dans mon cas:#define HAL_TURN_OFF_LED2() st( LED2_SBIT = LED2_POLARITY (0); )
#define HAL_TURN_ON_LED2() st( LED2_SBIT = LED2_POLARITY (1); )
Un peu plus haut dans le fichier se trouvent les correspondances de LED2_SBIT et LED2_POLARITY :
#define LED2_BV BV(1)
#define LED2_SBIT P1_1
#define LED2_DDR P1DIR
#define LED2_POLARITY ACTIVE_HIGH
Cela signifie que la LED 2 est située sur la broche P1_1 et que son niveau de commutation est élevé. Mais, à en juger par le code, la LED devrait s'éteindre lorsque le bouton est enfoncé, mais avec nous, il reste allumé. Si dans ce fichier hal_board_cfg.h on change:#define LED2_POLARITY ACTIVE_HIGH
sur le#define LED2_POLARITY ACTIVE_LOW
puis maintenant, la LED s'éteint lorsque vous appuyez sur le bouton S2, comme cela devrait être logique.Afin de ne pas modifier les fichiers communs qui ne sont pas liés à notre application, il vaut mieux faire autrement:- créez une copie du fichier hal_board_cfg.h (à partir du dossier Z-Stack 3.0.2 \ Components \ hal \ target \ CC2530EB \) dans notre dossier Source et nommez-le, par exemple, hal_board_cfg_DIYRuZRT.h
- faisons de notre copie du fichier la toute première (excluant ainsi la connexion du fichier partagé). Créez un fichier preinclude.h dans notre dossier Source et écrivez-y la ligne:
#include "hal_board_cfg_DIYRuZRT.h"
- indiquer que la connexion de ce fichier est la toute première - dans les paramètres du projet:
$PROJ_DIR$\..\Source\preinclude.h
Nous pouvons maintenant modifier les paramètres d'équipement dans notre fichier hal_board_cfg_DIYRuZRT.h et dans le fichier preinclude.h sans avoir à modifier les fichiers partagés.J'ai transféré les directives du compilateur dans le même fichier preinclude.h et les ai supprimées dans les options du compilateur:#define SECURE 1
#define TC_LINKKEY_JOIN
#define NV_INIT
#define NV_RESTORE
#define xZTOOL_P1
#define xMT_TASK
#define xMT_APP_FUNC
#define xMT_SYS_FUNC
#define xMT_ZDO_FUNC
#define xMT_ZDO_MGMT
#define xMT_APP_CNF_FUNC
#define LEGACY_LCD_DEBUG
#define LCD_SUPPORTED DEBUG
#define MULTICAST_ENABLED FALSE
#define ZCL_READ
#define ZCL_WRITE
#define ZCL_BASIC
#define ZCL_IDENTIFY
#define ZCL_SCENES
#define ZCL_GROUPS
Dans le mĂŞme fichier hal_board_cfg_DIYRuZRT.h on retrouve la description du bouton S1 et du Joystick Center Press:
#define PUSH1_BV BV(1)
#define PUSH1_SBIT P0_1
#define PUSH2_BV BV(0)
#define PUSH2_SBIT P2_0
#define PUSH2_POLARITY ACTIVE_HIGH
Cela correspond aux broches des boutons de la carte.Regardons l'initialisation matérielle - la macro HAL_BOARD_INIT dans le même fichier. Par défaut, la directive HAL_BOARD_CC2530EB_REV17 est activée , nous examinons donc la variante de macro correspondante.
#if defined (HAL_BOARD_CC2530EB_REV17) && !defined (HAL_PA_LNA) && \
!defined (HAL_PA_LNA_CC2590) && !defined (HAL_PA_LNA_SE2431L) && \
!defined (HAL_PA_LNA_CC2592)
#define HAL_BOARD_INIT() \
{ \
uint16 i; \
\
SLEEPCMD &= ~OSC_PD; \
while (!(SLEEPSTA & XOSC_STB)); \
asm("NOP"); \
for (i=0; i<504; i++) asm("NOP"); \
CLKCONCMD = (CLKCONCMD_32MHZ | OSC_32KHZ); \
while (CLKCONSTA != (CLKCONCMD_32MHZ | OSC_32KHZ)); \
SLEEPCMD |= OSC_PD; \
\
\
PREFETCH_ENABLE(); \
\
HAL_TURN_OFF_LED1(); \
LED1_DDR |= LED1_BV; \
HAL_TURN_OFF_LED2(); \
LED2_DDR |= LED2_BV; \
HAL_TURN_OFF_LED3(); \
LED3_DDR |= LED3_BV; \
HAL_TURN_OFF_LED4(); \
LED4_SET_DIR(); \
\
\
P0INP |= PUSH2_BV; \
}
C'est dans cette macro que les modes et registres du processeur sont initialisés.Au lieu de cela, LED2_DDR et d'autres seront remplacés P1DIR - enregistrez ce port P1 , chargez les broches du mode de fonctionnement (entrée ou sortie). Par conséquent, LED2_BV est réglé sur 1 par bit de la broche correspondante (dans notre cas, 1 bit, ce qui correspond à la broche P1_1 ): les
registres et les modes du processeur sont décrits dans la documentation du«Guide de l'utilisateur du cc253x».Mais il n'est nulle part visible comment les boutons sont configurés. Les boutons sont traités de la même manière, mais dans un autre fichier - hal_key.c . Il définit les paramètres des boutons et fonctions HalKeyInit , HalKeyConfig, HalKeyRead , HalKeyPoll . Ces fonctions sont chargées d'initialiser le sous-système de travail avec les boutons et de lecture des valeurs.Par défaut, le traitement des boutons est effectué sur une minuterie toutes les 100 ms. La broche P2_0 pour la configuration actuelle est affectée au joystick et son état actuel est lu comme un clic - par conséquent, la minuterie de clignotement LED démarre.6. Nous configurons nous-mêmes l'appareil
Changement dans le fichier zcl_DIYRuZRT.h :dans le fichier OSAL_DIYRuZRT_data.c :- DIYRuZRT_DEVICE_VERSION Ă 1
- zclDIYRuZRT_ManufacturerName sur {6, 'D', 'I', 'Y', 'R', 'u', 'Z'}
- zclDIYRuZRT_ModelId sur {9, 'D', 'I', 'Y', 'R', 'u', 'Z', '_', 'R', 'T'}
- zclDIYRuZRT_DateCode sur {8, '2', '0', '2', '0', '0', '4', '0', '5'}
Pour que le périphérique puisse se connecter au réseau sur n'importe quel canal (seulement 11 par défaut, spécifié dans la directive DEFAULT_CHANLIST dans le fichier Tools \ f8wConfig.cfg ), vous devez spécifier cette fonctionnalité dans le fichier preinclude.h en modifiant la valeur de la directive.Nous ajoutons également la directive de compilation DISABLE_GREENPOWER_BASIC_PROXY afin que le point de terminaison GREENPOWER ne soit pas créé pour notre appareil.Désactivez également la prise en charge inutile de l'écran LCD.
#define DISABLE_GREENPOWER_BASIC_PROXY
#define DEFAULT_CHANLIST 0x07FFF800
Afin que notre appareil essaie automatiquement de se connecter au réseau, nous ajouterons la connexion au réseau dans le code de fonction zclDIYRuZRT_Init .bdb_StartCommissioning(BDB_COMMISSIONING_MODE_NWK_STEERING |
BDB_COMMISSIONING_MODE_FINDING_BINDING);
Après cela, exécutez Build, remplissez le firmware dans la puce et commencez le couplage sur le coordinateur. Je vérifie le fonctionnement du réseau Zigbee dans ioBroker.zigbee, voici à quoi ressemble le nouvel appareil connecté:
super, il s'est avéré qu'il fallait connecter l'appareil!7. Nous compliquons le fonctionnement de l'appareil
Essayons maintenant d'adapter un peu la fonctionnalité:- Le processus de connexion de l'appareil au réseau se fait par un appui long sur le bouton.
- Si l'appareil était déjà sur le réseau, un appui long l'affiche depuis le réseau.
- Appui court - change l'état de la LED.
- L'état de la LED doit être conservé lorsque l'appareil démarre après une panne de courant.
Pour configurer mon propre traitement de bouton, j'ai créé la fonction DIYRuZRT_HalKeyInit similaire à celle du module hal_key.c , mais exclusivement pour mon ensemble de boutons.
void DIYRuZRT_HalKeyInit( void )
{
halKeySavedKeys = 0;
PUSH1_SEL &= ~(PUSH1_BV);
PUSH1_DIR &= ~(PUSH1_BV);
PUSH1_ICTL &= ~(PUSH1_ICTLBIT);
PUSH1_IEN &= ~(PUSH1_IENBIT);
PUSH2_SEL &= ~(PUSH2_BV);
PUSH2_DIR &= ~(PUSH2_BV);
PUSH2_ICTL &= ~(PUSH2_ICTLBIT);
PUSH2_IEN &= ~(PUSH2_IENBIT);
}
L'appel de cette fonction a été ajouté au fichier de macro HAL_BOARD_INIT hal_board_cfg_DIYRuZRT.h . Pour éviter les conflits, désactivez le hal_key intégré dans le même fichier hal_board_cfg_DIYRuZRT.h :#define HAL_KEY FALSE
Parce que le lecteur de bouton standard est désactivé, nous le ferons nous-mêmes.Dans la fonction d'initialisation zclDIYRuZRT_Init , nous démarrons le temporisateur pour lire les états des boutons. La minuterie générera notre événement HAL_KEY_EVENT .osal_start_reload_timer( zclDIYRuZRT_TaskID, HAL_KEY_EVENT, 100);
Et dans la boucle d'événement, nous traiterons l'événement HAL_KEY_EVENT en appelant la fonction DIYRuZRT_HalKeyPoll :
void DIYRuZRT_HalKeyPoll (void)
{
uint8 keys = 0;
if (HAL_PUSH_BUTTON1())
{
keys |= HAL_KEY_SW_1;
}
if (HAL_PUSH_BUTTON2())
{
keys |= HAL_KEY_SW_2;
}
if (keys == halKeySavedKeys)
{
return;
}
halKeySavedKeys = keys;
OnBoard_SendKeys(keys, HAL_KEY_STATE_NORMAL);
}
L'enregistrement de l'état des boutons dans la variable halKeySavedKeys nous permet de déterminer le moment du changement - en appuyant et en relâchant les boutons.Lorsque vous cliquez sur le bouton, démarrez la minuterie pendant 5 secondes. Si ce minuteur se déclenche, l'événement DIYRuZRT_EVT_LONG sera généré . Si le bouton est relâché, la minuterie est réinitialisée. Dans tous les cas, si vous appuyez sur le bouton, nous changeons l'état de la LED.
static void zclDIYRuZRT_HandleKeys( byte shift, byte keys )
{
if ( keys & HAL_KEY_SW_1 )
{
osal_start_timerEx(zclDIYRuZRT_TaskID, DIYRuZRT_EVT_LONG, 5000);
updateRelay(RELAY_STATE == 0);
}
else
{
osal_stop_timerEx(zclDIYRuZRT_TaskID, DIYRuZRT_EVT_LONG);
}
}
Maintenant, lors du traitement d'un événement de pression longue, nous prêtons attention à l'état actuel du réseau via l'attribut de structure bdbAttributes.bdbNodeIsOnANetwork
if ( events & DIYRuZRT_EVT_LONG )
{
if ( bdbAttributes.bdbNodeIsOnANetwork )
{
zclDIYRuZRT_LeaveNetwork();
}
else
{
bdb_StartCommissioning(
BDB_COMMISSIONING_MODE_NWK_FORMATION |
BDB_COMMISSIONING_MODE_NWK_STEERING |
BDB_COMMISSIONING_MODE_FINDING_BINDING |
BDB_COMMISSIONING_MODE_INITIATOR_TL
);
osal_start_timerEx(zclDIYRuZRT_TaskID, DIYRuZRT_EVT_BLINK, 500);
}
return ( events ^ DIYRuZRT_EVT_LONG );
}
Nous allons plus loin. L'état de la LED sera enregistré dans une variable, dont nous enregistrerons la valeur dans la mémoire NV. Lorsque l'appareil démarre, nous lirons la valeur de la mémoire dans une variable.
if ( SUCCESS == osal_nv_item_init( NV_DIYRuZRT_RELAY_STATE_ID, 1, &RELAY_STATE ) ) {
osal_nv_read( NV_DIYRuZRT_RELAY_STATE_ID, 0, 1, &RELAY_STATE );
}
applyRelay();
void updateRelay ( bool value )
{
if (value) {
RELAY_STATE = 1;
} else {
RELAY_STATE = 0;
}
osal_nv_write(NV_DIYRuZRT_RELAY_STATE_ID, 0, 1, &RELAY_STATE);
applyRelay();
}
void applyRelay ( void )
{
if (RELAY_STATE == 0) {
HalLedSet ( HAL_LED_1, HAL_LED_MODE_OFF );
} else {
HalLedSet ( HAL_LED_1, HAL_LED_MODE_ON );
}
}
8. Nous allons maintenant traiter avec Zigbee
Jusqu'à présent, nous avons trié le matériel - avec le bouton, nous contrôlons la LED. Maintenant, nous implémentons la même chose via Zigbee.Pour contrôler le relais, il nous suffit d'utiliser notre seul point de terminaison et d'implémenter le cluster GenOnOff . Nous lisons la spécification de la bibliothèque de clusters Zigbee pour le cluster GenOnOff :
il suffit d'implémenter l'attribut OnOff et les commandes On, Off, Toggle.Tout d'abord, ajoutez la directive à preinclude.h :#define ZCL_ON_OFF
Dans la description de nos attributs zclDIYRuZRT_Attrs, nous ajoutons de nouveaux attributs de cluster:
{
ZCL_CLUSTER_ID_GEN_ON_OFF,
{
ATTRID_ON_OFF,
ZCL_DATATYPE_BOOLEAN,
ACCESS_CONTROL_READ,
(void *)&RELAY_STATE
}
},
{
ZCL_CLUSTER_ID_GEN_ON_OFF,
{
ATTRID_CLUSTER_REVISION,
ZCL_DATATYPE_UINT16,
ACCESS_CONTROL_READ | ACCESS_CLIENT,
(void *)&zclDIYRuZRT_clusterRevision_all
}
},
Nous ajoutons également le cluster à la liste des clusters de noeuds finaux entrants pris en charge zclDIYRuZRT_InClusterList .Pour implémenter des commandes de contrôle, ajoutez un gestionnaire à la table zclDIYRuZRT_CmdCallbacks .
static zclGeneral_AppCallbacks_t zclDIYRuZRT_CmdCallbacks =
{
zclDIYRuZRT_BasicResetCB,
NULL,
zclDIYRuZRT_OnOffCB,
NULL,
NULL,
NULL,
#ifdef ZCL_LEVEL_CTRL
NULL,
NULL,
NULL,
NULL,
#endif
Et nous l'implémentons:
static void zclDIYRuZRT_OnOffCB(uint8 cmd)
{
afIncomingMSGPacket_t *pPtr = zcl_getRawAFMsg();
zclDIYRuZRT_DstAddr.addr.shortAddr = pPtr->srcAddr.addr.shortAddr;
if (cmd == COMMAND_ON) {
updateRelay(TRUE);
}
else if (cmd == COMMAND_OFF) {
updateRelay(FALSE);
}
else if (cmd == COMMAND_TOGGLE) {
updateRelay(RELAY_STATE == 0);
}
}
Génial, maintenant le relais peut être commuté par des commandes.
Mais ce n'est pas assez. Maintenant, nous devons également informer le coordinateur de l'état actuel de la LED, si nous l'allumons avec le bouton.Encore une fois, ajoutez la directive:#define ZCL_REPORTING_DEVICE
Créez maintenant une fonction zclDIYRuZRT_ReportOnOff qui envoie un message d'état. Nous l'appellerons lorsque la LED est commutée et lorsque l'appareil démarre.
void zclDIYRuZRT_ReportOnOff(void) {
const uint8 NUM_ATTRIBUTES = 1;
zclReportCmd_t *pReportCmd;
pReportCmd = osal_mem_alloc(sizeof(zclReportCmd_t) +
(NUM_ATTRIBUTES * sizeof(zclReport_t)));
if (pReportCmd != NULL) {
pReportCmd->numAttr = NUM_ATTRIBUTES;
pReportCmd->attrList[0].attrID = ATTRID_ON_OFF;
pReportCmd->attrList[0].dataType = ZCL_DATATYPE_BOOLEAN;
pReportCmd->attrList[0].attrData = (void *)(&RELAY_STATE);
zclDIYRuZRT_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
zclDIYRuZRT_DstAddr.addr.shortAddr = 0;
zclDIYRuZRT_DstAddr.endPoint = 1;
zcl_SendReportCmd(DIYRuZRT_ENDPOINT, &zclDIYRuZRT_DstAddr,
ZCL_CLUSTER_ID_GEN_ON_OFF, pReportCmd,
ZCL_FRAME_CLIENT_SERVER_DIR, false, SeqNum++);
}
osal_mem_free(pReportCmd);
}
Maintenant, dans les journaux, nous voyons des messages sur un changement d'état de la LED.9. Connectez le capteur de température ds18b20
Le capteur est connecté à n'importe quelle broche libre (dans mon cas, définissez P2_1 ).Ajoutez le code d'interrogation du capteur à l'application. Nous interviewerons régulièrement - une fois par minute.Immédiatement après le sondage, le coordinateur du réseau vous informera de la valeur actuelle.Lisez les spécifications ZCL pour l'envoi de données à partir de capteurs de température. Nous avons besoin d'unemesure de température de cluster .
Nous voyons que nous devons implémenter 3 attributs, dont l'un représente la valeur de température multipliée par 100.Ici, nous ajoutons les attributs par analogie avec le cluster GenOnOff . Le coordinateur sera informé de l'événement DIYRuZRT_REPORTING_EVT , que nous prévoyons au départ une fois par minute. Dans le gestionnaire d'événements, nous appelleronszclDIYRuZRT_ReportTemp , qui lira la température du capteur et enverra un message.
void zclDIYRuZRT_ReportTemp( void )
{
zclDIYRuZRT_MeasuredValue = readTemperature();
const uint8 NUM_ATTRIBUTES = 1;
zclReportCmd_t *pReportCmd;
pReportCmd = osal_mem_alloc(sizeof(zclReportCmd_t) +
(NUM_ATTRIBUTES * sizeof(zclReport_t)));
if (pReportCmd != NULL) {
pReportCmd->numAttr = NUM_ATTRIBUTES;
pReportCmd->attrList[0].attrID = ATTRID_MS_TEMPERATURE_MEASURED_VALUE;
pReportCmd->attrList[0].dataType = ZCL_DATATYPE_INT16;
pReportCmd->attrList[0].attrData = (void *)(&zclDIYRuZRT_MeasuredValue);
zclDIYRuZRT_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
zclDIYRuZRT_DstAddr.addr.shortAddr = 0;
zclDIYRuZRT_DstAddr.endPoint = 1;
zcl_SendReportCmd(DIYRuZRT_ENDPOINT, &zclDIYRuZRT_DstAddr,
ZCL_CLUSTER_ID_MS_TEMPERATURE_MEASUREMENT, pReportCmd,
ZCL_FRAME_CLIENT_SERVER_DIR, false, SeqNum++);
}
osal_mem_free(pReportCmd);
}
10. Versez le firmware dans l'appareil
Pour changer le devboard en Sonoff BASICZBR3, vous devez ajuster la correspondance des broches et boutons LED.
Refaire la LED 1 sur la broche P0_7 pour contrôler le relais. L'inclusion est effectuée par un niveau élevé de ACTIVE_HIGH . Nous raccrochons le bouton S1 sur la broche P1_3 , et la LED d'information 2 sur P1_0 . Nous laissons le capteur de température sur la broche P2_1 . Nous apportons toutes ces modifications dans le fichier hal_board_cfg_DIYRuZRT.h . Pour sélectionner une configuration, nous allons créer une directive distincte HAL_SONOFF . S'il est défini, les paramètres de Sonoff BASICZBR3 seront utilisés, sinon pour devboard.#ifdef HAL_SONOFF
#define LED1_BV BV(7)
#define LED1_SBIT P0_7
#define LED1_DDR P0DIR
#define LED1_POLARITY ACTIVE_HIGH
#define LED2_BV BV(0)
#define LED2_SBIT P1_0
#define LED2_DDR P1DIR
#define LED2_POLARITY ACTIVE_LOW
#else
#define LED1_BV BV(0)
#define LED1_SBIT P1_0
#define LED1_DDR P1DIR
#define LED1_POLARITY ACTIVE_LOW
#define LED2_BV BV(1)
#define LED2_SBIT P1_1
#define LED2_DDR P1DIR
#define LED2_POLARITY ACTIVE_LOW
#endif
Un autre paramètre important a dû être corrigé - la présence de quartz "montre", car Sur la carte Sonoff BASICZBR3, elle n'est pas soudée:
#define OSC32K_CRYSTAL_INSTALLED FALSE
Sans ces options, le firmware ne démarre pas (ou plutôt, pas toujours).Après cela, nous collectons le firmware et nous nous connectons au firmware.ATTENTION!!! Débranchez le relais Sonoff BASICZBR3 de l'alimentation secteur avant toute connexion et firmware!
Nous connectons le câblage Sonoff BASICZBR3 avec CCDebugger, effaçons la puce et flasher notre firmware.
11. Nous démarrons l'appareil dans zigbee2mqtt et ioBroker.Zigbee
Bien que l'appareil, nous sommes apparus dans la liste des appareils connectés et nous pouvons le gérer en envoyant des commandes, mais nous devons le faire plus correctement - avec des images et des états.Pour obtenir un nouvel appareil dans ioBroker.Zigbee , vous devez effectuer 2 étapes:- zigbee-herdsman-converters. , zigbee2mqtt.
- ioBroker.Zigbee
Toutes les modifications peuvent être effectuées d'abord dans les fichiers locaux, puis effectuées dans les référentiels correspondants.Nous trouvons l'emplacement du paquet zigbee-herdsman-converters dans le ioBroker installé (ou zigbee2mqtt). À l'intérieur du package, nous trouvons le fichier devices.js https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/devices.jsCe fichier contient des descriptions de tous les périphériques avec lesquels ioBroker.zigbee et zigbee2mqtt peuvent travailler . On y retrouve un bloc de descriptions des appareils DIYRuZ (après 2300 lignes). Ajoutez une description du nouvel appareil à ce bloc: {
zigbeeModel: ['DIYRuZ_RT'],
model: 'DIYRuZ_RT',
vendor: 'DIYRuZ',
description: '',
supports: 'on/off, temperature',
fromZigbee: [fz.on_off, fz.temperature],
toZigbee: [tz.on_off],
},
Dans l'attribut fromZigbee, nous spécifions les convertisseurs qui traiteront les messages provenant de l'appareil. Nos deux postes sont standardisés. Le convertisseur fz.on_off traite le message d'activation / désactivation et fz.temperature traite les données de température. Le code de ces convertisseurs (situé dans le fichier converters / fromZigbee.js) montre comment les messages entrants sont traités et que la température est divisée par 100. on_off: {
cluster: 'genOnOff',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
if (msg.data.hasOwnProperty('onOff')) {
const property = getProperty('state', msg, model);
return {[property]: msg.data['onOff'] === 1 ? 'ON' : 'OFF'};
}
},
},
temperature: {
cluster: 'msTemperatureMeasurement',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const temperature = parseFloat(msg.data['measuredValue']) / 100.0;
return {temperature: calibrateAndPrecisionRoundOptions(temperature, options, 'temperature')};
},
},
Dans l'attribut toZigbee , nous spécifions les convertisseurs qui traiteront nos commandes vers l'appareil. Dans notre cas, il s'agit du convertisseur tz.on_off pour la commutation des relais.Tout a été ajouté aux "convertisseurs". Qui utilise zigbee2mqtt - vous pouvez déjà l'utiliser.Et les utilisateurs d'ioBroker ajoutent toujours une description de l'appareil au fichier ioBroker.zigbee \ lib \ devices.js {
vendor: 'DIYRuZ',
models: ['DIYRuZ_RT'],
icon: 'img/DIYRuZ.png',
states: [
states.state,
states.temperature,
],
},
Ici, il suffit d'indiquer exactement le même modèle, un fichier avec une image et une liste d'états. Dans notre cas, les états sont également standard: état de l'état du relais, température d'affichage des valeurs de température.
12. Et ensuite?
Malheureusement, je n'ai pas pu comprendre tous les aspects et fonctionnalités fournis par Z-Stack 3.0. Très probablement, je n'ai même pas correctement implémenté une sorte de fonctionnalité ou certains mécanismes intégrés pourraient être utilisés pour l'implémenter.Par conséquent, la solution ci-dessus peut être améliorée et développée. Voici quelques directions:- Je n'ai pas pu trouver rapidement de solutions pour la possibilité de connecter des appareils enfants via des relais. D'autres périphériques de routeur peuvent exécuter la commande permit_join et connecter de nouveaux périphériques par eux-mêmes, sans avoir à apporter le nouveau périphérique au coordinateur. L'appareil est représenté par un routeur, il est correctement affiché sur la carte du réseau, mais refuse d'exécuter la commande «permit_join». Plus précisément, la commande s'exécute, mais les périphériques ne se connectent pas via elle.
- En outre, n'a pas mis en œuvre un rapport correct. Il s'agit d'un moyen de configurer la notification d'état lorsque vous pouvez utiliser la commande configReport pour spécifier une liste d'attributs à envoyer et la fréquence de notification.
- Travaillez avec des groupes.
- Traitez les interruptions et implémentez les boutons d'interrogation via les interruptions.
Eh bien, pour les appareils suivants, vous devez gérer les modes d'alimentation et la création d'appareils "en veille" sur piles.Comme toujours, en plus des commentaires, je vous invite à discuter de cela et d'autres appareils dans le chat Telegram sur Zigbee .
Je tiens à exprimer ma gratitude pour le soutien et l'assistance dans le développement de mes collègues de la communauté Telegram chat et Zigbee:
Références
La documentation principale est incluse dans le SDK Z-Stack 3.0.2 et est installée avec lui. Mais je vais donner une partie des liens ici: