前言

接下来将完成一个长途继电器,实时长途操控和查询的开关状况。用 5v 直流电操控 220v 交流电。

硬件上: 运用 nodemcu D1JQC-3FF-S-Z 继电器

软件上: 运用 nodejs 作为服务端,和 html 作为客户端。

在开端之前在电脑中树立一个文件夹 /project

作用

长途继电器模块

资料预备

硬件

名称 数量
nodemcu D1 1
JQC-3FF-S-Z 继电器 1
杜邦线 若干
led(3.3v) 1
面包板 1

服务端依靠

/project/package.json

{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.20.2",
    "express": "^4.18.2",
    "socket.io": "^4.7.1"
  }
}

IDE

Arduino

vsCode

服务端完成

/project/index.js


const bodyParser = require("body-parser");
const express = require("express");
const path = require("path");
const app = express();
const http = require("http");
const { Server } = require("socket.io");
const server = http.createServer(app);
const port = 3005;
/**
 * 一切开关的状况记载
*/
const switchs = {
    // 台灯开关
    desklamp: "0",
};
app.use(bodyParser.json({ limit: "50mb" }));
// for parsing application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }));
// 前端静态服务,包括文件和页面
app.use(express.static(path.join(__dirname,"./client")))
app.all("*",function (req,res,next) {
    res.set({
        "Content-Type": "text/plain",
        "Access-Control-Allow-Credentials": "true",
        "Access-Control-Allow-Headers": "X-Requested-With,Content-Type,token,authorization",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Origin": req.headers.origin,
        "Access-Control-Allow-Methods": "POST,GET",
        "Content-Type": "application/json",
    });
    next();
});
const io = new Server(server,{
    allowEIO3: true,
    credentials: true,
    cors: {},
}); 
io.on("connection",(socket) => {
    try {
        console.log("一个新的 scoket 衔接"); 
        /**
         * 用户衔接即推送信息
         */ 
        io.to(socket.id).emit("switchs",switchs);
        /**
         * 查询恳求,返回一切开关状况
        */
        socket.on("query",function (data) {
            console.log(`[query]`, data ? data : "");
            io.to(socket.id).emit("switchs",switchs);
        });
        /**
         * 设置开关状况
         * { id: "desklamp", data: "0" | "1" }
        */
        socket.on("set",function (body) {
            const id = body.id;
            const status = body.data;
            if(!id) {
                console.log('没有传入id,更新失利!')
                return
            };
            switchs[id] = status;
            console.log(`[set]`, id, body.data); 
            // 更新完后播送
            io.emit("switchs",switchs);
        });
        // 产生错误时触发
        socket.on("error",function (err) {
            console.log("socket 错误:",err);
        });
    } catch (err) {
        console.log("socket 错误:",err);
        io.to(socket.id).emit("error",`系统异常:${err}`);
    }
});
server.listen(port,() => {
    console.log(`服务正在运转: http://localhost:${port}`);
});

代码分析

  1. 首先是运用 3005 端口启动了一个服务器.
  2. ./client 设置为静态文件夹,该文件夹中将会放置客户端页面
  3. 运用 socket.io 插件完成一个 ws 服务。
  4. 监听 query 音讯,用于返回当前开关状况
  5. 监听 set 音讯,用于改动开关状况

代码中 switchs 写为目标形式是为了便利后期持续扩展其他继电器。

客户端完成

/project/client/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="info" style="margin-bottom: 16px; width: 100%;height: 300px;border: 1px solid red;"> </div>
    <button id="btn">手动查询</button>
    <button id="open">开灯</button>
    <button id="close">关灯</button>
</body>
</html>
<script type="module">
    import { io } from "https://cdn.socket.io/4.4.1/socket.io.esm.min.js";
    const server = "ws://" + location.host;
    const socket = io(server, {
        reconnectionDelayMax: 10000,
        auth: {},
        query: { }
    });
    function onSwitchs(data) {
        console.log("[switchs]接收:", data);
        document.querySelector("#info").innerHTML = JSON.stringify(data, null, 4)
    }
    socket.on("switchs", onSwitchs);
    const btn = document.querySelector("#btn");
    const obtn = document.querySelector("#open");
    const cbtn = document.querySelector("#close");
    btn.addEventListener("click", function () {
        // 发送输入框数据
        socket.emit("query")
    })
    obtn.addEventListener("click", function () {
        // 发送输入框数据
        socket.emit("set", { id: "desklamp", data: "1" })
    })
    cbtn.addEventListener("click", function () {
        // 发送输入框数据
        socket.emit("set", { id: "desklamp", data: "0" })
    })
</script>

页面如下:

浏览器访问:127.0.0.1:3005

远程继电器模块实现(nodemcu D1 + 继电器)

代码分析

代码比较简单,仅仅完成了操控开关和查询的按钮。

硬件代码

/project/main/main.ino

#include <ESP8266WiFi.h>
#include <Arduino.h>
#include <ArduinoJson.h>
#include <WebSocketsClient.h>
#include <SocketIOclient.h>
#include <Hash.h>
#ifndef STASSID
#define STASSID "oldwang"
#define STAPSK "wifi密码"
#endif
// 继电器引脚 高电平断开,低电平接通
int jdqPot = 12;
// 开关状况   "0"封闭, "1" 开启
String switchStatus = "0";
const char* ssid = STASSID;
const char* password = STAPSK;
const char* websockets_server_host = "192.168.xx.xx"; //"www.xxx.top";  // 服务器名
const uint16_t websockets_server_port = 3005;         // 端口
SocketIOclient socketIO; 
StaticJsonDocument<1024> iomsg;
void socketIOEvent(socketIOmessageType_t type, uint8_t* payload, size_t length) {
  switch (type) {
    case sIOtype_DISCONNECT:
      Serial.printf("[IOc] Disconnected!\n");
      break;
    case sIOtype_CONNECT:
      Serial.printf("[IOc] Connected to url: %s\n", payload);
      // join default namespace (no auto join in Socket.IO V3)
      socketIO.send(sIOtype_CONNECT, "/");
      break;
    case sIOtype_EVENT:
      // 获取到服务的音讯后打印出来
      Serial.printf("[IOc] get event: %s\n", payload);
      deserializeJson(iomsg, &(*payload));
      if (iomsg[0] == "switchs") { 
        if (iomsg[1]["desklamp"] == "1") {
          // 开灯
          switchStatus = "1";
        }else{
          // 关灯 
          switchStatus = "0";
        }
      } 
      break;
    case sIOtype_ACK:
      Serial.printf("[IOc] get ack: %u\n", length);
      hexdump(payload, length);
      break;
    case sIOtype_ERROR:
      Serial.printf("[IOc] get error: %u\n", length);
      hexdump(payload, length);
      break;
    case sIOtype_BINARY_EVENT:
      Serial.printf("[IOc] get binary: %u\n", length);
      hexdump(payload, length);
      break;
    case sIOtype_BINARY_ACK:
      Serial.printf("[IOc] get binary ack: %u\n", length);
      hexdump(payload, length);
      break;
  }
}
void setup() {
  // Serial.begin(115200);
  Serial.begin(9600);
  Serial.setDebugOutput(true);
  pinMode(jdqPot, OUTPUT);
  Serial.println();
  Serial.println();
  Serial.print("wifi 衔接中 ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.println("socketIO begin: ");
  // server address, port and URL
  socketIO.begin(websockets_server_host, websockets_server_port, "/socket.io/?EIO=4");
  // event handler
  socketIO.onEvent(socketIOEvent);
}
unsigned long messageTimestamp = 0;
void loop() {
  socketIO.loop();
  uint64_t now = millis();
  //  1分钟向服务器发送一次心跳音讯
  if (now - messageTimestamp > 60000) {
    // if (now - messageTimestamp > 20000) {
    messageTimestamp = now;
    sendMsg("heartbeatMonitoring", "hi");
    // 测试翻开开关
    // sendMsg("set", "1");
    // digitalWrite(jdqPot, LOW);
  }
  // 依据状况操控继电器状况
  if (switchStatus == "0") {
    // 封闭
    digitalWrite(jdqPot, LOW);
  } else {
    // 接通
    digitalWrite(jdqPot, HIGH);
  }
}
// 发送信息的办法
// @msgName 服务端监听的事情名
// @msg     会当到 data 中进行发送
void sendMsg(char msgName[100], char msg[100]) {
  // 创立一个 scoket.io json 音讯
  DynamicJsonDocument doc(1024);
  JsonArray array = doc.to<JsonArray>();
  // 音讯名称
  // ps: socket.on('event_name', ....
  array.add(msgName);
  // 添加音讯内容
  JsonObject param = array.createNestedObject();
  param["data"] = msg;
  param["id"] = "desklamp";
  // JSON to String (serializion)
  String output;
  serializeJson(doc, output);
  // Send event
  socketIO.sendEVENT(output);
  Serial.print("send:");
  Serial.println(output);
}

注意

arduinoWebSockets 这个依靠必须去 github 下载,不能在 Arduino IDE 中直接查找装置,因为不是一个作者写的,不相同。下载地址:github.com/Links2004/a… 。

下载后的依靠放到IDE首选项中设置的地址中即可,还不明白就百度一下装置 arduino 依靠库。

ArduinoJson 依靠直接在 Arduino IDE 中查找装置即可

IDE 中开发版选择和 nodemcu 相同:

远程继电器模块实现(nodemcu D1 + 继电器)

引脚接线

D1 继电器 led
5V DC+
3.3V NO
GND DC- 负极
D6 IN
COM 正极

附上一张 nodemcu d1 引脚和 arduino 引脚的对应图

远程继电器模块实现(nodemcu D1 + 继电器)