文章首发个人博客 高小哥漫谈前端
一、缘起
聊聊为什么要写这篇文章?要做这个事情?
从大学学习编程到如今从事大前端相关工作已快 7 年;其中正式工作 5 年;期间一直在做软件开发相关的事情;包括但不限于前端、跨端框架、工程化、低代码、Node 以及老早之前学习的客户端开发; 但一直以来都想去碰一碰硬件相关的东西,直到有这么一个契机。
那就是我家养了几只猫,每次铲屎都很麻烦,在网上买自动铲屎机 便宜的几百块,贵的几千块。于是为了解放我的双手;就想了解一下怎么通过软件控制硬件。
对于我们这种硬件门外汉,想要一步步去做控制最大的难题是知识体系的缺失; 完全不知道如何下手,于是我开始了(google/bilibili/xxx)求学之路;和在校学习的不同之处在于,工作后的我们没有大量的时间去学习硬件相关知识;所以我的学习路线是:
- 先看看别人怎么做
- 再自己动手
- 不懂的专业名词 + 知识就搜索学习
# 二、聊聊技术选型
我的目标是做一个自动铲屎机,但是自动铲屎机里面比较重要的是:一个远程控制开关去控制电机的运行; 下面我们就来聊聊远程控制开关的选型;
-
远程: 要实现远程功能,就需要单片机 和 手机进行通信; IOT 设备 的通信和传统互联网使用的协议有所不同,IOT 更多的使用
MQTT
协议MQTT 是一种基于发布/订阅模式的轻量级物联网消息传输协议 ,可以用极少的代码和带宽为联网设备提供实时可靠的消息服务,它适用于硬件资源有限的设备及带宽有限的网络环境。因此,MQTT 协议广泛应用于物联网、移动互联网、智能硬件、车联网、电力能源等行业。
-
控制: 要控制开关的打开和闭合,需要一块单片机, 网上比较火/教程比较多的是 esp8266 和 树莓派;
-
esp8266: ESP8266 的核心是一块 Diamond Standard 106Micro 控制器,一个低功耗的 32 位 RISC 控制器。它拥有 GPIO、I2C、ADC、SPI、PWM 等模块,你可以用它做一些微控制器能做的事。它还可以支持 AP(Access Point)、STA(Station)、AP+STA 共存模式,并且使用高效的 AT 指令
esp8266 比较便宜,但是对于我来说不太适用,因为目前只支持 使用 windows 将代码烧录到 板子上;而我家只有一台 mac;
-
树莓派: 树莓派是目前很火的一款微控制器,有很多型号(2B/3B/4B/400/Pico/Pico W/Pico WH 等);由于目前我并不需要蓝牙/音视频/GPU 等等功能,只需要简单的控制 + wifi 能力,于是选择了 Pico W; 其中选择 Pico 系列还有一个原因是 400 等型号现在太贵了 得 800 左右,而 pico 系列才 100 多
-
-
开关: 要实现电路的打开和闭合一方面需要单片机输出控制信号,另一方面需要 一个继电器 去控制电路。
# 三、开始实现
# 1. 整体架构
- 继电器 IN 和树莓派 GPIO 引脚相连
- 树莓派连接好 wifi 之后通过 建立 mqtt 连接,通过 mqtt broker 同其他客户端进行相连。
- 移动端 发送 一个 payload,树莓派接收到之后将其转换成 0/1, 从而控制继电器的输入口; 从而控制灯的打开和关闭
# 2. 继电器+树莓派连接图
先了解一下 继电器(我这里使用的是 SRD-05VDC)的构成
继电器的输入端:
- DC+: 接树莓派 3.3v 引脚
- DC-: 接 GND 引脚
- IN: 可以通过控制高电平/低电平 从而控制开关的开闭,我这里接的是 GPIO 12 引脚
继电器的输出端:
- NO: 继电器常开接口
- COM: 继电器公共接口
- NC: 继电器常闭接口
如果 线路为
NO + COM
那么 开关默认是打开的(也就是没有通电),如果是NC + COM
那么 开关默认是闭合的(也就是默认通电)
我这里选择 NO + COM
的方式。
# 3. mqtt broker
我这里使用的是 EMQX (opens new window):
第一步:使用 docker 启动 broker
# 一键启动 mqtt broker 支持 mqtt/mqtts/ws/wss 协议
docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 8084:8084 -p 8883:8883 -p 18083:18083 emqx/emqx:latest
第二步:在 EMQX 建立 mqtt 链接
由于前端不支持 mqtt 协议,所以需要再建立一个 websocket 用于可视化展示; 若你使用的事客户端开发也可以不建立 websocket 连接,直接使用上述的 mqtt 连接即可
# 4. 树莓派连接 wifi + mqtt
import time
import network
from machine import Pin
from umqtt.simple import MQTTClient
# 初始化 继电器的输入口,使用GPIO 19引脚
relay = Pin(19, mode = Pin.OUT)
# wifi 名称和密码
wifi_ssid = "" # wifi 名称
wifi_password = "" # wifi 谬吗
mqtt_host = "xxxxx" # 自己的ip,我这里就使用的是电脑的ip
mqtt_username = "" # emqt username
mqtt_password = "" # emqt password
mqtt_receive_topic = "switch-status" # MQTT要接收的topic
# 连接wifi
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(wifi_ssid, wifi_password)
while wlan.isconnected() == False:
print('Waiting for connection...')
time.sleep(1)
print("Connected to WiFi")
# MQTT Client 随机id
mqtt_client_id = "mrgaogang"
mqtt_client = MQTTClient(
client_id=mqtt_client_id,
server=mqtt_host,
user=mqtt_username,
password=mqtt_password)
def mqtt_subscription_callback(topic, message):
print (f'Topic {topic} received message {message}')
if message == b'open':
print("relay ON")
relay.value(1)
elif message == b'close':
print("relay OFF")
relay.value(0)
mqtt_client.set_callback(mqtt_subscription_callback)
mqtt_client.connect()
mqtt_client.subscribe(mqtt_receive_topic)
print("Connected and subscribed")
try:
while True:
print(f'Waiting for messages on {mqtt_receive_topic}')
mqtt_client.wait_msg()
except Exception as e:
print(f'Fairelay to wait for MQTT messages: {e}')
finally:
mqtt_client.disconnect()
# 5. 移动端连接 mqtt
由于移动端(web 端)不支持 mqtt 协议,所以需要使用 ws 协议去链接
// core.ts 核心文件
import { connect, MqttClient } from "mqtt";
export enum MQTT_TOPIC {
SWITCH_STATUS = "switch-status",
}
export type MQTTUseType = {
publish?: (payload: string) => void;
on?: (callback: (data: string) => void) => void;
};
export class MQTTController {
private static instance: MQTTController;
private status: "pending" | "success" | "closed";
private client: MqttClient | undefined;
private connectStatus: Promise<MQTTController> | undefined;
static create() {
if (!MQTTController.instance) {
MQTTController.instance = new MQTTController();
}
return MQTTController.instance;
}
constructor() {
this.status = "pending";
this.connect();
}
private isReady() {
return this.status === "success";
}
private async connect() {
const client = connect("ws://localhost:8083/mqtt");
this.status = "pending";
this.connectStatus = new Promise<MQTTController>((resolve, reject) => {
client.on("connect", () => {
console.log("mqtt 连接成功");
this.status = "success";
this.client = client;
resolve(this);
});
});
}
async use(topic: MQTT_TOPIC): Promise<MQTTUseType> {
if (this.status === "pending") {
await this.connectStatus;
} else if (this.status === "closed") {
await this.connect();
}
const publish = (payload: string) => {
if (this.isReady()) {
this.client?.publish(topic, payload);
}
};
const on = (callback: (data: string) => void) => {
if (this.isReady()) {
this.client?.subscribe(topic);
this.client?.on("message", (tpc, msg) => {
if (tpc === topic) {
callback(msg.toString());
}
});
}
};
return {
publish,
on,
};
}
end() {
if (this.isReady()) {
this.status = "closed";
this.client?.end();
}
}
}
页面控制
const mqtt = useRef<MQTTUseType>({});
// 建立链接,并使用对应的topic
mqtt.current = await MQTTController.create().use(MQTT_TOPIC.SWITCH_STATUS);
// 监听对应topic 的数据
mqtt.current?.on?.((data) => {
console.log("receive data", data);
setChecked(data === "open");
});
// 发送打开消息
mqtt.current?.publish?.("open");
// 发送关闭消息
mqtt.current?.publish?.("close");
完整代码地址: github.com/MrGaoGang/r…
# 四、效果展示
有兴趣的同学可以 使用抖音扫一扫 查看具体视频
原文链接:https://juejin.cn/post/7233065830957891644 作者:MrGaoGang