1.流程概述
2.开始搭建!
3.
https://mqttx.app/zh/downloads
0.点击左侧的加号,开始新建连接。
一共需要填写几个参数:
1.名称:想起什么名称都可以
2.服务器地址:填入你的云服务器外网IP
3.用户名 和 密码:填入你刚才登录 EMQX 的 用户名和密码(就是默认用户名是 admin 、默认密码是 public 的那个)
3.1 添加设备 和 实体
这个步骤需要注意的就是,topic 里面的内容应该是 unique_id 的值加上 /config。如果不明白,看我下面的 json代码 和 图片 来理解。照葫芦画瓢即可。
homeassistant/sensor/HA/HA-HYDC-A-60-01-currentTemp/config
{
"unique_id": "HA-HYDC-A-60-01-currentTemp",
"name": "currentTemp",
"icon": "mdi:thermometer",
"state_topic": "HA-HYDC-A-60-01/currentTemp/state",
"json_attributes_topic": "HA-HYDC-A-60-01/currentTemp/attributes",
"unit_of_measurement": "℃",
"device": {
"identifiers": "HYDC-A-60-01",
"manufacturer": "辽宁鸿昱石油机械制造有限公司",
"model": "HA",
"name": "HYDC-A-60-01",
"sw_version": "1.0"
}
}
3.2 查看设备和实体
去 HomeAssistant 里面 配置 -> MQTT 就能看见设备和实体啦。(我这里已经添加了实体,并且实验着赋值了,你不是这样的界面很正常。照葫芦画瓢,自己去改参数的内容,这很重要)
3.3 测试着使用 MQTTX 给设备的实体(设备上具体的传感器) 赋值
像下面这段代码,topic 是 HA-HYDC-A-60-01/currentTemp/state,指的就是你刚刚创建的设备上的实体 的 值。
我现在给它赋值34,就能在 HomeAssistant 里面看到这个值。
HA-HYDC-A-60-01/currentTemp/state
35
3.4 给此设备 添加 第2、3、4…个实体的操作步骤
我们刚才在创建这个设备的时候,也创建了它的第一个实体,现在我们要创建第二个,请注意看下面的代码和上面代码的相同和不同之处。你会发现 他们的 “device” 词条是一样的,但是上面的内容改变了。刚才我们创建的传感器叫“currentTemp”,而这次我们创建的传感器叫“setTemp”。而且标题也改变了。
在发送下面这段代码之后,会发现 HomeAssistant 里面出现了第二个传感器,和刚才是一样的,我们创建好第二个传感器之后也可以通过刚才的方式给它赋值。
homeassistant/sensor/HA/HA-HYDC-A-60-01-setTemp/config
{
"unique_id": "HA-HYDC-A-60-01-setTemp",
"name": "setTemp",
"icon": "mdi:thermometer",
"state_topic": "HA-HYDC-A-60-01/setTemp/state",
"json_attributes_topic": "HA-HYDC-A-60-01/setTemp/attributes",
"unit_of_measurement": "℃",
"device": {
"identifiers": "HYDC-A-60-01",
"manufacturer": "辽宁鸿昱石油机械制造有限公司",
"model": "HA",
"name": "HYDC-A-60-01",
"sw_version": "1.0"
}
}
HA-HYDC-A-60-01/setTemp/state
35
4.1 给 ESP32S3 写入程序
给这段程序起个名字吧,随便。我的是 MQTTX_WIFI.py
请注意,你需要更改这里面的几个地方,我在下面代码中句子的后面加上了 注释“请修改:xxx”
import time
from machine import Pin
import network
from umqttsimple import MQTTClient
def do_wifi_connect():
wifi_name = 'TP-LINK_FEEC' # 请修改:改成你的 WIFI 名称
wifi_password = 'hbz12345' # 请修改:改成你的 WIFI 密码
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('Connecting to WIFI...')
print('WIFI name == ',wifi_name)
print('WIFI password == ',wifi_password)
wlan.connect(wifi_name, wifi_password)
i = 1
while not wlan.isconnected():
print("Connecting for ",i,' seconds...')
i += 1
time.sleep(1)
print('network config:', wlan.ifconfig())
def sub_cb(topic, msg): # 回调函数,收到服务器消息后会调用这个函数
print(topic, msg)
# ---- 控制 指令 --------
if topic.decode("utf-8") == "ledctl" and msg.decode("utf-8") == "on":
led_pin.value(1)
elif topic.decode("utf-8") == "ledctl" and msg.decode("utf-8") == "off":
led_pin.value(0)
# ---- 监控 指令 --------
# 1. 联网
do_wifi_connect()
# 2. 创建mqt
YzyMqttClient = MQTTClient("YzyMqttClient", "182.93.213.218") # 请修改:可以选择将 YzyMqttClient 改成你想要的对象名,然后把 182.93.213.218 改成你的 云服务器外网 IP
YzyMqttClient.set_callback(sub_cb) # 设置回调函数
YzyMqttClient.connect() # 建立连接
YzyMqttClient.subscribe(b"ledctl") # 监控ledctl这个通道,接收控制命令
# ---- 添加 --------
# 3. 创建LED对应Pin对象
led_pin = Pin(1, Pin.OUT)
# ---- 添加 --------
while True:
i = 0
YzyMqttClient.check_msg()
time.sleep(0.5)
YzyMqttClient.publish("hello","my name is esp32s3...",i)
time.sleep(1)
i += 1
4.2 导入依赖包 umqttsimple.py
自己创建一个 umqttsimple.py 文件,内容如下(不需要自己往里面加什么):
import usocket as socket
import ustruct as struct
from ubinascii import hexlify
class MQTTException(Exception):
pass
class MQTTClient:
def __init__(
self,
client_id,
server,
port=0,
user=None,
password=None,
keepalive=0,
ssl=False,
ssl_params={},
):
if port == 0:
port = 8883 if ssl else 1883
self.client_id = client_id
self.sock = None
self.server = server
self.port = port
self.ssl = ssl
self.ssl_params = ssl_params
self.pid = 0
self.cb = None
self.user = user
self.pswd = password
self.keepalive = keepalive
self.lw_topic = None
self.lw_msg = None
self.lw_qos = 0
self.lw_retain = False
def _send_str(self, s):
self.sock.write(struct.pack("!H", len(s)))
self.sock.write(s)
def _recv_len(self):
n = 0
sh = 0
while 1:
b = self.sock.read(1)[0]
n |= (b & 0x7F) << sh
if not b & 0x80:
return n
sh += 7
def set_callback(self, f):
self.cb = f
def set_last_will(self, topic, msg, retain=False, qos=0):
assert 0 <= qos <= 2
assert topic
self.lw_topic = topic
self.lw_msg = msg
self.lw_qos = qos
self.lw_retain = retain
def connect(self, clean_session=True):
self.sock = socket.socket()
addr = socket.getaddrinfo(self.server, self.port)[0][-1]
self.sock.connect(addr)
if self.ssl:
import ussl
self.sock = ussl.wrap_socket(self.sock, **self.ssl_params)
premsg = bytearray(b"\x10\0\0\0\0\0")
msg = bytearray(b"\x04MQTT\x04\x02\0\0")
sz = 10 + 2 + len(self.client_id)
msg[6] = clean_session << 1
if self.user is not None:
sz += 2 + len(self.user) + 2 + len(self.pswd)
msg[6] |= 0xC0
if self.keepalive:
assert self.keepalive < 65536
msg[7] |= self.keepalive >> 8
msg[8] |= self.keepalive & 0x00FF
if self.lw_topic:
sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
msg[6] |= self.lw_retain << 5
i = 1
while sz > 0x7F:
premsg[i] = (sz & 0x7F) | 0x80
sz >>= 7
i += 1
premsg[i] = sz
self.sock.write(premsg, i + 2)
self.sock.write(msg)
# print(hex(len(msg)), hexlify(msg, ":"))
self._send_str(self.client_id)
if self.lw_topic:
self._send_str(self.lw_topic)
self._send_str(self.lw_msg)
if self.user is not None:
self._send_str(self.user)
self._send_str(self.pswd)
resp = self.sock.read(4)
assert resp[0] == 0x20 and resp[1] == 0x02
if resp[3] != 0:
raise MQTTException(resp[3])
return resp[2] & 1
def disconnect(self):
self.sock.write(b"\xe0\0")
self.sock.close()
def ping(self):
self.sock.write(b"\xc0\0")
def publish(self, topic, msg, retain=False, qos=0):
pkt = bytearray(b"\x30\0\0\0")
pkt[0] |= qos << 1 | retain
sz = 2 + len(topic) + len(msg)
if qos > 0:
sz += 2
assert sz < 2097152
i = 1
while sz > 0x7F:
pkt[i] = (sz & 0x7F) | 0x80
sz >>= 7
i += 1
pkt[i] = sz
# print(hex(len(pkt)), hexlify(pkt, ":"))
self.sock.write(pkt, i + 1)
self._send_str(topic)
if qos > 0:
self.pid += 1
pid = self.pid
struct.pack_into("!H", pkt, 0, pid)
self.sock.write(pkt, 2)
self.sock.write(msg)
if qos == 1:
while 1:
op = self.wait_msg()
if op == 0x40:
sz = self.sock.read(1)
assert sz == b"\x02"
rcv_pid = self.sock.read(2)
rcv_pid = rcv_pid[0] << 8 | rcv_pid[1]
if pid == rcv_pid:
return
elif qos == 2:
assert 0
def subscribe(self, topic, qos=0):
assert self.cb is not None, "Subscribe callback is not set"
pkt = bytearray(b"\x82\0\0\0")
self.pid += 1
struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid)
# print(hex(len(pkt)), hexlify(pkt, ":"))
self.sock.write(pkt)
self._send_str(topic)
self.sock.write(qos.to_bytes(1, "little"))
while 1:
op = self.wait_msg()
if op == 0x90:
resp = self.sock.read(4)
# print(resp)
assert resp[1] == pkt[2] and resp[2] == pkt[3]
if resp[3] == 0x80:
raise MQTTException(resp[3])
return
# Wait for a single incoming MQTT message and process it.
# Subscribed messages are delivered to a callback previously
# set by .set_callback() method. Other (internal) MQTT
# messages processed internally.
def wait_msg(self):
res = self.sock.read(1)
self.sock.setblocking(True)
if res is None:
return None
if res == b"":
raise OSError(-1)
if res == b"\xd0": # PINGRESP
sz = self.sock.read(1)[0]
assert sz == 0
return None
op = res[0]
if op & 0xF0 != 0x30:
return op
sz = self._recv_len()
topic_len = self.sock.read(2)
topic_len = (topic_len[0] << 8) | topic_len[1]
topic = self.sock.read(topic_len)
sz -= topic_len + 2
if op & 6:
pid = self.sock.read(2)
pid = pid[0] << 8 | pid[1]
sz -= 2
msg = self.sock.read(sz)
self.cb(topic, msg)
if op & 6 == 2:
pkt = bytearray(b"\x40\x02\0\0")
struct.pack_into("!H", pkt, 2, pid)
self.sock.write(pkt)
elif op & 6 == 4:
assert 0
# Checks whether a pending message from server is available.
# If not, returns immediately with None. Otherwise, does
# the same processing as wait_msg.
def check_msg(self):
self.sock.setblocking(False)
return self.wait_msg()
4.3 运行刚才我们编写的 MQTTX_WIFI.py,并使用 MQTTX 向它发送内容
我在 ESP32S3 的 GPIO1 接了一个继电器,所以上面的代码我解释一下:
def sub_cb(topic, msg): # 回调函数,收到服务器消息后会调用这个函数
print(topic, msg)
# ---- 控制 指令 --------
if topic.decode("utf-8") == "ledctl" and msg.decode("utf-8") == "on":
led_pin.value(1)
elif topic.decode("utf-8") == "ledctl" and msg.decode("utf-8") == "off":
led_pin.value(0)
# ---- 监控 指令 --------
这是刚才代码中回调函数的部分,意思是,如果 ESP32S3 接收到 topic 是 “ledctl” 的、内容是 on 的消息,就将 GPIO1 的电平拉高,继电器也就合了。相反,如果 ESP32S3 接收到 topic 是 “ledctl” 的、内容是 off 的消息,就将 GPIO1 的电平拉低,继电器也就开了。而有趣的是,我们能够使用电脑端的 MQTTX 给 ESP32S3 远程发送指令。
情况 2:远程控制 - 继电器开:
至此,你已经可以通过电脑远程控制 ESP32S3 终端了,我只是介绍这个过程,至于实际应用,你需要根据你现有的主程序等进行自己修改,让它控制你想要控制的 GPIO 等…
懒得写了… 太多了倒是还好,主要是每个人的程序不一样。把我的 main.py 粘上来大家照葫芦画瓢吧…
import time
import machine
import _thread
import sys
import max31865
from pid import PID
import ujson
from machine import UART
from machine import Pin
import network
from umqttsimple import MQTTClient
import MQTTX_WIFI
#------------------------------------------- 0.创建多线程 -------------------------------------------#
# 0.1 MQTT监听打开 - 线程
def MQTT_Listener(*args, **kwargs):
print("------------ 正在创建 MQTT_Listener 线程 -----------\n")
print("1.正在设置回调函数...")
YzyMqttClient.set_callback(MQTTX_WIFI.sub_cb) # 设置回调函数
print("1.OK\n")
print("2.正在建立 MQTT 连接")
YzyMqttClient.connect() # 建立连接
print("2.OK\n")
control_topic = "HYDC-A-60-01-control_topic"
print("3.正在设置 MQTT_Listener 通讯 control_topic == {} ... ".format(control_topic))
YzyMqttClient.subscribe(b"{}".format(control_topic)) # 监控ledctl这个通道,接收控制命令
print("3.OK\n")
print("4.正在设置引脚...")
led_pin = Pin(1, Pin.OUT)
print("4.OK\n")
time.sleep(1)
#------------------------------------- 1.初始化 PIN / UART 端口 -------------------------------------#
# 1.1 初始化-显示器:
# UART 1( pin_tx == 47; pin_rx == 48)
uart = UART(1, baudrate=115200, tx=47, rx=48)
# 1.2 初始化-继电器:
# GPIO 1 为 输出模式
pin_1 = machine.Pin(1, machine.Pin.OUT)
pin_1.init(mode=machine.Pin.OUT, pull=None)
# 1.3 初始化-温度+/-按钮:
# GPIO 16、GPIO 17 为 输入模式。
# GPIO 16(温度+); GPIO 17(温度-)
switch_setTemp_up = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_UP)
switch_setTemp_down = machine.Pin(17, machine.Pin.IN, machine.Pin.PULL_UP)
#--------------------------------------- 2.参数配置 PID / PWM ---------------------------------------#
# 2.1 PID 控制器配置:
setTemp = 60 # 目标温度(default==60)
Kp = 2 # 更改此值设置 PID 的 Kp 值
Ki = 0.2 # 更改此值设置 PID 的 Ki 值
Kd = 0.05 # 更改此值设置 PID 的 Kd 值
pid_controller = PID(Kp, Ki, Kd, setTemp) # 向 PID 函数传递参数
# 2.2 设置 PWM 引脚和频率:
pwm_pin = machine.Pin(15, machine.Pin.OUT) # 选择 GPIO15 作为 PWM 控制引脚
pwm = machine.PWM(pwm_pin) # 向 PWM 类传递参数,创建 PWM 对象
pwm.freq(1500) # 设置 PWM 对象的频率(建议范围:1000-20000 Hz)
#----------------------------------------- 3.WIFI 远程 MQTT -----------------------------------------#
# 3.1 使用 WIFI 连接网络
print("---------------- 正在连接 WIFI ... ---------------\n")
MQTTX_WIFI.do_wifi_connect()
time.sleep(1)
# 3.2 创建mqt
print("-------------- 正在建立 MQTTClient ... ------------\n")
print("1.正在创建 MQTT Client ...")
YzyMqttClient = MQTTClient("YzyMqttClient", "183.72.219.207",1883,"admin","fish0424",keepalive=60) # 建立一个MQTT客户端
print("1.OK\n")
# 0.定义 设备名称
homeassistant_device_name = "HYDC-A-60-01"
# 1.定义 传感器 currentTemp 名称、类型
print("2.正在创建 传感器 currentTemp ...")
homeassistant_device_sensor_currentTemp_name = "currentTemp"
homeassistant_device_sensor_currentTemp_type = "Temp"
print("2.OK\n")
# 2.定义 传感器 setTemp 名称、类型
print("3.正在创建 传感器 setTemp ...")
homeassistant_device_sensor_setTemp_name = "setTemp"
homeassistant_device_sensor_setTemp_type = "Temp"
print("3.OK\n")
# 1.1 定义 传感器 currentTemp 发送报文 topic
homeassistant_device_sensor_currentTemp_state_topic = "HA-%s/%s/state" % (homeassistant_device_name, homeassistant_device_sensor_currentTemp_name)
# 2.1 定义 传感器 setTemp 发送报文 topic
homeassistant_device_sensor_setTemp_state_topic = "HA-%s/%s/state" % (homeassistant_device_name, homeassistant_device_sensor_setTemp_name)
time.sleep(1)
#------------------------------------------- 4.开启 多线程 -------------------------------------------#
# 4.1 开启 MQTT_Listener 线程
thread_1 = _thread.start_new_thread(MQTT_Listener, (1,))
time.sleep(1)
print("---------------- main 函数开始运行 ... --------------\n")
while True:
# ------------ 1.温度检测 ------------ #
# 1.1:温度值读取 - temp_4、temp_5、temp_6、temp_7
temp_4 = round(max31865.read_max31865_temperature_4(), 1)
time.sleep_ms(30)
temp_5 = round(max31865.read_max31865_temperature_5(), 1)
time.sleep_ms(30)
temp_6 = round(max31865.read_max31865_temperature_6(), 1)
time.sleep_ms(30)
temp_7 = round(max31865.read_max31865_temperature_7(), 1)
time.sleep_ms(30)
# 1.2:计算4路config值
config_4 = max31865.read_config_4()
config_5 = max31865.read_config_5()
config_6 = max31865.read_config_6()
config_7 = max31865.read_config_7()
# -------------- 2.PID -------------- #
# 2.1:根据 temp_4 计算输出 PWM 信号强度
output_value = pid_controller.compute(temp_4)
# 2.2:将输出值限制在 0-100% 的范围内,并将其映射到PWM占空比(0-1023)
output_value = max(min(output_value, 100), 0)
duty_cycle = int(output_value * 1023 / 100)
# 2.3:设置 PWM 占空比
pwm.duty(duty_cycle)
# 2.4:获取 PID 参数
errorsum = pid_controller.get_constants()
# ------------- 3.继电器 ------------- #
# 3.1:设置继电器控制逻辑,大于设定温度15℃自动断开
if temp_4 < setTemp + 15:
pin_1.value(1)
else:
pin_1.value(0)
# ---------- 4.温度 +/- 按钮 --------- #
# 4.1:设置微动开关逻辑
if switch_setTemp_up.value() == 0 and setTemp < 120:
setTemp += 1
pid_controller.set_setTemp(setTemp) # 更新PID控制器的目标值
time.sleep_ms(200) # 为了防止多次触发,暂停200ms
if switch_setTemp_down.value() == 0 and setTemp > 30:
setTemp -= 1
pid_controller.set_setTemp(setTemp) # 更新PID控制器的目标值
time.sleep_ms(200) # 为了防止多次触发,暂停200ms
# ---------- 5.显示器串口传输 --------- #
# 5.1:UART,将数据转json
data = {'temp4': temp_4, 'temp5': temp_5, 'temp6': temp_6, 'setTemp': setTemp}
json_data = ujson.dumps(data)
# 5.2:发送JSON数据到另一个ESP32S3(显示器)
uart.write(json_data + '\n')
# ----------- 6.MQTT远传监视 ---------- #
YzyMqttClient.check_msg()
time.sleep(0.3)
homeassistant_device_sensor_currentTemp_state_content = round(temp_4, 1)
homeassistant_device_sensor_setTemp_state_content = setTemp
# 1.2 定义 传感器 currentTemp 发送报文 content
send_content_currentTemp = ujson.dumps(homeassistant_device_sensor_currentTemp_state_content)
# 2.2 定义 传感器 setTemp 发送报文 content
send_content_setTemp = ujson.dumps(homeassistant_device_sensor_setTemp_state_content)
# 3.发送 currentTemp、setTemp 报文(包含 topic 和 content)
YzyMqttClient.publish(homeassistant_device_sensor_currentTemp_state_topic,send_content_currentTemp)
time.sleep(0.1)
YzyMqttClient.publish(homeassistant_device_sensor_setTemp_state_topic,send_content_setTemp)
time.sleep(0.1)
# ---------- 7.串口监视器调试 --------- #
# 6.1:输出温度
print("CS-4-PT100:当前温度: {:.2f}".format(temp_4))
print("CS-5-PT100:当前温度: {:.2f}".format(temp_5))
print("CS-6-PT100:当前温度: {:.2f}".format(temp_6))
print("CS-7-PT100:当前温度: {:.2f}".format(temp_7))
# 6.2:输出 max-31865 的 config
print("CS-4-Config: 0x{:02x}".format(config_4))
print("CS-5-Config: 0x{:02x}".format(config_5))
print("CS-6-Config: 0x{:02x}".format(config_6))
print("CS-7-Config: 0x{:02x}".format(config_7))
# 6.3:输出 PID 相关参数
print("PID控制程序输出功率百分比: {:.2f}%".format(output_value))
print("errorsum ==",errorsum)
print("setTemp ==",setTemp)
print("PWM Duty Cycle: {}".format(duty_cycle))
print("Kp: {}, Ki: {}, Kd: {}".format(Kp, Ki, Kd))
# 6.4:输出 传输至显示器的 json_data 内容
print("json_data ==",json_data,"\n")
time.sleep(0.1)
然后,真实采集到的数据就会在 HomeAssistant 上显现了。