ESP8266 Non-OS SDK開發探坑之七-簡單的TCP狀態上報客戶端
繼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(參看【】)。
要開啟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左右了。
如上圖配置即可完成泛域名解析。
程式碼見:
原部落格: