Overview
TCP is a connection-oriented reliable transport protocol, widely used for devices reporting data and receiving server commands. The Ai-WB2 implements TCP via lwIP socket (lwIP is a lightweight network library written for embedded devices; socket is the network programming interface it provides), and the official SDK wraps easy-to-use APIs like tcp_client_init/connect/send/receive. This tutorial demonstrates: connecting to a remote TCP server (the official demo server tt.ai-thinker.com:7878), sending data and looping to receive replies.
In plain words: TCP is like making a phone call — first dial (connect), wait for the other side to answer (three-way handshake), then talk (send and receive data), and finally hang up (disconnect). It's reliable: whether the words got through and the other side heard clearly are confirmed by both sides, and lost words are automatically re-sent — so important data like device reports and command pushes all use it. The price is that you must "dial through" before sending, and one phone line can only call one person.
This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version
release_bl_iot_sdk_1.6.40) exampleapplications/protocols/socket/tcp_client; the code can be found directly in your local SDK.
Open a terminal and enter the official tcp_client example project directory:
cd ~/Ai-Thinker-WB2/applications/protocols/socket/tcp_client
Note:
cdis the “change directory” command, entering the official example project; all subsequentmakecommands must run in this directory.
Open tcp_client/main.c and change the SSID/password at the top (same as Connect Wi-Fi), and modify the server address as needed:
#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"
//This is Ai-Thinker Remote TCP Server: http://tt.ai-thinker.com:8000/ttcloud
#define TCP_SERVER_IP "122.114.122.174"
#define TCP_SERVER_PORT 7878
| Macro | Default | Description |
|---|---|---|
TCP_SERVER_IP |
122.114.122.174 |
Ai-Thinker remote TCP server; no need to build your own server |
TCP_SERVER_PORT |
7878 |
Server port (the “door number” on the server); find the right door to connect |
💡 Without a public server you can debug over the LAN: run a network debugging assistant on your computer to listen (e.g.
192.168.x.x:8888), then changeTCP_SERVER_IP/PORTto your computer’s IP and port.
Open tcp_client/main.c. The full code for this step has been moved to the end of this page:
📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (
applications/protocols/socket/tcp_client/tcp_client/main.c).
Code highlights:
| Code | Purpose |
|---|---|
tcp_client_init(ip, port) |
Creates the TCP socket and initializes (wrapped in tcp_example.h); no creation, no phone call |
tcp_client_connect(socketfd) |
Connects to the server (returns 0 on success); the dialing — busy line fails |
tcp_client_send(socketfd, "hell tcp server") |
Sends data to the server; the “talking” in the call |
tcp_client_receive(socketfd, buf) |
Receives server data (blocking wait, returns byte count); blocking = keeps waiting when there’s no message |
strstr(tcp_buff, "close") |
Disconnects on receiving the close command; when the server says hang up, hang up |
tcp_client_deinit(socketfd) |
Closes the socket and frees resources; not freeing keeps occupying memory |
CODE_WIFI_ON_GOT_IP callback |
After networking succeeds, creates the TCP client task; no IP means the call won’t go through either |
💡 The wrapper APIs are implemented in the project’s
src/tcp_example.c; underneath they’re the standard lwIP socket:socket()/connect()/send()/recv()/close().
Build in the project directory:
make -j8
Note:
makeis the “build” command, turning code into firmware (the program flashed into the board) the board can run;-j8builds with 8 parallel cores, faster.
On success a firmware build_out/tcp_client.bin is generated.
Keep the board connected via USB, confirm the serial device, and flash:
make flash p=/dev/ttyUSB0 b=921600
Note:
make flashis the “flash” command, writing the compiled firmware into the board. Afterp=comes the serial device (often/dev/ttyUSB0on Linux,COM3-like on Windows — use your computer’s actual one),b=921600is the flash baud rate (serial transfer speed), keep the default.
⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode; wait for the progress bar to complete — that means the flash succeeded.
After flashing, the board automatically restarts and runs. Open a serial assistant (baud rate 921600, the “speech speed” of serial data — both ends must match) and observe the logs:
[APP] [EVT] GOT IP 5
tcp client task run
tcp_client_task:tcp client connect OK
tcp client send OK
After connecting to the official server, the server sends data back and the serial prints the received content:
tcp_client_task:tcp receive data:hello, WB2 !
tcp client connect OK and tcp client send OK appearing, plus receiving the server’s reply (tcp receive data:...) means success. If you only see GOT IP without connect OK, first confirm the board is online and the target server IP/port is reachable (try on your computer whether you can connect to this IP and port), see the FAQ at the end.
💡 LAN self-test: open a network debugging assistant on your computer to listen, same LAN via phone hotspot or router; change
TCP_SERVER_IP/TCP_SERVER_PORTto your computer’s IP and port; after flashing, the assistant receiveshell tcp server; send any string to the board and the serial prints the received content; sendingclosedisconnects (the serial stops sending/receiving).
API Summary for This Tutorial
tcp_client_init(server_ip, port)
Creates the TCP socket and initializes (project wrapper).
Parameters:
server_ip: server IP stringport: server port
Return: socket descriptor on success; -1 on failure
tcp_client_connect(socket_fd)
Connects to the TCP server (project wrapper).
Parameters:
socket_fd: socket descriptor returned bytcp_client_init
Return: 0 on success; negative error code on failure
tcp_client_send(socket_fd, data)
Sends data (project wrapper).
Parameters:
socket_fd: socket descriptordata: the data string to send
Return: bytes sent on success; <0 on failure
tcp_client_receive(socket_fd, data)
Receives data (blocking, project wrapper).
Parameters:
socket_fd: socket descriptordata: receive buffer
Return: bytes received on success; negative on failure
tcp_client_deinit(socket_fd)
Closes the socket and frees resources (project wrapper).
Parameters:
socket_fd: socket descriptor
Return: none
socket(domain, type, proto)
Creates a socket (the wrapper's underlying layer).
Parameters:
domain:AF_INET(IPv4)type:SOCK_STREAM(streaming TCP)proto: pass0
Return: socket descriptor on success; -1 on failure
connect(fd, addr, len)
Connects to the server (the wrapper's underlying layer).
Parameters:
fd: socket descriptoraddr: server address (struct sockaddr*)len: address length
Return: 0 on success; negative error code on failure
send / recv(fd, buf, len, flags)
Sends / receives data (the wrapper's underlying layer).
Parameters:
fd: socket descriptorbuf: data bufferlen: buffer lengthflags: flags, pass0
Return: bytes sent/received on success; negative on failure
pvPortMalloc / vPortFree(size / ptr)
FreeRTOS dynamic memory allocation / release.
Parameters:
size: bytes to allocateptr: pointer to free (return ofpvPortMalloc)
Return: pvPortMalloc: pointer on success, NULL on failure; vPortFree: none
📌 Pass the string directly when the server address is an IP; domain servers must first be resolved to an IP via DNS (see the official dns example).
Full Code
Below is the complete tcp_client/main.c source, identical to the official example (applications/protocols/socket/tcp_client/tcp_client/main.c):
📜 Click to expand the full tcp_client/main.c code
#include <FreeRTOS.h>
#include <task.h>
#include <stdio.h>
#include <string.h>
#include <blog.h>
#include <aos/yloop.h>
#include <aos/kernel.h>
#include <lwip/sockets.h>
#include <lwip/tcpip.h>
#include <wifi_mgmr_ext.h>
#include <cli.h>
#include <hal_wifi.h>
#include <lwip/init.h>
#include "tcp_example.h"
#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"
//This is Ai-Thinker Remote TCP Server: http://tt.ai-thinker.com:8000/ttcloud
#define TCP_SERVER_IP "122.114.122.174"
#define TCP_SERVER_PORT 7878
static wifi_conf_t conf = {
.country_code = "CN",
};
/**
* @brief wifi_sta_connect
* wifi station mode connect start
* @param ssid
* @param password
*/
static void wifi_sta_connect(char* ssid, char* password)
{
wifi_interface_t wifi_interface;
wifi_interface = wifi_mgmr_sta_enable();
wifi_mgmr_sta_connect(wifi_interface, ssid, password, NULL, NULL, 0, 0);
}
/**
* @brief tcp_client_task
*
* @param arg
*/
static void tcp_client_task(void* arg)
{
blog_info("tcp client task run\r\n");
int socketfd;
int ret = 0;
char* tcp_buff = pvPortMalloc(512);
memset(tcp_buff, 0, 512);
socketfd = tcp_client_init(TCP_SERVER_IP, TCP_SERVER_PORT);
if (!tcp_client_connect(socketfd)) {
blog_info("%s:tcp client connect OK\r\n", __func__);
}
else goto __exit;
if (tcp_client_send(socketfd, "hell tcp server")<0) {
printf("tcp client send fail\r\n");
goto __exit;
}
else
blog_info("tcp client send OK\r\n");
while (1) {
ret = tcp_client_receive(socketfd, tcp_buff);
if (ret>0) {
blog_info("%s:tcp receive data:%s \r\n", __func__, tcp_buff);
if (strstr(tcp_buff, "close")) goto __exit;
memset(tcp_buff, 0, 512);
}
vTaskDelay(100/portTICK_PERIOD_MS);
}
__exit:
vPortFree(tcp_buff);
tcp_client_deinit(socketfd);
vTaskDelete(NULL);
}
/**
* @brief event_cb_wifi_event
* wifi connet ap event Callback function
* @param event
* @param private_data
*/
static void event_cb_wifi_event(input_event_t* event, void* private_data)
{
static char* ssid;
static char* password;
switch (event->code)
{
case CODE_WIFI_ON_INIT_DONE:
{
printf("[APP] [EVT] INIT DONE %lld\r\n", aos_now_ms());
wifi_mgmr_start_background(&conf);
}
break;
case CODE_WIFI_ON_MGMR_DONE:
{
printf("[APP] [EVT] MGMR DONE %lld\r\n", aos_now_ms());
//_connect_wifi();
wifi_sta_connect(ROUTER_SSID, ROUTER_PWD);
}
break;
case CODE_WIFI_ON_SCAN_DONE:
{
printf("[APP] [EVT] SCAN Done %lld\r\n", aos_now_ms());
// wifi_mgmr_cli_scanlist();
}
break;
case CODE_WIFI_ON_DISCONNECT:
{
printf("[APP] [EVT] disconnect %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTING:
{
printf("[APP] [EVT] Connecting %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_CMD_RECONNECT:
{
printf("[APP] [EVT] Reconnect %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTED:
{
printf("[APP] [EVT] connected %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_PRE_GOT_IP:
{
printf("[APP] [EVT] connected %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_GOT_IP:
{
printf("[APP] [EVT] GOT IP %lld\r\n", aos_now_ms());
printf("[SYS] Memory left is %d Bytes\r\n", xPortGetFreeHeapSize());
//WiFi connection succeeded, create TCP client task
xTaskCreate(tcp_client_task, (char*)"tcp_client_task", 1024*2, NULL, 16, NULL);
}
break;
case CODE_WIFI_ON_PROV_SSID:
{
printf("[APP] [EVT] [PROV] [SSID] %lld: %s\r\n",
aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (ssid)
{
vPortFree(ssid);
ssid = NULL;
}
ssid = (char*)event->value;
}
break;
case CODE_WIFI_ON_PROV_BSSID:
{
printf("[APP] [EVT] [PROV] [BSSID] %lld: %s\r\n",
aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (event->value)
{
vPortFree((void*)event->value);
}
}
break;
case CODE_WIFI_ON_PROV_PASSWD:
{
printf("[APP] [EVT] [PROV] [PASSWD] %lld: %s\r\n", aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (password)
{
vPortFree(password);
password = NULL;
}
password = (char*)event->value;
}
break;
case CODE_WIFI_ON_PROV_CONNECT:
{
printf("[APP] [EVT] [PROV] [CONNECT] %lld\r\n", aos_now_ms());
printf("connecting to %s:%s...\r\n", ssid, password);
wifi_sta_connect(ssid, password);
}
break;
case CODE_WIFI_ON_PROV_DISCONNECT:
{
printf("[APP] [EVT] [PROV] [DISCONNECT] %lld\r\n", aos_now_ms());
}
break;
default:
{
printf("[APP] [EVT] Unknown code %u, %lld\r\n", event->code, aos_now_ms());
/*nothing*/
}
}
}
static void proc_main_entry(void* pvParameters)
{
aos_register_event_filter(EV_WIFI, event_cb_wifi_event, NULL);
hal_wifi_start_firmware_task();
aos_post_event(EV_WIFI, CODE_WIFI_ON_INIT_DONE, 0);
vTaskDelete(NULL);
}
void main()
{
puts("[OS] Starting TCP/IP Stack...\r\n");
tcpip_init(NULL, NULL);
puts("[OS] proc_main_entry task...\r\n");
xTaskCreate(proc_main_entry, (char*)"main_entry", 1024, NULL, 15, NULL);
}FAQ & Troubleshooting
⚠️ connect OK but send fails
Cause: the server disconnected proactively, network interrupted, or the sent data is too long (limited TCP buffer)
Fix: check the socket state before sending; keep single sends under 1KB; after failure tcp_client_deinit then reconnect
⚠️ receive never gets data
Cause: the server doesn't reply, or the peer's firewall blocks it
Fix: first confirm the server actually replies; for LAN tests turn off the computer's firewall and confirm the port is allowed
⚠️ LAN IP hardcoded in code, invalid after router restart
Cause: the computer's IP is dynamically assigned by DHCP
Fix: set a static IP on the computer; or use a domain on the server side (resolve via DNS first, see the official dns example)
⚠️ Insufficient memory (malloc fails)
Cause: task stack and buffers take too much
Fix: use the official 1024*2 task stack; keep the receive buffer at 512B if it suffices; monitor with xPortGetFreeHeapSize()
⚠️ Keeps printing Connecting, never GOT IP (can't connect to the router)
Cause: wrong SSID/password, the router is on 5GHz, or the signal is too weak
Fix: double-check ROUTER_SSID/ROUTER_PWD in tcp_client/main.c match the router exactly; confirm the router is 2.4GHz (the board doesn't support 5GHz); try moving the board closer to the router
⚠️ Serial device not found / can't open
Cause: USB-to-serial driver not installed, insufficient permission, or the cable only charges and can't transfer data
Fix: on Linux check the device with lsusb/dmesg; if permission denied run sudo chmod 666 /dev/ttyUSB0; on Windows install the driver and check the COM port in Device Manager; try a data-capable cable
⚠️ Flashing keeps waiting / fails
Cause: download mode wasn't entered, wrong baud rate, or a wrong serial number
Fix: press and hold EN during flashing to enter download mode as prompted; change p=/dev/ttyUSB0 to your actual serial port; try another USB port or cable
Self-Check
The serial prints tcp client connect OK and tcp client send OK, and receives the server's reply — the TCP client is verified.

