pebble/src/fw/services/common/poll_remote.c

220 lines
6.3 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 "poll_remote.h"
#include "services/common/comm_session/protocol.h"
#include "services/common/comm_session/session.h"
#include "services/common/system_task.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/attributes.h"
#include <string.h>
/*
* Private
*/
static const uint8_t MIN_INTERVAL_MINUTES = 1;
static const uint16_t ENDPOINT_ID = 0xcafe;
static bool s_running = false;
typedef enum {
CMD_POLL = 0x0, // Formerly command poll mail
LEGACY_CMD_REQUEST_INTERVAL = 0x1, // for backwards compatibility
CMD_SET_INTERVAL = 0x2,
CMD_REQUEST_POLL = 0x3,
} PollRemoteCommand;
// Deprecated -- used to set the mail poll interval
typedef struct PACKED {
uint8_t cmd;
uint8_t interval_minutes;
} PollLegacySetIntervalMessage;
// Poll a service at a specific interval
typedef struct PACKED {
PollRemoteCommand cmd;
PollRemoteService service;
uint8_t interval_minutes;
} PollSetIntervalMessage;
// Request to poll a service now
typedef struct PACKED {
PollRemoteCommand cmd;
PollRemoteService service;
} PollRequestMessage;
typedef struct PACKED {
PollRemoteCommand cmd;
PollRemoteService service;
} PollRemoteMessage;
//! Struct that holds all the state required for a remote polling subsystem.
typedef struct {
PollRemoteService service;
//! The minimum interval between two "poll services" requests.
//! Calls to poll_remote_send_request() will be no-ops if min_interval_minutes has not been reached.
uint8_t min_interval_minutes;
//! The maximum interval between two "poll services" requests.
//! The automatic sending of poll requests will only occur when max_interval_minutes is reached.
uint8_t max_interval_minutes;
uint8_t counted_minutes; //!< Number of minutes passed since the last request
} PollRemoteContext;
static void poll_service_timer_callback();
static RegularTimerInfo s_poll_timer = {
.cb = poll_service_timer_callback,
.cb_data = NULL,
};
static PollRemoteContext s_poll_remote_contexts[NUM_POLL_REMOTE_SERVICES];
static void for_each_context(void (*poll_remote_function)(PollRemoteContext *ctx)) {
for (int i = 0; i < NUM_POLL_REMOTE_SERVICES; i++) {
poll_remote_function(&s_poll_remote_contexts[i]);
}
}
static bool has_min_interval_passed(PollRemoteContext *ctx) {
return (ctx->counted_minutes >= ctx->min_interval_minutes);
}
static bool has_max_interval_passed(PollRemoteContext *ctx) {
return (ctx->counted_minutes >= ctx->max_interval_minutes);
}
// SystemTaskCallback
static void prv_send_request(PollRemoteContext *ctx) {
if (has_min_interval_passed(ctx) == false) {
return;
}
CommSession *session = comm_session_get_system_session();
if (!session) {
return;
}
// [MT]: comm_session_send_data() doesn't make the link active,
// which is what we want here. If this this changes in the future
// we need to take measures here to make sure we don't pull the link active.
const PollRemoteMessage msg = {
.cmd = CMD_POLL,
.service = ctx->service
};
comm_session_send_data(session, ENDPOINT_ID, (const uint8_t *)&msg, sizeof(PollRemoteMessage),
COMM_SESSION_DEFAULT_TIMEOUT);
ctx->counted_minutes = 0;
}
static void context_interval_check(PollRemoteContext *ctx) {
if (ctx->max_interval_minutes == 0) {
return;
}
ctx->counted_minutes++;
if (has_max_interval_passed(ctx)) {
prv_send_request(ctx);
}
}
static void start(PollRemoteContext *ctx) {
ctx->counted_minutes = 0;
}
static void set_intervals(PollRemoteContext *ctx, const uint8_t min_interval_minutes, const uint8_t max_interval_minutes) {
ctx->min_interval_minutes = min_interval_minutes;
ctx->max_interval_minutes = max_interval_minutes;
}
void comm_poll_remote_protocol_msg_callback(CommSession *session, const uint8_t* data, size_t length) {
PollRemoteCommand cmd = data[0];
switch (cmd) {
case CMD_REQUEST_POLL: {
PollRequestMessage *msg = (PollRequestMessage *)data;
if (msg->service >= NUM_POLL_REMOTE_SERVICES) { return; }
prv_send_request(&s_poll_remote_contexts[msg->service]);
break;
}
case LEGACY_CMD_REQUEST_INTERVAL: {
PollLegacySetIntervalMessage *msg = (PollLegacySetIntervalMessage *)data;
poll_remote_set_intervals(POLL_REMOTE_SERVICE_MAIL, MIN_INTERVAL_MINUTES, msg->interval_minutes);
break;
}
case CMD_SET_INTERVAL: {
PollSetIntervalMessage *msg = (PollSetIntervalMessage *)data;
if (msg->service >= NUM_POLL_REMOTE_SERVICES) { return; }
poll_remote_set_intervals(msg->service, MIN_INTERVAL_MINUTES, msg->interval_minutes);
break;
}
default: {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid command.");
return;
}
}
}
/*
* Public
*/
static void poll_service_system_task_callback(void *data) {
PBL_ASSERTN(s_running);
for_each_context(context_interval_check);
}
static void poll_service_timer_callback(void *data) {
system_task_add_callback(poll_service_system_task_callback, data);
}
void poll_remote_init(void) {
for (int i = 0; i < NUM_POLL_REMOTE_SERVICES; i++) {
s_poll_remote_contexts[i].service = i;
}
}
void poll_remote_send_request(PollRemoteService service) {
prv_send_request(&s_poll_remote_contexts[service]);
}
void poll_remote_start(void) {
if (s_running) {
return;
}
s_running = true;
for_each_context(start);
regular_timer_add_minutes_callback(&s_poll_timer);
}
void poll_remote_stop(void) {
if (!s_running) {
return;
}
s_running = false;
regular_timer_remove_callback(&s_poll_timer);
}
void poll_remote_set_intervals(PollRemoteService service, const uint8_t min_interval_minutes, const uint8_t max_interval_minutes) {
set_intervals(&s_poll_remote_contexts[service], min_interval_minutes, max_interval_minutes);
(max_interval_minutes == 0) ? poll_remote_stop() : poll_remote_start();
}