Overview
1. Process overview
2. Start building!
3.
1. Process Overview
0.Overall process
2. Start building
1. Download the MQTTX client (on PC)
https://mqttx.app/zh/downloads
2. Create a new MQTTX connection
0. Click the plus sign on the left to start creating a new connection.
A total of several parameters need to be filled in:
1. Name: whatever name comes to mind
2. Server address: fill in your cloud server external network IP
3. Username and password: Fill in the username and password you just used to log in to EMQX (the default username is admin and the default password is public)
4.Create connection successfully
3. Use MQTTX to add devices (this device) & entities (sensors on this device...can be many) to HomeAssistant
3.1 Adding devices and entities
What needs to be noted in this step is that the content in the topic should be the value of unique_id plus /config. If you don’t understand, look at my json code and pictures below to understand. Just follow the gourd and draw the scoop.
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 View devices and entities
Go to Configuration -> MQTT in HomeAssistant to see devices and entities. (I have added entities here and experimented with assigning values. It is normal that you do not have an interface like this. Just follow the example and change the content of the parameters yourself. This is very important)
3.3 Test using MQTTX to assign values to the entities of the device (specific sensors on the device)
like the following code. The topic is HA-HYDC-A-60-01/currentTemp/state, which refers to the entity on the device you just created. value.
I now assign it a value of 34, and I can see this value in HomeAssistant.
HA-HYDC-A-60-01/currentTemp/state
35
3.4 Steps to add the 2nd, 3rd, 4th... entities to this device.
When we created this device just now, we also created its first entity. Now we want to create the second one. Please pay attention to the following code. Similarities and differences with the above code. You will find that their "device" entries are the same, but the content above has changed. The sensor we created just now is called "currentTemp", and the sensor we created this time is called "setTemp". And the title has changed.
After sending the following code, you will find that a second sensor appears in HomeAssistant, which is the same as before. After we create the second sensor, we can also assign it the same method as before.
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. Write a program on ESP32S3, connect to WIFI and let it connect to the MQTT network service on the cloud server
4.1 Write a program for ESP32S3.
Give this program a name, whatever you want. Mine is MQTTX_WIFI.py.
Please note that you need to change several places in it. I added the comment "Please modify: xxx" after the sentence in the code below.
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 Import the dependency package umqttsimple.py
and create a umqttsimple.py file yourself with the following content (you don’t need to add anything to it yourself):
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 Run the MQTTX_WIFI.py we just wrote and use MQTTX to send content to it.
I connected a relay to GPIO1 of ESP32S3, so I will explain the above code:
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)
# ---- 监控 指令 --------
This is the callback function part of the code just now. It means that if ESP32S3 receives a message with the topic "ledctl" and the content "on", it will pull the level of GPIO1 high and the relay will be closed. On the contrary, if ESP32S3 receives a message whose topic is "ledctl" and whose content is off, it will pull the level of GPIO1 low and the relay will turn on. What's interesting is that we can use MQTTX on the computer to send commands remotely to ESP32S3.
Case 1: Remote control - relay closed:
Situation 2: Remote control - relay on:
At this point, you can remotely control the ESP32S3 terminal through the computer. I am just introducing the process. As for the actual application, you need to modify it according to your existing main program, etc. to let it control you. GPIOs you want to control, etc…
5. Write a program on ESP32S3 so that it can send its own data to the cloud server
Too lazy to write... It's okay if there are too many, mainly because everyone's program is different. Paste my main.py and let everyone follow suit...
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)
Then, the actual collected data will be displayed on HomeAssistant.