Artikel ini adalah panduan langkah demi langkah untuk membangun Web Server mandiri pada ESP32 atau ESP8266 NodeMCU yang menampilkan pembacaan sensor BME680 menggunakan firmware MicroPython. Arduino Indonesia akan membuat Web Server di ESP32/ESP8266 yang responsif di perangkat seluler dan dapat diakses oleh perangkat apa pun yang memiliki browser dalam jaringan lokal Anda.
Modul Sensor Lingkungan BME680
BME680 adalah sensor lingkungan yang menggabungkan sensor gas, tekanan, kelembapan, dan suhu. Sensor gasnya dapat mendeteksi berbagai jenis gas, seperti senyawa organik yang mudah menguap (Volatile Organic Compounds/VOC). Oleh karena itu, BME680 cocok digunakan untuk pengendalian kualitas udara dalam ruangan. BME680 mendukung antarmuka komunikasi I2C dan SPI.
BME680 dengan Antarmuka I2C
Sensor ini berkomunikasi menggunakan protokol I2C, sehingga koneksi kabelnya sangat sederhana. Anda dapat menggunakan pin I2C default pada ESP32 atau ESP8266 seperti yang ditunjukkan pada tabel berikut:
Komponen yang Diperlukan
Untuk proyek ini, Anda perlu menghubungkan modul sensor BME680 ke pin I2C ESP32 atau ESP8266. Berikut adalah daftar komponen yang diperlukan untuk tutorial ini:
- Modul sensor BME680
- ESP32 atau ESP8266 (baca: [Perbandingan ESP32 vs ESP8266](URL))
- Papan breadboard
- Kabel jumper
Skematik – ESP32
Ikuti diagram skematik berikut jika Anda menggunakan papan ESP32:
Skematik – ESP8266
Ikuti diagram skematik berikut jika Anda menggunakan papan ESP8266:
Pustaka (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`).
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
Ikuti serangkaian instruksi berikut sesuai dengan IDE yang Anda gunakan:
- Unggah Pustaka BME680 dengan uPyCraft IDE
- Unggah Pustaka BME680 dengan Thonny IDE
A. Unggah Pustaka BME680 dengan uPyCraft IDE
Bagian ini menunjukkan cara mengunggah pustaka menggunakan uPyCraft IDE. Jika Anda menggunakan Thonny IDE, silakan baca bagian berikutnya.
1. Buat file baru dengan menekan tombol New File (1).
2. Salin kode pustaka BME680 ke dalam file tersebut. Kode pustaka BME680 dapat ditemukan [di sini](tautan-kode).
3. Setelah menyalin kode, simpan file dengan menekan tombol Save (2).
4. Beri nama file baru ini `bme680.py` dan tekan OK.
5. Klik tombol Download and Run.
File tersebut akan tersimpan di folder perangkat dengan nama `bme680.py`, seperti yang ditunjukkan pada gambar berikut.
Sekarang, Anda dapat menggunakan fungsi-fungsi pustaka dalam kode Anda dengan mengimpornya.
B. Unggah Pustaka BME680 dengan Thonny IDE
Jika Anda menggunakan Thonny IDE, ikuti langkah-langkah berikut:
1. Salin kode pustaka ke dalam file baru. Kode pustaka BME680 dapat ditemukan [di sini](tautan-kode).
2. Buka menu File > Save as….
3. Pilih opsi simpan ke "MicroPython device" (Simpan ke perangkat MicroPython):
4. Beri nama file Anda `bme680.py` dan tekan tombol OK:
Gambar
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:
Gambar
Setelah pustaka berhasil diunggah ke papan ESP, Anda dapat menggunakan fungsinya dalam kode dengan mengimpornya.
Kode Web Server – Suhu, Kelembapan, Tekanan, dan Kualitas Udara (Gas) dari BME680
Setelah memahami cara membaca suhu, kelembaban, tekanan, dan gas dari sensor BME680, kita akan menampilkan data tersebut pada sebuah web server yang dapat diakses dalam jaringan lokal Anda.
Untuk contoh ini, Anda membutuhkan tiga file:
1. bme680.py: File ini berisi semua metode untuk menggunakan sensor BME680. Ini adalah file yang telah Anda unggah sebelumnya.
2. boot.py: Dijalankan saat perangkat dinyalakan dan mengatur beberapa opsi konfigurasi seperti kredensial jaringan, mengimpor pustaka, mengatur pin, dll.
3. main.py: Ini adalah skrip utama tempat kita akan menangani web server. Skrip ini dieksekusi segera setelah `boot.py`.
Catatan: Adalah praktik yang baik untuk menyertakan file `boot.py` dan `main.py`. Namun, jika Anda lebih suka, Anda dapat menggabungkan semua kode dalam satu file `main.py`.
boot.py
Buat file baru di IDE Anda dengan nama `boot.py` dan salin kode berikut.
try:
import usocket as socket
except:
import socket
from time import sleep
from machine import Pin, I2C
import network
import esp
esp.osdebug(None)
import gc
gc.collect()
from bme680 import *
# ESP32 - Pin assignment
i2c = I2C(scl=Pin(22), sda=Pin(21))
# ESP8266 - Pin assignment
#i2c = I2C(scl=Pin(5), sda=Pin(4))
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
station = network.WLAN(network.STA_IF)
station.active(True)
station.connect(ssid, password)
while station.isconnected() == False:
pass
print('Connection successful')
print(station.ifconfig())
File ini mengimpor pustaka yang diperlukan, mendefinisikan pin I2C untuk terhubung ke sensor, serta menyambungkan ESP ke jaringan Anda. Dalam kode, kami menggunakan pin I2C default untuk ESP32:
i2c = I2C(scl=Pin(22), sda=Pin(21))
Jika Anda menggunakan ESP8266, komentari baris sebelumnya dan hapus komentar pada baris berikut:
i2c = I2C(scl=Pin(5), sda=Pin(4))
Kemudian, masukkan kredensial jaringan Anda (SSID dan password Wi-Fi) ke dalam variabel berikut:
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
main.py
Di dalam file `main.py` inilah kita akan membuat web server dan menangani permintaan (requests). Salin kode berikut ke file `main.py` Anda.
def web_page():
bme = BME680_I2C(i2c=i2c)
html = """<html><head><title>ESP with BME680</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,"><style>body { text-align: center; font-family: "Trebuchet MS", Arial;}
table { border-collapse: collapse; margin-left:auto; margin-right:auto; }
th { padding: 12px; background-color: #0043af; color: white; }
tr { border: 1px solid #ddd; padding: 12px; }
tr:hover { background-color: #bcbcbc; }
td { border: none; padding: 12px; }
.sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px;
</style></head><body><h1>ESP with BME680</h1>
<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>
<tr><td>Temp. Celsius</td><td><span class="sensor">""" + str(round(bme.temperature, 2)) + """ C</span></td></tr>
<tr><td>Temp. Fahrenheit</td><td><span class="sensor">""" + str(round((bme.temperature) * (9/5) + 32, 2)) + """ F</span></td></tr>
<tr><td>Pressure</td><td><span class="sensor">""" + str(round(bme.pressure, 2)) + """ hPa</span></td></tr>
<tr><td>Humidity</td><td><span class="sensor">""" + str(round(bme.humidity, 2)) + """ %</span></td></tr>
<tr><td>Gas</td><td><span class="sensor">""" + str(round(bme.gas/1000, 2)) + """ KOhms</span></td></tr></body></html>"""
return html
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)
while True:
try:
if gc.mem_free() < 102000:
gc.collect()
conn, addr = s.accept()
conn.settimeout(3.0)
print('Got a connection from %s' % str(addr))
request = conn.recv(1024)
conn.settimeout(None)
request = str(request)
print('Content = %s' % request)
response = web_page()
conn.send('HTTP/1.1 200 OK\n')
conn.send('Content-Type: text/html\n')
conn.send('Connection: close\n\n')
conn.sendall(response)
conn.close()
except OSError as e:
conn.close()
print('Connection closed')
Kode ini membuat server socket yang mengirimkan halaman HTML berisi pembacaan sensor terbaru setiap kali ada permintaan yang diterima di alamat IP ESP32 atau ESP8266.
Pada dasarnya, kita memiliki fungsi bernama `web_page()` yang mengembalikan kode HTML untuk membangun halaman web dengan data sensor terbaru. Teks HTML ini membuat tabel untuk menampilkan pembacaan sensor:
def web_page():
bme = BME680_I2C(i2c=i2c)
html = """<html><head><title>ESP with BME680</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,"><style>body { text-align: center; font-family: "Trebuchet MS", Arial;}
table { border-collapse: collapse; margin-left:auto; margin-right:auto; }
th { padding: 12px; background-color: #0043af; color: white; }
tr { border: 1px solid #ddd; padding: 12px; }
tr:hover { background-color: #bcbcbc; }
td { border: none; padding: 12px; }
.sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px;
</style></head><body><h1>ESP with BME680</h1>
<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>
<tr><td>Temp. Celsius</td><td><span class="sensor">""" + str(round(bme.temperature, 2)) + """ C</span></td></tr>
<tr><td>Temp. Fahrenheit</td><td><span class="sensor">""" + str(round((bme.temperature) * (9/5) + 32, 2)) + """ F</span></td></tr>
<tr><td>Pressure</td><td><span class="sensor">""" + str(round(bme.pressure, 2)) + """ hPa</span></td></tr>
<tr><td>Humidity</td><td><span class="sensor">""" + str(round(bme.humidity, 2)) + """ %</span></td></tr>
<tr><td>Gas</td><td><span class="sensor">""" + str(round(bme.gas/1000, 2)) + """ KOhms</span></td></tr></body></html>"""
return html
Selanjutnya, kita membuat server socket yang mengirimkan HTML saat menerima permintaan. Teks HTML tersebut kemudian disimpan dalam variabel `response`:
response = web_page()
Dan dikirimkan ke klien:
conn.sendall(response)
Demonstrasi Web Server
Unggah semua file sebelumnya ke papan ESP32 atau ESP8266 Anda dengan urutan sebagai berikut:
1. `bme680.py`
2. `boot.py`
3. `main.py`
Setelah kode terunggah, alamat IP ESP32 atau ESP8266 Anda akan ditampilkan di Serial Monitor.
Buka web browser di dalam jaringan lokal Anda dan ketikkan alamat IP ESP (dalam contoh kami, IP-nya adalah `http://192.168.1.114`). Anda akan mendapatkan halaman yang menampilkan pembacaan sensor terbaru seperti pada gambar berikut.
Halaman Web dengan Pembaruan Otomatis (Auto-refresh)
Dengan skrip web server yang disediakan dalam proyek ini, Anda perlu me-refresh halaman web secara manual untuk melihat pembacaan terbaru. Jika Anda menambahkan meta tag berikut di dalam tag `<head></head>` pada HTML, halaman web Anda akan memperbarui diri secara otomatis setiap 10 detik:
<meta http-equiv="refresh" content="10">
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: "Panduan Lengkap MicroPython ESP32/ESP8266 - Web Server BME680 sebagai Stasiun Cuaca"