基于MQTT协议实现远程控制的"智能"车
智能,但不完全智能
虽然我不觉得这玩意儿有啥智能的,但都这么叫就跟着叫喽。
时隔好几天才写的
其实在写这篇博文的时候我已经在做升级了,并且已经到了中后期阶段了。
主要是业余时间做着玩,看时间了。
规格 & 实拍
- 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
简单点
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://iot.b0x0.cn/"+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");
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相关的东西,麻了我都
基于MQTT协议实现远程控制的"智能"车的更多相关文章
- Android消息推送(二)--基于MQTT协议实现的推送功能
国内的Android设备,不能稳定的使用Google GCM(Google Cloud Messageing)消息推送服务. 1. 国内的Android设备,基本上从操作系统底层开始就去掉了Googl ...
- 通过集群的方式解决基于MQTT协议的RabbitMQ消息收发
在完成了基于AMQP协议的RabbitMQ消息收发后,我们要继续实现基于MQTT协议的RabbitMQ消息收发. 由于C#的RabbitMQ.Client包中只实现了基于AMQP协议的消息收发功能的封 ...
- 云巴:基于MQTT协议的实时通信编程模型
概要 有人常问,云巴实时通信系统到底提供了一种怎样的服务,与其他提供推送或 IM 服务的厂商有何本质区别.其实,从技术角度分析,云巴与其它同类厂商都是面向开发者的通信服务,宏观的编程模型都是大同小异, ...
- 基于MQTT协议进行应用开发
官方协议有句如下的话来形容MQTT的设计思想: "It is designed for connections with remote locations where a "sma ...
- 基于mqtt协议实现手机位置跟踪
Mqtt协议是物联网领域的一个标准协议,具有轻巧,对设备,带宽要求低,可靠稳定的特点,适合用来实现手机定位跟踪功能. 目前我初步搭建起来了整个可运行的框架,大致为如下思路:1.手机端通过位置服务,获取 ...
- 基于MQTT协议的云端proxy远程登陆
这篇文件是建立在一下两篇文章基础上完成的 很多重复的内容不会在这章提到 https://www.cnblogs.com/y-c-y/p/11685405.html telnet协议相关 https:/ ...
- 深度剖析MQTT协议的整个通信流程
http://www.elecfans.com/d/587483.html MQTT,目前物联网的最主要的协议,基本所有收费的云平台都是基于MQTT协议,比如机智云,和所有的开放云平台比如中国移动的o ...
- mqtt协议系统设计参考
作者:极寒链接:https://zhuanlan.zhihu.com/p/28525517来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 回顾自己的工作经历最遗憾的是没 ...
- 基于Http协议订阅发布系统设计
基于Http协议订阅发布系统设计 --物联网系统架构设计 1,订阅发布(subscriber-publisher) 订阅发布模式最典型的应用场景就是消息系统的设计.在消息系统的架构中 ...
随机推荐
- python读取、写入txt文本内容
转载:https://blog.csdn.net/qq_37828488/article/details/100024924 python常用的读取文件函数有三种read().readline().r ...
- wpa_supplicant启动出错rfkill: Cannot open RFKILL control device
在板子是调试网络,千辛万苦把wpa_supplicant及其依赖都移植编译进来了,在板子上调试启动的时候启动报错了 D/wpa_supplicant( 1152): wpa_supplicant v2 ...
- python的分支结构
python分支结构 if结构 python的 if 选择分支结构的基础语法如下,需要注意的是判断条件后面是半角的分号,它的作用相当于Java中的小括号 if 判断条件 : 代码块 elif 判断条件 ...
- Centos7上安装Ubuntu容器
1.再次之前我们要先装好docker,在上一篇我已经给出了教程,没有安装好的快去看看吧! 2.这里我们使用的是linux系统,所有在线安装是最简便的方法了.我们可以从国内拉取dockerhub镜像,这 ...
- DDL数据定义语言
DDL数据定义语言 (一)概述 DDL(Data Definition Language):数据定义语言,用来定义数据库对象,库.表.列等:创建.删除.修改 库,表结构.主要分为操作数据库的DDL和操 ...
- MySQL高级篇 | MySQL逻辑架构
思维导图 架构逻辑视图 每个虚线框为一层,总共三层. 第一层:连接层,所包含的服务并不是MySQL所独有的技术.它们都是服务于C/S程序或者是这些程序所需要的 :连接处理,身份验证,安全性等等. 第二 ...
- RabbitMQ(六)消息幂等性处理
一.springboot整合rabbitmq 我们需要新建两个工程,一个作为生产者,另一个作为消费者.在pom.xml中添加amqp依赖: <dependency> <groupId ...
- 什么是齐博x1标签
X系列的标签跟V系列的标签区别还是很大的.在V系列的时候,只有一种很简单的标签比如$label[XXXX]以前的标签相对现在的来说太简单的点,所以在功能上也比较受限.X系列目前有几下几种标签 {qb: ...
- ES6基础知识(Generator 函数)
1.next().throw().return() 的共同点 next().throw().return()这三个方法本质上是同一件事,可以放在一起理解.它们的作用都是让 Generator 函数恢复 ...
- 【linux系统】命令学习(四)统计命令
sort 排序 -b 忽略开头的空白符 -f 将小写字母看做为大写字母 -h 根据存储容量排序(kb mb hb )从小到大 -n 按数字排序 -o 把结果写入文件 -r 以相反的顺序来排序 -t 指 ...