[Private use] The terminal device (ESP32-S3) connects to the cloud server HomeAssistant + MQTT IoT platform

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

Insert image description here

2. Create a new MQTTX connection

0. Click the plus sign on the left to start creating a new connection.

Insert image description here

A total of several parameters need to be filled in:

1. Name: whatever name comes to mind

Insert image description here

2. Server address: fill in your cloud server external network IP

Insert image description here

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)

Insert image description here

4.Create connection successfully
Insert image description here

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"
    }
}

Insert image description here

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)

Insert image description here

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

Insert image description here
Insert image description here

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

Insert image description here

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:
Insert image description here

Situation 2: Remote control - relay on:
Insert image description here
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.

Insert image description here

Guess you like

Origin blog.csdn.net/qq_43768851/article/details/132248322
Recommended