项目整体思路比较简单, W801 上电后读取内部 flash 中保存在固定位置的 Wifi 账号密码, 同时开始蓝牙, 每当蓝牙接收到命令后, 就对其进行解析, 如果为连接 wifi 命令, 则断开当前 wifi, 并进行新的 wifi 连接, 连接成功后将账号密码更新至内部 flash 中固定位置, 用于下次上电连接. 大致的流程图如下:
(1) 使用说明: 本代码使用的基本都是基于 SDK 中代码进行的修改
(2) 主程序:
void UserMain (void)
{
printf ("\n-----------------------PROJECT Version1. 6. 5---------------------------\n") ;
printf ("\n-------------------------------BLE Start--------------------------------\n") ; //这里在 wm_main 中开启了蓝牙任务, 可以参考官方提供的蓝牙说明书
tls_os_time_delay (1000) ;
printf ("\n-------------------------------WiFi Start--------------------------------\n") ;
mqtt_start () ;
get_wifi_params (ssid, pwd) ; //获取内部 flash 中保存的最近一次成功连接的 wifi 账号密码
wifi_connect_net (ssid, pwd) ; //进行 wifi 连接, 这个使用的就是 demo 中的链接函数, 做了一点修改
tls_os_time_delay (3000) ;
printf ("\n-------------------------------APP Start--------------------------------\n") ;
tls_os_task_create (app_handle, "app", app_task,
NULL, (void *) app_task_stk,
APP_TASK_SIZE * sizeof (u32) ,
APP_TASK_PRIO, 0) ; //主任务
}
(3) 蓝牙命令处理的铺垫: 要实现蓝牙命令的解析, 首先要知道蓝牙接受到命令后在那个位置保存, 这个很多大佬已经进行了分享, 我这里再简单说明下如下图所示, 为了方便后续更多蓝牙命令的处理因此, 我单独创建了一个 ble_cmd_excute 函数用于命令解析执行.
(4) 蓝牙命令的定义: 为了我后续添加更多的功能, 因此我采用的 Json 格式来定义蓝牙命令, 具体格式如下:
{"method": "xxxx", "params": {"param1": "xxxxx", "param2": "xxxxx"}}
其中 method 代表的是具体的命令类型, params 则是该命令的参数, 因为后续命令可能带有很多参数, 因此 params 的键值设置了一个 Json 体方便填入更多参数, 例如本次 wifi 连接接用到的命令为:
{"method": "wifi_connect", "params": {"ssid": "xxxxx", "pwd": "xxxxx"}}
(5) 蓝牙处理函数代码解析:
/**
* @brief 蓝牙命令解析
*/
int ble_cmd_excute (char *cmd)
{
/*解析 Json 字符串
* root: 下行命令解析得到的 JSON 体
* str_method: 解析后得到的下传方法名
* json_params: 解析后得到的下传参数 JSON 体
* json_p1: 解析后得到第一个参数的 JSON 体
* json_p2: 解析后得到第二个个参数 JSON 体
*
*/
cJSON *root = 0;
cJSON *json_params, *json_p1, *json_p2;
char *str_method;
char temp[100];
root = cJSON_Parse (cmd) ;
if (! root)
{
BLE_CMD_DEBUG ("[INFO]: JSON 格式错误: %s\r\n", cJSON_GetErrorPtr () ) ;
}
else
{
//解析命令
str_method = cJSON_GetObjectItem (root, "method") - valuestring;
json_params = cJSON_GetObjectItem (root, "params") ;
//BLE_CMD_DEBUG ("str_method: %s\n", str_method) ;
//BLE_CMD_DEBUG ("%s\n", cJSON_Print (json_params) ) ;
if (strcmp (str_method, "wifi_connect") == 0) //先解析 method, 判断是否是进行 wifi 链接的命令, 如果是再调用 ble_set_wifi 命令进行具体参数解析以及操作
{
ble_set_wifi (json_params) ;
}
tls_os_time_delay (1000) ;
ble_send_rel () ;
}
return WM_SUCCESS;
}
/**
* @brief 解析完成返回数据
*/
int ble_send_rel (void)
{
char msg[] = "EXCUTE FINISHED";
tls_ble_server_demo_api_send_msg ( (uint8_t *) msg, strlen (msg) ) ; //通过 indicate 方法返回一个解析完成的反馈给手机
return WM_SUCCESS;
}
/**
* @brief 蓝牙配网
*/
int ble_set_wifi (cJSON *json_params)
{
cJSON *json_p1, *json_p2;
//首先解析参数得到 wifi 账号密码
json_p1 = cJSON_GetObjectItem (json_params, "ssid") ;
char *wifi_ssid = (int) json_p1- valuestring;
json_p2 = cJSON_GetObjectItem (json_params, "pwd") ;
char *wifi_pwd = json_p2- valuestring;
BLE_CMD_DEBUG ("wifi_ssid: %s wifi_pwd: %s\n", wifi_ssid, wifi_pwd) ;
//清空 ssid 和 pwd
memset (ssid, 0, WIFI_PARAMS_LEN) ;
memset (pwd, 0, WIFI_PARAMS_LEN) ;
strcpy (ssid, wifi_ssid) ;
strcpy (pwd, wifi_pwd) ;
//连接 wifi
wifi_connect_net (ssid, pwd) ;
return WM_SUCCESS;
}
(6) wifi 函数处理: 这一部分基本就是 SDK 自带的 demo 我做了一定的修改, 也就是注释部分
int wifi_connect_net (char *ssid, char *pwd)
{
struct tls_param_ip *ip_param = NULL;
u8 wireless_protocol = 0;
if (! ssid)
{
return WM_FAILED;
}
printf ("\n[INFO]: ssid: %s\n", ssid) ;
printf ("[INFO]: password=%s\n", pwd) ;
tls_wifi_disconnect () ;
tls_os_time_delay (500) ; //这里要添加一个延时, 我个人测试了几次发现不添加延时偶尔会出现还没有成功断开上一个 wifi 连接就开始下一个 wifi 连接了
tls_param_get (TLS_PARAM_ID_WPROTOCOL, (void *) &wireless_protocol, TRUE) ;
if (TLS_PARAM_IEEE80211_INFRA ! = wireless_protocol)
{
tls_wifi_softap_destroy () ;
wireless_protocol = TLS_PARAM_IEEE80211_INFRA;
tls_param_set (TLS_PARAM_ID_WPROTOCOL, (void *) &wireless_protocol, FALSE) ;
}
tls_wifi_set_oneshot_flag (0) ;
ip_param = tls_mem_alloc (sizeof (struct tls_param_ip) ) ;
if (ip_param)
{
tls_param_get (TLS_PARAM_ID_IP, ip_param, FALSE) ;
ip_param- dhcp_enable = TRUE;
tls_param_set (TLS_PARAM_ID_IP, ip_param, FALSE) ;
tls_mem_free (ip_param) ;
}
tls_netif_add_status_event (con_net_status_changed_event) ;
int ret = tls_wifi_connect ( (u8 *) ssid, strlen (ssid) , (u8 *) pwd, strlen (pwd) ) ;
//printf ("ret = %d\n", ret) ;
printf ("[INFO]: 等待网络连接中\n") ;
return WM_SUCCESS;
}
static void con_net_status_changed_event (u8 status )
{
switch (status)
{
case NETIF_WIFI_JOIN_SUCCESS:
printf ("[INFO]: WIFI 网络连接成功\n") ;
wifi_status_led (0) ; //led 点亮用于指示 wifi 连接成功, 这个可以自由修改
clear_wifi_params () ; //清空 wifi 所在内部 flash 存储区域
save_wifi_params () ; //将成功连接的 wifi 账号密码保存至 flash
break;
case NETIF_WIFI_JOIN_FAILED:
printf ("[INFO]: WIFI 网络连接失败\n") ;
wifi_status_led (1) ;
break;
case NETIF_WIFI_DISCONNECTED:
printf ("[INFO]: WIFI 网络已断开连接\n") ;
wifi_status_led (1) ;
break;
case NETIF_IP_NET_UP:
{
struct tls_ethif *tmpethif = tls_netif_get_ethif () ;
print_ipaddr (&tmpethif- ip_addr) ;
//如果有 IPV6, 在 demo/wm_demo. h 里面打开 TLS_CONFIG_IPV6
#if TLS_CONFIG_IPV6
print_ipaddr (&tmpethif- ip6_addr[0]) ;
print_ipaddr (&tmpethif- ip6_addr[1]) ;
print_ipaddr (&tmpethif- ip6_addr[2]) ;
#endif
}
break;
default:
//printf ("UNKONWN STATE: %d\n", status) ;
break;
}
}
(7) 内部 flash 存储: 这部分基本可以参考 demo 的读写函数, 不多赘述, 代码如下
/**
* @brief 清空 WIFI 链接参数 Flash 空间
*/
int clear_wifi_params (void)
{
u8 write_buf[WIFI_PARAMS_LEN];
memset (write_buf, 0, WIFI_PARAMS_LEN) ;
tls_fls_write (0x1F0303, (u8 *) write_buf, WIFI_PARAMS_LEN) ;
tls_fls_write (0x1F0303+WIFI_PARAMS_LEN, (u8 *) write_buf, WIFI_PARAMS_LEN) ;
return WM_SUCCESS;
}
/**
* @brief 保存 WIFI 链接参数
*/
int save_wifi_params (void)
{
tls_fls_write (0x1F0303, (u8 *) ssid, WIFI_PARAMS_LEN) ;
tls_fls_write (0x1F0303+WIFI_PARAMS_LEN, (u8 *) pwd, WIFI_PARAMS_LEN) ;
return WM_SUCCESS;
}
/**
* @brief 读取 WIFI 链接参数
*/
int get_wifi_params (char *temp_ssid, char *temp_pwd)
{
u8 *read_buf;
read_buf = tls_mem_alloc (WIFI_PARAMS_LEN) ;
if (NULL == read_buf)
{
printf ("\nmalloc read buf error\n") ;
return WM_FAILED;
}
memset (read_buf, 0, WIFI_PARAMS_LEN) ;
tls_fls_read (0x1F0303, read_buf, WIFI_PARAMS_LEN) ;
strcpy (temp_ssid, read_buf) ;
//printf ("ssid: %s\n", (char *) read_buf) ;
memset (read_buf, 0, WIFI_PARAMS_LEN) ;
tls_fls_read (0x1F0303+WIFI_PARAMS_LEN, read_buf, WIFI_PARAMS_LEN) ;
strcpy (temp_pwd, read_buf) ;
//printf ("pwd: %s\n", (char *) read_buf) ;
tls_mem_free (read_buf) ;
return WM_SUCCESS;
}
(1) 手机端使用的软件: nRF_connect
(2) 使用蓝牙发送的需要注意事项: 因为蓝牙默认的 MTU (最大交换数据量) 为 18, 而我的蓝牙命令比较长, 因此必须要 request MTU 让蓝牙和 W801 进行 MTU 的交换, 最后会取二者中小的, w801 最大的 MTU 为 256, 如下图所示:
(3) 试验结果:
(1) 这个小 demo 中遇到过的问题, 我都在问答社区中有提问过, 感谢回答的大佬们的解答, 如果遇到相似的问题, 可以点击下面的链接进行参考.
W801, 使用蓝牙接受命令更新 wifi 账号密码并重连失败
w801 使用过程中遇到的 3 个问题
(2) 后续可以进行的优化: 这里的 wifi 链接参数, 我保存在了内部 flash 中, 内部 flash 空间比较紧张, 且偶尔会出现乱码的问题, 应该是程序运行多了之后, 某些地方被覆盖了. 因此最好后续保存在外部 flash, 或者用 fatfs 挂载一个 SD 卡, 用. txt 文件保存, 类似于 esp32.
(3) 最后还是要感谢下, 问答社区和群里的大佬的热心解答.