上一篇通过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已有的代码,可以快速进行原型验证,并在此基础上对代码进行优化,开发应用程序。