first commit
This commit is contained in:
		
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| config.h | ||||
| .pio | ||||
| .vscode/.browse.c_cpp.db* | ||||
| .vscode/c_cpp_properties.json | ||||
| .vscode/launch.json | ||||
| .vscode/ipch | ||||
							
								
								
									
										10
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| { | ||||
|     // See http://go.microsoft.com/fwlink/?LinkId=827846 | ||||
|     // for the documentation about the extensions.json format | ||||
|     "recommendations": [ | ||||
|         "platformio.platformio-ide" | ||||
|     ], | ||||
|     "unwantedRecommendations": [ | ||||
|         "ms-vscode.cpptools-extension-pack" | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								hardware/Case/FlashButton.stl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								hardware/Case/FlashButton.stl
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								hardware/Case/SmartCube_Back_Speaker_Side_Hole.stl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								hardware/Case/SmartCube_Back_Speaker_Side_Hole.stl
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								hardware/Case/SmartCube_Body.stl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								hardware/Case/SmartCube_Body.stl
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								hardware/Case/SmartCube_Front.stl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								hardware/Case/SmartCube_Front.stl
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								hardware/Schematics/eoaza0nnhgq91.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								hardware/Schematics/eoaza0nnhgq91.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.4 MiB | 
							
								
								
									
										
											BIN
										
									
								
								hardware/case/SmartCube_Back_Speaker_Side_Hole.stl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								hardware/case/SmartCube_Back_Speaker_Side_Hole.stl
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								hardware/case/SmartCube_Body_3_button.stl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								hardware/case/SmartCube_Body_3_button.stl
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								hardware/case/SmartCube_Front.stl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								hardware/case/SmartCube_Front.stl
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								hardware/schematics/esp8266.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								hardware/schematics/esp8266.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 619 KiB | 
							
								
								
									
										39
									
								
								include/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								include/README
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
|  | ||||
| This directory is intended for project header files. | ||||
|  | ||||
| A header file is a file containing C declarations and macro definitions | ||||
| to be shared between several project source files. You request the use of a | ||||
| header file in your project source file (C, C++, etc) located in `src` folder | ||||
| by including it, with the C preprocessing directive `#include'. | ||||
|  | ||||
| ```src/main.c | ||||
|  | ||||
| #include "header.h" | ||||
|  | ||||
| int main (void) | ||||
| { | ||||
|  ... | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Including a header file produces the same results as copying the header file | ||||
| into each source file that needs it. Such copying would be time-consuming | ||||
| and error-prone. With a header file, the related declarations appear | ||||
| in only one place. If they need to be changed, they can be changed in one | ||||
| place, and programs that include the header file will automatically use the | ||||
| new version when next recompiled. The header file eliminates the labor of | ||||
| finding and changing all the copies as well as the risk that a failure to | ||||
| find one copy will result in inconsistencies within a program. | ||||
|  | ||||
| In C, the usual convention is to give header files names that end with `.h'. | ||||
| It is most portable to use only letters, digits, dashes, and underscores in | ||||
| header file names, and at most one dot. | ||||
|  | ||||
| Read more about using header files in official GCC documentation: | ||||
|  | ||||
| * Include Syntax | ||||
| * Include Operation | ||||
| * Once-Only Headers | ||||
| * Computed Includes | ||||
|  | ||||
| https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html | ||||
							
								
								
									
										46
									
								
								lib/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								lib/README
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
|  | ||||
| This directory is intended for project specific (private) libraries. | ||||
| PlatformIO will compile them to static libraries and link into executable file. | ||||
|  | ||||
| The source code of each library should be placed in an own separate directory | ||||
| ("lib/your_library_name/[here are source files]"). | ||||
|  | ||||
| For example, see a structure of the following two libraries `Foo` and `Bar`: | ||||
|  | ||||
| |--lib | ||||
| |  | | ||||
| |  |--Bar | ||||
| |  |  |--docs | ||||
| |  |  |--examples | ||||
| |  |  |--src | ||||
| |  |     |- Bar.c | ||||
| |  |     |- Bar.h | ||||
| |  |  |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html | ||||
| |  | | ||||
| |  |--Foo | ||||
| |  |  |- Foo.c | ||||
| |  |  |- Foo.h | ||||
| |  | | ||||
| |  |- README --> THIS FILE | ||||
| | | ||||
| |- platformio.ini | ||||
| |--src | ||||
|    |- main.c | ||||
|  | ||||
| and a contents of `src/main.c`: | ||||
| ``` | ||||
| #include <Foo.h> | ||||
| #include <Bar.h> | ||||
|  | ||||
| int main (void) | ||||
| { | ||||
|   ... | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| PlatformIO Library Dependency Finder will find automatically dependent | ||||
| libraries scanning project source files. | ||||
|  | ||||
| More information about PlatformIO Library Dependency Finder | ||||
| - https://docs.platformio.org/page/librarymanager/ldf.html | ||||
							
								
								
									
										19
									
								
								platformio.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								platformio.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| ; PlatformIO Project Configuration File | ||||
| ; | ||||
| ;   Build options: build flags, source filter | ||||
| ;   Upload options: custom upload port, speed and extra flags | ||||
| ;   Library options: dependencies, extra library storages | ||||
| ;   Advanced options: extra scripting | ||||
| ; | ||||
| ; Please visit documentation for the other options and examples | ||||
| ; https://docs.platformio.org/page/projectconf.html | ||||
|  | ||||
| [env:d1_mini_lite] | ||||
| platform = espressif8266 | ||||
| board = d1_mini_lite | ||||
| framework = arduino | ||||
| ; board_build.f_cpu = 160000000L  ; uncomment to set CPU frequency to 160 MHz | ||||
| lib_deps =  | ||||
| 	bblanchon/ArduinoJson@^7.2.0 | ||||
| 	adafruit/Adafruit SSD1306@^2.5.13 | ||||
| 	arduino-libraries/NTPClient@^3.2.1 | ||||
							
								
								
									
										18
									
								
								src/SmartCube/cubeBattery.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/SmartCube/cubeBattery.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #define ADC_PIN 10  // GPIO10 on WEMOS S2 Mini | ||||
|  | ||||
| const float R1 = 100000.0; // 100k ohms | ||||
| const float R2 = 47000.0;  // 47k ohms | ||||
| const float ADC_MAX = 4095.0; // 12-bit ADC resolution | ||||
| const float V_REF = 3.3;    // Reference voltage for ESP32-S2 ADC | ||||
|  | ||||
| float readBatteryVoltage() { | ||||
|   int rawADC = analogRead(ADC_PIN); | ||||
|   float voltage = (rawADC / ADC_MAX) * V_REF; | ||||
|   return voltage * (R1 + R2) / R2; // Scale to actual battery voltage | ||||
| } | ||||
|  | ||||
| int batteryPercentage(float voltage) { | ||||
|   if (voltage >= 4.2) return 100; | ||||
|   if (voltage <= 3.0) return 0; | ||||
|   return (voltage - 3.0) * 100 / (4.2 - 3.0); // Linear mapping | ||||
| } | ||||
							
								
								
									
										105
									
								
								src/SmartCube/cubeButtons.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/SmartCube/cubeButtons.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| extern Adafruit_SSD1306 display;    // Reference to the OLED display object | ||||
| bool is_display_off = false;        // State variable to track display on/off status | ||||
|  | ||||
| int buttonDebounceDelay = 50;       // Debounce delay in milliseconds | ||||
| int buttonLongPressDelay = 2000;    // Long press threshold in milliseconds | ||||
|  | ||||
| void cubeButtonHandler() { | ||||
|     unsigned long currentMillis = millis(); // Get the current time in milliseconds | ||||
|  | ||||
|     // Track the start times of button presses | ||||
|     static unsigned long leftPressStart = 0; | ||||
|     static unsigned long middlePressStart = 0; | ||||
|     static unsigned long rightPressStart = 0; | ||||
|  | ||||
|     // Track if a beep has already been played for each button to avoid repeated beeping | ||||
|     static bool leftBeeped = false; | ||||
|     static bool middleBeeped = false; | ||||
|     static bool rightBeeped = false; | ||||
|  | ||||
|     // Check if each button is currently pressed | ||||
|     bool leftPressed = (digitalRead(PIN_BTN_L) == HIGH); | ||||
|     bool middlePressed = (digitalRead(PIN_BTN_M) == HIGH); | ||||
|     bool rightPressed = (digitalRead(PIN_BTN_R) == HIGH); | ||||
|  | ||||
|     // Short press detection for left button | ||||
|     if (leftPressed) { | ||||
|         // Check if the button is held beyond the debounce delay | ||||
|         if ((currentMillis - leftPressStart > buttonDebounceDelay)) { | ||||
|             if (!leftBeeped) {  | ||||
|                 beep(1000);  // Play beep for a short press | ||||
|                 leftBeeped = true; // Mark as beeped to avoid repeat beeps | ||||
|                 // Handle left short press action here | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         leftPressStart = currentMillis;  // Reset timer when button is released | ||||
|         leftBeeped = false;              // Reset beep flag for next press | ||||
|     } | ||||
|  | ||||
|     // Short press detection for middle button | ||||
|     if (middlePressed) { | ||||
|         if ((currentMillis - middlePressStart > buttonDebounceDelay)) { | ||||
|             if (!middleBeeped) { | ||||
|                 beep(1000);  // Play beep for a short press | ||||
|                 middleBeeped = true; // Mark as beeped to avoid repeat beeps | ||||
|                 // Handle middle short press action here | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         middlePressStart = currentMillis;  // Reset timer when button is released | ||||
|         middleBeeped = false;              // Reset beep flag for next press | ||||
|     } | ||||
|  | ||||
|     // Short press detection for right button | ||||
|     if (rightPressed) { | ||||
|         if ((currentMillis - rightPressStart > buttonDebounceDelay)) { | ||||
|             if (!rightBeeped) { | ||||
|                 beep(1000);  // Play beep for a short press | ||||
|                 rightBeeped = true; // Mark as beeped to avoid repeat beeps | ||||
|                 if (is_display_off) {  | ||||
|                     display.ssd1306_command(SSD1306_DISPLAYON); // Turn on the display | ||||
|                     is_display_off = false;    // Update display state | ||||
|                     beep(1300);  // Additional beep to confirm display is on | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         rightPressStart = currentMillis;  // Reset timer when button is released | ||||
|         rightBeeped = false;              // Reset beep flag for next press | ||||
|     } | ||||
|  | ||||
|     // Long press detection for left button | ||||
|     if (leftPressed && (currentMillis - leftPressStart > buttonLongPressDelay)) { | ||||
|         if (!leftBeeped) { | ||||
|             beep(1000);  // Play beep for a long press | ||||
|             leftBeeped = true; // Mark as beeped to avoid repeat beeps | ||||
|             // Handle left long press action here | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Long press detection for middle button | ||||
|     if (middlePressed && (currentMillis - middlePressStart > buttonLongPressDelay)) { | ||||
|         if (!middleBeeped) { | ||||
|             beep(1000);  // Play beep for a long press | ||||
|             middleBeeped = true; // Mark as beeped to avoid repeat beeps | ||||
|             // Handle middle long press action here | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Long press detection for right button (with display control) | ||||
|     if (rightPressed && (currentMillis - rightPressStart > buttonLongPressDelay)) { | ||||
|         if (!is_display_off) { // Turn off the display if it's on | ||||
|             beep(1300); // Beep to indicate display turn-off | ||||
|             display.ssd1306_command(SSD1306_DISPLAYOFF); // Turn off the display | ||||
|             is_display_off = true;     // Update display state | ||||
|             beep(1000);  // Additional beep to confirm display is off | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Combined long press detection for left and middle buttons to restart device | ||||
|     if (leftPressed && middlePressed &&  | ||||
|         (currentMillis - leftPressStart > buttonLongPressDelay) && (currentMillis - middlePressStart > buttonLongPressDelay)) { | ||||
|             ESP.restart();  // Restart device if both buttons are held long enough | ||||
|     } | ||||
| } | ||||
							
								
								
									
										5
									
								
								src/SmartCube/cubeSound.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/SmartCube/cubeSound.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| void beep(int buzz) { | ||||
|     tone(PIN_BUZZER, buzz, 100); // Generate a tone on the buzzer at the specified frequency for 100 ms | ||||
|     delay(100);                  // Wait for the tone to finish playing | ||||
|     noTone(PIN_BUZZER);          // Stop the tone on the buzzer | ||||
| } | ||||
							
								
								
									
										336
									
								
								src/SmartCube/cubeWifiManager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								src/SmartCube/cubeWifiManager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,336 @@ | ||||
| #include <ESP8266WiFi.h> | ||||
| #include <WiFiClient.h> | ||||
| #include <ESP8266WebServer.h> | ||||
| #include <LittleFS.h> | ||||
| #include <DNSServer.h> | ||||
| #include <map> | ||||
|  | ||||
| // Constants for the HTML pages and config file | ||||
| static const String beginHtml = "<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'><title>SmartCube Configure</title><style>body{font-family:Arial,sans-serif;background-color:#f4f4f9;color:#333;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}.container{width:100%;max-width:300px;padding:20px;background:#fff;border-radius:10px;box-shadow:0 4px 8px rgba(0,0,0,0.2);box-sizing:border-box}h2{margin-top:0;text-align:center;color:#0073e6}label{display:block;margin-bottom:5px;font-weight:bold}input[type='text'],input[type='password']{width:100%;padding:8px;margin-bottom:15px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box}button{width:100%;padding:10px;background-color:#0073e6;color:white;border:none;border-radius:4px;cursor:pointer;font-size:16px}button:hover{background-color:#005bb5}table{width:100%;margin-top:20px}td{padding:5px;text-align:left}</style></head><body><div class='container'><h2>Configure AP</h2><table><tbody><tr><td><label for='ssid'>SSID</label></td><td><input id='ssid' type='text' placeholder='Enter SSID'/></td></tr><tr><td><label for='pass'>Password</label></td><td><input id='pass' type='password' placeholder='Enter Password'/></td></tr><tr><td colspan='2'><button onclick=\"location.href = '/add?ssid=' + encodeURIComponent(document.getElementById('ssid').value) + '&pass=' + encodeURIComponent(document.getElementById('pass').value);\">Add Network</button></td></tr></tbody></table><br/><table><tbody>"; | ||||
| static const String endHtml = "</tbody></table></body></html>"; | ||||
| static const String configFile = "/cubeWifiManager"; | ||||
|  | ||||
| // Timeout for WiFi connection attempts | ||||
| static const unsigned long connectionTimeout = 15000; // 15 seconds | ||||
| class cubeWifiManager { | ||||
| public: | ||||
|     // Constructors | ||||
|     cubeWifiManager(Adafruit_SSD1306& display); | ||||
|     cubeWifiManager(String ssid, String pass, bool hidden, Adafruit_SSD1306& display); | ||||
|  | ||||
|     // Public methods | ||||
|     bool start(); | ||||
|     void reset(); | ||||
|     void addSsid(String ssid, String password); | ||||
|     void removeSsid(String ssid, String password); | ||||
|      | ||||
| private: | ||||
|     // Private members | ||||
|     Adafruit_SSD1306& display; | ||||
|     std::unique_ptr<ESP8266WebServer> server; | ||||
|     std::map<String, String> _ssids; | ||||
|     String _ssid, _pass; | ||||
|     bool _hidden; | ||||
|      | ||||
|     // Private methods | ||||
|     void init(String ssid, String pass, bool hidden); | ||||
|     bool tryConnectToSsid(const char* ssid, const char* pass); | ||||
|     bool tryConnect(); | ||||
|     void createAP(); | ||||
|     bool redirectToIp(); | ||||
|     void readConfig(); | ||||
|     void writeConfig(); | ||||
|     void handleRoot(); | ||||
|     void handleAdd(); | ||||
|     void handleRemove(); | ||||
|     void handleSelect(); | ||||
| }; | ||||
|  | ||||
| // Initialization | ||||
| void cubeWifiManager::init(String ssid, String pass, bool hidden) { | ||||
|     // Ensure password meets minimum length | ||||
|     if (pass != "" && pass.length() < 8) { | ||||
|             display.clearDisplay(); | ||||
|             display.setCursor(0, 0); | ||||
|             display.println("Password too short"); | ||||
|             display.display();  | ||||
|             pass = "8characters"; | ||||
|     } | ||||
|      | ||||
|     // Get the last 4 characters of the MAC address | ||||
|     String macAddress = WiFi.macAddress(); | ||||
|     String macSuffix = macAddress.substring(macAddress.length() - 5);  | ||||
|     macSuffix.replace(":", ""); | ||||
|  | ||||
|     // Set default SSID if none provided | ||||
|     _ssid = ssid.isEmpty() ? "SmartCube_" + macSuffix : ssid; | ||||
|     _pass = pass; | ||||
|     _hidden = hidden; | ||||
|  | ||||
|  | ||||
|     // Initialize LittleFS with error handling | ||||
|     if (!LittleFS.begin()) { | ||||
|         display.clearDisplay(); | ||||
|         display.setCursor(0, 0); | ||||
|         display.println("FS init failed"); | ||||
|         display.display();  | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Constructors | ||||
| cubeWifiManager::cubeWifiManager(Adafruit_SSD1306& display) : display(display) { | ||||
|     init("", "", false); | ||||
| } | ||||
|  | ||||
| cubeWifiManager::cubeWifiManager(String ssid, String pass, bool hidden, Adafruit_SSD1306& display) : display(display) { | ||||
|     init(ssid, pass, hidden); | ||||
| } | ||||
|  | ||||
| // Attempt to start and connect or create AP | ||||
| bool cubeWifiManager::start() { | ||||
|     if (_pass == "8characters") { | ||||
|         display.clearDisplay(); | ||||
|         display.setCursor(0, 0); | ||||
|         display.println("Using default pass:"); | ||||
|         display.println("8characters"); | ||||
|         display.display();  | ||||
|     } | ||||
|     return tryConnect() || (createAP(), false); | ||||
| } | ||||
|  | ||||
| // Attempt connection to each saved SSID in order | ||||
| bool cubeWifiManager::tryConnect() { | ||||
|     readConfig(); | ||||
|     for (auto const& item : _ssids) { | ||||
|         if (tryConnectToSsid(item.first.c_str(), item.second.c_str())) { | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| // Attempt to connect to a specific SSID with timeout, dot animation, and IP display | ||||
| bool cubeWifiManager::tryConnectToSsid(const char* ssid, const char* pass) { | ||||
|     WiFi.begin(ssid, pass); | ||||
|     unsigned long start = millis(); | ||||
|  | ||||
|     // Clear display and set initial message | ||||
|     display.clearDisplay(); | ||||
|     display.setCursor(0, 0); | ||||
|     display.println("Connecting to WiFi:"); | ||||
|     display.setCursor(0, 11); | ||||
|     display.println(String(ssid)); | ||||
|     display.display(); | ||||
|  | ||||
|     int dotCount = 0; | ||||
|     while (millis() - start < connectionTimeout) { | ||||
|         delay(500); | ||||
|  | ||||
|         // Check WiFi connection status | ||||
|         if (WiFi.status() == WL_CONNECTED) { | ||||
|             // Success message with IP address | ||||
|             display.clearDisplay(); | ||||
|             display.setCursor(0, 0); | ||||
|             display.println("Connected!"); | ||||
|             display.setCursor(0, 12); | ||||
|             display.print("IP: "); | ||||
|             display.println(WiFi.localIP()); | ||||
|             display.display(); | ||||
|             delay(500); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // Animate by adding dots | ||||
|         display.setCursor(0, 20); | ||||
|         for (int i = 0; i < dotCount; i++) { | ||||
|             display.print("."); | ||||
|         } | ||||
|         display.display(); | ||||
|         dotCount = (dotCount + 1); | ||||
|     } | ||||
|  | ||||
|     // Connection failed | ||||
|     display.clearDisplay(); | ||||
|     display.setCursor(0, 0); | ||||
|     display.println("Connection failed."); | ||||
|     display.display(); | ||||
|     WiFi.disconnect(); | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| // Setup Access Point with DNS and HTTP server | ||||
| void cubeWifiManager::createAP() { | ||||
|     WiFi.softAPdisconnect(true); | ||||
|     WiFi.mode(WIFI_AP); | ||||
|     WiFi.softAP(_ssid.c_str(), _pass.c_str(), 1, _hidden); | ||||
|  | ||||
|     display.clearDisplay(); | ||||
|     display.setCursor(0, 0); | ||||
|     display.println("AccessPoint created"); | ||||
|     display.setCursor(0, 14); | ||||
|     display.println("SSID:"); | ||||
|     display.setCursor(0, 24); | ||||
|     display.setTextSize(1); | ||||
|     display.println(_ssid.c_str()); | ||||
|     display.setTextSize(1); | ||||
|     display.setCursor(0, 40); | ||||
|     display.println("Config portal:"); | ||||
|     display.setCursor(0, 50); | ||||
|     display.print("http://"); | ||||
|     display.println(WiFi.softAPIP().toString()); | ||||
|     display.display();  | ||||
|  | ||||
|     server.reset(new ESP8266WebServer(80)); | ||||
|     DNSServer dnsServer; | ||||
|     dnsServer.start(53, "*", WiFi.softAPIP()); | ||||
|  | ||||
|     server->on("/", std::bind(&cubeWifiManager::handleRoot, this)); | ||||
|     server->on("/add", std::bind(&cubeWifiManager::handleAdd, this)); | ||||
|     server->on("/remove", std::bind(&cubeWifiManager::handleRemove, this)); | ||||
|     server->on("/select", std::bind(&cubeWifiManager::handleSelect, this)); | ||||
|  | ||||
|     server->begin(); | ||||
|  | ||||
|     while (true) { | ||||
|         dnsServer.processNextRequest(); | ||||
|         server->handleClient(); | ||||
|         delay(10); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Redirect to AP IP if not accessed directly | ||||
| bool cubeWifiManager::redirectToIp() { | ||||
|     if (server->hostHeader() == WiFi.softAPIP().toString()) { | ||||
|         return false; | ||||
|     } | ||||
|     server->sendHeader("Location", "http://" + WiFi.softAPIP().toString(), true); | ||||
|     server->send(302, "text/plain", ""); | ||||
|     server->client().stop(); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| // Modify the addSsid function to take parameters from the `select` page | ||||
| void cubeWifiManager::addSsid(String ssid, String password) { | ||||
|     _ssids[ssid] = password; | ||||
|     writeConfig(); | ||||
|  | ||||
|     // Attempt to connect to the selected network | ||||
|     if (tryConnectToSsid(ssid.c_str(), password.c_str())) { | ||||
|         // Redirect to the main page on success | ||||
|         server->sendHeader("Location", "/", true); | ||||
|         server->send(302, "text/plain", ""); | ||||
|     } else { | ||||
|         // Show error message if connection failed | ||||
|         server->send(200, "text/html", "<html><body><h2>Connection failed. Please try again.</h2></body></html>"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Remove SSID from config | ||||
| void cubeWifiManager::removeSsid(String ssid, String password) { | ||||
|     if (_ssids.count(ssid) && _ssids[ssid] == password) { | ||||
|         _ssids.erase(ssid); | ||||
|         writeConfig(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Handle file-based config loading | ||||
| void cubeWifiManager::readConfig() { | ||||
|     _ssids.clear(); | ||||
|     File file = LittleFS.open(configFile, "r"); | ||||
|     if (!file) { | ||||
|         display.clearDisplay(); | ||||
|         display.setCursor(0, 0); | ||||
|         display.println("Config not found"); | ||||
|         display.display();  | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     while (file.available()) { | ||||
|         String ssid = file.readStringUntil('\n'); | ||||
|         ssid.trim(); | ||||
|         String pass = file.readStringUntil('\n'); | ||||
|         pass.trim(); | ||||
|         _ssids[ssid] = pass; | ||||
|     } | ||||
|     file.close(); | ||||
| } | ||||
|  | ||||
| // Handle file-based config saving | ||||
| void cubeWifiManager::writeConfig() { | ||||
|     File file = LittleFS.open(configFile, "w"); | ||||
|     for (const auto& item : _ssids) { | ||||
|         file.println(item.first); | ||||
|         file.println(item.second); | ||||
|     } | ||||
|     file.close(); | ||||
| } | ||||
|  | ||||
| // Reset configuration | ||||
| void cubeWifiManager::reset() { | ||||
|     LittleFS.remove(configFile); | ||||
|     _ssids.clear(); | ||||
| } | ||||
|  | ||||
| String urlEncode(const String &str) { | ||||
|     String encoded = ""; | ||||
|     char c; | ||||
|     for (size_t i = 0; i < str.length(); i++) { | ||||
|         c = str.charAt(i); | ||||
|         if (isalnum(c)) { | ||||
|             encoded += c; | ||||
|         } else { | ||||
|             encoded += '%'; | ||||
|             char buf[3]; | ||||
|             snprintf(buf, sizeof(buf), "%02X", c); | ||||
|             encoded += buf; | ||||
|         } | ||||
|     } | ||||
|     return encoded; | ||||
| } | ||||
|  | ||||
| void cubeWifiManager::handleRoot() { | ||||
|     if (redirectToIp()) return; | ||||
|  | ||||
|     // Scan for available networks | ||||
|     int n = WiFi.scanNetworks(); | ||||
|     String result = beginHtml; | ||||
|  | ||||
|     // Add stored SSIDs to the page | ||||
|     result += "<h3>Saved Networks</h3>"; | ||||
|     for (const auto& item : _ssids) { | ||||
|         result += "<tr><td><button onclick=\"location.href='/remove?ssid=' + escape('" + item.first + "') + '&pass=' + escape('" + item.second + "') \">×</button></td><td>" + item.first + "</td><td>-</td><td>" + item.second + "</td></tr>"; | ||||
|     } | ||||
|  | ||||
|     // Display available WiFi networks | ||||
|     result += "</tbody></table><h3>Available Networks</h3><table><tbody>"; | ||||
|     for (int i = 0; i < n; i++) { | ||||
|         // Get SSID and signal strength | ||||
|         String ssid = WiFi.SSID(i); | ||||
|         int rssi = WiFi.RSSI(i); | ||||
|         bool openNetwork = (WiFi.encryptionType(i) == ENC_TYPE_NONE); | ||||
|  | ||||
|         // Show network with button to select | ||||
|         result += "<tr><td><button onclick=\"location.href='/select?ssid=" + urlEncode(ssid) + "'\">" + ssid + "</button></td><td>" + (openNetwork ? "(Open)" : "(Secured)") + "</td><td>" + String(rssi) + " dBm</td></tr>"; | ||||
|     } | ||||
|     result += endHtml; | ||||
|     server->send(200, "text/html", result); | ||||
| } | ||||
|  | ||||
| void cubeWifiManager::handleAdd() { | ||||
|     server->send(200, "text/html", "The ESP will now reboot."); | ||||
|     addSsid(server->arg("ssid"), server->arg("pass")); | ||||
|     delay(500); | ||||
|     ESP.restart(); | ||||
| } | ||||
|  | ||||
| void cubeWifiManager::handleRemove() { | ||||
|     removeSsid(server->arg("ssid"), server->arg("pass")); | ||||
|     handleRoot(); | ||||
| } | ||||
|  | ||||
| // Add SSID to config and save | ||||
| void cubeWifiManager::handleSelect() { | ||||
|     String ssid = server->arg("ssid"); | ||||
|     String selectPage = "<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'><title>Connect to " + ssid + "</title><style>body{font-family:Arial,sans-serif;background-color:#f4f4f9;color:#333;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}.container{width:100%;max-width:300px;padding:20px;background:#fff;border-radius:10px;box-shadow:0 4px 8px rgba(0,0,0,0.2);box-sizing:border-box}h2{margin-top:0;text-align:center;color:#0073e6}label{display:block;margin-bottom:5px;font-weight:bold}input[type='password']{width:100%;padding:8px;margin-bottom:15px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box}button{width:100%;padding:10px;background-color:#0073e6;color:white;border:none;border-radius:4px;cursor:pointer;font-size:16px}button:hover{background-color:#005bb5}</style></head><body><div class='container'><h2>Connect to " + ssid + "</h2><form action='/add' method='get'><input type='hidden' name='ssid' value='" + ssid + "'><label for='pass'>Password:</label><input id='pass' type='password' name='pass' placeholder='Enter Password'><button type='submit'>Connect</button></form></div></body></html>"; | ||||
|     server->send(200, "text/html", selectPage); | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/example_config.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/example_config.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| // Zabbix API details | ||||
| const char* zabbixServer = "http://your.zabbix.com/api_jsonrpc.php"; | ||||
| const char* zabbixToken = "Pu770k3nH3r3"; | ||||
|  | ||||
| // OLED display settings | ||||
| #define SCREEN_WIDTH 128 | ||||
| #define SCREEN_HEIGHT 64 | ||||
| #define OLED_RESET -1 | ||||
| #define FRAMERATE 8 | ||||
|  | ||||
| // Buttons and buzzer | ||||
| #define PIN_BTN_L 12 // D6 | ||||
| #define PIN_BTN_M 13 // D7 | ||||
| #define PIN_BTN_R 15 // D8 | ||||
| #define PIN_BUZZER 0 // D3 | ||||
							
								
								
									
										189
									
								
								src/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								src/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | ||||
| #include <Arduino.h> | ||||
| #include <Adafruit_SSD1306.h> | ||||
| #include <ESP8266HTTPClient.h> | ||||
| #include <ArduinoJson.h> | ||||
| #include "example_config.h" | ||||
| #include "SmartCube/cubeSound.h" | ||||
| #include "SmartCube/cubeButtons.h" | ||||
| #include "SmartCube/cubeWifiManager.h" | ||||
|  | ||||
| Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); | ||||
| cubeWifiManager cubeWifiManager(display); | ||||
|  | ||||
| unsigned long lastRefresh = 0; | ||||
| const unsigned long refreshInterval = 60000; // 60 seconds | ||||
| unsigned long lastDisplayUpdate = 0; | ||||
| const unsigned long displayInterval = 100; // 100 ms | ||||
|  | ||||
| String lastEventId = ""; | ||||
| String problems[10]; | ||||
| int problemCount = 0; | ||||
| int severityCounts[5] = {0}; | ||||
| bool newProblemsDetected = false; | ||||
|  | ||||
| void initSystems() { | ||||
|   pinMode(PIN_BTN_L, INPUT); | ||||
|   pinMode(PIN_BTN_M, INPUT); | ||||
|   pinMode(PIN_BTN_R, INPUT); | ||||
|   pinMode(PIN_BUZZER, OUTPUT); | ||||
|   pinMode(LED_BUILTIN, OUTPUT); | ||||
|   digitalWrite(LED_BUILTIN, HIGH); | ||||
|   Wire.begin();             // Initialize I2C | ||||
|   Wire.setClock(400000L);    // Set I2C to Fast Mode (400 kHz) | ||||
|   display.ssd1306_command(SSD1306_SETCONTRAST); | ||||
|   display.ssd1306_command(200);  // Value between 0 and 255 | ||||
|   // Initialize the SSD1306 display | ||||
|   if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Adjust I2C address if needed | ||||
|     for(;;); // Don't proceed, loop forever | ||||
|   } | ||||
|  | ||||
|   // Rotate the display 180 degrees | ||||
|   display.setRotation(2); | ||||
|  | ||||
|   // Clear the display buffer | ||||
|   display.clearDisplay(); | ||||
|   display.setTextSize(1);       | ||||
|   display.setTextColor(WHITE); | ||||
|   display.setCursor(0, 0); | ||||
|   display.display(); | ||||
| } | ||||
|  | ||||
| void fetchActiveProblems() { | ||||
|   WiFiClient client; | ||||
|   HTTPClient http; | ||||
|   http.begin(client, zabbixServer); | ||||
|   http.addHeader("Content-Type", "application/json"); | ||||
|  | ||||
|   // JSON payload | ||||
|   DynamicJsonDocument doc(512); | ||||
|   doc["jsonrpc"] = "2.0"; | ||||
|   doc["method"] = "problem.get"; | ||||
|   doc["id"] = 1; | ||||
|   doc["auth"] = zabbixToken; | ||||
|   JsonObject params = doc["params"].to<JsonObject>(); | ||||
|   params["output"] = "extend"; | ||||
|   params["recent"] = true; | ||||
|   params["sortfield"] = "eventid"; | ||||
|   params["sortorder"] = "DESC"; | ||||
|  | ||||
|   String requestBody; | ||||
|   serializeJson(doc, requestBody); | ||||
|  | ||||
|   // POST request | ||||
|   int httpResponseCode = http.POST(requestBody); | ||||
|  | ||||
|   if (httpResponseCode > 0) { | ||||
|     String response = http.getString(); | ||||
|  | ||||
|     // Parse response | ||||
|     DynamicJsonDocument responseDoc(4096); | ||||
|     deserializeJson(responseDoc, response); | ||||
|      | ||||
|     JsonArray result = responseDoc["result"].as<JsonArray>(); | ||||
|     int newProblemCount = 0; | ||||
|     memset(severityCounts, 0, sizeof(severityCounts)); // Reset severities | ||||
|  | ||||
|     for (JsonObject problem : result) { | ||||
|       String eventId = problem["eventid"].as<String>(); | ||||
|       int severity = problem["severity"].as<int>(); | ||||
|       String description = problem["name"].as<String>(); | ||||
|  | ||||
|       // Check for new problems by comparing event IDs as integers | ||||
|       if (eventId.toInt() > lastEventId.toInt()) { | ||||
|         newProblemsDetected = true; | ||||
|       } | ||||
|  | ||||
|       // Add new problem descriptions to the array | ||||
|       if (newProblemCount < 10) { | ||||
|         problems[newProblemCount] = description; | ||||
|       } | ||||
|  | ||||
|       // Count severities | ||||
|       if (severity >= 0 && severity < 5) { | ||||
|         severityCounts[severity]++; | ||||
|       } | ||||
|  | ||||
|       newProblemCount++; | ||||
|     } | ||||
|  | ||||
|     problemCount = newProblemCount; | ||||
|  | ||||
|     // Update lastEventId after processing the most recent event | ||||
|     if (problemCount > 0) { | ||||
|       lastEventId = result[0]["eventid"].as<String>(); | ||||
|     } | ||||
|   } else { | ||||
|     beep(2000); | ||||
|   } | ||||
|  | ||||
|   http.end(); | ||||
| } | ||||
|  | ||||
| void displayProblems() { | ||||
|   static int scrollPos = SCREEN_WIDTH; | ||||
|   static unsigned long lastScrollTime = 0; | ||||
|  | ||||
|   if (millis() - lastDisplayUpdate < displayInterval) { | ||||
|     return; // Skip rendering if the display interval hasn't elapsed | ||||
|   } | ||||
|   lastDisplayUpdate = millis(); | ||||
|  | ||||
|   display.clearDisplay(); | ||||
|  | ||||
|   // Header | ||||
|   display.setTextSize(1); | ||||
|   display.setCursor(0, 0); | ||||
|   display.print("Problems: "); | ||||
|   display.print(problemCount); | ||||
|  | ||||
|   // Display severity counts (S0 to S4) | ||||
|   for (int i = 0; i < 5; i++) { | ||||
|     display.setCursor(0, 10 + (i * 10)); | ||||
|     display.printf("S%d: %d", i, severityCounts[i]); | ||||
|   } | ||||
|  | ||||
|   // Scrolling description of the first problem | ||||
|   if (problemCount > 0) { | ||||
|     String problemText = problems[0]; | ||||
|     int16_t x1, y1; | ||||
|     uint16_t textWidth, textHeight; | ||||
|     display.getTextBounds(problemText, 0, 55, &x1, &y1, &textWidth, &textHeight); | ||||
|  | ||||
|     if (millis() - lastScrollTime > 100) { | ||||
|       scrollPos -= 2; | ||||
|       if (scrollPos < -textWidth) { | ||||
|         scrollPos = SCREEN_WIDTH; | ||||
|       } | ||||
|       lastScrollTime = millis(); | ||||
|     } | ||||
|  | ||||
|     display.setTextSize(1); | ||||
|     display.setCursor(scrollPos, 55); | ||||
|     display.print(problemText); | ||||
|   } | ||||
|  | ||||
|   display.display(); | ||||
| } | ||||
|  | ||||
| void setup() { | ||||
|   initSystems(); | ||||
|   cubeWifiManager.start(); | ||||
|   fetchActiveProblems(); | ||||
| } | ||||
|  | ||||
| void loop() { | ||||
|   unsigned long currentMillis = millis(); | ||||
|   cubeButtonHandler(); | ||||
|  | ||||
|   if (currentMillis - lastRefresh >= refreshInterval) { | ||||
|     lastRefresh = currentMillis; | ||||
|     fetchActiveProblems(); | ||||
|   } | ||||
|  | ||||
|   displayProblems(); | ||||
|  | ||||
|   if (newProblemsDetected) { | ||||
|     beep(1000); | ||||
|     newProblemsDetected = false; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										333
									
								
								src/netman.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								src/netman.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,333 @@ | ||||
| #include <ESP8266WiFi.h> | ||||
| #include <WiFiClient.h> | ||||
| #include <ESP8266WebServer.h> | ||||
| #include <Adafruit_SSD1306.h> | ||||
| #include <LittleFS.h> | ||||
| #include <DNSServer.h> | ||||
| #include <map> | ||||
|  | ||||
| // Constants for the HTML pages and config file | ||||
| static const String beginHtml = "<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'><title>SmartCube Configure</title><style>body{font-family:Arial,sans-serif;background-color:#f4f4f9;color:#333;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}.container{width:100%;max-width:300px;padding:20px;background:#fff;border-radius:10px;box-shadow:0 4px 8px rgba(0,0,0,0.2);box-sizing:border-box}h2{margin-top:0;text-align:center;color:#0073e6}label{display:block;margin-bottom:5px;font-weight:bold}input[type='text'],input[type='password']{width:100%;padding:8px;margin-bottom:15px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box}button{width:100%;padding:10px;background-color:#0073e6;color:white;border:none;border-radius:4px;cursor:pointer;font-size:16px}button:hover{background-color:#005bb5}table{width:100%;margin-top:20px}td{padding:5px;text-align:left}</style></head><body><div class='container'><h2>Configure AP</h2><table><tbody><tr><td><label for='ssid'>SSID</label></td><td><input id='ssid' type='text' placeholder='Enter SSID'/></td></tr><tr><td><label for='pass'>Password</label></td><td><input id='pass' type='password' placeholder='Enter Password'/></td></tr><tr><td colspan='2'><button onclick=\"location.href = '/add?ssid=' + encodeURIComponent(document.getElementById('ssid').value) + '&pass=' + encodeURIComponent(document.getElementById('pass').value);\">Add Network</button></td></tr></tbody></table><br/><table><tbody>"; | ||||
| static const String endHtml = "</tbody></table></body></html>"; | ||||
| static const String configFile = "/netman"; | ||||
|  | ||||
| // Timeout for WiFi connection attempts | ||||
| static const unsigned long connectionTimeout = 15000; // 15 seconds | ||||
|  | ||||
| struct netman { | ||||
| public: | ||||
|     netman(Adafruit_SSD1306& display); | ||||
|     netman(String ssid, String pass, bool hidden, Adafruit_SSD1306& display); | ||||
|     bool start(); | ||||
|     void reset(); | ||||
|     void addSsid(String ssid, String password); | ||||
|     void removeSsid(String ssid, String password); | ||||
|      | ||||
| private: | ||||
|     Adafruit_SSD1306& display; | ||||
|     std::unique_ptr<ESP8266WebServer> server; | ||||
|     std::map<String, String> _ssids; | ||||
|     String _ssid, _pass; | ||||
|     bool _hidden; | ||||
|      | ||||
|     void init(String ssid, String pass, bool hidden); | ||||
|     bool tryConnectToSsid(const char* ssid, const char* pass); | ||||
|     bool tryConnect(); | ||||
|     void createAP(); | ||||
|     bool redirectToIp(); | ||||
|     void readConfig(); | ||||
|     void writeConfig(); | ||||
|     void handleRoot(); | ||||
|     void handleAdd(); | ||||
|     void handleRemove(); | ||||
|     void handleSelect(); | ||||
| }; | ||||
|  | ||||
| // Initialization | ||||
| void netman::init(String ssid, String pass, bool hidden) { | ||||
|     // Ensure password meets minimum length | ||||
|     if (pass != "" && pass.length() < 8) { | ||||
|             display.clearDisplay(); | ||||
|             display.setCursor(0, 0); | ||||
|             display.println("Password too short"); | ||||
|             display.display();  | ||||
|             pass = "8characters"; | ||||
|     } | ||||
|      | ||||
|     // Get the last 4 characters of the MAC address | ||||
|     String macAddress = WiFi.macAddress(); | ||||
|     String macSuffix = macAddress.substring(macAddress.length() - 5);  | ||||
|     macSuffix.replace(":", ""); | ||||
|  | ||||
|     // Set default SSID if none provided | ||||
|     _ssid = ssid.isEmpty() ? "SmartCube_" + macSuffix : ssid; | ||||
|     _pass = pass; | ||||
|     _hidden = hidden; | ||||
|  | ||||
|  | ||||
|     // Initialize LittleFS with error handling | ||||
|     if (!LittleFS.begin()) { | ||||
|         display.clearDisplay(); | ||||
|         display.setCursor(0, 0); | ||||
|         display.println("FS init failed"); | ||||
|         display.display();  | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Constructors | ||||
| netman::netman(Adafruit_SSD1306& display) : display(display) { | ||||
|     init("", "", false); | ||||
| } | ||||
|  | ||||
| netman::netman(String ssid, String pass, bool hidden, Adafruit_SSD1306& display) : display(display) { | ||||
|     init(ssid, pass, hidden); | ||||
| } | ||||
|  | ||||
| // Attempt to start and connect or create AP | ||||
| bool netman::start() { | ||||
|     if (_pass == "8characters") { | ||||
|         display.clearDisplay(); | ||||
|         display.setCursor(0, 0); | ||||
|         display.println("Using default pass:"); | ||||
|         display.println("8characters"); | ||||
|         display.display();  | ||||
|     } | ||||
|     return tryConnect() || (createAP(), false); | ||||
| } | ||||
|  | ||||
| // Attempt connection to each saved SSID in order | ||||
| bool netman::tryConnect() { | ||||
|     readConfig(); | ||||
|     for (auto const& item : _ssids) { | ||||
|         if (tryConnectToSsid(item.first.c_str(), item.second.c_str())) { | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| // Attempt to connect to a specific SSID with timeout, dot animation, and IP display | ||||
| bool netman::tryConnectToSsid(const char* ssid, const char* pass) { | ||||
|     WiFi.begin(ssid, pass); | ||||
|     unsigned long start = millis(); | ||||
|  | ||||
|     // Clear display and set initial message | ||||
|     display.clearDisplay(); | ||||
|     display.setCursor(0, 0); | ||||
|     display.println("Connecting to WiFi:"); | ||||
|     display.setCursor(0, 11); | ||||
|     display.println(String(ssid)); | ||||
|     display.display(); | ||||
|  | ||||
|     int dotCount = 0; | ||||
|     while (millis() - start < connectionTimeout) { | ||||
|         delay(500); | ||||
|  | ||||
|         // Check WiFi connection status | ||||
|         if (WiFi.status() == WL_CONNECTED) { | ||||
|             // Success message with IP address | ||||
|             display.clearDisplay(); | ||||
|             display.setCursor(0, 0); | ||||
|             display.println("Connected!"); | ||||
|             display.setCursor(0, 12); | ||||
|             display.print("IP: "); | ||||
|             display.println(WiFi.localIP()); | ||||
|             display.display(); | ||||
|             delay(500); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // Animate by adding dots | ||||
|         display.setCursor(0, 20); | ||||
|         for (int i = 0; i < dotCount; i++) { | ||||
|             display.print("."); | ||||
|         } | ||||
|         display.display(); | ||||
|         dotCount = (dotCount + 1); | ||||
|     } | ||||
|  | ||||
|     // Connection failed | ||||
|     display.clearDisplay(); | ||||
|     display.setCursor(0, 0); | ||||
|     display.println("Connection failed."); | ||||
|     display.display(); | ||||
|     WiFi.disconnect(); | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| // Setup Access Point with DNS and HTTP server | ||||
| void netman::createAP() { | ||||
|     WiFi.softAPdisconnect(true); | ||||
|     WiFi.mode(WIFI_AP); | ||||
|     WiFi.softAP(_ssid.c_str(), _pass.c_str(), 1, _hidden); | ||||
|  | ||||
|     display.clearDisplay(); | ||||
|     display.setCursor(0, 0); | ||||
|     display.println("AccessPoint created"); | ||||
|     display.setCursor(0, 14); | ||||
|     display.println("SSID:"); | ||||
|     display.setCursor(0, 24); | ||||
|     display.setTextSize(1); | ||||
|     display.println(_ssid.c_str()); | ||||
|     display.setTextSize(1); | ||||
|     display.setCursor(0, 40); | ||||
|     display.println("Config portal:"); | ||||
|     display.setCursor(0, 50); | ||||
|     display.print("http://"); | ||||
|     display.println(WiFi.softAPIP().toString()); | ||||
|     display.display();  | ||||
|  | ||||
|     server.reset(new ESP8266WebServer(80)); | ||||
|     DNSServer dnsServer; | ||||
|     dnsServer.start(53, "*", WiFi.softAPIP()); | ||||
|  | ||||
|     server->on("/", std::bind(&netman::handleRoot, this)); | ||||
|     server->on("/add", std::bind(&netman::handleAdd, this)); | ||||
|     server->on("/remove", std::bind(&netman::handleRemove, this)); | ||||
|     server->on("/select", std::bind(&netman::handleSelect, this)); | ||||
|  | ||||
|     server->begin(); | ||||
|  | ||||
|     while (true) { | ||||
|         dnsServer.processNextRequest(); | ||||
|         server->handleClient(); | ||||
|         delay(10); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Redirect to AP IP if not accessed directly | ||||
| bool netman::redirectToIp() { | ||||
|     if (server->hostHeader() == WiFi.softAPIP().toString()) { | ||||
|         return false; | ||||
|     } | ||||
|     server->sendHeader("Location", "http://" + WiFi.softAPIP().toString(), true); | ||||
|     server->send(302, "text/plain", ""); | ||||
|     server->client().stop(); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| // Modify the addSsid function to take parameters from the `select` page | ||||
| void netman::addSsid(String ssid, String password) { | ||||
|     _ssids[ssid] = password; | ||||
|     writeConfig(); | ||||
|  | ||||
|     // Attempt to connect to the selected network | ||||
|     if (tryConnectToSsid(ssid.c_str(), password.c_str())) { | ||||
|         // Redirect to the main page on success | ||||
|         server->sendHeader("Location", "/", true); | ||||
|         server->send(302, "text/plain", ""); | ||||
|     } else { | ||||
|         // Show error message if connection failed | ||||
|         server->send(200, "text/html", "<html><body><h2>Connection failed. Please try again.</h2></body></html>"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Remove SSID from config | ||||
| void netman::removeSsid(String ssid, String password) { | ||||
|     if (_ssids.count(ssid) && _ssids[ssid] == password) { | ||||
|         _ssids.erase(ssid); | ||||
|         writeConfig(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Handle file-based config loading | ||||
| void netman::readConfig() { | ||||
|     _ssids.clear(); | ||||
|     File file = LittleFS.open(configFile, "r"); | ||||
|     if (!file) { | ||||
|         display.clearDisplay(); | ||||
|         display.setCursor(0, 0); | ||||
|         display.println("Config not found"); | ||||
|         display.display();  | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     while (file.available()) { | ||||
|         String ssid = file.readStringUntil('\n'); | ||||
|         ssid.trim(); | ||||
|         String pass = file.readStringUntil('\n'); | ||||
|         pass.trim(); | ||||
|         _ssids[ssid] = pass; | ||||
|     } | ||||
|     file.close(); | ||||
| } | ||||
|  | ||||
| // Handle file-based config saving | ||||
| void netman::writeConfig() { | ||||
|     File file = LittleFS.open(configFile, "w"); | ||||
|     for (const auto& item : _ssids) { | ||||
|         file.println(item.first); | ||||
|         file.println(item.second); | ||||
|     } | ||||
|     file.close(); | ||||
| } | ||||
|  | ||||
| // Reset configuration | ||||
| void netman::reset() { | ||||
|     LittleFS.remove(configFile); | ||||
|     _ssids.clear(); | ||||
| } | ||||
|  | ||||
| String urlEncode(const String &str) { | ||||
|     String encoded = ""; | ||||
|     char c; | ||||
|     for (size_t i = 0; i < str.length(); i++) { | ||||
|         c = str.charAt(i); | ||||
|         if (isalnum(c)) { | ||||
|             encoded += c; | ||||
|         } else { | ||||
|             encoded += '%'; | ||||
|             char buf[3]; | ||||
|             snprintf(buf, sizeof(buf), "%02X", c); | ||||
|             encoded += buf; | ||||
|         } | ||||
|     } | ||||
|     return encoded; | ||||
| } | ||||
|  | ||||
| void netman::handleRoot() { | ||||
|     if (redirectToIp()) return; | ||||
|  | ||||
|     // Scan for available networks | ||||
|     int n = WiFi.scanNetworks(); | ||||
|     String result = beginHtml; | ||||
|  | ||||
|     // Add stored SSIDs to the page | ||||
|     result += "<h3>Saved Networks</h3>"; | ||||
|     for (const auto& item : _ssids) { | ||||
|         result += "<tr><td><button onclick=\"location.href='/remove?ssid=' + escape('" + item.first + "') + '&pass=' + escape('" + item.second + "') \">×</button></td><td>" + item.first + "</td><td>-</td><td>" + item.second + "</td></tr>"; | ||||
|     } | ||||
|  | ||||
|     // Display available WiFi networks | ||||
|     result += "</tbody></table><h3>Available Networks</h3><table><tbody>"; | ||||
|     for (int i = 0; i < n; i++) { | ||||
|         // Get SSID and signal strength | ||||
|         String ssid = WiFi.SSID(i); | ||||
|         int rssi = WiFi.RSSI(i); | ||||
|         bool openNetwork = (WiFi.encryptionType(i) == ENC_TYPE_NONE); | ||||
|  | ||||
|         // Show network with button to select | ||||
|         result += "<tr><td><button onclick=\"location.href='/select?ssid=" + urlEncode(ssid) + "'\">" + ssid + "</button></td><td>" + (openNetwork ? "(Open)" : "(Secured)") + "</td><td>" + String(rssi) + " dBm</td></tr>"; | ||||
|     } | ||||
|     result += endHtml; | ||||
|     server->send(200, "text/html", result); | ||||
| } | ||||
|  | ||||
| void netman::handleAdd() { | ||||
|     server->send(200, "text/html", "The ESP will now reboot."); | ||||
|     addSsid(server->arg("ssid"), server->arg("pass")); | ||||
|     delay(500); | ||||
|     ESP.restart(); | ||||
| } | ||||
|  | ||||
| void netman::handleRemove() { | ||||
|     removeSsid(server->arg("ssid"), server->arg("pass")); | ||||
|     handleRoot(); | ||||
| } | ||||
|  | ||||
| // Add SSID to config and save | ||||
| void netman::handleSelect() { | ||||
|     String ssid = server->arg("ssid"); | ||||
|     String selectPage = "<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'><title>Connect to " + ssid + "</title><style>body{font-family:Arial,sans-serif;background-color:#f4f4f9;color:#333;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}.container{width:100%;max-width:300px;padding:20px;background:#fff;border-radius:10px;box-shadow:0 4px 8px rgba(0,0,0,0.2);box-sizing:border-box}h2{margin-top:0;text-align:center;color:#0073e6}label{display:block;margin-bottom:5px;font-weight:bold}input[type='password']{width:100%;padding:8px;margin-bottom:15px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box}button{width:100%;padding:10px;background-color:#0073e6;color:white;border:none;border-radius:4px;cursor:pointer;font-size:16px}button:hover{background-color:#005bb5}</style></head><body><div class='container'><h2>Connect to " + ssid + "</h2><form action='/add' method='get'><input type='hidden' name='ssid' value='" + ssid + "'><label for='pass'>Password:</label><input id='pass' type='password' name='pass' placeholder='Enter Password'><button type='submit'>Connect</button></form></div></body></html>"; | ||||
|     server->send(200, "text/html", selectPage); | ||||
| } | ||||
							
								
								
									
										11
									
								
								test/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								test/README
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
|  | ||||
| This directory is intended for PlatformIO Test Runner and project tests. | ||||
|  | ||||
| Unit Testing is a software testing method by which individual units of | ||||
| source code, sets of one or more MCU program modules together with associated | ||||
| control data, usage procedures, and operating procedures, are tested to | ||||
| determine whether they are fit for use. Unit testing finds problems early | ||||
| in the development cycle. | ||||
|  | ||||
| More information about PlatformIO Unit Testing: | ||||
| - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html | ||||
		Reference in New Issue
	
	Block a user