├── controller ├── src │ ├── App.css │ ├── vite-env.d.ts │ ├── index.css │ ├── main.tsx │ ├── App.tsx │ ├── lib │ │ ├── drive.ts │ │ └── sensor-decode.ts │ ├── assets │ │ └── react.svg │ └── components │ │ ├── MotorControls.tsx │ │ ├── Joystick.tsx │ │ └── SensorDisplay.tsx ├── toESP32 │ └── index.html.gz ├── postcss.config.js ├── tsconfig.json ├── tailwind.config.js ├── .gitignore ├── index.html ├── vite.config.ts ├── tsconfig.node.json ├── tsconfig.app.json ├── eslint.config.js ├── package.json └── README.md ├── src ├── Config.cpp ├── secrets.h.sample ├── Config.h ├── WifiSetup.h └── main.cpp ├── lib ├── ESPAsyncWebSrv │ ├── keywords.txt │ ├── examples │ │ ├── ESP_AsyncFSBrowser │ │ │ ├── data │ │ │ │ ├── ace.js.gz │ │ │ │ ├── favicon.ico │ │ │ │ ├── mode-css.js.gz │ │ │ │ ├── mode-html.js.gz │ │ │ │ ├── worker-html.js.gz │ │ │ │ ├── ext-searchbox.js.gz │ │ │ │ ├── mode-javascript.js.gz │ │ │ │ └── index.htm │ │ │ └── ESP_AsyncFSBrowser.ino │ │ ├── CaptivePortal │ │ │ └── CaptivePortal.ino │ │ ├── simple_server │ │ │ └── simple_server.ino │ │ └── regex_patterns │ │ │ └── regex_patterns.ino │ ├── library.properties │ ├── README.md │ ├── src │ │ ├── SPIFFSEditor.h │ │ ├── WebAuthentication.h │ │ ├── AsyncWebSynchronization.h │ │ ├── AsyncEventSource.h │ │ ├── StringArray.h │ │ ├── WebResponseImpl.h │ │ ├── WebHandlerImpl.h │ │ ├── WebServer.cpp │ │ ├── WebAuthentication.cpp │ │ ├── WebHandlers.cpp │ │ ├── AsyncJson.h │ │ ├── AsyncEventSource.cpp │ │ ├── AsyncWebSocket.h │ │ └── edit.htm │ └── LICENSE ├── AsyncTCP │ ├── library.properties │ ├── README.md │ ├── LICENSE │ └── src │ │ └── AsyncTCP.h └── README ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── platformio.ini ├── compile_page_bytes.sh ├── include └── README ├── README.md └── .editorconfig /controller/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Config.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Config cfg; -------------------------------------------------------------------------------- /controller/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/secrets.h.sample: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIFI_PASSWORD "coolbeans" 4 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/keywords.txt: -------------------------------------------------------------------------------- 1 | JsonArray KEYWORD1 2 | add KEYWORD2 3 | createArray KEYWORD3 4 | -------------------------------------------------------------------------------- /controller/toESP32/index.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/roomba/HEAD/controller/toESP32/index.html.gz -------------------------------------------------------------------------------- /controller/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /controller/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | margin: 0; 7 | min-height: 100vh; 8 | } 9 | -------------------------------------------------------------------------------- /controller/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/ace.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/roomba/HEAD/lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/ace.js.gz -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/roomba/HEAD/lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/favicon.ico -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/mode-css.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/roomba/HEAD/lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/mode-css.js.gz -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/mode-html.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/roomba/HEAD/lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/mode-html.js.gz -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/worker-html.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/roomba/HEAD/lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/worker-html.js.gz -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/ext-searchbox.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/roomba/HEAD/lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/ext-searchbox.js.gz -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/mode-javascript.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/roomba/HEAD/lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/mode-javascript.js.gz -------------------------------------------------------------------------------- /controller/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import './index.css' 3 | import App from './App.tsx' 4 | 5 | createRoot(document.getElementById('root')!).render() 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | 7 | # Secrets file 8 | src/secrets.h 9 | 10 | .DS_Store 11 | js-src/ 12 | node_modules/ 13 | -------------------------------------------------------------------------------- /controller/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { RoombaController } from './components/controller' 2 | 3 | function App() { 4 | return ( 5 | <> 6 |
7 | 8 |
9 | 10 | ) 11 | } 12 | 13 | export default App 14 | -------------------------------------------------------------------------------- /controller/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } 12 | 13 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /lib/AsyncTCP/library.properties: -------------------------------------------------------------------------------- 1 | name=AsyncTCP 2 | version=1.1.4 3 | author=dvarrel 4 | maintainer=dvarrel 5 | sentence=Async TCP Library for ESP32. Forked from https://github.com/me-no-dev/AsyncTCP 6 | paragraph=to build a WebServer, with files saved in flash 7 | category=Communication 8 | url=https://github.com/dvarrel/AsyncTCP 9 | architectures=esp32 10 | -------------------------------------------------------------------------------- /controller/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /controller/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JavaScript Roomba 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /controller/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { viteSingleFile } from 'vite-plugin-singlefile' 3 | import react from '@vitejs/plugin-react' 4 | import { ViteMinifyPlugin } from 'vite-plugin-minify' 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | react(), 9 | viteSingleFile(), 10 | ViteMinifyPlugin({}) 11 | 12 | ], 13 | build: { 14 | outDir: './toESP32/' 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/library.properties: -------------------------------------------------------------------------------- 1 | name=ESPAsyncWebSrv 2 | version=1.2.6 3 | author=dvarrel 4 | maintainer=dvarrel 5 | sentence=Asynchronous HTTP and WebSocket Server Library for ESP8266 and ESP32 . Forked from https://github.com/me-no-dev/ESPAsyncWebServer 6 | paragraph=Build a WebServer, with files saved in flash 7 | category=Communication 8 | url=https://github.com/dvarrel/ESPAsyncWebSrv 9 | architectures=esp8266, esp32 10 | depends=AsyncTCP, ESPAsyncTCP -------------------------------------------------------------------------------- /lib/AsyncTCP/README.md: -------------------------------------------------------------------------------- 1 | ### Async TCP Library for ESP32 Arduino 2 | 3 | This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs. 4 | 5 | This library is the base for [ESPAsyncWebServer](https://github.com/dvarrel/ESPAsyncWebServer) 6 | 7 | ## AsyncClient and AsyncServer 8 | The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use. 9 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/README.md: -------------------------------------------------------------------------------- 1 | # ESPAsyncWebServer 2 | 3 | Async HTTP and WebSocket Server for ESP8266 Arduino 4 | 5 | For ESP8266 it requires [ESPAsyncTCP](https://github.com/dvarrel/ESPAsyncTCP) 6 | To use this library you might need to have the latest git versions of [ESP8266](https://github.com/esp8266/Arduino) Arduino Core 7 | 8 | For ESP32 it requires [AsyncTCP](https://github.com/dvarrel/AsyncTCP) to work 9 | To use this library you might need to have the latest git versions of [ESP32](https://github.com/espressif/arduino-esp32) Arduino Core 10 | 11 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32doit-devkit-v1] 12 | platform = espressif32 13 | board = esp32dev 14 | framework = arduino 15 | ; upload_speed = 115200 16 | monitor_speed = 115200 17 | -------------------------------------------------------------------------------- /compile_page_bytes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd ./controller/ 4 | 5 | # build options below for the JS page using vite, uncomment to enable options 6 | 7 | export VITE_APP_ON_DEVICE=1 8 | export COMPRESSION_ENABLED=TRUE # uses gz compression 9 | 10 | npm run build 11 | 12 | if [ ! -z "${COMPRESSION_ENABLED}" ]; then 13 | echo "Compressing index.html" 14 | gzip -c -9 toESP32/index.html > toESP32/index.html.gz 15 | xxd -i toESP32/index.html.gz > toESP32/index.h 16 | else 17 | echo "Not compressing index.html" 18 | xxd -i toESP32/index.html > toESP32/index.h 19 | fi 20 | 21 | echo "Adding const to index.h" 22 | sed -i '' '1s/^/const /' toESP32/index.h 23 | 24 | cp toESP32/index.h ../src/index.h 25 | -------------------------------------------------------------------------------- /controller/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /controller/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /controller/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/SPIFFSEditor.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIFFSEditor_H_ 2 | #define SPIFFSEditor_H_ 3 | #include 4 | 5 | class SPIFFSEditor: public AsyncWebHandler { 6 | private: 7 | fs::FS _fs; 8 | String _username; 9 | String _password; 10 | bool _authenticated; 11 | uint32_t _startTime; 12 | public: 13 | #ifdef ESP32 14 | SPIFFSEditor(const fs::FS& fs, const String& username=String(), const String& password=String()); 15 | #else 16 | SPIFFSEditor(const String& username=String(), const String& password=String(), const fs::FS& fs=SPIFFS); 17 | #endif 18 | virtual bool canHandle(AsyncWebServerRequest *request) override final; 19 | virtual void handleRequest(AsyncWebServerRequest *request) override final; 20 | virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final; 21 | virtual bool isRequestHandlerTrivial() override final {return false;} 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /controller/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roomba-controller", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.3.1", 14 | "react-dom": "^18.3.1", 15 | "vite-plugin-minify": "^2.1.0", 16 | "vite-plugin-singlefile": "^2.1.0" 17 | }, 18 | "devDependencies": { 19 | "@eslint/js": "^9.17.0", 20 | "@types/react": "^18.3.18", 21 | "@types/react-dom": "^18.3.5", 22 | "@vitejs/plugin-react": "^4.3.4", 23 | "autoprefixer": "^10.4.20", 24 | "eslint": "^9.17.0", 25 | "eslint-plugin-react-hooks": "^5.0.0", 26 | "eslint-plugin-react-refresh": "^0.4.16", 27 | "globals": "^15.14.0", 28 | "postcss": "^8.5.1", 29 | "tailwindcss": "^3.4.17", 30 | "typescript": "~5.6.2", 31 | "typescript-eslint": "^8.18.2", 32 | "vite": "^6.0.5" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "array": "cpp", 4 | "atomic": "cpp", 5 | "*.tcc": "cpp", 6 | "bitset": "cpp", 7 | "cctype": "cpp", 8 | "clocale": "cpp", 9 | "cmath": "cpp", 10 | "cstdarg": "cpp", 11 | "cstddef": "cpp", 12 | "cstdint": "cpp", 13 | "cstdio": "cpp", 14 | "cstdlib": "cpp", 15 | "cstring": "cpp", 16 | "ctime": "cpp", 17 | "cwchar": "cpp", 18 | "cwctype": "cpp", 19 | "deque": "cpp", 20 | "unordered_map": "cpp", 21 | "unordered_set": "cpp", 22 | "vector": "cpp", 23 | "exception": "cpp", 24 | "algorithm": "cpp", 25 | "functional": "cpp", 26 | "iterator": "cpp", 27 | "map": "cpp", 28 | "memory": "cpp", 29 | "memory_resource": "cpp", 30 | "numeric": "cpp", 31 | "optional": "cpp", 32 | "random": "cpp", 33 | "regex": "cpp", 34 | "string": "cpp", 35 | "string_view": "cpp", 36 | "system_error": "cpp", 37 | "tuple": "cpp", 38 | "type_traits": "cpp", 39 | "utility": "cpp", 40 | "fstream": "cpp", 41 | "initializer_list": "cpp", 42 | "iomanip": "cpp", 43 | "iosfwd": "cpp", 44 | "istream": "cpp", 45 | "limits": "cpp", 46 | "new": "cpp", 47 | "ostream": "cpp", 48 | "sstream": "cpp", 49 | "stdexcept": "cpp", 50 | "streambuf": "cpp", 51 | "cinttypes": "cpp", 52 | "typeinfo": "cpp" 53 | } 54 | } -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/examples/CaptivePortal/CaptivePortal.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef ESP32 3 | #include 4 | #include 5 | #elif defined(ESP8266) 6 | #include 7 | #include 8 | #endif 9 | #include "ESPAsyncWebSrv.h" 10 | 11 | DNSServer dnsServer; 12 | AsyncWebServer server(80); 13 | 14 | class CaptiveRequestHandler : public AsyncWebHandler { 15 | public: 16 | CaptiveRequestHandler() {} 17 | virtual ~CaptiveRequestHandler() {} 18 | 19 | bool canHandle(AsyncWebServerRequest *request){ 20 | //request->addInterestingHeader("ANY"); 21 | return true; 22 | } 23 | 24 | void handleRequest(AsyncWebServerRequest *request) { 25 | AsyncResponseStream *response = request->beginResponseStream("text/html"); 26 | response->print("Captive Portal"); 27 | response->print("

This is out captive portal front page.

"); 28 | response->printf("

You were trying to reach: http://%s%s

", request->host().c_str(), request->url().c_str()); 29 | response->printf("

Try opening this link instead

", WiFi.softAPIP().toString().c_str()); 30 | response->print(""); 31 | request->send(response); 32 | } 33 | }; 34 | 35 | 36 | void setup(){ 37 | //your other setup stuff... 38 | WiFi.softAP("esp-captive"); 39 | dnsServer.start(53, "*", WiFi.softAPIP()); 40 | server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP 41 | //more handlers... 42 | server.begin(); 43 | } 44 | 45 | void loop(){ 46 | dnsServer.processNextRequest(); 47 | } 48 | -------------------------------------------------------------------------------- /controller/src/lib/drive.ts: -------------------------------------------------------------------------------- 1 | export function drivePwm(rightPwm: number, leftPwm: number) { 2 | const rightPwmHighByte = (rightPwm >> 8) & 0xFF; 3 | const rightPwmLowByte = rightPwm & 0xFF; 4 | const leftPwmHighByte = (leftPwm >> 8) & 0xFF; 5 | const leftPwmLowByte = leftPwm & 0xFF; 6 | 7 | return [146, rightPwmHighByte, rightPwmLowByte, leftPwmHighByte, leftPwmLowByte]; 8 | } 9 | /** 10 | * This command controls Roomba’s drive wheels. It takes four data bytes, interpreted as two 16-bit signed 11 | values using two’s complement. (http://en.wikipedia.org/wiki/Two%27s_complement) The first two bytes 12 | specify the average velocity of the drive wheels in millimeters per second (mm/s), with the high byte 13 | being sent first. The next two bytes specify the radius in millimeters at which Roomba will turn. The 14 | longer radii make Roomba drive straighter, while the shorter radii make Roomba turn more. The radius is 15 | measured from the center of the turning circle to the center of Roomba. A Drive command with a 16 | positive velocity and a positive radius makes Roomba drive forward while turning toward the left. A 17 | negative radius makes Roomba turn toward the right. Special cases for the radius make Roomba turn in 18 | place or drive straight, as specified below. A negative velocity makes Roomba drive backward. 19 | */ 20 | export function drive(velocity: number, radius: number) { 21 | const velocityHighByte = (velocity >> 8) & 0xFF; 22 | const velocityLowByte = velocity & 0xFF; 23 | const radiusHighByte = (radius >> 8) & 0xFF; 24 | const radiusLowByte = radius & 0xFF; 25 | 26 | return [137, velocityHighByte, velocityLowByte, radiusHighByte, radiusLowByte]; 27 | } 28 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/WebAuthentication.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | 22 | #ifndef WEB_AUTHENTICATION_H_ 23 | #define WEB_AUTHENTICATION_H_ 24 | 25 | #include "Arduino.h" 26 | 27 | bool checkBasicAuthentication(const char * header, const char * username, const char * password); 28 | String requestDigestAuthentication(const char * realm); 29 | bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); 30 | 31 | //for storing hashed versions on the device that can be authenticated against 32 | String generateDigestHash(const char * username, const char * password, const char * realm); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /controller/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default tseslint.config({ 18 | languageOptions: { 19 | // other options... 20 | parserOptions: { 21 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 22 | tsconfigRootDir: import.meta.dirname, 23 | }, 24 | }, 25 | }) 26 | ``` 27 | 28 | - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` 29 | - Optionally add `...tseslint.configs.stylisticTypeChecked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: 31 | 32 | ```js 33 | // eslint.config.js 34 | import react from 'eslint-plugin-react' 35 | 36 | export default tseslint.config({ 37 | // Set the react version 38 | settings: { react: { version: '18.3' } }, 39 | plugins: { 40 | // Add the react plugin 41 | react, 42 | }, 43 | rules: { 44 | // other rules... 45 | // Enable its recommended rules 46 | ...react.configs.recommended.rules, 47 | ...react.configs['jsx-runtime'].rules, 48 | }, 49 | }) 50 | ``` 51 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/AsyncWebSynchronization.h: -------------------------------------------------------------------------------- 1 | #ifndef ASYNCWEBSYNCHRONIZATION_H_ 2 | #define ASYNCWEBSYNCHRONIZATION_H_ 3 | 4 | // Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default 5 | 6 | #include 7 | 8 | #ifdef ESP32 9 | 10 | // This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore 11 | class AsyncWebLock 12 | { 13 | private: 14 | SemaphoreHandle_t _lock; 15 | mutable void *_lockedBy; 16 | 17 | public: 18 | AsyncWebLock() { 19 | _lock = xSemaphoreCreateBinary(); 20 | _lockedBy = NULL; 21 | xSemaphoreGive(_lock); 22 | } 23 | 24 | ~AsyncWebLock() { 25 | vSemaphoreDelete(_lock); 26 | } 27 | 28 | bool lock() const { 29 | extern void *pxCurrentTCB; 30 | if (_lockedBy != pxCurrentTCB) { 31 | xSemaphoreTake(_lock, portMAX_DELAY); 32 | _lockedBy = pxCurrentTCB; 33 | return true; 34 | } 35 | return false; 36 | } 37 | 38 | void unlock() const { 39 | _lockedBy = NULL; 40 | xSemaphoreGive(_lock); 41 | } 42 | }; 43 | 44 | #else 45 | 46 | // This is the 8266 version of the Sync Lock which is currently unimplemented 47 | class AsyncWebLock 48 | { 49 | 50 | public: 51 | AsyncWebLock() { 52 | } 53 | 54 | ~AsyncWebLock() { 55 | } 56 | 57 | bool lock() const { 58 | return false; 59 | } 60 | 61 | void unlock() const { 62 | } 63 | }; 64 | #endif 65 | 66 | class AsyncWebLockGuard 67 | { 68 | private: 69 | const AsyncWebLock *_lock; 70 | 71 | public: 72 | AsyncWebLockGuard(const AsyncWebLock &l) { 73 | if (l.lock()) { 74 | _lock = &l; 75 | } else { 76 | _lock = NULL; 77 | } 78 | } 79 | 80 | ~AsyncWebLockGuard() { 81 | if (_lock) { 82 | _lock->unlock(); 83 | } 84 | } 85 | }; 86 | 87 | #endif // ASYNCWEBSYNCHRONIZATION_H_ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript Roomba 2 | 3 | A web-based controller for Roomba robot vacuums using an ESP32 microcontroller. This project combines hardware and software to create a custom interface for controlling your Roomba. 4 | 5 | ## Project Structure 6 | 7 | - `/src` - ESP32 firmware source code 8 | - `/controller` - React-based web interface 9 | 10 | ## Features 11 | 12 | - Web-based control interface for Roomba 13 | - ESP32 as the bridge between web interface and Roomba 14 | - Real-time control and monitoring 15 | - Custom movement patterns and commands 16 | 17 | ## Hardware Requirements 18 | 19 | - ESP32 development board 20 | - Roomba with serial interface 21 | - Appropriate cables to connect ESP32 to Roomba 22 | 23 | ## Software Requirements 24 | 25 | - PlatformIO for ESP32 firmware development 26 | - Node.js for web interface development - we use Vite to dev on the ESP32 locally 27 | 28 | ## Setup 29 | 30 | 1. Clone this repository 31 | 2. Install PlatformIO (for ESP32 firmware) 32 | 3. Install Node.js dependencies for the web interface 33 | 4. Upload firmware to ESP32 34 | 5. Connect ESP32 to your Roomba 35 | 6. Access the web interface through your browser 36 | 37 | ## Development 38 | 39 | ### ESP32 Firmware 40 | 41 | ```bash 42 | # Build firmware 43 | pio run 44 | 45 | # Upload to ESP32 46 | pio run --target upload 47 | ``` 48 | 49 | ### Web Interface 50 | 51 | ```bash 52 | # Install dependencies 53 | cd controller 54 | npm install 55 | 56 | # Start development server 57 | npm run dev 58 | ``` 59 | 60 | When you are ready to deploy the app, you can compile it into a single file, and then convert to byte code with `./compile_page_bytes.sh` 61 | 62 | Note - i've found that when I change the app - I need to run "clean" with Platform.io - otherwise I get a content length mismatch - likely doesn't re-upload the new binary for the web UI. 63 | 64 | ## Contributing 65 | 66 | Contributions are welcome! Please feel free to submit a Pull Request. 67 | 68 | ## Acknowledgments 69 | 70 | Initial code for the ESP32 wireless serial bridge comes from 71 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/examples/simple_server/simple_server.ino: -------------------------------------------------------------------------------- 1 | // 2 | // A simple server implementation showing how to: 3 | // * serve static messages 4 | // * read GET and POST parameters 5 | // * handle missing pages / 404s 6 | // 7 | 8 | #include 9 | #ifdef ESP32 10 | #include 11 | #include 12 | #elif defined(ESP8266) 13 | #include 14 | #include 15 | #endif 16 | #include 17 | 18 | AsyncWebServer server(80); 19 | 20 | const char* ssid = "YOUR_SSID"; 21 | const char* password = "YOUR_PASSWORD"; 22 | 23 | const char* PARAM_MESSAGE = "message"; 24 | 25 | void notFound(AsyncWebServerRequest *request) { 26 | request->send(404, "text/plain", "Not found"); 27 | } 28 | 29 | void setup() { 30 | 31 | Serial.begin(115200); 32 | WiFi.mode(WIFI_STA); 33 | WiFi.begin(ssid, password); 34 | if (WiFi.waitForConnectResult() != WL_CONNECTED) { 35 | Serial.printf("WiFi Failed!\n"); 36 | return; 37 | } 38 | 39 | Serial.print("IP Address: "); 40 | Serial.println(WiFi.localIP()); 41 | 42 | server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ 43 | request->send(200, "text/plain", "Hello, world"); 44 | }); 45 | 46 | // Send a GET request to /get?message= 47 | server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) { 48 | String message; 49 | if (request->hasParam(PARAM_MESSAGE)) { 50 | message = request->getParam(PARAM_MESSAGE)->value(); 51 | } else { 52 | message = "No message sent"; 53 | } 54 | request->send(200, "text/plain", "Hello, GET: " + message); 55 | }); 56 | 57 | // Send a POST request to /post with a form field message set to 58 | server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){ 59 | String message; 60 | if (request->hasParam(PARAM_MESSAGE, true)) { 61 | message = request->getParam(PARAM_MESSAGE, true)->value(); 62 | } else { 63 | message = "No message sent"; 64 | } 65 | request->send(200, "text/plain", "Hello, POST: " + message); 66 | }); 67 | 68 | server.onNotFound(notFound); 69 | 70 | server.begin(); 71 | } 72 | 73 | void loop() { 74 | } -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/examples/regex_patterns/regex_patterns.ino: -------------------------------------------------------------------------------- 1 | // 2 | // A simple server implementation with regex routes: 3 | // * serve static messages 4 | // * read GET and POST parameters 5 | // * handle missing pages / 404s 6 | // 7 | 8 | // Add buildflag ASYNCWEBSERVER_REGEX to enable the regex support 9 | 10 | // For platformio: platformio.ini: 11 | // build_flags = 12 | // -DASYNCWEBSERVER_REGEX 13 | 14 | // For arduino IDE: create/update platform.local.txt 15 | // Windows: C:\Users\(username)\AppData\Local\Arduino15\packages\espxxxx\hardware\espxxxx\{version}\platform.local.txt 16 | // Linux: ~/.arduino15/packages/espxxxx/hardware/espxxxx/{version}/platform.local.txt 17 | // 18 | // compiler.cpp.extra_flags=-DASYNCWEBSERVER_REGEX=1 19 | 20 | #include 21 | #ifdef ESP32 22 | #include 23 | #include 24 | #elif defined(ESP8266) 25 | #include 26 | #include 27 | #endif 28 | #include 29 | 30 | AsyncWebServer server(80); 31 | 32 | const char* ssid = "YOUR_SSID"; 33 | const char* password = "YOUR_PASSWORD"; 34 | 35 | const char* PARAM_MESSAGE = "message"; 36 | 37 | void notFound(AsyncWebServerRequest *request) { 38 | request->send(404, "text/plain", "Not found"); 39 | } 40 | 41 | void setup() { 42 | 43 | Serial.begin(115200); 44 | WiFi.mode(WIFI_STA); 45 | WiFi.begin(ssid, password); 46 | if (WiFi.waitForConnectResult() != WL_CONNECTED) { 47 | Serial.printf("WiFi Failed!\n"); 48 | return; 49 | } 50 | 51 | Serial.print("IP Address: "); 52 | Serial.println(WiFi.localIP()); 53 | 54 | server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ 55 | request->send(200, "text/plain", "Hello, world"); 56 | }); 57 | 58 | // Send a GET request to /sensor/ 59 | server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { 60 | String sensorNumber = request->pathArg(0); 61 | request->send(200, "text/plain", "Hello, sensor: " + sensorNumber); 62 | }); 63 | 64 | // Send a GET request to /sensor//action/ 65 | server.on("^\\/sensor\\/([0-9]+)\\/action\\/([a-zA-Z0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { 66 | String sensorNumber = request->pathArg(0); 67 | String action = request->pathArg(1); 68 | request->send(200, "text/plain", "Hello, sensor: " + sensorNumber + ", with action: " + action); 69 | }); 70 | 71 | server.onNotFound(notFound); 72 | 73 | server.begin(); 74 | } 75 | 76 | void loop() { 77 | } 78 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | cpp_indent_braces=false 3 | cpp_indent_multi_line_relative_to=innermost_parenthesis 4 | cpp_indent_within_parentheses=indent 5 | cpp_indent_preserve_within_parentheses=false 6 | cpp_indent_case_labels=false 7 | cpp_indent_case_contents=true 8 | cpp_indent_case_contents_when_block=false 9 | cpp_indent_lambda_braces_when_parameter=true 10 | cpp_indent_goto_labels=one_left 11 | cpp_indent_preprocessor=leftmost_column 12 | cpp_indent_access_specifiers=false 13 | cpp_indent_namespace_contents=true 14 | cpp_indent_preserve_comments=false 15 | cpp_new_line_before_open_brace_namespace=ignore 16 | cpp_new_line_before_open_brace_type=ignore 17 | cpp_new_line_before_open_brace_function=ignore 18 | cpp_new_line_before_open_brace_block=ignore 19 | cpp_new_line_before_open_brace_lambda=ignore 20 | cpp_new_line_scope_braces_on_separate_lines=true 21 | cpp_new_line_close_brace_same_line_empty_type=true 22 | cpp_new_line_close_brace_same_line_empty_function=true 23 | cpp_new_line_before_catch=true 24 | cpp_new_line_before_else=true 25 | cpp_new_line_before_while_in_do_while=false 26 | cpp_space_before_function_open_parenthesis=remove 27 | cpp_space_within_parameter_list_parentheses=false 28 | cpp_space_between_empty_parameter_list_parentheses=false 29 | cpp_space_after_keywords_in_control_flow_statements=true 30 | cpp_space_within_control_flow_statement_parentheses=false 31 | cpp_space_before_lambda_open_parenthesis=false 32 | cpp_space_within_cast_parentheses=false 33 | cpp_space_after_cast_close_parenthesis=false 34 | cpp_space_within_expression_parentheses=false 35 | cpp_space_before_block_open_brace=true 36 | cpp_space_between_empty_braces=false 37 | cpp_space_before_initializer_list_open_brace=false 38 | cpp_space_within_initializer_list_braces=true 39 | cpp_space_preserve_in_initializer_list=true 40 | cpp_space_before_open_square_bracket=false 41 | cpp_space_within_square_brackets=false 42 | cpp_space_before_empty_square_brackets=false 43 | cpp_space_between_empty_square_brackets=false 44 | cpp_space_group_square_brackets=true 45 | cpp_space_within_lambda_brackets=false 46 | cpp_space_between_empty_lambda_brackets=false 47 | cpp_space_before_comma=false 48 | cpp_space_after_comma=true 49 | cpp_space_remove_around_member_operators=true 50 | cpp_space_before_inheritance_colon=true 51 | cpp_space_before_constructor_colon=true 52 | cpp_space_remove_before_semicolon=true 53 | cpp_space_after_semicolon=false 54 | cpp_space_remove_around_unary_operator=true 55 | cpp_space_around_binary_operator=insert 56 | cpp_space_around_assignment_operator=insert 57 | cpp_space_pointer_reference_alignment=left 58 | cpp_space_around_ternary_operator=insert 59 | cpp_wrap_preserve_blocks=one_liners 60 | -------------------------------------------------------------------------------- /controller/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | // Add logging macros 8 | #define LOG_PREFIX "[ESP32] " 9 | #define LOG(msg) \ 10 | Serial.print(LOG_PREFIX); \ 11 | Serial.println(msg) 12 | #define LOG_F(msg, ...) \ 13 | { \ 14 | char buf[128]; \ 15 | snprintf(buf, sizeof(buf), msg, __VA_ARGS__); \ 16 | LOG(buf); \ 17 | } 18 | 19 | template 20 | struct StaticString 21 | { 22 | char data[size]; 23 | 24 | // Constructor to initialize the array with the given string 25 | StaticString(const char* str) 26 | { 27 | std::strncpy(data, str, size - 1); // Ensure no overflow 28 | data[size - 1] = '\0'; // Null-terminate 29 | } 30 | }; 31 | 32 | #include "secrets.h" // Include secrets file with sensitive data 33 | 34 | #define CONFIG_FIELDS(FIELD) \ 35 | FIELD(StaticString<100>, ssid_Router, "Bos"); \ 36 | FIELD(StaticString<100>, password_Router, WIFI_PASSWORD); \ 37 | FIELD(StaticString<100>, ssid_AP, "RoombaAP"); \ 38 | FIELD(StaticString<100>, password_AP, "sick"); \ 39 | FIELD(StaticString<100>, IP_AP, "192.168.0.1"); \ 40 | FIELD(StaticString<100>, hostname, "roomba-esp32") 41 | 42 | inline void writeByteIntoEEPROM(int address, uint8_t number) 43 | { 44 | if (EEPROM.read(address) != number) 45 | { 46 | EEPROM.write(address, number); 47 | LOG_F("EEPROM write at address %d: %u", address, number); 48 | } 49 | } 50 | 51 | inline uint8_t readByteFromEEPROM(int address) 52 | { 53 | uint8_t value = EEPROM.read(address); 54 | // LOG_F("EEPROM read at address %d: %u", address, value); 55 | return value; 56 | } 57 | 58 | // Macro to declare each field in the struct and to print the field 59 | #define DECLARE_STRUCT_FIELD(type, name, val) type name = val; 60 | #define PRINT_STRUCT_FIELD(type, name, val) \ 61 | s.print(#name); \ 62 | s.print(" = "); \ 63 | if (strcmp(#type, "double") == 0 || strcmp(#type, "float") == 0) \ 64 | s.println(this->name, 5); \ 65 | else \ 66 | s.println(this->name); 67 | 68 | struct Config 69 | { 70 | static const uint8_t requiredPreamble = 0xFC; 71 | uint8_t preamble; 72 | CONFIG_FIELDS(DECLARE_STRUCT_FIELD) 73 | 74 | template 75 | void dumpToSerial(SerialType& s) 76 | { 77 | LOG("Dumping configuration:"); 78 | CONFIG_FIELDS(PRINT_STRUCT_FIELD) 79 | } 80 | 81 | void save() 82 | { 83 | LOG("Saving configuration to EEPROM..."); 84 | preamble = requiredPreamble; 85 | const int classSize = sizeof(Config); 86 | uint8_t* data = (uint8_t*)this; 87 | 88 | for (int i = 0; i < classSize; i++) 89 | writeByteIntoEEPROM(i, data[i]); 90 | 91 | if (EEPROM.commit()) 92 | { 93 | LOG_F("Configuration saved and committed (%d bytes)", classSize); 94 | } 95 | else 96 | { 97 | LOG("Failed to commit configuration to EEPROM"); 98 | } 99 | } 100 | 101 | bool load() 102 | { 103 | LOG("Loading configuration from EEPROM..."); 104 | const int classSize = sizeof(Config); 105 | uint8_t* data = (uint8_t*)this; 106 | 107 | for (int i = 0; i < classSize; i++) 108 | data[i] = readByteFromEEPROM(i); 109 | 110 | if (preamble != requiredPreamble) 111 | { 112 | LOG("Configuration load failed: invalid preamble"); 113 | Config defaultSettings; 114 | *this = defaultSettings; 115 | LOG("Restored default settings"); 116 | return false; 117 | } 118 | 119 | LOG_F("Configuration loaded successfully (%d bytes)", classSize); 120 | return true; 121 | } 122 | }; 123 | 124 | extern Config cfg; 125 | 126 | #undef DECLARE_STRUCT_FIELD 127 | #undef PRINT_STRUCT_FIELD 128 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/data/index.htm: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | WebSocketTester 23 | 52 | 124 | 125 | 126 |

127 |     
128 | $ 129 |
130 | 131 | 132 | -------------------------------------------------------------------------------- /controller/src/components/MotorControls.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | interface MotorControlsProps { 4 | onSendCommand: (command: { commands: number[] }) => void; 5 | } 6 | 7 | export function MotorControls({ onSendCommand }: MotorControlsProps) { 8 | const [mainBrushOn, setMainBrushOn] = useState(false); 9 | const [mainBrushDirection, setMainBrushDirection] = useState(false); 10 | const [sideBrushOn, setSideBrushOn] = useState(false); 11 | const [sideBrushDirection, setSideBrushDirection] = useState(false); 12 | const [vacuumOn, setVacuumOn] = useState(false); 13 | const [motorByte, setMotorByte] = useState(0); 14 | 15 | useEffect(() => { 16 | // Build the motor control byte 17 | let motorByte = 0; 18 | 19 | // Side Brush (bit 0) 20 | if (sideBrushOn) motorByte |= 1; 21 | 22 | // Vacuum (bit 1) 23 | if (vacuumOn) motorByte |= 2; 24 | 25 | // Main Brush (bit 2) 26 | if (mainBrushOn) motorByte |= 4; 27 | 28 | // Side Brush Direction (bit 3) 29 | if (sideBrushDirection) motorByte |= 8; 30 | 31 | // Main Brush Direction (bit 4) 32 | if (mainBrushDirection) motorByte |= 16; 33 | 34 | setMotorByte(motorByte); 35 | onSendCommand({ commands: [138, motorByte] }); 36 | }, [mainBrushOn, mainBrushDirection, sideBrushOn, sideBrushDirection, vacuumOn]); 37 | 38 | 39 | return ( 40 |
41 |

Motor Controls (Byte: {motorByte} / 0b{motorByte.toString(2).padStart(8, '0')})

42 |
43 | {/* Bit 0: Side Brush */} 44 |
45 | 54 |
55 | 56 | {/* Bit 1: Vacuum */} 57 |
58 | 67 |
68 | 69 | {/* Bit 2: Main Brush */} 70 |
71 | 80 |
81 | 82 | {/* Bit 3: Side Brush Direction */} 83 |
84 | 93 |
94 | 95 | {/* Bit 4: Main Brush Direction */} 96 |
97 | 106 |
107 |
108 |
109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/AsyncEventSource.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | #ifndef ASYNCEVENTSOURCE_H_ 21 | #define ASYNCEVENTSOURCE_H_ 22 | 23 | #include 24 | #ifdef ESP32 25 | #include 26 | #define SSE_MAX_QUEUED_MESSAGES 32 27 | #else 28 | #include 29 | #define SSE_MAX_QUEUED_MESSAGES 8 30 | #endif 31 | #include 32 | 33 | #include "AsyncWebSynchronization.h" 34 | 35 | #ifdef ESP8266 36 | #include 37 | #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library 38 | #include <../src/Hash.h> 39 | #endif 40 | #endif 41 | 42 | #ifdef ESP32 43 | #define DEFAULT_MAX_SSE_CLIENTS 8 44 | #else 45 | #define DEFAULT_MAX_SSE_CLIENTS 4 46 | #endif 47 | 48 | class AsyncEventSource; 49 | class AsyncEventSourceResponse; 50 | class AsyncEventSourceClient; 51 | typedef std::function ArEventHandlerFunction; 52 | 53 | class AsyncEventSourceMessage { 54 | private: 55 | uint8_t * _data; 56 | size_t _len; 57 | size_t _sent; 58 | //size_t _ack; 59 | size_t _acked; 60 | public: 61 | AsyncEventSourceMessage(const char * data, size_t len); 62 | ~AsyncEventSourceMessage(); 63 | size_t ack(size_t len, uint32_t time __attribute__((unused))); 64 | size_t send(AsyncClient *client); 65 | bool finished(){ return _acked == _len; } 66 | bool sent() { return _sent == _len; } 67 | }; 68 | 69 | class AsyncEventSourceClient { 70 | private: 71 | AsyncClient *_client; 72 | AsyncEventSource *_server; 73 | uint32_t _lastId; 74 | LinkedList _messageQueue; 75 | void _queueMessage(AsyncEventSourceMessage *dataMessage); 76 | void _runQueue(); 77 | 78 | public: 79 | 80 | AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server); 81 | ~AsyncEventSourceClient(); 82 | 83 | AsyncClient* client(){ return _client; } 84 | void close(); 85 | void write(const char * message, size_t len); 86 | void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); 87 | bool connected() const { return (_client != NULL) && _client->connected(); } 88 | uint32_t lastId() const { return _lastId; } 89 | size_t packetsWaiting() const { return _messageQueue.length(); } 90 | 91 | //system callbacks (do not call) 92 | void _onAck(size_t len, uint32_t time); 93 | void _onPoll(); 94 | void _onTimeout(uint32_t time); 95 | void _onDisconnect(); 96 | }; 97 | 98 | class AsyncEventSource: public AsyncWebHandler { 99 | private: 100 | String _url; 101 | LinkedList _clients; 102 | ArEventHandlerFunction _connectcb; 103 | public: 104 | AsyncEventSource(const String& url); 105 | ~AsyncEventSource(); 106 | 107 | const char * url() const { return _url.c_str(); } 108 | void close(); 109 | void onConnect(ArEventHandlerFunction cb); 110 | void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); 111 | size_t count() const; //number clinets connected 112 | size_t avgPacketsWaiting() const; 113 | 114 | //system callbacks (do not call) 115 | void _addClient(AsyncEventSourceClient * client); 116 | void _handleDisconnect(AsyncEventSourceClient * client); 117 | virtual bool canHandle(AsyncWebServerRequest *request) override final; 118 | virtual void handleRequest(AsyncWebServerRequest *request) override final; 119 | }; 120 | 121 | class AsyncEventSourceResponse: public AsyncWebServerResponse { 122 | private: 123 | String _content; 124 | AsyncEventSource *_server; 125 | public: 126 | AsyncEventSourceResponse(AsyncEventSource *server); 127 | void _respond(AsyncWebServerRequest *request); 128 | size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 129 | bool _sourceValid() const { return true; } 130 | }; 131 | 132 | 133 | #endif /* ASYNCEVENTSOURCE_H_ */ 134 | -------------------------------------------------------------------------------- /controller/src/lib/sensor-decode.ts: -------------------------------------------------------------------------------- 1 | export interface SensorData { 2 | bump: { 3 | right?: boolean; 4 | left?: boolean; 5 | }; 6 | wheeldrop: { 7 | right?: boolean; 8 | left?: boolean; 9 | caster?: boolean; 10 | }; 11 | wall: boolean; 12 | cliff: { 13 | [key: number]: boolean; 14 | }; 15 | virtual_wall: boolean; 16 | overcurrent: { 17 | side_brush?: boolean; 18 | vacuum?: boolean; 19 | main_brush?: boolean; 20 | drive_right?: boolean; 21 | drive_left?: boolean; 22 | }; 23 | dirt: { 24 | left: number; 25 | right: number; 26 | }; 27 | remote: number; 28 | buttons: { 29 | max?: boolean; 30 | clean?: boolean; 31 | spot?: boolean; 32 | power?: boolean; 33 | }; 34 | distance: number; 35 | angle: number; 36 | battery: { 37 | charging_state: number; 38 | voltage: number; 39 | current: number; 40 | temp: number; 41 | level: number; 42 | capacity: number; 43 | }; 44 | } 45 | 46 | export const SENSOR_PKT_LEN = 25; 47 | 48 | const masks = { 49 | bump_wheeldrop: { 50 | BUMP_RIGHT: 0x01, 51 | BUMP_LEFT: 0x02, 52 | WHEELDROP_RIGHT: 0x04, 53 | WHEELDROP_LEFT: 0x08, 54 | WHEELDROP_CASTER: 0x10 55 | }, 56 | motor_overcurrent: { 57 | SIDEBRUSH: 0x01, 58 | VACUUM: 0x02, 59 | MAINBRUSH: 0x04, 60 | DRIVE_RIGHT: 0x08, 61 | DRIVE_LEFT: 0x10 62 | }, 63 | buttons: { 64 | MAX: 0x01, 65 | CLEAN: 0x02, 66 | SPOT: 0x04, 67 | POWER: 0x08 68 | } 69 | }; 70 | 71 | export function decode_sensors(buf: Uint8Array): SensorData { 72 | if (buf.length !== SENSOR_PKT_LEN) { 73 | throw new Error(`sensor packet must be ${SENSOR_PKT_LEN} bytes (got ${buf.length})`); 74 | } 75 | 76 | const msg: SensorData = { 77 | bump: {}, 78 | wheeldrop: {}, 79 | wall: false, 80 | cliff: {}, 81 | virtual_wall: false, 82 | overcurrent: {}, 83 | dirt: { left: 0, right: 0 }, 84 | remote: 0, 85 | buttons: {}, 86 | distance: 0, 87 | angle: 0, 88 | battery: { 89 | charging_state: 0, 90 | voltage: 0, 91 | current: 0, 92 | temp: 0, 93 | level: 0, 94 | capacity: 0 95 | } 96 | }; 97 | 98 | let pos = 0; 99 | let byte = 0x00; 100 | 101 | // bumper sensors 102 | byte = buf[pos++]; 103 | const bumpMask = masks.bump_wheeldrop; 104 | 105 | if (byte & bumpMask.BUMP_RIGHT) 106 | msg.bump.right = true; 107 | 108 | if (byte & bumpMask.BUMP_LEFT) 109 | msg.bump.left = true; 110 | 111 | // wheeldrop sensors 112 | if (byte & bumpMask.WHEELDROP_RIGHT) 113 | msg.wheeldrop.right = true; 114 | 115 | if (byte & bumpMask.WHEELDROP_LEFT) 116 | msg.wheeldrop.left = true; 117 | 118 | if (byte & bumpMask.WHEELDROP_CASTER) 119 | msg.wheeldrop.caster = true; 120 | 121 | // wall sensor 122 | byte = buf[pos++]; 123 | if (byte === 1) 124 | msg.wall = true; 125 | 126 | // cliff sensors 127 | for (let i = 0; i < 4; i++) { 128 | if (buf[pos++] === 1) { 129 | msg.cliff[i] = true; 130 | } else { 131 | msg.cliff[i] = false; 132 | } 133 | } 134 | 135 | // virtual wall sensor 136 | byte = buf[pos++]; 137 | if (byte === 1) 138 | msg.virtual_wall = true; 139 | 140 | // motor overcurrent sensors 141 | byte = buf[pos++]; 142 | const overcurrentMask = masks.motor_overcurrent; 143 | 144 | if (byte & overcurrentMask.SIDEBRUSH) 145 | msg.overcurrent.side_brush = true; 146 | 147 | if (byte & overcurrentMask.VACUUM) 148 | msg.overcurrent.vacuum = true; 149 | 150 | if (byte & overcurrentMask.MAINBRUSH) 151 | msg.overcurrent.main_brush = true; 152 | 153 | if (byte & overcurrentMask.DRIVE_RIGHT) 154 | msg.overcurrent.drive_right = true; 155 | 156 | if (byte & overcurrentMask.DRIVE_LEFT) 157 | msg.overcurrent.drive_left = true; 158 | 159 | // dirt detectors 160 | msg.dirt.left = buf[pos++]; 161 | msg.dirt.right = buf[pos++]; 162 | 163 | // remote control command receiver 164 | msg.remote = buf[pos++]; 165 | 166 | // button press detectors 167 | byte = buf[pos++]; 168 | const buttonMask = masks.buttons; 169 | 170 | if (byte & buttonMask.MAX) 171 | msg.buttons.max = true; 172 | 173 | if (byte & buttonMask.CLEAN) 174 | msg.buttons.clean = true; 175 | 176 | if (byte & buttonMask.SPOT) 177 | msg.buttons.spot = true; 178 | 179 | if (byte & buttonMask.POWER) 180 | msg.buttons.power = true; 181 | 182 | // distance traveled 183 | msg.distance = buf[pos]; pos += 2; 184 | 185 | // angle turned 186 | msg.angle = buf[pos]; pos += 2; 187 | 188 | // charging state 189 | msg.battery.charging_state = buf[pos++]; 190 | 191 | // voltage measurement 192 | msg.battery.voltage = buf[pos]; pos += 2; 193 | 194 | // current measurement 195 | msg.battery.current = buf[pos]; pos += 2; 196 | 197 | // temperature sensor 198 | msg.battery.temp = buf[pos++]; 199 | 200 | // charge level 201 | msg.battery.level = buf[pos]; pos += 2; 202 | 203 | // charge capacity 204 | msg.battery.capacity = buf[pos]; pos += 2; 205 | 206 | return msg; 207 | } 208 | 209 | -------------------------------------------------------------------------------- /src/WifiSetup.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Config.h" 9 | 10 | extern DynamicJsonDocument doc; 11 | 12 | bool WiFi_MODE = 1; 13 | 14 | IPAddress local_IP(10, 10, 0, 1); 15 | IPAddress gateway(10, 10, 0, 1); 16 | IPAddress subnet(255, 255, 255, 0); 17 | 18 | // Initialize WiFi function 19 | void WiFi_Setup() 20 | { 21 | LOG("Starting WiFi setup..."); 22 | cfg.load(); 23 | local_IP.fromString(cfg.IP_AP.data); 24 | gateway.fromString(cfg.IP_AP.data); 25 | unsigned long startTime = millis(); 26 | const unsigned long timeout = 15000; // 15 seconds in milliseconds 27 | // Set hostname for both STA and AP modes 28 | WiFi.setHostname(cfg.hostname.data); 29 | LOG_F("Set hostname to: %s", cfg.hostname.data); 30 | 31 | if (strcmp(cfg.ssid_Router.data, "") != 0) 32 | { 33 | LOG("Router SSID configured, attempting to connect in STA mode..."); 34 | WiFi.mode(WIFI_STA); 35 | if (strlen(cfg.password_Router.data) > 8) 36 | { 37 | WiFi.begin(cfg.ssid_Router.data, cfg.password_Router.data); 38 | } 39 | else 40 | { 41 | LOG_F("Connecting to open WiFi network: %s", cfg.ssid_Router.data); 42 | WiFi.begin(cfg.ssid_Router.data, nullptr); 43 | } 44 | WiFi.setSleep(false); 45 | WiFi.setAutoConnect(true); 46 | WiFi.setAutoReconnect(true); 47 | 48 | LOG("Waiting for WiFi connection..."); 49 | while (WiFi.status() != WL_CONNECTED) 50 | { 51 | LOG("."); 52 | if (millis() - startTime > timeout) 53 | { 54 | LOG("WiFi connection timed out"); 55 | break; 56 | } 57 | delay(100); 58 | } 59 | } 60 | 61 | if (WiFi.status() == WL_CONNECTED) 62 | { 63 | IPAddress local_ip = WiFi.localIP(); 64 | 65 | // Initialize mDNS when connected to WiFi 66 | if (MDNS.begin(cfg.hostname.data)) 67 | { 68 | MDNS.addService("http", "tcp", 80); 69 | LOG_F("mDNS responder started: %s.local", cfg.hostname.data); 70 | } 71 | else 72 | { 73 | LOG("Error setting up mDNS responder"); 74 | } 75 | } 76 | else 77 | { 78 | WiFi.disconnect(true); 79 | WiFi.mode(WIFI_AP); 80 | WiFi.softAPConfig(local_IP, gateway, subnet); 81 | if (strlen(cfg.password_AP.data) > 8) 82 | { 83 | WiFi.softAP(cfg.ssid_AP.data, cfg.password_AP.data); 84 | } 85 | else 86 | { 87 | WiFi.softAP(cfg.ssid_AP.data, nullptr); 88 | } 89 | } 90 | } 91 | 92 | void wifiSetupCallback(AsyncWebServerRequest* request, uint8_t* data, 93 | size_t len, size_t index, size_t total) 94 | { 95 | StaticJsonDocument<512> doc; 96 | DeserializationError error = deserializeJson(doc, (char*)data); 97 | 98 | if (error) 99 | { 100 | AsyncWebServerResponse* response = 101 | request->beginResponse(400, "text/plain", "Invalid JSON"); 102 | response->addHeader("Access-Control-Allow-Origin", "*"); 103 | request->send(response); 104 | return; 105 | } 106 | 107 | const char* ssid_AP = doc["ssid_AP"]; 108 | const char* password_AP = doc["password_AP"]; 109 | const char* ssid_Router = doc["ssid_Router"]; 110 | const char* password_Router = doc["password_Router"]; 111 | const char* ipAddr = doc["ipAddr"]; 112 | const char* hostname = doc["hostname"]; 113 | 114 | if (ssid_AP && password_AP && ssid_Router && password_Router && ipAddr) 115 | { 116 | cfg.password_AP = password_AP; 117 | cfg.password_Router = password_Router; 118 | cfg.ssid_AP = ssid_AP; 119 | cfg.ssid_Router = ssid_Router; 120 | cfg.IP_AP = ipAddr; 121 | if (hostname) 122 | { 123 | cfg.hostname = hostname; 124 | } 125 | cfg.save(); 126 | 127 | // Commit the changes to ensure they are saved 128 | if (!EEPROM.commit()) 129 | { 130 | AsyncWebServerResponse* response = request->beginResponse( 131 | 400, "text/plain", "Failed to commit EEPROM changes"); 132 | response->addHeader("Access-Control-Allow-Origin", "*"); 133 | request->send(response); 134 | Serial.println("Failed to commit EEPROM changes"); 135 | } 136 | else 137 | { 138 | AsyncWebServerResponse* response = request->beginResponse( 139 | 200, "application/json", "{\"status\":\"Network updated\"}"); 140 | response->addHeader("Access-Control-Allow-Origin", "*"); 141 | request->send(response); 142 | } 143 | } 144 | else 145 | { 146 | AsyncWebServerResponse* response = 147 | request->beginResponse(400, "text/plain", "Invalid parameters"); 148 | response->addHeader("Access-Control-Allow-Origin", "*"); 149 | request->send(response); 150 | } 151 | } 152 | 153 | void wifiGetConfigCallback(AsyncWebServerRequest* request) 154 | { 155 | cfg.load(); 156 | // Create a JSON document 157 | StaticJsonDocument<512> doc; 158 | 159 | // Add data from the cfg struct to the JSON document 160 | doc["ssid_AP"] = cfg.ssid_AP.data; 161 | doc["password_AP"] = cfg.password_AP.data; 162 | doc["ssid_Router"] = cfg.ssid_Router.data; 163 | doc["password_Router"] = cfg.password_Router.data; 164 | doc["ipAddr"] = cfg.IP_AP.data; 165 | doc["hostname"] = cfg.hostname.data; 166 | 167 | // Serialize JSON document to a string 168 | String responseString; 169 | serializeJson(doc, responseString); 170 | 171 | // Send the JSON response 172 | AsyncWebServerResponse* response = 173 | request->beginResponse(200, "application/json", responseString); 174 | response->addHeader("Access-Control-Allow-Origin", "*"); 175 | request->send(response); 176 | } 177 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/StringArray.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef STRINGARRAY_H_ 22 | #define STRINGARRAY_H_ 23 | 24 | #include "stddef.h" 25 | #include "WString.h" 26 | 27 | template 28 | class LinkedListNode { 29 | T _value; 30 | public: 31 | LinkedListNode* next; 32 | LinkedListNode(const T val): _value(val), next(nullptr) {} 33 | ~LinkedListNode(){} 34 | const T& value() const { return _value; }; 35 | T& value(){ return _value; } 36 | }; 37 | 38 | template class Item = LinkedListNode> 39 | class LinkedList { 40 | public: 41 | typedef Item ItemType; 42 | typedef std::function OnRemove; 43 | typedef std::function Predicate; 44 | private: 45 | ItemType* _root; 46 | OnRemove _onRemove; 47 | 48 | class Iterator { 49 | ItemType* _node; 50 | public: 51 | Iterator(ItemType* current = nullptr) : _node(current) {} 52 | Iterator(const Iterator& i) : _node(i._node) {} 53 | Iterator& operator ++() { _node = _node->next; return *this; } 54 | bool operator != (const Iterator& i) const { return _node != i._node; } 55 | const T& operator * () const { return _node->value(); } 56 | const T* operator -> () const { return &_node->value(); } 57 | }; 58 | 59 | public: 60 | typedef const Iterator ConstIterator; 61 | ConstIterator begin() const { return ConstIterator(_root); } 62 | ConstIterator end() const { return ConstIterator(nullptr); } 63 | 64 | LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {} 65 | ~LinkedList(){} 66 | void add(const T& t){ 67 | auto it = new ItemType(t); 68 | if(!_root){ 69 | _root = it; 70 | } else { 71 | auto i = _root; 72 | while(i->next) i = i->next; 73 | i->next = it; 74 | } 75 | } 76 | T& front() const { 77 | return _root->value(); 78 | } 79 | 80 | bool isEmpty() const { 81 | return _root == nullptr; 82 | } 83 | size_t length() const { 84 | size_t i = 0; 85 | auto it = _root; 86 | while(it){ 87 | i++; 88 | it = it->next; 89 | } 90 | return i; 91 | } 92 | size_t count_if(Predicate predicate) const { 93 | size_t i = 0; 94 | auto it = _root; 95 | while(it){ 96 | if (!predicate){ 97 | i++; 98 | } 99 | else if (predicate(it->value())) { 100 | i++; 101 | } 102 | it = it->next; 103 | } 104 | return i; 105 | } 106 | const T* nth(size_t N) const { 107 | size_t i = 0; 108 | auto it = _root; 109 | while(it){ 110 | if(i++ == N) 111 | return &(it->value()); 112 | it = it->next; 113 | } 114 | return nullptr; 115 | } 116 | bool remove(const T& t){ 117 | auto it = _root; 118 | auto pit = _root; 119 | while(it){ 120 | if(it->value() == t){ 121 | if(it == _root){ 122 | _root = _root->next; 123 | } else { 124 | pit->next = it->next; 125 | } 126 | 127 | if (_onRemove) { 128 | _onRemove(it->value()); 129 | } 130 | 131 | delete it; 132 | return true; 133 | } 134 | pit = it; 135 | it = it->next; 136 | } 137 | return false; 138 | } 139 | bool remove_first(Predicate predicate){ 140 | auto it = _root; 141 | auto pit = _root; 142 | while(it){ 143 | if(predicate(it->value())){ 144 | if(it == _root){ 145 | _root = _root->next; 146 | } else { 147 | pit->next = it->next; 148 | } 149 | if (_onRemove) { 150 | _onRemove(it->value()); 151 | } 152 | delete it; 153 | return true; 154 | } 155 | pit = it; 156 | it = it->next; 157 | } 158 | return false; 159 | } 160 | 161 | void free(){ 162 | while(_root != nullptr){ 163 | auto it = _root; 164 | _root = _root->next; 165 | if (_onRemove) { 166 | _onRemove(it->value()); 167 | } 168 | delete it; 169 | } 170 | _root = nullptr; 171 | } 172 | }; 173 | 174 | 175 | class StringArray : public LinkedList { 176 | public: 177 | 178 | StringArray() : LinkedList(nullptr) {} 179 | 180 | bool containsIgnoreCase(const String& str){ 181 | for (const auto& s : *this) { 182 | if (str.equalsIgnoreCase(s)) { 183 | return true; 184 | } 185 | } 186 | return false; 187 | } 188 | }; 189 | 190 | 191 | 192 | 193 | #endif /* STRINGARRAY_H_ */ 194 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/WebResponseImpl.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef ASYNCWEBSERVERRESPONSEIMPL_H_ 22 | #define ASYNCWEBSERVERRESPONSEIMPL_H_ 23 | 24 | #ifdef Arduino_h 25 | // arduino is not compatible with std::vector 26 | #undef min 27 | #undef max 28 | #endif 29 | #include 30 | // It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. 31 | 32 | class AsyncBasicResponse: public AsyncWebServerResponse { 33 | private: 34 | String _content; 35 | public: 36 | AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String()); 37 | void _respond(AsyncWebServerRequest *request); 38 | size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 39 | bool _sourceValid() const { return true; } 40 | }; 41 | 42 | class AsyncAbstractResponse: public AsyncWebServerResponse { 43 | private: 44 | String _head; 45 | // Data is inserted into cache at begin(). 46 | // This is inefficient with vector, but if we use some other container, 47 | // we won't be able to access it as contiguous array of bytes when reading from it, 48 | // so by gaining performance in one place, we'll lose it in another. 49 | std::vector _cache; 50 | size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len); 51 | size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen); 52 | protected: 53 | AwsTemplateProcessor _callback; 54 | public: 55 | AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr); 56 | void _respond(AsyncWebServerRequest *request); 57 | size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 58 | bool _sourceValid() const { return false; } 59 | virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } 60 | }; 61 | 62 | #ifndef TEMPLATE_PLACEHOLDER 63 | #define TEMPLATE_PLACEHOLDER '%' 64 | #endif 65 | 66 | #define TEMPLATE_PARAM_NAME_LENGTH 32 67 | class AsyncFileResponse: public AsyncAbstractResponse { 68 | using File = fs::File; 69 | using FS = fs::FS; 70 | private: 71 | File _content; 72 | String _path; 73 | void _setContentType(const String& path); 74 | public: 75 | AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); 76 | AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); 77 | ~AsyncFileResponse(); 78 | bool _sourceValid() const { return !!(_content); } 79 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 80 | }; 81 | 82 | class AsyncStreamResponse: public AsyncAbstractResponse { 83 | private: 84 | Stream *_content; 85 | public: 86 | AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); 87 | bool _sourceValid() const { return !!(_content); } 88 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 89 | }; 90 | 91 | class AsyncCallbackResponse: public AsyncAbstractResponse { 92 | private: 93 | AwsResponseFiller _content; 94 | size_t _filledLength; 95 | public: 96 | AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); 97 | bool _sourceValid() const { return !!(_content); } 98 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 99 | }; 100 | 101 | class AsyncChunkedResponse: public AsyncAbstractResponse { 102 | private: 103 | AwsResponseFiller _content; 104 | size_t _filledLength; 105 | public: 106 | AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); 107 | bool _sourceValid() const { return !!(_content); } 108 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 109 | }; 110 | 111 | class AsyncProgmemResponse: public AsyncAbstractResponse { 112 | private: 113 | const uint8_t * _content; 114 | size_t _readLength; 115 | public: 116 | AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); 117 | bool _sourceValid() const { return true; } 118 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 119 | }; 120 | 121 | class cbuf; 122 | 123 | class AsyncResponseStream: public AsyncAbstractResponse, public Print { 124 | private: 125 | cbuf *_content; 126 | public: 127 | AsyncResponseStream(const String& contentType, size_t bufferSize); 128 | ~AsyncResponseStream(); 129 | bool _sourceValid() const { return (_state < RESPONSE_END); } 130 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 131 | size_t write(const uint8_t *data, size_t len); 132 | size_t write(uint8_t data); 133 | using Print::write; 134 | }; 135 | 136 | #endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */ 137 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "ArduinoJson-v6.21.5.h" 6 | 7 | #include "WifiSetup.h" 8 | #include "index.h" 9 | 10 | #define LED 2 11 | #define ActiveSerial Serial2 12 | 13 | // Add logging macros 14 | #define LOG_PREFIX "[ESP32] " 15 | #define LOG(msg) \ 16 | Serial.print(LOG_PREFIX); \ 17 | Serial.println(msg) 18 | #define LOG_F(msg, ...) \ 19 | { \ 20 | char buf[128]; \ 21 | snprintf(buf, sizeof(buf), msg, __VA_ARGS__); \ 22 | LOG(buf); \ 23 | } 24 | 25 | AsyncWebServer server(80); 26 | AsyncWebSocket ws("/ws"); 27 | 28 | void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, 29 | AwsEventType type, void* arg, uint8_t* data, size_t len) 30 | { 31 | if (type == WS_EVT_CONNECT) 32 | { 33 | LOG_F("WebSocket client #%u connected from %s", client->id(), client->remoteIP().toString().c_str()); 34 | } 35 | else if (type == WS_EVT_DISCONNECT) 36 | { 37 | LOG_F("WebSocket client #%u disconnected", client->id()); 38 | } 39 | else if (type == WS_EVT_DATA) 40 | { 41 | data[len] = 0; // Null-terminate data (make sure len < buffer size) 42 | String dataStr = (const char*)data; 43 | LOG_F("WebSocket received %d bytes from client #%u", len, client->id()); 44 | // Log the raw data received 45 | LOG_F("Raw data received: %s", data); 46 | 47 | // Create JSON document 48 | StaticJsonDocument<200> doc; 49 | DeserializationError error = deserializeJson(doc, dataStr); 50 | 51 | if (error) { 52 | LOG_F("deserializeJson() failed: %s", error.c_str()); 53 | return; 54 | } 55 | 56 | // Get the commands array 57 | JsonArray commands = doc["commands"]; 58 | if (!commands.isNull()) { 59 | // Convert JsonArray to uint8_t array and write all at once 60 | uint8_t* cmdArray = new uint8_t[commands.size()]; 61 | for (size_t i = 0; i < commands.size(); i++) { 62 | cmdArray[i] = commands[i].as(); 63 | } 64 | 65 | ActiveSerial.write(cmdArray, commands.size()); 66 | LOG_F("Writing %d commands", commands.size()); 67 | // Flicker the LED 68 | // I took this out because I was adding 300m dela to every command - which is not what you need for realtime 69 | // digitalWrite(LED, HIGH); 70 | // delay(100); 71 | // digitalWrite(LED, LOW); 72 | // delay(100); 73 | // digitalWrite(LED, HIGH); 74 | // Delete the command array 75 | delete[] cmdArray; 76 | } 77 | } 78 | } 79 | 80 | void setup() 81 | { 82 | Serial.begin(115200); // Initialize debug serial 83 | LOG("Starting ESP32 application..."); 84 | 85 | EEPROM.begin(700); 86 | 87 | // Initialize configuration if needed 88 | if (!cfg.load()) 89 | { 90 | LOG("Initializing configuration with default values"); 91 | cfg.save(); 92 | } 93 | 94 | pinMode(LED, OUTPUT); 95 | WiFi_Setup(); 96 | ActiveSerial.begin(115200); 97 | LOG("Serial communication initialized"); 98 | 99 | // Setup WebSocket 100 | ws.onEvent(onWsEvent); 101 | server.addHandler(&ws); 102 | LOG("WebSocket handler initialized"); 103 | // Get the roomba Ready 104 | // Pulse Low to the BRC pin to wake it up (if sleeping). BRC is pin 23 105 | // digitalWrite(23, LOW); 106 | // LOG("Pulsing BRC pin to wake up roomba"); 107 | // delay(100); 108 | // digitalWrite(23, HIGH); 109 | // delay(100); 110 | // Reset the roomba 111 | // LOG("Resetting roomba [7]"); 112 | // ActiveSerial.write(7); 113 | delay(500); 114 | // run the start command 115 | LOG("Running start command [128]"); 116 | ActiveSerial.write(128); 117 | delay(500); 118 | // Enter into Full mode 119 | LOG("Entering into Full mode [132]"); 120 | ActiveSerial.write(132); 121 | delay(500); 122 | // Store Songs in the roomba 123 | LOG("Storing songs in the roomba"); 124 | // Store the HONK HONK Sound 125 | ActiveSerial.write(140); // Store Song 126 | ActiveSerial.write(1); // Song 1 127 | ActiveSerial.write(2); // number of notes 128 | ActiveSerial.write(60); 129 | ActiveSerial.write(16); 130 | ActiveSerial.write(60); 131 | ActiveSerial.write(16); 132 | // Store the Police car in Song 2 133 | ActiveSerial.write(140); 134 | ActiveSerial.write(2); 135 | ActiveSerial.write(6); 136 | ActiveSerial.write(60); 137 | ActiveSerial.write(100); 138 | ActiveSerial.write(80); 139 | ActiveSerial.write(100); 140 | ActiveSerial.write(60); 141 | ActiveSerial.write(100); 142 | ActiveSerial.write(80); 143 | ActiveSerial.write(100); 144 | ActiveSerial.write(60); 145 | ActiveSerial.write(100); 146 | delay(500); 147 | 148 | // Play the song 149 | LOG("Playing the song"); 150 | ActiveSerial.write(141); 151 | ActiveSerial.write(1); 152 | delay(500); 153 | 154 | 155 | 156 | // Serve the main page 157 | server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { 158 | AsyncWebServerResponse* response = request->beginResponse_P( 159 | 200, "text/html", toESP32_index_html_gz, sizeof(toESP32_index_html_gz)); 160 | response->addHeader("Access-Control-Allow-Origin", "*"); 161 | response->addHeader("Content-Encoding", "gzip"); 162 | request->send(response); 163 | }); 164 | 165 | // Receive an HTTP GET request 166 | server.on("/off", HTTP_GET, [](AsyncWebServerRequest* request) 167 | { 168 | digitalWrite(LED, LOW); 169 | request->send(200, "text/plain", "ok"); }); 170 | // Add WiFi setup endpoint 171 | server.on("/wifisetup", HTTP_POST, [](AsyncWebServerRequest* request) {}, NULL, wifiSetupCallback); 172 | 173 | server.on("/wificonfig", HTTP_GET, wifiGetConfigCallback); 174 | 175 | // Start server 176 | server.begin(); 177 | LOG_F("Web server started. IP address: %s", WiFi.localIP().toString().c_str()); 178 | digitalWrite(LED, HIGH); 179 | } 180 | 181 | void loop() 182 | { 183 | // Handle WebSocket events 184 | ws.cleanupClients(); 185 | 186 | // Handle serial data 187 | if (ActiveSerial.available()) 188 | { 189 | String data = ActiveSerial.readStringUntil('\n'); 190 | LOG_F("Received data from serial: %s", data.c_str()); 191 | ws.binaryAll(data); 192 | // ws.textAll(data); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/WebHandlerImpl.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef ASYNCWEBSERVERHANDLERIMPL_H_ 22 | #define ASYNCWEBSERVERHANDLERIMPL_H_ 23 | 24 | #include 25 | #ifdef ASYNCWEBSERVER_REGEX 26 | #include 27 | #endif 28 | 29 | #include "stddef.h" 30 | #include 31 | 32 | class AsyncStaticWebHandler: public AsyncWebHandler { 33 | using File = fs::File; 34 | using FS = fs::FS; 35 | private: 36 | bool _getFile(AsyncWebServerRequest *request); 37 | bool _fileExists(AsyncWebServerRequest *request, const String& path); 38 | uint8_t _countBits(const uint8_t value) const; 39 | protected: 40 | FS _fs; 41 | String _uri; 42 | String _path; 43 | String _default_file; 44 | String _cache_control; 45 | String _last_modified; 46 | AwsTemplateProcessor _callback; 47 | bool _isDir; 48 | bool _gzipFirst; 49 | uint8_t _gzipStats; 50 | public: 51 | AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control); 52 | virtual bool canHandle(AsyncWebServerRequest *request) override final; 53 | virtual void handleRequest(AsyncWebServerRequest *request) override final; 54 | AsyncStaticWebHandler& setIsDir(bool isDir); 55 | AsyncStaticWebHandler& setDefaultFile(const char* filename); 56 | AsyncStaticWebHandler& setCacheControl(const char* cache_control); 57 | AsyncStaticWebHandler& setLastModified(const char* last_modified); 58 | AsyncStaticWebHandler& setLastModified(struct tm* last_modified); 59 | #ifdef ESP8266 60 | AsyncStaticWebHandler& setLastModified(time_t last_modified); 61 | AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated 62 | #endif 63 | AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} 64 | }; 65 | 66 | class AsyncCallbackWebHandler: public AsyncWebHandler { 67 | private: 68 | protected: 69 | String _uri; 70 | WebRequestMethodComposite _method; 71 | ArRequestHandlerFunction _onRequest; 72 | ArUploadHandlerFunction _onUpload; 73 | ArBodyHandlerFunction _onBody; 74 | bool _isRegex; 75 | public: 76 | AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} 77 | void setUri(const String& uri){ 78 | _uri = uri; 79 | _isRegex = uri.startsWith("^") && uri.endsWith("$"); 80 | } 81 | void setMethod(WebRequestMethodComposite method){ _method = method; } 82 | void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; } 83 | void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; } 84 | void onBody(ArBodyHandlerFunction fn){ _onBody = fn; } 85 | 86 | virtual bool canHandle(AsyncWebServerRequest *request) override final{ 87 | 88 | if(!_onRequest) 89 | return false; 90 | 91 | if(!(_method & request->method())) 92 | return false; 93 | 94 | #ifdef ASYNCWEBSERVER_REGEX 95 | if (_isRegex) { 96 | std::regex pattern(_uri.c_str()); 97 | std::smatch matches; 98 | std::string s(request->url().c_str()); 99 | if(std::regex_search(s, matches, pattern)) { 100 | for (size_t i = 1; i < matches.size(); ++i) { // start from 1 101 | request->_addPathParam(matches[i].str().c_str()); 102 | } 103 | } else { 104 | return false; 105 | } 106 | } else 107 | #endif 108 | if (_uri.length() && _uri.startsWith("/*.")) { 109 | String uriTemplate = String (_uri); 110 | uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf(".")); 111 | if (!request->url().endsWith(uriTemplate)) 112 | return false; 113 | } 114 | else 115 | if (_uri.length() && _uri.endsWith("*")) { 116 | String uriTemplate = String(_uri); 117 | uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1); 118 | if (!request->url().startsWith(uriTemplate)) 119 | return false; 120 | } 121 | else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) 122 | return false; 123 | 124 | request->addInterestingHeader("ANY"); 125 | return true; 126 | } 127 | 128 | virtual void handleRequest(AsyncWebServerRequest *request) override final { 129 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 130 | return request->requestAuthentication(); 131 | if(_onRequest) 132 | _onRequest(request); 133 | else 134 | request->send(500); 135 | } 136 | virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { 137 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 138 | return request->requestAuthentication(); 139 | if(_onUpload) 140 | _onUpload(request, filename, index, data, len, final); 141 | } 142 | virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { 143 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 144 | return request->requestAuthentication(); 145 | if(_onBody) 146 | _onBody(request, data, len, index, total); 147 | } 148 | virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} 149 | }; 150 | 151 | #endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */ 152 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/WebServer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #include "ESPAsyncWebSrv.h" 22 | #include "WebHandlerImpl.h" 23 | 24 | bool ON_STA_FILTER(AsyncWebServerRequest *request) { 25 | return WiFi.localIP() == request->client()->localIP(); 26 | } 27 | 28 | bool ON_AP_FILTER(AsyncWebServerRequest *request) { 29 | return WiFi.localIP() != request->client()->localIP(); 30 | } 31 | 32 | 33 | AsyncWebServer::AsyncWebServer(uint16_t port) 34 | : _server(port) 35 | , _rewrites(LinkedList([](AsyncWebRewrite* r){ delete r; })) 36 | , _handlers(LinkedList([](AsyncWebHandler* h){ delete h; })) 37 | { 38 | _catchAllHandler = new AsyncCallbackWebHandler(); 39 | if(_catchAllHandler == NULL) 40 | return; 41 | _server.onClient([](void *s, AsyncClient* c){ 42 | if(c == NULL) 43 | return; 44 | c->setRxTimeout(3); 45 | AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c); 46 | if(r == NULL){ 47 | c->close(true); 48 | c->free(); 49 | delete c; 50 | } 51 | }, this); 52 | } 53 | 54 | AsyncWebServer::~AsyncWebServer(){ 55 | reset(); 56 | end(); 57 | if(_catchAllHandler) delete _catchAllHandler; 58 | } 59 | 60 | AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){ 61 | _rewrites.add(rewrite); 62 | return *rewrite; 63 | } 64 | 65 | bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){ 66 | return _rewrites.remove(rewrite); 67 | } 68 | 69 | AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){ 70 | return addRewrite(new AsyncWebRewrite(from, to)); 71 | } 72 | 73 | AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){ 74 | _handlers.add(handler); 75 | return *handler; 76 | } 77 | 78 | bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){ 79 | return _handlers.remove(handler); 80 | } 81 | 82 | void AsyncWebServer::begin(){ 83 | _server.setNoDelay(true); 84 | _server.begin(); 85 | } 86 | 87 | void AsyncWebServer::end(){ 88 | _server.end(); 89 | } 90 | 91 | #if ASYNC_TCP_SSL_ENABLED 92 | void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){ 93 | _server.onSslFileRequest(cb, arg); 94 | } 95 | 96 | void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){ 97 | _server.beginSecure(cert, key, password); 98 | } 99 | #endif 100 | 101 | void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){ 102 | delete request; 103 | } 104 | 105 | void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){ 106 | for(const auto& r: _rewrites){ 107 | if (r->match(request)){ 108 | request->_url = r->toUrl(); 109 | request->_addGetParams(r->params()); 110 | } 111 | } 112 | } 113 | 114 | void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){ 115 | for(const auto& h: _handlers){ 116 | if (h->filter(request) && h->canHandle(request)){ 117 | request->setHandler(h); 118 | return; 119 | } 120 | } 121 | 122 | request->addInterestingHeader("ANY"); 123 | request->setHandler(_catchAllHandler); 124 | } 125 | 126 | 127 | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){ 128 | AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); 129 | handler->setUri(uri); 130 | handler->setMethod(method); 131 | handler->onRequest(onRequest); 132 | handler->onUpload(onUpload); 133 | handler->onBody(onBody); 134 | addHandler(handler); 135 | return *handler; 136 | } 137 | 138 | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){ 139 | AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); 140 | handler->setUri(uri); 141 | handler->setMethod(method); 142 | handler->onRequest(onRequest); 143 | handler->onUpload(onUpload); 144 | addHandler(handler); 145 | return *handler; 146 | } 147 | 148 | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){ 149 | AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); 150 | handler->setUri(uri); 151 | handler->setMethod(method); 152 | handler->onRequest(onRequest); 153 | addHandler(handler); 154 | return *handler; 155 | } 156 | 157 | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){ 158 | AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); 159 | handler->setUri(uri); 160 | handler->onRequest(onRequest); 161 | addHandler(handler); 162 | return *handler; 163 | } 164 | 165 | AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){ 166 | AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control); 167 | addHandler(handler); 168 | return *handler; 169 | } 170 | 171 | void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){ 172 | _catchAllHandler->onRequest(fn); 173 | } 174 | 175 | void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){ 176 | _catchAllHandler->onUpload(fn); 177 | } 178 | 179 | void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){ 180 | _catchAllHandler->onBody(fn); 181 | } 182 | 183 | void AsyncWebServer::reset(){ 184 | _rewrites.free(); 185 | _handlers.free(); 186 | 187 | if (_catchAllHandler != NULL){ 188 | _catchAllHandler->onRequest(NULL); 189 | _catchAllHandler->onUpload(NULL); 190 | _catchAllHandler->onBody(NULL); 191 | } 192 | } 193 | 194 | -------------------------------------------------------------------------------- /controller/src/components/Joystick.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useRef } from 'react'; 2 | 3 | interface JoystickProps { 4 | onMove: (rightPwm: number, leftPwm: number) => void; 5 | size?: number; 6 | } 7 | 8 | export function Joystick({ onMove, size = 200 }: JoystickProps) { 9 | const [isDragging, setIsDragging] = useState(false); 10 | const [position, setPosition] = useState({ x: 0, y: 0 }); 11 | const joystickRef = useRef(null); 12 | 13 | // Calculate color and glow intensity based on position 14 | const getGlowColor = (x: number, y: number) => { 15 | // Calculate angle in degrees (0-360) 16 | const angle = Math.atan2(y, x) * (180 / Math.PI); 17 | 18 | // Map angle to color 19 | if (angle >= -45 && angle < 45) return 'cyan'; // Right 20 | if (angle >= 45 && angle < 135) return 'green'; // Down 21 | if (angle >= 135 || angle < -135) return 'blue'; // Left 22 | return 'purple'; // Up 23 | }; 24 | 25 | const handlePointerDown = (e: React.PointerEvent) => { 26 | e.preventDefault(); 27 | setIsDragging(true); 28 | joystickRef.current?.setPointerCapture(e.pointerId); 29 | }; 30 | 31 | const handlePointerMove = (e: React.PointerEvent) => { 32 | e.preventDefault(); 33 | if (!isDragging || !joystickRef.current) return; 34 | 35 | const rect = joystickRef.current.getBoundingClientRect(); 36 | const centerX = rect.left + rect.width / 2; 37 | const centerY = rect.top + rect.height / 2; 38 | 39 | // Calculate position relative to center 40 | let x = e.clientX - centerX; 41 | let y = e.clientY - centerY; 42 | 43 | // Limit to circle 44 | const radius = size / 2; 45 | const distance = Math.sqrt(x * x + y * y); 46 | if (distance > radius) { 47 | x = (x / distance) * radius; 48 | y = (y / distance) * radius; 49 | } 50 | 51 | // Convert to -255 to 255 range 52 | const normalizedX = Math.round((x / radius) * 255); 53 | const normalizedY = Math.round((-y / radius) * 255); // Invert Y axis 54 | 55 | // Convert to differential drive values 56 | const leftPwm = normalizedY + normalizedX; // Forward + rotation 57 | const rightPwm = normalizedY - normalizedX; // Forward - rotation 58 | 59 | // Clamp values between -255 and 255 60 | const clampedLeft = Math.max(-255, Math.min(255, leftPwm)); 61 | const clampedRight = Math.max(-255, Math.min(255, rightPwm)); 62 | 63 | setPosition({ x, y }); 64 | onMove(clampedRight, clampedLeft); 65 | }; 66 | 67 | const handlePointerUp = () => { 68 | setIsDragging(false); 69 | setPosition({ x: 0, y: 0 }); 70 | onMove(0, 0); 71 | }; 72 | 73 | const glowColor = getGlowColor(position.x, position.y); 74 | 75 | return ( 76 |
77 | 78 |
e.preventDefault()} 92 | onTouchMove={(e) => e.preventDefault()} 93 | onPointerDown={handlePointerDown} 94 | onPointerMove={handlePointerMove} 95 | onPointerUp={handlePointerUp} 96 | onPointerLeave={handlePointerUp} 97 | onPointerCancel={handlePointerUp} 98 | > 99 | {/* Base Ring with Dynamic Glow */} 100 |
102 |
104 |
105 | 106 | {/* Inner Ring Glow */} 107 |
109 |
111 |
112 | 113 | {/* Accent Ring with Dynamic Color */} 114 |
126 | 127 | {/* Guide Lines */} 128 |
129 | {/* Vertical Line */} 130 |
132 | {/* Horizontal Line */} 133 |
135 |
136 | 137 | {/* Handle */} 138 |
156 | {/* Center Dot */} 157 |
159 |
160 |
161 |
162 |
163 |
164 | ); 165 | } 166 | -------------------------------------------------------------------------------- /controller/src/components/SensorDisplay.tsx: -------------------------------------------------------------------------------- 1 | import { SensorData } from "../lib/sensor-decode"; 2 | 3 | interface SensorDisplayProps { 4 | data: SensorData | null; 5 | } 6 | 7 | export function SensorDisplay({ data }: SensorDisplayProps) { 8 | if (!data) return ( 9 |
NO SENSOR DATA
10 | ); 11 | 12 | return ( 13 |
14 | {/* Contact Sensors */} 15 |
16 |

CONTACT SENSORS

17 |
18 |
19 |

BUMPERS

20 |
21 |
22 | Left: {data.bump.left ? 'Hit' : 'Clear'} 23 |
24 |
25 |
26 | Right: {data.bump.right ? 'Hit' : 'Clear'} 27 |
28 |
29 |
30 |

WHEEL DROPS

31 |
32 |
33 | Left: {data.wheeldrop.left ? 'Lifted' : 'Down'} 34 |
35 |
36 |
37 | Right: {data.wheeldrop.right ? 'Lifted' : 'Down'} 38 |
39 |
40 |
41 | Caster: {data.wheeldrop.caster ? 'Lifted' : 'Down'} 42 |
43 |
44 |
45 |
46 | 47 | {/* Surface Sensors */} 48 |
49 |

SURFACE SENSORS

50 |
51 |
52 |
53 | 54 | Wall Detected: {data.wall ? 'Yes' : 'No'} 55 | 56 |
57 |
58 |
59 | 60 | Virtual Wall: {data.virtual_wall ? 'Yes' : 'No'} 61 | 62 |
63 |

CLIFF SENSORS

64 |
65 | {Object.entries(data.cliff).map(([position, isCliff]) => ( 66 |
67 |
68 | 69 | {position}: {isCliff ? 'Cliff!' : 'Safe'} 70 | 71 |
72 | ))} 73 |
74 |
75 |
76 | 77 | {/* Battery Status */} 78 |
79 |

BATTERY STATUS

80 |
81 |
82 |
86 |
87 |
88 |
89 | Level: {Math.round((data.battery.level / data.battery.capacity) * 100)}% 90 |
91 |
92 | Voltage: {(data.battery.voltage / 1000).toFixed(2)}V 93 |
94 |
95 | Current: {data.battery.current}mA 96 |
97 |
98 | Temp: {data.battery.temp}°C 99 |
100 |
101 |
102 | State: 103 | {getBatteryState(data.battery.charging_state)} 104 | 105 |
106 |
107 |
108 |
109 | ); 110 | } 111 | 112 | function getBatteryState(state: number): string { 113 | switch (state) { 114 | case 0: 115 | return "Not Charging"; 116 | case 1: 117 | return "Reconditioning"; 118 | case 2: 119 | return "Full Charging"; 120 | case 3: 121 | return "Trickle Charging"; 122 | case 4: 123 | return "Waiting"; 124 | case 5: 125 | return "Charging Fault"; 126 | default: 127 | return "Unknown"; 128 | } 129 | } 130 | 131 | function getBatteryStateColor(state: number): string { 132 | switch (state) { 133 | case 0: 134 | return "text-gray-400"; 135 | case 1: 136 | return "text-yellow-400"; 137 | case 2: 138 | return "text-green-400"; 139 | case 3: 140 | return "text-blue-400"; 141 | case 4: 142 | return "text-cyan-400"; 143 | case 5: 144 | return "text-red-400"; 145 | default: 146 | return "text-gray-400"; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/WebAuthentication.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #include "WebAuthentication.h" 22 | #include 23 | #ifdef ESP32 24 | #include "mbedtls/md5.h" 25 | #else 26 | #include "md5.h" 27 | #endif 28 | 29 | 30 | // Basic Auth hash = base64("username:password") 31 | 32 | bool checkBasicAuthentication(const char * hash, const char * username, const char * password){ 33 | if(username == NULL || password == NULL || hash == NULL) 34 | return false; 35 | 36 | size_t toencodeLen = strlen(username)+strlen(password)+1; 37 | size_t encodedLen = base64_encode_expected_len(toencodeLen); 38 | if(strlen(hash) != encodedLen) 39 | return false; 40 | 41 | char *toencode = new char[toencodeLen+1]; 42 | if(toencode == NULL){ 43 | return false; 44 | } 45 | char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; 46 | if(encoded == NULL){ 47 | delete[] toencode; 48 | return false; 49 | } 50 | sprintf(toencode, "%s:%s", username, password); 51 | if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){ 52 | delete[] toencode; 53 | delete[] encoded; 54 | return true; 55 | } 56 | delete[] toencode; 57 | delete[] encoded; 58 | return false; 59 | } 60 | 61 | static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more 62 | #ifdef ESP32 63 | mbedtls_md5_context _ctx; 64 | #else 65 | md5_context_t _ctx; 66 | #endif 67 | uint8_t i; 68 | uint8_t * _buf = (uint8_t*)malloc(16); 69 | if(_buf == NULL) 70 | return false; 71 | memset(_buf, 0x00, 16); 72 | #ifdef ESP32 73 | mbedtls_md5_init(&_ctx); 74 | mbedtls_md5_starts_ret(&_ctx); 75 | mbedtls_md5_update_ret(&_ctx, data, len); 76 | mbedtls_md5_finish_ret(&_ctx, _buf); 77 | #else 78 | MD5Init(&_ctx); 79 | MD5Update(&_ctx, data, len); 80 | MD5Final(_buf, &_ctx); 81 | #endif 82 | for(i = 0; i < 16; i++) { 83 | sprintf(output + (i * 2), "%02x", _buf[i]); 84 | } 85 | free(_buf); 86 | return true; 87 | } 88 | 89 | static String genRandomMD5(){ 90 | #ifdef ESP8266 91 | uint32_t r = RANDOM_REG32; 92 | #else 93 | uint32_t r = rand(); 94 | #endif 95 | char * out = (char*)malloc(33); 96 | if(out == NULL || !getMD5((uint8_t*)(&r), 4, out)) 97 | return ""; 98 | String res = String(out); 99 | free(out); 100 | return res; 101 | } 102 | 103 | static String stringMD5(const String& in){ 104 | char * out = (char*)malloc(33); 105 | if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) 106 | return ""; 107 | String res = String(out); 108 | free(out); 109 | return res; 110 | } 111 | 112 | String generateDigestHash(const char * username, const char * password, const char * realm){ 113 | if(username == NULL || password == NULL || realm == NULL){ 114 | return ""; 115 | } 116 | char * out = (char*)malloc(33); 117 | String res = String(username); 118 | res.concat(":"); 119 | res.concat(realm); 120 | res.concat(":"); 121 | String in = res; 122 | in.concat(password); 123 | if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) 124 | return ""; 125 | res.concat(out); 126 | free(out); 127 | return res; 128 | } 129 | 130 | String requestDigestAuthentication(const char * realm){ 131 | String header = "realm=\""; 132 | if(realm == NULL) 133 | header.concat("asyncesp"); 134 | else 135 | header.concat(realm); 136 | header.concat( "\", qop=\"auth\", nonce=\""); 137 | header.concat(genRandomMD5()); 138 | header.concat("\", opaque=\""); 139 | header.concat(genRandomMD5()); 140 | header.concat("\""); 141 | return header; 142 | } 143 | 144 | bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){ 145 | if(username == NULL || password == NULL || header == NULL || method == NULL){ 146 | //os_printf("AUTH FAIL: missing requred fields\n"); 147 | return false; 148 | } 149 | 150 | String myHeader = String(header); 151 | int nextBreak = myHeader.indexOf(","); 152 | if(nextBreak < 0){ 153 | //os_printf("AUTH FAIL: no variables\n"); 154 | return false; 155 | } 156 | 157 | String myUsername = String(); 158 | String myRealm = String(); 159 | String myNonce = String(); 160 | String myUri = String(); 161 | String myResponse = String(); 162 | String myQop = String(); 163 | String myNc = String(); 164 | String myCnonce = String(); 165 | 166 | myHeader += ", "; 167 | do { 168 | String avLine = myHeader.substring(0, nextBreak); 169 | avLine.trim(); 170 | myHeader = myHeader.substring(nextBreak+1); 171 | nextBreak = myHeader.indexOf(","); 172 | 173 | int eqSign = avLine.indexOf("="); 174 | if(eqSign < 0){ 175 | //os_printf("AUTH FAIL: no = sign\n"); 176 | return false; 177 | } 178 | String varName = avLine.substring(0, eqSign); 179 | avLine = avLine.substring(eqSign + 1); 180 | if(avLine.startsWith("\"")){ 181 | avLine = avLine.substring(1, avLine.length() - 1); 182 | } 183 | 184 | if(varName.equals("username")){ 185 | if(!avLine.equals(username)){ 186 | //os_printf("AUTH FAIL: username\n"); 187 | return false; 188 | } 189 | myUsername = avLine; 190 | } else if(varName.equals("realm")){ 191 | if(realm != NULL && !avLine.equals(realm)){ 192 | //os_printf("AUTH FAIL: realm\n"); 193 | return false; 194 | } 195 | myRealm = avLine; 196 | } else if(varName.equals("nonce")){ 197 | if(nonce != NULL && !avLine.equals(nonce)){ 198 | //os_printf("AUTH FAIL: nonce\n"); 199 | return false; 200 | } 201 | myNonce = avLine; 202 | } else if(varName.equals("opaque")){ 203 | if(opaque != NULL && !avLine.equals(opaque)){ 204 | //os_printf("AUTH FAIL: opaque\n"); 205 | return false; 206 | } 207 | } else if(varName.equals("uri")){ 208 | if(uri != NULL && !avLine.equals(uri)){ 209 | //os_printf("AUTH FAIL: uri\n"); 210 | return false; 211 | } 212 | myUri = avLine; 213 | } else if(varName.equals("response")){ 214 | myResponse = avLine; 215 | } else if(varName.equals("qop")){ 216 | myQop = avLine; 217 | } else if(varName.equals("nc")){ 218 | myNc = avLine; 219 | } else if(varName.equals("cnonce")){ 220 | myCnonce = avLine; 221 | } 222 | } while(nextBreak > 0); 223 | 224 | String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ":" + myRealm + ":" + String(password)); 225 | String ha2 = String(method) + ":" + myUri; 226 | String response = ha1 + ":" + myNonce + ":" + myNc + ":" + myCnonce + ":" + myQop + ":" + stringMD5(ha2); 227 | 228 | if(myResponse.equals(stringMD5(response))){ 229 | //os_printf("AUTH SUCCESS\n"); 230 | return true; 231 | } 232 | 233 | //os_printf("AUTH FAIL: password\n"); 234 | return false; 235 | } 236 | -------------------------------------------------------------------------------- /lib/AsyncTCP/LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /lib/AsyncTCP/src/AsyncTCP.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous TCP library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | 22 | #ifndef ASYNCTCP_H_ 23 | #define ASYNCTCP_H_ 24 | 25 | #include "IPAddress.h" 26 | #include "sdkconfig.h" 27 | #include 28 | extern "C" { 29 | #include "freertos/semphr.h" 30 | #include "lwip/pbuf.h" 31 | } 32 | 33 | //If core is not defined, then we are running in Arduino or PIO 34 | #ifndef CONFIG_ASYNC_TCP_RUNNING_CORE 35 | #define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core 36 | #define CONFIG_ASYNC_TCP_USE_WDT 0 //if enabled, adds between 33us and 200us per event 37 | #endif 38 | 39 | class AsyncClient; 40 | 41 | #define ASYNC_MAX_ACK_TIME 1000 42 | #define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given) 43 | #define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react. 44 | 45 | typedef std::function AcConnectHandler; 46 | typedef std::function AcAckHandler; 47 | typedef std::function AcErrorHandler; 48 | typedef std::function AcDataHandler; 49 | typedef std::function AcPacketHandler; 50 | typedef std::function AcTimeoutHandler; 51 | 52 | struct tcp_pcb; 53 | struct ip_addr; 54 | 55 | class AsyncClient { 56 | public: 57 | AsyncClient(tcp_pcb* pcb = 0); 58 | ~AsyncClient(); 59 | 60 | AsyncClient & operator=(const AsyncClient &other); 61 | AsyncClient & operator+=(const AsyncClient &other); 62 | 63 | bool operator==(const AsyncClient &other); 64 | 65 | bool operator!=(const AsyncClient &other) { 66 | return !(*this == other); 67 | } 68 | bool connect(IPAddress ip, uint16_t port); 69 | bool connect(const char* host, uint16_t port); 70 | void close(bool now = false); 71 | void stop(); 72 | int8_t abort(); 73 | bool free(); 74 | 75 | bool canSend();//ack is not pending 76 | size_t space();//space available in the TCP window 77 | size_t add(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY);//add for sending 78 | bool send();//send all data added with the method above 79 | 80 | //write equals add()+send() 81 | size_t write(const char* data); 82 | size_t write(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY); //only when canSend() == true 83 | 84 | uint8_t state(); 85 | bool connecting(); 86 | bool connected(); 87 | bool disconnecting(); 88 | bool disconnected(); 89 | bool freeable();//disconnected or disconnecting 90 | 91 | uint16_t getMss(); 92 | 93 | uint32_t getRxTimeout(); 94 | void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds 95 | 96 | uint32_t getAckTimeout(); 97 | void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds 98 | 99 | void setNoDelay(bool nodelay); 100 | bool getNoDelay(); 101 | 102 | uint32_t getRemoteAddress(); 103 | uint16_t getRemotePort(); 104 | uint32_t getLocalAddress(); 105 | uint16_t getLocalPort(); 106 | 107 | //compatibility 108 | IPAddress remoteIP(); 109 | uint16_t remotePort(); 110 | IPAddress localIP(); 111 | uint16_t localPort(); 112 | 113 | void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect 114 | void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected 115 | void onAck(AcAckHandler cb, void* arg = 0); //ack received 116 | void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error 117 | void onData(AcDataHandler cb, void* arg = 0); //data received (called if onPacket is not used) 118 | void onPacket(AcPacketHandler cb, void* arg = 0); //data received 119 | void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout 120 | void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected 121 | 122 | void ackPacket(struct pbuf * pb);//ack pbuf from onPacket 123 | size_t ack(size_t len); //ack data that you have not acked using the method below 124 | void ackLater(){ _ack_pcb = false; } //will not ack the current packet. Call from onData 125 | 126 | const char * errorToString(int8_t error); 127 | const char * stateToString(); 128 | 129 | //Do not use any of the functions below! 130 | static int8_t _s_poll(void *arg, struct tcp_pcb *tpcb); 131 | static int8_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, int8_t err); 132 | static int8_t _s_fin(void *arg, struct tcp_pcb *tpcb, int8_t err); 133 | static int8_t _s_lwip_fin(void *arg, struct tcp_pcb *tpcb, int8_t err); 134 | static void _s_error(void *arg, int8_t err); 135 | static int8_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len); 136 | static int8_t _s_connected(void* arg, void* tpcb, int8_t err); 137 | static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg); 138 | 139 | int8_t _recv(tcp_pcb* pcb, pbuf* pb, int8_t err); 140 | tcp_pcb * pcb(){ return _pcb; } 141 | 142 | protected: 143 | tcp_pcb* _pcb; 144 | int8_t _closed_slot; 145 | 146 | AcConnectHandler _connect_cb; 147 | void* _connect_cb_arg; 148 | AcConnectHandler _discard_cb; 149 | void* _discard_cb_arg; 150 | AcAckHandler _sent_cb; 151 | void* _sent_cb_arg; 152 | AcErrorHandler _error_cb; 153 | void* _error_cb_arg; 154 | AcDataHandler _recv_cb; 155 | void* _recv_cb_arg; 156 | AcPacketHandler _pb_cb; 157 | void* _pb_cb_arg; 158 | AcTimeoutHandler _timeout_cb; 159 | void* _timeout_cb_arg; 160 | AcConnectHandler _poll_cb; 161 | void* _poll_cb_arg; 162 | 163 | bool _pcb_busy; 164 | uint32_t _pcb_sent_at; 165 | bool _ack_pcb; 166 | uint32_t _rx_ack_len; 167 | uint32_t _rx_last_packet; 168 | uint32_t _rx_since_timeout; 169 | uint32_t _ack_timeout; 170 | uint16_t _connect_port; 171 | 172 | int8_t _close(); 173 | void _free_closed_slot(); 174 | void _allocate_closed_slot(); 175 | int8_t _connected(void* pcb, int8_t err); 176 | void _error(int8_t err); 177 | int8_t _poll(tcp_pcb* pcb); 178 | int8_t _sent(tcp_pcb* pcb, uint16_t len); 179 | int8_t _fin(tcp_pcb* pcb, int8_t err); 180 | int8_t _lwip_fin(tcp_pcb* pcb, int8_t err); 181 | void _dns_found(struct ip_addr *ipaddr); 182 | 183 | public: 184 | AsyncClient* prev; 185 | AsyncClient* next; 186 | }; 187 | 188 | class AsyncServer { 189 | public: 190 | AsyncServer(IPAddress addr, uint16_t port); 191 | AsyncServer(uint16_t port); 192 | ~AsyncServer(); 193 | void onClient(AcConnectHandler cb, void* arg); 194 | void begin(); 195 | void end(); 196 | void setNoDelay(bool nodelay); 197 | bool getNoDelay(); 198 | uint8_t status(); 199 | 200 | //Do not use any of the functions below! 201 | static int8_t _s_accept(void *arg, tcp_pcb* newpcb, int8_t err); 202 | static int8_t _s_accepted(void *arg, AsyncClient* client); 203 | 204 | protected: 205 | uint16_t _port; 206 | IPAddress _addr; 207 | bool _noDelay; 208 | tcp_pcb* _pcb; 209 | AcConnectHandler _connect_cb; 210 | void* _connect_cb_arg; 211 | 212 | int8_t _accept(tcp_pcb* newpcb, int8_t err); 213 | int8_t _accepted(AsyncClient* client); 214 | }; 215 | 216 | 217 | #endif /* ASYNCTCP_H_ */ 218 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/WebHandlers.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #include "ESPAsyncWebSrv.h" 22 | #include "WebHandlerImpl.h" 23 | 24 | AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control) 25 | : _fs(fs), _uri(uri), _path(path), _default_file("index.htm"), _cache_control(cache_control), _last_modified(""), _callback(nullptr) 26 | { 27 | // Ensure leading '/' 28 | if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri; 29 | if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path; 30 | 31 | // If path ends with '/' we assume a hint that this is a directory to improve performance. 32 | // However - if it does not end with '/' we, can't assume a file, path can still be a directory. 33 | _isDir = _path[_path.length()-1] == '/'; 34 | 35 | // Remove the trailing '/' so we can handle default file 36 | // Notice that root will be "" not "/" 37 | if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1); 38 | if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1); 39 | 40 | // Reset stats 41 | _gzipFirst = false; 42 | _gzipStats = 0xF8; 43 | } 44 | 45 | AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){ 46 | _isDir = isDir; 47 | return *this; 48 | } 49 | 50 | AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){ 51 | _default_file = String(filename); 52 | return *this; 53 | } 54 | 55 | AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){ 56 | _cache_control = String(cache_control); 57 | return *this; 58 | } 59 | 60 | AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){ 61 | _last_modified = String(last_modified); 62 | return *this; 63 | } 64 | 65 | AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){ 66 | char result[30]; 67 | strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified); 68 | return setLastModified((const char *)result); 69 | } 70 | 71 | #ifdef ESP8266 72 | AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){ 73 | return setLastModified((struct tm *)gmtime(&last_modified)); 74 | } 75 | 76 | AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){ 77 | time_t last_modified; 78 | if(time(&last_modified) == 0) //time is not yet set 79 | return *this; 80 | return setLastModified(last_modified); 81 | } 82 | #endif 83 | bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){ 84 | if(request->method() != HTTP_GET 85 | || !request->url().startsWith(_uri) 86 | || !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP) 87 | ){ 88 | return false; 89 | } 90 | if (_getFile(request)) { 91 | // We interested in "If-Modified-Since" header to check if file was modified 92 | if (_last_modified.length()) 93 | request->addInterestingHeader("If-Modified-Since"); 94 | 95 | if(_cache_control.length()) 96 | request->addInterestingHeader("If-None-Match"); 97 | 98 | DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n"); 99 | return true; 100 | } 101 | 102 | return false; 103 | } 104 | 105 | bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) 106 | { 107 | // Remove the found uri 108 | String path = request->url().substring(_uri.length()); 109 | 110 | // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' 111 | bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/'); 112 | 113 | path = _path + path; 114 | 115 | // Do we have a file or .gz file 116 | if (!canSkipFileCheck && _fileExists(request, path)) 117 | return true; 118 | 119 | // Can't handle if not default file 120 | if (_default_file.length() == 0) 121 | return false; 122 | 123 | // Try to add default file, ensure there is a trailing '/' ot the path. 124 | if (path.length() == 0 || path[path.length()-1] != '/') 125 | path += "/"; 126 | path += _default_file; 127 | 128 | return _fileExists(request, path); 129 | } 130 | 131 | #ifdef ESP32 132 | #define FILE_IS_REAL(f) (f == true && !f.isDirectory()) 133 | #else 134 | #define FILE_IS_REAL(f) (f == true) 135 | #endif 136 | 137 | bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path) 138 | { 139 | bool fileFound = false; 140 | bool gzipFound = false; 141 | 142 | String gzip = path + ".gz"; 143 | 144 | if (_gzipFirst) { 145 | request->_tempFile = _fs.open(gzip, "r"); 146 | gzipFound = FILE_IS_REAL(request->_tempFile); 147 | if (!gzipFound){ 148 | request->_tempFile = _fs.open(path, "r"); 149 | fileFound = FILE_IS_REAL(request->_tempFile); 150 | } 151 | } else { 152 | request->_tempFile = _fs.open(path, "r"); 153 | fileFound = FILE_IS_REAL(request->_tempFile); 154 | if (!fileFound){ 155 | request->_tempFile = _fs.open(gzip, "r"); 156 | gzipFound = FILE_IS_REAL(request->_tempFile); 157 | } 158 | } 159 | 160 | bool found = fileFound || gzipFound; 161 | 162 | if (found) { 163 | // Extract the file name from the path and keep it in _tempObject 164 | size_t pathLen = path.length(); 165 | char * _tempPath = (char*)malloc(pathLen+1); 166 | snprintf(_tempPath, pathLen+1, "%s", path.c_str()); 167 | request->_tempObject = (void*)_tempPath; 168 | 169 | // Calculate gzip statistic 170 | _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); 171 | if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip 172 | else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip 173 | else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first 174 | } 175 | 176 | return found; 177 | } 178 | 179 | uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const 180 | { 181 | uint8_t w = value; 182 | uint8_t n; 183 | for (n=0; w!=0; n++) w&=w-1; 184 | return n; 185 | } 186 | 187 | void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) 188 | { 189 | // Get the filename from request->_tempObject and free it 190 | String filename = String((char*)request->_tempObject); 191 | free(request->_tempObject); 192 | request->_tempObject = NULL; 193 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 194 | return request->requestAuthentication(); 195 | 196 | if (request->_tempFile == true) { 197 | String etag = String(request->_tempFile.size()); 198 | if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) { 199 | request->_tempFile.close(); 200 | request->send(304); // Not modified 201 | } else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) { 202 | request->_tempFile.close(); 203 | AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified 204 | response->addHeader("Cache-Control", _cache_control); 205 | response->addHeader("ETag", etag); 206 | request->send(response); 207 | } else { 208 | AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback); 209 | if (_last_modified.length()) 210 | response->addHeader("Last-Modified", _last_modified); 211 | if (_cache_control.length()){ 212 | response->addHeader("Cache-Control", _cache_control); 213 | response->addHeader("ETag", etag); 214 | } 215 | request->send(response); 216 | } 217 | } else { 218 | request->send(404); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/AsyncJson.h: -------------------------------------------------------------------------------- 1 | // AsyncJson.h 2 | /* 3 | Async Response to use with ArduinoJson and AsyncWebServer 4 | Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon. 5 | 6 | Example of callback in use 7 | 8 | server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) { 9 | 10 | AsyncJsonResponse * response = new AsyncJsonResponse(); 11 | JsonObject& root = response->getRoot(); 12 | root["key1"] = "key number one"; 13 | JsonObject& nested = root.createNestedObject("nested"); 14 | nested["key1"] = "key number one"; 15 | 16 | response->setLength(); 17 | request->send(response); 18 | }); 19 | 20 | -------------------- 21 | 22 | Async Request to use with ArduinoJson and AsyncWebServer 23 | Written by Arsène von Wyss (avonwyss) 24 | 25 | Example 26 | 27 | AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint"); 28 | handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) { 29 | JsonObject& jsonObj = json.as(); 30 | // ... 31 | }); 32 | server.addHandler(handler); 33 | 34 | */ 35 | #ifndef ASYNC_JSON_H_ 36 | #define ASYNC_JSON_H_ 37 | #include 38 | #include 39 | #include 40 | 41 | #if ARDUINOJSON_VERSION_MAJOR == 5 42 | #define ARDUINOJSON_5_COMPATIBILITY 43 | #else 44 | #ifndef DYNAMIC_JSON_DOCUMENT_SIZE 45 | #define DYNAMIC_JSON_DOCUMENT_SIZE 1024 46 | #endif 47 | #endif 48 | 49 | constexpr const char* JSON_MIMETYPE = "application/json"; 50 | 51 | /* 52 | * Json Response 53 | * */ 54 | 55 | class ChunkPrint : public Print { 56 | private: 57 | uint8_t* _destination; 58 | size_t _to_skip; 59 | size_t _to_write; 60 | size_t _pos; 61 | public: 62 | ChunkPrint(uint8_t* destination, size_t from, size_t len) 63 | : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {} 64 | virtual ~ChunkPrint(){} 65 | size_t write(uint8_t c){ 66 | if (_to_skip > 0) { 67 | _to_skip--; 68 | return 1; 69 | } else if (_to_write > 0) { 70 | _to_write--; 71 | _destination[_pos++] = c; 72 | return 1; 73 | } 74 | return 0; 75 | } 76 | size_t write(const uint8_t *buffer, size_t size) 77 | { 78 | return this->Print::write(buffer, size); 79 | } 80 | }; 81 | 82 | class AsyncJsonResponse: public AsyncAbstractResponse { 83 | protected: 84 | 85 | #ifdef ARDUINOJSON_5_COMPATIBILITY 86 | DynamicJsonBuffer _jsonBuffer; 87 | #else 88 | DynamicJsonDocument _jsonBuffer; 89 | #endif 90 | 91 | JsonVariant _root; 92 | bool _isValid; 93 | 94 | public: 95 | 96 | #ifdef ARDUINOJSON_5_COMPATIBILITY 97 | AsyncJsonResponse(bool isArray=false): _isValid{false} { 98 | _code = 200; 99 | _contentType = JSON_MIMETYPE; 100 | if(isArray) 101 | _root = _jsonBuffer.createArray(); 102 | else 103 | _root = _jsonBuffer.createObject(); 104 | } 105 | #else 106 | AsyncJsonResponse(bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} { 107 | _code = 200; 108 | _contentType = JSON_MIMETYPE; 109 | if(isArray) 110 | _root = _jsonBuffer.createNestedArray(); 111 | else 112 | _root = _jsonBuffer.createNestedObject(); 113 | } 114 | #endif 115 | 116 | ~AsyncJsonResponse() {} 117 | JsonVariant & getRoot() { return _root; } 118 | bool _sourceValid() const { return _isValid; } 119 | size_t setLength() { 120 | 121 | #ifdef ARDUINOJSON_5_COMPATIBILITY 122 | _contentLength = _root.measureLength(); 123 | #else 124 | _contentLength = measureJson(_root); 125 | #endif 126 | 127 | if (_contentLength) { _isValid = true; } 128 | return _contentLength; 129 | } 130 | 131 | size_t getSize() { return _jsonBuffer.size(); } 132 | 133 | size_t _fillBuffer(uint8_t *data, size_t len){ 134 | ChunkPrint dest(data, _sentLength, len); 135 | 136 | #ifdef ARDUINOJSON_5_COMPATIBILITY 137 | _root.printTo( dest ) ; 138 | #else 139 | serializeJson(_root, dest); 140 | #endif 141 | return len; 142 | } 143 | }; 144 | 145 | class PrettyAsyncJsonResponse: public AsyncJsonResponse { 146 | public: 147 | #ifdef ARDUINOJSON_5_COMPATIBILITY 148 | PrettyAsyncJsonResponse (bool isArray=false) : AsyncJsonResponse{isArray} {} 149 | #else 150 | PrettyAsyncJsonResponse (bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {} 151 | #endif 152 | size_t setLength () { 153 | #ifdef ARDUINOJSON_5_COMPATIBILITY 154 | _contentLength = _root.measurePrettyLength (); 155 | #else 156 | _contentLength = measureJsonPretty(_root); 157 | #endif 158 | if (_contentLength) {_isValid = true;} 159 | return _contentLength; 160 | } 161 | size_t _fillBuffer (uint8_t *data, size_t len) { 162 | ChunkPrint dest (data, _sentLength, len); 163 | #ifdef ARDUINOJSON_5_COMPATIBILITY 164 | _root.prettyPrintTo (dest); 165 | #else 166 | serializeJsonPretty(_root, dest); 167 | #endif 168 | return len; 169 | } 170 | }; 171 | 172 | typedef std::function ArJsonRequestHandlerFunction; 173 | 174 | class AsyncCallbackJsonWebHandler: public AsyncWebHandler { 175 | private: 176 | protected: 177 | const String _uri; 178 | WebRequestMethodComposite _method; 179 | ArJsonRequestHandlerFunction _onRequest; 180 | size_t _contentLength; 181 | #ifndef ARDUINOJSON_5_COMPATIBILITY 182 | const size_t maxJsonBufferSize; 183 | #endif 184 | size_t _maxContentLength; 185 | public: 186 | #ifdef ARDUINOJSON_5_COMPATIBILITY 187 | AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest) 188 | : _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} 189 | #else 190 | AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize=DYNAMIC_JSON_DOCUMENT_SIZE) 191 | : _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} 192 | #endif 193 | 194 | void setMethod(WebRequestMethodComposite method){ _method = method; } 195 | void setMaxContentLength(int maxContentLength){ _maxContentLength = maxContentLength; } 196 | void onRequest(ArJsonRequestHandlerFunction fn){ _onRequest = fn; } 197 | 198 | virtual bool canHandle(AsyncWebServerRequest *request) override final{ 199 | if(!_onRequest) 200 | return false; 201 | 202 | if(!(_method & request->method())) 203 | return false; 204 | 205 | if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) 206 | return false; 207 | 208 | if ( !request->contentType().equalsIgnoreCase(JSON_MIMETYPE) ) 209 | return false; 210 | 211 | request->addInterestingHeader("ANY"); 212 | return true; 213 | } 214 | 215 | virtual void handleRequest(AsyncWebServerRequest *request) override final { 216 | if(_onRequest) { 217 | if (request->_tempObject != NULL) { 218 | 219 | #ifdef ARDUINOJSON_5_COMPATIBILITY 220 | DynamicJsonBuffer jsonBuffer; 221 | JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject)); 222 | if (json.success()) { 223 | #else 224 | DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); 225 | DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); 226 | if(!error) { 227 | JsonVariant json = jsonBuffer.as(); 228 | #endif 229 | 230 | _onRequest(request, json); 231 | return; 232 | } 233 | } 234 | request->send(_contentLength > _maxContentLength ? 413 : 400); 235 | } else { 236 | request->send(500); 237 | } 238 | } 239 | virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { 240 | } 241 | virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { 242 | if (_onRequest) { 243 | _contentLength = total; 244 | if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { 245 | request->_tempObject = malloc(total); 246 | } 247 | if (request->_tempObject != NULL) { 248 | memcpy((uint8_t*)(request->_tempObject) + index, data, len); 249 | } 250 | } 251 | } 252 | virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} 253 | }; 254 | #endif 255 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef ESP32 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #elif defined(ESP8266) 9 | #include 10 | #include 11 | #include 12 | #endif 13 | #include 14 | #include 15 | 16 | // SKETCH BEGIN 17 | AsyncWebServer server(80); 18 | AsyncWebSocket ws("/ws"); 19 | AsyncEventSource events("/events"); 20 | 21 | void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ 22 | if(type == WS_EVT_CONNECT){ 23 | Serial.printf("ws[%s][%u] connect\n", server->url(), client->id()); 24 | client->printf("Hello Client %u :)", client->id()); 25 | client->ping(); 26 | } else if(type == WS_EVT_DISCONNECT){ 27 | Serial.printf("ws[%s][%u] disconnect\n", server->url(), client->id()); 28 | } else if(type == WS_EVT_ERROR){ 29 | Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); 30 | } else if(type == WS_EVT_PONG){ 31 | Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); 32 | } else if(type == WS_EVT_DATA){ 33 | AwsFrameInfo * info = (AwsFrameInfo*)arg; 34 | String msg = ""; 35 | if(info->final && info->index == 0 && info->len == len){ 36 | //the whole message is in a single frame and we got all of it's data 37 | Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); 38 | 39 | if(info->opcode == WS_TEXT){ 40 | for(size_t i=0; i < info->len; i++) { 41 | msg += (char) data[i]; 42 | } 43 | } else { 44 | char buff[3]; 45 | for(size_t i=0; i < info->len; i++) { 46 | sprintf(buff, "%02x ", (uint8_t) data[i]); 47 | msg += buff ; 48 | } 49 | } 50 | Serial.printf("%s\n",msg.c_str()); 51 | 52 | if(info->opcode == WS_TEXT) 53 | client->text("I got your text message"); 54 | else 55 | client->binary("I got your binary message"); 56 | } else { 57 | //message is comprised of multiple frames or the frame is split into multiple packets 58 | if(info->index == 0){ 59 | if(info->num == 0) 60 | Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); 61 | Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len); 62 | } 63 | 64 | Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len); 65 | 66 | if(info->opcode == WS_TEXT){ 67 | for(size_t i=0; i < len; i++) { 68 | msg += (char) data[i]; 69 | } 70 | } else { 71 | char buff[3]; 72 | for(size_t i=0; i < len; i++) { 73 | sprintf(buff, "%02x ", (uint8_t) data[i]); 74 | msg += buff ; 75 | } 76 | } 77 | Serial.printf("%s\n",msg.c_str()); 78 | 79 | if((info->index + len) == info->len){ 80 | Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len); 81 | if(info->final){ 82 | Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); 83 | if(info->message_opcode == WS_TEXT) 84 | client->text("I got your text message"); 85 | else 86 | client->binary("I got your binary message"); 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | 94 | const char* ssid = "*******"; 95 | const char* password = "*******"; 96 | const char * hostName = "esp-async"; 97 | const char* http_username = "admin"; 98 | const char* http_password = "admin"; 99 | 100 | void setup(){ 101 | Serial.begin(115200); 102 | Serial.setDebugOutput(true); 103 | WiFi.mode(WIFI_AP_STA); 104 | WiFi.softAP(hostName); 105 | WiFi.begin(ssid, password); 106 | if (WiFi.waitForConnectResult() != WL_CONNECTED) { 107 | Serial.printf("STA: Failed!\n"); 108 | WiFi.disconnect(false); 109 | delay(1000); 110 | WiFi.begin(ssid, password); 111 | } 112 | 113 | //Send OTA events to the browser 114 | ArduinoOTA.onStart([]() { events.send("Update Start", "ota"); }); 115 | ArduinoOTA.onEnd([]() { events.send("Update End", "ota"); }); 116 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 117 | char p[32]; 118 | sprintf(p, "Progress: %u%%\n", (progress/(total/100))); 119 | events.send(p, "ota"); 120 | }); 121 | ArduinoOTA.onError([](ota_error_t error) { 122 | if(error == OTA_AUTH_ERROR) events.send("Auth Failed", "ota"); 123 | else if(error == OTA_BEGIN_ERROR) events.send("Begin Failed", "ota"); 124 | else if(error == OTA_CONNECT_ERROR) events.send("Connect Failed", "ota"); 125 | else if(error == OTA_RECEIVE_ERROR) events.send("Recieve Failed", "ota"); 126 | else if(error == OTA_END_ERROR) events.send("End Failed", "ota"); 127 | }); 128 | ArduinoOTA.setHostname(hostName); 129 | ArduinoOTA.begin(); 130 | 131 | MDNS.addService("http","tcp",80); 132 | 133 | SPIFFS.begin(); 134 | 135 | ws.onEvent(onWsEvent); 136 | server.addHandler(&ws); 137 | 138 | events.onConnect([](AsyncEventSourceClient *client){ 139 | client->send("hello!",NULL,millis(),1000); 140 | }); 141 | server.addHandler(&events); 142 | 143 | #ifdef ESP32 144 | server.addHandler(new SPIFFSEditor(SPIFFS, http_username,http_password)); 145 | #elif defined(ESP8266) 146 | server.addHandler(new SPIFFSEditor(http_username,http_password)); 147 | #endif 148 | 149 | server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){ 150 | request->send(200, "text/plain", String(ESP.getFreeHeap())); 151 | }); 152 | 153 | server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm"); 154 | 155 | server.onNotFound([](AsyncWebServerRequest *request){ 156 | Serial.printf("NOT_FOUND: "); 157 | if(request->method() == HTTP_GET) 158 | Serial.printf("GET"); 159 | else if(request->method() == HTTP_POST) 160 | Serial.printf("POST"); 161 | else if(request->method() == HTTP_DELETE) 162 | Serial.printf("DELETE"); 163 | else if(request->method() == HTTP_PUT) 164 | Serial.printf("PUT"); 165 | else if(request->method() == HTTP_PATCH) 166 | Serial.printf("PATCH"); 167 | else if(request->method() == HTTP_HEAD) 168 | Serial.printf("HEAD"); 169 | else if(request->method() == HTTP_OPTIONS) 170 | Serial.printf("OPTIONS"); 171 | else 172 | Serial.printf("UNKNOWN"); 173 | Serial.printf(" http://%s%s\n", request->host().c_str(), request->url().c_str()); 174 | 175 | if(request->contentLength()){ 176 | Serial.printf("_CONTENT_TYPE: %s\n", request->contentType().c_str()); 177 | Serial.printf("_CONTENT_LENGTH: %u\n", request->contentLength()); 178 | } 179 | 180 | int headers = request->headers(); 181 | int i; 182 | for(i=0;igetHeader(i); 184 | Serial.printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str()); 185 | } 186 | 187 | int params = request->params(); 188 | for(i=0;igetParam(i); 190 | if(p->isFile()){ 191 | Serial.printf("_FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size()); 192 | } else if(p->isPost()){ 193 | Serial.printf("_POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); 194 | } else { 195 | Serial.printf("_GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); 196 | } 197 | } 198 | 199 | request->send(404); 200 | }); 201 | server.onFileUpload([](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){ 202 | if(!index) 203 | Serial.printf("UploadStart: %s\n", filename.c_str()); 204 | Serial.printf("%s", (const char*)data); 205 | if(final) 206 | Serial.printf("UploadEnd: %s (%u)\n", filename.c_str(), index+len); 207 | }); 208 | server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ 209 | if(!index) 210 | Serial.printf("BodyStart: %u\n", total); 211 | Serial.printf("%s", (const char*)data); 212 | if(index + len == total) 213 | Serial.printf("BodyEnd: %u\n", total); 214 | }); 215 | server.begin(); 216 | } 217 | 218 | void loop(){ 219 | ArduinoOTA.handle(); 220 | ws.cleanupClients(); 221 | } 222 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/AsyncEventSource.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | #include "Arduino.h" 21 | #include "AsyncEventSource.h" 22 | 23 | static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){ 24 | String ev = ""; 25 | 26 | if(reconnect){ 27 | ev += "retry: "; 28 | ev += String(reconnect); 29 | ev += "\r\n"; 30 | } 31 | 32 | if(id){ 33 | ev += "id: "; 34 | ev += String(id); 35 | ev += "\r\n"; 36 | } 37 | 38 | if(event != NULL){ 39 | ev += "event: "; 40 | ev += String(event); 41 | ev += "\r\n"; 42 | } 43 | 44 | if(message != NULL){ 45 | size_t messageLen = strlen(message); 46 | char * lineStart = (char *)message; 47 | char * lineEnd; 48 | do { 49 | char * nextN = strchr(lineStart, '\n'); 50 | char * nextR = strchr(lineStart, '\r'); 51 | if(nextN == NULL && nextR == NULL){ 52 | size_t llen = ((char *)message + messageLen) - lineStart; 53 | char * ldata = (char *)malloc(llen+1); 54 | if(ldata != NULL){ 55 | memcpy(ldata, lineStart, llen); 56 | ldata[llen] = 0; 57 | ev += "data: "; 58 | ev += ldata; 59 | ev += "\r\n\r\n"; 60 | free(ldata); 61 | } 62 | lineStart = (char *)message + messageLen; 63 | } else { 64 | char * nextLine = NULL; 65 | if(nextN != NULL && nextR != NULL){ 66 | if(nextR < nextN){ 67 | lineEnd = nextR; 68 | if(nextN == (nextR + 1)) 69 | nextLine = nextN + 1; 70 | else 71 | nextLine = nextR + 1; 72 | } else { 73 | lineEnd = nextN; 74 | if(nextR == (nextN + 1)) 75 | nextLine = nextR + 1; 76 | else 77 | nextLine = nextN + 1; 78 | } 79 | } else if(nextN != NULL){ 80 | lineEnd = nextN; 81 | nextLine = nextN + 1; 82 | } else { 83 | lineEnd = nextR; 84 | nextLine = nextR + 1; 85 | } 86 | 87 | size_t llen = lineEnd - lineStart; 88 | char * ldata = (char *)malloc(llen+1); 89 | if(ldata != NULL){ 90 | memcpy(ldata, lineStart, llen); 91 | ldata[llen] = 0; 92 | ev += "data: "; 93 | ev += ldata; 94 | ev += "\r\n"; 95 | free(ldata); 96 | } 97 | lineStart = nextLine; 98 | if(lineStart == ((char *)message + messageLen)) 99 | ev += "\r\n"; 100 | } 101 | } while(lineStart < ((char *)message + messageLen)); 102 | } 103 | 104 | return ev; 105 | } 106 | 107 | // Message 108 | 109 | AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len) 110 | : _data(nullptr), _len(len), _sent(0), _acked(0) 111 | { 112 | _data = (uint8_t*)malloc(_len+1); 113 | if(_data == nullptr){ 114 | _len = 0; 115 | } else { 116 | memcpy(_data, data, len); 117 | _data[_len] = 0; 118 | } 119 | } 120 | 121 | AsyncEventSourceMessage::~AsyncEventSourceMessage() { 122 | if(_data != NULL) 123 | free(_data); 124 | } 125 | 126 | size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { 127 | (void)time; 128 | // If the whole message is now acked... 129 | if(_acked + len > _len){ 130 | // Return the number of extra bytes acked (they will be carried on to the next message) 131 | const size_t extra = _acked + len - _len; 132 | _acked = _len; 133 | return extra; 134 | } 135 | // Return that no extra bytes left. 136 | _acked += len; 137 | return 0; 138 | } 139 | 140 | size_t AsyncEventSourceMessage::send(AsyncClient *client) { 141 | const size_t len = _len - _sent; 142 | if(client->space() < len){ 143 | return 0; 144 | } 145 | size_t sent = client->add((const char *)_data, len); 146 | if(client->canSend()) 147 | client->send(); 148 | _sent += sent; 149 | return sent; 150 | } 151 | 152 | // Client 153 | 154 | AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) 155 | : _messageQueue(LinkedList([](AsyncEventSourceMessage *m){ delete m; })) 156 | { 157 | _client = request->client(); 158 | _server = server; 159 | _lastId = 0; 160 | if(request->hasHeader("Last-Event-ID")) 161 | _lastId = atoi(request->getHeader("Last-Event-ID")->value().c_str()); 162 | 163 | _client->setRxTimeout(0); 164 | _client->onError(NULL, NULL); 165 | _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this); 166 | _client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this); 167 | _client->onData(NULL, NULL); 168 | _client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this); 169 | _client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this); 170 | 171 | _server->_addClient(this); 172 | delete request; 173 | } 174 | 175 | AsyncEventSourceClient::~AsyncEventSourceClient(){ 176 | _messageQueue.free(); 177 | close(); 178 | } 179 | 180 | void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){ 181 | if(dataMessage == NULL) 182 | return; 183 | if(!connected()){ 184 | delete dataMessage; 185 | return; 186 | } 187 | if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){ 188 | ets_printf("ERROR: Too many messages queued\n"); 189 | delete dataMessage; 190 | } else { 191 | _messageQueue.add(dataMessage); 192 | } 193 | if(_client->canSend()) 194 | _runQueue(); 195 | } 196 | 197 | void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){ 198 | while(len && !_messageQueue.isEmpty()){ 199 | len = _messageQueue.front()->ack(len, time); 200 | if(_messageQueue.front()->finished()) 201 | _messageQueue.remove(_messageQueue.front()); 202 | } 203 | 204 | _runQueue(); 205 | } 206 | 207 | void AsyncEventSourceClient::_onPoll(){ 208 | if(!_messageQueue.isEmpty()){ 209 | _runQueue(); 210 | } 211 | } 212 | 213 | 214 | void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){ 215 | _client->close(true); 216 | } 217 | 218 | void AsyncEventSourceClient::_onDisconnect(){ 219 | _client = NULL; 220 | _server->_handleDisconnect(this); 221 | } 222 | 223 | void AsyncEventSourceClient::close(){ 224 | if(_client != NULL) 225 | _client->close(); 226 | } 227 | 228 | void AsyncEventSourceClient::write(const char * message, size_t len){ 229 | _queueMessage(new AsyncEventSourceMessage(message, len)); 230 | } 231 | 232 | void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ 233 | String ev = generateEventMessage(message, event, id, reconnect); 234 | _queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length())); 235 | } 236 | 237 | void AsyncEventSourceClient::_runQueue(){ 238 | while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){ 239 | _messageQueue.remove(_messageQueue.front()); 240 | } 241 | 242 | for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) 243 | { 244 | if(!(*i)->sent()) 245 | (*i)->send(_client); 246 | } 247 | } 248 | 249 | 250 | // Handler 251 | 252 | AsyncEventSource::AsyncEventSource(const String& url) 253 | : _url(url) 254 | , _clients(LinkedList([](AsyncEventSourceClient *c){ delete c; })) 255 | , _connectcb(NULL) 256 | {} 257 | 258 | AsyncEventSource::~AsyncEventSource(){ 259 | close(); 260 | } 261 | 262 | void AsyncEventSource::onConnect(ArEventHandlerFunction cb){ 263 | _connectcb = cb; 264 | } 265 | 266 | void AsyncEventSource::_addClient(AsyncEventSourceClient * client){ 267 | /*char * temp = (char *)malloc(2054); 268 | if(temp != NULL){ 269 | memset(temp+1,' ',2048); 270 | temp[0] = ':'; 271 | temp[2049] = '\r'; 272 | temp[2050] = '\n'; 273 | temp[2051] = '\r'; 274 | temp[2052] = '\n'; 275 | temp[2053] = 0; 276 | client->write((const char *)temp, 2053); 277 | free(temp); 278 | }*/ 279 | 280 | _clients.add(client); 281 | if(_connectcb) 282 | _connectcb(client); 283 | } 284 | 285 | void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){ 286 | _clients.remove(client); 287 | } 288 | 289 | void AsyncEventSource::close(){ 290 | for(const auto &c: _clients){ 291 | if(c->connected()) 292 | c->close(); 293 | } 294 | } 295 | 296 | // pmb fix 297 | size_t AsyncEventSource::avgPacketsWaiting() const { 298 | if(_clients.isEmpty()) 299 | return 0; 300 | 301 | size_t aql=0; 302 | uint32_t nConnectedClients=0; 303 | 304 | for(const auto &c: _clients){ 305 | if(c->connected()) { 306 | aql+=c->packetsWaiting(); 307 | ++nConnectedClients; 308 | } 309 | } 310 | // return aql / nConnectedClients; 311 | return ((aql) + (nConnectedClients/2))/(nConnectedClients); // round up 312 | } 313 | 314 | void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ 315 | 316 | 317 | String ev = generateEventMessage(message, event, id, reconnect); 318 | for(const auto &c: _clients){ 319 | if(c->connected()) { 320 | c->write(ev.c_str(), ev.length()); 321 | } 322 | } 323 | } 324 | 325 | size_t AsyncEventSource::count() const { 326 | return _clients.count_if([](AsyncEventSourceClient *c){ 327 | return c->connected(); 328 | }); 329 | } 330 | 331 | bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){ 332 | if(request->method() != HTTP_GET || !request->url().equals(_url)) { 333 | return false; 334 | } 335 | request->addInterestingHeader("Last-Event-ID"); 336 | return true; 337 | } 338 | 339 | void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){ 340 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 341 | return request->requestAuthentication(); 342 | request->send(new AsyncEventSourceResponse(this)); 343 | } 344 | 345 | // Response 346 | 347 | AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){ 348 | _server = server; 349 | _code = 200; 350 | _contentType = "text/event-stream"; 351 | _sendContentLength = false; 352 | addHeader("Cache-Control", "no-cache"); 353 | addHeader("Connection","keep-alive"); 354 | } 355 | 356 | void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){ 357 | String out = _assembleHead(request->version()); 358 | request->client()->write(out.c_str(), _headLength); 359 | _state = RESPONSE_WAIT_ACK; 360 | } 361 | 362 | size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){ 363 | if(len){ 364 | new AsyncEventSourceClient(request, _server); 365 | } 366 | return 0; 367 | } 368 | 369 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/AsyncWebSocket.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef ASYNCWEBSOCKET_H_ 22 | #define ASYNCWEBSOCKET_H_ 23 | 24 | #include 25 | #ifdef ESP32 26 | #include 27 | #define WS_MAX_QUEUED_MESSAGES 32 28 | #else 29 | #include 30 | #define WS_MAX_QUEUED_MESSAGES 8 31 | #endif 32 | #include 33 | 34 | #include "AsyncWebSynchronization.h" 35 | 36 | #ifdef ESP8266 37 | #include 38 | #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library 39 | #include <../src/Hash.h> 40 | #endif 41 | #endif 42 | 43 | #ifdef ESP32 44 | #define DEFAULT_MAX_WS_CLIENTS 8 45 | #else 46 | #define DEFAULT_MAX_WS_CLIENTS 4 47 | #endif 48 | 49 | class AsyncWebSocket; 50 | class AsyncWebSocketResponse; 51 | class AsyncWebSocketClient; 52 | class AsyncWebSocketControl; 53 | 54 | typedef struct { 55 | /** Message type as defined by enum AwsFrameType. 56 | * Note: Applications will only see WS_TEXT and WS_BINARY. 57 | * All other types are handled by the library. */ 58 | uint8_t message_opcode; 59 | /** Frame number of a fragmented message. */ 60 | uint32_t num; 61 | /** Is this the last frame in a fragmented message ?*/ 62 | uint8_t final; 63 | /** Is this frame masked? */ 64 | uint8_t masked; 65 | /** Message type as defined by enum AwsFrameType. 66 | * This value is the same as message_opcode for non-fragmented 67 | * messages, but may also be WS_CONTINUATION in a fragmented message. */ 68 | uint8_t opcode; 69 | /** Length of the current frame. 70 | * This equals the total length of the message if num == 0 && final == true */ 71 | uint64_t len; 72 | /** Mask key */ 73 | uint8_t mask[4]; 74 | /** Offset of the data inside the current frame. */ 75 | uint64_t index; 76 | } AwsFrameInfo; 77 | 78 | typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus; 79 | typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType; 80 | typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus; 81 | typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType; 82 | 83 | class AsyncWebSocketMessageBuffer { 84 | private: 85 | uint8_t * _data; 86 | size_t _len; 87 | bool _lock; 88 | uint32_t _count; 89 | 90 | public: 91 | AsyncWebSocketMessageBuffer(); 92 | AsyncWebSocketMessageBuffer(size_t size); 93 | AsyncWebSocketMessageBuffer(uint8_t * data, size_t size); 94 | AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer &); 95 | AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&); 96 | ~AsyncWebSocketMessageBuffer(); 97 | void operator ++(int i) { (void)i; _count++; } 98 | void operator --(int i) { (void)i; if (_count > 0) { _count--; } ; } 99 | bool reserve(size_t size); 100 | void lock() { _lock = true; } 101 | void unlock() { _lock = false; } 102 | uint8_t * get() { return _data; } 103 | size_t length() { return _len; } 104 | uint32_t count() { return _count; } 105 | bool canDelete() { return (!_count && !_lock); } 106 | 107 | friend AsyncWebSocket; 108 | 109 | }; 110 | 111 | class AsyncWebSocketMessage { 112 | protected: 113 | uint8_t _opcode; 114 | bool _mask; 115 | AwsMessageStatus _status; 116 | public: 117 | AsyncWebSocketMessage():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR){} 118 | virtual ~AsyncWebSocketMessage(){} 119 | virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))){} 120 | virtual size_t send(AsyncClient *client __attribute__((unused))){ return 0; } 121 | virtual bool finished(){ return _status != WS_MSG_SENDING; } 122 | virtual bool betweenFrames() const { return false; } 123 | }; 124 | 125 | class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage { 126 | private: 127 | size_t _len; 128 | size_t _sent; 129 | size_t _ack; 130 | size_t _acked; 131 | uint8_t * _data; 132 | public: 133 | AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false); 134 | AsyncWebSocketBasicMessage(uint8_t opcode=WS_TEXT, bool mask=false); 135 | virtual ~AsyncWebSocketBasicMessage() override; 136 | virtual bool betweenFrames() const override { return _acked == _ack; } 137 | virtual void ack(size_t len, uint32_t time) override ; 138 | virtual size_t send(AsyncClient *client) override ; 139 | }; 140 | 141 | class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage { 142 | private: 143 | uint8_t * _data; 144 | size_t _len; 145 | size_t _sent; 146 | size_t _ack; 147 | size_t _acked; 148 | AsyncWebSocketMessageBuffer * _WSbuffer; 149 | public: 150 | AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode=WS_TEXT, bool mask=false); 151 | virtual ~AsyncWebSocketMultiMessage() override; 152 | virtual bool betweenFrames() const override { return _acked == _ack; } 153 | virtual void ack(size_t len, uint32_t time) override ; 154 | virtual size_t send(AsyncClient *client) override ; 155 | }; 156 | 157 | class AsyncWebSocketClient { 158 | private: 159 | AsyncClient *_client; 160 | AsyncWebSocket *_server; 161 | uint32_t _clientId; 162 | AwsClientStatus _status; 163 | 164 | LinkedList _controlQueue; 165 | LinkedList _messageQueue; 166 | 167 | uint8_t _pstate; 168 | AwsFrameInfo _pinfo; 169 | 170 | uint32_t _lastMessageTime; 171 | uint32_t _keepAlivePeriod; 172 | 173 | void _queueMessage(AsyncWebSocketMessage *dataMessage); 174 | void _queueControl(AsyncWebSocketControl *controlMessage); 175 | void _runQueue(); 176 | 177 | public: 178 | void *_tempObject; 179 | 180 | AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server); 181 | ~AsyncWebSocketClient(); 182 | 183 | //client id increments for the given server 184 | uint32_t id(){ return _clientId; } 185 | AwsClientStatus status(){ return _status; } 186 | AsyncClient* client(){ return _client; } 187 | AsyncWebSocket *server(){ return _server; } 188 | AwsFrameInfo const &pinfo() const { return _pinfo; } 189 | 190 | IPAddress remoteIP(); 191 | uint16_t remotePort(); 192 | 193 | //control frames 194 | void close(uint16_t code=0, const char * message=NULL); 195 | void ping(uint8_t *data=NULL, size_t len=0); 196 | 197 | //set auto-ping period in seconds. disabled if zero (default) 198 | void keepAlivePeriod(uint16_t seconds){ 199 | _keepAlivePeriod = seconds * 1000; 200 | } 201 | uint16_t keepAlivePeriod(){ 202 | return (uint16_t)(_keepAlivePeriod / 1000); 203 | } 204 | 205 | //data packets 206 | void message(AsyncWebSocketMessage *message){ _queueMessage(message); } 207 | bool queueIsFull(); 208 | 209 | size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3))); 210 | #ifndef ESP32 211 | size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); 212 | #endif 213 | void text(const char * message, size_t len); 214 | void text(const char * message); 215 | void text(uint8_t * message, size_t len); 216 | void text(char * message); 217 | void text(const String &message); 218 | void text(const __FlashStringHelper *data); 219 | void text(AsyncWebSocketMessageBuffer *buffer); 220 | 221 | void binary(const char * message, size_t len); 222 | void binary(const char * message); 223 | void binary(uint8_t * message, size_t len); 224 | void binary(char * message); 225 | void binary(const String &message); 226 | void binary(const __FlashStringHelper *data, size_t len); 227 | void binary(AsyncWebSocketMessageBuffer *buffer); 228 | 229 | bool canSend() { return _messageQueue.length() < WS_MAX_QUEUED_MESSAGES; } 230 | 231 | //system callbacks (do not call) 232 | void _onAck(size_t len, uint32_t time); 233 | void _onError(int8_t); 234 | void _onPoll(); 235 | void _onTimeout(uint32_t time); 236 | void _onDisconnect(); 237 | void _onData(void *pbuf, size_t plen); 238 | }; 239 | 240 | typedef std::function AwsEventHandler; 241 | 242 | //WebServer Handler implementation that plays the role of a socket server 243 | class AsyncWebSocket: public AsyncWebHandler { 244 | public: 245 | typedef LinkedList AsyncWebSocketClientLinkedList; 246 | private: 247 | String _url; 248 | AsyncWebSocketClientLinkedList _clients; 249 | uint32_t _cNextId; 250 | AwsEventHandler _eventHandler; 251 | bool _enabled; 252 | AsyncWebLock _lock; 253 | 254 | public: 255 | AsyncWebSocket(const String& url); 256 | ~AsyncWebSocket(); 257 | const char * url() const { return _url.c_str(); } 258 | void enable(bool e){ _enabled = e; } 259 | bool enabled() const { return _enabled; } 260 | bool availableForWriteAll(); 261 | bool availableForWrite(uint32_t id); 262 | 263 | size_t count() const; 264 | AsyncWebSocketClient * client(uint32_t id); 265 | bool hasClient(uint32_t id){ return client(id) != NULL; } 266 | 267 | void close(uint32_t id, uint16_t code=0, const char * message=NULL); 268 | void closeAll(uint16_t code=0, const char * message=NULL); 269 | void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); 270 | 271 | void ping(uint32_t id, uint8_t *data=NULL, size_t len=0); 272 | void pingAll(uint8_t *data=NULL, size_t len=0); // done 273 | 274 | void text(uint32_t id, const char * message, size_t len); 275 | void text(uint32_t id, const char * message); 276 | void text(uint32_t id, uint8_t * message, size_t len); 277 | void text(uint32_t id, char * message); 278 | void text(uint32_t id, const String &message); 279 | void text(uint32_t id, const __FlashStringHelper *message); 280 | 281 | void textAll(const char * message, size_t len); 282 | void textAll(const char * message); 283 | void textAll(uint8_t * message, size_t len); 284 | void textAll(char * message); 285 | void textAll(const String &message); 286 | void textAll(const __FlashStringHelper *message); // need to convert 287 | void textAll(AsyncWebSocketMessageBuffer * buffer); 288 | 289 | void binary(uint32_t id, const char * message, size_t len); 290 | void binary(uint32_t id, const char * message); 291 | void binary(uint32_t id, uint8_t * message, size_t len); 292 | void binary(uint32_t id, char * message); 293 | void binary(uint32_t id, const String &message); 294 | void binary(uint32_t id, const __FlashStringHelper *message, size_t len); 295 | 296 | void binaryAll(const char * message, size_t len); 297 | void binaryAll(const char * message); 298 | void binaryAll(uint8_t * message, size_t len); 299 | void binaryAll(char * message); 300 | void binaryAll(const String &message); 301 | void binaryAll(const __FlashStringHelper *message, size_t len); 302 | void binaryAll(AsyncWebSocketMessageBuffer * buffer); 303 | 304 | void message(uint32_t id, AsyncWebSocketMessage *message); 305 | void messageAll(AsyncWebSocketMultiMessage *message); 306 | 307 | size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4))); 308 | size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3))); 309 | #ifndef ESP32 310 | size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4))); 311 | #endif 312 | size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); 313 | 314 | //event listener 315 | void onEvent(AwsEventHandler handler){ 316 | _eventHandler = handler; 317 | } 318 | 319 | //system callbacks (do not call) 320 | uint32_t _getNextId(){ return _cNextId++; } 321 | void _addClient(AsyncWebSocketClient * client); 322 | void _handleDisconnect(AsyncWebSocketClient * client); 323 | void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); 324 | virtual bool canHandle(AsyncWebServerRequest *request) override final; 325 | virtual void handleRequest(AsyncWebServerRequest *request) override final; 326 | 327 | 328 | // messagebuffer functions/objects. 329 | AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0); 330 | AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size); 331 | LinkedList _buffers; 332 | void _cleanBuffers(); 333 | 334 | AsyncWebSocketClientLinkedList getClients() const; 335 | }; 336 | 337 | //WebServer response to authenticate the socket and detach the tcp client from the web server request 338 | class AsyncWebSocketResponse: public AsyncWebServerResponse { 339 | private: 340 | String _content; 341 | AsyncWebSocket *_server; 342 | public: 343 | AsyncWebSocketResponse(const String& key, AsyncWebSocket *server); 344 | void _respond(AsyncWebServerRequest *request); 345 | size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 346 | bool _sourceValid() const { return true; } 347 | }; 348 | 349 | 350 | #endif /* ASYNCWEBSOCKET_H_ */ 351 | -------------------------------------------------------------------------------- /lib/ESPAsyncWebSrv/src/edit.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ESP Editor 6 | 135 | 609 | 610 | 618 | 619 | 620 |
621 |
622 |
623 |
624 | 625 | 626 | 627 | 628 | --------------------------------------------------------------------------------