Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,512 @@
/*
* 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 "protobuf_log.h"
#include "protobuf_log_private.h"
#include "protobuf_log_util.h"
#include "applib/data_logging.h"
#include "drivers/rtc.h"
#include "kernel/pbl_malloc.h"
#include "mfg/mfg_serials.h"
#include "os/mutex.h"
#include "pb.h"
#include "pb_decode.h"
#include "pb_encode.h"
#include "services/normal/data_logging/data_logging_service.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/version.h"
#include "util/math.h"
#include "util/size.h"
#include "util/time/time.h"
#include "util/uuid.h"
#include <string.h>
// These headers auto-generated from the measurements.proto
#include "nanopb/common.pb.h"
#include "nanopb/event.pb.h"
#include "nanopb/measurements.pb.h"
#include "nanopb/payload.pb.h"
#define PROTOBUF_LOG_DEBUG(fmt, args...) \
PBL_LOG_D(LOG_DOMAIN_PROTOBUF, LOG_LEVEL_DEBUG, fmt, ## args)
#define MLOG_MAX_VARINT_ENCODED_SIZE 5
// Our globals
typedef struct PLogState {
PebbleMutex *mutex;
DataLoggingSession *dls_session;
} PLogState;
static PLogState s_plog_state;
// ---------------------------------------------------------------------------------------
// Get the data logging session. Creating it if not already created
static DataLoggingSession *prv_get_dls_session(void) {
if (s_plog_state.dls_session == NULL) {
const bool buffered = true;
const bool resume = false;
Uuid system_uuid = UUID_SYSTEM;
s_plog_state.dls_session = dls_create(DlsSystemTagProtobufLogSession,
DATA_LOGGING_BYTE_ARRAY, PLOG_DLS_RECORD_SIZE, buffered,
resume, &system_uuid);
if (!s_plog_state.dls_session) {
// This can happen when you are not connected to the phone and have rebooted a number of
// times because each time you reboot, you get new sessions created and reach the limit
// of the max # of sessions allowed.
PBL_LOG(LOG_LEVEL_WARNING, "Error creating activity logging session");
return NULL;
}
}
return s_plog_state.dls_session;
}
// ---------------------------------------------------------------------------------------------
// Our default transport, which sends the data over data logging
static bool prv_dls_transport(uint8_t *buffer, size_t buf_size) {
bool success = false;
mutex_lock(s_plog_state.mutex);
{
DataLoggingSession *dls_session = prv_get_dls_session();
if (!dls_session) {
goto unlock;
}
// Log the data now, padding with 0's
PBL_ASSERTN(buf_size <= PLOG_DLS_RECORD_SIZE);
memset(buffer + buf_size, 0, PLOG_DLS_RECORD_SIZE - buf_size);
DataLoggingResult result = dls_log(dls_session, buffer, 1);
if (result == DATA_LOGGING_SUCCESS) {
success = true;
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Error %d while logging data", (int)result);
}
}
unlock:
mutex_unlock(s_plog_state.mutex);
return success;
}
// -----------------------------------------------------------------------------------------
// Encode a struct `msg` with the field number and fields passed.
static bool prv_encode_struct(pb_ostream_t *stream, uint32_t field_number,
const pb_msgdesc_t * fields, const void *msg) {
// Encode the field tag and data type
if (!pb_encode_tag(stream, PB_WT_STRING, field_number)) {
return false;
}
return pb_encode_submessage(stream, fields, msg);
}
// -----------------------------------------------------------------------------------------
// Encode a payload containing the data blob passed in
static bool prv_populate_payload(ProtobufLogConfig *config, size_t buffer_len, uint8_t *buffer,
pb_ostream_t *stream) {
PLogBufferEncoderArg ms_encoder_arg = {
.len = buffer_len,
.buffer = buffer,
};
// Version and Patch
unsigned int v_major, v_minor;
const char *version_patch_ptr;
version_get_major_minor_patch(&v_major, &v_minor, &version_patch_ptr);
// Sender Id
const char *watch_serial = mfg_get_serial_number();
pebble_pipeline_Payload payload = {
.sender = {
.type = {
.funcs.encode = protobuf_log_util_encode_string,
.arg = (void *)PLOG_PAYLOAD_SENDER_TYPE,
},
.id = {
.funcs.encode = protobuf_log_util_encode_string,
.arg = (void *)watch_serial,
},
.has_version = true,
.version = {
.major = v_major,
.minor = v_minor,
.patch = {
.funcs.encode = protobuf_log_util_encode_string,
.arg = (void *)version_patch_ptr,
},
},
},
.send_time_utc = rtc_get_time(),
};
// NOTE: A Payload is the master protobuf struct that we send to the phone.
// Events have already been encoded for Payloads (in `protobuf_log_session_add_event`)
// MeasurementSets have not. They are currently only written to the stream as a self standing
// object, not for a payload.
// This results in encoding them a bit differently at the end.
// For Events, just write the exact buffer.
// For MeasurementSets, encode them for the Payload.
switch (config->type) {
case ProtobufLogType_Events:
pb_write(stream, ms_encoder_arg.buffer, ms_encoder_arg.len);
break;
case ProtobufLogType_Measurements:
payload.measurement_sets = (pb_callback_t) {
.funcs.encode = protobuf_log_util_encode_buffer,
.arg = &ms_encoder_arg,
};
break;
}
bool success = pb_encode(stream, pebble_pipeline_Payload_fields, &payload);
if (!success) {
PBL_LOG(LOG_LEVEL_ERROR, "Error encoding payload");
}
// PBL-43622: Will revert later
PBL_LOG(LOG_LEVEL_INFO, "Logged protobuf payload type: %d, utc:%"PRIu32, config->type,
payload.send_time_utc);
return success;
}
// -----------------------------------------------------------------------------------------
// Free all memory associated with a session
static void prv_session_free(PLogSession *session) {
kernel_free(session->msg_buffer);
kernel_free(session->data_buffer);
kernel_free(session);
}
bool protobuf_log_init(void) {
s_plog_state.mutex = mutex_create();
return true;
}
// ---------------------------------------------------------------------------------------
// How much space is needed in the allocation of the PLogSession (useful for storing extra data).
static size_t prv_session_extra_space_needed(const ProtobufLogConfig *config) {
switch (config->type) {
case ProtobufLogType_Measurements:
return (config->measurements.num_types * sizeof(ProtobufLogMeasurementType));
case ProtobufLogType_Events:
return 0;
}
WTF;
return 0;
}
// ---------------------------------------------------------------------------------------
// Starts/restarts a measurement session.
static bool prv_session_measurement_encode_start(PLogSession *session) {
const ProtobufLogConfig *config = &session->config;
// Set the types pointer to the space allocated right after the PLogSession
ProtobufLogMeasurementType *types_copy = (ProtobufLogMeasurementType *) (session + 1);
const size_t extra_space = prv_session_extra_space_needed(config);
// Copy the types array directly after the Session bytes.
memcpy(types_copy, config->measurements.types, extra_space);
// Generate a new UUID
Uuid uuid;
uuid_generate(&uuid);
PLogTypesEncoderArg types_encoder_arg = {
.num_types = session->config.measurements.num_types,
.types = session->config.measurements.types,
};
pebble_pipeline_MeasurementSet msg = {
.uuid = {
.funcs.encode = protobuf_log_util_encode_uuid,
.arg = &uuid,
},
.time_utc = session->start_utc,
.utc_to_local = time_util_utc_to_local_offset(),
.types = {
.funcs.encode = protobuf_log_util_encode_measurement_types,
.arg = &types_encoder_arg,
},
};
return pb_encode(&session->data_stream, pebble_pipeline_MeasurementSet_fields, &msg);
}
// ---------------------------------------------------------------------------------------
// Starts/restarts a session. Allows each type to setup what they need to setup.
static bool prv_session_encode_start(PLogSession *session) {
const ProtobufLogConfig *config = &session->config;
// New session start time
session->start_utc = rtc_get_time();
// Create a new stream, reserving space for the header in front
session->data_stream = pb_ostream_from_buffer(session->data_buffer, session->max_data_size);
switch (config->type) {
case ProtobufLogType_Measurements: {
return prv_session_measurement_encode_start(session);
}
case ProtobufLogType_Events:
return true;
}
WTF;
}
// ---------------------------------------------------------------------------------------
// Calculates how much space an empty Payload will consume in our buffer. Useful for seeing how
// much *other* data we can store in a fixed sized DataLogging packet
static uint32_t prv_get_hdr_reserved_size(ProtobufLogConfig *config) {
// Figure out how much space we need to reserve for the payload structure in each record
pb_ostream_t substream = PB_OSTREAM_SIZING;
// Encode a payload with a 0 length data blob.
bool success = prv_populate_payload(config, 0, NULL, &substream);
PBL_ASSERT(success, "error encoding payload");
// Save enough room for us to encode the length of the data buffer
return substream.bytes_written + MLOG_MAX_VARINT_ENCODED_SIZE;
}
ProtobufLogRef protobuf_log_create(ProtobufLogConfig *config,
ProtobufLogTransportCB transport,
size_t max_msg_size) {
// Error check the passed in max encoded message size
PBL_ASSERTN(max_msg_size <= PLOG_DLS_RECORD_SIZE);
if (max_msg_size == 0) {
max_msg_size = PLOG_DLS_RECORD_SIZE;
}
// Default transport
if (!transport) {
transport = prv_dls_transport;
}
// Create a buffer for the final fully-formed record. Since we send it out through data logging,
// make it the size of a data logging record
uint8_t *msg_buffer = kernel_zalloc(PLOG_DLS_RECORD_SIZE);
if (!msg_buffer) {
return NULL;
}
// Number of bytes that are needed to encode the payload structure
// (not including the data blob)
const uint32_t payload_hdr_size = prv_get_hdr_reserved_size(config);
PROTOBUF_LOG_DEBUG("Creating payload session with hdr size of %"PRIu32, payload_hdr_size);
// Create a buffer for the encoded data blob. We form this first as the caller calls
// protobuf_log_session_add_* repeatedly. Once it's filled up, we grab it as the
// data blob portion of the payload that's formed in msg_buffer.
uint32_t max_data_size = max_msg_size - payload_hdr_size - sizeof(PLogMessageHdr);
PROTOBUF_LOG_DEBUG("Max data buffer size: %"PRIu32, max_data_size);
uint8_t *data_buffer = kernel_zalloc(max_data_size);
if (!data_buffer) {
kernel_free(msg_buffer);
return NULL;
}
// Extra space needed for each config to store some variables and information.
// e.g. Measurement needs to store an array of types.
const size_t extra_size = prv_session_extra_space_needed(config);
PLogSession *session = kernel_zalloc(sizeof(PLogSession) + extra_size);
if (!session) {
kernel_free(msg_buffer);
kernel_free(data_buffer);
return NULL;
}
*session = (PLogSession) {
.config = *config,
.msg_buffer = msg_buffer,
.data_buffer = data_buffer,
.max_msg_size = max_msg_size,
.max_data_size = max_data_size,
.transport = transport,
};
// Start a new encoding
const bool success = prv_session_encode_start(session);
if (!success) {
PBL_LOG(LOG_LEVEL_ERROR, "Error encoding msg");
prv_session_free(session);
session = NULL;
}
return session;
}
// ---------------------------------------------------------------------------------------
// Sets the stream to PB_OSTREAM_SIZING and calculates the size of the protobuf structs
static uint32_t prv_get_encoded_struct_size(uint32_t field_number, const pb_msgdesc_t * fields,
const void *msg) {
pb_ostream_t stream = PB_OSTREAM_SIZING;
prv_encode_struct(&stream, field_number, fields, msg);
return stream.bytes_written;
}
// ---------------------------------------------------------------------------------------
// Takes a generic protobuf struct, calculates the size, and writes it out to the internal buffer.
// If it is full, flush then log it.
static bool prv_log_struct(PLogSession *session, uint32_t field_number,
const pb_msgdesc_t * fields, const void *msg) {
// Calculate the size of our struct encoded on wire
const uint32_t calc_size = prv_get_encoded_struct_size(field_number, fields, msg);
// Calculate our data blob buffer size if we add this struct to it
const uint32_t size_if_added = session->data_stream.bytes_written + calc_size;
// If it fits, add it. If it doesn't, flush first.
if (size_if_added > session->max_data_size) {
// We would be over capacity if we added this message. Let's flush first.
PROTOBUF_LOG_DEBUG("Session: 0x%x - Would have been over limit at size %"PRIu32", flushing",
(int)session, size_if_added);
protobuf_log_session_flush(session);
}
// Encode the struct into the message
bool success = prv_encode_struct(&session->data_stream, field_number, fields, msg);
if (!success) {
PBL_LOG(LOG_LEVEL_ERROR, "Error adding sample, resetting session");
return prv_session_encode_start(session);
}
return true;
}
bool protobuf_log_session_add_measurements(ProtobufLogRef session_ref, time_t sample_utc,
uint32_t num_values, uint32_t *values) {
PBL_ASSERTN(session_ref != NULL);
PLogSession *session = (PLogSession *)session_ref;
PBL_ASSERTN(session->config.type == ProtobufLogType_Measurements);
int32_t offset_sec = sample_utc - session->start_utc;
offset_sec = MAX(0, offset_sec);
// error check
PBL_ASSERT(num_values == session->config.measurements.num_types, "Wrong number of values passed");
PROTOBUF_LOG_DEBUG("Session: 0x%x - Adding measurement sample with %"PRIu32" values",
(int)session_ref, num_values);
// Encode the Measurement
PLogPackedVarintsEncoderArg packed_varint_encoder_arg = {
.num_values = num_values,
.values = values,
};
pebble_pipeline_Measurement msg = {
.offset_sec = offset_sec,
.data = {
.funcs.encode = protobuf_log_util_encode_packed_varints,
.arg = &packed_varint_encoder_arg,
},
};
bool success = prv_log_struct(session,
pebble_pipeline_MeasurementSet_measurements_tag,
pebble_pipeline_Measurement_fields,
&msg);
return success;
}
bool protobuf_log_session_add_event(ProtobufLogRef session_ref, pebble_pipeline_Event *event) {
PBL_ASSERTN(session_ref != NULL);
PLogSession *session = (PLogSession *)session_ref;
PBL_ASSERTN(session->config.type == ProtobufLogType_Events);
// Generate a new UUID
Uuid uuid;
uuid_generate(&uuid);
// Don't use {} notation because we won't want to overwrite the data that is already set in
// the event.
event->created_time_utc = rtc_get_time();
event->has_created_time_utc = true;
event->utc_to_local = time_util_utc_to_local_offset();
event->uuid = (pb_callback_t) {
.funcs.encode = protobuf_log_util_encode_uuid,
.arg = &uuid,
};
PROTOBUF_LOG_DEBUG("Session: 0x%x - Adding event with type: %d", (int)session_ref, event->type);
bool success = prv_log_struct(session,
pebble_pipeline_Payload_events_tag,
pebble_pipeline_Event_fields,
event);
return success;
}
bool protobuf_log_session_flush(ProtobufLogRef session_ref) {
PBL_ASSERTN(session_ref != NULL);
PLogSession *session = (PLogSession *)session_ref;
bool encode_success = false;
// Encode the buffer into a Payload
const size_t hdr_size = sizeof(PLogMessageHdr);
pb_ostream_t stream = pb_ostream_from_buffer(session->msg_buffer + hdr_size,
session->max_msg_size - hdr_size);
bool success = prv_populate_payload(&session->config, session->data_stream.bytes_written,
session->data_buffer, &stream);
if (!success) {
PBL_LOG(LOG_LEVEL_ERROR, "Error encoding payload");
goto exit;
}
// Fill in the message header now
PLogMessageHdr *hdr = (PLogMessageHdr *)session->msg_buffer;
*hdr = (PLogMessageHdr) {
.msg_size = stream.bytes_written,
};
// Send it out now
PROTOBUF_LOG_DEBUG("Session: 0x%x - Flushing %d bytes", (int)session_ref, hdr->msg_size);
success = (session->transport)(session->msg_buffer, hdr->msg_size + sizeof(PLogMessageHdr));
if (!success) {
PBL_LOG(LOG_LEVEL_ERROR, "Failure when sending encoded message, resetting session");
}
// TODO: Call a success callback so the clients know exactly which data has been sent.
// If we don't do this an we crash without pushing to datalogging, we'll lose data
exit:
encode_success = prv_session_encode_start(session);
return (success && encode_success);
}
bool protobuf_log_session_delete(ProtobufLogRef session_ref) {
PROTOBUF_LOG_DEBUG("Session: 0x%x - Deleting", (int)session_ref);
if (session_ref == NULL) {
return true;
}
protobuf_log_session_flush(session_ref);
prv_session_free(session_ref);
return true;
}

View file

@ -0,0 +1,156 @@
/*
* 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.
*/
#pragma once
//! This module handles the collection and sending of periodic protobuf payloads to the phone
//! using the protobuf schema defined at src/fw/idl/nanopb/*.proto and sent to the phone via
//! data logging.
#include "services/common/hrm/hrm_manager.h"
#include "services/normal/data_logging/dls_private.h"
#include "system/version.h"
#include "util/uuid.h"
#include <stdbool.h>
#include <stdint.h>
// Auto generated header produced by compiling the .proto files in src/fw/idl
#include "nanopb/measurements.pb.h"
#include "nanopb/event.pb.h"
// Create an alias typedef for the auto-generated name
typedef pebble_pipeline_MeasurementSet_Type ProtobufLogMeasurementType;
typedef pebble_pipeline_ActivityType_InternalType ProtobufLogActivityType;
#define ProtobufLogMeasurementType_TimeMS pebble_pipeline_MeasurementSet_Type_TimeMS
#define ProtobufLogMeasurementType_VMC pebble_pipeline_MeasurementSet_Type_VMC
#define ProtobufLogMeasurementType_Steps pebble_pipeline_MeasurementSet_Type_Steps
#define ProtobufLogMeasurementType_DistanceCM pebble_pipeline_MeasurementSet_Type_DistanceCM
#define ProtobufLogMeasurementType_RestingGCalories pebble_pipeline_MeasurementSet_Type_RestingGCalories
#define ProtobufLogMeasurementType_ActiveGCalories pebble_pipeline_MeasurementSet_Type_ActiveGCalories
#define ProtobufLogMeasurementType_BPM pebble_pipeline_MeasurementSet_Type_BPM
#define ProtobufLogMeasurementType_RR pebble_pipeline_MeasurementSet_Type_RR
#define ProtobufLogMeasurementType_Orientation pebble_pipeline_MeasurementSet_Type_Orientation
#define ProtobufLogMeasurementType_Light pebble_pipeline_MeasurementSet_Type_Light
#define ProtobufLogMeasurementType_Temperature pebble_pipeline_MeasurementSet_Type_Temperature
#define ProtobufLogMeasurementType_HRQuality pebble_pipeline_MeasurementSet_Type_HRQuality
#define ProtobufLogActivityType_UnknownType pebble_pipeline_ActivityType_InternalType_UnknownType
#define ProtobufLogActivityType_Sleep pebble_pipeline_ActivityType_InternalType_Sleep
#define ProtobufLogActivityType_DeepSleep pebble_pipeline_ActivityType_InternalType_DeepSleep
#define ProtobufLogActivityType_Nap pebble_pipeline_ActivityType_InternalType_Nap
#define ProtobufLogActivityType_DeepNap pebble_pipeline_ActivityType_InternalType_DeepNap
#define ProtobufLogActivityType_Walk pebble_pipeline_ActivityType_InternalType_Walk
#define ProtobufLogActivityType_Run pebble_pipeline_ActivityType_InternalType_Run
#define ProtobufLogActivityType_Open pebble_pipeline_ActivityType_InternalType_Open
#define PLOG_MAX_SENDER_ID_LEN 64
#define PLOG_MAX_SENDER_TYPE_LEN 64
#define PLOG_MAX_SENDER_VERSION_PATCH_LEN FW_METADATA_VERSION_TAG_BYTES
#define PLOG_PAYLOAD_SENDER_TYPE "watch"
// Size of the data logging records we use
#define PLOG_DLS_RECORD_SIZE DLS_SESSION_MAX_BUFFERED_ITEM_SIZE
// Currently supported Payload types
//
// MEASUREMENTS
// - Used today for logging HR bpm and quality for each sample
// - Can also be used for logging minute level data with steps, lights, orientation, etc.
// - How to use:
// - Create a `ProtobufLogConfig` struct with type `ProtobufLogType_Measurements`, the number of
// types each sample will contain and the array of types.
// - Call `protobuf_log_create` with the config.
// - Call `protobuf_log_session_add_measurements` repeatedly with new samples, each containing
// the same number of measurements, the number that as set in the `ProtobufLogConfig`.
// EVENTS
// - Used today for logging ActivitySession events
// - How to use:
// - Create a `ProtobufLogConfig` struct with type `ProtobufLogType_Events`.
// - Call `protobuf_log_create` with the config.
// - Call `protobuf_log_add_event` repeatedly with new pebble_pipeline_Event's.
typedef enum ProtobufLogType {
ProtobufLogType_Measurements,
ProtobufLogType_Events,
} ProtobufLogType;
typedef struct ProtobufLogConfig {
ProtobufLogType type;
union {
struct {
uint8_t num_types; // number of readings in each measurement
ProtobufLogMeasurementType *types; // Array of measurement types.
} measurements;
struct {
// empty for now
} events;
};
} ProtobufLogConfig;
// Handle returned when a new protobuf log session is created
typedef void *ProtobufLogRef;
// Signature of the transport callback that can be optionally provided to protobuf_log_create()
typedef bool (*ProtobufLogTransportCB)(uint8_t *buffer, size_t buf_size);
// Init the service
// @return true if successful
bool protobuf_log_init(void);
// Create a new protobuf log session.
// @param[in] config `ProtobufLogConfig` for the type of protobuf log session to create
// @param[in] transport optional callback that will be used to send the encoded data out. If
// NULL, the data will be sent over data logging by default
// @param[in] max_msg_size optional max message size. This should almost always be 0 so that
// a default size is used that fills the data logging record as fully as possible. Non-zero
// values are mostly used for unit tests.
// @return new session pointer, or NULL if error occurred
ProtobufLogRef protobuf_log_create(ProtobufLogConfig *config,
ProtobufLogTransportCB transport,
size_t max_encoded_msg_size);
// Add a new measurement sample to the session. Once the amount of accumulated measurement data
// gets large enough, it will be automatically encoded and sent out.
// @param[in] session created by protobuf_log_create
// @param[in] sample_utc the UTC timestamp of this sample
// @param[in] num_values the number of values in the 'values' array. This must match
// the 'num_types' value that was passed to protobuf_log_create() when the session was created
// @param[in] values array of measurement values for this sample
// @return true on success, false on failure
bool protobuf_log_session_add_measurements(ProtobufLogRef session, time_t sample_utc,
uint32_t num_values, uint32_t *values);
// Add a new `pebble_pipeline_Event` to the session. Once the amount of accumulated event data
// gets large enough, it will be automatically encoded and sent out.
// @param[in] session created by protobuf_log_create
// @param[in] event the event to be added
// @return true on success, false on failure
bool protobuf_log_session_add_event(ProtobufLogRef session_ref, pebble_pipeline_Event *event);
// Immediately encode and send all payload data accumulated so far.
// @param[in] session created by protobuf_log_create
// @return true on success, false on failure
bool protobuf_log_session_flush(ProtobufLogRef session);
// Delete a session. This will first issue a protobuf_log_session_flush before deleting the
// session.
// @param[in] session created by protobuf_log_create
// @return true on success, false on failure
bool protobuf_log_session_delete(ProtobufLogRef session);

View file

@ -0,0 +1,151 @@
/*
* 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 "protobuf_log_activity_sessions.h"
#include "protobuf_log.h"
#include "protobuf_log_private.h"
#include "protobuf_log_util.h"
#include "services/common/hrm/hrm_manager.h"
#include "services/normal/activity/activity.h"
#include "nanopb/event.pb.h"
#include "system/passert.h"
#include <util/size.h>
#include <stdint.h>
#include <stdbool.h>
// -----------------------------------------------------------------------------------------
// Convert ActivitySessionType to the internal protobuf representation.
static ActivitySessionType prv_proto_type_to_activity_type(ProtobufLogActivityType type) {
switch (type) {
case ProtobufLogActivityType_UnknownType:
return ActivitySessionType_None;
case ProtobufLogActivityType_Sleep:
return ActivitySessionType_Sleep;
case ProtobufLogActivityType_DeepSleep:
return ActivitySessionType_RestfulSleep;
case ProtobufLogActivityType_Nap:
return ActivitySessionType_Nap;
case ProtobufLogActivityType_DeepNap:
return ActivitySessionType_RestfulNap;
case ProtobufLogActivityType_Walk:
return ActivitySessionType_Walk;
case ProtobufLogActivityType_Run:
return ActivitySessionType_Run;
case ProtobufLogActivityType_Open:
return ActivitySessionType_Open;
}
WTF;
}
// -----------------------------------------------------------------------------------------
// Convert ActivitySessionType to the internal protobuf representation.
static ProtobufLogActivityType prv_activity_type_to_proto_type(ActivitySessionType type) {
switch (type) {
case ActivitySessionType_None:
return ProtobufLogActivityType_UnknownType;
case ActivitySessionType_Sleep:
return ProtobufLogActivityType_Sleep;
case ActivitySessionType_RestfulSleep:
return ProtobufLogActivityType_DeepSleep;
case ActivitySessionType_Nap:
return ProtobufLogActivityType_Nap;
case ActivitySessionType_RestfulNap:
return ProtobufLogActivityType_DeepNap;
case ActivitySessionType_Walk:
return ProtobufLogActivityType_Walk;
case ActivitySessionType_Run:
return ProtobufLogActivityType_Run;
case ActivitySessionType_Open:
return ProtobufLogActivityType_Open;
case ActivitySessionTypeCount:
break;
}
WTF;
}
// -----------------------------------------------------------------------------------------
// Callback used to stuff in the sender.type field of a payload
// TODO: Don't force it to be one interval
static bool prv_encode_intervals(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
if (!pb_encode_tag(stream, PB_WT_STRING, pebble_pipeline_ActivitySession_intervals_tag)) {
return false;
}
const ActivitySession *session = *arg;
pebble_pipeline_ActivityInterval msg = {
.offset_sec = 0,
.duration_sec = session->length_min
};
return pb_encode_submessage(stream, pebble_pipeline_ActivityInterval_fields, &msg);
}
ProtobufLogRef protobuf_log_activity_sessions_create(void) {
ProtobufLogConfig log_config = {
.type = ProtobufLogType_Events,
.events = {}
};
return protobuf_log_create(&log_config, NULL /*transport*/, 0 /*max_encoded_msg_size*/);
}
// TODO: Actually make sense of this. It is completely wrong.
bool protobuf_log_activity_sessions_add(ProtobufLogRef ref, time_t sample_utc,
ActivitySession *session) {
pebble_pipeline_Event event = {
.type = pebble_pipeline_Event_Type_ActivitySessionEvent,
.created_time_utc = sample_utc,
.duration = session->length_min,
.time_utc = session->start_utc,
.activity_session = {
.type = {
// TODO: Custom types
.which_type = pebble_pipeline_ActivityType_internal_type_tag,
.type = {
.internal_type = prv_activity_type_to_proto_type(session->type),
}
},
.start_reason = (session->manual) ? pebble_pipeline_ActivitySession_StartReason_Manual
: pebble_pipeline_ActivitySession_StartReason_Automatic,
.intervals = {
.funcs.encode = prv_encode_intervals,
.arg = session,
}
}
};
return true;
}
// TODO: Also make this exactly to spec
bool protobuf_log_activity_sessions_decode(pebble_pipeline_Event *event_in,
ActivitySession *session_out) {
pebble_pipeline_ActivitySession *activity = &event_in->activity_session;
*session_out = (ActivitySession) {
.start_utc = event_in->time_utc,
.type = prv_proto_type_to_activity_type(activity->type.type.internal_type),
.length_min = event_in->duration,
.manual = (activity->start_reason == pebble_pipeline_ActivitySession_StartReason_Manual),
};
return true;
}

View file

@ -0,0 +1,33 @@
/*
* 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.
*/
#pragma once
#include "protobuf_log.h"
#include "services/common/hrm/hrm_manager.h"
#include "services/normal/activity/activity.h"
#include <stdbool.h>
#include <stdint.h>
ProtobufLogRef protobuf_log_activity_sessions_create(void);
bool protobuf_log_activity_sessions_add(ProtobufLogRef ref, time_t sample_utc,
ActivitySession *session);
bool protobuf_log_activity_sessions_decode(pebble_pipeline_Event *event_in,
ActivitySession *session_out);

View file

@ -0,0 +1,77 @@
/*
* 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 "protobuf_log_hr.h"
#include "protobuf_log.h"
#include "services/common/hrm/hrm_manager.h"
#include "nanopb/measurements.pb.h"
#include "system/passert.h"
#include <util/size.h>
#include <stdint.h>
#include <stdbool.h>
// -----------------------------------------------------------------------------------------
// Convert HRMQuality to the internal protobuf representation.
T_STATIC uint32_t prv_hr_quality_int(HRMQuality quality) {
switch (quality) {
case HRMQuality_NoAccel:
return pebble_pipeline_MeasurementSet_HeartRateQuality_NoAccel;
case HRMQuality_OffWrist:
return pebble_pipeline_MeasurementSet_HeartRateQuality_OffWrist;
case HRMQuality_NoSignal:
return pebble_pipeline_MeasurementSet_HeartRateQuality_NoSignal;
case HRMQuality_Worst:
return pebble_pipeline_MeasurementSet_HeartRateQuality_Worst;
case HRMQuality_Poor:
return pebble_pipeline_MeasurementSet_HeartRateQuality_Poor;
case HRMQuality_Acceptable:
return pebble_pipeline_MeasurementSet_HeartRateQuality_Acceptable;
case HRMQuality_Good:
return pebble_pipeline_MeasurementSet_HeartRateQuality_Good;
case HRMQuality_Excellent:
return pebble_pipeline_MeasurementSet_HeartRateQuality_Excellent;
}
WTF; // Should never get here
return 0;
}
ProtobufLogRef protobuf_log_hr_create(ProtobufLogTransportCB transport) {
// Create a measure log session, which we use to send heart rate readings to the phone
ProtobufLogMeasurementType measure_types[] = {
ProtobufLogMeasurementType_BPM,
ProtobufLogMeasurementType_HRQuality,
};
ProtobufLogConfig log_config = {
.type = ProtobufLogType_Measurements,
.measurements = {
.types = measure_types,
.num_types = ARRAY_LENGTH(measure_types),
},
};
return protobuf_log_create(&log_config, transport, 0 /*max_encoded_msg_size*/);
}
bool protobuf_log_hr_add_sample(ProtobufLogRef ref, time_t sample_utc, uint8_t bpm,
HRMQuality quality) {
uint32_t values[] = {bpm, prv_hr_quality_int(quality)};
return protobuf_log_session_add_measurements(ref, sample_utc, ARRAY_LENGTH(values), values);
}

View file

@ -0,0 +1,30 @@
/*
* 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.
*/
#pragma once
#include "protobuf_log.h"
#include "services/common/hrm/hrm_manager.h"
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
ProtobufLogRef protobuf_log_hr_create(ProtobufLogTransportCB transport);
bool protobuf_log_hr_add_sample(ProtobufLogRef ref, time_t sample_utc, uint8_t bpm,
HRMQuality quality);

View file

@ -0,0 +1,49 @@
/*
* 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.
*/
#pragma once
#include "pb.h"
#include "pb_decode.h"
#include "pb_encode.h"
#include "protobuf_log.h"
#include "util/attributes.h"
#include <stdint.h>
// This fixed size header is placed at the beginning of the buffer, before the protobuf
// encoded message
typedef struct PACKED {
uint16_t msg_size;
} PLogMessageHdr;
// Internal structure of a protobuf log session
typedef struct PLogSession {
// TODO Change comments from MeasurementSet to MeasurementSet/Events
ProtobufLogConfig config;
uint8_t *msg_buffer; // allocated buffer for the final record: PLogMessageHdr + Payload
uint8_t *data_buffer; // allocated buffer for the encoded data blob. (e.g. MeasurementSet)
// We form the MeasurementSet first, and then after it's complete we
// copy it into msg_buffer inside of a Payload
size_t max_msg_size; // max # of bytes to use in the allocated buffer
size_t max_data_size; // max allowed size of the encoded data blob
pb_ostream_t data_stream; // output stream we are writing the data blob to
time_t start_utc; // UTC time when session was created
ProtobufLogTransportCB transport;
} PLogSession;

View file

@ -0,0 +1,386 @@
/*
* 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 "protobuf_log_test.h"
#include "protobuf_log.h"
#include "protobuf_log_private.h"
#include "protobuf_log_activity_sessions.h"
#include "pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
#include "nanopb/payload.pb.h"
#include "nanopb/measurements.pb.h"
#include "system/passert.h"
#include <util/uuid.h>
// -----------------------------------------------------------------------------------------
// Callback used to decode uuid
static bool prv_decode_uuid(pb_istream_t *stream, const pb_field_t *field, void **arg) {
Uuid *ret_uuid = *(Uuid **)arg;
size_t uuid_len = stream->bytes_left;
PBL_ASSERTN(uuid_len == sizeof(Uuid));
return pb_read(stream, (uint8_t *)ret_uuid, uuid_len);
}
// -----------------------------------------------------------------------------------------
// Callback used to decode ActivitySession
static bool prv_decode_activity_session(pb_istream_t *stream, const pb_field_t *field, void **arg) {
ActivitySession *ret_uuid = *(ActivitySession **)arg;
// TODO: Do something here
return true;
}
// -----------------------------------------------------------------------------------------
// Callback used to decode types
typedef struct PLogTypesDecoderArg {
uint32_t max_num_types;
uint32_t *num_types;
ProtobufLogMeasurementType *types;
} PLogTypesDecoderArg;
static bool prv_decode_types(pb_istream_t *stream, const pb_field_t *field, void **arg) {
PLogTypesDecoderArg *decoder_info = *(PLogTypesDecoderArg **)arg;
uint64_t value;
bool success = pb_decode_varint(stream, &value);
if (*decoder_info->num_types < decoder_info->max_num_types) {
decoder_info->types[(*decoder_info->num_types)++] = value;
}
return success;
}
typedef struct PLogMeasurementsDecoderArg {
uint32_t max_num_samples;
uint32_t *num_samples;
uint32_t *offset_sec;
uint32_t max_num_values;
uint32_t *num_values;
uint32_t *values;
} PLogMeasurementsDecoderArg;
// -----------------------------------------------------------------------------------------
// Callback used to decode the packed data in a measurement
static bool prv_decode_packed_measurement_data(pb_istream_t *stream, const pb_field_t *field,
void **arg) {
PLogMeasurementsDecoderArg *decoder_info = *(PLogMeasurementsDecoderArg **)arg;
while (stream->bytes_left) {
uint64_t value;
if (!pb_decode_varint(stream, &value)) {
return false;
}
if (*decoder_info->num_values < decoder_info->max_num_values) {
decoder_info->values[(*decoder_info->num_values)++] = value;
}
}
return true;
}
// -----------------------------------------------------------------------------------------
// Callback used to decode measurements. Called once for each measurement
static bool prv_decode_measurements(pb_istream_t *stream, const pb_field_t *field, void **arg) {
PLogMeasurementsDecoderArg *decoder_info = *(PLogMeasurementsDecoderArg **)arg;
pebble_pipeline_Measurement msg = {
.data = {
.funcs.decode = prv_decode_packed_measurement_data,
.arg = decoder_info,
}
};
if (!pb_decode(stream, pebble_pipeline_Measurement_fields, &msg)) {
return false;
}
if (*decoder_info->num_samples < decoder_info->max_num_samples) {
decoder_info->offset_sec[(*decoder_info->num_samples)++] = msg.offset_sec;
}
return true;
}
typedef struct PLogMeasurementSetDecoderArg {
Uuid *uuid;
PLogTypesDecoderArg *types_decoder_arg;
PLogMeasurementsDecoderArg *measurements_decoder_arg;
uint32_t *time_utc;
uint32_t *time_end_utc;
int32_t *utc_to_local;
} PLogMeasurementSetDecoderArg;
// -----------------------------------------------------------------------------------------
// Callback used to decode a MeasurementSet. Called once for each MeasurementSet
static bool prv_decode_measurement_set(pb_istream_t *stream, const pb_field_t *field, void **arg) {
PLogMeasurementSetDecoderArg *decoder_info = *(PLogMeasurementSetDecoderArg **)arg;
pebble_pipeline_MeasurementSet mset = {
.uuid = {
.funcs.decode = prv_decode_uuid,
.arg = decoder_info->uuid,
},
.types = {
.funcs.decode = prv_decode_types,
.arg = decoder_info->types_decoder_arg,
},
.measurements = {
.funcs.decode = prv_decode_measurements,
.arg = decoder_info->measurements_decoder_arg,
},
};
bool success = pb_decode(stream, pebble_pipeline_MeasurementSet_fields, &mset);
*decoder_info->time_utc = mset.time_utc;
*decoder_info->time_end_utc = mset.time_end_utc;
*decoder_info->utc_to_local = mset.utc_to_local;
return success;
}
typedef struct PLogEventsDecoderArg {
uint32_t max_num_events;
uint32_t *num_events;
pebble_pipeline_Event *events;
Uuid *event_uuids;
uint32_t max_num_sessions;
uint32_t *num_sessions;
ActivitySession *sessions;
} PLogEventsDecoderArg;
// -----------------------------------------------------------------------------------------
// Callback used to decode a pebble_pipeline_Event. Called once for each Event
static bool prv_decode_events(pb_istream_t *stream, const pb_field_t *field, void **arg) {
PLogEventsDecoderArg *decoder_info = *(PLogEventsDecoderArg **)arg;
const uint32_t event_idx = *decoder_info->num_events;
bool success;
pebble_pipeline_Event event = {
.uuid = {
.funcs.decode = prv_decode_uuid,
.arg = &decoder_info->event_uuids[event_idx],
},
};
success = pb_decode(stream, pebble_pipeline_Event_fields, &event);
if (event.type == pebble_pipeline_Event_Type_ActivitySessionEvent) {
protobuf_log_activity_sessions_decode(&event, &decoder_info->sessions[event_idx]);
(*decoder_info->num_sessions)++;
}
if (success) {
decoder_info->events[event_idx] = event;
(*decoder_info->num_events)++;
}
return true;
}
// -----------------------------------------------------------------------------------------
// Callback used to decode the payload sender type
static bool prv_decode_sender_type(pb_istream_t *stream, const pb_field_t *field, void **arg) {
char *ret_sender_type = *(char **)arg;
size_t str_len = stream->bytes_left;
PBL_ASSERTN(str_len <= PLOG_MAX_SENDER_TYPE_LEN);
return pb_read(stream, (uint8_t *)ret_sender_type, str_len);
}
// -----------------------------------------------------------------------------------------
// Callback used to decode the payload sender id
static bool prv_decode_sender_id(pb_istream_t *stream, const pb_field_t *field, void **arg) {
char *ret_sender_id = *(char **)arg;
size_t str_len = stream->bytes_left;
PBL_ASSERTN(str_len <= PLOG_MAX_SENDER_ID_LEN);
return pb_read(stream, (uint8_t *)ret_sender_id, str_len);
}
// -----------------------------------------------------------------------------------------
// Callback used to decode the payload sender version patch
static bool prv_decode_sender_version_patch(pb_istream_t *stream, const pb_field_t *field,
void **arg) {
char *ret_sender_version_patch = *(char **)arg;
size_t str_len = stream->bytes_left;
PBL_ASSERTN(str_len <= PLOG_MAX_SENDER_VERSION_PATCH_LEN);
return pb_read(stream, (uint8_t *)ret_sender_version_patch, str_len);
}
// ---------------------------------------------------------------------------------------------
// Decode an encoded message. Used for debugging and unit tests.
bool protobuf_log_private_mset_decode(ProtobufLogType *type,
void *encoded_buf,
uint32_t encoded_buf_size,
char payload_sender_type[PLOG_MAX_SENDER_TYPE_LEN],
char payload_sender_id[PLOG_MAX_SENDER_ID_LEN],
char payload_sender_version_patch[FW_METADATA_VERSION_TAG_BYTES],
uint32_t *payload_send_time,
uint32_t *payload_sender_v_major,
uint32_t *payload_sender_v_minor,
Uuid *uuid,
uint32_t *time_utc,
uint32_t *time_end_utc,
int32_t *utc_to_local,
uint32_t *num_types,
ProtobufLogMeasurementType *types,
uint32_t *num_samples,
uint32_t *offset_sec,
uint32_t *num_values,
uint32_t *values) {
pb_istream_t stream = pb_istream_from_buffer(encoded_buf, encoded_buf_size);
const uint32_t max_num_types = *num_types;
const uint32_t max_num_values = *num_values;
const uint32_t max_num_samples = *num_samples;
*num_values = 0;
*num_samples = 0;
*num_types = 0;
PLogTypesDecoderArg types_decoder_arg = {
.max_num_types = max_num_types,
.num_types = num_types,
.types = types,
};
PLogMeasurementsDecoderArg measurements_decoder_arg = {
.max_num_samples = max_num_samples,
.num_samples = num_samples,
.offset_sec = offset_sec,
.max_num_values = max_num_values,
.num_values = num_values,
.values = values,
};
PLogMeasurementSetDecoderArg mset_decoder_arg = {
.uuid = uuid,
.types_decoder_arg = &types_decoder_arg,
.measurements_decoder_arg = &measurements_decoder_arg,
.time_utc = time_utc,
.time_end_utc = time_end_utc,
.utc_to_local = utc_to_local,
};
pebble_pipeline_Payload payload = {
.sender = {
.type = {
.funcs.decode = prv_decode_sender_type,
.arg = payload_sender_type,
},
.id = {
.funcs.decode = prv_decode_sender_id,
.arg = payload_sender_id,
},
.version = {
.patch = {
.funcs.decode = prv_decode_sender_version_patch,
.arg = payload_sender_version_patch,
},
},
},
.measurement_sets = {
.funcs.decode = prv_decode_measurement_set,
.arg = &mset_decoder_arg,
}
};
bool success = pb_decode(&stream, pebble_pipeline_Payload_fields, &payload);
*payload_send_time = payload.send_time_utc;
if (payload.sender.has_version && payload_sender_v_major && payload_sender_v_minor) {
*payload_sender_v_major = payload.sender.version.major;
*payload_sender_v_minor = payload.sender.version.minor;
}
*type = ProtobufLogType_Measurements;
return success;
}
// ---------------------------------------------------------------------------------------------
bool protobuf_log_private_events_decode(ProtobufLogType *type,
void *encoded_buf,
uint32_t encoded_buf_size,
char payload_sender_type[PLOG_MAX_SENDER_TYPE_LEN],
char payload_sender_id[PLOG_MAX_SENDER_ID_LEN],
char payload_sender_version_patch[FW_METADATA_VERSION_TAG_BYTES],
uint32_t *payload_send_time,
uint32_t *payload_sender_v_major,
uint32_t *payload_sender_v_minor,
uint32_t *num_events,
pebble_pipeline_Event *events,
Uuid *event_uuids,
uint32_t *num_sessions,
ActivitySession *sessions) {
pb_istream_t stream = pb_istream_from_buffer(encoded_buf, encoded_buf_size);
const uint32_t max_num_events = *num_events;
const uint32_t max_num_sessions = *num_sessions;
*num_events = 0;
*num_sessions = 0;
PLogEventsDecoderArg event_arg = {
.max_num_events = max_num_events,
.num_events = num_events,
.events = events,
.event_uuids = event_uuids,
.max_num_sessions = max_num_sessions,
.num_sessions = num_sessions,
.sessions = sessions,
};
pebble_pipeline_Payload payload = {
.sender = {
.type = {
.funcs.decode = prv_decode_sender_type,
.arg = payload_sender_type,
},
.id = {
.funcs.decode = prv_decode_sender_id,
.arg = payload_sender_id,
},
.version = {
.patch = {
.funcs.decode = prv_decode_sender_version_patch,
.arg = payload_sender_version_patch,
},
},
},
.events = {
.funcs.decode = prv_decode_events,
.arg = &event_arg
}
};
bool success = pb_decode(&stream, pebble_pipeline_Payload_fields, &payload);
*payload_send_time = payload.send_time_utc;
if (payload.sender.has_version && payload_sender_v_major && payload_sender_v_minor) {
*payload_sender_v_major = payload.sender.version.major;
*payload_sender_v_minor = payload.sender.version.minor;
}
*type = ProtobufLogType_Events;
return success;
}

View file

@ -0,0 +1,104 @@
/*
* 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.
*/
#pragma once
#include "protobuf_log.h"
#include "services/normal/activity/activity.h"
#include <stdint.h>
// ---------------------------------------------------------------------------------------------
// Decode an encoded payload with measurementsets. Used for debugging and unit tests.
// @param[in] encoded_buf pointer to encoded data
// @param[in] encoded_buf_size size of encoded data
// @param[out] payload_sender_type returned payload sender type string
// @param[out] payload_sender_id returned payload sender id string
// @param[out] payload_sender_version_patch returned payload sender version patch string
// @param[out] payload_send_time returned payload send time
// @param[out] payload_sender_v_major returned payload sender version major digit
// @param[out] payload_sender_v_minor returned payload sender version minor digit
// @param[out] uuid returned UUID field
// @param[out] time_utc returned time_utc field
// @param[out] time_end_utc returned time_end_utc field
// @param[out] utc_to_local returned utc_to_local field
// @param[in:out] num_types on entry, length of the types array; on exit, returned number
// of measurement types in the types array
// @param[out] types returned array of measurement types
// @param[in:out] num_samples on entry, length of the offset_sec array; on exit, returned
// number of values in the offset_sec array
// @param[out] offset_sec returned offset_sec value for each measurement
// @param[in|out] num_values on entry, number of entries available in the values array; On exit,
// the actual number of values written to the values array
// @param[out] values returned measurement values here. This will contain
// num_types * num_measurements values if num_values on entry was large enough
// @param[out]
// @return true on success, false on failure
bool protobuf_log_private_mset_decode(ProtobufLogType *type,
void *encoded_buf,
uint32_t encoded_buf_size,
char payload_sender_type[PLOG_MAX_SENDER_TYPE_LEN],
char payload_sender_id[PLOG_MAX_SENDER_ID_LEN],
char payload_sender_version_patch[FW_METADATA_VERSION_TAG_BYTES],
uint32_t *payload_send_time,
uint32_t *payload_sender_v_major,
uint32_t *payload_sender_v_minor,
Uuid *uuid,
uint32_t *time_utc,
uint32_t *time_end_utc,
int32_t *utc_to_local,
uint32_t *num_types,
ProtobufLogMeasurementType *types,
uint32_t *num_samples,
uint32_t *offset_sec,
uint32_t *num_values,
uint32_t *values);
// ---------------------------------------------------------------------------------------------
// Decode an encoded payload with events. Used for debugging and unit tests.
// @param[in] encoded_buf pointer to encoded data
// @param[in] encoded_buf_size size of encoded data
// @param[out] payload_sender_type returned payload sender type string
// @param[out] payload_sender_id returned payload sender id string
// @param[out] payload_sender_version_patch returned payload sender version patch string
// @param[out] payload_send_time returned payload send time
// @param[out] payload_sender_v_major returned payload sender version major digit
// @param[out] payload_sender_v_minor returned payload sender version minor digit
// @param[out] num_events on entry, length of events array; on exit, returned number
// of events in the events array
// @param[out] events array of events
// @param[out] event_uuids array of uuids for events
// @param[out] num_sessions on entry, length of sessions array; on exit, returned number
// of sessions in the sessions array
// @param[out] sessions array of ActivitySessions for events. Indexed by events.
// e.g. If there are three events and the first two are Unknown events and the third is an
// of type ActivitySession, then it's activity session will be at sessions[2].
// @param[out]
// @return true on success, false on failure
bool protobuf_log_private_events_decode(ProtobufLogType *type,
void *encoded_buf,
uint32_t encoded_buf_size,
char payload_sender_type[PLOG_MAX_SENDER_TYPE_LEN],
char payload_sender_id[PLOG_MAX_SENDER_ID_LEN],
char payload_sender_version_patch[FW_METADATA_VERSION_TAG_BYTES],
uint32_t *payload_send_time,
uint32_t *payload_sender_v_major,
uint32_t *payload_sender_v_minor,
uint32_t *num_events,
pebble_pipeline_Event *events,
Uuid *event_uuids,
uint32_t *num_sessions,
ActivitySession *sessions);

View file

@ -0,0 +1,105 @@
/*
* 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 "protobuf_log.h"
#include "protobuf_log_util.h"
#include "pb_encode.h"
#include <util/uuid.h>
bool protobuf_log_util_encode_uuid(pb_ostream_t *stream, const pb_field_t *field,
void * const *arg) {
if (!pb_encode_tag_for_field(stream, field)) {
return false;
}
Uuid *uuid_p = *(Uuid **)arg;
return pb_encode_string(stream, (uint8_t *)uuid_p, sizeof(Uuid));
}
bool protobuf_log_util_encode_string(pb_ostream_t *stream, const pb_field_t *field,
void * const *arg) {
if (!pb_encode_tag_for_field(stream, field)) {
return false;
}
const char *str = *(char **)arg;
return pb_encode_string(stream, (uint8_t *)str, strlen(str));
}
bool protobuf_log_util_encode_packed_varints(pb_ostream_t *stream, const pb_field_t *field,
void * const *arg) {
PLogPackedVarintsEncoderArg *encoder_arg = *(PLogPackedVarintsEncoderArg **)arg;
// We need to figure out the size of the packed array of varints first
pb_ostream_t substream = PB_OSTREAM_SIZING;
for (unsigned i = 0; i < encoder_arg->num_values; i++) {
if (!pb_encode_varint(&substream, encoder_arg->values[i])) {
stream->errmsg = substream.errmsg;
return false;
}
}
size_t packed_array_size = substream.bytes_written;
// Encode the tag and wiretype
if (!pb_encode_tag(stream, PB_WT_STRING, field->tag)) {
return false;
}
// Encode the size
if (!pb_encode_varint(stream, packed_array_size)) {
return false;
}
// if just being called to size it up, the stream callback will be NULL
if (!stream->callback) {
return pb_write(stream, NULL, packed_array_size);
}
// Write out each of the values
for (unsigned i = 0; i < encoder_arg->num_values; i++) {
if (!pb_encode_varint(stream, encoder_arg->values[i])) {
return false;
}
}
return true;
}
bool protobuf_log_util_encode_measurement_types(pb_ostream_t *stream, const pb_field_t *field,
void * const *arg) {
PLogTypesEncoderArg *encoder_arg = *(PLogTypesEncoderArg **)arg;
for (unsigned i = 0; i < encoder_arg->num_types; i++) {
if (!pb_encode_tag_for_field(stream, field)) {
return false;
}
if (!pb_encode_varint(stream, encoder_arg->types[i])) {
return false;
}
}
return true;
}
bool protobuf_log_util_encode_buffer(pb_ostream_t *stream, const pb_field_t *field,
void * const *arg) {
PLogBufferEncoderArg *encoder_arg = *(PLogBufferEncoderArg **)arg;
if (!pb_encode_tag_for_field(stream, field)) {
return false;
}
return pb_encode_string(stream, encoder_arg->buffer, encoder_arg->len);
}

View file

@ -0,0 +1,59 @@
/*
* 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.
*/
#pragma once
#include "pb.h"
typedef struct PLogPackedVarintsEncoderArg {
uint16_t num_values;
uint32_t *values;
} PLogPackedVarintsEncoderArg;
typedef struct PLogTypesEncoderArg {
uint16_t num_types;
ProtobufLogMeasurementType *types;
} PLogTypesEncoderArg;
typedef struct PLogBufferEncoderArg {
uint16_t len;
uint8_t *buffer;
} PLogBufferEncoderArg;
// -----------------------------------------------------------------------------------------
// Callback used to stuff in the uuid
bool protobuf_log_util_encode_uuid(pb_ostream_t *stream, const pb_field_t *field,
void * const *arg);
// -----------------------------------------------------------------------------------------
// Callback used to stuff in a string
bool protobuf_log_util_encode_string(pb_ostream_t *stream, const pb_field_t *field,
void * const *arg);
// -----------------------------------------------------------------------------------------
// Callback used to stuff in a packed array of varints
bool protobuf_log_util_encode_packed_varints(pb_ostream_t *stream, const pb_field_t *field,
void * const *arg);
// -----------------------------------------------------------------------------------------
// Callback used to stuff in the array of types
bool protobuf_log_util_encode_measurement_types(pb_ostream_t *stream, const pb_field_t *field,
void * const *arg);
// -----------------------------------------------------------------------------------------
// Callback used to stuff in a data buffer. Useful for MeasurementSets or Events
bool protobuf_log_util_encode_buffer(pb_ostream_t *stream, const pb_field_t *field,
void * const *arg);