ESP8266串口透传+WiFi储存+OTA+smartconfig/airkiss

前言

去年在给做机器人时需要手机控制,选来选去选择了ESP8266,价格便宜,功能强大。最方便的是通信模块都有的AT指令,但是这个功能让我觉得很难受,AT指令需要机器人的MCU发送AT指令才能工作。如果换一个环境,通信代码还需要重新写,同时有些功能还很不好用,在借鉴别人的博客之后发现SDK开发比AT有意思多了,如果使用SDK开发ESP8266,直接做成串口透传,无论MCU发来什么数据,8266直接转发到服务器,这样任何MCU就都能兼容,一次开发,无限受用。于是就有了这次的文章。内容主要有以下几点:

  1. 串口透传,接收MCU串口数据,直接通过MQTT上传到服务器。
  2. smartconfig+airkiss配网,随意使用,场景丰富。
  3. 最多储存5个WIFI账号和密码,自动寻找网络连接。
  4. 按键配网,长按重新配网,前一次WiFi自动储存,添加配网指示灯。
  5. OTA空中升级(待验证)

一、配网

在群里发现很多小伙伴会遇到搭建环境的困扰,这里附上一键搭建链接:链接: ESP8266安装安信可一体化开发环境.
下面展示一些 程序入口:主函数

//程序入口
void ICACHE_FLASH_ATTR user_init(void)
{
     
	uart_init(115200, 115200);
	os_delay_us(60000);
	keyInit();
	set_uart_cb(uart_cb);

	PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12); //GPIO12初始化
	GPIO_OUTPUT_SET(GPIO_ID_PIN(12), 0);//低电平

    get_mac();//获取MAC地址

	wifi_set_opmode(STATION_MODE); 
	//设置wifi信息存储数量,最大为5个
	wifi_station_ap_number_set(2);

    mqtt_init();

	set_wifistate_cb(wifi_connect_cb, wifi_disconnect_cb);
}

模块上电进行初始化,之后会打印MAC地址,因为MQTT每个设备的地址都要不一致,防止错乱,所以我会把MAC地址填充到主题里。
按下按键会进入配网模式,D5对应GPIO14。这是由于我是用安信可nodeMCU开发的,建议新手使用此模块。
有两个按键回调函数,短按是第一次上电进入配网模式,长按可在需要跟换WiFi时使用。长按4S以上进入重新配网模式。

下面展示一些 按键代码

/**
 * 	按键短按回调
 */
LOCAL void ICACHE_FLASH_ATTR key1ShortPress(void) {
     

	start_smartconfig(smartconfig_cd);
	INFO("start_smartconfig\n");
}
/**
 * 	按键长按回调
 */
LOCAL void ICACHE_FLASH_ATTR key1LongPress(void) {
     

	start_smartconfig(smartconfig_cd);
	INFO("start_smartconfig\n");
}
/**
 * 	按键初始化
 */
LOCAL void ICACHE_FLASH_ATTR keyInit(void) {
     

	//设置按键数量
	set_key_num(1);
	//长按、短按的按键回调
	key_add(D5, NULL, key1ShortPress);
	key_add(D5, NULL, key1LongPress);

}

上电短按D5(GPIO14)进入 smartconfig连接回调
smartconfig_set_type();函数可选3个参数:分别是:SC_TYPE_ESPTOUCH、SC_TYPE_AIRKISS和SC_TYPE_ESPTOUCH_AIRKISS
第一个是smartconfig配网,第二个是airkiss配网,最后一个两者都可以。

/**
 * 开始Smartconfig配置  
 * @param  cd: Smartconfig状态回调
 * @retval None
 */
void ICACHE_FLASH_ATTR start_smartconfig(smartconfig_cd_t cd) {
     
	smartconfig_flag = 1;
	smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS); //SC_TYPE_ESPTOUCH,SC_TYPE_AIRKISS,SC_TYPE_ESPTOUCH_AIRKISS
	wifi_station_disconnect();
	wifi_set_opmode(STATION_MODE);
	finish_cd = cd;
	smartconfig_start(smartconfig_done);
	os_timer_disarm(&OS_Timer_Wifichange);	// 关闭定时器

	if(connect_flag == 1){
     
		w_disconnect();
		connect_flag = 0;
	}

	os_timer_disarm(&OS_Timer_SM);	// 关闭定时器
	os_timer_setfn(&OS_Timer_SM, (os_timer_func_t *) sm_wait_time, NULL);// 设置定时器
	os_timer_arm(&OS_Timer_SM, 1000, 1);  // 使能定时器
}

随后进入Smartconfig 状态处理:smartconfig_start(smartconfig_done);开始正式进入smartconfig/airkiss配网。
配网成功会打开灯。

/**
 * Smartconfig 状态处理 
 * @param  status: 状态
 * @param  *pdata: AP数据
 * @retval None
 */
void ICACHE_FLASH_ATTR
smartconfig_done(sc_status status, void *pdata) {
     
	switch (status) {
     
	case SC_STATUS_WAIT:
		INFO("SC_STATUS_WAIT\n");
		break;
	case SC_STATUS_FIND_CHANNEL:
		INFO("SC_STATUS_FIND_CHANNEL\n");
		break;
	case SC_STATUS_GETTING_SSID_PSWD:
		INFO("SC_STATUS_GETTING_SSID_PSWD\n");
		sc_type *type = pdata;
		if (*type == SC_TYPE_ESPTOUCH) {
     
			INFO("SC_TYPE:SC_TYPE_ESPTOUCH\n");
		} else {
     
			INFO("SC_TYPE:SC_TYPE_AIRKISS\n");
		}
		break;
	case SC_STATUS_LINK:
		INFO("SC_STATUS_LINK\n");
		sm_comfig_status = SM_STATUS_GETINFO;
		struct station_config *sta_conf = pdata;
		wifi_station_set_config(sta_conf);
		wifi_station_disconnect();
		wifi_station_connect();
		break;
	case SC_STATUS_LINK_OVER:
		sm_comfig_status = SM_STATUS_FINISH;
		INFO("SC_STATUS_LINK_OVER\n");
		if (pdata != NULL) {
     
			//SC_TYPE_ESPTOUCH
			uint8 phone_ip[4] = {
      0 };
			os_memcpy(phone_ip, (uint8*) pdata, 4);
			INFO("Phone ip: %d.%d.%d.%d\n", phone_ip[0], phone_ip[1],
					phone_ip[2], phone_ip[3]);
		} else {
     
			//SC_TYPE_AIRKISS - support airkiss v2.0
			airkiss_start_discover();
		}
		smartconfig_stop();
		smartconfig_flag = 0;
		connect_flag = 0;
		os_timer_disarm(&OS_Timer_SM);	// 关闭定时器
		finish_cd(sm_comfig_status);
		os_timer_arm(&OS_Timer_Wifichange, 3000, 1);  // 使能定时器
		break;
	}

}

/**
 * 	WIFI连接回调
 */
void wifi_connect_cb(void){
     

	INFO("wifi connect!\r\n");
	os_printf("----- WiFi连接成功,打开绿灯---\r\n");
	GPIO_OUTPUT_SET(GPIO_ID_PIN(12), 1);
	MQTT_Connect(&mqttClient);
}

/**
 * 	WIFI断开回调
 */
void wifi_disconnect_cb(void){
     
	INFO("wifi disconnect!\r\n");
	os_printf("----- WiFi断开,关闭绿灯---\r\n");
	GPIO_OUTPUT_SET(GPIO_ID_PIN(12), 0);
	MQTT_Disconnect(&mqttClient);
}

二、连接MQTT服务器

网络连接成功以后可以开始MQTT的初始化,初始化包涵一系列的连接初始化回调,连接成功或不成功回调,主题订阅发布回调等等。
下面展示一些 MQTT初始化

/**
 * 	MQTT初始化
 */
void ICACHE_FLASH_ATTR mqtt_init(void) {
     

	MQTT_InitConnection(&mqttClient, MQTT_HOST, MQTT_PORT, DEFAULT_SECURITY);
	MQTT_InitClient(&mqttClient, mac_str, MQTT_USER,MQTT_PASS, MQTT_KEEPALIVE, 1);
	MQTT_InitLWT(&mqttClient, lwt_topic, LWT_MESSAGE, 0, 0);
	MQTT_OnConnected(&mqttClient, mqttConnectedCb);
	MQTT_OnDisconnected(&mqttClient, mqttDisconnectedCb);
	MQTT_OnPublished(&mqttClient, mqttPublishedCb);
	MQTT_OnData(&mqttClient, mqttDataCb);
}
/**
  * @brief  MQTT initialization connection function
  * @param  client: 	MQTT_Client reference
  * @param  host: 	Domain or IP string
  * @param  port: 	Port to connect
  * @param  security:		1 for ssl, 0 for none
  * @retval None
  */
void ICACHE_FLASH_ATTR
MQTT_InitConnection(MQTT_Client *mqttClient, uint8_t* host, uint32_t port, uint8_t security)
{
     


	uint32_t temp;
	INFO("MQTT_InitConnection\r\n");
	os_memset(mqttClient, 0, sizeof(MQTT_Client));
	temp = os_strlen(host);
	mqttClient->host = (uint8_t*)os_zalloc(temp + 1);
	os_strcpy(mqttClient->host, host);
	mqttClient->host[temp] = 0;
	mqttClient->port = port;
	mqttClient->security = security;

}

/**
  * @brief  MQTT initialization mqtt client function
  * @param  client: 	MQTT_Client reference
  * @param  clientid: 	MQTT client id
  * @param  client_user:MQTT client user
  * @param  client_pass:MQTT client password
  * @param  client_pass:MQTT keep alive timer, in second
  * @retval None
  */
void ICACHE_FLASH_ATTR
MQTT_InitClient(MQTT_Client *mqttClient, uint8_t* client_id, uint8_t* client_user, uint8_t* client_pass, uint32_t keepAliveTime, uint8_t cleanSession)
{
     
	uint32_t temp;
	INFO("MQTT_InitClient\r\n");
	os_printf("CD MQTT_InitClient++++++++++++++++++++++\n");
	os_memset(&mqttClient->connect_info, 0, sizeof(mqtt_connect_info_t));

	temp = os_strlen(client_id);
	mqttClient->connect_info.client_id = (uint8_t*)os_zalloc(temp + 1);
	os_strcpy(mqttClient->connect_info.client_id, client_id);
	mqttClient->connect_info.client_id[temp] = 0;

	if (client_user)
	{
     
		temp = os_strlen(client_user);
		mqttClient->connect_info.username = (uint8_t*)os_zalloc(temp + 1);
		os_strcpy(mqttClient->connect_info.username, client_user);
		mqttClient->connect_info.username[temp] = 0;
	}

	if (client_pass)
	{
     
		temp = os_strlen(client_pass);
		mqttClient->connect_info.password = (uint8_t*)os_zalloc(temp + 1);
		os_strcpy(mqttClient->connect_info.password, client_pass);
		mqttClient->connect_info.password[temp] = 0;
	}


	mqttClient->connect_info.keepalive = keepAliveTime;
	mqttClient->connect_info.clean_session = cleanSession;

	mqttClient->mqtt_state.in_buffer = (uint8_t *)os_zalloc(MQTT_BUF_SIZE);
	mqttClient->mqtt_state.in_buffer_length = MQTT_BUF_SIZE;
	mqttClient->mqtt_state.out_buffer =  (uint8_t *)os_zalloc(MQTT_BUF_SIZE);
	mqttClient->mqtt_state.out_buffer_length = MQTT_BUF_SIZE;
	mqttClient->mqtt_state.connect_info = &mqttClient->connect_info;

	mqtt_msg_init(&mqttClient->mqtt_state.mqtt_connection, mqttClient->mqtt_state.out_buffer, mqttClient->mqtt_state.out_buffer_length);

	QUEUE_Init(&mqttClient->msgQueue, QUEUE_BUFFER_SIZE);

	system_os_task(MQTT_Task, MQTT_TASK_PRIO, mqtt_procTaskQueue, MQTT_TASK_QUEUE_SIZE);
	system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)mqttClient);
}

WiFi连接成功和失败会触发不同的回调函数:

/**
 * 	MQTT连接回调
 */
void mqttConnectedCb(uint32_t *args) {
     
	MQTT_Client* client = (MQTT_Client*) args;

	INFO("MQTT: Connected\r\n");
	MQTT_Publish(client, birth_topic, BIRTH_MESSAGE, os_strlen(BIRTH_MESSAGE), 0,0);
	MQTT_Subscribe(client,ota_topic, 0);
	if(updata_status_check()){
     
		MQTT_Publish(client, ota_topic, "updata_finish", os_strlen("updata_finish"), 0,0);
	}
}
/**

 * 	MQTT断开连接回调
 */
void mqttDisconnectedCb(uint32_t *args) {
     
	MQTT_Client* client = (MQTT_Client*) args;
	INFO("MQTT: Disconnected\r\n");
}

/**
 * 	MQTT发布消息回调
 */
void mqttPublishedCb(uint32_t *args) {
     
	MQTT_Client* client = (MQTT_Client*) args;
	INFO("MQTT: Published\r\n");
}

最重要的是串口透传和OTA升级,当有数据从串口发来,会判断需要升级还是转发:

/**
 * 	MQTT接收数据回调(用于OTA升级和串口透传)
 */
void mqttDataCb(uint32_t *args, const char* topic, uint32_t topic_len,
		const char *data, uint32_t data_len) {
     
	char *topicBuf = (char*) os_zalloc(topic_len + 1), *dataBuf =
			(char*) os_zalloc(data_len + 1);

	uint8 *pdata = (uint8*)data;
	uint16 len = data_len;
	uart0_tx_buffer(pdata, len);//串口输出

	MQTT_Client* client = (MQTT_Client*) args;

	os_memcpy(topicBuf, topic, topic_len);
	topicBuf[topic_len] = 0;

	os_memcpy(dataBuf, data, data_len);
	dataBuf[data_len] = 0;

//	INFO("Receive topic: %s, data: %s \r\n", topicBuf, dataBuf);

	//data = {"url"="http://yourdomain.com:9001/ota/"}
	if (os_strcmp(topicBuf, ota_topic) == 0) {
     
		char url_data[200];
		if(get_josn_str(dataBuf,"url",url_data)){
     
//            INFO("ota_start\n");
            ota_upgrade(url_data,ota_finished_callback);
		}
	}

	os_free(topicBuf);
	os_free(dataBuf);



}

/**
 * 	ota升级回调
 */
void ICACHE_FLASH_ATTR ota_finished_callback(void * arg) {
     
	struct upgrade_server_info *update = arg;
	if (update->upgrade_flag == true) {
     
		INFO("OTA  Success ! rebooting!\n");
		system_upgrade_reboot();
	} else {
     
		INFO("OTA Failed!\n");
	}
}

三、其他。

在mqtt_config.h文件中定义了连接服务器的相关代码:
如果直接使用我的代码,可以直接将服务器的地址、端口、主题修改为自己的就行。MAC地址自动填充。可使用串口助手查看(上电打印)

#define MQTT_HOST			"103.14.xxx.xx"       //服务器地址
#define MQTT_PORT			9091                  //端口号
#define MQTT_BUF_SIZE		1024				//无需修改
#define MQTT_KEEPALIVE		120	            /*second*/

#define MQTT_USER			"admin"			//用户名
#define MQTT_PASS			"12345667"		//密码

#define OTA_TOPIC           "reboot/dev/action/%s"  //8266订阅主题  %s为MAC地址
#define LWT_TOPIC           "/lwt/%s"		//遗嘱消息
#define BIRTH_TOPIC         "reboot/dev/data/%s"		//发布主题

#define BIRTH_MESSAGE         "online"
#define LWT_MESSAGE         "offline"

#define MQTT_RECONNECT_TIMEOUT 	5	        /*second*/

#define DEFAULT_SECURITY	0
#define QUEUE_BUFFER_SIZE		 		2048

//#define PROTOCOL_NAMEv31	                /*MQTT version 3.1 compatible with Mosquitto v0.15*/
#define PROTOCOL_NAMEv311			        /*MQTT version 3.11 compatible with https://eclipse.org/paho/clients/testing/*/

#endif // __MQTT_CONFIG_H__

此处需要获取MAC地址填充主题的发布和订阅:

/**
 * 获取MAC
 */
void ICACHE_FLASH_ATTR get_mac(void) {
     

	u8 mac[6];
	wifi_get_macaddr(STATION_IF, mac);
	HexToStr(mac_str, mac, 6, 1);
	INFO("mac:%s\n", mac_str);

	os_sprintf(ota_topic,OTA_TOPIC,mac_str);
	os_sprintf(lwt_topic,LWT_TOPIC,mac_str);
    os_sprintf(birth_topic,BIRTH_TOPIC,mac_str);
}

上电等待初始化完成,按下D5(短接地),出现如图,说明进入配网模式,开始配网吧!
ESP8266串口透传+WiFi储存+OTA+smartconfig/airkiss_第1张图片
配网支持两种模式,分别是smartconfig(手机APP)和airkiss(微信)可关注安信可科技微信公众号。配网完成自动连接MQTT服务器,出现如下显示说明已连接服务器,便可以正常使用。
ESP8266串口透传+WiFi储存+OTA+smartconfig/airkiss_第2张图片
源码连接:https://download.csdn.net/download/dy_ngmm/12506762

你可能感兴趣的:(ESP8266,物联网,单片机,wifi)