{"id":171897,"date":"2025-09-25T11:46:46","date_gmt":"2025-09-25T11:46:46","guid":{"rendered":"https:\/\/randomnerdtutorials.com\/?p=171897"},"modified":"2025-09-25T12:22:30","modified_gmt":"2025-09-25T12:22:30","slug":"micropython-esp-now-esp32-many-to-one","status":"publish","type":"post","link":"https:\/\/randomnerdtutorials.com\/micropython-esp-now-esp32-many-to-one\/","title":{"rendered":"MicroPython: ESP-NOW with ESP32\u2014Receive Data from Multiple Boards (many-to-one)"},"content":{"rendered":"\n<p>In this MicroPython guide, we\u2019ll show you how to set up an ESP32 to receive and display data from multiple ESP32 boards using the ESP-NOW communication protocol (many-to-one configuration). We&#8217;ll build a sample project where sender boards transmit sensor data in JSON format, and a single receiver board collects the data and displays it on an OLED display. The tutorial will use two sender boards as an example, but the setup can be easily scaled to include more.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" fetchpriority=\"high\" decoding=\"async\" width=\"1200\" height=\"675\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-ESP-NOW-MicroPython-Many-to-one-receive-data-from-multiple-boards.jpg?resize=1200%2C675&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"MicroPython: ESP-NOW with ESP32\u2014Receive Data from Multiple Boards (many-to-one)\" class=\"wp-image-171971\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-ESP-NOW-MicroPython-Many-to-one-receive-data-from-multiple-boards.jpg?w=1920&amp;quality=100&amp;strip=all&amp;ssl=1 1920w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-ESP-NOW-MicroPython-Many-to-one-receive-data-from-multiple-boards.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-ESP-NOW-MicroPython-Many-to-one-receive-data-from-multiple-boards.jpg?resize=1024%2C576&amp;quality=100&amp;strip=all&amp;ssl=1 1024w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-ESP-NOW-MicroPython-Many-to-one-receive-data-from-multiple-boards.jpg?resize=768%2C432&amp;quality=100&amp;strip=all&amp;ssl=1 768w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-ESP-NOW-MicroPython-Many-to-one-receive-data-from-multiple-boards.jpg?resize=1536%2C864&amp;quality=100&amp;strip=all&amp;ssl=1 1536w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure><\/div>\n\n\n<p class=\"rntbox rntclgreen\"><strong>Using Arduino IDE?<\/strong> Follow this tutorial instead: <a href=\"https:\/\/randomnerdtutorials.com\/esp-now-many-to-one-esp32\/\" title=\"\">ESP-NOW with ESP32: Receive Data from Multiple Boards (many-to-one)<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites &#8211; MicroPython Firmware<\/h2>\n\n\n\n<p>To follow this tutorial, you need MicroPython firmware installed on your ESP32 or ESP8266 boards. You also need an IDE to write and upload the code to your board. We suggest using Thonny IDE:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/getting-started-thonny-micropython-python-ide-esp32-esp8266\/\">Installing and getting started with Thonny IDE<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/flashing-micropython-firmware-esptool-py-esp32-esp8266\/\">Flashing MicroPython Firmware with esptool.py<\/a><\/li>\n<\/ul>\n\n\n\n<p class=\"rntbox rntclblue\"><strong>New to MicroPython?<\/strong> Check out our eBook: <a href=\"https:\/\/randomnerdtutorials.com\/micropython-programming-with-esp32-and-esp8266\/\" title=\"\">MicroPython Programming with ESP32 and ESP8266 eBook (2nd Edition)<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"esp-now-intro\">Introducing ESP-NOW<\/h2>\n\n\n\n<p>ESP-NOW is a wireless communication protocol developed by Espressif that allows multiple ESP32 or ESP8266 boards to exchange small amounts of data without using Wi-Fi or Bluetooth. ESP-NOW does not require a full Wi-Fi connection (though the Wi-Fi controller must be turned on), making it ideal for low-power and low-latency applications like sensor networks, remote controls, or data exchange between boards.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-medium\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"300\" height=\"75\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/esp-now-logo.png?resize=300%2C75&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP-NOW - ESP32 Logo\" class=\"wp-image-93360\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/esp-now-logo.png?resize=300%2C75&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/esp-now-logo.png?resize=1024%2C256&amp;quality=100&amp;strip=all&amp;ssl=1 1024w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/esp-now-logo.png?resize=768%2C192&amp;quality=100&amp;strip=all&amp;ssl=1 768w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/esp-now-logo.png?w=1280&amp;quality=100&amp;strip=all&amp;ssl=1 1280w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/figure><\/div>\n\n\n<p>ESP-NOW uses a connectionless communication model, meaning devices can send and receive data without connecting to a router or setting up an access point (unlike HTTP communication between boards). It supports unicast (sending data to a specific device using its MAC address) and broadcast (sending data to all nearby devices using a broadcast MAC address) messaging.<\/p>\n\n\n\n<p class=\"rntbox rntclgreen\"><strong>New to ESP-NOW?<\/strong> Read our getting started guide: MicroPython: ESP-NOW with ESP32 (Getting Started).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Project Overview<\/h2>\n\n\n\n<p>This tutorial shows how to set up an ESP32 board to receive data from multiple ESP32 boards via ESP-NOW communication protocol (many-to-one configuration) as shown in the following figure.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"750\" height=\"524\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-ESP-NOW-Receive-Data-From-Multiple-Boards.png?resize=750%2C524&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP-NOW ESP32 Receive Data from Multiple Boards (Many to One configuration) diagram\" class=\"wp-image-171972\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-ESP-NOW-Receive-Data-From-Multiple-Boards.png?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-ESP-NOW-Receive-Data-From-Multiple-Boards.png?resize=300%2C210&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<ul class=\"wp-block-list\">\n<li>One ESP32 board acts as a receiver;<\/li>\n\n\n\n<li>Multiple ESP32 boards act as senders. In this example, we&#8217;re using two ESP32 sender boards. You can add more boards to your setup if needed.<\/li>\n\n\n\n<li>The ESP32 receiver board receives the messages from all senders and identifies which board sent the message.<\/li>\n\n\n\n<li>As an example, we\u2019ll exchange BME280 sensor values between the boards. You can modify this project to use any other sensor or exchange any other data between boards.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Parts Required<\/h2>\n\n\n\n<p>To follow the project in this tutorial, you&#8217;ll need the following parts:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>3x (or more) <a href=\"https:\/\/makeradvisor.com\/tools\/esp32-dev-board-wi-fi-bluetooth\/\" target=\"_blank\" rel=\"noopener\" title=\"\">ESP32 boards<\/a><\/li>\n\n\n\n<li>2x <a href=\"https:\/\/makeradvisor.com\/tools\/bme280-sensor-module\/\" target=\"_blank\" rel=\"noopener\" title=\"\">BME280 sensors<\/a><\/li>\n\n\n\n<li>1x <a href=\"https:\/\/makeradvisor.com\/tools\/oled-display-128x64-0-96-inch\/\" target=\"_blank\" rel=\"noopener\" title=\"\">OLED Display SSD1306<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/mb-102-solderless-breadboard-830-points\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Breadboard<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/makeradvisor.com\/tools\/jumper-wires-kit-120-pieces\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Jumper wires<\/a><\/li>\n<\/ul>\n\n\n<p>You can use the preceding links or go directly to <a href=\"https:\/\/makeradvisor.com\/tools\/?utm_source=rnt&utm_medium=post&utm_campaign=post\" target=\"_blank\">MakerAdvisor.com\/tools<\/a> to find all the parts for your projects at the best price!<\/p><p style=\"text-align:center;\"><a href=\"https:\/\/makeradvisor.com\/tools\/?utm_source=rnt&utm_medium=post&utm_campaign=post\" target=\"_blank\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2017\/10\/header-200.png?w=1200&#038;quality=100&#038;strip=all&#038;ssl=1\"><\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"get-mac-address\">ESP32: Getting Board MAC Address<\/h2>\n\n\n\n<p>To communicate via ESP-NOW, you need to know the MAC address of your boards. To get your board\u2019s MAC Address, you can copy the following code to Thonny IDE and run it on your board. <\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-python\"># Rui Santos &amp; Sara Santos - Random Nerd Tutorials\r\n# Complete project details at https:\/\/RandomNerdTutorials.com\/micropython-esp-now-esp32\/\r\n \r\nimport network\r\n\r\nwlan = network.WLAN(network.STA_IF)\r\nwlan.active(True)\r\n\r\n# Get MAC address (returns bytes)\r\nmac = wlan.config('mac')\r\n\r\n# Convert to human-readable format\r\nmac_address = ':'.join('%02x' % b for b in mac)\r\n\r\nprint(&quot;MAC Address:&quot;, mac_address)<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/raw.githubusercontent.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/master\/Projects\/ESP-MicroPython\/esp32_get_mac_address.py\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<p>After running the code, it should print the board&#8217;s MAC address on the shell.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"707\" height=\"298\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/05\/thonny-ide-get-board-mac-address.png?resize=707%2C298&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Thonny IDE - Get ESP32 Board MAC Address - MicroPython\" class=\"wp-image-170329\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/05\/thonny-ide-get-board-mac-address.png?w=707&amp;quality=100&amp;strip=all&amp;ssl=1 707w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/05\/thonny-ide-get-board-mac-address.png?resize=300%2C126&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 707px) 100vw, 707px\" \/><\/figure><\/div>\n\n\n<p>Get the MAC address for all of your boards.<\/p>\n\n\n\n<p>For example, in my case, I get:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Sender board 1<\/strong>: <span class=\"rnthl rntliteral\">24:0A:C4:31:40:50<\/span><\/li>\n\n\n\n<li><strong>Sender board 2<\/strong>: <span class=\"rnthl rntliteral\">30:AE:A4:F6:7D:4C<\/span><\/li>\n\n\n\n<li><strong>Receiver board<\/strong>: <span class=\"rnthl rntliteral\">30:AE:A4:07:0D:64<\/span><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Preparing the Sender Boards<\/h2>\n\n\n\n<p>For this tutorial, we&#8217;ll send data to an ESP32 receiver board (via ESP-NOW) from two different boards. You can modify this project to send data from more boards.<\/p>\n\n\n\n<p>Each board will be identified by an ID (a number that we&#8217;ll attribute to each board:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>ID=1 for board1<\/li>\n\n\n\n<li>ID=2 for board2<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/Two-ESP32-Boards-with-BME280.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Two ESP32 Boards with a BME280 Sensor\" class=\"wp-image-171823\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/Two-ESP32-Boards-with-BME280.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/Two-ESP32-Boards-with-BME280.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<h3 class=\"wp-block-heading\">Wiring the BME280 Sensor<\/h3>\n\n\n\n<p>Each sender board will send environmental data from a BME280 sensor. Wire a BME280 sensor to each of your boards. We&#8217;ll use the ESP32 default I2C pins.<\/p>\n\n\n\n<p>Learn more about I2C with the ESP32:&nbsp;<a href=\"https:\/\/randomnerdtutorials.com\/esp32-i2c-communication-arduino-ide\/\">ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE)<\/a>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"675\" height=\"670\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/10\/ESP32-BME280-Sensor-Temperature-Humidity-Pressure-Wiring-Diagram-Circuit_f.png?resize=675%2C670&amp;quality=100&amp;strip=all&amp;ssl=1\" alt=\"ESP32 BME280 Sensor Temperature Humidity Pressure Wiring Diagram Circuit\" class=\"wp-image-99755\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/10\/ESP32-BME280-Sensor-Temperature-Humidity-Pressure-Wiring-Diagram-Circuit_f.png?w=675&amp;quality=100&amp;strip=all&amp;ssl=1 675w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/10\/ESP32-BME280-Sensor-Temperature-Humidity-Pressure-Wiring-Diagram-Circuit_f.png?resize=300%2C298&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/10\/ESP32-BME280-Sensor-Temperature-Humidity-Pressure-Wiring-Diagram-Circuit_f.png?resize=150%2C150&amp;quality=100&amp;strip=all&amp;ssl=1 150w\" sizes=\"(max-width: 675px) 100vw, 675px\" \/><\/figure><\/div>\n\n\n<p class=\"rntbox rntclgray\">Not familiar with the BME280 with the ESP32? <a href=\"https:\/\/randomnerdtutorials.com\/micropython-bme280-esp32-esp8266\/\" title=\"\">Read this tutorial: MicroPython: BME280 with ESP32 (Pressure, Temperature, Humidity)<\/a>.<\/p>\n\n\n\n<p>If you&#8217;re using a different board model, the default I2C pins might be different.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Importing the BME280 Library<\/h3>\n\n\n\n<p>The library to interface with the BME280 sensor is not part of the standard MicroPython library. So, you need to upload the library file to each of your sender boards.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">BME280.py MicroPython Module<\/h4>\n\n\n\n<p>1. Copy the following code to a new file on Thonny IDE.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-python\">from machine import I2C\nimport time\n\n# BME280 default address.\nBME280_I2CADDR = 0x76\n\n# Operating Modes\nBME280_OSAMPLE_1 = 1\nBME280_OSAMPLE_2 = 2\nBME280_OSAMPLE_4 = 3\nBME280_OSAMPLE_8 = 4\nBME280_OSAMPLE_16 = 5\n\n# BME280 Registers\n\nBME280_REGISTER_DIG_T1 = 0x88  # Trimming parameter registers\nBME280_REGISTER_DIG_T2 = 0x8A\nBME280_REGISTER_DIG_T3 = 0x8C\n\nBME280_REGISTER_DIG_P1 = 0x8E\nBME280_REGISTER_DIG_P2 = 0x90\nBME280_REGISTER_DIG_P3 = 0x92\nBME280_REGISTER_DIG_P4 = 0x94\nBME280_REGISTER_DIG_P5 = 0x96\nBME280_REGISTER_DIG_P6 = 0x98\nBME280_REGISTER_DIG_P7 = 0x9A\nBME280_REGISTER_DIG_P8 = 0x9C\nBME280_REGISTER_DIG_P9 = 0x9E\n\nBME280_REGISTER_DIG_H1 = 0xA1\nBME280_REGISTER_DIG_H2 = 0xE1\nBME280_REGISTER_DIG_H3 = 0xE3\nBME280_REGISTER_DIG_H4 = 0xE4\nBME280_REGISTER_DIG_H5 = 0xE5\nBME280_REGISTER_DIG_H6 = 0xE6\nBME280_REGISTER_DIG_H7 = 0xE7\n\nBME280_REGISTER_CHIPID = 0xD0\nBME280_REGISTER_VERSION = 0xD1\nBME280_REGISTER_SOFTRESET = 0xE0\n\nBME280_REGISTER_CONTROL_HUM = 0xF2\nBME280_REGISTER_CONTROL = 0xF4\nBME280_REGISTER_CONFIG = 0xF5\nBME280_REGISTER_PRESSURE_DATA = 0xF7\nBME280_REGISTER_TEMP_DATA = 0xFA\nBME280_REGISTER_HUMIDITY_DATA = 0xFD\n\n\nclass Device:\n  &quot;&quot;&quot;Class for communicating with an I2C device.\n\n  Allows reading and writing 8-bit, 16-bit, and byte array values to\n  registers on the device.&quot;&quot;&quot;\n\n  def __init__(self, address, i2c):\n    &quot;&quot;&quot;Create an instance of the I2C device at the specified address using\n    the specified I2C interface object.&quot;&quot;&quot;\n    self._address = address\n    self._i2c = i2c\n\n  def writeRaw8(self, value):\n    &quot;&quot;&quot;Write an 8-bit value on the bus (without register).&quot;&quot;&quot;\n    value = value &amp; 0xFF\n    self._i2c.writeto(self._address, value)\n\n  def write8(self, register, value):\n    &quot;&quot;&quot;Write an 8-bit value to the specified register.&quot;&quot;&quot;\n    b=bytearray(1)\n    b[0]=value &amp; 0xFF\n    self._i2c.writeto_mem(self._address, register, b)\n\n  def write16(self, register, value):\n    &quot;&quot;&quot;Write a 16-bit value to the specified register.&quot;&quot;&quot;\n    value = value &amp; 0xFFFF\n    b=bytearray(2)\n    b[0]= value &amp; 0xFF\n    b[1]= (value&gt;&gt;8) &amp; 0xFF\n    self.i2c.writeto_mem(self._address, register, value)\n\n  def readRaw8(self):\n    &quot;&quot;&quot;Read an 8-bit value on the bus (without register).&quot;&quot;&quot;\n    return int.from_bytes(self._i2c.readfrom(self._address, 1),'little') &amp; 0xFF\n\n  def readU8(self, register):\n    &quot;&quot;&quot;Read an unsigned byte from the specified register.&quot;&quot;&quot;\n    return int.from_bytes(\n        self._i2c.readfrom_mem(self._address, register, 1),'little') &amp; 0xFF\n\n  def readS8(self, register):\n    &quot;&quot;&quot;Read a signed byte from the specified register.&quot;&quot;&quot;\n    result = self.readU8(register)\n    if result &gt; 127:\n      result -= 256\n    return result\n\n  def readU16(self, register, little_endian=True):\n    &quot;&quot;&quot;Read an unsigned 16-bit value from the specified register, with the\n    specified endianness (default little endian, or least significant byte\n    first).&quot;&quot;&quot;\n    result = int.from_bytes(\n        self._i2c.readfrom_mem(self._address, register, 2),'little') &amp; 0xFFFF\n    if not little_endian:\n      result = ((result &lt;&lt; 8) &amp; 0xFF00) + (result &gt;&gt; 8)\n    return result\n\n  def readS16(self, register, little_endian=True):\n    &quot;&quot;&quot;Read a signed 16-bit value from the specified register, with the\n    specified endianness (default little endian, or least significant byte\n    first).&quot;&quot;&quot;\n    result = self.readU16(register, little_endian)\n    if result &gt; 32767:\n      result -= 65536\n    return result\n\n  def readU16LE(self, register):\n    &quot;&quot;&quot;Read an unsigned 16-bit value from the specified register, in little\n    endian byte order.&quot;&quot;&quot;\n    return self.readU16(register, little_endian=True)\n\n  def readU16BE(self, register):\n    &quot;&quot;&quot;Read an unsigned 16-bit value from the specified register, in big\n    endian byte order.&quot;&quot;&quot;\n    return self.readU16(register, little_endian=False)\n\n  def readS16LE(self, register):\n    &quot;&quot;&quot;Read a signed 16-bit value from the specified register, in little\n    endian byte order.&quot;&quot;&quot;\n    return self.readS16(register, little_endian=True)\n\n  def readS16BE(self, register):\n    &quot;&quot;&quot;Read a signed 16-bit value from the specified register, in big\n    endian byte order.&quot;&quot;&quot;\n    return self.readS16(register, little_endian=False)\n\n\nclass BME280:\n  def __init__(self, mode=BME280_OSAMPLE_1, address=BME280_I2CADDR, i2c=None,\n               **kwargs):\n    # Check that mode is valid.\n    if mode not in [BME280_OSAMPLE_1, BME280_OSAMPLE_2, BME280_OSAMPLE_4,\n                    BME280_OSAMPLE_8, BME280_OSAMPLE_16]:\n        raise ValueError(\n            'Unexpected mode value {0}. Set mode to one of '\n            'BME280_ULTRALOWPOWER, BME280_STANDARD, BME280_HIGHRES, or '\n            'BME280_ULTRAHIGHRES'.format(mode))\n    self._mode = mode\n    # Create I2C device.\n    if i2c is None:\n      raise ValueError('An I2C object is required.')\n    self._device = Device(address, i2c)\n    # Load calibration values.\n    self._load_calibration()\n    self._device.write8(BME280_REGISTER_CONTROL, 0x3F)\n    self.t_fine = 0\n\n  def _load_calibration(self):\n\n    self.dig_T1 = self._device.readU16LE(BME280_REGISTER_DIG_T1)\n    self.dig_T2 = self._device.readS16LE(BME280_REGISTER_DIG_T2)\n    self.dig_T3 = self._device.readS16LE(BME280_REGISTER_DIG_T3)\n\n    self.dig_P1 = self._device.readU16LE(BME280_REGISTER_DIG_P1)\n    self.dig_P2 = self._device.readS16LE(BME280_REGISTER_DIG_P2)\n    self.dig_P3 = self._device.readS16LE(BME280_REGISTER_DIG_P3)\n    self.dig_P4 = self._device.readS16LE(BME280_REGISTER_DIG_P4)\n    self.dig_P5 = self._device.readS16LE(BME280_REGISTER_DIG_P5)\n    self.dig_P6 = self._device.readS16LE(BME280_REGISTER_DIG_P6)\n    self.dig_P7 = self._device.readS16LE(BME280_REGISTER_DIG_P7)\n    self.dig_P8 = self._device.readS16LE(BME280_REGISTER_DIG_P8)\n    self.dig_P9 = self._device.readS16LE(BME280_REGISTER_DIG_P9)\n\n    self.dig_H1 = self._device.readU8(BME280_REGISTER_DIG_H1)\n    self.dig_H2 = self._device.readS16LE(BME280_REGISTER_DIG_H2)\n    self.dig_H3 = self._device.readU8(BME280_REGISTER_DIG_H3)\n    self.dig_H6 = self._device.readS8(BME280_REGISTER_DIG_H7)\n\n    h4 = self._device.readS8(BME280_REGISTER_DIG_H4)\n    h4 = (h4 &lt;&lt; 24) &gt;&gt; 20\n    self.dig_H4 = h4 | (self._device.readU8(BME280_REGISTER_DIG_H5) &amp; 0x0F)\n\n    h5 = self._device.readS8(BME280_REGISTER_DIG_H6)\n    h5 = (h5 &lt;&lt; 24) &gt;&gt; 20\n    self.dig_H5 = h5 | (\n        self._device.readU8(BME280_REGISTER_DIG_H5) &gt;&gt; 4 &amp; 0x0F)\n\n  def read_raw_temp(self):\n    &quot;&quot;&quot;Reads the raw (uncompensated) temperature from the sensor.&quot;&quot;&quot;\n    meas = self._mode\n    self._device.write8(BME280_REGISTER_CONTROL_HUM, meas)\n    meas = self._mode &lt;&lt; 5 | self._mode &lt;&lt; 2 | 1\n    self._device.write8(BME280_REGISTER_CONTROL, meas)\n    sleep_time = 1250 + 2300 * (1 &lt;&lt; self._mode)\n\n    sleep_time = sleep_time + 2300 * (1 &lt;&lt; self._mode) + 575\n    sleep_time = sleep_time + 2300 * (1 &lt;&lt; self._mode) + 575\n    time.sleep_us(sleep_time)  # Wait the required time\n    msb = self._device.readU8(BME280_REGISTER_TEMP_DATA)\n    lsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 1)\n    xlsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 2)\n    raw = ((msb &lt;&lt; 16) | (lsb &lt;&lt; 8) | xlsb) &gt;&gt; 4\n    return raw\n\n  def read_raw_pressure(self):\n    &quot;&quot;&quot;Reads the raw (uncompensated) pressure level from the sensor.&quot;&quot;&quot;\n    &quot;&quot;&quot;Assumes that the temperature has already been read &quot;&quot;&quot;\n    &quot;&quot;&quot;i.e. that enough delay has been provided&quot;&quot;&quot;\n    msb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA)\n    lsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 1)\n    xlsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 2)\n    raw = ((msb &lt;&lt; 16) | (lsb &lt;&lt; 8) | xlsb) &gt;&gt; 4\n    return raw\n\n  def read_raw_humidity(self):\n    &quot;&quot;&quot;Assumes that the temperature has already been read &quot;&quot;&quot;\n    &quot;&quot;&quot;i.e. that enough delay has been provided&quot;&quot;&quot;\n    msb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA)\n    lsb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA + 1)\n    raw = (msb &lt;&lt; 8) | lsb\n    return raw\n\n  def read_temperature(self):\n    &quot;&quot;&quot;Get the compensated temperature in 0.01 of a degree celsius.&quot;&quot;&quot;\n    adc = self.read_raw_temp()\n    var1 = ((adc &gt;&gt; 3) - (self.dig_T1 &lt;&lt; 1)) * (self.dig_T2 &gt;&gt; 11)\n    var2 = ((\n        (((adc &gt;&gt; 4) - self.dig_T1) * ((adc &gt;&gt; 4) - self.dig_T1)) &gt;&gt; 12) *\n        self.dig_T3) &gt;&gt; 14\n    self.t_fine = var1 + var2\n    return (self.t_fine * 5 + 128) &gt;&gt; 8\n\n  def read_pressure(self):\n    &quot;&quot;&quot;Gets the compensated pressure in Pascals.&quot;&quot;&quot;\n    adc = self.read_raw_pressure()\n    var1 = self.t_fine - 128000\n    var2 = var1 * var1 * self.dig_P6\n    var2 = var2 + ((var1 * self.dig_P5) &lt;&lt; 17)\n    var2 = var2 + (self.dig_P4 &lt;&lt; 35)\n    var1 = (((var1 * var1 * self.dig_P3) &gt;&gt; 8) +\n            ((var1 * self.dig_P2) &gt;&gt; 12))\n    var1 = (((1 &lt;&lt; 47) + var1) * self.dig_P1) &gt;&gt; 33\n    if var1 == 0:\n      return 0\n    p = 1048576 - adc\n    p = (((p &lt;&lt; 31) - var2) * 3125) \/\/ var1\n    var1 = (self.dig_P9 * (p &gt;&gt; 13) * (p &gt;&gt; 13)) &gt;&gt; 25\n    var2 = (self.dig_P8 * p) &gt;&gt; 19\n    return ((p + var1 + var2) &gt;&gt; 8) + (self.dig_P7 &lt;&lt; 4)\n\n  def read_humidity(self):\n    adc = self.read_raw_humidity()\n    # print 'Raw humidity = {0:d}'.format (adc)\n    h = self.t_fine - 76800\n    h = (((((adc &lt;&lt; 14) - (self.dig_H4 &lt;&lt; 20) - (self.dig_H5 * h)) +\n         16384) &gt;&gt; 15) * (((((((h * self.dig_H6) &gt;&gt; 10) * (((h *\n                          self.dig_H3) &gt;&gt; 11) + 32768)) &gt;&gt; 10) + 2097152) *\n                          self.dig_H2 + 8192) &gt;&gt; 14))\n    h = h - (((((h &gt;&gt; 15) * (h &gt;&gt; 15)) &gt;&gt; 7) * self.dig_H1) &gt;&gt; 4)\n    h = 0 if h &lt; 0 else h\n    h = 419430400 if h &gt; 419430400 else h\n    return h &gt;&gt; 12\n\n  @property\n  def temperature(self):\n    &quot;Return the temperature in degrees.&quot;\n    t = self.read_temperature()\n    ti = t \/\/ 100\n    td = t - ti * 100\n    return &quot;{}.{:02d}C&quot;.format(ti, td)\n\n  @property\n  def pressure(self):\n    &quot;Return the temperature in hPa.&quot;\n    p = self.read_pressure() \/\/ 256\n    pi = p \/\/ 100\n    pd = p - pi * 100\n    return &quot;{}.{:02d}hPa&quot;.format(pi, pd)\n\n  @property\n  def humidity(self):\n    &quot;Return the humidity in percent.&quot;\n    h = self.read_humidity()\n    hi = h \/\/ 1024\n    hd = h * 100 \/\/ 1024 - hi * 100\n    return &quot;{}.{:02d}%&quot;.format(hi, hd)\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/ESP-MicroPython\/raw\/master\/code\/WiFi\/HTTP_Client_IFTTT_BME280\/BME280.py\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<p><strong>2.<\/strong> Go to <strong>File<\/strong> &gt; <strong>Save as&#8230;<\/strong><\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"206\" height=\"294\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/10\/Thonny-IDE-ESP32-ESP8266-MicroPython-Save-file-library-to-device-save-as.png?resize=206%2C294&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Thonny IDE ESP32 ESP8266 MicroPython Save file library to device save as\" class=\"wp-image-99603\"\/><\/figure><\/div>\n\n\n<p><strong>3.<\/strong> Select save to &#8220;<strong>MicroPython device<\/strong>&#8220;:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"220\" height=\"202\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/10\/Thonny-IDE-ESP32-ESP8266-MicroPython-Save-file-library-to-device-select.png?resize=220%2C202&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Thonny IDE ESP32 ESP8266 MicroPython Save file library to device select\" class=\"wp-image-99605\"\/><\/figure><\/div>\n\n\n<p><strong>4.<\/strong> Name your file as <em>BME280.py<\/em> and press the OK button:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"553\" height=\"277\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/10\/bme280-library-new-MicroPython-file-Thonny-IDE.png?resize=553%2C277&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"BME280 library new MicroPython file Thonny IDE\" class=\"wp-image-99626\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/10\/bme280-library-new-MicroPython-file-Thonny-IDE.png?w=553&amp;quality=100&amp;strip=all&amp;ssl=1 553w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/10\/bme280-library-new-MicroPython-file-Thonny-IDE.png?resize=300%2C150&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 553px) 100vw, 553px\" \/><\/figure><\/div>\n\n\n<p>And that&#8217;s it. The library was uploaded to your board.<\/p>\n\n\n\n<p>Repeat this process for all your sender boards.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">ESP32 ESP-NOW Sender &#8211; MicroPython Script<\/h3>\n\n\n\n<p>The following code reads the data from the BME280 sensor and sends it via ESP-NOW in a JSON variable to the ESP32 Receiver board. Don&#8217;t forget to modify the code with your receiver board&#8217;s MAC address. Additionally, don&#8217;t forget to change the board ID for each of your boards.<\/p>\n\n\n\n<p>In this code, we&#8217;re using the aioespnow module, which allows us to use ESP-NOW asynchronously. To learn more about asynchronous programming with Micropython, read the following tutorial: <a href=\"https:\/\/randomnerdtutorials.com\/micropython-esp32-esp8266-asynchronous-programming\/\">MicroPython: ESP32\/ESP8266 Asynchronous Programming \u2013 Run Multiple Tasks<\/a>.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-python\"># Rui Santos &amp; Sara Santos - Random Nerd Tutorials\n# Complete project details at https:\/\/RandomNerdTutorials.com\/micropython-esp-now-esp32-many-to-one\/\n\nimport network\nimport aioespnow\nimport asyncio\nimport time\nimport ujson\nfrom machine import Pin, I2C\nimport BME280\n\n# Board ID\nBOARD_ID = 1\n\n# Receiver's MAC address\npeer_mac = b'\\xff\\xff\\xff\\xff\\xff\\xff'\n\n# Interval for sending data (in seconds)\nsend_interval = 10 \n\n# Initialize I2C and BME280\ntry:\n    i2c = I2C(0, scl=Pin(22), sda=Pin(21))  # Adjust pins as needed\n    bme = BME280.BME280(i2c=i2c, address=0x76)\nexcept OSError as err:\n    print(&quot;Failed to initialize BME280:&quot;, err)\n    raise\n\n# Initialize Wi-Fi in station mode\nsta = network.WLAN(network.STA_IF)\ntry:\n    sta.active(True)\n    sta.config(channel=1)  # Set channel explicitly\n    sta.disconnect()\nexcept OSError as err:\n    print(&quot;Failed to initialize Wi-Fi:&quot;, err)\n    raise\n\n# Initialize AIOESPNow\ne = aioespnow.AIOESPNow()\ntry:\n    e.active(True)\nexcept OSError as err:\n    print(&quot;Failed to initialize AIOESPNow:&quot;, err)\n    raise\n\n# Add peer\ntry:\n    e.add_peer(peer_mac)\nexcept OSError as err:\n    print(&quot;Failed to add peer:&quot;, err)\n    raise\n\n# Counter for reading ID\nreading_id = 0\n\ndef read_temperature():\n    try:\n        return float(bme.temperature[:-1])  # Remove 'C' from string\n    except Exception as err:\n        print(&quot;Error reading temperature:&quot;, err)\n        return 0.0\n\ndef read_humidity():\n    try:\n        return float(bme.humidity[:-1])  # Remove '%' from string\n    except Exception as err:\n        print(&quot;Error reading humidity:&quot;, err)\n        return 0.0\n\ndef prepare_sensor_data():\n    global reading_id\n    data = {\n        'id': BOARD_ID,\n        'temp': read_temperature(),\n        'hum': read_humidity(),\n        'readingId': reading_id\n    }\n    reading_id += 1\n    # Serialize to JSON and encode to bytes\n    return ujson.dumps(data).encode('utf-8')\n\nasync def send_messages(e, peer):\n    while True:\n        try:\n            # Prepare and serialize sensor data\n            message = prepare_sensor_data()\n            # Send JSON bytes\n            if await e.asend(peer, message, sync=True):\n                print(f&quot;Sent data: {message.decode('utf-8')}&quot;)\n            else:\n                print(&quot;Failed to send data&quot;)\n        except OSError as err:\n            print(&quot;Send error:&quot;, err)\n            await asyncio.sleep(5)\n        await asyncio.sleep(send_interval)  # Wait before next send\n\nasync def main(e, peer):\n    try:\n        await send_messages(e, peer)\n    except Exception as err:\n        print(f&quot;Error in main: {err}&quot;)\n        await asyncio.sleep(5)\n        raise\n\n# Run the async program\ntry:\n    asyncio.run(main(e, peer_mac))\nexcept KeyboardInterrupt:\n    print(&quot;Stopping sender...&quot;)\n    e.active(False)\n    sta.active(False)\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/ESP-MicroPython\/esp32_espnow_sender_BME280.py\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How Does the Code Work?<\/h3>\n\n\n\n<p>Let&#8217;s take a look at how the sender board code works. Alternatively, you can skip to the next section.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Importing Libraries<\/h4>\n\n\n\n<p>Start by importing the required libraries to use ESP-NOW, the BME280 sensor, and handle JSON variables.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>import network\nimport aioespnow\nimport asyncio\nimport time\nimport ujson\nfrom machine import Pin, I2C\nimport BME280<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Setting the Board ID<\/h4>\n\n\n\n<p>Give a unique ID to your board so that the receiver can easily identify it. Here, we&#8217;re just numbering the boards, but you can use a different method, like giving them a name, or simply identifying them by the MAC address (in this case, you don&#8217;t need this parameter).<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Board ID\nBOARD_ID = 2<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Receiver&#8217;s MAC Address<\/h4>\n\n\n\n<p>Insert the receiver&#8217;s MAC address. For example, in my case, the MAC address of the receiver board is <span class=\"rnthl rntliteral\">30:AE:A4:07:0D:64<\/span>. So, I must add ot to the code in bytes like this:<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Receiver's MAC address\npeer_mac = b'\\x30\\xae\\xa4\\x07\\x0d\\x64'<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Sending Interval<\/h4>\n\n\n\n<p>We&#8217;ll send new readings via ESP-NOW every 10 seconds. You can adjust that period on the <span class=\"rnthl rntliteral\">send_interval<\/span> variable.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Interval for sending data (in seconds)\nsend_interval = 10 <\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Initialize the BME280 Sensor<\/h4>\n\n\n\n<p>Initialize an I2C communication on GPIOs 22 and 21. Adjust if you&#8217;re using different pins. We also initialize the BME280 sensor.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Initialize I2C and BME280\ntry:\n    i2c = I2C(0, scl=Pin(22), sda=Pin(21))  # Adjust pins as needed\n    bme = BME280.BME280(i2c=i2c, address=0x76)\nexcept OSError as err:\n    print(\"Failed to initialize BME280:\", err)\n    raise<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Initialize Wi-Fi Interface and ESP-NOW<\/h4>\n\n\n\n<p>To use ESP-NOW, we need to activate the Wi-Fi interface. In the following lines, we initialize Wi-Fi and ESP-NOW communication protocol using the <span class=\"rnthl rntliteral\">aioespnow<\/span> module.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Initialize Wi-Fi in station mode\nsta = network.WLAN(network.STA_IF)\ntry:\n    sta.active(True)\n    sta.config(channel=1)  # Set channel explicitly\n    sta.disconnect()\nexcept OSError as err:\n    print(\"Failed to initialize Wi-Fi:\", err)\n    raise\n\n# Initialize AIOESPNow\ne = aioespnow.AIOESPNow()\ntry:\n    e.active(True)\nexcept OSError as err:\n    print(\"Failed to initialize AIOESPNow:\", err)\n    raise<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Add Peers<\/h4>\n\n\n\n<p>In the next line, we add the receiver board as a peer. The <span class=\"rnthl rntliteral\">e.add_peer(peer_mac)<\/span> function is needed for reliable unicast communication in aioespnow. It registers the receiver&#8217;s MAC address to ensure the  sender can send messages to that specific board.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Add peer\ntry:\n    e.add_peer(peer_mac)\nexcept OSError as err:\n    print(\"Failed to add peer:\", err)\n    raise<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Getting BME280 Sensor Data<\/h4>\n\n\n\n<p>The following functions return the temperature and humidity from the BME280 sensor.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>def read_temperature():\n    try:\n        return float(bme.temperature&#091;:-1])  # Remove 'C' from string\n    except Exception as err:\n        print(\"Error reading temperature:\", err)\n        return 0.0\n\ndef read_humidity():\n    try:\n        return float(bme.humidity&#091;:-1])  # Remove '%' from string\n    except Exception as err:\n        print(\"Error reading humidity:\", err)\n        return 0.0<\/code><\/pre>\n\n\n\n<p>The functions from the BME280 library return the results with the unit character, that&#8217;s why we&#8217;re removing characters at the end of the readings.<\/p>\n\n\n\n<p class=\"rntbox rntclgreen\">Learn more about interfacing the BME280 with the ESP32 using MicroPython: <a href=\"https:\/\/randomnerdtutorials.com\/micropython-bme280-esp32-esp8266\/\">MicroPython: BME280 with ESP32 (Pressure, Temperature, Humidity)<\/a>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Adding the Data to a JSON Variable<\/h4>\n\n\n\n<p>We create a function called <span class=\"rnthl rntliteral\">prepare_sensor_data()<\/span> that will return a JSON variable containing the data we want to send. In this case, we&#8217;re sending the board ID, the temperature, the humidity, and the reading ID (just a number to track how many readings were taken since the program started running).<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>def prepare_sensor_data():\n    global reading_id\n    data = {\n        'id': BOARD_ID,\n        'temp': read_temperature(),\n        'hum': read_humidity(),\n        'readingId': reading_id\n    }\n    reading_id += 1\n    # Serialize to JSON and encode to bytes\n    return ujson.dumps(data).encode('utf-8')<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Sending Messages via ESP-NOW<\/h4>\n\n\n\n<p>Then, we create an asynchronous function to send the ESP-NOW messages in an asynchronous way.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>async def send_messages(e, peer):\n    while True:\n        try:\n            # Prepare and serialize sensor data\n            message = prepare_sensor_data()\n            # Send JSON bytes\n            if await e.asend(peer, message, sync=True):\n                print(f\"Sent data: {message.decode('utf-8')}\")\n            else:\n                print(\"Failed to send data\")\n        except OSError as err:\n            print(\"Send error:\", err)\n            await asyncio.sleep(5)\n        await asyncio.sleep(send_interval)  # Wait before next send<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Running the Asynchronous Tasks<\/h4>\n\n\n\n<p>Then, the following function runs the <span class=\"rnthl rntliteral\">send_messages<\/span> task to send data via ESP-NOW.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>async def main(e, peer):\n    try:\n        await send_messages(e, peer)\n    except Exception as err:\n        print(f\"Error in main: {err}\")\n        await asyncio.sleep(5)\n        raise<\/code><\/pre>\n\n\n\n<p>Finally, we start the asynchronous program.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Run the async program\ntry:\n    asyncio.run(main(e, peer_mac))\nexcept KeyboardInterrupt:\n    print(\"Stopping sender...\")\n    e.active(False)\n    sta.active(False)<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Running and Uploading the Code to Your Boards<\/h3>\n\n\n\n<p>After copying the code to Thonny IDE and making the required changes, test the code by running it on Thonny IDE.<\/p>\n\n\n\n<p>After establishing a connection with your board, you can click on the green run button.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"470\" height=\"114\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/12\/thonny-ide-run-button.png?resize=470%2C114&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Thonny IDE Green Run Button\" class=\"wp-image-144594\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/12\/thonny-ide-run-button.png?w=470&amp;quality=100&amp;strip=all&amp;ssl=1 470w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2023\/12\/thonny-ide-run-button.png?resize=300%2C73&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 470px) 100vw, 470px\" \/><\/figure><\/div>\n\n\n<p>You should get something similar, as shown in the picture below, in your MicroPython shell.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"690\" height=\"317\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-Sender-ESP-NOW-Failed-To-Send-Terminal.png?resize=690%2C317&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 Sender ESP-NOW - Failed to Send\" class=\"wp-image-171967\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-Sender-ESP-NOW-Failed-To-Send-Terminal.png?w=690&amp;quality=100&amp;strip=all&amp;ssl=1 690w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-Sender-ESP-NOW-Failed-To-Send-Terminal.png?resize=300%2C138&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 690px) 100vw, 690px\" \/><\/figure><\/div>\n\n\n<p>At the moment, the delivery will fail because we haven&#8217;t set the receiver board yet.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Uploading the Code to Your Boards<\/h4>\n\n\n\n<p class=\"rntbox rntclgray\"><strong>Important note<\/strong>: just running the file with Thonny doesn\u2019t copy it permanently to the board\u2019s filesystem. This means that if you unplug it from your computer and apply power to the board, nothing will happen because it doesn\u2019t have any Python file saved on its filesystem. The Thonny IDE&nbsp;<em>Run&nbsp;<\/em>function is useful to test the code, but if you want to upload it permanently to your board, you need to create and save a file to the board&#8217;s filesystem.<\/p>\n\n\n\n<p>To run the code on your boards without being connected to your computer, you must upload it to the board&#8217;s filesystem with the name <span class=\"rnthl rntliteral\"><strong><em>main.py<\/em><\/strong><\/span>.<\/p>\n\n\n\n<p>Go to <strong>File <\/strong>&gt; <strong>Save as..<\/strong>. <strong>MicroPython Device<\/strong>. <\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"220\" height=\"202\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/10\/Thonny-IDE-ESP32-ESP8266-MicroPython-Save-file-library-to-device-select.png?resize=220%2C202&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Thonny IDE ESP32 ESP8266 MicroPython Save file library to device select\" class=\"wp-image-99605\"\/><\/figure><\/div>\n\n\n<p>Call that file <span class=\"rnthl rntliteral\"><strong><em>main.py<\/em><\/strong><\/span> and save it on the board.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"545\" height=\"327\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/save-to-micropython-device-main-file.png?resize=545%2C327&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Uploading main MicroPython file to ESP32 board using Thonny IDE\" class=\"wp-image-171917\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/save-to-micropython-device-main-file.png?w=545&amp;quality=100&amp;strip=all&amp;ssl=1 545w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/save-to-micropython-device-main-file.png?resize=300%2C180&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 545px) 100vw, 545px\" \/><\/figure><\/div>\n\n\n<p>Now, if you restart your board, it will start running the code. You may not be able to access the MicroPython Shell afterwards.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Preparing the Receiver Board<\/h2>\n\n\n\n<p>The ESP32 receiver board will receive the data from the different sender boards and will display it on the OLED display. In this example, we&#8217;re receiving data from two different boards<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"562\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-MicroPython-Many-to-one-setup-f.jpg?resize=750%2C562&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP-NOW with ESP32 MicroPython: setup with one receiver and two sender boards\" class=\"wp-image-171969\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-MicroPython-Many-to-one-setup-f.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-MicroPython-Many-to-one-setup-f.jpg?resize=300%2C225&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<h3 class=\"wp-block-heading\">Wiring the OLED Display<\/h3>\n\n\n\n<p>Wire the OLED display to your ESP32 board. We&#8217;re using the default I2C pins\u2014GPIO 22 (SCL) and GPIO21 (SDA). Adjust if you&#8217;re using an ESP32 model with a different pinout. <\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"873\" height=\"685\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/05\/ESP32_OLED.png?resize=873%2C685&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Esp32 with OLED Display Wiring Diagram\" class=\"wp-image-85566\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/05\/ESP32_OLED.png?w=873&amp;quality=100&amp;strip=all&amp;ssl=1 873w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/05\/ESP32_OLED.png?resize=300%2C235&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2019\/05\/ESP32_OLED.png?resize=768%2C603&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 873px) 100vw, 873px\" \/><\/figure><\/div>\n\n\n<h3 class=\"wp-block-heading\">Importing the SSD1306 Library<\/h3>\n\n\n\n<p>The library to interface with the OLED sensor is not part of the standard MicroPython library. So, you need to upload the library file to your receiver board.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">SSD1306.py MicroPython Module<\/h4>\n\n\n\n<p>1. Copy the following code to a new file on Thonny IDE.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-python\"># MicroPython SSD1306 OLED driver, I2C and SPI interfaces created by Adafruit\n\nimport time\nimport framebuf\n\n# register definitions\nSET_CONTRAST        = const(0x81)\nSET_ENTIRE_ON       = const(0xa4)\nSET_NORM_INV        = const(0xa6)\nSET_DISP            = const(0xae)\nSET_MEM_ADDR        = const(0x20)\nSET_COL_ADDR        = const(0x21)\nSET_PAGE_ADDR       = const(0x22)\nSET_DISP_START_LINE = const(0x40)\nSET_SEG_REMAP       = const(0xa0)\nSET_MUX_RATIO       = const(0xa8)\nSET_COM_OUT_DIR     = const(0xc0)\nSET_DISP_OFFSET     = const(0xd3)\nSET_COM_PIN_CFG     = const(0xda)\nSET_DISP_CLK_DIV    = const(0xd5)\nSET_PRECHARGE       = const(0xd9)\nSET_VCOM_DESEL      = const(0xdb)\nSET_CHARGE_PUMP     = const(0x8d)\n\n\nclass SSD1306:\n    def __init__(self, width, height, external_vcc):\n        self.width = width\n        self.height = height\n        self.external_vcc = external_vcc\n        self.pages = self.height \/\/ 8\n        # Note the subclass must initialize self.framebuf to a framebuffer.\n        # This is necessary because the underlying data buffer is different\n        # between I2C and SPI implementations (I2C needs an extra byte).\n        self.poweron()\n        self.init_display()\n\n    def init_display(self):\n        for cmd in (\n            SET_DISP | 0x00, # off\n            # address setting\n            SET_MEM_ADDR, 0x00, # horizontal\n            # resolution and layout\n            SET_DISP_START_LINE | 0x00,\n            SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0\n            SET_MUX_RATIO, self.height - 1,\n            SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0\n            SET_DISP_OFFSET, 0x00,\n            SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,\n            # timing and driving scheme\n            SET_DISP_CLK_DIV, 0x80,\n            SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,\n            SET_VCOM_DESEL, 0x30, # 0.83*Vcc\n            # display\n            SET_CONTRAST, 0xff, # maximum\n            SET_ENTIRE_ON, # output follows RAM contents\n            SET_NORM_INV, # not inverted\n            # charge pump\n            SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,\n            SET_DISP | 0x01): # on\n            self.write_cmd(cmd)\n        self.fill(0)\n        self.show()\n\n    def poweroff(self):\n        self.write_cmd(SET_DISP | 0x00)\n\n    def contrast(self, contrast):\n        self.write_cmd(SET_CONTRAST)\n        self.write_cmd(contrast)\n\n    def invert(self, invert):\n        self.write_cmd(SET_NORM_INV | (invert &amp; 1))\n\n    def show(self):\n        x0 = 0\n        x1 = self.width - 1\n        if self.width == 64:\n            # displays with width of 64 pixels are shifted by 32\n            x0 += 32\n            x1 += 32\n        self.write_cmd(SET_COL_ADDR)\n        self.write_cmd(x0)\n        self.write_cmd(x1)\n        self.write_cmd(SET_PAGE_ADDR)\n        self.write_cmd(0)\n        self.write_cmd(self.pages - 1)\n        self.write_framebuf()\n\n    def fill(self, col):\n        self.framebuf.fill(col)\n\n    def pixel(self, x, y, col):\n        self.framebuf.pixel(x, y, col)\n\n    def scroll(self, dx, dy):\n        self.framebuf.scroll(dx, dy)\n\n    def text(self, string, x, y, col=1):\n        self.framebuf.text(string, x, y, col)\n\n\nclass SSD1306_I2C(SSD1306):\n    def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):\n        self.i2c = i2c\n        self.addr = addr\n        self.temp = bytearray(2)\n        # Add an extra byte to the data buffer to hold an I2C data\/command byte\n        # to use hardware-compatible I2C transactions.  A memoryview of the\n        # buffer is used to mask this byte from the framebuffer operations\n        # (without a major memory hit as memoryview doesn't copy to a separate\n        # buffer).\n        self.buffer = bytearray(((height \/\/ 8) * width) + 1)\n        self.buffer[0] = 0x40  # Set first byte of data buffer to Co=0, D\/C=1\n        self.framebuf = framebuf.FrameBuffer1(memoryview(self.buffer)[1:], width, height)\n        super().__init__(width, height, external_vcc)\n\n    def write_cmd(self, cmd):\n        self.temp[0] = 0x80 # Co=1, D\/C#=0\n        self.temp[1] = cmd\n        self.i2c.writeto(self.addr, self.temp)\n\n    def write_framebuf(self):\n        # Blast out the frame buffer using a single I2C transaction to support\n        # hardware I2C interfaces.\n        self.i2c.writeto(self.addr, self.buffer)\n\n    def poweron(self):\n        pass\n\n\nclass SSD1306_SPI(SSD1306):\n    def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):\n        self.rate = 10 * 1024 * 1024\n        dc.init(dc.OUT, value=0)\n        res.init(res.OUT, value=0)\n        cs.init(cs.OUT, value=1)\n        self.spi = spi\n        self.dc = dc\n        self.res = res\n        self.cs = cs\n        self.buffer = bytearray((height \/\/ 8) * width)\n        self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height)\n        super().__init__(width, height, external_vcc)\n\n    def write_cmd(self, cmd):\n        self.spi.init(baudrate=self.rate, polarity=0, phase=0)\n        self.cs.high()\n        self.dc.low()\n        self.cs.low()\n        self.spi.write(bytearray([cmd]))\n        self.cs.high()\n\n    def write_framebuf(self):\n        self.spi.init(baudrate=self.rate, polarity=0, phase=0)\n        self.cs.high()\n        self.dc.high()\n        self.cs.low()\n        self.spi.write(self.buffer)\n        self.cs.high()\n\n    def poweron(self):\n        self.res.high()\n        time.sleep_ms(1)\n        self.res.low()\n        time.sleep_ms(10)\n        self.res.high()\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/ESP-MicroPython\/raw\/master\/code\/Others\/OLED\/ssd1306.py\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<p><strong>2<\/strong>. Go to&nbsp;<strong>File&nbsp;<\/strong>&gt;&nbsp;<strong>Save&nbsp;<\/strong>as and select&nbsp;<strong>MicroPython device<\/strong>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"220\" height=\"202\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/10\/Thonny-IDE-ESP32-ESP8266-MicroPython-Save-file-library-to-device-select.png?resize=220%2C202&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Thonny IDE ESP32 ESP8266 MicroPython Save file library to device select\" class=\"wp-image-99605\"\/><\/figure><\/div>\n\n\n<p><strong>3.<\/strong>&nbsp;Name the file&nbsp;<em>ssd1306.py<\/em>&nbsp;and click&nbsp;<strong>OK&nbsp;<\/strong>to save the file on the ESP Filesystem.<\/p>\n\n\n\n<p>And that\u2019s it. The library was uploaded to your board. Now, you can use the library functionalities in your code by importing the library.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">ESP-NOW Receiver Board &#8211; MicroPython Script<\/h3>\n\n\n\n<p>The following code will set up your ESP32 board as an ESP-NOW receiver to get data from the other two sender boards. After receiving the data, it will be displayed on the OLED screen.<\/p>\n\n\n\n<p>Don&#8217;t forget to modify the code to add your sender boards&#8217; MAC addresses.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-python\"># Rui Santos &amp; Sara Santos - Random Nerd Tutorials\n# Complete project details at https:\/\/RandomNerdTutorials.com\/micropython-esp-now-esp32-many-to-one\/\n\nimport network\nimport aioespnow\nimport asyncio\nimport ujson\nfrom machine import Pin, I2C\nimport ssd1306\n\n# Initialize I2C for OLED\ntry:\n    i2c = I2C(0, scl=Pin(22), sda=Pin(21))  # Adjust pins as needed\n    display = ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)\n    print(&quot;SSD1306 initialized&quot;)\nexcept Exception as err:\n    print(&quot;Failed to initialize SSD1306:&quot;, err)\n    raise\n\n# Initialize Wi-Fi in station mode\nsta = network.WLAN(network.STA_IF)\ntry:\n    sta.active(True)\n    sta.config(channel=1)  # Set channel explicitly\n    sta.disconnect()\nexcept OSError as err:\n    print(&quot;Failed to initialize Wi-Fi:&quot;, err)\n    raise\n\n# Initialize AIOESPNow\ne = aioespnow.AIOESPNow()\ntry:\n    e.active(True)\nexcept OSError as err:\n    print(&quot;Failed to initialize AIOESPNow:&quot;, err)\n    raise\n\n# Sender's MAC addresses (replace with actual sender MACs)\nsender_mac_1 = b'\\x24\\x0a\\xc4\\x31\\x40\\x50'  # First sender's MAC (Board ID=1)\nsender_mac_2 = b'\\x30\\xae\\xa4\\xf6\\x7d\\x4c'  # Second sender's MAC (Board ID=2)\n\n# Add peers\ntry:\n    e.add_peer(sender_mac_1)\nexcept OSError as err:\n    print(f&quot;Failed to add peer {sender_mac_1.hex()}:&quot;, err)\n    raise\n\ntry:\n    e.add_peer(sender_mac_2)\nexcept OSError as err:\n    print(f&quot;Failed to add peer {sender_mac_2.hex()}:&quot;, err)\n    raise\n\n# Dictionary to store latest readings for each board\nboard_readings = {\n    1: {'temp': 0.0, 'hum': 0.0, 'readingId': 0},\n    2: {'temp': 0.0, 'hum': 0.0, 'readingId': 0}\n}\n\n# Update OLED display with temperature and humidity for both boards on separate lines.\ndef update_display():\n    try:\n        display.fill(0)\n        # Board 1 data\n        display.text(&quot;Board 1:&quot;, 0, 0)\n        display.text(f&quot;Temp: {board_readings[1]['temp']:.1f} C&quot;, 0, 10)\n        display.text(f&quot;Hum: {board_readings[1]['hum']:.1f} %&quot;, 0, 20)\n        # Board 2 data\n        display.text(&quot;Board 2:&quot;, 0, 32)\n        display.text(f&quot;Temp: {board_readings[2]['temp']:.1f} C&quot;, 0, 42)\n        display.text(f&quot;Hum: {board_readings[2]['hum']:.1f} %&quot;, 0, 52)\n        display.show()\n        print(&quot;Display updated&quot;)\n    except Exception as err:\n        print(&quot;Error updating display:&quot;, err)\n\n# Async function to receive and process messages.\nasync def receive_messages(e):\n    print(&quot;Listening for ESP-NOW messages...&quot;)\n    while True:\n        try:\n            async for mac, msg in e:\n                try:\n                    # Decode and parse JSON message\n                    json_str = msg.decode('utf-8')\n                    data = ujson.loads(json_str)\n                    \n                    # Extract parameters\n                    board_id = data['id']\n                    temperature = data['temp']\n                    humidity = data['hum']\n                    reading_id = data['readingId']\n                    \n                    # Store in board_readings dictionary\n                    if board_id in (1, 2):\n                        board_readings[board_id] = {\n                            'temp': temperature,\n                            'hum': humidity,\n                            'readingId': reading_id\n                        }\n                        # Update OLED display\n                        update_display()\n                    \n                    # Display on MicroPython terminal\n                    print(f&quot;\\nReceived from {mac.hex()}:&quot;)\n                    print(f&quot;  Board ID: {board_id}&quot;)\n                    print(f&quot;  Temperature: {temperature} C&quot;)\n                    print(f&quot;  Humidity: {humidity} %&quot;)\n                    print(f&quot;  Reading ID: {reading_id}&quot;)\n                except (ValueError, KeyError) as err:\n                    print(f&quot;Error parsing JSON: {err}&quot;)\n        except OSError as err:\n            print(&quot;Receive error:&quot;, err)\n            await asyncio.sleep(5)\n\nasync def main(e):\n    await receive_messages(e)\n\n# Run the async program\ntry:\n    asyncio.run(main(e))\nexcept KeyboardInterrupt:\n    print(&quot;Stopping receiver...&quot;)\n    e.active(False)\n    sta.active(False)\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/ESP-MicroPython\/esp32_espnow_receiver_many_to_one.py\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How Does the Code Work?<\/h3>\n\n\n\n<p>Let&#8217;s take a look at how the sender board code works. Alternatively, you can skip to the next section.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Importing Libraries<\/h4>\n\n\n\n<p>Start by importing the required libraries (make sure you uploaded the <em>ssd1306.py<\/em> file previously to add support for interfacing with the display).<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>import network\nimport aioespnow\nimport asyncio\nimport ujson\nfrom machine import Pin, I2C\nimport ssd1306<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Initializing the OLED Display<\/h4>\n\n\n\n<p>Initialize I2C communication and the OLED display with the following lines.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Initialize I2C for OLED\ntry:\n    i2c = I2C(0, scl=Pin(22), sda=Pin(21))  # Adjust pins as needed\n    display = ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)\n    print(\"SSD1306 initialized\")\nexcept Exception as err:\n    print(\"Failed to initialize SSD1306:\", err)\n    raise<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Initializing the WiFi Interface and ESP-NOW<\/h4>\n\n\n\n<p>Initialize the WiFi interface and ESP-NOW.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Initialize Wi-Fi in station mode\nsta = network.WLAN(network.STA_IF)\ntry:\n    sta.active(True)\n    sta.config(channel=1)  # Set channel explicitly\n    sta.disconnect()\nexcept OSError as err:\n    print(\"Failed to initialize Wi-Fi:\", err)\n    raise\n\n# Initialize AIOESPNow\ne = aioespnow.AIOESPNow()\ntry:\n    e.active(True)\nexcept OSError as err:\n    print(\"Failed to initialize AIOESPNow:\", err)\n    raise<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Adding the Sender Boards as Peers<\/h4>\n\n\n\n<p>Add the the sender boards as peers (it is not mandatory to add the sender boards as peers on the receiver side, but it guarantees more reliability in the communication).<\/p>\n\n\n\n<p>Insert the sender boards&#8217; MAC addresses in the following variables (make sure to insert the MAC address in bytes format).<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Sender's MAC addresses (replace with actual sender MACs)\nsender_mac_1 = b'\\x24\\x0a\\xc4\\x31\\x40\\x50'  # First sender's MAC (Board ID=1)\nsender_mac_2 = b'\\x30\\xae\\xa4\\xf6\\x7d\\x4c'  # Second sender's MAC (Board ID=2)<\/code><\/pre>\n\n\n\n<p>Then, add them as ESP-NOW peers:<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Add peers\ntry:\n    e.add_peer(sender_mac_1)\nexcept OSError as err:\n    print(f\"Failed to add peer {sender_mac_1.hex()}:\", err)\n    raise\n\ntry:\n    e.add_peer(sender_mac_2)\nexcept OSError as err:\n    print(f\"Failed to add peer {sender_mac_2.hex()}:\", err)\n    raise<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Dictionary to Save the Readings<\/h4>\n\n\n\n<p>Create a dictionary to store the latest received sensor readings from each board. Saving this information in a dictionary is a great way to save all the data received in one single variable.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>board_readings = {\n    1: {'temp': 0.0, 'hum': 0.0, 'readingId': 0},\n    2: {'temp': 0.0, 'hum': 0.0, 'readingId': 0}\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Updating the Display<\/h4>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">update_display()<\/span> function gets the data received from each board, that is already stored in the <span class=\"rnthl rntliteral\">board_readings<\/span> variable, and displays it on the OLED screen.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>def update_display():\n    try:\n        display.fill(0)\n        # Board 1 data\n        display.text(\"Board 1:\", 0, 0)\n        display.text(f\"Temp: {board_readings&#091;1]&#091;'temp']:.1f} C\", 0, 10)\n        display.text(f\"Hum: {board_readings&#091;1]&#091;'hum']:.1f} %\", 0, 20)\n        # Board 2 data\n        display.text(\"Board 2:\", 0, 32)\n        display.text(f\"Temp: {board_readings&#091;2]&#091;'temp']:.1f} C\", 0, 42)\n        display.text(f\"Hum: {board_readings&#091;2]&#091;'hum']:.1f} %\", 0, 52)\n        display.show()\n        print(\"Display updated\")\n    except Exception as err:\n        print(\"Error updating display:\", err)<\/code><\/pre>\n\n\n\n<p class=\"rntbox rntclblue\">To learn more about interfacing the OLED with the ESP32 using MicroPython, you can check our tutorial: <a href=\"https:\/\/randomnerdtutorials.com\/micropython-oled-display-esp32-esp8266\/\" title=\"\">MicroPython: OLED Display with ESP32 and ESP8266<\/a>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Receiving ESP-NOW Messages<\/h4>\n\n\n\n<p>We create an asynchronous function to get and handle the messages received via ESP-NOW. <\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>async def receive_messages(e):\n    print(\"Listening for ESP-NOW messages...\")\n    while True:\n        try:\n            async for mac, msg in e:<\/code><\/pre>\n\n\n\n<p>The data comes in JSON format. We first decode and parse the JSON message.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>try:\n    # Decode and parse JSON message\n    json_str = msg.decode('utf-8')\n    data = ujson.loads(json_str)<\/code><\/pre>\n\n\n\n<p> We extract each of the parameters into individual variables.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Extract parameters into individual variables\nboard_id = data&#091;'id']\ntemperature = data&#091;'temp']\nhumidity = data&#091;'hum']\nreading_id = data&#091;'readingId']<\/code><\/pre>\n\n\n\n<p>Finally, we save the data in the right place in our <span class=\"rnthl rntliteral\">board_readings<\/span> dictionary according to the board&#8217;s ID.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Store in board_readings dictionary\nif board_id in (1, 2):\n    board_readings&#091;board_id] = {\n    'temp': temperature,\n    'hum': humidity,\n    'readingId': reading_id\n}<\/code><\/pre>\n\n\n\n<p>After that, we have the <span class=\"rnthl rntliteral\">board_readings<\/span> variable updated, so now we can call the <span class=\"rnthl rntliteral\">updated_display()<\/span> function to update the display with the current information.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>update_display()<\/code><\/pre>\n\n\n\n<p>Finally, we print the received data in the MicroPython shell.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Display on serial monitor\nprint(f\"\\nReceived from {mac.hex()}:\")\nprint(f\"  Board ID: {board_id}\")\nprint(f\"  Temperature: {temperature} C\")\nprint(f\"  Humidity: {humidity} %\")\nprint(f\"  Reading ID: {reading_id}\")<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Running the Asynchronous Tasks<\/h4>\n\n\n\n<p>Then, the following function runs the <span class=\"rnthl rntliteral\">receive_messages<\/span> task to send data via ESP-NOW.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>async def main(e):\n    await receive_messages(e)<\/code><\/pre>\n\n\n\n<p>Finally, we start the asynchronous program.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Run the async program\ntry:\n    asyncio.run(main(e, peer_mac))\nexcept KeyboardInterrupt:\n    print(\"Stopping sender...\")\n    e.active(False)\n    sta.active(False)<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Demonstration<\/h3>\n\n\n\n<p>Upload and\/or run the previous code on your receiver board. If you&#8217;re connected to Thonny IDE Terminal, it will print the received data.<\/p>\n\n\n\n<p>At the same time, it will print the readings received from each of the sender boards on the OLED screen.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"422\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-Receiver-Displaying-Data-From-Multiple-Boards.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP-NOW Receiver Displaying Data from Multiple ESP32 boards on OLED screen\" class=\"wp-image-171970\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-Receiver-Displaying-Data-From-Multiple-Boards.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-Receiver-Displaying-Data-From-Multiple-Boards.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>In this tutorial, you learned how to send data from multiple ESP32 senders to one ESP32 receiver board via ESP-NOW using MicroPython. We also showed you how to send multiple data variables in JSON format and how to parse them and handle them to get the individual bits of information that we want.<\/p>\n\n\n\n<p>The project in this tutorial can be easily adapted to add more sender boards and send any other information you want.<\/p>\n\n\n\n<p>We hope you&#8217;ve found this guide useful. We have more ESP-NOW guides with MicroPython that you may find useful:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/micropython-esp-now-esp32\/\">MicroPython: ESP-NOW with ESP32 (Getting Started)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/micropython-esp32-esp-now-two-way\/\">MicroPython: ESP32 ESP-NOW Two-Way Communication<\/a><\/li>\n<\/ul>\n\n\n\n<p>Finally, if you want to learn more about MicroPython, don&#8217;t forget to check out our resources:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/micropython-programming-with-esp32-and-esp8266\/\" title=\"\">MicroPython Programming with ESP32 and ESP8266 eBook<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/projects-esp32-esp8266-micropython\/\" title=\"\">All our MicroPython Projects, Tutorials and Guides<\/a><\/li>\n<\/ul>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this MicroPython guide, we\u2019ll show you how to set up an ESP32 to receive and display data from multiple ESP32 boards using the ESP-NOW communication protocol (many-to-one configuration). We&#8217;ll &#8230; <\/p>\n<p class=\"read-more-container\"><a title=\"MicroPython: ESP-NOW with ESP32\u2014Receive Data from Multiple Boards (many-to-one)\" class=\"read-more button\" href=\"https:\/\/randomnerdtutorials.com\/micropython-esp-now-esp32-many-to-one\/#more-171897\" aria-label=\"Read more about MicroPython: ESP-NOW with ESP32\u2014Receive Data from Multiple Boards (many-to-one)\">CONTINUE READING \u00bb<\/a><\/p>\n","protected":false},"author":5,"featured_media":171971,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[310,309,264],"tags":[],"class_list":["post-171897","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-micropython","category-0-esp32-micropython","category-project"],"aioseo_notices":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-ESP-NOW-MicroPython-Many-to-one-receive-data-from-multiple-boards.jpg?fit=1920%2C1080&quality=100&strip=all&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/171897","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/comments?post=171897"}],"version-history":[{"count":10,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/171897\/revisions"}],"predecessor-version":[{"id":179476,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/171897\/revisions\/179476"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media\/171971"}],"wp:attachment":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media?parent=171897"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/categories?post=171897"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/tags?post=171897"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}