{"id":710,"date":"2026-05-20T09:23:37","date_gmt":"2026-05-20T17:23:37","guid":{"rendered":"https:\/\/salemdata.net\/johnpress\/?p=710"},"modified":"2026-05-20T09:23:37","modified_gmt":"2026-05-20T17:23:37","slug":"hardware-in-the-loop-ai-debugging-reticulum-bluetooth-on-the-t-beam-supreme-works","status":"publish","type":"post","link":"https:\/\/salemdata.net\/johnpress\/?p=710","title":{"rendered":"Hardware-in-the-Loop AI Debugging: Reticulum Bluetooth on the T-Beam Supreme Works"},"content":{"rendered":"<h2>Highly Technical<\/h2>\n<p>Harnessing AI to control USB-connected devices can dramatically shorten hardware debugging cycles.<\/p>\n<p>I have successfully integrated the ble-reticulum C++ (protocol created by <a href=\"https:\/\/github.com\/torlando-tech\">Torlando<\/a>) interface into the <a href=\"https:\/\/github.com\/attermann\/microReticulum\">microReticulum<\/a> code. I built a binary image for the ESP32-based LilyGo! T-Beam SUPREME which I loaded into two T-Beams.\u00a0 The two T-Beams then negotiate a Bluetooth &#8220;pairing&#8221; and once paired, each sends the other hard-coded text, e.g. the entire United States Constitution (44,225 bytes), simultaneously.\u00a0 After the completion of a transmission, each unit starts another transmission after resting for 10 seconds.\u00a0 Bluetooth requires chunking large payloads.\u00a0 For example, the US Constitution payload is chopped into 148 chunks. So this test is to see if the chunking and pushing through Reticulum&#8217;s LINK (completely encrypted) works.\u00a0 This is a form of <em>stress-testing<\/em>:\u00a0 see what can break the transmission and simulate what two users might encounter if each tries to send the other huge files at the same time&#8230; like high resolution pictures of each person&#8217;s cat.<\/p>\n<p>I witnessed a serious degradation problem.\u00a0 For Bluetooth connections, each party must assume a role: server or client, much like the agreement of who&#8217;s on top.\u00a0 I&#8217;m seeing that when a T-Beam is a client, its incoming data is getting corrupted, but the T-Beam whose role is a server has incoming data without errors.\u00a0 I&#8217;m <em>stress-testing<\/em> with the US Constitution file on a bilateral transmission, so the BLE interfaces are tasked with both receiving and transmitting at the same time.<\/p>\n<p>What&#8217;s neat about Codex, OpenAI\u2019s coding agent, running in Visual Studio Code is that Codex can access both of my T-Beams (device ports<code>\/dev\/ttytBOB<\/code> and <code>\/dev\/ttytDAN<\/code>) and connect to them, upload modified binary images and then establish two serial interfaces to each unit just as I might with a two serial consoles. Here&#8217;s a screenshot of a desktop showing two serial consoles.<\/p>\n<figure id=\"attachment_716\" aria-describedby=\"caption-attachment-716\" style=\"width: 300px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-716\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/05\/20260519_182904_Tue-300x249.png\" alt=\"Screenshot of two Konsole on a Linux desktop\" width=\"300\" height=\"249\" srcset=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/05\/20260519_182904_Tue-300x249.png 300w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/05\/20260519_182904_Tue-768x637.png 768w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/05\/20260519_182904_Tue.png 1287w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><figcaption id=\"caption-attachment-716\" class=\"wp-caption-text\">Two Linux Consoles<\/figcaption><\/figure>\n<p>Until yesterday, I&#8217;ve been taking binary image blobs Codex compiles and then manually uploading the blob to each T-Beam and then executing in separate consoles a serial port console command wrapped by a Perl script which prefixes each incoming line with a high precision timestamp and then displays that on the console and writes the line to a log file.\u00a0 After a test run for 60 or 300 seconds, I would process the log files with a Perl script to get a statistical report. I would then upload the report and\/or log files to Codex or ChatGPT for analysis and recommendations.\u00a0 Then I would instruct Codex on the next step &#8212; usually articulating the bug found and asking for its suggestions and then in a subsequent prompt approving the proposed code change and directing Codex to proceed implementing the change.\u00a0 Now, I instruct Codex:<\/p>\n<ul>\n<li>to revise the code per its recommendation,<\/li>\n<li>compile the modified code,<\/li>\n<li>revise the code until a successful compile occurs,<\/li>\n<li>upload the compiled binary to both T-Beams,<\/li>\n<li>connect to both T-Beams in separate serial ports,<\/li>\n<li>monitor the text coming over the serial port,<\/li>\n<li>analyze the results to determine if successful,<\/li>\n<li>if not successful, then formulate a plan to correct, and<\/li>\n<li>repeat this cycle.<\/li>\n<\/ul>\n<p>With the above steps, Codex gets immediate results and can quickly decide what changes it might make in the code and then go through the cycle again.\u00a0 Much like the famous <a href=\"https:\/\/en.wikipedia.org\/wiki\/Lather,_rinse,_repeat\">shampoo instructions<\/a> &#8220;<b>Lather, rinse, repeat,<\/b>&#8221; Codex can keep cycling.\u00a0 I can still monitor its progress and interrupt it when I see it heading off in the wrong direction.\u00a0 This interchange seems very much like a manager directing a coder.\u00a0 With this procedure, Codex can cycle through the above again and again until it is satisfied the successful result has been accomplished without my involvement and delay.<\/p>\n<p>Here&#8217;s a screenshot of a snippet while Codex is &#8220;thinking&#8221; showing that it &#8220;found the mechanical failure&#8221;.\u00a0 After this, Codex revised the code, compiled it, uploaded, and monitored the results until the output was free of errors.<\/p>\n<figure id=\"attachment_712\" aria-describedby=\"caption-attachment-712\" style=\"width: 300px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-712\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/05\/20260520_054506_Wed-300x201.png\" alt=\"CODEX console output\" width=\"300\" height=\"201\" srcset=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/05\/20260520_054506_Wed-300x201.png 300w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/05\/20260520_054506_Wed.png 345w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><figcaption id=\"caption-attachment-712\" class=\"wp-caption-text\">Codex Thoughts<\/figcaption><\/figure>\n<p>I asked Codex to create a <em>post mortem<\/em> to document the cycles it went through along with the reasoning it applied at each juncture.\u00a0 Codex basically overcame a very low level design flaw at the byte level where a buffer specification was too large and causing data to disappear.\u00a0 This kind of debugging and testing would have taken hours and hours of time.<\/p>\n<p>The rest of this post is the technical post mortem Codex generated after the successful run. Readers who only want the story can stop here: the important point is that the debugging cycle moved from \u201chuman observes, uploads logs, asks for next step\u201d to \u201cCodex observes, modifies, compiles, uploads, tests, and repeats.\u201d For those who want the byte-level details, the report follows.<\/p>\n<div class=\"imported-codex-report\">\n<h1>Exercise 305: microReticulum BLE file transfer<\/h1>\n<p>This exercise builds on Exercise 304&#8217;s equal-peer BLE transport. Both boards run the same dual-role BLE interface, form a Reticulum Link, and then send the selected text file across that Link at the same time.<\/p>\n<p>The file transfer protocol is intentionally small and visible on the serial console:<\/p>\n<pre><code class=\"language-text\">FTB -&gt; file begin, with file name, byte count, chunk count, and checksum\r\nFTD -&gt; numbered file data chunk\r\nFTE -&gt; file end, with verification metadata repeated\r\n<\/code><\/pre>\n<p>Each receiver checks byte count, chunk count, and FNV-1a checksum. After a sender completes a transfer, it rests for 10 seconds and then starts the same selected file again.<\/p>\n<h2>Sample Set<\/h2>\n<p>Exercise 305 uses the same payload files as the Pi Zero BLE Reticulum tests:<\/p>\n<pre><code class=\"language-text\">texts\/If.txt                 195 bytes\r\ntexts\/If_full.txt           1583 bytes\r\ntexts\/US_Constitution.txt  44225 bytes\r\n<\/code><\/pre>\n<p>The selected file is compiled into the firmware. The transfer code does not care which file is selected; <code>platformio.ini<\/code> chooses the source text through <code>custom_text_source<\/code>, and <code>scripts\/embed_text.py<\/code> generates <code>SelectedText.h<\/code> in the build directory before compilation.<\/p>\n<h2>Transfer Profiles<\/h2>\n<p>The transfer pressure is selected in <code>platformio.ini<\/code> with build flags:<\/p>\n<pre><code class=\"language-ini\">-D FILE_TRANSFER_CHUNK_SIZE=32\r\n-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500\r\n<\/code><\/pre>\n<p><code>FILE_TRANSFER_CHUNK_SIZE<\/code> is the number of text bytes placed in each application-level <code>FTD<\/code> message before microReticulum wraps and encrypts it as a Link packet. Larger chunks reduce the number of packets needed for a file, but each encrypted packet becomes larger. If it grows beyond what the ESP32 BLE transport can reliably carry under simultaneous two-way traffic, Reticulum may log Link decrypt\/HMAC failures because ciphertext arrived damaged or incomplete.<\/p>\n<p><code>FILE_TRANSFER_CHUNK_INTERVAL_MS<\/code> is the delay between application-level file chunks. Smaller intervals increase throughput, but also increase BLE write\/notify pressure. With both nodes transmitting at the same time, too short a cadence can overflow buffers or expose ordering\/loss issues in the current ESP32 BLE transport.<\/p>\n<p>The conservative bring-up profile uses:<\/p>\n<pre><code class=\"language-text\">32 byte chunks, 500 ms between chunks\r\n<\/code><\/pre>\n<p>The Pi-Zero-comparison profile uses:<\/p>\n<pre><code class=\"language-text\">300 byte chunks, 100 ms between chunks\r\n<\/code><\/pre>\n<p>That is the apples-to-apples starting point for the previous Zero-to-Zero tests. Those commands requested <code>--message-chunk-size 900<\/code>, but the Python sender intentionally applied an internal board\/Link-budget cap before sending. The run17 report for the Constitution transfer shows effective chunk data around 300 to 316 bytes, not 900 bytes, with roughly 100 ms sender pacing.<\/p>\n<p><code>VERIFY_FAIL<\/code> means the file protocol received an incomplete or corrupted transfer. A Reticulum Link HMAC\/decryption error means corruption happened earlier, before the file protocol could parse the packet.<\/p>\n<h2>Priority<\/h2>\n<p>See Exercise 304_microReticulum_ble_dual_role_ping_pong README.md for explanation of &#8220;deterministic tie-breaker&#8221; of the role of client and server based on the ESP32 MAC.<\/p>\n<h2>Environments<\/h2>\n<p>Conservative ESP32 bring-up environments:<\/p>\n<pre><code class=\"language-text\">tbeam_if\r\ntbeam_if_full\r\ntbeam_constitution\r\n<\/code><\/pre>\n<p>Pi-Zero-comparison environments:<\/p>\n<pre><code class=\"language-text\">tbeam_if_pi_zero_profile\r\ntbeam_if_full_pi_zero_profile\r\ntbeam_constitution_pi_zero_profile\r\n<\/code><\/pre>\n<h2>Build Once, Upload Twice<\/h2>\n<p>Each selected text environment produces one firmware image. Build it once, then upload that same image to both boards.<\/p>\n<p>Build the short If sample:<\/p>\n<pre><code class=\"language-bash\">source \/home\/jlpoole\/rnsenv\/bin\/activate\r\ncd \/usr\/local\/src\/microreticulum\/microReticulumTbeam\r\npio run -d exercises\/305_microReticulum_ble_file_transfer -e tbeam_if\r\n<\/code><\/pre>\n<p>Build the Pi-Zero-profile Constitution sample:<\/p>\n<pre><code class=\"language-bash\">source \/home\/jlpoole\/rnsenv\/bin\/activate\r\ncd \/usr\/local\/src\/microreticulum\/microReticulumTbeam\r\npio run -d exercises\/305_microReticulum_ble_file_transfer -e tbeam_constitution_pi_zero_profile\r\n<\/code><\/pre>\n<p>After the build succeeds, upload the same environment to both boards. These commands may be run one after the other:<\/p>\n<pre><code class=\"language-bash\">pio run -d exercises\/305_microReticulum_ble_file_transfer -e tbeam_if -t upload --upload-port \/dev\/ttytAMY\r\npio run -d exercises\/305_microReticulum_ble_file_transfer -e tbeam_if -t upload --upload-port \/dev\/ttytBOB\r\n<\/code><\/pre>\n<p>Use the same <code>-e<\/code> value in upload commands that you used for the build.<\/p>\n<p>For strict parallel uploads, use <code>esptool.py<\/code> directly against the already-built artifacts. This avoids two concurrent <code>pio run<\/code> processes touching the same <code>.pio<\/code> build directory:<\/p>\n<pre><code class=\"language-bash\">cd \/usr\/local\/src\/microreticulum\/microReticulumTbeam\/exercises\/305_microReticulum_ble_file_transfer\r\nesptool.py --chip esp32s3 --port \/dev\/ttytAMY --baud 460800 write_flash -z \\\r\n  0x0000 .pio\/build\/tbeam_if\/bootloader.bin \\\r\n  0x8000 .pio\/build\/tbeam_if\/partitions.bin \\\r\n  0xe000 \/home\/jlpoole\/.platformio\/packages\/framework-arduinoespressif32\/tools\/partitions\/boot_app0.bin \\\r\n  0x10000 .pio\/build\/tbeam_if\/firmware.bin &amp;\r\nesptool.py --chip esp32s3 --port \/dev\/ttytBOB --baud 460800 write_flash -z \\\r\n  0x0000 .pio\/build\/tbeam_if\/bootloader.bin \\\r\n  0x8000 .pio\/build\/tbeam_if\/partitions.bin \\\r\n  0xe000 \/home\/jlpoole\/.platformio\/packages\/framework-arduinoespressif32\/tools\/partitions\/boot_app0.bin \\\r\n  0x10000 .pio\/build\/tbeam_if\/firmware.bin &amp;\r\nwait\r\n<\/code><\/pre>\n<p>For another environment, replace each <code>.pio\/build\/tbeam_if\/<\/code> path with that environment&#8217;s build directory.<\/p>\n<p>Monitor:<\/p>\n<pre><code class=\"language-bash\">pio device monitor -p \/dev\/ttytAMY -b 115200\r\npio device monitor -p \/dev\/ttytBOB -b 115200\r\n<\/code><\/pre>\n<h2>Expected Output<\/h2>\n<p>Once the Link is active, both nodes start sending:<\/p>\n<pre><code class=\"language-text\">Selected file=If.txt bytes=195 chunk=32 interval_ms=500 repeat_rest_ms=10000\r\nTX FILE BEGIN: round=1 file=If.txt bytes=195 chunks=7 crc=...\r\nTX FILE DATA: round=1 seq=1\/7 bytes=32 preview=\"If you can keep your head...\"\r\nTX FILE END: round=1 file=If.txt bytes=195 chunks=7 crc=... next_round_in_ms=10000\r\n<\/code><\/pre>\n<p>The receiver verifies the transfer:<\/p>\n<pre><code class=\"language-text\">RX FILE BEGIN: from=Node-... file=If.txt bytes=195 chunks=7 crc=...\r\nRX FILE DATA: from=Node-... seq=1\/7 bytes=32 preview=\"If you can keep your head...\"\r\nRX FILE END: from=Node-... file=If.txt received=195\/195 chunks=7\/7 crc=... status=OK\r\n<\/code><\/pre>\n<p>Ten seconds after <code>TX FILE END<\/code>, the same selected file starts again. This rest interval is measured after transfer completion, so large files get the same 10-second pause before the next round.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Highly Technical Harnessing AI to control USB-connected devices can dramatically shorten hardware debugging cycles. I have successfully integrated the ble-reticulum C++ (protocol created by Torlando) interface into the microReticulum code. I built a binary image for the ESP32-based LilyGo! T-Beam SUPREME which I loaded into two T-Beams.\u00a0 The two T-Beams then negotiate a Bluetooth &#8220;pairing&#8221; [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":713,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[37,127,126,26,121],"tags":[128,125],"class_list":["post-710","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","category-chatgpt","category-codex","category-electronics","category-t-beam","tag-ble-reticulum","tag-reticulum"],"_links":{"self":[{"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/posts\/710","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=710"}],"version-history":[{"count":5,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/posts\/710\/revisions"}],"predecessor-version":[{"id":719,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/posts\/710\/revisions\/719"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/media\/713"}],"wp:attachment":[{"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=710"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=710"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=710"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}