上一篇通过AT调试工具实现MQTT推送开发板状态的功能。在程序中添加响应的代码,实现消息的自动推送。
1.1添加AT指令
由于AT指令没有统一的格式,对不同的模块,需要根据AT指令的格式编写对应的驱动。通过手动测试AT指令,可以实现MQTT发布信息,通过MQTT服务器转发,在手机的MQTT客户端订阅对应的信息,监控开发板的状态。
既然该开发板是基于瑞萨DA16200开发的,参考瑞萨官网上给出的AT示例程序。移植示例程序中的AT指令部分的代码。

在示例程序的源码中,AT指令相关的代码在da16200_AT.c和da16200_AT.h中。

由于亿佰特的模块中的AT接口定义和示例程序中对应的DA16200模块的AT接口定义有差别,移植过程中需要对指令部分进行修改。修改后的AT指令集如下:
/** AT Command sets */
/*LDRA_INSPECTED 27 D This structure must be accessible in user code. It cannot be static. */
da16200_at_cmd_set_t g_da16200_cmd_set[] =
{
/** Intial AT command */
[DA16200_AT_CMD_INDEX_ATZ] =
{
.p_cmd = (uint8_t *) "ATZ",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_64,
.retry = DA16200_RETRY_VALUE_10,
.retry_delay = DA16200_DELAY_500MS
},
/** Echo on/off */
[DA16200_AT_CMD_INDEX_ATE] =
{
.p_cmd = (uint8_t *) "ATE",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_32,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
/* All profiles in NVRAM are removed and set up in Soft-AP mode with the default configuration. */
[DA16200_AT_CMD_INDEX_AT_CWDEFAP] =
{
.p_cmd = (uint8_t *) "AT+CWDEFAP",
.p_success_resp[0] = (uint8_t *) "OK",
.p_success_resp[1] = (uint8_t *) "+INIT:DONE,1",
.max_resp_length = DA16200_STR_LEN_128,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
/* Set AP mode, here is station */
[DA16200_AT_CMD_INDEX_AT_CWMODE] =
{
.p_cmd = (uint8_t *) "AT+CWMODE=0",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_32,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
/* Set Country Code */
[DA16200_AT_CMD_INDEX_AT_CWCOUNTRY] =
{
.p_cmd = (uint8_t *) "AT+CWCOUNTRY=",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_32,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
/* System restart */
[ DA16200_AT_CMD_INDEX_AT_RESTART] =
{
.p_cmd = (uint8_t *) "AT+RESTART",
.p_success_resp[0] = (uint8_t *) "OK",
.p_success_resp[1] = (uint8_t *) "+INIT:DONE,0",
.max_resp_length = DA16200_STR_LEN_128,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_1000MS
},
/* Connect to an AP */
[ DA16200_AT_CMD_INDEX_AT_CWJAPA] =
{
.p_cmd = (uint8_t *) ("AT+CWJAPA="),
.p_success_resp[0] = (uint8_t *) "OK",
.p_success_resp[1] = (uint8_t *) "+CWJAP:1",
.max_resp_length = DA16200_STR_LEN_128,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_1000MS
},
/* Start the DHCP Client */
[ DA16200_AT_CMD_INDEX_AT_CWDHCPC] =
{
.p_cmd = (uint8_t *) "AT+CWDHCPC=1",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_64,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
/* Start the DHCP Client */
[ DA16200_AT_CMD_INDEX_AT_CWDHCPC_READ] =
{
.p_cmd = (uint8_t *) "AT+CWDHCPC=?",
.p_success_resp[0] = (uint8_t *) "+CWDHCPC:1",
.p_success_resp[1] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_64,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
/* Set the IP address and the port number of the MQTT Broker */
[ DA16200_AT_CMD_INDEX_AT_MQTTBR] =
{
.p_cmd = (uint8_t *) "AT+MQTTBR=",
// .p_cmd = (uint8_t *) "AT+NWMQBR="+MQTT_SERVER_IP+","+MQTT_PORT+"\r\n",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_64,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
/* Set the MQTT QoS level */
[ DA16200_AT_CMD_INDEX_AT_MQTTQOS] =
{
.p_cmd = (uint8_t *) "AT+MQTTQOS=0",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_64,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
/* MQTT login information */
[ DA16200_AT_CMD_INDEX_AT_MQTTLI] =
{
.p_cmd = (uint8_t *) "AT+MQTTLI=",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_32,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
/* Set the MQTT Client ID */
[ DA16200_AT_CMD_INDEX_AT_MQTTCID] =
{
.p_cmd = (uint8_t *) "AT+MQTTCID=",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_32,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
/* Enable the MQTT client, 1 enable, 0 disable */
[ DA16200_AT_CMD_INDEX_AT_MQTTCL] =
{
.p_cmd = (uint8_t *) "AT+MQTTCL=1",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_32,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
/* Publish an MQTT message with <msg>,<topic> */
/* [ DA16200_AT_CMD_INDEX_AT_NWMQMSG] =
{
.p_cmd = (uint8_t *) "AT+NWMQMSG=open,door\r\n",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_32,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
}*/
[ DA16200_AT_CMD_INDEX_AT_MQTTMSG_BUTTON_PRESSED] =
{
.p_cmd = (uint8_t *) "AT+MQTTMSG=button is pressed,button",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_64,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
[ DA16200_AT_CMD_INDEX_AT_MQTTMSG_BUTTON_RELEASED] =
{
.p_cmd = (uint8_t *) "AT+MQTTMSG=button is released,button",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_64,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
[ DA16200_AT_CMD_INDEX_AT_MQTTMSG_TEMP_READ] =
{
.p_cmd = (uint8_t *) "AT+MQTTMSG=",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_64,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
},
[ DA16200_AT_CMD_INDEX_AT_MQTTMSG_HUMI_READ] =
{
.p_cmd = (uint8_t *) "AT+MQTTMSG=",
.p_success_resp[0] = (uint8_t *) "OK",
.max_resp_length = DA16200_STR_LEN_64,
.retry = DA16200_RETRY_VALUE_5,
.retry_delay = DA16200_DELAY_200MS
}
};
1.2 MQTT应用程序实现
上一篇中通过RT-Thread的AT指令调试工具对MQTT客户端连接服务器和发布消息的流程进行验证,确认过程可以实现。在NUCLEO-F411开发板上连接E103-W12C-TB通讯模块和HS3003温湿度传感器,将开发板按键事件和温湿度传感器数据发布到MQTT网络中,在手机APP上查看相应的结果。硬件连接如下图。

应用程序的流程如下,检测到按键状态发生变化后,发布按键的信息。对温湿度传感器数据的发布采用周期发布的方式。

发布温湿度数据的MQTT消息函数如下。
rt_err_t AT_cmd_send_temp(rt_int32_t temp)
{
rt_err_t status = RT_EOK;
static char tempc[AT_CMD_LENGTH];
sprintf(tempc,"AT+MQTTMSG=%3d.%d,temperature",temp / 10,temp % 10);
g_da16200_cmd_set[DA16200_AT_CMD_INDEX_AT_MQTTMSG_TEMP_READ].p_cmd = (uint8_t *)tempc;
status = AT_cmd_send_ok(DA16200_AT_CMD_INDEX_AT_MQTTMSG_TEMP_READ);
return status;
}
rt_err_t AT_cmd_send_humi(rt_int32_t humi)
{
rt_err_t status = RT_EOK;
static char humic[AT_CMD_LENGTH];
sprintf(humic,"AT+MQTTMSG=%3d.%d,humidity",humi / 10,humi % 10);
// LOG_D("%s",humic);
g_da16200_cmd_set[DA16200_AT_CMD_INDEX_AT_MQTTMSG_HUMI_READ].p_cmd = (uint8_t *)humic;
// LOG_D("%s",g_da16200_cmd_set[DA16200_AT_CMD_INDEX_AT_MQTTMSG_HUMI_READ].p_cmd);
status = AT_cmd_send_ok(DA16200_AT_CMD_INDEX_AT_MQTTMSG_HUMI_READ);
return status;
}通过sprintf函数和格式化字符串,将整型的温湿度数据添加到MQTT消息中,发布到MQTT网络。
主程序的代码如下
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <at.h> /* AT 组件头文件 */
#define LOG_TAG "mqtt_app"
#define LOG_LVL LOG_LVL_DBG
#include <ulog.h>
#include <agile_button.h>
#include "da16200_AT.h"
#include "sensor_renesas_hs300x.h"
#define HS300X_I2C_BUS "i2c1"
/* defined the LED0 pin: PA5 */
#define LED0_PIN GET_PIN(A, 5)
#define BUTTON_PIN GET_PIN(C, 13)
static agile_btn_t *_dbtn = RT_NULL;
static void read_temp_entry(void *parameter);
static void read_humi_entry(void *parameter);
static rt_int32_t temp_10,humi_10;
static void btn_click_event_cb(agile_btn_t *btn)
{
rt_kprintf("[button click event] pin:%d repeat:%d, hold_time:%d\r\n", btn->pin, btn->repeat_cnt, btn->hold_time);
send_message(1);
}
int main(void)
{
int count = 1;
rt_uint8_t msg;
rt_uint8_t mode=1;
int pin = BUTTON_PIN;
int active = 0;
if (active == PIN_HIGH)
_dbtn = agile_btn_create(pin, active, PIN_MODE_INPUT_PULLDOWN);
else
_dbtn = agile_btn_create(pin, active, PIN_MODE_INPUT_PULLUP);
agile_btn_set_event_cb(_dbtn, BTN_CLICK_EVENT, btn_click_event_cb);
agile_btn_start(_dbtn);
/* set LED0 pin mode to output */
rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
rt_thread_t hs3003temp_thread,hs3003humi_thread;
at_client_init("uart6", 512);
LOG_D("button pin %d!",BUTTON_PIN);
//AT_cmd_send_data(DA16200_AT_CMD_INDEX_AT_CWDEFAP, 4000);
wifi_con_routine();
mqtt_con_routine();
rt_thread_mdelay(3000);
hs3003temp_thread = rt_thread_create("hs3003tem",
read_temp_entry,
"temp_hs3",
2048,
RT_THREAD_PRIORITY_MAX / 2,
20);
if (hs3003temp_thread != RT_NULL)
{
rt_thread_startup(hs3003temp_thread);
}
while (count++)
{
msg=get_message();
switch(msg){
case 1:{
switch(mode)
{
case 1:
mode =2;
AT_cmd_send_ok(DA16200_AT_CMD_INDEX_AT_MQTTMSG_BUTTON_PRESSED);
break;
case 2:
AT_cmd_send_ok(DA16200_AT_CMD_INDEX_AT_MQTTMSG_BUTTON_RELEASED);
mode =1;
break;
}
break;
}
case 2:
AT_cmd_send_temp(temp_10);
rt_thread_mdelay(1000);
AT_cmd_send_humi(humi_10);
break;
}
}
return RT_EOK;
}
/* Notice: PB8 --> 24 SCL; PB9 --> 25 SDA*/
int rt_hw_hs300x_port(void)
{
struct rt_sensor_config cfg;
cfg.intf.dev_name = HS300X_I2C_BUS;
cfg.intf.user_data = (void *)HS300X_I2C_ADDR;
rt_hw_hs300x_init("hs300x", &cfg);
return RT_EOK;
}
INIT_ENV_EXPORT(rt_hw_hs300x_port);
static void read_temp_entry(void *parameter)
{
rt_device_t dev_t = RT_NULL;
rt_device_t dev_h = RT_NULL;
struct rt_sensor_data sensor_data,sensor_datah;
rt_size_t res;
dev_t = rt_device_find(parameter);
dev_h = rt_device_find("humi_hs3");
if (dev_t == RT_NULL)
{
rt_kprintf("Can't find device:%s\n", parameter);
return;
}
if (rt_device_open(dev_t, RT_DEVICE_FLAG_RDWR) != RT_EOK)
{
rt_kprintf("open device failed!\n");
return;
}
if (rt_device_open(dev_h, RT_DEVICE_FLAG_RDWR) != RT_EOK)
{
rt_kprintf("open device failed!\n");
return;
}
rt_device_control(dev_t, RT_SENSOR_CTRL_SET_ODR, (void *)100);
rt_device_control(dev_h, RT_SENSOR_CTRL_SET_ODR, (void *)100);
while (1)
{
res = rt_device_read(dev_h, 0, &sensor_datah, 1);
res = rt_device_read(dev_t, 0, &sensor_data, 1);
if (res != 1)
{
rt_kprintf("read data failed!size is %d\n", res);
rt_device_close(dev_t);
return;
}
else
{
temp_10=sensor_data.data.temp;
humi_10=sensor_datah.data.humi;
send_message(2);
}
rt_thread_mdelay(5000);
}
}代码的运行流程和图中一样,注册按键的回调函数、创建温湿度传感器数据读取任务,进入主循环等待接收信号队列数据,接收到信号后,根据接收到的信号类型,发布对应MQTT消息。
应用程序逻辑如上所述,在程序开发时,借助RT-Thread丰富的组件,比如程序中的alige-button、sensor框架等,可以快速完成原型程序的搭建,只需要在工程的RT-Thread Settings界面配置相应的组件,即可使用已有的代码创建应用程序,完成功能验证。


1.3 功能演示
参考上一篇中搭建MQTT服务器和App客户端订阅消息的设置,在APP上订阅传感器发布的按键和温湿度消息。可以看到数据更新到了手机端。

1.4 总结
AT指令由于各家的协议不同,在开发时需要根据协议要求编写通讯程序,借助RT-Thread已有的代码,可以快速进行原型验证,并在此基础上对代码进行优化,开发应用程序。
我要赚赏金
