ESP32+Mqtt+Node.js+Vue3 做工控界面

文章目录

1.主机安装 mosquitto

作为mqtt的服务端,转发消息

https://mosquitto.org/download/

2.EPS32端的代码

注意配置WIFI, mqtt_server 是安装了mosquitto,同局域网下的主机ip

cpp 复制代码
#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_ADS1X15.h>
#include <Wire.h>
#include <ArduinoJson.h>

// -------------------- 用户配置(必改) --------------------
const char* ssid = "TSK";
const char* password = "tsk_251010";
const char* mqtt_server = "192.168.234.34";
const int mqtt_port = 1883;

// -------------------- MQTT 主题 --------------------
const char* status_topic = "esp32/status";
const char* control_topic = "esp32/control";

// -------------------- 引脚定义 --------------------
// 数字输入 (外部下拉,高电平有效)
const int DI_PINS[4] = {40, 39, 38, 37};
// 数字输出
const int DO_PINS[4] = {7, 15, 16, 17};
// PWM 输出
const int PWM_PINS[4] = {47, 21, 20, 19};
const int PWM_FREQ = 1000;
const int PWM_RESOLUTION = 14;
const int PWM_MAX_VALUE = (1 << PWM_RESOLUTION) - 1;  // 16383

// I2C (ADS1115)
#define I2C_SDA 9
#define I2C_SCL 10
#define LED_BUILTIN 13

// -------------------- 全局对象 --------------------
WiFiClient espClient;
PubSubClient client(espClient);
Adafruit_ADS1115 ads;

// -------------------- 状态变量 --------------------
bool diState[4] = {0};
bool lastDIState[4] = {0};     // 用于边沿检测
bool doState[4] = {0};
float aiValue[4] = {0.0};
int pwmPercent[4] = {0};

// -------------------- 前向声明 --------------------
void setupWiFi();
void reconnectMQTT();
void callback(char* topic, byte* payload, unsigned int length);
void readInputs();
void publishStatus();
void handleControl(JsonDocument& doc);
void setPWMDuty(int index, int percent);

// ==================== 初始化 ====================
void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println("\n=== ESP32 PLC 4444 启动 ===");

  // ----- 配置 GPIO -----
  Serial.println("初始化数字输入 (外部下拉, 高电平有效)");
  for (int i = 0; i < 4; i++) {
    pinMode(DI_PINS[i], INPUT);        // 外部下拉,无需内部上拉
    diState[i] = digitalRead(DI_PINS[i]);
    lastDIState[i] = diState[i];
    Serial.printf("  DI%d (GPIO%d) 初始状态: %d\n", i+1, DI_PINS[i], diState[i]);
  }

  Serial.println("初始化数字输出 (初始LOW)");
  for (int i = 0; i < 4; i++) {
    pinMode(DO_PINS[i], OUTPUT);
    digitalWrite(DO_PINS[i], LOW);
    doState[i] = 0;
    Serial.printf("  DO%d (GPIO%d) -> LOW\n", i+1, DO_PINS[i]);
  }

  Serial.println("初始化 PWM 输出");
  for (int i = 0; i < 4; i++) {
    pinMode(PWM_PINS[i], OUTPUT);
    analogWriteResolution(PWM_PINS[i], PWM_RESOLUTION);
    analogWriteFrequency(PWM_PINS[i], PWM_FREQ);
    analogWrite(PWM_PINS[i], 0);
    Serial.printf("  PWM%d (GPIO%d) -> 0%%\n", i+1, PWM_PINS[i]);
  }

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  // ----- I2C & ADS1115 -----
  Wire.begin(I2C_SDA, I2C_SCL);
  if (!ads.begin()) {
    Serial.println("❌ ADS1115 初始化失败,请检查接线");
    while (1) delay(1000);
  }
  ads.setGain(GAIN_ONE);
  Serial.println("✅ ADS1115 初始化成功");

  // ----- WiFi & MQTT -----
  setupWiFi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);

  Serial.println("✅ 系统就绪\n");
}

// ==================== WiFi 连接 ====================
void setupWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("📶 连接 WiFi");
  int retry = 0;
  while (WiFi.status() != WL_CONNECTED && retry < 30) {
    delay(500);
    Serial.print(".");
    retry++;
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\n✅ WiFi 已连接,IP: " + WiFi.localIP().toString());
  } else {
    Serial.println("\n❌ WiFi 连接失败,请检查 SSID/密码");
  }
}

// ==================== MQTT 重连 ====================
void reconnectMQTT() {
  while (!client.connected()) {
    Serial.print("🔄 尝试连接 MQTT...");
    if (client.connect("ESP32_PLC_4444")) {
      Serial.println("✅ 已连接");
      client.subscribe(control_topic);
      digitalWrite(LED_BUILTIN, HIGH);
    } else {
      Serial.print("❌ 失败, rc=");
      Serial.print(client.state());
      Serial.println(",5秒后重试");
      digitalWrite(LED_BUILTIN, LOW);
      delay(5000);
    }
  }
}

// ==================== MQTT 回调 ====================
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("📨 收到指令 [");
  Serial.print(topic);
  Serial.print("] ");
  for (unsigned int i = 0; i < length; i++) Serial.print((char)payload[i]);
  Serial.println();

  DynamicJsonDocument doc(256);
  DeserializationError error = deserializeJson(doc, payload, length);
  if (error) {
    Serial.print("❌ JSON 解析失败: ");
    Serial.println(error.c_str());
    return;
  }
  handleControl(doc);
}

// ==================== 控制处理 ====================
void handleControl(JsonDocument& doc) {
  bool updateDO = false, updatePWM = false;

  if (doc.containsKey("do")) {
    JsonArray arr = doc["do"].as<JsonArray>();
    if (arr.size() == 4) {
      for (int i = 0; i < 4; i++) {
        doState[i] = arr[i].as<bool>();
        digitalWrite(DO_PINS[i], doState[i] ? HIGH : LOW);
      }
      updateDO = true;
    } else {
      Serial.println("⚠️ do 数组长度必须为 4");
    }
  }

  if (doc.containsKey("pwm")) {
    JsonArray arr = doc["pwm"].as<JsonArray>();
    if (arr.size() == 4) {
      for (int i = 0; i < 4; i++) {
        int p = arr[i].as<int>();
        p = constrain(p, 0, 100);
        pwmPercent[i] = p;
        setPWMDuty(i, p);
      }
      updatePWM = true;
    } else {
      Serial.println("⚠️ pwm 数组长度必须为 4");
    }
  }

  if (updateDO || updatePWM) {
    publishStatus();   // 立即反馈
  }
}

// ==================== PWM 设置 ====================
void setPWMDuty(int index, int percent) {
  int raw = map(percent, 0, 100, 0, PWM_MAX_VALUE);
  analogWrite(PWM_PINS[index], raw);
}

// ==================== 读取输入 ====================
void readInputs() {
  // 数字输入:直接读取,高电平为1,低电平为0
  for (int i = 0; i < 4; i++) {
    diState[i] = digitalRead(DI_PINS[i]);
  }

  // 模拟量
  for (int i = 0; i < 4; i++) {
    int16_t raw = ads.readADC_SingleEnded(i);
    float voltage = raw * 4.096 / 32767.0;
    float current = voltage / 150.0 * 1000.0;
    current = constrain(current, 0.0, 20.0);
    aiValue[i] = current;
  }
}

// ==================== 发布状态 ====================
void publishStatus() {
  DynamicJsonDocument doc(256);
  JsonArray di = doc.createNestedArray("di");
  JsonArray ai = doc.createNestedArray("ai");
  JsonArray doArr = doc.createNestedArray("do");
  JsonArray pwmArr = doc.createNestedArray("pwm");

  for (int i = 0; i < 4; i++) {
    di.add(diState[i] ? 1 : 0);
    ai.add(aiValue[i]);
    doArr.add(doState[i] ? 1 : 0);
    pwmArr.add(pwmPercent[i]);
  }

  String jsonStr;
  serializeJson(doc, jsonStr);
  if (client.publish(status_topic, jsonStr.c_str())) {
    Serial.println("📤 发布状态: " + jsonStr);
  } else {
    Serial.println("❌ 发布失败");
  }
}

// ==================== 主循环 ====================
void loop() {
  if (!client.connected()) {
    reconnectMQTT();
  }
  client.loop();

  // 读取所有输入
  readInputs();

  // 检测DI边沿变化
  bool diChanged = false;
  for (int i = 0; i < 4; i++) {
    if (diState[i] != lastDIState[i]) {
      diChanged = true;
      Serial.printf("[DI变化] GPIO%d %d -> %d\n", DI_PINS[i], lastDIState[i], diState[i]);
      lastDIState[i] = diState[i];
    }
  }

  // 如果DI有变化,立即发布
  if (diChanged) {
    publishStatus();
  }

  // 每秒定时发布全量状态(确保模拟量定期上报)
  static unsigned long lastPublish = 0;
  if (millis() - lastPublish >= 1000) {
    lastPublish = millis();
    publishStatus();
  }

  delay(10);  // 微小延时,减轻CPU负载
}

表示正在运行的状态

3.在电脑端查看

在电脑端查看:使用MQTTX

https://mqttx.app/zh/web

相关推荐
TE-茶叶蛋6 小时前
Node.js-Phase 1 学习总结:CLI 文件管理系统
学习·node.js
前端之虎陈随易14 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·vue.js·人工智能·typescript·node.js
meilindehuzi_a1 天前
从零开始:用原生 Node.js 徒手拆解 RAG 与向量检索底层原理
node.js·rag
炒毛豆1 天前
ai全栈-node.js相关的学习之路(草稿版)
学习·node.js
meilindehuzi_a1 天前
解密 MCP 协议:如何用 Node.js 从零手写一个本地文件读取 MCP 服务器
运维·服务器·node.js
hoLzwEge4 天前
pnpm vs npm:新一代包管理器的范式革命
前端框架·node.js
麻辣凉茶4 天前
给阿嬤一封来自云端的信(上)
前端·node.js
codingWhat5 天前
能效平台设计方案(打通gitlab和飞书)
后端·node.js·koa
见过夏天7 天前
Node.js 常用命令全攻略
node.js