├── .gitignore ├── .travis.yml ├── Docs ├── board-selection-small.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-README.md ├── favicon.ico ├── headline-image.png ├── hookup.png ├── linearled │ ├── README.md │ └── linearled.c ├── logo-big.png ├── logo.png ├── logo.svg ├── mygodsitsfullofcats.png ├── screenshot.png └── webcams.programmer.jpg ├── LICENSE ├── README.md ├── app_httpd.cpp ├── camera_pins.h ├── css.h ├── esp32-cam-webserver.ino ├── index_other.h ├── index_ov2640.h ├── index_ov3660.h ├── myconfig.sample.h ├── platformio.ini ├── src ├── favicons.h ├── jsonlib │ ├── README.md │ ├── jsonlib-LICENSE │ ├── jsonlib.cpp │ └── jsonlib.h └── logo.h ├── storage.cpp └── storage.h /.gitignore: -------------------------------------------------------------------------------- 1 | myconfig.h 2 | .pioenvs 3 | .piolibdeps 4 | .clang_complete 5 | .gcc-flags.json 6 | .pio 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: bash 4 | 5 | os: 6 | - linux 7 | 8 | before_script: 9 | - "export DISPLAY=:99.0" 10 | - sleep 3 # give xvfb some time to start 11 | - wget http://downloads.arduino.cc/arduino-1.8.13-linux64.tar.xz 12 | - tar xf arduino-1.8.13-linux64.tar.xz 13 | - mv arduino-1.8.13 $HOME/arduino_ide 14 | - cd $HOME/arduino_ide/hardware 15 | - mkdir esp32 16 | - cd esp32 17 | - git clone --depth 1 https://github.com/espressif/arduino-esp32.git esp32 18 | - cd esp32 19 | - git submodule update --init --recursive 20 | - cd tools 21 | - python get.py 22 | - pip install --user platformio 23 | - platformio update 24 | 25 | script: 26 | - cd $TRAVIS_BUILD_DIR 27 | - export PATH="$HOME/arduino_ide:$PATH" 28 | - arduino --board esp32:esp32:esp32:PartitionScheme=huge_app,FlashFreq=80 --pref compiler.warning_level=all --save-prefs 29 | - arduino --verbose --verify esp32-cam-webserver.ino 30 | - cp --preserve --verbose myconfig.sample.h myconfig.h 31 | - arduino --verbose --verify esp32-cam-webserver.ino 32 | - platformio run 33 | 34 | 35 | notifications: 36 | email: 37 | on_success: change 38 | on_failure: change 39 | 40 | -------------------------------------------------------------------------------- /Docs/board-selection-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aircoookie/esp32-cam-webserver/7d29e36853b1983acf286f937d5e61aefd8e9023/Docs/board-selection-small.png -------------------------------------------------------------------------------- /Docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aircoookie/esp32-cam-webserver/7d29e36853b1983acf286f937d5e61aefd8e9023/Docs/favicon-16x16.png -------------------------------------------------------------------------------- /Docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aircoookie/esp32-cam-webserver/7d29e36853b1983acf286f937d5e61aefd8e9023/Docs/favicon-32x32.png -------------------------------------------------------------------------------- /Docs/favicon-README.md: -------------------------------------------------------------------------------- 1 | # Favicons 2 | 3 | Source: A logo I created from the espressif logo, using inkscape 4 | 5 | ![logo image](../Docs/logo.svg) 6 | 7 | ## The 16x16 and 32x32 png images were extracted from a Favicon Package 8 | 9 | This package was generated with [RealFaviconGenerator](https://realfavicongenerator.net/) 10 | 11 | A very handy site, dont forget to select compression options in the 'html5' section, they are in a hard to spot tab. Doing this reduced the `.png`sizes by ~74% :-) 12 | 13 | ## The favicon.ico itself came from the command line 14 | 15 | The [Imagemagick](https://imagemagick.org/) tool provides a simple image converter that can create `.ico` files from a source image in another format. I simply needed to use this on the 32x32 png icon to make a suitably high-definition icon file. 16 | ``` 17 | $ convert favicon-32x32.png favicon.ico 18 | ``` 19 | 20 | ## favicons.h 21 | The icon files were packed into the `favicons.h` header using `xxd -i ` to generate the C compatible data structures, and then editing that with comments and adding PROGMEM directives to save on ram use. They should be stable and unlikely to change in the future. 22 | -------------------------------------------------------------------------------- /Docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aircoookie/esp32-cam-webserver/7d29e36853b1983acf286f937d5e61aefd8e9023/Docs/favicon.ico -------------------------------------------------------------------------------- /Docs/headline-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aircoookie/esp32-cam-webserver/7d29e36853b1983acf286f937d5e61aefd8e9023/Docs/headline-image.png -------------------------------------------------------------------------------- /Docs/hookup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aircoookie/esp32-cam-webserver/7d29e36853b1983acf286f937d5e61aefd8e9023/Docs/hookup.png -------------------------------------------------------------------------------- /Docs/linearled/README.md: -------------------------------------------------------------------------------- 1 | ## A Simple C program used to debug and examine the log function I used for LED control 2 | 3 | The bit width (precision) for the PWM can be specified (max. 16 bit) 4 | 5 | The Input values are integer percentages from 0-100. 6 | 7 | The Output is a logarithmically scaling integer between 0 and the max PWM value. 8 | 9 | ``` C 10 | /* Dump linear led values */ 11 | #include "stdio.h" 12 | #include 13 | #include 14 | 15 | //int pwmBits = 9; // Number of PWM bits 16 | 17 | int main(int argc, char **argv) { 18 | if (argc != 2) { 19 | printf("\nERROR: This utility expects a single parameter; the bit-width of the PWM stream\n\n"); 20 | return 1; } 21 | 22 | int pwmBits = atoi(argv[1]); 23 | 24 | if (pwmBits < 2 || pwmBits > 16) { 25 | printf("\nERROR: '%s' is not an integer in the range 2-16.\n\n", argv[1]); 26 | return 1; } 27 | 28 | int pwmMax = pow(2,pwmBits)-1; 29 | 30 | printf("\nThe PWM Bit width (resolution) specified is %i, pwm range is from 0 to %i\n-----------\n", pwmBits, pwmMax); 31 | 32 | for (int val = 0; val <= 100; val++) { 33 | int pwm = round((pow(2,(1+(val*0.02)))-2)/6*pwmMax); 34 | printf(" %3i : %5i\n", val, pwm); 35 | } 36 | printf("\n"); 37 | return 0; 38 | } 39 | ``` 40 | 41 | Adjust the width as necesscary and compile with: 42 | 43 | $ gcc linearled.c -o linearled -lm 44 | 45 | And run to see the results: 46 | ```bash 47 | $ ./linearled 9 48 | 49 | The PWM Bit width (resolution) specified is 9, pwm range is from 0 to 511 50 | ----------- 51 | 0 : 0 52 | 1 : 2 53 | 2 : 5 54 | 3 : 7 55 | 4 : 10 56 | 5 : 12 57 | 6 : 15 58 | 7 : 17 59 | 8 : 20 60 | 9 : 23 61 | 10 : 25 62 | 11 : 28 63 | 12 : 31 64 | 13 : 34 65 | 14 : 36 66 | 15 : 39 67 | 16 : 42 68 | 17 : 45 69 | 18 : 48 70 | 19 : 51 71 | 20 : 54 72 | 21 : 58 73 | 22 : 61 74 | 23 : 64 75 | 24 : 67 76 | 25 : 71 77 | 26 : 74 78 | 27 : 77 79 | 28 : 81 80 | 29 : 84 81 | 30 : 88 82 | 31 : 91 83 | 32 : 95 84 | 33 : 99 85 | 34 : 103 86 | 35 : 106 87 | 36 : 110 88 | 37 : 114 89 | 38 : 118 90 | 39 : 122 91 | 40 : 126 92 | 41 : 130 93 | 42 : 135 94 | 43 : 139 95 | 44 : 143 96 | 45 : 148 97 | 46 : 152 98 | 47 : 156 99 | 48 : 161 100 | 49 : 166 101 | 50 : 170 102 | 51 : 175 103 | 52 : 180 104 | 53 : 185 105 | 54 : 190 106 | 55 : 195 107 | 56 : 200 108 | 57 : 205 109 | 58 : 210 110 | 59 : 216 111 | 60 : 221 112 | 61 : 226 113 | 62 : 232 114 | 63 : 238 115 | 64 : 243 116 | 65 : 249 117 | 66 : 255 118 | 67 : 261 119 | 68 : 267 120 | 69 : 273 121 | 70 : 279 122 | 71 : 285 123 | 72 : 292 124 | 73 : 298 125 | 74 : 305 126 | 75 : 311 127 | 76 : 318 128 | 77 : 325 129 | 78 : 332 130 | 79 : 339 131 | 80 : 346 132 | 81 : 353 133 | 82 : 361 134 | 83 : 368 135 | 84 : 375 136 | 85 : 383 137 | 86 : 391 138 | 87 : 399 139 | 88 : 407 140 | 89 : 415 141 | 90 : 423 142 | 91 : 431 143 | 92 : 439 144 | 93 : 448 145 | 94 : 457 146 | 95 : 465 147 | 96 : 474 148 | 97 : 483 149 | 98 : 492 150 | 99 : 502 151 | 100 : 511 152 | ``` 153 | 154 | The code can be adapted into your custom LED setting function 155 | 156 | 157 | -------------------------------------------------------------------------------- /Docs/linearled/linearled.c: -------------------------------------------------------------------------------- 1 | /* Dump linear led values */ 2 | #include "stdio.h" 3 | #include 4 | #include 5 | 6 | //int pwmBits = 9; // Number of PWM bits 7 | 8 | int main(int argc, char **argv) { 9 | if (argc != 2) { 10 | printf("\nERROR: This utility expects a single parameter; the bit-width of the PWM stream\n\n"); 11 | return 1; } 12 | 13 | int pwmBits = atoi(argv[1]); 14 | 15 | if (pwmBits < 2 || pwmBits > 16) { 16 | printf("\nERROR: '%s' is not an integer in the range 2-16.\n\n", argv[1]); 17 | return 1; } 18 | 19 | int pwmMax = pow(2,pwmBits)-1; 20 | 21 | printf("\nThe PWM Bit width (resolution) specified is %i, pwm range is from 0 to %i\n-----------\n", pwmBits, pwmMax); 22 | 23 | for (int val = 0; val <= 100; val++) { 24 | int pwm = round((pow(2,(1+(val*0.02)))-2)/6*pwmMax); 25 | printf(" %3i : %5i\n", val, pwm); 26 | } 27 | printf("\n"); 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Docs/logo-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aircoookie/esp32-cam-webserver/7d29e36853b1983acf286f937d5e61aefd8e9023/Docs/logo-big.png -------------------------------------------------------------------------------- /Docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aircoookie/esp32-cam-webserver/7d29e36853b1983acf286f937d5e61aefd8e9023/Docs/logo.png -------------------------------------------------------------------------------- /Docs/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 32 | 33 | ESP32 CAM Webserver - logo 35 | 42 | 49 | 56 | 61 | 65 | 69 | 73 | 77 | 81 | 85 | 86 | 89 | 92 | 95 | 98 | 101 | 104 | 107 | 110 | 113 | 116 | 119 | 122 | 125 | 128 | 131 | 132 | -------------------------------------------------------------------------------- /Docs/mygodsitsfullofcats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aircoookie/esp32-cam-webserver/7d29e36853b1983acf286f937d5e61aefd8e9023/Docs/mygodsitsfullofcats.png -------------------------------------------------------------------------------- /Docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aircoookie/esp32-cam-webserver/7d29e36853b1983acf286f937d5e61aefd8e9023/Docs/screenshot.png -------------------------------------------------------------------------------- /Docs/webcams.programmer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aircoookie/esp32-cam-webserver/7d29e36853b1983acf286f937d5e61aefd8e9023/Docs/webcams.programmer.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32-CAM example revisited.     [![CI Status](https://travis-ci.org/easytarget/esp32-cam-webserver.svg?branch=master)](https://travis-ci.org/github/easytarget/esp32-cam-webserver)    ![ESP-EYE logo](Docs/logo.svg) 2 | 3 | ## Taken from the ESP examples, and expanded 4 | This sketch is a extension/expansion/rework of the 'official' ESP32 Camera example sketch from Espressif: 5 | 6 | https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/Camera/CameraWebServer 7 | 8 | But expanded with: 9 | * More options for default network and camera settings 10 | * Control of on-board lamps, view rotation in the browser 11 | * Dedicated standalone stream viewer 12 | * Lots of minor fixes and tweaks 13 | 14 | The original example is a bit incomprehensible and hard to modify as supplied. It is very focused on showing off the face recognition capabilities, and forgets the 'webcam' part. 15 | * There are many other variants of a webcam server for these modules online, but most are created for a specific scenario and not good for general, casual, webcam use. 16 | 17 | ![My Gods, it's full of cats!](Docs/mygodsitsfullofcats.png) 18 | 19 | Hopefully this expanded example is more useful for those users who wish to set up a simple ESP32 based webcam using the cheap(ish) modules freely available online. Especially the AI-THINKER board: 20 | 21 | #### AI-THINKER ESP32-CAM vs Other Modules: 22 | 23 | I have four [AI-THINKER ESP32-CAM](https://github.com/raphaelbs/esp32-cam-ai-thinker/blob/master/assets/ESP32-CAM_Product_Specification.pdf) boards, so the descriptions below are for that board. But I took care to leave the default definitions and controls for other boards in the example intact. You may need to adjust the programming method to suit the your board, look for examples online. 24 | 25 | * For some other good examples and information on ESP32 based webcams I also recommend the sketches here: 26 | https://github.com/raphaelbs/esp32-cam-ai-thinker 27 | * The AI thinker wiki can be quite informative, when run through an online translator and read sensibly: 28 | https://wiki.ai-thinker.com/esp32-cam 29 | * Default pinouts are also included for WRover Kit, ESP Eye and M5Stack esp32 camera modules. 30 | I do not have any of these boards, so they are untested by me. Please [let me know](https://github.com/easytarget/esp32-cam-webserver/issues) if you find issues or have a board not [in the list](./camera_pins.h). 31 | 32 | ## Troubleshooting: 33 | 34 | Please read this excellent guide for help with all the common issues: 35 | https://randomnerdtutorials.com/esp32-cam-troubleshooting-guide/ 36 | 37 | ## Setup: 38 | 39 | * For programming you will need a suitable development environment, I use the Arduino IDE, but this code should work in the Espressif development environment too. 40 | * Make sure you are using the [latest version](https://www.arduino.cc/en/main/software#download) of the IDE and then follow [This Guide](https://github.com/espressif/arduino-esp32/blob/master/docs/arduino-ide/boards_manager.md) to set up the Espressif Arduino core for the IDE. 41 | * If you have a development board (anything that can be programmed via a standard USB cable/jack on the board itself) you are in luck. Just plug it in and skip ahead to the [config](#config) section. Remember to set your board model. 42 | * The AI-THINKER board requires use of an external **3.3v** serial adapter to program; I use a `FTDI Friend` adapter, for more about this read AdaFruits excellent [FTDI Friend guide](https://learn.adafruit.com/ftdi-friend). 43 | * Be careful not to use a 5v serial adapter since this will damage the ESP32. 44 | 45 | ### Wiring for AI-THINKER Boards (and similar clone-alikes) 46 | 47 | Is pretty simple, You just need jumper wires, no soldering really required, see the diagram below. 48 | ![Hoockup](Docs/hookup.png) 49 | * Connect the **RX** line from the serial adapter to the **TX** pin on ESP32 50 | * The adapters **TX** line goes to the ESP32 **RX** pin 51 | * The **GPIO0** pin of the ESP32 must be held LOW (to ground) when the unit is powered up to allow it to enter it's programming mode. This can be done with simple jumper cable connected at poweron, fitting a switch for this is useful if you will be reprogramming a lot. 52 | * You must supply 5v to the ESP32 in order to power it during programming, the FTDI board can supply this. 53 | 54 | ### Config 55 | 56 | By default the sketch assumes you have an AI-THINKER board, it creates an AccessPoint called `ESP32-CAM-CONNECT` and with the password `InsecurePassword`; connect to that and then browse to [`http://192.168.4.1/`](http://192.168.4.1/). This is nice and easy for testing and demo purposes. 57 | 58 | To make a permanent config for a different board, or with your home wifi settings etc. copy (or rename) the file `myconfig.sample.h` in the sketch folder to `myconfig.h` 59 | 60 | Additional boot-time changes to other camera properties can be made in the main sketch file; see the comments [around line 253](https://github.com/easytarget/esp32-cam-webserver/blob/doc-options/esp32-cam-webserver.ino#L253). 61 | 62 | You can now set a camera name, board model, wifi settings and some other defaults in that file. And because this is your private copy it will not get overwritten if you update the main sketch! 63 | 64 | ### Programming 65 | 66 | Assuming you are using the latest Espressif Arduino core the AI-THINKER board (or whatever you use) will appear in the ESP32 Arduino section of the boards list. 67 | 68 | ![IDE board config](Docs/board-selection-small.png) 69 | 70 | Compile and upload the code from the IDE, when the `Connecting...` appears in the console reboot the ESP32 module while keeping **GPIO0** grounded. You can release GPO0 once the sketch is uploading, most boards have a 'boot' button to trigger a reboot. 71 | 72 | Once the upload completes (be patient, it can be a bit slow) open the serial monitor in the IDE and reboot the board again without GPIO0 grounded. In the serial monitor you should see the board start, connect to the wifi and then report the IP address it has been assigned. 73 | 74 | If you have a status LED configured it will give a double flash when it begins attempting to conenct to WiFi, and five short flashes once it has succeeded. It will also flash briefly when you access the camera to change settings. 75 | 76 | Go to the URL given in the serial output, the web UI should appear with the settings panel open. Click away! 77 | 78 | ## My Modifications: 79 | 80 | The basic example is extended to allow control of a high power LED FlashLamps, which are present on my modules. It can also blink a status LED to show when it connects to WiFi. 81 | 82 | The WiFi details can be stored in an (optional) header file to allow easier code development, and a camera name for the UI title can be configured. The lamp and status LED's are optional, and the lamp uses a exponential scale for brightness so that the control has some finess. 83 | 84 | The compressed and binary encoded HTML used in the example has been unpacked to raw text, this makes it much easier to access and modify the Javascript and UI elements. Given the relatively small size of the index page there is very little benefit from compressing it. 85 | 86 | The streamviewer, lamp control, and all the other new features have been added. I have tried to retain the basic structure of the original example,extending where necesscary. 87 | 88 | I have left all the Face Recognition code untouched, it works, and with good lighting and camera position it can work quite well. But you can only use it in low-resolution modes, and it is not something I will be using. 89 | 90 | The web UI has had minor changes to add the lamp control (only when enabled), I also made the 'Start Stream' and 'Snapshot' controls more prominent, and added feedback of the camera name + firmware. 91 | 92 | I would also like to shoutout to @jmfloyd; who suggested rotating the image in the browser since the esp32 itself cannot do this. 93 | 94 | ## Notes: 95 | 96 | * I only have AI-THINKER modules with OV2640 camera installed; so I have only been able to test with this combination. I have attempted to preserve all the code for other boards and the OV3660 module, and I have merged all changes for the WebUI etc, but I cannot guarantee operation for these. 97 | * I created a small board with a handy switch for power, a pushbutton for the GPIO0 programming switch, and a socket for the AI-THINKER board. This proved very useful for development work and programming multiple devices. 98 | * I found some excellent [cases on Thingieverse](https://www.thingiverse.com/thing:3708345). 99 | 100 | ![Cameras and a Programmer](Docs/webcams.programmer.jpg) 101 | 102 | ## Contributing 103 | 104 | Contributions are welcome! 105 | 106 | To make a PR please first fork the repo in github, and make a branch in that fork with a sensible name. Apply your changes to that branch (and test if you can), commit and then create a PR to merge to a branch of the same name in my repo. 107 | 108 | Please do not submit PR's onto the master branch of my repo unless they are very trivial (spellings etc). Make sure your changes are consistent with the style and purpose of the existing code; and provide a coherent explanation of what/why in the PR. 109 | 110 | ## Plans 111 | 112 | Time allowing; my Current plan is: 113 | 114 | V3 Options, UI and server enhancements; 115 | * All the primary config options moved to the `myconfig.h` file. 116 | * Miniviewer, favicons 117 | * UI now shows stream links and build info 118 | * Nearly Complete 119 | 120 | V4 Preferences; 121 | * Cam module preferences and face recognition Db saved between sessions in LittleFS (formerly SPIFS). 122 | * Upload/Download FaceDB as Json document in browser 123 | * Investigate using SD card to capture images 124 | * Not started; will have to wait until I have time. 125 | 126 | V5 Remove face recognition entirely; 127 | * Dont try to make it optional, this is a code and maintenance nightmare. V4 can be maintained on a branch for those who need it. 128 | * implement OTA and a better network stack for remembering multiple AP's, auto-config etc. 129 | * UI Skinning/Theming 130 | 131 | You can check the [enhancement list](https://github.com/easytarget/esp32-cam-webserver/issues?q=is%3Aissue+label%3Aenhancement) (past and present), and add any thoghts you may have there. Things that have occurred to me are, in no particular order: 132 | * Improve Wifi, add a captive portal for setup and fallback, better disconnect/reconnect behaviour. 133 | * The module has a SD/TF card slot; this is currently unused, but I would like to add the ability to store snapshots; recording Video at low resolution may be possible, but the card interface is too slow for HD video as far as I know. 134 | * Remove face rcognition to save a Mb+ of code space and then implement over the air updates. 135 | * Combine current split html pages (one per camera type) into one which adapts as needed. 136 | -------------------------------------------------------------------------------- /camera_pins.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Pin definitions for some common ESP-CAM modules 3 | * 4 | * Select the module to use in myconfig.h 5 | * Defaults to AI-THINKER CAM module 6 | * 7 | */ 8 | #if defined(CAMERA_MODEL_WROVER_KIT) 9 | // 10 | // ESP WROVER 11 | // https://dl.espressif.com/dl/schematics/ESP-WROVER-KIT_SCH-2.pdf 12 | // 13 | #define PWDN_GPIO_NUM -1 14 | #define RESET_GPIO_NUM -1 15 | #define XCLK_GPIO_NUM 21 16 | #define SIOD_GPIO_NUM 26 17 | #define SIOC_GPIO_NUM 27 18 | #define Y9_GPIO_NUM 35 19 | #define Y8_GPIO_NUM 34 20 | #define Y7_GPIO_NUM 39 21 | #define Y6_GPIO_NUM 36 22 | #define Y5_GPIO_NUM 19 23 | #define Y4_GPIO_NUM 18 24 | #define Y3_GPIO_NUM 5 25 | #define Y2_GPIO_NUM 4 26 | #define VSYNC_GPIO_NUM 25 27 | #define HREF_GPIO_NUM 23 28 | #define PCLK_GPIO_NUM 22 29 | #define LED_PIN 2 // A status led on the RGB; could also use pin 0 or 4 30 | #define LED_ON HIGH // 31 | #define LED_OFF LOW // 32 | // #define LAMP_PIN x // No LED FloodLamp. 33 | 34 | #elif defined(CAMERA_MODEL_ESP_EYE) 35 | // 36 | // ESP-EYE 37 | // https://twitter.com/esp32net/status/1085488403460882437 38 | #define PWDN_GPIO_NUM -1 39 | #define RESET_GPIO_NUM -1 40 | #define XCLK_GPIO_NUM 4 41 | #define SIOD_GPIO_NUM 18 42 | #define SIOC_GPIO_NUM 23 43 | #define Y9_GPIO_NUM 36 44 | #define Y8_GPIO_NUM 37 45 | #define Y7_GPIO_NUM 38 46 | #define Y6_GPIO_NUM 39 47 | #define Y5_GPIO_NUM 35 48 | #define Y4_GPIO_NUM 14 49 | #define Y3_GPIO_NUM 13 50 | #define Y2_GPIO_NUM 34 51 | #define VSYNC_GPIO_NUM 5 52 | #define HREF_GPIO_NUM 27 53 | #define PCLK_GPIO_NUM 25 54 | #define LED_PIN 21 // Status led 55 | #define LED_ON HIGH // 56 | #define LED_OFF LOW // 57 | // #define LAMP_PIN x // No LED FloodLamp. 58 | 59 | #elif defined(CAMERA_MODEL_M5STACK_PSRAM) 60 | // 61 | // ESP32 M5STACK 62 | // 63 | #define PWDN_GPIO_NUM -1 64 | #define RESET_GPIO_NUM 15 65 | #define XCLK_GPIO_NUM 27 66 | #define SIOD_GPIO_NUM 25 67 | #define SIOC_GPIO_NUM 23 68 | #define Y9_GPIO_NUM 19 69 | #define Y8_GPIO_NUM 36 70 | #define Y7_GPIO_NUM 18 71 | #define Y6_GPIO_NUM 39 72 | #define Y5_GPIO_NUM 5 73 | #define Y4_GPIO_NUM 34 74 | #define Y3_GPIO_NUM 35 75 | #define Y2_GPIO_NUM 32 76 | #define VSYNC_GPIO_NUM 22 77 | #define HREF_GPIO_NUM 26 78 | #define PCLK_GPIO_NUM 21 79 | // M5 Stack status/illumination LED details unknown/unclear 80 | // #define LED_PIN x // Status led 81 | // #define LED_ON HIGH // 82 | // #define LED_OFF LOW // 83 | // #define LAMP_PIN x // LED FloodLamp. 84 | 85 | #elif defined(CAMERA_MODEL_M5STACK_V2_PSRAM) 86 | // 87 | // ESP32 M5STACK V2 88 | // 89 | #define PWDN_GPIO_NUM -1 90 | #define RESET_GPIO_NUM 15 91 | #define XCLK_GPIO_NUM 27 92 | #define SIOD_GPIO_NUM 22 93 | #define SIOC_GPIO_NUM 23 94 | #define Y9_GPIO_NUM 19 95 | #define Y8_GPIO_NUM 36 96 | #define Y7_GPIO_NUM 18 97 | #define Y6_GPIO_NUM 39 98 | #define Y5_GPIO_NUM 5 99 | #define Y4_GPIO_NUM 34 100 | #define Y3_GPIO_NUM 35 101 | #define Y2_GPIO_NUM 32 102 | #define VSYNC_GPIO_NUM 25 103 | #define HREF_GPIO_NUM 26 104 | #define PCLK_GPIO_NUM 21 105 | // M5 Stack status/illumination LED details unknown/unclear 106 | // #define LED_PIN x // Status led 107 | // #define LED_ON HIGH // 108 | // #define LED_OFF LOW // 109 | // #define LAMP_PIN x // LED FloodLamp. 110 | 111 | #elif defined(CAMERA_MODEL_M5STACK_WIDE) 112 | // 113 | // ESP32 M5STACK WIDE 114 | // 115 | #define PWDN_GPIO_NUM -1 116 | #define RESET_GPIO_NUM 15 117 | #define XCLK_GPIO_NUM 27 118 | #define SIOD_GPIO_NUM 22 119 | #define SIOC_GPIO_NUM 23 120 | #define Y9_GPIO_NUM 19 121 | #define Y8_GPIO_NUM 36 122 | #define Y7_GPIO_NUM 18 123 | #define Y6_GPIO_NUM 39 124 | #define Y5_GPIO_NUM 5 125 | #define Y4_GPIO_NUM 34 126 | #define Y3_GPIO_NUM 35 127 | #define Y2_GPIO_NUM 32 128 | #define VSYNC_GPIO_NUM 25 129 | #define HREF_GPIO_NUM 26 130 | #define PCLK_GPIO_NUM 21 131 | // M5 Stack status/illumination LED details unknown/unclear 132 | // #define LED_PIN x // Status led 133 | // #define LED_ON HIGH // 134 | // #define LED_OFF LOW // 135 | // #define LAMP_PIN x // LED FloodLamp. 136 | 137 | #elif defined(CAMERA_MODEL_M5STACK_ESP32CAM) 138 | // 139 | // Common M5 Stack without PSRAM 140 | // 141 | #define PWDN_GPIO_NUM -1 142 | #define RESET_GPIO_NUM 15 143 | #define XCLK_GPIO_NUM 27 144 | #define SIOD_GPIO_NUM 25 145 | #define SIOC_GPIO_NUM 23 146 | #define Y9_GPIO_NUM 19 147 | #define Y8_GPIO_NUM 36 148 | #define Y7_GPIO_NUM 18 149 | #define Y6_GPIO_NUM 39 150 | #define Y5_GPIO_NUM 5 151 | #define Y4_GPIO_NUM 34 152 | #define Y3_GPIO_NUM 35 153 | #define Y2_GPIO_NUM 17 154 | #define VSYNC_GPIO_NUM 22 155 | #define HREF_GPIO_NUM 26 156 | #define PCLK_GPIO_NUM 21 157 | // Note NO PSRAM,; so maximum working resolution is XGA 1024×768 158 | // M5 Stack status/illumination LED details unknown/unclear 159 | // #define LED_PIN x // Status led 160 | // #define LED_ON HIGH // 161 | // #define LED_OFF LOW // 162 | // #define LAMP_PIN x // LED FloodLamp. 163 | 164 | #elif defined(CAMERA_MODEL_AI_THINKER) 165 | // 166 | // AI Thinker 167 | // https://github.com/SeeedDocument/forum_doc/raw/master/reg/ESP32_CAM_V1.6.pdf 168 | // 169 | #define PWDN_GPIO_NUM 32 170 | #define RESET_GPIO_NUM -1 171 | #define XCLK_GPIO_NUM 0 172 | #define SIOD_GPIO_NUM 26 173 | #define SIOC_GPIO_NUM 27 174 | #define Y9_GPIO_NUM 35 175 | #define Y8_GPIO_NUM 34 176 | #define Y7_GPIO_NUM 39 177 | #define Y6_GPIO_NUM 36 178 | #define Y5_GPIO_NUM 21 179 | #define Y4_GPIO_NUM 19 180 | #define Y3_GPIO_NUM 18 181 | #define Y2_GPIO_NUM 5 182 | #define VSYNC_GPIO_NUM 25 183 | #define HREF_GPIO_NUM 23 184 | #define PCLK_GPIO_NUM 22 185 | #define LED_PIN 33 // Status led 186 | #define LED_ON LOW // - Pin is inverted. 187 | #define LED_OFF HIGH // 188 | #define LAMP_PIN 4 // LED FloodLamp. 189 | 190 | #elif defined(CAMERA_MODEL_TTGO_T_JOURNAL) 191 | // 192 | // LilyGO TTGO T-Journal ESP32; with OLED! but not used here.. :-( 193 | #define PWDN_GPIO_NUM 0 194 | #define RESET_GPIO_NUM 15 195 | #define XCLK_GPIO_NUM 27 196 | #define SIOD_GPIO_NUM 25 197 | #define SIOC_GPIO_NUM 23 198 | #define Y9_GPIO_NUM 19 199 | #define Y8_GPIO_NUM 36 200 | #define Y7_GPIO_NUM 18 201 | #define Y6_GPIO_NUM 39 202 | #define Y5_GPIO_NUM 5 203 | #define Y4_GPIO_NUM 34 204 | #define Y3_GPIO_NUM 35 205 | #define Y2_GPIO_NUM 17 206 | #define VSYNC_GPIO_NUM 22 207 | #define HREF_GPIO_NUM 26 208 | #define PCLK_GPIO_NUM 21 209 | // TTGO T Journal status/illumination LED details unknown/unclear 210 | // #define LED_PIN 33 // Status led 211 | // #define LED_ON LOW // - Pin is inverted. 212 | // #define LED_OFF HIGH // 213 | // #define LAMP_PIN 4 // LED FloodLamp. 214 | 215 | #else 216 | // Well. 217 | // that went badly... 218 | #error "Camera model not selected, did you forget to uncomment it in myconfig?" 219 | #endif 220 | -------------------------------------------------------------------------------- /css.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Master CSS file for the camera pages 3 | */ 4 | 5 | const uint8_t style_css[] = R"=====(/* 6 | * CSS for the esp32 cam webserver 7 | */ 8 | 9 | body { 10 | font-family: Arial,Helvetica,sans-serif; 11 | background: #181818; 12 | color: #EFEFEF; 13 | font-size: 16px 14 | } 15 | 16 | a { 17 | color: #EFEFEF; 18 | text-decoration: underline 19 | } 20 | 21 | h2 { 22 | font-size: 18px 23 | } 24 | 25 | section.main { 26 | display: flex 27 | } 28 | 29 | #menu,section.main { 30 | flex-direction: column 31 | } 32 | 33 | #menu { 34 | display: none; 35 | flex-wrap: nowrap; 36 | width: 380px; 37 | background: #363636; 38 | padding: 8px; 39 | border-radius: 4px; 40 | margin-top: -10px; 41 | margin-right: 10px; 42 | } 43 | 44 | /* #content { 45 | display: flex; 46 | flex-wrap: wrap; 47 | align-items: stretch 48 | } 49 | */ 50 | figure { 51 | padding: 0px; 52 | margin: 0; 53 | margin-block-start: 0; 54 | margin-block-end: 0; 55 | margin-inline-start: 0; 56 | margin-inline-end: 0 57 | } 58 | 59 | figure img { 60 | display: block; 61 | max-width: 100%; 62 | width: auto; 63 | height: auto; 64 | border-radius: 4px; 65 | margin-top: 8px; 66 | } 67 | 68 | section#buttons { 69 | display: flex; 70 | flex-wrap: nowrap; 71 | justify-content: space-between 72 | } 73 | 74 | #nav-toggle { 75 | cursor: pointer; 76 | display: block 77 | } 78 | 79 | #nav-toggle-cb { 80 | outline: 0; 81 | opacity: 0; 82 | width: 0; 83 | height: 0 84 | } 85 | 86 | #nav-toggle-cb:checked+#menu { 87 | display: flex 88 | } 89 | 90 | .input-group { 91 | display: flex; 92 | flex-wrap: nowrap; 93 | line-height: 22px; 94 | margin: 5px 0 95 | } 96 | 97 | .input-group>label { 98 | display: inline-block; 99 | padding-right: 10px; 100 | min-width: 47% 101 | } 102 | 103 | .input-group input,.input-group select { 104 | flex-grow: 1 105 | } 106 | 107 | .range-max,.range-min { 108 | display: inline-block; 109 | padding: 0 5px 110 | } 111 | 112 | button { 113 | display: block; 114 | margin: 3px; 115 | padding: 0 8px; 116 | border: 0; 117 | line-height: 28px; 118 | cursor: pointer; 119 | color: #fff; 120 | background: #ff3034; 121 | border-radius: 5px; 122 | font-size: 16px; 123 | outline: 0 124 | } 125 | 126 | button:hover { 127 | background: #ff494d 128 | } 129 | 130 | button:active { 131 | background: #f21c21 132 | } 133 | 134 | button.disabled { 135 | cursor: default; 136 | background: #a0a0a0 137 | } 138 | 139 | input[type=range] { 140 | -webkit-appearance: none; 141 | width: 100%; 142 | height: 22px; 143 | background: #363636; 144 | cursor: pointer; 145 | margin: 0 146 | } 147 | 148 | input[type=range]:focus { 149 | outline: 0 150 | } 151 | 152 | input[type=range]::-webkit-slider-runnable-track { 153 | width: 100%; 154 | height: 2px; 155 | cursor: pointer; 156 | background: #EFEFEF; 157 | border-radius: 0; 158 | border: 0 solid #EFEFEF 159 | } 160 | 161 | input[type=range]::-webkit-slider-thumb { 162 | border: 1px solid rgba(0,0,30,0); 163 | height: 22px; 164 | width: 22px; 165 | border-radius: 50px; 166 | background: #ff3034; 167 | cursor: pointer; 168 | -webkit-appearance: none; 169 | margin-top: -11.5px 170 | } 171 | 172 | input[type=range]:focus::-webkit-slider-runnable-track { 173 | background: #EFEFEF 174 | } 175 | 176 | input[type=range]::-moz-range-track { 177 | width: 100%; 178 | height: 2px; 179 | cursor: pointer; 180 | background: #EFEFEF; 181 | border-radius: 0; 182 | border: 0 solid #EFEFEF 183 | } 184 | 185 | input[type=range]::-moz-range-thumb { 186 | border: 1px solid rgba(0,0,30,0); 187 | height: 22px; 188 | width: 22px; 189 | border-radius: 50px; 190 | background: #ff3034; 191 | cursor: pointer 192 | } 193 | 194 | input[type=range]::-ms-track { 195 | width: 100%; 196 | height: 2px; 197 | cursor: pointer; 198 | background: 0 0; 199 | border-color: transparent; 200 | color: transparent 201 | } 202 | 203 | input[type=range]::-ms-fill-lower { 204 | background: #EFEFEF; 205 | border: 0 solid #EFEFEF; 206 | border-radius: 0 207 | } 208 | 209 | input[type=range]::-ms-fill-upper { 210 | background: #EFEFEF; 211 | border: 0 solid #EFEFEF; 212 | border-radius: 0 213 | } 214 | 215 | input[type=range]::-ms-thumb { 216 | border: 1px solid rgba(0,0,30,0); 217 | height: 22px; 218 | width: 22px; 219 | border-radius: 50px; 220 | background: #ff3034; 221 | cursor: pointer; 222 | height: 2px 223 | } 224 | 225 | input[type=range]:focus::-ms-fill-lower { 226 | background: #EFEFEF 227 | } 228 | 229 | input[type=range]:focus::-ms-fill-upper { 230 | background: #363636 231 | } 232 | 233 | input[type=text] { 234 | border: 1px solid #363636; 235 | font-size: 14px; 236 | height: 20px; 237 | margin: 1px; 238 | outline: 0; 239 | border-radius: 5px 240 | } 241 | 242 | .switch { 243 | display: block; 244 | position: relative; 245 | line-height: 22px; 246 | font-size: 16px; 247 | height: 22px 248 | } 249 | 250 | .switch input { 251 | outline: 0; 252 | opacity: 0; 253 | width: 0; 254 | height: 0 255 | } 256 | 257 | .slider { 258 | width: 50px; 259 | height: 22px; 260 | border-radius: 22px; 261 | cursor: pointer; 262 | background-color: grey 263 | } 264 | 265 | .slider,.slider:before { 266 | display: inline-block; 267 | transition: .4s 268 | } 269 | 270 | .slider:before { 271 | position: relative; 272 | content: ""; 273 | border-radius: 50%; 274 | height: 16px; 275 | width: 16px; 276 | left: 4px; 277 | top: 3px; 278 | background-color: #fff 279 | } 280 | 281 | input:checked+.slider { 282 | background-color: #ff3034 283 | } 284 | 285 | input:checked+.slider:before { 286 | -webkit-transform: translateX(26px); 287 | transform: translateX(26px) 288 | } 289 | 290 | select { 291 | border: 1px solid #363636; 292 | font-size: 14px; 293 | height: 22px; 294 | outline: 0; 295 | border-radius: 5px 296 | } 297 | 298 | .image-container { 299 | position: relative; 300 | min-width: 160px; 301 | transform-origin: top left 302 | } 303 | 304 | .close { 305 | position: absolute; 306 | z-index: 99; 307 | background: #ff3034; 308 | width: 16px; 309 | height: 16px; 310 | border-radius: 100px; 311 | color: #fff; 312 | text-align: center; 313 | line-height: 18px; 314 | cursor: pointer 315 | } 316 | 317 | .close-rot-none { 318 | left: 5px; 319 | top: 5px; 320 | } 321 | 322 | .close-rot-left { 323 | right: 5px; 324 | top: 5px; 325 | } 326 | 327 | .close-rot-right { 328 | left: 5px; 329 | bottom: 5px; 330 | } 331 | 332 | .hidden { 333 | display: none 334 | } 335 | 336 | .inline-button { 337 | line-height: 20px; 338 | margin: 2px; 339 | padding: 1px 4px 2px 4px; 340 | } 341 | 342 | .loader { 343 | border: 0.5em solid #f3f3f3; /* Light grey */ 344 | border-top: 0.5em solid #000000; /* white */ 345 | border-radius: 50%; 346 | width: 1em; 347 | height: 1em; 348 | -webkit-animation: spin 2s linear infinite; /* Safari */ 349 | animation: spin 2s linear infinite; 350 | } 351 | 352 | @-webkit-keyframes spin { /* Safari */ 353 | 0% { -webkit-transform: rotate(0deg); } 354 | 100% { -webkit-transform: rotate(360deg); } 355 | } 356 | 357 | @keyframes spin { 358 | 0% { transform: rotate(0deg); } 359 | 100% { transform: rotate(360deg); } 360 | })====="; 361 | 362 | size_t style_css_len = sizeof(style_css)-1; 363 | -------------------------------------------------------------------------------- /esp32-cam-webserver.ino: -------------------------------------------------------------------------------- 1 | #include "esp_camera.h" 2 | #include 3 | #include 4 | 5 | /* This sketch is a extension/expansion/reork of the 'official' ESP32 Camera example 6 | * sketch from Expressif: 7 | * https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/Camera/CameraWebServer 8 | * 9 | * It is modified to allow control of Illumination LED Lamps's (present on some modules), 10 | * greater feedback via a status LED, and the HTML contents are present in plain text 11 | * for easy modification. 12 | * 13 | * A camera name can now be configured, and wifi details can be stored in an optional 14 | * header file to allow easier updated of the repo. 15 | * 16 | * The web UI has had changes to add the lamp control, rotation, a standalone viewer, 17 | * more feeedback, new controls and other tweaks and changes, 18 | * note: Make sure that you have either selected ESP32 AI Thinker, 19 | * or another board which has PSRAM enabled to use high resolution camera modes 20 | */ 21 | 22 | 23 | /* 24 | * FOR NETWORK AND HARDWARE SETTINGS COPY OR RENAME 'myconfig.sample.h' TO 'myconfig.h' AND EDIT THAT. 25 | * 26 | * By default this sketch will assume an AI-THINKER ESP-CAM and create 27 | * an accesspoint called "ESP32-CAM-CONNECT" (password: "InsecurePassword") 28 | * 29 | */ 30 | 31 | // Primary config, or defaults. 32 | #if __has_include("myconfig.h") 33 | #include "myconfig.h" 34 | #else 35 | #warning "Using Defaults: Copy myconfig.sample.h to myconfig.h and edit that to use your own settings" 36 | #define WIFI_AP_ENABLE 37 | #define CAMERA_MODEL_AI_THINKER 38 | struct station { const char ssid[64]; const char password[64]; const bool dhcp;} 39 | stationList[] = {{"ESP32-CAM-CONNECT","InsecurePassword", true}}; 40 | #endif 41 | 42 | // Pin Mappings 43 | #include "camera_pins.h" 44 | 45 | // Internal filesystem (SPIFFS) 46 | // used for non-volatile camera settings and face DB store 47 | #include "storage.h" 48 | 49 | // Sketch Info 50 | int sketchSize; 51 | int sketchSpace; 52 | String sketchMD5; 53 | 54 | // Start with accesspoint mode disabled, wifi setup will activate it if 55 | // no known networks are found, and WIFI_AP_ENABLE has been defined 56 | bool accesspoint = false; 57 | 58 | // IP address, Netmask and Gateway, populated when connected 59 | IPAddress ip; 60 | IPAddress net; 61 | IPAddress gw; 62 | 63 | // Declare external function from app_httpd.cpp 64 | extern void startCameraServer(int hPort, int sPort); 65 | 66 | // A Name for the Camera. (set in myconfig.h) 67 | #if defined(CAM_NAME) 68 | char myName[] = CAM_NAME; 69 | #else 70 | char myName[] = "ESP32 camera server"; 71 | #endif 72 | 73 | // Ports for http and stream (override in myconfig.h) 74 | #if defined(HTTP_PORT) 75 | int httpPort = HTTP_PORT; 76 | #else 77 | int httpPort = 80; 78 | #endif 79 | 80 | #if defined(STREAM_PORT) 81 | int streamPort = STREAM_PORT; 82 | #else 83 | int streamPort = 81; 84 | #endif 85 | 86 | #if !defined(WIFI_WATCHDOG) 87 | #define WIFI_WATCHDOG 5000 88 | #endif 89 | 90 | // Number of known networks in stationList[] 91 | int stationCount = sizeof(stationList)/sizeof(stationList[0]); 92 | 93 | // If we have AP mode enabled, ignore first entry in the stationList[] 94 | #if defined(WIFI_AP_ENABLE) 95 | int firstStation = 1; 96 | #else 97 | int firstStation = 0; 98 | #endif 99 | 100 | // Select bvetween full and simple index as the default. 101 | #if defined(DEFAULT_INDEX_FULL) 102 | char default_index[] = "full"; 103 | #else 104 | char default_index[] = "simple"; 105 | #endif 106 | 107 | 108 | // DNS server 109 | const byte DNS_PORT = 53; 110 | DNSServer dnsServer; 111 | bool captivePortal = false; 112 | char apName[64] = "Undefined"; 113 | 114 | // The app and stream URLs 115 | char httpURL[64] = {"Undefined"}; 116 | char streamURL[64] = {"Undefined"}; 117 | 118 | // This will be displayed to identify the firmware 119 | char myVer[] PROGMEM = __DATE__ " @ " __TIME__; 120 | 121 | // initial rotation 122 | // can be set in myconfig.h 123 | #if !defined(CAM_ROTATION) 124 | #define CAM_ROTATION 0 125 | #endif 126 | int myRotation = CAM_ROTATION; 127 | 128 | // Illumination LAMP/LED 129 | #if defined(LAMP_DISABLE) 130 | int lampVal = -1; // lamp is disabled in config 131 | #elif defined(LAMP_PIN) 132 | #if defined(LAMP_DEFAULT) 133 | int lampVal = constrain(LAMP_DEFAULT,0,100); // initial lamp value, range 0-100 134 | #else 135 | int lampVal = 0; //default to off 136 | #endif 137 | #else 138 | int lampVal = -1; // no lamp pin assigned 139 | #endif 140 | 141 | int lampChannel = 7; // a free PWM channel (some channels used by camera) 142 | const int pwmfreq = 50000; // 50K pwm frequency 143 | const int pwmresolution = 9; // duty cycle bit range 144 | const int pwmMax = pow(2,pwmresolution)-1; 145 | 146 | #if defined(NO_FS) 147 | bool filesystem = false; 148 | #else 149 | bool filesystem = true; 150 | #endif 151 | 152 | #if defined(FACE_DETECTION) 153 | int8_t detection_enabled = 1; 154 | #if defined(FACE_RECOGNITION) 155 | int8_t recognition_enabled = 1; 156 | #else 157 | int8_t recognition_enabled = 0; 158 | #endif 159 | #else 160 | int8_t detection_enabled = 0; 161 | int8_t recognition_enabled = 0; 162 | #endif 163 | 164 | // Notification LED 165 | void flashLED(int flashtime) { 166 | #ifdef LED_PIN // If we have it; flash it. 167 | digitalWrite(LED_PIN, LED_ON); // On at full power. 168 | delay(flashtime); // delay 169 | digitalWrite(LED_PIN, LED_OFF); // turn Off 170 | #else 171 | return; // No notifcation LED, do nothing, no delay 172 | #endif 173 | } 174 | 175 | // Lamp Control 176 | void setLamp(int newVal) { 177 | if (newVal != -1) { 178 | // Apply a logarithmic function to the scale. 179 | int brightness = round((pow(2,(1+(newVal*0.02)))-2)/6*pwmMax); 180 | ledcWrite(lampChannel, brightness); 181 | Serial.print("Lamp: "); 182 | Serial.print(newVal); 183 | Serial.print("%, pwm = "); 184 | Serial.println(brightness); 185 | } 186 | } 187 | 188 | void WifiSetup() { 189 | // Feedback that we are now attempting to connect 190 | flashLED(300); 191 | delay(100); 192 | flashLED(300); 193 | Serial.println("Starting WiFi"); 194 | Serial.print("Known external SSIDs: "); 195 | if (stationCount > firstStation) { 196 | for (int i=firstStation; i < stationCount; i++) Serial.printf(" '%s'", stationList[i].ssid); 197 | } else { 198 | Serial.print("None"); 199 | } 200 | Serial.println(); 201 | byte mac[6]; 202 | WiFi.macAddress(mac); 203 | Serial.printf("MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 204 | 205 | int bestStation = -1; 206 | long bestRSSI = -1024; 207 | if (stationCount > firstStation) { 208 | // We have a list to scan 209 | Serial.printf("Scanning local Wifi Networks\n"); 210 | int stationsFound = WiFi.scanNetworks(); 211 | Serial.printf("%i networks found\n", stationsFound); 212 | if (stationsFound > 0) { 213 | for (int i = 0; i < stationsFound; ++i) { 214 | // Print SSID and RSSI for each network found 215 | String thisSSID = WiFi.SSID(i); 216 | int thisRSSI = WiFi.RSSI(i); 217 | Serial.printf("%3i : %s (%i)", i + 1, thisSSID.c_str(), thisRSSI); 218 | // Scan our list of known external stations 219 | for (int sta = firstStation; sta < stationCount; sta++) { 220 | if (strcmp(stationList[sta].ssid, thisSSID.c_str()) == 0) { 221 | Serial.print(" - Known!"); 222 | // Chose the strongest RSSI seen 223 | if (thisRSSI > bestRSSI) { 224 | bestStation = sta; 225 | bestRSSI = thisRSSI; 226 | } 227 | } 228 | } 229 | Serial.println(); 230 | } 231 | } 232 | } else { 233 | // No list to scan, therefore we are an accesspoint 234 | accesspoint = true; 235 | } 236 | 237 | if (bestStation == -1) { 238 | if (!accesspoint) { 239 | #if defined(WIFI_AP_ENABLE) 240 | Serial.println("No known networks found, entering AccessPoint fallback mode"); 241 | accesspoint = true; 242 | #else 243 | Serial.println("No known networks found"); 244 | #endif 245 | } else { 246 | Serial.println("AccessPoint mode selected in config"); 247 | } 248 | } else { 249 | Serial.printf("Connecting to Wifi Network: %s\n", stationList[bestStation].ssid); 250 | if (stationList[bestStation].dhcp == false) { 251 | #if defined(ST_IP) 252 | Serial.println("Applying static IP settings"); 253 | #if !defined (ST_GATEWAY) || !defined (ST_NETMASK) 254 | #error "You must supply both Gateway and NetMask when specifying a static IP address" 255 | #endif 256 | IPAddress staticIP(ST_IP); 257 | IPAddress gateway(ST_GATEWAY); 258 | IPAddress subnet(ST_NETMASK); 259 | #if !defined(ST_DNS1) 260 | WiFi.config(staticIP, gateway, subnet); 261 | #else 262 | IPAddress dns1(ST_DNS1); 263 | #if !defined(ST_DNS2) 264 | WiFi.config(staticIP, gateway, subnet, dns1); 265 | #else 266 | IPAddress dns2(ST_DNS2); 267 | WiFi.config(staticIP, gateway, subnet, dns1, dns2); 268 | #endif 269 | #endif 270 | #else 271 | Serial.println("Static IP settings requested but not defined in config, falling back to dhcp"); 272 | #endif 273 | } 274 | // Initiate network connection request 275 | WiFi.begin(stationList[bestStation].ssid, stationList[bestStation].password); 276 | 277 | // Wait to connect, or timeout 278 | unsigned long start = millis(); 279 | while ((millis() - start <= WIFI_WATCHDOG) && (WiFi.status() != WL_CONNECTED)) { 280 | delay(500); 281 | Serial.print('.'); 282 | } 283 | // If we have connected, inform user 284 | if (WiFi.status() == WL_CONNECTED) { 285 | Serial.println("Client connection succeeded"); 286 | accesspoint = false; 287 | // Note IP details 288 | ip = WiFi.localIP(); 289 | net = WiFi.subnetMask(); 290 | gw = WiFi.gatewayIP(); 291 | Serial.printf("IP address: %d.%d.%d.%d\n",ip[0],ip[1],ip[2],ip[3]); 292 | Serial.printf("Netmask : %d.%d.%d.%d\n",net[0],net[1],net[2],net[3]); 293 | Serial.printf("Gateway : %d.%d.%d.%d\n",gw[0],gw[1],gw[2],gw[3]); 294 | // Flash the LED to show we are connected 295 | for (int i = 0; i < 5; i++) { 296 | flashLED(50); 297 | delay(150); 298 | } 299 | } else { 300 | Serial.println("Client connection Failed"); 301 | WiFi.disconnect(); // (resets the WiFi scan) 302 | } 303 | } 304 | 305 | if (accesspoint && (WiFi.status() != WL_CONNECTED)) { 306 | // The accesspoint has been enabled, and we have not connected to any existing networks 307 | #if defined(AP_CHAN) 308 | Serial.println("Setting up Fixed Channel AccessPoint"); 309 | Serial.print(" SSID : "); 310 | Serial.println(stationList[0].ssid); 311 | Serial.print(" Password : "); 312 | Serial.println(stationList[0].password); 313 | Serial.print(" Channel : "); 314 | Serial.println(AP_CHAN); 315 | WiFi.softAP(stationList[0].ssid, stationList[0].password, AP_CHAN); 316 | # else 317 | Serial.println("Setting up AccessPoint"); 318 | Serial.print(" SSID : "); 319 | Serial.println(stationList[0].ssid); 320 | Serial.print(" Password : "); 321 | Serial.println(stationList[0].password); 322 | WiFi.softAP(stationList[0].ssid, stationList[0].password); 323 | #endif 324 | #if defined(AP_ADDRESS) 325 | // User has specified the AP details; apply them after a short delay 326 | // (https://github.com/espressif/arduino-esp32/issues/985#issuecomment-359157428) 327 | delay(100); 328 | IPAddress local_IP(AP_ADDRESS); 329 | IPAddress gateway(AP_ADDRESS); 330 | IPAddress subnet(255,255,255,0); 331 | WiFi.softAPConfig(local_IP, gateway, subnet); 332 | #endif 333 | // Note AP details 334 | ip = WiFi.softAPIP(); 335 | net = WiFi.subnetMask(); 336 | gw = WiFi.gatewayIP(); 337 | strcpy(apName, stationList[0].ssid); 338 | Serial.printf("IP address: %d.%d.%d.%d\n",ip[0],ip[1],ip[2],ip[3]); 339 | // Flash the LED to show we are connected 340 | for (int i = 0; i < 5; i++) { 341 | flashLED(150); 342 | delay(50); 343 | } 344 | // Start the DNS captive portal if requested 345 | if (stationList[0].dhcp == true) { 346 | Serial.println("Starting Captive Portal"); 347 | dnsServer.start(DNS_PORT, "*", ip); 348 | captivePortal = true; 349 | } 350 | } 351 | } 352 | 353 | void setup() { 354 | Serial.begin(115200); 355 | Serial.setDebugOutput(true); 356 | Serial.println(); 357 | Serial.println("===="); 358 | Serial.print("esp32-cam-webserver: "); 359 | Serial.println(myName); 360 | Serial.print("Code Built: "); 361 | Serial.println(myVer); 362 | 363 | if (stationCount == 0) { 364 | Serial.println("\nFatal Error; Halting"); 365 | Serial.println("No wifi ssid details have been configured; we cannot connect to WiFi or start our own AccessPoint"); 366 | while (true) delay(1000); 367 | } 368 | 369 | #if defined(LED_PIN) // If we have a notification LED, set it to output 370 | pinMode(LED_PIN, OUTPUT); 371 | digitalWrite(LED_PIN, LED_ON); 372 | #endif 373 | 374 | // Create camera config structure; and populate with hardware and other defaults 375 | camera_config_t config; 376 | config.ledc_channel = LEDC_CHANNEL_0; 377 | config.ledc_timer = LEDC_TIMER_0; 378 | config.pin_d0 = Y2_GPIO_NUM; 379 | config.pin_d1 = Y3_GPIO_NUM; 380 | config.pin_d2 = Y4_GPIO_NUM; 381 | config.pin_d3 = Y5_GPIO_NUM; 382 | config.pin_d4 = Y6_GPIO_NUM; 383 | config.pin_d5 = Y7_GPIO_NUM; 384 | config.pin_d6 = Y8_GPIO_NUM; 385 | config.pin_d7 = Y9_GPIO_NUM; 386 | config.pin_xclk = XCLK_GPIO_NUM; 387 | config.pin_pclk = PCLK_GPIO_NUM; 388 | config.pin_vsync = VSYNC_GPIO_NUM; 389 | config.pin_href = HREF_GPIO_NUM; 390 | config.pin_sscb_sda = SIOD_GPIO_NUM; 391 | config.pin_sscb_scl = SIOC_GPIO_NUM; 392 | config.pin_pwdn = PWDN_GPIO_NUM; 393 | config.pin_reset = RESET_GPIO_NUM; 394 | config.xclk_freq_hz = 20000000; 395 | config.pixel_format = PIXFORMAT_JPEG; 396 | //init with highest supported specs to pre-allocate large buffers 397 | if(psramFound()){ 398 | config.frame_size = FRAMESIZE_UXGA; 399 | config.jpeg_quality = 10; 400 | config.fb_count = 2; 401 | } else { 402 | config.frame_size = FRAMESIZE_SVGA; 403 | config.jpeg_quality = 12; 404 | config.fb_count = 1; 405 | } 406 | 407 | #if defined(CAMERA_MODEL_ESP_EYE) 408 | pinMode(13, INPUT_PULLUP); 409 | pinMode(14, INPUT_PULLUP); 410 | #endif 411 | 412 | // camera init 413 | esp_err_t err = esp_camera_init(&config); 414 | if (err == ESP_OK) { 415 | Serial.println("Camera init succeeded"); 416 | } else { 417 | delay(100); // need a delay here or the next serial o/p gets missed 418 | Serial.println("Halted: Camera sensor failed to initialise"); 419 | Serial.println("Will reboot to try again in 10s\n"); 420 | delay(10000); 421 | ESP.restart(); 422 | } 423 | sensor_t * s = esp_camera_sensor_get(); 424 | 425 | // Dump camera module, warn for unsupported modules. 426 | switch (s->id.PID) { 427 | case OV9650_PID: Serial.println("WARNING: OV9650 camera module is not properly supported, will fallback to OV2640 operation"); break; 428 | case OV7725_PID: Serial.println("WARNING: OV7725 camera module is not properly supported, will fallback to OV2640 operation"); break; 429 | case OV2640_PID: Serial.println("OV2640 camera module detected"); break; 430 | case OV3660_PID: Serial.println("OV3660 camera module detected"); break; 431 | default: Serial.println("WARNING: Camera module is unknown and not properly supported, will fallback to OV2640 operation"); 432 | } 433 | 434 | // OV3660 initial sensors are flipped vertically and colors are a bit saturated 435 | if (s->id.PID == OV3660_PID) { 436 | s->set_vflip(s, 1); //flip it back 437 | s->set_brightness(s, 1); //up the blightness just a bit 438 | s->set_saturation(s, -2); //lower the saturation 439 | } 440 | 441 | // M5 Stack Wide has special needs 442 | #if defined(CAMERA_MODEL_M5STACK_WIDE) 443 | s->set_vflip(s, 1); 444 | s->set_hmirror(s, 1); 445 | #endif 446 | 447 | // Config can override mirror and flip 448 | #if defined(H_MIRROR) 449 | s->set_hmirror(s, H_MIRROR); 450 | #endif 451 | #if defined(V_FLIP) 452 | s->set_vflip(s, V_FLIP); 453 | #endif 454 | 455 | // set initial frame rate 456 | #if defined(DEFAULT_RESOLUTION) 457 | s->set_framesize(s, DEFAULT_RESOLUTION); 458 | #else 459 | s->set_framesize(s, FRAMESIZE_SVGA); 460 | #endif 461 | 462 | /* 463 | * Add any other defaults you want to apply at startup here: 464 | * uncomment the line and set the value as desired (see the comments) 465 | * 466 | * these are defined in the esp headers here: 467 | * https://github.com/espressif/esp32-camera/blob/master/driver/include/sensor.h#L149 468 | */ 469 | 470 | //s->set_framesize(s, FRAMESIZE_SVGA); // FRAMESIZE_[QQVGA|HQVGA|QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA|QXGA(ov3660)]); 471 | //s->set_quality(s, val); // 10 to 63 472 | //s->set_brightness(s, 0); // -2 to 2 473 | //s->set_contrast(s, 0); // -2 to 2 474 | //s->set_saturation(s, 0); // -2 to 2 475 | //s->set_special_effect(s, 0); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia) 476 | //s->set_whitebal(s, 1); // aka 'awb' in the UI; 0 = disable , 1 = enable 477 | //s->set_awb_gain(s, 1); // 0 = disable , 1 = enable 478 | //s->set_wb_mode(s, 0); // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home) 479 | //s->set_exposure_ctrl(s, 1); // 0 = disable , 1 = enable 480 | //s->set_aec2(s, 0); // 0 = disable , 1 = enable 481 | //s->set_ae_level(s, 0); // -2 to 2 482 | //s->set_aec_value(s, 300); // 0 to 1200 483 | //s->set_gain_ctrl(s, 1); // 0 = disable , 1 = enable 484 | //s->set_agc_gain(s, 0); // 0 to 30 485 | //s->set_gainceiling(s, (gainceiling_t)0); // 0 to 6 486 | //s->set_bpc(s, 0); // 0 = disable , 1 = enable 487 | //s->set_wpc(s, 1); // 0 = disable , 1 = enable 488 | //s->set_raw_gma(s, 1); // 0 = disable , 1 = enable 489 | //s->set_lenc(s, 1); // 0 = disable , 1 = enable 490 | //s->set_hmirror(s, 0); // 0 = disable , 1 = enable 491 | //s->set_vflip(s, 0); // 0 = disable , 1 = enable 492 | //s->set_dcw(s, 1); // 0 = disable , 1 = enable 493 | //s->set_colorbar(s, 0); // 0 = disable , 1 = enable 494 | 495 | // We now have camera with default init 496 | // check for saved preferences and apply them 497 | 498 | if (filesystem) { 499 | filesystemStart(); 500 | loadPrefs(SPIFFS); 501 | loadFaceDB(SPIFFS); 502 | } else { 503 | Serial.println("No Internal Filesystem, cannot save preferences or face DB"); 504 | } 505 | 506 | /* 507 | * Camera setup complete; initialise the rest of the hardware. 508 | */ 509 | 510 | // Initialise and set the lamp 511 | if (lampVal != -1) { 512 | ledcSetup(lampChannel, pwmfreq, pwmresolution); // configure LED PWM channel 513 | setLamp(lampVal); // set default value 514 | ledcAttachPin(LAMP_PIN, lampChannel); // attach the GPIO pin to the channel 515 | } else { 516 | Serial.println("No lamp, or lamp disabled in config"); 517 | } 518 | 519 | // Having got this far; start Wifi and loop until we are connected or have started an AccessPoint 520 | while ((WiFi.status() != WL_CONNECTED) && !accesspoint) { 521 | WifiSetup(); 522 | delay(1000); 523 | } 524 | 525 | // Now we have a network we can start the two http handlers for the UI and Stream. 526 | startCameraServer(httpPort, streamPort); 527 | 528 | // Construct the app and stream URLs 529 | if (httpPort != 80) { 530 | sprintf(httpURL, "http://%d.%d.%d.%d:%d/", ip[0], ip[1], ip[2], ip[3], httpPort); 531 | } else { 532 | sprintf(httpURL, "http://%d.%d.%d.%d/", ip[0], ip[1], ip[2], ip[3]); 533 | } 534 | Serial.printf("\nCamera Ready!\nUse '%s' to connect\n", httpURL); 535 | // Construct the Stream URL 536 | sprintf(streamURL, "http://%d.%d.%d.%d:%d/", ip[0], ip[1], ip[2], ip[3], streamPort); 537 | Serial.printf("Stream viewer available at '%sview'\n", streamURL); 538 | Serial.printf("Raw stream URL is '%s'\n", streamURL); 539 | 540 | // Used when dumping status; these are slow functions, so just do them once during startup 541 | sketchSize = ESP.getSketchSize(); 542 | sketchSpace = ESP.getFreeSketchSpace(); 543 | sketchMD5 = ESP.getSketchMD5(); 544 | } 545 | 546 | void loop() { 547 | /* 548 | * Just loop forever, reconnecting Wifi As necesscary in client mode 549 | * The stream and URI handler processes initiated by the startCameraServer() call at the 550 | * end of setup() will handle the camera and UI processing from now on. 551 | */ 552 | if (accesspoint) { 553 | // Accespoint is permanently up, so just loop, servicing the captive portal as needed 554 | unsigned long start = millis(); 555 | while (millis() - start < WIFI_WATCHDOG ) { 556 | delay(100); 557 | if (captivePortal) dnsServer.processNextRequest(); 558 | } 559 | } else { 560 | // client mode can fail; so reconnect as appropriate 561 | static bool warned = false; 562 | if (WiFi.status() == WL_CONNECTED) { 563 | // We are connected, wait a bit and re-check 564 | if (warned) { 565 | // Tell the user if we have just reconnected 566 | Serial.println("WiFi reconnected"); 567 | warned = false; 568 | } 569 | delay(WIFI_WATCHDOG); 570 | } else { 571 | // disconnected; attempt to reconnect 572 | if (!warned) { 573 | // Tell the user if we just disconnected 574 | WiFi.disconnect(); // ensures disconnect is complete, wifi scan cleared 575 | Serial.println("WiFi disconnected, retrying"); 576 | warned = true; 577 | } 578 | WifiSetup(); 579 | } 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /index_other.h: -------------------------------------------------------------------------------- 1 | /* 2 | * simpleviewer and streamviewer 3 | */ 4 | 5 | const uint8_t index_simple_html[] = R"=====( 6 | 7 | 8 | 9 | 10 | ESP32-CAM Simplified View 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 |
21 | 28 |
29 | 61 |
62 | 66 |
67 |
68 |
69 | 70 | 71 | 282 | )====="; 283 | 284 | size_t index_simple_html_len = sizeof(index_simple_html)-1; 285 | 286 | /* Stream Viewer */ 287 | 288 | const uint8_t streamviewer_html[] = R"=====( 289 | 290 | 291 | 292 | 293 | ESP32-CAM StreamViewer 294 | 295 | 296 | 336 | 337 | 338 | 339 |
340 |
341 |
342 | 344 | 345 | 346 | 347 |
348 | 349 |
350 | 351 | 352 | 434 | )====="; 435 | 436 | size_t streamviewer_html_len = sizeof(streamviewer_html)-1; 437 | 438 | /* Prototype Captive Portal page 439 | we replace the <> delimited strings with correct values as it is served */ 440 | 441 | const std::string portal_html = R"=====( 442 | 443 | 444 | 445 | 446 | <CAMNAME> - portal 447 | 448 | 449 | 450 | 451 | 452 | 453 |

- access portal

454 | 462 |
463 | Camera Details
464 | 465 | )====="; 466 | -------------------------------------------------------------------------------- /index_ov2640.h: -------------------------------------------------------------------------------- 1 | /* 2 | * primary HTML for the OV2640 camera module 3 | */ 4 | 5 | const uint8_t index_ov2640_html[] = R"=====( 6 | 7 | 8 | 9 | 10 | ESP32 OV2640 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 |
27 | 34 |
35 | 272 |
273 | 277 |
278 |
279 |
280 | 281 | 282 | 630 | )====="; 631 | 632 | size_t index_ov2640_html_len = sizeof(index_ov2640_html)-1; 633 | -------------------------------------------------------------------------------- /index_ov3660.h: -------------------------------------------------------------------------------- 1 | /* 2 | * primary HTML for the OV3660 camera module 3 | */ 4 | 5 | const uint8_t index_ov3660_html[] = R"=====( 6 | 7 | 8 | 9 | 10 | ESP32 OV3660 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 |
27 | 34 |
35 | 285 |
286 | 290 |
291 |
292 |
293 | 294 | 295 | 638 | )====="; 639 | 640 | size_t index_ov3660_html_len = sizeof(index_ov3660_html)-1; 641 | -------------------------------------------------------------------------------- /myconfig.sample.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Rename this example to 'myconfig.h' and fill in your details. 3 | * 4 | * The local config is in the '.gitignore' file, which helps to keep details secret. 5 | */ 6 | 7 | 8 | // Give the camera a name for the web interface 9 | // note: this is not the network hostname 10 | #define CAM_NAME "ESP32 camera server" 11 | 12 | 13 | /* 14 | * WiFi Settings 15 | * 16 | * Note the the use of commas as seperators in IP addresses! 17 | 18 | * Extend the stationList[] below with additional SSID+Password pairs. 19 | * The first block defines /what/ the structure holds 20 | * The second block is where our list of ssid/passwords live 21 | 22 | struct station { 23 | const char ssid[64]; // - ssid (max 64 chars) 24 | const char password[64]; // - password (max 64 chars) 25 | const bool dhcp; // - dhcp 26 | } station stationList[] = {{"ssid1", "pass1", true}, 27 | {"ssid2", "pass2", true}, 28 | {"ssid3", "pass3", false}}; 29 | 30 | * The first entry (ssid1, above) in the stationList[] is special, if WIFI_AP_ENABLE has been uncommented 31 | * it will be used for the AccessPoint ssid and password. 32 | * 33 | * The 'dhcp' setting controls wether the station uses static IP settings (if in doubt leave 'true') 34 | * Note the use of nested braces '{' and '}' to group each entry, and commas ',' to seperate them. 35 | */ 36 | struct station { 37 | const char ssid[64]; // ssid (max 64 chars) 38 | const char password[64]; // password (max 64 chars) 39 | const bool dhcp; // use dhcp? 40 | } stationList[] = {{"my_ssid","my_password", true}}; 41 | 42 | 43 | /* 44 | * Static network settings for client mode 45 | * 46 | * Note: The same settings will be applied to all client connections where the dhcp setting is 'false' 47 | * You must define all three: IP, Gateway and NetMask 48 | */ 49 | // warning - IP addresses must be seperated with commas (,) and not decimals (.) here 50 | // #define ST_IP 192,168,0,16 51 | // #define ST_GATEWAY 192,168,0,2 52 | // #define ST_NETMASK 255,255,255,0 53 | // One or two optional DNS servers can be supplied, but the current firmware never uses them ;-) 54 | // #define ST_DNS1 192,168,0,2 55 | // #define ST_DNS2 8,8,8,8 56 | 57 | /* 58 | * AccessPoint; 59 | * 60 | * Uncomment to enable AP mode; 61 | * 62 | */ 63 | // #define WIFI_AP_ENABLE 64 | 65 | /* AP Mode Notes: 66 | * 67 | * Once enabled the AP ssid and password will be taken from the 1st entry in the stationList[] above. 68 | * 69 | * If there are more entries listed they will be scanned at startup in the normal way and connected to 70 | * if they are found. AP then works as a fallback mode for when there are no 'real' networks available. 71 | * 72 | * Setting the 'dhcp' field to true for the AP enables a captive portal and attempts to send 73 | * all visitors to the webcam page, with varying degrees of success depending on the visitors 74 | * browser and other settings. 75 | */ 76 | // Optionally change the AccessPoint ip address (default = 192.168.4.1) 77 | // warning - IP addresses must be seperated with commas (,) and not decimals (.) here 78 | // #define AP_ADDRESS 192,168,4,1 79 | 80 | // Uncomment this to force the AccessPoint channel number, default = 1 81 | // #define AP_CHAN 1 82 | 83 | /* 84 | * Port numbers for WebUI and Stream, defaults to 80 and 81. 85 | * Uncomment and edit as appropriate 86 | */ 87 | // #define HTTP_PORT 80 88 | // #define STREAM_PORT 81 89 | 90 | /* 91 | * Wifi Watchdog defines how long we spend waiting for a connection before retrying, 92 | * and how often we check to see if we are still connected, milliseconds 93 | * You may wish to increase this if your WiFi is slow at conencting, 94 | */ 95 | //#define WIFI_WATCHDOG 5000 96 | 97 | /* 98 | * Camera Defaults 99 | * 100 | */ 101 | // Initial Reslolution, default SVGA 102 | // available values are: FRAMESIZE_[QQVGA|HQVGA|QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA|QXGA(ov3660)] 103 | // #define DEFAULT_RESOLUTION FRAMESIZE_SVGA 104 | 105 | // Hardware Horizontal Mirror, 0 or 1 (overrides default board setting) 106 | // #define H_MIRROR 0 107 | 108 | // Hardware Vertical Flip , 0 or 1 (overrides default board setting) 109 | // #define V_FLIP 1 110 | 111 | // Browser Rotation (one of: -90,0,90, default 0) 112 | // #define CAM_ROTATION 0 113 | 114 | 115 | /* 116 | * Additional Features 117 | * 118 | */ 119 | // Default Page: uncomment to make the full control page the default, otherwise show simple viewer 120 | // #define DEFAULT_INDEX_FULL 121 | 122 | // Uncomment to disable the illumination lamp features 123 | // #define LAMP_DISABLE 124 | 125 | // Define a initial lamp setting as a percentage, defaults to 0% 126 | // #define LAMP_DEFAULT 0 127 | 128 | // Assume we have SPIFFS/LittleFS partition, uncomment to disable this. 129 | // Controls will still be shown in the UI but are inoperative. 130 | // #define NO_FS 131 | 132 | // Uncomment to enable Face Detection (+ Recognition if desired) by default 133 | // Notes: You must set DEFAULT_RESOLUTION (above) to FRAMESIZE_CIF or lower before enabling this 134 | // Face recognition enrolements are currently lost between reboots. 135 | // #define FACE_DETECTION 136 | // #define FACE_RECOGNITION 137 | 138 | 139 | /* 140 | * Camera Hardware Selectiom 141 | * 142 | * You must uncomment one, and only one, of the lines below to select your board model. 143 | * Remember to also select the board in the Boards Manager 144 | * This is not optional 145 | */ 146 | #define CAMERA_MODEL_AI_THINKER // default 147 | // #define CAMERA_MODEL_WROVER_KIT 148 | // #define CAMERA_MODEL_ESP_EYE 149 | // #define CAMERA_MODEL_M5STACK_PSRAM 150 | // #define CAMERA_MODEL_M5STACK_V2_PSRAM 151 | // #define CAMERA_MODEL_M5STACK_WIDE 152 | // #define CAMERA_MODEL_M5STACK_ESP32CAM // Originally: CAMERA_MODEL_M5STACK_NO_PSRAM 153 | // #define CAMERA_MODEL_TTGO_T_JOURNAL 154 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; https://docs.platformio.org/page/projectconf.html 3 | 4 | ; The esp32-cam-webserver project is intended to be easily compilable 5 | ; with the stock Arduino IDE. 6 | ; - Maintaining compatibility with other development environments 7 | ; is important, but I wont accept changes to the PlatformIO build that 8 | ; break compatibilty with the stock IDE. Eg by using non-standard 9 | ; partition schemes or overriding Arduino defined limits, etc. 10 | 11 | [platformio] 12 | src_dir = ./ 13 | 14 | [env:esp32cam] 15 | platform = espressif32 16 | board = esp32cam 17 | framework = arduino 18 | -------------------------------------------------------------------------------- /src/jsonlib/README.md: -------------------------------------------------------------------------------- 1 | # A lightweight JSON library 2 | 3 | https://github.com/wyolum/jsonlib 4 | 5 | I dont want to use the 'goto' library for this, ArduinoJSON, something lighter and easier to include seemed appropriate 6 | -------------------------------------------------------------------------------- /src/jsonlib/jsonlib-LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/jsonlib/jsonlib.cpp: -------------------------------------------------------------------------------- 1 | #include "jsonlib.h" 2 | 3 | // remove all white space from the json string... preserving strings 4 | String jsonRemoveWhiteSpace(String json){ 5 | int i = 0; 6 | int cursor = 0; 7 | int quote_count = 0; 8 | String out = String(); 9 | char out_chars[json.length()+1]; 10 | 11 | for(i=0; i 0 && json[i - 1] == '\\'){ 23 | //escaped! 24 | } 25 | else{ // not escaped 26 | quote_count++; 27 | } 28 | } 29 | out_chars[cursor++] = json[i]; 30 | } 31 | } 32 | out_chars[cursor] = 0; 33 | out = String(out_chars); 34 | return out; 35 | } 36 | 37 | String jsonIndexList(String json, int idx){ 38 | int count = 1; // number of braces seen { = +1 } = -1 39 | int i = 1; 40 | int item_idx = 0; 41 | int start = i; 42 | int stop = json.length() - 1; 43 | 44 | while(i < json.length() && count > 0){ 45 | if(json.charAt(i) == ']' or json.charAt(i) == '}'){ 46 | count--; 47 | } 48 | if(json.charAt(i) == '{' or json.charAt(i) == '['){ 49 | count++; 50 | } 51 | if(count == 1 && json.charAt(i) == ',' && item_idx == idx){ 52 | //item separator! 53 | stop = i; 54 | return json.substring(start, stop); 55 | } 56 | if(count == 1 && json.charAt(i) == ']' && item_idx == idx){ 57 | stop = i + 1; 58 | return json.substring(start, stop); 59 | } 60 | if(count == 1 && json.charAt(i) == ','){ 61 | item_idx++; 62 | start = i + 1; 63 | } 64 | i++; 65 | } 66 | return json.substring(start, stop); 67 | } 68 | 69 | // return a sub-json struct 70 | String jsonExtract(String json, String name){ 71 | char next; 72 | int start = 0, stop = 0; 73 | 74 | name = String("\"") + name + String("\""); 75 | if (json.indexOf(name) == std::string::npos) return json.substring(0,0); 76 | start = json.indexOf(name) + name.length() + 1; 77 | next = json.charAt(start); 78 | if(next == '\"'){ 79 | //Serial.println(".. a string"); 80 | start = start + 1; 81 | stop = json.indexOf('"', start + 1); 82 | } 83 | else if(next == '['){ 84 | //Serial.println(".. a list"); 85 | int count = 1; 86 | int i = start; 87 | while(count > 0 && i++ < json.length()){ 88 | if(json.charAt(i) == ']'){ 89 | count--; 90 | } 91 | else if(json.charAt(i) == '['){ 92 | count++; 93 | } 94 | } 95 | stop = i + 1; 96 | } 97 | else if(next == '{'){ 98 | //Serial.println(".. a struct"); 99 | int count = 1; 100 | int i = start; 101 | while(count > 0 && i++ < json.length()){ 102 | if(json.charAt(i) == '}'){ 103 | count--; 104 | } 105 | else if(json.charAt(i) == '{'){ 106 | count++; 107 | } 108 | } 109 | stop = i + 1; 110 | } 111 | else if(next == '.' || next == '-' || ('0' <= next && next <= '9')){ 112 | //Serial.println(".. a number"); 113 | int i = start; 114 | while((i++ < json.length() && json.charAt(i) == '.') || ('0' <= json.charAt(i) && json.charAt(i) <= '9')){ 115 | } 116 | stop = i; 117 | } 118 | return json.substring(start, stop); 119 | } 120 | 121 | -------------------------------------------------------------------------------- /src/jsonlib/jsonlib.h: -------------------------------------------------------------------------------- 1 | #ifndef JSONLIB_H 2 | #define JSONLIB_H 3 | 4 | #include 5 | 6 | // remove all white space from the json string... preserving strings 7 | String jsonRemoveWhiteSpace(String json); 8 | 9 | // index a json list 10 | String jsonIndexList(String json, int idx); 11 | 12 | // extract a json component from json 13 | String jsonExtract(String json, String name); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/logo.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Logo (svg format) 3 | */ 4 | 5 | const uint8_t logo_svg[] = R"=====( 6 | 21 | 23 | 24 | 26 | image/svg+xml 27 | 29 | 30 | 31 | 32 | 34 | 36 | 37 | ESP32 CAM Webserver - logo 39 | 46 | 53 | 60 | 65 | 69 | 73 | 77 | 81 | 85 | 89 | 90 | 93 | 96 | 99 | 102 | 105 | 108 | 111 | 114 | 117 | 120 | 123 | 126 | 129 | 132 | 135 | )====="; 136 | 137 | size_t logo_svg_len = sizeof(logo_svg)-1; 138 | -------------------------------------------------------------------------------- /storage.cpp: -------------------------------------------------------------------------------- 1 | #include "esp_camera.h" 2 | #include "src/jsonlib/jsonlib.h" 3 | #include "storage.h" 4 | 5 | // These are defined in the main .ino file 6 | extern void flashLED(int flashtime); 7 | extern int myRotation; // Rotation 8 | extern int lampVal; // The current Lamp value 9 | extern int8_t detection_enabled; // Face detection enable 10 | extern int8_t recognition_enabled; // Face recognition enable 11 | 12 | /* 13 | * Useful utility when debugging... 14 | */ 15 | 16 | void listDir(fs::FS &fs, const char * dirname, uint8_t levels){ 17 | Serial.printf("Listing SPIFFS directory: %s\n", dirname); 18 | 19 | File root = fs.open(dirname); 20 | if(!root){ 21 | Serial.println("- failed to open directory"); 22 | return; 23 | } 24 | if(!root.isDirectory()){ 25 | Serial.println(" - not a directory"); 26 | return; 27 | } 28 | 29 | File file = root.openNextFile(); 30 | while(file){ 31 | if(file.isDirectory()){ 32 | Serial.print(" DIR : "); 33 | Serial.println(file.name()); 34 | if(levels){ 35 | listDir(fs, file.name(), levels -1); 36 | } 37 | } else { 38 | Serial.print(" FILE: "); 39 | Serial.print(file.name()); 40 | Serial.print("\tSIZE: "); 41 | Serial.println(file.size()); 42 | } 43 | file = root.openNextFile(); 44 | } 45 | } 46 | 47 | void dumpPrefs(fs::FS &fs){ 48 | if (fs.exists(PREFERENCES_FILE)) { 49 | // Dump contents for debug 50 | File file = fs.open(PREFERENCES_FILE, FILE_READ); 51 | while (file.available()) Serial.print(char(file.read())); 52 | Serial.println(""); 53 | file.close(); 54 | } else { 55 | Serial.printf("%s not found, nothing to dump.\n", PREFERENCES_FILE); 56 | } 57 | } 58 | 59 | void loadPrefs(fs::FS &fs){ 60 | if (fs.exists(PREFERENCES_FILE)) { 61 | // read file into a string 62 | String prefs; 63 | Serial.printf("Loading preferences from file %s\n", PREFERENCES_FILE); 64 | File file = fs.open(PREFERENCES_FILE, FILE_READ); 65 | if (!file) { 66 | Serial.println("Failed to open preferences file"); 67 | return; 68 | } 69 | size_t size = file.size(); 70 | if (size > 800) { 71 | Serial.println("Preferences file size is too large, maybe corrupt"); 72 | return; 73 | } 74 | while (file.available()) prefs += char(file.read()); 75 | // get sensor reference 76 | sensor_t * s = esp_camera_sensor_get(); 77 | // process all the settings 78 | lampVal = jsonExtract(prefs, "lamp").toInt(); 79 | s->set_framesize(s, (framesize_t)jsonExtract(prefs, "framesize").toInt()); 80 | s->set_quality(s, jsonExtract(prefs, "quality").toInt()); 81 | s->set_brightness(s, jsonExtract(prefs, "brightness").toInt()); 82 | s->set_contrast(s, jsonExtract(prefs, "contrast").toInt()); 83 | s->set_saturation(s, jsonExtract(prefs, "saturation").toInt()); 84 | s->set_special_effect(s, jsonExtract(prefs, "special_effect").toInt()); 85 | s->set_wb_mode(s, jsonExtract(prefs, "wb_mode").toInt()); 86 | s->set_whitebal(s, jsonExtract(prefs, "awb").toInt()); 87 | s->set_awb_gain(s, jsonExtract(prefs, "awb_gain").toInt()); 88 | s->set_exposure_ctrl(s, jsonExtract(prefs, "aec").toInt()); 89 | s->set_aec2(s, jsonExtract(prefs, "aec2").toInt()); 90 | s->set_ae_level(s, jsonExtract(prefs, "ae_level").toInt()); 91 | s->set_aec_value(s, jsonExtract(prefs, "aec_value").toInt()); 92 | s->set_gain_ctrl(s, jsonExtract(prefs, "agc").toInt()); 93 | s->set_agc_gain(s, jsonExtract(prefs, "agc_gain").toInt()); 94 | s->set_gainceiling(s, (gainceiling_t)jsonExtract(prefs, "gainceiling").toInt()); 95 | s->set_bpc(s, jsonExtract(prefs, "bpc").toInt()); 96 | s->set_wpc(s, jsonExtract(prefs, "wpc").toInt()); 97 | s->set_raw_gma(s, jsonExtract(prefs, "raw_gma").toInt()); 98 | s->set_lenc(s, jsonExtract(prefs, "lenc").toInt()); 99 | s->set_vflip(s, jsonExtract(prefs, "vflip").toInt()); 100 | s->set_hmirror(s, jsonExtract(prefs, "hmirror").toInt()); 101 | s->set_dcw(s, jsonExtract(prefs, "dcw").toInt()); 102 | s->set_colorbar(s, jsonExtract(prefs, "colorbar").toInt()); 103 | detection_enabled = jsonExtract(prefs, "face_detect").toInt(); 104 | recognition_enabled = jsonExtract(prefs, "face_recognize").toInt(); 105 | myRotation = jsonExtract(prefs, "rotate").toInt(); 106 | // close the file 107 | file.close(); 108 | dumpPrefs(SPIFFS); 109 | } else { 110 | Serial.printf("Preference file %s not found; using system defaults.\n", PREFERENCES_FILE); 111 | } 112 | } 113 | 114 | void savePrefs(fs::FS &fs){ 115 | if (fs.exists(PREFERENCES_FILE)) { 116 | Serial.printf("Updating %s\n", PREFERENCES_FILE); 117 | } else { 118 | Serial.printf("Creating %s\n", PREFERENCES_FILE); 119 | } 120 | File file = fs.open(PREFERENCES_FILE, FILE_WRITE); 121 | static char json_response[1024]; 122 | sensor_t * s = esp_camera_sensor_get(); 123 | char * p = json_response; 124 | *p++ = '{'; 125 | p+=sprintf(p, "\"lamp\":%i,", lampVal); 126 | p+=sprintf(p, "\"framesize\":%u,", s->status.framesize); 127 | p+=sprintf(p, "\"quality\":%u,", s->status.quality); 128 | p+=sprintf(p, "\"brightness\":%d,", s->status.brightness); 129 | p+=sprintf(p, "\"contrast\":%d,", s->status.contrast); 130 | p+=sprintf(p, "\"saturation\":%d,", s->status.saturation); 131 | p+=sprintf(p, "\"special_effect\":%u,", s->status.special_effect); 132 | p+=sprintf(p, "\"wb_mode\":%u,", s->status.wb_mode); 133 | p+=sprintf(p, "\"awb\":%u,", s->status.awb); 134 | p+=sprintf(p, "\"awb_gain\":%u,", s->status.awb_gain); 135 | p+=sprintf(p, "\"aec\":%u,", s->status.aec); 136 | p+=sprintf(p, "\"aec2\":%u,", s->status.aec2); 137 | p+=sprintf(p, "\"ae_level\":%d,", s->status.ae_level); 138 | p+=sprintf(p, "\"aec_value\":%u,", s->status.aec_value); 139 | p+=sprintf(p, "\"agc\":%u,", s->status.agc); 140 | p+=sprintf(p, "\"agc_gain\":%u,", s->status.agc_gain); 141 | p+=sprintf(p, "\"gainceiling\":%u,", s->status.gainceiling); 142 | p+=sprintf(p, "\"bpc\":%u,", s->status.bpc); 143 | p+=sprintf(p, "\"wpc\":%u,", s->status.wpc); 144 | p+=sprintf(p, "\"raw_gma\":%u,", s->status.raw_gma); 145 | p+=sprintf(p, "\"lenc\":%u,", s->status.lenc); 146 | p+=sprintf(p, "\"vflip\":%u,", s->status.vflip); 147 | p+=sprintf(p, "\"hmirror\":%u,", s->status.hmirror); 148 | p+=sprintf(p, "\"dcw\":%u,", s->status.dcw); 149 | p+=sprintf(p, "\"colorbar\":%u,", s->status.colorbar); 150 | p+=sprintf(p, "\"face_detect\":%u,", detection_enabled); 151 | p+=sprintf(p, "\"face_recognize\":%u,", recognition_enabled); 152 | p+=sprintf(p, "\"rotate\":\"%d\"", myRotation); 153 | *p++ = '}'; 154 | *p++ = 0; 155 | file.print(json_response); 156 | file.close(); 157 | dumpPrefs(SPIFFS); 158 | } 159 | 160 | void removePrefs(fs::FS &fs) { 161 | if (fs.exists(PREFERENCES_FILE)) { 162 | Serial.printf("Removing %s\r\n", PREFERENCES_FILE); 163 | if (!fs.remove(PREFERENCES_FILE)) { 164 | Serial.println("Error removing preferences"); 165 | } 166 | } else { 167 | Serial.println("No saved preferences file to remove"); 168 | } 169 | } 170 | 171 | void saveFaceDB(fs::FS &fs) { 172 | // Stub! 173 | return; 174 | } 175 | void loadFaceDB(fs::FS &fs) { 176 | // Stub! 177 | return; 178 | } 179 | void removeFaceDB(fs::FS &fs) { 180 | // Stub! 181 | return; 182 | } 183 | 184 | void filesystemStart(){ 185 | while ( !SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED) ) { 186 | // if we sit in this loop something is wrong; 187 | // if no existing spiffs partition exists one should be automagically created. 188 | Serial.println("SPIFFS Mount failed, this can happen on first-run initialisation."); 189 | Serial.println("If it happens repeatedly check if a SPIFFS partition is present for your board?"); 190 | for (int i=0; i<10; i++) { 191 | flashLED(100); // Show SPIFFS failure 192 | delay(100); 193 | } 194 | delay(1000); 195 | Serial.println("Retrying.."); 196 | } 197 | Serial.println("Internal filesystem contents"); 198 | listDir(SPIFFS, "/", 0); 199 | } 200 | -------------------------------------------------------------------------------- /storage.h: -------------------------------------------------------------------------------- 1 | #include "FS.h" 2 | #include "SPIFFS.h" 3 | 4 | #define FORMAT_SPIFFS_IF_FAILED true 5 | 6 | #define PREFERENCES_FILE "/esp32cam-preferences.json" 7 | #define FACE_DB_FILE "/esp32cam-facedb" 8 | 9 | extern void dumpPrefs(fs::FS &fs); 10 | extern void loadPrefs(fs::FS &fs); 11 | extern void removePrefs(fs::FS &fs); 12 | extern void savePrefs(fs::FS &fs); 13 | extern void loadFaceDB(fs::FS &fs); 14 | extern void removeFaceDB(fs::FS &fs); 15 | extern void saveFaceDB(fs::FS &fs); 16 | 17 | extern void filesystemStart(); 18 | --------------------------------------------------------------------------------