├── .gitignore ├── Makefile ├── README.md └── main ├── component.mk └── remote_shutter.c /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | components/ 3 | set_port.sh 4 | sdkconfig 5 | sdkconfig.old 6 | .vscode 7 | .clang-format -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # BTstack example 'hid_keyboard_demo' for ESP32 port 3 | # 4 | # Generated by C:/msys32/home/minatsu/esp/btstack/port/esp32 5 | # On 09/02/17 23:52:25 6 | 7 | PROJECT_NAME := esp32_remote_shutter 8 | EXTRA_COMPONENT_DIRS := components 9 | 10 | include $(IDF_PATH)/make/project.mk 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Bluetooth Remote Shutter for a Smartphone Camera 2 | 3 | ## Pre-requirements 4 | Before compile this project, please install [ESP-IDF](https://github.com/espressif/esp-idf) and set `$(IDF_PATH)`: 5 | 6 | `% export IDF_PATH=/your/esp-idf/path` 7 | 8 | ## How to compile 9 | 1. Clone the [BTstack](https://github.com/bluekitchen/btstack). 10 | ``` 11 | % cd ~/esp 12 | % git clone https://github.com/bluekitchen/btstack.git 13 | ``` 14 | 2. Clone this project into "btstack/port/esp32", and copy "components" and "sdkconfig" files from the template directory. 15 | ``` 16 | % cd btstack/port/esp32 17 | % git clone https://github.com/MinatsuT/esp32_remote_shutter.git 18 | % cp -r template/components esp32_remote_shutter 19 | % cp template/sdkconfig esp32_remote_shutter 20 | ``` 21 | 3. Edit serial configurations according to your environment. 22 | ``` 23 | % cd esp32_remote_shutter 24 | % make menuconfig 25 | ``` 26 | 4. Compile, flash and start monitoring. 27 | ``` 28 | % make flash monitor 29 | ``` 30 | 31 | ## How to use. 32 | - Scan BT devices from your smartphone and establish a connection to the "ESP32 Remote Shutter" device. 33 | - Start camera app on your smartphone. 34 | - Push "enter" from the above monitor, then shutter code (Volume Up) will be sent. 35 | -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Main component makefile. 3 | # 4 | # This Makefile can be left empty. By default, it will take the sources in the 5 | # src/ directory, compile them and link them into lib(subdirectory_name).a 6 | # in the build directory. This behaviour is entirely configurable, 7 | # please read the ESP-IDF documents if you need to do this. 8 | # 9 | CFLAGS += -Wno-format 10 | 11 | -------------------------------------------------------------------------------- /main/remote_shutter.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 BlueKitchen GmbH 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. Neither the name of the copyright holders nor the names of 14 | * contributors may be used to endorse or promote products derived 15 | * from this software without specific prior written permission. 16 | * 4. Any redistribution, use, or modification is done solely for 17 | * personal benefit and not for any commercial purpose or for 18 | * monetary gain. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS 21 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS 24 | * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 27 | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 28 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 30 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 | * SUCH DAMAGE. 32 | * 33 | * Please inquire about commercial licensing options at 34 | * contact@bluekitchen-gmbh.com 35 | * 36 | */ 37 | 38 | #define __BTSTACK_FILE__ "remote_shutter.c" 39 | 40 | // ***************************************************************************** 41 | /* Remote Shutter for smartphone camreras based on the BTstack HID keyboard demo 42 | * code. 43 | * 44 | * Status: Basic implementation. Works with Android and iOS. 45 | * 46 | * @text This is an HID keyboard device specialized for remote shutter. 47 | */ 48 | // ***************************************************************************** 49 | 50 | 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | #include "btstack.h" 58 | 59 | #ifdef HAVE_BTSTACK_STDIN 60 | #include "btstack_stdin.h" 61 | #endif 62 | 63 | #include "freertos/FreeRTOS.h" 64 | #include "freertos/queue.h" 65 | 66 | // to enable demo text on POSIX systems 67 | // #undef HAVE_BTSTACK_STDIN 68 | 69 | static uint8_t hid_service_buffer[250]; 70 | static const char hid_device_name[] = "BTstack HID Keyboard"; 71 | static btstack_packet_callback_registration_t hci_event_callback_registration; 72 | static uint16_t hid_cid; 73 | 74 | // from USB HID Specification 1.1, Appendix B.1 75 | const uint8_t hid_descriptor_keyboard_boot_mode[] = { 76 | 0x05, 0x01, // Usage Page (Generic Desktop) 77 | 0x09, 0x06, // Usage (Keyboard) 78 | 0xa1, 0x01, // Collection (Application) 79 | 0x85, 0x01, // Report Id (1) 80 | 81 | // Modifier byte 82 | 0x75, 0x01, // Report Size (1) 83 | 0x95, 0x08, // Report Count (8) 84 | 0x05, 0x07, // Usage Page (Key codes) 85 | 0x19, 0xe0, // Usage Minimum (Keyboard LeftControl) 86 | 0x29, 0xe7, // Usage Maxium (Keyboard Right GUI) 87 | 0x15, 0x00, // Logical Minimum (0) 88 | 0x25, 0x01, // Logical Maximum (1) 89 | 0x81, 0x02, // Input (Data, Variable, Absolute) 90 | 91 | // Reserved byte 92 | 0x75, 0x01, // Report Size (1) 93 | 0x95, 0x08, // Report Count (8) 94 | 0x81, 0x03, // Input (Constant, Variable, Absolute) 95 | 96 | // LED report + padding 97 | 0x95, 0x05, // Report Count (5) 98 | 0x75, 0x01, // Report Size (1) 99 | 0x05, 0x08, // Usage Page (LEDs) 100 | 0x19, 0x01, // Usage Minimum (Num Lock) 101 | 0x29, 0x05, // Usage Maxium (Kana) 102 | 0x91, 0x02, // Output (Data, Variable, Absolute) 103 | 104 | 0x95, 0x01, // Report Count (1) 105 | 0x75, 0x03, // Report Size (3) 106 | 0x91, 0x03, // Output (Constant, Variable, Absolute) 107 | 108 | // Keycodes 109 | 0x95, 0x06, // Report Count (6) 110 | 0x75, 0x08, // Report Size (8) 111 | 0x15, 0x00, // Logical Minimum (0) 112 | 0x25, 0xff, // Logical Maximum (1) 113 | 0x05, 0x07, // Usage Page (Key codes) 114 | 0x19, 0x00, // Usage Minimum (Reserved (no event indicated)) 115 | 0x29, 0xff, // Usage Maxium (Reserved) 116 | 0x81, 0x00, // Input (Data, Array) 117 | 118 | 0xc0, // End collection 119 | 120 | 0x05, 0x0C, // (GLOBAL) USAGE_PAGE 0x000C Consumer Device Page 121 | 0x09, 0x01, // (LOCAL) USAGE 0x000C0001 Consumer Control 122 | 0xA1, 0x01, // (MAIN) COLLECTION 0x01 Application 123 | 0x85, 0x02, // (GLOBAL) REPORT_ID 2 124 | 0x19, 0x00, // (LOCAL) USAGE_MINIMUM 125 | 0x2A, 0x9C, 0x02, // (LOCAL) USAGE_MAXIMUM 126 | 0x15, 0x00, // (GLOBAL) LOGICAL_MINIMUM 127 | 0x26, 0x9C, 0x02, // (GLOBAL) LOGICAL_MAXIMUM 0x029C (668) 128 | 0x95, 0x01, // (GLOBAL) REPORT_COUNT 0x01 (1) Number of fields 129 | 0x75, 0x10, // (GLOBAL) REPORT_SIZE 0x10 (16) Number of bits per field 130 | 0x81, 0x00, // (MAIN) INPUT 131 | 0xC0 // (MAIN) END_COLLECTION Application 132 | }; 133 | 134 | #if 0 135 | // 136 | #define CHAR_ILLEGAL 0xff 137 | #define CHAR_RETURN '\n' 138 | #define CHAR_ESCAPE 27 139 | #define CHAR_TAB '\t' 140 | #define CHAR_BACKSPACE 0x7f 141 | 142 | // Simplified US Keyboard with Shift modifier 143 | 144 | /** 145 | * English (US) 146 | */ 147 | static const uint8_t keytable_us_none [] = { 148 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 0-3 */ 149 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', /* 4-13 */ 150 | 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', /* 14-23 */ 151 | 'u', 'v', 'w', 'x', 'y', 'z', /* 24-29 */ 152 | '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', /* 30-39 */ 153 | CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB, ' ', /* 40-44 */ 154 | '-', '=', '[', ']', '\\', CHAR_ILLEGAL, ';', '\'', 0x60, ',', /* 45-54 */ 155 | '.', '/', CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 55-60 */ 156 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 61-64 */ 157 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 65-68 */ 158 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 69-72 */ 159 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 73-76 */ 160 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 77-80 */ 161 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 81-84 */ 162 | '*', '-', '+', '\n', '1', '2', '3', '4', '5', /* 85-97 */ 163 | '6', '7', '8', '9', '0', '.', 0xa7, /* 97-100 */ 164 | }; 165 | 166 | static const uint8_t keytable_us_shift[] = { 167 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 0-3 */ 168 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /* 4-13 */ 169 | 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 14-23 */ 170 | 'U', 'V', 'W', 'X', 'Y', 'Z', /* 24-29 */ 171 | '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', /* 30-39 */ 172 | CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB, ' ', /* 40-44 */ 173 | '_', '+', '{', '}', '|', CHAR_ILLEGAL, ':', '"', 0x7E, '<', /* 45-54 */ 174 | '>', '?', CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 55-60 */ 175 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 61-64 */ 176 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 65-68 */ 177 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 69-72 */ 178 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 73-76 */ 179 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 77-80 */ 180 | CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 81-84 */ 181 | '*', '-', '+', '\n', '1', '2', '3', '4', '5', /* 85-97 */ 182 | '6', '7', '8', '9', '0', '.', 0xb1, /* 97-100 */ 183 | }; 184 | 185 | // HID Keyboard lookup 186 | static int lookup_keycode(uint8_t character, const uint8_t * table, int size, uint8_t * keycode){ 187 | int i; 188 | for (i=0;i>8) & 0xff}; 247 | hid_device_send_interrupt_message(hid_cid, &report_cc[0], sizeof(report_cc)); 248 | break; 249 | default: 250 | ; 251 | } 252 | hid_device_request_can_send_now_event(hid_cid); 253 | } else { 254 | printf("Send nothing\n"); 255 | uint8_t report[] = { 0xa1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 256 | hid_device_send_interrupt_message(hid_cid, &report[0], sizeof(report)); 257 | } 258 | } 259 | 260 | // Demo Application 261 | 262 | #ifdef HAVE_BTSTACK_STDIN 263 | 264 | // On systems with STDIN, we can directly type on the console 265 | 266 | static void stdin_process(char character){ 267 | #if 0 268 | uint8_t modifier; 269 | uint8_t keycode; 270 | int found = keycode_and_modifer_us_for_character(character, &keycode, &modifier); 271 | if (found){ 272 | send_key(modifier, keycode); 273 | return; 274 | } 275 | #endif 276 | printf("BTstack: Put shutter keycodes into the FIFO\n"); 277 | // for Android 278 | send_key(1, 0, 0x40); // Return 279 | send_key(1, 0, 0x80); // Volume Up 280 | // for iOS 281 | send_key(2, 0, 0xe9); // Volume Increment 282 | send_key(2, 0, 0); // Release 283 | } 284 | #else 285 | 286 | // On embedded systems, send constant demo text with fixed period 287 | 288 | #define TYPING_PERIOD_MS 100 289 | static const char * demo_text = "\n\nHello World!\n\nThis is the BTstack HID Keyboard Demo running on an Embedded Device.\n\n"; 290 | 291 | static int demo_pos; 292 | static btstack_timer_source_t typing_timer; 293 | 294 | static void typing_timer_handler(btstack_timer_source_t * ts){ 295 | 296 | // abort if not connected 297 | if (!hid_cid) return; 298 | 299 | // get next character 300 | uint8_t character = demo_text[demo_pos++]; 301 | if (demo_text[demo_pos] == 0){ 302 | demo_pos = 0; 303 | } 304 | 305 | // get keycodeand send 306 | uint8_t modifier; 307 | uint8_t keycode; 308 | int found = keycode_and_modifer_us_for_character(character, &keycode, &modifier); 309 | if (found){ 310 | send_key(1, modifier, keycode); 311 | } 312 | 313 | // set next timer 314 | btstack_run_loop_set_timer(ts, TYPING_PERIOD_MS); 315 | btstack_run_loop_add_timer(ts); 316 | } 317 | 318 | static void hid_embedded_start_typing(void){ 319 | demo_pos = 0; 320 | // set one-shot timer 321 | typing_timer.process = &typing_timer_handler; 322 | btstack_run_loop_set_timer(&typing_timer, TYPING_PERIOD_MS); 323 | btstack_run_loop_add_timer(&typing_timer); 324 | } 325 | 326 | #endif 327 | 328 | static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t * packet, uint16_t packet_size){ 329 | UNUSED(channel); 330 | UNUSED(packet_size); 331 | switch (packet_type){ 332 | case HCI_EVENT_PACKET: 333 | switch (packet[0]){ 334 | case HCI_EVENT_USER_CONFIRMATION_REQUEST: 335 | // ssp: inform about user confirmation request 336 | log_info("SSP User Confirmation Request with numeric value '%06"PRIu32"'\n", hci_event_user_confirmation_request_get_numeric_value(packet)); 337 | log_info("SSP User Confirmation Auto accept\n"); 338 | break; 339 | 340 | case HCI_EVENT_HID_META: 341 | switch (hci_event_hid_meta_get_subevent_code(packet)){ 342 | case HID_SUBEVENT_CONNECTION_OPENED: 343 | if (hid_subevent_connection_opened_get_status(packet)) return; 344 | hid_cid = hid_subevent_connection_opened_get_hid_cid(packet); 345 | #ifdef HAVE_BTSTACK_STDIN 346 | printf("HID Connected, please start typing...\n"); 347 | #else 348 | printf("HID Connected, sending demo text...\n"); 349 | hid_embedded_start_typing(); 350 | #endif 351 | break; 352 | case HID_SUBEVENT_CONNECTION_CLOSED: 353 | printf("HID Disconnected\n"); 354 | hid_cid = 0; 355 | break; 356 | case HID_SUBEVENT_CAN_SEND_NOW: 357 | send_report(); 358 | break; 359 | default: 360 | break; 361 | } 362 | break; 363 | default: 364 | break; 365 | } 366 | break; 367 | default: 368 | break; 369 | } 370 | } 371 | 372 | /* @section Main Application Setup 373 | * 374 | * @text Listing MainConfiguration shows main application code. 375 | * To run a HID Device service you need to initialize the SDP, and to create and register HID Device record with it. 376 | * At the end the Bluetooth stack is started. 377 | */ 378 | 379 | /* LISTING_START(MainConfiguration): Setup HID Device */ 380 | 381 | int btstack_main(int argc, const char * argv[]); 382 | int btstack_main(int argc, const char * argv[]){ 383 | (void)argc; 384 | (void)argv; 385 | 386 | // register for HCI events 387 | hci_event_callback_registration.callback = &packet_handler; 388 | hci_add_event_handler(&hci_event_callback_registration); 389 | hci_register_sco_packet_handler(&packet_handler); 390 | 391 | gap_discoverable_control(1); 392 | gap_set_class_of_device(0x2540); 393 | gap_set_local_name("ESP32 Remote Shutter 00:00:00:00:00:00"); 394 | 395 | // L2CAP 396 | l2cap_init(); 397 | 398 | // SDP Server 399 | sdp_init(); 400 | memset(hid_service_buffer, 0, sizeof(hid_service_buffer)); 401 | // hid sevice subclass 2540 Keyboard, hid counntry code 33 US, hid virtual cable off, hid reconnect initiate off, hid boot device off 402 | hid_create_sdp_record(hid_service_buffer, 0x10001, 0x2540, 33, 0, 0, 0, hid_descriptor_keyboard_boot_mode, sizeof(hid_descriptor_keyboard_boot_mode), hid_device_name); 403 | printf("SDP service record size: %u\n", de_get_len( hid_service_buffer)); 404 | sdp_register_service(hid_service_buffer); 405 | 406 | // HID Device 407 | hid_device_init(); 408 | hid_device_register_packet_handler(&packet_handler); 409 | 410 | // Init send queue 411 | sendQueue = xQueueCreate(10,sizeof(keyReport_t)); 412 | 413 | #ifdef HAVE_BTSTACK_STDIN 414 | btstack_stdin_setup(stdin_process); 415 | #endif 416 | // turn on! 417 | hci_power_control(HCI_POWER_ON); 418 | return 0; 419 | } 420 | /* LISTING_END */ 421 | /* EXAMPLE_END */ --------------------------------------------------------------------------------