├── CMakeLists.txt ├── LICENSE ├── README.md ├── flask ├── Flask_logo.png ├── main.py └── templates │ ├── _layout.html │ ├── index.html │ ├── no_permission.html │ ├── not_found.html │ └── view.html ├── http_server.py ├── main ├── CMakeLists.txt ├── Kconfig.projbuild ├── camera_pin.h ├── cmd.h ├── component.mk ├── gpio.c ├── http_client.c ├── http_server.c ├── idf_component.yml ├── keyboard.c ├── main.c ├── tcp_server.c └── udp_server.c ├── partitions.csv ├── sdkconfig.defaults ├── tcp_send.py └── udp_send.py /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's CMakeLists 2 | # in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(http-camera) 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 nopnop2002 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp-idf-http-camera 2 | Take a picture and Publish it via HTTP. 3 | This project use [this](https://components.espressif.com/components/espressif/esp32-camera) Camera Driver. 4 | 5 | ![slide-0001](https://user-images.githubusercontent.com/6020549/119491922-7a092e00-bd99-11eb-8260-a52e9f5bddc2.jpg) 6 | ![slide-0002](https://user-images.githubusercontent.com/6020549/119491927-7bd2f180-bd99-11eb-88aa-a4c4c9ab6c84.jpg) 7 | 8 | # Hardware requirements 9 | ESP32 development board with OV2640 camera. 10 | If you use other camera, edit sdkconfig.default. 11 | From the left: 12 | - Aithinker ESP32-CAM 13 | - Freenove ESP32-WROVER CAM 14 | - UICPAL ESPS3 CAM 15 | - Freenove ESP32S3-WROVER CAM (Clone) 16 | 17 | ![es32-camera](https://github.com/nopnop2002/esp-idf-websocket-camera/assets/6020549/38dbef9a-ed85-4df2-8d22-499b2b497278) 18 | 19 | ## Software requirements 20 | ESP-IDF V5.0 or later. 21 | ESP-IDF V4.4 release branch reached EOL in July 2024. 22 | 23 | # Start HTTP Server 24 | You can use a server using flask or a simple server. 25 | 26 | ## flask HTTP server 27 | ``` 28 | sudo apt update 29 | sudo apt install python3-pip python3-setuptools libimage-exiftool-perl jhead 30 | python3 -m pip install -U pip 31 | python3 -m pip install -U wheel 32 | python3 -m pip install -U Werkzeug 33 | python3 -m pip install -U pillow 34 | python3 -m pip install -U python-magic 35 | python3 -m pip install -U requests 36 | python3 -m pip install -U flask 37 | python3 -m pip install -U piexif 38 | git clone https://github.com/nopnop2002/esp-idf-http-camera 39 | cd esp-idf-http-camera/flask 40 | python3 main.py 41 | ``` 42 | 43 | Open your browser and enter the host address in the address bar. 44 | ![Image](https://github.com/user-attachments/assets/01427bcf-e51d-40cd-a6cf-03fa41a2912f) 45 | 46 | When you start ESP32, a list of ESP32 will be displayed. 47 | ![Image](https://github.com/user-attachments/assets/4b42727e-5afc-4d95-bbec-d57b9131ca45) 48 | 49 | Select ESP32 and then press the Take Picture button. 50 | You can add Exif to JPEG. 51 | ![Image](https://github.com/user-attachments/assets/62d4a56e-226b-4131-8d96-de8185e5441c) 52 | 53 | ESP32 takes a photo and transmits it to the server. 54 | You can see the photos. 55 | ![Image](https://github.com/user-attachments/assets/885a06e4-50d0-42dd-a475-29ec8b24a85d) 56 | ![Image](https://github.com/user-attachments/assets/5d7207c9-a4f0-448e-9e76-5c16dd87b8be) 57 | ![Image](https://github.com/user-attachments/assets/898c799f-4c6a-4416-9452-2d0fab5ec94a) 58 | ![Image](https://github.com/user-attachments/assets/fae46e75-7f57-4807-b425-cfd206162a81) 59 | 60 | - Directory to save photos 61 | This project will save photos in the following directory: 62 | `esp-idf-http-camera/flask/uploaded` 63 | ``` 64 | $ pwd 65 | /home/nop/esp-idf-http-camera/flask/uploaded 66 | $ ls 67 | picture0_1024x768.jpg picture2_800x600.jpg 68 | picture1_1280x720.jpg 69 | ``` 70 | 71 | - About Exif tags 72 | In this project, we will use exiftool to add Exif tags to JPG files. 73 | The name of the tag to be added is `UserComment`. 74 | You can view the Exif content with the following command: 75 | `jhead *.jpg` 76 | ![Image](https://github.com/user-attachments/assets/7506b9b3-bbbf-414b-b5f3-2ee665fcf7d2) 77 | 78 | You can use exiftool to modify the Exif tags. 79 | Exiftool will save the file with the original Exif tag changes under a different name. 80 | ``` 81 | $ jhead picture2_800x600.jpg 82 | File name : picture2_800x600.jpg 83 | File size : 15020 bytes 84 | File date : 2025:04:23 03:47:27 85 | Resolution : 800 x 600 86 | JPEG Quality : 62 87 | Comment : test-03 88 | 89 | $ exiftool -usercomment=test-03-01 picture2_800x600.jpg 90 | 1 image files updated 91 | 92 | $ jhead picture2_800x600.jpg 93 | File name : picture2_800x600.jpg 94 | File size : 15022 bytes 95 | File date : 2025:04:23 04:11:26 96 | Resolution : 800 x 600 97 | JPEG Quality : 62 98 | Comment : test-03-01 99 | 100 | $ ls picture2_800x600* 101 | picture2_800x600.jpg picture2_800x600.jpg_original 102 | ``` 103 | 104 | jhead treats UserComment tags the same as Comment tags. 105 | exiftool distinguishes between UserComment and Comment tags. 106 | An explanation of the difference between UserComment tags and Comment tags can be found [here](https://exiftool.org/forum/index.php?topic=12466.0). 107 | 108 | 109 | ## Simple HTTP server 110 | ``` 111 | python3 -m pip install -U wheel 112 | python3 -m pip install opencv-python 113 | git clone https://github.com/nopnop2002/esp-idf-http-camera 114 | python3 ./http_server.py --help 115 | usage: http_server.py [-h] [--port PORT] [--timeout TIMEOUT] 116 | 117 | options: 118 | -h, --help show this help message and exit 119 | --port PORT http port 120 | --timeout TIMEOUT wait time for keyboard input[sec] 121 | ``` 122 | When timeout is specified, display the image for the specified number of seconds. 123 | When timeout is not specified, the image will be displayed until the ESC key is pressed. 124 | New requests are queued while the image is displayed. 125 | __Close the image window with the ESC key. Do not use the close button.__ 126 | ![opencv](https://github.com/nopnop2002/esp-idf-mqtt-camera/assets/6020549/516b2f25-d285-47d6-ae56-ee1cceed5c58) 127 | This script works not only on Linux but also on Windows 10. 128 | I used Python 3.9.13 for Windows. 129 | ![Image](https://github.com/user-attachments/assets/b1d4f037-3be3-4b02-bea7-6856aa2e1f8e) 130 | 131 | 132 | ## Installation for ESP32 133 | For AiThinker ESP32-CAM, you need to use a USB-TTL converter and connect GPIO0 to GND. 134 | 135 | |ESP-32|USB-TTL| 136 | |:-:|:-:| 137 | |U0TXD|RXD| 138 | |U0RXD|TXD| 139 | |GPIO0|GND| 140 | |5V|5V| 141 | |GND|GND| 142 | 143 | 144 | ``` 145 | git clone https://github.com/nopnop2002/esp-idf-http-camera 146 | cd esp-idf-http-camera 147 | idf.py set-target {esp32/esp32s3} 148 | idf.py menuconfig 149 | idf.py flash monitor 150 | ``` 151 | 152 | ## Start firmware 153 | For AiThinker ESP32-CAM, Change GPIO0 to open and press the RESET button. 154 | 155 | ## Configuration 156 | ![config-main](https://github.com/user-attachments/assets/3bdb6876-d47c-4396-88d1-395ffaeda2cc) 157 | ![config-app](https://user-images.githubusercontent.com/6020549/200204471-35b658fc-40b7-47aa-b0ba-86979049eba4.jpg) 158 | 159 | ### Wifi Setting 160 | Set the information of your access point. 161 | ![config-wifi-1](https://user-images.githubusercontent.com/6020549/119243503-529c4080-bba2-11eb-92c5-b59f66f9fea6.jpg) 162 | You can connect using the mDNS hostname instead of the IP address. 163 | ![config-wifi-2](https://user-images.githubusercontent.com/6020549/119243504-5334d700-bba2-11eb-8c77-f958251d8611.jpg) 164 | You can use static IP. 165 | ![config-wifi-3](https://user-images.githubusercontent.com/6020549/119243505-5334d700-bba2-11eb-9677-47cb6d1f9536.jpg) 166 | 167 | 168 | ### HTTP Server Setting 169 | Specify the IP address and port number of the http server. 170 | You can use mDNS hostnames instead of IP addresses. 171 | ![Image](https://github.com/user-attachments/assets/1d8ddb66-9442-4dac-a214-87ea6b2c4ca6) 172 | 173 | ### Attached File Name Setting 174 | You can select the file name to send to the HTTP server from the following. 175 | - Always the same file name 176 | ![config-filename-1](https://user-images.githubusercontent.com/6020549/119243498-5203aa00-bba2-11eb-87d5-053636dbb85a.jpg) 177 | - File name based on date and time 178 | When you choose date and time file name, you will need an NTP server. 179 | The file name will be YYYYMMDD-hhmmss.jpg. 180 | ![config-filename-2](https://user-images.githubusercontent.com/6020549/119243499-5203aa00-bba2-11eb-8c0f-6bb42d125d64.jpg) 181 | 182 | - Add FrameSize to Remote file Name 183 | When this is enabled, FrameSize is added to remote file name like this. 184 | `picture_800x600.jpg` 185 | `20210520-165740_800x600.jpg` 186 | ![config-filename-3](https://user-images.githubusercontent.com/6020549/119243501-529c4080-bba2-11eb-8ba4-85cdd764b0fc.jpg) 187 | 188 | 189 | ### Select Board 190 | ![config-board](https://github.com/nopnop2002/esp-idf-http-camera/assets/6020549/732c9574-68df-4e52-9b9e-6071d7ce5737) 191 | 192 | 193 | ### Select Frame Size 194 | Large frame sizes take longer to take a picture. 195 | ![config-framesize-1](https://user-images.githubusercontent.com/6020549/118947689-8bfe6180-b992-11eb-8657-b4e86d3acc70.jpg) 196 | ![config-framesize-2](https://user-images.githubusercontent.com/6020549/118947692-8d2f8e80-b992-11eb-9caa-1f6b6cb2210e.jpg) 197 | 198 | ### Select Shutter 199 | ESP32 acts as a HTTP server and listens for requests from HTTP clients. 200 | You can use this command as shutter. 201 | `curl -X POST http://{ESP32's IP Address}:8080/post` 202 | If your ESP32's IP address is `192.168.10.157`, it will look like this. 203 | `curl -X POST http://192.168.10.157:8080/post` 204 | 205 | In addition to this, you can select the following triggers: 206 | 207 | - Shutter is the Enter key on the keyboard 208 | For operation check. 209 | When using the USB port provided by the USB Serial/JTAG Controller Console, you need to enable the following line in sdkconfig. 210 | ``` 211 | CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y 212 | ``` 213 | ![config-shutter-1](https://user-images.githubusercontent.com/6020549/99890068-db432e00-2c9e-11eb-84e2-4e6c5f05fb7a.jpg) 214 | 215 | 216 | - Shutter is a GPIO toggle 217 | 218 | - Initial Sate is PULLDOWN 219 | The shutter is prepared when it is turned from OFF to ON, and a picture is taken when it is turned from ON to OFF. 220 | 221 | - Initial Sate is PULLUP 222 | The shutter is prepared when it is turned from ON to OFF, and a picture is taken when it is turned from OFF to ON. 223 | 224 | I confirmed that the following GPIO can be used. 225 | 226 | |GPIO|PullDown|PullUp| 227 | |:-:|:-:|:-:| 228 | |GPIO12|OK|NG| 229 | |GPIO13|OK|OK| 230 | |GPIO14|OK|OK| 231 | |GPIO15|OK|OK| 232 | |GPIO16|NG|NG| 233 | 234 | ![config-shutter-2](https://user-images.githubusercontent.com/6020549/99897437-d2714d00-2cdc-11eb-8e59-c8bf4ef25d62.jpg) 235 | 236 | - Shutter is TCP Socket 237 | ESP32 acts as a TCP server and listens for requests from TCP clients. 238 | You can use tcp_send.py as shutter. 239 | `python3 ./tcp_send.py` 240 | ![Image](https://github.com/user-attachments/assets/4c301018-2f8c-4644-be3f-417222fb1842) 241 | 242 | - Shutter is UDP Socket 243 | ESP32 acts as a UDP listener and listens for requests from UDP clients. 244 | You can use this command as shutter. 245 | `echo -n "take" | socat - UDP-DATAGRAM:255.255.255.255:49876,broadcast` 246 | You can use udp_send.py as shutter. 247 | Requires netifaces. 248 | `python3 ./udp_send.py` 249 | ![Image](https://github.com/user-attachments/assets/3dcd72be-d0ef-4bd9-9273-f420ca88f11b) 250 | You can use these devices as shutters. 251 | ![Image](https://github.com/user-attachments/assets/cc97da4e-6c06-4604-8362-f81c6fb6eb58) 252 | Click [here](https://github.com/nopnop2002/esp-idf-selfie-trigger) for details. 253 | 254 | 255 | ### Flash Light 256 | ESP32-CAM by AI-Thinker have flash light on GPIO4. 257 | 258 | ![config-flash](https://user-images.githubusercontent.com/6020549/99890190-0b3f0100-2ca0-11eb-94c6-ba7e2cfe1727.jpg) 259 | 260 | ## PSRAM 261 | When you use ESP32S3-WROVER CAM, you need to change the PSRAM type. 262 | 263 | ![config-psram](https://github.com/nopnop2002/esp-idf-websocket-camera/assets/6020549/ba04f088-c628-46ac-bc5b-2968032753e0) 264 | 265 | # View picture using Built-in WEB Server 266 | ESP32 works as a web server. 267 | You can view the pictures taken using the built-in WEB server. 268 | Enter the ESP32's IP address and port number in the address bar of your browser. 269 | You can connect using mDNS hostname instead of IP address. 270 | 271 | ![browser](https://user-images.githubusercontent.com/6020549/124227364-837a7880-db45-11eb-9d8b-fa15c676adac.jpg) 272 | -------------------------------------------------------------------------------- /flask/Flask_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nopnop2002/esp-idf-http-camera/751cb9e32e08e5fea3c1694fc6bfb5d1d65e2de9/flask/Flask_logo.png -------------------------------------------------------------------------------- /flask/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import sys 5 | import time 6 | import json 7 | import subprocess 8 | import pathlib 9 | import datetime 10 | 11 | # Non standard library 12 | # python3 -m pip install -U pillow 13 | from PIL import Image 14 | # python3 -m pip install -U python-magic 15 | import magic 16 | # python3 -m pip install -U requests 17 | import requests 18 | # python3 -m pip install -U flask 19 | #from flask import Flask, render_template, request, redirect, url_for 20 | from flask import * 21 | # Override flask.logging 22 | import logging 23 | # python3 -m pip install -U Werkzeug 24 | import werkzeug 25 | #from werkzeug.serving import WSGIRequestHandler 26 | # python3 -m pip install piexif 27 | import piexif 28 | 29 | app = Flask(__name__) 30 | logging.basicConfig(level=logging.INFO) 31 | 32 | # Initialize node list 33 | nodeList = [] 34 | waitList = [] 35 | 36 | # create UPLOAD_DIR 37 | UPLOAD_DIR = os.path.join(os.getcwd(), "uploaded") 38 | logging.info("UPLOAD_DIR={}".format(UPLOAD_DIR)) 39 | if (os.path.exists(UPLOAD_DIR) == False): 40 | logging.warning("UPLOAD_DIR [{}] not found. Create this".format(UPLOAD_DIR)) 41 | os.mkdir(UPLOAD_DIR) 42 | 43 | # Added /uploaded to static_url_path 44 | add_app = Blueprint("uploaded", __name__, static_url_path='/uploaded', static_folder='./uploaded') 45 | app.register_blueprint(add_app) 46 | 47 | # Execute external process 48 | def exec_subprocess(cmd: str, raise_error=True): 49 | child = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 50 | stdout, stderr = child.communicate() 51 | rt = child.returncode 52 | if rt != 0 and raise_error: 53 | raise Exception(f"command return code is not 0. got {rt}. stderr = {stderr}") 54 | 55 | return stdout, stderr, rt 56 | 57 | @app.route("/") 58 | def get(): 59 | fileList = [] 60 | dirList = [] 61 | for (dirpath, dirnames, filenames) in os.walk(UPLOAD_DIR): 62 | logging.debug("filenames={}".format(filenames)) 63 | for name in filenames: 64 | nm = os.path.join(dirpath, name).replace(UPLOAD_DIR, "").strip("/").split("/") 65 | logging.debug("nm={}".format(nm)) 66 | # Skip if the file is in a subdirect 67 | # nm=['templates', 'index.html'] 68 | if len(nm) != 1: continue 69 | 70 | fullpath = os.path.join(dirpath, name) 71 | logging.debug("fullpath={}".format(fullpath)) 72 | if os.path.isfile(fullpath) == False: continue 73 | size = os.stat(fullpath).st_size 74 | 75 | mime = magic.from_file(fullpath, mime=True) 76 | logging.debug("mime={}".format(mime)) 77 | visible = "image/" in mime 78 | if (visible == False): 79 | visible = "text/" in mime 80 | logging.debug("visible={}".format(visible)) 81 | 82 | p = pathlib.Path(fullpath) 83 | dt = datetime.datetime.fromtimestamp(p.stat().st_ctime) 84 | ctime = dt.strftime('%Y/%m/%d %H:%M:%S') 85 | 86 | exif = "" 87 | if (mime == "image/jpeg"): 88 | image = Image.open(fullpath) 89 | try: 90 | exif_dict = piexif.load(image.info["exif"]) 91 | exif = exif_dict["Exif"][piexif.ExifIFD.UserComment].decode("utf-8") 92 | exif = exif.replace('ASCII', '') 93 | except: 94 | pass 95 | logging.debug("exif=[{}]".format(exif)) 96 | 97 | fileList.append({ 98 | "name": name, 99 | "size": str(size) + " B", 100 | "mime": mime, 101 | "fullname": fullpath, 102 | "visible": visible, 103 | "exif": exif, 104 | "ctime": ctime, 105 | }) 106 | 107 | # Handling missing nodes 108 | nowtime = time.time() 109 | for i in range(len(nodeList)): 110 | logging.debug("nodeList[{}]={}".format(i, nodeList[i])) 111 | timestamp = nodeList[i][6] 112 | difftime = nowtime - timestamp 113 | logging.debug("timestamp={} difftime={}".format(timestamp, difftime)) 114 | if (difftime > 30): 115 | del nodeList[i] 116 | break; 117 | 118 | for i in range(len(waitList)): 119 | logging.debug("waitList[{}]={}".format(i, waitList[i])) 120 | timestamp = waitList[i][2] 121 | difftime = nowtime - timestamp 122 | logging.debug("timestamp={} difftime={}".format(timestamp, difftime)) 123 | if (difftime > 30): 124 | del waitList[i] 125 | break; 126 | 127 | meta = { 128 | "current_directory": UPLOAD_DIR, 129 | "node_list_count": len(nodeList), 130 | } 131 | 132 | logging.info("nodeList={}".format(nodeList)) 133 | logging.info("waitList={}".format(waitList)) 134 | logging.debug("dirList={}".format(dirList)) 135 | logging.debug("files={}".format(fileList)) 136 | logging.debug("meta={}".format(meta)) 137 | templateData = { 138 | 'nodes' : sorted(nodeList), 139 | 'files' : sorted(fileList, key=lambda k: k["name"].lower()), 140 | 'folders': dirList, 141 | 'meta' : meta 142 | } 143 | return render_template("index.html", **templateData) 144 | 145 | @app.route("/download") 146 | def download(): 147 | filename = request.args.get('filename', default=None, type=str) 148 | logging.info("{}:filename={}".format(sys._getframe().f_code.co_name, filename)) 149 | 150 | if os.path.isfile(filename): 151 | if os.path.dirname(filename) == UPLOAD_DIR.rstrip("/"): 152 | return send_file(filename, as_attachment=True) 153 | else: 154 | return render_template("no_permission.html") 155 | else: 156 | return render_template("not_found.html") 157 | return None 158 | 159 | @app.route("/imageview") 160 | def imageview(): 161 | filename = request.args.get('filename', default=None, type=str) 162 | logging.info("{}:filename={}".format(sys._getframe().f_code.co_name, filename)) 163 | rotate = request.args.get('rotate', default=0, type=int) 164 | logging.info("{}:rotate={}{}".format(sys._getframe().f_code.co_name, rotate, type(rotate))) 165 | 166 | mime = magic.from_file(filename, mime=True) 167 | mime = mime.split("/") 168 | logging.info("{}:mime={}".format(sys._getframe().f_code.co_name, mime)) 169 | 170 | if (mime[0] == "image"): 171 | logging.debug("filename={}".format(filename)) 172 | filename = os.path.basename(filename) 173 | logging.debug("filename={}".format(filename)) 174 | filename = os.path.join("/uploaded", filename) 175 | logging.debug("filename={}".format(filename)) 176 | return render_template("view.html", user_image = filename, rotate=rotate) 177 | 178 | if (mime[0] == "text"): 179 | contents = "" 180 | f = open(filename, 'r') 181 | datalist = f.readlines() 182 | for data in datalist: 183 | logging.debug("[{}]".format(data.rstrip())) 184 | contents = contents + data.rstrip() + "
" 185 | return contents 186 | 187 | @app.route('/upload_multipart', methods=['POST']) 188 | def upload_multipart(): 189 | logging.info("upload_multipart") 190 | logging.info("request={}".format(request)) 191 | logging.info("request.files={}".format(request.files)) 192 | logging.info("request.remote_addr={}".format(request.remote_addr)) 193 | dict = request.files.to_dict(flat=False) 194 | logging.info("dict={}".format(dict)) 195 | 196 | 197 | ''' 198 | file is werkzeug.datastructures.FileStorage Object. 199 | This object have these member. 200 | filename:Uploaded File Name 201 | name:Field name of Form 202 | headers:HTTP request header information(header object of flask) 203 | content_length:content-length of HTTP request 204 | mimetype:mimetype 205 | 206 | ''' 207 | 208 | FileStorage = dict['upfile'][0] 209 | logging.info("FileStorage={}".format(FileStorage)) 210 | logging.info("FileStorage.filename={}".format(FileStorage.filename)) 211 | logging.info("FileStorage.mimetype={}".format(FileStorage.mimetype)) 212 | 213 | filename = FileStorage.filename 214 | filepath = os.path.join(UPLOAD_DIR, werkzeug.utils.secure_filename(filename)) 215 | #FileStorage.save(filepath) 216 | 217 | try: 218 | FileStorage.save(filepath) 219 | responce = {'result':'upload OK'} 220 | logging.info("{} uploaded {}, saved as {}".format(request.remote_addr, filename, filepath)) 221 | 222 | # Add metadata to jpeg 223 | logging.info("request.remote_addr={}".format(request.remote_addr)) 224 | for i in range(len(waitList)): 225 | wait_addr = waitList[i][0] 226 | wait_exif = waitList[i][1] 227 | logging.info("wait_addr={} wait_exif=[{}]".format(wait_addr, wait_exif)) 228 | if (wait_addr == request.remote_addr): 229 | if (wait_exif != ""): 230 | # Add exif to usercomment using exiftool 231 | cmd = "exiftool -usercomment='{}' {}".format(wait_exif, filepath) 232 | logging.info("cmd=[{}]".format(cmd)) 233 | stdout, stderr, rt = exec_subprocess(cmd) 234 | logging.info("exec_subprocess stdout={} stderr={} rt={}".format(stdout, stderr, rt)) 235 | backpath = filepath + "_original" 236 | os.remove(backpath) 237 | del waitList[i] 238 | break; 239 | 240 | except IOError as e: 241 | logging.error("Failed to write file due to IOError %s", str(e)) 242 | responce = {'result':'upload FAIL'} 243 | 244 | #return json.dumps(responce) 245 | return Response(json.dumps(responce), mimetype='text/plain') 246 | 247 | @app.route("/select", methods=["POST"]) 248 | def select(): 249 | logging.info("select: request.form={}".format(request.form)) 250 | selected = request.form.getlist('selected') 251 | logging.info("selected={}".format(selected)) 252 | logging.info("len(selected)={}".format(len(selected))) 253 | for ip in selected: 254 | exif = request.form.get('exif') 255 | logging.info("select: exif=[{}]".format(exif)) 256 | logging.info("select: ip=[{}]".format(ip)) 257 | url = "http://{}:8080/post".format(ip) 258 | logging.info("select: url={}".format(url)) 259 | Uresponse = requests.post(url) 260 | logging.info("select: Uresponse={}".format(Uresponse)) 261 | 262 | # Add to waiting list for upload for metadata 263 | utime = time.time() 264 | wait = [] 265 | wait.append(ip) #0 266 | wait.append(exif) #1 267 | wait.append(utime) #2 268 | waitList.append(wait) 269 | 270 | return redirect(url_for('get')) 271 | 272 | @app.route("/node_information", methods=["POST"]) 273 | def post(): 274 | logging.debug("post: request={}".format(request)) 275 | logging.debug("post: request.data={}".format(request.data)) 276 | payload = request.data 277 | if (type(payload) is bytes): 278 | payload = payload.decode('utf-8') 279 | logging.info("post: payload={}".format(payload)) 280 | json_object = json.loads(payload) 281 | mac = json_object['mac'] 282 | logging.debug("post: mac=[{}]".format(mac)) 283 | logging.debug("post: nodeList={}".format(nodeList)) 284 | nodeIndex = None 285 | for i in range(len(nodeList)): 286 | logging.debug("post: nodeList[{}]={}".format(i, nodeList[i])) 287 | if (mac == nodeList[i][1]): 288 | nodeIndex = i 289 | 290 | logging.debug("post: nodeIndex={}".format(nodeIndex)) 291 | node = [] 292 | ip = json_object['ip'] 293 | board = json_object['board'] 294 | frame = json_object['frame'] 295 | dummy1 = 0 296 | dummy2 = 0 297 | utime = time.time() 298 | node.append(ip) #0 299 | node.append(mac) #1 300 | node.append(board) #2 301 | node.append(frame) #3 302 | node.append(dummy1) #4 303 | node.append(dummy2) #5 304 | node.append(utime) #6 305 | if (nodeIndex is None): 306 | nodeList.append(node) 307 | else: 308 | nodeList[nodeIndex] = node 309 | 310 | responce = {'result':'post OK'} 311 | #return json.dumps(responce) 312 | return Response(json.dumps(responce), mimetype='text/plain') 313 | 314 | if __name__ == "__main__": 315 | #WSGIRequestHandler.protocol_version = "HTTP/1.1" 316 | app.run(host='0.0.0.0', port=8080, debug=True) 317 | 318 | -------------------------------------------------------------------------------- /flask/templates/_layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% block title %}{% endblock %} 12 | 13 | 14 | {% block content %} 15 | {% endblock %} 16 |
17 |
18 |
19 |

20 | 21 | Get this script on GitHub! 22 | 23 |

24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /flask/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "_layout.html" %} 2 | {% block content %} 3 | 4 |
5 |
6 |
7 |
8 | 9 |
10 |
11 | Your camera list 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% for node in nodes %} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% endfor %} 34 | 35 |
Select IP MAC Board Frame
{{ node[0] }}{{ node[1] }}{{ node[2] }}{{ node[3] }}
36 |
37 | 38 | {% if meta.node_list_count != 0 %} 39 |
40 | Take picture from selected camera 41 |
42 |
43 | 44 | 48 |
49 | {% endif %} 50 |
51 | 52 |
53 | Directory listing for "{{meta.current_directory}}" 54 |
55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | {% for folder in folders %} 71 | 72 | 75 | 78 | 81 | 83 | 84 | {% endfor %} 85 | 86 | {% for file in files %} 87 | 88 | 91 | 94 | 97 | 100 | 103 | 110 | 117 | 120 | 121 | {% endfor %} 122 | 123 |
Name Size Date-Time MIME Download View View(Rotate) Exif
73 | {{ folder.name }} 74 | 76 | {{ folder.size }} 77 | 79 | {{ folder.mime }} 80 | 82 |
89 | {{ file.name }} 90 | 92 | {{ file.size }} 93 | 95 | {{ file.ctime }} 96 | 98 | {{ file.mime }} 99 | 101 | Download 102 | 104 | {% if file.visible == True %} 105 | View 106 | {% else %} 107 |
108 | {% endif %} 109 |
111 | {% if file.visible == True %} 112 | View 113 | {% else %} 114 |
115 | {% endif %} 116 |
118 | {{ file.exif }} 119 |
124 |
125 |
126 |
127 |
128 |
129 | 130 | {% endblock %} 131 | -------------------------------------------------------------------------------- /flask/templates/no_permission.html: -------------------------------------------------------------------------------- 1 | {% extends "_layout.html" %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |

Insufficient permissions!

9 |

This file is either not publicly accessible or read-protected.

10 |

11 | <-- Go back... 12 |

13 |
14 |
15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /flask/templates/not_found.html: -------------------------------------------------------------------------------- 1 | {% extends "_layout.html" %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |
8 |

File not found!

9 |

The file you are looking for was not found!

10 |

11 | <-- Go back... 12 |

13 |
14 |
15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /flask/templates/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ user_image }} 6 | 17 | 18 | 19 | {% if rotate == 0 %} 20 |
21 | {{ user_image }} 22 |
23 | {% elif rotate == 90 %} 24 |
25 | {{ user_image }} 26 |
27 | {% endif %} 28 | 29 | 30 | -------------------------------------------------------------------------------- /http_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # python3 -m pip install -U wheel 4 | # python3 -m pip install opencv-python 5 | import queue 6 | import threading 7 | import time 8 | import http.server as server 9 | import cv2 10 | import argparse 11 | 12 | queue01 = queue.Queue() 13 | queue02 = queue.Queue() 14 | imageFileName = "output.jpg" 15 | 16 | def threadView(q1, q2): 17 | time.sleep(1) 18 | localVal = threading.local() 19 | while True: 20 | if q1.empty(): 21 | time.sleep(1.0) 22 | #print("thread waiting..") 23 | else: 24 | localVal = q1.get() 25 | print("thread q1.get() localVal={} timeout={}".format(localVal, args.timeout)) 26 | image = cv2.imread(imageFileName) 27 | cv2.imshow('image', image) 28 | #cv2.waitKey(0) 29 | cv2.waitKey(args.timeout*1000) 30 | cv2.destroyWindow('image') 31 | print("thread q2.put() localVal={}".format(localVal)) 32 | q2.put(localVal) 33 | 34 | 35 | class MyHandler(server.BaseHTTPRequestHandler): 36 | def do_POST(self): 37 | self.send_response(200) 38 | self.send_header('Content-Type', 'text/plain; charset=utf-8') 39 | self.end_headers() 40 | self.wfile.write(b'{"result": "post OK"}') 41 | 42 | print('path=[{}]'.format(self.path)) 43 | if (self.path != "/upload_multipart"): return 44 | 45 | # Get request 46 | content_len = int(self.headers.get("content-length")) 47 | #body = self.rfile.read(content_len).decode('utf-8') 48 | body = self.rfile.read(content_len) 49 | print("content_len={}".format(content_len)) 50 | print("type(body)={}".format(type(body))) 51 | start = body.find(b"\r\n\r\n") 52 | print("start={}".format(start)) 53 | end = body.find(b"\r\n--X-ESPIDF_MULTIPART--\r\n\r\n") 54 | print("end={}".format(end)) 55 | 56 | image = body[start+4:end] 57 | #print("---------------------") 58 | #print(body) 59 | #print("---------------------") 60 | #print(image) 61 | 62 | outfile = open(imageFileName, 'wb') 63 | outfile.write(image) 64 | outfile.close 65 | 66 | queue01.put(0) 67 | while True: 68 | time.sleep(1) 69 | if queue02.empty(): 70 | print("thread end waiting. ESC to end.") 71 | pass 72 | else: 73 | queue02.get() 74 | break 75 | print("thread end") 76 | 77 | if __name__ == "__main__": 78 | parser = argparse.ArgumentParser() 79 | parser.add_argument('--port', type=int, help='http port', default=8080) 80 | parser.add_argument('--timeout', type=int, help='wait time for keyboard input[sec]', default=0) 81 | args = parser.parse_args() 82 | print("args.port={}".format(args.port)) 83 | print("args.timeout={}".format(args.timeout)) 84 | 85 | thread = threading.Thread(target=threadView, args=(queue01,queue02,) ,daemon = True) 86 | thread.start() 87 | 88 | host = '0.0.0.0' 89 | httpd = server.HTTPServer((host, args.port), MyHandler) 90 | #httpd = server.ThreadingHTTPServer((host, port), MyHandler) 91 | httpd.serve_forever() 92 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(srcs "main.c" "http_client.c" "http_server.c") 2 | 3 | if (CONFIG_SHUTTER_ENTER) 4 | list(APPEND srcs "keyboard.c") 5 | elseif (CONFIG_SHUTTER_GPIO) 6 | list(APPEND srcs "gpio.c") 7 | elseif (CONFIG_SHUTTER_TCP) 8 | list(APPEND srcs "tcp_server.c") 9 | elseif (CONFIG_SHUTTER_UDP) 10 | list(APPEND srcs "udp_server.c") 11 | endif() 12 | 13 | idf_component_register(SRCS "${srcs}" INCLUDE_DIRS ".") 14 | -------------------------------------------------------------------------------- /main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "Application Configuration" 2 | 3 | menu "WiFi Setting" 4 | 5 | config ESP_WIFI_SSID 6 | string "WiFi SSID" 7 | default "myssid" 8 | help 9 | SSID (network name) to connect to. 10 | 11 | config ESP_WIFI_PASSWORD 12 | string "WiFi Password" 13 | default "mypassword" 14 | help 15 | WiFi password (WPA or WPA2) to connect to. 16 | 17 | config ESP_MAXIMUM_RETRY 18 | int "Maximum retry" 19 | default 5 20 | help 21 | Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent. 22 | 23 | config MDNS_HOSTNAME 24 | string "mDNS Hostname" 25 | default "esp32-camera" 26 | help 27 | The mDNS host name used by the ESP32. 28 | 29 | config STATIC_IP 30 | bool "Enable Static IP Address" 31 | default false 32 | help 33 | Enable Static IP Address. 34 | 35 | config STATIC_IP_ADDRESS 36 | depends on STATIC_IP 37 | string "Static IP Address" 38 | default "192.168.10.100" 39 | help 40 | Static IP Address for Station. 41 | 42 | config STATIC_GW_ADDRESS 43 | depends on STATIC_IP 44 | string "Static GW Address" 45 | default "192.168.10.1" 46 | help 47 | Static GW Address for Station. 48 | 49 | config STATIC_NM_ADDRESS 50 | depends on STATIC_IP 51 | string "Static Netmask" 52 | default "255.255.255.0" 53 | help 54 | Static Netmask for Station. 55 | 56 | endmenu 57 | 58 | menu "HTTP Server Setting" 59 | 60 | config SERVER_IP 61 | string "HTTP Server" 62 | default "myhttpserver.local" 63 | help 64 | The host name or IP address of the HTTP server to use. 65 | 66 | config SERVER_PORT 67 | int "HTTP Server Port" 68 | default 8080 69 | help 70 | HTTP server port to use. 71 | 72 | endmenu 73 | 74 | menu "Attached File Name Setting" 75 | 76 | choice REMOTE_FILE 77 | bool "Select attached file name" 78 | default REMOTE_IS_FIXED_NAME 79 | help 80 | Select attached file name. 81 | 82 | config REMOTE_IS_FIXED_NAME 83 | bool "Attached file name is fixed" 84 | config REMOTE_IS_VARIABLE_NAME 85 | bool "Attached file name is date and time" 86 | endchoice 87 | 88 | config FIXED_REMOTE_FILE 89 | depends on REMOTE_IS_FIXED_NAME 90 | string "Fixed-Attached file name" 91 | default "picture.jpg" 92 | help 93 | Attached file name. 94 | 95 | config NTP_SERVER 96 | depends on REMOTE_IS_VARIABLE_NAME 97 | string "NTP Server" 98 | default "pool.ntp.org" 99 | help 100 | Hostname for NTP Server. 101 | 102 | config LOCAL_TIMEZONE 103 | depends on REMOTE_IS_VARIABLE_NAME 104 | int "Your TimeZone" 105 | range -23 23 106 | default 0 107 | help 108 | Your local timezone. When it is 0, Greenwich Mean Time. 109 | 110 | config REMOTE_FRAMESIZE 111 | bool "Add FrameSize to Remote file name" 112 | default false 113 | help 114 | Add FrameSize to Remote file name. 115 | 116 | endmenu 117 | 118 | choice BOARD 119 | bool "Select Board" 120 | default BOARD_ESP32CAM_AITHINKER 121 | help 122 | Select Board Type. 123 | 124 | config BOARD_ESP32_WROVER_FREENOVE 125 | bool "Freenove ESP32-WROVER CAM Board" 126 | config BOARD_ESP32S3_WROOM_FREENOVE 127 | bool "Freenove ESP32S3-WROOM CAM Board" 128 | config BOARD_CAMERA_MODEL_ESP_EYE 129 | bool "Espressif ESP-EYE" 130 | config BOARD_ESP32CAM_AITHINKER 131 | bool "AiThinker ESP32-CAM" 132 | config BOARD_CAMERA_MODEL_TTGO_T_JOURNAL 133 | bool "TTGO T-Journal ESP32 Camera" 134 | config BOARD_ESPS3_CAM_UICPAL 135 | bool "UICPAL ESPS3 CAM RE:1.0" 136 | endchoice 137 | 138 | choice FRAMESIZE 139 | bool "Select Frame Size" 140 | default FRAMESIZE_VGA 141 | help 142 | Select Picture Frame Size. 143 | 144 | config FRAMESIZE_VGA 145 | bool "Frame Size:640x480" 146 | config FRAMESIZE_SVGA 147 | bool "Frame Size:800x600" 148 | config FRAMESIZE_XGA 149 | bool "Frame Size:1024x768" 150 | config FRAMESIZE_HD 151 | bool "Frame Size:1280x720" 152 | config FRAMESIZE_SXGA 153 | bool "Frame Size:1280x1024" 154 | config FRAMESIZE_UXGA 155 | bool "Frame Size:1600x1200" 156 | endchoice 157 | 158 | menu "Select Shutter" 159 | 160 | choice SHUTTER_SELECT 161 | bool "Select Shutter" 162 | default SHUTTER_ENTER 163 | help 164 | Selecting the shutter method 165 | 166 | config SHUTTER_ENTER 167 | bool "Use Enter key" 168 | config SHUTTER_GPIO 169 | bool "Use GPIO" 170 | config SHUTTER_TCP 171 | bool "Use TCP Socket" 172 | config SHUTTER_UDP 173 | bool "Use UDP Socket" 174 | 175 | endchoice 176 | 177 | config GPIO_INPUT 178 | int "Input GPIO number" 179 | depends on SHUTTER_GPIO 180 | range 4 34 181 | default 15 182 | help 183 | GPIO number (IOxx) to Button Input. 184 | Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to Button. 185 | 186 | choice GPIO_INITIAL 187 | prompt "GPIO Initial state" 188 | depends on SHUTTER_GPIO 189 | default GPIO_PULLDOWN 190 | help 191 | Select initial state of GPIO. 192 | 193 | config GPIO_PULLUP 194 | bool "GPIO_PULLUP" 195 | help 196 | The initial state of GPIO is Pull-UP. 197 | 198 | config GPIO_PULLDOWN 199 | bool "GPIO_PULLDOWN" 200 | help 201 | The initial state of GPIO is Pull-Down. 202 | 203 | endchoice 204 | 205 | 206 | config TCP_PORT 207 | int "TCP Port" 208 | depends on SHUTTER_TCP 209 | range 49152 65535 210 | default 49876 211 | help 212 | Local port TCP server will listen on. 213 | 214 | config UDP_PORT 215 | int "UDP Port" 216 | depends on SHUTTER_UDP 217 | range 49152 65535 218 | default 49876 219 | help 220 | Local port UDP server will listen on. 221 | 222 | endmenu 223 | 224 | config ENABLE_FLASH 225 | bool "Enable Flash Light" 226 | help 227 | Enable Flash Light. 228 | 229 | config GPIO_FLASH 230 | int "Flash GPIO number" 231 | depends on ENABLE_FLASH 232 | range 4 34 233 | default 4 234 | help 235 | GPIO number (IOxx) to Button Input. 236 | Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to Button. 237 | 238 | endmenu 239 | -------------------------------------------------------------------------------- /main/camera_pin.h: -------------------------------------------------------------------------------- 1 | // Freenove ESP32-WROVER CAM Board PIN Map 2 | #if CONFIG_BOARD_ESP32_WROVER_FREENOVE 3 | #define CAM_PIN_PWDN -1 //power down is not used 4 | #define CAM_PIN_RESET -1 //software reset will be performed 5 | #define CAM_PIN_XCLK 21 6 | #define CAM_PIN_SIOD 26 7 | #define CAM_PIN_SIOC 27 8 | 9 | #define CAM_PIN_D7 35 // Y9 10 | #define CAM_PIN_D6 34 // Y8 11 | #define CAM_PIN_D5 39 // Y7 12 | #define CAM_PIN_D4 36 // Y6 13 | #define CAM_PIN_D3 19 // Y5 14 | #define CAM_PIN_D2 18 // Y4 15 | #define CAM_PIN_D1 5 // Y3 16 | #define CAM_PIN_D0 4 // Y2 17 | #define CAM_PIN_VSYNC 25 18 | #define CAM_PIN_HREF 23 19 | #define CAM_PIN_PCLK 22 20 | #endif 21 | 22 | // Freenove ESP32S3-WROOM CAM Board PIN Map 23 | #if CONFIG_BOARD_ESP32S3_WROOM_FREENOVE 24 | #define CAM_PIN_PWDN -1 //power down is not used 25 | #define CAM_PIN_RESET -1 //software reset will be performed 26 | #define CAM_PIN_XCLK 15 27 | #define CAM_PIN_SIOD 4 28 | #define CAM_PIN_SIOC 5 29 | 30 | #define CAM_PIN_D7 16 // Y9 31 | #define CAM_PIN_D6 17 // Y8 32 | #define CAM_PIN_D5 18 // Y7 33 | #define CAM_PIN_D4 12 // Y6 34 | #define CAM_PIN_D3 10 // Y5 35 | #define CAM_PIN_D2 8 // Y4 36 | #define CAM_PIN_D1 9 // Y3 37 | #define CAM_PIN_D0 11 // Y2 38 | #define CAM_PIN_VSYNC 6 39 | #define CAM_PIN_HREF 7 40 | #define CAM_PIN_PCLK 13 41 | #endif 42 | 43 | // ESP-EYE PIN Map 44 | #if CONFIG_BOARD_CAMERA_MODEL_ESP_EYE 45 | #define CAM_PIN_PWDN -1 //power down is not used 46 | #define CAM_PIN_RESET -1 //software reset will be performed 47 | #define CAM_PIN_XCLK 4 48 | #define CAM_PIN_SIOD 18 49 | #define CAM_PIN_SIOC 23 50 | 51 | #define CAM_PIN_D7 36 // Y9 52 | #define CAM_PIN_D6 37 // Y8 53 | #define CAM_PIN_D5 38 // Y7 54 | #define CAM_PIN_D4 39 // Y6 55 | #define CAM_PIN_D3 35 // Y5 56 | #define CAM_PIN_D2 14 // Y4 57 | #define CAM_PIN_D1 13 // Y3 58 | #define CAM_PIN_D0 34 // Y2 59 | #define CAM_PIN_VSYNC 5 60 | #define CAM_PIN_HREF 27 61 | #define CAM_PIN_PCLK 25 62 | #endif 63 | 64 | 65 | // AiThinker ESP32Cam PIN Map 66 | #if CONFIG_BOARD_ESP32CAM_AITHINKER 67 | #define CAM_PIN_PWDN 32 68 | #define CAM_PIN_RESET -1 //software reset will be performed 69 | #define CAM_PIN_XCLK 0 70 | #define CAM_PIN_SIOD 26 71 | #define CAM_PIN_SIOC 27 72 | 73 | #define CAM_PIN_D7 35 // Y9 74 | #define CAM_PIN_D6 34 // Y8 75 | #define CAM_PIN_D5 39 // Y7 76 | #define CAM_PIN_D4 36 // Y6 77 | #define CAM_PIN_D3 21 // Y5 78 | #define CAM_PIN_D2 19 // Y4 79 | #define CAM_PIN_D1 18 // Y3 80 | #define CAM_PIN_D0 5 // Y2 81 | #define CAM_PIN_VSYNC 25 82 | #define CAM_PIN_HREF 23 83 | #define CAM_PIN_PCLK 22 84 | #endif 85 | 86 | // TTGO T-Journal ESP32 Camera PIN Map 87 | #if CONFIG_BOARD_CAMERA_MODEL_TTGO_T_JOURNAL 88 | #define CAM_PIN_PWDN 0 89 | #define CAM_PIN_RESET 15 90 | #define CAM_PIN_XCLK 27 91 | #define CAM_PIN_SIOD 25 92 | #define CAM_PIN_SIOC 23 93 | 94 | #define CAM_PIN_D7 19 // Y9 95 | #define CAM_PIN_D6 36 // Y8 96 | #define CAM_PIN_D5 18 // Y7 97 | #define CAM_PIN_D4 39 // Y6 98 | #define CAM_PIN_D3 5 // Y5 99 | #define CAM_PIN_D2 34 // Y4 100 | #define CAM_PIN_D1 35 // Y3 101 | #define CAM_PIN_D0 17 // Y2 102 | #define CAM_PIN_VSYNC 22 103 | #define CAM_PIN_HREF 26 104 | #define CAM_PIN_PCLK 21 105 | #endif 106 | 107 | // UICPAL ESPS3 CAM RE:1.0 108 | #if CONFIG_BOARD_ESPS3_CAM_UICPAL 109 | #define CAM_PIN_PWDN -1 //power down is not used 110 | #define CAM_PIN_RESET -1 //software reset will be performed 111 | #define CAM_PIN_XCLK 10 112 | #define CAM_PIN_SIOD 21 113 | #define CAM_PIN_SIOC 14 114 | 115 | #define CAM_PIN_D7 11 // Y9 116 | #define CAM_PIN_D6 9 // Y8 117 | #define CAM_PIN_D5 8 // Y7 118 | #define CAM_PIN_D4 6 // Y6 119 | #define CAM_PIN_D3 4 // Y5 120 | #define CAM_PIN_D2 2 // Y4 121 | #define CAM_PIN_D1 3 // Y3 122 | #define CAM_PIN_D0 5 // Y2 123 | #define CAM_PIN_VSYNC 13 124 | #define CAM_PIN_HREF 12 125 | #define CAM_PIN_PCLK 7 126 | #endif 127 | -------------------------------------------------------------------------------- /main/cmd.h: -------------------------------------------------------------------------------- 1 | #define MAC_MAX_LEN (18) 2 | #define IP_MAX_LEN (16) 3 | 4 | typedef enum {CMD_TAKE, CMD_HALT, CMD_INFO, CMD_UPLOAD} COMMAND; 5 | 6 | // Shutter trigger request 7 | typedef struct { 8 | uint16_t command; 9 | TaskHandle_t taskHandle; 10 | } CMD_t; 11 | 12 | // Message to HTTP Client 13 | typedef struct { 14 | uint16_t command; 15 | char localFileName[64]; 16 | char remoteFileName[64]; 17 | esp_log_level_t logLevel; 18 | TaskHandle_t taskHandle; 19 | } REQUEST_t; 20 | 21 | #if 0 22 | // Message from HTTP Client 23 | typedef struct { 24 | uint16_t command; 25 | char response[256]; 26 | TaskHandle_t taskHandle; 27 | } RESPONSE_t; 28 | #endif 29 | 30 | // Message to HTTP Server 31 | typedef struct { 32 | char localFileName[64]; 33 | TaskHandle_t taskHandle; 34 | } HTTP_t; 35 | -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | -------------------------------------------------------------------------------- /main/gpio.c: -------------------------------------------------------------------------------- 1 | /* The example of GPIO Input 2 | * 3 | * This sample code is in the public domain. 4 | */ 5 | #include "freertos/FreeRTOS.h" 6 | #include "freertos/task.h" 7 | #include "freertos/queue.h" 8 | #include "driver/gpio.h" 9 | #include "esp_log.h" 10 | #include "cmd.h" 11 | 12 | extern QueueHandle_t xQueueCmd; 13 | 14 | static const char *TAG = "GPIO"; 15 | 16 | void gpio(void *pvParameter) 17 | { 18 | ESP_LOGI(TAG, "Start CONFIG_GPIO_INPUT=%d", CONFIG_GPIO_INPUT); 19 | CMD_t cmdBuf; 20 | cmdBuf.taskHandle = xTaskGetCurrentTaskHandle(); 21 | cmdBuf.command = CMD_TAKE; 22 | 23 | // set the GPIO as a input 24 | gpio_reset_pin(CONFIG_GPIO_INPUT); 25 | gpio_set_direction(CONFIG_GPIO_INPUT, GPIO_MODE_DEF_INPUT); 26 | 27 | #if CONFIG_GPIO_PULLUP 28 | ESP_LOGI(TAG, "GPIO%d is PULL UP", CONFIG_GPIO_INPUT); 29 | int push = 0; 30 | int release = 1; 31 | #endif 32 | #if CONFIG_GPIO_PULLDOWN 33 | ESP_LOGI(TAG, "GPIO%d is PULL DOWN", CONFIG_GPIO_INPUT); 34 | int push = 1; 35 | int release = 0; 36 | #endif 37 | ESP_LOGI(TAG, "push=%d release=%d", push, release); 38 | 39 | while(1) { 40 | int level = gpio_get_level(CONFIG_GPIO_INPUT); 41 | if (level == push) { 42 | ESP_LOGI(TAG, "Push Button"); 43 | while(1) { 44 | level = gpio_get_level(CONFIG_GPIO_INPUT); 45 | if (level == release) break; 46 | vTaskDelay(1); 47 | } 48 | ESP_LOGI(TAG, "Release Button"); 49 | if (xQueueSend(xQueueCmd, &cmdBuf, 10) != pdPASS) { 50 | ESP_LOGE(TAG, "xQueueSend fail"); 51 | } 52 | } 53 | vTaskDelay(1); 54 | } 55 | 56 | /* Never reach */ 57 | vTaskDelete( NULL ); 58 | } 59 | -------------------------------------------------------------------------------- /main/http_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | HTTP Client Example using plain POSIX sockets 3 | 4 | This example code is in the Public Domain (or CC0 licensed, at your option.) 5 | 6 | Unless required by applicable law or agreed to in writing, this 7 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | CONDITIONS OF ANY KIND, either express or implied. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include "freertos/FreeRTOS.h" 15 | #include "freertos/task.h" 16 | #include "esp_system.h" 17 | #include "esp_log.h" 18 | #include "esp_mac.h" // MACSTR 19 | #include "esp_wifi.h" // esp_wifi_get_mac 20 | 21 | #include "lwip/err.h" 22 | #include "lwip/sockets.h" 23 | #include "lwip/sys.h" 24 | #include "lwip/netdb.h" 25 | #include "lwip/dns.h" 26 | 27 | #include "cJSON.h" 28 | #include "cmd.h" 29 | 30 | /* Constants that are configurable in menuconfig */ 31 | #if 0 32 | #define CONFIG_SERVER_IP "192.168.10.43" 33 | #define CONFIG_SERVER_PORT 8080 34 | #endif 35 | 36 | #define WEB_PATH_INFO "/node_information" 37 | #define WEB_PATH_UPLOAD "/upload_multipart" 38 | #define BOUNDARY "X-ESPIDF_MULTIPART" 39 | 40 | extern QueueHandle_t xQueueRequest; 41 | 42 | static const char *TAG = "CLIENT"; 43 | 44 | void http_client(void *pvParameters) 45 | { 46 | ESP_LOGI(TAG, "Start SERVER_IP:%s SERVER_PORT:%d", CONFIG_SERVER_IP, CONFIG_SERVER_PORT); 47 | 48 | // Get the local IP address 49 | esp_netif_ip_info_t ip_info; 50 | ESP_ERROR_CHECK(esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info)); 51 | char ip_str[IP_MAX_LEN]; 52 | snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip)); 53 | 54 | // Get station mac address 55 | uint8_t sta_mac[6] = {0}; 56 | esp_wifi_get_mac(ESP_IF_WIFI_STA, sta_mac); 57 | char mac_str[MAC_MAX_LEN]; 58 | snprintf(mac_str, sizeof(mac_str), MACSTR, MAC2STR(sta_mac)); 59 | 60 | const struct addrinfo hints = { 61 | .ai_family = AF_INET, 62 | .ai_socktype = SOCK_STREAM, 63 | }; 64 | struct addrinfo *info; 65 | struct in_addr *addr; 66 | char service[16]; 67 | snprintf(service, sizeof(service), "%d", CONFIG_SERVER_PORT); 68 | 69 | REQUEST_t requestBuf; 70 | TickType_t startTick, endTick, diffTick; 71 | while(1) { 72 | ESP_LOGD(TAG,"Waitting...."); 73 | xQueueReceive(xQueueRequest, &requestBuf, portMAX_DELAY); 74 | esp_log_level_set(TAG, requestBuf.logLevel); 75 | ESP_LOGI(TAG,"requestBuf.command=%d", requestBuf.command); 76 | if (requestBuf.command == CMD_HALT) break; 77 | 78 | //int err = getaddrinfo(CONFIG_SERVER_IP, CONFIG_SERVER_PORT, &hints, &info); 79 | int err = getaddrinfo(CONFIG_SERVER_IP, service, &hints, &info); 80 | 81 | if(err != 0 || info == NULL) { 82 | ESP_LOGE(TAG, "DNS lookup failed err=%d info=%p", err, info); 83 | //vTaskDelay(1000 / portTICK_PERIOD_MS); 84 | if (requestBuf.taskHandle != NULL) 85 | xTaskNotify(requestBuf.taskHandle, 0x02, eSetValueWithOverwrite); 86 | continue; 87 | } 88 | 89 | /* Code to print the resolved IP. 90 | Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code */ 91 | addr = &((struct sockaddr_in *)info->ai_addr)->sin_addr; 92 | ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr)); 93 | 94 | int sock = socket(info->ai_family, info->ai_socktype, 0); 95 | if (sock < 0) { 96 | ESP_LOGE(TAG, "... Failed to allocate socket."); 97 | freeaddrinfo(info); 98 | //vTaskDelay(1000 / portTICK_PERIOD_MS); 99 | if (requestBuf.taskHandle != NULL) 100 | xTaskNotify(requestBuf.taskHandle, 0x03, eSetValueWithOverwrite); 101 | continue; 102 | } 103 | ESP_LOGI(TAG, "... allocated socket"); 104 | 105 | if(connect(sock, info->ai_addr, info->ai_addrlen) != 0) { 106 | ESP_LOGE(TAG, "... socket connect failed errno=%d", errno); 107 | close(sock); 108 | freeaddrinfo(info); 109 | //vTaskDelay(4000 / portTICK_PERIOD_MS); 110 | if (requestBuf.taskHandle != NULL) 111 | xTaskNotify(requestBuf.taskHandle, 0x04, eSetValueWithOverwrite); 112 | continue; 113 | } 114 | 115 | // I don't use info anymore 116 | ESP_LOGI(TAG, "... connected"); 117 | freeaddrinfo(info); 118 | 119 | 120 | if (requestBuf.command == CMD_INFO) { 121 | char HEADER[512]; 122 | char header[128]; 123 | 124 | sprintf(header, "POST %s HTTP/1.1\r\n", WEB_PATH_INFO); 125 | strcpy(HEADER, header); 126 | sprintf(header, "Host: %s:%d\r\n", CONFIG_SERVER_IP, CONFIG_SERVER_PORT); 127 | strcat(HEADER, header); 128 | sprintf(header, "User-Agent: esp-idf/%d.%d.%d esp32\r\n", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR, ESP_IDF_VERSION_PATCH); 129 | strcat(HEADER, header); 130 | sprintf(header, "Accept: */*\r\n"); 131 | strcat(HEADER, header); 132 | sprintf(header, "Content-Type: application/json\r\n"); 133 | strcat(HEADER, header); 134 | 135 | TickType_t nowTick = xTaskGetTickCount(); 136 | cJSON *root; 137 | root = cJSON_CreateObject(); 138 | cJSON_AddNumberToObject(root, "now", nowTick); 139 | cJSON_AddStringToObject(root, "ip", ip_str); 140 | cJSON_AddStringToObject(root, "mac", mac_str); 141 | 142 | #if CONFIG_BOARD_ESP32_WROVER_FREENOVE 143 | cJSON_AddStringToObject(root, "board", "Freenove ESP32-WROVER CAM Board"); 144 | #elif CONFIG_BOARD_ESP32S3_WROOM_FREENOVE 145 | cJSON_AddStringToObject(root, "board", "Freenove ESP32S3-WROVER CAM Board"); 146 | #elif CONFIG_BOARD_CAMERA_MODEL_ESP_EYE 147 | cJSON_AddStringToObject(root, "board", "Espressif ESP-EYE"); 148 | #elif CONFIG_BOARD_ESP32CAM_AITHINKER 149 | cJSON_AddStringToObject(root, "board", "AiThinker ESP32-CAM"); 150 | #elif CONFIG_BOARD_CAMERA_MODEL_TTGO_T_JOURNAL 151 | cJSON_AddStringToObject(root, "board", "TTGO T-Journal ESP32 Camera"); 152 | #elif CONFIG_BOARD_ESPS3_CAM_UICPAL 153 | cJSON_AddStringToObject(root, "board", "UICPAL ESPS3 CAM RE:1.0"); 154 | #endif 155 | 156 | #if CONFIG_FRAMESIZE_VGA 157 | cJSON_AddStringToObject(root, "frame", "640x480"); 158 | #elif CONFIG_FRAMESIZE_SVGA 159 | cJSON_AddStringToObject(root, "frame", "800x600"); 160 | #elif CONFIG_FRAMESIZE_XGA 161 | cJSON_AddStringToObject(root, "frame", "1024x768"); 162 | #elif CONFIG_FRAMESIZE_HD 163 | cJSON_AddStringToObject(root, "frame", "1280x720"); 164 | #elif CONFIG_FRAMESIZE_SXGA 165 | cJSON_AddStringToObject(root, "frame", "1280x1024"); 166 | #elif CONFIG_FRAMESIZE_UXGA 167 | cJSON_AddStringToObject(root, "frame", "1600x1200"); 168 | #endif 169 | //char *json_string = cJSON_Print(root); 170 | char *json_string = cJSON_PrintUnformatted(root); 171 | cJSON_Delete(root); 172 | 173 | char BODY[512]; 174 | strcpy(BODY, json_string); 175 | cJSON_free(json_string); 176 | 177 | int dataLength = strlen(BODY); 178 | sprintf(header, "Content-Length: %d\r\n\r\n", dataLength); 179 | strcat(HEADER, header); 180 | 181 | ESP_LOGD(TAG, "[%s]", HEADER); 182 | if (write(sock, HEADER, strlen(HEADER)) < 0) { 183 | ESP_LOGE(TAG, "... socket send failed"); 184 | close(sock); 185 | //vTaskDelay(4000 / portTICK_PERIOD_MS); 186 | if (requestBuf.taskHandle != NULL) 187 | xTaskNotify(requestBuf.taskHandle, 0x05, eSetValueWithOverwrite); 188 | continue; 189 | } 190 | ESP_LOGI(TAG, "HEADER socket send success"); 191 | 192 | ESP_LOGD(TAG, "[%s]", BODY); 193 | if (write(sock, BODY, strlen(BODY)) < 0) { 194 | ESP_LOGE(TAG, "... socket send failed"); 195 | close(sock); 196 | //vTaskDelay(4000 / portTICK_PERIOD_MS); 197 | if (requestBuf.taskHandle != NULL) 198 | xTaskNotify(requestBuf.taskHandle, 0x06, eSetValueWithOverwrite); 199 | continue; 200 | } 201 | ESP_LOGI(TAG, "BODY socket send success"); 202 | } 203 | 204 | if (requestBuf.command == CMD_UPLOAD) { 205 | startTick = xTaskGetTickCount(); 206 | ESP_LOGI(TAG,"requestBuf.localFileName=%s", requestBuf.localFileName); 207 | ESP_LOGI(TAG,"requestBuf.remoteFileName=%s", requestBuf.remoteFileName); 208 | struct stat statBuf; 209 | if (stat(requestBuf.localFileName, &statBuf) == 0) { 210 | ESP_LOGI(TAG, "st_size=%"PRIi32, statBuf.st_size); 211 | } else { 212 | ESP_LOGE(TAG, "stat fail"); 213 | if (requestBuf.taskHandle != NULL) 214 | xTaskNotify(requestBuf.taskHandle, 0x01, eSetValueWithOverwrite); 215 | continue; 216 | } 217 | 218 | char HEADER[512]; 219 | char header[128]; 220 | 221 | sprintf(header, "POST %s HTTP/1.1\r\n", WEB_PATH_UPLOAD); 222 | strcpy(HEADER, header); 223 | sprintf(header, "Host: %s:%d\r\n", CONFIG_SERVER_IP, CONFIG_SERVER_PORT); 224 | strcat(HEADER, header); 225 | sprintf(header, "User-Agent: esp-idf/%d.%d.%d esp32\r\n", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR, ESP_IDF_VERSION_PATCH); 226 | strcat(HEADER, header); 227 | sprintf(header, "Accept: */*\r\n"); 228 | strcat(HEADER, header); 229 | sprintf(header, "Content-Type: multipart/form-data; boundary=%s\r\n", BOUNDARY); 230 | strcat(HEADER, header); 231 | 232 | char BODY[512]; 233 | sprintf(header, "--%s\r\n", BOUNDARY); 234 | strcpy(BODY, header); 235 | sprintf(header, "Content-Disposition: form-data; name=\"upfile\"; filename=\"%s\"\r\n", requestBuf.remoteFileName); 236 | strcat(BODY, header); 237 | sprintf(header, "Content-Type: application/octet-stream\r\n\r\n"); 238 | strcat(BODY, header); 239 | 240 | char END[128]; 241 | sprintf(header, "\r\n--%s--\r\n\r\n", BOUNDARY); 242 | strcpy(END, header); 243 | 244 | int dataLength = strlen(BODY) + strlen(END) + statBuf.st_size; 245 | sprintf(header, "Content-Length: %d\r\n\r\n", dataLength); 246 | strcat(HEADER, header); 247 | 248 | ESP_LOGD(TAG, "[%s]", HEADER); 249 | if (write(sock, HEADER, strlen(HEADER)) < 0) { 250 | ESP_LOGE(TAG, "... socket send failed"); 251 | close(sock); 252 | //vTaskDelay(4000 / portTICK_PERIOD_MS); 253 | if (requestBuf.taskHandle != NULL) 254 | xTaskNotify(requestBuf.taskHandle, 0x05, eSetValueWithOverwrite); 255 | continue; 256 | } 257 | ESP_LOGI(TAG, "HEADER socket send success"); 258 | 259 | ESP_LOGD(TAG, "[%s]", BODY); 260 | if (write(sock, BODY, strlen(BODY)) < 0) { 261 | ESP_LOGE(TAG, "... socket send failed"); 262 | close(sock); 263 | //vTaskDelay(4000 / portTICK_PERIOD_MS); 264 | if (requestBuf.taskHandle != NULL) 265 | xTaskNotify(requestBuf.taskHandle, 0x06, eSetValueWithOverwrite); 266 | continue; 267 | } 268 | ESP_LOGI(TAG, "BODY socket send success"); 269 | 270 | FILE* f=fopen(requestBuf.localFileName, "rb"); 271 | uint8_t dataBuffer[128]; 272 | if (f == NULL) { 273 | ESP_LOGE(TAG, "Failed to open file for reading"); 274 | break; 275 | } 276 | while(!feof(f)) { 277 | int len = fread(dataBuffer, 1, sizeof(dataBuffer), f); 278 | if (write(sock, dataBuffer, len) < 0) { 279 | ESP_LOGE(TAG, "... socket send failed"); 280 | close(sock); 281 | //vTaskDelay(4000 / portTICK_PERIOD_MS); 282 | if (requestBuf.taskHandle != NULL) 283 | xTaskNotify(requestBuf.taskHandle, 0x07, eSetValueWithOverwrite); 284 | continue; 285 | } 286 | } 287 | fclose(f); 288 | ESP_LOGI(TAG, "DATA socket send success"); 289 | 290 | if (write(sock, END, strlen(END)) < 0) { 291 | ESP_LOGE(TAG, "... socket send failed"); 292 | close(sock); 293 | //vTaskDelay(4000 / portTICK_PERIOD_MS); 294 | if (requestBuf.taskHandle != NULL) 295 | xTaskNotify(requestBuf.taskHandle, 0x08, eSetValueWithOverwrite); 296 | continue; 297 | } 298 | ESP_LOGI(TAG, "END socket send success"); 299 | endTick = xTaskGetTickCount(); 300 | diffTick = endTick - startTick; 301 | ESP_LOGI(TAG, "elapsed time[ms]:%"PRIu32,diffTick*portTICK_PERIOD_MS); 302 | } 303 | 304 | struct timeval receiving_timeout; 305 | receiving_timeout.tv_sec = 5; 306 | receiving_timeout.tv_usec = 0; 307 | if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout, sizeof(receiving_timeout)) < 0) { 308 | ESP_LOGE(TAG, "... failed to set socket receiving timeout"); 309 | close(sock); 310 | //vTaskDelay(4000 / portTICK_PERIOD_MS); 311 | if (requestBuf.taskHandle != NULL) 312 | xTaskNotify(requestBuf.taskHandle, 0x09, eSetValueWithOverwrite); 313 | continue; 314 | } 315 | ESP_LOGI(TAG, "... set socket receiving timeout success"); 316 | 317 | /* Read HTTP response */ 318 | int readed; 319 | char recv_buf[64]; 320 | char responseBuf[256]; 321 | int responseLen = 0; 322 | bzero(responseBuf, sizeof(responseBuf)); 323 | do { 324 | bzero(recv_buf, sizeof(recv_buf)); 325 | readed = read(sock, recv_buf, sizeof(recv_buf)-1); 326 | ESP_LOGI(TAG, "readed=%d", readed); 327 | if (readed < 0) { 328 | ESP_LOGE(TAG, "socket read fail. readed=%d", readed); 329 | } else { 330 | if (responseLen + readed < sizeof(responseBuf)-1) { 331 | strcat(responseBuf, recv_buf); 332 | responseLen = responseLen + readed; 333 | } else { 334 | ESP_LOGW(TAG, "responseBuf too small. responseLen=%d readed=%d", responseLen, readed); 335 | ESP_LOGW(TAG, "responseBuf\n[%.*s]", responseLen, responseBuf); 336 | } 337 | } 338 | } while(readed > 0); 339 | 340 | /* send response */ 341 | ESP_LOGI(TAG, "done reading from socket. Last readed=%d responseLen=%d errno=%d.", readed, responseLen, errno); 342 | ESP_LOGI(TAG, "responseBuf\n[%.*s]", responseLen, responseBuf); 343 | uint32_t ulValue = 0x00; 344 | if (strncmp(responseBuf, "HTTP/1.0 200", 12) == 0) { 345 | //xTaskNotify(requestBuf.taskHandle, 0x00, eSetValueWithOverwrite); 346 | } else if (strncmp(responseBuf, "HTTP/1.1 200", 12) == 0) { 347 | //xTaskNotify(requestBuf.taskHandle, 0x00, eSetValueWithOverwrite); 348 | } else { 349 | ESP_LOGW(TAG, "responseBuf\n[%.*s]", responseLen, responseBuf); 350 | ESP_LOGW(TAG, "Server does not return a successful response"); 351 | ulValue = 0x90; 352 | //xTaskNotify(requestBuf.taskHandle, 0x90, eSetValueWithOverwrite); 353 | } 354 | if (requestBuf.taskHandle != NULL) 355 | xTaskNotify(requestBuf.taskHandle, ulValue, eSetValueWithOverwrite); 356 | close(sock); 357 | 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /main/http_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | Simple HTTP Server Example 3 | 4 | This example code is in the Public Domain (or CC0 licensed, at your option.) 5 | 6 | Unless required by applicable law or agreed to in writing, this 7 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | CONDITIONS OF ANY KIND, either express or implied. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "freertos/FreeRTOS.h" 16 | #include "freertos/task.h" 17 | #include "freertos/queue.h" 18 | #include "esp_log.h" 19 | #include "esp_vfs.h" 20 | #include "esp_spiffs.h" 21 | #include "esp_http_server.h" 22 | 23 | #include "cmd.h" 24 | 25 | static const char *TAG = "SERVER"; 26 | 27 | extern QueueHandle_t xQueueCmd; 28 | extern QueueHandle_t xQueueHttp; 29 | 30 | char * localFileName = NULL; 31 | 32 | // Calculate the size after conversion to base64 33 | // http://akabanessa.blog73.fc2.com/blog-entry-83.html 34 | int32_t calcBase64EncodedSize(int origDataSize) 35 | { 36 | // 6bit単位のブロック数(6bit単位で切り上げ) 37 | // Number of blocks in 6-bit units (rounded up in 6-bit units) 38 | int32_t numBlocks6 = ((origDataSize * 8) + 5) / 6; 39 | // 4文字単位のブロック数(4文字単位で切り上げ) 40 | // Number of blocks in units of 4 characters (rounded up in units of 4 characters) 41 | int32_t numBlocks4 = (numBlocks6 + 3) / 4; 42 | // 改行を含まない文字数 43 | // Number of characters without line breaks 44 | int32_t numNetChars = numBlocks4 * 4; 45 | // 76文字ごとの改行(改行は "\r\n" とする)を考慮したサイズ 46 | // Size considering line breaks every 76 characters (line breaks are "\ r \ n") 47 | //return numNetChars + ((numNetChars / 76) * 2); 48 | return numNetChars; 49 | } 50 | 51 | // Convert image to BASE64 52 | esp_err_t Image2Base64(char * filename, size_t fsize, unsigned char * base64_buffer, size_t base64_buffer_len) 53 | { 54 | unsigned char* image_buffer = NULL; 55 | //image_buffer = malloc(fsize + 1); 56 | image_buffer = malloc(fsize); 57 | if (image_buffer == NULL) { 58 | ESP_LOGE(TAG, "malloc fail. image_buffer %d", fsize); 59 | return ESP_FAIL; 60 | } 61 | 62 | FILE * fp; 63 | if((fp=fopen(filename,"rb"))==NULL){ 64 | ESP_LOGE(TAG, "fopen fail. [%s]", filename); 65 | return ESP_FAIL; 66 | }else{ 67 | for (int i=0;i"); 103 | 104 | // Convert from JPEG to BASE64 105 | unsigned char* img_src_buffer = NULL; 106 | size_t img_src_buffer_len = base64Size + 1; 107 | img_src_buffer = malloc(img_src_buffer_len); 108 | if (img_src_buffer == NULL) { 109 | ESP_LOGE(TAG, "malloc fail. img_src_buffer_len %d", img_src_buffer_len); 110 | } else { 111 | esp_err_t ret = Image2Base64(localFileName, st.st_size, img_src_buffer, img_src_buffer_len); 112 | ESP_LOGI(TAG, "Image2Base64=%d", ret); 113 | if (ret != 0) { 114 | ESP_LOGE(TAG, "Error in mbedtls encode! ret = -0x%x", -ret); 115 | } else { 116 | // 117 | httpd_resp_sendstr_chunk(req, ""); 120 | } 121 | } 122 | if (img_src_buffer != NULL) free(img_src_buffer); 123 | 124 | /* Finish the file list table */ 125 | httpd_resp_sendstr_chunk(req, ""); 126 | 127 | /* Send remaining chunk of HTML file to complete it */ 128 | httpd_resp_sendstr_chunk(req, ""); 129 | 130 | /* Send empty chunk to signal HTTP response completion */ 131 | httpd_resp_sendstr_chunk(req, NULL); 132 | return ESP_OK; 133 | } 134 | 135 | /* Handler for root post */ 136 | // curl -X POST http://192.168.10.157:8080/post 137 | static esp_err_t root_post_handler(httpd_req_t *req) 138 | { 139 | ESP_LOGI(TAG, "root_post_handler req->uri=[%s]", req->uri); 140 | CMD_t cmdBuf; 141 | cmdBuf.taskHandle = xTaskGetCurrentTaskHandle(); 142 | cmdBuf.command = CMD_TAKE; 143 | if (xQueueSendFromISR(xQueueCmd, &cmdBuf, NULL) != pdPASS) { 144 | ESP_LOGE(TAG, "xQueueSendFromISR fail"); 145 | } 146 | 147 | /* Send response with custom headers and body set as the 148 | * string passed in user context*/ 149 | const char* resp_str = (const char*) req->user_ctx; 150 | httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN); 151 | return ESP_OK; 152 | } 153 | 154 | /* favicon get handler */ 155 | static esp_err_t favicon_get_handler(httpd_req_t *req) 156 | { 157 | ESP_LOGI(TAG, "favicon_get_handler"); 158 | return ESP_OK; 159 | } 160 | 161 | /* Function to start the web server */ 162 | esp_err_t start_server(int port) 163 | { 164 | httpd_handle_t server = NULL; 165 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 166 | 167 | // Purge“"Least Recently Used” connection 168 | config.lru_purge_enable = true; 169 | // TCP Port number for receiving and transmitting HTTP traffic 170 | config.server_port = port; 171 | 172 | // Start the httpd server 173 | ESP_LOGD(TAG, "Starting server on port: '%d'", config.server_port); 174 | if (httpd_start(&server, &config) != ESP_OK) { 175 | ESP_LOGE(TAG, "Failed to start file server!"); 176 | return ESP_FAIL; 177 | } 178 | 179 | // Set URI handlers 180 | httpd_uri_t _root_get = { 181 | .uri = "/", 182 | .method = HTTP_GET, 183 | .handler = root_get_handler, 184 | //.user_ctx = "Hello World!" 185 | }; 186 | httpd_register_uri_handler(server, &_root_get); 187 | 188 | httpd_uri_t _root_post = { 189 | .uri = "/post", 190 | .method = HTTP_POST, 191 | .handler = root_post_handler, 192 | .user_ctx = "{\'result\':\'OK\'}" 193 | }; 194 | httpd_register_uri_handler(server, &_root_post); 195 | 196 | httpd_uri_t _favicon_get_handler = { 197 | .uri = "/favicon.ico", 198 | .method = HTTP_GET, 199 | .handler = favicon_get_handler, 200 | //.user_ctx = "Hello World!" 201 | }; 202 | httpd_register_uri_handler(server, &_favicon_get_handler); 203 | 204 | return ESP_OK; 205 | } 206 | 207 | void http_task(void *pvParameters) 208 | { 209 | char *task_parameter = (char *)pvParameters; 210 | ESP_LOGI(TAG, "Start task_parameter=%s", task_parameter); 211 | char url[64]; 212 | int port = 8080; 213 | sprintf(url, "http://%s:%d", task_parameter, port); 214 | ESP_LOGI(TAG, "Starting HTTP server on %s", url); 215 | ESP_ERROR_CHECK(start_server(port)); 216 | 217 | HTTP_t httpBuf; 218 | while(1) { 219 | // Wait to take a picture 220 | if (xQueueReceive(xQueueHttp, &httpBuf, portMAX_DELAY) == pdTRUE) { 221 | ESP_LOGI(TAG, "httpBuf.localFileName=[%s]", httpBuf.localFileName); 222 | localFileName = httpBuf.localFileName; 223 | ESP_LOGW(TAG, "Open this in your browser %s", url); 224 | } 225 | } 226 | 227 | // Never reach here 228 | ESP_LOGI(TAG, "finish"); 229 | vTaskDelete(NULL); 230 | } 231 | -------------------------------------------------------------------------------- /main/idf_component.yml: -------------------------------------------------------------------------------- 1 | ## IDF Component Manager Manifest File 2 | dependencies: 3 | espressif/esp32-camera: 4 | version: '>=2.0.3' 5 | espressif/mdns: 6 | version: "^1.0.3" 7 | rules: 8 | - if: "idf_version >=5.0" 9 | -------------------------------------------------------------------------------- /main/keyboard.c: -------------------------------------------------------------------------------- 1 | /* The example of Keyboard Input 2 | * 3 | * This sample code is in the public domain. 4 | */ 5 | #include "freertos/FreeRTOS.h" 6 | #include "freertos/task.h" 7 | #include "freertos/queue.h" 8 | #include "esp_log.h" 9 | #include "cmd.h" 10 | 11 | extern QueueHandle_t xQueueCmd; 12 | 13 | static const char *TAG = "KEYBOARD"; 14 | 15 | void keyin(void *pvParameters) 16 | { 17 | ESP_LOGI(TAG, "Start"); 18 | CMD_t cmdBuf; 19 | cmdBuf.taskHandle = xTaskGetCurrentTaskHandle(); 20 | cmdBuf.command = CMD_TAKE; 21 | 22 | uint16_t c; 23 | while (1) { 24 | c = fgetc(stdin); 25 | if (c == 0xffff) { 26 | vTaskDelay(10); 27 | continue; 28 | } 29 | //ESP_LOGI(TAG, "c=%x", c); 30 | if (c == 0x0a) { 31 | ESP_LOGI(TAG, "Push Enter"); 32 | if (xQueueSend(xQueueCmd, &cmdBuf, 10) != pdPASS) { 33 | ESP_LOGE(TAG, "xQueueSend fail"); 34 | } 35 | } 36 | } 37 | 38 | /* Never reach */ 39 | vTaskDelete( NULL ); 40 | } 41 | -------------------------------------------------------------------------------- /main/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | Take a picture and Publish it via HTTP Post. 3 | 4 | This code is in the Public Domain (or CC0 licensed, at your option.) 5 | 6 | Unless required by applicable law or agreed to in writing, this 7 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | CONDITIONS OF ANY KIND, either express or implied. 9 | 10 | I ported from here: 11 | https://github.com/espressif/esp32-camera/blob/master/examples/take_picture.c 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "freertos/FreeRTOS.h" 20 | #include "freertos/task.h" 21 | #include "freertos/queue.h" 22 | #include "freertos/event_groups.h" 23 | #include "freertos/timers.h" 24 | #include "esp_system.h" 25 | #include "esp_wifi.h" 26 | #include "esp_event.h" 27 | #include "esp_log.h" 28 | #include "nvs_flash.h" 29 | #include "esp_vfs.h" 30 | #include "esp_spiffs.h" 31 | #include "esp_sntp.h" 32 | #include "mdns.h" 33 | #include "lwip/dns.h" 34 | #include "driver/gpio.h" 35 | 36 | #include "esp_camera.h" 37 | #include "camera_pin.h" 38 | 39 | #include "cmd.h" 40 | 41 | /* FreeRTOS event group to signal when we are connected*/ 42 | static EventGroupHandle_t s_wifi_event_group; 43 | 44 | /* The event group allows multiple bits for each event, but we only care about one event */ 45 | /* - are we connected to the AP with an IP? */ 46 | #define WIFI_CONNECTED_BIT BIT0 47 | #define WIFI_FAIL_BIT BIT1 48 | 49 | static const char *TAG = "MAIN"; 50 | 51 | static int s_retry_num = 0; 52 | 53 | QueueHandle_t xQueueCmd; 54 | QueueHandle_t xQueueHttp; 55 | QueueHandle_t xQueueRequest; 56 | 57 | //static camera_config_t camera_config = { 58 | camera_config_t camera_config = { 59 | .pin_pwdn = CAM_PIN_PWDN, 60 | .pin_reset = CAM_PIN_RESET, 61 | .pin_xclk = CAM_PIN_XCLK, 62 | .pin_sscb_sda = CAM_PIN_SIOD, 63 | .pin_sscb_scl = CAM_PIN_SIOC, 64 | 65 | .pin_d7 = CAM_PIN_D7, 66 | .pin_d6 = CAM_PIN_D6, 67 | .pin_d5 = CAM_PIN_D5, 68 | .pin_d4 = CAM_PIN_D4, 69 | .pin_d3 = CAM_PIN_D3, 70 | .pin_d2 = CAM_PIN_D2, 71 | .pin_d1 = CAM_PIN_D1, 72 | .pin_d0 = CAM_PIN_D0, 73 | .pin_vsync = CAM_PIN_VSYNC, 74 | .pin_href = CAM_PIN_HREF, 75 | .pin_pclk = CAM_PIN_PCLK, 76 | 77 | //XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental) 78 | .xclk_freq_hz = 20000000, 79 | .ledc_timer = LEDC_TIMER_0, 80 | .ledc_channel = LEDC_CHANNEL_0, 81 | 82 | .pixel_format = PIXFORMAT_JPEG, //YUV422,GRAYSCALE,RGB565,JPEG 83 | .frame_size = FRAMESIZE_VGA, //QQVGA-UXGA Do not use sizes above QVGA when not JPEG 84 | 85 | .jpeg_quality = 12, //0-63 lower number means higher quality 86 | .fb_count = 1 //if more than one, i2s runs in continuous mode. Use only with JPEG 87 | }; 88 | 89 | static esp_err_t init_camera(int framesize) 90 | { 91 | //initialize the camera 92 | camera_config.frame_size = framesize; 93 | esp_err_t err = esp_camera_init(&camera_config); 94 | if (err != ESP_OK) 95 | { 96 | ESP_LOGE(TAG, "Camera Init Failed"); 97 | return err; 98 | } 99 | 100 | return ESP_OK; 101 | } 102 | 103 | static esp_err_t camera_capture(char * FileName, size_t *pictureSize) 104 | { 105 | //clear internal queue 106 | //for(int i=0;i<2;i++) { 107 | for(int i=0;i<1;i++) { 108 | camera_fb_t * fb = esp_camera_fb_get(); 109 | ESP_LOGI(TAG, "fb->len=%d", fb->len); 110 | esp_camera_fb_return(fb); 111 | } 112 | 113 | //acquire a frame 114 | camera_fb_t * fb = esp_camera_fb_get(); 115 | if (!fb) { 116 | ESP_LOGE(TAG, "Camera Capture Failed"); 117 | return ESP_FAIL; 118 | } 119 | 120 | //replace this with your own function 121 | //process_image(fb->width, fb->height, fb->format, fb->buf, fb->len); 122 | FILE* f = fopen(FileName, "wb"); 123 | if (f == NULL) { 124 | ESP_LOGE(TAG, "Failed to open file for writing"); 125 | return ESP_FAIL; 126 | } 127 | fwrite(fb->buf, fb->len, 1, f); 128 | ESP_LOGI(TAG, "fb->len=%d", fb->len); 129 | *pictureSize = (size_t)fb->len; 130 | fclose(f); 131 | 132 | //return the frame buffer back to the driver for reuse 133 | esp_camera_fb_return(fb); 134 | return ESP_OK; 135 | } 136 | 137 | static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) 138 | { 139 | if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { 140 | esp_wifi_connect(); 141 | } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { 142 | if (s_retry_num < CONFIG_ESP_MAXIMUM_RETRY) { 143 | esp_wifi_connect(); 144 | s_retry_num++; 145 | ESP_LOGI(TAG, "retry to connect to the AP"); 146 | } else { 147 | xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); 148 | } 149 | ESP_LOGI(TAG,"connect to the AP fail"); 150 | } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { 151 | ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; 152 | ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); 153 | s_retry_num = 0; 154 | xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); 155 | } 156 | } 157 | 158 | #if CONFIG_STATIC_IP 159 | static esp_err_t example_set_dns_server(esp_netif_t *netif, uint32_t addr, esp_netif_dns_type_t type) 160 | { 161 | if (addr && (addr != IPADDR_NONE)) { 162 | esp_netif_dns_info_t dns; 163 | dns.ip.u_addr.ip4.addr = addr; 164 | dns.ip.type = IPADDR_TYPE_V4; 165 | ESP_ERROR_CHECK(esp_netif_set_dns_info(netif, type, &dns)); 166 | } 167 | return ESP_OK; 168 | } 169 | #endif 170 | 171 | esp_err_t wifi_init_sta() 172 | { 173 | s_wifi_event_group = xEventGroupCreate(); 174 | 175 | ESP_ERROR_CHECK(esp_netif_init()); 176 | ESP_ERROR_CHECK(esp_event_loop_create_default()); 177 | esp_netif_t *netif = esp_netif_create_default_wifi_sta(); 178 | assert(netif); 179 | 180 | #if CONFIG_STATIC_IP 181 | 182 | ESP_LOGI(TAG, "CONFIG_STATIC_IP_ADDRESS=[%s]",CONFIG_STATIC_IP_ADDRESS); 183 | ESP_LOGI(TAG, "CONFIG_STATIC_GW_ADDRESS=[%s]",CONFIG_STATIC_GW_ADDRESS); 184 | ESP_LOGI(TAG, "CONFIG_STATIC_NM_ADDRESS=[%s]",CONFIG_STATIC_NM_ADDRESS); 185 | 186 | /* Stop DHCP client */ 187 | ESP_ERROR_CHECK(esp_netif_dhcpc_stop(netif)); 188 | ESP_LOGI(TAG, "Stop DHCP Services"); 189 | 190 | /* Set STATIC IP Address */ 191 | esp_netif_ip_info_t ip_info; 192 | memset(&ip_info, 0 , sizeof(esp_netif_ip_info_t)); 193 | ip_info.ip.addr = ipaddr_addr(CONFIG_STATIC_IP_ADDRESS); 194 | ip_info.netmask.addr = ipaddr_addr(CONFIG_STATIC_NM_ADDRESS); 195 | ip_info.gw.addr = ipaddr_addr(CONFIG_STATIC_GW_ADDRESS);; 196 | ESP_ERROR_CHECK(esp_netif_set_ip_info(netif, &ip_info)); 197 | 198 | /* Set DNS Server */ 199 | ESP_ERROR_CHECK(example_set_dns_server(netif, ipaddr_addr("8.8.8.8"), ESP_NETIF_DNS_MAIN)); 200 | ESP_ERROR_CHECK(example_set_dns_server(netif, ipaddr_addr("8.8.4.4"), ESP_NETIF_DNS_BACKUP)); 201 | 202 | #endif // CONFIG_STATIC_IP 203 | 204 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 205 | ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 206 | 207 | esp_event_handler_instance_t instance_any_id; 208 | esp_event_handler_instance_t instance_got_ip; 209 | ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, 210 | ESP_EVENT_ANY_ID, 211 | &event_handler, 212 | NULL, 213 | &instance_any_id)); 214 | ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, 215 | IP_EVENT_STA_GOT_IP, 216 | &event_handler, 217 | NULL, 218 | &instance_got_ip)); 219 | 220 | wifi_config_t wifi_config = { 221 | .sta = { 222 | .ssid = CONFIG_ESP_WIFI_SSID, 223 | .password = CONFIG_ESP_WIFI_PASSWORD 224 | }, 225 | }; 226 | ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); 227 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); 228 | ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); 229 | ESP_ERROR_CHECK(esp_wifi_start()); 230 | 231 | /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum 232 | * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */ 233 | esp_err_t ret_value = ESP_OK; 234 | EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, 235 | WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, 236 | pdFALSE, 237 | pdFALSE, 238 | portMAX_DELAY); 239 | 240 | /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually 241 | * happened. */ 242 | if (bits & WIFI_CONNECTED_BIT) { 243 | ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD); 244 | } else if (bits & WIFI_FAIL_BIT) { 245 | ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s", CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD); 246 | ret_value = ESP_FAIL; 247 | } else { 248 | ESP_LOGE(TAG, "UNEXPECTED EVENT"); 249 | ret_value = ESP_FAIL; 250 | } 251 | 252 | /* The event will not be processed after unregister */ 253 | ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip)); 254 | ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id)); 255 | vEventGroupDelete(s_wifi_event_group); 256 | return ret_value; 257 | } 258 | 259 | void initialise_mdns(void) 260 | { 261 | //initialize mDNS 262 | ESP_ERROR_CHECK( mdns_init() ); 263 | //set mDNS hostname (required if you want to advertise services) 264 | ESP_ERROR_CHECK( mdns_hostname_set(CONFIG_MDNS_HOSTNAME) ); 265 | ESP_LOGI(TAG, "mdns hostname set to: [%s]", CONFIG_MDNS_HOSTNAME); 266 | 267 | #if 0 268 | //set default mDNS instance name 269 | ESP_ERROR_CHECK( mdns_instance_name_set("ESP32 with mDNS") ); 270 | #endif 271 | } 272 | 273 | esp_err_t mountSPIFFS(char * partition_label, char * base_path) { 274 | ESP_LOGI(TAG, "Initializing SPIFFS file system"); 275 | 276 | esp_vfs_spiffs_conf_t conf = { 277 | .base_path = base_path, 278 | .partition_label = partition_label, 279 | .max_files = 8, 280 | .format_if_mount_failed = true 281 | }; 282 | 283 | // Use settings defined above to initialize and mount SPIFFS filesystem. 284 | // Note: esp_vfs_spiffs_register is an all-in-one convenience function. 285 | esp_err_t ret = esp_vfs_spiffs_register(&conf); 286 | 287 | if (ret != ESP_OK) { 288 | if (ret == ESP_FAIL) { 289 | ESP_LOGE(TAG, "Failed to mount or format filesystem"); 290 | } else if (ret == ESP_ERR_NOT_FOUND) { 291 | ESP_LOGE(TAG, "Failed to find SPIFFS partition"); 292 | } else { 293 | ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); 294 | } 295 | return ret; 296 | } 297 | 298 | size_t total = 0, used = 0; 299 | ret = esp_spiffs_info(partition_label, &total, &used); 300 | if (ret != ESP_OK) { 301 | ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret)); 302 | } else { 303 | ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used); 304 | } 305 | ESP_LOGI(TAG, "Mount SPIFFS filesystem"); 306 | return ret; 307 | } 308 | 309 | 310 | #if CONFIG_REMOTE_IS_VARIABLE_NAME 311 | void time_sync_notification_cb(struct timeval *tv) 312 | { 313 | ESP_LOGI(TAG, "Notification of a time synchronization event"); 314 | } 315 | 316 | static void initialize_sntp(void) 317 | { 318 | ESP_LOGI(TAG, "Initializing SNTP"); 319 | esp_sntp_setoperatingmode(SNTP_OPMODE_POLL); 320 | //esp_sntp_setservername(0, "pool.ntp.org"); 321 | ESP_LOGI(TAG, "Your NTP Server is %s", CONFIG_NTP_SERVER); 322 | esp_sntp_setservername(0, CONFIG_NTP_SERVER); 323 | sntp_set_time_sync_notification_cb(time_sync_notification_cb); 324 | esp_sntp_init(); 325 | } 326 | 327 | static esp_err_t obtain_time(void) 328 | { 329 | initialize_sntp(); 330 | // wait for time to be set 331 | int retry = 0; 332 | const int retry_count = 10; 333 | while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retry_count) { 334 | ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count); 335 | vTaskDelay(2000 / portTICK_PERIOD_MS); 336 | } 337 | 338 | if (retry == retry_count) return ESP_FAIL; 339 | return ESP_OK; 340 | } 341 | #endif 342 | 343 | static void post_system_info_timercb(TimerHandle_t timer) 344 | { 345 | REQUEST_t requestBuf; 346 | requestBuf.command = CMD_INFO; 347 | requestBuf.logLevel = ESP_LOG_WARN; 348 | //requestBuf.logLevel = ESP_LOG_INFO; 349 | requestBuf.taskHandle = NULL; 350 | // HTTP POST 351 | if (xQueueSendFromISR(xQueueRequest, &requestBuf, NULL) != pdPASS) { 352 | ESP_LOGE(TAG, "xQueueSendFromISR fail"); 353 | } 354 | } 355 | 356 | void http_client(void *pvParameters); 357 | 358 | #if CONFIG_SHUTTER_ENTER 359 | void keyin(void *pvParameters); 360 | #endif 361 | 362 | #if CONFIG_SHUTTER_GPIO 363 | void gpio(void *pvParameters); 364 | #endif 365 | 366 | #if CONFIG_SHUTTER_TCP 367 | void tcp_server(void *pvParameters); 368 | #endif 369 | 370 | #if CONFIG_SHUTTER_UDP 371 | void udp_server(void *pvParameters); 372 | #endif 373 | 374 | void http_task(void *pvParameters); 375 | 376 | void app_main(void) 377 | { 378 | // Initialize NVS 379 | esp_err_t ret = nvs_flash_init(); 380 | if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { 381 | ESP_ERROR_CHECK(nvs_flash_erase()); 382 | ret = nvs_flash_init(); 383 | } 384 | ESP_ERROR_CHECK(ret); 385 | 386 | // Initilize WiFi 387 | ESP_ERROR_CHECK(wifi_init_sta()); 388 | 389 | // Initialize mDNS 390 | initialise_mdns(); 391 | 392 | #if CONFIG_REMOTE_IS_VARIABLE_NAME 393 | // obtain time over NTP 394 | ESP_LOGI(TAG, "Connecting to WiFi and getting time over NTP."); 395 | ret = obtain_time(); 396 | if(ret != ESP_OK) { 397 | ESP_LOGE(TAG, "Fail to getting time over NTP."); 398 | return; 399 | } 400 | 401 | // update 'now' variable with current time 402 | time_t now; 403 | struct tm timeinfo; 404 | char strftime_buf[64]; 405 | time(&now); 406 | now = now + (CONFIG_LOCAL_TIMEZONE*60*60); 407 | localtime_r(&now, &timeinfo); 408 | strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo); 409 | ESP_LOGI(TAG, "The current date/time is: %s", strftime_buf); 410 | #endif 411 | 412 | // Initialize SPIFFS 413 | ESP_LOGI(TAG, "Initializing SPIFFS"); 414 | char *partition_label = "storage"; 415 | char *base_path = "/spiffs"; 416 | ret = mountSPIFFS(partition_label, base_path); 417 | if (ret != ESP_OK) return; 418 | 419 | #if CONFIG_ENABLE_FLASH 420 | // Enable Flash Light 421 | //gpio_pad_select_gpio(CONFIG_GPIO_FLASH); 422 | gpio_reset_pin(CONFIG_GPIO_FLASH); 423 | gpio_set_direction(CONFIG_GPIO_FLASH, GPIO_MODE_OUTPUT); 424 | gpio_set_level(CONFIG_GPIO_FLASH, 0); 425 | #endif 426 | 427 | /* Create Queue */ 428 | xQueueCmd = xQueueCreate( 1, sizeof(CMD_t) ); 429 | configASSERT( xQueueCmd ); 430 | xQueueRequest = xQueueCreate( 5, sizeof(REQUEST_t) ); 431 | configASSERT( xQueueRequest ); 432 | xQueueHttp = xQueueCreate( 10, sizeof(HTTP_t) ); 433 | configASSERT( xQueueHttp ); 434 | 435 | /* Create HTTP Client Task */ 436 | xTaskCreate(&http_client, "CLIENT", 1024*4, NULL, 5, NULL); 437 | 438 | /* Create Shutter Task */ 439 | #if CONFIG_SHUTTER_ENTER 440 | #define SHUTTER "Keybord Enter" 441 | xTaskCreate(keyin, "KEYIN", 1024*4, NULL, 2, NULL); 442 | #endif 443 | 444 | #if CONFIG_SHUTTER_GPIO 445 | #define SHUTTER "GPIO Input" 446 | xTaskCreate(gpio, "GPIO", 1024*4, NULL, 2, NULL); 447 | #endif 448 | 449 | #if CONFIG_SHUTTER_TCP 450 | #define SHUTTER "TCP Input" 451 | xTaskCreate(tcp_server, "TCP", 1024*4, NULL, 2, NULL); 452 | #endif 453 | 454 | #if CONFIG_SHUTTER_UDP 455 | #define SHUTTER "UDP Input" 456 | xTaskCreate(udp_server, "UDP", 1024*4, NULL, 2, NULL); 457 | #endif 458 | 459 | /* Get the local IP address */ 460 | esp_netif_ip_info_t ip_info; 461 | ESP_ERROR_CHECK(esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info)); 462 | 463 | /* Create HTTP Task */ 464 | char cparam0[64]; 465 | //sprintf(cparam0, "%s", ip4addr_ntoa(&ip_info.ip)); 466 | sprintf(cparam0, IPSTR, IP2STR(&ip_info.ip)); 467 | ESP_LOGI(TAG, "cparam0=[%s]", cparam0); 468 | xTaskCreate(http_task, "HTTP", 1024*6, (void *)cparam0, 2, NULL); 469 | 470 | #if CONFIG_FRAMESIZE_VGA 471 | int framesize = FRAMESIZE_VGA; 472 | #define FRAMESIZE_STRING "640x480" 473 | #elif CONFIG_FRAMESIZE_SVGA 474 | int framesize = FRAMESIZE_SVGA; 475 | #define FRAMESIZE_STRING "800x600" 476 | #elif CONFIG_FRAMESIZE_XGA 477 | int framesize = FRAMESIZE_XGA; 478 | #define FRAMESIZE_STRING "1024x768" 479 | #elif CONFIG_FRAMESIZE_HD 480 | int framesize = FRAMESIZE_HD; 481 | #define FRAMESIZE_STRING "1280x720" 482 | #elif CONFIG_FRAMESIZE_SXGA 483 | int framesize = FRAMESIZE_SXGA; 484 | #define FRAMESIZE_STRING "1280x1024" 485 | #elif CONFIG_FRAMESIZE_UXGA 486 | int framesize = FRAMESIZE_UXGA; 487 | #define FRAMESIZE_STRING "1600x1200" 488 | #endif 489 | 490 | ret = init_camera(framesize); 491 | if (ret != ESP_OK) { 492 | while(1) { vTaskDelay(1); } 493 | } 494 | 495 | REQUEST_t requestBuf; 496 | requestBuf.command = CMD_UPLOAD; 497 | requestBuf.logLevel = ESP_LOG_INFO; 498 | requestBuf.taskHandle = xTaskGetCurrentTaskHandle(); 499 | //sprintf(requestBuf.localFileName, "%s/picture.jpg", base_path); 500 | snprintf(requestBuf.localFileName, sizeof(requestBuf.localFileName)-1, "%s/picture.jpg", base_path); 501 | ESP_LOGI(TAG, "localFileName=%s",requestBuf.localFileName); 502 | #if CONFIG_REMOTE_IS_FIXED_NAME 503 | #if CONFIG_REMOTE_FRAMESIZE 504 | char baseFileName[32]; 505 | strcpy(baseFileName, CONFIG_FIXED_REMOTE_FILE); 506 | for (int index=0;index 10) { 581 | ESP_LOGE(TAG, "Retry over for capture"); 582 | break; 583 | } 584 | vTaskDelay(1000); 585 | } 586 | } // end while 587 | 588 | #if CONFIG_ENABLE_FLASH 589 | // Flash Light OFF 590 | gpio_set_level(CONFIG_GPIO_FLASH, 0); 591 | #endif 592 | 593 | // HTTP POST 594 | if (xQueueSend(xQueueRequest, &requestBuf, 10) != pdPASS) { 595 | ESP_LOGE(TAG, "xQueueSend fail"); 596 | } else { 597 | uint32_t value = ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); 598 | ESP_LOGI(TAG, "ulTaskNotifyTake value=%"PRIx32, value); 599 | } 600 | 601 | // send local file name to http task 602 | if (xQueueSend(xQueueHttp, &httpBuf, 10) != pdPASS) { 603 | ESP_LOGE(TAG, "xQueueSend xQueueHttp fail"); 604 | } 605 | } // end while 606 | 607 | } 608 | -------------------------------------------------------------------------------- /main/tcp_server.c: -------------------------------------------------------------------------------- 1 | /* BSD Socket API Example 2 | 3 | This example code is in the Public Domain (or CC0 licensed, at your option.) 4 | 5 | Unless required by applicable law or agreed to in writing, this 6 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 7 | CONDITIONS OF ANY KIND, either express or implied. 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "freertos/FreeRTOS.h" 14 | #include "freertos/task.h" 15 | #include "esp_log.h" 16 | #include "mdns.h" 17 | 18 | #include "lwip/err.h" 19 | #include "lwip/sockets.h" 20 | #include "lwip/sys.h" 21 | #include "lwip/netdb.h" 22 | 23 | #include "cmd.h" 24 | 25 | extern QueueHandle_t xQueueCmd; 26 | 27 | static const char *TAG = "TCP"; 28 | 29 | void tcp_server(void *pvParameters) 30 | { 31 | ESP_LOGI(TAG, "Start TCP PORT=%d", CONFIG_TCP_PORT); 32 | CMD_t cmdBuf; 33 | cmdBuf.taskHandle = xTaskGetCurrentTaskHandle(); 34 | cmdBuf.command = CMD_TAKE; 35 | 36 | char rx_buffer[128]; 37 | char tx_buffer[128]; 38 | char addr_str[128]; 39 | int addr_family; 40 | int ip_protocol; 41 | 42 | #if 1 43 | struct sockaddr_in dest_addr; 44 | dest_addr.sin_addr.s_addr = htonl(INADDR_ANY); 45 | dest_addr.sin_family = AF_INET; 46 | //dest_addr.sin_port = htons(PORT); 47 | dest_addr.sin_port = htons(CONFIG_TCP_PORT); 48 | addr_family = AF_INET; 49 | ip_protocol = IPPROTO_IP; 50 | inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1); 51 | #else 52 | struct sockaddr_in6 dest_addr; 53 | bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un)); 54 | dest_addr.sin6_family = AF_INET6; 55 | dest_addr.sin6_port = htons(CONFIG_TCP_PORT); 56 | addr_family = AF_INET6; 57 | ip_protocol = IPPROTO_IPV6; 58 | inet6_ntoa_r(dest_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); 59 | #endif 60 | 61 | int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol); 62 | if (listen_sock < 0) { 63 | ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); 64 | return; 65 | } 66 | ESP_LOGI(TAG, "Socket created"); 67 | 68 | int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); 69 | if (err != 0) { 70 | ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); 71 | return; 72 | } 73 | ESP_LOGI(TAG, "Socket bound, port %d", CONFIG_TCP_PORT); 74 | 75 | err = listen(listen_sock, 1); 76 | if (err != 0) { 77 | ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno); 78 | return; 79 | } 80 | ESP_LOGI(TAG, "Socket listening"); 81 | 82 | while (1) { 83 | struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6 84 | //uint addr_len = sizeof(source_addr); 85 | socklen_t addr_len = sizeof(source_addr); 86 | int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len); 87 | if (sock < 0) { 88 | ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno); 89 | break; 90 | } 91 | ESP_LOGI(TAG, "Socket accepted"); 92 | 93 | while (1) { 94 | int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0); 95 | // Error occurred during receiving 96 | if (len < 0) { 97 | ESP_LOGE(TAG, "recv failed: errno %d", errno); 98 | break; 99 | } 100 | // Connection closed by client 101 | else if (len == 0) { 102 | ESP_LOGI(TAG, "Connection closed"); 103 | break; 104 | } 105 | 106 | // Data received 107 | // Get the sender's ip address as string 108 | if (source_addr.sin6_family == PF_INET) { 109 | inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1); 110 | } else if (source_addr.sin6_family == PF_INET6) { 111 | inet6_ntoa_r(source_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); 112 | } 113 | 114 | rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string 115 | ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str); 116 | ESP_LOGI(TAG, "%s", rx_buffer); 117 | if (xQueueSend(xQueueCmd, &cmdBuf, 10) != pdPASS) { 118 | ESP_LOGE(TAG, "xQueueSend fail"); 119 | strcpy(tx_buffer, "FAIL"); 120 | int err = send(sock, tx_buffer, strlen(tx_buffer), 0); 121 | if (err < 0) { 122 | ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno); 123 | break; 124 | } 125 | 126 | } else { 127 | strcpy(tx_buffer, "OK"); 128 | int err = send(sock, tx_buffer, strlen(tx_buffer), 0); 129 | if (err < 0) { 130 | ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno); 131 | break; 132 | } 133 | } 134 | } 135 | 136 | ESP_LOGI(TAG, "Close socket"); 137 | close(sock); 138 | } 139 | 140 | /* Don't reach here. */ 141 | vTaskDelete(NULL); 142 | } 143 | -------------------------------------------------------------------------------- /main/udp_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | UDP Broadcast Receiver Example 3 | 4 | This example code is in the Public Domain (or CC0 licensed, at your option.) 5 | 6 | Unless required by applicable law or agreed to in writing, this 7 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | CONDITIONS OF ANY KIND, either express or implied. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "freertos/FreeRTOS.h" 16 | #include "freertos/task.h" 17 | #include "esp_log.h" 18 | 19 | #include "lwip/err.h" 20 | #include "lwip/sockets.h" 21 | #include "lwip/sys.h" 22 | #include "lwip/netdb.h" 23 | 24 | #include "cmd.h" 25 | 26 | extern QueueHandle_t xQueueCmd; 27 | 28 | static const char *TAG = "UDP"; 29 | 30 | void udp_server(void *pvParameters) 31 | { 32 | ESP_LOGI(TAG, "Start UDP PORT=%d", CONFIG_UDP_PORT); 33 | CMD_t cmdBuf; 34 | cmdBuf.taskHandle = xTaskGetCurrentTaskHandle(); 35 | cmdBuf.command = CMD_TAKE; 36 | 37 | /* set up address to recvfrom */ 38 | struct sockaddr_in addr; 39 | memset(&addr, 0, sizeof(addr)); 40 | addr.sin_family = AF_INET; 41 | //addr.sin_port = htons(UDP_PORT); 42 | addr.sin_port = htons(CONFIG_UDP_PORT); 43 | addr.sin_addr.s_addr = htonl(INADDR_ANY); /* senderInfo message from ANY */ 44 | 45 | /* create the socket */ 46 | int fd; 47 | int ret; 48 | fd = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP ); // Create a UDP socket. 49 | LWIP_ASSERT("fd >= 0", fd >= 0); 50 | 51 | #if 0 52 | /* set option */ 53 | int broadcast=1; 54 | ret = lwip_setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof broadcast); 55 | LWIP_ASSERT("ret >= 0", ret >= 0); 56 | #endif 57 | 58 | /* bind socket */ 59 | ret = lwip_bind(fd, (struct sockaddr *)&addr, sizeof(addr)); 60 | LWIP_ASSERT("ret >= 0", ret >= 0); 61 | 62 | /* senderInfo data */ 63 | char buffer[64]; 64 | struct sockaddr_in senderInfo; 65 | socklen_t senderInfoLen = sizeof(senderInfo); 66 | char senderstr[16]; 67 | while(1) { 68 | memset(buffer, 0, sizeof(buffer)); 69 | //ESP_LOGI(TAG, "senderInfoLen=%d", senderInfoLen); 70 | ret = lwip_recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr*)&senderInfo, &senderInfoLen); 71 | LWIP_ASSERT("ret > 0", ret > 0); 72 | ESP_LOGI(TAG, "lwip_recv ret=%d",ret); 73 | if (ret > 0) { 74 | buffer[ret] = 0; 75 | ESP_LOGI(TAG, "lwip_recv buffer=%s",buffer); 76 | inet_ntop(AF_INET, &senderInfo.sin_addr, senderstr, sizeof(senderstr)); 77 | ESP_LOGI(TAG, "recvfrom : %s, port=%d", senderstr, ntohs(senderInfo.sin_port)); 78 | if (xQueueSend(xQueueCmd, &cmdBuf, 10) != pdPASS) { 79 | ESP_LOGE(TAG, "xQueueSend fail"); 80 | } 81 | 82 | } 83 | } 84 | 85 | /* close socket. Don't reach here. */ 86 | ret = lwip_close(fd); 87 | LWIP_ASSERT("ret == 0", ret == 0); 88 | vTaskDelete( NULL ); 89 | } 90 | -------------------------------------------------------------------------------- /partitions.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap 3 | nvs, data, nvs, 0x9000, 0x6000, 4 | phy_init, data, phy, 0xf000, 0x1000, 5 | #factory, app, factory, 0x10000, 1M, 6 | factory, app, factory, 0x10000, 0x110000, 7 | storage, data, spiffs, , 0xE0000, 8 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # 2 | # Serial flasher config 3 | # 4 | CONFIG_ESPTOOLPY_FLASHFREQ_80M=y 5 | CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y 6 | 7 | # 8 | # ESP System Settings 9 | # 10 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 11 | 12 | # 13 | # Partition Table 14 | # 15 | CONFIG_PARTITION_TABLE_CUSTOM=y 16 | CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" 17 | CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" 18 | 19 | # 20 | # ESP32-specific 21 | # 22 | CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y 23 | CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240 24 | CONFIG_ESP32_SPIRAM_SUPPORT=y 25 | 26 | # 27 | # ESP32S3-specific 28 | # 29 | CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y 30 | CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=240 31 | CONFIG_ESP32S3_SPIRAM_SUPPORT=y 32 | 33 | # 34 | # Camera configuration 35 | # 36 | #CONFIG_OV7670_SUPPORT=y 37 | #CONFIG_OV7725_SUPPORT=y 38 | #CONFIG_NT99141_SUPPORT=y 39 | CONFIG_OV2640_SUPPORT=y 40 | #CONFIG_OV3660_SUPPORT=y 41 | #CONFIG_OV5640_SUPPORT=y 42 | CONFIG_CAMERA_NO_AFFINITY=y 43 | CONFIG_SCCB_HARDWARE_I2C_PORT0=y 44 | -------------------------------------------------------------------------------- /tcp_send.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- encoding: utf-8 -*- 3 | import argparse 4 | import socket 5 | 6 | if __name__=='__main__': 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument('--host', help='tcp host', default="esp32-camera.local") 9 | parser.add_argument('--port', type=int, help='tcp port', default=49876) 10 | args = parser.parse_args() 11 | print("args.host={}".format(args.host)) 12 | print("args.port={}".format(args.port)) 13 | 14 | client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 15 | client.connect((args.host, args.port)) 16 | client.send(b'take picture') 17 | response = client.recv(1024) 18 | client.close() 19 | if (type(response) is bytes): 20 | response=response.decode('utf-8') 21 | print(response) 22 | -------------------------------------------------------------------------------- /udp_send.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*- encoding: utf-8 -*- 3 | # 4 | # Requirement library 5 | # python3 -m pip install -U netifaces 6 | # 7 | import argparse 8 | import socket 9 | import netifaces 10 | 11 | # Get IP address 12 | for iface_name in netifaces.interfaces(): 13 | iface_data = netifaces.ifaddresses(iface_name) 14 | ipList=iface_data.get(netifaces.AF_INET) 15 | #print("ip={}".format(ipList)) 16 | ipDict = ipList[0] 17 | addr = ipDict["addr"] 18 | print("addr={}".format(addr)) 19 | if (addr != "127.0.0.1"): 20 | myIp = addr 21 | 22 | if __name__=='__main__': 23 | parser = argparse.ArgumentParser() 24 | parser.add_argument('--port', type=int, help='tcp port', default=49876) 25 | args = parser.parse_args() 26 | print("args.port={}".format(args.port)) 27 | 28 | print("myIp={}".format(myIp)) 29 | myIpList = myIp.split('.') 30 | print("myIpList={}".format(myIpList)) 31 | 32 | #address = "192.168.10.255" # for Broadcast 33 | address = "{}.{}.{}.255".format(myIpList[0], myIpList[1], myIpList[2]) 34 | print("address={}".format(address)) 35 | 36 | client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 37 | client.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 38 | client.bind(('', args.port)) 39 | client.sendto(b'take picture', (address, args.port)) 40 | client.close() 41 | --------------------------------------------------------------------------------