mirror of
https://github.com/google/pebble.git
synced 2025-05-30 06:53:12 +00:00
473 lines
18 KiB
C
473 lines
18 KiB
C
/*
|
|
* 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;
|
|
}
|