Предполагается, что читатель уже имеет начальные знания языка C, что-то знает о Zigbee, чипе cc2530, методах его прошивания и использования, а также знаком с такими проектами, как zigbee2mqtt. Если нет — подготовьтесь или сходите почитать на https://myzigbee.ru и https://www.zigbee2mqtt.io/
Статья написана сперва подробно, но постепенно ускоряется и уже не останавливается на деталях, а описывает готовый код прошивки. Если кому-то не интересны рассуждения, то просто открывайте исходники прошивки и читайте их.
Исходный код готовой прошивки
Код и подход к разработке не претендует на идеальность. “Я не волшебник, я только учусь.”
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
};
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 );
}
/*********************************************************************
* 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;
};
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 );
}
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 );
}
#define HAL_TURN_OFF_LED2() st( LED2_SBIT = LED2_POLARITY (0); )
#define HAL_TURN_ON_LED2() st( LED2_SBIT = LED2_POLARITY (1); )
/* 2 - Red */
#define LED2_BV BV(1)
#define LED2_SBIT P1_1
#define LED2_DDR P1DIR
#define LED2_POLARITY ACTIVE_HIGH
#define LED2_POLARITY ACTIVE_HIGH
#define LED2_POLARITY ACTIVE_LOW
#include "hal_board_cfg_DIYRuZRT.h"
$PROJ_DIR$\..\Source\preinclude.h
#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
/* 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
/* ----------- 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; }
//#define LCD_SUPPORTED DEBUG
#define DISABLE_GREENPOWER_BASIC_PROXY
#define DEFAULT_CHANLIST 0x07FFF800 // ALL Channels
bdb_StartCommissioning(BDB_COMMISSIONING_MODE_NWK_STEERING |
BDB_COMMISSIONING_MODE_FINDING_BINDING);
// Инициализация работы кнопок (входов)
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 */
}
#define HAL_KEY FALSE
osal_start_reload_timer( zclDIYRuZRT_TaskID, HAL_KEY_EVENT, 100);
// Считывание кнопок
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);
}
// Обработчик нажатий клавиш
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);
}
}
// событие 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 );
}
// инициализируем 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 );
}
}
#define ZCL_ON_OFF
// *** Атрибуты 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
}
},
/*********************************************************************
* Таблица обработчиков основных 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
// Обработчик команд кластера 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);
}
}
#define ZCL_REPORTING_DEVICE
// Информирование о состоянии реле
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);
}
// Информирование о температуре
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);
}
#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
//#define HAL_CLOCK_CRYSTAL
#define OSC32K_CRYSTAL_INSTALLED FALSE
ВНИМАНИЕ!!! Отключите реле Sonoff BASICZBR3 от сети переменного тока перед любыми действиями с подключением и прошивкой!
{
zigbeeModel: ['DIYRuZ_RT'],
model: 'DIYRuZ_RT',
vendor: 'DIYRuZ',
description: '',
supports: 'on/off, temperature',
fromZigbee: [fz.on_off, fz.temperature],
toZigbee: [tz.on_off],
},
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')};
},
},
{
vendor: 'DIYRuZ',
models: ['DIYRuZ_RT'],
icon: 'img/DIYRuZ.png',
states: [
states.state,
states.temperature,
],
},
Как всегда, помимо комментариев, приглашаю обсуждать это и другие устройства в Телеграм-чатике по Zigbee.
Хочу выразить благодарность за поддержку и помощь в разработке своим коллегам по Телеграм-чату и Zigbee сообществу:
К сожалению, не доступен сервер mySQL