Estamos escrevendo firmware para a TI cc2530 no Z-Stack 3.0 para relé Zigbee Sonoff BASICZBR3 com sensor ds18b20



Supõe-se que o leitor já tenha um conhecimento inicial da linguagem C, saiba algo sobre o Zigbee, o chip cc2530, métodos para exibi-lo e utilizá-lo e também esteja familiarizado com projetos como o zigbee2mqtt. Caso contrário, prepare-se ou leia-o em https://myzigbee.ru e https://www.zigbee2mqtt.io/ O
artigo foi escrito primeiro em detalhes, mas acelera gradualmente e não para nos detalhes, mas descreve o código de firmware finalizado. Se alguém não estiver interessado em raciocinar, basta abrir as fontes de firmware e lê-las.

O código-fonte do firmware finalizado

A abordagem de código e desenvolvimento não afirma ser ideal. "Eu não sou um mágico, estou apenas aprendendo."

objetivo


O objetivo principal é entender como escrever firmware para o Z-Stack por um longo tempo. Portanto, decidi implementar um firmware alternativo para o equipamento acabado (o relé Sonoff BASICZBR3 foi escolhido como exemplo ) e adicionar a capacidade de conectar o popular sensor de temperatura ds18b20.

Além disso, eu queria mostrar aos desenvolvedores do Zigbee para iniciantes um exemplo de desenvolvimento de firmware para o chip TI cc2530 no Z-Stack.

1. Preparação


Para iniciar o desenvolvimento, você precisa baixar e instalar o Z-Stack 3.0.2 - este é um SDK para o desenvolvimento de firmware com exemplos e documentação.
Você também precisa fazer o download e instalar o IAR Embeded Workbench for 8051 - este é um ambiente de desenvolvimento com capacidade de compilar para os chips TI cc2530. O período de uso gratuito é de 1 mês (mas o solicitante encontrará uma solução).

Para desenvolvimento e depuração, uso o CCDebugger - ele permite não apenas piscar os chips cc2531 / cc2530, mas também depurar o aplicativo no ambiente IAR.



Para simplificar os experimentos, prototipagem e depuração que eu faço no devboard e no módulo cc2530 correspondente:



2. Criando um novo aplicativo


Criamos um novo projeto baseado no GenericApp. Este é um exemplo de um aplicativo Z-Stack básico. Ele está localizado na pasta Z-Stack 3.0.2 \ Projects \ zstack \ HomeAutomation \ GenericApp.
Nós o copiamos nas proximidades e o renomeamos, por exemplo, para DIYRuZRT (vamos chamar o aplicativo para o nosso dispositivo).

Dentro da pasta CC2530DB existem arquivos:

  • GenericApp.ewd - configurações do projeto para C-SPY
  • GenericApp.ewp - arquivo de projeto
  • GenericApp.eww - Área de Trabalho

Renomeie os arquivos para DIYRuZRT.eww e DIYRuZRT.ewp.

Dentro de todos os arquivos (incluindo a pasta Origem), também alteramos todas as referências ao GenericApp para DIYRuZRT.

Agora abra o projeto DIYRuZRT.ewp no IAR. Selecionamos a configuração do RouterEB e executamos Rebuild All.



A pasta RouterEB será criada na pasta CC2530DB e o arquivo DIYRuZRT.d51 aparecerá dentro da pasta EXE - esse arquivo é conveniente para piscar e depurar a partir do IAR.

Mas se precisarmos atualizar o firmware via SmartRF Flash Programmer, faremos pequenas alterações. Para fazer isso, nas configurações do projeto na seção Link na guia Saída, altere as configurações do arquivo de saída e Formato:



Depois disso, o arquivo de firmware DIYRuZRT.hex será criado na pasta EXE, conveniente para piscar em outras ferramentas e de outras maneiras .
Mas após o upload deste firmware, o dispositivo não se conecta à rede. Bem, vamos entender.

3. Um pouco de terminologia


A terminologia do Zigbee possui os seguintes conceitos:

  • Ponto final - O ponto de descrição do dispositivo final. Normalmente, em dispositivos simples, um ponto final. Pode haver vários deles em dispositivos multifuncionais, bem como em dispositivos com diferentes perfis de interação (um perfil - um terminal).
  • Cluster (cluster) - um conjunto de atributos e comandos relacionados a um único funcional (ativado / desativado, escurecimento, medições de temperatura etc.). O cluster indica as oportunidades realizadas pelo terminal. Em um nó de extremidade, você pode implementar vários clusters diferentes, mas não os mesmos.
  • Atributo (atributo) - uma característica de um cluster cujo valor pode ser lido ou gravado. Um cluster pode ter muitos atributos.
  • Comando - uma mensagem de controle que o cluster pode processar. Uma equipe pode ter parâmetros. Isso é implementado por uma função que é executada quando um comando e parâmetros são recebidos.

Tipos de clusters, atributos e comandos são padronizados na Zigbee Cluster Library. Mas os fabricantes podem usar seus próprios clusters, com seus próprios atributos e equipes.

Alguns produtores lamentáveis ​​não se preocupam com os padrões e fazem algo com eles. Então você tem que se adaptar a eles. A

terminologia do Z-Stack também possui seus próprios conceitos , por exemplo:

  • OSAL (camada de abstração do sistema operacional) - o nível de abstração do sistema operacional. Aqui eles operam com tarefas (tarefas), mensagens (mensagens), eventos (eventos), temporizadores (temporizadores) e outros objetos.
  • HAL (Hardware Abstraction Layer) - nível de abstração do equipamento. Aqui eles operam com botões (teclas), LEDs (leds), interrupções (interrupção), etc.

A camada de hardware fornece isolamento do código do programa e do equipamento que ele controla. O nível operacional fornece mecanismos para criar e interagir entre os elementos do aplicativo.

Usar isso tudo espera por você abaixo e, em princípio, ao desenvolver firmware.

4. O que temos dentro do aplicativo base?


O código do aplicativo está localizado na pasta Origem:

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

OSAL_DIYRuZRT.c - o arquivo principal no qual a matriz de manipuladores de tarefas (tarefa)  pTaskEventHandlerFn tasksArr é preenchida e a função de inicialização osalInitTasks é implementada .

Todos os outros arquivos são necessários para implementar esses inicializadores e manipuladores.

A lista de manipuladores de tarefas pTaskEventHandlerFn tasksAr r é preenchida com referências de função. Algumas tarefas são conectadas / desconectadas pelas diretivas de compilação correspondentes.

Você pode visualizar e configurar diretivas de compilação nas opções do compilador 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 é a função de inicialização do aplicativo que registra tarefas executadas pelo aplicativo.

O registro das tarefas é realizado em ordem e cada tarefa obtém seu próprio número. É importante seguir a mesma ordem que na matriz tasksArr , como os manipuladores são chamados de acordo com o número da tarefa.

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

Nosso aplicativo registrou o manipulador de funções zclDIYRuZRT_event_loop e a função de inicialização zclDIYRuZRT_Init . Eles são adicionados por último na lista.
Estas são as duas principais funções da nossa aplicação. A implementação dessas funções está no arquivo zcl_DIYRuZRT.c .

zclDIYRuZRT_Init - função de registro de tarefas.
DIYRuZRT_ENDPOINT - o número do terminal implementado pelo nosso aplicativo.

As etapas de registro que descrevem nosso aplicativo são executadas sequencialmente:

  • bdb_RegisterSimpleDescriptor — . SimpleDescriptionFormat_t zclDIYRuZRT_SimpleDesc — , , , . OSAL_DIYRuZRT_data.c
  • zclGeneral_RegisterCmdCallbackszclGeneral_AppCallbacks_t zclDIYRuZRT_CmdCallbacks — , .
  • zcl_registerAttrListzclAttrRec_t zclDIYRuZRT_Attrs — , .
  • zcl_registerForMsg - registra o recebimento de mensagens de controle.
  • RegisterForKeys - assinamos nossa tarefa para receber eventos de clique em botão.

/*********************************************************************
 * 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 - função dos manipuladores de eventos do nosso aplicativo.

Primeiro, os eventos do sistema são processados ​​em um loop:

  • ZCL_INCOMING_MSG - os comandos de controle do dispositivo são processados ​​em zclDIYRuZRT_ProcessIncomingMsg.
  • KEY_CHANGE - os eventos de clique no botão são processados ​​em zclDIYRuZRT_HandleKeys .
  • ZDO_STATE_CHANGE - eventos de alteração de status da rede.

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

A seguir, é processado o evento especial DIYRuZRT_EVT_1 , que altera o estado do LED HAL_LED_2 e inicia o temporizador por 500m com o mesmo evento. Isso começa a piscar no 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 );
  }

O fato é que, quando o firmware é iniciado, o evento HAL_KEY_SW_1 ocorre e é nele que o timer e o evento DIYRuZRT_EVT_1 são inicializados . E se você pressionar o botão S2, o indicador irá parar (meu LED permanece aceso). Pressionar novamente começará a piscar.

5. HAL: LEDs e botões


“Espere, qual LED e botões?”, Você pergunta. Inicialmente, todos os exemplos na pilha Z são focados em vários tipos de placas de depuração da série SmartRF05 EB:



Eu tenho uma placa de depuração ligeiramente diferente e um módulo com um chip.

Existem 2 botões (+ redefinição) e 3 LEDs (+ indicador de energia) na placa. Aqui está um deles (D2) piscando quando o firmware está funcionando corretamente.

Tendo chamado os contatos, determinamos a correspondência de pinos, diodos e botões:

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

Portanto, o HAL é uma camada de abstração de hardware , uma maneira de abstrair da implementação de equipamentos. O código do aplicativo usa macros e funções que funcionam com abstrações como o Botão 1 ou LED 2 , e a correspondência específica de abstrações e equipamentos é definida separadamente.

Vamos ver que tipo de HAL_LED_2 é e como entender em qual pino está suspenso. Ao

pesquisar, encontramos o arquivo hal_led.h , onde essas constantes e a função HalLedSet são descritas , onde o número e o modo do LED são transmitidos. No interior, a função HalLedOnOff é chamada para ligar e desligar o LED, que por sua vez executa HAL_TURN_ON_LED2 ouHAL_TURN_OFF_LED2 .

HAL_TURN_ON_LED2 e HAL_TURN_OFF_LED2 são as macros descritas em hal_board_cfg.h . As macros mudam dependendo da configuração do hardware.
No meu caso:

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

Um pouco mais altas no arquivo são as correspondências de LED2_SBIT e LED2_POLARITY :

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

Isso significa que o LED 2 está localizado no pino P1_1 e seu nível de comutação é alto. Mas, a julgar pelo código, o LED deve apagar quando o botão é pressionado, mas conosco permanece aceso. Se neste arquivo hal_board_cfg.h mudarmos:

#define LED2_POLARITY     ACTIVE_HIGH

no

#define LED2_POLARITY     ACTIVE_LOW

então agora o LED apaga quando você pressiona o botão S2, como deve ser pela lógica.

Para não alterar arquivos comuns que não estão relacionados ao nosso aplicativo, é melhor fazer o contrário:

  • crie uma cópia do arquivo hal_board_cfg.h (da pasta Z-Stack 3.0.2 \ Components \ hal \ target \ CC2530EB \) em nossa pasta Source e nomeie-a, por exemplo, hal_board_cfg_DIYRuZRT.h
  • vamos fazer nossa cópia do arquivo a primeira (excluindo assim a conexão do arquivo compartilhado). Crie um arquivo preinclude.h em nossa pasta Source e escreva a linha lá:

#include "hal_board_cfg_DIYRuZRT.h"

  • indica que a conexão deste arquivo é a primeira - nas configurações do projeto:

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



Agora podemos alterar os parâmetros do equipamento em nosso arquivo hal_board_cfg_DIYRuZRT.h e no arquivo preinclude.h sem precisar editar arquivos compartilhados.

Transferi as diretivas do compilador para o mesmo arquivo preinclude.h e as excluí nas Opções do 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

No mesmo arquivo hal_board_cfg_DIYRuZRT.h , encontramos a descrição do botão S1 e do 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

Isso corresponde aos pinos dos botões no quadro.

Vejamos a inicialização do hardware - a macro HAL_BOARD_INIT no mesmo arquivo. Por padrão, a diretiva HAL_BOARD_CC2530EB_REV17 está ativada ; portanto, examinamos a variante de macro correspondente.

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

É nessa macro que os modos e registros do processador são inicializados.
Em vez disso, LED2_DDR e outros serão substituídos P1DIR - registre esta porta P1 , carregue os pinos do modo de operação (entrada ou saída). Conseqüentemente, LED2_BV está configurando para 1 por bit do pino correspondente (no nosso caso, 1 bit, que corresponde ao pino P1_1 ):



Registradores e modos de processador são descritos na documentação do

“Guia do Usuário do cc253x”.

Mas em nenhum lugar é visível como os botões são configurados. Os botões são processados ​​de maneira semelhante, mas em outro arquivo - hal_key.c . Ele define os parâmetros dos botões e funções HalKeyInit , HalKeyConfig, HalKeyRead , HalKeyPoll . Essas funções são responsáveis ​​por inicializar o subsistema de trabalhar com botões e ler valores.

Por padrão, o processamento do botão é realizado em um timer a cada 100 ms. O pino P2_0 para a configuração atual é atribuído ao joystick e seu estado atual é lido como um clique - portanto, o temporizador de pisca do LED é iniciado.

6. Nós configuramos o dispositivo para nós mesmos


Alteração no arquivo zcl_DIYRuZRT.h :

  • DIYRuZRT_ENDPOINT em 1

no arquivo OSAL_DIYRuZRT_data.c :

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

Para que o dispositivo possa se conectar à rede em qualquer canal (apenas 11 por padrão, especificado na diretiva DEFAULT_CHANLIST no arquivo Tools \ f8wConfig.cfg ), você deve especificar esse recurso no arquivo preinclude.h alterando o valor da diretiva.
Também adicionamos a diretiva de compilação DISABLE_GREENPOWER_BASIC_PROXY para que o ponto de extremidade GREENPOWER não seja criado para o nosso dispositivo.

Desative também o suporte desnecessário à tela LCD.

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

Para que nosso dispositivo tente se conectar automaticamente à rede, adicionaremos a conexão à rede no código de função zclDIYRuZRT_Init .

bdb_StartCommissioning(BDB_COMMISSIONING_MODE_NWK_STEERING |
                         BDB_COMMISSIONING_MODE_FINDING_BINDING);

Depois disso, execute Build, preencha o firmware no chip e comece a emparelhar no coordenador. Verifico o funcionamento da rede Zigbee no ioBroker.zigbee, é assim que o novo dispositivo conectado se parece:



Ótimo, acabou por conectar o dispositivo!

7. Nós complicamos a operação do dispositivo


Agora vamos tentar adaptar um pouco a funcionalidade:

  • O processo de conexão do dispositivo à rede é feito pressionando longamente o botão.
  • Se o dispositivo já estava na rede, uma pressão longa o exibe na rede.
  • Pressão curta - alterna o status do LED.
  • O status do LED deve ser mantido quando o dispositivo iniciar após uma falha de energia.

Para configurar meu próprio processamento de botão, criei a função DIYRuZRT_HalKeyInit semelhante à do módulo hal_key.c , mas exclusivamente para o meu conjunto de botões.

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

Chamar esta função adicionada à macro HAL_BOARD_INIT arquivo hal_board_cfg_DIYRuZRT.h . Para evitar conflitos, desative a hal_key interna no mesmo arquivo hal_board_cfg_DIYRuZRT.h :

#define HAL_KEY FALSE

Porque Se o leitor de botões padrão estiver desativado, faremos isso sozinhos.
Na função de inicialização zclDIYRuZRT_Init , iniciamos o timer para ler os estados dos botões. O cronômetro irá gerar nosso evento HAL_KEY_EVENT .

osal_start_reload_timer( zclDIYRuZRT_TaskID, HAL_KEY_EVENT, 100);

E no loop de eventos, processamos o evento HAL_KEY_EVENT chamando a função 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);
}

Salvar o estado dos botões na variável halKeySavedKeys nos permite determinar o momento da mudança - pressionando e liberando os botões.

Ao clicar no botão, inicie o cronômetro por 5 segundos. Se este timer disparar, o evento DIYRuZRT_EVT_LONG será gerado . Se o botão for solto, o temporizador é reiniciado. De qualquer forma, se você pressionar o botão, alteramos o status do 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);
  }
}

Agora, ao processar um evento de pressão longa, prestamos atenção ao estado atual da rede por meio do atributo de estrutura 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 );
  }

Nós vamos além. O estado do LED será salvo em uma variável, cujo valor será salvo na memória NV. Quando o dispositivo iniciar, leremos o valor da memória em uma variável.

  //  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. Agora vamos lidar com o Zigbee


Até agora, resolvemos o hardware - com o botão, controlamos o LED. Agora, implementamos o mesmo através do Zigbee.

Para controlar o relé, basta usar nosso único ponto de extremidade e implementar o cluster GenOnOff . Lemos a especificação da Zigbee Cluster Library para o cluster GenOnOff :





Basta implementar o atributo OnOff e os comandos On, Off, Toggle.
Primeiro, adicione a diretiva a preinclude.h :

#define ZCL_ON_OFF

Na descrição de nossos atributos zclDIYRuZRT_Attrs, adicionamos novos atributos 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
    }
  },

Também adicionamos o cluster à lista de clusters de terminal de entrada suportados zclDIYRuZRT_InClusterList .

Para implementar comandos de controle, adicione um manipulador à tabela 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

E nós o 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);
  }
}

Ótimo, agora o relé pode ser alternado por comandos.





Mas isto não é o suficiente. Agora também devemos informar o coordenador sobre o estado atual do LED, se o mudarmos com o botão

Novamente, adicione a diretiva:

#define ZCL_REPORTING_DEVICE

Agora crie uma função zclDIYRuZRT_ReportOnOff que envia uma mensagem de status. Vamos chamá-lo quando o LED estiver ligado e quando o dispositivo iniciar.

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

Agora, nos logs, vemos mensagens sobre uma alteração no status do LED.

9. Conecte o sensor de temperatura ds18b20


O sensor está conectado a qualquer pino livre (no meu caso, defina P2_1 ).

Adicione o código de pesquisa do sensor ao aplicativo. Entrevistaremos regularmente - uma vez por minuto.
Imediatamente após a votação, o coordenador da rede notificará você sobre o valor atual.

Leia as especificações da ZCL para enviar dados de sensores de temperatura. Precisamos de uma
medição de temperatura do cluster.Vemos



que precisamos implementar 3 atributos, um dos quais representa o valor da temperatura multiplicado por 100.

Aqui, adicionamos os atributos por analogia ao cluster GenOnOff . O coordenador será informado sobre o evento DIYRuZRT_REPORTING_EVT , que planejamos no início uma vez por minuto. No manipulador de eventos, chamaremoszclDIYRuZRT_ReportTemp , que lerá a temperatura do sensor e enviará uma mensagem.

//   
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. Coloque o firmware no dispositivo


Para alterar o devboard para Sonoff BASICZBR3, é necessário ajustar a correspondência dos pinos e botões do LED.



Refaça o LED 1 no pino P0_7 para controlar o relé. A inclusão é realizada por um alto nível de ACTIVE_HIGH . Penduramos novamente o botão S1 no pino P1_3 e o LED de informações 2 no P1_0 . Deixamos o sensor de temperatura no pino P2_1 . Nós fazemos todas essas alterações no arquivo hal_board_cfg_DIYRuZRT.h . Para selecionar uma configuração, faremos uma diretiva separada HAL_SONOFF . Se estiver definido, as configurações do Sonoff BASICZBR3 serão usadas, caso contrário, para o 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

Outro parâmetro importante teve que ser corrigido - a presença de quartzo "relógio", porque Na placa Sonoff BASICZBR3, ela não é soldada:

//#define HAL_CLOCK_CRYSTAL
  #define OSC32K_CRYSTAL_INSTALLED FALSE

Sem essas opções, o firmware não inicia (ou melhor, nem sempre).

Depois disso, coletamos o firmware e nos conectamos ao firmware.
ATENÇÃO!!! Desconecte o relé Sonoff BASICZBR3 da alimentação CA antes de qualquer conexão e firmware!

Conectamos a fiação Sonoff BASICZBR3 ao CCDebugger, apagamos o chip e atualizamos nosso firmware.



11. Iniciamos o dispositivo em zigbee2mqtt e ioBroker.Zigbee


Embora o dispositivo que tenhamos aparecido na lista de dispositivos conectados e possamos gerenciá-lo enviando comandos, mas precisamos fazer isso mais corretamente - com imagens e estados.

Para obter um novo dispositivo no ioBroker.Zigbee , você precisa executar 2 etapas:

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

Todas as alterações podem ser feitas primeiro nos arquivos locais e, em seguida, no PR nos repositórios correspondentes.

Nós encontramos a localização do pacote zigbee-herdsman-converters no ioBroker instalado (ou zigbee2mqtt). Dentro do pacote, encontramos o arquivo devices.js https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/devices.js

Este arquivo contém descrições de todos os dispositivos com os quais ioBroker.zigbee e zigbee2mqtt podem trabalhar . Encontramos nele um bloco de descrições de dispositivos DIYRuZ (após 2300 linhas). Adicione uma descrição do novo dispositivo a este bloco:

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

No atributo fromZigbee, especificamos os conversores que processarão as mensagens provenientes do dispositivo. Nossos dois posts são padronizados. O conversor fz.on_off processa a mensagem liga / desliga e fz.temperature processa os dados de temperatura. O código desses conversores (localizado no arquivo conversores / fromZigbee.js) mostra como as mensagens recebidas são processadas e que a temperatura é dividida 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')};
        },
    },

No atributo toZigbee , especificamos os conversores que processarão nossos comandos no dispositivo. No nosso caso, é o conversor tz.on_off para comutação de relés.

Tudo adicionado aos "conversores". Quem usa zigbee2mqtt - você já pode usá-lo.

E os usuários do ioBroker ainda incluem uma descrição do dispositivo no arquivo ioBroker.zigbee \ lib \ devices.js

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

Aqui basta indicar exatamente o mesmo modelo, um arquivo com uma imagem e uma lista de estados. No nosso caso, os estados também são padrão: estado para o estado do relé, temperatura para exibição dos valores de temperatura.



12. O que vem depois?


Infelizmente, não consegui descobrir todos os aspectos e recursos que o Z-Stack 3.0 fornece. Provavelmente nem implementei corretamente algum tipo de funcionalidade ou alguns mecanismos internos podem ser usados ​​para implementá-lo.
Portanto, a solução acima pode ser aprimorada e desenvolvida. Aqui estão algumas instruções:

  • Não consegui encontrar rapidamente soluções para a possibilidade de conectar dispositivos filhos através de relés. Outros dispositivos roteadores podem executar o comando allow_join e conectar novos dispositivos por si mesmos, sem precisar trazer o novo dispositivo ao coordenador. O dispositivo é representado por um roteador, é exibido corretamente no mapa da rede, mas se recusa a executar o comando “allow_join”. Mais precisamente, o comando é executado, mas os dispositivos não se conectam através dele.
  • Além disso, não implementou os relatórios corretos. Essa é uma maneira de configurar a notificação de status quando você pode usar o comando configReport para especificar uma lista de atributos a serem enviados e a frequência da notificação.
  • Trabalhe com grupos.
  • Lide com interrupções e implemente botões de pesquisa por meio de interrupções.

Bem, para os seguintes dispositivos, você precisa lidar com os modos de energia e a criação de dispositivos "adormecidos" nas baterias.
Como sempre, além dos comentários, convido você a discutir este e outros dispositivos no chat do Telegram no Zigbee .

Quero expressar minha gratidão pelo apoio e assistência no desenvolvimento de meus colegas na comunidade de bate-papo e Zigbee do Telegram:


Referências


A documentação principal está incluída no SDK do Z-Stack 3.0.2 e é instalada com ele. Mas vou dar parte dos links aqui:


All Articles