基于MQTT协议实现远程控制通常涉及以下几个步骤:### 1. 环境搭建首先,你需要搭建一个MQTT服务器,常用的MQTT服务器有:- **Mosquitto**- **EMQX**- **HiveMQ**### 2. 选择编程语言根据你的需求选择合适的编

摘要:智能,但不完全智能 虽然我不觉得这玩意儿有啥智能的,但都这么叫就跟着叫喽。 时隔好几天才写的 其实在写这篇博文的时候我已经在做升级了,并且已经到了中后期阶段了。 主要是业余时间做着玩,看时间了。 规格 & 实拍 ES
智能,但不完全智能 虽然我不觉得这玩意儿有啥智能的,但都这么叫就跟着叫喽。 时隔好几天才写的 其实在写这篇博文的时候我已经在做升级了,并且已经到了中后期阶段了。 主要是业余时间做着玩,看时间了。 规格 & 实拍 ESP32 远程控制 两驱动轮+一万向轮 所需硬件 继电器*4 或 双路电机2驱动模块 *1 电机*2 轮子*2 万向轮*1 电源*1 MCU *1 导线若干 (我就是因为没买够线只能用杜邦线了) …… 推荐使用电机驱动模块,或者自己用mos管。 直接使用双路继电器控制的缺点有: 体积大 不支持pwm调速 等等等 ESP32端开发 由于我目前正在升级的版本代码也是基于这个版本代码进行开发的,所以现在说的是我的新版本代码,从代码中体现出来的就是多了两个轮子,Copy时注意删减,虽然不影响。 开发基于: PaltformIO IDE 引入MQTT库 256dpi/MQTT@^2.5.0 这个库是老版本的车身控制用的,现在新版本换了个库,因为这个库不支持发送uint8_t数据。但是这个库,简单好用。 推荐使用库 (用了,但没测试): knolleary/PubSubClient@^2.8 继电器信号IO口管理 /** 右轮双路继电器 */ // 14 右轮一号继电器IO串口号 (吸合前进) int RIGHT_ONE_A = 14; // 12 右轮二号继电器IO串口号 (吸合后退) int RIGHT_TWO_A = 12; //右轮一号继电器IO串口号 (吸合前进) int RIGHT_ONE_B = 14; //右轮二号继电器IO串口号 (吸合后退) int RIGHT_TWO_B = 12; //===================================================== // 17 左轮二号继电器IO串口号 (吸合前进) int LEFT_ONE_A = 17; // 16 左轮二号继电器IO串口号 (吸合后退) int LEFT_TWO_A = 16; //左轮二号继电器IO串口号 (吸合前进) int LEFT_ONE_B = 17; //左轮二号继电器IO串口号 (吸合后退) int LEFT_TWO_B = 16; 继电器状态管理 /* 已更换使用基于内置mos管的驱动模块 */ //右轮1号继电器吸合状态 boolean RIGHT_ONE_A_STATUS = false; //右轮2号继电器吸合状态 boolean RIGHT_TWO_A_STATUS = false; //右后轮1号继电器吸合状态 boolean RIGHT_ONE_B_STATUS = false; //右后轮2号继电器吸合状态 boolean RIGHT_TWO_B_STATUS = false; //左轮1号继电器吸合状态 boolean LEFT_ONE_A_STATUS = false; //左轮2号继电器吸合状态 boolean LEFT_TWO_A_STATUS = false; //左后轮1号继电器吸合状态 boolean LEFT_ONE_B_STATUS = false; //左后轮2号继电器吸合状态 boolean LEFT_TWO_B_STATUS = false; //采用差速转向 //右转向动力锁 boolean RIGHT_TURN_LOCK = false; //左转向动力锁 boolean LEFT_TURN_LOCK = false; 继电器注意事项 继电器这里要说一下,有的像我一样的萌新一开始不知道继电器要怎么用,知道个大概逻辑却不知道怎么接线,所以这里提一下, 敲黑板 继电器接口有: VCC、GND、IN; NC、COM、ON; 六个接口 这里要注意的是: VCC、GND是给继电器供电用的!只是给继电器供电用!控制开合后VCC并不会连接到COM; ON或NC接用电器的电源正极; COM接到用电器,这时候对于NC、ON来说COM是负极,对于用电器是正极; 用电器负极接电源负极,形成通路; 比如电源正极接到了ON,那么继电器吸合后的电路如下: 电源正极——ON——COM——用电器——电源负极 鬼知道我经历了什么,问了学这个专业朋友都表示”我没用过“,淦哦 核心控制 在loop中调用; 该控制逻辑可实现有: 前进/后退 转弯时弯内侧轮反转缩小转弯半径 前进/后退同时转弯 /** * @brief 根据状态值为继电器输出高低电平 */ void relayOnStatus() { if ((RIGHT_ONE_A_STATUS || LEFT_TURN_LOCK) && RIGHT_TURN_LOCK == false) { digitalWrite(RIGHT_ONE_A, HIGH); digitalWrite(RIGHT_ONE_B, HIGH); } else { digitalWrite(RIGHT_ONE_A, LOW); digitalWrite(RIGHT_ONE_B, LOW); } if (RIGHT_TWO_A_STATUS || RIGHT_TURN_LOCK) { digitalWrite(RIGHT_TWO_A, HIGH); digitalWrite(RIGHT_TWO_B, HIGH); } else { digitalWrite(RIGHT_TWO_A, LOW); digitalWrite(RIGHT_TWO_B, LOW); } if ((LEFT_ONE_A_STATUS || RIGHT_TURN_LOCK) && LEFT_TURN_LOCK == false) { digitalWrite(LEFT_ONE_A, HIGH); digitalWrite(LEFT_ONE_B, HIGH); } else { digitalWrite(LEFT_ONE_A, LOW); digitalWrite(LEFT_ONE_B, LOW); } if (LEFT_TWO_A_STATUS || LEFT_TURN_LOCK) { digitalWrite(LEFT_TWO_A, HIGH); digitalWrite(LEFT_TWO_B, HIGH); } else { digitalWrite(LEFT_TWO_A, LOW); digitalWrite(LEFT_TWO_B, LOW); } } MQTT使用 MQTTClient client; WiFiClient net; //mqtt接收到消息的回调 void messageReceived(String &topic, String &payload) { //这个方法里的allRun()这种的函数我就不多说了,只是控制一下继电器状态管理那里变量的值 Serial.println("incoming: " + topic + " - " + payload); if (payload.equals("\"run\"")) { allRun(); } if (payload.equals("\"stop\"")) { allStop(); } if (payload.equals("\"back\"")) { allBack(); } if (payload.equals("\"leftStart\"")) { turnLeftStart(); } if (payload.equals("\"rightStart\"")) { turnRightStart(); } if (payload.equals("\"leftStop\"")) { turnLeftStop(); } if (payload.equals("\"rightStop\"")) { turnRightStop(); } } //mqtt连接封装函数 void connect() { while (!client.connect("car-client")) { Serial.print("."); delay(1000); } Serial.println("\nconnected!"); } void setup() { client.begin("***.***.***.***", net); client.onMessage(messageReceived); connect(); } void loop() { //mqtt消息处理 client.loop(); if (!client.connected()) { connect(); } //控制核心逻辑 relayOnStatus(); } Java 服务器端开发 可以说是一个中转,可以不要,只是可以避免控制端直接在ESP32端订阅的主题中直接发布控制命令; 引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-mqtt</artifactId> <version>5.3.2.RELEASE</version> </dependency> MQTT Client工厂 小声bb: copy来的 package cn.b0x0.carserver.common.factory; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; public class MqttFactory { private static MqttClient client; /** * 获取客户端实例 * 单例模式, 存在则返回, 不存在则初始化 */ public static MqttClient getInstance() { if (client == null) { init(); } return client; } /** * 初始化客户端 */ public static void init() { try { client = new MqttClient("tcp://***.***.***.***:1883", "car-****-" + System.currentTimeMillis()); // MQTT配置对象 MqttConnectOptions options = new MqttConnectOptions(); // 设置自动重连, 其它具体参数可以查看MqttConnectOptions options.setAutomaticReconnect(true); if (!client.isConnected()) { client.connect(options); } } catch (MqttException e) { throw new RuntimeException("MQTT: 连接消息服务器失败"); } } } MQTT Util package cn.b0x0.carserver.common.util; import cn.b0x0.carserver.common.factory.MqttFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.paho.client.mqttv3.IMqttMessageListener; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import java.nio.charset.StandardCharsets; public class MqttUtil { /** * 发送消息 * @param topic 主题 * @param data 消息内容 */ public static void send(String topic, Object data) { // 获取客户端实例 MqttClient client = MqttFactory.getInstance(); ObjectMapper mapper = new ObjectMapper(); try { // 转换消息为json字符串 String json = mapper.writeValueAsString(data); MqttMessage message = new MqttMessage(json.getBytes(StandardCharsets.UTF_8)); //小车控制要求,消息级别固定2 message.setQos(2); client.publish(topic, message); } catch (JsonProcessingException | MqttException ignored) { } } /** * 订阅主题 * @param topic 主题 * @param listener 消息监听处理器 */ public static void subscribe(String topic, IMqttMessageListener listener) { MqttClient client = MqttFactory.getInstance(); try { client.subscribe(topic, listener); } catch (MqttException ignored) { } } } Controller 简单点(WebSocket、MQTT都可以) package cn.b0x0.carserver.controller; import cn.b0x0.carserver.common.util.MqttUtil; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/") public class CarControlController { @RequestMapping("/all/run") public String run(){ MqttUtil.send("car-client","run"); return "success"; } @RequestMapping("/all/stop") public String stop(){ MqttUtil.send("car-client","stop"); return "success"; } @RequestMapping("/all/back") public String back(){ MqttUtil.send("car-client","back"); return "success"; } @RequestMapping("/turn/left/start") public String leftStart(){ MqttUtil.send("car-client","leftStart"); return "success"; } @RequestMapping("/turn/right/start") public String rightStart(){ MqttUtil.send("car-client","rightStart"); return "success"; } @RequestMapping("/turn/left/stop") public String leftStop(){ MqttUtil.send("car-client","leftStop"); return "success"; } @RequestMapping("/turn/right/stop") public String rightStop(){ MqttUtil.send("car-client","rightStop"); return "success"; } } 控制端开发 使用的web页面进行控制,主要是跨平台,因为我不会写IOSApp这些。 之前web页面是要在ESP32运行的,所以基本都使用了原生JS,现在没这个必要了 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ESP32 WebController</title> </head> <script src="https://unpkg.com/mqtt@2.18.8/dist/mqtt.min.js"></script> <style> * { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } /* ========================== */ body { height: 100vh; width: 100%; } #title { flex-grow: 4; width: 100%; display: flex; flex-direction: row; } .cam-but-left { display: flex; flex-direction: column; flex-grow: 1; padding: 10px; } .cam-but-right { flex-grow: 1; display: flex; flex-direction: row; } .cam-video { flex-grow: 5; background-color: #8b8b8b; } .cam-but{ background-color: #dedede; margin: 5px; height: 100%; width: 100%; } .controller-content { height: 100%; width: 100%; display: flex; display: -webkit-flex; flex-direction: column; justify-content: flex-start; align-items: center; } #controller-but { width: 100%; flex-grow: 2; display: flex; display: -webkit-flex; flex-direction: row; justify-content: center; align-items: center; } #runAndBack { height: 100%; width: 100%; display: flex; flex-direction: column; } #leftAndRight { height: 100%; width: 100%; display: flex; display: -webkit-flex; flex-direction: row; } #run { /* border-left: 385px solid transparent; border-right: 385px solid transparent; border-bottom: 350px solid #d9d9d9; background-color: #dedede; */ background-color: #dedede; height: 100%; /* width: 100%; */ margin: 10px; } #back { background-color: #dedede; height: 100%; margin: 10px; } #left { background-color: #dedede; height: 100%; width: 100%; margin: 10px; margin-right: 0px; } #right { background-color: #dedede; height: 100%; width: 100%; margin: 10px; } #right:active { background-color: #d5d5d5; } .controller-but { border-radius: 5px; } </style> <body> <div class="controller-content"> <div id="title"> <!-- <h1>Web Controller</h1> 控制按钮功能只可意会不可言传 --> <div class="cam-but-left"> <div class="cam-but" id="cam-but-left-up"> </div> <div class="cam-but" id="cam-but-left-down"> </div> </div> <div class="cam-video"> </div> <div class="cam-but-right"> <div class="cam-but" id="cam-but-right-left"> </div> <div class="cam-but" id="cam-but-right-right"> </div> </div> </div> <div id="controller-but"> <div id="runAndBack"> <div id="run" class="controller-but"></div> <div id="back" class="controller-but"></div> </div> <div id="leftAndRight"> <div id="left" class="controller-but"></div> <div id="right" class="controller-but"></div> </div> </div> </div> </body> <script type="text/javascript"> const options = { // 认证信息 clientId: 'car-***-****' } const client = mqtt.connect('ws://**.**.**.**:8083/mqtt', options); client.subscribe('car-cam-images-view'); client.on('message', function (topic, message) { //在这里处理 var p1 = message.toString(); console.log(p1); }) client.on('reconnect', (error) => { console.log('正在重连:', error) }) client.on('connect', (error) => { console.log('连接成功:', error) }) client.on('error', (error) => { console.log('连接失败:', error) }) function createXHR() { if (typeof XMLHttpRequest != "undefined") { return new XMLHttpRequest(); } else if (typeof ActiveXObject != "undefined") { if (typeof arguments.callee.activeXString != "string") { var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"], i, len; for (i = 0, len = versions.length; i < len; i++) { try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (ex) { //跳过 } } } return new ActiveXObject(arguments.callee.activeXString); } else { throw new Error("No XHR object available."); } } function send(url) { var xhr = createXHR(); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { console.log(xhr.responseText); } else { console.log("Request was unsuccessful: " + xhr.status); } } }; xhr.open("get", "http://**.**.**/"+url, true); xhr.send(null); } var runBut = document.getElementById("run"); var backBut = document.getElementById("back"); var leftBut = document.getElementById("left"); var rightBut = document.getElementById("right"); var camLeftUp = document.getElementById("cam-but-left-up"); var camLeftDown = document.getElementById("cam-but-left-down"); var camRightLeft = document.getElementById("cam-but-right-left"); var camRightRight = document.getElementById("cam-but-right-right"); //这里都使用了h5移动端的点击事件,操作体验感强,如果可以的话还能做一下虚拟摇杆 camLeftUp.addEventListener("touchstart", function(event) { event.preventDefault(); send("cam/left/up"); runBut.style.cssText = 'background-color: #d5d5d5;' console.log("camLeftUp touchstart"); }); camLeftDown.addEventListener("touchstart", function(event) { event.preventDefault(); send("cam/left/down"); runBut.style.cssText = 'background-color: #d5d5d5;' console.log("camLeftDown touchstart"); }); camRightLeft.addEventListener("touchstart", function(event) { event.preventDefault(); send("cam/right/left"); runBut.style.cssText = 'background-color: #d5d5d5;' console.log("camRightLeft touchstart"); }); camRightRight.addEventListener("touchstart", function(event) { event.preventDefault(); send("cam/right/right"); runBut.style.cssText = 'background-color: #d5d5d5;' console.log("camRightRight touchstart"); }); runBut.addEventListener("touchstart", function(event) { event.preventDefault(); send("all/run"); runBut.style.cssText = 'background-color: #d5d5d5;' console.log("runBut touchstart"); }); leftBut.addEventListener("touchstart", function(event) { event.preventDefault(); send("turn/left/start"); leftBut.style.cssText = 'background-color: #d5d5d5;' console.log("leftBut touchstart"); }); backBut.addEventListener("touchstart", function(event) { event.preventDefault(); send("all/back"); backBut.style.cssText = 'background-color: #d5d5d5;' console.log("backBut touchstart"); }); rightBut.addEventListener("touchstart", function(event) { event.preventDefault(); send("turn/right/start"); rightBut.style.cssText = 'background-color: #d5d5d5;' console.log("rightBut touchstart"); }); runBut.addEventListener("touchend", function() { send("all/stop"); runBut.style.cssText = 'background-color: #dedede;' console.log("runBut touchend"); }); leftBut.addEventListener("touchend", function() { send("turn/left/stop"); leftBut.style.cssText = 'background-color: #dedede;' console.log("leftBut touchend"); }); backBut.addEventListener("touchend", function() { send("all/stop"); backBut.style.cssText = 'background-color: #dedede;' console.log("backBut touchend"); }); rightBut.addEventListener("touchend", function() { send("turn/right/stop"); rightBut.style.cssText = 'background-color: #dedede;' console.log("rightBut touchend"); }); camLeftUp.addEventListener("touchend", function() { send("cam/left/stop"); rightBut.style.cssText = 'background-color: #dedede;' console.log("camLeftUp touchend"); }); camLeftDown.addEventListener("touchend", function() { send("cam/left/stop"); rightBut.style.cssText = 'background-color: #dedede;' console.log("camLeftDown touchend"); }); camRightLeft.addEventListener("touchend", function() { send("cam/right/stop"); rightBut.style.cssText = 'background-color: #dedede;' console.log("camRightLeft touchend"); }); camRightRight.addEventListener("touchend", function() { send("cam/right/stop"); rightBut.style.cssText = 'background-color: #dedede;' console.log("camRightRight touchend"); }); </script> </html> 麻了,复制代码复制麻了 待我新版本搞好,到时候用git分享这些。 因为公司在用其他的,家里电脑刚换没多久,都没装git相关的东西,麻了我都