├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── gpioviewer │ └── gpioviewer.ino └── platformio │ ├── .gitignore │ ├── platformio.ini │ └── src │ ├── _secrets.h │ └── main.cpp ├── gpio_viewer.ino ├── keywords.txt ├── library.properties └── src └── gpio_viewer.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | buy_me_a_coffee: thelastoutpostworkshop 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | sounds 3 | secrets.h 4 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 thelastoutpostworkshop 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GPIOViewer Arduino Library to see live GPIO Pins on ESP32 boards 2 | 3 | **Transforms the way you troubleshoot your microcontroller projects**.
4 | 5 | Buy Me A Coffee 6 | 7 | 8 | ## Youtube Tutorial 9 | 10 | [](https://youtu.be/UxkOosaNohU) 11 | [](https://youtu.be/JJzRXcQrl3I) 12 | 13 | ## Installation 14 | 15 | ### Installation Arduino IDE (Version 2) 16 | 17 | > ℹ️ Make sure you have the [latest ESP32 boards](https://github.com/espressif/arduino-esp32) 18 | > by Espressif Systems in your Board Manager
19 | 20 | - Install the **GPIOViewer Library with the Arduino IDE Library Manager** or Download the [latest stable release](https://github.com/thelastoutpostworkshop/gpio_viewer/releases/latest) and install the library in the Arduino IDE : `Sketch > Include Library > Add ZIP Library...` 21 | - Install [ESPAsyncWebServer](https://github.com/mathieucarbou/ESPAsyncWebServer) using the Arduino IDE Library Manager 22 | - Install the the [Async TCP](https://github.com/mathieucarbou/AsyncTCP) using the Arduino IDE Library Manager. 23 | 24 | ### Installation VSCode + PlatformIO 25 | 26 | > ℹ️ Make sure you have the [latest ESP32 boards](https://github.com/espressif/arduino-esp32) 27 | > by Espressif Systems in your Platforms
28 | 29 | - Install the **GPIOViewer Library using PlateformIO Libraries** 30 | 31 | Add (or change) the following to your platformio.ini file: 32 | 33 | ``` 34 | platform = espressif32 35 | framework = arduino 36 | lib_deps = 37 | https://github.com/mathieucarbou/AsyncTCP.git 38 | https://github.com/mathieucarbou/AsyncTCP.git 39 | ``` 40 | 41 | ## Usage 42 | 43 | > ℹ️ You can also get examples provided with the library in the Arduino IDE through the menu `File > Examples > GPIOViewer`
44 | > ℹ️ You only need to include the library, declare the GPIOViewer and call begin() at the end of your setup, and that's it!
45 | > ℹ️ The URL to the web GPIO viewer application is printed on the serial monitor
46 | 47 | ```c 48 | #include // Must me the first include in your project 49 | GPIOViewer gpio_viewer; 50 | 51 | void setup() 52 | { 53 | Serial.begin(115200); 54 | 55 | // Comment the next line, If your code aleady include connection to Wifi in mode WIFI_STA (WIFI_AP and WIFI_AP_STA are not supported) 56 | gpio_viewer.connectToWifi("Your SSID network", "Your WiFi Password"); 57 | // gpio_viewer.setPort(5555); // You can set the http port, if not set default port is 8080 58 | 59 | // Your own setup code start here 60 | 61 | // Must be at the end of your setup 62 | // gpio_viewer.setSamplingInterval(25); // You can set the sampling interval in ms, if not set default is 100ms 63 | gpio_viewer.begin(); 64 | } 65 | ``` 66 | 67 | > ℹ️ The default HTTP port is **8080** and default sampling interval is **100ms**
68 | > ℹ️ Wifi must be in mode WIFI_STA (WIFI_AP and WIFI_AP_STA are not supported) 69 | 70 | ## GPIO Supported 71 | 72 | - Digital 73 | - Analog 74 | - PWM 75 | - ADC 76 | 77 | ## Library Size 78 | 79 | - The GPIOViewer Library adds 50 KB to your projects. 80 | - No worries! All the assets (ex. board images) of the web application are loaded from github pages and don't add to the size of your projects. 81 | 82 | ## Espressif ESP32 Core SDK Compatibility 83 | 84 | - The Espressif ESP32 Arduino Core that is installed in your system will need to be v2.0.5 or greater / v3.0.0 or greater, in order for GPIO viewer to compile properly. 85 | - See the official Espressif Systems ESP32 Core documentation located here for more details: https://docs.espressif.com/projects/arduino-esp32/en/latest/ 86 | 87 | ## Performance 88 | 89 | - Ensure you have a strong Wifi signal with a good transfer rate. 25ms sampling interval works great on Wifi 6 with 125 Mbps. 90 | - If you get "ERROR: Too many messages queued" on the Serial Monitor, this means the data is not read fast enough by the web application. The data will still be displayed, but with some latency. Reduce the sampling interval or try to improve your Wifi performance. 91 | 92 | ## Contributors 93 | 94 | Contributors are welcomed! If you want to submit pull requests, [here is how you can do it](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project). 95 | 96 | ## Troubleshooting 97 | 98 | ### Last tested to be working on : 99 | 100 | - v3.2.0 [ESP32 Arduino Core](https://github.com/espressif/arduino-esp32) 101 | - v3.4.2 [Async TCP](https://github.com/mathieucarbou/AsyncTCP) 102 | - v3.7.7 [ESP Async WebServer](https://github.com/mathieucarbou/ESPAsyncWebServer) 103 | 104 | ### Conflicts with pin function detection on some boards 105 | 106 | Since version 1.5.6, the library detects pin functions like ADC and Touch, this has been causing problems on some boards, like the XiaoESP32-S3-Sense. 107 | You can disable pin detection by adding this define before including the library : 108 | 109 | ```C++ 110 | #define NO_PIN_FUNCTIONS 111 | #include 112 | ``` 113 | 114 | ### Code not compiling 115 | 116 | If your code don't compile, **before submitting an issue:** 117 | 118 | - Compile with the [latest stable release](https://github.com/thelastoutpostworkshop/gpio_viewer/releases/latest) of the GPIOViewer Library **and** with the [latest ESP32 boards](https://github.com/espressif/arduino-esp32) 119 | - See also this [solved issue](https://github.com/thelastoutpostworkshop/gpio_viewer/issues/116) 120 | 121 | ### GPIOViewer running 122 | 123 | If GPIOViewer is running and your are experiencing problems in the web application, **before submitting an issue:** 124 | 125 | - Make sure you are using the [latest stable release](https://github.com/thelastoutpostworkshop/gpio_viewer/releases/latest) of the GPIOViewer Library 126 | - Clear your browser cache data and refresh the window in your browser 127 | 128 | ## ESP32 Boards Supported 129 | 130 | > ℹ️ You can use the "Generic View" in the GPIO Web Application to see GPIO pin activites live even if your board image is not listed
131 | > ℹ️ You can also request an ESP32 board image addition by [creating a new issue](https://github.com/thelastoutpostworkshop/gpio_viewer/issues). 132 | 133 | | Description | Image | Pinout | 134 | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 135 | | AZ Delivery NodeMCU ESP32 | ! | | 136 | | DFROBOT Beetle ESP32-C6 | ! | | 137 | | ESP32 NodeMCU 32S | ! | | 138 | | ESP32 VROOM 32D (38 pins) | ! | | 139 | | ESP32 VROOM 32D (38 pins) | ! | | 140 | | ESP32 VROOM 32D (30 pins) | ! | | 141 | | ESP32 D1 R32 | ! | | 142 | | ESP32-CAM | ! | | 143 | | ESP32-C3 Super Mini | ! | | 144 | | ESP32 C3 Wroom-02 | ! | | 145 | | ESP32-C5 DevKit1 | ! | | 146 | | ESP32-C6 DevKitM | ! | | 147 | | ESP32 Wroom-32UE | ! | | 148 | | ESP32 EVB | ! | | 149 | | ESP32-S3-DevKitM-1-N8 | ! | | 150 | | ESP32 S3 Wroom-1 | ! | | 151 | | Esp32 S2 Mini V1.0.0 | ! | | 152 | | ESP32 POE | ! | | 153 | | ESP32 C3 Mini | ! | | 154 | | ESP32 C3 Mini-1 | ! | | 155 | | ESP32 C3 Zero | ! | | 156 | | ESP32 C6 Zero | ! | | 157 | | ESP32-S3 Zero | ! | | 158 | | ESP32 Pico Kit v4.1 | ! | | 159 | | Leaf S3 | ! | | 160 | | Lilygo T-Display ESP32-S3 | ! | | 161 | | Lilygo T-SIM A7670x | ! | | 162 | | Lilygo T7 Mini32 v1.5 | ! | | 163 | | Lolin D32 | ! | | 164 | | Freenove ESP32-S3 | ! | | 165 | | Freenove ESP32-Wroom | ! | | 166 | | Heltec HT-WB32_V3 | ! | | 167 | | Nano ESP32 | ! | | 168 | | Nano ESP32-C6 | ! | | 169 | | Olimex ESP32-P4 | ! | | 170 | | Sailor Hat ESP32 | ! | | 171 | | SparkleIoT ESP32-C3F | ! | | 172 | | StickLite-V3-ESP32S3 | ! | | 173 | | T-Display S3 AMOLED | ! | | 174 | | TinyPICO Nano | ! | | 175 | | TinyPico V3 | ! | | 176 | | TTGO Display V1.1 | ! | | 177 | | Wemos Lolin32 Lite V1 | ! | | 178 | | Wemos Lolin S3 Mini | ! | | 179 | | Wemos D1 Mini ESP32 | ! | | 180 | | Wemos D1 Mini ESP8266 | ! | | 181 | | WT32-S1-ETH01 | ! | | 182 | | NodeMcu ESP8266 | ! | | 183 | | XIAO ESP32 C3 | ! | | 184 | | XIAO ESP32 S3 | ! | | 185 | -------------------------------------------------------------------------------- /examples/gpioviewer/gpioviewer.ino: -------------------------------------------------------------------------------- 1 | /*** 2 | This example is intended to demonstrate the use of the GPIO Viewer Library. 3 | 4 | Tutorial : https://youtu.be/UxkOosaNohU 5 | Latest Features : https://youtu.be/JJzRXcQrl3I 6 | Documentation : https://github.com/thelastoutpostworkshop/gpio_viewer 7 | 8 | // Last tested on: 9 | // Espressif Arduino Core v3.2.0 10 | // ESP Async WebServer 3.7.7 11 | // AsyncTCP 3.4.0 12 | ***/ 13 | 14 | // Since version 1.5.6, the library detects pin functions like ADC and Touch, this has been causing problems on some boards, like the XiaoESP32-S3-Sense. You can disable pin detection by uncommenting the following line: 15 | // #define NO_PIN_FUNCTIONS 16 | 17 | #include // Must me the first include in your project 18 | GPIOViewer gpio_viewer; 19 | 20 | void setup() 21 | { 22 | Serial.begin(115200); 23 | 24 | // Comment the next line, If your code aleady include connection to Wifi in mode WIFI_STA (WIFI_AP and WIFI_AP_STA are not supported) 25 | gpio_viewer.connectToWifi("Your SSID network", "Your WiFi Password"); 26 | // gpio_viewer.setPort(5555); // You can set the http port, if not set default port is 8080 27 | 28 | // Your own setup code start here 29 | 30 | // Must be at the end of your setup 31 | // gpio_viewer.setSamplingInterval(25); // You can set the sampling interval in ms, if not set default is 100ms 32 | gpio_viewer.begin(); 33 | } 34 | 35 | // You don't need to change your loop function 36 | void loop() { 37 | 38 | } 39 | // The rest of your code here -------------------------------------------------------------------------------- /examples/platformio/.gitignore: -------------------------------------------------------------------------------- 1 | secrets.h 2 | .vscode 3 | .pio 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /examples/platformio/platformio.ini: -------------------------------------------------------------------------------- 1 | ; example for the ESP32 or the ESP32-S2 chip 2 | 3 | [platformio] 4 | default_envs = esp32 5 | ; or 6 | ;default_envs = esp32-s2 7 | 8 | [env] 9 | build_src_filter = +<*> -<.git/> -<.svn/> - - - - 10 | 11 | lib_deps = 12 | thelastoutpostworkshop/GPIOViewer @ ^1.0.7 13 | https://github.com/me-no-dev/ESPAsyncWebServer.git 14 | 15 | [env:esp32] 16 | platform = espressif32 17 | board = esp32dev 18 | framework = arduino 19 | 20 | build_flags = 21 | -D CORE_DEBUG_LEVEL=5 22 | 23 | [env:esp32-s2] 24 | platform = espressif32 25 | board = esp32-s2-saola-1 26 | framework = arduino 27 | 28 | build_flags = 29 | -D ARDUINO_USB_MODE=0 30 | -D ARDUINO_USB_CDC_ON_BOOT=1 31 | -D CORE_DEBUG_LEVEL=5 32 | -------------------------------------------------------------------------------- /examples/platformio/src/_secrets.h: -------------------------------------------------------------------------------- 1 | #define WIFI_SSID "SSID" 2 | #define WIFI_PASS "PASSWORD" -------------------------------------------------------------------------------- /examples/platformio/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "secrets.h" // rename the "_secrets.h" file before building 4 | 5 | #define DEMO_PIN 18 6 | 7 | GPIOViewer gpioViewer; 8 | bool pinState = false; 9 | 10 | void setup() { 11 | Serial.begin(115200); 12 | Serial.setDebugOutput(true); // send ESP inbuilt log messages to Serial 13 | 14 | pinMode(DEMO_PIN, OUTPUT); 15 | 16 | gpioViewer.connectToWifi(WIFI_SSID, WIFI_PASS); 17 | gpioViewer.setSamplingInterval(125); 18 | gpioViewer.begin(); 19 | } 20 | 21 | void loop() { 22 | pinState = !pinState; 23 | digitalWrite(DEMO_PIN, pinState); 24 | log_i("Current pin state: %d", pinState); 25 | delay(1000); 26 | } -------------------------------------------------------------------------------- /gpio_viewer.ino: -------------------------------------------------------------------------------- 1 | // Test Code for the GPIOViewer development 2 | // Adjust Last tested on in the example. 3 | // 4 | #include "src/gpio_viewer.h" 5 | #include 6 | #include 7 | #include "secrets.h" 8 | 9 | GPIOViewer gpio_viewer; 10 | 11 | struct PWM_PINS 12 | { 13 | int pin; 14 | int channel; 15 | uint16_t level; 16 | }; 17 | 18 | #define TEST_ESP32_S3 19 | // #define TEST_NO_EXTENDED_SOC // example Xiao ESP32-C3 20 | // #define TEST_ESP32 21 | 22 | #ifdef TEST_ESP32 23 | #define ROTARY_PIN_A 23 24 | #define ROTARY_PIN_B 22 25 | #define ROTARY_PUSH_BUTTON 22 // Not used 26 | #define SLOW_PWM_PIN 27 27 | #define SLOW_PMW_CHANNEL 5 28 | 29 | SimpleRotary rotary(ROTARY_PIN_A, ROTARY_PIN_B, ROTARY_PUSH_BUTTON); 30 | 31 | int test_digital_pins[] = {33, 25, 26}; 32 | const int testDigitalPinsCount = sizeof(test_digital_pins) / sizeof(test_digital_pins[0]); 33 | int currentLed = 0; // Start with the first LED 34 | 35 | const int analogPinsCount = 3; 36 | int test_analog_pins[analogPinsCount] = {32, 19, 18}; 37 | byte analogValue = 0; 38 | 39 | const int freq = 200; 40 | const int resolution = 16; 41 | 42 | PWM_PINS test_pwm_pins[] = {{15, 0}, {2, 1}, {0, 2}, {4, 3}}; 43 | const int testPWMPinsCount = sizeof(test_pwm_pins) / sizeof(test_pwm_pins[0]); 44 | #endif 45 | 46 | #ifdef TEST_ESP32_S3 47 | // Test ESP32-S3 48 | #define INPUT_PIN 1 49 | #define ROTARY_PIN_A 41 50 | #define ROTARY_PIN_B 42 51 | #define ROTARY_PUSH_BUTTON 42 // Not used 52 | 53 | #define SLOW_PWM_PIN 20 54 | #define SLOW_PMW_CHANNEL 5 55 | 56 | SimpleRotary rotary(ROTARY_PIN_A, ROTARY_PIN_B, ROTARY_PUSH_BUTTON); 57 | 58 | int test_digital_pins[] = {15, 4, 2}; 59 | const int testDigitalPinsCount = sizeof(test_digital_pins) / sizeof(test_digital_pins[0]); 60 | int currentLed = 0; // Start with the first LED 61 | 62 | const int analogPinsCount = 1; 63 | int test_analog_pins[analogPinsCount] = {7}; 64 | int analogValue = 0; 65 | 66 | const int freq = 1000; 67 | const int resolution = 10; 68 | 69 | PWM_PINS test_pwm_pins[] = {{17, 0, 0}, {18, 1, 0}, {8, 2, 0}, {3, 3, 0}}; 70 | const int testPWMPinsCount = sizeof(test_pwm_pins) / sizeof(test_pwm_pins[0]); 71 | #endif 72 | 73 | #ifdef TEST_NO_EXTENDED_SOC 74 | #define INPUT_PIN 2 75 | 76 | #define SLOW_PWM_PIN 20 77 | #define SLOW_PMW_CHANNEL 5 78 | 79 | int test_digital_pins[] = {3, 4, 6}; 80 | const int testDigitalPinsCount = sizeof(test_digital_pins) / sizeof(test_digital_pins[0]); 81 | int currentLed = 0; // Start with the first LED 82 | 83 | const int analogPinsCount = 1; 84 | int test_analog_pins[analogPinsCount] = {7}; 85 | int analogValue = 0; 86 | 87 | const int freq = 1000; 88 | const int resolution = 10; 89 | 90 | PWM_PINS test_pwm_pins[] = {{10, 0, 0}, {9, 1, 0}, {8, 2, 0}, {20, 3, 0}}; 91 | const int testPWMPinsCount = sizeof(test_pwm_pins) / sizeof(test_pwm_pins[0]); 92 | #endif 93 | 94 | void setup() 95 | { 96 | Serial.begin(115200); 97 | 98 | gpio_viewer.connectToWifi(ssid, password); 99 | 100 | test1_setup(); 101 | 102 | #ifdef TEST_ESP32_S3 103 | if (psramFound()) 104 | { 105 | uint8_t *largeMemoryBlock = (uint8_t *)malloc(4 * 1024 * 1024); // 4MB 106 | 107 | if (largeMemoryBlock == nullptr) 108 | { 109 | Serial.println("Memory allocation failed!"); 110 | } 111 | else 112 | { 113 | Serial.println("Memory allocation successful."); 114 | } 115 | } 116 | #endif 117 | 118 | gpio_viewer.setSamplingInterval(75); 119 | gpio_viewer.begin(); 120 | } 121 | 122 | void loop() 123 | { 124 | test1_loop(); 125 | } 126 | 127 | uint32_t getMaxDutyCycle(int resolution) 128 | { 129 | return (1 << resolution) - 1; 130 | } 131 | 132 | void slowPWMPin(void *pvParameters) 133 | { 134 | // Setup 135 | #if ESP_ARDUINO_VERSION_MAJOR >= 3 136 | ledcAttach(SLOW_PWM_PIN, 5000, 8); 137 | uint8_t slow_level = 0; 138 | #else 139 | ledcSetup(SLOW_PMW_CHANNEL, 5000, 8); 140 | ledcAttachPin(SLOW_PWM_PIN, SLOW_PMW_CHANNEL); 141 | uint8_t slow_level = 0; 142 | // Serial.printf("SLOW_PWM_PIN=%d\n", SLOW_PWM_PIN); 143 | #endif 144 | 145 | // Loop 146 | for (;;) 147 | { // Infinite loop 148 | // Serial.printf("ledcWrite=%d\n", slow_level); 149 | ledcWrite(SLOW_PMW_CHANNEL, slow_level += 20); 150 | delay(2000); 151 | } 152 | } 153 | 154 | void TestPWMPin(void *pvParameters) 155 | { 156 | // Setup 157 | uint16_t amount = 0; 158 | for (int i = 0; i < testPWMPinsCount; i++) 159 | { 160 | amount += (getMaxDutyCycle(resolution) / testPWMPinsCount); 161 | 162 | #if ESP_ARDUINO_VERSION_MAJOR >= 3 163 | ledcAttach(test_pwm_pins[i].pin, freq, resolution); 164 | #else 165 | ledcSetup(test_pwm_pins[i].channel, freq, resolution); 166 | ledcAttachPin(test_pwm_pins[i].pin, test_pwm_pins[i].channel); 167 | #endif 168 | 169 | test_pwm_pins[i].level = amount; 170 | } 171 | for (int i = 0; i < analogPinsCount; i++) 172 | { 173 | pinMode(test_analog_pins[i], OUTPUT); 174 | } 175 | 176 | // Loop 177 | for (;;) 178 | { 179 | for (int i = 0; i < testPWMPinsCount; i++) 180 | { 181 | #if ESP_ARDUINO_VERSION_MAJOR == 3 182 | ledcWrite(test_pwm_pins[i].pin, test_pwm_pins[i].level); 183 | #else 184 | ledcWrite(test_pwm_pins[i].channel, test_pwm_pins[i].level); 185 | #endif 186 | delay(150); 187 | test_pwm_pins[i].level += (getMaxDutyCycle(resolution) / 4); 188 | if (test_pwm_pins[i].level > getMaxDutyCycle(resolution)) 189 | { 190 | test_pwm_pins[i].level = 0; 191 | } 192 | } 193 | } 194 | } 195 | 196 | void TestDigitalPin(void *pvParameters) 197 | { 198 | // Setup 199 | for (int i = 0; i < testDigitalPinsCount; i++) 200 | { 201 | pinMode(test_digital_pins[i], OUTPUT); 202 | digitalWrite(test_digital_pins[i], LOW); 203 | } 204 | 205 | // Loop 206 | while (true) 207 | { 208 | for (int i = 0; i < testDigitalPinsCount; i++) 209 | { 210 | if (digitalRead(test_digital_pins[i]) == LOW) 211 | { 212 | 213 | digitalWrite(test_digital_pins[i], HIGH); 214 | } 215 | else 216 | { 217 | digitalWrite(test_digital_pins[i], LOW); 218 | } 219 | } 220 | delay(300); 221 | } 222 | } 223 | void TestAnalogPin(void *pvParameters) 224 | { 225 | // Setup 226 | 227 | // Loop 228 | while (true) 229 | { 230 | for (int i = 0; i < analogPinsCount; i++) 231 | { 232 | analogValue += (i * 3); 233 | if (analogValue > getMaxDutyCycle(8)) 234 | { 235 | analogValue = 0; 236 | } 237 | analogWrite(test_analog_pins[i], analogValue++); 238 | } 239 | 240 | delay(300); 241 | } 242 | } 243 | 244 | void test1_setup() 245 | { 246 | // pinMode(INPUT_PIN,INPUT_PULLUP); 247 | 248 | // xTaskCreate(readRotaryEncoderTask, "ReadRotaryEncoder", 2048, NULL, 1, NULL); 249 | xTaskCreate(slowPWMPin, "slowPWMPin", 2048, NULL, 1, NULL); 250 | xTaskCreate(TestPWMPin, "TestPWMPin", 2048, NULL, 1, NULL); 251 | xTaskCreate(TestDigitalPin, "TestDigitalPin", 2048, NULL, 1, NULL); 252 | xTaskCreate(TestAnalogPin, "TestAnalogPin", 2048, NULL, 1, NULL); 253 | } 254 | void test1_loop() 255 | { 256 | delay(1); 257 | } 258 | 259 | void updateLeds() 260 | { 261 | for (int i = 0; i < testDigitalPinsCount; i++) 262 | { 263 | digitalWrite(test_digital_pins[i], i == currentLed ? HIGH : LOW); 264 | } 265 | } 266 | 267 | // void readRotaryEncoderTask(void *pvParameters) 268 | // { 269 | // for (;;) 270 | // { // Infinite loop 271 | // readRotaryEncoder(); 272 | // vTaskDelay(pdMS_TO_TICKS(10)); // Delay for debouncing, adjust as needed 273 | // } 274 | // } 275 | 276 | // void readRotaryEncoder(void) 277 | // { 278 | // byte i; 279 | // i = rotary.rotate(); 280 | 281 | // if (i == 1) 282 | // { 283 | // currentLed = (currentLed - 1 + testDigitalPinsCount) % testDigitalPinsCount; 284 | // updateLeds(); 285 | // Serial.println("CounterClockwise"); 286 | // } 287 | 288 | // if (i == 2) 289 | // { 290 | // currentLed = (currentLed + 1) % testDigitalPinsCount; 291 | // updateLeds(); 292 | // Serial.println("Clockwise"); 293 | // } 294 | // } 295 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | [Keywords] 2 | ####################################### 3 | # Datatypes (KEYWORD1) 4 | ####################################### 5 | byte 6 | boolean 7 | uint16_t 8 | int16_t 9 | int 10 | enum 11 | 12 | ####################################### 13 | # Methods and Functions (KEYWORD2) 14 | ####################################### 15 | begin KEYWORD2 16 | setPort KEYWORD2 17 | setSamplingInterval KEYWORD2 18 | connectToWifi KEYWORD2 19 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=GPIOViewer 2 | version=1.6.3 3 | author=The Last Outpost Workshop 4 | maintainer=The Last Outpost Workshop 5 | sentence=Web Application to view GPIO pins live! 6 | paragraph=Web Application to view GPIO pins live! Works on any ESP32 Boards with Arduino Core from Espressif 7 | category=Other 8 | keywords=keywords.txt 9 | url=https://github.com/thelastoutpostworkshop/gpio_viewer 10 | architectures=esp32 11 | depends=Async TCP,ESP Async WebServer -------------------------------------------------------------------------------- /src/gpio_viewer.h: -------------------------------------------------------------------------------- 1 | #ifndef _GPIOVIEWER_ 2 | #define _GPIOVIEWER_ 3 | #ifndef WiFi_h 4 | #include 5 | #endif 6 | #ifndef _ESPAsyncWebServer_H_ 7 | #include 8 | #endif 9 | #ifndef ASYNCTCP_H_ 10 | #include 11 | #endif 12 | #ifndef INC_FREERTOS_H 13 | #include 14 | #endif 15 | #ifndef INC_TASK_H 16 | #include 17 | #endif 18 | #endif 19 | #include 20 | 21 | const char *release = "1.6.3"; 22 | 23 | const String baseURL = "https://thelastoutpostworkshop.github.io/microcontroller_devkit/gpio_viewer_1_5/"; 24 | 25 | #ifdef ESP_ARDUINO_VERSION_MAJOR 26 | #if ESP_ARDUINO_VERSION_MAJOR == 3 27 | #define GPIOVIEWER_ESP32CORE_VERSION_3 28 | #else 29 | #if ESP_ARDUINO_VERSION_MAJOR == 2 30 | #define GPIOVIEWER_ESP32CORE_VERSION_2 31 | #else 32 | #define GPIOVIEWER_ESP32CORE_NOTSUPPORTED 33 | #endif 34 | #endif 35 | #else 36 | #define GPIOVIEWER_ESP32CORE_NOTSUPPORTED 37 | #endif 38 | 39 | #ifdef GPIOVIEWER_ESP32CORE_VERSION_3 40 | #include "esp32-hal-periman.h" 41 | #include "soc/gpio_struct.h" 42 | #else 43 | extern uint8_t channels_resolution[]; 44 | #endif 45 | 46 | String arduinoCoreVersion = ""; 47 | 48 | #ifdef GPIO_PIN_COUNT 49 | #define maxGPIOPins GPIO_PIN_COUNT 50 | #else 51 | #define maxGPIOPins 49 52 | #endif 53 | 54 | #if defined(GPIO_PIN_COUNT) && (GPIO_PIN_COUNT > 32) 55 | #define SUPPORT_EXTENDED_GPIO 56 | #endif 57 | 58 | #define sentIntervalIfNoActivity 1000L // If no activity for this interval, resend to show connection activity 59 | 60 | // Global variables to capture PMW pins 61 | const uint8_t maxChannels = 64; 62 | uint8_t ledcChannelPin[maxChannels][2]; 63 | uint8_t ledcChannelPinCount = 0; 64 | uint8_t ledcChannelResolution[maxChannels][2]; 65 | uint8_t ledcChannelResolutionCount = 0; 66 | 67 | // Global variables to pins set with PinMode 68 | uint8_t pinmode[maxGPIOPins][2]; 69 | uint8_t pinModeCount = 0; 70 | 71 | #ifdef GPIOVIEWER_ESP32CORE_VERSION_3 72 | // Macro to trap values pass to ledcAttach functions since there is no ESP32 API 73 | #define ledcAttach(pin, freq, resolution) \ 74 | (ledcChannelPinCount < maxChannels ? ledcChannelPin[ledcChannelPinCount][0] = (pin), ledcChannelPin[ledcChannelPinCount++][1] = (resolution) : 0), \ 75 | ledcAttach((pin), (freq), (resolution)) 76 | #define ledcAttachChannel(pin, freq, resolution, channel) \ 77 | (ledcChannelPinCount < maxChannels ? ledcChannelPin[ledcChannelPinCount][0] = (pin), ledcChannelPin[ledcChannelPinCount++][1] = (resolution) : 0), \ 78 | ledcAttachChannel((pin), (freq), (resolution), (channel)) 79 | #define IS_VERSION_3_OR_HIGHER true 80 | #else 81 | // Macro to trap values pass to ledcAttachPin since there is no ESP32 API 82 | #define ledcAttachPin(pin, channel) \ 83 | (ledcChannelPinCount < maxChannels ? ledcChannelPin[ledcChannelPinCount][0] = (pin), ledcChannelPin[ledcChannelPinCount++][1] = (channel) : 0), \ 84 | ledcAttachPin((pin), (channel)) 85 | 86 | // Macro to trap values pass to ledcSetup since there is no ESP32 API 87 | #define ledcSetup(channel, freq, resolution) \ 88 | (ledcChannelResolutionCount < maxChannels ? ledcChannelResolution[ledcChannelResolutionCount][0] = (channel), ledcChannelResolution[ledcChannelResolutionCount++][1] = (resolution) : 0), \ 89 | ledcSetup((channel), (freq), (resolution)) 90 | #endif 91 | 92 | // Macro to trap values pass to pinMode since there is no ESP32 API 93 | #define pinMode(pin, mode) \ 94 | (pinModeCount < maxGPIOPins ? pinmode[pinModeCount][0] = (pin), pinmode[pinModeCount++][1] = (mode) : 0), \ 95 | pinMode((pin), (mode)) 96 | 97 | enum pinTypes 98 | { 99 | digitalPin = 0, 100 | PWMPin = 1, 101 | analogPin = 2 102 | }; 103 | 104 | class GPIOViewer 105 | { 106 | public: 107 | GPIOViewer() 108 | { 109 | } 110 | 111 | ~GPIOViewer() 112 | { 113 | delete events; 114 | server->end(); 115 | } 116 | 117 | void setPort(uint16_t port) 118 | { 119 | this->port = port; 120 | } 121 | 122 | void setSamplingInterval(unsigned long samplingInterval) 123 | { 124 | this->samplingInterval = samplingInterval; 125 | } 126 | 127 | void connectToWifi(const char *ssid, const char *password) 128 | { 129 | WiFi.begin(ssid, password); 130 | Serial.println("GPIOViewer >> Connecting to WiFi..."); 131 | while (WiFi.status() != WL_CONNECTED) 132 | { 133 | delay(500); 134 | Serial.print("."); 135 | } 136 | Serial.println("GPIOViewer >> Connected to WiFi"); 137 | } 138 | 139 | void begin() 140 | { 141 | Serial.setDebugOutput(false); 142 | Serial.printf("GPIOViewer >> Release %s\n", release); 143 | #if defined(ESP_ARDUINO_VERSION_MAJOR) && defined(ESP_ARDUINO_VERSION_MINOR) && defined(ESP_ARDUINO_VERSION_PATCH) 144 | arduinoCoreVersion = String(ESP_ARDUINO_VERSION_MAJOR) + "." + String(ESP_ARDUINO_VERSION_MINOR) + "." + String(ESP_ARDUINO_VERSION_PATCH); 145 | Serial.printf("GPIOViewer >> ESP32 Core Version %s\n", arduinoCoreVersion.c_str()); 146 | #ifdef GPIOVIEWER_ESP32CORE_NOTSUPPORTED 147 | Serial.printf("GPIOViewer >> Your ESP32 Core Version is not supported, update your ESP32 boards to the latest version\n"); 148 | return; 149 | #endif 150 | #endif 151 | Serial.printf("GPIOViewer >> Chip Model:%s, revision:%d\n", ESP.getChipModel(), ESP.getChipRevision()); 152 | if (psramFound()) 153 | { 154 | psramSize = ESP.getPsramSize(); 155 | Serial.printf("GPIOViewer >> PSRAM Size %s\n", formatBytes(psramSize).c_str()); 156 | } 157 | else 158 | { 159 | Serial.printf("GPIOViewer >> No PSRAM\n"); 160 | } 161 | 162 | // #ifdef GPIOVIEWER_ESP32CORE_VERSION_3 163 | // readValidPins(); 164 | // #endif 165 | #if defined(SOC_ADC_SUPPORTED) && defined(GPIOVIEWER_ESP32CORE_VERSION_3) 166 | readADCPinsConfiguration(); 167 | #endif 168 | #if defined(SOC_TOUCH_SENSOR_NUM) && defined(GPIOVIEWER_ESP32CORE_VERSION_3) 169 | #if SOC_TOUCH_SENSOR_NUM > 0 170 | readTouchPinsConfiguration(); 171 | #endif 172 | #endif 173 | 174 | if (checkWifiStatus()) 175 | { 176 | // printPWNTraps(); 177 | server = new AsyncWebServer(port); 178 | 179 | // Set CORS headers for global responses 180 | DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); 181 | DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); 182 | DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type"); 183 | 184 | // Initialize and set up the AsyncEventSource 185 | events = new AsyncEventSource("/events"); 186 | events->onConnect([this](AsyncEventSourceClient *client) 187 | { this->resetStatePins(); }); 188 | 189 | server->addHandler(events); 190 | 191 | server->on("/", [this](AsyncWebServerRequest *request) 192 | { request->send(200, "text/html", generateIndexHTML().c_str()); }); 193 | 194 | server->on("/release", HTTP_GET, [this](AsyncWebServerRequest *request) 195 | { sendMinReleaseVersion(request); }); 196 | 197 | server->on("/free_psram", HTTP_GET, [this](AsyncWebServerRequest *request) 198 | { sendFreePSRAM(request); }); 199 | 200 | server->on("/sampling", HTTP_GET, [this](AsyncWebServerRequest *request) 201 | { sendSamplingInterval(request); }); 202 | server->on("/espinfo", HTTP_GET, [this](AsyncWebServerRequest *request) 203 | { sendESPInfo(request); }); 204 | server->on("/partition", HTTP_GET, [this](AsyncWebServerRequest *request) 205 | { sendESPPartition(request); }); 206 | server->on("/pinmodes", HTTP_GET, [this](AsyncWebServerRequest *request) 207 | { sendPinModes(request); }); 208 | 209 | #ifndef NO_PIN_FUNCTIONS 210 | server->on("/pinfunctions", HTTP_GET, [this](AsyncWebServerRequest *request) 211 | { sendPinFunctions(request); }); 212 | #endif 213 | 214 | server->begin(); 215 | 216 | Serial.print("GPIOViewer >> Web Application URL is: http://"); 217 | Serial.print(WiFi.localIP()); 218 | Serial.print(":"); 219 | Serial.println(port); 220 | 221 | // Create a task for monitoring GPIOs 222 | xTaskCreate(&GPIOViewer::monitorTaskStatic, "GPIO Monitor Task", 4096, this, 1, NULL); 223 | } 224 | } 225 | 226 | static void monitorTaskStatic(void *pvParameter) 227 | { 228 | static_cast(pvParameter)->monitorTask(); 229 | } 230 | 231 | private: 232 | uint32_t lastPinStates[maxGPIOPins]; 233 | uint16_t port = 8080; 234 | unsigned long samplingInterval = 100; 235 | unsigned long lastSentWithNoActivity = millis(); 236 | AsyncWebServer *server; 237 | AsyncEventSource *events; 238 | uint32_t freeHeap = 0; 239 | uint32_t freePSRAM = 0; 240 | uint32_t psramSize = 0; 241 | uint8_t ADCPins[maxGPIOPins]; 242 | uint8_t ADCPinsCount = 0; 243 | uint8_t TouchPins[maxGPIOPins]; 244 | uint8_t TouchPinsCount = 0; 245 | String freeRAM = formatBytes(ESP.getFreeSketchSpace()); 246 | 247 | // #ifdef GPIOVIEWER_ESP32CORE_VERSION_3 248 | // // Read valid pins for the SOC 249 | // void readValidPins(void) 250 | // { 251 | // for (int i = 0; i < maxGPIOPins; i++) 252 | // { 253 | // if (GPIO_IS_VALID_GPIO(i)) 254 | // { 255 | // Serial.printf("Pin %d is valid\n", i); 256 | // } 257 | // else 258 | // { 259 | // // The GPIO number is not valid for general I/O operations. 260 | // } 261 | // } 262 | // } 263 | // #endif 264 | 265 | void sendESPPartition(AsyncWebServerRequest *request) 266 | { 267 | String jsonResponse = "["; // Start of JSON array 268 | bool firstEntry = true; // Used to format the JSON array correctly 269 | 270 | esp_partition_iterator_t iter = esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, NULL); 271 | 272 | // Loop through partitions 273 | while (iter != NULL) 274 | { 275 | const esp_partition_t *partition = esp_partition_get(iter); 276 | 277 | // Add comma before the next entry if it's not the first 278 | if (!firstEntry) 279 | { 280 | jsonResponse += ","; 281 | } 282 | firstEntry = false; 283 | 284 | // Append partition information in JSON format 285 | jsonResponse += "{"; 286 | jsonResponse += "\"label\":\"" + String(partition->label) + "\","; 287 | jsonResponse += "\"type\":" + String(partition->type) + ","; 288 | jsonResponse += "\"subtype\":" + String(partition->subtype) + ","; 289 | jsonResponse += "\"address\":\"0x" + String(partition->address, HEX) + "\","; 290 | jsonResponse += "\"size\":" + String(partition->size); 291 | jsonResponse += "}"; 292 | 293 | // Move to next partition 294 | iter = esp_partition_next(iter); 295 | } 296 | 297 | // Clean up the iterator 298 | esp_partition_iterator_release(iter); 299 | 300 | jsonResponse += "]"; // End of JSON array 301 | 302 | request->send(200, "application/json", jsonResponse); 303 | } 304 | 305 | void sendPinModes(AsyncWebServerRequest *request) 306 | { 307 | String jsonResponse = "["; // Start of JSON array 308 | bool firstEntry = true; // Used to format the JSON array correctly 309 | 310 | for (int i = 0; i < pinModeCount; i++) 311 | { 312 | if (!firstEntry) 313 | { 314 | jsonResponse += ","; 315 | } 316 | firstEntry = false; 317 | jsonResponse += "{"; 318 | jsonResponse += "\"pin\":\"" + String(pinmode[i][0]) + "\","; 319 | jsonResponse += "\"mode\":\"" + String(pinmode[i][1]) + "\""; 320 | jsonResponse += "}"; 321 | } 322 | 323 | jsonResponse += "]"; // End of JSON array 324 | 325 | request->send(200, "application/json", jsonResponse); 326 | } 327 | 328 | void sendESPInfo(AsyncWebServerRequest *request) 329 | { 330 | 331 | // const FlashMode_t flashMode = ESP.getFlashChipMode(); // removed, it crashes with ESP32-S3 332 | const char *flashMode = "\"not available\""; 333 | 334 | String jsonResponse = "{\"chip_model\": \"" + String(ESP.getChipModel()) + "\","; 335 | jsonResponse += "\"cores_count\": \"" + String(ESP.getChipCores()) + "\","; 336 | jsonResponse += "\"chip_revision\": \"" + String(ESP.getChipRevision()) + "\","; 337 | jsonResponse += "\"cpu_frequency\": \"" + String(ESP.getCpuFreqMHz()) + "\","; 338 | jsonResponse += "\"cycle_count\": " + String(ESP.getCycleCount()) + ","; 339 | jsonResponse += "\"mac\": \"" + String(ESP.getEfuseMac()) + "\","; 340 | jsonResponse += "\"flash_mode\": " + String(flashMode) + ","; 341 | jsonResponse += "\"flash_chip_size\": " + String(ESP.getFlashChipSize()) + ","; 342 | jsonResponse += "\"flash_chip_speed\": " + String(ESP.getFlashChipSpeed()) + ","; 343 | jsonResponse += "\"heap_size\": " + String(ESP.getHeapSize()) + ","; 344 | jsonResponse += "\"heap_max_alloc\": " + String(ESP.getMaxAllocHeap()) + ","; 345 | jsonResponse += "\"psram_size\": " + String(ESP.getPsramSize()) + ","; 346 | jsonResponse += "\"free_psram\": " + String(ESP.getFreePsram()) + ","; 347 | jsonResponse += "\"psram_max_alloc\": " + String(ESP.getMaxAllocPsram()) + ","; 348 | jsonResponse += "\"free_heap\": " + String(ESP.getFreeHeap()) + ","; 349 | jsonResponse += "\"up_time\": \"" + String(millis()) + "\","; 350 | jsonResponse += "\"sketch_size\": " + String(ESP.getSketchSize()) + ","; 351 | jsonResponse += "\"arduino_core_version\": \"" + arduinoCoreVersion + "\","; 352 | jsonResponse += "\"free_sketch\": " + String(ESP.getFreeSketchSpace()) + ""; 353 | 354 | jsonResponse += '}'; 355 | request->send(200, "application/json", jsonResponse); 356 | } 357 | 358 | void sendSamplingInterval(AsyncWebServerRequest *request) 359 | { 360 | String jsonResponse = "{\"sampling\": \"" + String(samplingInterval) + "\"}"; 361 | 362 | request->send(200, "application/json", jsonResponse); 363 | } 364 | void sendMinReleaseVersion(AsyncWebServerRequest *request) 365 | { 366 | String jsonResponse = "{\"release\": \"" + String(release) + "\"}"; 367 | 368 | request->send(200, "application/json", jsonResponse); 369 | } 370 | void sendFreePSRAM(AsyncWebServerRequest *request) 371 | { 372 | String jsonResponse = "{\"sampling\": \"" + String(samplingInterval) + "\"}"; 373 | if (psramFound()) 374 | { 375 | jsonResponse += formatBytes(ESP.getFreePsram()) + "\"}"; 376 | } 377 | else 378 | { 379 | jsonResponse += "No PSRAM\"}"; 380 | } 381 | 382 | request->send(200, "application/json", jsonResponse); 383 | } 384 | bool checkWifiStatus(void) 385 | { 386 | if (WiFi.status() == WL_CONNECTED) 387 | { 388 | return true; 389 | } 390 | else 391 | { 392 | wifi_mode_t mode = WiFi.getMode(); 393 | 394 | switch (mode) 395 | { 396 | case WIFI_OFF: 397 | Serial.println("GPIOViewer >> WiFi Mode: OFF"); 398 | break; 399 | case WIFI_STA: 400 | Serial.println("GPIOViewer >> WiFi Mode: Station (STA)"); 401 | break; 402 | case WIFI_AP: 403 | Serial.println("GPIOViewer >> WiFi Mode: Access Point (AP) is not supported"); 404 | break; 405 | case WIFI_AP_STA: 406 | Serial.println("GPIOViewer >> WiFi Mode: Access Point and Station (AP_STA) is not supported"); 407 | break; 408 | default: 409 | Serial.println("GPIOViewer >> WiFi Mode: Unknown, cannot run the wep application"); 410 | } 411 | Serial.println("GPIOViewer >> ESP32 is not connected to WiFi."); 412 | } 413 | return false; 414 | } 415 | 416 | void printPWNTraps() 417 | { 418 | Serial.printf("GPIOViewer >> %d pins are PWM\n", ledcChannelPinCount); 419 | for (int i = 0; i < ledcChannelPinCount; i++) 420 | { 421 | Serial.printf("GPIOViewer >> Pin %d is using channel %d\n", ledcChannelPin[i][0], ledcChannelPin[i][1]); 422 | } 423 | Serial.printf("GPIOViewer >> %d channels are used\n", ledcChannelResolutionCount); 424 | for (int i = 0; i < ledcChannelResolutionCount; i++) 425 | { 426 | Serial.printf("GPIOViewer >> Channel %d resolution is %d bits\n", ledcChannelResolution[i][0], ledcChannelResolution[i][1]); 427 | } 428 | Serial.printf("GPIOViewer >> %d pins have PinMode\n", pinModeCount); 429 | for (int i = 0; i < pinModeCount; i++) 430 | { 431 | Serial.printf("GPIOViewer >> Pin %d is using mode %d\n", pinmode[i][0], pinmode[i][1]); 432 | } 433 | } 434 | 435 | String generateIndexHTML() 436 | { 437 | String html = ""; 438 | html += ""; 439 | html += "GPIOViewer"; 440 | html += ""; 441 | html += "
"; 442 | 443 | html += ""; 450 | 451 | html += ""; 452 | return html; 453 | } 454 | 455 | void resetStatePins(void) 456 | { 457 | uint32_t originalValue; 458 | pinTypes pintype; 459 | Serial.printf("GPIOViewer >> Connected, sampling interval is %lums\n", samplingInterval); 460 | 461 | for (int i = 0; i < maxGPIOPins; i++) 462 | { 463 | lastPinStates[i] = readGPIO(i, &originalValue, &pintype); 464 | } 465 | } 466 | 467 | // Check GPIO values 468 | bool checkGPIOValues() 469 | { 470 | uint32_t originalValue; 471 | pinTypes pintype; 472 | 473 | String jsonMessage = "{"; 474 | bool hasChanges = false; 475 | 476 | for (int i = 0; i < maxGPIOPins; i++) 477 | { 478 | int currentState = readGPIO(i, &originalValue, &pintype); 479 | 480 | if (originalValue != lastPinStates[i]) 481 | { 482 | if (hasChanges) 483 | { 484 | jsonMessage += ", "; 485 | } 486 | jsonMessage += "\"" + String(i) + "\": {\"s\": " + currentState + ", \"v\": " + originalValue + ", \"t\": " + pintype + "}"; 487 | lastPinStates[i] = originalValue; 488 | hasChanges = true; 489 | } 490 | } 491 | 492 | jsonMessage += "}"; 493 | 494 | if (hasChanges) 495 | { 496 | sendGPIOStates(jsonMessage); 497 | } 498 | return hasChanges; 499 | } 500 | 501 | bool checkFreeHeap() 502 | { 503 | uint32_t heap = esp_get_free_heap_size(); 504 | if (heap != freeHeap) 505 | { 506 | freeHeap = heap; 507 | events->send(formatBytes(freeHeap).c_str(), "free_heap", millis()); 508 | return true; 509 | } 510 | return false; 511 | } 512 | 513 | bool checkFreePSRAM() 514 | { 515 | if (psramFound()) 516 | { 517 | uint32_t psram = ESP.getFreePsram(); 518 | if (psram != freePSRAM) 519 | { 520 | freePSRAM = psram; 521 | events->send(formatBytes(freePSRAM).c_str(), "free_psram", millis()); 522 | return true; 523 | } 524 | } 525 | return false; 526 | } 527 | 528 | // Monitor Task 529 | void monitorTask() 530 | { 531 | while (1) 532 | { 533 | bool changes = false; 534 | changes = checkGPIOValues(); 535 | changes |= checkFreeHeap(); 536 | changes |= checkFreePSRAM(); 537 | 538 | if (!changes) 539 | { 540 | unsigned long delay = millis() - lastSentWithNoActivity; 541 | if (delay > sentIntervalIfNoActivity) 542 | { 543 | // No activity, resending for pulse 544 | events->send(formatBytes(freeHeap).c_str(), "free_heap", millis()); 545 | lastSentWithNoActivity = millis(); 546 | } 547 | } 548 | else 549 | { 550 | lastSentWithNoActivity = millis(); 551 | } 552 | vTaskDelay(pdMS_TO_TICKS(samplingInterval)); 553 | } 554 | } 555 | 556 | int getLedcChannelForPin(int pin) 557 | { 558 | for (int i = 0; i < ledcChannelPinCount; i++) 559 | { 560 | if (ledcChannelPin[i][0] == pin) 561 | { 562 | return ledcChannelPin[i][1]; 563 | } 564 | } 565 | return -1; // Pin not found, return -1 to indicate no channel is associated 566 | } 567 | 568 | #ifdef GPIOVIEWER_ESP32CORE_VERSION_3 569 | int mapLedcReadTo8Bit(int gpioNum, int channel, uint32_t *originalValue) 570 | { 571 | ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(gpioNum, ESP32_BUS_TYPE_LEDC); 572 | if (bus != NULL) 573 | { 574 | uint8_t resolution; 575 | resolution = bus->channel_resolution; 576 | uint32_t maxDutyCycle = (1 << resolution) - 1; 577 | *originalValue = ledcRead(gpioNum); 578 | return map(*originalValue, 0, maxDutyCycle, 0, 255); 579 | } 580 | return 0; 581 | } 582 | bool isPinPMW(int gpioNum) 583 | { 584 | for (int i = 0; i < ledcChannelPinCount; i++) 585 | { 586 | if (ledcChannelPin[i][0] == gpioNum) 587 | { 588 | return true; 589 | } 590 | } 591 | return false; 592 | } 593 | bool isPinModeSet(int gpioNum) 594 | { 595 | for (int i = 0; i < pinModeCount; i++) 596 | { 597 | if (pinmode[i][0] == gpioNum) 598 | { 599 | return true; 600 | } 601 | } 602 | return false; 603 | } 604 | #ifdef SOC_ADC_SUPPORTED 605 | void 606 | readADCPinsConfiguration(void) 607 | { 608 | // Serial.println("GPIOViewer >> ADC Supported"); 609 | // Serial.printf("GPIOViewer >> %d ADC available, %d channels each \n", SOC_ADC_PERIPH_NUM, SOC_ADC_MAX_CHANNEL_NUM); 610 | int8_t channel; 611 | for (int i = 0; i < GPIO_PIN_COUNT; i++) 612 | { 613 | if (!GPIO_IS_VALID_GPIO(i)) 614 | { 615 | continue; 616 | } 617 | #ifdef CONFIG_IDF_TARGET_ESP32C3 618 | if (i == 5) 619 | { 620 | // Pin 5 on ESP32C3 appears not supported for ADC 621 | continue; 622 | } 623 | #endif 624 | channel = digitalPinToAnalogChannel(i); 625 | if (channel != -1) 626 | { 627 | #ifdef SUPPORT_EXTENDED_GPIO 628 | if (i >= 32) 629 | { 630 | // Check the extended bank using enable1 631 | if (GPIO.enable1.val & (1 << (i - 32))) 632 | { 633 | // Pin is configured as OUTPUT (e.g., SPI may have taken over), skip it 634 | continue; 635 | } 636 | } 637 | else 638 | { 639 | // For pins 0-31, check the base register 640 | if (GPIO.enable1.val & (1 << i)) 641 | { 642 | continue; 643 | } 644 | } 645 | #else 646 | if (GPIO.enable.val & (1 << i)) 647 | { 648 | // If GPIO has been configured as OUTPUT (for example by SPI), we cannot read it 649 | continue; 650 | } 651 | #endif 652 | // This ADC pin can be safely read 653 | ADCPins[ADCPinsCount] = i; 654 | ADCPinsCount++; 655 | } 656 | } 657 | // Serial.printf("GPIOViewer >> %d pins support ADC on your board\n", ADCPinsCount); 658 | } 659 | bool isPinInADCPins(int gpioNum) 660 | { 661 | for (int i = 0; i < ADCPinsCount; i++) 662 | { 663 | if (ADCPins[i] == gpioNum) 664 | { 665 | return true; 666 | } 667 | } 668 | return false; 669 | } 670 | uint32_t readADCPin(int gpioNum) 671 | { 672 | if (isPinInADCPins(gpioNum) && !(isPinPMW(gpioNum) || isPinModeSet(gpioNum))) 673 | { 674 | 675 | uint32_t analogValue = analogRead(gpioNum); 676 | return analogValue; 677 | } 678 | return 0; 679 | } 680 | #endif 681 | 682 | #ifdef SOC_TOUCH_SENSOR_NUM 683 | void readTouchPinsConfiguration(void) 684 | { 685 | // Serial.println("GPIOViewer >> Touch Supported"); 686 | int8_t channel; 687 | for (int i = 0; i < GPIO_PIN_COUNT; i++) 688 | { 689 | channel = digitalPinToTouchChannel(i); 690 | if (channel != -1) 691 | { 692 | TouchPins[TouchPinsCount] = i; 693 | TouchPinsCount++; 694 | } 695 | } 696 | // Serial.printf("GPIOViewer >> %d pins support Touch on your board\n", TouchPinsCount); 697 | } 698 | #endif 699 | 700 | int readGPIO(int gpioNum, uint32_t *originalValue, pinTypes *pintype) 701 | { 702 | int value; 703 | int channel = getLedcChannelForPin(gpioNum); 704 | if (channel != -1) 705 | { 706 | // This is an explicitely defined PWM Pin 707 | value = mapLedcReadTo8Bit(gpioNum, channel, originalValue); 708 | *pintype = PWMPin; 709 | return value; 710 | } 711 | 712 | uint32_t ledc_value = ledcRead(gpioNum); 713 | if (ledc_value != 0) 714 | { 715 | // This is an implicit PWM Pin (direct analogWrite call without explicit ledcAttach) 716 | value = mapLedcReadTo8Bit(gpioNum, 0, originalValue); 717 | *pintype = PWMPin; 718 | 719 | return ledc_value; 720 | } 721 | 722 | #ifdef SOC_ADC_SUPPORTED 723 | uint32_t analog_value = readADCPin(gpioNum); 724 | if (analog_value != 0) 725 | { 726 | *originalValue = analog_value; 727 | *pintype = analogPin; 728 | // Map value using the default resolution of 12 bits 729 | value = map(analog_value, 0, 4095, 0, 255); 730 | return value; 731 | } 732 | #endif 733 | 734 | // Assume this is a digital pin 735 | *pintype = digitalPin; 736 | value = digitalRead(gpioNum); 737 | *originalValue = value; 738 | if (value == 1) 739 | { 740 | return 256; 741 | } 742 | return 0; 743 | } 744 | #else 745 | int mapLedcReadTo8Bit(int gpioNum, int channel, uint32_t *originalValue) 746 | { 747 | uint8_t resolution; 748 | resolution = channels_resolution[channel]; 749 | if (resolution > 0) 750 | { 751 | uint32_t maxDutyCycle = (1 << channels_resolution[channel]) - 1; 752 | // Serial.printf("channel=%d,maxDutyCycle=%ld, channel resolution=%d\n", channel, maxDutyCycle, channels_resolution[channel]); 753 | *originalValue = ledcRead(channel); 754 | // Serial.printf("originalValue = %ld\n", *originalValue); 755 | return map(*originalValue, 0, maxDutyCycle, 0, 255); 756 | } 757 | return 0; 758 | } 759 | int readGPIO(int gpioNum, uint32_t *originalValue, pinTypes *pintype) 760 | { 761 | int channel = getLedcChannelForPin(gpioNum); 762 | int value; 763 | if (channel != -1) 764 | { 765 | // This is a PWM Pin 766 | value = mapLedcReadTo8Bit(gpioNum, channel, originalValue); 767 | *pintype = PWMPin; 768 | return value; 769 | } 770 | uint8_t analogChannel = analogGetChannel(gpioNum); 771 | if (analogChannel != 0 && analogChannel != 255) 772 | { 773 | value = mapLedcReadTo8Bit(gpioNum, analogChannel, originalValue); 774 | *pintype = analogPin; 775 | return value; 776 | } 777 | else 778 | { 779 | // This is a digital pin 780 | *pintype = digitalPin; 781 | value = digitalRead(gpioNum); 782 | *originalValue = value; 783 | if (value == 1) 784 | { 785 | return 256; 786 | } 787 | return 0; 788 | } 789 | } 790 | #endif 791 | 792 | void sendGPIOStates(const String &states) 793 | { 794 | events->send(states.c_str(), "gpio-state", millis()); 795 | } 796 | 797 | String formatBytes(size_t bytes) 798 | { 799 | if (bytes < 1024) 800 | { 801 | return String(bytes) + " B"; 802 | } 803 | else if (bytes < (1024 * 1024)) 804 | { 805 | return String(bytes / 1024.0, 2) + " KB"; 806 | } 807 | else 808 | { 809 | return String(bytes / 1024.0 / 1024.0, 2) + " MB"; 810 | } 811 | } 812 | 813 | #ifndef NO_PIN_FUNCTIONS 814 | void sendPinFunctions(AsyncWebServerRequest *request) 815 | { 816 | String jsonResponse = "{\"boardpinsfunction\":["; 817 | 818 | // ADC pins 819 | startPinFunction("ADC", &jsonResponse); 820 | for (int i = 0; i < ADCPinsCount; i++) 821 | { 822 | addPinFunction("ADC", ADCPins[i], &jsonResponse); 823 | } 824 | endPinFunction(&jsonResponse); 825 | 826 | // Touch pins 827 | jsonResponse += ","; 828 | startPinFunction("Touch", &jsonResponse); 829 | for (int i = 0; i < TouchPinsCount; i++) 830 | { 831 | addPinFunction("Touch", TouchPins[i], &jsonResponse); 832 | } 833 | endPinFunction(&jsonResponse); 834 | 835 | jsonResponse += "]}"; 836 | 837 | request->send(200, "application/json", jsonResponse); 838 | } 839 | 840 | void startPinFunction(const char *pinFunction, String *json) 841 | { 842 | *json += "{\"name\":\"" + String(pinFunction) + "\", \"functions\":["; 843 | } 844 | 845 | void addPinFunction(const char *pinName, int pin, String *json) 846 | { 847 | if (!json->endsWith("[")) 848 | { 849 | *json += ","; 850 | } 851 | 852 | *json += "{\"function\":\"" + String(pinName) + "\",\"pin\":" + String(pin) + "}"; 853 | } 854 | 855 | void endPinFunction(String *json) 856 | { 857 | *json += "]}"; 858 | } 859 | #endif 860 | }; --------------------------------------------------------------------------------