/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "clar.h" #include "protobuf_log_test_helpers.h" #include "services/normal/protobuf_log/protobuf_log.h" #include "services/normal/protobuf_log/protobuf_log_private.h" #include "services/normal/protobuf_log/protobuf_log_test.h" #include "services/normal/protobuf_log/protobuf_log_hr.h" #include "services/normal/protobuf_log/protobuf_log_activity_sessions.h" #include "services/normal/activity/activity.h" #include "applib/data_logging.h" #include "drivers/rtc.h" #include "services/normal/data_logging/data_logging_service.h" #include "system/logging.h" #include "util/attributes.h" #include "util/size.h" #include "stubs_passert.h" #include "stubs_logging.h" #include "stubs_mutex.h" #include "stubs_pbl_malloc.h" #include "stubs_prompt.h" #include "stubs_serial.h" #include "fake_rtc.h" #include #include #include #define WRITE_TO_FILE 0 #define LOG(fmt, args...) \ PBL_LOG(LOG_LEVEL_DEBUG, fmt, ## args) extern uint32_t prv_hr_quality_int(HRMQuality quality); // --------------------------------------------------------------------------------------------- // We start time out at 5pm on Jan 1, 2015 for all of these tests static const struct tm s_init_time_tm = { // Thursday, Jan 1, 2015, 5:pm .tm_hour = 17, .tm_mday = 1, .tm_mon = 0, .tm_year = 115 }; // Logged items #define TEST_PL_DLS_SESSION_ID 1 typedef struct { uint8_t data[PLOG_DLS_RECORD_SIZE]; } TestPLDLSRecord; static bool s_dls_session_created; static int s_num_dls_records; static TestPLDLSRecord s_dls_records[10]; static void prv_reset_captured_dls_data(void) { s_num_dls_records = 0; } // // Data Logging Fakes // DataLoggingResult dls_log(DataLoggingSession *logging_session, const void *data, uint32_t num_items) { cl_assert(s_dls_session_created); TestPLDLSRecord *records = (TestPLDLSRecord *)data; for (int i = 0; i < num_items; i++) { cl_assert(s_num_dls_records < ARRAY_LENGTH(s_dls_records)); s_dls_records[s_num_dls_records++] = records[i]; } return DATA_LOGGING_SUCCESS; } DataLoggingSession *dls_create(uint32_t tag, DataLoggingItemType item_type, uint16_t item_size, bool buffered, bool resume, const Uuid *uuid) { if (tag == DlsSystemTagProtobufLogSession) { s_dls_session_created = true; cl_assert_equal_i(item_size, sizeof(TestPLDLSRecord)); return (DataLoggingSession *)TEST_PL_DLS_SESSION_ID; } else { return NULL; } } void dls_finish(DataLoggingSession *logging_session) { if (logging_session == (DataLoggingSession *)TEST_PL_DLS_SESSION_ID) { s_dls_session_created = false; } else { cl_assert(false); } } // // MFG/Version Fakes // #define TEST_PL_SERIAL_NUM "ABC01234567" const char* mfg_get_serial_number(void) { return TEST_PL_SERIAL_NUM; } #define GIT_TAG_V_MAJOR 4 #define GIT_TAG_V_MINOR 17 #define GIT_TAG_V_PATCH "ROBERT-mfg4-6-gb91951a" void version_get_major_minor_patch(unsigned int *major, unsigned int *minor, const char **patch_ptr) { *major = GIT_TAG_V_MAJOR; *minor = GIT_TAG_V_MINOR; *patch_ptr = GIT_TAG_V_PATCH; } // --------------------------------------------------------------------------------------------- // The transport callback we pass to protobuf_log_create that captures the encoded message static uint8_t *s_saved_encoded_msg; static bool prv_protobuf_log_transport(uint8_t *buffer, size_t buf_size) { if (s_saved_encoded_msg != NULL) { free(s_saved_encoded_msg); s_saved_encoded_msg = NULL; } s_saved_encoded_msg = malloc(buf_size); cl_assert(s_saved_encoded_msg != NULL); memcpy(s_saved_encoded_msg, buffer, buf_size); return true; } // This structure used to capture the contents of a decoded message into a global typedef struct { ProtobufLogType type; char payload_sender_type[PLOG_MAX_SENDER_TYPE_LEN]; char payload_sender_id[PLOG_MAX_SENDER_ID_LEN]; char payload_sender_version_patch[PLOG_MAX_SENDER_VERSION_PATCH_LEN]; uint32_t payload_send_time; uint32_t payload_sender_v_major; uint32_t payload_sender_v_minor; union { struct { Uuid uuid; uint32_t num_types; ProtobufLogMeasurementType *types; uint32_t num_samples; uint32_t num_values; uint32_t *values; uint32_t *offset_sec; uint32_t time_utc; uint32_t time_end_utc; int32_t utc_to_local; } msrmt; }; struct { uint32_t num_events; pebble_pipeline_Event *events; Uuid *uuids; uint32_t num_sessions; ActivitySession *sessions; } events; } TestPLParsedMsg; // --------------------------------------------------------------------------------------------- // Parse and encoded message and return a TestPLParsedMsg structure pointer pointing to it's // parsed contents. The contents are valid until prv_parse_encoded_msg is called again. static TestPLParsedMsg *prv_parse_encoded_mset_payload(void *buffer) { static TestPLParsedMsg s_parsed_msg; static ProtobufLogMeasurementType s_types[10]; static uint32_t s_offsets[1000]; static uint32_t s_values[1000]; memset(&s_parsed_msg, 0, sizeof(s_parsed_msg)); memset(&s_types, 0, sizeof(s_types)); memset(&s_offsets, 0, sizeof(s_offsets)); memset(&s_values, 0, sizeof(s_values)); s_parsed_msg = (TestPLParsedMsg) { .msrmt = { .num_types = ARRAY_LENGTH(s_types), .types = s_types, .num_samples = ARRAY_LENGTH(s_offsets), .offset_sec = s_offsets, .num_values = ARRAY_LENGTH(s_values), .values = s_values, }, }; // Get the message size and pointer to encoded data PLogMessageHdr *hdr = (PLogMessageHdr *)buffer; bool success = protobuf_log_private_mset_decode( &s_parsed_msg.type, (uint8_t *)buffer + sizeof(*hdr), hdr->msg_size, s_parsed_msg.payload_sender_type, s_parsed_msg.payload_sender_id, s_parsed_msg.payload_sender_version_patch, &s_parsed_msg.payload_send_time, &s_parsed_msg.payload_sender_v_major, &s_parsed_msg.payload_sender_v_minor, &s_parsed_msg.msrmt.uuid, &s_parsed_msg.msrmt.time_utc, &s_parsed_msg.msrmt.time_end_utc, &s_parsed_msg.msrmt.utc_to_local, &s_parsed_msg.msrmt.num_types, s_parsed_msg.msrmt.types, &s_parsed_msg.msrmt.num_samples, s_parsed_msg.msrmt.offset_sec, &s_parsed_msg.msrmt.num_values, s_parsed_msg.msrmt.values); if (!success) { LOG("No encoded msg available"); } else { LOG("ProtobufLogType: %d", s_parsed_msg.type); LOG("payload_sender_type: %s", s_parsed_msg.payload_sender_type); LOG("payload_sender_id: %s", s_parsed_msg.payload_sender_id); LOG("payload_sender_version: major: %"PRIu32", minor: %"PRIu32", patch: %s", s_parsed_msg.payload_sender_v_major, s_parsed_msg.payload_sender_v_minor, s_parsed_msg.payload_sender_version_patch); LOG("payload_send_time: %"PRIu32"", s_parsed_msg.payload_send_time); LOG("MeasurementSet:"); char uuid_str[UUID_STRING_BUFFER_LENGTH]; uuid_to_string(&s_parsed_msg.msrmt.uuid, uuid_str); LOG(" Uuid: %s", uuid_str); LOG(" time_utc: %"PRIu32", time_end_utc: %"PRIu32", utc_to_local: %"PRIi32"", s_parsed_msg.msrmt.time_utc, s_parsed_msg.msrmt.time_end_utc, s_parsed_msg.msrmt.utc_to_local); LOG(" %"PRIu32" types: ", s_parsed_msg.msrmt.num_types); for (unsigned i = 0; i < s_parsed_msg.msrmt.num_types; i++) { LOG(" %d", (int)s_parsed_msg.msrmt.types[i]); } LOG(" %"PRIu32" measurements: ", s_parsed_msg.msrmt.num_samples); for (unsigned i = 0; i < s_parsed_msg.msrmt.num_samples; i++) { LOG(" offset_sec: %"PRIu32"", s_parsed_msg.msrmt.offset_sec[i]); for (unsigned j = 0; j < s_parsed_msg.msrmt.num_types; j++) { LOG(" 0x%"PRIx32"", s_parsed_msg.msrmt.values[i * s_parsed_msg.msrmt.num_types + j]); } } } return &s_parsed_msg; } // --------------------------------------------------------------------------------------------- // Parse and encoded message and return a TestPLParsedMsg structure pointer pointing to it's // parsed contents. The contents are valid until prv_parse_encoded_msg is called again. static TestPLParsedMsg *prv_parse_encoded_event_payload(void *buffer) { static TestPLParsedMsg s_parsed_msg; static pebble_pipeline_Event events[10]; static Uuid event_uuids[10]; static ActivitySession event_sessions[10]; s_parsed_msg = (TestPLParsedMsg) { .events = { .num_events = ARRAY_LENGTH(events), .events = events, .uuids = event_uuids, .num_sessions = ARRAY_LENGTH(event_sessions), .sessions = event_sessions, }, }; // Get the message size and pointer to encoded data PLogMessageHdr *hdr = (PLogMessageHdr *)buffer; bool success = protobuf_log_private_events_decode( &s_parsed_msg.type, (uint8_t *)buffer + sizeof(*hdr), hdr->msg_size, s_parsed_msg.payload_sender_type, s_parsed_msg.payload_sender_id, s_parsed_msg.payload_sender_version_patch, &s_parsed_msg.payload_send_time, &s_parsed_msg.payload_sender_v_major, &s_parsed_msg.payload_sender_v_minor, &s_parsed_msg.events.num_events, s_parsed_msg.events.events, s_parsed_msg.events.uuids, &s_parsed_msg.events.num_sessions, s_parsed_msg.events.sessions); if (!success) { LOG("No encoded msg available"); } else { LOG("ProtobufLogType: %d", s_parsed_msg.type); LOG("payload_sender_type: %s", s_parsed_msg.payload_sender_type); LOG("payload_sender_id: %s", s_parsed_msg.payload_sender_id); LOG("payload_sender_version: major: %"PRIu32", minor: %"PRIu32", patch: %s", s_parsed_msg.payload_sender_v_major, s_parsed_msg.payload_sender_v_minor, s_parsed_msg.payload_sender_version_patch); LOG("payload_send_time: %"PRIu32"", s_parsed_msg.payload_send_time); const int num_events = s_parsed_msg.events.num_events; LOG("Events: Number: %d", num_events); for (int i = 0; i < num_events; i++) { pebble_pipeline_Event *event = &s_parsed_msg.events.events[i]; LOG(" Event -- Type: %d", event->type); char uuid_str[UUID_STRING_BUFFER_LENGTH]; uuid_to_string(&s_parsed_msg.events.uuids[i], uuid_str); LOG(" Uuid: %s", uuid_str); LOG(" time_utc: %"PRIu32", created_time_utc: %"PRIu32", utc_to_local: %"PRIi32"", event->time_utc, event->created_time_utc, event->utc_to_local); LOG(" duration: %"PRIu32, event->duration); // Activity Event if (event->type == pebble_pipeline_Event_Type_ActivitySessionEvent) { const pebble_pipeline_ActivitySession *session = &event->activity_session; LOG(" Activity Type: %d, Start Reason: %d", session->type.type.internal_type, session->start_reason); // TODO: Add more detailed logging } } } return &s_parsed_msg; } // --------------------------------------------------------------------------------------------- static void prv_assert_msg_equal(TestPLParsedMsg *a, TestPLParsedMsg *b) { // Payload Specific cl_assert_equal_s(a->payload_sender_type, b->payload_sender_type); cl_assert_equal_s(a->payload_sender_id, b->payload_sender_id); cl_assert_equal_i(a->payload_send_time, b->payload_send_time); cl_assert_equal_i(a->payload_sender_v_major, b->payload_sender_v_major); cl_assert_equal_i(a->payload_sender_v_minor, b->payload_sender_v_minor); cl_assert_equal_s(a->payload_sender_version_patch, b->payload_sender_version_patch); // Ensure they are the same type cl_assert_equal_i(a->type, b->type); // MeasurementSet Specific if (a->type == ProtobufLogType_Measurements) { cl_assert_equal_i(a->msrmt.time_utc, b->msrmt.time_utc); cl_assert_equal_i(a->msrmt.utc_to_local, b->msrmt.utc_to_local); cl_assert_equal_i(a->msrmt.num_types, b->msrmt.num_types); cl_assert_equal_m(a->msrmt.types, b->msrmt.types, b->msrmt.num_types * sizeof(ProtobufLogMeasurementType)); cl_assert_equal_i(a->msrmt.num_samples, b->msrmt.num_samples); cl_assert_equal_m(a->msrmt.offset_sec, b->msrmt.offset_sec, b->msrmt.num_samples * sizeof(uint32_t)); cl_assert_equal_i(a->msrmt.num_values, b->msrmt.num_values); cl_assert_equal_m(a->msrmt.values, b->msrmt.values, b->msrmt.num_values * sizeof(uint32_t)); } else if (a->type == ProtobufLogType_Events) { cl_assert_equal_i(a->events.num_events, b->events.num_events); cl_assert_equal_i(a->events.num_sessions, b->events.num_sessions); for (int i = 0; i < a->events.num_events; i++) { const pebble_pipeline_Event *a_event = &a->events.events[i]; const pebble_pipeline_Event *b_event = &b->events.events[i]; cl_assert_equal_i(a_event->type, b_event->type); cl_assert_equal_i(a_event->created_time_utc, b_event->created_time_utc); cl_assert_equal_i(a_event->duration, b_event->duration); cl_assert_equal_i(a_event->time_utc, b_event->time_utc); cl_assert_equal_i(a_event->utc_to_local, b_event->utc_to_local); if (a_event->type == pebble_pipeline_Event_Type_ActivitySessionEvent) { cl_assert_equal_i(a_event->activity_session.type.type.internal_type, b_event->activity_session.type.type.internal_type); cl_assert_equal_i(a_event->activity_session.start_reason, b_event->activity_session.start_reason); // TODO: Add more detailed comparisons } } } } static void prv_fill_version(TestPLParsedMsg *msg) { msg->payload_sender_v_major = GIT_TAG_V_MAJOR; msg->payload_sender_v_minor = GIT_TAG_V_MINOR; strcpy(msg->payload_sender_version_patch, GIT_TAG_V_PATCH); } static void prv_common_payload_initialize(TestPLParsedMsg *input) { // We always have the same sender type and id strncpy(input->payload_sender_type, PLOG_PAYLOAD_SENDER_TYPE, PLOG_MAX_SENDER_ID_LEN); strncpy(input->payload_sender_id, TEST_PL_SERIAL_NUM, PLOG_MAX_SENDER_ID_LEN); prv_fill_version(input); // Reset data logging storage prv_reset_captured_dls_data(); } static ProtobufLogRef prv_log_create_measurement(TestPLParsedMsg *input, bool use_data_logging) { // Create a session ProtobufLogTransportCB transport_cb = prv_protobuf_log_transport; if (use_data_logging) { transport_cb = NULL; } ProtobufLogConfig log_config = { .type = ProtobufLogType_Measurements, .measurements = { .num_types = input->msrmt.num_types, .types = input->msrmt.types, } }; ProtobufLogRef session_ref = protobuf_log_create(&log_config, transport_cb, 0); cl_assert(session_ref != NULL); return session_ref; } static TestPLParsedMsg *prv_flush_get_record(TestPLParsedMsg *input, bool use_data_logging, ProtobufLogRef session_ref) { // Flush it out now, this should end up in our transport method being called input->payload_send_time = rtc_get_time(); bool success = protobuf_log_session_flush(session_ref); cl_assert(success); TestPLParsedMsg *(*parser)(void *); switch (input->type) { case ProtobufLogType_Events: parser = prv_parse_encoded_event_payload; break; case ProtobufLogType_Measurements: parser = prv_parse_encoded_mset_payload; break; } // Verify the contents TestPLParsedMsg *msg; if (use_data_logging) { cl_assert(s_num_dls_records == 1); msg = parser(&s_dls_records[0]); } else { #if WRITE_TO_FILE protobuf_log_test_parse_protoc(s_saved_encoded_msg); #endif msg = parser(s_saved_encoded_msg); } return msg; } // --------------------------------------------------------------------------------------------- static ProtobufLogRef *prv_test_encode_measurements(TestPLParsedMsg *input, bool use_data_logging) { prv_common_payload_initialize(input); ProtobufLogRef session_ref = prv_log_create_measurement(input, use_data_logging); // Encode the measurements uint32_t values_per_samples = input->msrmt.num_types; bool success; for (unsigned i = 0; i < input->msrmt.num_samples; i++) { rtc_set_time(input->msrmt.time_utc + input->msrmt.offset_sec[i]); success = protobuf_log_session_add_measurements(session_ref, rtc_get_time(), input->msrmt.num_types, &input->msrmt.values[i * values_per_samples]); cl_assert(success); } return session_ref; } static void prv_test_decode_payload(TestPLParsedMsg *input, bool use_data_logging, ProtobufLogRef session_ref) { TestPLParsedMsg *record = prv_flush_get_record(input, use_data_logging, session_ref); prv_assert_msg_equal(input, record); // Delete the session protobuf_log_session_delete(session_ref); } // --------------------------------------------------------------------------------------------- void test_protobuf_log__initialize(void) { struct tm time_tm = s_init_time_tm; time_t utc_sec = mktime(&time_tm); fake_rtc_init(100 /*initial_ticks*/, utc_sec); TimezoneInfo tz_info = { .tm_zone = "???", .tm_gmtoff = SECONDS_PER_HOUR, }; time_util_update_timezone(&tz_info); s_dls_session_created = false; s_saved_encoded_msg = NULL; protobuf_log_init(); } // --------------------------------------------------------------------------------------------- void test_protobuf_log__cleanup(void) { } // --------------------------------------------------------------------------------------------- // Test some simple message variants void test_protobuf_log__measurements_simple(void) { // A simple message with 2 types, 2 samples { ProtobufLogMeasurementType types[] = {ProtobufLogMeasurementType_Steps, ProtobufLogMeasurementType_BPM}; uint32_t offset_sec[] = {1, 2}; uint32_t values[] = {0x11, 0x22, 0x33, 0x44}; TestPLParsedMsg input = { .type = ProtobufLogType_Measurements, .msrmt = { .time_utc = rtc_get_time(), .utc_to_local = time_util_utc_to_local_offset(), .num_types = ARRAY_LENGTH(types), .types = types, .num_samples = ARRAY_LENGTH(offset_sec), .offset_sec = offset_sec, .num_values = ARRAY_LENGTH(values), .values = values, }, }; ProtobufLogRef ref = prv_test_encode_measurements(&input, false /*use_data_logging*/); prv_test_decode_payload(&input, false /*use_data_logging*/, ref); } // A simple message with 1 type, 1 sample. Large sample values { ProtobufLogMeasurementType types[] = {ProtobufLogMeasurementType_BPM}; uint32_t offset_sec[] = {2, 4}; uint32_t values[] = {0x11223344, 0x22334455}; TestPLParsedMsg input = { .type = ProtobufLogType_Measurements, .msrmt = { .time_utc = rtc_get_time(), .utc_to_local = time_util_utc_to_local_offset(), .num_types = ARRAY_LENGTH(types), .types = types, .num_samples = ARRAY_LENGTH(offset_sec), .offset_sec = offset_sec, .num_values = ARRAY_LENGTH(values), .values = values, }, }; ProtobufLogRef ref = prv_test_encode_measurements(&input, false /*use_data_logging*/); prv_test_decode_payload(&input, false /*use_data_logging*/, ref); } // A message with 4 types, 3 samples { ProtobufLogMeasurementType types[] = {ProtobufLogMeasurementType_Steps, ProtobufLogMeasurementType_BPM, ProtobufLogMeasurementType_VMC, ProtobufLogMeasurementType_DistanceCM}; uint32_t offset_sec[] = {1, 2, 3}; uint32_t values[] = {0x11, 0x22, 0x33, 0x44, 0x1111, 0x2222, 0x3333, 0x4444, 0x111111, 0x222222, 0x333333, 0x444444}; TestPLParsedMsg input = { .type = ProtobufLogType_Measurements, .msrmt = { .time_utc = rtc_get_time(), .utc_to_local = time_util_utc_to_local_offset(), .num_types = ARRAY_LENGTH(types), .types = types, .num_samples = ARRAY_LENGTH(offset_sec), .offset_sec = offset_sec, .num_values = ARRAY_LENGTH(values), .values = values, } }; ProtobufLogRef ref = prv_test_encode_measurements(&input, false /*use_data_logging*/); prv_test_decode_payload(&input, false /*use_data_logging*/, ref); } } // --------------------------------------------------------------------------------------------- // Try doing multiple flushes from the same session void test_protobuf_log__measurements_multiple(void) { ProtobufLogMeasurementType types[] = {ProtobufLogMeasurementType_Steps, ProtobufLogMeasurementType_BPM}; uint32_t offset_sec[] = {1, 2}; uint32_t values[] = {0x11, 0x22, 0x33, 0x44}; TestPLParsedMsg input = { .type = ProtobufLogType_Measurements, .msrmt = { .time_utc = rtc_get_time(), .utc_to_local = time_util_utc_to_local_offset(), .num_types = ARRAY_LENGTH(types), .types = types, .num_samples = ARRAY_LENGTH(offset_sec), .offset_sec = offset_sec, .num_values = ARRAY_LENGTH(values), .values = values, }, }; prv_common_payload_initialize(&input); // Create a session ProtobufLogConfig log_config = { .type = ProtobufLogType_Measurements, .measurements = { .num_types = input.msrmt.num_types, .types = input.msrmt.types, } }; ProtobufLogRef session_ref = protobuf_log_create(&log_config, prv_protobuf_log_transport, 0); cl_assert(session_ref != NULL); // ------------------ // Encode the first set of measurements uint32_t values_per_samples = input.msrmt.num_types; bool success; for (unsigned i = 0; i < input.msrmt.num_samples; i++) { rtc_set_time(input.msrmt.time_utc + input.msrmt.offset_sec[i]); success = protobuf_log_session_add_measurements(session_ref, rtc_get_time(), input.msrmt.num_types, &input.msrmt.values[i * values_per_samples]); cl_assert(success); } // Flush it out now, this should end up in our transport method being called TestPLParsedMsg *msg = prv_flush_get_record(&input, false /* use_data_logging */, session_ref); prv_assert_msg_equal(&input, msg); // ------------------ // Send another set of measurements uint32_t offset_sec_b[] = {2, 4, 6}; uint32_t values_b[] = {0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666}; input = (TestPLParsedMsg) { .type = ProtobufLogType_Measurements, .msrmt = { .time_utc = rtc_get_time(), .utc_to_local = time_util_utc_to_local_offset(), .num_types = ARRAY_LENGTH(types), .types = types, .num_samples = ARRAY_LENGTH(offset_sec_b), .offset_sec = offset_sec_b, .num_values = ARRAY_LENGTH(values_b), .values = values_b, } }; prv_common_payload_initialize(&input); for (unsigned i = 0; i < input.msrmt.num_samples; i++) { rtc_set_time(input.msrmt.time_utc + input.msrmt.offset_sec[i]); success = protobuf_log_session_add_measurements(session_ref, rtc_get_time(), input.msrmt.num_types, &input.msrmt.values[i * values_per_samples]); cl_assert(success); } // Flush it out now, this should end up in our transport method being called msg = prv_flush_get_record(&input, false /* use_data_logging */, session_ref); prv_assert_msg_equal(&input, msg); // Delete the session now protobuf_log_session_delete(session_ref); } // --------------------------------------------------------------------------------------------- // Test the automatic flush functionality void test_protobuf_log__measurements_auto_flush(void) { ProtobufLogMeasurementType types[] = {ProtobufLogMeasurementType_Steps, ProtobufLogMeasurementType_BPM}; const int num_samples = 50; const int num_values_per_sample = ARRAY_LENGTH(types); uint32_t offset_sec[num_samples]; uint32_t values[num_samples * num_values_per_sample]; for (unsigned i = 0; i < num_samples; i++) { offset_sec[i] = i * 2; } for (unsigned i = 0; i < num_samples * num_values_per_sample; i++) { values[i] = i * 3; } // Create a session with an artifically small buffer size which will cause it to flush // automatically time_t start_time = rtc_get_time(); ProtobufLogConfig log_config = { .type = ProtobufLogType_Measurements, .measurements = { .num_types = num_values_per_sample, .types = types, } }; ProtobufLogRef session_ref = protobuf_log_create(&log_config, prv_protobuf_log_transport, 110); cl_assert(session_ref != NULL); // ------------------ // Keep adding samples, relying on auto-flush bool success; TestPLParsedMsg *msg; uint32_t num_samples_encoded = 0; for (unsigned i = 0; i < num_samples; i++) { rtc_set_time(start_time + offset_sec[i]); success = protobuf_log_session_add_measurements(session_ref, rtc_get_time(), num_values_per_sample, &values[i * num_values_per_sample]); cl_assert(success); if (s_saved_encoded_msg == NULL) { LOG("No message available yet..."); } else { msg = prv_parse_encoded_mset_payload(s_saved_encoded_msg); cl_assert_equal_m(msg->msrmt.values, &values[num_samples_encoded * num_values_per_sample], msg->msrmt.num_values * sizeof(uint32_t)); num_samples_encoded += msg->msrmt.num_samples; free(s_saved_encoded_msg); s_saved_encoded_msg = NULL; } } // Flush it out now, this should send any remaining data out success = protobuf_log_session_flush(session_ref); cl_assert(success); if (num_samples_encoded < num_samples) { if (s_saved_encoded_msg == NULL) { LOG("No message available yet..."); } else { msg = prv_parse_encoded_mset_payload(s_saved_encoded_msg); cl_assert_equal_m(msg->msrmt.values, &values[num_samples_encoded * num_values_per_sample], msg->msrmt.num_values * sizeof(uint32_t)); } } // Delete the session now protobuf_log_session_delete(session_ref); } // --------------------------------------------------------------------------------------------- // Test using the data logging transport void test_protobuf_log__measurements_with_data_logging(void) { ProtobufLogMeasurementType types[] = {ProtobufLogMeasurementType_Steps, ProtobufLogMeasurementType_BPM}; uint32_t offset_sec[] = {1, 2}; uint32_t values[] = {0x11, 0x22, 0x33, 0x44}; TestPLParsedMsg input = { .type = ProtobufLogType_Measurements, .msrmt = { .time_utc = rtc_get_time(), .utc_to_local = time_util_utc_to_local_offset(), .num_types = ARRAY_LENGTH(types), .types = types, .num_samples = ARRAY_LENGTH(offset_sec), .offset_sec = offset_sec, .num_values = ARRAY_LENGTH(values), .values = values, } }; ProtobufLogRef ref = prv_test_encode_measurements(&input, false /*use_data_logging*/); prv_test_decode_payload(&input, false /*use_data_logging*/, ref); } // --------------------------------------------------------------------------------------------- // Test using the data logging transport void test_protobuf_log__hr_samples(void) { ProtobufLogMeasurementType types[] = {ProtobufLogMeasurementType_BPM, ProtobufLogMeasurementType_HRQuality}; uint32_t offset_sec[] = {1, 2}; uint32_t values[] = {0x11, HRMQuality_Acceptable, 0x33, HRMQuality_Excellent}; TestPLParsedMsg input = { .type = ProtobufLogType_Measurements, .msrmt = { .time_utc = rtc_get_time(), .utc_to_local = time_util_utc_to_local_offset(), .num_types = 2, .types = types, .num_samples = ARRAY_LENGTH(offset_sec), .offset_sec = offset_sec, .num_values = ARRAY_LENGTH(values), .values = values, } }; prv_common_payload_initialize(&input); // Create a session ProtobufLogTransportCB transport_cb = prv_protobuf_log_transport; ProtobufLogRef session_ref = protobuf_log_hr_create(transport_cb); cl_assert(session_ref != NULL); const uint32_t values_per_samples = input.msrmt.num_types; bool success; for (unsigned i = 0; i < input.msrmt.num_samples; i++) { rtc_set_time(input.msrmt.time_utc + input.msrmt.offset_sec[i]); uint32_t *vals = &input.msrmt.values[i * values_per_samples]; success = protobuf_log_hr_add_sample(session_ref, rtc_get_time(), vals[0], // BPM vals[1]); // Quality cl_assert(success); } // Convert the values from HRMQuality to pebble_pipeline values for (unsigned i = 1; i < input.msrmt.num_values; i += 2) { values[i] = prv_hr_quality_int(values[i]); } prv_test_decode_payload(&input, false /*use_data_logging*/, session_ref); } // --------------------------------------------------------------------------------------------- // Test using the data logging transport void test_protobuf_log__events_basic(void) { pebble_pipeline_Event events[] = { { .type = pebble_pipeline_Event_Type_UnknownEvent, .duration = 17, .has_duration = true, .time_utc = rtc_get_time() - 3000, }, { .type = pebble_pipeline_Event_Type_UnknownEvent, .duration = 34, .has_duration = true, .time_utc = rtc_get_time() - 2000, } }; TestPLParsedMsg input = { .type = ProtobufLogType_Events, .events = { .num_events = ARRAY_LENGTH(events), .events = events, } }; prv_common_payload_initialize(&input); // Create a session ProtobufLogConfig log_config = { .type = ProtobufLogType_Events, }; ProtobufLogTransportCB transport_cb = prv_protobuf_log_transport; ProtobufLogRef session_ref = protobuf_log_create(&log_config, transport_cb, 0); cl_assert(session_ref != NULL); bool success; for (int i = 0; i < ARRAY_LENGTH(events); i++) { success = protobuf_log_session_add_event(session_ref, &events[i]); cl_assert(success); } prv_test_decode_payload(&input, false /*use_data_logging*/, session_ref); }