26、语音控制智能风扇实践

语音控制智能风扇实践

1. 前期准备与设备发现

在完成由 Lambda 函数提供的后端服务后,我们可以在 Alexa 账户中进行设备发现。具体操作如下:
- 访问链接:https://alexa.amazon.com/。
- 导航至“Smart Home | Devices”,点击“Discover”。大约 20 秒后,智能风扇应该会作为新设备列出。

完成 AWS 端的开发后,接下来我们将实现 ESP32 应用。

2. 固件开发概述

ESP32 应用的主要功能是检测风扇上哪个按钮被按下,并切换相应的继电器来设置风扇速度,同时更新 AWS 云上设备的状态。当语音命令改变设备的期望状态时,运行在 ESP32 上的应用会捕获该信息并反映在物理风扇上。

3. 硬件电路连接

对于第二个原型,我们将风扇按钮连接到 ESP32 上。具体步骤如下:
- 打开风扇的底盖,切断按钮电缆。
- 将按钮连接到 ESP32 的 GPIO 引脚。
- 继电器将控制风扇速度,取代风扇按钮。将电缆的另一端连接到继电器负载。

注意事项
- 处理高电压时,请采取所有必要的预防措施。可参考网站 https://ncd.io/relay-logic/ 了解如何使用带负载的继电器。
- 可以在继电器周围使用 RC 缓冲器,以保护它们免受风扇电机的感应反冲影响。例如 Okaya 的 XE1201 缓冲器(https://okaya.com/product/?id=9716226e - c62c - e111 - a207 - 0026551ab73e)。

TXS0108E 逻辑转换器模块有两个端口:
|端口|支持电压范围|
|----|----|
|Port A(底行)|1.4 V - 3.6 V|
|Port B(顶行)|1.65 V - 5.5 V|

规则是 VA 应小于 VB,当 OE 设置为高电平时,可启用 Port B 输出。在线数据手册链接:https://www.ti.com/document - viewer/TXS0108E/datasheet。

我们将使用四个按钮来控制风扇速度:
- 第一个按钮:通过关闭所有继电器停止风扇。
- 其他三个按钮:用于不同的速度设置。由于启用了 GPIO 引脚的内部上拉电阻,当没有按钮按下时,按钮的 GPIO 引脚将读取高电平。

4. 创建 PlatformIO 项目

创建一个以 ESP - IDF 为框架的新 PlatformIO 项目,具体步骤如下:
1. 编辑 platformio.ini 文件

[env:az - delivery - devkit - v4]
platform = espressif32
board = az - delivery - devkit - v4
framework = espidf
monitor_speed = 115200
lib_extra_dirs = 
    ../../common/esp - idf - lib/components
build_flags =
    -DWIFI_SSID=${sysenv.WIFI_SSID}
    -DWIFI_PASS=${sysenv.WIFI_PASS}
    -DAWS_ENDPOINT=${sysenv.AWS_ENDPOINT}
board_build.embed_txtfiles = 
    ./tmp/private.pem.key
    ./tmp/certificate.pem.crt
    ./tmp/AmazonRootCA1.pem

AWS 设备 SDK 位于 ../../common/esp - idf - lib/components 路径下,我们将其包含在项目中。定义 AWS_ENDPOINT 为环境变量,应用将连接到该端点与 AWS IoT Core 中创建的设备进行通信。同时指定设备加密文件的路径,这些文件将被复制到 tmp 文件夹,该文件夹会从 GitHub 仓库中排除,以避免加密文件公开暴露。

  1. 编辑 src/CMakeList.txt 文件
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*)
set(COMPONENT_ADD_INCLUDEDIRS ".")
idf_component_register(SRCS ${app_sources})
target_add_binary_data(${COMPONENT_TARGET} "../tmp/AmazonRootCA1.pem" TEXT)
target_add_binary_data(${COMPONENT_TARGET} "../tmp/certificate.pem.crt" TEXT)
target_add_binary_data(${COMPONENT_TARGET} "../tmp/private.pem.key" TEXT)

使用 target_add_binary_data 指令指定加密文件的路径,让 ESP - IDF 知晓这些文件。

  1. 复制加密文件到 tmp 文件夹
$ mkdir tmp && cp <thing_cert_files> tmp/ && cd tmp
$ wget https://www.amazontrust.com/repository/AmazonRootCA1.pem
$ ls
AmazonRootCA1.pem  certificate.pem.crt  private.pem.key  public.pem.key
  1. 复制库文件 :从 GitHub 链接 https://github.com/PacktPublishing/Internet - of - Things - with - ESP32/tree/main/ch12/smart_fan/lib 复制库文件。完成后,项目的目录结构如下:
.:
CMakeLists.txt  include  lib  platformio.ini  sdkconfig  src  test  tmp
./lib:
aws  hw  README  wifi
./lib/aws:
app_aws.c  app_aws.h
./lib/hw:
app_hw.c  app_hw.h
./lib/wifi:
app_wifi.c  app_wifi.h
./src:
CMakeLists.txt  main.c
./tmp:
AmazonRootCA1.pem  certificate.pem.crt  private.pem.key  public.pem.key
  1. 激活 PlatformIO 虚拟环境并设置环境变量
$ source ~/.platformio/penv/bin/activate
(penv)$ aws iot describe - endpoint --endpoint - type iot:Data - ATS
{
    "endpointAddress": "<your_endpoint>"
}
(penv)$ export AWS_ENDPOINT='\"<your_encpoint>\"'
(penv)$ export WIFI_SSID='\"<your_ssid>\"'
(penv)$ export WIFI_PASS='\"<your_password>\"'
5. 项目库介绍

项目中有三个主要的库:
- lib/wifi/app_wifi.{c,h} :实现 Wi - Fi 连接。
- lib/aws/app_aws.{c,h} :处理 AWS 通信。
- lib/hw/app_hw.{c,h} :处理按钮按下事件并管理继电器。

以下是这些库中的关键代码和功能:

app_wifi.h

#ifndef app_wifi_h_
#define app_wifi_h_
typedef void (*on_connected_f)(void);
typedef void (*on_failed_f)(void);
typedef struct {
    on_connected_f on_connected;
    on_failed_f on_failed;
} connect_wifi_params_t;
void appwifi_connect(connect_wifi_params_t);
#endif

appwifi_connect 函数尝试连接到本地 Wi - Fi,并根据连接成功状态运行相应的回调函数。

app_hw.h

#ifndef app_hw_h_
#define app_hw_h_
#include <stdbool.h>
#include <stdint.h>
#include "driver/gpio.h"
// GPIO pins
#define APP_BTN0 19 // OFF
#define APP_BTN1 18 // 33%
#define APP_BTN2 5  // 66%
#define APP_BTN3 17 // 100%
#define APP_OE 27   // ENABLE
#define APP_RELAY1 32
#define APP_RELAY2 33
#define APP_RELAY3 25

typedef struct
{
    gpio_num_t btn_pin;
    gpio_num_t relay_pin;
    uint8_t val;
} btn_map_t;
typedef void (*appbtn_fan_changed_f)(uint8_t);
void apphw_init(appbtn_fan_changed_f);
uint8_t apphw_get_state(void);
void apphw_set_state(uint8_t);
#endif

apphw_init 函数根据用途将 GPIO 引脚初始化为输入或输出。按钮引脚作为输入,继电器控制引脚作为输出。该函数还接受一个函数参数,当按钮按下且相应继电器状态改变时调用该函数,以便更新设备状态。 apphw_set_state 函数用于从外部改变继电器状态。

app_aws.h

#ifndef app_aws_h_
#define app_aws_h_
#include <stdint.h>
#define AWS_THING_NAME "myhome_fan1"
typedef void (*fan_state_changed_f)(uint8_t);
void appaws_init(fan_state_changed_f);
void appaws_connect(void *);
void appaws_publish(uint8_t);
#endif

appaws_init 函数初始化库内部,接受一个回调参数,当用户请求改变风扇状态时调用该回调。 appaws_connect 函数在本地 Wi - Fi 连接后调用,连接到 AWS 云并监控设备状态变化。 appaws_publish 函数用于在风扇速度因按钮按下而改变时更新设备状态。

6. main.c 代码整合
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "app_wifi.h"
#include "app_hw.h"
#include "app_aws.h"
#define TAG "app"

static void handle_wifi_connect(void)
{
    xTaskCreatePinnedToCore(appaws_connect, "appaws_connect", 
15 * configMINIMAL_STACK_SIZE, NULL, 5, NULL, 0);
}

static void handle_wifi_failed(void)
{
    ESP_LOGE(TAG, "wifi failed");
}

void app_main()
{
    apphw_init(appaws_publish);
    appaws_init(apphw_set_state);
    connect_wifi_params_t cbs = {
        .on_connected = handle_wifi_connect,
        .on_failed = handle_wifi_failed};
    appwifi_connect(cbs);
}

app_main 函数中,我们初始化硬件库和 AWS 库,并设置回调函数。最后调用 appwifi_connect 函数连接到本地 Wi - Fi。

7. AWS 通信库实现

appaws_connect 函数

void appaws_connect(void *param)
{
    memset((void *)&aws_client, 0, sizeof(aws_client));
    ShadowInitParameters_t sp = ShadowInitParametersDefault;
    sp.pHost = endpoint_address;
    sp.port = AWS_IOT_MQTT_PORT;
    sp.pClientCRT = (const char *)certificate_pem_crt_start;
    sp.pClientKey = (const char *)private_pem_key_start;
    sp.pRootCA = (const char *)aws_root_ca_pem_start;
    sp.disconnectHandler = disconnected_handler;
    aws_iot_shadow_init(&aws_client, &sp);

    ShadowConnectParameters_t scp = 
ShadowConnectParametersDefault;
    scp.pMyThingName = thing_name;
    scp.pMqttClientId = client_id;
    scp.mqttClientIdLen = (uint16_t)strlen(client_id);
    while (aws_iot_shadow_connect(&aws_client, &scp) != 
SUCCESS)
    {
        ESP_LOGW(TAG, "trying to connect");
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }

    uint8_t fan_powerlevel = 0;
    jsonStruct_t fan_controller;
    fan_controller.cb = fan_powerlevel_change_requested;
    fan_controller.pData = &fan_powerlevel;
    fan_controller.pKey = "powerlevel";
    fan_controller.type = SHADOW_JSON_UINT8;
    fan_controller.dataLength = sizeof(uint8_t);
    if (aws_iot_shadow_register_delta(&aws_client, &fan_
controller) == SUCCESS)
    {
        ESP_LOGI(TAG, "shadow delta registered");
    }

    IoT_Error_t err = SUCCESS;
    while (1)
    {
        if (xSemaphoreTake(aws_guard, 100) == pdTRUE)
        {
            err = aws_iot_shadow_yield(&aws_client, 250);
            xSemaphoreGive(aws_guard);
        }
        if (err != SUCCESS)
        {
            ESP_LOGE(TAG, "yield failed: %d", err);
        }
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
}

该函数首先初始化设备的影子,然后尝试连接到设备。连接成功后,注册一个 delta 回调函数,用于处理设备影子的变化。最后,在一个循环中监控 AWS 消息。

appaws_publish 函数

void appaws_publish(uint8_t val)
{
    jsonStruct_t temp_json = {
        .cb = NULL,
        .pKey = "powerlevel",
        .pData = &val,
        .type = SHADOW_JSON_UINT8,
        .dataLength = sizeof(val)};
    char jsondoc_buffer[200];
    aws_iot_shadow_init_json_document(jsondoc_buffer, 
sizeof(jsondoc_buffer));
    aws_iot_shadow_add_reported(jsondoc_buffer, sizeof(jsondoc_
buffer), 1, &temp_json);
    if (desired_state != val)
    {
        aws_iot_shadow_add_desired(jsondoc_buffer, 
sizeof(jsondoc_buffer), 1, &temp_json);
    }
    aws_iot_finalize_json_document(jsondoc_buffer, 
sizeof(jsondoc_buffer));

    IoT_Error_t err = SUCCESS;
    if (xSemaphoreTake(aws_guard, portMAX_DELAY) == pdTRUE)
    {
        err = aws_iot_shadow_update(&aws_client, thing_name, 
jsondoc_buffer, NULL, NULL, 4, true);
        xSemaphoreGive(aws_guard);
    }
    if (err != SUCCESS)
    {
        ESP_LOGE(TAG, "publish failed: %d", err);
    }
}

该函数用于更新设备的影子状态。当风扇速度因按钮按下而改变时,会准备一个 JSON 消息并发送到 AWS IoT Core。如果期望状态与当前状态不同,还会更新期望状态,以避免不必要的 delta 消息。

8. 测试步骤

烧录开发板后,我们可以进行以下测试:
1. 按下 GPIO17 处的全速按钮,在 AWS 控制台(https://aws.amazon.com/console/)上观察设备影子的 powerlevel 更新为 100。
2. 按下 GPIO19 处的关闭按钮,观察设备影子的 powerlevel 更新为 0。
3. 在浏览器中访问 Alexa 开发者控制台(https://developer.amazon.com/alexa/console/ask),进入 myhome_smartfan 技能的测试页面。在 Alexa 模拟器中输入“set smart fan to 50”,观察中间的正常速度继电器是否开启,设备影子的 powerlevel 应为 66。

通过重复测试不同的功率级别,可以确认固件是否按预期工作。

9. 项目总结与展望

这个项目涵盖了典型物联网项目的各个层面,包括传感器(按钮)、执行器(继电器)、网络连接(Wi - Fi)和云集成(AWS IoT Core)。实现 Alexa 支持是该项目的一个亮点,涉及到 AWS IoT Core 的配置、AWS Lambda 后端代码的开发以及 Alexa 开发者控制台中智能家庭技能的创建。

未来,我们可以为智能风扇添加更多功能,例如:
- 集成温度传感器,实现自动模式,根据温度读数自动设置风扇速度。
- 实现 Alexa 文档中描述的塔式风扇设备模板(https://developer.amazon.com/en - US/docs/alexa/smarthome/get - started - with - device - templates.html#tower - fan)。
- 支持无线(OTA)固件更新。
- 实现 BLE 通用属性配置文件(GATT)服务器,用于本地通信。

物联网是一个快速发展、应用广泛的领域,只要我们跟上技术的变化,就会有无数的机会。不同的 ESP32 芯片,如 ESP32 - S2、ESP32 - C3 和 ESP32 - S3,针对不同的应用场景进行了优化,大家可以尝试使用这些芯片来提升自己的技能。

语音控制智能风扇实践

10. 项目架构总结

本项目构建了一个完整的语音控制智能风扇系统,其架构主要由硬件、软件和云服务三部分组成,以下是详细的架构总结:
|部分|描述|
|----|----|
|硬件|包括 ESP32 开发板、风扇按钮、继电器、TXS0108E 逻辑转换器模块等。风扇按钮作为传感器,继电器作为执行器,逻辑转换器模块用于电压转换。|
|软件|基于 ESP - IDF 框架,使用 PlatformIO 进行项目管理。包含 Wi - Fi 连接库、AWS 通信库和硬件控制库,通过 main.c 进行整合。|
|云服务|使用 AWS IoT Core 进行设备管理和通信,通过 Lambda 函数提供后端服务,结合 Alexa 开发者控制台实现语音控制。|

下面是该项目的整体架构 mermaid 流程图:

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A(用户语音指令) --> B(Alexa 开发者控制台)
    B --> C(AWS Lambda 函数)
    C --> D(AWS IoT Core)
    D --> E(ESP32 设备)
    E --> F(风扇按钮)
    F --> G(继电器)
    G --> H(风扇)
    I(风扇按钮操作) --> E
    E --> D
    D --> C
    C --> B
    B --> A(用户反馈)
11. 关键技术点分析
  • AWS IoT Core 影子服务 :通过 aws_iot_shadow_init aws_iot_shadow_connect 函数初始化和连接设备影子,使用 aws_iot_shadow_register_delta 注册 delta 回调,实现对设备状态变化的实时监控和响应。
  • JSON 消息处理 :在 appaws_publish 函数中,使用 aws_iot_shadow_init_json_document aws_iot_shadow_add_reported aws_iot_shadow_add_desired 函数处理 JSON 消息,确保设备状态的准确更新。
  • GPIO 引脚控制 :在 app_hw.h 中定义了 GPIO 引脚的宏和 API 函数,通过 apphw_init 函数初始化引脚,使用 apphw_set_state 函数控制继电器状态。
12. 代码优化建议
  • 错误处理 :在现有代码中,虽然对一些关键操作进行了错误检查,但可以进一步完善错误处理机制,例如在 appaws_connect appaws_publish 函数中,当连接失败或发布失败时,可以进行更详细的错误日志记录和重试策略。
  • 代码复用 :部分代码可以进行复用和封装,例如 JSON 消息处理部分,可以将其封装成独立的函数,提高代码的可维护性和复用性。
  • 性能优化 :在 appaws_connect 函数的循环中, vTaskDelay 的时间可以根据实际情况进行调整,以减少不必要的等待时间,提高系统的响应速度。
13. 常见问题及解决方案
问题 解决方案
设备无法连接到 AWS IoT Core 检查网络连接、AWS_ENDPOINT 环境变量、加密文件路径和权限等。
语音指令无法控制风扇 检查 Alexa 技能配置、AWS Lambda 函数逻辑和 ESP32 设备的消息接收和处理逻辑。
继电器状态异常 检查 GPIO 引脚连接、继电器驱动电路和 apphw_set_state 函数的调用逻辑。
14. 结语

通过这个项目,我们深入了解了如何利用 ESP32 构建一个完整的物联网系统,包括硬件连接、软件编程和云服务集成。实现语音控制智能风扇不仅提升了用户体验,还展示了物联网技术在智能家居领域的应用潜力。

在实践过程中,我们遇到了各种挑战,如设备连接问题、消息处理错误等,但通过不断调试和优化,最终实现了系统的稳定运行。同时,我们也看到了项目的可扩展性,可以通过添加更多功能来进一步完善智能风扇的性能。

希望本文能为大家在物联网开发方面提供一些参考和启示,鼓励大家不断探索和实践,将更多的创意和想法转化为实际的项目。在未来的物联网发展中,我们可以期待更多创新的应用和解决方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值