远程继电器模块实现(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 URLsocketIO.begin(websockets_server_host, websockets_server_port, "/socket.io/?EIO=4");// event handlersocketIO.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 eventsocketIO.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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Scrapy vs. Beautiful Soup | 网络抓取教程 2024

网络爬虫是任何想要从网上收集数据用于分析、研究或商业智能的人必备的技能。Python中两个最受欢迎的网络爬虫工具是Scrapy和Beautiful Soup。在本教程中&#xff0c;我们将比较这些工具&#xff0c;探索它们的功能&#xff0c;并指导你如何有效地使用它们。此外&#xff0c;我…

精雕细琢,B 端 UI 设计展典雅风范

精雕细琢&#xff0c;B 端 UI 设计展典雅风范

CC工具箱使用指南:【山西省村规结构调整表(亦求长生亦求你)】

一、简介 群友定制工具。 工具根据输入的用地图层&#xff0c;生成山西村规的结构调整表。 和一般的用地表有些不一样的地方是&#xff0c;现状和规划字段都在同一个图层里。 并且还有一个【村庄名称】的字段&#xff0c;可以将多个村庄放在一个图层中&#xff0c;一次性生…

【网络研究观】-20240531

战争揭开美国武器优势的面纱 随着俄军在哈尔科夫地区稳步推进&#xff0c;乌克兰战争对美国国防机器而言是一场灾难&#xff0c;这一点越来越明显&#xff0c;这不仅是因为我们的援助未能挽救乌克兰的撤退和可能的失败。更重要的是&#xff0c;这场战争无情地暴露了我们国防体…

Nginx一个端口代理多个vue项目,通过不同路由转到不同系统,反向代理Apache进行文件处理

需求&#xff1a;由于一些因素限制&#xff0c;需要尽可能的少开放外部端口访问&#xff0c;这里将多个vue项目通过一个nginx端口进行代理&#xff0c;由不同的路由来确定访问哪些项目&#xff0c;apache同理 nginx代理多个vue项目 安装和配置nginx的基础教程这里就不写了&…

thinkphp6 queue队列的maxTries自定义

前景需求&#xff1a;在我们用队列的时候发现maxtries的个数时255次&#xff0c;这个太影响其他队列任务 我目前使用的thinkphp版本是6.1 第一部定义一个新的类 CustomDataBase&#xff08;我用的mysql数据库存放的队列&#xff09; 重写__make 和createPlainPayload方法 …

前端功能拖拽篇:dragleave拖拽事件穿透子元素的优雅解决方案

文章目录 前情提要应用场景⭐拖拽改变元素位置⭐拖拽改变目标区域的样式⭐dragleave拖拽事件穿透子元素的优雅解决方案 最后 前情提要 在前端工作过程中&#xff0c;避免不了要接触各种技术&#xff0c;拖拽就是其中一个&#xff0c;大部分关于拖拽的基础知识和Demo都在MDN中写…

day-36 删除链表的倒数第 N 个结点

思路 首先计算出链表的长度&#xff0c;然后删除第n个节点即可&#xff0c;但要注意考虑特殊情况 解题方法 特殊情况&#xff1a;1.删除节点为最后一个节点 2.删除节点为头结点 Code /*** Definition for singly-linked list.* public class ListNode {* int val;* …

Go-知识并发控制Context

Go-知识并发控制Context 1. 介绍2. 实现原理2.1 接口定义2.2 Deadline()2.3 Done()2.4 Err()2.5 Value() 3. 空 context4. cancelCtx4.1 Done()4.2 Err()4.3 cancel()4.4 WithCancel4.5 例子4.6 总结 5. timerCtx5.1 Deadline5.2 cancel5.3 WithDeadline5.4 WithTimeout5.5 例子…

HTML+CSS+JS 熊猫登录表单

效果演示 实现了一个可爱的熊猫登录界面,页面背景使用了渐变色,熊猫的头部和身体使用了圆形和椭圆形的边框,使用了CSS的伪元素和阴影效果来实现熊猫的手和脚。登录框使用了flex布局,包括用户名和密码的输入框和登录按钮,使用了CSS的过渡效果和伪类来实现输入框的动态效果。…

【LeetCode刷题】二分查找:山脉数组的峰顶索引、寻找峰值

【LeetCode刷题】Day 13 题目1&#xff1a;852.山脉数组的峰顶索引思路分析&#xff1a;思路1&#xff1a;暴力枚举O(N)思路2&#xff1a;二分查找O(logN) 题目2&#xff1a;162.寻找峰值思路分析&#xff1a;思路1&#xff1a;二分查找O(logN) 题目1&#xff1a;852.山脉数组的…

(二刷)代码随想录第15天|层序遍历 226.翻转二叉树 101.对称二叉树2

层序遍历 10 102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; 代码随想录 (programmercarl.com) 综合代码&#xff1a; class Solution{public List<List<Integer>> resList new ArrayList<List<Integer>>();public List<List<…

24年海南三支一扶报名流程步骤详解

一、考试时间安排&#xff1a; 报名时间&#xff1a;6月1日8:00至6月7日18:00 准考证打印时间&#xff1a;6月17日8:00 考试时间&#xff1a;6月22日 二、招聘人数 海南省计划招募390名高校毕业生 三、笔试内容&#xff1a; 笔试内容&#xff1a;综合能力和素质&#xff08;满分…

【设计模式】JAVA Design Patterns——Iterator(迭代器模式)

&#x1f50d;目的 提供一种在不暴露其基础表示的情况下顺序访问聚合对象的元素的方法。 &#x1f50d;解释 真实世界例子 百宝箱包含一组魔法物品。有多种物品&#xff0c;例如戒指&#xff0c;药水和武器。可以使用藏宝箱提供的迭代器按类型浏览商品。 通俗描述 容器可以提供…

【AIGC-数字人】V-Express:渐进式训练的数字人视频生成技术

介绍 在人像视频生成领域&#xff0c;使用单张图像生成人像视频已经变得越来越普遍。一种常见的方法涉及利用生成模型来增强适配器以实现受控生成。然而&#xff0c;控制信号的强度可能会有所不同&#xff0c;包括文本、音频、图像参考、姿态、深度图等。其中&#xff0c;较弱的…

Vue.js - 生命周期与工程化开发【0基础向 Vue 基础学习】

文章目录 Vue 的生命周期Vue 生命周期的四个阶段Vue 生命周期函数&#xff08;钩子函数 工程化开发 & 脚手架 Vue CLI**开发 Vue 的两种方式&#xff1a;**脚手架目录文件介绍项目运行流程组件化开发 & 根组件App.vue 文件&#xff08;单文件组件&#xff09;的三个组成…

LeetCode 图-岛屿问题

图 图的基本知识基本概念图的类型相关术语 图的存储 LeetCode 相关题目岛屿问题岛屿的最大面积岛屿的周长 图的基本知识 基本概念 图的类型 无向图有向图加权图 相关术语 顶点边路径路径长度环负权环连通性顶点的度入度出度 图的存储 邻接矩阵存储&#xff1a;是用一个二…

真机调试 Error:系统错误,xxx exceed max limit 2MB

我们在使用微信开发者工具开发小程序、小游戏等应用时&#xff0c;往往会点击“真机调试”&#xff0c;微信扫描查看真实情况。 但是会出现下面的报错提示&#xff0c;是因为主包体积超过了2MB。 小程序有体积和资源加载限制&#xff0c;在微信小程序中&#xff0c;每个包不能…

vue3简单快速实现主题切换功能

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《vue3实战》 目录 内容概要 实现步骤 1.定义不同主题的css样式变量 2.入口main.ts中引入这个样式文件 3.主题样式css变量引用 4.设置默认主题样式 5.实现点击按钮主题切换 总结 最近发现了一个巨牛的人工智…

【Linux-buildroot,】

Linux-buildroot, ■ buildroot■ 1、简介■ 2、下载■ 2、编译■ 问题一&#xff1a;buildroot 编译的时候会先从网上下载所需的软件源码&#xff0c;下载cmake-3.8.2.tar.gz或下载很慢的情况 ■ buildroot-构建根文件系统■ 1、配置 buildroot■ 2、■ 3、 ■ buildroot-构建…