Rozdíly
Zde můžete vidět rozdíly mezi vybranou verzí a aktuální verzí dané stránky.
| Obě strany předchozí revize Předchozí verze Následující verze | Předchozí verze | ||
| it:iot:esp8266:vyroba-wifi-teplomeru [2023/01/04 12:53] – Petr Nosek | it:iot:esp8266:vyroba-wifi-teplomeru [2024/01/02 17:17] (aktuální) – Petr Nosek | ||
|---|---|---|---|
| Řádek 1: | Řádek 1: | ||
| ====== Výroba Wi-Fi teploměru s ESP8266 ====== | ====== Výroba Wi-Fi teploměru s ESP8266 ====== | ||
| - | Wi-Fi teploměr | + | Wi-Fi teploměr |
| * [[https:// | * [[https:// | ||
| Řádek 8: | Řádek 8: | ||
| 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. | 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. | ||
| + | <adm warning> | ||
| + | Jako výborný zdroj posloužil článek [[https:// | ||
| + | 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 [[https:// | ||
| + | ===== Připojení destičky k Wi-Fi ===== | ||
| + | |||
| + | Micropython počítá se 2 soubory: | ||
| + | |||
| + | * boot.py | ||
| + | * main.py | ||
| + | |||
| + | 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: | ||
| + | |||
| + | <code python> | ||
| + | # This file is executed on every boot (including wake-boot from deepsleep) | ||
| + | #import esp | ||
| + | # | ||
| + | import uos, machine | ||
| + | # | ||
| + | import gc | ||
| + | #import webrepl | ||
| + | # | ||
| + | gc.collect() | ||
| + | |||
| + | |||
| + | SSID = ' | ||
| + | PASS = ' | ||
| + | |||
| + | def connect(): | ||
| + | import network | ||
| + | sta_if = network.WLAN(network.STA_IF) | ||
| + | if not sta_if.isconnected(): | ||
| + | sta_if.active(True) | ||
| + | sta_if.connect(SSID, | ||
| + | while not sta_if.isconnected(): | ||
| + | pass # wait till connection | ||
| + | print(' | ||
| + | | ||
| + | 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: | ||
| + | |||
| + | <code python> | ||
| + | def connect(): | ||
| + | import network | ||
| + | sta_if = network.WLAN(network.STA_IF) | ||
| + | sta_if.connect(SSID, | ||
| + | if not sta_if.isconnected(): | ||
| + | sta_if.active(True) | ||
| + | sta_if.connect(SSID, | ||
| + | while not sta_if.isconnected(): | ||
| + | pass # wait till connection | ||
| + | print(' | ||
| + | </ | ||
| + | |||
| + | 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 [[https:// | ||
| + | |||
| + | <code python> | ||
| + | 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: | ||
| + | """ | ||
| + | |||
| + | Allows reading and writing 8-bit, 16-bit, and byte array values to | ||
| + | registers on the device.""" | ||
| + | |||
| + | def __init__(self, | ||
| + | """ | ||
| + | the specified I2C interface object.""" | ||
| + | self._address = address | ||
| + | self._i2c = i2c | ||
| + | |||
| + | def writeRaw8(self, | ||
| + | """ | ||
| + | value = value & 0xFF | ||
| + | self._i2c.writeto(self._address, | ||
| + | |||
| + | def write8(self, | ||
| + | """ | ||
| + | b=bytearray(1) | ||
| + | b[0]=value & 0xFF | ||
| + | self._i2c.writeto_mem(self._address, | ||
| + | |||
| + | def write16(self, | ||
| + | """ | ||
| + | value = value & 0xFFFF | ||
| + | b=bytearray(2) | ||
| + | b[0]= value & 0xFF | ||
| + | b[1]= (value>> | ||
| + | self.i2c.writeto_mem(self._address, | ||
| + | |||
| + | def readRaw8(self): | ||
| + | """ | ||
| + | return int.from_bytes(self._i2c.readfrom(self._address, | ||
| + | |||
| + | def readU8(self, | ||
| + | """ | ||
| + | return int.from_bytes( | ||
| + | self._i2c.readfrom_mem(self._address, | ||
| + | |||
| + | def readS8(self, | ||
| + | """ | ||
| + | result = self.readU8(register) | ||
| + | if result > 127: | ||
| + | result -= 256 | ||
| + | return result | ||
| + | |||
| + | def readU16(self, | ||
| + | """ | ||
| + | specified endianness (default little endian, or least significant byte | ||
| + | first).""" | ||
| + | result = int.from_bytes( | ||
| + | self._i2c.readfrom_mem(self._address, | ||
| + | if not little_endian: | ||
| + | result = ((result << 8) & 0xFF00) + (result >> 8) | ||
| + | return result | ||
| + | |||
| + | def readS16(self, | ||
| + | """ | ||
| + | specified endianness (default little endian, or least significant byte | ||
| + | first).""" | ||
| + | result = self.readU16(register, | ||
| + | if result > 32767: | ||
| + | result -= 65536 | ||
| + | return result | ||
| + | |||
| + | def readU16LE(self, | ||
| + | """ | ||
| + | endian byte order.""" | ||
| + | return self.readU16(register, | ||
| + | |||
| + | def readU16BE(self, | ||
| + | """ | ||
| + | endian byte order.""" | ||
| + | return self.readU16(register, | ||
| + | |||
| + | def readS16LE(self, | ||
| + | """ | ||
| + | endian byte order.""" | ||
| + | return self.readS16(register, | ||
| + | |||
| + | def readS16BE(self, | ||
| + | """ | ||
| + | endian byte order.""" | ||
| + | return self.readS16(register, | ||
| + | |||
| + | |||
| + | class BME280: | ||
| + | def __init__(self, | ||
| + | | ||
| + | # Check that mode is valid. | ||
| + | if mode not in [BME280_OSAMPLE_1, | ||
| + | BME280_OSAMPLE_8, | ||
| + | raise ValueError( | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | self._mode = mode | ||
| + | # Create I2C device. | ||
| + | if i2c is None: | ||
| + | raise ValueError(' | ||
| + | self._device = Device(address, | ||
| + | # Load calibration values. | ||
| + | self._load_calibration() | ||
| + | self._device.write8(BME280_REGISTER_CONTROL, | ||
| + | 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): | ||
| + | """ | ||
| + | meas = self._mode | ||
| + | self._device.write8(BME280_REGISTER_CONTROL_HUM, | ||
| + | meas = self._mode << 5 | self._mode << 2 | 1 | ||
| + | self._device.write8(BME280_REGISTER_CONTROL, | ||
| + | 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) | ||
| + | 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): | ||
| + | """ | ||
| + | """ | ||
| + | """ | ||
| + | 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): | ||
| + | """ | ||
| + | """ | ||
| + | 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): | ||
| + | """ | ||
| + | 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): | ||
| + | """ | ||
| + | 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: | ||
| + | h = self.t_fine - 76800 | ||
| + | h = (((((adc << 14) - (self.dig_H4 << 20) - (self.dig_H5 * 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): | ||
| + | " | ||
| + | t = self.read_temperature() | ||
| + | ti = t // 100 | ||
| + | td = t - ti * 100 | ||
| + | return " | ||
| + | |||
| + | @property | ||
| + | def pressure(self): | ||
| + | " | ||
| + | p = self.read_pressure() // 256 | ||
| + | pi = p // 100 | ||
| + | pd = p - pi * 100 | ||
| + | return " | ||
| + | |||
| + | @property | ||
| + | def humidity(self): | ||
| + | " | ||
| + | h = self.read_humidity() | ||
| + | hi = h // 1024 | ||
| + | hd = h * 100 // 1024 - hi * 100 | ||
| + | return " | ||
| + | |||
| + | </ | ||
| + | |||
| + | |||
| + | |||
| + | ===== Čtení dat ze senzoru BME280 ===== | ||
| + | |||
| + | S knihovnou je pak jednoduché vyčítat hodnoty ze senzoru: | ||
| + | |||
| + | <code python> | ||
| + | # Complete project details at https:// | ||
| + | |||
| + | from machine import Pin, I2C | ||
| + | from time import sleep | ||
| + | import BME280 | ||
| + | |||
| + | # ESP32 - Pin assignment | ||
| + | i2c = I2C(scl=Pin(22), | ||
| + | # ESP8266 - Pin assignment | ||
| + | #i2c = I2C(scl=Pin(5), | ||
| + | |||
| + | 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()/ | ||
| + | #temp = str(round(temp, | ||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | |||
| + | 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 [[https:// | ||
| + | |||
| + | ==== 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 [[https:// | ||
| + | |||
| + | 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: | ||
| + | |||
| + | <code python> | ||
| + | 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 = ' | ||
| + | |||
| + | |||
| + | print(CLIENT_NAME) | ||
| + | |||
| + | |||
| + | # ESP8266 - Pin assignment | ||
| + | i2c = I2C(scl=Pin(5), | ||
| + | | ||
| + | |||
| + | def connect_and_subscribe(): | ||
| + | mqttc = MQTTClient(CLIENT_NAME, | ||
| + | mqttc.connect() | ||
| + | return mqttc | ||
| + | | ||
| + | def restart_and_reconnect(): | ||
| + | print(' | ||
| + | sleep(10) | ||
| + | machine.reset() | ||
| + | |||
| + | |||
| + | try: | ||
| + | mqttc = connect_and_subscribe() | ||
| + | except OSError as e: | ||
| + | restart_and_reconnect() | ||
| + | | ||
| + | | ||
| + | BTN_TOPIC = b' | ||
| + | |||
| + | |||
| + | while True: | ||
| + | bme = BME280.BME280(i2c=i2c) | ||
| + | temp = bme.temperature | ||
| + | hum = bme.humidity | ||
| + | pres = bme.pressure | ||
| + | print(' | ||
| + | | ||
| + | try: | ||
| + | mqttc.publish( BTN_TOPIC, str(' | ||
| + | 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: | ||
| + | |||
| + | <code python> | ||
| + | USER = b' | ||
| + | PASSWORD = b' | ||
| + | </ | ||
| + | |||
| + | A poté upravit připojení tak, aby bralo proměnné v potaz: | ||
| + | |||
| + | <code python> | ||
| + | mqttc = MQTTClient(CLIENT_NAME, | ||
| + | </ | ||
| + | |||
| + | |||
| + | |||
| + | ==== 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, | ||
| + | |||
| + | 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. | ||
| + | |||
| + | <code python> | ||
| + | def is_connected(mqttc): | ||
| + | try: | ||
| + | mqttc.publish(b" | ||
| + | return True | ||
| + | except OSError: | ||
| + | return False | ||
| + | </ | ||
| + | |||
| + | <adm warning> | ||
| + | |||
| + | 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/ | ||
| + | * 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 ==== | ||
| + | |||
| + | <code bash> | ||
| + | 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 = ' | ||
| + | USER = b' | ||
| + | PASSWORD | ||
| + | MQTT_TOPIC | ||
| + | |||
| + | |||
| + | |||
| + | with open(" | ||
| + | key = f.read() | ||
| + | | ||
| + | with open(" | ||
| + | cert = f.read() | ||
| + | |||
| + | |||
| + | ssl_params = dict() | ||
| + | ssl_params[" | ||
| + | ssl_params[" | ||
| + | |||
| + | |||
| + | print(CLIENT_NAME) | ||
| + | |||
| + | |||
| + | # ESP8266 - Pin assignment | ||
| + | i2c = I2C(scl=Pin(5), | ||
| + | |||
| + | | ||
| + | |||
| + | def connect_and_subscribe(): | ||
| + | mqttc = MQTTClient(CLIENT_NAME, | ||
| + | mqttc.connect() | ||
| + | return mqttc | ||
| + | | ||
| + | def restart_and_reconnect(): | ||
| + | print(' | ||
| + | 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í | ||
| + | # | ||
| + | |||
| + | |||
| + | def is_connected(mqttc): | ||
| + | try: | ||
| + | mqttc.publish(b" | ||
| + | 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[' | ||
| + | data[' | ||
| + | data[' | ||
| + | | ||
| + | | ||
| + | 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í ===== | ||
| + | |||
| + | * [[https:// | ||
| + | * [[http:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | |||
| + | |||
| + | |||
| + | ===== 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, | ||
| + | |||
| + | <code cpp> | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | |||
| + | #include " | ||
| + | #include " | ||
| + | |||
| + | |||
| + | |||
| + | const char* WiFiName = " | ||
| + | const char* WiFiPassword = " | ||
| + | |||
| + | |||
| + | // MQTT connection | ||
| + | const char* clientName = " | ||
| + | const char* mqttBroker = " | ||
| + | const int mqttPort = 8883; | ||
| + | const char* mqttUser = " | ||
| + | const char* mqttPassword = " | ||
| + | const char* mqttTopic = "/ | ||
| + | |||
| + | Adafruit_BME280 bme; | ||
| + | |||
| + | |||
| + | |||
| + | WiFiClientSecure espClient; | ||
| + | PubSubClient mqttClient(espClient); | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | void setupWiFi() { | ||
| + | | ||
| + | | ||
| + | WiFi.mode(WIFI_STA); | ||
| + | WiFi.begin(WiFiName, | ||
| + | | ||
| + | // connect to WiFi | ||
| + | Serial.print(" | ||
| + | | ||
| + | // 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(" | ||
| + | Serial.println(WiFiName); | ||
| + | Serial.print(" | ||
| + | Serial.println(WiFi.localIP()); | ||
| + | |||
| + | WiFi.setAutoReconnect(true); | ||
| + | WiFi.persistent(true); | ||
| + | |||
| + | } | ||
| + | |||
| + | |||
| + | void reconnectMQTT() { | ||
| + | |||
| + | int retryCount = 0; | ||
| + | |||
| + | while (!mqttClient.connected()) { | ||
| + | |||
| + | Serial.println(" | ||
| + | |||
| + | // SSL/TLS certificate and key settings | ||
| + | // The BearSSL:: | ||
| + | // 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:: | ||
| + | BearSSL:: | ||
| + | |||
| + | |||
| + | |||
| + | espClient.setInsecure(); | ||
| + | espClient.setClientRSACert(& | ||
| + | |||
| + | mqttClient.setServer(mqttBroker, | ||
| + | |||
| + | |||
| + | if (mqttClient.connect(clientName, | ||
| + | | ||
| + | Serial.println(" | ||
| + | | ||
| + | } else { | ||
| + | Serial.print(" | ||
| + | Serial.print(mqttClient.state()); | ||
| + | Serial.println(" | ||
| + | delay(5000); | ||
| + | |||
| + | retryCount++; | ||
| + | |||
| + | if (retryCount > 5) { // Restart the ESP if it fails to connect 5 times | ||
| + | // | ||
| + | } | ||
| + | |||
| + | } | ||
| + | } | ||
| + | |||
| + | } | ||
| + | |||
| + | |||
| + | |||
| + | 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(" | ||
| + | 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< | ||
| + | | ||
| + | doc[" | ||
| + | doc[" | ||
| + | doc[" | ||
| + | |||
| + | // Serializace JSON objektu do řetězce | ||
| + | char jsonBuffer[200]; | ||
| + | serializeJson(doc, | ||
| + | |||
| + | // Odeslání JSON řetězce na MQTT server | ||
| + | mqttClient.publish(mqttTopic, | ||
| + | | ||
| + | } | ||
| + | |||
| + | |||
| + | } | ||
| + | |||
| + | </ | ||
| + | |||
| + | <adm warning> | ||