/* * Copyright (C) 2014 BlueKitchen GmbH * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * 4. Any redistribution, use, or modification is done solely for * personal benefit and not for any commercial purpose or for * monetary gain. * * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Please inquire about commercial licensing options at * contact@bluekitchen-gmbh.com * */ #include "btstack-config.h" #include #include #include #include #include #include "ancs_client_lib.h" #include "att.h" #include "debug.h" #include "gap_le.h" #include "gatt_client.h" #include "sm.h" // ancs_client.h Start typedef enum ancs_chunk_parser_state { W4_ATTRIBUTE_ID, W4_ATTRIBUTE_LEN, W4_ATTRIBUTE_COMPLETE, } ancs_chunk_parser_state_t; typedef enum { TC_IDLE, TC_W4_ENCRYPTED_CONNECTION, TC_W4_SERVICE_RESULT, TC_W4_CHARACTERISTIC_RESULT, TC_W4_DATA_SOURCE_SUBSCRIBED, TC_W4_NOTIFICATION_SOURCE_SUBSCRIBED, TC_SUBSCRIBED, TC_W4_DISCONNECT } tc_state_t; static const char * ancs_attribute_names[] = { "AppIdentifier", "IDTitle", "IDSubtitle", "IDMessage", "IDMessageSize", "IDDate" }; static const int ANCS_ATTRBUTE_NAMES_COUNT = sizeof(ancs_attribute_names) / sizeof(char *); static const uint8_t ancs_service_uuid[] = {0x79,0x05,0xF4,0x31,0xB5,0xCE,0x4E,0x99,0xA4,0x0F,0x4B,0x1E,0x12,0x2D,0x00,0xD0}; static const uint8_t ancs_notification_source_uuid[] = {0x9F,0xBF,0x12,0x0D,0x63,0x01,0x42,0xD9,0x8C,0x58,0x25,0xE6,0x99,0xA2,0x1D,0xBD}; static const uint8_t ancs_control_point_uuid[] = {0x69,0xD1,0xD8,0xF3,0x45,0xE1,0x49,0xA8,0x98,0x21,0x9B,0xBD,0xFD,0xAA,0xD9,0xD9}; static const uint8_t ancs_data_source_uuid[] = {0x22,0xEA,0xC6,0xE9,0x24,0xD6,0x4B,0xB5,0xBE,0x44,0xB3,0x6A,0xCE,0x7C,0x7B,0xFB}; static uint32_t ancs_notification_uid; static uint16_t gc_handle, gc_id; static int ancs_service_found; static le_service_t ancs_service; static le_characteristic_t ancs_notification_source_characteristic; static le_characteristic_t ancs_control_point_characteristic; static le_characteristic_t ancs_data_source_characteristic; static int ancs_characteristcs; static tc_state_t tc_state = TC_IDLE; static ancs_chunk_parser_state_t chunk_parser_state; static char ancs_notification_buffer[50]; static uint16_t ancs_bytes_received; static uint16_t ancs_bytes_needed; static uint8_t ancs_attribute_id; static uint16_t ancs_attribute_len; static void (*client_handler)(ancs_event_t * event); void ancs_client_register_callback(void (*handler)(ancs_event_t * event)){ client_handler = handler; } static void notify_client(int event_type){ if (!client_handler) return; ancs_event_t event; event.type = event_type; event.handle = gc_handle; event.attribute_id = ancs_attribute_id; event.text = ancs_notification_buffer; (*client_handler)(&event); } static void ancs_chunk_parser_init(){ chunk_parser_state = W4_ATTRIBUTE_ID; ancs_bytes_received = 0; ancs_bytes_needed = 6; } const char * ancs_client_attribute_name_for_id(int id){ if (id >= ANCS_ATTRBUTE_NAMES_COUNT) return 0; return ancs_attribute_names[id]; } static void ancs_chunk_parser_handle_byte(uint8_t data){ ancs_notification_buffer[ancs_bytes_received++] = data; if (ancs_bytes_received < ancs_bytes_needed) return; switch (chunk_parser_state){ case W4_ATTRIBUTE_ID: ancs_attribute_id = ancs_notification_buffer[ancs_bytes_received-1]; ancs_bytes_received = 0; ancs_bytes_needed = 2; chunk_parser_state = W4_ATTRIBUTE_LEN; break; case W4_ATTRIBUTE_LEN: ancs_attribute_len = READ_BT_16(ancs_notification_buffer, ancs_bytes_received-2); ancs_bytes_received = 0; ancs_bytes_needed = ancs_attribute_len; if (ancs_attribute_len == 0) { ancs_bytes_needed = 1; chunk_parser_state = W4_ATTRIBUTE_ID; break; } chunk_parser_state = W4_ATTRIBUTE_COMPLETE; break; case W4_ATTRIBUTE_COMPLETE: ancs_notification_buffer[ancs_bytes_received] = 0; notify_client(ANCS_CLIENT_NOTIFICATION); ancs_bytes_received = 0; ancs_bytes_needed = 1; chunk_parser_state = W4_ATTRIBUTE_ID; break; } } static void handle_gatt_client_event(le_event_t * event){ le_characteristic_t characteristic; le_characteristic_value_event_t * value_event; switch(tc_state){ case TC_W4_SERVICE_RESULT: switch(event->type){ case GATT_SERVICE_QUERY_RESULT: ancs_service = ((le_service_event_t *) event)->service; ancs_service_found = 1; break; case GATT_QUERY_COMPLETE: if (!ancs_service_found){ printf("ANCS Service not found"); tc_state = TC_IDLE; break; } tc_state = TC_W4_CHARACTERISTIC_RESULT; printf("ANCS Client - Discover characteristics for ANCS SERVICE \n"); gatt_client_discover_characteristics_for_service(gc_id, gc_handle, &ancs_service); break; default: break; } break; case TC_W4_CHARACTERISTIC_RESULT: switch(event->type){ case GATT_CHARACTERISTIC_QUERY_RESULT: characteristic = ((le_characteristic_event_t *) event)->characteristic; if (memcmp(characteristic.uuid128, ancs_notification_source_uuid, 16) == 0){ printf("ANCS Notification Source Characterisic found\n"); ancs_notification_source_characteristic = characteristic; ancs_characteristcs++; break; } if (memcmp(characteristic.uuid128, ancs_control_point_uuid, 16) == 0){ printf("ANCS Control Point found\n"); ancs_control_point_characteristic = characteristic; ancs_characteristcs++; break; } if (memcmp(characteristic.uuid128, ancs_data_source_uuid, 16) == 0){ printf("ANCS Data Source Characterisic found\n"); ancs_data_source_characteristic = characteristic; ancs_characteristcs++; break; } break; case GATT_QUERY_COMPLETE: printf("ANCS Characteristcs count %u\n", ancs_characteristcs); tc_state = TC_W4_NOTIFICATION_SOURCE_SUBSCRIBED; gatt_client_write_client_characteristic_configuration(gc_id, gc_handle, &ancs_notification_source_characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); break; default: break; } break; case TC_W4_NOTIFICATION_SOURCE_SUBSCRIBED: switch(event->type){ case GATT_QUERY_COMPLETE: printf("ANCS Notification Source subscribed\n"); tc_state = TC_W4_DATA_SOURCE_SUBSCRIBED; gatt_client_write_client_characteristic_configuration(gc_id, gc_handle, &ancs_data_source_characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); break; default: break; } break; case TC_W4_DATA_SOURCE_SUBSCRIBED: switch(event->type){ case GATT_QUERY_COMPLETE: printf("ANCS Data Source subscribed\n"); tc_state = TC_SUBSCRIBED; notify_client(ANCS_CLIENT_CONNECTED); break; default: break; } break; case TC_SUBSCRIBED: if ( event->type != GATT_NOTIFICATION && event->type != GATT_INDICATION ) break; value_event = (le_characteristic_value_event_t *) event; if (value_event->value_handle == ancs_data_source_characteristic.value_handle){ int i; for (i=0;iblob_length;i++) { ancs_chunk_parser_handle_byte(value_event->blob[i]); } } else if (value_event->value_handle == ancs_notification_source_characteristic.value_handle){ ancs_notification_uid = READ_BT_32(value_event->blob, 4); printf("Notification received: EventID %02x, EventFlags %02x, CategoryID %02x, CategoryCount %u, UID %04x\n", value_event->blob[0], value_event->blob[1], value_event->blob[2], value_event->blob[3], ancs_notification_uid); static uint8_t get_notification_attributes[] = {0, 0,0,0,0, 0, 1,32,0, 2,32,0, 3,32,0, 4, 5}; bt_store_32(get_notification_attributes, 1, ancs_notification_uid); ancs_notification_uid = 0; ancs_chunk_parser_init(); gatt_client_write_value_of_characteristic(gc_id, gc_handle, ancs_control_point_characteristic.value_handle, sizeof(get_notification_attributes), get_notification_attributes); } else { printf("Unknown Source: "); printf_hexdump(value_event->blob , value_event->blob_length); } break; default: break; } // app_run(); } void ancs_client_hci_event_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ int connection_encrypted; switch (packet_type) { case HCI_EVENT_PACKET: switch (packet[0]) { case HCI_EVENT_LE_META: switch (packet[2]) { case HCI_SUBEVENT_LE_CONNECTION_COMPLETE: gc_handle = READ_BT_16(packet, 4); printf("Connection handle 0x%04x\n", gc_handle); // we need to be paired to enable notifications tc_state = TC_W4_ENCRYPTED_CONNECTION; sm_send_security_request(gc_handle); break; default: break; } break; case HCI_EVENT_ENCRYPTION_CHANGE: if (gc_handle != READ_BT_16(packet, 3)) break; connection_encrypted = packet[5]; log_info("Encryption state change: %u", connection_encrypted); if (!connection_encrypted) break; if (tc_state != TC_W4_ENCRYPTED_CONNECTION) break; // let's start printf("\nANCS Client - CONNECTED, discover ANCS service\n"); tc_state = TC_W4_SERVICE_RESULT; gatt_client_discover_primary_services_by_uuid128(gc_id, gc_handle, ancs_service_uuid); break; case HCI_EVENT_DISCONNECTION_COMPLETE: notify_client(ANCS_CLIENT_DISCONNECTED); break; default: break; } } } void ancs_client_init(){ gc_id = gatt_client_register_packet_handler(&handle_gatt_client_event); }