本車載智能設(shè)備的設(shè)計(jì)理念是將安全、智能與舒適完美融合。通過(guò)多種高精度傳感器、AI處理技術(shù)和智能交互設(shè)計(jì),我們?yōu)檐囍魈峁┝艘粋€(gè)全面、便捷且智能的出行伴侶。從實(shí)時(shí)環(huán)境監(jiān)控到超速提醒,從人臉解鎖到語(yǔ)音助手,每一項(xiàng)功能都緊密圍繞車主的需求,致力于提升駕駛安全性、舒適性以及便捷性。無(wú)論是長(zhǎng)途旅行還是日常出行,這款設(shè)備都將成為您在路上的智能助手,讓每一次旅程都充滿安心與愉悅。所有功能都無(wú)需聯(lián)網(wǎng)就可以完成.
模塊清單
1.M5Cores3 ?
2.M5Stack Module ??LLM
3.M5Stack SPG30
4.M5Stack BME680
5.M5Stack STHS34PF80
6.M5Stack Pahub
7.M5Stack GPS
8.M5Stack Grove-T
9.M5Stack ??Base?? M5GO Bottom3
10.Grove * 5
11.樂(lè)高板一塊,配件若干
一、 ??AI?人臉識(shí)別?與管理功能
基于ESP32 S3的AI處理能力,設(shè)備通過(guò)車載攝像頭進(jìn)行人臉數(shù)據(jù)錄入和管理,實(shí)現(xiàn)身份識(shí)別和權(quán)限控制。車主可通過(guò)簡(jiǎn)單的人臉驗(yàn)證輕松解鎖設(shè)備,提高車載設(shè)備的安全性和私密性。
人臉?shù)浫?/p>
鎖屏之后會(huì)開(kāi)啟tmos檢測(cè),如果檢測(cè)到人在.會(huì)主動(dòng)打開(kāi)攝像頭進(jìn)行人臉識(shí)別.識(shí)別通過(guò)才能進(jìn)入主菜單
二、 鎖屏狀態(tài)下的人臉解鎖功能
設(shè)備處于鎖屏狀態(tài)時(shí),車主可以通過(guò)實(shí)時(shí)的人臉識(shí)別功能進(jìn)行解鎖。系統(tǒng)會(huì)自動(dòng)檢測(cè)攝像頭前是否有人臉,確保快速、安全地驗(yàn)證身份。
鎖屏
三、 ??Web Server?與WiFi?配置管理
通過(guò)WiFi AP模式開(kāi)啟Web Server,車主可以輕松連接設(shè)備,遠(yuǎn)程進(jìn)行配置和管理。用戶可以通過(guò)直觀的網(wǎng)頁(yè)界面實(shí)時(shí)查看設(shè)備狀態(tài),調(diào)整參數(shù),確保設(shè)備始終保持最佳狀態(tài)。
?配置項(xiàng)?**:**
通過(guò)點(diǎn)擊Setting 菜單進(jìn)入配置功能.屏幕上會(huì)顯示當(dāng)前配置的值.已經(jīng)內(nèi)置了默認(rèn)值.
同時(shí)開(kāi)啟一個(gè)wifi ap 服務(wù)打開(kāi)一個(gè)http server 使用手機(jī)連接wifi 進(jìn)行配置.并實(shí)時(shí)展示屏幕上和做持久化處理.下次啟動(dòng)可以保持配置內(nèi)容
四、 GPS時(shí)間與車速同步,智能導(dǎo)航展示
系統(tǒng)基于高精度GPS模塊,實(shí)時(shí)同步車載設(shè)備的時(shí)間和日期,確保您始終掌握準(zhǔn)確的時(shí)刻。同時(shí),智能車速檢測(cè)與目標(biāo)距離設(shè)定功能幫助系統(tǒng)精準(zhǔn)顯示行駛方向、車速和位置,讓每段旅程更加智能與高效。車輛速度超過(guò)設(shè)定的安全限值時(shí),系統(tǒng)會(huì)自動(dòng)發(fā)出超速警告,提醒駕駛員及時(shí)減速,降低事故風(fēng)險(xiǎn),保障行車安全。
五、 智能環(huán)境監(jiān)測(cè)與實(shí)時(shí)臨界值提醒
內(nèi)置BME688與SPG30傳感器,實(shí)時(shí)監(jiān)測(cè)車內(nèi)溫濕度與CO2濃度。當(dāng)檢測(cè)到數(shù)據(jù)超出設(shè)定的臨界值時(shí),系統(tǒng)會(huì)自動(dòng)發(fā)出警報(bào),幫助車主調(diào)整車內(nèi)環(huán)境,確保乘車人員的健康與舒適
六、 實(shí)時(shí)語(yǔ)音助手,旅途中的小幫手
LLM語(yǔ)音模塊內(nèi)置Qwen0.5b 大模型,支持實(shí)時(shí)語(yǔ)音對(duì)話功能。車主可以隨時(shí)喚醒語(yǔ)音助手進(jìn)行語(yǔ)音詢問(wèn),甚至解決旅途中的問(wèn)題,讓駕駛過(guò)程更加輕松愉悅。
對(duì)話
通過(guò)設(shè)置項(xiàng)中的weakup word的喚起詞進(jìn)行喚起對(duì)話,默認(rèn)值是你好小明
軟件設(shè)計(jì)
功能列表
啟動(dòng)流程
啟動(dòng)
未人臉通過(guò)識(shí)別
通過(guò)人臉識(shí)別
代碼:
入口文件
#include <M5CoreS3.h>
#include <Wire.h>
#include "lvgl.h"
#include "m5gfx_lvgl.h"
#include "apps/app_launcher/app_launcher.h"
#include "LittleFS.h"
#include "./shared/shared.h"
void setup() {
Serial.begin(115200);
SharedData::Load();
if (!LittleFS.begin(true)) {
printf("LittleFS Mount Failed");
return;
}
printf("LittleFS mounted successfully");
auto cfg = M5.config();
CoreS3.begin(cfg);
int8_t pin_num_sda = CoreS3.Ex_I2C.getSDA();
uint8_t pin_num_scl = CoreS3.Ex_I2C.getSCL();
printf("getPin: SDA:%u SCL:%un", pin_num_sda, pin_num_scl);
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
lv_init();
m5gfx_lvgl_init();
}
void loop() {
auto app_launcher = GetMooncake().installApp(std::make_unique<AppLauncher>());
GetMooncake().openApp(app_launcher);
while (true) {
CoreS3.update();
GetMooncake().update();
lv_timer_handler();
}
}
UI管理
#pragma once
#include <mooncake.h>
#include "../../shared/shared.h"
#include "ui/ui.h"
#include "worker/worker.h"
using namespace mooncake;
class AppLauncher : public AppAbility {
public:
AppLauncher() { setAppInfo().name = "AppLauncher"; }
void onOpen() override {
int splash_ui = GetMooncake().createExtension(std::make_unique<SplashUIAbility>());
int menu_ui = GetMooncake().createExtension(std::make_unique<MenusUIAbility>());
int camera_ui = GetMooncake().createExtension(std::make_unique<CameraUIAbility>());
int env_ui = GetMooncake().createExtension(std::make_unique<ENVUIAbility>());
int gps_ui = GetMooncake().createExtension(std::make_unique<GPSUIAbility>());
int setting_ui = GetMooncake().createExtension(std::make_unique<SettingUIAbility>());
int info_ui = GetMooncake().createExtension(std::make_unique<InfoUIAbility>());
int compass_ui = GetMooncake().createExtension(std::make_unique<CompassUIAbility>());
int blank_ui = GetMooncake().createExtension(std::make_unique<BlankUIAbility>());
GetMooncake().extensionManager()->hideUIAbility(splash_ui);
GetMooncake().extensionManager()->hideUIAbility(camera_ui);
GetMooncake().extensionManager()->hideUIAbility(gps_ui);
GetMooncake().extensionManager()->hideUIAbility(env_ui);
GetMooncake().extensionManager()->showUIAbility(menu_ui);
GetMooncake().extensionManager()->hideUIAbility(setting_ui);
GetMooncake().extensionManager()->hideUIAbility(info_ui);
GetMooncake().extensionManager()->hideUIAbility(compass_ui);
GetMooncake().extensionManager()->hideUIAbility(blank_ui);
int tmos_worker = GetMooncake().createExtension(std::make_unique<TMOSWorkerAbility>());
int llm_worker = GetMooncake().createExtension(std::make_unique<LLMWorkerAbility>());
int hmi_worker = GetMooncake().createExtension(std::make_unique<HMIWorkerAbility>());
GetMooncake().extensionManager()->pauseWorkerAbility(hmi_worker);
SharedData::Get().uiHandler.splash_ui = splash_ui;
SharedData::Get().uiHandler.menu_ui = menu_ui;
SharedData::Get().uiHandler.camera_ui = camera_ui;
SharedData::Get().uiHandler.env_ui = env_ui;
SharedData::Get().uiHandler.gps_ui = gps_ui;
SharedData::Get().uiHandler.setting_ui = setting_ui;
SharedData::Get().uiHandler.info_ui = info_ui;
SharedData::Get().uiHandler.compass_ui = compass_ui;
SharedData::Get().uiHandler.blank_ui = blank_ui;
SharedData::Get().workerHandler.tmos_worker = tmos_worker;
SharedData::Get().workerHandler.llm_worker = llm_worker;
SharedData::Get().workerHandler.hmi_worker = hmi_worker;
}
void onRunning() override {}
};
菜單 UI
#include "../../../shared/shared.h"
#include "../ui/camera.h"
#include "./assets/images/images.h"
#include <ability/ability.h>
using namespace mooncake;
static void btn_event_cb(lv_event_t *e) {
char menus[9] = {SharedData::Get().uiHandler.gps_ui,
SharedData::Get().uiHandler.env_ui,
SharedData::Get().uiHandler.camera_ui,
SharedData::Get().uiHandler.camera_ui,
SharedData::Get().uiHandler.info_ui,
SharedData::Get().uiHandler.setting_ui,
SharedData::Get().uiHandler.splash_ui,
SharedData::Get().uiHandler.compass_ui,
SharedData::Get().uiHandler.blank_ui};
lv_obj_t *btn = lv_event_get_target(e);
int btn_id = reinterpret_cast<int>(lv_obj_get_user_data(btn));
AbilityManager *abilityManager = GetMooncake().extensionManager();
abilityManager->hideUIAbility(SharedData::Get().uiHandler.menu_ui);
abilityManager->showUIAbility(menus[btn_id]);
if (btn_id == 2) {
CameraUIAbility *ability =
(CameraUIAbility *)abilityManager->getAbilityInstance(
SharedData::Get().uiHandler.camera_ui);
ability->setMode(0);
} else {
CameraUIAbility *ability =
(CameraUIAbility *)abilityManager->getAbilityInstance(
SharedData::Get().uiHandler.camera_ui);
ability->setMode(1);
}
}
class MenusUIAbility : public UIAbility {
private:
lv_obj_t *container = nullptr;
lv_obj_t *label_vaild = nullptr;
public:
void onCreate() override {
LV_IMG_DECLARE(image_setting);
container = lv_obj_create(lv_scr_act());
lv_obj_set_size(container, LV_HOR_RES, LV_VER_RES);
lv_obj_set_style_bg_color(container, lv_color_hex(0x000000), 0);
lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0);
lv_obj_clear_flag(container, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_clear_flag(container, LV_OBJ_FLAG_GESTURE_BUBBLE);
create_ui(container);
}
void create_button(lv_obj_t *parent, int index, const char *icon,
const char *text) {
lv_obj_t *btn = lv_btn_create(parent);
lv_obj_set_grid_cell(btn, LV_GRID_ALIGN_STRETCH, index % 3, 1,
LV_GRID_ALIGN_STRETCH, index / 3, 1);
// 設(shè)置按鈕的用戶數(shù)據(jù)
lv_obj_set_user_data(btn, reinterpret_cast<void *>(index));
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
// 設(shè)置為 Flex 布局
lv_obj_set_flex_flow(btn, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(btn, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER);
lv_obj_t *icon_label = lv_label_create(btn);
lv_label_set_text(icon_label, icon);
lv_obj_t *text_label = lv_label_create(btn);
lv_label_set_text(text_label, text);
}
void create_ui(lv_obj_t *parent) {
label_vaild = lv_label_create(parent);
lv_obj_align(label_vaild, LV_ALIGN_TOP_MID, 0, -14);
lv_obj_set_style_text_color(label_vaild, lv_color_hex(0xFF0000), 0);
static lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1),
LV_GRID_TEMPLATE_LAST}; // 3 列
static lv_coord_t row_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1),
LV_GRID_TEMPLATE_LAST}; // 3 行
// 創(chuàng)建網(wǎng)格布局容器
lv_obj_t *grid = lv_obj_create(parent);
lv_obj_set_size(grid, 300, 200); // 適配屏幕
lv_obj_center(grid);
lv_obj_set_layout(grid, LV_LAYOUT_GRID);
lv_obj_set_grid_dsc_array(grid, col_dsc, row_dsc);
const struct {
const char *icon;
const char *text;
} sensors[] = {{LV_SYMBOL_GPS, "GPS"}, {LV_SYMBOL_HOME, "ENV"},
{LV_SYMBOL_SAVE, "Record"}, {LV_SYMBOL_CHARGE, "Verify"},
{LV_SYMBOL_POWER, "Info"}, {LV_SYMBOL_SETTINGS, "Setting"},
{LV_SYMBOL_POWER, "Lock"}, {LV_SYMBOL_WARNING, "Compass"},
{LV_SYMBOL_SHUFFLE, "Blank"}};
for (int i = 0; i < 9; i++) {
create_button(grid, i, sensors[i].icon, sensors[i].text);
}
}
void onShow() override {
if (container != nullptr) {
lv_obj_clear_flag(container, LV_OBJ_FLAG_HIDDEN);
}
}
void onHide() override {
if (container != nullptr) {
lv_obj_add_flag(container, LV_OBJ_FLAG_HIDDEN);
}
}
void onForeground() override {
lv_label_set_text(label_vaild, SharedData::Get().status.is_valid ? "" : "no vaild");
}
};
GPS UI
#include "../utils/page.h"
#include <TinyGPSPlus.h>
#include <ability/ability.h>
#include <lvgl.h>
#include "../../../shared/shared.h"
#include "../utils/gps.h"
#include "./assets/images/images.h"
using namespace mooncake;
static void timer_cb(lv_timer_t *timer);
class GPSUIAbility : public UIAbility {
private:
TinyGPSPlus gps;
HardwareSerial gpsSerial;
lv_obj_t *container = nullptr;
public:
String current_date = "--------";
String utc_time = "";
String latitude = "--.--";
String longitude = "--.--";
String altitude = "--.--";
double speed = 0.0;
String hdop = "-";
String satellites = "-.-";
double distance = 0.0;
double courseTo = 0.0;
String direction = "---";
lv_obj_t *text_label_date;
lv_obj_t *text_label_latitude;
lv_obj_t *text_label_longitude;
lv_obj_t *text_label_altitude;
// lv_obj_t *text_label_speed;
lv_obj_t *text_label_utc_time;
lv_obj_t *text_label_hdop_satellites;
lv_obj_t *text_label_gps_data;
lv_obj_t *text_label_speed_container;
lv_obj_t *text_label_speed_limit;
lv_timer_t *timer = NULL;
GPSUIAbility() : gpsSerial(1) { gpsSerial.begin(9600, SERIAL_8N1, 8, 9); }
~GPSUIAbility() { printf("[ui] on deconstructn"); }
void onShow() override {
if (container == nullptr) {
static lv_style_t cont_style;
lv_style_init(&cont_style);
lv_style_set_bg_opa(&cont_style, 0);
lv_style_set_radius(&cont_style, 0);
lv_style_set_pad_all(&cont_style, 0);
container = lv_obj_create(lv_scr_act());
lv_obj_set_size(container, LV_HOR_RES, LV_VER_RES);
lv_obj_clear_flag(container, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_clear_flag(container, LV_OBJ_FLAG_GESTURE_BUBBLE);
lv_obj_add_style(container, &cont_style, 0);
create_ui(container);
} else {
lv_timer_resume(timer);
lv_obj_clear_flag(container, LV_OBJ_FLAG_HIDDEN);
}
}
void onHide() override {
if (container != nullptr) {
lv_timer_pause(timer);
lv_obj_add_flag(container, LV_OBJ_FLAG_HIDDEN);
}
}
void create_ui(lv_obj_t *parent) {
LV_IMG_DECLARE(image_gpsmap);
lv_obj_t *img_gpsmap = lv_img_create(parent);
lv_obj_align(img_gpsmap, LV_ALIGN_LEFT_MID, 10, -20);
lv_img_set_src(img_gpsmap, &image_gpsmap);
text_label_hdop_satellites = lv_label_create(parent);
lv_label_set_text(text_label_hdop_satellites,
String("HDOP " + hdop + " SAT. " + satellites).c_str());
lv_obj_align(text_label_hdop_satellites, LV_ALIGN_BOTTOM_LEFT, 30, -50);
lv_obj_set_style_text_font((lv_obj_t *)text_label_hdop_satellites,
&lv_font_montserrat_12, 0);
lv_obj_set_style_text_color((lv_obj_t *)text_label_hdop_satellites,
lv_palette_main(LV_PALETTE_GREY), 0);
text_label_date = lv_label_create(parent);
lv_label_set_text(text_label_date, current_date.c_str());
lv_obj_align(text_label_date, LV_ALIGN_CENTER, 60, -80);
lv_obj_set_style_text_font((lv_obj_t *)text_label_date,
&lv_font_montserrat_26, 0);
lv_obj_set_style_text_color((lv_obj_t *)text_label_date,
lv_palette_main(LV_PALETTE_TEAL), 0);
lv_obj_t *text_label_location = lv_label_create(parent);
lv_label_set_text(text_label_location, "LOCATION");
lv_obj_align(text_label_location, LV_ALIGN_CENTER, 50, -40 - 10);
lv_obj_set_style_text_font((lv_obj_t *)text_label_location,
&lv_font_montserrat_20, 0);
text_label_gps_data = lv_label_create(parent);
lv_label_set_text(text_label_gps_data,
String("LAT " + latitude + "nLON " + longitude +
"nALT " + altitude + "m" + "nSPEED " +
String(speed, 2) + "km/h" + "nDISTANCE " +
String(distance) + "km" + "nDIRECTION " +
direction)
.c_str());
lv_obj_align(text_label_gps_data, LV_ALIGN_CENTER, 60, 20);
lv_obj_set_style_text_font((lv_obj_t *)text_label_gps_data,
&lv_font_montserrat_14, 0);
text_label_utc_time = lv_label_create(parent);
lv_label_set_text(text_label_utc_time,
String("UTC TIME - " + utc_time).c_str());
lv_obj_align(text_label_utc_time, LV_ALIGN_BOTTOM_MID, 0, -10);
lv_obj_set_style_text_font((lv_obj_t *)text_label_utc_time,
&lv_font_montserrat_20, 0);
lv_obj_set_style_text_color((lv_obj_t *)text_label_utc_time,
lv_palette_main(LV_PALETTE_TEAL), 0);
text_label_speed_container = lv_obj_create(parent); // 創(chuàng)建一個(gè)容器對(duì)象
lv_obj_set_size(text_label_speed_container, 300, 50); // 設(shè)置容器大小
lv_obj_align(text_label_speed_container, LV_ALIGN_CENTER, 0,
0); // 容器居中對(duì)齊
lv_obj_set_scrollbar_mode(text_label_speed_container,
LV_SCROLLBAR_MODE_OFF);
lv_obj_set_style_bg_color(text_label_speed_container,
lv_palette_main(LV_PALETTE_RED), 0);
lv_obj_add_flag(text_label_speed_container, LV_OBJ_FLAG_HIDDEN);
// 創(chuàng)建標(biāo)簽并添加到容器中
text_label_speed_limit = lv_label_create(text_label_speed_container);
lv_label_set_text(text_label_speed_limit, "Speed Limit: 60 km/h");
lv_obj_align(text_label_speed_limit, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_text_font(text_label_speed_limit, &lv_font_montserrat_20,
0);
lv_obj_set_style_text_color(text_label_speed_limit,
lv_color_make(255, 255, 255), 0);
lv_obj_t *fullscreen_obj = lv_obj_create(parent);
// 設(shè)置對(duì)象的大小為屏幕大小
lv_obj_set_size(fullscreen_obj, LV_HOR_RES, LV_VER_RES);
// 設(shè)置背景透明
lv_obj_set_style_bg_color(fullscreen_obj, lv_color_hex(0x000000), 0);
lv_obj_set_style_bg_opa(fullscreen_obj, LV_OPA_TRANSP, 0); // 設(shè)置為完全透明
lv_obj_add_event_cb(
fullscreen_obj,
[](lv_event_t *e) {
auto &uiHandler = SharedData::Get().uiHandler;
SwitchUIAbility(uiHandler.gps_ui, uiHandler.menu_ui);
},
LV_EVENT_CLICKED, NULL);
timer = lv_timer_create(timer_cb, 1000, this);
lv_timer_ready(timer);
}
void upodateGPS() {
while (gpsSerial.available() > 0) {
gps.encode(gpsSerial.read());
}
if (gps.location.isUpdated()) {
latitude = String(gps.location.lat(), 6);
longitude = String(gps.location.lng(), 6);
speed = gps.speed.kmph();
altitude = String(gps.altitude.meters(), 2);
hdop = String(gps.hdop.value() / 100.0, 2);
satellites = String(gps.satellites.value());
current_date = String(gps.date.year()) + "-" + String(gps.date.month()) +
"-" + String(gps.date.day());
if (SharedData::Get().setting.date != current_date) {
SharedData::Get().setting.date = current_date;
SharedData::Save();
}
int adjusted_hour = gps.time.hour() + 8;
if (adjusted_hour >= 24) {
adjusted_hour -= 24;
}
utc_time = String(format_time(adjusted_hour)) + ":" +
String(format_time(gps.time.minute())) + ":" +
String(format_time(gps.time.second()));
const double DISTANCE_LAT = SharedData::Get().setting.distance_lat;
const double DISTANCE_LNG = SharedData::Get().setting.distance_lng;
distance =
TinyGPSPlus::distanceBetween(gps.location.lat(), gps.location.lng(),
DISTANCE_LAT, DISTANCE_LNG) /
1000.0;
courseTo = TinyGPSPlus::courseTo(gps.location.lat(), gps.location.lng(),
DISTANCE_LAT, DISTANCE_LNG);
direction = TinyGPSPlus::cardinal(courseTo);
if (speed >= SharedData::Get().setting.speed_limit) {
lv_obj_clear_flag(text_label_speed_container, LV_OBJ_FLAG_HIDDEN);
lv_obj_set_style_bg_color(text_label_gps_data,
lv_palette_main(LV_PALETTE_RED), 0);
} else {
lv_obj_add_flag(text_label_speed_container, LV_OBJ_FLAG_HIDDEN);
lv_obj_set_style_text_color(text_label_gps_data,
lv_palette_main(LV_PALETTE_NONE), 0);
}
}
}
};
static void timer_cb(lv_timer_t *timer) {
GPSUIAbility *self = static_cast<GPSUIAbility *>(timer->user_data);
if (self) {
self->upodateGPS();
lv_label_set_text(self->text_label_date, self->current_date.c_str());
lv_label_set_text(
self->text_label_hdop_satellites,
String("HDOP " + self->hdop + " SAT. " + self->satellites).c_str());
lv_label_set_text(self->text_label_gps_data,
String("LAT " + self->latitude + "nLON " +
self->longitude + "nALT " + self->altitude +
"m" + "nSPEED " + String(self->speed, 2) +
"km/h" + "nDISTANCE " + String(self->distance) +
"km" + "nDIRECTION " + self->direction)
.c_str());
lv_label_set_text(self->text_label_utc_time,
String("UTC+8 TIME - " + self->utc_time).c_str());
}
}
ENV UI
#include "../../../shared/shared.h"
#include "../utils/env.h"
#include "../utils/page.h"
#include "./assets/images/images.h"
#include <ability/ability.h>
using namespace mooncake;
static void env_timer_cb(lv_timer_t *timer);
void updateWarningLabel(lv_obj_t *label, lv_obj_t *container, const char *text,
lv_obj_t *text_label, bool show_warning) {
if (show_warning) {
lv_obj_clear_flag(container, LV_OBJ_FLAG_HIDDEN); // 顯示容器
lv_label_set_text(label, text); // 設(shè)置警告文本
lv_obj_set_style_text_color(text_label, lv_palette_main(LV_PALETTE_RED),
0); // 設(shè)置紅色文本
} else {
lv_obj_add_flag(container, LV_OBJ_FLAG_HIDDEN); // 隱藏容器
lv_obj_set_style_text_color(text_label, lv_palette_main(LV_PALETTE_NONE),
0); // 恢復(fù)原色
}
}
class ENVUIAbility : public UIAbility {
private:
lv_obj_t *container = nullptr;
lv_obj_t *label_temp, *label_humi, *label_co2, *label_tvoc;
lv_obj_t *label_pressure, *label_iaq, *label_gas;
public:
String location = "Beijing";
String current_date = "2025-02-10";
String last_weather_update = "";
String temperature = "--.--";
String humidity = "--.--";
String pressure = "---.-";
String co2 = "---";
int is_day;
int weather_code = 0;
String weather_description = "CLEAR SKY";
static constexpr const char *degree_symbol = "u00B0C";
ENVSensor env_sensor;
lv_obj_t *weather_image;
lv_obj_t *text_label_date;
lv_obj_t *text_label_temperature;
lv_obj_t *text_label_humidity;
lv_obj_t *text_label_pressure;
lv_obj_t *text_label_co2;
lv_obj_t *text_label_weather_description;
lv_obj_t *text_label_time_location;
lv_obj_t *text_label_env_container;
lv_obj_t *text_label_env_tip;
lv_timer_t *timer = nullptr;
void onCreate() override { env_sensor.begin(); }
void create_ui(lv_obj_t *parent) {
LV_IMG_DECLARE(image_weather_sun);
LV_IMG_DECLARE(image_temperature);
LV_IMG_DECLARE(image_pressure);
LV_IMG_DECLARE(image_humidity);
LV_IMG_DECLARE(image_co2);
lv_obj_add_event_cb(
parent,
[](lv_event_t *e) {
auto &uiHandler = SharedData::Get().uiHandler;
SwitchUIAbility(uiHandler.env_ui, uiHandler.menu_ui);
},
LV_EVENT_CLICKED, NULL);
weather_image = lv_img_create(parent);
lv_obj_align(weather_image, LV_ALIGN_CENTER, -80, -20);
text_label_date = lv_label_create(parent);
lv_label_set_text(text_label_date, current_date.c_str());
lv_obj_align(text_label_date, LV_ALIGN_CENTER, 70, -70 - 20);
lv_obj_set_style_text_font(text_label_date, &lv_font_montserrat_26, 0);
lv_obj_set_style_text_color(text_label_date,
lv_palette_main(LV_PALETTE_TEAL), 0);
lv_obj_t *weather_image_temperature = lv_img_create(parent);
lv_img_set_src(weather_image_temperature, &image_temperature);
lv_obj_align(weather_image_temperature, LV_ALIGN_CENTER, 30, -25 - 30);
text_label_temperature = lv_label_create(parent);
lv_label_set_text(text_label_temperature,
String(" " + temperature + degree_symbol).c_str());
lv_obj_align(text_label_temperature, LV_ALIGN_CENTER, 70, -25 - 30);
lv_obj_set_style_text_font(text_label_temperature, &lv_font_montserrat_22,
0);
lv_obj_t *weather_image_humidity = lv_img_create(parent);
lv_img_set_src(weather_image_humidity, &image_humidity);
lv_obj_align(weather_image_humidity, LV_ALIGN_CENTER, 30, 20 - 45);
text_label_humidity = lv_label_create(parent);
lv_label_set_text(text_label_humidity,
String(" " + humidity + "%").c_str());
lv_obj_align(text_label_humidity, LV_ALIGN_CENTER, 70, 20 - 45);
lv_obj_set_style_text_font(text_label_humidity, &lv_font_montserrat_22, 0);
lv_obj_t *weather_image_pressure = lv_img_create(parent);
lv_img_set_src(weather_image_pressure, &image_pressure);
lv_obj_align(weather_image_pressure, LV_ALIGN_CENTER, 30, 65 - 60);
text_label_pressure = lv_label_create(parent);
lv_label_set_text(text_label_pressure, " 100 kPa");
lv_obj_align(text_label_pressure, LV_ALIGN_CENTER, 70 + 20, 65 - 60);
lv_obj_set_style_text_font(text_label_pressure, &lv_font_montserrat_22, 0);
lv_obj_t *weather_image_co2 = lv_img_create(parent);
lv_img_set_src(weather_image_co2, &image_co2);
lv_obj_align(weather_image_co2, LV_ALIGN_CENTER, 30, 110 - 75);
text_label_co2 = lv_label_create(parent);
lv_label_set_text(text_label_co2, " 400 ppm");
lv_obj_align(text_label_co2, LV_ALIGN_CENTER, 70 + 20, 110 - 75);
lv_obj_set_style_text_font(text_label_co2, &lv_font_montserrat_22, 0);
text_label_weather_description = lv_label_create(parent);
lv_label_set_text(text_label_weather_description,
weather_description.c_str());
lv_obj_align(text_label_weather_description, LV_ALIGN_BOTTOM_MID, 0, -40);
lv_obj_set_style_text_font(text_label_weather_description,
&lv_font_montserrat_18, 0);
text_label_env_container = lv_obj_create(parent); // 創(chuàng)建一個(gè)容器對(duì)象
lv_obj_set_size(text_label_env_container, 300, 50); // 設(shè)置容器大小
lv_obj_align(text_label_env_container, LV_ALIGN_CENTER, 0,
0); // 容器居中對(duì)齊
lv_obj_set_scrollbar_mode(text_label_env_container, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_style_bg_color(text_label_env_container,
lv_palette_main(LV_PALETTE_RED), 0);
lv_obj_clear_flag(text_label_env_container, LV_OBJ_FLAG_HIDDEN);
// 創(chuàng)建標(biāo)簽并添加到容器中
text_label_env_tip = lv_label_create(text_label_env_container);
lv_label_set_text(text_label_env_tip, "ENVIRONMENT");
lv_obj_align(text_label_env_tip, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_text_font(text_label_env_tip, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(text_label_env_tip,
lv_color_make(255, 255, 255), 0);
text_label_time_location = lv_label_create(parent);
lv_label_set_text(
text_label_time_location,
String("Last Update: " + last_weather_update + " | " + location)
.c_str());
lv_obj_align(text_label_time_location, LV_ALIGN_BOTTOM_MID, 0, -10);
lv_obj_set_style_text_font(text_label_time_location, &lv_font_montserrat_12,
0);
lv_obj_set_style_text_color(text_label_time_location,
lv_palette_main(LV_PALETTE_GREY), 0);
timer = lv_timer_create(env_timer_cb, 1000, this);
lv_timer_ready(timer);
}
void onShow() override {
if (container == nullptr) {
static lv_style_t cont_style;
lv_style_init(&cont_style);
lv_style_set_bg_opa(&cont_style, 0);
lv_style_set_radius(&cont_style, 0);
lv_style_set_pad_all(&cont_style, 0);
container = lv_obj_create(lv_scr_act());
lv_obj_set_size(container, LV_HOR_RES, LV_VER_RES);
lv_obj_clear_flag(container, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_clear_flag(container, LV_OBJ_FLAG_GESTURE_BUBBLE);
lv_obj_add_style(container, &cont_style, 0);
create_ui(container);
} else {
lv_timer_resume(timer);
lv_obj_clear_flag(container, LV_OBJ_FLAG_HIDDEN);
}
}
void onHide() override {
if (container != nullptr) {
lv_timer_pause(timer);
lv_obj_add_flag(container, LV_OBJ_FLAG_HIDDEN);
}
}
void onForeground() override { env_sensor.update(); }
void onBackground() override {}
void onDestroy() override {}
};
static void env_timer_cb(lv_timer_t *timer) {
ENVUIAbility *self = static_cast<ENVUIAbility *>(timer->user_data);
if (self) {
// 輸出ENVUIAbility 狀態(tài)
if (self->currentState() != UIAbility::State_t::StateForeground)
return;
lv_img_set_src(self->weather_image, &image_weather_sun);
if (self->env_sensor.unitTVOC.available()) {
self->co2 = String(self->env_sensor.unitTVOC.co2eq());
}
if (self->env_sensor.unitENVPro.available()) {
self->temperature = String(self->env_sensor.unitENVPro.temperature(), 2);
self->humidity = String(self->env_sensor.unitENVPro.humidity(), 2);
self->pressure = String(self->env_sensor.unitENVPro.pressure() / 10, 2);
}
// 初始化一個(gè)標(biāo)志變量,用于判斷是否有任何警告
bool warning_triggered = false;
// 判斷溫度是否超過(guò)限制
if (self->env_sensor.unitENVPro.temperature() >
SharedData::Get().setting.temperature_limit) {
warning_triggered = true;
lv_label_set_text(self->text_label_env_tip, "TEMPERATURE WARNING");
lv_obj_set_style_text_color(self->text_label_temperature,
lv_palette_main(LV_PALETTE_RED), 0);
} else {
lv_obj_set_style_text_color(self->text_label_temperature,
lv_palette_main(LV_PALETTE_NONE), 0);
}
// 判斷CO2是否超過(guò)限制
if (self->env_sensor.unitTVOC.co2eq() >
SharedData::Get().setting.co2_limit &&
self->env_sensor.unitTVOC.co2eq() != uint16_t(-1)) {
if (warning_triggered && lv_label_get_text(self->text_label_env_tip) !=
"TEMPERATURE WARNING") {
lv_label_set_text(self->text_label_env_tip,
"TEMPERATURE and CO2 WARNING"); // 合并提示
} else {
lv_label_set_text(self->text_label_env_tip, "CO2 WARNING");
}
lv_obj_set_style_text_color(self->text_label_co2,
lv_palette_main(LV_PALETTE_RED), 0);
warning_triggered = true;
} else {
lv_obj_set_style_text_color(self->text_label_co2,
lv_palette_main(LV_PALETTE_NONE), 0);
}
// 如果有警告,顯示提示框;否則隱藏提示框
if (warning_triggered) {
lv_obj_clear_flag(self->text_label_env_container, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(self->text_label_env_container, LV_OBJ_FLAG_HIDDEN);
}
lv_label_set_text(self->text_label_date,
SharedData::Get().setting.date.c_str());
lv_label_set_text(
self->text_label_temperature,
String(" " + self->temperature + self->degree_symbol).c_str());
lv_label_set_text(self->text_label_humidity,
String(" " + self->humidity + "%").c_str());
lv_label_set_text(self->text_label_weather_description,
self->weather_description.c_str());
lv_label_set_text(self->text_label_time_location,
String("Last Update: " + self->last_weather_update +
" | " + self->location)
.c_str());
lv_label_set_text(self->text_label_pressure,
(" " + self->pressure + " kPa").c_str());
lv_label_set_text(self->text_label_co2,
(" " + self->co2 + " ppm").c_str());
}
}
設(shè)置 UI
#include "../../../shared/shared.h"
#include "../utils/page.h"
#include "./assets/images/images.h"
#include <WebServer.h>
#include <WiFi.h>
#include <ability/ability.h>
#include <lvgl.h>
using namespace mooncake;
const char *apSSID = "Digikey_Config_AP";
const char *apPassword = "12345678";
const char *htmlRoot = R"html(
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
</head>
<body>
<h1>Digikey Configuration Page</h1>
<form action='/set' method='POST'>
<label for='date'>Date:</label>
<input type='text' id='date' name='date' value='%dt%'><br><br>
<label for='weakup_word'>Weakup Word:</label>
<input type='text' id='weakup_word' name='weakup_word' value='%w%'><br><br>
<label for='employee'>Employee:</label>
<input type='text' id='employee' name='employee' value='%e%'><br><br>
<label for='distance'>Distance:</label>
<input type='text' id='distance' name='distance' value='%d%'><br><br>
<label for='distance_lat'>Distance Latitude:</label>
<input type='text' id='distance_lat' name='distance_lat' value='%dlat%'><br><br>
<label for='distance_lng'>Distance Longitude:</label>
<input type='text' id='distance_lng' name='distance_lng' value='%dlng%'><br><br>
<label for='speed_limit'>Speed Limit:</label>
<input type='text' id='speed_limit' name='speed_limit' value='%s%'><br><br>
<label for='co2_limit'>CO2 Limit:</label>
<input type='text' id='co2_limit' name='co2_limit' value='%c%'><br><br>
<label for='temperature_limit'>Temperature Limit:</label>
<input type='text' id='temperature_limit' name='temperature_limit' value='%t%'><br><br>
<input type='submit' value='Submit'>
</form>
</body></html>
)html";
const char *htmlSet = R"html(
<html><body><h1>Configuration Updated</h1>
<p>SUCCESS</p>
<p><a href='/'>Back</a></p>
</body></html>
)html";
class SettingUIAbility : public UIAbility {
private:
lv_obj_t *container = nullptr;
lv_obj_t *date_label = nullptr;
lv_obj_t *weakup_word_label = nullptr;
lv_obj_t *employee_label = nullptr;
lv_obj_t *distance_label = nullptr;
lv_obj_t *distance_lat_label = nullptr;
lv_obj_t *distance_lng_label = nullptr;
lv_obj_t *speed_limit_label = nullptr;
lv_obj_t *co2_limit_label = nullptr;
lv_obj_t *temperature_limit_label = nullptr;
String date = "2025-02-17";
String weakup_word = "你好小明";
String employee = "Xiao Ming";
String distance = "XIPING";
double distance_lat = 33.387700;
double distance_lng = 114.021001;
float co2_limit = 0;
float temperature_limit = 0;
int speed_limit = 0;
WebServer server{80};
public:
void create_ui(lv_obj_t *parent) {
displayLabel("Server IP: " +
std::string(WiFi.softAPIP().toString().c_str()),
10, parent);
date_label = displayLabel("Date: " + std::string(date.c_str()), 25, parent);
weakup_word_label = displayLabel(
"Weakup Word: " + std::string(weakup_word.c_str()), 40, parent);
employee_label =
displayLabel("Employee: " + std::string(employee.c_str()), 55, parent);
distance_label =
displayLabel("Distance: " + std::string(distance.c_str()), 70, parent);
distance_lat_label = displayLabel(
"Distance Latitude: " + std::to_string(distance_lat), 85, parent);
distance_lng_label = displayLabel(
"Distance Longitude: " + std::to_string(distance_lng), 100, parent);
speed_limit_label = displayLabel(
"Speed Limit: " + std::to_string(speed_limit), 115, parent);
co2_limit_label =
displayLabel("CO2 Limit: " + std::to_string(co2_limit), 130, parent);
temperature_limit_label = displayLabel(
"Temperature Limit: " + std::to_string(temperature_limit), 145, parent);
// 退出按鈕
lv_obj_t *btn = lv_btn_create(parent);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "Exit");
lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -10);
lv_obj_set_size(btn, 80, 50);
lv_obj_set_flex_flow(btn, LV_FLEX_FLOW_COLUMN); // 垂直排列
lv_obj_set_flex_align(btn, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER); // 垂直和水平居中
lv_obj_add_event_cb(
btn,
[](lv_event_t *event) {
if (lv_event_get_code(event) == LV_EVENT_CLICKED) {
auto &uiHandler = SharedData::Get().uiHandler;
SwitchUIAbility(uiHandler.setting_ui, uiHandler.menu_ui);
}
},
LV_EVENT_CLICKED, NULL);
}
void onCreate() override {}
void onShow() override {
// 設(shè)置 ESP32 為 AP 模式
WiFi.softAP(apSSID, apPassword);
Serial.println("ESP32 AP Mode Started");
Serial.print("IP Address: ");
Serial.println(WiFi.softAPIP());
// SharedData::Load();
date = SharedData::Get().setting.date;
weakup_word = SharedData::Get().setting.weakup_word;
employee = SharedData::Get().setting.employee;
distance = SharedData::Get().setting.distance;
distance_lat = SharedData::Get().setting.distance_lat;
distance_lng = SharedData::Get().setting.distance_lng;
speed_limit = SharedData::Get().setting.speed_limit;
co2_limit = SharedData::Get().setting.co2_limit;
temperature_limit = SharedData::Get().setting.temperature_limit;
// temperature_limit
printf("temperature_limit: %fn", temperature_limit);
// 配置 Web 服務(wù)器路由
server.on("/", HTTP_GET, [this]() { handleRoot(); });
server.on("/set", HTTP_POST, [this]() { handleSet(); });
server.begin();
if (container == nullptr) {
static lv_style_t cont_style;
lv_style_init(&cont_style);
lv_style_set_bg_opa(&cont_style, 0);
lv_style_set_radius(&cont_style, 0);
lv_style_set_pad_all(&cont_style, 0);
container = lv_obj_create(lv_scr_act());
lv_obj_set_size(container, LV_HOR_RES, LV_VER_RES);
lv_obj_clear_flag(container, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_clear_flag(container, LV_OBJ_FLAG_GESTURE_BUBBLE);
lv_obj_add_style(container, &cont_style, 0);
create_ui(container);
} else {
lv_obj_clear_flag(container, LV_OBJ_FLAG_HIDDEN);
}
}
void onHide() override {
server.close();
WiFi.softAPdisconnect();
if (container != nullptr) {
lv_obj_add_flag(container, LV_OBJ_FLAG_HIDDEN);
}
}
void onForeground() override { server.handleClient(); }
private:
// 處理根目錄請(qǐng)求
void handleRoot() {
String htmlContent;
htmlContent.reserve(1024);
htmlContent = String(htmlRoot).c_str(); // 拼接HTML字符串
htmlContent.replace("%dt%", date);
htmlContent.replace("%w%", weakup_word);
htmlContent.replace("%e%", employee);
htmlContent.replace("%d%", distance);
htmlContent.replace("%dlat%", String(distance_lat).c_str());
htmlContent.replace("%dlng%", String(distance_lng).c_str());
htmlContent.replace("%s%", String(speed_limit).c_str());
htmlContent.replace("%c%", String(co2_limit).c_str());
htmlContent.replace("%t%", String(temperature_limit).c_str());
server.send(200, "text/html", htmlContent);
}
// 處理表單提交
void handleSet() {
// 驗(yàn)證提交的參數(shù)是否有效
if (!server.hasArg("employee") || !server.hasArg("distance_lat") ||
!server.hasArg("distance_lng") || !server.hasArg("speed_limit") ||
!server.hasArg("co2_limit") || !server.hasArg("temperature_limit") ||
!server.hasArg("date")) {
server.send(400, "text/html", "Missing configuration parameter.");
return;
}
date = server.arg("date");
updateLabel(date_label, "Date: " + std::string(date.c_str()));
employee = server.arg("employee");
updateLabel(employee_label, "Employee: " + std::string(employee.c_str()));
distance = server.arg("distance");
updateLabel(distance_label, "Distance: " + std::string(distance.c_str()));
distance_lat = server.arg("distance_lat").toFloat();
updateLabel(distance_lat_label,
"Distance Latitude: " + std::to_string(distance_lat));
distance_lng = server.arg("distance_lng").toFloat();
updateLabel(distance_lng_label,
"Distance Longitude: " + std::to_string(distance_lng));
speed_limit = server.arg("speed_limit").toInt();
updateLabel(speed_limit_label,
"Speed Limit: " + std::to_string(speed_limit));
co2_limit = server.arg("co2_limit").toFloat();
updateLabel(co2_limit_label, "CO2 Limit: " + std::to_string(co2_limit));
temperature_limit = server.arg("temperature_limit").toFloat();
updateLabel(temperature_limit_label,
"Temperature Limit: " + std::to_string(temperature_limit));
SharedData::Get().setting.date = date;
SharedData::Get().setting.weakup_word = weakup_word;
SharedData::Get().setting.employee = employee;
SharedData::Get().setting.distance = distance;
SharedData::Get().setting.distance_lat = distance_lat;
SharedData::Get().setting.distance_lng = distance_lng;
SharedData::Get().setting.speed_limit = speed_limit;
SharedData::Get().setting.co2_limit = co2_limit;
SharedData::Get().setting.temperature_limit = temperature_limit;
SharedData::Save();
// 返回更新的配置
String response = String(htmlSet).c_str();
response.replace("%s", employee);
server.send(200, "text/html", response);
}
// 創(chuàng)建顯示標(biāo)簽
lv_obj_t *displayLabel(const std::string &text, int y_offset,
lv_obj_t *parent = lv_scr_act()) {
lv_obj_t *label = lv_label_create(parent);
lv_label_set_text(label, text.c_str());
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, y_offset);
return label;
}
// 更新標(biāo)簽內(nèi)容
void updateLabel(lv_obj_t *label, const std::string &text) {
lv_label_set_text(label, text.c_str());
}
};
攝像頭 UI
#pragma once
#include "../../../shared/shared.h"
#include "../utils/page.h"
#include "face_recognition_112_v1_s16.hpp"
#include "face_recognition_112_v1_s8.hpp"
#include "face_recognition_tool.hpp"
#include "human_face_detect_mnp01.hpp"
#include "human_face_detect_msr01.hpp"
#include <ability/ability.h>
#include <fb_gfx.h>
#define FACE_ID_SAVE_NUMBER 1
using namespace mooncake;
HumanFaceDetectMSR01 s1(0.1F, 0.5F, 10, 0.2F);
HumanFaceDetectMNP01 s2(0.5F, 0.3F, 5);
class CameraUIAbility : public UIAbility {
private:
int8_t is_enrolling = 0;
M5Canvas canvas;
FaceRecognition112V1S8 recognizer;
int8_t mode = 1;
lv_obj_t *container = nullptr;
lv_obj_t *btn_delete;
public:
CameraUIAbility() : canvas(&CoreS3.Display) {}
~CameraUIAbility() {}
void setMode(int8_t mode) { this->mode = mode; }
void onCreate() override {
if (!CoreS3.Camera.begin()) {
CoreS3.Display.drawString("Camera Init Fail", 0, 0);
}
recognizer.set_partition(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "fr");
recognizer.set_ids_from_flash();
int enrolled_count = recognizer.get_enrolled_id_num();
printf("enrolled_count %dn", enrolled_count);
}
esp_err_t camera_capture_and_face_detect() {
if (CoreS3.Camera.get()) {
camera_fb_t *fb = CoreS3.Camera.fb;
std::list<dl::detect::result_t> &candidates =
s1.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3});
std::list<dl::detect::result_t> &results =
s2.infer((uint16_t *)fb->buf, {(int)fb->height, (int)fb->width, 3},
candidates);
canvas.pushImage(0, 0, fb->width, fb->height, (uint16_t *)fb->buf);
if (results.size() > 0) {
int face_id = 0;
fb_data_t rfb;
rfb.width = fb->width;
rfb.height = fb->height;
rfb.data = fb->buf;
rfb.bytes_per_pixel = 2;
rfb.format = FB_RGB565;
face_id = run_face_recognition(&rfb, &results);
face_coordinate(&rfb, &results, face_id);
show_face_id(face_id);
int enrolled_count = recognizer.get_enrolled_id_num();
if (face_id > 0 && mode) {
auto &uiHandler = SharedData::Get().uiHandler;
SharedData::Get().status.is_valid = true;
SwitchUIAbility(uiHandler.camera_ui, uiHandler.menu_ui);
}
}
canvas.pushSprite(0, 0);
CoreS3.Camera.free();
}
return ESP_OK;
}
void createUI() {
container = lv_obj_create(lv_scr_act());
lv_obj_set_size(container, 80, 240);
lv_obj_align(container, LV_ALIGN_RIGHT_MID, 0, 0);
lv_obj_set_scrollbar_mode(container, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_style_border_opa(container, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER);
// 刪除按鈕
btn_delete = lv_btn_create(container);
lv_obj_t *label_delete = lv_label_create(btn_delete);
lv_label_set_text(label_delete, "Delete");
lv_obj_align(btn_delete, LV_ALIGN_TOP_LEFT, 0, -10);
lv_obj_set_size(btn_delete, 60, 50);
lv_obj_add_flag(btn_delete, LV_OBJ_FLAG_HIDDEN);
lv_obj_set_flex_flow(btn_delete, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(btn_delete, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_add_event_cb(
btn_delete,
[](lv_event_t *event) {
if (lv_event_get_code(event) == LV_EVENT_CLICKED) {
((CameraUIAbility *)event->user_data)->recognizer.delete_id(false);
((CameraUIAbility *)event->user_data)
->recognizer.write_ids_to_flash();
}
},
LV_EVENT_CLICKED, this);
lv_obj_t *btn_enroll = lv_btn_create(container);
lv_obj_t *label_enroll = lv_label_create(btn_enroll);
lv_label_set_text(label_enroll, "Enroll");
lv_obj_align(btn_enroll, LV_ALIGN_TOP_LEFT, 0, 10);
lv_obj_set_size(btn_enroll, 60, 50);
lv_obj_set_flex_flow(btn_enroll, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(btn_enroll, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
if (mode == 1) {
lv_obj_add_flag(btn_enroll, LV_OBJ_FLAG_HIDDEN); // 隱藏容器
} else {
;
lv_obj_clear_flag(btn_enroll, LV_OBJ_FLAG_HIDDEN); // 顯示容器
}
lv_obj_add_event_cb(
btn_enroll,
[](lv_event_t *event) {
if (lv_event_get_code(event) == LV_EVENT_CLICKED) {
((CameraUIAbility *)event->user_data)->is_enrolling = 1;
}
},
LV_EVENT_CLICKED, this);
lv_obj_t *btn_exit = lv_btn_create(container);
lv_obj_t *label_exit = lv_label_create(btn_exit);
lv_label_set_text(label_exit, "Exit");
lv_obj_align(btn_exit, LV_ALIGN_TOP_RIGHT, 0, -10);
lv_obj_set_size(btn_exit, 60, 50);
lv_obj_set_flex_flow(btn_exit, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(btn_exit, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER);
lv_obj_add_event_cb(
btn_exit,
[](lv_event_t *event) {
if (lv_event_get_code(event) == LV_EVENT_CLICKED) {
auto &uiHandler = SharedData::Get().uiHandler;
int mode = ((CameraUIAbility *)event->user_data)->mode;
SwitchUIAbility(uiHandler.camera_ui, mode == 0
? uiHandler.menu_ui
: uiHandler.splash_ui);
}
},
LV_EVENT_CLICKED, this);
}
void onShow() override {
canvas.createSprite(240, 240);
if (container == nullptr) {
this->createUI();
} else {
lv_obj_clear_flag(container, LV_OBJ_FLAG_HIDDEN);
}
}
void onHide() override {
if (container != nullptr) {
lv_obj_add_flag(container, LV_OBJ_FLAG_HIDDEN);
canvas.deleteSprite();
}
}
void onForeground() override {
int enrolled_count = recognizer.get_enrolled_id_num();
if (mode == 0 && enrolled_count > 0) {
lv_obj_clear_flag(btn_delete, LV_OBJ_FLAG_HIDDEN);
return;
} else {
lv_obj_add_flag(btn_delete, LV_OBJ_FLAG_HIDDEN);
}
camera_capture_and_face_detect();
}
void face_coordinate(fb_data_t *fb, std::list<dl::detect::result_t> *results,
int face_id) {
int x, y, w, h;
int xf = 160, yf = 120;
int i = 0;
for (std::list<dl::detect::result_t>::iterator prediction =
results->begin();
prediction != results->end(); prediction++, i++) {
x = (int)prediction->box[0];
y = (int)prediction->box[1];
if (y < 0)
y = 0;
w = (int)prediction->box[2] - x + 1;
h = (int)prediction->box[3] - y + 1;
if ((x + w) > fb->width)
w = fb->width - x;
if ((y + h) > fb->height)
h = fb->height - y;
xf = x + w / 2;
yf = y + h / 2;
canvas.drawRect(x, y, w, h, face_id > 0 ? TFT_GREEN : TFT_RED);
}
}
void show_face_id(int face_id) {
canvas.setFont(&fonts::efontCN_12_b);
canvas.setTextSize(2);
canvas.setTextColor(TFT_WHITE);
canvas.setCursor(10, 10);
if (face_id > 0)
canvas.printf("ID: %d %s", face_id, SharedData::Get().setting.employee);
else {
canvas.printf("未知員工!");
}
}
int run_face_recognition(fb_data_t *fb,
std::list<dl::detect::result_t> *results) {
std::vector<int> landmarks = results->front().keypoint;
int id = -1;
Tensor<uint8_t> tensor;
tensor.set_element((uint8_t *)fb->data)
.set_shape({fb->height, fb->width, 3})
.set_auto_free(false);
int enrolled_count = recognizer.get_enrolled_id_num();
if (enrolled_count < FACE_ID_SAVE_NUMBER && is_enrolling) {
id = recognizer.enroll_id(tensor, landmarks, "face", true);
recognizer.write_ids_to_flash();
printf("Enrolled ID: %d n", id);
is_enrolling = 0;
}
face_info_t recognize = recognizer.recognize(tensor, landmarks);
if (recognize.id >= 0) {
printf("ID[%u]: %.2f n", recognize.id, recognize.similarity);
}
return recognize.id;
}
};
LLM Worker
#pragma once
#include "../../../shared/shared.h"
#include <M5ModuleLLM.h>
#include <M5Unified.h>
#include <mooncake.h>
using namespace mooncake;
class LLMWorkerAbility : public WorkerAbility {
private:
uint32_t baud = 115200;
HardwareSerial llm_serial;
// String tts_work_id;
String language;
M5ModuleLLM module_llm;
M5ModuleLLM_VoiceAssistant *voice_assistant;
lv_obj_t *text_label_tip_container;
lv_obj_t *text_label_tip;
public:
LLMWorkerAbility() : llm_serial(2), language("zh_CN") {
voice_assistant = new M5ModuleLLM_VoiceAssistant(&module_llm);
}
~LLMWorkerAbility() { delete voice_assistant; }
void onCreate() override {
text_label_tip_container = lv_obj_create(lv_scr_act());
lv_obj_set_size(text_label_tip_container, 300, 50);
lv_obj_align(text_label_tip_container, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_scrollbar_mode(text_label_tip_container, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_style_bg_color(text_label_tip_container,
lv_palette_main(LV_PALETTE_GREEN), 0);
lv_obj_add_flag(text_label_tip_container, LV_OBJ_FLAG_HIDDEN);
text_label_tip = lv_label_create(text_label_tip_container);
lv_label_set_text(text_label_tip, "saying...");
lv_obj_align(text_label_tip, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_style_text_font(text_label_tip, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(text_label_tip, lv_color_make(255, 255, 255),
0);
llm_serial.begin(baud, SERIAL_8N1, 18, 17);
module_llm.begin(&llm_serial);
while (1) {
if (module_llm.checkConnection()) {
Serial.println("llm_setup success");
break;
}
}
// m5_module_llm::ApiTtsSetupConfig_t tts_config;
// tts_work_id = module_llm.tts.setup(tts_config, "tts_setup", language);
// Serial.println("LLMWorkerAbility created");
int ret = voice_assistant->begin(SharedData::Get().setting.weakup_word, "",
"zh_CN");
if (ret != MODULE_LLM_OK) {
Serial.println("voice assistant begin failed");
}
/* Register on ASR data callback function 顯示 text_label_tip_container*/
voice_assistant->onAsrDataInput(
[this](String data, bool isFinish, int index) {
Serial.printf("asr data: %sn", data.c_str());
lv_obj_add_flag(text_label_tip_container, LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(text_label_tip, "linstening...");
if (isFinish) {
lv_obj_add_flag(text_label_tip_container, LV_OBJ_FLAG_HIDDEN);
Serial.printf("asr data finish >> n");
}
});
voice_assistant->onLlmDataInput(
[this](String data, bool isFinish, int index) {
lv_obj_clear_flag(text_label_tip_container, LV_OBJ_FLAG_HIDDEN);
Serial.printf("llm data: %sn", data.c_str());
lv_label_set_text(text_label_tip, "say...");
if (isFinish) {
lv_obj_add_flag(text_label_tip_container, LV_OBJ_FLAG_HIDDEN);
Serial.printf("llm data finishn");
}
});
}
void onRunning() override { voice_assistant->update(); }
// void play(String text) { module_llm.tts.inference(tts_work_id, text,
// 10000); }
void onPause() override { llm_serial.end(); }
void onResume() override { llm_serial.begin(baud, SERIAL_8N1, 9, 8); }
};
TMOS Worker
#include "M5_STHS34PF80.h"
#include <mooncake.h>
#include <mooncake_event.h>
using namespace mooncake;
class TMOSWorkerAbility : public WorkerAbility {
private:
M5_STHS34PF80 tmos;
uint8_t motionHysteresis = 0;
int16_t motionVal = 0, presenceVal = 0;
uint16_t motionThresholdVal = 0, presenceThresholdVal = 0;
sths34pf80_tmos_func_status_t status;
sths34pf80_gain_mode_t gainMode;
public:
sths34pf80_tmos_func_status_t getStatus() { return status; }
sths34pf80_gain_mode_t getGainMode() { return gainMode; }
bool isPresenceDetected() { return status.pres_flag; }
bool isMotionDetected() { return status.mot_flag; }
void onCreate() override {
while (tmos.begin(&Wire) == false) {
printf("Error setting up device - please check wiring.n");
delay(200);
}
tmos.setGainMode(STHS34PF80_GAIN_WIDE_MODE);
tmos.setTmosODR(STHS34PF80_TMOS_ODR_AT_2Hz);
tmos.setTmosSensitivity(0xff);
tmos.setMotionThreshold(0xC8);
tmos.setPresenceThreshold(0xC8);
tmos.resetAlgo();
tmos.getGainMode(&gainMode);
}
void onRunning() override {
sths34pf80_tmos_drdy_status_t dataReady;
tmos.getDataReady(&dataReady);
if (dataReady.drdy == 1) {
tmos.getPresenceValue(&presenceVal);
tmos.getMotionValue(&motionVal);
tmos.getStatus(&status);
if (status.pres_flag == 1) {
tmos.getPresenceValue(&presenceVal);
SharedData::Get().status.is_presence = true;
if (GetMooncake().extensionManager()->getUIAbilityCurrentState(
SharedData::Get().uiHandler.splash_ui) ==
UIAbility::State_t::StateForeground) {
GetMooncake().extensionManager()->hideUIAbility(
SharedData::Get().uiHandler.splash_ui);
GetMooncake().extensionManager()->showUIAbility(
SharedData::Get().uiHandler.camera_ui);
}
}
if (status.mot_flag == 1) {
tmos.getMotionValue(&motionVal);
}
}
}
};
代碼地址: ???代碼地址
視頻地址: ??鏈接:?https://pan.baidu.com/s/1yRhPv3BXbSlQB9YuatgIfw?pwd=qhs4 提取碼: qhs4?