1 nRF7002-DK 简介
nRF7002-DK是用于nRF7002 Wi-Fi 6协同IC的开发套件,该开发套件采用nRF5340多协议片上系统 (SoC) 作为nRF7002的主处理器,在单一的电路板上包含了开发工作所需的一切,可让开发人员轻松开启基于nRF7002 的物联网项目。该 DK 包括 Arduino 连接器、两个可编程按钮、一个 Wi-Fi 双频段天线和一个低功耗蓝牙天线,以及电流测量引脚。
这款DK支持低功耗 Wi-Fi 应用开发,并实现了多项 Wi-Fi 6 功能,比如 OFDMA、波束成型和 TWT。nRF7002 Wi-Fi 6配套IC为另一个主机添加了低功耗Wi-Fi 6功能,提供无缝连接和基于Wi-Fi的定位(本地Wi-Fi集线器的SSID嗅探)功能。该IC设计用于搭配Nordic现有的nRF52®和nRF53®系列多协议片上系统 (SoC) 和nRF91®系列蜂窝物联网系统级封装 (SiP) 使用。nRF7002 IC 还可与非nordic主机器件搭配使用。通过SPI或QSPI与主机通信,并带有额外的共存功能,可与其他协议如蓝牙、Thread或Zigbee无缝共存。
图1.1 外观图
2 任务及实现方案
2.1 本期任务
任务2:使用WiFi连接功能,连接网络,并实现远程控制板卡LED和读取按键信息
2.2 实现思路
结合智能家居平台Home Assistant,开发板与Home Assistant服务器以MQTT协议进行wifi通信,传输开关状态与LED开关指令。
2.3 功能展示
开发板通电后自动通过wifi与Home Assistant服务器进行通信。图2.1为Home Assistant界面,界面上显示了开发板上当前按键状态与LED控制开关。按下开发板上的key1,可以观察到key1状态变为开启。在Home Assistant控制面板中点击led开关,可以对开发板上的led进行开关操作。
图2.1 Home Assistant界面
图2.2 按下开关1
图2.3 点亮LED1
图2.4 板载LED1亮起
3 实现过程
3.1 总览
图3.1为本项目的工作流程图,其中红色部分为用户操作,绿色部分为另一端的结果反馈,nRF7002和Home Assistant服务器通过MQTT协议进行通信。
图3.1 工作流程图(以key1和led1为例)
3.2 MQTT通信
MQTT是基于TCP/IP协议栈构建的异步通信消息协议,是一种轻量级的发布、订阅信息传输协议,在物联网领域应用广泛。
实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(topic)和负载(payload)两部分:
(1)topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);
(2)payload,可以理解为消息的内容,是指订阅者具体要使用的内容。
本项目中,nRF7002作为客户端,需要订阅Home Assistant发来的LED控制指令,同时发布板载的按键状态信息。
nRF7002部分的代码主要参考了https://github.com/AliNordic/mqtt_over_wifi_nrf7002DK,部分重要代码分析如下:
配置mqtt的topic与服务器的IP地址与端口号(prj.conf文件):
# Application
CONFIG_MQTT_PUB_TOPIC1="homeassistant/binary_sensor/key1"
CONFIG_MQTT_PUB_TOPIC2="homeassistant/binary_sensor/key2"
CONFIG_MQTT_SUB_TOPIC="homeassistant/button/led"
#Note If you notice that the test.mosquitto.org is unresponsive,
#there are several other public MQTT brokers that you can use (Ex: broker.hivemq.com at port 1883)
CONFIG_MQTT_BROKER_HOSTNAME="192.168.1.254"
CONFIG_MQTT_CLIENT_ID="nRF7002DK"
CONFIG_MQTT_BROKER_PORT=1883
初始化mqtt客户端,这里需要注意的是原代码并没有设置mqtt的登录用户名和密码,如果与Home Assistant连接需要手动配置,代码如下(mqtt_connection.c文件client_init()函数):
struct mqtt_utf8 pass, user_name;
/**@brief Initialize the MQTT client structure
*/
int client_init(struct mqtt_client *client)
{
int err;
/* Initializes the client instance. */
mqtt_client_init(client);
/* Resolves the configured hostname and initializes the MQTT broker structure */
err = broker_init();
if (err) {
LOG_ERR("Failed to initialize broker connection");
return err;
}
pass.size = strlen("password");
pass.utf8 = (uint8_t *)"password";
user_name.size = strlen("mqtt");
user_name.utf8 = (uint8_t *)"mqtt";
/* MQTT client configuration */
client->broker = &broker;
client->evt_cb = mqtt_evt_handler;
client->client_id.utf8 = client_id_get();
client->client_id.size = strlen(client->client_id.utf8);
client->password = &pass;
client->user_name = &user_name;
client->protocol_version = MQTT_VERSION_3_1_1;
/* MQTT buffers configuration */
client->rx_buf = rx_buffer;
client->rx_buf_size = sizeof(rx_buffer);
client->tx_buf = tx_buffer;
client->tx_buf_size = sizeof(tx_buffer);
/* We are not using TLS */
client->transport.type = MQTT_TRANSPORT_NON_SECURE;
return err;
}
开发板收到服务器传来的LED控制指令,触发mqtt event事件,根据topic与payload执行对应LED的开关操作(mqtt_connection.c文件mqtt_evt_handler()函数):
case MQTT_EVT_PUBLISH:
{
const struct mqtt_publish_param *p = &evt->param.publish;
//Print the length of the recived message
LOG_INF("MQTT PUBLISH result=%d len=%d",
evt->result, p->message.payload.len);
//Extract the data of the recived message
err = get_received_payload(c, p->message.payload.len);
//Send acknowledgment to the broker on receiving QoS1 publish message
if (p->message.topic.qos == MQTT_QOS_1_AT_LEAST_ONCE) {
const struct mqtt_puback_param ack = {
.message_id = p->message_id
};
/* Send acknowledgment. */
mqtt_publish_qos1_ack(c, &ack);
}
if (err >= 0) {
data_print("Received: ", payload_buf, p->message.payload.len);
// Control LED1 and LED2
if(strncmp(payload_buf,CONFIG_TURN_LED1_ON_CMD,sizeof(CONFIG_TURN_LED1_ON_CMD)-1) == 0){
dk_set_led_on(DK_LED1);
}
else if(strncmp(payload_buf,CONFIG_TURN_LED1_OFF_CMD,sizeof(CONFIG_TURN_LED1_OFF_CMD)-1) == 0){
dk_set_led_off(DK_LED1);
}
else if(strncmp(payload_buf,CONFIG_TURN_LED2_ON_CMD,sizeof(CONFIG_TURN_LED2_ON_CMD)-1) == 0){
dk_set_led_on(DK_LED2);
}
else if(strncmp(payload_buf,CONFIG_TURN_LED2_OFF_CMD,sizeof(CONFIG_TURN_LED2_OFF_CMD)-1) == 0){
dk_set_led_off(DK_LED2);
}
// Payload buffer is smaller than the received data
} else if (err == -EMSGSIZE) {
LOG_ERR("Received payload (%d bytes) is larger than the payload buffer size (%d bytes).",
p->message.payload.len, sizeof(payload_buf));
// Failed to extract data, disconnect
} else {
LOG_ERR("get_received_payload failed: %d", err);
LOG_INF("Disconnecting MQTT client...");
err = mqtt_disconnect(c);
if (err) {
LOG_ERR("Could not disconnect: %d", err);
}
}
} break;
板载按键开关状态变化时,将对应开关名称写入topic,开关状态写入payload,并发布mqtt消息(main.c文件button_handler()函数):
static void button_handler(uint32_t button_state, uint32_t has_changed)
{
switch (has_changed) {
case DK_BTN1_MSK:
if (button_state & DK_BTN1_MSK){
int err = data_publish(&client, MQTT_QOS_1_AT_LEAST_ONCE,
"ON", sizeof("ON")-1, CONFIG_MQTT_PUB_TOPIC1);
if (err) {
LOG_ERR("Failed to send message, %d", err);
return;
}
}
else
{
int err = data_publish(&client, MQTT_QOS_1_AT_LEAST_ONCE,
"OFF", sizeof("OFF")-1, CONFIG_MQTT_PUB_TOPIC1);
if (err) {
LOG_ERR("Failed to send message, %d", err);
return;
}
}
break;
case DK_BTN2_MSK:
if (button_state & DK_BTN2_MSK){
int err = data_publish(&client, MQTT_QOS_1_AT_LEAST_ONCE,
"ON", sizeof("ON")-1, CONFIG_MQTT_PUB_TOPIC2);
if (err) {
LOG_ERR("Failed to send message, %d", err);
return;
}
}
else
{
int err = data_publish(&client, MQTT_QOS_1_AT_LEAST_ONCE,
"OFF", sizeof("OFF")-1, CONFIG_MQTT_PUB_TOPIC2);
if (err) {
LOG_ERR("Failed to send message, %d", err);
return;
}
}
break;
}
}
3.3 Home Assistant配置
根据Home Assistant MQTT配置文档(https://www.home-assistant.io/integrations/mqtt/),分别建立key1、key2、led1和led2四个实体,其中key的类型为Binary sensor,led的类型为button。相关配置文件如下(添加在Home Assistant的configuration.yaml文件的最后):
mqtt:
binary_sensor:
- name: "key1"
state_topic: "homeassistant/binary_sensor/key1"
- name: "key2"
state_topic: "homeassistant/binary_sensor/key2"
switch:
- name: "led1"
command_topic: "homeassistant/button/led"
payload_on: "LED1ON"
payload_off: "LED1OFF"
- name: "led2"
command_topic: "homeassistant/button/led"
payload_on: "LED2ON"
payload_off: "LED2OFF"
4 心得体会
本次活动的开发板具有wifi、蓝牙、nfc等多种通信方式,且支持MQTT、matter等物联网通信协议,非常适合智能家居、物联网传感器等应用。同时板卡自带的例程丰富,配套软件与SDK功能齐全,比较容易上手。