mirror of https://github.com/jeelabs/esp-link.git
Conflicts: Makefile cmd/handlers.c esp-link/cgi.c esp-link/cgiwifi.c html/console.html html/style.css html/ui.js httpd/httpd.c mqtt/mqtt.c mqtt/mqtt.h mqtt/mqtt_cmd.c user/user_main.cpull/47/head
commit
965b70a408
@ -0,0 +1,111 @@ |
|||||||
|
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||||
|
// // TCP Client settings
|
||||||
|
|
||||||
|
#include <esp8266.h> |
||||||
|
#include "cgi.h" |
||||||
|
#include "config.h" |
||||||
|
#include "cgimqtt.h" |
||||||
|
|
||||||
|
// Cgi to return MQTT settings
|
||||||
|
int ICACHE_FLASH_ATTR cgiMqttGet(HttpdConnData *connData) { |
||||||
|
char buff[2048]; |
||||||
|
int len; |
||||||
|
|
||||||
|
if (connData->conn==NULL) return HTTPD_CGI_DONE; |
||||||
|
|
||||||
|
len = os_sprintf(buff, "{ " |
||||||
|
"\"slip-enable\":%d, " |
||||||
|
"\"mqtt-enable\":%d, " |
||||||
|
"\"mqtt-status-enable\":%d, " |
||||||
|
"\"mqtt-port\":%d, " |
||||||
|
"\"mqtt-host\":\"%s\", " |
||||||
|
"\"mqtt-client-id\":\"%s\", " |
||||||
|
"\"mqtt-username\":\"%s\", " |
||||||
|
"\"mqtt-password\":\"%s\", " |
||||||
|
"\"mqtt-status-topic\":\"%s\", " |
||||||
|
"\"mqtt-state\":\"%s\" }", |
||||||
|
flashConfig.slip_enable, flashConfig.mqtt_enable, flashConfig.mqtt_status_enable, |
||||||
|
flashConfig.mqtt_port, flashConfig.mqtt_hostname, flashConfig.mqtt_client, |
||||||
|
flashConfig.mqtt_username, flashConfig.mqtt_password, |
||||||
|
flashConfig.mqtt_status_topic, "connected"); |
||||||
|
|
||||||
|
jsonHeader(connData, 200); |
||||||
|
httpdSend(connData, buff, len); |
||||||
|
return HTTPD_CGI_DONE; |
||||||
|
} |
||||||
|
|
||||||
|
// Cgi to change choice of pin assignments
|
||||||
|
int ICACHE_FLASH_ATTR cgiMqttSet(HttpdConnData *connData) { |
||||||
|
if (connData->conn==NULL) return HTTPD_CGI_DONE; |
||||||
|
|
||||||
|
// handle MQTT server settings
|
||||||
|
int mqtt_server = 0; // accumulator for changes/errors
|
||||||
|
mqtt_server |= getStringArg(connData, "mqtt-host", |
||||||
|
flashConfig.mqtt_hostname, sizeof(flashConfig.mqtt_hostname)); |
||||||
|
if (mqtt_server < 0) return HTTPD_CGI_DONE; |
||||||
|
mqtt_server |= getStringArg(connData, "mqtt-client-id", |
||||||
|
flashConfig.mqtt_client, sizeof(flashConfig.mqtt_client)); |
||||||
|
if (mqtt_server < 0) return HTTPD_CGI_DONE; |
||||||
|
mqtt_server |= getStringArg(connData, "mqtt-username", |
||||||
|
flashConfig.mqtt_username, sizeof(flashConfig.mqtt_username)); |
||||||
|
if (mqtt_server < 0) return HTTPD_CGI_DONE; |
||||||
|
mqtt_server |= getStringArg(connData, "mqtt-password", |
||||||
|
flashConfig.mqtt_password, sizeof(flashConfig.mqtt_password)); |
||||||
|
if (mqtt_server < 0) return HTTPD_CGI_DONE; |
||||||
|
mqtt_server |= getBoolArg(connData, "mqtt-enable", |
||||||
|
&flashConfig.mqtt_enable); |
||||||
|
|
||||||
|
// handle mqtt port
|
||||||
|
char buff[16]; |
||||||
|
if (httpdFindArg(connData->getArgs, "mqtt-port", buff, sizeof(buff)) > 0) { |
||||||
|
int32_t port = atoi(buff); |
||||||
|
if (port > 0 && port < 65536) { |
||||||
|
flashConfig.mqtt_port = port; |
||||||
|
mqtt_server |= 1; |
||||||
|
} else { |
||||||
|
errorResponse(connData, 400, "Invalid MQTT port"); |
||||||
|
return HTTPD_CGI_DONE; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// if server setting changed, we need to "make it so"
|
||||||
|
if (mqtt_server) { |
||||||
|
os_printf("MQTT server settings changed, enable=%d\n", flashConfig.mqtt_enable); |
||||||
|
// TODO
|
||||||
|
} |
||||||
|
|
||||||
|
// no action required if mqtt status settings change, they just get picked up at the
|
||||||
|
// next status tick
|
||||||
|
if (getBoolArg(connData, "mqtt-status-enable", &flashConfig.mqtt_status_enable) < 0) |
||||||
|
return HTTPD_CGI_DONE; |
||||||
|
if (getStringArg(connData, "mqtt-status-topic", |
||||||
|
flashConfig.mqtt_status_topic, sizeof(flashConfig.mqtt_status_topic)) < 0) |
||||||
|
return HTTPD_CGI_DONE; |
||||||
|
|
||||||
|
// if SLIP-enable is toggled it gets picked-up immediately by the parser
|
||||||
|
int slip_update = getBoolArg(connData, "slip-enable", &flashConfig.slip_enable); |
||||||
|
if (slip_update < 0) return HTTPD_CGI_DONE; |
||||||
|
if (slip_update > 0) os_printf("SLIP-enable changed: %d\n", flashConfig.slip_enable); |
||||||
|
|
||||||
|
os_printf("Saving config\n"); |
||||||
|
if (configSave()) { |
||||||
|
httpdStartResponse(connData, 200); |
||||||
|
httpdEndHeaders(connData); |
||||||
|
} else { |
||||||
|
httpdStartResponse(connData, 500); |
||||||
|
httpdEndHeaders(connData); |
||||||
|
httpdSend(connData, "Failed to save config", -1); |
||||||
|
} |
||||||
|
return HTTPD_CGI_DONE; |
||||||
|
} |
||||||
|
|
||||||
|
int ICACHE_FLASH_ATTR cgiMqtt(HttpdConnData *connData) { |
||||||
|
if (connData->requestType == HTTPD_METHOD_GET) { |
||||||
|
return cgiMqttGet(connData); |
||||||
|
} else if (connData->requestType == HTTPD_METHOD_POST) { |
||||||
|
return cgiMqttSet(connData); |
||||||
|
} else { |
||||||
|
jsonHeader(connData, 404); |
||||||
|
return HTTPD_CGI_DONE; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
#ifndef CGIMQTT_H |
||||||
|
#define CGIMQTT_H |
||||||
|
|
||||||
|
#include "httpd.h" |
||||||
|
|
||||||
|
int cgiMqtt(HttpdConnData *connData); |
||||||
|
|
||||||
|
#endif |
@ -1 +1,46 @@ |
|||||||
<!DOCTYPE html><script src='http://linux-ws/esplink/console.js'></script> |
<div id="main"> |
||||||
|
<div class="header"> |
||||||
|
<h1>Microcontroller Console</h1> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="content"> |
||||||
|
<p>The Microcontroller console shows the last 1024 characters |
||||||
|
received from UART0, to which a microcontroller is typically attached. |
||||||
|
The UART is configured for 8 bits, no parity, 1 stop bit (8N1).</p> |
||||||
|
<p> |
||||||
|
<a id="reset-button" class="pure-button button-primary" href="#">Reset µC</a> |
||||||
|
Baud: |
||||||
|
<span id="baud-btns"></span> |
||||||
|
</p> |
||||||
|
<pre class="console" id="console"></pre> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<script type="text/javascript">console_url = "/console/text"</script> |
||||||
|
<script src="console.js"></script> |
||||||
|
<script type="text/javascript"> |
||||||
|
var rates = [9600, 57600, 115200, 250000]; |
||||||
|
|
||||||
|
onLoad(function() { |
||||||
|
fetchText(100, true); |
||||||
|
|
||||||
|
$("#reset-button").addEventListener("click", function(e) { |
||||||
|
e.preventDefault(); |
||||||
|
var co = $("#console"); |
||||||
|
co.innerHTML = ""; |
||||||
|
ajaxSpin('POST', "/console/reset", |
||||||
|
function(resp) { showNotification("uC reset"); co.textEnd = 0; }, |
||||||
|
function(s, st) { showWarning("Error resetting uC"); } |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
rates.forEach(function(r) { baudButton(r); }); |
||||||
|
|
||||||
|
ajaxJson('GET', "/console/baud", |
||||||
|
function(data) { showRate(data.rate); }, |
||||||
|
function(s, st) { showNotification(st); } |
||||||
|
); |
||||||
|
}); |
||||||
|
</script> |
||||||
|
</body></html> |
||||||
|
@ -0,0 +1,99 @@ |
|||||||
|
<div id="main"> |
||||||
|
<div class="header"> |
||||||
|
<h1>REST & MQTT</h1> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="content"> |
||||||
|
<div class="pure-g"> |
||||||
|
<div class="pure-u-1"><div class="card"> |
||||||
|
<p>The REST & MQTT support uses the SLIP protocol over the serial port to enable |
||||||
|
the attached microcontroller to initiate outbound connections. |
||||||
|
The REST support lets the uC initiate simple HTTP requests while the MQTT support |
||||||
|
lets it communicate with an MQTT server bidirectionally at QoS 0 thru 2.</p> |
||||||
|
<p>The MQTT support is in the form of a built-in client that connects to a server |
||||||
|
using parameters set below and stored in esp-link's flash settings. This allows |
||||||
|
esp-link to take care of connection parameters and disconnect/reconnect operations.</p> |
||||||
|
<p>The MQTT client also supports sending periodic status messages about esp-link itself, |
||||||
|
including Wifi RSSI, and free heap memory.</p> |
||||||
|
<div class="form-horizontal"> |
||||||
|
<input type="checkbox" name="slip-enable"/> |
||||||
|
<label>Enable SLIP on serial port</label> |
||||||
|
</div> |
||||||
|
</div></div> |
||||||
|
</div> |
||||||
|
<div class="pure-g"> |
||||||
|
<div class="pure-u-1 pure-u-md-1-2"> |
||||||
|
<div class="card"> |
||||||
|
<h1>MQTT |
||||||
|
<div id="mqtt-spinner" class="spinner spinner-small"></div> |
||||||
|
</h1> |
||||||
|
<form action="#" id="mqtt-form" class="pure-form" hidden> |
||||||
|
<div class="form-horizontal"> |
||||||
|
<input type="checkbox" name="mqtt-enable"/> |
||||||
|
<label>Enable MQTT client</label> |
||||||
|
</div> |
||||||
|
<div class="form-horizontal"> |
||||||
|
<label>MQTT client state: </label> |
||||||
|
<b id="mqtt-state"></b> |
||||||
|
</div> |
||||||
|
<br> |
||||||
|
<legend>MQTT server settings</legend> |
||||||
|
<div class="pure-form-stacked"> |
||||||
|
<label>Server hostname/ip</label> |
||||||
|
<input type="text" name="mqtt-host"/> |
||||||
|
<label>Server port/ip</label> |
||||||
|
<input type="text" name="mqtt-port"/> |
||||||
|
<label>Client ID</label> |
||||||
|
<input type="text" name="mqtt-client-id"/> |
||||||
|
<label>Username</label> |
||||||
|
<input type="text" name="mqtt-username"/> |
||||||
|
<label>Password</label> |
||||||
|
<input type="password" name="mqtt-password"/> |
||||||
|
</div> |
||||||
|
<button id="mqtt-button" type="submit" class="pure-button button-primary"> |
||||||
|
Update server settings! |
||||||
|
</button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="pure-u-1 pure-u-md-1-2"> |
||||||
|
<div class="card"> |
||||||
|
<h1>Status reporting |
||||||
|
<div id="mqtt-status-spinner" class="spinner spinner-small"></div> |
||||||
|
</h1> |
||||||
|
<form action="#" id="mqtt-status-form" class="pure-form" hidden> |
||||||
|
<div class="form-horizontal"> |
||||||
|
<input type="checkbox" name="mqtt-status-enable"/> |
||||||
|
<label>Enable status reporting via MQTT</label> |
||||||
|
</div> |
||||||
|
<br> |
||||||
|
<legend>Status reporting settings</legend> |
||||||
|
<div class="pure-form-stacked"> |
||||||
|
<label>Topic prefix</label> |
||||||
|
<input type="text" name="mqtt-status-topic"/> |
||||||
|
Suffixes: rssi, heap-free, ... |
||||||
|
</div> |
||||||
|
<button id="mqtt-status-button" type="submit" class="pure-button button-primary"> |
||||||
|
Update status settings! |
||||||
|
</button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
<div class="card"> |
||||||
|
<h1>REST</h1> |
||||||
|
<p>REST requests are enabled as soon as SLIP is enabled. |
||||||
|
There are no REST-specific settings.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<script type="text/javascript"> |
||||||
|
onLoad(function() { |
||||||
|
fetchMqtt(); |
||||||
|
bnd($("#mqtt-form"), "submit", changeMqtt); |
||||||
|
bnd($("#mqtt-status-form"), "submit", changeMqttStatus); |
||||||
|
}); |
||||||
|
</script> |
||||||
|
</body></html> |
@ -0,0 +1,384 @@ |
|||||||
|
/* All fonts */ |
||||||
|
html, button, input, select, textarea, .pure-g [class *= "pure-u"] { |
||||||
|
font-family: sans-serif; |
||||||
|
} |
||||||
|
|
||||||
|
input[type="text"], input[type="password"] { |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
body { |
||||||
|
color: #777; |
||||||
|
} |
||||||
|
a:visited, a:link { |
||||||
|
color: #009; |
||||||
|
} |
||||||
|
a:hover { |
||||||
|
color: #00c; |
||||||
|
} |
||||||
|
|
||||||
|
.card { |
||||||
|
background-color: #eee; |
||||||
|
padding: 1em; |
||||||
|
margin: 0.5em; |
||||||
|
-moz-border-radius: 0.5em; |
||||||
|
-webkit-border-radius: 0.5em; |
||||||
|
border-radius: 0.5em; |
||||||
|
border: 0px solid #000000; |
||||||
|
} |
||||||
|
|
||||||
|
/* wifi AP selection form */ |
||||||
|
#aps label div { |
||||||
|
display: inline-block; |
||||||
|
margin: 0em 0.2em; |
||||||
|
} |
||||||
|
fieldset.radios { |
||||||
|
border: none; |
||||||
|
padding-left: 0px; |
||||||
|
} |
||||||
|
fieldset fields { |
||||||
|
clear: both; |
||||||
|
} |
||||||
|
#pin-mux input { |
||||||
|
display: block; |
||||||
|
margin-top: 0.4em; |
||||||
|
float: left; |
||||||
|
} |
||||||
|
#pin-mux label { |
||||||
|
display: block; |
||||||
|
margin: 0em 0.2em 0em 1em; |
||||||
|
width: 90%; |
||||||
|
} |
||||||
|
|
||||||
|
.pure-table td, .pure-table th { |
||||||
|
padding: 0.5em 0.5em; |
||||||
|
} |
||||||
|
|
||||||
|
/* make images size-up */ |
||||||
|
.xx-pure-img-responsive { |
||||||
|
max-width: 100%; |
||||||
|
height: auto; |
||||||
|
} |
||||||
|
|
||||||
|
/* Add transition to containers so they can push in and out */ |
||||||
|
#layout, #menu, .menu-link { |
||||||
|
-webkit-transition: all 0.2s ease-out; |
||||||
|
-moz-transition: all 0.2s ease-out; |
||||||
|
-ms-transition: all 0.2s ease-out; |
||||||
|
-o-transition: all 0.2s ease-out; |
||||||
|
transition: all 0.2s ease-out; |
||||||
|
} |
||||||
|
|
||||||
|
/* This is the parent `<div>` that contains the menu and the content area */ |
||||||
|
#layout { |
||||||
|
position: relative; |
||||||
|
padding-left: 0; |
||||||
|
} |
||||||
|
#layout.active #menu { |
||||||
|
left: 150px; |
||||||
|
width: 150px; |
||||||
|
} |
||||||
|
|
||||||
|
#layout.active .menu-link { |
||||||
|
left: 150px; |
||||||
|
} |
||||||
|
|
||||||
|
div.tt { |
||||||
|
font-family: monospace; |
||||||
|
font-size: 120%; |
||||||
|
color: #390; |
||||||
|
background-color: #ddd; |
||||||
|
padding: 2px; |
||||||
|
margin: 2px 0; |
||||||
|
line-height: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
/* The content `<div>` */ |
||||||
|
.content { |
||||||
|
margin: 0 auto; |
||||||
|
padding: 0 2em; |
||||||
|
max-width: 800px; |
||||||
|
margin-bottom: 50px; |
||||||
|
line-height: 1.6em; |
||||||
|
} |
||||||
|
|
||||||
|
.header { |
||||||
|
margin: 0; |
||||||
|
color: #333; |
||||||
|
text-align: center; |
||||||
|
padding: 2.5em 2em 0; |
||||||
|
border-bottom: 1px solid #eee; |
||||||
|
background-color: #fc0; |
||||||
|
} |
||||||
|
.header h1 { |
||||||
|
margin: 0.2em 0; |
||||||
|
font-size: 3em; |
||||||
|
font-weight: 300; |
||||||
|
} |
||||||
|
.header h1 .esp { |
||||||
|
font-size: 1.25em; |
||||||
|
} |
||||||
|
.jl { |
||||||
|
font: normal 800 1.5em sans-serif; |
||||||
|
position: relative; |
||||||
|
bottom: 19px; |
||||||
|
color: #9d1414; |
||||||
|
margin-left: 3px; |
||||||
|
} |
||||||
|
|
||||||
|
.content-subhead { |
||||||
|
margin: 50px 0 20px 0; |
||||||
|
font-weight: 300; |
||||||
|
color: #888; |
||||||
|
} |
||||||
|
|
||||||
|
form button { |
||||||
|
margin-top: 0.5em; |
||||||
|
} |
||||||
|
.button-primary { |
||||||
|
background-color: #99f; |
||||||
|
} |
||||||
|
.button-selected { |
||||||
|
background-color: #fc6; |
||||||
|
} |
||||||
|
|
||||||
|
/* Text console */ |
||||||
|
pre.console { |
||||||
|
background-color: #663300; |
||||||
|
-moz-border-radius: 5px; |
||||||
|
-webkit-border-radius: 5px; |
||||||
|
border-radius: 5px; |
||||||
|
border: 0px solid #000000; |
||||||
|
color: #66ff66; |
||||||
|
padding: 5px; |
||||||
|
} |
||||||
|
|
||||||
|
pre.console a { |
||||||
|
color: #66ff66; |
||||||
|
} |
||||||
|
|
||||||
|
/* log page */ |
||||||
|
.dbg-btn, #refresh-button { |
||||||
|
vertical-align: baseline; |
||||||
|
} |
||||||
|
|
||||||
|
.lock-icon { |
||||||
|
background-image: url("/wifi/icons.png"); |
||||||
|
background-color: transparent; |
||||||
|
width: 32px; |
||||||
|
height: 32px; |
||||||
|
display: inline-block; |
||||||
|
} |
||||||
|
|
||||||
|
#menu { |
||||||
|
margin-left: -150px; |
||||||
|
width: 150px; |
||||||
|
position: fixed; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
bottom: 0; |
||||||
|
z-index: 1000; |
||||||
|
background: #191818; |
||||||
|
overflow-y: auto; |
||||||
|
-webkit-overflow-scrolling: touch; |
||||||
|
} |
||||||
|
|
||||||
|
#menu a { |
||||||
|
color: #999; |
||||||
|
border: none; |
||||||
|
padding: 0.6em 0 0.6em 0.6em; |
||||||
|
} |
||||||
|
|
||||||
|
#menu .pure-menu, #menu .pure-menu ul { |
||||||
|
border: none; |
||||||
|
background: transparent; |
||||||
|
} |
||||||
|
|
||||||
|
#menu .pure-menu ul, #menu .pure-menu .menu-item-divided { |
||||||
|
border-top: 1px solid #333; |
||||||
|
} |
||||||
|
|
||||||
|
#menu .pure-menu li a:hover, #menu .pure-menu li a:focus { |
||||||
|
background: #333; |
||||||
|
} |
||||||
|
|
||||||
|
#menu .pure-menu-selected, #menu .pure-menu-heading { |
||||||
|
background: #9d1414; |
||||||
|
} |
||||||
|
|
||||||
|
#menu .pure-menu-selected a { |
||||||
|
color: #fff; |
||||||
|
} |
||||||
|
|
||||||
|
#menu .pure-menu-heading { |
||||||
|
font-size: 110%; |
||||||
|
color: #fff; |
||||||
|
margin: 0; |
||||||
|
text-transform: none; |
||||||
|
} |
||||||
|
|
||||||
|
#menu .pure-menu-heading img { |
||||||
|
vertical-align: middle; |
||||||
|
top: -1px; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
#menu .pure-menu-item { |
||||||
|
height:2em; |
||||||
|
} |
||||||
|
|
||||||
|
/* -- Dynamic Button For Responsive Menu -------------------------------------*/ |
||||||
|
|
||||||
|
.menu-link { |
||||||
|
position: fixed; |
||||||
|
display: block; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
background: #000; |
||||||
|
background: rgba(0,0,0,0.7); |
||||||
|
font-size: 10px; |
||||||
|
z-index: 10; |
||||||
|
width: 2em; |
||||||
|
height: auto; |
||||||
|
padding: 2.1em 1.6em; |
||||||
|
} |
||||||
|
|
||||||
|
.menu-link:hover, .menu-link:focus { |
||||||
|
background: #000; |
||||||
|
} |
||||||
|
|
||||||
|
.menu-link span { |
||||||
|
position: relative; |
||||||
|
display: block; |
||||||
|
} |
||||||
|
|
||||||
|
.menu-link span, .menu-link span:before, .menu-link span:after { |
||||||
|
background-color: #fff; |
||||||
|
width: 100%; |
||||||
|
height: 0.2em; |
||||||
|
} |
||||||
|
|
||||||
|
.menu-link span:before, .menu-link span:after { |
||||||
|
position: absolute; |
||||||
|
margin-top: -0.6em; |
||||||
|
content: " "; |
||||||
|
} |
||||||
|
|
||||||
|
.menu-link span:after { |
||||||
|
margin-top: 0.6em; |
||||||
|
} |
||||||
|
|
||||||
|
/* -- Responsive Styles (Media Queries) ------------------------------------- */ |
||||||
|
|
||||||
|
@media (min-width: 56em) { |
||||||
|
.header, .content { |
||||||
|
padding-left: 2em; |
||||||
|
padding-right: 2em; |
||||||
|
} |
||||||
|
|
||||||
|
#layout { |
||||||
|
padding-left: 150px; |
||||||
|
left: 0; |
||||||
|
} |
||||||
|
#menu { |
||||||
|
left: 150px; |
||||||
|
} |
||||||
|
|
||||||
|
.menu-link { |
||||||
|
position: fixed; |
||||||
|
left: 150px; |
||||||
|
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
#layout.active .menu-link { |
||||||
|
left: 150px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media (max-width: 56em) { |
||||||
|
#layout.active { |
||||||
|
position: relative; |
||||||
|
left: 150px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/*===== spinners and notification messages */ |
||||||
|
|
||||||
|
#messages { |
||||||
|
position: absolute; |
||||||
|
left: 25%; |
||||||
|
width: 50%; |
||||||
|
top: 10; |
||||||
|
z-index: 200; |
||||||
|
font-size: 110%; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
#warning { |
||||||
|
background-color: #933; |
||||||
|
color: #fcc; |
||||||
|
padding: 0.1em 0.4em; |
||||||
|
} |
||||||
|
#notification { |
||||||
|
background-color: #693; |
||||||
|
color: #cfc; |
||||||
|
padding: 0.1em 0.4em; |
||||||
|
} |
||||||
|
|
||||||
|
#spinner { |
||||||
|
position: absolute; |
||||||
|
right: 10%; |
||||||
|
top: 20; |
||||||
|
z-index: 1000; |
||||||
|
} |
||||||
|
.spinner { |
||||||
|
height: 50px; |
||||||
|
width: 50px; |
||||||
|
-webkit-animation: rotation 1s infinite linear; |
||||||
|
-moz-animation: rotation 1s infinite linear; |
||||||
|
-o-animation: rotation 1s infinite linear; |
||||||
|
animation: rotation 1s infinite linear; |
||||||
|
border-left: 10px solid rgba(204, 51, 0, 0.15); |
||||||
|
border-right: 10px solid rgba(204, 51, 0, 0.15); |
||||||
|
border-bottom: 10px solid rgba(204, 51, 0, 0.15); |
||||||
|
border-top: 10px solid rgba(204, 51, 0, 0.8); |
||||||
|
border-radius: 100%; |
||||||
|
} |
||||||
|
.spinner-small { |
||||||
|
display: inline-block; |
||||||
|
height: 1em; |
||||||
|
width: 1em; |
||||||
|
border-width: 4px; |
||||||
|
} |
||||||
|
|
||||||
|
@-webkit-keyframes rotation { |
||||||
|
from { |
||||||
|
-webkit-transform: rotate(0deg); |
||||||
|
} |
||||||
|
to { |
||||||
|
-webkit-transform: rotate(359deg); |
||||||
|
} |
||||||
|
} |
||||||
|
@-moz-keyframes rotation { |
||||||
|
from { |
||||||
|
-moz-transform: rotate(0deg); |
||||||
|
} |
||||||
|
to { |
||||||
|
-moz-transform: rotate(359deg); |
||||||
|
} |
||||||
|
} |
||||||
|
@-o-keyframes rotation { |
||||||
|
from { |
||||||
|
-o-transform: rotate(0deg); |
||||||
|
} |
||||||
|
to { |
||||||
|
-o-transform: rotate(359deg); |
||||||
|
} |
||||||
|
} |
||||||
|
@keyframes rotation { |
||||||
|
from { |
||||||
|
transform: rotate(0deg); |
||||||
|
} |
||||||
|
to { |
||||||
|
transform: rotate(359deg); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,473 @@ |
|||||||
|
//===== Collection of small utilities
|
||||||
|
|
||||||
|
/* |
||||||
|
* Bind/Unbind events |
||||||
|
* |
||||||
|
* Usage: |
||||||
|
* var el = document.getElementyById('#container'); |
||||||
|
* bnd(el, 'click', function() { |
||||||
|
* console.log('clicked'); |
||||||
|
* }); |
||||||
|
*/ |
||||||
|
|
||||||
|
var bnd = function( |
||||||
|
d, // a DOM element
|
||||||
|
e, // an event name such as "click"
|
||||||
|
f // a handler function
|
||||||
|
){ |
||||||
|
d.addEventListener(e, f, false); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Create DOM element |
||||||
|
* |
||||||
|
* Usage: |
||||||
|
* var el = m('<h1>Hello</h1>'); |
||||||
|
* document.body.appendChild(el); |
||||||
|
* |
||||||
|
* Copyright (C) 2011 Jed Schmidt <http://jed.is> - WTFPL
|
||||||
|
* More: https://gist.github.com/966233
|
||||||
|
*/ |
||||||
|
|
||||||
|
var m = function( |
||||||
|
a, // an HTML string
|
||||||
|
b, // placeholder
|
||||||
|
c // placeholder
|
||||||
|
){ |
||||||
|
b = document; // get the document,
|
||||||
|
c = b.createElement("p"); // create a container element,
|
||||||
|
c.innerHTML = a; // write the HTML to it, and
|
||||||
|
a = b.createDocumentFragment(); // create a fragment.
|
||||||
|
|
||||||
|
while ( // while
|
||||||
|
b = c.firstChild // the container element has a first child
|
||||||
|
) a.appendChild(b); // append the child to the fragment,
|
||||||
|
|
||||||
|
return a // and then return the fragment.
|
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* DOM selector |
||||||
|
* |
||||||
|
* Usage: |
||||||
|
* $('div'); |
||||||
|
* $('#name'); |
||||||
|
* $('.name'); |
||||||
|
* |
||||||
|
* Copyright (C) 2011 Jed Schmidt <http://jed.is> - WTFPL
|
||||||
|
* More: https://gist.github.com/991057
|
||||||
|
*/ |
||||||
|
|
||||||
|
var $ = function( |
||||||
|
a, // take a simple selector like "name", "#name", or ".name", and
|
||||||
|
b // an optional context, and
|
||||||
|
){ |
||||||
|
a = a.match(/^(\W)?(.*)/); // split the selector into name and symbol.
|
||||||
|
return( // return an element or list, from within the scope of
|
||||||
|
b // the passed context
|
||||||
|
|| document // or document,
|
||||||
|
)[ |
||||||
|
"getElement" + ( // obtained by the appropriate method calculated by
|
||||||
|
a[1] |
||||||
|
? a[1] == "#" |
||||||
|
? "ById" // the node by ID,
|
||||||
|
: "sByClassName" // the nodes by class name, or
|
||||||
|
: "sByTagName" // the nodes by tag name,
|
||||||
|
) |
||||||
|
]( |
||||||
|
a[2] // called with the name.
|
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Get cross browser xhr object |
||||||
|
* |
||||||
|
* Copyright (C) 2011 Jed Schmidt <http://jed.is>
|
||||||
|
* More: https://gist.github.com/993585
|
||||||
|
*/ |
||||||
|
|
||||||
|
var j = function( |
||||||
|
a // cursor placeholder
|
||||||
|
){ |
||||||
|
for( // for all a
|
||||||
|
a=0; // from 0
|
||||||
|
a<4; // to 4,
|
||||||
|
a++ // incrementing
|
||||||
|
) try { // try
|
||||||
|
return a // returning
|
||||||
|
? new ActiveXObject( // a new ActiveXObject
|
||||||
|
[ // reflecting
|
||||||
|
, // (elided)
|
||||||
|
"Msxml2", // the various
|
||||||
|
"Msxml3", // working
|
||||||
|
"Microsoft" // options
|
||||||
|
][a] + // for Microsoft implementations, and
|
||||||
|
".XMLHTTP" // the appropriate suffix,
|
||||||
|
) // but make sure to
|
||||||
|
: new XMLHttpRequest // try the w3c standard first, and
|
||||||
|
} |
||||||
|
|
||||||
|
catch(e){} // ignore when it fails.
|
||||||
|
} |
||||||
|
|
||||||
|
// createElement short-hand
|
||||||
|
|
||||||
|
e = function(a) { return document.createElement(a); } |
||||||
|
|
||||||
|
// chain onload handlers
|
||||||
|
|
||||||
|
function onLoad(f) { |
||||||
|
var old = window.onload; |
||||||
|
if (typeof old != 'function') { |
||||||
|
window.onload = f; |
||||||
|
} else { |
||||||
|
window.onload = function() { |
||||||
|
old(); |
||||||
|
f(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//===== helpers to add/remove/toggle HTML element classes
|
||||||
|
|
||||||
|
function addClass(el, cl) { |
||||||
|
el.className += ' ' + cl; |
||||||
|
} |
||||||
|
function removeClass(el, cl) { |
||||||
|
var cls = el.className.split(/\s+/), |
||||||
|
l = cls.length; |
||||||
|
for (var i=0; i<l; i++) { |
||||||
|
if (cls[i] === cl) cls.splice(i, 1); |
||||||
|
} |
||||||
|
el.className = cls.join(' '); |
||||||
|
return cls.length != l |
||||||
|
} |
||||||
|
function toggleClass(el, cl) { |
||||||
|
if (!removeClass(el, cl)) addClass(el, cl); |
||||||
|
} |
||||||
|
|
||||||
|
//===== AJAX
|
||||||
|
|
||||||
|
function ajaxReq(method, url, ok_cb, err_cb) { |
||||||
|
var xhr = j(); |
||||||
|
xhr.open(method, url, true); |
||||||
|
var timeout = setTimeout(function() { |
||||||
|
xhr.abort(); |
||||||
|
console.log("XHR abort:", method, url); |
||||||
|
xhr.status = 599; |
||||||
|
xhr.responseText = "request time-out"; |
||||||
|
}, 9000); |
||||||
|
xhr.onreadystatechange = function() { |
||||||
|
if (xhr.readyState != 4) { return; } |
||||||
|
clearTimeout(timeout); |
||||||
|
if (xhr.status >= 200 && xhr.status < 300) { |
||||||
|
console.log("XHR done:", method, url, "->", xhr.status); |
||||||
|
ok_cb(xhr.responseText); |
||||||
|
} else { |
||||||
|
console.log("XHR ERR :", method, url, "->", xhr.status, xhr.responseText, xhr); |
||||||
|
err_cb(xhr.status, xhr.responseText); |
||||||
|
} |
||||||
|
} |
||||||
|
console.log("XHR send:", method, url); |
||||||
|
try { |
||||||
|
xhr.send(); |
||||||
|
} catch(err) { |
||||||
|
console.log("XHR EXC :", method, url, "->", err); |
||||||
|
err_cb(599, err); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function dispatchJson(resp, ok_cb, err_cb) { |
||||||
|
var j; |
||||||
|
try { j = JSON.parse(resp); } |
||||||
|
catch(err) { |
||||||
|
console.log("JSON parse error: " + err + ". In: " + resp); |
||||||
|
err_cb(500, "JSON parse error: " + err); |
||||||
|
return; |
||||||
|
} |
||||||
|
ok_cb(j); |
||||||
|
} |
||||||
|
|
||||||
|
function ajaxJson(method, url, ok_cb, err_cb) { |
||||||
|
ajaxReq(method, url, function(resp) { dispatchJson(resp, ok_cb, err_cb); }, err_cb); |
||||||
|
} |
||||||
|
|
||||||
|
function ajaxSpin(method, url, ok_cb, err_cb) { |
||||||
|
$("#spinner").removeAttribute('hidden'); |
||||||
|
ajaxReq(method, url, function(resp) { |
||||||
|
$("#spinner").setAttribute('hidden', ''); |
||||||
|
ok_cb(resp); |
||||||
|
}, function(status, statusText) { |
||||||
|
$("#spinner").setAttribute('hidden', ''); |
||||||
|
//showWarning("Error: " + statusText);
|
||||||
|
err_cb(status, statusText); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function ajaxJsonSpin(method, url, ok_cb, err_cb) { |
||||||
|
ajaxSpin(method, url, function(resp) { dispatchJson(resp, ok_cb, err_cb); }, err_cb); |
||||||
|
} |
||||||
|
|
||||||
|
//===== main menu, header spinner and notification boxes
|
||||||
|
|
||||||
|
onLoad(function() { |
||||||
|
var l = $("#layout"); |
||||||
|
var o = l.childNodes[0]; |
||||||
|
// spinner
|
||||||
|
l.insertBefore(m('<div id="spinner" class="spinner" hidden></div>'), o); |
||||||
|
// notification boxes
|
||||||
|
l.insertBefore(m( |
||||||
|
'<div id="messages"><div id="warning" hidden></div><div id="notification" hidden></div></div>'), o); |
||||||
|
// menu hamburger button
|
||||||
|
l.insertBefore(m('<a href="#menu" id="menuLink" class="menu-link"><span></span></a>'), o); |
||||||
|
// menu left-pane
|
||||||
|
var mm = m( |
||||||
|
'<div id="menu">\ |
||||||
|
<div class="pure-menu">\ |
||||||
|
<a class="pure-menu-heading" href="https://github.com/jeelabs/esp-link">\ |
||||||
|
<img src="/favicon.ico" height="32"> esp-link</a>\ |
||||||
|
<ul id="menu-list" class="pure-menu-list"></ul>\ |
||||||
|
</div>\ |
||||||
|
</div>\ |
||||||
|
'); |
||||||
|
l.insertBefore(mm, o); |
||||||
|
|
||||||
|
// make hamburger button pull out menu
|
||||||
|
var ml = $('#menuLink'), mm = $('#menu'); |
||||||
|
bnd(ml, 'click', function (e) { |
||||||
|
console.log("hamburger time"); |
||||||
|
var active = 'active'; |
||||||
|
e.preventDefault(); |
||||||
|
toggleClass(l, active); |
||||||
|
toggleClass(mm, active); |
||||||
|
toggleClass(ml, active); |
||||||
|
}); |
||||||
|
|
||||||
|
// populate menu via ajax call
|
||||||
|
var getMenu = function() { |
||||||
|
ajaxJson("GET", "/menu", function(data) { |
||||||
|
var html = "", path = window.location.pathname; |
||||||
|
for (var i=0; i<data.menu.length; i+=2) { |
||||||
|
var href = data.menu[i+1]; |
||||||
|
html = html.concat(" <li class=\"pure-menu-item" + |
||||||
|
(path === href ? " pure-menu-selected" : "") + "\">" + |
||||||
|
"<a href=\"" + href + "\" class=\"pure-menu-link\">" + |
||||||
|
data.menu[i] + "</a></li>"); |
||||||
|
} |
||||||
|
$("#menu-list").innerHTML = html; |
||||||
|
|
||||||
|
v = $("#version"); |
||||||
|
if (v != null) { v.innerHTML = data.version; } |
||||||
|
}, function() { setTimeout(getMenu, 1000); }); |
||||||
|
}; |
||||||
|
getMenu(); |
||||||
|
}); |
||||||
|
|
||||||
|
//===== Wifi info
|
||||||
|
|
||||||
|
function showWifiInfo(data) { |
||||||
|
Object.keys(data).forEach(function(v) { |
||||||
|
el = $("#wifi-" + v); |
||||||
|
if (el != null) { |
||||||
|
if (el.nodeName === "INPUT") el.value = data[v]; |
||||||
|
else el.innerHTML = data[v]; |
||||||
|
} |
||||||
|
}); |
||||||
|
var dhcp = $('#dhcp-r'+data.dhcp); |
||||||
|
if (dhcp) dhcp.click(); |
||||||
|
$("#wifi-spinner").setAttribute("hidden", ""); |
||||||
|
$("#wifi-table").removeAttribute("hidden"); |
||||||
|
currAp = data.ssid; |
||||||
|
} |
||||||
|
|
||||||
|
function getWifiInfo() { |
||||||
|
ajaxJson('GET', "/wifi/info", showWifiInfo, |
||||||
|
function(s, st) { window.setTimeout(getWifiInfo, 1000); }); |
||||||
|
} |
||||||
|
|
||||||
|
//===== Notifications
|
||||||
|
|
||||||
|
function showWarning(text) { |
||||||
|
var el = $("#warning"); |
||||||
|
el.innerHTML = text; |
||||||
|
el.removeAttribute('hidden'); |
||||||
|
} |
||||||
|
function hideWarning() { |
||||||
|
el = $("#warning").setAttribute('hidden', ''); |
||||||
|
} |
||||||
|
var notifTimeout = null; |
||||||
|
function showNotification(text) { |
||||||
|
var el = $("#notification"); |
||||||
|
el.innerHTML = text; |
||||||
|
el.removeAttribute('hidden'); |
||||||
|
if (notifTimeout != null) clearTimeout(notifTimeout); |
||||||
|
notifTimout = setTimeout(function() { |
||||||
|
el.setAttribute('hidden', ''); |
||||||
|
notifTimout = null; |
||||||
|
}, 4000); |
||||||
|
} |
||||||
|
|
||||||
|
//===== GPIO Pin mux card
|
||||||
|
|
||||||
|
var currPin; |
||||||
|
// pin={reset:12, isp:13, LED_conn:0, LED_ser:2}
|
||||||
|
function createInputForPin(pin) { |
||||||
|
var input = document.createElement("input"); |
||||||
|
input.type = "radio"; |
||||||
|
input.name = "pins"; |
||||||
|
input.data = pin.name; |
||||||
|
input.className = "pin-input"; |
||||||
|
input.value= pin.value; |
||||||
|
input.id = "opt-" + pin.value; |
||||||
|
if (currPin == pin.name) input.checked = "1"; |
||||||
|
|
||||||
|
var descr = m('<label for="opt-'+pin.value+'"><b>'+pin.name+":</b>"+pin.descr+"</label>"); |
||||||
|
var div = document.createElement("div"); |
||||||
|
div.appendChild(input); |
||||||
|
div.appendChild(descr); |
||||||
|
return div; |
||||||
|
} |
||||||
|
|
||||||
|
function displayPins(resp) { |
||||||
|
var po = $("#pin-mux"); |
||||||
|
po.innerHTML = ""; |
||||||
|
currPin = resp.curr; |
||||||
|
resp.map.forEach(function(v) { |
||||||
|
po.appendChild(createInputForPin(v)); |
||||||
|
}); |
||||||
|
var i, inputs = $(".pin-input"); |
||||||
|
for (i=0; i<inputs.length; i++) { |
||||||
|
inputs[i].onclick = function() { setPins(this.value, this.data) }; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
function fetchPins() { |
||||||
|
ajaxJson("GET", "/pins", displayPins, function() { |
||||||
|
window.setTimeout(fetchPins, 1000); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function setPins(v, name) { |
||||||
|
ajaxSpin("POST", "/pins?map="+v, function() { |
||||||
|
showNotification("Pin assignment changed to " + name); |
||||||
|
}, function() { |
||||||
|
showNotification("Pin assignment change failed"); |
||||||
|
window.setTimeout(fetchPins, 100); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
//===== TCP client card
|
||||||
|
|
||||||
|
function tcpEn(){return document.querySelector('input[name="tcp_enable"]')} |
||||||
|
function rssiEn(){return document.querySelector('input[name="rssi_enable"]')} |
||||||
|
function apiKey(){return document.querySelector('input[name="api_key"]')} |
||||||
|
|
||||||
|
function changeTcpClient(e) { |
||||||
|
e.preventDefault(); |
||||||
|
var url = "tcpclient"; |
||||||
|
url += "?tcp_enable=" + tcpEn().checked; |
||||||
|
url += "&rssi_enable=" + rssiEn().checked; |
||||||
|
url += "&api_key=" + encodeURIComponent(apiKey().value); |
||||||
|
|
||||||
|
hideWarning(); |
||||||
|
var cb = $("#tcp-button"); |
||||||
|
addClass(cb, 'pure-button-disabled'); |
||||||
|
ajaxSpin("POST", url, function(resp) { |
||||||
|
removeClass(cb, 'pure-button-disabled'); |
||||||
|
fetchTcpClient(); |
||||||
|
}, function(s, st) { |
||||||
|
showWarning("Error: "+st); |
||||||
|
removeClass(cb, 'pure-button-disabled'); |
||||||
|
fetchTcpClient(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function displayTcpClient(resp) { |
||||||
|
tcpEn().checked = resp.tcp_enable > 0; |
||||||
|
rssiEn().checked = resp.rssi_enable > 0; |
||||||
|
apiKey().value = resp.api_key; |
||||||
|
} |
||||||
|
|
||||||
|
function fetchTcpClient() { |
||||||
|
ajaxJson("GET", "/tcpclient", displayTcpClient, function() { |
||||||
|
window.setTimeout(fetchTcpClient, 1000); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
//===== MQTT cards
|
||||||
|
|
||||||
|
function changeMqtt(e) { |
||||||
|
e.preventDefault(); |
||||||
|
var url = "mqtt?1=1"; |
||||||
|
var i, inputs = document.querySelectorAll('#mqtt-form input'); |
||||||
|
for (i=0; i<inputs.length; i++) { |
||||||
|
if (inputs[i].type != "checkbox") |
||||||
|
url += "&" + inputs[i].name + "=" + inputs[i].value; |
||||||
|
}; |
||||||
|
|
||||||
|
hideWarning(); |
||||||
|
var cb = $("#mqtt-button"); |
||||||
|
addClass(cb, 'pure-button-disabled'); |
||||||
|
ajaxSpin("POST", url, function(resp) { |
||||||
|
showNotification("MQTT updated"); |
||||||
|
removeClass(cb, 'pure-button-disabled'); |
||||||
|
}, function(s, st) { |
||||||
|
showWarning("Error: "+st); |
||||||
|
removeClass(cb, 'pure-button-disabled'); |
||||||
|
window.setTimeout(fetchMqtt, 100); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function displayMqtt(data) { |
||||||
|
Object.keys(data).forEach(function(v) { |
||||||
|
el = $("#" + v); |
||||||
|
if (el != null) { |
||||||
|
if (el.nodeName === "INPUT") el.value = data[v]; |
||||||
|
else el.innerHTML = data[v]; |
||||||
|
return; |
||||||
|
} |
||||||
|
el = document.querySelector('input[name="' + v + '"]'); |
||||||
|
if (el != null) { |
||||||
|
if (el.type == "checkbox") el.checked = data[v] > 0; |
||||||
|
else el.value = data[v]; |
||||||
|
} |
||||||
|
}); |
||||||
|
$("#mqtt-spinner").setAttribute("hidden", ""); |
||||||
|
$("#mqtt-status-spinner").setAttribute("hidden", ""); |
||||||
|
$("#mqtt-form").removeAttribute("hidden"); |
||||||
|
$("#mqtt-status-form").removeAttribute("hidden"); |
||||||
|
|
||||||
|
var i, inputs = $("input"); |
||||||
|
for (i=0; i<inputs.length; i++) { |
||||||
|
if (inputs[i].type == "checkbox") |
||||||
|
inputs[i].onclick = function() { console.log(this); setMqtt(this.name, this.checked) }; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function fetchMqtt() { |
||||||
|
ajaxJson("GET", "/mqtt", displayMqtt, function() { |
||||||
|
window.setTimeout(fetchMqtt, 1000); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function changeMqttStatus(e) { |
||||||
|
e.preventDefault(); |
||||||
|
var v = document.querySelector('input[name="mqtt-status-topic"]').value; |
||||||
|
ajaxSpin("POST", "/mqtt?mqtt-status-topic=" + v, function() { |
||||||
|
showNotification("MQTT status settings updated"); |
||||||
|
}, function(s, st) { |
||||||
|
showWarning("Error: "+st); |
||||||
|
window.setTimeout(fetchMqtt, 100); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function setMqtt(name, v) { |
||||||
|
ajaxSpin("POST", "/mqtt?" + name + "=" + (v ? 1 : 0), function() { |
||||||
|
var n = name.replace("-enable", ""); |
||||||
|
showNotification(n + " is now " + (v ? "enabled" : "disabled")); |
||||||
|
}, function() { |
||||||
|
showWarning("Enable/disable failed"); |
||||||
|
window.setTimeout(fetchMqtt, 100); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,64 @@ |
|||||||
|
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||||
|
|
||||||
|
#include <esp8266.h> |
||||||
|
#include "pktbuf.h" |
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR |
||||||
|
PktBuf_Print(PktBuf *buf) { |
||||||
|
os_printf("PktBuf:"); |
||||||
|
for (int i=-16; i<0; i++) |
||||||
|
os_printf(" %02X", ((uint8_t*)buf)[i]); |
||||||
|
os_printf(" %p", buf); |
||||||
|
for (int i=0; i<16; i++) |
||||||
|
os_printf(" %02X", ((uint8_t*)buf)[i]); |
||||||
|
os_printf("\n"); |
||||||
|
os_printf("PktBuf: next=%p len=0x%04x\n", |
||||||
|
((void**)buf)[-4], ((uint16_t*)buf)[-6]); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
PktBuf * ICACHE_FLASH_ATTR |
||||||
|
PktBuf_New(uint16_t length) { |
||||||
|
PktBuf *buf = os_zalloc(length+sizeof(PktBuf)); |
||||||
|
buf->next = NULL; |
||||||
|
buf->filled = 0; |
||||||
|
//os_printf("PktBuf_New: %p l=%d->%d d=%p\n",
|
||||||
|
// buf, length, length+sizeof(PktBuf), buf->data);
|
||||||
|
return buf; |
||||||
|
} |
||||||
|
|
||||||
|
PktBuf * ICACHE_FLASH_ATTR |
||||||
|
PktBuf_Push(PktBuf *headBuf, PktBuf *buf) { |
||||||
|
if (headBuf == NULL) { |
||||||
|
//os_printf("PktBuf_Push: %p\n", buf);
|
||||||
|
return buf; |
||||||
|
} |
||||||
|
PktBuf *h = headBuf; |
||||||
|
while (h->next != NULL) h = h->next; |
||||||
|
h->next = buf; |
||||||
|
//os_printf("PktBuf_Push: %p->..->%p\n", headBuf, buf);
|
||||||
|
return headBuf; |
||||||
|
} |
||||||
|
|
||||||
|
PktBuf * ICACHE_FLASH_ATTR |
||||||
|
PktBuf_Unshift(PktBuf *headBuf, PktBuf *buf) { |
||||||
|
buf->next = headBuf; |
||||||
|
//os_printf("PktBuf_Unshift: %p->%p\n", buf, buf->next);
|
||||||
|
return buf; |
||||||
|
} |
||||||
|
|
||||||
|
PktBuf * ICACHE_FLASH_ATTR |
||||||
|
PktBuf_Shift(PktBuf *headBuf) { |
||||||
|
PktBuf *buf = headBuf->next; |
||||||
|
headBuf->next = NULL; |
||||||
|
//os_printf("PktBuf_Shift: (%p)->%p\n", headBuf, buf);
|
||||||
|
return buf; |
||||||
|
} |
||||||
|
|
||||||
|
PktBuf * ICACHE_FLASH_ATTR |
||||||
|
PktBuf_ShiftFree(PktBuf *headBuf) { |
||||||
|
PktBuf *buf = headBuf->next; |
||||||
|
//os_printf("PktBuf_ShiftFree: (%p)->%p\n", headBuf, buf);
|
||||||
|
os_free(headBuf); |
||||||
|
return buf; |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
||||||
|
|
||||||
|
#ifndef PKTBUF_H |
||||||
|
#define PKTBUF_H |
||||||
|
|
||||||
|
typedef struct PktBuf { |
||||||
|
struct PktBuf *next; // next buffer in chain
|
||||||
|
uint16_t filled; // number of bytes filled in buffer
|
||||||
|
uint8_t data[0]; // data in buffer
|
||||||
|
} PktBuf; |
||||||
|
|
||||||
|
// Allocate a new packet buffer of given length
|
||||||
|
PktBuf *PktBuf_New(uint16_t length); |
||||||
|
|
||||||
|
// Append a buffer to the end of a packet buffer queue, returns new head
|
||||||
|
PktBuf *PktBuf_Push(PktBuf *headBuf, PktBuf *buf); |
||||||
|
|
||||||
|
// Prepend a buffer to the beginning of a packet buffer queue, return new head
|
||||||
|
PktBuf * PktBuf_Unshift(PktBuf *headBuf, PktBuf *buf); |
||||||
|
|
||||||
|
// Shift first buffer off queue, returns new head (not shifted buffer!)
|
||||||
|
PktBuf *PktBuf_Shift(PktBuf *headBuf); |
||||||
|
|
||||||
|
// Shift first buffer off queue, free it, return new head
|
||||||
|
PktBuf *PktBuf_ShiftFree(PktBuf *headBuf); |
||||||
|
|
||||||
|
void PktBuf_Print(PktBuf *buf); |
||||||
|
|
||||||
|
#endif |
@ -1,86 +0,0 @@ |
|||||||
#include "proto.h" |
|
||||||
|
|
||||||
int8_t ICACHE_FLASH_ATTR
|
|
||||||
PROTO_Init(PROTO_PARSER* parser, PROTO_PARSE_CALLBACK* completeCallback, uint8_t* buf, uint16_t bufSize) { |
|
||||||
parser->buf = buf; |
|
||||||
parser->bufSize = bufSize; |
|
||||||
parser->dataLen = 0; |
|
||||||
parser->callback = completeCallback; |
|
||||||
parser->isEsc = 0; |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
int8_t ICACHE_FLASH_ATTR
|
|
||||||
PROTO_ParseByte(PROTO_PARSER* parser, uint8_t value) { |
|
||||||
switch (value) { |
|
||||||
case 0x7D: |
|
||||||
parser->isEsc = 1; |
|
||||||
break; |
|
||||||
|
|
||||||
case 0x7E: |
|
||||||
parser->dataLen = 0; |
|
||||||
parser->isEsc = 0; |
|
||||||
parser->isBegin = 1; |
|
||||||
break; |
|
||||||
|
|
||||||
case 0x7F: |
|
||||||
if (parser->callback != NULL) |
|
||||||
parser->callback(); |
|
||||||
parser->isBegin = 0; |
|
||||||
return 0; |
|
||||||
break; |
|
||||||
|
|
||||||
default: |
|
||||||
if (parser->isBegin == 0) break; |
|
||||||
|
|
||||||
if (parser->isEsc) { |
|
||||||
value ^= 0x20; |
|
||||||
parser->isEsc = 0; |
|
||||||
} |
|
||||||
|
|
||||||
if (parser->dataLen < parser->bufSize) |
|
||||||
parser->buf[parser->dataLen++] = value; |
|
||||||
|
|
||||||
break; |
|
||||||
} |
|
||||||
return -1; |
|
||||||
} |
|
||||||
|
|
||||||
int16_t ICACHE_FLASH_ATTR
|
|
||||||
PROTO_ParseRb(RINGBUF* rb, uint8_t* bufOut, uint16_t* len, uint16_t maxBufLen) { |
|
||||||
uint8_t c; |
|
||||||
|
|
||||||
PROTO_PARSER proto; |
|
||||||
PROTO_Init(&proto, NULL, bufOut, maxBufLen); |
|
||||||
while (RINGBUF_Get(rb, &c) == 0) { |
|
||||||
if (PROTO_ParseByte(&proto, c) == 0) { |
|
||||||
*len = proto.dataLen; |
|
||||||
return 0; |
|
||||||
} |
|
||||||
} |
|
||||||
return -1; |
|
||||||
} |
|
||||||
|
|
||||||
int16_t ICACHE_FLASH_ATTR
|
|
||||||
PROTO_AddRb(RINGBUF* rb, const uint8_t* packet, int16_t len) { |
|
||||||
uint16_t i = 2; |
|
||||||
if (RINGBUF_Put(rb, 0x7E) == -1) return -1; |
|
||||||
while (len--) { |
|
||||||
switch (*packet) { |
|
||||||
case 0x7D: |
|
||||||
case 0x7E: |
|
||||||
case 0x7F: |
|
||||||
if (RINGBUF_Put(rb, 0x7D) == -1) return -1; |
|
||||||
if (RINGBUF_Put(rb, *packet++ ^ 0x20) == -1) return -1; |
|
||||||
i += 2; |
|
||||||
break; |
|
||||||
default: |
|
||||||
if (RINGBUF_Put(rb, *packet++) == -1) return -1; |
|
||||||
i++; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
if (RINGBUF_Put(rb, 0x7F) == -1) return -1; |
|
||||||
|
|
||||||
return i; |
|
||||||
} |
|
@ -1,21 +0,0 @@ |
|||||||
#ifndef _PROTO_H_ |
|
||||||
#define _PROTO_H_ |
|
||||||
#include <esp8266.h> |
|
||||||
#include "ringbuf.h" |
|
||||||
|
|
||||||
typedef void (PROTO_PARSE_CALLBACK)(); |
|
||||||
|
|
||||||
typedef struct { |
|
||||||
uint8_t* buf; |
|
||||||
uint16_t bufSize; |
|
||||||
uint16_t dataLen; |
|
||||||
uint8_t isEsc; |
|
||||||
uint8_t isBegin; |
|
||||||
PROTO_PARSE_CALLBACK* callback; |
|
||||||
} PROTO_PARSER; |
|
||||||
|
|
||||||
int8_t PROTO_Init(PROTO_PARSER* parser, PROTO_PARSE_CALLBACK* completeCallback, uint8_t* buf, uint16_t bufSize); |
|
||||||
int16_t PROTO_AddRb(RINGBUF* rb, const uint8_t* packet, int16_t len); |
|
||||||
int8_t PROTO_ParseByte(PROTO_PARSER* parser, uint8_t value); |
|
||||||
int16_t PROTO_ParseRb(RINGBUF* rb, uint8_t* bufOut, uint16_t* len, uint16_t maxBufLen); |
|
||||||
#endif |
|
@ -1,53 +0,0 @@ |
|||||||
/* str_queue.c
|
|
||||||
* |
|
||||||
* Copyright (c) 2014-2015, Tuan PM <tuanpm at live dot com> |
|
||||||
* All rights reserved. |
|
||||||
* |
|
||||||
* Redistribution and use in source and binary forms, with or without |
|
||||||
* modification, are permitted provided that the following conditions are met: |
|
||||||
* |
|
||||||
* * Redistributions of source code must retain the above copyright notice, |
|
||||||
* this list of conditions and the following disclaimer. |
|
||||||
* * Redistributions in binary form must reproduce the above copyright |
|
||||||
* notice, this list of conditions and the following disclaimer in the |
|
||||||
* documentation and/or other materials provided with the distribution. |
|
||||||
* * Neither the name of Redis nor the names of its contributors may be used |
|
||||||
* to endorse or promote products derived from this software without |
|
||||||
* specific prior written permission. |
|
||||||
* |
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|
||||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
||||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
||||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
||||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
||||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
||||||
* POSSIBILITY OF SUCH DAMAGE. |
|
||||||
*/ |
|
||||||
#include "queue.h" |
|
||||||
|
|
||||||
void ICACHE_FLASH_ATTR
|
|
||||||
QUEUE_Init(QUEUE* queue, int bufferSize) { |
|
||||||
queue->buf = (uint8_t*)os_zalloc(bufferSize); |
|
||||||
RINGBUF_Init(&queue->rb, queue->buf, bufferSize); |
|
||||||
} |
|
||||||
|
|
||||||
int32_t ICACHE_FLASH_ATTR
|
|
||||||
QUEUE_Puts(QUEUE* queue, uint8_t* buffer, uint16_t len) { |
|
||||||
return PROTO_AddRb(&queue->rb, buffer, len); |
|
||||||
} |
|
||||||
|
|
||||||
int32_t ICACHE_FLASH_ATTR
|
|
||||||
QUEUE_Gets(QUEUE* queue, uint8_t* buffer, uint16_t* len, uint16_t maxLen) { |
|
||||||
return PROTO_ParseRb(&queue->rb, buffer, len, maxLen); |
|
||||||
} |
|
||||||
|
|
||||||
bool ICACHE_FLASH_ATTR
|
|
||||||
QUEUE_IsEmpty(QUEUE* queue) { |
|
||||||
if (queue->rb.fill_cnt <= 0) |
|
||||||
return TRUE; |
|
||||||
return FALSE; |
|
||||||
} |
|
@ -1,46 +0,0 @@ |
|||||||
/* str_queue.h --
|
|
||||||
* |
|
||||||
* Copyright (c) 2014-2015, Tuan PM <tuanpm at live dot com> |
|
||||||
* All rights reserved. |
|
||||||
* |
|
||||||
* Redistribution and use in source and binary forms, with or without |
|
||||||
* modification, are permitted provided that the following conditions are met: |
|
||||||
* |
|
||||||
* * Redistributions of source code must retain the above copyright notice, |
|
||||||
* this list of conditions and the following disclaimer. |
|
||||||
* * Redistributions in binary form must reproduce the above copyright |
|
||||||
* notice, this list of conditions and the following disclaimer in the |
|
||||||
* documentation and/or other materials provided with the distribution. |
|
||||||
* * Neither the name of Redis nor the names of its contributors may be used |
|
||||||
* to endorse or promote products derived from this software without |
|
||||||
* specific prior written permission. |
|
||||||
* |
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|
||||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
||||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
||||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
||||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
||||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
||||||
* POSSIBILITY OF SUCH DAMAGE. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef USER_QUEUE_H_ |
|
||||||
#define USER_QUEUE_H_ |
|
||||||
#include <esp8266.h> |
|
||||||
#include "proto.h" |
|
||||||
#include "ringbuf.h" |
|
||||||
|
|
||||||
typedef struct { |
|
||||||
uint8_t* buf; |
|
||||||
RINGBUF rb; |
|
||||||
} QUEUE; |
|
||||||
|
|
||||||
void QUEUE_Init(QUEUE* queue, int bufferSize); |
|
||||||
int32_t QUEUE_Puts(QUEUE* queue, uint8_t* buffer, uint16_t len); |
|
||||||
int32_t QUEUE_Gets(QUEUE* queue, uint8_t* buffer, uint16_t* len, uint16_t maxLen); |
|
||||||
bool QUEUE_IsEmpty(QUEUE* queue); |
|
||||||
#endif /* USER_QUEUE_H_ */ |
|
@ -1,63 +0,0 @@ |
|||||||
#include "ringbuf.h" |
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief init a RINGBUF object |
|
||||||
* \param r pointer to a RINGBUF object |
|
||||||
* \param buf pointer to a byte array |
|
||||||
* \param size size of buf |
|
||||||
* \return 0 if successfull, otherwise failed |
|
||||||
*/ |
|
||||||
int16_t ICACHE_FLASH_ATTR
|
|
||||||
RINGBUF_Init(RINGBUF* r, uint8_t* buf, int32_t size) { |
|
||||||
if (r == NULL || buf == NULL || size < 2) return -1; |
|
||||||
|
|
||||||
r->p_o = r->p_r = r->p_w = buf; |
|
||||||
r->fill_cnt = 0; |
|
||||||
r->size = size; |
|
||||||
|
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief put a character into ring buffer |
|
||||||
* \param r pointer to a ringbuf object |
|
||||||
* \param c character to be put |
|
||||||
* \return 0 if successfull, otherwise failed |
|
||||||
*/ |
|
||||||
int16_t ICACHE_FLASH_ATTR
|
|
||||||
RINGBUF_Put(RINGBUF* r, uint8_t c) { |
|
||||||
if (r->fill_cnt >= r->size)return -1; // ring buffer is full, this should be atomic operation
|
|
||||||
|
|
||||||
|
|
||||||
r->fill_cnt++; // increase filled slots count, this should be atomic operation
|
|
||||||
|
|
||||||
|
|
||||||
*r->p_w++ = c; // put character into buffer
|
|
||||||
|
|
||||||
if (r->p_w >= r->p_o + r->size) // rollback if write pointer go pass
|
|
||||||
r->p_w = r->p_o; // the physical boundary
|
|
||||||
|
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief get a character from ring buffer |
|
||||||
* \param r pointer to a ringbuf object |
|
||||||
* \param c read character |
|
||||||
* \return 0 if successfull, otherwise failed |
|
||||||
*/ |
|
||||||
int16_t ICACHE_FLASH_ATTR
|
|
||||||
RINGBUF_Get(RINGBUF* r, uint8_t* c) { |
|
||||||
if (r->fill_cnt <= 0)return -1; // ring buffer is empty, this should be atomic operation
|
|
||||||
|
|
||||||
|
|
||||||
r->fill_cnt--; // decrease filled slots count
|
|
||||||
|
|
||||||
|
|
||||||
*c = *r->p_r++; // get the character out
|
|
||||||
|
|
||||||
if (r->p_r >= r->p_o + r->size) // rollback if write pointer go pass
|
|
||||||
r->p_r = r->p_o; // the physical boundary
|
|
||||||
|
|
||||||
return 0; |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
#ifndef _RING_BUF_H_ |
|
||||||
#define _RING_BUF_H_ |
|
||||||
|
|
||||||
#include <esp8266.h> |
|
||||||
|
|
||||||
typedef struct { |
|
||||||
uint8_t* p_o; /**< Original pointer */ |
|
||||||
uint8_t* volatile p_r; /**< Read pointer */ |
|
||||||
uint8_t* volatile p_w; /**< Write pointer */ |
|
||||||
volatile int32_t fill_cnt; /**< Number of filled slots */ |
|
||||||
int32_t size; /**< Buffer size */ |
|
||||||
} RINGBUF; |
|
||||||
|
|
||||||
int16_t RINGBUF_Init(RINGBUF* r, uint8_t* buf, int32_t size); |
|
||||||
int16_t RINGBUF_Put(RINGBUF* r, uint8_t c); |
|
||||||
int16_t RINGBUF_Get(RINGBUF* r, uint8_t* c); |
|
||||||
#endif |
|
@ -0,0 +1,6 @@ |
|||||||
|
#ifndef SLIP_H |
||||||
|
#define SLIP_H |
||||||
|
|
||||||
|
void slip_parse_buf(char *buf, short length); |
||||||
|
|
||||||
|
#endif |
Loading…
Reference in new issue