鲜de芒果

  • 2024-10-20
  • 回复了主题帖: 【2024 DigiKey 创意大赛】红外温度检测及火灾报警器

    JOEYCH 发表于 2024-10-20 13:53 想问问Homeassisant是部署在哪里的呀? NAS服务器上

  • 2024-10-16
  • 发表了主题帖: 【2024 DigiKey 创意大赛】红外温度检测及火灾报警器

    本帖最后由 鲜de芒果 于 2024-10-16 21:18 编辑 一、作品简介         本项目旨在开发一种基于红外技术的智能温度检测与火灾报警系统。该系统通过高精度的红外传感器,实时监测环境温度变化,识别温度异常上升的情况,并在检测到异常高温或快速升温时立即发出声光报警信号,有效预防火灾事故的发生。同时,系统具备远程监控能力,可通过无线网络将数据传输至监控中心,实现远程预警和数据分析。         该系统核心硬件包括高灵敏度红外温度传感器、微控制器单元(MCU)、声光报警模块、无线通信模块以及电源管理模块。红外传感器负责捕捉环境温度数据;MCU作为系统的大脑,处理数据并做出逻辑判断;无线通信模块实现数据的远程传输;电源管理模块确保系统稳定运行,支持电池和外部电源供电,以确保在紧急情况下的稳定运行。   二、系统框图 设计思路         设计的核心思想是集成高精度温度测量和快速反应报警机制。系统设计以高可靠性和低功耗为指导原则,确保设备在长时间运行时的可靠性和灵活性。同时,通过合理的软件设计,实现实时温度监控和智能火灾判定。         采用模块化设计思想,各部分独立又协同工作。设计上注重低功耗与高效率,确保设备能在无持续电源供应的情况下长时间运行。软件方面,采用事件驱动编程模型,优化响应速度和准确性。   系统框图 如以上系统框图所示,左右两个模块分别为左侧的传感器模块,右侧的监控模块。它们之间通过 WiFi 连接到网络进行通讯。 左侧的传感器模块主要作用是监控并采集环境温度数据,并通过 MQTT 将数据上报至公有云服务器或私有云服务器。搭载了舵机模块,可以使得监控的区域变得更加广阔,可以实现 300° 范围内对整个房间或区域进行监控。 右侧的监控模块,使用的是 ESP32-S3-DEVKITC-1-N8R8 开发板搭配一块 1.8 英寸 TFT-LCD 显示模块组成,分辨率 128 * 160。用于接收左侧传感器模块上报的环境温度数据,以及温度图的显示与异常温度警报显示。 整个系统采用模块化设计,可实现无缝接入各种云端,实现远程监控。也可以将数据对接至监控中心,实现远程预警和数据分析。   硬件介绍 ESP32-S3-DEVKITC-1-N8R8 是一款入门级开发板,搭载 Wi-Fi + Bluetooth® LE 模组 ESP32-S3-WROOM-1、ESP32-S3-WROOM-1U 或 ESP32-S3-WROOM-2。在本应用中原本打算作为主控使用,但由于体积过大,因此作为远程协控制器使用。   MLX90640ESF-BAB-000-TU 是一款远红外热像仪传感器,MLX90640远红外热传感器阵列可精确检测特定区域和温度范围内的目标物体,尺寸小巧,可方便集成到各种工业或智能控制应用中。在本应用中作为红外温度测量传感器使用。   ESP32-S3-WROOM-1U-N16R8 是一款通用型 Wi-Fi + 低功耗蓝牙 MCU 模组,搭载 ESP32-S3 系列芯片。除具有丰富的外设接口外,模组还拥有强大的神经网络运算能力和信号处理能力,适用于 AIoT 领域的多种应用场景,例如唤醒词检测和语音命令识别、人脸检测和识别、智能家居、智能家电、智能控制面板、智能扬声器等。在本应用中作为红外传感器主控使用。   SER0056 是 DFRobot 推出的一款带离合功能的 300° 转向舵机,在本应用中作为转向机构使用,使得红外监控的范围变大。   软件实现 传感器模块     软件方面参考了很多成熟方案,综合评比各方面功能最终决定采用 ESPHome 完成固件开发。ESPHome 是一个通过简单而强大的配置文件控制您的 ESP8266/ESP32 的系统,并通过家庭自动化系统远程控制它们。   监控模块     监控模块采用 CircuitPython 进行开发。CircuitPython 是一种编程语言,目的是希望能在低成本微控制器板上用简化的Python进行编程。它使入门比以往更容易,无需预先下载桌面。设置好电路板后,打开任何文本编辑器,然后开始编辑代码,就这么简单。   三、各部分功能说明 红外温度采集 利用 ESP32-S3 的强大处理能力和 MLX90640 的高灵敏度红外测温功能,使用 ESPHome 环境下构建一个实时温度监测系统。通过ESP32-S3读取 MLX90640 传感器的温度数据,并通过 Wi-Fi 将数据传输到云端或本地服务器进行进一步分析和处理。以下是关键代码实现: # 红外摄像头 MLX90640 camera_mlx90640:   id: thermal_cam   update_interval: 1s # 温度数据更新间隔   sda: 4  # I2C SDA PIN   scl: 5  # I2C SCL PIN   frequency: 400000  # I2C Clock Frequency   address: 0x33 # MLX90640 Address   mintemp: 0 # Minimal temperature for color mapping   maxtemp: 90 # Maximal temperature for color mapping   refresh_rate: 0x05 # For 16Hz or 0x04 for 8Hz   filter_level: 10.0 # 温度数据过滤等级   min_temperature:     id: mlx90640_min     name: "MLX90640 最小温度"   max_temperature:     id: mlx90640_max     name: "MLX90640 最大温度"   mean_temperature:     id: mlx90640_mean     name: "MLX90640 平均温度"   median_temperature:     id: mlx90640_median     name: "MLX90640 中间温度" 上述代码为 ESPHome 采集 MLX90640 温度数据的实现,可对温度数据进行平滑过滤。实现 最小温度、最大温度、平均温度、中间温度 的采集,采集的温度还可以通过 Wi-Fi 将数据传输到云端或本地服务器进行进一步分析和处理。下图为智能家居平台以可视化形式将温度展示出来。 电池电压采集         通过测量电池单体或电池组的电压值,来监测电池的工作状态和健康程度。这一功能对于确保电池在安全范围内运行、预防过充过放、延长电池寿命等方面具有重要意义。         ESP32-S3的ADC模块具有高精度和高分辨率的特点,能够准确测量电池电压的变化。通过选择合适的 GPIO 引脚和衰减倍数,ESP32-S3 可以灵活适应不同电压范围的测量需求。         本项目中,使用两颗 470KΩ 的电阻分压后,接入 ESP32-S3 的 IO2 引脚。如下图所示,可在智能家居平台可看电池电压的历史数据。 环境温度数据发布         MQTT(Message Queuing Telemetry Transport)是一种轻量级的消息传输协议,广泛应用于物联网设备之间的通信。其基于发布/订阅模式,能够实现高效、可靠的数据传输。在环境温度数据发布功能中,MQTT协议扮演着至关重要的角色,确保温度数据从传感器节点准确无误地传输到云平台或监控中心。以下为温度数据发布的实现,以 1Hz 的频率进行发布,确保温度数据实时准确地发布至云平台、监控节点或监控中心。 time:   - platform: homeassistant     on_time:     # 每秒发布温度数据,包含 最小温度、最大温度、平均温度、中值温度     - seconds: /1       then:         - mqtt.publish_json:             topic: "/esphome/sensor/mlx90640"             payload: |-               root["min"] = id(mlx90640_min).state;               root["max"] = id(mlx90640_max).state;               root["mean"] = id(mlx90640_mean).state;               root["median"] = id(mlx90640_min).state;   智能家居平台集成         智能家居平台能够实现设备的互联互通和集中控制,提供简单易懂的用户界面和操作方式,同时采取严格的安全措施来保护用户的信息和设备的安全。通过实时监测环境温度并上传到智能家居平台,可以实现对家居环境的智能化控制和管理。例如,当室内温度过高时,智能家居平台可以自动打开空调进行降温;当室外温度过低时,可以触发暖气设备的开启等。这种集成功能不仅提高了居住环境的舒适度和便利性,还有助于节能减排和环保。当然本例中的作用为监控环境温度更重要的是监控并预警火灾的发生,通过与智能家居平台集成后,可以很方便地实现异常温升的报警与监控,同时如果有其它辅助灭火的智能设备,还可以进行联动控制进行灭火操作,进一步且更智能化地保障人们的生命与财产安全。 下图为智能家居平台集成后的控制面板展示: 温度可视化         通过其红外温度可视化功能,用户可以将不可见的红外辐射转化为可见的热图像,从而直观地了解物体表面的温度分布情况。           MLX90640通过其内部的红外热电堆阵列接收物体表面的红外辐射,并将其转换为电信号。这些电信号经过处理后,可以得到物体表面各点的温度值。然后,通过特定的算法将这些温度值转化为热图像,并在屏幕上进行显示。用户可以通过观察热图像来了解物体表面的温度分布情况,从而进行进一步的分析和应用。           下图为通过算法将温度值转化为热图像后的效果图,分另为两根手指加一个热源和一个手掌的温度数据转换后的热图像: 异常温度警报         异常温度警报是一种基于温度监测的安全预警机制,旨在及时发现并提醒用户潜在的温度异常情况,以防止火灾、设备过热等危险事件的发生。                  异常温度警报功能广泛应用于各种场所,如工厂、仓库、数据中心、医院、学校等。在这些场所中,由于设备运行、人员活动等原因,可能存在温度异常的风险。通过部署异常温度警报系统,可以及时发现并处理这些风险,保障人员和财产的安全。同时,该功能还可以应用于智能家居领域,为用户提供更加舒适和安全的居住环境。           异常温度警报功能通常由温度传感器、数据处理单元和警报系统组成。温度传感器负责实时监测特定区域或设备的温度数据,并将数据传输至数据处理单元。数据处理单元对接收的温度数据进行分析,判断是否存在异常温度情况。一旦检测到异常温度,数据处理单元会立即触发警报系统,发出声光或其他形式的警报信号,以提醒用户采取相应措施。         在本应用中使用监控单元,远程监控最大温度的异常温度情况,出现异常温度则以红色字体形式表示最大温度。 正常温度效果图 异常温度效果图,实际为在传感器前方点燃了一盏酒精灯 启动或关闭区域自动扫描         区域自动扫描功能是一种智能化的功能,它允许设备在设定的区域内自动进行扫描,以获取相关信息或数据。这种功能广泛应用于各种领域,如安全监控、环境监测、智能家居等。通过启动或关闭该功能,用户可以灵活地控制设备的扫描行为,以满足不同的应用需求。         本项目中使用的温度传感器 MLX90640ESF-BAB-000-TU 在 X 轴方向只有 55°,Y 轴方向只有 35° 的视角。因此想要对大片区域监控需要借助一个可转动机构进行扫描来实现,类似摄像头的云台功能。本项目中选用了一个 SER0056 舵机来实现 X 轴的控制来实现范围扫描。如需要实现更大范围的扫描可以通过再增加一个舵机,实现 Y 轴方面的扫描。硬件上已实现两路舵机的控制。         当用户需要对设定的区域进行自动扫描时,可以通过特定的操作来启动扫描功能。可通过智能家居平台上的启动自动扫描开关来发送远程指令。一旦扫描功能被启动,设备将开始按照预设的 X 轴路径每 5秒 转动一次角度对目标区域进行扫描。 四、作品源码 项目源码下载地址:https://download.eeworld.com.cn/detail/%E9%B2%9Cde%E8%8A%92%E6%9E%9C/634584   源码说明 传感器模块源码 目录下为 ESPHome 项目源码,使用 ESPHome 进行编译。 监控模块源码 目录下为 CircuitPython 项目源码,需要先烧录 CircuitPython 固件后,将源码拷贝至设备中运行。 adafruit-circuitpython-espressif_esp32s3_devkitc_1_n8r2-en_US-8.2.6.bin 文件为项目中使用的 CircuitPython 固件, 可使用 ESPTools 进行烧录,也可使用 CircuitPython 的 Web-tools 进行烧录。 五、作品功能演示视频   六、项目总结         红外温度检测及火灾报警器通过集成高效传感器和可靠处理器,实现了实时温度监控和火灾早期预警的功能。它为提升安全预防措施提供了一种智能化解决方案,具有广泛的应用前景。然而,还通过远程监控技术实现了对潜在风险的早期识别与及时响应。未来,我们计划进一步优化算法,增强系统的智能化水平,如引入机器学习算法以提高误报率的降低,并探索更多应用场景,为公共安全贡献更大力量。           最后,感谢电子工程世界与得捷电子联合举办的【2024 DigiKey 创意大赛】活动,非常荣幸第一次参与这个活动。本次活动使我学到了不少的新知识,在项目过程中也遇到不少困难,最终实现的成品虽然有些潦草,但好在最终实现了相应的功能。祝愿 EEWorld 越办越好!   七、参考文献 https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitc-1.html https://esphome.io/ https://www.arduino.cc/ https://github.com/infotronic218/esphome-mlx9060 https://circuitpython.org/board/espressif_esp32s3_devkitc_1_n8r8/ https://docs.circuitpython.org/en/9.1.x/docs/index.html   八、项目文档

  • 加入了学习《得捷电子专区》,观看 【2024 DigiKey 创意大赛】红外温度检测及火灾报警器

  • 上传了资料: 【2024-DigiKey-创意大赛】红外温度检测及火灾报警器源码

  • 2024-10-02
  • 加入了学习《智能书房》,观看 智能书房

  • 发表了主题帖: 【Follow me第二季第2期】智能家居之智能书房

    本帖最后由 鲜de芒果 于 2024-10-2 17:28 编辑 1. 任务要求 通过外部LTR-329 环境光传感器,上传温湿度到HA,通过HA面板显示数据 通过外部AHT20温湿度传感器,上传温湿度到HA,通过HA面板显示数据 使用板载LED模拟台灯,可通过HA面板控制其开关与亮度调节 书房自动光线调节是一个智能书房的重要组成部分,它能够根据环境光线的变化自动调整台灯的亮度或开关状态,以确保阅读或工作时的光线既充足又舒适      2. 硬件准备 Arduino UNO R4 WiFi 开发板 * 1块 Adafruit LTR-329 Light Sensor 环境光传感器模块 * 1块 Adafruit AHT20 - Temperature & Humidity Sensor Breakout Board 温湿度传感器模块 * 1块 STEMMA QT / Qwiic JST SH 4-pin Cable 连接线 * 2根   3. HomeAssistant集成 **MQTT** 设备的发现将使人们能够在 [HomeAssistant](https://www.home-assistant.io) 方面只需要很少的配置工作就可以使用 **MQTT** 设备。配置是在设备本身和设备使用的主题上完成的。   > MQTT 发现默认启用,但可以禁用。发现主题的前缀(默认:`homeassistant`)可以更改。配置详情请参阅 [MQTT 选项部分](https://www.home-assistant.io/integrations/mqtt#configure-mqtt-options)   3.1 配置主题 向 MQTT 发送配置主题后, HomeAssistant 会自动发现当前传感器。 当前任务中使用了一个板载LED灯,作为 HomeAssistant 中的 Light 组件。该组件在 HomeAssistant 的 仪表盘 中可以远程控制板载LED的开和关。使用 MQTT 客户端向配置主题发送消息即可。本项目中使用了三个传感器 + 一个台灯,具体配置主题如下: // 台灯 消息 { "name":"table-lamp", "device_class": "light", "command_topic":"homeassistant/light/FollowMe2-2-table-lamp/switch", "state_topic":"homeassistant/sensor/FollowMe2-2/state", "brightness_command_topic": "homeassistant/light/FollowMe2-2-table-lamp/brightness/set", "brightness_state_topic": "homeassistant/sensor/FollowMe2-2/state", "state_value_template": "{{ value_json.builtinLed }}", "brightness_value_template": "{{ value_json.brightness }}", "unique_id":"FollowMe2-2-table-lamp", "device":{ "identifiers":[ "Arduino UNO R4 WiFi" ], "name":"UNO R4 WiFi", "manufacturer": "Arduino", "model": "UNO R4 WiFi", "hw_version": "1.0" } } // 书房 温度传感器 消息 { "device_class":"temperature", "state_topic":"homeassistant/sensor/FollowMe2-2/state", "unit_of_measurement":"°C", "value_template":"{{ value_json.temperature}}", "unique_id":"FollowMe2-2-study-temperature", "device":{ "identifiers":[ "Arduino UNO R4 WiFi" ], "name":"UNO R4 WiFi", "manufacturer": "Arduino", "model": "UNO R4 WiFi", "hw_version": "1.0" } } // 书房 湿度传感器 消息 { "device_class":"humidity", "state_topic":"homeassistant/sensor/FollowMe2-2/state", "unit_of_measurement":"%", "value_template":"{{ value_json.humidity}}", "unique_id":"FollowMe2-2-study-humidity", "device":{ "identifiers":[ "Arduino UNO R4 WiFi" ], "name":"UNO R4 WiFi", "manufacturer": "Arduino", "model": "UNO R4 WiFi", "hw_version": "1.0" } } // 书房 环境光传感器 消息 { "device_class":"illuminance", "state_topic":"homeassistant/sensor/FollowMe2-2/state", "unit_of_measurement":"lx", "value_template":"{{ value_json.ambientLight}}", "unique_id":"FollowMe2-2-study-illuminance", "device":{ "identifiers":[ "Arduino UNO R4 WiFi" ], "name":"UNO R4 WiFi", "manufacturer": "Arduino", "model": "UNO R4 WiFi", "hw_version": "1.0" } }   4. 代码实现 /** * FollowMe 2-2 任务4: * 1. 通过Wi-Fi,利用MQTT协议接入到开源的智能家居平台HA(HomeAssistant) */ #include <Wire.h> #include <WiFiS3.h> #include <ArduinoMqttClient.h> #include <ArduinoJson.h> #include <Adafruit_AHTX0.h> #include <Adafruit_LTR329_LTR303.h> #include "arduino_secrets.h" #define LOOP_DELAY 10 // loop函数延时(单位:毫秒) #define SENSOR_REPORT_INTERVAL 5 // 传感器数据更新间隔(单位:秒) #define STATE_ON "ON" // HA LED开状态 #define STATE_OFF "OFF" // HA LED关状态 char ssid[] = SECRET_SSID; // WIFI SSID char pass[] = SECRET_PASS; // WIFI PASSWD int status = WL_IDLE_STATUS; // WIFI 状态 // MQTT WiFiClient wifiClient; MqttClient mqttClient(wifiClient); const char broker[] = "192.168.2.120"; // HomeAssistant MQTT服务器地址 int port = 1883; // HomeAssistant MQTT服务器端口 char mqtt_user[] = MQTT_USER; // MQTT 用户名 char mqtt_pass[] = MQTT_PASS; // MQTT 密码 // 订阅主题(接收 HomeAssistant 控制板载LED开关指令) const char switch_subscribe_topic[] = "homeassistant/light/FollowMe2-2-table-lamp/switch"; // 订阅主题(接收 HomeAssistant 控制板载LED亮度调节指令) const char brightness_subscribe_topic[] = "homeassistant/light/FollowMe2-2-table-lamp/brightness/set"; bool isNeedReportState = true; // 是否需要更新状态到HA bool ledState = false; // 板载LED状态 // 发布主题(上报板载LED状态至 HomeAssistant的状态主题) const char publish_topic[] = "homeassistant/sensor/FollowMe2-2/state"; uint32_t tick = 0; // loop循环次数 uint8_t brightness = 0; // 灯亮度调节 Adafruit_AHTX0 aht; // AHT20 传感器 float temperature = 0; // AHT20 采集到的温度值 float humidity = 0; // AHT20 采集到的湿度值 Adafruit_LTR329 ltr = Adafruit_LTR329(); // LTR329传感器 float ambientLight = 0; // 环境光强度 ltr329_gain_t gain = LTR3XX_GAIN_1; // ALS增益 ltr329_integrationtime_t integrationTime = LTR3XX_INTEGTIME_100; // 积分时间 float integrationTimeVal = 0.1; uint16_t visible_plus_ir; // 可见光 uint16_t infrared; // 红外 /** * 设置板载LED状态 */ void setLightState() { if (ledState) { if(0 == brightness) { brightness = 255; } analogWrite(LED_BUILTIN, brightness); } else { analogWrite(LED_BUILTIN, 0); } } void setup() { // 初始化串口 Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); // 初始化板载LED引脚为输出 // 初始化 QWIIC 接口的 I2C 总线 Wire1.begin(); // 检查WIFI模块 if (WiFi.status() == WL_NO_MODULE) { Serial.println("WiFi模块通信失败!"); while (true); } // 连接到WIFI Serial.print("尝试连接到 WIFI SSID: "); Serial.println(ssid); while (WiFi.begin(ssid, pass) != WL_CONNECTED) { Serial.print("."); delay(5 * 1000); } Serial.println("WIFI 连接成功!"); // 连接MQTT mqttClient.setUsernamePassword(mqtt_user, mqtt_pass); Serial.print("尝试连接到MQTT服务器: "); Serial.println(broker); if (!mqttClient.connect(broker, port)) { Serial.print("MQTT 连接失败! "); Serial.println(mqttClient.connectError()); while (1); } Serial.println("MQTT连接成功!"); // 订阅主题 mqttClient.onMessage(onMqttMessage); mqttClient.subscribe(switch_subscribe_topic); mqttClient.subscribe(brightness_subscribe_topic); // AHT20 初始化 if (!aht.begin(&Wire1)) { Serial.println("未找到 AHTX0 传感器!"); while (1) delay(10); } Serial.println("AHTX0 传感器初始化成功!"); if (!ltr.begin(&Wire1)) { Serial.println("未找到 LTR329 传感器!"); while (1) delay(10); } Serial.println("LTR329 传感器初始化成功!"); } void loop() { if(isNeedReportState) { // 汇报当前板载LED状态到HA char msg[100] = {0}; sprintf(msg, "{\"builtinLed\": \"%s\", \"brightness\": %d,\"temperature\":%.2f,\"humidity\":%.2f, \"ambientLight\":%.2f}", ledState ? STATE_ON : STATE_OFF, brightness, temperature, humidity, ambientLight); mqttClient.beginMessage(publish_topic); mqttClient.print(msg); mqttClient.endMessage(); isNeedReportState = false; // 上报板载LED状态到HA后重置汇报状态 } if(0 == (tick % (SENSOR_REPORT_INTERVAL * 1000 / LOOP_DELAY))) { // 根据传感器数据更新间隔 SENSOR_REPORT_INTERVAL 宏定义进行上报传感器数据 // 读取 AHT20 传感器数据 sensors_event_t humi, temp; aht.getEvent(&humi, &temp); temperature = temp.temperature; humidity = humi.relative_humidity; // 读取 LTR329 传感器数据 if (ltr.newDataAvailable()) { ltr.readBothChannels(visible_plus_ir, infrared); // 环境光照度计算 float ratio = infrared / (visible_plus_ir + infrared); if(ratio < 0.45) { ambientLight = (1.7743 * visible_plus_ir + 1.1059 * infrared) / (1 << gain) / integrationTimeVal; } else if(ratio < 0.64 && ratio >= 0.45) { ambientLight = (4.2785 * visible_plus_ir - 1.9548 * infrared) / (1 << gain) / integrationTimeVal; } else if(ratio < 0.85 && ratio >= 0.64) { ambientLight = (0.5926 * visible_plus_ir + 0.1185 * infrared) / (1 << gain) / integrationTimeVal; } else { ambientLight = 0; } } // 更新状态,更新传感器数据到HA isNeedReportState = true; } // 更新板载LED状态 setLightState(); mqttClient.poll(); // 定期检查新MQTT消息 delay(LOOP_DELAY); // 主循环延时时长 tick ++; } // MQTT 订阅消息回调,接收HA控制板载LED的指令(ON:开,OFF:关) void onMqttMessage(int messageSize) { char topic[100] = {0}; String message; // 打印消息主题 Serial.print("Received message from topic: "); strcpy(topic, mqttClient.messageTopic().c_str()); Serial.println(topic); // 读取消息内容 for (int i = 0; i < messageSize; i++) { message += (char)mqttClient.read(); } // 打印消息的内容 Serial.print("Received message: "); Serial.println(message); if(0 == strcmp(switch_subscribe_topic, topic)) { // 开关 if(0 == strcmp(STATE_ON, message.c_str())) { if(false == ledState) { // 需要更新板载LED状态到HA isNeedReportState = true; } ledState = true; } else { if(true == ledState) { // 需要更新板载LED状态到HA isNeedReportState = true; } ledState = false; } } else if(0 == strcmp(brightness_subscribe_topic, topic)) { // 亮度 uint8_t b = message.toInt(); if (brightness < 0 || brightness > 255) { // 范围校验 // do nothing... return; } else { brightness = b; setLightState(); isNeedReportState = true; } } }   5. 智能家居自动化 本项目中结合 Adafruit LTR-329 Light Sensor 环境光传感器模块作为书房环境光照监测。当光照低于阀值时,自动打开台灯(本例中没有对接真实的智能台灯,使用板载LED模拟,可以控制开关与亮度)。光照充足时自动关闭台灯。实现自动调整书房光线,无需手动干预。在节能方面,避免不必要的照明,节省电力。同时保持书房光线在最佳水平,保护眼睛,提高阅读和工作效率。   6. 效果展示   7. 演示视频     8. 结语 Arduino Uno R4 WiFi智能书房是一个基于Arduino Uno R4开发板和ESP8266 Wi-Fi模块的智能家居项目。这个项目的主要功能是通过Wi-Fi连接,实现对书房内环境以及各种设备的远程控制和管理。 通过这个项目,我们不仅提高了对物联网技术的理解和运用能力,也学会了如何将理论转化为解决实际问题的应用。希望这些经验和心得能为未来的项目提供参考和启发。   9. 参考资料 https://docs.arduino.cc/tutorials/uno-r4-wifi/r4-wifi-getting-started/ https://edm.eeworld.com.cn/Arduino_UNO_R4_WiFi-schematics.pdf https://mm.digikey.com/Volume0/opasdata/d220001/medias/docus/5407/ABX00087_Web.pdf https://www.eeworld.com.cn/huodong/digikey_follow_me_2024_02/image/Arduino_UNO_R4_WiFi_pinout.png   10. 传送门 【Follow me第二季第2期】智能家居之智能书房 - 入门任务 【Follow me第二季第2期】智能家居之智能书房 - 基础任务 【Follow me第二季第2期】智能家居之智能书房 - 进阶任务   11. 项目源码  

  • 2024-09-08
  • 发表了主题帖: 【Follow me第二季第2期】智能家居之智能书房 - 进阶任务

    本帖最后由 鲜de芒果 于 2024-9-8 15:13 编辑 1. 任务要求 通过Wi-Fi,利用MQTT协议接入到开源的智能家居平台HA(HomeAssistant)   2. HomeAssistant集成 MQTT 设备的发现将使人们能够在 HomeAssistant 方面只需要很少的配置工作就可以使用 MQTT 设备。配置是在设备本身和设备使用的主题上完成的。 MQTT 发现默认启用,但可以禁用。发现主题的前缀(默认:homeassistant)可以更改。配置详情请参阅 MQTT 选项部分 2.1 配置主题 向 MQTT 发送配置主题后, HomeAssistant 会自动发现当前传感器。 当前任务中使用了一个板载LED灯,作为 HomeAssistant 中的 Light 组件。该组件在 HomeAssistant 的 仪表盘 中可以远程控制板载LED的开和关。使用 MQTT 客户端向配置主题发送消息即可,配置主题如下: // 板载LED灯 主题消息 { "name":"led-builtin-Light", "device_class": "light", "command_topic":"homeassistant/light/FollowMe2-2-LED_BUILTIN/switch", "state_topic":"homeassistant/sensor/FollowMe2-2/state", "state_value_template": "{{ value_json.builtinLed }}", "unique_id":"FollowMe2-2-LED_BUILTIN-light", "device":{ "identifiers":[ "Arduino UNO R4 WiFi" ], "name":"UNO R4 WiFi", "manufacturer": "Arduino", "model": "UNO R4 WiFi", "hw_version": "1.0" } }   3. 代码实现 /** * FollowMe 2-2 任务3: * 1. 通过Wi-Fi,利用MQTT协议接入到开源的智能家居平台HA(HomeAssistant) */ #include <WiFiS3.h> #include <ArduinoMqttClient.h> #include <ArduinoJson.h> #include "arduino_secrets.h" #define STATE_ON "ON" // HA LED开状态 #define STATE_OFF "OFF" // HA LED关状态 char ssid[] = SECRET_SSID; // WIFI SSID char pass[] = SECRET_PASS; // WIFI PASSWD int status = WL_IDLE_STATUS; // WIFI 状态 // MQTT WiFiClient wifiClient; MqttClient mqttClient(wifiClient); const char broker[] = "192.168.2.120"; // HomeAssistant MQTT服务器地址 int port = 1883; // HomeAssistant MQTT服务器端口 char mqtt_user[] = MQTT_USER; // MQTT 用户名 char mqtt_pass[] = MQTT_PASS; // MQTT 密码 // 订阅主题(接收 HomeAssistant 控制板载LED开关指令) const char subscribe_topic[] = "homeassistant/light/FollowMe2-2-LED_BUILTIN/switch"; bool isNeedReportLedState = true; // 是否需要更新板载LED状态到HA bool ledState = false; // 板载LED状态 // 发布主题(上报板载LED状态至 HomeAssistant的状态主题) const char publish_topic[] = "homeassistant/sensor/FollowMe2-2/state"; void setup() { // 初始化串口 Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); // 初始化板载LED引脚为输出 // 检查WIFI模块 if (WiFi.status() == WL_NO_MODULE) { Serial.println("WiFi模块通信失败!"); while (true); } // 连接到WIFI Serial.print("尝试连接到 WIFI SSID: "); Serial.println(ssid); while (WiFi.begin(ssid, pass) != WL_CONNECTED) { Serial.print("."); delay(5 * 1000); } Serial.println("WIFI 连接成功!"); // 连接MQTT mqttClient.setUsernamePassword(mqtt_user, mqtt_pass); Serial.print("尝试连接到MQTT服务器: "); Serial.println(broker); if (!mqttClient.connect(broker, port)) { Serial.print("MQTT 连接失败! "); Serial.println(mqttClient.connectError()); while (1); } Serial.println("MQTT连接成功!"); // 订阅主题 mqttClient.onMessage(onMqttMessage); mqttClient.subscribe(subscribe_topic); } void loop() { if(isNeedReportLedState) { // 汇报当前板载LED状态到HA char msg[30] = {0}; sprintf(msg, "{\"builtinLed\": \"%s\"}", ledState ? STATE_ON : STATE_OFF); mqttClient.beginMessage(publish_topic); mqttClient.print(msg); mqttClient.endMessage(); isNeedReportLedState = false; // 上报板载LED状态到HA后重置汇报状态 } // 更新板载LED状态 if(ledState) { // 亮 digitalWrite(LED_BUILTIN, HIGH); } else { // 灭 digitalWrite(LED_BUILTIN, LOW); } mqttClient.poll(); // 定期检查新MQTT消息 } // MQTT 订阅消息回调,接收HA控制板载LED的指令(ON:开,OFF:关) void onMqttMessage(int messageSize) { // 读取消息内容 String message; for (int i = 0; i < messageSize; i++) { message += (char)mqttClient.read(); } // 打印消息的内容 Serial.print("Received message: "); Serial.println(message); if(0 == strcmp(STATE_ON, message.c_str())) { if(false == ledState) { // 需要更新板载LED状态到HA isNeedReportLedState = true; } ledState = true; } else { if(true == ledState) { // 需要更新板载LED状态到HA isNeedReportLedState = true; } ledState = false; } }   4. 效果展示 智能家居平台HA(HomeAssistant)中操作关灯后的效果   智能家居平台HA(HomeAssistant)中操作开灯后的效果   串口打印日志   效果动图  

  • 发表了主题帖: 【Follow me第二季第2期】智能家居之智能书房 - 基础任务

    1. 任务要求 驱动12x8点阵LED;用DAC生成正弦波;用OPAMP放大DAC信号;用ADC采集并且打印数据到串口等其他接口可上传到上位机显示曲线   2. 硬件准备 Arduino R4 WIFI板子上自带一个内置的放大器,可以将 A1 引脚作为正向输入、A2 引脚作为反向输入、A3 引脚作为输出。电压放大器,顾名思义是放大电压的。一个简单的 2 倍放大器可以使用两个 10k 电阻来构建,计算公式如上图所示。在反向输入 和 GND 之间连接一个电阻。然后使用第二个电阻连接输出和 反向输入 在一起。电路图如下所示。在 正向输入 的任何信号现在将以输出引脚的两倍幅度出现。注意,输入信号 和Arduino板应该共享同一个 GND。这里我将 A0 引脚作为 DAC 输出连接到 A1 引脚,作为放大器的正向输入。   3. 代码实现 /** * FollowMe 2-2 任务2: * 1. 驱动12x8点阵LED; * 2. 用DAC生成正弦波; * 3. 用OPAMP放大DAC信号; * 4. 用ADC采集并且打印数据到串口等其他接口可上传到上位机显示曲线 */ #include "Arduino_LED_Matrix.h" #include "analogWave.h" #include "OPAMP.h" // LED矩阵 ArduinoLEDMatrix matrix; // 使用DAC(A0)引脚模拟波形 analogWave wave(DAC); uint32_t tick = 0; bool isWink = false; int freq = 10;  // 正弦波频率 void setup() {   // 初始化串口   Serial.begin(115200);   // LED矩阵初始化   matrix.begin();   delay(1000);   // 初始化ADC   // 根据指定正弦波频率产生正弦波   wave.sine(freq);   // 限制 DAC 正弦波幅值,避免经过放大后超出ADC的随范围而导致器件损坏   wave.amplitude(0.2f);   // 开启放大器   OPAMP.begin(OPAMP_SPEED_HIGHSPEED); } // LED 矩阵帧映射 uint8_t frame[8][12] = {   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; // 绘制微笑表情 左眼 void leftEye(){   frame[1][3] = 1;   frame[1][4] = 1;   frame[2][3] = 1;   frame[2][4] = 1; } // 绘制微笑表情 右眼 void rightEye(){   frame[1][8] = 1;   frame[1][9] = 1;   frame[2][8] = 1;   frame[2][9] = 1; } // 绘制微笑表情 嘴吧 void mouth(){   frame[5][3] = 1;   frame[5][9] = 1;   frame[6][3] = 1;   frame[6][4] = 1;   frame[6][5] = 1;   frame[6][6] = 1;   frame[6][7] = 1;   frame[6][8] = 1;   frame[6][9] = 1; } // 绘制微笑表情 眨右眼 void wink(){   frame[1][3] = 0;   frame[1][4] = 0;   frame[2][3] = 1;   frame[2][4] = 1; } // 绘制微笑表情 眨双眼 void winkAll(){   frame[1][3] = 0;   frame[1][4] = 0;   frame[1][8] = 0;   frame[1][9] = 0; } void loop(){   if(0 == (tick % 1000)) {     if(isWink) {       // wink();       winkAll(); // 眨双眼       // 使用LED矩阵渲染 眨双眼微笑表情       matrix.renderBitmap(frame, 8, 12);       isWink = false;     } else {       // 生成微笑表情       leftEye();       rightEye();       mouth();       // 使用LED矩阵渲染 微笑表情       matrix.renderBitmap(frame, 8, 12);       isWink = true;     }   }   Serial.println(analogRead(A4));   delay(1); // 延时1毫秒   tick ++; }   4. 效果展示 下图为使用面包板搭建的 2 倍放大电路,从左侧的示波器可看出 蓝色曲线 为 DAC 输出信号,幅值上限大概 1V 左右,黄色曲线为放大 2 倍后的信号,可以看到频率是一样的,幅值为 DAC 输出信号的 2 倍。 下图为使用 ADC 采样的 DAC 输出信号放大后的结果,使用串口发送数据到电脑端的上位机 Vofa+ 绘制的图形。可以看到最大值为 413 , ADC 使用 10bit 精度进行采样,得出放大后的信号最大约为:2V  

  • 发表了主题帖: 【Follow me第二季第2期】智能家居之智能书房 - 入门任务

    本帖最后由 鲜de芒果 于 2024-9-8 15:11 编辑 1. 任务要求 搭建环境并开启第一步Blink / 串口打印Hello EEWorld!   2 . Arduino开发环境搭建 下载 Arduino IDE 解压,将下载的 Arduino IDE 解压到磁盘中。   打开解压后的文件夹,双击 运行 Arduino IDE.exe  首次打开的 Arduino IDE 如下图所示:   3. 代码实现 /** * FollowMe 2-2 任务1: * 1. 搭建环境并开启第一步Blink * 2. 串口打印 Hello EEWorld! */ #define MIN_VAL 0 // PWM分辨率最小值 #define MAX_VAL 255 // PWM分辨率最大值 uint32_t tick = 0; // loop 循环次数 uint32_t fadeValue = 0; // 呼吸灯PWM占空比 uint8_t fadeStep = 5; // 呼吸灯步长 bool isFadeIn = true; // 当前呼吸灯状态,true:表示由暗转亮,false:表示由亮转暗 void setup() { // 初始化串口 Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); delay(1000); // 串口打印 Serial.println("Hello EEWorld!"); } void loop(){ if(0 == (tick % 40)) { // 每隔1秒串口打印一次loop函数当前循环次数。loop中每次执行延时25毫秒,因此,loop每执行40次正好是1秒钟。 Serial.print("loop count: "); // 打印loop函数当前循环次数 Serial.println(tick); } // PWM输出驱动板载LED analogWrite(LED_BUILTIN, fadeValue); // 呼吸灯由暗转亮 if(isFadeIn) { fadeValue += fadeStep; if(MAX_VAL <= fadeValue) { // 当呼吸灯PWM占空比大于等于 PWM 分辨率最大值时,翻转呼吸灯状态。 fadeValue = MAX_VAL; isFadeIn = !isFadeIn; } } else { // 呼吸灯由亮转暗 fadeValue -= fadeStep; if(MIN_VAL >= fadeValue) { // 当呼吸灯PWM占空比小于等于 PWM 分辨率最小值时,翻转呼吸灯状态。 fadeValue = MIN_VAL; isFadeIn = !isFadeIn; } } delay(25); // 每30毫秒更新一次PWM占空比 tick ++; }   4. 效果展示  

  • 发表了主题帖: 【2024 DigiKey 创意大赛】红外温度检测及火灾报警器 - 搞定红外热成像图像生成

    随着科技的不断进步,安全监控领域也迎来了创新技术的融合。其中,红外温度检测技术和火灾报警器的红外矩阵成像技术是两个重要的发展方向。这些技术的应用不仅提高了火灾预防和响应的效率,还增强了我们对潜在危险区域的监控能力。今天,我们将深入探讨这两种技术的原理、优势以及它们如何共同作用于提升火灾安全系统的整体性能。   一、红外温度检测技术概述 红外温度检测技术利用物体表面发射的红外辐射来测量其温度。该技术基于所有物体根据其温度会发出红外辐射这一物理特性。通过专业的红外传感器,我们可以无需接触即可快速准确地获取目标的温度信息。在火灾防控中,这种非接触式的温度监测对于早期发现过热现象至关重要,从而可以及时采取措施防止火情发生。   二、火灾报警器红外矩阵成像技术 红外矩阵成像技术则是将多个红外传感器组合成一个阵列,用于构建热像图或热分布图。这种技术能够提供更广阔的视角和更高的分辨率,使得监控区域的温度分布一目了然。在火灾报警系统中,红外矩阵成像可以帮助操作人员快速定位热点区域,并实时监控火势发展,为紧急疏散和火灾扑救提供了宝贵的时间。   三、技术结合的优势 当红外温度检测与红外矩阵成像技术相结合时,我们得到的是一个高度精确且响应迅速的安全监控系统。这样的系统不仅可以在火灾发生初期就进行预警,还能够持续追踪火源的位置变化,为消防人员的救援行动提供关键信息。此外,这种技术的组合还可以减少误报的可能性,因为通过成像技术可以直观地确认温度异常是否真的由火灾引起。   本次分享红外热成像生成的bmp图片两张,分别位置红外传感器前方大概15厘米处拍摄所得,依稀能辨别出手势为五指分开与双指分开。   双指分开的旁边,可以看到有一个热源,是远处的灯板。   结语: 红外温度检测与火灾报警器红外矩阵成像技术的结合代表了现代火灾预防和控制领域的前沿方向。通过不断的技术创新和应用实践,我们有望建立一个更加智能、高效的火灾安全网络,保护人们的生命财产安全不受火灾威胁。随着这些技术的不断完善和普及,未来的火灾防控工作将变得更加科学和精准。

  • 2024-08-12
  • 发表了主题帖: 【2024 DigiKey 创意大赛】红外温度检测及火灾报警器 - 物料开箱

    本帖最后由 鲜de芒果 于 2024-8-12 19:36 编辑 大家好!今天非常兴奋地分享我最近的一个小收获——一个装有《2024 DigiKey 创意大赛》器件的包裹收到啦!     这次的包裹是一个大号的纸盒包装,有之前 3 ~ 4 个包装盒那么大。拆开包装,发货清单映入眼帘。这次活动选择了红外温度传感器以及ESP32开发板,等几个核心器件。     下面简单介绍一下各个器件以用途吧 ESP32-S3-DEVKITC-1-N8R8 是一款入门级开发板,搭载 Wi-Fi + Bluetooth® LE 模组 ESP32-S3-WROOM-1、ESP32-S3-WROOM-1U 或 ESP32-S3-WROOM-2。在本应用中原本打算作为主控使用,但由于体积过大,因此作为远程协控制器使用。   MLX90640ESF-BAB-000-TU 是一款远红外热像仪传感器,MLX90640远红外热传感器阵列可精确检测特定区域和温度范围内的目标物体,尺寸小巧,可方便集成到各种工业或智能控制应用中。在本应用中作为红外温度测量传感器使用。   ESP32-S3-WROOM1U-N16R8 是一款通用型 Wi-Fi + 低功耗蓝牙 MCU 模组,搭载 ESP32-S3 系列芯片。除具有丰富的外设接口外,模组还拥有强大的神经网络运算能力和信号处理能力,适用于 AIoT 领域的多种应用场景,例如唤醒词检测和语音命令识别、人脸检测和识别、智能家居、智能家电、智能控制面板、智能扬声器等。在本应用中作为红外传感器主控使用。    SER0056 SER0056 是 DFRobot 推出的一款带离合功能的 300 转向舵机,在本应用中作为转向机构使用,使得红外监控的范围变大。   由于打样的PCB和其它元器件还未到齐,暂时不拆开包装展示了。最后,再来一张器件全家福吧!  

  • 2024-02-23
  • 回复了主题帖: 【得捷Follow me第4期】综合实践之智能家居控制器

    秦天qintian0303 发表于 2024-2-23 08:36 这个手机APP是一个在线网页?   这是手机访问的 HA 视图界面

  • 回复了主题帖: 【得捷Follow me第4期】综合实践之智能家居控制器

    pomin 发表于 2024-2-23 09:11 看起来是 HomeAssistant的界面 没错,正是 HA 的界面

  • 2024-02-22
  • 加入了学习《 【得捷电子Follow me第4期】》,观看 【得捷Follow me第4期】综合实践之智能家居控制器

  • 发表了主题帖: 【得捷Follow me第4期】综合实践之智能家居控制器

    本帖最后由 鲜de芒果 于 2024-2-22 16:27 编辑 1 任务说明 这次我准备使用 W5500-EVB-Pico 作为一个控制器, 通过 MQTT 协议控制智能家居中的其它设备。   2 硬件准备 本综合实践使用到的器件有: ESP32-C3-Mini-1U模块及开发板 + DHT11温湿度传感器模块 + ENS160空气质量传感器模块 组成的卧室环境与空气质量数据采集。 W5500-EVB-Pico + 摇杆电位器 组成的控制中心,用于控制窗帘的打开与关闭以及换气扇的打开与关闭。 ESP32-S3模块与开发板 + 减速马达 + 继电器 组成的智能家居控制设备。用于控制窗帘的打开与关闭以及换气扇的打开与关闭。 该项目使用的三个模块之间相互独立,通过对接到智能家居总控平台实现联动控制。   3 智能家居平台对接 与智能家居平台对接使用MQTT协议,ESP32C3 连接到WIFI后,通过 MQTT 将传感器数据上报至智能家居平台。   想要实现任务介绍中的功能,首先需要在智能家居平台建立相应的设备,并建立接收环境数据相应的 MQTT 主题和与之相对应的环境数据上报的JSON数据格式即可实现对接。这里我定义的数据结构为: { "temperature": "16.75", // 温度 "humidity": "69.82", // 湿度 "aqi": 2, // 空气质量等级(Air Quality Index) "tvoc": 343, // 有机挥发物浓度(Total Volatile Organic Compounds) "eCO2": 842, // 二氧化碳浓度 "wifi_rssi": "-41.00" // WIFI信号强度 }   4 功能代码 4.1 环境数据采集 环境数据采集的流程比较简单,只需要初始化好I2C,然后通过I2C读取空气质量数据。使用MQTT将预定格式的数据上报至智能家居平台即可。如需要根据空气质量情况自动控制其它智能家居设备联动的话,只需要在智能家居平台上进行配置相应的执行逻辑即可。例如:室内空气中二氧化碳浓度过高,则自动打开换气扇或新风机。直到室内二氧化碳浓度降低至预定值再关闭。 功能实现采用 MicroPython 开发,固件使用 ESP32_GENERIC_C3 通用固件,实现代码如下: import dht import time import ujson import asyncio import machine import network import ubinascii from button import Button from ENS160 import myENS160 from umqtt.simple import MQTTClient report_interval = 10 # 上报传感器数据间隔时间(单位:秒) # 网络配置 wifi_ssid = "@PHICOMM_34" wifi_password = "12345678" station = network.WLAN(network.STA_IF) station.active(True) # 连接至wifi def connect_wifi(): # 不断尝试连接到配置的WIFI while not station.isconnected(): print("Connecting...") station.connect(wifi_ssid, wifi_password) # 连接至配置的WIFI AP time.sleep(10) # 连接成功,输出IP地址 print("WIFI连接成功,IP地址:", station.ifconfig()[0]) dht11_sensor=dht.DHT11(machine.Pin(9)) ens160_sersor=myENS160() # 获取传感器数据,用于将传感器数据上报至智能家居平台 def get_sensor_data(): temperature = 0 # 温度数据 humidity = 0 # 湿度数据 aqi = 0 # 光照强度 tvoc = 0 # 光照强度 eCO2 = 0 # 光照采样 wifi_rssi = 0 # 信号强度 # 获取环境温湿度数据 dht11_sensor.measure() # 测量 temperature=dht11_sensor.temperature() # 读取温度 humidity=dht11_sensor.humidity() # 读取湿度 # 获取空气质量数据 tvoc=ens160_sersor.getTVOC() aqi=ens160_sersor.getAQI() eCO2=ens160_sersor.getECO2() # 获取WIFI信号质量 wifi_rssi = station.status('rssi') json_data = { 'temperature': f'{temperature:.2f}', # 温度数据 'humidity': f'{humidity:.2f}', # 湿度数据 'aqi': aqi, # 光照强度 'tvoc': tvoc, # 光照采样 'eCO2': eCO2, # 光照采样 'wifi_rssi': f'{wifi_rssi:.2f}' # 信号强度 } # print(ujson.dumps(json_data)) return ujson.dumps(json_data) ''' MQTT 连接 ''' client = None CLIENT_ID = ubinascii.hexlify(machine.unique_id()) MQTT_SERVER = '192.168.2.120' async def mqtt_client(): # 连接wifi connect_wifi() print("connected wifi") global client client = MQTTClient(client_id=CLIENT_ID, server=MQTT_SERVER,user="esp32c3",password="123456") client.connect() print("mqtt client connected") while True: # client.wait_msg() await asyncio.sleep_ms(100) # 上报传感器数据至智能家居平台 async def report_sensor_data(): print("report_sensor_data()") while True: await asyncio.sleep(report_interval) # 间隔段时间才开始上报 # 上报传感器数据 data = get_sensor_data() print("publish: ", data) client.publish(b"sensor/esp32c3/bedroom/state", data) async def main(): tasks = [] # MQTT任务 mqtt_task = asyncio.create_task( mqtt_client() ) tasks.append(mqtt_task) # 传感器数据上报任务 report_task = asyncio.create_task( report_sensor_data() ) tasks.append(report_task) # 提交任务执行 await asyncio.gather(i for i in tasks) if __name__ == "__main__": print("run in main") # 异步执行任务 asyncio.run(main())   4.2 窗帘与换气扇控制设备 窗帘与换气扇设备控制使用自己打样的控制板,基于 ESP32-S3 模块制作而成,板载有两路继电器模块,一路减速马达驱动模块等。软件实现原理基于MQTT通信,订阅两个控制设备的主题。智能家居平台中的其它设备需要控制窗帘和换气扇时,向相应的主题发送指定即可实现联动控制。 窗帘控制:窗帘的控制有三种状态,打开、关闭、停止。而控制窗帘的打开与关闭使用的是减速电机,因此设计时使用 -100 ~ 100 表示正反转的占空比,当值为0时表示窗帘的减速电机停止动作。正值表示窗帘进行关闭动作,负值表示窗帘打开动作。正负值越大,占空比越高,减速电机转动越快。基于以上逻辑,需要打开窗帘时,向 MQTT 主题【device/esp32s3/curtain/control】发送 -100 ~ 100 之间的数值即可控制窗帘动作。 换气扇控制:换气扇控制设计的比较简单,只有两种状态,便于理解,指定值为 0,1。0 表示关闭换气扇,1 表示打开换气扇。因此需要控制打开换气扇时,只需要向 MQTT 主题 【device/esp32s3/ventilator/control】发送 1 即可打开换气扇。 功能实现采用 MicroPython 开发,固件使用 ESP32_GENERIC_S3 通用固件,实现代码如下: import dht import time import ujson import asyncio import machine import network import ubinascii from umqtt.simple import MQTTClient report_interval = 10 # 上报传感器数据间隔时间(单位:秒) # 网络配置 wifi_ssid = "@PHICOMM_34" wifi_password = "12345678" ventilator_pin = machine.Pin(46, machine.Pin.OUT, value=0) # 换气扇控制 curtain_forward_pwm = machine.PWM(machine.Pin(21)) # 窗帘正向控制(打开) curtain_forward_pwm.freq(10000) # 设定 PWM 频率为 10KHz curtain_reverse_pwm = machine.PWM(machine.Pin(47)) # 窗帘逆向控制(关闭) curtain_reverse_pwm.freq(10000) # 设定 PWM 频率为 10KHz station = network.WLAN(network.STA_IF) station.active(True) # 连接至wifi def connect_wifi(): # 不断尝试连接到配置的WIFI while not station.isconnected(): print("Connecting...") station.connect(wifi_ssid, wifi_password) # 连接至配置的WIFI AP time.sleep(10) # 连接成功,输出IP地址 print("WIFI连接成功,IP地址:", station.ifconfig()[0]) ''' MQTT 连接 ''' # MQTT订阅消息回调 def message_callback(topic, msg): print(topic, msg) if topic == MQTT_TOPIC_CURTAIN_CONTROL: curtain_state = int(msg.decode()) print("窗帘控制: %s" %(curtain_state)) if -100 <= curtain_state <= 100: # 窗帘控制使用百分比数值,需要转换为占空比数值,相差10.23倍。因此简单相乘即可 curtain_state *= 10 else: # 非百分比范围,重置值 curtain_state = 0 # 窗帘有三种状态:打开,静止,关闭。其中打开和关闭状态可以通过PWM进行控制。 if 0 == curtain_state: # 窗帘停止移动 print("停止 %d" %curtain_state) curtain_forward_pwm.duty_u16(curtain_state) curtain_reverse_pwm.duty_u16(curtain_state) elif 0 < curtain_state: # 窗帘正向移动(关闭) print("关闭 %d" %(abs(curtain_state))) curtain_forward_pwm.duty_u16(0) curtain_reverse_pwm.duty_u16(abs(curtain_state)) elif 0 > curtain_state: # 窗帘正向移动(关闭) print("打开 %d" %(abs(curtain_state))) curtain_forward_pwm.duty_u16(abs(curtain_state)) curtain_reverse_pwm.duty_u16(0) else: print("未知") elif topic == MQTT_TOPIC_VENTILATOR_CONTROL: ventilator_state = int(msg.decode()) print("换气扇控制: %s" %(ventilator_state)) # 换气扇只有两个状态:开和关 if 0 == ventilator_state: # 消息为0即为关闭状态 print("关闭换气扇") ventilator_pin.off() else: # 非0即为打开状态 print("打开换气扇") ventilator_pin.on() else: print("未知指令: %s ---> %s" %(topic.decode(), msg.decode())) client = None CLIENT_ID = ubinascii.hexlify(machine.unique_id()) MQTT_SERVER = '192.168.2.120' MQTT_TOPIC_CURTAIN_CONTROL = b"device/esp32s3/curtain/control" # 窗帘控制消息主题 MQTT_TOPIC_VENTILATOR_CONTROL = b"device/esp32s3/ventilator/control" # 换气扇控制消息主题 async def mqtt_client(): # 连接wifi connect_wifi() print("connected wifi") global client client = MQTTClient(client_id=CLIENT_ID, server=MQTT_SERVER,user="esp32c3",password="123456") client.connect() print("mqtt client connected") client.set_callback(message_callback) # 注册订阅消息回调函数 client.subscribe(MQTT_TOPIC_CURTAIN_CONTROL) # 订阅主题(窗帘控制) client.subscribe(MQTT_TOPIC_VENTILATOR_CONTROL) # 订阅主题(换气扇控制) while True: # client.wait_msg() await asyncio.sleep_ms(10) client.check_msg() # 上报传感器数据至智能家居平台 async def report_sensor_data(): print("report_sensor_data()") while True: await asyncio.sleep(report_interval) # 间隔段时间才开始上报 # 获取WIFI信号质量 wifi_rssi = station.status('rssi') # 上报传感器数据 data = { 'wifi_rssi': f'{wifi_rssi:.2f}' # 信号强度 } print("publish: ", data) client.publish(b"sensor/esp32s3/bedroom/state", ujson.dumps(data)) async def main(): tasks = [] # MQTT任务 mqtt_task = asyncio.create_task( mqtt_client() ) tasks.append(mqtt_task) # 传感器数据上报任务 report_task = asyncio.create_task( report_sensor_data() ) tasks.append(report_task) # 提交任务执行 await asyncio.gather(i for i in tasks) if __name__ == "__main__": print("run in main") # 异步执行任务 asyncio.run(main())   4.3 控制器实现 控制器使用 W5500-EVB-Pico + 摇杆电位器实现,连接网络成功后,读取摇杆电位器与摇杆按键的状态,向 MQTT 相应的主题发送指令值,窗帘与换气扇设备接收到指令后即可控制窗帘或换气扇执行相应动作。 由于窗帘只有一个方向,因此只需要用到摇杆的一个轴即可。这里使用摇杆的 X 轴来控制窗帘左右移动。摇杆按键用于控制换气扇的开关,按下一次打开换气扇,再按下一次关闭换气扇,依此类推。 功能实现采用 CircuitPython 开发,固件使用 wiznet_w5500_evb_pico 官方固件,版本:9.0.0-beta.2 下载地址:https://downloads.circuitpython.org/bin/wiznet_w5500_evb_pico/en_US/adafruit-circuitpython-wiznet_w5500_evb_pico-en_US-9.0.0-beta.2.uf2 实现代码如下: import os import time import board import busio import asyncio import analogio import digitalio import displayio import terminalio import adafruit_displayio_ssd1306 from adafruit_display_text import label import adafruit_minimqtt.adafruit_minimqtt as MQTT import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket import task2 ''' 摇杆引脚初始化 ''' x = analogio.AnalogIn(board.GP27) y = analogio.AnalogIn(board.GP26) led = digitalio.DigitalInOut(board.LED) led.direction = digitalio.Direction.OUTPUT button = digitalio.DigitalInOut(board.GP6) # button.switch_to_input(pull=digitalio.Pull.UP) """ 递推平均滤波法(又称滑动平均滤波法)""" FILTER_N = 12 x_buf = [] def filter_x(value): global x_buf x_buf.append(value) if FILTER_N < len(x_buf): x_buf = x_buf[1:] x_sum = sum(x_buf) return int(x_sum / FILTER_N) y_buf = [] def filter_y(value): global y_buf y_buf.append(value) if FILTER_N < len(y_buf): y_buf = y_buf[1:] y_sum = sum(y_buf) return int(y_sum / FILTER_N) ADC_VALUE_MIN = 0 ADC_VALUE_MAX = 65535 RANGE_MIN = -100 RANGE_MAX = 100 def map(value, source_min = ADC_VALUE_MIN, source_max = ADC_VALUE_MAX, dest_min = RANGE_MIN, dest_max = RANGE_MAX): return (value - source_min) * (dest_max - dest_min) / (source_max - source_min) + dest_min ### MQTT主题 ### mqtt_host = os.getenv("MQTT_HOST") mqtt_port = os.getenv("MQTT_PORT") mqtt_user = os.getenv("MQTT_USER") mqtt_pass = os.getenv("MQTT_PASS") # 窗帘控制 curtain_topic = "device/esp32s3/curtain/control" # 换气扇控制 ventilator_topic = "device/esp32s3/ventilator/control" ### MQTT事件 ### ''' MQTT 连接成功 ''' def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. print(f"Connected to MQTT broker [{mqtt_host}:{mqtt_port}]! Listening for topic changes on {curtain_topic}, {ventilator_topic}") # Subscribe to all changes on the onoff_feed. # client.subscribe(curtain_topic) ''' MQTT 连接断开 ''' def disconnected(client, userdata, rc): # This method is called when the client is disconnected print(f"Disconnected from MQTT broker [{mqtt_host}:{mqtt_port}]!") ''' 接收到订阅主题消息 ''' def message(client, topic, message): # This method is called when a topic the client is subscribed to # has a new message. print(f"New message on topic {topic}: {message}") if topic == curtain_topic: print(f"synchronizing curtain: {message}") elif topic == ventilator_topic: print(f"synchronizing ventilator: {message}") mqtt_client = None async def init(): ### MQTT 连接配置 ### socket.set_interface(task2.eth) MQTT.set_socket(socket, task2.eth) # 设置客户端参数 global mqtt_client mqtt_client = MQTT.MQTT( broker=mqtt_host, port=mqtt_port, username=mqtt_user, password=mqtt_pass ) # 订阅事件 mqtt_client.on_connect = connected mqtt_client.on_disconnect = disconnected mqtt_client.on_message = message print("MQTT init done.") mqtt_client.connect() mqtt_client.subscribe(ventilator_topic) async def monitor_control(): ventilator_state = False # 换气扇开关状态 while True: print("X value is: %d, Y value is: %d" %(map(filter_x(x.value)), map(filter_y(y.value)))) mqtt_client.publish(curtain_topic, int(map(filter_x(x.value)))) if not button.value: # 摇杆按键按下 if not ventilator_state: # 当前状态 led.value = True mqtt_client.publish(ventilator_topic, 1) else: led.value = False mqtt_client.publish(ventilator_topic, 0) ventilator_state = not ventilator_state # 重置开关状态 await asyncio.sleep_ms(10)   5 效果展示 概览   换气扇控制   窗帘控制     6 总结 RP2040 的 ADC 还是比较能满足摇杆的采样需求,本次使用 W5500-EVB-Pico 作为主控制器,缺点是不能随心所欲地移动。但作为窗帘与换气扇的控制器,将其置于 86 面板中也是一种不错的选择。   7 项目源码 8 演示视频     9 传送门 【得捷Follow me第4期】入门任务:开发环境搭建 【得捷Follow me第4期】基础任务一:完成主控板W5500初始化(静态IP配置) 【得捷Follow me第4期】基础任务二:主控板建立TCPIP或UDP服务器 【得捷Follow me第4期】进阶任务:从NTP服务器同步时间 【得捷Follow me第4期】简易FTP文件服务器

  • 发表了主题帖: 【得捷Follow me第4期】简易FTP文件服务器

    本帖最后由 鲜de芒果 于 2024-2-22 14:22 编辑 5.1 任务说明 终极任务一:得捷电子API应用,电子元器件价格、库存监控 访问 https://www.digikey.cn/zh/resources/api-solutions,以了解得捷(DigiKey)数字化解决方案及其API操作,设计一款DigiKey电子元器件价格及库存监视器,能实时同步并显示指定,电子元器件的价格、库存等信息。 终极任务二:使用外部存储器,组建简易FTP文件服务器,并能正常上传下载文件。 这里我选择实现任务二,实现一个简易FTP文件服务器。   5.2 功能实现 本任务实现使用的 MicroPython 作为开发语言,需要下载 W5500-EVB-Pico 对应的 MicroPython 固件,详见:固件下载页面 本次任务使用的 MicroPython 固件版本为当前最新版本:v1.22.1 (2024-01-05) .uf2 实现步骤: 下载最新 MicroPython 固件并烧录到 W5500-EVB-Pico 开发板,具体的烧录步骤请稳步之前的文章,这里不作展开说明。 建立 www 目录,并存放一个测试文件。(可选步骤) 简单实现 ftp 协议。   5.3 功能代码 import gc import uos import time import socket import network from time import localtime from machine import Pin, SPI from micropython import const _LED_PIN = const(25) # 绿色 LED 引脚 _SPI_SPEED = const(2_000_000) # SPI 速率 _MOSI_PIN = const(19) # SPI MOSI 引脚 _MISO_PIN = const(16) # SPI MISO 引脚 _SCK_PIN = const(18) # SPI SCK 引脚 _CS_PIN = const(17) # SPI CS 引脚 _RST_PIN = const(20) # SPI RESET 引脚 FTP_ROOT_PATH = const("/www") # FTP 根目录 month_name = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] # SPI 定义 spi=SPI(0, _SPI_SPEED, mosi=Pin(_MOSI_PIN), miso=Pin(_MISO_PIN), sck=Pin(_SCK_PIN)) nic = None """ W5500 初始化 """ def w5x00_init(): global nic # 网口初始化 nic = network.WIZNET5K(spi, Pin(_CS_PIN), Pin(_RST_PIN)) #spi,cs,reset pin nic.active(True) # 配置网络 nic.ifconfig(('192.168.1.101','255.255.255.0','192.168.1.1','8.8.8.8')) while not nic.isconnected(): time.sleep(1) print(nic.regs()) print("IP地址: %s" %nic.ifconfig()[0]) print("子网掩码: %s" %nic.ifconfig()[1]) print("网关: %s" %nic.ifconfig()[2]) print("DNS: %s" %nic.ifconfig()[3]) """ 响应文件列表请求 """ def send_list_data(path, dataclient, full): try: # whether path is a directory name for fname in uos.listdir(path): dataclient.sendall(make_description(path, fname, full)) except: # path may be a file name or pattern pattern = path.split("/")[-1] path = path[:-(len(pattern) + 1)] if path == "": path = "/" for fname in uos.listdir(path): if fncmp(fname, pattern) == True: dataclient.sendall(make_description(path, fname, full)) """ 列出目录详情 """ def make_description(path, fname, full): if full: stat = uos.stat(get_absolute_path(path,fname)) file_permissions = "drwxr-xr-x" if (stat[0] & 0o170000 == 0o040000) else "-rw-r--r--" file_size = stat[6] tm = localtime(stat[7]) if tm[0] != localtime()[0]: description = "{} 1 owner group {:>10} {} {:2} {:>5} {}\r\n".format( file_permissions, file_size, month_name[tm[1]], tm[2], tm[0], fname) else: description = "{} 1 owner group {:>10} {} {:2} {:02}:{:02} {}\r\n".format( file_permissions, file_size, month_name[tm[1]], tm[2], tm[3], tm[4], fname) else: description = fname + "\r\n" return description """ 发送文件数据 """ def send_file_data(path, dataclient): try: with open(path, "rb") as file: chunk = file.read(512) print("chunk 0: ", len(chunk)) while len(chunk) > 0: print("chunk: ", len(chunk)) dataclient.sendall(chunk) chunk = file.read(512) except Exception as err: print("error: ", err.args, err.value, err.errno) """ 保存文件上传数据 """ def save_file_data(path, dataclient, mode): with open(path, mode) as file: chunk = dataclient.read(512) while len(chunk) > 0: file.write(chunk) chunk = dataclient.read(512) """ 获取文件绝对路径 """ def get_absolute_path(cwd, payload): # Just a few special cases "..", "." and "" # If payload start's with /, set cwd to / # and consider the remainder a relative path if payload.startswith('/'): cwd = "/" for token in payload.split("/"): if token == '..': if cwd != '/': cwd = '/'.join(cwd.split('/')[:-1]) if cwd == '': cwd = '/' elif token != '.' and token != '': if cwd == '/': cwd += token else: cwd = cwd + '/' + token return cwd """ 文件名比较 """ def fncmp(fname, pattern): pi = 0 si = 0 while pi < len(pattern) and si < len(fname): if (fname[si] == pattern[pi]) or (pattern[pi] == '?'): si += 1 pi += 1 else: if pattern[pi] == '*': # recurse if (pi + 1) == len(pattern): return True while si < len(fname): if fncmp(fname[si:], pattern[pi+1:]) == True: return True else: si += 1 return False else: return False if pi == len(pattern.rstrip("*")) and si == len(fname): return True else: return False """ 启动FTP服务 """ def ftpserver(): DATA_PORT = 13333 ftpsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) datasocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ftpsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) datasocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ftpsocket.bind(socket.getaddrinfo("0.0.0.0", 21)[0][4]) datasocket.bind(socket.getaddrinfo("0.0.0.0", DATA_PORT)[0][4]) ftpsocket.listen(1) datasocket.listen(1) datasocket.settimeout(10) print("FTP服务启动成功!监听端口:21"); msg_250_OK = '250 OK\r\n' msg_550_fail = '550 Failed\r\n' try: dataclient = None fromname = None while True: cl, remote_addr = ftpsocket.accept() cl.settimeout(300) cwd = FTP_ROOT_PATH try: print("新的FTP连接来自: %s:%s" %(remote_addr[0], remote_addr[1])) cl.sendall("220 Welcome! This is the W5500_EVB_PICO!\r\n") while True: gc.collect() data = cl.readline().decode("utf-8").rstrip("\r\n") if len(data) <= 0: print("Client disappeared") break command = data.split(" ")[0].upper() payload = data[len(command):].lstrip() path = get_absolute_path(cwd, payload) print("命令={}, 参数={}, 路径={}".format(command, payload, path)) if command == "USER": cl.sendall("230 Logged in.\r\n") elif command == "SYST": cl.sendall("215 UNIX Type: L8\r\n") elif command == "NOOP": cl.sendall("200 OK\r\n") elif command == "FEAT": cl.sendall("211 no-features\r\n") elif command == "PWD": cl.sendall('257 "{}"\r\n'.format(cwd)) elif command == "CWD": try: files = uos.listdir(path) cwd = path cl.sendall(msg_250_OK) except: cl.sendall(msg_550_fail) elif command == "CDUP": cwd = get_absolute_path(cwd, "..") cl.sendall(msg_250_OK) elif command == "TYPE": # probably should switch between binary and not cl.sendall('200 Transfer mode set\r\n') elif command == "SIZE": try: size = uos.stat(path)[6] cl.sendall('213 {}\r\n'.format(size)) except: cl.sendall(msg_550_fail) elif command == "QUIT": cl.sendall('221 Bye.\r\n') break elif command == "PASV": addr = nic.ifconfig()[0] cl.sendall('227 Entering Passive Mode ({},{},{}).\r\n'.format( addr.replace('.',','), DATA_PORT>>8, DATA_PORT%256)) dataclient, data_addr = datasocket.accept() print("新的FTP数据连接来自: %s:%s" %(data_addr[0], data_addr[1])) elif command == "LIST" or command == "NLST": if not payload.startswith("-"): place = path else: place = cwd try: send_list_data(place, dataclient, command == "LIST" or payload == "-l") cl.sendall("150 Here comes the directory listing.\r\n") cl.sendall("226 Listed.\r\n") except: cl.sendall(msg_550_fail) if dataclient is not None: dataclient.close() dataclient = None elif command == "RETR": try: send_file_data(path, dataclient) cl.sendall("150 Opening data connection.\r\n") cl.sendall("226 Transfer complete.\r\n") except: cl.sendall(msg_550_fail) if dataclient is not None: dataclient.close() dataclient = None elif command == "STOR": try: cl.sendall("150 Ok to send data.\r\n") save_file_data(path, dataclient, "wb") cl.sendall("226 Transfer complete.\r\n") except: cl.sendall(msg_550_fail) if dataclient is not None: dataclient.close() dataclient = None elif command == "APPE": try: cl.sendall("150 Ok to send data.\r\n") save_file_data(path, dataclient, "a") cl.sendall("226 Transfer complete.\r\n") except: cl.sendall(msg_550_fail) if dataclient is not None: dataclient.close() dataclient = None elif command == "DELE": try: uos.remove(path) cl.sendall(msg_250_OK) except: cl.sendall(msg_550_fail) elif command == "RMD": try: uos.rmdir(path) cl.sendall(msg_250_OK) except: cl.sendall(msg_550_fail) elif command == "MKD": try: uos.mkdir(path) cl.sendall(msg_250_OK) except: cl.sendall(msg_550_fail) elif command == "RNFR": fromname = path cl.sendall("350 Rename from\r\n") elif command == "RNTO": if fromname is not None: try: uos.rename(fromname, path) cl.sendall(msg_250_OK) except: cl.sendall(msg_550_fail) else: cl.sendall(msg_550_fail) fromname = None else: cl.sendall("502 Unsupported command.\r\n") # print("Unsupported command {} with payload {}".format(command, payload)) except Exception as err: print(err) finally: cl.close() cl = None finally: datasocket.close() ftpsocket.close() if dataclient is not None: dataclient.close() if __name__ == "__main__": print("run in main") w5x00_init() # 初始化网络 ftpserver() # 运行 FTP Server   5.4 效果展示 启动服务   建立连接   上传文件     删除文件    下载文件   5.5 总结 FTP 的实现使用 MicroPython 的原因是为了简化存储的访问。CircuitPython 对存储访问的管理较为严格,实现文件存储比较复杂。     5.6 源码     5.7 视频演示  

  • 加入了学习《 【得捷电子Follow me第4期】》,观看 【得捷Follow me第4期】简易FTP文件服务器

  • 加入了学习《 【得捷电子Follow me第4期】》,观看 【得捷Follow me第4期】进阶任务:从NTP服务器同步时间

  • 发表了主题帖: 【得捷Follow me第4期】进阶任务:从NTP服务器同步时间

    本帖最后由 鲜de芒果 于 2024-2-22 13:29 编辑 4.1 任务说明 从NTP服务器(注意数据交互格式的解析)同步时间,获取时间送显示屏(串口)显示。   4.2 功能代码 import time import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket import task2 days = ( "星期天", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" ) # 查询阿里云NTP服务器IP地址 ntp_server_domain = "ntp.aliyun.com" ntp_server_ip = task2.eth.pretty_ip(task2.eth.get_host_by_name(ntp_server_domain)) print("NTP服务器【%s】IP地址: %s" %(ntp_server_domain, ntp_server_ip)) # NTP端口 ntp_server_port= 123 packet_buf = bytearray() # UTF时区, 这里取北京时间时区 +8 utc = 8 # 构建 NTP 请求头 def build_ntp_header(): packet_buf.append(0x23) for i in range(55): packet_buf.append(0x00) # 请求NTP时间 def get_ntp_time(): build_ntp_header() socket.set_interface(task2.eth) # 创建UDP Socket对象 ntp_socket = socket.socket(type=socket.SOCK_DGRAM) # 设置超时时间 ntp_socket.settimeout(10) ntp_socket.bind((None,50001)) ntp_socket.sendto(packet_buf,(ntp_server_ip, ntp_server_port)) while True: data = ntp_socket.recv(48) print("receive ntp data: ", data) if data: sec = data[40:44] int_cal = int.from_bytes(sec,"big") cal = int_cal - 2208988800 + utc * 3600 cal = time.localtime(cal) return cal # 获取当前时间 def get_time(): cal = get_ntp_time() print("%d年%d月%d日 %s %d时%d分%d秒" %(cal.tm_year, cal.tm_mon, cal.tm_mday, days[cal.tm_wday], cal.tm_hour, cal.tm_min, cal.tm_sec))   4.3 效果展示   4.4 总结 NTP 时间解析需要注意获取到的数据有可能为空,需要进行逻辑判断处理。另外就是时间的起始日期。   4.5 演示视频  

  • 加入了学习《 【得捷电子Follow me第4期】》,观看 【得捷Follow me第4期】基础任务二:主控板建立TCPIP或UDP服务器

最近访客

< 1/3 >

统计信息

已有37人来访过

  • 芯积分:161
  • 好友:--
  • 主题:21
  • 回复:7

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言