Kami menulis firmware untuk TI cc2530 pada Z-Stack 3.0 untuk Zigbee relay Sonoff BASICZBR3 dengan sensor ds18b20



Diasumsikan bahwa pembaca sudah memiliki pengetahuan awal tentang bahasa C, tahu sesuatu tentang Zigbee, chip cc2530, metode untuk menginstal dan menggunakannya, dan juga akrab dengan proyek-proyek seperti zigbee2mqtt. Jika tidak, bersiap-siaplah atau bacalah di https://myzigbee.ru dan https://www.zigbee2mqtt.io/
Artikel ini ditulis pertama kali secara terperinci, tetapi secara bertahap berakselerasi dan tidak berhenti pada detailnya, tetapi menjelaskan kode firmware yang sudah jadi. Jika seseorang tidak tertarik pada alasan, maka buka saja sumber firmware dan bacalah.

Kode sumber dari firmware yang sudah selesai

Kode dan pendekatan pengembangan tidak mengklaim sebagai ideal. "Aku bukan penyihir, aku hanya belajar."

tujuan


Tujuan utamanya adalah memahami cara menulis firmware untuk Z-Stack untuk waktu yang lama. Oleh karena itu, saya memutuskan untuk mengimplementasikan firmware alternatif untuk peralatan jadi ( Sonoff BASICZBR3 relay dipilih sebagai contoh ) dan menambahkan kemampuan untuk menghubungkan sensor suhu ds18b20 yang populer.

Selain itu, saya ingin menunjukkan kepada pengembang pemula Zigbee contoh pengembangan firmware untuk chip TI cc2530 pada Z-Stack.

1. Persiapan


Untuk memulai pengembangan, Anda perlu mengunduh dan menginstal Z-Stack 3.0.2 - ini adalah SDK untuk mengembangkan firmware dengan contoh dan dokumentasi.
Anda juga perlu mengunduh dan menginstal IAR Embeded Workbench untuk 8051 - ini adalah lingkungan pengembangan dengan kemampuan untuk mengkompilasi chip TI cc2530. Periode penggunaan gratis adalah 1 bulan (tetapi pencari akan menemukan solusi).

Untuk pengembangan dan debugging, saya menggunakan CCDebugger - memungkinkan tidak hanya menginstal chip cc2531 / cc2530, tetapi juga debugging aplikasi di lingkungan IAR.



Untuk menyederhanakan percobaan, membuat prototipe dan debugging yang saya lakukan di devboard dan modul cc2530 yang sesuai:



2. Membuat aplikasi baru


Kami membuat proyek baru berdasarkan GenericApp. Ini adalah contoh aplikasi Z-Stack dasar. Itu terletak di folder Z-Stack 3.0.2 \ Projects \ zstack \ HomeAutomation \ GenericApp.
Kami menyalinnya di dekat situ dan mengganti namanya, misalnya, ke DIYRuZRT (sebut saja aplikasi untuk perangkat kami).

Di dalam folder CC2530DB ada file:

  • GenericApp.ewd - pengaturan proyek untuk C-SPY
  • GenericApp.ewp - file proyek
  • GenericApp.eww - Workspace

Ubah nama file menjadi DIYRuZRT.eww dan DIYRuZRT.ewp.

Di dalam semua file (termasuk folder Sumber), kami juga mengubah semua referensi untuk GenericApp ke DIYRuZRT.

Sekarang buka proyek DIYRuZRT.ewp di IAR. Kami memilih konfigurasi RouterEB dan melakukan Rebuild All.



Folder RouterEB akan dibuat di folder CC2530DB, dan file DIYRuZRT.d51 akan muncul di dalam folder EXE - file ini nyaman untuk menginstal dan debugging dari IAR.

Tetapi jika kita perlu mem-flash firmware melalui SmartRF Flash Programmer, maka kita akan membuat perubahan kecil. Untuk melakukan ini, dalam pengaturan proyek di bagian Tautan pada tab Output, ubah pengaturan untuk file Output dan Format:



Setelah itu, file firmware DIYRuZRT.hex akan dibuat di folder EXE, nyaman untuk flashing dari alat lain dan dengan cara lain .
Tetapi setelah mengunggah firmware ini, perangkat tidak terhubung ke jaringan. Baiklah, kita akan mengerti.

3. Sedikit terminologi


Terminologi Zigbee memiliki konsep berikut:

  • Endpoint - Titik deskripsi perangkat akhir. Biasanya dalam perangkat sederhana satu titik akhir. Ada beberapa dari mereka di perangkat multifungsi, serta di perangkat dengan profil interaksi yang berbeda (satu profil - satu titik akhir).
  • Cluster (cluster) - seperangkat atribut dan perintah yang terkait dengan fungsional tunggal (on / off, peredupan, pengukuran suhu, dll.). Cluster menunjukkan peluang yang direalisasikan oleh titik akhir. Di satu titik akhir, Anda dapat menerapkan beberapa kelompok berbeda, tetapi tidak yang sama.
  • Atribut (atribut) - karakteristik kluster yang nilainya dapat dibaca atau ditulis. Cluster dapat memiliki banyak atribut.
  • Command - Pesan kontrol yang bisa diproses oleh cluster. Tim mungkin memiliki parameter. Ini diimplementasikan oleh fungsi yang dieksekusi ketika perintah dan parameter diterima.

Jenis cluster, atribut, perintah distandarisasi di Perpustakaan Zigbee Cluster. Tetapi pabrikan dapat menggunakan kelompok mereka sendiri, dengan atribut dan tim mereka sendiri.

Beberapa produsen yang menyedihkan tidak mempedulikan standar dan melakukan sesuatu tentang standar tersebut. Maka Anda harus beradaptasi dengan mereka.

Terminologi Z-Stack juga memiliki konsep sendiri , misalnya:

  • OSAL (Lapisan Sistem Abstraksi Sistem Operasi) - tingkat abstraksi Sistem Operasi. Di sini mereka beroperasi dengan tugas (tugas), pesan (pesan), acara (acara), timer (timer) dan objek lainnya.
  • HAL (Hardware Abstraction Layer) - tingkat abstraksi peralatan. Di sini mereka beroperasi dengan tombol (tombol), LED (led), interupsi (Interrupt), dll.

Lapisan perangkat keras menyediakan isolasi kode program dan peralatan yang dikontrolnya. Tingkat operasional menyediakan mekanisme untuk membangun dan berinteraksi di antara elemen-elemen aplikasi.

Menggunakan ini semua menanti Anda di bawah ini dan, pada prinsipnya, saat mengembangkan firmware.

4. Apa yang kita miliki di dalam aplikasi basis?


Kode aplikasi terletak di folder Sumber:

  • OSAL_DIYRuZRT.c β€”
  • zcl_DIYRuZRT.h β€”
  • zcl_DIYRuZRT.c β€”
  • zcl_DIYRuZRT_data.c β€” ,

OSAL_DIYRuZRT.c - file utama di mana array penangan tugas (task)  pTaskEventHandlerFn taskArr diisi dan fungsi inisialisasi osalInitTasks diimplementasikan .

Semua file lain diperlukan untuk mengimplementasikan inisialisasi dan penangan ini.

Daftar penangan tugas pTaskEventHandlerFn taskAr r diisi dengan referensi fungsi. Beberapa tugas terhubung / terputus oleh arahan kompilasi yang sesuai.

Anda dapat melihat dan mengonfigurasi arahan kompilasi dalam opsi kompiler Simbol yang ditentukan:



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 adalah fungsi start-up aplikasi yang mendaftarkan tugas yang dilakukan oleh aplikasi.

Registrasi tugas dilakukan berurutan, dan setiap tugas mendapatkan nomornya sendiri. Penting untuk mengikuti urutan yang sama seperti dalam array taskArr , seperti penangan dipanggil sesuai dengan nomor tugas.

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

Aplikasi kami mendaftarkan function handler zclDIYRuZRT_event_loop dan fungsi inisialisasi zclDIYRuZRT_Init . Mereka ditambahkan terakhir dalam daftar.
Ini adalah dua fungsi utama aplikasi kita. Implementasi fungsi-fungsi ini ada di file zcl_DIYRuZRT.c .

zclDIYRuZRT_Init - fungsi pendaftaran tugas.
DIYRuZRT_ENDPOINT - nomor titik akhir yang diterapkan oleh aplikasi kami.

Langkah-langkah pendaftaran yang menggambarkan aplikasi kami dilakukan secara berurutan:

  • 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 - daftar tanda terima pesan kontrol.
  • RegisterForKeys - kami menandatangani tugas kami untuk menerima acara klik tombol.

/*********************************************************************
 * 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 - fungsi event handler aplikasi kita.

Pertama, peristiwa sistem diproses dalam satu lingkaran:

  • ZCL_INCOMING_MSG - perintah kontrol perangkat diproses dalam zclDIYRuZRT_ProcessIncomingMsg.
  • KEY_CHANGE - acara klik tombol diproses di zclDIYRuZRT_HandleKeys .
  • ZDO_STATE_CHANGE - acara perubahan status jaringan.

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

Berikutnya adalah pemrosesan acara khusus DIYRuZRT_EVT_1 , yang mengubah status LED HAL_LED_2 dan memulai timer untuk 500 m dengan acara yang sama. Ini memulai flashing 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 );
  }

Faktanya adalah bahwa ketika firmware dimulai, peristiwa HAL_KEY_SW_1 terjadi dan di dalamnya timer dan acara DIYRuZRT_EVT_1 diinisialisasi . Dan jika Anda menekan tombol S2, maka kedipan akan berhenti (LED saya tetap menyala). Menekannya lagi akan mulai berkedip.

5. HAL: LED dan tombol


"Tunggu, LED dan tombol mana?", Anda bertanya. Awalnya, semua contoh dalam Z-stack difokuskan pada berbagai jenis papan debug dari seri SmartRF05 EB:



Saya memiliki papan debug yang sedikit berbeda dan modul dengan chip.

Ada 2 tombol (+ reset) dan 3 LED (+ indikator daya) di papan tulis. Berikut adalah salah satu dari mereka (D2) yang berkedip ketika firmware bekerja dengan benar.

Setelah memanggil kontak, kami menentukan korespondensi pin, dioda dan tombol:

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

Jadi, HAL adalah Lapisan Abstraksi Perangkat Keras , cara untuk abstrak dari implementasi peralatan. Kode aplikasi menggunakan makro dan fungsi yang bekerja dengan abstraksi seperti Tombol 1 atau LED 2 , dan korespondensi spesifik abstraksi dan peralatan diatur secara terpisah.

Mari kita lihat HAL_LED_2 jenis apa dan bagaimana memahami pin yang ditangguhkan. Dengan

mencari, kami menemukan file hal_led.h , di mana konstanta ini dan fungsi HalLedSet dijelaskan , di mana nomor LED dan mode ditransmisikan. Di dalam, fungsi HalLedOnOff dipanggil untuk menghidupkan dan mematikan LED, yang selanjutnya mengeksekusi HAL_TURN_ON_LED2 atauHAL_TURN_OFF_LED2 .

HAL_TURN_ON_LED2 dan HAL_TURN_OFF_LED2 adalah makro yang dijelaskan dalam hal_board_cfg.h . Perubahan makro tergantung pada konfigurasi perangkat keras.
Dalam hal ini:

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

Sedikit lebih tinggi dalam file adalah korespondensi LED2_SBIT dan LED2_POLARITY :

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

Ini berarti bahwa LED 2 terletak di pin P1_1 dan level switching-nya tinggi. Tapi, kalau dilihat dari kodenya, LED harus padam ketika tombol ditekan, tetapi dengan kita tetap menyala. Jika dalam file ini hal_board_cfg.h kita ubah:

#define LED2_POLARITY     ACTIVE_HIGH

di

#define LED2_POLARITY     ACTIVE_LOW

maka sekarang LED padam ketika Anda menekan tombol S2, sebagaimana mestinya dengan logika.

Agar tidak mengubah file umum yang tidak terkait dengan aplikasi kita, lebih baik melakukan sebaliknya:

  • buat salinan file hal_board_cfg.h (dari folder Z-Stack 3.0.2 \ Components \ hal \ target \ CC2530EB \) di folder Source kami dan beri nama, misalnya, hal_board_cfg_DIYRuZRT.h
  • mari kita buat salinan file kita yang pertama (dengan demikian tidak termasuk koneksi file bersama). Buat file preinclude.h di folder Sumber kami dan tulis baris di sana:

#include "hal_board_cfg_DIYRuZRT.h"

  • menunjukkan koneksi file ini adalah yang pertama - dalam pengaturan proyek:

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



Sekarang kita dapat mengubah parameter peralatan di file hal_board_cfg_DIYRuZRT.h kita dan di file preinclude.h tanpa harus mengedit file bersama.

Saya mentransfer arahan kompiler ke file preinclude.h yang sama dan menghapusnya di Opsi kompiler:

#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

Dalam file yang sama hal_board_cfg_DIYRuZRT.h kita menemukan deskripsi tombol S1 dan Joystick Center Tekan:

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

Ini sesuai dengan pin tombol di papan tulis.

Mari kita lihat inisialisasi perangkat keras - makro HAL_BOARD_INIT dalam file yang sama. Secara default, arahan HAL_BOARD_CC2530EB_REV17 dihidupkan , jadi kami melihat varian makro yang sesuai.

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

Dalam makro ini mode dan register prosesor diinisialisasi.
Sebaliknya LED2_DDR dan yang lainnya akan diganti P1DIR - daftarkan port P1 ini , isi pin mode operasi (input atau output). Oleh karena itu, LED2_BV diatur ke 1 per bit dari pin yang sesuai (dalam kasus kami, 1 bit, yang sesuai dengan pin P1_1 ):



Mode register dan prosesor dijelaskan dalam dokumentasi

"Panduan Pengguna cc253x".

Tetapi tidak terlihat bagaimana tombol-tombol tersebut dikonfigurasikan. Tombol diproses sama, tetapi di file lain - hal_key.c . Ini mendefinisikan parameter tombol dan fungsi HalKeyInit , HalKeyConfig, HalKeyRead , HalKeyPoll . Fungsi-fungsi ini bertanggung jawab untuk menginisialisasi subsistem bekerja dengan tombol dan nilai membaca.

Secara default, pemrosesan tombol dilakukan pada timer setiap 100 ms. Pin P2_0 untuk konfigurasi saat ini ditetapkan ke joystick dan status saat ini dibaca sebagai klik - oleh karena itu, timer kedip LED mulai.

6. Kami mengkonfigurasi perangkat untuk diri kita sendiri


Ubah file zcl_DIYRuZRT.h :

  • DIYRuZRT_ENDPOINT pada 1

dalam file OSAL_DIYRuZRT_data.c :

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

Agar perangkat dapat terhubung ke jaringan pada saluran apa pun (hanya 11 secara default, ditentukan dalam arahan DEFAULT_CHANLIST dalam file Tools \ f8wConfig.cfg ), Anda harus menentukan fitur ini di file preinclude.h dengan mengubah nilai arahan.
Kami juga menambahkan arahan kompilasi DISABLE_GREENPOWER_BASIC_PROXY sehingga titik akhir GREENPOWER tidak dibuat untuk perangkat kami.

Matikan juga dukungan yang tidak perlu untuk layar LCD.

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

Agar perangkat kami mencoba terhubung secara otomatis ke jaringan, kami akan menambahkan koneksi ke jaringan dalam kode fungsi zclDIYRuZRT_Init .

bdb_StartCommissioning(BDB_COMMISSIONING_MODE_NWK_STEERING |
                         BDB_COMMISSIONING_MODE_FINDING_BINDING);

Setelah itu, jalankan Build, isi firmware ke dalam chip dan mulai memasangkan pada koordinator. Saya memeriksa operasi jaringan Zigbee di ioBroker.zigbee, beginilah tampilannya perangkat yang terhubung:



Hebat, ternyata menghubungkan perangkat!

7. Kami mempersulit pengoperasian perangkat


Sekarang mari kita coba sedikit menyesuaikan fungsi:

  • Proses menghubungkan perangkat ke jaringan dilakukan dengan menekan agak lama pada tombol.
  • Jika perangkat sudah ada di jaringan, maka tekan lama akan menampilkannya dari jaringan.
  • Tekan sebentar - mengalihkan status LED.
  • Status LED harus dipertahankan ketika perangkat dimulai setelah listrik mati.

Untuk mengatur pemrosesan tombol saya sendiri, saya membuat fungsi DIYRuZRT_HalKeyInit mirip dengan yang ada di modul hal_key.c , tetapi khusus untuk set tombol saya.

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

Memanggil fungsi ini ditambahkan ke file HAL_BOARD_INIT makro hal_board_cfg_DIYRuZRT.h . Untuk menghindari konflik, nonaktifkan hal_key bawaan di file yang sama hal_board_cfg_DIYRuZRT.h :

#define HAL_KEY FALSE

Karena tombol pembaca standar dinonaktifkan, kami akan melakukannya sendiri.
Dalam fungsi inisialisasi zclDIYRuZRT_Init , kita memulai timer untuk membaca status tombol. Timer akan menghasilkan acara HAL_KEY_EVENT kami .

osal_start_reload_timer( zclDIYRuZRT_TaskID, HAL_KEY_EVENT, 100);

Dan dalam loop acara, kami akan menangani acara HAL_KEY_EVENT dengan memanggil fungsi 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);
}

Menyimpan keadaan tombol dalam variabel halKeySavedKeys memungkinkan kita untuk menentukan momen perubahan - menekan dan melepaskan tombol.

Ketika Anda mengklik tombol, mulai timer selama 5 detik. Jika timer ini menyala, acara DIYRuZRT_EVT_LONG akan dibuat . Jika tombol dilepaskan, timer diatur ulang. Bagaimanapun, jika Anda menekan tombol, kami mengganti status 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);
  }
}

Sekarang, saat memproses acara pers lama, kami memperhatikan kondisi jaringan saat ini melalui atribut struktur 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 );
  }

Kami melangkah lebih jauh. Keadaan LED akan disimpan dalam variabel, nilai yang akan kami simpan dalam memori NV. Saat perangkat dimulai, kita akan membaca nilai dari memori menjadi variabel.

  //  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. Sekarang kita akan berurusan dengan Zigbee


Sejauh ini kami telah memilah-milah perangkat keras - dengan tombol kami mengontrol LED. Sekarang kami menerapkan hal yang sama melalui Zigbee.

Untuk mengontrol relai, cukup bagi kami untuk menggunakan titik akhir kami dan mengimplementasikan cluster GenOnOff . Kami membaca spesifikasi Zigbee Cluster Library untuk GenOnOff cluster :





Cukup untuk mengimplementasikan atribut OnOff dan perintah Hidupkan, Matikan, Beralih.
Pertama, tambahkan arahan ke preinclude.h :

#define ZCL_ON_OFF

Dalam deskripsi atribut kami zclDIYRuZRT_Attrs, kami menambahkan atribut cluster baru:

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

Kami juga menambahkan cluster ke daftar cluster titik akhir masuk yang didukung zclDIYRuZRT_InClusterList .

Untuk menerapkan perintah kontrol, tambahkan handler ke tabel 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

Dan kami menerapkannya:
//    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);
  }
}

Hebat, sekarang relay dapat diaktifkan oleh perintah.





Tetapi ini tidak cukup. Sekarang kita juga harus memberi tahu koordinator tentang keadaan LED saat ini, jika kita mengganti dengan tombol.

Sekali lagi, tambahkan arahan:

#define ZCL_REPORTING_DEVICE

Sekarang buat fungsi zclDIYRuZRT_ReportOnOff yang mengirim pesan status. Kami akan memanggilnya ketika LED diaktifkan dan ketika perangkat dimulai.

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

Sekarang dalam log kita melihat pesan tentang perubahan status LED.

9. Hubungkan sensor suhu ds18b20


Sensor terhubung ke pin gratis (dalam kasus saya, set P2_1 ).

Tambahkan kode polling sensor ke aplikasi. Kami akan mewawancarai secara teratur - satu menit sekali.
Segera setelah pemungutan suara, koordinator jaringan akan memberi tahu Anda tentang nilai saat ini.

Baca spesifikasi ZCL untuk mengirim data dari sensor suhu. Kita membutuhkan
Pengukuran Suhu cluster .



Kita melihat bahwa kita perlu mengimplementasikan 3 atribut, yang salah satunya mewakili nilai suhu dikalikan dengan 100.

Di sini kita menambahkan atribut dengan analogi dengan cluster GenOnOff . Koordinator akan diberi tahu tentang acara DIYRuZRT_REPORTING_EVT , yang kami rencanakan di awal satu menit sekali. Di event handler, kami akan meneleponzclDIYRuZRT_ReportTemp , yang akan membaca suhu sensor dan mengirim pesan.

//   
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. Tuang firmware ke dalam perangkat


Untuk mengubah devboard ke Sonoff BASICZBR3, Anda perlu menyesuaikan pencocokan pin dan tombol LED.



Ulangi LED 1 pada pin P0_7 untuk mengontrol relai. Inklusi dilakukan oleh ACTIVE_HIGH tingkat tinggi . Kami menggantung kembali tombol S1 pada pin P1_3 , dan informasi LED 2 pada P1_0 . Kami membiarkan sensor suhu pada pin P2_1 . Kami membuat semua perubahan ini dalam file hal_board_cfg_DIYRuZRT.h . Untuk memilih konfigurasi, kami akan membuat arahan terpisah HAL_SONOFF . Jika sudah diatur, maka pengaturan untuk Sonoff BASICZBR3 akan digunakan, jika tidak untuk 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

Parameter penting lainnya harus diperbaiki - kehadiran kuarsa "menonton", karena Di papan Sonoff BASICZBR3 tidak disolder:

//#define HAL_CLOCK_CRYSTAL
  #define OSC32K_CRYSTAL_INSTALLED FALSE

Tanpa opsi ini, firmware tidak memulai (atau lebih tepatnya, tidak selalu).

Setelah itu, kami mengumpulkan firmware dan terhubung ke firmware.
PERHATIAN!!! Putuskan relai Sonoff BASICZBR3 dari daya AC sebelum koneksi dan firmware apa pun!

Kami menghubungkan kabel Sonoff BASICZBR3 dengan CCDebugger, menghapus chip dan mem-flash firmware kami.



11. Kami memulai perangkat di zigbee2mqtt dan ioBroker.Zigbee


Meskipun perangkat kami telah muncul dalam daftar perangkat yang terhubung dan kami dapat mengelolanya dengan mengirim perintah, tetapi kami perlu melakukan ini dengan lebih benar - dengan gambar dan status.

Untuk mendapatkan perangkat baru di ioBroker.Zigbee , Anda perlu melakukan 2 langkah:

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

Semua perubahan dapat dilakukan terlebih dahulu dalam file lokal, dan kemudian dilakukan PR dalam repositori yang sesuai.

Kami menemukan lokasi paket zigbee-herdsman-converter di ioBroker yang terinstal (atau zigbee2mqtt). Di dalam paket kami menemukan file devices.js https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/devices.js File

ini berisi deskripsi semua perangkat yang ioBroker.zigbee dan zigbee2mqtt dapat bekerja dengan . Kami menemukan di dalamnya satu blok deskripsi perangkat DIYRuZ (setelah 2300 baris). Tambahkan deskripsi perangkat baru ke blok ini:

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

Dalam atribut fromZigbee, kami menentukan konverter yang akan memproses pesan yang berasal dari perangkat. Dua pos kami distandarisasi. Konverter fz.on_off memproses pesan on / off, dan fz.temperature memproses data suhu. Kode konverter ini (terletak di file converters / fromZigbee.js) menunjukkan bagaimana pesan masuk diproses dan bahwa suhu dibagi 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')};
        },
    },

Dalam atribut toZigbee , kami menentukan konverter yang akan memproses perintah kami ke perangkat. Dalam kasus kami, ini adalah konverter tz.on_off untuk mengalihkan relay.

Semuanya ditambahkan ke "konverter". Siapa yang menggunakan zigbee2mqtt - Anda sudah bisa menggunakannya.

Dan pengguna ioBroker masih menambahkan deskripsi perangkat ke file ioBroker.zigbee \ lib \ devices.js

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

Ini cukup untuk menunjukkan model yang persis sama, file dengan gambar dan daftar status. Dalam kasus kami, status juga standar: status untuk kondisi relai, suhu untuk menampilkan nilai suhu.



12. Apa selanjutnya?


Sayangnya, saya tidak dapat menemukan semua aspek dan fitur yang disediakan Z-Stack 3.0. Kemungkinan besar saya bahkan tidak mengimplementasikan dengan benar beberapa jenis fungsionalitas atau mekanisme bawaan yang dapat digunakan untuk mengimplementasikannya.
Oleh karena itu, solusi di atas dapat ditingkatkan dan dikembangkan. Berikut ini beberapa arahan:

  • Saya tidak dapat dengan cepat menemukan solusi untuk kemungkinan menghubungkan perangkat anak melalui relay. Perangkat router lain dapat menjalankan perintah permit_join dan menghubungkan perangkat baru melalui diri mereka sendiri, tanpa harus membawa perangkat baru ke koordinator. Perangkat ini diwakili oleh router, ditampilkan dengan benar di peta jaringan, tetapi menolak untuk mengeksekusi perintah β€œpermit_join”. Lebih tepatnya, perintah dijalankan, tetapi perangkat tidak terhubung melalui itu.
  • Juga, tidak menerapkan pelaporan yang benar. Ini adalah cara untuk mengkonfigurasi pemberitahuan status ketika Anda dapat menggunakan perintah configReport untuk menentukan daftar atribut yang akan dikirim dan frekuensi pemberitahuan.
  • Bekerja dengan kelompok.
  • Menangani interupsi dan mengimplementasikan tombol polling melalui interupsi.

Nah, untuk perangkat berikut ini, Anda perlu berurusan dengan mode daya dan pembuatan "tidur" perangkat pada baterai.
Seperti biasa, selain komentar, saya mengundang Anda untuk mendiskusikan hal ini dan perangkat lain dalam obrolan Telegram di Zigbee .

Saya ingin mengucapkan terima kasih atas dukungan dan bantuannya dalam pengembangan kolega saya dalam obrolan Telegram dan komunitas Zigbee:


Referensi


Dokumentasi utama termasuk dalam Z-Stack 3.0.2 SDK dan diinstal dengannya. Tetapi saya akan memberikan bagian dari tautan di sini:


All Articles