added WebSockets (#331)
This commit is contained in:
parent
f64935eb1d
commit
bce2139f06
16 changed files with 3810 additions and 1363 deletions
|
@ -1,10 +1,11 @@
|
|||
PROGRAM=http_server
|
||||
|
||||
#ESPBAUD=921600
|
||||
|
||||
EXTRA_CFLAGS=-DLWIP_HTTPD_CGI=1 -DLWIP_HTTPD_SSI=1 -I./fsdata
|
||||
|
||||
EXTRA_COMPONENTS=extras/httpd
|
||||
#Enable debugging
|
||||
#EXTRA_CFLAGS+=-DLWIP_DEBUG=1 -DHTTPD_DEBUG=LWIP_DBG_ON
|
||||
|
||||
EXTRA_COMPONENTS=extras/mbedtls extras/httpd
|
||||
|
||||
include ../../common.mk
|
||||
|
||||
|
|
|
@ -3,16 +3,15 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="css/siimple.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css">
|
||||
<link rel="shortcut icon" href="img/favicon.png">
|
||||
|
||||
<title>HTTP Server</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul class="navbar">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="websockets">WebSockets</a></li>
|
||||
<li><a href="about">About</a></li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -3,24 +3,23 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="css/siimple.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css">
|
||||
<link rel="shortcut icon" href="img/favicon.png">
|
||||
|
||||
<title>HTTP Server</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul class="navbar">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="websockets">WebSockets</a></li>
|
||||
<li><a class="active" href="about">About</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="grid main">
|
||||
<h1>About</h1>
|
||||
<p>This server is built on httpd from LwIP.</p>
|
||||
<p>This server is based on httpd from LwIP.</p>
|
||||
<p>To enable debugging compile with flags -DLWIP_DEBUG=1 -DHTTPD_DEBUG=LWIP_DBG_ON.</p>
|
||||
<p>For more info see <a href="http://www.nongnu.org/lwip/2_0_0/group__httpd.html">HTTP Server documentation</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
@ -3,16 +3,15 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="css/siimple.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css">
|
||||
<link rel="shortcut icon" href="img/favicon.png">
|
||||
|
||||
<title>HTTP Server</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul class="navbar">
|
||||
<li><a class="active" href="/">Home</a></li>
|
||||
<li><a href="websockets">WebSockets</a></li>
|
||||
<li><a href="about">About</a></li>
|
||||
</ul>
|
||||
|
||||
|
|
15
examples/http_server/fsdata/fs/js/smoothie_min.js
Normal file
15
examples/http_server/fsdata/fs/js/smoothie_min.js
Normal file
File diff suppressed because one or more lines are too long
130
examples/http_server/fsdata/fs/websockets.html
Normal file
130
examples/http_server/fsdata/fs/websockets.html
Normal file
|
@ -0,0 +1,130 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<link rel="stylesheet" type="text/css" href="css/siimple.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css">
|
||||
<link rel="shortcut icon" href="img/favicon.png">
|
||||
<title>HTTP Server</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul class="navbar">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a class="active" href="websockets">WebSockets</a></li>
|
||||
<li><a href="about">About</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="grid main">
|
||||
<h1>WebSockets Demo</h1>
|
||||
<div id="status_box" class="alert alert-info">Loading..</div>
|
||||
<p>This page is similar to the home page but uses WebSockets for real-time updates.</p>
|
||||
<div class="cover" align="center">
|
||||
<canvas id="chartCanvas" width="512" height="100"></canvas>
|
||||
<p/>
|
||||
<p>LED Control</p>
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="led-switch" onclick="gpio()">
|
||||
<label class="onoffswitch-label" for="led-switch">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Server Status</h1>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<td><b>Uptime:</b></td>
|
||||
<td id="uptime"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Free heap:</b></td>
|
||||
<td id="heap"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>LED state:</b></td>
|
||||
<td id="led"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1>How it works</h1>
|
||||
<p>This demo uses 2 WebScokets. Status parameters are streamed by the server in JSON format every 2 seconds.
|
||||
A <code>websocket_task</code> is created each time a specific URI is requested.</p>
|
||||
<p>ADC values are being continuously polled by the client (i.e. your browser).
|
||||
Each time a WebSocket frame is received on the server side, <code>websocket_cb</code> function is being called.</p>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="js/smoothie_min.js"></script>
|
||||
<script>
|
||||
var ws;
|
||||
var retries;
|
||||
var series = new TimeSeries();
|
||||
window.onload = function() {
|
||||
wsOpen();
|
||||
startPolling();
|
||||
}
|
||||
function setMsg(cls, text) {
|
||||
sbox = document.getElementById('status_box');
|
||||
sbox.className = "alert alert-" + cls;
|
||||
sbox.innerHTML = text;
|
||||
console.log(text);
|
||||
}
|
||||
function startPolling() {
|
||||
var chart = new SmoothieChart({millisPerPixel:11,grid:{fillStyle:'#ffffff',strokeStyle:'#ffffff',borderVisible:false},
|
||||
labels:{fillStyle:'#000000'},maxValue:1024,minValue:0});
|
||||
chart.addTimeSeries(series, {lineWidth:2,strokeStyle:'#03a9f4',fillStyle:'#f1f5fa'});
|
||||
chart.streamTo(document.getElementById("chartCanvas"), 500);
|
||||
setInterval(function() { wsWrite('A'); }, 500);
|
||||
}
|
||||
function onMessage(evt) {
|
||||
retries = 0;
|
||||
var dv = new DataView(evt.data);
|
||||
var val = dv.getUint16(0);
|
||||
if (val == 0xBEEF || val == 0xDEAD)
|
||||
console.log("LED switched");
|
||||
else
|
||||
series.append(new Date().getTime(), val);
|
||||
}
|
||||
function wsOpen() {
|
||||
if (ws === undefined || ws.readyState != 0) {
|
||||
if (retries)
|
||||
setMsg("error", "WebSocket timeout, retrying..");
|
||||
else
|
||||
setMsg("info", "Opening WebSocket..");
|
||||
ws = new WebSocket("ws://" + location.host);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.onopen = function(evt) { retries = 0; setMsg("done", "WebSocket is open."); };
|
||||
ws.onerror = function(evt) { setMsg("error", "WebSocket error!"); };
|
||||
ws.onmessage = function(evt) { onMessage(evt); };
|
||||
wsOpenStream();
|
||||
retries = 0;
|
||||
}
|
||||
}
|
||||
function wsOpenStream() {
|
||||
var uri = "/stream"
|
||||
var ws = new WebSocket("ws://" + location.host + uri);
|
||||
ws.onmessage = function(evt) {
|
||||
console.log(evt.data);
|
||||
var stats = JSON.parse(evt.data);
|
||||
console.log(stats);
|
||||
document.getElementById('uptime').innerHTML = stats.uptime + ' seconds';
|
||||
document.getElementById('heap').innerHTML = stats.heap + ' bytes';
|
||||
document.getElementById('led').innerHTML = (stats.led == 1) ? 'On' : 'Off';
|
||||
};
|
||||
}
|
||||
function wsWrite(data) {
|
||||
if (ws.readyState == 3 || retries++ > 5)
|
||||
wsOpen();
|
||||
else if (ws.readyState == 1)
|
||||
ws.send(data);
|
||||
}
|
||||
function gpio() {
|
||||
if (document.getElementById('led-switch').checked)
|
||||
wsWrite('E');
|
||||
else
|
||||
wsWrite('D');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load diff
|
@ -21,6 +21,28 @@ enum {
|
|||
SSI_LED_STATE
|
||||
};
|
||||
|
||||
int32_t ssi_handler(int32_t iIndex, char *pcInsert, int32_t iInsertLen)
|
||||
{
|
||||
switch (iIndex) {
|
||||
case SSI_UPTIME:
|
||||
snprintf(pcInsert, iInsertLen, "%d",
|
||||
xTaskGetTickCount() * portTICK_PERIOD_MS / 1000);
|
||||
break;
|
||||
case SSI_FREE_HEAP:
|
||||
snprintf(pcInsert, iInsertLen, "%d", (int) xPortGetFreeHeapSize());
|
||||
break;
|
||||
case SSI_LED_STATE:
|
||||
snprintf(pcInsert, iInsertLen, (GPIO.OUT & BIT(LED_PIN)) ? "Off" : "On");
|
||||
break;
|
||||
default:
|
||||
snprintf(pcInsert, iInsertLen, "N/A");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Tell the server how many characters to insert */
|
||||
return (strlen(pcInsert));
|
||||
}
|
||||
|
||||
char *gpio_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
|
||||
{
|
||||
for (int i = 0; i < iNumParams; i++) {
|
||||
|
@ -46,26 +68,89 @@ char *about_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcVal
|
|||
return "/about.html";
|
||||
}
|
||||
|
||||
int32_t ssi_handler(int32_t iIndex, char *pcInsert, int32_t iInsertLen)
|
||||
char *websocket_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
|
||||
{
|
||||
switch (iIndex) {
|
||||
case SSI_UPTIME:
|
||||
snprintf(pcInsert, iInsertLen, "%d",
|
||||
xTaskGetTickCount() * portTICK_PERIOD_MS / 1000);
|
||||
return "/websockets.html";
|
||||
}
|
||||
|
||||
void websocket_task(void *pvParameter)
|
||||
{
|
||||
struct tcp_pcb *pcb = (struct tcp_pcb *) pvParameter;
|
||||
|
||||
for (;;) {
|
||||
if (pcb == NULL || pcb->state != ESTABLISHED) {
|
||||
printf("Connection closed, deleting task\n");
|
||||
break;
|
||||
case SSI_FREE_HEAP:
|
||||
snprintf(pcInsert, iInsertLen, "%d", (int) xPortGetFreeHeapSize());
|
||||
}
|
||||
|
||||
int uptime = xTaskGetTickCount() * portTICK_PERIOD_MS / 1000;
|
||||
int heap = (int) xPortGetFreeHeapSize();
|
||||
int led = !gpio_read(LED_PIN);
|
||||
|
||||
/* Generate response in JSON format */
|
||||
char response[64];
|
||||
int len = snprintf(response, sizeof (response),
|
||||
"{\"uptime\" : \"%d\","
|
||||
" \"heap\" : \"%d\","
|
||||
" \"led\" : \"%d\"}", uptime, heap, led);
|
||||
if (len < sizeof (response))
|
||||
websocket_write(pcb, (unsigned char *) response, len, WS_TEXT_MODE);
|
||||
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when websocket frame is received.
|
||||
*
|
||||
* Note: this function is executed on TCP thread and should return as soon
|
||||
* as possible.
|
||||
*/
|
||||
void websocket_cb(struct tcp_pcb *pcb, uint8_t *data, u16_t data_len, uint8_t mode)
|
||||
{
|
||||
printf("[websocket_callback]:\n%.*s\n", (int) data_len, (char*) data);
|
||||
|
||||
uint8_t response[2];
|
||||
uint16_t val;
|
||||
|
||||
switch (data[0]) {
|
||||
case 'A': // ADC
|
||||
/* This should be done on a separate thread in 'real' applications */
|
||||
val = sdk_system_adc_read();
|
||||
break;
|
||||
case SSI_LED_STATE:
|
||||
snprintf(pcInsert, iInsertLen, (GPIO.OUT & BIT(LED_PIN)) ? "Off" : "On");
|
||||
case 'D': // Disable LED
|
||||
gpio_write(LED_PIN, true);
|
||||
val = 0xDEAD;
|
||||
break;
|
||||
case 'E': // Enable LED
|
||||
gpio_write(LED_PIN, false);
|
||||
val = 0xBEEF;
|
||||
break;
|
||||
default:
|
||||
snprintf(pcInsert, iInsertLen, "N/A");
|
||||
printf("Unknown command\n");
|
||||
val = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Tell the server how many characters to insert */
|
||||
return (strlen(pcInsert));
|
||||
response[1] = (uint8_t) val;
|
||||
response[0] = val >> 8;
|
||||
|
||||
websocket_write(pcb, response, 2, WS_BIN_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when new websocket is open and
|
||||
* creates a new websocket_task if requested URI equals '/stream'.
|
||||
*/
|
||||
void websocket_open_cb(struct tcp_pcb *pcb, const char *uri)
|
||||
{
|
||||
printf("WS URI: %s\n", uri);
|
||||
if (!strcmp(uri, "/stream")) {
|
||||
printf("request for streaming\n");
|
||||
xTaskCreate(&websocket_task, "websocket_task", 256, (void *) pcb, 2, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void httpd_task(void *pvParameters)
|
||||
|
@ -73,6 +158,7 @@ void httpd_task(void *pvParameters)
|
|||
tCGI pCGIs[] = {
|
||||
{"/gpio", (tCGIHandler) gpio_cgi_handler},
|
||||
{"/about", (tCGIHandler) about_cgi_handler},
|
||||
{"/websockets", (tCGIHandler) websocket_cgi_handler},
|
||||
};
|
||||
|
||||
const char *pcConfigSSITags[] = {
|
||||
|
@ -85,6 +171,8 @@ void httpd_task(void *pvParameters)
|
|||
http_set_cgi_handlers(pCGIs, sizeof (pCGIs) / sizeof (pCGIs[0]));
|
||||
http_set_ssi_handler((tSSIHandler) ssi_handler, pcConfigSSITags,
|
||||
sizeof (pcConfigSSITags) / sizeof (pcConfigSSITags[0]));
|
||||
websocket_register_callbacks((tWsOpenHandler) websocket_open_cb,
|
||||
(tWsHandler) websocket_cb);
|
||||
httpd_init();
|
||||
|
||||
for (;;);
|
||||
|
@ -110,5 +198,5 @@ void user_init(void)
|
|||
gpio_write(LED_PIN, true);
|
||||
|
||||
/* initialize tasks */
|
||||
xTaskCreate(&httpd_task, "HTTP Daemon", 1024, NULL, 2, NULL);
|
||||
xTaskCreate(&httpd_task, "HTTP Daemon", 128, NULL, 2, NULL);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue