diff --git a/Esp32_oscilloscope.ino b/Esp32_oscilloscope.ino index 298c89b..a9b4fc8 100644 --- a/Esp32_oscilloscope.ino +++ b/Esp32_oscilloscope.ino @@ -1,262 +1,254 @@ /* - Esp32_web_ftp_telnet_server_template.ino + Esp32_oscilloscope.ino - This file is part of Esp32_web_ftp_telnet_server_template project: https://github.com/BojanJurca/Multitasking-Esp32-HTTP-FTP-Telnet-servers-for-Arduino - - File contains a working template for some operating system functionalities that can support your projects. - - Copy all files in the package to Esp32_web_ftp_telnet_server_template directory, compile them with Arduino and run on ESP32. + This file is part of Esp32_oscilloscope project: https://github.com/BojanJurca/Esp32_oscilloscope + Esp32 oscilloscope is also fully included in Esp32_web_ftp_telnet_server_template project: https://github.com/BojanJurca/Esp32_web_ftp_telnet_server_template + + Esp32 oscilloscope is built upon Esp32_web_ftp_telnet_server_template. As a stand-alone project it uses only those + parts of Esp32_web_ftp_telnet_sfileserver_template that are necessary to run Esp32 oscilloscope. - May 22, 2024, Bojan Jurca + See https://github.com/BojanJurca/Esp32_web_ftp_telnet_server_template for details on how the servers work. - */ + Copy all files in the package into Esp32_oscilloscope directory, compile them with Arduino (with FAT partition scheme) and run on ESP32. + + December 25, 2023, Bojan Jurca + +*/ + +// Compile this code with Arduino for: +// - one of ESP32 boards (Tools | Board) and +// - one of FAT partition schemes (Tools | Partition scheme) and +// - at least 80 MHz CPU (Tools | CPU Frequency) + + +// ------------------------------------------ Esp32_web_ftp_telnet_server_template configuration ------------------------------------------ +// you can skip some files #included if you don't need the whole functionality + + +// uncomment the following line to get additional compile-time information about how the project gets compiled +// #define SHOW_COMPILE_TIME_INFORMATION + + +// include version_of_servers.h to include version information +#include "version_of_servers.h" +// include dmesg_functions.h which is useful for run-time debugging - for dmesg telnet command +// #include "dmesg_functions.h" + + +// 1. TIME: #define which time settings, wil be used with time_functions.h - will be included later + // define which 3 NTP servers will be called to get current GMT (time) from + // this information will be written into /etc/ntp.conf file if fileSystem.hpp will be included + // #define DEFAULT_NTP_SERVER_1 "1.si.pool.ntp.org" // <- replace with your information + // #define DEFAULT_NTP_SERVER_2 "2.si.pool.ntp.org" // <- replace with your information + // #define DEFAULT_NTP_SERVER_3 "3.si.pool.ntp.org" // <- replace with your information + // define time zone to calculate local time from GMT + // #define TZ "CET-1CEST,M3.5.0,M10.5.0/3" // default: Europe/Ljubljana, or select another (POSIX) time zones: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv + + +// 2. FILE SYSTEM: #define which file system you want to use + // the file system must correspond to Tools | Partition scheme setting: FAT for FAT partition scheme, LittleFS for SPIFFS partition scheme) + + // COMMENT THIS DEFINITION OUT IF YOUR ESP32 DOES NOT HAVE A FLASH DISK + #define FILE_SYSTEM FILE_SYSTEM_LITTLEFS // FILE_SYSTEM_FAT // the file system must correspond to Tools | Partition scheme setting: FILE_SYSTEM_FAT (for FAT partition scheme), FILE_SYSTEM_LITTLEFS (for SPIFFS partition scheme) or FILE_SYSTEM_SD_CARD (if SC card is attached) + // FAT file system can be bitwise combined with FILE_SYSTEM_SD_CARD, like #define FILE_SYSTEM (FILE_SYSTEM_FAT | FILE_SYSTEM_SD_CARD) + + // When file system is used a disc will be created and formatted. You can later FTP .html and other files to /var/www/html directory, + // which is the root directory of a HTTP server. + // Some ESP32 boards do not have a flash disk. In this case just comment the #include line above and Oscilloscope.html will be stored in program memory. + // This is where HTTP server will fetch it from. + + +// 3. NETWORK: #define how ESP32 will use the network + // STA(tion) + // #define how ESP32 will connecto to WiFi router + // this information will be written into /etc/wpa_supplicant/wpa_supplicant.conf file if fileSystem.hpp will be included + // if these #definitions are missing STAtion will not be set up + #define DEFAULT_STA_SSID "YOUR_STA_SSID" // <- replace with your information + #define DEFAULT_STA_PASSWORD "YOUR_STA_PASSWORD" // <- replace with your information + // the use of DHCP or static IP address will be set in /network/interfaces if fileSystem.hpp is included, the following is information needed for static IP configuration + // if these #definitions are missing DHCP is assumed + // #define DEFAULT_STA_IP "10.18.1.200" // <- replace with your information + // #define DEFAULT_STA_SUBNET_MASK "255.255.255.0" // <- replace with your information + // #define DEFAULT_STA_GATEWAY "10.18.1.1" // <- replace with your information + // #define DEFAULT_STA_DNS_1 "193.189.160.13" // <- replace with your information + // #define DEFAULT_STA_DNS_2 "193.189.160.23" // <- replace with your information + + // A(ccess) P(oint) + // #define how ESP32 will set up its access point + // this information will be writte into /etc/dhcpcd.conf and /etc/hostapd/hostapd.conf files if fileSystem.hpp will be included + // if these #definitions are missing Access Point will not be set up + // #define DEFAULT_AP_SSID HOSTNAME // <- replace with your information, + // #define DEFAULT_AP_PASSWORD "YOUR_AP_PASSWORD" // <- replace with your information, at least 8 characters + // #define DEFAULT_AP_IP "192.168.0.1" // <- replace with your information + // #define DEFAULT_AP_SUBNET_MASK "255.255.255.0" // <- replace with your information + + // define the name Esp32 will use as its host name + #define HOSTNAME "MyEsp32Oscilloscope" // <- replace with your information, max 32 bytes + + #if CONFIG_IDF_TARGET_ESP32 + #define MACHINETYPE "ESP32" + #elif CONFIG_IDF_TARGET_ESP32S2 + #define MACHINETYPE "ESP32-S2" + #elif CONFIG_IDF_TARGET_ESP32S3 + #define MACHINETYPE "ESP32-S3" + #elif CONFIG_IDF_TARGET_ESP32C3 + #define MACHINETYPE "ESP32-C3" + #elif CONFIG_IDF_TARGET_ESP32C6 + #define MACHINETYPE "ESP32-C6" + #elif CONFIG_IDF_TARGET_ESP32H2 + #define MACHINETYPE "ESP32-H2" + #else + #define MACHINETYPE "ESP32 (other)" + #endif -// Compile this code with Arduino for one of ESP32 boards (Tools | Board) and one of FAT partition schemes (Tools | Partition scheme)! -#include +// 4. USERS: #define what kind of user management you want before #including user_management.h + #define USER_MANAGEMENT NO_USER_MANAGEMENT // or UNIX_LIKE_USER_MANAGEMENT or HARDCODED_USER_MANAGEMENT + // if UNIX_LIKE_USER_MANAGEMENT is selected you must also include fileSystem.hpp to be able to use /etc/passwd and /etc/shadow files + // #define DEFAULT_ROOT_PASSWORD "rootpassword" // <- replace with your information if UNIX_LIKE_USER_MANAGEMENT or HARDCODED_USER_MANAGEMENT are used + // #define DEFAULT_WEBADMIN_PASSWORD "webadminpassword" // <- replace with your information if UNIX_LIKE_USER_MANAGEMENT is used + // #define DEFAULT_USER_PASSWORD "changeimmediatelly" // <- replace with your information if UNIX_LIKE_USER_MANAGEMENT is used -#include "servers/std/Cstring.hpp" -#include "servers/std/Map.hpp" -#include "servers/std/console.hpp" -#include "Esp32_servers_config.h" +// #include (or comment-out) the functionalities you want (or don't want) to use +#ifdef FILE_SYSTEM + #include "fileSystem.hpp" +#endif +// #include "time_functions.h" // fileSystem.hpp is needed prior to #including time_functions.h if you want to store the default parameters +#include "network.h" // fileSystem.hpp is needed prior to #including network.h if you want to store the default parameters +// #include "httpClient.h" +// #include "ftpClient.h" // fileSystem.hpp is needed prior to #including ftpClient.h if you want to store the default parameters +// #include "smtpClient.h" // fileSystem.hpp is needed prior to #including smtpClient.h if you want to store the default parameters +#include "userManagement.hpp" // fileSystem.hpp is needed prior to #including userManagement.hpp in case of UNIX_LIKE_USER_MANAGEMENT +// #include "telnetServer.hpp" // needs almost all the above files for whole functionality, but can also work without them +#ifdef FILE_SYSTEM + #include "ftpServer.hpp" // fileSystem.hpp is also necessary to use ftpServer.h +#endif +#define USE_I2S_INTERFACE // I2S interface improves web based oscilloscope analog sampling (of a single signal) if ESP32 board has one +// check INVERT_ADC1_GET_RAW and INVERT_I2S_READ #definitions in oscilloscope.h if the signals are inverted +#include "oscilloscope.h" // web based oscilloscope: you must #include httpServer.hpp as well to use it +#include "httpServer.hpp" // fileSystem.hpp is needed prior to #including httpServer.h if you want server also to serve .html and other files from built-in flash disk -#include "soc/rtc_wdt.h" +// ------------------------------------------------------ end of configuration ------------------------------------------------------------ - -// ----- HTTP request handler example - if you don't want to handle HTTP requests just delete this function and pass NULL to httpSrv instead of its address ----- -// normally httpRequest is HTTP request, function returns a reply in HTML, json, ... formats or "" if request is unhandeled by httpRequestHandler -// httpRequestHandler is supposed to be used with smaller replies, -// if you want to reply with larger pages you may consider FTP-ing .html files onto the file system (into /var/www/html/ directory) +#include -#ifndef FILE_SYSTEM - #include "oscilloscope_amber.h" // use RAM version -#endif -String httpRequestHandler (char *httpRequest, httpConnection *hcn) { +String httpRequestHandler (char *httpRequest, httpConnection *hcn); +void wsRequestHandler (char *wsRequest, WebSocket *webSocket); - #define httpRequestStartsWith(X) (strstr(httpRequest,X)==httpRequest) +#include +#include - #ifdef __OSCILLOSCOPE__ - #ifdef __FILE_SYSTEM__ +void setup () { + + Serial.begin (115200); + Serial.println (string (MACHINETYPE " (") + string ((int) ESP.getCpuFreqMHz ()) + (char *) " MHz) " HOSTNAME " SDK: " + ESP.getSdkVersion () + (char *) " " VERSION_OF_SERVERS " compiled at: " __DATE__ " " __TIME__); - // if HTTP request is GET /oscilloscope.html HTTP server will fetch the file but let us redirect GET / and GET /index.html to it as well - if (httpRequestStartsWith ("GET / ") || httpRequestStartsWith ("GET /index.html ")) { - hcn->setHttpReplyHeaderField ("Location", "/oscilloscope.html"); - hcn->setHttpReplyStatus ((char *) "307 temporary redirect"); // change reply status to 307 and set Location so client browser will know where to go next - return "Redirecting ..."; // whatever - } - #else + // disable watchdog if you can afford it - watchdog gets occasionally triggered when loaded heavily + #if CONFIG_FREERTOS_UNICORE // CONFIG_FREERTOS_UNICORE == 1 => 1 core ESP32 + disableCore0WDT (); + #else // CONFIG_FREERTOS_UNICORE == 0 => 2 core ESP32 + disableCore0WDT (); + disableCore1WDT (); + #endif - if (httpRequestStartsWith ("GET / ") || httpRequestStartsWith ("GET /index.html ") || httpRequestStartsWith ("GET /oscilloscope.html ")) { - return F (oscilloscope_amber); - } + #ifdef __FILE_SYSTEM__ + #if (FILE_SYSTEM & FILE_SYSTEM_LITTLEFS) == FILE_SYSTEM_LITTLEFS + // fileSystem.formatLittleFs (); Serial.printf ("\nFormatting FAT file system ...\n\n"); // format flash disk to reset everithing and start from the scratch + fileSystem.mountLittleFs (true); // this is the first thing to do - all configuration files reside on the file system #endif - + #if (FILE_SYSTEM & FILE_SYSTEM_FAT) == FILE_SYSTEM_FAT + // fileSystem.formatFAT (); Serial.printf ("\nFormatting FAT file system ...\n\n"); // format flash disk to reset everithing and start from the scratch + fileSystem.mountFAT (true); // this is the first thing to do - all configuration files reside on the file system + #endif #endif - - return ""; // httpRequestHandler did not handle the request - tell httpServer to handle it internally by returning "" reply -} + // userManagement.initialize (); // creates user management files with root and webadmin users, if they don't exist yet, not needed for NO_USER_MANAGEMENT + // fileSystem.deleteFile ("/network/interfaces"); // contation STA(tion) configuration - deleting this file would cause creating default one + // fileSystem.deleteFile ("/etc/wpa_supplicant/wpa_supplicant.conf"); // contation STA(tion) credentials - deleting this file would cause creating default one + // fileSystem.deleteFile ("/etc/dhcpcd.conf"); // contains A(ccess) P(oint) configuration - deleting this file would cause creating default one + // fileSystem.deleteFile ("/etc/hostapd/hostapd.conf"); // contains A(ccess) P(oint) credentials - deleting this file would cause creating default one + startWiFi (); // starts WiFi according to configuration files, creates configuration files if they don't exist -void wsRequestHandler (char *wsRequest, WebSocket *webSocket) { - - #define wsRequestStartsWith(X) (strstr(wsRequest,X)==wsRequest) - - #ifdef __OSCILLOSCOPE__ - if (wsRequestStartsWith ("GET /runOscilloscope")) runOscilloscope (webSocket); // used by oscilloscope.html + // start web server + httpServer *httpSrv = new httpServer (httpRequestHandler, // a callback function that will handle HTTP requests that are not handled by webServer itself + wsRequestHandler, // a callback function that will handle WS requests, NULL to ignore WS requests + "0.0.0.0", // start HTTP server on all available IP addresses + 80, // default HTTP port + NULL); // we won't use firewallCallback function for HTTP server + if (!httpSrv && httpSrv->state () != httpServer::RUNNING) Serial.printf ("[httpServer] did not start.\n"); + + #ifdef __FILE_SYSTEM__ + // start FTP server + ftpServer *ftpSrv = new ftpServer ("0.0.0.0", // start FTP server on all available ip addresses + 21, // default FTP port + NULL); // we won't use firewallCallback function for FTP server + if (ftpSrv && ftpSrv->state () != ftpServer::RUNNING) Serial.printf ("[ftpServer] did not start.\n"); #endif -} - -// ----- telnet command handler example - if you don't want to handle telnet commands yourself just delete this function and pass NULL to telnetSrv instead of its address ----- - -String telnetCommandHandlerCallback (int argc, char *argv [], telnetConnection *tcn) { - - #define argv0is(X) (argc > 0 && !strcmp (argv[0], X)) - #define argv1is(X) (argc > 1 && !strcmp (argv[1], X)) - #define argv2is(X) (argc > 2 && !strcmp (argv[2], X)) + // initialize GPIOs you are going to use here: + // ... + + /* test - generate the signal that can be viewed with oscilloscope + #undef BUILTIN_LED + #define BUILTIN_LED 2 + Serial.printf ("\nGenerating 1 KHz PWM signal on built-in LED pin %i, just for demonstration purposes.\n" + "Please, delete this code when if it is no longer needed.\n\n", BUILTIN_LED); + ledcSetup (0, 100, 10); // channel, freqency, resolution_bits + ledcAttachPin (BUILTIN_LED, 0); // GPIO, channel + ledcWrite (0, 307); // channel, 1/3 duty cycle (307 out of 1024 (10 bit resolution)) + */ + + // test - generate low analog voltage to check if the read signal is inverted + // dacWrite (25, 64); + +} - // get Telnet session private memory to keep track of the session - this memory will be autmatically freed at the end of Telnet connection - oscSharedMemory *pOscSharedMemory = (oscSharedMemory *) tcn->privateMemory; - - if (tcn->privateMemory == NULL) { // if not allocated yet - tcn->privateMemory = malloc (sizeof (oscSharedMemory)); - if (tcn->privateMemory == NULL) // if memory allocation failed - return "Out of memory"; - - // fill in the default values - pOscSharedMemory = (oscSharedMemory *) tcn->privateMemory; - pOscSharedMemory->analog = true; // ------------------------ Are we going to read only analog values ??? - } - +void loop () { - // process SCPI commands - if (argv0is ("*IDN?")) { - if (argc == 1) { - #ifdef DEFAULT_AP_SSID - return String ("*IDN" HOSTNAME " " VERSION_OF_SERVERS ", SN: ") + WiFi.softAPmacAddress ().c_str (); - #else - return String ("*IDN" HOSTNAME " " VERSION_OF_SERVERS ", SN: ") + WiFi.macAddress ().c_str (); - #endif - } else { - return "Wrong number of parameters"; - } - } +} - else if (argv0is ("SARA")) { - if (argc == 2) { - float sara; - if (sscanf (argv [1], "%fSa/s", sara) == 1 && sara > 0 && sara <= 1000) { - // sampling time (calculated from sara) sara value would go into pOscSharedMemory->samplingTime +#include "oscilloscope_amber.h" - return "OK"; - } else { - return "Parameter out of range"; - } - } else { - return "Wrong number of parameters"; - } - } +String httpRequestHandler (char *httpRequest, httpConnection *hcn) { + // httpServer will add HTTP header to the String that this callback function returns and send everithing to the Web browser (this callback function is suppose to return only the content part of HTTP reply) - else if (argv0is ("SARA?")) { - if (argc == 1) { - return "733"; - } else { - return "Wrong number of parameters"; - } - } - - else if (argv0is ("CHAN")) { - int gpio1, gpio2; - switch (argc) { - case 3: gpio2 = atoi (argv [2]); - if (gpio2 < 0 || gpio2 > 39) { - return String ("Wrong channel ") + argv [2]; - } - pOscSharedMemory->gpio2 = gpio2; - - case 2: gpio1 = atoi (argv [1]); - if (gpio1 < 0 || gpio1 > 39) { - return String ("Wrong channel ") + argv [1]; - } - pOscSharedMemory->gpio1 = gpio1; - break; - default: return "Wrong number of parameters"; - } - return "OK"; - } - - else if (argv0is ("NS")) { - if (argc == 2) { - int noOfSamples = atoi (argv [1]); - if (noOfSamples > 0 && noOfSamples <= 1000) { - return "OK"; - } else { - return "Parameter out of range"; - } - } else { - return "Wrong number of parameters"; - } - } + #define httpRequestStartsWith(X) (strstr(httpRequest,X)==httpRequest) - else if (argv0is ("TRSE")) { - if (argc == 5) { - return "Can this be simplified? As to channel, slope and value?"; - } else { - return "Wrong number of parameters"; - } - } + #ifdef __FILE_SYSTEM__ - else if (argv0is ("ARM")) { - if (argc == 1) { - return "1.1 2.2 3.3 and so on until STOP. Which commands MUST be issued before ARM? SARA? CHAN? NS? What happens if they are not?"; - } else { - return "Wrong number of parameters"; + // if HTTP request is GET /oscilloscope.html HTTP server will fetch the file but let us redirect GET / and GET /index.html to it as well + if (httpRequestStartsWith ("GET / ") || httpRequestStartsWith ("GET /index.html ")) { + hcn->setHttpReplyHeaderField ("Location", "/oscilloscope.html"); + hcn->setHttpReplyStatus ((char *) "307 temporary redirect"); // change reply status to 307 and set Location so client browser will know where to go next + return "Redirecting ..."; // whatever } - } - else if (argv0is ("STOP")) { - if (argc == 1) { - return "OK"; - } else { - return "Wrong number of parameters"; - } - } + #else - else if (argv0is ("STAST?")) { - if (argc == 1) { - return "Ready"; - } else { - return "Wrong number of parameters"; + if (httpRequestStartsWith ("GET / ") || httpRequestStartsWith ("GET /index.html ") || httpRequestStartsWith ("GET /oscilloscope.html ")) { + return F (oscilloscope_amber); } - } - - return "Unknown command"; -} - -void setup () { - - // disable watchdog if you can afford it - watchdog gets occasionally triggered when loaded heavily - #if CONFIG_FREERTOS_UNICORE // CONFIG_FREERTOS_UNICORE == 1 => 1 core ESP32 - disableCore0WDT (); - #else // CONFIG_FREERTOS_UNICORE == 0 => 2 core ESP32 - disableCore0WDT (); - disableCore1WDT (); #endif - - cinit (); - - #ifdef FILE_SYSTEM - fileSystem.mountLittleFs (true); // this is the first thing to do - all configuration files are on file system - // fileSystem.formatLittleFs (); - #endif - - // deleteFile ("/etc/ntp.conf"); // contains ntp server names for time sync - deleting this file would cause creating default one - // deleteFile ("/etc/crontab"); // contains cheduled tasks - deleting this file would cause creating empty one - // startCronDaemon (cronHandler); // creates /etc/ntp.conf with default NTP server names and syncronize ESP32 time with them once a day - // creates empty /etc/crontab, reads it at startup and executes cronHandler when the time is right - // 3 KB stack size is minimal requirement for NTP time synchronization, add more if your cronHandler requires more - - // deleteFile ("/etc/passwd"); // contains users' accounts information - deleting this file would cause creating default one - // deleteFile ("/etc/shadow"); // contains users' passwords - deleting this file would cause creating default one - // userManagement.initialize (); // creates user management files with root, webadmin, webserver and telnetserver users, if they don't exist - - // deleteFile ("/network/interfaces"); // contation STA(tion) configuration - deleting this file would cause creating default one - // deleteFile ("/etc/wpa_supplicant/wpa_supplicant.conf"); // contation STA(tion) credentials - deleting this file would cause creating default one - // deleteFile ("/etc/dhcpcd.conf"); // contains A(ccess) P(oint) configuration - deleting this file would cause creating default one - // deleteFile ("/etc/hostapd/hostapd.conf"); // contains A(ccess) P(oint) credentials - deleting this file would cause creating default one - startWiFi (); // starts WiFi according to configuration files, creates configuration files if they don't exist - - // start web server - httpServer *httpSrv = new httpServer (httpRequestHandler, // a callback function that will handle HTTP requests that are not handled by webServer itself - wsRequestHandler); // a callback function that will handle WS requests, NULL to ignore WS requests - if (!httpSrv || httpSrv->state () != httpServer::RUNNING) - cout << "[httpServer] did not start."; - - // start FTP server - ftpServer *ftpSrv = new ftpServer (); - if (!ftpSrv || ftpSrv->state () != ftpServer::RUNNING) - cout << "[ftpServer] did not start."; - - // start telnet server on port 5025 for SCPI communication - telnetServer *telnetSrv = new telnetServer (telnetCommandHandlerCallback, "0.0.0.0", 5025); - if (!telnetSrv || telnetSrv->state () != telnetServer::RUNNING) - cout << "[telnetServer] did not start."; - + // if the request is GET /oscilloscope.html we don't have to interfere - web server will read the file from file system + return ""; // HTTP request has not been handled by httpRequestHandler - let the httpServer handle it itself } -void loop () { +void wsRequestHandler (char *wsRequest, WebSocket *webSocket) { + + #define wsRequestStartsWith(X) (strstr(wsRequest,X)==wsRequest) + if (wsRequestStartsWith ("GET /runOscilloscope")) runOscilloscope (webSocket); // used by oscilloscope.html }