mirror of
https://github.com/google/pebble.git
synced 2025-05-04 17:01:40 -04:00
566 lines
20 KiB
C
566 lines
20 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 "dls_private.h"
|
|
#include "dls_endpoint.h"
|
|
#include "dls_list.h"
|
|
#include "dls_storage.h"
|
|
|
|
#include "services/common/analytics/analytics.h"
|
|
#include "services/common/comm_session/protocol.h"
|
|
#include "services/common/comm_session/session_send_buffer.h"
|
|
#include "services/common/system_task.h"
|
|
#include "services/common/new_timer/new_timer.h"
|
|
#include "services/normal/data_logging/data_logging_service.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
#include "system/logging.h"
|
|
#include "system/passert.h"
|
|
#include "util/attributes.h"
|
|
#include "util/legacy_checksum.h"
|
|
#include "util/math.h"
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include "FreeRTOS.h"
|
|
#include "timers.h"
|
|
|
|
typedef struct {
|
|
ListNode list_node;
|
|
DataLoggingSession *session;
|
|
// Session metadata to make sure the session pointer corresponds to
|
|
// the same session that was added to the reopen list. This guards
|
|
// against the session being destroyed and another getting allocated
|
|
// to the same address.
|
|
Uuid app_uuid;
|
|
time_t timestamp;
|
|
uint32_t tag;
|
|
} DataLoggingReopenEntry;
|
|
|
|
static struct {
|
|
PebbleMutex * mutex;
|
|
TimerID ack_timer;
|
|
bool report_in_progress;
|
|
} s_endpoint_data;
|
|
|
|
typedef struct PACKED {
|
|
uint8_t command;
|
|
uint8_t session_id;
|
|
} DataLoggingCloseSessionMessage;
|
|
|
|
typedef struct PACKED {
|
|
uint8_t command;
|
|
uint8_t session_id;
|
|
Uuid app_uuid;
|
|
uint32_t timestamp;
|
|
uint32_t logging_session_tag;
|
|
DataLoggingItemType data_item_type:8;
|
|
uint16_t data_item_size;
|
|
} DataLoggingOpenSessionMessage;
|
|
|
|
static const uint16_t ENDPOINT_ID_DATA_LOGGING = 0x1a7a;
|
|
|
|
#define ACK_NACK_TIMEOUT_TICKS (30 * RTC_TICKS_HZ)
|
|
|
|
static const uint8_t MAX_NACK_COUNT = 20;
|
|
|
|
static void reschedule_ack_timeout(void);
|
|
|
|
static void update_session_state(DataLoggingSession *session, DataLoggingSessionCommState new_state,
|
|
bool reschedule) {
|
|
session->comm.state = new_state;
|
|
|
|
switch (new_state) {
|
|
case DataLoggingSessionCommStateOpening:
|
|
case DataLoggingSessionCommStateSending:
|
|
// These states need an ack from the phone.
|
|
session->comm.ack_timeout = rtc_get_ticks() + ACK_NACK_TIMEOUT_TICKS;
|
|
break;
|
|
case DataLoggingSessionCommStateIdle:
|
|
session->comm.ack_timeout = 0;
|
|
break;
|
|
}
|
|
|
|
if (reschedule) {
|
|
reschedule_ack_timeout();
|
|
}
|
|
}
|
|
|
|
static void send_timeout_msg(void *session_id_param) {
|
|
uint8_t session_id = (uint8_t)(uintptr_t)session_id_param;
|
|
CommSession *session = comm_session_get_system_session();
|
|
if (!session) {
|
|
// timed out because of lost connection
|
|
return;
|
|
}
|
|
|
|
DataLoggingSession *logging_session = dls_list_find_by_session_id(session_id);
|
|
|
|
struct PACKED {
|
|
uint8_t command;
|
|
uint8_t session_id;
|
|
} msg = {
|
|
.command = DataLoggingEndpointCmdTimeout,
|
|
.session_id = logging_session->comm.session_id,
|
|
};
|
|
|
|
comm_session_send_data(session, ENDPOINT_ID_DATA_LOGGING, (uint8_t *)&msg,
|
|
sizeof(msg), COMM_SESSION_DEFAULT_TIMEOUT);
|
|
}
|
|
|
|
static bool check_ack_timeout_for_session(DataLoggingSession *session, void *data) {
|
|
RtcTicks *current_ticks = (RtcTicks*) data;
|
|
|
|
if (session->comm.ack_timeout != 0 && session->comm.ack_timeout <= *current_ticks) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "session %"PRIu8" timeout", session->comm.session_id);
|
|
|
|
// Send timeout msg from system task because it could take a while and also require
|
|
// more stack space than provided by the timer task.
|
|
system_task_add_callback(send_timeout_msg, (void*)(uintptr_t)(session->comm.session_id));
|
|
|
|
// Set reschedule to false because: 1.) we don't need to reschedule the timer since all
|
|
// we did was process one that already expired, 2.) it can cause an infinite recursion
|
|
// because reschedule_ack_timeout() will call check_ack_timeout() (which we are already in) if
|
|
// any other timers have already expired.
|
|
update_session_state(session, DataLoggingSessionCommStateIdle, false /*reschedule*/);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void check_ack_timeout(void) {
|
|
RtcTicks current_ticks = rtc_get_ticks();
|
|
|
|
dls_list_for_each_session(check_ack_timeout_for_session, ¤t_ticks);
|
|
|
|
reschedule_ack_timeout();
|
|
}
|
|
|
|
static void ack_timer_cb(void *cb_data) {
|
|
mutex_lock(s_endpoint_data.mutex);
|
|
|
|
check_ack_timeout();
|
|
|
|
mutex_unlock(s_endpoint_data.mutex);
|
|
}
|
|
|
|
static bool find_soonest_ack_timeout_cb(DataLoggingSession *session, void *data) {
|
|
RtcTicks *soonest_ack_timeout = (RtcTicks*) data;
|
|
if (session->comm.ack_timeout != 0
|
|
&& (session->comm.ack_timeout < *soonest_ack_timeout || *soonest_ack_timeout == 0)) {
|
|
*soonest_ack_timeout = session->comm.ack_timeout;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void reschedule_ack_timeout(void) {
|
|
RtcTicks soonest_ack_timeout = 0;
|
|
dls_list_for_each_session(find_soonest_ack_timeout_cb, &soonest_ack_timeout);
|
|
|
|
if (soonest_ack_timeout == 0) {
|
|
// No one is waiting for ack, just stop the timer
|
|
new_timer_stop(s_endpoint_data.ack_timer);
|
|
return;
|
|
}
|
|
|
|
RtcTicks current_ticks = rtc_get_ticks();
|
|
if (soonest_ack_timeout < current_ticks) {
|
|
// Handle the timeout immediately. This will result the in the timer being rescheduled if we're still
|
|
// waiting for an ack.
|
|
check_ack_timeout();
|
|
return;
|
|
}
|
|
|
|
// Convert from ticks to ms for the timer
|
|
RtcTicks ticks_until_timeout = soonest_ack_timeout - current_ticks;
|
|
uint32_t ms_until_timeout = ((uint64_t) ticks_until_timeout * 1000) / RTC_TICKS_HZ;
|
|
|
|
bool success = new_timer_start(s_endpoint_data.ack_timer, ms_until_timeout, ack_timer_cb, NULL, 0 /*flags*/);
|
|
PBL_ASSERTN(success);
|
|
}
|
|
|
|
static void dls_endpoint_print_message(uint8_t *message, int num_bytes) {
|
|
PBL_ASSERTN(message != NULL);
|
|
|
|
switch (message[0]) {
|
|
case DataLoggingEndpointCmdClose:
|
|
{
|
|
DataLoggingCloseSessionMessage *msg = (DataLoggingCloseSessionMessage *)message;
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Closing session %d", msg->session_id);
|
|
break;
|
|
}
|
|
case DataLoggingEndpointCmdOpen:
|
|
{
|
|
DataLoggingOpenSessionMessage *msg = (DataLoggingOpenSessionMessage *)message;
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Opening session %u with tag %"PRIu32", type %u, size %hu",
|
|
msg->session_id, msg->logging_session_tag, msg->data_item_type, msg->data_item_size);
|
|
break;
|
|
}
|
|
case DataLoggingEndpointCmdData:
|
|
{
|
|
DataLoggingSendDataMessage *msg = (DataLoggingSendDataMessage *)message;
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Sending data with session_id %"PRIu8", items remaining %"PRIu32", crc 0x%"PRIx32", num_bytes %d",
|
|
msg->session_id, msg->items_left_hereafter, msg->crc32, num_bytes);
|
|
break;
|
|
}
|
|
default:
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Message type 0x%x not recognized", message[0]);
|
|
}
|
|
}
|
|
|
|
bool dls_endpoint_open_session(DataLoggingSession *session) {
|
|
CommSession *comm_session = comm_session_get_system_session();
|
|
if (!session) {
|
|
return false;
|
|
}
|
|
|
|
DataLoggingOpenSessionMessage msg = {
|
|
.command = DataLoggingEndpointCmdOpen,
|
|
.session_id = session->comm.session_id,
|
|
.app_uuid = session->app_uuid,
|
|
.timestamp = session->session_created_timestamp,
|
|
.logging_session_tag = session->tag,
|
|
.data_item_type = session->item_type,
|
|
.data_item_size = session->item_size,
|
|
};
|
|
|
|
dls_endpoint_print_message((uint8_t *)&msg, 0);
|
|
|
|
update_session_state(session, DataLoggingSessionCommStateOpening, true /*reschedule*/);
|
|
|
|
return (comm_session_send_data(comm_session, ENDPOINT_ID_DATA_LOGGING,
|
|
(uint8_t *)&msg, sizeof(DataLoggingOpenSessionMessage),
|
|
COMM_SESSION_DEFAULT_TIMEOUT));
|
|
}
|
|
|
|
void dls_endpoint_close_session(uint8_t session_id) {
|
|
CommSession *session = comm_session_get_system_session();
|
|
if (!session) {
|
|
return;
|
|
}
|
|
|
|
DataLoggingCloseSessionMessage msg = {
|
|
.command = DataLoggingEndpointCmdClose,
|
|
.session_id = session_id,
|
|
};
|
|
|
|
dls_endpoint_print_message((uint8_t *)&msg, 0);
|
|
|
|
comm_session_send_data(session, ENDPOINT_ID_DATA_LOGGING,
|
|
(uint8_t *)&msg, sizeof(DataLoggingCloseSessionMessage),
|
|
COMM_SESSION_DEFAULT_TIMEOUT);
|
|
}
|
|
|
|
bool dls_endpoint_send_data(DataLoggingSession *logging_session, const uint8_t *data,
|
|
unsigned int num_bytes) {
|
|
if (num_bytes < 1) {
|
|
// not sending anything
|
|
return true;
|
|
}
|
|
|
|
CommSession *session = comm_session_get_system_session();
|
|
if (!session) {
|
|
return false;
|
|
}
|
|
|
|
mutex_lock(s_endpoint_data.mutex);
|
|
if (logging_session->comm.state != DataLoggingSessionCommStateIdle) {
|
|
mutex_unlock(s_endpoint_data.mutex);
|
|
// logging_session is waiting for an ack, we'll send next time around
|
|
// don't return a failure, this is pretty innocuous.
|
|
return true;
|
|
}
|
|
|
|
const uint32_t total_length = sizeof(DataLoggingSendDataMessage) + num_bytes;
|
|
const uint32_t timeout_ms = 500;
|
|
SendBuffer *sb = comm_session_send_buffer_begin_write(session, ENDPOINT_ID_DATA_LOGGING,
|
|
total_length, timeout_ms);
|
|
if (!sb) {
|
|
mutex_unlock(s_endpoint_data.mutex);
|
|
return false;
|
|
}
|
|
|
|
analytics_inc(ANALYTICS_DEVICE_METRIC_DATA_LOGGING_ENDPOINT_SENDS,
|
|
AnalyticsClient_System);
|
|
|
|
const DataLoggingSendDataMessage header = (const DataLoggingSendDataMessage) {
|
|
.command = DataLoggingEndpointCmdData,
|
|
.session_id = logging_session->comm.session_id,
|
|
.items_left_hereafter = 0xffff, // FIXME: logging_session->storage.num_bytes - num_bytes,
|
|
.crc32 = legacy_defective_checksum_memory(data, num_bytes),
|
|
};
|
|
comm_session_send_buffer_write(sb, (const uint8_t *) &header, sizeof(header));
|
|
comm_session_send_buffer_write(sb, data, num_bytes);
|
|
comm_session_send_buffer_end_write(sb);
|
|
|
|
dls_endpoint_print_message((uint8_t *) &header, num_bytes);
|
|
DLS_HEXDUMP(data, MIN(num_bytes, 64));
|
|
|
|
logging_session->comm.num_bytes_pending = num_bytes;
|
|
|
|
update_session_state(logging_session, DataLoggingSessionCommStateSending, true /*reschedule*/);
|
|
|
|
mutex_unlock(s_endpoint_data.mutex);
|
|
|
|
unsigned int data_buffer_length = sizeof(DataLoggingSendDataMessage) + num_bytes;
|
|
if (!uuid_is_system(&logging_session->app_uuid)) {
|
|
analytics_inc_for_uuid(ANALYTICS_APP_METRIC_LOG_OUT_COUNT, &logging_session->app_uuid);
|
|
analytics_add_for_uuid(ANALYTICS_APP_METRIC_LOG_BYTE_OUT_COUNT, data_buffer_length, &logging_session->app_uuid);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void prv_dls_endpoint_handle_ack(uint8_t session_id) {
|
|
DataLoggingSession *session = dls_list_find_by_session_id(session_id);
|
|
if (session == NULL) {
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_WARNING, "Received ack for non-existent session id: %"PRIu8, session_id);
|
|
return;
|
|
}
|
|
|
|
mutex_lock(s_endpoint_data.mutex);
|
|
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Received ACK for id: %"PRIu8" state: %u", session->comm.session_id, session->comm.state);
|
|
|
|
switch (session->comm.state) {
|
|
case DataLoggingSessionCommStateIdle:
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Unexpected ACK");
|
|
break;
|
|
case DataLoggingSessionCommStateOpening:
|
|
update_session_state(session, DataLoggingSessionCommStateIdle, true /*reschedule*/);
|
|
break;
|
|
case DataLoggingSessionCommStateSending:
|
|
session->comm.nack_count = 0;
|
|
update_session_state(session, DataLoggingSessionCommStateIdle, true /*reschedule*/);
|
|
|
|
mutex_unlock(s_endpoint_data.mutex);
|
|
|
|
// unlock for time consuming activities
|
|
dls_storage_consume(session, session->comm.num_bytes_pending);
|
|
session->comm.num_bytes_pending = 0;
|
|
|
|
// the bt session is likely already active so continue to flush data
|
|
dls_private_send_session(session, true);
|
|
return;
|
|
}
|
|
|
|
mutex_unlock(s_endpoint_data.mutex);
|
|
}
|
|
|
|
static void prv_dls_endpoint_handle_nack(uint8_t session_id) {
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Received NACK for id: %"PRIu8, session_id);
|
|
|
|
DataLoggingSession *logging_session = dls_list_find_by_session_id(session_id);
|
|
if (!logging_session) {
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_WARNING, "Received nack for non-existent session id: %"PRIu8, session_id);
|
|
return;
|
|
}
|
|
|
|
mutex_lock(s_endpoint_data.mutex);
|
|
switch (logging_session->comm.state) {
|
|
case DataLoggingSessionCommStateIdle:
|
|
case DataLoggingSessionCommStateOpening:
|
|
//Currently, these messages never get NACK'd
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Unexpected NACK");
|
|
break;
|
|
case DataLoggingSessionCommStateSending:
|
|
//Maybe queue a resend
|
|
logging_session->comm.num_bytes_pending = 0;
|
|
if (++logging_session->comm.nack_count > MAX_NACK_COUNT) {
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Too many nacks. Flushing...");
|
|
dls_storage_consume(logging_session, logging_session->storage.num_bytes);
|
|
analytics_inc(ANALYTICS_DEVICE_METRIC_DATA_LOGGING_FLUSH_COUNT, AnalyticsClient_System);
|
|
logging_session->comm.nack_count = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
update_session_state(logging_session, DataLoggingSessionCommStateIdle, true /*reschedule*/);
|
|
|
|
mutex_unlock(s_endpoint_data.mutex);
|
|
|
|
// reopen the session that was NACK'ed
|
|
dls_endpoint_open_session(logging_session);
|
|
}
|
|
|
|
//! System task callback executed which reopens the next session in the list built up by report_cmd_system_task_cb
|
|
static void prv_reopen_next_session_system_task_cb(void* data) {
|
|
DataLoggingReopenEntry *entry = (DataLoggingReopenEntry *)data;
|
|
if (!entry) {
|
|
s_endpoint_data.report_in_progress = false;
|
|
return;
|
|
}
|
|
DataLoggingReopenEntry *new_head = (DataLoggingReopenEntry *)list_pop_head((ListNode *)entry);
|
|
|
|
// Try and reopen this session
|
|
bool success = false;
|
|
if (dls_list_is_session_valid(entry->session) &&
|
|
uuid_equal(&entry->app_uuid, &entry->session->app_uuid) &&
|
|
entry->timestamp == entry->session->session_created_timestamp &&
|
|
entry->tag == entry->session->tag) {
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Reopening session %d",
|
|
entry->session->comm.session_id);
|
|
success = (dls_endpoint_open_session(entry->session)
|
|
&& dls_private_send_session(entry->session, false));
|
|
} else {
|
|
// Session has disappeared between the time that the reopen list was
|
|
// created and now. This ideally shouldn't happen, but there's a lot
|
|
// that's broken about datalogging. See PBL-37078.
|
|
success = true;
|
|
}
|
|
kernel_free(entry);
|
|
|
|
if (success) {
|
|
// Schedule next one
|
|
if (new_head) {
|
|
bool result = system_task_add_callback(prv_reopen_next_session_system_task_cb, new_head);
|
|
PBL_ASSERTN(result);
|
|
} else {
|
|
s_endpoint_data.report_in_progress = false;
|
|
}
|
|
} else {
|
|
s_endpoint_data.report_in_progress = false;
|
|
// If we failed, give up on the remaining ones
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Aborting all remaining open requests");
|
|
while (new_head) {
|
|
DataLoggingReopenEntry *entry = new_head;
|
|
new_head = (DataLoggingReopenEntry *)list_pop_head((ListNode *)new_head);
|
|
kernel_free(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
//! For use with dls_list_for_each_session. Appends this session to our list of sesions we need to open.
|
|
//! On entry, 'data' points to the variable holding the head of the list.
|
|
static bool dls_endpoint_add_reopen_sessions_cb(DataLoggingSession *session, void *data) {
|
|
DataLoggingReopenEntry **head_ptr = (DataLoggingReopenEntry **)data;
|
|
DataLoggingReopenEntry *entry = kernel_malloc_check(sizeof(DataLoggingReopenEntry));
|
|
*entry = (DataLoggingReopenEntry) {
|
|
.session = session,
|
|
.app_uuid = session->app_uuid,
|
|
.timestamp = session->session_created_timestamp,
|
|
.tag = session->tag,
|
|
};
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "adding session %d to reopen list", session->comm.session_id);
|
|
*head_ptr = (DataLoggingReopenEntry *)list_insert_before((ListNode *)(*head_ptr), &entry->list_node);
|
|
return true;
|
|
}
|
|
|
|
static void prv_handle_report_cmd(const uint8_t *session_ids, size_t num_sessions) {
|
|
for (size_t i = 0; i < num_sessions; ++i) {
|
|
const uint8_t session_id = session_ids[i];
|
|
|
|
DataLoggingSession *logging_session = dls_list_find_by_session_id(session_id);
|
|
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Phone reported session %u opened", session_id);
|
|
|
|
// If the phone thinks we're open and we're not, send a close message.
|
|
if (logging_session == NULL) {
|
|
dls_endpoint_close_session(session_id);
|
|
}
|
|
}
|
|
|
|
// If the bluetooth connection is flaky, a session reopen could take a few seconds, so we will chain them
|
|
// and only do 1 re-open per system callback so that we don't trigger a watchdog timeout.
|
|
DataLoggingReopenEntry *head = NULL;
|
|
dls_list_for_each_session(dls_endpoint_add_reopen_sessions_cb, (void *)&head);
|
|
|
|
// Re-open the first one and reschedule the next one
|
|
prv_reopen_next_session_system_task_cb((void *)head);
|
|
}
|
|
|
|
//! Empty a session by session id
|
|
static void prv_empty_session(uint8_t session_id) {
|
|
PBL_LOG_D(LOG_DOMAIN_DATA_LOGGING, LOG_LEVEL_DEBUG, "Phone requested empty of session %u",
|
|
session_id);
|
|
DataLoggingSession *logging_session = dls_list_find_by_session_id(session_id);
|
|
if (logging_session) {
|
|
dls_private_send_session(logging_session, true /*empty_all_data*/);
|
|
}
|
|
}
|
|
|
|
|
|
//! data_logging_protocol_msg_callback runs on Bluetooth task. Keep it quick.
|
|
void data_logging_protocol_msg_callback(CommSession *session, const uint8_t *data, size_t length) {
|
|
// consume the first byte to read the command
|
|
uint8_t command = data[0];
|
|
|
|
--length; // the length now reflects the sizeof the payload
|
|
|
|
// All commands from the phone have their high bit set.
|
|
if ((command & ~DLS_ENDPOINT_CMD_MASK) == 0) {
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Invalid data logging endpoint command 0x%x", command);
|
|
// TODO: send some error code back?
|
|
return;
|
|
}
|
|
|
|
switch (command & DLS_ENDPOINT_CMD_MASK) {
|
|
case (DataLoggingEndpointCmdAck):
|
|
prv_dls_endpoint_handle_ack(data[1]);
|
|
break;
|
|
|
|
case (DataLoggingEndpointCmdNack):
|
|
prv_dls_endpoint_handle_nack(data[1]);
|
|
break;
|
|
|
|
case (DataLoggingEndpointCmdReport):
|
|
if (s_endpoint_data.report_in_progress) {
|
|
PBL_LOG(LOG_LEVEL_INFO, "Report already in progress");
|
|
} else {
|
|
s_endpoint_data.report_in_progress = true;
|
|
prv_handle_report_cmd(&data[1], length);
|
|
}
|
|
break;
|
|
|
|
case (DataLoggingEndpointCmdEmptySession):
|
|
prv_empty_session(data[1]);
|
|
break;
|
|
|
|
case (DataLoggingEndpointCmdGetSendEnableReq):
|
|
{
|
|
bool enabled = dls_get_send_enable();
|
|
struct PACKED {
|
|
uint8_t command;
|
|
uint8_t enabled;
|
|
} msg = {
|
|
.command = DataLoggingEndpointCmdGetSendEnableRsp,
|
|
.enabled = enabled,
|
|
};
|
|
|
|
comm_session_send_data(session, ENDPOINT_ID_DATA_LOGGING, (uint8_t *)&msg,
|
|
sizeof(msg), COMM_SESSION_DEFAULT_TIMEOUT);
|
|
}
|
|
break;
|
|
|
|
case (DataLoggingEndpointCmdSetSendEnable):
|
|
dls_set_send_enable_pp(data[1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void dls_endpoint_init(void) {
|
|
s_endpoint_data.mutex = mutex_create();
|
|
s_endpoint_data.ack_timer = new_timer_create();
|
|
}
|
|
|
|
static bool prv_handle_disconnect_cb(DataLoggingSession *session, void *data) {
|
|
session->comm.state = DataLoggingSessionCommStateIdle;
|
|
return true;
|
|
}
|
|
|
|
void dls_private_handle_disconnect(void *data) {
|
|
mutex_lock(s_endpoint_data.mutex);
|
|
dls_list_for_each_session(prv_handle_disconnect_cb, 0);
|
|
mutex_unlock(s_endpoint_data.mutex);
|
|
}
|