• 方案介紹
    • 模塊清單
    • 一、 ??AI?人臉識(shí)別?與管理功能
    • 二、 鎖屏狀態(tài)下的人臉解鎖功能
    • 三、 ??Web Server?與WiFi?配置管理
    • 四、 GPS時(shí)間與車速同步,智能導(dǎo)航展示
    • 五、 智能環(huán)境監(jiān)測(cè)與實(shí)時(shí)臨界值提醒
    • 六、 實(shí)時(shí)語(yǔ)音助手,旅途中的小幫手
  • 相關(guān)推薦
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

智能車載設(shè)備

05/28 11:50
192
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

本車載智能設(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>

人臉識(shí)別

鎖屏之后會(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ì)

247111740446104_pic.jpg

247191740446465_pic.jpg

247731740449614_pic_hd.jpg

功能列表

whiteboard_exported_image1.png

啟動(dòng)流程

whiteboard_exported_image2.png

啟動(dòng)

未人臉通過(guò)識(shí)別

WechatIMG370.jpg

通過(guò)人臉識(shí)別

IMG_0133.jpg

代碼:

入口文件

#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?

DigiKey得捷

DigiKey得捷

DigiKey 總部位于美國(guó)明尼蘇達(dá)州錫夫里弗福爾斯市,是一家獲得原廠授權(quán)的全球性、全類目電子元器件和自動(dòng)化產(chǎn)品分銷商。我們通過(guò)分銷來(lái)自 2,300 多家優(yōu)質(zhì)品牌制造商的 1,020 多萬(wàn)種元器件獲得了強(qiáng)大的技術(shù)優(yōu)勢(shì)。DigiKey 還為工程師、設(shè)計(jì)師、開(kāi)發(fā)者和采購(gòu)專業(yè)人員提供豐富的數(shù)字解決方案、無(wú)障礙互動(dòng)和工具支持,以幫助他們提升工作效率。在中國(guó),客戶可以通過(guò)電子郵件、電話和客服獲得全方位技術(shù)支持。如需了解更多信息和獲取 DigiKey 廣泛的產(chǎn)品,請(qǐng)?jiān)L問(wèn) www.digikey.cn 并關(guān)注我們的微信、微博、騰訊視頻和 BiliBili 賬號(hào)。

DigiKey 總部位于美國(guó)明尼蘇達(dá)州錫夫里弗福爾斯市,是一家獲得原廠授權(quán)的全球性、全類目電子元器件和自動(dòng)化產(chǎn)品分銷商。我們通過(guò)分銷來(lái)自 2,300 多家優(yōu)質(zhì)品牌制造商的 1,020 多萬(wàn)種元器件獲得了強(qiáng)大的技術(shù)優(yōu)勢(shì)。DigiKey 還為工程師、設(shè)計(jì)師、開(kāi)發(fā)者和采購(gòu)專業(yè)人員提供豐富的數(shù)字解決方案、無(wú)障礙互動(dòng)和工具支持,以幫助他們提升工作效率。在中國(guó),客戶可以通過(guò)電子郵件、電話和客服獲得全方位技術(shù)支持。如需了解更多信息和獲取 DigiKey 廣泛的產(chǎn)品,請(qǐng)?jiān)L問(wèn) www.digikey.cn 并關(guān)注我們的微信、微博、騰訊視頻和 BiliBili 賬號(hào)。收起

查看更多

相關(guān)推薦