易语言开发从入门到精通:补充篇·易语言与物联网(IoT)深度实践·ESP8266本地MQTT通信·数据采集存储·Windows端可视化监控平台 🔧📡📊
1.31.1 学习目标 🎯
作为《易语言开发从入门到精通》的物联网开发补充篇·本地应用与嵌入式设备联动实战·深度技术融合 ,本章将帮你构建完整的易语言IoT开发思维体系,完成首个本地联动的物联网项目,达成以下4重可落地的明确目标:
- 构建易语言IoT开发架构模型:掌握"本地EMQX MQTT broker消息中间件→ESP8266/ESP32数据采集端→易语言Windows数据存储监控端"的三层架构;
- 掌握跨设备本地通信技术:学会使用本地EMQX MQTT broker实现易语言Windows程序与ESP8266/ESP32的稳定通信;
- 完成完整的IoT实战项目:开发一个农业大棚温湿度光照数据采集存储监控系统,支持实时数据展示、历史数据查询、异常告警、数据导出功能;
- 掌握IoT开发优化经验:学习易语言IoT开发中常见问题(如MQTT连接失败、数据乱码、网络延迟高)的排查方法和优化经验。
1.31.2 易语言IoT开发核心架构设计------三层架构的稳定联动 🧠
物联网开发的核心在于设备间的稳定通信和数据的高效处理。易语言作为中文可视化编程语言,非常适合开发Windows端的数据存储和监控平台;ESP8266/ESP32作为性价比极高的嵌入式设备,非常适合作为数据采集端;本地EMQX MQTT broker作为轻量级的消息中间件,非常适合实现设备间的本地通信。
1.31.2.1 三层架构的功能划分
✅ 第一层:本地EMQX MQTT broker(消息中间件层)
- 功能:负责接收和转发数据采集端发送的数据,以及监控端发送的控制指令;
- 特点:轻量级、高性能、稳定可靠、支持本地部署、不需要连接互联网;
- 适用场景:本地物联网项目(如家庭监控、农业大棚监控、工厂生产线监控)。
✅ 第二层:ESP8266/ESP32数据采集端(感知层)
- 功能:负责采集温湿度、光照、土壤湿度等传感器数据,并通过MQTT协议发送到本地EMQX broker;
- 特点:性价比高、体积小、功耗低、支持WiFi通信、支持Arduino开发;
- 适用场景:需要远程或本地数据采集的项目。
✅ 第三层:易语言Windows数据存储监控端(应用层)
- 功能:负责从本地EMQX broker接收数据,存储到SQLite数据库中,实时展示数据,查询历史数据,异常告警,数据导出;
- 特点:中文可视化编程、开发效率高、用户友好、支持本地存储、不需要连接互联网;
- 适用场景:本地物联网项目的监控和管理。
1.31.2.2 数据流程
① 数据采集端发送数据 :ESP8266/ESP32采集传感器数据→封装成JSON格式→通过MQTT协议发送到本地EMQX broker的"agricultural/sensor/data"主题;
② 消息中间件转发数据 :本地EMQX broker接收数据→转发到订阅了"agricultural/sensor/data"主题的监控端;
③ 监控端接收和处理数据 :易语言Windows程序接收数据→解析JSON格式→存储到SQLite数据库→实时展示数据→异常告警;
④ 监控端发送控制指令 :易语言Windows程序发送控制指令(如打开/关闭大棚通风扇)→封装成JSON格式→通过MQTT协议发送到本地EMQX broker的"agricultural/device/control"主题;
⑤ 数据采集端接收和执行控制指令:ESP8266/ESP32接收控制指令→解析JSON格式→执行控制操作。
1.31.3 农业大棚温湿度光照数据采集存储监控系统实战 📱
现在,我们来开发一个功能完整的农业大棚温湿度光照数据采集存储监控系统,这个系统将用到所有三层架构的技术,覆盖本地EMQX broker部署、ESP8266开发、易语言Windows程序开发、SQLite数据库存储、数据可视化等核心知识点。
1.31.3.1 项目需求分析 📝
✅ 功能需求:
- 基础功能:实时采集温湿度、光照数据,实时展示数据,查询历史数据,异常告警,数据导出;
- 数据管理功能:数据存储到SQLite数据库,数据导入(从SQLite数据库导入),数据导出(导出到Excel文件);
- 辅助功能 :本地EMQX broker连接状态监测,数据采集端在线状态监测,异常告警阈值设置,定时保存数据(每隔30秒);
✅ 非功能需求: - 界面设计:功能分区明确(数据展示区、历史数据查询区、异常告警区、系统状态区)、简洁美观(系统默认颜色为主,突出"异常告警"区域);
- 数据安全:程序启动时自动读取数据,程序关闭前自动保存数据,定时保存数据;
- 数据验证:验证温湿度、光照数据的范围是否正常(如温度范围为-40℃到80℃,湿度范围为0%到100%,光照范围为0lux到100000lux);
- 响应时间 :程序响应时间不超过1秒,数据采集间隔不超过30秒;
✅ 目标用户:中小微农业企业员工、农业合作社社员、个人养殖户;
1.31.3.2 第一层:本地EMQX MQTT broker的安装和配置 🛠️
① 下载EMQX :访问EMQX官方网站(https://www.emqx.io/),下载Windows版本的EMQX;
② 安装EMQX :双击下载的安装文件,按照安装向导进行安装;
③ 启动EMQX:
- 方法1:使用命令行启动,打开CMD命令行工具,输入"cd C:\Program Files\emqx\bin"(安装路径可能不同),然后输入"emqx start";
- 方法2:使用EMQX Dashboard启动,访问"http://localhost:18083",输入默认用户名"admin"和密码"public",然后点击"启动EMQX";
④ 配置EMQX: - 配置MQTT协议的端口号:默认端口号为1883(TCP协议)、8883(SSL协议),如果需要修改,可以在EMQX Dashboard的"配置"→"MQTT"→"基本配置"中修改;
- 配置用户和权限:默认用户名和密码为"admin"和"public",如果需要添加新用户,可以在EMQX Dashboard的"访问控制"→"用户管理"中添加;
- 配置主题权限:默认主题权限为"允许所有用户订阅和发布所有主题",如果需要限制主题权限,可以在EMQX Dashboard的"访问控制"→"ACL管理"中添加。
✅ 完成以上步骤后,本地EMQX MQTT broker就安装和配置好了。
1.31.3.3 第二层:ESP8266数据采集端的开发(Arduino代码) 💻
① 硬件准备:
- ESP8266开发板:NodeMCU ESP8266 12E/12F;
- 传感器:DHT11温湿度传感器、BH1750光照传感器;
- 连接线:杜邦线(公对公、公对母);
- 电源 :USB电源适配器(5V/2A);
② 硬件连接: - DHT11温湿度传感器:VCC连接到ESP8266的3.3V或5V引脚,GND连接到ESP8266的GND引脚,DATA连接到ESP8266的D2引脚;
- BH1750光照传感器 :VCC连接到ESP8266的3.3V或5V引脚,GND连接到ESP8266的GND引脚,SCL连接到ESP8266的D5引脚,SDA连接到ESP8266的D6引脚;
③ 软件准备: - Arduino IDE:下载并安装Arduino IDE 1.8.19或更高版本;
- ESP8266开发板支持:在Arduino IDE中添加ESP8266开发板支持;
- 传感器库:安装DHT传感器库(Adafruit DHT sensor library)和BH1750传感器库(BH1750);
- MQTT库 :安装PubSubClient库;
④ 代码编写:
cpp
// 引入所需库
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <Wire.h>
#include <BH1750.h>
#include <ArduinoJson.h>
// WiFi配置
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";
// MQTT配置
const char* mqtt_server = "你的本地EMQX broker地址"; // 如192.168.1.100
const int mqtt_port = 1883;
const char* mqtt_user = "你的MQTT用户名"; // 如admin
const char* mqtt_password = "你的MQTT密码"; // 如public
const char* mqtt_topic_sensor_data = "agricultural/sensor/data"; // 传感器数据主题
const char* mqtt_topic_device_control = "agricultural/device/control"; // 设备控制主题
// 传感器配置
#define DHT_PIN D2
#define DHT_TYPE DHT11
DHT dht(DHT_PIN, DHT_TYPE);
BH1750 lightMeter;
// WiFi客户端和MQTT客户端
WiFiClient espClient;
PubSubClient client(espClient);
// 定时器配置
unsigned long lastMsg = 0;
const long interval = 30000; // 数据采集间隔:30秒
// 解析控制指令函数
void callback(char* topic, byte* payload, unsigned int length) {
// 打印主题和负载
Serial.print("收到主题:");
Serial.println(topic);
Serial.print("收到负载:");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// 解析控制指令
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, payload, length);
if (error) {
Serial.print("解析控制指令失败:");
Serial.println(error.c_str());
return;
}
// 获取设备ID和控制指令
String device_id = doc["device_id"].as<String>();
String control_command = doc["control_command"].as<String>();
// 执行控制指令
if (device_id == "esp8266_001") {
if (control_command == "open_fan") {
Serial.println("打开通风扇");
// 在这里添加打开通风扇的代码
} else if (control_command == "close_fan") {
Serial.println("关闭通风扇");
// 在这里添加关闭通风扇的代码
} else if (control_command == "open_light") {
Serial.println("打开补光灯");
// 在这里添加打开补光灯的代码
} else if (control_command == "close_light") {
Serial.println("关闭补光灯");
// 在这里添加关闭补光灯的代码
}
}
}
// 连接WiFi函数
void connectWiFi() {
Serial.print("正在连接WiFi:");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi连接成功");
Serial.print("IP地址:");
Serial.println(WiFi.localIP());
}
// 连接MQTT broker函数
void connectMQTT() {
while (!client.connected()) {
Serial.print("正在连接MQTT broker:");
Serial.println(mqtt_server);
// 尝试连接MQTT broker
if (client.connect("ESP8266_001", mqtt_user, mqtt_password)) {
Serial.println("MQTT broker连接成功");
// 订阅设备控制主题
client.subscribe(mqtt_topic_device_control);
} else {
Serial.print("MQTT broker连接失败,错误代码:");
Serial.print(client.state());
Serial.println(",5秒后重试");
delay(5000);
}
}
}
// 发送传感器数据函数
void sendSensorData() {
// 采集传感器数据
float temperature = dht.readTemperature();
float humidity = dht.readHumidity();
float light = lightMeter.readLightLevel();
// 检查传感器数据是否有效
if (isnan(temperature) || isnan(humidity)) {
Serial.println("DHT11传感器读取失败");
return;
}
if (isnan(light)) {
Serial.println("BH1750传感器读取失败");
return;
}
// 封装传感器数据为JSON格式
DynamicJsonDocument doc(1024);
doc["device_id"] = "esp8266_001";
doc["timestamp"] = millis();
doc["temperature"] = temperature;
doc["humidity"] = humidity;
doc["light"] = light;
// 打印传感器数据
Serial.print("设备ID:");
Serial.println(doc["device_id"].as<String>());
Serial.print("时间戳:");
Serial.println(doc["timestamp"].as<unsigned long>());
Serial.print("温度:");
Serial.print(doc["temperature"].as<float>());
Serial.println("℃");
Serial.print("湿度:");
Serial.print(doc["humidity"].as<float>());
Serial.println("%");
Serial.print("光照:");
Serial.print(doc["light"].as<float>());
Serial.println("lux");
// 将JSON格式数据转换为字符串
String payload;
serializeJson(doc, payload);
// 发送传感器数据到MQTT broker
client.publish(mqtt_topic_sensor_data, payload.c_str());
}
// 初始化函数
void setup() {
Serial.begin(115200);
delay(10);
// 初始化传感器
dht.begin();
Wire.begin(D6, D5); // SDA, SCL
lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
// 连接WiFi
connectWiFi();
// 配置MQTT客户端
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
// 连接MQTT broker
connectMQTT();
}
// 主循环函数
void loop() {
// 检查MQTT连接状态
if (!client.connected()) {
connectMQTT();
}
// 保持MQTT连接
client.loop();
// 定时发送传感器数据
unsigned long currentMsg = millis();
if (currentMsg - lastMsg > interval) {
lastMsg = currentMsg;
sendSensorData();
}
}
⑤ 代码上传:
- 连接ESP8266开发板到电脑;
- 在Arduino IDE中选择正确的开发板和端口;
- 点击"上传"按钮,将代码上传到ESP8266开发板;
⑥ 代码调试: - 打开Arduino IDE的串口监视器;
- 设置波特率为115200;
- 观察串口监视器输出的信息,检查WiFi连接、MQTT连接、传感器数据采集是否正常。
✅ 完成以上步骤后,ESP8266数据采集端就开发好了。
1.31.3.4 第三层:易语言Windows数据存储监控端的开发 🔧
现在,我们来开发易语言Windows数据存储监控端,这个端将用到所有核心组件、本地存储、网络通讯、数据可视化等知识点。
1. 界面设计------组件箱拖拽+属性表配置 🎨
① 新建Windows窗口程序 :打开易语言官方IDE,点击"文件→新建→Windows窗口程序→确定";
② 添加容器组件 :从组件箱中拖拽4个分组框组件到"_启动窗口"上,属性名分别为"_分组框_数据展示区""_分组框_历史数据查询区""_分组框_异常告警区""_分组框_系统状态区",标题分别为"数据展示区""历史数据查询区""异常告警区""系统状态区",位置分别为(10,10)、(10,200)、(520,10)、(520,200),宽度分别为500px、500px、300px、300px,高度分别为180px、300px、180px、300px;
③ 添加数据展示区组件:
- 3个标签组件:属性名分别为"_标签_温度""_标签_湿度""_标签_光照",标题分别为"温度:""湿度:""光照:",位置分别为(20,30)、(20,70)、(20,110);
- 3个编辑框组件:属性名分别为"_编辑框_温度""_编辑框_湿度""_编辑框_光照",位置分别为(80,30)、(80,70)、(80,110),宽度分别为100px、100px、100px,高度统一为25px,可用为假;
- 3个标签组件:属性名分别为"_标签_温度单位""_标签_湿度单位""_标签_光照单位",标题分别为"℃""%""lux",位置分别为(190,30)、(190,70)、(190,110);
- 1个图片框组件:属性名分别为"_图片框_温度图标""_图片框_湿度图标""_图片框_光照图标",位置分别为(220,30)、(220,70)、(220,110),宽度和高度统一为30px;
- 1个高级表格组件 :属性名分别为"_高级表格_实时数据",列数为6,行数为10,列宽分别为100px、130px、80px、80px、100px、50px,行高统一为25px,位置为(270,30),宽度为220px,高度为130px;
④ 添加历史数据查询区组件: - 2个标签组件:属性名分别为"_标签_查询开始时间""_标签_查询结束时间",标题分别为"查询开始时间:""查询结束时间:",位置分别为(20,30)、(20,60);
- 2个日期框组件:属性名分别为"_日期框_查询开始时间""_日期框_查询结束时间",位置分别为(120,30)、(120,60),宽度分别为150px、150px,高度统一为25px;
- 1个按钮组件:属性名分别为"_按钮_查询历史数据",标题为"查询历史数据",位置为(20,90),宽度为100px,高度为25px;
- 1个高级表格组件:属性名分别为"_高级表格_历史数据",列数为6,行数为50,列宽分别为100px、130px、80px、80px、100px、50px,行高统一为25px,位置为(20,130),宽度为460px,高度为150px;
- 1个按钮组件 :属性名分别为"_按钮_导出历史数据",标题为"导出历史数据",位置为(380,90),宽度为100px,高度为25px;
⑤ 添加异常告警区组件: - 2个标签组件:属性名分别为"_标签_温度阈值""_标签_湿度阈值""_标签_光照阈值",标题分别为"温度阈值:""湿度阈值:""光照阈值:",位置分别为(20,30)、(20,60)、(20,90);
- 4个编辑框组件:属性名分别为"_编辑框_温度最低阈值""_编辑框_温度最高阈值""_编辑框_湿度最低阈值""_编辑框_湿度最高阈值""_编辑框_光照最低阈值""_编辑框_光照最高阈值",位置分别为(80,30)、(190,30)、(80,60)、(190,60)、(80,90)、(190,90),宽度分别为100px、100px、100px、100px、100px、100px,高度统一为25px;
- 2个标签组件:属性名分别为"_标签_温度阈值单位""_标签_湿度阈值单位""_标签_光照阈值单位",标题分别为"℃""℃""%""%""lux""lux",位置分别为(190,30)、(300,30)、(190,60)、(300,60)、(190,90)、(300,90);
- 1个按钮组件:属性名分别为"_按钮_保存阈值设置",标题为"保存阈值设置",位置为(20,120),宽度为100px,高度为25px;
- 1个列表框组件 :属性名分别为"_列表框_异常告警",位置为(20,150),宽度为260px,高度为120px;
⑥ 添加系统状态区组件: - 2个标签组件:属性名分别为"_标签_EMQX连接状态""_标签_数据采集端在线状态",标题分别为"EMQX连接状态:""数据采集端在线状态:",位置分别为(20,30)、(20,60);
- 2个标签组件:属性名分别为"_标签_EMQX连接状态值""_标签_数据采集端在线状态值",标题分别为"未连接""离线",位置分别为(130,30)、(130,60),字体颜色分别为#FF0000、#FF0000;
- 1个按钮组件:属性名分别为"_按钮_连接EMQX",标题为"连接EMQX",位置为(20,90),宽度为100px,高度为25px;
- 1个时钟组件:属性名分别为"_时钟_定时保存",时钟周期为30000,可见为假;
- 1个时钟组件:属性名分别为"_时钟_定时刷新数据",时钟周期为10000,可见为假;
- 1个通用对话框组件 :属性名分别为"_通用对话框_保存文件",类型为"保存文件",过滤器为"Excel文件 (.xlsx)| .xlsx|所有文件 (. )|.",初始目录为程序的运行目录;
- 1个MQTT客户端组件:属性名分别为"_MQTT客户端_数据通信",可见为假;
✅ 完成以上步骤后,窗口程序的界面就设计好了。
2. 代码实现------分模块详细代码+注释 💻
我们需要为每个组件编写事件驱动代码,同时编写公共函数来处理数据读取、保存、显示、异常告警等操作。
模块1:启动窗口事件驱动代码
e
.版本 2
.支持库 spec
.支持库 eSQLite
.支持库 iconv
.支持库 eExcel
.程序集 窗口程序集_启动窗口
.程序集变量 数据库连接, SQL连接句柄
.程序集变量 数据库文件路径, 文本型 ' 存储数据库文件路径
.程序集变量 EMQX连接状态, 逻辑型 ' 存储EMQX连接状态
.程序集变量 数据采集端在线状态, 逻辑型 ' 存储数据采集端在线状态
.程序集变量 温度最低阈值, 小数型 ' 存储温度最低阈值
.程序集变量 温度最高阈值, 小数型 ' 存储温度最高阈值
.程序集变量 湿度最低阈值, 小数型 ' 存储湿度最低阈值
.程序集变量 湿度最高阈值, 小数型 ' 存储湿度最高阈值
.程序集变量 光照最低阈值, 小数型 ' 存储光照最低阈值
.程序集变量 光照最高阈值, 小数型 ' 存储光照最高阈值
.程序集变量 数据采集端在线时间戳, 整数型 ' 存储数据采集端在线时间戳
.子程序 _启动窗口_创建完毕
' 设置数据库文件路径
数据库文件路径 = 取运行目录 () + "/农业大棚数据.db"
' 检查数据库文件是否存在,如果不存在则创建
.如果真 (是否存在文件 (数据库文件路径) = 假)
创建农业大棚数据库 ()
.如果真结束
' 连接数据库
数据库连接 = SQL打开数据库 (数据库文件路径)
' 检查数据库连接是否成功
.如果真 (数据库连接 = 0)
信息框 ("数据库连接失败!", #错误图标, "农业大棚监控系统")
返回 ()
.如果真结束
' 读取阈值设置
读取阈值设置 ()
' 初始化高级表格
初始化高级表格 ()
' 显示实时数据
显示实时数据 ()
' 启动定时保存和定时刷新数据的时钟
_时钟_定时保存.时钟周期 = 30000
_时钟_定时刷新数据.时钟周期 = 10000
.子程序 _启动窗口_将被销毁
' 停止定时保存和定时刷新数据的时钟
_时钟_定时保存.时钟周期 = 0
_时钟_定时刷新数据.时钟周期 = 0
' 断开EMQX连接
.如果真 (EMQX连接状态 = 真)
_MQTT客户端_数据通信.断开连接 ()
.如果真结束
' 断开数据库连接
SQL关闭数据库 (数据库连接)
.子程序 创建农业大棚数据库
' 创建数据库
.局部变量 临时连接, SQL连接句柄
临时连接 = SQL创建数据库 (数据库文件路径)
' 创建传感器数据表
SQL执行语句 (临时连接, "CREATE TABLE IF NOT EXISTS sensor_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT NOT NULL,
timestamp INTEGER NOT NULL,
temperature REAL NOT NULL,
humidity REAL NOT NULL,
light REAL NOT NULL
)")
' 创建阈值设置表
SQL执行语句 (临时连接, "CREATE TABLE IF NOT EXISTS threshold_settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
temperature_min REAL DEFAULT 15,
temperature_max REAL DEFAULT 30,
humidity_min REAL DEFAULT 40,
humidity_max REAL DEFAULT 80,
light_min REAL DEFAULT 1000,
light_max REAL DEFAULT 100000
)")
' 插入默认阈值设置
SQL执行语句 (临时连接, "INSERT OR IGNORE INTO threshold_settings (
temperature_min, temperature_max, humidity_min, humidity_max, light_min, light_max
) VALUES (15, 30, 40, 80, 1000, 100000)")
' 断开数据库连接
SQL关闭数据库 (临时连接)
.子程序 读取阈值设置
' 查询阈值设置表
.局部变量 查询结果, SQL查询句柄
查询结果 = SQL查询 (数据库连接, "SELECT * FROM threshold_settings WHERE id = 1")
' 读取阈值设置
.如果真 (SQL取记录数 (查询结果) > 0)
SQL到首记录 (查询结果)
温度最低阈值 = SQL取字段值 (查询结果, "temperature_min", #SQL库_字段值类型_数值型)
温度最高阈值 = SQL取字段值 (查询结果, "temperature_max", #SQL库_字段值类型_数值型)
湿度最低阈值 = SQL取字段值 (查询结果, "humidity_min", #SQL库_字段值类型_数值型)
湿度最高阈值 = SQL取字段值 (查询结果, "humidity_max", #SQL库_字段值类型_数值型)
光照最低阈值 = SQL取字段值 (查询结果, "light_min", #SQL库_字段值类型_数值型)
光照最高阈值 = SQL取字段值 (查询结果, "light_max", #SQL库_字段值类型_数值型)
' 显示阈值设置
_编辑框_温度最低阈值.内容 = 到文本 (温度最低阈值)
_编辑框_温度最高阈值.内容 = 到文本 (温度最高阈值)
_编辑框_湿度最低阈值.内容 = 到文本 (湿度最低阈值)
_编辑框_湿度最高阈值.内容 = 到文本 (湿度最高阈值)
_编辑框_光照最低阈值.内容 = 到文本 (光照最低阈值)
_编辑框_光照最高阈值.内容 = 到文本 (光照最高阈值)
.如果真结束
' 释放查询结果
SQL释放查询结果 (查询结果)
.子程序 保存阈值设置
' 更新阈值设置表
SQL执行语句 (数据库连接, "UPDATE threshold_settings SET
temperature_min = " + 到文本 (温度最低阈值) + ",
temperature_max = " + 到文本 (温度最高阈值) + ",
humidity_min = " + 到文本 (湿度最低阈值) + ",
humidity_max = " + 到文本 (湿度最高阈值) + ",
light_min = " + 到文本 (光照最低阈值) + ",
light_max = " + 到文本 (光照最高阈值) + "
WHERE id = 1")
.子程序 初始化高级表格
' 设置实时数据高级表格的列标题
_高级表格_实时数据.置数据 (0, 0, #表格常量_文本型, "设备ID")
_高级表格_实时数据.置数据 (0, 1, #表格常量_文本型, "时间戳")
_高级表格_实时数据.置数据 (0, 2, #表格常量_文本型, "温度")
_高级表格_实时数据.置数据 (0, 3, #表格常量_文本型, "湿度")
_高级表格_实时数据.置数据 (0, 4, #表格常量_文本型, "光照")
_高级表格_实时数据.置数据 (0, 5, #表格常量_文本型, "状态")
' 设置实时数据高级表格的列宽
_高级表格_实时数据.置列宽 (0, 100)
_高级表格_实时数据.置列宽 (1, 130)
_高级表格_实时数据.置列宽 (2, 80)
_高级表格_实时数据.置列宽 (3, 80)
_高级表格_实时数据.置列宽 (4, 100)
_高级表格_实时数据.置列宽 (5, 50)
' 设置实时数据高级表格的行高
.局部变量 i, 整数型
.计次循环首 (_高级表格_实时数据.取行数 (), i)
_高级表格_实时数据.置行高 (i - 1, 25)
.计次循环尾 ()
' 设置历史数据高级表格的列标题
_高级表格_历史数据.置数据 (0, 0, #表格常量_文本型, "设备ID")
_高级表格_历史数据.置数据 (0, 1, #表格常量_文本型, "时间戳")
_高级表格_历史数据.置数据 (0, 2, #表格常量_文本型, "温度")
_高级表格_历史数据.置数据 (0, 3, #表格常量_文本型, "湿度")
_高级表格_历史数据.置数据 (0, 4, #表格常量_文本型, "光照")
_高级表格_历史数据.置数据 (0, 5, #表格常量_文本型, "状态")
' 设置历史数据高级表格的列宽
_高级表格_历史数据.置列宽 (0, 100)
_高级表格_历史数据.置列宽 (1, 130)
_高级表格_历史数据.置列宽 (2, 80)
_高级表格_历史数据.置列宽 (3, 80)
_高级表格_历史数据.置列宽 (4, 100)
_高级表格_历史数据.置列宽 (5, 50)
' 设置历史数据高级表格的行高
.计次循环首 (_高级表格_历史数据.取行数 (), i)
_高级表格_历史数据.置行高 (i - 1, 25)
.计次循环尾 ()
模块2:系统状态区组件事件驱动代码
⌨️ 连接EMQX按钮事件驱动代码
e
.版本 2
.支持库 spec
.子程序 _按钮_连接EMQX_被单击
' 配置MQTT客户端
_MQTT客户端_数据通信.服务器地址 = "192.168.1.100"
_MQTT客户端_数据通信.服务器端口 = 1883
_MQTT客户端_数据通信.客户端ID = "Windows_001"
_MQTT客户端_数据通信.用户名 = "admin"
_MQTT客户端_数据通信.密码 = "public"
' 连接EMQX broker
.局部变量 连接结果, 逻辑型
连接结果 = _MQTT客户端_数据通信.连接 ()
' 检查连接结果
.如果真 (连接结果 = 真)
EMQX连接状态 = 真
_标签_EMQX连接状态值.标题 = "已连接"
_标签_EMQX连接状态值.字体颜色 = #00FF00
' 订阅传感器数据主题
_MQTT客户端_数据通信.订阅主题 ("agricultural/sensor/data")
信息框 ("EMQX broker连接成功!", #信息图标, "农业大棚监控系统")
.如果真结束
.如果真 (连接结果 = 假)
EMQX连接状态 = 假
_标签_EMQX连接状态值.标题 = "未连接"
_标签_EMQX连接状态值.字体颜色 = #FF0000
信息框 ("EMQX broker连接失败!", #错误图标, "农业大棚监控系统")
.如果真结束
⌨️ MQTT客户端消息接收事件驱动代码
e
.版本 2
.支持库 spec
.支持库 json
.子程序 _MQTT客户端_数据通信_消息接收
' 解析MQTT消息
.局部变量 消息主题, 文本型
消息主题 = _MQTT客户端_数据通信.取消息主题 ()
.局部变量 消息内容, 文本型
消息内容 = UTF8到文本 (_MQTT客户端_数据通信.取消息内容 ())
' 处理传感器数据主题的消息
.如果真 (寻找文本 (消息主题, "agricultural/sensor/data", , 真) ≠ -1)
处理传感器数据消息 (消息内容)
.如果真结束
' 处理设备控制主题的消息
.如果真 (寻找文本 (消息主题, "agricultural/device/control", , 真) ≠ -1)
处理设备控制消息 (消息内容)
.如果真结束
.子程序 处理传感器数据消息, , , 参数:消息内容为JSON格式的字符串
.参数 消息内容, 文本型
' 解析JSON格式的消息内容
.局部变量 解析结果, 逻辑型
.局部变量 解析对象, JSON对象
解析结果 = 解析对象.解析 (消息内容)
.如果真 (解析结果 = 假)
调试输出 ("解析传感器数据消息失败:" + 解析对象.取错误信息 ())
返回 ()
.如果真结束
' 读取传感器数据
.局部变量 设备ID, 文本型
设备ID = 解析对象.取成员文本 ("device_id")
.局部变量 时间戳, 整数型
时间戳 = 解析对象.取成员数值 ("timestamp")
.局部变量 温度, 小数型
温度 = 解析对象.取成员数值 ("temperature")
.局部变量 湿度, 小数型
湿度 = 解析对象.取成员数值 ("humidity")
.局部变量 光照, 小数型
光照 = 解析对象.取成员数值 ("light")
' 检查传感器数据是否有效
.如果真 (温度 < -40 或 温度 > 80 或 湿度 < 0 或 湿度 > 100 或 光照 < 0 或 光照 > 100000)
调试输出 ("传感器数据无效:温度 = " + 到文本 (温度) + "℃,湿度 = " + 到文本 (湿度) + "%,光照 = " + 到文本 (光照) + "lux")
返回 ()
.如果真结束
' 显示传感器数据
_编辑框_温度.内容 = 到文本 (温度)
_编辑框_湿度.内容 = 到文本 (湿度)
_编辑框_光照.内容 = 到文本 (光照)
' 更新数据采集端在线状态
数据采集端在线状态 = 真
数据采集端在线时间戳 = 取现行时间 ().取时间戳 ()
_标签_数据采集端在线状态值.标题 = "在线"
_标签_数据采集端在线状态值.字体颜色 = #00FF00
' 保存传感器数据到数据库
保存传感器数据到数据库 (设备ID, 时间戳, 温度, 湿度, 光照)
' 检查传感器数据是否异常
检查传感器数据是否异常 (设备ID, 时间戳, 温度, 湿度, 光照)
' 显示实时数据到高级表格
显示实时数据到高级表格 (设备ID, 时间戳, 温度, 湿度, 光照)
.子程序 处理设备控制消息, , , 参数:消息内容为JSON格式的字符串
.参数 消息内容, 文本型
' 解析JSON格式的消息内容
.局部变量 解析结果, 逻辑型
.局部变量 解析对象, JSON对象
解析结果 = 解析对象.解析 (消息内容)
.如果真 (解析结果 = 假)
调试输出 ("解析设备控制消息失败:" + 解析对象.取错误信息 ())
返回 ()
.如果真结束
' 读取设备控制指令
.局部变量 设备ID, 文本型
设备ID = 解析对象.取成员文本 ("device_id")
.局部变量 控制指令, 文本型
控制指令 = 解析对象.取成员文本 ("control_command")
' 打印设备控制指令
调试输出 ("收到设备控制指令:设备ID = " + 设备ID + ",控制指令 = " + 控制指令)
模块3:异常告警区组件事件驱动代码
⌨️ 保存阈值设置按钮事件驱动代码
e
.版本 2
.支持库 spec
.子程序 _按钮_保存阈值设置_被单击
' 检查输入是否为空
.如果真 (_编辑框_温度最低阈值.内容 = "" 或 _编辑框_温度最高阈值.内容 = "" 或 _编辑框_湿度最低阈值.内容 = "" 或 _编辑框_湿度最高阈值.内容 = "" 或 _编辑框_光照最低阈值.内容 = "" 或 _编辑框_光照最高阈值.内容 = "")
信息框 ("请输入所有阈值设置!", #错误图标, "农业大棚监控系统")
返回 ()
.如果真结束
' 读取输入的阈值设置
温度最低阈值 = 到数值 (_编辑框_温度最低阈值.内容)
温度最高阈值 = 到数值 (_编辑框_温度最高阈值.内容)
湿度最低阈值 = 到数值 (_编辑框_湿度最低阈值.内容)
湿度最高阈值 = 到数值 (_编辑框_湿度最高阈值.内容)
光照最低阈值 = 到数值 (_编辑框_光照最低阈值.内容)
光照最高阈值 = 到数值 (_编辑框_光照最高阈值.内容)
' 检查阈值设置是否合理
.如果真 (温度最低阈值 > 温度最高阈值 或 湿度最低阈值 > 湿度最高阈值 或 光照最低阈值 > 光照最高阈值)
信息框 ("阈值设置不合理!最低阈值不能大于最高阈值!", #错误图标, "农业大棚监控系统")
返回 ()
.如果真结束
' 保存阈值设置
保存阈值设置 ()
信息框 ("阈值设置保存成功!", #信息图标, "农业大棚监控系统")
模块4:历史数据查询区组件事件驱动代码
⌨️ 查询历史数据按钮事件驱动代码
e
.版本 2
.支持库 spec
.支持库 eSQLite
.支持库 iext
.子程序 _按钮_查询历史数据_被单击
' 检查查询时间是否合理
.如果真 (_日期框_查询开始时间.数值 > _日期框_查询结束时间.数值)
信息框 ("查询时间不合理!开始时间不能大于结束时间!", #错误图标, "农业大棚监控系统")
返回 ()
.如果真结束
' 清空历史数据高级表格
_高级表格_历史数据.清空 ()
初始化高级表格 ()
' 查询历史数据
.局部变量 查询SQL语句, 文本型
查询SQL语句 = "SELECT * FROM sensor_data WHERE timestamp BETWEEN " + 到文本 (_日期框_查询开始时间.数值) + " AND " + 到文本 (_日期框_查询结束时间.数值)
.局部变量 查询结果, SQL查询句柄
查询结果 = SQL查询 (数据库连接, 查询SQL语句)
' 显示查询结果到高级表格
.局部变量 i, 整数型
.计次循环首 (SQL取记录数 (查询结果), i)
SQL到指定记录 (查询结果, i - 1)
.局部变量 设备ID, 文本型
设备ID = SQL取字段值 (查询结果, "device_id", #SQL库_字段值类型_文本型)
.局部变量 时间戳, 整数型
时间戳 = SQL取字段值 (查询结果, "timestamp", #SQL库_字段值类型_数值型)
.局部变量 温度, 小数型
温度 = SQL取字段值 (查询结果, "temperature", #SQL库_字段值类型_数值型)
.局部变量 湿度, 小数型
湿度 = SQL取字段值 (查询结果, "humidity", #SQL库_字段值类型_数值型)
.局部变量 光照, 小数型
光照 = SQL取字段值 (查询结果, "light", #SQL库_字段值类型_数值型)
' 显示查询结果到高级表格
显示历史数据到高级表格 (i, 设备ID, 时间戳, 温度, 湿度, 光照)
.计次循环尾 ()
' 释放查询结果
SQL释放查询结果 (查询结果)
' 检查是否找到查询结果
.如果真 (SQL取记录数 (查询结果) = 0)
信息框 ("未找到查询结果!", #错误图标, "农业大棚监控系统")
返回 ()
.如果真结束
信息框 ("查询成功!共找到" + 到文本 (SQL取记录数 (查询结果)) + "条记录!", #信息图标, "农业大棚监控系统")
⌨️ 导出历史数据按钮事件驱动代码
e
.版本 2
.支持库 spec
.支持库 eSQLite
.支持库 eExcel
.子程序 _按钮_导出历史数据_被单击
' 检查是否有查询结果
.如果真 (_高级表格_历史数据.取数据行数 () = 0)
信息框 ("没有查询结果可以导出!", #错误图标, "农业大棚监控系统")
返回 ()
.如果真结束
' 打开保存文件对话框
.如果真 (_通用对话框_保存文件.打开 () = 假)
返回 ()
.如果真结束
' 创建Excel工作簿
.局部变量 Excel工作簿, 对象
Excel工作簿.创建 ("Excel.Application")
' 添加新工作表
Excel工作簿.读属性 ("Workbooks").数值方法 ("Add", )
' 写入表头
Excel工作簿.读属性 ("ActiveSheet").读属性 ("Cells").数值方法 ("Item", 1, 1).写属性 ("Value", "设备ID")
Excel工作簿.读属性 ("ActiveSheet").读属性 ("Cells").数值方法 ("Item", 1, 2).写属性 ("Value", "时间戳")
Excel工作簿.读属性 ("ActiveSheet").读属性 ("Cells").数值方法 ("Item", 1, 3).写属性 ("Value", "温度")
Excel工作簿.读属性 ("ActiveSheet").读属性 ("Cells").数值方法 ("Item", 1, 4).写属性 ("Value", "湿度")
Excel工作簿.读属性 ("ActiveSheet").读属性 ("Cells").数值方法 ("Item", 1, 5).写属性 ("Value", "光照")
Excel工作簿.读属性 ("ActiveSheet").读属性 ("Cells").数值方法 ("Item", 1, 6).写属性 ("Value", "状态")
' 写入查询结果
.局部变量 i, 整数型
.计次循环首 (_高级表格_历史数据.取数据行数 (), i)
Excel工作簿.读属性 ("ActiveSheet").读属性 ("Cells").数值方法 ("Item", i + 1, 1).写属性 ("Value", _高级表格_历史数据.取数据 (i, 0))
Excel工作簿.读属性 ("ActiveSheet").读属性 ("Cells").数值方法 ("Item", i + 1, 2).写属性 ("Value", _高级表格_历史数据.取数据 (i, 1))
Excel工作簿.读属性 ("ActiveSheet").读属性 ("Cells").数值方法 ("Item", i + 1, 3).写属性 ("Value", _高级表格_历史数据.取数据 (i, 2))
Excel工作簿.读属性 ("ActiveSheet").读属性 ("Cells").数值方法 ("Item", i + 1, 4).写属性 ("Value", _高级表格_历史数据.取数据 (i, 3))
Excel工作簿.读属性 ("ActiveSheet").读属性 ("Cells").数值方法 ("Item", i + 1, 5).写属性 ("Value", _高级表格_历史数据.取数据 (i, 4))
Excel工作簿.读属性 ("ActiveSheet").读属性 ("Cells").数值方法 ("Item", i + 1, 6).写属性 ("Value", _高级表格_历史数据.取数据 (i, 5))
.计次循环尾 ()
' 保存Excel工作簿
Excel工作簿.读属性 ("ActiveWorkbook").数值方法 ("SaveAs", _通用对话框_保存文件.文件名)
' 关闭Excel工作簿
Excel工作簿.读属性 ("ActiveWorkbook").数值方法 ("Close", )
' 退出Excel应用程序
Excel工作簿.数值方法 ("Quit", )
' 释放Excel工作簿对象
Excel工作簿.释放 ()
信息框 ("导出成功!导出路径:" + _通用对话框_保存文件.文件名, #信息图标, "农业大棚监控系统")
模块5:公共函数代码
e
.版本 2
.支持库 spec
.支持库 eSQLite
.支持库 iext
.子程序 保存传感器数据到数据库, , , 参数:设备ID、时间戳、温度、湿度、光照
.参数 设备ID, 文本型
.参数 时间戳, 整数型
.参数 温度, 小数型
.参数 湿度, 小数型
.参数 光照, 小数型
' 插入传感器数据到数据库
.局部变量 插入SQL语句, 文本型
插入SQL语句 = "INSERT INTO sensor_data (device_id, timestamp, temperature, humidity, light) VALUES ('" + 设备ID + "', " + 到文本 (时间戳) + ", " + 到文本 (温度) + ", " + 到文本 (湿度) + ", " + 到文本 (光照) + ")"
SQL执行语句 (数据库连接, 插入SQL语句)
.子程序 检查传感器数据是否异常, , , 参数:设备ID、时间戳、温度、湿度、光照
.参数 设备ID, 文本型
.参数 时间戳, 整数型
.参数 温度, 小数型
.参数 湿度, 小数型
.参数 光照, 小数型
' 检查温度是否异常
.如果真 (温度 < 温度最低阈值 或 温度 > 温度最高阈值)
显示异常告警信息 (设备ID, 时间戳, "温度异常", 到文本 (温度) + "℃")
.如果真结束
' 检查湿度是否异常
.如果真 (湿度 < 湿度最低阈值 或 湿度 > 湿度最高阈值)
显示异常告警信息 (设备ID, 时间戳, "湿度异常", 到文本 (湿度) + "%")
.如果真结束
' 检查光照是否异常
.如果真 (光照 < 光照最低阈值 或 光照 > 光照最高阈值)
显示异常告警信息 (设备ID, 时间戳, "光照异常", 到文本 (光照) + "lux")
.如果真结束
.子程序 显示异常告警信息, , , 参数:设备ID、时间戳、异常类型、异常值
.参数 设备ID, 文本型
.参数 时间戳, 整数型
.参数 异常类型, 文本型
.参数 异常值, 文本型
' 格式化异常告警信息
.局部变量 异常告警信息, 文本型
异常告警信息 = 取现行时间 ().格式化 ("YYYY-MM-DD HH:MM:SS") + " - 设备ID:" + 设备ID + " - " + 异常类型 + ":" + 异常值
' 添加异常告警信息到列表框
_列表框_异常告警.加入项目 (异常告警信息)
' 高亮显示列表框的最后一条记录
_列表框_异常告警.现行选中项 = _列表框_异常告警.取项目数 () - 1
' 播放异常告警声音
播放提示音 (#错误提示音)
1.31.3.5 项目测试调试和优化经验 🧪
编写完事件驱动代码后,我们需要对程序进行全流程测试和优化:
1.31.3.5.1 项目测试调试
① 硬件测试 :检查ESP8266开发板、传感器、连接线的连接是否正常;
② 软件测试 :检查Arduino代码的上传和调试是否正常,检查易语言Windows程序的编译和运行是否正常;
③ 通信测试 :检查ESP8266数据采集端与本地EMQX broker的通信是否正常,检查易语言Windows监控端与本地EMQX broker的通信是否正常;
④ 功能测试 :检查所有功能是否正常,如实时数据展示、历史数据查询、异常告警、数据导出等;
⑤ 性能测试:检查程序的响应时间和数据采集间隔是否符合要求;
1.31.3.5.2 项目优化经验
① 通信优化 :使用本地EMQX broker代替公共MQTT broker,提高通信稳定性和响应时间;
② 数据存储优化 :使用SQLite数据库代替本地文本文件,提高数据存储和查询效率;
③ 数据可视化优化 :使用高级表格组件代替列表框组件,提高数据展示的可读性;
④ 异常告警优化 :使用播放提示音和高亮显示列表框记录的方法,提高异常告警的辨识度;
⑤ 程序稳定性优化:使用错误处理机制,避免程序崩溃;
1.31.4 易语言IoT开发常见问题排查手册 🚨
在易语言IoT开发过程中,你可能会遇到各种各样的问题,有些问题看起来很简单,但解决起来却很麻烦;有些问题看起来很复杂,但解决起来却很简单。为了帮助你快速解决这些问题,我整理了一份易语言IoT开发常见问题排查手册,分为五大类,每类3个问题,共15个。
1.31.4.1 通信问题(3个)
问题1:ESP8266连接本地EMQX broker失败
问题现象 :ESP8266开发板连接本地EMQX broker失败,串口监视器输出"MQTT broker连接失败,错误代码:-2,5秒后重试"。
原因分析 :
① 本地EMQX broker没有启动;
② 本地EMQX broker的地址或端口号设置不正确;
③ 本地EMQX broker的用户名或密码设置不正确;
④ 本地EMQX broker的网络连接失败;
解决方案:
- 检查本地EMQX broker是否启动;
- 检查本地EMQX broker的地址或端口号设置是否正确;
- 检查本地EMQX broker的用户名或密码设置是否正确;
- 检查本地EMQX broker的网络连接是否失败;
问题2:易语言Windows程序连接本地EMQX broker失败
问题现象 :易语言Windows程序连接本地EMQX broker失败,信息框显示"EMQX broker连接失败!"。
原因分析 :
① 本地EMQX broker没有启动;
② 本地EMQX broker的地址或端口号设置不正确;
③ 本地EMQX broker的用户名或密码设置不正确;
④ 本地EMQX broker的网络连接失败;
⑤ 易语言Windows程序的防火墙设置阻止了程序访问网络;
解决方案:
- 检查本地EMQX broker是否启动;
- 检查本地EMQX broker的地址或端口号设置是否正确;
- 检查本地EMQX broker的用户名或密码设置是否正确;
- 检查本地EMQX broker的网络连接是否失败;
- 检查易语言Windows程序的防火墙设置是否阻止了程序访问网络;
问题3:数据采集端发送的数据在监控端显示乱码
问题现象 :ESP8266数据采集端发送的数据在易语言Windows监控端显示乱码。
原因分析 :
① 数据采集端发送的数据格式与监控端解析的数据格式不一致;
② 数据采集端发送的数据编码格式与监控端解析的数据编码格式不一致;
解决方案:
- 检查数据采集端发送的数据格式是否与监控端解析的数据格式一致(如都是JSON格式);
- 检查数据采集端发送的数据编码格式是否与监控端解析的数据编码格式一致(如都是UTF-8编码格式);
1.31.5 中文编程与物联网的未来发展方向 📜
中文编程与物联网的结合是未来发展的必然趋势,它可以带来以下5大优势:
✅ 降低学习门槛 :中文编程可以降低物联网开发的学习门槛,让更多人参与物联网开发;
✅ 提高开发效率 :中文编程的可视化编程功能可以提高物联网开发的效率;
✅ 保护用户隐私 :本地物联网项目不需要连接互联网,保护用户隐私;
✅ 促进经济发展 :中文编程与物联网的结合可以促进国内物联网行业的发展,提高国内物联网行业的竞争力;
✅ 传承文化:中文编程与物联网的结合可以通过中文编程传承中华文化;
1.31.6 补充篇总结与中文编程IoT开发展望 🚀
1.31.6.1 本章核心收获
- ✅ 构建易语言IoT开发架构模型:掌握"本地EMQX MQTT broker消息中间件→ESP8266/ESP32数据采集端→易语言Windows数据存储监控端"的三层架构;
- ✅ 掌握跨设备本地通信技术:学会使用本地EMQX MQTT broker实现易语言Windows程序与ESP8266/ESP32的稳定通信;
- ✅ 完成完整的IoT实战项目:开发一个农业大棚温湿度光照数据采集存储监控系统,支持实时数据展示、历史数据查询、异常告警、数据导出功能;
- ✅ 掌握IoT开发优化经验:学习易语言IoT开发中常见问题(如MQTT连接失败、数据乱码、网络延迟高)的排查方法和优化经验。
1.31.6.2 中文编程IoT开发展望
中文编程与物联网的结合是未来发展的必然趋势,随着技术的不断进步,中文编程IoT开发将会更加简单、高效、智能。未来,我们可以期待以下5个方面的发展:
💡 AI辅助开发 :进一步优化AI辅助开发工具,提高物联网开发的效率;
💡 本地大模型 :进一步完善本地大模型部署工具,提供更强大的本地智能服务;
💡 跨平台开发 :进一步完善跨平台开发工具,让易语言开发的物联网应用覆盖更多设备;
💡 低代码开发 :进一步完善低代码开发平台,降低物联网开发的学习门槛;
💡 开源社区建设:进一步加强开源社区建设,吸引更多开发者参与中文编程IoT开发;
🎉 易语言开发从入门到精通的物联网开发补充篇·本地应用与嵌入式设备联动实战·深度技术融合,至此结束!愿你在易语言的世界里,不断学习,不断进步,开发出更多"小而美、快而灵、智而强"的物联网产品和服务!
