Estamos escribiendo firmware para TI cc2530 en Z-Stack 3.0 para Zigbee relay Sonoff BASICZBR3 con sensor ds18b20



Se supone que el lector ya tiene un conocimiento inicial del lenguaje C, sabe algo sobre Zigbee, el chip cc2530, los métodos para flashearlo y usarlo, y también está familiarizado con proyectos como zigbee2mqtt. Si no es así, prepárese o lea en https://myzigbee.ru y https://www.zigbee2mqtt.io/ El
artículo está escrito primero en detalle, pero se acelera gradualmente y no se detiene en los detalles, sino que describe el código de firmware terminado. Si alguien no está interesado en razonar, simplemente abra las fuentes de firmware y léalas.

El código fuente del firmware terminado

El código y el enfoque de desarrollo no dicen ser ideales. "No soy un mago, solo estoy aprendiendo".

propósito


El objetivo principal es comprender cómo escribir firmware para Z-Stack durante mucho tiempo. Por lo tanto, decidí implementar un firmware alternativo para el equipo terminado (se eligió como ejemplo el relé Sonoff BASICZBR3 ) y agregar la capacidad de conectar el popular sensor de temperatura ds18b20.

Además, quería mostrar a los desarrolladores principiantes de Zigbee un ejemplo de desarrollo de firmware para el chip TI cc2530 en Z-Stack.

1. Preparación


Para comenzar el desarrollo, debe descargar e instalar Z-Stack 3.0.2 : este es un SDK para desarrollar firmware con ejemplos y documentación.
También debe descargar e instalar IAR Embedded Workbench para 8051 : este es un entorno de desarrollo con la capacidad de compilar chips TI cc2530. El período de uso gratuito es de 1 mes (pero el buscador encontrará una solución).

Para el desarrollo y la depuración utilizo CCDebugger: permite no solo flashear los chips cc2531 / cc2530, sino también depurar la aplicación en el entorno IAR.



Para simplificar los experimentos, la creación de prototipos y la depuración que hago en el panel de control y el módulo cc2530 correspondiente:



2. Crear una nueva aplicación


Creamos un nuevo proyecto basado en GenericApp. Este es un ejemplo de una aplicación básica de Z-Stack. Se encuentra en la carpeta Z-Stack 3.0.2 \ Projects \ zstack \ HomeAutomation \ GenericApp.
Lo copiamos cerca y le cambiamos el nombre, por ejemplo, a DIYRuZRT (llamemos a la aplicación para nuestro dispositivo).

Dentro de la carpeta CC2530DB hay archivos:

  • GenericApp.ewd - configuración del proyecto para C-SPY
  • GenericApp.ewp - archivo de proyecto
  • GenericApp.eww - Espacio de trabajo

Cambie el nombre de los archivos a DIYRuZRT.eww y DIYRuZRT.ewp.

Dentro de todos los archivos (incluida la carpeta Fuente), también cambiamos todas las referencias a GenericApp por DIYRuZRT.

Ahora abra el proyecto DIYRuZRT.ewp en IAR. Seleccionamos la configuración de RouterEB y realizamos Reconstruir todo.



La carpeta RouterEB se creará en la carpeta CC2530DB, y el archivo DIYRuZRT.d51 aparecerá dentro de la carpeta EXE; este archivo es conveniente para flashear y depurar desde IAR.

Pero si necesitamos actualizar el firmware a través de SmartRF Flash Programmer, haremos pequeños cambios. Para hacer esto, en la configuración del proyecto en la sección Enlace en la pestaña Salida, cambie la configuración del archivo de salida y el formato:



después de eso, el archivo de firmware DIYRuZRT.hex se creará en la carpeta EXE, conveniente para flashear desde otras herramientas y de otras maneras .
Pero después de cargar este firmware, el dispositivo no se conecta a la red. Bueno, lo entenderemos.

3. Un poco de terminología


La terminología de Zigbee tiene los siguientes conceptos:

  • Punto final : el punto de descripción del dispositivo final. Por lo general, en dispositivos simples, un punto final. Puede haber varios de ellos en dispositivos multifunción, así como en dispositivos con diferentes perfiles de interacción (un perfil - un punto final).
  • Cluster (cluster) : un conjunto de atributos y comandos relacionados con un solo funcional (encendido / apagado, atenuación, mediciones de temperatura, etc.). El clúster indica las oportunidades realizadas por el punto final. En un punto final, puede implementar varios clústeres diferentes, pero no los mismos.
  • Atributo (atributo) : una característica de un clúster cuyo valor puede leerse o escribirse. Un clúster puede tener muchos atributos.
  • Comando : un mensaje de control que el clúster puede procesar. Un equipo puede tener parámetros. Esto se implementa mediante una función que se ejecuta cuando se recibe un comando y parámetros.

Los tipos de clústeres, atributos y comandos están estandarizados en la Biblioteca de clústeres de Zigbee. Pero los fabricantes pueden usar sus propios grupos, con sus propios atributos y equipos.

Algunos productores lamentables no se preocupan por los estándares y hacen algo al respecto. Entonces tienes que adaptarte a ellos. La

terminología de Z-Stack también tiene sus propios conceptos , por ejemplo:

  • OSAL (Capa de abstracción del sistema operativo) : el nivel de abstracción del sistema operativo. Aquí operan con tareas (tareas), mensajes (mensajes), eventos (eventos), temporizadores (temporizadores) y otros objetos.
  • HAL (Capa de abstracción de hardware) : nivel de abstracción del equipo. Aquí operan con botones (teclas), LED (leds), interrupciones (interrupción), etc.

La capa de hardware proporciona aislamiento del código del programa y el equipo que controla. El nivel operativo proporciona mecanismos para construir e interactuar entre los elementos de la aplicación.

El uso de todo esto le espera a continuación y, en principio, al desarrollar firmware.

4. ¿Qué tenemos dentro de la aplicación base?


El código de la aplicación se encuentra en la carpeta Fuente:

  • OSAL_DIYRuZRT.c
  • zcl_DIYRuZRT.h
  • zcl_DIYRuZRT.c
  • zcl_DIYRuZRT_data.c — ,

OSAL_DIYRuZRT.c : el archivo principal en el que se completa la matriz de manejadores de tareas (tarea)  pTaskEventHandlerFn taskArr y se implementa la función de inicialización osalInitTasks .

Todos los demás archivos son necesarios para implementar estos inicializadores y controladores.

La lista de manejadores de tareas pTaskEventHandlerFn taskAr r se llena con referencias de funciones. Algunas tareas están conectadas / desconectadas por las directivas de compilación correspondientes.

Puede ver y configurar directivas de compilación en las opciones del compilador de símbolos definidos:



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 es la función de inicio de la aplicación que registra las tareas realizadas por la aplicación.

El registro de tareas se realiza en orden, y cada tarea obtiene su propio número. Es importante seguir el mismo orden que en la matriz taskArr , como los manejadores se llaman según el número de tarea.

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 );
}

Nuestra aplicación registró el controlador de funciones zclDIYRuZRT_event_loop y la función de inicialización zclDIYRuZRT_Init . Se agregan al final de la lista.
Estas son las dos funciones principales de nuestra aplicación. La implementación de estas funciones se encuentra en el archivo zcl_DIYRuZRT.c .

zclDIYRuZRT_Init - función de registro de tareas.
DIYRuZRT_ENDPOINT : el número de punto final implementado por nuestra aplicación.

Los pasos de registro que describen nuestra aplicación se realizan secuencialmente:

  • bdb_RegisterSimpleDescriptor — . SimpleDescriptionFormat_t zclDIYRuZRT_SimpleDesc — , , , . OSAL_DIYRuZRT_data.c
  • zclGeneral_RegisterCmdCallbackszclGeneral_AppCallbacks_t zclDIYRuZRT_CmdCallbacks — , .
  • zcl_registerAttrListzclAttrRec_t zclDIYRuZRT_Attrs — , .
  • zcl_registerForMsg - registra la recepción de mensajes de control.
  • RegisterForKeys : firmamos nuestra tarea para recibir eventos de clic de botón.

/*********************************************************************
 * 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 - función de controladores de eventos de nuestra aplicación.

Primero, los eventos del sistema se procesan en un bucle:

  • ZCL_INCOMING_MSG : los comandos de control del dispositivo se procesan en zclDIYRuZRT_ProcessIncomingMsg.
  • KEY_CHANGE : los eventos de clic de botón se procesan en zclDIYRuZRT_HandleKeys .
  • ZDO_STATE_CHANGE : eventos de cambio de estado de la red.

  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 );
    }

El siguiente es el procesamiento del evento especial DIYRuZRT_EVT_1 , que cambia el estado del LED HAL_LED_2 e inicia el temporizador durante 500m con el mismo evento. Esto inicia el parpadeo del 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 );
  }

El hecho es que cuando se inicia el firmware, se produce el evento HAL_KEY_SW_1 y es allí donde se inicializan el temporizador y el evento DIYRuZRT_EVT_1 . Y si presiona el botón S2, el parpadeo se detendrá (mi LED permanece encendido). Al presionarlo nuevamente comenzará a parpadear.

5. HAL: LEDs y botones


“Espera, ¿qué LED y botones?”, Preguntas. Inicialmente, todos los ejemplos en Z-stack se centran en varios tipos de placas de depuración de la serie SmartRF05 EB:



tengo una placa de depuración ligeramente diferente y un módulo con un chip.

Hay 2 botones (+ reinicio) y 3 LED (+ indicador de encendido) en el tablero. Aquí está uno de ellos (D2) parpadeando cuando el firmware funciona correctamente.

Habiendo llamado a los contactos, determinamos la correspondencia de pines, diodos y botones:

  • D1 - P10
  • D2 - P11
  • D3 - P14
  • S2 - P20
  • S1 - P01

Entonces, HAL es una capa de abstracción de hardware , una forma de abstraerse de la implementación de equipos. El código de la aplicación utiliza macros y funciones que funcionan con abstracciones como el Botón 1 o el LED 2 , y la correspondencia específica de las abstracciones y el equipo se configura por separado.

Veamos qué tipo de HAL_LED_2 es y cómo entender en qué pin está suspendido. Al

buscar, encontramos el archivo hal_led.h , donde se describen estas constantes y la función HalLedSet , donde se transmiten el modo y el número de LED. En el interior, se llama a la función HalLedOnOff para encender y apagar el LED, que a su vez ejecuta HAL_TURN_ON_LED2 oHAL_TURN_OFF_LED2 .

HAL_TURN_ON_LED2 y HAL_TURN_OFF_LED2 son las macros descritas en hal_board_cfg.h . Las macros cambian según la configuración del hardware.
En mi caso:

#define HAL_TURN_OFF_LED2()       st( LED2_SBIT = LED2_POLARITY (0); )
#define HAL_TURN_ON_LED2()        st( LED2_SBIT = LED2_POLARITY (1); )

Un poco más arriba en el archivo están las correspondencias de LED2_SBIT y LED2_POLARITY :

  /* 2 - Red */
  #define LED2_BV           BV(1)
  #define LED2_SBIT         P1_1
  #define LED2_DDR          P1DIR
  #define LED2_POLARITY     ACTIVE_HIGH

Esto significa que el LED 2 está ubicado en el pin P1_1 y su nivel de conmutación es alto. Pero, a juzgar por el código, el LED debería apagarse cuando se presiona el botón, pero con nosotros permanece encendido. Si en este archivo hal_board_cfg.h cambiamos:

#define LED2_POLARITY     ACTIVE_HIGH

sobre el

#define LED2_POLARITY     ACTIVE_LOW

entonces ahora el LED se apaga cuando presiona el botón S2, como debería ser por lógica.

Para no cambiar los archivos comunes que no están relacionados con nuestra aplicación, es mejor hacer lo contrario:

  • cree una copia del archivo hal_board_cfg.h (de la carpeta Z-Stack 3.0.2 \ Components \ hal \ target \ CC2530EB \) en nuestra carpeta Fuente y asígnele un nombre, por ejemplo, hal_board_cfg_DIYRuZRT.h
  • hagamos que nuestra copia del archivo sea la primera (excluyendo así la conexión del archivo compartido). Cree un archivo preinclude.h en nuestra carpeta Fuente y escriba la línea allí:

#include "hal_board_cfg_DIYRuZRT.h"

  • indica que la conexión de este archivo es la primera, en la configuración del proyecto:

$PROJ_DIR$\..\Source\preinclude.h



Ahora podemos cambiar los parámetros del equipo en nuestro archivo hal_board_cfg_DIYRuZRT.h y en el archivo preinclude.h sin tener que editar archivos compartidos.

Transferí las directivas del compilador al mismo archivo preinclude.h y las eliminé en las Opciones del compilador:

#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

En el mismo archivo hal_board_cfg_DIYRuZRT.h encontramos la descripción del botón S1 y 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

Esto corresponde a los pines de los botones en el tablero.

Veamos la inicialización del hardware: la macro HAL_BOARD_INIT en el mismo archivo. De forma predeterminada, la directiva HAL_BOARD_CC2530EB_REV17 está activada , por lo que observamos la variante de macro correspondiente.

/* ----------- 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;                                             \
}

Es en esta macro que se inicializan los modos y registros del procesador.
En su lugar, LED2_DDR y otros serán sustituidos por P1DIR : registre este puerto P1 , cargue los pines del modo de operación (entrada o salida). En consecuencia, LED2_BV se establece en 1 por bit del pin correspondiente (en nuestro caso, 1 bit, que corresponde al pin P1_1 ): los



modos de registro y procesador se describen en la documentación de la

"Guía del usuario de cc253x".

Pero en ningún lugar se ve cómo se configuran los botones. Los botones se procesan de manera similar, pero en otro archivo: hal_key.c . Define los parámetros de los botones y funciones HalKeyInit , HalKeyConfig, HalKeyRead , HalKeyPoll . Estas funciones son responsables de inicializar el subsistema de trabajar con botones y leer valores.

Por defecto, el procesamiento del botón se realiza en un temporizador cada 100 ms. El pin P2_0 para la configuración actual se asigna al joystick y su estado actual se lee como un clic; por lo tanto, se inicia el temporizador de parpadeo del LED.

6. Configuramos el dispositivo para nosotros


Cambio en el archivo zcl_DIYRuZRT.h :

  • DIYRuZRT_ENDPOINT en 1

en el archivo OSAL_DIYRuZRT_data.c :

  • DIYRuZRT_DEVICE_VERSION a la 1
  • zclDIYRuZRT_ManufacturerName en {6, 'D', 'I', 'Y', 'R', 'u', 'Z'}
  • zclDIYRuZRT_ModelId en {9, 'D', 'I', 'Y', 'R', 'u', 'Z', '_', 'R', 'T'}
  • zclDIYRuZRT_DateCode en {8, '2', '0', '2', '0', '0', '4', '0', '5'}

Para que el dispositivo pueda conectarse a la red en cualquier canal (solo 11 de forma predeterminada, especificada en la directiva DEFAULT_CHANLIST en el archivo Tools \ f8wConfig.cfg ), debe especificar esta función en el archivo preinclude.h cambiando el valor de la directiva.
También agregamos la directiva de compilación DISABLE_GREENPOWER_BASIC_PROXY para que el punto final GREENPOWER no se cree para nuestro dispositivo.

También apague el soporte innecesario para la pantalla LCD.

//#define LCD_SUPPORTED DEBUG
#define DISABLE_GREENPOWER_BASIC_PROXY
#define DEFAULT_CHANLIST 0x07FFF800  // ALL Channels

Para que nuestro dispositivo intente conectarse automáticamente a la red, agregaremos la conexión a la red al código de función zclDIYRuZRT_Init .

bdb_StartCommissioning(BDB_COMMISSIONING_MODE_NWK_STEERING |
                         BDB_COMMISSIONING_MODE_FINDING_BINDING);

Después de eso, ejecute Build, complete el firmware en el chip y comience a emparejar en el coordinador. Compruebo el funcionamiento de la red Zigbee en ioBroker.zigbee, así es como se ve el nuevo dispositivo conectado: ¡



Genial, resultó conectar el dispositivo!

7. Complicamos el funcionamiento del dispositivo.


Ahora intentemos adaptar un poco la funcionalidad:

  • El proceso de conectar el dispositivo a la red se realiza presionando prolongadamente el botón.
  • Si el dispositivo ya estaba en la red, una pulsación larga lo muestra desde la red.
  • Pulsación breve: cambia el estado del LED.
  • El estado del LED debe mantenerse cuando el dispositivo se inicia después de un corte de energía.

Para configurar mi propio procesamiento de botones, creé la función DIYRuZRT_HalKeyInit similar a la del módulo hal_key.c , pero exclusivamente para mi conjunto de botones.

//    ()
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 */
}

Llamar a esta función agregada al archivo macro HAL_BOARD_INIT hal_board_cfg_DIYRuZRT.h . Para evitar conflictos, desactive la hal_key incorporada en el mismo archivo hal_board_cfg_DIYRuZRT.h :

#define HAL_KEY FALSE

Porque El lector de botones estándar está desactivado, lo haremos nosotros mismos.
En la función de inicialización zclDIYRuZRT_Init , iniciamos el temporizador para leer los estados de los botones. El temporizador generará nuestro evento HAL_KEY_EVENT .

osal_start_reload_timer( zclDIYRuZRT_TaskID, HAL_KEY_EVENT, 100);

Y en el bucle de eventos, procesamos el evento HAL_KEY_EVENT llamando a la función 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);
}

Guardar el estado de los botones en la variable halKeySavedKeys nos permite determinar el momento del cambio, presionando y soltando los botones.

Cuando presione el botón, inicie el temporizador durante 5 segundos. Si se dispara este temporizador, se generará el evento DIYRuZRT_EVT_LONG . Si se suelta el botón, el temporizador se reinicia. En cualquier caso, si presiona el botón, cambiamos el estado del 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);
  }
}

Ahora, al procesar un evento de pulsación larga, prestamos atención al estado actual de la red a través del atributo de estructura 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 );
  }

Vamos más allá El estado del LED se guardará en una variable, cuyo valor guardaremos en la memoria NV. Cuando se inicia el dispositivo, leeremos el valor de la memoria en una 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. Ahora trataremos con Zigbee


Hasta ahora hemos resuelto el hardware: con el botón controlamos el LED. Ahora implementamos lo mismo a través de Zigbee.

Para controlar el relé, nos basta con usar nuestro único punto final e implementar el clúster GenOnOff . Leemos la especificación de la biblioteca de clústeres de Zigbee para el clúster GenOnOff :





es suficiente implementar el atributo OnOff y los comandos On, Off, Toggle.
Primero, agregue la directiva a preinclude.h :

#define ZCL_ON_OFF

En la descripción de nuestros atributos zclDIYRuZRT_Attrs agregamos nuevos atributos de clúster:

  // ***  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
    }
  },

También agregamos el clúster a la lista de clústeres de punto final entrantes admitidos zclDIYRuZRT_InClusterList .

Para implementar comandos de control, agregue un controlador a la tabla 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

Y lo implementamos:
//    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);
  }
}

Genial, ahora el relé se puede cambiar por comandos.





Pero esto no es suficiente. Ahora también debemos informar al coordinador sobre el estado actual del LED, si lo cambiamos con el botón.

Nuevamente, agregue la directiva:

#define ZCL_REPORTING_DEVICE

Ahora cree una función zclDIYRuZRT_ReportOnOff que envíe un mensaje de estado. Lo llamaremos cuando se encienda el LED y cuando se inicie el dispositivo.

//    
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);
}

Ahora en los registros vemos mensajes sobre un cambio en el estado del LED.

9. Conecte el sensor de temperatura ds18b20


El sensor está conectado a cualquier pin libre (en mi caso, configure P2_1 ).

Agregue el código de sondeo del sensor a la aplicación. Entrevistaremos regularmente, una vez por minuto.
Inmediatamente después de la encuesta, el coordinador de la red le notificará el valor actual.

Lea las especificaciones de ZCL para enviar datos desde sensores de temperatura. Necesitamos una
medición de temperatura del clúster .



Vemos que necesitamos implementar 3 atributos, uno de los cuales representa el valor de la temperatura multiplicado por 100.

Aquí agregamos los atributos por analogía con el clúster GenOnOff . El coordinador será informado sobre el evento DIYRuZRT_REPORTING_EVT , que planificamos al inicio una vez por minuto. En el controlador de eventos, llamaremoszclDIYRuZRT_ReportTemp , que leerá la temperatura del sensor y enviará un mensaje.

//   
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. Vierta el firmware en el dispositivo


Para cambiar devboard a Sonoff BASICZBR3, debe ajustar la coincidencia de los pines y botones LED. Vuelva a hacer el



LED 1 en el pin P0_7 para controlar el relé. La inclusión se lleva a cabo por un alto nivel de ACTIVE_HIGH . Volvemos a colgar el botón S1 en el pin P1_3 y el LED de información 2 en P1_0 . Dejamos el sensor de temperatura en el pin P2_1 . Realizamos todos estos cambios en el archivo hal_board_cfg_DIYRuZRT.h . Para seleccionar una configuración, crearemos una directiva separada HAL_SONOFF . Si está configurado, se usará la configuración de Sonoff BASICZBR3, de lo contrario para 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

Otro parámetro importante tuvo que ser corregido: la presencia de cuarzo "reloj", porque En la placa Sonoff BASICZBR3 no está soldada:

//#define HAL_CLOCK_CRYSTAL
  #define OSC32K_CRYSTAL_INSTALLED FALSE

Sin estas opciones, el firmware no se inicia (o más bien, no siempre).

Después de eso, recopilamos el firmware y nos conectamos al firmware.
¡¡¡ATENCIÓN!!! ¡Desconecte el relé Sonoff BASICZBR3 de la alimentación de CA antes de cualquier conexión y firmware!

Conectamos el cableado Sonoff BASICZBR3 con CCDebugger, borramos el chip y flasheamos nuestro firmware.



11. Iniciamos el dispositivo en zigbee2mqtt y ioBroker.Zigbee


Aunque el dispositivo hemos aparecido en la lista de dispositivos conectados y podemos administrarlo enviando comandos, necesitamos hacer esto más correctamente, con imágenes y estados.

Para obtener un nuevo dispositivo en ioBroker.Zigbee , debe realizar 2 pasos:

  1. zigbee-herdsman-converters. , zigbee2mqtt.
  2. ioBroker.Zigbee

Todos los cambios se pueden hacer primero en archivos locales, y luego hacer PR en los repositorios correspondientes.

Encontramos la ubicación del paquete zigbee-herdsman-converters en el ioBroker instalado (o zigbee2mqtt). Dentro del paquete encontramos el archivo devices.js https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/devices.js

Este archivo contiene descripciones de todos los dispositivos con los que ioBroker.zigbee y zigbee2mqtt pueden funcionar . Encontramos en él un bloque de descripciones de dispositivos DIYRuZ (después de 2300 líneas). Agregue una descripción del nuevo dispositivo a este bloque:

    {
        zigbeeModel: ['DIYRuZ_RT'],
        model: 'DIYRuZ_RT',
        vendor: 'DIYRuZ',
        description: '',
        supports: 'on/off, temperature',
        fromZigbee: [fz.on_off, fz.temperature],
        toZigbee: [tz.on_off],
    },

En el atributo fromZigbee, especificamos los convertidores que procesarán los mensajes provenientes del dispositivo. Nuestras dos publicaciones están estandarizadas. El convertidor fz.on_off procesa el mensaje de encendido / apagado, y fz.temperature procesa los datos de temperatura. El código de estos convertidores (ubicado en el archivo converters / fromZigbee.js) muestra cómo se procesan los mensajes entrantes y que la temperatura se divide por 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')};
        },
    },

En el atributo toZigbee , especificamos los convertidores que procesarán nuestros comandos en el dispositivo. En nuestro caso, es el convertidor tz.on_off para conmutar relés.

Todo añadido a los "convertidores". Quién usa zigbee2mqtt : ya puede usarlo.

Y los usuarios de ioBroker todavía agregan una descripción del dispositivo al archivo ioBroker.zigbee \ lib \ devices.js

    {
        vendor: 'DIYRuZ',
        models: ['DIYRuZ_RT'],
        icon: 'img/DIYRuZ.png',
        states: [
            states.state,
            states.temperature,
        ],
    },

Aquí es suficiente para indicar exactamente el mismo modelo, un archivo con una imagen y una lista de estados. En nuestro caso, los estados también son estándar: estado para el estado del relé, temperatura para mostrar los valores de temperatura.



12. ¿Qué sigue?


Desafortunadamente, no pude entender todos los aspectos y características que proporciona Z-Stack 3.0. Lo más probable es que ni siquiera implementé correctamente algún tipo de funcionalidad o podrían usarse algunos mecanismos integrados para implementarla.
Por lo tanto, la solución anterior se puede mejorar y desarrollar. Aquí hay algunas instrucciones:

  • No pude encontrar rápidamente soluciones para la posibilidad de conectar dispositivos secundarios a través de relés. Otros dispositivos de enrutador pueden ejecutar el comando permit_join y conectar nuevos dispositivos a través de ellos mismos, sin tener que llevar el nuevo dispositivo al coordinador. El dispositivo está representado por un enrutador, se muestra correctamente en el mapa de red, pero se niega a ejecutar el comando "permit_join". Más precisamente, el comando se ejecuta, pero los dispositivos no se conectan a través de él.
  • Además, no implementó informes correctos. Esta es una forma de configurar la notificación de estado cuando puede usar el comando configReport para especificar una lista de atributos para enviar y la frecuencia de la notificación.
  • Trabajar con grupos.
  • Maneje las interrupciones e implemente botones de sondeo mediante interrupciones.

Bueno, para los siguientes dispositivos debe lidiar con los modos de energía y la creación de dispositivos "inactivos" con baterías.
Como siempre, además de los comentarios, los invito a discutir este y otros dispositivos en el chat de Telegram en Zigbee .

Quiero expresar mi gratitud por el apoyo y la asistencia en el desarrollo de mis colegas en el chat de Telegram y la comunidad Zigbee:


Referencias


La documentación principal se incluye en el SDK de Z-Stack 3.0.2 y se instala con él. Pero daré parte de los enlaces aquí:


All Articles