Skip to main content

通过 Zigbee 将 Seeed Studio IoT 按钮连接到 Home Assistant

在本教程中,我们将向您展示如何使用 Zigbee 将 Seeed Studio IoT 按钮连接到 Home Assistant。Seeed Studio IoT 按钮配备了内置的 ESP32-C6 芯片,具有 Zigbee 功能,使其成为智能家居的多功能设备。您将学习如何刷写 Zigbee 固件、将其与 Home Assistant 配对,甚至通过 Arduino 开发自定义按钮的行为。

所需材料

Seeed Studio IoT 按钮Zigbee 协调器(例如 Home Assistant Connect ZBT-1)

Seeed Studio IoT 按钮是一个多功能智能按钮,配备内置的 ESP32-C6 芯片。它是一个完整的独立设备,可以通过 Zigbee 与 Home Assistant 集成,以控制各种设备并触发自动化。凭借其 ESP32-C6 芯片,它提供低功耗和可靠的连接性。

功能概述(基于最新固件)

  • 多动作按钮检测

    • 单击、双击和三击。
    • 短长按(按住 1-5 秒)。
    • 长按(按住 > 5 秒)触发 Zigbee 恢复出厂设置。
    • 立即报告按下和释放事件,用于实时自动化。
  • 四个 Zigbee 端点

    • 端点 10: 反映物理按钮实时状态的主二进制传感器(按下时为开,释放时为关)。
    • 端点 11: 通过单击切换的虚拟开关。
    • 端点 12: 通过双击切换的虚拟开关。
    • 端点 13: 通过短长按切换的虚拟开关。
  • 电池监控(仅限 IoT 按钮 V2)

    • 通过 ADC 读取电池电压,并应用指数移动平均(EMA)滤波器以获得平滑、稳定的读数。
    • 向 Zigbee 报告电压(以 0.01V 为单位)和电池百分比。
    • 低电量状态(< 20%)驱动红色 LED 指示器。
  • LED 反馈

    • RGB WS2812 LED 为点击动作提供视觉反馈(呼吸、闪烁、彩虹效果)。
    • 蓝色状态 LED 指示 Zigbee 连接状态。
    • 红色状态 LED(仅限 V2)指示低电量状态。
  • 电源管理

    • 在 2 分钟不活动后自动进入睡眠模式以节省电力。
    • V1 使用轻度睡眠,V2 使用带 RTC 状态保持的深度睡眠,确保按钮点击状态不会丢失。
    • 按下按钮时立即从睡眠中唤醒。

使用 Arduino 开发自定义 Zigbee 固件

如果您想自定义 IoT 按钮的行为,可以使用 Arduino 开发自己的 Zigbee 固件。

步骤 1:为 ESP32-C6 设置 Arduino IDE

  1. 安装最新版本的 Arduino IDE。
  2. 添加 ESP32 开发板支持:
    • 转到 文件 > 首选项
    • 在"附加开发板管理器网址"字段中添加 https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
    • 转到 工具 > 开发板 > 开发板管理器
    • 搜索"esp32"并安装最新版本(确保是 3.2.1 版本或更高版本)。

步骤 2:安装所需库

IoT 按钮固件使用 FastLED 库来实现 RGB 效果,以及包含在 ESP32 Arduino 包中的 Espressif Zigbee SDK。FastLED 库可以通过 Arduino 库管理器安装。

  1. 转到 项目 > 加载库 > 管理库...

  2. 搜索"FastLED"并安装 Daniel Garcia 的库。

步骤 3:配置 Arduino IDE 进行 Zigbee 开发

  1. 选择正确的开发板:

    • 工具 > 开发板 > ESP32 Arduino > XIAO ESP32C6
  2. 配置 Zigbee 设置:

    • 工具 > Zigbee 模式 > Zigbee 终端设备
    • 工具 > 分区方案 > Zigbee 4MB with spiffs

步骤 4:创建您的自定义固件

新固件是一个自包含的 Arduino 项目。它支持 IoT 按钮 V1 和 V2 硬件、高级按钮事件检测(单击、双击、三击、短按/长按)、电池监控(V2)、丰富的 LED 反馈,以及使用 FreeRTOS 任务的强大 Zigbee 集成。

硬件版本选择

代码默认为 IoT 按钮 V2 编译。要为 V1 编译,您必须取消注释代码顶部的相应行:

device version

请验证正确的设备版本。目前市场上所有可用的 IoT 按钮都是 V1。

点击此处预览完整代码
#ifndef ZIGBEE_MODE_ED
#error "Zigbee end device mode is not selected in Tools->Zigbee mode"
#endif

#include "Zigbee.h"
#include <FastLED.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <esp_sleep.h>
#include "driver/rtc_io.h"

// Logging macro switch
#define ENABLE_LOGGING // Comment out to disable logging

#ifdef ENABLE_LOGGING
#define LOG_PRINTLN(x) Serial.println(x)
#define LOG_PRINTF(x, ...) Serial.printf(x, __VA_ARGS__)
#else
#define LOG_PRINTLN(x)
#define LOG_PRINTF(x, ...)
#endif

#define IOT_BUTTON_V1 //Uncomment this line to select to compile the iot button v1 version
// #define IOT_BUTTON_V2 //Uncomment this line to select to compile the iot button v2 version

#if !defined(IOT_BUTTON_V1) && !defined(IOT_BUTTON_V2)
#define IOT_BUTTON_V2
#endif

#define BUTTON_PIN_BITMASK(GPIO) (1ULL << GPIO)

/* Hardware Configuration */
#if defined(IOT_BUTTON_V1)
const uint8_t BUTTON_PIN = 9;
const uint8_t BLUE_LED_PIN = 2;
const uint8_t RGB_ENABLE_PIN = 18;
const uint8_t RGB_PIN = 19;
const uint8_t NUM_RGBS = 1;
#elif defined(IOT_BUTTON_V2)
const uint8_t BUTTON_PIN = 2;
const uint8_t BLUE_LED_PIN = 3;
const uint8_t RED_LED_PIN = 14;
const uint8_t RGB_ENABLE_PIN = 18;
const uint8_t RGB_PIN = 19;
const uint8_t NUM_RGBS = 1;
const uint8_t BATTERY_ADC_PIN = 1;
const uint8_t BATTERY_ENABLE_PIN = 0;
const int SAMPLE_COUNT = 10;
const float MIN_VOLTAGE = 2.75;
const float MAX_VOLTAGE = 4.2;
const float ALPHA = 0.1; // Smoothing factor for EMA
#endif

/* Button Configuration */
const uint32_t MULTI_CLICK_TIME = 300; // Maximum time between clicks for multi-click (ms)
const uint32_t SHORT_LONG_PRESS_TIME = 1000; // Minimum time for short long press (1 second)
const uint32_t LONG_PRESS_TIME = 5000; // Minimum time for long press (5 seconds)
const uint32_t DEBOUNCE_TIME = 20; // Debounce time (ms)
const uint32_t INACTIVITY_TIMEOUT = 2 * 60 * 1000; // 2 minutes inactivity timeout (ms)

/* LED Configuration */
CRGB rgbs[NUM_RGBS];

/* Button Events */
enum class ButtonEvent
{
PRESS, // Pressed
RELEASE, // Released
SINGLE_CLICK, // Single click
DOUBLE_CLICK, // Double click
TRIPLE_CLICK, // Triple click
SHORT_LONG_PRESS, // Short long press (1-5 seconds)
LONG_PRESS // Long press (>5 seconds)
};

/* Zigbee Configuration */
#define BUTTON_ENDPOINT 10
#define SWITCH1_ENDPOINT 11
#define SWITCH2_ENDPOINT 12
#define SWITCH3_ENDPOINT 13
ZigbeeBinary zbIoTButton = ZigbeeBinary(BUTTON_ENDPOINT);
ZigbeeBinary zbSwitch1 = ZigbeeBinary(SWITCH1_ENDPOINT);
ZigbeeBinary zbSwitch2 = ZigbeeBinary(SWITCH2_ENDPOINT);
ZigbeeBinary zbSwitch3 = ZigbeeBinary(SWITCH3_ENDPOINT);
bool buttonStatus = false;
RTC_DATA_ATTR bool switch1Status = false;
RTC_DATA_ATTR bool switch2Status = false;
RTC_DATA_ATTR bool switch3Status = false;

/* Global Variables */
QueueHandle_t eventQueue;

uint32_t pressStartTime = 0;
uint32_t lastReleaseTime = 0;
uint8_t clickCount = 0;
bool longPressTriggered = false;
bool clickSequenceActive = false; // Tracks if a click sequence is in progress
TaskHandle_t clickTimeoutTaskHandle = NULL;
uint32_t lastActivityTime = 0; // Tracks last button activity for sleep
volatile bool isAwake = true; // Tracks device awake/sleep state
bool lastConnected = false; // Track previous Zigbee connection state
bool zigbeeInitialized = false; // Track Zigbee initialization status

#if defined(IOT_BUTTON_V2)
// RTC variables for button state persistence
RTC_DATA_ATTR uint32_t pressStartTimeRTC = 0;
RTC_DATA_ATTR uint32_t lastReleaseTimeRTC = 0;
RTC_DATA_ATTR uint8_t clickCountRTC = 0;
RTC_DATA_ATTR bool longPressTriggeredRTC = false;
RTC_DATA_ATTR bool clickSequenceActiveRTC = false;

float emaVoltage = 0.0;
float batteryPercentage = 100.0;
#endif

#if defined(IOT_BUTTON_V2)
/********************* Battery Functions **************************/
void measureBattery()
{
digitalWrite(BATTERY_ENABLE_PIN, HIGH);
vTaskDelay(10 / portTICK_PERIOD_MS); // Wait for stabilization

// Take multiple samples and compute average
float adcSum = 0;
for (int i = 0; i < SAMPLE_COUNT; i++)
{
adcSum += analogRead(BATTERY_ADC_PIN);
vTaskDelay(5 / portTICK_PERIOD_MS); // Small delay between samples
}
digitalWrite(BATTERY_ENABLE_PIN, LOW);

float adcAverage = adcSum / SAMPLE_COUNT;
float voltage = (adcAverage / 4095.0) * 3.3 * 3.0; // Apply divider ratio

if (voltage < MIN_VOLTAGE)
{
emaVoltage = 0.0;
batteryPercentage = 0.0;
LOG_PRINTF("Battery voltage: %.2fV (too low or not connected), EMA voltage: %.2fV, Percentage: %.2f%%\n",
voltage, emaVoltage, batteryPercentage);
}
else
{
// Update EMA
if (emaVoltage == 0.0)
{
emaVoltage = voltage;
}
else
{
emaVoltage = ALPHA * voltage + (1 - ALPHA) * emaVoltage;
}

// Calculate battery percentage from emaVoltage
float localBatteryPercentage = (emaVoltage - MIN_VOLTAGE) / (MAX_VOLTAGE - MIN_VOLTAGE) * 100;
if (localBatteryPercentage < 0)
localBatteryPercentage = 0;
if (localBatteryPercentage > 100)
localBatteryPercentage = 100;

// Update global battery percentage
batteryPercentage = localBatteryPercentage;

LOG_PRINTF("Battery voltage: %.2fV, EMA voltage: %.2fV, Percentage: %.2f%%\n",
voltage, emaVoltage, localBatteryPercentage);
}
}
#endif

/********************* FreeRTOS Tasks **************************/
void breathingLedTask(void *pvParameters)
{
LOG_PRINTLN("Breathing LED");
uint8_t hue = random8(); // Random color hue
for (int i = 0; i < 1; i++) // one breathing cycle
{
// Brighten
for (int brightness = 0; brightness <= 255; brightness += 5)
{
rgbs[0] = CHSV(hue, 255, brightness);
FastLED.show();
vTaskDelay(20 / portTICK_PERIOD_MS);
}
// Dim
for (int brightness = 255; brightness >= 0; brightness -= 5)
{
rgbs[0] = CHSV(hue, 255, brightness);
FastLED.show();
vTaskDelay(20 / portTICK_PERIOD_MS);
}
}
rgbs[0] = CRGB::Black;
FastLED.show();
vTaskDelete(NULL);
}

void blinkLedTask(void *pvParameters)
{
LOG_PRINTLN("Blink LED");
uint8_t rand = random8();
for (int i = 0; i < 2; i++)
{
rgbs[0] = CHSV(rand, 255, 255); // Random color
FastLED.show();
vTaskDelay(200 / portTICK_PERIOD_MS);
rgbs[0] = CRGB::Black;
FastLED.show();
vTaskDelay(200 / portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}

void rainbowLedTask(void *pvParameters)
{
LOG_PRINTLN("Rainbow LED");
for (int hue = 0; hue < 128; hue += 10)
{
rgbs[0] = CHSV(hue, 255, 255);
FastLED.show();
vTaskDelay(100 / portTICK_PERIOD_MS);
}
rgbs[0] = CRGB::Black;
FastLED.show();
vTaskDelete(NULL);
}

void clickTimeoutTask(void *pvParameters)
{
uint32_t localClickCount = clickCount;
uint32_t localLastReleaseTime = lastReleaseTime;

while (millis() - localLastReleaseTime < MULTI_CLICK_TIME)
{
vTaskDelay(10 / portTICK_PERIOD_MS);
}

ButtonEvent event;
switch (localClickCount)
{
case 1:
event = ButtonEvent::SINGLE_CLICK;
break;
case 2:
event = ButtonEvent::DOUBLE_CLICK;
break;
case 3:
event = ButtonEvent::TRIPLE_CLICK;
break;
default:
vTaskDelete(NULL);
return;
}
xQueueSend(eventQueue, &event, 0);

clickCount = 0;
clickSequenceActive = false;
clickTimeoutTaskHandle = NULL;

vTaskDelete(NULL);
}

// --- Button Task Refactor ---
static bool debounceButton(bool currentState, uint32_t currentTime, uint32_t &lastDebounceTime)
{
if (currentTime - lastDebounceTime < DEBOUNCE_TIME)
{
vTaskDelay(1 / portTICK_PERIOD_MS);
return true;
}
return false;
}

static void handleButtonPress(uint32_t currentTime)
{
pressStartTime = currentTime;
ButtonEvent event = ButtonEvent::PRESS;
xQueueSend(eventQueue, &event, 0);
lastActivityTime = millis();

if (clickSequenceActive && (currentTime - lastReleaseTime <= MULTI_CLICK_TIME))
{
clickCount++;
if (clickTimeoutTaskHandle != NULL)
{
vTaskDelete(clickTimeoutTaskHandle);
clickTimeoutTaskHandle = NULL;
}
}
else
{
clickCount = 1;
clickSequenceActive = true;
}
longPressTriggered = false;
}

static void handleButtonRelease(uint32_t currentTime)
{
uint32_t pressDuration = currentTime - pressStartTime;
ButtonEvent event = ButtonEvent::RELEASE;
xQueueSend(eventQueue, &event, 0);
lastActivityTime = millis();

if (!longPressTriggered)
{
if (pressDuration >= LONG_PRESS_TIME)
{
event = ButtonEvent::LONG_PRESS;
longPressTriggered = true;
clickSequenceActive = false;
clickCount = 0;
xQueueSend(eventQueue, &event, 0);
if (clickTimeoutTaskHandle != NULL)
{
vTaskDelete(clickTimeoutTaskHandle);
clickTimeoutTaskHandle = NULL;
}
}
else if (pressDuration >= SHORT_LONG_PRESS_TIME)
{
event = ButtonEvent::SHORT_LONG_PRESS;
longPressTriggered = true;
clickSequenceActive = false;
clickCount = 0;
xQueueSend(eventQueue, &event, 0);
if (clickTimeoutTaskHandle != NULL)
{
vTaskDelete(clickTimeoutTaskHandle);
clickTimeoutTaskHandle = NULL;
}
}
else
{
lastReleaseTime = currentTime;
if (clickTimeoutTaskHandle != NULL)
{
vTaskDelete(clickTimeoutTaskHandle);
clickTimeoutTaskHandle = NULL;
}
xTaskCreate(clickTimeoutTask, "ClickTimeout", 2048, NULL, 1, &clickTimeoutTaskHandle);
}
}
}

static void checkLongPress(uint32_t currentTime)
{
if (currentTime - pressStartTime >= LONG_PRESS_TIME)
{
ButtonEvent event = ButtonEvent::LONG_PRESS;
longPressTriggered = true;
clickSequenceActive = false;
clickCount = 0;
xQueueSend(eventQueue, &event, 0);
lastActivityTime = millis();
if (clickTimeoutTaskHandle != NULL)
{
vTaskDelete(clickTimeoutTaskHandle);
clickTimeoutTaskHandle = NULL;
}
}
}

void buttonTask(void *pvParameters)
{
uint32_t lastDebounceTime = 0;
bool lastState = false;

// Check if woken up by button press
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1)
{
bool currentState = (digitalRead(BUTTON_PIN) == LOW);
if (currentState)
{
handleButtonPress(millis());
}
}

while (1)
{
bool currentState = (digitalRead(BUTTON_PIN) == LOW);
uint32_t currentTime = millis();

if (debounceButton(currentState, currentTime, lastDebounceTime))
continue;

if (currentState != lastState)
{
lastDebounceTime = currentTime;
lastState = currentState;
if (currentState)
{
handleButtonPress(currentTime);
}
else
{
handleButtonRelease(currentTime);
}
}
else if (currentState && !longPressTriggered)
{
checkLongPress(currentTime);
}

vTaskDelay(10 / portTICK_PERIOD_MS);
}
}

void mainTask(void *pvParameters)
{
ButtonEvent event;
while (1)
{
if (xQueueReceive(eventQueue, &event, portMAX_DELAY) == pdTRUE)
{
switch (event)
{
case ButtonEvent::PRESS:
if (buttonStatus == false)
{
buttonStatus = true;
LOG_PRINTLN("Button Pressed");
if (zigbeeInitialized && Zigbee.connected())
{
zbIoTButton.setBinaryInput(buttonStatus);
zbIoTButton.reportBinaryInput();
}
}
break;

case ButtonEvent::RELEASE:
if (buttonStatus == true)
{
buttonStatus = false;
LOG_PRINTLN("Button Released");
if (zigbeeInitialized && Zigbee.connected())
{
zbIoTButton.setBinaryInput(buttonStatus);
zbIoTButton.reportBinaryInput();
}
}
break;

case ButtonEvent::SINGLE_CLICK:
LOG_PRINTLN("Single Click");
switch1Status = !switch1Status;
if (zigbeeInitialized && Zigbee.connected())
{
zbSwitch1.setBinaryInput(switch1Status);
zbSwitch1.reportBinaryInput();
}
xTaskCreate(breathingLedTask, "BreathingLed", 2048, NULL, 1, NULL);
break;

case ButtonEvent::DOUBLE_CLICK:
LOG_PRINTLN("Double Click");
switch2Status = !switch2Status;
if (zigbeeInitialized && Zigbee.connected())
{
zbSwitch2.setBinaryInput(switch2Status);
zbSwitch2.reportBinaryInput();
}
xTaskCreate(blinkLedTask, "BlinkLed", 2048, NULL, 1, NULL);
break;

case ButtonEvent::TRIPLE_CLICK:
LOG_PRINTLN("Triple Click");
if (zigbeeInitialized && Zigbee.connected())
{
// Add any specific Zigbee action here if needed
}
break;

case ButtonEvent::SHORT_LONG_PRESS:
LOG_PRINTLN("Short Long Press");
switch3Status = !switch3Status;
if (zigbeeInitialized && Zigbee.connected())
{
zbSwitch3.setBinaryInput(switch3Status);
zbSwitch3.reportBinaryInput();
}
xTaskCreate(rainbowLedTask, "RainbowLed", 2048, NULL, 1, NULL);
break;

case ButtonEvent::LONG_PRESS:
LOG_PRINTLN("Long Press\nReset Zigbee");
vTaskDelay(1000 / portTICK_PERIOD_MS);
if (zigbeeInitialized)
{
Zigbee.factoryReset();
}
break;
}
}
}
}

#if defined(IOT_BUTTON_V1)
void ledTask(void *pvParameters)
{
pinMode(BLUE_LED_PIN, OUTPUT);
while (1)
{
if (isAwake)
{
if (!zigbeeInitialized || !Zigbee.connected()) // Blink when not connected or not initialized
{
digitalWrite(BLUE_LED_PIN, LOW); // On
vTaskDelay(500 / portTICK_PERIOD_MS);
digitalWrite(BLUE_LED_PIN, HIGH); // Off
vTaskDelay(500 / portTICK_PERIOD_MS);
}
else
{
digitalWrite(BLUE_LED_PIN, LOW); // On when connected
}
}
else
{
digitalWrite(BLUE_LED_PIN, HIGH); // Off during sleep
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
#elif defined(IOT_BUTTON_V2)
void ledTask(void *pvParameters)
{
pinMode(BLUE_LED_PIN, OUTPUT);
pinMode(RED_LED_PIN, OUTPUT);
bool ledState = false;

while (1)
{
if (isAwake)
{
bool isLowBattery = (batteryPercentage < 20.0);
bool isConnected = zigbeeInitialized && Zigbee.connected();
uint8_t activeLedPin = isLowBattery ? RED_LED_PIN : BLUE_LED_PIN;
uint8_t inactiveLedPin = isLowBattery ? BLUE_LED_PIN : RED_LED_PIN;

if (isConnected)
{
digitalWrite(activeLedPin, LOW);
digitalWrite(inactiveLedPin, HIGH);
}
else
{
ledState = !ledState;
digitalWrite(activeLedPin, ledState ? LOW : HIGH);
digitalWrite(inactiveLedPin, HIGH);
}
}
else
{
digitalWrite(BLUE_LED_PIN, HIGH);
digitalWrite(RED_LED_PIN, HIGH);
}
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
#endif

#if defined(IOT_BUTTON_V2)
void batteryTask(void *pvParameters)
{
pinMode(BATTERY_ENABLE_PIN, OUTPUT);

while (1)
{
measureBattery();
if (zigbeeInitialized && Zigbee.connected())
{
zbIoTButton.setBatteryVoltage((uint8_t)(emaVoltage * 100)); // Unit: 0.01V
zbIoTButton.setBatteryPercentage((uint8_t)batteryPercentage);
zbIoTButton.reportBatteryPercentage();
}
vTaskDelay(30000 / portTICK_PERIOD_MS); // Check every 30 seconds
}
}
#endif

void sleepTask(void *pvParameters)
{
while (1)
{
if (isAwake && (millis() - lastActivityTime > INACTIVITY_TIMEOUT))
{
LOG_PRINTLN("Entering sleep due to inactivity");
#if defined(IOT_BUTTON_V1)
isAwake = false;
digitalWrite(BLUE_LED_PIN, HIGH);
esp_sleep_enable_gpio_wakeup();
digitalWrite(BLUE_LED_PIN, HIGH); // Turn off LED
gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL);
digitalWrite(RGB_ENABLE_PIN, LOW);
esp_light_sleep_start();
digitalWrite(RGB_ENABLE_PIN, HIGH);
LOG_PRINTLN("Woke up from light sleep");
isAwake = true;
digitalWrite(BLUE_LED_PIN, LOW); // Turn on LED
#elif defined(IOT_BUTTON_V2)
// Save button state to RTC memory
pressStartTimeRTC = pressStartTime;
lastReleaseTimeRTC = lastReleaseTime;
clickCountRTC = clickCount;
longPressTriggeredRTC = longPressTriggered;
clickSequenceActiveRTC = clickSequenceActive;

digitalWrite(BLUE_LED_PIN, HIGH);
digitalWrite(RED_LED_PIN, HIGH);
digitalWrite(RGB_PIN, LOW);
digitalWrite(RGB_ENABLE_PIN, LOW);
static gpio_num_t WAKEUP_GPIO = (gpio_num_t)BUTTON_PIN;
esp_sleep_enable_ext1_wakeup_io(BUTTON_PIN_BITMASK(WAKEUP_GPIO), ESP_EXT1_WAKEUP_ANY_LOW);
esp_deep_sleep_start();
#endif
}
vTaskDelay(10000 / portTICK_PERIOD_MS); // Check every 10 seconds
}
}

/********************* Zigbee Functions **************************/
void onZigbeeConnected()
{
if (!zigbeeInitialized)
{
return;
}
#if defined(IOT_BUTTON_V2)
measureBattery(); // Ensure latest battery data
zbIoTButton.setBatteryVoltage((uint8_t)(emaVoltage * 100)); // Unit: 0.01V
zbIoTButton.setBatteryPercentage((uint8_t)batteryPercentage);
zbIoTButton.reportBatteryPercentage();
#endif
zbSwitch1.setBinaryInput(switch1Status);
zbSwitch1.reportBinaryInput();
zbSwitch2.setBinaryInput(switch2Status);
zbSwitch2.reportBinaryInput();
zbSwitch3.setBinaryInput(switch3Status);
zbSwitch3.reportBinaryInput();
}

void zigbeeSetupTask(void *pvParameters)
{
zbIoTButton.addBinaryInput();
zbIoTButton.setBinaryInputApplication(BINARY_INPUT_APPLICATION_TYPE_SECURITY_MOTION_DETECTION);
zbIoTButton.setBinaryInputDescription("Button");
zbSwitch1.addBinaryInput();
zbSwitch1.setBinaryInputApplication(BINARY_INPUT_APPLICATION_TYPE_SECURITY_MOTION_DETECTION);
zbSwitch1.setBinaryInputDescription("Switch1");
zbSwitch2.addBinaryInput();
zbSwitch2.setBinaryInputApplication(BINARY_INPUT_APPLICATION_TYPE_SECURITY_MOTION_DETECTION);
zbSwitch2.setBinaryInputDescription("Switch2");
zbSwitch3.addBinaryInput();
zbSwitch3.setBinaryInputApplication(BINARY_INPUT_APPLICATION_TYPE_SECURITY_MOTION_DETECTION);
zbSwitch3.setBinaryInputDescription("Switch3");

// Set Zigbee device information
#if defined(IOT_BUTTON_V1)
zbIoTButton.setManufacturerAndModel("Seeed Studio", "IoT_Button");
#elif defined(IOT_BUTTON_V2)
zbIoTButton.setManufacturerAndModel("Seeed Studio", "IoT Button V2");
zbIoTButton.setPowerSource(ZB_POWER_SOURCE_BATTERY, 100);
#endif

// Add endpoint to Zigbee Core
Zigbee.addEndpoint(&zbIoTButton);
Zigbee.addEndpoint(&zbSwitch1);
Zigbee.addEndpoint(&zbSwitch2);
Zigbee.addEndpoint(&zbSwitch3);
esp_zb_cfg_t zigbeeConfig = ZIGBEE_DEFAULT_ED_CONFIG();
zigbeeConfig.nwk_cfg.zed_cfg.keep_alive = 10000;

Zigbee.setTimeout(10000); // Set timeout for Zigbee Begin to 10s (default is 30s)
LOG_PRINTLN("Starting Zigbee...");
if (!Zigbee.begin(&zigbeeConfig, false))
{
LOG_PRINTLN("Zigbee failed to start!");
LOG_PRINTLN("Please try holding down the 5S key for a long time to reset zigbee");
zigbeeInitialized = false;
}
else
{
LOG_PRINTLN("Zigbee started successfully!");
zigbeeInitialized = true;
}

vTaskDelete(NULL); // Terminate the task after completion
}

/********************* Arduino Setup **************************/
void setup()
{
Serial.begin(115200);

LOG_PRINTLN("Zigbee IoT Button Starting...");
#if defined(IOT_BUTTON_V2)
// Restore button state from RTC memory
pressStartTime = pressStartTimeRTC;
lastReleaseTime = lastReleaseTimeRTC;
clickCount = clickCountRTC;
longPressTriggered = longPressTriggeredRTC;
clickSequenceActive = clickSequenceActiveRTC;
#endif

// Initialize button pin
pinMode(BUTTON_PIN, INPUT_PULLUP);

pinMode(RGB_ENABLE_PIN, OUTPUT);
digitalWrite(RGB_ENABLE_PIN, HIGH);
#if defined(IOT_BUTTON_V2)
pinMode(BATTERY_ENABLE_PIN, OUTPUT);
#endif

// Initialize LED
FastLED.addLeds<WS2812, RGB_PIN, GRB>(rgbs, NUM_RGBS);
FastLED.setBrightness(50);

// Create event queue
eventQueue = xQueueCreate(10, sizeof(ButtonEvent));
if (eventQueue == NULL)
{
LOG_PRINTLN("Failed to create event queue!");
ESP.restart();
}

#if defined(IOT_BUTTON_V2)
// Check if woken up by button press and handle immediately
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1)
{
uint32_t currentTime = millis();
if (digitalRead(BUTTON_PIN) == LOW)
{
handleButtonPress(currentTime);
}
}
#endif

// Create FreeRTOS tasks
xTaskCreate(buttonTask, "ButtonTask", 2048, NULL, 4, NULL);
xTaskCreate(ledTask, "LedTask", 1024, NULL, 0, NULL);
xTaskCreate(mainTask, "MainTask", 2048, NULL, 3, NULL);
xTaskCreate(sleepTask, "SleepTask", 2048, NULL, 2, NULL);
xTaskCreate(zigbeeSetupTask, "ZigbeeSetup", 2048, NULL, 1, NULL);
#if defined(IOT_BUTTON_V2)
xTaskCreate(batteryTask, "BatteryTask", 2048, NULL, 1, NULL);
#endif
}

/********************* Arduino Loop **************************/
void loop()
{
if (zigbeeInitialized)
{
bool currentConnected = Zigbee.connected();
if (currentConnected && !lastConnected)
{
LOG_PRINTLN("Zigbee connected!");
onZigbeeConnected();
}
else if (!currentConnected && lastConnected)
{
LOG_PRINTLN("Zigbee disconnected!");
}
lastConnected = currentConnected;
if (!currentConnected)
{
LOG_PRINTLN("Zigbee not connected, retrying...");
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
else
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
else
{
vTaskDelay(1000 / portTICK_PERIOD_MS); // Keep loop running even if Zigbee fails
}
}

文档可能不会随着代码更新而及时更新,您也可以点击下面的按钮获取最新的步骤。

步骤 5:上传并测试您的固件

  1. 通过 USB 将您的 IoT Button 连接到计算机。
  2. 在 Arduino IDE 中选择正确的端口。
  3. 点击上传按钮。
  4. 打开串口监视器(波特率 115200)查看调试信息。
  5. 刷写完成后,按钮就可以进行配对了。

步骤 6:在 Home Assistant 中设置 Zigbee

在配对您的 IoT Button 之前,您需要在 Home Assistant 中设置 Zigbee 协调器:

  1. 安装 Zigbee 协调器:将 Zigbee 协调器(如 Home Assistant Connect ZBT-1)连接到您的 Home Assistant 服务器。
  2. 设置 Zigbee Home Automation (ZHA)
    • 转到设置 > 设备和服务
    • 点击"添加集成"并搜索"Zigbee Home Automation"。
    • 按照提示使用您的协调器设置 ZHA。

步骤 7:将 IoT Button 与 Home Assistant 配对

  1. 在 Home Assistant 中,转到设置 > 设备和服务 > Zigbee Home Automation
  2. 点击您的 Zigbee 协调器设备。
  3. 点击"添加设备"将协调器置于配对模式。
  1. 按一次 IoT Button 上的按钮来唤醒它并启动配对。如果无法配对,请尝试按住按钮超过 5 秒钟以触发恢复出厂设置,这也会将其置于配对模式。
  2. Home Assistant 应该会发现 IoT Button 为"Seeed Studio IoT_Button"。
  3. 按照提示完成配对过程。您将看到一个具有多个实体(一个二进制传感器和三个开关)的设备。

步骤 8:在 Home Assistant 中创建自动化

配对完成后,您可以基于按钮的操作创建自动化。固件将不同的点击类型作为单独的开关公开,使自动化变得简单。

  1. 转到设置 > 自动化和场景 > 创建自动化
  2. 选择"设备"作为触发器类型。
  3. 在设备列表中找到您的 IoT Button。
  4. 从列表中选择所需的触发器。ZHA 将不同的点击作为设备操作公开,例如:
    • "单击"(来自端点 11 上的虚拟开关)
    • "双击"(来自端点 12 上的虚拟开关)
    • "长按"(来自端点 13 上的虚拟开关)
  5. 配置按下按钮时要执行的操作。
  6. 保存自动化。

在 Home Assistant YAML 中使用双击切换灯光的示例自动化:

alias: IoT Button Double Click - Toggle Living Room Light
description: ""
trigger:
- platform: device
domain: zha
device_id: YOUR_DEVICE_ID_HERE # Replace with your button's device ID
type: "remote_button_double_press" # The exact type may vary, select it from the UI
action:
- service: light.toggle
target:
entity_id: light.living_room
mode: single

结论

具有 Zigbee 功能的 Seeed Studio IoT Button 提供了一种多功能且节能的方式来控制您的智能家居。无论您使用预构建的固件还是开发自己的自定义解决方案,该按钮都为在 Home Assistant 中触发复杂自动化提供了简单的界面。

通过利用 ESP32-C6 的内置 Zigbee 功能,IoT Button 可以在电池供电下长时间运行,同时与您的智能家居生态系统保持可靠的连接。

故障排除

Q1:为什么我的设备在更换电池后不断掉线且无法连接到互联网?我可以确认电池是有电的。

电池取出后,由于 18650 电池的芯片保护策略,需要通过充电的 USB 电源线稍微激活一下才能正常工作。

资源

技术支持和产品讨论

感谢您选择我们的产品!我们在这里为您提供不同的支持,以确保您使用我们产品的体验尽可能顺畅。我们提供多种沟通渠道,以满足不同的偏好和需求。

Loading Comments...