设为首页收藏本站
查看: 323|回复: 0

[MYD-YM62X 测评] 基于MYD-YM62X的个人智能家居平台开发

[复制链接]

7

主题

2

回帖

125

积分

注册会员

积分
125
忙碌的死龙 发表于 2024-6-1 12:24:49 | 显示全部楼层 |阅读模式
本帖最后由 忙碌的死龙 于 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左右,不足以安装相关环境,我们需要使用以下命令进行扩容
  1. resize2fs /dev/mmcblk0p2
复制代码
扩容后的分区大小如下图



二、安装node运行环境

由于开发板上没有apt之类的包管理软件,这时候就需要请出nvm工具了,我们可以使用国内源加速的安装方式。
  1. bash -c "$(curl -fsSL https://gitee.com/RubyMetric/nvm-cn/raw/main/install.sh)"
复制代码
运行这个命令之后,会在用户目录下新建一个.nvm目录,并安装nvm相关软件。安装完毕需要重新加载bashrc配置环境文件。
  1. source ~/.bashrc
复制代码
然后安装node和pnpm

  1. nvm install v20.14
复制代码
给pnpm配置国内源
  1. pnpm config set registry https://registry.npmmirror.com
复制代码


三、初始化vue项目

使用命令初始化项目,并安装需要用到的软件包

  1. pnpm create vite@latest myesp32 -- --template vue
  2. cd myesp32/
  3. pnpm install -D tailwindcss postcss autoprefixer
  4. npx tailwindcss init -p
  5. pnpm install @headlessui/vue
复制代码
安装完对应软件包后,根据tailwindcss 官网文档的,我们需要将tailwind.config.js这个文件修改成以下内容
  1. /** @type {import('tailwindcss').Config} */
  2. export default {
  3.   content: [
  4.     "./index.html",
  5.     "./src/**/*.{vue,js,ts,jsx,tsx}",
  6.   ],
  7.   theme: {
  8.     extend: {},
  9.   },
  10.   plugins: [],
  11. }
复制代码
然后将 ./src/style.css 文件修改成以下内容
  1. @tailwind base;
  2. @tailwind components;
  3. @tailwind utilities;
复制代码
然后就可以用以下命令开启服务器进行开发调试了
  1. pnpm run dev --host
复制代码


然后就可以点击链接进行开发了。

四、ESP32 HUB的WebSocket服务
esp32自带的源码Demo里,可以找到相关的案例代码(examples/protocols/http_server/ws_echo_server)。

  1. /* WebSocket Echo Server Example

  2.    This example code is in the Public Domain (or CC0 licensed, at your option.)

  3.    Unless required by applicable law or agreed to in writing, this
  4.    software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  5.    CONDITIONS OF ANY KIND, either express or implied.
  6. */

  7. #include <esp_wifi.h>
  8. #include <esp_event.h>
  9. #include <esp_log.h>
  10. #include <esp_system.h>
  11. #include <nvs_flash.h>
  12. #include <sys/param.h>
  13. #include "esp_netif.h"
  14. #include "esp_eth.h"
  15. #include "protocol_examples_common.h"

  16. #include <esp_http_server.h>

  17. /* A simple example that demonstrates using websocket echo server
  18. */
  19. static const char *TAG = "ws_echo_server";

  20. /*
  21. * Structure holding server handle
  22. * and internal socket fd in order
  23. * to use out of request send
  24. */
  25. struct async_resp_arg {
  26.     httpd_handle_t hd;
  27.     int fd;
  28. };

  29. /*
  30. * async send function, which we put into the httpd work queue
  31. */
  32. static void ws_async_send(void *arg)
  33. {
  34.     static const char * data = "Async data";
  35.     struct async_resp_arg *resp_arg = arg;
  36.     httpd_handle_t hd = resp_arg->hd;
  37.     int fd = resp_arg->fd;
  38.     httpd_ws_frame_t ws_pkt;
  39.     memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
  40.     ws_pkt.payload = (uint8_t*)data;
  41.     ws_pkt.len = strlen(data);
  42.     ws_pkt.type = HTTPD_WS_TYPE_TEXT;

  43.     httpd_ws_send_frame_async(hd, fd, &ws_pkt);
  44.     free(resp_arg);
  45. }

  46. static esp_err_t trigger_async_send(httpd_handle_t handle, httpd_req_t *req)
  47. {
  48.     struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
  49.     if (resp_arg == NULL) {
  50.         return ESP_ERR_NO_MEM;
  51.     }
  52.     resp_arg->hd = req->handle;
  53.     resp_arg->fd = httpd_req_to_sockfd(req);
  54.     esp_err_t ret = httpd_queue_work(handle, ws_async_send, resp_arg);
  55.     if (ret != ESP_OK) {
  56.         free(resp_arg);
  57.     }
  58.     return ret;
  59. }

  60. /*
  61. * This handler echos back the received ws data
  62. * and triggers an async send if certain message received
  63. */
  64. static esp_err_t echo_handler(httpd_req_t *req)
  65. {
  66.     if (req->method == HTTP_GET) {
  67.         ESP_LOGI(TAG, "Handshake done, the new connection was opened");
  68.         return ESP_OK;
  69.     }
  70.     httpd_ws_frame_t ws_pkt;
  71.     uint8_t *buf = NULL;
  72.     memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
  73.     ws_pkt.type = HTTPD_WS_TYPE_TEXT;
  74.     /* Set max_len = 0 to get the frame len */
  75.     esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
  76.     if (ret != ESP_OK) {
  77.         ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
  78.         return ret;
  79.     }
  80.     ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
  81.     if (ws_pkt.len) {
  82.         /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
  83.         buf = calloc(1, ws_pkt.len + 1);
  84.         if (buf == NULL) {
  85.             ESP_LOGE(TAG, "Failed to calloc memory for buf");
  86.             return ESP_ERR_NO_MEM;
  87.         }
  88.         ws_pkt.payload = buf;
  89.         /* Set max_len = ws_pkt.len to get the frame payload */
  90.         ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
  91.         if (ret != ESP_OK) {
  92.             ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
  93.             free(buf);
  94.             return ret;
  95.         }
  96.         ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload);
  97.     }
  98.     ESP_LOGI(TAG, "Packet type: %d", ws_pkt.type);
  99.     if (ws_pkt.type == HTTPD_WS_TYPE_TEXT &&
  100.         strcmp((char*)ws_pkt.payload,"Trigger async") == 0) {
  101.         free(buf);
  102.         return trigger_async_send(req->handle, req);
  103.     }

  104.     ret = httpd_ws_send_frame(req, &ws_pkt);
  105.     if (ret != ESP_OK) {
  106.         ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
  107.     }
  108.     free(buf);
  109.     return ret;
  110. }

  111. static const httpd_uri_t ws = {
  112.         .uri        = "/ws",
  113.         .method     = HTTP_GET,
  114.         .handler    = echo_handler,
  115.         .user_ctx   = NULL,
  116.         .is_websocket = true
  117. };


  118. static httpd_handle_t start_webserver(void)
  119. {
  120.     httpd_handle_t server = NULL;
  121.     httpd_config_t config = HTTPD_DEFAULT_CONFIG();

  122.     // Start the httpd server
  123.     ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
  124.     if (httpd_start(&server, &config) == ESP_OK) {
  125.         // Registering the ws handler
  126.         ESP_LOGI(TAG, "Registering URI handlers");
  127.         httpd_register_uri_handler(server, &ws);
  128.         return server;
  129.     }

  130.     ESP_LOGI(TAG, "Error starting server!");
  131.     return NULL;
  132. }

  133. static esp_err_t stop_webserver(httpd_handle_t server)
  134. {
  135.     // Stop the httpd server
  136.     return httpd_stop(server);
  137. }

  138. static void disconnect_handler(void* arg, esp_event_base_t event_base,
  139.                                int32_t event_id, void* event_data)
  140. {
  141.     httpd_handle_t* server = (httpd_handle_t*) arg;
  142.     if (*server) {
  143.         ESP_LOGI(TAG, "Stopping webserver");
  144.         if (stop_webserver(*server) == ESP_OK) {
  145.             *server = NULL;
  146.         } else {
  147.             ESP_LOGE(TAG, "Failed to stop http server");
  148.         }
  149.     }
  150. }

  151. static void connect_handler(void* arg, esp_event_base_t event_base,
  152.                             int32_t event_id, void* event_data)
  153. {
  154.     httpd_handle_t* server = (httpd_handle_t*) arg;
  155.     if (*server == NULL) {
  156.         ESP_LOGI(TAG, "Starting webserver");
  157.         *server = start_webserver();
  158.     }
  159. }


  160. void app_main(void)
  161. {
  162.     static httpd_handle_t server = NULL;

  163.     ESP_ERROR_CHECK(nvs_flash_init());
  164.     ESP_ERROR_CHECK(esp_netif_init());
  165.     ESP_ERROR_CHECK(esp_event_loop_create_default());

  166.     /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
  167.      * Read "Establishing Wi-Fi or Ethernet Connection" section in
  168.      * examples/protocols/README.md for more information about this function.
  169.      */
  170.     ESP_ERROR_CHECK(example_connect());

  171.     /* Register event handlers to stop the server when Wi-Fi or Ethernet is disconnected,
  172.      * and re-start it upon connection.
  173.      */
  174. #ifdef CONFIG_EXAMPLE_CONNECT_WIFI
  175.     ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
  176.     ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
  177. #endif // CONFIG_EXAMPLE_CONNECT_WIFI
  178. #ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
  179.     ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
  180.     ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
  181. #endif // CONFIG_EXAMPLE_CONNECT_ETHERNET

  182.     /* Start the server for the first time */
  183.     server = start_webserver();
  184. }
复制代码
运行websocket代码后,就可以在vue应用里编写web应用,调用WebSocket获取esp32数据了。
esp32这边的数据应对,可以在echo_handler 里添加相关的处理代码,例如使用Json格式的命令,更新温湿度信息,ESP32运行资源信息等等。



五、Vue里使用WebSocket

  1. async connectWebSocket() {
  2.             const PING_INTERVAL = 10000 // 心跳间隔,单位为毫秒
  3.             const heartbeatMessage = { type: 0, msg: "ping" } // 心跳消息
  4.             const HOST_ADDRESS = "ws://192.168.50.132/ws"
  5.             this.socket = new WebSocket(HOST_ADDRESS)
  6.             this.socket.binaryType = "arraybuffer"
  7.             let checkTask: NodeJS.Timeout | null = null
  8.             // 监听连接事件
  9.             this.socket.addEventListener("open", () => {
  10.                 // 启动心跳检测
  11.                 checkTask = setInterval(() => {
  12.                     if (this.socket)
  13.                         this.socket.send(JSON.stringify(heartbeatMessage))
  14.                 }, PING_INTERVAL)
  15.             })
  16.             // 监听消息事件
  17.             this.socket.addEventListener("message", (event) => {
  18.                 try {
  19.                     const message = JSON.parse(event.data)
  20.                     if (message.type === WebSocket.CONNECTING) {
  21.                         return
  22.                     } else {
  23.                         if (this.messageQueue.length > 2 << 16) {
  24.                             this.messageQueue = []
  25.                         }
  26.                         console.log("WebSocket消息: ", message)
  27.                     }
  28.                 } catch (error) {
  29.                 }
  30.             })
  31.             // 监听关闭事件 断线重连
  32.             this.socket.addEventListener("close", () => {
  33.                 if (this.socket && this.socket.readyState === WebSocket.CLOSED) {
  34.                     this.messageQueue.forEach((message) => {
  35.                         this.sendMessage(message)
  36.                     })
  37.                     this.messageQueue = []
  38.                 }
  39.                 checkTask && clearInterval(checkTask)
  40.                 checkTask = null
  41.                 setTimeout(() => {
  42.                     this.connectWebSocket()
  43.                 }, 3000)
  44.             })
  45.             this.socket.addEventListener("error", (event) => {
  46.                 console.log("WebSocket error:", event)
  47.             })
  48.         },
复制代码

六、开发界面显示ESP32设备数据内容
我们可以在app.vue文件里修改内容,添加以下内容
在这里,我们使用了headlessui的RadioGroup 组件,并使用了tailwind css的一些常见配置(渐变背景、常用布局配置等等)
  1. <template>
  2.   <div class="w-full px-4 py-16 bg-gradient-to-r from-cyan-500 to-blue-600">
  3.     <div class="mx-auto w-full max-w-md">
  4.       <RadioGroup v-model="selected">
  5.         <RadioGroupLabel class="sr-only">Server size</RadioGroupLabel>
  6.         <div class="space-y-2">
  7.           <RadioGroupOption as="template" v-for="device in devices" :key="device.name" :value="device"
  8.             v-slot="{ active, checked }">
  9.             <div :class="[
  10.               active
  11.                 ? 'ring-1 ring-white/60 ring-offset-1 ring-offset-sky-300'
  12.                 : '',
  13.               checked ? 'bg-sky-900/75 text-white ' : 'bg-white ',
  14.             ]" class="relative flex cursor-pointer rounded-lg px-5 py-4 shadow-md focus:outline-none">
  15.               <div class="flex w-full items-center justify-between">
  16.                 <div class="flex items-center">
  17.                   <div class="text-sm">
  18.                     <RadioGroupLabel as="p" :class="checked ? 'text-white' : 'text-gray-900'" class="font-medium pb-1">
  19.                       {{ device.name }}
  20.                     </RadioGroupLabel>
  21.                     <RadioGroupDescription as="span" :class="checked ? 'text-sky-100' : 'text-gray-500'" class="inline">
  22.                       <span>{{ device.status1 }}</span>
  23.                       <span class="pl-8">{{ device.status2 }}</span>
  24.                     </RadioGroupDescription>
  25.                   </div>
  26.                 </div>
  27.                 <div v-show="checked" class="shrink-0 text-white">
  28.                   <svg class="h-6 w-6" viewBox="0 0 24 24" fill="none">
  29.                     <circle cx="12" cy="12" r="12" fill="#fff" fill-opacity="0.2" />
  30.                     <path d="M7 13l3 3 7-7" stroke="#fff" stroke-width="1.5" stroke-linecap="round"
  31.                       stroke-linejoin="round" />
  32.                   </svg>
  33.                 </div>
  34.               </div>
  35.             </div>
  36.           </RadioGroupOption>
  37.         </div>
  38.       </RadioGroup>
  39.     </div>
  40.   </div>
  41. </template>

  42. <script setup>
  43. import { ref } from 'vue'
  44. import {
  45.   RadioGroup,
  46.   RadioGroupLabel,
  47.   RadioGroupDescription,
  48.   RadioGroupOption,
  49. } from '@headlessui/vue'

  50. import { useESPDevicesStore } from '../stores/espDevicesStore'
  51. import { storeToRefs } from 'pinia'

  52. const piniaSocketStore = useWebSocketStore()
  53. const Devices = useESPDevicesStore()
  54. const esplist = storeToRefs(Devices).espList

  55. const selected = ref(esplist[0])
  56. function askForESPDevices() {
  57.     piniaSocketStore.sendMessage("GET_DEVICES")
  58. }

  59. onMounted(() => {
  60.   askForESPDevices();
  61. })
  62. </script>
复制代码


界面预览如下图





由于时间有限,控制LED灯的界面和其他内容就来不及做了,本次测试就先做到这里吧。



回复

使用道具 举报

您需要登录后才可以回帖 登录

本版积分规则

Archiver|手机版|小黑屋|米尔科技论坛   

GMT+8, 2024-10-31 15:14 , Processed in 0.162683 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表