wangzhibo
10 天以前 220e4fab27b7f419b3e89e2f2d28ae789aac09d7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
#ifndef __SYSTEM_H__
#define __SYSTEM_H__
 
#define APP_FIRMWARE_VERSION "1.0.0"
 
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#include <sys/time.h>
#include "esp_sntp.h"
#include "driver/gpio.h"
#include "driver/i2c_master.h"
#include <ds3231.h>
#include <stdbool.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_gc9d01.h>
 
#ifdef __cplusplus
extern "C" {
#endif
 
#define ALARM_MAX_NUM   10
#define ALARM_ID_MAX_LEN 32
#define ALARM_REPEAT_MON 0x01
#define ALARM_REPEAT_TUE 0x02
#define ALARM_REPEAT_WED 0x04
#define ALARM_REPEAT_THU 0x08
#define ALARM_REPEAT_FRI 0x10
#define ALARM_REPEAT_SAT 0x20
#define ALARM_REPEAT_SUN 0x40
#define ALARM_REPEAT_ALL 0x7F
 
#define MOUNT_POINT "/sdcard"
#define SDCARD_PATH_MAX_LEN 256
/* 跟读/泛听没有「课文只能 N 句」的业务上限。256 只是一次 MCP 下发的安全顶。 */
#define SDCARD_MP3_PLAYLIST_MAX 256
 
typedef struct {
    char path[SDCARD_PATH_MAX_LEN];
    float speed;
    int repeat;
    bool power_off_after;
} sdcard_mp3_playlist_item_t;
 
#define SCREEN_MIRROR_X false
#define SCREEN_MIRROR_Y false
#define SCREEN_SWAP_XY  false
 
#define WIFI_HISTORY_MAX_NUM 5
 
#define BOARD_MQTT_BROKER_HOST "124.222.109.197"
#define BOARD_MQTT_BROKER_PORT 2100
#define BOARD_MQTT_USERNAME "AI_XIAOZHI"
#define BOARD_MQTT_PASSWORD "V9qL7!mX2@rT8$kN5^wP1&c"
#define BOARD_MQTT_CMD_CTL_TOPIC_FORMAT "snlp/v1/%s/cmd/ctl"
#define BOARD_MQTT_CMD_TASK_TOPIC_FORMAT "snlp/v1/%s/cmd/task"
#define BOARD_MQTT_RPT_LOG_TOPIC_FORMAT "snlp/v1/%s/rpt/log"
#define BOARD_MQTT_RPT_ACK_TOPIC_FORMAT "snlp/v1/%s/rpt/ack"
#define BOARD_MQTT_RPT_PROGRESS_TOPIC_FORMAT "snlp/v1/%s/rpt/progress"
#define BOARD_MQTT_RPT_STATE_TOPIC_FORMAT "snlp/v1/%s/rpt/state"
#define BOARD_MQTT_RPT_EVENT_TOPIC_FORMAT "snlp/v1/%s/rpt/event"
#define BOARD_MQTT_EXECUTE_COMMANDS 1
 
/**
 * @brief Wi-Fi 待机低功耗超时时间,单位为秒。
 *
 * 当设备持续处于空闲待命状态超过该时间后,板级逻辑会断开 Wi-Fi station 以降低功耗,
 * 但不会关闭本地离线唤醒词检测。设置为 0 或负数可禁用该待机模式。
 */
#define WIFI_STANDBY_TIMEOUT_SEC 300
#define POWER_OFF_WAKE_RECONNECT_TIMEOUT_MS 30000
 
/**
 * @brief 板级 Custom/MultiNet 唤醒词配置。
 *
 * 如果需要修改智能体的名,要在控制台中修改小智的记忆。
 */
#define BOARD_CUSTOM_WAKE_WORD_PINYIN "ni hao xiao zhi"
#define BOARD_CUSTOM_WAKE_WORD_DISPLAY "你好小智"
#define BOARD_CUSTOM_WAKE_WORD_THRESHOLD_PERCENT 20
 
/**
 * @brief 板级 MultiNet 离线命令词。只留没网也必须能用的入口:
 * 「切换模式」「模式切换」「泛听」「开始泛听」「退出泛听」「退出跟读」「我们聊天吧」「我们来聊天吧」。
 * 泛听/跟读播音频时不上行 ASR,退出口令必须能离线识别。其它 App 的进入词仍只在 Server apps.json。
 */
#define OFFLINE_COMMAND_WORD "切换模式"
#define OFFLINE_COMMAND_WORD_PINYIN "qie huan mo shi"
#define OFFLINE_COMMAND_WORD_ALT "模式切换"
#define OFFLINE_COMMAND_WORD_ALT_PINYIN "mo shi qie huan"
#define OFFLINE_REPEATER_WORD "泛听"
#define OFFLINE_REPEATER_WORD_PINYIN "fan ting"
#define OFFLINE_START_REPEATER_WORD "开始泛听"
#define OFFLINE_START_REPEATER_WORD_PINYIN "kai shi fan ting"
#define OFFLINE_EXIT_REPEATER_WORD "退出泛听"
#define OFFLINE_EXIT_REPEATER_WORD_PINYIN "tui chu fan ting"
#define OFFLINE_EXIT_SHADOWING_WORD "退出跟读"
#define OFFLINE_EXIT_SHADOWING_WORD_PINYIN "tui chu gen du"
#define OFFLINE_CHAT_WORD "我们聊天吧"
#define OFFLINE_CHAT_WORD_PINYIN "wo men liao tian ba"
#define OFFLINE_CHAT_WORD_FULL "我们来聊天吧"
#define OFFLINE_CHAT_WORD_FULL_PINYIN "wo men lai liao tian ba"
 
void Board_Offline_Command_Callback(void);
bool Board_Handle_Offline_Command(const char *command);
bool Board_Is_Mode_Switch_Command(const char *command);
bool Board_Is_Chat_Return_Command(const char *command);
bool Board_Is_App_Launch_Command(const char *command);
 
/**
 * @brief 使用 SNTP 网络时间同步 DS3231 RTC。
 *
 * 该函数会启动 SNTP 客户端并在时间同步完成后写入 DS3231,适用于设备连接 Wi-Fi
 * 之后校准 RTC 时间。函数会保存传入的 DS3231 I2C 设备描述符,供 SNTP 同步完成
 * 回调继续使用。
 *
 * @param i2c_dev 已初始化的 DS3231 I2C 设备描述符,不能为空。
 */
void SNTP_To_DS2312(i2c_dev_t *i2c_dev);
 
/**
 * @brief 根据已保存的每日闹钟列表更新 DS3231 Alarm1。
 *
 * 该函数会读取持久化闹钟列表,选择相对于当前时间最近的未来闹钟,并写入
 * DS3231 Alarm1。若列表为空或读取失败,则会关闭 Alarm1。传入 time 时使用该
 * 时间作为当前时间基准;传入 NULL 时会优先从 DS3231 读取当前时间,失败后回退
 * 到系统时间。
 *
 * @param i2c_dev 已初始化的 DS3231 I2C 设备描述符,不能为空。
 * @param time 可选的当前时间基准;为 NULL 时由函数自行获取当前时间。
 */
void DS2312_Update_Alarm(i2c_dev_t *i2c_dev, struct tm *time);
 
/**
 * @brief 启动 DS3231 闹钟后台监控任务。
 *
 * 该函数会创建一个后台任务,每秒检查闹钟列表、当前时间和 DS3231 Alarm1 触发标志。
 * 如果检测到当前写入的 Alarm1 已不是最近的未来闹钟,或 Alarm1 已触发但没有外部
 * IO 通知,任务会主动清除标志并重新调度后续闹钟。重复调用不会创建多个监控任务。
 *
 * @param i2c_dev 已初始化的 DS3231 I2C 设备描述符,不能为空。
 */
void DS2312_Start_Alarm_Monitor(i2c_dev_t *i2c_dev);
 
/**
 * @brief 添加一个每日重复的 RTC 闹钟。
 *
 * 该函数会将指定时分秒写入持久化闹钟列表,并在保存成功后立即重新调度
 * DS3231 Alarm1。若时间参数非法、闹钟已存在或列表已满,函数会忽略本次添加。
 *
 * @param i2c_dev 已初始化的 DS3231 I2C 设备描述符,不能为空。
 * @param hour 小时,取值范围 0-23。
 * @param min 分钟,取值范围 0-59。
 * @param sec 秒,取值范围 0-59。
 */
void DS2312_Add_Alarm_Time(i2c_dev_t *i2c_dev, int hour, int min, int sec);
 
/**
 * @brief 获取当前保存的 RTC 闹钟列表。
 *
 * 返回值为 JSON 字符串,供 MCP 或其他上层逻辑直接解析。字符串由模块内部静态缓冲区
 * 保存,调用者不应释放或长期保存该指针;后续再次调用本函数可能覆盖原内容。
 *
 * @param i2c_dev 保留参数,用于保持接口与其他 DS2312 操作一致,当前实现不依赖该参数。
 * @return 指向内部静态缓冲区的 JSON 字符串。
 */
const char *DS2312_Get_Alarm_List(i2c_dev_t *i2c_dev);
 
/**
 * @brief 按索引删除一个已保存的 RTC 闹钟。
 *
 * index 对应 DS2312_Get_Alarm_List() 返回列表中的 index 字段。删除成功后会立即重新
 * 调度 DS3231 Alarm1;若索引非法或列表读取失败,函数不会修改现有闹钟配置。
 *
 * @param i2c_dev 已初始化的 DS3231 I2C 设备描述符,不能为空。
 * @param index 待删除闹钟的列表索引。
 */
void DS2312_Delete_Alarm_Index(i2c_dev_t *i2c_dev, uint8_t index);
 
/**
 * @brief 清空所有已保存的 RTC 闹钟。
 *
 * 该函数会清空持久化闹钟列表,并关闭 DS3231 Alarm1 中断配置,防止旧闹钟继续触发。
 *
 * @param i2c_dev 已初始化的 DS3231 I2C 设备描述符,不能为空。
 */
void DS2312_Delete_Alarm_All(i2c_dev_t *i2c_dev);
 
esp_err_t DS2312_Set_Unix_Time(i2c_dev_t *i2c_dev, time_t timestamp);
esp_err_t DS2312_Set_Unix_Time_Saved(time_t timestamp);
esp_err_t DS2312_Set_Alarm_Id(i2c_dev_t *i2c_dev, const char *id, int hour, int min, int sec, uint8_t repeat_mask);
esp_err_t DS2312_Delete_Alarm_Id(i2c_dev_t *i2c_dev, const char *id);
esp_err_t DS2312_Set_Alarm_Id_Saved(const char *id, int hour, int min, int sec, uint8_t repeat_mask);
esp_err_t DS2312_Delete_Alarm_Id_Saved(const char *id);
 
/**
 * @brief 获取 SD 卡中的 MP3 文件列表。
 *
 * 函数会递归遍历 SD 卡挂载目录,仅返回后缀为 .mp3 或 .MP3 的文件。子目录文件
 * 以 / 开头的 SD 卡相对路径返回。返回值为 JSON 字符串,字符串存放在模块内部静态缓冲区中,调用者不应释放该指针。
 *
 * @return 指向内部静态缓冲区的 MP3 文件列表 JSON 字符串。
 */
const char *SDCard_Get_MP3_List(void);
 
/**
 * @brief 按文件名启动 SD 卡 MP3 播放。
 *
 * mp3_name 可以是完整文件名,也可以是能够唯一匹配 MP3 文件名的关键字。若当前正在播放
 * 其他 MP3,会先停止旧任务再启动新播放;若当前处于小智播报暂停音乐的状态,新音乐会
 * 保持暂停,等待上层恢复。
 *
 * @param mp3_name MP3 文件名或匹配关键字,不能为空。
 * @return ESP_OK 表示播放任务已成功启动;其他值表示文件不存在、参数非法或播放启动失败。
 */
esp_err_t SDCard_Play_MP3(char *mp3_name);
 
#define SDCARD_MP3_SPEED_MIN 0.5f
#define SDCARD_MP3_SPEED_MAX 2.0f
#define SDCARD_MP3_SPEED_DEFAULT 1.0f
 
/**
 * @brief 将播放倍率夹紧到 [0.5, 2.0];NaN/非法则回退 1.0。
 *
 * 实现是先按 speed 做线性时域拉伸,再用文件原始采样率转到喇叭采样率。
 * 0.5~2.0 内任意浮点均可;非 1.0 会改变音调。不要把 sample_rate*speed 交给 AE 变采样。
 */
float SDCard_Clamp_MP3_Speed(float speed);
 
/**
 * @brief 按文件名和速度启动 SD 卡 MP3 播放。
 *
 * speed 取值 [0.5, 2.0],缺省 1.0;超出范围会被夹紧。非 1.0 通过重采样实现,会改变音调。
 *
 * @param mp3_name MP3 文件名或匹配关键字,不能为空。
 * @param speed 播放速度倍率。
 * @return ESP_OK 表示播放任务已成功启动;ESP_ERR_INVALID_ARG 表示文件名非法。
 */
esp_err_t SDCard_Play_MP3_With_Speed(char *mp3_name, float speed);
esp_err_t SDCard_Play_MP3_With_Speed_Repeat(char *mp3_name, float speed, int repeat, bool power_off_after);
void SDCard_Show_Matching_JPG_For_MP3_Path(const char *mp3_path);
 
/**
 * @brief 停止当前 SD 卡 MP3 播放。
 *
 * 该函数会取消当前播放任务,并清除暂停状态,确保后续不会因为小智播报结束而自动恢复
 * 已被用户停止的音乐。
 */
void SDCard_Stop_Play(void);
 
/**
 * @brief 暂停当前或即将启动的 SD 卡 MP3 播放。
 *
 * 若 MP3 正在播放,播放任务会在安全检查点阻塞输出;若当前没有播放任务,也会记录暂停
 * 状态,使随后启动的 MP3 在上层调用恢复接口前保持静音暂停。
 */
void SDCard_Pause_Play(void);
 
/**
 * @brief 恢复被暂停的 SD 卡 MP3 播放。
 *
 * 该函数会清除暂停标志,使正在等待的 MP3 播放任务继续读取、解码并输出音频。
 */
void SDCard_Resume_Play(void);
 
/**
 * @brief 查询 SD 卡 MP3 播放任务是否存在。
 *
 * @return true 表示当前存在 MP3 播放任务;false 表示没有正在运行的播放任务。
 */
bool SDCard_Is_Playing(void);
 
/**
 * @brief 课文开播前释放 AFE、退出聆听并等到内部 SRAM 够 SD DMA。
 *
 * 必须在创建 MP3 播放任务之前调用。AFE/MultiNet 和 SDMMC DMA 争用连续内部 SRAM
 * 时,fopen/sdmmc_read_blocks 会失败,严重时 newlib 锁初始化 abort 重启。
 */
void SDCard_PreparePlaybackAndWait(void);
 
/**
 * @brief 课文 MP3 已进入 PSRAM 后才允许离线口令 AFE。
 */
bool SDCard_Mp3OfflineWakeAllowed(void);
 
/**
 * @brief 查询 SD 卡 MP3 播放是否处于暂停状态。
 *
 * @return true 表示播放被暂停或后续新播放应从暂停状态开始;false 表示未暂停。
 */
bool SDCard_Is_Paused(void);
 
/**
 * @brief 删除 SD 卡中的 MP3 文件。
 *
 * 函数会根据文件名或匹配关键字解析目标 MP3 文件并执行删除。若文件不存在或名称非法,
 * 函数不会修改 SD 卡内容。
 *
 * @param mp3_name MP3 文件名或匹配关键字,不能为空。
 */
void SDCard_Delete_MP3(char *mp3_name);
 
/**
 * @brief 按完整路径启动 MP3 文件播放。
 *
 * 该接口面向模块内部或高级调用者,要求传入已经解析完成的 SD 卡文件路径。普通 MCP
 * 调用应优先使用 SDCard_Play_MP3(),由其负责文件名安全检查和路径解析。
 *
 * @param path MP3 文件完整路径,不能为空。
 * @return ESP_OK 表示播放任务已成功启动;其他值表示路径非法、播放器忙或任务创建失败。
 */
esp_err_t SDCard_Play_MP3_File(const char *path);
 
/**
 * @brief 按完整路径和速度启动 MP3 文件播放。
 *
 * speed 取值 [0.5, 2.0],缺省 1.0;超出范围会被夹紧。非 1.0 通过重采样实现,会改变音调。
 *
 * @param path MP3 文件完整路径,不能为空。
 * @param speed 播放速度倍率。
 * @return ESP_OK 表示播放任务已成功启动;ESP_ERR_INVALID_ARG 表示路径非法。
 */
esp_err_t SDCard_Play_MP3_File_With_Speed(const char *path, float speed);
esp_err_t SDCard_Play_MP3_File_With_Speed_Repeat(const char *path, float speed, int repeat, bool power_off_after);
bool SDCard_Resolve_MP3_Path(const char *mp3_name, char *path, size_t path_size);
esp_err_t SDCard_Play_MP3_File_Playlist(const sdcard_mp3_playlist_item_t *items, int count);
int SDCard_Get_Repeat_Left(void);
esp_err_t SDCard_Map_Business_Path(const char *business_path, char *sd_path, size_t sd_path_size);
esp_err_t SDCard_Ensure_Business_Dir(const char *business_path);
esp_err_t SDCard_Prepare_Business_File(const char *business_path,
                                       char *final_path,
                                       size_t final_path_size,
                                       char *part_path,
                                       size_t part_path_size);
esp_err_t SDCard_Commit_Part_File(const char *part_path, const char *final_path);
void SDCard_Remove_File_If_Exists(const char *path);
esp_err_t SDCard_Delete_File_By_Business_Path(const char *business_path);
esp_err_t SDCard_Save_Manifest(const char *course_id, const char *manifest_json);
 
typedef bool (*board_download_progress_cb_t)(int progress, const char *info, void *ctx);
esp_err_t Board_Download_File_To_Path(const char *url,
                                      const char *part_path,
                                      const char *expected_md5,
                                      board_download_progress_cb_t progress_cb,
                                      void *ctx,
                                      char *error_info,
                                      size_t error_info_size);
 
typedef void (*board_ota_progress_cb_t)(int progress, size_t speed, void *ctx);
typedef void (*board_ota_complete_cb_t)(bool success, void *ctx);
esp_err_t Board_Start_Firmware_Upgrade(const char *url,
                                       const char *version,
                                       board_ota_progress_cb_t progress_cb,
                                       board_ota_complete_cb_t complete_cb,
                                       void *ctx);
 
/**
 * @brief 获取 SD 卡中的 JPG 图片列表。
 *
 * 函数会遍历 SD 卡挂载目录,仅返回后缀为 .jpg 或 .JPG 的图片文件名。尺寸校验在显示图片时执行,
 * 避免列表查询阶段逐个解码图片导致 MCP 响应变慢。返回值为 JSON 字符串,存放在模块内部静态缓冲区中,调用者不应释放该指针。
 *
 * @return 指向内部静态缓冲区的 JPG 图片列表 JSON 字符串。
 */
const char *SDCard_Get_JPG_List(void);
 
/**
 * @brief 在 LCD 面板上显示 SD 卡中的 JPG 图片。
 *
 * 函数会根据文件名解析图片路径,读取并解码 160x160 JPG,完成必要的 RGB565/BGR565
 * 像素格式转换后绘制到指定 LCD 面板。显示成功后会保存最后一次显示的图片名称,用于
 * 下次开机恢复显示。
 *
 * @param panel 已初始化的 LCD panel 句柄,不能为空。
 * @param jpg_name JPG 文件名,不能为空。
 */
void SDCard_Play_JPG(esp_lcd_panel_handle_t panel, char *jpg_name);
 
/**
 * @brief 删除 SD 卡中的 JPG 图片文件。
 *
 * 函数会根据文件名解析目标 JPG 文件并执行删除。若文件不存在、名称非法或删除失败,
 * 函数不会影响其他图片文件。
 *
 * @param jpg_name JPG 文件名,不能为空。
 */
void SDCard_Delete_JPG(char *jpg_name);
 
/**
 * @brief 恢复显示上一次成功显示的 JPG 图片。
 *
 * 函数会从 NVS 中读取最后一次显示的 JPG 文件名,并在确认文件仍然存在后重新显示。
 * 适合在 LCD 初始化和 SD 卡挂载完成后调用。
 *
 * @param panel 已初始化的 LCD panel 句柄,不能为空。
 */
void SDCard_Restore_Last_JPG(esp_lcd_panel_handle_t panel);
 
/**
 * @brief 获取当前电池电量百分比。
 *
 * 函数会读取电池 ADC 引脚电压,结合板级分压参数换算为单节锂电池电压,并映射到
 * 0-100 的百分比范围。返回值用于 MCP 电池状态和小智设备状态上报。
 *
 * @return 电池电量百分比,范围 0-100。
 */
uint8_t Battery_Get_Value(void);
 
/**
 * @brief 获取当前充电状态。
 *
 * 函数会读取充电检测 GPIO,并按照板级硬件定义判断是否正在充电。
 *
 * @return true 表示正在充电;false 表示未处于充电状态。
 */
bool Battery_Is_Charging(void);
 
/**
 * @brief 获取已保存的 Wi-Fi 历史列表。
 *
 * 返回值为 JSON 字符串,仅包含 SSID 和索引,不包含密码等敏感信息。字符串由模块内部
 * 静态对象保存,调用者不应释放该指针;后续再次调用本函数可能覆盖原内容。
 *
 * @return 指向内部静态缓冲区的 Wi-Fi 历史列表 JSON 字符串。
 */
const char *WIFI_Get_History_List(void);
 
/**
 * @brief 清空已保存的 Wi-Fi 历史列表。
 *
 * 该函数会删除持久化保存的 Wi-Fi SSID 和密码,并清空内存中的历史列表。函数不会主动
 * 断开当前 Wi-Fi 连接;清空后通常需要重启设备,设备会因无历史 Wi-Fi 而重新进入配网流程。
 */
void WIFI_Delete_All(void);
 
uint8_t Board_Get_CPU_Usage(void);
esp_err_t Board_Set_Output_Volume(int level);
int Board_Get_Output_Volume(void);
esp_err_t Board_Enter_Standby(void);
esp_err_t Board_Request_Wake(int timeout_ms);
esp_err_t Board_Enter_Power_Off(void);
esp_err_t Board_Restore_From_Power_Off(int timeout_ms);
bool Board_Is_Power_Off(void);
bool Board_Get_Wall_Clock(struct tm *out);
esp_err_t DS2312_Set_Schedule_Wake(int hour, int min, int sec, uint8_t weekday_bit);
void DS2312_Clear_Schedule_Wake(void);
esp_err_t Board_Reboot_Delayed(int delay_seconds);
void Board_Acquire_Sleep_Inhibit(void);
void Board_Release_Sleep_Inhibit(void);
bool Board_Is_Voice_Session_Active(void);
void Board_HoldIdleForServerPrompt(void);
void Board_ReleaseHoldIdle(void);
void Board_AnnounceLessonSyncAndWait(void);
void Board_SetLessonDownloadBoost(bool enable);
bool Board_Is_Audio_Busy(void);
void Board_Show_JPG(const char *jpg_name);
void Board_Show_Black_Screen(void);
void Board_Show_Alert(const char *status, const char *message, const char *emotion);
 
#ifdef __cplusplus
}
#endif
 
#endif