├── .DS_Store ├── .firebaserc ├── .gitignore ├── README.md ├── custom_model ├── Train Classifier.ipynb ├── activate.sh ├── create_tf_record.py ├── download_model.sh ├── download_trained_model.sh ├── eval_cloudml.sh ├── export_images.py ├── gcs_deploy.sh ├── model │ └── graph.pbtxt ├── object_detection_tutorial.ipynb ├── opencv.py ├── pipeline.config ├── run_cloudml.sh ├── ssd_mobilenet_v1_coco_11_06_2017 │ ├── graph.pbtxt │ ├── model.ckpt.index │ └── model.ckpt.meta ├── tensorboard.sh ├── test │ └── popcorn_test.png ├── tfjs-models │ ├── group1-shard1of6 │ ├── group1-shard2of6 │ ├── group1-shard3of6 │ ├── group1-shard4of6 │ ├── group1-shard5of6 │ ├── group1-shard6of6 │ └── model.json └── training │ ├── cloud.yml │ ├── faster_rcnn_resnet101_coco.config │ └── pascal_label_map.pbtxt ├── database.rules.json ├── edge-server ├── .gitignore ├── BaseClassifier.js ├── CatClassifier.js ├── CatDetector.js ├── CloudIoTCoreGateway.js ├── DeviceListener.js ├── EXAMPLE_BERRY.jpeg ├── EXAMPLE_JAM.jpeg ├── EdgeServer.js ├── ImageClassifier.js ├── Log.js ├── WebInterface.js ├── edge-server-flowchart.md ├── index.html ├── index.js ├── package-lock.json ├── package.json ├── popcorn.jpeg ├── test_1.jpeg ├── test_2.jpeg ├── test_3.jpeg ├── test_model.js └── util │ └── Bag.js ├── esp32-camera-firmware ├── .gitignore ├── .travis.yml ├── .vscode │ ├── extensions.json │ └── settings.json ├── include │ └── README ├── lib │ ├── README │ ├── esp_camera │ │ ├── camera.c │ │ ├── camera_common.h │ │ ├── esp_camera.h │ │ ├── ov2640.c │ │ ├── ov2640.h │ │ ├── ov2640_regs.h │ │ ├── ov2640_settings.h │ │ ├── ov7725.c │ │ ├── ov7725.h │ │ ├── ov7725_regs.h │ │ ├── sccb.c │ │ ├── sccb.h │ │ ├── sensor.c │ │ ├── sensor.h │ │ ├── twi.c │ │ ├── twi.h │ │ ├── xclk.c │ │ └── xclk.h │ ├── esp_http_server │ │ ├── ctrl_sock.c │ │ ├── ctrl_sock.h │ │ ├── esp_http_server.h │ │ ├── esp_httpd_priv.h │ │ ├── http_server.h │ │ ├── httpd_main.c │ │ ├── httpd_parse.c │ │ ├── httpd_sess.c │ │ ├── httpd_txrx.c │ │ ├── httpd_uri.c │ │ └── osal.h │ └── img_converters │ │ ├── esp_jpg_decode.c │ │ ├── esp_jpg_decode.h │ │ ├── img_converters.h │ │ ├── jpge.cpp │ │ ├── jpge.h │ │ ├── to_bmp.c │ │ ├── to_jpg.cpp │ │ ├── yuv.c │ │ └── yuv.h ├── platformio.ini ├── src │ ├── Kconfig.projbuild │ ├── app_main.c │ ├── component.mk │ ├── sdkconfig.h │ └── sdkconfig.h.bak └── test │ └── README ├── firebase.json ├── functions ├── .eslintrc.json ├── index.js ├── package-lock.json └── package.json ├── generate_key_pair.sh ├── public └── index.html ├── register_device.sh ├── set_env_vars.sh └── setup.sh /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/.DS_Store -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "production": "gcloud-iot-edge" 4 | } 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pem 2 | .DS_Store 3 | node_modules 4 | *.log 5 | .firebase 6 | .ipynb_checkpoints 7 | custom_model/**/*.jpg 8 | custom_model/**/*.xml 9 | custom_model/**/*.csv 10 | custom_model/**/*.record 11 | custom_model/**/*.pb 12 | custom_model/**/*.h5 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asset Tracking using Edge Computing and Computer Vision. 2 | 3 | A bunch of ESP32 with camera, a local server (probablye a raspberry pi ) running image classification and object detection using Tensorflow and sending the data to 4 | processed data to Google Cloud. 5 | 6 | [Work in Progress] 7 | 8 | ### Upload firmware with PlatformIO 9 | 10 | Open `esp32-camera-firmware` folder on PlatformIO. Now the firmware have support for two models of esp32 with camera: 11 | 12 | * ESP32 Cam from M5 Stack 13 | * ESP32 Cam from SeedStudio 14 | 15 | Depending on your model, change on the platformio.ini file the `env_default` configuration depending on your board (`m5cam` or `esp32cam`). Also you need to change the Wifi credentials on the `sdkconfig.h` file ( `CONFIG_WIFI_SSID` and `CONFIG_WIFI_PASSWORD`). 16 | 17 | Then click on upload to flash the firmware into the board. 18 | 19 | ### Run server edge node 20 | 21 | The server was written using NodeJS, Tensorflow.js library and the CocoSSD model to detect objects on the image. 22 | 23 | Run the following commands inside the `edge-server` folder to setup the server: 24 | 25 | * Install dependencies: 26 | * `npm install` 27 | * Run server: 28 | * `npm start` 29 | * Open `localhost:3000` to see the UI 30 | 31 | ### Google Cloud Setup 32 | 33 | [Work in Progress] -------------------------------------------------------------------------------- /custom_model/activate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source ~/tensorflow/bin/activate 3 | export TENSORFLOWLIBPATH=/Users/alvaroviebrantz/Documents/Desenvolvimento/Python/models/research 4 | export PYTHONPATH=$PYTHONPATH:$TENSORFLOWLIBPATH:$TENSORFLOWLIBPATH/slim 5 | -------------------------------------------------------------------------------- /custom_model/create_tf_record.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | r"""Convert raw PASCAL dataset to TFRecord for object_detection. 17 | 18 | Example usage: 19 | python create_tf_record.py \ 20 | --data_dir=/home/user/VOCdevkit \ 21 | --output_path=/home/user/pascal.record 22 | """ 23 | from __future__ import absolute_import 24 | from __future__ import division 25 | from __future__ import print_function 26 | 27 | import hashlib 28 | import io 29 | import logging 30 | import os 31 | import glob 32 | import random 33 | 34 | from lxml import etree 35 | import PIL.Image 36 | import tensorflow as tf 37 | 38 | from object_detection.utils import dataset_util 39 | from object_detection.utils import label_map_util 40 | 41 | 42 | flags = tf.app.flags 43 | flags.DEFINE_string('set', 'train', 'Convert training set, validation set or ' 44 | 'merged set.') 45 | flags.DEFINE_string('label_map_path', 'training/pascal_label_map.pbtxt', 46 | 'Path to label map proto') 47 | FLAGS = flags.FLAGS 48 | 49 | SETS = ['train', 'val', 'trainval', 'test'] 50 | 51 | 52 | def dict_to_tf_example(data, full_path, label_map_dict): 53 | 54 | #full_path = os.path.join(data['folder'], data['filename']) 55 | with tf.gfile.GFile(full_path, 'rb') as fid: 56 | encoded_jpg = fid.read() 57 | encoded_jpg_io = io.BytesIO(encoded_jpg) 58 | image = PIL.Image.open(encoded_jpg_io) 59 | 60 | if image.format != 'JPEG': 61 | raise ValueError('Image format not JPEG') 62 | key = hashlib.sha256(encoded_jpg).hexdigest() 63 | 64 | width = int(data['size']['width']) 65 | height = int(data['size']['height']) 66 | 67 | xmin = [] 68 | ymin = [] 69 | xmax = [] 70 | ymax = [] 71 | classes = [] 72 | classes_text = [] 73 | truncated = [] 74 | poses = [] 75 | difficult_obj = [] 76 | if 'object' in data: 77 | for obj in data['object']: 78 | difficult = bool(int(obj['difficult'])) 79 | difficult_obj.append(int(difficult)) 80 | 81 | xmin.append(float(obj['bndbox']['xmin']) / width) 82 | ymin.append(float(obj['bndbox']['ymin']) / height) 83 | xmax.append(float(obj['bndbox']['xmax']) / width) 84 | ymax.append(float(obj['bndbox']['ymax']) / height) 85 | classes_text.append(obj['name'].encode('utf8')) 86 | classes.append(label_map_dict[obj['name']]) 87 | truncated.append(int(obj['truncated'])) 88 | poses.append(obj['pose'].encode('utf8')) 89 | 90 | example = tf.train.Example(features=tf.train.Features(feature={ 91 | 'image/height': dataset_util.int64_feature(height), 92 | 'image/width': dataset_util.int64_feature(width), 93 | 'image/filename': dataset_util.bytes_feature( 94 | data['filename'].encode('utf8')), 95 | 'image/source_id': dataset_util.bytes_feature( 96 | data['filename'].encode('utf8')), 97 | 'image/key/sha256': dataset_util.bytes_feature(key.encode('utf8')), 98 | 'image/encoded': dataset_util.bytes_feature(encoded_jpg), 99 | 'image/format': dataset_util.bytes_feature('jpg'.encode('utf8')), 100 | 'image/object/bbox/xmin': dataset_util.float_list_feature(xmin), 101 | 'image/object/bbox/xmax': dataset_util.float_list_feature(xmax), 102 | 'image/object/bbox/ymin': dataset_util.float_list_feature(ymin), 103 | 'image/object/bbox/ymax': dataset_util.float_list_feature(ymax), 104 | 'image/object/class/text': dataset_util.bytes_list_feature(classes_text), 105 | 'image/object/class/label': dataset_util.int64_list_feature(classes), 106 | 'image/object/difficult': dataset_util.int64_list_feature(difficult_obj), 107 | 'image/object/truncated': dataset_util.int64_list_feature(truncated), 108 | 'image/object/view': dataset_util.bytes_list_feature(poses), 109 | })) 110 | return example 111 | 112 | 113 | def generate_record(examples, annotations_dir, label_map_dict, output_path): 114 | writer = tf.python_io.TFRecordWriter(output_path) 115 | for idx, full_path in enumerate(examples): 116 | if idx % 100 == 0: 117 | logging.info('On image %d of %d', idx, len(examples)) 118 | 119 | filename = os.path.splitext(os.path.basename(full_path))[0] 120 | path = os.path.join(annotations_dir, filename + '.xml') 121 | with tf.gfile.GFile(path, 'r') as fid: 122 | xml_str = fid.read() 123 | xml = etree.fromstring(xml_str) 124 | data = dataset_util.recursive_parse_xml_to_dict(xml)['annotation'] 125 | tf_example = dict_to_tf_example(data, full_path, label_map_dict) 126 | # print(full_path, output_path) 127 | writer.write(tf_example.SerializeToString()) 128 | writer.close() 129 | 130 | 131 | def main(_): 132 | val_output_path = os.path.join('training', 'val.record') 133 | train_output_path = os.path.join('training', 'train.record') 134 | 135 | label_map_dict = label_map_util.get_label_map_dict(FLAGS.label_map_path) 136 | annotations_dir = 'annotations' 137 | 138 | images_path = os.path.join(os.getcwd(), 'images', '*.jpg') 139 | examples = glob.glob(images_path) 140 | 141 | random.shuffle(examples) 142 | 143 | size = len(examples) 144 | train_size = int(0.85 * size) 145 | val_size = size - train_size 146 | train_data = examples[train_size:] 147 | val_data = examples[:val_size] 148 | 149 | print(train_size, val_size) 150 | generate_record(train_data, annotations_dir, 151 | label_map_dict, train_output_path) 152 | generate_record(val_data, annotations_dir, label_map_dict, val_output_path) 153 | 154 | 155 | if __name__ == '__main__': 156 | tf.app.run() 157 | -------------------------------------------------------------------------------- /custom_model/download_model.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export GCS_BUCKET=gcloud-iot-edge-tf-records 3 | 4 | # curl -O http://storage.googleapis.com/download.tensorflow.org/models/object_detection/faster_rcnn_resnet101_coco_11_06_2017.tar.gz 5 | # tar -xvf faster_rcnn_resnet101_coco_11_06_2017.tar.gz 6 | # gsutil cp faster_rcnn_resnet101_coco_11_06_2017/model.ckpt.* gs://${GCS_BUCKET}/data/ 7 | 8 | # curl -O https://raw.githubusercontent.com/tensorflow/models/master/research/object_detection/samples/configs/ssd_mobilenet_v1_0.75_depth_quantized_300x300_pets_sync.config 9 | pushd /tmp 10 | curl -O http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_0.75_depth_300x300_coco14_sync_2018_07_03.tar.gz /tmp/ssd_mobilenet_v1_0.75_depth_300x300_coco14_sync_2018_07_03.tar.gz 11 | tar xzf ssd_mobilenet_v1_0.75_depth_300x300_coco14_sync_2018_07_03.tar.gz 12 | gsutil cp /tmp/ssd_mobilenet_v1_0.75_depth_300x300_coco14_sync_2018_07_03/model.ckpt.* gs://${GCS_BUCKET}/data/ 13 | popd -------------------------------------------------------------------------------- /custom_model/download_trained_model.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export GCS_BUCKET=gcloud-iot-edge-tf-records 3 | 4 | export CONFIG_FILE=gs://${GCS_BUCKET}/data/pipeline.config 5 | export CHECKPOINT_PATH=gs://${GCS_BUCKET}/train/model.ckpt-10000 6 | export OUTPUT_DIR=/tmp/tflite 7 | 8 | export TENSORFLOWLIBPATH=/Users/alvaroviebrantz/Documents/Desenvolvimento/Python/models/research 9 | export PYTHONPATH=$PYTHONPATH:$TENSORFLOWLIBPATH:$TENSORFLOWLIBPATH/slim 10 | 11 | python $TENSORFLOWLIBPATH/object_detection/export_inference_graph.py \ 12 | --pipeline_config_path=$CONFIG_FILE \ 13 | --trained_checkpoint_prefix=$CHECKPOINT_PATH \ 14 | --output_directory=$OUTPUT_DIR \ 15 | --add_postprocessing_op=true -------------------------------------------------------------------------------- /custom_model/eval_cloudml.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export GCS_BUCKET=gcloud-iot-edge-tf-records 3 | export TENSORFLOWLIB=/Users/alvaroviebrantz/Documents/Desenvolvimento/Python/models/research 4 | 5 | gcloud ml-engine jobs submit training `whoami`_object_detection_eval_validation_`date +%s` \ 6 | --job-dir=gs://${GCS_BUCKET}/train \ 7 | --packages ${TENSORFLOWLIB}/dist/object_detection-0.1.tar.gz,${TENSORFLOWLIB}/slim/dist/slim-0.1.tar.gz,/tmp/pycocotools/pycocotools-2.0.tar.gz \ 8 | --module-name object_detection.model_main \ 9 | --runtime-version 1.12 \ 10 | --python-version 3.5 \ 11 | --scale-tier BASIC_GPU \ 12 | --region us-central1 \ 13 | -- \ 14 | --model_dir=gs://${GCS_BUCKET}/train \ 15 | --pipeline_config_path=gs://${GCS_BUCKET}/data/pipeline.config \ 16 | --checkpoint_dir=gs://${GCS_BUCKET}/train 17 | 18 | -------------------------------------------------------------------------------- /custom_model/export_images.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import xmltodict 4 | import json 5 | 6 | from PIL import Image 7 | 8 | # Look for XML files and parses then as if they were Pascal VOC Files 9 | base_dir = "annotations/" 10 | image_dir = "images/" 11 | save_dir = "output/" 12 | 13 | # Extract image samples and save to output dir 14 | 15 | 16 | def extractDataset(dataset): 17 | objs = dataset['object'] 18 | if type(objs) is not list: 19 | objs = [objs] 20 | print("Found {} objects on image '{}'...".format( 21 | len(dataset['object']), dataset['filename'])) 22 | 23 | # Open image and get ready to process 24 | img = Image.open(image_dir + dataset['filename']) 25 | 26 | try: 27 | os.mkdir(save_dir) 28 | except: 29 | pass 30 | 31 | # Run through each item and save cut image to output folder 32 | print(objs) 33 | for item in objs: 34 | # Convert str to integers 35 | label = item['name'] 36 | current_dir = save_dir + label + '/' 37 | # Create output directory 38 | try: 39 | os.mkdir(current_dir) 40 | except: 41 | pass 42 | bndbox = dict([(a, int(b)) for (a, b) in item['bndbox'].items()]) 43 | # Crop image 44 | im = img.crop((bndbox['xmin'], bndbox['ymin'], 45 | bndbox['xmax'], bndbox['ymax'])) 46 | # Save 47 | count = len(os.listdir(current_dir)) 48 | im.save(current_dir + str(count+1) + '.jpg') 49 | 50 | 51 | def main(): 52 | # Finds all XML files on data/ and append to list 53 | pascal_voc_contents = [] 54 | xmls = glob.glob(base_dir + "*.xml") 55 | print("Found {} files in data directory!".format(str(len(xmls)))) 56 | for xml in xmls: 57 | f_handle = open(xml, 'r') 58 | print("Parsing file '{}'...".format(xml)) 59 | pascal_voc_contents.append(xmltodict.parse(f_handle.read())) 60 | 61 | # Process each file individually 62 | for index in pascal_voc_contents: 63 | image_file = image_dir + index['annotation']['filename'] 64 | # If there's a corresponding file in the folder, 65 | # process the images and save to output folder 66 | if os.path.isfile(image_file): 67 | extractDataset(index['annotation']) 68 | else: 69 | print("Image file '{}' not found, skipping file...".format(image_file)) 70 | 71 | 72 | if __name__ == '__main__': 73 | main() 74 | -------------------------------------------------------------------------------- /custom_model/gcs_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export GCS_BUCKET=gcloud-iot-edge-tf-records 3 | 4 | gsutil cp training/train.record gs://${GCS_BUCKET}/data/train.record 5 | gsutil cp training/val.record gs://${GCS_BUCKET}/data/val.record 6 | gsutil cp training/pascal_label_map.pbtxt gs://${GCS_BUCKET}/data/pascal_label_map.pbtxt 7 | #gsutil cp training/faster_rcnn_resnet101_coco.config gs://${GCS_BUCKET}/data/faster_rcnn_resnet101_coco.config 8 | gsutil cp pipeline.config gs://${GCS_BUCKET}/data/pipeline.config -------------------------------------------------------------------------------- /custom_model/model/graph.pbtxt: -------------------------------------------------------------------------------- 1 | item { 2 | id: 1 3 | name: 'berry' 4 | } 5 | 6 | item { 7 | id: 2 8 | name: 'jam' 9 | } 10 | 11 | item { 12 | id: 3 13 | name: 'muffin' 14 | } 15 | 16 | item { 17 | id: 4 18 | name: 'popcorn' 19 | } 20 | 21 | item { 22 | id: 5 23 | name: 'raspberry' 24 | } 25 | -------------------------------------------------------------------------------- /custom_model/opencv.py: -------------------------------------------------------------------------------- 1 | from object_detection.utils import visualization_utils as vis_util 2 | from object_detection.utils import label_map_util 3 | from keras.preprocessing import image 4 | import cv2 5 | from PIL import Image 6 | from matplotlib import pyplot as plt 7 | from io import StringIO 8 | from collections import defaultdict 9 | import zipfile 10 | import tensorflow as tf 11 | import time 12 | import sys 13 | import numpy as np 14 | import os 15 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' 16 | 17 | 18 | #MODEL_NAME = 'ssd_mobilenet_v1_coco_11_06_2017' 19 | OBJECT_DETECTION_MODEL_NAME = 'ssd_mobilenet_v1_coco_11_06_2017' 20 | OBJECT_DETECTION_PATH_TO_CKPT = OBJECT_DETECTION_MODEL_NAME + \ 21 | '/frozen_inference_graph.pb' 22 | OBJECT_DETECTION_PATH_TO_LABELS = os.path.join( 23 | OBJECT_DETECTION_MODEL_NAME, 'graph.pbtxt') 24 | OBJECT_DETECTION_NUM_CLASSES = 90 25 | 26 | MODEL_NAME = 'model' 27 | PATH_TO_CKPT = MODEL_NAME + '/cats_model.pb' 28 | PATH_TO_LABELS = os.path.join(MODEL_NAME, 'graph.pbtxt') 29 | NUM_CLASSES = 5 30 | 31 | 32 | PATH_TO_TEST_IMAGES_DIR = 'test_images' 33 | TEST_IMAGE_PATHS = [os.path.join( 34 | PATH_TO_TEST_IMAGES_DIR, 'image{}.jpg'.format(i)) for i in range(1, 7)] 35 | 36 | # Size, in inches, of the output images. 37 | IMAGE_SIZE = (12, 8) 38 | 39 | 40 | def load_image_into_numpy_array(image): 41 | (im_width, im_height) = image.size 42 | return np.array(image.getdata()).reshape( 43 | (im_height, im_width, 3)).astype(np.uint8) 44 | 45 | 46 | def draw_boxes_with_class(image, boxes, classes, scores): 47 | vis_util.visualize_boxes_and_labels_on_image_array( 48 | image, 49 | np.squeeze(boxes), 50 | np.squeeze(classes).astype(np.int32), 51 | np.squeeze(scores), 52 | category_index, 53 | use_normalized_coordinates=True, 54 | line_thickness=8) 55 | return image 56 | 57 | 58 | def image_resize(image, width=None, height=None, inter=cv2.INTER_AREA): 59 | # initialize the dimensions of the image to be resized and 60 | # grab the image size 61 | dim = None 62 | (h, w) = image.shape[:2] 63 | 64 | # if both the width and height are None, then return the 65 | # original image 66 | if width is None and height is None: 67 | return image 68 | 69 | # check to see if the width is None 70 | if width is None: 71 | # calculate the ratio of the height and construct the 72 | # dimensions 73 | r = height / float(h) 74 | dim = (int(w * r), height) 75 | 76 | # otherwise, the height is None 77 | else: 78 | # calculate the ratio of the width and construct the 79 | # dimensions 80 | r = width / float(w) 81 | dim = (width, int(h * r)) 82 | 83 | # resize the image 84 | resized = cv2.resize(image, dim, interpolation=inter) 85 | 86 | # return the resized image 87 | return resized 88 | 89 | 90 | def classify_image(frame, sess, graph): 91 | img_tensor = image.img_to_array(frame) 92 | # print(frame, img_tensor) 93 | img_tensor = np.expand_dims(img_tensor, axis=0) 94 | img_tensor /= 255. 95 | # print(frame, img_tensor) 96 | image_tensor = graph.get_tensor_by_name('input_4:0') 97 | classes = graph.get_tensor_by_name('dense_12/Softmax:0') 98 | (classes) = sess.run( 99 | [classes], 100 | feed_dict={image_tensor: img_tensor}) 101 | local_classes = np.squeeze(classes) 102 | min_score_thresh = .5 103 | # print('classify image, found ', local_classes) 104 | classes = [] 105 | for i in range(len(local_classes)): 106 | score = local_classes[i] 107 | if score > min_score_thresh: 108 | class_name = cats_category_index[i+1]['name'] 109 | print('found ', i, class_name, score) 110 | classes.append((class_name, score)) 111 | return classes 112 | 113 | 114 | def detect_objects(frame, sess, detection_graph): 115 | # Expand dimensions since the model expects images to have shape: [1, None, None, 3] 116 | image_np_expanded = np.expand_dims(frame, axis=0) 117 | image_tensor = detection_graph.get_tensor_by_name('image_tensor:0') 118 | # Each box represents a part of the image where a particular object was detected. 119 | boxes = detection_graph.get_tensor_by_name('detection_boxes:0') 120 | # Each score represent how level of confidence for each of the objects. 121 | # Score is shown on the result image, together with the class label. 122 | scores = detection_graph.get_tensor_by_name('detection_scores:0') 123 | classes = detection_graph.get_tensor_by_name('detection_classes:0') 124 | num_detections = detection_graph.get_tensor_by_name( 125 | 'num_detections:0') 126 | # Actual detection. 127 | (boxes, scores, classes, num_detections) = sess.run( 128 | [boxes, scores, classes, num_detections], 129 | feed_dict={image_tensor: image_np_expanded}) 130 | 131 | min_score_thresh = .5 132 | im_width = image_np_expanded.shape[2] 133 | im_height = image_np_expanded.shape[1] 134 | local_boxes = np.squeeze(boxes) 135 | local_classes = np.squeeze(classes).astype(np.int32) 136 | local_scores = np.squeeze(scores) 137 | for i in range(local_boxes.shape[0]): 138 | if local_scores[i] > min_score_thresh: 139 | box = tuple(local_boxes[i].tolist()) 140 | class_name = 'N/A' 141 | if local_classes[i] in category_index.keys(): 142 | class_name = category_index[local_classes[i]]['name'] 143 | 144 | if(class_name == 'cat'): 145 | print('found a cat', box) 146 | ymin, xmin, ymax, xmax = box 147 | (xminn, xmaxx, yminn, ymaxx) = (xmin * im_width, 148 | xmax * im_width, 149 | ymin * im_height, 150 | ymax * im_height) 151 | print('found a cat', xminn, xmaxx, yminn, ymaxx) 152 | # crop_img = tf.image.crop_to_bounding_box(frame,int(yminn), int(xminn), int(ymaxx-yminn), int(xmaxx-xminn)) 153 | crop_img = frame[int(yminn):int(ymaxx), int(xminn):int(xmaxx)] 154 | # cv2.imshow("Found a Cat", crop_img) 155 | findings = classify_image(crop_img, classification_sess, 156 | classification_graph) 157 | for item in findings: 158 | cv2.imshow("Found a Cat: " + item[0], crop_img) 159 | 160 | # Visualization of the results of a detection. 161 | # print scores, classes, num_detections 162 | frame = draw_boxes_with_class(frame, boxes, classes, scores) 163 | return frame 164 | 165 | 166 | start_time = time.time() 167 | 168 | label_map = label_map_util.load_labelmap(OBJECT_DETECTION_PATH_TO_LABELS) 169 | categories = label_map_util.convert_label_map_to_categories( 170 | label_map, max_num_classes=OBJECT_DETECTION_NUM_CLASSES, use_display_name=True) 171 | category_index = label_map_util.create_category_index(categories) 172 | 173 | cats_label_map = label_map_util.load_labelmap(PATH_TO_LABELS) 174 | cats_categories = label_map_util.convert_label_map_to_categories( 175 | cats_label_map, max_num_classes=NUM_CLASSES, use_display_name=True) 176 | cats_category_index = label_map_util.create_category_index(cats_categories) 177 | 178 | detection_graph = tf.Graph() 179 | with detection_graph.as_default(): 180 | od_graph_def = tf.GraphDef() 181 | with tf.gfile.GFile(OBJECT_DETECTION_PATH_TO_CKPT, 'rb') as fid: 182 | serialized_graph = fid.read() 183 | od_graph_def.ParseFromString(serialized_graph) 184 | tf.import_graph_def(od_graph_def, name='') 185 | 186 | sess = tf.Session(graph=detection_graph) 187 | 188 | classification_graph = tf.Graph() 189 | with classification_graph.as_default(): 190 | od_graph_def = tf.GraphDef() 191 | with tf.gfile.GFile(PATH_TO_CKPT, 'rb') as fid: 192 | serialized_graph = fid.read() 193 | od_graph_def.ParseFromString(serialized_graph) 194 | tf.import_graph_def(od_graph_def, name='') 195 | 196 | classification_sess = tf.Session(graph=classification_graph) 197 | 198 | 199 | print("--- %s seconds for initialization ---" % (time.time() - start_time)) 200 | 201 | ''' 202 | for image_path in TEST_IMAGE_PATHS: 203 | start_time = time.time() 204 | image = Image.open(image_path) 205 | # the array based representation of the image will be used later in order to prepare the 206 | # result image with boxes and labels on it. 207 | image_np = load_image_into_numpy_array(image) 208 | image_np = detect_objects(image_np, sess, detection_graph) 209 | # plt.figure(figsize=IMAGE_SIZE) 210 | # plt.imgshow(image_np) 211 | print("--- %s seconds for image %s---" % 212 | (time.time() - start_time, image_path)) 213 | cv2.imshow(image_path, image_np) 214 | 215 | cv2.waitKey(0) 216 | cv2.destroyAllWindows() 217 | ''' 218 | 219 | cap = cv2.VideoCapture(0) 220 | 221 | while(True): 222 | # Capture frame-by-frame 223 | ret, frame = cap.read() 224 | proportion = 0.5 225 | small = cv2.resize(frame, (0, 0), fx=proportion, fy=proportion) 226 | # small = image_resize(frame, height=300) 227 | # (h, w) = small.shape[:2] 228 | # y = 0 229 | # x = round((w-300)/2) 230 | # h = 300 231 | # w = 300 232 | # print(h, w, x, y) 233 | # small = small[y:y+h, x:x+w] 234 | # small = img[y:y+h, x:x+w] 235 | 236 | # Our operations on the frame come here 237 | # gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 238 | start_time = time.time() 239 | image_with_box = detect_objects(small, sess, detection_graph) 240 | # print("--- %s seconds for frame ---" % (time.time() - start_time)) 241 | # Display the resulting frame 242 | cv2.imshow('frame', image_with_box) 243 | 244 | if cv2.waitKey(1) & 0xFF == ord('q'): 245 | break 246 | 247 | # When everything done, release the capture 248 | cap.release() 249 | cv2.destroyAllWindows() 250 | -------------------------------------------------------------------------------- /custom_model/pipeline.config: -------------------------------------------------------------------------------- 1 | # SSD with Mobilenet v1 0.75 depth multiplied feature extractor, focal loss and 2 | # quantized training. 3 | # Trained on IIIT-Oxford pets, initialized from COCO detection checkpoint 4 | 5 | # This config is TPU compatible 6 | 7 | model { 8 | ssd { 9 | inplace_batchnorm_update: true 10 | freeze_batchnorm: false 11 | num_classes: 5 12 | box_coder { 13 | faster_rcnn_box_coder { 14 | y_scale: 10.0 15 | x_scale: 10.0 16 | height_scale: 5.0 17 | width_scale: 5.0 18 | } 19 | } 20 | matcher { 21 | argmax_matcher { 22 | matched_threshold: 0.5 23 | unmatched_threshold: 0.5 24 | ignore_thresholds: false 25 | negatives_lower_than_unmatched: true 26 | force_match_for_each_row: true 27 | use_matmul_gather: true 28 | } 29 | } 30 | similarity_calculator { 31 | iou_similarity { 32 | } 33 | } 34 | encode_background_as_zeros: true 35 | anchor_generator { 36 | ssd_anchor_generator { 37 | num_layers: 6 38 | min_scale: 0.2 39 | max_scale: 0.95 40 | aspect_ratios: 1.0 41 | aspect_ratios: 2.0 42 | aspect_ratios: 0.5 43 | aspect_ratios: 3.0 44 | aspect_ratios: 0.3333 45 | } 46 | } 47 | image_resizer { 48 | fixed_shape_resizer { 49 | height: 300 50 | width: 300 51 | } 52 | } 53 | box_predictor { 54 | convolutional_box_predictor { 55 | min_depth: 0 56 | max_depth: 0 57 | num_layers_before_predictor: 0 58 | use_dropout: false 59 | dropout_keep_probability: 0.8 60 | kernel_size: 1 61 | box_code_size: 4 62 | apply_sigmoid_to_scores: false 63 | class_prediction_bias_init: -4.6 64 | conv_hyperparams { 65 | activation: RELU_6, 66 | regularizer { 67 | l2_regularizer { 68 | weight: 0.00004 69 | } 70 | } 71 | initializer { 72 | random_normal_initializer { 73 | stddev: 0.01 74 | mean: 0.0 75 | } 76 | } 77 | batch_norm { 78 | train: true, 79 | scale: true, 80 | center: true, 81 | decay: 0.9, 82 | epsilon: 0.001, 83 | } 84 | } 85 | } 86 | } 87 | feature_extractor { 88 | type: 'ssd_mobilenet_v1' 89 | min_depth: 16 90 | depth_multiplier: 0.75 91 | conv_hyperparams { 92 | activation: RELU_6, 93 | regularizer { 94 | l2_regularizer { 95 | weight: 0.00004 96 | } 97 | } 98 | initializer { 99 | truncated_normal_initializer { 100 | stddev: 0.03 101 | mean: 0.0 102 | } 103 | } 104 | batch_norm { 105 | scale: true, 106 | center: true, 107 | decay: 0.9, 108 | epsilon: 0.001, 109 | } 110 | } 111 | override_base_feature_extractor_hyperparams: true 112 | } 113 | loss { 114 | classification_loss { 115 | weighted_sigmoid_focal { 116 | alpha: 0.75, 117 | gamma: 2.0 118 | } 119 | } 120 | localization_loss { 121 | weighted_smooth_l1 { 122 | delta: 1.0 123 | } 124 | } 125 | classification_weight: 1.0 126 | localization_weight: 1.0 127 | } 128 | normalize_loss_by_num_matches: true 129 | normalize_loc_loss_by_codesize: true 130 | post_processing { 131 | batch_non_max_suppression { 132 | score_threshold: 1e-8 133 | iou_threshold: 0.6 134 | max_detections_per_class: 100 135 | max_total_detections: 100 136 | } 137 | score_converter: SIGMOID 138 | } 139 | } 140 | } 141 | 142 | train_config: { 143 | fine_tune_checkpoint: "gs://gcloud-iot-edge-tf-records/data/model.ckpt" 144 | fine_tune_checkpoint_type: "detection" 145 | load_all_detection_checkpoint_vars: true 146 | batch_size: 128 147 | sync_replicas: true 148 | startup_delay_steps: 0 149 | replicas_to_aggregate: 8 150 | num_steps: 10000 151 | data_augmentation_options { 152 | random_horizontal_flip { 153 | } 154 | } 155 | data_augmentation_options { 156 | ssd_random_crop { 157 | } 158 | } 159 | optimizer { 160 | momentum_optimizer: { 161 | learning_rate: { 162 | cosine_decay_learning_rate { 163 | learning_rate_base: 0.2 164 | total_steps: 10000 165 | warmup_steps: 0 166 | } 167 | } 168 | momentum_optimizer_value: 0.9 169 | } 170 | use_moving_average: false 171 | } 172 | max_number_of_boxes: 100 173 | unpad_groundtruth_tensors: false 174 | } 175 | 176 | train_input_reader: { 177 | tf_record_input_reader { 178 | input_path: "gs://gcloud-iot-edge-tf-records/data/train.record" 179 | } 180 | label_map_path: "gs://gcloud-iot-edge-tf-records/data/pascal_label_map.pbtxt" 181 | } 182 | 183 | eval_config: { 184 | metrics_set: "coco_detection_metrics" 185 | use_moving_averages: false 186 | num_examples: 1100 187 | } 188 | 189 | eval_input_reader: { 190 | tf_record_input_reader { 191 | input_path: "gs://gcloud-iot-edge-tf-records/data/val.record" 192 | } 193 | label_map_path: "gs://gcloud-iot-edge-tf-records/data/pascal_label_map.pbtxt" 194 | shuffle: false 195 | num_readers: 1 196 | } 197 | 198 | graph_rewriter { 199 | quantization { 200 | delay: 1800 201 | activation_bits: 8 202 | weight_bits: 8 203 | } 204 | } -------------------------------------------------------------------------------- /custom_model/run_cloudml.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export GCS_BUCKET=gcloud-iot-edge-tf-records 3 | export TENSORFLOWLIB=/Users/alvaroviebrantz/Documents/Desenvolvimento/Python/models/research 4 | 5 | gcloud ml-engine jobs submit training `whoami`_object_detection_`date +%s` \ 6 | --job-dir=gs://${GCS_BUCKET}/train \ 7 | --packages ${TENSORFLOWLIB}/dist/object_detection-0.1.tar.gz,${TENSORFLOWLIB}/slim/dist/slim-0.1.tar.gz,/tmp/pycocotools/pycocotools-2.0.tar.gz \ 8 | --module-name object_detection.model_tpu_main \ 9 | --runtime-version 1.12 \ 10 | --python-version 3.5 \ 11 | --scale-tier BASIC_TPU \ 12 | --region us-central1 \ 13 | -- \ 14 | --model_dir=gs://${GCS_BUCKET}/train \ 15 | --tpu_zone us-central1 \ 16 | --pipeline_config_path=gs://${GCS_BUCKET}/data/pipeline.config -------------------------------------------------------------------------------- /custom_model/ssd_mobilenet_v1_coco_11_06_2017/graph.pbtxt: -------------------------------------------------------------------------------- 1 | item { 2 | name: "/m/01g317" 3 | id: 1 4 | display_name: "person" 5 | } 6 | item { 7 | name: "/m/0199g" 8 | id: 2 9 | display_name: "bicycle" 10 | } 11 | item { 12 | name: "/m/0k4j" 13 | id: 3 14 | display_name: "car" 15 | } 16 | item { 17 | name: "/m/04_sv" 18 | id: 4 19 | display_name: "motorcycle" 20 | } 21 | item { 22 | name: "/m/05czz6l" 23 | id: 5 24 | display_name: "airplane" 25 | } 26 | item { 27 | name: "/m/01bjv" 28 | id: 6 29 | display_name: "bus" 30 | } 31 | item { 32 | name: "/m/07jdr" 33 | id: 7 34 | display_name: "train" 35 | } 36 | item { 37 | name: "/m/07r04" 38 | id: 8 39 | display_name: "truck" 40 | } 41 | item { 42 | name: "/m/019jd" 43 | id: 9 44 | display_name: "boat" 45 | } 46 | item { 47 | name: "/m/015qff" 48 | id: 10 49 | display_name: "traffic light" 50 | } 51 | item { 52 | name: "/m/01pns0" 53 | id: 11 54 | display_name: "fire hydrant" 55 | } 56 | item { 57 | name: "/m/02pv19" 58 | id: 13 59 | display_name: "stop sign" 60 | } 61 | item { 62 | name: "/m/015qbp" 63 | id: 14 64 | display_name: "parking meter" 65 | } 66 | item { 67 | name: "/m/0cvnqh" 68 | id: 15 69 | display_name: "bench" 70 | } 71 | item { 72 | name: "/m/015p6" 73 | id: 16 74 | display_name: "bird" 75 | } 76 | item { 77 | name: "/m/01yrx" 78 | id: 17 79 | display_name: "cat" 80 | } 81 | item { 82 | name: "/m/0bt9lr" 83 | id: 18 84 | display_name: "dog" 85 | } 86 | item { 87 | name: "/m/03k3r" 88 | id: 19 89 | display_name: "horse" 90 | } 91 | item { 92 | name: "/m/07bgp" 93 | id: 20 94 | display_name: "sheep" 95 | } 96 | item { 97 | name: "/m/01xq0k1" 98 | id: 21 99 | display_name: "cow" 100 | } 101 | item { 102 | name: "/m/0bwd_0j" 103 | id: 22 104 | display_name: "elephant" 105 | } 106 | item { 107 | name: "/m/01dws" 108 | id: 23 109 | display_name: "bear" 110 | } 111 | item { 112 | name: "/m/0898b" 113 | id: 24 114 | display_name: "zebra" 115 | } 116 | item { 117 | name: "/m/03bk1" 118 | id: 25 119 | display_name: "giraffe" 120 | } 121 | item { 122 | name: "/m/01940j" 123 | id: 27 124 | display_name: "backpack" 125 | } 126 | item { 127 | name: "/m/0hnnb" 128 | id: 28 129 | display_name: "umbrella" 130 | } 131 | item { 132 | name: "/m/080hkjn" 133 | id: 31 134 | display_name: "handbag" 135 | } 136 | item { 137 | name: "/m/01rkbr" 138 | id: 32 139 | display_name: "tie" 140 | } 141 | item { 142 | name: "/m/01s55n" 143 | id: 33 144 | display_name: "suitcase" 145 | } 146 | item { 147 | name: "/m/02wmf" 148 | id: 34 149 | display_name: "frisbee" 150 | } 151 | item { 152 | name: "/m/071p9" 153 | id: 35 154 | display_name: "skis" 155 | } 156 | item { 157 | name: "/m/06__v" 158 | id: 36 159 | display_name: "snowboard" 160 | } 161 | item { 162 | name: "/m/018xm" 163 | id: 37 164 | display_name: "sports ball" 165 | } 166 | item { 167 | name: "/m/02zt3" 168 | id: 38 169 | display_name: "kite" 170 | } 171 | item { 172 | name: "/m/03g8mr" 173 | id: 39 174 | display_name: "baseball bat" 175 | } 176 | item { 177 | name: "/m/03grzl" 178 | id: 40 179 | display_name: "baseball glove" 180 | } 181 | item { 182 | name: "/m/06_fw" 183 | id: 41 184 | display_name: "skateboard" 185 | } 186 | item { 187 | name: "/m/019w40" 188 | id: 42 189 | display_name: "surfboard" 190 | } 191 | item { 192 | name: "/m/0dv9c" 193 | id: 43 194 | display_name: "tennis racket" 195 | } 196 | item { 197 | name: "/m/04dr76w" 198 | id: 44 199 | display_name: "bottle" 200 | } 201 | item { 202 | name: "/m/09tvcd" 203 | id: 46 204 | display_name: "wine glass" 205 | } 206 | item { 207 | name: "/m/08gqpm" 208 | id: 47 209 | display_name: "cup" 210 | } 211 | item { 212 | name: "/m/0dt3t" 213 | id: 48 214 | display_name: "fork" 215 | } 216 | item { 217 | name: "/m/04ctx" 218 | id: 49 219 | display_name: "knife" 220 | } 221 | item { 222 | name: "/m/0cmx8" 223 | id: 50 224 | display_name: "spoon" 225 | } 226 | item { 227 | name: "/m/04kkgm" 228 | id: 51 229 | display_name: "bowl" 230 | } 231 | item { 232 | name: "/m/09qck" 233 | id: 52 234 | display_name: "banana" 235 | } 236 | item { 237 | name: "/m/014j1m" 238 | id: 53 239 | display_name: "apple" 240 | } 241 | item { 242 | name: "/m/0l515" 243 | id: 54 244 | display_name: "sandwich" 245 | } 246 | item { 247 | name: "/m/0cyhj_" 248 | id: 55 249 | display_name: "orange" 250 | } 251 | item { 252 | name: "/m/0hkxq" 253 | id: 56 254 | display_name: "broccoli" 255 | } 256 | item { 257 | name: "/m/0fj52s" 258 | id: 57 259 | display_name: "carrot" 260 | } 261 | item { 262 | name: "/m/01b9xk" 263 | id: 58 264 | display_name: "hot dog" 265 | } 266 | item { 267 | name: "/m/0663v" 268 | id: 59 269 | display_name: "pizza" 270 | } 271 | item { 272 | name: "/m/0jy4k" 273 | id: 60 274 | display_name: "donut" 275 | } 276 | item { 277 | name: "/m/0fszt" 278 | id: 61 279 | display_name: "cake" 280 | } 281 | item { 282 | name: "/m/01mzpv" 283 | id: 62 284 | display_name: "chair" 285 | } 286 | item { 287 | name: "/m/02crq1" 288 | id: 63 289 | display_name: "couch" 290 | } 291 | item { 292 | name: "/m/03fp41" 293 | id: 64 294 | display_name: "potted plant" 295 | } 296 | item { 297 | name: "/m/03ssj5" 298 | id: 65 299 | display_name: "bed" 300 | } 301 | item { 302 | name: "/m/04bcr3" 303 | id: 67 304 | display_name: "dining table" 305 | } 306 | item { 307 | name: "/m/09g1w" 308 | id: 70 309 | display_name: "toilet" 310 | } 311 | item { 312 | name: "/m/07c52" 313 | id: 72 314 | display_name: "tv" 315 | } 316 | item { 317 | name: "/m/01c648" 318 | id: 73 319 | display_name: "laptop" 320 | } 321 | item { 322 | name: "/m/020lf" 323 | id: 74 324 | display_name: "mouse" 325 | } 326 | item { 327 | name: "/m/0qjjc" 328 | id: 75 329 | display_name: "remote" 330 | } 331 | item { 332 | name: "/m/01m2v" 333 | id: 76 334 | display_name: "keyboard" 335 | } 336 | item { 337 | name: "/m/050k8" 338 | id: 77 339 | display_name: "cell phone" 340 | } 341 | item { 342 | name: "/m/0fx9l" 343 | id: 78 344 | display_name: "microwave" 345 | } 346 | item { 347 | name: "/m/029bxz" 348 | id: 79 349 | display_name: "oven" 350 | } 351 | item { 352 | name: "/m/01k6s3" 353 | id: 80 354 | display_name: "toaster" 355 | } 356 | item { 357 | name: "/m/0130jx" 358 | id: 81 359 | display_name: "sink" 360 | } 361 | item { 362 | name: "/m/040b_t" 363 | id: 82 364 | display_name: "refrigerator" 365 | } 366 | item { 367 | name: "/m/0bt_c3" 368 | id: 84 369 | display_name: "book" 370 | } 371 | item { 372 | name: "/m/01x3z" 373 | id: 85 374 | display_name: "clock" 375 | } 376 | item { 377 | name: "/m/02s195" 378 | id: 86 379 | display_name: "vase" 380 | } 381 | item { 382 | name: "/m/01lsmm" 383 | id: 87 384 | display_name: "scissors" 385 | } 386 | item { 387 | name: "/m/0kmg4" 388 | id: 88 389 | display_name: "teddy bear" 390 | } 391 | item { 392 | name: "/m/03wvsk" 393 | id: 89 394 | display_name: "hair drier" 395 | } 396 | item { 397 | name: "/m/012xff" 398 | id: 90 399 | display_name: "toothbrush" 400 | } -------------------------------------------------------------------------------- /custom_model/ssd_mobilenet_v1_coco_11_06_2017/model.ckpt.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/custom_model/ssd_mobilenet_v1_coco_11_06_2017/model.ckpt.index -------------------------------------------------------------------------------- /custom_model/ssd_mobilenet_v1_coco_11_06_2017/model.ckpt.meta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/custom_model/ssd_mobilenet_v1_coco_11_06_2017/model.ckpt.meta -------------------------------------------------------------------------------- /custom_model/tensorboard.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export GCS_BUCKET=gcloud-iot-edge-tf-records 3 | 4 | tensorboard --logdir=gs://${GCS_BUCKET}/train 5 | -------------------------------------------------------------------------------- /custom_model/test/popcorn_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/custom_model/test/popcorn_test.png -------------------------------------------------------------------------------- /custom_model/tfjs-models/group1-shard1of6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/custom_model/tfjs-models/group1-shard1of6 -------------------------------------------------------------------------------- /custom_model/tfjs-models/group1-shard2of6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/custom_model/tfjs-models/group1-shard2of6 -------------------------------------------------------------------------------- /custom_model/tfjs-models/group1-shard3of6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/custom_model/tfjs-models/group1-shard3of6 -------------------------------------------------------------------------------- /custom_model/tfjs-models/group1-shard4of6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/custom_model/tfjs-models/group1-shard4of6 -------------------------------------------------------------------------------- /custom_model/tfjs-models/group1-shard5of6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/custom_model/tfjs-models/group1-shard5of6 -------------------------------------------------------------------------------- /custom_model/tfjs-models/group1-shard6of6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/custom_model/tfjs-models/group1-shard6of6 -------------------------------------------------------------------------------- /custom_model/training/cloud.yml: -------------------------------------------------------------------------------- 1 | trainingInput: 2 | runtimeVersion: "1.0" 3 | scaleTier: CUSTOM 4 | masterType: standard_gpu 5 | workerCount: 5 6 | workerType: standard_gpu 7 | parameterServerCount: 3 8 | parameterServerType: standard -------------------------------------------------------------------------------- /custom_model/training/faster_rcnn_resnet101_coco.config: -------------------------------------------------------------------------------- 1 | # Faster R-CNN with Resnet-101 (v1), configuration for MSCOCO Dataset. 2 | # Users should configure the fine_tune_checkpoint field in the train config as 3 | # well as the label_map_path and input_path fields in the train_input_reader and 4 | # eval_input_reader. Search for "gs://gcloud-iot-edge-tf-records/data" to find the fields that 5 | # should be configured. 6 | 7 | model { 8 | faster_rcnn { 9 | num_classes: 5 10 | image_resizer { 11 | keep_aspect_ratio_resizer { 12 | min_dimension: 600 13 | max_dimension: 1024 14 | } 15 | } 16 | feature_extractor { 17 | type: 'faster_rcnn_resnet101' 18 | first_stage_features_stride: 16 19 | } 20 | first_stage_anchor_generator { 21 | grid_anchor_generator { 22 | scales: [0.25, 0.5, 1.0, 2.0] 23 | aspect_ratios: [0.5, 1.0, 2.0] 24 | height_stride: 16 25 | width_stride: 16 26 | } 27 | } 28 | first_stage_box_predictor_conv_hyperparams { 29 | op: CONV 30 | regularizer { 31 | l2_regularizer { 32 | weight: 0.0 33 | } 34 | } 35 | initializer { 36 | truncated_normal_initializer { 37 | stddev: 0.01 38 | } 39 | } 40 | } 41 | first_stage_nms_score_threshold: 0.0 42 | first_stage_nms_iou_threshold: 0.7 43 | first_stage_max_proposals: 300 44 | first_stage_localization_loss_weight: 2.0 45 | first_stage_objectness_loss_weight: 1.0 46 | initial_crop_size: 14 47 | maxpool_kernel_size: 2 48 | maxpool_stride: 2 49 | second_stage_box_predictor { 50 | mask_rcnn_box_predictor { 51 | use_dropout: false 52 | dropout_keep_probability: 1.0 53 | fc_hyperparams { 54 | op: FC 55 | regularizer { 56 | l2_regularizer { 57 | weight: 0.0 58 | } 59 | } 60 | initializer { 61 | variance_scaling_initializer { 62 | factor: 1.0 63 | uniform: true 64 | mode: FAN_AVG 65 | } 66 | } 67 | } 68 | } 69 | } 70 | second_stage_post_processing { 71 | batch_non_max_suppression { 72 | score_threshold: 0.0 73 | iou_threshold: 0.6 74 | max_detections_per_class: 100 75 | max_total_detections: 300 76 | } 77 | score_converter: SOFTMAX 78 | } 79 | second_stage_localization_loss_weight: 2.0 80 | second_stage_classification_loss_weight: 1.0 81 | } 82 | } 83 | 84 | train_config: { 85 | batch_size: 1 86 | optimizer { 87 | momentum_optimizer: { 88 | learning_rate: { 89 | manual_step_learning_rate { 90 | initial_learning_rate: 0.0003 91 | schedule { 92 | step: 900000 93 | learning_rate: .00003 94 | } 95 | schedule { 96 | step: 1200000 97 | learning_rate: .000003 98 | } 99 | } 100 | } 101 | momentum_optimizer_value: 0.9 102 | } 103 | use_moving_average: false 104 | } 105 | gradient_clipping_by_norm: 10.0 106 | fine_tune_checkpoint: "gs://gcloud-iot-edge-tf-records/data/model.ckpt" 107 | from_detection_checkpoint: true 108 | data_augmentation_options { 109 | random_horizontal_flip { 110 | } 111 | } 112 | } 113 | 114 | train_input_reader: { 115 | tf_record_input_reader { 116 | input_path: "gs://gcloud-iot-edge-tf-records/data/train.record" 117 | } 118 | label_map_path: "gs://gcloud-iot-edge-tf-records/data/pascal_label_map.pbtxt" 119 | } 120 | 121 | eval_config: { 122 | num_examples: 8000 123 | # Note: The below line limits the evaluation process to 10 evaluations. 124 | # Remove the below line to evaluate indefinitely. 125 | max_evals: 10 126 | } 127 | 128 | eval_input_reader: { 129 | tf_record_input_reader { 130 | input_path: "gs://gcloud-iot-edge-tf-records/data/val.record" 131 | } 132 | label_map_path: "gs://gcloud-iot-edge-tf-records/data/pascal_label_map.pbtxt" 133 | shuffle: false 134 | num_readers: 1 135 | } -------------------------------------------------------------------------------- /custom_model/training/pascal_label_map.pbtxt: -------------------------------------------------------------------------------- 1 | item { 2 | id: 1 3 | name: 'raspberry' 4 | } 5 | 6 | item { 7 | id: 2 8 | name: 'muffin' 9 | } 10 | 11 | item { 12 | id: 3 13 | name: 'berry' 14 | } 15 | 16 | item { 17 | id: 4 18 | name: 'popcorn' 19 | } 20 | 21 | item { 22 | id: 5 23 | name: 'jam' 24 | } -------------------------------------------------------------------------------- /database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | ".read": "true", 4 | ".write": "true" 5 | } 6 | } -------------------------------------------------------------------------------- /edge-server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log -------------------------------------------------------------------------------- /edge-server/BaseClassifier.js: -------------------------------------------------------------------------------- 1 | const tf = require('@tensorflow/tfjs') 2 | 3 | const fs = require('fs') 4 | const jpeg = require('jpeg-js') 5 | 6 | class BaseClassifier { 7 | 8 | readImage(path) { 9 | const buf = fs.readFileSync(path) 10 | const pixels = jpeg.decode(buf, true) 11 | return pixels 12 | } 13 | 14 | imageByteArray(image, numChannels) { 15 | const pixels = image.data 16 | const numPixels = image.width * image.height 17 | const values = new Int32Array(numPixels * numChannels) 18 | 19 | for (let i = 0; i < numPixels; i++) { 20 | for (let channel = 0; channel < numChannels; ++channel) { 21 | values[i * numChannels + channel] = pixels[i * 4 + channel] 22 | } 23 | } 24 | 25 | return values 26 | } 27 | 28 | imageToInput(image, numChannels) { 29 | const values = this.imageByteArray(image, numChannels) 30 | const outShape = [image.height, image.width, numChannels] 31 | const input = tf.tensor3d(values, outShape, 'int32') 32 | 33 | return input 34 | } 35 | 36 | async classifyFromFile(path) { 37 | const image = this.readImage(path) 38 | return this.classifyFromImage(image) 39 | } 40 | 41 | async classifyFromImage(image) { 42 | throw new Error('Not implemented') 43 | } 44 | } 45 | 46 | module.exports = BaseClassifier 47 | -------------------------------------------------------------------------------- /edge-server/CatClassifier.js: -------------------------------------------------------------------------------- 1 | const tf = require('@tensorflow/tfjs') 2 | 3 | const BaseClassifier = require('./BaseClassifier') 4 | 5 | const NUMBER_OF_CHANNELS = 3 6 | 7 | class CatClassifier extends BaseClassifier { 8 | /* 9 | * @param mode - detect or classify model 10 | */ 11 | constructor({ modelPath, labels }) { 12 | super() 13 | this.modelPath = modelPath 14 | this.labels = labels 15 | } 16 | 17 | async load() { 18 | const model = await tf.loadGraphModel(this.modelPath, { 19 | strict: false, 20 | }) 21 | // model.summary() 22 | this.model = model 23 | } 24 | 25 | async classifyFromImage(image) { 26 | let input = this.imageToInput(image, NUMBER_OF_CHANNELS) 27 | input = tf.image.resizeNearestNeighbor(input, [150, 150]) 28 | input = input.expandDims(0).div(255.0) 29 | return this.classifyFromInput(input) 30 | } 31 | 32 | async classifyFromInput(input) { 33 | const output = await this.model.executeAsync(input) 34 | output.print() 35 | const prediction = output.argMax(1).dataSync() 36 | const labelIndex = prediction[0] 37 | const score = output.dataSync()[labelIndex] 38 | tf.dispose(input) 39 | return { class: this.labels[labelIndex], score } 40 | } 41 | } 42 | 43 | module.exports = CatClassifier 44 | -------------------------------------------------------------------------------- /edge-server/CatDetector.js: -------------------------------------------------------------------------------- 1 | const tf = require('@tensorflow/tfjs') 2 | const jpeg = require('jpeg-js') 3 | const fs = require('fs') 4 | 5 | const BaseClassifier = require('./BaseClassifier') 6 | const CatClassifier = require('./CatClassifier') 7 | const ImageClassifier = require('./ImageClassifier') 8 | 9 | class CatDetector extends BaseClassifier { 10 | 11 | constructor({ modelPath, labels, threshold, trackingTags }) { 12 | super() 13 | const mode = 'detect' 14 | this.classifier = new CatClassifier({ labels, modelPath }) 15 | this.detector = new ImageClassifier({ mode, threshold, trackingTags }) 16 | this.groupClasses = this.detector.groupClasses.bind(this.detector) 17 | } 18 | 19 | async load() { 20 | await this.classifier.load() 21 | await this.detector.load() 22 | } 23 | 24 | saveImage(tensor, path) { 25 | const [batch, height, width, channels] = tensor.shape 26 | //create an Image data var 27 | const buffer = Buffer.alloc(width * height * 4) 28 | //get the tensor values as data 29 | const data = tensor.dataSync() 30 | //map the values to the buffer 31 | let i = 0 32 | for (let y = 0; y < height; y++) { 33 | for (let x = 0; x < width; x++) { 34 | const pos = (y * width + x) * 4 // position in buffer based on x and y 35 | buffer[pos] = data[i] // some R value [0, 255] 36 | buffer[pos + 1] = data[i + 1] // some G value 37 | buffer[pos + 2] = data[i + 2] // some B value 38 | buffer[pos + 3] = 255 // set alpha channel 39 | i += 3 40 | } 41 | } 42 | 43 | const rawImageData = { 44 | data: buffer, 45 | width: width, 46 | height: height 47 | }; 48 | const jpegImageData = jpeg.encode(rawImageData, 50) 49 | fs.writeFile(path, jpegImageData.data, err => { 50 | 51 | }) 52 | } 53 | 54 | 55 | async classifyFromImage(image) { 56 | const input = this.classifier.imageToInput(image, 3) 57 | const objects = await this.detector.classifyFromImage(image) 58 | 59 | //console.log(objects) 60 | 61 | const classifyInput = input 62 | .asType('float32') 63 | .expandDims(0) 64 | 65 | const [height, width, channels] = input.shape 66 | 67 | const promises = objects 68 | .filter(obj => obj.class === 'cat') 69 | .map(async (obj, i) => { 70 | const [x1, y1, w, h] = obj.bbox 71 | const y2 = (y1 + h) / height 72 | const x2 = (x1 + w) / width 73 | const box = [[y1 / height, x1 / width, y2, x2]] 74 | const crop = tf.image.cropAndResize(classifyInput, box, [0], [h, w]) 75 | 76 | const result = await this.classifier.classifyFromInput(crop.div(255.0)) 77 | //console.log(result) 78 | objects.push(result) 79 | this.saveImage(crop, `test_${i}.jpeg`) 80 | }) 81 | await Promise.all(promises) 82 | tf.dispose(input) 83 | return objects 84 | } 85 | } 86 | 87 | module.exports = CatDetector 88 | -------------------------------------------------------------------------------- /edge-server/CloudIoTCoreGateway.js: -------------------------------------------------------------------------------- 1 | const fs = require( 'fs' ) 2 | const jwt = require( 'jsonwebtoken' ) 3 | const mqtt = require( 'async-mqtt' ) 4 | 5 | const createLogger = require( './Log' ) 6 | 7 | const logger = createLogger( 'CloudIoTCoreGateway' ) 8 | 9 | class CloudIoTCoreGateway { 10 | constructor( { projectId, cloudRegion, registryId, gatewayId, privateKeyFile } ) { 11 | this.projectId = projectId 12 | this.cloudRegion = cloudRegion 13 | this.registryId = registryId 14 | this.gatewayId = gatewayId 15 | this.privateKeyFile = privateKeyFile 16 | 17 | this.tokenExpMins = 20 // Token expiration time in minutes 18 | } 19 | 20 | start() { 21 | this.connect() 22 | this.connectionTicker = setInterval( this.checkConnection.bind( this ), 10000 ) 23 | } 24 | 25 | stop() { 26 | logger.info( 'Closing...' ) 27 | if ( this.client ) { 28 | this.client.end() 29 | } 30 | if ( this.connectionTicker ) { 31 | clearInterval( this.connectionTicker ) 32 | } 33 | logger.info( 'Done' ) 34 | } 35 | 36 | connect() { 37 | if ( this.client ) { 38 | this.client.end() 39 | } 40 | 41 | // Cloud iot core requires a specific client id 42 | const clientId = `projects/${this.projectId}/locations/${ 43 | this.cloudRegion 44 | }/registries/${this.registryId}/devices/${this.gatewayId}` 45 | 46 | const connectionArgs = { 47 | host : 'mqtt.googleapis.com', 48 | port : 8883, 49 | clientId, 50 | username : 'unused', 51 | password : this.createJwt(), 52 | protocol : 'mqtts', 53 | secureProtocol : 'TLSv1_2_method', 54 | } 55 | 56 | // Create a client, and connect to the Google MQTT bridge 57 | this.iatTime = parseInt( Date.now() / 1000 ) 58 | this.client = mqtt.connect( connectionArgs ) 59 | this.client.on( 'connect', ( success ) => { 60 | if ( success ) { 61 | logger.info( 'Client connected.' ) 62 | } else { 63 | logger.info( 'Client not connected.' ) 64 | } 65 | } ) 66 | } 67 | 68 | checkConnection() { 69 | const secsFromIssue = parseInt( Date.now() / 1000 ) - this.iatTime 70 | if ( secsFromIssue > this.tokenExpMins * 60 ) { 71 | logger.info( `Refreshing token after ${secsFromIssue} seconds.` ) 72 | this.connect() 73 | } 74 | } 75 | 76 | /* 77 | * Create a Cloud IoT Core JWT for the given project id, signed with the given 78 | * private key. 79 | */ 80 | createJwt() { 81 | const algorithm = 'ES256' 82 | const token = { 83 | iat : parseInt( Date.now() / 1000 ), 84 | exp : parseInt( Date.now() / 1000 ) + this.tokenExpMins * 60, 85 | aud : this.projectId, 86 | } 87 | const privateKey = fs.readFileSync( this.privateKeyFile ) 88 | return jwt.sign( token, privateKey, { algorithm } ) 89 | } 90 | 91 | async publish( deviceId, payload, eventType ) { 92 | let finalPayload = payload 93 | if ( payload instanceof Object ) { 94 | finalPayload = JSON.stringify( payload ) 95 | } 96 | 97 | const mqttTopic = `/devices/${deviceId}/${eventType}` 98 | return this.client.publish( mqttTopic, finalPayload, { qos : 0 } ) 99 | } 100 | 101 | attachDevice( deviceId ) { 102 | return this.publish( deviceId, {}, 'attach' ) 103 | } 104 | 105 | detachDevice( deviceId ) { 106 | return this.publish( deviceId, {}, 'detach' ) 107 | } 108 | 109 | publishDeviceTelemetry( deviceId, payload ) { 110 | return this.publish( deviceId, payload, 'events' ) 111 | } 112 | 113 | publishDeviceState( deviceId, payload ) { 114 | return this.publish( deviceId, payload, 'state' ) 115 | } 116 | 117 | publishGatewayTelemetry( payload ) { 118 | return this.publish( this.gatewayId, payload, 'events' ) 119 | } 120 | 121 | publishGatewayState( payload ) { 122 | return this.publish( this.gatewayId, payload, 'state' ) 123 | } 124 | } 125 | 126 | module.exports = CloudIoTCoreGateway 127 | -------------------------------------------------------------------------------- /edge-server/DeviceListener.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events') 2 | const mdns = require('mdns') 3 | const AbortController = require('abort-controller') 4 | const fetch = require('node-fetch') 5 | const jpeg = require('jpeg-js') 6 | 7 | const createLogger = require('./Log') 8 | 9 | const logger = createLogger('DeviceListener') 10 | 11 | class CameraDevice { 12 | constructor(service) { 13 | this.name = service.name 14 | this.host = service.host 15 | this.addresses = service.addresses 16 | } 17 | 18 | /* 19 | * Using ip address directly is faster 20 | */ 21 | getImageUrl() { 22 | let host = `${this.name}.local` 23 | if (this.addresses && this.addresses.length > 0) { 24 | host = `${this.addresses[0]}` 25 | } 26 | return `http://${host}/jpg` 27 | } 28 | 29 | async fetchImage() { 30 | const controller = new AbortController() 31 | const timeout = setTimeout( 32 | () => { controller.abort() }, 33 | 5000, 34 | ) 35 | 36 | const url = this.getImageUrl() 37 | try { 38 | const res = await fetch(url, { signal: controller.signal }) 39 | const buffer = await res.arrayBuffer() 40 | const image = jpeg.decode(buffer, true) 41 | return image 42 | } catch (e) { 43 | throw new Error('Error Fetching Image') 44 | } finally { 45 | clearTimeout(timeout) 46 | } 47 | } 48 | } 49 | 50 | 51 | class DeviceListener extends EventEmitter { 52 | constructor() { 53 | super() 54 | const sequence = [ 55 | mdns.rst.DNSServiceResolve(), 56 | 'DNSServiceGetAddrInfo' in mdns.dns_sd ? mdns.rst.DNSServiceGetAddrInfo() : mdns.rst.getaddrinfo({ families: [0] }), 57 | mdns.rst.makeAddressesUnique() 58 | ] 59 | this.browser = mdns.createBrowser(mdns.tcp('camera'), { resolverSequence: sequence }) 60 | this.devices = {} 61 | } 62 | 63 | start() { 64 | this.browser.start() 65 | this.browser.on('serviceUp', (service) => { 66 | const { name } = service 67 | logger.info(`service up: ${name}`) 68 | this.devices[name] = new CameraDevice(service) 69 | this.emit('deviceAdded', name) 70 | }) 71 | this.browser.on('serviceDown', (service) => { 72 | const { name } = service 73 | logger.info(`service down: ${name}`) 74 | delete this.devices[name] 75 | this.emit('deviceRemoved', name) 76 | }) 77 | } 78 | 79 | onDeviceAdded(callback) { 80 | return this.on('deviceAdded', callback) 81 | } 82 | 83 | onDeviceRemoved(callback) { 84 | return this.on('deviceRemoved', callback) 85 | } 86 | 87 | stop() { 88 | logger.info('Closing...') 89 | this.browser.stop() 90 | this.removeAllListeners() 91 | logger.info('Done') 92 | } 93 | 94 | getDevices() { 95 | return Object.values(this.devices) 96 | } 97 | 98 | } 99 | 100 | module.exports = DeviceListener 101 | -------------------------------------------------------------------------------- /edge-server/EXAMPLE_BERRY.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/edge-server/EXAMPLE_BERRY.jpeg -------------------------------------------------------------------------------- /edge-server/EXAMPLE_JAM.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/edge-server/EXAMPLE_JAM.jpeg -------------------------------------------------------------------------------- /edge-server/EdgeServer.js: -------------------------------------------------------------------------------- 1 | const { RateLimiter } = require('limiter') 2 | 3 | const DeviceListener = require('./DeviceListener') 4 | const ImageClassifier = require('./ImageClassifier') 5 | const CatDetector = require('./CatDetector') 6 | const CloudIoTCoreGateway = require('./CloudIoTCoreGateway') 7 | const WebInterface = require('./WebInterface') 8 | 9 | const createLogger = require('./Log') 10 | 11 | const logger = createLogger('EdgeServer') 12 | 13 | class EdgeServer { 14 | constructor(config) { 15 | const { classifier, web, gateway } = config 16 | 17 | this.deviceListener = new DeviceListener() 18 | if (classifier.mode === 'cats') { 19 | this.classifier = new CatDetector(classifier) 20 | } else { 21 | this.classifier = new ImageClassifier(classifier) 22 | } 23 | this.gateway = new CloudIoTCoreGateway(gateway) 24 | this.web = new WebInterface(web) 25 | this.limiter = new RateLimiter(10, 'minute') 26 | 27 | this.deviceQueue = {} 28 | this.lastDeviceData = {} 29 | } 30 | 31 | async start() { 32 | this.stopped = false 33 | await this.classifier.load() 34 | this.deviceListener.start() 35 | this.web.start() 36 | await this.gateway.start() 37 | 38 | this.deviceListener.onDeviceAdded(async (deviceId) => { 39 | await this.gateway.attachDevice(deviceId) 40 | this.gateway.publishDeviceState(deviceId, { status: 'online', type: 'camera' }) 41 | }) 42 | 43 | this.deviceListener.onDeviceRemoved(async (deviceId) => { 44 | this.gateway.publishDeviceState(deviceId, { status: 'offline' }) 45 | this.gateway.detachDevice(deviceId) 46 | }) 47 | 48 | this.run() 49 | // this.ticker = setInterval( this.run.bind( this ), 10000 ) 50 | 51 | const serverInfo = this.web.getServerInfo() 52 | await this.gateway.publishGatewayState({ status: 'online', server: serverInfo, type: 'gateway' }) 53 | } 54 | 55 | hasChanges() { 56 | const devices = Object.keys(this.deviceQueue) 57 | if (devices.length !== Object.keys(this.lastDeviceData).length) { 58 | return true 59 | } 60 | const hasChanged = devices.some((deviceId) => { 61 | return JSON.stringify(this.deviceQueue[deviceId]) !== JSON.stringify(this.lastDeviceData[deviceId]) 62 | }) 63 | return hasChanged 64 | } 65 | 66 | queueData(device, { classes, trackedClasses, countClasses }) { 67 | const { name } = device 68 | let deviceData = this.deviceQueue[name] 69 | if (!deviceData) { 70 | deviceData = { 71 | name, 72 | classes: [], 73 | trackedClasses: [], 74 | countClasses: {}, 75 | } 76 | this.deviceQueue[name] = deviceData 77 | } 78 | 79 | const classesSet = new Set(deviceData.classes.concat(classes)) 80 | const nClasses = [...classesSet] 81 | 82 | const trackedClassesSet = new Set(deviceData.trackedClasses.concat(trackedClasses)) 83 | const nTrackedClasses = [...trackedClassesSet] 84 | 85 | const nCountClasses = { 86 | ...deviceData.countClasses, 87 | ...countClasses 88 | } 89 | Object.keys(nCountClasses).forEach((key) => { 90 | nCountClasses[key] = Math.max(deviceData.countClasses[key], countClasses[key]) || 1 91 | }) 92 | 93 | const nDeviceData = { 94 | name, 95 | classes: nClasses, 96 | trackedClasses: nTrackedClasses, 97 | countClasses: nCountClasses 98 | } 99 | 100 | this.deviceQueue[name] = nDeviceData 101 | } 102 | 103 | clearQueue() { 104 | this.deviceQueue = {} 105 | this.lastDeviceData = {} 106 | } 107 | 108 | async run() { 109 | const devices = this.deviceListener.getDevices() 110 | const findings = {} 111 | const promises = devices.map(async (device) => { 112 | logger.info(`Looking for image from ${device.name}`) 113 | try { 114 | logger.profile(`FetchImage-${device.name}`) 115 | const image = await device.fetchImage() 116 | logger.info(`Fetched image from ${device.name}`) 117 | logger.profile(`FetchImage-${device.name}`) 118 | 119 | logger.profile(`ClassifyImage-${device.name}`) 120 | const predictions = await this.classifier.classifyFromImage(image) 121 | logger.profile(`ClassifyImage-${device.name}`) 122 | 123 | const grouped = this.classifier.groupClasses(predictions) 124 | const { countClasses } = grouped 125 | logger.info(`Found classes ${device.name} - ${JSON.stringify(countClasses)}`) 126 | 127 | this.queueData(device, grouped) 128 | findings[device.name] = { 129 | name: device.name, 130 | ...grouped 131 | } 132 | } catch (e) { 133 | logger.error('Error fetching image from device', device.name, e.message) 134 | findings[device.name] = { 135 | name: device.name 136 | } 137 | } 138 | }) 139 | 140 | // Wait for inference results and filter for valid ones 141 | try { 142 | await Promise.all(promises) 143 | } catch (e) { 144 | logger.error('Error running inference on some devices', e.message) 145 | } 146 | 147 | // Update local web interface 148 | logger.info(`Device queue : ${JSON.stringify(this.deviceQueue)}`) 149 | logger.info(`Last device data : ${JSON.stringify(this.lastDeviceData)}`) 150 | logger.info(`Findings : ${JSON.stringify(findings)}`) 151 | const deviceData = Object.values(findings) 152 | this.web.broadcastData('devices', deviceData) 153 | 154 | // Send data to cloud iot core 155 | try { 156 | if (this.hasChanges()) { 157 | if (this.limiter.tryRemoveTokens(1)) { 158 | logger.info('[PublishData] Sending data to cloud iot core.') 159 | const publishPromises = Object.keys(this.deviceQueue).map((deviceId) => { 160 | const res = this.deviceQueue[deviceId] 161 | this.lastDeviceData[deviceId] = res 162 | return this.gateway.publishDeviceTelemetry(deviceId, { classes: res.countClasses }) 163 | }) 164 | await Promise.all(publishPromises) 165 | this.clearQueue() 166 | } else { 167 | logger.info('[PublishData] Publishing throttled.') 168 | } 169 | } 170 | } catch (err) { 171 | logger.error(`Error sending data to cloud iot core ${err}`, err) 172 | } 173 | 174 | if (!this.stopped) { 175 | setTimeout(this.run.bind(this), 100) 176 | } 177 | } 178 | 179 | async stop() { 180 | logger.info('Closing...') 181 | this.stopped = true 182 | if (this.ticker) { 183 | clearInterval(this.ticker) 184 | } 185 | 186 | const devices = this.deviceListener.getDevices() 187 | 188 | this.deviceListener.stop() 189 | this.web.stop() 190 | 191 | logger.info('Sending offline events') 192 | try { 193 | logger.info('Sending gateway offline event') 194 | await this.gateway.publishGatewayState({ status: 'offline' }) 195 | const publishPromises = devices.map((device) => { 196 | logger.info(`Sending offline event for device ${device.name}`) 197 | return this.gateway.publishDeviceState(device.name, { status: 'offline' }) 198 | }) 199 | await Promise.all(publishPromises) 200 | logger.info('All offline events sent') 201 | await new Promise(resolve => setTimeout(resolve, 2000)) 202 | } catch (err) { 203 | logger.error(`Error sending data to cloud iot core ${err}`, err) 204 | } 205 | 206 | this.gateway.stop() 207 | logger.info('Done') 208 | } 209 | } 210 | 211 | module.exports = EdgeServer 212 | -------------------------------------------------------------------------------- /edge-server/ImageClassifier.js: -------------------------------------------------------------------------------- 1 | const tf = require('@tensorflow/tfjs') 2 | 3 | const mobilenet = require('@tensorflow-models/mobilenet') 4 | const cocossd = require('@tensorflow-models/coco-ssd') 5 | 6 | const fs = require('fs') 7 | const jpeg = require('jpeg-js') 8 | const flatMap = require('flatmap') 9 | 10 | const BaseClassifier = require('./BaseClassifier') 11 | const Bag = require('./util/Bag') 12 | 13 | const NUMBER_OF_CHANNELS = 3 14 | 15 | class ImageClassifier extends BaseClassifier { 16 | /* 17 | * @param mode - detect or classify model 18 | */ 19 | constructor({ mode, trackingTags, threshold }) { 20 | super() 21 | let model = null 22 | if (mode === 'detect') { 23 | model = cocossd 24 | } else if (mode === 'classify') { 25 | model = mobilenet 26 | } else { 27 | throw new Error('Unknow classifier mode') 28 | } 29 | this.unloadedModel = model 30 | this.mode = mode 31 | this.trackingTags = trackingTags 32 | this.threshold = threshold 33 | } 34 | 35 | async load() { 36 | const model = await this.unloadedModel.load() 37 | this.model = model 38 | } 39 | 40 | mapPredictions(predictions) { 41 | return flatMap(predictions, (prediction) => { 42 | if (this.mode === 'detect') { 43 | return prediction 44 | } 45 | // this.mode === 'classify' 46 | const classes = prediction.className.split(', ') 47 | return classes.map(clss => ({ 48 | score: prediction.probability, 49 | class: clss 50 | })) 51 | }) 52 | } 53 | 54 | groupClasses(predictions) { 55 | const trackingSet = new Set(this.trackingTags) 56 | const all = new Bag() 57 | predictions.forEach((prediction) => { 58 | if (prediction.score >= this.threshold) { 59 | all.add(prediction.class) 60 | } 61 | }) 62 | const classes = all.toArray() 63 | const countClasses = all.toObject() 64 | const trackedClasses = classes.filter(x => trackingSet.has(x)) 65 | return { 66 | classes, 67 | trackedClasses, 68 | countClasses, 69 | } 70 | } 71 | 72 | async classifyFromImage(image) { 73 | const input = this.imageToInput(image, NUMBER_OF_CHANNELS) 74 | return this.classifyFromInput(input) 75 | } 76 | 77 | async classifyFromInput(input) { 78 | let predictions 79 | if (this.mode === 'detect') { 80 | predictions = await this.model.detect(input) 81 | } else { 82 | predictions = await this.model.classify(input) 83 | } 84 | tf.dispose(input) 85 | return this.mapPredictions(predictions) 86 | } 87 | } 88 | 89 | module.exports = ImageClassifier 90 | -------------------------------------------------------------------------------- /edge-server/Log.js: -------------------------------------------------------------------------------- 1 | const { createLogger, format, transports } = require( 'winston' ) 2 | 3 | const { combine, timestamp, label, printf } = format 4 | 5 | const appFormat = printf( ( info ) => { 6 | return `${info.timestamp} ${info.level}: [${info.label}] ${info.message}` 7 | } ) 8 | 9 | function create( customLabel ) { 10 | const logger = createLogger( { 11 | format : combine( 12 | label( { label : customLabel } ), 13 | timestamp(), 14 | appFormat 15 | ), 16 | exitOnError : false, 17 | transports : [ 18 | new transports.File( { filename : 'error.log', level : 'error' } ), 19 | new transports.File( { filename : 'combined.log' } ) 20 | ] 21 | } ) 22 | 23 | if ( process.env.NODE_ENV !== 'production' ) { 24 | logger.add( new transports.Console() ) 25 | } 26 | 27 | return logger 28 | } 29 | 30 | module.exports = create 31 | -------------------------------------------------------------------------------- /edge-server/WebInterface.js: -------------------------------------------------------------------------------- 1 | const express = require( 'express' ) 2 | const ip = require( 'ip' ) 3 | const { Server } = require( 'http' ) 4 | const SocketIO = require( 'socket.io' ) 5 | 6 | const createLogger = require( './Log' ) 7 | 8 | const logger = createLogger( 'WebInterface' ) 9 | 10 | class WebInterface { 11 | constructor( { port } ) { 12 | this.app = express() 13 | this.server = Server( this.app ) 14 | this.io = SocketIO( this.server ) 15 | 16 | this.port = port || 3000 17 | 18 | this.app.get( '/', this.indexHandler.bind( this ) ) 19 | 20 | } 21 | 22 | start() { 23 | this.server.listen( this.port ) 24 | } 25 | 26 | stop() { 27 | logger.info( 'Closing...' ) 28 | this.server.close( () => { 29 | logger.info( 'Done' ) 30 | } ) 31 | } 32 | 33 | getServerInfo() { 34 | return { 35 | port : this.port, 36 | ip : ip.address(), 37 | } 38 | } 39 | 40 | indexHandler( req, res ) { 41 | res.sendFile( `${__dirname}/index.html` ) 42 | } 43 | 44 | broadcastData( channel, data ) { 45 | this.io.emit( channel, data ) 46 | } 47 | } 48 | 49 | module.exports = WebInterface 50 | -------------------------------------------------------------------------------- /edge-server/edge-server-flowchart.md: -------------------------------------------------------------------------------- 1 | graph TD 2 | A[Edge Server] --> B(Device Listener) 3 | A[Edge Server] --> C(Image Classifier) 4 | A[Edge Server] --> D(CloudIoTCoreGateway) 5 | A[Edge Server] --> E(WebInterface) 6 | B --> |Search via mDns| F[fa:fa-camera Cameras] 7 | C --> |Classify with Tensorflow| G[Queue Data] 8 | D --> |Send Queue Data| H[Google Cloud] 9 | E --> |Web Socket| Z(Socket.io) 10 | E --> X(Web) 11 | -------------------------------------------------------------------------------- /edge-server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 53 | IoT Edge Server Monitor 54 | 55 | 56 | 57 |
58 | 59 | 60 | IoT Edge - Local Monitor 61 | 62 | 63 | 65 | 66 |
67 | 68 | 69 |
{{device.name}}
70 |
{{device.updatedString}}
71 |
72 | 73 | 74 | camera 75 | 76 | 77 | 78 | 79 | {{c}}({{device.countClasses[c]}}) 80 | 81 | 82 | Nothing found. 83 | 84 | 85 |
86 |
87 |
88 |
89 |
90 | 91 | 92 | 93 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /edge-server/index.js: -------------------------------------------------------------------------------- 1 | require('@tensorflow/tfjs-node') 2 | // global.fetch = require( 'node-fetch' ) 3 | 4 | const EdgeServer = require('./EdgeServer') 5 | 6 | const config = { 7 | classifier: { 8 | trackingTags: ['cats', 'person', 'dogs'], 9 | threshold: 0.6, 10 | // mode: 'detect', 11 | mode: 'cats', 12 | labels: { 0: 'berry', 1: 'jam', 2: 'muffin', 3: 'popcorn', 4: 'raspberry' }, 13 | modelPath: 'file://../custom_model/tfjs-models/model.json' 14 | }, 15 | web: { 16 | port: 3000 17 | }, 18 | gateway: { 19 | projectId: 'gcloud-iot-edge', 20 | cloudRegion: 'us-central1', 21 | registryId: 'iot-edge-registry', 22 | gatewayId: 'gw-mark-one', 23 | privateKeyFile: '../ec_private.pem' 24 | } 25 | } 26 | 27 | const server = new EdgeServer(config) 28 | server.start() 29 | 30 | async function gracefulShutdown() { 31 | try { 32 | console.info('SIGTERM signal received.') 33 | console.info('Shutting down server.') 34 | await Promise.race([ 35 | server.stop(), 36 | new Promise((resolve, reject) => { 37 | setTimeout(() => { 38 | reject() 39 | }, 5000) 40 | }) 41 | ]) 42 | process.exit(0) 43 | } catch (err) { 44 | console.error('Forced shutdown', err) 45 | process.exit(1) 46 | } 47 | } 48 | 49 | process.on('SIGTERM', gracefulShutdown) 50 | process.on('SIGINT', gracefulShutdown) 51 | -------------------------------------------------------------------------------- /edge-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "edge-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@tensorflow-models/coco-ssd": "^1.0.2", 8 | "@tensorflow-models/mobilenet": "^1.0.1", 9 | "@tensorflow/tfjs": "^1.1.2", 10 | "@tensorflow/tfjs-node": "^1.1.2", 11 | "abort-controller": "^2.0.0", 12 | "async-mqtt": "^2.0.0", 13 | "express": "^4.16.4", 14 | "flatmap": "0.0.3", 15 | "ip": "^1.1.5", 16 | "jpeg-js": "^0.4.0", 17 | "jsonwebtoken": "^8.4.0", 18 | "limiter": "^1.1.3", 19 | "mdns": "^2.4.0", 20 | "mqtt": "^2.18.8", 21 | "node-fetch": "^2.3.0", 22 | "socket.io": "^2.2.0", 23 | "winston": "^3.1.0" 24 | }, 25 | "devDependencies": {}, 26 | "scripts": { 27 | "start": "node index.js", 28 | "test": "echo \"Error: no test specified\" && exit 1" 29 | }, 30 | "author": "", 31 | "license": "SEE LICENSE IN License.md" 32 | } 33 | -------------------------------------------------------------------------------- /edge-server/popcorn.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/edge-server/popcorn.jpeg -------------------------------------------------------------------------------- /edge-server/test_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/edge-server/test_1.jpeg -------------------------------------------------------------------------------- /edge-server/test_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/edge-server/test_2.jpeg -------------------------------------------------------------------------------- /edge-server/test_3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarowolfx/gcloud-iot-edge-tensorflow/af12aa60c9baf11d55b2672990aeb55afd2848a2/edge-server/test_3.jpeg -------------------------------------------------------------------------------- /edge-server/test_model.js: -------------------------------------------------------------------------------- 1 | require('@tensorflow/tfjs-node') 2 | const tf = require('@tensorflow/tfjs') 3 | const jpeg = require('jpeg-js') 4 | const fs = require('fs') 5 | 6 | const CatClassifier = require('./CatClassifier') 7 | const ImageClassifier = require('./ImageClassifier') 8 | 9 | function saveImage(tensor, path) { 10 | const [batch, height, width, channels] = tensor.shape 11 | //create an Image data var 12 | const buffer = Buffer.alloc(width * height * 4) 13 | //get the tensor values as data 14 | const data = tensor.dataSync() 15 | //map the values to the buffer 16 | let i = 0 17 | for (let y = 0; y < height; y++) { 18 | for (let x = 0; x < width; x++) { 19 | const pos = (y * width + x) * 4 // position in buffer based on x and y 20 | buffer[pos] = data[i] // some R value [0, 255] 21 | buffer[pos + 1] = data[i + 1] // some G value 22 | buffer[pos + 2] = data[i + 2] // some B value 23 | buffer[pos + 3] = 255 // set alpha channel 24 | i += 3 25 | } 26 | } 27 | 28 | const rawImageData = { 29 | data: buffer, 30 | width: width, 31 | height: height 32 | }; 33 | const jpegImageData = jpeg.encode(rawImageData, 50) 34 | fs.writeFile(path, jpegImageData.data, err => { 35 | 36 | }) 37 | } 38 | 39 | async function main() { 40 | try { 41 | const labels = { 0: 'berry', 1: 'jam', 2: 'muffin', 3: 'popcorn', 4: 'raspberry' } 42 | const modelPath = 'file://../custom_model/tfjs-models/model.json' 43 | const classifier = new CatClassifier({ labels, modelPath }) 44 | await classifier.load() 45 | 46 | const trackingTags = ['cat'] 47 | const threshold = 0.6 48 | const mode = 'detect' 49 | const detector = new ImageClassifier({ mode, threshold, trackingTags }) 50 | await detector.load() 51 | console.log("Loaded") 52 | 53 | const index = Math.floor((Math.random() * 9) + 1) 54 | const catIndex = Math.floor((Math.random() * 4)) 55 | const cat = Object.values(labels)[catIndex] 56 | //const path = `../custom_model/output/${cat}/${index}.jpg` 57 | //const path = '../custom_model/images/cat00005.jpg' 58 | const path = './test_3.jpeg' 59 | console.log(`Reading image ${path}`) 60 | 61 | const result = await classifier.classifyFromFile(path) 62 | console.log(result) 63 | 64 | return; 65 | 66 | const image = classifier.readImage(path) 67 | const input = classifier.imageToInput(image, 3) 68 | 69 | const objects = await detector.classifyFromFile(path) 70 | console.log(objects) 71 | 72 | const classifyInput = input 73 | .asType('float32') 74 | .expandDims(0) 75 | 76 | const [height, width, channels] = input.shape 77 | 78 | objects 79 | .filter(obj => obj.class === 'cat') 80 | .forEach(async (obj, i) => { 81 | const [x1, y1, w, h] = obj.bbox 82 | const y2 = (y1 + h) / height 83 | const x2 = (x1 + w) / width 84 | const box = [[y1 / height, x1 / width, y2, x2]] 85 | const crop = tf.image.cropAndResize(classifyInput, box, [0], [h, w]) 86 | 87 | const result = await classifier.classifyFromInput(crop.div(255.0)) 88 | console.log(result) 89 | saveImage(crop, `test_${i}.jpeg`) 90 | }) 91 | 92 | } catch (e) { 93 | console.error(e) 94 | } 95 | } 96 | 97 | main() 98 | -------------------------------------------------------------------------------- /edge-server/util/Bag.js: -------------------------------------------------------------------------------- 1 | 2 | class Bag { 3 | constructor() { 4 | this.bag = {} 5 | } 6 | 7 | keyExtractor( item ) { 8 | return item 9 | } 10 | 11 | add( item ) { 12 | const key = this.keyExtractor( item ) 13 | if ( this.contains( item ) ) { 14 | this.bag[key] += 1 15 | } else { 16 | this.bag[key] = 1 17 | } 18 | } 19 | 20 | remove( item ) { 21 | const key = this.keyExtractor( item ) 22 | if ( this.contains( item ) ) { 23 | if ( this.count( item ) > 1 ) { 24 | this.bag[key] -= 1 25 | } else { 26 | delete this.bag[key] 27 | } 28 | } 29 | } 30 | 31 | contains( item ) { 32 | const key = this.keyExtractor( item ) 33 | return !!this.bag[key] 34 | } 35 | 36 | count( item ) { 37 | const key = this.keyExtractor( item ) 38 | return this.bag[key] 39 | } 40 | 41 | toArray() { 42 | return Object.keys( this.bag ) 43 | } 44 | 45 | toObject() { 46 | return { ...this.bag } 47 | } 48 | } 49 | 50 | module.exports = Bag 51 | -------------------------------------------------------------------------------- /esp32-camera-firmware/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .pioenvs 3 | .piolibdeps 4 | .vscode/.browse.c_cpp.db* 5 | .vscode/c_cpp_properties.json 6 | .vscode/launch.json 7 | -------------------------------------------------------------------------------- /esp32-camera-firmware/.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /esp32-camera-firmware/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } -------------------------------------------------------------------------------- /esp32-camera-firmware/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "terminal.integrated.env.osx": { 3 | "PATH": "/Users/alvaroviebrantz/.platformio/penv/bin:/Users/alvaroviebrantz/.platformio/penv:/Users/alvaroviebrantz/bin:/Users/alvaroviebrantz/Library/Android/sdk/tools/bin:/Users/alvaroviebrantz/Library/Android/sdk/platform-tools:/Users/alvaroviebrantz/Documents/Desenvolvimento/google-cloud-sdk/bin:/Users/alvaroviebrantz/.mos/bin:/Users/alvaroviebrantz/Documents/Desenvolvimento/Flutter/sdk/bin:/Users/alvaroviebrantz/.nvm/versions/node/v10.13.0/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Users/alvaroviebrantz/Documents/Desenvolvimento/go/gocode/bin:/Users/alvaroviebrantz/.fastlane/bin:/Users/alvaroviebrantz/Documents/Desenvolvimento/leverege/platform/build-tools/src:/Users/alvaroviebrantz/Documents/Desenvolvimento/leverege/platform/terraform-k8s", 4 | "PLATFORMIO_CALLER": "vscode" 5 | }, 6 | "files.associations": { 7 | "sdkconfig.h": "c", 8 | "qr_recoginize.h": "c", 9 | "ov2640.h": "c", 10 | "sensor.h": "c", 11 | "esp_wifi.h": "c" 12 | } 13 | } -------------------------------------------------------------------------------- /esp32-camera-firmware/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/camera_common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "rom/lldesc.h" 7 | #include "esp_err.h" 8 | #include "esp_intr_alloc.h" 9 | #include "freertos/FreeRTOS.h" 10 | #include "freertos/semphr.h" 11 | #include "freertos/task.h" 12 | #include "esp_camera.h" 13 | #include "sensor.h" 14 | 15 | typedef union { 16 | struct { 17 | uint8_t sample2; 18 | uint8_t unused2; 19 | uint8_t sample1; 20 | uint8_t unused1; 21 | }; 22 | uint32_t val; 23 | } dma_elem_t; 24 | 25 | typedef enum { 26 | /* camera sends byte sequence: s1, s2, s3, s4, ... 27 | * fifo receives: 00 s1 00 s2, 00 s2 00 s3, 00 s3 00 s4, ... 28 | */ 29 | SM_0A0B_0B0C = 0, 30 | /* camera sends byte sequence: s1, s2, s3, s4, ... 31 | * fifo receives: 00 s1 00 s2, 00 s3 00 s4, ... 32 | */ 33 | SM_0A0B_0C0D = 1, 34 | /* camera sends byte sequence: s1, s2, s3, s4, ... 35 | * fifo receives: 00 s1 00 00, 00 s2 00 00, 00 s3 00 00, ... 36 | */ 37 | SM_0A00_0B00 = 3, 38 | } i2s_sampling_mode_t; 39 | 40 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/esp_camera.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | /* 15 | * Example Use 16 | * 17 | static camera_config_t camera_example_config = { 18 | .pin_pwdn = PIN_PWDN, 19 | .pin_reset = PIN_RESET, 20 | .pin_xclk = PIN_XCLK, 21 | .pin_sscb_sda = PIN_SIOD, 22 | .pin_sscb_scl = PIN_SIOC, 23 | .pin_d7 = PIN_D7, 24 | .pin_d6 = PIN_D6, 25 | .pin_d5 = PIN_D5, 26 | .pin_d4 = PIN_D4, 27 | .pin_d3 = PIN_D3, 28 | .pin_d2 = PIN_D2, 29 | .pin_d1 = PIN_D1, 30 | .pin_d0 = PIN_D0, 31 | .pin_vsync = PIN_VSYNC, 32 | .pin_href = PIN_HREF, 33 | .pin_pclk = PIN_PCLK, 34 | 35 | .xclk_freq_hz = 20000000, 36 | .ledc_timer = LEDC_TIMER_0, 37 | .ledc_channel = LEDC_CHANNEL_0, 38 | .pixel_format = PIXFORMAT_JPEG, 39 | .frame_size = FRAMESIZE_SVGA, 40 | .jpeg_quality = 10, 41 | .fb_count = 2 42 | }; 43 | 44 | esp_err_t camera_example_init(){ 45 | return esp_camera_init(&camera_example_config); 46 | } 47 | 48 | esp_err_t camera_example_capture(){ 49 | //capture a frame 50 | camera_fb_t * fb = esp_camera_fb_get(); 51 | if (!fb) { 52 | ESP_LOGE(TAG, "Frame buffer could not be acquired"); 53 | return ESP_FAIL; 54 | } 55 | 56 | //replace this with your own function 57 | display_image(fb->width, fb->height, fb->pixformat, fb->buf, fb->len); 58 | 59 | //return the frame buffer back to be reused 60 | esp_camera_fb_return(fb); 61 | 62 | return ESP_OK; 63 | } 64 | */ 65 | 66 | #pragma once 67 | 68 | #include "esp_err.h" 69 | #include "driver/ledc.h" 70 | #include "sensor.h" 71 | 72 | #ifdef __cplusplus 73 | extern "C" { 74 | #endif 75 | 76 | /** 77 | * @brief Configuration structure for camera initialization 78 | */ 79 | typedef struct { 80 | int pin_pwdn; /*!< GPIO pin for camera power down line */ 81 | int pin_reset; /*!< GPIO pin for camera reset line */ 82 | int pin_xclk; /*!< GPIO pin for camera XCLK line */ 83 | int pin_sscb_sda; /*!< GPIO pin for camera SDA line */ 84 | int pin_sscb_scl; /*!< GPIO pin for camera SCL line */ 85 | int pin_d7; /*!< GPIO pin for camera D7 line */ 86 | int pin_d6; /*!< GPIO pin for camera D6 line */ 87 | int pin_d5; /*!< GPIO pin for camera D5 line */ 88 | int pin_d4; /*!< GPIO pin for camera D4 line */ 89 | int pin_d3; /*!< GPIO pin for camera D3 line */ 90 | int pin_d2; /*!< GPIO pin for camera D2 line */ 91 | int pin_d1; /*!< GPIO pin for camera D1 line */ 92 | int pin_d0; /*!< GPIO pin for camera D0 line */ 93 | int pin_vsync; /*!< GPIO pin for camera VSYNC line */ 94 | int pin_href; /*!< GPIO pin for camera HREF line */ 95 | int pin_pclk; /*!< GPIO pin for camera PCLK line */ 96 | 97 | int xclk_freq_hz; /*!< Frequency of XCLK signal, in Hz. Either 10KHz or 20KHz */ 98 | 99 | ledc_timer_t ledc_timer; /*!< LEDC timer to be used for generating XCLK */ 100 | ledc_channel_t ledc_channel; /*!< LEDC channel to be used for generating XCLK */ 101 | 102 | pixformat_t pixel_format; /*!< Format of the pixel data: PIXFORMAT_ + YUV422|GRAYSCALE|RGB565|JPEG */ 103 | framesize_t frame_size; /*!< Size of the output image: FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA */ 104 | 105 | int jpeg_quality; /*!< Quality of JPEG output. 0-63 lower means higher quality */ 106 | size_t fb_count; /*!< Number of frame buffers to be allocated. If more than one, then each frame will be acquired (double speed) */ 107 | } camera_config_t; 108 | 109 | /** 110 | * @brief Data structure of camera frame buffer 111 | */ 112 | typedef struct { 113 | uint8_t * buf; /*!< Pointer to the pixel data */ 114 | size_t len; /*!< Length of the buffer in bytes */ 115 | size_t width; /*!< Width of the buffer in pixels */ 116 | size_t height; /*!< Height of the buffer in pixels */ 117 | pixformat_t format; /*!< Format of the pixel data */ 118 | } camera_fb_t; 119 | 120 | #define ESP_ERR_CAMERA_BASE 0x20000 121 | #define ESP_ERR_CAMERA_NOT_DETECTED (ESP_ERR_CAMERA_BASE + 1) 122 | #define ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE (ESP_ERR_CAMERA_BASE + 2) 123 | #define ESP_ERR_CAMERA_NOT_SUPPORTED (ESP_ERR_CAMERA_BASE + 3) 124 | 125 | /** 126 | * @brief Initialize the camera driver 127 | * 128 | * @note call camera_probe before calling this function 129 | * 130 | * This function detects and configures camera over I2C interface, 131 | * allocates framebuffer and DMA buffers, 132 | * initializes parallel I2S input, and sets up DMA descriptors. 133 | * 134 | * Currently this function can only be called once and there is 135 | * no way to de-initialize this module. 136 | * 137 | * @param config Camera configuration parameters 138 | * 139 | * @return ESP_OK on success 140 | */ 141 | esp_err_t esp_camera_init(const camera_config_t* config); 142 | 143 | /** 144 | * @brief Deinitialize the camera driver 145 | * 146 | * @return 147 | * - ESP_OK on success 148 | * - ESP_ERR_INVALID_STATE if the driver hasn't been initialized yet 149 | */ 150 | esp_err_t esp_camera_deinit(); 151 | 152 | /** 153 | * @brief Obtain pointer to a frame buffer. 154 | * 155 | * @return pointer to the frame buffer 156 | */ 157 | camera_fb_t* esp_camera_fb_get(); 158 | 159 | /** 160 | * @brief Return the frame buffer to be reused again. 161 | * 162 | * @param fb Pointer to the frame buffer 163 | */ 164 | void esp_camera_fb_return(camera_fb_t * fb); 165 | 166 | /** 167 | * @brief Get a pointer to the image sensor control structure 168 | * 169 | * @return pointer to the sensor 170 | */ 171 | sensor_t * esp_camera_sensor_get(); 172 | 173 | 174 | #ifdef __cplusplus 175 | } 176 | #endif 177 | 178 | #include "img_converters.h" 179 | 180 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/ov2640.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the OpenMV project. 3 | * Copyright (c) 2013/2014 Ibrahim Abdelkader 4 | * This work is licensed under the MIT license, see the file LICENSE for details. 5 | * 6 | * OV2640 driver. 7 | * 8 | */ 9 | #ifndef __OV2640_H__ 10 | #define __OV2640_H__ 11 | #include "sensor.h" 12 | int ov2640_init(sensor_t *sensor); 13 | #endif // __OV2640_H__ 14 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/ov2640_regs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the OpenMV project. 3 | * Copyright (c) 2013/2014 Ibrahim Abdelkader 4 | * This work is licensed under the MIT license, see the file LICENSE for details. 5 | * 6 | * OV2640 register definitions. 7 | */ 8 | #ifndef __REG_REGS_H__ 9 | #define __REG_REGS_H__ 10 | /* DSP register bank FF=0x00*/ 11 | #define R_BYPASS 0x05 12 | #define QS 0x44 13 | #define CTRLI 0x50 14 | #define HSIZE 0x51 15 | #define VSIZE 0x52 16 | #define XOFFL 0x53 17 | #define YOFFL 0x54 18 | #define VHYX 0x55 19 | #define DPRP 0x56 20 | #define TEST 0x57 21 | #define ZMOW 0x5A 22 | #define ZMOH 0x5B 23 | #define ZMHH 0x5C 24 | #define BPADDR 0x7C 25 | #define BPDATA 0x7D 26 | #define CTRL2 0x86 27 | #define CTRL3 0x87 28 | #define SIZEL 0x8C 29 | #define HSIZE8 0xC0 30 | #define VSIZE8 0xC1 31 | #define CTRL0 0xC2 32 | #define CTRL1 0xC3 33 | #define R_DVP_SP 0xD3 34 | #define IMAGE_MODE 0xDA 35 | #define RESET 0xE0 36 | #define MS_SP 0xF0 37 | #define SS_ID 0xF7 38 | #define SS_CTRL 0xF7 39 | #define MC_BIST 0xF9 40 | #define MC_AL 0xFA 41 | #define MC_AH 0xFB 42 | #define MC_D 0xFC 43 | #define P_CMD 0xFD 44 | #define P_STATUS 0xFE 45 | #define BANK_SEL 0xFF 46 | 47 | #define CTRLI_LP_DP 0x80 48 | #define CTRLI_ROUND 0x40 49 | 50 | #define CTRL0_AEC_EN 0x80 51 | #define CTRL0_AEC_SEL 0x40 52 | #define CTRL0_STAT_SEL 0x20 53 | #define CTRL0_VFIRST 0x10 54 | #define CTRL0_YUV422 0x08 55 | #define CTRL0_YUV_EN 0x04 56 | #define CTRL0_RGB_EN 0x02 57 | #define CTRL0_RAW_EN 0x01 58 | 59 | #define CTRL2_DCW_EN 0x20 60 | #define CTRL2_SDE_EN 0x10 61 | #define CTRL2_UV_ADJ_EN 0x08 62 | #define CTRL2_UV_AVG_EN 0x04 63 | #define CTRL2_CMX_EN 0x01 64 | 65 | #define CTRL3_BPC_EN 0x80 66 | #define CTRL3_WPC_EN 0x40 67 | 68 | #define R_DVP_SP_AUTO_MODE 0x80 69 | 70 | #define R_BYPASS_DSP_EN 0x00 71 | #define R_BYPASS_DSP_BYPAS 0x01 72 | 73 | #define IMAGE_MODE_Y8_DVP_EN 0x40 74 | #define IMAGE_MODE_JPEG_EN 0x10 75 | #define IMAGE_MODE_YUV422 0x00 76 | #define IMAGE_MODE_RAW10 0x04 77 | #define IMAGE_MODE_RGB565 0x08 78 | #define IMAGE_MODE_HREF_VSYNC 0x02 79 | #define IMAGE_MODE_LBYTE_FIRST 0x01 80 | 81 | #define RESET_MICROC 0x40 82 | #define RESET_SCCB 0x20 83 | #define RESET_JPEG 0x10 84 | #define RESET_DVP 0x04 85 | #define RESET_IPU 0x02 86 | #define RESET_CIF 0x01 87 | 88 | #define MC_BIST_RESET 0x80 89 | #define MC_BIST_BOOT_ROM_SEL 0x40 90 | #define MC_BIST_12KB_SEL 0x20 91 | #define MC_BIST_12KB_MASK 0x30 92 | #define MC_BIST_512KB_SEL 0x08 93 | #define MC_BIST_512KB_MASK 0x0C 94 | #define MC_BIST_BUSY_BIT_R 0x02 95 | #define MC_BIST_MC_RES_ONE_SH_W 0x02 96 | #define MC_BIST_LAUNCH 0x01 97 | 98 | 99 | typedef enum { 100 | BANK_DSP, BANK_SENSOR, BANK_MAX 101 | } ov2640_bank_t; 102 | 103 | /* Sensor register bank FF=0x01*/ 104 | #define GAIN 0x00 105 | #define COM1 0x03 106 | #define REG04 0x04 107 | #define REG08 0x08 108 | #define COM2 0x09 109 | #define REG_PID 0x0A 110 | #define REG_VER 0x0B 111 | #define COM3 0x0C 112 | #define COM4 0x0D 113 | #define AEC 0x10 114 | #define CLKRC 0x11 115 | #define COM7 0x12 116 | #define COM8 0x13 117 | #define COM9 0x14 /* AGC gain ceiling */ 118 | #define COM10 0x15 119 | #define HSTART 0x17 120 | #define HSTOP 0x18 121 | #define VSTART 0x19 122 | #define VSTOP 0x1A 123 | #define MIDH 0x1C 124 | #define MIDL 0x1D 125 | #define AEW 0x24 126 | #define AEB 0x25 127 | #define VV 0x26 128 | #define REG2A 0x2A 129 | #define FRARL 0x2B 130 | #define ADDVSL 0x2D 131 | #define ADDVSH 0x2E 132 | #define YAVG 0x2F 133 | #define HSDY 0x30 134 | #define HEDY 0x31 135 | #define REG32 0x32 136 | #define ARCOM2 0x34 137 | #define REG45 0x45 138 | #define FLL 0x46 139 | #define FLH 0x47 140 | #define COM19 0x48 141 | #define ZOOMS 0x49 142 | #define COM22 0x4B 143 | #define COM25 0x4E 144 | #define BD50 0x4F 145 | #define BD60 0x50 146 | #define REG5D 0x5D 147 | #define REG5E 0x5E 148 | #define REG5F 0x5F 149 | #define REG60 0x60 150 | #define HISTO_LOW 0x61 151 | #define HISTO_HIGH 0x62 152 | 153 | #define REG04_DEFAULT 0x28 154 | #define REG04_HFLIP_IMG 0x80 155 | #define REG04_VFLIP_IMG 0x40 156 | #define REG04_VREF_EN 0x10 157 | #define REG04_HREF_EN 0x08 158 | #define REG04_SET(x) (REG04_DEFAULT|x) 159 | 160 | #define COM2_STDBY 0x10 161 | #define COM2_OUT_DRIVE_1x 0x00 162 | #define COM2_OUT_DRIVE_2x 0x01 163 | #define COM2_OUT_DRIVE_3x 0x02 164 | #define COM2_OUT_DRIVE_4x 0x03 165 | 166 | #define COM3_DEFAULT 0x38 167 | #define COM3_BAND_50Hz 0x04 168 | #define COM3_BAND_60Hz 0x00 169 | #define COM3_BAND_AUTO 0x02 170 | #define COM3_BAND_SET(x) (COM3_DEFAULT|x) 171 | 172 | #define COM7_SRST 0x80 173 | #define COM7_RES_UXGA 0x00 /* UXGA */ 174 | #define COM7_RES_SVGA 0x40 /* SVGA */ 175 | #define COM7_RES_CIF 0x20 /* CIF */ 176 | #define COM7_ZOOM_EN 0x04 /* Enable Zoom */ 177 | #define COM7_COLOR_BAR 0x02 /* Enable Color Bar Test */ 178 | 179 | #define COM8_DEFAULT 0xC0 180 | #define COM8_BNDF_EN 0x20 /* Enable Banding filter */ 181 | #define COM8_AGC_EN 0x04 /* AGC Auto/Manual control selection */ 182 | #define COM8_AEC_EN 0x01 /* Auto/Manual Exposure control */ 183 | #define COM8_SET(x) (COM8_DEFAULT|x) 184 | 185 | #define COM9_DEFAULT 0x08 186 | #define COM9_AGC_GAIN_2x 0x00 /* AGC: 2x */ 187 | #define COM9_AGC_GAIN_4x 0x01 /* AGC: 4x */ 188 | #define COM9_AGC_GAIN_8x 0x02 /* AGC: 8x */ 189 | #define COM9_AGC_GAIN_16x 0x03 /* AGC: 16x */ 190 | #define COM9_AGC_GAIN_32x 0x04 /* AGC: 32x */ 191 | #define COM9_AGC_GAIN_64x 0x05 /* AGC: 64x */ 192 | #define COM9_AGC_GAIN_128x 0x06 /* AGC: 128x */ 193 | #define COM9_AGC_SET(x) (COM9_DEFAULT|(x<<5)) 194 | 195 | #define COM10_HREF_EN 0x80 /* HSYNC changes to HREF */ 196 | #define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */ 197 | #define COM10_PCLK_FREE 0x20 /* PCLK output option: free running PCLK */ 198 | #define COM10_PCLK_EDGE 0x10 /* Data is updated at the rising edge of PCLK */ 199 | #define COM10_HREF_NEG 0x08 /* HREF negative */ 200 | #define COM10_VSYNC_NEG 0x02 /* VSYNC negative */ 201 | #define COM10_HSYNC_NEG 0x01 /* HSYNC negative */ 202 | 203 | #define CTRL1_AWB 0x08 /* Enable AWB */ 204 | 205 | #define VV_AGC_TH_SET(h,l) ((h<<4)|(l&0x0F)) 206 | 207 | #define REG32_UXGA 0x36 208 | #define REG32_SVGA 0x09 209 | #define REG32_CIF 0x89 210 | 211 | #define CLKRC_2X 0x80 212 | 213 | #endif //__REG_REGS_H__ 214 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/ov7725.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the OpenMV project. 3 | * Copyright (c) 2013/2014 Ibrahim Abdelkader 4 | * This work is licensed under the MIT license, see the file LICENSE for details. 5 | * 6 | * OV7725 driver. 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "sccb.h" 14 | #include "ov7725.h" 15 | #include "ov7725_regs.h" 16 | #include "freertos/FreeRTOS.h" 17 | #include "freertos/task.h" 18 | 19 | #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) 20 | #include "esp32-hal-log.h" 21 | #else 22 | #include "esp_log.h" 23 | static const char* TAG = "ov7725"; 24 | #endif 25 | 26 | 27 | static const uint8_t default_regs[][2] = { 28 | {COM3, COM3_SWAP_YUV}, 29 | {COM7, COM7_RES_QVGA | COM7_FMT_YUV}, 30 | 31 | {COM4, 0x01}, /* bypass PLL */ 32 | {CLKRC, 0xC0}, /* Res/Bypass pre-scalar */ 33 | 34 | // QVGA Window Size 35 | {HSTART, 0x3F}, 36 | {HSIZE, 0x50}, 37 | {VSTART, 0x03}, 38 | {VSIZE, 0x78}, 39 | {HREF, 0x00}, 40 | 41 | // Scale down to QVGA Resolution 42 | {HOUTSIZE, 0x50}, 43 | {VOUTSIZE, 0x78}, 44 | 45 | {COM12, 0x03}, 46 | {EXHCH, 0x00}, 47 | {TGT_B, 0x7F}, 48 | {FIXGAIN, 0x09}, 49 | {AWB_CTRL0, 0xE0}, 50 | {DSP_CTRL1, 0xFF}, 51 | 52 | {DSP_CTRL2, DSP_CTRL2_VDCW_EN | DSP_CTRL2_HDCW_EN | DSP_CTRL2_HZOOM_EN | DSP_CTRL2_VZOOM_EN}, 53 | 54 | {DSP_CTRL3, 0x00}, 55 | {DSP_CTRL4, 0x00}, 56 | {DSPAUTO, 0xFF}, 57 | 58 | {COM8, 0xF0}, 59 | {COM6, 0xC5}, 60 | {COM9, 0x11}, 61 | {COM10, COM10_VSYNC_NEG | COM10_PCLK_MASK}, //Invert VSYNC and MASK PCLK 62 | {BDBASE, 0x7F}, 63 | {DBSTEP, 0x03}, 64 | {AEW, 0x96}, 65 | {AEB, 0x64}, 66 | {VPT, 0xA1}, 67 | {EXHCL, 0x00}, 68 | {AWB_CTRL3, 0xAA}, 69 | {COM8, 0xFF}, 70 | 71 | //Gamma 72 | {GAM1, 0x0C}, 73 | {GAM2, 0x16}, 74 | {GAM3, 0x2A}, 75 | {GAM4, 0x4E}, 76 | {GAM5, 0x61}, 77 | {GAM6, 0x6F}, 78 | {GAM7, 0x7B}, 79 | {GAM8, 0x86}, 80 | {GAM9, 0x8E}, 81 | {GAM10, 0x97}, 82 | {GAM11, 0xA4}, 83 | {GAM12, 0xAF}, 84 | {GAM13, 0xC5}, 85 | {GAM14, 0xD7}, 86 | {GAM15, 0xE8}, 87 | 88 | {SLOP, 0x20}, 89 | {EDGE1, 0x05}, 90 | {EDGE2, 0x03}, 91 | {EDGE3, 0x00}, 92 | {DNSOFF, 0x01}, 93 | 94 | {MTX1, 0xB0}, 95 | {MTX2, 0x9D}, 96 | {MTX3, 0x13}, 97 | {MTX4, 0x16}, 98 | {MTX5, 0x7B}, 99 | {MTX6, 0x91}, 100 | {MTX_CTRL, 0x1E}, 101 | 102 | {BRIGHTNESS, 0x08}, 103 | {CONTRAST, 0x30}, 104 | {UVADJ0, 0x81}, 105 | {SDE, (SDE_CONT_BRIGHT_EN | SDE_SATURATION_EN)}, 106 | 107 | // For 30 fps/60Hz 108 | {DM_LNL, 0x00}, 109 | {DM_LNH, 0x00}, 110 | {BDBASE, 0x7F}, 111 | {DBSTEP, 0x03}, 112 | 113 | // Lens Correction, should be tuned with real camera module 114 | {LC_RADI, 0x10}, 115 | {LC_COEF, 0x10}, 116 | {LC_COEFB, 0x14}, 117 | {LC_COEFR, 0x17}, 118 | {LC_CTR, 0x05}, 119 | {COM5, 0xF5}, //0x65 120 | 121 | {0x00, 0x00}, 122 | }; 123 | 124 | 125 | static int reset(sensor_t *sensor) 126 | { 127 | int i=0; 128 | const uint8_t (*regs)[2]; 129 | 130 | // Reset all registers 131 | SCCB_Write(sensor->slv_addr, COM7, COM7_RESET); 132 | 133 | // Delay 10 ms 134 | vTaskDelay(10 / portTICK_PERIOD_MS); 135 | 136 | // Write default regsiters 137 | for (i=0, regs = default_regs; regs[i][0]; i++) { 138 | SCCB_Write(sensor->slv_addr, regs[i][0], regs[i][1]); 139 | } 140 | 141 | // Delay 142 | vTaskDelay(30 / portTICK_PERIOD_MS); 143 | 144 | return 0; 145 | } 146 | 147 | 148 | static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) 149 | { 150 | int ret=0; 151 | // Read register COM7 152 | uint8_t reg = SCCB_Read(sensor->slv_addr, COM7); 153 | 154 | switch (pixformat) { 155 | case PIXFORMAT_RGB565: 156 | reg = COM7_SET_RGB(reg, COM7_FMT_RGB565); 157 | break; 158 | case PIXFORMAT_YUV422: 159 | case PIXFORMAT_GRAYSCALE: 160 | reg = COM7_SET_FMT(reg, COM7_FMT_YUV); 161 | break; 162 | default: 163 | return -1; 164 | } 165 | 166 | // Write back register COM7 167 | ret = SCCB_Write(sensor->slv_addr, COM7, reg); 168 | 169 | // Delay 170 | vTaskDelay(30 / portTICK_PERIOD_MS); 171 | 172 | return ret; 173 | } 174 | 175 | static int set_framesize(sensor_t *sensor, framesize_t framesize) 176 | { 177 | int ret=0; 178 | uint16_t w = resolution[framesize][0]; 179 | uint16_t h = resolution[framesize][1]; 180 | 181 | // Write MSBs 182 | ret |= SCCB_Write(sensor->slv_addr, HOUTSIZE, w>>2); 183 | ret |= SCCB_Write(sensor->slv_addr, VOUTSIZE, h>>1); 184 | 185 | // Write LSBs 186 | ret |= SCCB_Write(sensor->slv_addr, EXHCH, ((w&0x3) | ((h&0x1) << 2))); 187 | 188 | if (framesize < FRAMESIZE_VGA) { 189 | // Enable auto-scaling/zooming factors 190 | ret |= SCCB_Write(sensor->slv_addr, DSPAUTO, 0xFF); 191 | } else { 192 | // Disable auto-scaling/zooming factors 193 | ret |= SCCB_Write(sensor->slv_addr, DSPAUTO, 0xF3); 194 | 195 | // Clear auto-scaling/zooming factors 196 | ret |= SCCB_Write(sensor->slv_addr, SCAL0, 0x00); 197 | ret |= SCCB_Write(sensor->slv_addr, SCAL1, 0x00); 198 | ret |= SCCB_Write(sensor->slv_addr, SCAL2, 0x00); 199 | } 200 | 201 | // Delay 202 | vTaskDelay(30 / portTICK_PERIOD_MS); 203 | 204 | return ret; 205 | } 206 | 207 | static int set_colorbar(sensor_t *sensor, int enable) 208 | { 209 | int ret=0; 210 | uint8_t reg; 211 | 212 | // Read reg COM3 213 | reg = SCCB_Read(sensor->slv_addr, COM3); 214 | // Enable colorbar test pattern output 215 | reg = COM3_SET_CBAR(reg, enable); 216 | // Write back COM3 217 | ret |= SCCB_Write(sensor->slv_addr, COM3, reg); 218 | 219 | // Read reg DSP_CTRL3 220 | reg = SCCB_Read(sensor->slv_addr, DSP_CTRL3); 221 | // Enable DSP colorbar output 222 | reg = DSP_CTRL3_SET_CBAR(reg, enable); 223 | // Write back DSP_CTRL3 224 | ret |= SCCB_Write(sensor->slv_addr, DSP_CTRL3, reg); 225 | 226 | return ret; 227 | } 228 | 229 | static int set_whitebal(sensor_t *sensor, int enable) 230 | { 231 | // Read register COM8 232 | uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); 233 | 234 | // Set white bal on/off 235 | reg = COM8_SET_AWB(reg, enable); 236 | 237 | // Write back register COM8 238 | return SCCB_Write(sensor->slv_addr, COM8, reg); 239 | } 240 | 241 | static int set_gain_ctrl(sensor_t *sensor, int enable) 242 | { 243 | // Read register COM8 244 | uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); 245 | 246 | // Set white bal on/off 247 | reg = COM8_SET_AGC(reg, enable); 248 | 249 | // Write back register COM8 250 | return SCCB_Write(sensor->slv_addr, COM8, reg); 251 | } 252 | 253 | static int set_exposure_ctrl(sensor_t *sensor, int enable) 254 | { 255 | // Read register COM8 256 | uint8_t reg = SCCB_Read(sensor->slv_addr, COM8); 257 | 258 | // Set white bal on/off 259 | reg = COM8_SET_AEC(reg, enable); 260 | 261 | // Write back register COM8 262 | return SCCB_Write(sensor->slv_addr, COM8, reg); 263 | } 264 | 265 | static int set_hmirror(sensor_t *sensor, int enable) 266 | { 267 | // Read register COM3 268 | uint8_t reg = SCCB_Read(sensor->slv_addr, COM3); 269 | 270 | // Set mirror on/off 271 | reg = COM3_SET_MIRROR(reg, enable); 272 | 273 | // Write back register COM3 274 | return SCCB_Write(sensor->slv_addr, COM3, reg); 275 | } 276 | 277 | static int set_vflip(sensor_t *sensor, int enable) 278 | { 279 | // Read register COM3 280 | uint8_t reg = SCCB_Read(sensor->slv_addr, COM3); 281 | 282 | // Set mirror on/off 283 | reg = COM3_SET_FLIP(reg, enable); 284 | 285 | // Write back register COM3 286 | return SCCB_Write(sensor->slv_addr, COM3, reg); 287 | } 288 | 289 | int ov7725_init(sensor_t *sensor) 290 | { 291 | // Set function pointers 292 | sensor->reset = reset; 293 | sensor->set_pixformat = set_pixformat; 294 | sensor->set_framesize = set_framesize; 295 | sensor->set_colorbar = set_colorbar; 296 | sensor->set_whitebal = set_whitebal; 297 | sensor->set_gain_ctrl = set_gain_ctrl; 298 | sensor->set_exposure_ctrl = set_exposure_ctrl; 299 | sensor->set_hmirror = set_hmirror; 300 | sensor->set_vflip = set_vflip; 301 | 302 | // Retrieve sensor's signature 303 | sensor->id.MIDH = SCCB_Read(sensor->slv_addr, REG_MIDH); 304 | sensor->id.MIDL = SCCB_Read(sensor->slv_addr, REG_MIDL); 305 | sensor->id.PID = SCCB_Read(sensor->slv_addr, REG_PID); 306 | sensor->id.VER = SCCB_Read(sensor->slv_addr, REG_VER); 307 | 308 | ESP_LOGD(TAG, "OV7725 Attached"); 309 | 310 | return 0; 311 | } 312 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/ov7725.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the OpenMV project. 3 | * Copyright (c) 2013/2014 Ibrahim Abdelkader 4 | * This work is licensed under the MIT license, see the file LICENSE for details. 5 | * 6 | * OV7725 driver. 7 | * 8 | */ 9 | #ifndef __OV7725_H__ 10 | #define __OV7725_H__ 11 | #include "sensor.h" 12 | 13 | int ov7725_init(sensor_t *sensor); 14 | #endif // __OV7725_H__ 15 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/sccb.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the OpenMV project. 3 | * Copyright (c) 2013/2014 Ibrahim Abdelkader 4 | * This work is licensed under the MIT license, see the file LICENSE for details. 5 | * 6 | * SCCB (I2C like) driver. 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include "sccb.h" 13 | #include "twi.h" 14 | #include 15 | #include "sdkconfig.h" 16 | #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) 17 | #include "esp32-hal-log.h" 18 | #else 19 | #include "esp_log.h" 20 | static const char* TAG = "sccb"; 21 | #endif 22 | 23 | 24 | #define SCCB_FREQ (100000) // We don't need fast I2C. 100KHz is fine here. 25 | #define TIMEOUT (1000) /* Can't be sure when I2C routines return. Interrupts 26 | while polling hardware may result in unknown delays. */ 27 | 28 | 29 | int SCCB_Init(int pin_sda, int pin_scl) 30 | { 31 | twi_init(pin_sda, pin_scl); 32 | return 0; 33 | } 34 | 35 | uint8_t SCCB_Probe() 36 | { 37 | uint8_t reg = 0x00; 38 | uint8_t slv_addr = 0x00; 39 | 40 | for (uint8_t i=0; i<127; i++) { 41 | if (twi_writeTo(i, ®, 1, true) == 0) { 42 | slv_addr = i; 43 | break; 44 | } 45 | 46 | if (i!=126) { 47 | vTaskDelay(1 / portTICK_PERIOD_MS); // Necessary for OV7725 camera (not for OV2640). 48 | } 49 | } 50 | return slv_addr; 51 | } 52 | 53 | uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) 54 | { 55 | uint8_t data=0; 56 | 57 | int rc = twi_writeTo(slv_addr, ®, 1, true); 58 | if (rc != 0) { 59 | data = 0xff; 60 | } else { 61 | rc = twi_readFrom(slv_addr, &data, 1, true); 62 | if (rc != 0) { 63 | data=0xFF; 64 | } 65 | } 66 | if (rc != 0) { 67 | ESP_LOGE(TAG, "SCCB_Read [%02x] failed rc=%d\n", reg, rc); 68 | } 69 | return data; 70 | } 71 | 72 | uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) 73 | { 74 | uint8_t ret=0; 75 | uint8_t buf[] = {reg, data}; 76 | 77 | if(twi_writeTo(slv_addr, buf, 2, true) != 0) { 78 | ret=0xFF; 79 | } 80 | if (ret != 0) { 81 | printf("SCCB_Write [%02x]=%02x failed\n", reg, data); 82 | } 83 | return ret; 84 | } 85 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/sccb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the OpenMV project. 3 | * Copyright (c) 2013/2014 Ibrahim Abdelkader 4 | * This work is licensed under the MIT license, see the file LICENSE for details. 5 | * 6 | * SCCB (I2C like) driver. 7 | * 8 | */ 9 | #ifndef __SCCB_H__ 10 | #define __SCCB_H__ 11 | #include 12 | int SCCB_Init(int pin_sda, int pin_scl); 13 | uint8_t SCCB_Probe(); 14 | uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg); 15 | uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data); 16 | #endif // __SCCB_H__ 17 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/sensor.c: -------------------------------------------------------------------------------- 1 | 2 | const int resolution[][2] = { 3 | { 160, 120 }, /* QQVGA */ 4 | { 128, 160 }, /* QQVGA2*/ 5 | { 176, 144 }, /* QCIF */ 6 | { 240, 176 }, /* HQVGA */ 7 | { 320, 240 }, /* QVGA */ 8 | { 400, 296 }, /* CIF */ 9 | { 640, 480 }, /* VGA */ 10 | { 800, 600 }, /* SVGA */ 11 | { 1024, 768 }, /* XGA */ 12 | { 1280, 1024 }, /* SXGA */ 13 | { 1600, 1200 }, /* UXGA */ 14 | }; 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/sensor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the OpenMV project. 3 | * Copyright (c) 2013/2014 Ibrahim Abdelkader 4 | * This work is licensed under the MIT license, see the file LICENSE for details. 5 | * 6 | * Sensor abstraction layer. 7 | * 8 | */ 9 | #ifndef __SENSOR_H__ 10 | #define __SENSOR_H__ 11 | #include 12 | 13 | #define OV9650_PID (0x96) 14 | #define OV2640_PID (0x26) 15 | #define OV7725_PID (0x77) 16 | 17 | typedef enum { 18 | PIXFORMAT_RGB565, // 2BPP/RGB565 19 | PIXFORMAT_YUV422, // 2BPP/YUV422 20 | PIXFORMAT_GRAYSCALE, // 1BPP/GRAYSCALE 21 | PIXFORMAT_JPEG, // JPEG/COMPRESSED 22 | PIXFORMAT_RGB888, // 3BPP/RGB888 23 | } pixformat_t; 24 | 25 | typedef enum { 26 | FRAMESIZE_QQVGA, // 160x120 27 | FRAMESIZE_QQVGA2, // 128x160 28 | FRAMESIZE_QCIF, // 176x144 29 | FRAMESIZE_HQVGA, // 240x176 30 | FRAMESIZE_QVGA, // 320x240 31 | FRAMESIZE_CIF, // 400x296 32 | FRAMESIZE_VGA, // 640x480 33 | FRAMESIZE_SVGA, // 800x600 34 | FRAMESIZE_XGA, // 1024x768 35 | FRAMESIZE_SXGA, // 1280x1024 36 | FRAMESIZE_UXGA, // 1600x1200 37 | } framesize_t; 38 | 39 | typedef enum { 40 | GAINCEILING_2X, 41 | GAINCEILING_4X, 42 | GAINCEILING_8X, 43 | GAINCEILING_16X, 44 | GAINCEILING_32X, 45 | GAINCEILING_64X, 46 | GAINCEILING_128X, 47 | } gainceiling_t; 48 | 49 | typedef struct { 50 | uint8_t MIDH; 51 | uint8_t MIDL; 52 | uint8_t PID; 53 | uint8_t VER; 54 | } sensor_id_t; 55 | 56 | typedef struct { 57 | framesize_t framesize;//0 - 10 58 | uint8_t quality;//0 - 63 59 | int8_t brightness;//-2 - 2 60 | int8_t contrast;//-2 - 2 61 | int8_t saturation;//-2 - 2 62 | uint8_t special_effect;//0 - 6 63 | uint8_t wb_mode;//0 - 4 64 | uint8_t awb; 65 | uint8_t awb_gain; 66 | uint8_t aec; 67 | uint8_t aec2; 68 | int8_t ae_level;//-2 - 2 69 | uint16_t aec_value;//0 - 1200 70 | uint8_t agc; 71 | uint8_t agc_gain;//0 - 30 72 | uint8_t gainceiling;//0 - 6 73 | uint8_t bpc; 74 | uint8_t wpc; 75 | uint8_t raw_gma; 76 | uint8_t lenc; 77 | uint8_t hmirror; 78 | uint8_t vflip; 79 | uint8_t dcw; 80 | uint8_t colorbar; 81 | } camera_status_t; 82 | 83 | typedef struct _sensor sensor_t; 84 | typedef struct _sensor { 85 | sensor_id_t id; // Sensor ID. 86 | uint8_t slv_addr; // Sensor I2C slave address. 87 | pixformat_t pixformat; 88 | camera_status_t status; 89 | 90 | // Sensor function pointers 91 | int (*init_status) (sensor_t *sensor); 92 | int (*reset) (sensor_t *sensor); 93 | int (*set_pixformat) (sensor_t *sensor, pixformat_t pixformat); 94 | int (*set_framesize) (sensor_t *sensor, framesize_t framesize); 95 | int (*set_contrast) (sensor_t *sensor, int level); 96 | int (*set_brightness) (sensor_t *sensor, int level); 97 | int (*set_saturation) (sensor_t *sensor, int level); 98 | int (*set_gainceiling) (sensor_t *sensor, gainceiling_t gainceiling); 99 | int (*set_quality) (sensor_t *sensor, int quality); 100 | int (*set_colorbar) (sensor_t *sensor, int enable); 101 | int (*set_whitebal) (sensor_t *sensor, int enable); 102 | int (*set_gain_ctrl) (sensor_t *sensor, int enable); 103 | int (*set_exposure_ctrl) (sensor_t *sensor, int enable); 104 | int (*set_hmirror) (sensor_t *sensor, int enable); 105 | int (*set_vflip) (sensor_t *sensor, int enable); 106 | 107 | int (*set_aec2) (sensor_t *sensor, int enable); 108 | int (*set_awb_gain) (sensor_t *sensor, int enable); 109 | int (*set_agc_gain) (sensor_t *sensor, int gain); 110 | int (*set_aec_value) (sensor_t *sensor, int gain); 111 | 112 | int (*set_special_effect) (sensor_t *sensor, int effect); 113 | int (*set_wb_mode) (sensor_t *sensor, int mode); 114 | int (*set_ae_level) (sensor_t *sensor, int level); 115 | 116 | int (*set_dcw) (sensor_t *sensor, int enable); 117 | int (*set_bpc) (sensor_t *sensor, int enable); 118 | int (*set_wpc) (sensor_t *sensor, int enable); 119 | 120 | int (*set_raw_gma) (sensor_t *sensor, int enable); 121 | int (*set_lenc) (sensor_t *sensor, int enable); 122 | } sensor_t; 123 | 124 | // Resolution table (in camera.c) 125 | extern const int resolution[][2]; 126 | 127 | #endif /* __SENSOR_H__ */ 128 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/twi.h: -------------------------------------------------------------------------------- 1 | /* 2 | twi.h - Software I2C library for ESP31B 3 | 4 | Copyright (c) 2015 Hristo Gochkov. All rights reserved. 5 | This file is part of the ESP31B core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef SI2C_h 22 | #define SI2C_h 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | void twi_init(unsigned char sda, unsigned char scl); 29 | void twi_stop(void); 30 | void twi_setClock(unsigned int freq); 31 | uint8_t twi_writeTo(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop); 32 | uint8_t twi_readFrom(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop); 33 | 34 | #ifdef __cplusplus 35 | } 36 | #endif 37 | 38 | #endif -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/xclk.c: -------------------------------------------------------------------------------- 1 | #include "driver/gpio.h" 2 | #include "driver/ledc.h" 3 | #include "esp_err.h" 4 | #include "esp_log.h" 5 | #include "xclk.h" 6 | 7 | #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) 8 | #include "esp32-hal-log.h" 9 | #else 10 | #include "esp_log.h" 11 | static const char* TAG = "camera_xclk"; 12 | #endif 13 | 14 | esp_err_t camera_enable_out_clock(camera_config_t* config) 15 | { 16 | periph_module_enable(PERIPH_LEDC_MODULE); 17 | 18 | ledc_timer_config_t timer_conf; 19 | timer_conf.duty_resolution = 2; 20 | timer_conf.freq_hz = config->xclk_freq_hz; 21 | timer_conf.speed_mode = LEDC_HIGH_SPEED_MODE; 22 | timer_conf.timer_num = config->ledc_timer; 23 | esp_err_t err = ledc_timer_config(&timer_conf); 24 | if (err != ESP_OK) { 25 | ESP_LOGE(TAG, "ledc_timer_config failed, rc=%x", err); 26 | return err; 27 | } 28 | 29 | ledc_channel_config_t ch_conf; 30 | ch_conf.gpio_num = config->pin_xclk; 31 | ch_conf.speed_mode = LEDC_HIGH_SPEED_MODE; 32 | ch_conf.channel = config->ledc_channel; 33 | ch_conf.intr_type = LEDC_INTR_DISABLE; 34 | ch_conf.timer_sel = config->ledc_timer; 35 | ch_conf.duty = 2; 36 | ch_conf.hpoint = 0; 37 | err = ledc_channel_config(&ch_conf); 38 | if (err != ESP_OK) { 39 | ESP_LOGE(TAG, "ledc_channel_config failed, rc=%x", err); 40 | return err; 41 | } 42 | return ESP_OK; 43 | } 44 | 45 | void camera_disable_out_clock() 46 | { 47 | periph_module_disable(PERIPH_LEDC_MODULE); 48 | } 49 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_camera/xclk.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "camera_common.h" 4 | 5 | esp_err_t camera_enable_out_clock(); 6 | 7 | void camera_disable_out_clock(); 8 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_http_server/ctrl_sock.c: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "ctrl_sock.h" 23 | 24 | /* Control socket, because in some network stacks select can't be woken up any 25 | * other way 26 | */ 27 | int cs_create_ctrl_sock(int port) 28 | { 29 | int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 30 | if (fd < 0) { 31 | return -1; 32 | } 33 | 34 | int ret; 35 | struct sockaddr_in addr; 36 | memset(&addr, 0, sizeof(addr)); 37 | addr.sin_family = AF_INET; 38 | addr.sin_port = htons(port); 39 | inet_aton("127.0.0.1", &addr.sin_addr); 40 | ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); 41 | if (ret < 0) { 42 | close(fd); 43 | return -1; 44 | } 45 | return fd; 46 | } 47 | 48 | void cs_free_ctrl_sock(int fd) 49 | { 50 | close(fd); 51 | } 52 | 53 | int cs_send_to_ctrl_sock(int send_fd, int port, void *data, unsigned int data_len) 54 | { 55 | int ret; 56 | struct sockaddr_in to_addr; 57 | to_addr.sin_family = AF_INET; 58 | to_addr.sin_port = htons(port); 59 | inet_aton("127.0.0.1", &to_addr.sin_addr); 60 | ret = sendto(send_fd, data, data_len, 0, (struct sockaddr *)&to_addr, sizeof(to_addr)); 61 | 62 | if (ret < 0) { 63 | return -1; 64 | } 65 | return ret; 66 | } 67 | 68 | int cs_recv_from_ctrl_sock(int fd, void *data, unsigned int data_len) 69 | { 70 | int ret; 71 | ret = recvfrom(fd, data, data_len, 0, NULL, NULL); 72 | 73 | if (ret < 0) { 74 | return -1; 75 | } 76 | return ret; 77 | } 78 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_http_server/ctrl_sock.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * \file ctrl_sock.h 17 | * \brief Control Socket for select() wakeup 18 | * 19 | * LWIP doesn't allow an easy mechanism to on-demand wakeup a thread 20 | * sleeping on select. This is a common requirement for sending 21 | * control commands to a network server. This control socket API 22 | * facilitates the same. 23 | */ 24 | #ifndef _CTRL_SOCK_H_ 25 | #define _CTRL_SOCK_H_ 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | /** 32 | * @brief Create a control socket 33 | * 34 | * LWIP doesn't allow an easy mechanism to on-demand wakeup a thread 35 | * sleeping on select. This is a common requirement for sending 36 | * control commands to a network server. This control socket API 37 | * facilitates the same. 38 | * 39 | * This API will create a UDP control socket on the specified port. It 40 | * will return a socket descriptor that can then be added to your 41 | * fd_set in select() 42 | * 43 | * @param[in] port the local port on which the control socket will listen 44 | * 45 | * @return - the socket descriptor that can be added to the fd_set in select. 46 | * - an error code if less than zero 47 | */ 48 | int cs_create_ctrl_sock(int port); 49 | 50 | /** 51 | * @brief Free the control socket 52 | * 53 | * This frees up the control socket that was earlier created using 54 | * cs_create_ctrl_sock() 55 | * 56 | * @param[in] fd the socket descriptor associated with this control socket 57 | */ 58 | void cs_free_ctrl_sock(int fd); 59 | 60 | /** 61 | * @brief Send data to control socket 62 | * 63 | * This API sends data to the control socket. If a server is blocked 64 | * on select() with the control socket, this call will wake up that 65 | * server. 66 | * 67 | * @param[in] send_fd the socket for sending ctrl messages 68 | * @param[in] port the port on which the control socket was created 69 | * @param[in] data pointer to a buffer that contains data to send on the socket 70 | * @param[in] data_len the length of the data contained in the buffer pointed to be data 71 | * 72 | * @return - the number of bytes sent to the control socket 73 | * - an error code if less than zero 74 | */ 75 | int cs_send_to_ctrl_sock(int send_fd, int port, void *data, unsigned int data_len); 76 | 77 | /** 78 | * @brief Receive data from control socket 79 | * 80 | * This API receives any data that was sent to the control 81 | * socket. This will be typically called from the server thread to 82 | * process any commands on this socket. 83 | * 84 | * @param[in] fd the socket descriptor of the control socket 85 | * @param[in] data pointer to a buffer that will be used to store 86 | * received from the control socket 87 | * @param[in] data_len the maximum length of the data that can be 88 | * stored in the buffer pointed by data 89 | * 90 | * @return - the number of bytes received from the control socket 91 | * - an error code if less than zero 92 | */ 93 | int cs_recv_from_ctrl_sock(int fd, void *data, unsigned int data_len); 94 | 95 | #ifdef __cplusplus 96 | } 97 | #endif 98 | 99 | #endif /* ! _CTRL_SOCK_H_ */ 100 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_http_server/http_server.h: -------------------------------------------------------------------------------- 1 | #warning http_server.h has been renamed to esp_http_server.h, please update include directives 2 | #include "esp_http_server.h" 3 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_http_server/httpd_sess.c: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include "esp_httpd_priv.h" 22 | 23 | static const char *TAG = "httpd_sess"; 24 | 25 | bool httpd_is_sess_available(struct httpd_data *hd) 26 | { 27 | int i; 28 | for (i = 0; i < hd->config.max_open_sockets; i++) { 29 | if (hd->hd_sd[i].fd == -1) { 30 | return true; 31 | } 32 | } 33 | return false; 34 | } 35 | 36 | static struct sock_db *httpd_sess_get(struct httpd_data *hd, int newfd) 37 | { 38 | int i; 39 | for (i = 0; i < hd->config.max_open_sockets; i++) { 40 | if (hd->hd_sd[i].fd == newfd) { 41 | return &hd->hd_sd[i]; 42 | } 43 | } 44 | return NULL; 45 | } 46 | 47 | esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd) 48 | { 49 | ESP_LOGD(TAG, LOG_FMT("fd = %d"), newfd); 50 | 51 | if (httpd_sess_get(hd, newfd)) { 52 | ESP_LOGE(TAG, LOG_FMT("session already exists with fd = %d"), newfd); 53 | return ESP_FAIL; 54 | } 55 | 56 | int i; 57 | for (i = 0; i < hd->config.max_open_sockets; i++) { 58 | if (hd->hd_sd[i].fd == -1) { 59 | memset(&hd->hd_sd[i], 0, sizeof(hd->hd_sd[i])); 60 | hd->hd_sd[i].fd = newfd; 61 | hd->hd_sd[i].handle = (httpd_handle_t) hd; 62 | hd->hd_sd[i].send_fn = httpd_default_send; 63 | hd->hd_sd[i].recv_fn = httpd_default_recv; 64 | return ESP_OK; 65 | } 66 | } 67 | ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd); 68 | return ESP_FAIL; 69 | } 70 | 71 | void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd) 72 | { 73 | if (handle == NULL) { 74 | return NULL; 75 | } 76 | 77 | struct httpd_data *hd = (struct httpd_data *) handle; 78 | struct sock_db *sd = httpd_sess_get(hd, sockfd); 79 | if (sd == NULL) { 80 | return NULL; 81 | } 82 | 83 | return sd->ctx; 84 | } 85 | 86 | void httpd_sess_set_descriptors(struct httpd_data *hd, 87 | fd_set *fdset, int *maxfd) 88 | { 89 | int i; 90 | *maxfd = -1; 91 | for (i = 0; i < hd->config.max_open_sockets; i++) { 92 | if (hd->hd_sd[i].fd != -1) { 93 | FD_SET(hd->hd_sd[i].fd, fdset); 94 | if (hd->hd_sd[i].fd > *maxfd) { 95 | *maxfd = hd->hd_sd[i].fd; 96 | } 97 | } 98 | } 99 | } 100 | 101 | int httpd_sess_delete(struct httpd_data *hd, int fd) 102 | { 103 | ESP_LOGD(TAG, LOG_FMT("fd = %d"), fd); 104 | int i; 105 | int pre_sess_fd = -1; 106 | for (i = 0; i < hd->config.max_open_sockets; i++) { 107 | if (hd->hd_sd[i].fd == fd) { 108 | hd->hd_sd[i].fd = -1; 109 | if (hd->hd_sd[i].ctx) { 110 | if (hd->hd_sd[i].free_ctx) { 111 | hd->hd_sd[i].free_ctx(hd->hd_sd[i].ctx); 112 | } else { 113 | free(hd->hd_sd[i].ctx); 114 | } 115 | hd->hd_sd[i].ctx = NULL; 116 | hd->hd_sd[i].free_ctx = NULL; 117 | } 118 | break; 119 | } else if (hd->hd_sd[i].fd != -1) { 120 | /* Return the fd just preceding the one being 121 | * deleted so that iterator can continue from 122 | * the correct fd */ 123 | pre_sess_fd = hd->hd_sd[i].fd; 124 | } 125 | } 126 | return pre_sess_fd; 127 | } 128 | 129 | void httpd_sess_init(struct httpd_data *hd) 130 | { 131 | int i; 132 | for (i = 0; i < hd->config.max_open_sockets; i++) { 133 | hd->hd_sd[i].fd = -1; 134 | hd->hd_sd[i].ctx = NULL; 135 | } 136 | } 137 | 138 | bool httpd_sess_pending(struct httpd_data *hd, int fd) 139 | { 140 | struct sock_db *sd = httpd_sess_get(hd, fd); 141 | if (! sd) { 142 | return ESP_FAIL; 143 | } 144 | 145 | return (sd->pending_len != 0); 146 | } 147 | 148 | /* This MUST return ESP_OK on successful execution. If any other 149 | * value is returned, everything related to this socket will be 150 | * cleaned up and the socket will be closed. 151 | */ 152 | esp_err_t httpd_sess_process(struct httpd_data *hd, int newfd) 153 | { 154 | struct sock_db *sd = httpd_sess_get(hd, newfd); 155 | if (! sd) { 156 | return ESP_FAIL; 157 | } 158 | 159 | ESP_LOGD(TAG, LOG_FMT("httpd_req_new")); 160 | if (httpd_req_new(hd, sd) != ESP_OK) { 161 | return ESP_FAIL; 162 | } 163 | ESP_LOGD(TAG, LOG_FMT("httpd_req_delete")); 164 | if (httpd_req_delete(hd) != ESP_OK) { 165 | return ESP_FAIL; 166 | } 167 | ESP_LOGD(TAG, LOG_FMT("success")); 168 | sd->timestamp = httpd_os_get_timestamp(); 169 | return ESP_OK; 170 | } 171 | 172 | esp_err_t httpd_sess_update_timestamp(httpd_handle_t handle, int sockfd) 173 | { 174 | if (handle == NULL) { 175 | return ESP_ERR_INVALID_ARG; 176 | } 177 | 178 | /* Search for the socket database entry */ 179 | struct httpd_data *hd = (struct httpd_data *) handle; 180 | int i; 181 | for (i = 0; i < hd->config.max_open_sockets; i++) { 182 | if (hd->hd_sd[i].fd == sockfd) { 183 | hd->hd_sd[i].timestamp = httpd_os_get_timestamp(); 184 | return ESP_OK; 185 | } 186 | } 187 | return ESP_ERR_NOT_FOUND; 188 | } 189 | 190 | esp_err_t httpd_sess_close_lru(struct httpd_data *hd) 191 | { 192 | int64_t timestamp = INT64_MAX; 193 | int lru_fd = -1; 194 | int i; 195 | for (i = 0; i < hd->config.max_open_sockets; i++) { 196 | /* If a descriptor is -1, there is no need to close any session. 197 | * So, we can return from here, without finding the Least Recently Used 198 | * session 199 | */ 200 | if (hd->hd_sd[i].fd == -1) { 201 | return ESP_OK; 202 | } 203 | if (hd->hd_sd[i].timestamp < timestamp) { 204 | timestamp = hd->hd_sd[i].timestamp; 205 | lru_fd = hd->hd_sd[i].fd; 206 | } 207 | } 208 | ESP_LOGD(TAG, LOG_FMT("fd = %d"), lru_fd); 209 | return httpd_trigger_sess_close(hd, lru_fd); 210 | } 211 | 212 | int httpd_sess_iterate(struct httpd_data *hd, int start_fd) 213 | { 214 | int start_index = 0; 215 | int i; 216 | 217 | if (start_fd != -1) { 218 | /* Take our index to where this fd is stored */ 219 | for (i = 0; i < hd->config.max_open_sockets; i++) { 220 | if (hd->hd_sd[i].fd == start_fd) { 221 | start_index = i + 1; 222 | break; 223 | } 224 | } 225 | } 226 | 227 | for (i = start_index; i < hd->config.max_open_sockets; i++) { 228 | if (hd->hd_sd[i].fd != -1) { 229 | return hd->hd_sd[i].fd; 230 | } 231 | } 232 | return -1; 233 | } 234 | 235 | static void httpd_sess_close(void *arg) 236 | { 237 | struct sock_db *sock_db = (struct sock_db *)arg; 238 | if (sock_db) { 239 | int fd = sock_db->fd; 240 | struct httpd_data *hd = (struct httpd_data *) sock_db->handle; 241 | httpd_sess_delete(hd, fd); 242 | close(fd); 243 | } 244 | } 245 | 246 | esp_err_t httpd_trigger_sess_close(httpd_handle_t handle, int sockfd) 247 | { 248 | if (handle == NULL) { 249 | return ESP_ERR_INVALID_ARG; 250 | } 251 | 252 | struct httpd_data *hd = (struct httpd_data *) handle; 253 | struct sock_db *sock_db = httpd_sess_get(hd, sockfd); 254 | if (sock_db) { 255 | return httpd_queue_work(handle, httpd_sess_close, sock_db); 256 | } 257 | 258 | return ESP_ERR_NOT_FOUND; 259 | } 260 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_http_server/httpd_uri.c: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include "esp_httpd_priv.h" 23 | 24 | static const char *TAG = "httpd_uri"; 25 | 26 | static int httpd_find_uri_handler(struct httpd_data *hd, 27 | const char* uri, 28 | httpd_method_t method) 29 | { 30 | for (int i = 0; i < hd->config.max_uri_handlers; i++) { 31 | if (hd->hd_calls[i]) { 32 | ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri); 33 | if ((hd->hd_calls[i]->method == method) && // First match methods 34 | (strcmp(hd->hd_calls[i]->uri, uri) == 0)) { // Then match uri strings 35 | return i; 36 | } 37 | } 38 | } 39 | return -1; 40 | } 41 | 42 | esp_err_t httpd_register_uri_handler(httpd_handle_t handle, 43 | const httpd_uri_t *uri_handler) 44 | { 45 | if (handle == NULL || uri_handler == NULL) { 46 | return ESP_ERR_INVALID_ARG; 47 | } 48 | 49 | struct httpd_data *hd = (struct httpd_data *) handle; 50 | 51 | /* Make sure another handler with same URI and method 52 | * is not already registered 53 | */ 54 | if (httpd_find_uri_handler(handle, uri_handler->uri, 55 | uri_handler->method) != -1) { 56 | ESP_LOGW(TAG, LOG_FMT("handler %s with method %d already registered"), 57 | uri_handler->uri, uri_handler->method); 58 | return ESP_ERR_HTTPD_HANDLER_EXISTS; 59 | } 60 | 61 | for (int i = 0; i < hd->config.max_uri_handlers; i++) { 62 | if (hd->hd_calls[i] == NULL) { 63 | hd->hd_calls[i] = malloc(sizeof(httpd_uri_t)); 64 | if (hd->hd_calls[i] == NULL) { 65 | /* Failed to allocate memory */ 66 | return ESP_ERR_HTTPD_ALLOC_MEM; 67 | } 68 | 69 | /* Copy URI string */ 70 | hd->hd_calls[i]->uri = strdup(uri_handler->uri); 71 | if (hd->hd_calls[i]->uri == NULL) { 72 | /* Failed to allocate memory */ 73 | free(hd->hd_calls[i]); 74 | return ESP_ERR_HTTPD_ALLOC_MEM; 75 | } 76 | 77 | /* Copy remaining members */ 78 | hd->hd_calls[i]->method = uri_handler->method; 79 | hd->hd_calls[i]->handler = uri_handler->handler; 80 | hd->hd_calls[i]->user_ctx = uri_handler->user_ctx; 81 | ESP_LOGD(TAG, LOG_FMT("[%d] installed %s"), i, uri_handler->uri); 82 | return ESP_OK; 83 | } 84 | ESP_LOGD(TAG, LOG_FMT("[%d] exists %s"), i, hd->hd_calls[i]->uri); 85 | } 86 | ESP_LOGW(TAG, LOG_FMT("no slots left for registering handler")); 87 | return ESP_ERR_HTTPD_HANDLERS_FULL; 88 | } 89 | 90 | esp_err_t httpd_unregister_uri_handler(httpd_handle_t handle, 91 | const char *uri, httpd_method_t method) 92 | { 93 | if (handle == NULL || uri == NULL) { 94 | return ESP_ERR_INVALID_ARG; 95 | } 96 | 97 | struct httpd_data *hd = (struct httpd_data *) handle; 98 | int i = httpd_find_uri_handler(hd, uri, method); 99 | 100 | if (i != -1) { 101 | ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri); 102 | 103 | free((char*)hd->hd_calls[i]->uri); 104 | free(hd->hd_calls[i]); 105 | hd->hd_calls[i] = NULL; 106 | return ESP_OK; 107 | } 108 | ESP_LOGW(TAG, LOG_FMT("handler %s with method %d not found"), uri, method); 109 | return ESP_ERR_NOT_FOUND; 110 | } 111 | 112 | esp_err_t httpd_unregister_uri(httpd_handle_t handle, const char *uri) 113 | { 114 | if (handle == NULL || uri == NULL) { 115 | return ESP_ERR_INVALID_ARG; 116 | } 117 | 118 | struct httpd_data *hd = (struct httpd_data *) handle; 119 | bool found = false; 120 | 121 | for (int i = 0; i < hd->config.max_uri_handlers; i++) { 122 | if ((hd->hd_calls[i] != NULL) && 123 | (strcmp(hd->hd_calls[i]->uri, uri) == 0)) { 124 | ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, uri); 125 | 126 | free((char*)hd->hd_calls[i]->uri); 127 | free(hd->hd_calls[i]); 128 | hd->hd_calls[i] = NULL; 129 | found = true; 130 | } 131 | } 132 | if (!found) { 133 | ESP_LOGW(TAG, LOG_FMT("no handler found for URI %s"), uri); 134 | } 135 | return (found ? ESP_OK : ESP_ERR_NOT_FOUND); 136 | } 137 | 138 | void httpd_unregister_all_uri_handlers(struct httpd_data *hd) 139 | { 140 | for (unsigned i = 0; i < hd->config.max_uri_handlers; i++) { 141 | if (hd->hd_calls[i]) { 142 | ESP_LOGD(TAG, LOG_FMT("[%d] removing %s"), i, hd->hd_calls[i]->uri); 143 | 144 | free((char*)hd->hd_calls[i]->uri); 145 | free(hd->hd_calls[i]); 146 | } 147 | } 148 | } 149 | 150 | /* Alternate implmentation of httpd_find_uri_handler() 151 | * which takes a uri_len field. This is useful when the URI 152 | * string contains extra parameters that are not to be included 153 | * while matching with the registered URI_handler strings 154 | */ 155 | static httpd_uri_t* httpd_find_uri_handler2(httpd_err_resp_t *err, 156 | struct httpd_data *hd, 157 | const char *uri, size_t uri_len, 158 | httpd_method_t method) 159 | { 160 | *err = 0; 161 | for (int i = 0; i < hd->config.max_uri_handlers; i++) { 162 | if (hd->hd_calls[i]) { 163 | ESP_LOGD(TAG, LOG_FMT("[%d] = %s"), i, hd->hd_calls[i]->uri); 164 | if ((strlen(hd->hd_calls[i]->uri) == uri_len) && // First match uri length 165 | (strncmp(hd->hd_calls[i]->uri, uri, uri_len) == 0)) { // Then match uri strings 166 | if (hd->hd_calls[i]->method == method) { // Finally match methods 167 | return hd->hd_calls[i]; 168 | } 169 | /* URI found but method not allowed. 170 | * If URI IS found later then this 171 | * error is to be neglected */ 172 | *err = HTTPD_405_METHOD_NOT_ALLOWED; 173 | } 174 | } 175 | } 176 | if (*err == 0) { 177 | *err = HTTPD_404_NOT_FOUND; 178 | } 179 | return NULL; 180 | } 181 | 182 | esp_err_t httpd_uri(struct httpd_data *hd) 183 | { 184 | httpd_uri_t *uri = NULL; 185 | httpd_req_t *req = &hd->hd_req; 186 | struct http_parser_url *res = &hd->hd_req_aux.url_parse_res; 187 | 188 | /* For conveying URI not found/method not allowed */ 189 | httpd_err_resp_t err = 0; 190 | 191 | ESP_LOGD(TAG, LOG_FMT("request for %s with type %d"), req->uri, req->method); 192 | /* URL parser result contains offset and length of path string */ 193 | if (res->field_set & (1 << UF_PATH)) { 194 | uri = httpd_find_uri_handler2(&err, hd, 195 | req->uri + res->field_data[UF_PATH].off, 196 | res->field_data[UF_PATH].len, 197 | req->method); 198 | } 199 | 200 | /* If URI with method not found, respond with error code */ 201 | if (uri == NULL) { 202 | switch (err) { 203 | case HTTPD_404_NOT_FOUND: 204 | return httpd_resp_send_err(req, HTTPD_404_NOT_FOUND); 205 | case HTTPD_405_METHOD_NOT_ALLOWED: 206 | return httpd_resp_send_err(req, HTTPD_405_METHOD_NOT_ALLOWED); 207 | default: 208 | return ESP_FAIL; 209 | } 210 | } 211 | 212 | /* Attach user context data (passed during URI registration) into request */ 213 | req->user_ctx = uri->user_ctx; 214 | 215 | /* Invoke handler */ 216 | if (uri->handler(req) != ESP_OK) { 217 | /* Handler returns error, this socket should be closed */ 218 | ESP_LOGW(TAG, LOG_FMT("uri handler execution failed")); 219 | return ESP_FAIL; 220 | } 221 | return ESP_OK; 222 | } 223 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/esp_http_server/osal.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef _OSAL_H_ 16 | #define _OSAL_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | #define OS_SUCCESS ESP_OK 29 | #define OS_FAIL ESP_FAIL 30 | 31 | typedef TaskHandle_t othread_t; 32 | 33 | static inline int httpd_os_thread_create(othread_t *thread, 34 | const char *name, uint16_t stacksize, int prio, 35 | void (*thread_routine)(void *arg), void *arg) 36 | { 37 | int ret = xTaskCreate(thread_routine, name, stacksize, arg, prio, thread); 38 | if (ret == pdPASS) { 39 | return OS_SUCCESS; 40 | } 41 | return OS_FAIL; 42 | } 43 | 44 | /* Only self delete is supported */ 45 | static inline void httpd_os_thread_delete() 46 | { 47 | vTaskDelete(xTaskGetCurrentTaskHandle()); 48 | } 49 | 50 | static inline void httpd_os_thread_sleep(int msecs) 51 | { 52 | vTaskDelay(msecs / portTICK_RATE_MS); 53 | } 54 | 55 | static inline int64_t httpd_os_get_timestamp() 56 | { 57 | return esp_timer_get_time(); 58 | } 59 | 60 | static inline othread_t httpd_os_thread_handle() 61 | { 62 | return xTaskGetCurrentTaskHandle(); 63 | } 64 | 65 | #ifdef __cplusplus 66 | } 67 | #endif 68 | 69 | #endif /* ! _OSAL_H_ */ 70 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/img_converters/esp_jpg_decode.c: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "esp_jpg_decode.h" 15 | #include "rom/tjpgd.h" 16 | 17 | #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) 18 | #include "esp32-hal-log.h" 19 | #define TAG "" 20 | #else 21 | #include "esp_log.h" 22 | static const char* TAG = "esp_jpg_decode"; 23 | #endif 24 | 25 | typedef struct { 26 | jpg_scale_t scale; 27 | jpg_reader_cb reader; 28 | jpg_writer_cb writer; 29 | void * arg; 30 | size_t len; 31 | size_t index; 32 | } esp_jpg_decoder_t; 33 | 34 | static const char * jd_errors[] = { 35 | "Succeeded", 36 | "Interrupted by output function", 37 | "Device error or wrong termination of input stream", 38 | "Insufficient memory pool for the image", 39 | "Insufficient stream input buffer", 40 | "Parameter error", 41 | "Data format error", 42 | "Right format but not supported", 43 | "Not supported JPEG standard" 44 | }; 45 | 46 | static uint32_t _jpg_write(JDEC *decoder, void *bitmap, JRECT *rect) 47 | { 48 | uint16_t x = rect->left; 49 | uint16_t y = rect->top; 50 | uint16_t w = rect->right + 1 - x; 51 | uint16_t h = rect->bottom + 1 - y; 52 | uint8_t *data = (uint8_t *)bitmap; 53 | 54 | esp_jpg_decoder_t * jpeg = (esp_jpg_decoder_t *)decoder->device; 55 | 56 | if (jpeg->writer) { 57 | return jpeg->writer(jpeg->arg, x, y, w, h, data); 58 | } 59 | return 0; 60 | } 61 | 62 | static uint32_t _jpg_read(JDEC *decoder, uint8_t *buf, uint32_t len) 63 | { 64 | esp_jpg_decoder_t * jpeg = (esp_jpg_decoder_t *)decoder->device; 65 | if (jpeg->len && len > (jpeg->len - jpeg->index)) { 66 | len = jpeg->len - jpeg->index; 67 | } 68 | if (len) { 69 | len = jpeg->reader(jpeg->arg, jpeg->index, buf, len); 70 | if (!len) { 71 | ESP_LOGE(TAG, "Read Fail at %u/%u", jpeg->index, jpeg->len); 72 | } 73 | jpeg->index += len; 74 | } 75 | return len; 76 | } 77 | 78 | esp_err_t esp_jpg_decode(size_t len, jpg_scale_t scale, jpg_reader_cb reader, jpg_writer_cb writer, void * arg) 79 | { 80 | static uint8_t work[3100]; 81 | JDEC decoder; 82 | esp_jpg_decoder_t jpeg; 83 | 84 | jpeg.len = len; 85 | jpeg.reader = reader; 86 | jpeg.writer = writer; 87 | jpeg.arg = arg; 88 | jpeg.scale = scale; 89 | jpeg.index = 0; 90 | 91 | JRESULT jres = jd_prepare(&decoder, _jpg_read, work, 3100, &jpeg); 92 | if(jres != JDR_OK){ 93 | ESP_LOGE(TAG, "JPG Header Parse Failed! %s", jd_errors[jres]); 94 | return ESP_FAIL; 95 | } 96 | 97 | uint16_t output_width = decoder.width / (1 << (uint8_t)(jpeg.scale)); 98 | uint16_t output_height = decoder.height / (1 << (uint8_t)(jpeg.scale)); 99 | 100 | //output start 101 | writer(arg, 0, 0, output_width, output_height, NULL); 102 | //output write 103 | jres = jd_decomp(&decoder, _jpg_write, (uint8_t)jpeg.scale); 104 | //output end 105 | writer(arg, output_width, output_height, output_width, output_height, NULL); 106 | 107 | if (jres != JDR_OK) { 108 | ESP_LOGE(TAG, "JPG Decompression Failed! %s", jd_errors[jres]); 109 | return ESP_FAIL; 110 | } 111 | //check if all data has been consumed. 112 | if (len && jpeg.index < len) { 113 | _jpg_read(&decoder, NULL, len - jpeg.index); 114 | } 115 | 116 | return ESP_OK; 117 | } 118 | 119 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/img_converters/esp_jpg_decode.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef _ESP_JPG_DECODE_H_ 15 | #define _ESP_JPG_DECODE_H_ 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | #include 22 | #include 23 | #include 24 | #include "esp_err.h" 25 | 26 | typedef enum { 27 | JPG_SCALE_NONE, 28 | JPG_SCALE_2X, 29 | JPG_SCALE_4X, 30 | JPG_SCALE_8X, 31 | JPG_SCALE_MAX = JPG_SCALE_8X 32 | } jpg_scale_t; 33 | 34 | typedef size_t (* jpg_reader_cb)(void * arg, size_t index, uint8_t *buf, size_t len); 35 | typedef bool (* jpg_writer_cb)(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data); 36 | 37 | esp_err_t esp_jpg_decode(size_t len, jpg_scale_t scale, jpg_reader_cb reader, jpg_writer_cb writer, void * arg); 38 | 39 | #ifdef __cplusplus 40 | } 41 | #endif 42 | 43 | #endif /* _ESP_JPG_DECODE_H_ */ 44 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/img_converters/img_converters.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef _IMG_CONVERTERS_H_ 15 | #define _IMG_CONVERTERS_H_ 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | #include 22 | #include 23 | #include 24 | #include "esp_camera.h" 25 | 26 | typedef size_t (* jpg_out_cb)(void * arg, size_t index, const void* data, size_t len); 27 | 28 | /** 29 | * @brief Convert image buffer to JPEG 30 | * 31 | * @param src Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format 32 | * @param src_len Length in bytes of the source buffer 33 | * @param width Width in pixels of the source image 34 | * @param height Height in pixels of the source image 35 | * @param format Format of the source image 36 | * @param quality JPEG quality of the resulting image 37 | * @param cp Callback to be called to write the bytes of the output JPEG 38 | * @param arg Pointer to be passed to the callback 39 | * 40 | * @return true on success 41 | */ 42 | bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg); 43 | 44 | /** 45 | * @brief Convert camera frame buffer to JPEG 46 | * 47 | * @param fb Source camera frame buffer 48 | * @param quality JPEG quality of the resulting image 49 | * @param cp Callback to be called to write the bytes of the output JPEG 50 | * @param arg Pointer to be passed to the callback 51 | * 52 | * @return true on success 53 | */ 54 | bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg); 55 | 56 | /** 57 | * @brief Convert image buffer to JPEG buffer 58 | * 59 | * @param src Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format 60 | * @param src_len Length in bytes of the source buffer 61 | * @param width Width in pixels of the source image 62 | * @param height Height in pixels of the source image 63 | * @param format Format of the source image 64 | * @param quality JPEG quality of the resulting image 65 | * @param out Pointer to be populated with the address of the resulting buffer 66 | * @param out_len Pointer to be populated with the length of the output buffer 67 | * 68 | * @return true on success 69 | */ 70 | bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len); 71 | 72 | /** 73 | * @brief Convert camera frame buffer to JPEG buffer 74 | * 75 | * @param fb Source camera frame buffer 76 | * @param quality JPEG quality of the resulting image 77 | * @param out Pointer to be populated with the address of the resulting buffer 78 | * @param out_len Pointer to be populated with the length of the output buffer 79 | * 80 | * @return true on success 81 | */ 82 | bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len); 83 | 84 | /** 85 | * @brief Convert image buffer to BMP buffer 86 | * 87 | * @param src Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format 88 | * @param src_len Length in bytes of the source buffer 89 | * @param width Width in pixels of the source image 90 | * @param height Height in pixels of the source image 91 | * @param format Format of the source image 92 | * @param out Pointer to be populated with the address of the resulting buffer 93 | * @param out_len Pointer to be populated with the length of the output buffer 94 | * 95 | * @return true on success 96 | */ 97 | bool fmt2bmp(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t ** out, size_t * out_len); 98 | 99 | /** 100 | * @brief Convert camera frame buffer to BMP buffer 101 | * 102 | * @param fb Source camera frame buffer 103 | * @param out Pointer to be populated with the address of the resulting buffer 104 | * @param out_len Pointer to be populated with the length of the output buffer 105 | * 106 | * @return true on success 107 | */ 108 | bool frame2bmp(camera_fb_t * fb, uint8_t ** out, size_t * out_len); 109 | 110 | /** 111 | * @brief Convert image buffer to RGB888 buffer (used for face detection) 112 | * 113 | * @param src Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format 114 | * @param src_len Length in bytes of the source buffer 115 | * @param format Format of the source image 116 | * @param rgb_buf Pointer to the output buffer (width * height * 3) 117 | * 118 | * @return true on success 119 | */ 120 | bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf); 121 | 122 | #ifdef __cplusplus 123 | } 124 | #endif 125 | 126 | #endif /* _IMG_CONVERTERS_H_ */ 127 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/img_converters/jpge.h: -------------------------------------------------------------------------------- 1 | // jpge.h - C++ class for JPEG compression. 2 | // Public domain, Rich Geldreich 3 | // Alex Evans: Added RGBA support, linear memory allocator. 4 | #ifndef JPEG_ENCODER_H 5 | #define JPEG_ENCODER_H 6 | 7 | namespace jpge 8 | { 9 | typedef unsigned char uint8; 10 | typedef signed short int16; 11 | typedef signed int int32; 12 | typedef unsigned short uint16; 13 | typedef unsigned int uint32; 14 | typedef unsigned int uint; 15 | 16 | // JPEG chroma subsampling factors. Y_ONLY (grayscale images) and H2V2 (color images) are the most common. 17 | enum subsampling_t { Y_ONLY = 0, H1V1 = 1, H2V1 = 2, H2V2 = 3 }; 18 | 19 | // JPEG compression parameters structure. 20 | struct params { 21 | inline params() : m_quality(85), m_subsampling(H2V2) { } 22 | 23 | inline bool check() const { 24 | if ((m_quality < 1) || (m_quality > 100)) { 25 | return false; 26 | } 27 | if ((uint)m_subsampling > (uint)H2V2) { 28 | return false; 29 | } 30 | return true; 31 | } 32 | 33 | // Quality: 1-100, higher is better. Typical values are around 50-95. 34 | int m_quality; 35 | 36 | // m_subsampling: 37 | // 0 = Y (grayscale) only 38 | // 1 = H1V1 subsampling (YCbCr 1x1x1, 3 blocks per MCU) 39 | // 2 = H2V1 subsampling (YCbCr 2x1x1, 4 blocks per MCU) 40 | // 3 = H2V2 subsampling (YCbCr 4x1x1, 6 blocks per MCU-- very common) 41 | subsampling_t m_subsampling; 42 | }; 43 | 44 | // Output stream abstract class - used by the jpeg_encoder class to write to the output stream. 45 | // put_buf() is generally called with len==JPGE_OUT_BUF_SIZE bytes, but for headers it'll be called with smaller amounts. 46 | class output_stream { 47 | public: 48 | virtual ~output_stream() { }; 49 | virtual bool put_buf(const void* Pbuf, int len) = 0; 50 | virtual uint get_size() const = 0; 51 | }; 52 | 53 | // Lower level jpeg_encoder class - useful if more control is needed than the above helper functions. 54 | class jpeg_encoder { 55 | public: 56 | jpeg_encoder(); 57 | ~jpeg_encoder(); 58 | 59 | // Initializes the compressor. 60 | // pStream: The stream object to use for writing compressed data. 61 | // params - Compression parameters structure, defined above. 62 | // width, height - Image dimensions. 63 | // channels - May be 1, or 3. 1 indicates grayscale, 3 indicates RGB source data. 64 | // Returns false on out of memory or if a stream write fails. 65 | bool init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params = params()); 66 | 67 | // Call this method with each source scanline. 68 | // width * src_channels bytes per scanline is expected (RGB or Y format). 69 | // You must call with NULL after all scanlines are processed to finish compression. 70 | // Returns false on out of memory or if a stream write fails. 71 | bool process_scanline(const void* pScanline); 72 | 73 | // Deinitializes the compressor, freeing any allocated memory. May be called at any time. 74 | void deinit(); 75 | 76 | private: 77 | jpeg_encoder(const jpeg_encoder &); 78 | jpeg_encoder &operator =(const jpeg_encoder &); 79 | 80 | typedef int32 sample_array_t; 81 | enum { JPGE_OUT_BUF_SIZE = 512 }; 82 | 83 | output_stream *m_pStream; 84 | params m_params; 85 | uint8 m_num_components; 86 | uint8 m_comp_h_samp[3], m_comp_v_samp[3]; 87 | int m_image_x, m_image_y, m_image_bpp, m_image_bpl; 88 | int m_image_x_mcu, m_image_y_mcu; 89 | int m_image_bpl_xlt, m_image_bpl_mcu; 90 | int m_mcus_per_row; 91 | int m_mcu_x, m_mcu_y; 92 | uint8 *m_mcu_lines[16]; 93 | uint8 m_mcu_y_ofs; 94 | sample_array_t m_sample_array[64]; 95 | int16 m_coefficient_array[64]; 96 | 97 | int m_last_dc_val[3]; 98 | uint8 m_out_buf[JPGE_OUT_BUF_SIZE]; 99 | uint8 *m_pOut_buf; 100 | uint m_out_buf_left; 101 | uint32 m_bit_buffer; 102 | uint m_bits_in; 103 | uint8 m_pass_num; 104 | bool m_all_stream_writes_succeeded; 105 | 106 | bool jpg_open(int p_x_res, int p_y_res, int src_channels); 107 | 108 | void flush_output_buffer(); 109 | void put_bits(uint bits, uint len); 110 | 111 | void emit_byte(uint8 i); 112 | void emit_word(uint i); 113 | void emit_marker(int marker); 114 | 115 | void emit_jfif_app0(); 116 | void emit_dqt(); 117 | void emit_sof(); 118 | void emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag); 119 | void emit_dhts(); 120 | void emit_sos(); 121 | 122 | void compute_quant_table(int32 *dst, const int16 *src); 123 | void load_quantized_coefficients(int component_num); 124 | 125 | void load_block_8_8_grey(int x); 126 | void load_block_8_8(int x, int y, int c); 127 | void load_block_16_8(int x, int c); 128 | void load_block_16_8_8(int x, int c); 129 | 130 | void code_coefficients_pass_two(int component_num); 131 | void code_block(int component_num); 132 | 133 | void process_mcu_row(); 134 | bool process_end_of_image(); 135 | void load_mcu(const void* src); 136 | void clear(); 137 | void init(); 138 | }; 139 | 140 | } // namespace jpge 141 | 142 | #endif // JPEG_ENCODER 143 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/img_converters/to_bmp.c: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include 15 | #include 16 | #include "img_converters.h" 17 | #include "esp_spiram.h" 18 | #include "soc/efuse_reg.h" 19 | #include "esp_heap_caps.h" 20 | #include "yuv.h" 21 | #include "sdkconfig.h" 22 | #include "esp_jpg_decode.h" 23 | 24 | #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) 25 | #include "esp32-hal-log.h" 26 | #define TAG "" 27 | #else 28 | #include "esp_log.h" 29 | static const char* TAG = "to_bmp"; 30 | #endif 31 | 32 | static const int BMP_HEADER_LEN = 54; 33 | 34 | typedef struct { 35 | uint32_t filesize; 36 | uint32_t reserved; 37 | uint32_t fileoffset_to_pixelarray; 38 | uint32_t dibheadersize; 39 | int32_t width; 40 | int32_t height; 41 | uint16_t planes; 42 | uint16_t bitsperpixel; 43 | uint32_t compression; 44 | uint32_t imagesize; 45 | uint32_t ypixelpermeter; 46 | uint32_t xpixelpermeter; 47 | uint32_t numcolorspallette; 48 | uint32_t mostimpcolor; 49 | } bmp_header_t; 50 | 51 | typedef struct { 52 | uint16_t width; 53 | uint16_t height; 54 | uint16_t data_offset; 55 | const uint8_t *input; 56 | uint8_t *output; 57 | } rgb_jpg_decoder; 58 | 59 | static void *_malloc(size_t size) 60 | { 61 | return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); 62 | } 63 | 64 | //output buffer and image width 65 | static bool _rgb_write(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data) 66 | { 67 | rgb_jpg_decoder * jpeg = (rgb_jpg_decoder *)arg; 68 | if(!data){ 69 | if(x == 0 && y == 0){ 70 | //write start 71 | jpeg->width = w; 72 | jpeg->height = h; 73 | //if output is null, this is BMP 74 | if(!jpeg->output){ 75 | jpeg->output = (uint8_t *)_malloc((w*h*3)+jpeg->data_offset); 76 | if(!jpeg->output){ 77 | return false; 78 | } 79 | } 80 | } else { 81 | //write end 82 | } 83 | return true; 84 | } 85 | 86 | size_t jw = jpeg->width*3; 87 | size_t t = y * jw; 88 | size_t b = t + (h * jw); 89 | size_t l = x * 3; 90 | uint8_t *out = jpeg->output+jpeg->data_offset; 91 | uint8_t *o = out; 92 | size_t iy, ix; 93 | 94 | w = w * 3; 95 | 96 | for(iy=t; iyinput + index, len); 114 | } 115 | return len; 116 | } 117 | 118 | static bool jpg2rgb888(const uint8_t *src, size_t src_len, uint8_t * out, jpg_scale_t scale) 119 | { 120 | rgb_jpg_decoder jpeg; 121 | jpeg.width = 0; 122 | jpeg.height = 0; 123 | jpeg.input = src; 124 | jpeg.output = out; 125 | jpeg.data_offset = 0; 126 | 127 | if(esp_jpg_decode(src_len, scale, _jpg_read, _rgb_write, (void*)&jpeg) != ESP_OK){ 128 | return false; 129 | } 130 | return true; 131 | } 132 | 133 | bool jpg2bmp(const uint8_t *src, size_t src_len, uint8_t ** out, size_t * out_len) 134 | { 135 | 136 | rgb_jpg_decoder jpeg; 137 | jpeg.width = 0; 138 | jpeg.height = 0; 139 | jpeg.input = src; 140 | jpeg.output = NULL; 141 | jpeg.data_offset = BMP_HEADER_LEN; 142 | 143 | if(esp_jpg_decode(src_len, JPG_SCALE_NONE, _jpg_read, _rgb_write, (void*)&jpeg) != ESP_OK){ 144 | return false; 145 | } 146 | 147 | size_t output_size = jpeg.width*jpeg.height*3; 148 | 149 | jpeg.output[0] = 'B'; 150 | jpeg.output[1] = 'M'; 151 | bmp_header_t * bitmap = (bmp_header_t*)&jpeg.output[2]; 152 | bitmap->reserved = 0; 153 | bitmap->filesize = output_size+BMP_HEADER_LEN; 154 | bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN; 155 | bitmap->dibheadersize = 40; 156 | bitmap->width = jpeg.width; 157 | bitmap->height = -jpeg.height;//set negative for top to bottom 158 | bitmap->planes = 1; 159 | bitmap->bitsperpixel = 24; 160 | bitmap->compression = 0; 161 | bitmap->imagesize = output_size; 162 | bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI 163 | bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI 164 | bitmap->numcolorspallette = 0; 165 | bitmap->mostimpcolor = 0; 166 | 167 | *out = jpeg.output; 168 | *out_len = output_size+BMP_HEADER_LEN; 169 | 170 | return true; 171 | } 172 | 173 | bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf) 174 | { 175 | int pix_count = 0; 176 | if(format == PIXFORMAT_JPEG) { 177 | return jpg2rgb888(src_buf, src_len, rgb_buf, JPG_SCALE_NONE); 178 | } else if(format == PIXFORMAT_RGB888) { 179 | memcpy(rgb_buf, src_buf, src_len); 180 | } else if(format == PIXFORMAT_RGB565) { 181 | int i; 182 | uint8_t hb, lb; 183 | pix_count = src_len / 2; 184 | for(i=0; i> 3; 189 | *rgb_buf++ = hb & 0xF8; 190 | } 191 | } else if(format == PIXFORMAT_GRAYSCALE) { 192 | int i; 193 | uint8_t b; 194 | pix_count = src_len; 195 | for(i=0; ireserved = 0; 247 | bitmap->filesize = out_size; 248 | bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN; 249 | bitmap->dibheadersize = 40; 250 | bitmap->width = width; 251 | bitmap->height = -height;//set negative for top to bottom 252 | bitmap->planes = 1; 253 | bitmap->bitsperpixel = 24; 254 | bitmap->compression = 0; 255 | bitmap->imagesize = pix_count * 3; 256 | bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI 257 | bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI 258 | bitmap->numcolorspallette = 0; 259 | bitmap->mostimpcolor = 0; 260 | 261 | uint8_t * rgb_buf = out_buf + BMP_HEADER_LEN; 262 | uint8_t * src_buf = src; 263 | 264 | 265 | //convert data to RGB888 266 | if(format == PIXFORMAT_RGB888) { 267 | memcpy(rgb_buf, src_buf, pix_count*3); 268 | } else if(format == PIXFORMAT_RGB565) { 269 | int i; 270 | uint8_t hb, lb; 271 | for(i=0; i> 3; 276 | *rgb_buf++ = hb & 0xF8; 277 | } 278 | } else if(format == PIXFORMAT_GRAYSCALE) { 279 | int i; 280 | uint8_t b; 281 | for(i=0; ibuf, fb->len, fb->width, fb->height, fb->format, out, out_len); 316 | } 317 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/img_converters/to_jpg.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include 15 | #include 16 | #include "esp_spiram.h" 17 | #include "esp_attr.h" 18 | #include "soc/efuse_reg.h" 19 | #include "esp_heap_caps.h" 20 | #include "esp_camera.h" 21 | #include "img_converters.h" 22 | #include "jpge.h" 23 | #include "yuv.h" 24 | 25 | #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) 26 | #include "esp32-hal-log.h" 27 | #define TAG "" 28 | #else 29 | #include "esp_log.h" 30 | static const char* TAG = "to_bmp"; 31 | #endif 32 | 33 | static void *_malloc(size_t size) 34 | { 35 | void * res = malloc(size); 36 | if(res) { 37 | return res; 38 | } 39 | return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); 40 | } 41 | 42 | static IRAM_ATTR void convert_line_format(uint8_t * src, pixformat_t format, uint8_t * dst, size_t width, size_t in_channels, size_t line) 43 | { 44 | int i=0, o=0, l=0; 45 | if(format == PIXFORMAT_GRAYSCALE) { 46 | memcpy(dst, src + line * width, width); 47 | } else if(format == PIXFORMAT_RGB888) { 48 | l = width * 3; 49 | src += l * line; 50 | for(i=0; i> 3; 61 | dst[o++] = (src[i+1] & 0x1F) << 3; 62 | } 63 | } else if(format == PIXFORMAT_YUV422) { 64 | uint8_t y0, y1, u, v; 65 | uint8_t r, g, b; 66 | l = width * 2; 67 | src += l * line; 68 | for(i=0; i 100) { 100 | quality = 100; 101 | } 102 | 103 | jpge::params comp_params = jpge::params(); 104 | comp_params.m_subsampling = subsampling; 105 | comp_params.m_quality = quality; 106 | 107 | jpge::jpeg_encoder dst_image; 108 | 109 | if (!dst_image.init(dst_stream, width, height, num_channels, comp_params)) { 110 | ESP_LOGE(TAG, "JPG encoder init failed"); 111 | return false; 112 | } 113 | 114 | uint8_t* line = (uint8_t*)_malloc(width * num_channels); 115 | if(!line) { 116 | ESP_LOGE(TAG, "Scan line malloc failed"); 117 | return false; 118 | } 119 | 120 | for (int i = 0; i < height; i++) { 121 | convert_line_format(src, format, line, width, num_channels, i); 122 | if (!dst_image.process_scanline(line)) { 123 | ESP_LOGE(TAG, "JPG process line %u failed", i); 124 | free(line); 125 | return false; 126 | } 127 | } 128 | free(line); 129 | 130 | if (!dst_image.process_scanline(NULL)) { 131 | ESP_LOGE(TAG, "JPG image finish failed"); 132 | return false; 133 | } 134 | dst_image.deinit(); 135 | return true; 136 | } 137 | 138 | class callback_stream : public jpge::output_stream { 139 | protected: 140 | jpg_out_cb ocb; 141 | void * oarg; 142 | size_t index; 143 | 144 | public: 145 | callback_stream(jpg_out_cb cb, void * arg) : ocb(cb), oarg(arg), index(0) { } 146 | virtual ~callback_stream() { } 147 | virtual bool put_buf(const void* data, int len) 148 | { 149 | index += ocb(oarg, index, data, len); 150 | return true; 151 | } 152 | virtual size_t get_size() const 153 | { 154 | return index; 155 | } 156 | }; 157 | 158 | bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg) 159 | { 160 | callback_stream dst_stream(cb, arg); 161 | return convert_image(src, width, height, format, quality, &dst_stream); 162 | } 163 | 164 | bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg) 165 | { 166 | return fmt2jpg_cb(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, cb, arg); 167 | } 168 | 169 | 170 | 171 | class memory_stream : public jpge::output_stream { 172 | protected: 173 | uint8_t *out_buf; 174 | size_t max_len, index; 175 | 176 | public: 177 | memory_stream(void *pBuf, uint buf_size) : out_buf(static_cast(pBuf)), max_len(buf_size), index(0) { } 178 | 179 | virtual ~memory_stream() { } 180 | 181 | virtual bool put_buf(const void* pBuf, int len) 182 | { 183 | if (!pBuf) { 184 | //end of image 185 | return true; 186 | } 187 | if ((size_t)len > (max_len - index)) { 188 | ESP_LOGW(TAG, "JPG output overflow: %d bytes", len - (max_len - index)); 189 | len = max_len - index; 190 | } 191 | if (len) { 192 | memcpy(out_buf + index, pBuf, len); 193 | index += len; 194 | } 195 | return true; 196 | } 197 | 198 | virtual size_t get_size() const 199 | { 200 | return index; 201 | } 202 | }; 203 | 204 | bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len) 205 | { 206 | //todo: allocate proper buffer for holding JPEG data 207 | //this should be enough for CIF frame size 208 | int jpg_buf_len = 24*1024; 209 | 210 | 211 | uint8_t * jpg_buf = (uint8_t *)_malloc(jpg_buf_len); 212 | if(jpg_buf == NULL) { 213 | ESP_LOGE(TAG, "JPG buffer malloc failed"); 214 | return false; 215 | } 216 | memory_stream dst_stream(jpg_buf, jpg_buf_len); 217 | 218 | if(!convert_image(src, width, height, format, quality, &dst_stream)) { 219 | free(jpg_buf); 220 | return false; 221 | } 222 | 223 | *out = jpg_buf; 224 | *out_len = dst_stream.get_size(); 225 | return true; 226 | } 227 | 228 | bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len) 229 | { 230 | return fmt2jpg(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, out, out_len); 231 | } 232 | -------------------------------------------------------------------------------- /esp32-camera-firmware/lib/img_converters/yuv.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #ifndef _CONVERSIONS_YUV_H_ 15 | #define _CONVERSIONS_YUV_H_ 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | #include 22 | 23 | void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b); 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | 29 | #endif /* _CONVERSIONS_YUV_H_ */ 30 | -------------------------------------------------------------------------------- /esp32-camera-firmware/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | env_default = esp32cam 13 | 14 | [common] 15 | monitor_speed = 115200 16 | platform = espressif32 17 | board = esp32dev 18 | framework = espidf 19 | 20 | [env:esp32cam] 21 | build_flags = -DCONFIG_MODEL=2 22 | platform = ${common.platform} 23 | board = ${common.board} 24 | framework = ${common.framework} 25 | monitor_speed = ${common.monitor_speed} 26 | 27 | [env:m5cam] 28 | build_flags = -DCONFIG_MODEL=1 29 | platform = ${common.platform} 30 | board = ${common.board} 31 | framework = ${common.framework} 32 | monitor_speed = ${common.monitor_speed} -------------------------------------------------------------------------------- /esp32-camera-firmware/src/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "ESP32 Camera Demo Configuration" 2 | 3 | config WIFI_SSID 4 | string "WiFi SSID" 5 | default "" 6 | help 7 | SSID (network name) for the demo to connect to. 8 | 9 | config WIFI_PASSWORD 10 | string "WiFi Password" 11 | default "" 12 | help 13 | Password for your network. 14 | 15 | config XCLK_FREQ 16 | int "XCLK Frequency" 17 | default "20000000" 18 | help 19 | The XCLK Frequency in Herz. 20 | 21 | 22 | menu "Pin Configuration" 23 | config D0 24 | int "D0" 25 | default "5" 26 | config D1 27 | int "D1" 28 | default "18" 29 | config D2 30 | int "D2" 31 | default "19" 32 | config D3 33 | int "D3" 34 | default "21" 35 | config D4 36 | int "D4" 37 | default "36" 38 | config D5 39 | int "D5" 40 | default "39" 41 | config D6 42 | int "D6" 43 | default "34" 44 | config D7 45 | int "D7" 46 | default "35" 47 | config XCLK 48 | int "XCLK" 49 | default "0" 50 | config PCLK 51 | int "PCLK" 52 | default "22" 53 | config VSYNC 54 | int "VSYNC" 55 | default "25" 56 | config HREF 57 | int "HREF" 58 | default "23" 59 | config SDA 60 | int "SDA" 61 | default "26" 62 | config SCL 63 | int "SCL" 64 | default "27" 65 | config RESET 66 | int "RESET" 67 | default "32" 68 | endmenu 69 | 70 | config QR_RECOGNIZE 71 | bool "QR recognize Support" 72 | default y 73 | help 74 | Enable this option if you want to recognize qr. 75 | endmenu 76 | 77 | -------------------------------------------------------------------------------- /esp32-camera-firmware/src/app_main.c: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "freertos/FreeRTOS.h" 20 | #include "freertos/task.h" 21 | #include "freertos/semphr.h" 22 | #include "freertos/event_groups.h" 23 | 24 | #include "esp_camera.h" 25 | #include "esp_system.h" 26 | #include "esp_wifi.h" 27 | #include "mdns.h" 28 | #include "esp_event_loop.h" 29 | #include "esp_log.h" 30 | #include "esp_err.h" 31 | #include "esp_http_server.h" 32 | #include "nvs_flash.h" 33 | #include "driver/gpio.h" 34 | 35 | static esp_err_t jpg_http_handler(httpd_req_t *req); 36 | static esp_err_t event_handler(void *ctx, system_event_t *event); 37 | static void initialise_wifi(void); 38 | static void start_mdns_service(void); 39 | 40 | static const char *TAG = "camera_demo"; 41 | 42 | EventGroupHandle_t s_wifi_event_group; 43 | static const int CONNECTED_BIT = BIT0; 44 | static ip4_addr_t s_ip_addr; 45 | 46 | #define CAMERA_PIXEL_FORMAT CAMERA_PF_JPEG 47 | #if CONFIG_MODEL == CONFIG_MODEL_ESP32CAM 48 | // #define CAMERA_FRAME_SIZE FRAMESIZE_UXGA // Full 2MP 49 | #define CAMERA_FRAME_SIZE FRAMESIZE_SVGA // No PSRAM 50 | #else 51 | #define CAMERA_FRAME_SIZE FRAMESIZE_SVGA // No PSRAM 52 | #endif 53 | 54 | void app_main() 55 | { 56 | 57 | esp_log_level_set("wifi", ESP_LOG_WARN); 58 | esp_log_level_set("gpio", ESP_LOG_WARN); 59 | esp_err_t err = nvs_flash_init(); 60 | if (err != ESP_OK) 61 | { 62 | ESP_ERROR_CHECK(nvs_flash_erase()); 63 | ESP_ERROR_CHECK(nvs_flash_init()); 64 | } 65 | 66 | camera_config_t camera_config = { 67 | .pin_reset = CONFIG_RESET, 68 | .pin_pwdn = CONFIG_PWDN, 69 | .pin_xclk = CONFIG_XCLK, 70 | .pin_sscb_sda = CONFIG_SDA, 71 | .pin_sscb_scl = CONFIG_SCL, 72 | 73 | .pin_d0 = CONFIG_D0, 74 | .pin_d1 = CONFIG_D1, 75 | .pin_d2 = CONFIG_D2, 76 | .pin_d3 = CONFIG_D3, 77 | .pin_d4 = CONFIG_D4, 78 | .pin_d5 = CONFIG_D5, 79 | .pin_d6 = CONFIG_D6, 80 | .pin_d7 = CONFIG_D7, 81 | .pin_vsync = CONFIG_VSYNC, 82 | .pin_href = CONFIG_HREF, 83 | .pin_pclk = CONFIG_PCLK, 84 | 85 | .xclk_freq_hz = CONFIG_XCLK_FREQ, 86 | .ledc_channel = LEDC_CHANNEL_0, 87 | .ledc_timer = LEDC_TIMER_0, 88 | 89 | .pixel_format = PIXFORMAT_JPEG, 90 | .frame_size = CAMERA_FRAME_SIZE, 91 | .jpeg_quality = 12, 92 | .fb_count = 1}; 93 | 94 | err = esp_camera_init(&camera_config); 95 | if (err != ESP_OK) 96 | { 97 | ESP_LOGE(TAG, "Camera init failed with error 0x%x", err); 98 | return; 99 | } 100 | // databuf = (char *) malloc(BUF_SIZE); 101 | initialise_wifi(); 102 | 103 | start_mdns_service(); 104 | 105 | httpd_handle_t server = NULL; 106 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 107 | ESP_ERROR_CHECK(httpd_start(&server, &config)); 108 | 109 | httpd_uri_t jpg_uri = { 110 | .uri = "/jpg", 111 | .method = HTTP_GET, 112 | .handler = jpg_http_handler, 113 | .user_ctx = NULL}; 114 | 115 | ESP_ERROR_CHECK(httpd_register_uri_handler(server, &jpg_uri)); 116 | ESP_LOGI(TAG, "Open http://" IPSTR "/jpg for single image/jpg image", IP2STR(&s_ip_addr)); 117 | ESP_LOGI(TAG, "Free heap: %u", xPortGetFreeHeapSize()); 118 | ESP_LOGI(TAG, "Camera demo ready"); 119 | } 120 | 121 | typedef struct 122 | { 123 | httpd_req_t *req; 124 | size_t len; 125 | } jpg_chunking_t; 126 | 127 | static size_t jpg_encode_stream(void *arg, size_t index, const void *data, size_t len) 128 | { 129 | jpg_chunking_t *j = (jpg_chunking_t *)arg; 130 | if (!index) 131 | { 132 | j->len = 0; 133 | } 134 | if (httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK) 135 | { 136 | return 0; 137 | } 138 | j->len += len; 139 | return len; 140 | } 141 | 142 | esp_err_t jpg_http_handler(httpd_req_t *req) 143 | { 144 | camera_fb_t *fb = NULL; 145 | esp_err_t res = ESP_OK; 146 | size_t fb_len = 0; 147 | int64_t fr_start = esp_timer_get_time(); 148 | 149 | fb = esp_camera_fb_get(); 150 | if (!fb) 151 | { 152 | ESP_LOGE(TAG, "Camera capture failed"); 153 | httpd_resp_send_500(req); 154 | return ESP_FAIL; 155 | } 156 | res = httpd_resp_set_type(req, "image/jpeg"); 157 | if (res == ESP_OK) 158 | { 159 | res = httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); 160 | } 161 | 162 | if (res == ESP_OK) 163 | { 164 | if (fb->format == PIXFORMAT_JPEG) 165 | { 166 | fb_len = fb->len; 167 | res = httpd_resp_send(req, (const char *)fb->buf, fb->len); 168 | } 169 | else 170 | { 171 | jpg_chunking_t jchunk = {req, 0}; 172 | res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk) ? ESP_OK : ESP_FAIL; 173 | httpd_resp_send_chunk(req, NULL, 0); 174 | fb_len = jchunk.len; 175 | } 176 | } 177 | esp_camera_fb_return(fb); 178 | int64_t fr_end = esp_timer_get_time(); 179 | ESP_LOGI(TAG, "JPG: %uKB %ums", (uint32_t)(fb_len / 1024), (uint32_t)((fr_end - fr_start) / 1000)); 180 | return res; 181 | } 182 | 183 | static esp_err_t event_handler(void *ctx, system_event_t *event) 184 | { 185 | switch (event->event_id) 186 | { 187 | case SYSTEM_EVENT_STA_START: 188 | esp_wifi_connect(); 189 | break; 190 | case SYSTEM_EVENT_STA_GOT_IP: 191 | xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT); 192 | s_ip_addr = event->event_info.got_ip.ip_info.ip; 193 | break; 194 | case SYSTEM_EVENT_STA_DISCONNECTED: 195 | esp_wifi_connect(); 196 | xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT); 197 | break; 198 | default: 199 | break; 200 | } 201 | return ESP_OK; 202 | } 203 | 204 | void start_mdns_service() 205 | { 206 | //initialize mDNS service 207 | esp_err_t err = mdns_init(); 208 | if (err) 209 | { 210 | printf("MDNS Init failed: %d\n", err); 211 | return; 212 | } 213 | 214 | uint64_t chipId = 0LL; 215 | esp_efuse_mac_get_default((uint8_t *)(&chipId)); 216 | char id[4]; 217 | sprintf(id, "%04x", (uint16_t)(chipId >> 32)); 218 | 219 | char name[18] = "indoor-camera-"; 220 | strcat(name, id); 221 | 222 | mdns_hostname_set(name); 223 | mdns_service_add(NULL, "_camera", "_tcp", 80, NULL, 0); 224 | mdns_service_instance_name_set("_camera", "_tcp", name); 225 | 226 | ESP_LOGI(TAG, "MDNS domain: %s", name); 227 | } 228 | 229 | static void initialise_wifi(void) 230 | { 231 | tcpip_adapter_init(); 232 | s_wifi_event_group = xEventGroupCreate(); 233 | ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); 234 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 235 | ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 236 | // ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); 237 | wifi_config_t wifi_config = { 238 | .sta = { 239 | .ssid = CONFIG_WIFI_SSID, 240 | .password = CONFIG_WIFI_PASSWORD, 241 | }, 242 | }; 243 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); 244 | ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); 245 | ESP_ERROR_CHECK(esp_wifi_start()); 246 | ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); 247 | ESP_LOGI(TAG, "Connecting to \"%s\"", wifi_config.sta.ssid); 248 | xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); 249 | ESP_LOGI(TAG, "Connected"); 250 | } 251 | -------------------------------------------------------------------------------- /esp32-camera-firmware/src/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Main Makefile. This is basically the same as a component makefile. 3 | # 4 | # This Makefile should, at the very least, just include $(SDK_PATH)/make/component.mk. By default, 5 | # this will take the sources in the src/ directory, compile them and link them into 6 | # lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, 7 | # please read the SDK documents if you need to do this. 8 | # 9 | 10 | -------------------------------------------------------------------------------- /esp32-camera-firmware/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "rules": "database.rules.json" 4 | }, 5 | "functions": { 6 | "predeploy": [ 7 | "npm --prefix \"$RESOURCE_DIR\" run lint" 8 | ] 9 | }, 10 | "hosting": { 11 | "public": "public", 12 | "ignore": [ 13 | "firebase.json", 14 | "**/.*", 15 | "**/node_modules/**" 16 | ], 17 | "rewrites": [ 18 | { 19 | "source": "**", 20 | "destination": "/index.html" 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /functions/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | // Required for certain syntax usages 4 | "ecmaVersion": 6 5 | }, 6 | "plugins": [ 7 | "promise" 8 | ], 9 | "extends": "eslint:recommended", 10 | "rules": { 11 | // Removed rule "disallow the use of console" from recommended eslint rules 12 | "no-console": "off", 13 | 14 | // Removed rule "disallow multiple spaces in regular expressions" from recommended eslint rules 15 | "no-regex-spaces": "off", 16 | 17 | // Removed rule "disallow the use of debugger" from recommended eslint rules 18 | "no-debugger": "off", 19 | 20 | // Removed rule "disallow unused variables" from recommended eslint rules 21 | "no-unused-vars": "off", 22 | 23 | // Removed rule "disallow mixed spaces and tabs for indentation" from recommended eslint rules 24 | "no-mixed-spaces-and-tabs": "off", 25 | 26 | // Removed rule "disallow the use of undeclared variables unless mentioned in /*global */ comments" from recommended eslint rules 27 | "no-undef": "off", 28 | 29 | // Warn against template literal placeholder syntax in regular strings 30 | "no-template-curly-in-string": 1, 31 | 32 | // Warn if return statements do not either always or never specify values 33 | "consistent-return": 1, 34 | 35 | // Warn if no return statements in callbacks of array methods 36 | "array-callback-return": 1, 37 | 38 | // Require the use of === and !== 39 | "eqeqeq": 2, 40 | 41 | // Disallow the use of alert, confirm, and prompt 42 | "no-alert": 2, 43 | 44 | // Disallow the use of arguments.caller or arguments.callee 45 | "no-caller": 2, 46 | 47 | // Disallow null comparisons without type-checking operators 48 | "no-eq-null": 2, 49 | 50 | // Disallow the use of eval() 51 | "no-eval": 2, 52 | 53 | // Warn against extending native types 54 | "no-extend-native": 1, 55 | 56 | // Warn against unnecessary calls to .bind() 57 | "no-extra-bind": 1, 58 | 59 | // Warn against unnecessary labels 60 | "no-extra-label": 1, 61 | 62 | // Disallow leading or trailing decimal points in numeric literals 63 | "no-floating-decimal": 2, 64 | 65 | // Warn against shorthand type conversions 66 | "no-implicit-coercion": 1, 67 | 68 | // Warn against function declarations and expressions inside loop statements 69 | "no-loop-func": 1, 70 | 71 | // Disallow new operators with the Function object 72 | "no-new-func": 2, 73 | 74 | // Warn against new operators with the String, Number, and Boolean objects 75 | "no-new-wrappers": 1, 76 | 77 | // Disallow throwing literals as exceptions 78 | "no-throw-literal": 2, 79 | 80 | // Require using Error objects as Promise rejection reasons 81 | "prefer-promise-reject-errors": 2, 82 | 83 | // Enforce “for” loop update clause moving the counter in the right direction 84 | "for-direction": 2, 85 | 86 | // Enforce return statements in getters 87 | "getter-return": 2, 88 | 89 | // Disallow await inside of loops 90 | "no-await-in-loop": 2, 91 | 92 | // Disallow comparing against -0 93 | "no-compare-neg-zero": 2, 94 | 95 | // Warn against catch clause parameters from shadowing variables in the outer scope 96 | "no-catch-shadow": 1, 97 | 98 | // Disallow identifiers from shadowing restricted names 99 | "no-shadow-restricted-names": 2, 100 | 101 | // Enforce return statements in callbacks of array methods 102 | "callback-return": 2, 103 | 104 | // Require error handling in callbacks 105 | "handle-callback-err": 2, 106 | 107 | // Warn against string concatenation with __dirname and __filename 108 | "no-path-concat": 1, 109 | 110 | // Prefer using arrow functions for callbacks 111 | "prefer-arrow-callback": 1, 112 | 113 | // Return inside each then() to create readable and reusable Promise chains. 114 | // Forces developers to return console logs and http calls in promises. 115 | "promise/always-return": 2, 116 | 117 | //Enforces the use of catch() on un-returned promises 118 | "promise/catch-or-return": 2, 119 | 120 | // Warn against nested then() or catch() statements 121 | "promise/no-nesting": 1 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /functions/index.js: -------------------------------------------------------------------------------- 1 | const functions = require('firebase-functions') 2 | const admin = require('firebase-admin') 3 | 4 | admin.initializeApp() 5 | 6 | const db = admin.database() 7 | 8 | exports.processTelemetry = functions 9 | .pubsub 10 | .topic('telemetry') 11 | .onPublish( handleMessage('data') ) 12 | 13 | exports.processState = functions 14 | .pubsub 15 | .topic('state') 16 | .onPublish( handleMessage('meta') ) 17 | 18 | function handleMessage( topic ) { 19 | return ( message, context ) => { 20 | const attributes = message.attributes 21 | const payload = message.json 22 | 23 | const deviceId = attributes['deviceId'] 24 | 25 | const data = Object.assign({}, payload, { 26 | updated: context.timestamp 27 | }) 28 | 29 | return db.ref(`/devices/${deviceId}/${topic}`).update(data) 30 | } 31 | } -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "scripts": { 5 | "lint": "eslint .", 6 | "serve": "firebase serve --only functions", 7 | "shell": "firebase functions:shell", 8 | "start": "npm run shell", 9 | "deploy": "firebase deploy --only functions", 10 | "logs": "firebase functions:log" 11 | }, 12 | "engines": { 13 | "node": "8" 14 | }, 15 | "dependencies": { 16 | "firebase-admin": "~6.0.0", 17 | "firebase-functions": "^2.0.3" 18 | }, 19 | "devDependencies": { 20 | "eslint": "^4.12.0", 21 | "eslint-plugin-promise": "^3.6.0" 22 | }, 23 | "private": true 24 | } 25 | -------------------------------------------------------------------------------- /generate_key_pair.sh: -------------------------------------------------------------------------------- 1 | openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem 2 | openssl ec -in ec_private.pem -pubout -out ec_public.pem 3 | openssl ec -in ec_private.pem -noout -text -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 61 | IoT Cloud Monitor 62 | 63 | 64 |
65 | 66 | 67 | IoT Edge - Cloud Monitor 68 | Create Room 69 | 70 | 71 | 76 | 77 |
78 | {{room.name}} 79 | 80 | No devices assgined. 81 | 82 |
83 | 84 | 85 | 86 | {{device.icon}} 87 | 88 |
{{device.name}}
89 |
90 | [{{device.meta.status}}] {{device.updated}} 91 |
92 |
93 | 94 | 95 | 96 | {{c}} ({{device.classes[c]}}) 97 | 98 | 99 | Nothing found. 100 | 101 | 102 | 103 | 104 | 105 | {{room.name}} 106 | 107 | 108 | Remove from room 109 | 110 | 111 | 112 | 113 | 114 | 115 | Open local monitor 116 | open_in_new 117 | 118 | 119 |
120 |
121 |
122 | 130 | 131 |
132 |
133 |
134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /register_device.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . set_env_vars.sh $1 4 | 5 | DEVICE=$2 6 | 7 | # Create device 8 | gcloud iot devices create $DEVICE \ 9 | --region=$LOCATION \ 10 | --registry=$REGISTRY 11 | 12 | # Bind device to gateway 13 | gcloud beta iot devices gateways "bind" \ 14 | --gateway=$GATEWAY_ID \ 15 | --gateway-region=$LOCATION \ 16 | --gateway-registry=$REGISTRY \ 17 | --device=$DEVICE \ 18 | --device-region=$LOCATION \ 19 | --device-registry=$REGISTRY -------------------------------------------------------------------------------- /set_env_vars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export LOCATION="us-central1" 4 | export GATEWAY_ID="gw-mark-one" 5 | export REGISTRY="iot-edge-registry" 6 | export PUBSUB_TOPIC_EVENTS="telemetry" 7 | export PUBSUB_TOPIC_STATE="state" 8 | 9 | if [ -z "$GCLOUD_PROJECT" ]; then 10 | printf "\nOnce you've logged into your project, enter the project id below." 11 | GCLOUD_PROJECT="$1" 12 | if [ -z "$1" ]; then 13 | echo 14 | read -p 'Please enter project id: ' GCLOUD_PROJECT 15 | echo 16 | fi 17 | if [ -z "$GCLOUD_PROJECT" ]; then 18 | cat<