Add AsyncWebSocket plugin

This commit is contained in:
Me No Dev 2016-04-23 15:11:32 +03:00
parent d65e4bd4e5
commit 8d67809acf
8 changed files with 1355 additions and 668 deletions

View File

@ -110,6 +110,70 @@ class SPIFFSEditor: public AsyncWebHandler {
// SKETCH BEGIN
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if(type == WS_EVT_CONNECT){
os_printf("ws[%s][%u] connect\n", server->url(), client->id());
client->printf("Hello Client %u :)", client->id());
client->ping();
} else if(type == WS_EVT_DISCONNECT){
os_printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
} else if(type == WS_EVT_ERROR){
os_printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
} else if(type == WS_EVT_PONG){
os_printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
} else if(type == WS_EVT_DATA){
AwsFrameInfo * info = (AwsFrameInfo*)arg;
if(info->final && info->index == 0 && info->len == len){
//the whole message is in a single frame and we got all of it's data
os_printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
if(info->opcode == WS_TEXT){
data[len] = 0;
os_printf("%s\n", (char*)data);
} else {
for(size_t i=0; i < info->len; i++){
os_printf("%02x ", data[i]);
}
os_printf("\n");
}
if(info->opcode == WS_TEXT)
client->text("I got your text message");
else
client->binary("I got your binary message");
} else {
//message is comprised of multiple frames or the frame is split into multiple packets
if(info->index == 0){
if(info->num == 0)
os_printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
os_printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
}
os_printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);
if(info->message_opcode == WS_TEXT){
data[len] = 0;
os_printf("%s\n", (char*)data);
} else {
for(size_t i=0; i < len; i++){
os_printf("%02x ", data[i]);
}
os_printf("\n");
}
if((info->index + len) == info->len){
os_printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
if(info->final){
os_printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
if(info->message_opcode == WS_TEXT)
client->text("I got your text message");
else
client->binary("I got your binary message");
}
}
}
}
}
const char* ssid = "**********";
const char* password = "************";
const char* http_username = "admin";
@ -124,6 +188,9 @@ void setup(){
if(WiFi.waitForConnectResult() != WL_CONNECTED){
Serial.printf("WiFi Failed!\n");
}
ws.onEvent(onEvent);
server.addHandler(&ws);
server.serveStatic("/fs", SPIFFS, "/");

View File

@ -17,350 +17,94 @@
-->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>ESP Monitor</title>
<script type="text/javascript" src="graphs.js"></script>
<script type="text/javascript">
if (typeof XMLHttpRequest === "undefined") {
XMLHttpRequest = function () {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
throw new Error("This browser does not support XMLHttpRequest.");
};
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>WebSocketTester</title>
<style type="text/css" media="screen">
body {
margin:0;
padding:0;
background-color: black;
}
var QueuedRequester = function () {
this.queue = [];
this.running = false;
this.xmlhttp = null;
}
QueuedRequester.prototype = {
_request: function(req){
this.running = true;
if(!req instanceof Object) return;
var that = this;
function scriptCb(d){ return function(e){
d.callback(e);
if(that.queue.length === 0) that.running = false;
if(that.running) that._request(that.queue.shift());
}}
function ajaxCb(x,d){ return function(){
if (x.readyState == 4){
if(x.status == 200){
if(d.type === "json") d.callback(JSON.parse(x.responseText));
else if(d.type === "xml") d.callback(x.responseXML);
else d.callback(x.responseText);
}
if(that.queue.length === 0) that.running = false;
if(that.running) that._request(that.queue.shift());
}
}}
var p = "";
if(req.params instanceof Object){
for (var key in req.params) {
if(p === "")
p += (req.method === "GET" || req.method === "SCRIPT")?"?":"";
else
p += "&";
p += key+"="+req.params[key];
};
}
if(req.method === "SCRIPT"){
var head = document.getElementsByTagName('head')[0];
if(req.type === "css"){
var c = document.createElement('link');
c.setAttribute('href', req.url+p);
c.setAttribute('rel', 'stylesheet');
c.setAttribute('type', 'text/css');
c.setAttribute('async', 'true');
c.addEventListener('load', scriptCb(req), false);
head.appendChild(c);
} else if(req.type === "javascript"){
var s = document.createElement('script');
s.setAttribute('src', req.url+p);
s.setAttribute('type', 'text/javascript');
s.setAttribute('async', 'true');
s.addEventListener('load', scriptCb(req), false);
head.appendChild(s);
}
return;
}
this.xmlhttp = new XMLHttpRequest();
this.xmlhttp.onreadystatechange = ajaxCb(this.xmlhttp, req);
if(req.method === "GET"){
this.xmlhttp.open(req.method, req.url+p, true);
this.xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
this.xmlhttp.send();
} else {
this.xmlhttp.open(req.method, req.url, true);
this.xmlhttp.send(p);
}
},
stop: function(){
if(this.running) this.running = false;
if(this.xmlhttp && this.xmlhttp.readyState < 4){
this.xmlhttp.abort();
}
},
add: function(url, method, params, type, callback){
this.queue.push({url:url,method:method,params:params,type:type,callback:callback});
if(!this.running){
this._request(this.queue.shift());
}
}
#dbg, #input_div, #input_el {
font-family: monaco;
font-size: 12px;
line-height: 13px;
color: #AAA;
}
var requests = new QueuedRequester();
var heap,temp,digi,rssi;
var reloadPeriod = 1000;
var running = false;
function ipStr(ip){
return (ip & 0xFF) + "." + ((ip >> 8) & 0xFF) + "." + ((ip >> 16) & 0xFF) + "." + ((ip >> 24) & 0xFF);
}
function statusStr(status){
if(status === 0) return "IDLE";
else if(status === 1) return "CONNECTING";
else if(status === 2) return "WRONG_PASSWORD";
else if(status === 3) return "NO_AP_FOUND";
else if(status === 4) return "CONNECT_FAILED";
else if(status === 5) return "CONNECTED";
else return "UNKNOWN";
#dbg, #input_div {
margin:0;
padding:0;
padding-left:4px;
}
function securityStr(status){
if(status === 7) return "";
else if(status === 8) return "WPA/WPA2";
else if(status === 2) return "WPA";
else if(status === 4) return "WPA2";
else if(status === 5) return "WEP";
else return "UNKNOWN";
#input_el {
width:98%;
background-color: rgba(0,0,0,0);
border: 0px;
}
function loadValues(){
if(!running) return;
requests.add("/wificonf", "GET", null, "json", function(res){
if(res){
wifiState(res);
//rssi.add(res.rssi);
//heap.add(res.heap);
//temp.add(res.analog);
//digi.add(res.gpio);
if(running) setTimeout(loadValues, reloadPeriod);
} else running = false;
});
};
function loadAvailables(){
if(!running) return;
requests.add("/scan", "GET", null, "json", function(res){
if(res){
wifiScan(res);
if(running) setTimeout(loadAvailables, 10*1000);
} else running = false;
});
};
/*
var avrParts = null;
var signature = null;
var lfuse = null;
var hfuse = null;
var efuse = null;
var lock = null;
var part = null;
function onNoPart(){
document.getElementById("avr-info").innerText = "AVR Part not found";
#input_el:focus {
outline: none;
}
function onPart(){
document.getElementById("avr-info").innerText = ("Part: id:"+part.id+" name:"+part.name+", signature:"+part.signature+", flash:"+part.flash+"B, eeprom:"+part.eeprom+"B, low:"+((part.fuses.low)?lfuse:"")+", high:"+((part.fuses.high)?hfuse:"")+", ext:"+((part.fuses.ext)?efuse:"")+", lock:"+((part.fuses.lock)?lock:"")+"");
</style>
<script type="text/javascript">
var ws = null;
function ge(s){ return document.getElementById(s);}
function ce(s){ return document.createElement(s);}
function stb(){ window.scrollTo(0, document.body.scrollHeight || document.documentElement.scrollHeight); }
function sendBlob(str){
var buf = new Uint8Array(str.length);
for (var i = 0; i < str.length; ++i) buf[i] = str.charCodeAt(i);
ws.send(buf);
}
function avrInfo(){
requests.add("/avr/info", "GET", null, "json", function(res){
var sig = (res.sig0 << 16) + (res.sig1 << 8) + res.sig2;
if(sig == 0){
onNoPart();
return;
}
signature = "0x"+sig.toString(16);
lfuse = res.low.toString(16);
hfuse = res.high.toString(16);
efuse = res.ext.toString(16);
lock = res.lock.toString(16);
if(avrParts == null){
requests.add("/avrs.json", "GET", null, "json", function(res){
if(!res) return;
avrParts = res;
for(p in avrParts){
if(avrParts[p].signature === signature){
part = avrParts[p];
onPart();
}
}
});
} else {
for(p in avrParts){
if(avrParts[p].signature === signature){
part = avrParts[p];
onPart();
}
function addMessage(m){
var msg = ce("div");
msg.innerText = m;
ge("dbg").appendChild(msg);
stb();
}
function startSocket(){
ws = new WebSocket('ws://'+document.location.host+'/ws',['arduino']);
ws.binaryType = "arraybuffer";
ws.onopen = function(e){
addMessage("Connected");
};
ws.onclose = function(e){
addMessage("Disconnected");
};
ws.onerror = function(e){
console.log("ws error", e);
addMessage("Error");
};
ws.onmessage = function(e){
var msg = "";
if(e.data instanceof ArrayBuffer){
msg = "BIN:";
var bytes = new Uint8Array(e.data);
for (var i = 0; i < bytes.length; i++) {
msg += String.fromCharCode(bytes[i]);
}
} else {
msg = "TXT:"+e.data;
}
addMessage(msg);
};
ge("input_el").onkeydown = function(e){
stb();
if(e.keyCode == 13 && ge("input_el").value != ""){
ws.send(ge("input_el").value);
ge("input_el").value = "";
}
});
};
*/
function run(){
if(!running){
running = true;
loadValues();
loadAvailables();
}
}
function onBodyLoad(){
var refreshInput = document.getElementById("refresh-rate");
refreshInput.value = reloadPeriod;
refreshInput.onchange = function(e){
var value = parseInt(e.target.value);
reloadPeriod = (value > 0)?value:0;
e.target.value = reloadPeriod;
}
var stopButton = document.getElementById("stop-button");
stopButton.onclick = function(e){
running = false;
requests.stop();
}
var startButton = document.getElementById("start-button");
startButton.onclick = function(e){
run();
}
// Example with 10K thermistor
//function calcThermistor(v) {
// var t = Math.log(((10230000 / v) - 10000));
// t = (1/(0.001129148+(0.000234125*t)+(0.0000000876741*t*t*t)))-273.15;
// return (t>120)?0:Math.round(t*10)/10;
//}
//temp = createGraph(document.getElementById("analog"), "Temperature", 100, 128, 10, 40, false, "cyan", calcThermistor);
//temp = createGraph(document.getElementById("analog"), "Analog Input", 100, 128, 0, 1023, false, "cyan");
rssi = createGraph(document.getElementById("rssi"), "WiFi RSSI", 100, 116, -100, -10, false, "green");
//heap = createGraph(document.getElementById("heap"), "Current Heap", 100, 125, 0, 40000, true, "orange");
//digi = createDigiGraph(document.getElementById("digital"), "GPIO", 100, 146, [0, 4, 5, 16], "gold");
//run();
//avrInfo();
}
function wifiState(res){
if(!res) return;
document.getElementsByName("ssid")[0].value = res.sta.ssid;
document.getElementsByName("bssid")[0].value = res.sta.bssid;
document.getElementsByName("channel")[0].value = res.sta.channel;
rssi.add(res.sta.rssi);
document.getElementsByName("status")[0].value = statusStr(res.sta.status);
document.getElementsByName("rssi")[0].value = res.sta.rssi;
document.getElementsByName("mac")[0].value = res.sta.mac;
document.getElementsByName("ip")[0].value = ipStr(res.sta.ip);
document.getElementsByName("mask")[0].value = ipStr(res.sta.netmask);
document.getElementsByName("gateway")[0].value = ipStr(res.sta.gateway);
document.getElementsByName("dns")[0].value = ipStr(res.sta.dns);
}
function wifiScan(res){
if(!res || !res.length) return;
var table = document.getElementById("available");
table.innerHTML = "";
for (var i=0; i<res.length; i++){
var row = document.createElement("tr");
row.innerHTML = "<td>"+(i+1)+"</td><td>"+res[i].ssid+"</td><td>"+res[i].bssid+"</td><td>"+res[i].channel+"</td><td>"+securityStr(res[i].secure)+"</td><td>"+res[i].hidden+"</td><td>"+res[i].rssi+"</td>";
table.appendChild(row);
}
}
</script>
<style>
#wifi-state {
display: block;
border: 1px solid rgb(68, 68, 68);
padding: 5px;
margin: 5px;
width: 500px;
background-color: rgb(238, 238, 238);
}
#wifi-state label {
width:100px;
display:inline-block;
font:14px Verdana;
}
#wifi-state input {
margin:1px;
}
table {
border: 1px solid rgb(68, 68, 68);
padding: 5px;
margin: 5px;
}
th {
background-color:#CCCCCC;
}
</style>
</head>
<body id="index" style="margin:0; padding:0;" onload="onBodyLoad()">
<!--div id="avr-info"></div-->
<div>
<div>
<div id="controls" style="display: block; border: 1px solid rgb(68, 68, 68); padding: 5px; margin: 5px; width: 362px; background-color: rgb(238, 238, 238);">
<label>Period (ms):</label>
<input type="number" id="refresh-rate"/>
<input type="button" id="start-button" value="Start"/>
<input type="button" id="stop-button" value="Stop"/>
</div>
<div id="rssi"></div>
</script>
</head>
<body id="body" onload="startSocket()">
<pre id="dbg"></pre>
<div id="input_div">
$<input type="text" value="" id="input_el">
</div>
<div style="display:inline-block">
<div id="wifi-form">
<form id="wifi-state">
<label>SSID:</label><input type="text" name="ssid" disabled="true"/><label>MAC:</label><input type="text" name="mac" disabled="true"/><br />
<label>BSSID:</label><input type="text" name="bssid" disabled="true"/><label>IP:</label><input type="text" name="ip" disabled="true"/><br />
<label>Channel:</label><input type="text" name="channel" disabled="true"/><label>Netmask:</label><input type="text" name="mask" disabled="true"/><br />
<label>Status:</label><input type="text" name="status" disabled="true"/><label>Gateway:</label><input type="text" name="gateway" disabled="true"/><br />
<label>RSSI:</label><input type="text" name="rssi" disabled="true"/><label>DNS:</label><input type="text" name="dns" disabled="true"/>
</form>
</div>
</div>
</div>
<div>
<table>
<thead>
<tr>
<th>&nbsp;&nbsp;&nbsp;</th>
<th style="width:200px">SSID</th>
<th style="width:150px">BSSID</th>
<th>Channel</th>
<th style="width:80px">Secure</th>
<th>Hidden</th>
<th>RSSI</th>
</tr>
</thead>
<tbody id="available"></tbody>
</table>
</div>
<!--div id="heap"></div>
<div id="analog"></div>
<div id="digital"></div-->
</body>
</body>
</html>

View File

@ -112,6 +112,71 @@ class SPIFFSEditor: public AsyncWebHandler {
// SKETCH BEGIN
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if(type == WS_EVT_CONNECT){
os_printf("ws[%s][%u] connect\n", server->url(), client->id());
client->printf("Hello Client %u :)", client->id());
client->ping();
} else if(type == WS_EVT_DISCONNECT){
os_printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
} else if(type == WS_EVT_ERROR){
os_printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
} else if(type == WS_EVT_PONG){
os_printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
} else if(type == WS_EVT_DATA){
AwsFrameInfo * info = (AwsFrameInfo*)arg;
if(info->final && info->index == 0 && info->len == len){
//the whole message is in a single frame and we got all of it's data
os_printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
if(info->opcode == WS_TEXT){
data[len] = 0;
os_printf("%s\n", (char*)data);
} else {
for(size_t i=0; i < info->len; i++){
os_printf("%02x ", data[i]);
}
os_printf("\n");
}
if(info->opcode == WS_TEXT)
client->text("I got your text message");
else
client->binary("I got your binary message");
} else {
//message is comprised of multiple frames or the frame is split into multiple packets
if(info->index == 0){
if(info->num == 0)
os_printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
os_printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
}
os_printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);
if(info->message_opcode == WS_TEXT){
data[len] = 0;
os_printf("%s\n", (char*)data);
} else {
for(size_t i=0; i < len; i++){
os_printf("%02x ", data[i]);
}
os_printf("\n");
}
if((info->index + len) == info->len){
os_printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
if(info->final){
os_printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
if(info->message_opcode == WS_TEXT)
client->text("I got your text message");
else
client->binary("I got your binary message");
}
}
}
}
}
const char* ssid = "**********";
const char* password = "************";
const char* http_username = "admin";
@ -147,6 +212,9 @@ void setup(){
ArduinoOTA.begin();
//Serial.printf("format start\n"); SPIFFS.format(); Serial.printf("format end\n");
ws.onEvent(onEvent);
server.addHandler(&ws);
server.serveStatic("/fs", SPIFFS, "/");
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){

View File

@ -17,350 +17,94 @@
-->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>ESP Monitor</title>
<script type="text/javascript" src="graphs.js"></script>
<script type="text/javascript">
if (typeof XMLHttpRequest === "undefined") {
XMLHttpRequest = function () {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
throw new Error("This browser does not support XMLHttpRequest.");
};
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>WebSocketTester</title>
<style type="text/css" media="screen">
body {
margin:0;
padding:0;
background-color: black;
}
var QueuedRequester = function () {
this.queue = [];
this.running = false;
this.xmlhttp = null;
}
QueuedRequester.prototype = {
_request: function(req){
this.running = true;
if(!req instanceof Object) return;
var that = this;
function scriptCb(d){ return function(e){
d.callback(e);
if(that.queue.length === 0) that.running = false;
if(that.running) that._request(that.queue.shift());
}}
function ajaxCb(x,d){ return function(){
if (x.readyState == 4){
if(x.status == 200){
if(d.type === "json") d.callback(JSON.parse(x.responseText));
else if(d.type === "xml") d.callback(x.responseXML);
else d.callback(x.responseText);
}
if(that.queue.length === 0) that.running = false;
if(that.running) that._request(that.queue.shift());
}
}}
var p = "";
if(req.params instanceof Object){
for (var key in req.params) {
if(p === "")
p += (req.method === "GET" || req.method === "SCRIPT")?"?":"";
else
p += "&";
p += key+"="+req.params[key];
};
}
if(req.method === "SCRIPT"){
var head = document.getElementsByTagName('head')[0];
if(req.type === "css"){
var c = document.createElement('link');
c.setAttribute('href', req.url+p);
c.setAttribute('rel', 'stylesheet');
c.setAttribute('type', 'text/css');
c.setAttribute('async', 'true');
c.addEventListener('load', scriptCb(req), false);
head.appendChild(c);
} else if(req.type === "javascript"){
var s = document.createElement('script');
s.setAttribute('src', req.url+p);
s.setAttribute('type', 'text/javascript');
s.setAttribute('async', 'true');
s.addEventListener('load', scriptCb(req), false);
head.appendChild(s);
}
return;
}
this.xmlhttp = new XMLHttpRequest();
this.xmlhttp.onreadystatechange = ajaxCb(this.xmlhttp, req);
if(req.method === "GET"){
this.xmlhttp.open(req.method, req.url+p, true);
this.xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
this.xmlhttp.send();
} else {
this.xmlhttp.open(req.method, req.url, true);
this.xmlhttp.send(p);
}
},
stop: function(){
if(this.running) this.running = false;
if(this.xmlhttp && this.xmlhttp.readyState < 4){
this.xmlhttp.abort();
}
},
add: function(url, method, params, type, callback){
this.queue.push({url:url,method:method,params:params,type:type,callback:callback});
if(!this.running){
this._request(this.queue.shift());
}
}
#dbg, #input_div, #input_el {
font-family: monaco;
font-size: 12px;
line-height: 13px;
color: #AAA;
}
var requests = new QueuedRequester();
var heap,temp,digi,rssi;
var reloadPeriod = 1000;
var running = false;
function ipStr(ip){
return (ip & 0xFF) + "." + ((ip >> 8) & 0xFF) + "." + ((ip >> 16) & 0xFF) + "." + ((ip >> 24) & 0xFF);
}
function statusStr(status){
if(status === 0) return "IDLE";
else if(status === 1) return "CONNECTING";
else if(status === 2) return "WRONG_PASSWORD";
else if(status === 3) return "NO_AP_FOUND";
else if(status === 4) return "CONNECT_FAILED";
else if(status === 5) return "CONNECTED";
else return "UNKNOWN";
#dbg, #input_div {
margin:0;
padding:0;
padding-left:4px;
}
function securityStr(status){
if(status === 7) return "";
else if(status === 8) return "WPA/WPA2";
else if(status === 2) return "WPA";
else if(status === 4) return "WPA2";
else if(status === 5) return "WEP";
else return "UNKNOWN";
#input_el {
width:98%;
background-color: rgba(0,0,0,0);
border: 0px;
}
function loadValues(){
if(!running) return;
requests.add("/wificonf", "GET", null, "json", function(res){
if(res){
wifiState(res);
//rssi.add(res.rssi);
//heap.add(res.heap);
//temp.add(res.analog);
//digi.add(res.gpio);
if(running) setTimeout(loadValues, reloadPeriod);
} else running = false;
});
};
function loadAvailables(){
if(!running) return;
requests.add("/scan", "GET", null, "json", function(res){
if(res){
wifiScan(res);
if(running) setTimeout(loadAvailables, 10*1000);
} else running = false;
});
};
/*
var avrParts = null;
var signature = null;
var lfuse = null;
var hfuse = null;
var efuse = null;
var lock = null;
var part = null;
function onNoPart(){
document.getElementById("avr-info").innerText = "AVR Part not found";
#input_el:focus {
outline: none;
}
function onPart(){
document.getElementById("avr-info").innerText = ("Part: id:"+part.id+" name:"+part.name+", signature:"+part.signature+", flash:"+part.flash+"B, eeprom:"+part.eeprom+"B, low:"+((part.fuses.low)?lfuse:"")+", high:"+((part.fuses.high)?hfuse:"")+", ext:"+((part.fuses.ext)?efuse:"")+", lock:"+((part.fuses.lock)?lock:"")+"");
</style>
<script type="text/javascript">
var ws = null;
function ge(s){ return document.getElementById(s);}
function ce(s){ return document.createElement(s);}
function stb(){ window.scrollTo(0, document.body.scrollHeight || document.documentElement.scrollHeight); }
function sendBlob(str){
var buf = new Uint8Array(str.length);
for (var i = 0; i < str.length; ++i) buf[i] = str.charCodeAt(i);
ws.send(buf);
}
function avrInfo(){
requests.add("/avr/info", "GET", null, "json", function(res){
var sig = (res.sig0 << 16) + (res.sig1 << 8) + res.sig2;
if(sig == 0){
onNoPart();
return;
}
signature = "0x"+sig.toString(16);
lfuse = res.low.toString(16);
hfuse = res.high.toString(16);
efuse = res.ext.toString(16);
lock = res.lock.toString(16);
if(avrParts == null){
requests.add("/avrs.json", "GET", null, "json", function(res){
if(!res) return;
avrParts = res;
for(p in avrParts){
if(avrParts[p].signature === signature){
part = avrParts[p];
onPart();
}
}
});
} else {
for(p in avrParts){
if(avrParts[p].signature === signature){
part = avrParts[p];
onPart();
}
function addMessage(m){
var msg = ce("div");
msg.innerText = m;
ge("dbg").appendChild(msg);
stb();
}
function startSocket(){
ws = new WebSocket('ws://'+document.location.host+'/ws',['arduino']);
ws.binaryType = "arraybuffer";
ws.onopen = function(e){
addMessage("Connected");
};
ws.onclose = function(e){
addMessage("Disconnected");
};
ws.onerror = function(e){
console.log("ws error", e);
addMessage("Error");
};
ws.onmessage = function(e){
var msg = "";
if(e.data instanceof ArrayBuffer){
msg = "BIN:";
var bytes = new Uint8Array(e.data);
for (var i = 0; i < bytes.length; i++) {
msg += String.fromCharCode(bytes[i]);
}
} else {
msg = "TXT:"+e.data;
}
addMessage(msg);
};
ge("input_el").onkeydown = function(e){
stb();
if(e.keyCode == 13 && ge("input_el").value != ""){
ws.send(ge("input_el").value);
ge("input_el").value = "";
}
});
};
*/
function run(){
if(!running){
running = true;
loadValues();
loadAvailables();
}
}
function onBodyLoad(){
var refreshInput = document.getElementById("refresh-rate");
refreshInput.value = reloadPeriod;
refreshInput.onchange = function(e){
var value = parseInt(e.target.value);
reloadPeriod = (value > 0)?value:0;
e.target.value = reloadPeriod;
}
var stopButton = document.getElementById("stop-button");
stopButton.onclick = function(e){
running = false;
requests.stop();
}
var startButton = document.getElementById("start-button");
startButton.onclick = function(e){
run();
}
// Example with 10K thermistor
//function calcThermistor(v) {
// var t = Math.log(((10230000 / v) - 10000));
// t = (1/(0.001129148+(0.000234125*t)+(0.0000000876741*t*t*t)))-273.15;
// return (t>120)?0:Math.round(t*10)/10;
//}
//temp = createGraph(document.getElementById("analog"), "Temperature", 100, 128, 10, 40, false, "cyan", calcThermistor);
//temp = createGraph(document.getElementById("analog"), "Analog Input", 100, 128, 0, 1023, false, "cyan");
rssi = createGraph(document.getElementById("rssi"), "WiFi RSSI", 100, 116, -100, -10, false, "green");
//heap = createGraph(document.getElementById("heap"), "Current Heap", 100, 125, 0, 40000, true, "orange");
//digi = createDigiGraph(document.getElementById("digital"), "GPIO", 100, 146, [0, 4, 5, 16], "gold");
//run();
//avrInfo();
}
function wifiState(res){
if(!res) return;
document.getElementsByName("ssid")[0].value = res.sta.ssid;
document.getElementsByName("bssid")[0].value = res.sta.bssid;
document.getElementsByName("channel")[0].value = res.sta.channel;
rssi.add(res.sta.rssi);
document.getElementsByName("status")[0].value = statusStr(res.sta.status);
document.getElementsByName("rssi")[0].value = res.sta.rssi;
document.getElementsByName("mac")[0].value = res.sta.mac;
document.getElementsByName("ip")[0].value = ipStr(res.sta.ip);
document.getElementsByName("mask")[0].value = ipStr(res.sta.netmask);
document.getElementsByName("gateway")[0].value = ipStr(res.sta.gateway);
document.getElementsByName("dns")[0].value = ipStr(res.sta.dns);
}
function wifiScan(res){
if(!res || !res.length) return;
var table = document.getElementById("available");
table.innerHTML = "";
for (var i=0; i<res.length; i++){
var row = document.createElement("tr");
row.innerHTML = "<td>"+(i+1)+"</td><td>"+res[i].ssid+"</td><td>"+res[i].bssid+"</td><td>"+res[i].channel+"</td><td>"+securityStr(res[i].secure)+"</td><td>"+res[i].hidden+"</td><td>"+res[i].rssi+"</td>";
table.appendChild(row);
}
}
</script>
<style>
#wifi-state {
display: block;
border: 1px solid rgb(68, 68, 68);
padding: 5px;
margin: 5px;
width: 500px;
background-color: rgb(238, 238, 238);
}
#wifi-state label {
width:100px;
display:inline-block;
font:14px Verdana;
}
#wifi-state input {
margin:1px;
}
table {
border: 1px solid rgb(68, 68, 68);
padding: 5px;
margin: 5px;
}
th {
background-color:#CCCCCC;
}
</style>
</head>
<body id="index" style="margin:0; padding:0;" onload="onBodyLoad()">
<!--div id="avr-info"></div-->
<div>
<div>
<div id="controls" style="display: block; border: 1px solid rgb(68, 68, 68); padding: 5px; margin: 5px; width: 362px; background-color: rgb(238, 238, 238);">
<label>Period (ms):</label>
<input type="number" id="refresh-rate"/>
<input type="button" id="start-button" value="Start"/>
<input type="button" id="stop-button" value="Stop"/>
</div>
<div id="rssi"></div>
</script>
</head>
<body id="body" onload="startSocket()">
<pre id="dbg"></pre>
<div id="input_div">
$<input type="text" value="" id="input_el">
</div>
<div style="display:inline-block">
<div id="wifi-form">
<form id="wifi-state">
<label>SSID:</label><input type="text" name="ssid" disabled="true"/><label>MAC:</label><input type="text" name="mac" disabled="true"/><br />
<label>BSSID:</label><input type="text" name="bssid" disabled="true"/><label>IP:</label><input type="text" name="ip" disabled="true"/><br />
<label>Channel:</label><input type="text" name="channel" disabled="true"/><label>Netmask:</label><input type="text" name="mask" disabled="true"/><br />
<label>Status:</label><input type="text" name="status" disabled="true"/><label>Gateway:</label><input type="text" name="gateway" disabled="true"/><br />
<label>RSSI:</label><input type="text" name="rssi" disabled="true"/><label>DNS:</label><input type="text" name="dns" disabled="true"/>
</form>
</div>
</div>
</div>
<div>
<table>
<thead>
<tr>
<th>&nbsp;&nbsp;&nbsp;</th>
<th style="width:200px">SSID</th>
<th style="width:150px">BSSID</th>
<th>Channel</th>
<th style="width:80px">Secure</th>
<th>Hidden</th>
<th>RSSI</th>
</tr>
</thead>
<tbody id="available"></tbody>
</table>
</div>
<!--div id="heap"></div>
<div id="analog"></div>
<div id="digital"></div-->
</body>
</body>
</html>

860
src/AsyncWebSocket.cpp Normal file
View File

@ -0,0 +1,860 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Arduino.h"
#include "AsyncWebSocket.h"
#include <libb64/cencode.h>
#ifndef ESP8266
extern "C" {
typedef struct {
uint32_t state[5];
uint32_t count[2];
unsigned char buffer[64];
} SHA1_CTX;
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
void SHA1Init(SHA1_CTX* context);
void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len);
void SHA1Final(unsigned char digest[20], SHA1_CTX* context);
}
#else
#include <Hash.h>
#endif
size_t webSocketSendFrameWindow(AsyncClient *client){
if(!client->canSend())
return 0;
size_t space = client->space();
if(space < 9)
return 0;
return space - 8;
}
size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool mask, uint8_t *data, size_t len){
if(!client->canSend())
return 0;
size_t space = client->space();
if(space < 2)
return 0;
uint8_t mbuf[4] = {0,0,0,0};
uint8_t headLen = 2;
if(len && mask){
headLen += 4;
mbuf[0] = rand() % 0xFF;
mbuf[1] = rand() % 0xFF;
mbuf[2] = rand() % 0xFF;
mbuf[3] = rand() % 0xFF;
os_printf("frame mask: 0x%02x 0x%02x 0x%02x 0x%02x\n", mbuf[0], mbuf[1], mbuf[2], mbuf[3]);
}
if(len > 125)
headLen += 2;
if(space < headLen)
return 0;
space -= headLen;
if(len > space) len = space;
uint8_t *buf = (uint8_t*)malloc(headLen);
if(buf == NULL){
os_printf("could not malloc %u bytes for frame header\n", headLen);
return 0;
}
buf[0] = opcode & 0x0F;
if(final)
buf[0] |= 0x80;
if(len < 126)
buf[1] = len & 0x7F;
else {
buf[1] = 126;
buf[2] = (uint8_t)((len >> 8) & 0xFF);
buf[3] = (uint8_t)(len & 0xFF);
}
if(len && mask){
buf[1] |= 0x80;
memcpy(buf + (headLen - 4), mbuf, 4);
}
if(client->add((const char *)buf, headLen) != headLen){
os_printf("error adding %lu header bytes\n", headLen);
free(buf);
return 0;
}
free(buf);
if(len){
if(len && mask){
os_printf("masking the payload: %lu\n", len);
size_t i;
for(i=0;i<len;i++)
data[i] = data[i] ^ mbuf[i%4];
}
if(client->add((const char *)data, len) != len){
os_printf("error adding %lu data bytes\n", len);
return 0;
}
}
if(!client->send()){
os_printf("error sending frame: %lu\n", headLen+len);
return 0;
}
return len;
}
/*
* Control Frame
*/
class AsyncWebSocketControl {
private:
uint8_t _opcode;
uint8_t *_data;
size_t _len;
bool _mask;
bool _finished;
public:
AsyncWebSocketControl * next;
AsyncWebSocketControl(uint8_t opcode, uint8_t *data=NULL, size_t len=0, bool mask=false)
:_opcode(opcode)
,_len(len)
,_mask(len && mask)
,_finished(false)
,next(NULL){
if(data == NULL)
_len = 0;
if(_len){
if(_len > 125)
_len = 125;
_data = (uint8_t*)malloc(_len);
if(_data == NULL)
_len = 0;
else memcpy(_data, data, len);
} else _data = NULL;
}
~AsyncWebSocketControl(){
if(_data != NULL)
free(_data);
}
bool finished(){ return _finished; }
uint8_t opcode(){ return _opcode; }
uint8_t len(){ return _len + 2; }
size_t send(AsyncClient *client){
_finished = true;
return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len);
}
};
/*
* Basic Buffered Message
*/
class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage {
private:
uint8_t * _data;
size_t _len;
size_t _sent;
size_t _ack;
size_t _acked;
public:
AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false)
:_len(len)
,_sent(0)
,_ack(0)
,_acked(0)
{
_opcode = opcode & 0x07;
_mask = mask;
_data = (uint8_t*)malloc(_len+1);
if(_data == NULL){
_len = 0;
_status = WS_MSG_ERROR;
} else {
_status = WS_MSG_SENDING;
memcpy(_data, data, _len);
_data[_len] = 0;
}
}
virtual ~AsyncWebSocketBasicMessage(){
if(_data != NULL)
free(_data);
}
virtual bool betweenFrames(){ return _acked == _ack; }
virtual void ack(size_t len, uint32_t time){
_acked += len;
if(_sent == _len && _acked == _ack){
_status = WS_MSG_SENT;
}
}
virtual size_t send(AsyncClient *client){
if(_status != WS_MSG_SENDING)
return 0;
if(_acked < _ack){
return 0;
}
if(_sent == _len){
if(_acked == _ack)
_status = WS_MSG_SENT;
return 0;
}
size_t window = webSocketSendFrameWindow(client);
size_t toSend = _len - _sent;
if(window < toSend) toSend = window;
bool final = ((toSend + _sent) == _len);
size_t sent = webSocketSendFrame(client, final, (_sent == 0)?_opcode:WS_CONTINUATION, _mask, (uint8_t*)(_data+_sent), toSend);
_sent += sent;
uint8_t headLen = ((sent < 126)?2:4)+(_mask*4);
_ack += sent + headLen;
return sent;
}
};
/*
* Async WebSocket Client
*/
AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server){
_client = request->client();
_server = server;
_clientId = _server->_getNextId();
_status = WS_CONNECTED;
_controlQueue = NULL;
_messageQueue = NULL;
_pstate = 0;
next = NULL;
delete request;
_client->onError([](void *r, AsyncClient* c, int8_t error){ ((AsyncWebSocketClient*)(r))->_onError(error); }, this);
_client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this);
_client->onDisconnect([](void *r, AsyncClient* c){ ((AsyncWebSocketClient*)(r))->_onDisconnect(); }, this);
_client->onTimeout([](void *r, AsyncClient* c, uint32_t time){ ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this);
_client->onData([](void *r, AsyncClient* c, void *buf, size_t len){ ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this);
_client->onPoll([](void *r, AsyncClient* c){ ((AsyncWebSocketClient*)(r))->_onPoll(); }, this);
_server->_addClient(this);
_server->_handleEvent(this, WS_EVT_CONNECT, NULL, NULL, 0);
}
AsyncWebSocketClient::~AsyncWebSocketClient(){
while(_messageQueue != NULL){
AsyncWebSocketMessage * m = _messageQueue;
_messageQueue = _messageQueue->next;
delete(m);
}
while(_controlQueue != NULL){
AsyncWebSocketControl * c = _controlQueue;
_controlQueue = _controlQueue->next;
delete(c);
}
_server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0);
}
void AsyncWebSocketClient::_onAck(size_t len, uint32_t time){
if(_controlQueue != NULL){
AsyncWebSocketControl *controlMessage = _controlQueue;
if(controlMessage->finished()){
_controlQueue = _controlQueue->next;
len -= controlMessage->len();
if(_status == WS_DISCONNECTING && controlMessage->opcode() == WS_DISCONNECT){
delete controlMessage;
_status = WS_DISCONNECTED;
_client->close(true);
return;
}
delete controlMessage;
}
}
if(len && _messageQueue != NULL){
_messageQueue->ack(len, time);
}
_runQueue();
}
void AsyncWebSocketClient::_onPoll(){
if(_client->canSend() && (_controlQueue != NULL || _messageQueue != NULL)){
_runQueue();
}
//ToDo: check for last activity here and maybe ping?
//ping();
}
void AsyncWebSocketClient::_runQueue(){
while(_messageQueue != NULL && _messageQueue->finished()){
AsyncWebSocketMessage * m = _messageQueue;
_messageQueue = _messageQueue->next;
delete(m);
}
if(_controlQueue != NULL && (_messageQueue == NULL || _messageQueue->betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue->len() - 1)){
AsyncWebSocketControl *control = _controlQueue;
control->send(_client);
} else if(_messageQueue != NULL && _messageQueue->betweenFrames() && webSocketSendFrameWindow(_client)){
_messageQueue->send(_client);
}
}
void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage *dataMessage){
if(dataMessage == NULL)
return;
if(_status != WS_CONNECTED){
delete dataMessage;
return;
}
if(_messageQueue == NULL){
_messageQueue = dataMessage;
} else {
AsyncWebSocketMessage * m = _messageQueue;
while(m->next != NULL) m = m->next;
m->next = dataMessage;
}
if(_client->canSend())
_runQueue();
}
void AsyncWebSocketClient::_queueControl(AsyncWebSocketControl *controlMessage){
if(controlMessage == NULL)
return;
if(_controlQueue == NULL){
_controlQueue = controlMessage;
} else {
AsyncWebSocketControl * m = _controlQueue;
while(m->next != NULL) m = m->next;
m->next = controlMessage;
}
if(_client->canSend())
_runQueue();
}
void AsyncWebSocketClient::close(uint16_t code, const char * message){
if(_status != WS_CONNECTED)
return;
if(code){
uint8_t packetLen = 2;
if(message != NULL){
size_t mlen = strlen(message);
if(mlen > 123) mlen = 123;
packetLen += mlen;
}
char * buf = (char*)malloc(packetLen);
if(buf != NULL){
buf[0] = (uint8_t)(code >> 8);
buf[1] = (uint8_t)(code & 0xFF);
if(message != NULL){
memcpy(buf+2, message, packetLen -2);
}
_queueControl(new AsyncWebSocketControl(WS_DISCONNECT,(uint8_t*)buf,packetLen));
free(buf);
return;
}
}
_queueControl(new AsyncWebSocketControl(WS_DISCONNECT));
}
void AsyncWebSocketClient::ping(uint8_t *data, size_t len){
if(_status == WS_CONNECTED)
_queueControl(new AsyncWebSocketControl(WS_PING, data, len));
}
void AsyncWebSocketClient::_onError(int8_t){
}
void AsyncWebSocketClient::_onTimeout(uint32_t time){
os_printf("_onTimeout: %u, state: %s\n", time, _client->stateToString());
_client->close();
}
void AsyncWebSocketClient::_onDisconnect(){
_client->free();
delete _client;
_server->_handleDisconnect(this);
}
void AsyncWebSocketClient::_onData(void *buf, size_t plen){
uint8_t *fdata = (uint8_t*)buf;
uint8_t * data = fdata;
if(!_pstate){
_pinfo.index = 0;
_pinfo.final = (fdata[0] & 0x80) != 0;
_pinfo.opcode = fdata[0] & 0x0F;
_pinfo.masked = (fdata[1] & 0x80) != 0;
_pinfo.len = fdata[1] & 0x7F;
data += 2;
plen = plen - 2;
if(_pinfo.len == 126){
_pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8;
data += 2;
plen = plen - 2;
} else if(_pinfo.len == 127){
_pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56;
data += 8;
plen = plen - 8;
}
if(_pinfo.masked){
memcpy(_pinfo.mask, data, 4);
data += 4;
plen = plen - 4;
size_t i;
for(i=0;i<plen;i++)
data[i] = data[i] ^ _pinfo.mask[(_pinfo.index+i)%4];
}
} else {
if(_pinfo.masked){
size_t i;
for(i=0;i<plen;i++)
data[i] = data[i] ^ _pinfo.mask[(_pinfo.index+i)%4];
}
}
if((plen + _pinfo.index) < _pinfo.len){
_pstate = 1;
if(_pinfo.index == 0){
if(_pinfo.opcode){
_pinfo.message_opcode = _pinfo.opcode;
_pinfo.num = 0;
} else _pinfo.num += 1;
}
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, (uint8_t*)data, plen);
_pinfo.index += plen;
} else if((plen + _pinfo.index) == _pinfo.len){
_pstate = 0;
if(_pinfo.opcode == WS_DISCONNECT){
if(plen){
uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1];
char * reasonString = (char*)(data+2);
if(reasonCode > 1001){
_server->_handleEvent(this, WS_EVT_ERROR, (void *)&reasonCode, (uint8_t*)reasonString, strlen(reasonString));
}
}
if(_status == WS_DISCONNECTING){
_status = WS_DISCONNECTED;
_client->close(true);
} else {
_status = WS_DISCONNECTING;
_queueControl(new AsyncWebSocketControl(WS_DISCONNECT, data, plen));
}
} else if(_pinfo.opcode == WS_PING){
_queueControl(new AsyncWebSocketControl(WS_PONG, data, plen));
} else if(_pinfo.opcode == WS_PONG){
_server->_handleEvent(this, WS_EVT_PONG, NULL, (uint8_t*)data, plen);
} else if(_pinfo.opcode < 8){//continuation or text/binary frame
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, (uint8_t*)data, plen);
}
} else {
os_printf("frame error: len: %u, index: %llu, total: %llu\n", plen, _pinfo.index, _pinfo.len);
//what should we do?
}
}
size_t AsyncWebSocketClient::printf(const char *format, ...) {
va_list arg;
va_start(arg, format);
#ifdef ESP8266
//ToDo: figure out a way around this
size_t len = 1440;
#else
size_t len = vsnprintf(NULL, 0, format, arg);
#endif
char * msg = (char*)malloc(len+1);
if(msg == NULL){
va_end(arg);
return 0;
}
len = vsnprintf(msg, len, format, arg);
msg[len] = 0;
text(msg);
va_end(arg);
free(msg);
return len;
}
void AsyncWebSocketClient::text(const char * message, size_t len){
_queueMessage(new AsyncWebSocketBasicMessage(message, len));
}
void AsyncWebSocketClient::text(const char * message){
text(message, strlen(message));
}
void AsyncWebSocketClient::text(uint8_t * message, size_t len){
text((const char *)message, len);
}
void AsyncWebSocketClient::text(char * message){
text(message, strlen(message));
}
void AsyncWebSocketClient::text(String &message){
text(message.c_str(), message.length());
}
void AsyncWebSocketClient::binary(const char * message, size_t len){
_queueMessage(new AsyncWebSocketBasicMessage(message, len, WS_BINARY));
}
void AsyncWebSocketClient::binary(const char * message){
binary(message, strlen(message));
}
void AsyncWebSocketClient::binary(uint8_t * message, size_t len){
binary((const char *)message, len);
}
void AsyncWebSocketClient::binary(char * message){
binary(message, strlen(message));
}
void AsyncWebSocketClient::binary(String &message){
binary(message.c_str(), message.length());
}
/*
* Async Web Socket - Each separate socket location
*/
AsyncWebSocket::AsyncWebSocket(String url)
:_url(url)
,_clients(NULL)
,_cNextId(0)
{
_eventHandler = NULL;
}
AsyncWebSocket::~AsyncWebSocket(){}
void AsyncWebSocket::_handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if(_eventHandler != NULL){
_eventHandler(this, client, type, arg, data, len);
}
}
void AsyncWebSocket::_addClient(AsyncWebSocketClient * client){
if(_clients == NULL){
_clients = client;
return;
}
AsyncWebSocketClient * c = _clients;
while(c->next != NULL) c = c->next;
c->next = client;
}
void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient * client){
if(_clients == NULL){
os_printf("we have no clients to disconnect!");
return;
}
if(_clients->id() == client->id()){
_clients = client->next;
delete client;
return;
}
AsyncWebSocketClient * c = _clients;
while(c->next != NULL && c->next->id() != client->id()) c = c->next;
if(c->next == NULL){
os_printf("we could not find client [%u] to disconnect!", client->id());
return;
}
c->next = client->next;
delete client;
}
size_t AsyncWebSocket::count(){
size_t i = 0;
AsyncWebSocketClient * c = _clients;
while(c != NULL){
if(c->status() == WS_CONNECTED)
i++;
c = c->next;
}
return i;
}
AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id){
AsyncWebSocketClient * c = _clients;
while(c != NULL && c->id() != id)
c = c->next;
if(c != NULL && c->status() == WS_CONNECTED)
return c;
return NULL;
}
void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message){
AsyncWebSocketClient * c = client(id);
if(c != NULL)
c->close(code, message);
}
void AsyncWebSocket::closeAll(uint16_t code, const char * message){
AsyncWebSocketClient * c = _clients;
while(c != NULL){
if(c->status() == WS_CONNECTED)
c->close(code, message);
c = c->next;
}
}
void AsyncWebSocket::ping(uint32_t id, uint8_t *data, size_t len){
AsyncWebSocketClient * c = client(id);
if(c != NULL)
c->ping(data, len);
}
void AsyncWebSocket::pingAll(uint8_t *data, size_t len){
AsyncWebSocketClient * c = _clients;
while(c != NULL){
if(c->status() == WS_CONNECTED)
c->ping(data, len);
c = c->next;
}
}
void AsyncWebSocket::text(uint32_t id, const char * message, size_t len){
AsyncWebSocketClient * c = client(id);
if(c != NULL)
c->text(message, len);
}
void AsyncWebSocket::textAll(const char * message, size_t len){
AsyncWebSocketClient * c = _clients;
while(c != NULL){
if(c->status() == WS_CONNECTED)
c->text(message, len);
c = c->next;
}
}
void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len){
AsyncWebSocketClient * c = client(id);
if(c != NULL)
c->binary(message, len);
}
void AsyncWebSocket::binaryAll(const char * message, size_t len){
AsyncWebSocketClient * c = _clients;
while(c != NULL){
if(c->status() == WS_CONNECTED)
c->binary(message, len);
c = c->next;
}
}
void AsyncWebSocket::message(uint32_t id, AsyncWebSocketMessage *message){
AsyncWebSocketClient * c = client(id);
if(c != NULL)
c->message(message);
}
void AsyncWebSocket::messageAll(AsyncWebSocketMessage *message){
AsyncWebSocketClient * c = _clients;
while(c != NULL){
if(c->status() == WS_CONNECTED)
c->message(message);
c = c->next;
}
}
size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...){
AsyncWebSocketClient * c = client(id);
if(c != NULL){
va_list arg;
va_start(arg, format);
size_t len = c->printf(format, arg);
va_end(arg);
return len;
}
return 0;
}
size_t AsyncWebSocket::printfAll(const char *format, ...) {
va_list arg;
va_start(arg, format);
#ifdef ESP8266
//ToDo: figure out a way around this
size_t len = 1440;
#else
size_t len = vsnprintf(NULL, 0, format, arg);
#endif
char * msg = (char*)malloc(len+1);
if(msg == NULL){
va_end(arg);
return 0;
}
len = vsnprintf(msg, len, format, arg);
msg[len] = 0;
textAll(msg);
va_end(arg);
free(msg);
return len;
}
void AsyncWebSocket::text(uint32_t id, const char * message){
text(id, message, strlen(message));
}
void AsyncWebSocket::text(uint32_t id, uint8_t * message, size_t len){
text(id, (const char *)message, len);
}
void AsyncWebSocket::text(uint32_t id, char * message){
text(id, message, strlen(message));
}
void AsyncWebSocket::text(uint32_t id, String &message){
text(id, message.c_str(), message.length());
}
void AsyncWebSocket::textAll(const char * message){
textAll(message, strlen(message));
}
void AsyncWebSocket::textAll(uint8_t * message, size_t len){
textAll((const char *)message, len);
}
void AsyncWebSocket::textAll(char * message){
textAll(message, strlen(message));
}
void AsyncWebSocket::textAll(String &message){
textAll(message.c_str(), message.length());
}
void AsyncWebSocket::binary(uint32_t id, const char * message){
binary(id, message, strlen(message));
}
void AsyncWebSocket::binary(uint32_t id, uint8_t * message, size_t len){
binary(id, (const char *)message, len);
}
void AsyncWebSocket::binary(uint32_t id, char * message){
binary(id, message, strlen(message));
}
void AsyncWebSocket::binary(uint32_t id, String &message){
binary(id, message.c_str(), message.length());
}
void AsyncWebSocket::binaryAll(const char * message){
binaryAll(message, strlen(message));
}
void AsyncWebSocket::binaryAll(uint8_t * message, size_t len){
binaryAll((const char *)message, len);
}
void AsyncWebSocket::binaryAll(char * message){
binaryAll(message, strlen(message));
}
void AsyncWebSocket::binaryAll(String &message){
binaryAll(message.c_str(), message.length());
}
const char * WS_STR_CONNECTION = "Connection";
const char * WS_STR_UPGRADE = "Upgrade";
const char * WS_STR_ORIGIN = "Origin";
const char * WS_STR_VERSION = "Sec-WebSocket-Version";
const char * WS_STR_KEY = "Sec-WebSocket-Key";
const char * WS_STR_PROTOCOL = "Sec-WebSocket-Protocol";
const char * WS_STR_ACCEPT = "Sec-WebSocket-Accept";
const char * WS_STR_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
bool AsyncWebSocket::canHandle(AsyncWebServerRequest *request){
if(request->method() != HTTP_GET || !request->url().equals(_url))
return false;
request->addInterestingHeader(WS_STR_CONNECTION);
request->addInterestingHeader(WS_STR_UPGRADE);
request->addInterestingHeader(WS_STR_ORIGIN);
request->addInterestingHeader(WS_STR_VERSION);
request->addInterestingHeader(WS_STR_KEY);
request->addInterestingHeader(WS_STR_PROTOCOL);
return true;
}
void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request){
if(!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)){
request->send(400);
return;
}
AsyncWebHeader* version = request->getHeader(WS_STR_VERSION);
if(version->value().toInt() != 13){
AsyncWebServerResponse *response = request->beginResponse(400);
response->addHeader(WS_STR_VERSION,"13");
request->send(response);
return;
}
AsyncWebHeader* key = request->getHeader(WS_STR_KEY);
AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this);
if(request->hasHeader(WS_STR_PROTOCOL)){
AsyncWebHeader* protocol = request->getHeader(WS_STR_PROTOCOL);
//ToDo: check protocol
response->addHeader(WS_STR_PROTOCOL, protocol->value());
}
request->send(response);
}
/*
* Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server
*/
AsyncWebSocketResponse::AsyncWebSocketResponse(String key, AsyncWebSocket *server){
_server = server;
_code = 101;
uint8_t * hash = (uint8_t*)malloc(20);
if(hash == NULL){
_state = RESPONSE_FAILED;
return;
}
char * buffer = (char *) malloc(33);
if(buffer == NULL){
free(hash);
_state = RESPONSE_FAILED;
return;
}
#ifdef ESP8266
sha1(key + WS_STR_UUID, hash);
#else
key += WS_STR_UUID;
SHA1_CTX ctx;
SHA1Init(&ctx);
SHA1Update(&ctx, (const unsigned char*)key.c_str(), key.length());
SHA1Final(hash, &ctx);
#endif
base64_encodestate _state;
base64_init_encodestate(&_state);
int len = base64_encode_block((const char *) hash, 20, buffer, &_state);
len = base64_encode_blockend((buffer + len), &_state);
addHeader(WS_STR_CONNECTION, WS_STR_UPGRADE);
addHeader(WS_STR_UPGRADE, "websocket");
addHeader(WS_STR_ACCEPT,buffer);
free(buffer);
free(hash);
}
void AsyncWebSocketResponse::_respond(AsyncWebServerRequest *request){
if(_state == RESPONSE_FAILED){
request->client()->close(true);
return;
}
String out = _assembleHead(request->version());
request->client()->write(out.c_str(), _headLength);
_state = RESPONSE_WAIT_ACK;
}
size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
if(len){
new AsyncWebSocketClient(request, _server);
}
return 0;
}

202
src/AsyncWebSocket.h Normal file
View File

@ -0,0 +1,202 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCWEBSOCKET_H_
#define ASYNCWEBSOCKET_H_
#include <Arduino.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
class AsyncWebSocket;
class AsyncWebSocketResponse;
class AsyncWebSocketClient;
class AsyncWebSocketControl;
typedef struct {
uint8_t message_opcode;
uint32_t num;
uint8_t final;
uint8_t masked;
uint8_t opcode;
uint64_t len;
uint8_t mask[4];
uint64_t index;
} AwsFrameInfo;
typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus;
typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType;
typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus;
typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType;
class AsyncWebSocketMessage {
protected:
uint8_t _opcode;
bool _mask;
AwsMessageStatus _status;
public:
AsyncWebSocketMessage * next;
AsyncWebSocketMessage():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR),next(NULL){}
virtual ~AsyncWebSocketMessage(){}
virtual void ack(size_t len, uint32_t time){}
virtual size_t send(AsyncClient *client){ return 0; }
virtual bool finished(){ return _status != WS_MSG_SENDING; }
virtual bool betweenFrames(){ return false; }
};
class AsyncWebSocketClient {
private:
AsyncClient *_client;
AsyncWebSocket *_server;
uint32_t _clientId;
AwsClientStatus _status;
AsyncWebSocketControl * _controlQueue;
AsyncWebSocketMessage * _messageQueue;
uint8_t _pstate;
AwsFrameInfo _pinfo;
void _queueMessage(AsyncWebSocketMessage *dataMessage);
void _queueControl(AsyncWebSocketControl *controlMessage);
void _runQueue();
public:
AsyncWebSocketClient * next;
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
~AsyncWebSocketClient();
//client id increments for the given server
uint32_t id(){ return _clientId; }
AwsClientStatus status(){ return _status; }
//control frames
void close(uint16_t code=0, const char * message=NULL);
void ping(uint8_t *data=NULL, size_t len=0);
//data packets
void message(AsyncWebSocketMessage *message){ _queueMessage(message); }
size_t printf(const char *format, ...);
void text(const char * message, size_t len);
void text(const char * message);
void text(uint8_t * message, size_t len);
void text(char * message);
void text(String &message);
void binary(const char * message, size_t len);
void binary(const char * message);
void binary(uint8_t * message, size_t len);
void binary(char * message);
void binary(String &message);
//system callbacks (do not call)
void _onAck(size_t len, uint32_t time);
void _onError(int8_t);
void _onPoll();
void _onTimeout(uint32_t time);
void _onDisconnect();
void _onData(void *buf, size_t plen);
};
typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)> AwsEventHandler;
//WebServer Handler implementation that plays the role of a socket server
class AsyncWebSocket: public AsyncWebHandler {
private:
String _url;
AsyncWebSocketClient * _clients;
uint32_t _cNextId;
AwsEventHandler _eventHandler;
public:
AsyncWebSocket(String url);
~AsyncWebSocket();
const char * url(){ return _url.c_str(); }
size_t count();
AsyncWebSocketClient * client(uint32_t id);
bool hasClient(uint32_t id){ return client(id) != NULL; }
void close(uint32_t id, uint16_t code=0, const char * message=NULL);
void closeAll(uint16_t code=0, const char * message=NULL);
void ping(uint32_t id, uint8_t *data=NULL, size_t len=0);
void pingAll(uint8_t *data=NULL, size_t len=0);
void text(uint32_t id, const char * message, size_t len);
void text(uint32_t id, const char * message);
void text(uint32_t id, uint8_t * message, size_t len);
void text(uint32_t id, char * message);
void text(uint32_t id, String &message);
void textAll(const char * message, size_t len);
void textAll(const char * message);
void textAll(uint8_t * message, size_t len);
void textAll(char * message);
void textAll(String &message);
void binary(uint32_t id, const char * message, size_t len);
void binary(uint32_t id, const char * message);
void binary(uint32_t id, uint8_t * message, size_t len);
void binary(uint32_t id, char * message);
void binary(uint32_t id, String &message);
void binaryAll(const char * message, size_t len);
void binaryAll(const char * message);
void binaryAll(uint8_t * message, size_t len);
void binaryAll(char * message);
void binaryAll(String &message);
void message(uint32_t id, AsyncWebSocketMessage *message);
void messageAll(AsyncWebSocketMessage *message);
size_t printf(uint32_t id, const char *format, ...);
size_t printfAll(const char *format, ...);
//event listener
void onEvent(AwsEventHandler handler){
_eventHandler = handler;
}
//system callbacks (do not call)
uint32_t _getNextId(){ return _cNextId++; }
void _addClient(AsyncWebSocketClient * client);
void _handleDisconnect(AsyncWebSocketClient * client);
void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len);
bool canHandle(AsyncWebServerRequest *request);
void handleRequest(AsyncWebServerRequest *request);
};
//WebServer response to authenticate the socket and detach the tcp client from the web server request
class AsyncWebSocketResponse: public AsyncWebServerResponse {
private:
String _content;
AsyncWebSocket *_server;
public:
AsyncWebSocketResponse(String key, AsyncWebSocket *server);
void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid(){ return true; }
};
#endif /* ASYNCWEBSOCKET_H_ */

View File

@ -308,5 +308,6 @@ class AsyncWebServer {
};
#include "WebResponseImpl.h"
#include "AsyncWebSocket.h"
#endif /* _AsyncWebServer_H_ */

View File

@ -82,10 +82,7 @@ AsyncWebServerResponse::AsyncWebServerResponse()
, _sentLength(0)
, _ackedLength(0)
, _state(RESPONSE_SETUP)
{
addHeader("Connection","close");
addHeader("Access-Control-Allow-Origin","*");
}
{}
AsyncWebServerResponse::~AsyncWebServerResponse(){
while(_headers != NULL){
@ -160,6 +157,8 @@ AsyncBasicResponse::AsyncBasicResponse(int code, String contentType, String cont
if(!_contentType.length())
_contentType = "text/plain";
}
addHeader("Connection","close");
addHeader("Access-Control-Allow-Origin","*");
}
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){
@ -228,6 +227,8 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint
* */
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){
addHeader("Connection","close");
addHeader("Access-Control-Allow-Origin","*");
_head = _assembleHead(request->version());
_state = RESPONSE_HEADERS;
_ack(request, 0, 0);