项目背景
智能家居市场近年来蓬勃发展,引领着未来生活方式的变革。在这片充满活力的市场中,各类智能家居设备如雨后春笋般涌现,从智能音箱的悦耳旋律到智能电视的沉浸式体验,从智能冰箱的智能管理到智能门锁的安全守护,再到智能照明的温馨氛围营造,无一不展现着科技如何让居家生活变得更加智能与便捷。
在此基础上,本项目推出的智能家居系统集成了上述智能家居设备的核心功能,可以融合多媒体显示等功能,为用户带来直观与便捷的交互体验。更为重要的是,本项目创新性地将大语言模型深度融入该系统平台之中,这一举措不仅极大地提升了设备的智能化水平,更赋予了其强大的决策辅助能力。
所用主要核心器件介绍
1.(来自DFROBOT)FireBeetle 2 ESP32-S3是一款基于ESP32-S3-WROOM-1-N16R8模组设计的主控板。ESP32-S3-WROOM-1-N16R8模组拥有16MB Flash和8MB PSRAM,可以存储更多的代码和数据,模组搭载的ESP32-S3芯片拥有强大的神经网络运算能力和信号处理能力,适用于图像识别、语音识别等项目。FireBeetle 2 ESP32-S3板载摄像头接口,可以方便的连接摄像头,独立的摄像头供电电路,减少了其他信号对摄像头的干扰。开发板附带了一个OV2640摄像头,该摄像头拥有200万像素和68°视场角,最高支持1600*1200分辨率。FireBeetle 2 ESP32-S3板载GDI屏幕接口,解决使用屏幕时的接线烦恼,集成电源管理功能,支持锂电池充电和硬件开关机。
2.(来自乐鑫)ESP32-S3-LCD-EV-Board 是一款基于 ESP32-S3 芯片的屏幕交互开发板,通过搭配不同类型的 LCD 子板,可以驱动 IIC、SPI、8080 以及 RGB 接口的 LCD 显示屏。同时它还搭载双麦克风阵列,支持语音识别和近/远场语音唤醒,具有触摸屏交互和语音交互功能,满足用户对多种不同分辨率以及接口的触摸屏应用产品的开发需求。
3. (来自DFROBOT)TCS3200颜色传感器(SEN0101)是一款全彩的颜色检测器,包括了一块TAOS TCS3200RGB感应芯片和4个白色LED灯,TCS3200能在一定的范围内检测和测量几乎所有的可见光。TCS3200有大量的光检测器,每个都有红绿蓝和清除4种滤光器。每6种颜色滤光器均匀地按数组分布来清除颜色中偏移位置的颜色分量。内置的振荡器能输出方波,其频率与所选择的光的强度成比例关系。
4.Adafruit的两个板卡,其中一款Feather板也是基于ESP32-S3,拥有不俗的性能,板载一块小屏幕,方便调试,另一块则是15x7的矩阵灯板。
方案框图与设计思路
本项目的设计框图如下所示,其中大致可以分为三部分,首先是PC机上的云端服务,负责提供flask的总体通信交互框架,用于不同板卡间以及与上位机的通信交互,同时也在上位机上部署了大语言模型的推理服务,在通过ESP32-S3-LCD-EV-BOARD的LVGL界面完成与用户的人机交互,收集到用户的意向(比如通过文本输入等)后,经由GPT-4o模型的分析,通过Function Call等技术完成推理,进行决策,下发需要完成的任务,如开灯、开启空调等,而这些细节任务的执行则由Adafruit的ESP32板辅助完成,其中信息交互中枢的任务则有FireBeetle2 ESP32 S3承担任务。
功能演示
首先在上位机pc上启动flask服务以及大语言模型推理服务的接口,启动成功如图所示:
启动FireBeetle板的中间通信服务,然后将Adafruit的ESP32S3板卡与矩阵灯板以及SEN0101传感器相连(本项目主要使用其4个闪亮的白色LED灯,用以模拟家庭环境中的开灯如白炽灯场景),连接对应引脚,配置完I2C地址后,上电,可以看到默认情况下,灯板以及LED灯都是熄灭状态,而ESP32由于烧录的CPY固件,因此板载的小屏幕上会显示输出信息,可以看到板子上电后连接到wifi,并且自动显示地址,并且已经在监听5005端口,等待大语言模型下发的指令传递至此,从而继续操作其他两个外设。
将ESP32的EV板上电,可以看到基于LVGL构建的交互界面,最上方显示连接wifi后分配的IP地址,中间则是一个文本输入框,用来接收用户输入的文字文本信息,下方一个按钮,用来确认用户输入的信息。
点击文本输入框,界面会自动弹出一个软键盘,方便用户直接输入信息,免去要外接键盘的麻烦。
然后用户可以自由输入感受、想法等等一切文本信息,借由大语言模型的出色的理解能力,可以与用户共情,懂你所想,从而完成用户需要的任务。比如这里输入“it is night already”,并点击确认,首先会回显确认信息,然后借由flask服务将文本信息转发至PC机,基于Function Call技术进行大语言模型处理。
这里查看大语言模型输出可以发现,模型成功意识到现在天色已晚,潜台词需要开灯照明了
然后查看“白炽灯”状态,果然已经由大语言模型智能处理,打开了灯泡开始照明,“Enjoy your brightness”!
同理,可以输入如“I am hot”之类的主观感受,大语言模型也会懂你意思,指挥开启空调服务
而且也可以输入比如“I am so bored”让大模型帮忙找点乐子,然后就会发现大模型用灯板摆了些字符图案什么的逗你开心
具体细节可以通过视频观看了解,总体来说,智能性以及可拓展性还是很强的。
关键代码说明
在大语言模型的实际使用中,我个人基于Langchain框架进行开发,也推荐初学者采用此框架。Langchain是一个流行的开源大语言模型操作框架,为开发者提供了一系列的工具和组件,使得与语言模型中的各种数据(如SQL、PDF、CSV)等的连接、语言模型的应用和优化直接简洁。
下面代码是使用langchain框架调用工具的一个示例,主要部分有两处,一处是@tool下的Function的功能定义说明以及具体实现,另一处则是基于OpenAI的chat框架,应用langchain提供的Agent进行Function Call的效果实现。
复制import httpx
from langchain import hub
from langchain.agents import AgentExecutor, create_react_agent, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
api_key = "Your_API_Key"
# Define tools to be used with the agent
@tool
def call_ambulance(position: str) -> str:
"""Call ambulance in terms of emergency like accident to the given position """
return "Ambulance called successfully."
@tool
def call_police(position: str) -> str:
"""Call police in terms of heavy emergency to maintain situation to the given position"""
return "Police called successfully."
tools = [call_ambulance, call_police]
llm = ChatOpenAI(
openai_api_key=api_key,
model="gpt-4o")
question = "发生交通事故了"
prompt = hub.pull("hwchase17/react")
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
# Invoke the agent with the combined input
result = agent_executor.invoke(
{"input": question, "tools": tools}).values()
ESP32的LCD板上的LVGL界面也是项目核心,按官方文档配置arduino环境后,即可使用下面我提供的代码构建视频中显示的界面,其中每段功能的注释也已加入。
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ESP_Panel_Library.h>
#include <lvgl.h>
#include "lvgl_port_v8.h"
// WiFi信息
const char* ssid = "Iridescent";
const char* password = "20020120";
const char* serverUrl = "http://172.20.10.4:5000/api/data"; // 替换为PC的IP地址
lv_obj_t *textarea;
lv_obj_t *keyboard;
lv_obj_t *label_status;
// 按钮事件处理函数,发送文本内容到PC上的Flask服务器
static void btn_event_cb(lv_event_t * e)
{
const char * text = lv_textarea_get_text(textarea);
// 在串口输出文本框中的内容
Serial.print("Text input: ");
Serial.println(text);
// 发送数据到Flask服务器
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(serverUrl);
http.addHeader("Content-Type", "application/json");
// 创建JSON数据
String jsonData = "{\"data\": \"" + String(text) + "\"}";
// 发送POST请求
int httpResponseCode = http.POST(jsonData);
if (httpResponseCode > 0) {
Serial.print("POST Response Code: ");
Serial.println(httpResponseCode);
if (httpResponseCode == HTTP_CODE_OK) {
String response = http.getString();
Serial.println("Server response: " + response);
}
} else {
Serial.print("Error on sending POST: ");
Serial.println(httpResponseCode);
}
http.end();
} else {
Serial.println("Error in WiFi connection");
}
// 弹出提示框显示输入内容
lv_obj_t *msg = lv_msgbox_create(NULL, "Input", text, NULL, true);
lv_obj_align(msg, LV_ALIGN_CENTER, 0, 0);
}
// 文本框事件处理函数,点击文本框时弹出键盘
static void textarea_event_cb(lv_event_t * e)
{
lv_keyboard_set_textarea(keyboard, textarea); // 关联键盘和文本输入框
lv_obj_clear_flag(keyboard, LV_OBJ_FLAG_HIDDEN); // 显示键盘
}
// 键盘事件处理函数
static void keyboard_event_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t *kb = lv_event_get_target(e);
if(code == LV_EVENT_CANCEL) {
lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN); // 隐藏键盘
}
}
// 初始化WiFi并显示状态
void initWiFi()
{
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi...");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected to WiFi!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
// 在屏幕上显示WiFi连接成功和IP地址
String status_text = "Connected: " + WiFi.localIP().toString();
lv_label_set_text(label_status, status_text.c_str());
}
void setup()
{
Serial.begin(115200);
String title = "LLM Based HomeAssistant Platform";
Serial.println(title + " start");
// 初始化显示屏
ESP_Panel *panel = new ESP_Panel();
panel->init();
#if LVGL_PORT_AVOID_TEAR
ESP_PanelBus_RGB *rgb_bus = static_cast<ESP_PanelBus_RGB *>(panel->getLcd()->getBus());
rgb_bus->configRgbFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM);
rgb_bus->configRgbBounceBufferSize(LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE);
#endif
panel->begin();
// 初始化LVGL
lvgl_port_init(panel->getLcd(), panel->getTouch());
lvgl_port_lock(-1);
// 创建标题标签
lv_obj_t *title_label = lv_label_create(lv_scr_act());
lv_label_set_text(title_label, title.c_str());
lv_obj_align(title_label, LV_ALIGN_TOP_MID, 0, 5);
// 创建WiFi状态标签
label_status = lv_label_create(lv_scr_act());
lv_label_set_text(label_status, "Connecting to WiFi...");
lv_obj_align(label_status, LV_ALIGN_TOP_MID, 0, 30);
// 初始化WiFi连接
initWiFi();
// 创建文本输入框
textarea = lv_textarea_create(lv_scr_act());
lv_obj_set_width(textarea, 200);
lv_obj_align(textarea, LV_ALIGN_CENTER, 0, -40);
lv_textarea_set_placeholder_text(textarea, "Enter text here");
lv_obj_add_event_cb(textarea, textarea_event_cb, LV_EVENT_CLICKED, NULL);
// 创建按钮
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 40);
lv_obj_set_size(btn, 100, 50);
lv_obj_t *btn_label = lv_label_create(btn);
lv_label_set_text(btn_label, "Submit");
lv_obj_center(btn_label);
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
// 创建并隐藏虚拟键盘
keyboard = lv_keyboard_create(lv_scr_act());
lv_obj_add_flag(keyboard, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_event_cb(keyboard, keyboard_event_cb, LV_EVENT_CANCEL, NULL);
lvgl_port_unlock();
Serial.println(title + " end");
}
void loop()
{
lv_task_handler();
delay(5);
}
PCB以及其他说明
很可惜,基于Adafruit的Feather系列修改画了一版pcb板,准备用来容纳不同小ESP32板之间的交互,然而打板有了一些延误,不能在项目提交截止前完全展示了,因此用面包板替代展示了所构建的功能。修改的一版PCB板可以在附件看到,后面PCB到了,也会继续尝试完美完成哈哈哈,如果效果好,也会再传上来。
心得体会
非常荣幸能够参与FastBond活动,通过这次活动,我深刻地体验到了电子电路设计的乐趣和挑战,并且对电子器件有了更深入的了解。我希望能够进一步探索更智能、人性化的方式,让用户更加轻松地体验到良好的家庭生活。为了实现这个目标,最后,再次感谢FastBond活动的组织者和主办方提供了一个如此棒的学习和交流平台,祝愿FastBond活动越来越成功!