源碼github地址:https://github.com/linzhongpaihuai/smartplug
①燒錄方法:https://blog.csdn.net/u010177891/article/details/90348729
②esp8266實現http server服務詳解:https://blog.csdn.net/u010177891/article/details/100024710
③esp8266對接天貓精靈實現語音控制:https://blog.csdn.net/u010177891/article/details/100026511
④esp8266對接貝殼物聯平臺詳解:https://blog.csdn.net/u010177891/article/details/100058124
說明:
通過修改esp8266 RTOS的固件實現http服務器,這樣可以通過瀏覽器完成與esp8266的交互。看下做好的樣子:
代碼實現
1,啓動http服務器
for ( ; ; )
{
if ( uiCurStatus == STATION_GOT_IP && WEB_GetWebSvcStatus() == FALSE )
{
LOG_OUT(LOGOUT_INFO, "WEB_StartWebServerTheard.");
WEB_StartWebServerTheard();
}
if ( uiCurStatus != STATION_GOT_IP && WEB_GetWebSvcStatus() == TRUE )
{
LOG_OUT(LOGOUT_INFO, "WEB_StopWebServerTheard.");
WEB_StopWebServerTheard();
}
vTaskDelay( 1000/portTICK_RATE_MS );
}
當esp8266連接好wifi時進入for死循環,uiCurStatus 爲wifi的連接狀態,WEB_GetWebSvcStatus() 返回http server是否啓動。 WEB_StartWebServerTheard()會創建http server任務。 WEB_StopWebServerTheard()停止http server任務。該for死循環可以保證wifi斷開重連時重新啓動http server服務
創建http server任務,啓動WEB_WebServerTask任務
VOID WEB_StartWebServerTheard( VOID )
{
xTaskCreate(WEB_WebServerTask, "WEB_WebServerTask", 512, NULL, 4, &xWebServerHandle);
}
停止http server任務,通過設置bWebServerTaskTerminate爲true,在WEB_WebServerTask任務中判斷該標誌爲true時且響應完所有http請求後主動結束http server任務。
VOID WEB_StopWebServerTheard( VOID )
{
if ( xWebServerHandle != NULL )
{
bWebServerTaskTerminate = TRUE;
while( bWebServerTaskTerminate == TRUE )
{
LOG_OUT(LOGOUT_DEBUG, "web server task stop...");
vTaskDelay(1000/portTICK_RATE_MS);
}
}
WEB_SetWebSvcStatus( FALSE );
LOG_OUT(LOGOUT_INFO, "stop web server successed");
}
WEB_WebServerTask主要實現:
1,創建tcp socket等待tcp客戶端的連接
2,當有客戶端連接時啓動一個任務來響應請求,在請求完成時結束這個任務
3,當連接個數超過一定限制時等待其他連接釋放後才響應。至於爲什麼要限制連接個數,主要是因爲esp8266的RAM大小有限,除去系統本身佔用的大小實際可用也就四五十K。每響應一個連接就需要消耗10~20K所以要限制連接個數避免內存消耗完導致esp8266重啓
STATIC VOID WEB_WebServerTask( VOID *Para )
{
struct sockaddr_in stServerAddr;
struct sockaddr_in stClientAddr;
INT32 iClientAddrLen = sizeof( struct sockaddr_in );
INT32 iClientFd = -1;
INT32 iRet = 0;
INT32 Reuseaddr = 1;
INT32 iLoop = 0;
fd_set stFdRead;
struct timeval stSelectTimeOut = {1, 0};
struct timeval stRecvTimeOut = {1, 0};
LOG_OUT(LOGOUT_INFO, "WEB_WebServerTask started.");
bWebServerTaskTerminate = FALSE;
WEB_SetWebSvcStatus( TRUE );
memset(&stServerAddr, 0, sizeof(stServerAddr));
stServerAddr.sin_family = AF_INET;
stServerAddr.sin_addr.s_addr = INADDR_ANY;
stServerAddr.sin_len = sizeof(stServerAddr);
stServerAddr.sin_port = htons(80);
iSocketFd = socket( AF_INET, SOCK_STREAM, 0 );
if ( iSocketFd == -1 )
{
LOG_OUT(LOGOUT_ERROR, "socket failed started. iSocketFd:%d", iSocketFd);
}
LOG_OUT(LOGOUT_DEBUG, "socket ok, iSocketFd:%d.", iSocketFd);
//不知道爲什麼一直失敗
//iRet = setsockopt(iSocketFd, SOL_SOCKET, SO_REUSEADDR, (const char*)&Reuseaddr, sizeof(Reuseaddr));
//iRet = setsockopt(iSocketFd, SOL_SOCKET, SO_RCVTIMEO, (char *)&stRecvTimeOut, sizeof(stRecvTimeOut));
//if ( 0 != iRet )
//{
// LOG_OUT(LOGOUT_ERROR, "setsockopt failed, iRet:%d.", iRet);
//}
//LOG_OUT(LOGOUT_DEBUG, "setsockopt ok, iSocketFd:%d.", iSocketFd);
do
{
iRet = bind( iSocketFd, (struct sockaddr *)&stServerAddr, sizeof(stServerAddr));
if ( 0 != iRet )
{
LOG_OUT(LOGOUT_ERROR, "bind failed, iRet:%d.", iRet);
vTaskDelay(1000/portTICK_RATE_MS);
}
} while ( iRet != 0 );
LOG_OUT(LOGOUT_DEBUG, "bind ok, iSocketFd:%d.", iSocketFd);
do
{
iRet = listen( iSocketFd, WEB_MAX_FD );
if (iRet != 0)
{
vTaskDelay(1000/portTICK_RATE_MS);
}
} while ( iRet != 0 );
//LOG_OUT(LOGOUT_DEBUG, "listen ok, iSocketFd:%d.", iSocketFd);
FD_ZERO( &stFdRead );
FD_SET( iSocketFd, &stFdRead );
WEB_WebCtxInitAll();
HTTP_RouterInit();
for ( ;; )
{
if ( bWebServerTaskTerminate == TRUE )
{
goto end;
}
FD_ZERO( &stFdRead );
FD_SET( iSocketFd, &stFdRead );
//等待有新的客戶端連接
iRet = select( WEB_MAX_FD+1, &stFdRead, NULL, NULL, &stSelectTimeOut );
if ( iRet < 0 )
{
LOG_OUT(LOGOUT_ERROR, "select accept error, errno:%d, iRet:%d", errno, iRet);
vTaskDelay(1000/portTICK_RATE_MS);
continue;
}
else if ( 0 == iRet || errno == EINTR )
{
//LOG_OUT(LOGOUT_DEBUG, "WEB_WebServerTask, select timeout.");
vTaskDelay(100/portTICK_RATE_MS);
continue;
}
//LOG_OUT(LOGOUT_DEBUG, "WEB_WebServerTask, select ok, iRet:%d.", iRet);
//客戶端已滿,等待釋放
while (1)
{
for ( iLoop = 0; iLoop < WEB_MAX_FD; iLoop++ )
{
if ( stWebCtx[iLoop].iClientFd < 0 )
{
break;
}
}
//有其他客戶端的連接被釋放,退出等待開始處理請求
if ( iLoop < WEB_MAX_FD )
{
break;
}
LOG_OUT(LOGOUT_DEBUG, "connect full, fd num:%d", WEB_MAX_FD);
vTaskDelay(1000/portTICK_RATE_MS);
}
if ( FD_ISSET(iSocketFd, &stFdRead ))
{
//LOG_OUT(LOGOUT_DEBUG, "accept...");
//接受客戶端的連接
iClientFd = accept(iSocketFd, (struct sockaddr *)&stClientAddr, (socklen_t *)&iClientAddrLen);
if ( -1 != iClientFd )
{
FD_CLR(iSocketFd, &stFdRead);
for ( iLoop = 0; iLoop < WEB_MAX_FD; iLoop++ )
{
if ( stWebCtx[iLoop].iClientFd < 0 )
{
LOG_OUT(LOGOUT_DEBUG, "fd:%d connect", iClientFd);
stWebCtx[iLoop].iClientFd = iClientFd;
//UINT oldHeap = system_get_free_heap_size();
//啓動一個新任務來響應客戶端的請求
WEB_StartHandleTheard( &stWebCtx[iLoop] );
//UINT newHeap = system_get_free_heap_size();
//LOG_OUT(LOGOUT_DEBUG, "Free heap:%d, used:%d", newHeap, oldHeap-newHeap);
break;
}
}
}
else
{
LOG_OUT(LOGOUT_ERROR, "fd:%d, accept error", iClientFd);
}
}
}
end:
LOG_OUT(LOGOUT_INFO, "WEB_WebServerTask stopped.");
close( iSocketFd );
iSocketFd = -1;
bWebServerTaskTerminate = FALSE;
vTaskDelete( NULL );
}
啓動WEB_WebHandleTask任務處理http請求
VOID WEB_StartHandleTheard( VOID *Para )
{
xTaskCreate(WEB_WebHandleTask, "WEB_WebHandleTask", 512, Para, 3, &xWebHandle);
}
WEB_WebHandleTask任務實現:
1,接受客戶端發送過來的數據
2,解析htpp的請求頭,主要是method和url
3,根據method和url來匹配到具體的函數來進行處理請求
STATIC VOID WEB_WebHandleTask( VOID *Para )
{
CHAR* pcRecvBuf = NULL;
struct timeval stRdTimeOut = {1, 0};
fd_set stFdRead;
INT iRetN = 0;
INT32 iRet = 0;
HTTP_CTX *pstCtx = Para;
//LOG_OUT(LOGOUT_INFO, "fd:%d, WEB_WebHandleTask started", pstCtx->iClientFd);
pcRecvBuf = ( CHAR* )malloc( WEB_RECVBUF_SIZE + 10 );
if ( NULL == pcRecvBuf )
{
LOG_OUT(LOGOUT_ERROR, "malloc pcRecvBuf failed.");
goto end;
}
FD_ZERO( &stFdRead );
for ( ;; )
{
FD_SET( pstCtx->iClientFd, &stFdRead );
//等待接收數據
iRet = select( pstCtx->iClientFd + 1, &stFdRead, NULL, NULL, &stRdTimeOut );
if ( iRet < 0 )
{
LOG_OUT(LOGOUT_ERROR, "fd:%d, read error, errno:%d, iRet:%d.", pstCtx->iClientFd, errno, iRet);
goto end;
}
//等待接收超時
else if ( 0 == iRet )
{
pstCtx->uiCostTime ++;
//LOG_OUT(LOGOUT_DEBUG, "fd:%d, select timeout, uiCostTime:%d", pstCtx->iClientFd, pstCtx->uiCostTime);
//客戶端超過一定的時間沒有數據發送過來就斷開這個連接,避免資源佔用
if ( pstCtx->uiCostTime >= WEB_CONTINUE_TMOUT )
{
//LOG_OUT(LOGOUT_DEBUG, "fd:%d, recv timeout closed", pstCtx->iClientFd);
goto end;
}
continue;
}
if ( !FD_ISSET(pstCtx->iClientFd, &stFdRead ))
{
LOG_OUT(LOGOUT_ERROR, "fd:%d, stFdRead failed", pstCtx->iClientFd );
goto end;
}
FD_CLR( pstCtx->iClientFd, &stFdRead );
pstCtx->uiCostTime = 0;
//開始接受客戶端發送過來的數據
iRetN = recv( pstCtx->iClientFd, pcRecvBuf, WEB_RECVBUF_SIZE, 0 );
//數據接收出錯
if ( iRetN <= 0 )
{
LOG_OUT(LOGOUT_DEBUG, "fd:%d recv failed, client closed", pstCtx->iClientFd );
goto end;
}
pcRecvBuf[iRetN] = 0;
//LOG_OUT(LOGOUT_DEBUG, "recv:\r\n%s\r\n\r\n", pcRecvBuf);
//LOG_OUT(LOGOUT_DEBUG, "fd:%d, recv:%d", pstCtx->iClientFd, iRetN );
//解析http請求頭,method,url等等參數
iRet = HTTP_ParsingHttpHead( pstCtx, pcRecvBuf, iRetN );
if ( iRet != OK )
{
LOG_OUT(LOGOUT_INFO, "fd:%d Parsing http header failed", pstCtx->iClientFd );
goto end;
}
//LOG_OUT(LOGOUT_DEBUG, "fd:%d, ParsingHttpHead", pstCtx->iClientFd );
//根據http請求頭中的method和url來匹配對應的函數來處理
iRet = HTTP_RouterHandle( pstCtx );
if ( iRet != OK )
{
LOG_OUT(LOGOUT_INFO, "fd:%d Router handle failed", pstCtx->iClientFd );
goto end;
}
//LOG_OUT(LOGOUT_DEBUG, "fd:%d, HTTP_RouterHandle", pstCtx->iClientFd );
//判斷http響應是否完成,完成的話需要將數據全部清零等待下一個連接
if ( HTTP_IS_SEND_FINISH( pstCtx ) )
{
LOG_OUT(LOGOUT_INFO, "fd:%d, [Response] Method:%s URL:%s Code:%s",
pstCtx->iClientFd,
szHttpMethodStr[pstCtx->stReq.eMethod],
pstCtx->stReq.szURL,
szHttpCodeMap[pstCtx->stResp.eHttpCode]);
//初始化數據等待下一個連接
HTTP_RequestInit( pstCtx );
}
}
end:
//LOG_OUT(LOGOUT_INFO, "fd:%d, WEB_WebHandleTask over", pstCtx->iClientFd);
WEB_CloseWebCtx( pstCtx );
FREE_MEM( pcRecvBuf );
vTaskDelete( NULL );
}
解析的http的請求頭結構:
typedef struct tagHttpRequest
{
HTTP_METHOD_E eMethod; // GET, POST PUT DELETE
CHAR szURL[HTTP_URL_MAX_LEN]; // /index.html
HTTP_USERAGENT_E eUserAgent; // windows, Android,
CHAR szHost[HTTP_HOST_MAX_LEN]; // 192.168.0.102:8080
CHAR* pcRouter; //匹配到的Router
HTTP_PROCESS_E eProcess;
UINT uiRecvTotalLen; // 請求body體長度,不包括head長度
UINT uiRecvLen; // 已收到body的長度
UINT uiRecvCurLen; // 本次收到body的長度
CHAR* pcResqBody; // 請求體
}HTTP_REQ_S;
路由信息已在WEB_WebServerTask中註冊,HTTP_RouterHandle中會根據method和url來匹配到具體的函數來處理,如果匹配不到就返回404 Not Found
VOID HTTP_RouterInit( VOID )
{
HTTP_RouterMapInit();
HTTP_RouterRegiste(HTTP_METHOD_GET, "/", HTTP_GetHome, "home");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/health", HTTP_GetHealth, "HTTP_GetHealth");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/info", HTTP_GetInfo, "info");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/timer/:timer", HTTP_GetTimerData, "HTTP_GetTimerData");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/delay/:delay", HTTP_GetDelayData, "HTTP_GetDelayData");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/infrared/:infrared",
HTTP_GetInfraredData, "HTTP_GetInfraredData");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/infrared/:infrared/switch/:switch",
HTTP_GetInfraredValue, "HTTP_GetInfraredValue");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/system", HTTP_GetSystemData, "HTTP_GetSystemData");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/cloudplatform", HTTP_GetCloudPlatformData, "HTTP_GetCloudPlatformData");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/temperature", HTTP_GetTemperature, "HTTP_GetTemperature");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/html/header", HTTP_GetHtmlHeader, "HTTP_GetHtmlHeader");
HTTP_RouterRegiste(HTTP_METHOD_POST, "/html/header", HTTP_PostHtmlHeader,"HTTP_PostHtmlHeader");
HTTP_RouterRegiste(HTTP_METHOD_PUT, "/html/:html", HTTP_PutHtml, "HTTP_PutHtml");
HTTP_RouterRegiste(HTTP_METHOD_POST, "/timer", HTTP_PostTimerData, "HTTP_PostTimerData");
HTTP_RouterRegiste(HTTP_METHOD_POST, "/delay", HTTP_PostDelayData, "HTTP_PostDelayData");
HTTP_RouterRegiste(HTTP_METHOD_POST, "/infrared", HTTP_PostInfraredData, "HTTP_PostInfraredData");
HTTP_RouterRegiste(HTTP_METHOD_POST, "/system", HTTP_PostSystemData, "HTTP_PostSystemData");
HTTP_RouterRegiste(HTTP_METHOD_POST, "/cloudplatform", HTTP_PostCloudPlatformData, "HTTP_PostCloudPlatformData");
HTTP_RouterRegiste(HTTP_METHOD_POST, "/control", HTTP_PostDeviceControl, "HTTP_PostDeviceControl");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/scanwifi", HTTP_GetScanWifi, "HTTP_GetScanWifi");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/relaystatus", HTTP_GetRelayStatus, "HTTP_GetRelayStatus");
HTTP_RouterRegiste(HTTP_METHOD_POST, "/relaystatus", HTTP_PostRelayStatus, "HTTP_PostRelayStatus");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/date", HTTP_GetDate, "HTTP_GetDate");
HTTP_RouterRegiste(HTTP_METHOD_POST, "/date", HTTP_PostDate, "HTTP_PostDate");
HTTP_RouterRegiste(HTTP_METHOD_PUT, "/upgrade", HTTP_PutUpgrade, "HTTP_PutUpgrade");
HTTP_RouterRegiste(HTTP_METHOD_GET, "/upload", HTTP_GetUploadHtml, "HTTP_GetUploadHtml");
HTTP_FileListRegiste();
}
以http://192.168.1.101/health爲例
HTTP_RouterRegiste(HTTP_METHOD_GET, "/health", HTTP_GetHealth, "HTTP_GetHealth");
會匹配到HTTP_GetHealth函數,則執行HTTP_GetHealth函數:
UINT HTTP_GetHealth( HTTP_CTX *pstCtx )
{
UINT uiRet = 0;
pstCtx->stResp.eHttpCode = HTTP_CODE_Ok;
pstCtx->stResp.eContentType = HTTP_CONTENT_TYPE_Json;
pstCtx->stResp.eCacheControl = HTTP_CACHE_CTL_TYPE_No;
HTTP_Malloc(pstCtx, HTTP_BUF_1K);
//填充http響應頭
uiRet = HTTP_SetHeader( pstCtx );
if ( uiRet != OK )
{
LOG_OUT( LOGOUT_ERROR, "fd:%d, set header failed", pstCtx->iClientFd );
return FAIL;
}
//設置http的響應體爲‘{"health":true}’
uiRet = HTTP_SetResponseBody(pstCtx, "{\"health\":true}");
if ( uiRet != OK )
{
LOG_OUT( LOGOUT_ERROR, "fd:%d, set response body failed", pstCtx->iClientFd );
return FAIL;
}
//發送響應
uiRet = HTTP_SendOnce(pstCtx);
if ( uiRet != OK )
{
LOG_OUT( LOGOUT_ERROR, "fd:%d, send once failed", pstCtx->iClientFd );
return FAIL;
}
return OK;
}
用瀏覽器訪問health接口的返回樣例:
至此一個完整的請求 ~ 響應過程就完成了。