WIFI模块开发教程之W600网络篇3:STA模式下TCP Client通信

前言

本文研究如何在STA模式下进行TCP Client通信,STA模式是说模块直接连接AP(手机热点或者路由器),进入局域网中和其他无线设备通信,局域网中其他设备作为服务端,WIFI模块作为客户端。

一、理论基础

本节主要要处理的有一个问题:如何利用RT_Thread连接一个已知的AP,连上AP后,TCP Client程序和网络篇1中内容完全一致。

1.模块连接AP

路由器名字:HUAWEI-6ZCHWJ,密码:123456789a:。

rt_wlan_set_mode(RT_WLAN_DEVICE_AP_NAME, RT_WLAN_AP);
rt_wlan_start_ap("sand", "12345678");

2.Socket套接字实现TCP Client

为了编写一个可用的TCP Client demo,需要先了解套接字使用流程
在这里插入图片描述

3.要点说明

由于本篇和网络篇1很相似,本以为如前言中所说的,直接把模块起softAP,换成连网的就OK了,几分钟的事情,实际代码写起来加上调试的时间大大超出了预期,下面我说下,写STA模式下的TCP Client demo遇到的具体问题。

首先是如文章开始所说的,把模块起来softAP的API换成连接路由器的API,这个核心功能实现起来还是轻松无压力,1-2分钟就搞定了,但是替换了API后,总感觉不是一个实际项目该有的操作,于是开始考虑,是不是需要等到模块连网成功后才创建TCP Client Thread,进行和局域网下服务器的通信,细想来,的确需要,于是开始搜索API,有没有检测到系统网络状态的函数,发现也是在wlan_mgnt.h有个时间管理的接口:

rt_err_t  rt_wlan_register_event_handler(rt_wlan_event_t event, rt_wlan_event_handler handler, void *parameter);

在这里插入图片描述

有了注册函数,接下里就好办了,在连接路由器之前,首先注册回调函数,有连网成功回调、连网失败回调、断开连接回调,如下:
在这里插入图片描述
然后在连网成功回调中设置连网成功信号量,上图中在注册时间之后,调用连接到路由器的API函数,然后就是等待信号量,这样做的目的就是为了等系统连网成功后再进行之后的操作。

static void wifi_connect_callback(int event, struct rt_wlan_buff *buff, void *parameter)
{
    rt_kprintf("%s\n", __FUNCTION__);
    rt_sem_release(wait_sem);
}

心里想着做到这一步,应该差不多了吧,于是乎,代码用scons编译后,跑一波,我天,模块跑着跑着突然断线了然后可恶的是它竟然断线后不重连了,于是接着找API,发现有个自动重连的函数没有设置,果断加上,如下:

rt_wlan_config_autoreconnect(RT_TRUE);

由此可见一个稳定的项目,往往是大量测试后优化的结晶。

二、使用实例

1.程序

/*
 * Copyright (c) 2019 Winner Microelectronics Co., Ltd.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2019-02-13     tyx          first implementation
 */


#include <rtthread.h>
#include <rtdevice.h>
#include <sys/socket.h> //使用BSD socket需要包含此头文件

#define TCP_SERVER_ADDR "192.168.1.13"
#define TCP_SERVER_PORT 8089

static rt_sem_t wait_sem = NULL;

static void wifi_connect_callback(int event, struct rt_wlan_buff *buff, void *parameter)
{
    rt_kprintf("%s\n", __FUNCTION__);
    rt_sem_release(wait_sem);
}

static void wifi_disconnect_callback(int event, struct rt_wlan_buff *buff, void *parameter)
{
    rt_kprintf("%s\n", __FUNCTION__);
    if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info)))
    {
        rt_kprintf("ssid : %s\r\n", ((struct rt_wlan_info *)buff->data)->ssid.val);
    }
}

static void wifi_connect_fail_callback(int event, struct rt_wlan_buff *buff, void *parameter)
{
    rt_kprintf("%s\n", __FUNCTION__);
    if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info)))
    {
        rt_kprintf("ssid : %s\r\n", ((struct rt_wlan_info *)buff->data)->ssid.val);
    }
}

static void tcp_client_thread_entry(void *args)
{
    int ret = 0;
    int fd = -1;
    struct sockaddr_in server_addr;
    struct  timeval t;
    fd_set readfds;

    char buf[512] = { 0x00 };
    int len = 0;

reconnect:
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == fd)
    {
        rt_kprintf("create socket error!!!\r\n");
        goto exit;
    }
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(TCP_SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr( TCP_SERVER_ADDR );
    rt_memset(&server_addr.sin_zero, 0x00, sizeof(server_addr.sin_zero));

    ret = connect(fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr ));
    if (0 == ret)
    {
        rt_kprintf("connect success\r\n");
    }else
    {
        rt_kprintf("connect error!!!\r\n");
        goto label_try_reconnect;
    }
    
    t.tv_sec = 2;
    t.tv_usec = 0;

    while (1)
    {
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);

        ret = select(fd + 1, &readfds, 0, 0, &t);
        if (-1 == ret)
        {
            rt_kprintf("select() error!\r\n");
            goto label_try_reconnect;
        }
        // else if(0 == ret)
        // {
        //     rt_kprintf("select() timeout!\r\n");
        // }
        else if(ret > 0)
        {
            if (FD_ISSET(fd, &readfds));
            {
                len = recv(fd, buf, sizeof(buf), 0);
                if (len > 0)
                {
                    buf[len] = 0x00;
                    rt_kprintf("receive data:%s\r\n", buf);
                }else
                {
                    rt_kprintf("receive data from tcp server error!\r\n");
                    goto label_try_reconnect;
                }

                if (-1 != fd)
                {
                    rt_sprintf(buf, "%s\r\n", buf);
                    ret = send(fd, buf, strlen(buf), 0);
                    if (ret < 0)
                    {
                        rt_kprintf("send error, closee socket");
                        goto label_try_reconnect;
                    }
                }
            }
        }
    }
label_try_reconnect:
    if (-1 != fd)
    {
        closesocket(fd);
    }
    rt_thread_mdelay(1000);
    goto reconnect;

exit:
    if (-1 != fd)
    {
        closesocket(fd);
    }
    rt_kprintf("thread tcp_client exit!\r\n");
}

int main(void)
{
    rt_err_t ret = RT_EOK;
    char str[] = "hello world!\r\n";

    // 创建一个动态信号量,初始值为0
    wait_sem = rt_sem_create("sem_conn", 0, RT_IPC_FLAG_FIFO);

    /* Start automatic connection */
    rt_wlan_config_autoreconnect(RT_TRUE);

    // register event
    ret = rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED, wifi_connect_callback, RT_NULL);
    if (0 != ret)
    {
        rt_kprintf("register event handler error!\r\n");
    }
    ret = rt_wlan_register_event_handler(RT_WLAN_EVT_STA_DISCONNECTED, wifi_disconnect_callback, RT_NULL);
    if (0 != ret)
    {
        rt_kprintf("register event handler error!\r\n");
    }
    ret = rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED_FAIL, wifi_connect_fail_callback, RT_NULL);
    if (0 != ret)
    {
        rt_kprintf("register event handler error!\r\n");
    }

    // connect to router
    rt_wlan_set_mode(RT_WLAN_DEVICE_STA_NAME, RT_WLAN_STATION);
    rt_wlan_connect("HUAWEI-6ZCHWJ", "123456789a");
    rt_kprintf("start to connect ap ...\n");

    // wait until module connect to ap success
    ret = rt_sem_take(wait_sem, RT_WAITING_FOREVER);
    if (0 != ret)
    {
        rt_kprintf("wait_sem error!\r\n");
    }
    rt_kprintf("connect to ap success!\r\n");

    //create client
    rt_thread_t client_thread = rt_thread_create("tcp_client", tcp_client_thread_entry, RT_NULL, 4*1024, 25, 10);
    if (client_thread != NULL)
    {
        rt_thread_startup(client_thread);
    }else
    {
        ret = RT_ERROR;
        rt_kprintf("create tcp client error!!!");
    }

exit:
    rt_sem_delete(wait_sem);
    return ret;
}

2.配置

在applications目录下新建一个文件夹:5-sta_tcp_client,直接输入scons会提示编译错误,此时需要修改aplications/SConscript脚本。

Import('RTT_ROOT')
Import('rtconfig')
from building import *

cwd = GetCurrentDir()
src  = Glob('5-sta_tcp_client/main.c')
CPPPATH = [cwd]

group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH)

Return('group')

三、下载运行

在ENV控制台,输入scons命令,在build/Bin目录下生成rtthread_1M.FLS,

烧录运行后,电脑连接模块起来的热点,然后打开电脑网络调试助手,开启一个TCP Server,IP地址是电脑的IP地址,端口为8089,注意如果填错,网络助手是无法开启TCP Server的,设备连接到TCP Server后,通过网络助手发送hello world,模块收到数据后,会加上"\r\n",然后返回给网络助手。

网络助手界面如下:
在这里插入图片描述
模块调试串口信息如下:
在这里插入图片描述

四、结语

1.总结:

本节完,实际操作过程中需要注意的地方有如下几点:

(1) 注意电脑连接模块后会得到一个IP地址,可以通过在cmd控制台中输入ipconfig
在这里插入图片描述
本文得到电脑IP为:192.168.1.13,因此电脑连接模块热点后,打开网络助手,需要设置TCP Server地址为192.168.1.13, Port端口可以自行定义,本文使用8089。

(2) 延时纠错

TCP Client重连的时候,rt_err_t rt_thread_sleep(rt_tick_t tick)的单位是tick不是秒,本代码已修改为rt_thread_mdelay(1000);

(3) 网络状态

本篇中没有针对网络状态做过多处理,实际项目使用中,需要根据注册的事件回调函数更新网络状态,一般断网的时候应该指示灯熄灭,连接成功的时候常亮,配网和连网过程中闪烁,关于配网在之后的文章中会有;再次提醒下,千万不要在int main(void)之后调用解除注册函数,释放连网前注册的函数,笔者之前不小心犯过这个错误,导致连网后网络状态变化,模块实际未同步给板卡,导致客户MCU控制的板卡还以为是正常在线的。解除注册接口如下:rt_err_t rt_wlan_unregister_event_handler(rt_wlan_event_t event);对于其他资源需要酌情考虑是否需要删除,本篇中的连网等待信号量可以删除,回收系统资源:rt_sem_delete(wait_sem)。

2.资料获取

如您在使用过程中有任何问题,请加QQ群进一步交流。

QQ交流群:906015840 (备注:物联网项目交流)

公众号:物联网客栈,扫码关注,回复w600即可。
在这里插入图片描述
一叶孤沙出品:一沙一世界,一叶一菩提

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

物联网客栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值