آموزش میکروپایتون: publish کردن مقادیر خوانده شده از BME280 با MQTT (ESP32/ESP8266)

در این آموزش یاد خواهید گرفت چگونه مقادیر خوانده شده از سنسور BME180 (دما، رطوبت، فشار) را با پروگرم کردن بردهای ESP32/ESP8266 و با استفاده از میکروپایتون و از طریق MQTT به هر پلتفرم دیگری که MQTT را پشتیبانی می‌کند و یا هر MQTT client دیگری publish کنید. به عنوان مثال، ما مقادیر خوانده شده از سنسور را به publish ،Node-RED Dashboard می‌کنیم.

مرور کلی پروژه

دیاگرام زیر، نمای کلی پروژه‌ای که می‌خواهیم بسازیم را نشان می‌دهد:

مرور کلی پروژه
  • ESP، دما و رطوبت خوانده شده از سنسور BME280 را درخواست می‌کند.
  • دمای خوانده شده در تاپیک poublish ،esp/bme280/temperature می‌شود.
  • رطوبت خوانده شده در تاپیک publish ،esp/bme280/humidityمی‌شود.
  • فشار خوانده شده در تاپیک publish ،esp/bme280/pressure می‌شود.
  • این تاپیک‌ها را subscribe ،Node-RED می‌کند.
  • Node-RED مقادیر خوانده شده سنسور را دریافت می‌کند و آن‌ها را توسط وسایل اندازه‌گیری (دماسنج، رطوبت سنج و فشارسنج) نمایش می‌دهد.
  • شما می‌توانید مقادیری که سنسور خوانده را در هر پلتفرم دیگری که MQTT را پشتیبانی می‌کند، دریافت و آن‌ها را هر طور که مایلید، مدیریت کنید.

پیشنیازها

برای ادامه این آموزش، باید فریمور میکروپایتون در برد ESP32 یا ESP8266 شما نصب باشد. همچنین برای نوشتن و آپلود کد در بردتان به یک IDE نیاز دارید. ما پیشنهاد می‌کنیم از Thonny IDE یا uPyCraft IDE استفاده کنید.

بروکر MQTT

برای استفاده از MQTT، به یک بروکر نیاز دارید. ما از Mosquitto broker استفاده می‌کنیم که روی یک Raspberry Pi نصب است.

قطعات مورد نیاز

برای ادامه این آموزش، به قطعات زیر احتیاج دارید:

ESP32 یا ESP8266
BME280
برد Raspberry Pi
MicroSD Card – ۱۶GB Class10
منبع تغذیه ۵V 2.5A) Raspberry Pi)
سیم جامپر
بردبرد

کتابخانه umqtttsimple

برای استفاده از MQTT با ESP32/ESP8266 و با میکروپایتون، از کتابخانه umqttsimple.py استفاده می‌کنیم.
با توجه به IDE که استفاده می‌کنید، از دستورالعمل‌های زیر استفاده کنید:

A. آپلود کتابخانه umqttsimple با uPyCraft IDE
B. آپلود کتابخانه umqttsimple با Thonny IDE

کتابخانه umqtttsimple:

try:
    import usocket as socket
except:
    import 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()

A. آپلود کتابخانه umqttsimple با uPyCraft IDE

۱- با کلیک روی New File یک فایل جدید ایجاد کنید.

۲- کد کتابخانه umqttsimple را در آن کپی کنید.

۳- با کلیک روی Save آن را ذخیره کنید.

۴- این فایل را “umqttsimple.py” بنامید و روی ok کلیک کنید.

۵- روی Download and Run کلیک کنید.

۶- فایل باید در پوشه دستگاه شما با نام“umqttsimple.py” همان‌طور که در تصویر مشخص شده است، ایجاد شود.

حالا می‌توانید از این کتابخانه در کدتان استفاده کنید.

B. آپلود کتابخانه umqttsimple با Thonny IDE

۱- کد کتابخانه را فایل جدبد کپی کنید.

۲- به مسیر زیر بروید.
…File > Save as

۳- ذخیره در “MicroPython device“ را انتخاب کنید.

۴- فایل را umqttsimple.py بنامید و OK را فشار دهید.

تمام. کتابخانه در برد شما آپلود شده است. برای اینکه مطمئن شوید آپلود موفقیت‌آمیز بوده است، به مسیر … File> Save as بروید و MicroPython device را انتخاب کنید. فایل شما باید در آن‌جا لیست شده باشد:

پس از آپلود کتابخانه به بردتان، با وارد کردن کتابخانه به کدتان، می‌توانید از قابلیت‌های آن استفاده کنید.

کتابخانه میکروپایتون BME280

کتابخانه‌ای که مقادیر را از سنسور BME280 می‌خواند، به صورت پیش‌فرض قسمتی از کتابخانه استاندارد پایتون نیست. پس، شما نیاز دارید که این کتابخانه را در برد ESP32/ESP8266 آپلود کنید.(آن را با نام BME280.py ذخیره کنید.)

from machine import I2C
import time

# BME280 default address.
BME280_I2CADDR = 0x76

# Operating Modes
BME280_OSAMPLE_1 = 1
BME280_OSAMPLE_2 = 2
BME280_OSAMPLE_4 = 3
BME280_OSAMPLE_8 = 4
BME280_OSAMPLE_16 = 5

# BME280 Registers

BME280_REGISTER_DIG_T1 = 0x88  # Trimming parameter registers
BME280_REGISTER_DIG_T2 = 0x8A
BME280_REGISTER_DIG_T3 = 0x8C

BME280_REGISTER_DIG_P1 = 0x8E
BME280_REGISTER_DIG_P2 = 0x90
BME280_REGISTER_DIG_P3 = 0x92
BME280_REGISTER_DIG_P4 = 0x94
BME280_REGISTER_DIG_P5 = 0x96
BME280_REGISTER_DIG_P6 = 0x98
BME280_REGISTER_DIG_P7 = 0x9A
BME280_REGISTER_DIG_P8 = 0x9C
BME280_REGISTER_DIG_P9 = 0x9E

BME280_REGISTER_DIG_H1 = 0xA1
BME280_REGISTER_DIG_H2 = 0xE1
BME280_REGISTER_DIG_H3 = 0xE3
BME280_REGISTER_DIG_H4 = 0xE4
BME280_REGISTER_DIG_H5 = 0xE5
BME280_REGISTER_DIG_H6 = 0xE6
BME280_REGISTER_DIG_H7 = 0xE7

BME280_REGISTER_CHIPID = 0xD0
BME280_REGISTER_VERSION = 0xD1
BME280_REGISTER_SOFTRESET = 0xE0

BME280_REGISTER_CONTROL_HUM = 0xF2
BME280_REGISTER_CONTROL = 0xF4
BME280_REGISTER_CONFIG = 0xF5
BME280_REGISTER_PRESSURE_DATA = 0xF7
BME280_REGISTER_TEMP_DATA = 0xFA
BME280_REGISTER_HUMIDITY_DATA = 0xFD


class Device:
  """Class for communicating with an I2C device.

  Allows reading and writing 8-bit, 16-bit, and byte array values to
  registers on the device."""

  def __init__(self, address, i2c):
    """Create an instance of the I2C device at the specified address using
    the specified I2C interface object."""
    self._address = address
    self._i2c = i2c

  def writeRaw8(self, value):
    """Write an 8-bit value on the bus (without register)."""
    value = value & 0xFF
    self._i2c.writeto(self._address, value)

  def write8(self, register, value):
    """Write an 8-bit value to the specified register."""
    b=bytearray(1)
    b[0]=value & 0xFF
    self._i2c.writeto_mem(self._address, register, b)

  def write16(self, register, value):
    """Write a 16-bit value to the specified register."""
    value = value & 0xFFFF
    b=bytearray(2)
    b[0]= value & 0xFF
    b[1]= (value>>8) & 0xFF
    self.i2c.writeto_mem(self._address, register, value)

  def readRaw8(self):
    """Read an 8-bit value on the bus (without register)."""
    return int.from_bytes(self._i2c.readfrom(self._address, 1),'little') & 0xFF

  def readU8(self, register):
    """Read an unsigned byte from the specified register."""
    return int.from_bytes(
        self._i2c.readfrom_mem(self._address, register, 1),'little') & 0xFF

  def readS8(self, register):
    """Read a signed byte from the specified register."""
    result = self.readU8(register)
    if result > 127:
      result -= 256
    return result

  def readU16(self, register, little_endian=True):
    """Read an unsigned 16-bit value from the specified register, with the
    specified endianness (default little endian, or least significant byte
    first)."""
    result = int.from_bytes(
        self._i2c.readfrom_mem(self._address, register, 2),'little') & 0xFFFF
    if not little_endian:
      result = ((result << 8) & 0xFF00) + (result >> 8)
    return result

  def readS16(self, register, little_endian=True):
    """Read a signed 16-bit value from the specified register, with the
    specified endianness (default little endian, or least significant byte
    first)."""
    result = self.readU16(register, little_endian)
    if result > 32767:
      result -= 65536
    return result

  def readU16LE(self, register):
    """Read an unsigned 16-bit value from the specified register, in little
    endian byte order."""
    return self.readU16(register, little_endian=True)

  def readU16BE(self, register):
    """Read an unsigned 16-bit value from the specified register, in big
    endian byte order."""
    return self.readU16(register, little_endian=False)

  def readS16LE(self, register):
    """Read a signed 16-bit value from the specified register, in little
    endian byte order."""
    return self.readS16(register, little_endian=True)

  def readS16BE(self, register):
    """Read a signed 16-bit value from the specified register, in big
    endian byte order."""
    return self.readS16(register, little_endian=False)


class BME280:
  def __init__(self, mode=BME280_OSAMPLE_1, address=BME280_I2CADDR, i2c=None,
               **kwargs):
    # Check that mode is valid.
    if mode not in [BME280_OSAMPLE_1, BME280_OSAMPLE_2, BME280_OSAMPLE_4,
                    BME280_OSAMPLE_8, BME280_OSAMPLE_16]:
        raise ValueError(
            'Unexpected mode value {0}. Set mode to one of '
            'BME280_ULTRALOWPOWER, BME280_STANDARD, BME280_HIGHRES, or '
            'BME280_ULTRAHIGHRES'.format(mode))
    self._mode = mode
    # Create I2C device.
    if i2c is None:
      raise ValueError('An I2C object is required.')
    self._device = Device(address, i2c)
    # Load calibration values.
    self._load_calibration()
    self._device.write8(BME280_REGISTER_CONTROL, 0x3F)
    self.t_fine = 0

  def _load_calibration(self):

    self.dig_T1 = self._device.readU16LE(BME280_REGISTER_DIG_T1)
    self.dig_T2 = self._device.readS16LE(BME280_REGISTER_DIG_T2)
    self.dig_T3 = self._device.readS16LE(BME280_REGISTER_DIG_T3)

    self.dig_P1 = self._device.readU16LE(BME280_REGISTER_DIG_P1)
    self.dig_P2 = self._device.readS16LE(BME280_REGISTER_DIG_P2)
    self.dig_P3 = self._device.readS16LE(BME280_REGISTER_DIG_P3)
    self.dig_P4 = self._device.readS16LE(BME280_REGISTER_DIG_P4)
    self.dig_P5 = self._device.readS16LE(BME280_REGISTER_DIG_P5)
    self.dig_P6 = self._device.readS16LE(BME280_REGISTER_DIG_P6)
    self.dig_P7 = self._device.readS16LE(BME280_REGISTER_DIG_P7)
    self.dig_P8 = self._device.readS16LE(BME280_REGISTER_DIG_P8)
    self.dig_P9 = self._device.readS16LE(BME280_REGISTER_DIG_P9)

    self.dig_H1 = self._device.readU8(BME280_REGISTER_DIG_H1)
    self.dig_H2 = self._device.readS16LE(BME280_REGISTER_DIG_H2)
    self.dig_H3 = self._device.readU8(BME280_REGISTER_DIG_H3)
    self.dig_H6 = self._device.readS8(BME280_REGISTER_DIG_H7)

    h4 = self._device.readS8(BME280_REGISTER_DIG_H4)
    h4 = (h4 << 24) >> 20
    self.dig_H4 = h4 | (self._device.readU8(BME280_REGISTER_DIG_H5) & 0x0F)

    h5 = self._device.readS8(BME280_REGISTER_DIG_H6)
    h5 = (h5 << 24) >> 20
    self.dig_H5 = h5 | (
        self._device.readU8(BME280_REGISTER_DIG_H5) >> 4 & 0x0F)

  def read_raw_temp(self):
    """Reads the raw (uncompensated) temperature from the sensor."""
    meas = self._mode
    self._device.write8(BME280_REGISTER_CONTROL_HUM, meas)
    meas = self._mode << 5 | self._mode << 2 | 1
    self._device.write8(BME280_REGISTER_CONTROL, meas)
    sleep_time = 1250 + 2300 * (1 << self._mode)

    sleep_time = sleep_time + 2300 * (1 << self._mode) + 575
    sleep_time = sleep_time + 2300 * (1 << self._mode) + 575
    time.sleep_us(sleep_time)  # Wait the required time
    msb = self._device.readU8(BME280_REGISTER_TEMP_DATA)
    lsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 1)
    xlsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 2)
    raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4
    return raw

  def read_raw_pressure(self):
    """Reads the raw (uncompensated) pressure level from the sensor."""
    """Assumes that the temperature has already been read """
    """i.e. that enough delay has been provided"""
    msb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA)
    lsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 1)
    xlsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 2)
    raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4
    return raw

  def read_raw_humidity(self):
    """Assumes that the temperature has already been read """
    """i.e. that enough delay has been provided"""
    msb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA)
    lsb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA + 1)
    raw = (msb << 8) | lsb
    return raw

  def read_temperature(self):
    """Get the compensated temperature in 0.01 of a degree celsius."""
    adc = self.read_raw_temp()
    var1 = ((adc >> 3) - (self.dig_T1 << 1)) * (self.dig_T2 >> 11)
    var2 = ((
        (((adc >> 4) - self.dig_T1) * ((adc >> 4) - self.dig_T1)) >> 12) *
        self.dig_T3) >> 14
    self.t_fine = var1 + var2
    return (self.t_fine * 5 + 128) >> 8

  def read_pressure(self):
    """Gets the compensated pressure in Pascals."""
    adc = self.read_raw_pressure()
    var1 = self.t_fine - 128000
    var2 = var1 * var1 * self.dig_P6
    var2 = var2 + ((var1 * self.dig_P5) << 17)
    var2 = var2 + (self.dig_P4 << 35)
    var1 = (((var1 * var1 * self.dig_P3) >> 8) +
            ((var1 * self.dig_P2) >> 12))
    var1 = (((1 << 47) + var1) * self.dig_P1) >> 33
    if var1 == 0:
      return 0
    p = 1048576 - adc
    p = (((p << 31) - var2) * 3125) // var1
    var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25
    var2 = (self.dig_P8 * p) >> 19
    return ((p + var1 + var2) >> 8) + (self.dig_P7 << 4)

  def read_humidity(self):
    adc = self.read_raw_humidity()
    # print 'Raw humidity = {0:d}'.format (adc)
    h = self.t_fine - 76800
    h = (((((adc << 14) - (self.dig_H4 << 20) - (self.dig_H5 * h)) +
         ۱۶۳۸۴) >> 15) * (((((((h * self.dig_H6) >> 10) * (((h *
                          self.dig_H3) >> 11) + 32768)) >> 10) + 2097152) *
                          self.dig_H2 + 8192) >> 14))
    h = h - (((((h >> 15) * (h >> 15)) >> 7) * self.dig_H1) >> 4)
    h = 0 if h < 0 else h
    h = 419430400 if h > 419430400 else h
    return h >> 12

  @property
  def temperature(self):
    "Return the temperature in degrees."
    t = self.read_temperature()
    ti = t // 100
    td = t - ti * 100
    return "{}.{:02d}C".format(ti, td)

  @property
  def pressure(self):
    "Return the temperature in hPa."
    p = self.read_pressure() // 256
    pi = p // 100
    pd = p - pi * 100
    return "{}.{:02d}hPa".format(pi, pd)

  @property
  def humidity(self):
    "Return the humidity in percent."
    h = self.read_humidity()
    hi = h // 1024
    hd = h * 100 // 1024 - hi * 100
    return "{}.{:02d}%".format(hi, hd)

شماتیک: ESP32 با BME280

همان‌طور که در دیاگرام شماتیک زیر آمده است، سنسور BME280 را به برد ESP32 متصل کنید.

شماتیک ESP32 – BME280

شماتیک: ESP8266 NodeMCU با BME280

اگر از ESP8266 NodeMCU استفاده می‌کنید، دیاگرام زیر را دنبال کنید.

شماتیک ESP8266 – BME280

کد

پس از آپلود کتابخانه‌ها در برد ESP32/ESP8266، کد زیر را در فایل main.py کپی کنید. این کد هر ۵ ثانیه مقادیر دما، رطوبت و فشار را در تاپیک‌هایesp/bme280/pressure، esp/bme280/humidity ،esp/bme280/temperature، پابلیش می‌کند.

# Complete project details at https://RandomNerdTutorials.com/micropython-mqtt-publish-bme280-esp32-esp8266/

import time
from umqttsimple import MQTTClient
import ubinascii
import machine
import micropython
import network
import esp
import BME280
from machine import Pin, I2C

esp.osdebug(None)
import gc
gc.collect()

ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
mqtt_server = '192.168.1.XXX'
#EXAMPLE IP ADDRESS
#mqtt_server = '192.168.1.106'

client_id = ubinascii.hexlify(machine.unique_id())

topic_pub_temp = b'esp/bme280/temperature'
topic_pub_hum = b'esp/bme280/humidity'
topic_pub_pres = b'esp/bme280/pressure'

last_message = 0
message_interval = 5

station = network.WLAN(network.STA_IF)

station.active(True)
station.connect(ssid, password)

while station.isconnected() == False:
  pass

print('Connection successful')

# ESP32 - Pin assignment
i2c = I2C(scl=Pin(22), sda=Pin(21), freq=10000)
# ESP8266 - Pin assignment
#i2c = I2C(scl=Pin(5), sda=Pin(4), freq=10000)
bme = BME280.BME280(i2c=i2c)

def connect_mqtt():
  global client_id, mqtt_server
  client = MQTTClient(client_id, mqtt_server)
  #client = MQTTClient(client_id, mqtt_server, user=your_username, password=your_password)
  client.connect()
  print('Connected to %s MQTT broker' % (mqtt_server))
  return client

def restart_and_reconnect():
  print('Failed to connect to MQTT broker. Reconnecting...')
  time.sleep(10)
  machine.reset()

def read_bme_sensor():
  try:
    temp = b'%s' % bme.temperature[:-1]
    #temp = (b'{0:3.1f},'.format((bme.read_temperature()/100) * (9/5) + 32))
    hum = b'%s' % bme.humidity[:-1]
    pres = b'%s'% bme.pressure[:-3]

    return temp, hum, pres
    #else:
    #  return('Invalid sensor readings.')
  except OSError as e:
    return('Failed to read sensor.')

try:
  client = connect_mqtt()
except OSError as e:
  restart_and_reconnect()

while True:
  try:
    if (time.time() - last_message) > message_interval:
      temp, hum, pres = read_bme_sensor()
      print(temp)
      print(hum)
      print(pres)
      client.publish(topic_pub_temp, temp)
      client.publish(topic_pub_hum, hum)      
      client.publish(topic_pub_pres, pres)

      last_message = time.time()
  except OSError as e:
    restart_and_reconnect()

کد چگونه کار می‌کند

کتابخانه‌های لازم را وارد کنید:

import time
from umqttsimple import MQTTClient
import ubinascii
import machine
import micropython
import network
import esp
import BME280
from machine import Pin, I2C

در متغیرهای زیر، نیاز دارید که اعتبارنامه شبکه‌تان و آدرس IP بروکرتان وارد کنید.

ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
mqtt_server = 'REPLACE_WITH_YOUR_MQTT_BROKER_IP'

برای مثال، آدرس IP بروکر ما ۱۹۲.۱۶۸.۱.۱۰۶ است.

mqtt_server = '192.168.1.106'

برای ایجاد یک MQTT client، نیاز داریم که ID منحصر به فرد ESP را بگیرید. در خطوط زیر این کار را انجام می‌دهیم.(ID را در متغیر client_id ذخیره می‌کنیم.)

client_id = ubinascii.hexlify(machine.unique_id())

سپس، تاپیک‌‌هایی که می‌خواهید ESP آن‌ها را publish کند را بسازید. در مثال ما، دما در تاپیک esp/bme280/temperature، رطوبت در تاپیک esp/bme280/humidity و فشار در تاپیک publish ،esp/bme280/pressure می‌شوند.

topic_pub_temp = b'esp/bme280/temperature'
topic_pub_hum = b'esp/bme280/humidity'
topic_pub_pres = b'esp/bme280/pressure'

سپس متغیرهای زیر را بسازید:

last_message = 0
message_interval = 5

متغیر last_message آخرین پیامی که فرستاده شده است را نگه می‌دارد. message_interval هم زمان بین پیام‌های فرستاده شده است. در اینجا ما آن را روی ۵ ثانیه تنظیم کردیم. (یعنی پیام جدید هر ۵ ثانیه فرستاده می‌شود.) اگر می‌خواهید، می‌توانید آن را تغییر دهید.

بعد از آن، ESP را به شبکه local خود متصل کنید.

station = network.WLAN(network.STA_IF)

station.active(True)
station.connect(ssid, password)

while station.isconnected() == False:
  pass

print('Connection successful')

برای ارتباط با سنسور BME280، یک نمونه i2c در پین‌های ESP32 I2C بسازید:

i2c = I2C(scl=Pin(22), sda=Pin(21), freq=10000)

اگر برد شما ESP8266 است، از خطوط زیر استفاده کنید.(استفاده از پین‌‌های پیش‌فرض I2C برد ESP8266)

#i2c = I2C(scl=Pin(5), sda=Pin(4), freq=10000)

یک نمونه BME280 در پین‌های I2C برد ESP ایجاد کنید:

bme = BME280.BME280(i2c=i2c)

اتصال به MQTT Broker

تابع ()connect_mqtt، یک MQTT Client را می‌سازد و به بروکر شما وصل می‌کند.

def connect_mqtt():
  global client_id, mqtt_server
  client = MQTTClient(client_id, mqtt_server)
  #client = MQTTClient(client_id, mqtt_server, user=your_username, password=your_password)
  client.connect()
  print('Connected to %s MQTT broker' % (mqtt_server))
  return client

اگر MQTT broker شما username و password می‌خواهد، از خطوط زیر برای ارسال usernsame و password بروکر به عنوان آرگومان، استفاده کنید.

client = MQTTClient(client_id, mqtt_server, user=your_username, password=your_password)

راه‌اندازی و اتصال مجدد

تابع ()restart_and_reconnect، برد ESP32/ESP8266 را ریست می‌کند. در مواری که بروکر قطع شود،نمی‌توانیم مقادیر خوانده شده را باpublish ،MQTT کنیم، در این مواقع، این تابع فراخوانی می‌شود.

def restart_and_reconnect():
  print('Failed to connect to MQTT broker. Reconnecting...')
  time.sleep(10)
  machine.reset()

خواندن سنسور BME280

یک تابع با نام ()read_bme_sensor ایجاد می‌کنیم که دما، رطوبت و فشار کنونی خوانده شده از سنسور BME280 را برمی‌گرداند و وقتی که نمی‌توان مقادیر خوانده شده از سنسور را دیافت کرد، موارد استثنا را کنترل می‌کند.

def read_bme_sensor():
  try:
    temp = b'%s' % bme.temperature[:-1]
    #temp = (b'{0:3.1f},'.format((bme.read_temperature()/100) * (9/5) + 32))
    hum = b'%s' % bme.humidity[:-1]
    pres = b'%s'% bme.pressure[:-3]

    return temp, hum, pres
    #else:
    #  return('Invalid sensor readings.')
  except OSError as e:
    return('Failed to read sensor.')

publish کردن پیام‌های MQTT

در حلقه while، مقادیر خوانده شده از BME280 را هر ۵ ثانیه publish می‌کنیم.

ابتدا چک می‌کنیم که زمان خواندن پیام جدید رسیده است یا نه:

if (time.time() - last_message) > message_interval:

اگر زمان خواندن پیام جدید رسیده باشد، با فراخوانی تابع ()read_bme_sensor، مقادیر جدید خوانده شده از سنسور BME280 درخواست می‌شود. دما در متغیر temp، رطوبت در متغیر hum و فشار در متغیر pres ذخیره می‌شوند.

temp, hum, pres = read_bme_sensor()

در نهایت، مقادیر جدید خوانده شده، توسط متد ()publish در آبجکت publish ،client می‌شوند. متد ()publish، تاپیک و پیغام را (مانند خطوط زیر) به عنوان آرگومان می‌پذیرد.

client.publish(topic_pub_temp, temp)
client.publish(topic_pub_hum, hum)
client.publish(topic_pub_pres, pres)

در آخر، وقتی آخرین پیغام فرستاده شد، زمان را آپدیت کنید:

last_message = time.time()

اگر اتصال ESP32 یا ESP8266 از بروکر قطع شد، ما نمی‌توانیم مقادیر خوانده شده از سنسور را publish کنیم. در این موارد، تابع ()restart_and_reconnect برای ریست کردن برد ESP و تلاش برای اتصل دوباره به بروکر، فراخوانی می‌شود.

except OSError as e:
  restart_and_reconnect()

بعد از آپلود کد، باید مقادیر جدید خوانده شده هر ۵ ثانیه در Shell نمایش داده شوند.

حال برای آماده کردن Node-RED به بخش بعدی بروید تا مقادیر خوانده شده‌ای که ESP publish می‌کند را دریافت کنید.

آماده سازی Node-RED Dashboard

ESP32 یا ESP8266 هر ۱۰ ثانیه مقادیر خوانده شده خوانده شده را درتاپیک‌های esp/bme280/temperature ،esp/bme280/humidity و publish esp/bme280/pressure می‌کند. شما می‌توانید از هر dashboard دیگری که MQTT را پشتیبانی می‌کند و یا هر دستگاه دیگری که MQTT را برای subscribe تاپیک‌ها و دریافت مقادیر خوانده شده، پشتیبانی می‌کند استفاده کنید.

به عنوان مثال، ما یک flow ساده با استفاده از Node-RED می‌سازیم تا تاپیک‌ها subscribe شوند و مقادیر خوانده شده، روی دما‌سنج، رطوبت‌سنج و فشارسنج نشان داده شوند.

با نصب Node-RED روی رزبری پای، به آدرس IP رزبری پای خود (بعد آن ۱۸۸۰: قرار می‌گیرد) بروید.

http://raspberry-pi-ip-address:1880

رابط Node-RED باید باز شود. سه MQTT node و سه node وسایل اندازه‌گیری(دماسنج، رطوبت‌سنج و فشارسنج) را در flow ایجاد کنید.

nodeهای ایجاد شده

روی MQTT node کلیک کنید و مشخصات آن را ویرایش کنید.

مشخصات node

فیلد Server به MQTT broker اشاره دارد. در مثال ما، MQTT broker رزبری پای است؛ بنابراین روی localhost:1883 تنظیم شده است. اگر از یک Cloud MQTT broker استفاده کنید، باید فیلد را تغییر دهید.

تاپیکی که می‌خواهید subscribed شود و QoS را درج کنید. MQTT node قبلی به تاپیک subscribe esp/bme280/temperature شده است.

روی دو MQTT node دیگر کلیک کنید و مشخصات آن‌ها را با همان سرور اما برای تاپیک‌های esp/bme280/humidity و esp/bme280/pressure ویرایش کنید.

روی nodeهای وسایل اندازه‌گیری (دماسنج و رطوبت‌سنج) کلیک کنید و ویژگی‌های آن‌ها را ویرایش کنید. node زیر برای دمای خوانده شده تنظیم شده است. nodeهای دیگر را هم برای رطوبت و فشار خوانده شده تنظیم کنید.

node دما

node ها را مانند شکل زیر به هم وصل کنید.

nodeهای متصل شده

در نهایت، flow خود را deploy کنید.(دکمه بالا سمت راست)

همچنین می‌توانید به مسیر Menu > Import بروید و عبارت زیر را در Clipboard کپی کنید تا Node-RED flow شما ایجاد شود.

[{"id":"5a45b8da.52b0d8","type":"mqtt in","z":"b01416d3.f69f38","name":"","topic":"esp/bme280/temperature","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":310,"y":60,"wires":[["3042e15e.80a4ee"]]},{"id":"3042e15e.80a4ee","type":"ui_gauge","z":"b01416d3.f69f38","name":"","group":"37de8fe8.46846","order":2,"width":0,"height":0,"gtype":"gage","title":"Temperature","label":"ºC","format":"{{value}}","min":0,"max":"40","colors":["#00b500","#f7df09","#ca3838"],"seg1":"","seg2":"","x":590,"y":60,"wires":[]},{"id":"8ff168f0.0c74a8","type":"mqtt in","z":"b01416d3.f69f38","name":"","topic":"esp/bme280/humidity","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":300,"y":140,"wires":[["29251f29.6687c"]]},{"id":"29251f29.6687c","type":"ui_gauge","z":"b01416d3.f69f38","name":"","group":"37de8fe8.46846","order":2,"width":0,"height":0,"gtype":"gage","title":"Humidity","label":"%","format":"{{value}}","min":"30","max":"100","colors":["#53a4e6","#1d78a9","#4e38c9"],"seg1":"","seg2":"","x":580,"y":140,"wires":[]},{"id":"294f7eea.999d72","type":"mqtt in","z":"b01416d3.f69f38","name":"","topic":"esp/bme280/pressure","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":300,"y":220,"wires":[["58610d70.bb9764"]]},{"id":"58610d70.bb9764","type":"ui_gauge","z":"b01416d3.f69f38","name":"","group":"37de8fe8.46846","order":4,"width":0,"height":0,"gtype":"gage","title":"Pressure","label":"hPa","format":"{{value}}","min":0,"max":"1200","colors":["#b366ff","#8000ff","#440088"],"seg1":"","seg2":"","x":580,"y":220,"wires":[]},{"id":"8db3fac0.99dd48","type":"mqtt-broker","z":"","name":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"37de8fe8.46846","type":"ui_group","z":"","name":"BME280","tab":"53b8c8f9.cfbe48","order":1,"disp":true,"width":"6","collapse":false},{"id":"53b8c8f9.cfbe48","type":"ui_tab","z":"","name":"Home","icon":"dashboard","order":2,"disabled":false,"hidden":false}]

نمایش عملی

به صورت زیر به آدرس IP رزبری پای خود بروید.

http://raspberry-pi-ip-address:1880/ui

شما باید به دما، رطوبت و فشار خوانده شده BME280 در Dashboard دسترسی داشته باشید.
شما می‌توانید از nodeهای دیگر dashboard برای نمایش مقادیر خوانده شده به روش‌های دیگر استفاده کنید.

نمایش دما، رطوبت و فشار

حال شما برد ESP32 یا ESP8266 را دارید که مقادیر خوانده شده‌ی دما، رطوبت و فشار BME280 را از طریق MQTT و با استفاده از میکروپایتون در publish ،Node-RED می‌کند.

جمع‌بندی

MQTT یک پروتکل عالی برای تبادل داده‌های کوچک بین دستگاه‌های IoT است. در این آموزش یاد گرفتید که چگونه دما و رطوبت خوانده شده از یک سنسور BME280 را با برد ESP32 یا ESP8266 و با استفاده از میکروپایتون در تاپیک‌های مختلف publish ،MQTT کنید. شما می‌توانید از هر دستگاه یا پلتفرم اتوماسیون‌سازی خانه برای subscribe کردن تاپیک‌ها استفاده کنید و مقادیر خوانده شده را دریافت کنید.

به جای سنسور BME280 می‌توانید از هر سنسور دیگری مانند سنسور دمای DS18B20 و یا DHT11/DHT22 استفاده کنید.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

قبلا حساب کاربری ایجاد کرده اید؟
گذرواژه خود را فراموش کرده اید؟
Loading...