├── 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 | 
6 | 
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 | 
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 | 
45 |
46 | When you start ESP32, a list of ESP32 will be displayed.
47 | 
48 |
49 | Select ESP32 and then press the Take Picture button.
50 | You can add Exif to JPEG.
51 | 
52 |
53 | ESP32 takes a photo and transmits it to the server.
54 | You can see the photos.
55 | 
56 | 
57 | 
58 | 
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 | 
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 | 
127 | This script works not only on Linux but also on Windows 10.
128 | I used Python 3.9.13 for Windows.
129 | 
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 | 
157 | 
158 |
159 | ### Wifi Setting
160 | Set the information of your access point.
161 | 
162 | You can connect using the mDNS hostname instead of the IP address.
163 | 
164 | You can use static IP.
165 | 
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 | 
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 | 
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 | 
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 | 
187 |
188 |
189 | ### Select Board
190 | 
191 |
192 |
193 | ### Select Frame Size
194 | Large frame sizes take longer to take a picture.
195 | 
196 | 
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 | 
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 | 
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 | 
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 | 
250 | You can use these devices as shutters.
251 | 
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 | 
259 |
260 | ## PSRAM
261 | When you use ESP32S3-WROVER CAM, you need to change the PSRAM type.
262 |
263 | 
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 | 
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 |
51 |
52 |
55 |
56 |
57 |
58 |
59 | Name |
60 | Size |
61 | Date-Time |
62 | MIME |
63 | Download |
64 | View |
65 | View(Rotate) |
66 | Exif |
67 |
68 |
69 |
70 | {% for folder in folders %}
71 |
72 |
73 | {{ folder.name }}
74 | |
75 |
76 | {{ folder.size }}
77 | |
78 |
79 | {{ folder.mime }}
80 | |
81 |
82 | |
83 |
84 | {% endfor %}
85 |
86 | {% for file in files %}
87 |
88 |
89 | {{ file.name }}
90 | |
91 |
92 | {{ file.size }}
93 | |
94 |
95 | {{ file.ctime }}
96 | |
97 |
98 | {{ file.mime }}
99 | |
100 |
101 | Download
102 | |
103 |
104 | {% if file.visible == True %}
105 | View
106 | {% else %}
107 |
108 | {% endif %}
109 | |
110 |
111 | {% if file.visible == True %}
112 | View
113 | {% else %}
114 |
115 | {% endif %}
116 | |
117 |
118 | {{ file.exif }}
119 | |
120 |
121 | {% endfor %}
122 |
123 |
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 |

22 |
23 | {% elif rotate == 90 %}
24 |
25 |

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 |
--------------------------------------------------------------------------------