├── .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 |
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 | };
--------------------------------------------------------------------------------