diff --git a/app/ble/gadget_bridge.c b/app/ble/gadget_bridge.c index 56393a2..930ea30 100644 --- a/app/ble/gadget_bridge.c +++ b/app/ble/gadget_bridge.c @@ -6,7 +6,7 @@ * @version 0.1 * @date 2023-04-05 * - * @copyright Copyright (c) 2023 + * @copyright MIT * */ @@ -28,9 +28,11 @@ typedef enum gadget_bridge_parser_fsm GADGET_BRIDGE_PARSER_FSM_FOUND_SENDER, GADGET_BRIDGE_PARSER_FSM_FOUND_TEL, GADGET_BRIDGE_PARSER_FSM_FOUND_TITLE, + GADGET_BRIDGE_PARSER_FSM_PARSING_TITLE_CONTENT, GADGET_BRIDGE_PARSER_FSM_FOUND_ID_SRC, GADGET_BRIDGE_PARSER_FSM_FOUND_SRC_BODY, + GADGET_BRIDGE_PARSER_FSM_PARSING_BODY_CONTENT, GADGET_BRIDGE_PARSER_FSM_FOUND_CALL, GADGET_BRIDGE_PARSER_FSM_FOUND_NAME, @@ -77,6 +79,7 @@ time_t _unix_timestamp = 0; /* Internal function definition */ static const char *_gadget_bridge_toast_type_2_str(gadget_bridge_toast_type_e toast_type); static const char *_gadget_bridge_music_control_2_str(gadget_bridge_music_control_e music_control); +static const char *_gadget_bridge_http_request_method_2_str(gadget_bridge_http_request_method_e http_request_method); static void _parser_free_buffer(uint16_t length); static void _free_event_data(void); @@ -169,6 +172,30 @@ bool gadget_bridge_send_activity_data(uint16_t heart_rate_in_bpm, uint32_t step_ return to_return; } +bool gadget_bridge_send_http_request(uint32_t id, const char *url, gadget_bridge_http_request_method_e http_request_method, const char *http_body, const http_header_t *http_headers) +{ + bool to_return = true; + char num_2_str[11] = ""; + sprintf(num_2_str, "%u", id); + + to_return &= ble_service_send_nus_data((const uint8_t *)"{\"t\":\"http\",\"id\":\"", 18); + to_return &= ble_service_send_nus_data((const uint8_t *)num_2_str, strlen(num_2_str)); + to_return &= ble_service_send_nus_data((const uint8_t *)"\",\"url\":\"", 9); + to_return &= ble_service_send_nus_data((const uint8_t *)url, strlen(url)); + to_return &= ble_service_send_nus_data((const uint8_t *)"\",\"method\":\"", 12); + to_return &= ble_service_send_nus_data((const uint8_t *)_gadget_bridge_http_request_method_2_str(http_request_method), strlen(_gadget_bridge_http_request_method_2_str(http_request_method))); + to_return &= ble_service_send_nus_data((const uint8_t *)"\"} \n", 4); + return to_return; +} + +/*bool gadget_bridge_send_force_calendar_sync(void) +{ + bool to_return = true; + to_return &= ble_service_send_nus_data((const uint8_t *)"{\"t\":\"force_calendar_sync\"} \n", 29); + + return to_return; +}*/ + void gadget_bridge_parser_register_event_callback(parser_event_callback_t parser_event_callback) { _gadget_bridge_internals.parser_event_callback = parser_event_callback; @@ -389,12 +416,31 @@ gadget_bridge_parser_code_e gadget_bridge_parser_run(void) else to_return = GADGET_BRIDGE_PARSER_CODE_OK; break; case GADGET_BRIDGE_PARSER_FSM_FOUND_TITLE: - if((start = strstr(_gadget_bridge_internals.buffer, "title:")) - && (end = strstr(_gadget_bridge_internals.buffer, ",body"))) + if((start = strstr(_gadget_bridge_internals.buffer, "title:"))) + { + //printf("###Parsing TITLE content\n"); + + // We remove the parsed part from the buffer + start += 7; + _parser_free_buffer( + start -_gadget_bridge_internals.buffer + ); + _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_PARSING_TITLE_CONTENT; + } + else if((start = strstr(_gadget_bridge_internals.buffer, "GB("))) + _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; + else if(_gadget_bridge_internals.buffer_content_size > GADGET_BRIDGE_PARSER_BUFFER_THRESHOLD) + free_some_space = true; + else to_return = GADGET_BRIDGE_PARSER_CODE_OK; + break; + case GADGET_BRIDGE_PARSER_FSM_PARSING_TITLE_CONTENT: + { + end = strstr(_gadget_bridge_internals.buffer, ",body"); + if(end) { //printf("###Found BODY\n"); - _parser_extract_char_str(start + 7, end - 1, &_gadget_bridge_internals.event_data.notification.title); + _parser_extract_char_str(_gadget_bridge_internals.buffer, end - 1, &_gadget_bridge_internals.event_data.notification.title); // We remove the parsed part from the buffer end += 1; @@ -403,19 +449,52 @@ gadget_bridge_parser_code_e gadget_bridge_parser_run(void) ); _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_FOUND_SRC_BODY; } + else if((start = strstr(_gadget_bridge_internals.buffer, "GB("))) + _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; + // Then we have a very long title, in this case we juste keep the max set up + else if(!end && _gadget_bridge_internals.buffer_content_size >= GADGET_BRIDGE_PARSER_MAX_TITLE_SIZE) + { + printf("###NOTIFICATION (MAX TITLE SIZE)\n"); + + _parser_extract_char_str(_gadget_bridge_internals.buffer, _gadget_bridge_internals.buffer + GADGET_BRIDGE_PARSER_MAX_TITLE_SIZE, &_gadget_bridge_internals.event_data.notification.title); + + // We remove the parsed part from the buffer + _parser_free_buffer( + GADGET_BRIDGE_PARSER_MAX_TITLE_SIZE+1 + ); + + // The end of the road for this object + _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_FOUND_SRC_BODY; + } + else to_return = GADGET_BRIDGE_PARSER_CODE_OK; + } + break; + case GADGET_BRIDGE_PARSER_FSM_FOUND_SRC_BODY: + if((start = strstr(_gadget_bridge_internals.buffer, "body:"))) + { + //printf("###Parsing body content\n"); + + // We remove the : "body":" part + start += 6; + _parser_free_buffer( + start -_gadget_bridge_internals.buffer + ); + _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_PARSING_BODY_CONTENT; + } else if((start = strstr(_gadget_bridge_internals.buffer, "GB("))) _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; else if(_gadget_bridge_internals.buffer_content_size > GADGET_BRIDGE_PARSER_BUFFER_THRESHOLD) free_some_space = true; else to_return = GADGET_BRIDGE_PARSER_CODE_OK; break; - case GADGET_BRIDGE_PARSER_FSM_FOUND_SRC_BODY: - if((start = strstr(_gadget_bridge_internals.buffer, "body:")) - && (end = strstr(_gadget_bridge_internals.buffer, "})"))) + case GADGET_BRIDGE_PARSER_FSM_PARSING_BODY_CONTENT: + { + end = strstr(_gadget_bridge_internals.buffer, "})"); + if(end) { - //printf("###NOTIFICATION Type one done\n"); + //printf("###NOTIFICATION Type done\n"); - _parser_extract_char_str(start + 6, end - 1, &_gadget_bridge_internals.event_data.notification.body); + _parser_extract_char_str(_gadget_bridge_internals.buffer, end - 1, &_gadget_bridge_internals.event_data.notification.body); // We remove the parsed part from the buffer end += 2; @@ -431,12 +510,34 @@ gadget_bridge_parser_code_e gadget_bridge_parser_run(void) _free_event_data(); } // The end of the road for this object + _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; } else if((start = strstr(_gadget_bridge_internals.buffer, "GB("))) _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; - else if(_gadget_bridge_internals.buffer_content_size > GADGET_BRIDGE_PARSER_BUFFER_THRESHOLD) - free_some_space = true; + // Then we have a very long body, in this case we juste keep the max set up + else if(!end && _gadget_bridge_internals.buffer_content_size >= GADGET_BRIDGE_PARSER_MAX_BODY_SIZE) + { + //printf("###NOTIFICATION (MAX BODY SIZE) Type done\n"); + + _parser_extract_char_str(_gadget_bridge_internals.buffer, _gadget_bridge_internals.buffer + GADGET_BRIDGE_PARSER_MAX_BODY_SIZE, &_gadget_bridge_internals.event_data.notification.body); + + // We remove the parsed part from the buffer + _parser_free_buffer( + GADGET_BRIDGE_PARSER_MAX_BODY_SIZE + 1 + ); + + // If a callback was registered, we call it and pass the data to it + if(_gadget_bridge_internals.parser_event_callback) + { + _gadget_bridge_internals.parser_event_callback(&_gadget_bridge_internals.event_data); + // Free the allocated data + _free_event_data(); + } + // The end of the road for this object + _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; + } else to_return = GADGET_BRIDGE_PARSER_CODE_OK; + } break; /*case GADGET_BRIDGE_PARSER_FSM_FOUND_ID_BODY: if((start = strstr(_gadget_bridge_internals.buffer, "body:")) @@ -556,6 +657,7 @@ gadget_bridge_parser_code_e gadget_bridge_parser_run(void) _free_event_data(); } // The end of the road for this object + _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; } else if((start = strstr(_gadget_bridge_internals.buffer, "GB("))) _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; @@ -690,6 +792,7 @@ gadget_bridge_parser_code_e gadget_bridge_parser_run(void) _free_event_data(); } // The end of the road for this object + _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; } else if((start = strstr(_gadget_bridge_internals.buffer, "GB("))) _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; @@ -716,6 +819,7 @@ gadget_bridge_parser_code_e gadget_bridge_parser_run(void) _gadget_bridge_internals.parser_event_callback(&_gadget_bridge_internals.event_data); } // The end of the road for this object + _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; } else if((start = strstr(_gadget_bridge_internals.buffer, "GB("))) _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; @@ -783,6 +887,7 @@ gadget_bridge_parser_code_e gadget_bridge_parser_run(void) } // The end of the road for this object + _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; } else if((start = strstr(_gadget_bridge_internals.buffer, "GB("))) _gadget_bridge_internals.gadget_bridge_parser_fsm = GADGET_BRIDGE_PARSER_FSM_NEW_MESSAGE; @@ -898,6 +1003,26 @@ static const char *_gadget_bridge_music_control_2_str(gadget_bridge_music_contro } } +static const char *_gadget_bridge_http_request_method_2_str(gadget_bridge_http_request_method_e http_request_method) +{ + switch(http_request_method) + { + case GADGET_BRIDGE_HTTP_REQUEST_POST: + return "post"; + case GADGET_BRIDGE_HTTP_REQUEST_HEAD: + return "head"; + case GADGET_BRIDGE_HTTP_REQUEST_PUT: + return "put"; + case GADGET_BRIDGE_HTTP_REQUEST_PATCH: + return "patch"; + case GADGET_BRIDGE_HTTP_REQUEST_DELETE: + return "delete"; + case GADGET_BRIDGE_HTTP_REQUEST_GET: + default: + return "get"; + } +} + static bool _parser_extract_time(char *start, char *end) { *end = '\0'; diff --git a/app/ble/gadget_bridge.h b/app/ble/gadget_bridge.h index 0020186..d548f57 100644 --- a/app/ble/gadget_bridge.h +++ b/app/ble/gadget_bridge.h @@ -17,13 +17,34 @@ #include /** - * @brief Size of the internal buffer used to store incoming data - * which needs to be parsed. + * @brief GADGET_BRIDGE_PARSER_BUFFER_SIZE allows to set the size of the buffer + * which is internally used by the parser to do it's job. * */ #define GADGET_BRIDGE_PARSER_BUFFER_SIZE (300) + +/** + * @brief GADGET_BRIDGE_PARSER_BUFFER_THRESHOLD permits to set a size threshold used to free up + * some space in the parser's internal buffer when the threshold is reached. + * This ensures that we can keep on feeding new data and not get stuck. + * + */ #define GADGET_BRIDGE_PARSER_BUFFER_THRESHOLD (100) +/** + * @brief GADGET_BRIDGE_PARSER_MAX_BODY_SIZE defines the max body size that will be saved in the event_data + * structure when parsing the body of a notification. + * + */ +#define GADGET_BRIDGE_PARSER_MAX_BODY_SIZE (200) + +/** + * @brief GADGET_BRIDGE_PARSER_MAX_TITLE_SIZE defines the max title size that will be saved in the event_data + * structure when parsing the title of a notification. + * + */ +#define GADGET_BRIDGE_PARSER_MAX_TITLE_SIZE (100) + typedef enum gadget_bridge_toast_type { GADGET_BRIDGE_TOAST_TYPE_INFO = 0, @@ -157,28 +178,112 @@ typedef struct gadget_bridge_event_data typedef void (*parser_event_callback_t)(const gadget_bridge_event_data_t *gadget_bridge_event_data); +/** + * @brief Sends an Android toast to GadgetBridge to be displayed on the phone. + * + * @param toast_type the type of the toast (INFO, WARN or ERROR). + * @param message a string representing the message to display. + * @return true if the command was successfully sent. + * @return false otherwise. + */ bool gadget_bridge_send_toast(gadget_bridge_toast_type_e toast_type, const char *message); +/** + * @brief Sends up to two firmwares version to GadgetBridge. + * These are displayed in the display details section of the watch in GadgetBridge. + * + * @param fw1 a string representing the first firmware version. + * @param fw2 a string representing the second firmware version. + * @return true if the command was successfully sent. + * @return false otherwise. + */ bool gadget_bridge_send_firmware_version(const char *fw1, const char *fw2); +/** + * @brief Sends the current battery status to GadgetBridge. + * + * @param battery_level_in_percent the current battery level from 0 to 100%. + * @param battery_level_in_V the current battery voltage in volts (3.942 for example). + * @param is_charging a boolean which indicates if the battery is currently charging or not. + * @return true if the command was successfully sent. + * @return false otherwise. + */ bool gadget_bridge_send_battery_status(uint8_t battery_level_in_percent, float battery_level_in_V, bool is_charging); +/** + * @brief Sends the find phone command to GagdetBridge, this will make the phone ring and vibrate + * so that you can locate it. + * + * @param find_phone a boolean which indicates to make the phone rind and vibrate or not. + * @return true if the command was successfully sent. + * @return false otherwise. + */ bool gadget_bridge_send_find_phone(bool find_phone); +/** + * @brief Sends a command to control the music playback of the phone through GadgetBridge. + * + * @param music_control an enumeration value indicating the action to perform: + * PLAY, PAUSE, NEXT, PREVIOUS, VOLUMEUP etc.. + * @return true if the command was successfully sent. + * @return false otherwise. + */ bool gadget_bridge_send_music_control(gadget_bridge_music_control_e music_control); bool gadget_bridge_handle_call(gadget_bridge_call_action_e call_action); bool gadget_bridge_handle_notification(gadget_bridge_call_action_e notification_action, uint32_t handle, const char *phone_number, const char *message); +/** + * @brief Sends the provided activity data to GadgetBridge. This will then be displayed + * on the app in the activity section. + * + * @param heart_rate_in_bpm the current heart rate in beat per minute + * @param step_count the number of new steps since the last time the count was sent. + * @return true if the command was successfully sent. + * @return false otherwise. + */ bool gadget_bridge_send_activity_data(uint16_t heart_rate_in_bpm, uint32_t step_count); +/** + * @brief Tells GadgetBridge to perform an HTTP request for us. + * @note THIS DOES NOT WORK as GadgetBridge don't and will never have network permission... what a pitty ! + * + * @param id an unsigned integer representing the ID of the http request + * @param url a string representing the URL to fetch + * @param http_request_method a enumeration value specifying the http verb to use : GET, POST, PATCH etc.. + * @param http_body the body to include in the request (not implemented yet) + * @param http_headers various headers to include in the request (not implemented yet) + * @return true if the request has been successfully sent to GadgetBridge + * @return false otherwise + */ bool gadget_bridge_send_http_request(uint32_t id, const char *url, gadget_bridge_http_request_method_e http_request_method, const char *http_body, const http_header_t *http_headers); +//bool gadget_bridge_send_force_calendar_sync(void); + +/** + * @brief Registers a callback function used to listen for GadgetBridge events. + * + * @param parser_event_callback + */ void gadget_bridge_parser_register_event_callback(parser_event_callback_t parser_event_callback); +/** + * @brief Feeds new data to the GadgetBridge parser. + * + * @param data the new chunk of data to parse, it will be copied to the parser's internal buffer, + * so you can free the memory containing the string after calling the function. + * @param length the length in bytes of the new chunk. + * @return gadget_bridge_parser_code_e GADGET_BRIDGE_PARSER_CODE_OK if no error occured. + */ gadget_bridge_parser_code_e gadget_bridge_parser_feed(const char *data, uint16_t length); +/** + * @brief Call this function to run the parser. + * It should be safe to call if in a loop like : while((code = gadget_bridge_parser_run()) == GADGET_BRIDGE_PARSER_CODE_PARSING); + * + * @return gadget_bridge_parser_code_e the parser's execution status code. + */ gadget_bridge_parser_code_e gadget_bridge_parser_run(void); const char *gadget_bridge_parser_code_2_str(gadget_bridge_parser_code_e parser_code);