Files
pebble/src/fw/services/common/ping.c
2025-01-27 11:38:16 -08:00

170 lines
5.0 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 "applib/ui/dialogs/dialog.h"
#include "applib/ui/dialogs/simple_dialog.h"
#include "console/prompt.h"
#include "drivers/battery.h"
#include "kernel/event_loop.h"
#include "kernel/ui/modals/modal_manager.h"
#include "services/common/accel_manager.h"
#include "services/common/analytics/analytics.h"
#include "services/common/comm_session/session.h"
#include "services/common/system_task.h"
#include "system/logging.h"
#include "util/attributes.h"
#include "util/net.h"
#include <inttypes.h>
#define PING_ENDPOINT 2001
#define PING_MIN_PERIOD_SECS (60 * 60) // 1 hour
static time_t s_last_send_time;
static bool s_is_ping_kernel_bg_callback_scheduled;
// ---------------------------------------------------------------------------------------------------------
// Ping Pong structures
typedef struct PACKED {
uint8_t cmd;
uint32_t cookie;
} PingMsgHeader;
typedef struct PACKED {
PingMsgHeader hdr;
} PingMsgV1;
typedef struct PACKED {
PingMsgHeader hdr;
uint8_t idle; // Optional
} PingMsgV2;
typedef struct PACKED {
PingMsgHeader hdr;
} PongMsg;
static void prv_send_ping_kernel_bg_cb(void *unused) {
CommSession *system_session = comm_session_get_system_session();
if (system_session) {
// Are we idle?
bool idle = (battery_is_usb_connected() || accel_is_idle());
PingMsgV2 ping_msg = (PingMsgV2) {
.hdr = {
.cmd = 0,
.cookie = htonl(42)
},
.idle = idle
};
bool success = comm_session_send_data(system_session, PING_ENDPOINT,
(const uint8_t *) &ping_msg, sizeof(ping_msg),
COMM_SESSION_DEFAULT_TIMEOUT);
if (success) {
s_last_send_time = rtc_get_time();
analytics_inc(ANALYTICS_DEVICE_METRIC_PING_SENT_COUNT, AnalyticsClient_System);
}
PBL_LOG(LOG_LEVEL_DEBUG, "Sent ping idle=%d, success=%d", (int)idle, (int)success);
}
s_is_ping_kernel_bg_callback_scheduled = false;
}
// ------------------------------------------------------------------------------------------------------------
// If a ping is due to be sent, send it.
// bt_lock() is held by the caller
void ping_send_if_due(void) {
if (s_is_ping_kernel_bg_callback_scheduled) {
return;
}
// Only send if we haven't sent within the last PING_MIN_PERIOD_SECS
time_t current_time = rtc_get_time();
if (current_time < s_last_send_time + PING_MIN_PERIOD_SECS) {
return;
}
// Offload to KernelBG, because we cannot use comm_session_send_data() with bt_lock held.
system_task_add_callback(prv_send_ping_kernel_bg_cb, NULL);
s_is_ping_kernel_bg_callback_scheduled = true;
}
static void prv_push_window(void *data) {
SimpleDialog *s_dialog = simple_dialog_create("Ping");
Dialog *dialog = simple_dialog_get_dialog(s_dialog);
dialog_set_background_color(dialog, GColorCobaltBlue);
dialog_set_text_color(dialog, GColorWhite);
dialog_set_text(dialog, "Ping");
WindowStack *stack = modal_manager_get_window_stack(ModalPriorityGeneric);
simple_dialog_push(s_dialog, stack);
}
void ping_protocol_msg_callback(CommSession *session, const uint8_t* data, size_t length) {
PingMsgV1 *ping = (PingMsgV1 *)data;
switch (ping->hdr.cmd) {
case 0:
{
if (length != sizeof(PingMsgV1) && length != sizeof(PingMsgV2) /* idle boolean is optional */) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid Ping, l=%u", length);
return;
}
// Ping message
uint32_t cookie = ntohl(ping->hdr.cookie);
PBL_LOG(LOG_LEVEL_DEBUG, "Ping c=%"PRIu32"", cookie);
launcher_task_add_callback(prv_push_window, NULL);
// Send the pong response
PongMsg pong = {
.hdr = {
.cmd = 1,
.cookie = htonl(cookie)
}
};
comm_session_send_data(session, PING_ENDPOINT, (uint8_t *)&pong, sizeof(pong), COMM_SESSION_DEFAULT_TIMEOUT);
break;
}
case 1:
if (length != sizeof(PongMsg)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid Pong, l=%u", length);
return;
}
PongMsg *pong = (PongMsg *)data;
PBL_LOG(LOG_LEVEL_DEBUG, "Pong c=%"PRIu32, ntohl(pong->hdr.cookie));
analytics_inc(ANALYTICS_DEVICE_METRIC_PONG_RECEIVED_COUNT, AnalyticsClient_System);
break;
default:
PBL_LOG(LOG_LEVEL_ERROR, "Invalid message received. First byte is %u", ping->hdr.cmd);
break;
}
}
// Serial Commands
//////////////////////////////////////////////////////////////////////
void command_ping_send(void) {
// Override last send time
s_last_send_time = 0;
ping_send_if_due();
}