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

前言

接下来将实现一个远程继电器,实时远程控制和查询的开关状态。用 5v 直流电控制 220v 交流电。

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

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

在开始之前在电脑中建立一个文件夹 /project

效果

远程继电器模块

材料准备

硬件

名称数量
nodemcu D11
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

image.png

代码分析

代码比较简单,只是实现了控制开关和查询的按钮。

硬件代码

/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 中直接搜索安装,因为不是一个作者写的,不一样。下载地址:https://github.com/Links2004/arduinoWebSockets 。

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

ArduinoJson 依赖直接在 Arduino IDE 中搜索安装即可

IDE 中开发版选择和 nodemcu 一样:

image.png

引脚接线

D1继电器led
5VDC+
3.3VNO
GNDDC-负极
D6IN
COM正极

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

d1 引脚图.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/670346.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数模混合芯片设计中的修调技术是什么?

一、修调目的 数模混合芯片需要修调技术主要是因为以下几个原因&#xff1a; 工艺偏差&#xff08;Process Variations&#xff09;&#xff1a; 半导体制造过程中存在不可避免的工艺偏差&#xff0c;如晶体管尺寸、阈值电压、电阻和电容值等&#xff0c;这些参数的实际值与…

2024年海南省三支一扶报名指南,照片要求

2024年海南省三支一扶报名指南&#xff0c;照片要求 一、考试时间安排&#xff1a; 报名时间&#xff1a;6月1日8:00至6月7日18:00 准考证打印时间&#xff1a;6月17日8:00 考试时间&#xff1a;6月22日 二、招聘人数 海南省计划招募390名高校毕业生

Golang | Leetcode Golang题解之第125题验证回文串

题目&#xff1a; 题解&#xff1a; func isPalindrome(s string) bool {s strings.ToLower(s)left, right : 0, len(s) - 1for left < right {for left < right && !isalnum(s[left]) {left}for left < right && !isalnum(s[right]) {right--}if l…

Golang | Leetcode Golang题解之第126题单词接龙II

题目&#xff1a; 题解&#xff1a; //bfsdfs(如果是双向bfs&#xff0c;效果会更好) func findLadders(beginWord string, endWord string, wordList []string) [][]string {//字典表&#xff08;将wordList中的单词放入hash表中&#xff0c;方便查找&#xff09;dict:make(m…

学习笔记——网络参考模型——TCP/IP模型(物理层)

一、TCP/IP模型-物理层 1、数据传输(交换)的形式 (1)电路交换 特点&#xff1a;通信双方独占通信链路。 优点&#xff1a;数据传输时延小&#xff0c;适用于实时通信&#xff1b;数据按序发送&#xff0c;不存在失序问题&#xff1b;适合模拟信号和数字信号传输。 缺点&am…

指纹采集技术

目录 1.概述 1.1 捺印油墨采集 1.2 现场指纹提取 1.3 在线指纹采集 2. 指纹采集器的关键技术指标 2.1 采集面积 2.2 分辨率 2.3 图像质量 2.4 耐用性 1.概述 最早的指纹采集技术是油墨法&#xff0c;至少已经有上百年的历史。1990年代出现了活体指纹采集器&#xff0c…

国内AI工具访问量第一的竟然是它?!不是Kimi,也不是文心一言

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

spoon基础使用-第一个转换文件

新建一个转换&#xff0c;文件->新建->转换&#xff0c;也可以直接ctralN新建。 从右边主对象树拖拽一个输入->表输入&#xff1b;输出->文本文档输出&#xff1b;也可以直接在搜索框搜素表输入、文本文档输出。 双击表输入新建一个数据库连接 确定后就可以在S…

AndroidStudio中debug.keystore的创建和配置使用

1.如果没有debug.keystore,可以按照下面方法创建 首先在C:\Users\Admin\.android路径下打开cmd窗口 之后输入命令:keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -validity 10000 输入两次密码(密码不可见,打码处随便填写没关系) 2.在build…

JavaScript拖拽API的简单使用

演示效果&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><st…

基于JSP的九宫格日志网站

你好呀&#xff0c;我是学长猫哥&#xff01;如果有需求可以文末加我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;浏览器/服务器&#xff08;B/S&#xff09;结构 系统展示 首页 管理员功能模块 用户功能模块 摘要 本…

2024年春季学期《算法分析与设计》练习13

问题 A: 菱形图案 [命题人 : admin] 时间限制 : 1.000 sec 内存限制 : 128 MB提交问题列表 解决: 1041提交量: 2744统计 题目描述 KiKi学习了循环&#xff0c;BoBo老师给他出了一系列打印图案的练习&#xff0c;该任务是打印用“*”组成的菱形图案。 输入 多组输入&…

[数据集][目标检测]脑肿瘤检测数据集VOC+YOLO格式9787张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;9787 标注数量(xml文件个数)&#xff1a;9787 标注数量(txt文件个数)&#xff1a;9787 标注…

基础—SQL—DQL(数据查询语言)聚合函数

一、引言 一般情况下&#xff0c;我们在进行分组查询的时候&#xff0c;一般配合着聚合函数来进行操作&#xff0c;所以先了解和学习聚合函数再学习和操作分组查询。 二、DQL—聚合函数 1、介绍 聚合函数指的是讲一列数据作为一个整体&#xff0c;进行纵向的计算。 2、常见…

VMWare下安装Linux虚拟机(图文)

大家好&#xff0c;在当今科技发展迅速的时代&#xff0c;虚拟化技术在企业和个人用户中变得越来越普遍。VMware作为一款领先的虚拟化软件&#xff0c;为用户提供了在单一物理计算机上运行多个操作系统的能力&#xff0c;为开发、测试和运维等任务提供了便利。在这篇文章中&…

Linux Shell:lsof 命令

Linux Shell&#xff1a;lsof 命令 在 Linux 系统中&#xff0c;lsof&#xff08;list open files&#xff09;命令是一款非常有用的工具。它可以列出当前系统中所有打开的文件&#xff0c;并且还能显示与这些文件相关的进程信息。因为在 Linux 中&#xff0c;一切皆文件&…

汽车IVI中控开发入门及进阶(二十五):CVBS视频流

前言: AHD和CVBS是两种视频格式,在车载摄像头中,有支持传统CVBS模拟视频的摄像头,也有支持新的高分辨率AHD格式的摄像头。 CVBS视频是经典的模拟视频格式,在视频经常显示在小型监视器上的车辆上仍然最受欢迎。如果想要车辆的最大分辨率,可选择AHD格式,即高分辨率模拟视…

生成随机图片

package com.zhuguohui.app.lib.tools;/*** Created by zhuguohui* Date: 2024/6/1* Time: 13:39* Desc:获取随机图片*/ public class RandomImage {// static final String url "https://picsum.photos/%d/%d?random%d";static final String url "https://…

Java进阶学习笔记34——Arrays类

Arrays&#xff1a; 用来操作数组的工具类。 解释说明&#xff1a; 只要知道代码这么写就可以了。 package cn.ensource.d5_arrays;import java.util.Arrays; import java.util.function.IntToDoubleFunction;public class ArraysTest1 {public static void main(String[] arg…

“论软件的可靠性评价”必过范文,突击2024软考高项论文

论文部分 摘要 2023年03月&#xff0c;我参与了某艺术品公司线上拍卖管理平台的研发。该项目的目标是建立一个互联网在线拍卖平台&#xff0c;用户可以通过手机或PC浏览器进入拍卖平台&#xff0c;对喜欢的拍品进行参拍出价。平台提供了在线支付、在线出价、保证金管理、拍品…