智能,但不完全智能

虽然我不觉得这玩意儿有啥智能的,但都这么叫就跟着叫喽。

时隔好几天才写的

其实在写这篇博文的时候我已经在做升级了,并且已经到了中后期阶段了。

主要是业余时间做着玩,看时间了。

规格 & 实拍

  • 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协议实现远程控制的"智能"车的更多相关文章

  1. Android消息推送(二)--基于MQTT协议实现的推送功能

    国内的Android设备,不能稳定的使用Google GCM(Google Cloud Messageing)消息推送服务. 1. 国内的Android设备,基本上从操作系统底层开始就去掉了Googl ...

  2. 通过集群的方式解决基于MQTT协议的RabbitMQ消息收发

    在完成了基于AMQP协议的RabbitMQ消息收发后,我们要继续实现基于MQTT协议的RabbitMQ消息收发. 由于C#的RabbitMQ.Client包中只实现了基于AMQP协议的消息收发功能的封 ...

  3. 云巴:基于MQTT协议的实时通信编程模型

    概要 有人常问,云巴实时通信系统到底提供了一种怎样的服务,与其他提供推送或 IM 服务的厂商有何本质区别.其实,从技术角度分析,云巴与其它同类厂商都是面向开发者的通信服务,宏观的编程模型都是大同小异, ...

  4. 基于MQTT协议进行应用开发

    官方协议有句如下的话来形容MQTT的设计思想: "It is designed for connections with remote locations where a "sma ...

  5. 基于mqtt协议实现手机位置跟踪

    Mqtt协议是物联网领域的一个标准协议,具有轻巧,对设备,带宽要求低,可靠稳定的特点,适合用来实现手机定位跟踪功能. 目前我初步搭建起来了整个可运行的框架,大致为如下思路:1.手机端通过位置服务,获取 ...

  6. 基于MQTT协议的云端proxy远程登陆

    这篇文件是建立在一下两篇文章基础上完成的 很多重复的内容不会在这章提到 https://www.cnblogs.com/y-c-y/p/11685405.html telnet协议相关 https:/ ...

  7. 深度剖析MQTT协议的整个通信流程

    http://www.elecfans.com/d/587483.html MQTT,目前物联网的最主要的协议,基本所有收费的云平台都是基于MQTT协议,比如机智云,和所有的开放云平台比如中国移动的o ...

  8. mqtt协议系统设计参考

    作者:极寒链接:https://zhuanlan.zhihu.com/p/28525517来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 回顾自己的工作经历最遗憾的是没 ...

  9. 基于Http协议订阅发布系统设计

      基于Http协议订阅发布系统设计 --物联网系统架构设计   1,订阅发布(subscriber-publisher)      订阅发布模式最典型的应用场景就是消息系统的设计.在消息系统的架构中 ...

随机推荐

  1. QT判断文件/目录是否存在

    最近在用qt写一个ui,遇到删除sd卡中的文件失败情况,有些时候是存在删除链表里面的文件在sd卡上已经不存在了,导致失败,以为我的链表是定时刷新的,但是文件是实时更新会同步覆盖的.这样就存在可能上一秒 ...

  2. Shadertoy 教程 Part 4 - 绘制多个2D图形和混入

    Note: This series blog was translated from Nathan Vaughn's Shaders Language Tutorial and has been au ...

  3. Redis去重方法

    目录 1.基于 set 2.基于 bit 3.基于 HyperLogLog 4. 基于bloomfilter 这篇文章主要介绍了Redis实现唯一计数的3种方法分享,本文讲解了基于SET.基于 bit ...

  4. Linux&C———进程间通信

    管道和有名管道 消息队列 共享内存 信号 套接字 由于进程之间的并不会像线程那样共享地址空间和数据空间,所以进程之间就必须有自己特有的通信方式,这篇博客主要介绍自己了解到的几种进程之间的通信方式,内容 ...

  5. 【java+selenium3】自动化截图 (十四)

    一.截图 1. Firefox浏览器截图 FirefoxDriver firefoxDriver = new FirefoxDriver(); firefoxDriver.getScreenshotA ...

  6. js 事件流和事件冒泡阻止

    js 事件流和事件冒泡阻止 事件流 当浏览器发展到第四代的时候(IE4与Netscape4)浏览器开发团队遇到一个有意思的的问题: 页面的哪一部分会拥有某个特定的事件? 比如在纸上画上一组同心圆,如果 ...

  7. selenium截屏操作(也支持截长图)

    1.常用的可能是谷歌和火狐做自动化在抛异常的时候可以截屏保存 from selenium import webdriver br=webdriver.Chrome() br.maximize_wind ...

  8. 设计模式学习-使用go实现适配器模式

    适配器模式 定义 代码实现 优点 缺点 适用范围 代理.桥接.装饰器.适配器4种设计模式的区别 参考 适配器模式 定义 适配器模式的英文翻译是Adapter Design Pattern.顾名思义,这 ...

  9. feignclient各种使用技巧说明

    FeignClient常见用法 常规的FeignClient的创建与使用我相信只要使用过spring cloud全家桶的套件的基本上都是非常熟悉了,我们只需定义一个interface,然后定义相关的远 ...

  10. vue自定义指令实例使用(实例说明自定义指令的作用)

    在写vue项目的时候,我们经常需要对后台返回的数据进行大量的渲染操作,其中就包含了大量的对特殊数据的进一步处理,比如说时间戳.图片地址.特殊数据显示等等特殊数据处理改进. 其实遇到这种情况,通过Vue ...