<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Aluizio Tomazelli Junior</title>
    <description>The latest articles on Forem by Aluizio Tomazelli Junior (@aluiziotomazelli).</description>
    <link>https://forem.com/aluiziotomazelli</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3749407%2Fb506594f-9cb3-41a5-93a4-da6189b3be31.png</url>
      <title>Forem: Aluizio Tomazelli Junior</title>
      <link>https://forem.com/aluiziotomazelli</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/aluiziotomazelli"/>
    <language>en</language>
    <item>
      <title>Unit testing ESP-IDF components with GoogleTest (host-based)</title>
      <dc:creator>Aluizio Tomazelli Junior</dc:creator>
      <pubDate>Sun, 22 Feb 2026 06:05:27 +0000</pubDate>
      <link>https://forem.com/aluiziotomazelli/unit-testing-esp-idf-components-with-googletest-host-based-1c16</link>
      <guid>https://forem.com/aluiziotomazelli/unit-testing-esp-idf-components-with-googletest-host-based-1c16</guid>
      <description>&lt;p&gt;I had been looking for a way to test ESP-IDF component logic without flashing to the board every time. The cycle of edit, flash, read serial gets old fast, especially when the bug is just in a calculation or a state machine — nothing hardware-specific.&lt;/p&gt;

&lt;p&gt;ESP-IDF supports building for a Linux target, which means you can run tests straight on your machine. I put together a repo to document how I set this up, using GoogleTest as the test framework. This post covers the first example: &lt;a href="https://github.com/aluiziotomazelli/gtest-esp-idf" rel="noopener noreferrer"&gt;aluiziotomazelli/gtest-esp-idf&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To run these tests you need ESP-IDF 5.x installed and sourced, a Linux machine or WSL2, and two system packages that the IDF linux target depends on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;libbsd0 libbsd-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use the official ESP-IDF Docker container (&lt;code&gt;idf-env&lt;/code&gt; latest), these are already included and you can skip this step — the CI badges on the repo run exactly that way, with no extra setup.&lt;/p&gt;

&lt;p&gt;Ruby is &lt;strong&gt;not&lt;/strong&gt; needed for this example. It only comes in when using CMock-based IDF mocks, which is a different approach covered in a future chapter.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why GoogleTest instead of Unity?
&lt;/h2&gt;

&lt;p&gt;Unity is a native ESP-IDF component — it's already there, no setup needed. For simple assertions it works fine. The issue shows up when your component has multiple C++ classes with distinct responsibilities and you need to test them in isolation.&lt;/p&gt;

&lt;p&gt;Unity works alongside CMock, which ESP-IDF uses internally to mock its own components. The problem is that CMock doesn't handle C++ classes, so you end up writing mocks by hand. GMock handles this directly in the test file, in a few lines.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project layout
&lt;/h2&gt;

&lt;p&gt;The repo is organized as numbered chapters. The first one, &lt;code&gt;01_basic_test&lt;/code&gt;, has this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;01_basic_test/
├── CMakeLists.txt
├── include/
│   ├── i_sum.hpp     # Interface (abstract base class)
│   └── sum.hpp       # Concrete class header
├── src/
│   └── sum.cpp       # Production code
├── host_test/
│   ├── gtest/        # GTest wrapper component
│   └── test_sum/     # Test project for the Sum class
└── test_apps/        # Hardware verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production code stays in &lt;code&gt;src/&lt;/code&gt; and &lt;code&gt;include/&lt;/code&gt;. Tests stay in &lt;code&gt;host_test/&lt;/code&gt;. They don't mix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the interface?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;i_sum.hpp&lt;/code&gt; defines an abstract base class for &lt;code&gt;Sum&lt;/code&gt;. For this example it's not strictly needed — the class is simple enough to test directly. But it's worth doing from the start: in future chapters, we'll derive &lt;strong&gt;mocks&lt;/strong&gt; from interfaces using GMock, which lets you test components that &lt;em&gt;depend on&lt;/em&gt; a class without using the real implementation. Without an interface, GMock can't create a mock.&lt;/p&gt;




&lt;h2&gt;
  
  
  The GTest wrapper
&lt;/h2&gt;

&lt;p&gt;GoogleTest isn't part of ESP-IDF, so it needs to be introduced as a component. The wrapper lives at &lt;code&gt;host_test/gtest/&lt;/code&gt; and is just a &lt;code&gt;CMakeLists.txt&lt;/code&gt; — no source files. A few things worth explaining:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linux-only guard.&lt;/strong&gt; GTest only gets processed when the build target is &lt;code&gt;linux&lt;/code&gt;. It never ends up in the ESP32 binary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cmake"&gt;&lt;code&gt;&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;IDF_TARGET STREQUAL &lt;span class="s2"&gt;"linux"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FetchContent.&lt;/strong&gt; GTest is downloaded from GitHub at build time. The repo stays small, and updating the version is just changing the tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cmake"&gt;&lt;code&gt;&lt;span class="nf"&gt;FetchContent_Declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        v1.14.0
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Build phase guard.&lt;/strong&gt; ESP-IDF runs in two phases: first it scans components to resolve dependencies, then it builds. FetchContent can't run during the scan — it would fail trying to download something while the build system is still mapping dependencies. The guard keeps it to the real build phase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cmake"&gt;&lt;code&gt;&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;NOT CMAKE_BUILD_EARLY_EXPANSION&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;FetchContent_MakeAvailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;googletest&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;INTERFACE linking.&lt;/strong&gt; Since the wrapper has no source files, it registers as an &lt;code&gt;INTERFACE&lt;/code&gt; component — it doesn't compile anything itself, just exposes GTest and GMock to whoever lists &lt;code&gt;gtest&lt;/code&gt; in their &lt;code&gt;REQUIRES&lt;/code&gt;. This pattern keeps the wrapper reusable as more test projects are added.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test project configuration
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;host_test/test_sum/&lt;/code&gt; is a standalone ESP-IDF project. Two CMake details matter here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EXTRA_COMPONENT_DIRS&lt;/strong&gt; — the component under test and the GTest wrapper are outside this project's folder. This tells the build system where to find them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;COMPONENTS&lt;/strong&gt; — without this, the build would process the entire ESP-IDF component tree. Listing only what's needed cuts compilation time significantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WHOLE_ARCHIVE&lt;/strong&gt; — GoogleTest registers tests through static constructors. Since the test functions are never called directly by &lt;code&gt;main.cpp&lt;/code&gt;, the linker may discard them as unused. &lt;code&gt;WHOLE_ARCHIVE&lt;/code&gt; forces every object file to be included, so all tests are actually discovered and run. Without this, you can have a successful build with zero tests executing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test structure
&lt;/h2&gt;

&lt;p&gt;The test project is at &lt;code&gt;host_test/test_sum/&lt;/code&gt;. The &lt;code&gt;Sum&lt;/code&gt; class has two methods: &lt;code&gt;add(a, b)&lt;/code&gt; for plain addition, and &lt;code&gt;add_constrained(a, b)&lt;/code&gt; which returns &lt;code&gt;-1&lt;/code&gt; if the result exceeds the allowed range. Three test groups:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smoke test&lt;/strong&gt; — checks that GoogleTest itself is running. If this fails, the problem is the setup, not the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standard addition&lt;/strong&gt; — tests &lt;code&gt;add(a, b)&lt;/code&gt; with positives, negatives, and zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Constrained addition&lt;/strong&gt; — three scenarios: inputs within range, at the exact limit, and over it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;TEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestSum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AddConstrained_OutOfRange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Sum&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;EXPECT_EQ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_constrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// 11 &amp;gt; 10, should return -1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Running the tests
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;01_basic_test/host_test/test_sum
idf.py &lt;span class="nt"&gt;--preview&lt;/span&gt; set-target linux
idf.py build
./build/test_sum.elf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--preview&lt;/code&gt; flag is needed because the Linux target is still marked as experimental in ESP-IDF. It switches the compiler from Xtensa/RISC-V to your local GCC.&lt;/p&gt;

&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[==========] Running 6 tests from 1 test suite.
[----------] 6 tests from TestSum
[ RUN      ] TestSum.GTestSmokeTest
[       OK ] TestSum.GTestSmokeTest (0 ms)
...
[  PASSED  ] 6 tests.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  CI
&lt;/h2&gt;

&lt;p&gt;The repo has GitHub Actions workflows that run the host tests on every push using the official ESP-IDF container. Since no hardware is needed, this works out of the box with no self-hosted runners.&lt;br&gt;
&lt;a href="https://github.com/aluiziotomazelli/gtest-esp-idf/actions/workflows/01_build_esp32.yml/badge.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://github.com/aluiziotomazelli/gtest-esp-idf/actions/workflows/01_build_esp32.yml/badge.svg" alt="Build Status" width="165" height="20"&gt;&lt;/a&gt; &lt;a href="https://github.com/aluiziotomazelli/gtest-esp-idf/actions/workflows/01_host_tests.yml/badge.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://github.com/aluiziotomazelli/gtest-esp-idf/actions/workflows/01_host_tests.yml/badge.svg" alt="Host Tests Status" width="249" height="20"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The next chapters will cover mocks — both GMock for testing components in isolation, and IDF's CMock-based approach for mocking hardware dependencies. Full source at &lt;a href="https://github.com/aluiziotomazelli/gtest-esp-idf" rel="noopener noreferrer"&gt;github.com/aluiziotomazelli/gtest-esp-idf&lt;/a&gt;&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>testing</category>
      <category>cpp</category>
      <category>iot</category>
    </item>
    <item>
      <title>wifi_manager: a Wi-Fi state manager for ESP-IDF</title>
      <dc:creator>Aluizio Tomazelli Junior</dc:creator>
      <pubDate>Tue, 03 Feb 2026 03:38:48 +0000</pubDate>
      <link>https://forem.com/aluiziotomazelli/wifimanager-a-wi-fi-state-manager-for-esp-idf-ejg</link>
      <guid>https://forem.com/aluiziotomazelli/wifimanager-a-wi-fi-state-manager-for-esp-idf-ejg</guid>
      <description>&lt;p&gt;I’m sharing a Wi-Fi manager component I built for ESP-IDF, designed to handle connection state, reconnection, and persistence in a predictable way.&lt;br&gt;
&lt;a href="https://github.com/aluiziotomazelli/wifi_manager" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; English is not my native language. If it sounds AI-generated, it’s because I translate my drafts.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why I built it
&lt;/h3&gt;

&lt;p&gt;In my embedded projects I often need reliable Wi-Fi management without relying on Arduino-style libraries. Most existing solutions either are tied to the Arduino ecosystem, use blocking APIs, or lack good state control and reconnection logic. I wanted something that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;works natively with ESP-IDF (not Arduino),&lt;/li&gt;
&lt;li&gt;gives controlled, testable Wi-Fi state transitions,&lt;/li&gt;
&lt;li&gt;works well in real deployments where connectivity is intermittent,&lt;/li&gt;
&lt;li&gt;meshes cleanly with event-driven embedded designs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s what led me to build &lt;code&gt;wifi_manager&lt;/code&gt;, initially for my solar automation setup but now generalized for reuse.&lt;/p&gt;
&lt;h3&gt;
  
  
  What it does
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;wifi_manager&lt;/code&gt; wraps the low-level &lt;code&gt;esp_wifi&lt;/code&gt; driver behind a state machine and a singleton interface. Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dedicated task for Wi-Fi operations, decoupled from your main application.&lt;/li&gt;
&lt;li&gt;Automatic reconnection with exponential backoff on disconnects.&lt;/li&gt;
&lt;li&gt;Graceful handling of timeouts and failures.&lt;/li&gt;
&lt;li&gt;Thread-safe API via mutexes and queues.&lt;/li&gt;
&lt;li&gt;Persistent credentials stored in NVS.&lt;/li&gt;
&lt;li&gt;Both blocking (sync) and non-blocking (async) APIs.&lt;/li&gt;
&lt;li&gt;Built with C++, but usable from C projects too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s designed to be used like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WiFiManager&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;get_instance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_credentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SSID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ESP_OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connected!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach gives you fine-grained control over how your application handles Wi-Fi without forcing you into a particular pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Credentials handling and persistence
&lt;/h3&gt;

&lt;p&gt;In the example above, credentials are passed directly in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_credentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SSID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the supported paths.&lt;/p&gt;

&lt;p&gt;Credentials can also be configured via Kconfig (&lt;code&gt;idf.py menuconfig&lt;/code&gt;). Internally, Wi-Fi credentials are stored by the ESP-IDF Wi-Fi driver itself in NVS, and are automatically reused across reboots.&lt;/p&gt;

&lt;p&gt;The component maintains an additional &lt;code&gt;is_credential_valid&lt;/code&gt; (private) flag, also stored in NVS, to decide which source to use in boot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If credentials are marked as valid, the saved credentials from NVS are used.&lt;/li&gt;
&lt;li&gt;If not, the component falls back to credentials provided via Kconfig (if configured).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This validity flag is managed through Wi-Fi events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A successful connection marks the credentials as valid.&lt;/li&gt;
&lt;li&gt;A disconnection caused by authentication errors invalidates them. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows the system to recover cleanly from wrong credentials without hardcoding values or requiring recompilation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Current limitations
&lt;/h3&gt;

&lt;p&gt;At the moment, the component does not implement provisioning or signal quality evaluation.&lt;/p&gt;

&lt;p&gt;In particular:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is no RSSI-based logic to distinguish very weak signal conditions.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT&lt;/code&gt; usually occurs when the access point aborts the handshake due to invalid credentials. Currently, it is treated &lt;strong&gt;only&lt;/strong&gt; as an authentication failure, even though in practice it may also occur under marginal signal conditions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These cases are known limitations and candidates for future improvement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing from the start
&lt;/h3&gt;

&lt;p&gt;The component comes with a comprehensive test suite (40+ cases) covering lifecycle, state machine behavior, persistence, stress tests, and error handling. Running these tests on real hardware is part of how I validate changes. &lt;/p&gt;

&lt;h3&gt;
  
  
  Design decisions
&lt;/h3&gt;

&lt;p&gt;A few decisions you’ll find in the code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Thread safety first&lt;/strong&gt; — Wi-Fi in real products often runs concurrently with other tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State machine core&lt;/strong&gt; — lets you reason about connection state instead of relying on sequences of &lt;code&gt;esp_wifi_*&lt;/code&gt; calls scattered around your code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CMake / IDF component layout&lt;/strong&gt; — makes integration with existing ESP-IDF apps trivial.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where it fits
&lt;/h3&gt;

&lt;p&gt;While popular Arduino-based Wi-Fi managers are great for quick prototypes, for embedded production systems I find I need more structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reliable recovery from disconnects,&lt;/li&gt;
&lt;li&gt;testability,&lt;/li&gt;
&lt;li&gt;integration with FreeRTOS tasks and event loops.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esp-IDF’s own netif and event system are solid foundations, but a clean wrapper like &lt;code&gt;wifi_manager&lt;/code&gt; makes it easier to build bigger systems on top of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it out
&lt;/h3&gt;

&lt;p&gt;Check out the repo: &lt;a href="https://github.com/aluiziotomazelli/wifi_manager" rel="noopener noreferrer"&gt;https://github.com/aluiziotomazelli/wifi_manager&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s MIT-licensed and ready for use or adaptation. Contributions welcome.&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>cpp</category>
      <category>iot</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
