Author: Villivateur Von from CAE, NUAA.
First published by 苏瑞辅的网络日志.
All rights reserved. Please contact the Author for reposting.

Background

I am now learning something about socket programming and hardware development. And then it occurs to me that an IoT device controled by smart phone could be exciting. This passage only show one of the smallest steps of the project, yet, the most important one. Now I can control the LED via Arduino, but the same can be applied to the remote control of any smart device.

I used to use a cloud platform called BigIoT. Powerful this platform is, but for my project it is too bloated. And I am also want to try my own development capabilities, so this time I do this by myself.

Principle

如果服务器与客户端处于同一子网内,数据传输相对容易一点,但是,当前的IPv4地址不够用,很多的客户端都是通过NAT转换连接到公网的,这就使得服务器不能直接向内网客户端发送数据。方法总是有的,由于客户端可以直接向公网服务器发送数据,而服务器在接收到数据之后,是可以向客户端回复一个数据的,这样一来,“回复”,便可以理解为服务器向内网客户端发送了数据。

Hardware Needed

  1. Arduino Nano
  2. ESP8266 Wi-Fi Module

Control Process

首先,Arduino向ESP8266发送请求连接远程服务器(TCP),在200ms延时内得到回复后,继续请求发送TCP数据包,而后传输数据(我这里只发送了一个“6”)。如果上面任意一步出现错误,错误指示灯将亮起,并且自动重新连接。如果连接无误,Arduino将会收到服务器回复的信息,“1”或“0”。这里我用“1”代表亮起被控制的LED,“0”代表熄灭它。

配合服务端的Socket程序,便可以实现简单的远程通讯。

Pictures

Real Products

Real Products

The Server Terminal

The Server Terminal

Source Code

The Arduino Client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*
2018/1/5
@Villivateur Von
基于ESP8266和Arduino的Socket内网穿透控制客户端
*/
#include <Servo.h>

#define ErrorLED 12 //错误指示灯
Servo myservo;//控制舵机

void setup() {
Serial.begin(115200);

myservo.attach(9);

pinMode(ErrorLED, OUTPUT);

digitalWrite(ErrorLED, 0);//错误指示灯置0
delay(6000);//ESP8266启动预留时间

Serial.print("ATE0\r\n");//关闭回显
delay(300);
}

char msg[64];//用于存放ESP8266回传信息

void loop() {
int msglen = 0;//用于存放回传信息长度

Serial.print("AT+CIPCLOSE\r\n");//为防止前一个TCP连接仍存在而导致错误,先清空链连接
delay(100);
recvmsg();//清空缓冲区

Serial.print("AT+CIPSTART=\"TCP\",\"192.168.27.2\",10081\r\n");//建立TCP连接
delay(300);
msglen = recvmsg();
if (msg[0] == 'C' && msg[4] == 'E' && msg[msglen - 3] == 'K') {//比较回传信息(基于ESP8266回传信息特性),是否已经成功建立连接,若否,错误指示灯亮
digitalWrite(ErrorLED, 0);
while (1) {
Serial.print("AT+CIPSEND=1\r\n");//发送数据
delay(100);
msglen = recvmsg();
if (msg[2] == 'O' && msg[3] == 'K') {//同上比较
digitalWrite(ErrorLED, 0);
Serial.print("6");//发送一个“6”(测试用)
delay(300);
msglen = recvmsg();

if (msg[2] == 'R' && msg[7] == '1' && msg[18] == 'S' && msg[23] == 'O' && msg[34] == '1') {//同上比较
digitalWrite(ErrorLED, 0);
if (msg[msglen - 1] == '1') myservo.write(0);
if (msg[msglen - 1] == '0') myservo.write(140); //根据回传数据控制
}
else {
digitalWrite(ErrorLED, 1);
break;
}

}
else {
digitalWrite(ErrorLED, 1);
break;
}
}
}
else digitalWrite(ErrorLED, 1);
}

//接收ESP8266数据的函数,返回值为数据长度
int recvmsg() {
int i = 0;
while (Serial.available()) {
msg[i] = (char)Serial.read();
i++;
}
return i;
}

The Server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define MYPORT 10081
#define QUEUE 20
#define BUFFER_SIZE 1024

int main()
{
while (1)
{
int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);


struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(MYPORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
{
perror("bind");
}

///listen,成功返回0,出错返回-1
if(listen(server_sockfd,QUEUE) == -1)
{
perror("listen");
}

///客户端套接字
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);

///成功返回非负描述字,出错返回-1
int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);
if(conn<0)
{
perror("connect");
}

while(1)
{
memset(buffer,0,sizeof(buffer));
int len = recv(conn, buffer, sizeof(buffer),0);
//printf("recive message from client:\n");
//fputs(buffer,stdout);
if (buffer[0]!='6') break;
//printf("\n");
FILE *ctrl=fopen("ctrl.in","r");
buffer[0]=fgetc(ctrl);
fclose(ctrl);
//buffer[0]='1';
//printf("Send messsge to client:\n");
//fputs(buffer,stdout);
//printf("\n");
send(conn, buffer, len, 0);
}
close(conn);
close(server_sockfd);
}
return 0;
}

P.S.

  1. 如果服务端回复的数据由另一个客户端提供(比如手机),那么就可以实现手机控制Arduino的行为了。
  2. 本人学识短浅,所采用的方法必定不是最优,还望各位大佬批评指正。