|
本帖最后由 忙碌的死龙 于 2024-6-1 12:27 编辑
前言
[backcolor=rgba(255, 255, 255, 0.04)]在当今智能家居日益普及的时代,融合创新技术以打造个性化、高效能的居住环境已成为技术爱好者们的共同追求。本项目旨在利用ESP32的强大微控制器能力和Vue.js的灵活前端框架,构建一个轻量级却功能丰富的个人智能家居平台。选择ESP32作为核心,因其能高效地作为数据枢纽,连接自行开发的传感器模块和其他控制模块。
[backcolor=rgba(255, 255, 255, 0.04)]而MYD-YM62X就是用来开发和部署web应用的重点,[backcolor=rgba(255, 255, 255, 0.04)]Vue拥有丰富的生态系统,可以进行[backcolor=rgba(255, 255, 255, 0.04)]快速原型开发,跨平台能力强。我们直接在MYD-YM62X上部署Vue开发环境,可以有效的利用Linux生态。也可以在后期开发Linux服务端应用的时候,使用其他的数据库等等。
一、给EMMC分区扩容
出厂时的分区大小是比较小的,剩余容量仅有100M左右,不足以安装相关环境,我们需要使用以下命令进行扩容
扩容后的分区大小如下图
二、安装node运行环境
由于开发板上没有apt之类的包管理软件,这时候就需要请出nvm工具了,我们可以使用国内源加速的安装方式。
- bash -c "$(curl -fsSL https://gitee.com/RubyMetric/nvm-cn/raw/main/install.sh)"
复制代码 运行这个命令之后,会在用户目录下新建一个.nvm目录,并安装nvm相关软件。安装完毕需要重新加载bashrc配置环境文件。
然后安装node和pnpm
给pnpm配置国内源
- pnpm config set registry https://registry.npmmirror.com
复制代码
三、初始化vue项目
使用命令初始化项目,并安装需要用到的软件包
- pnpm create vite@latest myesp32 -- --template vue
- cd myesp32/
- pnpm install -D tailwindcss postcss autoprefixer
- npx tailwindcss init -p
- pnpm install @headlessui/vue
复制代码 安装完对应软件包后,根据tailwindcss 官网文档的,我们需要将tailwind.config.js这个文件修改成以下内容
- /** @type {import('tailwindcss').Config} */
- export default {
- content: [
- "./index.html",
- "./src/**/*.{vue,js,ts,jsx,tsx}",
- ],
- theme: {
- extend: {},
- },
- plugins: [],
- }
复制代码 然后将 ./src/style.css 文件修改成以下内容
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
复制代码 然后就可以用以下命令开启服务器进行开发调试了
然后就可以点击链接进行开发了。
四、ESP32 HUB的WebSocket服务
esp32自带的源码Demo里,可以找到相关的案例代码(examples/protocols/http_server/ws_echo_server)。
- /* WebSocket Echo Server Example
- This example code is in the Public Domain (or CC0 licensed, at your option.)
- Unless required by applicable law or agreed to in writing, this
- software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
- CONDITIONS OF ANY KIND, either express or implied.
- */
- #include <esp_wifi.h>
- #include <esp_event.h>
- #include <esp_log.h>
- #include <esp_system.h>
- #include <nvs_flash.h>
- #include <sys/param.h>
- #include "esp_netif.h"
- #include "esp_eth.h"
- #include "protocol_examples_common.h"
- #include <esp_http_server.h>
- /* A simple example that demonstrates using websocket echo server
- */
- static const char *TAG = "ws_echo_server";
- /*
- * Structure holding server handle
- * and internal socket fd in order
- * to use out of request send
- */
- struct async_resp_arg {
- httpd_handle_t hd;
- int fd;
- };
- /*
- * async send function, which we put into the httpd work queue
- */
- static void ws_async_send(void *arg)
- {
- static const char * data = "Async data";
- struct async_resp_arg *resp_arg = arg;
- httpd_handle_t hd = resp_arg->hd;
- int fd = resp_arg->fd;
- httpd_ws_frame_t ws_pkt;
- memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
- ws_pkt.payload = (uint8_t*)data;
- ws_pkt.len = strlen(data);
- ws_pkt.type = HTTPD_WS_TYPE_TEXT;
- httpd_ws_send_frame_async(hd, fd, &ws_pkt);
- free(resp_arg);
- }
- static esp_err_t trigger_async_send(httpd_handle_t handle, httpd_req_t *req)
- {
- struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
- if (resp_arg == NULL) {
- return ESP_ERR_NO_MEM;
- }
- resp_arg->hd = req->handle;
- resp_arg->fd = httpd_req_to_sockfd(req);
- esp_err_t ret = httpd_queue_work(handle, ws_async_send, resp_arg);
- if (ret != ESP_OK) {
- free(resp_arg);
- }
- return ret;
- }
- /*
- * This handler echos back the received ws data
- * and triggers an async send if certain message received
- */
- static esp_err_t echo_handler(httpd_req_t *req)
- {
- if (req->method == HTTP_GET) {
- ESP_LOGI(TAG, "Handshake done, the new connection was opened");
- return ESP_OK;
- }
- httpd_ws_frame_t ws_pkt;
- uint8_t *buf = NULL;
- memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
- ws_pkt.type = HTTPD_WS_TYPE_TEXT;
- /* Set max_len = 0 to get the frame len */
- esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
- if (ret != ESP_OK) {
- ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
- return ret;
- }
- ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
- if (ws_pkt.len) {
- /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
- buf = calloc(1, ws_pkt.len + 1);
- if (buf == NULL) {
- ESP_LOGE(TAG, "Failed to calloc memory for buf");
- return ESP_ERR_NO_MEM;
- }
- ws_pkt.payload = buf;
- /* Set max_len = ws_pkt.len to get the frame payload */
- ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
- if (ret != ESP_OK) {
- ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
- free(buf);
- return ret;
- }
- ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload);
- }
- ESP_LOGI(TAG, "Packet type: %d", ws_pkt.type);
- if (ws_pkt.type == HTTPD_WS_TYPE_TEXT &&
- strcmp((char*)ws_pkt.payload,"Trigger async") == 0) {
- free(buf);
- return trigger_async_send(req->handle, req);
- }
- ret = httpd_ws_send_frame(req, &ws_pkt);
- if (ret != ESP_OK) {
- ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
- }
- free(buf);
- return ret;
- }
- static const httpd_uri_t ws = {
- .uri = "/ws",
- .method = HTTP_GET,
- .handler = echo_handler,
- .user_ctx = NULL,
- .is_websocket = true
- };
- static httpd_handle_t start_webserver(void)
- {
- httpd_handle_t server = NULL;
- httpd_config_t config = HTTPD_DEFAULT_CONFIG();
- // Start the httpd server
- ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
- if (httpd_start(&server, &config) == ESP_OK) {
- // Registering the ws handler
- ESP_LOGI(TAG, "Registering URI handlers");
- httpd_register_uri_handler(server, &ws);
- return server;
- }
- ESP_LOGI(TAG, "Error starting server!");
- return NULL;
- }
- static esp_err_t stop_webserver(httpd_handle_t server)
- {
- // Stop the httpd server
- return httpd_stop(server);
- }
- static void disconnect_handler(void* arg, esp_event_base_t event_base,
- int32_t event_id, void* event_data)
- {
- httpd_handle_t* server = (httpd_handle_t*) arg;
- if (*server) {
- ESP_LOGI(TAG, "Stopping webserver");
- if (stop_webserver(*server) == ESP_OK) {
- *server = NULL;
- } else {
- ESP_LOGE(TAG, "Failed to stop http server");
- }
- }
- }
- static void connect_handler(void* arg, esp_event_base_t event_base,
- int32_t event_id, void* event_data)
- {
- httpd_handle_t* server = (httpd_handle_t*) arg;
- if (*server == NULL) {
- ESP_LOGI(TAG, "Starting webserver");
- *server = start_webserver();
- }
- }
- void app_main(void)
- {
- static httpd_handle_t server = NULL;
- ESP_ERROR_CHECK(nvs_flash_init());
- ESP_ERROR_CHECK(esp_netif_init());
- ESP_ERROR_CHECK(esp_event_loop_create_default());
- /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
- * Read "Establishing Wi-Fi or Ethernet Connection" section in
- * examples/protocols/README.md for more information about this function.
- */
- ESP_ERROR_CHECK(example_connect());
- /* Register event handlers to stop the server when Wi-Fi or Ethernet is disconnected,
- * and re-start it upon connection.
- */
- #ifdef CONFIG_EXAMPLE_CONNECT_WIFI
- ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
- ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
- #endif // CONFIG_EXAMPLE_CONNECT_WIFI
- #ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
- ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
- ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
- #endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
- /* Start the server for the first time */
- server = start_webserver();
- }
复制代码 运行websocket代码后,就可以在vue应用里编写web应用,调用WebSocket获取esp32数据了。
esp32这边的数据应对,可以在echo_handler 里添加相关的处理代码,例如使用Json格式的命令,更新温湿度信息,ESP32运行资源信息等等。
五、Vue里使用WebSocket
- async connectWebSocket() {
- const PING_INTERVAL = 10000 // 心跳间隔,单位为毫秒
- const heartbeatMessage = { type: 0, msg: "ping" } // 心跳消息
- const HOST_ADDRESS = "ws://192.168.50.132/ws"
- this.socket = new WebSocket(HOST_ADDRESS)
- this.socket.binaryType = "arraybuffer"
- let checkTask: NodeJS.Timeout | null = null
- // 监听连接事件
- this.socket.addEventListener("open", () => {
- // 启动心跳检测
- checkTask = setInterval(() => {
- if (this.socket)
- this.socket.send(JSON.stringify(heartbeatMessage))
- }, PING_INTERVAL)
- })
- // 监听消息事件
- this.socket.addEventListener("message", (event) => {
- try {
- const message = JSON.parse(event.data)
- if (message.type === WebSocket.CONNECTING) {
- return
- } else {
- if (this.messageQueue.length > 2 << 16) {
- this.messageQueue = []
- }
- console.log("WebSocket消息: ", message)
- }
- } catch (error) {
- }
- })
- // 监听关闭事件 断线重连
- this.socket.addEventListener("close", () => {
- if (this.socket && this.socket.readyState === WebSocket.CLOSED) {
- this.messageQueue.forEach((message) => {
- this.sendMessage(message)
- })
- this.messageQueue = []
- }
- checkTask && clearInterval(checkTask)
- checkTask = null
- setTimeout(() => {
- this.connectWebSocket()
- }, 3000)
- })
- this.socket.addEventListener("error", (event) => {
- console.log("WebSocket error:", event)
- })
- },
复制代码
六、开发界面显示ESP32设备数据内容
我们可以在app.vue文件里修改内容,添加以下内容
在这里,我们使用了headlessui的RadioGroup 组件,并使用了tailwind css的一些常见配置(渐变背景、常用布局配置等等)
- <template>
- <div class="w-full px-4 py-16 bg-gradient-to-r from-cyan-500 to-blue-600">
- <div class="mx-auto w-full max-w-md">
- <RadioGroup v-model="selected">
- <RadioGroupLabel class="sr-only">Server size</RadioGroupLabel>
- <div class="space-y-2">
- <RadioGroupOption as="template" v-for="device in devices" :key="device.name" :value="device"
- v-slot="{ active, checked }">
- <div :class="[
- active
- ? 'ring-1 ring-white/60 ring-offset-1 ring-offset-sky-300'
- : '',
- checked ? 'bg-sky-900/75 text-white ' : 'bg-white ',
- ]" class="relative flex cursor-pointer rounded-lg px-5 py-4 shadow-md focus:outline-none">
- <div class="flex w-full items-center justify-between">
- <div class="flex items-center">
- <div class="text-sm">
- <RadioGroupLabel as="p" :class="checked ? 'text-white' : 'text-gray-900'" class="font-medium pb-1">
- {{ device.name }}
- </RadioGroupLabel>
- <RadioGroupDescription as="span" :class="checked ? 'text-sky-100' : 'text-gray-500'" class="inline">
- <span>{{ device.status1 }}</span>
- <span class="pl-8">{{ device.status2 }}</span>
- </RadioGroupDescription>
- </div>
- </div>
- <div v-show="checked" class="shrink-0 text-white">
- <svg class="h-6 w-6" viewBox="0 0 24 24" fill="none">
- <circle cx="12" cy="12" r="12" fill="#fff" fill-opacity="0.2" />
- <path d="M7 13l3 3 7-7" stroke="#fff" stroke-width="1.5" stroke-linecap="round"
- stroke-linejoin="round" />
- </svg>
- </div>
- </div>
- </div>
- </RadioGroupOption>
- </div>
- </RadioGroup>
- </div>
- </div>
- </template>
- <script setup>
- import { ref } from 'vue'
- import {
- RadioGroup,
- RadioGroupLabel,
- RadioGroupDescription,
- RadioGroupOption,
- } from '@headlessui/vue'
- import { useESPDevicesStore } from '../stores/espDevicesStore'
- import { storeToRefs } from 'pinia'
- const piniaSocketStore = useWebSocketStore()
- const Devices = useESPDevicesStore()
- const esplist = storeToRefs(Devices).espList
- const selected = ref(esplist[0])
- function askForESPDevices() {
- piniaSocketStore.sendMessage("GET_DEVICES")
- }
- onMounted(() => {
- askForESPDevices();
- })
- </script>
复制代码
界面预览如下图
由于时间有限,控制LED灯的界面和其他内容就来不及做了,本次测试就先做到这里吧。
|
|