Today’s post is not aviation related, and instead a quick one about something technical regarding the “system on a chip (SOC)” that PaperMETAR uses. It’s called ESP32 and is one of the most common SOCs in use these days. I have gone back-and-forth on the need for a mobile app, and finally concluded that one was needed. I picked Flutter as the cross-platform development framework.

This weekend I was focusing on the proof-of-concept mobile app. I spent plenty of time on it and was hoping to make great progress on the app, but I kept needing to learn new tools and kept running into issues that required many deep dives into the ESP32 code.

To minimize additional code and leverage existing tools, I decided to use ESP32’s standard WiFi provisioning functionality, specifically the more secure “Security 1” provisioning method. This method involves an upfront handshake, key exchange, and continuous encryption and decryption of the exchanged payload throughout the session. While seemingly straightforward, implementing this revealed several tricky nuances.

Hurdle 1: The Stateful Session Trap

The first hurdle that I kept running into was that I could make the first call to the ESP32, but the subsequent calls would fail. Coming from a mostly-web based development background, I assumed all the HTTP calls would be stateless. This was my first mistake. ESP32 restarts the handshake across each connection! After banging my head for a little time, I finally figured this out. This meant that my first step would succeed, but given the library that I was using, the subsequent HTTP call would fail because it was creating a new connection. I was able to fix this by initializing a single HTTP client and making sure that I used it across the session. Problem 1 solved.

Hurdle 2: Decoding Protocol Buffers and Encryption

The second hurdle was figuring out the Protocol Buffers that ESP32 prefers to use. This is my first time working with Protocol Buffers so I spent some time learning about it, installing the needed tooling for Dart and C languages, and then making the test calls. Again, it was time to start the head banging session! Everything would look OK on the ESP32, but I couldn’t decode the buffer on the client side. After a little more time and deep dives and troubleshooting, I got the right libraries and ciphers configured in Flutter. If you are curious, it’s CTRStream with AESEngine using the shared secret key and the device random that ESP32 returns as the IV (mostly like what the documentation says). Problem 2 solved.

Hurdle 3: The Empty Body Encryption Trap

Then I got to implementing my custom PaperMETAR endpoints that would return and save the PaperMETAR specific configuration. I thought this would be easy since I had the standard stuff figured out and plenty of standard Espressif code to reference. Then, again, I was wrong! What was weird was that the order of my calls would dictate which calls would succeed. After another deep dive and a few hours later, I figured out my issue. My initial call to get the settings did not have any body contents. Knowing that the ESP32 balked at an empty request body, I sent, what was basically, a random string to it.

  Future<http.Response> makeEmptyProtobufPostRequest(String endpoint) async {

    debugPrint('Making protobuf POST request to $endpoint');

    return await _httpClient.post(

      Uri.parse('http://$_hostIP/$endpoint'),

      headers: {

        'Content-Type': 'application/x-protobuf',

        'Accept': 'application/x-protobuf'

      },

      body: '{}'

    );

  }

Little did I know, this threw off the encryption since the ESP32 incremented the stream, but the client hadn’t. This is still a very weak point in my understanding and something I need to spend more time reading. Suffice it to say, encrypting this empty body content synchronized encryption on both client and server. This took me the longest time to figure out because I kept focusing on my custom ESP32 endpoint handler implementation rather than this part of the code in the mobile app. Problem 3 solved.

Once I addressed the above issues, I was able to finally complete my proof-of-concept mobile app implementation. It’s now Sunday night and I am glad that I got as far as I did.

Here's a checklist for ESP32 Wi-Fi Provisioning and custom endpoints, presented in the spirit of aviation checklists.

[ ] Use standard ESP32 Wifi Provisioning with Security 1

[ ] Use X25519 key exchange with a proof of possession, followed by AES-CTR for encryption/decryption

[ ] Use the same HTTP connection for the complete session

[ ] If session disconnects, start again from the handshake

[ ] Encrypt all incoming and outgoing body contents, even if the contents will be ignored or empty

[ ] Most importantly, rejoice if you succeed!

PaperMETAR! Join our pre-release

Loved this post? Let's stay connected!

  • Early information on the upcoming Kickstarter campaign
  • First dibs on new PaperMETAR device launches & special offers
  • Firmware & API release alerts
  • Pro tips for decoding METARs & TAFs
Join the Pre-Release
The ESP32 Provisioning Debugging Checklist: What I Learned When Things Went Wrong | PaperMETAR Blog