{"id":171258,"date":"2025-08-21T13:10:57","date_gmt":"2025-08-21T13:10:57","guid":{"rendered":"https:\/\/randomnerdtutorials.com\/?p=171258"},"modified":"2025-08-21T13:39:05","modified_gmt":"2025-08-21T13:39:05","slug":"micropython-esp32-esp-now-two-way","status":"publish","type":"post","link":"https:\/\/randomnerdtutorials.com\/micropython-esp32-esp-now-two-way\/","title":{"rendered":"MicroPython: ESP32 ESP-NOW Two-Way Communication"},"content":{"rendered":"\n<p>Learn how to establish a two-way communication between two ESP32 boards using ESP-NOW communication protocol. First, we&#8217;ll test a simple example to show you how to implement two-way communication. Finally, we&#8217;ll build a more complex project, in which we exchange sensor readings between boards and display the results on an OLED display.<\/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\/ESP-NOW-MicroPython-ESP32-Two-Way-communication.jpg?resize=1200%2C675&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"MicroPython: ESP32 ESP-NOW Two-Way Communication\" class=\"wp-image-171320\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-MicroPython-ESP32-Two-Way-communication.jpg?w=1920&amp;quality=100&amp;strip=all&amp;ssl=1 1920w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-MicroPython-ESP32-Two-Way-communication.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\/ESP-NOW-MicroPython-ESP32-Two-Way-communication.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\/ESP-NOW-MicroPython-ESP32-Two-Way-communication.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\/ESP-NOW-MicroPython-ESP32-Two-Way-communication.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-two-way-communication-esp32\/\">ESP-NOW Two-Way Communication Between ESP32 Boards<\/a>.<\/p>\n\n\n\n<p><strong>Table of Contents<\/strong><\/p>\n\n\n\n<p>In this tutorial, we&#8217;ll cover the following subjects:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"#esp-now-intro\" title=\"\">Introducing ESP-NOW<\/a><\/li>\n\n\n\n<li><a href=\"#get-mac-address\" title=\"ESP32: Getting Board MAC Address\">ESP32: Getting Board MAC Address<\/a><\/li>\n\n\n\n<li><a href=\"#esp-now-two-way-communication-micropython-code\" title=\"\">ESP-NOW Two-Way Communication Between Two ESP32 Boards (MicroPython)<\/a><\/li>\n\n\n\n<li><a href=\"#esp-now-two-way-communication-oled-bme280\" title=\"\">ESP32 ESP-NOW Two-Way Communication &#8211; Exchange Sensor Readings and Display on OLED<\/a><\/li>\n<\/ul>\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> <a href=\"https:\/\/randomnerdtutorials.com\/micropython-esp-now-esp32\/\" title=\"\">Read our getting started guide for MicroPython using ESP-NOW with ESP32<\/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:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/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\" 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>We recommend that you add a label or sticker to each board so that you can clearly identify them.<\/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\/ESP32-boards-with-MAC-Address.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Two ESP32 boards with a label with their MAC addresses\" class=\"wp-image-171268\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-boards-with-MAC-Address.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-boards-with-MAC-Address.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<p>We&#8217;ll be using an <a href=\"https:\/\/makeradvisor.com\/tools\/esp32-dev-board-wi-fi-bluetooth\/\" target=\"_blank\" rel=\"noopener\" title=\"\">ESP32 DOIT V1 board<\/a> and an <a href=\"https:\/\/randomnerdtutorials.com\/esp32-s3-devkitc-pinout-guide\/\" title=\"\">ESP32 S3 DevKitC<\/a>, but this should be compatible with any ESP32 model.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"esp-now-two-way-communication-micropython-code\">ESP-NOW Two-Way Communication Between Two ESP32 Boards (MicroPython)<\/h2>\n\n\n\n<p>In this section, we&#8217;ll show you a basic example on how to exchange simple messages between ESP32 boards using ESP-NOW communication protocol. Each board is at the same time a receiver and transmitter, so we can call them transceivers.<\/p>\n\n\n\n<p>There are two ESP-NOW MicroPython libraries included by default now in the current MicroPython firmware versions: <span class=\"rnthl rntliteral\">espnow<\/span>, and <span class=\"rnthl rntliteral\">aioespnow<\/span>. We&#8217;ll use the <span class=\"rnthl rntliteral\">aioespnow<\/span>, which is the asynchronous version of the <span class=\"rnthl rntliteral\">espnow<\/span> library.<\/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=\"370\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/ESP_NOW_two_way_communication_two_boards.png?resize=750%2C370&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP-NOW Two-Way communication between two ESP32 boards\" class=\"wp-image-93054\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/ESP_NOW_two_way_communication_two_boards.png?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/ESP_NOW_two_way_communication_two_boards.png?resize=300%2C148&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/figure><\/div>\n\n\n<p>Copy the following code to Thonny IDE.<\/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-esp32-esp-now-two-way\/\n\nimport network\nimport aioespnow\nimport asyncio\nimport time\n\n# Initialize Wi-Fi in station mode\nsta = network.WLAN(network.STA_IF)\nsta.active(True)\nsta.config(channel=1)  # Set channel explicitly if packets are not received\nsta.disconnect()\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# Peer MAC address (replace with the actual MAC of the other board)\npeer_mac = b'\\xff\\xff\\xff\\xff\\xff\\xff'  # Example peer MAC for unicast\n\n# Add peer for unicast reliability\ntry:\n    e.add_peer(peer_mac)\nexcept OSError as err:\n    print(&quot;Failed to add peer:&quot;, err)\n    raise\n\n# Stats tracking\nlast_stats_time = time.time()\nstats_interval = 10  # Print stats every 10 seconds\n\n# Async function to send messages\nasync def send_messages(e, peer):\n    message_count = 0\n    while True:\n        try:\n            message = f&quot;Hello from ESP32 #{message_count}&quot;\n            if await e.asend(peer, message, sync=True):\n                print(f&quot;Sent message: {message}&quot;)\n            else:\n                print(&quot;Failed to send message&quot;)\n            message_count += 1\n            await asyncio.sleep(1)  # Send every 1 second\n        except OSError as err:\n            print(&quot;Send error:&quot;, err)\n            await asyncio.sleep(5)\n\n# Async function to receive messages\nasync def receive_messages(e):\n    while True:\n        try:\n            async for mac, msg in e:\n                print(f&quot;Received from {mac.hex()}: {msg.decode()}&quot;)\n        except OSError as err:\n            print(&quot;Receive error:&quot;, err)\n            await asyncio.sleep(5)\n\n# Async function to print stats periodically\nasync def print_stats(e):\n    global last_stats_time\n    while True:\n        if time.time() - last_stats_time &gt;= stats_interval:\n            stats = e.stats()\n            print(&quot;\\nESP-NOW Statistics:&quot;)\n            print(f&quot;  Packets Sent: {stats[0]}&quot;)\n            print(f&quot;  Packets Delivered: {stats[1]}&quot;)\n            print(f&quot;  Packets Dropped (TX): {stats[2]}&quot;)\n            print(f&quot;  Packets Received: {stats[3]}&quot;)\n            print(f&quot;  Packets Dropped (RX): {stats[4]}&quot;)\n            last_stats_time = time.time()\n        await asyncio.sleep(1)  # Check every second\n\n# Main async function\nasync def main(e, peer):\n    # Run send, receive, and stats tasks concurrently\n    await asyncio.gather(send_messages(e, peer), receive_messages(e), print_stats(e))\n\n# Run the async program\ntry:\n    asyncio.run(main(e, peer_mac))\nexcept KeyboardInterrupt:\n    print(&quot;Stopping transceiver...&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_two_way.py\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<p>This code sets the ESP32 board as an ESP-NOW receiver and transmitter. In the code, you must insert the MAC address of the board to which you want to send data.<\/p>\n\n\n\n<p>For example, if the receiver board MAC address is <span class=\"rnthl rntliteral\">30:AE:A4:F6:7D:4C<\/span>. You need to convert it to bytes format as follows:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><span class=\"rnthl rntliteral\">30:AE:A4:F6:7D:4C<\/span> \u00bb <span class=\"rnthl rntliteral\">b&#8217;\\x30\\xae\\xa4\\xf6\\x7d\\x4c&#8217;<\/span><\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code># Peer MAC address\nreceiver_mac = b'\\x30\\xae\\xa4\\xf6\\x7d\\x4c'<\/code><\/pre>\n\n\n\n<p>Upload this code to both boards, but adjust the MAC address.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How Does the Code Work?<\/h3>\n\n\n\n<p>To better understand this code, we recommend that you get familiar with MicroPython asynchronous programming first. You can read the following guide:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/micropython-esp32-esp8266-asynchronous-programming\/\">MicroPython: ESP32\/ESP8266 Asynchronous Programming \u2013 Run Multiple Tasks<\/a><\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">Importing Modules<\/h4>\n\n\n\n<p>First, import the required modules.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>import network\nimport aioespnow\nimport asyncio\nimport time<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Initialize the Wi-Fi Interface<\/h4>\n\n\n\n<p>Then, we need to initialize Wi-Fi (even if we don&#8217;t use it) to use ESP-NOW. We can use station (<span class=\"rnthl rntliteral\">STA_IF<\/span>) or access point mode (<span class=\"rnthl rntliteral\">AP_IF<\/span>).<\/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)\nsta.active(True)\nsta.config(channel=1)  # Set channel \nsta.disconnect()<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Initialize ESP-NOW<\/h4>\n\n\n\n<p>Then, we can initialize ESP-NOW. First, create an <span class=\"rnthl rntliteral\">aioespnow<\/span> instance called <span class=\"rnthl rntliteral\">e<\/span>. Then activate it using the <span class=\"rnthl rntliteral\">active()<\/span> method and passing the <span class=\"rnthl rntliteral\">True<\/span> value as argument.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Initialize AIOESPNow\ne = aioespnow.AIOESPNow()\ntry:\n    e.active(True)\n    print(\"AIOESPNow initialized\")\nexcept OSError as err:\n    print(\"Failed to initialize AIOESPNow:\", err)\n    raise<\/code><\/pre>\n\n\n\n<p>We activate ESP-NOW inside a <span class=\"rnthl rntliteral\">try<\/span> and <span class=\"rnthl rntliteral\">except<\/span> statements so that we can catch any errors if the initialization fails.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Add ESP-NOW Peer<\/h4>\n\n\n\n<p>Insert the peer MAC address (the MAC address of the board you want to send data to).<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Receiver MAC address (the board you want to send data to)\npeer_mac = b'\\x68\\xb6\\xb3\\x22\\x9e\\x60'<\/code><\/pre>\n\n\n\n<p>Then, we can add the receiver&#8217;s MAC address as a peer using the <span class=\"rnthl rntliteral\">add_peer()<\/span> method.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>try:\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\">Function to Send ESP-NOW Messages<\/h4>\n\n\n\n<p>The following function sends ESP-NOW messages to the peer. Pass as argument the ESP-NOW instance <span class=\"rnthl rntliteral\">e<\/span> and the <span class=\"rnthl rntliteral\">peer<\/span>. As an example, we&#8217;ll send a <span class=\"rnthl rntliteral\">Hello from ESP32<\/span> message followed by a counter. We send a new message every second.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Async function to send messages\nasync def send_messages(e, peer):\n    message_count = 0\n    while True:\n        try:\n            message = f\"Hello from ESP32 #{message_count}\"\n            if await e.asend(peer, message, sync=True):\n                print(f\"Sent message: {message}\")\n            else:\n                print(\"Failed to send message\")\n            message_count += 1\n            await asyncio.sleep(1)  # Send every 1 second\n        except OSError as err:\n            print(\"Send error:\", err)\n            await asyncio.sleep(5)<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Function to Receive ESP-NOW Messages<\/h4>\n\n\n\n<p>We also need to create a function to receive the ESP-NOW messages. When there&#8217;s a new message, we print it in the MicroPython shell.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Async function to receive messages\nasync def receive_messages(e):\n    while True:\n        try:\n            async for mac, msg in e:\n                print(f\"Received from {mac.hex()}: {msg.decode()}\")\n        except OSError as err:\n            print(\"Receive error:\", err)\n            await asyncio.sleep(5)<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Print ESP-NOW Statistics<\/h4>\n\n\n\n<p>Then, we create a message to call later in the code, called <span class=\"rnthl rntliteral\">print_stats()<\/span> that prints current statistics about the ESP-NOW packets. To get the number of packets sent\/received and lost, we can call the <span class=\"rnthl rntliteral\">stats()<\/span> method on the ESP-NOW <span class=\"rnthl rntliteral\">e<\/span> object.<\/p>\n\n\n\n<p>This returns a 5-tuple containing the number of packets sent\/received\/lost:<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>(tx_pkts, tx_responses, tx_failures, rx_packets, rx_dropped_packets)<\/code><\/pre>\n\n\n\n<p>This is the complete asynchronous function.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>async def print_stats(e):\n    global last_stats_time\n    while True:\n        if time.time() - last_stats_time &gt;= stats_interval:\n            stats = e.stats()\n            print(\"\\nESP-NOW Statistics:\")\n            print(f\"  Packets Sent: {stats&#091;0]}\")\n            print(f\"  Packets Delivered: {stats&#091;1]}\")\n            print(f\"  Packets Dropped (TX): {stats&#091;2]}\")\n            print(f\"  Packets Received: {stats&#091;3]}\")\n            print(f\"  Packets Dropped (RX): {stats&#091;4]}\")\n            last_stats_time = time.time()\n        await asyncio.sleep(1)  # Check every second<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Main Async Function<\/h4>\n\n\n\n<p>The following line defines an asynchronous function <span class=\"rnthl rntliteral\">main()<\/span> that takes the ESP-NOW object <span class=\"rnthl rntliteral\">e<\/span> and peer MAC address <span class=\"rnthl rntliteral\">peer<\/span> as arguments.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>async def main(e, peer):<\/code><\/pre>\n\n\n\n<p>Then, we use <span class=\"rnthl rntliteral\">asyncio.gather<\/span> to run three async tasks concurrently:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><span class=\"rnthl rntliteral\">send_messages(e, peer)<\/span>: sends BME280 sensor data to the peer;<\/li>\n\n\n\n<li><span class=\"rnthl rntliteral\">receive_messages(e)<\/span>: listens for incoming data from the peer;<\/li>\n\n\n\n<li><span class=\"rnthl rntliteral\">print_stats(e)<\/span>: periodically prints ESP-NOW statistics.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>await asyncio.gather(send_messages(e, peer), receive_messages(e), print_stats(e))<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Running the Async Program<\/h4>\n\n\n\n<p>Finally, the following lines will run the program asynchronously.<\/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 transceiver...\")\n    e.active(False)\n    sta.active(False)<\/code><\/pre>\n\n\n\n<p>This command <span class=\"rnthl rntliteral\">asyncio.run(main(e, peer_mac))<\/span> starts the MicroPython asyncio event loop, executing the <span class=\"rnthl rntliteral\">main()<\/span> function.<\/p>\n\n\n\n<p>This goes inside <span class=\"rnthl rntliteral\">try<\/span> and <span class=\"rnthl rntliteral\">except<\/span> statements to catch a <span class=\"rnthl rntliteral\">KeyboardInterrupt<\/span> (manually stopping the code). When this happens, we disable ESP-NOW and turn off Wi-Fi.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Running the Code<\/h3>\n\n\n\n<p>After connecting the board to your computer and establishing a communication with Thonny IDE, you can upload the code as <span class=\"rnthl rntliteral\">main.py<\/span> to the board or run it using the green Run button. Make sure you&#8217;ve inserted the receiver&#8217;s MAC address in the code.<\/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=\"Running the Code Thonny IDE MicroPython\" 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>It will start printing messages in the Shell. It will fail to send the message while the other board is still not ready.<\/p>\n\n\n\n<p>Open another instance of Thonny IDE, and establish a communication with the other board. <\/p>\n\n\n\n<div class=\"wp-block-group rntbox rntclgray\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<p><strong>Enabling Two Instances of Thonny IDE<\/strong><\/p>\n\n\n\n<p>Go to <strong>Tools <\/strong>&gt; <strong>Options <\/strong>and untick the option <em>Allow only single Thonny instance<\/em>.<\/p>\n<\/div><\/div>\n\n\n\n<p>Copy the same code, but insert the other board MAC address. After a few seconds, it will start sending and receiving ESP-NOW messages.<\/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\/ESP32-boards-with-MAC-Address.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Two ESP32 boards with a label with their MAC addresses\" class=\"wp-image-171268\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-boards-with-MAC-Address.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-boards-with-MAC-Address.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<p>The following screenshots show the MicroPython Shell for each of my 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=\"757\" height=\"658\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-Transceiver-1.png?resize=757%2C658&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 ESP-NOW Transceiver Two-Way Communication - printing the results on the MicroPython shell\" class=\"wp-image-171261\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-Transceiver-1.png?w=757&amp;quality=100&amp;strip=all&amp;ssl=1 757w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-Transceiver-1.png?resize=300%2C261&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 757px) 100vw, 757px\" \/><\/figure><\/div>\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"785\" height=\"543\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-Transceiver-2.png?resize=785%2C543&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 ESP-NOW Transceiver Two-Way Communication - printing the results on the MicroPython shell\" class=\"wp-image-171262\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-Transceiver-2.png?w=785&amp;quality=100&amp;strip=all&amp;ssl=1 785w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-Transceiver-2.png?resize=300%2C208&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP-NOW-Transceiver-2.png?resize=768%2C531&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 785px) 100vw, 785px\" \/><\/figure><\/div>\n\n\n<p>You can see that after powering both boards, they&#8217;ll start exchanging messages with each other.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"esp-now-two-way-communication-oled-bme280\">ESP32 ESP-NOW Two-Way Communication &#8211; Exchange Sensor Readings and Display on OLED<\/h2>\n\n\n\n<p>In this section, we&#8217;ll build a project with a real-world application in which the two ESP32 boards exchange sensor readings and display the received data on an OLED display.<\/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\/ESP32-with-OLED-Display-BME280-Sensor-ESP-NOW-Two-Way-Communication.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 ESP-NOW Two-Way Communication - Exchange Sensor Readings and Display on OLED\" class=\"wp-image-171272\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-with-OLED-Display-BME280-Sensor-ESP-NOW-Two-Way-Communication.jpg?w=750&amp;quality=100&amp;strip=all&amp;ssl=1 750w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-with-OLED-Display-BME280-Sensor-ESP-NOW-Two-Way-Communication.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\">Project Overview<\/h3>\n\n\n\n<p>The following diagram shows a high-level overview of the project we&#8217;ll build.<\/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=\"900\" height=\"508\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/ESP-NOW-send-sensor-readings-project-overview.png?resize=900%2C508&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP-NOW Two-Way Communication - Send Sensor Readings Between Boards\" class=\"wp-image-93101\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/ESP-NOW-send-sensor-readings-project-overview.png?w=900&amp;quality=100&amp;strip=all&amp;ssl=1 900w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/ESP-NOW-send-sensor-readings-project-overview.png?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/ESP-NOW-send-sensor-readings-project-overview.png?resize=768%2C433&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 900px) 100vw, 900px\" \/><\/figure><\/div>\n\n\n<ul class=\"wp-block-list\">\n<li>In this project we&#8217;ll have two ESP32 boards. Each board is connected to an OLED display and a BME280 sensor;<\/li>\n\n\n\n<li>Each board gets temperature, humidity, and pressure readings from its corresponding sensors;<\/li>\n\n\n\n<li>Each board sends its readings to the other board via ESP-NOW; <\/li>\n\n\n\n<li>When a board receives the readings, it displays them on the OLED display; <\/li>\n\n\n\n<li>After sending the readings, the board displays on the OLED if the message was successfully delivered;<\/li>\n\n\n\n<li>Each board needs to know the other board MAC address in order to send the message.<\/li>\n<\/ul>\n\n\n\n<p>In this example, we&#8217;re using a two-way communication between two boards, but you can add more boards to this setup, and having all boards communicating with each other.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Parts Required<\/h3>\n\n\n\n<p>For this tutorial, you need the following parts:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>2x <a aria-label=\"ESP32 development boards (opens in a new tab)\" href=\"https:\/\/makeradvisor.com\/tools\/esp32-dev-board-wi-fi-bluetooth\/\" target=\"_blank\" rel=\"noreferrer noopener\">ESP32 development boards<\/a> (read <a aria-label=\" (opens in a new tab)\" href=\"https:\/\/makeradvisor.com\/esp32-development-boards-review-comparison\/\" target=\"_blank\" rel=\"noreferrer noopener\">Best ESP32 boards<\/a>)<\/li>\n\n\n\n<li>2x <a aria-label=\"BME280 sensors (opens in a new tab)\" href=\"https:\/\/makeradvisor.com\/tools\/bme280-sensor-module\/\" target=\"_blank\" rel=\"noreferrer noopener\">BME280 sensors<\/a> (<a href=\"https:\/\/randomnerdtutorials.com\/micropython-bme280-esp32-esp8266\/\" title=\"\">BME280 Complete Guide<\/a>)<\/li>\n\n\n\n<li>2x <a aria-label=\"0.96inch OLED displays (opens in a new tab)\" href=\"https:\/\/makeradvisor.com\/tools\/oled-display-128x64-0-96-inch\/\" target=\"_blank\" rel=\"noreferrer noopener\">0.96 inch OLED displays<\/a> (<a href=\"https:\/\/randomnerdtutorials.com\/micropython-oled-display-esp32-esp8266\/\" title=\"\">OLED Complete Guide<\/a>)<\/li>\n\n\n\n<li><a aria-label=\"Breadboard\u2028 (opens in a new tab)\" href=\"https:\/\/makeradvisor.com\/tools\/mb-102-solderless-breadboard-830-points\/\" target=\"_blank\" rel=\"noreferrer noopener\">Breadboard<\/a><\/li>\n\n\n\n<li><a aria-label=\"Jumper wires (opens in a new tab)\" href=\"https:\/\/makeradvisor.com\/tools\/jumper-wires-kit-120-pieces\/\" target=\"_blank\" rel=\"noreferrer noopener\">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<h3 class=\"wp-block-heading\">Schematic Diagram<\/h3>\n\n\n\n<p>Wire an OLED display and a BME280 sensor to each ESP32 board. Follow the next schematic diagram (adjust if using an ESP32 model with a different pinout).<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"744\" height=\"878\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/ESP32_OLED_BME280.png?resize=744%2C878&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 wiring schematic diagram to BME280 sensor and OLED display\" class=\"wp-image-93089\" style=\"width:647px;height:auto\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/ESP32_OLED_BME280.png?w=744&amp;quality=100&amp;strip=all&amp;ssl=1 744w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2020\/01\/ESP32_OLED_BME280.png?resize=254%2C300&amp;quality=100&amp;strip=all&amp;ssl=1 254w\" sizes=\"(max-width: 744px) 100vw, 744px\" \/><\/figure><\/div>\n\n\n<p>You can use the following table as a reference when wiring the BME280 sensor.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>BME280<\/strong><\/td><td><strong>ESP32<\/strong><\/td><\/tr><tr><td><span class=\"rnthl rntcred\">VIN<\/span><\/td><td><span class=\"rnthl rntcred\">3.3V<\/span><\/td><\/tr><tr><td><span class=\"rnthl rntcblack\">GND<\/span><\/td><td><span class=\"rnthl rntcblack\">GND<\/span><\/td><\/tr><tr><td><span class=\"rnthl rntcblue\">SCL<\/span><\/td><td><span class=\"rnthl rntcblue\">GPIO 22<\/span><\/td><\/tr><tr><td><span class=\"rnthl rntcgreen\">SDA<\/span><\/td><td><span class=\"rnthl rntcgreen\">GPIO 21<\/span><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>You can also follow the next table to wire the OLED display to the ESP32.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>OLED Display<\/strong><\/td><td><strong>ESP32<\/strong><\/td><\/tr><tr><td><span class=\"rnthl rntcblack\">GND<\/span><\/td><td><span class=\"rnthl rntcblack\">GND<\/span><\/td><\/tr><tr><td><span class=\"rnthl rntcred\">VDD \/ VCC<\/span><\/td><td><span class=\"rnthl rntcred\">VIN<\/span><\/td><\/tr><tr><td><span class=\"rnthl rntcblue\">SCK \/ SCL<\/span><\/td><td><span class=\"rnthl rntcblue\">GPIO 22<\/span><\/td><\/tr><tr><td><span class=\"rnthl rntcgreen\">SDA<\/span><\/td><td><span class=\"rnthl rntcgreen\">GPIO 21<\/span><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Importing Libraries<\/h3>\n\n\n\n<p>The libraries to interface with the OLED display and get BME280 data are not part of the standard MicroPython package. So, we need to import those modules into our boards.<\/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>2. Go to <strong>File <\/strong>&gt; <strong>Save <\/strong>as and select <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\/2019\/01\/save-micropython-device-thonny-ide.png?resize=220%2C202&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Thonny IDE Save to MicroPython Device\" class=\"wp-image-109022\"\/><\/figure><\/div>\n\n\n<p><strong>3.<\/strong> Name the file <em>ssd1306.py<\/em> and click <strong>OK <\/strong>to save the file on the ESP Filesystem.<\/p>\n\n\n\n<p>And that&#8217;s it. The library was uploaded to your board.<\/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<h3 class=\"wp-block-heading\">ESP32 Exchange BME280 Sensor Readings via ESP-NOW &#8211; MicroPython Code<\/h3>\n\n\n\n<p>Now that you have all the required modules uploaded to your boards, you can upload the following code to each of your boards.<\/p>\n\n\n\n<p><strong>Important: <\/strong> don&#8217;t forget to insert the receiver&#8217;s MAC address on the code.<\/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-esp32-esp-now-two-way\/\n\nimport network\nimport aioespnow\nimport asyncio\nimport time\nimport ujson\nfrom machine import Pin, I2C\nimport BME280\nimport ssd1306\n\n# Initialize I2C for BME280 and SSD1306\ni2c = I2C(0, scl=Pin(22), sda=Pin(21))\n\n# Initialize BME280 sensor\ntry:\n    bme = BME280.BME280(i2c=i2c, address=0x76)\n    print(&quot;BME280 initialized&quot;)\nexcept Exception as err:\n    print(&quot;Failed to initialize BME280:&quot;, err)\n    raise\n\n# Initialize SSD1306 OLED display\ntry:\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)\nsta.active(True)\nsta.config(channel=1) # set Wi-Fi channel for more stable communication\nsta.disconnect()\nprint(&quot;Wi-Fi initialized&quot;)\n\n# Initialize AIOESPNow\ne = aioespnow.AIOESPNow()\ntry:\n    e.active(True)\n    print(&quot;AIOESPNow initialized&quot;)\nexcept OSError as err:\n    print(&quot;Failed to initialize AIOESPNow:&quot;, err)\n    raise\n\n# Receiver MAC address (the board you want to send data to)\npeer_mac = b'\\xff\\xff\\xff\\xff\\xff\\xff'\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# Variables to store readings and status\nlast_send_status = &quot; &quot;\nincoming_readings = {'temp': 0.0, 'hum': 0.0, 'pres': 0.0}\n\n# Function to get BME280 readings\ndef get_readings():\n    try:\n        temp = float(bme.temperature[:-1]) # Remove 'C'\n        hum = float(bme.humidity[:-1])     # Remove '%'\n        pres = float(bme.pressure[:-3])    # Remove 'hPa'\n        print(&quot;BME280 readings:&quot;, temp, hum, pres)\n        return temp, hum, pres\n    except Exception as err:\n        print(&quot;Error reading BME280:&quot;, err)\n        return 0.0, 0.0, 0.0\n\n# Function to update OLED display\ndef update_display():\n    try:\n        display.fill(0)\n        display.text(&quot;INCOM. READINGS&quot;, 0, 0)\n        display.text(&quot;Temp: {:.1f} C&quot;.format(incoming_readings['temp']), 0, 15)\n        display.text(&quot;Hum: {:.1f} %&quot;.format(incoming_readings['hum']), 0, 25)\n        display.text(&quot;Pres: {:.1f} hPa&quot;.format(incoming_readings['pres']), 0, 35)\n        display.text(last_send_status, 0, 55)\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 send messages\nasync def send_messages(e, peer):\n    global last_send_status\n    while True:\n        try:\n            print(&quot;Sending data&quot;)\n            temp, hum, pres = get_readings()\n            # Create JSON string\n            data_dict = {&quot;temp&quot;: temp, &quot;hum&quot;: hum, &quot;pres&quot;: pres}\n            json_str = ujson.dumps(data_dict)\n            data = json_str.encode('utf-8')  # Convert to bytes\n            print(&quot;Sending JSON:&quot;, json_str)\n            if await e.asend(peer, data, sync=True):\n                print(&quot;Sent with success&quot;)\n                last_send_status = &quot;Delivery Success :)&quot;\n            else:\n                print(&quot;Send failed&quot;)\n                last_send_status = &quot;Delivery Fail :(&quot;\n            update_display()\n            print(&quot;Sending task complete&quot;)\n            await asyncio.sleep(10)  # Send every 10 seconds\n        except OSError as err:\n            print(&quot;Send error:&quot;, err)\n            last_send_status = &quot;Delivery Fail :(&quot;\n            update_display()\n            await asyncio.sleep(0.1)  # Shorter delay in case of error\n\n# Async function to receive messages\nasync def receive_messages(e):\n    global incoming_readings\n    while True:\n        try:\n            print(&quot;Checking for messages&quot;)\n            async for mac, msg in e:\n                try:\n                    # Decode bytes to string and parse JSON\n                    json_str = msg.decode('utf-8')\n                    data_dict = ujson.loads(json_str)\n                    temp = data_dict['temp']\n                    hum = data_dict['hum']\n                    pres = data_dict['pres']\n                    incoming_readings['temp'] = temp\n                    incoming_readings['hum'] = hum\n                    incoming_readings['pres'] = pres\n                    print(&quot;\\nINCOMING READINGS&quot;)\n                    print(&quot;Temperature: {:.1f} \u00baC&quot;.format(temp))\n                    print(&quot;Humidity: {:.1f} %&quot;.format(hum))\n                    print(&quot;Pressure: {:.1f} hPa&quot;.format(pres))\n                    update_display()\n                except (ValueError, KeyError) as err:\n                    print(&quot;Error parsing JSON:&quot;, err)\n            await asyncio.sleep(0.01)  # Yield if no received messages\n        except OSError as err:\n            print(&quot;Receive error:&quot;, err)\n            await asyncio.sleep(0.1)  # Shorter delay in case of error\n\n# Main async function\nasync def main(e, peer):\n    print(&quot;Starting main loop&quot;)\n    await asyncio.gather(send_messages(e, peer), receive_messages(e))\n\n# Run the async program\ntry:\n    print(&quot;Starting transceiver...&quot;)\n    asyncio.run(main(e, peer_mac))\nexcept KeyboardInterrupt:\n    print(&quot;Stopping transceiver...&quot;)\n    e.active(False)\n    sta.active(False)\nexcept Exception as err:\n    print(&quot;Main loop error:&quot;, err)\nfinally:\n    print(&quot;Cleaning up...&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_two_way_BME280_OLED.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 quick look at how the code works. Alternatively, you can skip to the <a href=\"#demonstration\" title=\"\">demonstration <\/a>section.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Importing Libraries<\/h4>\n\n\n\n<p>We start by importing the required modules.<\/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\nimport ssd1306<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">I2C Communication<\/h4>\n\n\n\n<p>Initialize an I2C communication on GPIOs 22 and 21. Adjust if you&#8217;re using different pins.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>i2c = I2C(0, scl=Pin(22), sda=Pin(21))<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">BME280<\/h4>\n\n\n\n<p>Initialize the BME280 sensor.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Initialize BME280 sensor\ntry:\n    bme = BME280.BME280(i2c=i2c, address=0x76)\n    print(\"BME280 initialized\")\nexcept Exception as err:\n    print(\"Failed to initialize BME280:\", err)\n    raise<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">OLED Display<\/h4>\n\n\n\n<p>Initialize the OLED Display.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Initialize SSD1306 OLED display\ntry:\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\">Initialize the Wi-Fi Interface<\/h4>\n\n\n\n<p>Then, we need to initialize Wi-Fi (even if we don&#8217;t use it) to use ESP-NOW. We can use station (<span class=\"rnthl rntliteral\">STA_IF<\/span>) or access point mode (<span class=\"rnthl rntliteral\">AP_IF<\/span>).<\/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)\nsta.active(True)\nsta.config(channel=1)  # Set channel \nsta.disconnect()<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Initialize ESP-NOW<\/h4>\n\n\n\n<p>Then, we can initialize ESP-NOW. First, create an <span class=\"rnthl rntliteral\">aioespnow<\/span> instance called <span class=\"rnthl rntliteral\">e<\/span>. Then activate it using the <span class=\"rnthl rntliteral\">active()<\/span> method and passing the <span class=\"rnthl rntliteral\">True<\/span> value as argument.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Initialize AIOESPNow\ne = aioespnow.AIOESPNow()\ntry:\n    e.active(True)\n    print(\"AIOESPNow initialized\")\nexcept OSError as err:\n    print(\"Failed to initialize AIOESPNow:\", err)\n    raise<\/code><\/pre>\n\n\n\n<p>We activate ESP-NOW inside a <span class=\"rnthl rntliteral\">try<\/span> and <span class=\"rnthl rntliteral\">except<\/span> statements so that we can catch any errors if the initialization fails.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Add ESP-NOW Peer<\/h4>\n\n\n\n<p>Insert the peer MAC address (the MAC address of the board you want to send data to).<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Receiver MAC address (the board you want to send data to)\npeer_mac = b'\\x68\\xb6\\xb3\\x22\\x9e\\x60'<\/code><\/pre>\n\n\n\n<p>Then, we can add the receiver&#8217;s MAC address as a peer using the <span class=\"rnthl rntliteral\">add_peer()<\/span> method.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>try:\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\">Variables<\/h4>\n\n\n\n<p>Create variables to store the incoming sensor readings, and the state of the sending process. We save the incoming sensor readings in a dictionary.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>last_send_status = \" \"\nincoming_readings = {'temp': 0.0, 'hum': 0.0, 'pres': 0.0}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Get BME280 Sensor Readings<\/h4>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">get_readings()<\/span> function returns the BME280 sensor readings in this order, temperature, humidity, and pressure. 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>In case there&#8217;s an error reading the sensor, it will return 0.0 for all readings.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Function to get BME280 readings\ndef get_readings():\n    try:\n        temp = float(bme.temperature&#091;:-1]) # Remove 'C'\n        hum = float(bme.humidity&#091;:-1])     # Remove '%'\n        pres = float(bme.pressure&#091;:-3])    # Remove 'hPa'\n        print(\"BME280 readings:\", temp, hum, pres)\n        return temp, hum, pres\n    except Exception as err:\n        print(\"Error reading BME280:\", err)\n        return 0.0, 0.0, 0.0<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Update the OLED Display<\/h4>\n\n\n\n<p>The following function updates the display with the current incoming sensor readings. It also displays the last sending status.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Function to update OLED display\ndef update_display():\n    try:\n        display.fill(0)\n        display.text(\"INCOM. READINGS\", 0, 0)\n        display.text(\"Temp: {:.1f} C\".format(incoming_readings&#091;'temp']), 0, 15)\n        display.text(\"Hum: {:.1f} %\".format(incoming_readings&#091;'hum']), 0, 25)\n        display.text(\"Pres: {:.1f} hPa\".format(incoming_readings&#091;'pres']), 0, 35)\n        display.text(last_send_status, 0, 55)\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 rntclgray\">To learn more about using the OLED display with the ESP32 programmed with MicroPython, check our guide: <a href=\"https:\/\/randomnerdtutorials.com\/micropython-oled-display-esp32-esp8266\/\">MicroPython: OLED Display with ESP32 and ESP8266<\/a>.<\/p>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">send_message(e, peer)<\/span> function sends a message to the specified peer using an espnow instance <span class=\"rnthl rntliteral\">e<\/span>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Async function to send messages\nasync def send_messages(e, peer):\n    global last_send_status\n    while True:\n        try:\n            print(\"Sending data\")\n            temp, hum, pres = get_readings()\n            # Create JSON string\n            data_dict = {\"temp\": temp, \"hum\": hum, \"pres\": pres}\n            json_str = ujson.dumps(data_dict)\n            data = json_str.encode('utf-8')  # Convert to bytes\n            print(\"Sending JSON:\", json_str)\n            if await e.asend(peer, data, sync=True):\n                print(\"Sent with success\")\n                last_send_status = \"Delivery Success :)\"\n            else:\n                print(\"Send failed\")\n                last_send_status = \"Delivery Fail :(\"\n            update_display()\n            print(\"Sending task complete\")\n            await asyncio.sleep(10)  # Send every 10 seconds\n        except OSError as err:\n            print(\"Send error:\", err)\n            last_send_status = \"Delivery Fail :(\"\n            update_display()\n            await asyncio.sleep(0.1)  # Shorter delay in case of error<\/code><\/pre>\n\n\n\n<p>In this function, we start by calling <span class=\"rnthl rntliteral\">get_readings()<\/span> to get the current temperature, humidity and pressure readings. Then, we update the dictionary with the current readings. We convert it to a JSON String and then to bytes.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>temp, hum, pres = get_readings()\n# Create JSON string\ndata_dict = {\"temp\": temp, \"hum\": hum, \"pres\": pres}\njson_str = ujson.dumps(data_dict)\ndata = json_str.encode('utf-8')  # Convert to bytes<\/code><\/pre>\n\n\n\n<p>Then, we use the same procedure we described previously to send data. During this process, we also update the display with the result of sending the data.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code>if await e.asend(peer, data, sync=True):\n    print(\"Sent with success\")\n    last_send_status = \"Delivery Success :)\"\nelse:\n    print(\"Send failed\")\n    last_send_status = \"Delivery Fail :(\"\n    update_display()\n    print(\"Sending task complete\")\n    await asyncio.sleep(10)  # Send every 10 seconds\nexcept OSError as err:\n    print(\"Send error:\", err)\n    last_send_status = \"Delivery Fail :(\"\n    update_display()\n    await asyncio.sleep(0.1)  # Shorter delay in case of error<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Receiving Incoming Data<\/h4>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">receive_messages()<\/span> function will receive the data from the other board. <\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Async function to receive messages\nasync def receive_messages(e):\n    global incoming_readings\n    while True:\n        try:\n            print(\"Checking for messages\")\n            async for mac, msg in e:\n                try:\n                    # Decode bytes to string and parse JSON\n                    json_str = msg.decode('utf-8')\n                    data_dict = ujson.loads(json_str)\n                    temp = data_dict&#091;'temp']\n                    hum = data_dict&#091;'hum']\n                    pres = data_dict&#091;'pres']\n                    incoming_readings&#091;'temp'] = temp\n                    incoming_readings&#091;'hum'] = hum\n                    incoming_readings&#091;'pres'] = pres\n                    print(\"\\nINCOMING READINGS\")\n                    print(\"Temperature: {:.1f} \u00baC\".format(temp))\n                    print(\"Humidity: {:.1f} %\".format(hum))\n                    print(\"Pressure: {:.1f} hPa\".format(pres))\n                    update_display()\n                except (ValueError, KeyError) as err:\n                    print(\"Error parsing JSON:\", err)\n            await asyncio.sleep(0.01)  # Yield if no received messages\n        except OSError as err:\n            print(\"Receive error:\", err)\n            await asyncio.sleep(0.1)  # Shorter delay in case of error<\/code><\/pre>\n\n\n\n<p>When we receive the data, we convert it to a JSON string. Then, we update the <span class=\"rnthl rntliteral\">incoming_readings<\/span> dictionary with the received data.<\/p>\n\n\n\n<p>Finally, we print the readings in the serial monitor and also call the <span class=\"rnthl rntliteral\">update_display()<\/span> function to update the screen with the latest sensor readings.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Creating the Async Loop Function and Running the Program<\/h4>\n\n\n\n<p>Finally, we create the main asynchronous loop function and run the program asynchronously like we did in the previous example.<\/p>\n\n\n\n<pre class=\"wp-block-code language-python\"><code># Run the async program\ntry:\n    print(\"Starting transceiver...\")\n    asyncio.run(main(e, peer_mac))\nexcept KeyboardInterrupt:\n    print(\"Stopping transceiver...\")\n    e.active(False)\n    sta.active(False)\nexcept Exception as err:\n    print(\"Main loop error:\", err)\nfinally:\n    print(\"Cleaning up...\")\n    e.active(False)\n    sta.active(False)<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Uploading the Code to Your Boards<\/h3>\n\n\n\n<p class=\"rntbox rntcgray\"><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<\/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-py-f.png?resize=545%2C327&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Save main.py file to MicroPython device - Thonny IDE\" class=\"wp-image-171326\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/save-to-micropython-device-main-py-f.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-py-f.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<h3 class=\"wp-block-heading\" id=\"demonstration\">Demonstration<\/h3>\n\n\n\n<p>After uploading the code to both boards, you should see the OLED displaying the sensor readings from the other board, as well as a &#8220;<em>Delivery Success<\/em>&#8221; message.<\/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\/ESP32-ESP-NOW-Two-Way-communication-OLED-BME280.jpg?resize=750%2C422&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 Exchange BME280 Sensor Readings via ESP-NOW\" class=\"wp-image-171274\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2025\/06\/ESP32-ESP-NOW-Two-Way-communication-OLED-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\/ESP32-ESP-NOW-Two-Way-communication-OLED-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<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>We hope you&#8217;ve found this guide useful. The ESP-NOW wireless communication protocol is one of the easiest methods to communicate between ESP32 boards remotely without the need for a Wi-Fi router.<\/p>\n\n\n\n<p>In this tutorial, you learned how to establish a two-way communication between boards. You can easily add more boards to your setup by adding more peers to create a network of ESP32 boards that communicate with each other. <\/p>\n\n\n\n<p>If you&#8217;re just getting started with ESP-NOW, we recommend starting with our <strong><a href=\"https:\/\/randomnerdtutorials.com\/micropython-esp-now-esp32\/\">ESP-NOW Getting Started Guide for the ESP32 Programmed with MicroPython<\/a><\/strong>.<\/p>\n\n\n\n<p>Want to learn more about MicroPython with the ESP32? Check out our resources:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/projects-esp32-esp8266-micropython\/\" title=\"\">Compilation of all our MicroPython Projects and Tutorials<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/micropython-programming-with-esp32-and-esp8266\/\" title=\"\">MicroPython Programming with ESP32 and ESP8266 <strong>eBook<\/strong><\/a><\/li>\n<\/ul>\n\n\n\n<p>Thanks for reading.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to establish a two-way communication between two ESP32 boards using ESP-NOW communication protocol. First, we&#8217;ll test a simple example to show you how to implement two-way communication. Finally, &#8230; <\/p>\n<p class=\"read-more-container\"><a title=\"MicroPython: ESP32 ESP-NOW Two-Way Communication\" class=\"read-more button\" href=\"https:\/\/randomnerdtutorials.com\/micropython-esp32-esp-now-two-way\/#more-171258\" aria-label=\"Read more about MicroPython: ESP32 ESP-NOW Two-Way Communication\">CONTINUE READING \u00bb<\/a><\/p>\n","protected":false},"author":5,"featured_media":171320,"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-171258","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\/ESP-NOW-MicroPython-ESP32-Two-Way-communication.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\/171258","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=171258"}],"version-history":[{"count":19,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/171258\/revisions"}],"predecessor-version":[{"id":175956,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/171258\/revisions\/175956"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media\/171320"}],"wp:attachment":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media?parent=171258"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/categories?post=171258"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/tags?post=171258"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}