diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a14318 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +Thumbs.db +.DS_Store +*.orig +embedded/node_modules +embedded/dist diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae4476c --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# ESP3DLib 1.0 + + +Library for ESP32 used with 3D printer using [[ESP32 core version](https://github.com/espressif/arduino-esp32) + + +Firmware should work with any 3D printer using Marlin 2.0 + +The web interface files are present in data directory but UI has it's own repository [ESP3D-WEBUI](https://github.com/luc-github/ESP3D-WEBUI/tree/2.1). + +## Donate +Every support is welcome: [PayPal – The safer, easier way to pay online.](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Y8FFE7NA4LJWQ) +Especially if need to buy new modules for testing. + +## Features +* Serial/Wifi bridge using configurable port 8888, here to enable/disable [TCP_IP_DATA_FEATURE](https://github.com/luc-github/ESP3D/blob/master/esp3d/config.h) +* Use GPIO2 to ground to reset all settings in hard way - 2-6 sec after boot / not before!! Set GPIO2 to ground before boot change boot mode and go to special boot that do not reach FW. Currently boot take 10 sec - giving 8 seconds to connect GPIO2 to GND and do an hard recovery for settings, here to enable/disable [RECOVERY_FEATURE](https://github.com/luc-github/ESP8266/blob/master/esp8266/config.h) +* Complete configuration by web browser (Station or Access point) or by Serial commands +* Authentication for sensitive pages, here to enable/disable [AUTHENTICATION_FEATURE](https://github.com/luc-github/ESP3D/blob/master/esp3d/config.h) +* Update firmware by web browser, here to enable/disable [WEB_UPDATE_FEATURE](https://github.com/luc-github/ESP3D/blob/master/esp3d/config.h) +* Control ESP module using commands on serial or data port, here to enable/disable [SERIAL_COMMAND_FEATURE](https://github.com/luc-github/ESP3D/blob/master/esp3d/config.h) +* Captive portal in Access point mode which redirect all unknow call to main page, here to enable/disable [CAPTIVE_PORTAL_FEATURE](https://github.com/luc-github/ESP3D/blob/master/esp3d/config.h) +* mDNS which allows to key the name defined in web browser and connect only with bonjour installed on computer, here to enable/disable [MDNS_FEATURE](https://github.com/luc-github/ESP3D/blob/master/esp3d/config.h) +* SSDP, this feature is a discovery protocol, supported on Windows out of the box, here to enable/disable [SSDP_FEATURE](https://github.com/luc-github/ESP3D/blob/master/esp3d/config.h) +* Fail safe mode (Access point)is enabled if cannot connect to defined station at boot. +* Choice of web server Async or Sync +* Websocket support +* OLED screen support +* The web ui add even more feature : https://github.com/luc-github/ESP3D-WEBUI/blob/master/README.md#features + + +## Web configuration +*Wifi Mode : Access point / Client station +*IP Generation: DHCP/Static IP +*IP/MASK/GATEWAY for static data +*Baud Rate for serial (supported : 9600, 19200, 38400, 57600, 115200, 230400, 250000) +*web port and data port + + +## Default Configuration +Default Settings: +AP:ESP8266 +PW:12345678 +Authentification: WPA +Mode: g (n is not supported by AP, just by STA) +channel: 11 +AP: visible +Sleep Mode: Modem +IP Mode: Static IP +IP: 192.168.0.1 +Mask: 255.255.255.0 +GW:192.168.0.1 +Baud rate: 115200 +Web port:80 +the websocket is web port + 1 => 80+1 : 81 +Data port: 8888 +Web Page refresh: 3 secondes +User: admin +Password: admin +User:user +Password: user + + + +## Direct commands: +Check wiki : https://github.com/luc-github/ESP3DLib/wiki/Direct-ESP3D-commands + +h your Printer fw with ESP connected on Serial - it bring troubles, at least on DaVinci, but no issue if you update using web UI + +## Contribution/customization +* To style the code before pushing PR please use [astyle --style=otbs *.h *.cpp *.ino](http://astyle.sourceforge.net/) +* The embedded page is created using nodejs then gulp to generate a compressed html page (tool.html.gz), all necessary modules can be installed using the install.bat file content, then it is included using bin2c (https://sourceforge.net/projects/bin2c/) to generate the h file used to create the file nofile.h, update the array and size according new out.h. +* The current UI is located [here](https://github.com/luc-github/ESP3D-WEBUI) + +# :question:Any question ? +Check [Wiki](https://github.com/luc-github/ESP3DLib/wiki) or [![Join the chat at https://gitter.im/luc-github/ESP3D](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/luc-github/ESP3D?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## :exclamation:Any issue/feedback ? +Check [FAQ](https://github.com/luc-github/ESP3DLib/issues?utf8=%E2%9C%93&q=label%3AFAQ+) or [submit ticket](https://github.com/luc-github/ESP3DLib/issues) + + +## TODO/On going : +-- Put feature at same level as ESP3D V2.1 / V3.0 diff --git a/astyle.bat b/astyle.bat new file mode 100644 index 0000000..484dc7a --- /dev/null +++ b/astyle.bat @@ -0,0 +1,7 @@ + +cd %~dp0src +astyle --recursive --style=otbs *.h *.cpp *.ino +del /S *.ori +dir +cd .. +pause diff --git a/examples/basicesp3d/basicesp3d.ino b/examples/basicesp3d/basicesp3d.ino new file mode 100644 index 0000000..e2f54d8 --- /dev/null +++ b/examples/basicesp3d/basicesp3d.ino @@ -0,0 +1,35 @@ +/* + basic esp3dlib sample + + Copyright (c) 2019 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +//library include +#include "esp3dLib.h" + +//global variable +Esp3DLib myesp3d; + +//Setup +void setup() +{ + myesp3d.init(); +} + +//main loop +void loop() +{ +} diff --git a/images/ESP3D_social.png b/images/ESP3D_social.png new file mode 100644 index 0000000..4c4df6a Binary files /dev/null and b/images/ESP3D_social.png differ diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..0630d2f --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=ESP3DLib +version=1.0.0 +author=Luc Lebosse +maintainer=Luc Lebosse, +sentence=A 3D printer front end for ESP boards. +paragraph=This library implements a 3D printer front end. +category=Communication +url=https://github.com/luc-github/ESP3DLib +architectures=esp32 diff --git a/src/esp3dlib.cpp b/src/esp3dlib.cpp new file mode 100644 index 0000000..249e52c --- /dev/null +++ b/src/esp3dlib.cpp @@ -0,0 +1,61 @@ +/* + This file is part of ESP3DLib library for 3D printer. + + ESP3D Firmware for 3D printer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ESP3D Firmware for 3D printer 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 Firmware. If not, see . + + This firmware is using the standard arduino IDE with module to support ESP8266/ESP32: + https://github.com/esp8266/Arduino + https://github.com/espressif/arduino-esp32 + + Latest version of the code and documentation can be found here : + https://github.com/luc-github/ESP3D + + Main author: luc lebosse + +*/ +#include "esp3dlib.h" + +void WiFiTaskfn( void * parameter ) +{ + WiFiConfig::begin(); + for(;;) { + wifi_config.handle(); + wifi_config.wait(0); // Yield to other tasks + } + vTaskDelete( NULL ); +} + + + + +//Contructor +Esp3DLib::Esp3DLib() +{ + +} + +//Begin which setup everything +void Esp3D::init() +{ + + xTaskCreatePinnedToCore( + WiFiTaskfn, /* Task function. */ + "WiFi Task", /* name of task. */ + 10000, /* Stack size of task */ + NULL, /* parameter of the task */ + 1, /* priority of the task */ + NULL, /* Task handle to keep track of created task */ + 0 /* Core to run the task */ + ); +} diff --git a/src/esp3dlib.h b/src/esp3dlib.h new file mode 100644 index 0000000..505b700 --- /dev/null +++ b/src/esp3dlib.h @@ -0,0 +1,35 @@ +/* + esp3dlib.h - esp3dlib class + + Copyright (c) 2019 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ESP3DLIB_H +#define ESP3DLIB_H +//be sure correct IDE and settings are used for ESP8266 or ESP32 +#if !defined(ARDUINO_ARCH_ESP32) +#error Oops! Make sure you have 'ESP32' compatible board selected +#endif + +#include "Arduino.h" +class Esp3DLib +{ +public: + Esp3DLib(); + void init(); +}; +#endif diff --git a/src/nofile.h b/src/nofile.h new file mode 100644 index 0000000..f10f2d8 --- /dev/null +++ b/src/nofile.h @@ -0,0 +1,329 @@ +/* + nofile.h - ESP3D data file + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//data generated by https://github.com/AraHaan/bin2c +//bin2c Conversion Tool v0.14.0 - Windows - [FINAL]. +#define PAGE_NOFILES_SIZE 4862 +const char PAGE_NOFILES [] PROGMEM = { + 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xED, 0x5C, 0x7D, 0x93, 0xDA, 0x46, + 0x93, 0xFF, 0x2A, 0xB2, 0x52, 0x36, 0x70, 0x2B, 0x40, 0x12, 0xAF, 0x8B, 0x16, 0xF2, 0x24, 0xB1, + 0x7D, 0xF1, 0x95, 0x13, 0xBB, 0xBC, 0xEB, 0x7B, 0xAE, 0x2A, 0x4E, 0xB9, 0x84, 0x34, 0x80, 0xCE, + 0x42, 0xD2, 0x49, 0xC3, 0xEE, 0x62, 0xC2, 0x77, 0xBF, 0xEE, 0x79, 0x91, 0x46, 0x42, 0xB0, 0xEC, + 0x26, 0x79, 0xF2, 0xFC, 0x91, 0x60, 0x23, 0x98, 0x99, 0xEE, 0xE9, 0xE9, 0xE9, 0xFE, 0x75, 0x4F, + 0x0F, 0xCE, 0xD5, 0x8A, 0xAE, 0xC3, 0xD9, 0xD5, 0x8A, 0xB8, 0xFE, 0xEC, 0x2A, 0xA3, 0xDB, 0x90, + 0xCC, 0xB0, 0x65, 0xB7, 0x88, 0x23, 0xDA, 0x5E, 0xB8, 0xEB, 0x20, 0xDC, 0x4E, 0x32, 0x37, 0xCA, + 0xDA, 0x19, 0x49, 0x83, 0x85, 0xD3, 0x5E, 0x67, 0x6D, 0x4A, 0xEE, 0x69, 0x3B, 0x0B, 0xBE, 0x92, + 0xB6, 0xEB, 0xFF, 0xEF, 0x26, 0xA3, 0x13, 0xCB, 0x34, 0x9F, 0x3B, 0xED, 0x3B, 0x32, 0xFF, 0x12, + 0xD0, 0x23, 0xBD, 0x8C, 0x1D, 0xB6, 0xC2, 0xD7, 0xE4, 0x7E, 0x3F, 0x8F, 0xFD, 0x6D, 0x69, 0x0A, + 0xFD, 0x47, 0x12, 0xDE, 0x12, 0x1A, 0x78, 0xAE, 0xF6, 0x33, 0xD9, 0x10, 0xDD, 0xC8, 0xBF, 0x1B, + 0xDF, 0xA5, 0x81, 0x1B, 0x1A, 0x8A, 0x0C, 0x0A, 0xAF, 0x7E, 0x72, 0xEF, 0x84, 0x41, 0x44, 0xDA, + 0x2B, 0x12, 0x2C, 0x57, 0x30, 0x57, 0xA7, 0x6F, 0x8F, 0x07, 0x23, 0xAB, 0xDF, 0x73, 0xBC, 0x38, + 0x8C, 0xD3, 0xC9, 0x37, 0xBD, 0x5E, 0xCF, 0x99, 0xBB, 0xDE, 0x97, 0x65, 0x1A, 0x6F, 0x22, 0xBF, + 0x2D, 0x5A, 0x17, 0x8B, 0xC5, 0xBE, 0xE3, 0x01, 0x1F, 0x17, 0x88, 0xD3, 0xDD, 0xDA, 0x4D, 0x97, + 0x41, 0xD4, 0x4E, 0x19, 0x0F, 0x77, 0x43, 0x63, 0x47, 0xB4, 0x84, 0x64, 0x21, 0x1A, 0x12, 0xD7, + 0xF7, 0x83, 0x68, 0xC9, 0x5B, 0xAC, 0x01, 0xCC, 0x2B, 0x5B, 0x38, 0x15, 0x36, 0xED, 0xA9, 0x3B, + 0x0F, 0xC9, 0x6E, 0x1E, 0xA7, 0x3E, 0x49, 0x27, 0xA6, 0xC3, 0x3F, 0xB4, 0xB3, 0xC4, 0xF5, 0x60, + 0x20, 0x34, 0xAC, 0xDD, 0xFB, 0xF6, 0x5D, 0xE0, 0xD3, 0x15, 0x53, 0xCA, 0xBE, 0xC3, 0xC6, 0xB7, + 0xF9, 0x30, 0xE2, 0xEF, 0x8A, 0x2E, 0x41, 0x3A, 0xB1, 0x92, 0x7B, 0x2D, 0x8B, 0xC3, 0xC0, 0xD7, + 0xBE, 0xF1, 0x7D, 0x5F, 0x4A, 0x35, 0x8F, 0x29, 0x8D, 0xD7, 0x13, 0x1B, 0x35, 0x49, 0x81, 0x6C, + 0x15, 0x50, 0xC2, 0x66, 0x21, 0x93, 0x28, 0xBE, 0x4B, 0xDD, 0x44, 0xCA, 0x36, 0xB1, 0xD7, 0xEB, + 0x3D, 0x5D, 0xED, 0xD8, 0x9E, 0xB8, 0x61, 0xB0, 0x8C, 0x26, 0x28, 0xBF, 0x98, 0x78, 0x46, 0x71, + 0x1B, 0x66, 0x34, 0x9D, 0x51, 0xDF, 0x38, 0x68, 0x5A, 0xE5, 0x4D, 0xCC, 0x36, 0xCA, 0xA3, 0xF2, + 0xA6, 0xD5, 0x4E, 0x4E, 0x35, 0x3E, 0xBE, 0x15, 0xB7, 0x24, 0xC5, 0x9D, 0x0C, 0x85, 0x08, 0x34, + 0x4E, 0xA4, 0x6A, 0xE0, 0x63, 0x65, 0x8D, 0x55, 0xA5, 0xD4, 0x08, 0x59, 0xD7, 0xB7, 0x3A, 0xEC, + 0x3B, 0x10, 0xBB, 0xAE, 0x6F, 0xB5, 0xAB, 0xD5, 0xF4, 0xA1, 0x14, 0x8F, 0xE2, 0x26, 0x76, 0x48, + 0xEC, 0xB5, 0x0D, 0xDB, 0x24, 0x68, 0x32, 0x9A, 0x06, 0x89, 0x22, 0xF8, 0x24, 0xA2, 0xAB, 0x76, + 0xBC, 0x68, 0xD3, 0x6D, 0x42, 0x9A, 0xB1, 0xEF, 0xB7, 0x76, 0x35, 0xB6, 0x7A, 0x89, 0xAF, 0xFD, + 0x3F, 0xD6, 0xC4, 0x0F, 0x5C, 0xAD, 0xB9, 0x06, 0x03, 0xE0, 0x7C, 0x47, 0x43, 0xD0, 0x79, 0x6B, + 0xA7, 0xD8, 0xB1, 0x68, 0x1F, 0xA0, 0x61, 0xD4, 0x10, 0x5C, 0x5E, 0xDA, 0xB5, 0x04, 0x97, 0xA3, + 0x23, 0x04, 0x96, 0x6D, 0x9A, 0xB5, 0x14, 0x96, 0xC5, 0x49, 0x3A, 0x91, 0x7B, 0xAB, 0x9A, 0xAD, + 0x10, 0xD9, 0xF3, 0xBC, 0x8A, 0xC3, 0x98, 0x55, 0x77, 0x31, 0xC1, 0x58, 0x32, 0x70, 0x63, 0x44, + 0x1C, 0xB0, 0xDA, 0x88, 0xD4, 0x78, 0x29, 0xF3, 0x5D, 0xAE, 0xD0, 0xD4, 0xF5, 0x83, 0x4D, 0x36, + 0x19, 0x82, 0x91, 0xD5, 0x38, 0x81, 0xBB, 0x4B, 0xE2, 0x2C, 0xA0, 0x41, 0x1C, 0x4D, 0x52, 0x12, + 0xBA, 0x34, 0xB8, 0x25, 0x8E, 0x1F, 0x64, 0x49, 0xE8, 0x6E, 0x27, 0xF3, 0x30, 0xF6, 0xBE, 0xE4, + 0x0E, 0x81, 0xE8, 0xA3, 0x31, 0xF7, 0x65, 0x3E, 0xE1, 0x13, 0x2F, 0x4E, 0x5D, 0x46, 0xC8, 0x64, + 0x28, 0xE4, 0xDF, 0x77, 0x5C, 0x0F, 0xF9, 0xEC, 0x0A, 0xC4, 0xA8, 0x91, 0xD0, 0x34, 0x4D, 0x39, + 0x50, 0x73, 0x0D, 0x77, 0xB2, 0x88, 0xBD, 0x4D, 0x06, 0xCF, 0x55, 0x0C, 0x36, 0xBF, 0x53, 0xC1, + 0x26, 0x71, 0x23, 0x12, 0xEE, 0x0E, 0x65, 0xAF, 0x07, 0xA7, 0x23, 0xFE, 0x5F, 0x56, 0x06, 0x82, + 0x9F, 0x44, 0xDD, 0x79, 0x7C, 0xDF, 0xCE, 0x56, 0xAE, 0x1F, 0xDF, 0x4D, 0x4C, 0x0D, 0xA9, 0xF0, + 0x6F, 0xBA, 0x9C, 0xBB, 0x4D, 0xD3, 0xC0, 0x57, 0xC7, 0x1C, 0xB4, 0x9C, 0x73, 0x06, 0x09, 0x49, + 0xDB, 0x0C, 0xA1, 0x73, 0xAD, 0x21, 0xB8, 0x89, 0x0E, 0x34, 0x76, 0x68, 0xDB, 0x1D, 0x6A, 0xF4, + 0x34, 0xE2, 0x0E, 0xF0, 0x25, 0x57, 0x20, 0x1A, 0x95, 0x35, 0x01, 0x12, 0x70, 0xD3, 0x90, 0xAB, + 0xEB, 0xA1, 0x6E, 0x8A, 0x3E, 0x34, 0xA3, 0x9A, 0x2E, 0xA1, 0xC9, 0x8A, 0xF7, 0x86, 0xEE, 0x1C, + 0x94, 0x2D, 0x2D, 0x20, 0x88, 0x18, 0x2E, 0x71, 0x43, 0x28, 0x43, 0x70, 0xC5, 0x98, 0x70, 0x15, + 0x2C, 0xBA, 0xDC, 0x71, 0x0C, 0x1B, 0xE1, 0xF6, 0x32, 0x43, 0x09, 0xA2, 0x45, 0x2C, 0xF7, 0xB3, + 0x07, 0xC6, 0x3F, 0x86, 0x2D, 0x5D, 0xC4, 0xE9, 0xBA, 0x8D, 0x9E, 0x91, 0xC6, 0xC5, 0x64, 0x7C, + 0x16, 0x3E, 0x03, 0x0B, 0x1C, 0x02, 0x0E, 0x7B, 0xFD, 0x22, 0x64, 0xA0, 0x19, 0x6B, 0x96, 0x2D, + 0x27, 0x3B, 0x37, 0x94, 0x0D, 0x06, 0x83, 0x63, 0xD6, 0x52, 0xB4, 0x06, 0x6B, 0x77, 0x29, 0x1D, + 0xEA, 0xC0, 0x86, 0xD0, 0x2F, 0xCF, 0xB2, 0xA1, 0x20, 0xCA, 0x08, 0xD5, 0x8E, 0x18, 0xC9, 0xA8, + 0x6C, 0x4A, 0x0F, 0x8E, 0x6D, 0xC7, 0x6D, 0x9A, 0x42, 0xF8, 0xE6, 0x0E, 0xAA, 0x5A, 0x80, 0x46, + 0xDC, 0x8C, 0x80, 0x6E, 0xDB, 0xF1, 0x86, 0x6A, 0x1D, 0x6B, 0x90, 0x19, 0x05, 0xDF, 0x83, 0xBE, + 0xB2, 0xC2, 0xB9, 0xAB, 0xED, 0xCA, 0xF6, 0x34, 0x1C, 0xBA, 0x0B, 0x72, 0xE9, 0x00, 0x05, 0x6A, + 0x12, 0x02, 0xEE, 0x13, 0x96, 0x66, 0x98, 0xD0, 0x39, 0x96, 0x1D, 0x96, 0x69, 0x1B, 0xD6, 0x68, + 0x60, 0xD8, 0xBD, 0x9E, 0xD1, 0x19, 0xB6, 0x84, 0x0C, 0xA8, 0xEB, 0xA4, 0xE2, 0xCC, 0xDC, 0x47, + 0xE6, 0x34, 0x3A, 0x66, 0x77, 0xEA, 0x60, 0xB3, 0x64, 0x66, 0x7D, 0xD3, 0x74, 0x94, 0x10, 0xED, + 0x91, 0x88, 0x92, 0xB4, 0x1A, 0x35, 0xD7, 0x81, 0xEF, 0x87, 0x84, 0x27, 0x60, 0xF1, 0xC6, 0x5B, + 0xB5, 0x11, 0x76, 0x40, 0x9F, 0x6B, 0x37, 0x0A, 0x92, 0x4D, 0xC8, 0x40, 0xCC, 0x39, 0xDE, 0xE3, + 0x6D, 0xD2, 0x0C, 0x54, 0x94, 0xC4, 0x01, 0x63, 0x7E, 0xA6, 0xC5, 0xB0, 0x7D, 0x4B, 0xDC, 0x14, + 0x24, 0x72, 0x4E, 0xA4, 0x19, 0x8F, 0xB4, 0xE7, 0x1A, 0x13, 0x5C, 0xC7, 0x5F, 0xDB, 0x9B, 0x0C, + 0x93, 0x25, 0x12, 0x12, 0x8F, 0x72, 0x71, 0x70, 0xAD, 0x07, 0x8D, 0xD5, 0x06, 0xA6, 0xF3, 0x76, + 0x92, 0xC2, 0x32, 0xD2, 0xED, 0x69, 0xB4, 0xEE, 0xF5, 0x46, 0xEE, 0x7C, 0x54, 0xC1, 0x20, 0x9B, + 0x0C, 0x7D, 0xB7, 0x5F, 0xE2, 0x22, 0x10, 0xDD, 0x28, 0xB5, 0x71, 0x68, 0x2F, 0x35, 0x31, 0x94, + 0x2F, 0x35, 0x4D, 0x6A, 0x28, 0x27, 0x87, 0x94, 0x07, 0xF1, 0xA1, 0x46, 0x58, 0x7B, 0x3C, 0x34, + 0x2F, 0xCD, 0x8A, 0xB0, 0x96, 0x6D, 0xCF, 0xFB, 0xE6, 0xDE, 0x73, 0x13, 0xDC, 0x54, 0x89, 0xC1, + 0x2C, 0x8D, 0x1A, 0x2B, 0x29, 0xA9, 0xB0, 0xB2, 0x71, 0x01, 0xCA, 0xA3, 0xD1, 0xC8, 0x39, 0xC8, + 0x02, 0xDD, 0x10, 0x4C, 0xAC, 0x04, 0xF2, 0x35, 0xC1, 0xF5, 0xB4, 0x51, 0x1C, 0x6C, 0xA5, 0xE0, + 0xDA, 0xCE, 0x36, 0x9E, 0x47, 0xB2, 0xAC, 0x26, 0x9F, 0xF1, 0x17, 0x0B, 0xD3, 0x1F, 0x57, 0x23, + 0xC1, 0x90, 0x5C, 0x7A, 0xC3, 0x3C, 0x84, 0x78, 0xA3, 0x61, 0xCF, 0x97, 0xAC, 0x7C, 0x37, 0x5A, + 0x82, 0xB6, 0x6A, 0xA0, 0xCF, 0xF6, 0x89, 0x4F, 0x2A, 0x9C, 0xC8, 0xDC, 0xF3, 0x7C, 0x4B, 0x72, + 0x72, 0x2F, 0xFB, 0xFD, 0xBE, 0xBD, 0xEF, 0xAC, 0xDC, 0xAC, 0x4D, 0xD2, 0x14, 0x20, 0xA7, 0x0C, + 0xDB, 0x65, 0x5A, 0x3E, 0xFA, 0xCF, 0x06, 0xC4, 0xA3, 0xD2, 0xD4, 0x62, 0xDA, 0xB8, 0xDF, 0x1B, + 0xF4, 0xFA, 0x4F, 0x46, 0x32, 0x74, 0xCD, 0x6F, 0x3C, 0x32, 0xEE, 0x8F, 0x7B, 0x8F, 0x91, 0xB1, + 0x4A, 0x5B, 0x92, 0x59, 0x88, 0xDB, 0xE6, 0x61, 0xB6, 0x46, 0xD3, 0x62, 0xF3, 0x4F, 0xEA, 0x9A, + 0xEF, 0xF1, 0xBF, 0x46, 0xD7, 0xB5, 0xF2, 0xD4, 0x6A, 0xDB, 0x9E, 0x0F, 0xFA, 0xB6, 0xF7, 0xFB, + 0xB4, 0x3D, 0x1C, 0xCD, 0xAD, 0xE1, 0xF8, 0x69, 0xDA, 0xE6, 0xB4, 0x15, 0xA9, 0x6B, 0xF5, 0x2D, + 0x7D, 0x04, 0x61, 0x45, 0x78, 0xC8, 0x49, 0x3C, 0xF1, 0x2F, 0xC1, 0x8C, 0x16, 0x55, 0xB7, 0xEB, + 0xF7, 0x16, 0x3D, 0x57, 0x65, 0x52, 0xC2, 0x3E, 0xD1, 0xA4, 0x00, 0x98, 0x68, 0x51, 0x90, 0x8F, + 0xB7, 0x4C, 0x0E, 0xC9, 0x26, 0x07, 0x64, 0xE7, 0xC0, 0x9E, 0x77, 0xD9, 0x33, 0x6D, 0xAF, 0x22, + 0xE6, 0x68, 0x68, 0x79, 0xD6, 0x25, 0x13, 0x33, 0x58, 0x2F, 0x77, 0x22, 0x96, 0xAD, 0xDC, 0xA8, + 0x9A, 0x12, 0x0F, 0xEB, 0xF0, 0x8A, 0x27, 0xE0, 0x9C, 0x56, 0x88, 0x50, 0x83, 0x25, 0x26, 0xBE, + 0x2A, 0xF3, 0x9A, 0x20, 0xE2, 0x5F, 0xEE, 0x78, 0x20, 0x38, 0x93, 0xF4, 0xF4, 0xCA, 0x7B, 0xA6, + 0x48, 0x3F, 0xE4, 0xD8, 0x87, 0x56, 0xFA, 0xD7, 0xAF, 0x2B, 0x04, 0xD1, 0x20, 0x43, 0xF8, 0x22, + 0x0D, 0x82, 0x1D, 0xA6, 0xF2, 0xD6, 0x89, 0xB0, 0xB1, 0x45, 0x10, 0x12, 0xF6, 0x9D, 0xBB, 0x6B, + 0x3E, 0xF6, 0xB2, 0x0F, 0xBB, 0x1A, 0x44, 0xC9, 0x86, 0xFE, 0x82, 0xA7, 0xE7, 0x29, 0x8E, 0xFB, + 0x75, 0x32, 0x91, 0xCB, 0xC2, 0xAF, 0xED, 0x4D, 0x12, 0xC6, 0xAE, 0xDF, 0x9E, 0x6F, 0x20, 0x9A, + 0xFD, 0x9D, 0x97, 0xFD, 0x6B, 0xF3, 0x32, 0xE7, 0xA4, 0x9B, 0x0F, 0xE6, 0x9E, 0x79, 0x10, 0xBA, + 0xFB, 0xC3, 0xF9, 0xD8, 0x77, 0x1F, 0xB5, 0xA9, 0xC2, 0x2A, 0xFE, 0xDE, 0xDA, 0x7F, 0x9F, 0xAD, + 0xED, 0x59, 0x73, 0xD3, 0xAF, 0x9E, 0xF4, 0xAD, 0xF9, 0xD0, 0x1F, 0x0F, 0x1E, 0xB7, 0xB5, 0x1C, + 0xC0, 0xFE, 0xDE, 0xDA, 0x7F, 0xF3, 0xAD, 0xB5, 0x87, 0x97, 0xEE, 0xDC, 0xDB, 0xE7, 0x40, 0x5D, + 0x82, 0xF3, 0x32, 0x7A, 0x2B, 0x68, 0x5E, 0x4A, 0x05, 0x04, 0x9A, 0x8B, 0x0A, 0xD3, 0x22, 0x8E, + 0x41, 0xA9, 0x27, 0x0A, 0x4C, 0xAC, 0xFE, 0xF2, 0xB4, 0x1A, 0xD3, 0x41, 0x9D, 0x17, 0x0D, 0x0E, + 0xC3, 0x24, 0xDF, 0xAB, 0xBE, 0x92, 0x34, 0xF4, 0xF0, 0xA5, 0x92, 0x2A, 0x9D, 0xBD, 0xFE, 0xE5, + 0xD8, 0x9F, 0x57, 0x54, 0x3F, 0x30, 0x9F, 0x3B, 0xB2, 0x6E, 0x0A, 0xD2, 0xCA, 0x9D, 0xC2, 0xCF, + 0x60, 0x3B, 0x6B, 0x5E, 0x66, 0xCC, 0x92, 0x20, 0xD2, 0xEC, 0x4C, 0xC3, 0xCD, 0x74, 0x53, 0x2D, + 0x88, 0x16, 0x41, 0x04, 0x96, 0xB0, 0xFF, 0xC7, 0x17, 0xB2, 0x5D, 0xA4, 0xEE, 0x9A, 0x64, 0x1A, + 0x0E, 0xD9, 0x99, 0xCF, 0x77, 0xCC, 0x5C, 0x30, 0x63, 0x9D, 0xA4, 0x31, 0x75, 0x29, 0x69, 0x9A, + 0xAD, 0x3D, 0x16, 0xAD, 0x0E, 0x3B, 0x7A, 0x43, 0x00, 0xD3, 0x65, 0x6B, 0xFF, 0x97, 0x68, 0x70, + 0x1D, 0xFB, 0x6E, 0x51, 0xFF, 0x62, 0x46, 0x94, 0x57, 0x63, 0x17, 0xC1, 0x3D, 0xF1, 0x9D, 0xAF, + 0xED, 0x20, 0xF2, 0xC9, 0x3D, 0x56, 0xDC, 0xCC, 0xA2, 0x10, 0xCC, 0x78, 0x61, 0x7D, 0xD9, 0x61, + 0x25, 0x62, 0x70, 0x5A, 0x68, 0x30, 0x1D, 0xA5, 0x38, 0x27, 0x35, 0x88, 0x9F, 0xD1, 0x5C, 0x16, + 0x21, 0x24, 0x1A, 0xAC, 0xA8, 0x56, 0x5B, 0x89, 0x3D, 0x6C, 0x55, 0x93, 0x90, 0x7E, 0x4B, 0x88, + 0xCA, 0xF2, 0x7F, 0x70, 0xC1, 0x5D, 0xB1, 0xA6, 0x52, 0x75, 0xD1, 0x32, 0xCB, 0x95, 0xC7, 0x52, + 0x55, 0x52, 0xED, 0x14, 0x45, 0xFE, 0x63, 0xB4, 0xA2, 0xFB, 0x18, 0x39, 0x5E, 0x0B, 0xE4, 0xE6, + 0x24, 0x0B, 0x13, 0x4A, 0x7D, 0x16, 0x4B, 0x50, 0x16, 0x42, 0x81, 0x59, 0xCA, 0xA5, 0xEC, 0x96, + 0x73, 0x58, 0xEB, 0xE6, 0x70, 0x58, 0xBA, 0xA8, 0x9A, 0xD4, 0xA8, 0xE3, 0x9B, 0x05, 0xC1, 0x97, + 0xD4, 0x03, 0x56, 0x72, 0x15, 0x2B, 0xB1, 0xC5, 0x84, 0x4E, 0x9E, 0xFC, 0xE2, 0xAB, 0x8E, 0x8B, + 0x8D, 0xAF, 0x63, 0xC5, 0xD9, 0x47, 0xAA, 0xAF, 0x54, 0x9E, 0x5C, 0xE0, 0x4B, 0x8A, 0x57, 0xAE, + 0x40, 0x9B, 0x42, 0x3A, 0xD9, 0x5B, 0x35, 0xF1, 0xA1, 0x94, 0x5E, 0x18, 0x4D, 0xBF, 0x33, 0x20, + 0xEB, 0xC7, 0x2F, 0xE5, 0x50, 0x9C, 0xDF, 0xB9, 0xDB, 0x27, 0xEE, 0x6D, 0xCA, 0xD6, 0xC8, 0xFB, + 0x06, 0x63, 0xF5, 0x2A, 0x26, 0xF3, 0x52, 0x42, 0x22, 0x0D, 0xB2, 0x7D, 0xA0, 0xCF, 0x0B, 0xD7, + 0xA3, 0xE1, 0xE8, 0x28, 0x3D, 0xBB, 0x57, 0xDC, 0x5F, 0x75, 0xF9, 0x4D, 0xEE, 0x55, 0x97, 0xDF, + 0xEB, 0xB2, 0xDB, 0xA6, 0x2B, 0x3F, 0xB8, 0xD5, 0x58, 0xFB, 0x54, 0xCF, 0x4D, 0xC8, 0x9D, 0xC3, + 0x62, 0x37, 0x94, 0x08, 0xE7, 0xE3, 0x97, 0x33, 0xA6, 0x3E, 0xFB, 0x6F, 0xAB, 0x63, 0x6B, 0x2F, + 0xA2, 0x79, 0x96, 0x38, 0xFC, 0xFD, 0xAA, 0x0B, 0xE4, 0xB3, 0x2B, 0x1E, 0x4D, 0x67, 0x57, 0x2B, + 0x7B, 0xF6, 0x86, 0x6A, 0x19, 0x21, 0xEB, 0x4C, 0xDB, 0xC6, 0x1B, 0xCD, 0x8F, 0xB5, 0x28, 0xA6, + 0xDA, 0xCA, 0xC5, 0x8B, 0x90, 0x68, 0xAB, 0x31, 0x87, 0xEF, 0xE0, 0x4D, 0xB2, 0x16, 0x91, 0x80, + 0xAE, 0x48, 0xAA, 0x34, 0x75, 0x96, 0x5F, 0x0D, 0x2D, 0x09, 0xB1, 0xC0, 0xAB, 0xF1, 0x90, 0xAF, + 0x05, 0x54, 0x8B, 0x53, 0xF8, 0xE2, 0x03, 0x9C, 0x21, 0xC3, 0x54, 0x5B, 0x04, 0xE9, 0xFA, 0x0E, + 0x62, 0xA5, 0x16, 0x2C, 0x80, 0x05, 0x1E, 0x84, 0xB1, 0xE4, 0x06, 0x2B, 0xB2, 0x67, 0x38, 0xA1, + 0xE7, 0x46, 0x30, 0x04, 0x14, 0x03, 0x78, 0xA3, 0x01, 0x7B, 0xA2, 0x4D, 0xB4, 0x2B, 0x57, 0xF3, + 0x42, 0x37, 0xCB, 0xA6, 0x7A, 0x7E, 0x8A, 0xD0, 0xB5, 0x55, 0x4A, 0x16, 0x53, 0x7D, 0x45, 0x69, + 0x92, 0x4D, 0xBA, 0xDD, 0x25, 0xC8, 0xB2, 0x99, 0xC3, 0x89, 0x7A, 0xDD, 0x0D, 0x37, 0x5E, 0x9B, + 0x7F, 0xED, 0xBE, 0xBA, 0x7E, 0xDF, 0x7B, 0xD9, 0xFE, 0xE7, 0xAB, 0xEF, 0x3F, 0xBE, 0xD1, 0x67, + 0x67, 0x0F, 0xBD, 0xEA, 0xBA, 0xA0, 0x61, 0xA9, 0x11, 0xD4, 0xAE, 0x98, 0x9D, 0x81, 0xB0, 0xAE, + 0x05, 0xFE, 0x54, 0xBF, 0x7E, 0xFF, 0xE6, 0xF5, 0xEB, 0x6B, 0xFD, 0xB0, 0x5B, 0xDE, 0xA3, 0xE8, + 0xB3, 0xD7, 0xD0, 0xBA, 0xD2, 0x5E, 0x43, 0x60, 0xCC, 0xB6, 0x19, 0x25, 0x6B, 0xA1, 0xE9, 0x03, + 0x02, 0xDC, 0x44, 0x60, 0xC4, 0x52, 0x28, 0x8D, 0xA5, 0x50, 0x3A, 0x46, 0x53, 0x3E, 0x0F, 0x4B, + 0x9F, 0x78, 0x1C, 0xD7, 0xB5, 0x08, 0xC2, 0xC8, 0x54, 0x5F, 0x6F, 0xB1, 0x31, 0xFB, 0xE5, 0x57, + 0x5D, 0x5B, 0x6F, 0x42, 0x1A, 0x24, 0xB8, 0xF1, 0xF2, 0x93, 0x3E, 0xD3, 0x04, 0x27, 0xA9, 0x31, + 0x1A, 0x69, 0x4A, 0x85, 0x52, 0x17, 0x33, 0xF0, 0x54, 0x8C, 0xCF, 0x51, 0xCA, 0xCE, 0x74, 0x50, + 0xBC, 0x17, 0x06, 0xDE, 0x17, 0x58, 0x23, 0x89, 0x7C, 0x9C, 0xAA, 0xD9, 0x72, 0x74, 0xED, 0xD6, + 0x0D, 0x37, 0x40, 0xF7, 0x91, 0x8D, 0xD5, 0x67, 0x25, 0x13, 0x4A, 0xD2, 0x78, 0x99, 0x62, 0x45, + 0x43, 0x58, 0xE1, 0x6D, 0x90, 0x05, 0xF3, 0x20, 0x0C, 0xE8, 0x76, 0xB2, 0x82, 0x7C, 0x8C, 0x44, + 0x52, 0xF4, 0x24, 0x5D, 0xF2, 0x29, 0xD9, 0x07, 0xB0, 0xFC, 0xA9, 0x0E, 0x86, 0x0D, 0x8B, 0xEF, + 0x4A, 0x16, 0x60, 0xD3, 0x29, 0xFF, 0x7B, 0xA0, 0xF7, 0xE3, 0xAA, 0xE3, 0x97, 0xD7, 0x57, 0x14, + 0xA8, 0xA8, 0xAF, 0x31, 0x87, 0x99, 0xEA, 0xE6, 0xF3, 0x5C, 0xA9, 0xE7, 0xA9, 0xA2, 0xB4, 0xEE, + 0x1F, 0xE2, 0x35, 0x24, 0x86, 0x7E, 0xB3, 0x81, 0xB7, 0x99, 0x0D, 0xA3, 0xE1, 0x86, 0x61, 0x43, + 0x51, 0xC3, 0x07, 0xB2, 0x00, 0x69, 0x57, 0x28, 0x39, 0xF5, 0x0F, 0x66, 0x45, 0x39, 0x73, 0x6E, + 0x3F, 0xA4, 0x04, 0x6C, 0xDF, 0x0F, 0xD2, 0x66, 0x4B, 0x57, 0x24, 0x81, 0x93, 0x3C, 0x8C, 0xCC, + 0x6E, 0x97, 0x92, 0xB2, 0x6F, 0x82, 0x4D, 0x33, 0x8C, 0xE3, 0x9F, 0x6F, 0x03, 0x72, 0xF7, 0x7D, + 0x0C, 0x1A, 0xC2, 0x03, 0x76, 0x1F, 0xFF, 0xC0, 0xF8, 0x14, 0xEC, 0x40, 0x83, 0xB6, 0x81, 0xAE, + 0x6D, 0x51, 0x77, 0xBA, 0xA4, 0xEE, 0x29, 0xD4, 0x36, 0x7C, 0x4E, 0x61, 0x90, 0x0D, 0x8F, 0x2D, + 0x7B, 0xC0, 0x2E, 0x86, 0x53, 0x5D, 0xA4, 0x79, 0x7A, 0xB7, 0xE0, 0x83, 0x43, 0xB7, 0x8C, 0x9D, + 0xE0, 0x63, 0x0D, 0x0A, 0x3E, 0xF8, 0xF9, 0x01, 0x3E, 0x98, 0x8F, 0x23, 0x1F, 0x8B, 0x0B, 0x64, + 0xC3, 0x23, 0x4F, 0x6E, 0xA1, 0x75, 0x2C, 0xBE, 0xDE, 0x09, 0x8E, 0x63, 0xD8, 0x6C, 0xC1, 0x84, + 0xE5, 0xC9, 0xFA, 0xEC, 0x02, 0x14, 0x08, 0x3C, 0x40, 0x8F, 0xA0, 0x8A, 0x99, 0x70, 0x11, 0xA1, + 0x53, 0xAE, 0x48, 0x34, 0x17, 0x9E, 0xCC, 0xE5, 0xEA, 0x13, 0x5F, 0x2B, 0xC3, 0xF3, 0x25, 0x98, + 0xF9, 0x26, 0x30, 0x53, 0x73, 0xE9, 0x2A, 0xA7, 0xC4, 0xEB, 0x3E, 0x69, 0xBC, 0x2A, 0x75, 0x17, + 0x6D, 0xA7, 0x2B, 0xED, 0x08, 0x1F, 0x92, 0x82, 0x7F, 0x29, 0xDD, 0xF2, 0xEB, 0xD2, 0xCE, 0x8B, + 0x83, 0x00, 0x1A, 0x21, 0x87, 0x64, 0x66, 0x84, 0x2B, 0xD5, 0x1C, 0x6E, 0xC0, 0xCA, 0x80, 0xF7, + 0x0A, 0xDB, 0x67, 0x3F, 0x83, 0x1F, 0xE4, 0x5F, 0xAE, 0x41, 0x4B, 0xF2, 0x4B, 0xC9, 0x80, 0x2A, + 0x6D, 0x62, 0x45, 0xAC, 0x55, 0x48, 0x2A, 0x26, 0x43, 0x07, 0xC8, 0x71, 0xE2, 0x33, 0xDA, 0x2A, + 0x1B, 0xC7, 0xE3, 0x82, 0x5C, 0xCF, 0x11, 0xDC, 0xE1, 0x91, 0x96, 0xFB, 0x63, 0x06, 0xE9, 0xE6, + 0x26, 0x2B, 0x34, 0x7A, 0xF0, 0x7E, 0x8E, 0x37, 0x16, 0xC8, 0x27, 0x21, 0xFE, 0x23, 0xC3, 0xFD, + 0x07, 0x80, 0xAF, 0xE4, 0xBD, 0x47, 0x71, 0xF0, 0xEE, 0x28, 0x0A, 0x2A, 0xF6, 0xF2, 0x34, 0xE4, + 0x03, 0xDE, 0x07, 0x18, 0xC0, 0x71, 0xEE, 0x10, 0xFD, 0x70, 0x3D, 0xEA, 0x8C, 0x8F, 0x81, 0xBE, + 0xC5, 0x5D, 0x0E, 0x7E, 0xF8, 0xB1, 0x1E, 0xFE, 0x72, 0xCE, 0x70, 0x84, 0x8C, 0xD8, 0xF0, 0x75, + 0xB6, 0xD4, 0x8F, 0xB3, 0x9F, 0x7D, 0x20, 0xB0, 0x79, 0x70, 0x06, 0x8E, 0x96, 0x79, 0xEC, 0xBD, + 0x73, 0x03, 0xDA, 0x81, 0xFF, 0xC0, 0xA9, 0x80, 0x89, 0xC2, 0xCA, 0x83, 0x1C, 0x89, 0x72, 0xCF, + 0xE1, 0x3D, 0x87, 0xC6, 0x5F, 0xDD, 0x74, 0xEE, 0x7E, 0x90, 0x85, 0x26, 0x70, 0xFE, 0xCD, 0xFD, + 0x88, 0xA5, 0x28, 0x65, 0x1B, 0x28, 0x65, 0x2D, 0x75, 0x5D, 0x3C, 0x2D, 0x85, 0x9E, 0x55, 0x6F, + 0xF6, 0x06, 0x44, 0xA7, 0xC1, 0x02, 0x0E, 0xEE, 0x98, 0xAD, 0x40, 0xF0, 0xEF, 0xD5, 0x18, 0x5A, + 0x91, 0x2E, 0xEA, 0x7C, 0x0D, 0x62, 0x25, 0xA5, 0x6E, 0x44, 0x0F, 0x9D, 0x9F, 0xC0, 0x21, 0x69, + 0x26, 0xB3, 0x8F, 0x70, 0xF0, 0x9D, 0x88, 0xE5, 0x55, 0x42, 0xA1, 0x7A, 0x49, 0x20, 0xAD, 0x81, + 0x93, 0xE7, 0x8B, 0xFC, 0x8C, 0xE7, 0xE6, 0xCF, 0xBC, 0x51, 0xA8, 0xBC, 0xB8, 0xF2, 0x2F, 0xD4, + 0xB6, 0x4A, 0xCF, 0x17, 0xE8, 0x3D, 0xF4, 0xDD, 0x01, 0x50, 0x3C, 0x42, 0xA8, 0x44, 0x90, 0xA8, + 0x82, 0xC9, 0xB6, 0x87, 0x85, 0xC3, 0xE0, 0x79, 0x44, 0x97, 0xC2, 0xE5, 0xCB, 0x4E, 0x26, 0x6D, + 0xFF, 0x98, 0xDB, 0x14, 0x71, 0x71, 0x33, 0x5F, 0x07, 0xF4, 0x03, 0xF9, 0xBF, 0x0D, 0x98, 0x1C, + 0x46, 0x33, 0xE1, 0x15, 0xBC, 0xBD, 0x16, 0x3C, 0x20, 0xD1, 0x0D, 0x12, 0x3A, 0x5B, 0x6C, 0x22, + 0x56, 0x6C, 0x01, 0x5F, 0xB8, 0x9D, 0xBB, 0x10, 0x09, 0x77, 0xB7, 0x70, 0x46, 0x06, 0x52, 0xC5, + 0xF9, 0x75, 0x83, 0x4E, 0xBD, 0x4D, 0x8A, 0x45, 0x14, 0x84, 0xEC, 0x0E, 0x1C, 0x3B, 0x03, 0xDA, + 0xD4, 0xBB, 0x7A, 0xCB, 0x88, 0xA6, 0xF0, 0x30, 0x82, 0xA9, 0xE5, 0x80, 0xB6, 0x9A, 0xE4, 0x02, + 0xE9, 0x7C, 0x21, 0x6F, 0x83, 0xC7, 0xD2, 0x86, 0x96, 0xCB, 0xF9, 0x49, 0x57, 0xD8, 0x4C, 0x1B, + 0xDD, 0x86, 0xA3, 0x1D, 0x8F, 0xE8, 0x9F, 0xF4, 0x59, 0x97, 0x79, 0x81, 0xEE, 0x04, 0x57, 0xB4, + 0x13, 0x92, 0x68, 0x49, 0x57, 0x6D, 0xCB, 0x69, 0x45, 0x17, 0x53, 0xFA, 0x4B, 0xF0, 0xEB, 0x05, + 0xCE, 0x7C, 0x64, 0xC6, 0x23, 0x13, 0xEA, 0x17, 0xD1, 0x85, 0xFE, 0xD0, 0xA4, 0xFA, 0x05, 0xE7, + 0x9E, 0xFB, 0xBB, 0x90, 0xC2, 0x08, 0x2E, 0x2E, 0x9C, 0x94, 0xD0, 0x4D, 0x1A, 0x69, 0x6C, 0x5A, + 0xD5, 0x39, 0xF5, 0x7D, 0xAE, 0x48, 0xB0, 0xAF, 0x6C, 0xF5, 0x39, 0x00, 0xC3, 0x51, 0x94, 0x59, + 0x64, 0x13, 0x0D, 0xBB, 0xDF, 0x90, 0x71, 0x9C, 0x7D, 0x96, 0xD9, 0x44, 0x03, 0xB3, 0x09, 0xCB, + 0x1E, 0xE3, 0xDF, 0x06, 0x2C, 0x5A, 0x9D, 0x4A, 0x24, 0x05, 0x8D, 0x81, 0xDD, 0x80, 0x60, 0xDE, + 0xB0, 0xE0, 0x01, 0xE1, 0xBF, 0x31, 0x6C, 0x60, 0xF8, 0xC7, 0x87, 0xE4, 0x3D, 0x28, 0x78, 0x8F, + 0x1A, 0xC2, 0x14, 0x1B, 0x18, 0xD6, 0xE1, 0xE4, 0xEA, 0x3B, 0x0D, 0xAD, 0x3B, 0x13, 0x3A, 0xAB, + 0x72, 0xAC, 0xE7, 0x61, 0x97, 0x79, 0xB0, 0xCC, 0xA0, 0x8E, 0x4B, 0xCF, 0xE4, 0x5C, 0xC6, 0x47, + 0xE4, 0x1A, 0x8E, 0x0A, 0x9E, 0x80, 0xAB, 0x67, 0x49, 0x66, 0x97, 0x79, 0x5A, 0x26, 0x67, 0x8A, + 0x4F, 0xC1, 0x75, 0xAC, 0x72, 0xED, 0x3F, 0x86, 0xA9, 0x7D, 0x59, 0xCB, 0xA4, 0x77, 0xE6, 0x72, + 0xFB, 0x9C, 0x4B, 0xBF, 0xC7, 0x45, 0x1B, 0x71, 0xC9, 0x46, 0x39, 0x4F, 0x85, 0xE5, 0xF0, 0x5C, + 0x9E, 0xC3, 0x3F, 0x81, 0xE7, 0xF8, 0x8F, 0xE0, 0xC9, 0xF3, 0x3F, 0xC5, 0xC0, 0xF1, 0x4C, 0x2F, + 0xED, 0x9B, 0x1B, 0xE9, 0xF9, 0xF6, 0x6D, 0xF7, 0xE1, 0x4F, 0x03, 0x82, 0x33, 0xF8, 0xA3, 0xE6, + 0x4F, 0x1B, 0x3F, 0x8D, 0x8C, 0x9E, 0xF6, 0xD6, 0x36, 0xC6, 0xDA, 0xDB, 0x91, 0x61, 0xF5, 0xD8, + 0xBB, 0xA9, 0xBD, 0xB5, 0xC4, 0x63, 0x6C, 0x58, 0x16, 0x7F, 0x0C, 0x78, 0xE3, 0x10, 0x1E, 0x26, + 0x7B, 0x5C, 0x1A, 0xD6, 0x88, 0xBD, 0x5F, 0xB2, 0x26, 0x1B, 0x86, 0xDB, 0xE2, 0x61, 0x1B, 0xD6, + 0x98, 0x3D, 0xC6, 0xAC, 0x6D, 0x88, 0x5C, 0x87, 0xDA, 0x57, 0x5C, 0x60, 0x1A, 0x7F, 0x81, 0x15, + 0xB2, 0xB3, 0x6A, 0x83, 0xA7, 0xBB, 0x0D, 0xB6, 0xD2, 0xDA, 0x85, 0xF2, 0xB4, 0xE6, 0x33, 0x1E, + 0x10, 0x48, 0x6B, 0xA7, 0x20, 0xC9, 0xC5, 0x94, 0x30, 0xF4, 0x51, 0x71, 0x44, 0x67, 0x29, 0x9E, + 0xA1, 0x03, 0x8E, 0xE8, 0xAD, 0x82, 0x07, 0x9C, 0x65, 0xB1, 0xFC, 0x7C, 0x0D, 0xF9, 0x69, 0xB4, + 0xCC, 0x9A, 0xC4, 0xA0, 0x52, 0x69, 0x80, 0x0B, 0xA4, 0x43, 0xE3, 0xB7, 0xF1, 0x1D, 0x49, 0x7F, + 0x80, 0xDC, 0xA0, 0xD9, 0x02, 0x98, 0xA5, 0x95, 0x16, 0x72, 0x45, 0xBF, 0x6D, 0x5B, 0x13, 0x32, + 0xA3, 0xDF, 0x5A, 0x13, 0xB3, 0x60, 0x8B, 0xB5, 0x3F, 0x97, 0x7A, 0x2B, 0x96, 0x69, 0xB1, 0x0C, + 0x11, 0x45, 0x44, 0xB4, 0x81, 0x5C, 0x1E, 0x00, 0x73, 0xD1, 0x84, 0xA7, 0x7A, 0x12, 0xBC, 0x66, + 0x83, 0x26, 0x9A, 0x7E, 0x41, 0x3A, 0x9C, 0xC0, 0xA0, 0x17, 0xE5, 0x21, 0xBF, 0xA9, 0x5F, 0x6E, + 0x62, 0xEA, 0x86, 0x1A, 0x2F, 0x96, 0x33, 0x22, 0x8A, 0x0D, 0xA7, 0x69, 0x20, 0xC0, 0xFB, 0x2A, + 0x09, 0x44, 0x6C, 0xFF, 0x34, 0xC5, 0x3B, 0xCF, 0xDB, 0x24, 0xBC, 0x4A, 0xAB, 0xE9, 0x6C, 0xE8, + 0xD5, 0x9A, 0x40, 0x1C, 0xD4, 0xD6, 0x41, 0x04, 0x06, 0xD3, 0x60, 0x99, 0x18, 0x47, 0x8C, 0x15, + 0x58, 0xD4, 0xB4, 0x71, 0x09, 0x9F, 0x78, 0x6C, 0x6B, 0xE0, 0x0C, 0x71, 0x4E, 0x0F, 0xB8, 0x0E, + 0x1B, 0xC8, 0x88, 0xC5, 0x21, 0xA2, 0xDA, 0xFF, 0x5C, 0x37, 0xFC, 0xD8, 0xDB, 0xAC, 0x61, 0x0F, + 0x3B, 0x4B, 0x42, 0x5F, 0x85, 0x04, 0x3F, 0x7E, 0xBF, 0x7D, 0x03, 0x7B, 0x27, 0x92, 0xEC, 0x56, + 0x27, 0x88, 0x22, 0x92, 0xFE, 0x78, 0xF3, 0xD3, 0xDB, 0x29, 0x35, 0x50, 0x93, 0x06, 0x6C, 0xF3, + 0x33, 0x35, 0xF8, 0x71, 0x25, 0x47, 0xA5, 0x78, 0x08, 0xB1, 0x87, 0xBE, 0xC1, 0x52, 0xCB, 0xBB, + 0x05, 0x46, 0x45, 0xA3, 0xD4, 0xC7, 0xC3, 0x96, 0xDD, 0x72, 0xD8, 0xEA, 0x68, 0x2A, 0xBD, 0x4C, + 0xBD, 0x57, 0x3E, 0x11, 0xB0, 0x4A, 0x71, 0x17, 0x86, 0x90, 0xA6, 0x69, 0x44, 0x17, 0x56, 0xEB, + 0xE1, 0x38, 0x86, 0x61, 0x11, 0x82, 0x99, 0xE2, 0xAD, 0x45, 0x4C, 0x03, 0xB3, 0x0C, 0x31, 0x25, + 0x01, 0x6C, 0x68, 0xCC, 0xE0, 0x2C, 0x80, 0x09, 0xA9, 0xCC, 0x37, 0xF5, 0x3D, 0x61, 0x77, 0x06, + 0x59, 0x07, 0xE4, 0xA3, 0x4D, 0x69, 0x72, 0x25, 0xD3, 0xAD, 0x5A, 0x75, 0x07, 0x33, 0x69, 0x83, + 0xB2, 0x47, 0x6B, 0xDF, 0x62, 0xC9, 0x00, 0xEA, 0x29, 0x98, 0x9A, 0x10, 0xBC, 0x25, 0x3F, 0xAE, + 0x0B, 0x07, 0xE2, 0x68, 0x4B, 0x6F, 0x5B, 0xA0, 0x57, 0x4E, 0xDF, 0x14, 0xFD, 0x10, 0x75, 0x3B, + 0x78, 0x3A, 0x6D, 0xBD, 0x78, 0xD1, 0x64, 0xCA, 0xBA, 0xF9, 0x30, 0x13, 0x46, 0xC1, 0xB2, 0x6E, + 0x80, 0x18, 0x15, 0x57, 0x14, 0xB8, 0xA9, 0x81, 0x18, 0x6D, 0xF6, 0x89, 0x16, 0x28, 0x63, 0x19, + 0x36, 0xA0, 0x84, 0x61, 0x5B, 0x88, 0x35, 0x36, 0x7E, 0x1E, 0xF2, 0xC7, 0x88, 0xB5, 0x59, 0x88, + 0x0F, 0x6F, 0x2D, 0x5B, 0xBC, 0x5B, 0x1A, 0x0E, 0xB3, 0xCE, 0x40, 0x0C, 0xBC, 0x52, 0xD0, 0xEE, + 0x2D, 0x1E, 0x91, 0xB7, 0xF8, 0x6C, 0x68, 0xF7, 0x36, 0x3C, 0x00, 0x59, 0xB7, 0x36, 0x8B, 0x80, + 0x15, 0x0E, 0xFC, 0x6B, 0x5B, 0x08, 0x6F, 0x35, 0xBA, 0x72, 0x89, 0x39, 0xAB, 0x81, 0xE0, 0x64, + 0x0A, 0x56, 0x3D, 0xCE, 0xCA, 0x32, 0xCF, 0xE0, 0x05, 0x6B, 0x3E, 0xE0, 0xD3, 0xAF, 0xF0, 0xE9, + 0x3F, 0x91, 0xCF, 0xB8, 0xC2, 0x67, 0x7C, 0x06, 0x1F, 0x59, 0x3B, 0x60, 0xF9, 0x13, 0x2C, 0xB3, + 0x71, 0x75, 0xF3, 0x52, 0xE4, 0x6A, 0x9F, 0x44, 0xB2, 0xF6, 0xA9, 0x91, 0x57, 0x48, 0x65, 0x19, + 0x3D, 0xB9, 0x77, 0xC0, 0x7C, 0x5D, 0x51, 0x47, 0x6C, 0x80, 0x1F, 0x33, 0xD0, 0x55, 0xCC, 0x04, + 0xED, 0xEC, 0xA2, 0x01, 0x49, 0xB9, 0x9B, 0x82, 0x2B, 0x4F, 0x3F, 0x83, 0x00, 0xD1, 0x97, 0x52, + 0x56, 0x5D, 0x94, 0x23, 0x67, 0x0D, 0x9C, 0xB9, 0x42, 0xCC, 0x75, 0x2E, 0xF2, 0x61, 0xAC, 0x29, + 0xDE, 0xBC, 0x9C, 0x81, 0x6C, 0x5C, 0xCA, 0x8A, 0x3D, 0x8A, 0xB1, 0x7C, 0x84, 0x34, 0x3B, 0xF3, + 0x79, 0x43, 0x9D, 0xEF, 0x93, 0x28, 0x1C, 0x7D, 0xD2, 0x15, 0x57, 0x7E, 0x09, 0xF1, 0x83, 0x92, + 0x26, 0x43, 0xAA, 0xB2, 0xE8, 0x7A, 0xA3, 0x85, 0x99, 0x26, 0x72, 0x56, 0xD3, 0xC5, 0x92, 0x54, + 0x38, 0x1F, 0xF7, 0x49, 0x7C, 0x43, 0x5F, 0x28, 0xFC, 0x2A, 0x04, 0xBF, 0x0A, 0xAB, 0x7E, 0x15, + 0x0A, 0xBF, 0x9A, 0x56, 0xFD, 0x2A, 0xFC, 0x43, 0xFD, 0x4A, 0xF1, 0xAA, 0x4B, 0x1E, 0x9E, 0x2F, + 0x31, 0xD0, 0x42, 0x90, 0x86, 0x78, 0x2C, 0xDE, 0x06, 0x18, 0x72, 0xFB, 0xE8, 0x45, 0x7D, 0xF4, + 0xBB, 0x01, 0x73, 0x3E, 0x9B, 0x0D, 0xC5, 0x07, 0x06, 0x6A, 0x74, 0xC5, 0x1E, 0xA3, 0x1F, 0xB0, + 0x77, 0x9B, 0x7B, 0x22, 0xF4, 0x9F, 0x17, 0xA7, 0x0B, 0xA3, 0xD2, 0x71, 0x5B, 0xCA, 0x27, 0x00, + 0x2D, 0xDF, 0xFE, 0x3C, 0xAB, 0x39, 0xBC, 0x99, 0x53, 0x51, 0x57, 0x09, 0xF5, 0xCA, 0x76, 0x85, + 0xC5, 0x76, 0x39, 0x72, 0xBF, 0x2A, 0x7D, 0x25, 0xE3, 0x90, 0xF2, 0x3C, 0xD9, 0x56, 0x8E, 0xCF, + 0xFF, 0x24, 0x73, 0x39, 0x1A, 0xEF, 0x8A, 0x9A, 0x54, 0x39, 0xE4, 0x1D, 0x25, 0x60, 0xA5, 0x3A, + 0x75, 0xAC, 0x3C, 0x2E, 0x16, 0x29, 0x89, 0xB0, 0x76, 0xCC, 0x94, 0xE2, 0x08, 0xAF, 0x12, 0x9A, + 0xFA, 0x0F, 0xFC, 0x83, 0xE6, 0x63, 0x17, 0x8E, 0x89, 0x17, 0xB8, 0x99, 0x3C, 0x3D, 0x00, 0x73, + 0x2C, 0xE5, 0x4F, 0x6C, 0x10, 0x81, 0x0C, 0xF6, 0x80, 0xA7, 0x4C, 0xC0, 0x4E, 0xB1, 0x85, 0x31, + 0xB0, 0x81, 0x71, 0xBA, 0x3D, 0xC1, 0x1B, 0xC6, 0x94, 0xD9, 0x2B, 0x05, 0x60, 0x71, 0x52, 0x4B, + 0x52, 0x88, 0x6C, 0x70, 0xB6, 0x7D, 0xCF, 0xCB, 0x35, 0xEC, 0xA2, 0xA1, 0xE0, 0xCD, 0x0A, 0x46, + 0x90, 0x13, 0x80, 0x6A, 0xA3, 0x4D, 0x18, 0x3E, 0x9B, 0x92, 0xCA, 0x3C, 0x9E, 0x64, 0x08, 0xF3, + 0x74, 0xC0, 0x09, 0xD7, 0xCD, 0x96, 0x32, 0x9D, 0x3A, 0x94, 0x45, 0x53, 0x9E, 0x4A, 0x44, 0xE4, + 0x4E, 0xFB, 0x9F, 0x9F, 0xDE, 0xFE, 0x48, 0x69, 0x22, 0x4E, 0xF0, 0x70, 0xA0, 0xD6, 0xBB, 0xCC, + 0x04, 0xBE, 0xE5, 0x3F, 0x85, 0x98, 0xC2, 0x9A, 0x20, 0x6E, 0x42, 0x26, 0x85, 0xAD, 0xBC, 0x6C, + 0x75, 0x41, 0x22, 0x2F, 0xF6, 0xC9, 0xC7, 0x0F, 0x6F, 0x9A, 0xB4, 0x65, 0xB0, 0x4E, 0x96, 0x34, + 0xA8, 0x1D, 0x6A, 0xE2, 0x72, 0x7C, 0x73, 0x45, 0xD1, 0xB6, 0xD5, 0x61, 0xAE, 0xD2, 0x29, 0x2A, + 0x59, 0xA2, 0xAA, 0x15, 0xC2, 0x9A, 0xA3, 0x4E, 0x1C, 0xC1, 0xE2, 0xFC, 0x2D, 0xA6, 0x4A, 0xC4, + 0x5B, 0xE1, 0xAF, 0x0B, 0xA7, 0x79, 0x6E, 0xD0, 0xDA, 0x41, 0xC6, 0xD9, 0x9F, 0x4E, 0xA3, 0x0E, + 0x1B, 0x83, 0xC9, 0x26, 0x69, 0x41, 0x93, 0x6D, 0x9A, 0xD8, 0xC8, 0xD3, 0x2B, 0xA9, 0xE3, 0xFF, + 0xBA, 0x7E, 0xF7, 0x33, 0x20, 0x7A, 0x0A, 0x09, 0x2E, 0x8E, 0xCF, 0x92, 0x38, 0xCA, 0xC8, 0x0D, + 0xB9, 0xA7, 0x27, 0x0C, 0xF6, 0x84, 0x88, 0xA2, 0xDA, 0x66, 0xD4, 0xA6, 0xC4, 0x7B, 0x12, 0xC2, + 0x36, 0x56, 0xCA, 0x23, 0x7B, 0x5C, 0x4D, 0x42, 0xA2, 0xA6, 0xFE, 0x9F, 0xAF, 0x6E, 0xE0, 0x5C, + 0x6F, 0x3C, 0x33, 0x5B, 0xD0, 0x94, 0xC1, 0xF6, 0x34, 0x2B, 0xDB, 0xC5, 0xCB, 0x8C, 0xBB, 0xBC, + 0x8D, 0xD9, 0x37, 0x2C, 0x8C, 0x08, 0xD0, 0x85, 0xED, 0x4C, 0x36, 0xAC, 0x04, 0x20, 0xB3, 0x6F, + 0xC2, 0x7F, 0xDF, 0xE0, 0x77, 0x45, 0xCA, 0xFC, 0x1F, 0x90, 0xBD, 0x1E, 0x5F, 0x17, 0xDE, 0xB6, + 0xB4, 0x3A, 0x3C, 0xA3, 0x3D, 0xE1, 0x7E, 0xE5, 0x7B, 0x20, 0x49, 0x20, 0x4A, 0xA1, 0x80, 0x6A, + 0x5A, 0xA7, 0xD3, 0xD1, 0x2F, 0xF0, 0xF0, 0xF0, 0x1A, 0xAF, 0xFF, 0x9B, 0x66, 0x0B, 0xF3, 0xDD, + 0xFD, 0x9E, 0x8B, 0x74, 0x12, 0x06, 0x64, 0xF1, 0xB6, 0xC5, 0x31, 0x07, 0x8F, 0x0E, 0xE6, 0xB3, + 0xA9, 0xAC, 0xB7, 0xB4, 0x76, 0x4F, 0x96, 0x09, 0x45, 0x3A, 0x01, 0x28, 0x6C, 0xE1, 0xC7, 0x0D, + 0xCE, 0x29, 0x7C, 0xE3, 0x75, 0x9C, 0xAE, 0x5F, 0xBA, 0xD4, 0x75, 0xA2, 0x8E, 0x9B, 0x24, 0xB8, + 0x49, 0x1C, 0x8E, 0xD4, 0x3C, 0xBB, 0x9A, 0x72, 0x52, 0x35, 0xD9, 0xDC, 0xF1, 0x90, 0x89, 0x65, + 0x1D, 0xC3, 0x57, 0x33, 0xF7, 0x8B, 0x50, 0xE0, 0xEB, 0xB5, 0x5E, 0x30, 0xF7, 0x8D, 0x90, 0x87, + 0x4B, 0xA3, 0x98, 0xAF, 0x28, 0x69, 0x1B, 0xA1, 0x71, 0xC8, 0xA0, 0xC5, 0x14, 0xED, 0xD6, 0x78, + 0xB2, 0xE3, 0x0A, 0x4B, 0x7B, 0xFF, 0xEE, 0xFA, 0x06, 0x4F, 0x13, 0x8C, 0x8F, 0xCE, 0x2C, 0xCE, + 0xED, 0x70, 0x15, 0x76, 0x20, 0x32, 0xBD, 0xBA, 0x05, 0x8E, 0x6F, 0x01, 0x90, 0x09, 0x00, 0x2C, + 0x6A, 0x87, 0x17, 0x9D, 0x01, 0x46, 0x8C, 0x67, 0x16, 0x0E, 0x8D, 0x23, 0x1C, 0x5A, 0xF1, 0x38, + 0xE6, 0x5E, 0x53, 0x37, 0xF7, 0xAF, 0xA7, 0xED, 0xD5, 0x13, 0x76, 0x29, 0xF7, 0xB9, 0xF3, 0x2C, + 0x4B, 0x4C, 0xA7, 0x3B, 0x35, 0x3E, 0xAA, 0xA0, 0x81, 0x5B, 0x46, 0x03, 0xE1, 0xBC, 0xEC, 0x5F, + 0x24, 0x34, 0xF5, 0xEF, 0xC0, 0xF9, 0xD8, 0x6F, 0xE1, 0xF1, 0x4C, 0x07, 0x1B, 0xE0, 0x3F, 0x83, + 0x63, 0x36, 0x28, 0x86, 0x79, 0x6D, 0xD4, 0xDA, 0x17, 0x7E, 0xAB, 0x5E, 0x10, 0xFC, 0xA9, 0x9E, + 0xBB, 0xB8, 0x3B, 0xC7, 0x77, 0xF1, 0x92, 0x40, 0x8D, 0x9C, 0x0F, 0x7B, 0x2D, 0x48, 0x79, 0x10, + 0xEB, 0x2A, 0x17, 0x37, 0xDA, 0xB7, 0x7A, 0x4B, 0xCA, 0x7D, 0x7C, 0x13, 0xEE, 0x7E, 0xA7, 0x73, + 0x17, 0xD7, 0x2F, 0x4F, 0x33, 0x02, 0x65, 0xFE, 0x27, 0x90, 0x73, 0xC5, 0x9D, 0x88, 0x49, 0xE7, + 0xAB, 0xFC, 0xC4, 0x58, 0xF1, 0x73, 0x01, 0x39, 0x91, 0xF8, 0x51, 0xD5, 0x54, 0xC7, 0x5F, 0x55, + 0x9D, 0xF6, 0x0C, 0xB6, 0xFD, 0x27, 0x10, 0x4C, 0x22, 0x52, 0x19, 0xC5, 0x8C, 0x07, 0x01, 0x4A, + 0xEF, 0xEA, 0x8F, 0x04, 0x26, 0x8E, 0x4B, 0x05, 0xDD, 0x23, 0xF0, 0x88, 0xFF, 0xFE, 0x03, 0x96, + 0xF2, 0xD7, 0x40, 0x92, 0x6A, 0x61, 0xE7, 0x82, 0xD2, 0xC1, 0xEE, 0x9E, 0xB8, 0x67, 0x3B, 0xC1, + 0x46, 0xDE, 0xB8, 0x3D, 0xCD, 0xC4, 0xFE, 0x10, 0x07, 0x39, 0xC6, 0x44, 0x5E, 0x38, 0x27, 0xF7, + 0x67, 0x7A, 0x97, 0x04, 0xD8, 0x3F, 0xCD, 0x17, 0x0F, 0xC9, 0x15, 0x19, 0x9D, 0x83, 0xBC, 0xAE, + 0x82, 0xE4, 0x88, 0x3B, 0x3A, 0x16, 0x7C, 0x64, 0xED, 0xF1, 0xC5, 0x0B, 0xBD, 0x5F, 0xFE, 0xAA, + 0xF6, 0xFE, 0xF6, 0x9B, 0xC0, 0x7C, 0x81, 0x75, 0x0B, 0x17, 0x6C, 0xDC, 0xD7, 0x5B, 0x86, 0x6E, + 0xC3, 0xE1, 0x56, 0x8E, 0x6A, 0x95, 0x07, 0x79, 0x6E, 0xE4, 0x81, 0x84, 0x18, 0x16, 0x1C, 0x16, + 0x37, 0x70, 0xCE, 0x9E, 0x3A, 0x9E, 0xC3, 0xA5, 0x11, 0x4D, 0x4D, 0xF0, 0xC1, 0x87, 0x1C, 0xDB, + 0x09, 0x3A, 0x58, 0x7B, 0xEC, 0x9B, 0x06, 0x9D, 0x66, 0x84, 0xBE, 0x41, 0x53, 0x01, 0x2D, 0x37, + 0x15, 0x6B, 0x8F, 0x2E, 0xA6, 0xD6, 0xC3, 0x00, 0xC1, 0xB7, 0x26, 0x3A, 0xC7, 0x0E, 0x0B, 0x93, + 0xEE, 0x5B, 0xED, 0xC8, 0x88, 0x66, 0x7D, 0x13, 0x8E, 0xEC, 0x1E, 0x58, 0x74, 0x9A, 0xCF, 0x0F, + 0xA9, 0x7E, 0x18, 0xF3, 0xBB, 0x59, 0xD0, 0x31, 0x9A, 0x0F, 0x1E, 0x31, 0x0C, 0x8B, 0xF4, 0xCA, + 0xE1, 0xB2, 0xA4, 0x3A, 0x0C, 0x95, 0x8F, 0x8A, 0xA5, 0x45, 0x30, 0x15, 0xB8, 0xF1, 0x16, 0x6F, + 0x3A, 0x9B, 0x27, 0xDC, 0xF9, 0x9C, 0x04, 0xFD, 0x38, 0xAD, 0xBC, 0xC4, 0x3E, 0x40, 0x61, 0xF6, + 0xDB, 0x73, 0xA5, 0x24, 0x5F, 0xC9, 0xE4, 0x4F, 0xC9, 0x73, 0x94, 0x27, 0x43, 0x76, 0x61, 0xB3, + 0xA7, 0xE9, 0x95, 0x3B, 0x67, 0xB1, 0x91, 0xE2, 0x50, 0x67, 0x9C, 0x88, 0xBA, 0x75, 0x97, 0xC2, + 0x15, 0x6A, 0xBC, 0x35, 0x65, 0xC3, 0xBE, 0xFD, 0x78, 0xFD, 0xEA, 0x83, 0x7A, 0x62, 0xC3, 0xAC, + 0x04, 0xC4, 0x8B, 0x28, 0x64, 0x2B, 0x17, 0xFA, 0x8B, 0xF7, 0xDF, 0x5D, 0x5F, 0xFF, 0xF3, 0xDD, + 0x87, 0x97, 0xF5, 0x43, 0x28, 0x0E, 0xB9, 0xFE, 0xF8, 0xFD, 0x4F, 0x6F, 0x6E, 0xA6, 0x5B, 0xCC, + 0x2A, 0x83, 0x3A, 0xC4, 0x0F, 0x1E, 0x38, 0xB9, 0xC1, 0xB1, 0x2D, 0x50, 0x8E, 0x6D, 0x2F, 0x5E, + 0x00, 0x84, 0x3F, 0x83, 0x26, 0xE9, 0x9A, 0x65, 0x0B, 0xD8, 0x1B, 0x81, 0x7A, 0x76, 0x8A, 0x58, + 0xD8, 0x08, 0xE4, 0xD9, 0x09, 0x95, 0xAA, 0x16, 0xB0, 0x21, 0x24, 0x39, 0x77, 0x41, 0xE4, 0xC7, + 0x77, 0x35, 0xD1, 0xE2, 0xF8, 0xED, 0x89, 0x73, 0xD5, 0x15, 0xD7, 0xD3, 0x57, 0x5D, 0xF1, 0x83, + 0x19, 0xF6, 0xFF, 0xCC, 0xF9, 0x7F, 0xE5, 0xCC, 0x32, 0xCA, 0x3A, 0x47, 0x00, 0x00 +}; diff --git a/src/sd_ESP32.cpp b/src/sd_ESP32.cpp new file mode 100644 index 0000000..07e5984 --- /dev/null +++ b/src/sd_ESP32.cpp @@ -0,0 +1,300 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + * + */ + +#ifdef ARDUINO_ARCH_ESP32 +#include "../../inc/MarlinConfigPre.h" +#if ENABLED(SDSUPPORT) +#include "../../sd/cardreader.h" +#include "../../sd/SdVolume.h" +#include "../../sd/SdFatStructs.h" +#include "../../sd/SdFile.h" +#include "sd_ESP32.h" + + +//Cannot move to static variable due to conflict with ESP32 SD library +SdFile workDir; +dir_t dir_info; +SdVolume sd_volume; + + +ESP_SD::ESP_SD(){ + _size = 0; + _pos = 0; + _readonly=true; + //ugly Workaround to not expose SdFile class which conflict with Native ESP32 SD class + _sdfile = new SdFile; +} +ESP_SD::~ESP_SD(){ +if (((SdFile *)_sdfile)->isOpen())close(); +if (_sdfile) delete (SdFile *) _sdfile; +} + +bool ESP_SD::isopen(){ + return (((SdFile *)_sdfile)->isOpen()); +} + +int8_t ESP_SD::card_status(){ +if (!IS_SD_INSERTED() || !card.isMounted()) return 0; //No sd +if ( card.isPrinting() || card.isFileOpen() ) return -1; // busy +return 1; //ok +} + +bool ESP_SD::open(const char * path, bool readonly ){ + if (path == NULL) return false; + String fullpath=path; + String pathname = fullpath.substring(0,fullpath.lastIndexOf("/")); + String filename = makeshortname(fullpath.substring(fullpath.lastIndexOf("/")+1)); + if (pathname.length() == 0)pathname="/"; + if (!openDir(pathname))return false; + _pos = 0; + _readonly = readonly; + return ((SdFile *)_sdfile)->open(&workDir, filename.c_str(), readonly?O_READ:(O_CREAT | O_APPEND | O_WRITE | O_TRUNC)); +} + +uint32_t ESP_SD::size(){ + if(((SdFile *)_sdfile)->isOpen()) { + _size = ((SdFile *)_sdfile)->fileSize(); + } + return _size ; +} + +uint32_t ESP_SD::available(){ + if(!((SdFile *)_sdfile)->isOpen() || !_readonly) return 0; + _size = ((SdFile *)_sdfile)->fileSize(); + if (_size == 0) return 0; + + return _size - _pos ; +} + +void ESP_SD::close(){ + if(((SdFile *)_sdfile)->isOpen()) { + ((SdFile *)_sdfile)->sync(); + _size = ((SdFile *)_sdfile)->fileSize(); + ((SdFile *)_sdfile)->close(); + } +} + +int16_t ESP_SD::write(const uint8_t * data, uint16_t len){ + return ((SdFile *)_sdfile)->write(data, len); +} + +int16_t ESP_SD::write(const uint8_t byte){ + return ((SdFile *)_sdfile)->write(&byte, 1); +} + + +bool ESP_SD::exists(const char * path){ + bool response = open(path); + if (response) close(); + return response; +} + +bool ESP_SD::remove(const char * path){ + if (path == NULL) return false; + String fullpath=path; + String pathname = fullpath.substring(0,fullpath.lastIndexOf("/")); + String filename = makeshortname(fullpath.substring(fullpath.lastIndexOf("/")+1)); + if (pathname.length() == 0)pathname="/"; + if (!openDir(pathname))return false; + SdFile file; + return file.remove(&workDir, filename.c_str()); +} + +bool ESP_SD::dir_exists(const char * path){ + return openDir(path); +} + +bool ESP_SD::rmdir(const char * path){ + if (path == NULL) return false; + String fullpath=path; + if (fullpath=="/") return false; + if (!openDir(fullpath))return false; + return workDir.rmRfStar(); +} + +bool ESP_SD::mkdir(const char * path){ + if (path == NULL) return false; + String fullpath=path; + String pathname = fullpath.substring(0,fullpath.lastIndexOf("/")); + String filename = makeshortname(fullpath.substring(fullpath.lastIndexOf("/")+1)); + if (pathname.length() == 0)pathname="/"; + if (!openDir(pathname))return false; + SdFile file; + return file.mkdir(&workDir, filename.c_str()); +} + +int16_t ESP_SD::read(){ + if (!_readonly) return 0; + int16_t v = ((SdFile *)_sdfile)->read(); + if (v!=-1)_pos++; + return v; + +} + +uint16_t ESP_SD::read(uint8_t * buf, uint16_t nbyte){ + if (!_readonly) return 0; + int16_t v = ((SdFile *)_sdfile)->read(buf, nbyte); + if (v!=-1)_pos+=v; + return v; +} + +String ESP_SD::get_path_part(String data, int index){ + int found = 0; + int strIndex[] = {0, -1}; + int maxIndex; + String no_res; + String s = data; + s.trim(); + if (s.length() == 0) return no_res; + maxIndex = s.length()-1; + if ((s[0] == '/') && (s.length() > 1)){ + String s2 = &s[1]; + s = s2; + } + for(int i=0; i<=maxIndex && found<=index; i++){ + if(s.charAt(i)=='/' || i==maxIndex){ + found++; + strIndex[0] = strIndex[1]+1; + strIndex[1] = (i == maxIndex) ? i+1 : i; + } + } + + return found>index ? s.substring(strIndex[0], strIndex[1]) : no_res; +} + +String ESP_SD::makeshortname(String longname, uint8_t index){ + String s = longname; + String part_name; + String part_ext; + //Sanity check name is uppercase and no space + s.replace(" ",""); + s.toUpperCase(); + int pos = s.lastIndexOf("."); + //do we have extension ? + if (pos != -1) { + part_name = s.substring(0,pos); + if (part_name.lastIndexOf(".") !=-1) { + part_name.replace(".",""); + //trick for short name but force ~1 at the end + part_name+=" "; + } + part_ext = s.substring(pos+1,pos+4); + } else { + part_name = s; + } + //check is under 8 char + if (part_name.length() > 8) { + //if not cut and use index + //check file exists is not part of this function + part_name = part_name.substring(0,6); + part_name += "~" + String(index); + } + //remove the possible " " for the trick + part_name.replace(" ",""); + //create full short name + if (part_ext.length() > 0) part_name+="." + part_ext; + return part_name; +} + +String ESP_SD::makepath83(String longpath){ + String path; + String tmp; + int index = 0; + tmp = get_path_part(longpath,index); + while (tmp.length() > 0) { + path += "/"; + //TODO need to check short name index (~1) match actually the long name... + path += makeshortname (tmp); + index++; + tmp = get_path_part(longpath,index); + } + return path; +} + + +uint32_t ESP_SD::card_total_space(){ + + return (512.00) * (sd_volume.clusterCount()) * (sd_volume.blocksPerCluster()); +} +uint32_t ESP_SD::card_used_space(){ + return (512.00) * (sd_volume.clusterCount() - sd_volume.freeClusterCount() ) * (sd_volume.blocksPerCluster()); +} + +bool ESP_SD::openDir(String path){ + static SdFile root; + static String name; + int index = 0; + //SdFile *parent; + if(root.isOpen())root.close(); + if (!sd_volume.init(&(card.getSd2Card()))) { + return false; + } + if (!root.openRoot(&sd_volume)){ + return false; + } + root.rewind(); + workDir = root; + //parent = &workDir; + name = get_path_part(path,index); + while ((name.length() > 0) && (name!="/")) { + SdFile newDir; + if (!newDir.open(&root, name.c_str(), O_READ)) { + return false; + } + workDir=newDir; + //parent = &workDir; + index++; + if (index > MAX_DIR_DEPTH) { + return false; + } + name = get_path_part(path,index); + } + return true; +} +//TODO may be add date and use a struct for all info +bool ESP_SD::readDir(char name[13], uint32_t * size, bool * isFile){ + if ((name == NULL) || (size==NULL)) { + return false; + } + * size = 0; + name[0]= 0; + * isFile = false; + + if ((workDir.readDir(&dir_info, NULL)) > 0){ + workDir.dirName(dir_info,name); + * size = dir_info.fileSize; + if (DIR_IS_FILE(&dir_info))* isFile = true; + return true; + } + return false; +} + + +//TODO +/* +bool SD_file_timestamp(const char * path, uint8_t flag, uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t second){ +}**/ + +#endif + +#endif diff --git a/src/sd_ESP32.h b/src/sd_ESP32.h new file mode 100644 index 0000000..777852a --- /dev/null +++ b/src/sd_ESP32.h @@ -0,0 +1,59 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + * + */ + +#ifndef _ESP_SD_H_ +#define _ESP_SD_H_ +class ESP_SD{ + public: + ESP_SD(); + ~ESP_SD(); + int8_t card_status(); + uint32_t card_total_space(); + uint32_t card_used_space(); + bool open(const char * path, bool readonly = true ); + void close(); + int16_t write(const uint8_t * data, uint16_t len); + int16_t write(const uint8_t byte); + uint16_t read(uint8_t * buf, uint16_t nbyte); + int16_t read(); + uint32_t size(); + uint32_t available(); + bool exists(const char * path); + bool dir_exists(const char * path); + bool remove(const char * path); + bool rmdir(const char * path); + bool mkdir(const char * path); + bool isopen(); + String makepath83(String longpath); + String makeshortname(String longname, uint8_t index = 1); + bool openDir(String path); + bool readDir(char name[13], uint32_t * size, bool * isFile); + bool * isFile; + private: + void * _sdfile; + uint32_t _size; + uint32_t _pos; + bool _readonly; + String get_path_part(String data, int index); + +}; +#endif diff --git a/src/serial2socket.cpp b/src/serial2socket.cpp new file mode 100644 index 0000000..f5c603f --- /dev/null +++ b/src/serial2socket.cpp @@ -0,0 +1,170 @@ +/* + serial2socket.cpp - serial 2 socket functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifdef ARDUINO_ARCH_ESP32 + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(WIFISUPPORT) +#include "serial2socket.h" +#include "wificonfig.h" +#include +#include +Serial_2_Socket Serial2Socket; + + +Serial_2_Socket::Serial_2_Socket(){ + _web_socket = NULL; + _TXbufferSize = 0; + _RXbufferSize = 0; + _RXbufferpos = 0; +} +Serial_2_Socket::~Serial_2_Socket(){ + if (_web_socket) detachWS(); + _TXbufferSize = 0; + _RXbufferSize = 0; + _RXbufferpos = 0; +} +void Serial_2_Socket::begin(long speed){ + _TXbufferSize = 0; + _RXbufferSize = 0; + _RXbufferpos = 0; +} + +void Serial_2_Socket::end(){ + _TXbufferSize = 0; + _RXbufferSize = 0; + _RXbufferpos = 0; +} + +long Serial_2_Socket::baudRate(){ + return 0; +} + +bool Serial_2_Socket::attachWS(void * web_socket){ + if (web_socket) { + _web_socket = web_socket; + _TXbufferSize=0; + return true; + } + return false; +} + +bool Serial_2_Socket::detachWS(){ + _web_socket = NULL; + return true; +} + +Serial_2_Socket::operator bool() const +{ + return true; +} +int Serial_2_Socket::available(){ + return _RXbufferSize; +} + + +size_t Serial_2_Socket::write(uint8_t c) +{ + if(!_web_socket) return 0; + write(&c,1); + return 1; +} + +size_t Serial_2_Socket::write(const uint8_t *buffer, size_t size) +{ + if((buffer == NULL) ||(!_web_socket)) { + if(buffer == NULL)log_i("[SOCKET]No buffer"); + if(!_web_socket)log_i("[SOCKET]No socket"); + return 0; + } +#if defined(ENABLE_SERIAL2SOCKET_OUT) + if (_TXbufferSize==0)_lastflush = millis(); + //send full line + if (_TXbufferSize + size > TXBUFFERSIZE) flush(); + //need periodic check to force to flush in case of no end + for (int i = 0; i < size;i++){ + _TXbuffer[_TXbufferSize] = buffer[i]; + _TXbufferSize++; + } + log_i("[SOCKET]buffer size %d",_TXbufferSize); + handle_flush(); +#endif + return size; +} + +int Serial_2_Socket::peek(void){ + if (_RXbufferSize > 0)return _RXbuffer[_RXbufferpos]; + else return -1; +} + +bool Serial_2_Socket::push (const char * data){ +#if defined(ENABLE_SERIAL2SOCKET_IN) + int data_size = strlen(data); + if ((data_size + _RXbufferSize) <= RXBUFFERSIZE){ + int current = _RXbufferpos + _RXbufferSize; + if (current > RXBUFFERSIZE) current = current - RXBUFFERSIZE; + for (int i = 0; i < data_size; i++){ + if (current > (RXBUFFERSIZE-1)) current = 0; + _RXbuffer[current] = data[i]; + current ++; + } + _RXbufferSize+=strlen(data); + return true; + } + return false; +#else + return true; +#endif +} + +int Serial_2_Socket::read(void){ + if (_RXbufferSize > 0) { + int v = _RXbuffer[_RXbufferpos]; + _RXbufferpos++; + if (_RXbufferpos > (RXBUFFERSIZE-1))_RXbufferpos = 0; + _RXbufferSize--; + return v; + } else return -1; +} + +void Serial_2_Socket::handle_flush() { + if (_TXbufferSize > 0) { + if ((_TXbufferSize>=TXBUFFERSIZE) || ((millis()- _lastflush) > FLUSHTIMEOUT)) { + log_i("[SOCKET]need flush, buffer size %d",_TXbufferSize); + flush(); + } + } +} +void Serial_2_Socket::flush(void){ + if (_TXbufferSize > 0){ + log_i("[SOCKET]flush data, buffer size %d",_TXbufferSize); + ((WebSocketsServer *)_web_socket)->broadcastBIN(_TXbuffer,_TXbufferSize); + //refresh timout + _lastflush = millis(); + //reset buffer + _TXbufferSize = 0; + } +} + +#endif // ENABLE_WIFI + +#endif // ARDUINO_ARCH_ESP32 diff --git a/src/serial2socket.h b/src/serial2socket.h new file mode 100644 index 0000000..30a2cb0 --- /dev/null +++ b/src/serial2socket.h @@ -0,0 +1,81 @@ +/* + serial2socket.h - serial 2 socket functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef _SERIAL_2_SOCKET_H_ +#define _SERIAL_2_SOCKET_H_ + +#include "Print.h" +#define TXBUFFERSIZE 1200 +#define RXBUFFERSIZE 128 +#define FLUSHTIMEOUT 500 +class Serial_2_Socket: public Print{ + public: + Serial_2_Socket(); + ~Serial_2_Socket(); + size_t write(uint8_t c); + size_t write(const uint8_t *buffer, size_t size); + + inline size_t write(const char * s) + { + return write((uint8_t*) s, strlen(s)); + } + inline size_t write(unsigned long n) + { + return write((uint8_t) n); + } + inline size_t write(long n) + { + return write((uint8_t) n); + } + inline size_t write(unsigned int n) + { + return write((uint8_t) n); + } + inline size_t write(int n) + { + return write((uint8_t) n); + } + long baudRate(); + void begin(long speed); + void end(); + int available(); + int peek(void); + int read(void); + bool push (const char * data); + void flush(void); + void handle_flush(); + operator bool() const; + bool attachWS(void * web_socket); + bool detachWS(); + private: + uint32_t _lastflush; + void * _web_socket; + uint8_t _TXbuffer[TXBUFFERSIZE]; + uint16_t _TXbufferSize; + uint8_t _RXbuffer[RXBUFFERSIZE]; + uint16_t _RXbufferSize; + uint16_t _RXbufferpos; +}; + + +extern Serial_2_Socket Serial2Socket; + +#endif diff --git a/src/web_server.cpp b/src/web_server.cpp new file mode 100644 index 0000000..148481c --- /dev/null +++ b/src/web_server.cpp @@ -0,0 +1,2565 @@ +/* + web_server.cpp - web server functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifdef ARDUINO_ARCH_ESP32 + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(WIFISUPPORT) + +#include "wificonfig.h" + +#if defined (ENABLE_HTTP) +#include "HAL.h" +#include "../../gcode/queue.h" +#include "wifiservices.h" +#include "serial2socket.h" +#include "web_server.h" +#include +#include "../../inc/Version.h" +#include +#include +#include +#if ENABLED(SDSUPPORT) +#include "sd_ESP32.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef ENABLE_MDNS +#include +#endif +#ifdef ENABLE_SSDP +#include +#endif +#ifdef ENABLE_CAPTIVE_PORTAL +#include +const byte DNS_PORT = 53; +DNSServer dnsServer; +#endif + +//embedded response file if no files on SPIFFS +#include "nofile.h" + +//Upload status +typedef enum { + UPLOAD_STATUS_NONE = 0, + UPLOAD_STATUS_FAILED = 1, + UPLOAD_STATUS_CANCELLED = 2, + UPLOAD_STATUS_SUCCESSFUL = 3, + UPLOAD_STATUS_ONGOING = 4 +} upload_status_type; + +#ifdef ENABLE_AUTHENTICATION +#define DEFAULT_ADMIN_PWD "admin" +#define DEFAULT_USER_PWD "user"; +#define DEFAULT_ADMIN_LOGIN "admin" +#define DEFAULT_USER_LOGIN "user" +#define ADMIN_PWD_ENTRY "ADMIN_PWD" +#define USER_PWD_ENTRY "USER_PWD" +#define AUTH_ENTRY_NB 20 +#define MAX_LOCAL_PASSWORD_LENGTH 16 +#define MIN_LOCAL_PASSWORD_LENGTH 1 +#endif + +//Default 404 +const char PAGE_404 [] = "\n\nRedirecting... \n\n\n
Unknown page : $QUERY$- you will be redirected...\n

\nif not redirected, click here\n

\n\n\n\n
\n\n\n\n"; +const char PAGE_CAPTIVE [] = "\n\nCaptive Portal \n\n\n
Captive Portal page : $QUERY$- you will be redirected...\n

\nif not redirected, click here\n

\n\n\n\n
\n\n\n\n"; + + +Web_Server web_server; +bool Web_Server::_setupdone = false; +uint16_t Web_Server::_port = 0; +String Web_Server::_hostname = ""; +uint16_t Web_Server::_data_port = 0; +long Web_Server::_id_connection = 0; +uint8_t Web_Server::_upload_status = UPLOAD_STATUS_NONE; +WebServer * Web_Server::_webserver = NULL; +WebSocketsServer * Web_Server::_socket_server = NULL; +#ifdef ENABLE_AUTHENTICATION +auth_ip * Web_Server::_head = NULL; +uint8_t Web_Server::_nb_ip = 0; +#define MAX_AUTH_IP 10 +#endif +Web_Server::Web_Server(){ + +} +Web_Server::~Web_Server(){ + end(); +} + +long Web_Server::get_client_ID() { + return _id_connection; +} + +bool Web_Server::begin(){ + + bool no_error = true; + _setupdone = false; + Preferences prefs; + prefs.begin(NAMESPACE, true); + int8_t penabled = prefs.getChar(HTTP_ENABLE_ENTRY, DEFAULT_HTTP_STATE); + //Get http port + _port = prefs.getUShort(HTTP_PORT_ENTRY, DEFAULT_WEBSERVER_PORT); + //Get hostname + String defV = DEFAULT_HOSTNAME; + _hostname = prefs.getString(HOSTNAME_ENTRY, defV); + prefs.end(); + if (penabled == 0) return false; + //create instance + _webserver= new WebServer(_port); +#ifdef ENABLE_AUTHENTICATION + //here the list of headers to be recorded + const char * headerkeys[] = {"Cookie"} ; + size_t headerkeyssize = sizeof (headerkeys) / sizeof (char*); + //ask server to track these headers + _webserver->collectHeaders (headerkeys, headerkeyssize ); +#endif + _socket_server = new WebSocketsServer(_port + 1); + _socket_server->begin(); + _socket_server->onEvent(handle_Websocket_Event); + + + //Websocket output + Serial2Socket.attachWS(_socket_server); + + //Web server handlers + //trick to catch command line on "/" before file being processed + _webserver->on("/",HTTP_ANY, handle_root); + + //Page not found handler + _webserver->onNotFound (handle_not_found); + + //need to be there even no authentication to say to UI no authentication + _webserver->on("/login", HTTP_ANY, handle_login); + + //web commands + _webserver->on ("/command", HTTP_ANY, handle_web_command); + _webserver->on ("/command_silent", HTTP_ANY, handle_web_command_silent); + + //SPIFFS + _webserver->on ("/files", HTTP_ANY, handleFileList, SPIFFSFileupload); + + //web update + _webserver->on ("/updatefw", HTTP_ANY, handleUpdate, WebUpdateUpload); + +#if ENABLED(SDSUPPORT) + //Direct SD management + _webserver->on("/upload", HTTP_ANY, handle_direct_SDFileList,SDFile_direct_upload); +#endif + +#ifdef ENABLE_CAPTIVE_PORTAL + if(WiFi.getMode() == WIFI_AP){ + // if DNSServer is started with "*" for domain name, it will reply with + // provided IP to all DNS request + dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); + _webserver->on ("/generate_204", HTTP_ANY, handle_root); + _webserver->on ("/gconnectivitycheck.gstatic.com", HTTP_ANY, handle_root); + //do not forget the / at the end + _webserver->on ("/fwlink/", HTTP_ANY, handle_root); + } +#endif + +#ifdef ENABLE_SSDP + //SSDP service presentation + if(WiFi.getMode() == WIFI_STA){ + _webserver->on ("/description.xml", HTTP_GET, handle_SSDP); + //Add specific for SSDP + SSDP.setSchemaURL ("description.xml"); + SSDP.setHTTPPort (_port); + SSDP.setName (_hostname); + SSDP.setURL ("/"); + SSDP.setDeviceType ("upnp:rootdevice"); + /*Any customization could be here + SSDP.setModelName (ESP32_MODEL_NAME); + SSDP.setModelURL (ESP32_MODEL_URL); + SSDP.setModelNumber (ESP_MODEL_NUMBER); + SSDP.setManufacturer (ESP_MANUFACTURER_NAME); + SSDP.setManufacturerURL (ESP_MANUFACTURER_URL); + */ + + //Start SSDP + MYSERIAL0.println("SSDP Started"); + SSDP.begin(); + } +#endif + MYSERIAL0.println("HTTP Started"); + //start webserver + _webserver->begin(); +#ifdef ENABLE_MDNS + //add mDNS + if(WiFi.getMode() == WIFI_STA){ + MDNS.addService("http","tcp",_port); + } +#endif + _setupdone = true; + return no_error; +} + +void Web_Server::end(){ + _setupdone = false; +#ifdef ENABLE_SSDP + SSDP.end(); +#endif +#ifdef ENABLE_MDNS + //remove mDNS + mdns_service_remove("_http", "_tcp"); +#endif + if (_socket_server) { + delete _socket_server; + _socket_server = NULL; + } + if (_webserver) { + delete _webserver; + _webserver = NULL; + } +#ifdef ENABLE_AUTHENTICATION + while (_head) { + auth_ip * current = _head; + _head = _head->_next; + delete current; + } + _nb_ip = 0; +#endif +} + +//Root of Webserver///////////////////////////////////////////////////// + +void Web_Server::handle_root() +{ + String path = "/index.html"; + String contentType = getContentType(path); + String pathWithGz = path + ".gz"; + //if have a index.html or gzip version this is default root page + if((SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) && !_webserver->hasArg("forcefallback") && _webserver->arg("forcefallback")!="yes") { + if(SPIFFS.exists(pathWithGz)) { + path = pathWithGz; + } + File file = SPIFFS.open(path, FILE_READ); + _webserver->streamFile(file, contentType); + file.close(); + return; + } + //if no lets launch the default content + _webserver->sendHeader("Content-Encoding", "gzip"); + _webserver->send_P(200,"text/html",PAGE_NOFILES,PAGE_NOFILES_SIZE); +} + +//Handle not registred path on SPIFFS neither SD /////////////////////// +void Web_Server:: handle_not_found() +{ + if (is_authenticated() == LEVEL_GUEST) { + _webserver->sendContent_P("HTTP/1.1 301 OK\r\nLocation: /\r\nCache-Control: no-cache\r\n\r\n"); + //_webserver->client().stop(); + return; + } + bool page_not_found = false; + String path = _webserver->urlDecode(_webserver->uri()); + String contentType = getContentType(path); + String pathWithGz = path + ".gz"; + +#if ENABLED(SDSUPPORT) + if ((path.substring(0,4) == "/SD/")) { + //remove /SD + path = path.substring(3); + ESP_SD SD_card; + if (SD_card.card_status() == 1) { + if (SD_card.exists(pathWithGz.c_str()) || SD_card.exists(path.c_str())) { + if (!SD_card.exists(path.c_str())) path = pathWithGz; + if(SD_card.open(path.c_str())) { + uint8_t buf[1200]; + _webserver->setContentLength(SD_card.size()); + _webserver->sendHeader("Cache-Control","no-cache"); + _webserver->send(200, "application/octet-stream", ""); + + WiFiClient c = _webserver->client(); + int16_t len = SD_card.read( buf, 1200); + while(len > 0) { + c.write(buf, len); + len = SD_card.read( buf, 1200); + wifi_config.wait(0); + handle(); + } + SD_card.close(); + return; + } + } + } + + String content = "cannot find "; + content+=path; + _webserver->send(404,"text/plain",content.c_str()); + return; + } else +#endif + if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { + if(SPIFFS.exists(pathWithGz)) { + path = pathWithGz; + } + File file = SPIFFS.open(path, FILE_READ); + _webserver->streamFile(file, contentType); + file.close(); + return; + } else { + page_not_found = true; + } + + if (page_not_found ) { +#ifdef ENABLE_CAPTIVE_PORTAL + if (WiFi.getMode()!=WIFI_STA ) { + String content=PAGE_CAPTIVE; + String stmp = WiFi.softAPIP().toString(); + //Web address = ip + port + String KEY_IP = "$WEB_ADDRESS$"; + String KEY_QUERY = "$QUERY$"; + if (_port != 80) { + stmp+=":"; + stmp+=String(_port); + } + content.replace(KEY_IP,stmp); + content.replace(KEY_IP,stmp); + content.replace(KEY_QUERY,_webserver->uri()); + _webserver->send(200,"text/html",content); + return; + } +#endif + path = "/404.htm"; + contentType = getContentType(path); + pathWithGz = path + ".gz"; + if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { + if(SPIFFS.exists(pathWithGz)) { + path = pathWithGz; + } + File file = SPIFFS.open(path, FILE_READ); + _webserver->streamFile(file, contentType); + file.close(); + + } else { + //if not template use default page + contentType = PAGE_404; + String stmp; + if (WiFi.getMode()==WIFI_STA ) { + stmp=WiFi.localIP().toString(); + } else { + stmp=WiFi.softAPIP().toString(); + } + //Web address = ip + port + String KEY_IP = "$WEB_ADDRESS$"; + String KEY_QUERY = "$QUERY$"; + if ( _port != 80) { + stmp+=":"; + stmp+=String(_port); + } + contentType.replace(KEY_IP,stmp); + contentType.replace(KEY_QUERY,_webserver->uri()); + _webserver->send(200,"text/html",contentType); + } + } +} +#ifdef ENABLE_SSDP +//http SSDP xml presentation +void Web_Server::handle_SSDP () +{ + StreamString sschema ; + if (sschema.reserve (1024) ) { + String templ = "" + "" + "" + "1" + "0" + "" + "http://%s:%u/" + "" + "upnp:rootdevice" + "%s" + "/" + "%s" + "ESP32" + "Marlin" + "http://espressif.com/en/products/hardware/esp-wroom-32/overview" + "Espressif Systems" + "http://espressif.com" + "uuid:%s" + "" + "\r\n" + "\r\n"; + char uuid[37]; + String sip = WiFi.localIP().toString(); + uint32_t chipId = (uint16_t) (ESP.getEfuseMac() >> 32); + sprintf (uuid, "38323636-4558-4dda-9188-cda0e6%02x%02x%02x", + (uint16_t) ( (chipId >> 16) & 0xff), + (uint16_t) ( (chipId >> 8) & 0xff), + (uint16_t) chipId & 0xff ); + String serialNumber = String (chipId); + sschema.printf (templ.c_str(), + sip.c_str(), + _port, + _hostname.c_str(), + serialNumber.c_str(), + uuid); + _webserver->send (200, "text/xml", (String) sschema); + } else { + _webserver->send (500); + } +} + +#endif + +//Handle web command query and send answer////////////////////////////// +void Web_Server::handle_web_command () +{ + //to save time if already disconnected + //if (_webserver->hasArg ("PAGEID") ) { + // if (_webserver->arg ("PAGEID").length() > 0 ) { + // if (_webserver->arg ("PAGEID").toInt() != _id_connection) { + // _webserver->send (200, "text/plain", "Invalid command"); + // return; + // } + // } + //} + level_authenticate_type auth_level = is_authenticated(); + String cmd = ""; + if (_webserver->hasArg ("plain") || _webserver->hasArg ("commandText") ) { + if (_webserver->hasArg ("plain") ) { + cmd = _webserver->arg ("plain"); + } else { + cmd = _webserver->arg ("commandText"); + } + } else { + _webserver->send (200, "text/plain", "Invalid command"); + return; + } + //if it is internal command [ESPXXX] + cmd.trim(); + int ESPpos = cmd.indexOf ("[ESP"); + if (ESPpos > -1) { + //is there the second part? + int ESPpos2 = cmd.indexOf ("]", ESPpos); + if (ESPpos2 > -1) { + //Split in command and parameters + String cmd_part1 = cmd.substring (ESPpos + 4, ESPpos2); + String cmd_part2 = ""; + //only [ESP800] is allowed login free if authentication is enabled + if ( (auth_level == LEVEL_GUEST) && (cmd_part1.toInt() != 800) ) { + _webserver->send (401, "text/plain", "Authentication failed!\n"); + return; + } + //is there space for parameters? + if (ESPpos2 < cmd.length() ) { + cmd_part2 = cmd.substring (ESPpos2 + 1); + } + //if command is a valid number then execute command + if (cmd_part1.toInt() != 0) { + ESPResponseStream espresponse(_webserver); + //commmand is web only + execute_internal_command (cmd_part1.toInt(), cmd_part2, auth_level, &espresponse); + //flush + espresponse.flush(); + } + //if not is not a valid [ESPXXX] command + } + } else { //execute GCODE + if (auth_level == LEVEL_GUEST) { + _webserver->send (401, "text/plain", "Authentication failed!\n"); + return; + } + //Instead of send several commands one by one by web / send full set and split here + String scmd; + String res = "Ok"; + uint8_t sindex = 0; + scmd = get_Splited_Value(cmd,'\n', sindex); + while ( scmd != "" ){ + scmd+="\n"; + Serial2Socket.push(scmd.c_str()); + //GCodeQueue::enqueue_one_now(scmd.c_str()); + sindex++; + scmd = get_Splited_Value(cmd,'\n', sindex); + } + _webserver->send (200, "text/plain", res.c_str()); + } +} +//Handle web command query and send answer////////////////////////////// +void Web_Server::handle_web_command_silent () +{ + //to save time if already disconnected + //if (_webserver->hasArg ("PAGEID") ) { + // if (_webserver->arg ("PAGEID").length() > 0 ) { + // if (_webserver->arg ("PAGEID").toInt() != _id_connection) { + // _webserver->send (200, "text/plain", "Invalid command"); + // return; + // } + // } + //} + level_authenticate_type auth_level = is_authenticated(); + String cmd = ""; + if (_webserver->hasArg ("plain") || _webserver->hasArg ("commandText") ) { + if (_webserver->hasArg ("plain") ) { + cmd = _webserver->arg ("plain"); + } else { + cmd = _webserver->arg ("commandText"); + } + } else { + _webserver->send (200, "text/plain", "Invalid command"); + return; + } + //if it is internal command [ESPXXX] + cmd.trim(); + int ESPpos = cmd.indexOf ("[ESP"); + if (ESPpos > -1) { + //is there the second part? + int ESPpos2 = cmd.indexOf ("]", ESPpos); + if (ESPpos2 > -1) { + //Split in command and parameters + String cmd_part1 = cmd.substring (ESPpos + 4, ESPpos2); + String cmd_part2 = ""; + //only [ESP800] is allowed login free if authentication is enabled + if ( (auth_level == LEVEL_GUEST) && (cmd_part1.toInt() != 800) ) { + _webserver->send (401, "text/plain", "Authentication failed!\n"); + return; + } + //is there space for parameters? + if (ESPpos2 < cmd.length() ) { + cmd_part2 = cmd.substring (ESPpos2 + 1); + } + //if command is a valid number then execute command + if (cmd_part1.toInt() != 0) { + //commmand is web only + if(execute_internal_command (cmd_part1.toInt(), cmd_part2, auth_level, NULL)) _webserver->send (200, "text/plain", "ok"); + else _webserver->send (200, "text/plain", "error"); + } + //if not is not a valid [ESPXXX] command + } + } else { //execute GCODE + if (auth_level == LEVEL_GUEST) { + _webserver->send (401, "text/plain", "Authentication failed!\n"); + return; + } + //Instead of send several commands one by one by web / send full set and split here + String scmd; + uint8_t sindex = 0; + scmd = get_Splited_Value(cmd,'\n', sindex); + String res = "Ok"; + while (scmd != "" ){ + scmd+="\n"; + Serial2Socket.push(scmd.c_str()); + //GCodeQueue::enqueue_one_now(scmd.c_str()); + sindex++; + scmd = get_Splited_Value(cmd,'\n', sindex); + } + _webserver->send (200, "text/plain", res.c_str()); + } +} + + +bool Web_Server::execute_internal_command (int cmd, String cmd_params, level_authenticate_type auth_level, ESPResponseStream *espresponse) +{ + bool response = true; + level_authenticate_type auth_type = auth_level; + + //manage parameters + String parameter; + switch (cmd) { + //Get SD Card Status + //[ESP200] + case 200: + { + if (!espresponse) return false; + String resp = "No SD card"; +#if ENABLED(SDSUPPORT) + ESP_SD card; + int8_t state = card.card_status(); + if (state == -1)resp="Busy"; + else if (state == 1)resp="SD card detected"; + else resp="No SD card"; +#endif + espresponse->println (resp.c_str()); + } + break; + //Get full ESP32 wifi settings content + //[ESP400] + case 400: + { + String v; + String defV; + Preferences prefs; + if (!espresponse) return false; +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) return false; +#endif + int8_t vi; + espresponse->print("{\"EEPROM\":["); + prefs.begin(NAMESPACE, true); + //1 - Hostname + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (HOSTNAME_ENTRY); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + espresponse->print (_hostname.c_str()); + espresponse->print ("\",\"H\":\"Hostname\" ,\"S\":\""); + espresponse->print (String(MAX_HOSTNAME_LENGTH).c_str()); + espresponse->print ("\", \"M\":\""); + espresponse->print (String(MIN_HOSTNAME_LENGTH).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + + //2 - http protocol mode + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (HTTP_ENABLE_ENTRY); + espresponse->print ("\",\"T\":\"B\",\"V\":\""); + vi = prefs.getChar(HTTP_ENABLE_ENTRY, 1); + espresponse->print (String(vi).c_str()); + espresponse->print ("\",\"H\":\"HTTP protocol\",\"O\":[{\"Enabled\":\"1\"},{\"Disabled\":\"0\"}]}"); + espresponse->print (","); + + //3 - http port + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (HTTP_PORT_ENTRY); + espresponse->print ("\",\"T\":\"I\",\"V\":\""); + espresponse->print (String(_port).c_str()); + espresponse->print ("\",\"H\":\"HTTP Port\",\"S\":\""); + espresponse->print (String(MAX_HTTP_PORT).c_str()); + espresponse->print ("\",\"M\":\""); + espresponse->print (String(MIN_HTTP_PORT).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + + //TODO + //4 - telnet protocol mode + /* espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (TELNET_ENABLE_ENTRY); + espresponse->print ("\",\"T\":\"B\",\"V\":\""); + vi = prefs.getChar(TELNET_ENABLE_ENTRY, 0); + espresponse->print (String(vi).c_str()); + espresponse->print ("\",\"H\":\"Telnet protocol\",\"O\":[{\"Enabled\":\"1\"},{\"Disabled\":\"0\"}]}"); + espresponse->print (",");*/ + + //5 - telnet Port + /* espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (TELNET_PORT_ENTRY); + espresponse->print ("\",\"T\":\"I\",\"V\":\""); + espresponse->print (String(_data_port).c_str()); + espresponse->print ("\",\"H\":\"Telnet Port\",\"S\":\""); + espresponse->print (String(MAX_TELNET_PORT).c_str()); + espresponse->print ("\",\"M\":\""); + espresponse->print (String(MIN_TELNET_PORT).c_str()); + espresponse->print ("\"}"); + espresponse->print (",");*/ + + //6 - wifi mode + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (ESP_WIFI_MODE); + espresponse->print ("\",\"T\":\"B\",\"V\":\""); + vi = prefs.getChar(ESP_WIFI_MODE, ESP_WIFI_OFF); + espresponse->print (String(vi).c_str()); + espresponse->print ("\",\"H\":\"Wifi mode\",\"O\":[{\"STA\":\"1\"},{\"AP\":\"2\"},{\"None\":\"0\"}]}"); + espresponse->print (","); + + //7 - STA SSID + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (STA_SSID_ENTRY); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + defV = DEFAULT_STA_SSID; + espresponse->print (prefs.getString(STA_SSID_ENTRY, defV).c_str()); + espresponse->print ("\",\"S\":\""); + espresponse->print (String(MAX_SSID_LENGTH).c_str()); + espresponse->print ("\",\"H\":\"Station SSID\",\"M\":\""); + espresponse->print (String(MIN_SSID_LENGTH).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + + //8 - STA password + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (STA_PWD_ENTRY); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + espresponse->print (HIDDEN_PASSWORD); + espresponse->print ("\",\"S\":\""); + espresponse->print (String(MAX_PASSWORD_LENGTH).c_str()); + espresponse->print ("\",\"H\":\"Station Password\",\"M\":\""); + espresponse->print (String(MIN_PASSWORD_LENGTH).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + + // 9 - STA IP mode + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (STA_IP_MODE_ENTRY); + espresponse->print ("\",\"T\":\"B\",\"V\":\""); + espresponse->print (String(prefs.getChar(STA_IP_MODE_ENTRY, DHCP_MODE)).c_str()); + espresponse->print ("\",\"H\":\"Station IP Mode\",\"O\":[{\"DHCP\":\"0\"},{\"Static\":\"1\"}]}"); + espresponse->print (","); + + //10-STA static IP + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (STA_IP_ENTRY); + espresponse->print ("\",\"T\":\"A\",\"V\":\""); + espresponse->print (wifi_config.IP_string_from_int(prefs.getInt(STA_IP_ENTRY, 0)).c_str()); + espresponse->print ("\",\"H\":\"Station Static IP\"}"); + espresponse->print (","); + + //11-STA static Gateway + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (STA_GW_ENTRY); + espresponse->print ("\",\"T\":\"A\",\"V\":\""); + espresponse->print (wifi_config.IP_string_from_int(prefs.getInt(STA_GW_ENTRY, 0)).c_str()); + espresponse->print ("\",\"H\":\"Station Static Gateway\"}"); + espresponse->print (","); + + //12-STA static Mask + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (STA_MK_ENTRY); + espresponse->print ("\",\"T\":\"A\",\"V\":\""); + espresponse->print (wifi_config.IP_string_from_int(prefs.getInt(STA_MK_ENTRY, 0)).c_str()); + espresponse->print ("\",\"H\":\"Station Static Mask\"}"); + espresponse->print (","); + + //13 - AP SSID + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (AP_SSID_ENTRY); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + defV = DEFAULT_AP_SSID; + espresponse->print (prefs.getString(AP_SSID_ENTRY, defV).c_str()); + espresponse->print ("\",\"S\":\""); + espresponse->print (String(MAX_SSID_LENGTH).c_str()); + espresponse->print ("\",\"H\":\"AP SSID\",\"M\":\""); + espresponse->print (String(MIN_SSID_LENGTH).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + + //14 - AP password + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (AP_PWD_ENTRY); + espresponse->print ("\",\"T\":\"S\",\"V\":\""); + espresponse->print (HIDDEN_PASSWORD); + espresponse->print ("\",\"S\":\""); + espresponse->print (String(MAX_PASSWORD_LENGTH).c_str()); + espresponse->print ("\",\"H\":\"AP Password\",\"M\":\""); + espresponse->print (String(MIN_PASSWORD_LENGTH).c_str()); + espresponse->print ("\"}"); + espresponse->print (","); + + //15 - AP static IP + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (AP_IP_ENTRY); + espresponse->print ("\",\"T\":\"A\",\"V\":\""); + defV = DEFAULT_AP_IP; + espresponse->print (wifi_config.IP_string_from_int(prefs.getInt(AP_IP_ENTRY, wifi_config.IP_int_from_string(defV))).c_str()); + espresponse->print ("\",\"H\":\"AP Static IP\"}"); + espresponse->print (","); + + //16 - AP Channel + espresponse->print ("{\"F\":\"network\",\"P\":\""); + espresponse->print (AP_CHANNEL_ENTRY); + espresponse->print ("\",\"T\":\"B\",\"V\":\""); + espresponse->print (String(prefs.getChar(AP_CHANNEL_ENTRY, DEFAULT_AP_CHANNEL)).c_str()); + espresponse->print ("\",\"H\":\"AP Channel\",\"O\":["); + for (int i = MIN_CHANNEL; i <= MAX_CHANNEL ; i++) { + espresponse->print ("{\""); + espresponse->print (String(i).c_str()); + espresponse->print ("\":\""); + espresponse->print (String(i).c_str()); + espresponse->print ("\"}"); + if (i < MAX_CHANNEL) { + espresponse->print (","); + } + } + espresponse->print ("]}"); + + espresponse->print ("]}"); + prefs.end(); + } + break; + //Set EEPROM setting + //[ESP401]P= T= V= pwd= + case 401: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) return false; +#endif + //check validity of parameters + String spos = get_param (cmd_params, "P=", false); + String styp = get_param (cmd_params, "T=", false); + String sval = get_param (cmd_params, "V=", true); + spos.trim(); + sval.trim(); + if (spos.length() == 0) { + response = false; + } + if (! (styp == "B" || styp == "S" || styp == "A" || styp == "I" || styp == "F") ) { + response = false; + } + if ((sval.length() == 0) && !((spos==AP_PWD_ENTRY) || (spos==STA_PWD_ENTRY))){ + response = false; + } + + if (response) { + Preferences prefs; + prefs.begin(NAMESPACE, false); + //Byte value + if ((styp == "B") || (styp == "F")){ + int8_t bbuf = sval.toInt(); + if (prefs.putChar(spos.c_str(), bbuf) ==0 ) { + response = false; + } else { + //dynamique refresh is better than restart the board + if (spos == ESP_WIFI_MODE){ + //TODO + } + if (spos == AP_CHANNEL_ENTRY) { + //TODO + } + if (spos == HTTP_ENABLE_ENTRY) { + //TODO + } + if (spos == TELNET_ENABLE_ENTRY) { + //TODO + } + } + } + //Integer value + if (styp == "I") { + int16_t ibuf = sval.toInt(); + if (prefs.putUShort(spos.c_str(), ibuf) == 0) { + response = false; + } else { + if (spos == HTTP_PORT_ENTRY){ + //TODO + } + if (spos == TELNET_PORT_ENTRY){ + //TODO + //Serial.println(ibuf); + } + } + + } + //String value + if (styp == "S") { + if (prefs.putString(spos.c_str(), sval) == 0) { + response = false; + } else { + if (spos == HOSTNAME_ENTRY){ + //TODO + } + if (spos == STA_SSID_ENTRY){ + //TODO + } + if (spos == STA_PWD_ENTRY){ + //TODO + } + if (spos == AP_SSID_ENTRY){ + //TODO + } + if (spos == AP_PWD_ENTRY){ + //TODO + } + } + + } + //IP address + if (styp == "A") { + if (prefs.putInt(spos.c_str(), wifi_config.IP_int_from_string(sval)) == 0) { + response = false; + } else { + if (spos == STA_IP_ENTRY){ + //TODO + } + if (spos == STA_GW_ENTRY){ + //TODO + } + if (spos == STA_MK_ENTRY){ + //TODO + } + if (spos == AP_IP_ENTRY){ + //TODO + } + } + } + prefs.end(); + } + if (!response) { + if (espresponse) espresponse->println ("Error: Incorrect Command"); + } else { + if (espresponse) espresponse->println ("ok"); + } + + } + break; + //Get available AP list (limited to 30) + //output is JSON + //[ESP410] + case 410: { + if (!espresponse)return false; +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) return false; +#endif + espresponse->print("{\"AP_LIST\":["); + int n = WiFi.scanComplete(); + if (n == -2) { + WiFi.scanNetworks (true); + } else if (n) { + for (int i = 0; i < n; ++i) { + if (i > 0) { + espresponse->print (","); + } + espresponse->print ("{\"SSID\":\""); + espresponse->print (WiFi.SSID (i).c_str()); + espresponse->print ("\",\"SIGNAL\":\""); + espresponse->print (String(wifi_config.getSignal (WiFi.RSSI (i) )).c_str()); + espresponse->print ("\",\"IS_PROTECTED\":\""); + + if (WiFi.encryptionType (i) == WIFI_AUTH_OPEN) { + espresponse->print ("0"); + } else { + espresponse->print ("1"); + } + espresponse->print ("\"}"); + } + } + WiFi.scanDelete(); + if (WiFi.scanComplete() == -2) { + WiFi.scanNetworks (true); + } + espresponse->print ("]}"); + } + break; + //Get ESP current status + case 420: + { +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) return false; +#endif + if (!espresponse)return false; + espresponse->print ("Chip ID: "); + espresponse->print (String ( (uint16_t) (ESP.getEfuseMac() >> 32) ).c_str()); + espresponse->print ("\n"); + espresponse->print ("CPU Frequency: "); + espresponse->print (String (ESP.getCpuFreqMHz() ).c_str()); + espresponse->print ("Mhz"); + espresponse->print ("\n"); + espresponse->print ("CPU Temperature: "); + espresponse->print (String (temperatureRead(), 1).c_str()); + espresponse->print ("°C"); + espresponse->print ("\n"); + espresponse->print ("Free memory: "); + espresponse->print (formatBytes (ESP.getFreeHeap()).c_str()); + espresponse->print ("\n"); + espresponse->print ("SDK: "); + espresponse->print (ESP.getSdkVersion()); + espresponse->print ("\n"); + espresponse->print ("Flash Size: "); + espresponse->print (formatBytes (ESP.getFlashChipSize()).c_str()); + espresponse->print ("\n"); + espresponse->print ("Available Size for update: "); + //Not OTA on 2Mb board per spec + if (ESP.getFlashChipSize() > 0x20000) { + espresponse->print (formatBytes (0x140000).c_str()); + } else { + espresponse->print (formatBytes (0x0).c_str()); + } + espresponse->print ("\n"); + espresponse->print ("Available Size for SPIFFS: "); + espresponse->print (formatBytes (SPIFFS.totalBytes()).c_str()); + espresponse->print ("\n"); + espresponse->print ("Baud rate: "); + long br = Serial.baudRate(); + //workaround for ESP32 + if (br == 115201) { + br = 115200; + } + if (br == 230423) { + br = 230400; + } + espresponse->print (String(br).c_str()); + espresponse->print ("\n"); + espresponse->print ("Sleep mode: "); + if (WiFi.getSleep())espresponse->print ("Modem"); + else espresponse->print ("None"); + espresponse->print ("\n"); + espresponse->print ("Web port: "); + espresponse->print (String(_port).c_str()); + espresponse->print ("\n"); + espresponse->print ("Data port: "); + if (_data_port!=0)espresponse->print (String(_data_port).c_str()); + else espresponse->print ("Disabled"); + espresponse->print ("\n"); + espresponse->print ("Hostname: "); + espresponse->print ( _hostname.c_str()); + espresponse->print ("\n"); + espresponse->print ("Active Mode: "); + if (WiFi.getMode() == WIFI_STA) { + espresponse->print ("STA ("); + espresponse->print ( WiFi.macAddress().c_str()); + espresponse->print (")"); + espresponse->print ("\n"); + espresponse->print ("Connected to: "); + if (WiFi.isConnected()){ //in theory no need but ... + espresponse->print (WiFi.SSID().c_str()); + espresponse->print ("\n"); + espresponse->print ("Signal: "); + espresponse->print ( String(wifi_config.getSignal (WiFi.RSSI())).c_str()); + espresponse->print ("%"); + espresponse->print ("\n"); + uint8_t PhyMode; + esp_wifi_get_protocol (ESP_IF_WIFI_STA, &PhyMode); + espresponse->print ("Phy Mode: "); + if (PhyMode == (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N)) espresponse->print ("11n"); + else if (PhyMode == (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G)) espresponse->print ("11g"); + else if (PhyMode == (WIFI_PROTOCOL_11B )) espresponse->print ("11b"); + else espresponse->print ("???"); + espresponse->print ("\n"); + espresponse->print ("Channel: "); + espresponse->print (String (WiFi.channel()).c_str()); + espresponse->print ("\n"); + espresponse->print ("IP Mode: "); + tcpip_adapter_dhcp_status_t dhcp_status; + tcpip_adapter_dhcpc_get_status (TCPIP_ADAPTER_IF_STA, &dhcp_status); + if (dhcp_status == TCPIP_ADAPTER_DHCP_STARTED)espresponse->print ("DHCP"); + else espresponse->print ("Static"); + espresponse->print ("\n"); + espresponse->print ("IP: "); + espresponse->print (WiFi.localIP().toString().c_str()); + espresponse->print ("\n"); + espresponse->print ("Gateway: "); + espresponse->print (WiFi.gatewayIP().toString().c_str()); + espresponse->print ("\n"); + espresponse->print ("Mask: "); + espresponse->print (WiFi.subnetMask().toString().c_str()); + espresponse->print ("\n"); + espresponse->print ("DNS: "); + espresponse->print (WiFi.dnsIP().toString().c_str()); + espresponse->print ("\n"); + } //this is web command so connection => no command + espresponse->print ("Disabled Mode: "); + espresponse->print ("AP ("); + espresponse->print (WiFi.softAPmacAddress().c_str()); + espresponse->print (")"); + espresponse->print ("\n"); + } else if (WiFi.getMode() == WIFI_AP) { + espresponse->print ("AP ("); + espresponse->print (WiFi.softAPmacAddress().c_str()); + espresponse->print (")"); + espresponse->print ("\n"); + wifi_config_t conf; + esp_wifi_get_config (ESP_IF_WIFI_AP, &conf); + espresponse->print ("SSID: "); + espresponse->print ((const char*) conf.ap.ssid); + espresponse->print ("\n"); + espresponse->print ("Visible: "); + espresponse->print ( (conf.ap.ssid_hidden == 0) ? "Yes" : "No"); + espresponse->print ("\n"); + espresponse->print ("Authentication: "); + if (conf.ap.authmode == WIFI_AUTH_OPEN) { + espresponse->print ("None"); + } else if (conf.ap.authmode == WIFI_AUTH_WEP) { + espresponse->print ("WEP"); + } else if (conf.ap.authmode == WIFI_AUTH_WPA_PSK) { + espresponse->print ("WPA"); + } else if (conf.ap.authmode == WIFI_AUTH_WPA2_PSK) { + espresponse->print ("WPA2"); + } else { + espresponse->print ("WPA/WPA2"); + } + espresponse->print ("\n"); + espresponse->print ("Max Connections: "); + espresponse->print (String(conf.ap.max_connection).c_str()); + espresponse->print ("\n"); + espresponse->print ("DHCP Server: "); + tcpip_adapter_dhcp_status_t dhcp_status; + tcpip_adapter_dhcps_get_status (TCPIP_ADAPTER_IF_AP, &dhcp_status); + if (dhcp_status == TCPIP_ADAPTER_DHCP_STARTED)espresponse->print ("Started"); + else espresponse->print ("Stopped"); + espresponse->print ("\n"); + espresponse->print ("IP: "); + espresponse->print (WiFi.softAPIP().toString().c_str()); + espresponse->print ("\n"); + tcpip_adapter_ip_info_t ip_AP; + tcpip_adapter_get_ip_info (TCPIP_ADAPTER_IF_AP, &ip_AP); + espresponse->print ("Gateway: "); + espresponse->print (IPAddress (ip_AP.gw.addr).toString().c_str()); + espresponse->print ("\n"); + espresponse->print ("Mask: "); + espresponse->print (IPAddress (ip_AP.netmask.addr).toString().c_str()); + espresponse->print ("\n"); + espresponse->print ("Connected clients: "); + wifi_sta_list_t station; + tcpip_adapter_sta_list_t tcpip_sta_list; + esp_wifi_ap_get_sta_list (&station); + tcpip_adapter_get_sta_list (&station, &tcpip_sta_list); + espresponse->print (String(station.num).c_str()); + espresponse->print ("\n"); + for (int i = 0; i < station.num; i++) { + espresponse->print (mac2str(tcpip_sta_list.sta[i].mac)); + espresponse->print (" "); + espresponse->print ( IPAddress (tcpip_sta_list.sta[i].ip.addr).toString().c_str()); + espresponse->print ("\n"); + } + espresponse->print ("Disabled Mode: "); + espresponse->print ("STA ("); + espresponse->print (WiFi.macAddress().c_str()); + espresponse->print (")"); + espresponse->print ("\n"); + } else if (WiFi.getMode() == WIFI_AP_STA) //we should not be in this state but just in case .... + { + espresponse->print ("Mixed"); + espresponse->print ("\n"); + espresponse->print ("STA ("); + espresponse->print (WiFi.macAddress().c_str()); + espresponse->print (")"); + espresponse->print ("\n"); + espresponse->print ("AP ("); + espresponse->print (WiFi.softAPmacAddress().c_str()); + espresponse->print (")"); + espresponse->print ("\n"); + + } else { //we should not be there if no wifi .... + espresponse->print ("Wifi Off"); + espresponse->print ("\n"); + } + //TODO to complete + espresponse->print ("FW version: Marlin "); + espresponse->print (SHORT_BUILD_VERSION); + espresponse->print (" (ESP32)"); + } + break; + //Set ESP mode + //cmd is RESTART + //[ESP444] + case 444: + parameter = get_param(cmd_params,"", true); +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + response = false; + } else +#endif + { + if (parameter=="RESTART") { + MYSERIAL0.println("Restart ongoing"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Restart ongoing"); +#endif + wifi_config.restart_ESP(); + } else response = false; + } + if (!response) { + if (espresponse)espresponse->println ("Error: Incorrect Command"); + } else { + if (espresponse)espresponse->println ("ok"); + } + break; +#ifdef ENABLE_AUTHENTICATION + //Change / Reset user password + //[ESP555] + case 555: { + if (auth_type == LEVEL_ADMIN) { + parameter = get_param (cmd_params, "", true); + if (parameter.length() == 0) { + Preferences prefs; + parameter = DEFAULT_USER_PWD; + prefs.begin(NAMESPACE, false); + if (prefs.putString(USER_PWD_ENTRY, parameter) != parameter.length()){ + response = false; + espresponse->println ("error"); + } else espresponse->println ("ok"); + prefs.end(); + + } else { + if (isLocalPasswordValid (parameter.c_str() ) ) { + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(USER_PWD_ENTRY, parameter) != parameter.length()) { + response = false; + espresponse->println ("error"); + } else espresponse->println ("ok"); + prefs.end(); + } else { + espresponse->println ("error"); + response = false; + } + } + } else { + espresponse->println ("error"); + response = false; + } + break; + } +#endif + //[ESP700] + case 700: { //read local file +#ifdef ENABLE_AUTHENTICATION + if (auth_type == LEVEL_GUEST) return false; +#endif + cmd_params.trim() ; + if ( (cmd_params.length() > 0) && (cmd_params[0] != '/') ) { + cmd_params = "/" + cmd_params; + } + File currentfile = SPIFFS.open (cmd_params, FILE_READ); + if (currentfile) {//if file open success + //until no line in file + while (currentfile.available()) { + String currentline = currentfile.readStringUntil('\n'); + currentline.replace("\n",""); + currentline.replace("\r",""); + if (currentline.length() > 0) { + int ESPpos = currentline.indexOf ("[ESP"); + if (ESPpos > -1) { + //is there the second part? + int ESPpos2 = currentline.indexOf ("]", ESPpos); + if (ESPpos2 > -1) { + //Split in command and parameters + String cmd_part1 = currentline.substring (ESPpos + 4, ESPpos2); + String cmd_part2 = ""; + //is there space for parameters? + if (ESPpos2 < currentline.length() ) { + cmd_part2 = currentline.substring (ESPpos2 + 1); + } + //if command is a valid number then execute command + if(cmd_part1.toInt()!=0) { + if (!execute_internal_command(cmd_part1.toInt(),cmd_part2, auth_type, espresponse)) response = false; + } + //if not is not a valid [ESPXXX] command ignore it + } + } else { + if (currentline.length() > 0){ + currentline+="\n"; + Serial2Socket.push(currentline.c_str()); + //GCodeQueue::enqueue_one_now(currentline.c_str()); + } + wifi_config.wait (1); + } + wifi_config.wait (1); + } + } + currentfile.close(); + if (espresponse)espresponse->println ("ok"); + } else { + if (espresponse)espresponse->println ("error"); + response = false; + } + break; + } + //Format SPIFFS + //[ESP710]FORMAT pwd= + case 710: +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) return false; +#endif + parameter = get_param (cmd_params, "", true); +#ifdef ENABLE_AUTHENTICATION + if (auth_type != LEVEL_ADMIN) { + espresponse->println ("error"); + response = false; + break; + } else +#endif + { + if (parameter == "FORMAT") { + if (espresponse)espresponse->print ("Formating"); + SPIFFS.format(); + if (espresponse)espresponse->println ("...Done"); + } else { + if (espresponse)espresponse->println ("error"); + response = false; + } + } + break; + //get fw version / fw target / hostname / authentication + //[ESP800] + case 800: + { + if (!espresponse)return false; + String resp; + resp = "FW version:"; + resp += SHORT_BUILD_VERSION; + resp += " # FW target:marlin-embedded # FW HW:"; + #if ENABLED(SDSUPPORT) + resp += "Direct SD"; + #else + resp += "No SD"; + #endif + resp += " # primary sd:/sd # secondary sd:none # authentication:"; + #ifdef ENABLE_AUTHENTICATION + resp += "yes"; + #else + resp += "no"; + #endif + resp += " # webcommunication: Sync: "; + resp += String(_port + 1); + resp += "# hostname:"; + resp += _hostname; + if (WiFi.getMode() == WIFI_AP)resp += "(AP mode)"; + if (espresponse)espresponse->println (resp.c_str()); + } + break; + default: + if (espresponse)espresponse->println ("Error: Incorrect Command"); + response = false; + break; + } + return response; +} + +//login status check +void Web_Server::handle_login() +{ +#ifdef ENABLE_AUTHENTICATION + String smsg; + String sUser,sPassword; + String auths; + int code = 200; + bool msg_alert_error=false; + //disconnect can be done anytime no need to check credential + if (_webserver->hasArg("DISCONNECT")) { + String cookie = _webserver->header("Cookie"); + int pos = cookie.indexOf("ESPSESSIONID="); + String sessionID; + if (pos!= -1) { + int pos2 = cookie.indexOf(";",pos); + sessionID = cookie.substring(pos+strlen("ESPSESSIONID="),pos2); + } + ClearAuthIP(_webserver->client().remoteIP(), sessionID.c_str()); + _webserver->sendHeader("Set-Cookie","ESPSESSIONID=0"); + _webserver->sendHeader("Cache-Control","no-cache"); + String buffer2send = "{\"status\":\"Ok\",\"authentication_lvl\":\"guest\"}"; + _webserver->send(code, "application/json", buffer2send); + //_webserver->client().stop(); + return; + } + + level_authenticate_type auth_level = is_authenticated(); + if (auth_level == LEVEL_GUEST) auths = "guest"; + else if (auth_level == LEVEL_USER) auths = "user"; + else if (auth_level == LEVEL_ADMIN) auths = "admin"; + else auths = "???"; + + //check is it is a submission or a query + if (_webserver->hasArg("SUBMIT")) { + //is there a correct list of query? + if ( _webserver->hasArg("PASSWORD") && _webserver->hasArg("USER")) { + //USER + sUser = _webserver->arg("USER"); + if ( !((sUser == DEFAULT_ADMIN_LOGIN) || (sUser == DEFAULT_USER_LOGIN))) { + msg_alert_error=true; + smsg = "Error : Incorrect User"; + code = 401; + } + if (msg_alert_error == false) { + //Password + sPassword = _webserver->arg("PASSWORD"); + String sadminPassword; + + Preferences prefs; + prefs.begin(NAMESPACE, true); + String defV = DEFAULT_ADMIN_PWD; + sadminPassword = prefs.getString(ADMIN_PWD_ENTRY, defV); + String suserPassword; + defV = DEFAULT_USER_PWD; + suserPassword = prefs.getString(USER_PWD_ENTRY, defV); + prefs.end(); + + if(!(((sUser == DEFAULT_ADMIN_LOGIN) && (strcmp(sPassword.c_str(),sadminPassword.c_str()) == 0)) || + ((sUser == DEFAULT_USER_LOGIN) && (strcmp(sPassword.c_str(),suserPassword.c_str()) == 0)))) { + msg_alert_error=true; + smsg = "Error: Incorrect password"; + code = 401; + } + } + } else { + msg_alert_error=true; + smsg = "Error: Missing data"; + code = 500; + } + //change password + if (_webserver->hasArg("PASSWORD") && _webserver->hasArg("USER") && _webserver->hasArg("NEWPASSWORD") && (msg_alert_error==false) ) { + String newpassword = _webserver->arg("NEWPASSWORD"); + if (isLocalPasswordValid(newpassword.c_str())) { + String spos; + if(sUser == DEFAULT_ADMIN_LOGIN) spos = ADMIN_PWD_ENTRY; + else spos = USER_PWD_ENTRY; + + Preferences prefs; + prefs.begin(NAMESPACE, false); + if (prefs.putString(spos.c_str(), newpassword) != newpassword.length()) { + msg_alert_error = true; + smsg = "Error: Cannot apply changes"; + code = 500; + } + prefs.end(); + } else { + msg_alert_error=true; + smsg = "Error: Incorrect password"; + code = 500; + } + } + if ((code == 200) || (code == 500)) { + level_authenticate_type current_auth_level; + if(sUser == DEFAULT_ADMIN_LOGIN) { + current_auth_level = LEVEL_ADMIN; + } else if(sUser == DEFAULT_USER_LOGIN){ + current_auth_level = LEVEL_USER; + } else { + current_auth_level = LEVEL_GUEST; + } + //create Session + if ((current_auth_level != auth_level) || (auth_level== LEVEL_GUEST)) { + auth_ip * current_auth = new auth_ip; + current_auth->level = current_auth_level; + current_auth->ip=_webserver->client().remoteIP(); + strcpy(current_auth->sessionID,create_session_ID()); + strcpy(current_auth->userID,sUser.c_str()); + current_auth->last_time=millis(); + if (AddAuthIP(current_auth)) { + String tmps ="ESPSESSIONID="; + tmps+=current_auth->sessionID; + _webserver->sendHeader("Set-Cookie",tmps); + _webserver->sendHeader("Cache-Control","no-cache"); + switch(current_auth->level) { + case LEVEL_ADMIN: + auths = "admin"; + break; + case LEVEL_USER: + auths = "user"; + break; + default: + auths = "guest"; + break; + } + } else { + delete current_auth; + msg_alert_error=true; + code = 500; + smsg = "Error: Too many connections"; + } + } + } + if (code == 200) smsg = "Ok"; + + //build JSON + String buffer2send = "{\"status\":\"" + smsg + "\",\"authentication_lvl\":\""; + buffer2send += auths; + buffer2send += "\"}"; + _webserver->send(code, "application/json", buffer2send); + } else { + if (auth_level != LEVEL_GUEST) { + String cookie = _webserver->header("Cookie"); + int pos = cookie.indexOf("ESPSESSIONID="); + String sessionID; + if (pos!= -1) { + int pos2 = cookie.indexOf(";",pos); + sessionID = cookie.substring(pos+strlen("ESPSESSIONID="),pos2); + auth_ip * current_auth_info = GetAuth(_webserver->client().remoteIP(), sessionID.c_str()); + if (current_auth_info != NULL){ + sUser = current_auth_info->userID; + } + } + } + String buffer2send = "{\"status\":\"200\",\"authentication_lvl\":\""; + buffer2send += auths; + buffer2send += "\",\"user\":\""; + buffer2send += sUser; + buffer2send +="\"}"; + _webserver->send(code, "application/json", buffer2send); + } +#else + _webserver->sendHeader("Cache-Control","no-cache"); + _webserver->send(200, "application/json", "{\"status\":\"Ok\",\"authentication_lvl\":\"admin\"}"); +#endif +} +//SPIFFS +//SPIFFS files list and file commands +void Web_Server::handleFileList () +{ + level_authenticate_type auth_level = is_authenticated(); + if (auth_level == LEVEL_GUEST) { + _upload_status = UPLOAD_STATUS_NONE; + _webserver->send (401, "text/plain", "Authentication failed!\n"); + return; + } + String path ; + String status = "Ok"; + if ( (_upload_status == UPLOAD_STATUS_FAILED) || (_upload_status == UPLOAD_STATUS_CANCELLED) ) { + status = "Upload failed"; + } + //be sure root is correct according authentication + if (auth_level == LEVEL_ADMIN) { + path = "/"; + } else { + path = "/user"; + } + //get current path + if (_webserver->hasArg ("path") ) { + path += _webserver->arg ("path") ; + } + //to have a clean path + path.trim(); + path.replace ("//", "/"); + if (path[path.length() - 1] != '/') { + path += "/"; + } + //check if query need some action + if (_webserver->hasArg ("action") ) { + //delete a file + if (_webserver->arg ("action") == "delete" && _webserver->hasArg ("filename") ) { + String filename; + String shortname = _webserver->arg ("filename"); + shortname.replace ("/", ""); + filename = path + _webserver->arg ("filename"); + filename.replace ("//", "/"); + if (!SPIFFS.exists (filename) ) { + status = shortname + " does not exists!"; + } else { + if (SPIFFS.remove (filename) ) { + status = shortname + " deleted"; + //what happen if no "/." and no other subfiles ? + String ptmp = path; + if ( (path != "/") && (path[path.length() - 1] = '/') ) { + ptmp = path.substring (0, path.length() - 1); + } + File dir = SPIFFS.open (ptmp); + File dircontent = dir.openNextFile(); + if (!dircontent) { + //keep directory alive even empty + File r = SPIFFS.open (path + "/.", FILE_WRITE); + if (r) { + r.close(); + } + } + } else { + status = "Cannot deleted " ; + status += shortname ; + } + } + } + //delete a directory + if (_webserver->arg ("action") == "deletedir" && _webserver->hasArg ("filename") ) { + String filename; + String shortname = _webserver->arg ("filename"); + shortname.replace ("/", ""); + filename = path + _webserver->arg ("filename"); + filename += "/"; + filename.replace ("//", "/"); + if (filename != "/") { + bool delete_error = false; + File dir = SPIFFS.open (path + shortname); + { + File file2deleted = dir.openNextFile(); + while (file2deleted) { + String fullpath = file2deleted.name(); + if (!SPIFFS.remove (fullpath) ) { + delete_error = true; + status = "Cannot deleted " ; + status += fullpath; + } + file2deleted = dir.openNextFile(); + } + } + if (!delete_error) { + status = shortname ; + status += " deleted"; + } + } + } + //create a directory + if (_webserver->arg ("action") == "createdir" && _webserver->hasArg ("filename") ) { + String filename; + filename = path + _webserver->arg ("filename") + "/."; + String shortname = _webserver->arg ("filename"); + shortname.replace ("/", ""); + filename.replace ("//", "/"); + if (SPIFFS.exists (filename) ) { + status = shortname + " already exists!"; + } else { + File r = SPIFFS.open (filename, FILE_WRITE); + if (!r) { + status = "Cannot create "; + status += shortname ; + } else { + r.close(); + status = shortname + " created"; + } + } + } + } + String jsonfile = "{"; + String ptmp = path; + if ( (path != "/") && (path[path.length() - 1] = '/') ) { + ptmp = path.substring (0, path.length() - 1); + } + File dir = SPIFFS.open (ptmp); + jsonfile += "\"files\":["; + bool firstentry = true; + String subdirlist = ""; + File fileparsed = dir.openNextFile(); + while (fileparsed) { + String filename = fileparsed.name(); + String size = ""; + bool addtolist = true; + //remove path from name + filename = filename.substring (path.length(), filename.length() ); + //check if file or subfile + if (filename.indexOf ("/") > -1) { + //Do not rely on "/." to define directory as SPIFFS upload won't create it but directly files + //and no need to overload SPIFFS if not necessary to create "/." if no need + //it will reduce SPIFFS available space so limit it to creation + filename = filename.substring (0, filename.indexOf ("/") ); + String tag = "*"; + tag += filename + "*"; + if (subdirlist.indexOf (tag) > -1 || filename.length() == 0) { //already in list + addtolist = false; //no need to add + } else { + size = "-1"; //it is subfile so display only directory, size will be -1 to describe it is directory + if (subdirlist.length() == 0) { + subdirlist += "*"; + } + subdirlist += filename + "*"; //add to list + } + } else { + //do not add "." file + if (! ( (filename == ".") || (filename == "") ) ) { + size = formatBytes (fileparsed.size() ); + } else { + addtolist = false; + } + } + if (addtolist) { + if (!firstentry) { + jsonfile += ","; + } else { + firstentry = false; + } + jsonfile += "{"; + jsonfile += "\"name\":\""; + jsonfile += filename; + jsonfile += "\",\"size\":\""; + jsonfile += size; + jsonfile += "\""; + jsonfile += "}"; + } + fileparsed = dir.openNextFile(); + } + jsonfile += "],"; + jsonfile += "\"path\":\"" + path + "\","; + jsonfile += "\"status\":\"" + status + "\","; + size_t totalBytes; + size_t usedBytes; + totalBytes = SPIFFS.totalBytes(); + usedBytes = SPIFFS.usedBytes(); + jsonfile += "\"total\":\"" + formatBytes (totalBytes) + "\","; + jsonfile += "\"used\":\"" + formatBytes (usedBytes) + "\","; + jsonfile.concat (F ("\"occupation\":\"") ); + jsonfile += String (100 * usedBytes / totalBytes); + jsonfile += "\""; + jsonfile += "}"; + path = ""; + _webserver->sendHeader("Cache-Control", "no-cache"); + _webserver->send(200, "application/json", jsonfile); + _upload_status = UPLOAD_STATUS_NONE; +} + +//SPIFFS files uploader handle +void Web_Server::SPIFFSFileupload () +{ + //get authentication status + level_authenticate_type auth_level= is_authenticated(); + //Guest cannot upload - only admin + if (auth_level == LEVEL_GUEST) { + _upload_status = UPLOAD_STATUS_CANCELLED; + MYSERIAL0.println("Upload rejected"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Upload rejected"); +#endif + _webserver->client().stop(); + return; + } + static String filename; + static File fsUploadFile = (File)0; + + HTTPUpload& upload = _webserver->upload(); + //Upload start + //************** + if(upload.status == UPLOAD_FILE_START) { + String upload_filename = upload.filename; + if (upload_filename[0] != '/') filename = "/" + upload_filename; + else filename = upload.filename; + //according User or Admin the root is different as user is isolate to /user when admin has full access + if(auth_level != LEVEL_ADMIN) { + upload_filename = filename; + filename = "/user" + upload_filename; + } + + if (SPIFFS.exists (filename) ) { + SPIFFS.remove (filename); + } + if (fsUploadFile ) { + fsUploadFile.close(); + } + //create file + fsUploadFile = SPIFFS.open(filename, FILE_WRITE); + //check If creation succeed + if (fsUploadFile) { + //if yes upload is started + _upload_status= UPLOAD_STATUS_ONGOING; + } else { + //if no set cancel flag + _upload_status=UPLOAD_STATUS_CANCELLED; + MYSERIAL0.println("Upload error"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Upload error"); +#endif + _webserver->client().stop(); + } + //Upload write + //************** + } else if(upload.status == UPLOAD_FILE_WRITE) { + //check if file is available and no error + if(fsUploadFile && _upload_status == UPLOAD_STATUS_ONGOING) { + //no error so write post date + fsUploadFile.write(upload.buf, upload.currentSize); + } else { + //we have a problem set flag UPLOAD_STATUS_CANCELLED + _upload_status=UPLOAD_STATUS_CANCELLED; + fsUploadFile.close(); + if (SPIFFS.exists (filename) ) { + SPIFFS.remove (filename); + } + _webserver->client().stop(); + MYSERIAL0.println("Upload error"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Upload error"); +#endif + } + //Upload end + //************** + } else if(upload.status == UPLOAD_FILE_END) { + //check if file is still open + if(fsUploadFile) { + //close it + fsUploadFile.close(); + //check size + String sizeargname = upload.filename + "S"; + fsUploadFile = SPIFFS.open (filename, FILE_READ); + uint32_t filesize = fsUploadFile.size(); + fsUploadFile.close(); + if (_webserver->hasArg (sizeargname.c_str()) ) { + if (_webserver->arg (sizeargname.c_str()) != String(filesize)) { + _upload_status = UPLOAD_STATUS_FAILED; + SPIFFS.remove (filename); + } + } + if (_upload_status == UPLOAD_STATUS_ONGOING) { + _upload_status = UPLOAD_STATUS_SUCCESSFUL; + } else { + MYSERIAL0.println("Upload error"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Upload error"); +#endif + } + } else { + //we have a problem set flag UPLOAD_STATUS_CANCELLED + _upload_status=UPLOAD_STATUS_CANCELLED; + _webserver->client().stop(); + if (SPIFFS.exists (filename) ) { + SPIFFS.remove (filename); + } + MYSERIAL0.println("Upload error"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Upload error"); +#endif + } + //Upload cancelled + //************** + } else { + if (_upload_status == UPLOAD_STATUS_ONGOING) { + _upload_status = UPLOAD_STATUS_CANCELLED; + } + if(fsUploadFile)fsUploadFile.close(); + if (SPIFFS.exists (filename) ) { + SPIFFS.remove (filename); + } + MYSERIAL0.println("Upload error"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Upload error"); +#endif + } + wifi_config.wait(0); +} + +//Web Update handler +void Web_Server::handleUpdate () +{ + level_authenticate_type auth_level = is_authenticated(); + if (auth_level != LEVEL_ADMIN) { + _upload_status = UPLOAD_STATUS_NONE; + _webserver->send (403, "text/plain", "Not allowed, log in first!\n"); + return; + } + String jsonfile = "{\"status\":\"" ; + jsonfile += String(_upload_status); + jsonfile += "\"}"; + //send status + _webserver->sendHeader("Cache-Control", "no-cache"); + _webserver->send(200, "application/json", jsonfile); + //if success restart + if (_upload_status == UPLOAD_STATUS_SUCCESSFUL) { + wifi_config.wait(1000); + wifi_config.restart_ESP(); + } else { + _upload_status = UPLOAD_STATUS_NONE; + } +} + +//File upload for Web update +void Web_Server::WebUpdateUpload () +{ + static size_t last_upload_update; + static uint32_t maxSketchSpace ; + //only admin can update FW + if (is_authenticated() != LEVEL_ADMIN) { + _upload_status = UPLOAD_STATUS_CANCELLED; + _webserver->client().stop(); + MYSERIAL0.println("Upload rejected"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Upload rejected"); +#endif + return; + } + + //get current file ID + HTTPUpload& upload = _webserver->upload(); + //Upload start + //************** + if(upload.status == UPLOAD_FILE_START) { + MYSERIAL0.println("Update Firmware"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Update Firmware"); +#endif + _upload_status= UPLOAD_STATUS_ONGOING; + + //Not sure can do OTA on 2Mb board + maxSketchSpace = (ESP.getFlashChipSize() > 0x20000) ? 0x140000 : 0x140000 / 2; + last_upload_update = 0; + if(!Update.begin(maxSketchSpace)) { //start with max available size + _upload_status=UPLOAD_STATUS_CANCELLED; + MYSERIAL0.println("Update cancelled"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Update cancelled"); +#endif + _webserver->client().stop(); + return; + } else { + MYSERIAL0.println("Update 0%"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Update 0%"); +#endif + } + //Upload write + //************** + } else if(upload.status == UPLOAD_FILE_WRITE) { + //check if no error + if (_upload_status == UPLOAD_STATUS_ONGOING) { + //we do not know the total file size yet but we know the available space so let's use it + if ( ((100 * upload.totalSize) / maxSketchSpace) !=last_upload_update) { + last_upload_update = (100 * upload.totalSize) / maxSketchSpace; + String s = "Update "; + s+= String(last_upload_update); + s+="%"; + MYSERIAL0.println(s.c_str()); +#if NUM_SERIAL > 1 + MYSERIAL1.println(s.c_str()); +#endif + } + if(Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + _upload_status=UPLOAD_STATUS_CANCELLED; + } + } + //Upload end + //************** + } else if(upload.status == UPLOAD_FILE_END) { + if(Update.end(true)) { //true to set the size to the current progress + //Now Reboot + MYSERIAL0.println("Update 100%"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Update 100%"); +#endif + _upload_status=UPLOAD_STATUS_SUCCESSFUL; + } + } else if(upload.status == UPLOAD_FILE_ABORTED) { + MYSERIAL0.println("Update failed"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Update failed"); +#endif + Update.end(); + _upload_status=UPLOAD_STATUS_CANCELLED; + } + wifi_config.wait(0); +} + + +#if ENABLED(SDSUPPORT) + +//direct SD files list////////////////////////////////////////////////// +void Web_Server::handle_direct_SDFileList() +{ + //this is only for admin an user + if (is_authenticated() == LEVEL_GUEST) { + _upload_status=UPLOAD_STATUS_NONE; + _webserver->send(401, "application/json", "{\"status\":\"Authentication failed!\"}"); + return; + } + + + String path="/"; + String sstatus="Ok"; + if ((_upload_status == UPLOAD_STATUS_FAILED) || (_upload_status == UPLOAD_STATUS_CANCELLED)) { + sstatus = "Upload failed"; + _upload_status = UPLOAD_STATUS_NONE; + } + bool list_files = true; + ESP_SD card; + int8_t state = card.card_status(); + if (state != 1){ + _webserver->sendHeader("Cache-Control","no-cache"); + _webserver->send(200, "application/json", "{\"status\":\"No SD Card\"}"); + return; + } + + //get current path + if(_webserver->hasArg("path")) { + path += _webserver->arg("path") ; + } + //to have a clean path + path.trim(); + path.replace("//","/"); + + //check if query need some action + if(_webserver->hasArg("action")) { + //delete a file + if(_webserver->arg("action") == "delete" && _webserver->hasArg("filename")) { + String filename; + String shortname = _webserver->arg("filename"); + filename = path + shortname; + shortname.replace("/",""); + filename.replace("//","/"); + + if(!card.exists(filename.c_str())) { + sstatus = shortname + " does not exist!"; + } else { + if (card.remove(filename.c_str())) { + sstatus = shortname + " deleted"; + } else { + sstatus = "Cannot deleted "; + sstatus+=shortname ; + } + } + } + //delete a directory + if( _webserver->arg("action") == "deletedir" && _webserver->hasArg("filename")) { + String filename; + String shortname = _webserver->arg("filename"); + shortname.replace("/",""); + filename = path + "/" + shortname; + filename.replace("//","/"); + if (filename != "/") { + if(!card.dir_exists(filename.c_str())) { + sstatus = shortname + " does not exist!"; + } else { + if (!card.rmdir(filename.c_str())) { + sstatus ="Error deleting: "; + sstatus += shortname ; + } else { + sstatus = shortname ; + sstatus+=" deleted"; + } + } + } else { + sstatus ="Cannot delete root"; + } + } + //create a directory + if( _webserver->arg("action")=="createdir" && _webserver->hasArg("filename")) { + String filename; + String shortname = _webserver->arg("filename"); + filename = path + shortname; + shortname.replace("/",""); + filename.replace("//","/"); + if(card.exists(filename.c_str())) { + sstatus = shortname + " already exists!"; + } else { + if (!card.mkdir(filename.c_str())) { + sstatus = "Cannot create "; + sstatus += shortname ; + } else { + sstatus = shortname + " created"; + } + } + } + } + + //check if no need build file list + if( _webserver->hasArg("dontlist")) { + if( _webserver->arg("dontlist") == "yes") { + list_files = false; + } + } + String jsonfile = "{" ; + jsonfile+="\"files\":["; + if (!card.openDir(path)){ + String s = "{\"status\":\" "; + s += path; + s+= " does not exist on SD Card\"}"; + _webserver->sendHeader("Cache-Control","no-cache"); + _webserver->send(200, "application/json", s.c_str()); + return; + } + if (list_files) { + char name[13]; + uint32_t size; + bool isFile; + uint i = 0; + while (card.readDir(name,&size ,&isFile)) { + if (i>0) { + jsonfile+=","; + } + jsonfile+="{\"name\":\""; + jsonfile+=name; + jsonfile+="\",\"shortname\":\""; + jsonfile+=name; + jsonfile+="\",\"size\":\""; + if (isFile)jsonfile+=formatBytes(size); + else jsonfile+="-1"; + jsonfile+="\",\"datetime\":\""; + //TODO datatime + jsonfile+="\"}"; + i++; + wifi_config.wait(1); + } + jsonfile+="],\"path\":\""; + jsonfile+=path + "\","; + } + static uint32_t volTotal = card.card_total_space(); + static uint32_t volUsed = card.card_used_space();; + //TODO + //Get right values + uint32_t occupedspace = (volUsed/volTotal)*100; + jsonfile+="\"total\":\""; + if ( (occupedspace <= 1) && (volTotal!=volUsed)) { + occupedspace=1; + } + jsonfile+= formatBytes(volTotal); ; + + jsonfile+="\",\"used\":\""; + jsonfile+= formatBytes(volUsed); ; + jsonfile+="\",\"occupation\":\""; + jsonfile+=String(occupedspace); + jsonfile+= "\","; + + jsonfile+= "\"mode\":\"direct\","; + jsonfile+= "\"status\":\""; + jsonfile+=sstatus + "\""; + jsonfile+= "}"; + + _webserver->sendHeader("Cache-Control","no-cache"); + _webserver->send (200, "application/json", jsonfile.c_str()); + _upload_status=UPLOAD_STATUS_NONE; +} + +//SD File upload with direct access to SD/////////////////////////////// +void Web_Server::SDFile_direct_upload() +{ + static ESP_SD sdfile; + static String upload_filename; + //this is only for admin and user + if (is_authenticated() == LEVEL_GUEST) { + _upload_status=UPLOAD_STATUS_NONE; + _webserver->send(401, "application/json", "{\"status\":\"Authentication failed!\"}"); + return; + } + //retrieve current file id + HTTPUpload& upload = _webserver->upload(); + + //Upload start + //************** + if(upload.status == UPLOAD_FILE_START) { + upload_filename = upload.filename; + if (upload_filename[0] != '/') { + upload_filename = "/" + upload.filename; + } + upload_filename= sdfile.makepath83(upload_filename); + if ( sdfile.card_status() != 1) { + _upload_status=UPLOAD_STATUS_CANCELLED; + MYSERIAL0.println("Upload cancelled"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Upload cancelled"); +#endif + _webserver->client().stop(); + return; + } + if (sdfile.exists (upload_filename.c_str()) ) { + sdfile.remove (upload_filename.c_str()); + } + + if (sdfile.isopen())sdfile.close(); + if (!sdfile.open (upload_filename.c_str(),false)) { + MYSERIAL0.println("Upload cancelled"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Upload cancelled"); +#endif + _webserver->client().stop(); + _upload_status = UPLOAD_STATUS_FAILED; + return ; + } else { + _upload_status = UPLOAD_STATUS_ONGOING; + } + //Upload write + //************** + } else if(upload.status == UPLOAD_FILE_WRITE) { + //we need to check SD is inside + if ( sdfile.card_status() != 1) { + sdfile.close(); + MYSERIAL0.println("Upload failed"); + #if NUM_SERIAL > 1 + MYSERIAL1.println("Upload failed"); + #endif + if (sdfile.exists (upload_filename.c_str()) ) { + sdfile.remove (upload_filename.c_str()); + } + _webserver->client().stop(); + return; + } + if (sdfile.isopen()) { + if ( (_upload_status = UPLOAD_STATUS_ONGOING) && (upload.currentSize > 0)) { + sdfile.write (upload.buf, upload.currentSize); + } + } + //Upload end + //************** + } else if(upload.status == UPLOAD_FILE_END) { + sdfile.close(); + uint32_t filesize = sdfile.size(); + String sizeargname = upload.filename + "S"; + if (_webserver->hasArg (sizeargname.c_str()) ) { + if (_webserver->arg (sizeargname.c_str()) != String(filesize)) { + MYSERIAL0.println("Upload failed"); + #if NUM_SERIAL > 1 + MYSERIAL1.println("Upload failed"); + #endif + _upload_status = UPLOAD_STATUS_FAILED; + } + } + if (_upload_status == UPLOAD_STATUS_ONGOING) { + _upload_status = UPLOAD_STATUS_SUCCESSFUL; + } + } else {//Upload cancelled + _upload_status=UPLOAD_STATUS_FAILED; + + MYSERIAL0.println("Upload failed"); +#if NUM_SERIAL > 1 + MYSERIAL1.println("Upload failed"); +#endif + _webserver->client().stop(); + sdfile.close(); + if (sdfile.exists (upload_filename.c_str()) ) { + sdfile.remove (upload_filename.c_str()); + } + } + wifi_config.wait(0); +} +#endif + +void Web_Server::handle(){ +static uint32_t timeout = millis(); +#ifdef ENABLE_CAPTIVE_PORTAL + if(WiFi.getMode() == WIFI_AP){ + dnsServer.processNextRequest(); + } +#endif + if (_webserver)_webserver->handleClient(); + if (_socket_server && _setupdone) { + Serial2Socket.handle_flush(); + _socket_server->loop(); + } + if ((millis() - timeout) > 10000) { + if (_socket_server){ + String s = "PING:"; + s+=String(_id_connection); + _socket_server->broadcastTXT(s); + timeout=millis(); + } + } +} + + +void Web_Server::handle_Websocket_Event(uint8_t num, uint8_t type, uint8_t * payload, size_t length) { + + switch(type) { + case WStype_DISCONNECTED: + //USE_SERIAL.printf("[%u] Disconnected!\n", num); + break; + case WStype_CONNECTED: + { + IPAddress ip = _socket_server->remoteIP(num); + //USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + String s = "CURRENT_ID:" + String(num); + // send message to client + _id_connection = num; + _socket_server->sendTXT(_id_connection, s); + s = "ACTIVE_ID:" + String(_id_connection); + _socket_server->broadcastTXT(s); + } + break; + case WStype_TEXT: + //USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); + + // send message to client + // webSocket.sendTXT(num, "message here"); + + // send data to all connected clients + // webSocket.broadcastTXT("message here"); + break; + case WStype_BIN: + //USE_SERIAL.printf("[%u] get binary length: %u\n", num, length); + //hexdump(payload, length); + + // send message to client + // webSocket.sendBIN(num, payload, length); + break; + default: + break; + } + +} + +//just simple helper to convert mac address to string +char * Web_Server::mac2str (uint8_t mac [8]) +{ + static char macstr [18]; + if (0 > sprintf (macstr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]) ) { + strcpy (macstr, "00:00:00:00:00:00"); + } + return macstr; +} + +String Web_Server::get_Splited_Value(String data, char separator, int index) +{ + int found = 0; + int strIndex[] = {0, -1}; + int maxIndex = data.length()-1; + + for(int i=0; i<=maxIndex && found<=index; i++){ + if(data.charAt(i)==separator || i==maxIndex){ + found++; + strIndex[0] = strIndex[1]+1; + strIndex[1] = (i == maxIndex) ? i+1 : i; + } + } + + return found>index ? data.substring(strIndex[0], strIndex[1]) : ""; +} + + +//helper to format size to readable string +String Web_Server::formatBytes (uint32_t bytes) +{ + if (bytes < 1024) { + return String (bytes) + " B"; + } else if (bytes < (1024 * 1024) ) { + return String (bytes / 1024.0) + " KB"; + } else if (bytes < (1024 * 1024 * 1024) ) { + return String (bytes / 1024.0 / 1024.0) + " MB"; + } else { + return String (bytes / 1024.0 / 1024.0 / 1024.0) + " GB"; + } +} + +//helper to extract content type from file extension +//Check what is the content tye according extension file +String Web_Server::getContentType (String filename) +{ + String file_name = filename; + file_name.toLowerCase(); + if (filename.endsWith (".htm") ) { + return "text/html"; + } else if (file_name.endsWith (".html") ) { + return "text/html"; + } else if (file_name.endsWith (".css") ) { + return "text/css"; + } else if (file_name.endsWith (".js") ) { + return "application/javascript"; + } else if (file_name.endsWith (".png") ) { + return "image/png"; + } else if (file_name.endsWith (".gif") ) { + return "image/gif"; + } else if (file_name.endsWith (".jpeg") ) { + return "image/jpeg"; + } else if (file_name.endsWith (".jpg") ) { + return "image/jpeg"; + } else if (file_name.endsWith (".ico") ) { + return "image/x-icon"; + } else if (file_name.endsWith (".xml") ) { + return "text/xml"; + } else if (file_name.endsWith (".pdf") ) { + return "application/x-pdf"; + } else if (file_name.endsWith (".zip") ) { + return "application/x-zip"; + } else if (file_name.endsWith (".gz") ) { + return "application/x-gzip"; + } else if (file_name.endsWith (".txt") ) { + return "text/plain"; + } + return "application/octet-stream"; +} + +//check authentification +level_authenticate_type Web_Server::is_authenticated() +{ +#ifdef ENABLE_AUTHENTICATION + if (_webserver->hasHeader ("Cookie") ) { + String cookie = _webserver->header ("Cookie"); + int pos = cookie.indexOf ("ESPSESSIONID="); + if (pos != -1) { + int pos2 = cookie.indexOf (";", pos); + String sessionID = cookie.substring (pos + strlen ("ESPSESSIONID="), pos2); + IPAddress ip = _webserver->client().remoteIP(); + //check if cookie can be reset and clean table in same time + return ResetAuthIP (ip, sessionID.c_str() ); + } + } + return LEVEL_GUEST; +#else + return LEVEL_ADMIN; +#endif +} + +#ifdef ENABLE_AUTHENTICATION + +bool Web_Server::isLocalPasswordValid (const char * password) +{ + char c; + //limited size + if ( (strlen (password) > MAX_LOCAL_PASSWORD_LENGTH) || (strlen (password) < MIN_LOCAL_PASSWORD_LENGTH) ) { + return false; + } + //no space allowed + for (int i = 0; i < strlen (password); i++) { + c = password[i]; + if (c == ' ') { + return false; + } + } + return true; +} + +//add the information in the linked list if possible +bool Web_Server::AddAuthIP (auth_ip * item) +{ + if (_nb_ip > MAX_AUTH_IP) { + return false; + } + item->_next = _head; + _head = item; + _nb_ip++; + return true; +} + +//Session ID based on IP and time using 16 char +char * Web_Server::create_session_ID() +{ + static char sessionID[17]; +//reset SESSIONID + for (int i = 0; i < 17; i++) { + sessionID[i] = '\0'; + } +//get time + uint32_t now = millis(); +//get remote IP + IPAddress remoteIP = _webserver->client().remoteIP(); +//generate SESSIONID + if (0 > sprintf (sessionID, "%02X%02X%02X%02X%02X%02X%02X%02X", remoteIP[0], remoteIP[1], remoteIP[2], remoteIP[3], (uint8_t) ( (now >> 0) & 0xff), (uint8_t) ( (now >> 8) & 0xff), (uint8_t) ( (now >> 16) & 0xff), (uint8_t) ( (now >> 24) & 0xff) ) ) { + strcpy (sessionID, "NONE"); + } + return sessionID; +} + + +bool Web_Server::ClearAuthIP (IPAddress ip, const char * sessionID) +{ + auth_ip * current = _head; + auth_ip * previous = NULL; + bool done = false; + while (current) { + if ( (ip == current->ip) && (strcmp (sessionID, current->sessionID) == 0) ) { + //remove + done = true; + if (current == _head) { + _head = current->_next; + _nb_ip--; + delete current; + current = _head; + } else { + previous->_next = current->_next; + _nb_ip--; + delete current; + current = previous->_next; + } + } else { + previous = current; + current = current->_next; + } + } + return done; +} + +//Get info +auth_ip * Web_Server::GetAuth (IPAddress ip, const char * sessionID) +{ + auth_ip * current = _head; + //auth_ip * previous = NULL; + //get time + //uint32_t now = millis(); + while (current) { + if (ip == current->ip) { + if (strcmp (sessionID, current->sessionID) == 0) { + //found + return current; + } + } + //previous = current; + current = current->_next; + } + return NULL; +} + +//Review all IP to reset timers +level_authenticate_type Web_Server::ResetAuthIP (IPAddress ip, const char * sessionID) +{ + auth_ip * current = _head; + auth_ip * previous = NULL; + //get time + //uint32_t now = millis(); + while (current) { + if ( (millis() - current->last_time) > 360000) { + //remove + if (current == _head) { + _head = current->_next; + _nb_ip--; + delete current; + current = _head; + } else { + previous->_next = current->_next; + _nb_ip--; + delete current; + current = previous->_next; + } + } else { + if (ip == current->ip) { + if (strcmp (sessionID, current->sessionID) == 0) { + //reset time + current->last_time = millis(); + return (level_authenticate_type) current->level; + } + } + previous = current; + current = current->_next; + } + } + return LEVEL_GUEST; +} +#endif + +String Web_Server::get_param (String & cmd_params, const char * id, bool withspace) +{ + static String parameter; + String sid = id; + int start; + int end = -1; + parameter = ""; + //if no id it means it is first part of cmd + if (strlen (id) == 0) { + start = 0; + } + //else find id position + else { + start = cmd_params.indexOf (id); + } + //if no id found and not first part leave + if (start == -1 ) { + return parameter; + } + //password and SSID can have space so handle it + //if no space expected use space as delimiter + if (!withspace) { + end = cmd_params.indexOf (" ", start); + } + //if no end found - take all + if (end == -1) { + end = cmd_params.length(); + } + //extract parameter + parameter = cmd_params.substring (start + strlen (id), end); + //be sure no extra space + parameter.trim(); + return parameter; +} + +ESPResponseStream::ESPResponseStream(WebServer * webserver){ + _header_sent=false; + _webserver = webserver; +} + +void ESPResponseStream::println(const char *data){ + print(data); + print("\n"); +} + +void ESPResponseStream::print(const char *data){ + if (!_header_sent) { + _webserver->setContentLength(CONTENT_LENGTH_UNKNOWN); + _webserver->sendHeader("Content-Type","text/html"); + _webserver->sendHeader("Cache-Control","no-cache"); + _webserver->send(200); + _header_sent = true; + } + _buffer+=data; + if (_buffer.length() > 1200) { + //send data + _webserver->sendContent(_buffer); + //reset buffer + _buffer = ""; + } + +} + +void ESPResponseStream::flush(){ + if(_header_sent) { + //send data + if(_buffer.length() > 0)_webserver->sendContent(_buffer); + //close connection + _webserver->sendContent(""); + } + _header_sent = false; + _buffer = ""; + +} + +#endif // Enable HTTP + +#endif // ENABLE_WIFI + +#endif // ARDUINO_ARCH_ESP32 diff --git a/src/web_server.h b/src/web_server.h new file mode 100644 index 0000000..d96e32e --- /dev/null +++ b/src/web_server.h @@ -0,0 +1,120 @@ +/* + web_server.h - wifi services functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef _WEB_SERVER_H +#define _WEB_SERVER_H + + +#include "wificonfig.h" +class WebSocketsServer; +class WebServer; + +//Authentication level +typedef enum { + LEVEL_GUEST = 0, + LEVEL_USER = 1, + LEVEL_ADMIN = 2 +} level_authenticate_type; + +#ifdef ENABLE_AUTHENTICATION +struct auth_ip { + IPAddress ip; + level_authenticate_type level; + char userID[17]; + char sessionID[17]; + uint32_t last_time; + auth_ip * _next; +}; + +#endif + + + + +class ESPResponseStream{ + public: + void print(const char *data); + void println(const char *data); + void flush(); + ESPResponseStream(WebServer * webserver); + private: + bool _header_sent; + WebServer * _webserver; + String _buffer; +}; + +class Web_Server { + public: + Web_Server(); + ~Web_Server(); + bool begin(); + void end(); + static void handle(); + static long get_client_ID(); + private: + static bool _setupdone; + static WebServer * _webserver; + static long _id_connection; + static WebSocketsServer * _socket_server; + static uint16_t _port; + static uint16_t _data_port; + static String _hostname; + static uint8_t _upload_status; + static char * mac2str (uint8_t mac [8]); + static String formatBytes (uint32_t bytes); + static String getContentType (String filename); + static String get_Splited_Value(String data, char separator, int index); + static level_authenticate_type is_authenticated(); +#ifdef ENABLE_AUTHENTICATION + static auth_ip * _head; + static uint8_t _nb_ip; + static bool AddAuthIP (auth_ip * item); + static char * create_session_ID(); + static bool ClearAuthIP (IPAddress ip, const char * sessionID); + static auth_ip * GetAuth (IPAddress ip, const char * sessionID); + static level_authenticate_type ResetAuthIP (IPAddress ip, const char * sessionID); + static bool isLocalPasswordValid (const char * password); +#endif + static String get_param (String & cmd_params, const char * id, bool withspace); + static bool execute_internal_command (int cmd, String cmd_params, level_authenticate_type auth_level, ESPResponseStream *espresponse); +#ifdef ENABLE_SSDP + static void handle_SSDP (); +#endif + static void handle_root(); + static void handle_login(); + static void handle_not_found (); + static void handle_web_command (); + static void handle_web_command_silent (); + static void handle_Websocket_Event(uint8_t num, uint8_t type, uint8_t * payload, size_t length); + static void SPIFFSFileupload (); + static void handleFileList (); + static void handleUpdate (); + static void WebUpdateUpload (); +#if ENABLED(SDSUPPORT) + static void handle_direct_SDFileList(); + static void SDFile_direct_upload(); +#endif +}; + +extern Web_Server web_server; + +#endif + diff --git a/src/wificonfig.cpp b/src/wificonfig.cpp new file mode 100644 index 0000000..af0bfe7 --- /dev/null +++ b/src/wificonfig.cpp @@ -0,0 +1,429 @@ +/* + wificonfig.cpp - wifi functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifdef ARDUINO_ARCH_ESP32 + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(WIFISUPPORT) +#include "HAL.h" +#include +#include +#include +#include +#include +#include +#include "wificonfig.h" +#include "wifiservices.h" + + +#ifdef __cplusplus +extern "C" { +#endif +esp_err_t esp_task_wdt_reset(); +#ifdef __cplusplus +} +#endif + +WiFiConfig wifi_config; + +bool WiFiConfig::restart_ESP_module = false; + +WiFiConfig::WiFiConfig(){ +} + +WiFiConfig::~WiFiConfig(){ + end(); +} + +/** + * Helper to convert IP string to int + */ + +uint32_t WiFiConfig::IP_int_from_string(String & s){ + uint32_t ip_int = 0; + IPAddress ipaddr; + if (ipaddr.fromString(s)) ip_int = ipaddr; + return ip_int; +} + +/** + * Helper to convert int to IP string + */ + +String WiFiConfig::IP_string_from_int(uint32_t ip_int){ + IPAddress ipaddr(ip_int); + return ipaddr.toString(); +} + +/** + * Check if Hostname string is valid + */ + +bool WiFiConfig::isHostnameValid (const char * hostname) +{ + //limited size + char c; + if (strlen (hostname) > MAX_HOSTNAME_LENGTH || strlen (hostname) < MIN_HOSTNAME_LENGTH) { + return false; + } + //only letter and digit + for (int i = 0; i < strlen (hostname); i++) { + c = hostname[i]; + if (! (isdigit (c) || isalpha (c) || c == '_') ) { + return false; + } + if (c == ' ') { + return false; + } + } + return true; +} + +/** + * Check if SSID string is valid + */ + +bool WiFiConfig::isSSIDValid (const char * ssid) +{ + //limited size + //char c; + if (strlen (ssid) > MAX_SSID_LENGTH || strlen (ssid) < MIN_SSID_LENGTH) { + return false; + } + //only printable + for (int i = 0; i < strlen (ssid); i++) { + if (!isPrintable (ssid[i]) ) { + return false; + } + } + return true; +} + +/** + * Check if password string is valid + */ + +bool WiFiConfig::isPasswordValid (const char * password) +{ + if (strlen (password) == 0) return true; //open network + //limited size + if ((strlen (password) > MAX_PASSWORD_LENGTH) || (strlen (password) < MIN_PASSWORD_LENGTH)) { + return false; + } + //no space allowed ? + /* for (int i = 0; i < strlen (password); i++) + if (password[i] == ' ') { + return false; + }*/ + return true; +} + +/** + * Check if IP string is valid + */ +bool WiFiConfig::isValidIP(const char * string){ + IPAddress ip; + return ip.fromString(string); +} + +/* + * delay is to avoid with asyncwebserver and may need to wait sometimes + */ +void WiFiConfig::wait(uint32_t milliseconds){ + uint32_t timeout = millis(); + esp_task_wdt_reset(); //for a wait 0; + //wait feeding WDT + while ( (millis() - timeout) < milliseconds) { + esp_task_wdt_reset(); + } +} + +/** + * WiFi events + * SYSTEM_EVENT_WIFI_READY < ESP32 WiFi ready + * SYSTEM_EVENT_SCAN_DONE < ESP32 finish scanning AP + * SYSTEM_EVENT_STA_START < ESP32 station start + * SYSTEM_EVENT_STA_STOP < ESP32 station stop + * SYSTEM_EVENT_STA_CONNECTED < ESP32 station connected to AP + * SYSTEM_EVENT_STA_DISCONNECTED < ESP32 station disconnected from AP + * SYSTEM_EVENT_STA_AUTHMODE_CHANGE < the auth mode of AP connected by ESP32 station changed + * SYSTEM_EVENT_STA_GOT_IP < ESP32 station got IP from connected AP + * SYSTEM_EVENT_STA_LOST_IP < ESP32 station lost IP and the IP is reset to 0 + * SYSTEM_EVENT_STA_WPS_ER_SUCCESS < ESP32 station wps succeeds in enrollee mode + * SYSTEM_EVENT_STA_WPS_ER_FAILED < ESP32 station wps fails in enrollee mode + * SYSTEM_EVENT_STA_WPS_ER_TIMEOUT < ESP32 station wps timeout in enrollee mode + * SYSTEM_EVENT_STA_WPS_ER_PIN < ESP32 station wps pin code in enrollee mode + * SYSTEM_EVENT_AP_START < ESP32 soft-AP start + * SYSTEM_EVENT_AP_STOP < ESP32 soft-AP stop + * SYSTEM_EVENT_AP_STACONNECTED < a station connected to ESP32 soft-AP + * SYSTEM_EVENT_AP_STADISCONNECTED < a station disconnected from ESP32 soft-AP + * SYSTEM_EVENT_AP_PROBEREQRECVED < Receive probe request packet in soft-AP interface + * SYSTEM_EVENT_GOT_IP6 < ESP32 station or ap or ethernet interface v6IP addr is preferred + * SYSTEM_EVENT_ETH_START < ESP32 ethernet start + * SYSTEM_EVENT_ETH_STOP < ESP32 ethernet stop + * SYSTEM_EVENT_ETH_CONNECTED < ESP32 ethernet phy link up + * SYSTEM_EVENT_ETH_DISCONNECTED < ESP32 ethernet phy link down + * SYSTEM_EVENT_ETH_GOT_IP < ESP32 ethernet got IP from connected AP + * SYSTEM_EVENT_MAX + */ + +void WiFiConfig::WiFiEvent(WiFiEvent_t event) +{ + switch (event) + { + case SYSTEM_EVENT_STA_GOT_IP: + MYSERIAL0.println ("Connected"); + MYSERIAL0.println(WiFi.localIP()); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + MYSERIAL0.println("WiFi lost connection"); + break; + default: + break; + } +} + +/* + * Get WiFi signal strength + */ +int32_t WiFiConfig::getSignal (int32_t RSSI) +{ + if (RSSI <= -100) { + return 0; + } + if (RSSI >= -50) { + return 100; + } + return (2 * (RSSI + 100) ); +} + +/* + * Connect client to AP + */ + +bool WiFiConfig::ConnectSTA2AP(){ + String msg, msg_out; + uint8_t count = 0; + uint8_t dot = 0; + wl_status_t status = WiFi.status(); + while (status != WL_CONNECTED && count < 40) { + + switch (status) { + case WL_NO_SSID_AVAIL: + msg="No SSID"; + break; + case WL_CONNECT_FAILED: + msg="Connection failed"; + break; + case WL_CONNECTED: + break; + default: + if ((dot>3) || (dot==0) ){ + dot=0; + msg_out = "Connecting"; + } + msg_out+="."; + msg= msg_out; + dot++; + break; + } + MYSERIAL0.println(msg.c_str()); + wait (500); + count++; + status = WiFi.status(); + } + return (status == WL_CONNECTED); +} + +/* + * Start client mode (Station) + */ + +bool WiFiConfig::StartSTA(){ + String defV; + Preferences prefs; + //stop active service + wifi_services.end(); + //Sanity check + if((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))WiFi.disconnect(); + if((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))WiFi.softAPdisconnect(); + WiFi.enableAP (false); + WiFi.mode(WIFI_STA); + //Get parameters for STA + prefs.begin(NAMESPACE, true); + defV = DEFAULT_HOSTNAME; + String h = prefs.getString(HOSTNAME_ENTRY, defV); + WiFi.setHostname(h.c_str()); + //SSID + defV = DEFAULT_STA_SSID; + String SSID = prefs.getString(STA_SSID_ENTRY, defV); + if (SSID.length() == 0)SSID = DEFAULT_STA_SSID; + //password + defV = DEFAULT_STA_PWD; + String password = prefs.getString(STA_PWD_ENTRY, defV); + int8_t IP_mode = prefs.getChar(STA_IP_MODE_ENTRY, DHCP_MODE); + //IP + defV = DEFAULT_STA_IP; + int32_t IP = prefs.getInt(STA_IP_ENTRY, IP_int_from_string(defV)); + //GW + defV = DEFAULT_STA_GW; + int32_t GW = prefs.getInt(STA_GW_ENTRY, IP_int_from_string(defV)); + //MK + defV = DEFAULT_STA_MK; + int32_t MK = prefs.getInt(STA_MK_ENTRY, IP_int_from_string(defV)); + prefs.end(); + //if not DHCP + if (IP_mode != DHCP_MODE) { + IPAddress ip(IP), mask(MK), gateway(GW); + WiFi.config(ip, gateway,mask); + } + if (WiFi.begin(SSID.c_str(), (password.length() > 0)?password.c_str():NULL)){ + MYSERIAL0.print("\nClient Started\nConnecting "); + MYSERIAL0.println(SSID.c_str()); + return ConnectSTA2AP(); + } else { + MYSERIAL0.println("\nStarting client failed"); + return false; + } +} + +/** + * Setup and start Access point + */ + +bool WiFiConfig::StartAP(){ + String defV; + Preferences prefs; + //stop active services + wifi_services.end(); + //Sanity check + if((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))WiFi.disconnect(); + if((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))WiFi.softAPdisconnect(); + WiFi.enableSTA (false); + WiFi.mode(WIFI_AP); + //Get parameters for AP + prefs.begin(NAMESPACE, true); + //SSID + defV = DEFAULT_AP_SSID; + String SSID = prefs.getString(AP_SSID_ENTRY, defV); + if (SSID.length() == 0)SSID = DEFAULT_AP_SSID; + //password + defV = DEFAULT_AP_PWD; + String password = prefs.getString(AP_PWD_ENTRY, defV); + //channel + int8_t channel = prefs.getChar(AP_CHANNEL_ENTRY, DEFAULT_AP_CHANNEL); + if (channel == 0)channel = DEFAULT_AP_CHANNEL; + //IP + defV = DEFAULT_AP_IP; + int32_t IP = prefs.getInt(AP_IP_ENTRY, IP_int_from_string(defV)); + if (IP==0){ + IP = IP_int_from_string(defV); + } + prefs.end(); + IPAddress ip(IP); + IPAddress mask; + mask.fromString(DEFAULT_AP_MK); + //Set static IP + WiFi.softAPConfig(ip, ip, mask); + //Start AP + if(WiFi.softAP(SSID.c_str(), (password.length() > 0)?password.c_str():NULL, channel)) { + MYSERIAL0.print("\nAP Started "); + MYSERIAL0.println(WiFi.softAPIP().toString()); + return true; + } else { + MYSERIAL0.println("\nStarting AP failed"); + return false; + } +} + +/** + * Stop WiFi + */ + +void WiFiConfig::StopWiFi(){ + //Sanity check + if((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))WiFi.disconnect(true); + if((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))WiFi.softAPdisconnect(true); + wifi_services.end(); + WiFi.mode(WIFI_OFF); + MYSERIAL0.println("\nWiFi Off"); +} + +/** + * begin WiFi setup + */ +void WiFiConfig::begin() { + Preferences prefs; + //stop active services + wifi_services.end(); + //setup events + WiFi.onEvent(WiFiConfig::WiFiEvent); + //open preferences as read-only + prefs.begin(NAMESPACE, true); + int8_t wifiMode = prefs.getChar(ESP_WIFI_MODE, DEFAULT_WIFI_MODE); + prefs.end(); + if (wifiMode == ESP_WIFI_AP) { + StartAP(); + //start services + wifi_services.begin(); + } else if (wifiMode == ESP_WIFI_STA){ + if(!StartSTA()){ + MYSERIAL0.println("\nCannot connect to AP"); + StartAP(); + } + //start services + wifi_services.begin(); + }else WiFi.mode(WIFI_OFF); +} + +/** + * End WiFi + */ +void WiFiConfig::end() { + StopWiFi(); +} + +/** + * Restart ESP + */ +void WiFiConfig::restart_ESP(){ + restart_ESP_module=true; +} + +/** + * Handle not critical actions that must be done in sync environement + */ +void WiFiConfig::handle() { + //in case of restart requested + if (restart_ESP_module) { + end(); + ESP.restart(); + while (1) {}; + } + + //Services + wifi_services.handle(); +} + + +#endif // ENABLE_WIFI + +#endif // ARDUINO_ARCH_ESP32 diff --git a/src/wificonfig.h b/src/wificonfig.h new file mode 100644 index 0000000..3d31cab --- /dev/null +++ b/src/wificonfig.h @@ -0,0 +1,133 @@ +/* + wificonfig.h - wifi functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//Services that need to be used +#include "../../inc/MarlinConfigPre.h" +#define ENABLE_MDNS +#define ENABLE_OTA +#define ENABLE_HTTP +#define ENABLE_SSDP +#define ENABLE_CAPTIVE_PORTAL +//Authentication flag is now in Configuration_adv.h +//#define ENABLE_AUTHENTICATION +#define ENABLE_SERIAL2SOCKET_OUT + +#define ENABLE_SERIAL2SOCKET_IN + +//Preferences entries +#define NAMESPACE "MARLIN" +#define HOSTNAME_ENTRY "ESP_HOSTNAME" +#define STA_SSID_ENTRY "STA_SSID" +#define STA_PWD_ENTRY "STA_PWD" +#define STA_IP_ENTRY "STA_IP" +#define STA_GW_ENTRY "STA_GW" +#define STA_MK_ENTRY "STA_MK" +#define ESP_WIFI_MODE "WIFI_MODE" +#define AP_SSID_ENTRY "AP_SSID" +#define AP_PWD_ENTRY "AP_PWD" +#define AP_IP_ENTRY "AP_IP" +#define AP_CHANNEL_ENTRY "AP_CHANNEL" +#define HTTP_ENABLE_ENTRY "HTTP_ON" +#define HTTP_PORT_ENTRY "HTTP_PORT" +#define TELNET_ENABLE_ENTRY "TELNET_ON" +#define TELNET_PORT_ENTRY "TELNET_PORT" +#define STA_IP_MODE_ENTRY "STA_IP_MODE" + +//Wifi Mode +#define ESP_WIFI_OFF 0 +#define ESP_WIFI_STA 1 +#define ESP_WIFI_AP 2 + +#define DHCP_MODE 0 +#define STATIC_MODE 0 + +//Switch +#define ESP_SAVE_ONLY 0 +#define ESP_APPLY_NOW 1 + +//defaults values +#define DEFAULT_HOSTNAME "marlinesp" +#define DEFAULT_STA_SSID "MARLIN_ESP" +#define DEFAULT_STA_PWD "12345678" +#define DEFAULT_STA_IP "0.0.0.0" +#define DEFAULT_STA_GW "0.0.0.0" +#define DEFAULT_STA_MK "0.0.0.0" +#define DEFAULT_WIFI_MODE ESP_WIFI_AP +#define DEFAULT_AP_SSID "MARLIN_ESP" +#define DEFAULT_AP_PWD "12345678" +#define DEFAULT_AP_IP "192.168.0.1" +#define DEFAULT_AP_MK "255.255.255.0" +#define DEFAULT_AP_CHANNEL 1 +#define DEFAULT_WEBSERVER_PORT 80 +#define DEFAULT_HTTP_STATE 1 +#define HIDDEN_PASSWORD "********" + +//boundaries +#define MAX_SSID_LENGTH 32 +#define MIN_SSID_LENGTH 1 +#define MAX_PASSWORD_LENGTH 64 +//min size of password is 0 or upper than 8 char +//so let set min is 8 +#define MIN_PASSWORD_LENGTH 8 +#define MAX_HOSTNAME_LENGTH 32 +#define MIN_HOSTNAME_LENGTH 1 +#define MAX_HTTP_PORT 65001 +#define MIN_HTTP_PORT 1 +#define MAX_TELNET_PORT 65001 +#define MIN_TELNET_PORT 1 +#define MIN_CHANNEL 1 +#define MAX_CHANNEL 14 + + +#ifndef _WIFI_CONFIG_H +#define _WIFI_CONFIG_H + +#include + +class WiFiConfig { +public: + WiFiConfig(); + ~WiFiConfig(); + static void wait(uint32_t milliseconds); + static bool isValidIP(const char * string); + static bool isPasswordValid (const char * password); + static bool isSSIDValid (const char * ssid); + static bool isHostnameValid (const char * hostname); + static uint32_t IP_int_from_string(String & s); + static String IP_string_from_int(uint32_t ip_int); + + static bool StartAP(); + static bool StartSTA(); + static void StopWiFi(); + static int32_t getSignal (int32_t RSSI); + static void begin(); + static void end(); + static void handle(); + static void restart_ESP(); + + private : + static bool ConnectSTA2AP(); + static void WiFiEvent(WiFiEvent_t event); + static bool restart_ESP_module; +}; + +extern WiFiConfig wifi_config; + +#endif diff --git a/src/wifiservices.cpp b/src/wifiservices.cpp new file mode 100644 index 0000000..59bb716 --- /dev/null +++ b/src/wifiservices.cpp @@ -0,0 +1,142 @@ +/* + wifiservices.cpp - wifi services functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifdef ARDUINO_ARCH_ESP32 + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(WIFISUPPORT) + +#include "HAL.h" +#include +#include +#include +#include +#include "wificonfig.h" +#include "wifiservices.h" +#ifdef ENABLE_MDNS +#include +#endif +#ifdef ENABLE_OTA +#include +#endif +#ifdef ENABLE_HTTP +#include "web_server.h" +#endif + +WiFiServices wifi_services; + +WiFiServices::WiFiServices(){ +} +WiFiServices::~WiFiServices(){ + end(); +} + +bool WiFiServices::begin(){ + bool no_error = true; + //Sanity check + if(WiFi.getMode() == WIFI_OFF) return false; + String h; + Preferences prefs; + //Get hostname + String defV = DEFAULT_HOSTNAME; + prefs.begin(NAMESPACE, true); + h = prefs.getString(HOSTNAME_ENTRY, defV); + prefs.end(); + WiFi.scanNetworks (true); + //Start SPIFFS + SPIFFS.begin(true); + +#ifdef ENABLE_OTA + ArduinoOTA + .onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) + type = "sketch"; + else {// U_SPIFFS + // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() + type = "filesystem"; + SPIFFS.end(); + } + MYSERIAL0.printf("OTA:Start OTA updating %s]\r\n", type.c_str()); + }) + .onEnd([]() { + MYSERIAL0.println("OTA:End"); + + }) + .onProgress([](unsigned int progress, unsigned int total) { + MYSERIAL0.printf("OTA:OTA Progress: %u%%]\r\n", (progress / (total / 100))); + }) + .onError([](ota_error_t error) { + MYSERIAL0.printf("OTA: Error(%u)\r\n", error); + if (error == OTA_AUTH_ERROR) MYSERIAL0.println("OTA:Auth Failed]"); + else if (error == OTA_BEGIN_ERROR) MYSERIAL0.println("OTA:Begin Failed"); + else if (error == OTA_CONNECT_ERROR) MYSERIAL0.println("OTA:Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) MYSERIAL0.println("OTA:Receive Failed"); + else if (error == OTA_END_ERROR) MYSERIAL0.println("OTA:End Failed]"); + }); + ArduinoOTA.begin(); +#endif +#ifdef ENABLE_MDNS + //no need in AP mode + if(WiFi.getMode() == WIFI_STA){ + //start mDns + if (!MDNS.begin(h.c_str())) { + MYSERIAL0.println("Cannot start mDNS"); + no_error = false; + } else { + MYSERIAL0.printf("Start mDNS with hostname:%s\r\n",h.c_str()); + } + } +#endif +#ifdef ENABLE_HTTP + web_server.begin(); +#endif + return no_error; +} +void WiFiServices::end(){ +#ifdef ENABLE_HTTP + web_server.end(); +#endif + //stop OTA +#ifdef ENABLE_OTA + ArduinoOTA.end(); +#endif + //Stop SPIFFS + SPIFFS.end(); + +#ifdef ENABLE_MDNS + //Stop mDNS + //MDNS.end(); +#endif +} + +void WiFiServices::handle(){ +#ifdef ENABLE_OTA + ArduinoOTA.handle(); +#endif +#ifdef ENABLE_HTTP + web_server.handle(); +#endif +} + +#endif // ENABLE_WIFI + +#endif // ARDUINO_ARCH_ESP32 diff --git a/src/wifiservices.h b/src/wifiservices.h new file mode 100644 index 0000000..790fd67 --- /dev/null +++ b/src/wifiservices.h @@ -0,0 +1,39 @@ +/* + wifiservices.h - wifi services functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + + +#ifndef _WIFI_SERVICES_H +#define _WIFI_SERVICES_H + + +class WiFiServices { + public: + WiFiServices(); + ~WiFiServices(); + static bool begin(); + static void end(); + static void handle(); +}; + +extern WiFiServices wifi_services; + +#endif +