Nous Ă©crivons le firmware pour TI cc2530 sur Z-Stack 3.0 pour relais Zigbee Sonoff BASICZBR3 avec capteur ds18b20



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 fini

L'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. La

terminologie 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
  //Added to include TouchLink functionality
  #if defined ( INTER_PAN )
    StubAPS_ProcessEvent,
  #endif
  // Added to include TouchLink initiator functionality
  #if defined ( BDB_TL_INITIATOR )
    touchLinkInitiator_event_loop,
  #endif
  // Added to include TouchLink target functionality
  #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
  // Added to include TouchLink functionality
#if defined ( INTER_PAN )
  StubAPS_Init( taskID++ );
#endif
// Added to include TouchLink initiator functionality
#if defined( BDB_TL_INITIATOR )
  touchLinkInitiator_Init( taskID++ );
#endif
// Added to include TouchLink target functionality
#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.

/*********************************************************************
 * SIMPLE DESCRIPTOR
 */
// This is the Cluster ID List and should be filled with Application
// specific cluster IDs.
const cId_t zclDIYRuZRT_InClusterList[] =
{
  ZCL_CLUSTER_ID_GEN_BASIC,
  ZCL_CLUSTER_ID_GEN_IDENTIFY,
  
  // DIYRuZRT_TODO: Add application specific Input Clusters Here. 
  //       See zcl.h for Cluster ID definitions
  
};
#define ZCLDIYRuZRT_MAX_INCLUSTERS   (sizeof(zclDIYRuZRT_InClusterList) / sizeof(zclDIYRuZRT_InClusterList[0]))


const cId_t zclDIYRuZRT_OutClusterList[] =
{
  ZCL_CLUSTER_ID_GEN_BASIC,
  
  // DIYRuZRT_TODO: Add application specific Output Clusters Here. 
  //       See zcl.h for Cluster ID definitions
};
#define ZCLDIYRuZRT_MAX_OUTCLUSTERS  (sizeof(zclDIYRuZRT_OutClusterList) / sizeof(zclDIYRuZRT_OutClusterList[0]))


SimpleDescriptionFormat_t zclDIYRuZRT_SimpleDesc =
{
  DIYRuZRT_ENDPOINT,                  //  int Endpoint;
  ZCL_HA_PROFILE_ID,                     //  uint16 AppProfId;
  // DIYRuZRT_TODO: Replace ZCL_HA_DEVICEID_ON_OFF_LIGHT with application specific device ID
  ZCL_HA_DEVICEID_ON_OFF_LIGHT,          //  uint16 AppDeviceId; 
  DIYRuZRT_DEVICE_VERSION,            //  int   AppDevVer:4;
  DIYRuZRT_FLAGS,                     //  int   AppFlags:4;
  ZCLDIYRuZRT_MAX_INCLUSTERS,         //  byte  AppNumInClusters;
  (cId_t *)zclDIYRuZRT_InClusterList, //  byte *pAppInClusterList;
  ZCLDIYRuZRT_MAX_OUTCLUSTERS,        //  byte  AppNumInClusters;
  (cId_t *)zclDIYRuZRT_OutClusterList //  byte *pAppInClusterList;
};


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:
          // Incoming ZCL Foundation command/response messages
          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);

          // now on the network
          if ( (zclDIYRuZRT_NwkState == DEV_ZB_COORD) ||
               (zclDIYRuZRT_NwkState == DEV_ROUTER)   ||
               (zclDIYRuZRT_NwkState == DEV_END_DEVICE) )
          {
            giGenAppScreenMode = GENERIC_MAINMODE;
            zclDIYRuZRT_LcdDisplayUpdate();
          }
          break;

        default:
          break;
      }

      // Release the memory
      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 )
  {
    // toggle LED 2 state, start another timer for 500ms
    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. En

recherchant, 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 :

  /* 2 - Red */
  #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:

/* S1 */
#define PUSH1_BV          BV(1)
#define PUSH1_SBIT        P0_1

/* Joystick Center Press */
#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.

/* ----------- Board Initialization ---------- */
#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;                       /* turn on 16MHz RC and 32MHz XOSC */                \
  while (!(SLEEPSTA & XOSC_STB));            /* wait for 32MHz XOSC stable */                     \
  asm("NOP");                                /* chip bug workaround */                            \
  for (i=0; i<504; i++) asm("NOP");          /* Require 63us delay for all revs */                \
  CLKCONCMD = (CLKCONCMD_32MHZ | OSC_32KHZ); /* Select 32MHz XOSC and the source for 32K clock */ \
  while (CLKCONSTA != (CLKCONCMD_32MHZ | OSC_32KHZ)); /* Wait for the change to be effective */   \
  SLEEPCMD |= OSC_PD;                        /* turn off 16MHz RC */                              \
                                                                 \
  /* Turn on cache prefetch mode */                              \
  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();                                                \
                                                                 \
  /* configure tristates */                                      \
  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 :

  • DIYRuZRT_ENDPOINT sur 1

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 LCD_SUPPORTED DEBUG
#define DISABLE_GREENPOWER_BASIC_PROXY
#define DEFAULT_CHANLIST 0x07FFF800  // ALL Channels

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 )
{
  /*      0 */
  halKeySavedKeys = 0;

  PUSH1_SEL &= ~(PUSH1_BV); /*    - GPIO */
  PUSH1_DIR &= ~(PUSH1_BV); /*    -  */
  
  PUSH1_ICTL &= ~(PUSH1_ICTLBIT); /*      */
  PUSH1_IEN &= ~(PUSH1_IENBIT);   /*     */
  
  PUSH2_SEL &= ~(PUSH2_BV); /* Set pin function to GPIO */
  PUSH2_DIR &= ~(PUSH2_BV); /* Set pin direction to Input */
  
  PUSH2_ICTL &= ~(PUSH2_ICTLBIT); /* don't generate interrupt */
  PUSH2_IEN &= ~(PUSH2_IENBIT);   /* Clear interrupt enable bit */
}

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;

  //   1 ?
  if (HAL_PUSH_BUTTON1())
  {
    keys |= HAL_KEY_SW_1;
  }
  
  //   2 ?
  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 )
  {
    //       - 5 
    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

  //  DIYRuZRT_EVT_LONG
  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.

  //  NVM   RELAY STATE
  if ( SUCCESS == osal_nv_item_init( NV_DIYRuZRT_RELAY_STATE_ID, 1, &RELAY_STATE ) ) {
    //   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) {
    //    1
    HalLedSet ( HAL_LED_1, HAL_LED_MODE_OFF );
  } else {
    //    1
    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:

  // ***  On/Off  ***
  {
    ZCL_CLUSTER_ID_GEN_ON_OFF,
    { // 
      ATTRID_ON_OFF,
      ZCL_DATATYPE_BOOLEAN,
      ACCESS_CONTROL_READ,
      (void *)&RELAY_STATE
    }
  },
  {
    ZCL_CLUSTER_ID_GEN_ON_OFF,
    {  //  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 .

/*********************************************************************
 *    ZCL 
 */
static zclGeneral_AppCallbacks_t zclDIYRuZRT_CmdCallbacks =
{
  zclDIYRuZRT_BasicResetCB,               // Basic Cluster Reset command
  NULL,                                   // Identify Trigger Effect command
  zclDIYRuZRT_OnOffCB,                    // On/Off cluster commands
  NULL,                                   // On/Off cluster enhanced command Off with Effect
  NULL,                                   // On/Off cluster enhanced command On with Recall Global Scene
  NULL,                                   // On/Off cluster enhanced command On with Timed Off
#ifdef ZCL_LEVEL_CTRL
  NULL,                                   // Level Control Move to Level command
  NULL,                                   // Level Control Move command
  NULL,                                   // Level Control Step command
  NULL,                                   // Level Control Stop command
#endif

Et nous l'implémentons:
//    OnOff
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'une
mesure 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
  /* 1 - P0_7  */
  #define LED1_BV           BV(7)
  #define LED1_SBIT         P0_7
  #define LED1_DDR          P0DIR
  #define LED1_POLARITY     ACTIVE_HIGH

  /* 2 - P1_0  */
  #define LED2_BV           BV(0)
  #define LED2_SBIT         P1_0
  #define LED2_DDR          P1DIR
  #define LED2_POLARITY     ACTIVE_LOW
#else
  /* 1 - P1_0  */
  #define LED1_BV           BV(0)
  #define LED1_SBIT         P1_0
  #define LED1_DDR          P1DIR
  #define LED1_POLARITY     ACTIVE_LOW

  /* 2 - P1_1  */
  #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 HAL_CLOCK_CRYSTAL
  #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:

  1. zigbee-herdsman-converters. , zigbee2mqtt.
  2. 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.js

Ce 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:


All Articles