Pelajari cara memprogram papan ESP32 atau ESP8266 dengan MicroPython untuk menerbitkan pembacaan sensor BME680 (suhu, kelembaban, tekanan, kualitas udara/gas) via MQTT ke platform apa pun yang mendukung MQTT atau ke klien MQTT mana pun. Sebagai contoh, kami akan menerbitkan data sensor ke Node-RED Dashboard.
Gambaran Proyek
Diagram berikut menunjukkan gambaran tingkat tinggi dari proyek yang akan kita bangun.
1. ESP membaca data suhu, kelembaban, tekanan, dan gas dari sensor BME680.
2. Data suhu diterbitkan (published) ke topik esp/bme680/temperature.
3. Data kelembaban diterbitkan ke topik esp/bme680/humidity.
4. Data tekanan diterbitkan ke topik esp/bme680/pressure.
5. Data kualitas gas diterbitkan ke topik esp/bme680/gas.
6. Node-RED berlangganan (subscribe) ke topik-topik tersebut.
7. Node-RED menerima data sensor dan menampilkannya pada dashboard.
8. Anda dapat menerima data di platform lain yang mendukung MQTT dan mengolahnya sesuai kebutuhan.
MQTT Broker
Untuk menggunakan MQTT, Anda memerlukan sebuah broker. Kami akan menggunakan broker Mosquitto yang diinstal pada Raspberry Pi.
Komponen yang Diperlukan
- ESP32 atau ESP8266
- Sensor BME680
- Papan Raspberry Pi (lihat: [Kit Pemula Raspberry Pi Terbaik](tautan))
- Kartu MicroSD – 16GB Class10
- Catu Daya Raspberry Pi (5V 2.5A)
- Kabel jumper
- Papan breadboard
Library umqttsimple
Untuk menggunakan MQTT dengan ESP32/ESP8266 dan MicroPython, kami akan menggunakan library `umqttsimple.py`. Ikuti set instruksi berikut sesuai dengan IDE yang Anda gunakan:
- Unggah Library `umqttsimple` dengan uPyCraft IDE
- Unggah Library `umqttsimple` dengan Thonny IDE
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()
Unggah Pustaka umqttsimple dengan uPyCraft IDE
1. Buat file baru dengan menekan tombol New File.
2. Salin kode library `umqttsimple` ke dalamnya.
3. Simpan file dengan menekan tombol Save.
4. Beri nama file baru ini `umqttsimple.py` dan tekan OK.
5. Klik tombol Download and Run.
6. File tersebut akan tersimpan di folder perangkat dengan nama `umqttsimple.py`, seperti yang ditunjukkan pada gambar di bawah.
Sekarang, Anda dapat menggunakan fungsi-fungsi pustaka dalam kode Anda dengan mengimpornya.
Unggah Pustaka umqttsimple dengan Thonny IDE
1. Salin kode library ke dalam file baru.
2. Buka menu File > Save as….
3. Pilih opsi simpan ke "MicroPython device" (Simpan ke perangkat MicroPython):
4. Beri nama file Anda `umqttsimple.py` dan tekan tombol OK:
Selesai. Pustaka telah berhasil diunggah ke papan Anda. Untuk memastikan pengunggahan berhasil, buka File > Save as… dan pilih MicroPython device. File Anda akan terdaftar di sana:
Setelah pustaka berhasil diunggah ke papan ESP, Anda dapat menggunakan fungsinya dalam kode dengan mengimpornya.
Library MicroPython untuk BME680
Pustaka untuk membaca data dari sensor BME680 tidak termasuk dalam pustaka standar MicroPython secara default. Oleh karena itu, Anda perlu mengunggah pustaka berikut ke papan ESP32/ESP8266 Anda (simpan dengan nama `bme680.py`).
# Original source: https://github.com/adafruit/Adafruit_CircuitPython_BME680/blob/master/adafruit_bme680.py
import time
import math
from micropython import const
from ubinascii import hexlify as hex
try:
import struct
except ImportError:
import ustruct as struct
_BME680_CHIPID = const(0x61)
_BME680_REG_CHIPID = const(0xD0)
_BME680_BME680_COEFF_ADDR1 = const(0x89)
_BME680_BME680_COEFF_ADDR2 = const(0xE1)
_BME680_BME680_RES_HEAT_0 = const(0x5A)
_BME680_BME680_GAS_WAIT_0 = const(0x64)
_BME680_REG_SOFTRESET = const(0xE0)
_BME680_REG_CTRL_GAS = const(0x71)
_BME680_REG_CTRL_HUM = const(0x72)
_BME280_REG_STATUS = const(0xF3)
_BME680_REG_CTRL_MEAS = const(0x74)
_BME680_REG_CONFIG = const(0x75)
_BME680_REG_PAGE_SELECT = const(0x73)
_BME680_REG_MEAS_STATUS = const(0x1D)
_BME680_REG_PDATA = const(0x1F)
_BME680_REG_TDATA = const(0x22)
_BME680_REG_HDATA = const(0x25)
_BME680_SAMPLERATES = (0, 1, 2, 4, 8, 16)
_BME680_FILTERSIZES = (0, 1, 3, 7, 15, 31, 63, 127)
_BME680_RUNGAS = const(0x10)
_LOOKUP_TABLE_1 = (2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0,
2126008810.0, 2147483647.0, 2130303777.0, 2147483647.0, 2147483647.0,
2143188679.0, 2136746228.0, 2147483647.0, 2126008810.0, 2147483647.0,
2147483647.0)
_LOOKUP_TABLE_2 = (4096000000.0, 2048000000.0, 1024000000.0, 512000000.0, 255744255.0, 127110228.0,
64000000.0, 32258064.0, 16016016.0, 8000000.0, 4000000.0, 2000000.0, 1000000.0,
500000.0, 250000.0, 125000.0)
def _read24(arr):
ret = 0.0
for b in arr:
ret *= 256.0
ret += float(b & 0xFF)
return ret
class Adafruit_BME680:
def __init__(self, *, refresh_rate=10):
self._write(_BME680_REG_SOFTRESET, [0xB6])
time.sleep(0.005)
chip_id = self._read_byte(_BME680_REG_CHIPID)
if chip_id != _BME680_CHIPID:
raise RuntimeError('Failed 0x%x' % chip_id)
self._read_calibration()
self._write(_BME680_BME680_RES_HEAT_0, [0x73])
self._write(_BME680_BME680_GAS_WAIT_0, [0x65])
self.sea_level_pressure = 1013.25
self._pressure_oversample = 0b011
self._temp_oversample = 0b100
self._humidity_oversample = 0b010
self._filter = 0b010
self._adc_pres = None
self._adc_temp = None
self._adc_hum = None
self._adc_gas = None
self._gas_range = None
self._t_fine = None
self._last_reading = 0
self._min_refresh_time = 1000 / refresh_rate
@property
def pressure_oversample(self):
return _BME680_SAMPLERATES[self._pressure_oversample]
@pressure_oversample.setter
def pressure_oversample(self, sample_rate):
if sample_rate in _BME680_SAMPLERATES:
self._pressure_oversample = _BME680_SAMPLERATES.index(sample_rate)
else:
raise RuntimeError("Invalid")
@property
def humidity_oversample(self):
return _BME680_SAMPLERATES[self._humidity_oversample]
@humidity_oversample.setter
def humidity_oversample(self, sample_rate):
if sample_rate in _BME680_SAMPLERATES:
self._humidity_oversample = _BME680_SAMPLERATES.index(sample_rate)
else:
raise RuntimeError("Invalid")
@property
def temperature_oversample(self):
return _BME680_SAMPLERATES[self._temp_oversample]
@temperature_oversample.setter
def temperature_oversample(self, sample_rate):
if sample_rate in _BME680_SAMPLERATES:
self._temp_oversample = _BME680_SAMPLERATES.index(sample_rate)
else:
raise RuntimeError("Invalid")
@property
def filter_size(self):
return _BME680_FILTERSIZES[self._filter]
@filter_size.setter
def filter_size(self, size):
if size in _BME680_FILTERSIZES:
self._filter = _BME680_FILTERSIZES[size]
else:
raise RuntimeError("Invalid")
@property
def temperature(self):
self._perform_reading()
calc_temp = (((self._t_fine * 5) + 128) / 256)
return calc_temp / 100
@property
def pressure(self):
self._perform_reading()
var1 = (self._t_fine / 2) - 64000
var2 = ((var1 / 4) * (var1 / 4)) / 2048
var2 = (var2 * self._pressure_calibration[5]) / 4
var2 = var2 + (var1 * self._pressure_calibration[4] * 2)
var2 = (var2 / 4) + (self._pressure_calibration[3] * 65536)
var1 = (((((var1 / 4) * (var1 / 4)) / 8192) *
(self._pressure_calibration[2] * 32) / 8) +
((self._pressure_calibration[1] * var1) / 2))
var1 = var1 / 262144
var1 = ((32768 + var1) * self._pressure_calibration[0]) / 32768
calc_pres = 1048576 - self._adc_pres
calc_pres = (calc_pres - (var2 / 4096)) * 3125
calc_pres = (calc_pres / var1) * 2
var1 = (self._pressure_calibration[8] * (((calc_pres / 8) * (calc_pres / 8)) / 8192)) / 4096
var2 = ((calc_pres / 4) * self._pressure_calibration[7]) / 8192
var3 = (((calc_pres / 256) ** 3) * self._pressure_calibration[9]) / 131072
calc_pres += ((var1 + var2 + var3 + (self._pressure_calibration[6] * 128)) / 16)
return calc_pres/100
@property
def humidity(self):
self._perform_reading()
temp_scaled = ((self._t_fine * 5) + 128) / 256
var1 = ((self._adc_hum - (self._humidity_calibration[0] * 16)) -
((temp_scaled * self._humidity_calibration[2]) / 200))
var2 = (self._humidity_calibration[1] *
(((temp_scaled * self._humidity_calibration[3]) / 100) +
(((temp_scaled * ((temp_scaled * self._humidity_calibration[4]) / 100)) /
64) / 100) + 16384)) / 1024
var3 = var1 * var2
var4 = self._humidity_calibration[5] * 128
var4 = (var4 + ((temp_scaled * self._humidity_calibration[6]) / 100)) / 16
var5 = ((var3 / 16384) * (var3 / 16384)) / 1024
var6 = (var4 * var5) / 2
calc_hum = (((var3 + var6) / 1024) * 1000) / 4096
calc_hum /= 1000
if calc_hum > 100:
calc_hum = 100
if calc_hum < 0:
calc_hum = 0
return calc_hum
@property
def altitude(self):
pressure = self.pressure
return 44330 * (1.0 - math.pow(pressure / self.sea_level_pressure, 0.1903))
@property
def gas(self):
self._perform_reading()
var1 = ((1340 + (5 * self._sw_err)) * (_LOOKUP_TABLE_1[self._gas_range])) / 65536
var2 = ((self._adc_gas * 32768) - 16777216) + var1
var3 = (_LOOKUP_TABLE_2[self._gas_range] * var1) / 512
calc_gas_res = (var3 + (var2 / 2)) / var2
return int(calc_gas_res)
def _perform_reading(self):
if (time.ticks_diff(self._last_reading, time.ticks_ms()) * time.ticks_diff(0, 1)
< self._min_refresh_time):
return
self._write(_BME680_REG_CONFIG, [self._filter << 2])
self._write(_BME680_REG_CTRL_MEAS,
[(self._temp_oversample << 5)|(self._pressure_oversample << 2)])
self._write(_BME680_REG_CTRL_HUM, [self._humidity_oversample])
self._write(_BME680_REG_CTRL_GAS, [_BME680_RUNGAS])
ctrl = self._read_byte(_BME680_REG_CTRL_MEAS)
ctrl = (ctrl & 0xFC) | 0x01
self._write(_BME680_REG_CTRL_MEAS, [ctrl])
new_data = False
while not new_data:
data = self._read(_BME680_REG_MEAS_STATUS, 15)
new_data = data[0] & 0x80 != 0
time.sleep(0.005)
self._last_reading = time.ticks_ms()
self._adc_pres = _read24(data[2:5]) / 16
self._adc_temp = _read24(data[5:8]) / 16
self._adc_hum = struct.unpack('>H', bytes(data[8:10]))[0]
self._adc_gas = int(struct.unpack('>H', bytes(data[13:15]))[0] / 64)
self._gas_range = data[14] & 0x0F
var1 = (self._adc_temp / 8) - (self._temp_calibration[0] * 2)
var2 = (var1 * self._temp_calibration[1]) / 2048
var3 = ((var1 / 2) * (var1 / 2)) / 4096
var3 = (var3 * self._temp_calibration[2] * 16) / 16384
self._t_fine = int(var2 + var3)
def _read_calibration(self):
coeff = self._read(_BME680_BME680_COEFF_ADDR1, 25)
coeff += self._read(_BME680_BME680_COEFF_ADDR2, 16)
coeff = list(struct.unpack('<hbBHhbBhhbbHhhBBBHbbbBbHhbb', bytes(coeff[1:39])))
coeff = [float(i) for i in coeff]
self._temp_calibration = [coeff[x] for x in [23, 0, 1]]
self._pressure_calibration = [coeff[x] for x in [3, 4, 5, 7, 8, 10, 9, 12, 13, 14]]
self._humidity_calibration = [coeff[x] for x in [17, 16, 18, 19, 20, 21, 22]]
self._gas_calibration = [coeff[x] for x in [25, 24, 26]]
self._humidity_calibration[1] *= 16
self._humidity_calibration[1] += self._humidity_calibration[0] % 16
self._humidity_calibration[0] /= 16
self._heat_range = (self._read_byte(0x02) & 0x30) / 16
self._heat_val = self._read_byte(0x00)
self._sw_err = (self._read_byte(0x04) & 0xF0) / 16
def _read_byte(self, register):
return self._read(register, 1)[0]
def _read(self, register, length):
raise NotImplementedError()
def _write(self, register, values):
raise NotImplementedError()
class BME680_I2C(Adafruit_BME680):
def __init__(self, i2c, address=0x77, debug=False, *, refresh_rate=10):
self._i2c = i2c
self._address = address
self._debug = debug
super().__init__(refresh_rate=refresh_rate)
def _read(self, register, length):
result = bytearray(length)
self._i2c.readfrom_mem_into(self._address, register & 0xff, result)
if self._debug:
print("\t${:x} read ".format(register), " ".join(["{:02x}".format(i) for i in result]))
return result
def _write(self, register, values):
if self._debug:
print("\t${:x} write".format(register), " ".join(["{:02x}".format(i) for i in values]))
for value in values:
self._i2c.writeto_mem(self._address, register, bytearray([value & 0xFF]))
register += 1
Skematik: ESP32 dengan BME680
Hubungkan sensor BME680 ke papan pengembangan ESP32 seperti yang ditunjukkan pada diagram skematik berikut.
Skematik: ESP8266 NodeMCU dengan BME680
Jika Anda menggunakan ESP8266 NodeMCU, ikuti diagram berikut.
Kode Program
Setelah mengunggah pustaka-pustaka ke ESP32 atau ESP8266, salin kode berikut ke file `main.py`. Kode ini akan menerbitkan (publish) data suhu, kelembaban, tekanan, dan gas ke topik esp/bme680/temperature, esp/bme680/humidity, esp/bme680/pressure, dan esp/bme680/gas setiap 5 detik.
import time
from umqttsimple import MQTTClient
import ubinascii
import machine
import micropython
import network
import esp
from bme680 import *
from machine import Pin, I2C
esp.osdebug(None)
import gc
gc.collect()
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
mqtt_server = 'XXX.XXX.XXX.XXX'
#EXAMPLE IP ADDRESS
#mqtt_server = '192.168.1.106'
client_id = ubinascii.hexlify(machine.unique_id())
topic_pub_temp = b'esp/bme680/temperature'
topic_pub_hum = b'esp/bme680/humidity'
topic_pub_pres = b'esp/bme680/pressure'
topic_pub_gas = b'esp/bme680/gas'
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))
# ESP8266 - Pin assignment
i2c = I2C(scl=Pin(5), sda=Pin(4))
bme = BME680_I2C(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'{:.2f}'.format(bme.temperature))
#temp = (b'{0:.2f}'.format((bme.temperature) * (9/5) + 32))
hum = (b'{:.2f}'.format(bme.humidity))
pres = (b'{:.2f}'.format(bme.pressure))
gas = (b'{:.2f}'.format(bme.gas/1000))
return temp, hum, pres, gas
#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, gas = read_bme_sensor()
print(temp)
print(hum)
print(pres)
print(gas)
client.publish(topic_pub_temp, temp)
client.publish(topic_pub_hum, hum)
client.publish(topic_pub_pres, pres)
client.publish(topic_pub_gas, gas)
last_message = time.time()
except OSError as e:
restart_and_reconnect()
Cara Kerja Kode
Impor library-library berikut:
import time
from umqttsimple import MQTTClient
import ubinascii
import machine
import micropython
import network
import esp
from bme680 import *
from machine import Pin, I2C
Pada variabel berikut, Anda perlu memasukkan kredensial jaringan Anda (SSID dan password) serta alamat IP broker MQTT Anda.
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
mqtt_server = 'REPLACE_WITH_YOUR_MQTT_BROKER_IP'
Sebagai contoh, alamat IP broker kami adalah: 192.168.1.106.
mqtt_server = '192.168.1.106'
Untuk membuat klien MQTT, kita perlu mendapatkan ID unik ESP. Itulah yang kita lakukan pada baris berikut (ID disimpan dalam variabel `client_id`).
client_id = ubinascii.hexlify(machine.unique_id())
Selanjutnya, buat topik-topik di mana ESP Anda akan menerbitkan data. Dalam contoh kami, ESP akan menerbitkan:
- Suhu ke topik esp/bme680/temperature
- Kelembaban ke topik esp/bme680/humidity
- Tekanan ke topik esp/bme680/pressure
- Gas/kualitas udara ke topik esp/bme680/gas
topic_pub_temp = b'esp/bme680/temperature'
topic_pub_hum = b'esp/bme680/humidity'
topic_pub_pres = b'esp/bme680/pressure'
topic_pub_gas = b'esp/bme680/gas'
Kemudian, buat variabel-variabel berikut:
last_message = 0
message_interval = 5
Variabel `last_message` akan menyimpan waktu terakhir sebuah pesan dikirim. `message_interval` adalah interval waktu antara setiap pesan yang dikirim. Di sini, kami mengaturnya menjadi 5 detik (artinya pesan baru akan dikirim setiap 5 detik). Anda dapat mengubahnya sesuai keinginan.
Setelah itu, hubungkan ESP ke jaringan lokal Anda.
station = network.WLAN(network.STA_IF)
station.active(True)
station.connect(ssid, password)
while station.isconnected() == False:
pass
print('Connection successful')
Buat instance `i2c` pada pin I2C ESP32 untuk berkomunikasi dengan sensor BME680:
i2c = I2C(scl=Pin(22), sda=Pin(21))
Jika Anda menggunakan ESP8266, gunakan baris berikut sebagai gantinya (untuk menggunakan pin I2C default ESP8266).
i2c = I2C(scl=Pin(5), sda=Pin(4))
Buat instance BME680 pada pin I2C ESP:
bme = BME680_I2C(i2c=i2c)
Terhubung ke Broker MQTT
Fungsi `connect_mqtt()` membuat Klien MQTT dan menghubungkannya ke broker Anda.
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
Jika broker MQTT Anda memerlukan username dan password, Anda harus menggunakan baris berikut dan memberikan username serta password broker sebagai argumen.
client = MQTTClient(client_id, mqtt_server, user=your_username, password=your_password)
Restart dan Sambung Ulang
Fungsi `restart_and_reconnect()` me-reset papan ESP32/ESP8266. Fungsi ini akan dipanggil jika kita tidak dapat menerbitkan pembacaan via MQTT (misalnya karena broker terputus).
def restart_and_reconnect():
print('Failed to connect to MQTT broker. Reconnecting...')
time.sleep(10)
machine.reset()
Membaca Sensor BME680
Kami membuat fungsi bernama `read_bme_sensor()` yang mengembalikan pembacaan suhu, kelembapan, tekanan, dan gas terkini dari sensor BME680, serta menangani pengecualian jika gagal membaca sensor.
def read_bme_sensor():
try:
temp = (b'{:.2f}'.format(bme.temperature))
#temp = (b'{0:.2f}'.format((bme.temperature) * (9/5) + 32))
hum = (b'{:.2f}'.format(bme.humidity))
pres = (b'{:.2f}'.format(bme.pressure))
gas = (b'{:.2f}'.format(bme.gas/1000))
return temp, hum, pres, gas
#else:
# return('Invalid sensor readings.')
except OSError as e:
return('Failed to read sensor.')
Menerbitkan Pesan MQTT
Di dalam perulangan `while`, kami menerbitkan pembacaan baru dari BME680 setiap 5 detik.
Pertama, kami memeriksa apakah sudah waktunya untuk mendapatkan pembacaan baru:
if (time.time() - last_message) > message_interval:
Jika ya, minta pembacaan baru dari sensor BME680 dengan memanggil fungsi `read_bme_sensor()`. Suhu disimpan dalam variabel `temp`, kelembaban dalam variabel `hum`, tekanan dalam variabel `pres`, dan gas dalam variabel `gas`.
temp, hum, pres, gas = read_bme_sensor()
Terakhir, terbitkan pembacaan tersebut dengan menggunakan metode `publish()` pada objek `client`. Metode `publish()` menerima argumen berupa topik dan pesan, seperti berikut:
client.publish(topic_pub_temp, temp)
client.publish(topic_pub_hum, hum)
client.publish(topic_pub_pres, pres)
client.publish(topic_pub_gas, gas)
Terakhir, perbarui waktu ketika pesan terakhir dikirim:
last_message = time.time()
Jika ESP32 atau ESP8266 terputus dari broker dan tidak dapat menerbitkan pembacaan, panggil fungsi `restart_and_reconnect()` untuk me-reset papan ESP dan mencoba menyambung kembali ke broker.
except OSError as e:
restart_and_reconnect()
Setelah mengunggah kode, Anda akan mendapatkan pembacaan sensor baru di shell setiap 5 detik.
Sekarang, lanjutkan ke bagian berikutnya untuk menyiapkan Node-RED guna menerima data yang diterbitkan oleh ESP.
Mempersiapkan Dashboard Node-RED
ESP32 atau ESP8266 kini menerbitkan data sensor setiap 5 detik ke topik `esp/bme680/temperature`, `esp/bme680/humidity`, `esp/bme680/pressure`, dan `esp/bme680/gas`. Anda dapat menggunakan dashboard apa pun yang mendukung MQTT, atau perangkat lain yang mendukung MQTT, untuk berlangganan (subscribe) ke topik-topik tersebut dan menerima datanya.
Sebagai contoh, kami akan membuat alur (flow) sederhana menggunakan Node-RED untuk berlangganan ke topik-topik tersebut dan menampilkan datanya pada gauge. Dengan Node-RED berjalan di Raspberry Pi Anda, buka alamat IP Raspberry Pi diikuti dengan `:1880`.
http://raspberry-pi-ip-address:1880
Antarmuka Node-RED akan terbuka. Seret empat node "mqtt in", empat node "gauge", dan empat node "text" ke dalam alur (flow).
Klik node MQTT dan edit propertinya.
Kolom Server merujuk ke broker MQTT. Dalam kasus kami, broker MQTT adalah Raspberry Pi itu sendiri, sehingga diatur ke `localhost:1883`. Jika Anda menggunakan broker MQTT cloud, Anda harus mengubah kolom tersebut.
Masukkan topik yang ingin Anda langgani dan QoS-nya. Node MQTT ini berlangganan ke topik `esp/bme680/temperature`.
Klik pada node mqtt in lainnya dan edit propertinya dengan server yang sama, namun untuk topik lain: `esp/bme680/humidity`, `esp/bme680/pressure`, dan `esp/bme680/gas`.
Klik pada node gauge dan edit propertinya untuk setiap jenis pembacaan. Node berikut diatur untuk pembacaan suhu. Edit node gauge lainnya untuk pembacaan kelembaban, tekanan, dan gas.
Hubungkan node-node Anda seperti yang ditunjukkan di bawah:
Terakhir, deploy alur Anda (tekan tombol di sudut kanan atas).
Sebagai alternatif, Anda dapat membuka Menu > Import dan menyalin kode berikut ke Clipboard untuk membuat alur Node-RED Anda.
[{"id":"3b7f947c.9759ec","type":"mqtt in","z":"254c9c97.f85b34","name":"","topic":"esp/bme680/temperature","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":470,"y":2640,"wires":[["b87b21c3.96672"]]},{"id":"b87b21c3.96672","type":"ui_gauge","z":"254c9c97.f85b34","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":690,"y":2640,"wires":[]},{"id":"f92248f4.545778","type":"mqtt in","z":"254c9c97.f85b34","name":"","topic":"esp/bme680/humidity","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":460,"y":2700,"wires":[["4114a401.5ac69c"]]},{"id":"4114a401.5ac69c","type":"ui_gauge","z":"254c9c97.f85b34","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":680,"y":2700,"wires":[]},{"id":"ad51f895.2c2848","type":"mqtt in","z":"254c9c97.f85b34","name":"","topic":"esp/bme680/pressure","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":460,"y":2760,"wires":[["3a95123b.66405e"]]},{"id":"c074e688.198b78","type":"mqtt in","z":"254c9c97.f85b34","name":"","topic":"esp/bme680/gas","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":440,"y":2820,"wires":[["d3539c06.00a17"]]},{"id":"3a95123b.66405e","type":"ui_text","z":"254c9c97.f85b34","group":"37de8fe8.46846","order":2,"width":0,"height":0,"name":"","label":"Pressure","format":"{{msg.payload}} hPa","layout":"row-spread","x":680,"y":2760,"wires":[]},{"id":"d3539c06.00a17","type":"ui_text","z":"254c9c97.f85b34","group":"37de8fe8.46846","order":3,"width":0,"height":0,"name":"","label":"Gas","format":"{{msg.payload}} KOhm","layout":"row-spread","x":670,"y":2820,"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":"BME680","tab":"53b8c8f9.cfbe48","order":1,"disp":true,"width":"6","collapse":false},{"id":"53b8c8f9.cfbe48","type":"ui_tab","z":"","name":"Home","icon":"dashboard","order":5,"disabled":false,"hidden":false}]
Demonstrasi
Buka alamat IP Raspberry Pi Anda diikuti dengan `:1880/ui`.
http://raspberry-pi-ip-address:1880/ui
Anda akan mendapatkan akses ke pembacaan suhu, kelembapan, tekanan, dan gas BME680 terkini di Dashboard. Anda dapat menggunakan node tipe dashboard lainnya untuk menampilkan data dengan cara yang berbeda.
Selesai! Kini papan ESP32 atau ESP8266 Anda telah menerbitkan pembacaan suhu, kelembapan, tekanan, dan gas dari BME680 ke Node-RED via MQTT menggunakan MicroPython.
Siap Untuk Membuat Proyek Impianmu Menjadi Kenyataan?
Klik di sini untuk chat langsung via WhatsApp dan dapatkan dukungan langsung dari tim ahli kami!






















0 on: "Tutorial MicroPython ESP32/ESP8266 - Publish Suhu, Kelembaban, dan Gas BME680 via MQTT"