├── .github └── FUNDING.yml ├── ESP32CameraRobotOldVer ├── ESP32CameraRobotOldVer.ino ├── app_httpd.cpp ├── camera_index.h ├── camera_pins.h └── index.ov2640.html ├── ESP32VideoControllerM5Stack ├── ESP32VideoControllerM5Stack.ino └── MjpegClass.h ├── ESP32VideoControllerOdroidGo ├── ESP32VideoControllerOdroidGo.ino └── MjpegClass.h ├── ESP32VideoControllerOldVer └── ESP32VideoControllerOldVer.ino ├── ESP32VideoControllerTWatch ├── ESP32VideoControllerTWatch.ino └── MjpegClass.h ├── ESP32VideoViewer ├── ESP32VideoViewer.ino └── MjpegClass.h ├── ESP32VideoViewerSIMD ├── ESP32VideoViewerSIMD.ino └── MjpegClass.h ├── README.md └── SerialServoTester └── SerialServoTester.ino /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: moononournation 2 | -------------------------------------------------------------------------------- /ESP32CameraRobotOldVer/ESP32CameraRobotOldVer.ino: -------------------------------------------------------------------------------- 1 | #define SOFTAP 2 | #ifdef SOFTAP 3 | const char *ssid = "ESP32CameraRobot"; 4 | const char *password = ""; 5 | const int channel = 1; 6 | #else 7 | const char *ssid = "YourAP"; 8 | const char *password = "PleaseInputYourPasswordHere"; 9 | #endif 10 | 11 | // Select camera model 12 | // #define CAMERA_MODEL_WROVER_KIT 13 | // #define CAMERA_MODEL_ESP_EYE 14 | // #define CAMERA_MODEL_ESP32_CAM 15 | // #define CAMERA_MODEL_M5CAM 16 | #define CAMERA_MODEL_M5CAM_PSRAM 17 | // #define CAMERA_MODEL_M5CAM_PSRAM_WIDE 18 | // #define CAMERA_MODEL_AI_THINKER 19 | // #define CAMERA_MODEL_JSZWY_CYIS 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #define SERVO_FREQ 60 // Analog servos run at ~60 Hz updates 27 | #include 28 | #include 29 | 30 | #include "camera_pins.h" 31 | 32 | static Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(); 33 | 34 | void startCameraServer(); 35 | int getNewAngle(int servo_id); 36 | 37 | void setup() 38 | { 39 | Wire.begin(I2C_SDA_NUM, I2C_SCL_NUM); 40 | pwm.begin(); 41 | // In theory the internal oscillator is 25MHz but it really isn't 42 | // that precise. You can 'calibrate' by tweaking this number till 43 | // you get the frequency you're expecting! 44 | pwm.setOscillatorFrequency(27000000); // The int.osc. is closer to 27MHz 45 | pwm.setPWMFreq(SERVO_FREQ); // Analog servos run at ~60 Hz updates 46 | 47 | camera_config_t config; 48 | config.ledc_channel = LEDC_CHANNEL_0; 49 | config.ledc_timer = LEDC_TIMER_0; 50 | config.pin_d0 = Y2_GPIO_NUM; 51 | config.pin_d1 = Y3_GPIO_NUM; 52 | config.pin_d2 = Y4_GPIO_NUM; 53 | config.pin_d3 = Y5_GPIO_NUM; 54 | config.pin_d4 = Y6_GPIO_NUM; 55 | config.pin_d5 = Y7_GPIO_NUM; 56 | config.pin_d6 = Y8_GPIO_NUM; 57 | config.pin_d7 = Y9_GPIO_NUM; 58 | config.pin_xclk = XCLK_GPIO_NUM; 59 | config.pin_pclk = PCLK_GPIO_NUM; 60 | config.pin_vsync = VSYNC_GPIO_NUM; 61 | config.pin_href = HREF_GPIO_NUM; 62 | config.pin_sscb_sda = SIOD_GPIO_NUM; 63 | config.pin_sscb_scl = SIOC_GPIO_NUM; 64 | config.pin_pwdn = PWDN_GPIO_NUM; 65 | config.pin_reset = RESET_GPIO_NUM; 66 | config.xclk_freq_hz = 20000000; 67 | config.pixel_format = PIXFORMAT_JPEG; 68 | //init with high specs to pre-allocate larger buffers 69 | if (psramFound()) 70 | { 71 | config.frame_size = FRAMESIZE_VGA; 72 | config.jpeg_quality = 10; 73 | config.fb_count = 2; 74 | } 75 | else 76 | { 77 | config.frame_size = FRAMESIZE_QVGA; 78 | config.jpeg_quality = 10; 79 | config.fb_count = 2; 80 | } 81 | 82 | #if defined(CAMERA_MODEL_ESP_EYE) 83 | pinMode(13, INPUT_PULLUP); 84 | pinMode(14, INPUT_PULLUP); 85 | #endif 86 | 87 | // camera init 88 | esp_err_t err = esp_camera_init(&config); 89 | if (err != ESP_OK) 90 | { 91 | log_i("Camera init failed with error 0x%x", err); 92 | return; 93 | } 94 | 95 | sensor_t *s = esp_camera_sensor_get(); 96 | //initial sensors are flipped vertically and colors are a bit saturated 97 | if (s->id.PID == OV3660_PID) 98 | { 99 | s->set_vflip(s, 1); //flip it back 100 | s->set_brightness(s, 1); //up the blightness just a bit 101 | s->set_saturation(s, -2); //lower the saturation 102 | } 103 | else 104 | { 105 | // s->set_brightness(s, 2); 106 | // s->set_contrast(s, 2); 107 | s->set_saturation(s, 2); 108 | s->set_aec2(s, true); 109 | s->set_gainceiling(s, GAINCEILING_128X); 110 | s->set_lenc(s, true); 111 | } 112 | 113 | #if defined(CAMERA_MODEL_M5STACK_WIDE) 114 | s->set_vflip(s, 1); 115 | s->set_hmirror(s, 1); 116 | #endif 117 | 118 | esp_wifi_set_max_tx_power(127); // max tx 119 | #ifdef SOFTAP 120 | WiFi.softAP(ssid, password, channel); 121 | #else 122 | WiFi.mode(WIFI_STA); 123 | WiFi.begin(ssid, password); 124 | 125 | while (WiFi.status() != WL_CONNECTED) 126 | { 127 | delay(500); 128 | log_i("."); 129 | } 130 | log_i("WiFi connected"); 131 | #endif 132 | 133 | startCameraServer(); 134 | 135 | #ifdef SOFTAP 136 | log_i("Camera Ready! Use 'http://%s to connect", WiFi.softAPIP().toString()); 137 | #else 138 | log_i("Camera Ready! Use 'http://%s to connect", WiFi.localIP().toString()); 139 | #endif 140 | } 141 | 142 | void loop() 143 | { 144 | for (int i = 0; i < 16; ++i) 145 | { 146 | int newAngle = getNewAngle(i); 147 | if (newAngle >= 0) 148 | { 149 | log_i("pwm.writeMicroseconds(%d, %d)", i, newAngle); 150 | pwm.writeMicroseconds(i, newAngle); 151 | } else { 152 | pwm.writeMicroseconds(i, 0); 153 | } 154 | } 155 | 156 | delay(20); 157 | } 158 | -------------------------------------------------------------------------------- /ESP32CameraRobotOldVer/app_httpd.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 | // 15 | // test camera: 16 | // http://192.168.4.1/ 17 | // 18 | // test servo: 19 | // http://192.168.4.1/servo?servo=0&pwm=320 20 | // 21 | // test pose: 22 | // close: http://10.0.1.47/pose?angles=0000000000000000&steps=10 23 | // open: http://10.0.1.47/pose?angles=9999999999999999&steps=10 24 | // stand: http://10.0.1.47/pose?angles=3855500000055583&steps=10 25 | // crouching: http://10.0.1.47/pose?angles=1500200000020051&steps=10 26 | // sit: http://10.0.1.47/pose?angles=1900300000030091&steps=10 27 | #include 28 | #include 29 | #include 30 | 31 | #include "camera_index.h" 32 | 33 | typedef struct 34 | { 35 | size_t size; //number of values used for filtering 36 | size_t index; //current value index 37 | size_t count; //value count 38 | int sum; 39 | int *values; //array to be filled with values 40 | } ra_filter_t; 41 | 42 | typedef struct 43 | { 44 | httpd_req_t *req; 45 | size_t len; 46 | } jpg_chunking_t; 47 | 48 | #define PART_BOUNDARY "123456789000000000000987654321" 49 | static const char *_STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; 50 | static const char *_STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; 51 | static const char *_STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; 52 | 53 | static ra_filter_t ra_filter; 54 | httpd_handle_t stream_httpd = NULL; 55 | httpd_handle_t camera_httpd = NULL; 56 | 57 | // define each servo pulsewidth range 58 | static int servo_range[16][2] = { 59 | {1720, 720}, // 1, front left leg, range 1000 60 | {710, 2010}, // 2, front left foot, range 1300 61 | {1380, 2230}, // 3, back left leg A, range 850 62 | {2300, 1450}, // 4, back left leg B, range 850 63 | {780, 2080}, // 5, back left foot, range 1300 64 | {670, 2400}, // 6, head, range 1730 65 | {1000, 2000}, // 7 66 | {1000, 2000}, // 8 67 | {1000, 2000}, // 9 68 | {1000, 2000}, // 10 69 | {2400, 670}, // 11, tail, range 1730 70 | {2380, 1080}, // 12, back right foot, range 1300 71 | {850, 1700}, // 13, back right leg B, range 850 72 | {1520, 670}, // 14, back right leg A, range 850 73 | {2230, 930}, // 15, front right foot, range 1300 74 | {1360, 2360} // 16, front right leg, range 1000 75 | }; 76 | 77 | static int current_positions[16] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; 78 | static int target_positions[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 79 | static int step_angles[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 80 | 81 | static void test_pwm(uint8_t servo_id, int pwm) 82 | { 83 | log_i("servo_id: %d, pwm: %d", servo_id, pwm); 84 | target_positions[servo_id] = pwm; 85 | step_angles[servo_id] = target_positions[servo_id] - current_positions[servo_id]; 86 | } 87 | 88 | static void set_angle(uint8_t servo_id, int angle, int steps) 89 | { 90 | log_i("servo_id: %d, angle: %d", servo_id, angle); 91 | target_positions[servo_id] = map(angle, 0, 9, servo_range[servo_id][0], servo_range[servo_id][1]); 92 | step_angles[servo_id] = (target_positions[servo_id] - current_positions[servo_id]) / steps; 93 | current_positions[servo_id] = target_positions[servo_id] - (step_angles[servo_id] * steps); 94 | } 95 | 96 | static ra_filter_t *ra_filter_init(ra_filter_t *filter, size_t sample_size) 97 | { 98 | memset(filter, 0, sizeof(ra_filter_t)); 99 | 100 | filter->values = (int *)malloc(sample_size * sizeof(int)); 101 | if (!filter->values) 102 | { 103 | return NULL; 104 | } 105 | memset(filter->values, 0, sample_size * sizeof(int)); 106 | 107 | filter->size = sample_size; 108 | return filter; 109 | } 110 | 111 | static int ra_filter_run(ra_filter_t *filter, int value) 112 | { 113 | if (!filter->values) 114 | { 115 | return value; 116 | } 117 | filter->sum -= filter->values[filter->index]; 118 | filter->values[filter->index] = value; 119 | filter->sum += filter->values[filter->index]; 120 | ++filter->index; 121 | filter->index = filter->index % filter->size; 122 | if (filter->count < filter->size) 123 | { 124 | ++filter->count; 125 | } 126 | return filter->sum / filter->count; 127 | } 128 | 129 | static size_t jpg_encode_stream(void *arg, size_t index, const void *data, size_t len) 130 | { 131 | jpg_chunking_t *j = (jpg_chunking_t *)arg; 132 | if (!index) 133 | { 134 | j->len = 0; 135 | } 136 | if (httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK) 137 | { 138 | return 0; 139 | } 140 | j->len += len; 141 | return len; 142 | } 143 | 144 | static esp_err_t capture_handler(httpd_req_t *req) 145 | { 146 | camera_fb_t *fb = NULL; 147 | esp_err_t res = ESP_OK; 148 | int64_t fr_start = esp_timer_get_time(); 149 | 150 | fb = esp_camera_fb_get(); 151 | if (!fb) 152 | { 153 | log_e("Camera capture failed"); 154 | httpd_resp_send_500(req); 155 | return ESP_FAIL; 156 | } 157 | 158 | httpd_resp_set_type(req, "image/jpeg"); 159 | httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); 160 | 161 | size_t out_len, out_width, out_height; 162 | uint8_t *out_buf; 163 | bool s; 164 | size_t fb_len = 0; 165 | fb_len = fb->len; 166 | res = httpd_resp_send(req, (const char *)fb->buf, fb->len); 167 | esp_camera_fb_return(fb); 168 | int64_t fr_end = esp_timer_get_time(); 169 | log_i("JPG: %uB %ums\n", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start) / 1000)); 170 | return res; 171 | } 172 | 173 | static esp_err_t stream_handler(httpd_req_t *req) 174 | { 175 | camera_fb_t *fb = NULL; 176 | esp_err_t res = ESP_OK; 177 | size_t _jpg_buf_len = 0; 178 | uint8_t *_jpg_buf = NULL; 179 | char *part_buf[64]; 180 | int64_t fr_start = 0; 181 | int64_t fr_ready = 0; 182 | int64_t fr_encode = 0; 183 | 184 | static int64_t last_frame = 0; 185 | if (!last_frame) 186 | { 187 | last_frame = esp_timer_get_time(); 188 | } 189 | 190 | res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); 191 | if (res != ESP_OK) 192 | { 193 | return res; 194 | } 195 | 196 | while (true) 197 | { 198 | fb = esp_camera_fb_get(); 199 | if (!fb) 200 | { 201 | log_e("Camera capture failed"); 202 | res = ESP_FAIL; 203 | } 204 | else 205 | { 206 | fr_start = esp_timer_get_time(); 207 | fr_ready = fr_start; 208 | fr_encode = fr_start; 209 | _jpg_buf_len = fb->len; 210 | _jpg_buf = fb->buf; 211 | } 212 | if (res == ESP_OK) 213 | { 214 | size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len); 215 | res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); 216 | } 217 | if (res == ESP_OK) 218 | { 219 | res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); 220 | } 221 | if (res == ESP_OK) 222 | { 223 | res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); 224 | } 225 | if (fb) 226 | { 227 | esp_camera_fb_return(fb); 228 | fb = NULL; 229 | _jpg_buf = NULL; 230 | } 231 | else if (_jpg_buf) 232 | { 233 | free(_jpg_buf); 234 | _jpg_buf = NULL; 235 | } 236 | if (res != ESP_OK) 237 | { 238 | break; 239 | } 240 | int64_t fr_end = esp_timer_get_time(); 241 | 242 | int64_t ready_time = (fr_ready - fr_start) / 1000; 243 | int64_t encode_time = (fr_encode - fr_ready) / 1000; 244 | int64_t process_time = (fr_encode - fr_start) / 1000; 245 | 246 | int64_t frame_time = fr_end - last_frame; 247 | last_frame = fr_end; 248 | frame_time /= 1000; 249 | uint32_t avg_frame_time = ra_filter_run(&ra_filter, frame_time); 250 | log_i("MJPG: %uB %ums (%.1ffps), AVG: %ums (%.1ffps), %u+%u=%u\n", 251 | (uint32_t)(_jpg_buf_len), 252 | (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time, 253 | avg_frame_time, 1000.0 / avg_frame_time, 254 | (uint32_t)ready_time, (uint32_t)encode_time, (uint32_t)process_time); 255 | } 256 | 257 | last_frame = 0; 258 | return res; 259 | } 260 | 261 | static esp_err_t stream_vga(httpd_req_t *req) 262 | { 263 | sensor_t *s = esp_camera_sensor_get(); 264 | s->set_framesize(s, FRAMESIZE_VGA); 265 | return stream_handler(req); 266 | } 267 | 268 | static esp_err_t stream_cif(httpd_req_t *req) 269 | { 270 | sensor_t *s = esp_camera_sensor_get(); 271 | s->set_framesize(s, FRAMESIZE_CIF); 272 | return stream_handler(req); 273 | } 274 | 275 | static esp_err_t stream_qvga(httpd_req_t *req) 276 | { 277 | sensor_t *s = esp_camera_sensor_get(); 278 | s->set_framesize(s, FRAMESIZE_QVGA); 279 | return stream_handler(req); 280 | } 281 | 282 | static esp_err_t stream_hqvga(httpd_req_t *req) 283 | { 284 | sensor_t *s = esp_camera_sensor_get(); 285 | s->set_framesize(s, FRAMESIZE_HQVGA); 286 | return stream_handler(req); 287 | } 288 | 289 | static esp_err_t stream_qcif(httpd_req_t *req) 290 | { 291 | sensor_t *s = esp_camera_sensor_get(); 292 | s->set_framesize(s, FRAMESIZE_QCIF); 293 | return stream_handler(req); 294 | } 295 | 296 | static esp_err_t stream_qqvga(httpd_req_t *req) 297 | { 298 | sensor_t *s = esp_camera_sensor_get(); 299 | s->set_framesize(s, FRAMESIZE_QQVGA); 300 | return stream_handler(req); 301 | } 302 | 303 | static esp_err_t cmd_handler(httpd_req_t *req) 304 | { 305 | char *buf; 306 | size_t buf_len; 307 | char variable[32] = {0}; 308 | char value[32] = {0}; 309 | 310 | buf_len = httpd_req_get_url_query_len(req) + 1; 311 | if (buf_len > 1) 312 | { 313 | buf = (char *)malloc(buf_len); 314 | if (!buf) 315 | { 316 | httpd_resp_send_500(req); 317 | return ESP_FAIL; 318 | } 319 | if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) 320 | { 321 | if (httpd_query_key_value(buf, "var", variable, sizeof(variable)) == ESP_OK && 322 | httpd_query_key_value(buf, "val", value, sizeof(value)) == ESP_OK) 323 | { 324 | } 325 | else 326 | { 327 | free(buf); 328 | httpd_resp_send_404(req); 329 | return ESP_FAIL; 330 | } 331 | } 332 | else 333 | { 334 | free(buf); 335 | httpd_resp_send_404(req); 336 | return ESP_FAIL; 337 | } 338 | free(buf); 339 | } 340 | else 341 | { 342 | httpd_resp_send_404(req); 343 | return ESP_FAIL; 344 | } 345 | 346 | int val = atoi(value); 347 | sensor_t *s = esp_camera_sensor_get(); 348 | int res = 0; 349 | 350 | if (!strcmp(variable, "framesize")) 351 | { 352 | if (s->pixformat == PIXFORMAT_JPEG) 353 | res = s->set_framesize(s, (framesize_t)val); 354 | } 355 | else if (!strcmp(variable, "quality")) 356 | res = s->set_quality(s, val); 357 | else if (!strcmp(variable, "contrast")) 358 | res = s->set_contrast(s, val); 359 | else if (!strcmp(variable, "brightness")) 360 | res = s->set_brightness(s, val); 361 | else if (!strcmp(variable, "saturation")) 362 | res = s->set_saturation(s, val); 363 | else if (!strcmp(variable, "gainceiling")) 364 | res = s->set_gainceiling(s, (gainceiling_t)val); 365 | else if (!strcmp(variable, "colorbar")) 366 | res = s->set_colorbar(s, val); 367 | else if (!strcmp(variable, "awb")) 368 | res = s->set_whitebal(s, val); 369 | else if (!strcmp(variable, "agc")) 370 | res = s->set_gain_ctrl(s, val); 371 | else if (!strcmp(variable, "aec")) 372 | res = s->set_exposure_ctrl(s, val); 373 | else if (!strcmp(variable, "hmirror")) 374 | res = s->set_hmirror(s, val); 375 | else if (!strcmp(variable, "vflip")) 376 | res = s->set_vflip(s, val); 377 | else if (!strcmp(variable, "awb_gain")) 378 | res = s->set_awb_gain(s, val); 379 | else if (!strcmp(variable, "agc_gain")) 380 | res = s->set_agc_gain(s, val); 381 | else if (!strcmp(variable, "aec_value")) 382 | res = s->set_aec_value(s, val); 383 | else if (!strcmp(variable, "aec2")) 384 | res = s->set_aec2(s, val); 385 | else if (!strcmp(variable, "dcw")) 386 | res = s->set_dcw(s, val); 387 | else if (!strcmp(variable, "bpc")) 388 | res = s->set_bpc(s, val); 389 | else if (!strcmp(variable, "wpc")) 390 | res = s->set_wpc(s, val); 391 | else if (!strcmp(variable, "raw_gma")) 392 | res = s->set_raw_gma(s, val); 393 | else if (!strcmp(variable, "lenc")) 394 | res = s->set_lenc(s, val); 395 | else if (!strcmp(variable, "special_effect")) 396 | res = s->set_special_effect(s, val); 397 | else if (!strcmp(variable, "wb_mode")) 398 | res = s->set_wb_mode(s, val); 399 | else if (!strcmp(variable, "ae_level")) 400 | res = s->set_ae_level(s, val); 401 | else 402 | { 403 | res = -1; 404 | } 405 | 406 | if (res) 407 | { 408 | return httpd_resp_send_500(req); 409 | } 410 | 411 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 412 | return httpd_resp_send(req, NULL, 0); 413 | } 414 | 415 | static esp_err_t status_handler(httpd_req_t *req) 416 | { 417 | static char json_response[1024]; 418 | 419 | sensor_t *s = esp_camera_sensor_get(); 420 | char *p = json_response; 421 | *p++ = '{'; 422 | 423 | p += sprintf(p, "\"framesize\":%u,", s->status.framesize); 424 | p += sprintf(p, "\"quality\":%u,", s->status.quality); 425 | p += sprintf(p, "\"brightness\":%d,", s->status.brightness); 426 | p += sprintf(p, "\"contrast\":%d,", s->status.contrast); 427 | p += sprintf(p, "\"saturation\":%d,", s->status.saturation); 428 | p += sprintf(p, "\"sharpness\":%d,", s->status.sharpness); 429 | p += sprintf(p, "\"special_effect\":%u,", s->status.special_effect); 430 | p += sprintf(p, "\"wb_mode\":%u,", s->status.wb_mode); 431 | p += sprintf(p, "\"awb\":%u,", s->status.awb); 432 | p += sprintf(p, "\"awb_gain\":%u,", s->status.awb_gain); 433 | p += sprintf(p, "\"aec\":%u,", s->status.aec); 434 | p += sprintf(p, "\"aec2\":%u,", s->status.aec2); 435 | p += sprintf(p, "\"ae_level\":%d,", s->status.ae_level); 436 | p += sprintf(p, "\"aec_value\":%u,", s->status.aec_value); 437 | p += sprintf(p, "\"agc\":%u,", s->status.agc); 438 | p += sprintf(p, "\"agc_gain\":%u,", s->status.agc_gain); 439 | p += sprintf(p, "\"gainceiling\":%u,", s->status.gainceiling); 440 | p += sprintf(p, "\"bpc\":%u,", s->status.bpc); 441 | p += sprintf(p, "\"wpc\":%u,", s->status.wpc); 442 | p += sprintf(p, "\"raw_gma\":%u,", s->status.raw_gma); 443 | p += sprintf(p, "\"lenc\":%u,", s->status.lenc); 444 | p += sprintf(p, "\"vflip\":%u,", s->status.vflip); 445 | p += sprintf(p, "\"hmirror\":%u,", s->status.hmirror); 446 | p += sprintf(p, "\"dcw\":%u,", s->status.dcw); 447 | p += sprintf(p, "\"colorbar\":%u", s->status.colorbar); 448 | *p++ = '}'; 449 | *p++ = 0; 450 | httpd_resp_set_type(req, "application/json"); 451 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 452 | return httpd_resp_send(req, json_response, strlen(json_response)); 453 | } 454 | 455 | static esp_err_t servo_handler(httpd_req_t *req) 456 | { 457 | char *buf; 458 | size_t buf_len; 459 | char servo[32] = {0}; 460 | char pwm[32] = {0}; 461 | 462 | buf_len = httpd_req_get_url_query_len(req) + 1; 463 | if (buf_len > 1) 464 | { 465 | buf = (char *)malloc(buf_len); 466 | if (!buf) 467 | { 468 | httpd_resp_send_500(req); 469 | return ESP_FAIL; 470 | } 471 | if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) 472 | { 473 | if (httpd_query_key_value(buf, "servo", servo, sizeof(servo)) == ESP_OK && 474 | httpd_query_key_value(buf, "pwm", pwm, sizeof(pwm)) == ESP_OK) 475 | { 476 | test_pwm(atoi(servo), atoi(pwm)); 477 | } 478 | else 479 | { 480 | free(buf); 481 | httpd_resp_send_404(req); 482 | return ESP_FAIL; 483 | } 484 | } 485 | else 486 | { 487 | free(buf); 488 | httpd_resp_send_404(req); 489 | return ESP_FAIL; 490 | } 491 | free(buf); 492 | } 493 | else 494 | { 495 | httpd_resp_send_404(req); 496 | return ESP_FAIL; 497 | } 498 | 499 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 500 | return httpd_resp_send(req, NULL, 0); 501 | } 502 | 503 | static esp_err_t pose_handler(httpd_req_t *req) 504 | { 505 | char *buf; 506 | size_t buf_len; 507 | char angles[32] = {0}; 508 | char steps[32] = {0}; 509 | 510 | buf_len = httpd_req_get_url_query_len(req) + 1; 511 | if (buf_len > 1) 512 | { 513 | buf = (char *)malloc(buf_len); 514 | if (!buf) 515 | { 516 | httpd_resp_send_500(req); 517 | return ESP_FAIL; 518 | } 519 | if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) 520 | { 521 | if (httpd_query_key_value(buf, "angles", angles, sizeof(angles)) == ESP_OK && 522 | httpd_query_key_value(buf, "steps", steps, sizeof(steps)) == ESP_OK) 523 | { 524 | int t = atoi(steps); 525 | for (int i = 0; i < 16; ++i) 526 | { 527 | set_angle(i, angles[i] - 0x30, t); 528 | } 529 | } 530 | else 531 | { 532 | free(buf); 533 | httpd_resp_send_404(req); 534 | return ESP_FAIL; 535 | } 536 | } 537 | else 538 | { 539 | free(buf); 540 | httpd_resp_send_404(req); 541 | return ESP_FAIL; 542 | } 543 | free(buf); 544 | } 545 | else 546 | { 547 | httpd_resp_send_404(req); 548 | return ESP_FAIL; 549 | } 550 | 551 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 552 | return httpd_resp_send(req, NULL, 0); 553 | } 554 | 555 | static esp_err_t index_handler(httpd_req_t *req) 556 | { 557 | httpd_resp_set_type(req, "text/html"); 558 | httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); 559 | sensor_t *s = esp_camera_sensor_get(); 560 | if (s->id.PID == OV3660_PID) 561 | { 562 | return httpd_resp_send(req, (const char *)index_ov3660_html_gz, index_ov3660_html_gz_len); 563 | } 564 | return httpd_resp_send(req, (const char *)index_ov2640_html_gz, index_ov2640_html_gz_len); 565 | } 566 | 567 | void startCameraServer() 568 | { 569 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 570 | 571 | httpd_uri_t index_uri = { 572 | .uri = "/", 573 | .method = HTTP_GET, 574 | .handler = index_handler, 575 | .user_ctx = NULL}; 576 | 577 | httpd_uri_t status_uri = { 578 | .uri = "/status", 579 | .method = HTTP_GET, 580 | .handler = status_handler, 581 | .user_ctx = NULL}; 582 | 583 | httpd_uri_t cmd_uri = { 584 | .uri = "/control", 585 | .method = HTTP_GET, 586 | .handler = cmd_handler, 587 | .user_ctx = NULL}; 588 | 589 | httpd_uri_t servo_uri = { 590 | .uri = "/servo", 591 | .method = HTTP_GET, 592 | .handler = servo_handler, 593 | .user_ctx = NULL}; 594 | 595 | httpd_uri_t pose_uri = { 596 | .uri = "/pose", 597 | .method = HTTP_GET, 598 | .handler = pose_handler, 599 | .user_ctx = NULL}; 600 | 601 | httpd_uri_t capture_uri = { 602 | .uri = "/capture", 603 | .method = HTTP_GET, 604 | .handler = capture_handler, 605 | .user_ctx = NULL}; 606 | 607 | httpd_uri_t stream_uri = { 608 | .uri = "/stream", 609 | .method = HTTP_GET, 610 | .handler = stream_handler, 611 | .user_ctx = NULL}; 612 | 613 | httpd_uri_t stream_vga_uri = { 614 | .uri = "/vga", 615 | .method = HTTP_GET, 616 | .handler = stream_vga, 617 | .user_ctx = NULL}; 618 | 619 | httpd_uri_t stream_cif_uri = { 620 | .uri = "/cif", 621 | .method = HTTP_GET, 622 | .handler = stream_cif, 623 | .user_ctx = NULL}; 624 | 625 | httpd_uri_t stream_qvga_uri = { 626 | .uri = "/qvga", 627 | .method = HTTP_GET, 628 | .handler = stream_qvga, 629 | .user_ctx = NULL}; 630 | 631 | httpd_uri_t stream_hqvga_uri = { 632 | .uri = "/hqvga", 633 | .method = HTTP_GET, 634 | .handler = stream_hqvga, 635 | .user_ctx = NULL}; 636 | 637 | httpd_uri_t stream_qcif_uri = { 638 | .uri = "/qcif", 639 | .method = HTTP_GET, 640 | .handler = stream_qcif, 641 | .user_ctx = NULL}; 642 | 643 | httpd_uri_t stream_qqvga_uri = { 644 | .uri = "/qqvga", 645 | .method = HTTP_GET, 646 | .handler = stream_qqvga, 647 | .user_ctx = NULL}; 648 | 649 | ra_filter_init(&ra_filter, 20); 650 | 651 | log_i("Starting web server on port: '%d'\n", config.server_port); 652 | if (httpd_start(&camera_httpd, &config) == ESP_OK) 653 | { 654 | httpd_register_uri_handler(camera_httpd, &index_uri); 655 | httpd_register_uri_handler(camera_httpd, &cmd_uri); 656 | httpd_register_uri_handler(camera_httpd, &status_uri); 657 | httpd_register_uri_handler(camera_httpd, &servo_uri); 658 | httpd_register_uri_handler(camera_httpd, &pose_uri); 659 | httpd_register_uri_handler(camera_httpd, &capture_uri); 660 | } 661 | 662 | config.server_port += 1; 663 | config.ctrl_port += 1; 664 | log_i("Starting stream server on port: '%d'\n", config.server_port); 665 | if (httpd_start(&stream_httpd, &config) == ESP_OK) 666 | { 667 | httpd_register_uri_handler(stream_httpd, &stream_uri); 668 | httpd_register_uri_handler(stream_httpd, &stream_vga_uri); 669 | httpd_register_uri_handler(stream_httpd, &stream_cif_uri); 670 | httpd_register_uri_handler(stream_httpd, &stream_qvga_uri); 671 | httpd_register_uri_handler(stream_httpd, &stream_hqvga_uri); 672 | httpd_register_uri_handler(stream_httpd, &stream_qcif_uri); 673 | httpd_register_uri_handler(stream_httpd, &stream_qqvga_uri); 674 | } 675 | } 676 | 677 | int getNewAngle(int servo_id) 678 | { 679 | if (current_positions[servo_id] != target_positions[servo_id]) 680 | { 681 | current_positions[servo_id] += step_angles[servo_id]; 682 | return current_positions[servo_id]; 683 | } 684 | 685 | return -1; 686 | } 687 | -------------------------------------------------------------------------------- /ESP32CameraRobotOldVer/camera_index.h: -------------------------------------------------------------------------------- 1 | //File: index_ov2640.html.gz, Size: 4316 2 | #define index_ov2640_html_gz_len 3845 3 | const uint8_t index_ov2640_html_gz[] = { 4 | 0x1F, 0x8B, 0x08, 0x08, 0x96, 0xF8, 0x54, 0x5D, 0x02, 0x00, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x2E, 5 | 0x68, 0x74, 0x6D, 0x6C, 0x00, 0xE5, 0x5D, 0xFD, 0x6E, 0xDB, 0x36, 0x10, 0xFF, 0x3F, 0x4F, 0xA1, 6 | 0x6A, 0x5B, 0xE3, 0x60, 0xB1, 0x63, 0x3B, 0x6E, 0x9A, 0xB9, 0x89, 0xB7, 0x36, 0xCD, 0xDA, 0x01, 7 | 0xEB, 0xBA, 0x35, 0xFB, 0x02, 0x86, 0xA1, 0xA5, 0xA5, 0xB3, 0xCD, 0x55, 0x96, 0x34, 0x89, 0x8A, 8 | 0x93, 0x15, 0x79, 0x8E, 0x3D, 0xD0, 0x5E, 0x6C, 0x27, 0x92, 0x36, 0xA3, 0x50, 0x12, 0x4D, 0xA1, 9 | 0xAE, 0xF7, 0xE1, 0x0C, 0x33, 0x2D, 0x92, 0xF7, 0xF5, 0xBB, 0x3B, 0x1E, 0x29, 0xD9, 0x3D, 0xB9, 10 | 0xE7, 0x47, 0x1E, 0xBB, 0x8E, 0xC1, 0x99, 0xB1, 0x79, 0x30, 0xDA, 0x39, 0x11, 0x6F, 0xF8, 0x0E, 11 | 0xC4, 0x1F, 0xED, 0x38, 0xF8, 0x3A, 0x99, 0x03, 0x23, 0x8E, 0x37, 0x23, 0x49, 0x0A, 0xEC, 0xD4, 12 | 0xCD, 0xD8, 0xA4, 0x7D, 0xEC, 0xDE, 0xEE, 0x0A, 0xC9, 0x1C, 0x4E, 0xDD, 0x4B, 0x0A, 0x8B, 0x38, 13 | 0x4A, 0x98, 0xEB, 0x78, 0x51, 0xC8, 0x20, 0xC4, 0xA1, 0x0B, 0xEA, 0xB3, 0xD9, 0xA9, 0x0F, 0x97, 14 | 0xD4, 0x83, 0x36, 0xFF, 0xB0, 0x4F, 0x43, 0xCA, 0x28, 0x09, 0xDA, 0xA9, 0x47, 0x02, 0x38, 0xED, 15 | 0x2D, 0xE9, 0x30, 0xCA, 0x02, 0x18, 0x9D, 0x5F, 0x7C, 0x7B, 0xD8, 0x77, 0x5E, 0xFE, 0xD8, 0x3F, 16 | 0x1A, 0x74, 0x4F, 0x0E, 0xF8, 0x35, 0xD9, 0x9F, 0xB2, 0x6B, 0xDE, 0x96, 0xAF, 0x71, 0xE4, 0x5F, 17 | 0x3B, 0xEF, 0xF0, 0xA3, 0x7A, 0x4D, 0x90, 0x69, 0x7B, 0x42, 0xE6, 0x34, 0xB8, 0x1E, 0x3A, 0x8F, 18 | 0x13, 0xE4, 0xB1, 0xEF, 0x3C, 0x87, 0xE0, 0x12, 0x18, 0xF5, 0xC8, 0xBE, 0x93, 0x92, 0x30, 0x6D, 19 | 0xA7, 0x90, 0xD0, 0xC9, 0xA3, 0xC2, 0xB4, 0x31, 0xF1, 0xDE, 0x4E, 0x93, 0x28, 0x0B, 0xFD, 0xA1, 20 | 0xF3, 0x51, 0xEF, 0x38, 0xFF, 0x2B, 0x0E, 0xF0, 0xA2, 0x20, 0x4A, 0xB0, 0xEF, 0xFC, 0xCB, 0xFC, 21 | 0xEF, 0x91, 0xCE, 0x33, 0xA5, 0x7F, 0xC0, 0xD0, 0xE9, 0x1D, 0xC5, 0x57, 0xAB, 0xBE, 0x9B, 0x9D, 22 | 0x55, 0x73, 0xD6, 0x57, 0x72, 0x6A, 0x73, 0x8E, 0xCB, 0xE7, 0xA4, 0xE0, 0x31, 0x1A, 0x85, 0x9D, 23 | 0x39, 0xA1, 0xE1, 0x9D, 0xD9, 0x3E, 0x4D, 0xE3, 0x80, 0xA0, 0x86, 0x93, 0x00, 0x4A, 0xE7, 0x7E, 24 | 0x34, 0x87, 0x30, 0xDB, 0x5F, 0x83, 0x14, 0xA7, 0xD0, 0xF6, 0x69, 0x22, 0x46, 0x0C, 0x73, 0x45, 25 | 0xB3, 0x79, 0x58, 0x49, 0xB3, 0x4A, 0x90, 0x30, 0x0A, 0xE1, 0x91, 0x4E, 0x78, 0x91, 0x90, 0x38, 26 | 0xEF, 0xCC, 0xDF, 0x8B, 0xDD, 0x73, 0x1A, 0x0A, 0x5F, 0x18, 0x3A, 0x87, 0x83, 0x6E, 0x7C, 0x55, 27 | 0x83, 0xC7, 0xE1, 0x51, 0xFE, 0x57, 0x1C, 0x10, 0x13, 0xDF, 0xA7, 0xE1, 0x74, 0xE8, 0x1C, 0x6B, 28 | 0x53, 0xA3, 0xC4, 0x87, 0xA4, 0x9D, 0x10, 0x9F, 0x66, 0xE9, 0xD0, 0x19, 0xDC, 0xED, 0x9F, 0x93, 29 | 0x64, 0x8A, 0xBC, 0x59, 0x84, 0x82, 0xB5, 0x7B, 0xDD, 0x8A, 0xEE, 0x84, 0x4E, 0x67, 0x0C, 0xB1, 30 | 0x91, 0xFD, 0xBA, 0x31, 0xA4, 0x77, 0xD7, 0x01, 0x53, 0x69, 0x0F, 0xDD, 0x1A, 0x24, 0xA0, 0xD3, 31 | 0xB0, 0x4D, 0x19, 0xCC, 0x51, 0xE4, 0x94, 0x25, 0xC0, 0xBC, 0x59, 0x19, 0xDB, 0x09, 0x9D, 0x66, 32 | 0x09, 0x08, 0xA6, 0xBA, 0x2D, 0x2A, 0x94, 0xC1, 0x8E, 0xE2, 0xE5, 0xF6, 0x02, 0xC6, 0x6F, 0x29, 33 | 0x6B, 0x4B, 0x5D, 0xC7, 0x30, 0x89, 0x12, 0xD0, 0x46, 0x2D, 0x7B, 0x83, 0xC8, 0x7B, 0xDB, 0x4E, 34 | 0x19, 0x49, 0x98, 0x89, 0x10, 0x99, 0x30, 0x48, 0xEA, 0xE9, 0x40, 0xE8, 0x1B, 0xA8, 0x54, 0xB0, 35 | 0x92, 0x9D, 0x34, 0x0C, 0x68, 0x08, 0xEB, 0x89, 0x83, 0xBC, 0x0C, 0x64, 0xC4, 0x88, 0x1A, 0x43, 36 | 0xD3, 0xF9, 0xB4, 0x0A, 0x61, 0xAE, 0x4F, 0x91, 0xB8, 0xF4, 0xE7, 0x5E, 0xB7, 0xFB, 0x49, 0xB1, 37 | 0x63, 0x06, 0xC2, 0x9D, 0x48, 0xC6, 0xA2, 0xE6, 0xDE, 0x7A, 0x5C, 0xE1, 0x8B, 0x5F, 0xCC, 0xC1, 38 | 0xA7, 0xC4, 0x69, 0xDD, 0x0A, 0xA9, 0xE3, 0x2E, 0xFA, 0xC2, 0x9E, 0x43, 0x42, 0xDF, 0x69, 0x45, 39 | 0x09, 0x45, 0x47, 0x25, 0x3C, 0xBC, 0x03, 0xBC, 0x82, 0x39, 0x37, 0x86, 0x3D, 0xA9, 0x96, 0xC1, 40 | 0x9F, 0x0D, 0x3E, 0x6D, 0x88, 0x73, 0xA3, 0x77, 0x6B, 0xCA, 0x18, 0x8C, 0x6F, 0x00, 0x40, 0x5A, 41 | 0x0C, 0xC5, 0x51, 0x40, 0x94, 0x0F, 0x58, 0x02, 0xE2, 0x91, 0xC0, 0x6B, 0xE1, 0xB0, 0xCB, 0x99, 42 | 0xD3, 0x76, 0x06, 0xB9, 0xCD, 0xF4, 0xF1, 0x9C, 0x98, 0x8E, 0x9D, 0x8E, 0xAC, 0x59, 0x25, 0xA9, 43 | 0x4E, 0x79, 0xF0, 0x8A, 0x3F, 0x85, 0xB1, 0x31, 0x8C, 0xCD, 0xA1, 0x6C, 0x17, 0xCE, 0xE6, 0x90, 44 | 0xB6, 0x09, 0x6B, 0x73, 0x68, 0x5B, 0x86, 0xB7, 0x39, 0xC4, 0xCD, 0x61, 0xAE, 0xE0, 0xA9, 0x59, 45 | 0x71, 0x3F, 0x1A, 0x67, 0x8C, 0x45, 0x61, 0x6A, 0x9D, 0xDB, 0x55, 0x0C, 0x94, 0x2E, 0x19, 0x21, 46 | 0xB9, 0xC4, 0x40, 0x9E, 0x4E, 0x83, 0xBB, 0x7E, 0xE0, 0x65, 0x49, 0x9A, 0x57, 0x17, 0x71, 0x44, 47 | 0x43, 0x34, 0xF7, 0xA3, 0x9A, 0x84, 0x63, 0x20, 0xDC, 0xF6, 0xC6, 0x77, 0x68, 0x47, 0x19, 0x43, 48 | 0x1B, 0xE8, 0x2E, 0x11, 0xC5, 0xC4, 0xA3, 0xEC, 0x5A, 0x5C, 0xD7, 0xBD, 0xBD, 0x5B, 0x9E, 0xC0, 49 | 0xBA, 0x66, 0xFE, 0x43, 0x6F, 0x06, 0xDE, 0x5B, 0xF0, 0x3F, 0xE5, 0xF5, 0x82, 0x6D, 0xE1, 0xD2, 50 | 0xA1, 0x61, 0x9C, 0xB1, 0x76, 0xBE, 0xF6, 0xC7, 0x75, 0x73, 0xAD, 0x6A, 0x0D, 0xEE, 0x04, 0x4B, 51 | 0x15, 0xFA, 0xFD, 0xAA, 0x55, 0xF2, 0x41, 0x7C, 0xE5, 0x74, 0x4D, 0x42, 0x8D, 0x02, 0x32, 0x86, 52 | 0xA0, 0x4A, 0x34, 0xE9, 0x70, 0x1C, 0xAA, 0xD2, 0x82, 0x45, 0x2B, 0x2C, 0xCA, 0x0A, 0xA2, 0xC1, 53 | 0xC3, 0x4F, 0x8C, 0xB6, 0xE1, 0xED, 0xFD, 0xF2, 0xBE, 0x14, 0x02, 0xF4, 0xE4, 0xB2, 0x42, 0x0F, 54 | 0xFB, 0x17, 0xC8, 0xBC, 0x94, 0x7A, 0x42, 0xC2, 0x29, 0x60, 0x60, 0x5D, 0xED, 0x6B, 0xD7, 0x68, 55 | 0x68, 0xA1, 0xB0, 0x9E, 0xD8, 0x1E, 0x94, 0x17, 0xB8, 0x22, 0xCC, 0x0C, 0xEB, 0x6B, 0x05, 0x50, 56 | 0x95, 0xBC, 0x7A, 0xFD, 0xF2, 0x82, 0x10, 0xFB, 0xEA, 0x9C, 0x42, 0xAD, 0xAD, 0xE6, 0x98, 0x54, 57 | 0xBB, 0x81, 0xC9, 0xA4, 0x66, 0x1F, 0x81, 0xBD, 0x87, 0xDD, 0xC3, 0x41, 0xED, 0x7A, 0x8F, 0x9A, 58 | 0xD4, 0xEE, 0x25, 0x1E, 0x55, 0xC4, 0x72, 0xB5, 0x39, 0x87, 0xB3, 0xE8, 0x12, 0x12, 0xE7, 0x5D, 59 | 0x9D, 0x58, 0x83, 0xCF, 0x06, 0x7E, 0x0D, 0x05, 0xE2, 0x31, 0x7A, 0x09, 0xB5, 0x24, 0xFA, 0x3D, 60 | 0xAF, 0xDF, 0xAB, 0x26, 0xD1, 0x41, 0x14, 0xC9, 0x38, 0x00, 0xBF, 0x22, 0xD1, 0xF9, 0x30, 0x21, 61 | 0x59, 0xC0, 0x6A, 0x6C, 0x47, 0xBA, 0xF8, 0x57, 0xA6, 0xA6, 0x70, 0xFC, 0x5F, 0xF2, 0xBD, 0xEA, 62 | 0x29, 0xF7, 0xCE, 0x5F, 0x39, 0x0F, 0x7D, 0x85, 0x20, 0x71, 0x0C, 0x04, 0x47, 0x78, 0x20, 0x76, 63 | 0x27, 0x96, 0xA5, 0x5A, 0xBF, 0x6F, 0xB1, 0x27, 0x31, 0xBA, 0x8C, 0xF2, 0xDD, 0xF5, 0x74, 0x1A, 64 | 0x4E, 0x22, 0x2F, 0x4B, 0xCB, 0x53, 0xF9, 0xDA, 0x34, 0x86, 0x4B, 0x53, 0xA4, 0x01, 0xE5, 0x4E, 65 | 0x97, 0x85, 0x61, 0x8E, 0x4A, 0x9B, 0x25, 0xA8, 0x0A, 0x12, 0xB7, 0x33, 0x88, 0x55, 0x84, 0x28, 66 | 0x63, 0xA9, 0x4D, 0x73, 0x5D, 0x20, 0x74, 0x2B, 0x62, 0xD6, 0x49, 0x23, 0x14, 0x7E, 0x49, 0xA2, 67 | 0x99, 0xDE, 0x6C, 0x96, 0xCD, 0xC5, 0xB2, 0xA8, 0x33, 0xE8, 0xC5, 0x57, 0x92, 0x45, 0x32, 0x1D, 68 | 0x93, 0x56, 0x77, 0xDF, 0xC1, 0xFF, 0x0E, 0xF3, 0xB7, 0xBD, 0x1A, 0xBF, 0xD0, 0x2D, 0xA7, 0x77, 69 | 0xE8, 0xB1, 0x8E, 0x29, 0xDF, 0x2A, 0x5B, 0xE8, 0x26, 0xB6, 0x74, 0x74, 0x7D, 0xBB, 0xDB, 0xEB, 70 | 0x60, 0xC6, 0xB1, 0xF0, 0x40, 0x0B, 0x1F, 0xD2, 0x40, 0xB7, 0x42, 0x6C, 0x1E, 0xFD, 0xD1, 0xCE, 71 | 0x3F, 0xFC, 0x0F, 0x9C, 0x73, 0xA5, 0xEA, 0x7F, 0xC7, 0x31, 0xD7, 0x56, 0x3D, 0xFD, 0xD0, 0xF0, 72 | 0x76, 0xCB, 0xC1, 0x6B, 0xCB, 0x15, 0x9C, 0xA1, 0x68, 0x69, 0x4C, 0x12, 0x08, 0x59, 0x71, 0x98, 73 | 0xDE, 0x6F, 0xA1, 0xE3, 0x84, 0x06, 0x41, 0x3B, 0x88, 0x16, 0x90, 0x98, 0x03, 0xC4, 0xE8, 0x5A, 74 | 0x66, 0xEF, 0xB4, 0x95, 0x2C, 0x8B, 0xE3, 0x7F, 0x9E, 0x64, 0xFF, 0x99, 0x58, 0xA8, 0x90, 0xC8, 75 | 0x2A, 0xED, 0xDA, 0xBB, 0x91, 0x35, 0x71, 0xB3, 0x27, 0xA8, 0x32, 0xA7, 0x74, 0xBB, 0x90, 0x2E, 76 | 0x28, 0xF3, 0x66, 0x16, 0xD5, 0x7B, 0x1C, 0xA5, 0x54, 0x9C, 0x31, 0x27, 0x10, 0x90, 0xBC, 0xC4, 77 | 0x34, 0xEF, 0xD2, 0xCC, 0x95, 0xB1, 0x0E, 0x7C, 0x8D, 0xB4, 0xC2, 0x24, 0x1F, 0x7E, 0x87, 0xDC, 78 | 0x11, 0xAB, 0x67, 0x79, 0xDA, 0x93, 0x2E, 0xA7, 0xD3, 0x32, 0x7B, 0x6B, 0xBF, 0x69, 0x62, 0x5C, 79 | 0xE6, 0xBF, 0x69, 0x02, 0xD7, 0x35, 0x02, 0xEF, 0xDF, 0xBD, 0x30, 0x14, 0x07, 0x4C, 0xD6, 0xBB, 80 | 0x41, 0x91, 0x48, 0x25, 0xFC, 0x9D, 0x41, 0xAA, 0xF3, 0xD4, 0x59, 0x58, 0x39, 0x8F, 0x3C, 0xBE, 81 | 0x1C, 0x3A, 0xAE, 0x6B, 0x88, 0xEF, 0x8A, 0x25, 0x46, 0xFA, 0x94, 0x0E, 0x8F, 0xDE, 0x11, 0xC0, 82 | 0x84, 0x95, 0x1C, 0xD5, 0xF2, 0x12, 0xEB, 0xB0, 0x3A, 0x7D, 0xB4, 0x6F, 0xED, 0x1A, 0x2B, 0xC3, 83 | 0x75, 0x75, 0x7A, 0xA2, 0x5C, 0xC6, 0x40, 0x4D, 0xA4, 0x24, 0x33, 0xC1, 0x1A, 0xF3, 0xAA, 0x5A, 84 | 0x92, 0xC3, 0x84, 0xFD, 0x73, 0xB9, 0xF4, 0xA1, 0xA9, 0xE1, 0xE7, 0x56, 0xFF, 0x48, 0x3B, 0x18, 85 | 0xAD, 0x19, 0x58, 0x22, 0x8A, 0x3C, 0x91, 0x30, 0xA6, 0x77, 0x99, 0x6F, 0xAA, 0x03, 0x7F, 0x60, 86 | 0x08, 0x15, 0x63, 0x58, 0xEB, 0x0E, 0x51, 0x91, 0x30, 0xE8, 0x9C, 0x60, 0x85, 0x96, 0xBB, 0x15, 87 | 0x41, 0x2A, 0x89, 0xA5, 0x3B, 0xAA, 0xC3, 0x1C, 0xF4, 0x9F, 0x6E, 0x05, 0x0B, 0x2F, 0x88, 0xD2, 88 | 0x6A, 0x3F, 0x27, 0x63, 0xB4, 0x49, 0xC6, 0xEE, 0x10, 0x96, 0xC7, 0x47, 0x0F, 0x4A, 0x9D, 0xEF, 89 | 0x81, 0xD5, 0xDA, 0xA5, 0xFB, 0xB8, 0x39, 0x2C, 0x74, 0xFB, 0xF5, 0xBA, 0xA8, 0xDE, 0x9A, 0xA7, 90 | 0x23, 0x0C, 0xAE, 0x58, 0x9B, 0xDF, 0x0C, 0x18, 0x3A, 0x1E, 0xE4, 0xF9, 0xA9, 0x66, 0x05, 0xE8, 91 | 0x99, 0x8E, 0x64, 0x4A, 0x6D, 0x3A, 0xA3, 0xBE, 0x0F, 0x61, 0xDD, 0x5D, 0x4A, 0x35, 0x8D, 0xB7, 92 | 0x4E, 0x0E, 0xE4, 0x4D, 0xE5, 0x93, 0x03, 0x71, 0xAF, 0x7B, 0xE7, 0x24, 0xBF, 0xAD, 0x3C, 0x12, 93 | 0x9D, 0xF2, 0x44, 0xD8, 0xF1, 0x02, 0x92, 0xA6, 0xA7, 0x6E, 0x7E, 0xFF, 0xD4, 0x15, 0x5D, 0xBC, 94 | 0xDB, 0xA7, 0x97, 0x0E, 0xF5, 0x4F, 0xDD, 0x20, 0x9A, 0x46, 0xEA, 0xBA, 0xE8, 0x13, 0xE7, 0x84, 95 | 0x18, 0x22, 0xA7, 0x6E, 0xE1, 0x7C, 0xD4, 0xE5, 0x33, 0xD4, 0x25, 0x77, 0x74, 0xFF, 0xA3, 0xCF, 96 | 0x1E, 0x3E, 0x3C, 0x7A, 0x74, 0x3F, 0x1C, 0xA7, 0xB1, 0xFC, 0xFF, 0xF7, 0xBC, 0x4B, 0xDE, 0x03, 97 | 0xC7, 0xE8, 0x61, 0x8C, 0x86, 0xD3, 0xF4, 0xE4, 0x80, 0x13, 0xBD, 0x25, 0xC0, 0x01, 0x4A, 0x50, 98 | 0x22, 0x8F, 0xCC, 0x86, 0x52, 0x24, 0xAD, 0x3B, 0xA5, 0x3E, 0x8C, 0x49, 0xA2, 0xBA, 0xD5, 0x10, 99 | 0xB1, 0x40, 0xF2, 0x9A, 0xC1, 0xE5, 0x99, 0x63, 0x1C, 0x5D, 0xE5, 0x12, 0x6B, 0x4A, 0x88, 0xB4, 100 | 0x82, 0xD7, 0x65, 0xA3, 0x8C, 0x58, 0x48, 0x04, 0xBF, 0xFC, 0x20, 0x58, 0xEF, 0x57, 0x32, 0x49, 101 | 0xEB, 0xDE, 0x3A, 0xC2, 0x14, 0x2C, 0x27, 0x09, 0x99, 0x43, 0x1E, 0xF9, 0xF2, 0xA2, 0x4E, 0x42, 102 | 0xB7, 0xB6, 0x9A, 0x85, 0x96, 0x7D, 0x05, 0x3C, 0x86, 0x10, 0x41, 0xCD, 0x74, 0x1A, 0x05, 0x91, 103 | 0xA2, 0x8A, 0x7C, 0xDD, 0xA5, 0x68, 0xF2, 0xC0, 0xAA, 0x4D, 0xB8, 0x3B, 0x48, 0x41, 0x2A, 0x49, 104 | 0x45, 0x31, 0x77, 0x9A, 0x4B, 0x12, 0x64, 0x68, 0xC6, 0x5E, 0xD7, 0x1D, 0xFD, 0xF0, 0xF3, 0xB3, 105 | 0xC7, 0x2D, 0xCC, 0x04, 0xDD, 0xAB, 0x5E, 0xBF, 0xDB, 0xDD, 0x3B, 0x39, 0x10, 0x43, 0xAC, 0xE8, 106 | 0x7C, 0xE6, 0x8E, 0x2E, 0x38, 0x99, 0xFE, 0x31, 0x92, 0xE9, 0xF6, 0x07, 0xCD, 0xC8, 0x1C, 0xBB, 107 | 0x23, 0x4E, 0x05, 0x09, 0x5C, 0x3D, 0x3C, 0x3A, 0x6E, 0x46, 0xE4, 0x21, 0xCA, 0xF2, 0x23, 0x52, 108 | 0x39, 0x46, 0x8D, 0x8E, 0x9A, 0x2A, 0x74, 0xE4, 0x8E, 0x72, 0x1A, 0xE8, 0xE1, 0x57, 0x83, 0xE3, 109 | 0x86, 0x34, 0x1E, 0xB8, 0xA3, 0xB3, 0xAF, 0xBE, 0x6C, 0x0D, 0x50, 0x8E, 0xFE, 0x67, 0x47, 0xCD, 110 | 0x68, 0x0C, 0x5C, 0xB9, 0x3E, 0xE5, 0xEE, 0xBC, 0x6C, 0xB9, 0xA3, 0xEF, 0x72, 0xE1, 0x0E, 0xFB, 111 | 0x48, 0x78, 0xD0, 0x50, 0xB8, 0x43, 0x77, 0xF4, 0x9C, 0x53, 0x41, 0x0A, 0x57, 0xBD, 0x87, 0x0D, 112 | 0xC5, 0xEB, 0xA2, 0x28, 0x9C, 0x4A, 0xEF, 0x88, 0xBB, 0x8F, 0x99, 0x0A, 0xE6, 0x05, 0xA1, 0x86, 113 | 0x3E, 0x42, 0x65, 0x0D, 0xFB, 0x70, 0xFC, 0x3D, 0x23, 0x01, 0x16, 0xC4, 0x56, 0xC1, 0x28, 0xE7, 114 | 0xA0, 0x0A, 0xA2, 0x51, 0x88, 0x43, 0x93, 0x04, 0xAB, 0xBB, 0x0F, 0xEE, 0x68, 0xA0, 0x09, 0x5D, 115 | 0x99, 0xB8, 0xE4, 0xBC, 0x82, 0xCC, 0x6E, 0xBE, 0x22, 0x73, 0xA4, 0xE7, 0xE4, 0x0A, 0x3D, 0xEF, 116 | 0xD0, 0x55, 0xD1, 0x69, 0x0E, 0x72, 0xB3, 0x8C, 0x48, 0xD5, 0x1D, 0x1D, 0x1D, 0x56, 0x0A, 0xD9, 117 | 0xDC, 0xE8, 0x63, 0xBE, 0xE2, 0x87, 0x90, 0xA6, 0x56, 0x76, 0x57, 0xD3, 0xDC, 0xD1, 0x93, 0x55, 118 | 0xBB, 0xA9, 0xF5, 0xDB, 0xFD, 0x86, 0xE6, 0x57, 0x62, 0x48, 0x04, 0xDA, 0x7D, 0x09, 0x41, 0x7F, 119 | 0x85, 0xC0, 0xFB, 0x03, 0xA0, 0xBF, 0x01, 0xFB, 0xE7, 0x4B, 0x69, 0x42, 0x52, 0x66, 0x61, 0x7D, 120 | 0x35, 0x09, 0x93, 0x93, 0x6C, 0x7D, 0x60, 0xCB, 0x2B, 0x11, 0xFE, 0xAD, 0x76, 0x4F, 0x09, 0xCB, 121 | 0x12, 0x92, 0xCB, 0x63, 0x65, 0x79, 0x35, 0x0D, 0x57, 0xA8, 0x55, 0xFB, 0x83, 0x5B, 0x5F, 0x89, 122 | 0xF1, 0xAF, 0xB5, 0x7F, 0x0C, 0x1E, 0x25, 0xC1, 0x6B, 0x98, 0x4C, 0xC0, 0xB3, 0xF3, 0xFE, 0xE2, 123 | 0x54, 0xC4, 0x41, 0x7C, 0x76, 0xCE, 0xF9, 0x67, 0x8B, 0x4A, 0x4C, 0x23, 0xF5, 0x3E, 0xCA, 0xB1, 124 | 0x6E, 0xF9, 0x6A, 0xFF, 0x4D, 0x24, 0xE5, 0x6B, 0xB4, 0x46, 0xF7, 0x90, 0x00, 0x4C, 0xF9, 0x46, 125 | 0xB0, 0xD1, 0xFC, 0xBE, 0x3B, 0x7A, 0x96, 0x90, 0x6B, 0xFE, 0x38, 0x6C, 0xD3, 0x52, 0xE3, 0x15, 126 | 0xF8, 0xCE, 0xF7, 0x34, 0x6C, 0xA6, 0xC0, 0x20, 0x17, 0x00, 0x20, 0x6C, 0x4E, 0xE1, 0x01, 0x2E, 127 | 0x34, 0xD8, 0x68, 0x4E, 0xE0, 0x08, 0x3D, 0x05, 0x62, 0x4A, 0xB6, 0x55, 0xDE, 0x90, 0xC5, 0xD8, 128 | 0xCA, 0xCD, 0x71, 0xBC, 0x3B, 0x7A, 0xFC, 0xD3, 0x13, 0xAB, 0xE4, 0x22, 0xCE, 0x21, 0x25, 0x07, 129 | 0x43, 0x56, 0x91, 0x42, 0xB9, 0xDA, 0x76, 0xAC, 0x34, 0x0A, 0x2A, 0xB7, 0x64, 0x06, 0x5D, 0x24, 130 | 0x31, 0x71, 0x3A, 0xE4, 0xDE, 0x52, 0x4D, 0xD3, 0xCB, 0xC6, 0xD0, 0xCD, 0x31, 0x78, 0x3D, 0x25, 131 | 0xD4, 0x26, 0xE7, 0xAB, 0x49, 0x1C, 0x0D, 0xE7, 0x19, 0xB6, 0x36, 0x08, 0x89, 0x62, 0xB7, 0x0D, 132 | 0x5C, 0x24, 0xEB, 0xED, 0x80, 0x83, 0xCC, 0xE7, 0x91, 0x6F, 0xB7, 0x19, 0x97, 0x73, 0xDC, 0x11, 133 | 0x22, 0xF3, 0x02, 0x1B, 0x56, 0xD9, 0x5F, 0x4E, 0xDE, 0x64, 0xDA, 0x7F, 0x9C, 0xB1, 0xA8, 0x69, 134 | 0xC6, 0xBF, 0xC8, 0xC2, 0xF0, 0xBA, 0x69, 0xBA, 0x3F, 0x0B, 0xA2, 0xCC, 0xBF, 0x6E, 0x9A, 0xEB, 135 | 0x5F, 0x4E, 0x26, 0xD4, 0x83, 0xA6, 0x99, 0xFE, 0x79, 0x34, 0x87, 0xAD, 0x65, 0x59, 0xF0, 0xEC, 136 | 0x82, 0x1B, 0x3C, 0x44, 0xE9, 0xFC, 0xCC, 0xB9, 0x38, 0xFF, 0xE6, 0xE2, 0xE5, 0xAB, 0xCD, 0x46, 137 | 0x36, 0x78, 0xDB, 0x08, 0x6A, 0xF0, 0xB6, 0x94, 0x6C, 0xC1, 0xEB, 0xDB, 0x62, 0xD1, 0x17, 0x60, 138 | 0x3C, 0xBD, 0xF8, 0x76, 0xD3, 0x48, 0xF4, 0xB7, 0x03, 0x45, 0x7F, 0x5B, 0x58, 0xBC, 0x0E, 0xE0, 139 | 0x12, 0x02, 0x0B, 0x3C, 0xD4, 0xA4, 0x1C, 0x13, 0xE7, 0xEB, 0xBC, 0xF5, 0x81, 0x37, 0x3A, 0x4A, 140 | 0x84, 0x7F, 0xEB, 0x36, 0x07, 0x11, 0x7F, 0xCD, 0x05, 0xB5, 0x31, 0xBC, 0x9A, 0xE5, 0x8E, 0xCE, 141 | 0xAF, 0xE2, 0x28, 0xCD, 0x12, 0x68, 0x6A, 0xF9, 0x6E, 0x33, 0xC3, 0x2B, 0x11, 0xA4, 0xE5, 0xBB, 142 | 0xD2, 0xF0, 0xF9, 0xF9, 0xF2, 0xCA, 0xF6, 0xFD, 0xEE, 0xE0, 0xBD, 0x59, 0x3F, 0x27, 0xBC, 0x09, 143 | 0x00, 0xA6, 0x96, 0xEB, 0xC1, 0x34, 0x5F, 0x0F, 0x9E, 0x9D, 0x6D, 0x36, 0xFD, 0x4C, 0xB7, 0xB2, 144 | 0x10, 0x4C, 0x3F, 0xF8, 0x42, 0xE0, 0x88, 0xFB, 0x65, 0x2B, 0x28, 0x6C, 0x8B, 0x6F, 0x35, 0xC9, 145 | 0x1D, 0xA9, 0xC2, 0xDB, 0x3E, 0x0A, 0x7A, 0x57, 0xCD, 0xC2, 0x40, 0xB1, 0x2F, 0x46, 0xC1, 0xA1, 146 | 0x8A, 0x81, 0x07, 0xEF, 0x2D, 0x02, 0x0E, 0x7B, 0x57, 0x1B, 0x08, 0x80, 0x5C, 0x7A, 0x0F, 0x68, 147 | 0x40, 0xC3, 0xA9, 0x85, 0xE1, 0x0B, 0xF3, 0x84, 0xED, 0x9D, 0x33, 0xF1, 0xA9, 0x29, 0x06, 0x7D, 148 | 0x7B, 0x0C, 0x34, 0x0D, 0xEE, 0xC0, 0x70, 0xB4, 0x81, 0x55, 0x00, 0x6F, 0x7F, 0x6D, 0x02, 0x86, 149 | 0x71, 0x6C, 0x97, 0x87, 0x70, 0x3C, 0x1E, 0x78, 0x7C, 0xBB, 0xD1, 0x3C, 0x84, 0x4C, 0xD6, 0xCD, 150 | 0x43, 0xCD, 0xB3, 0x0E, 0xF2, 0xD8, 0xD2, 0x76, 0xD2, 0xD2, 0xE2, 0x38, 0x1E, 0xB7, 0x91, 0x9B, 151 | 0xB5, 0x38, 0x32, 0xD9, 0x46, 0xE6, 0x5F, 0x6C, 0x0B, 0x83, 0x84, 0x2C, 0x5E, 0x4F, 0xE7, 0xC4, 152 | 0x0A, 0x07, 0x39, 0x07, 0x0F, 0x1C, 0xC9, 0xC2, 0x79, 0xF6, 0xE2, 0xF1, 0x46, 0xF1, 0x90, 0xCC, 153 | 0xB6, 0x80, 0x89, 0xE4, 0xBC, 0x1D, 0x5C, 0x02, 0x08, 0x6D, 0x82, 0x43, 0x4C, 0x70, 0x47, 0x5F, 154 | 0x43, 0x98, 0x3A, 0x67, 0x51, 0x22, 0xBF, 0xCD, 0xBF, 0x41, 0x64, 0x04, 0xC7, 0x2D, 0xC0, 0x92, 155 | 0xB3, 0xDD, 0x0E, 0x26, 0xB3, 0x39, 0x4D, 0x92, 0x28, 0xB1, 0x82, 0x45, 0xCE, 0xC1, 0x23, 0x97, 156 | 0xF6, 0x0B, 0xDE, 0xDA, 0x28, 0x24, 0x92, 0xDB, 0x16, 0x50, 0x91, 0x9C, 0xB7, 0x03, 0xCC, 0xE5, 157 | 0x24, 0xA0, 0xB1, 0x05, 0x2C, 0x72, 0x06, 0x3E, 0x7D, 0xD2, 0xFE, 0x12, 0xDF, 0x37, 0x08, 0x89, 158 | 0xE4, 0xB4, 0x05, 0x40, 0x38, 0xDF, 0xED, 0xC0, 0xE1, 0x7B, 0x0B, 0x2B, 0x30, 0x70, 0xBC, 0x3B, 159 | 0x7A, 0x7A, 0xF6, 0x93, 0xD3, 0x7A, 0x1A, 0x2D, 0xC2, 0xFC, 0xB1, 0x2B, 0xE7, 0xFC, 0x9B, 0xBD, 160 | 0x8D, 0xA2, 0x82, 0x2C, 0xB7, 0x80, 0x09, 0x72, 0xDD, 0x0E, 0x22, 0xFC, 0x81, 0xCC, 0x31, 0xB1, 161 | 0x49, 0x5D, 0x6A, 0x12, 0x1E, 0x55, 0xE7, 0x2D, 0xE7, 0x09, 0xD9, 0x64, 0xF2, 0x52, 0xFC, 0x36, 162 | 0x5C, 0xF0, 0x2A, 0x46, 0x1B, 0xC2, 0x42, 0x3D, 0x35, 0xCA, 0xD5, 0x92, 0xBF, 0x25, 0x50, 0x67, 163 | 0x74, 0x31, 0x44, 0x6C, 0xA5, 0x80, 0xB5, 0x53, 0x46, 0x83, 0x00, 0xB7, 0x74, 0xC0, 0x9C, 0x8B, 164 | 0xBC, 0x79, 0x72, 0x20, 0x06, 0xAC, 0x47, 0x41, 0x3E, 0xA2, 0x99, 0xB2, 0x04, 0xC8, 0x1C, 0xEF, 165 | 0x51, 0x30, 0x92, 0x20, 0x1D, 0xFE, 0xA9, 0x96, 0x10, 0x2A, 0x24, 0xC4, 0xC6, 0x6E, 0xBD, 0x2B, 166 | 0x24, 0xA8, 0xAB, 0x41, 0xFD, 0x13, 0xF1, 0x1B, 0x17, 0xEA, 0xA2, 0xF6, 0xDC, 0xA9, 0x10, 0x4A, 167 | 0x3D, 0x54, 0xBD, 0x82, 0xF6, 0xEE, 0xC3, 0xD6, 0xF2, 0x54, 0xC2, 0xEC, 0xEA, 0xFC, 0x11, 0x6A, 168 | 0xE9, 0xE4, 0xD8, 0x54, 0x7A, 0xFF, 0xF5, 0x67, 0x1D, 0x40, 0x74, 0x3E, 0xBD, 0x25, 0x90, 0xEB, 169 | 0xA4, 0x89, 0x77, 0xEA, 0x4A, 0x76, 0x26, 0x2D, 0x0F, 0x94, 0x9A, 0xC5, 0x41, 0xBA, 0x0D, 0x4F, 170 | 0x52, 0x2F, 0xA1, 0x31, 0x53, 0x43, 0xFD, 0xC8, 0xCB, 0xE6, 0x10, 0xB2, 0x0E, 0xF1, 0xFD, 0xF3, 171 | 0x4B, 0x6C, 0x7C, 0x4D, 0x53, 0x06, 0xA8, 0x71, 0x6B, 0xF7, 0xE9, 0xCB, 0x17, 0x67, 0xA8, 0x7F, 172 | 0x7E, 0x2D, 0x22, 0x3E, 0xF8, 0xBB, 0xFB, 0xCE, 0x24, 0x0B, 0x85, 0x1F, 0xB5, 0x20, 0x1F, 0x7B, 173 | 0xF7, 0x27, 0x5B, 0x2E, 0x49, 0xE2, 0x8C, 0x49, 0x0A, 0xCF, 0xA3, 0x94, 0x39, 0xA7, 0x8A, 0x76, 174 | 0x10, 0x79, 0x24, 0x9F, 0xD6, 0x89, 0x12, 0x3A, 0xA5, 0xA1, 0x36, 0x47, 0x28, 0xFD, 0x43, 0x12, 175 | 0x38, 0xA7, 0x6A, 0xFE, 0xA7, 0xCE, 0xEE, 0xF0, 0xB8, 0xB7, 0xBB, 0x73, 0xF7, 0x4B, 0x15, 0x29, 176 | 0xCB, 0xC1, 0x00, 0x1C, 0x0A, 0x38, 0x7E, 0xA4, 0xFF, 0x80, 0x09, 0x5E, 0xEF, 0x70, 0x30, 0x50, 177 | 0x15, 0xAE, 0x57, 0x6B, 0x57, 0xA0, 0xB7, 0xBB, 0x57, 0x18, 0x7A, 0x53, 0x42, 0x39, 0x9D, 0x45, 178 | 0x8B, 0x75, 0x29, 0x27, 0x30, 0x8F, 0x2E, 0xA1, 0x92, 0x78, 0x09, 0x75, 0xF1, 0x1D, 0x74, 0x3B, 179 | 0xD1, 0xE5, 0x1C, 0x3F, 0xA7, 0xAF, 0x0F, 0x5E, 0xF6, 0x22, 0x49, 0x96, 0x64, 0x60, 0x16, 0x01, 180 | 0xC2, 0x7C, 0xBC, 0x9D, 0x8A, 0x16, 0x42, 0x4C, 0x48, 0x90, 0xAE, 0x21, 0x45, 0x16, 0xFB, 0x84, 181 | 0xC1, 0x8F, 0x24, 0xC8, 0x72, 0x51, 0x5A, 0x10, 0xEC, 0x8B, 0x53, 0x99, 0x7D, 0xD9, 0xF3, 0x0A, 182 | 0xF9, 0x32, 0xD8, 0x2B, 0x95, 0xB0, 0x30, 0x04, 0x67, 0x17, 0x3F, 0x9E, 0x3A, 0x61, 0x16, 0x04, 183 | 0xCE, 0xE7, 0xDC, 0x1C, 0xCE, 0xB0, 0xD0, 0xAB, 0x51, 0x0A, 0x80, 0x39, 0xF2, 0x77, 0xDF, 0x72, 184 | 0x59, 0xF4, 0x01, 0x74, 0x92, 0x0B, 0xD7, 0xC1, 0xF4, 0x8F, 0xA4, 0x91, 0xF6, 0xEE, 0x72, 0x09, 185 | 0xD8, 0xE5, 0xAE, 0xAF, 0xBF, 0x0A, 0xE4, 0xB8, 0x95, 0x3B, 0x72, 0x91, 0x2E, 0x1D, 0x7D, 0x29, 186 | 0x87, 0xDD, 0xBB, 0xC7, 0x5B, 0xA5, 0x63, 0x14, 0x09, 0x1C, 0x58, 0x3E, 0xEC, 0x06, 0x07, 0xA5, 187 | 0xB0, 0xB6, 0x44, 0x35, 0xBC, 0x64, 0x67, 0x35, 0xA7, 0x9D, 0x52, 0x1B, 0x15, 0x30, 0xB8, 0x7F, 188 | 0xBF, 0xC8, 0xF3, 0xDE, 0xA9, 0xA4, 0x56, 0x65, 0x33, 0x31, 0x1B, 0x73, 0x0D, 0xE6, 0x31, 0x34, 189 | 0xF7, 0xDE, 0xA3, 0x0A, 0x05, 0x39, 0xAB, 0x7B, 0xB7, 0x78, 0x29, 0x8A, 0xE5, 0xB8, 0x51, 0x9F, 190 | 0xA3, 0xC6, 0xEF, 0x67, 0x6A, 0x43, 0x35, 0x18, 0x3E, 0xE7, 0x79, 0xA5, 0x05, 0xE2, 0xF6, 0x09, 191 | 0xD2, 0x1E, 0xF2, 0x74, 0xA0, 0x2E, 0xA8, 0xF9, 0x9A, 0x68, 0x1A, 0xC7, 0xA9, 0xC6, 0x51, 0x13, 192 | 0x50, 0xB7, 0x89, 0xFE, 0x12, 0x12, 0xE4, 0xC7, 0x99, 0xF2, 0x28, 0x75, 0xAF, 0x6E, 0xB4, 0x50, 193 | 0x00, 0x79, 0xE7, 0x87, 0xAF, 0xDA, 0x48, 0xCD, 0x5D, 0x8C, 0xA4, 0x0A, 0x8C, 0xCD, 0x62, 0x9A, 194 | 0x19, 0xDB, 0x58, 0x50, 0x3E, 0x58, 0xB2, 0x0E, 0x70, 0x82, 0xFD, 0x62, 0x8C, 0x90, 0x09, 0xB9, 195 | 0xB1, 0x59, 0xCE, 0x4A, 0xF7, 0xAC, 0x9A, 0x64, 0xB5, 0x5A, 0xEE, 0x34, 0x0F, 0xCD, 0x45, 0xD2, 196 | 0x73, 0x49, 0x45, 0xCC, 0x88, 0xB2, 0x73, 0x99, 0x48, 0x2A, 0x11, 0xF7, 0x48, 0x0A, 0xB7, 0x12, 197 | 0xCC, 0xB0, 0x5A, 0x69, 0x2D, 0xB7, 0xA0, 0x05, 0x7A, 0xA8, 0x7A, 0xB7, 0x72, 0xCA, 0x38, 0x01, 198 | 0xF2, 0xB6, 0x86, 0x2B, 0x3F, 0xCC, 0x46, 0x96, 0xD5, 0x23, 0xC4, 0x83, 0x17, 0xED, 0x28, 0x84, 199 | 0xF5, 0x24, 0xE3, 0xCD, 0xC6, 0xF2, 0x88, 0xAA, 0xB0, 0x5E, 0xA0, 0x6C, 0x3C, 0xA7, 0x6C, 0x0D, 200 | 0x61, 0x76, 0x7B, 0xBB, 0x0D, 0xE4, 0x90, 0xA5, 0x7D, 0x35, 0xF9, 0x04, 0x58, 0x96, 0x84, 0xA6, 201 | 0x04, 0xA9, 0x96, 0xBC, 0xDF, 0x33, 0x48, 0xAE, 0x51, 0x9C, 0x37, 0x1F, 0xBF, 0x5B, 0x96, 0x38, 202 | 0x37, 0x07, 0xFC, 0x91, 0xE8, 0x28, 0xF8, 0x1C, 0x8B, 0xA0, 0xD3, 0x8F, 0xDF, 0x71, 0xEF, 0xBF, 203 | 0xB9, 0x8F, 0x82, 0xE3, 0x07, 0x2E, 0xFE, 0xCD, 0x1B, 0x9D, 0xDC, 0x04, 0xD0, 0x9B, 0x5A, 0x9C, 204 | 0x5C, 0xB9, 0x93, 0x77, 0xD8, 0x0C, 0xC2, 0x56, 0x02, 0x69, 0x8C, 0x8C, 0x41, 0x5B, 0x43, 0x35, 205 | 0xD9, 0xA2, 0x00, 0xB0, 0x42, 0x9B, 0xB6, 0xDE, 0x24, 0x80, 0x54, 0x51, 0x54, 0x16, 0x39, 0x1F, 206 | 0xBF, 0xE3, 0x0C, 0x6E, 0x9C, 0x09, 0x0D, 0x69, 0x3A, 0x03, 0x7F, 0xDF, 0x49, 0x19, 0x61, 0x59, 207 | 0x3A, 0xC4, 0xAE, 0x25, 0xE9, 0x8E, 0xB8, 0x74, 0xF3, 0xA6, 0x22, 0xD8, 0x6A, 0x2A, 0x22, 0x55, 208 | 0x78, 0x6A, 0x53, 0x3B, 0x9C, 0xF3, 0x05, 0xF7, 0xB7, 0x28, 0x79, 0x1C, 0x04, 0xAD, 0x5D, 0xF1, 209 | 0xBD, 0xC4, 0x92, 0xE2, 0xA3, 0x83, 0xDB, 0xA7, 0x73, 0x82, 0xE6, 0x90, 0xD5, 0x4C, 0xD5, 0xA2, 210 | 0x16, 0x85, 0x5E, 0x40, 0xBD, 0xB7, 0x79, 0xAD, 0xA1, 0x17, 0x15, 0x7A, 0xDA, 0xC3, 0x19, 0xE2, 211 | 0x17, 0x16, 0xBE, 0x89, 0x7C, 0x58, 0x3B, 0x93, 0xEC, 0x15, 0x15, 0x3C, 0x38, 0x40, 0x1F, 0x21, 212 | 0xFE, 0x72, 0x2D, 0x14, 0xFE, 0x98, 0xEE, 0xE8, 0x50, 0x16, 0x3C, 0x42, 0x98, 0x54, 0x5A, 0x54, 213 | 0x47, 0x55, 0x15, 0xE0, 0x12, 0x04, 0x9E, 0x4E, 0xAA, 0xFD, 0xD3, 0x59, 0x61, 0xF5, 0x5B, 0x1A, 214 | 0x85, 0x2D, 0x9D, 0xEC, 0x8D, 0x99, 0x53, 0x2E, 0x52, 0x81, 0x8D, 0x01, 0x46, 0x33, 0x9C, 0xC5, 215 | 0x7D, 0xB3, 0x86, 0xAB, 0x0D, 0xBE, 0xEA, 0x75, 0xBB, 0xA8, 0xE4, 0x15, 0x25, 0x97, 0xFB, 0x17, 216 | 0x1E, 0x54, 0xBF, 0xEE, 0x8B, 0xCA, 0xB4, 0x66, 0x69, 0x2A, 0x33, 0x4D, 0x59, 0xE9, 0x7A, 0x49, 217 | 0x61, 0x71, 0x7B, 0x6F, 0x33, 0x05, 0x76, 0x1E, 0x40, 0xDE, 0x7C, 0x72, 0xFD, 0x15, 0xD6, 0xEB, 218 | 0x62, 0x2F, 0x23, 0x75, 0xD2, 0xA7, 0x9E, 0x2D, 0x77, 0x94, 0x66, 0x1A, 0x6A, 0xF7, 0x59, 0x4A, 219 | 0x8D, 0x6F, 0xCA, 0x9F, 0xF0, 0x44, 0x59, 0x47, 0x6B, 0xB5, 0x7F, 0x2F, 0x25, 0x22, 0x38, 0x99, 220 | 0xA9, 0xC8, 0x3D, 0x7C, 0x8D, 0x72, 0x3C, 0x44, 0xCD, 0x84, 0xF8, 0x30, 0x45, 0xA7, 0x54, 0xA4, 221 | 0x28, 0x16, 0xA7, 0x03, 0x35, 0xE1, 0xBA, 0xA0, 0xA1, 0x1F, 0x2D, 0x3A, 0xF9, 0xD8, 0x96, 0x5E, 222 | 0x2F, 0x16, 0xD4, 0xEA, 0xD0, 0x10, 0x4D, 0xF8, 0xFC, 0xFB, 0x17, 0x5F, 0xE7, 0x8B, 0xC1, 0xED, 223 | 0xD3, 0x87, 0x5D, 0xF3, 0x46, 0x25, 0xCD, 0x87, 0x1B, 0xA5, 0x41, 0x60, 0x51, 0x96, 0xC4, 0x13, 224 | 0xE9, 0x7D, 0xB5, 0x9B, 0xBD, 0x39, 0x10, 0xCD, 0x37, 0x3B, 0xA5, 0xE5, 0x52, 0xC1, 0x1D, 0xF6, 225 | 0xAC, 0x74, 0x88, 0x62, 0x93, 0x0A, 0x32, 0x03, 0x3D, 0x66, 0x0C, 0xC3, 0xC7, 0x11, 0x81, 0x96, 226 | 0xE6, 0x99, 0x5D, 0x9E, 0xFE, 0x14, 0x06, 0xDE, 0x72, 0x25, 0x73, 0xAA, 0xBC, 0x05, 0x50, 0x6B, 227 | 0xAF, 0xD2, 0x14, 0xDA, 0x4A, 0x47, 0x62, 0xCC, 0x47, 0xF0, 0xF9, 0x6B, 0x6F, 0x8C, 0x8B, 0xDB, 228 | 0x53, 0xC2, 0xA0, 0x13, 0xA2, 0x0D, 0xF6, 0x6E, 0x6C, 0x8D, 0xA3, 0x03, 0xA5, 0x1C, 0xCF, 0x5E, 229 | 0x78, 0x3D, 0xF9, 0x9B, 0x38, 0xEB, 0x08, 0x99, 0x59, 0xEB, 0x11, 0x77, 0x1E, 0xCA, 0x1D, 0x73, 230 | 0x25, 0xCC, 0xA7, 0x3A, 0xD0, 0xFA, 0x16, 0xA2, 0x40, 0x4C, 0x4F, 0xD1, 0xBA, 0xCA, 0x56, 0x5B, 231 | 0x47, 0xE1, 0xFD, 0xD5, 0x53, 0xD7, 0xF3, 0x3D, 0x99, 0xEC, 0x1D, 0xCC, 0x0B, 0xDE, 0x2C, 0xAF, 232 | 0x33, 0xA5, 0x37, 0x1A, 0xAB, 0x01, 0xFB, 0xE5, 0xC3, 0xBE, 0x2C, 0x90, 0x12, 0x49, 0xD0, 0xB4, 233 | 0x3A, 0x7F, 0x9D, 0x45, 0xFE, 0x2C, 0x43, 0x03, 0xCF, 0x97, 0x21, 0x76, 0xB7, 0x37, 0xDF, 0x10, 234 | 0x95, 0xE4, 0x15, 0x32, 0xF5, 0xEA, 0xB2, 0x24, 0x76, 0x97, 0x26, 0x59, 0xB9, 0xC3, 0xAA, 0x9B, 235 | 0x5A, 0x7C, 0x64, 0xAA, 0x94, 0xCA, 0xAD, 0x5D, 0x5D, 0x1D, 0xA5, 0xE2, 0x63, 0x40, 0x3A, 0x31, 236 | 0xE4, 0xA4, 0x59, 0x50, 0xB7, 0x75, 0xD1, 0xA6, 0x38, 0x67, 0xAF, 0xD4, 0x93, 0xB1, 0x43, 0xEC, 237 | 0x66, 0x94, 0x1F, 0x9B, 0xB7, 0xC2, 0xA6, 0x2D, 0xB0, 0xC1, 0xDD, 0xCD, 0x5B, 0x5E, 0xD3, 0x56, 238 | 0xD7, 0x3E, 0x28, 0x96, 0x0F, 0x67, 0x96, 0x81, 0x0B, 0xF5, 0x3E, 0x01, 0xE5, 0x3E, 0x01, 0x92, 239 | 0xA2, 0x61, 0xAE, 0x7A, 0xA6, 0x54, 0x07, 0x12, 0x1A, 0x00, 0x09, 0x25, 0x40, 0x12, 0x58, 0x81, 240 | 0x68, 0x3E, 0x4D, 0x31, 0x65, 0x8F, 0x9F, 0x9E, 0x94, 0xD9, 0x68, 0x31, 0xAE, 0xD5, 0x53, 0x1E, 241 | 0x1A, 0x94, 0x1A, 0xAA, 0x7E, 0xAA, 0xFA, 0x2A, 0x89, 0x6E, 0x20, 0x24, 0x6B, 0x6F, 0x20, 0xED, 242 | 0x0C, 0x42, 0x12, 0x92, 0x06, 0x32, 0x9E, 0x5A, 0x28, 0x5F, 0x52, 0x69, 0xE7, 0xE4, 0x60, 0x79, 243 | 0x7E, 0x7F, 0x72, 0x20, 0x7E, 0x13, 0x04, 0x1B, 0xFC, 0x1F, 0xC6, 0xF8, 0x1B, 0x1E, 0x2A, 0xAE, 244 | 0xA1, 0x2F, 0x63, 0x00, 0x00 245 | }; 246 | 247 | 248 | //File: index_ov3660.html.gz, Size: 4408 249 | #define index_ov3660_html_gz_len 4408 250 | const uint8_t index_ov3660_html_gz[] = { 251 | 0x1F, 0x8B, 0x08, 0x08, 0x28, 0x5C, 0xAE, 0x5C, 0x00, 0x03, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x5F, 252 | 0x6F, 0x76, 0x33, 0x36, 0x36, 0x30, 0x2E, 0x68, 0x74, 0x6D, 0x6C, 0x00, 0xE5, 0x5D, 0xEB, 0x92, 253 | 0xD3, 0xC6, 0x12, 0xFE, 0xCF, 0x53, 0x08, 0x41, 0x58, 0x6F, 0x65, 0xED, 0xF5, 0x6D, 0xCD, 0xE2, 254 | 0xD8, 0xE6, 0xC0, 0xB2, 0x84, 0x54, 0x01, 0x49, 0x20, 0x21, 0xA9, 0x4A, 0xA5, 0x60, 0x2C, 0x8D, 255 | 0xED, 0x09, 0xB2, 0xE4, 0x48, 0x23, 0x7B, 0x37, 0xD4, 0x3E, 0xC7, 0x79, 0xA0, 0xF3, 0x62, 0xA7, 256 | 0xE7, 0x22, 0x69, 0x24, 0x8F, 0x2E, 0xB6, 0x59, 0x9B, 0xC3, 0x31, 0x55, 0x20, 0x5B, 0xD3, 0x3D, 257 | 0xDD, 0xFD, 0xF5, 0x6D, 0x46, 0x17, 0x06, 0x77, 0x6D, 0xCF, 0xA2, 0xD7, 0x0B, 0x6C, 0xCC, 0xE8, 258 | 0xDC, 0x19, 0xDD, 0x19, 0x88, 0x7F, 0x0C, 0xF8, 0x0C, 0x66, 0x18, 0xD9, 0xE2, 0x90, 0x7F, 0x9D, 259 | 0x63, 0x8A, 0x0C, 0x6B, 0x86, 0xFC, 0x00, 0xD3, 0xA1, 0x19, 0xD2, 0x49, 0xFD, 0xDC, 0xCC, 0x9E, 260 | 0x76, 0xD1, 0x1C, 0x0F, 0xCD, 0x25, 0xC1, 0xAB, 0x85, 0xE7, 0x53, 0xD3, 0xB0, 0x3C, 0x97, 0x62, 261 | 0x17, 0x86, 0xAF, 0x88, 0x4D, 0x67, 0x43, 0x1B, 0x2F, 0x89, 0x85, 0xEB, 0xFC, 0xCB, 0x09, 0x71, 262 | 0x09, 0x25, 0xC8, 0xA9, 0x07, 0x16, 0x72, 0xF0, 0xB0, 0xA5, 0xF2, 0xA2, 0x84, 0x3A, 0x78, 0x74, 263 | 0xF9, 0xF6, 0xA7, 0x4E, 0xDB, 0xF8, 0xF1, 0x5D, 0xA7, 0xD7, 0x6B, 0x0E, 0x4E, 0xC5, 0x6F, 0xC9, 264 | 0x98, 0x80, 0x5E, 0xAB, 0xDF, 0xD9, 0x67, 0xEC, 0xD9, 0xD7, 0xC6, 0xA7, 0xD4, 0x4F, 0xEC, 0x33, 265 | 0x01, 0x21, 0xEA, 0x13, 0x34, 0x27, 0xCE, 0x75, 0xDF, 0x78, 0xE2, 0xC3, 0x9C, 0x27, 0x2F, 0xB0, 266 | 0xB3, 0xC4, 0x94, 0x58, 0xE8, 0x24, 0x40, 0x6E, 0x50, 0x0F, 0xB0, 0x4F, 0x26, 0xDF, 0xAD, 0x11, 267 | 0x8E, 0x91, 0xF5, 0x71, 0xEA, 0x7B, 0xA1, 0x6B, 0xF7, 0x8D, 0x7B, 0xAD, 0x73, 0xF6, 0x67, 0x7D, 268 | 0x90, 0xE5, 0x39, 0x9E, 0x0F, 0xE7, 0x2F, 0x9F, 0xB3, 0x3F, 0xEB, 0xE7, 0xF9, 0xEC, 0x01, 0xF9, 269 | 0x07, 0xF7, 0x8D, 0x56, 0x6F, 0x71, 0x95, 0x3A, 0x7F, 0x73, 0x27, 0xF5, 0x75, 0xD6, 0xCE, 0x93, 270 | 0x5E, 0xD2, 0x9F, 0x17, 0xD3, 0x07, 0xD8, 0xA2, 0xC4, 0x73, 0x1B, 0x73, 0x44, 0x5C, 0x0D, 0x27, 271 | 0x9B, 0x04, 0x0B, 0x07, 0x81, 0x0D, 0x26, 0x0E, 0x2E, 0xE4, 0x73, 0x6F, 0x8E, 0xDD, 0xF0, 0xA4, 272 | 0x84, 0x1B, 0x63, 0x52, 0xB7, 0x89, 0x2F, 0x46, 0xF5, 0x99, 0x1D, 0xC2, 0xB9, 0x5B, 0xCA, 0xB6, 273 | 0x48, 0x2E, 0xD7, 0x73, 0xB1, 0xC6, 0x80, 0x6C, 0xA2, 0x95, 0x8F, 0x16, 0x6C, 0x00, 0xFB, 0x77, 274 | 0x7D, 0xC8, 0x9C, 0xB8, 0xC2, 0xA9, 0xFA, 0x46, 0xA7, 0xDB, 0x5C, 0x5C, 0x95, 0x40, 0xD9, 0xE9, 275 | 0xB1, 0x3F, 0xEB, 0x83, 0x16, 0xC8, 0xB6, 0x89, 0x3B, 0xED, 0x1B, 0xE7, 0x5A, 0x16, 0x9E, 0x6F, 276 | 0x63, 0xBF, 0xEE, 0x23, 0x9B, 0x84, 0x41, 0xDF, 0xE8, 0xEA, 0xC6, 0xCC, 0x91, 0x3F, 0x05, 0x59, 277 | 0xA8, 0x07, 0xC2, 0xD6, 0x5B, 0x5A, 0x49, 0xE4, 0x10, 0x9F, 0x4C, 0x67, 0x14, 0x20, 0x5D, 0x1B, 278 | 0x93, 0x35, 0x9A, 0x0C, 0xA1, 0x32, 0x3C, 0x0B, 0xED, 0xA6, 0xB7, 0x1A, 0x72, 0xC8, 0xD4, 0xAD, 279 | 0x13, 0x8A, 0xE7, 0xA0, 0x4E, 0x40, 0x7D, 0x4C, 0xAD, 0x59, 0x91, 0x28, 0x13, 0x32, 0x0D, 0x7D, 280 | 0xAC, 0x11, 0x24, 0xB6, 0x5B, 0x81, 0xC2, 0x70, 0x72, 0xFD, 0x54, 0x7D, 0x85, 0xC7, 0x1F, 0x09, 281 | 0xAD, 0x4B, 0x9B, 0x8C, 0xF1, 0xC4, 0xF3, 0xB1, 0x76, 0x64, 0x34, 0xC2, 0xF1, 0xAC, 0x8F, 0xF5, 282 | 0x80, 0x22, 0x9F, 0x56, 0x61, 0x88, 0x26, 0x14, 0xFB, 0xE5, 0xFC, 0x30, 0xF3, 0x8A, 0x72, 0x6E, 283 | 0xF9, 0xD3, 0xCA, 0x01, 0xC4, 0x75, 0x88, 0x8B, 0xAB, 0x8B, 0x97, 0x37, 0x6F, 0x9A, 0x9D, 0x18, 284 | 0x55, 0x01, 0x18, 0x32, 0x9F, 0x16, 0x79, 0x09, 0xD7, 0x75, 0x7D, 0x32, 0x19, 0x37, 0xAD, 0x66, 285 | 0xF3, 0x9B, 0xF5, 0x93, 0x33, 0x2C, 0xDC, 0x14, 0x85, 0xD4, 0xDB, 0x3D, 0x22, 0xD6, 0xC2, 0x2A, 286 | 0xA3, 0xC7, 0xBF, 0xE6, 0xD8, 0x26, 0xC8, 0xA8, 0x29, 0xE1, 0x7C, 0xDE, 0x04, 0x9F, 0x3A, 0x36, 287 | 0x90, 0x6B, 0x1B, 0x35, 0xCF, 0x27, 0x10, 0x08, 0x88, 0xA7, 0x1B, 0x07, 0x7E, 0x81, 0xC2, 0xB1, 288 | 0xC0, 0xC7, 0x1A, 0x95, 0x0B, 0x62, 0x46, 0xB5, 0x88, 0x3E, 0x6C, 0xD8, 0xA7, 0x42, 0xCA, 0x61, 289 | 0x9F, 0xD2, 0x00, 0xD2, 0xE8, 0xC8, 0xD9, 0x17, 0xE1, 0xA5, 0x4A, 0x98, 0x87, 0x19, 0xFB, 0xCC, 290 | 0xD1, 0x55, 0xBD, 0x10, 0xBB, 0x68, 0x50, 0x84, 0x21, 0x94, 0x59, 0xAB, 0x06, 0x43, 0x97, 0x33, 291 | 0xA3, 0x6E, 0xB0, 0x2C, 0x79, 0xAC, 0xA7, 0x91, 0x4C, 0xF5, 0x90, 0xB3, 0x8F, 0xEA, 0x14, 0x1B, 292 | 0xA8, 0xAB, 0x57, 0x35, 0xC9, 0x1D, 0xE2, 0x8F, 0xCE, 0x87, 0x84, 0x26, 0xB9, 0x59, 0x84, 0x7D, 293 | 0xAA, 0x67, 0x92, 0x84, 0x59, 0x69, 0x36, 0xD1, 0x30, 0xCE, 0xCF, 0x28, 0x6B, 0x7C, 0xF3, 0xA2, 294 | 0x5B, 0xC3, 0xB5, 0x58, 0x84, 0xAA, 0xD9, 0x45, 0xC3, 0xB8, 0x48, 0x86, 0xD2, 0x2C, 0xC3, 0x3E, 295 | 0x37, 0x15, 0xFA, 0x8D, 0x7B, 0xE3, 0x90, 0x52, 0xCF, 0x0D, 0x76, 0x2A, 0x51, 0x79, 0x71, 0xF6, 296 | 0x57, 0x18, 0x50, 0x32, 0xB9, 0xAE, 0xCB, 0x90, 0x86, 0x38, 0x5B, 0x20, 0x68, 0x21, 0xC7, 0x98, 297 | 0xAE, 0x30, 0x2E, 0x6E, 0x37, 0x5C, 0xB4, 0x84, 0xBC, 0x33, 0x9D, 0x3A, 0x3A, 0xDF, 0xB3, 0x42, 298 | 0x3F, 0x60, 0x7D, 0xDB, 0xC2, 0x23, 0xC0, 0xD8, 0x5F, 0x9F, 0x38, 0x1D, 0x83, 0x15, 0x27, 0xAA, 299 | 0x5B, 0x63, 0xCD, 0x5C, 0x5E, 0x48, 0x99, 0x8D, 0xB5, 0x48, 0x78, 0xA0, 0x0E, 0xA1, 0xD7, 0xDA, 300 | 0x73, 0x32, 0x12, 0x35, 0x67, 0xA2, 0x10, 0x2C, 0x2C, 0x0B, 0x69, 0xB9, 0xFA, 0xD6, 0x0C, 0x5B, 301 | 0x1F, 0xB1, 0xFD, 0x6D, 0x69, 0x1B, 0x56, 0xD6, 0x1E, 0x36, 0x88, 0xBB, 0x08, 0x69, 0x9D, 0xB5, 302 | 0x53, 0x8B, 0x5B, 0xC1, 0x9C, 0x3B, 0x64, 0xA4, 0x62, 0xBB, 0x5D, 0xD4, 0x54, 0x9C, 0x2D, 0xAE, 303 | 0x8A, 0x8D, 0xA0, 0x0A, 0x3B, 0x72, 0xD0, 0x18, 0x3B, 0x45, 0x22, 0xCB, 0x60, 0xC8, 0x49, 0xBB, 304 | 0x32, 0x57, 0xE5, 0xF7, 0x6E, 0x5C, 0xB2, 0xA4, 0x78, 0x75, 0x1F, 0x7E, 0x53, 0xD9, 0x8E, 0xFC, 305 | 0xF8, 0x24, 0xF5, 0x53, 0x80, 0x1D, 0x08, 0xB0, 0xBC, 0xD6, 0x1B, 0xC6, 0xAC, 0x40, 0x86, 0xC2, 306 | 0x09, 0x7C, 0xE4, 0x4E, 0x31, 0xE4, 0x82, 0xAB, 0x93, 0xE8, 0xB0, 0x78, 0x61, 0x50, 0x49, 0x7D, 307 | 0x96, 0xAA, 0xCF, 0x8A, 0x17, 0x22, 0x22, 0x21, 0x6C, 0xD1, 0x8C, 0x28, 0xB0, 0x16, 0xCE, 0xDF, 308 | 0xD2, 0x3A, 0x85, 0xE8, 0x47, 0xB4, 0x01, 0x93, 0x76, 0x29, 0x6D, 0x7F, 0x5F, 0x9A, 0x11, 0xA2, 309 | 0x95, 0xDE, 0x64, 0x52, 0xB6, 0x56, 0x9C, 0x4C, 0x3A, 0xCD, 0x4E, 0xB7, 0xB4, 0x61, 0xD2, 0x6A, 310 | 0x99, 0x59, 0x2F, 0x6A, 0x32, 0x46, 0x9C, 0x4D, 0xCA, 0x21, 0xE8, 0xCF, 0xBC, 0x25, 0xF6, 0x35, 311 | 0x40, 0x64, 0xC4, 0xED, 0x3E, 0xEA, 0xDA, 0x15, 0xB8, 0x21, 0xC8, 0xF7, 0x4B, 0x5D, 0x36, 0x4D, 312 | 0xB3, 0x6B, 0xB7, 0xAC, 0x76, 0xA1, 0x63, 0x0A, 0x76, 0x0D, 0xF0, 0x06, 0x34, 0x76, 0xB0, 0x5D, 313 | 0x90, 0x9E, 0x6D, 0x3C, 0x41, 0xA1, 0x43, 0x4B, 0xEC, 0x8D, 0x9A, 0xEC, 0x4F, 0xD1, 0x8C, 0x3C, 314 | 0xAE, 0xFE, 0x60, 0x1B, 0x1D, 0x43, 0x1E, 0x09, 0x7F, 0x6A, 0xE6, 0x8C, 0x6A, 0x27, 0x5A, 0x2C, 315 | 0x30, 0x82, 0x51, 0x16, 0xCE, 0x5B, 0x92, 0x56, 0xEA, 0x99, 0xF5, 0x89, 0xAB, 0xD2, 0x42, 0xB4, 316 | 0xD4, 0x15, 0xE3, 0x6E, 0x68, 0x23, 0x9D, 0xFB, 0x13, 0xCF, 0x0A, 0x75, 0x65, 0xBA, 0x9A, 0x4B, 317 | 0xAD, 0xF3, 0xEB, 0x47, 0x26, 0x0B, 0x1C, 0xC2, 0x1D, 0x3B, 0x74, 0x5D, 0x86, 0x68, 0x9D, 0xFA, 318 | 0xA0, 0xA6, 0x66, 0xA2, 0x6A, 0x86, 0xDB, 0x2A, 0x3A, 0x53, 0x86, 0xCD, 0xDB, 0x8C, 0xC9, 0x04, 319 | 0xA0, 0x26, 0x51, 0xC4, 0x39, 0xC4, 0x08, 0x3C, 0x50, 0x2A, 0x62, 0xB5, 0x9B, 0x5D, 0xE8, 0x2C, 320 | 0x9C, 0xEB, 0x1A, 0x83, 0x68, 0xB2, 0x16, 0x54, 0x31, 0x31, 0x9D, 0x3F, 0x1D, 0xA3, 0x5A, 0xF3, 321 | 0xA4, 0x79, 0xD2, 0x81, 0xBF, 0x34, 0x0D, 0x7A, 0xB1, 0x73, 0x49, 0xF3, 0xE6, 0x78, 0x5E, 0x26, 322 | 0xF9, 0x94, 0xEF, 0x93, 0xE4, 0xA5, 0xB1, 0x52, 0x2C, 0xAA, 0x47, 0x52, 0x7A, 0xC3, 0xA4, 0xD5, 323 | 0x28, 0x29, 0x2C, 0x39, 0x2E, 0xBD, 0xB9, 0x23, 0x6A, 0xBC, 0x65, 0x53, 0x88, 0xE7, 0xDE, 0x3F, 324 | 0x75, 0x51, 0x55, 0xFF, 0xEF, 0xBD, 0x5D, 0x31, 0xC5, 0x57, 0xED, 0xE9, 0x1B, 0xDB, 0x25, 0x38, 325 | 0xB4, 0x6F, 0x34, 0xF3, 0x51, 0xAF, 0xCB, 0x7E, 0x06, 0x24, 0x74, 0x61, 0x51, 0xE5, 0xC3, 0xEA, 326 | 0x2A, 0xB7, 0xE7, 0x51, 0xC6, 0x6C, 0x61, 0x83, 0x09, 0x71, 0x9C, 0xBA, 0xE3, 0xAD, 0xCA, 0x3B, 327 | 0x91, 0x62, 0x4F, 0x5E, 0xF3, 0xD3, 0x72, 0x97, 0xDF, 0x56, 0xDA, 0x10, 0x32, 0xD7, 0xFF, 0x84, 328 | 0xB4, 0x5F, 0x77, 0xC0, 0x15, 0x86, 0xC6, 0x76, 0x85, 0x62, 0x0B, 0x7F, 0xDC, 0x6D, 0xA2, 0x4A, 329 | 0xAE, 0x24, 0x3A, 0xC1, 0xC2, 0xC5, 0x5C, 0xB0, 0x22, 0xD4, 0x9A, 0x6D, 0xB1, 0xA8, 0x5A, 0x78, 330 | 0x01, 0x11, 0xD7, 0x68, 0x7C, 0xEC, 0x20, 0xD6, 0xC1, 0x6F, 0xB5, 0xE4, 0x2E, 0x5D, 0x98, 0xA8, 331 | 0xE4, 0x55, 0x34, 0xE1, 0xA6, 0xFB, 0x72, 0xB6, 0x4B, 0x1A, 0xA2, 0x77, 0xC8, 0xCF, 0xD5, 0x7A, 332 | 0xB7, 0x2E, 0x69, 0xF7, 0xD3, 0x91, 0xA1, 0x1F, 0xB4, 0x41, 0x46, 0x8F, 0x92, 0xF6, 0xD4, 0xC7, 333 | 0xD7, 0x15, 0x94, 0x39, 0x91, 0xFF, 0xF6, 0xC5, 0x86, 0xE8, 0xF6, 0x6B, 0x7F, 0x5E, 0x00, 0xA4, 334 | 0x17, 0x35, 0xBA, 0x41, 0x85, 0xA9, 0xF3, 0xA7, 0xAC, 0xE2, 0x8F, 0xF1, 0x76, 0x9F, 0x69, 0x56, 335 | 0x48, 0x37, 0x05, 0x25, 0x54, 0xEF, 0xAA, 0x51, 0xF5, 0xD5, 0x9E, 0x74, 0xF0, 0x84, 0xE6, 0x5C, 336 | 0xCD, 0xE0, 0x7D, 0x6A, 0xA7, 0x38, 0xBB, 0xD5, 0x95, 0x7D, 0x82, 0xD2, 0xCC, 0x11, 0xEF, 0xCA, 337 | 0xE5, 0x7B, 0x9F, 0x96, 0x33, 0xCB, 0x9E, 0x1B, 0x33, 0xCF, 0x87, 0x24, 0x6A, 0x9F, 0x39, 0xCC, 338 | 0x30, 0x66, 0x2E, 0x4B, 0x3E, 0xC0, 0x83, 0x7F, 0xAF, 0xB5, 0x7B, 0xDA, 0x8B, 0x05, 0x05, 0x83, 339 | 0x8B, 0x44, 0xCB, 0xDD, 0xD6, 0x5A, 0x2F, 0x59, 0xB9, 0x0B, 0x64, 0x35, 0x17, 0x69, 0x81, 0x2A, 340 | 0x8E, 0xCA, 0xA2, 0x0C, 0xB3, 0xBE, 0x47, 0x53, 0xE8, 0xEC, 0x64, 0x8E, 0xA0, 0xED, 0x65, 0xEE, 341 | 0x8A, 0x80, 0xA3, 0x0E, 0xBF, 0x2A, 0xEE, 0xAE, 0x6C, 0x1A, 0xB6, 0x7A, 0xCD, 0x92, 0x29, 0x2D, 342 | 0xC7, 0x0B, 0x8A, 0xE3, 0x0A, 0x8D, 0xC1, 0x7E, 0x21, 0xD5, 0x4C, 0x24, 0xB7, 0x2E, 0xB5, 0x3B, 343 | 0x4F, 0xDC, 0xB9, 0xB5, 0x67, 0x2A, 0x95, 0xEE, 0xC2, 0x98, 0x2A, 0x0E, 0xC7, 0x8C, 0xCD, 0x5B, 344 | 0x4D, 0x6D, 0xA6, 0x2D, 0xDC, 0x7F, 0xA3, 0xF8, 0x0A, 0xD6, 0x9B, 0xEC, 0x82, 0x5C, 0xDF, 0xB0, 345 | 0xB0, 0x3E, 0x8D, 0xA6, 0x8A, 0x5C, 0xAB, 0xCA, 0x26, 0x60, 0x21, 0x0E, 0x33, 0x62, 0xDB, 0xB8, 346 | 0x70, 0x97, 0x93, 0xAD, 0x79, 0x2B, 0x36, 0x0F, 0x4C, 0x7E, 0xDD, 0xA6, 0xD4, 0xAD, 0x04, 0x45, 347 | 0xE1, 0x75, 0xFA, 0xD6, 0x6D, 0x47, 0x8C, 0x2C, 0x34, 0x79, 0x7B, 0xC4, 0xE9, 0x56, 0xA4, 0x50, 348 | 0x54, 0x6D, 0x70, 0xC7, 0xDB, 0xC4, 0xCC, 0x64, 0x60, 0x07, 0x36, 0x6A, 0x3D, 0x9B, 0x2B, 0x52, 349 | 0x0D, 0x4E, 0x95, 0x7B, 0x89, 0x06, 0xA7, 0xC9, 0x6D, 0x4F, 0x03, 0x76, 0x43, 0x91, 0x7A, 0xCB, 350 | 0x91, 0xB8, 0xDE, 0x65, 0x58, 0x0E, 0x0A, 0x82, 0xA1, 0xC9, 0x6E, 0x8C, 0x31, 0xD3, 0x77, 0x20, 351 | 0x0D, 0x6C, 0xB2, 0x34, 0x88, 0x3D, 0x34, 0x1D, 0x6F, 0xEA, 0x65, 0xCE, 0xF1, 0xF3, 0xE2, 0x0A, 352 | 0x04, 0x24, 0xCD, 0xA1, 0x99, 0xBA, 0x3A, 0x63, 0x72, 0xAA, 0xE4, 0x27, 0x73, 0xF4, 0xE0, 0xDE, 353 | 0xA3, 0x87, 0x0F, 0x7B, 0xDF, 0x3D, 0x70, 0xC7, 0xC1, 0x42, 0xFE, 0xFD, 0x8B, 0xB8, 0x98, 0x25, 354 | 0xEE, 0x88, 0x82, 0x3C, 0x4A, 0x29, 0xE8, 0x19, 0x0C, 0x4E, 0x39, 0xD3, 0x8C, 0x20, 0xA7, 0x20, 355 | 0x49, 0x8E, 0x6C, 0xB2, 0xB6, 0xEA, 0xC4, 0x8B, 0x86, 0x04, 0x50, 0x2E, 0xC6, 0xC8, 0xD7, 0x0C, 356 | 0xE1, 0xC3, 0x44, 0xE7, 0xC6, 0xFD, 0xD6, 0xE4, 0x35, 0x66, 0xEC, 0x5D, 0x65, 0x35, 0xE0, 0x4A, 357 | 0xC9, 0x02, 0x24, 0x47, 0x61, 0x3B, 0x8F, 0x21, 0x90, 0x71, 0x72, 0x76, 0x69, 0x2A, 0x67, 0x4C, 358 | 0x2C, 0x9F, 0xB4, 0xBE, 0x72, 0xA5, 0x44, 0x4C, 0x3D, 0xF1, 0xD1, 0x1C, 0x33, 0xF7, 0x97, 0x3F, 359 | 0xE6, 0xB3, 0xC9, 0x22, 0x11, 0x53, 0x9A, 0xA3, 0x37, 0x98, 0x67, 0x4E, 0x40, 0x59, 0x6B, 0xD6, 360 | 0x35, 0x2E, 0xB2, 0x98, 0xA5, 0xE6, 0x37, 0x23, 0x11, 0xE5, 0xE6, 0x75, 0x1D, 0x71, 0xB7, 0x29, 361 | 0x11, 0x88, 0xB3, 0xF3, 0x16, 0xDC, 0xC1, 0x96, 0xC8, 0x09, 0xC1, 0xB4, 0xAD, 0x96, 0x39, 0xFA, 362 | 0xF9, 0xF7, 0xEF, 0x9F, 0xD4, 0xDA, 0xCD, 0xEE, 0xF9, 0x55, 0xEB, 0xAC, 0xD7, 0x3D, 0x1E, 0x9C, 363 | 0x8A, 0x21, 0x9B, 0xF3, 0x6A, 0x9A, 0xA3, 0x5F, 0x19, 0x2F, 0xA8, 0x2F, 0xCD, 0xAB, 0x56, 0xBB, 364 | 0xD9, 0xDC, 0x9E, 0xD7, 0x23, 0x73, 0xF4, 0x96, 0xB3, 0x6A, 0x9F, 0x03, 0xAB, 0x66, 0x7B, 0x07, 365 | 0xB1, 0xCE, 0xCD, 0x11, 0xE7, 0x04, 0x4C, 0xAE, 0x1E, 0xF6, 0xCE, 0xB7, 0x67, 0xF4, 0x10, 0x64, 366 | 0x7A, 0x07, 0x9C, 0xCE, 0x41, 0xBB, 0xDE, 0x2E, 0xCA, 0xF5, 0xCC, 0x11, 0xE3, 0xD3, 0xEB, 0x36, 367 | 0xAF, 0xBA, 0xE7, 0x3B, 0xF0, 0x39, 0x33, 0x65, 0xA7, 0xC3, 0xDC, 0x3F, 0x3A, 0x32, 0x47, 0x17, 368 | 0x3F, 0x3C, 0xAF, 0x75, 0x41, 0xC6, 0xF6, 0xA3, 0xDE, 0xF6, 0xBC, 0xBB, 0xE0, 0x17, 0x4C, 0xC8, 369 | 0x4E, 0x1B, 0x18, 0x75, 0x77, 0x10, 0xB2, 0x63, 0x8E, 0x5E, 0x70, 0x4E, 0xC0, 0xE5, 0xAA, 0xF5, 370 | 0x70, 0x07, 0x91, 0xC0, 0xBD, 0x7E, 0xE6, 0x9C, 0xC0, 0xBF, 0x98, 0x7B, 0x55, 0xE4, 0x04, 0xB9, 371 | 0x97, 0x9B, 0xA6, 0x20, 0xE6, 0xD7, 0x33, 0x59, 0xEA, 0x74, 0x51, 0x4A, 0xF8, 0x3B, 0x84, 0x8E, 372 | 0x80, 0x5E, 0x6F, 0x9C, 0x10, 0x24, 0x1D, 0xA8, 0x24, 0x0E, 0xAA, 0xE5, 0x02, 0x45, 0x92, 0xF8, 373 | 0x6A, 0xAB, 0x39, 0xEA, 0x96, 0x28, 0xC0, 0x49, 0xD5, 0x84, 0xCA, 0x69, 0x53, 0xF2, 0x9B, 0xAC, 374 | 0x3F, 0x64, 0xA8, 0xB3, 0xFB, 0x79, 0xC0, 0x43, 0x3B, 0xA6, 0x12, 0xD5, 0x5B, 0x25, 0x1B, 0x8D, 375 | 0xAC, 0xE8, 0xCA, 0x1C, 0xF5, 0x3A, 0x65, 0xD6, 0xDE, 0x01, 0x8C, 0x31, 0xEF, 0x3D, 0x5D, 0x1C, 376 | 0x04, 0x1B, 0xE3, 0x91, 0x90, 0x9A, 0xA3, 0xA7, 0xF1, 0xF1, 0x2E, 0xA8, 0xD4, 0xCB, 0x34, 0xE5, 377 | 0xB4, 0x39, 0xB0, 0x28, 0xE2, 0x08, 0x64, 0xEA, 0x1D, 0x09, 0x4D, 0x82, 0xCC, 0xE7, 0x05, 0xE6, 378 | 0x36, 0x71, 0x61, 0xED, 0x80, 0x8F, 0x02, 0xBA, 0x31, 0x2A, 0x11, 0x21, 0x24, 0x35, 0x79, 0x74, 379 | 0x30, 0x44, 0x62, 0x51, 0xBE, 0x02, 0x3C, 0x02, 0x44, 0x43, 0x9F, 0xDF, 0xE5, 0xB8, 0x31, 0x22, 380 | 0x09, 0x29, 0x54, 0xC3, 0xF8, 0x78, 0x27, 0x54, 0x76, 0x49, 0x5F, 0x8A, 0x38, 0x12, 0x97, 0x28, 381 | 0x85, 0x75, 0x6F, 0x09, 0x97, 0x32, 0x69, 0x77, 0xC2, 0x65, 0x86, 0xFC, 0xC5, 0x56, 0xE9, 0x2B, 382 | 0xA6, 0x04, 0x54, 0xA2, 0xC3, 0x83, 0x85, 0x4A, 0x22, 0xCC, 0x57, 0x10, 0x2B, 0xB0, 0xFE, 0xF6, 383 | 0x48, 0xB0, 0x79, 0xC7, 0x2F, 0xE9, 0xCC, 0xD1, 0x33, 0x5C, 0x7F, 0xCD, 0x8E, 0x76, 0x81, 0xE3, 384 | 0x49, 0x48, 0xBD, 0x1D, 0x00, 0x89, 0x64, 0x11, 0x70, 0x34, 0x25, 0x1A, 0xE7, 0xB7, 0x84, 0xC6, 385 | 0xF9, 0x2D, 0xA2, 0x81, 0xF0, 0x7B, 0x07, 0x2F, 0xB1, 0xB3, 0x31, 0x1C, 0x11, 0xA1, 0x39, 0xBA, 386 | 0xBC, 0x5A, 0x78, 0x01, 0xBB, 0x5B, 0xF8, 0x25, 0xFB, 0xBE, 0x53, 0x90, 0x9C, 0xED, 0x80, 0x49, 387 | 0x2C, 0x90, 0x8C, 0x91, 0x33, 0x89, 0xCA, 0xD9, 0x2D, 0xA1, 0x52, 0x26, 0xEB, 0x2E, 0xA8, 0x4C, 388 | 0x11, 0x71, 0x2D, 0x4C, 0x1C, 0x76, 0xE7, 0xE2, 0xA6, 0xC0, 0x28, 0xB4, 0xE6, 0xE8, 0xFB, 0xE4, 389 | 0xCB, 0x2E, 0xC0, 0x34, 0x77, 0xC0, 0x45, 0x95, 0x27, 0x1D, 0x2F, 0x67, 0xB0, 0x58, 0xBE, 0x25, 390 | 0x6C, 0x5A, 0xAD, 0xDB, 0xAC, 0x2A, 0x0B, 0x6C, 0x11, 0xE4, 0xBC, 0xC7, 0x93, 0x09, 0x2C, 0x83, 391 | 0x36, 0x2F, 0x2D, 0x29, 0x72, 0xA8, 0x2F, 0xE2, 0xBB, 0x71, 0xC9, 0xBF, 0x6F, 0xBC, 0x87, 0x91, 392 | 0x61, 0xF7, 0xB9, 0x36, 0x32, 0x9A, 0xFA, 0xB5, 0xF0, 0x6B, 0x2F, 0x96, 0x73, 0xDB, 0x5D, 0x0D, 393 | 0x60, 0x82, 0xA7, 0x7C, 0x53, 0x7D, 0x6B, 0x1E, 0x6D, 0xF0, 0x6C, 0x1F, 0x5D, 0xF3, 0xC7, 0x10, 394 | 0x77, 0x59, 0x48, 0xBF, 0xC1, 0xB6, 0xF1, 0x0B, 0x71, 0xB7, 0x57, 0xA6, 0xCB, 0x04, 0xC1, 0xD8, 395 | 0xDD, 0x8D, 0xCB, 0x19, 0x2C, 0x91, 0xE0, 0x60, 0x37, 0x26, 0x3D, 0xF0, 0x24, 0xBC, 0x20, 0xE8, 396 | 0x4B, 0x58, 0xC4, 0xA3, 0xD5, 0x78, 0xF3, 0x82, 0xB2, 0x1A, 0x43, 0x5D, 0xFE, 0xED, 0xA9, 0x71, 397 | 0xC9, 0x6F, 0x03, 0xDB, 0x38, 0x5D, 0x89, 0x2B, 0xD4, 0x55, 0x1C, 0x5D, 0x24, 0x2A, 0x29, 0xA7, 398 | 0xB9, 0xB6, 0x27, 0xAA, 0x0F, 0xA0, 0xAA, 0xFB, 0xA2, 0x1A, 0xF5, 0x22, 0x01, 0xF9, 0x05, 0x3D, 399 | 0x53, 0xD1, 0xB6, 0x9A, 0x8E, 0xB7, 0xD8, 0x8A, 0x59, 0xAB, 0xCD, 0xDB, 0x30, 0x6B, 0x05, 0x30, 400 | 0xD9, 0x4B, 0x76, 0x87, 0xA0, 0x6D, 0x00, 0x5E, 0x7B, 0x01, 0x8A, 0xCD, 0x7A, 0x18, 0xA0, 0xB8, 401 | 0xBE, 0x87, 0x06, 0x0A, 0xBC, 0xE5, 0x3D, 0xAB, 0xA3, 0xDB, 0x04, 0x15, 0x27, 0x34, 0x47, 0xAF, 402 | 0x90, 0x1B, 0x42, 0x91, 0xD9, 0x17, 0x60, 0xF1, 0xC4, 0x07, 0x0B, 0x2F, 0xA9, 0xF7, 0xA1, 0xA1, 403 | 0x03, 0x41, 0xE6, 0x9E, 0xBD, 0xF9, 0x72, 0x47, 0xD2, 0x89, 0x94, 0xF8, 0x0A, 0x8E, 0x36, 0x6E, 404 | 0x0C, 0x22, 0x0E, 0xB7, 0xDC, 0x11, 0x88, 0xA5, 0xD4, 0xF6, 0xCD, 0xC0, 0xDB, 0xD0, 0x75, 0xAF, 405 | 0x77, 0xE9, 0x04, 0x2E, 0x1C, 0x2F, 0xB4, 0xB7, 0xE7, 0x00, 0x6D, 0xC0, 0x8F, 0x93, 0x09, 0xB1, 406 | 0xB6, 0x6F, 0x24, 0xA0, 0x09, 0x78, 0xE1, 0xCD, 0x2B, 0xD2, 0xDF, 0x72, 0xE1, 0xC5, 0xD6, 0x16, 407 | 0x2B, 0x39, 0x0B, 0x50, 0xBC, 0xBC, 0xD8, 0x6B, 0xE1, 0x85, 0x39, 0x0F, 0x94, 0x19, 0x98, 0xB6, 408 | 0x87, 0x4E, 0x0A, 0x20, 0xC4, 0x7B, 0xEE, 0x3C, 0xDB, 0x80, 0x25, 0x28, 0xE3, 0x8C, 0x1E, 0x2D, 409 | 0xBF, 0x0F, 0xB5, 0xBE, 0x4B, 0x24, 0x4A, 0xAF, 0xEE, 0x5A, 0x67, 0x9D, 0x5E, 0xBC, 0xBC, 0xEB, 410 | 0xB4, 0x3F, 0xEF, 0x02, 0x8F, 0x31, 0xBF, 0x5D, 0x7C, 0xDA, 0xDB, 0x40, 0x03, 0xD9, 0xE8, 0x35, 411 | 0xBB, 0xCE, 0xB0, 0x41, 0xC2, 0xDE, 0x3D, 0x90, 0xDA, 0x87, 0x8B, 0xA4, 0xF6, 0x17, 0x10, 0x4A, 412 | 0xD3, 0x2D, 0x32, 0xDE, 0x94, 0x65, 0xBC, 0xEF, 0x2F, 0xF6, 0x83, 0xD0, 0xF4, 0x60, 0xA9, 0x6E, 413 | 0x7A, 0xD0, 0x54, 0x67, 0x88, 0x9B, 0xAD, 0x62, 0x98, 0xB6, 0xEC, 0x60, 0x25, 0xA1, 0xD8, 0xCB, 414 | 0xDA, 0x25, 0xC9, 0xB5, 0xAE, 0x76, 0xC9, 0x72, 0x91, 0x18, 0xE9, 0x24, 0xD7, 0x4B, 0xAE, 0x8A, 415 | 0x9C, 0x7D, 0xDE, 0xCB, 0xBA, 0xDD, 0x32, 0x69, 0x77, 0x09, 0x1A, 0x1F, 0xAD, 0xDE, 0x4F, 0xE7, 416 | 0x68, 0x63, 0x30, 0x24, 0x1D, 0x60, 0xF1, 0xEA, 0xC9, 0x3E, 0xDB, 0x85, 0x68, 0xDE, 0xC3, 0xC4, 417 | 0x51, 0xAC, 0xF5, 0xA1, 0x73, 0x9D, 0x83, 0xDD, 0xCD, 0x93, 0x1D, 0x23, 0x32, 0x47, 0x2F, 0xB1, 418 | 0x1B, 0x18, 0x17, 0x9E, 0x2F, 0xDF, 0xFD, 0xB4, 0x17, 0xD4, 0xF8, 0xCC, 0x87, 0x81, 0x4C, 0x28, 419 | 0x7D, 0x68, 0xBC, 0x66, 0x73, 0xE2, 0xFB, 0x9E, 0xBF, 0x31, 0x64, 0x92, 0x0E, 0x96, 0x15, 0xF5, 420 | 0x57, 0xFC, 0x68, 0x2F, 0x70, 0x45, 0xB3, 0x1E, 0x06, 0xB1, 0x58, 0xE7, 0x43, 0x83, 0xB6, 0x9C, 421 | 0x38, 0x64, 0xB1, 0x31, 0x64, 0x9C, 0xCA, 0x1C, 0xBD, 0xAB, 0x3F, 0x87, 0x7F, 0xF7, 0x02, 0x97, 422 | 0x98, 0xF1, 0x30, 0x60, 0x49, 0x6D, 0x0F, 0x0D, 0xD5, 0x78, 0xB1, 0x79, 0x3A, 0x04, 0x1A, 0x73, 423 | 0xF4, 0xF4, 0xA7, 0xFD, 0xF4, 0x7E, 0x6C, 0xB2, 0x8A, 0x08, 0xED, 0x84, 0x07, 0x57, 0xEA, 0xD0, 424 | 0x68, 0xAC, 0xB6, 0x40, 0x63, 0xC5, 0x04, 0xFF, 0x6D, 0x4F, 0x68, 0xAC, 0xAA, 0xA3, 0xF1, 0x99, 425 | 0xE3, 0x65, 0xF5, 0x25, 0xE0, 0xC3, 0x9F, 0xC5, 0x18, 0xA3, 0xCD, 0xCB, 0x51, 0x44, 0xC8, 0x6E, 426 | 0x1A, 0x83, 0x23, 0xE3, 0x29, 0xDA, 0x4F, 0x41, 0x8A, 0xE7, 0xDD, 0x47, 0x08, 0x25, 0x4A, 0x1E, 427 | 0x1A, 0xA7, 0x09, 0xB2, 0xF0, 0x7B, 0x1B, 0xD3, 0x6D, 0xAE, 0x2D, 0x2B, 0xB4, 0xE6, 0xE8, 0x39, 428 | 0x7C, 0x31, 0x9E, 0xF1, 0x2F, 0xFB, 0x6A, 0xF9, 0xD4, 0xF9, 0xF7, 0x81, 0x5A, 0x4A, 0xDF, 0x2F, 429 | 0x02, 0x38, 0x68, 0xB0, 0xBD, 0xA9, 0xBB, 0xD5, 0x23, 0x0D, 0x29, 0x72, 0x09, 0xDF, 0x1B, 0xF1, 430 | 0x7D, 0xBF, 0x00, 0x26, 0x42, 0xEC, 0x0D, 0x43, 0x45, 0xEF, 0x7D, 0xC0, 0x18, 0x3D, 0x16, 0xC4, 431 | 0x8B, 0xB4, 0x78, 0x15, 0x5E, 0x19, 0x52, 0xF2, 0xE1, 0x27, 0x7E, 0x4B, 0x0B, 0xA6, 0xF5, 0x80, 432 | 0x12, 0xC7, 0x81, 0x85, 0x30, 0xA6, 0xC6, 0x5B, 0x76, 0x38, 0x38, 0x15, 0x03, 0xAA, 0x73, 0x91, 433 | 0xCF, 0xDC, 0xB0, 0x97, 0x50, 0xA2, 0xB9, 0x39, 0x7A, 0xCB, 0x5E, 0x12, 0x08, 0xBC, 0xD8, 0xB7, 434 | 0xCD, 0x99, 0x71, 0x23, 0x62, 0xD7, 0xF7, 0x40, 0xA8, 0x18, 0x24, 0xF9, 0xAE, 0x26, 0xD3, 0x88, 435 | 0x8E, 0x94, 0xDF, 0x46, 0x97, 0x7C, 0xB0, 0xC1, 0xBC, 0xAC, 0x7C, 0x3A, 0x76, 0xD5, 0xC2, 0xCA, 436 | 0xBF, 0xB8, 0x31, 0x38, 0x75, 0x91, 0xC6, 0xDC, 0x39, 0x28, 0x0C, 0xC4, 0xDB, 0x25, 0x73, 0x58, 437 | 0xC5, 0xCF, 0x33, 0x71, 0x4B, 0x24, 0x8F, 0x69, 0xC6, 0x6A, 0x65, 0x1F, 0xDF, 0x94, 0xDB, 0x4C, 438 | 0xD5, 0x82, 0x96, 0x3F, 0x88, 0x29, 0xEB, 0x21, 0x3B, 0x8C, 0xCD, 0xFF, 0x9F, 0x7F, 0x97, 0xF9, 439 | 0x0C, 0x7B, 0xF7, 0x67, 0x22, 0x98, 0x69, 0x04, 0xBE, 0x35, 0x34, 0xF3, 0x9E, 0x8E, 0xCA, 0xD1, 440 | 0xFC, 0x54, 0xA7, 0x7A, 0x66, 0xB0, 0xC6, 0xD6, 0x83, 0xC0, 0xF2, 0xC9, 0x82, 0x8E, 0xEE, 0xD8, 441 | 0x9E, 0x15, 0xCE, 0xB1, 0x4B, 0x1B, 0xC8, 0xB6, 0x2F, 0x97, 0x70, 0xF0, 0x92, 0x04, 0x14, 0x83, 442 | 0x15, 0x6A, 0x47, 0xCF, 0x7E, 0x7C, 0x75, 0x21, 0x9E, 0x12, 0x7B, 0xE9, 0x21, 0x1B, 0xDB, 0x47, 443 | 0x27, 0xC6, 0x24, 0x74, 0x85, 0x9B, 0xD7, 0x30, 0x1B, 0x2B, 0xDE, 0xBB, 0xBA, 0x44, 0xBE, 0x31, 444 | 0x46, 0x01, 0x7E, 0xE1, 0x05, 0xD4, 0x18, 0x1A, 0x31, 0x47, 0xC7, 0xB3, 0xF8, 0x7D, 0xBF, 0x0D, 445 | 0xCF, 0x27, 0x53, 0xE2, 0xCA, 0x91, 0x42, 0xD9, 0x5F, 0x7D, 0x07, 0x86, 0xC6, 0x54, 0xDF, 0x1A, 446 | 0x47, 0xFD, 0xF3, 0xD6, 0x11, 0x7B, 0x1C, 0x0F, 0x60, 0x80, 0x1F, 0x00, 0x02, 0x0C, 0x03, 0x20, 447 | 0xC0, 0x87, 0x23, 0xF9, 0x78, 0x20, 0x76, 0x1A, 0xDC, 0xE4, 0x4C, 0x40, 0x26, 0x6D, 0xED, 0x48, 448 | 0xE0, 0x74, 0xC4, 0x1E, 0x34, 0xBE, 0x89, 0x29, 0x83, 0x99, 0xB7, 0x2A, 0xA2, 0xF4, 0xF1, 0xDC, 449 | 0x5B, 0xE2, 0x0C, 0x71, 0x4C, 0x2D, 0xBD, 0xB9, 0x74, 0xEA, 0xC8, 0xEB, 0x8F, 0x8E, 0xA3, 0x01, 450 | 0xF1, 0x7B, 0xCC, 0x86, 0x06, 0xF5, 0x43, 0x9C, 0x66, 0x8B, 0xDD, 0x32, 0xAE, 0x91, 0x58, 0x85, 451 | 0x8C, 0x27, 0xC8, 0x09, 0x32, 0x9C, 0xC3, 0x85, 0x8D, 0x28, 0x7E, 0xC7, 0x76, 0x0C, 0x61, 0x40, 452 | 0x0D, 0x3B, 0x27, 0x62, 0xFB, 0xF0, 0x44, 0x9E, 0x79, 0x03, 0x7C, 0x29, 0x3E, 0x4E, 0x66, 0x55, 453 | 0x7F, 0x06, 0x8A, 0xF4, 0xD7, 0xA1, 0xE1, 0x86, 0x10, 0xC2, 0x8F, 0xB9, 0x0A, 0x46, 0x3F, 0x75, 454 | 0x96, 0x53, 0x3B, 0x90, 0x9D, 0xE4, 0x3B, 0xDB, 0xF9, 0x9C, 0xFC, 0x47, 0x32, 0x61, 0x13, 0x37, 455 | 0xF8, 0x1B, 0xE4, 0x87, 0xC0, 0xE3, 0x28, 0xCA, 0xEE, 0x47, 0xC9, 0x8B, 0x79, 0x55, 0x22, 0x6E, 456 | 0x87, 0x86, 0xEC, 0x83, 0xE5, 0xF9, 0xA5, 0x3C, 0x71, 0xF7, 0xEE, 0x32, 0xE6, 0x6B, 0x28, 0xC3, 457 | 0xE0, 0x54, 0x72, 0xE2, 0x06, 0x4E, 0x28, 0x4F, 0x3F, 0xAF, 0xF3, 0xCE, 0xF0, 0x88, 0x98, 0x2B, 458 | 0x1C, 0xEE, 0xC4, 0x92, 0xA7, 0x2C, 0xF0, 0xE0, 0x41, 0x9A, 0xDB, 0xDD, 0xA1, 0xA4, 0x4A, 0x34, 459 | 0x11, 0xE3, 0x21, 0x32, 0x20, 0xF2, 0x40, 0x6D, 0xF9, 0x4C, 0xBC, 0x14, 0x89, 0x4C, 0x6A, 0x77, 460 | 0x53, 0x86, 0x8F, 0x65, 0x9C, 0x30, 0x13, 0x11, 0x9B, 0x1B, 0x88, 0x5F, 0x33, 0x3C, 0x4E, 0x9E, 461 | 0x7A, 0x15, 0xF2, 0x3D, 0xE6, 0x5E, 0x5F, 0xC3, 0xF2, 0xF2, 0xDB, 0x31, 0xD8, 0x9F, 0x39, 0x73, 462 | 0xF2, 0x83, 0x1C, 0x9F, 0x4C, 0xA5, 0x72, 0x9C, 0xA6, 0x38, 0x32, 0xC5, 0x32, 0x72, 0xB3, 0x0F, 463 | 0x9F, 0x00, 0x86, 0xB2, 0x9D, 0xEF, 0xE4, 0xF9, 0xFC, 0x8C, 0x39, 0xD9, 0x87, 0x4F, 0xBC, 0x3E, 464 | 0xB0, 0x50, 0x82, 0xE8, 0x0E, 0x09, 0x8D, 0x62, 0x9C, 0xDD, 0x6A, 0xCC, 0x54, 0xE2, 0x22, 0xC0, 465 | 0x61, 0x11, 0xAB, 0x4C, 0x01, 0xD7, 0x30, 0x14, 0x01, 0x55, 0x13, 0xF5, 0xE9, 0x29, 0xAF, 0x35, 466 | 0x8C, 0xB9, 0x8C, 0x95, 0xF4, 0xEF, 0x77, 0x54, 0xE1, 0x6F, 0xA2, 0xF0, 0x89, 0x53, 0x99, 0x8A, 467 | 0x27, 0xF3, 0xE3, 0xC8, 0x62, 0xCC, 0xD5, 0x13, 0x87, 0x91, 0xAF, 0x2B, 0x89, 0xFC, 0x3C, 0x31, 468 | 0xAB, 0x05, 0x39, 0x4C, 0xF1, 0xF8, 0x7E, 0x46, 0x54, 0xD5, 0xD5, 0x41, 0xEE, 0x96, 0xA1, 0xBE, 469 | 0x80, 0x64, 0x0C, 0xA9, 0xF0, 0x63, 0x8A, 0x0F, 0xDF, 0xB0, 0x8F, 0x99, 0x88, 0xDF, 0xC4, 0xE5, 470 | 0xFD, 0xBA, 0xE7, 0x62, 0x3D, 0x77, 0xD5, 0xD9, 0x75, 0x3C, 0x45, 0x29, 0xCE, 0x32, 0x0D, 0xC7, 471 | 0x73, 0x42, 0x35, 0x0C, 0x8F, 0x20, 0x0D, 0xEB, 0x78, 0xC9, 0x06, 0x2D, 0x21, 0xF0, 0x31, 0x0D, 472 | 0x7D, 0x57, 0x8D, 0x26, 0x91, 0x91, 0xFE, 0x0E, 0xB1, 0x7F, 0x0D, 0x8C, 0x3E, 0xDC, 0xFF, 0x14, 473 | 0xE5, 0xF7, 0x9B, 0x53, 0xFE, 0x6C, 0x8E, 0xE7, 0x3C, 0x86, 0x0A, 0x30, 0xBC, 0xFF, 0x89, 0x43, 474 | 0x7D, 0xF3, 0x00, 0xA6, 0x84, 0x2F, 0x7C, 0xE2, 0x9B, 0x0F, 0x82, 0xC5, 0x84, 0xBD, 0x3E, 0xBB, 475 | 0xC6, 0x59, 0x44, 0xB8, 0x35, 0xE8, 0x0C, 0xBB, 0x35, 0x1F, 0x07, 0x0B, 0x60, 0x8F, 0x93, 0x44, 476 | 0x16, 0xCD, 0xE8, 0x39, 0x18, 0x4A, 0xCD, 0xB4, 0xF6, 0xC1, 0xC7, 0x40, 0x07, 0x02, 0x50, 0xCF, 477 | 0xB8, 0xFF, 0x89, 0xB3, 0xB8, 0x31, 0x26, 0x10, 0xCD, 0xC1, 0x0C, 0xDB, 0x27, 0x50, 0x77, 0x10, 478 | 0x65, 0x4F, 0xA6, 0xDF, 0xFF, 0x14, 0xB1, 0x6A, 0x88, 0x9F, 0x6E, 0x3E, 0xC4, 0x1E, 0x12, 0x17, 479 | 0x83, 0xA8, 0x86, 0xF1, 0x13, 0x0D, 0xCE, 0xEB, 0x2D, 0x47, 0xC1, 0xF3, 0x9F, 0x38, 0x4E, 0xED, 480 | 0x48, 0xBC, 0x7E, 0x41, 0xE6, 0xE8, 0x06, 0x34, 0x9D, 0x97, 0x08, 0xC4, 0x56, 0x93, 0x3B, 0xCF, 481 | 0x3B, 0x9E, 0x6B, 0x39, 0xC4, 0xFA, 0xC8, 0x12, 0xF3, 0x71, 0x5A, 0x70, 0x11, 0xE9, 0x4E, 0x43, 482 | 0xBC, 0x4E, 0xEB, 0xB5, 0x67, 0xE3, 0x8C, 0x9B, 0x1E, 0x33, 0x31, 0x4E, 0x4F, 0xC1, 0xCA, 0xC8, 483 | 0x8E, 0x52, 0x92, 0xC0, 0x88, 0xBD, 0x77, 0x45, 0x98, 0x29, 0x65, 0x61, 0xA1, 0x8C, 0xD4, 0x45, 484 | 0xD8, 0x2C, 0xA9, 0xD6, 0x91, 0xCA, 0x89, 0xDB, 0x0A, 0xF4, 0x8C, 0xD8, 0x16, 0x7F, 0x05, 0x9E, 485 | 0x5B, 0x3B, 0xBE, 0x13, 0x9B, 0x61, 0x9D, 0x07, 0x9B, 0x40, 0x61, 0x90, 0x32, 0x51, 0x9E, 0x99, 486 | 0xD2, 0x5D, 0xFD, 0x51, 0x92, 0x49, 0x72, 0x6C, 0x26, 0x3E, 0x4A, 0x4D, 0xE3, 0x05, 0x8D, 0xCF, 487 | 0xFC, 0x07, 0x77, 0x9A, 0x3F, 0x4F, 0x44, 0x11, 0x54, 0x72, 0xD2, 0xB1, 0x62, 0x30, 0xE1, 0x81, 488 | 0xEC, 0xBF, 0x1E, 0x51, 0x1B, 0x11, 0xE8, 0xAE, 0x2F, 0x1D, 0xCC, 0x0E, 0x9F, 0x5E, 0xFF, 0x00, 489 | 0xC5, 0x5B, 0xB4, 0x20, 0x5C, 0x9A, 0x84, 0xE0, 0x22, 0x6E, 0xFF, 0x4A, 0x29, 0x93, 0x56, 0x51, 490 | 0xE1, 0xC1, 0xDB, 0x77, 0x91, 0x71, 0x8A, 0x38, 0xC4, 0x9D, 0x7E, 0x8A, 0x94, 0x71, 0x2D, 0xA7, 491 | 0x4D, 0xF5, 0xF7, 0x0A, 0xBD, 0x9A, 0xED, 0x8A, 0xE8, 0x95, 0x96, 0x5E, 0xA1, 0xE6, 0xAE, 0x5C, 492 | 0x4E, 0xAC, 0x36, 0xB7, 0x47, 0x8A, 0xB1, 0x03, 0xEA, 0x2D, 0xC4, 0x1A, 0x23, 0xE3, 0xE6, 0x2B, 493 | 0xE2, 0xDA, 0xDE, 0xAA, 0xC1, 0xCE, 0xD7, 0x64, 0x91, 0x54, 0x15, 0x6D, 0x10, 0x17, 0x0C, 0xF8, 494 | 0xE2, 0x97, 0x57, 0x2F, 0x59, 0xD2, 0x51, 0xD7, 0x2A, 0x47, 0xE9, 0x0E, 0x87, 0xBF, 0xEB, 0x5C, 495 | 0x3B, 0x03, 0x83, 0xAD, 0x01, 0x4D, 0xB3, 0x48, 0x36, 0x71, 0x63, 0xC9, 0x62, 0x81, 0x1D, 0x7E, 496 | 0x10, 0x73, 0xB2, 0xD2, 0x93, 0x02, 0xF8, 0xB8, 0x54, 0x16, 0x6F, 0x91, 0x15, 0x05, 0x22, 0xF1, 497 | 0x09, 0xA5, 0xE0, 0xB0, 0x86, 0x70, 0xE5, 0x80, 0x65, 0x19, 0xB9, 0xCE, 0xBB, 0x63, 0xA8, 0xE0, 498 | 0xE7, 0x04, 0x7D, 0x62, 0x26, 0x19, 0x65, 0x69, 0xE1, 0x95, 0x4C, 0x89, 0x16, 0x10, 0x99, 0xF8, 499 | 0xF1, 0x7B, 0x6B, 0x0C, 0xC9, 0xF1, 0x19, 0x78, 0x7E, 0xC3, 0x05, 0x0D, 0x8E, 0x6F, 0x8A, 0xD4, 500 | 0x11, 0xE6, 0x4A, 0x80, 0xAC, 0x2A, 0x04, 0x4F, 0x43, 0x7A, 0x6E, 0x29, 0xFB, 0xE8, 0xD9, 0xA9, 501 | 0xDE, 0x2B, 0xAE, 0xDD, 0xB2, 0x36, 0x2D, 0xCF, 0xB0, 0xC3, 0x75, 0xD3, 0x8A, 0x3E, 0x25, 0xC5, 502 | 0x20, 0x49, 0x30, 0x6B, 0xC2, 0x66, 0xDA, 0x14, 0xC5, 0x2F, 0xA2, 0x01, 0x91, 0xEC, 0x6A, 0x40, 503 | 0xE4, 0xC8, 0x9E, 0xEE, 0xE2, 0x32, 0xED, 0x42, 0x06, 0x72, 0x99, 0xC5, 0x0C, 0xF6, 0xD6, 0x8F, 504 | 0x19, 0x2B, 0xD0, 0xD2, 0x09, 0xAA, 0x14, 0x0A, 0x6D, 0x06, 0x2C, 0xAC, 0x18, 0x62, 0x86, 0x48, 505 | 0xDA, 0x6C, 0xB7, 0x99, 0xAE, 0x0E, 0x17, 0x21, 0x58, 0x69, 0x1E, 0xF9, 0xA4, 0xF8, 0x8D, 0xB5, 506 | 0x6C, 0x71, 0xF0, 0x40, 0x0B, 0x57, 0x14, 0xD4, 0x70, 0x5A, 0xC9, 0x04, 0xB2, 0xDF, 0x2B, 0x21, 507 | 0x50, 0xEE, 0xBA, 0xE0, 0xB4, 0xF0, 0xD3, 0xBA, 0xD8, 0x1A, 0x23, 0xC3, 0xB8, 0xE3, 0x18, 0x73, 508 | 0x46, 0x24, 0xBB, 0xA2, 0x04, 0xF1, 0xF5, 0xEE, 0x34, 0x0B, 0xF9, 0x5A, 0x57, 0x7A, 0xA3, 0xA0, 509 | 0x15, 0xDD, 0xB7, 0x96, 0xE8, 0x83, 0x8B, 0x95, 0xC7, 0xAA, 0xF2, 0x51, 0x97, 0x5D, 0x42, 0xA1, 510 | 0xDE, 0x65, 0x27, 0xD4, 0xC7, 0x15, 0xD5, 0xC7, 0x52, 0x7D, 0x46, 0x90, 0x34, 0x84, 0xE5, 0x2D, 511 | 0x7F, 0xEC, 0x8C, 0xBF, 0x3D, 0x4D, 0x34, 0x5B, 0x8D, 0x0B, 0xE5, 0x94, 0xAD, 0xB8, 0xA2, 0x5E, 512 | 0x31, 0x41, 0xEA, 0x9E, 0x62, 0xA1, 0xD6, 0x6A, 0x5C, 0x4D, 0xAD, 0xA8, 0x95, 0x67, 0x04, 0x89, 513 | 0x5A, 0xFA, 0x86, 0x3F, 0x52, 0x25, 0xDE, 0x42, 0xE6, 0xFF, 0xA7, 0x4B, 0xFC, 0xCE, 0x94, 0x58, 514 | 0x58, 0xB1, 0xFF, 0x5A, 0x5A, 0xCA, 0xC4, 0x30, 0x45, 0xC9, 0x78, 0xC9, 0x50, 0x4A, 0x1A, 0x8F, 515 | 0x54, 0xA8, 0x63, 0x39, 0x0A, 0xA9, 0xA3, 0x41, 0xA2, 0x06, 0xC6, 0x5F, 0x2B, 0x19, 0x2B, 0x1E, 516 | 0x9D, 0x04, 0x42, 0xC2, 0x40, 0x34, 0xE0, 0x23, 0xE3, 0x2C, 0xBB, 0xD4, 0x14, 0x8D, 0x90, 0x50, 517 | 0x36, 0xD3, 0xFE, 0xA8, 0x03, 0x62, 0x95, 0x52, 0x63, 0xE2, 0x00, 0x11, 0xF4, 0x79, 0x62, 0x96, 518 | 0x8A, 0x82, 0x1C, 0xEC, 0xD3, 0x9A, 0xF9, 0x93, 0x83, 0xD9, 0xF2, 0x41, 0xDE, 0x14, 0x7E, 0xF1, 519 | 0xC3, 0x73, 0xC3, 0xF3, 0x0D, 0xF1, 0x16, 0x4D, 0x3F, 0x7E, 0x6B, 0x8E, 0x21, 0x5F, 0x31, 0xC7, 520 | 0x17, 0x69, 0xC4, 0x9D, 0x1A, 0x74, 0x46, 0x02, 0xE8, 0x59, 0xD9, 0x93, 0xE0, 0xF8, 0xAE, 0x19, 521 | 0xBF, 0x45, 0xAE, 0x54, 0x3D, 0xD1, 0xA4, 0x7E, 0x17, 0x2B, 0x92, 0x31, 0xA7, 0xA0, 0x49, 0x6C, 522 | 0x79, 0x57, 0xEA, 0xB8, 0x96, 0x58, 0x8A, 0x96, 0x85, 0x1B, 0x98, 0x30, 0x3E, 0xFD, 0xC5, 0x5A, 523 | 0x51, 0xAF, 0x40, 0xA9, 0x21, 0x63, 0xB2, 0xC4, 0x96, 0x89, 0xAE, 0x6B, 0xD6, 0xD4, 0xAD, 0xBD, 524 | 0x0B, 0x10, 0x65, 0x5B, 0x49, 0xDA, 0x6C, 0x9E, 0x8F, 0x8A, 0xB0, 0xB8, 0xA8, 0x72, 0xE2, 0x33, 525 | 0x38, 0x8D, 0x36, 0x2C, 0xC5, 0x37, 0xF1, 0x52, 0xAE, 0xC1, 0xA9, 0xF8, 0x9F, 0x0A, 0xFF, 0x0B, 526 | 0x9B, 0xFC, 0x8E, 0x51, 0xC1, 0x70, 0x00, 0x00 527 | }; 528 | -------------------------------------------------------------------------------- /ESP32CameraRobotOldVer/camera_pins.h: -------------------------------------------------------------------------------- 1 | 2 | #if defined(CAMERA_MODEL_WROVER_KIT) 3 | #define PWDN_GPIO_NUM -1 4 | #define RESET_GPIO_NUM -1 5 | #define XCLK_GPIO_NUM 21 6 | #define SIOD_GPIO_NUM 26 7 | #define SIOC_GPIO_NUM 27 8 | 9 | #define Y9_GPIO_NUM 35 10 | #define Y8_GPIO_NUM 34 11 | #define Y7_GPIO_NUM 39 12 | #define Y6_GPIO_NUM 36 13 | #define Y5_GPIO_NUM 19 14 | #define Y4_GPIO_NUM 18 15 | #define Y3_GPIO_NUM 5 16 | #define Y2_GPIO_NUM 4 17 | #define VSYNC_GPIO_NUM 25 18 | #define HREF_GPIO_NUM 23 19 | #define PCLK_GPIO_NUM 22 20 | 21 | #elif defined(CAMERA_MODEL_ESP_EYE) 22 | #define PWDN_GPIO_NUM -1 23 | #define RESET_GPIO_NUM -1 24 | #define XCLK_GPIO_NUM 4 25 | #define SIOD_GPIO_NUM 18 26 | #define SIOC_GPIO_NUM 23 27 | 28 | #define Y9_GPIO_NUM 36 29 | #define Y8_GPIO_NUM 37 30 | #define Y7_GPIO_NUM 38 31 | #define Y6_GPIO_NUM 39 32 | #define Y5_GPIO_NUM 35 33 | #define Y4_GPIO_NUM 14 34 | #define Y3_GPIO_NUM 13 35 | #define Y2_GPIO_NUM 34 36 | #define VSYNC_GPIO_NUM 5 37 | #define HREF_GPIO_NUM 27 38 | #define PCLK_GPIO_NUM 25 39 | 40 | #elif defined(CAMERA_MODEL_ESP32_CAM) 41 | #define PWDN_GPIO_NUM 32 42 | #define RESET_GPIO_NUM -1 43 | #define XCLK_GPIO_NUM 0 44 | #define SIOD_GPIO_NUM 26 45 | #define SIOC_GPIO_NUM 27 46 | 47 | #define Y9_GPIO_NUM 35 48 | #define Y8_GPIO_NUM 34 49 | #define Y7_GPIO_NUM 39 50 | #define Y6_GPIO_NUM 36 51 | #define Y5_GPIO_NUM 21 52 | #define Y4_GPIO_NUM 19 53 | #define Y3_GPIO_NUM 18 54 | #define Y2_GPIO_NUM 5 55 | #define VSYNC_GPIO_NUM 25 56 | #define HREF_GPIO_NUM 23 57 | #define PCLK_GPIO_NUM 22 58 | 59 | #define SD_CLK 14 60 | #define SD_CMD 15 61 | #define SD_SDA 2 62 | #define SD_SDA1 4 63 | #define SD_SDA2 12 64 | #define SD_SDA3 13 65 | 66 | #elif defined(CAMERA_MODEL_M5CAM) 67 | #define PWDN_GPIO_NUM -1 68 | #define RESET_GPIO_NUM 15 69 | #define XCLK_GPIO_NUM 27 70 | #define SIOD_GPIO_NUM 25 71 | #define SIOC_GPIO_NUM 23 72 | 73 | #define Y9_GPIO_NUM 19 74 | #define Y8_GPIO_NUM 36 75 | #define Y7_GPIO_NUM 18 76 | #define Y6_GPIO_NUM 39 77 | #define Y5_GPIO_NUM 5 78 | #define Y4_GPIO_NUM 34 79 | #define Y3_GPIO_NUM 35 80 | #define Y2_GPIO_NUM 17 81 | #define VSYNC_GPIO_NUM 22 82 | #define HREF_GPIO_NUM 26 83 | #define PCLK_GPIO_NUM 21 84 | 85 | #define I2C_SDA_NUM 12 86 | #define I2C_SCL_NUM 13 87 | 88 | #elif defined(CAMERA_MODEL_M5CAM_PSRAM) 89 | #define PWDN_GPIO_NUM -1 90 | #define RESET_GPIO_NUM 15 91 | #define XCLK_GPIO_NUM 27 92 | #define SIOD_GPIO_NUM 25 93 | #define SIOC_GPIO_NUM 23 94 | 95 | #define Y9_GPIO_NUM 19 96 | #define Y8_GPIO_NUM 36 97 | #define Y7_GPIO_NUM 18 98 | #define Y6_GPIO_NUM 39 99 | #define Y5_GPIO_NUM 5 100 | #define Y4_GPIO_NUM 34 101 | #define Y3_GPIO_NUM 35 102 | #define Y2_GPIO_NUM 32 103 | #define VSYNC_GPIO_NUM 22 104 | #define HREF_GPIO_NUM 26 105 | #define PCLK_GPIO_NUM 21 106 | 107 | #define I2C_SDA_NUM 12 108 | #define I2C_SCL_NUM 13 109 | 110 | #elif defined(CAMERA_MODEL_M5CAM_PSRAM_WIDE) 111 | #define PWDN_GPIO_NUM -1 112 | #define RESET_GPIO_NUM 15 113 | #define XCLK_GPIO_NUM 27 114 | #define SIOD_GPIO_NUM 22 115 | #define SIOC_GPIO_NUM 23 116 | 117 | #define Y9_GPIO_NUM 19 118 | #define Y8_GPIO_NUM 36 119 | #define Y7_GPIO_NUM 18 120 | #define Y6_GPIO_NUM 39 121 | #define Y5_GPIO_NUM 5 122 | #define Y4_GPIO_NUM 34 123 | #define Y3_GPIO_NUM 35 124 | #define Y2_GPIO_NUM 32 125 | #define VSYNC_GPIO_NUM 25 126 | #define HREF_GPIO_NUM 26 127 | #define PCLK_GPIO_NUM 21 128 | 129 | #elif defined(CAMERA_MODEL_AI_THINKER) 130 | #define PWDN_GPIO_NUM 32 131 | #define RESET_GPIO_NUM -1 132 | #define XCLK_GPIO_NUM 0 133 | #define SIOD_GPIO_NUM 26 134 | #define SIOC_GPIO_NUM 27 135 | 136 | #define Y9_GPIO_NUM 35 137 | #define Y8_GPIO_NUM 34 138 | #define Y7_GPIO_NUM 39 139 | #define Y6_GPIO_NUM 36 140 | #define Y5_GPIO_NUM 21 141 | #define Y4_GPIO_NUM 19 142 | #define Y3_GPIO_NUM 18 143 | #define Y2_GPIO_NUM 5 144 | #define VSYNC_GPIO_NUM 25 145 | #define HREF_GPIO_NUM 23 146 | #define PCLK_GPIO_NUM 22 147 | 148 | #elif defined(CAMERA_MODEL_JSZWY_CYIS) 149 | #define PWDN_GPIO_NUM -1 150 | #define RESET_GPIO_NUM -1 151 | #define XCLK_GPIO_NUM 27 152 | #define SIOD_GPIO_NUM 2 153 | #define SIOC_GPIO_NUM 15 154 | 155 | #define Y9_GPIO_NUM 14 156 | #define Y8_GPIO_NUM 26 157 | #define Y7_GPIO_NUM 25 158 | #define Y6_GPIO_NUM 32 159 | #define Y5_GPIO_NUM 34 160 | #define Y4_GPIO_NUM 38 161 | #define Y3_GPIO_NUM 39 162 | #define Y2_GPIO_NUM 35 163 | #define VSYNC_GPIO_NUM 13 164 | #define HREF_GPIO_NUM 12 165 | #define PCLK_GPIO_NUM 33 166 | 167 | #define I2C_SDA_NUM -1 168 | #define I2C_SCL_NUM -1 169 | 170 | #define Headlamp_Pin 4 171 | #define MotorR_A_Pin 9 172 | #define MotorR_B_Pin 10 173 | #define MotorL_A_Pin 18 174 | #define MotorL_B_Pin 23 175 | 176 | #else 177 | #error "Camera model not selected" 178 | #endif 179 | -------------------------------------------------------------------------------- /ESP32CameraRobotOldVer/index.ov2640.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ESP32 OV2640 8 | 342 | 343 | 344 | 345 |
346 | 349 |
350 | 534 |
535 | 539 |
540 |
541 |
542 | 721 | 722 | 723 | -------------------------------------------------------------------------------- /ESP32VideoControllerM5Stack/ESP32VideoControllerM5Stack.ino: -------------------------------------------------------------------------------- 1 | #define SSID_NAME "Strider Walker V2" 2 | #define SSID_PASSWORD "" 3 | 4 | #define MJPEG_BUFFER_SIZE (320 * 240 * 2 / 4) 5 | 6 | #define HTTP_HOST "192.168.4.1" 7 | #define HTTP_PORT 80 8 | #define STREAM_PORT 81 9 | #define STREAM_PATH "/stream" 10 | #define MOTOR_STOP_PATH "/motor?la=0&lb=0&ra=0&rb=0" 11 | #define MOTOR_FORWARD_PATH "/motor?la=0&lb=255&ra=0&rb=255" 12 | #define MOTOR_BACKWARD_PATH "/motor?la=255&lb=0&ra=255&rb=0" 13 | #define MOTOR_LEFT_PATH "/motor?la=255&lb=0&ra=0&rb=255" 14 | #define MOTOR_RIGHT_PATH "/motor?la=0&lb=255&ra=255&rb=0" 15 | #define FRAMESIZE_QVGA_PATH "/control?var=framesize&val=5" 16 | #define LED_ON_PATH "/gpio?pin=4&val=1" 17 | #define LED_OFF_PATH "/gpio?pin=4&val=0" 18 | 19 | enum motorCommand 20 | { 21 | stop, 22 | forward, 23 | backward, 24 | left, 25 | right 26 | }; 27 | motorCommand lastMotorCommand = stop; 28 | 29 | #include 30 | #include 31 | WiFiClient streamClient; 32 | HTTPClient streamHttp; 33 | WiFiClient controlClient; 34 | HTTPClient controlHttp; 35 | 36 | /* Arduino_GFX */ 37 | #include 38 | #define TFT_BL 32 39 | Arduino_DataBus *bus = new Arduino_ESP32SPI(27 /* DC */, 14 /* CS */, SCK, MOSI, MISO); 40 | Arduino_GFX *gfx = new Arduino_ILI9342(bus, 33 /* RST */, 0 /* rotation */); 41 | 42 | /* MJPEG Video */ 43 | #include "MjpegClass.h" 44 | static MjpegClass mjpeg; 45 | 46 | // pixel drawing callback 47 | static int drawMCU(JPEGDRAW *pDraw) 48 | { 49 | // log_v("Draw pos = %d,%d. size = %d x %d", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); 50 | gfx->draw16bitBeRGBBitmap(pDraw->x, pDraw->y, pDraw->pPixels, pDraw->iWidth, pDraw->iHeight); 51 | return 1; 52 | } /* drawMCU() */ 53 | 54 | void setup() 55 | { 56 | gfx->begin(); 57 | gfx->fillScreen(BLACK); 58 | 59 | WiFi.begin(SSID_NAME, SSID_PASSWORD); 60 | 61 | #ifdef TFT_BL 62 | pinMode(TFT_BL, OUTPUT); 63 | digitalWrite(TFT_BL, HIGH); 64 | #endif 65 | 66 | pinMode(39, INPUT_PULLUP); // Button A 67 | pinMode(38, INPUT_PULLUP); // Button B 68 | pinMode(37, INPUT_PULLUP); // Button C 69 | } 70 | 71 | void loop() 72 | { 73 | if (WiFi.status() != WL_CONNECTED) 74 | { 75 | // wait for WiFi connection 76 | delay(500); 77 | } 78 | else 79 | { 80 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, FRAMESIZE_QVGA_PATH); 81 | controlHttp.GET(); 82 | controlHttp.end(); 83 | 84 | log_v("[HTTP] begin..."); 85 | streamHttp.begin(streamClient, HTTP_HOST, STREAM_PORT, STREAM_PATH); 86 | 87 | log_v("[HTTP] GET..."); 88 | int httpCode = streamHttp.GET(); 89 | 90 | log_v("[HTTP] GET... code: %d", httpCode); 91 | // HTTP header has been send and Server response header has been handled 92 | if (httpCode <= 0) 93 | { 94 | log_e("[HTTP] GET... failed, error: %s", streamHttp.errorToString(httpCode).c_str()); 95 | } 96 | else 97 | { 98 | if (httpCode != HTTP_CODE_OK) 99 | { 100 | log_e("[HTTP] Not OK!"); 101 | } 102 | else 103 | { 104 | // get tcp stream 105 | WiFiClient *stream = streamHttp.getStreamPtr(); 106 | 107 | uint8_t *mjpeg_buf = (uint8_t *)malloc(MJPEG_BUFFER_SIZE); 108 | 109 | // init Video 110 | mjpeg.setup(stream, mjpeg_buf, drawMCU, true, true); 111 | 112 | bool recycleStream = false; 113 | while ((streamHttp.connected()) && (!recycleStream)) 114 | { 115 | unsigned long s = millis(); 116 | // Read video 117 | mjpeg.readMjpegBuf(); 118 | unsigned long r = millis() - s; 119 | // Play video 120 | mjpeg.drawJpg(); 121 | unsigned long d = millis() - s - r; 122 | // log_v("[Mjpeg] read used: %lu, draw used: %lu", r, d); 123 | 124 | int btnA = digitalRead(39); 125 | int btnB = digitalRead(38); 126 | int btnC = digitalRead(37); 127 | // log_v("btnA: %d, btnB: %d, btnC: %d", btnA, btnB, btnC); 128 | 129 | motorCommand currentMotorCommand = stop; 130 | if (btnA == 0) // pressed 131 | { 132 | currentMotorCommand = left; 133 | } 134 | else if (btnB == 0) // pressed 135 | { 136 | currentMotorCommand = forward; 137 | } 138 | else if (btnC == 0) // pressed 139 | { 140 | currentMotorCommand = right; 141 | } 142 | 143 | if (currentMotorCommand != lastMotorCommand) 144 | { 145 | switch (currentMotorCommand) 146 | { 147 | case forward: 148 | log_i("forward"); 149 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_FORWARD_PATH); 150 | break; 151 | case backward: 152 | log_i("backward"); 153 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_BACKWARD_PATH); 154 | break; 155 | case left: 156 | log_i("left"); 157 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_LEFT_PATH); 158 | break; 159 | case right: 160 | log_i("right"); 161 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_RIGHT_PATH); 162 | break; 163 | default: 164 | log_i("stop"); 165 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_STOP_PATH); 166 | } 167 | controlHttp.GET(); 168 | controlHttp.end(); 169 | lastMotorCommand = currentMotorCommand; 170 | } 171 | } 172 | stream->flush(); 173 | } 174 | } 175 | 176 | log_v("[HTTP] connection closed."); 177 | streamHttp.end(); 178 | } 179 | } -------------------------------------------------------------------------------- /ESP32VideoControllerM5Stack/MjpegClass.h: -------------------------------------------------------------------------------- 1 | #ifndef _MJPEGCLASS_H_ 2 | #define _MJPEGCLASS_H_ 3 | 4 | #define READ_BUFFER_SIZE 1024 5 | #define MAXOUTPUTSIZE 8 6 | #define NUMBER_OF_DRAW_BUFFER 4 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | typedef struct 18 | { 19 | JPEG_DRAW_CALLBACK *drawFunc; 20 | } paramDrawTask; 21 | 22 | static xQueueHandle xqh = 0; 23 | static JPEGDRAW jpegdraws[NUMBER_OF_DRAW_BUFFER]; 24 | static int queue_cnt = 0; 25 | static int draw_cnt = 0; 26 | static bool taskCreated = false; 27 | 28 | static int queueDrawMCU(JPEGDRAW *pDraw) 29 | { 30 | ++queue_cnt; 31 | while ((queue_cnt - draw_cnt) > NUMBER_OF_DRAW_BUFFER) 32 | { 33 | delay(1); 34 | } 35 | 36 | int len = pDraw->iWidth * pDraw->iHeight * 2; 37 | JPEGDRAW *j = &jpegdraws[queue_cnt % NUMBER_OF_DRAW_BUFFER]; 38 | j->x = pDraw->x; 39 | j->y = pDraw->y; 40 | j->iWidth = pDraw->iWidth; 41 | j->iHeight = pDraw->iHeight; 42 | memcpy(j->pPixels, pDraw->pPixels, len); 43 | 44 | xQueueSend(xqh, &j, 0); 45 | return 1; 46 | } 47 | 48 | static void drawTask(void *arg) 49 | { 50 | paramDrawTask *p = (paramDrawTask *)arg; 51 | for (int i = 0; i < NUMBER_OF_DRAW_BUFFER; i++) 52 | { 53 | jpegdraws[i].pPixels = (uint16_t *)heap_caps_malloc(MAXOUTPUTSIZE * 16 * 16 * 2, MALLOC_CAP_DMA); 54 | Serial.printf("#%d draw buffer allocated\n", i); 55 | } 56 | JPEGDRAW *pDraw; 57 | Serial.println("drawTask start"); 58 | while (xQueueReceive(xqh, &pDraw, portMAX_DELAY)) 59 | { 60 | // Serial.printf("task work: x: %d, y: %d, iWidth: %d, iHeight: %d\r\n", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); 61 | p->drawFunc(pDraw); 62 | // Serial.println("task work done"); 63 | ++draw_cnt; 64 | } 65 | vQueueDelete(xqh); 66 | Serial.println("drawTask end"); 67 | vTaskDelete(NULL); 68 | } 69 | 70 | class MjpegClass 71 | { 72 | public: 73 | bool setup(Stream *input, uint8_t *mjpeg_buf, JPEG_DRAW_CALLBACK *pfnDraw, bool enableMultiTask, bool useBigEndian) 74 | { 75 | _input = input; 76 | _inputindex = 0; 77 | _mjpeg_buf = mjpeg_buf; 78 | _pfnDraw = pfnDraw; 79 | _enableMultiTask = enableMultiTask; 80 | _useBigEndian = useBigEndian; 81 | 82 | if (!_read_buf) 83 | { 84 | _read_buf = (uint8_t *)malloc(READ_BUFFER_SIZE); 85 | } 86 | 87 | if (_enableMultiTask) 88 | { 89 | if (!taskCreated) 90 | { 91 | TaskHandle_t task; 92 | _p.drawFunc = pfnDraw; 93 | xqh = xQueueCreate(NUMBER_OF_DRAW_BUFFER, sizeof(JPEGDRAW)); 94 | xTaskCreatePinnedToCore(drawTask, "drawTask", 1600, &_p, 1, &task, 0); 95 | taskCreated = true; 96 | } 97 | } 98 | 99 | return true; 100 | } 101 | 102 | bool readMjpegBuf() 103 | { 104 | if (_inputindex == 0) 105 | { 106 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 107 | _inputindex += _buf_read; 108 | } 109 | _mjpeg_buf_offset = 0; 110 | int i = 0; 111 | bool found_FFD8 = false; 112 | while ((_buf_read > 0) && (!found_FFD8)) 113 | { 114 | i = 0; 115 | while ((i < _buf_read) && (!found_FFD8)) 116 | { 117 | if ((_read_buf[i] == 0xFF) && (_read_buf[i + 1] == 0xD8)) // JPEG header 118 | { 119 | // Serial.printf("Found FFD8 at: %d.\n", i); 120 | found_FFD8 = true; 121 | } 122 | ++i; 123 | } 124 | if (found_FFD8) 125 | { 126 | --i; 127 | } 128 | else 129 | { 130 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 131 | } 132 | } 133 | uint8_t *_p = _read_buf + i; 134 | _buf_read -= i; 135 | bool found_FFD9 = false; 136 | if (_buf_read > 0) 137 | { 138 | i = 3; 139 | while ((_buf_read > 0) && (!found_FFD9)) 140 | { 141 | if ((_mjpeg_buf_offset > 0) && (_mjpeg_buf[_mjpeg_buf_offset - 1] == 0xFF) && (_p[0] == 0xD9)) // JPEG trailer 142 | { 143 | // Serial.printf("Found FFD9 at: %d.\n", i); 144 | found_FFD9 = true; 145 | } 146 | else 147 | { 148 | while ((i < _buf_read) && (!found_FFD9)) 149 | { 150 | if ((_p[i] == 0xFF) && (_p[i + 1] == 0xD9)) // JPEG trailer 151 | { 152 | found_FFD9 = true; 153 | ++i; 154 | } 155 | ++i; 156 | } 157 | } 158 | 159 | // Serial.printf("i: %d\n", i); 160 | memcpy(_mjpeg_buf + _mjpeg_buf_offset, _p, i); 161 | _mjpeg_buf_offset += i; 162 | size_t o = _buf_read - i; 163 | if (o > 0) 164 | { 165 | // Serial.printf("o: %d\n", o); 166 | memcpy(_read_buf, _p + i, o); 167 | _buf_read = _input->readBytes(_read_buf + o, READ_BUFFER_SIZE - o); 168 | _p = _read_buf; 169 | _inputindex += _buf_read; 170 | _buf_read += o; 171 | // Serial.printf("_buf_read: %d\n", _buf_read); 172 | } 173 | else 174 | { 175 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 176 | _p = _read_buf; 177 | _inputindex += _buf_read; 178 | } 179 | i = 0; 180 | } 181 | if (found_FFD9) 182 | { 183 | return true; 184 | } 185 | } 186 | 187 | return false; 188 | } 189 | 190 | int getWidth() 191 | { 192 | return _jpeg.getWidth(); 193 | } 194 | 195 | int getHeight() 196 | { 197 | return _jpeg.getHeight(); 198 | } 199 | 200 | bool drawJpg() 201 | { 202 | _remain = _mjpeg_buf_offset; 203 | 204 | if (_enableMultiTask) 205 | { 206 | _jpeg.openRAM(_mjpeg_buf, _remain, queueDrawMCU); 207 | } 208 | else 209 | { 210 | _jpeg.openRAM(_mjpeg_buf, _remain, _pfnDraw); 211 | } 212 | 213 | _jpeg.setMaxOutputSize(MAXOUTPUTSIZE); 214 | if (_useBigEndian) 215 | { 216 | _jpeg.setPixelType(RGB565_BIG_ENDIAN); 217 | } 218 | _jpeg.decode(0, 0, 0); 219 | _jpeg.close(); 220 | 221 | return true; 222 | } 223 | 224 | private: 225 | Stream *_input; 226 | uint8_t *_mjpeg_buf; 227 | JPEG_DRAW_CALLBACK *_pfnDraw; 228 | bool _enableMultiTask; 229 | bool _useBigEndian; 230 | 231 | uint8_t *_read_buf; 232 | int32_t _mjpeg_buf_offset = 0; 233 | 234 | JPEGDEC _jpeg; 235 | paramDrawTask _p; 236 | 237 | int32_t _inputindex = 0; 238 | int32_t _buf_read; 239 | int32_t _remain = 0; 240 | }; 241 | 242 | #endif // _MJPEGCLASS_H_ 243 | -------------------------------------------------------------------------------- /ESP32VideoControllerOdroidGo/ESP32VideoControllerOdroidGo.ino: -------------------------------------------------------------------------------- 1 | #define SSID_NAME "XIAO nanotank" 2 | #define SSID_PASSWORD "" 3 | 4 | #define MJPEG_BUFFER_SIZE (320 * 240 * 2 / 4) 5 | 6 | #define HTTP_HOST "192.168.4.1" 7 | #define HTTP_PORT 80 8 | #define STREAM_PORT 81 9 | #define STREAM_PATH "/stream" 10 | #define MOTOR_STOP_PATH "/motor?la=0&lb=0&ra=0&rb=0" 11 | #define MOTOR_FORWARD_PATH "/motor?la=0&lb=255&ra=0&rb=255" 12 | #define MOTOR_BACKWARD_PATH "/motor?la=255&lb=0&ra=255&rb=0" 13 | #define MOTOR_LEFT_PATH "/motor?la=255&lb=0&ra=0&rb=255" 14 | #define MOTOR_RIGHT_PATH "/motor?la=0&lb=255&ra=255&rb=0" 15 | #define FRAMESIZE_QVGA_PATH "/control?var=framesize&val=5" 16 | #define LED_ON_PATH "/gpio?pin=4&val=1" 17 | #define LED_OFF_PATH "/gpio?pin=4&val=0" 18 | 19 | enum motorCommand 20 | { 21 | stop, 22 | forward, 23 | backward, 24 | left, 25 | right 26 | }; 27 | motorCommand lastMotorCommand = stop; 28 | int lastBtnStartStatus = 1; 29 | int lastBtnAStatus = 1; 30 | int lastLedCommand = 0; 31 | 32 | #include 33 | #include 34 | WiFiClient streamClient; 35 | HTTPClient streamHttp; 36 | WiFiClient controlClient; 37 | HTTPClient controlHttp; 38 | 39 | /* Arduino_GFX */ 40 | #include 41 | #define TFT_BL 14 42 | Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(21 /* DC */, 5 /* CS */, 18 /* SCK */, 23 /* MOSI */, GFX_NOT_DEFINED /* MISO */); 43 | // Arduino_ILI9341 *gfx = new Arduino_ILI9341(bus, GFX_NOT_DEFINED /* RST */, 3 /* rotation */); 44 | Arduino_GFX *gfx = new Arduino_ST7789(bus, GFX_NOT_DEFINED /* RST */, 3 /* rotation */, true /* IPS */); 45 | 46 | /* MJPEG Video */ 47 | #include "MjpegClass.h" 48 | static MjpegClass mjpeg; 49 | 50 | // pixel drawing callback 51 | static int drawMCU(JPEGDRAW *pDraw) 52 | { 53 | // Serial.printf("Draw pos = %d,%d. size = %d x %d\n", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); 54 | gfx->draw16bitBeRGBBitmap(pDraw->x, pDraw->y, pDraw->pPixels, pDraw->iWidth, pDraw->iHeight); 55 | return 1; 56 | } /* drawMCU() */ 57 | 58 | void setup() 59 | { 60 | Serial.begin(115200); 61 | // Serial.setDebugOutput(true); 62 | // while(!Serial); 63 | Serial.println("ESP32 Video Controller OdroidGo!"); 64 | 65 | #ifdef GFX_EXTRA_PRE_INIT 66 | GFX_EXTRA_PRE_INIT(); 67 | #endif 68 | 69 | // Init Display 70 | if (!gfx->begin()) 71 | { 72 | Serial.println("gfx->begin() failed!"); 73 | } 74 | gfx->fillScreen(BLACK); 75 | 76 | #ifdef TFT_BL 77 | pinMode(TFT_BL, OUTPUT); 78 | digitalWrite(TFT_BL, HIGH); 79 | #endif 80 | 81 | WiFi.begin(SSID_NAME, SSID_PASSWORD); 82 | 83 | pinMode(13, INPUT_PULLUP); // Button Menu 84 | pinMode(0, INPUT_PULLUP); // Button Volume 85 | pinMode(27, INPUT_PULLUP); // Button Select 86 | pinMode(39, INPUT_PULLUP); // Button Start 87 | pinMode(32, INPUT_PULLUP); // Button A 88 | pinMode(33, INPUT_PULLUP); // Button B 89 | pinMode(34, INPUT); // X-axis 90 | pinMode(35, INPUT); // Y-axis 91 | } 92 | 93 | void loop() 94 | { 95 | if (WiFi.status() != WL_CONNECTED) 96 | { 97 | // wait for WiFi connection 98 | Serial.println("."); 99 | delay(500); 100 | } 101 | else 102 | { 103 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, FRAMESIZE_QVGA_PATH); 104 | controlHttp.GET(); 105 | controlHttp.end(); 106 | 107 | Serial.println("[HTTP] begin..."); 108 | streamHttp.begin(streamClient, HTTP_HOST, STREAM_PORT, STREAM_PATH); 109 | 110 | Serial.println("[HTTP] GET..."); 111 | int httpCode = streamHttp.GET(); 112 | 113 | Serial.printf("[HTTP] GET... code: %d\n", httpCode); 114 | // HTTP header has been send and Server response header has been handled 115 | if (httpCode <= 0) 116 | { 117 | Serial.printf("[HTTP] GET... failed, error: %s\n", streamHttp.errorToString(httpCode).c_str()); 118 | } 119 | else 120 | { 121 | if (httpCode != HTTP_CODE_OK) 122 | { 123 | Serial.println("[HTTP] Not OK!"); 124 | } 125 | else 126 | { 127 | // get tcp stream 128 | WiFiClient *stream = streamHttp.getStreamPtr(); 129 | 130 | uint8_t *mjpeg_buf = (uint8_t *)malloc(MJPEG_BUFFER_SIZE); 131 | 132 | // init Video 133 | mjpeg.setup(stream, mjpeg_buf, drawMCU, true, true); 134 | 135 | bool recycleStream = false; 136 | while ((streamHttp.connected()) && (!recycleStream)) 137 | { 138 | unsigned long s = millis(); 139 | // Read video 140 | mjpeg.readMjpegBuf(); 141 | unsigned long r = millis() - s; 142 | // Play video 143 | mjpeg.drawJpg(); 144 | unsigned long d = millis() - s - r; 145 | // Serial.printf("[Mjpeg] read used: %lu, draw used: %lu\n", r, d); 146 | 147 | int btnMenu = digitalRead(13); 148 | int btnVolume = digitalRead(0); 149 | int btnSelect = digitalRead(27); 150 | int btnStart = digitalRead(39); 151 | int btnA = digitalRead(32); 152 | int btnB = digitalRead(33); 153 | int xVal = analogRead(34); 154 | int yVal = analogRead(35); 155 | // Serial.printf("btnMenu: %d, btnVolume: %d, btnSelect: %d, btnStart: %d, btnA: %d, btnB: %d, xVal: %d, yVal: %d\n", btnMenu, btnVolume, btnSelect, btnStart, btnA, btnB, xVal, yVal); 156 | 157 | if (lastBtnStartStatus != btnStart) 158 | { 159 | if (btnStart == 0) // pressed 160 | { 161 | Serial.println("Recycle video stream"); 162 | recycleStream = true; 163 | } 164 | lastBtnStartStatus = btnStart; 165 | } 166 | 167 | if (lastBtnAStatus != btnA) 168 | { 169 | if (btnA == 0) // pressed 170 | { 171 | if (lastLedCommand == 0) 172 | { 173 | Serial.println("LED on"); 174 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, LED_ON_PATH); 175 | controlHttp.GET(); 176 | controlHttp.end(); 177 | lastLedCommand = 1; 178 | } 179 | else 180 | { 181 | Serial.println("LED off"); 182 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, LED_OFF_PATH); 183 | controlHttp.GET(); 184 | controlHttp.end(); 185 | lastLedCommand = 0; 186 | } 187 | } 188 | lastBtnAStatus = btnA; 189 | } 190 | motorCommand currentMotorCommand = stop; 191 | if (yVal > 3800) 192 | { 193 | currentMotorCommand = forward; 194 | } 195 | else if (yVal > 1600) 196 | { 197 | currentMotorCommand = backward; 198 | } 199 | else if (xVal > 3800) 200 | { 201 | currentMotorCommand = left; 202 | } 203 | else if (xVal > 1600) 204 | { 205 | currentMotorCommand = right; 206 | } 207 | 208 | if (currentMotorCommand != lastMotorCommand) 209 | { 210 | switch (currentMotorCommand) 211 | { 212 | case forward: 213 | Serial.println("forward"); 214 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_FORWARD_PATH); 215 | break; 216 | case backward: 217 | Serial.println("backward"); 218 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_BACKWARD_PATH); 219 | break; 220 | case left: 221 | Serial.println("left"); 222 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_LEFT_PATH); 223 | break; 224 | case right: 225 | Serial.println("right"); 226 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_RIGHT_PATH); 227 | break; 228 | default: 229 | Serial.println("stop"); 230 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_STOP_PATH); 231 | } 232 | controlHttp.GET(); 233 | controlHttp.end(); 234 | lastMotorCommand = currentMotorCommand; 235 | } 236 | } 237 | stream->flush(); 238 | } 239 | } 240 | 241 | Serial.println("[HTTP] connection closed."); 242 | streamHttp.end(); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /ESP32VideoControllerOdroidGo/MjpegClass.h: -------------------------------------------------------------------------------- 1 | #ifndef _MJPEGCLASS_H_ 2 | #define _MJPEGCLASS_H_ 3 | 4 | #define READ_BUFFER_SIZE 1024 5 | #define MAXOUTPUTSIZE 8 6 | #define NUMBER_OF_DRAW_BUFFER 4 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | typedef struct 18 | { 19 | JPEG_DRAW_CALLBACK *drawFunc; 20 | } paramDrawTask; 21 | 22 | static xQueueHandle xqh = 0; 23 | static JPEGDRAW jpegdraws[NUMBER_OF_DRAW_BUFFER]; 24 | static int queue_cnt = 0; 25 | static int draw_cnt = 0; 26 | static bool taskCreated = false; 27 | 28 | static int queueDrawMCU(JPEGDRAW *pDraw) 29 | { 30 | ++queue_cnt; 31 | while ((queue_cnt - draw_cnt) > NUMBER_OF_DRAW_BUFFER) 32 | { 33 | delay(1); 34 | } 35 | 36 | int len = pDraw->iWidth * pDraw->iHeight * 2; 37 | JPEGDRAW *j = &jpegdraws[queue_cnt % NUMBER_OF_DRAW_BUFFER]; 38 | j->x = pDraw->x; 39 | j->y = pDraw->y; 40 | j->iWidth = pDraw->iWidth; 41 | j->iHeight = pDraw->iHeight; 42 | memcpy(j->pPixels, pDraw->pPixels, len); 43 | 44 | xQueueSend(xqh, &j, 0); 45 | return 1; 46 | } 47 | 48 | static void drawTask(void *arg) 49 | { 50 | paramDrawTask *p = (paramDrawTask *)arg; 51 | for (int i = 0; i < NUMBER_OF_DRAW_BUFFER; i++) 52 | { 53 | jpegdraws[i].pPixels = (uint16_t *)heap_caps_malloc(MAXOUTPUTSIZE * 16 * 16 * 2, MALLOC_CAP_DMA); 54 | Serial.printf("#%d draw buffer allocated\n", i); 55 | } 56 | JPEGDRAW *pDraw; 57 | Serial.println("drawTask start"); 58 | while (xQueueReceive(xqh, &pDraw, portMAX_DELAY)) 59 | { 60 | // Serial.printf("task work: x: %d, y: %d, iWidth: %d, iHeight: %d\r\n", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); 61 | p->drawFunc(pDraw); 62 | // Serial.println("task work done"); 63 | ++draw_cnt; 64 | } 65 | vQueueDelete(xqh); 66 | Serial.println("drawTask end"); 67 | vTaskDelete(NULL); 68 | } 69 | 70 | class MjpegClass 71 | { 72 | public: 73 | bool setup(Stream *input, uint8_t *mjpeg_buf, JPEG_DRAW_CALLBACK *pfnDraw, bool enableMultiTask, bool useBigEndian) 74 | { 75 | _input = input; 76 | _inputindex = 0; 77 | _mjpeg_buf = mjpeg_buf; 78 | _pfnDraw = pfnDraw; 79 | _enableMultiTask = enableMultiTask; 80 | _useBigEndian = useBigEndian; 81 | 82 | if (!_read_buf) 83 | { 84 | _read_buf = (uint8_t *)malloc(READ_BUFFER_SIZE); 85 | } 86 | 87 | if (_enableMultiTask) 88 | { 89 | if (!taskCreated) 90 | { 91 | TaskHandle_t task; 92 | _p.drawFunc = pfnDraw; 93 | xqh = xQueueCreate(NUMBER_OF_DRAW_BUFFER, sizeof(JPEGDRAW)); 94 | xTaskCreatePinnedToCore(drawTask, "drawTask", 1600, &_p, 1, &task, 0); 95 | taskCreated = true; 96 | } 97 | } 98 | 99 | return true; 100 | } 101 | 102 | bool readMjpegBuf() 103 | { 104 | if (_inputindex == 0) 105 | { 106 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 107 | _inputindex += _buf_read; 108 | } 109 | _mjpeg_buf_offset = 0; 110 | int i = 0; 111 | bool found_FFD8 = false; 112 | while ((_buf_read > 0) && (!found_FFD8)) 113 | { 114 | i = 0; 115 | while ((i < _buf_read) && (!found_FFD8)) 116 | { 117 | if ((_read_buf[i] == 0xFF) && (_read_buf[i + 1] == 0xD8)) // JPEG header 118 | { 119 | // Serial.printf("Found FFD8 at: %d.\n", i); 120 | found_FFD8 = true; 121 | } 122 | ++i; 123 | } 124 | if (found_FFD8) 125 | { 126 | --i; 127 | } 128 | else 129 | { 130 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 131 | } 132 | } 133 | uint8_t *_p = _read_buf + i; 134 | _buf_read -= i; 135 | bool found_FFD9 = false; 136 | if (_buf_read > 0) 137 | { 138 | i = 3; 139 | while ((_buf_read > 0) && (!found_FFD9)) 140 | { 141 | if ((_mjpeg_buf_offset > 0) && (_mjpeg_buf[_mjpeg_buf_offset - 1] == 0xFF) && (_p[0] == 0xD9)) // JPEG trailer 142 | { 143 | // Serial.printf("Found FFD9 at: %d.\n", i); 144 | found_FFD9 = true; 145 | } 146 | else 147 | { 148 | while ((i < _buf_read) && (!found_FFD9)) 149 | { 150 | if ((_p[i] == 0xFF) && (_p[i + 1] == 0xD9)) // JPEG trailer 151 | { 152 | found_FFD9 = true; 153 | ++i; 154 | } 155 | ++i; 156 | } 157 | } 158 | 159 | // Serial.printf("i: %d\n", i); 160 | memcpy(_mjpeg_buf + _mjpeg_buf_offset, _p, i); 161 | _mjpeg_buf_offset += i; 162 | size_t o = _buf_read - i; 163 | if (o > 0) 164 | { 165 | // Serial.printf("o: %d\n", o); 166 | memcpy(_read_buf, _p + i, o); 167 | _buf_read = _input->readBytes(_read_buf + o, READ_BUFFER_SIZE - o); 168 | _p = _read_buf; 169 | _inputindex += _buf_read; 170 | _buf_read += o; 171 | // Serial.printf("_buf_read: %d\n", _buf_read); 172 | } 173 | else 174 | { 175 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 176 | _p = _read_buf; 177 | _inputindex += _buf_read; 178 | } 179 | i = 0; 180 | } 181 | if (found_FFD9) 182 | { 183 | return true; 184 | } 185 | } 186 | 187 | return false; 188 | } 189 | 190 | int getWidth() 191 | { 192 | return _jpeg.getWidth(); 193 | } 194 | 195 | int getHeight() 196 | { 197 | return _jpeg.getHeight(); 198 | } 199 | 200 | bool drawJpg() 201 | { 202 | _remain = _mjpeg_buf_offset; 203 | 204 | if (_enableMultiTask) 205 | { 206 | _jpeg.openRAM(_mjpeg_buf, _remain, queueDrawMCU); 207 | } 208 | else 209 | { 210 | _jpeg.openRAM(_mjpeg_buf, _remain, _pfnDraw); 211 | } 212 | 213 | _jpeg.setMaxOutputSize(MAXOUTPUTSIZE); 214 | if (_useBigEndian) 215 | { 216 | _jpeg.setPixelType(RGB565_BIG_ENDIAN); 217 | } 218 | _jpeg.decode(0, 0, 0); 219 | _jpeg.close(); 220 | 221 | return true; 222 | } 223 | 224 | private: 225 | Stream *_input; 226 | uint8_t *_mjpeg_buf; 227 | JPEG_DRAW_CALLBACK *_pfnDraw; 228 | bool _enableMultiTask; 229 | bool _useBigEndian; 230 | 231 | uint8_t *_read_buf; 232 | int32_t _mjpeg_buf_offset = 0; 233 | 234 | JPEGDEC _jpeg; 235 | paramDrawTask _p; 236 | 237 | int32_t _inputindex = 0; 238 | int32_t _buf_read; 239 | int32_t _remain = 0; 240 | }; 241 | 242 | #endif // _MJPEGCLASS_H_ 243 | -------------------------------------------------------------------------------- /ESP32VideoControllerOldVer/ESP32VideoControllerOldVer.ino: -------------------------------------------------------------------------------- 1 | #define SSID_NAME "ESP32CameraRobot" 2 | #define SSID_PASSWORD "" 3 | // #define URL "http://192.168.4.1:81/vga" 4 | // #define JPG_SCALE JPG_SCALE_2X 5 | // #define JPG_WIDTH 640 6 | // #define JPG_HEIGHT 480 7 | // #define DISP_WIDTH 320 8 | // #define DISP_HEIGHT 240 9 | // #define RATIO_BOUND 4 10 | #define URL "http://192.168.4.1:81/qvga" 11 | #define JPG_SCALE JPG_SCALE_NONE 12 | #define JPG_WIDTH 320 13 | #define JPG_HEIGHT 240 14 | #define DISP_WIDTH 320 15 | #define DISP_HEIGHT 240 16 | #define RATIO_BOUND 4 17 | // #define URL "http://192.168.4.1:81/hqvga" 18 | // #define JPG_SCALE JPG_SCALE_NONE 19 | // #define JPG_WIDTH 240 20 | // #define JPG_HEIGHT 160 21 | // #define DISP_WIDTH 240 22 | // #define DISP_HEIGHT 160 23 | // #define RATIO_BOUND 4 24 | // #define URL "http://192.168.4.1:81/qcif" 25 | // #define JPG_SCALE JPG_SCALE_NONE 26 | // #define JPG_WIDTH 176 27 | // #define JPG_HEIGHT 144 28 | // #define DISP_WIDTH 176 29 | // #define DISP_HEIGHT 144 30 | // #define RATIO_BOUND 4 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #define TFT_BL 14 38 | Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(21 /* DC */, 5 /* CS */, SCK, MOSI, MISO); 39 | Arduino_ST7789 *tft = new Arduino_ST7789(bus, -1 /* RST */, 3 /* rotation */, true /* IPS */); 40 | // Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(27 /* DC */, 5 /* CS */, SCK, MOSI, MISO); 41 | // Arduino_ST7789 *tft = new Arduino_ST7789(bus, 33 /* RST */, 3 /* rotation */, true /* IPS */); 42 | 43 | typedef struct 44 | { 45 | int len; 46 | int offset; 47 | uint8_t *buff; 48 | } buffer_t; 49 | 50 | buffer_t buffer1; 51 | buffer_t buffer2; 52 | uint8_t curr_buffer_idx = 1; 53 | buffer_t *curr_buffer = &buffer1; 54 | bool bufflock = false; 55 | uint16_t drop_frame = 0; 56 | uint16_t decode_frame = 0; 57 | 58 | uint16_t *framebuffer = NULL; 59 | 60 | void setup() 61 | { 62 | tft->begin(); 63 | tft->fillScreen(BLACK); 64 | tft->setAddrWindow((tft->width() - DISP_WIDTH) / 2, (tft->height() - DISP_HEIGHT) / 2, DISP_WIDTH, DISP_HEIGHT); 65 | 66 | WiFi.begin(SSID_NAME, SSID_PASSWORD); 67 | 68 | #ifdef TFT_BL 69 | pinMode(TFT_BL, OUTPUT); 70 | digitalWrite(TFT_BL, HIGH); 71 | #endif 72 | 73 | buffer1.buff = (uint8_t *)heap_caps_malloc(2 * JPG_WIDTH * JPG_HEIGHT / RATIO_BOUND, MALLOC_CAP_SPIRAM); 74 | buffer2.buff = (uint8_t *)heap_caps_malloc(2 * JPG_WIDTH * JPG_HEIGHT / RATIO_BOUND, MALLOC_CAP_SPIRAM); 75 | framebuffer = (uint16_t *)heap_caps_malloc(DISP_WIDTH * DISP_WIDTH * 2, MALLOC_CAP_SPIRAM); 76 | } 77 | 78 | void loop() 79 | { 80 | if (WiFi.status() != WL_CONNECTED) 81 | { 82 | // wait for WiFi connection 83 | delay(500); 84 | } 85 | else 86 | { 87 | HTTPClient http; 88 | 89 | log_i("[HTTP] begin...\n"); 90 | http.begin(URL); 91 | 92 | log_i("[HTTP] GET...\n"); 93 | int httpCode = http.GET(); 94 | 95 | log_i("[HTTP] GET... code: %d\n", httpCode); 96 | // HTTP header has been send and Server response header has been handled 97 | if (httpCode <= 0) 98 | { 99 | log_i("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); 100 | } 101 | else 102 | { 103 | if (httpCode != HTTP_CODE_OK) 104 | { 105 | log_i("[HTTP] Not OK!\n"); 106 | } 107 | else 108 | { 109 | // get tcp stream 110 | WiFiClient *stream = http.getStreamPtr(); 111 | while (http.connected()) 112 | { 113 | curr_buffer->len = -1; 114 | while (http.connected() && (curr_buffer->len < 0)) 115 | { 116 | stream->find('\n'); 117 | if (stream->read() == 'C') 118 | { 119 | if (stream->read() == 'o') 120 | { 121 | if (stream->read() == 'n') 122 | { 123 | if (stream->read() == 't') 124 | { 125 | if (stream->read() == 'e') 126 | { 127 | if (stream->read() == 'n') 128 | { 129 | if (stream->read() == 't') 130 | { 131 | if (stream->read() == '-') 132 | { 133 | if (stream->read() == 'L') 134 | { 135 | if (stream->read() == 'e') 136 | { 137 | if (stream->read() == 'n') 138 | { 139 | if (stream->read() == 'g') 140 | { 141 | if (stream->read() == 't') 142 | { 143 | if (stream->read() == 'h') 144 | { 145 | if (stream->read() == ':') 146 | { 147 | if (stream->read() == ' ') 148 | { 149 | curr_buffer->len = stream->parseInt(); 150 | } 151 | } 152 | } 153 | } 154 | } 155 | } 156 | } 157 | } 158 | } 159 | } 160 | } 161 | } 162 | } 163 | } 164 | } 165 | } 166 | } 167 | log_i("[HTTP] size: %d\n", curr_buffer->len); 168 | 169 | stream->find('\n'); 170 | stream->find('\n'); 171 | stream->find('\n'); 172 | stream->find('\n'); 173 | 174 | // read all data from server 175 | uint8_t *p = curr_buffer->buff; 176 | int l = curr_buffer->len; 177 | while (http.connected() && (l > 0)) 178 | { 179 | // get available data size 180 | size_t size = stream->available(); 181 | 182 | if (size) 183 | { 184 | int s = ((size > l) ? l : size); 185 | int c = stream->readBytes(p, s); 186 | p += c; 187 | 188 | // log_i("[HTTP] read: %d\n", c); 189 | 190 | if (l > 0) 191 | { 192 | l -= c; 193 | } 194 | } 195 | } 196 | 197 | if (bufflock) 198 | { 199 | ++drop_frame; 200 | } 201 | else 202 | { 203 | ++decode_frame; 204 | bufflock = true; 205 | curr_buffer->offset = 0; 206 | 207 | xTaskCreate( 208 | drawFramebufferTask, /* Task function. */ 209 | "DrawFramebufferTask", /* String with name of task. */ 210 | 10000, /* Stack size in bytes. */ 211 | curr_buffer, /* Parameter passed as input of the task */ 212 | 1, /* Priority of the task. */ 213 | NULL); /* Task handle. */ 214 | 215 | if (curr_buffer_idx == 1) 216 | { 217 | curr_buffer_idx = 2; 218 | curr_buffer = &buffer2; 219 | } 220 | else 221 | { 222 | curr_buffer_idx = 1; 223 | curr_buffer = &buffer1; 224 | } 225 | } 226 | log_i("[JPG] drop: %d, decode: %d\n", drop_frame, decode_frame); 227 | } 228 | } 229 | } 230 | 231 | log_i("[HTTP] connection closed.\n"); 232 | 233 | http.end(); 234 | } 235 | } 236 | 237 | void drawFramebufferTask(void *parameter) 238 | { 239 | buffer_t *b = (buffer_t *)parameter; 240 | 241 | log_i("[JPG] start: %d\n", millis()); 242 | esp_jpg_decode(b->len, JPG_SCALE, buff_reader, framebuffer_writer, b /* arg */); 243 | log_i("[JPG] end: %d\n", millis()); 244 | 245 | bufflock = false; 246 | 247 | log_i("[TFT] start: %d\n", millis()); 248 | tft->startWrite(); 249 | tft->writePixels(framebuffer, DISP_WIDTH * DISP_HEIGHT); 250 | tft->endWrite(); 251 | log_i("[TFT] end: %d\n", millis()); 252 | 253 | vTaskDelete(NULL); 254 | } 255 | 256 | static size_t buff_reader(void *arg, size_t index, uint8_t *buf, size_t len) 257 | { 258 | buffer_t *b = (buffer_t *)arg; 259 | 260 | int l = len; 261 | if ((b->offset + l) > b->len) 262 | { 263 | l = b->len - b->offset; 264 | } 265 | 266 | if (buf) 267 | { 268 | memcpy(buf, b->buff + b->offset, l); 269 | } 270 | b->offset += l; 271 | return l; // Returns number of bytes read 272 | } 273 | 274 | static bool framebuffer_writer(void *arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data) 275 | { 276 | if (data) 277 | { 278 | uint8_t *pixels = data; 279 | log_d("%d, %d, %d, %d\n", x, y, w, h); 280 | for (int i = 0; i < h; ++i) 281 | { 282 | for (int j = 0; j < w; ++j) 283 | { 284 | uint8_t r = *(pixels++); 285 | uint8_t g = *(pixels++); 286 | uint8_t b = *(pixels++); 287 | framebuffer[(y + i) * DISP_WIDTH + x + j] = tft->color565(r, g, b); 288 | } 289 | } 290 | } 291 | return true; // Continue to decompression 292 | } 293 | -------------------------------------------------------------------------------- /ESP32VideoControllerTWatch/ESP32VideoControllerTWatch.ino: -------------------------------------------------------------------------------- 1 | #define SSID_NAME "Strider Walker V2" 2 | #define SSID_PASSWORD "" 3 | 4 | #define MJPEG_BUFFER_SIZE (240 * 240 * 2 / 4) 5 | 6 | #define HTTP_HOST "192.168.4.1" 7 | #define HTTP_PORT 80 8 | #define STREAM_PORT 81 9 | #define STREAM_PATH "/stream" 10 | #define MOTOR_STOP_PATH "/motor?la=0&lb=0&ra=0&rb=0" 11 | #define MOTOR_FORWARD_PATH "/motor?la=0&lb=255&ra=0&rb=255" 12 | #define MOTOR_BACKWARD_PATH "/motor?la=255&lb=0&ra=255&rb=0" 13 | #define MOTOR_LEFT_PATH "/motor?la=255&lb=0&ra=0&rb=255" 14 | #define MOTOR_RIGHT_PATH "/motor?la=0&lb=255&ra=255&rb=0" 15 | #define FRAMESIZE_240_PATH "/control?var=framesize&val=4" 16 | #define LED_ON_PATH "/gpio?pin=4&val=1" 17 | #define LED_OFF_PATH "/gpio?pin=4&val=0" 18 | 19 | enum motorCommand 20 | { 21 | stop, 22 | forward, 23 | backward, 24 | left, 25 | right 26 | }; 27 | motorCommand lastMotorCommand = stop; 28 | int lastBtnStartStatus = 1; 29 | int lastBtnAStatus = 1; 30 | int lastLedCommand = 0; 31 | 32 | #include 33 | #include 34 | WiFiClient streamClient; 35 | HTTPClient streamHttp; 36 | WiFiClient controlClient; 37 | HTTPClient controlHttp; 38 | 39 | /* Arduino_GFX */ 40 | #include 41 | #define TFT_BL 12 42 | Arduino_DataBus *bus = new Arduino_ESP32SPI(27 /* DC */, 5 /* CS */, 18 /* SCK */, 19 /* MOSI */, -1 /* MISO */); 43 | Arduino_ST7789 *gfx = new Arduino_ST7789(bus, -1 /* RST */, 3 /* rotation */, true /* IPS */, 240, 240, 0, 80); 44 | 45 | /* MJPEG Video */ 46 | #include "MjpegClass.h" 47 | static MjpegClass mjpeg; 48 | 49 | // pixel drawing callback 50 | static int drawMCU(JPEGDRAW *pDraw) 51 | { 52 | // log_v("Draw pos = %d,%d. size = %d x %d", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); 53 | gfx->draw16bitBeRGBBitmap(pDraw->x, pDraw->y, pDraw->pPixels, pDraw->iWidth, pDraw->iHeight); 54 | return 1; 55 | } /* drawMCU() */ 56 | 57 | void setup() 58 | { 59 | gfx->begin(); 60 | gfx->fillScreen(BLACK); 61 | 62 | WiFi.begin(SSID_NAME, SSID_PASSWORD); 63 | 64 | #ifdef TFT_BL 65 | pinMode(TFT_BL, OUTPUT); 66 | digitalWrite(TFT_BL, HIGH); 67 | #endif 68 | 69 | pinMode(15, INPUT_PULLUP); // Button Select 70 | pinMode(36, INPUT_PULLUP); // Button Start 71 | pinMode(13, INPUT_PULLUP); // Button A 72 | pinMode(25, INPUT_PULLUP); // Button B 73 | pinMode(14, INPUT_PULLUP); // Button X 74 | pinMode(26, INPUT_PULLUP); // Button Y 75 | pinMode(33, INPUT); // X-axis 76 | pinMode(34, INPUT); // Y-axis 77 | } 78 | 79 | void loop() 80 | { 81 | if (WiFi.status() != WL_CONNECTED) 82 | { 83 | // wait for WiFi connection 84 | delay(500); 85 | } 86 | else 87 | { 88 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, FRAMESIZE_240_PATH); 89 | controlHttp.GET(); 90 | controlHttp.end(); 91 | 92 | log_v("[HTTP] begin..."); 93 | streamHttp.begin(streamClient, HTTP_HOST, STREAM_PORT, STREAM_PATH); 94 | 95 | log_v("[HTTP] GET..."); 96 | int httpCode = streamHttp.GET(); 97 | 98 | log_v("[HTTP] GET... code: %d", httpCode); 99 | // HTTP header has been send and Server response header has been handled 100 | if (httpCode <= 0) 101 | { 102 | log_e("[HTTP] GET... failed, error: %s", streamHttp.errorToString(httpCode).c_str()); 103 | } 104 | else 105 | { 106 | if (httpCode != HTTP_CODE_OK) 107 | { 108 | log_e("[HTTP] Not OK!"); 109 | } 110 | else 111 | { 112 | // get tcp stream 113 | WiFiClient *stream = streamHttp.getStreamPtr(); 114 | 115 | uint8_t *mjpeg_buf = (uint8_t *)malloc(MJPEG_BUFFER_SIZE); 116 | 117 | // init Video 118 | mjpeg.setup(stream, mjpeg_buf, drawMCU, true, true); 119 | 120 | bool recycleStream = false; 121 | while ((streamHttp.connected()) && (!recycleStream)) 122 | { 123 | unsigned long s = millis(); 124 | // Read video 125 | mjpeg.readMjpegBuf(); 126 | unsigned long r = millis() - s; 127 | // Play video 128 | mjpeg.drawJpg(); 129 | unsigned long d = millis() - s - r; 130 | // log_v("[Mjpeg] read used: %lu, draw used: %lu", r, d); 131 | 132 | int btnSelect = digitalRead(15); 133 | int btnStart = digitalRead(36); 134 | int btnA = digitalRead(13); 135 | int btnB = digitalRead(25); 136 | int btnX = digitalRead(14); 137 | int btnY = digitalRead(26); 138 | int xVal = analogRead(33); 139 | int yVal = analogRead(34); 140 | // log_v("btnSelect: %d, btnStart: %d, btnA: %d, btnB: %d, btnX: %d, btnY: %d, xVal: %d, yVal: %d", btnSelect, btnStart, btnA, btnB, btnX, btnY, xVal, yVal); 141 | 142 | if (lastBtnStartStatus != btnStart) 143 | { 144 | if (btnStart == 0) // pressed 145 | { 146 | log_v("Recycle video stream"); 147 | recycleStream = true; 148 | } 149 | lastBtnStartStatus = btnStart; 150 | } 151 | 152 | if (lastBtnAStatus != btnA) 153 | { 154 | if (btnA == 0) // pressed 155 | { 156 | if (lastLedCommand == 0) 157 | { 158 | log_v("LED on"); 159 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, LED_ON_PATH); 160 | controlHttp.GET(); 161 | controlHttp.end(); 162 | lastLedCommand = 1; 163 | } 164 | else 165 | { 166 | log_v("LED off"); 167 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, LED_OFF_PATH); 168 | controlHttp.GET(); 169 | controlHttp.end(); 170 | lastLedCommand = 0; 171 | } 172 | } 173 | lastBtnAStatus = btnA; 174 | } 175 | motorCommand currentMotorCommand = stop; 176 | if (yVal < 1024) 177 | { 178 | currentMotorCommand = forward; 179 | } 180 | else if (yVal > (2048 + 1024)) 181 | { 182 | currentMotorCommand = backward; 183 | } 184 | else if (xVal < 1024) 185 | { 186 | currentMotorCommand = left; 187 | } 188 | else if (xVal > (2048 + 1024)) 189 | { 190 | currentMotorCommand = right; 191 | } 192 | 193 | if (currentMotorCommand != lastMotorCommand) 194 | { 195 | switch (currentMotorCommand) 196 | { 197 | case forward: 198 | log_i("forward"); 199 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_FORWARD_PATH); 200 | break; 201 | case backward: 202 | log_i("backward"); 203 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_BACKWARD_PATH); 204 | break; 205 | case left: 206 | log_i("left"); 207 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_LEFT_PATH); 208 | break; 209 | case right: 210 | log_i("right"); 211 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_RIGHT_PATH); 212 | break; 213 | default: 214 | log_i("stop"); 215 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, MOTOR_STOP_PATH); 216 | } 217 | controlHttp.GET(); 218 | controlHttp.end(); 219 | lastMotorCommand = currentMotorCommand; 220 | } 221 | } 222 | stream->flush(); 223 | } 224 | } 225 | 226 | log_v("[HTTP] connection closed."); 227 | streamHttp.end(); 228 | } 229 | } -------------------------------------------------------------------------------- /ESP32VideoControllerTWatch/MjpegClass.h: -------------------------------------------------------------------------------- 1 | #ifndef _MJPEGCLASS_H_ 2 | #define _MJPEGCLASS_H_ 3 | 4 | #define READ_BUFFER_SIZE 1024 5 | #define MAXOUTPUTSIZE 8 6 | #define NUMBER_OF_DRAW_BUFFER 4 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | typedef struct 18 | { 19 | JPEG_DRAW_CALLBACK *drawFunc; 20 | } paramDrawTask; 21 | 22 | static xQueueHandle xqh = 0; 23 | static JPEGDRAW jpegdraws[NUMBER_OF_DRAW_BUFFER]; 24 | static int queue_cnt = 0; 25 | static int draw_cnt = 0; 26 | static bool taskCreated = false; 27 | 28 | static int queueDrawMCU(JPEGDRAW *pDraw) 29 | { 30 | ++queue_cnt; 31 | while ((queue_cnt - draw_cnt) > NUMBER_OF_DRAW_BUFFER) 32 | { 33 | delay(1); 34 | } 35 | 36 | int len = pDraw->iWidth * pDraw->iHeight * 2; 37 | JPEGDRAW *j = &jpegdraws[queue_cnt % NUMBER_OF_DRAW_BUFFER]; 38 | j->x = pDraw->x; 39 | j->y = pDraw->y; 40 | j->iWidth = pDraw->iWidth; 41 | j->iHeight = pDraw->iHeight; 42 | memcpy(j->pPixels, pDraw->pPixels, len); 43 | 44 | xQueueSend(xqh, &j, 0); 45 | return 1; 46 | } 47 | 48 | static void drawTask(void *arg) 49 | { 50 | paramDrawTask *p = (paramDrawTask *)arg; 51 | for (int i = 0; i < NUMBER_OF_DRAW_BUFFER; i++) 52 | { 53 | jpegdraws[i].pPixels = (uint16_t *)heap_caps_malloc(MAXOUTPUTSIZE * 16 * 16 * 2, MALLOC_CAP_DMA); 54 | Serial.printf("#%d draw buffer allocated\n", i); 55 | } 56 | JPEGDRAW *pDraw; 57 | Serial.println("drawTask start"); 58 | while (xQueueReceive(xqh, &pDraw, portMAX_DELAY)) 59 | { 60 | // Serial.printf("task work: x: %d, y: %d, iWidth: %d, iHeight: %d\r\n", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); 61 | p->drawFunc(pDraw); 62 | // Serial.println("task work done"); 63 | ++draw_cnt; 64 | } 65 | vQueueDelete(xqh); 66 | Serial.println("drawTask end"); 67 | vTaskDelete(NULL); 68 | } 69 | 70 | class MjpegClass 71 | { 72 | public: 73 | bool setup(Stream *input, uint8_t *mjpeg_buf, JPEG_DRAW_CALLBACK *pfnDraw, bool enableMultiTask, bool useBigEndian) 74 | { 75 | _input = input; 76 | _inputindex = 0; 77 | _mjpeg_buf = mjpeg_buf; 78 | _pfnDraw = pfnDraw; 79 | _enableMultiTask = enableMultiTask; 80 | _useBigEndian = useBigEndian; 81 | 82 | if (!_read_buf) 83 | { 84 | _read_buf = (uint8_t *)malloc(READ_BUFFER_SIZE); 85 | } 86 | 87 | if (_enableMultiTask) 88 | { 89 | if (!taskCreated) 90 | { 91 | TaskHandle_t task; 92 | _p.drawFunc = pfnDraw; 93 | xqh = xQueueCreate(NUMBER_OF_DRAW_BUFFER, sizeof(JPEGDRAW)); 94 | xTaskCreatePinnedToCore(drawTask, "drawTask", 1600, &_p, 1, &task, 0); 95 | taskCreated = true; 96 | } 97 | } 98 | 99 | return true; 100 | } 101 | 102 | bool readMjpegBuf() 103 | { 104 | if (_inputindex == 0) 105 | { 106 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 107 | _inputindex += _buf_read; 108 | } 109 | _mjpeg_buf_offset = 0; 110 | int i = 0; 111 | bool found_FFD8 = false; 112 | while ((_buf_read > 0) && (!found_FFD8)) 113 | { 114 | i = 0; 115 | while ((i < _buf_read) && (!found_FFD8)) 116 | { 117 | if ((_read_buf[i] == 0xFF) && (_read_buf[i + 1] == 0xD8)) // JPEG header 118 | { 119 | // Serial.printf("Found FFD8 at: %d.\n", i); 120 | found_FFD8 = true; 121 | } 122 | ++i; 123 | } 124 | if (found_FFD8) 125 | { 126 | --i; 127 | } 128 | else 129 | { 130 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 131 | } 132 | } 133 | uint8_t *_p = _read_buf + i; 134 | _buf_read -= i; 135 | bool found_FFD9 = false; 136 | if (_buf_read > 0) 137 | { 138 | i = 3; 139 | while ((_buf_read > 0) && (!found_FFD9)) 140 | { 141 | if ((_mjpeg_buf_offset > 0) && (_mjpeg_buf[_mjpeg_buf_offset - 1] == 0xFF) && (_p[0] == 0xD9)) // JPEG trailer 142 | { 143 | // Serial.printf("Found FFD9 at: %d.\n", i); 144 | found_FFD9 = true; 145 | } 146 | else 147 | { 148 | while ((i < _buf_read) && (!found_FFD9)) 149 | { 150 | if ((_p[i] == 0xFF) && (_p[i + 1] == 0xD9)) // JPEG trailer 151 | { 152 | found_FFD9 = true; 153 | ++i; 154 | } 155 | ++i; 156 | } 157 | } 158 | 159 | // Serial.printf("i: %d\n", i); 160 | memcpy(_mjpeg_buf + _mjpeg_buf_offset, _p, i); 161 | _mjpeg_buf_offset += i; 162 | size_t o = _buf_read - i; 163 | if (o > 0) 164 | { 165 | // Serial.printf("o: %d\n", o); 166 | memcpy(_read_buf, _p + i, o); 167 | _buf_read = _input->readBytes(_read_buf + o, READ_BUFFER_SIZE - o); 168 | _p = _read_buf; 169 | _inputindex += _buf_read; 170 | _buf_read += o; 171 | // Serial.printf("_buf_read: %d\n", _buf_read); 172 | } 173 | else 174 | { 175 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 176 | _p = _read_buf; 177 | _inputindex += _buf_read; 178 | } 179 | i = 0; 180 | } 181 | if (found_FFD9) 182 | { 183 | return true; 184 | } 185 | } 186 | 187 | return false; 188 | } 189 | 190 | int getWidth() 191 | { 192 | return _jpeg.getWidth(); 193 | } 194 | 195 | int getHeight() 196 | { 197 | return _jpeg.getHeight(); 198 | } 199 | 200 | bool drawJpg() 201 | { 202 | _remain = _mjpeg_buf_offset; 203 | 204 | if (_enableMultiTask) 205 | { 206 | _jpeg.openRAM(_mjpeg_buf, _remain, queueDrawMCU); 207 | } 208 | else 209 | { 210 | _jpeg.openRAM(_mjpeg_buf, _remain, _pfnDraw); 211 | } 212 | 213 | _jpeg.setMaxOutputSize(MAXOUTPUTSIZE); 214 | if (_useBigEndian) 215 | { 216 | _jpeg.setPixelType(RGB565_BIG_ENDIAN); 217 | } 218 | _jpeg.decode(0, 0, 0); 219 | _jpeg.close(); 220 | 221 | return true; 222 | } 223 | 224 | private: 225 | Stream *_input; 226 | uint8_t *_mjpeg_buf; 227 | JPEG_DRAW_CALLBACK *_pfnDraw; 228 | bool _enableMultiTask; 229 | bool _useBigEndian; 230 | 231 | uint8_t *_read_buf; 232 | int32_t _mjpeg_buf_offset = 0; 233 | 234 | JPEGDEC _jpeg; 235 | paramDrawTask _p; 236 | 237 | int32_t _inputindex = 0; 238 | int32_t _buf_read; 239 | int32_t _remain = 0; 240 | }; 241 | 242 | #endif // _MJPEGCLASS_H_ 243 | -------------------------------------------------------------------------------- /ESP32VideoViewer/ESP32VideoViewer.ino: -------------------------------------------------------------------------------- 1 | #define SSID_NAME "XIAO nanotank" 2 | #define SSID_PASSWORD "" 3 | 4 | #define MJPEG_BUFFER_SIZE (640 * 480 * 2 / 7) 5 | 6 | #define HTTP_HOST "192.168.4.1" 7 | #define HTTP_PORT 80 8 | #define STREAM_PORT 81 9 | #define STREAM_PATH "/stream" 10 | #define FRAMESIZE_VGA_PATH "/control?var=framesize&val=8" 11 | 12 | #include 13 | #include 14 | WiFiClient streamClient; 15 | HTTPClient streamHttp; 16 | WiFiClient controlClient; 17 | HTTPClient controlHttp; 18 | 19 | /* Arduino_GFX */ 20 | #include 21 | #define GFX_DEV_DEVICE ESP32_S3_RGB 22 | #define GFX_BL 38 23 | Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel( 24 | 18 /* DE */, 17 /* VSYNC */, 16 /* HSYNC */, 21 /* PCLK */, 25 | 4 /* R0 */, 3 /* R1 */, 2 /* R2 */, 1 /* R3 */, 0 /* R4 */, 26 | 10 /* G0 */, 9 /* G1 */, 8 /* G2 */, 7 /* G3 */, 6 /* G4 */, 5 /* G5 */, 27 | 15 /* B0 */, 14 /* B1 */, 13 /* B2 */, 12 /* B3 */, 11 /* B4 */, 28 | 1 /* hsync_polarity */, 20 /* hsync_front_porch */, 30 /* hsync_pulse_width */, 38 /* hsync_back_porch */, 29 | 1 /* vsync_polarity */, 4 /* vsync_front_porch */, 3 /* vsync_pulse_width */, 15 /* vsync_back_porch */, 30 | 10, 16000000); 31 | Arduino_RGB_Display *gfx = new Arduino_RGB_Display( 32 | 640 /* width */, 480 /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */); 33 | 34 | /* MJPEG Video */ 35 | #include "MjpegClass.h" 36 | static MjpegClass mjpeg; 37 | 38 | // pixel drawing callback 39 | static int drawMCU(JPEGDRAW *pDraw) 40 | { 41 | // log_v("Draw pos = %d,%d. size = %d x %d", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); 42 | gfx->draw16bitBeRGBBitmap(pDraw->x, pDraw->y, pDraw->pPixels, pDraw->iWidth, pDraw->iHeight); 43 | return 1; 44 | } /* drawMCU() */ 45 | 46 | void setup() 47 | { 48 | Serial.begin(115200); 49 | // Serial.setDebugOutput(true); 50 | // while(!Serial); 51 | Serial.println("ESP32VideoViewerRGB"); 52 | 53 | #ifdef GFX_EXTRA_PRE_INIT 54 | GFX_EXTRA_PRE_INIT(); 55 | #endif 56 | 57 | // Init Display 58 | if (!gfx->begin()) 59 | { 60 | Serial.println("gfx->begin() failed!"); 61 | } 62 | gfx->fillScreen(BLACK); 63 | 64 | #ifdef TFT_BL 65 | pinMode(TFT_BL, OUTPUT); 66 | digitalWrite(TFT_BL, HIGH); 67 | #endif 68 | 69 | WiFi.begin(SSID_NAME, SSID_PASSWORD); 70 | log_v("Connect to "SSID_NAME); 71 | } 72 | 73 | void loop() 74 | { 75 | if (WiFi.status() != WL_CONNECTED) 76 | { 77 | // wait for WiFi connection 78 | delay(500); 79 | log_v("."); 80 | } 81 | else 82 | { 83 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, FRAMESIZE_VGA_PATH); 84 | controlHttp.GET(); 85 | controlHttp.end(); 86 | 87 | log_v("[HTTP] begin..."); 88 | streamHttp.begin(streamClient, HTTP_HOST, STREAM_PORT, STREAM_PATH); 89 | 90 | log_v("[HTTP] GET..."); 91 | int httpCode = streamHttp.GET(); 92 | 93 | log_v("[HTTP] GET... code: %d", httpCode); 94 | // HTTP header has been send and Server response header has been handled 95 | if (httpCode <= 0) 96 | { 97 | log_e("[HTTP] GET... failed, error: %s", streamHttp.errorToString(httpCode).c_str()); 98 | } 99 | else 100 | { 101 | if (httpCode != HTTP_CODE_OK) 102 | { 103 | log_e("[HTTP] Not OK!"); 104 | } 105 | else 106 | { 107 | // get tcp stream 108 | WiFiClient *stream = streamHttp.getStreamPtr(); 109 | 110 | uint8_t *mjpeg_buf = (uint8_t *)malloc(MJPEG_BUFFER_SIZE); 111 | 112 | // init Video 113 | mjpeg.setup( 114 | stream, mjpeg_buf, drawMCU, true /* useBigEndian */, 115 | 0 /* x */, 0 /* y */, gfx->width() /* widthLimit */, gfx->height() /* heightLimit */); 116 | 117 | bool recycleStream = false; 118 | while ((streamHttp.connected()) && (!recycleStream)) 119 | { 120 | unsigned long s = millis(); 121 | // Read video 122 | mjpeg.readMjpegBuf(); 123 | unsigned long r = millis() - s; 124 | // Play video 125 | mjpeg.drawJpg(); 126 | unsigned long d = millis() - s - r; 127 | log_v("[Mjpeg] read used: %lu, draw used: %lu", r, d); 128 | } 129 | stream->flush(); 130 | } 131 | } 132 | 133 | log_v("[HTTP] connection closed."); 134 | streamHttp.end(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /ESP32VideoViewer/MjpegClass.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * JPEGDEC Wrapper Class 3 | * 4 | * Dependent libraries: 5 | * JPEGDEC: https://github.com/bitbank2/JPEGDEC.git 6 | ******************************************************************************/ 7 | #ifndef _MJPEGCLASS_H_ 8 | #define _MJPEGCLASS_H_ 9 | 10 | #define READ_BUFFER_SIZE 1024 11 | #define MAXOUTPUTSIZE (MAX_BUFFERED_PIXELS / 16 / 16) 12 | // #define MAXOUTPUTSIZE (288 / 3 / 16) 13 | 14 | #include 15 | 16 | #include 17 | 18 | class MjpegClass 19 | { 20 | public: 21 | bool setup( 22 | Stream *input, uint8_t *mjpeg_buf, JPEG_DRAW_CALLBACK *pfnDraw, bool useBigEndian, 23 | int x, int y, int widthLimit, int heightLimit) 24 | { 25 | _input = input; 26 | _mjpeg_buf = mjpeg_buf; 27 | _pfnDraw = pfnDraw; 28 | _useBigEndian = useBigEndian; 29 | _x = x; 30 | _y = y; 31 | _widthLimit = widthLimit; 32 | _heightLimit = heightLimit; 33 | _inputindex = 0; 34 | 35 | _read_buf = (uint8_t *)malloc(READ_BUFFER_SIZE); 36 | 37 | return true; 38 | } 39 | 40 | bool readMjpegBuf() 41 | { 42 | if (_inputindex == 0) 43 | { 44 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 45 | _inputindex += _buf_read; 46 | } 47 | _mjpeg_buf_offset = 0; 48 | int i = 0; 49 | bool found_FFD8 = false; 50 | while ((_buf_read > 0) && (!found_FFD8)) 51 | { 52 | i = 0; 53 | while ((i < _buf_read) && (!found_FFD8)) 54 | { 55 | if ((_read_buf[i] == 0xFF) && (_read_buf[i + 1] == 0xD8)) // JPEG header 56 | { 57 | // Serial.printf("Found FFD8 at: %d.\n", i); 58 | found_FFD8 = true; 59 | } 60 | ++i; 61 | } 62 | if (found_FFD8) 63 | { 64 | --i; 65 | } 66 | else 67 | { 68 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 69 | } 70 | } 71 | uint8_t *_p = _read_buf + i; 72 | _buf_read -= i; 73 | bool found_FFD9 = false; 74 | if (_buf_read > 0) 75 | { 76 | i = 3; 77 | while ((_buf_read > 0) && (!found_FFD9)) 78 | { 79 | if ((_mjpeg_buf_offset > 0) && (_mjpeg_buf[_mjpeg_buf_offset - 1] == 0xFF) && (_p[0] == 0xD9)) // JPEG trailer 80 | { 81 | // Serial.printf("Found FFD9 at: %d.\n", i); 82 | found_FFD9 = true; 83 | } 84 | else 85 | { 86 | while ((i < _buf_read) && (!found_FFD9)) 87 | { 88 | if ((_p[i] == 0xFF) && (_p[i + 1] == 0xD9)) // JPEG trailer 89 | { 90 | found_FFD9 = true; 91 | ++i; 92 | } 93 | ++i; 94 | } 95 | } 96 | 97 | // Serial.printf("i: %d\n", i); 98 | memcpy(_mjpeg_buf + _mjpeg_buf_offset, _p, i); 99 | _mjpeg_buf_offset += i; 100 | size_t o = _buf_read - i; 101 | if (o > 0) 102 | { 103 | // Serial.printf("o: %d\n", o); 104 | memcpy(_read_buf, _p + i, o); 105 | _buf_read = _input->readBytes(_read_buf + o, READ_BUFFER_SIZE - o); 106 | _p = _read_buf; 107 | _inputindex += _buf_read; 108 | _buf_read += o; 109 | // Serial.printf("_buf_read: %d\n", _buf_read); 110 | } 111 | else 112 | { 113 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 114 | _p = _read_buf; 115 | _inputindex += _buf_read; 116 | } 117 | i = 0; 118 | } 119 | if (found_FFD9) 120 | { 121 | return true; 122 | } 123 | } 124 | 125 | return false; 126 | } 127 | 128 | bool drawJpg() 129 | { 130 | _remain = _mjpeg_buf_offset; 131 | _jpeg.openRAM(_mjpeg_buf, _remain, _pfnDraw); 132 | if (_scale == -1) 133 | { 134 | // scale to fit height 135 | int w = _jpeg.getWidth(); 136 | int h = _jpeg.getHeight(); 137 | float ratio = (float)h / _heightLimit; 138 | if (ratio <= 1) 139 | { 140 | _scale = 0; 141 | } 142 | else if (ratio <= 2) 143 | { 144 | _scale = JPEG_SCALE_HALF; 145 | w /= 2; 146 | h /= 2; 147 | } 148 | else if (ratio <= 4) 149 | { 150 | _scale = JPEG_SCALE_QUARTER; 151 | w /= 4; 152 | h /= 4; 153 | } 154 | else 155 | { 156 | _scale = JPEG_SCALE_EIGHTH; 157 | w /= 8; 158 | h /= 8; 159 | } 160 | _x = (w > _widthLimit) ? 0 : ((_widthLimit - w) / 2); 161 | _y = (_heightLimit - h) / 2; 162 | } 163 | _jpeg.setMaxOutputSize(MAXOUTPUTSIZE); 164 | if (_useBigEndian) 165 | { 166 | _jpeg.setPixelType(RGB565_BIG_ENDIAN); 167 | } 168 | // log_i("_jpeg.decode(%d, %d, _scale);", _x, _y); 169 | _jpeg.decode(_x, _y, _scale); 170 | _jpeg.close(); 171 | 172 | return true; 173 | } 174 | 175 | private: 176 | Stream *_input; 177 | uint8_t *_mjpeg_buf; 178 | JPEG_DRAW_CALLBACK *_pfnDraw; 179 | bool _useBigEndian; 180 | int _x; 181 | int _y; 182 | int _widthLimit; 183 | int _heightLimit; 184 | 185 | uint8_t *_read_buf; 186 | int32_t _mjpeg_buf_offset = 0; 187 | 188 | JPEGDEC _jpeg; 189 | int _scale = -1; 190 | 191 | int32_t _inputindex = 0; 192 | int32_t _buf_read; 193 | int32_t _remain = 0; 194 | }; 195 | 196 | #endif // _MJPEGCLASS_H_ 197 | -------------------------------------------------------------------------------- /ESP32VideoViewerSIMD/ESP32VideoViewerSIMD.ino: -------------------------------------------------------------------------------- 1 | #define SSID_NAME "XIAO nanotank" 2 | #define SSID_PASSWORD "" 3 | 4 | #define MJPEG_BUFFER_SIZE (640 * 480 * 2 / 7) 5 | 6 | #define HTTP_HOST "192.168.4.1" 7 | #define HTTP_PORT 80 8 | #define STREAM_PORT 81 9 | #define STREAM_PATH "/stream" 10 | #define FRAMESIZE_VGA_PATH "/control?var=framesize&val=8" 11 | 12 | #include 13 | #include 14 | WiFiClient streamClient; 15 | HTTPClient streamHttp; 16 | WiFiClient controlClient; 17 | HTTPClient controlHttp; 18 | 19 | /* Arduino_GFX */ 20 | #include 21 | #define GFX_DEV_DEVICE ESP32_S3_RGB 22 | #define GFX_BL 38 23 | Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel( 24 | 18 /* DE */, 17 /* VSYNC */, 16 /* HSYNC */, 21 /* PCLK */, 25 | 4 /* R0 */, 3 /* R1 */, 2 /* R2 */, 1 /* R3 */, 0 /* R4 */, 26 | 10 /* G0 */, 9 /* G1 */, 8 /* G2 */, 7 /* G3 */, 6 /* G4 */, 5 /* G5 */, 27 | 15 /* B0 */, 14 /* B1 */, 13 /* B2 */, 12 /* B3 */, 11 /* B4 */, 28 | 1 /* hsync_polarity */, 20 /* hsync_front_porch */, 30 /* hsync_pulse_width */, 38 /* hsync_back_porch */, 29 | 1 /* vsync_polarity */, 4 /* vsync_front_porch */, 3 /* vsync_pulse_width */, 15 /* vsync_back_porch */, 30 | 10, 16000000); 31 | Arduino_RGB_Display *gfx = new Arduino_RGB_Display( 32 | 640 /* width */, 480 /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */); 33 | 34 | /* MJPEG Video */ 35 | #include "MjpegClass.h" 36 | static MjpegClass mjpeg; 37 | 38 | void setup() 39 | { 40 | Serial.begin(115200); 41 | // Serial.setDebugOutput(true); 42 | // while(!Serial); 43 | Serial.println("ESP32VideoViewerRGB"); 44 | 45 | #ifdef GFX_EXTRA_PRE_INIT 46 | GFX_EXTRA_PRE_INIT(); 47 | #endif 48 | 49 | // Init Display 50 | if (!gfx->begin()) 51 | { 52 | Serial.println("gfx->begin() failed!"); 53 | } 54 | gfx->fillScreen(BLACK); 55 | 56 | #ifdef TFT_BL 57 | pinMode(TFT_BL, OUTPUT); 58 | digitalWrite(TFT_BL, HIGH); 59 | #endif 60 | 61 | WiFi.begin(SSID_NAME, SSID_PASSWORD); 62 | log_v("Connect to "SSID_NAME); 63 | } 64 | 65 | void loop() 66 | { 67 | if (WiFi.status() != WL_CONNECTED) 68 | { 69 | // wait for WiFi connection 70 | delay(500); 71 | log_v("."); 72 | } 73 | else 74 | { 75 | controlHttp.begin(controlClient, HTTP_HOST, HTTP_PORT, FRAMESIZE_VGA_PATH); 76 | controlHttp.GET(); 77 | controlHttp.end(); 78 | 79 | log_v("[HTTP] begin..."); 80 | streamHttp.begin(streamClient, HTTP_HOST, STREAM_PORT, STREAM_PATH); 81 | 82 | log_v("[HTTP] GET..."); 83 | int httpCode = streamHttp.GET(); 84 | 85 | log_v("[HTTP] GET... code: %d", httpCode); 86 | // HTTP header has been send and Server response header has been handled 87 | if (httpCode <= 0) 88 | { 89 | log_e("[HTTP] GET... failed, error: %s", streamHttp.errorToString(httpCode).c_str()); 90 | } 91 | else 92 | { 93 | if (httpCode != HTTP_CODE_OK) 94 | { 95 | log_e("[HTTP] Not OK!"); 96 | } 97 | else 98 | { 99 | // get tcp stream 100 | WiFiClient *stream = streamHttp.getStreamPtr(); 101 | 102 | uint8_t *mjpeg_buf = (uint8_t *)malloc(MJPEG_BUFFER_SIZE); 103 | 104 | // init Video 105 | mjpeg.setup( 106 | stream, mjpeg_buf, gfx->getFramebuffer(), true /* useBigEndian */, 107 | gfx->width() /* widthLimit */, gfx->height() /* heightLimit */); 108 | 109 | bool recycleStream = false; 110 | while ((streamHttp.connected()) && (!recycleStream)) 111 | { 112 | unsigned long s = millis(); 113 | // Read video 114 | mjpeg.readMjpegBuf(); 115 | unsigned long r = millis() - s; 116 | // Play video 117 | mjpeg.drawJpg(); 118 | unsigned long d = millis() - s - r; 119 | log_v("[Mjpeg] read used: %lu, draw used: %lu", r, d); 120 | } 121 | stream->flush(); 122 | } 123 | } 124 | 125 | log_v("[HTTP] connection closed."); 126 | streamHttp.end(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /ESP32VideoViewerSIMD/MjpegClass.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * ESP32_JPEG Wrapper Class 3 | * 4 | * Dependent libraries: 5 | * ESP32_JPEG: https://github.com/esp-arduino-libs/ESP32_JPEG.git 6 | ******************************************************************************/ 7 | #ifndef _MJPEGCLASS_H_ 8 | #define _MJPEGCLASS_H_ 9 | 10 | #define READ_BUFFER_SIZE 1024 11 | #define MAXOUTPUTSIZE (MAX_BUFFERED_PIXELS / 16 / 16) 12 | 13 | #include 14 | 15 | #include 16 | 17 | class MjpegClass 18 | { 19 | public: 20 | bool setup( 21 | Stream *input, uint8_t *mjpeg_buf, uint16_t *output_buf, bool useBigEndian, 22 | int widthLimit, int heightLimit) 23 | { 24 | _input = input; 25 | _mjpeg_buf = mjpeg_buf; 26 | _output_buf = (uint8_t *)output_buf; 27 | _useBigEndian = useBigEndian; 28 | _widthLimit = widthLimit; 29 | _heightLimit = heightLimit; 30 | _inputindex = 0; 31 | 32 | if (!_read_buf) 33 | { 34 | _read_buf = (uint8_t *)malloc(READ_BUFFER_SIZE); 35 | } 36 | 37 | return true; 38 | } 39 | 40 | bool readMjpegBuf() 41 | { 42 | if (_inputindex == 0) 43 | { 44 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 45 | _inputindex += _buf_read; 46 | } 47 | _mjpeg_buf_offset = 0; 48 | int i = 0; 49 | bool found_FFD8 = false; 50 | while ((_buf_read > 0) && (!found_FFD8)) 51 | { 52 | i = 0; 53 | while ((i < _buf_read) && (!found_FFD8)) 54 | { 55 | if ((_read_buf[i] == 0xFF) && (_read_buf[i + 1] == 0xD8)) // JPEG header 56 | { 57 | // Serial.printf("Found FFD8 at: %d.\n", i); 58 | found_FFD8 = true; 59 | } 60 | ++i; 61 | } 62 | if (found_FFD8) 63 | { 64 | --i; 65 | } 66 | else 67 | { 68 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 69 | } 70 | } 71 | uint8_t *_p = _read_buf + i; 72 | _buf_read -= i; 73 | bool found_FFD9 = false; 74 | if (_buf_read > 0) 75 | { 76 | i = 3; 77 | while ((_buf_read > 0) && (!found_FFD9)) 78 | { 79 | if ((_mjpeg_buf_offset > 0) && (_mjpeg_buf[_mjpeg_buf_offset - 1] == 0xFF) && (_p[0] == 0xD9)) // JPEG trailer 80 | { 81 | // Serial.printf("Found FFD9 at: %d.\n", i); 82 | found_FFD9 = true; 83 | } 84 | else 85 | { 86 | while ((i < _buf_read) && (!found_FFD9)) 87 | { 88 | if ((_p[i] == 0xFF) && (_p[i + 1] == 0xD9)) // JPEG trailer 89 | { 90 | found_FFD9 = true; 91 | ++i; 92 | } 93 | ++i; 94 | } 95 | } 96 | 97 | // Serial.printf("i: %d\n", i); 98 | memcpy(_mjpeg_buf + _mjpeg_buf_offset, _p, i); 99 | _mjpeg_buf_offset += i; 100 | size_t o = _buf_read - i; 101 | if (o > 0) 102 | { 103 | // Serial.printf("o: %d\n", o); 104 | memcpy(_read_buf, _p + i, o); 105 | _buf_read = _input->readBytes(_read_buf + o, READ_BUFFER_SIZE - o); 106 | _p = _read_buf; 107 | _inputindex += _buf_read; 108 | _buf_read += o; 109 | // Serial.printf("_buf_read: %d\n", _buf_read); 110 | } 111 | else 112 | { 113 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 114 | _p = _read_buf; 115 | _inputindex += _buf_read; 116 | } 117 | i = 0; 118 | } 119 | if (found_FFD9) 120 | { 121 | return true; 122 | } 123 | } 124 | 125 | return false; 126 | } 127 | 128 | bool drawJpg() 129 | { 130 | _remain = _mjpeg_buf_offset; 131 | 132 | // Generate default configuration 133 | jpeg_dec_config_t config = { 134 | .output_type = JPEG_RAW_TYPE_RGB565_LE, 135 | .rotate = JPEG_ROTATE_0D, 136 | }; 137 | // Create jpeg_dec 138 | _jpeg_dec = jpeg_dec_open(&config); 139 | 140 | // Create io_callback handle 141 | _jpeg_io = (jpeg_dec_io_t *)calloc(1, sizeof(jpeg_dec_io_t)); 142 | 143 | // Create out_info handle 144 | _out_info = (jpeg_dec_header_info_t *)calloc(1, sizeof(jpeg_dec_header_info_t)); 145 | 146 | // Set input buffer and buffer len to io_callback 147 | _jpeg_io->inbuf = _mjpeg_buf; 148 | _jpeg_io->inbuf_len = _remain; 149 | 150 | jpeg_dec_parse_header(_jpeg_dec, _jpeg_io, _out_info); 151 | 152 | if (_x == -1) 153 | { 154 | int w = _out_info->width; 155 | int h = _out_info->height; 156 | _x = (w > _widthLimit) ? 0 : ((_widthLimit - w) / 2); 157 | _y = (_heightLimit - h) / 2; 158 | } 159 | 160 | _jpeg_io->outbuf = _output_buf; 161 | 162 | jpeg_dec_process(_jpeg_dec, _jpeg_io); 163 | jpeg_dec_close(_jpeg_dec); 164 | 165 | free(_jpeg_io); 166 | free(_out_info); 167 | 168 | return true; 169 | } 170 | 171 | int getX() 172 | { 173 | return _x; 174 | } 175 | 176 | int getY() 177 | { 178 | return _y; 179 | } 180 | 181 | private: 182 | Stream *_input; 183 | uint8_t *_mjpeg_buf; 184 | uint8_t *_output_buf; 185 | bool _useBigEndian; 186 | int _x = -1; 187 | int _y; 188 | int _widthLimit; 189 | int _heightLimit; 190 | 191 | uint8_t *_read_buf; 192 | int32_t _mjpeg_buf_offset = 0; 193 | 194 | jpeg_dec_handle_t *_jpeg_dec; 195 | jpeg_dec_io_t *_jpeg_io; 196 | jpeg_dec_header_info_t *_out_info; 197 | 198 | int32_t _inputindex = 0; 199 | int32_t _buf_read; 200 | int32_t _remain = 0; 201 | }; 202 | 203 | #endif // _MJPEGCLASS_H_ 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32VideoRemote 2 | 3 | This project contains 2 parts: 4 | - ESP32 Camera Robot - A ESP32 robot with camera module 5 | - ESP32 Video Controller - Controller with display that can stream MJPEG video from ESP32 Camera Robot and with buttons that can control robot action 6 | 7 | ## ESP32 Camera Robot 8 | 9 | The Camera Robot code in this repository is a little bit old, please use the lastest [FSBrowserPlus](https://github.com/moononournation/FSBrowserPlus) instead. 10 | 11 | ## ESP32 Video Controller 12 | 13 | Currently tailed-made for Odroid-Go but you can easily alter the GPIO to any customize device. -------------------------------------------------------------------------------- /SerialServoTester/SerialServoTester.ino: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | This is an example for our Adafruit 16-channel PWM & Servo driver 3 | Servo test - this will drive 8 servos, one after the other on the 4 | first 8 pins of the PCA9685 5 | 6 | Pick one up today in the adafruit shop! 7 | ------> http://www.adafruit.com/products/815 8 | 9 | These drivers use I2C to communicate, 2 pins are required to 10 | interface. 11 | 12 | Adafruit invests time and resources providing this open source code, 13 | please support Adafruit and open-source hardware by purchasing 14 | products from Adafruit! 15 | 16 | Written by Limor Fried/Ladyada for Adafruit Industries. 17 | BSD license, all text above must be included in any redistribution 18 | ****************************************************/ 19 | 20 | #include 21 | #include 22 | 23 | // called this way, it uses the default address 0x40 24 | Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(); 25 | // you can also call it with a different address you want 26 | //Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x41); 27 | // you can also call it with a different address and I2C interface 28 | //Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40, Wire); 29 | 30 | // Depending on your servo make, the pulse width min and max may vary, you 31 | // want these to be as small/large as possible without hitting the hard stop 32 | // for max range. You'll have to tweak them as necessary to match the servos you 33 | // have! 34 | #define SERVO_MIN 1 35 | #define SERVO_MAX 16 36 | #define USMIN 670 // This is the rounded 'minimum' microsecond length based on the minimum pulse of 150 37 | #define USMAX 2400 // This is the rounded 'maximum' microsecond length based on the maximum pulse of 600 38 | #define SERVO_FREQ 60 // Analog servos run at ~60 Hz updates 39 | 40 | // our servo # counter 41 | uint8_t servo_id = 1; 42 | 43 | void setup() { 44 | Serial.begin(115200); 45 | Serial.printf("Serial Servo Tester for Adafruit_PWMServoDriver\n"); 46 | Serial.printf("===============================================\n"); 47 | Serial.printf("Input %d-%d for select servo;\n", SERVO_MIN, SERVO_MAX); 48 | Serial.printf("Input %d-%d for servo pulse width:\n", USMIN, USMAX); 49 | 50 | Wire.begin(12, 13); 51 | pwm.begin(); 52 | // In theory the internal oscillator is 25MHz but it really isn't 53 | // that precise. You can 'calibrate' by tweaking this number till 54 | // you get the frequency you're expecting! 55 | pwm.setOscillatorFrequency(27000000); // The int.osc. is closer to 27MHz 56 | pwm.setPWMFreq(SERVO_FREQ); // Analog servos run at ~60 Hz updates 57 | 58 | delay(10); 59 | } 60 | 61 | void loop() { 62 | if (Serial.available() > 0) { 63 | int i = Serial.parseInt(); 64 | if ((i >= SERVO_MIN) && (i <= SERVO_MAX)) { 65 | Serial.printf("Set servo to %d.\n", i); 66 | servo_id = i; 67 | } else if ((i >= USMIN) && (i <= USMAX)) { 68 | Serial.printf("Set servo %d pulsewidth to %d.\n", servo_id, i); 69 | // Drive each servo one at a time using writeMicroseconds(), it's not precise due to calculation rounding! 70 | // The writeMicroseconds() function is used to mimic the Arduino Servo library writeMicroseconds() behavior. 71 | pwm.writeMicroseconds(servo_id - 1, i); 72 | } 73 | } 74 | } 75 | --------------------------------------------------------------------------------