در این آموزش یاد خواهید گرفت چگونه مقادیر خوانده شده از سنسور 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 متصل کنید.
شماتیک: ESP8266 NodeMCU با BME280
اگر از ESP8266 NodeMCU استفاده میکنید، دیاگرام زیر را دنبال کنید.
کد
پس از آپلود کتابخانهها در برد 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 ایجاد کنید.
روی MQTT 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 ها را مانند شکل زیر به هم وصل کنید.
در نهایت، 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 استفاده کنید.