Files
lucky-cat/components/platform/include/platform.h
T

627 lines
24 KiB
C

/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PLATFORM_STEPPER_SPEED_ULTRA_FAST 600
#define PLATFORM_STEPPER_SPEED_FAST 800
#define PLATFORM_STEPPER_SPEED_NORMAL 1500
#define PLATFORM_STEPPER_SPEED_SLOW 2000
typedef struct {
int gpio_num;
int active_level;
bool pullup_enabled;
bool pulldown_enabled;
} platform_digital_input_config_t;
typedef struct {
void (*set_keep_awake)(bool keep_awake, void *user_data);
esp_err_t (*play_prompt_file)(const char *path, void *user_data);
void *user_data;
} platform_wifi_behavior_hooks_t;
typedef struct {
void (*on_connected)(void *user_data);
void (*on_disconnected)(void *user_data);
void (*on_error)(void *user_data);
void (*on_text_payload)(const uint8_t *data, size_t data_len, void *user_data);
void (*on_binary_payload)(const uint8_t *data, size_t data_len, void *user_data);
void *user_data;
} platform_multimodal_transport_callbacks_t;
typedef struct {
void (*on_started)(uint32_t playback_generation, void *user_data);
void (*on_completed)(uint32_t playback_generation, void *user_data);
void (*on_stopped)(uint32_t playback_generation, void *user_data);
void (*on_stalled)(uint32_t playback_generation, void *user_data);
void *user_data;
} platform_multimodal_downstream_playback_callbacks_t;
typedef void (*platform_multimodal_audio_pcm_observer_t)(
const int16_t *pcm,
size_t samples,
uint32_t sample_rate_hz,
void *user_data);
#define PLATFORM_LAN_HTTP_PATH_MAX_LEN 96
#define PLATFORM_LAN_HTTP_HOSTNAME_MAX_LEN 32
#define PLATFORM_LAN_HTTP_REQUEST_BODY_MAX_LEN 1024
#define PLATFORM_LAN_HTTP_RESPONSE_BODY_MAX_LEN 16384
typedef enum {
PLATFORM_LAN_HTTP_METHOD_GET = 0,
PLATFORM_LAN_HTTP_METHOD_POST,
} platform_lan_http_method_t;
typedef enum {
PLATFORM_LAN_HTTP_STATUS_OK = 200,
PLATFORM_LAN_HTTP_STATUS_ACCEPTED = 202,
PLATFORM_LAN_HTTP_STATUS_BAD_REQUEST = 400,
PLATFORM_LAN_HTTP_STATUS_NOT_FOUND = 404,
PLATFORM_LAN_HTTP_STATUS_METHOD_NOT_ALLOWED = 405,
PLATFORM_LAN_HTTP_STATUS_PAYLOAD_TOO_LARGE = 413,
PLATFORM_LAN_HTTP_STATUS_INTERNAL_ERROR = 500,
} platform_lan_http_status_t;
typedef enum {
PLATFORM_LAN_HTTP_CONTENT_TEXT = 0,
PLATFORM_LAN_HTTP_CONTENT_HTML,
PLATFORM_LAN_HTTP_CONTENT_JSON,
} platform_lan_http_content_type_t;
typedef struct {
platform_lan_http_method_t method;
char path[PLATFORM_LAN_HTTP_PATH_MAX_LEN];
const uint8_t *body;
size_t body_len;
} platform_lan_http_request_t;
typedef struct {
platform_lan_http_status_t status;
platform_lan_http_content_type_t content_type;
uint8_t *body;
size_t body_capacity;
size_t body_len;
} platform_lan_http_response_t;
typedef esp_err_t (*platform_lan_http_handler_t)(
const platform_lan_http_request_t *request,
platform_lan_http_response_t *response,
void *user_data);
typedef struct {
uint16_t port;
char hostname[PLATFORM_LAN_HTTP_HOSTNAME_MAX_LEN];
platform_lan_http_handler_t handler;
void *user_data;
} platform_lan_http_server_config_t;
typedef struct {
bool running;
uint16_t port;
char hostname[PLATFORM_LAN_HTTP_HOSTNAME_MAX_LEN];
} platform_lan_http_server_state_t;
typedef struct {
const char *name;
const char *value;
} platform_https_header_t;
typedef struct {
const char *url;
const char *payload;
const platform_https_header_t *headers;
size_t header_count;
int timeout_ms;
int tx_buffer_size;
int rx_buffer_size;
} platform_https_post_json_request_t;
typedef struct {
char *body;
size_t body_capacity;
size_t body_len;
int status_code;
} platform_https_response_t;
typedef void (*platform_ota_progress_callback_t)(uint64_t bytes_received,
uint64_t total_bytes,
void *user_data);
typedef bool (*platform_ota_cancel_callback_t)(void *user_data);
#define PLATFORM_BLE_UUID128_LEN 16
#define PLATFORM_BLE_GATT_NAME_MAX_LEN 31
#define PLATFORM_BLE_GATT_VALUE_MAX_LEN 512
typedef struct {
uint8_t bytes[PLATFORM_BLE_UUID128_LEN];
} platform_ble_uuid128_t;
typedef enum {
PLATFORM_BLE_GATT_NOTIFY_VALUE = 0,
PLATFORM_BLE_GATT_READ_NOTIFY_VALUE,
} platform_ble_gatt_notify_target_t;
typedef void (*platform_ble_gatt_write_callback_t)(const uint8_t *data,
size_t data_len,
void *user_data);
typedef struct {
const char *advertised_name;
platform_ble_uuid128_t service_uuid;
platform_ble_uuid128_t write_uuid;
platform_ble_uuid128_t notify_uuid;
platform_ble_uuid128_t read_notify_uuid;
const uint8_t *read_value;
size_t read_value_len;
platform_ble_gatt_write_callback_t on_write;
void *user_data;
} platform_ble_gatt_server_config_t;
esp_err_t platform_init(void);
esp_err_t platform_prepare_storage(void);
bool platform_storage_is_ready(void);
esp_err_t platform_mount_littlefs(void);
esp_err_t platform_mount_logfs(void);
void platform_read_device_identity(char *device_id,
size_t device_id_len,
char *serial_number,
size_t serial_number_len);
/**
* @brief Persist device identity (device_id + serial_number) to NVS.
*
* Writes the same storage that `platform_read_device_identity()` reads. Both
* arguments must be non-empty; returns an explicit error otherwise or on NVS
* failure. Intended for factory provisioning.
*/
esp_err_t platform_write_device_identity(const char *device_id, const char *serial_number);
/**
* @brief Console (UART) line-transport primitives for the factory CLI.
*
* Stateless primitives over the console UART. `platform_console_install()` is
* idempotent (a pre-existing console driver is treated as success).
* `platform_console_read_bytes()` returns bytes read (0 on timeout, <0 on error).
* A zero timeout polls once; negative timeouts are invalid and return <0.
*/
esp_err_t platform_console_install(void);
int platform_console_read_bytes(uint8_t *buf, size_t len, int timeout_ms);
void platform_console_write_line(const char *line);
/**
* @brief Generic persistent key/value blob storage (NVS-backed).
*
* Platform owns the storage primitive and stays semantics-free: upper layers
* serialize their own typed records into opaque bytes. `ns`/`key` follow NVS
* limits (<=15 chars). Semantics:
* - `set_blob`: opens the namespace read/write and commits atomically.
* - `get_blob`: returns `ESP_ERR_NOT_FOUND` when the namespace/key is absent,
* `ESP_ERR_NVS_INVALID_LENGTH` when `out_len` is too small. `out_len_actual`
* (optional) receives the number of bytes written.
* - `erase`: removing an absent key is treated as success (idempotent).
*/
esp_err_t platform_storage_set_blob(const char *ns, const char *key, const void *data, size_t len);
esp_err_t platform_storage_set_string(const char *ns, const char *key, const char *value);
esp_err_t platform_storage_set_u8(const char *ns, const char *key, uint8_t value);
esp_err_t platform_storage_set_i8(const char *ns, const char *key, int8_t value);
esp_err_t platform_storage_get_blob(const char *ns,
const char *key,
void *out,
size_t out_len,
size_t *out_len_actual);
esp_err_t platform_storage_get_u8(const char *ns, const char *key, uint8_t *out);
esp_err_t platform_storage_get_i8(const char *ns, const char *key, int8_t *out);
esp_err_t platform_storage_get_u32(const char *ns, const char *key, uint32_t *out);
esp_err_t platform_storage_get_u64(const char *ns, const char *key, uint64_t *out);
esp_err_t platform_storage_get_string(const char *ns,
const char *key,
char *out,
size_t out_len,
size_t *out_len_actual);
esp_err_t platform_storage_erase(const char *ns, const char *key);
/**
* @brief Wall-clock time synchronization (SNTP) primitives.
*
* - `platform_time_set_timezone()` applies the process-local POSIX time zone
* without starting SNTP. Call it during boot before local time is rendered.
* - `platform_time_sync_start()` is idempotent: it sets the process time zone
* (`posix_tz`, e.g. "CST-8"; ignored when NULL/empty) on every call and
* starts the SNTP client once. Must be called after the network is up.
* - `platform_time_is_synced()` reports whether the system clock has advanced
* past a sane epoch lower bound (i.e. SNTP has set the time at least once).
*/
esp_err_t platform_time_set_timezone(const char *posix_tz);
esp_err_t platform_time_sync_start(const char *sntp_server, const char *posix_tz);
bool platform_time_is_synced(void);
/**
* @brief POST a JSON body over HTTPS with bounded caller-owned response storage.
*
* Platform owns the outbound HTTPS transport primitive and TLS client resource
* lifecycle. Completion of an HTTP exchange returns ESP_OK regardless of status
* code; callers inspect `response->status_code` and body semantics. Transport,
* allocation, oversized response, and protocol setup failures return explicit
* ESP errors.
*/
esp_err_t platform_https_post_json(const platform_https_post_json_request_t *request,
platform_https_response_t *response);
esp_err_t platform_ota_install_image(const char *url,
const uint8_t expected_sha256[32],
uint64_t expected_size,
int timeout_ms,
platform_ota_progress_callback_t progress_cb,
platform_ota_cancel_callback_t cancel_cb,
void *user_data);
esp_err_t platform_ota_confirm_running_image(void);
esp_err_t platform_ota_get_memory_headroom(uint32_t *free_internal,
uint32_t *largest_internal,
uint32_t *largest_dma);
void platform_restart(void);
bool platform_https_is_connect_error(esp_err_t err);
/** Start a generic single-service BLE GATT peripheral. */
esp_err_t platform_ble_gatt_server_start(const platform_ble_gatt_server_config_t *config);
/** Send a payload to a subscribed GATT notification characteristic. */
esp_err_t platform_ble_gatt_server_notify(platform_ble_gatt_notify_target_t target,
const uint8_t *data,
size_t data_len);
/**
* Stop the platform-owned NimBLE runtime. A positive timeout bounds the wait;
* zero requests cooperative teardown without waiting.
*/
esp_err_t platform_ble_gatt_server_stop(int timeout_ms);
/** Read the running application version into caller-owned storage. */
esp_err_t platform_read_firmware_version(char *out, size_t out_len);
/**
* @brief Rotate stepper synchronously with a bounded timeout.
*
* Semantics:
* - `timeout_ms <= 0`: no timeout limit is enforced.
* - `timeout_ms > 0`: returns `ESP_ERR_TIMEOUT` if the move cannot complete
* within the timeout budget.
* - `angle == 0`: returns `ESP_OK` and performs no hardware action (idempotent).
*/
esp_err_t platform_stepper_rotate_angle_with_accel_timeout(float angle,
int target_delay_us,
int timeout_ms);
/**
* @brief Release stepper holding current with bounded lock acquisition.
*
* A negative timeout is invalid, zero is non-blocking, and a positive value
* bounds the wait for an in-progress movement to finish.
*/
esp_err_t platform_stepper_release_hold(int timeout_ms);
void platform_stepper_power_off(void);
/** Return true when this boot was caused by a configured deep-sleep wake source. */
bool platform_power_woke_from_deep_sleep(void);
/** Release board-output holds after a deep-sleep wake is accepted. */
void platform_power_release_deep_sleep_holds(void);
/**
* Enter deep sleep after configuring active-low charger and optional button
* wake GPIOs. A successful call does not return.
*/
esp_err_t platform_power_enter_deep_sleep(int charger_gpio, int button_gpio);
typedef enum {
PLATFORM_BATTERY_CHARGE_UNKNOWN = 0,
PLATFORM_BATTERY_CHARGE_NOT_CHARGING,
PLATFORM_BATTERY_CHARGE_CHARGING,
} platform_battery_charge_state_t;
typedef struct {
int adc_voltage_mv;
int voltage_mv;
platform_battery_charge_state_t charge_state;
bool voltage_valid;
bool high_load_active;
bool cooldown_active;
} platform_battery_sample_t;
/** Initialize the board battery ADC and TP4057 status input. Idempotent. */
esp_err_t platform_battery_init(void);
/**
* Read one trimmed-mean battery voltage sample and a load-quality snapshot.
* A successful call can still return voltage_valid=false for an implausible
* electrical reading; callers must not interpret such a sample as an empty
* battery.
*/
esp_err_t platform_battery_read_sample(platform_battery_sample_t *out_sample);
esp_err_t platform_configure_digital_input(const platform_digital_input_config_t *config);
bool platform_is_digital_input_active(const platform_digital_input_config_t *config);
void platform_behavior_set_keep_awake(bool keep_awake);
esp_err_t platform_behavior_play_prompt_file(const char *path);
void platform_wifi_portal_set_behavior_hooks(const platform_wifi_behavior_hooks_t *hooks);
esp_err_t platform_wifi_portal_init_defaults(const char *seed_ssid, const char *seed_password);
bool platform_wifi_portal_has_saved_credentials(void);
/**
* Copy saved SSIDs in persistent priority order without exposing passwords.
* `out_ssids` contains `capacity` rows of `ssid_stride` bytes.
*/
esp_err_t platform_wifi_portal_get_saved_ssids(char *out_ssids,
size_t ssid_stride,
size_t capacity,
size_t *out_count);
bool platform_wifi_portal_sta_is_connected(void);
/** Apply standby modem power save, or restore the configured active policy. */
esp_err_t platform_wifi_portal_set_light_standby(bool standby);
esp_err_t platform_wifi_portal_start_config_ap(const char *ssid_prefix);
/**
* @brief Connect STA using saved credentials with timeout.
*
* Semantics:
* - `timeout_ms <= 0`: use default timeout (10s).
* - Returns `ESP_OK` when already connected (idempotent).
* - Returns explicit error (`ESP_ERR_TIMEOUT`, `ESP_FAIL`, etc.) on failure.
* - On timeout/failure, no hidden destructive continuation is started after return.
*/
esp_err_t platform_wifi_portal_connect_sta(int timeout_ms);
esp_err_t platform_wifi_portal_connect_sta_with_credentials(const char *ssid,
const char *password,
int timeout_ms);
esp_err_t platform_wifi_portal_reconnect_sta(int timeout_ms);
esp_err_t platform_wifi_portal_stop_sta(void);
esp_err_t platform_wifi_portal_save_credentials(const char *ssid, const char *password);
esp_err_t platform_wifi_portal_clear_saved_credentials(void);
bool platform_wifi_connect_error_keeps_credentials(esp_err_t err);
/**
* @brief Start the LAN HTTP transport on the connected STA interface.
*
* The transport accepts bounded GET/POST requests, invokes the snapshotted
* handler outside locks, and advertises `_http._tcp` using mDNS. Repeated start
* calls are idempotent while the server is starting or running.
*/
esp_err_t platform_lan_http_server_start(const platform_lan_http_server_config_t *config);
/**
* @brief Stop the LAN HTTP transport and release its worker/socket resources.
*
* Semantics:
* - `timeout_ms < 0`: invalid argument.
* - `timeout_ms == 0`: request cooperative stop and return immediately.
* - `timeout_ms > 0`: wait up to the bound, then force-delete the worker.
* - Repeated stop calls are idempotent.
*/
esp_err_t platform_lan_http_server_stop(int timeout_ms);
esp_err_t platform_lan_http_server_get_state(platform_lan_http_server_state_t *out_state);
/**
* @brief Low-level multimodal transport primitive used by the domain adapter.
*
* The domain facade owns the cross-layer multimodal session contract. These
* callbacks intentionally stay platform-shaped and are mapped exactly once in
* the domain runtime adapter before orchestration code can observe them.
*/
esp_err_t platform_multimodal_transport_start(
const char *bearer_token,
const char *workspace_id,
const platform_multimodal_transport_callbacks_t *callbacks);
/**
* @brief Stop multimodal WebSocket transport with bounded teardown.
*
* Semantics:
* - `timeout_ms < 0`: invalid argument.
* - `timeout_ms == 0`: request cooperative stop and return immediately.
* - `timeout_ms > 0`: wait up to the bound, then force-delete owned workers.
* - Repeated calls are idempotent.
*/
esp_err_t platform_multimodal_transport_stop(int timeout_ms);
bool platform_multimodal_transport_is_connected(void);
esp_err_t platform_multimodal_transport_send_text(const char *label, const char *text);
esp_err_t platform_multimodal_transport_send_text_realtime(const char *label, const char *text);
esp_err_t platform_multimodal_transport_send_binary_realtime(const uint8_t *data, size_t len);
esp_err_t platform_multimodal_audio_init(void);
void platform_multimodal_audio_deinit(void);
esp_err_t platform_multimodal_audio_start_uplink(
esp_err_t (*uplink_send)(const uint8_t *data, size_t len, void *user_data),
void *user_data);
/**
* @brief Stop multimodal uplink capture/stream worker with bounded teardown.
*
* Semantics:
* - `timeout_ms < 0`: invalid argument.
* - `timeout_ms == 0`: request cooperative stop and return immediately.
* - `timeout_ms > 0`: wait up to the bound, then force-delete owned workers.
* - Repeated calls are idempotent.
*/
esp_err_t platform_multimodal_audio_stop_uplink(int timeout_ms);
bool platform_multimodal_audio_is_uplink_running(void);
bool platform_multimodal_audio_is_initialized(void);
/**
* @brief Gate the speaker power amplifier without stopping microphone capture.
*
* Disabling first mutes the codec output and then powers down the speaker PA.
* Enabling restores the PA while leaving output muted until playback resumes.
*/
esp_err_t platform_multimodal_audio_set_speaker_output_enabled(bool enabled);
/**
* @brief Register the single mono PCM observer for the active capture path.
*
* The callback runs synchronously in the platform capture task, outside the
* registration lock, and must remain bounded and non-blocking.
*/
esp_err_t platform_multimodal_audio_register_pcm_observer(
platform_multimodal_audio_pcm_observer_t observer,
void *user_data);
/**
* @brief Remove a matching PCM observer and wait for in-flight callbacks.
*
* Negative timeout is invalid. Zero performs a non-blocking drain check.
*/
esp_err_t platform_multimodal_audio_unregister_pcm_observer(
platform_multimodal_audio_pcm_observer_t observer,
void *user_data,
int timeout_ms);
esp_err_t platform_multimodal_audio_play_downstream_opus(const uint8_t *data, size_t len);
esp_err_t platform_multimodal_audio_begin_downstream_response(uint32_t *out_playback_generation);
esp_err_t platform_multimodal_audio_finish_downstream_response(void);
esp_err_t platform_multimodal_audio_stop_downstream_playback(void);
esp_err_t platform_multimodal_audio_register_downstream_playback_callbacks(
const platform_multimodal_downstream_playback_callbacks_t *callbacks);
esp_err_t platform_init_lcd_device(void);
/**
* @brief Set LCD backlight brightness using PWM.
*
* @param brightness_percent Brightness from 0 (off) to 100 (full).
* @return ESP_OK on success, ESP_ERR_INVALID_ARG outside the supported range,
* or ESP_ERR_INVALID_STATE before LCD initialization.
*/
esp_err_t platform_lcd_set_backlight_brightness(uint8_t brightness_percent);
/**
* @brief Start LCD render loop.
*
* Calling this API while the render loop is already running is a no-op (idempotent).
* Use `platform_stop_lcd_render_loop()` to request termination.
*/
void platform_start_lcd_render_loop(void);
/**
* @brief Stop LCD render loop.
*
* Semantics:
* - `timeout_ms < 0`: invalid argument.
* - `timeout_ms == 0`: request cooperative stop and return immediately.
* - `timeout_ms > 0`: wait up to the bound, then force-delete the render task.
* - Repeated calls are idempotent.
*/
esp_err_t platform_stop_lcd_render_loop(int timeout_ms);
#define PLATFORM_LCD_PRESENTATION_FRAME_ORDER_MAX 16
#define PLATFORM_LCD_PRESENTATION_ASSET_DIR_MAX 32
#define PLATFORM_LCD_PRESENTATION_STEP_MAX 12
#define PLATFORM_LCD_PRESENTATION_NO_ICON 0xFFFFU
typedef enum {
PLATFORM_LCD_PRESENTATION_IDLE = 0,
PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
PLATFORM_LCD_PRESENTATION_SLOT_REELS,
PLATFORM_LCD_PRESENTATION_STEP_SEQUENCE,
} platform_lcd_presentation_kind_t;
typedef struct {
const char *asset_dir;
uint8_t frame_index;
uint16_t icon_id;
uint16_t icon_x;
uint16_t icon_y;
uint32_t delay_ms;
} platform_lcd_presentation_step_t;
typedef struct {
platform_lcd_presentation_kind_t kind;
uint8_t priority;
bool interrupt_current;
bool return_to_idle;
const char *asset_dir;
const uint8_t *frame_order;
size_t frame_order_count;
uint16_t repeat_count;
uint32_t frame_ms;
uint32_t duration_ms;
const platform_lcd_presentation_step_t *steps;
size_t step_count;
} platform_lcd_presentation_request_t;
typedef struct {
bool low_battery_visible;
bool charging_visible;
} platform_lcd_status_overlay_t;
/**
* @brief Set persistent LCD status overlays composited over presentation frames.
*
* The state is copied synchronously and remains active until replaced. The
* render task owns all PNG decoding and panel access.
*/
esp_err_t platform_lcd_set_status_overlay(const platform_lcd_status_overlay_t *overlay);
/**
* @brief Request a LCD presentation playback.
*
* Semantics:
* - Non-blocking.
* - Returns `ESP_ERR_INVALID_STATE` if the LCD is not initialized or is stopping.
* - Request data is copied during the call; caller-owned pointers only need to
* remain valid until this function returns.
* - Higher-priority interrupting requests replace the active presentation.
* - Same active/pending requests are coalesced and return `ESP_OK`.
*/
esp_err_t platform_lcd_request_presentation(const platform_lcd_presentation_request_t *request);
/**
* @brief Preload the decoded assets for a LCD presentation without changing display state.
*
* Semantics:
* - Queues the preload on the LCD render task and blocks until it completes or
* the internal safety timeout elapses.
* - Returns `ESP_ERR_INVALID_STATE` if the LCD is not initialized or is stopping.
* - Request data is copied during the call; caller-owned pointers only need to
* remain valid until this function returns.
* - Only asset-sequence presentations are supported; other kinds return
* `ESP_ERR_INVALID_ARG`.
* - The render task performs the preload between display frames and may drop
* older cached assets if memory is needed.
*/
esp_err_t platform_lcd_preheat_presentation(const platform_lcd_presentation_request_t *request);
/**
* @brief Read the LCD presentation completion counter.
*
* The counter increments each time a finite asset-sequence presentation
* finishes its configured cycles. Snapshot it before queuing a finite
* presentation, then pass the snapshot to
* `platform_lcd_wait_presentation_completion()`.
*/
uint32_t platform_lcd_presentation_completion_count(void);
/**
* @brief Block until a finite asset-sequence presentation completes.
*
* Semantics:
* - Blocks the calling task until the completion
* counter advances past `since_count`, or `timeout_ms` elapses.
* - `timeout_ms == 0` polls once and returns without waiting.
* - Returns `ESP_OK` on completion, `ESP_ERR_TIMEOUT` on timeout, and
* `ESP_ERR_INVALID_STATE` if the LCD is not initialized or is stopping.
* - Intended for finite presentations (`repeat_count > 0`); an infinite
* presentation never completes and will time out.
*/
esp_err_t platform_lcd_wait_presentation_completion(uint32_t since_count, uint32_t timeout_ms);
#ifdef __cplusplus
}
#endif