added WebSockets (#331)

This commit is contained in:
lujji 2017-02-02 06:17:53 +00:00 committed by Ruslan V. Uss
parent f64935eb1d
commit bce2139f06
16 changed files with 3810 additions and 1363 deletions

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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>

File diff suppressed because one or more lines are too long

View 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

View file

@ -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);
}