mirror of
https://github.com/google/pebble.git
synced 2025-05-27 13:33:12 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
473
src/fw/applib/accel_service.c
Normal file
473
src/fw/applib/accel_service.c
Normal file
|
@ -0,0 +1,473 @@
|
|||
/*
|
||||
* 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 "accel_service.h"
|
||||
|
||||
#include "accel_service_private.h"
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "applib/pbl_std/pbl_std.h"
|
||||
#include "event_service_client.h"
|
||||
#include "kernel/kernel_applib_state.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "process_state/worker_state/worker_state.h"
|
||||
#include "services/common/accel_manager.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "services/common/vibe_pattern.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "queue.h"
|
||||
|
||||
static bool prv_is_session_task(void) {
|
||||
PebbleTask task = pebble_task_get_current();
|
||||
return (task == PebbleTask_KernelMain || task == PebbleTask_KernelBackground ||
|
||||
task == PebbleTask_App);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Assert that the current task is allowed to create/delete a session
|
||||
static void prv_assert_session_task(void) {
|
||||
PBL_ASSERTN(prv_is_session_task());
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Return the session ref for the given task. This should ONLY be used by 3rd party tasks
|
||||
// (app or worker).
|
||||
AccelServiceState * accel_service_private_get_session(PebbleTask task) {
|
||||
if (task == PebbleTask_Unknown) {
|
||||
task = pebble_task_get_current();
|
||||
}
|
||||
|
||||
if (task == PebbleTask_App) {
|
||||
return app_state_get_accel_state();
|
||||
} else if (task == PebbleTask_Worker) {
|
||||
return worker_state_get_accel_state();
|
||||
} else {
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
|
||||
void accel_service_cleanup_task_session(PebbleTask task) {
|
||||
AccelServiceState *state = accel_service_private_get_session(task);
|
||||
if (state->manager_state) {
|
||||
sys_accel_manager_data_unsubscribe(state->manager_state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Event service handler for tap events
|
||||
static void prv_do_shake_handle(PebbleEvent *e, void *context) {
|
||||
PebbleTask task = pebble_task_get_current();
|
||||
AccelServiceState *state = (AccelServiceState *)accel_service_private_get_session(task);
|
||||
PBL_ASSERTN(state->shake_handler != NULL);
|
||||
|
||||
if (task == PebbleTask_Worker || task == PebbleTask_App) {
|
||||
sys_analytics_inc(ANALYTICS_APP_METRIC_ACCEL_SHAKE_COUNT, AnalyticsClient_CurrentTask);
|
||||
}
|
||||
state->shake_handler(e->accel_tap.axis, e->accel_tap.direction);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
static void prv_do_double_tap_handle(PebbleEvent *e, void *context) {
|
||||
PebbleTask task = pebble_task_get_current();
|
||||
AccelServiceState *state = (AccelServiceState *)accel_service_private_get_session(task);
|
||||
PBL_ASSERTN(state->double_tap_handler != NULL);
|
||||
// only kernel clients can subscribe to double tap right now, so just increment double tap count
|
||||
// device analytic here
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_ACCEL_DOUBLE_TAP_COUNT, AnalyticsClient_System);
|
||||
state->double_tap_handler(e->accel_tap.axis, e->accel_tap.direction);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// Handle a chunk of data received for a data subscription. Called by prv_do_data_handle.
|
||||
static uint32_t prv_do_data_handle_chunk(AccelServiceState *state, uint16_t time_interval_ms) {
|
||||
uint32_t num_samples = 0;
|
||||
|
||||
uint64_t timestamp_ms;
|
||||
num_samples = sys_accel_manager_get_num_samples(state->manager_state, ×tamp_ms);
|
||||
if (num_samples < state->samples_per_update) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if LOG_DOMAIN_ACCEL
|
||||
uint32_t time_since_last_sample =
|
||||
(state->prev_timestamp_ms != 0) ? timestamp_ms - state->prev_timestamp_ms : 0;
|
||||
state->prev_timestamp_ms = timestamp_ms;
|
||||
|
||||
ACCEL_LOG_DEBUG("got %d samples for task %d at %ld (%lu ms delta)",
|
||||
(int)num_samples, (int)pebble_task_get_current(), (uint32_t)timestamp_ms,
|
||||
time_since_last_sample);
|
||||
|
||||
for (unsigned int i=0; i<num_samples; i++) {
|
||||
ACCEL_LOG_DEBUG(" => x:%d, y:%d, z:%d", state->raw_data[i].x, state->raw_data[i].y, state->raw_data[i].z);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (state->raw_data_handler_deprecated) {
|
||||
state->raw_data_handler_deprecated(state->raw_data, num_samples);
|
||||
|
||||
} else if (state->raw_data_handler) {
|
||||
state->raw_data_handler(state->raw_data, num_samples, timestamp_ms);
|
||||
|
||||
} else {
|
||||
AccelData data[num_samples];
|
||||
for (uint32_t i = 0; i < num_samples; i++) {
|
||||
data[i] = (AccelData) {
|
||||
.x = state->raw_data[i].x,
|
||||
.y = state->raw_data[i].y,
|
||||
.z = state->raw_data[i].z,
|
||||
.timestamp = timestamp_ms,
|
||||
.did_vibrate = sys_vibe_history_was_vibrating(timestamp_ms)
|
||||
};
|
||||
timestamp_ms += time_interval_ms;
|
||||
}
|
||||
state->data_handler(data, num_samples);
|
||||
}
|
||||
|
||||
// Tell accel_manager that it can put more data in now
|
||||
bool success = sys_accel_manager_consume_samples(state->manager_state, num_samples);
|
||||
PBL_ASSERTN(success);
|
||||
return num_samples;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Called by sys_accel_manager when we have data available for this subscriber
|
||||
static void prv_do_data_handle(void *context) {
|
||||
AccelServiceState *state = (AccelServiceState *)context;
|
||||
|
||||
if (state->manager_state == NULL) {
|
||||
if (state->deferred_free) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Deferred free");
|
||||
kernel_free(state);
|
||||
}
|
||||
// event queue is handled kernel-side, so an event may fire after we've unsubscribed
|
||||
return;
|
||||
}
|
||||
|
||||
PBL_ASSERTN(state->data_handler != NULL || state->raw_data_handler != NULL
|
||||
|| state->raw_data_handler_deprecated != NULL);
|
||||
|
||||
uint16_t time_interval_ms = 1000 / state->sampling_rate;
|
||||
|
||||
// Process in chunks to limit the amount of stack space we use up.
|
||||
uint32_t num_processed;
|
||||
do {
|
||||
num_processed = prv_do_data_handle_chunk(state, time_interval_ms);
|
||||
|
||||
sys_analytics_add(ANALYTICS_APP_METRIC_ACCEL_SAMPLE_COUNT, num_processed, AnalyticsClient_CurrentTask);
|
||||
} while (num_processed);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
int accel_service_set_sampling_rate(AccelSamplingRate rate) {
|
||||
AccelServiceState * session = accel_service_private_get_session(PebbleTask_Unknown);
|
||||
return accel_session_set_sampling_rate(session, rate);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
int accel_service_set_samples_per_update(uint32_t samples_per_update) {
|
||||
AccelServiceState * session = accel_service_private_get_session(PebbleTask_Unknown);
|
||||
return accel_session_set_samples_per_update(session, samples_per_update);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
static void prv_shared_subscribe(AccelServiceState *state, AccelSamplingRate sampling_rate,
|
||||
uint32_t samples_per_update, PebbleTask handler_task) {
|
||||
state->manager_state = sys_accel_manager_data_subscribe(
|
||||
sampling_rate, prv_do_data_handle, state, handler_task);
|
||||
|
||||
accel_session_set_samples_per_update((AccelServiceState *)state, samples_per_update);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
void accel_data_service_subscribe(uint32_t samples_per_update, AccelDataHandler handler) {
|
||||
AccelServiceState * session = accel_service_private_get_session(PebbleTask_Unknown);
|
||||
accel_session_data_subscribe(session, samples_per_update, handler);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
void accel_raw_data_service_subscribe(uint32_t samples_per_update, AccelRawDataHandler handler) {
|
||||
AccelServiceState * session = accel_service_private_get_session(PebbleTask_Unknown);
|
||||
accel_session_raw_data_subscribe(session, ACCEL_SAMPLING_25HZ, samples_per_update, handler);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
void accel_data_service_subscribe__deprecated(uint32_t samples_per_update, AccelRawDataHandler__deprecated handler) {
|
||||
AccelServiceState * session = accel_service_private_get_session(PebbleTask_Unknown);
|
||||
AccelServiceState *state = (AccelServiceState *)session;
|
||||
|
||||
state->raw_data_handler_deprecated = handler;
|
||||
state->raw_data_handler = NULL;
|
||||
state->data_handler = NULL;
|
||||
|
||||
prv_shared_subscribe(state, ACCEL_SAMPLING_25HZ, samples_per_update, pebble_task_get_current());
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
void accel_data_service_unsubscribe(void) {
|
||||
AccelServiceState * session = accel_service_private_get_session(PebbleTask_Unknown);
|
||||
accel_session_data_unsubscribe(session);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
void accel_tap_service_subscribe(AccelTapHandler handler) {
|
||||
AccelServiceState * session = accel_service_private_get_session(PebbleTask_Unknown);
|
||||
accel_session_shake_subscribe(session, handler);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
void accel_tap_service_unsubscribe(void) {
|
||||
AccelServiceState * session = accel_service_private_get_session(PebbleTask_Unknown);
|
||||
accel_session_shake_unsubscribe(session);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
void accel_double_tap_service_subscribe(AccelTapHandler handler) {
|
||||
AccelServiceState * session = accel_service_private_get_session(PebbleTask_Unknown);
|
||||
accel_session_double_tap_subscribe(session, handler);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
void accel_double_tap_service_unsubscribe(void) {
|
||||
AccelServiceState * session = accel_service_private_get_session(PebbleTask_Unknown);
|
||||
accel_session_double_tap_unsubscribe(session);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
int accel_service_peek(AccelData *accel_data) {
|
||||
AccelServiceState *state = accel_service_private_get_session(PebbleTask_Unknown);
|
||||
|
||||
int rc = sys_accel_manager_peek(accel_data);
|
||||
|
||||
ACCEL_LOG_DEBUG("peek data x:%d, y:%d, z:%d", accel_data->x, accel_data->y, accel_data->z);
|
||||
if (rc != 0 || state->raw_data_handler_deprecated || state->raw_data_handler) {
|
||||
// No timestamp info needed
|
||||
return rc;
|
||||
}
|
||||
|
||||
accel_data->did_vibrate = (sys_vibe_get_vibe_strength() != 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
void accel_service_state_init(AccelServiceState *state) {
|
||||
*state = (AccelServiceState) {
|
||||
.sampling_rate = ACCEL_DEFAULT_SAMPLING_RATE,
|
||||
.accel_shake_info = {
|
||||
.type = PEBBLE_ACCEL_SHAKE_EVENT,
|
||||
.handler = &prv_do_shake_handle,
|
||||
},
|
||||
.accel_double_tap_info = {
|
||||
.type = PEBBLE_ACCEL_DOUBLE_TAP_EVENT,
|
||||
.handler = &prv_do_double_tap_handle,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Event service handler for shake events
|
||||
static void prv_session_do_shake_handle(PebbleEvent *e, void *context) {
|
||||
AccelServiceState *state = context;
|
||||
if (state->shake_handler != NULL) {
|
||||
state->shake_handler(e->accel_tap.axis, e->accel_tap.direction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Event service handler for double tap events
|
||||
static void prv_session_do_double_tap_handle(PebbleEvent *e, void *context) {
|
||||
AccelServiceState *state = context;
|
||||
if (state->double_tap_handler != NULL) {
|
||||
state->double_tap_handler(e->accel_tap.axis, e->accel_tap.direction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
AccelServiceState * accel_session_create(void) {
|
||||
prv_assert_session_task();
|
||||
AccelServiceState *state = kernel_malloc_check(sizeof(AccelServiceState));
|
||||
|
||||
*state = (AccelServiceState) {
|
||||
.sampling_rate = ACCEL_DEFAULT_SAMPLING_RATE,
|
||||
.accel_shake_info = {
|
||||
.type = PEBBLE_ACCEL_SHAKE_EVENT,
|
||||
.handler = &prv_session_do_shake_handle,
|
||||
.context = state,
|
||||
},
|
||||
.accel_double_tap_info = {
|
||||
.type = PEBBLE_ACCEL_DOUBLE_TAP_EVENT,
|
||||
.handler = &prv_session_do_double_tap_handle,
|
||||
.context = state,
|
||||
},
|
||||
};
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
static void prv_cleanup_accel_session_cb(void *session) {
|
||||
kernel_free(session);
|
||||
}
|
||||
|
||||
void accel_session_delete(AccelServiceState * session) {
|
||||
prv_assert_session_task();
|
||||
|
||||
// we better have unsubscribed at this point
|
||||
PBL_ASSERTN(session->manager_state == NULL);
|
||||
|
||||
// A deferred free means one lingering event was posted. We will free the session once the event
|
||||
// gets drained in 'prv_do_data_handle'
|
||||
if (!session->deferred_free) {
|
||||
kernel_free(session);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
void accel_session_shake_subscribe(AccelServiceState * session, AccelTapHandler handler) {
|
||||
AccelServiceState *state = (AccelServiceState *)session;
|
||||
state->shake_handler = handler;
|
||||
event_service_client_subscribe(&state->accel_shake_info);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
void accel_session_shake_unsubscribe(AccelServiceState *state) {
|
||||
event_service_client_unsubscribe(&state->accel_shake_info);
|
||||
state->shake_handler = NULL;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
void accel_session_double_tap_subscribe(AccelServiceState *state, AccelTapHandler handler) {
|
||||
state->double_tap_handler = handler;
|
||||
event_service_client_subscribe(&state->accel_double_tap_info);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
void accel_session_double_tap_unsubscribe(AccelServiceState *state) {
|
||||
event_service_client_unsubscribe(&state->accel_double_tap_info);
|
||||
state->double_tap_handler = NULL;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
void accel_session_data_subscribe(AccelServiceState *state, uint32_t samples_per_update,
|
||||
AccelDataHandler handler) {
|
||||
state->data_handler = handler;
|
||||
state->raw_data_handler = NULL;
|
||||
state->raw_data_handler_deprecated = NULL;
|
||||
|
||||
prv_shared_subscribe(state, ACCEL_SAMPLING_25HZ, samples_per_update, pebble_task_get_current());
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
void accel_session_raw_data_subscribe(
|
||||
AccelServiceState *state, AccelSamplingRate sampling_rate, uint32_t samples_per_update,
|
||||
AccelRawDataHandler handler) {
|
||||
state->raw_data_handler = handler;
|
||||
state->raw_data_handler_deprecated = NULL;
|
||||
state->data_handler = NULL;
|
||||
|
||||
prv_shared_subscribe(state, sampling_rate, samples_per_update, pebble_task_get_current());
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
void accel_session_data_unsubscribe(AccelServiceState *state) {
|
||||
if (!state->manager_state) {
|
||||
return;
|
||||
}
|
||||
if (sys_accel_manager_data_unsubscribe(state->manager_state)) {
|
||||
// There is a pending event posted. Only session tasks allocate memory for their state in the
|
||||
// first place so only free the memory if this is true
|
||||
state->deferred_free = prv_is_session_task();
|
||||
}
|
||||
|
||||
applib_free(state->raw_data);
|
||||
state->manager_state = NULL;
|
||||
state->raw_data = NULL;
|
||||
state->data_handler = NULL;
|
||||
state->raw_data_handler = NULL;
|
||||
state->raw_data_handler_deprecated = NULL;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
int accel_session_set_sampling_rate(AccelServiceState *state, AccelSamplingRate rate) {
|
||||
if (!state->manager_state || (!state->data_handler && !state->raw_data_handler
|
||||
&& !state->raw_data_handler_deprecated)) {
|
||||
return -1;
|
||||
}
|
||||
state->sampling_rate = rate;
|
||||
return sys_accel_manager_set_sampling_rate(state->manager_state, rate);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
int accel_session_set_samples_per_update(AccelServiceState *state, uint32_t samples_per_update) {
|
||||
|
||||
if (samples_per_update > ACCEL_MAX_SAMPLES_PER_UPDATE) {
|
||||
APP_LOG(LOG_LEVEL_WARNING, "%d samples per update requested, max is %d",
|
||||
(int)samples_per_update, ACCEL_MAX_SAMPLES_PER_UPDATE);
|
||||
samples_per_update = ACCEL_MAX_SAMPLES_PER_UPDATE;
|
||||
}
|
||||
if (!state->manager_state
|
||||
|| (samples_per_update > 0 && !state->data_handler && !state->raw_data_handler
|
||||
&& !state->raw_data_handler_deprecated)) {
|
||||
return -1;
|
||||
}
|
||||
AccelRawData *old_buf = state->raw_data;
|
||||
|
||||
// This is a packed array of simple types and therefore shouldn't have compatibility padding
|
||||
state->raw_data = applib_malloc(samples_per_update * sizeof(AccelRawData));
|
||||
if (!state->raw_data) {
|
||||
APP_LOG(LOG_LEVEL_ERROR, "Not enough memory to subscribe");
|
||||
state->raw_data = old_buf;
|
||||
return -1;
|
||||
}
|
||||
state->samples_per_update = samples_per_update;
|
||||
int result = sys_accel_manager_set_sample_buffer(state->manager_state, state->raw_data,
|
||||
samples_per_update);
|
||||
applib_free(old_buf);
|
||||
return result;
|
||||
}
|
148
src/fw/applib/accel_service.h
Normal file
148
src/fw/applib/accel_service.h
Normal file
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* 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 "services/common/accel_manager.h"
|
||||
#include "services/imu/units.h"
|
||||
#include "kernel/pebble_tasks.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup EventService
|
||||
//! @{
|
||||
//! @addtogroup AccelerometerService
|
||||
//!
|
||||
//! \brief Using the Pebble accelerometer
|
||||
//!
|
||||
//! The AccelerometerService enables the Pebble accelerometer to detect taps,
|
||||
//! perform measures at a given frequency, and transmit samples in batches to save CPU time
|
||||
//! and processing.
|
||||
//!
|
||||
//! For available code samples, see the
|
||||
//! <a href="https://github.com/pebble-examples/feature-accel-discs/">feature-accel-discs</a>
|
||||
//! example app.
|
||||
//! @{
|
||||
|
||||
//! Enumerated values defining the three accelerometer axes.
|
||||
typedef enum {
|
||||
//! Accelerometer's X axis. The positive direction along the X axis goes
|
||||
//! toward the right of the watch.
|
||||
ACCEL_AXIS_X = 0,
|
||||
//! Accelerometer's Y axis. The positive direction along the Y axis goes
|
||||
//! toward the top of the watch.
|
||||
ACCEL_AXIS_Y = 1,
|
||||
//! Accelerometer's Z axis. The positive direction along the Z axis goes
|
||||
//! vertically out of the watchface.
|
||||
ACCEL_AXIS_Z = 2,
|
||||
} AccelAxisType;
|
||||
|
||||
// Make sure the AccelAxisType enum is compatible with the unified
|
||||
// IMUCoordinateAxis enum.
|
||||
_Static_assert(ACCEL_AXIS_X == (int)AXIS_X,
|
||||
"AccelAxisType incompatible with IMUCoordinateAxis");
|
||||
_Static_assert(ACCEL_AXIS_Y == (int)AXIS_Y,
|
||||
"AccelAxisType incompatible with IMUCoordinateAxis");
|
||||
_Static_assert(ACCEL_AXIS_Z == (int)AXIS_Z,
|
||||
"AccelAxisType incompatible with IMUCoordinateAxis");
|
||||
|
||||
#define ACCEL_DEFAULT_SAMPLING_RATE ACCEL_SAMPLING_25HZ
|
||||
#define ACCEL_MINIMUM_SAMPLING_RATE ACCEL_SAMPLING_10HZ
|
||||
|
||||
//! Callback type for accelerometer tap events
|
||||
//! @param axis the axis on which a tap was registered (x, y, or z)
|
||||
//! @param direction the direction (-1 or +1) of the tap
|
||||
typedef void (*AccelTapHandler)(AccelAxisType axis, int32_t direction);
|
||||
|
||||
//! Callback type for accelerometer data events
|
||||
//! @param data Pointer to the collected accelerometer samples.
|
||||
//! @param num_samples the number of samples stored in data.
|
||||
typedef void (*AccelDataHandler)(AccelData *data, uint32_t num_samples);
|
||||
|
||||
//! Callback type for accelerometer raw data events
|
||||
//! @param data Pointer to the collected accelerometer samples.
|
||||
//! @param num_samples the number of samples stored in data.
|
||||
//! @param timestamp the timestamp, in ms, of the first sample.
|
||||
typedef void (*AccelRawDataHandler)(AccelRawData *data, uint32_t num_samples, uint64_t timestamp);
|
||||
|
||||
//! Subscribe to the accelerometer tap event service. Once subscribed, the handler
|
||||
//! gets called on every tap event emitted by the accelerometer.
|
||||
//! @param handler A callback to be executed on tap event
|
||||
void accel_tap_service_subscribe(AccelTapHandler handler);
|
||||
|
||||
//! Unsubscribe from the accelerometer tap event service. Once unsubscribed,
|
||||
//! the previously registered handler will no longer be called.
|
||||
void accel_tap_service_unsubscribe(void);
|
||||
|
||||
//! @internal
|
||||
//! Subscribe to the accelerometer double tap event service. Once subscribed, the handler
|
||||
//! gets called on every double tap event emitted by the accelerometer.
|
||||
//! @param handler A callback to be executed on double tap event
|
||||
void accel_double_tap_service_subscribe(AccelTapHandler handler);
|
||||
|
||||
//! @internal
|
||||
//! Unsubscribe from the accelerometer double tap event service. Once unsubscribed,
|
||||
//! the previously registered handler will no longer be called.
|
||||
void accel_double_tap_service_unsubscribe(void);
|
||||
|
||||
//! Subscribe to the accelerometer data event service. Once subscribed, the handler
|
||||
//! gets called every time there are new accelerometer samples available.
|
||||
//! @note Cannot use \ref accel_service_peek() when subscribed to accelerometer data events.
|
||||
//! @param handler A callback to be executed on accelerometer data events
|
||||
//! @param samples_per_update the number of samples to buffer, between 0 and 25.
|
||||
void accel_data_service_subscribe(uint32_t samples_per_update, AccelDataHandler handler);
|
||||
|
||||
//! Subscribe to the accelerometer raw data event service. Once subscribed, the handler
|
||||
//! gets called every time there are new accelerometer samples available.
|
||||
//! @note Cannot use \ref accel_service_peek() when subscribed to accelerometer data events.
|
||||
//! @param handler A callback to be executed on accelerometer data events
|
||||
//! @param samples_per_update the number of samples to buffer, between 0 and 25.
|
||||
void accel_raw_data_service_subscribe(uint32_t samples_per_update, AccelRawDataHandler handler);
|
||||
|
||||
//! Unsubscribe from the accelerometer data event service. Once unsubscribed,
|
||||
//! the previously registered handler will no longer be called.
|
||||
void accel_data_service_unsubscribe(void);
|
||||
|
||||
//! Change the accelerometer sampling rate.
|
||||
//! @param rate The sampling rate in Hz (10Hz, 25Hz, 50Hz, and 100Hz possible)
|
||||
int accel_service_set_sampling_rate(AccelSamplingRate rate);
|
||||
|
||||
//! Change the number of samples buffered between each accelerometer data event
|
||||
//! @param num_samples the number of samples to buffer, between 0 and 25.
|
||||
int accel_service_set_samples_per_update(uint32_t num_samples);
|
||||
|
||||
//! Peek at the last recorded reading.
|
||||
//! @param[out] data a pointer to a pre-allocated AccelData item
|
||||
//! @note Cannot be used when subscribed to accelerometer data events.
|
||||
//! @return -1 if the accel is not running
|
||||
//! @return -2 if subscribed to accelerometer events.
|
||||
int accel_service_peek(AccelData *data);
|
||||
|
||||
//! @} // end addtogroup AccelerometerService
|
||||
//! @} // end addtogroup EventService
|
||||
//! @} // end addtogroup Foundation
|
||||
|
||||
|
||||
//! @internal
|
||||
typedef void (*AccelRawDataHandler__deprecated)(AccelRawData *data, uint32_t num_samples);
|
||||
|
||||
//! @internal
|
||||
//! This is used to stay in the jump table where the old accel_data_service_subscribe was located.
|
||||
//! Allows operation on AccelRawData data, which is the same as the previous version of AccelData.
|
||||
void accel_data_service_subscribe__deprecated(uint32_t samples_per_update, AccelRawDataHandler__deprecated handler);
|
123
src/fw/applib/accel_service_private.h
Normal file
123
src/fw/applib/accel_service_private.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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 "accel_service.h"
|
||||
#include "event_service_client.h"
|
||||
|
||||
#include "system/logging.h"
|
||||
|
||||
typedef struct AccelServiceState {
|
||||
// Configuration for our data callback subscription to the accel manager
|
||||
AccelManagerState *manager_state;
|
||||
AccelSamplingRate sampling_rate;
|
||||
bool deferred_free;
|
||||
uint16_t samples_per_update;
|
||||
AccelRawData *raw_data; // of size samples_per_update
|
||||
|
||||
// User-provided callbacks for various events
|
||||
AccelDataHandler data_handler;
|
||||
AccelTapHandler shake_handler;
|
||||
AccelTapHandler double_tap_handler;
|
||||
AccelRawDataHandler raw_data_handler;
|
||||
AccelRawDataHandler__deprecated raw_data_handler_deprecated;
|
||||
|
||||
// Configuration for our other types of events
|
||||
EventServiceInfo accel_shake_info;
|
||||
EventServiceInfo accel_double_tap_info;
|
||||
|
||||
#if LOG_DOMAIN_ACCEL
|
||||
uint64_t prev_timestamp_ms;
|
||||
#endif
|
||||
} AccelServiceState;
|
||||
|
||||
//! Initialize an existing state object
|
||||
void accel_service_state_init(AccelServiceState *state);
|
||||
|
||||
AccelServiceState* accel_service_private_get_session(PebbleTask task);
|
||||
|
||||
void accel_service_cleanup_task_session(PebbleTask task);
|
||||
|
||||
//! Create a new accel session. Used by kernel clients only. Kernel clients MUST use the
|
||||
//! AccelSession based calls (accel_session_data_subscribe, etc.) to access the
|
||||
//! accel service whereas apps use the accel_service based calls (accel_data_service_subscribe,
|
||||
//! etc.). The accel_.*_service_.* calls are simply wrapper functions that look up the
|
||||
//! AccelServiceState given the current task_id (app or worker) and then called into the respective
|
||||
//! accel_session_.* call.
|
||||
//! @return A non-zero session upon success, NULL if error
|
||||
AccelServiceState* accel_session_create(void);
|
||||
|
||||
//! Delete an accel session created by accel_session_create. Used by kernel clients only.
|
||||
//! @param session An Accel session created by accel_session_create()
|
||||
void accel_session_delete(AccelServiceState *session);
|
||||
|
||||
//! Subscribe to the accelerometer shake event service by session ref. Used by kernel clients
|
||||
//! only.
|
||||
//! @param session An Accel session created by accel_session_create()
|
||||
//! @param handler A callback to be executed on shake event
|
||||
void accel_session_shake_subscribe(AccelServiceState *session, AccelTapHandler handler);
|
||||
|
||||
//! Unsubscribe from the accelerometer shake event service by session ref. Used by kernel clients
|
||||
//! only.
|
||||
//! @param session An Accel session created by accel_session_create()
|
||||
void accel_session_shake_unsubscribe(AccelServiceState *session);
|
||||
|
||||
//! Subscribe to the accelerometer double tap event service by session ref. Used by kernel clients
|
||||
//! only.
|
||||
//! @param session An Accel session created by accel_session_create()
|
||||
//! @param handler A callback to be executed on tap event
|
||||
void accel_session_double_tap_subscribe(AccelServiceState *session, AccelTapHandler handler);
|
||||
|
||||
//! Unsubscribe from the accelerometer double tap event service by session ref. Used by kernel
|
||||
//! clients only.
|
||||
//! @param session An Accel session created by accel_session_create()
|
||||
void accel_session_double_tap_unsubscribe(AccelServiceState *session);
|
||||
|
||||
//! Subscribe to the accelerometer data event service by session ref. Used by kernel clients
|
||||
//! only.
|
||||
//! @param session An accel session created by accel_session_create()
|
||||
//! @param handler A callback to be executed on accelerometer data events
|
||||
//! @param samples_per_update the number of samples to buffer, between 0 and 25.
|
||||
void accel_session_data_subscribe(AccelServiceState *session, uint32_t samples_per_update,
|
||||
AccelDataHandler handler);
|
||||
|
||||
//! Subscribe to the accelerometer data event service by session ref. Used by kernel clients
|
||||
//! only.
|
||||
//! @param session An accel session created by accel_session_create()
|
||||
//! @param sampling_rate the desired sampling_rate
|
||||
//! @param samples_per_update the number of samples to buffer, between 0 and 25.
|
||||
//! @param handler A callback to be executed on accelerometer data events. The callback will
|
||||
//! execute on the current task calling this function.
|
||||
void accel_session_raw_data_subscribe(
|
||||
AccelServiceState *session, AccelSamplingRate sampling_rate, uint32_t samples_per_update,
|
||||
AccelRawDataHandler handler);
|
||||
|
||||
//! Unsubscribe from the accelerometer data event service. Used by kernel clients
|
||||
//! only.
|
||||
//! @param session An accel session created by accel_session_create()
|
||||
void accel_session_data_unsubscribe(AccelServiceState *session);
|
||||
|
||||
//! Change the accelerometer sampling rate. Used by kernel clients only.
|
||||
//! @param session An accel session created by accel_session_create()
|
||||
//! @param rate The sampling rate in Hz (10Hz, 25Hz, 50Hz, and 100Hz possible)
|
||||
int accel_session_set_sampling_rate(AccelServiceState *session, AccelSamplingRate rate);
|
||||
|
||||
//! Change the number of samples buffered between each accelerometer data event. Used by kernel
|
||||
//! clients only.
|
||||
//! @param session An accel session created by accel_session_create()
|
||||
//! @param num_samples the number of samples to buffer, between 0 and 25.
|
||||
int accel_session_set_samples_per_update(AccelServiceState *session, uint32_t num_samples);
|
284
src/fw/applib/app.c
Normal file
284
src/fw/applib/app.c
Normal file
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* 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 "app.h"
|
||||
|
||||
#include "applib/app_heap_analytics.h"
|
||||
#include "applib/graphics/graphics_private.h"
|
||||
#include "applib/ui/app_window_stack.h"
|
||||
#include "applib/ui/window_stack.h"
|
||||
#include "applib/ui/window_private.h"
|
||||
#include "mcu/fpu.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/profiler.h"
|
||||
|
||||
static void prv_render_app(void) {
|
||||
WindowStack *stack = app_state_get_window_stack();
|
||||
GContext *ctx = app_state_get_graphics_context();
|
||||
if (!window_stack_is_animating(stack)) {
|
||||
SYS_PROFILER_NODE_START(render_app);
|
||||
window_render(app_window_stack_get_top_window(), ctx);
|
||||
SYS_PROFILER_NODE_STOP(render_app);
|
||||
} else {
|
||||
// TODO: PBL-17645 render container layer instead of the two windows
|
||||
WindowTransitioningContext *transition_context = &stack->transition_context;
|
||||
|
||||
if (transition_context->implementation->render) {
|
||||
transition_context->implementation->render(transition_context, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
*app_state_get_framebuffer_render_pending() = true;
|
||||
|
||||
PebbleEvent event = {
|
||||
.type = PEBBLE_RENDER_READY_EVENT,
|
||||
};
|
||||
sys_send_pebble_event_to_kernel(&event);
|
||||
}
|
||||
|
||||
static bool prv_window_is_render_scheduled(Window *window) {
|
||||
return window && window->is_render_scheduled;
|
||||
}
|
||||
|
||||
static bool prv_app_is_render_scheduled() {
|
||||
Window *top_window = app_window_stack_get_top_window();
|
||||
if (prv_window_is_render_scheduled(top_window)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
WindowStack *stack = app_state_get_window_stack();
|
||||
if (!window_stack_is_animating(stack)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WindowTransitioningContext *transition_ctx = &stack->transition_context;
|
||||
return prv_window_is_render_scheduled(transition_ctx->window_from) ||
|
||||
prv_window_is_render_scheduled(transition_ctx->window_to);
|
||||
}
|
||||
|
||||
void app_request_render(void) {
|
||||
Window *window = app_window_stack_get_top_window();
|
||||
if (window) {
|
||||
window_schedule_render(window);
|
||||
}
|
||||
}
|
||||
|
||||
//! Tasks that have to be done in between each event.
|
||||
static NOINLINE void event_loop_upkeep(void) {
|
||||
|
||||
// Check to see if the most recent event caused us to pop our final window. If that's the case, we need to
|
||||
// kill ourselves.
|
||||
if (app_window_stack_count() == 0) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "No more windows, killing current app");
|
||||
|
||||
PebbleEvent event = { .type = PEBBLE_PROCESS_KILL_EVENT, .kill = { .gracefully = true, .task=PebbleTask_App } };
|
||||
sys_send_pebble_event_to_kernel(&event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check to see if handling the previous event requires us to rerender ourselves.
|
||||
if (prv_app_is_render_scheduled() &&
|
||||
!*app_state_get_framebuffer_render_pending()) {
|
||||
prv_render_app();
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_app_will_focus_handler(PebbleEvent *e, void *context) {
|
||||
Window *window = app_window_stack_get_top_window();
|
||||
if (e->app_focus.in_focus) {
|
||||
if (window) {
|
||||
// Do not call 'appear' handler on window displacing modal window
|
||||
window_set_on_screen(window, true, false);
|
||||
window_render(window, app_state_get_graphics_context());
|
||||
}
|
||||
click_manager_reset(app_state_get_click_manager());
|
||||
} else if (window) {
|
||||
// Do not call 'disappear' handler on window displaced by modal window
|
||||
window_set_on_screen(window, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_app_button_down_handler(PebbleEvent *e, void *context) {
|
||||
WindowStack *app_window_stack = app_state_get_window_stack();
|
||||
if (window_stack_is_animating(app_window_stack)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sys_analytics_inc(ANALYTICS_APP_METRIC_BUTTONS_PRESSED_COUNT, AnalyticsClient_App);
|
||||
|
||||
if (e->button.button_id == BUTTON_ID_BACK &&
|
||||
!app_window_stack_get_top_window()->overrides_back_button) {
|
||||
// a transition of NULL means we will use the stored pop transition for this stack item
|
||||
window_stack_pop_with_transition(app_window_stack, NULL /* transition */);
|
||||
return;
|
||||
}
|
||||
|
||||
click_recognizer_handle_button_down(
|
||||
&app_state_get_click_manager()->recognizers[e->button.button_id]);
|
||||
}
|
||||
|
||||
static void prv_app_button_up_handler(PebbleEvent *e, void *context) {
|
||||
if (window_stack_is_animating(app_state_get_window_stack())) {
|
||||
return;
|
||||
}
|
||||
|
||||
click_recognizer_handle_button_up(
|
||||
&app_state_get_click_manager()->recognizers[e->button.button_id]);
|
||||
}
|
||||
|
||||
// this handler is called via the legacy2_status_bar_change_event and
|
||||
// will update the status bar once a minute for non-fullscreen legacy2 apps
|
||||
static void prv_legacy2_status_bar_handler(PebbleEvent *e, void *context) {
|
||||
Window *window = app_window_stack_get_top_window();
|
||||
// only force render if we're not fullscreen
|
||||
if (!window->is_fullscreen) {
|
||||
// a little logic to only force update when the minute changes
|
||||
ApplibInternalEventsInfo *events_info =
|
||||
app_state_get_applib_internal_events_info();
|
||||
struct tm currtime;
|
||||
sys_localtime_r(&e->clock_tick.tick_time, &currtime);
|
||||
const int minute_of_day = (currtime.tm_hour * 60) + currtime.tm_min;
|
||||
if (events_info->minute_of_last_legacy2_statusbar_change != minute_of_day) {
|
||||
events_info->minute_of_last_legacy2_statusbar_change = minute_of_day;
|
||||
window_schedule_render(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_legacy2_status_bar_timer_subscribe(void) {
|
||||
// we only need this tick event if we are a legacy2 app
|
||||
if (process_manager_compiled_with_legacy2_sdk()) {
|
||||
ApplibInternalEventsInfo *events_info =
|
||||
app_state_get_applib_internal_events_info();
|
||||
// Initialize the state for the status bar handler.
|
||||
events_info->minute_of_last_legacy2_statusbar_change = -1;
|
||||
events_info->legacy2_status_bar_change_event = (EventServiceInfo) {
|
||||
.type = PEBBLE_TICK_EVENT,
|
||||
.handler = prv_legacy2_status_bar_handler,
|
||||
};
|
||||
event_service_client_subscribe(
|
||||
&events_info->legacy2_status_bar_change_event);
|
||||
}
|
||||
// NOTE: We could be super fancy and register and unregister when the fullscreen
|
||||
// status changes, but it's probably not worth it as we'll be waking up once a
|
||||
// minute anyway to update the face itself and it will happen as part of the same interval
|
||||
}
|
||||
|
||||
static void prv_legacy2_status_bar_timer_unsubscribe(void) {
|
||||
// we should only unsubscribe if we subscribed in the first place
|
||||
if (process_manager_compiled_with_legacy2_sdk()) {
|
||||
ApplibInternalEventsInfo *events_info =
|
||||
app_state_get_applib_internal_events_info();
|
||||
event_service_client_unsubscribe(
|
||||
&events_info->legacy2_status_bar_change_event);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_app_callback_handler(PebbleEvent *e) {
|
||||
e->callback.callback(e->callback.data);
|
||||
}
|
||||
|
||||
static NOINLINE void prv_handle_deinit_event(void) {
|
||||
ApplibInternalEventsInfo *events_info =
|
||||
app_state_get_applib_internal_events_info();
|
||||
event_service_client_unsubscribe(&events_info->will_focus_event);
|
||||
event_service_client_unsubscribe(&events_info->button_down_event);
|
||||
event_service_client_unsubscribe(&events_info->button_up_event);
|
||||
prv_legacy2_status_bar_timer_unsubscribe(); // a no-op on sdk3+ applications
|
||||
WindowStack *app_window_stack = app_state_get_window_stack();
|
||||
window_stack_lock_push(app_window_stack);
|
||||
window_stack_pop_all(app_window_stack, false);
|
||||
window_stack_unlock_push(app_window_stack);
|
||||
}
|
||||
|
||||
// Get the app_id for the current app or worker
|
||||
// @return INSTALL_ID_INVALID if unsuccessful
|
||||
AppInstallId app_get_app_id(void) {
|
||||
// Only support from app or workers
|
||||
PebbleTask task = pebble_task_get_current();
|
||||
if ((task != PebbleTask_App) && (task != PebbleTask_Worker)) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Only supported from app or worker tasks");
|
||||
return INSTALL_ID_INVALID;
|
||||
}
|
||||
|
||||
// Get the app id
|
||||
if (task == PebbleTask_App) {
|
||||
return sys_app_manager_get_current_app_id();
|
||||
} else {
|
||||
return sys_worker_manager_get_current_worker_id();
|
||||
}
|
||||
}
|
||||
|
||||
void app_event_loop_common(void) {
|
||||
// Register our event handlers before we do anything else. Registering for an event requires
|
||||
// an event being sent to the kernel and therefore should be done before any other events are
|
||||
// generated by us to ensure we don't miss out on anything.
|
||||
ApplibInternalEventsInfo *events_info =
|
||||
app_state_get_applib_internal_events_info();
|
||||
events_info->will_focus_event = (EventServiceInfo) {
|
||||
.type = PEBBLE_APP_WILL_CHANGE_FOCUS_EVENT,
|
||||
.handler = prv_app_will_focus_handler,
|
||||
};
|
||||
events_info->button_down_event = (EventServiceInfo) {
|
||||
.type = PEBBLE_BUTTON_DOWN_EVENT,
|
||||
.handler = prv_app_button_down_handler,
|
||||
};
|
||||
events_info->button_up_event = (EventServiceInfo) {
|
||||
.type = PEBBLE_BUTTON_UP_EVENT,
|
||||
.handler = prv_app_button_up_handler,
|
||||
};
|
||||
event_service_client_subscribe(&events_info->will_focus_event);
|
||||
event_service_client_subscribe(&events_info->button_down_event);
|
||||
event_service_client_subscribe(&events_info->button_up_event);
|
||||
prv_legacy2_status_bar_timer_subscribe(); // a no-op on sdk3+ applications
|
||||
|
||||
event_loop_upkeep();
|
||||
|
||||
// Event loop:
|
||||
while (1) {
|
||||
PebbleEvent event;
|
||||
|
||||
sys_get_pebble_event(&event);
|
||||
|
||||
if (event.type == PEBBLE_PROCESS_DEINIT_EVENT) {
|
||||
prv_handle_deinit_event();
|
||||
// We're done here. Return the app's main function.
|
||||
event_cleanup(&event);
|
||||
return;
|
||||
} else if (event.type == PEBBLE_CALLBACK_EVENT) {
|
||||
prv_app_callback_handler(&event);
|
||||
} else if (event.type == PEBBLE_RENDER_REQUEST_EVENT) {
|
||||
app_request_render();
|
||||
} else if (event.type == PEBBLE_RENDER_FINISHED_EVENT) {
|
||||
*app_state_get_framebuffer_render_pending() = false;
|
||||
} else {
|
||||
event_service_client_handle_event(&event);
|
||||
}
|
||||
|
||||
mcu_fpu_cleanup();
|
||||
event_cleanup(&event);
|
||||
|
||||
event_loop_upkeep();
|
||||
}
|
||||
}
|
||||
|
||||
void app_event_loop(void) {
|
||||
app_event_loop_common();
|
||||
app_heap_analytics_log_stats_to_app_heartbeat(false /* is_rocky_app */);
|
||||
}
|
47
src/fw/applib/app.h
Normal file
47
src/fw/applib/app.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 "applib/graphics/gtypes.h"
|
||||
#include "process_management/app_install_types.h"
|
||||
|
||||
//! @file app.h
|
||||
//!
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup App
|
||||
//! @{
|
||||
|
||||
//! @internal
|
||||
//! Requests the app to re-render by scheduling its top window to be rendered.
|
||||
void app_request_render(void);
|
||||
|
||||
//! @internal
|
||||
//! Event loop that is shared between Rocky.js and C apps. This is called by both app_event_loop()
|
||||
//! as well as rocky_event_loop_with_...().
|
||||
void app_event_loop_common(void);
|
||||
|
||||
//! The event loop for C apps, to be used in app's main().
|
||||
//! Will block until the app is ready to exit.
|
||||
void app_event_loop(void);
|
||||
|
||||
//! @internal
|
||||
//! Get the AppInstallId for the current app or worker
|
||||
AppInstallId app_get_app_id(void);
|
||||
|
||||
//! @} // end addtogroup App
|
||||
//! @} // end addtogroup Foundation
|
28
src/fw/applib/app_comm.c
Normal file
28
src/fw/applib/app_comm.c
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 "app_comm.h"
|
||||
|
||||
#include "syscall/syscall.h"
|
||||
|
||||
void app_comm_set_sniff_interval(const SniffInterval interval) {
|
||||
sys_app_comm_set_responsiveness(interval);
|
||||
}
|
||||
|
||||
SniffInterval app_comm_get_sniff_interval(void) {
|
||||
return sys_app_comm_get_sniff_interval();
|
||||
}
|
||||
|
61
src/fw/applib/app_comm.h
Normal file
61
src/fw/applib/app_comm.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup AppComm App Communication
|
||||
//! \brief API for interacting with the Pebble communication subsystem.
|
||||
//!
|
||||
//! @note To send messages to a remote device, see the \ref AppMessage or
|
||||
//! \ref AppSync modules.
|
||||
//! @{
|
||||
|
||||
//! Intervals during which the Bluetooth module may enter a low power mode.
|
||||
//! The sniff interval defines the period during which the Bluetooth module may
|
||||
//! not exchange (ACL) packets. The longer the sniff interval, the more time the
|
||||
//! Bluetooth module may spend in a low power mode.
|
||||
//! It may be necessary to reduce the sniff interval if an app requires reduced
|
||||
//! latency when sending messages.
|
||||
//! @note These settings have a dramatic effect on the Pebble's energy
|
||||
//! consumption. Use the normal sniff interval whenever possible.
|
||||
//! Note, however, that switching between modes increases power consumption
|
||||
//! during the process. Frequent switching between modes is thus
|
||||
//! discouraged. Ensure you do not drop to normal frequently. The Bluetooth module
|
||||
//! is a major consumer of the Pebble's energy.
|
||||
|
||||
typedef enum {
|
||||
//! Set the sniff interval to normal (power-saving) mode
|
||||
SNIFF_INTERVAL_NORMAL = 0,
|
||||
//! Reduce the sniff interval to increase the responsiveness of the radio at
|
||||
//! the expense of increasing Bluetooth energy consumption by a multiple of 2-5
|
||||
//! (very significant)
|
||||
SNIFF_INTERVAL_REDUCED = 1,
|
||||
} SniffInterval;
|
||||
|
||||
//! Set the Bluetooth module's sniff interval.
|
||||
//! The sniff interval will be restored to normal by the OS after the app's
|
||||
//! de-init handler is called. Set the sniff interval to normal whenever
|
||||
//! possible.
|
||||
void app_comm_set_sniff_interval(const SniffInterval interval);
|
||||
|
||||
//! Get the Bluetooth module's sniff interval
|
||||
//! @return The SniffInterval value corresponding to the current interval
|
||||
SniffInterval app_comm_get_sniff_interval(void);
|
||||
|
||||
//! @} // end addtogroup AppComm
|
||||
//! @} // end addtogroup Foundation
|
27
src/fw/applib/app_exit_reason.c
Normal file
27
src/fw/applib/app_exit_reason.c
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 "app_exit_reason.h"
|
||||
|
||||
#include "syscall/syscall.h"
|
||||
|
||||
AppExitReason app_exit_reason_get(void) {
|
||||
return sys_process_get_exit_reason();
|
||||
}
|
||||
|
||||
void app_exit_reason_set(AppExitReason exit_reason) {
|
||||
sys_process_set_exit_reason(exit_reason);
|
||||
}
|
52
src/fw/applib/app_exit_reason.h
Normal file
52
src/fw/applib/app_exit_reason.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup ExitReason Exit Reason
|
||||
//! \brief API for the application to notify the system of the reason it will exit.
|
||||
//!
|
||||
//! If the application has not specified an exit reason before it exits, then the exit reason will
|
||||
//! default to APP_EXIT_NOT_SPECIFIED.
|
||||
//!
|
||||
//! Only an application can set its exit reason. The system will not modify it.
|
||||
//!
|
||||
//! @{
|
||||
|
||||
//! AppExitReason is used to notify the system of the reason of an application exiting, which may
|
||||
//! affect the part of the system UI that is presented after the application terminates.
|
||||
//! @internal
|
||||
//! New exit reasons may be added in the future. As a best practice, it is recommended to only
|
||||
//! handle the cases needed, rather than trying to handle all possible exit reasons.
|
||||
typedef enum AppExitReason {
|
||||
APP_EXIT_NOT_SPECIFIED = 0, //!< Exit reason not specified
|
||||
APP_EXIT_ACTION_PERFORMED_SUCCESSFULLY, //!< Application performed an action when it exited
|
||||
|
||||
NUM_EXIT_REASONS //!< Number of AppExitReason options
|
||||
} AppExitReason;
|
||||
|
||||
//! Returns the current app exit reason.
|
||||
//! @return The current app exit reason
|
||||
AppExitReason app_exit_reason_get(void);
|
||||
|
||||
//! Set the app exit reason to a new reason.
|
||||
//! @param reason The new app exit reason
|
||||
void app_exit_reason_set(AppExitReason exit_reason);
|
||||
|
||||
//! @} // group ExitReason
|
||||
//! @} // group Foundation
|
72
src/fw/applib/app_focus_service.c
Normal file
72
src/fw/applib/app_focus_service.c
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 "app_focus_service.h"
|
||||
|
||||
#include "event_service_client.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "services/common/event_service.h"
|
||||
|
||||
static void prv_focus_event_handler(PebbleEvent *e, void *context) {
|
||||
AppFocusState *state = app_state_get_app_focus_state();
|
||||
bool in_focus = e->app_focus.in_focus;
|
||||
if (e->type == PEBBLE_APP_WILL_CHANGE_FOCUS_EVENT &&
|
||||
state->handlers.will_focus) {
|
||||
state->handlers.will_focus(in_focus);
|
||||
} else if (e->type == PEBBLE_APP_DID_CHANGE_FOCUS_EVENT &&
|
||||
state->handlers.did_focus) {
|
||||
state->handlers.did_focus(in_focus);
|
||||
}
|
||||
}
|
||||
|
||||
void app_focus_service_subscribe_handlers(AppFocusHandlers handlers) {
|
||||
AppFocusState *state = app_state_get_app_focus_state();
|
||||
|
||||
app_focus_service_unsubscribe();
|
||||
if (handlers.did_focus) {
|
||||
// NOTE: the individual fields of state->did_focus_info are assigned
|
||||
// to instead of writing
|
||||
// state->did_focus_info = (EventServiceInfo) { ... }
|
||||
// as the latter would zero out the ListNode embedded in the struct.
|
||||
// Doing so would corrupt the events list if the event was already
|
||||
// subscribed.
|
||||
state->did_focus_info.type = PEBBLE_APP_DID_CHANGE_FOCUS_EVENT;
|
||||
state->did_focus_info.handler = prv_focus_event_handler;
|
||||
event_service_client_subscribe(&state->did_focus_info);
|
||||
}
|
||||
if (handlers.will_focus) {
|
||||
state->will_focus_info.type = PEBBLE_APP_WILL_CHANGE_FOCUS_EVENT;
|
||||
state->will_focus_info.handler = prv_focus_event_handler;
|
||||
event_service_client_subscribe(&state->will_focus_info);
|
||||
}
|
||||
state->handlers = handlers;
|
||||
}
|
||||
|
||||
void app_focus_service_subscribe(AppFocusHandler handler) {
|
||||
AppFocusHandlers handlers = (AppFocusHandlers) { .will_focus = handler };
|
||||
app_focus_service_subscribe_handlers(handlers);
|
||||
}
|
||||
|
||||
void app_focus_service_unsubscribe(void) {
|
||||
AppFocusState *state = app_state_get_app_focus_state();
|
||||
if (state->handlers.will_focus) {
|
||||
event_service_client_unsubscribe(&state->will_focus_info);
|
||||
}
|
||||
if (state->handlers.did_focus) {
|
||||
event_service_client_unsubscribe(&state->did_focus_info);
|
||||
}
|
||||
state->handlers = (AppFocusHandlers) {};
|
||||
}
|
96
src/fw/applib/app_focus_service.h
Normal file
96
src/fw/applib/app_focus_service.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 <stdbool.h>
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup EventService
|
||||
//! @{
|
||||
//! @addtogroup AppFocusService
|
||||
//!
|
||||
//!
|
||||
//! \brief Handling app focus
|
||||
//! The AppFocusService allows developers to be notified when their apps become visible on the
|
||||
//! screen. Common reasons your app may be running but not be on screen are: it's still in the
|
||||
//! middle of launching and being revealed by a system animation, or it is being covered by a system
|
||||
//! window such as a notification. This service is useful for apps that require a high degree of
|
||||
//! user interactivity, like a game where you'll want to pause when a notification covers your app
|
||||
//! window. It can be also used for apps that want to sync up an intro animation to the end of the
|
||||
//! system animation that occurs before your app is visible.
|
||||
//!
|
||||
//! @{
|
||||
|
||||
//! Callback type for focus events
|
||||
//! @param in_focus True if the app is gaining focus, false otherwise.
|
||||
typedef void (*AppFocusHandler)(bool in_focus);
|
||||
|
||||
//! There are two different focus events which take place when transitioning to and from an app
|
||||
//! being in focus. Below is an example of when these events will occur:
|
||||
//! 1) The app is launched. Once the system animation to the app has completed and the app is
|
||||
//! completely in focus, the did_focus handler is called with in_focus set to true.
|
||||
//! 2) A notification comes in and the animation to show the notification starts. The will_focus
|
||||
//! handler is called with in_focus set to false.
|
||||
//! 3) The animation completes and the notification is in focus, with the app being completely
|
||||
//! covered. The did_focus hander is called with in_focus set to false.
|
||||
//! 4) The notification is dismissed and the animation to return to the app starts. The will_focus
|
||||
//! handler is called with in_focus set to true.
|
||||
//! 5) The animation completes and the app is in focus. The did_focus handler is called with
|
||||
//! in_focus set to true.
|
||||
typedef struct {
|
||||
//! Handler which will be called right before an app will lose or gain focus.
|
||||
//! @note This will be called with in_focus set to true when a window which is covering the app is
|
||||
//! about to close and return focus to the app.
|
||||
//! @note This will be called with in_focus set to false when a window which will cover the app is
|
||||
//! about to open, causing the app to lose focus.
|
||||
AppFocusHandler will_focus;
|
||||
//! Handler which will be called when an animation finished which has put the app into focus or
|
||||
//! taken the app out of focus.
|
||||
//! @note This will be called with in_focus set to true when a window which was covering the app
|
||||
//! has closed and the app has gained focus.
|
||||
//! @note This will be called with in_focus set to false when a window has opened which is now
|
||||
//! covering the app, causing the app to lose focus.
|
||||
AppFocusHandler did_focus;
|
||||
} AppFocusHandlers;
|
||||
|
||||
//! Subscribe to the focus event service. Once subscribed, the handlers get called every time the
|
||||
//! app gains or loses focus.
|
||||
//! @param handler Handlers which will be called on will-focus and did-focus events.
|
||||
//! @see AppFocusHandlers
|
||||
void app_focus_service_subscribe_handlers(AppFocusHandlers handlers);
|
||||
|
||||
//! Subscribe to the focus event service. Once subscribed, the handler
|
||||
//! gets called every time the app focus changes.
|
||||
//! @note Calling this function is equivalent to
|
||||
//! \code{.c}
|
||||
//! app_focus_service_subscribe_handlers((AppFocusHandlers){
|
||||
//! .will_focus = handler,
|
||||
//! });
|
||||
//! \endcode
|
||||
//! @note Out focus events are triggered when a modal window is about to open and cover the app.
|
||||
//! @note In focus events are triggered when a modal window which is covering the app is about to
|
||||
//! close.
|
||||
//! @param handler A callback to be called on will-focus events.
|
||||
void app_focus_service_subscribe(AppFocusHandler handler);
|
||||
|
||||
//! Unsubscribe from the focus event service. Once unsubscribed, the previously
|
||||
//! registered handlers will no longer be called.
|
||||
void app_focus_service_unsubscribe(void);
|
||||
|
||||
//! @} // end addtogroup AppFocusService
|
||||
//! @} // end addtogroup EventService
|
||||
//! @} // end addtogroup Foundation
|
137
src/fw/applib/app_glance.c
Normal file
137
src/fw/applib/app_glance.c
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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 "app_glance.h"
|
||||
|
||||
#include "applib/template_string.h"
|
||||
#include "apps/system_apps/launcher/default/launcher_app_glance_generic.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/normal/blob_db/app_glance_db_private.h"
|
||||
#include "services/normal/timeline/attribute.h"
|
||||
#include "services/normal/timeline/timeline_resources.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "util/string.h"
|
||||
#include "util/uuid.h"
|
||||
|
||||
AppGlanceResult app_glance_add_slice(AppGlanceReloadSession *session, AppGlanceSlice slice) {
|
||||
#if !CAPABILITY_HAS_APP_GLANCES
|
||||
return APP_GLANCE_RESULT_INVALID_SESSION;
|
||||
#endif
|
||||
AppGlanceResult result = APP_GLANCE_RESULT_SUCCESS;
|
||||
|
||||
AppGlance *glance = app_state_get_glance();
|
||||
|
||||
// We use the glance set in the session as a crude way of validating the session
|
||||
// Bail out if the session is invalid
|
||||
if (!session || (glance != session->glance)) {
|
||||
result |= APP_GLANCE_RESULT_INVALID_SESSION;
|
||||
return result;
|
||||
}
|
||||
|
||||
// From here on to the end of the function we can accumulate multiple failures in `result`
|
||||
|
||||
// Check if this slice would put us over the max slices per glance
|
||||
if (glance->num_slices >= APP_GLANCE_DB_MAX_SLICES_PER_GLANCE) {
|
||||
result |= APP_GLANCE_RESULT_SLICE_CAPACITY_EXCEEDED;
|
||||
}
|
||||
|
||||
// Check if this slice's icon is a valid resource (but only if it's not the value that specifies
|
||||
// that the app's default icon should be used)
|
||||
if (slice.layout.icon != APP_GLANCE_SLICE_DEFAULT_ICON) {
|
||||
Uuid app_uuid;
|
||||
sys_get_app_uuid(&app_uuid);
|
||||
const TimelineResourceInfo timeline_resource_info = (TimelineResourceInfo) {
|
||||
.app_id = &app_uuid,
|
||||
.res_id = (TimelineResourceId)slice.layout.icon,
|
||||
};
|
||||
AppResourceInfo app_resource_info = (AppResourceInfo) {};
|
||||
sys_timeline_resources_get_id(&timeline_resource_info,
|
||||
LAUNCHER_APP_GLANCE_GENERIC_ICON_SIZE_TYPE, &app_resource_info);
|
||||
if (app_resource_info.res_id == RESOURCE_ID_INVALID) {
|
||||
result |= APP_GLANCE_RESULT_INVALID_ICON;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the template string is too long (if it's not NULL)
|
||||
// Plus one for the null terminator, then plus one more to become too long
|
||||
const size_t template_string_too_long_size = ATTRIBUTE_APP_GLANCE_SUBTITLE_MAX_LEN + 1 + 1;
|
||||
if (slice.layout.subtitle_template_string &&
|
||||
strnlen(slice.layout.subtitle_template_string,
|
||||
template_string_too_long_size) == template_string_too_long_size) {
|
||||
result |= APP_GLANCE_RESULT_TEMPLATE_STRING_TOO_LONG;
|
||||
}
|
||||
|
||||
// Check if the provided subtitle string is a valid template string, if it's present.
|
||||
const time_t current_time = sys_get_time();
|
||||
if (slice.layout.subtitle_template_string) {
|
||||
const TemplateStringVars template_string_vars = (TemplateStringVars) {
|
||||
.current_time = current_time,
|
||||
};
|
||||
TemplateStringError template_string_error = {0};
|
||||
if (!template_string_evaluate(slice.layout.subtitle_template_string, NULL, 0, NULL,
|
||||
&template_string_vars, &template_string_error)) {
|
||||
result |= APP_GLANCE_RESULT_INVALID_TEMPLATE_STRING;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the slice would expire in the past (if it's not APP_GLANCE_SLICE_NO_EXPIRATION)
|
||||
if ((slice.expiration_time != APP_GLANCE_SLICE_NO_EXPIRATION) &&
|
||||
slice.expiration_time <= current_time) {
|
||||
result |= APP_GLANCE_RESULT_EXPIRES_IN_THE_PAST;
|
||||
}
|
||||
|
||||
// If we haven't failed at this point, we're ready to add the slice to the glance!
|
||||
if (result == APP_GLANCE_RESULT_SUCCESS) {
|
||||
AppGlanceSliceInternal *slice_dest = &glance->slices[glance->num_slices];
|
||||
*slice_dest = (AppGlanceSliceInternal) {
|
||||
.expiration_time = slice.expiration_time,
|
||||
.type = AppGlanceSliceType_IconAndSubtitle,
|
||||
.icon_and_subtitle.icon_resource_id = slice.layout.icon,
|
||||
};
|
||||
// We already checked that the provided template string fits, so use strcpy instead of strncpy
|
||||
if (slice.layout.subtitle_template_string) {
|
||||
strcpy(slice_dest->icon_and_subtitle.template_string, slice.layout.subtitle_template_string);
|
||||
}
|
||||
glance->num_slices++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void app_glance_reload(AppGlanceReloadCallback callback, void *context) {
|
||||
#if !CAPABILITY_HAS_APP_GLANCES
|
||||
return;
|
||||
#endif
|
||||
|
||||
AppGlance *glance = app_state_get_glance();
|
||||
|
||||
Uuid current_app_uuid;
|
||||
sys_get_app_uuid(¤t_app_uuid);
|
||||
|
||||
// Zero out the glance
|
||||
app_glance_service_init_glance(glance);
|
||||
|
||||
if (callback) {
|
||||
// Create a "reload session" on the stack that wraps the glance, and then call the user callback
|
||||
AppGlanceReloadSession session = (AppGlanceReloadSession) {
|
||||
.glance = glance,
|
||||
};
|
||||
callback(&session, APP_GLANCE_DB_MAX_SLICES_PER_GLANCE, context);
|
||||
}
|
||||
|
||||
sys_app_glance_update(¤t_app_uuid, glance);
|
||||
}
|
106
src/fw/applib/app_glance.h
Normal file
106
src/fw/applib/app_glance.h
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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 "services/normal/app_glances/app_glance_service.h"
|
||||
#include "util/time/time.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup AppGlance App Glance
|
||||
//! \brief API for the application to modify its glance.
|
||||
//!
|
||||
//! @{
|
||||
|
||||
//! The ID of a published app resource defined within the publishedMedia section of package.json.
|
||||
typedef uint32_t PublishedId;
|
||||
|
||||
//! Can be used for the expiration_time of an \ref AppGlanceSlice so that the slice never expires.
|
||||
#define APP_GLANCE_SLICE_NO_EXPIRATION ((time_t)0)
|
||||
|
||||
//! Can be used for the icon of an \ref AppGlanceSlice so that the slice displays the app's default
|
||||
//! icon.
|
||||
#define APP_GLANCE_SLICE_DEFAULT_ICON ((PublishedId)0)
|
||||
|
||||
//! An app's glance can change over time as defined by zero or more app glance slices that each
|
||||
//! describe the state of the app glance at a particular point in time. Slices are displayed in the
|
||||
//! order they are added, and they are removed at the specified expiration time.
|
||||
typedef struct AppGlanceSlice {
|
||||
//! Describes how the slice should be visualized in the app's glance in the launcher.
|
||||
struct {
|
||||
//! The published resource ID of the bitmap icon to display in the app's glance. Use \ref
|
||||
//! APP_GLANCE_SLICE_DEFAULT_ICON to use the app's default bitmap icon.
|
||||
PublishedId icon;
|
||||
//! A template string to visualize in the app's glance. The string will be copied, so it is safe
|
||||
//! to destroy after adding the slice to the glance. Use NULL if no string should be displayed.
|
||||
const char *subtitle_template_string;
|
||||
} layout;
|
||||
//! The UTC time after which this slice should no longer be shown in the app's glance. Use \ref
|
||||
//! APP_GLANCE_SLICE_NO_EXPIRATION if the slice should never expire.
|
||||
time_t expiration_time;
|
||||
} AppGlanceSlice;
|
||||
|
||||
//! Bitfield enum describing the result of trying to add an AppGlanceSlice to an app's glance.
|
||||
typedef enum AppGlanceResult {
|
||||
//! The slice was successfully added to the app's glance.
|
||||
APP_GLANCE_RESULT_SUCCESS = 0,
|
||||
//! The subtitle_template_string provided in the slice was invalid.
|
||||
APP_GLANCE_RESULT_INVALID_TEMPLATE_STRING = 1 << 0,
|
||||
//! The subtitle_template_string provided in the slice was longer than 150 bytes.
|
||||
APP_GLANCE_RESULT_TEMPLATE_STRING_TOO_LONG = 1 << 1,
|
||||
//! The icon provided in the slice was invalid.
|
||||
APP_GLANCE_RESULT_INVALID_ICON = 1 << 2,
|
||||
//! The provided slice would exceed the app glance's slice capacity.
|
||||
APP_GLANCE_RESULT_SLICE_CAPACITY_EXCEEDED = 1 << 3,
|
||||
//! The expiration_time provided in the slice expires in the past.
|
||||
APP_GLANCE_RESULT_EXPIRES_IN_THE_PAST = 1 << 4,
|
||||
//! The \ref AppGlanceReloadSession provided was invalid.
|
||||
APP_GLANCE_RESULT_INVALID_SESSION = 1 << 5,
|
||||
} AppGlanceResult;
|
||||
|
||||
//! A session variable that is provided in an \ref AppGlanceReloadCallback and must be used when
|
||||
//! adding slices to the app's glance via \ref app_glance_add_slice.
|
||||
typedef struct AppGlanceReloadSession AppGlanceReloadSession;
|
||||
struct AppGlanceReloadSession {
|
||||
AppGlance *glance;
|
||||
};
|
||||
|
||||
//! Add a slice to the app's glance. This function will only succeed if called with a valid
|
||||
//! \ref AppGlanceReloadSession that is provided in an \ref AppGlanceReloadCallback.
|
||||
//! @param session The session variable provided in an \ref AppGlanceReloadCallback
|
||||
//! @param slice The slice to add to the app's glance
|
||||
//! @return The result of trying to add the slice to the app's glance
|
||||
AppGlanceResult app_glance_add_slice(AppGlanceReloadSession *session, AppGlanceSlice slice);
|
||||
|
||||
//! User-provided callback for reloading the slices in the app's glance.
|
||||
//! @param session A session variable that must be passed to \ref app_glance_add_slice when adding
|
||||
//! slices to the app's glance; it becomes invalid when the \ref AppGlanceReloadCallback returns
|
||||
//! @param limit The number of entries that can be added to the app's glance
|
||||
//! @param context User-provided context provided when calling \ref app_glance_reload()
|
||||
typedef void (*AppGlanceReloadCallback)(AppGlanceReloadSession *session, size_t limit,
|
||||
void *context);
|
||||
|
||||
//! Clear any existing slices in the app's glance and trigger a reload via the provided callback.
|
||||
//! @param callback A function that will be called to add new slices to the app's glance; even if
|
||||
//! the provided callback is NULL, any existing slices will still be cleared from the app's glance
|
||||
//! @param context User-provided context that will be passed to the callback
|
||||
void app_glance_reload(AppGlanceReloadCallback callback, void *context);
|
||||
|
||||
//! @} // group AppGlance
|
||||
//! @} // group Foundation
|
87
src/fw/applib/app_heap_analytics.c
Normal file
87
src/fw/applib/app_heap_analytics.c
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 "app_heap_analytics.h"
|
||||
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "kernel/pebble_tasks.h"
|
||||
#include "services/common/analytics/analytics_event.h"
|
||||
#include "syscall/syscall.h"
|
||||
|
||||
#if CAPABILITY_HAS_JAVASCRIPT && !RECOVERY_FW
|
||||
#include "jmem-heap.h"
|
||||
#endif
|
||||
|
||||
#include <util/heap.h>
|
||||
|
||||
static bool prv_is_current_task_app_or_worker(void) {
|
||||
switch (pebble_task_get_current()) {
|
||||
case PebbleTask_App:
|
||||
case PebbleTask_Worker:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void app_heap_analytics_log_native_heap_oom_fault(size_t requested_size, Heap *heap) {
|
||||
if (!prv_is_current_task_app_or_worker()) {
|
||||
return;
|
||||
}
|
||||
const uint32_t total_size = heap_size(heap);
|
||||
unsigned int used = 0;
|
||||
unsigned int total_free = 0;
|
||||
unsigned int largest_free_block = 0;
|
||||
heap_calc_totals(heap, &used, &total_free, &largest_free_block);
|
||||
analytics_event_app_oom(AnalyticsEvent_AppOOMNative, requested_size,
|
||||
total_size, total_free, largest_free_block);
|
||||
}
|
||||
|
||||
void app_heap_analytics_log_rocky_heap_oom_fault(void) {
|
||||
#if CAPABILITY_HAS_JAVASCRIPT && !RECOVERY_FW
|
||||
if (!prv_is_current_task_app_or_worker()) {
|
||||
return;
|
||||
}
|
||||
|
||||
jmem_heap_stats_t jerry_mem_stats = {};
|
||||
jmem_heap_get_stats(&jerry_mem_stats);
|
||||
const size_t requested_size = 0; // not available unfortunately
|
||||
const size_t free = (jerry_mem_stats.size - jerry_mem_stats.allocated_bytes);
|
||||
analytics_event_app_oom(AnalyticsEvent_AppOOMRocky, requested_size,
|
||||
jerry_mem_stats.size, free, jerry_mem_stats.largest_free_block_bytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_heap_analytics_log_stats_to_app_heartbeat(bool is_rocky_app) {
|
||||
Heap *const heap = task_heap_get_for_current_task();
|
||||
sys_analytics_max(ANALYTICS_APP_METRIC_MEM_NATIVE_HEAP_SIZE, heap_size(heap),
|
||||
AnalyticsClient_CurrentTask);
|
||||
sys_analytics_max(ANALYTICS_APP_METRIC_MEM_NATIVE_HEAP_PEAK, heap->high_water_mark,
|
||||
AnalyticsClient_CurrentTask);
|
||||
#if CAPABILITY_HAS_JAVASCRIPT && !RECOVERY_FW
|
||||
|
||||
if (is_rocky_app) {
|
||||
jmem_heap_stats_t jerry_mem_stats = {};
|
||||
jmem_heap_get_stats(&jerry_mem_stats);
|
||||
sys_analytics_max(ANALYTICS_APP_METRIC_MEM_ROCKY_HEAP_PEAK,
|
||||
jerry_mem_stats.global_peak_allocated_bytes,
|
||||
AnalyticsClient_CurrentTask);
|
||||
sys_analytics_max(ANALYTICS_APP_METRIC_MEM_ROCKY_HEAP_WASTE,
|
||||
jerry_mem_stats.global_peak_waste_bytes,
|
||||
AnalyticsClient_CurrentTask);
|
||||
}
|
||||
#endif
|
||||
}
|
32
src/fw/applib/app_heap_analytics.h
Normal file
32
src/fw/applib/app_heap_analytics.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct Heap Heap;
|
||||
|
||||
//! Logs an analytics event about the native heap OOM fault.
|
||||
void app_heap_analytics_log_native_heap_oom_fault(size_t requested_size, Heap *heap);
|
||||
|
||||
//! Logs an analytics event about the JerryScript heap OOM fault.
|
||||
void app_heap_analytics_log_rocky_heap_oom_fault(void);
|
||||
|
||||
//! Captures native heap and given JerryScript heap stats to the app heartbeat.
|
||||
//! @param jerry_mem_stats JerryScript heap stats on NULL if not available.
|
||||
void app_heap_analytics_log_stats_to_app_heartbeat(bool is_rocky_app);
|
47
src/fw/applib/app_heap_util.c
Normal file
47
src/fw/applib/app_heap_util.c
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 "app_heap_util.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "process_state/worker_state/worker_state.h"
|
||||
#include "kernel/pebble_tasks.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/heap.h"
|
||||
|
||||
static Heap* get_task_heap(void) {
|
||||
PebbleTask task = pebble_task_get_current();
|
||||
Heap *heap = NULL;
|
||||
|
||||
if (task == PebbleTask_App) {
|
||||
heap = app_state_get_heap();
|
||||
} else if (task == PebbleTask_Worker) {
|
||||
heap = worker_state_get_heap();
|
||||
} else {
|
||||
WTF;
|
||||
}
|
||||
|
||||
return heap;
|
||||
}
|
||||
|
||||
size_t heap_bytes_used(void) {
|
||||
Heap *heap = get_task_heap();
|
||||
return heap->current_size;
|
||||
}
|
||||
|
||||
size_t heap_bytes_free(void) {
|
||||
Heap *heap = get_task_heap();
|
||||
return heap_size(heap) - heap->current_size;
|
||||
}
|
37
src/fw/applib/app_heap_util.h
Normal file
37
src/fw/applib/app_heap_util.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 <stddef.h>
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup MemoryManagement Memory Management
|
||||
//! \brief Utility functions for managing an application's memory.
|
||||
//!
|
||||
//! @{
|
||||
|
||||
//! Calculates the number of bytes of heap memory currently being used by the application.
|
||||
//! @return The number of bytes on the heap currently being used.
|
||||
size_t heap_bytes_used(void);
|
||||
|
||||
//! Calculates the number of bytes of heap memory \a not currently being used by the application.
|
||||
//! @return The number of bytes on the heap not currently being used.
|
||||
size_t heap_bytes_free(void);
|
||||
|
||||
//! @} // end addtogroup MemoryManagement
|
||||
//! @} // end addtogroup Foundation
|
60
src/fw/applib/app_inbox.c
Normal file
60
src/fw/applib/app_inbox.c
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 "app_inbox.h"
|
||||
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "services/normal/app_inbox_service.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
AppInbox *app_inbox_create_and_register(size_t buffer_size, uint32_t min_num_messages,
|
||||
AppInboxMessageHandler message_handler,
|
||||
AppInboxDroppedHandler dropped_handler) {
|
||||
if (!message_handler) {
|
||||
return NULL;
|
||||
}
|
||||
if (!buffer_size) {
|
||||
return NULL;
|
||||
}
|
||||
if (!min_num_messages) {
|
||||
return NULL;
|
||||
}
|
||||
buffer_size += (min_num_messages * sizeof(AppInboxMessageHeader));
|
||||
uint8_t *buffer = applib_zalloc(buffer_size);
|
||||
if (!buffer) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Not enough memory to allocate App Inbox of size %"PRIu32,
|
||||
(uint32_t)buffer_size);
|
||||
return NULL;
|
||||
}
|
||||
if (!sys_app_inbox_service_register(buffer, buffer_size, message_handler, dropped_handler)) {
|
||||
applib_free(buffer);
|
||||
return NULL;
|
||||
}
|
||||
return (AppInbox *) buffer;
|
||||
}
|
||||
|
||||
uint32_t app_inbox_destroy_and_deregister(AppInbox *app_inbox) {
|
||||
uint8_t *buffer = (uint8_t *)app_inbox;
|
||||
uint32_t num_messages_lost = sys_app_inbox_service_unregister(buffer);
|
||||
applib_free(buffer);
|
||||
return num_messages_lost;
|
||||
}
|
||||
|
||||
void app_inbox_consume(AppInboxConsumerInfo *consumer_info) {
|
||||
sys_app_inbox_service_consume(consumer_info);
|
||||
}
|
59
src/fw/applib/app_inbox.h
Normal file
59
src/fw/applib/app_inbox.h
Normal 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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct AppInboxConsumerInfo AppInboxConsumerInfo;
|
||||
|
||||
//! @param data Pointer to the available data.
|
||||
//! @param length The length of the available data.
|
||||
//! @param consumer_info Opaque context object to be passed into app_inbox_consume(). It is NOT
|
||||
//! mandatory for the handler to call app_inbox_consume().
|
||||
typedef void (*AppInboxMessageHandler)(const uint8_t *data, size_t length,
|
||||
AppInboxConsumerInfo *consumer_info);
|
||||
|
||||
typedef void (*AppInboxDroppedHandler)(uint32_t num_dropped_messages);
|
||||
|
||||
//! Opaque reference to an app inbox.
|
||||
typedef struct AppInbox AppInbox;
|
||||
|
||||
//! @param min_num_messages The minimum number of messages that the inbox should be able to hold
|
||||
//! if the total payload size is exactly buffer_size. This is used to calculate how much additional
|
||||
//! buffer space has to be allocated for message header overhead.
|
||||
//! @param message_handler The callback that will handle received messages (required).
|
||||
//! @param dropped_handler The callback that will handle dropped messages (optional, use NULL if
|
||||
//! not needed).
|
||||
//! @note The system only allows certain handlers, see prv_tag_for_event_handlers()
|
||||
//! in app_inbox_service.c.
|
||||
//! @return An opaque value that can be used with app_inbox_destroy_and_deregister, or NULL
|
||||
//! if the process failed.
|
||||
AppInbox *app_inbox_create_and_register(size_t buffer_size, uint32_t min_num_messages,
|
||||
AppInboxMessageHandler message_handler,
|
||||
AppInboxDroppedHandler dropped_handler);
|
||||
|
||||
//! @param app_inbox_ref The app inbox to destroy, pass in the value that
|
||||
//! app_inbox_create_and_register returned.
|
||||
//! @return The number of messages that were dropped, plus the ones that were still waiting
|
||||
//! to be consumed.
|
||||
uint32_t app_inbox_destroy_and_deregister(AppInbox *app_inbox_ref);
|
||||
|
||||
//! Call this function from a AppInboxMessageHandler to immediately consume the message and free
|
||||
//! up the space in the buffer that was occupied by the message.
|
||||
//! @param consume_info The opaque context object as passed into the AppInboxMessageHandler.
|
||||
void app_inbox_consume(AppInboxConsumerInfo *consume_info);
|
23
src/fw/applib/app_launch_button.c
Normal file
23
src/fw/applib/app_launch_button.c
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 "app_launch_button.h"
|
||||
|
||||
#include "syscall/syscall.h"
|
||||
|
||||
ButtonId app_launch_button(void) {
|
||||
return sys_process_get_launch_button();
|
||||
}
|
24
src/fw/applib/app_launch_button.h
Normal file
24
src/fw/applib/app_launch_button.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 "drivers/button_id.h"
|
||||
|
||||
//! @internal
|
||||
//! Get the button id used to launch the app.
|
||||
//! Only valid if the launch reason is APP_LAUNCH_USER or APP_LAUNCH_QUICK_LAUNCH.
|
||||
ButtonId app_launch_button(void);
|
29
src/fw/applib/app_launch_reason.c
Normal file
29
src/fw/applib/app_launch_reason.c
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 "app_launch_reason.h"
|
||||
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "syscall/syscall.h"
|
||||
|
||||
AppLaunchReason app_launch_reason(void) {
|
||||
return sys_process_get_launch_reason();
|
||||
}
|
||||
|
||||
|
||||
uint32_t app_launch_get_args(void) {
|
||||
return sys_process_get_launch_args();
|
||||
}
|
61
src/fw/applib/app_launch_reason.h
Normal file
61
src/fw/applib/app_launch_reason.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup LaunchReason Launch Reason
|
||||
//! \brief API for checking what caused the application to launch.
|
||||
//!
|
||||
//! This includes the system, launch by user interaction (User selects the
|
||||
//! application from the launcher menu),
|
||||
//! launch by the mobile or a mobile companion application,
|
||||
//! or launch by a scheduled wakeup event for the specified application.
|
||||
//!
|
||||
//! @{
|
||||
|
||||
//! AppLaunchReason is used to inform the application about how it was launched
|
||||
//! @note New launch reasons may be added in the future. As a best practice, it
|
||||
//! is recommended to only handle the cases that the app needs to know about,
|
||||
//! rather than trying to handle all possible launch reasons.
|
||||
typedef enum {
|
||||
APP_LAUNCH_SYSTEM = 0, //!< App launched by the system
|
||||
APP_LAUNCH_USER, //!< App launched by user selection in launcher menu
|
||||
APP_LAUNCH_PHONE, //!< App launched by mobile or companion app
|
||||
APP_LAUNCH_WAKEUP, //!< App launched by wakeup event
|
||||
APP_LAUNCH_WORKER, //!< App launched by worker calling worker_launch_app()
|
||||
APP_LAUNCH_QUICK_LAUNCH, //!< App launched by user using quick launch
|
||||
APP_LAUNCH_TIMELINE_ACTION, //!< App launched by user opening it from a pin
|
||||
APP_LAUNCH_SMARTSTRAP, //!< App launched by a smartstrap
|
||||
} AppLaunchReason;
|
||||
|
||||
//! Provides the method used to launch the current application.
|
||||
//! @return The method or reason the current application was launched
|
||||
AppLaunchReason app_launch_reason(void);
|
||||
|
||||
//! Get the argument passed to the app when it was launched.
|
||||
//! @note Currently the only way to pass arguments to apps is by using an openWatchApp action
|
||||
//! on a pin.
|
||||
//! @return The argument passed to the app, or 0 if the app wasn't launched from a Launch App action
|
||||
uint32_t app_launch_get_args(void);
|
||||
|
||||
//! @} // group Launch_Reason
|
||||
//! @} // group Foundation
|
||||
|
25
src/fw/applib/app_light.c
Normal file
25
src/fw/applib/app_light.c
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 "syscall/syscall.h"
|
||||
|
||||
void app_light_enable_interaction(void) {
|
||||
sys_light_enable_interaction();
|
||||
}
|
||||
|
||||
void app_light_enable(bool enable) {
|
||||
sys_light_enable(enable);
|
||||
}
|
44
src/fw/applib/app_light.h
Normal file
44
src/fw/applib/app_light.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 <stdbool.h>
|
||||
|
||||
//! @file light.h
|
||||
//! @addtogroup UI
|
||||
//! @{
|
||||
//! @addtogroup Light Light
|
||||
//! \brief Controlling Pebble's backlight
|
||||
//!
|
||||
//! The Light API provides you with functions to turn on Pebble’s backlight or
|
||||
//! put it back into automatic control. You can trigger the backlight and schedule a timer
|
||||
//! to automatically disable the backlight after a short delay, which is the preferred
|
||||
//! method of interacting with the backlight.
|
||||
//! @{
|
||||
|
||||
//! Trigger the backlight and schedule a timer to automatically disable the backlight
|
||||
//! after a short delay. This is the preferred method of interacting with the backlight.
|
||||
void app_light_enable_interaction(void);
|
||||
|
||||
//! Turn the watch's backlight on or put it back into automatic control.
|
||||
//! Developers should take care when calling this function, keeping Pebble's backlight on for long periods of time
|
||||
//! will rapidly deplete the battery.
|
||||
//! @param enable Turn the backlight on if `true`, otherwise `false` to put it back into automatic control.
|
||||
void app_light_enable(bool enable);
|
||||
|
||||
//! @} // group Light
|
||||
//! @} // group UI
|
47
src/fw/applib/app_logging.c
Normal file
47
src/fw/applib/app_logging.c
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 "applib/app_logging.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// FIXME PBL-1629: move needed declarations into applib
|
||||
#include "syscall/syscall.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "services/common/comm_session/session.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
void app_log_vargs(uint8_t log_level, const char* src_filename, int src_line_number, const char* fmt, va_list args) {
|
||||
char log_buffer[LOG_BUFFER_LENGTH];
|
||||
_Static_assert(sizeof(log_buffer) > sizeof(AppLogBinaryMessage), "log_buffer too small for AppLogBinaryMessage");
|
||||
|
||||
AppLogBinaryMessage* msg = (AppLogBinaryMessage*)log_buffer;
|
||||
sys_get_app_uuid(&msg->uuid);
|
||||
const size_t log_msg_offset = offsetof(AppLogBinaryMessage, log_msg);
|
||||
int bin_msg_length = pbl_log_binary_format((char*)&msg->log_msg, sizeof(log_buffer) - log_msg_offset, log_level,
|
||||
src_filename, src_line_number, fmt, args);
|
||||
sys_app_log(log_msg_offset + bin_msg_length, log_buffer);
|
||||
}
|
||||
|
||||
void app_log(uint8_t log_level, const char* src_filename, int src_line_number, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
app_log_vargs(log_level, src_filename, src_line_number, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
101
src/fw/applib/app_logging.h
Normal file
101
src/fw/applib/app_logging.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*!
|
||||
@file app_logging.h
|
||||
@brief Interface for the SDK's App Logging API.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/uuid.h"
|
||||
|
||||
// FIXME PBL-1629: move needed declarations into applib
|
||||
#include "kernel/logging_private.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup Logging Logging
|
||||
//! \brief Functions related to logging from apps.
|
||||
//!
|
||||
//! This module contains the functions necessary to log messages through
|
||||
//! Bluetooth.
|
||||
//! @note It is no longer necessary to enable app logging output from the "settings->about" menu on the Pebble for
|
||||
//! them to be transmitted! Instead use the "pebble logs" command included with the SDK to activate logs. The logs
|
||||
//! will appear right in your console. Logging
|
||||
//! over Bluetooth is a fairly power hungry operation that non-developers will
|
||||
//! not need when your apps are distributed.
|
||||
//! @{
|
||||
|
||||
// @internal
|
||||
// Log an app message, takes a va_list rather than varags
|
||||
// @see app_log
|
||||
void app_log_vargs(uint8_t log_level, const char *src_filename, int src_line_number,
|
||||
const char *fmt, va_list args);
|
||||
|
||||
//! Log an app message.
|
||||
//! @param log_level
|
||||
//! @param src_filename The source file where the log originates from
|
||||
//! @param src_line_number The line number in the source file where the log originates from
|
||||
//! @param fmt A C formatting string
|
||||
//! @param ... The arguments for the formatting string
|
||||
//! @param log_level
|
||||
//! \sa snprintf for details about the C formatting string.
|
||||
#if __clang__
|
||||
void app_log(uint8_t log_level, const char* src_filename, int src_line_number, const char* fmt,
|
||||
...);
|
||||
#else
|
||||
void app_log(uint8_t log_level, const char* src_filename, int src_line_number, const char* fmt,
|
||||
...) __attribute__((format(printf, 4, 5)));
|
||||
#endif
|
||||
|
||||
//! A helper macro that simplifies the use of the app_log function
|
||||
//! @param level The log level to log output as
|
||||
//! @param fmt A C formatting string
|
||||
//! @param args The arguments for the formatting string
|
||||
#define APP_LOG(level, fmt, args...) \
|
||||
app_log(level, __FILE_NAME__, __LINE__, fmt, ## args)
|
||||
|
||||
//! Suggested log level values
|
||||
typedef enum {
|
||||
//! Error level log message
|
||||
APP_LOG_LEVEL_ERROR = 1,
|
||||
//! Warning level log message
|
||||
APP_LOG_LEVEL_WARNING = 50,
|
||||
//! Info level log message
|
||||
APP_LOG_LEVEL_INFO = 100,
|
||||
//! Debug level log message
|
||||
APP_LOG_LEVEL_DEBUG = 200,
|
||||
//! Verbose Debug level log message
|
||||
APP_LOG_LEVEL_DEBUG_VERBOSE = 255,
|
||||
} AppLogLevel;
|
||||
|
||||
//! @}
|
||||
//! @}
|
||||
|
||||
typedef enum AppLoggingMode {
|
||||
AppLoggingDisabled = 0,
|
||||
AppLoggingEnabled = 1,
|
||||
NumAppLoggingModes
|
||||
} AppLoggingMode;
|
||||
|
||||
typedef struct __attribute__((__packed__)) AppLogBinaryMessage {
|
||||
Uuid uuid;
|
||||
LogBinaryMessage log_msg;
|
||||
} AppLogBinaryMessage;
|
240
src/fw/applib/app_message/app_message.c
Normal file
240
src/fw/applib/app_message/app_message.c
Normal file
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* 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 "applib/app_message/app_message.h"
|
||||
#include "applib/app_message/app_message_internal.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "services/common/comm_session/protocol.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
// -------- Initialization ---------------------------------------------------------------------- //
|
||||
|
||||
void app_message_init(void) {
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
*app_message_ctx = (const AppMessageCtx) {};
|
||||
}
|
||||
|
||||
// -------- Pebble Protocol Handlers ------------------------------------------------------------ //
|
||||
|
||||
static bool prv_has_invalid_header_length(size_t length) {
|
||||
if (length < sizeof(AppMessageHeader)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Too short");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//! The new implementation uses up to 72 bytes more stack space than the previous implementation.
|
||||
//! Might need to do some extra work to get a "thinner" stack, if this causes issues.
|
||||
//! Executes on App task.
|
||||
void app_message_app_protocol_msg_callback(CommSession *session,
|
||||
const uint8_t* data, size_t length,
|
||||
AppInboxConsumerInfo *consumer_info) {
|
||||
if (prv_has_invalid_header_length(length)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppMessageHeader *message = (AppMessageHeader *) data;
|
||||
switch (message->command) {
|
||||
|
||||
case CMD_PUSH:
|
||||
// Incoming message:
|
||||
app_message_inbox_receive(session, (AppMessagePush *) message, length, consumer_info);
|
||||
return;
|
||||
|
||||
case CMD_REQUEST:
|
||||
// Incoming request for an update push:
|
||||
// TODO PBL-1636: decide to implement CMD_REQUEST, or remove it
|
||||
return;
|
||||
|
||||
case CMD_ACK:
|
||||
case CMD_NACK:
|
||||
// Received ACK/NACK in response to previously pushed update:
|
||||
app_message_out_handle_ack_nack_received(message);
|
||||
return;
|
||||
|
||||
default:
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Unknown Cmd 0x%x", message->command);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//! Executes on KernelBG, sends back NACK on behalf of the app if it is not able to do so.
|
||||
//! Note that app_message_receiver_dropped_handler will also get called on the App task,
|
||||
//! to report the number of missed messages.
|
||||
void app_message_app_protocol_system_nack_callback(CommSession *session,
|
||||
const uint8_t* data, size_t length) {
|
||||
if (prv_has_invalid_header_length(length)) {
|
||||
return;
|
||||
}
|
||||
AppMessageHeader *message = (AppMessageHeader *) data;
|
||||
if (message->command != CMD_PUSH) {
|
||||
return;
|
||||
}
|
||||
app_message_inbox_send_ack_nack_reply(session, message->transaction_id, CMD_NACK);
|
||||
}
|
||||
|
||||
// -------- Developer Interface ----------------------------------------------------------------- //
|
||||
|
||||
void *app_message_get_context(void) {
|
||||
return app_state_get_app_message_ctx()->inbox.user_context;
|
||||
}
|
||||
|
||||
void *app_message_set_context(void *context) {
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
void *retval = app_message_ctx->inbox.user_context;
|
||||
app_message_ctx->inbox.user_context = context;
|
||||
app_message_ctx->outbox.user_context = context;
|
||||
return retval;
|
||||
}
|
||||
|
||||
AppMessageInboxReceived app_message_register_inbox_received(
|
||||
AppMessageInboxReceived received_callback) {
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
AppMessageInboxReceived retval = app_message_ctx->inbox.received_callback;
|
||||
app_message_ctx->inbox.received_callback = received_callback;
|
||||
return retval;
|
||||
}
|
||||
|
||||
AppMessageInboxDropped app_message_register_inbox_dropped(AppMessageInboxDropped dropped_callback) {
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
AppMessageInboxDropped retval = app_message_ctx->inbox.dropped_callback;
|
||||
app_message_ctx->inbox.dropped_callback = dropped_callback;
|
||||
return retval;
|
||||
}
|
||||
|
||||
AppMessageOutboxSent app_message_register_outbox_sent(AppMessageOutboxSent sent_callback) {
|
||||
AppMessageOutboxSent retval = app_state_get_app_message_ctx()->outbox.sent_callback;
|
||||
app_state_get_app_message_ctx()->outbox.sent_callback = sent_callback;
|
||||
return retval;
|
||||
}
|
||||
|
||||
AppMessageOutboxFailed app_message_register_outbox_failed(AppMessageOutboxFailed failed_callback) {
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
AppMessageOutboxFailed retval = app_message_ctx->outbox.failed_callback;
|
||||
app_message_ctx->outbox.failed_callback = failed_callback;
|
||||
return retval;
|
||||
}
|
||||
|
||||
void app_message_deregister_callbacks(void) {
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
app_message_ctx->inbox.received_callback = NULL;
|
||||
app_message_ctx->inbox.dropped_callback = NULL;
|
||||
app_message_ctx->inbox.user_context = NULL;
|
||||
app_message_ctx->outbox.sent_callback = NULL;
|
||||
app_message_ctx->outbox.failed_callback = NULL;
|
||||
app_message_ctx->outbox.user_context = NULL;
|
||||
}
|
||||
|
||||
static bool prv_supports_8k(void) {
|
||||
if (!sys_app_pp_has_capability(CommSessionAppMessage8kSupport)) {
|
||||
return false;
|
||||
}
|
||||
const Version app_sdk_version = sys_get_current_app_sdk_version();
|
||||
const Version sdk_version_8k_messages_enabled = (const Version) { 0x05, 0x3f };
|
||||
return (version_compare(sdk_version_8k_messages_enabled, app_sdk_version) <= 0);
|
||||
}
|
||||
|
||||
uint32_t app_message_inbox_size_maximum(void) {
|
||||
if (prv_supports_8k()) {
|
||||
// New behavior, allow up to one large 8K byte array per message:
|
||||
return (APP_MSG_8K_DICT_SIZE);
|
||||
} else {
|
||||
// Legacy behavior:
|
||||
if (sys_get_current_app_is_js_allowed()) {
|
||||
return (COMM_PRIVATE_MAX_INBOUND_PAYLOAD_SIZE - APP_MSG_HDR_OVRHD_SIZE);
|
||||
} else {
|
||||
return (COMM_PUBLIC_MAX_INBOUND_PAYLOAD_SIZE - APP_MSG_HDR_OVRHD_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t app_message_outbox_size_maximum(void) {
|
||||
if (prv_supports_8k()) {
|
||||
return (APP_MSG_8K_DICT_SIZE);
|
||||
} else {
|
||||
// Legacy behavior:
|
||||
return (APP_MESSAGE_OUTBOX_SIZE_MINIMUM + APP_MSG_HDR_OVRHD_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
AppMessageResult app_message_open(const uint32_t size_inbound, const uint32_t size_outbound) {
|
||||
// We're making this assumption in this file; here's as good a place to check it as any.
|
||||
// It's probably not super-bad if this isn't true, but we'll have type casts between different
|
||||
// sizes without over/underflow verification.
|
||||
#ifndef UNITTEST
|
||||
_Static_assert(sizeof(size_t) == sizeof(uint32_t), "sizeof(size_t) != sizeof(uint32_t)");
|
||||
#endif
|
||||
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
if (app_message_ctx->outbox.phase != OUT_CLOSED ||
|
||||
app_message_ctx->inbox.is_open) {
|
||||
return APP_MSG_INVALID_STATE; // Already open
|
||||
}
|
||||
|
||||
AppMessageResult result = app_message_outbox_open(&app_message_ctx->outbox, size_outbound);
|
||||
if (APP_MSG_OK != result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = app_message_inbox_open(&app_message_ctx->inbox, size_inbound);
|
||||
if (APP_MSG_OK != result) {
|
||||
app_message_outbox_close(&app_message_ctx->outbox);
|
||||
return result;
|
||||
}
|
||||
|
||||
return APP_MSG_OK;
|
||||
}
|
||||
|
||||
void app_message_close(void) {
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
|
||||
// TODO PBL-1634: handle the the return status when this function returns status.
|
||||
// For now, continue to ignore failure.
|
||||
app_message_outbox_close(&app_message_ctx->outbox);
|
||||
app_message_inbox_close(&app_message_ctx->inbox);
|
||||
|
||||
app_message_deregister_callbacks();
|
||||
}
|
||||
|
||||
// -------- Testing Interface (only) ------------------------------------------------------------ //
|
||||
|
||||
AppTimer *app_message_ack_timer_id(void) {
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
return app_message_ctx->outbox.ack_nack_timer;
|
||||
}
|
||||
|
||||
bool app_message_is_accepting_inbound(void) {
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
return app_message_ctx->inbox.is_open;
|
||||
}
|
||||
|
||||
bool app_message_is_accepting_outbound(void) {
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
return (app_message_ctx->outbox.phase == OUT_ACCEPTING);
|
||||
}
|
||||
|
||||
bool app_message_is_closed_inbound(void) {
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
return (!app_message_ctx->inbox.is_open);
|
||||
}
|
||||
|
||||
bool app_message_is_closed_outbound(void) {
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
return (app_message_ctx->outbox.phase == OUT_CLOSED);
|
||||
}
|
418
src/fw/applib/app_message/app_message.h
Normal file
418
src/fw/applib/app_message/app_message.h
Normal file
|
@ -0,0 +1,418 @@
|
|||
/*
|
||||
* 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 "util/dict.h"
|
||||
#include "util/list.h"
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup AppMessage
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//! \brief Bi-directional communication between phone apps and Pebble watchapps
|
||||
//!
|
||||
//! AppMessage is a bi-directional messaging subsystem that enables communication between phone apps
|
||||
//! and Pebble watchapps. This is accomplished by allowing phone and watchapps to exchange arbitrary
|
||||
//! sets of key/value pairs. The key/value pairs are stored in the form of a Dictionary, the layout
|
||||
//! of which is left for the application developer to define.
|
||||
//!
|
||||
//! AppMessage implements a push-oriented messaging protocol, enabling your app to call functions and
|
||||
//! methods to push messages from Pebble to phone and vice versa. The protocol is symmetric: both Pebble
|
||||
//! and the phone can send messages. All messages are acknowledged. In this context, there is no
|
||||
//! client-server model, as such.
|
||||
//!
|
||||
//! During the sending phase, one side initiates the communication by transferring a dictionary over the air.
|
||||
//! The other side then receives this message and is given an opportunity to perform actions on that data.
|
||||
//! As soon as possible, the other side is expected to reply to the message with a simple acknowledgment
|
||||
//! that the message was received successfully.
|
||||
//!
|
||||
//! PebbleKit JavaScript provides you with a set of standard JavaScript APIs that let your app receive messages
|
||||
//! from the watch, make HTTP requests, and send new messages to the watch. AppMessage APIs are used to send and
|
||||
//! receive data. A Pebble watchapp can use the resources of the connected phone to fetch information from web services,
|
||||
//! send information to web APIs, or store login credentials. On the JavaScript side, you communicate
|
||||
//! with Pebble via a Pebble object exposed in the namespace.
|
||||
//!
|
||||
//! Messages always need to get either ACKnowledged or "NACK'ed," that is, not acknowledged.
|
||||
//! If not, messages will result in a time-out failure. The AppMessage subsystem takes care of this implicitly.
|
||||
//! In the phone libraries, this step is a bit more explicit.
|
||||
//!
|
||||
//! The Pebble watch interfaces make a distinction between the Inbox and the Outbox calls. The Inbox
|
||||
//! receives messages from the phone on the watch; the Outbox sends messages from the watch to the phone.
|
||||
//! These two buffers can be managed separately.
|
||||
//!
|
||||
//! <h4>Warning</h4>
|
||||
//! A critical constraint of AppMessage is that messages are limited in size. An ingoing (outgoing) message
|
||||
//! larger than the inbox (outbox) will not be transmitted and will generate an error. You can choose your
|
||||
//! inbox and outbox size when you call app_message_open().
|
||||
//!
|
||||
//! Pebble SDK provides a static minimum guaranteed size (APP_MESSAGE_INBOX_SIZE_MINIMUM and APP_MESSAGE_OUTBOX_SIZE_MINIMUM).
|
||||
//! Requesting a buffer of the minimum guaranteed size (or smaller) is always guaranteed to succeed on all
|
||||
//! Pebbles in this SDK version or higher, and with every phone.
|
||||
//!
|
||||
//! In some context, Pebble might be able to provide your application with larger inbox/outbox.
|
||||
//! You can call app_message_inbox_size_maximum() and app_message_outbox_size_maximum() in your code to get
|
||||
//! the largest possible value you can use.
|
||||
//!
|
||||
//! To always get the largest buffer available, follow this best practice:
|
||||
//!
|
||||
//! app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum())
|
||||
//!
|
||||
//! AppMessage uses your application heap space. That means that the sizes you pick for the AppMessage
|
||||
//! inbox and outbox buffers are important in optimizing your app’s performance. The more you use for
|
||||
//! AppMessage, the less space you’ll have for the rest of your app.
|
||||
//!
|
||||
//! To register callbacks, you should call app_message_register_inbox_received(), app_message_register_inbox_dropped(),
|
||||
//! app_message_register_outbox_sent(), app_message_register_outbox_failed().
|
||||
//!
|
||||
//! Pebble recommends that you call them before app_message_open() to ensure you do not miss a message
|
||||
//! arriving between starting AppMessage and registering the callback. You can set a context that will be passed
|
||||
//! to all the callbacks with app_message_set_context().
|
||||
//!
|
||||
//! In circumstances that may not be ideal, when using AppMessage several types of errors may occur.
|
||||
//! For example:
|
||||
//!
|
||||
//! * The send can’t start because the system state won't allow for a success. Several reasons
|
||||
//! you're unable to perform a send: A send() is already occurring (only one is possible at a time) or Bluetooth
|
||||
//! is not enabled or connected.
|
||||
//! * The send and receive occur, but the receiver can’t accept the message. For instance, there is no app
|
||||
//! that receives such a message.
|
||||
//! * The send occurs, but the receiver either does not actually receive the message or can’t handle it
|
||||
//! in a timely fashion.
|
||||
//! * In the case of a dropped message, the phone sends a message to the watchapp, while there is still
|
||||
//! an unprocessed message in the Inbox.
|
||||
//!
|
||||
//! Other errors are possible and described by AppMessageResult. A client of the AppMessage interface
|
||||
//! should use the result codes to be more robust in the face of communication problems either in the field or while debugging.
|
||||
//!
|
||||
//! Refer to the \htmlinclude app-phone-communication.html for a conceptual overview and code usage.
|
||||
//!
|
||||
//! For code examples, refer to the SDK Examples that directly use App Message. These include:
|
||||
//! * <a href="https://github.com/pebble-examples/pebblekit-js-weather">
|
||||
//! pebblekit-js-weather</a>
|
||||
//! * <a href="https://github.com/pebble-examples/pebblekit-js-quotes">
|
||||
//! pebblekit-js-quotes</a>
|
||||
//! @{
|
||||
//!
|
||||
//! AppMessage is a messaging subsystem that allows phone and watch applications
|
||||
//! to exchange arbitrary sets of key-value pairs. The key value pairs are
|
||||
//! stored in the form of a Dictionary, the layout of which is left for the
|
||||
//! application developer to define.
|
||||
//!
|
||||
//! <h3>Communication Model</h3>
|
||||
//!
|
||||
//! AppMessage is a simple send-receive-reply protocol. The protocol is symmetric: both the watch and phone can start
|
||||
//! sending a message and expect a reply from the other side.
|
||||
//!
|
||||
//! In the sending phase, one side initiates the communication by transferring a dictionary over the air. The other
|
||||
//! side then receives this message and is given an opportunity to perform actions on that data. As soon as possible,
|
||||
//! the other side is expected to reply to the message with a simple acknowledgement that the message was received
|
||||
//! successfully.
|
||||
//!
|
||||
//! In non-ideal circumstances, several errors may occur. For example:
|
||||
//! * The send can't start as the system state won't allow for a success.
|
||||
//! * The send and receive occur, but the receiver cannot accept the message (for example, there is no app that receives such
|
||||
//! a message).
|
||||
//! * The send occurs, but the receiver either does not actually receive the message or can't handle it in a timely
|
||||
//! fashion.
|
||||
//!
|
||||
//! Other errors are possible, described by \ref AppMessageResult. A client of the AppMessage interface
|
||||
//! can use the result codes to be more robust in the face of communication problems either in the field or while
|
||||
//! debugging.
|
||||
//!
|
||||
//! The watch interfaces make a distinction between the Inbox and the Outbox. The Inbox receives messages from the
|
||||
//! phone on the watch; the Outbox sends messages from the watch to the phone. These two objects can be managed
|
||||
//! separately.
|
||||
//!
|
||||
//! \note Messages are actually addressed by the UUID of the watch and phone apps. This is done automatically by the
|
||||
//! system for the convenience of the client. However, this does require that both the watch and phone apps
|
||||
//! share their UUID. AppMessage is not capable of 1:N or M:N communication at this time, and is merely 1:1.
|
||||
//!
|
||||
//! \sa AppMessageResult
|
||||
|
||||
// -------- Defines, Enumerations, and Structures ------------------------------------------------------------------ //
|
||||
|
||||
//! As long as the firmware maintains its current major version, inboxes of this size or smaller will be allowed.
|
||||
//!
|
||||
//! \sa app_message_inbox_size_maximum()
|
||||
//! \sa APP_MESSAGE_OUTBOX_SIZE_MINIMUM
|
||||
//!
|
||||
#define APP_MESSAGE_INBOX_SIZE_MINIMUM 124 /* bytes */
|
||||
|
||||
//! As long as the firmware maintains its current major version, outboxes of this size or smaller will be allowed.
|
||||
//!
|
||||
//! \sa app_message_outbox_size_maximum()
|
||||
//! \sa APP_MESSAGE_INBOX_SIZE_MINIMUM
|
||||
//!
|
||||
#define APP_MESSAGE_OUTBOX_SIZE_MINIMUM 636 /* bytes */
|
||||
|
||||
//! AppMessage result codes.
|
||||
typedef enum {
|
||||
//! (0) All good, operation was successful.
|
||||
APP_MSG_OK = 0,
|
||||
|
||||
//! (2) The other end did not confirm receiving the sent data with an (n)ack in time.
|
||||
APP_MSG_SEND_TIMEOUT = 1 << 1,
|
||||
|
||||
//! (4) The other end rejected the sent data, with a "nack" reply.
|
||||
APP_MSG_SEND_REJECTED = 1 << 2,
|
||||
|
||||
//! (8) The other end was not connected.
|
||||
APP_MSG_NOT_CONNECTED = 1 << 3,
|
||||
|
||||
//! (16) The local application was not running.
|
||||
APP_MSG_APP_NOT_RUNNING = 1 << 4,
|
||||
|
||||
//! (32) The function was called with invalid arguments.
|
||||
APP_MSG_INVALID_ARGS = 1 << 5,
|
||||
|
||||
//! (64) There are pending (in or outbound) messages that need to be processed first before
|
||||
//! new ones can be received or sent.
|
||||
APP_MSG_BUSY = 1 << 6,
|
||||
|
||||
//! (128) The buffer was too small to contain the incoming message.
|
||||
//! @internal
|
||||
//! @see \ref app_message_open()
|
||||
APP_MSG_BUFFER_OVERFLOW = 1 << 7,
|
||||
|
||||
//! (512) The resource had already been released.
|
||||
APP_MSG_ALREADY_RELEASED = 1 << 9,
|
||||
|
||||
//! (1024) The callback was already registered.
|
||||
APP_MSG_CALLBACK_ALREADY_REGISTERED = 1 << 10,
|
||||
|
||||
//! (2048) The callback could not be deregistered, because it had not been registered before.
|
||||
APP_MSG_CALLBACK_NOT_REGISTERED = 1 << 11,
|
||||
|
||||
//! (4096) The system did not have sufficient application memory to
|
||||
//! perform the requested operation.
|
||||
APP_MSG_OUT_OF_MEMORY = 1 << 12,
|
||||
|
||||
//! (8192) App message was closed.
|
||||
APP_MSG_CLOSED = 1 << 13,
|
||||
|
||||
//! (16384) An internal OS error prevented AppMessage from completing an operation.
|
||||
APP_MSG_INTERNAL_ERROR = 1 << 14,
|
||||
|
||||
//! (32768) The function was called while App Message was not in the appropriate state.
|
||||
APP_MSG_INVALID_STATE = 1 << 15,
|
||||
} AppMessageResult;
|
||||
|
||||
//! Called after an incoming message is received.
|
||||
//!
|
||||
//! \param[in] iterator
|
||||
//! The dictionary iterator to the received message. Never NULL. Note that the iterator cannot be modified or
|
||||
//! saved off. The library may need to re-use the buffered space where this message is supplied. Returning from
|
||||
//! the callback indicates to the library that the received message contents are no longer needed or have already
|
||||
//! been externalized outside its buffering space and iterator.
|
||||
//!
|
||||
//! \param[in] context
|
||||
//! Pointer to application data as specified when registering the callback.
|
||||
//!
|
||||
typedef void (*AppMessageInboxReceived)(DictionaryIterator *iterator, void *context);
|
||||
|
||||
//! Called after an incoming message is dropped.
|
||||
//!
|
||||
//! \param[in] result
|
||||
//! The reason why the message was dropped. Some possibilities include \ref APP_MSG_BUSY and
|
||||
//! \ref APP_MSG_BUFFER_OVERFLOW.
|
||||
//!
|
||||
//! \param[in] context
|
||||
//! Pointer to application data as specified when registering the callback.
|
||||
//!
|
||||
//! Note that you can call app_message_outbox_begin() from this handler to prepare a new message.
|
||||
//! This will invalidate the previous dictionary iterator; do not use it after calling app_message_outbox_begin().
|
||||
//!
|
||||
typedef void (*AppMessageInboxDropped)(AppMessageResult reason, void *context);
|
||||
|
||||
//! Called after an outbound message has been sent and the reply has been received.
|
||||
//!
|
||||
//! \param[in] iterator
|
||||
//! The dictionary iterator to the sent message. The iterator will be in the final state that was sent. Note that
|
||||
//! the iterator cannot be modified or saved off as the library will re-open the dictionary with dict_begin() after
|
||||
//! this callback returns.
|
||||
//!
|
||||
//! \param[in] context
|
||||
//! Pointer to application data as specified when registering the callback.
|
||||
//!
|
||||
typedef void (*AppMessageOutboxSent)(DictionaryIterator *iterator, void *context);
|
||||
|
||||
//! Called after an outbound message has not been sent successfully.
|
||||
//!
|
||||
//! \param[in] iterator
|
||||
//! The dictionary iterator to the sent message. The iterator will be in the final state that was sent. Note that
|
||||
//! the iterator cannot be modified or saved off as the library will re-open the dictionary with dict_begin() after
|
||||
//! this callback returns.
|
||||
//!
|
||||
//! \param[in] result
|
||||
//! The result of the operation. Some possibilities for the value include \ref APP_MSG_SEND_TIMEOUT,
|
||||
//! \ref APP_MSG_SEND_REJECTED, \ref APP_MSG_NOT_CONNECTED, \ref APP_MSG_APP_NOT_RUNNING, and the combination
|
||||
//! `(APP_MSG_NOT_CONNECTED | APP_MSG_APP_NOT_RUNNING)`.
|
||||
//!
|
||||
//! \param context
|
||||
//! Pointer to application data as specified when registering the callback.
|
||||
//!
|
||||
//! Note that you can call app_message_outbox_begin() from this handler to prepare a new message.
|
||||
//! This will invalidate the previous dictionary iterator; do not use it after calling app_message_outbox_begin().
|
||||
//!
|
||||
typedef void (*AppMessageOutboxFailed)(DictionaryIterator *iterator, AppMessageResult reason, void *context);
|
||||
|
||||
|
||||
// -------- AppMessage Callbacks ----------------------------------------------------------------------------------- //
|
||||
|
||||
//! Gets the context that will be passed to all AppMessage callbacks.
|
||||
//!
|
||||
//! \return The current context on record.
|
||||
//!
|
||||
void *app_message_get_context(void);
|
||||
|
||||
//! Sets the context that will be passed to all AppMessage callbacks.
|
||||
//!
|
||||
//! \param[in] context The context that will be passed to all AppMessage callbacks.
|
||||
//!
|
||||
//! \return The previous context that was on record.
|
||||
//!
|
||||
void *app_message_set_context(void *context);
|
||||
|
||||
//! Registers a function that will be called after any Inbox message is received successfully.
|
||||
//!
|
||||
//! Only one callback may be registered at a time. Each subsequent call to this function will replace the previous
|
||||
//! callback. The callback is optional; setting it to NULL will deregister the current callback and no function will
|
||||
//! be called anymore.
|
||||
//!
|
||||
//! \param[in] received_callback The callback that will be called going forward; NULL to not have a callback.
|
||||
//!
|
||||
//! \return The previous callback (or NULL) that was on record.
|
||||
//!
|
||||
AppMessageInboxReceived app_message_register_inbox_received(AppMessageInboxReceived received_callback);
|
||||
|
||||
//! Registers a function that will be called after any Inbox message is received but dropped by the system.
|
||||
//!
|
||||
//! Only one callback may be registered at a time. Each subsequent call to this function will replace the previous
|
||||
//! callback. The callback is optional; setting it to NULL will deregister the current callback and no function will
|
||||
//! be called anymore.
|
||||
//!
|
||||
//! \param[in] dropped_callback The callback that will be called going forward; NULL to not have a callback.
|
||||
//!
|
||||
//! \return The previous callback (or NULL) that was on record.
|
||||
//!
|
||||
AppMessageInboxDropped app_message_register_inbox_dropped(AppMessageInboxDropped dropped_callback);
|
||||
|
||||
//! Registers a function that will be called after any Outbox message is sent and an ACK reply occurs in a timely
|
||||
//! fashion.
|
||||
//!
|
||||
//! Only one callback may be registered at a time. Each subsequent call to this function will replace the previous
|
||||
//! callback. The callback is optional; setting it to NULL will deregister the current callback and no function will
|
||||
//! be called anymore.
|
||||
//!
|
||||
//! \param[in] sent_callback The callback that will be called going forward; NULL to not have a callback.
|
||||
//!
|
||||
//! \return The previous callback (or NULL) that was on record.
|
||||
//!
|
||||
AppMessageOutboxSent app_message_register_outbox_sent(AppMessageOutboxSent sent_callback);
|
||||
|
||||
//! Registers a function that will be called after any Outbox message is not sent with a timely ACK reply.
|
||||
//! The call to \ref app_message_outbox_send() must have succeeded.
|
||||
//!
|
||||
//! Only one callback may be registered at a time. Each subsequent call to this function will replace the previous
|
||||
//! callback. The callback is optional; setting it to NULL will deregister the current callback and no function will
|
||||
//! be called anymore.
|
||||
//!
|
||||
//! \param[in] failed_callback The callback that will be called going forward; NULL to not have a callback.
|
||||
//!
|
||||
//! \return The previous callback (or NULL) that was on record.
|
||||
//!
|
||||
AppMessageOutboxFailed app_message_register_outbox_failed(AppMessageOutboxFailed failed_callback);
|
||||
|
||||
//! Deregisters all callbacks and their context.
|
||||
//!
|
||||
void app_message_deregister_callbacks(void);
|
||||
|
||||
// -------- AppMessage Lifecycle ----------------------------------------------------------------------------------- //
|
||||
|
||||
//! Programatically determine the inbox size maximum in the current configuration.
|
||||
//!
|
||||
//! \return The inbox size maximum on this firmware.
|
||||
//!
|
||||
//! \sa APP_MESSAGE_INBOX_SIZE_MINIMUM
|
||||
//! \sa app_message_outbox_size_maximum()
|
||||
//!
|
||||
uint32_t app_message_inbox_size_maximum(void);
|
||||
|
||||
//! Programatically determine the outbox size maximum in the current configuration.
|
||||
//!
|
||||
//! \return The outbox size maximum on this firmware.
|
||||
//!
|
||||
//! \sa APP_MESSAGE_OUTBOX_SIZE_MINIMUM
|
||||
//! \sa app_message_inbox_size_maximum()
|
||||
//!
|
||||
uint32_t app_message_outbox_size_maximum(void);
|
||||
|
||||
//! Open AppMessage to transfers.
|
||||
//!
|
||||
//! Use \ref dict_calc_buffer_size_from_tuplets() or \ref dict_calc_buffer_size() to estimate the size you need.
|
||||
//!
|
||||
//! \param[in] size_inbound The required size for the Inbox buffer
|
||||
//! \param[in] size_outbound The required size for the Outbox buffer
|
||||
//!
|
||||
//! \return A result code such as \ref APP_MSG_OK or \ref APP_MSG_OUT_OF_MEMORY.
|
||||
//!
|
||||
//! \note It is recommended that if the Inbox will be used, that at least the Inbox callbacks should be registered
|
||||
//! before this call. Otherwise it is possible for an Inbox message to be NACK'ed without being seen by the
|
||||
//! application.
|
||||
//!
|
||||
AppMessageResult app_message_open(const uint32_t size_inbound, const uint32_t size_outbound);
|
||||
|
||||
//! Close AppMessage to further transfers.
|
||||
//!
|
||||
void app_message_close(void);
|
||||
|
||||
|
||||
// -------- AppMessage Inbox --------------------------------------------------------------------------------------- //
|
||||
|
||||
// Note: the Inbox has no direct functions, only callbacks.
|
||||
|
||||
|
||||
// -------- AppMessage Outbox -------------------------------------------------------------------------------------- //
|
||||
|
||||
//! Begin writing to the Outbox's Dictionary buffer.
|
||||
//!
|
||||
//! \param[out] iterator Location to write the DictionaryIterator pointer. This will be NULL on failure.
|
||||
//!
|
||||
//! \return A result code, including but not limited to \ref APP_MSG_OK, \ref APP_MSG_INVALID_ARGS or
|
||||
//! \ref APP_MSG_BUSY.
|
||||
//!
|
||||
//! \note After a successful call, one can add values to the dictionary using functions like \ref dict_write_data()
|
||||
//! and friends.
|
||||
//!
|
||||
//! \sa Dictionary
|
||||
//!
|
||||
AppMessageResult app_message_outbox_begin(DictionaryIterator **iterator);
|
||||
|
||||
//! Sends the outbound dictionary.
|
||||
//!
|
||||
//! \return A result code, including but not limited to \ref APP_MSG_OK or \ref APP_MSG_BUSY. The APP_MSG_OK code does
|
||||
//! not mean that the message was sent successfully, but only that the start of processing was successful.
|
||||
//! Since this call is asynchronous, callbacks provide the final result instead.
|
||||
//!
|
||||
//! \sa AppMessageOutboxSent
|
||||
//! \sa AppMessageOutboxFailed
|
||||
//!
|
||||
AppMessageResult app_message_outbox_send(void);
|
||||
|
||||
//! @} // end addtogroup AppMessage
|
||||
//! @} // end addtogroup Foundation
|
120
src/fw/applib/app_message/app_message_inbox.c
Normal file
120
src/fw/applib/app_message/app_message_inbox.c
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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 "applib/app_message/app_message_internal.h"
|
||||
#include "applib/app_message/app_message_receiver.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "system/logging.h"
|
||||
#include "syscall/syscall.h"
|
||||
|
||||
AppMessageResult app_message_inbox_open(AppMessageCtxInbox *inbox, size_t size_inbound) {
|
||||
const size_t size_maximum = app_message_inbox_size_maximum();
|
||||
if (size_inbound > size_maximum) {
|
||||
// Truncate if it's more than the max:
|
||||
size_inbound = size_maximum;
|
||||
} else if (size_inbound == size_maximum) {
|
||||
APP_LOG(LOG_LEVEL_INFO, "app_message_open() called with app_message_inbox_size_maximum().");
|
||||
APP_LOG(LOG_LEVEL_INFO,
|
||||
"This consumes %"PRIu32" bytes of heap memory, potentially more in the future!",
|
||||
(uint32_t)size_maximum);
|
||||
|
||||
}
|
||||
if (size_inbound == 0) {
|
||||
return APP_MSG_OK;
|
||||
}
|
||||
// Add extra space needed for protocol overhead:
|
||||
if (!app_message_receiver_open(size_inbound + APP_MSG_HDR_OVRHD_SIZE)) {
|
||||
return APP_MSG_OUT_OF_MEMORY;
|
||||
}
|
||||
inbox->is_open = true;
|
||||
|
||||
return APP_MSG_OK;
|
||||
}
|
||||
|
||||
void app_message_inbox_close(AppMessageCtxInbox *inbox) {
|
||||
app_message_receiver_close();
|
||||
inbox->is_open = false;
|
||||
}
|
||||
|
||||
void app_message_inbox_send_ack_nack_reply(CommSession *session, const uint8_t transaction_id,
|
||||
AppMessageCmd cmd) {
|
||||
const AppMessageAck nack_message = (const AppMessageAck) {
|
||||
.header = {
|
||||
.command = cmd,
|
||||
.transaction_id = transaction_id,
|
||||
},
|
||||
};
|
||||
// Just use a syscall to enqueue the message using kernel heap.
|
||||
// We could use app_outbox, but then we'd need to allocate the message on the app heap and I'm
|
||||
// afraid this might break apps, especially if the mobile app is misbehaving and avalanching the
|
||||
// app with messages that need to be (n)ack'd.
|
||||
sys_app_pp_send_data(session, APP_MESSAGE_ENDPOINT_ID,
|
||||
(const uint8_t *) &nack_message, sizeof(nack_message));
|
||||
}
|
||||
|
||||
void app_message_inbox_handle_dropped_messages(uint32_t num_drops) {
|
||||
// Taking a shortcut here. We used to report either APP_MSG_BUFFER_OVERFLOW or APP_MSG_BUSY back
|
||||
// to the app. With the new the Receiver / AppInbox system, there are different reasons why
|
||||
// messages get dropped. Just map everything to "APP_MSG_BUSY":
|
||||
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
|
||||
AppMessageCtxInbox *inbox = &app_message_ctx->inbox;
|
||||
const bool is_open_and_has_handler = (inbox->is_open && inbox->dropped_callback);
|
||||
for (uint32_t i = 0; i < num_drops; ++i) {
|
||||
if (is_open_and_has_handler) {
|
||||
inbox->dropped_callback(APP_MSG_BUSY, inbox->user_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool prv_is_app_with_uuid_running(const Uuid *uuid) {
|
||||
Uuid app_uuid = {};
|
||||
sys_get_app_uuid(&app_uuid);
|
||||
return uuid_equal(&app_uuid, uuid);
|
||||
}
|
||||
|
||||
void app_message_inbox_receive(CommSession *session, AppMessagePush *push_message, size_t length,
|
||||
AppInboxConsumerInfo *consumer_info) {
|
||||
// Test if the data is long enough to contain a push message:
|
||||
if (length < sizeof(AppMessagePush)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Too short");
|
||||
return;
|
||||
}
|
||||
|
||||
AppMessageCtxInbox *inbox = &app_state_get_app_message_ctx()->inbox;
|
||||
const uint8_t transaction_id = push_message->header.transaction_id;
|
||||
|
||||
// Verify UUID for app-bound messages:
|
||||
if (!prv_is_app_with_uuid_running(&push_message->uuid)) {
|
||||
app_message_inbox_send_ack_nack_reply(session, transaction_id, CMD_NACK);
|
||||
sys_app_pp_app_message_analytics_count_drop();
|
||||
return;
|
||||
}
|
||||
|
||||
DictionaryIterator iterator;
|
||||
const uint16_t dict_size = (length - APP_MSG_HDR_OVRHD_SIZE);
|
||||
// TODO PBL-1639: Maybe do some sanity checking on the dict structure?
|
||||
dict_read_begin_from_buffer(&iterator, (const uint8_t *) &push_message->dictionary, dict_size);
|
||||
|
||||
if (inbox->received_callback) {
|
||||
inbox->received_callback(&iterator, inbox->user_context);
|
||||
}
|
||||
|
||||
// Mark data as consumed...
|
||||
app_inbox_consume(consumer_info);
|
||||
|
||||
// ... only then send the ACK:
|
||||
app_message_inbox_send_ack_nack_reply(session, transaction_id, CMD_ACK);
|
||||
}
|
156
src/fw/applib/app_message/app_message_internal.h
Normal file
156
src/fw/applib/app_message/app_message_internal.h
Normal 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
|
||||
|
||||
#include "applib/app_message/app_message.h"
|
||||
#include "applib/app_timer.h"
|
||||
#include "services/normal/app_message/app_message_sender.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/uuid.h"
|
||||
|
||||
typedef struct CommSession CommSession;
|
||||
|
||||
#define ACK_NACK_TIME_OUT_MS (10000)
|
||||
#define APP_MESSAGE_ENDPOINT_ID (0x30)
|
||||
|
||||
typedef enum {
|
||||
CMD_PUSH = 0x01,
|
||||
CMD_REQUEST = 0x02,
|
||||
CMD_ACK = 0xff,
|
||||
CMD_NACK = 0x7f,
|
||||
} AppMessageCmd;
|
||||
|
||||
typedef struct PACKED {
|
||||
AppMessageCmd command:8;
|
||||
uint8_t transaction_id;
|
||||
} AppMessageHeader;
|
||||
|
||||
//! The actual wire format of an app message message
|
||||
typedef struct PACKED {
|
||||
AppMessageHeader header;
|
||||
Uuid uuid;
|
||||
Dictionary dictionary; //!< Variable length!
|
||||
} AppMessagePush;
|
||||
// AppMessageHeader and Uuid size should be opaque to user of API
|
||||
#define APP_MSG_HDR_OVRHD_SIZE (offsetof(AppMessagePush, dictionary))
|
||||
|
||||
#define APP_MSG_8K_DICT_SIZE (sizeof(Dictionary) + sizeof(Tuple) + (8 * 1024))
|
||||
|
||||
typedef struct PACKED {
|
||||
AppMessageHeader header;
|
||||
} AppMessageAck;
|
||||
|
||||
// For a diagram of the state machine:
|
||||
// https://pebbletechnology.atlassian.net/wiki/pages/editpage.action?pageId=91914242
|
||||
|
||||
typedef enum AppMessagePhaseOut {
|
||||
//! The App Message Outbox is not enabled. To enable it, call app_message_open().
|
||||
OUT_CLOSED = 0,
|
||||
//! The dictionary writing can be "started" by calling app_message_outbox_begin()
|
||||
OUT_ACCEPTING,
|
||||
//! app_message_outbox_begin() has been called and the dictionary can be written and then sent.
|
||||
OUT_WRITING,
|
||||
//! app_message_outbox_send() has been called. The ack/nack timeout timer has been set and
|
||||
//! we're awaiting an ack/nack on the sent message AND the callback from the AppOutbox subsystem
|
||||
//! that the data has been consumed. These 2 things happen in parallel, the order in which they
|
||||
//! happen is undefined.
|
||||
OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK,
|
||||
//! We're still awaiting the AppMessage ack/nack, but the AppOutbox subsystem has indicated that
|
||||
//! the data has been consumed.
|
||||
OUT_AWAITING_REPLY,
|
||||
//! We're still awaiting the callback from the AppOutbox subsystem to indicate the data has been
|
||||
//! consumed, but we have already received the AppMessage ack/nack. This state is possible because
|
||||
//! acking at a lower layer (i.e. PPoGATT) can happen with a slight delay.
|
||||
OUT_AWAITING_OUTBOX_CALLBACK,
|
||||
} AppMessagePhaseOut;
|
||||
|
||||
typedef struct AppMessageCtxInbox {
|
||||
bool is_open;
|
||||
void *user_context;
|
||||
AppMessageInboxReceived received_callback;
|
||||
AppMessageInboxDropped dropped_callback;
|
||||
} AppMessageCtxInbox;
|
||||
|
||||
typedef struct AppMessageCtxOutbox {
|
||||
DictionaryIterator iterator;
|
||||
size_t transmission_size_limit;
|
||||
|
||||
AppMessageAppOutboxData *app_outbox_message;
|
||||
|
||||
AppMessageOutboxSent sent_callback;
|
||||
AppMessageOutboxFailed failed_callback;
|
||||
void *user_context;
|
||||
|
||||
AppTimer *ack_nack_timer;
|
||||
|
||||
struct PACKED {
|
||||
AppMessagePhaseOut phase:8;
|
||||
uint8_t transaction_id;
|
||||
uint16_t not_ready_throttle_ms; // used for throttling app task when outbox is not ready
|
||||
AppMessageResult result:16;
|
||||
};
|
||||
} AppMessageCtxOutbox;
|
||||
|
||||
typedef struct AppMessageCtx {
|
||||
AppMessageCtxInbox inbox;
|
||||
AppMessageCtxOutbox outbox;
|
||||
} AppMessageCtx;
|
||||
|
||||
_Static_assert(sizeof(AppMessageCtx) <= 112,
|
||||
"AppMessageCtx must not exceed 112 bytes!");
|
||||
|
||||
typedef struct {
|
||||
CommSession *session;
|
||||
//! To give us some room for future changes. This structure ends up in a buffer that is sized by
|
||||
//! the app, so we can't easily increase the size of this once shipped.
|
||||
uint8_t padding[8];
|
||||
uint8_t data[];
|
||||
} AppMessageReceiverHeader;
|
||||
|
||||
#ifndef UNITTEST
|
||||
_Static_assert(sizeof(AppMessageReceiverHeader) == 12,
|
||||
"The size of AppMessageReceiverHeader cannot grow beyond 12 bytes!");
|
||||
#endif
|
||||
|
||||
void app_message_init(void);
|
||||
|
||||
AppMessageResult app_message_inbox_open(AppMessageCtxInbox *inbox, size_t size_inbound);
|
||||
|
||||
void app_message_inbox_close(AppMessageCtxInbox *inbox);
|
||||
|
||||
typedef struct AppInboxConsumerInfo AppInboxConsumerInfo;
|
||||
|
||||
void app_message_inbox_receive(CommSession *session, AppMessagePush *push_message, size_t length,
|
||||
AppInboxConsumerInfo *consumer_info);
|
||||
|
||||
AppMessageResult app_message_outbox_open(AppMessageCtxOutbox *outbox, size_t size_outbound);
|
||||
|
||||
void app_message_outbox_close(AppMessageCtxOutbox *outbox);
|
||||
|
||||
void app_message_out_handle_ack_nack_received(const AppMessageHeader *header);
|
||||
|
||||
void app_message_inbox_send_ack_nack_reply(CommSession *session, const uint8_t transaction_id,
|
||||
AppMessageCmd cmd);
|
||||
|
||||
void app_message_inbox_handle_dropped_messages(uint32_t num_drops);
|
||||
|
||||
void app_message_app_protocol_msg_callback(CommSession *session,
|
||||
const uint8_t* data, size_t length,
|
||||
AppInboxConsumerInfo *consumer_info);
|
||||
|
||||
void app_message_app_protocol_system_nack_callback(CommSession *session,
|
||||
const uint8_t* data, size_t length);
|
316
src/fw/applib/app_message/app_message_outbox.c
Normal file
316
src/fw/applib/app_message/app_message_outbox.c
Normal file
|
@ -0,0 +1,316 @@
|
|||
/*
|
||||
* 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 "applib/applib_malloc.auto.h"
|
||||
#include "applib/app_message/app_message_internal.h"
|
||||
#include "applib/app_outbox.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "services/common/comm_session/session.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/math.h"
|
||||
|
||||
static void prv_outbox_prepare(AppMessageCtxOutbox *outbox);
|
||||
|
||||
static uint16_t prv_get_next_transaction_id(AppMessageCtxOutbox *outbox) {
|
||||
return ++(outbox->transaction_id);
|
||||
}
|
||||
|
||||
static void prv_transition_to_accepting(AppMessageCtxOutbox *outbox) {
|
||||
outbox->phase = OUT_ACCEPTING;
|
||||
if (outbox->result == APP_MSG_OK) {
|
||||
if (outbox->sent_callback) {
|
||||
outbox->sent_callback(&outbox->iterator, outbox->user_context);
|
||||
}
|
||||
} else {
|
||||
if (outbox->failed_callback) {
|
||||
outbox->failed_callback(&outbox->iterator, outbox->result, outbox->user_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_handle_nack_or_ack_timeout(AppMessageCtxOutbox *outbox,
|
||||
AppMessageResult result) {
|
||||
outbox->result = result;
|
||||
if (outbox->phase == OUT_AWAITING_REPLY) {
|
||||
prv_transition_to_accepting(outbox);
|
||||
} else if (outbox->phase == OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK) {
|
||||
outbox->phase = OUT_AWAITING_OUTBOX_CALLBACK;
|
||||
} else {
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_handle_outbox_error_cb(void *data) {
|
||||
AppMessageResult result = (AppMessageResult)(uintptr_t) data;
|
||||
_Static_assert(sizeof(result) <= sizeof(data), "AppMessageResult expected to fit in void *");
|
||||
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
|
||||
if (outbox->phase != OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK) {
|
||||
APP_LOG(LOG_LEVEL_ERROR, "Outbox failure, but unexpected state: %u", outbox->phase);
|
||||
return;
|
||||
}
|
||||
// If app_message_outbox_handle_app_outbox_message_sent() has been called with an error,
|
||||
// don't wait for an (N)ACK (it won't ever come), but finish right away:
|
||||
outbox->result = result;
|
||||
prv_transition_to_accepting(outbox);
|
||||
}
|
||||
|
||||
//! Use sys_current_process_schedule_callback to maximize the stack space available to the
|
||||
//! app's failed_callback.
|
||||
static void prv_handle_outbox_error_async(AppMessageResult result) {
|
||||
sys_current_process_schedule_callback(prv_handle_outbox_error_cb, (void *)(uintptr_t)result);
|
||||
}
|
||||
|
||||
AppMessageResult app_message_outbox_open(AppMessageCtxOutbox *outbox, size_t size_outbound) {
|
||||
const size_t size_maximum = app_message_outbox_size_maximum();
|
||||
if (size_outbound > size_maximum) {
|
||||
// Truncate if it's more than the max:
|
||||
size_outbound = size_maximum;
|
||||
} else if (size_outbound == size_maximum) {
|
||||
APP_LOG(LOG_LEVEL_INFO, "app_message_open() called with app_message_outbox_size_maximum().");
|
||||
APP_LOG(LOG_LEVEL_INFO,
|
||||
"This consumes %"PRIu32" bytes of heap memory, potentially more in the future!",
|
||||
(uint32_t)size_maximum);
|
||||
}
|
||||
if (size_outbound == 0) {
|
||||
return APP_MSG_OK;
|
||||
}
|
||||
|
||||
// Extra space needed by App Message protocol...:
|
||||
size_outbound += APP_MSG_HDR_OVRHD_SIZE;
|
||||
|
||||
// ... and extra space header for app outbox message (not counting towards the transmission size):
|
||||
outbox->app_outbox_message = applib_zalloc(sizeof(AppMessageAppOutboxData) + size_outbound);
|
||||
if (outbox->app_outbox_message == NULL) {
|
||||
return APP_MSG_OUT_OF_MEMORY;
|
||||
}
|
||||
outbox->transmission_size_limit = size_outbound;
|
||||
outbox->transaction_id = 0;
|
||||
prv_outbox_prepare(outbox);
|
||||
|
||||
outbox->phase = OUT_ACCEPTING;
|
||||
|
||||
return APP_MSG_OK;
|
||||
}
|
||||
|
||||
static void prv_outbox_prepare(AppMessageCtxOutbox *outbox) {
|
||||
AppMessagePush *push = (AppMessagePush *)outbox->app_outbox_message->payload;
|
||||
dict_write_begin(&outbox->iterator,
|
||||
(uint8_t *)&push->dictionary,
|
||||
outbox->transmission_size_limit - APP_MSG_HDR_OVRHD_SIZE);
|
||||
}
|
||||
|
||||
static void prv_stop_timer(AppMessageCtxOutbox *outbox) {
|
||||
if (outbox->ack_nack_timer) {
|
||||
app_timer_cancel(outbox->ack_nack_timer);
|
||||
outbox->ack_nack_timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void app_message_outbox_close(AppMessageCtxOutbox *outbox) {
|
||||
// Verify outbox phase.
|
||||
if (outbox->phase == OUT_CLOSED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel any outstanding timer.
|
||||
prv_stop_timer(outbox);
|
||||
|
||||
outbox->transmission_size_limit = 0;
|
||||
applib_free(outbox->app_outbox_message);
|
||||
outbox->app_outbox_message = NULL;
|
||||
|
||||
// Finish by moving to the next phase.
|
||||
outbox->phase = OUT_CLOSED;
|
||||
}
|
||||
|
||||
static void prv_throttle(AppMessageCtxOutbox *outbox) {
|
||||
if (outbox->not_ready_throttle_ms == 0) {
|
||||
outbox->not_ready_throttle_ms = 1;
|
||||
} else {
|
||||
outbox->not_ready_throttle_ms = MIN(outbox->not_ready_throttle_ms * 2, 100 /*ms*/);
|
||||
}
|
||||
sys_psleep(outbox->not_ready_throttle_ms);
|
||||
}
|
||||
|
||||
static bool prv_is_message_pending(AppMessagePhaseOut phase) {
|
||||
return (phase == OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK ||
|
||||
phase == OUT_AWAITING_REPLY ||
|
||||
phase == OUT_AWAITING_OUTBOX_CALLBACK);
|
||||
}
|
||||
|
||||
static bool prv_is_awaiting_ack(AppMessagePhaseOut phase) {
|
||||
return (phase == OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK ||
|
||||
phase == OUT_AWAITING_REPLY);
|
||||
}
|
||||
|
||||
AppMessageResult app_message_outbox_begin(DictionaryIterator **iterator) {
|
||||
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
|
||||
if (iterator == NULL) {
|
||||
return APP_MSG_INVALID_ARGS;
|
||||
}
|
||||
|
||||
AppMessagePhaseOut phase = outbox->phase;
|
||||
*iterator = NULL;
|
||||
if (prv_is_message_pending(phase)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Can't call app_message_outbox_begin() now, wait for sent_callback!");
|
||||
|
||||
// See https://pebbletechnology.atlassian.net/browse/PBL-10146
|
||||
// Workaround for apps that sit in a while() loop waiting on app_message_outbox_begin().
|
||||
// Sleep a little longer each time we get a consecutive poll that returns failure.
|
||||
prv_throttle(outbox);
|
||||
|
||||
return APP_MSG_BUSY;
|
||||
} else if (phase == OUT_WRITING) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR,
|
||||
"Must call app_message_outbox_send() before calling app_message_outbox_begin() again!");
|
||||
return APP_MSG_INVALID_STATE;
|
||||
} else if (phase == OUT_CLOSED) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR,
|
||||
"Must call app_message_open() before calling app_message_outbox_begin()!");
|
||||
return APP_MSG_INVALID_STATE;
|
||||
}
|
||||
|
||||
// Reset the send state (dictionary, counters, etc.)
|
||||
// We do this here, as this function is only called when we begin a new outbox,
|
||||
// so the state should always be clean when we return successfully.
|
||||
prv_outbox_prepare(outbox);
|
||||
*iterator = &outbox->iterator;
|
||||
outbox->phase = OUT_WRITING;
|
||||
outbox->result = APP_MSG_OK;
|
||||
|
||||
return APP_MSG_OK;
|
||||
}
|
||||
|
||||
static void ack_nack_timer_callback(void *data) {
|
||||
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
|
||||
outbox->ack_nack_timer = NULL;
|
||||
if (!prv_is_awaiting_ack(outbox->phase)) {
|
||||
// Reply was received and handled in the mean time, or app message was closed.
|
||||
return;
|
||||
}
|
||||
prv_handle_nack_or_ack_timeout(outbox, APP_MSG_SEND_TIMEOUT);
|
||||
}
|
||||
|
||||
void app_message_outbox_handle_app_outbox_message_sent(AppOutboxStatus status, void *cb_ctx) {
|
||||
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
|
||||
|
||||
AppMessageSenderError e = (AppMessageSenderError)status;
|
||||
if (e != AppMessageSenderErrorSuccess) {
|
||||
if (e != AppMessageSenderErrorDisconnected) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "App message corrupted outbox? %"PRIu8, (uint8_t)e);
|
||||
}
|
||||
|
||||
// Sleep a bit to prevent apps that hammer app_message_outbox_begin() when disconnected to
|
||||
// become battery hogs:
|
||||
prv_throttle(outbox);
|
||||
|
||||
prv_stop_timer(outbox);
|
||||
|
||||
// Just report any error as "not connected" to the app.
|
||||
prv_handle_outbox_error_async(APP_MSG_NOT_CONNECTED);
|
||||
} else {
|
||||
// Only stop throttling if outbox message was consumed successfully:
|
||||
outbox->not_ready_throttle_ms = 0;
|
||||
|
||||
if (outbox->phase == OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK) {
|
||||
outbox->phase = OUT_AWAITING_REPLY;
|
||||
return;
|
||||
}
|
||||
|
||||
if (outbox->phase == OUT_AWAITING_OUTBOX_CALLBACK) {
|
||||
prv_transition_to_accepting(outbox);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppMessageResult app_message_outbox_send(void) {
|
||||
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
|
||||
if (prv_is_message_pending(outbox->phase)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Can't call app_message_outbox_send() now, wait for sent_callback!");
|
||||
return APP_MSG_BUSY;
|
||||
}
|
||||
if (outbox->phase != OUT_WRITING) {
|
||||
return APP_MSG_INVALID_STATE;
|
||||
}
|
||||
|
||||
const size_t transmission_size = dict_write_end(&outbox->iterator) + APP_MSG_HDR_OVRHD_SIZE;
|
||||
if (transmission_size > outbox->transmission_size_limit) {
|
||||
return APP_MSG_BUFFER_OVERFLOW;
|
||||
}
|
||||
|
||||
uint8_t transaction_id = prv_get_next_transaction_id(outbox);
|
||||
AppMessageAppOutboxData *app_outbox_message = outbox->app_outbox_message;
|
||||
AppMessagePush *transmission = (AppMessagePush *)app_outbox_message->payload;
|
||||
transmission->header.command = CMD_PUSH;
|
||||
transmission->header.transaction_id = transaction_id;
|
||||
sys_get_app_uuid(&transmission->uuid);
|
||||
|
||||
outbox->phase = OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK;
|
||||
|
||||
app_outbox_message->session = NULL;
|
||||
app_outbox_message->endpoint_id = APP_MESSAGE_ENDPOINT_ID;
|
||||
|
||||
PBL_ASSERTN(!outbox->ack_nack_timer);
|
||||
outbox->ack_nack_timer = app_timer_register(ACK_NACK_TIME_OUT_MS,
|
||||
ack_nack_timer_callback, NULL);
|
||||
|
||||
app_outbox_send((const uint8_t *)app_outbox_message,
|
||||
sizeof(AppMessageAppOutboxData) + transmission_size,
|
||||
app_message_outbox_handle_app_outbox_message_sent, NULL);
|
||||
|
||||
return APP_MSG_OK;
|
||||
}
|
||||
|
||||
void app_message_out_handle_ack_nack_received(const AppMessageHeader *header) {
|
||||
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
|
||||
|
||||
if (!prv_is_awaiting_ack(outbox->phase)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Received (n)ack, but was not expecting one");
|
||||
return;
|
||||
}
|
||||
|
||||
if (outbox->transaction_id != header->transaction_id) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Tx ID mismatch: %"PRIu8" != %"PRIu8,
|
||||
outbox->transaction_id, header->transaction_id);
|
||||
return;
|
||||
}
|
||||
|
||||
prv_stop_timer(outbox);
|
||||
|
||||
if (header->command == CMD_NACK) {
|
||||
prv_handle_nack_or_ack_timeout(outbox, APP_MSG_SEND_REJECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (outbox->phase == OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK) {
|
||||
outbox->phase = OUT_AWAITING_OUTBOX_CALLBACK;
|
||||
return;
|
||||
}
|
||||
// phase == OUT_AWAITING_REPLY, because of !prv_is_awaiting_ack() check above.
|
||||
prv_transition_to_accepting(outbox);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Unit test interfaces
|
||||
|
||||
AppTimer *app_message_outbox_get_ack_nack_timer(void) {
|
||||
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
|
||||
return outbox ? outbox->ack_nack_timer : NULL;
|
||||
}
|
70
src/fw/applib/app_message/app_message_receiver.c
Normal file
70
src/fw/applib/app_message/app_message_receiver.c
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 "applib/app_message/app_message_receiver.h"
|
||||
#include "applib/app_message/app_message_internal.h"
|
||||
#include "applib/app_inbox.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// All these functions execute on App Task
|
||||
|
||||
void app_message_receiver_message_handler(const uint8_t *data, size_t length,
|
||||
AppInboxConsumerInfo *consumer_info) {
|
||||
AppMessageReceiverHeader *message = (AppMessageReceiverHeader *)data;
|
||||
app_message_app_protocol_msg_callback(message->session, message->data,
|
||||
length - sizeof(AppMessageReceiverHeader), consumer_info);
|
||||
}
|
||||
|
||||
void app_message_receiver_dropped_handler(uint32_t num_dropped_messages) {
|
||||
app_message_inbox_handle_dropped_messages(num_dropped_messages);
|
||||
}
|
||||
|
||||
bool app_message_receiver_open(size_t buffer_size) {
|
||||
AppInbox **app_message_inbox = app_state_get_app_message_inbox();
|
||||
if (*app_message_inbox) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "App PP receiver already open, not opening again");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Make sure that at least one message of `buffer_size` will fit, by adding the header size:
|
||||
// Allocate overhead for 1 (N)ACK + 1 Push message:
|
||||
static const uint32_t min_num_messages = 2;
|
||||
size_t final_buffer_size =
|
||||
(sizeof(AppMessageReceiverHeader) * min_num_messages) + buffer_size + sizeof(AppMessageAck);
|
||||
AppInbox *inbox = app_inbox_create_and_register(final_buffer_size, min_num_messages,
|
||||
app_message_receiver_message_handler,
|
||||
app_message_receiver_dropped_handler);
|
||||
if (!inbox) {
|
||||
// No logging needed, the inner calls log themselves already
|
||||
return false;
|
||||
}
|
||||
|
||||
*app_message_inbox = inbox;
|
||||
return true;
|
||||
}
|
||||
|
||||
void app_message_receiver_close(void) {
|
||||
AppInbox **inbox = app_state_get_app_message_inbox();
|
||||
if (!(*inbox)) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "App PP receiver already closed");
|
||||
return;
|
||||
}
|
||||
|
||||
app_inbox_destroy_and_deregister(*inbox);
|
||||
*inbox = NULL;
|
||||
}
|
23
src/fw/applib/app_message/app_message_receiver.h
Normal file
23
src/fw/applib/app_message/app_message_receiver.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
bool app_message_receiver_open(size_t buffer_size);
|
||||
void app_message_receiver_close(void);
|
39
src/fw/applib/app_outbox.c
Normal file
39
src/fw/applib/app_outbox.c
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 "applib/app_outbox.h"
|
||||
#include "kernel/events.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "syscall/syscall.h"
|
||||
|
||||
static void prv_handle_event(PebbleEvent *e, void *unused) {
|
||||
const PebbleAppOutboxSentEvent *sent_event = &e->app_outbox_sent;
|
||||
sent_event->sent_handler(sent_event->status, sent_event->cb_ctx);
|
||||
}
|
||||
|
||||
void app_outbox_send(const uint8_t *data, size_t length,
|
||||
AppOutboxSentHandler sent_handler, void *cb_ctx) {
|
||||
sys_app_outbox_send(data, length, sent_handler, cb_ctx);
|
||||
}
|
||||
|
||||
void app_outbox_init(void) {
|
||||
EventServiceInfo *info = app_state_get_app_outbox_subscription_info();
|
||||
*info = (EventServiceInfo) {
|
||||
.type = PEBBLE_APP_OUTBOX_SENT_EVENT,
|
||||
.handler = prv_handle_event,
|
||||
};
|
||||
event_service_client_subscribe(info);
|
||||
}
|
50
src/fw/applib/app_outbox.h
Normal file
50
src/fw/applib/app_outbox.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef enum {
|
||||
AppOutboxStatusSuccess,
|
||||
AppOutboxStatusConsumerDoesNotExist,
|
||||
AppOutboxStatusOutOfResources,
|
||||
AppOutboxStatusOutOfMemory,
|
||||
|
||||
// Clients of app outbox can use this range for use-case specific status codes:
|
||||
AppOutboxStatusUserRangeStart,
|
||||
AppOutboxStatusUserRangeEnd = 0xff,
|
||||
} AppOutboxStatus;
|
||||
|
||||
typedef void (*AppOutboxSentHandler)(AppOutboxStatus status, void *cb_ctx);
|
||||
|
||||
//! Sends a message to the outbox.
|
||||
//! If the outbox is not registered, the `sent_handler` will be called immediately with
|
||||
//! status AppOutboxStatusConsumerDoesNotExist, *after* this function returns.
|
||||
//! @param data The message data. The caller is responsible for keeping this buffer around until
|
||||
//! `sent_handler` is called. Don't write to this buffer after calling this either, or the sent
|
||||
//! data might get corrupted.
|
||||
//! @param length Size of `data` in number of bytes.
|
||||
//! @param sent_handler Pointer to the function to call when the kernel has consumed the message,
|
||||
//! after which the `data` buffer will be no longer in use. Note that the `sent_handler` MUST be
|
||||
//! white-listed in app_outbox_service.c.
|
||||
//! @param cb_ctx Pointer to user data that will be passed into the `sent_handler`
|
||||
void app_outbox_send(const uint8_t *data, size_t length,
|
||||
AppOutboxSentHandler sent_handler, void *cb_ctx);
|
||||
|
||||
//! To be called once per app launch by the system.
|
||||
void app_outbox_init(void);
|
27
src/fw/applib/app_recognizers.c
Normal file
27
src/fw/applib/app_recognizers.c
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 "app_recognizers.h"
|
||||
|
||||
#include "process_state/app_state/app_state.h"
|
||||
|
||||
void app_recognizers_attach_recognizer(Recognizer *recognizer) {
|
||||
recognizer_add_to_list(recognizer, app_state_get_recognizer_list());
|
||||
}
|
||||
|
||||
void app_recognizers_detach_recognizer(Recognizer *recognizer) {
|
||||
recognizer_remove_from_list(recognizer, app_state_get_recognizer_list());
|
||||
}
|
30
src/fw/applib/app_recognizers.h
Normal file
30
src/fw/applib/app_recognizers.h
Normal 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 "applib/ui/recognizer/recognizer.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! Attach a recognizer to the app
|
||||
//! @param recognizer \ref Recognizer to attach
|
||||
void app_recognizers_attach_recognizer(Recognizer *recognizer);
|
||||
|
||||
//! Detach a recognizer from the app
|
||||
//! @param recognizer \ref Recognizer to detach
|
||||
void app_recognizers_detach_recognizer(Recognizer *recognizer);
|
252
src/fw/applib/app_smartstrap.c
Normal file
252
src/fw/applib/app_smartstrap.c
Normal file
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* 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 "applib/app_logging.h"
|
||||
#include "applib/app_smartstrap.h"
|
||||
#include "applib/app_smartstrap_private.h"
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "applib/event_service_client.h"
|
||||
#include "board/board.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "services/normal/accessory/smartstrap_attribute.h"
|
||||
#include "services/normal/accessory/smartstrap_connection.h"
|
||||
#include "services/normal/accessory/smartstrap_state.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/mbuf.h"
|
||||
|
||||
#define USE_SMARTSTRAP (CAPABILITY_HAS_ACCESSORY_CONNECTOR && !defined(RECOVERY_FW))
|
||||
|
||||
#if USE_SMARTSTRAP
|
||||
// Event handler
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void prv_app_smartstrap_event(PebbleEvent *e, void *context) {
|
||||
SmartstrapConnectionState *state = app_state_get_smartstrap_state();
|
||||
PebbleSmartstrapEvent event = e->smartstrap;
|
||||
if (event.type == SmartstrapConnectionEvent) {
|
||||
// Drop this event if there's no handler registered
|
||||
if (state->handlers.availability_did_change) {
|
||||
const SmartstrapServiceId service_id = event.service_id;
|
||||
const bool is_available = (event.result == SmartstrapResultOk);
|
||||
state->handlers.availability_did_change(service_id, is_available);
|
||||
}
|
||||
} else {
|
||||
// All events other than SmartstrapConnectionEvent contain the attribute pointer
|
||||
SmartstrapAttribute *attr = event.attribute;
|
||||
PBL_ASSERTN(attr);
|
||||
if ((event.type == SmartstrapDataSentEvent) && state->handlers.did_write) {
|
||||
state->handlers.did_write(attr, event.result);
|
||||
} else if ((event.type == SmartstrapDataReceivedEvent) && state->handlers.did_read) {
|
||||
// 'attr' already points to the read buffer, so just need to cast it
|
||||
state->handlers.did_read(attr, event.result, (uint8_t *)attr, event.read_length);
|
||||
} else if ((event.type == SmartstrapNotifyEvent) && state->handlers.notified) {
|
||||
state->handlers.notified(attr);
|
||||
}
|
||||
sys_smartstrap_attribute_event_processed(attr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Subscription functions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static bool prv_should_subscribe(void) {
|
||||
// We don't need to subscribe until either the app creates an attribute or subscribes an
|
||||
// availability_did_change handler.
|
||||
SmartstrapConnectionState *state = app_state_get_smartstrap_state();
|
||||
return state->handlers.availability_did_change || state->num_attributes;
|
||||
}
|
||||
|
||||
static void prv_state_init(void) {
|
||||
SmartstrapConnectionState *state = app_state_get_smartstrap_state();
|
||||
if (state->is_initialized) {
|
||||
return;
|
||||
}
|
||||
state->event_info = (EventServiceInfo) {
|
||||
.type = PEBBLE_SMARTSTRAP_EVENT,
|
||||
.handler = prv_app_smartstrap_event
|
||||
};
|
||||
event_service_client_subscribe(&state->event_info);
|
||||
state->timeout_ms = SMARTSTRAP_TIMEOUT_DEFAULT;
|
||||
sys_smartstrap_subscribe();
|
||||
state->is_initialized = true;
|
||||
}
|
||||
|
||||
static void prv_state_deinit(void) {
|
||||
SmartstrapConnectionState *state = app_state_get_smartstrap_state();
|
||||
if (!state->is_initialized) {
|
||||
return;
|
||||
}
|
||||
state->is_initialized = false;
|
||||
event_service_client_unsubscribe(&state->event_info);
|
||||
sys_smartstrap_unsubscribe();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Internal APIs
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void app_smartstrap_cleanup(void) {
|
||||
#if USE_SMARTSTRAP
|
||||
prv_state_deinit();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Exported APIs
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SmartstrapResult app_smartstrap_subscribe(SmartstrapHandlers handlers) {
|
||||
#if USE_SMARTSTRAP
|
||||
SmartstrapConnectionState *state = app_state_get_smartstrap_state();
|
||||
state->handlers = handlers;
|
||||
if (prv_should_subscribe()) {
|
||||
prv_state_init();
|
||||
}
|
||||
return SmartstrapResultOk;
|
||||
#else
|
||||
return SmartstrapResultNotPresent;
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_smartstrap_unsubscribe(void) {
|
||||
#if USE_SMARTSTRAP
|
||||
SmartstrapConnectionState *state = app_state_get_smartstrap_state();
|
||||
state->handlers = (SmartstrapHandlers) {0};
|
||||
if (!prv_should_subscribe()) {
|
||||
prv_state_deinit();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_smartstrap_set_timeout(uint16_t timeout_ms) {
|
||||
#if USE_SMARTSTRAP
|
||||
SmartstrapConnectionState *state = app_state_get_smartstrap_state();
|
||||
state->timeout_ms = timeout_ms;
|
||||
#endif
|
||||
}
|
||||
|
||||
SmartstrapAttribute *app_smartstrap_attribute_create(SmartstrapServiceId service_id,
|
||||
SmartstrapAttributeId attribute_id,
|
||||
size_t buffer_length) {
|
||||
#if USE_SMARTSTRAP
|
||||
if (!buffer_length) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t *buffer = applib_zalloc(buffer_length);
|
||||
if (!buffer) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!sys_smartstrap_attribute_register(service_id, attribute_id, buffer, buffer_length)) {
|
||||
applib_free(buffer);
|
||||
return NULL;
|
||||
}
|
||||
SmartstrapConnectionState *state = app_state_get_smartstrap_state();
|
||||
state->num_attributes++;
|
||||
prv_state_init();
|
||||
return (SmartstrapAttribute *)buffer;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_smartstrap_attribute_destroy(SmartstrapAttribute *attr) {
|
||||
#if USE_SMARTSTRAP
|
||||
SmartstrapConnectionState *state = app_state_get_smartstrap_state();
|
||||
state->num_attributes--;
|
||||
if (!prv_should_subscribe()) {
|
||||
prv_state_deinit();
|
||||
}
|
||||
sys_smartstrap_attribute_unregister(attr);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool app_smartstrap_service_is_available(SmartstrapServiceId service_id) {
|
||||
#if USE_SMARTSTRAP
|
||||
return sys_smartstrap_is_service_connected(service_id);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
SmartstrapServiceId app_smartstrap_attribute_get_service_id(SmartstrapAttribute *attr) {
|
||||
#if USE_SMARTSTRAP
|
||||
SmartstrapServiceId service_id;
|
||||
sys_smartstrap_attribute_get_info(attr, &service_id, NULL, NULL);
|
||||
return service_id;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
SmartstrapAttributeId app_smartstrap_attribute_get_attribute_id(SmartstrapAttribute *attr) {
|
||||
#if USE_SMARTSTRAP
|
||||
SmartstrapServiceId attribute_id;
|
||||
sys_smartstrap_attribute_get_info(attr, NULL, &attribute_id, NULL);
|
||||
return attribute_id;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
SmartstrapResult app_smartstrap_attribute_read(SmartstrapAttribute *attr) {
|
||||
#if USE_SMARTSTRAP
|
||||
if (!attr) {
|
||||
return SmartstrapResultInvalidArgs;
|
||||
}
|
||||
SmartstrapConnectionState *state = app_state_get_smartstrap_state();
|
||||
return sys_smartstrap_attribute_do_request(attr, SmartstrapRequestTypeRead, state->timeout_ms, 0);
|
||||
#else
|
||||
return SmartstrapResultNotPresent;
|
||||
#endif
|
||||
}
|
||||
|
||||
SmartstrapResult app_smartstrap_attribute_begin_write(SmartstrapAttribute *attr, uint8_t **buffer,
|
||||
size_t *buffer_length) {
|
||||
#if USE_SMARTSTRAP
|
||||
if (!attr || !buffer || !buffer_length) {
|
||||
return SmartstrapResultInvalidArgs;
|
||||
}
|
||||
const SmartstrapRequestType type = SmartstrapRequestTypeBeginWrite;
|
||||
SmartstrapResult result = sys_smartstrap_attribute_do_request(attr, type, 0, 0);
|
||||
if (result == SmartstrapResultOk) {
|
||||
*buffer = (uint8_t *)attr;
|
||||
sys_smartstrap_attribute_get_info(attr, NULL, NULL, buffer_length);
|
||||
}
|
||||
return result;
|
||||
#else
|
||||
return SmartstrapResultNotPresent;
|
||||
#endif
|
||||
}
|
||||
|
||||
SmartstrapResult app_smartstrap_attribute_end_write(SmartstrapAttribute *attr, size_t write_length,
|
||||
bool request_read) {
|
||||
#if USE_SMARTSTRAP
|
||||
if (!attr) {
|
||||
return SmartstrapResultInvalidArgs;
|
||||
}
|
||||
SmartstrapRequestType type = request_read ?
|
||||
SmartstrapRequestTypeWriteRead :
|
||||
SmartstrapRequestTypeWrite;
|
||||
SmartstrapConnectionState *state = app_state_get_smartstrap_state();
|
||||
return sys_smartstrap_attribute_do_request(attr, type, state->timeout_ms, write_length);
|
||||
#else
|
||||
return SmartstrapResultNotPresent;
|
||||
#endif
|
||||
}
|
216
src/fw/applib/app_smartstrap.h
Normal file
216
src/fw/applib/app_smartstrap.h
Normal file
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! @addtogroup Smartstrap
|
||||
//! @{
|
||||
//!
|
||||
|
||||
//! As long as the firmware maintains its current major version, attributes of this length or
|
||||
//! less will be allowed. Note that the maximum number of bytes which may be read from the
|
||||
//! smartstrap before a timeout occurs depends on the baud rate and implementation efficiency of the
|
||||
//! underlying UART protocol on the smartstrap.
|
||||
#define SMARTSTRAP_ATTRIBUTE_LENGTH_MAXIMUM 65535
|
||||
|
||||
//! The default request timeout in milliseconds (see \ref smartstrap_set_timeout).
|
||||
#define SMARTSTRAP_TIMEOUT_DEFAULT 250
|
||||
|
||||
//! The service_id to specify in order to read/write raw data to the smartstrap.
|
||||
#define SMARTSTRAP_RAW_DATA_SERVICE_ID 0
|
||||
|
||||
//! The attribute_id to specify in order to read/write raw data to the smartstrap.
|
||||
#define SMARTSTRAP_RAW_DATA_ATTRIBUTE_ID 0
|
||||
|
||||
// convenient macros to distinguish between smartstrap and no smartstrap.
|
||||
// TODO: PBL-21978 remove redundant comments as a workaround around for SDK generator
|
||||
#if defined(PBL_SMARTSTRAP)
|
||||
|
||||
//! Convenience macro to switch between two expressions depending on smartstrap support.
|
||||
//! On platforms with a smartstrap the first expression will be chosen, the second otherwise.
|
||||
#define PBL_IF_SMARTSTRAP_ELSE(if_true, if_false) (if_true)
|
||||
|
||||
#else
|
||||
|
||||
//! Convenience macro to switch between two expressions depending on smartstrap support.
|
||||
//! On platforms with a smartstrap the first expression will be chosen, the second otherwise.
|
||||
#define PBL_IF_SMARTSTRAP_ELSE(if_true, if_false) (if_false)
|
||||
#endif
|
||||
|
||||
//! Error values which may be returned from the smartstrap APIs.
|
||||
typedef enum {
|
||||
//! No error occured.
|
||||
SmartstrapResultOk = 0,
|
||||
//! Invalid function arguments were supplied.
|
||||
SmartstrapResultInvalidArgs,
|
||||
//! The smartstrap port is not present on this watch.
|
||||
SmartstrapResultNotPresent,
|
||||
//! A request is already pending on the specified attribute.
|
||||
SmartstrapResultBusy,
|
||||
//! Either a smartstrap is not connected or the connected smartstrap does not support the
|
||||
//! specified service.
|
||||
SmartstrapResultServiceUnavailable,
|
||||
//! The smartstrap reported that it does not support the requested attribute.
|
||||
SmartstrapResultAttributeUnsupported,
|
||||
//! A time-out occured during the request.
|
||||
SmartstrapResultTimeOut,
|
||||
} SmartstrapResult;
|
||||
|
||||
//! A type representing a smartstrap ServiceId.
|
||||
typedef uint16_t SmartstrapServiceId;
|
||||
|
||||
//! A type representing a smartstrap AttributeId.
|
||||
typedef uint16_t SmartstrapAttributeId;
|
||||
|
||||
//! A type representing an attribute of a service provided by a smartstrap. This type is used when
|
||||
//! issuing requests to the smartstrap.
|
||||
typedef struct SmartstrapAttribute SmartstrapAttribute;
|
||||
|
||||
//! The type of function which is called after the smartstrap connection status changes.
|
||||
//! @param service_id The ServiceId for which the availability changed.
|
||||
//! @param is_available Whether or not this service is now available.
|
||||
typedef void (*SmartstrapServiceAvailabilityHandler)(SmartstrapServiceId service_id,
|
||||
bool is_available);
|
||||
|
||||
//! The type of function which can be called when a read request is completed.
|
||||
//! @note Any write request made to the same attribute within this function will fail with
|
||||
//! SmartstrapResultBusy.
|
||||
//! @param attribute The attribute which was read.
|
||||
//! @param result The result of the read.
|
||||
//! @param data The data read from the smartstrap or NULL if the read was not successful.
|
||||
//! @param length The length of the data or 0 if the read was not successful.
|
||||
typedef void (*SmartstrapReadHandler)(SmartstrapAttribute *attribute, SmartstrapResult result,
|
||||
const uint8_t *data, size_t length);
|
||||
|
||||
//! The type of function which can be called when a write request is completed.
|
||||
//! @param attribute The attribute which was written.
|
||||
//! @param result The result of the write.
|
||||
typedef void (*SmartstrapWriteHandler)(SmartstrapAttribute *attribute, SmartstrapResult result);
|
||||
|
||||
//! The type of function which can be called when the smartstrap sends a notification to the watch
|
||||
//! @param attribute The attribute which the notification came from.
|
||||
typedef void (*SmartstrapNotifyHandler)(SmartstrapAttribute *attribute);
|
||||
|
||||
//! Handlers which are passed to smartstrap_subscribe.
|
||||
typedef struct {
|
||||
//! The connection handler is called after the connection state changes.
|
||||
SmartstrapServiceAvailabilityHandler availability_did_change;
|
||||
//! The read handler is called whenever a read is complete or the read times-out.
|
||||
SmartstrapReadHandler did_read;
|
||||
//! The did_write handler is called when a write has completed.
|
||||
SmartstrapWriteHandler did_write;
|
||||
//! The notified handler is called whenever a notification is received for an attribute.
|
||||
SmartstrapNotifyHandler notified;
|
||||
} SmartstrapHandlers;
|
||||
|
||||
//! Subscribes handlers to be called after certain smartstrap events occur.
|
||||
//! @note Registering an availability_did_change handler will cause power to be applied to the
|
||||
//! smartstrap port and connection establishment to begin.
|
||||
//! @see smartstrap_unsubscribe
|
||||
//! @returns `SmartstrapResultNotPresent` if the watch does not have a smartstrap port or
|
||||
//! `SmartstrapResultOk` otherwise.
|
||||
SmartstrapResult app_smartstrap_subscribe(SmartstrapHandlers handlers);
|
||||
|
||||
//! Unsubscribes the handlers. The handlers will no longer be called, but in-flight requests will
|
||||
//! otherwise be unaffected.
|
||||
//! @note If power was being applied to the smartstrap port and there are no attributes have been
|
||||
//! created (or they have all been destroyed), this will cause the smartstrap power to be turned
|
||||
//! off.
|
||||
void app_smartstrap_unsubscribe(void);
|
||||
|
||||
//! Changes the value of the timeout which is used for smartstrap requests. This timeout is started
|
||||
//! after the request is completely sent to the smartstrap and will be canceled only if the entire
|
||||
//! response is received before it triggers. The new timeout value will take affect only for
|
||||
//! requests made after this API is called.
|
||||
//! @param timeout_ms The duration of the timeout to set, in milliseconds.
|
||||
//! @note The maximum allowed timeout is currently 1000ms. If a larger value is passed, it will be
|
||||
//! internally lowered to the maximum.
|
||||
//! @see SMARTSTRAP_TIMEOUT_DEFAULT
|
||||
void app_smartstrap_set_timeout(uint16_t timeout_ms);
|
||||
|
||||
//! Creates and returns a SmartstrapAttribute for the specified service and attribute. This API
|
||||
//! will allocate an internal buffer of the requested length on the app's heap.
|
||||
//! @note Creating an attribute will result in power being applied to the smartstrap port (if it
|
||||
//! isn't already) and connection establishment to begin.
|
||||
//! @param service_id The ServiceId to create the attribute for.
|
||||
//! @param attribute_id The AttributeId to create the attribute for.
|
||||
//! @param buffer_length The length of the internal buffer which will be used to store the read
|
||||
//! and write requests for this attribute.
|
||||
//! @returns The newly created SmartstrapAttribute or NULL if an internal error occured or if the
|
||||
//! specified length is greater than SMARTSTRAP_ATTRIBUTE_LENGTH_MAXIMUM.
|
||||
SmartstrapAttribute *app_smartstrap_attribute_create(SmartstrapServiceId service_id,
|
||||
SmartstrapAttributeId attribute_id,
|
||||
size_t buffer_length);
|
||||
|
||||
//! Destroys a SmartstrapAttribute. No further handlers will be called for this attribute and it
|
||||
//! may not be used for any future requests.
|
||||
//! @param[in] attribute The SmartstrapAttribute which should be destroyed.
|
||||
//! @note If power was being applied to the smartstrap port, no availability_did_change handler is
|
||||
//! subscribed, and the last attribute is being destroyed, this will cause the smartstrap power to
|
||||
//! be turned off.
|
||||
void app_smartstrap_attribute_destroy(SmartstrapAttribute *attribute);
|
||||
|
||||
//! Checks whether or not the specified service is currently supported by a connected smartstrap.
|
||||
//! @param service_id The SmartstrapServiceId of the service to check for availability.
|
||||
//! @returns Whether or not the service is available.
|
||||
bool app_smartstrap_service_is_available(SmartstrapServiceId service_id);
|
||||
|
||||
//! Returns the ServiceId which the attribute was created for (see \ref
|
||||
//! smartstrap_attribute_create).
|
||||
//! @param attribute The SmartstrapAttribute for which to obtain the service ID.
|
||||
//! @returns The SmartstrapServiceId which the attribute was created with.
|
||||
SmartstrapServiceId app_smartstrap_attribute_get_service_id(SmartstrapAttribute *attribute);
|
||||
|
||||
//! Gets the AttributeId which the attribute was created for (see \ref smartstrap_attribute_create).
|
||||
//! @param attribute The SmartstrapAttribute for which to obtain the attribute ID.
|
||||
//! @returns The SmartstrapAttributeId which the attribute was created with.
|
||||
SmartstrapAttributeId app_smartstrap_attribute_get_attribute_id(SmartstrapAttribute *attribute);
|
||||
|
||||
//! Performs a read request for the specified attribute. The `did_read` callback will be called when
|
||||
//! the response is received from the smartstrap or when an error occurs.
|
||||
//! @param attribute The attribute to be perform the read request on.
|
||||
//! @returns `SmartstrapResultOk` if the read operation was started. The `did_read` callback will
|
||||
//! be called once the read request has been completed.
|
||||
SmartstrapResult app_smartstrap_attribute_read(SmartstrapAttribute *attribute);
|
||||
|
||||
//! Begins a write request for the specified attribute and returns a buffer into which the app
|
||||
//! should write the data before calling smartstrap_attribute_end_write.
|
||||
//! @note The buffer must not be used after smartstrap_attribute_end_write is called.
|
||||
//! @param[in] attribute The attribute to begin writing for.
|
||||
//! @param[out] buffer The buffer to write the data into.
|
||||
//! @param[out] buffer_length The length of the buffer in bytes.
|
||||
//! @returns `SmartstrapResultOk` if a write operation was started and the `buffer` and
|
||||
//! `buffer_length` parameters were set, or an error otherwise.
|
||||
SmartstrapResult app_smartstrap_attribute_begin_write(SmartstrapAttribute *attribute,
|
||||
uint8_t **buffer, size_t *buffer_length);
|
||||
|
||||
//! This should be called by the app when it is done writing to the buffer provided by
|
||||
//! smartstrap_begin_write and the data is ready to be sent to the smartstrap.
|
||||
//! @param[in] attribute The attribute to begin writing for.
|
||||
//! @param write_length The length of the data to be written, in bytes.
|
||||
//! @param request_read Whether or not a read request on this attribute should be
|
||||
//! automatically triggered following a successful write request.
|
||||
//! @returns `SmartstrapResultOk` if a write operation was queued to be sent to the smartstrap. The
|
||||
//! `did_write` handler will be called when the request is written to the smartstrap, and if
|
||||
//! `request_read` was set to true, the `did_read` handler will be called when the read is complete.
|
||||
SmartstrapResult app_smartstrap_attribute_end_write(SmartstrapAttribute *attribute,
|
||||
size_t write_length, bool request_read);
|
||||
|
||||
//! @} // end addtogroup Smartstrap
|
41
src/fw/applib/app_smartstrap_private.h
Normal file
41
src/fw/applib/app_smartstrap_private.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 "applib/app_smartstrap.h"
|
||||
#include "applib/event_service_client.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/list.h"
|
||||
#include "util/mbuf.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct PACKED {
|
||||
//! Info used to subscribed to smartstrap events
|
||||
EventServiceInfo event_info;
|
||||
//! How many attributes the app has created
|
||||
uint32_t num_attributes;
|
||||
//! Handlers supplied by the app
|
||||
SmartstrapHandlers handlers;
|
||||
//! Timeout configurable by the app
|
||||
uint16_t timeout_ms;
|
||||
//! Whether or not this struct is initialized
|
||||
bool is_initialized;
|
||||
} SmartstrapConnectionState;
|
||||
|
||||
void app_smartstrap_cleanup(void);
|
123
src/fw/applib/app_sync/app_sync.c
Normal file
123
src/fw/applib/app_sync/app_sync.c
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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 "applib/app_sync/app_sync.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static void delegate_errors(AppSync *s, DictionaryResult dict_result,
|
||||
AppMessageResult app_message_result) {
|
||||
if (dict_result == DICT_OK && app_message_result == APP_MSG_OK) {
|
||||
return;
|
||||
}
|
||||
if (s->callback.error) {
|
||||
s->callback.error(dict_result, app_message_result, s->callback.context);
|
||||
}
|
||||
}
|
||||
|
||||
static void update_key_callback(const uint32_t key, const Tuple *new_tuple,
|
||||
const Tuple *old_tuple, void *context) {
|
||||
AppSync *s = context;
|
||||
if (s->callback.value_changed) {
|
||||
s->callback.value_changed(key, new_tuple, old_tuple, s->callback.context);
|
||||
}
|
||||
}
|
||||
|
||||
static void pass_initial_values_app_task_callback(void *data) {
|
||||
AppSync *s = data;
|
||||
Tuple *tuple = dict_read_first(&s->current_iter);
|
||||
while (tuple) {
|
||||
update_key_callback(tuple->key, tuple, NULL, s);
|
||||
tuple = dict_read_next(&s->current_iter);
|
||||
}
|
||||
}
|
||||
|
||||
static void update_callback(DictionaryIterator *updated_iter, void *context) {
|
||||
AppSync *s = context;
|
||||
uint32_t size = s->buffer_size;
|
||||
const bool update_existing_keys_only = true;
|
||||
DictionaryResult result = dict_merge(&s->current_iter, &size,
|
||||
updated_iter,
|
||||
update_existing_keys_only,
|
||||
update_key_callback, s);
|
||||
delegate_errors(s, result, APP_MSG_OK);
|
||||
}
|
||||
|
||||
static void out_failed_callback(DictionaryIterator *failed, AppMessageResult reason,
|
||||
void *context) {
|
||||
AppSync *s = context;
|
||||
delegate_errors(s, DICT_OK, reason);
|
||||
}
|
||||
|
||||
static void in_dropped_callback(AppMessageResult reason, void *context) {
|
||||
AppSync *s = context;
|
||||
delegate_errors(s, DICT_OK, reason);
|
||||
}
|
||||
|
||||
// FIXME PBL-1709: this should return an AppMessageResult ...
|
||||
void app_sync_init(AppSync *s,
|
||||
uint8_t *buffer, const uint16_t buffer_size,
|
||||
const Tuplet * const keys_and_initial_values, const uint8_t count,
|
||||
AppSyncTupleChangedCallback tuple_changed_callback,
|
||||
AppSyncErrorCallback error_callback,
|
||||
void *context) {
|
||||
PBL_ASSERTN(buffer != NULL);
|
||||
PBL_ASSERTN(buffer_size > 0);
|
||||
s->buffer = buffer;
|
||||
s->buffer_size = buffer_size;
|
||||
s->callback.value_changed = tuple_changed_callback;
|
||||
s->callback.error = error_callback;
|
||||
s->callback.context = context;
|
||||
uint32_t in_out_size = buffer_size;
|
||||
const DictionaryResult dict_result = dict_serialize_tuplets_to_buffer_with_iter(
|
||||
&s->current_iter, keys_and_initial_values, count, s->buffer, &in_out_size);
|
||||
app_message_set_context(s);
|
||||
app_message_register_outbox_sent(update_callback);
|
||||
app_message_register_outbox_failed(out_failed_callback);
|
||||
app_message_register_inbox_received(update_callback);
|
||||
app_message_register_inbox_dropped(in_dropped_callback);
|
||||
sys_current_process_schedule_callback(pass_initial_values_app_task_callback, s);
|
||||
delegate_errors(s, dict_result, APP_MSG_OK);
|
||||
}
|
||||
|
||||
void app_sync_deinit(AppSync *s) {
|
||||
app_message_set_context(NULL);
|
||||
app_message_register_outbox_sent(NULL);
|
||||
app_message_register_outbox_failed(NULL);
|
||||
app_message_register_inbox_received(NULL);
|
||||
app_message_register_inbox_dropped(NULL);
|
||||
s->current = NULL;
|
||||
}
|
||||
|
||||
AppMessageResult app_sync_set(AppSync *s, const Tuplet * const updated_keys_and_values,
|
||||
const uint8_t count) {
|
||||
DictionaryIterator *iter;
|
||||
AppMessageResult result = app_message_outbox_begin(&iter);
|
||||
if (iter == NULL) {
|
||||
return result;
|
||||
}
|
||||
for (unsigned int i = 0; i < count; ++i) {
|
||||
dict_write_tuplet(iter, &updated_keys_and_values[i]);
|
||||
}
|
||||
dict_write_end(iter);
|
||||
return app_message_outbox_send();
|
||||
}
|
||||
|
||||
const Tuple * app_sync_get(const AppSync *s, const uint32_t key) {
|
||||
return dict_find(&s->current_iter, key);
|
||||
}
|
164
src/fw/applib/app_sync/app_sync.h
Normal file
164
src/fw/applib/app_sync/app_sync.h
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* 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 "util/dict.h"
|
||||
#include "applib/app_message/app_message.h"
|
||||
|
||||
//! @file app_sync.h
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup AppSync
|
||||
//! \brief UI synchronization layer for AppMessage
|
||||
//!
|
||||
//! AppSync is a convenience layer that resides on top of \ref AppMessage, and serves
|
||||
//! as a UI synchronization layer for AppMessage. In so doing, AppSync makes it easier
|
||||
//! to drive the information displayed in the watchapp UI with messages sent by a phone app.
|
||||
//!
|
||||
//! AppSync maintains and updates a Dictionary, and provides your app with a callback
|
||||
//! (AppSyncTupleChangedCallback) routine that is called whenever the Dictionary changes
|
||||
//! and the app's UI is updated. Note that the app UI is not updated automatically.
|
||||
//! To update the UI, you need to implement the callback.
|
||||
//!
|
||||
//! Pebble OS provides support for data serialization utilities, like Dictionary,
|
||||
//! Tuple and Tuplet data structures and their accompanying functions. You use Tuplets to create
|
||||
//! a Dictionary with Tuple structures.
|
||||
//!
|
||||
//! AppSync manages the storage and bookkeeping chores of the current Tuple values. AppSync copies
|
||||
//! incoming AppMessage Tuples into this "current" Dictionary, so that the key/values remain
|
||||
//! available for the UI to use. For example, it is safe to use a C-string value provided by AppSync
|
||||
//! and use it directly in a text_layer_set_text() call.
|
||||
//!
|
||||
//! Your app needs to supply the buffer that AppSync uses for the "current" Dictionary when
|
||||
//! initializing AppSync.
|
||||
//!
|
||||
//! Refer to the
|
||||
//! <a href="https://developer.getpebble.com/guides/pebble-apps/communications/appsync/">
|
||||
//! Synchronizing App UI</a>
|
||||
//! guide for a conceptual overview and code usage.
|
||||
//! @{
|
||||
|
||||
struct AppSync;
|
||||
|
||||
//! Called whenever a Tuple changes. This does not necessarily mean the value in
|
||||
//! the Tuple has changed. When the internal "current" dictionary gets updated,
|
||||
//! existing Tuples might get shuffled around in the backing buffer, even though
|
||||
//! the values stay the same. In this callback, the client code gets the chance
|
||||
//! to remove the old reference and start using the new one.
|
||||
//! In this callback, your application MUST clean up any references to the
|
||||
//! `old_tuple` of a PREVIOUS call to this callback (and replace it with the
|
||||
//! `new_tuple` that is passed in with the current call).
|
||||
//! @param key The key for which the Tuple was changed.
|
||||
//! @param new_tuple The new tuple. The tuple points to the actual, updated
|
||||
//! "current" dictionary, as backed by the buffer internal to the AppSync
|
||||
//! struct. Therefore the Tuple can be used after the callback returns, until
|
||||
//! the AppSync is deinited. In case there was an error (e.g. storage shortage),
|
||||
//! this `new_tuple` can be `NULL_TUPLE`.
|
||||
//! @param old_tuple The values that will be replaced with `new_tuple`. The key,
|
||||
//! value and type will be equal to the previous tuple in the old destination
|
||||
//! dictionary; however, the `old_tuple` points to a stack-allocated copy of the
|
||||
//! old data. This value will be `NULL_TUPLE` when the initial values are
|
||||
//! being set.
|
||||
//! @param context Pointer to application specific data, as set using
|
||||
//! \ref app_sync_init()
|
||||
//! @see \ref app_sync_init()
|
||||
typedef void (*AppSyncTupleChangedCallback)(const uint32_t key, const Tuple *new_tuple,
|
||||
const Tuple *old_tuple, void *context);
|
||||
|
||||
//! Called whenever there was an error.
|
||||
//! @param dict_error The dictionary result error code, if the error was
|
||||
//! dictionary related.
|
||||
//! @param app_message_error The app_message result error code, if the error
|
||||
//! was app_message related.
|
||||
//! @param context Pointer to application specific data, as set using
|
||||
//! \ref app_sync_init()
|
||||
//! @see \ref app_sync_init()
|
||||
typedef void (*AppSyncErrorCallback)(DictionaryResult dict_error,
|
||||
AppMessageResult app_message_error, void *context);
|
||||
|
||||
//! Initialized an AppSync system with specific buffer size and initial keys and
|
||||
//! values. The `callback.value_changed` callback will be called
|
||||
//! __asynchronously__ with the initial keys and values, as to avoid duplicating
|
||||
//! code to update your app's UI.
|
||||
//! @param s The AppSync context to initialize
|
||||
//! @param buffer The buffer that AppSync should use
|
||||
//! @param buffer_size The size of the backing storage of the "current"
|
||||
//! dictionary. Use \ref dict_calc_buffer_size_from_tuplets() to estimate the
|
||||
//! size you need.
|
||||
//! @param keys_and_initial_values An array of Tuplets with the initial keys and
|
||||
//! values.
|
||||
//! @param count The number of Tuplets in the `keys_and_initial_values` array.
|
||||
//! @param tuple_changed_callback The callback that will handle changed
|
||||
//! key/value pairs
|
||||
//! @param error_callback The callback that will handle errors
|
||||
//! @param context Pointer to app specific data that will get passed into calls
|
||||
//! to the callbacks
|
||||
//! @note Only updates for the keys specified in this initial array will be
|
||||
//! accepted by AppSync, updates for other keys that might come in will just be
|
||||
//! ignored.
|
||||
void app_sync_init(struct AppSync *s, uint8_t *buffer, const uint16_t buffer_size,
|
||||
const Tuplet * const keys_and_initial_values, const uint8_t count,
|
||||
AppSyncTupleChangedCallback tuple_changed_callback,
|
||||
AppSyncErrorCallback error_callback, void *context);
|
||||
|
||||
|
||||
//! Cleans up an AppSync system.
|
||||
//! It frees the buffer allocated by an \ref app_sync_init() call and
|
||||
//! deregisters itself from the \ref AppMessage subsystem.
|
||||
//! @param s The AppSync context to deinit.
|
||||
void app_sync_deinit(struct AppSync *s);
|
||||
|
||||
//! Updates key/value pairs using an array of Tuplets.
|
||||
//! @note The call will attempt to send the updated keys and values to the
|
||||
//! application on the other end.
|
||||
//! Only after the other end has acknowledged the update, the `.value_changed`
|
||||
//! callback will be called to confirm the update has completed and your
|
||||
//! application code can update its user interface.
|
||||
//! @param s The AppSync context
|
||||
//! @param keys_and_values_to_update An array of Tuplets with the keys and
|
||||
//! values to update. The data in the Tuplets are copied during the call, so the
|
||||
//! array can be stack-allocated.
|
||||
//! @param count The number of Tuplets in the `keys_and_values_to_update` array.
|
||||
//! @return The result code from the \ref AppMessage subsystem.
|
||||
//! Can be \ref APP_MSG_OK, \ref APP_MSG_BUSY or \ref APP_MSG_INVALID_ARGS
|
||||
AppMessageResult app_sync_set(struct AppSync *s, const Tuplet * const keys_and_values_to_update,
|
||||
const uint8_t count);
|
||||
|
||||
//! Finds and gets a tuple in the "current" dictionary.
|
||||
//! @param s The AppSync context
|
||||
//! @param key The key for which to find a Tuple
|
||||
//! @return Pointer to a found Tuple, or NULL if there was no Tuple with the
|
||||
//! specified key.
|
||||
const Tuple * app_sync_get(const struct AppSync *s, const uint32_t key);
|
||||
|
||||
|
||||
//! @} // end addtogroup AppSync
|
||||
//! @} // end addtogroup Foundation
|
||||
|
||||
typedef struct AppSync {
|
||||
DictionaryIterator current_iter;
|
||||
union {
|
||||
Dictionary *current;
|
||||
uint8_t *buffer;
|
||||
};
|
||||
uint16_t buffer_size;
|
||||
struct {
|
||||
AppSyncTupleChangedCallback value_changed;
|
||||
AppSyncErrorCallback error;
|
||||
void *context;
|
||||
} callback;
|
||||
} AppSync;
|
86
src/fw/applib/app_timer.c
Normal file
86
src/fw/applib/app_timer.c
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 "applib/app_timer.h"
|
||||
|
||||
#include "applib/app_logging.h"
|
||||
#include "services/common/evented_timer.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
|
||||
//! @file fw/applib/app_timer.c
|
||||
//!
|
||||
//! Surpise! All this is is a dumb wrapper around evented_timer!
|
||||
|
||||
DEFINE_SYSCALL(AppTimer*, app_timer_register, uint32_t timeout_ms,
|
||||
AppTimerCallback callback,
|
||||
void* callback_data) {
|
||||
// No need to check callback_data, we only dereference it in userspace anyway.
|
||||
return (AppTimer*)(uintptr_t)evented_timer_register(timeout_ms, false, callback, callback_data);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(AppTimer*, app_timer_register_repeatable, uint32_t timeout_ms,
|
||||
AppTimerCallback callback,
|
||||
void* callback_data,
|
||||
bool repeating) {
|
||||
// No need to check callback_data, we only dereference it in userspace anyway.
|
||||
return (AppTimer*)(uintptr_t)evented_timer_register(timeout_ms,
|
||||
repeating,
|
||||
callback,
|
||||
callback_data);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(bool, app_timer_reschedule, AppTimer *timer, uint32_t new_timeout_ms) {
|
||||
if (PRIVILEGE_WAS_ELEVATED) {
|
||||
if (!evented_timer_exists((EventedTimerID)timer)) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Timer %u does not exist", (unsigned)timer);
|
||||
return (false);
|
||||
}
|
||||
if (!evented_timer_is_current_task((EventedTimerID)timer)) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Invalid timer %u used in app_timer_reschedule", (unsigned)timer);
|
||||
syscall_failed();
|
||||
}
|
||||
}
|
||||
return evented_timer_reschedule((EventedTimerID)timer, new_timeout_ms);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(void, app_timer_cancel, AppTimer *timer) {
|
||||
if (PRIVILEGE_WAS_ELEVATED) {
|
||||
if (!evented_timer_exists((EventedTimerID)timer)) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Timer %u does not exist", (unsigned)timer);
|
||||
return;
|
||||
}
|
||||
if (!evented_timer_is_current_task((EventedTimerID)timer)) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Invalid timer %u used in app_timer_reschedule", (unsigned)timer);
|
||||
syscall_failed();
|
||||
}
|
||||
}
|
||||
evented_timer_cancel((EventedTimerID)timer);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(void *, app_timer_get_data, AppTimer *timer) {
|
||||
if (PRIVILEGE_WAS_ELEVATED) {
|
||||
if (!evented_timer_exists((EventedTimerID)timer)) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Timer %u does not exist", (unsigned)timer);
|
||||
return NULL;
|
||||
}
|
||||
if (!evented_timer_is_current_task((EventedTimerID)timer)) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Invalid timer %u used in app_timer_reschedule", (unsigned)timer);
|
||||
syscall_failed();
|
||||
}
|
||||
}
|
||||
|
||||
return evented_timer_get_data((EventedTimerID)timer);
|
||||
}
|
70
src/fw/applib/app_timer.h
Normal file
70
src/fw/applib/app_timer.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup Timer
|
||||
//! \brief Can be used to execute some code at some point in the future.
|
||||
//! @{
|
||||
|
||||
//! An opaque handle to a timer
|
||||
struct AppTimer;
|
||||
typedef struct AppTimer AppTimer;
|
||||
|
||||
//! The type of function which can be called when a timer fires. The argument will be the @p callback_data passed to
|
||||
//! @ref app_timer_register().
|
||||
typedef void (*AppTimerCallback)(void* data);
|
||||
|
||||
//! Registers a timer that ends up in callback being called some specified time in the future.
|
||||
//! @param timeout_ms The expiry time in milliseconds from the current time
|
||||
//! @param callback The callback that gets called at expiry time
|
||||
//! @param callback_data The data that will be passed to callback
|
||||
//! @return A pointer to an `AppTimer` that can be used to later reschedule or cancel this timer
|
||||
AppTimer* app_timer_register(uint32_t timeout_ms, AppTimerCallback callback, void* callback_data);
|
||||
|
||||
//! @internal
|
||||
//! Registers a timer that ends up in callback being called repeatedly at a specified interval
|
||||
//! @param timeout_ms The interval time in milliseconds from the current time
|
||||
//! @param callback The callback that gets called at every interval
|
||||
//! @param callback_data The data that will be passed to callback
|
||||
//! @return A pointer to an `AppTimer` that can be used to later reschedule or cancel this timer
|
||||
AppTimer* app_timer_register_repeatable(uint32_t timeout_ms,
|
||||
AppTimerCallback callback,
|
||||
void* callback_data,
|
||||
bool repeating);
|
||||
|
||||
//! @internal
|
||||
//! Get the data passed to the app timer
|
||||
void *app_timer_get_data(AppTimer *timer);
|
||||
|
||||
//! Reschedules an already running timer for some point in the future.
|
||||
//! @param timer_handle The timer to reschedule
|
||||
//! @param new_timeout_ms The new expiry time in milliseconds from the current time
|
||||
//! @return true if the timer was rescheduled, false if the timer has already elapsed
|
||||
bool app_timer_reschedule(AppTimer *timer_handle, uint32_t new_timeout_ms);
|
||||
|
||||
//! Cancels an already registered timer.
|
||||
//! Once cancelled the handle may no longer be used for any purpose.
|
||||
void app_timer_cancel(AppTimer *timer_handle);
|
||||
|
||||
//! @} // group Timer
|
||||
//! @} // group Foundation
|
||||
|
82
src/fw/applib/app_wakeup.c
Normal file
82
src/fw/applib/app_wakeup.c
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 "applib/app_wakeup.h"
|
||||
|
||||
#include "event_service_client.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "services/normal/wakeup.h"
|
||||
#include "kernel/events.h"
|
||||
|
||||
static void do_handle(PebbleEvent *e, void *context) {
|
||||
WakeupHandler wakeup_handler = app_state_get_wakeup_handler();
|
||||
if (wakeup_handler != NULL) {
|
||||
wakeup_handler(e->wakeup.wakeup_info.wakeup_id, e->wakeup.wakeup_info.wakeup_reason);
|
||||
}
|
||||
}
|
||||
|
||||
void app_wakeup_service_subscribe(WakeupHandler handler) {
|
||||
if (handler) {
|
||||
app_state_set_wakeup_handler(handler);
|
||||
// Subscribe to PEBBLE_WAKEUP_EVENT
|
||||
EventServiceInfo *wakeup_event_info = app_state_get_wakeup_event_info();
|
||||
// NOTE: the individual fields of wakeup_event_info are assigned to
|
||||
// instead of writing
|
||||
// *wakeup_event_info = (EventServiceInfo) { ... }
|
||||
// as the latter would zero out the ListNode embedded in the struct.
|
||||
// Doing so would corrupt the events list if the event was already
|
||||
// subscribed to (the app calls app_wakeup_service_subscribe twice).
|
||||
wakeup_event_info->type = PEBBLE_WAKEUP_EVENT;
|
||||
wakeup_event_info->handler = do_handle;
|
||||
event_service_client_subscribe(wakeup_event_info);
|
||||
}
|
||||
}
|
||||
|
||||
WakeupId app_wakeup_schedule(time_t timestamp, int32_t cookie, bool notify_if_missed) {
|
||||
return sys_wakeup_schedule(timestamp, cookie, notify_if_missed);
|
||||
}
|
||||
|
||||
void app_wakeup_cancel(WakeupId wakeup_id) {
|
||||
sys_wakeup_delete(wakeup_id);
|
||||
}
|
||||
|
||||
void app_wakeup_cancel_all(void) {
|
||||
sys_wakeup_cancel_all_for_app();
|
||||
}
|
||||
|
||||
bool app_wakeup_get_launch_event(WakeupId *wakeup_id, int32_t *cookie) {
|
||||
WakeupInfo wakeup_info;
|
||||
sys_process_get_wakeup_info(&wakeup_info);
|
||||
//If the id is invalid, return false
|
||||
if (wakeup_info.wakeup_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*wakeup_id = wakeup_info.wakeup_id;
|
||||
*cookie = wakeup_info.wakeup_reason;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool app_wakeup_query(WakeupId wakeup_id, time_t *timestamp) {
|
||||
time_t scheduled_time = sys_wakeup_query(wakeup_id);
|
||||
if (timestamp != NULL) {
|
||||
*timestamp = scheduled_time;
|
||||
}
|
||||
|
||||
return (scheduled_time >= 0) ? true : false;
|
||||
}
|
85
src/fw/applib/app_wakeup.h
Normal file
85
src/fw/applib/app_wakeup.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/time/time.h"
|
||||
#include "services/normal/wakeup.h"
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup Wakeup
|
||||
//! \brief Allows applications to schedule to be launched even if they are not running.
|
||||
//! @{
|
||||
|
||||
|
||||
//! The type of function which can be called when a wakeup event occurs.
|
||||
//! The arguments will be the id of the wakeup event that occurred,
|
||||
//! as well as the scheduled cookie provided to \ref wakeup_schedule.
|
||||
typedef void (*WakeupHandler)(WakeupId wakeup_id, int32_t cookie);
|
||||
|
||||
//! Registers a WakeupHandler to be called when wakeup events occur.
|
||||
//! @param handler The callback that gets called when the wakeup event occurs
|
||||
void app_wakeup_service_subscribe(WakeupHandler handler);
|
||||
|
||||
//! Registers a wakeup event that triggers a callback at the specified time.
|
||||
//! Applications may only schedule up to 8 wakeup events.
|
||||
//! Wakeup events are given a 1 minute duration window, in that no application may schedule a
|
||||
//! wakeup event with 1 minute of a currently scheduled wakeup event.
|
||||
//! @param timestamp The requested time (UTC) for the wakeup event to occur
|
||||
//! @param cookie The application specific reason for the wakeup event
|
||||
//! @param notify_if_missed On powering on Pebble, will alert user when
|
||||
//! notifications were missed due to Pebble being off.
|
||||
//! @return negative values indicate errors (StatusCode)
|
||||
//! E_RANGE if the event cannot be scheduled due to another event in that period.
|
||||
//! E_INVALID_ARGUMENT if the time requested is in the past.
|
||||
//! E_OUT_OF_RESOURCES if the application has already scheduled all 8 wakeup events.
|
||||
//! E_INTERNAL if a system error occurred during scheduling.
|
||||
WakeupId app_wakeup_schedule(time_t timestamp, int32_t cookie, bool notify_if_missed);
|
||||
|
||||
//! Cancels a wakeup event.
|
||||
//! @param wakeup_id Wakeup event to cancel
|
||||
void app_wakeup_cancel(WakeupId wakeup_id);
|
||||
|
||||
//! Cancels all wakeup event for the app.
|
||||
void app_wakeup_cancel_all(void);
|
||||
|
||||
//! Retrieves the wakeup event info for an app that was launched
|
||||
//! by a wakeup_event (ie. \ref launch_reason() === APP_LAUNCH_WAKEUP)
|
||||
//! so that an app may display information regarding the wakeup event
|
||||
//! @param wakeup_id WakeupId for the wakeup event that caused the app to wakeup
|
||||
//! @param cookie App provided reason for the wakeup event
|
||||
//! @return True if app was launched due to a wakeup event, false otherwise
|
||||
bool app_wakeup_get_launch_event(WakeupId *wakeup_id, int32_t *cookie);
|
||||
|
||||
//! Checks if the current WakeupId is still scheduled and therefore valid
|
||||
//! @param wakeup_id Wakeup event to query for validity and scheduled time
|
||||
//! @param timestamp Optionally points to an address of a time_t variable to
|
||||
//! store the time that the wakeup event is scheduled to occur.
|
||||
//! (The time is in UTC, but local time when \ref clock_is_timezone_set
|
||||
//! returns false).
|
||||
//! You may pass NULL instead if you do not need it.
|
||||
//! @return True if WakeupId is still scheduled, false if it doesn't exist or has
|
||||
//! already occurred
|
||||
bool app_wakeup_query(WakeupId wakeup_id, time_t *timestamp);
|
||||
|
||||
//! @} // group Wakeup
|
||||
//! @} // group Foundation
|
||||
|
90
src/fw/applib/app_watch_info.c
Normal file
90
src/fw/applib/app_watch_info.c
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 "app_watch_info.h"
|
||||
|
||||
#include "syscall/syscall_internal.h"
|
||||
#include "system/version.h"
|
||||
#include "mfg/mfg_info.h"
|
||||
|
||||
#include "git_version.auto.h"
|
||||
|
||||
DEFINE_SYSCALL(WatchInfoColor, sys_watch_info_get_color, void) {
|
||||
return mfg_info_get_watch_color();
|
||||
}
|
||||
|
||||
WatchInfoModel watch_info_get_model() {
|
||||
// Pull the model for pebble time steel from the factory set model color bits.
|
||||
switch (sys_watch_info_get_color()) {
|
||||
// Pebble Original Colors
|
||||
case WATCH_INFO_COLOR_BLACK:
|
||||
case WATCH_INFO_COLOR_WHITE:
|
||||
case WATCH_INFO_COLOR_RED:
|
||||
case WATCH_INFO_COLOR_ORANGE:
|
||||
case WATCH_INFO_COLOR_GRAY:
|
||||
case WATCH_INFO_COLOR_BLUE:
|
||||
case WATCH_INFO_COLOR_GREEN:
|
||||
case WATCH_INFO_COLOR_PINK:
|
||||
return WATCH_INFO_MODEL_PEBBLE_ORIGINAL;
|
||||
// Pebble Steel Colors
|
||||
case WATCH_INFO_COLOR_STAINLESS_STEEL:
|
||||
case WATCH_INFO_COLOR_MATTE_BLACK:
|
||||
return WATCH_INFO_MODEL_PEBBLE_STEEL;
|
||||
// Pebble Time Colors
|
||||
case WATCH_INFO_COLOR_TIME_WHITE:
|
||||
case WATCH_INFO_COLOR_TIME_BLACK:
|
||||
case WATCH_INFO_COLOR_TIME_RED:
|
||||
return WATCH_INFO_MODEL_PEBBLE_TIME;
|
||||
// Pebble Time Steel Colors
|
||||
case WATCH_INFO_COLOR_TIME_STEEL_SILVER:
|
||||
case WATCH_INFO_COLOR_TIME_STEEL_BLACK:
|
||||
case WATCH_INFO_COLOR_TIME_STEEL_GOLD:
|
||||
return WATCH_INFO_MODEL_PEBBLE_TIME_STEEL;
|
||||
case WATCH_INFO_COLOR_TIME_ROUND_BLACK_14:
|
||||
case WATCH_INFO_COLOR_TIME_ROUND_SILVER_14:
|
||||
case WATCH_INFO_COLOR_TIME_ROUND_ROSE_GOLD_14:
|
||||
return WATCH_INFO_MODEL_PEBBLE_TIME_ROUND_14;
|
||||
case WATCH_INFO_COLOR_TIME_ROUND_BLACK_20:
|
||||
case WATCH_INFO_COLOR_TIME_ROUND_SILVER_20:
|
||||
return WATCH_INFO_MODEL_PEBBLE_TIME_ROUND_20;
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_HR_BLACK:
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_HR_LIME:
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_HR_FLAME:
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_HR_WHITE:
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_HR_AQUA:
|
||||
return WATCH_INFO_MODEL_PEBBLE_2_HR;
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_SE_BLACK:
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_SE_WHITE:
|
||||
return WATCH_INFO_MODEL_PEBBLE_2_SE;
|
||||
case WATCH_INFO_COLOR_PEBBLE_TIME_2_BLACK:
|
||||
case WATCH_INFO_COLOR_PEBBLE_TIME_2_SILVER:
|
||||
case WATCH_INFO_COLOR_PEBBLE_TIME_2_GOLD:
|
||||
return WATCH_INFO_MODEL_PEBBLE_TIME_2;
|
||||
case WATCH_INFO_COLOR_UNKNOWN:
|
||||
case WATCH_INFO_COLOR__MAX:
|
||||
return WATCH_INFO_MODEL_UNKNOWN;
|
||||
}
|
||||
// Should never be reached
|
||||
return WATCH_INFO_MODEL_UNKNOWN;
|
||||
}
|
||||
|
||||
WatchInfoVersion watch_info_get_firmware_version(void) {
|
||||
return (WatchInfoVersion) {
|
||||
.major = GIT_MAJOR_VERSION,
|
||||
.minor = GIT_MINOR_VERSION,
|
||||
.patch = GIT_PATCH_VERSION
|
||||
};
|
||||
}
|
116
src/fw/applib/app_watch_info.h
Normal file
116
src/fw/applib/app_watch_info.h
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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 <inttypes.h>
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup WatchInfo
|
||||
//! \brief Provides information about the watch itself.
|
||||
//!
|
||||
//! This API provides access to information such as the watch model, watch color
|
||||
//! and watch firmware version.
|
||||
//! @{
|
||||
|
||||
//! The different watch models.
|
||||
typedef enum {
|
||||
WATCH_INFO_MODEL_UNKNOWN, //!< Unknown model
|
||||
WATCH_INFO_MODEL_PEBBLE_ORIGINAL, //!< Original Pebble
|
||||
WATCH_INFO_MODEL_PEBBLE_STEEL, //!< Pebble Steel
|
||||
WATCH_INFO_MODEL_PEBBLE_TIME, //!< Pebble Time
|
||||
WATCH_INFO_MODEL_PEBBLE_TIME_STEEL, //!< Pebble Time Steel
|
||||
WATCH_INFO_MODEL_PEBBLE_TIME_ROUND_14, //!< Pebble Time Round, 14mm lug size
|
||||
WATCH_INFO_MODEL_PEBBLE_TIME_ROUND_20, //!< Pebble Time Round, 20mm lug size
|
||||
WATCH_INFO_MODEL_PEBBLE_2_HR, //!< Pebble 2 HR
|
||||
WATCH_INFO_MODEL_PEBBLE_2_SE, //!< Pebble 2 SE
|
||||
WATCH_INFO_MODEL_PEBBLE_TIME_2, //!< Pebble Time 2
|
||||
|
||||
WATCH_INFO_MODEL__MAX
|
||||
} WatchInfoModel;
|
||||
|
||||
//! The different watch colors.
|
||||
// This color enum is programmed by the factory into the factory registry. Therefore these
|
||||
// numbers must not change.
|
||||
typedef enum {
|
||||
WATCH_INFO_COLOR_UNKNOWN = 0, //!< Unknown color
|
||||
WATCH_INFO_COLOR_BLACK = 1, //!< Black
|
||||
WATCH_INFO_COLOR_WHITE = 2, //!< White
|
||||
WATCH_INFO_COLOR_RED = 3, //!< Red
|
||||
WATCH_INFO_COLOR_ORANGE = 4, //!< Orange
|
||||
WATCH_INFO_COLOR_GRAY = 5, //!< Gray
|
||||
|
||||
WATCH_INFO_COLOR_STAINLESS_STEEL = 6, //!< Stainless Steel
|
||||
WATCH_INFO_COLOR_MATTE_BLACK = 7, //!< Matte Black
|
||||
|
||||
WATCH_INFO_COLOR_BLUE = 8, //!< Blue
|
||||
WATCH_INFO_COLOR_GREEN = 9, //!< Green
|
||||
WATCH_INFO_COLOR_PINK = 10, //!< Pink
|
||||
|
||||
WATCH_INFO_COLOR_TIME_WHITE = 11, //!< Time White
|
||||
WATCH_INFO_COLOR_TIME_BLACK = 12, //!< Time Black
|
||||
WATCH_INFO_COLOR_TIME_RED = 13, //!< Time Red
|
||||
|
||||
WATCH_INFO_COLOR_TIME_STEEL_SILVER = 14, //!< Time Steel Silver
|
||||
WATCH_INFO_COLOR_TIME_STEEL_BLACK = 15, //!< Time Steel Black
|
||||
WATCH_INFO_COLOR_TIME_STEEL_GOLD = 16, //!< Time Steel Gold
|
||||
|
||||
WATCH_INFO_COLOR_TIME_ROUND_SILVER_14 = 17, //!< Time Round 14mm lug size, Silver
|
||||
WATCH_INFO_COLOR_TIME_ROUND_BLACK_14 = 18, //!< Time Round 14mm lug size, Black
|
||||
WATCH_INFO_COLOR_TIME_ROUND_SILVER_20 = 19, //!< Time Round 20mm lug size, Silver
|
||||
WATCH_INFO_COLOR_TIME_ROUND_BLACK_20 = 20, //!< Time Round 20mm lug size, Black
|
||||
WATCH_INFO_COLOR_TIME_ROUND_ROSE_GOLD_14 = 21, //!< Time Round 14mm lug size, Rose Gold
|
||||
|
||||
WATCH_INFO_COLOR_PEBBLE_2_HR_BLACK = 25, //!< Pebble 2 HR, Black / Charcoal
|
||||
WATCH_INFO_COLOR_PEBBLE_2_HR_LIME = 27, //!< Pebble 2 HR, Charcoal / Sorbet Green
|
||||
WATCH_INFO_COLOR_PEBBLE_2_HR_FLAME = 28, //!< Pebble 2 HR, Charcoal / Red
|
||||
WATCH_INFO_COLOR_PEBBLE_2_HR_WHITE = 29, //!< Pebble 2 HR, White / Gray
|
||||
WATCH_INFO_COLOR_PEBBLE_2_HR_AQUA = 30, //!< Pebble 2 HR, White / Turquoise
|
||||
|
||||
WATCH_INFO_COLOR_PEBBLE_2_SE_BLACK = 24, //!< Pebble 2 SE, Black / Charcoal
|
||||
WATCH_INFO_COLOR_PEBBLE_2_SE_WHITE = 26, //!< Pebble 2 SE, White / Gray
|
||||
|
||||
WATCH_INFO_COLOR_PEBBLE_TIME_2_BLACK = 31, //!< Pebble Time 2, Black
|
||||
WATCH_INFO_COLOR_PEBBLE_TIME_2_SILVER = 32, //!< Pebble Time 2, Silver
|
||||
WATCH_INFO_COLOR_PEBBLE_TIME_2_GOLD = 33, //!< Pebble Time 2, Gold
|
||||
|
||||
WATCH_INFO_COLOR__MAX
|
||||
} WatchInfoColor;
|
||||
|
||||
//! Data structure containing the version of the firmware running on the watch.
|
||||
//! The version of the firmware has the form X.[X.[X]]. If a version number is not present it will be 0.
|
||||
//! For example: the version numbers of 2.4.1 are 2, 4, and 1. The version numbers of 2.4 are 2, 4, and 0.
|
||||
typedef struct {
|
||||
uint8_t major; //!< Major version number
|
||||
uint8_t minor; //!< Minor version number
|
||||
uint8_t patch; //!< Patch version number
|
||||
} WatchInfoVersion;
|
||||
|
||||
//! Provides the model of the watch.
|
||||
//! @return {@link WatchInfoModel} representing the model of the watch.
|
||||
WatchInfoModel watch_info_get_model(void);
|
||||
|
||||
//! Provides the version of the firmware running on the watch.
|
||||
//! @return {@link WatchInfoVersion} representing the version of the firmware running on the watch.
|
||||
WatchInfoVersion watch_info_get_firmware_version(void);
|
||||
|
||||
//! @internal Get the watch color from mfg info
|
||||
WatchInfoColor sys_watch_info_get_color(void);
|
||||
|
||||
//! @} // end addtogroup WatchInfo
|
||||
//! @} // end addtogroup Foundation
|
||||
|
62
src/fw/applib/app_worker.c
Normal file
62
src/fw/applib/app_worker.c
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 "syscall/syscall.h"
|
||||
#include "app_worker.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// Determine if the worker for the current app is running
|
||||
bool app_worker_is_running(void) {
|
||||
return sys_app_worker_is_running();
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// Launch the worker for the current app
|
||||
AppWorkerResult app_worker_launch(void) {
|
||||
return sys_app_worker_launch();
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// Kill the worker for the current app
|
||||
AppWorkerResult app_worker_kill(void) {
|
||||
return sys_app_worker_kill();
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// Subscribe to the app_message service
|
||||
bool app_worker_message_subscribe(AppWorkerMessageHandler handler) {
|
||||
return plugin_service_subscribe(NULL, (PluginServiceHandler)handler);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// Unsubscribe from a specific plug-in service by uuid.
|
||||
bool app_worker_message_unsubscribe(void) {
|
||||
return plugin_service_unsubscribe(NULL);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// Send an event to all registered subscribers of the given plugin service identified by UUID.
|
||||
void app_worker_send_message(uint8_t type, AppWorkerMessage *data) {
|
||||
_Static_assert(sizeof(AppWorkerMessage) == sizeof(PluginEventData), "These must match!");
|
||||
plugin_service_send_event(NULL, type, (PluginEventData *)data);
|
||||
}
|
||||
|
97
src/fw/applib/app_worker.h
Normal file
97
src/fw/applib/app_worker.h
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include "util/uuid.h"
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup AppWorker
|
||||
//! \brief Runs in the background, and can communicate with the foreground app.
|
||||
//! @{
|
||||
|
||||
|
||||
//! Possible error codes from app_worker_launch, app_worker_kill
|
||||
typedef enum {
|
||||
//! Success
|
||||
APP_WORKER_RESULT_SUCCESS= 0,
|
||||
//! No worker found for the current app
|
||||
APP_WORKER_RESULT_NO_WORKER = 1,
|
||||
//! A worker for a different app is already running
|
||||
APP_WORKER_RESULT_DIFFERENT_APP = 2,
|
||||
//! The worker is not running
|
||||
APP_WORKER_RESULT_NOT_RUNNING = 3,
|
||||
//! The worker is already running
|
||||
APP_WORKER_RESULT_ALREADY_RUNNING = 4,
|
||||
//! The user will be asked for confirmation
|
||||
APP_WORKER_RESULT_ASKING_CONFIRMATION = 5,
|
||||
} AppWorkerResult;
|
||||
|
||||
|
||||
//! Generic structure of a worker message that can be sent between an app and its worker
|
||||
typedef struct {
|
||||
uint16_t data0;
|
||||
uint16_t data1;
|
||||
uint16_t data2;
|
||||
} AppWorkerMessage;
|
||||
|
||||
|
||||
//! Determine if the worker for the current app is running
|
||||
//! @return true if running
|
||||
bool app_worker_is_running(void);
|
||||
|
||||
//! Launch the worker for the current app. Note that this is an asynchronous operation, a result code
|
||||
//! of APP_WORKER_RESULT_SUCCESS merely means that the request was successfully queued up.
|
||||
//! @return result code
|
||||
AppWorkerResult app_worker_launch(void);
|
||||
|
||||
//! Kill the worker for the current app. Note that this is an asynchronous operation, a result code
|
||||
//! of APP_WORKER_RESULT_SUCCESS merely means that the request was successfully queued up.
|
||||
//! @return result code
|
||||
AppWorkerResult app_worker_kill(void);
|
||||
|
||||
|
||||
//! Callback type for worker messages. Messages can be sent from worker to app or vice versa.
|
||||
//! @param type An application defined message type
|
||||
//! @param data pointer to message data. The receiver must know the structure of the data provided by the sender.
|
||||
typedef void (*AppWorkerMessageHandler)(uint16_t type, AppWorkerMessage *data);
|
||||
|
||||
//! Subscribe to worker messages. Once subscribed, the handler gets called on every message emitted by the other task
|
||||
//! (either worker or app).
|
||||
//! @param handler A callback to be executed when the event is received
|
||||
//! @return true on success
|
||||
bool app_worker_message_subscribe(AppWorkerMessageHandler handler);
|
||||
|
||||
//! Unsubscribe from worker messages. Once unsubscribed, the previously registered handler will no longer be called.
|
||||
//! @return true on success
|
||||
bool app_worker_message_unsubscribe(void);
|
||||
|
||||
//! Send a message to the other task (either worker or app).
|
||||
//! @param type An application defined message type
|
||||
//! @param data the message data structure
|
||||
void app_worker_send_message(uint8_t type, AppWorkerMessage *data);
|
||||
|
||||
|
||||
//! @} // end addtogroup AppWorker
|
||||
//! @} // end addtogroup Foundation
|
||||
|
||||
//! @internal
|
||||
//! Register the app message service with the event service system
|
||||
void app_worker_message_init(void);
|
||||
|
||||
|
283
src/fw/applib/applib_malloc.json
Normal file
283
src/fw/applib/applib_malloc.json
Normal file
|
@ -0,0 +1,283 @@
|
|||
{
|
||||
"_comment": [
|
||||
"This file defines the different types we allocate on an application's heap. For ",
|
||||
"compatibility purposes we need to keep these sizes the same from release to release ",
|
||||
"in order to ensure that an app that ran on one version of the firmware works on another ",
|
||||
"version without running out of memory. We support allocating different sizes for ",
|
||||
"apps, 3.x apps, and system apps with different amounts of padding. ",
|
||||
"",
|
||||
"Types are defined below with the following parameters: ",
|
||||
" name: The name of the type as it appears in our codebase. This type definition must ",
|
||||
" be visibile in one of the headers in the headers list below. ",
|
||||
" size_2x: The size in bytes that should be used for legacy2 apps ",
|
||||
" size_3x: The size in bytes that should be used for 3.x apps ",
|
||||
" size_3x_padding: The amount of padding to add directly to this particular struct. ",
|
||||
" dependencies: Which other type is composed of. This will increase the final 3x ",
|
||||
" padding by the combined padding of all dependent structs. ",
|
||||
"",
|
||||
" Note that sizeof(type) == size_3x_padding + sum(dependencies.size_3x_padding) or else ",
|
||||
" an assert will trigger telling you that you've accidentally shifted the padding around."
|
||||
],
|
||||
"headers": [
|
||||
"applib/event_service_client.h",
|
||||
"applib/plugin_service_private.h",
|
||||
"applib/graphics/gbitmap_sequence.h",
|
||||
"applib/graphics/gtypes.h",
|
||||
"applib/health_service_private.h",
|
||||
"applib/voice/dictation_session_private.h",
|
||||
"applib/voice/transcription_dialog.h",
|
||||
"applib/voice/voice_window_private.h",
|
||||
"applib/ui/action_bar_layer.h",
|
||||
"applib/ui/action_menu_window_private.h",
|
||||
"applib/ui/animation.h",
|
||||
"applib/ui/animation_private.h",
|
||||
"applib/ui/bitmap_layer.h",
|
||||
"applib/ui/content_indicator_private.h",
|
||||
"applib/ui/inverter_layer.h",
|
||||
"applib/ui/layer.h",
|
||||
"applib/ui/menu_layer.h",
|
||||
"applib/ui/menu_layer_private.h",
|
||||
"applib/ui/number_window.h",
|
||||
"applib/ui/option_menu_window.h",
|
||||
"applib/ui/property_animation_private.h",
|
||||
"applib/ui/rotate_bitmap_layer.h",
|
||||
"applib/ui/scroll_layer.h",
|
||||
"applib/ui/selection_layer.h",
|
||||
"applib/ui/simple_menu_layer.h",
|
||||
"applib/ui/status_bar_layer.h",
|
||||
"applib/ui/text_layer.h",
|
||||
"applib/ui/window.h",
|
||||
"applib/ui/window_stack_private.h",
|
||||
"applib/legacy2/ui/animation_legacy2.h"
|
||||
],
|
||||
"types": [{
|
||||
"name": "ActionBarLayer",
|
||||
"size_3x_padding": 28,
|
||||
"size_3x": 172,
|
||||
"dependencies": ["Layer"],
|
||||
"_comment": "ActionBarLayerLegacy2 should be used for 2.x apps"
|
||||
}, {
|
||||
"name": "ActionMenuItem",
|
||||
"size_3x_padding": 8,
|
||||
"size_3x": 20,
|
||||
"_comment": "Only for 3.x apps"
|
||||
}, {
|
||||
"name": "ActionMenuLevel",
|
||||
"size_3x_padding": 12,
|
||||
"size_3x": 32,
|
||||
"_comment": "Only for 3.x apps"
|
||||
}, {
|
||||
"name": "AnimationContext",
|
||||
"size_3x_padding": 8,
|
||||
"size_3x": 16,
|
||||
"_comment": "Only for 3.x apps"
|
||||
}, {
|
||||
"name": "ActionMenuData",
|
||||
"size_3x_padding": 90,
|
||||
"size_3x": 930,
|
||||
"dependencies": ["Window", "MenuLayer", "Layer", "Layer"],
|
||||
"_comment": "Only for 3.x apps"
|
||||
}, {
|
||||
"name": "AnimationLegacy2",
|
||||
"size_2x": 40,
|
||||
"_comment": "Only for legacy2 apps"
|
||||
}, {
|
||||
"name": "AnimationPrivate",
|
||||
"size_2x": 60,
|
||||
"size_3x_padding": 16,
|
||||
"size_3x": 76
|
||||
}, {
|
||||
"name": "AnimationAuxState",
|
||||
"size_3x_padding": 12,
|
||||
"size_3x": 32,
|
||||
"_comment": "Only for 3.x apps"
|
||||
}, {
|
||||
"name": "BitmapLayer",
|
||||
"size_2x": 52,
|
||||
"size_3x_padding": 8,
|
||||
"size_3x": 76,
|
||||
"dependencies": ["Layer"]
|
||||
}, {
|
||||
"name": "ContentIndicator",
|
||||
"size_3x_padding": 64,
|
||||
"size_3x": 108,
|
||||
"_comment": "Only for 3.x apps"
|
||||
}, {
|
||||
"name": "FontInfo",
|
||||
"size_2x": 40,
|
||||
"size_3x_padding": 16,
|
||||
"size_3x": 56
|
||||
}, {
|
||||
"name": "GBitmapLegacy2",
|
||||
"size_2x": 16,
|
||||
"_comment": "Only for Legacy 2.x apps."
|
||||
}, {
|
||||
"name": "GBitmap",
|
||||
"size_3x_padding": 8,
|
||||
"size_3x": 32,
|
||||
"_comment": "Only for 3.x apps."
|
||||
}, {
|
||||
"name": "GBitmapSequence",
|
||||
"size_3x_padding": 16,
|
||||
"size_3x": 88,
|
||||
"_comment": "Only for 3.x apps"
|
||||
}, {
|
||||
"name": "InverterLayer",
|
||||
"size_2x": 44,
|
||||
"size_3x": 44,
|
||||
"_comment": [ "Only meant for 2.x apps, but there are 3.x apps out there that abuse this.",
|
||||
"See PBL-31276.",
|
||||
"Layer is not marked as a dependency (event though it is!) because Layer ",
|
||||
"has padding on it we don't want. We don't want this padding because we ",
|
||||
"didn't have any originally with 3.x, and now we can't retroactively go ",
|
||||
"back and add some without breaking our contract." ]
|
||||
}, {
|
||||
"name": "Layer",
|
||||
"size_2x": 44,
|
||||
"size_3x_padding": 16,
|
||||
"size_3x": 60
|
||||
}, {
|
||||
"name": "MenuLayer",
|
||||
"size_2x": 348,
|
||||
"size_3x_padding": 40,
|
||||
"size_3x": 452,
|
||||
"dependencies": ["ScrollLayer", "InverterLayer"],
|
||||
"_comment": "FIXME: MenuLayer has internal padding that needs removing"
|
||||
}, {
|
||||
"name": "NumberWindow",
|
||||
"size_2x": 352,
|
||||
"size_3x_padding": 188,
|
||||
"size_3x": 568,
|
||||
"dependencies": ["Window", "ActionBarLayer", "TextLayer", "TextLayer"]
|
||||
}, {
|
||||
"name": "PluginServiceEntry",
|
||||
"size_2x": 16,
|
||||
"size_3x_padding": 8,
|
||||
"size_3x": 24
|
||||
}, {
|
||||
"name": "PropertyAnimationPrivate",
|
||||
"size_3x_padding": 16,
|
||||
"size_3x": 144,
|
||||
"dependencies": ["AnimationPrivate"],
|
||||
"_comment": "Only for 3.x apps"
|
||||
}, {
|
||||
"name": "RotBitmapLayer",
|
||||
"size_2x": 68,
|
||||
"size_3x_padding": 12,
|
||||
"size_3x": 96,
|
||||
"dependencies": ["Layer"]
|
||||
}, {
|
||||
"name": "ScrollLayer",
|
||||
"size_2x": 204,
|
||||
"size_3x_padding": 16,
|
||||
"size_3x": 212,
|
||||
"dependencies": ["Layer", "Layer", "Layer"]
|
||||
}, {
|
||||
"name": "SimpleMenuLayer",
|
||||
"size_2x": 360,
|
||||
"size_3x_padding": 32,
|
||||
"size_3x": 496,
|
||||
"dependencies": ["MenuLayer"]
|
||||
}, {
|
||||
"name": "StatusBarLayer",
|
||||
"size_2x": 104,
|
||||
"size_3x_padding": 80,
|
||||
"size_3x": 200,
|
||||
"dependencies": ["Layer"],
|
||||
"_comment": "Generously padded because it's not feature complete yet"
|
||||
}, {
|
||||
"name": "TextLayer",
|
||||
"size_2x": 60,
|
||||
"size_3x_padding": 16,
|
||||
"size_3x": 92,
|
||||
"dependencies": ["Layer"]
|
||||
}, {
|
||||
"name": "TextLayout",
|
||||
"size_2x": 24,
|
||||
"_comment": "Only for legacy2 apps"
|
||||
}, {
|
||||
"name": "TextLayoutExtended",
|
||||
"size_3x_padding": 0,
|
||||
"size_3x": 40,
|
||||
"_comment": "Only for 3.x apps"
|
||||
}, {
|
||||
"name": "Window",
|
||||
"size_2x": 84,
|
||||
"size_3x_padding": 0,
|
||||
"size_3x": 100,
|
||||
"dependencies": ["Layer"]
|
||||
}, {
|
||||
"name": "WindowStackItem",
|
||||
"size_2x": 16,
|
||||
"size_3x_padding": 16,
|
||||
"size_3x": 32
|
||||
}, {
|
||||
"name": "MenuRenderIterator",
|
||||
"size_2x": 96,
|
||||
"size_3x_padding": 0,
|
||||
"size_3x": 112,
|
||||
"dependencies": ["Layer"]
|
||||
}, {
|
||||
"name": "EventServiceInfo",
|
||||
"size_3x_padding": 20,
|
||||
"size_3x": 37,
|
||||
"_comment": "Generously padded because it's not feature complete yet (only 3.x)"
|
||||
}, {
|
||||
"name": "KinoPlayer",
|
||||
"size_3x_padding": 20,
|
||||
"size_3x": 52,
|
||||
"_comment": "Generously padded because it's not feature complete yet (only 3.x)"
|
||||
}, {
|
||||
"name": "KinoLayer",
|
||||
"size_3x_padding": 40,
|
||||
"size_3x": 164,
|
||||
"dependencies": ["Layer", "KinoPlayer"],
|
||||
"_comment": "Generously padded because it's not feature complete yet (only 3.x)"
|
||||
}, {
|
||||
"name": "Dialog",
|
||||
"size_3x_padding": 40,
|
||||
"size_3x": 640,
|
||||
"dependencies": ["Window", "StatusBarLayer", "TextLayer", "KinoLayer"],
|
||||
"_comment": "Generously padded because it's not feature complete yet (only 3.x)"
|
||||
}, {
|
||||
"name": "ExpandableDialog",
|
||||
"size_3x_padding": 48,
|
||||
"size_3x": 1280,
|
||||
"dependencies": ["Dialog", "TextLayer", "ScrollLayer", "ActionBarLayer", "Layer"],
|
||||
"_comment": "Generously padded because it's not feature complete yet (only 3.x)"
|
||||
}, {
|
||||
"name": "TranscriptionDialog",
|
||||
"size_3x_padding": 40,
|
||||
"size_3x": 1352,
|
||||
"dependencies": ["ExpandableDialog"],
|
||||
"_comment": "Generously padded because it's not feature complete yet (only 3.x)"
|
||||
}, {
|
||||
"name": "VoiceWindow",
|
||||
"size_3x_padding": 40,
|
||||
"size_3x": 2368,
|
||||
"dependencies": ["TranscriptionDialog", "Layer", "Layer", "StatusBarLayer", "TextLayer",
|
||||
"KinoLayer", "Window", "EventServiceInfo"],
|
||||
"_comment": "Generously padded because it's not feature complete yet (only 3.x)"
|
||||
}, {
|
||||
"name": "DictationSession",
|
||||
"size_3x_padding": 1196,
|
||||
"size_3x": 1284,
|
||||
"dependencies": ["EventServiceInfo", "EventServiceInfo"],
|
||||
"_comment": ["Enough padding to enable RAM allocation of required resources up-front if ",
|
||||
"the resources it uses need to be moved out of MCU flash (only 3.x)"]
|
||||
}, {
|
||||
"name": "HealthServiceCache",
|
||||
"size_3x_padding": 740,
|
||||
"size_3x": 2048,
|
||||
"dependencies": [],
|
||||
"_comment": ["Cache to speed up frequent health service API calls (only 3.9+)"]
|
||||
}, {
|
||||
"name": "OptionMenu",
|
||||
"size_2x": 632,
|
||||
"size_3x_padding": 12,
|
||||
"size_3x": 876,
|
||||
"dependencies": ["Window", "StatusBarLayer", "MenuLayer", "GBitmap", "GBitmap"],
|
||||
"_comment": "Not exported yet (only 3.x)"
|
||||
}]
|
||||
}
|
||||
|
130
src/fw/applib/applib_resource.c
Normal file
130
src/fw/applib/applib_resource.c
Normal file
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* 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 "applib/applib_malloc.auto.h"
|
||||
#include "applib_resource_private.h"
|
||||
#include "board/board.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "resource/resource_storage_builtin.h"
|
||||
#include "resource/resource_storage_flash.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
ResHandle applib_resource_get_handle(uint32_t resource_id) {
|
||||
if (sys_resource_is_valid(sys_get_current_resource_num(), resource_id)) {
|
||||
return (ResHandle)resource_id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t applib_resource_size(ResHandle h) {
|
||||
return sys_resource_size(sys_get_current_resource_num(), (uint32_t)h);
|
||||
}
|
||||
|
||||
size_t applib_resource_load(ResHandle h, uint8_t *buffer, size_t max_length) {
|
||||
return sys_resource_load_range(sys_get_current_resource_num(),
|
||||
(uint32_t)h, 0, buffer, max_length);
|
||||
}
|
||||
|
||||
size_t applib_resource_load_byte_range(
|
||||
ResHandle h, uint32_t start_offset, uint8_t *buffer, size_t num_bytes) {
|
||||
return sys_resource_load_range(sys_get_current_resource_num(),
|
||||
(uint32_t)h, start_offset, buffer, num_bytes);
|
||||
}
|
||||
|
||||
void *applib_resource_mmap_or_load(ResAppNum app_num, uint32_t resource_id,
|
||||
size_t offset, size_t num_bytes, bool used_aligned) {
|
||||
if (num_bytes == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const uint8_t *mapped_data = (app_num == SYSTEM_APP) ?
|
||||
sys_resource_read_only_bytes(SYSTEM_APP, resource_id, NULL) : NULL;
|
||||
|
||||
uint8_t *result = NULL;
|
||||
|
||||
if (mapped_data) {
|
||||
applib_resource_track_mmapped(mapped_data);
|
||||
result = (uint8_t *)(mapped_data + offset);
|
||||
} else {
|
||||
// TODO: PBL-40010 clean this up
|
||||
// we are wasting 7 bytes here so that clients of this API have the chance to
|
||||
// align the data
|
||||
result = applib_malloc(num_bytes + (used_aligned ? 7 : 0));
|
||||
if (!result || sys_resource_load_range(app_num, resource_id, offset,
|
||||
result, num_bytes) != num_bytes) {
|
||||
applib_free(result);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void applib_resource_munmap_or_free(void *bytes) {
|
||||
if (!applib_resource_munmap(bytes)) {
|
||||
applib_free(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if CAPABILITY_HAS_MAPPABLE_FLASH
|
||||
bool applib_resource_track_mmapped(const void *bytes) {
|
||||
if (resource_storage_builtin_bytes_are_readonly(bytes)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (resource_storage_flash_bytes_are_readonly(bytes)) {
|
||||
sys_resource_mapped_use();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool applib_resource_is_mmapped(const void *bytes) {
|
||||
return resource_storage_builtin_bytes_are_readonly(bytes) ||
|
||||
resource_storage_flash_bytes_are_readonly(bytes);
|
||||
}
|
||||
|
||||
bool applib_resource_munmap(const void *bytes) {
|
||||
if (resource_storage_builtin_bytes_are_readonly(bytes)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (resource_storage_flash_bytes_are_readonly(bytes)) {
|
||||
sys_resource_mapped_release();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool applib_resource_track_mmapped(const void *bytes) {
|
||||
return resource_storage_builtin_bytes_are_readonly(bytes);
|
||||
}
|
||||
|
||||
bool applib_resource_is_mmapped(const void *bytes) {
|
||||
return resource_storage_builtin_bytes_are_readonly(bytes);
|
||||
}
|
||||
|
||||
bool applib_resource_munmap(const void *bytes) {
|
||||
return resource_storage_builtin_bytes_are_readonly(bytes);
|
||||
}
|
||||
#endif // CAPABILITY_HAS_MAPPABLE_FLASH
|
100
src/fw/applib/applib_resource.h
Normal file
100
src/fw/applib/applib_resource.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! @file applib_resource.h
|
||||
//! Wrapper functions for the resource syscalls. These functions give us a nice interface to export to 3rd party apps.
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup Resources
|
||||
//! \brief Managing application resources
|
||||
//!
|
||||
//! Resources are data files that are bundled with your application binary and can be
|
||||
//! loaded at runtime. You use resources to embed images or custom fonts in your app,
|
||||
//! but also to embed any data file. Resources are always read-only.
|
||||
//!
|
||||
//! Resources are stored on Pebble’s flash memory and only loaded in RAM when you load
|
||||
//! them. This means that you can have a large number of resources embedded inside your app,
|
||||
//! even though Pebble’s RAM memory is very limited.
|
||||
//!
|
||||
//! See \htmlinclude UsingResources.html for information on how to embed
|
||||
//! resources into your app's bundle.
|
||||
|
||||
//!
|
||||
//! @{
|
||||
|
||||
#define RESOURCE_ID_FONT_FALLBACK RESOURCE_ID_GOTHIC_14
|
||||
|
||||
//! Opaque reference to a resource.
|
||||
//! @see \ref resource_get_handle()
|
||||
typedef void * ResHandle;
|
||||
|
||||
//! Gets the resource handle for a file identifier.
|
||||
//! @param resource_id The resource ID
|
||||
//!
|
||||
//! The resource IDs are auto-generated by the Pebble build process, based
|
||||
//! on the `appinfo.json`. The "name" field of each resource is prefixed
|
||||
//! by `RESOURCE_ID_` and made visible to the application (through the
|
||||
//! `build/src/resource_ids.auto.h` header which is automatically included).
|
||||
//!
|
||||
//! For example, given the following fragment of `appinfo.json`:
|
||||
//! \code{.json}
|
||||
//! ...
|
||||
//! "resources" : {
|
||||
//! "media": [
|
||||
//! {
|
||||
//! "name": "MY_ICON",
|
||||
//! "file": "img/icon.png",
|
||||
//! "type": "png",
|
||||
//! },
|
||||
//! ...
|
||||
//! \endcode
|
||||
//! The generated file identifier for this resource is `RESOURCE_ID_MY_ICON`.
|
||||
//! To get a resource handle for that resource write:
|
||||
//! \code{.c}
|
||||
//! ResHandle rh = resource_get_handle(RESOURCE_ID_MY_ICON);
|
||||
//! \endcode
|
||||
ResHandle applib_resource_get_handle(uint32_t resource_id);
|
||||
|
||||
//! Gets the size of the resource given a resource handle.
|
||||
//! @param h The handle to the resource
|
||||
//! @return The size of the resource in bytes
|
||||
size_t applib_resource_size(ResHandle h);
|
||||
|
||||
//! Copies the bytes for the resource with a given handle from flash storage into a given buffer.
|
||||
//! @param h The handle to the resource
|
||||
//! @param buffer The buffer to load the resource data into
|
||||
//! @param max_length The maximum number of bytes to copy
|
||||
//! @return The number of bytes actually copied
|
||||
size_t applib_resource_load(ResHandle h, uint8_t *buffer, size_t max_length);
|
||||
|
||||
//! Copies a range of bytes from a resource with a given handle into a given buffer.
|
||||
//! @param h The handle to the resource
|
||||
//! @param start_offset The offset in bytes at which to start reading from the resource
|
||||
//! @param buffer The buffer to load the resource data into
|
||||
//! @param num_bytes The maximum number of bytes to copy
|
||||
//! @return The number of bytes actually copied
|
||||
size_t applib_resource_load_byte_range(
|
||||
ResHandle h, uint32_t start_offset, uint8_t *buffer, size_t num_bytes);
|
||||
|
||||
//! @} // end addtogroup Resources
|
||||
//! @} // end addtogroup Foundation
|
||||
|
51
src/fw/applib/applib_resource_private.h
Normal file
51
src/fw/applib/applib_resource_private.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 "applib_resource.h"
|
||||
#include "resource/resource.h"
|
||||
|
||||
//! Checks if a passed pointer refers to builtin or memory-mapped data and manages reference
|
||||
//! counters as needed.
|
||||
//! @note You might want to use \ref applib_resource_mmap_or_load() instead
|
||||
//! @return true, if the passed pointer expresses memory-mapped data ans was successfully tracked
|
||||
bool applib_resource_track_mmapped(const void *bytes);
|
||||
|
||||
//! True, if the passed pointer refers to builtin or memory-mapped data
|
||||
bool applib_resource_is_mmapped(const void *bytes);
|
||||
|
||||
//! Checks if a passed pointer refers to builtin or memory-mapped data and manages reference
|
||||
//! counters as needed.
|
||||
//! @note You might want to use \ref applib_resource_munmap_or_free() instead
|
||||
//! @return true, if the passed pointer expresses memory-mapped data ans was successfully
|
||||
//! untracked
|
||||
bool applib_resource_munmap(const void *bytes);
|
||||
|
||||
//! Manages the reference counters for memory-mapped resources.
|
||||
//! Should not be called by anyone but the process manager.
|
||||
//! @return true, if any remaining resources were untracked
|
||||
bool applib_resource_munmap_all();
|
||||
|
||||
//! Tries to load a resource as memory-mapped data. If this isn't supported on the system
|
||||
//! or for a given resource if will try to allocate data and load it into RAM instead.
|
||||
//! Have a look at \ref resource_load_byte_range_system for the discussion of arguments
|
||||
//! @param used_aligned True, if you want this function to allocate 7 extra bytes if it cannot mmap
|
||||
//! @return NULL, if the resource coudln't be memory-mapped or allocated
|
||||
void *applib_resource_mmap_or_load(ResAppNum app_num, uint32_t resource_id,
|
||||
size_t offset, size_t length, bool used_aligned);
|
||||
|
||||
//! Updates reference counters if bytes is memory-mapped,
|
||||
//! alternatively it deallocates the data bytes points to.
|
||||
void applib_resource_munmap_or_free(void *bytes);
|
78
src/fw/applib/battery_state_service.c
Normal file
78
src/fw/applib/battery_state_service.c
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 "battery_state_service.h"
|
||||
|
||||
#include "event_service_client.h"
|
||||
#include "kernel/events.h"
|
||||
#include "services/common/event_service.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "process_state/worker_state/worker_state.h"
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
static BatteryStateServiceState* prv_get_state(PebbleTask task) {
|
||||
if (task == PebbleTask_Unknown) {
|
||||
task = pebble_task_get_current();
|
||||
}
|
||||
|
||||
if (task == PebbleTask_App) {
|
||||
return app_state_get_battery_state_service_state();
|
||||
} else if (task == PebbleTask_Worker) {
|
||||
return worker_state_get_battery_state_service_state();
|
||||
} else {
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void do_handle(PebbleEvent *e, void *context) {
|
||||
BatteryStateServiceState *state = prv_get_state(PebbleTask_Unknown);
|
||||
PBL_ASSERTN(state->handler != NULL);
|
||||
state->handler(sys_battery_get_charge_state());
|
||||
}
|
||||
|
||||
void battery_state_service_init(void) {
|
||||
event_service_init(PEBBLE_BATTERY_STATE_CHANGE_EVENT, NULL, NULL);
|
||||
}
|
||||
|
||||
void battery_state_service_subscribe(BatteryStateHandler handler) {
|
||||
BatteryStateServiceState *state = prv_get_state(PebbleTask_Unknown);
|
||||
state->handler = handler;
|
||||
event_service_client_subscribe(&state->bss_info);
|
||||
}
|
||||
|
||||
BatteryChargeState battery_state_service_peek(void) {
|
||||
return (sys_battery_get_charge_state());
|
||||
}
|
||||
|
||||
void battery_state_service_unsubscribe(void) {
|
||||
BatteryStateServiceState *state = prv_get_state(PebbleTask_Unknown);
|
||||
event_service_client_unsubscribe(&state->bss_info);
|
||||
state->handler = NULL;
|
||||
}
|
||||
|
||||
void battery_state_service_state_init(BatteryStateServiceState *state) {
|
||||
*state = (BatteryStateServiceState) {
|
||||
.bss_info = {
|
||||
.type = PEBBLE_BATTERY_STATE_CHANGE_EVENT,
|
||||
.handler = &do_handle,
|
||||
},
|
||||
};
|
||||
}
|
60
src/fw/applib/battery_state_service.h
Normal file
60
src/fw/applib/battery_state_service.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 "services/common/battery/battery_monitor.h"
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup EventService
|
||||
//! @{
|
||||
//! @addtogroup BatteryStateService
|
||||
//!
|
||||
//! \brief Determines when the battery state changes
|
||||
//!
|
||||
//! The BatteryStateService API lets you know when the battery state changes, that is,
|
||||
//! its current charge level, whether it is plugged and charging. It uses the
|
||||
//! BatteryChargeState structure to describe the current power state of Pebble.
|
||||
//!
|
||||
//! Refer to the <a href="https://github.com/pebble-examples/classio-battery-connection">
|
||||
//! classio-battery-connection</a> example, which demonstrates using the battery state service
|
||||
//! in a watchface.
|
||||
//! @{
|
||||
|
||||
//! Callback type for battery state change events
|
||||
//! @param charge the state of the battery \ref BatteryChargeState
|
||||
typedef void (*BatteryStateHandler)(BatteryChargeState charge);
|
||||
|
||||
|
||||
//! Subscribe to the battery state event service. Once subscribed, the handler gets called
|
||||
//! on every battery state change
|
||||
//! @param handler A callback to be executed on battery state change event
|
||||
void battery_state_service_subscribe(BatteryStateHandler handler);
|
||||
|
||||
//! Unsubscribe from the battery state event service. Once unsubscribed, the previously registered
|
||||
//! handler will no longer be called.
|
||||
void battery_state_service_unsubscribe(void);
|
||||
|
||||
//! Peek at the last known battery state.
|
||||
//! @return a \ref BatteryChargeState containing the last known data
|
||||
BatteryChargeState battery_state_service_peek(void);
|
||||
|
||||
//! @} // end addtogroup PEBBLE_BATTERY_STATE_CHANGE_EVENT
|
||||
//! @} // end addtogroup EventService
|
||||
//! @} // end addtogroup Foundation
|
||||
|
||||
|
28
src/fw/applib/battery_state_service_private.h
Normal file
28
src/fw/applib/battery_state_service_private.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 "event_service_client.h"
|
||||
#include "battery_state_service.h"
|
||||
|
||||
typedef struct __attribute__((packed)) BatteryStateServiceState {
|
||||
BatteryStateHandler handler;
|
||||
|
||||
EventServiceInfo bss_info;
|
||||
} BatteryStateServiceState;
|
||||
|
||||
void battery_state_service_state_init(BatteryStateServiceState *state);
|
761
src/fw/applib/bluetooth/ble_ad_parse.c
Normal file
761
src/fw/applib/bluetooth/ble_ad_parse.c
Normal file
|
@ -0,0 +1,761 @@
|
|||
/*
|
||||
* 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 "ble_ad_parse.h"
|
||||
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "util/math.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#include <btutil/bt_uuid.h>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal parsed advertisement data structures.
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! AD TYPE Values as specified by the Bluetooth 4.0 Spec.
|
||||
//! See "Appendix C (Normative): EIR and AD Formats" in Core_v4.0.pdf
|
||||
typedef enum {
|
||||
BLEAdTypeFlags = 0x01,
|
||||
BLEAdTypeService16BitUUIDPartial = 0x02,
|
||||
BLEAdTypeService16BitUUIDComplete = 0x03,
|
||||
BLEAdTypeService32BitUUIDPartial = 0x04,
|
||||
BLEAdTypeService32BitUUIDComplete = 0x05,
|
||||
BLEAdTypeService128BitUUIDPartial = 0x06,
|
||||
BLEAdTypeService128BitUUIDComplete = 0x07,
|
||||
|
||||
BLEAdTypeLocalNameShortened = 0x08,
|
||||
BLEAdTypeLocalNameComplete = 0x09,
|
||||
|
||||
BLEAdTypeTxPowerLevel = 0x0a,
|
||||
|
||||
BLEAdTypeManufacturerSpecific = 0xff,
|
||||
} BLEAdType;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! AD DATA element header
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
uint8_t length;
|
||||
BLEAdType type:8;
|
||||
} BLEAdElementHeader;
|
||||
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
BLEAdElementHeader header;
|
||||
uint8_t data[];
|
||||
} BLEAdElement;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Consuming BLEAdData:
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal parser callback. Gets called for a parsed Service UUIDs element.
|
||||
//! @param uuids The array with Service UUIDs
|
||||
//! @param count The number of Service UUIDs the array contains
|
||||
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
|
||||
//! @return true to continue parsing or false to stop after returning.
|
||||
typedef bool (*BLEAdParseServicesCallback)(const Uuid uuids[], uint8_t count,
|
||||
void *cb_data);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal parser callback. Gets called for a parsed Local Name element.
|
||||
//! @param local_name_bytes This is a *NON* zero terminated UTF-8 string.
|
||||
//! @param length The length of local_name_bytes
|
||||
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
|
||||
//! @return true to continue parsing or false to stop after returning.
|
||||
typedef bool (*BLEAdParseLocalNameCallback)(const uint8_t *local_name_bytes,
|
||||
uint8_t length, void *cb_data);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal parser callback. Gets called for a parsed TX Power Level element.
|
||||
//! @param tx_power_level The TX Power Level value.
|
||||
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
|
||||
//! @return true to continue parsing or false to stop after returning.
|
||||
typedef bool (*BLEAdParseTXPowerLevelCallback)(int8_t tx_power_level,
|
||||
void *cb_data);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal parser callback. Gets called for a Manufacturer Specific data elem.
|
||||
//! @param company_id The Company ID
|
||||
//! @param data The Manufacturer Specific data
|
||||
//! @param length The length in bytes of data
|
||||
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
|
||||
//! @return true to continue parsing or false to stop after returning.
|
||||
typedef bool (*BLEAdParseManufacturerSpecificCallback)(uint16_t company_id,
|
||||
const uint8_t *data,
|
||||
uint8_t length,
|
||||
void *cb_data);
|
||||
|
||||
typedef struct {
|
||||
BLEAdParseServicesCallback services_cb;
|
||||
BLEAdParseLocalNameCallback local_name_cb;
|
||||
BLEAdParseTXPowerLevelCallback tx_power_level_cb;
|
||||
BLEAdParseManufacturerSpecificCallback manufacturer_cb;
|
||||
} BLEAdParseCallbacks;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Parser for Services List data elements.
|
||||
static bool parse_services_list(const BLEAdElement *elem,
|
||||
const BLEAdParseCallbacks *callbacks,
|
||||
void *cb_data) {
|
||||
const uint8_t uuid_type = elem->header.type / 2;
|
||||
// Most common is probably 128-bit UUID, then 16-bit, then 32-bit:
|
||||
const size_t uuid_width = (uuid_type == 3) ? 16 : ((uuid_type == 1) ? 2 : 4);
|
||||
const size_t num_uuids = (elem->header.length - 1 /* Type byte */)
|
||||
/ uuid_width;
|
||||
|
||||
if (!num_uuids) {
|
||||
return true; // continue parsing
|
||||
}
|
||||
|
||||
Uuid uuids[num_uuids];
|
||||
|
||||
// Iterate through the list, expanding each UUID to 128-bit equivalents,
|
||||
// then copying them into the uuids[] array:
|
||||
const uint8_t *uuid_data = elem->data;
|
||||
for (size_t i = 0; i < num_uuids; ++i) {
|
||||
switch (uuid_type) {
|
||||
case 1: { // 16 bit
|
||||
uint16_t u16 = *(uint16_t *) uuid_data;
|
||||
uuids[i] = bt_uuid_expand_16bit(u16);
|
||||
uuid_data += sizeof(uint16_t);
|
||||
break;
|
||||
}
|
||||
case 2: { // 32 bit
|
||||
uint32_t u32 = *(uint32_t *) uuid_data;
|
||||
uuids[i] = bt_uuid_expand_32bit(u32);
|
||||
uuid_data += sizeof(uint32_t);
|
||||
break;
|
||||
}
|
||||
case 3: // 128-bit
|
||||
uuids[i] = UuidMakeFromLEBytes(uuid_data);
|
||||
uuid_data += sizeof(Uuid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Call back to client with parsed data:
|
||||
return callbacks->services_cb(uuids, num_uuids, cb_data);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Parser for Local Name data element.
|
||||
static bool parse_local_name(const BLEAdElement *elem,
|
||||
const BLEAdParseCallbacks *callbacks,
|
||||
void *cb_data) {
|
||||
// Length of the raw string:
|
||||
const uint8_t raw_length = elem->header.length - 1 /* -1 Type byte */;
|
||||
if (!raw_length) {
|
||||
return true; // continue parsing
|
||||
}
|
||||
|
||||
// Call back to client with parsed data:
|
||||
return callbacks->local_name_cb(elem->data, raw_length, cb_data);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Parser for TX Power Level data element.
|
||||
static bool parse_power_level(const BLEAdElement *elem,
|
||||
const BLEAdParseCallbacks *callbacks,
|
||||
void *cb_data) {
|
||||
if (elem->header.length != 2) {
|
||||
// In case the length is not what it should be, do not add data element.
|
||||
return true; // continue parsing
|
||||
}
|
||||
|
||||
const int8_t tx_power_level = *(int8_t *)elem->data;
|
||||
|
||||
// Call back to client with parsed data:
|
||||
return callbacks->tx_power_level_cb(tx_power_level, cb_data);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Parser for Manufacturer Specific data element.
|
||||
|
||||
static bool parse_manufact_spec(const BLEAdElement *elem,
|
||||
const BLEAdParseCallbacks *callbacks,
|
||||
void *cb_data) {
|
||||
|
||||
if (elem->header.length < 3) {
|
||||
// The first 2 octets should be the Company Identifier Code
|
||||
// (+1 for Type byte)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (callbacks->manufacturer_cb) {
|
||||
// Little-endian:
|
||||
const uint16_t *company_id = (uint16_t *) elem->data;
|
||||
|
||||
// Call back to client with parsed data:
|
||||
const uint8_t manufacturer_data_size =
|
||||
elem->header.length - sizeof(*company_id) - 1 /* -1 Type Byte */;
|
||||
return callbacks->manufacturer_cb(ltohs(*company_id),
|
||||
elem->data + sizeof(*company_id),
|
||||
manufacturer_data_size,
|
||||
cb_data);
|
||||
}
|
||||
|
||||
return true; // continue parsing
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! @param ad_data The advertising data and scan response data to parse.
|
||||
//! @param callbacks Callbacks for each of the types of data the client wants
|
||||
//! to receive parse callbacks for. You can leave a callback NULL if you are not
|
||||
//! interested in receiving callbacks for that type of data.
|
||||
//! @param cb_data Pointer to client data that is passed into the callback.
|
||||
static void ble_ad_parse_ad_data(const BLEAdData *ad_data,
|
||||
const BLEAdParseCallbacks *callbacks,
|
||||
void *cb_data) {
|
||||
const uint8_t *cursor = ad_data->data;
|
||||
const uint8_t *end = cursor + ad_data->ad_data_length +
|
||||
ad_data->scan_resp_data_length;
|
||||
while (cursor < end) {
|
||||
|
||||
const BLEAdElement *elem = (const BLEAdElement *) cursor;
|
||||
if (elem->header.length == 0) {
|
||||
// We've hit a padding zero. We should be done, or this packet is corrupt.
|
||||
return;
|
||||
}
|
||||
|
||||
if (cursor + elem->header.length + 1 /* +1 length byte */ > end) {
|
||||
return; // corrupted
|
||||
}
|
||||
|
||||
bool (*parse_func)(const BLEAdElement *,
|
||||
const BLEAdParseCallbacks *, void *) = NULL;
|
||||
|
||||
switch (elem->header.type) {
|
||||
case BLEAdTypeService16BitUUIDPartial ... BLEAdTypeService128BitUUIDComplete:
|
||||
if (callbacks->services_cb) {
|
||||
parse_func = parse_services_list;
|
||||
}
|
||||
break;
|
||||
|
||||
case BLEAdTypeLocalNameShortened ... BLEAdTypeLocalNameComplete:
|
||||
if (callbacks->local_name_cb) {
|
||||
parse_func = parse_local_name;
|
||||
}
|
||||
break;
|
||||
|
||||
case BLEAdTypeTxPowerLevel:
|
||||
if (callbacks->tx_power_level_cb) {
|
||||
parse_func = parse_power_level;
|
||||
}
|
||||
break;
|
||||
|
||||
case BLEAdTypeManufacturerSpecific:
|
||||
if (callbacks->manufacturer_cb) {
|
||||
parse_func = parse_manufact_spec;
|
||||
}
|
||||
break;
|
||||
|
||||
default: // parse_func == NULL
|
||||
break;
|
||||
|
||||
} // switch()
|
||||
|
||||
if (parse_func) {
|
||||
if (!parse_func(elem, callbacks, cb_data)) {
|
||||
// The callback indicated we should not continue parsing
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The Length byte itself is not counted, so +1:
|
||||
cursor += elem->header.length + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_includes_service() wrapper and helper function:
|
||||
|
||||
struct IncludesServiceCtx {
|
||||
const Uuid *service_uuid;
|
||||
bool included;
|
||||
};
|
||||
|
||||
static bool includes_service_parse_cb(const Uuid uuids[], uint8_t count,
|
||||
void *cb_data) {
|
||||
struct IncludesServiceCtx *ctx = (struct IncludesServiceCtx *) cb_data;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (uuid_equal(ctx->service_uuid, &uuids[i])) {
|
||||
// Found!
|
||||
ctx->included = true;
|
||||
return false; // stop parsing
|
||||
}
|
||||
}
|
||||
return true; // continue parsing
|
||||
}
|
||||
|
||||
bool ble_ad_includes_service(const BLEAdData *ad, const Uuid *service_uuid) {
|
||||
struct IncludesServiceCtx ctx = {
|
||||
.service_uuid = service_uuid,
|
||||
.included = false,
|
||||
};
|
||||
const BLEAdParseCallbacks callbacks = (const BLEAdParseCallbacks) {
|
||||
.services_cb = includes_service_parse_cb,
|
||||
};
|
||||
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
|
||||
return ctx.included;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_copy_service_uuids() wrapper and helper function:
|
||||
|
||||
struct CopyServiceUUIDsCtx {
|
||||
Uuid *uuids_out;
|
||||
const uint8_t max;
|
||||
uint8_t copied;
|
||||
uint8_t total;
|
||||
};
|
||||
|
||||
static bool copy_services_parse_cb(const Uuid uuids[], uint8_t count,
|
||||
void *cb_data) {
|
||||
struct CopyServiceUUIDsCtx *ctx = (struct CopyServiceUUIDsCtx *)cb_data;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (ctx->copied < ctx->max) {
|
||||
// Still space left, so copy:
|
||||
const Uuid *uuid = &uuids[i];
|
||||
memcpy(&ctx->uuids_out[ctx->copied++], uuid, sizeof(Uuid));
|
||||
}
|
||||
++ctx->total;
|
||||
}
|
||||
return false; // stop parsing, only one Services UUID element allowed by spec
|
||||
}
|
||||
|
||||
uint8_t ble_ad_copy_service_uuids(const BLEAdData *ad,
|
||||
Uuid *uuids_out,
|
||||
uint8_t num_uuids) {
|
||||
struct CopyServiceUUIDsCtx ctx = {
|
||||
.uuids_out = uuids_out,
|
||||
.max = num_uuids,
|
||||
};
|
||||
const BLEAdParseCallbacks callbacks = {
|
||||
.services_cb = copy_services_parse_cb,
|
||||
};
|
||||
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
|
||||
return ctx.total;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_get_tx_power_level() wrapper and helper function:
|
||||
|
||||
struct TxPowerLevelCtx {
|
||||
int8_t *tx_power_level_out;
|
||||
bool included;
|
||||
};
|
||||
|
||||
static bool tx_power_level_cb(int8_t tx_power_level,
|
||||
void *cb_data) {
|
||||
struct TxPowerLevelCtx *ctx = (struct TxPowerLevelCtx *)cb_data;
|
||||
*ctx->tx_power_level_out = tx_power_level;
|
||||
ctx->included = true;
|
||||
return false; // stop parsing
|
||||
}
|
||||
|
||||
bool ble_ad_get_tx_power_level(const BLEAdData *ad,
|
||||
int8_t *tx_power_level_out) {
|
||||
struct TxPowerLevelCtx ctx = {
|
||||
.tx_power_level_out = tx_power_level_out,
|
||||
.included = false,
|
||||
};
|
||||
const BLEAdParseCallbacks callbacks = {
|
||||
.tx_power_level_cb = tx_power_level_cb,
|
||||
};
|
||||
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
|
||||
return ctx.included;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_copy_local_name() wrapper and helper function:
|
||||
|
||||
struct LocalNameCtx {
|
||||
char *buffer;
|
||||
const uint8_t size;
|
||||
size_t copied_size;
|
||||
};
|
||||
|
||||
static bool copy_local_name_parse_cb(const uint8_t *local_name_bytes,
|
||||
uint8_t length, void *cb_data) {
|
||||
struct LocalNameCtx *ctx = (struct LocalNameCtx *)cb_data;
|
||||
const uint8_t copied_size = MIN(ctx->size, length + 1 /* zero terminator */);
|
||||
|
||||
memcpy(ctx->buffer, local_name_bytes, copied_size - 1);
|
||||
ctx->buffer[copied_size - 1] = 0; // zero terminator
|
||||
ctx->copied_size = copied_size;
|
||||
|
||||
return false; // stop parsing
|
||||
}
|
||||
|
||||
size_t ble_ad_copy_local_name(const BLEAdData *ad, char *buffer, size_t size) {
|
||||
struct LocalNameCtx ctx = {
|
||||
.buffer = buffer,
|
||||
.size = MIN(size, 0xff),
|
||||
.copied_size = 0,
|
||||
};
|
||||
const BLEAdParseCallbacks callbacks = {
|
||||
.local_name_cb = copy_local_name_parse_cb,
|
||||
};
|
||||
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
|
||||
return ctx.copied_size;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_get_raw_data_size() wrapper and helper function:
|
||||
|
||||
size_t ble_ad_get_raw_data_size(const BLEAdData *ad) {
|
||||
return ad->ad_data_length + ad->scan_resp_data_length;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_copy_raw_data() wrapper and helper function:
|
||||
|
||||
size_t ble_ad_copy_raw_data(const BLEAdData *ad, uint8_t *buffer, size_t size) {
|
||||
const size_t size_to_copy = ble_ad_get_raw_data_size(ad);
|
||||
if (size < size_to_copy) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(buffer, ad->data, size_to_copy);
|
||||
return size_to_copy;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_get_manufacturer_specific_data() wrapper and helper function:
|
||||
|
||||
struct ManufacturerSpecificCtx {
|
||||
uint16_t company_id;
|
||||
uint8_t *buffer;
|
||||
const uint8_t size;
|
||||
size_t copied_size;
|
||||
};
|
||||
|
||||
static bool copy_manufacturer_specific_parse_cb(uint16_t company_id,
|
||||
const uint8_t *data,
|
||||
uint8_t length,
|
||||
void *cb_data) {
|
||||
|
||||
struct ManufacturerSpecificCtx *ctx =
|
||||
(struct ManufacturerSpecificCtx *) cb_data;
|
||||
const uint8_t copied_size = MIN(ctx->size, length);
|
||||
|
||||
memcpy(ctx->buffer, data, copied_size);
|
||||
ctx->copied_size = copied_size;
|
||||
ctx->company_id = company_id;
|
||||
|
||||
return false; // stop parsing
|
||||
}
|
||||
|
||||
size_t ble_ad_copy_manufacturer_specific_data(const BLEAdData *ad,
|
||||
uint16_t *company_id,
|
||||
uint8_t *buffer, size_t size) {
|
||||
struct ManufacturerSpecificCtx ctx = {
|
||||
.size = size,
|
||||
.buffer = buffer,
|
||||
};
|
||||
const BLEAdParseCallbacks callbacks = {
|
||||
.manufacturer_cb = copy_manufacturer_specific_parse_cb,
|
||||
};
|
||||
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
|
||||
if (company_id) {
|
||||
*company_id = ctx.company_id;
|
||||
}
|
||||
return ctx.copied_size;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Creating BLEAdData:
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
//! Magic high bit used as scan_resp_data_length to indicate that the ad_data
|
||||
//! has been finalized and the next write should be counted towards the scan
|
||||
//! response payload. The maximum scan_resp_data_length is 31 bytes, so this
|
||||
//! value lies outside of the valid range. This is basically a memory savings
|
||||
//! optimization, saving another "finalized" bool.
|
||||
#define BLE_AD_DATA_FINALIZED ((uint8_t) 0x80)
|
||||
|
||||
bool prv_ad_is_finalized(const BLEAdData *ad_data) {
|
||||
// Scan response data has already been added / started
|
||||
return (ad_data->scan_resp_data_length != 0);
|
||||
}
|
||||
|
||||
void ble_ad_start_scan_response(BLEAdData *ad_data) {
|
||||
if (prv_ad_is_finalized(ad_data)) {
|
||||
// Already finalized
|
||||
return;
|
||||
}
|
||||
ad_data->scan_resp_data_length = BLE_AD_DATA_FINALIZED;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Helper to calculate whether a number of bytes will still fit when appended.
|
||||
//! @param length Pointer to the length of the part for which to try to fit in
|
||||
//! size_to_write number of bytes.
|
||||
//! @param size_to_write The number of bytes to that need to be appended.
|
||||
//! @return Pointer to length if it could still appends size_to_write bytes or
|
||||
//! NULL if not.
|
||||
static uint8_t *prv_length_ptr_if_fits_or_null(uint8_t *length,
|
||||
size_t size_to_write) {
|
||||
// Unset finalized bit:
|
||||
const uint8_t used = *length & (~BLE_AD_DATA_FINALIZED);
|
||||
const uint8_t left = GAP_LE_AD_REPORT_DATA_MAX_LENGTH - used;
|
||||
// Return pointer to the pointer if size_to_write will fit, or NULL otherwise:
|
||||
return (left >= size_to_write) ? length : NULL;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! @return Pointer to the length that is incremented when writing size_to_write
|
||||
//! number of bytes, or NULL if there is not enough space left.
|
||||
static uint8_t* prv_length_to_increase(BLEAdData *ad_data,
|
||||
size_t size_to_write) {
|
||||
if (ad_data->scan_resp_data_length) {
|
||||
// The scan response part is already being populated:
|
||||
return prv_length_ptr_if_fits_or_null(&ad_data->scan_resp_data_length,
|
||||
size_to_write);
|
||||
} else {
|
||||
// The advertisement is still being populated:
|
||||
uint8_t *length = prv_length_ptr_if_fits_or_null(&ad_data->ad_data_length,
|
||||
size_to_write);
|
||||
if (length) {
|
||||
// Hurray, the size_to_write fits in the advertisement part:
|
||||
return length;
|
||||
}
|
||||
// Last resort, try fitting into scan response part:
|
||||
return prv_length_ptr_if_fits_or_null(&ad_data->scan_resp_data_length,
|
||||
size_to_write);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool prv_write_element_to_ad_data(BLEAdData *ad_data,
|
||||
const BLEAdElement *element) {
|
||||
if (!ad_data || !element) {
|
||||
return false;
|
||||
}
|
||||
const size_t size_to_write = element->header.length + 1 /* Length Byte */;
|
||||
uint8_t* length = prv_length_to_increase(ad_data, size_to_write);
|
||||
if (!length) {
|
||||
// Not enough space...
|
||||
return false;
|
||||
}
|
||||
|
||||
// Undo the magic number trick:
|
||||
if (*length == BLE_AD_DATA_FINALIZED) {
|
||||
*length = 0;
|
||||
}
|
||||
|
||||
// Append the element to the end:
|
||||
uint8_t * const end = ad_data->data +
|
||||
ad_data->ad_data_length +
|
||||
ad_data->scan_resp_data_length;
|
||||
memcpy(end, (const uint8_t *) element, size_to_write);
|
||||
|
||||
// Length book-keeping:
|
||||
*length += size_to_write;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
BLEAdData * ble_ad_create(void) {
|
||||
const size_t max_ad_data_size = sizeof(BLEAdData) +
|
||||
(GAP_LE_AD_REPORT_DATA_MAX_LENGTH * 2);
|
||||
BLEAdData *ad_data = applib_malloc(max_ad_data_size);
|
||||
if (ad_data) {
|
||||
memset(ad_data, 0, sizeof(BLEAdData));
|
||||
}
|
||||
return ad_data;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
void ble_ad_destroy(BLEAdData *ad) {
|
||||
applib_free(ad);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! The smallest UUID width, by reducing the width when a UUID is based on the
|
||||
//! Bluetooth base UUID. @see bt_uuid_expand_16bit @see bt_uuid_expand_32bit
|
||||
static uint8_t prv_smallest_bt_uuid_width_in_bytes(const Uuid *uuid) {
|
||||
const Uuid bt_uuid_base = bt_uuid_expand_16bit(0);
|
||||
|
||||
// The bytes after the first 4 contain the Bluetooth base.
|
||||
// Check if the uuid is based off of the Bluetooth base UUID:
|
||||
const bool is_bt_uuid_based = (memcmp(&bt_uuid_base.byte4, &uuid->byte4,
|
||||
sizeof(Uuid) - offsetof(Uuid, byte4)) == 0);
|
||||
if (!is_bt_uuid_based) {
|
||||
// Not based on the Bluetooth base UUID, so use 128-bits:
|
||||
return sizeof(Uuid);
|
||||
}
|
||||
if (uuid->byte0 || uuid->byte1) {
|
||||
// If byte0 and byte1 not zero: 32-bit UUID, Bluetooth base UUID based:
|
||||
return sizeof(uint32_t);
|
||||
}
|
||||
// If byte0 and byte1 are zero: 16-bit UUID, Bluetooth base UUID based:
|
||||
return sizeof(uint16_t);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Finds the largest common UUID width. For UUIDs that are based on the
|
||||
//! Bluetooth base UUID, a reduced width will be taken of either 16-bits or
|
||||
//! 32-bits.
|
||||
static uint8_t prv_largest_common_bt_uuid_width(const Uuid uuids[],
|
||||
uint8_t num_uuids) {
|
||||
uint8_t max_width_bytes = sizeof(uint16_t);
|
||||
for (unsigned int i = 0; i < num_uuids; ++i) {
|
||||
const Uuid *uuid = &uuids[i];
|
||||
const uint8_t width_bytes = prv_smallest_bt_uuid_width_in_bytes(uuid);
|
||||
max_width_bytes = MAX(width_bytes, max_width_bytes);
|
||||
}
|
||||
return max_width_bytes;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Helper to reduces a 128-bit UUID to 16-bits. Note: this function does not
|
||||
//! check whether the original UUID is based on the Bluetooth base.
|
||||
static uint16_t prv_convert_to_16bit_uuid(const Uuid *uuid) {
|
||||
uint16_t uuid_16bits = 0;
|
||||
// Use bytes 2-3 of the Uuid:
|
||||
for (int i = 2; i < 4; ++i) {
|
||||
uuid_16bits <<= 8;
|
||||
uuid_16bits += ((const uint8_t *) uuid)[i];
|
||||
}
|
||||
return uuid_16bits;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Helper to reduces a 128-bit UUID to 32-bits. Note: this function does not
|
||||
//! check whether the original UUID is based on the Bluetooth base.
|
||||
static uint32_t prv_convert_to_32bit_uuid(const Uuid *uuid) {
|
||||
uint32_t uuid_32bits = 0;
|
||||
// Use bytes 0-3 of the Uuid:
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
uuid_32bits <<= 8;
|
||||
uuid_32bits += ((const uint8_t *) uuid)[i];
|
||||
}
|
||||
return uuid_32bits;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool ble_ad_set_service_uuids(BLEAdData *ad,
|
||||
const Uuid uuids[], uint8_t num_uuids) {
|
||||
struct __attribute__((__packed__)) BLEAdElementService {
|
||||
BLEAdElementHeader header;
|
||||
union {
|
||||
Uuid uuid_128[0];
|
||||
uint32_t uuid_32[0];
|
||||
uint16_t uuid_16[0];
|
||||
};
|
||||
};
|
||||
|
||||
const uint8_t max_width_bytes = prv_largest_common_bt_uuid_width(uuids,
|
||||
num_uuids);
|
||||
// Allocate buffer:
|
||||
const size_t buffer_size = sizeof(struct BLEAdElementService) +
|
||||
(max_width_bytes * num_uuids);
|
||||
uint8_t element_buffer[buffer_size];
|
||||
struct BLEAdElementService *element = (struct BLEAdElementService *) element_buffer;
|
||||
|
||||
// Set header fields (assume Complete):
|
||||
switch (max_width_bytes) {
|
||||
case 16: element->header.type = BLEAdTypeService128BitUUIDComplete; break;
|
||||
case 4: element->header.type = BLEAdTypeService32BitUUIDComplete; break;
|
||||
case 2: element->header.type = BLEAdTypeService16BitUUIDComplete; break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
|
||||
element->header.length = buffer_size - 1 /* -1 Length byte */;
|
||||
|
||||
// Copy UUIDs:
|
||||
for (unsigned int i = 0; i < num_uuids; ++i) {
|
||||
switch (max_width_bytes) {
|
||||
case 16: element->uuid_128[i] = uuids[i]; break;
|
||||
case 4: element->uuid_32[i] = prv_convert_to_32bit_uuid(&uuids[i]); break;
|
||||
case 2: element->uuid_16[i] = prv_convert_to_16bit_uuid(&uuids[i]); break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
|
||||
return prv_write_element_to_ad_data(ad, (const BLEAdElement *) element);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool ble_ad_set_local_name(BLEAdData *ad,
|
||||
const char *local_name) {
|
||||
if (!local_name) {
|
||||
return false;
|
||||
}
|
||||
const size_t length = strlen(local_name);
|
||||
uint8_t element_buffer[sizeof(BLEAdElement) + length];
|
||||
BLEAdElement *element = (BLEAdElement *) &element_buffer;
|
||||
element->header.length = length + 1 /* +1 Type byte */;
|
||||
element->header.type = BLEAdTypeLocalNameComplete; /* assume Complete */
|
||||
// Note: *not* zero terminated by design
|
||||
memcpy(element->data, local_name, length);
|
||||
return prv_write_element_to_ad_data(ad, element);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool ble_ad_set_tx_power_level(BLEAdData *ad) {
|
||||
uint8_t element_buffer[sizeof(BLEAdElement) + sizeof(int8_t)];
|
||||
BLEAdElement *element = (BLEAdElement *) element_buffer;
|
||||
element->header.length = sizeof(int8_t) + 1 /* +1 Type byte */;
|
||||
element->header.type = BLEAdTypeTxPowerLevel;
|
||||
*((int8_t *) element->data) = sys_ble_get_advertising_tx_power();
|
||||
|
||||
return prv_write_element_to_ad_data(ad, element);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool ble_ad_set_manufacturer_specific_data(BLEAdData *ad, uint16_t company_id,
|
||||
const uint8_t *data, size_t size) {
|
||||
struct __attribute__((__packed__)) BLEAdElementManufacturerSpecific {
|
||||
BLEAdElementHeader header;
|
||||
uint16_t company_id;
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
uint8_t element_buffer[sizeof(struct BLEAdElementManufacturerSpecific) + size];
|
||||
struct BLEAdElementManufacturerSpecific *element =
|
||||
(struct BLEAdElementManufacturerSpecific *) element_buffer;
|
||||
element->header.length = sizeof(struct BLEAdElementManufacturerSpecific)
|
||||
- 1 /* -1 Length byte */ + size;
|
||||
element->header.type = BLEAdTypeManufacturerSpecific;
|
||||
element->company_id = ltohs(company_id);
|
||||
memcpy(element->data, data, size);
|
||||
return prv_write_element_to_ad_data(ad, (const BLEAdElement *) element);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool ble_ad_set_flags(BLEAdData *ad, uint8_t flags) {
|
||||
struct __attribute__((__packed__)) BLEAdElementManufacturerSpecific {
|
||||
BLEAdElementHeader header;
|
||||
uint8_t flags;
|
||||
} element = {
|
||||
.header = {
|
||||
.length = sizeof(struct BLEAdElementManufacturerSpecific)
|
||||
- 1 /* -1 Length byte */,
|
||||
.type = BLEAdTypeFlags,
|
||||
},
|
||||
.flags = flags,
|
||||
};
|
||||
return prv_write_element_to_ad_data(ad, (const BLEAdElement *) &element);
|
||||
}
|
206
src/fw/applib/bluetooth/ble_ad_parse.h
Normal file
206
src/fw/applib/bluetooth/ble_ad_parse.h
Normal file
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* 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 <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! @file ble_ad_parse.h
|
||||
//! API to serialize and deserialize advertisment and scan response payloads.
|
||||
//!
|
||||
//! Inbound payloads, as received using the ble_scan.h public API, can be
|
||||
//! consumed/deserialized using the functions below.
|
||||
//!
|
||||
//! Outbound payloads can be created/serialized and then advertised using the
|
||||
//! gap_le_advert.h functions. At the moment, there is no public API.
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Consuming BLEAdData:
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
//! Searching the advertisement data to check whether a given service UUID is
|
||||
//! included.
|
||||
//! @param ad The advertisement data
|
||||
//! @param service_uuid The UUID of the service to look for
|
||||
//! @return true if the service UUID was found, false if not
|
||||
bool ble_ad_includes_service(const BLEAdData *ad, const Uuid *service_uuid);
|
||||
|
||||
//! If present, copies the Service UUIDs from the advertisement data.
|
||||
//! @param ad The advertisement data
|
||||
//! @param[out] uuids_out An array of Uuid`s into which the found Service UUIDs
|
||||
//! will be copied.
|
||||
//! @param num_uuids The size of the uuids_out array.
|
||||
//! @return The total number of found Service UUIDs. This might be a larger
|
||||
//! number than num_uuids, if the passed array was not large enough to hold all
|
||||
//! the UUIDs.
|
||||
//! @note All UUIDs from advertisement data will be converted to their 128-bit
|
||||
//! equivalents using the Bluetooth Base UUID using bt_uuid_expand_16bit or
|
||||
//! bt_uuid_expand_32bit.
|
||||
//! @see ble_ad_get_number_of_service_uuids
|
||||
uint8_t ble_ad_copy_service_uuids(const BLEAdData *ad,
|
||||
Uuid *uuids_out,
|
||||
uint8_t num_uuids);
|
||||
|
||||
//! If present, returns the number of Service UUIDs the advertisement data
|
||||
//! contains.
|
||||
//! @param ad The advertisement data
|
||||
//! @return If Service UUIDs data is present, the number of UUIDs is contains,
|
||||
//! or zero otherwise.
|
||||
uint8_t ble_ad_get_number_of_service_uuids(const BLEAdData *ad);
|
||||
|
||||
//! If present, gets the TX Power Level from the advertisement data.
|
||||
//! @param ad The advertisement data
|
||||
//! @param[out] tx_power_level_out Will contain the TX Power Level if the return
|
||||
//! value is true.
|
||||
//! @return true if the TX Power Level was found and assigned.
|
||||
bool ble_ad_get_tx_power_level(const BLEAdData *ad, int8_t *tx_power_level_out);
|
||||
|
||||
//! If present, copies the Local Name from the advertisement data.
|
||||
//! If the Local Name is bigger than the size of the buffer, only the part that
|
||||
//! fits will be copied. For convenience, the copied c-string will always be
|
||||
//! zero terminated for you.
|
||||
//! @param ad The advertisement data
|
||||
//! @param buffer The buffer into which to copy the Local Name, if found.
|
||||
//! @param size The size of the buffer
|
||||
//! @return The size of the Local Name in bytes, *including* zero terminator.
|
||||
//! Note that this might be more than the size of the provided buffer.
|
||||
//! If the Local Name was not found, the return value will be zero.
|
||||
size_t ble_ad_copy_local_name(const BLEAdData *ad,
|
||||
char *buffer, size_t size);
|
||||
|
||||
//! If the Local Name is present in the advertisment data, returns the number
|
||||
//! of bytes a C-string needs to be to hold the full name.
|
||||
//! @param ad The advertisement data
|
||||
//! @return The size of the Local Name in bytes, *including* zero terminator.
|
||||
//! If the Local Name is not present, zero is returned.
|
||||
size_t ble_ad_get_local_name_buffer_size(const BLEAdData *ad);
|
||||
|
||||
//! If present, copies the Manufacturer Specific data from the advertisement
|
||||
//! data. If the provided buffer is smaller than the size of the data, only
|
||||
//! the data up that fits the buffer will be copied.
|
||||
//! @param ad The advertisement data
|
||||
//! @param[out] company_id_out Out: The Company Identifier Code, see
|
||||
//! https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers
|
||||
//! This will only get written, if there was Manufacturer Specific data in the
|
||||
//! advertisement. In case you are not interested in getting the company ID,
|
||||
//! NULL can be passed in.
|
||||
//! @param buffer The buffer into which to copy the Manufacturer Specific Data,
|
||||
//! if found.
|
||||
//! @param size The size of the buffer
|
||||
//! @return The size of the Manufacturer Specific data in bytes. Note that this
|
||||
//! can be larger than the size of the provided buffer. If the Manufacturer
|
||||
//! Specific data was not found, the return value will be zero.
|
||||
//! @see ble_ad_get_manufacturer_specific_data_size
|
||||
size_t ble_ad_copy_manufacturer_specific_data(const BLEAdData *ad,
|
||||
uint16_t *company_id_out,
|
||||
uint8_t *buffer, size_t size);
|
||||
|
||||
//! Gets the size in bytes of Manufacturer Specific data in the advertisment.
|
||||
//! @param ad The advertisement data
|
||||
//! @return The size of the data, in bytes. If the Manufacturer Specific data is
|
||||
//! not present, zero is returned.
|
||||
size_t ble_ad_get_manufacturer_specific_data_size(const BLEAdData *ad);
|
||||
|
||||
//! Gets the size in bytes of the raw advertisement and scan response data.
|
||||
//! @param ad The advertisement data
|
||||
//! @return The size of the raw advertisement and scan response data in bytes.
|
||||
size_t ble_ad_get_raw_data_size(const BLEAdData *ad);
|
||||
|
||||
//! Copies the raw bytes of advertising and scan response data into a buffer.
|
||||
//! If there was scan response data, it will be concatenated directly after the
|
||||
//! advertising data.
|
||||
//! @param ad The advertisement data
|
||||
//! @param buffer The buffer into which to copy the raw bytes
|
||||
//! @param size The size of the buffer
|
||||
//! @return The number of bytes copied.
|
||||
size_t ble_ad_copy_raw_data(const BLEAdData *ad, uint8_t *buffer, size_t size);
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Creating BLEAdData:
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
//! Creates a blank, mutable advertisement and scan response payload.
|
||||
//! It can contain up to 31 bytes of advertisement data and up to 31 bytes of
|
||||
//! scan response data. The underlying storage for this is automatically
|
||||
//! allocated.
|
||||
//! @note When adding elements to the BLEAdData, using the ble_ad_set_...
|
||||
//! functions, elements will be added to the advertisement data part first.
|
||||
//! If the element to add does not fit, the scan response is automatically
|
||||
//! used. Added elements cannot span across the two parts.
|
||||
//! @return The blank payload object.
|
||||
//! @note BLEAdData created with this function must be destroyed at some point
|
||||
//! in time using ble_ad_destroy()
|
||||
//! @note Use the ble_ad_set... functions to write data into the object. The
|
||||
//! data written to it will occupy the advertisement payload until there is not
|
||||
//! enough space left, in which case all following data is written into the scan
|
||||
//! response. @see ble_ad_start_scan_response()
|
||||
BLEAdData* ble_ad_create(void);
|
||||
|
||||
//! Destroys an advertisement payload that was created earlier with
|
||||
//! ble_ad_create().
|
||||
void ble_ad_destroy(BLEAdData *ad);
|
||||
|
||||
//! Marks the start of the scan response and finalizes the advertisement
|
||||
//! payload. This forces successive writes to be written to the scan response,
|
||||
//! even though it would have fit into the advertisement payload.
|
||||
void ble_ad_start_scan_response(BLEAdData *ad_data);
|
||||
|
||||
//! Writes the Service UUID list to the advertisement or scan response payload.
|
||||
//! The list is assumed to be the complete list of Service UUIDs.
|
||||
//! @param ad The advertisement payload as created earlier by ble_ad_create()
|
||||
//! @param uuids Array of UUIDs to add to the list. If the UUIDs are all derived
|
||||
//! from the Bluetooth SIG base UUID, this function will automatically use
|
||||
//! a smaller Service UUID size if possible.
|
||||
//! @see bt_uuid_expand_16bit
|
||||
//! @see bt_uuid_expand_32bit
|
||||
//! @param num_uuids Number of UUIDs in the uuids array.
|
||||
//! @return true if the data was successfully written or false if not.
|
||||
bool ble_ad_set_service_uuids(BLEAdData *ad,
|
||||
const Uuid uuids[], uint8_t num_uuids);
|
||||
|
||||
//! Writes the Local Name to the advertisement or scan response payload.
|
||||
//! @param ad The advertisement payload as created earlier by ble_ad_create()
|
||||
//! @param local_name Zero terminated, UTF-8 string with the Local Name. The
|
||||
//! name is assumed to be complete and not abbreviated.
|
||||
//! @return true if the data was successfully written or false if not.
|
||||
bool ble_ad_set_local_name(BLEAdData *ad,
|
||||
const char *local_name);
|
||||
|
||||
//! Writes the TX Power Level to advertisement or scan response payload.
|
||||
//! The actual transmission power level value is set automatically, based on the
|
||||
//! value as used by the Bluetooth hardware.
|
||||
//! @param ad The advertisement payload as created earlier by ble_ad_create()
|
||||
//! @return true if the data was successfully written or false if not.
|
||||
bool ble_ad_set_tx_power_level(BLEAdData *ad);
|
||||
|
||||
//! Writes Manufacturer Specific Data to advertisement or scan response payload.
|
||||
//! @param ad The advertisement payload as created earlier by ble_ad_create()
|
||||
//! @param company_id The Company Identifier Code, see
|
||||
//! https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers
|
||||
//! @param data The data
|
||||
//! @param size The size of data in bytes
|
||||
//! @return true if the data was successfully written or false if not.
|
||||
bool ble_ad_set_manufacturer_specific_data(BLEAdData *ad,
|
||||
uint16_t company_id,
|
||||
const uint8_t *data, size_t size);
|
||||
|
||||
//! @internal -- Do not export
|
||||
//! Writes the Flags AD Type to the advertisement or scan response payload.
|
||||
//! @param ad The advertisement payload as created earlier by ble_ad_create()
|
||||
//! @param flags The flags to write. See Core_v4.0.pdf Vol 3, Appendix C, 18.1.
|
||||
//! @return true if the data was successfully written or false if not.
|
||||
bool ble_ad_set_flags(BLEAdData *ad, uint8_t flags);
|
66
src/fw/applib/bluetooth/ble_app_support.c
Normal file
66
src/fw/applib/bluetooth/ble_app_support.c
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 "ble_app_support.h"
|
||||
|
||||
#include "comm/ble/gap_le_scan.h"
|
||||
#include "comm/ble/gap_le_connect.h"
|
||||
#include "comm/ble/gatt_client_operations.h"
|
||||
#include "comm/ble/gatt_client_subscriptions.h"
|
||||
|
||||
#include "process_state/app_state/app_state.h"
|
||||
|
||||
//! @see ble_scan.c
|
||||
extern void ble_scan_handle_event(PebbleEvent *e, void *context);
|
||||
|
||||
//! @see ble_central.c
|
||||
extern void ble_central_handle_event(PebbleEvent *e, void *context);
|
||||
|
||||
//! @see ble_client.c
|
||||
extern void ble_client_handle_event(PebbleEvent *e, void *context);
|
||||
|
||||
void ble_init_app_state(void) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
*ble_app_state = (const BLEAppState) {
|
||||
// ble_scan_...:
|
||||
.scan_service_info = (const EventServiceInfo) {
|
||||
.type = PEBBLE_BLE_SCAN_EVENT,
|
||||
.handler = ble_scan_handle_event,
|
||||
},
|
||||
|
||||
// ble_central_...:
|
||||
.connection_service_info = (const EventServiceInfo) {
|
||||
.type = PEBBLE_BLE_CONNECTION_EVENT,
|
||||
.handler = ble_central_handle_event,
|
||||
},
|
||||
|
||||
// ble_client_...:
|
||||
.gatt_client_service_info = (const EventServiceInfo) {
|
||||
.type = PEBBLE_BLE_GATT_CLIENT_EVENT,
|
||||
.handler = ble_client_handle_event,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
void ble_app_cleanup(void) {
|
||||
// Gets run on the KernelMain task.
|
||||
// All kernel / shared resources for BLE that were allocated on behalf of the
|
||||
// app must be freed / released here:
|
||||
gap_le_stop_scan();
|
||||
gap_le_connect_cancel_all(GAPLEClientApp);
|
||||
gatt_client_subscriptions_cleanup_by_client(GAPLEClientApp);
|
||||
gatt_client_op_cleanup(GAPLEClientApp);
|
||||
}
|
54
src/fw/applib/bluetooth/ble_app_support.h
Normal file
54
src/fw/applib/bluetooth/ble_app_support.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 "ble_scan.h"
|
||||
#include "ble_central.h"
|
||||
#include "ble_client.h"
|
||||
|
||||
#include "applib/event_service_client.h"
|
||||
|
||||
typedef struct {
|
||||
// Scanning
|
||||
EventServiceInfo scan_service_info;
|
||||
BLEScanHandler scan_handler;
|
||||
|
||||
// Connecting
|
||||
EventServiceInfo connection_service_info;
|
||||
BLEConnectionHandler connection_handler;
|
||||
|
||||
// GATT Client
|
||||
EventServiceInfo gatt_client_service_info;
|
||||
|
||||
BLEClientServiceChangeHandler gatt_service_change_handler;
|
||||
|
||||
BLEClientReadHandler gatt_characteristic_read_handler;
|
||||
BLEClientWriteHandler gatt_characteristic_write_handler;
|
||||
BLEClientSubscribeHandler gatt_characteristic_subscribe_handler;
|
||||
|
||||
BLEClientReadDescriptorHandler gatt_descriptor_read_handler;
|
||||
BLEClientWriteDescriptorHandler gatt_descriptor_write_handler;
|
||||
|
||||
uint8_t gatt_client_num_handlers;
|
||||
} BLEAppState;
|
||||
|
||||
//! Initializes the static BLE state for the currently running app.
|
||||
void ble_init_app_state(void);
|
||||
|
||||
//! Must be called by the kernel, after an app is killed to stop any ongoing
|
||||
//! BLE activity that was running on behalf of the app.
|
||||
void ble_app_cleanup(void);
|
57
src/fw/applib/bluetooth/ble_central.c
Normal file
57
src/fw/applib/bluetooth/ble_central.c
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 "ble_central.h"
|
||||
|
||||
#include "process_state/app_state/app_state.h"
|
||||
|
||||
#include "comm/ble/gap_le_connect.h"
|
||||
|
||||
static BTErrno prv_bt_errno_for_event(const PebbleBLEConnectionEvent *e) {
|
||||
if (e->connected) {
|
||||
return BTErrnoConnected;
|
||||
}
|
||||
|
||||
// FIXME: PBL-35506 We need to re-evaluate what error code to actually use here
|
||||
return e->hci_reason;
|
||||
}
|
||||
|
||||
void ble_central_handle_event(PebbleEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
if (!ble_app_state->connection_handler) {
|
||||
return;
|
||||
}
|
||||
const PebbleBLEConnectionEvent *conn_event = &e->bluetooth.le.connection;
|
||||
const BTDeviceInternal device = PebbleEventToBTDeviceInternal(conn_event);
|
||||
ble_app_state->connection_handler(device.opaque,
|
||||
prv_bt_errno_for_event(conn_event));
|
||||
}
|
||||
|
||||
BTErrno ble_central_set_connection_handler(BLEConnectionHandler handler) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
const bool is_subscribed = (ble_app_state->connection_handler != NULL);
|
||||
ble_app_state->connection_handler = handler;
|
||||
if (handler) {
|
||||
if (!is_subscribed) {
|
||||
event_service_client_subscribe(&ble_app_state->connection_service_info);
|
||||
}
|
||||
} else {
|
||||
if (is_subscribed) {
|
||||
event_service_client_unsubscribe(&ble_app_state->connection_service_info);
|
||||
}
|
||||
}
|
||||
return BTErrnoOK;
|
||||
}
|
86
src/fw/applib/bluetooth/ble_central.h
Normal file
86
src/fw/applib/bluetooth/ble_central.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! Callback that is called for each connection and disconnection event.
|
||||
//! @param device The device that got (dis)connected
|
||||
//! @param connection_status BTErrnoConnected if connected, otherwise the
|
||||
//! reason for the disconnection: BTErrnoConnectionTimeout,
|
||||
//! BTErrnoRemotelyTerminated, BTErrnoLocallyTerminatedBySystem or
|
||||
//! BTErrnoLocallyTerminatedByApp.
|
||||
//! @note See additional notes with ble_central_set_connection_handler()
|
||||
typedef void (*BLEConnectionHandler)(BTDevice device,
|
||||
BTErrno connection_status);
|
||||
|
||||
//! Registers the connection event handler of the application.
|
||||
//! This event handler will be called when connections and disconnection occur,
|
||||
//! for devices for which ble_central_connect() has been called by the
|
||||
//! application.
|
||||
//! Only for successful connections and complete disconnections will the event
|
||||
//! handler be called. Transient issues that might happen during connection
|
||||
//! establishment will be not be reported to the application. Instead, the
|
||||
//! system will attempt to initiate a connection to the device again.
|
||||
//! If this is called again, the previous handler will be unregistered.
|
||||
//! @param handler The connection event handler of the application
|
||||
//! @return Always returns BTErrnoOK.
|
||||
BTErrno ble_central_set_connection_handler(BLEConnectionHandler handler);
|
||||
|
||||
//! Attempts to initiate a connection from the application to another device.
|
||||
//! In case there is no Bluetooth connection to the device yet, this function
|
||||
//! configures the Bluetooth subsystem to scan for the specified
|
||||
//! device and instructs it to connect to it as soon as a connectable
|
||||
//! advertisement has been received. The application will need to register a
|
||||
//! connection event handler prior to calling ble_central_connect(), using
|
||||
//! ble_central_set_connection_handler().
|
||||
//! Outstanding (auto)connection attempts can be cancelled using
|
||||
//! ble_central_cancel_connect().
|
||||
//! @note Connections are virtualized. This means that your application needs to
|
||||
//! call the ble_central_connect, even though the device might already have an
|
||||
//! actual Bluetooth connection already. This can be the case when connecting
|
||||
//! to the user's phone: it is likely the system has created a Bluetooth
|
||||
//! connection already, still the application has to connect internally in order
|
||||
//! to use the connection.
|
||||
//! @param device The device to connect to
|
||||
//! @param auto_reconnect Pass in true to automatically attempt to reconnect
|
||||
//! again if the connection is lost. The BLEConnectionHandler will be called for
|
||||
//! each time the device is (re)connected.
|
||||
//! Pass in false, if the system should connect once only.
|
||||
//! The BLEConnectionHandler will be called up to one time only.
|
||||
//! @param is_pairing_required If the application requires that the Bluetooth
|
||||
//! traffic is encrypted, is_pairing_required can be set to true to let the
|
||||
//! system transparently set up pairing, or reestablish encryption, if the
|
||||
//! device is already paired. Only after this security procedure is finished,
|
||||
//! the BLEConnectionHandler will be called. Note that not all devices support
|
||||
//! pairing and one of the BTErrnoPairing... errors can result from a failed
|
||||
//! pairing process. If the application does not require pairing, set to false.
|
||||
//! @note It is possible that encryption is still enabled, even if the
|
||||
//! application did not require this.
|
||||
//! @return BTErrnoOK if the intent to connect was processed successfully, or
|
||||
//! ... TODO
|
||||
BTErrno ble_central_connect(BTDevice device,
|
||||
bool auto_reconnect,
|
||||
bool is_pairing_required);
|
||||
|
||||
//! Attempts to cancel the connection, as initiated by ble_central_connect().
|
||||
//! The underlying Bluetooth connection might not be disconnected if the
|
||||
//! connection is still in use by the system. However, as far as the application
|
||||
//! is concerned, the device is disconnected and the connection handler will
|
||||
//! be called with BTErrnoLocallyTerminatedByApp.
|
||||
//! @return BTErrnoOK if the cancelling was successful, or ... TODO
|
||||
BTErrno ble_central_cancel_connect(BTDevice device);
|
49
src/fw/applib/bluetooth/ble_characteristic.c
Normal file
49
src/fw/applib/bluetooth/ble_characteristic.c
Normal 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.
|
||||
*/
|
||||
|
||||
#include "ble_characteristic.h"
|
||||
|
||||
#include "syscall/syscall.h"
|
||||
|
||||
bool ble_characteristic_is_readable(BLECharacteristic characteristic) {
|
||||
return (sys_ble_characteristic_get_properties(characteristic) &
|
||||
BLEAttributePropertyRead);
|
||||
}
|
||||
|
||||
bool ble_characteristic_is_writable(BLECharacteristic characteristic) {
|
||||
return (sys_ble_characteristic_get_properties(characteristic) &
|
||||
BLEAttributePropertyWrite);
|
||||
}
|
||||
|
||||
bool ble_characteristic_is_writable_without_response(BLECharacteristic characteristic) {
|
||||
return (sys_ble_characteristic_get_properties(characteristic) &
|
||||
BLEAttributePropertyWriteWithoutResponse);
|
||||
}
|
||||
|
||||
bool ble_characteristic_is_subscribable(BLECharacteristic characteristic) {
|
||||
return (sys_ble_characteristic_get_properties(characteristic) &
|
||||
(BLEAttributePropertyNotify | BLEAttributePropertyIndicate));
|
||||
}
|
||||
|
||||
bool ble_characteristic_is_notifiable(BLECharacteristic characteristic) {
|
||||
return (sys_ble_characteristic_get_properties(characteristic) &
|
||||
BLEAttributePropertyNotify);
|
||||
}
|
||||
|
||||
bool ble_characteristic_is_indicatable(BLECharacteristic characteristic) {
|
||||
return (sys_ble_characteristic_get_properties(characteristic) &
|
||||
BLEAttributePropertyIndicate);
|
||||
}
|
99
src/fw/applib/bluetooth/ble_characteristic.h
Normal file
99
src/fw/applib/bluetooth/ble_characteristic.h
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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 <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! Gets the UUID for a characteristic.
|
||||
//! @param characteristic The characteristic for which to get the UUID
|
||||
//! @return The UUID of the characteristic
|
||||
Uuid ble_characteristic_get_uuid(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets the properties bitset for a characteristic.
|
||||
//! @param characteristic The characteristic for which to get the properties
|
||||
//! @return The properties bitset
|
||||
BLEAttributeProperty ble_characteristic_get_properties(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets whether the characteristic is readable or not.
|
||||
//! @param characteristic The characteristic for which to get its readability.
|
||||
//! @return true if the characteristic is readable, false if it is not.
|
||||
bool ble_characteristic_is_readable(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets whether the characteristic is writable or not.
|
||||
//! @param characteristic The characteristic for which to get its write-ability.
|
||||
//! @return true if the characteristic is writable, false if it is not.
|
||||
bool ble_characteristic_is_writable(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets whether the characteristic is writable without response or not.
|
||||
//! @param characteristic The characteristic for which to get its write-ability.
|
||||
//! @return true if the characteristic is writable without response, false if it is not.
|
||||
bool ble_characteristic_is_writable_without_response(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets whether the characteristic is subscribable or not.
|
||||
//! @param characteristic The characteristic for which to get its subscribability.
|
||||
//! @return true if the characteristic is subscribable, false if it is not.
|
||||
bool ble_characteristic_is_subscribable(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets whether the characteristic is notifiable or not.
|
||||
//! @param characteristic The characteristic for which to get its notifiability.
|
||||
//! @return true if the characteristic is notifiable, false if it is not.
|
||||
bool ble_characteristic_is_notifiable(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets whether the characteristic is indicatable or not.
|
||||
//! @param characteristic The characteristic for which to get its indicatability.
|
||||
//! @return true if the characteristic is indicatable, false if it is not.
|
||||
bool ble_characteristic_is_indicatable(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets the service that the characteristic belongs to.
|
||||
//! @param characteristic The characteristic for which to find the service it
|
||||
//! belongs to.
|
||||
//! @return The service owning the characteristic
|
||||
BLEService ble_characteristic_get_service(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets the device that the characteristic belongs to.
|
||||
//! @param characteristic The characteristic for which to find the device it
|
||||
//! belongs to.
|
||||
//! @return The device owning the characteristic.
|
||||
BTDevice ble_characteristic_get_device(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets the descriptors associated with the characteristic.
|
||||
//! @param characteristic The characteristic for which to get the descriptors
|
||||
//! @param[out] descriptors_out An array of pointers to descriptors, into which
|
||||
//! the associated descriptors will be copied.
|
||||
//! @param num_descriptors The size of the descriptors_out array.
|
||||
//! @return The total number of descriptors for the service. This might be a
|
||||
//! larger number than num_descriptors will contain, if the passed array was
|
||||
//! not large enough to hold all the pointers.
|
||||
//! @note For convenience, the services are owned by the system and references
|
||||
//! to services, characteristics and descriptors are guaranteed to remain valid
|
||||
//! *until the BLEClientServiceChangeHandler is called again* or until
|
||||
//! application is terminated.
|
||||
uint8_t ble_characteristic_get_descriptors(BLECharacteristic characteristic,
|
||||
BLEDescriptor descriptors_out[],
|
||||
uint8_t num_descriptors);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// (FUTURE / LATER / NOT SCOPED)
|
||||
// Just to see how symmetric the Server APIs would be:
|
||||
|
||||
BLECharacteristic ble_characteristic_create(const Uuid *uuid,
|
||||
BLEAttributeProperty properties);
|
||||
|
||||
BLECharacteristic ble_characteristic_create_with_descriptors(const Uuid *uuid,
|
||||
BLEAttributeProperty properties,
|
||||
BLEDescriptor descriptors[],
|
||||
uint8_t num_descriptors);
|
305
src/fw/applib/bluetooth/ble_client.c
Normal file
305
src/fw/applib/bluetooth/ble_client.c
Normal file
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define FILE_LOG_COLOR LOG_COLOR_BLUE
|
||||
|
||||
#include "ble_client.h"
|
||||
|
||||
#include "ble_app_support.h"
|
||||
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// TODO:
|
||||
// - device name
|
||||
// - connection speed (?)
|
||||
// - app_info.json
|
||||
// - airplane mode service / BT HW state
|
||||
|
||||
// - API to detect that accessory is currently already connected to the phone?
|
||||
// - How to identify? iOS SDK does not expose addresses. Use DIS info? Fall
|
||||
// back to device name?
|
||||
|
||||
static void prv_handle_services_added(
|
||||
BLEClientServiceChangeHandler handler, BTDeviceInternal device, BTErrno status) {
|
||||
BLEService services[BLE_GATT_MAX_SERVICES_CHANGED];
|
||||
|
||||
uint8_t num_services = sys_ble_client_copy_services(device, services,
|
||||
BLE_GATT_MAX_SERVICES_CHANGED);
|
||||
|
||||
if (num_services != 0) {
|
||||
handler(device.opaque, BLEClientServicesAdded, services, num_services, status);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (PBL-22086): We should really make this easier to do ...
|
||||
// We can't directly dereference the service discovery info pointer from third
|
||||
// party apps because it was malloc'ed from the kernel heap. Instead copy the
|
||||
// info that is used onto a variable which has been allocated from the stack.
|
||||
DEFINE_SYSCALL(void, sys_get_service_discovery_info, const PebbleBLEGATTClientServiceEvent *e,
|
||||
PebbleBLEGATTClientServiceEventInfo *info) {
|
||||
if (PRIVILEGE_WAS_ELEVATED) {
|
||||
// Note: if we start storing services, we will need to update the size
|
||||
syscall_assert_userspace_buffer(info, sizeof(*info));
|
||||
}
|
||||
|
||||
*info = (PebbleBLEGATTClientServiceEventInfo) {
|
||||
.type = e->info->type,
|
||||
.device = e->info->device,
|
||||
.status = e->info->status
|
||||
};
|
||||
}
|
||||
|
||||
static void prv_handle_service_change(const PebbleBLEGATTClientEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
const BLEClientServiceChangeHandler handler =
|
||||
ble_app_state->gatt_service_change_handler;
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
PebbleBLEGATTClientServiceEventInfo info;
|
||||
sys_get_service_discovery_info((const PebbleBLEGATTClientServiceEvent *)e, &info);
|
||||
|
||||
switch (info.type) {
|
||||
case PebbleServicesAdded:
|
||||
prv_handle_services_added(handler, info.device, info.status);
|
||||
break;
|
||||
case PebbleServicesRemoved:
|
||||
// TODO (PBL-22087): This is suboptimal. Before we release the BLE API, we should
|
||||
// either communicate to the App all the handles which have changed or
|
||||
// allow the getters for removed services to still work for the duration
|
||||
// of the callback. For now just force a full handle flush and then resync the app
|
||||
handler(info.device.opaque, BLEClientServicesInvalidateAll, NULL, 0, info.status);
|
||||
prv_handle_services_added(handler, info.device, info.status);
|
||||
break;
|
||||
case PebbleServicesInvalidateAll:
|
||||
handler(info.device.opaque, BLEClientServicesInvalidateAll, NULL, 0, info.status);
|
||||
break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
|
||||
typedef void (*GenericReadHandler)(BLECharacteristic characteristic,
|
||||
const uint8_t *value,
|
||||
size_t value_length,
|
||||
uint16_t value_offset,
|
||||
BLEGATTError error);
|
||||
|
||||
static void prv_consume_read_response(const PebbleBLEGATTClientEvent *e,
|
||||
GenericReadHandler handler) {
|
||||
uint8_t *value = NULL;
|
||||
uint16_t value_length = e->value_length;
|
||||
const uintptr_t object_ref = e->object_ref;
|
||||
BLEGATTError gatt_error = e->gatt_error;
|
||||
|
||||
// Read Responses / Notifications with 0 length data must not be attempted to be consumed
|
||||
if (value_length) {
|
||||
value = (uint8_t *) applib_malloc(value_length);
|
||||
if (!value) {
|
||||
gatt_error = BLEGATTErrorLocalInsufficientResources;
|
||||
value_length = 0;
|
||||
}
|
||||
// If there is a read response, we *must* consume it,
|
||||
// otherwise the events and associated awaiting responses will
|
||||
// get out of sync with each other.
|
||||
sys_ble_client_consume_read(object_ref, value, &value_length);
|
||||
}
|
||||
|
||||
if (handler) {
|
||||
handler(object_ref, value, value_length, 0 /* value_offset (future proofing) */, gatt_error);
|
||||
}
|
||||
applib_free(value);
|
||||
}
|
||||
|
||||
static void prv_consume_notifications(const PebbleBLEGATTClientEvent *e,
|
||||
GenericReadHandler handler) {
|
||||
uint8_t *value = NULL;
|
||||
BLEGATTError gatt_error = e->gatt_error;
|
||||
|
||||
uint16_t heap_buffer_size = 0;
|
||||
uint16_t value_length = 0;
|
||||
bool has_more = sys_ble_client_get_notification_value_length(&value_length);
|
||||
while (has_more) {
|
||||
if (heap_buffer_size < value_length) {
|
||||
const uint16_t new_heap_buffer_size = MIN(value_length, 64 /* arbitrary min size.. */);
|
||||
applib_free(value);
|
||||
value = (uint8_t *) applib_malloc(new_heap_buffer_size);
|
||||
heap_buffer_size = value ? new_heap_buffer_size : 0;
|
||||
}
|
||||
if (!value) {
|
||||
gatt_error = BLEGATTErrorLocalInsufficientResources;
|
||||
value_length = 0;
|
||||
}
|
||||
uintptr_t object_ref;
|
||||
// Consume, even if we didn't have enough memory, this will eat the notification and free up
|
||||
// the space in the buffer.
|
||||
const uint16_t next_value_length = sys_ble_client_consume_notification(&object_ref,
|
||||
value, &value_length,
|
||||
&has_more);
|
||||
if (handler) {
|
||||
handler(object_ref, value, value_length, 0 /* value_offset (future proofing) */, gatt_error);
|
||||
}
|
||||
|
||||
value_length = next_value_length;
|
||||
}
|
||||
|
||||
applib_free(value);
|
||||
}
|
||||
|
||||
static void prv_handle_notifications(const PebbleBLEGATTClientEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
prv_consume_notifications(e, ble_app_state->gatt_characteristic_read_handler);
|
||||
}
|
||||
|
||||
static void prv_handle_characteristic_read(const PebbleBLEGATTClientEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
prv_consume_read_response(e, ble_app_state->gatt_characteristic_read_handler);
|
||||
}
|
||||
|
||||
static void prv_handle_characteristic_write(const PebbleBLEGATTClientEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
const BLEClientWriteHandler handler = ble_app_state->gatt_characteristic_write_handler;
|
||||
if (handler) {
|
||||
handler(e->object_ref, e->gatt_error);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_handle_characteristic_subscribe(const PebbleBLEGATTClientEvent *e) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "TODO: GATT Client Event, subtype=%u", e->subtype);
|
||||
}
|
||||
|
||||
static void prv_handle_descriptor_read(const PebbleBLEGATTClientEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
prv_consume_read_response(e, ble_app_state->gatt_descriptor_read_handler);
|
||||
}
|
||||
|
||||
static void prv_handle_descriptor_write(const PebbleBLEGATTClientEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
const BLEClientWriteDescriptorHandler handler = ble_app_state->gatt_descriptor_write_handler;
|
||||
if (handler) {
|
||||
handler(e->object_ref, e->gatt_error);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_handle_buffer_empty(const PebbleBLEGATTClientEvent *e) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
typedef void(*PrvHandler)(const PebbleBLEGATTClientEvent *);
|
||||
|
||||
static PrvHandler prv_handler_for_subtype(
|
||||
PebbleBLEGATTClientEventType event_subtype) {
|
||||
if (event_subtype >= PebbleBLEGATTClientEventTypeNum) {
|
||||
WTF;
|
||||
}
|
||||
// MT: This is a bit smaller in code size than a switch():
|
||||
static const PrvHandler handler[] = {
|
||||
[PebbleBLEGATTClientEventTypeServiceChange] = prv_handle_service_change,
|
||||
[PebbleBLEGATTClientEventTypeCharacteristicRead] = prv_handle_characteristic_read,
|
||||
[PebbleBLEGATTClientEventTypeNotification] = prv_handle_notifications,
|
||||
[PebbleBLEGATTClientEventTypeCharacteristicWrite] = prv_handle_characteristic_write,
|
||||
[PebbleBLEGATTClientEventTypeCharacteristicSubscribe] = prv_handle_characteristic_subscribe,
|
||||
[PebbleBLEGATTClientEventTypeDescriptorRead] = prv_handle_descriptor_read,
|
||||
[PebbleBLEGATTClientEventTypeDescriptorWrite] = prv_handle_descriptor_write,
|
||||
[PebbleBLEGATTClientEventTypeBufferEmpty] = prv_handle_buffer_empty,
|
||||
};
|
||||
return handler[event_subtype];
|
||||
}
|
||||
|
||||
// Exported for ble_app_support.c
|
||||
void ble_client_handle_event(PebbleEvent *e) {
|
||||
const PebbleBLEGATTClientEvent *gatt_event = &e->bluetooth.le.gatt_client;
|
||||
prv_handler_for_subtype(gatt_event->subtype)(gatt_event);
|
||||
}
|
||||
|
||||
static BTErrno prv_set_handler(void *new_handler, off_t struct_offset_bytes) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
typedef void (*BLEGenericHandler)(void);
|
||||
BLEGenericHandler *handler_storage =
|
||||
(BLEGenericHandler *)(((uint8_t *) ble_app_state) + struct_offset_bytes);
|
||||
|
||||
const bool had_previous_handler = (*handler_storage == NULL);
|
||||
*handler_storage = (BLEGenericHandler) new_handler;
|
||||
|
||||
if (had_previous_handler) {
|
||||
if (new_handler) {
|
||||
if (ble_app_state->gatt_client_num_handlers++ == 0) {
|
||||
// First GATT handler to be registered.
|
||||
// Subscribe to GATT Client event service:
|
||||
event_service_client_subscribe(&ble_app_state->gatt_client_service_info);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!new_handler) {
|
||||
if (--ble_app_state->gatt_client_num_handlers == 0) {
|
||||
// Last GATT handler to be de-registered.
|
||||
// Unsubscribe from GATT Client event service:
|
||||
event_service_client_unsubscribe(&ble_app_state->gatt_client_service_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
return BTErrnoOK;
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_service_filter(const Uuid service_uuids[],
|
||||
uint8_t num_uuids) {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_service_change_handler(BLEClientServiceChangeHandler handler) {
|
||||
const off_t offset = offsetof(BLEAppState, gatt_service_change_handler);
|
||||
return prv_set_handler(handler, offset);
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_read_handler(BLEClientReadHandler handler) {
|
||||
const off_t offset = offsetof(BLEAppState, gatt_characteristic_read_handler);
|
||||
return prv_set_handler(handler, offset);
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_write_response_handler(BLEClientWriteHandler handler) {
|
||||
const off_t offset = offsetof(BLEAppState, gatt_characteristic_write_handler);
|
||||
return prv_set_handler(handler, offset);
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_subscribe_handler(BLEClientSubscribeHandler handler) {
|
||||
const off_t offset = offsetof(BLEAppState, gatt_characteristic_subscribe_handler);
|
||||
return prv_set_handler(handler, offset);
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_buffer_empty_handler(BLEClientBufferEmptyHandler empty_handler) {
|
||||
// TODO
|
||||
return BTErrnoOther;
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_descriptor_write_handler(BLEClientWriteDescriptorHandler handler) {
|
||||
const off_t offset = offsetof(BLEAppState, gatt_descriptor_write_handler);
|
||||
return prv_set_handler(handler, offset);
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_descriptor_read_handler(BLEClientReadDescriptorHandler handler) {
|
||||
const off_t offset = offsetof(BLEAppState, gatt_descriptor_read_handler);
|
||||
return prv_set_handler(handler, offset);
|
||||
}
|
345
src/fw/applib/bluetooth/ble_client.h
Normal file
345
src/fw/applib/bluetooth/ble_client.h
Normal file
|
@ -0,0 +1,345 @@
|
|||
/*
|
||||
* 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 <bluetooth/bluetooth_types.h>
|
||||
|
||||
typedef enum {
|
||||
BLEClientServicesAdded,
|
||||
BLEClientServicesRemoved,
|
||||
BLEClientServicesInvalidateAll
|
||||
} BLEClientServiceChangeUpdate;
|
||||
|
||||
//! Callback that is called when the services on a remote device that are
|
||||
//! available to the application have changed. It gets called when:
|
||||
//! + the initial discovery of services completes
|
||||
//! + a new service has been discovered on the device after initial discovery
|
||||
//! + a service has been removed
|
||||
//! + the remote device has disconnected
|
||||
//!
|
||||
//! @note For convenience, the services are owned by the system and references
|
||||
//! to services, characteristics and descriptors are guaranteed to remain valid
|
||||
//! *until the service exposing the characteristic or descriptor is removed or
|
||||
//! all services are invalidated* or until application is terminated.
|
||||
//! @note References to services, characteristics and descriptors for the
|
||||
//! specified device, that have been obtained in a previous callback to the
|
||||
//! handler, are no longer valid. The application is responsible for cleaning up
|
||||
//! these references.
|
||||
//!
|
||||
//! @param device The device associated with the service discovery process
|
||||
//! @param update_type Defines what type of service update is being received
|
||||
//! @param services Array of pointers to the discovered services. The array will
|
||||
//! only contain Service UUIDs that matches the filter that was passed into the
|
||||
//! ble_client_discover_services_and_characteristics() call.
|
||||
//! @param num_services The number of discovered services matching the criteria.
|
||||
//! @param status BTErrnoOK if the service discovery was completed successfully.
|
||||
//! If the device was disconnected, BTErrnoConnectionTimeout,
|
||||
//! BTErrnoRemotelyTerminated, BTErrnoLocallyTerminatedBySystem or
|
||||
//! BTErrnoLocallyTerminatedByApp will specify the reason of the disconnection.
|
||||
//! The number of services will be zero upon a disconnection.
|
||||
typedef void (*BLEClientServiceChangeHandler)(BTDevice device,
|
||||
BLEClientServiceChangeUpdate update_type,
|
||||
const BLEService services[],
|
||||
uint8_t num_services,
|
||||
BTErrno status);
|
||||
|
||||
//! Registers the callback that handles service changes. After the call to
|
||||
//! this function returns, the application should be ready to handle calls to
|
||||
//! the handler.
|
||||
//! @param handler Pointer to the function that will handle the service changes
|
||||
//! @return BTErrnoOK if the handler was registered successfully,
|
||||
//! or TODO....
|
||||
BTErrno ble_client_set_service_change_handler(BLEClientServiceChangeHandler handler);
|
||||
|
||||
//! Registers the filter list of Service UUIDs.
|
||||
//! @param service_uuids An array of the Service UUIDs that the application is
|
||||
//! interested in and the system should filter by. Passing NULL will discover
|
||||
//! all services on the device.
|
||||
//! @param num_uuids The number of Uuid`s in the service_uuids array. Ignored
|
||||
//! when NULL is passed for the service_uuids argument.
|
||||
//! @return BTErrnoOK if the filter was set up successfully,
|
||||
//! or TODO....
|
||||
BTErrno ble_client_set_service_filter(const Uuid service_uuids[],
|
||||
uint8_t num_uuids);
|
||||
|
||||
//! Starts a discovery of services and characteristics on a remote device.
|
||||
//! The discovered services will be delivered to the application through the
|
||||
//! BLEClientServiceChangeHandler. The results will be filtered with the list
|
||||
//! of Service UUIDs as configured with ble_client_set_service_filter().
|
||||
//! @param device The device for which to perform service discovery.
|
||||
//! @return BTErrnoOK if the service discovery started successfully,
|
||||
//! BTErrnoInvalidParameter if the device was not connected,
|
||||
//! BTErrnoInvalidState if service discovery was already on-going, or
|
||||
//! an internal error otherwise (>= BTErrnoInternalErrorBegin).
|
||||
BTErrno ble_client_discover_services_and_characteristics(BTDevice device);
|
||||
|
||||
//! Different subscription types that can be used with ble_client_subscribe()
|
||||
typedef enum {
|
||||
//! No subscription.
|
||||
BLESubscriptionNone = 0,
|
||||
//! Notification subscription.
|
||||
BLESubscriptionNotifications = (1 << 0),
|
||||
//! Indication subscription.
|
||||
BLESubscriptionIndications = (1 << 1),
|
||||
//! Any subscription. Use this value with ble_client_subscribe(), in case
|
||||
//! the application does not care about the type of subscription. If both
|
||||
//! types are supported by the server, the notification subscription type
|
||||
//! will be used.
|
||||
BLESubscriptionAny = BLESubscriptionNotifications | BLESubscriptionIndications,
|
||||
} BLESubscription;
|
||||
|
||||
//! Callback to receive the characteristic value, resulting from either
|
||||
//! ble_client_read() and/or ble_client_subscribe().
|
||||
//! @param characteristic The characteristic of the received value
|
||||
//! @param value Byte-array containing the value
|
||||
//! @param value_length The number of bytes the byte-array contains
|
||||
//! @param value_offset The offset in bytes from the start of the characteristic
|
||||
//! value that has been read.
|
||||
//! @param error The error or status as returned by the remote server. If the
|
||||
//! read was successful, this remote server is supposed to send
|
||||
//! BLEGATTErrorSuccess.
|
||||
typedef void (*BLEClientReadHandler)(BLECharacteristic characteristic,
|
||||
const uint8_t *value,
|
||||
size_t value_length,
|
||||
uint16_t value_offset,
|
||||
BLEGATTError error);
|
||||
|
||||
//! Callback to handle the response to a written characteristic, resulting from
|
||||
//! ble_client_write().
|
||||
//! @param characteristic The characteristic that was written to.
|
||||
//! @param error The error or status as returned by the remote server. If the
|
||||
//! write was successful, this remote server is supposed to send
|
||||
//! BLEGATTErrorSuccess.
|
||||
typedef void (*BLEClientWriteHandler)(BLECharacteristic characteristic,
|
||||
BLEGATTError error);
|
||||
|
||||
//! Callback to handle the confirmation of a subscription or unsubscription to
|
||||
//! characteristic value changes (notifications or indications).
|
||||
//! @param characteristic The characteristic for which the client is now
|
||||
//! (un)subscribed.
|
||||
//! @param subscription_type The type of subscription. If the client is now
|
||||
//! unsubscribed, the type will be BLESubscriptionNone.
|
||||
//! @param The error or status as returned by the remote server. If the
|
||||
//! (un)subscription was successful, this remote server is supposed to send
|
||||
//! BLEGATTErrorSuccess.
|
||||
typedef void (*BLEClientSubscribeHandler)(BLECharacteristic characteristic,
|
||||
BLESubscription subscription_type,
|
||||
BLEGATTError error);
|
||||
|
||||
//! Callback to handle the event that the buffer for outbound data is empty.
|
||||
typedef void (*BLEClientBufferEmptyHandler)(void);
|
||||
|
||||
//! Registers the handler for characteristic value read operations.
|
||||
//! @param read_handler Pointer to the function that will handle callbacks
|
||||
//! with read characteristic values as result of calls to ble_client_read().
|
||||
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
|
||||
BTErrno ble_client_set_read_handler(BLEClientReadHandler read_handler);
|
||||
|
||||
//! Registers the handler for characteristic value write (with response)
|
||||
//! operations.
|
||||
//! @param write_handler Pointer to the function that will handle callbacks
|
||||
//! for written characteristic values as result of calls to ble_client_write().
|
||||
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
|
||||
BTErrno ble_client_set_write_response_handler(BLEClientWriteHandler write_handler);
|
||||
|
||||
//! Registers the handler for characteristic value subscribe operations.
|
||||
//! @param subscribe_handler Pointer to the function that will handle callbacks
|
||||
//! for (un)subscription confirmations as result of calls to
|
||||
//! ble_client_subscribe().
|
||||
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
|
||||
BTErrno ble_client_set_subscribe_handler(BLEClientSubscribeHandler subscribe_handler);
|
||||
|
||||
//! Registers the handler to get called back when the buffer for outbound
|
||||
//! data is empty again.
|
||||
//! @param empty_handler Pointer to the function that will handle callbacks
|
||||
//! for "buffer empty" events.
|
||||
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
|
||||
BTErrno ble_client_set_buffer_empty_handler(BLEClientBufferEmptyHandler empty_handler);
|
||||
|
||||
//! Gets the maximum characteristic value size that can be written. This size
|
||||
//! can vary depending on the connected device.
|
||||
//! @param device The device for which to get the maximum characteristic value
|
||||
//! size.
|
||||
//! @return The maximum characteristic value size that can be written.
|
||||
uint16_t ble_client_get_maximum_value_length(BTDevice device);
|
||||
|
||||
//! Read the value of a characteristic.
|
||||
//! A call to this function will result in a callback to the registered
|
||||
//! BLEClientReadHandler handler. @see ble_client_set_read_handler.
|
||||
//! @param characteristic The characteristic for which to read the value
|
||||
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
|
||||
BTErrno ble_client_read(BLECharacteristic characteristic);
|
||||
|
||||
//! Write the value of a characterstic.
|
||||
//! A call to this function will result in a callback to the registered
|
||||
//! BLEClientWriteHandler handler. @see ble_client_set_write_response_handler.
|
||||
//! @param characteristic The characteristic for which to write the value
|
||||
//! @param value Buffer with the value to write
|
||||
//! @param value_length Number of bytes to write
|
||||
//! @note Values must not be longer than ble_client_get_maximum_value_length().
|
||||
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
|
||||
BTErrno ble_client_write(BLECharacteristic characteristic,
|
||||
const uint8_t *value,
|
||||
size_t value_length);
|
||||
|
||||
//! Write the value of a characterstic without response.
|
||||
//! @param characteristic The characteristic for which to write the value
|
||||
//! @param value Buffer with the value to write
|
||||
//! @param value_length Number of bytes to write
|
||||
//! @note Values must not be longer than ble_client_get_maximum_value_length().
|
||||
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
|
||||
//! If the buffer for outbound data was full, BTErrnoNotEnoughResources will
|
||||
//! be returned. When the buffer is emptied, the handler that is registered
|
||||
//! using ble_client_set_buffer_empty_handler() will be called.
|
||||
BTErrno ble_client_write_without_response(BLECharacteristic characteristic,
|
||||
const uint8_t *value,
|
||||
size_t value_length);
|
||||
|
||||
//! Subscribe to be notified or indicated of value changes of a characteristic.
|
||||
//!
|
||||
//! The value updates are delivered to the application through the
|
||||
//! BLEClientReadHandler, which should be registered before calling this
|
||||
//! function using ble_client_set_read_handler().
|
||||
//!
|
||||
//! There are two types of subscriptions: notifications and indications.
|
||||
//! For notifications there is no flow-control. This means that notifications
|
||||
//! can get dropped if the rate at which they are sent is too high. Conversely,
|
||||
//! each indication needs an acknowledgement from the receiver before the next
|
||||
//! one can get sent and is thus more reliable. The system performs
|
||||
//! acknowledgements to indications automatically. Applications do not need to
|
||||
//! worry about this, nor can they affect this.
|
||||
//! @param characteristic The characteristic to subscribe to.
|
||||
//! @param subscription_type The type of subscription to use.
|
||||
//! If BLESubscriptionAny is used as subscription_type and both types are
|
||||
//! supported by the server, the notification subscription type will be used.
|
||||
//! @note This call does not block and returns quickly. A callback to
|
||||
//! the BLEClientSubscribeHandler will happen at a later point in time, to
|
||||
//! report the success or failure of the subscription. This handler should be
|
||||
//! registered before calling this function using
|
||||
//! ble_client_set_subscribe_handler().
|
||||
//! @note Under the hood, this API writes to the Client Characteristic
|
||||
//! Configuration Descriptor's Notifications or Indications enabled/disabled
|
||||
//! bit.
|
||||
//! @return BTErrnoOK if the subscription request was sent sucessfully, or
|
||||
//! TODO...
|
||||
BTErrno ble_client_subscribe(BLECharacteristic characteristic,
|
||||
BLESubscription subscription_type);
|
||||
|
||||
|
||||
//! Callback to receive the descriptor value, resulting from a call to
|
||||
//! ble_client_read_descriptor().
|
||||
//! @param descriptor The descriptor of the received value
|
||||
//! @param value Byte-array containing the value
|
||||
//! @param value_length The number of bytes the byte-array contains
|
||||
//! @param value_offset The offset in bytes from the start of the descriptor
|
||||
//! value that has been read.
|
||||
//! @param error The error or status as returned by the remote server. If the
|
||||
//! read was successful, this remote server is supposed to send
|
||||
//! BLEGATTErrorSuccess.
|
||||
typedef void (*BLEClientReadDescriptorHandler)(BLEDescriptor descriptor,
|
||||
const uint8_t *value,
|
||||
size_t value_length,
|
||||
uint16_t value_offset,
|
||||
BLEGATTError error);
|
||||
|
||||
//! Callback to handle the response to a written descriptor, resulting from
|
||||
//! ble_client_write_descriptor().
|
||||
//! @param descriptor The descriptor that was written to.
|
||||
//! @param error The error or status as returned by the remote server. If the
|
||||
//! write was successful, this remote server is supposed to send
|
||||
//! BLEGATTErrorSuccess.
|
||||
typedef void (*BLEClientWriteDescriptorHandler)(BLEDescriptor descriptor,
|
||||
BLEGATTError error);
|
||||
|
||||
//! Registers the handlers for descriptor value write operations.
|
||||
//! @param write_handler Pointer to the function that will handle callbacks
|
||||
//! for written descriptor values as result of calls to
|
||||
//! ble_client_write_descriptor().
|
||||
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
|
||||
BTErrno ble_client_set_descriptor_write_handler(BLEClientWriteDescriptorHandler write_handler);
|
||||
|
||||
//! Registers the handlers for descriptor value read operations.
|
||||
//! @param read_handler Pointer to the function that will handle callbacks
|
||||
//! with read descriptor values as result of calls to
|
||||
//! ble_client_read_descriptor().
|
||||
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
|
||||
BTErrno ble_client_set_descriptor_read_handler(BLEClientReadDescriptorHandler read_handler);
|
||||
|
||||
//! Write the value of a descriptor.
|
||||
//! A call to this function will result in a callback to the registered
|
||||
//! BLEClientWriteDescriptorHandler handler.
|
||||
//! @see ble_client_set_descriptor_write_handler.
|
||||
//! @param descriptor The descriptor for which to write the value
|
||||
//! @param value Buffer with the value to write
|
||||
//! @param value_length Number of bytes to write
|
||||
//! @note Values must not be longer than ble_client_get_maximum_value_length().
|
||||
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
|
||||
BTErrno ble_client_write_descriptor(BLEDescriptor descriptor,
|
||||
const uint8_t *value,
|
||||
size_t value_length);
|
||||
|
||||
//! Read the value of a descriptor.
|
||||
//! A call to this function will result in a callback to the registered
|
||||
//! BLEClientReadDescriptorHandler handler.
|
||||
//! @see ble_client_set_descriptor_read_handler.
|
||||
//! @param descriptor The descriptor for which to read the value
|
||||
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
|
||||
BTErrno ble_client_read_descriptor(BLEDescriptor descriptor);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// (FUTURE / LATER / NOT SCOPED)
|
||||
// Just to see how symmetric the Server APIs would be:
|
||||
|
||||
|
||||
//! Opaque ATT request context
|
||||
typedef void * BLERequest;
|
||||
|
||||
typedef void (*BLEServerWriteHandler)(BLERequest request,
|
||||
BLECharacteristic characteristic,
|
||||
BTDevice remote_device,
|
||||
const uint8_t *value,
|
||||
size_t value_length,
|
||||
uint16_t value_offset);
|
||||
|
||||
typedef void (*BLEServerReadHandler)(BLECharacteristic characteristic,
|
||||
BTDevice remote_device,
|
||||
uint16_t value_offset);
|
||||
|
||||
typedef void (*BLEServerSubscribeHandler)(BLECharacteristic characteristic,
|
||||
BTDevice remote_device,
|
||||
BLESubscription subscription_type);
|
||||
|
||||
BTErrno ble_server_set_handlers(BLEServerReadHandler read_handler,
|
||||
BLEServerWriteHandler write_handler,
|
||||
BLEServerSubscribeHandler subscription_handler);
|
||||
|
||||
BTErrno ble_server_start_service(BLEService service);
|
||||
|
||||
BTErrno ble_server_stop_service(BLEService service);
|
||||
|
||||
BTErrno ble_server_respond_to_write(BLERequest request, BLEGATTError error);
|
||||
|
||||
BTErrno ble_server_respond_to_read(BLERequest request, BLEGATTError error,
|
||||
const uint8_t *value, size_t value_length,
|
||||
uint16_t value_offset);
|
||||
|
||||
BTErrno ble_server_send_update(BLECharacteristic characteristic,
|
||||
const uint8_t *value, size_t value_length);
|
||||
|
||||
BTErrno ble_server_send_update_selectively(BLECharacteristic characteristic,
|
||||
const uint8_t *value, size_t value_length,
|
||||
const BTDevice *devices, uint8_t num_devices);
|
43
src/fw/applib/bluetooth/ble_descriptor.h
Normal file
43
src/fw/applib/bluetooth/ble_descriptor.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! Gets the UUID for a descriptor.
|
||||
//! @param descriptor The descriptor for which to get the UUID.
|
||||
//! @return The UUID of the descriptor
|
||||
Uuid ble_descriptor_get_uuid(BLEDescriptor descriptor);
|
||||
|
||||
//! Gets the characteristic for a descriptor.
|
||||
//! @param descriptor The descriptor for which to get the characteristic.
|
||||
//! @return The characteristic
|
||||
//! @note For convenience, the services are owned by the system and references
|
||||
//! to services, characteristics and descriptors are guaranteed to remain valid
|
||||
//! *until the BLEClientServiceChangeHandler is called again* or until
|
||||
//! application is terminated.
|
||||
BLECharacteristic ble_descriptor_get_characteristic(BLEDescriptor descriptor);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// (FUTURE / LATER / NOT SCOPED)
|
||||
// Just to see how symmetric the Server APIs would be:
|
||||
|
||||
|
||||
BLEDescriptor ble_descriptor_create(const Uuid *uuid,
|
||||
BLEAttributeProperty properties);
|
||||
|
||||
BTErrno ble_descriptor_destroy(BLEDescriptor descriptor);
|
17
src/fw/applib/bluetooth/ble_device.c
Normal file
17
src/fw/applib/bluetooth/ble_device.c
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 "ble_device.h"
|
33
src/fw/applib/bluetooth/ble_device.h
Normal file
33
src/fw/applib/bluetooth/ble_device.h
Normal 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 <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! Copies the devices that are known to the system. This set includes all
|
||||
//! paired devices (connected or not) and devices for which there is a Bluetooth
|
||||
//! connection to the system (but not necessarily paired and not necessarily
|
||||
//! connected to the application).
|
||||
//! @param[out] devices_out An array of BTDevice`s into which the known
|
||||
//! devices will be copied.
|
||||
//! @param[in,out] num_devices_out In: the size of the devices_out array.
|
||||
//! Out: the number of BTDevice`s that were copied.
|
||||
//! @return The total number of known devices. This might be a larger
|
||||
//! number than num_devices_out will contain, if the passed array was not large
|
||||
//! enough to hold all the connected devices.
|
||||
uint8_t ble_device_copy_known_devices(BTDevice *devices_out,
|
||||
uint8_t *num_devices_out);
|
145
src/fw/applib/bluetooth/ble_ibeacon.c
Normal file
145
src/fw/applib/bluetooth/ble_ibeacon.c
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* 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 "ble_ibeacon.h"
|
||||
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Apple's iBeacon AD DATA format.
|
||||
//! The byte-order of Apple's fields (uuid, major and minor) is Big Endian (!!!)
|
||||
//! @see Apple's docs for more info: http://goo.gl/iOrnpj
|
||||
//! @see StackOverflow distance/accuracy calculations: http://goo.gl/yH0ubM
|
||||
static const uint16_t COMPANY_ID_APPLE = 0x004c;
|
||||
static const uint8_t APPLE_TYPE_IBEACON = 0x02;
|
||||
static const uint8_t APPLE_IBEACON_LENGTH = 0x15;
|
||||
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
//! @see APPLE_AD_TYPE_IBEACON
|
||||
uint8_t type;
|
||||
|
||||
//! @see APPLE_IBEACON_LENGTH
|
||||
uint8_t length;
|
||||
|
||||
//! The application "proximityUUID" of the iBeacon. Generally, multiple
|
||||
//! iBeacons share one UUID and an (iOS) app scans for one particular UUID.
|
||||
uint8_t uuid[16];
|
||||
|
||||
//! The most significant value in the beacon.
|
||||
uint16_t major;
|
||||
|
||||
//! The least significant value in the beacon.
|
||||
uint16_t minor;
|
||||
|
||||
//! The calibrated transmit power.
|
||||
int8_t calibrated_tx_power;
|
||||
} AdDataManufacturerSpecificAppleiBeacon;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Accessors
|
||||
|
||||
Uuid ble_ibeacon_get_uuid(const BLEiBeacon *ibeacon) {
|
||||
return ibeacon->uuid;
|
||||
}
|
||||
|
||||
uint16_t ble_ibeacon_get_major(const BLEiBeacon *ibeacon) {
|
||||
return ibeacon->major;
|
||||
}
|
||||
|
||||
uint16_t ble_ibeacon_get_minor(const BLEiBeacon *ibeacon) {
|
||||
return ibeacon->minor;
|
||||
}
|
||||
|
||||
uint16_t ble_ibeacon_get_distance_cm(const BLEiBeacon *ibeacon) {
|
||||
return ibeacon->distance_cm;
|
||||
}
|
||||
|
||||
BLEiBeacon *ble_ibeacon_create_from_ad_data(const BLEAdData *ad,
|
||||
int8_t rssi) {
|
||||
// Note, not yet exported to 3rd party apps so no padding necessary
|
||||
BLEiBeacon *ibeacon = applib_malloc(sizeof(BLEiBeacon));
|
||||
if (ibeacon && !ble_ibeacon_parse(ad, rssi, ibeacon)) {
|
||||
// Failed to parse.
|
||||
applib_free(ibeacon);
|
||||
ibeacon = NULL;
|
||||
}
|
||||
return ibeacon;
|
||||
}
|
||||
|
||||
void ble_ibeacon_destroy(BLEiBeacon *ibeacon) {
|
||||
applib_free(ibeacon);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Below is the iBeacon advertisement parsing code.
|
||||
|
||||
static uint16_t calculate_distance_cm(int8_t tx_power, int8_t rssi) {
|
||||
return 0; // TODO
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! iBeacon Advertisement Data parser
|
||||
bool ble_ibeacon_parse(const BLEAdData *ad, int8_t rssi,
|
||||
BLEiBeacon *ibeacon_out) {
|
||||
uint16_t company_id = 0;
|
||||
AdDataManufacturerSpecificAppleiBeacon raw_ibeacon;
|
||||
const size_t size_copied =
|
||||
ble_ad_copy_manufacturer_specific_data(ad, &company_id,
|
||||
(uint8_t *) &raw_ibeacon,
|
||||
sizeof(raw_ibeacon));
|
||||
if (size_copied != sizeof(raw_ibeacon)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (company_id == COMPANY_ID_APPLE &&
|
||||
raw_ibeacon.type == APPLE_TYPE_IBEACON &&
|
||||
raw_ibeacon.length == APPLE_IBEACON_LENGTH) {
|
||||
|
||||
const int8_t tx_power = raw_ibeacon.calibrated_tx_power;
|
||||
*ibeacon_out = (const BLEiBeacon) {
|
||||
.uuid = UuidMakeFromBEBytes(raw_ibeacon.uuid),
|
||||
.major = ntohs(raw_ibeacon.major),
|
||||
.minor = ntohs(raw_ibeacon.minor),
|
||||
.distance_cm = calculate_distance_cm(tx_power, rssi),
|
||||
.rssi = rssi,
|
||||
.calibrated_tx_power = tx_power,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! iBeacon Advertisement Data composer
|
||||
bool ble_ibeacon_compose(const BLEiBeacon *ibeacon_in, BLEAdData *ad_out) {
|
||||
AdDataManufacturerSpecificAppleiBeacon raw_ibeacon = {
|
||||
.type = APPLE_TYPE_IBEACON,
|
||||
.length = APPLE_IBEACON_LENGTH,
|
||||
// Major/Minor are part of Apple's iBeacon spec and are Big Endian!
|
||||
.major = htons(ibeacon_in->major),
|
||||
.minor = htons(ibeacon_in->minor),
|
||||
.calibrated_tx_power = ibeacon_in->calibrated_tx_power,
|
||||
};
|
||||
// Uuid is stored Big Endian on Pebble, so just copy over:
|
||||
memcpy(&raw_ibeacon.uuid, &ibeacon_in->uuid, sizeof(Uuid));
|
||||
|
||||
return ble_ad_set_manufacturer_specific_data(ad_out, COMPANY_ID_APPLE,
|
||||
(uint8_t *) &raw_ibeacon,
|
||||
sizeof(raw_ibeacon));
|
||||
}
|
105
src/fw/applib/bluetooth/ble_ibeacon.h
Normal file
105
src/fw/applib/bluetooth/ble_ibeacon.h
Normal 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
|
||||
#include "applib/bluetooth/ble_ad_parse.h"
|
||||
|
||||
//! Size in bytes of the iBeacon advertisement data, including the length and
|
||||
//! AD Type bytes.
|
||||
#define IBEACON_ADVERTISEMENT_DATA_SIZE (27)
|
||||
|
||||
//! Data structure representing an iBeacon advertisement.
|
||||
typedef struct {
|
||||
//! The application UUID that the iBeacon advertised. In iOS' CoreBluetooth,
|
||||
//! this corresponds to the "proximityUUID" property of instances of CLBeacon.
|
||||
Uuid uuid;
|
||||
|
||||
//! Custom value, most significant part.
|
||||
uint16_t major;
|
||||
|
||||
//! Custom value, least significant part.
|
||||
uint16_t minor;
|
||||
|
||||
//! Estimated distance to the iBeacon in centimeters. In iOS' CoreBluetooth,
|
||||
//! this corresponds to the "accuracy" property of instances of CLBeacon.
|
||||
uint16_t distance_cm;
|
||||
|
||||
//! The received signal strength from the iBeacon, in decibels.
|
||||
int8_t rssi;
|
||||
|
||||
//! The calibrated power of the iBeacon. This is the RSSI measured at 1 meter
|
||||
//! distance from the iBeacon. The iBeacon transmits this information in its
|
||||
//! advertisment. Using this and the actual RSSI, the distance is estimated.
|
||||
int8_t calibrated_tx_power;
|
||||
} BLEiBeacon;
|
||||
|
||||
//! Gets the UUID of the iBeacon.
|
||||
//! @param The iBeacon
|
||||
//! @return The UUID that the iBeacon advertised. In iOS' CoreBluetooth,
|
||||
//! this corresponds to the "proximityUUID" property of instances of CLBeacon.
|
||||
Uuid ble_ibeacon_get_uuid(const BLEiBeacon *ibeacon);
|
||||
|
||||
//! Gets the major value of the iBeacon.
|
||||
//! @param The iBeacon
|
||||
//! @return The major, custom value.
|
||||
uint16_t ble_ibeacon_get_major(const BLEiBeacon *ibeacon);
|
||||
|
||||
//! Gets the minor value of the iBeacon.
|
||||
//! @param The iBeacon
|
||||
//! @return The minor, custom value.
|
||||
uint16_t ble_ibeacon_get_minor(const BLEiBeacon *ibeacon);
|
||||
|
||||
//! Gets the estimated distance to the iBeacon, in centimeters.
|
||||
//! @param The iBeacon
|
||||
//! @return The estimated distance in centimeters.
|
||||
uint16_t ble_ibeacon_get_distance_cm(const BLEiBeacon *ibeacon);
|
||||
|
||||
//! Create BLEiBeacon from advertisement data.
|
||||
//! @param ad Advertisement data, as acquired from the BLEScanHandler callback.
|
||||
//! @param rssi The RSSI of the advertisement, as acquired from the
|
||||
//! BLEScanHandler callback.
|
||||
//! @return BLEiBeacon object if iBeacon data is found, or NULL if the
|
||||
//! advertisement data did not contain valid iBeacon data.
|
||||
BLEiBeacon *ble_ibeacon_create_from_ad_data(const BLEAdData *ad,
|
||||
int8_t rssi);
|
||||
|
||||
//! Destroys an BLEiBeacon object and frees its resources that were allocated
|
||||
//! earlier by ble_ibeacon_create_from_ad_data().
|
||||
//! @param ibeacon Reference to the BLEiBeacon to destroy.
|
||||
void ble_ibeacon_destroy(BLEiBeacon *ibeacon);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal iBeacon Advertisement Data parser
|
||||
//! @param ad The raw advertisement data
|
||||
//! @param rssi The RSSI of the advertisement
|
||||
//! @param[out] ibeacon_out Will contain the parsed iBeacon data if the call
|
||||
//! returns true.
|
||||
//! @return true if the data element was succesfully parsed as iBeacon,
|
||||
//! false if the data element could not be parsed as iBeacon.
|
||||
bool ble_ibeacon_parse(const BLEAdData *ad, int8_t rssi,
|
||||
BLEiBeacon *ibeacon_out);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal iBeacon Advertisement Data serializer
|
||||
//! @param ibeacon_in The iBeacon structure to serialize. The rssi and
|
||||
//! distance_cm fields are ignored because they are only valid for received
|
||||
//! iBeacon packets.
|
||||
//! @param[out] ad_out The advertisement payload to write the data into.
|
||||
//! @return true if the iBeacon data was written successfully.
|
||||
bool ble_ibeacon_compose(const BLEiBeacon *ibeacon_in, BLEAdData *ad_out);
|
109
src/fw/applib/bluetooth/ble_scan.c
Normal file
109
src/fw/applib/bluetooth/ble_scan.c
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 "ble_scan.h"
|
||||
|
||||
#include "ble_ad_parse.h"
|
||||
|
||||
#include "applib/app_logging.h"
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "comm/ble/gap_le_scan.h"
|
||||
|
||||
#include "kernel/events.h"
|
||||
|
||||
#include "syscall/syscall.h"
|
||||
|
||||
|
||||
void ble_scan_handle_event(PebbleEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
if (!ble_app_state->scan_handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the same buffer size as the kernel itself:
|
||||
uint8_t *buffer = (uint8_t *) applib_malloc(GAP_LE_SCAN_REPORTS_BUFFER_SIZE);
|
||||
if (!buffer) {
|
||||
APP_LOG(LOG_LEVEL_ERROR, "Need %u bytes of heap for ble_scan_start()",
|
||||
GAP_LE_SCAN_REPORTS_BUFFER_SIZE);
|
||||
return;
|
||||
}
|
||||
uint16_t size = GAP_LE_SCAN_REPORTS_BUFFER_SIZE;
|
||||
sys_ble_consume_scan_results(buffer, &size);
|
||||
|
||||
if (size == 0) {
|
||||
goto finally;
|
||||
}
|
||||
|
||||
// Walk all the reports in the buffer:
|
||||
const uint8_t *cursor = buffer;
|
||||
while (cursor < buffer + size) {
|
||||
const GAPLERawAdReport *report = (GAPLERawAdReport *)cursor;
|
||||
|
||||
const BTDeviceInternal device = (const BTDeviceInternal) {
|
||||
.address = report->address.address,
|
||||
.is_classic = false,
|
||||
.is_random_address = report->is_random_address,
|
||||
};
|
||||
|
||||
// Call the scan handler for each report:
|
||||
ble_app_state->scan_handler(device.opaque, report->rssi, &report->payload);
|
||||
|
||||
const size_t report_length = sizeof(GAPLERawAdReport) +
|
||||
report->payload.ad_data_length +
|
||||
report->payload.scan_resp_data_length;
|
||||
cursor += report_length;
|
||||
}
|
||||
|
||||
finally:
|
||||
applib_free(buffer);
|
||||
}
|
||||
|
||||
BTErrno ble_scan_start(BLEScanHandler handler) {
|
||||
if (!handler) {
|
||||
return (BTErrnoInvalidParameter);
|
||||
}
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
if (ble_app_state->scan_handler) {
|
||||
return (BTErrnoInvalidState);
|
||||
}
|
||||
const bool result = sys_ble_scan_start();
|
||||
if (!result) {
|
||||
return BTErrnoOther;
|
||||
}
|
||||
ble_app_state->scan_handler = handler;
|
||||
event_service_client_subscribe(&ble_app_state->scan_service_info);
|
||||
return BTErrnoOK;
|
||||
}
|
||||
|
||||
BTErrno ble_scan_stop(void) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
if (!ble_app_state->scan_handler) {
|
||||
return (BTErrnoInvalidState);
|
||||
}
|
||||
const bool result = sys_ble_scan_stop();
|
||||
if (!result) {
|
||||
return BTErrnoOther;
|
||||
}
|
||||
event_service_client_unsubscribe(&ble_app_state->scan_service_info);
|
||||
ble_app_state->scan_handler = NULL;
|
||||
return BTErrnoOK;
|
||||
}
|
||||
|
||||
bool ble_scan_is_scanning(void) {
|
||||
return sys_ble_scan_is_scanning();
|
||||
}
|
53
src/fw/applib/bluetooth/ble_scan.h
Normal file
53
src/fw/applib/bluetooth/ble_scan.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! Callback that is called for each advertisment that is found while scanning
|
||||
//! using ble_scan_start().
|
||||
//! @param device The device from which the advertisment originated.
|
||||
//! @param rssi The RSSI (Received Signal Strength Indication) of the
|
||||
//! advertisement.
|
||||
//! @param advertisement_data The payload of the advertisement. When there was
|
||||
//! a scan response, this payload will contain the data of the scan response
|
||||
//! as well. The information in the payload can be accessed using the
|
||||
//! ble_ad_... functions, @see for example ble_ad_copy_local_name() and
|
||||
//! ble_ad_includes_service().
|
||||
//! @note The advertisement_data is cleaned up by the system automatically
|
||||
//! immediately after returning from this callback. Do not keep around
|
||||
//! any long-lived references around to the advertisement_data.
|
||||
//! @note Do not use ble_ad_destroy() on the advertisement_data.
|
||||
typedef void (*BLEScanHandler)(BTDevice device,
|
||||
int8_t rssi,
|
||||
const BLEAdData *advertisement_data);
|
||||
|
||||
//! Start scanning for advertisements. Pebble will scan actively, meaning it
|
||||
//! will perform scan requests whenever the advertisement is scannable.
|
||||
//! @param handler The callback to handle the found advertisments. It must not
|
||||
//! be NULL.
|
||||
//! @return BTErrnoOK if scanning started successfully, BTErrnoInvalidParameter
|
||||
//! if the handler was invalid or BTErrnoInvalidState if scanning had already
|
||||
//! been started.
|
||||
BTErrno ble_scan_start(BLEScanHandler handler);
|
||||
|
||||
//! Stop scanning for advertisements.
|
||||
//! @return BTErrnoOK if scanning stopped successfully, or TODO...
|
||||
BTErrno ble_scan_stop(void);
|
||||
|
||||
//! @return True if the system is scanning for advertisements or false if not.
|
||||
bool ble_scan_is_scanning(void);
|
17
src/fw/applib/bluetooth/ble_security.c
Normal file
17
src/fw/applib/bluetooth/ble_security.c
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 "ble_security.h"
|
94
src/fw/applib/bluetooth/ble_security.h
Normal file
94
src/fw/applib/bluetooth/ble_security.h
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 <bluetooth/bluetooth_types.h>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Out-Of-Band additions
|
||||
|
||||
|
||||
//! "Out-of-Band" (OOB) is one of the mechanisms to exchange a shared secret
|
||||
//! during a pairing procedure between two devices. "PIN" and "Just Works" are
|
||||
//! the two other exchange mechanisms that the Bluetooth 4.0 Specification
|
||||
//! defines, but both are susceptible to eavesdropping of the exchanged keys.
|
||||
//! OOB provides better protection against this, by offering a way to exchange
|
||||
//! the shared secret via a communications channel other than Bluetooth itself
|
||||
//! (hence the name "Out-of-Band"). Of course, this is only more secure if the
|
||||
//! channel through which the OOB data is exchanged itself is harder to
|
||||
//! eavesdrop.
|
||||
//!
|
||||
//! The exchanged OOB data is used as Temporary-Key (TK) to encrypt the
|
||||
//! connection during the one-time pairing information exchange. Part of this
|
||||
//! information exchange are Long-Term-Key(s) (LTK) that will be used upon
|
||||
//! successive reconnections. For more details, see Bluetooth 4.0 Specification,
|
||||
//! Volume 3, Part H, 2.3.5, "Pairing Algorithms".
|
||||
//!
|
||||
//! The OOB APIs enable the application to provide the system with OOB data.
|
||||
//! The application will need to indicate to the system for what devices it
|
||||
//! is capable of providing OOB data. Later, when a pairing procedure takes
|
||||
//! place with an OOB-enabled device, the system will ask the application to
|
||||
//! provide that OOB data.
|
||||
//!
|
||||
//! It is up to the application and the manufacturer of the device how the OOB
|
||||
//! data is exchanged between the application and the remote device. Examples of
|
||||
//! how this can be done:
|
||||
//! - The application could generate the OOB data and show a QR code containing
|
||||
//! the data on the screen of the Pebble that is then read by the device.
|
||||
//! - If the device is connected to the Internet, the OOB data could be
|
||||
//! provisioned to Pebble via a web service. The application would use the
|
||||
//! JavaScript APIs to fetch the data from the web service and transfer the
|
||||
//! data to the application on the watch using the AppMessage APIs.
|
||||
|
||||
|
||||
//! Pointer to a function that can provide Out-Of-Band keys.
|
||||
//! @see ble_security_set_oob_handler() and ble_security_enable_oob()
|
||||
//! @param device The device for which the OOB key needs to be provided
|
||||
//! @param oob_key_buffer_out The buffer into which the OOB key should be
|
||||
//! written.
|
||||
//! @param oob_key_buffer_size The size of the buffer in bytes. Currently only
|
||||
//! keys of 128-bit (16 byte) size are supported.
|
||||
//! @return true if the OOB key was written or false if no OOB data could be
|
||||
//! provided for the device.
|
||||
typedef bool (*BLESecurityOOBHandler)(BTDevice device,
|
||||
uint8_t *oob_key_buffer_out,
|
||||
size_t oob_key_buffer_size);
|
||||
|
||||
//! Registers a permanent callback function that is responsible for providing
|
||||
//! Out-Of-Band (OOB) keys. The callback is guaranteed to get called only for
|
||||
//! devices for which the application has enabled OOB using
|
||||
//! ble_security_enable_oob(). The callback will get called by the system during
|
||||
//! a pairing procedure, but only if the remote device indicated to have OOB
|
||||
//! data as well.
|
||||
//! @param oob_handler Pointer to the function that will provide OOB key data.
|
||||
//! @return BTErrnoOK if the call was successful, or TODO...
|
||||
BTErrno ble_security_set_oob_handler(BLESecurityOOBHandler oob_handler);
|
||||
|
||||
//! Enable or disable Out-Of-Band pairing for the device.
|
||||
//! This function is a way to indicate to the system that the application has
|
||||
//! Out-Of-Band data that can be used when pairing with a particular device.
|
||||
//! @note The application is encouraged to configure OOB as soon as possible,
|
||||
//! *before* connecting to any devices. If the application supports OOB, but
|
||||
//! enables is *after* connecting, there is a chance that the remote requests to
|
||||
//! start pairing before your application has had the chance to enable OOB.
|
||||
//! @note After terminating the application, the system will automatically
|
||||
//! disable OOB for any devices it had enabled OOB for. Upon re-launching the
|
||||
//! application, it will need to re-enable OOB if required.
|
||||
//! @param device The device for which to enable or disable OOB
|
||||
//! @param enable Pass in true to enable OOB for the device, or false to disable
|
||||
//! @return BTErrnoOK if the call was successful, or TODO...
|
||||
BTErrno ble_security_enable_oob(BTDevice device, bool enable);
|
17
src/fw/applib/bluetooth/ble_service.c
Normal file
17
src/fw/applib/bluetooth/ble_service.c
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 "ble_service.h"
|
88
src/fw/applib/bluetooth/ble_service.h
Normal file
88
src/fw/applib/bluetooth/ble_service.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! Gets the characteristics associated with a service.
|
||||
//! @param service The service for which to get the characteristics
|
||||
//! @param[out] characteristics_out An array of pointers to characteristics,
|
||||
//! into which the associated characteristics will be copied.
|
||||
//! @param num_characteristics The size of the characteristics_out array.
|
||||
//! @return The total number of characteristics for the service. This might be a
|
||||
//! larger number than num_in_out will contain, if the passed array was not
|
||||
//! large enough to hold all the pointers.
|
||||
//! @note For convenience, the services are owned by the system and references
|
||||
//! to services, characteristics and descriptors are guaranteed to remain valid
|
||||
//! *until the BLEClientServiceChangeHandler is called again* or until
|
||||
//! application is terminated.
|
||||
uint8_t ble_service_get_characteristics(BLEService service,
|
||||
BLECharacteristic characteristics_out[],
|
||||
uint8_t num_characteristics);
|
||||
|
||||
//! Gets the Service UUID of a service.
|
||||
//! @param service The service for which to get the Service UUID.
|
||||
//! @return The 128-bit Service UUID, or UUID_INVALID if the service reference
|
||||
//! was invalid.
|
||||
//! @note The returned UUID is always a 128-bit UUID, even if the device
|
||||
//! its interal GATT service database uses 16-bit or 32-bit Service UUIDs.
|
||||
//! @see bt_uuid_expand_16bit for a macro that converts 16-bit UUIDs to 128-bit
|
||||
//! equivalents.
|
||||
//! @see bt_uuid_expand_32bit for a macro that converts 32-bit UUIDs to 128-bit
|
||||
//! equivalents.
|
||||
Uuid ble_service_get_uuid(BLEService service);
|
||||
|
||||
//! Gets the device that hosts the service.
|
||||
//! @param service The service for which to find the device it belongs to.
|
||||
//! @return The device hosting the service, or an invalid device if the service
|
||||
//! reference was invalid. Use bt_device_is_invalid() to test whether the
|
||||
//! returned device is invalid.
|
||||
BTDevice ble_service_get_device(BLEService service);
|
||||
|
||||
//! Gets the services that are references by a service as "Included Service".
|
||||
//! @param service The service for which to get the included services
|
||||
//! @param[out] included_services_out An array of pointers to services,
|
||||
//! into which the included services will be copied.
|
||||
//! @param num_services the size of the included_services_out array.
|
||||
//! @return The total number of included services for the service. This might be
|
||||
//! a larger number than included_services_out can contain, if the passed array
|
||||
//! was not large enough to hold all the pointers.
|
||||
//! @note For convenience, the services are owned by the system and references
|
||||
//! to services, characteristics and descriptors are guaranteed to remain valid
|
||||
//! *until the BLEClientServiceChangeHandler is called again* or until
|
||||
//! application is terminated.
|
||||
uint8_t ble_service_get_included_services(BLEService service,
|
||||
BLEService included_services_out[],
|
||||
uint8_t num_services);
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// (FUTURE / LATER / NOT SCOPED)
|
||||
// Just to see how symmetric the Server APIs could be:
|
||||
|
||||
// creates + adds to GATT DB (?)
|
||||
// Services aren't supposed to change. Pass everything into the 'create' call:
|
||||
BLEService ble_service_create(const Uuid *service_uuid,
|
||||
BLECharacteristic characteristics[],
|
||||
uint8_t num_characteristics);
|
||||
|
||||
void ble_service_set_included_services(BLEService service,
|
||||
BLEService included_services[],
|
||||
uint8_t num_included_services);
|
||||
|
||||
// removes from GATT DB (?) + destroys
|
||||
void ble_service_destroy(BLEService service);
|
146
src/fw/applib/compass_service.c
Normal file
146
src/fw/applib/compass_service.c
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#if !CAPABILITY_HAS_MAGNETOMETER
|
||||
#error "Use fw/applib/compass_service_stub.c instead if we don't have a magnetometer"
|
||||
#endif
|
||||
|
||||
#include "compass_service.h"
|
||||
#include "compass_service_private.h"
|
||||
|
||||
#include "applib/app_logging.h"
|
||||
#include "applib/app_timer.h"
|
||||
#include "applib/event_service_client.h"
|
||||
#include "util/trig.h"
|
||||
#include "drivers/mag.h"
|
||||
#include "kernel/events.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "kernel/kernel_applib_state.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "process_state/worker_state/worker_state.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#define PEEK_TIMEOUT_MS 11 * 1000
|
||||
|
||||
static CompassServiceConfig **prv_get_config(PebbleTask task) {
|
||||
CompassServiceConfig **config = NULL;
|
||||
|
||||
if (task == PebbleTask_Unknown) {
|
||||
task = pebble_task_get_current();
|
||||
}
|
||||
|
||||
if (task == PebbleTask_App) {
|
||||
config = app_state_get_compass_config();
|
||||
} else if (task == PebbleTask_Worker) {
|
||||
config = worker_state_get_compass_config();
|
||||
} else if (task == PebbleTask_KernelMain) {
|
||||
config = kernel_applib_get_compass_config();
|
||||
} else {
|
||||
WTF;
|
||||
}
|
||||
|
||||
if (*config == NULL) {
|
||||
// Note that config will never be NULL after grabbing it from an app,
|
||||
// worker, or kernel state. However, the value pointed to it may be
|
||||
// NULL.
|
||||
*config = task_zalloc_check(sizeof(CompassServiceConfig));
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
static void prv_do_data_handle(PebbleEvent *e, void *context) {
|
||||
PebbleCompassDataEvent *m = &e->compass_data;
|
||||
|
||||
CompassHeadingData data = {
|
||||
.is_declination_valid = false,
|
||||
.compass_status = m->calib_status,
|
||||
.magnetic_heading = m->magnetic_heading,
|
||||
.true_heading = m->magnetic_heading
|
||||
};
|
||||
|
||||
CompassServiceConfig *config = *prv_get_config(PebbleTask_Unknown);
|
||||
if (config->compass_cb != NULL) {
|
||||
if (ABS(config->last_angle - data.magnetic_heading) > config->compass_filter) {
|
||||
config->compass_cb(data);
|
||||
config->last_angle = data.magnetic_heading;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Callback for disabling compass service on timeout
|
||||
static void prv_peek_timeout_callback(void *data) {
|
||||
compass_service_unsubscribe();
|
||||
}
|
||||
|
||||
int compass_service_peek(CompassHeadingData *data) {
|
||||
CompassServiceConfig *config = *prv_get_config(PebbleTask_Unknown);
|
||||
if ((config->peek_timer == NULL) && !sys_ecompass_service_subscribed()) {
|
||||
// If we haven't initialized the compass yet by subscribing, do that now.
|
||||
compass_service_subscribe(NULL);
|
||||
}
|
||||
|
||||
sys_ecompass_get_last_heading(data);
|
||||
|
||||
if (data->is_declination_valid) {
|
||||
data->true_heading += config->heading_declination;
|
||||
}
|
||||
|
||||
// 11 second timer to turn off compass, reset timeout every peek
|
||||
if (config->peek_timer == NULL) {
|
||||
config->peek_timer = app_timer_register(PEEK_TIMEOUT_MS,
|
||||
prv_peek_timeout_callback, NULL);
|
||||
} else {
|
||||
app_timer_reschedule(config->peek_timer, PEEK_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int compass_service_set_heading_filter(CompassHeading filter) {
|
||||
if ((filter < 0) || (filter > (TRIG_MAX_ANGLE / 2))) {
|
||||
return (-1);
|
||||
}
|
||||
|
||||
CompassServiceConfig *config = *prv_get_config(PebbleTask_Unknown);
|
||||
config->compass_filter = filter;
|
||||
return (0);
|
||||
}
|
||||
|
||||
void compass_service_subscribe(CompassHeadingHandler handler) {
|
||||
CompassServiceConfig *config = *prv_get_config(PebbleTask_Unknown);
|
||||
|
||||
*config = (const CompassServiceConfig){ 0 };
|
||||
config->compass_cb = handler;
|
||||
|
||||
config->info = (EventServiceInfo) {
|
||||
.type = PEBBLE_COMPASS_DATA_EVENT,
|
||||
.handler = &prv_do_data_handle
|
||||
};
|
||||
|
||||
event_service_client_subscribe(&config->info);
|
||||
}
|
||||
|
||||
void compass_service_unsubscribe(void) {
|
||||
CompassServiceConfig **config = prv_get_config(PebbleTask_Unknown);
|
||||
event_service_client_unsubscribe(&(*config)->info);
|
||||
task_free(*config);
|
||||
*config = NULL;
|
||||
}
|
85
src/fw/applib/compass_service.h
Normal file
85
src/fw/applib/compass_service.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "drivers/mag.h"
|
||||
#include "services/common/ecompass.h"
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup EventService
|
||||
//! @{
|
||||
//! @addtogroup CompassService
|
||||
//!
|
||||
//! \brief The Compass Service combines information from Pebble's accelerometer and
|
||||
//! magnetometer to automatically calibrate
|
||||
//! the compass and transform the raw magnetic field information into a \ref CompassHeading,
|
||||
//! that is an angle to north. It also
|
||||
//! provides magnetic north and information about its status and accuracy through the \ref
|
||||
//! CompassHeadingData structure. The API is designed to also provide true north in a future
|
||||
//! release.
|
||||
//!
|
||||
//! Note that not all platforms have compasses. To check for the presence of a compass at
|
||||
//! compile time for the current platform use the `PBL_COMPASS` define.
|
||||
//!
|
||||
//! To learn more about the Compass Service and how to use it, read the
|
||||
//! <a href="https://developer.getpebble.com/guides/pebble-apps/sensors/magnetometer/">
|
||||
//! Determining Direction</a> guide.
|
||||
//!
|
||||
//! For available code samples, see the
|
||||
//! <a href="https://github.com/pebble-examples/feature-compass">feature-compass</a> example.
|
||||
//!
|
||||
//! @{
|
||||
|
||||
//! Peek at the last recorded reading.
|
||||
//! @param[out] data a pointer to a pre-allocated CompassHeadingData
|
||||
//! @return Always returns 0 to indicate success.
|
||||
int compass_service_peek(CompassHeadingData *data);
|
||||
|
||||
//! Set the minimum angular change required to generate new compass heading events.
|
||||
//! The angular distance is measured relative to the last delivered heading event.
|
||||
//! Use 0 to be notified of all movements.
|
||||
//! Negative values and values > TRIG_MAX_ANGLE / 2 are not valid.
|
||||
//! The default value of this property is TRIG_MAX_ANGLE / 360.
|
||||
//! @return 0, success.
|
||||
//! @return Non-Zero, if filter is invalid.
|
||||
//! @see compass_service_subscribe
|
||||
int compass_service_set_heading_filter(CompassHeading filter);
|
||||
|
||||
//! Callback type for compass heading events
|
||||
//! @param heading copy of last recorded heading
|
||||
typedef void (*CompassHeadingHandler)(CompassHeadingData heading);
|
||||
|
||||
//! Subscribe to the compass heading event service. Once subscribed, the handler
|
||||
//! gets called every time the angular distance relative to the previous value
|
||||
//! exceeds the configured filter.
|
||||
//! @param handler A callback to be executed on heading events
|
||||
//! @see compass_service_set_heading_filter
|
||||
//! @see compass_service_unsubscribe
|
||||
void compass_service_subscribe(CompassHeadingHandler handler);
|
||||
|
||||
//! Unsubscribe from the compass heading event service. Once unsubscribed,
|
||||
//! the previously registered handler will no longer be called.
|
||||
//! @see compass_service_subscribe
|
||||
void compass_service_unsubscribe(void);
|
||||
|
||||
//! @} // end addtogroup CompassService
|
||||
//! @} // end addtogroup EventService
|
||||
//! @} // end addtogroup Foundation
|
33
src/fw/applib/compass_service_private.h
Normal file
33
src/fw/applib/compass_service_private.h
Normal 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 "compass_service.h"
|
||||
|
||||
#include "applib/app_timer.h"
|
||||
#include "applib/event_service_client.h"
|
||||
|
||||
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
CompassHeading compass_filter;
|
||||
int32_t last_angle;
|
||||
CompassHeading heading_declination;
|
||||
|
||||
AppTimer* peek_timer;
|
||||
CompassHeadingHandler compass_cb;
|
||||
|
||||
EventServiceInfo info;
|
||||
} CompassServiceConfig;
|
66
src/fw/applib/compass_service_stub.c
Normal file
66
src/fw/applib/compass_service_stub.c
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#if CAPABILITY_HAS_MAGNETOMETER
|
||||
#error "Use fw/applib/compass_service.c instead if we don't have a magnetometer"
|
||||
#endif
|
||||
|
||||
//! @file compass_service_stub.c
|
||||
//!
|
||||
//! Implements the compass_service for devices that don't actually have a compass. See
|
||||
//! fw/applib/compass_service.c for the real implementation for boards that do have a compass.
|
||||
|
||||
#include "compass_service.h"
|
||||
|
||||
#include "process_management/process_manager.h"
|
||||
|
||||
//! @return which status value we should use to indicate we have no compass
|
||||
static CompassStatus prv_get_status(void) {
|
||||
if (process_manager_compiled_with_legacy2_sdk() ||
|
||||
process_manager_compiled_with_legacy3_sdk()) {
|
||||
// This value is new in 4.x. Use the old CompassStatusDataInvalid value instead for old apps
|
||||
// that may not know how to handle the previously undefined status.
|
||||
return CompassStatusDataInvalid;
|
||||
}
|
||||
|
||||
return CompassStatusUnavailable;
|
||||
}
|
||||
|
||||
int compass_service_peek(CompassHeadingData *data) {
|
||||
*data = (CompassHeadingData) {
|
||||
.compass_status = prv_get_status()
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int compass_service_set_heading_filter(CompassHeading filter) {
|
||||
// Just ignore the filter, we're not going to call the handler regularly anyway.
|
||||
return 0;
|
||||
}
|
||||
|
||||
void compass_service_subscribe(CompassHeadingHandler handler) {
|
||||
CompassHeadingData data = {
|
||||
.compass_status = prv_get_status()
|
||||
};
|
||||
|
||||
// Call the handler once to indicate status
|
||||
handler(data);
|
||||
}
|
||||
|
||||
void compass_service_unsubscribe(void) {
|
||||
// Nothing to do because we never handle the subscribe in the first place
|
||||
}
|
105
src/fw/applib/connection_service.c
Normal file
105
src/fw/applib/connection_service.c
Normal 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 "connection_service.h"
|
||||
#include "connection_service_private.h"
|
||||
|
||||
#include "applib/event_service_client.h"
|
||||
#include "kernel/events.h"
|
||||
#include "services/common/debounced_connection_service.h"
|
||||
#include "services/common/event_service.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "process_state/worker_state/worker_state.h"
|
||||
|
||||
static ConnectionServiceState* prv_get_state(void) {
|
||||
PebbleTask task = pebble_task_get_current();
|
||||
|
||||
if (task == PebbleTask_App) {
|
||||
return app_state_get_connection_service_state();
|
||||
} else if (task == PebbleTask_Worker) {
|
||||
return worker_state_get_connection_service_state();
|
||||
}
|
||||
|
||||
WTF;
|
||||
}
|
||||
|
||||
static void prv_do_handle(PebbleEvent *e, void *context) {
|
||||
ConnectionServiceState *state = prv_get_state();
|
||||
bool connected = e->bluetooth.comm_session_event.is_open;
|
||||
|
||||
ConnectionHandler handler =
|
||||
(e->bluetooth.comm_session_event.is_system ?
|
||||
state->handlers.pebble_app_connection_handler :
|
||||
state->handlers.pebblekit_connection_handler);
|
||||
|
||||
if (handler) {
|
||||
if (!connected) {
|
||||
sys_analytics_inc(ANALYTICS_DEVICE_METRIC_APP_NOTIFIED_DISCONNECTED_COUNT,
|
||||
AnalyticsClient_System);
|
||||
}
|
||||
handler(connected);
|
||||
}
|
||||
}
|
||||
|
||||
bool connection_service_peek_pebble_app_connection(void) {
|
||||
return sys_mobile_app_is_connected_debounced();
|
||||
}
|
||||
|
||||
bool connection_service_peek_pebblekit_connection(void) {
|
||||
return sys_pebblekit_is_connected_debounced();
|
||||
}
|
||||
|
||||
void connection_service_unsubscribe(void) {
|
||||
ConnectionServiceState *state = prv_get_state();
|
||||
event_service_client_unsubscribe(&state->bcs_info);
|
||||
memset(&state->handlers, 0x0, sizeof(state->handlers));
|
||||
}
|
||||
|
||||
void connection_service_subscribe(ConnectionHandlers conn_handlers) {
|
||||
ConnectionServiceState *state = prv_get_state();
|
||||
state->handlers = conn_handlers;
|
||||
event_service_client_subscribe(&state->bcs_info);
|
||||
}
|
||||
|
||||
void connection_service_state_init(ConnectionServiceState *state) {
|
||||
*state = (ConnectionServiceState) {
|
||||
.bcs_info = {
|
||||
.type = PEBBLE_BT_CONNECTION_DEBOUNCED_EVENT,
|
||||
.handler = prv_do_handle,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Deprecated routines kept around for backward compile compatibility
|
||||
|
||||
void bluetooth_connection_service_subscribe(ConnectionHandler handler) {
|
||||
ConnectionHandlers conn_handlers = {
|
||||
.pebble_app_connection_handler = handler,
|
||||
.pebblekit_connection_handler = NULL
|
||||
};
|
||||
connection_service_subscribe(conn_handlers);
|
||||
}
|
||||
|
||||
void bluetooth_connection_service_unsubscribe(void) {
|
||||
connection_service_unsubscribe();
|
||||
}
|
||||
|
||||
bool bluetooth_connection_service_peek(void) {
|
||||
return connection_service_peek_pebble_app_connection();
|
||||
}
|
109
src/fw/applib/connection_service.h
Normal file
109
src/fw/applib/connection_service.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 <stdbool.h>
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup EventService
|
||||
//! @{
|
||||
//! @addtogroup ConnectionService
|
||||
//! \brief Determine what the Pebble watch is connected to
|
||||
//!
|
||||
//! The ConnectionService allows your app to learn about the apps the Pebble
|
||||
//! watch is connected to. You can ask the system for this information at a
|
||||
//! given time or you can register to receive events every time connection or
|
||||
//! disconnection events occur.
|
||||
//!
|
||||
//! It allows you to determine whether the watch is connected to the Pebble
|
||||
//! mobile app by subscribing to the pebble_app_connection_handler or by calling
|
||||
//! the connection_service_peek_pebble_app_connection function. Note that when
|
||||
//! the Pebble app is connected, you can assume PebbleKit JS apps will also be
|
||||
//! running correctly.
|
||||
//!
|
||||
//! The service also allows you to determine if the Pebble watch can establish
|
||||
//! a connection to a PebbleKit companion app by subscribing to the
|
||||
//! pebblekit_connection_handler or by calling the
|
||||
//! connection_service_peek_pebblekit_connection function. Today, due to
|
||||
//! architectural differences between iOS and Android, this will return true
|
||||
//! for Android anytime a connection with the Pebble mobile app is established
|
||||
//! (since PebbleKit messages are routed through the Android app). For iOS,
|
||||
//! this will return true when any PebbleKit companion app has established a
|
||||
//! connection with the Pebble watch (since companion app messages are routed
|
||||
//! directly to the watch)
|
||||
//!
|
||||
//! @{
|
||||
//! Callback type for connection events
|
||||
//! @param connected true on connection, false on disconnection
|
||||
typedef void (*ConnectionHandler)(bool connected);
|
||||
|
||||
//! Query the bluetooth connection service for the current Pebble app connection status
|
||||
//! @return true if the Pebble app is connected, false otherwise
|
||||
bool connection_service_peek_pebble_app_connection(void);
|
||||
|
||||
//! Query the bluetooth connection service for the current PebbleKit connection status
|
||||
//! @return true if a PebbleKit companion app is connected, false otherwise
|
||||
bool connection_service_peek_pebblekit_connection(void);
|
||||
|
||||
typedef struct {
|
||||
//! callback to be executed when the connection state between the watch and
|
||||
//! the phone app has changed. Note, if the phone App is connected, PebbleKit JS apps
|
||||
//! will also be working correctly
|
||||
ConnectionHandler pebble_app_connection_handler;
|
||||
//! ID for callback to be executed on PebbleKit connection event
|
||||
ConnectionHandler pebblekit_connection_handler;
|
||||
} ConnectionHandlers;
|
||||
|
||||
//! Subscribe to the connection event service. Once subscribed, the appropriate
|
||||
//! handler gets called based on the type of connection event and user provided
|
||||
//! handlers
|
||||
//! @param ConnectionHandlers A struct populated with the handlers to
|
||||
//! be called when the specified connection event occurs. If a given handler is
|
||||
//! NULL, no function will be called.
|
||||
void connection_service_subscribe(ConnectionHandlers conn_handlers);
|
||||
|
||||
//! Unsubscribe from the bluetooth event service. Once unsubscribed, the previously registered
|
||||
//! handler will no longer be called.
|
||||
void connection_service_unsubscribe(void);
|
||||
|
||||
//! @deprecated Backwards compatibility typedef for ConnectionHandler. New code
|
||||
//! should use ConnectionHandler directly. This will be removed in a future
|
||||
//! version of the Pebble SDK.
|
||||
typedef ConnectionHandler BluetoothConnectionHandler;
|
||||
|
||||
//! @deprecated Backward compatibility function for
|
||||
//! connection_service_subscribe. New code should use
|
||||
//! connection_service_subscribe directly. This will be removed in a future
|
||||
//! version of the Pebble SDK
|
||||
void bluetooth_connection_service_subscribe(ConnectionHandler handler);
|
||||
|
||||
//! @deprecated Backward compatibility function for
|
||||
//! connection_service_unsubscribe. New code should use
|
||||
//! connection_service_unsubscribe directly. This will be removed in a future
|
||||
//! version of the Pebble SDK
|
||||
void bluetooth_connection_service_unsubscribe(void);
|
||||
|
||||
//! @deprecated Backward compatibility function for
|
||||
//! connection_service_peek_pebble_app_connection. New code should use
|
||||
//! connection_service_peek_pebble_app_connection directly. This will be
|
||||
//! removed in a future version of the Pebble SDK
|
||||
bool bluetooth_connection_service_peek(void);
|
||||
|
||||
//! @} // end addtogroup ConnectionService
|
||||
//! @} // end addtogroup EventService
|
||||
//! @} // end addtogroup Foundation
|
27
src/fw/applib/connection_service_private.h
Normal file
27
src/fw/applib/connection_service_private.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 "applib/event_service_client.h"
|
||||
#include "connection_service.h"
|
||||
|
||||
typedef struct ConnectionServiceState {
|
||||
ConnectionHandlers handlers;
|
||||
EventServiceInfo bcs_info;
|
||||
} ConnectionServiceState;
|
||||
|
||||
void connection_service_state_init(ConnectionServiceState *state);
|
44
src/fw/applib/cpu_cache.c
Normal file
44
src/fw/applib/cpu_cache.c
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 "cpu_cache.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
|
||||
#include "mcu/cache.h"
|
||||
|
||||
DEFINE_SYSCALL(void, memory_cache_flush, void *start, size_t size) {
|
||||
// We need to align the address and size properly for the cache functions. It needs to be done
|
||||
// before we do syscall_assert_userspace_buffer though, because otherwise the user could
|
||||
// potentially abuse this behavior to flush+invalidate kernel memory. Theoretically that
|
||||
// shouldn't actually have any really effect, but it's better to be safe than sorry.
|
||||
// That should only be possible if the user region is not cache aligned, so in any realistic
|
||||
// case this won't even matter.
|
||||
uintptr_t start_addr = (uintptr_t)start;
|
||||
icache_align(&start_addr, &size);
|
||||
dcache_align(&start_addr, &size);
|
||||
start = (void *)start_addr;
|
||||
|
||||
if (PRIVILEGE_WAS_ELEVATED) {
|
||||
syscall_assert_userspace_buffer(start, size);
|
||||
}
|
||||
|
||||
if (dcache_is_enabled()) {
|
||||
dcache_flush(start, size);
|
||||
}
|
||||
if (icache_is_enabled()) {
|
||||
icache_invalidate(start, size);
|
||||
}
|
||||
}
|
39
src/fw/applib/cpu_cache.h
Normal file
39
src/fw/applib/cpu_cache.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 <stddef.h>
|
||||
|
||||
//! @file cpu_cache.h
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup MemoryManagement Memory Management
|
||||
//! \brief Utility functions for managing an application's memory.
|
||||
//!
|
||||
//! @{
|
||||
|
||||
//! Flushes the data cache and invalidates the instruction cache for the given region of memory,
|
||||
//! if necessary. This is only required when your app is loading or modifying code in memory and
|
||||
//! intends to execute it. On some platforms, code executed may be cached internally to improve
|
||||
//! performance. After writing to memory, but before executing, this function must be called in
|
||||
//! order to avoid undefined behavior. On platforms without caching, this performs no operation.
|
||||
//! @param start The beginning of the buffer to flush
|
||||
//! @param size How many bytes to flush
|
||||
void memory_cache_flush(void *start, size_t size);
|
||||
|
||||
//! @} // end addtogroup MemoryManagement
|
||||
//! @} // end addtogroup Foundation
|
56
src/fw/applib/data_logging.c
Normal file
56
src/fw/applib/data_logging.c
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 "data_logging.h"
|
||||
#include "services/normal/data_logging/dls_private.h"
|
||||
|
||||
#include "applib/app_logging.h"
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "syscall/syscall.h"
|
||||
|
||||
DataLoggingSessionRef data_logging_create(uint32_t tag, DataLoggingItemType item_type,
|
||||
uint16_t item_length, bool resume) {
|
||||
void *buffer = NULL;
|
||||
|
||||
// For workers, dls_create_current_process() will create the buffer for us. All others must
|
||||
// allocate the buffer in their own heap (before going into privileged mode).
|
||||
if (pebble_task_get_current() != PebbleTask_Worker) {
|
||||
buffer = applib_malloc(DLS_SESSION_MIN_BUFFER_SIZE);
|
||||
if (!buffer) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "insufficient memory");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the session
|
||||
DataLoggingSessionRef session = sys_data_logging_create(tag, item_type, item_length, buffer,
|
||||
resume);
|
||||
if (session == NULL && buffer != NULL) {
|
||||
applib_free(buffer);
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
void data_logging_finish(DataLoggingSessionRef logging_session) {
|
||||
sys_data_logging_finish(logging_session);
|
||||
}
|
||||
|
||||
DataLoggingResult data_logging_log(DataLoggingSessionRef logging_session, const void *data,
|
||||
uint32_t num_items) {
|
||||
return sys_data_logging_log(logging_session, data, num_items);
|
||||
}
|
||||
|
140
src/fw/applib/data_logging.h
Normal file
140
src/fw/applib/data_logging.h
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup DataLogging
|
||||
//! \brief Enables logging data asynchronously to a mobile app
|
||||
//!
|
||||
//! In Pebble OS, data logging is a data storage and transfer subsystem that allows watchapps to
|
||||
//! save data on non-volatile storage devices when the phone is not available to process it. The
|
||||
//! API provides your watchapp with a mechanism for short-term data buffering for asynchronous data
|
||||
//! transmission to a mobile app.
|
||||
//!
|
||||
//! Using this API, your Pebble watchapp can create an arbitrary number of logs, but you’re
|
||||
//! limited in the amount of storage space you can use. Note that approximately 640K is available
|
||||
//! for data logging, which is shared among all watchapps that use it. This value is subject to
|
||||
//! change in the future. When the data spool is full, an app will start overwriting its own data.
|
||||
//! An app cannot overwrite another apps's data. However, the other app might have 0 bytes for data
|
||||
//! logging.
|
||||
//!
|
||||
//! Your app can log data to a session. Every new block of data is appended to the session.
|
||||
//! The data is then sent to the associated phone application at the earliest convenience.
|
||||
//! If a phone is available, the data is sent directly to the phone. Otherwise, it is saved to the
|
||||
//! watch storage until the watch is connected to a phone.
|
||||
//!
|
||||
//!
|
||||
//! For example:
|
||||
//!
|
||||
//! To create a data logging session for 4-byte unsigned integers with a tag of 0x1234, you would
|
||||
//! do this: \code{.c}
|
||||
//!
|
||||
//! DataLoggingSessionRef logging_session = data_logging_create(0x1234, DATA_LOGGING_UINT, 4,
|
||||
//! false);
|
||||
//!
|
||||
//! // Fake creating some data and logging it to the session.
|
||||
//! uint32_t data[] = { 1, 2, 3};
|
||||
//! data_logging_log(logging_session, &data, 3);
|
||||
//!
|
||||
//! // Fake creating more data and logging that as well.
|
||||
//! uint32_t data2[] = { 1, 2 };
|
||||
//! data_logging_log(logging_session, &data, 2);
|
||||
//!
|
||||
//! // When we don't need to log anything else, we can close off the session.
|
||||
//! data_logging_finish(logging_session);
|
||||
//! \endcode
|
||||
//!
|
||||
//! @{
|
||||
|
||||
//! The different types of session data that Pebble supports. This type describes the type of a
|
||||
//! singular item in the data session. Every item in a given session is the same type and size.
|
||||
typedef enum {
|
||||
//! Array of bytes. Remember that this is the type of a single item in the logging session, so
|
||||
//! using this type means you'll be logging multiple byte arrays (each a fixed length described
|
||||
//! by item_length) for the duration of the session.
|
||||
DATA_LOGGING_BYTE_ARRAY = 0,
|
||||
//! Unsigned integer. This may be a 1, 2, or 4 byte integer depending on the item_length parameter
|
||||
DATA_LOGGING_UINT = 2,
|
||||
//! Signed integer. This may be a 1, 2, or 4 byte integer depending on the item_length parameter
|
||||
DATA_LOGGING_INT = 3,
|
||||
} DataLoggingItemType;
|
||||
|
||||
//! Enumerated values describing the possible outcomes of data logging operations
|
||||
typedef enum {
|
||||
DATA_LOGGING_SUCCESS = 0, //!< Successful operation
|
||||
DATA_LOGGING_BUSY, //!< Someone else is writing to this logging session
|
||||
DATA_LOGGING_FULL, //!< No more space to save data
|
||||
DATA_LOGGING_NOT_FOUND, //!< The logging session does not exist
|
||||
DATA_LOGGING_CLOSED, //!< The logging session was made inactive
|
||||
DATA_LOGGING_INVALID_PARAMS, //!< An invalid parameter was passed to one of the functions
|
||||
DATA_LOGGING_INTERNAL_ERR //!< An internal error occurred
|
||||
} DataLoggingResult;
|
||||
|
||||
typedef void *DataLoggingSessionRef;
|
||||
|
||||
//! Create a new data logging session.
|
||||
//!
|
||||
//! @param tag A tag associated with the logging session.
|
||||
//! @param item_type The type of data stored in this logging session
|
||||
//! @param item_length The size of a single data item in bytes
|
||||
//! @param resume True if we want to look for a logging session of the same tag and
|
||||
//! resume logging to it. If this is false and a session with the specified tag exists, that
|
||||
//! session will be closed and a new session will be opened.
|
||||
//! @return An opaque reference to the data logging session
|
||||
DataLoggingSessionRef data_logging_create(uint32_t tag, DataLoggingItemType item_type,
|
||||
uint16_t item_length, bool resume);
|
||||
|
||||
//! Finish up a data logging_session. Logging data is kept until it has successfully been
|
||||
//! transferred over to the phone, but no data may be added to the session after this function is
|
||||
//! called.
|
||||
//!
|
||||
//! @param logging_session a reference to the data logging session previously allocated using
|
||||
//! data_logging_create
|
||||
void data_logging_finish(DataLoggingSessionRef logging_session);
|
||||
|
||||
//! Add data to the data logging session. If a phone is available, the data is sent directly
|
||||
//! to the phone. Otherwise, it is saved to the watch storage until the watch is connected to a
|
||||
//! phone.
|
||||
//!
|
||||
//! @param logging_session a reference to the data logging session you want to add the data to
|
||||
//! @param data a pointer to the data buffer that contains multiple items
|
||||
//! @param num_items the number of items to log. This means data must be at least
|
||||
//! (num_items * item_length) long in bytes
|
||||
//! @return
|
||||
//! DATA_LOGGING_SUCCESS on success
|
||||
//!
|
||||
//! @return
|
||||
//! DATA_LOGGING_NOT_FOUND if the logging session is invalid
|
||||
//!
|
||||
//! @return
|
||||
//! DATA_LOGGING_CLOSED if the sesion is not active
|
||||
//!
|
||||
//! @return
|
||||
//! DATA_LOGGING_BUSY if the sesion is not available for writing
|
||||
//!
|
||||
//! @return
|
||||
//! DATA_LOGGING_INVALID_PARAMS if num_items is 0 or data is NULL
|
||||
DataLoggingResult data_logging_log(DataLoggingSessionRef logging_session, const void *data,
|
||||
uint32_t num_items);
|
||||
|
||||
//! @} // end addtogroup DataLogging
|
||||
//! @} // end addtogroup Foundation
|
||||
|
97
src/fw/applib/event_service_client.c
Normal file
97
src/fw/applib/event_service_client.c
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* 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 "util/list.h"
|
||||
#include "services/common/event_service.h"
|
||||
#include "kernel/kernel_applib_state.h"
|
||||
|
||||
#include "event_service_client.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "util/list.h"
|
||||
#include "applib/app_logging.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "process_state/worker_state/worker_state.h"
|
||||
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
|
||||
static EventServiceInfo *prv_get_state(void) {
|
||||
PebbleTask task = pebble_task_get_current();
|
||||
if (task == PebbleTask_App) {
|
||||
return app_state_get_event_service_state();
|
||||
} else if (task == PebbleTask_Worker) {
|
||||
return worker_state_get_event_service_state();
|
||||
} else if (task == PebbleTask_KernelMain) {
|
||||
return kernel_applib_get_event_service_state();
|
||||
} else {
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
|
||||
static int event_service_comparator(EventServiceInfo *a, EventServiceInfo *b) {
|
||||
return (b->type - a->type);
|
||||
}
|
||||
|
||||
bool event_service_filter(ListNode *node, void *tp) {
|
||||
EventServiceInfo *info = (EventServiceInfo *)node;
|
||||
uint32_t type = (uint32_t)tp;
|
||||
return (info->type == type);
|
||||
}
|
||||
|
||||
static void do_handle(EventServiceInfo *info, PebbleEvent *e) {
|
||||
PBL_ASSERTN(info->handler != NULL);
|
||||
info->handler(e, info->context);
|
||||
}
|
||||
|
||||
void event_service_client_subscribe(EventServiceInfo *handler) {
|
||||
EventServiceInfo *state = prv_get_state();
|
||||
ListNode *list = &state->list_node;
|
||||
if (list_contains(list, &handler->list_node)) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Event service handler already subscribed");
|
||||
return;
|
||||
}
|
||||
// Add to handlers list
|
||||
list_sorted_add(list, &handler->list_node, (Comparator)event_service_comparator, true);
|
||||
|
||||
sys_event_service_client_subscribe(handler);
|
||||
}
|
||||
|
||||
void event_service_client_unsubscribe(EventServiceInfo *handler) {
|
||||
EventServiceInfo *state = prv_get_state();
|
||||
ListNode *list = &state->list_node;
|
||||
if (!list_contains(list, &handler->list_node)) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Event service handler not subscribed");
|
||||
return;
|
||||
}
|
||||
sys_event_service_client_unsubscribe(state, handler);
|
||||
}
|
||||
|
||||
void event_service_client_handle_event(PebbleEvent *e) {
|
||||
EventServiceInfo *state = prv_get_state();
|
||||
const uintptr_t type = e->type;
|
||||
// find the first callback
|
||||
ListNode *handler = list_find(&state->list_node, event_service_filter, (void *) type);
|
||||
while (handler) {
|
||||
// find the next callback before we call the current one, because the CB may alter the list
|
||||
ListNode *next_handler = list_find_next(handler, event_service_filter, false, (void *) type);
|
||||
do_handle((EventServiceInfo *)handler, e);
|
||||
handler = next_handler;
|
||||
}
|
||||
|
||||
sys_event_service_cleanup(e);
|
||||
}
|
33
src/fw/applib/event_service_client.h
Normal file
33
src/fw/applib/event_service_client.h
Normal 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 "kernel/events.h"
|
||||
|
||||
typedef void (*EventServiceEventHandler)(PebbleEvent *e, void *context);
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
ListNode list_node;
|
||||
PebbleEventType type;
|
||||
EventServiceEventHandler handler;
|
||||
void *context;
|
||||
} EventServiceInfo;
|
||||
|
||||
void event_service_client_subscribe(EventServiceInfo * service_info);
|
||||
void event_service_client_unsubscribe(EventServiceInfo * service_info);
|
||||
void event_service_client_handle_event(PebbleEvent *e);
|
||||
bool event_service_filter(ListNode *node, void *tp);
|
147
src/fw/applib/fonts/codepoint.c
Normal file
147
src/fw/applib/fonts/codepoint.c
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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 "codepoint.h"
|
||||
|
||||
#include "util/size.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#define MAX_LATIN_CODEPOINT 0x02AF
|
||||
#define MIN_SOFTBANK_EMOJI_CODEPOINT 0xE000
|
||||
#define MAX_SOFTBANK_EMOJI_CODEPOINT 0xE537
|
||||
#define MIN_UNIFIED_EMOJI_CODEPOINT 0x1F300
|
||||
#define MAX_UNIFIED_EMOJI_CODEPOINT 0x1F6FF
|
||||
#define MIN_SYMBOLS_CODEPOINT 0x2000
|
||||
#define MAX_SYMBOLS_CODEPOINT 0x2BFF
|
||||
#define MIN_IDEOGRAPH_CODEPOINT 0x2e80
|
||||
#define MIN_SPECIAL_CODEPOINT 0xE0A0
|
||||
#define MAX_SPECIAL_CODEPOINT 0xE0A2
|
||||
#define MIN_SKIN_TONE_CODEPOINT 0x1F3FB
|
||||
#define MAX_SKIN_TONE_CODEPOINT 0x1F3FF
|
||||
|
||||
// Note: Please keep these sorted
|
||||
static const Codepoint NONSTANDARD_EMOJI_CODEPOINTS[] = {
|
||||
0x2192, // rightwards_arrow
|
||||
0x25BA, // black_right_pointing_pointer
|
||||
0x2605, // black_star
|
||||
0x260E, // black_telephone
|
||||
0x261D, // white_up_pointing_index
|
||||
0x263A, // white_smiling_face
|
||||
0x270A, // raised_fist
|
||||
0x270B, // raised_hand
|
||||
0x270C, // victory_hand
|
||||
0x2764, // heavy_black_heart
|
||||
};
|
||||
|
||||
// Note: Please keep these sorted
|
||||
static const Codepoint END_OF_WORD_CODEPOINTS[] = {
|
||||
NULL_CODEPOINT, // 0x0
|
||||
NEWLINE_CODEPOINT, // 0xa
|
||||
SPACE_CODEPOINT, // 0x20
|
||||
HYPHEN_CODEPOINT, // 0x2d
|
||||
ZERO_WIDTH_SPACE_CODEPOINT // 0x200b
|
||||
};
|
||||
|
||||
// Note: Please keep these sorted
|
||||
static const Codepoint FORMATTING_CODEPOINTS[] = {
|
||||
0x7F, // delete
|
||||
0x200C, // zero-width non-joiner
|
||||
0x200D, // zero-width joiner
|
||||
0x200E, // left to right
|
||||
0x200F, // right to left
|
||||
0x202A, // bidirectional - right to left
|
||||
0x202C, // bidirectional - pop direction
|
||||
0x202D, // left to right override
|
||||
0xFE0E, // variation selector 1
|
||||
0xFE0F, // variation selector 2
|
||||
0xFEFF, // zero-width-no-break
|
||||
};
|
||||
|
||||
// Note: Please keep these sorted
|
||||
static const Codepoint ZERO_WIDTH_CODEPOINTS[] = {
|
||||
ZERO_WIDTH_SPACE_CODEPOINT,
|
||||
WORD_JOINER_CODEPOINT,
|
||||
};
|
||||
|
||||
static bool codepoint_in_list(const Codepoint codepoint, const Codepoint *codepoints, size_t size) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
if (codepoints[i] >= codepoint) {
|
||||
return (codepoints[i] == codepoint);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool codepoint_is_formatting_indicator(const Codepoint codepoint) {
|
||||
return codepoint_in_list(codepoint, FORMATTING_CODEPOINTS, ARRAY_LENGTH(FORMATTING_CODEPOINTS));
|
||||
}
|
||||
|
||||
bool codepoint_is_ideograph(const Codepoint codepoint) {
|
||||
if (codepoint > MIN_IDEOGRAPH_CODEPOINT) {
|
||||
// non ideographic characters. This is an approximation that is good enough until
|
||||
// we start supporting some exotic scripts (e.g. tibetan)
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// see http://www.unicode.org/reports/tr14/ for the whole enchilada
|
||||
bool codepoint_is_end_of_word(const Codepoint codepoint) {
|
||||
return codepoint_in_list(codepoint, END_OF_WORD_CODEPOINTS, ARRAY_LENGTH(END_OF_WORD_CODEPOINTS));
|
||||
}
|
||||
|
||||
// see http://unicode.org/reports/tr51/ section 2.2 "Diversity"
|
||||
bool codepoint_is_skin_tone_modifier(const Codepoint codepoint) {
|
||||
return (codepoint >= MIN_SKIN_TONE_CODEPOINT && codepoint <= MAX_SKIN_TONE_CODEPOINT);
|
||||
}
|
||||
|
||||
bool codepoint_should_skip(const Codepoint codepoint) {
|
||||
return ((codepoint < 0x20 && codepoint != NEWLINE_CODEPOINT) ||
|
||||
(codepoint_is_skin_tone_modifier(codepoint)));
|
||||
}
|
||||
|
||||
bool codepoint_is_zero_width(const Codepoint codepoint) {
|
||||
return codepoint_in_list(codepoint, ZERO_WIDTH_CODEPOINTS, ARRAY_LENGTH(ZERO_WIDTH_CODEPOINTS));
|
||||
}
|
||||
|
||||
bool codepoint_is_latin(const Codepoint codepoint) {
|
||||
return (codepoint <= MAX_LATIN_CODEPOINT ||
|
||||
(codepoint >= MIN_SYMBOLS_CODEPOINT &&
|
||||
codepoint <= MAX_SYMBOLS_CODEPOINT));
|
||||
}
|
||||
|
||||
bool codepoint_is_emoji(const Codepoint codepoint) {
|
||||
// search for the codepoint in the list of nonstandard emoji codepoints first.
|
||||
const bool found = codepoint_in_list(codepoint,
|
||||
NONSTANDARD_EMOJI_CODEPOINTS,
|
||||
ARRAY_LENGTH(NONSTANDARD_EMOJI_CODEPOINTS));
|
||||
if (found) {
|
||||
return true;
|
||||
} else {
|
||||
return ((codepoint >= MIN_SOFTBANK_EMOJI_CODEPOINT &&
|
||||
codepoint <= MAX_SOFTBANK_EMOJI_CODEPOINT) ||
|
||||
(codepoint >= MIN_UNIFIED_EMOJI_CODEPOINT &&
|
||||
codepoint <= MAX_UNIFIED_EMOJI_CODEPOINT));
|
||||
}
|
||||
}
|
||||
|
||||
bool codepoint_is_special(const Codepoint codepoint) {
|
||||
return (codepoint >= MIN_SPECIAL_CODEPOINT &&
|
||||
codepoint <= MAX_SPECIAL_CODEPOINT);
|
||||
}
|
54
src/fw/applib/fonts/codepoint.h
Normal file
54
src/fw/applib/fonts/codepoint.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef uint32_t Codepoint;
|
||||
|
||||
#define EM_DASH "—"
|
||||
#define EN_DASH "–"
|
||||
|
||||
#define ELLIPSIS_CODEPOINT 0x2026
|
||||
#define HYPHEN_CODEPOINT 0x002D
|
||||
#define MINUS_SIGN_CODEPOINT 0x2212
|
||||
#define SPACE_CODEPOINT ' '
|
||||
#define NEWLINE_CODEPOINT '\n'
|
||||
#define NULL_CODEPOINT '\0'
|
||||
#define ZERO_WIDTH_SPACE_CODEPOINT 0x200B
|
||||
#define WORD_JOINER_CODEPOINT 0x2060
|
||||
|
||||
bool codepoint_is_formatting_indicator(const Codepoint codepoint);
|
||||
|
||||
bool codepoint_is_skin_tone_modifier(const Codepoint codepoint);
|
||||
|
||||
bool codepoint_is_end_of_word(const Codepoint codepoint);
|
||||
|
||||
bool codepoint_is_ideograph(const Codepoint codepoint);
|
||||
|
||||
bool codepoint_should_skip(const Codepoint codepoint);
|
||||
|
||||
bool codepoint_is_zero_width(const Codepoint codepoint);
|
||||
|
||||
bool codepoint_is_latin(const Codepoint codepoint);
|
||||
|
||||
bool codepoint_is_emoji(const Codepoint codepoint);
|
||||
|
||||
// This is a least dirty hack to enable special rendering when a special codepoint is hit in the
|
||||
// text being rendered
|
||||
bool codepoint_is_special(const Codepoint codepoint);
|
147
src/fw/applib/fonts/fonts.c
Normal file
147
src/fw/applib/fonts/fonts.c
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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 "fonts.h"
|
||||
#include "fonts_private.h"
|
||||
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "applib/applib_resource.h"
|
||||
#include "applib/graphics/text.h"
|
||||
#include "applib/graphics/text_resources.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "resource/resource.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/list.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
GFont fonts_get_fallback_font(void) {
|
||||
// No font key for the fallback font
|
||||
return sys_font_get_system_font(NULL);
|
||||
}
|
||||
|
||||
GFont fonts_get_system_font(const char *font_key) {
|
||||
static const char bitham_alias[] = "RESOURCE_ID_GOTHAM";
|
||||
static const char bitham_prefix[] = "RESOURCE_ID_BITHAM";
|
||||
static const size_t bitham_alias_len = sizeof(bitham_alias)-1;
|
||||
static const size_t bitham_prefix_len = sizeof(bitham_prefix)-1;
|
||||
|
||||
GFont res = sys_font_get_system_font(font_key);
|
||||
|
||||
// maybe they wanted a renamed font
|
||||
if (NULL == res && 0 == strncmp(font_key, bitham_alias, bitham_alias_len)) {
|
||||
char new_font_key[bitham_prefix_len - bitham_alias_len + strlen(font_key) + 1];
|
||||
strncpy(new_font_key, bitham_prefix, bitham_prefix_len);
|
||||
strcpy(new_font_key+bitham_prefix_len, font_key+bitham_alias_len);
|
||||
// let's try again
|
||||
res = sys_font_get_system_font(new_font_key);
|
||||
}
|
||||
|
||||
if (NULL == res) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Getting fallback font instead");
|
||||
res = fonts_get_fallback_font();
|
||||
PBL_ASSERTN(res);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
GFont fonts_load_custom_font(ResHandle handle) {
|
||||
GFont res = fonts_load_custom_font_system(sys_get_current_resource_num(), (uint32_t)handle);
|
||||
if (res == NULL) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Getting fallback font instead");
|
||||
res = sys_font_get_system_font("RESOURCE_ID_GOTHIC_14");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
GFont fonts_load_custom_font_system(ResAppNum app_num, uint32_t resource_id) {
|
||||
if (resource_id == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Tried to load a font from a NULL resource");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FontInfo *font_info = applib_type_malloc(FontInfo);
|
||||
if (font_info == NULL) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Couldn't malloc space for new font");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool result = text_resources_init_font(app_num, resource_id,
|
||||
0 /* extended resource */, font_info);
|
||||
|
||||
if (!result) {
|
||||
// couldn't init the font
|
||||
applib_free(font_info);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return font_info;
|
||||
}
|
||||
|
||||
void fonts_unload_custom_font(GFont font) {
|
||||
// fonts_load_custom_font can return gothic 14 if loading their font didn't
|
||||
// work for whatever reason. We don't let the app know that it failed, so it makes sense that
|
||||
// they'll later try to unload this returned pointer at a later point. We don't actually want
|
||||
// to free this, so just no-op.
|
||||
if (font == sys_font_get_system_font("RESOURCE_ID_GOTHIC_14")) {
|
||||
return;
|
||||
}
|
||||
|
||||
FontInfo *font_info = (FontInfo*) font;
|
||||
applib_free(font_info);
|
||||
}
|
||||
|
||||
#if !RECOVERY_FW
|
||||
static const struct {
|
||||
const char *key_name;
|
||||
uint8_t height;
|
||||
} s_emoji_fonts[] = {
|
||||
// Keep this sorted in descending order
|
||||
{ FONT_KEY_GOTHIC_28_EMOJI, 28 },
|
||||
{ FONT_KEY_GOTHIC_24_EMOJI, 24 },
|
||||
{ FONT_KEY_GOTHIC_18_EMOJI, 18 },
|
||||
{ FONT_KEY_GOTHIC_14_EMOJI, 14 },
|
||||
};
|
||||
|
||||
FontInfo *fonts_get_system_emoji_font_for_size(unsigned int font_height) {
|
||||
for (uint32_t i = 0; i < ARRAY_LENGTH(s_emoji_fonts); i++) {
|
||||
if (font_height == s_emoji_fonts[i].height) {
|
||||
return sys_font_get_system_font(s_emoji_fonts[i].key_name);
|
||||
}
|
||||
}
|
||||
// Didn't find a suitable emoji font
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint8_t fonts_get_font_height(GFont font) {
|
||||
FontInfo* fontinfo = (FontInfo*) font;
|
||||
return fontinfo->max_height;
|
||||
}
|
||||
|
||||
int16_t fonts_get_font_cap_offset(GFont font) {
|
||||
if (!font) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// FIXME PBL-25709: Actually use font-specific caps and also provide function for baseline offsets
|
||||
return (int16_t)(((int16_t)font->max_height) * 22 / 100);
|
||||
}
|
86
src/fw/applib/fonts/fonts.h
Normal file
86
src/fw/applib/fonts/fonts.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 "applib/fonts/fonts_private.h"
|
||||
#include "resource/resource.h"
|
||||
|
||||
#if !defined(SDK)
|
||||
#include "font_resource_keys.auto.h"
|
||||
#endif
|
||||
|
||||
//! @addtogroup Graphics
|
||||
//! @{
|
||||
//! @addtogroup Fonts
|
||||
//! @see \ref TextLayer
|
||||
//! @see \ref TextDrawing
|
||||
//! @see \ref text_layer_set_font
|
||||
//! @see \ref graphics_draw_text
|
||||
//! @{
|
||||
|
||||
//! Pointer to opaque font data structure.
|
||||
//! @see \ref fonts_load_custom_font()
|
||||
//! @see \ref text_layer_set_font()
|
||||
//! @see \ref graphics_draw_text()
|
||||
typedef FontInfo* GFont;
|
||||
|
||||
//! @internal
|
||||
//! Gets the fallback system font (14pt Raster Gothic)
|
||||
GFont fonts_get_fallback_font(void);
|
||||
|
||||
//! Loads a system font corresponding to the specified font key.
|
||||
//! @param font_key The string key of the font to load. See
|
||||
//! <a href="https://developer.pebble.com/guides/app-resources/system-fonts/">System
|
||||
//! Fonts</a> guide for a list of system fonts.
|
||||
//! @return An opaque pointer to the loaded font, or, a pointer to the default
|
||||
//! (fallback) font if the specified font cannot be loaded.
|
||||
//! @note This may load a font from the flash peripheral into RAM.
|
||||
GFont fonts_get_system_font(const char *font_key);
|
||||
|
||||
GFont fonts_get_system_emoji_font_for_size(unsigned int font_size);
|
||||
|
||||
//! Loads a custom font.
|
||||
//! @param handle The resource handle of the font to load. See resource_ids.auto.h
|
||||
//! for a list of resource IDs, and use \ref resource_get_handle() to obtain the resource handle.
|
||||
//! @return An opaque pointer to the loaded font, or a pointer to the default
|
||||
//! (fallback) font if the specified font cannot be loaded.
|
||||
//! @see Read the <a href="http://developer.getpebble.com/guides/pebble-apps/resources/">App
|
||||
//! Resources</a> guide on how to embed a font into your app.
|
||||
//! @note this may load a font from the flash peripheral into RAM.
|
||||
GFont fonts_load_custom_font(ResHandle handle);
|
||||
|
||||
//! @internal
|
||||
//! firmware-only access version of fonts_load_custom_font
|
||||
GFont fonts_load_custom_font_system(ResAppNum app_num, uint32_t resource_id);
|
||||
|
||||
//! Unloads the specified custom font and frees the memory that is occupied by
|
||||
//! it.
|
||||
//! @note When an application exits, the system automatically unloads all fonts
|
||||
//! that have been loaded.
|
||||
//! @param font The font to unload.
|
||||
void fonts_unload_custom_font(GFont font);
|
||||
|
||||
//! @internal
|
||||
uint8_t fonts_get_font_height(GFont font);
|
||||
|
||||
//! @internal
|
||||
// Get the vertical offset of the top of the font's caps from the origin of a text frame
|
||||
// Currently only an approximation, see PBL-25709
|
||||
int16_t fonts_get_font_cap_offset(GFont font);
|
||||
|
||||
//! @} // end addtogroup Fonts
|
||||
//! @} // end addtogroup Graphics
|
98
src/fw/applib/fonts/fonts_private.h
Normal file
98
src/fw/applib/fonts/fonts_private.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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 "resource/resource.h"
|
||||
#include "util/attributes.h"
|
||||
|
||||
//
|
||||
// Definitions only for font loading and text rendering
|
||||
//
|
||||
|
||||
// Initial version
|
||||
#define FONT_VERSION_1 1
|
||||
// 4 byte codepoints in offset table
|
||||
#define FONT_VERSION_2 2
|
||||
// feature bits: 2 or 4 byte offsets, RLE encoding
|
||||
#define FONT_VERSION_3 3
|
||||
#define FEATURE_OFFSET_16 (1 << 0)
|
||||
#define FEATURE_RLE4 (1 << 1)
|
||||
|
||||
// HACK ALERT: Store the v3 FontMetaDataV3 feature bits in the top two bits of FontMetaData
|
||||
// version field. We need this information at the lowest levels and can't extend FontMetaData
|
||||
// for legacy support reasons.
|
||||
#define FONT_VERSION(_version) ((_version) & 0x3F)
|
||||
#define HAS_FEATURE(_version, _feature) ((_version) & (_feature))
|
||||
#define VERSION_FIELD_FEATURE_OFFSET_16 (1 << 7)
|
||||
#define VERSION_FIELD_FEATURE_RLE4 (1 << 6)
|
||||
|
||||
|
||||
// There are now three versions of the FontMetaData structure: V1 (formerly known as 'legacy'), V2
|
||||
// (still known as FontMetaData), and V3 (know as V3). We can't change the stack/memory usage
|
||||
// until we drop support for existing applications so we can't simply use V3 as the base.
|
||||
//
|
||||
// The name 'FontMetaData' is retained instead of a more consistent 'FontMetaDataV2' because the
|
||||
// uses of V1 and V3 are localized but 'FontMetaData' is used in many places, requiring many ugly
|
||||
// changes.
|
||||
typedef struct PACKED {
|
||||
uint8_t version;
|
||||
uint8_t max_height;
|
||||
uint16_t number_of_glyphs;
|
||||
uint16_t wildcard_codepoint;
|
||||
uint8_t hash_table_size;
|
||||
uint8_t codepoint_bytes;
|
||||
uint8_t size;
|
||||
uint8_t features;
|
||||
} FontMetaDataV3;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint8_t version;
|
||||
uint8_t max_height;
|
||||
uint16_t number_of_glyphs;
|
||||
uint16_t wildcard_codepoint;
|
||||
uint8_t hash_table_size;
|
||||
uint8_t codepoint_bytes;
|
||||
} FontMetaData;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint8_t version;
|
||||
uint8_t max_height;
|
||||
uint16_t number_of_glyphs;
|
||||
uint16_t wildcard_codepoint;
|
||||
} FontMetaDataV1;
|
||||
|
||||
typedef struct {
|
||||
FontMetaData md;
|
||||
ResAppNum app_num;
|
||||
uint32_t resource_id;
|
||||
} FontResource;
|
||||
|
||||
typedef struct {
|
||||
bool loaded;
|
||||
bool extended;
|
||||
uint8_t max_height;
|
||||
FontResource base;
|
||||
FontResource extension;
|
||||
ResourceCallbackHandle extension_changed_cb;
|
||||
} FontInfo;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint8_t hash;
|
||||
uint8_t count;
|
||||
uint16_t offset;
|
||||
} FontHashTableEntry;
|
||||
|
194
src/fw/applib/graphics/1_bit/bitblt_private.c
Normal file
194
src/fw/applib/graphics/1_bit/bitblt_private.c
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* 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 "../bitblt_private.h"
|
||||
|
||||
#include "applib/app_logging.h"
|
||||
#include "applib/graphics/graphics.h"
|
||||
#include "applib/graphics/graphics_private.h"
|
||||
#include "applib/graphics/gtypes.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/bitset.h"
|
||||
#include "util/graphics.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#define MAX_SUPPORTED_PALETTE_ENTRIES 4
|
||||
|
||||
// stores transparent masks + color patterns for even+odd scanlines
|
||||
typedef struct {
|
||||
// true, if color entry will be visible on 1bit, false otherwise
|
||||
bool transparent_mask[MAX_SUPPORTED_PALETTE_ENTRIES];
|
||||
// a 32bit value you can OR into the 1bit destination for each color entry
|
||||
uint32_t palette_pattern[MAX_SUPPORTED_PALETTE_ENTRIES];
|
||||
} RowLookUp;
|
||||
|
||||
typedef RowLookUp TwoRowLookUp[2];
|
||||
|
||||
T_STATIC void prv_apply_tint_color(GColor *color, GColor tint_color) {
|
||||
// tint_color.a is always 0 or 3
|
||||
if (tint_color.a != 0) {
|
||||
tint_color.a = (*color).a;
|
||||
*color = tint_color;
|
||||
}
|
||||
}
|
||||
|
||||
T_STATIC void prv_calc_two_row_look_ups(TwoRowLookUp *look_up,
|
||||
GCompOp compositing_mode,
|
||||
const GColor8 *palette,
|
||||
uint8_t num_entries,
|
||||
GColor tint_color) {
|
||||
for (unsigned int palette_index = 0; palette_index < num_entries; palette_index++) {
|
||||
GColor color = palette[palette_index];
|
||||
// gcolor_get_grayscale will convert any color with an alpha less than 2 to clear
|
||||
// alpha should be ignored in the case of GCompOpAssign so the alpha is set to 3
|
||||
if (compositing_mode == GCompOpAssign) {
|
||||
color.a = 3;
|
||||
} else if (compositing_mode == GCompOpTint) {
|
||||
prv_apply_tint_color(&color, tint_color);
|
||||
} else if (compositing_mode == GCompOpTintLuminance) {
|
||||
color = gcolor_tint_using_luminance_and_multiply_alpha(color, tint_color);
|
||||
}
|
||||
color = gcolor_get_grayscale(color);
|
||||
for (unsigned int row_number = 0; row_number < ARRAY_LENGTH(*look_up); row_number++) {
|
||||
(*look_up)[row_number].palette_pattern[palette_index] =
|
||||
graphics_private_get_1bit_grayscale_pattern(color, row_number);
|
||||
(*look_up)[row_number].transparent_mask[palette_index] =
|
||||
gcolor_is_transparent(color) ? false : true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bitblt_bitmap_into_bitmap_tiled_palette_to_1bit(GBitmap* dest_bitmap,
|
||||
const GBitmap* src_bitmap, GRect dest_rect,
|
||||
GPoint src_origin_offset,
|
||||
GCompOp compositing_mode,
|
||||
GColor tint_color) {
|
||||
if (!src_bitmap->palette) {
|
||||
return;
|
||||
}
|
||||
const int8_t dest_begin_x = (dest_rect.origin.x / 32);
|
||||
const uint32_t * const dest_block_x_begin = ((uint32_t *)dest_bitmap->addr) + dest_begin_x;
|
||||
const int dest_row_length_words = (dest_bitmap->row_size_bytes / 4);
|
||||
// The number of bits between the beginning of dest_block and
|
||||
// the beginning of the nearest 32-bit block:
|
||||
const uint8_t dest_shift_at_line_begin = (dest_rect.origin.x % 32);
|
||||
|
||||
const int16_t src_begin_x = src_bitmap->bounds.origin.x;
|
||||
const int16_t src_begin_y = src_bitmap->bounds.origin.y;
|
||||
// The bounds size is relative to the bounds origin, but the offset is within the origin. This
|
||||
// means that the end coordinates may need to be adjusted.
|
||||
const int16_t src_end_x = grect_get_max_x(&src_bitmap->bounds);
|
||||
const int16_t src_end_y = grect_get_max_y(&src_bitmap->bounds);
|
||||
|
||||
// how many 32-bit blocks do we need to bitblt on this row:
|
||||
const int16_t dest_end_x = grect_get_max_x(&dest_rect);
|
||||
const int16_t dest_y_end = grect_get_max_y(&dest_rect);
|
||||
const uint8_t num_dest_blocks_per_row = (dest_end_x / 32) +
|
||||
((dest_end_x % 32) ? 1 : 0) - dest_begin_x;
|
||||
|
||||
const GColor *palette = src_bitmap->palette;
|
||||
const uint8_t *src = src_bitmap->addr;
|
||||
const uint8_t src_bpp = gbitmap_get_bits_per_pixel(gbitmap_get_format(src_bitmap));
|
||||
const uint8_t src_palette_size = 1 << src_bpp;
|
||||
PBL_ASSERTN(src_palette_size <= MAX_SUPPORTED_PALETTE_ENTRIES);
|
||||
// The bitblt loops:
|
||||
int16_t src_y = src_begin_y + src_origin_offset.y;
|
||||
int16_t dest_y = dest_rect.origin.y;
|
||||
|
||||
TwoRowLookUp look_ups;
|
||||
prv_calc_two_row_look_ups(&look_ups, compositing_mode, palette, src_palette_size, tint_color);
|
||||
|
||||
while (dest_y < dest_y_end) {
|
||||
if (src_y >= src_end_y) {
|
||||
src_y = src_begin_y;
|
||||
}
|
||||
uint8_t dest_shift = dest_shift_at_line_begin;
|
||||
|
||||
RowLookUp look_up = look_ups[dest_y % 2];
|
||||
|
||||
int16_t src_x = src_begin_x + src_origin_offset.x;
|
||||
uint8_t row_bits_left = dest_rect.size.w;
|
||||
uint32_t *dest_block = (uint32_t *)dest_block_x_begin + (dest_y * dest_row_length_words);
|
||||
|
||||
const uint32_t *dest_block_end = dest_block + num_dest_blocks_per_row;
|
||||
|
||||
while (dest_block != dest_block_end) {
|
||||
uint8_t dest_x = dest_shift;
|
||||
while (dest_x < 32 && row_bits_left > 0) {
|
||||
if (src_x >= src_end_x) { // Wrap horizontally
|
||||
src_x = src_begin_x;
|
||||
}
|
||||
uint8_t cindex = raw_image_get_value_for_bitdepth(src, src_x, src_y,
|
||||
src_bitmap->row_size_bytes, src_bpp);
|
||||
uint32_t mask = 0;
|
||||
bitset32_update(&mask, dest_x, look_up.transparent_mask[cindex]);
|
||||
|
||||
// This can be optimized by performing actions on the current word all at once
|
||||
// instead of iterating through each pixel
|
||||
switch (compositing_mode) {
|
||||
case GCompOpAssign:
|
||||
case GCompOpSet:
|
||||
case GCompOpTint:
|
||||
case GCompOpTintLuminance:
|
||||
*dest_block = (*dest_block & ~mask) | (look_up.palette_pattern[cindex] & mask);
|
||||
break;
|
||||
default:
|
||||
PBL_LOG(LOG_LEVEL_DEBUG,
|
||||
"Only the assign, set and tint modes are allowed for palettized bitmaps");
|
||||
return;
|
||||
}
|
||||
dest_x++;
|
||||
row_bits_left--;
|
||||
src_x++;
|
||||
}
|
||||
dest_shift = 0;
|
||||
dest_block++;
|
||||
}
|
||||
dest_y++;
|
||||
src_y++;
|
||||
}
|
||||
}
|
||||
|
||||
void bitblt_bitmap_into_bitmap_tiled(GBitmap* dest_bitmap, const GBitmap* src_bitmap,
|
||||
GRect dest_rect, GPoint src_origin_offset,
|
||||
GCompOp compositing_mode, GColor8 tint_color) {
|
||||
if (bitblt_compositing_mode_is_noop(compositing_mode, tint_color)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GBitmapFormat src_fmt = gbitmap_get_format(src_bitmap);
|
||||
GBitmapFormat dest_fmt = gbitmap_get_format(dest_bitmap);
|
||||
if (dest_fmt != GBitmapFormat1Bit) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (src_fmt) {
|
||||
case GBitmapFormat1Bit:
|
||||
bitblt_bitmap_into_bitmap_tiled_1bit_to_1bit(dest_bitmap, src_bitmap, dest_rect,
|
||||
src_origin_offset, compositing_mode, tint_color);
|
||||
break;
|
||||
case GBitmapFormat1BitPalette:
|
||||
case GBitmapFormat2BitPalette:
|
||||
bitblt_bitmap_into_bitmap_tiled_palette_to_1bit(dest_bitmap, src_bitmap, dest_rect,
|
||||
src_origin_offset, compositing_mode,
|
||||
tint_color);
|
||||
break;
|
||||
default:
|
||||
APP_LOG(APP_LOG_LEVEL_DEBUG, "Only 1 and 2 bit palettized images can be displayed.");
|
||||
return;
|
||||
}
|
||||
}
|
61
src/fw/applib/graphics/1_bit/framebuffer.c
Normal file
61
src/fw/applib/graphics/1_bit/framebuffer.c
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 "applib/graphics/framebuffer.h"
|
||||
|
||||
#include "applib/graphics/gtypes.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/bitset.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
volatile const int FrameBuffer_MaxX = DISP_COLS;
|
||||
volatile const int FrameBuffer_MaxY = DISP_ROWS;
|
||||
volatile const int FrameBuffer_BytesPerRow = FRAMEBUFFER_BYTES_PER_ROW;
|
||||
|
||||
uint32_t *framebuffer_get_line(FrameBuffer *f, uint8_t y) {
|
||||
PBL_ASSERTN(y < f->size.h);
|
||||
|
||||
return f->buffer + (y * ((f->size.w / 32) + 1));
|
||||
}
|
||||
|
||||
inline size_t framebuffer_get_size_bytes(FrameBuffer *f) {
|
||||
// TODO: Make FRAMEBUFFER_SIZE_BYTES a macro which takes the cols and rows if we ever want to
|
||||
// support different size framebuffers for watches which have native 1-bit framebuffers where the
|
||||
// size is not just COLS * ROWS.
|
||||
return FRAMEBUFFER_SIZE_BYTES;
|
||||
}
|
||||
|
||||
void framebuffer_clear(FrameBuffer *f) {
|
||||
memset(f->buffer, 0xff, framebuffer_get_size_bytes(f));
|
||||
framebuffer_dirty_all(f);
|
||||
f->is_dirty = true;
|
||||
}
|
||||
|
||||
void framebuffer_mark_dirty_rect(FrameBuffer *f, GRect rect) {
|
||||
if (!f->is_dirty) {
|
||||
f->dirty_rect = rect;
|
||||
} else {
|
||||
f->dirty_rect = grect_union(&f->dirty_rect, &rect);
|
||||
}
|
||||
|
||||
const GRect clip_rect = (GRect) { GPointZero, f->size };
|
||||
grect_clip(&f->dirty_rect, &clip_rect);
|
||||
|
||||
f->is_dirty = true;
|
||||
}
|
32
src/fw/applib/graphics/1_bit/framebuffer.h
Normal file
32
src/fw/applib/graphics/1_bit/framebuffer.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
#define FRAMEBUFFER_WORDS_PER_ROW ((DISP_COLS / 32) + 1)
|
||||
#define FRAMEBUFFER_SIZE_DWORDS (DISP_ROWS * FRAMEBUFFER_WORDS_PER_ROW)
|
||||
|
||||
#define FRAMEBUFFER_BYTES_PER_ROW (FRAMEBUFFER_WORDS_PER_ROW * 4)
|
||||
#define FRAMEBUFFER_SIZE_BYTES (DISP_ROWS * FRAMEBUFFER_BYTES_PER_ROW)
|
||||
|
||||
typedef struct FrameBuffer {
|
||||
uint32_t buffer[FRAMEBUFFER_SIZE_DWORDS];
|
||||
GSize size;
|
||||
GRect dirty_rect; //<! Smallest rect covering all dirty pixels.
|
||||
bool is_dirty;
|
||||
} FrameBuffer;
|
||||
|
||||
uint32_t* framebuffer_get_line(FrameBuffer* f, uint8_t y);
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue