11374浏览
查看: 11374|回复: 0

[M10项目] 用行空板语音命令实现橱柜自动开关

[复制链接]
本帖最后由 zoey不种土豆 于 2024-10-16 13:54 编辑

项目概述
本次分享的社区优秀案例是一个基于行空板和FireBeetle ESP32 实现的无障碍家庭项目———用语音控制的自动开关橱柜。通过语音控制柜门自动开关,来方便居家日常生活,也可用来改善因家庭空间或动线不合理带来的橱柜取用困难问题,或是为行动不便的残障人士减少居家生活中的困难。(注:原作者还分享了一个通过接近式传感器控制自动开关的方案,如果有兴趣,可以看到文末阅读原文
本项目不只局限于设计智能自动开关橱柜,还整合了智能厨房解决方案,大家还可以基于此项目来添加更多设备,并使这些定制设备更易于使用。
在设计方案时,考虑到有多个橱柜的情况,计划对不同橱柜设置不同的名字来语音激活。
本次项目的构思主要有以下有 3 个部分:
1.发出“打开”或“关闭”指令的设备。
2.负责开启和关闭智能橱柜的设备。
3.作为中介,能监听传入的指令,并将它们分发给正在等待激活指令的智能设备。
用行空板语音命令实现橱柜自动开关图1

项目构思视图


用品清单
硬件
行空板 ×1
DFRobot FireBeetle ESP32 ×1
M5Stack CM4Stack 开发套件 ×1
SG90 微型伺服电机 ×若干
软件
Arduino IDE   
Mind+
Thonny  

项目过程
想要控制橱柜的智能开关,可通过使用IFTTT(If This Then That,基于任务的触发)来轻松集成语音命令的方法。这个IFTTT设置是一个易于理解的方法,用于为已有设备添加语音命令。
IFTTT的工作原理:
1.触发器(This):这是配方的起始点,可以是特定服务上的事件,如收到新邮件、日历上的新事件、社交媒体上的新帖子等。
2.动作(That):这是触发器发生后要执行的操作,可以是发送通知、发送邮件、控制智能家居设备等。
3.频道(channel):将触发器和动作结合起来的规则,用户可以创建多个频道来自动化不同的任务。

IFTTT的界面简单直观,使得即使是没有编程背景的用户也能轻松创建和使用配方。
作者使用的是安卓和谷歌设备,通过Google助手添加语音命令开始。
用行空板语音命令实现橱柜自动开关图2

可以设置场景命名,本项目命名为 "sink cabinet"
对于 “if this then that” 的 “that” 部分,选择 webhook。按如下所示进行设置,可以正常运行。
用行空板语音命令实现橱柜自动开关图3

由于IFTTT通过外部服务工作,所以外部IP地址会用于智能家居设备。如果您不想使用IFTTT或包含外部IP,我们的解决方案将也包括一个完全内部的设置——智能厨房的自定义中心。

智能化厨房
使用CM4Stack 开发套件与 Raspberry Pi Compute Module 4 设置智能家庭设备。
目标是将其设置通用到可以添加更多新设备。编写一个设置了Flask服务器的Python脚本,等待接收命令,然后将命令发布到同一服务上的MQTT主题。这样,像ESP32这样的设备就可以轻松接收到“cabinet”命令。
设置
开始编码之前,在 CM4Stack 上完成设置,只需复制粘贴下面的许多命令即可。只需将特定字段更新为您想要的内容,例如您的用户名和密码。
安装和配置 Mosquitto (MQTT Broker):
通过安装 Mosquitto MQTT 代理来设置 Mosquitto MQTT 代理:
用行空板语音命令实现橱柜自动开关图4

接下来,将 Mosquitto 配置为侦听所有网络接口并设置身份验证。编辑 Mosquitto 配置文件:
用行空板语音命令实现橱柜自动开关图5

在文件末尾添加以下内容:
用行空板语音命令实现橱柜自动开关图6

创建密码文件并添加用户(在此文件中更改用户名):
用行空板语音命令实现橱柜自动开关图7

系统会提示设置密码。然后,重新启动 Mosquitto 以应用更改:
用行空板语音命令实现橱柜自动开关图8

配置网络:
通过编辑 DHCP 客户端配置文件,确保设备具有静态 IP 地址。这样,当重启时,不需要更新所有连接的设备:
用行空板语音命令实现橱柜自动开关图9

添加以下行:
用行空板语音命令实现橱柜自动开关图10

再次重启:
用行空板语音命令实现橱柜自动开关图11

配置防火墙:
需要确保某些端口是打开的:
用行空板语音命令实现橱柜自动开关图12

设置环境变量:
最后需要设置 Python 脚本将使用的环境变量,根据需要更新下面的值:
用行空板语音命令实现橱柜自动开关图13

到此为止,您应该已经准备好运行我们在本节开头讨论的程序了。代码包含在项目中。只需确保在代码中更新值以匹配您刚才设置的值,并且一切都应该立即正常工作。
按需要添加更多橱柜
如果想添加更多橱柜,也很容易实现更改。这里添加了第二个伺服器并扩展了程序。我们不再只监听“cabinet”,而是监听特定的橱柜。因此,程序将改为监听“dishwasher cabinet”和“stove cabinet”。这感觉更像是实际使用时的更真实表现。
如介绍 Flask 服务器设置的部分所述,不需要在那里进行任何修改。
自定义语音命令
行空板配有一个内置麦克风,可以进行语音命令设置。通过设置一个简单的 python 脚本,可以让行空板作为通过刚设置的 CM4Stack 的 Flask 服务器运行语音命令的一种方法,然后转到我们刚刚构建的 Smart Cabinet 等设备。
通过 Mind+ 运行,监听语音命令,我们监听语音命令,当我们听到单词“butler”时,将随后的单词发送到Flask服务器。然后这些命令会传输到家中的设备,包括我们的智能开关橱柜。“Butler”作为触发词,就像你说“小爱同学”或“Hey Siri”一样,同时也很容易更改。
实现这个功能,需要以下两个库:
用行空板语音命令实现橱柜自动开关图14

可以通过 Mind+ 中的 Library Management 选项卡进行安装。
用行空板语音命令实现橱柜自动开关图15

需要通过 ssh 连接到 行空板 并运行以下命令,否则您将遇到 Google api 错误:
  1. sudo apt-get install flac
复制代码

项目总结
这套可以自动运行的智能橱柜完成了!可选择自动运行,或作为智能家居中心,为未来的自定义智能家居项目做准备,可以通过语音命令控制这些自定义的智能家居项目。希望这个项目可以帮助到有需要的人,为他们带来无障碍体验的居家生活。
希望大家喜欢这个项目。


附件
1.Smart Cabinets 原理图
用行空板语音命令实现橱柜自动开关图16

控制 2 个舵机,来演示使用 ESP32 控制多个橱柜


voice_controller.py
在行空板上运行,以侦听语音命令,然后这些命令可以与 Flask 服务器通信并控制我们的橱柜
  1. <div data-page-id="YqyEdhFqao7fmGxKfa2cALV3nOc" data-lark-html-role="root" data-docx-has-block-data="false"><pre class="ace-line ace-line old-record-id-AREPdjLz1omexTxZYxBc2qg5nWg"><code class="language-Python" data-lark-language="Python" data-wrap="false"># -*- coding: UTF-8 -*-
  2. import sys
  3. import speech_recognition as sr
  4. import time
  5. import paho.mqtt.publish as publish
  6. # MQTT Broker Settings
  7. BROKER = "192.168.86.84"  # Change to your MQTT broker's IP address - this is the default one
  8. PORT = 1883
  9. TOPIC = "home/<topic>"
  10. USERNAME = "<username>"  # Change to your MQTT username
  11. PASSWORD = "<password>"  # Change to your MQTT password
  12. said_butler = False  # To check if the last word we heard was "butler" so we know to send the next word(s) as a command
  13. def listen_for_commands(recognizer, audio):
  14.     global said_butler
  15.     """Callback function that processes the audio as soon as it is available."""
  16.     try:
  17.         # Recognize speech using Google's speech recognition
  18.         command = recognizer.recognize_google(audio).lower()
  19.         print("You said:", command)
  20.         
  21.         # Check if the command contains the wake word 'butler'
  22.         if 'butler' in command:
  23.             said_butler = True  # Set flag to true to send next commands
  24.             command = command.split('butler', 1)[1].strip()  # Get everything after 'butler'
  25.         if said_butler and command:  # If the flag is set and there is a command
  26.             print("Command received:", command)
  27.             words = command.split()
  28.             if len(words) > 0:
  29.                 first_word = words[0]
  30.                 print("First word received:", first_word)
  31.                 # Send the first word as a separate command to the MQTT broker
  32.                 publish.single(TOPIC, first_word, hostname=BROKER, port=PORT, auth={'username': USERNAME, 'password': PASSWORD})
  33.             
  34.             # Send the full command to the MQTT broker
  35.             publish.single(TOPIC, command, hostname=BROKER, port=PORT, auth={'username': USERNAME, 'password': PASSWORD})
  36.             print("Commands sent to the Flask server via MQTT.")
  37.             said_butler = False  # Reset flag after command is sent
  38.     except sr.UnknownValueError:
  39.         print("Google Speech Recognition could not understand the audio")
  40.     except sr.RequestError as e:
  41.         print(f"Could not request results from Google Speech Recognition service; {e}")
  42. def main():
  43.     # Initialize recognizer class (for recognizing the speech)
  44.     recognizer = sr.Recognizer()
  45.     microphone = sr.Microphone()
  46.     # Adjust the recognizer sensitivity to ambient noise and record audio
  47.     with microphone as source:
  48.         recognizer.adjust_for_ambient_noise(source)
  49.         print("Set minimum energy threshold to:", recognizer.energy_threshold)
  50.         recognizer.pause_threshold = 0.8  # Adjust based on testing; default is 0.8 seconds
  51.         recognizer.non_speaking_duration = 0.4  # Adjust based on testing; default is 0.5 seconds
  52.     # Start listening in the background (non-blocking)
  53.     stop_listening = recognizer.listen_in_background(microphone, listen_for_commands, phrase_time_limit=5)
  54.     # Keep the main thread alive, or the background listener will stop
  55.     try:
  56.         while True:
  57.             time.sleep(0.1)  # Sleep briefly to limit CPU usage
  58.     except KeyboardInterrupt:
  59.         stop_listening(wait_for_stop=False)  # Stop listening when Ctrl+C is pressed
  60.         print("Stopped listening...")
  61. if __name__ == "__main__":
  62.     main()
复制代码

SmartCabinetsPlural_Generified.ino
这是一个如何让一个微控制器运行多个 smart cabinet 的示例,以及演示处理多个 cabinets 的一般逻辑调整。
  1. <div data-page-id="YqyEdhFqao7fmGxKfa2cALV3nOc" data-lark-html-role="root" data-docx-has-block-data="false"><pre class="ace-line ace-line old-record-id-EucJdUwITodnFyxYjmmcF5aCnYA"><code class="language-Python" data-lark-language="Python" data-wrap="false">#include <WiFi.h>
  2. #include <PubSubClient.h>
  3. #include <ESP32Servo.h>
  4. const char* ssid = "<Your Wifi>";
  5. const char* password = "<Your wifi password>";
  6. //MQTT Broker settings
  7. const char* mqtt_server = "<server ip>";  // MQTT broker IP address
  8. const int mqtt_port = 1883;
  9. const char* mqtt_user = "<usernames>";
  10. const char* mqtt_password = "<password>";
  11. const char* mqtt_topic = "home/<topic>";
  12. //Servo settings
  13. const int servoPinDishwasher = 32;
  14. const int servoPinStove = 33;
  15. Servo servoDishwasher;
  16. Servo servoStove;
  17. int servoOpenPosition = 180;  // Position to open the cabinet - change as needed
  18. int servoClosePosition = 0;   // Position to close the cabinet - change as needed
  19. bool isDishwasherOpen = false;
  20. bool isStoveOpen = false;
  21. WiFiClient espClient;
  22. PubSubClient client(espClient);
  23. void setup() {
  24.   Serial.begin(115200);
  25.   delay(5000);
  26.   servoDishwasher.attach(servoPinDishwasher);
  27.   servoStove.attach(servoPinStove);
  28.   servoDishwasher.write(servoClosePosition);
  29.   servoStove.write(servoClosePosition);
  30.   setup_wifi();
  31.   client.setServer(mqtt_server, mqtt_port);
  32.   client.setCallback(callback);
  33. }
  34. void setup_wifi() {
  35.   Serial.print("Connecting to ");
  36.   Serial.println(ssid);
  37.   WiFi.begin(ssid, password);
  38.   while (WiFi.status() != WL_CONNECTED) {
  39.     delay(500);
  40.     Serial.print(".");
  41.   }
  42.   Serial.println("WiFi connected");
  43.   Serial.println("IP address: ");
  44.   Serial.println(WiFi.localIP());
  45. }
  46. void callback(char* topic, byte* message, unsigned int length) {
  47.   Serial.print("Message arrived on topic: ");
  48.   Serial.print(topic);
  49.   Serial.print(". Message: ");
  50.   String messageTemp;
  51.   for (int i = 0; i < length; i++) {
  52.     messageTemp += (char)message[i];
  53.   }
  54.   Serial.println(messageTemp);
  55.   // Check to use specific cabinets
  56.   if (messageTemp == "dishwasher cabinet") {
  57.     if (isDishwasherOpen) {
  58.       servoDishwasher.write(servoClosePosition);
  59.       isDishwasherOpen = false;
  60.       Serial.println("Dishwasher Cabinet closed");
  61.     } else {
  62.       servoDishwasher.write(servoOpenPosition);
  63.       isDishwasherOpen = true;
  64.       Serial.println("Dishwasher Cabinet opened");
  65.     }
  66.   } else if (messageTemp == "stove cabinet") {
  67.     if (isStoveOpen) {
  68.       servoStove.write(servoClosePosition);
  69.       isStoveOpen = false;
  70.       Serial.println("Stove Cabinet closed");
  71.     } else {
  72.       servoStove.write(servoOpenPosition);
  73.       isStoveOpen = true;
  74.       Serial.println("Stove Cabinet opened");
  75.     }
  76.   }
  77. }
  78. void reconnect() {
  79.   while (!client.connected()) {
  80.     Serial.print("Attempting MQTT connection...");
  81.     if (client.connect("ESP32Client", mqtt_user, mqtt_password)) {
  82.       Serial.println("connected");
  83.       client.subscribe(mqtt_topic);
  84.     } else {
  85.       Serial.print("failed, rc=");
  86.       Serial.print(client.state());
  87.       Serial.println(" try again in 5 seconds");
  88.       delay(5000);
  89.     }
  90.   }
  91. }
  92. void loop() {
  93.   if (!client.connected()) {
  94.     reconnect();
  95.   }
  96.   client.loop();
  97. }
复制代码

smartCabinet_Generified.ino
在 ESP32 上运行的单个智能橱柜的代码。

  1. <div data-page-id="YqyEdhFqao7fmGxKfa2cALV3nOc" data-lark-html-role="root" data-docx-has-block-data="false"><pre class="ace-line ace-line old-record-id-PodidFet0oNpANxwe34cNuuZnog"><code class="language-Python" data-lark-language="Python" data-wrap="false">#include <WiFi.h>
  2. #include <PubSubClient.h>
  3. #include <ESP32Servo.h>
  4. const char* ssid = "<Your Wifi>";
  5. const char* password = "<Your wifi password>";
  6. //MQTT Broker settings
  7. const char* mqtt_server = "<server ip>";  // MQTT broker IP address
  8. const int mqtt_port = 1883;
  9. const char* mqtt_user = "<usernames>";
  10. const char* mqtt_password = "<password>";
  11. const char* mqtt_topic = "home/<topic>";
  12. const int servoPin = 32;
  13. Servo myServo;
  14. int servoOpenPosition = 180;  // Position to open the cabinet - change as needed
  15. int servoClosePosition = 0;   // Position to close the cabinet - change as needed
  16. bool isCabinetOpen = false;   // Initial state of the cabinet
  17. WiFiClient espClient;
  18. PubSubClient client(espClient);
  19. void setup() {
  20.   Serial.begin(115200);  // Lowering baud rate for troubleshooting
  21.   delay(5000);
  22.   myServo.attach(servoPin);
  23.   myServo.write(servoClosePosition);  // Ensure servo starts in the closed position
  24.   setup_wifi();
  25.   client.setServer(mqtt_server, mqtt_port);
  26.   client.setCallback(callback);
  27. }
  28. void setup_wifi() {
  29.   delay(10);
  30.   Serial.println();
  31.   Serial.print("Connecting to ");
  32.   Serial.println(ssid);
  33.   WiFi.begin(ssid, password);
  34.   while (WiFi.status() != WL_CONNECTED) {
  35.     delay(500);
  36.     Serial.print(".");
  37.   }
  38.   Serial.println("");
  39.   Serial.println("WiFi connected");
  40.   Serial.println("IP address: ");
  41.   Serial.println(WiFi.localIP());
  42. }
  43. void callback(char* topic, byte* message, unsigned int length) {
  44.   Serial.print("Message arrived on topic: ");
  45.   Serial.print(topic);
  46.   Serial.print(". Message: ");
  47.   String messageTemp;
  48.   for (int i = 0; i < length; i++) {
  49.     Serial.print((char)message[i]);
  50.     messageTemp += (char)message[i];
  51.   }
  52.   Serial.println();
  53.   if (messageTemp == "cabinet") {
  54.     if (isCabinetOpen) {
  55.       myServo.write(servoClosePosition);
  56.       isCabinetOpen = false;
  57.       Serial.println("Cabinet closed");
  58.     } else {
  59.       myServo.write(servoOpenPosition);
  60.       isCabinetOpen = true;
  61.       Serial.println("Cabinet opened");
  62.     }
  63.   }
  64. }
  65. void reconnect() {
  66.   while (!client.connected()) {
  67.     Serial.print("Attempting MQTT connection...");
  68.     if (client.connect("ESP32Client", mqtt_user, mqtt_password)) {
  69.       Serial.println("connected");
  70.       // Subscribe to the topic
  71.       client.subscribe(mqtt_topic);
  72.     } else {
  73.       Serial.print("failed, rc=");
  74.       Serial.print(client.state());
  75.       Serial.println(" try again in 5 seconds");
  76.       delay(5000);
  77.     }
  78.   }
  79. }
  80. void loop() {
  81.   if (!client.connected()) {
  82.     reconnect();
  83.   }
  84.   client.loop();
  85. }
复制代码
smart_house.py
用来在 CM4Stack 上运行 Flask 服务器的方法,充当发出语音命令和 Smart Cabinet 之间的中间人。
  1. <div data-page-id="YqyEdhFqao7fmGxKfa2cALV3nOc" data-lark-html-role="root" data-docx-has-block-data="false"><pre class="ace-line ace-line old-record-id-Z3LXduqgeog1WdxFiVCcV9K6nib"><code class="language-Python" data-lark-language="Python" data-wrap="false">from flask import Flask, request, jsonify
  2. import paho.mqtt.client as mqtt
  3. import logging
  4. import os
  5. # Configuration parameters
  6. broker_address = os.getenv('MQTT_BROKER_ADDRESS', '192.168.86.84')  # Default to internal IP address
  7. mqtt_topic = os.getenv('MQTT_TOPIC', 'home/<topic>')
  8. http_server_port = int(os.getenv('HTTP_SERVER_PORT', 5000))
  9. mqtt_username = os.getenv('MQTT_USERNAME', '<username>')
  10. mqtt_password = os.getenv('MQTT_PASSWORD', '<password>')
  11. # Create Flask app
  12. app = Flask(__name__)
  13. # Setup logging
  14. logging.basicConfig(level=logging.INFO)
  15. # MQTT setup
  16. mqtt_client = mqtt.Client(client_id="<client id>", protocol=mqtt.MQTTv311)
  17. mqtt_client.username_pw_set(username=mqtt_username, password=mqtt_password)
  18. # Define on_connect callback
  19. def on_connect(client, userdata, flags, rc):
  20.     if rc == 0:
  21.         logging.info("Connected to MQTT Broker")
  22.         client.subscribe(mqtt_topic)
  23.     elif rc == 5:
  24.         logging.error("Authentication failed - check username and password")
  25.     else:
  26.         logging.error(f"Failed to connect, return code {rc}")
  27. # Define on_message callback
  28. def on_message(client, userdata, message):
  29.     logging.info(f"Received message: {message.payload.decode()} on topic {message.topic}")
  30. mqtt_client.on_connect = on_connect
  31. mqtt_client.on_message = on_message
  32. try:
  33.     mqtt_client.connect(broker_address)
  34.     mqtt_client.loop_start()
  35. except Exception as e:
  36.     logging.error(f"Failed to connect to MQTT Broker: {e}")
  37. @app.route('/command', methods=['POST'])
  38. def handle_command():
  39.     try:
  40.         data = request.json
  41.         action = data.get('action', '')
  42.         if action:
  43.             mqtt_client.publish(mqtt_topic, action)
  44.             logging.info(f"Command '{action}' sent to MQTT topic.")
  45.             return jsonify({"status": "success", "message": f"Command '{action}' sent to MQTT topic."}), 200
  46.         logging.warning("No action specified in the command.")
  47.         return jsonify({"status": "error", "message": "No action specified in the command."}), 400
  48.     except Exception as e:
  49.         logging.error(f"Error handling command: {e}")
  50.         return jsonify({"status": "error", "message": "Failed to process command."}), 500
  51. if __name__ == '__main__':
  52.     try:
  53.         app.run(host='0.0.0.0', port=http_server_port)
  54.     except Exception as e:
  55.         logging.error(f"Exception occurred: {e}")
  56.     finally:
  57.         mqtt_client.loop_stop()
  58.         mqtt_client.disconnect()
复制代码

作者:donutsorelse
发布时间:2024.09.05
原文链接:Smart Cabinets with a Custom Smart Home

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail