Unverified Commit c158b4ae authored by Wolle's avatar Wolle Committed by GitHub

first release

parent c3f8c981
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html
/*
* websrv.cpp
*
* Created on: 09.07.2017
* updated on: 19.10.2022
* Author: Wolle
*/
#include "websrv.h"
//--------------------------------------------------------------------------------------------------------------
WebSrv::WebSrv(String Name, String Version){
_Name=Name; _Version=Version;
method = HTTP_NONE;
}
//--------------------------------------------------------------------------------------------------------------
void WebSrv::show_not_found(){
cmdclient.print("HTTP/1.1 404 Not Found\n\n");
return;
}
//--------------------------------------------------------------------------------------------------------------
String WebSrv::calculateWebSocketResponseKey(String sec_WS_key){
// input Sec-WebSocket-Key from client
// output Sec-WebSocket-Accept-Key (used in response message to client)
unsigned char sha1_result[20];
String concat = sec_WS_key + WS_sec_conKey;
mbedtls_sha1_ret((unsigned char*)concat.c_str(), concat.length(), (unsigned char*) sha1_result );
return base64::encode(sha1_result, 20);
}
//--------------------------------------------------------------------------------------------------------------
void WebSrv::printWebSocketHeader(String wsRespKey){
String wsHeader = (String)"HTTP/1.1 101 Switching Protocols\r\n" +
"Upgrade: websocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Accept: " + wsRespKey + "\r\n" +
"Access-Control-Allow-Origin: \r\n\r\n";
// "Sec-WebSocket-Protocol: chat\r\n\r\n";
//log_i("wsheader %s", wsHeader.c_str());
webSocketClient.print(wsHeader) ; // header sent
}
//--------------------------------------------------------------------------------------------------------------
void WebSrv::show(const char* pagename, int16_t len){
uint TCPCHUNKSIZE = 1024; // Max number of bytes per write
size_t pagelen=0, res=0; // Size of requested page
const unsigned char* p;
p = reinterpret_cast<const unsigned char*>(pagename);
if(len==-1){
pagelen=strlen(pagename);
}
else{
if(len>0) pagelen = len;
}
while((*p=='\n') && (pagelen>0)){ // If page starts with newline:
p++; // Skip first character
pagelen--;
}
// HTTP header
String httpheader="";
httpheader += "HTTP/1.1 200 OK\r\n";
httpheader += "Connection: close\r\n";
httpheader += "Content-type: text/html\r\n";
httpheader += "Content-Length: " + String(pagelen, 10) + "\r\n";
httpheader += "Server: " + _Name+ "\r\n";
httpheader += "Cache-Control: max-age=86400\r\n";
httpheader += "Last-Modified: " + _Version + "\r\n\r\n";
cmdclient.print(httpheader) ; // header sent
sprintf(buff, "Length of page is %d", pagelen);
if(WEBSRV_onInfo) WEBSRV_onInfo(buff);
// The content of the HTTP response follows the header:
while(pagelen){ // Loop through the output page
if (pagelen <= TCPCHUNKSIZE){ // Near the end?
res=cmdclient.write(p, pagelen); // Yes, send last part
if(res!=pagelen){
log_e("write error in webpage");
cmdclient.clearWriteError();
return;
}
pagelen = 0;
}
else{
res=cmdclient.write(p, TCPCHUNKSIZE); // Send part of the page
if(res!=TCPCHUNKSIZE){
log_e("write error in webpage");
cmdclient.clearWriteError();
return;
}
p += TCPCHUNKSIZE; // Update startpoint and rest of bytes
pagelen -= TCPCHUNKSIZE;
}
}
return;
}
//--------------------------------------------------------------------------------------------------------------
boolean WebSrv::streamfile(fs::FS &fs,const char* path){ // transfer file from SD to webbrowser
size_t bytesPerTransaction = 1024;
uint8_t transBuf[bytesPerTransaction], i=0;
size_t wIndex = 0, res=0, leftover=0;
if(!cmdclient.connected()){log_e("not connected"); return false;}
while(path[i] != 0){ // protect SD for invalid signs to avoid a crash!!
if(path[i] < 32)return false;
i++;
}
if(!fs.exists(path)) return false;
File file = fs.open(path, "r");
if(!file){
sprintf(buff, "Failed to open file for reading %s", path);
if(WEBSRV_onInfo) WEBSRV_onInfo(buff);
show_not_found();
return false;
}
sprintf(buff, "Length of file %s is %d", path, file.size());
if(WEBSRV_onInfo) WEBSRV_onInfo(buff);
// HTTP header
String httpheader="";
httpheader += "HTTP/1.1 200 OK\r\n";
httpheader += "Connection: close\r\n";
httpheader += "Content-type: " + getContentType(String(path)) +"\r\n";
httpheader += "Content-Length: " + String(file.size(),10) + "\r\n";
httpheader += "Server: " + _Name+ "\r\n";
httpheader += "Cache-Control: max-age=86400\r\n";
httpheader += "Last-Modified: " + _Version + "\r\n\r\n";
cmdclient.print(httpheader) ; // header sent
while(wIndex+bytesPerTransaction < file.size()){
file.read(transBuf, bytesPerTransaction);
res=cmdclient.write(transBuf, bytesPerTransaction);
wIndex+=res;
if(res!=bytesPerTransaction){
log_i("write error %s", path);
cmdclient.clearWriteError();
return false;
}
}
leftover=file.size()-wIndex;
file.read(transBuf, leftover);
res=cmdclient.write(transBuf, leftover);
wIndex+=res;
if(res!=leftover){
log_i("write error %s", path);
cmdclient.clearWriteError();
return false;
}
if(wIndex!=file.size()) log_e("file %s not correct sent", path);
file.close();
return true;
}
//--------------------------------------------------------------------------------------------------------------
boolean WebSrv::send(String msg, uint8_t opcode) { // sends text messages via websocket
return send(msg.c_str(), opcode);
}
//--------------------------------------------------------------------------------------------------------------
boolean WebSrv::send(const char *msg, uint8_t opcode) { // sends text messages via websocket
uint8_t headerLen = 2;
if(!hasclient_WS) {
// log_e("can't send, websocketserver not connected");
return false;
}
size_t msgLen = strlen(msg);
if(msgLen > UINT16_MAX) {
log_e("send: message too long, greather than 64kB");
return false;
}
uint8_t fin = 1;
uint8_t rsv1 = 0;
uint8_t rsv2 = 0;
uint8_t rsv3 = 0;
uint8_t mask = 0;
buff[0] = (128 * fin) + (64 * rsv1) + (32 * rsv2) + (16 * rsv3) + opcode;
if(msgLen < 126) {
buff[1] = (128 * mask) + msgLen;
}
else {
headerLen = 4;
buff[1] = (128 * mask) + 126;
buff[2] = (msgLen >> 8) & 0xFF;
buff[3] = msgLen & 0xFF;
}
webSocketClient.write(buff, headerLen);
webSocketClient.write(msg, msgLen);
return true;
}
//--------------------------------------------------------------------------------------------------------------
void WebSrv::sendPing(){ // heartbeat, keep alive via websockets
if(!hasclient_WS) {
return;
}
uint8_t fin = 1;
uint8_t rsv1 = 0;
uint8_t rsv2 = 0;
uint8_t rsv3 = 0;
uint8_t mask = 0;
buff[0] = (128 * fin) + (64 * rsv1) + (32 * rsv2) + (16 * rsv3) + Ping_Frame;
buff[1] = (128 * mask) + 0;
webSocketClient.write(buff,2);
}
//--------------------------------------------------------------------------------------------------------------
void WebSrv::sendPong(){ // heartbeat, keep alive via websockets
if(!hasclient_WS) {
return;
}
uint8_t fin = 1;
uint8_t rsv1 = 0;
uint8_t rsv2 = 0;
uint8_t rsv3 = 0;
uint8_t mask = 0;
buff[0] = (128 * fin) + (64 * rsv1) + (32 * rsv2) + (16 * rsv3) + Pong_Frame;
buff[1] = (128 * mask) + 0;
webSocketClient.write(buff,2);
}
//--------------------------------------------------------------------------------------------------------------
boolean WebSrv::uploadB64image(fs::FS &fs,const char* path, uint32_t contentLength){ // transfer imagefile from webbrowser to SD
size_t bytesPerTransaction = 1024;
uint8_t tBuf[bytesPerTransaction];
uint16_t av, i, j;
uint32_t len = contentLength;
boolean f_werror=false;
String str="";
int n=0;
File file;
fs.remove(path); // Remove a previous version, otherwise data is appended the file again
file = fs.open(path, FILE_WRITE); // Open the file for writing (create it, if doesn't exist)
log_i("ContentLength %i", contentLength);
str = str + cmdclient.readStringUntil(','); // data:image/jpeg;base64,
len -= str.length();
while(cmdclient.available()){
av=cmdclient.available();
if(av==0) break;
if(av>bytesPerTransaction) av=bytesPerTransaction;
if(av>len) av=len;
len -= av;
i=0; j=0;
cmdclient.read(tBuf, av); // b64 decode
while(i<av){
if(tBuf[i]==0)break; // ignore all other stuff
n=B64index[tBuf[i]]<<18 | B64index[tBuf[i+1]]<<12 | B64index[tBuf[i+2]]<<6 | B64index[tBuf[i+3]];
tBuf[j ]= n>>16;
tBuf[j+1]= n>>8 & 0xFF;
tBuf[j+2]= n & 0xFF;
i+=4;
j+=3;
}
if(tBuf[j]=='=') j--;
if(tBuf[j]=='=') j--; // remove =
if(file.write(tBuf, j)!=j) f_werror=true; // write error?
if(len == 0) break;
}
cmdclient.readStringUntil('\n'); // read the remains, first \n
cmdclient.readStringUntil('\n'); // read the remains webkit\n
file.close();
if(f_werror) {
sprintf(buff, "File: %s write error", path);
if(WEBSRV_onInfo) WEBSRV_onInfo(buff);
return false;
}
sprintf(buff, "File: %s written, FileSize: %d", path, contentLength);
//log_i(buff);
if(WEBSRV_onInfo) WEBSRV_onInfo(buff);
return true;
}
//--------------------------------------------------------------------------------------------------------------
boolean WebSrv::uploadfile(fs::FS &fs,const char* path, uint32_t contentLength){ // transfer file from webbrowser to sd
size_t bytesPerTransaction = 1024;
uint8_t tBuf[bytesPerTransaction];
uint16_t av;
uint32_t len = contentLength;
boolean f_werror=false;
String str="";
File file;
if(fs.exists(path)) fs.remove(path); // Remove a previous version, otherwise data is appended the file again
file = fs.open(path, FILE_WRITE); // Open the file for writing in SD (create it, if doesn't exist)
while(cmdclient.available()){
av=cmdclient.available();
if(av>bytesPerTransaction) av=bytesPerTransaction;
if(av>len) av=len;
len -= av;
cmdclient.read(tBuf, av);
if(file.write(tBuf, av)!=av) f_werror=true; // write error?
if(len == 0) break;
}
cmdclient.readStringUntil('\n'); // read the remains, first \n
cmdclient.readStringUntil('\n'); // read the remains webkit\n
file.close();
if(f_werror) {
sprintf(buff, "File: %s write error", path);
if(WEBSRV_onInfo) WEBSRV_onInfo(buff);
return false;
}
sprintf(buff, "File: %s written, FileSize %d: ", path, contentLength);
if(WEBSRV_onInfo) WEBSRV_onInfo(buff);
return true;
}
//--------------------------------------------------------------------------------------------------------------
void WebSrv::begin(uint16_t http_port, uint16_t websocket_port) {
cmdserver.begin(http_port);
webSocketServer.begin(websocket_port);
}
//--------------------------------------------------------------------------------------------------------------
void WebSrv::stop() {
cmdclient.stop();
webSocketClient.stop();
}
//--------------------------------------------------------------------------------------------------------------
String WebSrv::getContentType(String filename){
if (filename.endsWith(".html")) return "text/html" ;
else if (filename.endsWith(".htm" )) return "text/html";
else if (filename.endsWith(".css" )) return "text/css";
else if (filename.endsWith(".txt" )) return "text/plain";
else if (filename.endsWith(".js" )) return "application/javascript";
else if (filename.endsWith(".json")) return "application/json";
else if (filename.endsWith(".svg" )) return "image/svg+xml";
else if (filename.endsWith(".ttf" )) return "application/x-font-ttf";
else if (filename.endsWith(".otf" )) return "application/x-font-opentype";
else if (filename.endsWith(".xml" )) return "text/xml";
else if (filename.endsWith(".pdf" )) return "application/pdf";
else if (filename.endsWith(".png" )) return "image/png" ;
else if (filename.endsWith(".bmp" )) return "image/bmp" ;
else if (filename.endsWith(".gif" )) return "image/gif" ;
else if (filename.endsWith(".jpg" )) return "image/jpeg" ;
else if (filename.endsWith(".ico" )) return "image/x-icon" ;
else if (filename.endsWith(".css" )) return "text/css" ;
else if (filename.endsWith(".zip" )) return "application/x-zip" ;
else if (filename.endsWith(".gz" )) return "application/x-gzip" ;
else if (filename.endsWith(".xls" )) return "application/msexcel" ;
else if (filename.endsWith(".mp3" )) return "audio/mpeg" ;
return "text/plain" ;
}
//--------------------------------------------------------------------------------------------------------------
boolean WebSrv::handlehttp() { // HTTPserver, message received
bool wswitch=true;
int16_t inx0, inx1, inx2, inx3; // Pos. of search string in currenLine
String currentLine = ""; // Build up to complete line
String ct; // contentType
uint32_t contentLength = 0; // contentLength
uint8_t count = 0;
if (!cmdclient.connected()){
log_e("cmdclient schould be connected but is not!");
return false;
}
while (wswitch==true){ // first while
if(!cmdclient.available()){
log_e("Command client schould be available but is not!");
return false;
}
currentLine = cmdclient.readStringUntil('\n');
// log_i("currLine %s", currentLine.c_str());
// If the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 1) { // contains '\n' only
wswitch=false; // use second while
if (http_cmd.length()) {
if(WEBSRV_onInfo) WEBSRV_onInfo(URLdecode(http_cmd).c_str());
if(WEBSRV_onCommand) WEBSRV_onCommand(URLdecode(http_cmd), URLdecode(http_param), URLdecode(http_arg));
}
else if(http_rqfile.length()){
if(WEBSRV_onInfo) WEBSRV_onInfo(URLdecode(http_rqfile).c_str());
if(WEBSRV_onCommand) WEBSRV_onCommand(URLdecode(http_rqfile), URLdecode(http_param), URLdecode(http_arg));
}
else { // An empty "GET"?
if(WEBSRV_onInfo) WEBSRV_onInfo("Filename is: index.html");
if(WEBSRV_onCommand) WEBSRV_onCommand("index.html", URLdecode(http_param), URLdecode(http_arg));
}
currentLine = "";
http_cmd = "";
http_param = "";
http_arg = "";
http_rqfile = "";
method = HTTP_NONE;
break;
} else {
// Newline seen
inx0 = 0;
if (currentLine.startsWith("Content-Length:")) contentLength = currentLine.substring(15).toInt();
if (currentLine.startsWith("GET /")) {method = HTTP_GET; inx0 = 5;} // GET request?
if (currentLine.startsWith("POST /")){method = HTTP_PUT; inx0 = 6;} // POST request?
if (inx0 == 0) method = HTTP_NONE;
if(inx0>0){
inx1 = currentLine.indexOf("?"); // Search for 1st parameter
inx2 = currentLine.lastIndexOf("&"); // Search for 2nd parameter
inx3 = currentLine.indexOf(" HTTP");// Search for 3th parameter
if(inx1 > inx0){ // it is a command
http_cmd = currentLine.substring(inx0, inx1);//isolate the command
http_rqfile = ""; // No file
}
if((inx1>0) && (inx1+1 < inx3)){ // it is a parameter
http_param = currentLine.substring(inx1+1, inx3);//isolate the parameter
if(inx2>0){
http_arg = currentLine.substring(inx2+1, inx3);//isolate the arguments
http_param = currentLine.substring(inx1+1, inx2);//cut the parameter
}
http_rqfile = ""; // No file
}
if(inx1 < 0 && inx2 < 0){ // it is a filename
http_rqfile = currentLine.substring(inx0, inx3);
http_cmd = "";
http_param = "";
http_arg = "";
}
}
currentLine = "";
}
} //end first while
while(wswitch==false){ // second while
if(cmdclient.available()) {
//log_i("%i", cmdclient.available());
currentLine = cmdclient.readStringUntil('\n');
//log_i("currLine %s", currentLine.c_str());
contentLength -= currentLine.length();
}
else{
currentLine = "";
}
if(!currentLine.length()){
return true;
}
if((currentLine.length() == 1 && count == 0) || count >= 2){
wswitch=true; // use first while
currentLine = "";
count = 0;
break;
}
else{ // its the requestbody
if(currentLine.length() > 1){
if(WEBSRV_onRequest) WEBSRV_onRequest(currentLine, 0);
if(WEBSRV_onInfo) WEBSRV_onInfo(currentLine.c_str());
}
if(currentLine.startsWith("------")) {
count++; // WebKitFormBoundary header
contentLength -= (currentLine.length() + 2); // WebKitFormBoundary footer ist 2 chars longer
}
if(currentLine.length() == 1 && count == 1){
contentLength -= 6; // "\r\n\r\n..."
if(WEBSRV_onRequest) WEBSRV_onRequest("fileUpload", contentLength);
count++;
}
}
} // end second while
cmdclient.stop();
return true;
}
//--------------------------------------------------------------------------------------------------------------
boolean WebSrv::handleWS() { // Websocketserver, receive messages
String currentLine = ""; // Build up to complete line
if (!webSocketClient.connected()){
log_e("webSocketClient schould be connected but is not!");
hasclient_WS = false;
return false;
}
if(!hasclient_WS){
while(true){
currentLine = webSocketClient.readStringUntil('\n');
if (currentLine.length() == 1) { // contains '\n' only
if(ws_conn_request_flag){
ws_conn_request_flag = false;
printWebSocketHeader(WS_resp_Key);
hasclient_WS = true;
}
break;
}
if (currentLine.startsWith("Sec-WebSocket-Key:")) { // Websocket connection request
WS_sec_Key = currentLine.substring(18);
WS_sec_Key.trim();
WS_resp_Key = calculateWebSocketResponseKey(WS_sec_Key);
ws_conn_request_flag = true;
}
}
}
int av = webSocketClient.available();
if(av){
parseWsMessage(av);
}
return true;
}
//--------------------------------------------------------------------------------------------------------------
void WebSrv::parseWsMessage(uint32_t len){
uint8_t headerLen = 2;
uint16_t paylodLen;
uint8_t maskingKey[4];
if(len > UINT16_MAX){
log_e("Websocketmessage too long");
return;
}
webSocketClient.readBytes(buff, 1);
uint8_t fin = ((buff[0] >> 7) & 0x01); (void)fin;
uint8_t rsv1 = ((buff[0] >> 6) & 0x01); (void)rsv1;
uint8_t rsv2 = ((buff[0] >> 5) & 0x01); (void)rsv2;
uint8_t rsv3 = ((buff[0] >> 4) & 0x01); (void)rsv3;
uint8_t opcode = (buff[0] & 0x0F);
webSocketClient.readBytes(buff, 1);
uint8_t mask = ((buff[0]>>7) & 0x01);
paylodLen = (buff[0] & 0x7F);
if(paylodLen == 126){
headerLen = 4;
webSocketClient.readBytes(buff, 2);
paylodLen = buff[0] << 8;
paylodLen += buff[1];
}
(void)headerLen;
if(mask){
maskingKey[0] = webSocketClient.read();
maskingKey[1] = webSocketClient.read();
maskingKey[2] = webSocketClient.read();
maskingKey[3] = webSocketClient.read();
}
if(opcode == 0x08) { // denotes a connection close
hasclient_WS = false;
webSocketClient.stop();
return;
}
if(opcode == 0x09) { // denotes a ping
if(WEBSRV_onCommand) WEBSRV_onCommand("ping received, send pong", "", "");
if(WEBSRV_onInfo) WEBSRV_onInfo("pong received, send pong");
sendPong();
}
if(opcode == 0x0A) { // denotes a pong
if(WEBSRV_onCommand) WEBSRV_onCommand("pong received", "", "");
if(WEBSRV_onInfo) WEBSRV_onInfo("pong received");
return;
}
if(opcode == 0x01) { // denotes a text frame
int plen;
while(paylodLen){
if(paylodLen > 255){
plen = 255;
paylodLen -= webSocketClient.readBytes(buff, plen);
}
else{
plen = paylodLen;
paylodLen = 0;
webSocketClient.readBytes(buff, plen);
}
if(mask){
for(int i = 0; i < plen; i++){
buff[i] = (buff[i] ^ maskingKey[i % 4]);
}
}
buff[plen] = 0;
if(WEBSRV_onInfo) WEBSRV_onInfo(buff);
if(len < 256){ // can be a command like "mute=1"
char *ret;
ret = strchr((const char*)buff, '=');
if(ret){
*ret = 0;
// log_i("cmd=%s, para=%s", buff, ret);
if(WEBSRV_onCommand) WEBSRV_onCommand((const char*) buff, ret + 1, "");
buff[0] = 0;
return;
}
}
if(WEBSRV_onCommand) WEBSRV_onCommand((const char*) buff, "", "");
}
buff[0] = 0;
}
}
//--------------------------------------------------------------------------------------------------------------
boolean WebSrv::loop() {
cmdclient = cmdserver.available();
if (cmdclient.available()){ // Check Input from client?
if(WEBSRV_onInfo) WEBSRV_onInfo("Command client available");
return handlehttp();
}
if(!webSocketClient.connected()){
hasclient_WS = false;
}
if(!hasclient_WS) webSocketClient = webSocketServer.available();
if (webSocketClient.available()){
if(WEBSRV_onInfo) WEBSRV_onInfo("WebSocket client available");
return handleWS();
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
void WebSrv::reply(const String &response, bool header){
if(header==true) {
int l= response.length();
// HTTP header
String httpheader="";
httpheader += "HTTP/1.1 200 OK\r\n";
httpheader += "Connection: close\r\n";
httpheader += "Content-type: text/html\r\n";
httpheader += "Content-Length: " + String(l, 10) + "\r\n";
httpheader += "Server: " + _Name+ "\r\n";
httpheader += "Cache-Control: max-age=3600\r\n";
httpheader += "Last-Modified: " + _Version + "\r\n\r\n";
cmdclient.print(httpheader) ; // header sent
}
cmdclient.print(response);
}
//--------------------------------------------------------------------------------------------------------------
String WebSrv::UTF8toASCII(String str){
uint16_t i=0;
String res="";
char tab[96]={
96,173,155,156, 32,157, 32, 32, 32, 32,166,174,170, 32, 32, 32,248,241,253, 32,
32,230, 32,250, 32, 32,167,175,172,171, 32,168, 32, 32, 32, 32,142,143,146,128,
32,144, 32, 32, 32, 32, 32, 32, 32,165, 32, 32, 32, 32,153, 32, 32, 32, 32, 32,
154, 32, 32,225,133,160,131, 32,132,134,145,135,138,130,136,137,141,161,140,139,
32,164,149,162,147, 32,148,246, 32,151,163,150,129, 32, 32,152
};
while(str[i]!=0){
if(str[i]==0xC2){ // compute unicode from utf8
i++;
if((str[i]>159)&&(str[i]<192)) res+=char(tab[str[i]-160]);
else res+=char(32);
}
else if(str[i]==0xC3){
i++;
if((str[i]>127)&&(str[i]<192)) res+=char(tab[str[i]-96]);
else res+=char(32);
}
else res+=str[i];
i++;
}
return res;
}
//--------------------------------------------------------------------------------------------------------------
String WebSrv::URLdecode(String str){
String hex="0123456789ABCDEF";
String res="";
uint16_t i=0;
while(str[i]!=0){
if((str[i]=='%') && isHexadecimalDigit(str[i+1]) && isHexadecimalDigit(str[i+2])){
res+=char((hex.indexOf(str[i+1])<<4) + hex.indexOf(str[i+2])); i+=3;}
else{res+=str[i]; i++;}
}
return res;
}
//--------------------------------------------------------------------------------------------------------------
String WebSrv::responseCodeToString(int code) {
switch (code) {
case 100: return F("Continue");
case 101: return F("Switching Protocols");
case 200: return F("OK");
case 201: return F("Created");
case 202: return F("Accepted");
case 203: return F("Non-Authoritative Information");
case 204: return F("No Content");
case 205: return F("Reset Content");
case 206: return F("Partial Content");
case 300: return F("Multiple Choices");
case 301: return F("Moved Permanently");
case 302: return F("Found");
case 303: return F("See Other");
case 304: return F("Not Modified");
case 305: return F("Use Proxy");
case 307: return F("Temporary Redirect");
case 400: return F("Bad Request");
case 401: return F("Unauthorized");
case 402: return F("Payment Required");
case 403: return F("Forbidden");
case 404: return F("Not Found");
case 405: return F("Method Not Allowed");
case 406: return F("Not Acceptable");
case 407: return F("Proxy Authentication Required");
case 408: return F("Request Time-out");
case 409: return F("Conflict");
case 410: return F("Gone");
case 411: return F("Length Required");
case 412: return F("Precondition Failed");
case 413: return F("Request Entity Too Large");
case 414: return F("Request-URI Too Large");
case 415: return F("Unsupported Media Type");
case 416: return F("Requested range not satisfiable");
case 417: return F("Expectation Failed");
case 500: return F("Internal Server Error");
case 501: return F("Not Implemented");
case 502: return F("Bad Gateway");
case 503: return F("Service Unavailable");
case 504: return F("Gateway Time-out");
case 505: return F("HTTP Version not supported");
default: return "";
}
}
//--------------------------------------------------------------------------------------------------------------
/*
* websrv.h
*
* Created on: 09.07.2017
* updated on: 11.04.2022
* Author: Wolle
*/
#ifndef WEBSRV_H_
#define WEBSRV_H_
#include "Arduino.h"
#include "WiFi.h"
#include "SD.h"
#include "FS.h"
#include "mbedtls/sha1.h"
#include "base64.h"
extern __attribute__((weak)) void WEBSRV_onInfo(const char*);
extern __attribute__((weak)) void WEBSRV_onCommand(const String cmd, const String param, const String arg);
extern __attribute__((weak)) void WEBSRV_onRequest(const String, uint32_t contentLength);
class WebSrv
{
protected:
WiFiClient cmdclient; // An instance of the client for commands
WiFiClient webSocketClient ;
WiFiServer cmdserver;
WiFiServer webSocketServer;
private:
bool http_reponse_flag = false ; // Response required
bool ws_conn_request_flag = false; // websocket connection attempt
bool hasclient_WS = false;
String http_rqfile ; // Requested file
String http_cmd ; // Content of command
String http_param; // Content of parameter
String http_arg; // Content of argument
String _Name;
String _Version;
String contenttype;
char buff[256];
uint8_t method;
String WS_sec_Key;
String WS_resp_Key;
String WS_sec_conKey = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
protected:
String calculateWebSocketResponseKey(String sec_WS_key);
void printWebSocketHeader(String wsRespKey);
String getContentType(String filename);
boolean handlehttp();
boolean handleWS();
void parseWsMessage(uint32_t len);
uint8_t inbyte();
String URLdecode(String str);
String UTF8toASCII(String str);
String responseCodeToString(int code);
public:
enum { HTTP_NONE = 0, HTTP_GET = 1, HTTP_PUT = 2 };
enum { Continuation_Frame = 0x00, Text_Frame = 0x01, Binary_Frame = 0x02, Connection_Close_Frame = 0x08,
Ping_Frame = 0x09, Pong_Frame = 0x0A };
WebSrv(String Name="WebSrv library", String Version="1.0");
void begin(uint16_t http_port = 80, uint16_t websocket_port = 81);
void stop();
boolean loop();
void show(const char* pagename, int16_t len=-1);
void show_not_found();
boolean streamfile(fs::FS &fs,const char* path);
boolean send(String msg, uint8_t opcode = Text_Frame);
boolean send(const char* msg, uint8_t opcode = Text_Frame);
void sendPing();
void sendPong();
boolean uploadfile(fs::FS &fs,const char* path, uint32_t contentLength);
boolean uploadB64image(fs::FS &fs,const char* path, uint32_t contentLength);
void reply(const String &response, boolean header=true);
const char* ASCIItoUTF8(const char* str);
private:
const int B64index[123] ={
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0,
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63,
0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
};
};
#endif /* WEBSRV_H_ */
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x4000
phy_init, data, phy, 0xd000, 0x1000
factory, app, factory, 0x10000, 3M,
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/en/latest/platforms/espressif32.html
[env:esp32dev]
platform = https://github.com/platformio/platform-espressif32.git#v5.2.0 ; Arduino V2.0.5
board = esp32dev ;chipmodel ESP32, 4M FLASH, USBtoTTL
board_build.f_cpu = 240000000L
board_build.flash_size=4MB
board_build.flash_freq=80M
board_build.spiram_mode=2
framework = arduino
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
board_build.partitions = default.csv
upload_speed = 460800 ; 921600, 512000, 460800, 256000, 115200
lib_deps =
https://github.com/schreibfaul1/ESP32-audioI2S.git#2.0.6
https://github.com/yellobyte/SoapESP32.git#1.1.4
https://github.com/arduino-libraries/Arduino_JSON.git
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.5
board_upload.maximum_size = 3145728
board_upload.flash_size = 4MB
board_build.flash_mode = qio
board_build.bootloader = dio
;build_flags = -DCORE_DEBUG_LEVEL=0 ; None
;build_flags = -DCORE_DEBUG_LEVEL=1 ; Error
;build_flags = -DCORE_DEBUG_LEVEL=2 ; Warn
;build_flags = -DCORE_DEBUG_LEVEL=3 ; Info
;build_flags = -DCORE_DEBUG_LEVEL=4 ; Debug
;build_flags = -DCORE_DEBUG_LEVEL=5 ; Verbose
build_flags =
; -Wall
; -Wextra
-Wdouble-promotion ; double to float warnings
-Wimplicit-fallthrough ; switch case without break
-DCORE_DEBUG_LEVEL=3
-DCONFIG_ARDUHAL_LOG_COLORS
-DBOARD_HAS_PSRAM
-DARDUINO_RUNNING_CORE=3 ; Arduino Runs On Core (setup, loop)
-DARDUINO_EVENT_RUNNING_CORE=1 ; Events Run On Core
; -DAUDIO_LOG
build_unflags =
; -DARDUINO_USB_CDC_ON_BOOT=0 ; traditional log
; -DBOARD_HAS_PSRAM
/*
* index.h
*
* Created on: 13.12.2022
* Updated on:
* Author: Wolle
*
* ESP32 - DLNA
*
*/
#ifndef INDEX_H_
#define INDEX_H_
#include "Arduino.h"
// file in raw data format for PROGMEM
const char index_html[] PROGMEM = R"=====(
<!DOCTYPE HTML>
<html>
<head>
<title>ESP32 - DLNA</title>
<style type="text/css"> /* optimized with csstidy */
html { /* This is the groundplane */
font-family : serif;
height : 100%;
font-size: 16px;
color : DarkSlateGray;
background-color : navy;
margin : 0;
padding : 0;
}
#content {
min-height : 540px;
min-width : 725px;
overflow : hidden;
background-color : lightskyblue;
margin : 0;
padding : 5px;
}
#tab-content1 {
margin : 20px;
}
.boxstyle {
height : 36px;
padding-top : 0;
padding-left : 15px;
padding-bottom : 0;
background-color: white;
font-size : 16px;
line-height : normal;
border-color: black;
border-style: solid;
border-width: thin;
border-radius : 5px;
}
#BODY { display:block; }
</style>
</head>
<script>
// global variables and functions
// ---- websocket section------------------------
var socket = undefined
var host = location.hostname
var tm
function ping() {
if (socket.readyState == 1) { // reayState 'open'
socket.send("ping")
console.log("send ping")
tm = setTimeout(function () {
console.log('The connection to the ESP32 is interrupted! Please reload the page!')
}, 10000)
}
}
function connect() {
socket = new WebSocket('ws://'+window.location.hostname+':81/');
socket.onopen = function () {
console.log("Websocket connected")
socket.send('DLNA_getServer')
setInterval(ping, 20000)
};
socket.onclose = function (e) {
console.log(e)
console.log('Socket is closed. Reconnect will be attempted in 1 second.', e)
socket = null
setTimeout(function () {
connect()
}, 1000)
}
socket.onerror = function (err) {
console.log(err)
}
socket.onmessage = function(event) {
var socketMsg = event.data
var n = socketMsg.indexOf('=')
var msg = ''
var val = ''
if (n >= 0) {
var msg = socketMsg.substring(0, n)
var val = socketMsg.substring(n + 1)
// console.log("para ",msg, " val ",val)
}
else {
msg = socketMsg
}
switch(msg) {
case "pong": clearTimeout(tm)
break
case "DLNA_Names": showServer(val)
break
case "Level0": show_DLNA_Content(val, 0)
break
case "Level1": show_DLNA_Content(val, 1)
break
case "Level2": show_DLNA_Content(val, 2)
break
case "Level3": show_DLNA_Content(val, 3)
break
default: console.log('unknown message', msg, val)
}
}
}
// ---- end websocket section------------------------
document.addEventListener('readystatechange', event => {
if (event.target.readyState === 'interactive') { // same as: document.addEventListener('DOMContentLoaded'...
// same as jQuery.ready
console.log('All HTML DOM elements are accessible')
// document.getElementById('dialog').style.display = 'none' // hide the div (its only a template)
}
if (event.target.readyState === 'complete') {
console.log('Now external resources are loaded too, like css,src etc... ')
connect(); // establish websocket connection
}
})
function showServer(val){
console.log(val)
var select = document.getElementById('server')
select.options.length = 0;
var server = val.split(",")
for (i = -1; i < (server.length); i++) {
opt = document.createElement('OPTION')
if(i == -1){
opt.value = ""
opt.text = "Select a DLNA Server here"
}
else{
console.log(server[i])
opt.value = server[i]
opt.text = server[i]
}
select.add(opt)
}
}
function show_DLNA_Content(val, level){
var select
if(level == 0) select = document.getElementById('level1')
if(level == 1) select = document.getElementById('level2')
if(level == 2) select = document.getElementById('level3')
if(level == 3) select = document.getElementById('level4')
content =JSON.parse(val)
//console.log(ct[1].name)
select.options.length = 0;
for (i = -1; i < (content.length); i++) {
opt = document.createElement('OPTION')
if(i == -1){
opt.value = ""
opt.text = "Select level " + level.toString()
}
else{
var n
var c
if(content[i].isDir == true){
n = content[i].name + ' <DIR>';
c = 'D=' + content[i].id // is directory
}
else{
n = content[i].name + ' ' + content[i].size;
c = 'F=' + content[i].id // is file
}
opt.value = c
opt.text = n
}
select.add(opt)
}
}
function selectserver (presctrl) { // preset, select a server
socket.send('DLNA_getContent0=' + presctrl.value)
select = document.getElementById('level1'); select.options.length = 0; // clear next level
select = document.getElementById('level2'); select.options.length = 0;
select = document.getElementById('level3'); select.options.length = 0;
select = document.getElementById('level4'); select.options.length = 0;
console.log('DLNA_getContent0=' + presctrl.value)
}
function select_l0 (presctrl) { // preset, select root
socket.send('DLNA_getContent1=' + presctrl.value)
select = document.getElementById('level2'); select.options.length = 0; // clear next level
select = document.getElementById('level3'); select.options.length = 0;
select = document.getElementById('level4'); select.options.length = 0;
console.log('DLNA_getContent1=' + presctrl.value)
}
function select_l1 (presctrl) { // preset, select level 1
socket.send('DLNA_getContent2=' + presctrl.value)
select = document.getElementById('level3'); select.options.length = 0;
select = document.getElementById('level4'); select.options.length = 0;
console.log('DLNA_getContent2=' + presctrl.value)
}
function select_l2 (presctrl) { // preset, select level 2
socket.send('DLNA_getContent3=' + presctrl.value)
select = document.getElementById('level4'); select.options.length = 0;
console.log('DLNA_getContent3=' + presctrl.value)
}
function select_l3 (presctrl) { // preset, select level 3
socket.send('DLNA_getContent4=' + presctrl.value)
console.log('DLNA_getContent4=' + presctrl.value)
}
</script>
<body id="BODY">
<!--==============================================================================================-->
<div id="content">
<div id="content1">
<div style="font-size: 50px; text-align: center; flex: 1;">
ESP32 - DLNA
</div>
<div style="display: flex;">
<div style="flex: 0 0 calc(100% - 0px);">
<select class="boxstyle" style="width: 100%;" onchange="selectserver(this)" id="server">
<option value="-1">Select a DLNA Server here</option>
</select>
<select class="boxstyle" style="width: 100%; margin-top: 5px;" onchange="select_l0(this)" id="level1">
<option value="-1"> </option>
</select>
<select class="boxstyle" style="width: 100%; margin-top: 5px;" onchange="select_l1(this)" id="level2">
<option value="-1"> </option>
</select>
<select class="boxstyle" style="width: 100%; margin-top: 5px;" onchange="select_l2(this)" id="level3">
<option value="-1"> </option>
</select>
<select class="boxstyle" style="width: 100%; margin-top: 5px;" onchange="select_l3(this)" id="level4">
<option value="-1"> </option>
</select>
</div>
</div>
<hr>
</div>
</div>
<!--==============================================================================================-->
</body>
</html>
)=====";
#endif /* INDEX_H_ */
\ No newline at end of file
#include <Arduino.h>
#include <WiFi.h>
#include "websrv.h"
#include "index.h"
#include "Audio.h"
#include "SoapESP32.h"
#include "Arduino_JSON.h"
#include <vector>
using namespace std;
#define I2S_DOUT 25
#define I2S_BCLK 27
#define I2S_LRC 26
char SSID[] = "*****";
char PASS[] = "*****";
WebSrv webSrv;
WiFiClient client;
WiFiUDP udp;
SoapESP32 soap(&client, &udp);
Audio audio;
uint numServers = 0;
int currentServer = -1;
uint32_t media_downloadPort = 0;
String media_downloadIP = "";
vector<String> names{};
//----------------------------------------------------------------------------------------------------------------------
int DLNA_setCurrentServer(String serverName){
int serverNum = -1;
for(int i = 0; i < names.size(); i++){
if(names[i] == serverName) serverNum = i;
}
currentServer = serverNum;
return serverNum;
}
void DLNA_showServer(){ // Show connection details of all discovered, usable media servers
String msg = "DLNA_Names=";
soapServer_t srv;
names.clear();
for(int i = 0; i < numServers; i++){
soap.getServerInfo(i, &srv);
Serial.printf("Server[%d]: IP address: %s port: %d name: %s -> controlURL: %s\n",
i, srv.ip.toString().c_str(), srv.port, srv.friendlyName.c_str(), srv.controlURL.c_str());
msg += srv.friendlyName;
if(i < numServers - 1) msg += ',';
names.push_back(srv.friendlyName);
}
webSrv.send(msg);
}
void DLNA_browseServer(String objectId, uint8_t level){
JSONVar myObject;
soapObjectVect_t browseResult;
soapObject_t object;
// Here the user selects the DLNA server whose content he wants to see, level 0 is root
if(level == 0){
if(DLNA_setCurrentServer(objectId) < 0) {log_e("DLNA Server not found"); return;}
objectId = "0";
}
soap.browseServer(currentServer, objectId.c_str(), &browseResult);
if(browseResult.size() == 0){
log_i("no content!"); // then the directory is empty
return;
}
log_i("objectID: %s", objectId.c_str());
for (int i = 0; i < browseResult.size(); i++){
object = browseResult[i];
myObject[i]["name"]= object.name;
myObject[i]["isDir"] = object.isDirectory;
if(object.isDirectory){
myObject[i]["id"] = object.id;
}
else {
myObject[i]["id"] = object.uri;
media_downloadPort = object.downloadPort;
media_downloadIP = object.downloadIp.toString();
}
myObject[i]["size"] = (uint32_t)object.size;
myObject[i]["uri"] = object.id;
log_i("objectName %s", browseResult[i].name.c_str());
log_i("objectId %s", browseResult[i].artist.c_str());
}
String msg = "Level" + String(level,10) + "=" + JSON.stringify(myObject);
log_i("msg = %s", msg.c_str());
webSrv.send(msg);
browseResult.clear();
}
void DLNA_getFileItems(String uri){
soapObjectVect_t browseResult;
log_i("uri: %s", uri.c_str());
log_w("downloadIP: %s", media_downloadIP.c_str());
log_w("downloadport: %d", media_downloadPort);
String URL = "http://" + media_downloadIP + ":" + media_downloadPort + "/" + uri;
log_i("URL=%s", URL.c_str());
audio.connecttohost(URL.c_str());
}
void DLNA_showContent(String objectId, uint8_t level){
log_i("obkId=%s", objectId.c_str());
if(level == 0){
DLNA_browseServer(objectId, level);
}
if(objectId.startsWith("D=")) {
objectId = objectId.substring(2);
DLNA_browseServer(objectId, level);
}
if(objectId.startsWith("F=")) {
objectId = objectId.substring(2);
DLNA_getFileItems(objectId);
}
}
//----------------------------------------------------------------------------------------------------------------------
// S E T U P
//----------------------------------------------------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED) delay(1500);
log_i("connected, IP=%s", WiFi.localIP().toString().c_str());
webSrv.begin(80, 81); // HTTP port, WebSocket port
soap.seekServer();
numServers = soap.getServerCount();
audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
audio.setVolume(21); // 0...21
}
//----------------------------------------------------------------------------------------------------------------------
// L O O P
//----------------------------------------------------------------------------------------------------------------------
void loop() {
if(webSrv.loop()) return; // if true: ignore all other for faster response to web
audio.loop();
}
//----------------------------------------------------------------------------------------------------------------------
// E V E N T S
//----------------------------------------------------------------------------------------------------------------------
void WEBSRV_onCommand(const String cmd, const String param, const String arg){ // called from html
log_d("WS_onCmd: cmd=\"%s\", params=\"%s\", arg=\"%s\"", cmd.c_str(),param.c_str(), arg.c_str());
if(cmd == "index.html"){ webSrv.show(index_html); return;}
if(cmd == "ping"){webSrv.send("pong"); return;}
if(cmd == "favicon.ico") return;
if(cmd == "DLNA_getServer") {DLNA_showServer(); return;}
if(cmd == "DLNA_getContent0"){DLNA_showContent(param, 0); return;}
if(cmd == "DLNA_getContent1"){DLNA_showContent(param, 1); return;} // search for level 1 content
if(cmd == "DLNA_getContent2"){DLNA_showContent(param, 2); return;} // search for level 2 content
if(cmd == "DLNA_getContent3"){DLNA_showContent(param, 3); return;} // search for level 3 content
if(cmd == "DLNA_getContent4"){DLNA_showContent(param, 4); return;} // search for level 4 content
log_e("unknown HTMLcommand %s, param=%s", cmd.c_str(), param.c_str());
}
void WEBSRV_onRequest(const String request, uint32_t contentLength){
log_d("WS_onReq: %s contentLength %d", request.c_str(), contentLength);
if(request.startsWith("------")) return; // uninteresting WebKitFormBoundaryString
if(request.indexOf("form-data") > 0) return; // uninteresting Info
log_e("unknown request: %s",request.c_str());
}
void WEBSRV_onInfo(const char* info){
log_v("HTML_info: %s", info); // infos for debug
}
void audio_info(const char *info){
Serial.print("info "); Serial.println(info);
}
void audio_id3data(const char *info){ //id3 metadata
Serial.print("id3data ");Serial.println(info);
}
void audio_eof_stream(const char* info){ // The webstream comes to an end
Serial.print("end of stream: ");Serial.println(info);
}
void audio_bitrate(const char *info){
Serial.print("bitrate ");Serial.println(info);
}
void audio_lasthost(const char *info){ //stream URL played
Serial.print("lasthost ");Serial.println(info);
}
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment