【Starting with ESP8266 — Light a LED】
【Starting with ESP8266 (2)–Touch to control relay status-circuit design & electronic components selection】
【Starting with ESP8266(3) — Touch to control Relay-Programming & PCB design】
【Starting with ESP8266(4)–User parameters securely save & load on flash】
【Starting with ESP8266(5)–Simple HTTP configure server】
【Starting with ESP8266(6)–Simple TCP command server】
【Starting with ESP8266(7)–Simple TCP report client】
继TCP Server之后,推出TCP Client,用于完成信息的上报,其实这两个保留一个即可完成几乎全部功能,这里既然是造轮子,既然是探坑,就索性一并实现了。
至于只保留一个,如果是TCP Server,可以配备查询命令,即可返回所需要的传感器状态和数据。
而如果是TCP Client,在定时上报的时候服务端可以顺带返回指令,不过缺点是指令有延迟,必须等ESP8266上报的时候才能进行指令控制,实时性靠上报的频率决定。
TCP Client比TCP Server复杂点,需要远程服务器的链接信息,比如IP和Port,这里为了能支持的更泛一些,我也加入了DNS支持的方法,所以就显的繁琐了。首先,TCP Client连接的远程服务器信息需要提前配置,可以 用宏进行固定烧写,可以用上一篇的TCP Server进行配置,当然也可以用上上篇节的Web server 进行配置。这里采用web server 的serverconfig页面进行配置并保存到flash(参看【Starting with ESP8266(4)–User parameters securely save & load on flash】)。
要开启TCP Client,首先要连上wifi,否则client无法连接远程服务器,所以开启tcp client的命令需要在wifi连上的时候进行。
我把和wifi相关的部分行为放在了WifiManager.c文件中。主要是wifi AP、Station参数配置模式切换,并开启定时器对wifi状态轮询以便进行程序状态控制。wifi的断线重连 SDK有提供api
通过wifi_station_get_connect_status();函数查询状态,可以得到:
STATION_IDLE
STATION_CONNECTING
STATION_WRONG_PASSWORD
STATION_NO_AP_FOUND
STATION_CONNECT_FAIL
STATION_GOT_IP
这里主要是在STATION_GOT_IP状态下进行处理,自定义了一个回调,如果配置了该回调函数,那么当连上wifi的时候就执行该回调。
回调函数:
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 |
void ICACHE_FLASH_ATTR WifiStaConnCB(){ if(!TCPServOn){ TCPServInit(TCP_SERV_PORT); } if(!TCPClientOn){ if(stFlashProtParam.Domain){ struct ip_info stationIP; wifi_get_ip_info(STATION_IF,&stationIP); memcpy(tcpDNSTmp.local_ip,&stationIP.ip.addr,4);
connDNSTmp.type = ESPCONN_TCP; connDNSTmp.state = ESPCONN_NONE; connDNSTmp.proto.tcp = &tcpDNSTmp;
espconn_gethostbyname(&connDNSTmp,stFlashProtParam.RemoteAddr.Domain , &ipDNS, DNSFoundCB);
os_timer_disarm(&tmDNS); os_timer_setfn(&tmDNS, (os_timer_func_t *)DNSRetryTimerCB, &connDNSTmp); os_timer_arm(&tmDNS, 5000, 0); }else{ TCPClientInit(stFlashProtParam.RemoteAddr.IP.addr, stFlashProtParam.RemotePort); StationTimerInit(); } } } |
这里面判断了如果TCPServer或者TCP Client没开启就开启,其中TCP Client判断如果远端服务器是域名信息,则启动dns和dns状态控制定时器。
dns解析回调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
LOCAL void ICACHE_FLASH_ATTR DNSFoundCB(const char *name, ip_addr_t *ipAddr, void *arg) { struct espconn *pEspConn = (struct espconn *)arg; if (ipAddr == NULL) { DNSRetryCtn++; TRACE("domain dns not found\r\n"); return; }
DNSFound = true; TRACE("domain dns found "IPSTR"\n",IP2STR(ipAddr)); TCPClientInit(ipAddr->addr, stFlashProtParam.RemotePort); StationTimerInit(); } |
回调里判断是否成功获取到IP,如果是,则开启TCP Client,否则自增重试次数,在定时器回调里判断DNS状态,如果还未成功就重试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
LOCAL void ICACHE_FLASH_ATTR DNSRetryTimerCB(void *arg) { struct espconn *pEspConn = arg; TRACE("DNS retry\n"); if(DNSFound){ os_timer_disarm(&tmDNS); return; } espconn_gethostbyname(pEspConn, (char *)stFlashProtParam.RemoteAddr.Domain, &ipDNS, DNSFoundCB); if(DNSRetryCtn >= MAX_DNS_RETRY ){ system_os_post(TCPCOMM_TASK_PRIO, TSIG_DNS_FAILED, 0x00); DNSRetryCtn = 0; os_timer_disarm(&tmDNS); }else{ os_timer_arm(&tmDNS, 5000, 0); } } |
当TCP Client成功连接服务器后,开启StationTimer定时器,用来定时发送设备状态、传感器数据等。发送消息到任务队列排队发送。
1 2 3 4 |
void ICACHE_FLASH_ATTR StationTimerCB(){ system_os_post(TCPCOMM_TASK_PRIO, TSIG_REPORT,0x00); } |
而在任务队列中将消息收集好发送:
1 2 3 4 5 6 7 |
case TSIG_REPORT: if(!TCPClientOn) return; LOCAL char szSendBuf[64]; os_memset(szSendBuf,0,sizeof(szSendBuf)); os_sprintf(szSendBuf,"{\"relay\":\"%s\"}",RelayStatus?"off":"on"); TCPResponse(&connClient,szSendBuf,(uint16)os_strlen(szSendBuf)); break; |
相比前文的两个server,client的收发数据回调都不怎么做什么。如果需要服务端的确认消息,可以在接收回调里处理。
此外,由于web配置的时候讲过tcp client 连接服务器支持服务器域名格式,那么如果服务器是部署在局域网内,那么为了更方便进行配置部署,在局域网内部署一个DNS服务器,我用的dnsmasq,然后将路由器的DNS解析指向该服务器即可,我先是把cubieboard 上部署了dnsmasq,解析速度只需要不到1ms,但突然某天cubieboard硬件故障无法启动了,只好把路由器刷了老毛子固件,然后直接用路由器上的dnsmasq, 好在都支持泛域名解析,但是解析速度降到5ms左右了。
如上图配置即可完成泛域名解析。
代码见:
原博客:
http://www.straka.cn/blog/esp82666-7-tcp-report-client/