/* * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include #include #include #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)(void *user_data); void (*on_completed)(void *user_data); void (*on_stopped)(void *user_data); void (*on_stalled)(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; #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_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_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); 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); void platform_stepper_power_off(void); 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); bool platform_wifi_portal_sta_is_connected(void); 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 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(void); 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 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; /** * @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