Removed example project junk & added UART drivers
This commit is contained in:
@@ -1,96 +1,2 @@
|
|||||||
# esp-httpd README #
|
# esp-vt100-firmware
|
||||||
|
ESP8266 Remote Terminal project
|
||||||
This is the demonstration project for the small but powerful libesphttpd webserver
|
|
||||||
for ESP8266(EX) chips. It is an example of how to make a module that can have
|
|
||||||
the AP it connects to configured over a webbrowser. It also illustrates multiple
|
|
||||||
flash layouts and some OTA update functionality.
|
|
||||||
|
|
||||||
## ABOUT THE WEBSERVER ##
|
|
||||||
|
|
||||||
The Good (aka: what's awesome)
|
|
||||||
- Supports multiple connections, for eg simultaneous html/css/js/images downloading
|
|
||||||
- Static files stored in flash, in an (optionally compressed) RO filesystem
|
|
||||||
- Pluggable using external cgi routines
|
|
||||||
- Simple template engine for mixed c and html things
|
|
||||||
- Usable as an embedded library - should be easy to drop into your existing projects
|
|
||||||
- Includes websocket support
|
|
||||||
|
|
||||||
The Bad (aka: what can be improved)
|
|
||||||
- Not built for speediness, although it's reasonable fast.
|
|
||||||
- Built according to what I remember of the HTTP protocol, not according to the
|
|
||||||
RFCs. Should work with most modern browsers, though.
|
|
||||||
- No support for https.
|
|
||||||
|
|
||||||
The Ugly (aka: bugs, misbehaviour)
|
|
||||||
- Possible buffer overflows (usually not remotely exploitable) due to no os_snprintf
|
|
||||||
This can be theoretically remedied by either Espressif including an os_snprintf in
|
|
||||||
their libs or by using some alternate printf lib, like elm-chans xprintf
|
|
||||||
|
|
||||||
## SOURCE OF THIS CODE ##
|
|
||||||
The official esphttpd repo lives at http://git.spritesserver.nl/esphttpd.git/ and
|
|
||||||
http://git.spritesserver.nl/libesphttpd.git/ . If you're a fan of Github, you can also
|
|
||||||
peruse the official mirror at https://github.com/Spritetm/esphttpd and https://github.com/Spritetm/libesphttpd . If
|
|
||||||
you want to discuss this code, there is a subforum at esp8266.com: http://www.esp8266.com/viewforum.php?f=34 .
|
|
||||||
|
|
||||||
|
|
||||||
## ABOUT THE EXAMPLE ##
|
|
||||||
|
|
||||||
When you flash the example into an ESP8266(EX) module, you get a small webserver with a few example
|
|
||||||
pages. If you've already connected your module to your WLAN before, it'll keep those settings. When
|
|
||||||
you haven't or the settings are wrong, keep GPIO0 for >5 seconds. The module will reboot into
|
|
||||||
its STA+AP mode. Connect a computer to the newly formed access point and browse to
|
|
||||||
http://192.168.4.1/wifi in order to connect the module to your WiFi network. The example also
|
|
||||||
allows you to control a LED that's connected to GPIO2.
|
|
||||||
|
|
||||||
## BUILDING EVERYTHING ##
|
|
||||||
|
|
||||||
For this, you need an environment that can compile ESP8266 firmware. Environments for this still
|
|
||||||
are in flux at the moment, but I'm using esp-open-sdk: https://github.com/pfalcon/esp-open-sdk .
|
|
||||||
You probably also need an UNIX-like system; I'm working on Debian Linux myself.
|
|
||||||
|
|
||||||
To manage the paths to all this, you can source a small shell fragment into your current session. For
|
|
||||||
example, I source a file with these contents:
|
|
||||||
|
|
||||||
export PATH=${PWD}/esp-open-sdk/xtensa-lx106-elf/bin:$PATH
|
|
||||||
export SDK_BASE=${PWD}/esp-open-sdk/sdk
|
|
||||||
export ESPTOOL=${PWD}/esptool/esptool.py
|
|
||||||
export ESPPORT=/dev/ttyUSB0
|
|
||||||
export ESPBAUD=460800
|
|
||||||
|
|
||||||
Actual setup of the SDK and toolchain is out of the scope of this document, so I hope this helps you
|
|
||||||
enough to set up your own if you haven't already.
|
|
||||||
|
|
||||||
If you have that, you can clone out the source code:
|
|
||||||
git clone http://git.spritesserver.nl/esphttpd.git/
|
|
||||||
|
|
||||||
This project makes use of heatshrink, which is a git submodule. To fetch the code:
|
|
||||||
|
|
||||||
cd esphttpd
|
|
||||||
git submodule init
|
|
||||||
git submodule update
|
|
||||||
|
|
||||||
Now, build the code:
|
|
||||||
|
|
||||||
make
|
|
||||||
|
|
||||||
Depending on the way you built it, esp-open-sdk sometimes patches Espressifs SDK, needing a slightly different
|
|
||||||
compiling process. If this is needed, you will get errors during compiling complaining about uint8_t being
|
|
||||||
undeclared. If this happens, try building like this:
|
|
||||||
|
|
||||||
make USE_OPENSDK=yes
|
|
||||||
|
|
||||||
You can also edit the Makefile to change this more permanently.
|
|
||||||
|
|
||||||
After the compile process, flash the code happens in 2 steps. First the code itself gets flashed. Reset the module into bootloader
|
|
||||||
mode and enter 'make flash'.
|
|
||||||
|
|
||||||
The 2nd step is to pack the static files the webserver will serve and flash that. Reset the module into
|
|
||||||
bootloader mode again and enter `make htmlflash`.
|
|
||||||
|
|
||||||
You should have a working webserver now.
|
|
||||||
|
|
||||||
## WRITING CODE FOR THE WEBSERVER ##
|
|
||||||
|
|
||||||
Please see the README.md of the libesphttpd project for the programming manual.
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Executable
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
yuicompressor html_orig/style.css > html/style.css
|
||||||
|
yuicompressor html_orig/script.js > html/script.js
|
||||||
|
minify --type=html html_orig/index.html -o html/index.html
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
Vendored
-2
@@ -1,2 +0,0 @@
|
|||||||
var t=function(a,b){return function(c,d){return a.replace(/#{([^}]*)}/g,function(a,f){return Function("x","with(x)return "+f).call(c,d||b||{})})}},s=function(a,b){return b?{get:function(c){return a[c]&&b.parse(a[c])},set:function(c,d){a[c]=b.stringify(d)}}:{}}(this.localStorage||{},JSON),p=function(a,b,c,d){c=c||document;d=c[b="on"+b];a=c[b]=function(e){d=d&&d(e=e||c.event);return(a=a&&b(e))?b:d};c=this},m=function(a,b,c){b=document;c=b.createElement("p");c.innerHTML=a;for(a=b.createDocumentFragment();b=
|
|
||||||
c.firstChild;)a.appendChild(b);return a},$=function(a,b){a=a.match(/^(\W)?(.*)/);return(b||document)["getElement"+(a[1]?a[1]=="#"?"ById":"sByClassName":"sByTagName")](a[2])},j=function(a){for(a=0;a<4;a++)try{return a?new ActiveXObject([,"Msxml2","Msxml3","Microsoft"][a]+".XMLHTTP"):new XMLHttpRequest}catch(b){}};
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head><title>Upgrade firmware</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css">
|
|
||||||
<script type="text/javascript" src="140medley.min.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
var xhr=j();
|
|
||||||
|
|
||||||
function doReboot() {
|
|
||||||
//Grab the /flash/reboot url in order to reboot into the new firmware.
|
|
||||||
xhr.open("GET", "reboot");
|
|
||||||
xhr.onreadystatechange=function() {
|
|
||||||
if (xhr.readyState==4 && xhr.status>=200 && xhr.status<300) {
|
|
||||||
//Reload the window after 5 seconds.
|
|
||||||
window.setTimeout(function() {
|
|
||||||
location.reload(true);
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Sets the progress bar to a specific level. Amt is between 0 and 1.
|
|
||||||
function setProgress(amt) {
|
|
||||||
$("#progressbarinner").style.width=String(amt*200)+"px";
|
|
||||||
}
|
|
||||||
|
|
||||||
//Called when the submit button is called.
|
|
||||||
function doUpgrade() {
|
|
||||||
//Grab the file, see if it's a real file.
|
|
||||||
var f=$("#file").files[0];
|
|
||||||
if (typeof f=='undefined') {
|
|
||||||
$("#remark").innerHTML="Can't read file!";
|
|
||||||
return
|
|
||||||
}
|
|
||||||
xhr.open("POST", "upload");
|
|
||||||
xhr.onreadystatechange=function() {
|
|
||||||
if (xhr.readyState==4 && xhr.status>=200 && xhr.status<300) {
|
|
||||||
setProgress(1);
|
|
||||||
if (xhr.responseText!="") {
|
|
||||||
$("#remark").innerHTML="Error: "+xhr.responseText;
|
|
||||||
} else {
|
|
||||||
$("#remark").innerHTML="Uploading done. Rebooting.";
|
|
||||||
doReboot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//If the webbrowser enables it, make progress bar show progress.
|
|
||||||
if (typeof xhr.upload.onprogress != 'undefined') {
|
|
||||||
xhr.upload.onprogress=function(e) {
|
|
||||||
setProgress(e.loaded / e.total);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Upload the file
|
|
||||||
xhr.send(f);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
window.onload=function(e) {
|
|
||||||
//Grab the initial remark telling user which userx.bin file to upload.
|
|
||||||
xhr.open("GET", "next");
|
|
||||||
xhr.onreadystatechange=function() {
|
|
||||||
if (xhr.readyState==4 && xhr.status>=200 && xhr.status<300) {
|
|
||||||
var txt="Please upload "+xhr.responseText+" or ota file.";
|
|
||||||
$("#remark").innerHTML=txt;
|
|
||||||
setProgress(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="main">
|
|
||||||
<h1>Update firmware</h1>
|
|
||||||
<div id="remark">Loading...</div>
|
|
||||||
<input type="file" id="file" />
|
|
||||||
<input type="submit" value="Upgrade!" onclick="doUpgrade()" />
|
|
||||||
<div id="progressbar"><div id="progressbarinner"></div></div>
|
|
||||||
</body>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
|
|
||||||
body {
|
|
||||||
background-color: #404040;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
#main {
|
|
||||||
background-color: #d0d0FF;
|
|
||||||
-moz-border-radius: 5px;
|
|
||||||
-webkit-border-radius: 5px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 2px solid #000000;
|
|
||||||
width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px
|
|
||||||
}
|
|
||||||
|
|
||||||
#progressbar {
|
|
||||||
margin: 10px;
|
|
||||||
padding: 0;
|
|
||||||
border: 1px solid #000000;
|
|
||||||
height: 20px;
|
|
||||||
width: 200px;
|
|
||||||
background-color: #808080;
|
|
||||||
}
|
|
||||||
|
|
||||||
#progressbarinner {
|
|
||||||
width: 10px;
|
|
||||||
height: 20px;
|
|
||||||
border: none;
|
|
||||||
background-color: #00ff00;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<!doctype html><meta charset=utf-8><title>ESP8266 Remote Terminal</title><meta name=viewport content="width=device-width,shrink-to-fit=no,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link rel=stylesheet href=style.css><script src=script.js></script><header>ESP8266 Remote Terminal</header><div id=screen></div><div id=buttons><button>1</button><button>2</button><button>3</button><button>4</button></div><script>init()</script>
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head><title>Esp8266 web server</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="main">
|
|
||||||
<h1>It Works</h1>
|
|
||||||
<p>
|
|
||||||
If you see this, it means the tiny li'l website in your ESP8266 does actually work. Fyi, this page has
|
|
||||||
been loaded <b>%counter%</b> times.
|
|
||||||
<ul>
|
|
||||||
<li>If you haven't connected this device to your WLAN network now, you can <a href="/wifi">do so.</a></li>
|
|
||||||
<li>You can also control the <a href="led.tpl">LED</a>.</li>
|
|
||||||
<li>If you have a firmware upgrade file and the module is compiled for firmware upgrades, you
|
|
||||||
can <a href="flash/index.html">upgrade the firmware</a> of this module.</li>
|
|
||||||
<li>Esphttpd now also supports <a href="websocket/index.html">websockets</a>.</li>
|
|
||||||
<li>Test esphttpd using the built-in <a href="test/">test suite</a></li>
|
|
||||||
<li>And because I can, here's a link to my <a href="http://spritesmods.com/?f=esphttpd">website</a></ul>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>And because we're on the Internets now, here are the required pictures of cats:<br />
|
|
||||||
<img src="cats/cross-eyed-cat.jpg"><br />
|
|
||||||
<img src="cats/junge-katze-iv.jpg"><br />
|
|
||||||
<img src="cats/kitten-loves-toy.jpg"><br />
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</body></html>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<html><head><title>Test</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="main">
|
|
||||||
<h1>The LED</h1>
|
|
||||||
<p>
|
|
||||||
If there's a LED connected to GPIO2, it's now %ledstate%. You can change that using the buttons below.
|
|
||||||
</p>
|
|
||||||
<form method="post" action="led.cgi">
|
|
||||||
<input type="submit" name="led" value="1">
|
|
||||||
<input type="submit" name="led" value="0">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body></html>
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
var $=function(d,c){d=d.match(/^(\W)?(.*)/);return(c||document)["getElement"+(d[1]?d[1]=="#"?"ById":"sByClassName":"sByTagName")](d[2])};var m=function(e,d,f){d=document;f=d.createElement("p");f.innerHTML=e;e=d.createDocumentFragment();while(d=f.firstChild){e.appendChild(d)}return e};(function(){function a(r){return document.createElement(r)}var b=26,k=10;var l={a:false,x:0,y:0,suppress:false,hidden:false};var o=[];var g=["#111213","#CC0000","#4E9A06","#C4A000","#3465A4","#75507B","#06989A","#D3D7CF","#555753","#EF2929","#8AE234","#FCE94F","#729FCF","#AD7FA8","#34E2E2","#EEEEEC"];function p(){o.forEach(function(r,s){r.t=" ";r.fg=7;r.bg=0;d(r)})}function c(s,r){return o[s*b+r]}function i(){return c(l.y,l.x)}function q(r){l.hidden=!r;l.a&=r;d(i(),l.a)}function f(s,r){l.suppress=true;d(i(),false);l.x=r;l.y=s;l.suppress=false;d(i(),l.a)}function d(s,r){var v=s.e,t,u;t=r?s.bg:s.fg;u=r?s.fg:s.bg;v.innerText=(s.t+" ")[0];v.style.color=e(t);v.style.backgroundColor=e(u);v.style.fontWeight=t>7?"bold":"normal"}function h(){o.forEach(function(s,t){var r=l.a&&(t==l.y*b+l.x);d(s,r)})}function j(s){if(s.length!=b*k*3){throw"Bad data format."}for(var t=0;t<b*k;t++){var r=o[t];r.t=s[t*3];r.fg=parseInt(s[t*3+1],16);r.bg=parseInt(s[t*3+2],16)}h()}function e(r){var r=parseInt(r);if(r<0||r>15){r=0}return g[r]}function n(){var u,t=$("#screen");for(var s=0;s<b*k;s++){u=a("span");if((s>0)&&(s%b==0)){t.appendChild(a("br"))}t.appendChild(u);var r={t:" ",fg:7,bg:0,e:u};o.push(r);d(r)}setInterval(function(){l.a=!l.a;if(l.hidden){l.a=false}if(!l.suppress){d(i(),l.a)}},500)}window.Term={init:n,load:j,setCursor:f,enableCursor:q,clear:p}})();(function(){var g="ws://"+window.location.host+"/ws/update.cgi";var d;function c(i){console.log("CONNECTED")}function b(i){console.error("SOCKET CLOSED")}function f(i){console.log("Message received!",i.data)}function e(i){console.error(i.data)}function a(i){d.send(i)}function h(){d=new WebSocket(g);d.onopen=c;d.onclose=b;d.onmessage=f;d.onerror=e;console.log("Opening socket.")}window.Conn={ws:null,init:h}})();function init(){Term.init();Conn.init()};
|
||||||
+1
-17
@@ -1,17 +1 @@
|
|||||||
|
html,body{background:#48505f;color:#eee;font-family:monospace;font-size:16pt;text-align:center}header{font-weight:bold;font-size:14pt;padding:6px;display:block}#screen{display:block;white-space:nowrap;background:#111213;border-radius:3px;padding:6px;box-shadow:inset 0 0 5px black;display:inline-block}#screen span{white-space:pre}#buttons{margin-top:10px}button{margin:0 2px;padding:10px 0;width:22%;max-width:80px;cursor:pointer}
|
||||||
body {
|
|
||||||
background-color: #404040;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
#main {
|
|
||||||
background-color: #d0d0FF;
|
|
||||||
-moz-border-radius: 5px;
|
|
||||||
-webkit-border-radius: 5px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 2px solid #000000;
|
|
||||||
width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<html><head><title>Webserver test</title></head>
|
|
||||||
<link rel="stylesheet" type="text/css" href="../wifi/style.css">
|
|
||||||
<script type="text/javascript" src="../wifi/140medley.min.js"></script>
|
|
||||||
<script type="text/javascript" src="test.js"></script>
|
|
||||||
<body>
|
|
||||||
<div id="main">
|
|
||||||
<div id="log">Initializing test...</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
|
|
||||||
/*
|
|
||||||
Code to test the webserver. This depends on:
|
|
||||||
- The cat images being available, for concurrent espfs testing
|
|
||||||
- the test.cgi script available, for generic data mangling tests
|
|
||||||
|
|
||||||
|
|
||||||
This test does a max of 4 requests in parallel. The nonos SDK supports a max of
|
|
||||||
5 connections; the default libesphttpd setting is 4 sockets at a time. Unfortunately,
|
|
||||||
the nonos sdk just closes all sockets opened after the available sockets are opened,
|
|
||||||
instead of queueing them until a socket frees up.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
function log(line) {
|
|
||||||
$("#log").insertAdjacentHTML('beforeend', line+'<br />');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Load an image multiple times in parallel
|
|
||||||
function testParLdImg(url, ct, doneFn) {
|
|
||||||
var im=[];
|
|
||||||
var state={"loaded":0, "count":ct, "doneFn": doneFn, "error":false};
|
|
||||||
for (var x=0; x<ct; x++) {
|
|
||||||
im[x]=new Image();
|
|
||||||
im[x].onload=function(no) {
|
|
||||||
log("File "+no+" loaded successfully.");
|
|
||||||
this.loaded++;
|
|
||||||
if (this.loaded==this.count) this.doneFn(!this.error);
|
|
||||||
}.bind(state, x);
|
|
||||||
im[x].onerror=function(no) {
|
|
||||||
log("Error loading image "+no+"!");
|
|
||||||
this.loaded++;
|
|
||||||
this.error++;
|
|
||||||
if (this.loaded==this.count) this.doneFn(!this.error);
|
|
||||||
}.bind(state, x);
|
|
||||||
im[x].src=url+"?"+Math.floor(Math.random()*100000).toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function testDownloadCgi(len, doneFn) {
|
|
||||||
var xhr=j();
|
|
||||||
var state={"len":len, "doneFn":doneFn, "ts": Date.now()};
|
|
||||||
xhr.open("GET", "test.cgi?len="+len+"&nocache="+Math.floor(Math.random()*100000).toString());
|
|
||||||
xhr.onreadystatechange=function() {
|
|
||||||
if (xhr.readyState==4 && xhr.status>=200 && xhr.status<300) {
|
|
||||||
if (xhr.response.length==this.len) {
|
|
||||||
log("Downloaded "+this.len+" bytes successfully.");
|
|
||||||
this.doneFn(true);
|
|
||||||
} else {
|
|
||||||
log("Downloaded "+xhr.response.length+" bytes successfully, but needed "+this.len+"!");
|
|
||||||
this.doneFn(false);
|
|
||||||
}
|
|
||||||
} else if (xhr.readyState==4) {
|
|
||||||
log("Failed! Error "+xhr.status);
|
|
||||||
this.doneFn(false);
|
|
||||||
}
|
|
||||||
}.bind(state);
|
|
||||||
//If the webbrowser enables it, show progress.
|
|
||||||
if (typeof xhr.onprogress != 'undefined') {
|
|
||||||
xhr.onprogress=function(e) {
|
|
||||||
if (Date.now()>this.ts+2000) {
|
|
||||||
log("..."+Math.floor(e.loaded*100/this.len).toString()+"%");
|
|
||||||
this.ts=Date.now();
|
|
||||||
}
|
|
||||||
}.bind(state);
|
|
||||||
}
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function testUploadCgi(len, doneFn) {
|
|
||||||
var xhr=j();
|
|
||||||
var state={"len":len, "doneFn":doneFn, "ts": Date.now()};
|
|
||||||
var data="";
|
|
||||||
for (var x=0; x<len; x++) data+="X";
|
|
||||||
xhr.open("POST", "test.cgi");
|
|
||||||
xhr.onreadystatechange=function() {
|
|
||||||
if (xhr.readyState==4 && xhr.status>=200 && xhr.status<300) {
|
|
||||||
var ulen=parseInt(xhr.responseText);
|
|
||||||
if (ulen==this.len) {
|
|
||||||
log("Uploaded "+this.len+" bytes successfully.");
|
|
||||||
this.doneFn(true);
|
|
||||||
} else {
|
|
||||||
log("Webserver received "+ulen+" bytes successfully, but sent "+this.len+"!");
|
|
||||||
this.doneFn(false);
|
|
||||||
}
|
|
||||||
} else if (xhr.readyState==4) {
|
|
||||||
log("Failed! Error "+xhr.status);
|
|
||||||
this.doneFn(false);
|
|
||||||
}
|
|
||||||
}.bind(state);
|
|
||||||
//If the webbrowser enables it, show progress.
|
|
||||||
if (typeof xhr.upload.onprogress != 'undefined') {
|
|
||||||
xhr.upload.onprogress=function(e) {
|
|
||||||
if (Date.now()>this.ts+2000) {
|
|
||||||
log("..."+Math.floor(e.loaded*100/e.total).toString()+"%");
|
|
||||||
this.ts=Date.now();
|
|
||||||
}
|
|
||||||
}.bind(state);
|
|
||||||
}
|
|
||||||
//Upload the file
|
|
||||||
xhr.send(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hammerNext(state, xhr) {
|
|
||||||
if (state.done==state.count) {
|
|
||||||
state.doneFn(!state.error);
|
|
||||||
}
|
|
||||||
if (state.started==state.count) return;
|
|
||||||
xhr.open("GET", "test.cgi?len="+state.len+"&nocache="+Math.floor(Math.random()*100000).toString());
|
|
||||||
xhr.onreadystatechange=function(xhr) {
|
|
||||||
if (xhr.readyState==4 && xhr.status>=200 && xhr.status<300) {
|
|
||||||
if (xhr.response.length==this.len) {
|
|
||||||
state.done++;
|
|
||||||
hammerNext(this, xhr);
|
|
||||||
} else {
|
|
||||||
log("Downloaded "+xhr.response.length+" bytes successfully, but needed "+this.len+"!");
|
|
||||||
state.done++;
|
|
||||||
hammerNext(this, xhr);
|
|
||||||
}
|
|
||||||
} else if (xhr.readyState==4) {
|
|
||||||
log("Failed! Error "+xhr.status);
|
|
||||||
state.done++;
|
|
||||||
hammerNext(this, xhr);
|
|
||||||
}
|
|
||||||
}.bind(state, xhr);
|
|
||||||
//If the webbrowser enables it, show progress.
|
|
||||||
if (typeof xhr.onprogress != 'undefined') {
|
|
||||||
xhr.onprogress=function(e) {
|
|
||||||
if (Date.now()>this.ts+2000) {
|
|
||||||
log("..."+state.done+"/"+state.count);
|
|
||||||
this.ts=Date.now();
|
|
||||||
}
|
|
||||||
}.bind(state);
|
|
||||||
}
|
|
||||||
state.started++;
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function testHammer(count, par, len, doneFn) {
|
|
||||||
var state={"count":count, "started":0, "done":0, "par":par, "len":len, "doneFn":doneFn, "ts": Date.now(), "error":false};
|
|
||||||
var xhr=[];
|
|
||||||
for (var i=0; i<par; i++) {
|
|
||||||
xhr[i]=j();
|
|
||||||
hammerNext(state, xhr[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var tstState=0;
|
|
||||||
var successCnt=0;
|
|
||||||
|
|
||||||
function nextTest(lastOk) {
|
|
||||||
if (tstState!=0) {
|
|
||||||
if (lastOk) {
|
|
||||||
log("<b>Success!</b>");
|
|
||||||
successCnt++;
|
|
||||||
} else {
|
|
||||||
log("<b>Test failed!</b>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tstState++;
|
|
||||||
if (tstState==1) {
|
|
||||||
log("Testing parallel load of espfs files...");
|
|
||||||
testParLdImg("../cats/kitten-loves-toy.jpg", 3, nextTest);
|
|
||||||
} else if (tstState==2) {
|
|
||||||
log("Testing GET request of 32K...");
|
|
||||||
testDownloadCgi(32*1024, nextTest);
|
|
||||||
} else if (tstState==3) {
|
|
||||||
log("Testing GET request of 128K...");
|
|
||||||
testDownloadCgi(128*1024, nextTest);
|
|
||||||
} else if (tstState==4) {
|
|
||||||
log("Testing GET request of 512K...");
|
|
||||||
testDownloadCgi(512*1024, nextTest);
|
|
||||||
} else if (tstState==5) {
|
|
||||||
log("Testing POST request of 512 bytes...");
|
|
||||||
testUploadCgi(512, nextTest);
|
|
||||||
} else if (tstState==6) {
|
|
||||||
log("Testing POST request of 16K bytes...");
|
|
||||||
testUploadCgi(16*1024, nextTest);
|
|
||||||
} else if (tstState==7) {
|
|
||||||
log("Testing POST request of 512K bytes...");
|
|
||||||
testUploadCgi(512*1024, nextTest);
|
|
||||||
} else if (tstState==8) {
|
|
||||||
log("Hammering webserver with 500 requests of size 512...");
|
|
||||||
testHammer(500, 3, 512, nextTest);
|
|
||||||
} else if (tstState==9) {
|
|
||||||
log("Hammering webserver with 500 requests of size 2048...");
|
|
||||||
testHammer(500, 3, 2048, nextTest);
|
|
||||||
} else {
|
|
||||||
log("Tests done! "+successCnt+" out of "+(tstState-1)+" tests were successful.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.onload=function(e) {
|
|
||||||
log("Starting tests.");
|
|
||||||
nextTest(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
|
|
||||||
<title>WebSocket Test</title>
|
|
||||||
|
|
||||||
<script language="javascript" type="text/javascript">
|
|
||||||
|
|
||||||
var wsUri = "ws://"+window.location.host+"/websocket/ws.cgi";
|
|
||||||
var output;
|
|
||||||
|
|
||||||
function init()
|
|
||||||
{
|
|
||||||
output = document.getElementById("output");
|
|
||||||
testWebSocket();
|
|
||||||
}
|
|
||||||
|
|
||||||
function testWebSocket()
|
|
||||||
{
|
|
||||||
websocket = new WebSocket(wsUri);
|
|
||||||
websocket.onopen = function(evt) { onOpen(evt) };
|
|
||||||
websocket.onclose = function(evt) { onClose(evt) };
|
|
||||||
websocket.onmessage = function(evt) { onMessage(evt) };
|
|
||||||
websocket.onerror = function(evt) { onError(evt) };
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOpen(evt)
|
|
||||||
{
|
|
||||||
writeToScreen("CONNECTED");
|
|
||||||
doSend("WebSocket rocks");
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClose(evt)
|
|
||||||
{
|
|
||||||
writeToScreen("DISCONNECTED");
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMessage(evt)
|
|
||||||
{
|
|
||||||
writeToScreen('<span style="color: blue;">RECEIVED: ' + evt.data+'</span>');
|
|
||||||
// websocket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onError(evt)
|
|
||||||
{
|
|
||||||
writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function doSend(message)
|
|
||||||
{
|
|
||||||
writeToScreen("SENT: " + message);
|
|
||||||
websocket.send(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeToScreen(message)
|
|
||||||
{
|
|
||||||
var pre = document.createElement("p");
|
|
||||||
pre.style.wordWrap = "break-word";
|
|
||||||
pre.innerHTML = message;
|
|
||||||
output.appendChild(pre);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("load", init, false);
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h2>WebSocket Test</h2>
|
|
||||||
|
|
||||||
<div id="output"></div>
|
|
||||||
Vendored
-2
@@ -1,2 +0,0 @@
|
|||||||
var t=function(a,b){return function(c,d){return a.replace(/#{([^}]*)}/g,function(a,f){return Function("x","with(x)return "+f).call(c,d||b||{})})}},s=function(a,b){return b?{get:function(c){return a[c]&&b.parse(a[c])},set:function(c,d){a[c]=b.stringify(d)}}:{}}(this.localStorage||{},JSON),p=function(a,b,c,d){c=c||document;d=c[b="on"+b];a=c[b]=function(e){d=d&&d(e=e||c.event);return(a=a&&b(e))?b:d};c=this},m=function(a,b,c){b=document;c=b.createElement("p");c.innerHTML=a;for(a=b.createDocumentFragment();b=
|
|
||||||
c.firstChild;)a.appendChild(b);return a},$=function(a,b){a=a.match(/^(\W)?(.*)/);return(b||document)["getElement"+(a[1]?a[1]=="#"?"ById":"sByClassName":"sByTagName")](a[2])},j=function(a){for(a=0;a<4;a++)try{return a?new ActiveXObject([,"Msxml2","Msxml3","Microsoft"][a]+".XMLHTTP"):new XMLHttpRequest}catch(b){}};
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<html><head><title>Connecting...</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css">
|
|
||||||
<script type="text/javascript" src="140medley.min.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
var xhr=j();
|
|
||||||
|
|
||||||
|
|
||||||
function getStatus() {
|
|
||||||
xhr.open("GET", "connstatus.cgi");
|
|
||||||
xhr.onreadystatechange=function() {
|
|
||||||
if (xhr.readyState==4 && xhr.status>=200 && xhr.status<300) {
|
|
||||||
var data=JSON.parse(xhr.responseText);
|
|
||||||
if (data.status=="idle") {
|
|
||||||
$("#status").innerHTML="Preparing to connect...";
|
|
||||||
window.setTimeout(getStatus, 1000);
|
|
||||||
} else if (data.status=="success") {
|
|
||||||
$("#status").innerHTML="Connected! Got IP "+data.ip+". If you're in the same network, you can access it <a href=\"http://"+data.ip+"/\">here</a>.";
|
|
||||||
} else if (data.status=="working") {
|
|
||||||
$("#status").innerHTML="Trying to connect to selected access point...";
|
|
||||||
window.setTimeout(getStatus, 1000);
|
|
||||||
} else if (data.status=="fail") {
|
|
||||||
$("#status").innerHTML="Connection failed. Check password and selected AP.<br /><a href=\"wifi.tpl\">Go Back</a>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onload=function(e) {
|
|
||||||
getStatus();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="main">
|
|
||||||
<h2>Connecting to AP...</h2>
|
|
||||||
<p>Status:<br />
|
|
||||||
<div id="status">...</div>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 914 B |
@@ -1,24 +0,0 @@
|
|||||||
|
|
||||||
body {
|
|
||||||
background-color: #404040;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
#main {
|
|
||||||
background-color: #d0d0FF;
|
|
||||||
-moz-border-radius: 5px;
|
|
||||||
-webkit-border-radius: 5px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 2px solid #000000;
|
|
||||||
width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
background-image: url("icons.png");
|
|
||||||
background-color: transparent;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
<html><head><title>WiFi connection</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css">
|
|
||||||
<script type="text/javascript" src="140medley.min.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
var xhr=j();
|
|
||||||
var currAp="%currSsid%";
|
|
||||||
|
|
||||||
function createInputForAp(ap) {
|
|
||||||
if (ap.essid=="" && ap.rssi==0) return;
|
|
||||||
var div=document.createElement("div");
|
|
||||||
div.id="apdiv";
|
|
||||||
var rssi=document.createElement("div");
|
|
||||||
var rssiVal=-Math.floor(ap.rssi/51)*32;
|
|
||||||
rssi.className="icon";
|
|
||||||
rssi.style.backgroundPosition="0px "+rssiVal+"px";
|
|
||||||
var encrypt=document.createElement("div");
|
|
||||||
var encVal="-64"; //assume wpa/wpa2
|
|
||||||
if (ap.enc=="0") encVal="0"; //open
|
|
||||||
if (ap.enc=="1") encVal="-32"; //wep
|
|
||||||
encrypt.className="icon";
|
|
||||||
encrypt.style.backgroundPosition="-32px "+encVal+"px";
|
|
||||||
var input=document.createElement("input");
|
|
||||||
input.type="radio";
|
|
||||||
input.name="essid";
|
|
||||||
input.value=ap.essid;
|
|
||||||
if (currAp==ap.essid) input.checked="1";
|
|
||||||
input.id="opt-"+ap.essid;
|
|
||||||
var label=document.createElement("label");
|
|
||||||
label.htmlFor="opt-"+ap.essid;
|
|
||||||
label.textContent=ap.essid;
|
|
||||||
div.appendChild(input);
|
|
||||||
div.appendChild(rssi);
|
|
||||||
div.appendChild(encrypt);
|
|
||||||
div.appendChild(label);
|
|
||||||
return div;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSelectedEssid() {
|
|
||||||
var e=document.forms.wifiform.elements;
|
|
||||||
for (var i=0; i<e.length; i++) {
|
|
||||||
if (e[i].type=="radio" && e[i].checked) return e[i].value;
|
|
||||||
}
|
|
||||||
return currAp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function scanAPs() {
|
|
||||||
xhr.open("GET", "wifiscan.cgi");
|
|
||||||
xhr.onreadystatechange=function() {
|
|
||||||
if (xhr.readyState==4 && xhr.status>=200 && xhr.status<300) {
|
|
||||||
var data=JSON.parse(xhr.responseText);
|
|
||||||
currAp=getSelectedEssid();
|
|
||||||
if (data.result.inProgress=="0" && data.result.APs.length>1) {
|
|
||||||
$("#aps").innerHTML="";
|
|
||||||
for (var i=0; i<data.result.APs.length; i++) {
|
|
||||||
if (data.result.APs[i].essid=="" && data.result.APs[i].rssi==0) continue;
|
|
||||||
$("#aps").appendChild(createInputForAp(data.result.APs[i]));
|
|
||||||
}
|
|
||||||
window.setTimeout(scanAPs, 20000);
|
|
||||||
} else {
|
|
||||||
window.setTimeout(scanAPs, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
window.onload=function(e) {
|
|
||||||
scanAPs();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="main">
|
|
||||||
<p>
|
|
||||||
Current WiFi mode: %WiFiMode%
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Note: %WiFiapwarn%
|
|
||||||
</p>
|
|
||||||
<form name="wifiform" action="connect.cgi" method="post">
|
|
||||||
<p>
|
|
||||||
To connect to a WiFi network, please select one of the detected networks...<br>
|
|
||||||
<div id="aps">Scanning...</div>
|
|
||||||
<br>
|
|
||||||
WiFi password, if applicable: <br />
|
|
||||||
<input type="text" name="passwd" val="%WiFiPasswd%"> <br />
|
|
||||||
<input type="submit" name="connect" value="Connect!">
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>ESP8266 Remote Terminal</title>
|
||||||
|
<meta name="viewport" content="width=device-width,shrink-to-fit=no,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script src="script.js"></script>
|
||||||
|
|
||||||
|
<!-- Katedra mereni FEL, 2017 -->
|
||||||
|
<!-- TODO GitHub link -->
|
||||||
|
|
||||||
|
<header>ESP8266 Remote Terminal</header>
|
||||||
|
|
||||||
|
<div id="screen"></div>
|
||||||
|
|
||||||
|
<div id="buttons">
|
||||||
|
<button>1</button><button>2</button><button>3</button><button>4</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>init()</script>
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
//region Libs / utils
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create DOM element
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* var el = m('<h1>Hello</h1>');
|
||||||
|
* document.body.appendChild(el);
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
* Version 2, December 2004
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
};
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Terminal class
|
||||||
|
//
|
||||||
|
(function () {
|
||||||
|
function make(e) {return document.createElement(e)}
|
||||||
|
|
||||||
|
var W = 26, H = 10; //26, 10
|
||||||
|
var cursor = {a: false, x: 0, y: 0, suppress: false, hidden: false};
|
||||||
|
var screen = [];
|
||||||
|
|
||||||
|
/** Colors table */
|
||||||
|
var CLR = [// dark gray #2E3436
|
||||||
|
// 0 black, 1 red, 2 green, 3 yellow
|
||||||
|
// 4 blue, 5 mag, 6 cyan, 7 white
|
||||||
|
'#111213','#CC0000','#4E9A06','#C4A000',
|
||||||
|
'#3465A4','#75507B','#06989A','#D3D7CF',
|
||||||
|
// BRIGHT
|
||||||
|
// 8 black, 9 red, 10 green, 11 yellow
|
||||||
|
// 12 blue, 13 mag, 14 cyan, 15 white
|
||||||
|
'#555753','#EF2929','#8AE234','#FCE94F',
|
||||||
|
'#729FCF','#AD7FA8','#34E2E2','#EEEEEC'
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Clear screen */
|
||||||
|
function cls() {
|
||||||
|
screen.forEach(function(cell, i) {
|
||||||
|
cell.t = ' ';
|
||||||
|
cell.fg = 7;
|
||||||
|
cell.bg = 0;
|
||||||
|
blit(cell);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set text and color at XY */
|
||||||
|
function cellAt(y, x) {
|
||||||
|
return screen[y*W+x];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get cell under cursor */
|
||||||
|
function cursorCell() {
|
||||||
|
return cellAt(cursor.y, cursor.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Enable or disable cursor visibility */
|
||||||
|
function cursorEnable(enable) {
|
||||||
|
cursor.hidden = !enable;
|
||||||
|
cursor.a &= enable;
|
||||||
|
blit(cursorCell(), cursor.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Safely move cursor */
|
||||||
|
function cursorSet(y, x) {
|
||||||
|
// Hide and prevent from showing up during the move
|
||||||
|
cursor.suppress = true;
|
||||||
|
blit(cursorCell(), false);
|
||||||
|
|
||||||
|
cursor.x = x;
|
||||||
|
cursor.y = y;
|
||||||
|
|
||||||
|
// Show again
|
||||||
|
cursor.suppress = false;
|
||||||
|
blit(cursorCell(), cursor.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update cell on display. inv = invert (for cursor) */
|
||||||
|
function blit(cell, inv) {
|
||||||
|
var e = cell.e, fg, bg;
|
||||||
|
// Colors
|
||||||
|
fg = inv ? cell.bg : cell.fg;
|
||||||
|
bg = inv ? cell.fg : cell.bg;
|
||||||
|
// Update
|
||||||
|
e.innerText = (cell.t+' ')[0];
|
||||||
|
e.style.color = colorHex(fg);
|
||||||
|
e.style.backgroundColor = colorHex(bg);
|
||||||
|
e.style.fontWeight = fg > 7 ? 'bold' : 'normal';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Show entire screen */
|
||||||
|
function blitAll() {
|
||||||
|
screen.forEach(function(cell, i) {
|
||||||
|
/* Invert if under cursor & cursor active */
|
||||||
|
var inv = cursor.a && (i == cursor.y*W+cursor.x);
|
||||||
|
blit(cell, inv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Load screen content from a 'binary' sequence */
|
||||||
|
function load(seq) {
|
||||||
|
if (seq.length != W*H*3) throw "Bad data format.";
|
||||||
|
|
||||||
|
// primitive format with 3 chars per cell: letter, fg [hex], bg [hex]
|
||||||
|
for (var i = 0; i < W * H; i++) {
|
||||||
|
var cell = screen[i];
|
||||||
|
cell.t = seq[i*3];
|
||||||
|
cell.fg = parseInt(seq[i*3+1], 16);
|
||||||
|
cell.bg = parseInt(seq[i*3+2], 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
blitAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse color */
|
||||||
|
function colorHex(c) {
|
||||||
|
var c = parseInt(c);
|
||||||
|
if (c < 0 || c > 15) c = 0;
|
||||||
|
return CLR[c];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Init the terminal */
|
||||||
|
function init() {
|
||||||
|
/* Build screen & show */
|
||||||
|
var e, scr = $('#screen');
|
||||||
|
for(var i = 0; i < W*H; i++) {
|
||||||
|
e = make('span');
|
||||||
|
|
||||||
|
/* End of line */
|
||||||
|
if ((i > 0) && (i % W == 0)) {
|
||||||
|
scr.appendChild(make('br'));
|
||||||
|
}
|
||||||
|
/* The cell */
|
||||||
|
scr.appendChild(e);
|
||||||
|
|
||||||
|
var cell = {t: ' ', fg: 7, bg: 0, e: e};
|
||||||
|
screen.push(cell);
|
||||||
|
blit(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cursor blinking */
|
||||||
|
setInterval(function() {
|
||||||
|
cursor.a = !cursor.a;
|
||||||
|
if (cursor.hidden) {
|
||||||
|
cursor.a = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cursor.suppress) {
|
||||||
|
blit(cursorCell(), cursor.a);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// publish
|
||||||
|
window.Term = {
|
||||||
|
init: init,
|
||||||
|
load: load,
|
||||||
|
setCursor: cursorSet,
|
||||||
|
enableCursor: cursorEnable,
|
||||||
|
clear: cls,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Connection class
|
||||||
|
//
|
||||||
|
(function() {
|
||||||
|
var wsUri = "ws://"+window.location.host+"/ws/update.cgi";
|
||||||
|
var ws;
|
||||||
|
|
||||||
|
function onOpen(evt) {
|
||||||
|
console.log("CONNECTED");
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClose(evt) {
|
||||||
|
console.error("SOCKET CLOSED");
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMessage(evt) {
|
||||||
|
console.log("Message received!", evt.data);
|
||||||
|
// TODO process
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError(evt) {
|
||||||
|
console.error(evt.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSend(message) {
|
||||||
|
ws.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
ws = new WebSocket(wsUri);
|
||||||
|
ws.onopen = onOpen;
|
||||||
|
ws.onclose = onClose
|
||||||
|
ws.onmessage = onMessage;
|
||||||
|
ws.onerror = onError;
|
||||||
|
|
||||||
|
console.log("Opening socket.");
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Conn = {
|
||||||
|
ws: null,
|
||||||
|
init: init,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
Term.init();
|
||||||
|
Conn.init();
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
html, body {
|
||||||
|
background: #48505F;
|
||||||
|
color: #eee;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 16pt;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14pt;
|
||||||
|
padding: 6px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#screen {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
background: #111213;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 6px;
|
||||||
|
box-shadow: inset 0 0 5px black;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#screen span {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
#buttons {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0 2px;
|
||||||
|
padding: 10px 0;
|
||||||
|
width: 22%;
|
||||||
|
max-width: 80px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <c_types.h>
|
||||||
|
|
||||||
|
// copied from nodemcu source
|
||||||
|
extern uint32_t system_get_time();
|
||||||
|
extern uint32_t platform_tmr_exists(uint32_t t);
|
||||||
|
extern uint32_t system_rtc_clock_cali_proc();
|
||||||
|
extern uint32_t system_get_rtc_time();
|
||||||
|
extern void system_restart();
|
||||||
|
extern void system_soft_wdt_feed();
|
||||||
|
|
||||||
|
// https://github.com/mziwisky/esp8266-dev/blob/master/esphttpd/include/espmissingincludes.h
|
||||||
|
extern int ets_str2macaddr(void *, void *);
|
||||||
|
extern void ets_update_cpu_frequency(int freqmhz);
|
||||||
|
extern int os_printf_plus(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
|
||||||
|
//extern uint8 wifi_get_opmode(void);
|
||||||
|
extern void ets_bzero(void *s, size_t n);
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
Cgi routines as used by the tests in the html/test subdirectory.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
|
||||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
|
||||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
|
||||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include <esp8266.h>
|
|
||||||
#include "cgi-test.h"
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int len;
|
|
||||||
int sendPos;
|
|
||||||
} TestbedState;
|
|
||||||
|
|
||||||
|
|
||||||
httpd_cgi_state ICACHE_FLASH_ATTR cgiTestbed(HttpdConnData *connData) {
|
|
||||||
char buff[1024];
|
|
||||||
int first=0;
|
|
||||||
int l, x;
|
|
||||||
TestbedState *state=(TestbedState*)connData->cgiData;
|
|
||||||
|
|
||||||
if (connData->conn==NULL) {
|
|
||||||
//Connection aborted. Clean up.
|
|
||||||
if (state) free(state);
|
|
||||||
return HTTPD_CGI_DONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state==NULL) {
|
|
||||||
//First call
|
|
||||||
state=malloc(sizeof(TestbedState));
|
|
||||||
memset(state, 0, sizeof(state));
|
|
||||||
connData->cgiData=state;
|
|
||||||
first=1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connData->requestType==HTTPD_METHOD_GET) {
|
|
||||||
if (first) {
|
|
||||||
httpdStartResponse(connData, 200);
|
|
||||||
httpdHeader(connData, "content-type", "application/data");
|
|
||||||
httpdEndHeaders(connData);
|
|
||||||
l=httpdFindArg(connData->getArgs, "len", buff, sizeof(buff));
|
|
||||||
state->len=1024;
|
|
||||||
if (l!=-1) state->len=atoi(buff);
|
|
||||||
state->sendPos=0;
|
|
||||||
return HTTPD_CGI_MORE;
|
|
||||||
} else {
|
|
||||||
l=sizeof(buff);
|
|
||||||
if (l>(state->len-state->sendPos)) l=(state->len-state->sendPos);
|
|
||||||
//Fill with semi-random data
|
|
||||||
for (x=0; x<l; x++) buff[x]=((x^(state->sendPos>>10))&0x1F)+'0';
|
|
||||||
httpdSend(connData, buff, l);
|
|
||||||
state->sendPos+=l;
|
|
||||||
printf("Test: Uploaded %d/%d bytes\n", state->sendPos, state->len);
|
|
||||||
if (state->len<=state->sendPos) {
|
|
||||||
if (state) free(state);
|
|
||||||
return HTTPD_CGI_DONE;
|
|
||||||
} else {
|
|
||||||
return HTTPD_CGI_MORE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (connData->requestType==HTTPD_METHOD_POST) {
|
|
||||||
if (connData->post->len!=connData->post->received) {
|
|
||||||
//Still receiving data. Ignore this.
|
|
||||||
printf("Test: got %d/%d bytes\n", connData->post->received, connData->post->len);
|
|
||||||
return HTTPD_CGI_MORE;
|
|
||||||
} else {
|
|
||||||
httpdStartResponse(connData, 200);
|
|
||||||
httpdHeader(connData, "content-type", "text/plain");
|
|
||||||
httpdEndHeaders(connData);
|
|
||||||
l=sprintf(buff, "%d", connData->post->received);
|
|
||||||
httpdSend(connData, buff, l);
|
|
||||||
return HTTPD_CGI_DONE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return HTTPD_CGI_DONE;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#ifndef CGI_TEST_H
|
|
||||||
#define CGI_TEST_H
|
|
||||||
|
|
||||||
#include "httpd.h"
|
|
||||||
|
|
||||||
httpd_cgi_state cgiTestbed(HttpdConnData *connData);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
-76
@@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
Some random cgi routines. Used in the LED example and the page that returns the entire
|
|
||||||
flash as a binary. Also handles the hit counter on the main page.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
|
||||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
|
||||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
|
||||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include <esp8266.h>
|
|
||||||
#include "cgi.h"
|
|
||||||
#include "io.h"
|
|
||||||
|
|
||||||
|
|
||||||
//cause I can't be bothered to write an ioGetLed()
|
|
||||||
static char currLedState=0;
|
|
||||||
|
|
||||||
//Cgi that turns the LED on or off according to the 'led' param in the POST data
|
|
||||||
httpd_cgi_state ICACHE_FLASH_ATTR cgiLed(HttpdConnData *connData) {
|
|
||||||
int len;
|
|
||||||
char buff[1024];
|
|
||||||
|
|
||||||
if (connData->conn==NULL) {
|
|
||||||
//Connection aborted. Clean up.
|
|
||||||
return HTTPD_CGI_DONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
len=httpdFindArg(connData->post->buff, "led", buff, sizeof(buff));
|
|
||||||
if (len!=0) {
|
|
||||||
currLedState=atoi(buff);
|
|
||||||
ioLed(currLedState);
|
|
||||||
}
|
|
||||||
|
|
||||||
httpdRedirect(connData, "led.tpl");
|
|
||||||
return HTTPD_CGI_DONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Template code for the led page.
|
|
||||||
httpd_cgi_state ICACHE_FLASH_ATTR tplLed(HttpdConnData *connData, char *token, void **arg) {
|
|
||||||
char buff[128];
|
|
||||||
if (token==NULL) return HTTPD_CGI_DONE;
|
|
||||||
|
|
||||||
os_strcpy(buff, "Unknown");
|
|
||||||
if (os_strcmp(token, "ledstate")==0) {
|
|
||||||
if (currLedState) {
|
|
||||||
os_strcpy(buff, "on");
|
|
||||||
} else {
|
|
||||||
os_strcpy(buff, "off");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
httpdSend(connData, buff, -1);
|
|
||||||
return HTTPD_CGI_DONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static long hitCounter=0;
|
|
||||||
|
|
||||||
//Template code for the counter on the index page.
|
|
||||||
httpd_cgi_state ICACHE_FLASH_ATTR tplCounter(HttpdConnData *connData, char *token, void **arg) {
|
|
||||||
char buff[128];
|
|
||||||
if (token==NULL) return HTTPD_CGI_DONE;
|
|
||||||
|
|
||||||
if (os_strcmp(token, "counter")==0) {
|
|
||||||
hitCounter++;
|
|
||||||
os_sprintf(buff, "%ld", hitCounter);
|
|
||||||
}
|
|
||||||
httpdSend(connData, buff, -1);
|
|
||||||
return HTTPD_CGI_DONE;
|
|
||||||
}
|
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
#ifndef CGI_H
|
|
||||||
#define CGI_H
|
|
||||||
|
|
||||||
#include "httpd.h"
|
|
||||||
|
|
||||||
httpd_cgi_state cgiLed(HttpdConnData *connData);
|
|
||||||
httpd_cgi_state tplLed(HttpdConnData *connData, char *token, void **arg);
|
|
||||||
httpd_cgi_state tplCounter(HttpdConnData *connData, char *token, void **arg);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
#define LEDGPIO 2
|
#define LEDGPIO 2
|
||||||
#define BTNGPIO 0
|
#define BTNGPIO 0
|
||||||
|
|
||||||
|
static bool enable_ap_button = false;
|
||||||
|
|
||||||
static ETSTimer resetBtntimer;
|
static ETSTimer resetBtntimer;
|
||||||
|
|
||||||
void ICACHE_FLASH_ATTR ioLed(int ena) {
|
void ICACHE_FLASH_ATTR ioLed(int ena) {
|
||||||
@@ -27,25 +29,34 @@ void ICACHE_FLASH_ATTR ioLed(int ena) {
|
|||||||
|
|
||||||
static void ICACHE_FLASH_ATTR resetBtnTimerCb(void *arg) {
|
static void ICACHE_FLASH_ATTR resetBtnTimerCb(void *arg) {
|
||||||
static int resetCnt=0;
|
static int resetCnt=0;
|
||||||
if (!GPIO_INPUT_GET(BTNGPIO)) {
|
if (enable_ap_button && !GPIO_INPUT_GET(BTNGPIO)) {
|
||||||
resetCnt++;
|
resetCnt++;
|
||||||
} else {
|
} else {
|
||||||
if (resetCnt>=6) { //3 sec pressed
|
if (resetCnt>=6) { //3 sec pressed
|
||||||
wifi_station_disconnect();
|
wifi_station_disconnect();
|
||||||
wifi_set_opmode(0x3); //reset to AP+STA mode
|
wifi_set_opmode(STATIONAP_MODE); //reset to AP+STA mode
|
||||||
os_printf("Reset to AP mode. Restarting system...\n");
|
info("Reset to AP mode from GPIO0, Restarting system...");
|
||||||
system_restart();
|
system_restart();
|
||||||
}
|
}
|
||||||
resetCnt=0;
|
resetCnt=0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ioInit() {
|
void ICACHE_FLASH_ATTR ioInit() {
|
||||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2);
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2);
|
||||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0);
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0);
|
||||||
gpio_output_set(0, 0, (1<<LEDGPIO), (1<<BTNGPIO));
|
gpio_output_set(0, 0, (1<<LEDGPIO), (1<<BTNGPIO));
|
||||||
os_timer_disarm(&resetBtntimer);
|
os_timer_disarm(&resetBtntimer);
|
||||||
os_timer_setfn(&resetBtntimer, resetBtnTimerCb, NULL);
|
os_timer_setfn(&resetBtntimer, resetBtnTimerCb, NULL);
|
||||||
os_timer_arm(&resetBtntimer, 500, 1);
|
os_timer_arm(&resetBtntimer, 500, 1);
|
||||||
|
|
||||||
|
// One way to enter AP mode - hold GPIO0 low.
|
||||||
|
if (GPIO_INPUT_GET(BTNGPIO) == 0) {
|
||||||
|
// starting "in BOOT mode" - do not install the AP reset timer
|
||||||
|
warn("GPIO0 stuck low - AP reset button disabled.\n");
|
||||||
|
} else {
|
||||||
|
enable_ap_button = true;
|
||||||
|
dbg("Note: Hold GPIO0 low for reset to AP mode.\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef IO_H
|
#ifndef IO_H
|
||||||
#define IO_H
|
#define IO_H
|
||||||
|
|
||||||
void ICACHE_FLASH_ATTR ioLed(int ena);
|
void ioLed(int ena);
|
||||||
void ioInit(void);
|
void ioInit(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#include <esp8266.h>
|
||||||
|
#include "uart_driver.h"
|
||||||
|
#include "uart_handler.h"
|
||||||
|
|
||||||
|
// Here the bitrates are defined
|
||||||
|
#define UART0_BAUD BIT_RATE_115200
|
||||||
|
#define UART1_BAUD BIT_RATE_115200
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init the serial ports
|
||||||
|
*/
|
||||||
|
void ICACHE_FLASH_ATTR serialInit(void)
|
||||||
|
{
|
||||||
|
UART_Init(UART0_BAUD, UART1_BAUD);
|
||||||
|
UART_SetPrintPort(UART0);
|
||||||
|
UART_SetupAsyncReceiver();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a byte received from UART.
|
||||||
|
* Might do some buffering here maybe
|
||||||
|
*
|
||||||
|
* @param c
|
||||||
|
*/
|
||||||
|
void ICACHE_FLASH_ATTR UART_HandleRxByte(char c)
|
||||||
|
{
|
||||||
|
printf("'%c',", c);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#ifndef SERIAL_H
|
||||||
|
#define SERIAL_H
|
||||||
|
|
||||||
|
/** Init the uarts */
|
||||||
|
void serialInit(void);
|
||||||
|
|
||||||
|
void UART_HandleRxByte(char c);
|
||||||
|
|
||||||
|
#endif //SERIAL_H
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
//Stupid bit of code that does the bare minimum to make os_printf work.
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
|
||||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
|
||||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
|
||||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <esp8266.h>
|
|
||||||
#include <uart_hw.h>
|
|
||||||
|
|
||||||
static void ICACHE_FLASH_ATTR stdoutUartTxd(char c) {
|
|
||||||
//Wait until there is room in the FIFO
|
|
||||||
while (((READ_PERI_REG(UART_STATUS(0))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT)>=126) ;
|
|
||||||
//Send the character
|
|
||||||
WRITE_PERI_REG(UART_FIFO(0), c);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ICACHE_FLASH_ATTR stdoutPutchar(char c) {
|
|
||||||
//convert \n -> \r\n
|
|
||||||
if (c=='\n') stdoutUartTxd('\r');
|
|
||||||
stdoutUartTxd(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void stdoutInit() {
|
|
||||||
//Enable TxD pin
|
|
||||||
PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U);
|
|
||||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD);
|
|
||||||
|
|
||||||
//Set baud rate and other serial parameters to 115200,n,8,1
|
|
||||||
uart_div_modify(0, UART_CLK_FREQ/BIT_RATE_115200);
|
|
||||||
WRITE_PERI_REG(UART_CONF0(0), (STICK_PARITY_DIS)|(ONE_STOP_BIT << UART_STOP_BIT_NUM_S)| \
|
|
||||||
(EIGHT_BITS << UART_BIT_NUM_S));
|
|
||||||
|
|
||||||
//Reset tx & rx fifo
|
|
||||||
SET_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST|UART_TXFIFO_RST);
|
|
||||||
CLEAR_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST|UART_TXFIFO_RST);
|
|
||||||
//Clear pending interrupts
|
|
||||||
WRITE_PERI_REG(UART_INT_CLR(0), 0xffff);
|
|
||||||
|
|
||||||
//Install our own putchar handler
|
|
||||||
os_install_putc1((void *)stdoutPutchar);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#ifndef STDOUT_H
|
|
||||||
#define STDOUT_H
|
|
||||||
|
|
||||||
void stdoutInit();
|
|
||||||
|
|
||||||
#endif
|
|
||||||
Executable
+249
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* Driver file for ESP8266 UART, works with the SDK.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "uart_driver.h"
|
||||||
|
|
||||||
|
#include <esp8266.h>
|
||||||
|
|
||||||
|
#include "ets_sys.h"
|
||||||
|
#include "osapi.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "os_type.h"
|
||||||
|
|
||||||
|
#include "ets_sys_extra.h"
|
||||||
|
#include "uart_register.h"
|
||||||
|
|
||||||
|
//========================================================
|
||||||
|
|
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR UART_SetWordLength(UARTn uart_no, UartBitsNum4Char len)
|
||||||
|
{
|
||||||
|
SET_PERI_REG_BITS(UART_CONF0(uart_no), UART_BIT_NUM, len, UART_BIT_NUM_S);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR UART_SetStopBits(UARTn uart_no, UartStopBitsNum bit_num)
|
||||||
|
{
|
||||||
|
SET_PERI_REG_BITS(UART_CONF0(uart_no), UART_STOP_BIT_NUM, bit_num, UART_STOP_BIT_NUM_S);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR UART_SetLineInverse(UARTn uart_no, UART_LineLevelInverse inverse_mask)
|
||||||
|
{
|
||||||
|
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_LINE_INV_MASK);
|
||||||
|
SET_PERI_REG_MASK(UART_CONF0(uart_no), inverse_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR UART_SetParity(UARTn uart_no, UartParityMode Parity_mode)
|
||||||
|
{
|
||||||
|
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_PARITY | UART_PARITY_EN);
|
||||||
|
if (Parity_mode == PARITY_NONE) {
|
||||||
|
} else {
|
||||||
|
SET_PERI_REG_MASK(UART_CONF0(uart_no), Parity_mode | UART_PARITY_EN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR UART_SetBaudrate(UARTn uart_no, uint32 baud_rate)
|
||||||
|
{
|
||||||
|
uart_div_modify(uart_no, UART_CLK_FREQ / baud_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR UART_SetFlowCtrl(UARTn uart_no, UART_HwFlowCtrl flow_ctrl, uint8 rx_thresh)
|
||||||
|
{
|
||||||
|
if (flow_ctrl & USART_HWFlow_RTS) {
|
||||||
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_U0RTS);
|
||||||
|
SET_PERI_REG_BITS(UART_CONF1(uart_no), UART_RX_FLOW_THRHD, rx_thresh, UART_RX_FLOW_THRHD_S);
|
||||||
|
SET_PERI_REG_MASK(UART_CONF1(uart_no), UART_RX_FLOW_EN);
|
||||||
|
} else {
|
||||||
|
CLEAR_PERI_REG_MASK(UART_CONF1(uart_no), UART_RX_FLOW_EN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flow_ctrl & USART_HWFlow_CTS) {
|
||||||
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_UART0_CTS);
|
||||||
|
SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_TX_FLOW_EN);
|
||||||
|
} else {
|
||||||
|
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_TX_FLOW_EN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR UART_WaitTxFifoEmpty(UARTn uart_no , uint32 time_out_us) //do not use if tx flow control enabled
|
||||||
|
{
|
||||||
|
uint32 t_s = system_get_time();
|
||||||
|
while (READ_PERI_REG(UART_STATUS(uart_no)) & (UART_TXFIFO_CNT << UART_TXFIFO_CNT_S)) {
|
||||||
|
|
||||||
|
if ((system_get_time() - t_s) > time_out_us) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
system_soft_wdt_feed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ICACHE_FLASH_ATTR UART_CheckOutputFinished(UARTn uart_no, uint32 time_out_us)
|
||||||
|
{
|
||||||
|
uint32 t_start = system_get_time();
|
||||||
|
uint8 tx_fifo_len;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
tx_fifo_len = UART_TxQueLen(uart_no);
|
||||||
|
|
||||||
|
// TODO If using output circbuf, check if empty
|
||||||
|
|
||||||
|
if (tx_fifo_len == 0) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (system_get_time() - t_start > time_out_us) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
system_soft_wdt_feed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR UART_ResetFifo(UARTn uart_no)
|
||||||
|
{
|
||||||
|
SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST);
|
||||||
|
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR UART_ClearIntrStatus(UARTn uart_no, uint32 clr_mask)
|
||||||
|
{
|
||||||
|
WRITE_PERI_REG(UART_INT_CLR(uart_no), clr_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR UART_SetIntrEna(UARTn uart_no, uint32 ena_mask)
|
||||||
|
{
|
||||||
|
SET_PERI_REG_MASK(UART_INT_ENA(uart_no), ena_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LOCAL void u0_putc_crlf(char c)
|
||||||
|
{
|
||||||
|
UART_WriteCharCRLF(UART0, (u8)c, UART_TIMEOUT_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LOCAL void u1_putc_crlf(char c)
|
||||||
|
{
|
||||||
|
UART_WriteCharCRLF(UART1, (u8)c, UART_TIMEOUT_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR UART_SetPrintPort(UARTn uart_no)
|
||||||
|
{
|
||||||
|
if (uart_no == UART0) {
|
||||||
|
os_install_putc1((void *)u0_putc_crlf);
|
||||||
|
} else {
|
||||||
|
os_install_putc1((void *)u1_putc_crlf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -------------- Custom UART functions -------------------------
|
||||||
|
|
||||||
|
// !!! write handlers are not ICACHE_FLASH_ATTR -> can be used in IRQ !!!
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a char to UART.
|
||||||
|
* @param uart_no
|
||||||
|
* @param c
|
||||||
|
* @param timeout_us - how long to max wait for space in FIFO.
|
||||||
|
* @return write success
|
||||||
|
*/
|
||||||
|
STATUS UART_WriteChar(UARTn uart_no, uint8 c, uint32 timeout_us)
|
||||||
|
{
|
||||||
|
if (timeout_us == 0) {
|
||||||
|
timeout_us = UART_TIMEOUT_US;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint32 t_s = system_get_time();
|
||||||
|
|
||||||
|
while ((system_get_time() - t_s) < timeout_us) {
|
||||||
|
uint8 fifo_cnt = UART_TxQueLen(uart_no);
|
||||||
|
|
||||||
|
if (fifo_cnt < UART_TX_FULL_THRESH_VAL) {
|
||||||
|
WRITE_PERI_REG(UART_FIFO(uart_no), c);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
system_soft_wdt_feed();
|
||||||
|
}
|
||||||
|
|
||||||
|
return FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a char to UART, translating LF to CRLF and discarding CR.
|
||||||
|
* @param uart_no
|
||||||
|
* @param c
|
||||||
|
* @param timeout_us - how long to max wait for space in FIFO.
|
||||||
|
* @return write success
|
||||||
|
*/
|
||||||
|
STATUS UART_WriteCharCRLF(UARTn uart_no, uint8 c, uint32 timeout_us)
|
||||||
|
{
|
||||||
|
STATUS st;
|
||||||
|
|
||||||
|
if (c == '\r') {
|
||||||
|
return OK;
|
||||||
|
} else if (c == '\n') {
|
||||||
|
|
||||||
|
st = UART_WriteChar(uart_no, '\r', timeout_us);
|
||||||
|
if (st != OK) return st;
|
||||||
|
|
||||||
|
st = UART_WriteChar(uart_no, '\n', timeout_us);
|
||||||
|
return st;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return UART_WriteChar(uart_no, c, timeout_us);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a string to UART.
|
||||||
|
* @param uart_no
|
||||||
|
* @param str
|
||||||
|
* @param timeout_us - how long to max wait for space in FIFO.
|
||||||
|
* @return write success
|
||||||
|
*/
|
||||||
|
STATUS UART_WriteString(UARTn uart_no, const char *str, uint32 timeout_us)
|
||||||
|
{
|
||||||
|
while (*str) {
|
||||||
|
STATUS suc = UART_WriteChar(uart_no, (u8) * str++, timeout_us);
|
||||||
|
if (suc != OK) return suc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a buffer
|
||||||
|
* @param uart_no
|
||||||
|
* @param buffer - buffer to send
|
||||||
|
* @param len - buffer size
|
||||||
|
* @param timeout_us - how long to max wait for space in FIFO.
|
||||||
|
* @return write success
|
||||||
|
*/
|
||||||
|
STATUS UART_WriteBuffer(UARTn uart_no, const uint8 *buffer, size_t len, uint32 timeout_us)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
STATUS suc = UART_WriteChar(uart_no, (u8) * buffer++, timeout_us);
|
||||||
|
if (suc != OK) return suc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
Executable
+201
@@ -0,0 +1,201 @@
|
|||||||
|
/**
|
||||||
|
* Low level UART peripheral support functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File : uart.h
|
||||||
|
* Copyright (C) 2013 - 2016, Espressif Systems
|
||||||
|
* Copyright (C) 2016, Ondřej Hruška (cleaning, modif.)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of version 3 of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef UART_APP_H
|
||||||
|
#define UART_APP_H
|
||||||
|
|
||||||
|
#include "uart_register.h"
|
||||||
|
|
||||||
|
#include "eagle_soc.h"
|
||||||
|
#include "c_types.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ===========
|
||||||
|
|
||||||
|
// timeout for sending / receiving a char (default)
|
||||||
|
#define UART_TIMEOUT_US 5000
|
||||||
|
|
||||||
|
#define UART_TX_FULL_THRESH_VAL (UART_FIFO_LEN - 2) // if more than this many bytes in queue, don't write more
|
||||||
|
#define UART_TX_EMPTY_THRESH_VAL 16
|
||||||
|
|
||||||
|
// ===========
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
UART0 = 0,
|
||||||
|
UART1 = 1
|
||||||
|
} UARTn;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FIVE_BITS = 0x0,
|
||||||
|
SIX_BITS = 0x1,
|
||||||
|
SEVEN_BITS = 0x2,
|
||||||
|
EIGHT_BITS = 0x3
|
||||||
|
} UartBitsNum4Char;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ONE_STOP_BIT = 0x1,
|
||||||
|
ONE_HALF_STOP_BIT = 0x2,
|
||||||
|
TWO_STOP_BIT = 0x3
|
||||||
|
} UartStopBitsNum;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PARITY_NONE = 0x2,
|
||||||
|
PARITY_ODD = 1,
|
||||||
|
PARITY_EVEN = 0
|
||||||
|
} UartParityMode;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PARITY_DIS = 0,
|
||||||
|
PARITY_EN = 1
|
||||||
|
} UartExistParity;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
UART_None_Inverse = 0x0,
|
||||||
|
UART_Rxd_Inverse = UART_RXD_INV,
|
||||||
|
UART_CTS_Inverse = UART_CTS_INV,
|
||||||
|
UART_Txd_Inverse = UART_TXD_INV,
|
||||||
|
UART_RTS_Inverse = UART_RTS_INV,
|
||||||
|
} UART_LineLevelInverse;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BIT_RATE_300 = 300,
|
||||||
|
BIT_RATE_600 = 600,
|
||||||
|
BIT_RATE_1200 = 1200,
|
||||||
|
BIT_RATE_2400 = 2400,
|
||||||
|
BIT_RATE_4800 = 4800,
|
||||||
|
BIT_RATE_9600 = 9600,
|
||||||
|
BIT_RATE_19200 = 19200,
|
||||||
|
BIT_RATE_38400 = 38400,
|
||||||
|
BIT_RATE_57600 = 57600,
|
||||||
|
BIT_RATE_74880 = 74880,
|
||||||
|
BIT_RATE_115200 = 115200,
|
||||||
|
BIT_RATE_230400 = 230400,
|
||||||
|
BIT_RATE_460800 = 460800,
|
||||||
|
BIT_RATE_921600 = 921600,
|
||||||
|
BIT_RATE_1843200 = 1843200,
|
||||||
|
BIT_RATE_3686400 = 3686400,
|
||||||
|
} UartBautRate;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
NONE_CTRL,
|
||||||
|
HARDWARE_CTRL,
|
||||||
|
XON_XOFF_CTRL
|
||||||
|
} UartFlowCtrl;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
USART_HWFlow_None = 0x0,
|
||||||
|
USART_HWFlow_RTS = 0x1,
|
||||||
|
USART_HWFlow_CTS = 0x2,
|
||||||
|
USART_HWFlow_CTS_RTS = 0x3
|
||||||
|
} UART_HwFlowCtrl;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EMPTY,
|
||||||
|
UNDER_WRITE,
|
||||||
|
WRITE_OVER
|
||||||
|
} RcvMsgBuffState;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32 RcvBuffSize;
|
||||||
|
uint8 *pRcvMsgBuff;
|
||||||
|
uint8 *pWritePos;
|
||||||
|
uint8 *pReadPos;
|
||||||
|
uint8 TrigLvl; //JLU: may need to pad
|
||||||
|
RcvMsgBuffState BuffState;
|
||||||
|
} RcvMsgBuff;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32 TrxBuffSize;
|
||||||
|
uint8 *pTrxBuff;
|
||||||
|
} TrxMsgBuff;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BAUD_RATE_DET,
|
||||||
|
WAIT_SYNC_FRM,
|
||||||
|
SRCH_MSG_HEAD,
|
||||||
|
RCV_MSG_BODY,
|
||||||
|
RCV_ESC_CHAR,
|
||||||
|
} RcvMsgState;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
UartBautRate baut_rate;
|
||||||
|
UartBitsNum4Char data_bits;
|
||||||
|
UartExistParity exist_parity;
|
||||||
|
UartParityMode parity;
|
||||||
|
UartStopBitsNum stop_bits;
|
||||||
|
UartFlowCtrl flow_ctrl;
|
||||||
|
RcvMsgBuff rcv_buff;
|
||||||
|
TrxMsgBuff trx_buff;
|
||||||
|
RcvMsgState rcv_state;
|
||||||
|
int received;
|
||||||
|
int buff_uart_no; //indicate which uart use tx/rx buffer
|
||||||
|
} UartDevice;
|
||||||
|
|
||||||
|
// UartDev is defined and initialized in rom code.
|
||||||
|
extern UartDevice UartDev;
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================
|
||||||
|
|
||||||
|
// FIFO used count
|
||||||
|
#define UART_TxQueLen(uart_no) ((READ_PERI_REG(UART_STATUS((uart_no))) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT)
|
||||||
|
#define UART_RxQueLen(uart_no) ((READ_PERI_REG(UART_STATUS((uart_no))) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT)
|
||||||
|
|
||||||
|
STATUS UART_WriteCharCRLF(UARTn uart_no, uint8 c, uint32 timeout_us);
|
||||||
|
STATUS UART_WriteChar(UARTn uart_no, uint8 c, uint32 timeout_us);
|
||||||
|
STATUS UART_WriteString(UARTn uart_no, const char *str, uint32 timeout_us);
|
||||||
|
STATUS UART_WriteBuffer(UARTn uart_no, const uint8 *buffer, size_t len, uint32 timeout_us);
|
||||||
|
|
||||||
|
//==============================================
|
||||||
|
|
||||||
|
void UART_SetWordLength(UARTn uart_no, UartBitsNum4Char len);
|
||||||
|
void UART_SetStopBits(UARTn uart_no, UartStopBitsNum bit_num);
|
||||||
|
void UART_SetLineInverse(UARTn uart_no, UART_LineLevelInverse inverse_mask);
|
||||||
|
void UART_SetParity(UARTn uart_no, UartParityMode Parity_mode);
|
||||||
|
void UART_SetBaudrate(UARTn uart_no, uint32 baud_rate);
|
||||||
|
void UART_SetFlowCtrl(UARTn uart_no, UART_HwFlowCtrl flow_ctrl, uint8 rx_thresh);
|
||||||
|
void UART_WaitTxFifoEmpty(UARTn uart_no , uint32 time_out_us); //do not use if tx flow control enabled
|
||||||
|
void UART_ResetFifo(UARTn uart_no);
|
||||||
|
void UART_ClearIntrStatus(UARTn uart_no, uint32 clr_mask);
|
||||||
|
void UART_SetIntrEna(UARTn uart_no, uint32 ena_mask);
|
||||||
|
void UART_SetPrintPort(UARTn uart_no);
|
||||||
|
bool UART_CheckOutputFinished(UARTn uart_no, uint32 time_out_us);
|
||||||
|
|
||||||
|
//==============================================
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
Executable
+185
@@ -0,0 +1,185 @@
|
|||||||
|
//Stupid bit of code that does the bare minimum to make os_printf work.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||||
|
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
||||||
|
* this notice you can do whatever you want with this stuff. If we meet some day,
|
||||||
|
* and you think this stuff is worth it, you can buy me a beer in return.
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <esp8266.h>
|
||||||
|
#include "uart_driver.h"
|
||||||
|
#include "uart_handler.h"
|
||||||
|
|
||||||
|
// messy irq/task based UART handling below
|
||||||
|
|
||||||
|
static void uart0_rx_intr_handler(void *para);
|
||||||
|
static void uart_recvTask(os_event_t *events);
|
||||||
|
|
||||||
|
#define uart_recvTaskPrio 1
|
||||||
|
#define uart_recvTaskQueueLen 10
|
||||||
|
static os_event_t uart_recvTaskQueue[uart_recvTaskQueueLen];
|
||||||
|
|
||||||
|
/** Clear the fifos */
|
||||||
|
void ICACHE_FLASH_ATTR clear_rxtx(int uart_no)
|
||||||
|
{
|
||||||
|
SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST);
|
||||||
|
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configure UART 115200-8-N-1
|
||||||
|
* @param uart_no
|
||||||
|
*/
|
||||||
|
static void ICACHE_FLASH_ATTR my_uart_init(UARTn uart_no, uint32 baud)
|
||||||
|
{
|
||||||
|
UART_SetParity(uart_no, PARITY_NONE);
|
||||||
|
UART_SetStopBits(uart_no, ONE_STOP_BIT);
|
||||||
|
UART_SetWordLength(uart_no, EIGHT_BITS);
|
||||||
|
UART_SetBaudrate(uart_no, baud);
|
||||||
|
UART_ResetFifo(uart_no);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Configure basic UART func and pins */
|
||||||
|
void ICACHE_FLASH_ATTR UART_Init(uint32_t baud0, uint32_t baud1)
|
||||||
|
{
|
||||||
|
// U0TXD
|
||||||
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD);
|
||||||
|
PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U);
|
||||||
|
|
||||||
|
// U0RXD
|
||||||
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD);
|
||||||
|
|
||||||
|
// U1TXD (GPIO2)
|
||||||
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK);
|
||||||
|
|
||||||
|
// Configure the UART peripherals
|
||||||
|
my_uart_init(UART0, baud0); // main
|
||||||
|
my_uart_init(UART1, baud1); // debug (output only)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Configure Rx on UART0 */
|
||||||
|
void ICACHE_FLASH_ATTR UART_SetupAsyncReceiver(void)
|
||||||
|
{
|
||||||
|
// Start the Rx reading task
|
||||||
|
system_os_task(uart_recvTask, uart_recvTaskPrio, uart_recvTaskQueue, uart_recvTaskQueueLen);
|
||||||
|
// set handler
|
||||||
|
ETS_UART_INTR_ATTACH((void *)uart0_rx_intr_handler, &(UartDev.rcv_buff)); // the buf will be used as an arg
|
||||||
|
|
||||||
|
// fifo threshold config
|
||||||
|
uint32_t conf = ((100 & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S);
|
||||||
|
conf |= ((0x10 & UART_TXFIFO_EMPTY_THRHD) << UART_TXFIFO_EMPTY_THRHD_S);
|
||||||
|
// timeout config
|
||||||
|
conf |= ((0x02 & UART_RX_TOUT_THRHD) << UART_RX_TOUT_THRHD_S); // timeout threshold
|
||||||
|
conf |= UART_RX_TOUT_EN; // enable timeout
|
||||||
|
WRITE_PERI_REG(UART_CONF1(UART0), conf);
|
||||||
|
|
||||||
|
// enable TOUT and ERR irqs
|
||||||
|
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_RXFIFO_TOUT_INT_ENA | UART_FRM_ERR_INT_ENA);
|
||||||
|
/* clear interrupt flags */
|
||||||
|
WRITE_PERI_REG(UART_INT_CLR(UART0), 0xffff);
|
||||||
|
/* enable RX interrupts */
|
||||||
|
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_OVF_INT_ENA);
|
||||||
|
|
||||||
|
// Enable IRQ in Extensa
|
||||||
|
ETS_UART_INTR_ENABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- async receive stuff ----
|
||||||
|
|
||||||
|
|
||||||
|
void uart_rx_intr_disable(uint8 uart_no)
|
||||||
|
{
|
||||||
|
CLEAR_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void uart_rx_intr_enable(uint8 uart_no)
|
||||||
|
{
|
||||||
|
SET_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief get number of bytes in UART tx fifo
|
||||||
|
* @param UART number
|
||||||
|
*/
|
||||||
|
#define UART_GetRxFifoCount(uart_no) ((READ_PERI_REG(UART_STATUS((uart_no))) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT)
|
||||||
|
|
||||||
|
|
||||||
|
void ICACHE_FLASH_ATTR UART_PollRx(void)
|
||||||
|
{
|
||||||
|
uint8 fifo_len = UART_GetRxFifoCount(UART0);
|
||||||
|
|
||||||
|
for (uint8 idx = 0; idx < fifo_len; idx++) {
|
||||||
|
uint8 d_tmp = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF;
|
||||||
|
UART_HandleRxByte(d_tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void ICACHE_FLASH_ATTR uart_recvTask(os_event_t *events)
|
||||||
|
{
|
||||||
|
if (events->sig == 0) {
|
||||||
|
UART_PollRx();
|
||||||
|
|
||||||
|
// clear irq flags
|
||||||
|
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR | UART_RXFIFO_TOUT_INT_CLR);
|
||||||
|
// enable rx irq again
|
||||||
|
uart_rx_intr_enable(UART0);
|
||||||
|
} else if (events->sig == 1) {
|
||||||
|
// ???
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* FunctionName : uart0_rx_intr_handler
|
||||||
|
* Description : Internal used function
|
||||||
|
* UART0 interrupt handler, add self handle code inside
|
||||||
|
* Parameters : void *para - point to ETS_UART_INTR_ATTACH's arg
|
||||||
|
* Returns : NONE
|
||||||
|
*******************************************************************************/
|
||||||
|
static void
|
||||||
|
uart0_rx_intr_handler(void *para)
|
||||||
|
{
|
||||||
|
(void)para;
|
||||||
|
|
||||||
|
uint32_t status_reg = READ_PERI_REG(UART_INT_ST(UART0));
|
||||||
|
|
||||||
|
if (status_reg & UART_FRM_ERR_INT_ST) {
|
||||||
|
// Framing Error
|
||||||
|
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_FRM_ERR_INT_CLR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status_reg & UART_RXFIFO_FULL_INT_ST) {
|
||||||
|
// RX fifo full
|
||||||
|
uart_rx_intr_disable(UART0);
|
||||||
|
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR);
|
||||||
|
|
||||||
|
// run handler
|
||||||
|
system_os_post(uart_recvTaskPrio, 0, 0); /* -> notify the polling thread */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status_reg & UART_RXFIFO_TOUT_INT_ST) {
|
||||||
|
// Fifo timeout
|
||||||
|
uart_rx_intr_disable(UART0);
|
||||||
|
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_TOUT_INT_CLR);
|
||||||
|
|
||||||
|
// run handler
|
||||||
|
system_os_post(uart_recvTaskPrio, 0, 0); /* -> notify the polling thread */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status_reg & UART_TXFIFO_EMPTY_INT_ST) {
|
||||||
|
CLEAR_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status_reg & UART_RXFIFO_OVF_INT_ST) {
|
||||||
|
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_OVF_INT_CLR);
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* UART init & async rx module.
|
||||||
|
*
|
||||||
|
* Call UART_Init(), UART_SetupAsyncReceiver() and
|
||||||
|
* define UART_HandleRxByte() somewhere in application code.
|
||||||
|
*
|
||||||
|
* Call UART_PollRx() to allow rx in a blocking handler.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef UART_HANDLER_H
|
||||||
|
#define UART_HANDLER_H
|
||||||
|
|
||||||
|
#include <esp8266.h>
|
||||||
|
|
||||||
|
/** Configure basic UART func and pins */
|
||||||
|
void UART_Init(uint32_t baud0, uint32_t baud1);
|
||||||
|
|
||||||
|
/** Configure Rx on UART0 */
|
||||||
|
void UART_SetupAsyncReceiver(void);
|
||||||
|
|
||||||
|
/** User must provide this func for handling received bytes */
|
||||||
|
extern void UART_HandleRxByte(char c);
|
||||||
|
|
||||||
|
/** poll uart while waiting for something */
|
||||||
|
void UART_PollRx(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
+48
-114
@@ -7,84 +7,26 @@
|
|||||||
* ----------------------------------------------------------------------------
|
* ----------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/**
|
||||||
This is example code for the esphttpd library. It's a small-ish demo showing off
|
* This is the ESP8266 Remote Terminal project main file.
|
||||||
the server, including WiFi connection management capabilities, some IO and
|
*/
|
||||||
some pictures of cats.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <esp8266.h>
|
#include <esp8266.h>
|
||||||
#include "httpd.h"
|
#include <httpdespfs.h>
|
||||||
|
#include <cgiwebsocket.h>
|
||||||
|
#include <captdns.h>
|
||||||
|
#include <espfs.h>
|
||||||
|
#include <webpages-espfs.h>
|
||||||
|
#include "serial.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
#include "httpdespfs.h"
|
|
||||||
#include "cgi.h"
|
|
||||||
#include "cgiwifi.h"
|
|
||||||
#include "cgiflash.h"
|
|
||||||
#include "stdout.h"
|
|
||||||
#include "auth.h"
|
|
||||||
#include "espfs.h"
|
|
||||||
#include "captdns.h"
|
|
||||||
#include "webpages-espfs.h"
|
|
||||||
#include "cgiwebsocket.h"
|
|
||||||
#include "cgi-test.h"
|
|
||||||
|
|
||||||
//The example can print out the heap use every 3 seconds. You can use this to catch memory leaks.
|
#define FIRMWARE_VERSION "0.1"
|
||||||
//#define SHOW_HEAP_USE
|
|
||||||
|
|
||||||
//Function that tells the authentication system what users/passwords live on the system.
|
#define SHOW_HEAP_USE 1
|
||||||
//This is disabled in the default build; if you want to try it, enable the authBasic line in
|
|
||||||
//the builtInUrls below.
|
|
||||||
int myPassFn(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen) {
|
|
||||||
if (no==0) {
|
|
||||||
os_strcpy(user, "admin");
|
|
||||||
os_strcpy(pass, "s3cr3t");
|
|
||||||
return 1;
|
|
||||||
//Add more users this way. Check against incrementing no for each user added.
|
|
||||||
// } else if (no==1) {
|
|
||||||
// os_strcpy(user, "user1");
|
|
||||||
// os_strcpy(pass, "something");
|
|
||||||
// return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ETSTimer websockTimer;
|
|
||||||
|
|
||||||
//Broadcast the uptime in seconds every second over connected websockets
|
|
||||||
static void ICACHE_FLASH_ATTR websockTimerCb(void *arg) {
|
|
||||||
static int ctr=0;
|
|
||||||
char buff[128];
|
|
||||||
ctr++;
|
|
||||||
os_sprintf(buff, "Up for %d minutes %d seconds!\n", ctr/60, ctr%60);
|
|
||||||
cgiWebsockBroadcast("/websocket/ws.cgi", buff, os_strlen(buff), WEBSOCK_FLAG_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
//On reception of a message, send "You sent: " plus whatever the other side sent
|
|
||||||
void myWebsocketRecv(Websock *ws, char *data, int len, int flags) {
|
|
||||||
int i;
|
|
||||||
char buff[128];
|
|
||||||
os_sprintf(buff, "You sent: ");
|
|
||||||
for (i=0; i<len; i++) buff[i+10]=data[i];
|
|
||||||
buff[i+10]=0;
|
|
||||||
cgiWebsocketSend(ws, buff, os_strlen(buff), WEBSOCK_FLAG_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Websocket connected. Install reception handler and send welcome message.
|
|
||||||
void myWebsocketConnect(Websock *ws) {
|
void myWebsocketConnect(Websock *ws) {
|
||||||
ws->recvCb=myWebsocketRecv;
|
// NOOP
|
||||||
cgiWebsocketSend(ws, "Hi, Websocket!", 14, WEBSOCK_FLAG_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
//On reception of a message, echo it back verbatim
|
|
||||||
void myEchoWebsocketRecv(Websock *ws, char *data, int len, int flags) {
|
|
||||||
os_printf("EchoWs: echo, len=%d\n", len);
|
|
||||||
cgiWebsocketSend(ws, data, len, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Echo websocket connected. Install reception handler.
|
|
||||||
void myEchoWebsocketConnect(Websock *ws) {
|
|
||||||
os_printf("EchoWs: connect\n");
|
|
||||||
ws->recvCb=myEchoWebsocketRecv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -108,47 +50,14 @@ CgiUploadFlashDef uploadParams={
|
|||||||
#define INCLUDE_FLASH_FNS
|
#define INCLUDE_FLASH_FNS
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/** Routes */
|
||||||
This is the main url->function dispatching data struct.
|
|
||||||
In short, it's a struct with various URLs plus their handlers. The handlers can
|
|
||||||
be 'standard' CGI functions you wrote, or 'special' CGIs requiring an argument.
|
|
||||||
They can also be auth-functions. An asterisk will match any url starting with
|
|
||||||
everything before the asterisks; "*" matches everything. The list will be
|
|
||||||
handled top-down, so make sure to put more specific rules above the more
|
|
||||||
general ones. Authorization things (like authBasic) act as a 'barrier' and
|
|
||||||
should be placed above the URLs they protect.
|
|
||||||
*/
|
|
||||||
HttpdBuiltInUrl builtInUrls[]={
|
HttpdBuiltInUrl builtInUrls[]={
|
||||||
ROUTE_CGI_ARG("*", cgiRedirectApClientToHostname, "esp8266.nonet"), // redirect func for the captive portal
|
// redirect func for the captive portal
|
||||||
ROUTE_REDIRECT("/", "/index.tpl"),
|
ROUTE_CGI_ARG("*", cgiRedirectApClientToHostname, "esp8266.nonet"),
|
||||||
ROUTE_TPL("/led.tpl", tplLed),
|
|
||||||
ROUTE_TPL("/index.tpl", tplCounter),
|
|
||||||
ROUTE_CGI("/led.cgi", cgiLed),
|
|
||||||
#ifdef INCLUDE_FLASH_FNS
|
|
||||||
ROUTE_CGI_ARG("/flash/next", cgiGetFirmwareNext, &uploadParams),
|
|
||||||
ROUTE_CGI_ARG("/flash/upload", cgiUploadFirmware, &uploadParams),
|
|
||||||
#endif
|
|
||||||
ROUTE_CGI("/flash/reboot", cgiRebootFirmware),
|
|
||||||
|
|
||||||
//Routines to make the /wifi URL and everything beneath it work.
|
ROUTE_WS("/ws/update.cgi", myWebsocketConnect),
|
||||||
|
|
||||||
//Enable the line below to protect the WiFi configuration with an username/password combo.
|
// TODO add funcs for WiFi management (when web UI is added)
|
||||||
// ROUTE_AUTH("/wifi/*", myPassFn),
|
|
||||||
|
|
||||||
ROUTE_REDIRECT("/wifi", "/wifi/wifi.tpl"),
|
|
||||||
ROUTE_REDIRECT("/wifi/", "/wifi/wifi.tpl"),
|
|
||||||
ROUTE_CGI("/wifi/wifiscan.cgi", cgiWiFiScan),
|
|
||||||
ROUTE_TPL("/wifi/wifi.tpl", tplWlan),
|
|
||||||
ROUTE_CGI("/wifi/connect.cgi", cgiWiFiConnect),
|
|
||||||
ROUTE_CGI("/wifi/connstatus.cgi", cgiWiFiConnStatus),
|
|
||||||
ROUTE_CGI("/wifi/setmode.cgi", cgiWiFiSetMode),
|
|
||||||
|
|
||||||
ROUTE_WS("/websocket/ws.cgi", myWebsocketConnect),
|
|
||||||
ROUTE_WS("/websocket/echo.cgi", myEchoWebsocketConnect),
|
|
||||||
|
|
||||||
ROUTE_REDIRECT("/test", "/test/index.html"),
|
|
||||||
ROUTE_REDIRECT("/test/", "/test/index.html"),
|
|
||||||
ROUTE_CGI("/test/test.cgi", cgiTestbed),
|
|
||||||
|
|
||||||
ROUTE_FILESYSTEM(),
|
ROUTE_FILESYSTEM(),
|
||||||
ROUTE_END(),
|
ROUTE_END(),
|
||||||
@@ -159,15 +68,28 @@ HttpdBuiltInUrl builtInUrls[]={
|
|||||||
static ETSTimer prHeapTimer;
|
static ETSTimer prHeapTimer;
|
||||||
|
|
||||||
static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) {
|
static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) {
|
||||||
|
static int led = 0;
|
||||||
os_printf("Heap: %ld\n", (unsigned long)system_get_free_heap_size());
|
os_printf("Heap: %ld\n", (unsigned long)system_get_free_heap_size());
|
||||||
|
|
||||||
|
cgiWebsockBroadcast("/ws/update.cgi", "HELLO", 5, WEBSOCK_FLAG_NONE);
|
||||||
|
|
||||||
|
ioLed(led);
|
||||||
|
led = !led;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done.
|
//Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done.
|
||||||
void user_init(void) {
|
void user_init(void) {
|
||||||
stdoutInit();
|
serialInit();
|
||||||
ioInit();
|
|
||||||
|
banner("ESP8266 Remote Terminal");
|
||||||
|
banner_info("Version "FIRMWARE_VERSION", built "__DATE__" at "__TIME__);
|
||||||
|
dbg("!!! TODO (c) and GitHub link here !!!");
|
||||||
|
|
||||||
|
//stdoutInit();
|
||||||
|
|
||||||
captdnsInit();
|
captdnsInit();
|
||||||
|
ioInit();
|
||||||
|
|
||||||
// 0x40200000 is the base address for spi flash memory mapping, ESPFS_POS is the position
|
// 0x40200000 is the base address for spi flash memory mapping, ESPFS_POS is the position
|
||||||
// where image is written in flash that is defined in Makefile.
|
// where image is written in flash that is defined in Makefile.
|
||||||
@@ -176,18 +98,30 @@ void user_init(void) {
|
|||||||
#else
|
#else
|
||||||
espFsInit((void*)(webpages_espfs_start));
|
espFsInit((void*)(webpages_espfs_start));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
httpdInit(builtInUrls, 80);
|
httpdInit(builtInUrls, 80);
|
||||||
|
|
||||||
#ifdef SHOW_HEAP_USE
|
#ifdef SHOW_HEAP_USE
|
||||||
os_timer_disarm(&prHeapTimer);
|
os_timer_disarm(&prHeapTimer);
|
||||||
os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL);
|
os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL);
|
||||||
os_timer_arm(&prHeapTimer, 3000, 1);
|
os_timer_arm(&prHeapTimer, 3000, 1);
|
||||||
#endif
|
#endif
|
||||||
os_timer_disarm(&websockTimer);
|
|
||||||
os_timer_setfn(&websockTimer, websockTimerCb, NULL);
|
info("System ready!");
|
||||||
os_timer_arm(&websockTimer, 1000, 1);
|
|
||||||
os_printf("\nReady\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- unused funcs removed from sdk to save space ---
|
||||||
|
|
||||||
void user_rf_pre_init() {
|
void user_rf_pre_init() {
|
||||||
//Not needed, but some SDK versions want this defined.
|
//Not needed, but some SDK versions want this defined.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// вызывается из phy_chip_v6.o
|
||||||
|
void ICACHE_FLASH_ATTR chip_v6_set_sense(void) {
|
||||||
|
// ret.n
|
||||||
|
}
|
||||||
|
|
||||||
|
// вызывается из phy_chip_v6.o
|
||||||
|
int ICACHE_FLASH_ATTR chip_v6_unset_chanfreq(void) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user