Obsah

Výroba Wi-Fi teploměru s ESP8266

Wi-Fi teploměr jsem skládal z těchto součástek:

Pro programování jsem na destičku esp8266 nahrál Micropython. Cílem je, aby se destička připojovala k Mosquitto a posílala tak zprávy skrze MQTT protokol.

Warning

Edit 2.1.2024: Nakonec jsem Micropython opustil, protože se mi nepodařilo dosáhnout stabilního obnovování připojení při výpadku Wi-Fi či MQTT. Původní příspěvek ponechávám, nicméně na závěr připojím kód v C++ pro vyčítání dat ze senzoru a automatickou obnovu spojení v případě výpadku.

Jako výborný zdroj posloužil článek ESP32 MQTT – Publish and Subscribe with Arduino IDE senzor BME280. Nicméně v článku nepoužívají Python - kód jsem čerpat tedy odjinud.

Později jsem objevil tento článek MicroPython: BME280 with ESP32 and ESP8266 (Pressure, Temperature, Humidity) ze kterého jsem čerpal jak zapojení senzoru, tak kód Pythonu pro senzor.

Připojení destičky k Wi-Fi

Micropython počítá se 2 soubory:

Snažil jsem se pochopit na co který použít. Pochopil jsem, že nejprve se po startu načte boot.py a potom teprve main.py. V rámci best practises je doporučeno používat oba, ale vzápětí jsem se dočetl, že když se rozhodnu použít jenom main.py a do něj nahrát veškerý svůj kód, tak je to také ok.

Zatím jsem to tedy udělal tak, že nastavení a připojení Wi-Fi jsem nahrál do boot.py a čtení dat z čidla a obsluha MQTT je zase main.py.

Nyní tedy kód boot.py pro připojení k Wi-Fi:

# This file is executed on every boot (including wake-boot from deepsleep)
#import esp
#esp.osdebug(None)
import uos, machine
#uos.dupterm(None, 1) # disable REPL on UART(0)
import gc
#import webrepl
#webrepl.start()
gc.collect()
 
 
SSID = 'Wi-fi'
PASS = 'super_secret_password'
 
def connect():
    import network
    sta_if = network.WLAN(network.STA_IF)
    if not sta_if.isconnected():
        sta_if.active(True)
        sta_if.connect(SSID, PASS)
        while not sta_if.isconnected():
            pass # wait till connection
    print('network config:', sta_if.ifconfig())
 
connect()

První co mě napadlo je, jak ošetřit to, že se Wi-Fi odpojí - třeba spadne síť. Jak vyřešit opětovný reconnect. Dobrá zpráva je, že není potřeba dělat nic a současný kód stačí. Testoval jsem kód tak, že jsem vypínal a zapínal Wi-Fi na mém routeru a vždycky, když jsem Wi-Fi zapnul, tak se připojilo i zařízení ESP8266 aniž bych musel cokoli dělat. Což mě docela mile překvapilo.

Změna Wi-Fi sítě

Narazil jsem na zajímavý problém v kódu. Zdá se, že ESP8266 si po úspěšném připojení k Wi-Fi ukládá SSID sítě a heslo do paměti. Protože v rámci testování jsem vytvořil jinou síť, změnil jsem v kódu údaje a ESP8266 se ne a ne připojit k nové síti. Byl jsem v situaci, kdy obě sítě běžely a ESP8266 se připojovalo pořád k původní, i když v kódu po ní nic nezůstalo. A nepomohl mi ani reset zařízení či odpojení od napájení.

Nakonec jsem to vyřešil v kódu tak, že jsem na první spuštění upravil funkci connect() takto:

def connect():
    import network
    sta_if = network.WLAN(network.STA_IF)
    sta_if.connect(SSID, PASS)
    if not sta_if.isconnected():
        sta_if.active(True)
        sta_if.connect(SSID, PASS)
        while not sta_if.isconnected():
            pass # wait till connection
    print('network config:', sta_if.ifconfig())

Jakmile jsme se připojil k nové Wi-Fi, tak jsem kód vrátil do původního stavu. Nechtělo se mi hlouběji zkoumat, jak to vlastně funguje a hledat elegantnější řešení, protože pro můj účel mi to stačilo.

BME280 MicroPython knihovna

Pro senzor BME280 je potřeba knihovna, která bude umět se senzorem pracovat. Knihovna musí být pro Python a našel jsem ji v tomto zdroji MicroPython: BME280 with ESP32 and ESP8266 (Pressure, Temperature, Humidity). Stačí ji nakopírovat do ESP8266 a uložit jako soubor 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)) +
         16384) >> 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)

Čtení dat ze senzoru BME280

S knihovnou je pak jednoduché vyčítat hodnoty ze senzoru:

# Complete project details at https://RandomNerdTutorials.com
 
from machine import Pin, I2C
from time import sleep
import BME280
 
# 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)
 
while True:
  bme = BME280.BME280(i2c=i2c)
  temp = bme.temperature
  hum = bme.humidity
  pres = bme.pressure
  # uncomment for temperature in Fahrenheit
  #temp = (bme.read_temperature()/100) * (9/5) + 32
  #temp = str(round(temp, 2)) + 'F'
  print('Temperature: ', temp)
  print('Humidity: ', hum)
  print('Pressure: ', pres)
 
  sleep(5)

Posílání dat skrze MQTT v MicroPythonu

Nejprve jsem si nainstaloval MQTT server Mosquitto a napsal jednoduchý kód v MicroPythonu pro komunikaci, dle článku Secrets of MicroPython: MQTT on ESP32.

Ochrana kódu proti odpojení od MQTT serveru

Wi-fi a její automatické připojení je sice vyřešeno, ale jiná věc je, že může sehlat spojení s MQTT serverem. Prostě a jednoduše MQTT server třeba spadne a zařízení nemůže naslouchat nebo posílat data.

V článku MicroPython – Getting Started with MQTT on ESP32/ESP8266 jsem se inspiroval, jak toto ošetřit. Řešení není až tak uspokojivé, jak jsem si představoval, protože ve chvíli, kdy se připojení rozpadne a dojde na MQTT k chybě, restartuje se celé zařízení ESP8266. Moje představa byla, že se zařízení bude zkoušet obnovit jenom MQTT připojení.

Protože se mi to nedařilo, tak jsem se prozatím spokojil s restartem celého zařízení. Výsledný kód tak vypadá takto:

from umqtt.simple import MQTTClient
from time import sleep
from machine import Pin, I2C
from time import sleep
import ubinascii
import BME280
 
 
 
CLIENT_NAME = ubinascii.hexlify(machine.unique_id())
BROKER_ADDR = '192.168.1.120'
 
 
print(CLIENT_NAME)
 
 
# ESP8266 - Pin assignment
i2c = I2C(scl=Pin(5), sda=Pin(4), freq=10000)
 
 
def connect_and_subscribe():
    mqttc = MQTTClient(CLIENT_NAME, BROKER_ADDR, keepalive=60)
    mqttc.connect()
    return mqttc
 
def restart_and_reconnect():
    print('Failed to connect to MQTT broker. Reconnecting...')
    sleep(10)
    machine.reset()
 
 
try:
    mqttc = connect_and_subscribe()
except OSError as e:
    restart_and_reconnect()
 
 
BTN_TOPIC = b'dum/pracovna/svetelnost'
 
 
while True:
    bme = BME280.BME280(i2c=i2c)
    temp = bme.temperature
    hum = bme.humidity
    pres = bme.pressure
    print('Temperature: ', temp , ' hum: ', hum )
 
    try:
        mqttc.publish( BTN_TOPIC, str('temp:'+ temp + ', hum:' + hum + ', pres: ' + pres).encode() )
    except OSError as e:
        restart_and_reconnect()
 
    sleep(5)

Úprava kódu po zapnutí autentizace na MQTT

Nejprve jsem musel přidat proměné s uživatelským jménem a heslem:

USER = b'majordomus'
PASSWORD = b'supersecretpassword'

A poté upravit připojení tak, aby bralo proměnné v potaz:

mqttc = MQTTClient(CLIENT_NAME, BROKER_ADDR, 1883, USER, PASSWORD, keepalive=60)

Problém s přerušením spojení s MQTT serverem

V provozu se mi ukázalo, že když dojde k přerušení spojení s MQTT serverem, tak mi nezbyde nic jiného, než zařízení fyzicky resetovat. Což není zrovna příjemný počin - zejména, pokud budu mít více takových zařízení. Hledal jsem způsob, jak kontrolovat, zda nedošlo k přerušení spojení. Protože výjimky pro spuštění restart_and_reconnect nebyla v mém případě spuštěna.

Proto jsem vytvořil funkci, která kontroluje, zda je připojení aktivní. V rámci funkce posílám testovací zprávu na mqtt servrer. Podmínkou je, že řeším Quality Of Service = 1, tedy čekám na odpověď ze serveru, zda byla zpráva opravdu doručena. Pokud nebyla, vyvolá se výjimka.

def is_connected(mqttc):
    try:
        mqttc.publish(b"connection_test", b"test", qos=1)
        return True
    except OSError:
        return False

Warning

Edit 2.1.2024: Nakonec se ukázalo, že ani toto ošetření nefunguje. Zařízení se prostě po restartu MQTT serveru nebo Wi-Fi sítě nepřipojí. A co ještě hůř - do 14 dnů od spuštění se náhodně odpojí - aniž by došlo k výpadku na síti. Samozřejmě může dojít k mikrovýpadku, který ani nezaznamenám a to může být příčina.

Proto jsem použití MicroPython přehodnotil. Vede mě k tomu víc důvodů:

  • softwarová podpora - je mnoho kódu v C pro Arduino, ale málo pro MicroPython. Pro další vzdělávání je to problém. Navíc u MicroPythonu se musí využívat knihovny, které jsou nějak minimalizované. Proto kód pro Micropython není jednoduše přenesitelný na Python a naopak, jak jsem si mylně sliboval.
  • výkon/rychlost - program zkompilovaný pro C++ mi přijde, že se připojuje rychleji k síti a MQTT
  • bezpečnost použití - kód v C je kompilovaný. Takže když někdo přijde k zařízení s esp8266, nebude mít jednoduché vyčíst hesla k Wi-Fi, certifikát atp. Kdežto při použití Micro Pythonu to bude velmi jednoduché. Stačí zařízení na chvíli připojit k USB portu a program a certifikát v otevřené podobě.
  • od C++ očekávám větší stabilitu - co se týče čipu esp8266

Proto jsem myšlenku použití Micro Pythonu pro moje účely opustil a raději si napíšu program v C++. V rámci stránky připojuji i kód v C++ pro esp8266.

Finální verze kódu po dalších úpravách

from umqtt.simple import MQTTClient
from time import sleep
from machine import Pin, I2C
from time import sleep
import ubinascii
import BME280
import ujson
 
 
CLIENT_NAME = ubinascii.hexlify(machine.unique_id())
BROKER_ADDR = '192.168.1.20'
USER        = b'majordomus'
PASSWORD    = b'MQTTuserPassword'
MQTT_TOPIC  = b'dum/sensor/bme280'
 
 
 
with open("key.der", 'rb') as f:
            key = f.read()
 
with open("cert.der", 'rb') as f:
            cert = f.read()
 
 
ssl_params = dict()
ssl_params["cert"] = cert
ssl_params["key"] = key
 
 
print(CLIENT_NAME)
 
 
# ESP8266 - Pin assignment
i2c = I2C(scl=Pin(5), sda=Pin(4), freq=10000)
 
 
 
def connect_and_subscribe():
    mqttc = MQTTClient(CLIENT_NAME, BROKER_ADDR, 8883, USER, PASSWORD, ssl=True, ssl_params=ssl_params, keepalive=60)
    mqttc.connect()
    return mqttc
 
def restart_and_reconnect():
    print('Failed to connect to MQTT broker. Reconnecting...')
    sleep(10)
    # Místo resetování ESP8266 zkuste znovu navázat spojení s MQTT serverem
    try:
        mqttc = connect_and_subscribe()
    except OSError as e:
        restart_and_reconnect()
        # alternativou je reset celého zařízení
        #machine.reset()
 
 
def is_connected(mqttc):
    try:
        mqttc.publish(b"connection_test", b"test", qos=1)
        return True
    except OSError:
        return False
 
 
try:
    mqttc = connect_and_subscribe()
except OSError as e:
    restart_and_reconnect()
 
 
 
while True:
    bme = BME280.BME280(i2c=i2c)
 
    data = {}
    data['temperature'] = bme.temperature
    data['humidity']  = bme.humidity
    data['pressure'] = bme.pressure
 
 
    if not is_connected(mqttc):
        restart_and_reconnect()
 
 
    print(ujson.dumps(data))
 
    try:
        mqttc.publish( MQTT_TOPIC, str(ujson.dumps(data)).encode() )
    except OSError as e:
        restart_and_reconnect()
 
    sleep(5)

Zdroje s inspirací

Kód pro teploměr s ESP8266 napsaný v C++

I když jsem začal stránku s tím, že budu používat MicroPython, přešel jsem kvůli stabilitě a dalším důvodům k C++. Co se týče certifikátů, tak řešení s certifikáty je popsáno na stránce Self signed certificate. A tady už je kód v C++ pro ESP8266 kmpilovaný v Arduino IDE:

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <ArduinoJson.h>
 
#include "client_crt.h"  // client certificate
#include "client_key.h"  // client key
 
 
 
const char* WiFiName = "IOT-WiFi";
const char* WiFiPassword = "supersecretpassword";
 
 
// MQTT connection
const char* clientName = "thermometer";
const char* mqttBroker = "192.168.0.120";
const int mqttPort = 8883;
const char* mqttUser = "user";
const char* mqttPassword = "supersecretpassword";
const char* mqttTopic = "/sensor/bme280";
 
Adafruit_BME280 bme;
 
 
 
WiFiClientSecure espClient;
PubSubClient mqttClient(espClient);
 
 
 
 
void setupWiFi() {
 
 
  WiFi.mode(WIFI_STA);
  WiFi.begin(WiFiName, WiFiPassword);
 
  // connect to WiFi
  Serial.print("Connecting to WiFi...");
 
  // wait for connecting to the server
  // during this waiting it will write dot to serial link
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
 
  // write new line, Wi-Fi network and IP address of connection
  Serial.println("");
  Serial.print("Connected to WiFi ");
  Serial.println(WiFiName);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
 
  WiFi.setAutoReconnect(true);
  WiFi.persistent(true);
 
}
 
 
void reconnectMQTT() {
 
  int retryCount = 0;
 
  while (!mqttClient.connected()) {
 
    Serial.println("Attempting to connect to MQTT server...");
 
    // SSL/TLS certificate and key settings
    // The BearSSL::X509List and BearSSL::PrivateKey objects must be in scope (alive)
    // for the duration of their usage by espClient. If these objects are defined
    // within a separate function and used here, they will be destroyed when that 
    // function exits, leading to undefined behavior and potential device resets.
    // A solution to this is to declare them as global variables if they need to 
    // be used across multiple functions.
    BearSSL::X509List cert(cert_der, cert_der_len);
    BearSSL::PrivateKey key(key_der, key_der_len);
 
 
 
    espClient.setInsecure();
    espClient.setClientRSACert(&cert, &key);  // Setting the client's cert and key
 
    mqttClient.setServer(mqttBroker, mqttPort);
 
 
    if (mqttClient.connect(clientName, mqttUser, mqttPassword)) {
 
      Serial.println("Connected to MQTT");
 
    } else {
      Serial.print("Failed, rc=");
      Serial.print(mqttClient.state());
      Serial.println(" trying again in 5 seconds");
      delay(5000); // 5-second delay between attempts
 
      retryCount++;
 
      if (retryCount > 5) {  // Restart the ESP if it fails to connect 5 times
        //ESP.restart();
      }
 
    }
  }
 
}
 
 
 
void setup() {
 
 
  // put your setup code here, to run once:
 
 
  // start communication on serial line
  Serial.begin(9600);
 
 
  // Inicializace BME280
  if (!bme.begin(0x76)) {  // BME280 I2C  sensor address
    Serial.println("Nepodařilo se najít senzor BME280!");
    while (1);
  }
 
  setupWiFi();
 
  reconnectMQTT();
 
}
 
 
void loop() {
  // put your main code here, to run repeatedly:
 
  if (!mqttClient.connected()) {
    reconnectMQTT();
  }
  mqttClient.loop();
 
 
  // send message each 5 sec
  static unsigned long lastMsg = 0;
  unsigned long now = millis();
 
  if (now - lastMsg > 5000) {
    lastMsg = now;
 
    // read data from BME280
    float humidity = bme.readHumidity();
    float temperature = bme.readTemperature();
    float pressure = bme.readPressure() / 100.0F;
 
 
    // Vytvoření JSON objektu
    StaticJsonDocument<200> doc;
 
    doc["humidity"] = humidity;
    doc["temperature"] = temperature;
    doc["pressure"] = pressure;
 
    // Serializace JSON objektu do řetězce
    char jsonBuffer[200];
    serializeJson(doc, jsonBuffer);
 
    // Odeslání JSON řetězce na MQTT server
    mqttClient.publish(mqttTopic, jsonBuffer);
 
  }
 
 
}

Warning

Ještě jsem se poučil s MQTT s pojmenováním zařízení. Protože jsem měl už zařízení se stejným názvem thermometer na síti, tak se mi zařízení neustále odpojovalo od MQTT. Přejmenování zařízení problém vyřešilo.