<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>My blog on Urhbaan</title><link>https://www.uhrbaan.ch/blog/</link><description>Recent content in My blog on Urhbaan</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>© 2026 Léonard Clément</copyright><lastBuildDate>Thu, 20 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.uhrbaan.ch/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Fripuck devlog n°5: Lua is moon in portuguese</title><link>https://www.uhrbaan.ch/blog/fripuck-5/</link><pubDate>Thu, 20 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.uhrbaan.ch/blog/fripuck-5/</guid><description>&lt;p&gt;These last two weeks, I&amp;rsquo;ve been busy working on the radio module of the e-puck2, specifically embedding a Lua VM onto its ESP32 chip, which handles remote communication. This work is part of my &lt;strong&gt;Fripuck&lt;/strong&gt; project, my bachelor thesis aimed at adapting the e-puck2 software to better suit the needs of the University of Fribourg.&lt;/p&gt;
&lt;p&gt;In this post, I&amp;rsquo;ll explain &lt;em&gt;why&lt;/em&gt; I wanted to introduce a Lua VM into the project, present the API, and talk about the C implementation.&lt;/p&gt;
&lt;h2 id="why-add-a-vm"&gt;Why add a VM?&lt;/h2&gt;
&lt;p&gt;First, a quick reminder of who this software is for. At the University of Fribourg, the &lt;a href="https://www.gctronic.com/doc/index.php/e-puck2"&gt;e-puck2 robot&lt;/a&gt; is used to introduce students to mobile robotics. Fripuck is the name I&amp;rsquo;ve given to the new software I&amp;rsquo;m writing for the platform.&lt;/p&gt;
&lt;p&gt;These robots are currently controlled remotely over Wi-Fi via a Python API built by a previous student during their own thesis. While that was a huge step up from the raw C API used before, remote control still comes with inherent drawbacks, either due to API abstractions or issues fundamentally linked to wireless telemetry.&lt;/p&gt;
&lt;p&gt;Adding an on-board Lua VM allows students to program the robot directly without needing a continuous network connection. This allows them to investigate network latency, resource constraints, and the classic problem of dividing work between on-device and off-device execution. Scripting behavior in Lua is also vastly simpler than setting up toolchains, compiling C, and flashing new firmware to the robot, a high hurdle for first-year students who might be completely new to programming.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It&amp;rsquo;s worth noting that the Lua scripting engine isn&amp;rsquo;t meant to replace the remote Python API entirely&lt;/strong&gt;. Remote controlling remains the primary way to interact with the robot, especially since Lua on an embedded chip isn&amp;rsquo;t practical or fast enough for heavy sensor data analysis. Instead, the VM acts as a complementary tool: ideal for low-latency tasks, reactive behavior, or simple on-device configuration.&lt;/p&gt;
&lt;h2 id="why-lua"&gt;Why Lua?&lt;/h2&gt;
&lt;p&gt;Choosing Lua over other scripting options was a deliberate choice on my part. It gets its fair share of criticism: array indices famously start at &lt;code&gt;1&lt;/code&gt;, it is far less ubiquitous than Python, and its single complex data structure, the &lt;code&gt;table&lt;/code&gt;, takes some getting used to.&lt;/p&gt;
&lt;p&gt;However, Lua has its advantages in embedded contexts: it is easy to integrate into C programs via its clean, well-documented C API, while remaining fast, flexible, and lightweight. Compared to other embeddable languages like MicroPython, Lua is generally less RAM-hungry and features a cleaner architecture for constrained hardware. I&amp;rsquo;ll also admit to a personal fascination with the technology, and this project felt like the perfect sandbox to explore it thoroughly.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(If you are curious about Python support, &lt;a href="https://www.gctronic.com/"&gt;GCtronic&lt;/a&gt;, the makers of the robot, maintain an official &lt;a href="https://www.gctronic.com/doc/index.php?title=e-puck2_PC_side_development#Micropython"&gt;MicroPython port&lt;/a&gt; of the e-puck2 software).&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="architecture-and-api"&gt;Architecture and API&lt;/h2&gt;
&lt;p&gt;The two Lua environments I am most familiar with are game engines (like &lt;a href="https://www.lexaloffle.com/pico-8.php"&gt;PICO-8&lt;/a&gt; or &lt;a href="https://www.love2d.org/"&gt;LÖVE&lt;/a&gt;) and &lt;a href="https://neovim.io/"&gt;Neovim&lt;/a&gt;. From them, I borrowed two common patterns: &lt;strong&gt;update loops&lt;/strong&gt; and &lt;strong&gt;hooks&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="update-loops--hooks"&gt;Update Loops &amp;amp; Hooks&lt;/h3&gt;
&lt;p&gt;Update loops are standard in game engines and other frameworks. I chose to expose two: &lt;code&gt;init()&lt;/code&gt; and &lt;code&gt;update()&lt;/code&gt;, following PICO-8 terminology. Students write an &lt;code&gt;init()&lt;/code&gt; function for startup tasks, followed by an &lt;code&gt;update()&lt;/code&gt; loop that runs at roughly 20Hz (~50ms delta time).&lt;/p&gt;
&lt;p&gt;Alongside the loop, students can register &lt;strong&gt;hooks&lt;/strong&gt;, callback functions triggered immediately when an event occurs, such as a sensor read or an incoming command from a remote controller.&lt;/p&gt;
&lt;p&gt;Here is what a complete user script looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;30
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;31
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;32
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;33
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;34
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;35
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;36
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;37
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-lua" data-lang="lua"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;-- Declare local variables for fast lookup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;local&lt;/span&gt; last_side &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#ba2121"&gt;&amp;#34;center&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;local&lt;/span&gt; side &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#ba2121"&gt;&amp;#34;center&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;function&lt;/span&gt; &lt;span style="color:#00f"&gt;init&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#408080;font-style:italic"&gt;-- Register a hook to the ground sensors to track a black line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; robot.on(&lt;span style="color:#ba2121"&gt;&amp;#34;telemetry:ground&amp;#34;&lt;/span&gt;, &lt;span style="color:#008000;font-weight:bold"&gt;function&lt;/span&gt;(ground)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#408080;font-style:italic"&gt;-- Lua arrays start at 1!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;local&lt;/span&gt; difference &lt;span style="color:#666"&gt;=&lt;/span&gt; ground[&lt;span style="color:#666"&gt;1&lt;/span&gt;] &lt;span style="color:#666"&gt;-&lt;/span&gt; ground[&lt;span style="color:#666"&gt;3&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;if&lt;/span&gt; math.abs(difference) &lt;span style="color:#666"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#666"&gt;20&lt;/span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;if&lt;/span&gt; difference &lt;span style="color:#666"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#666"&gt;0&lt;/span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;then&lt;/span&gt; side &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#ba2121"&gt;&amp;#34;left&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;else&lt;/span&gt; side &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#ba2121"&gt;&amp;#34;right&amp;#34;&lt;/span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;else&lt;/span&gt; side &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#ba2121"&gt;&amp;#34;center&amp;#34;&lt;/span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;if&lt;/span&gt; side &lt;span style="color:#666"&gt;~=&lt;/span&gt; last_side &lt;span style="color:#008000;font-weight:bold"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#ba2121"&gt;&amp;#34;Black line on the &amp;#34;&lt;/span&gt; &lt;span style="color:#666"&gt;..&lt;/span&gt; side &lt;span style="color:#666"&gt;..&lt;/span&gt; &lt;span style="color:#ba2121"&gt;&amp;#34; side.&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; last_side &lt;span style="color:#666"&gt;=&lt;/span&gt; side
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;end&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;local&lt;/span&gt; counter &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#666"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;local&lt;/span&gt; total_dt &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#666"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;-- Runs every 50ms; prints average delta time every 100 iterations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;function&lt;/span&gt; &lt;span style="color:#00f"&gt;update&lt;/span&gt;(dt)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; counter &lt;span style="color:#666"&gt;=&lt;/span&gt; counter &lt;span style="color:#666"&gt;+&lt;/span&gt; &lt;span style="color:#666"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; total_dt &lt;span style="color:#666"&gt;=&lt;/span&gt; total_dt &lt;span style="color:#666"&gt;+&lt;/span&gt; dt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;if&lt;/span&gt; counter &lt;span style="color:#666"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#666"&gt;100&lt;/span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;local&lt;/span&gt; average &lt;span style="color:#666"&gt;=&lt;/span&gt; total_dt &lt;span style="color:#666"&gt;/&lt;/span&gt; counter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#ba2121"&gt;&amp;#34;Over 100 iterations, the average dt is: &amp;#34;&lt;/span&gt; &lt;span style="color:#666"&gt;..&lt;/span&gt; average &lt;span style="color:#666"&gt;..&lt;/span&gt; &lt;span style="color:#ba2121"&gt;&amp;#34;s.&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; counter &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#666"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; total_dt &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#666"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This script does two things: it listens to the &lt;code&gt;&amp;quot;telemetry:ground&amp;quot;&lt;/code&gt; hook to log which side of the robot detects a black line whenever new readings arrive, while the &lt;code&gt;update()&lt;/code&gt; loop continuously averages execution timing over 100 cycles.&lt;/p&gt;
&lt;p&gt;This structure provides the flexibility of simple update loops while allowing students to handle asynchronous data sources independently.&lt;/p&gt;
&lt;h3 id="handling-single-threaded-execution"&gt;Handling Single-Threaded Execution&lt;/h3&gt;
&lt;p&gt;Lua is strictly single-threaded, meaning the main update loop and event hooks cannot run concurrently. To prevent race conditions, I implemented an &lt;strong&gt;execution queue&lt;/strong&gt;. Incoming hooks are queued up and processed sequentially &lt;em&gt;before&lt;/em&gt; calling the update function. This keeps event processing prioritized without colliding with the main loop.&lt;/p&gt;
&lt;h2 id="c-api-implementation"&gt;C API Implementation&lt;/h2&gt;
&lt;p&gt;Before looking at the implementation details, here is a quick primer on how the Lua C API operates.&lt;/p&gt;
&lt;p&gt;All interactions between the host C code and the Lua engine pass through a &lt;strong&gt;virtual stack&lt;/strong&gt;. When a C function is called from Lua, we pop the input arguments off the stack, perform our logic, and push any return values back onto the stack. The same stack-based pattern applies to everything: defining tables, assigning metatables, or invoking Lua callbacks.&lt;/p&gt;
&lt;h3 id="exposing-userdata"&gt;Exposing Userdata&lt;/h3&gt;
&lt;p&gt;Exposing C data types to Lua as &lt;code&gt;userdata&lt;/code&gt; was new territory for me. The cleanest way to manage custom data types in Lua is through &lt;strong&gt;metatables&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Metatables attach custom behavior (using metamethods like &lt;code&gt;__index&lt;/code&gt; for field lookups or &lt;code&gt;__len&lt;/code&gt; for array length operators) to tables or userdata. Through metatables, C functions can define exactly how Lua reads or writes underlying C memory.&lt;/p&gt;
&lt;p&gt;For example, the &lt;code&gt;&amp;quot;telemetry:ground&amp;quot;&lt;/code&gt; hook exposes a 3-element array corresponding to the floor-facing infrared sensors. Here is how that lookup is handled in C:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-C" data-lang="C"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// Static buffer storing current sensor telemetry
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;static&lt;/span&gt; &lt;span style="color:#b00040"&gt;uint16_t&lt;/span&gt; ground[&lt;span style="color:#666"&gt;3&lt;/span&gt;] &lt;span style="color:#666"&gt;=&lt;/span&gt; {&lt;span style="color:#666"&gt;0&lt;/span&gt;, &lt;span style="color:#666"&gt;0&lt;/span&gt;, &lt;span style="color:#666"&gt;0&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// The __index metamethod function
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;static&lt;/span&gt; &lt;span style="color:#b00040"&gt;int&lt;/span&gt; &lt;span style="color:#00f"&gt;l_ground_index&lt;/span&gt;(lua_State&lt;span style="color:#666"&gt;*&lt;/span&gt; L) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;if&lt;/span&gt; (&lt;span style="color:#00f"&gt;lua_type&lt;/span&gt;(L, &lt;span style="color:#666"&gt;2&lt;/span&gt;) &lt;span style="color:#666"&gt;==&lt;/span&gt; LUA_TNUMBER) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;int&lt;/span&gt; i &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#00f"&gt;lua_tointeger&lt;/span&gt;(L, &lt;span style="color:#666"&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;if&lt;/span&gt; (i &lt;span style="color:#666"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#666"&gt;1&lt;/span&gt; &lt;span style="color:#666"&gt;||&lt;/span&gt; i &lt;span style="color:#666"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#666"&gt;3&lt;/span&gt;) &lt;span style="color:#008000;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#00f"&gt;luaL_error&lt;/span&gt;(L, &lt;span style="color:#ba2121"&gt;&amp;#34;Index %d out of range [1,3]&amp;#34;&lt;/span&gt;, i);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#408080;font-style:italic"&gt;// Convert 1-based Lua index to 0-based C array index
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;lua_pushinteger&lt;/span&gt;(L, ground[i &lt;span style="color:#666"&gt;-&lt;/span&gt; &lt;span style="color:#666"&gt;1&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#666"&gt;1&lt;/span&gt;; &lt;span style="color:#408080;font-style:italic"&gt;// Number of return values pushed to stack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;ESP_LOGW&lt;/span&gt;(TAG, &lt;span style="color:#ba2121"&gt;&amp;#34;Lua ground array called with unknown key.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#666"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// Handler for the &amp;#39;#&amp;#39; length operator (always 3 for ground sensors)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;static&lt;/span&gt; &lt;span style="color:#b00040"&gt;int&lt;/span&gt; &lt;span style="color:#00f"&gt;l_ground_len&lt;/span&gt;(lua_State&lt;span style="color:#666"&gt;*&lt;/span&gt; L) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;lua_pushinteger&lt;/span&gt;(L, &lt;span style="color:#666"&gt;3&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#666"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Next, we map these C functions to a metatable structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-C" data-lang="C"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;static&lt;/span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;const&lt;/span&gt; luaL_Reg ground_mt[] &lt;span style="color:#666"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#ba2121"&gt;&amp;#34;__index&amp;#34;&lt;/span&gt;, l_ground_index},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#ba2121"&gt;&amp;#34;__len&amp;#34;&lt;/span&gt;, l_ground_len},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#008000"&gt;NULL&lt;/span&gt;, &lt;span style="color:#008000"&gt;NULL&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;To register this metatable in Lua, we create it in the registry, populate its methods, and assign it when creating the userdata:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-C" data-lang="C"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// 1. Create a new metatable named &amp;#34;Ground&amp;#34; on top of the stack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;luaL_newmetatable&lt;/span&gt;(L, &lt;span style="color:#ba2121"&gt;&amp;#34;Ground&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// 2. Register functions into the metatable on top of the stack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;luaL_register&lt;/span&gt;(L, &lt;span style="color:#008000"&gt;NULL&lt;/span&gt;, ground_mt);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// 3. Pop the metatable off the stack now that it&amp;#39;s registered
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;lua_pop&lt;/span&gt;(L, &lt;span style="color:#666"&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Then, when instantiated, we allocate our &lt;code&gt;ground_t&lt;/code&gt; userdata inside Lua and attach the metatable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-C" data-lang="C"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;typedef&lt;/span&gt; &lt;span style="color:#b00040"&gt;uint16_t&lt;/span&gt; &lt;span style="color:#b00040"&gt;ground_t&lt;/span&gt;[&lt;span style="color:#666"&gt;3&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// Create userdata memory inside Lua (pushes it to the top of stack)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b00040"&gt;ground_t&lt;/span&gt;&lt;span style="color:#666"&gt;*&lt;/span&gt; g &lt;span style="color:#666"&gt;=&lt;/span&gt; (&lt;span style="color:#b00040"&gt;ground_t&lt;/span&gt;&lt;span style="color:#666"&gt;*&lt;/span&gt;)&lt;span style="color:#00f"&gt;lua_newuserdata&lt;/span&gt;(L, &lt;span style="color:#008000;font-weight:bold"&gt;sizeof&lt;/span&gt;(&lt;span style="color:#b00040"&gt;ground_t&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// Retrieve the &amp;#34;Ground&amp;#34; metatable and push it to the top of stack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;luaL_getmetatable&lt;/span&gt;(L, &lt;span style="color:#ba2121"&gt;&amp;#34;Ground&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// Attach the metatable to our userdata (located at stack index -2) and pop the metatable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;lua_setmetatable&lt;/span&gt;(L, &lt;span style="color:#666"&gt;-&lt;/span&gt;&lt;span style="color:#666"&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="managing-callbacks"&gt;Managing Callbacks&lt;/h3&gt;
&lt;p&gt;Allowing users to define hook callbacks in Lua requires three straightforward steps in C:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Store a reference to the user&amp;rsquo;s function in the Lua registry when registered.&lt;/li&gt;
&lt;li&gt;Push the function and its arguments onto the virtual stack when the event triggers.&lt;/li&gt;
&lt;li&gt;Execute the function using &lt;code&gt;lua_pcall()&lt;/code&gt; (it stands for &lt;em&gt;protected function call&lt;/em&gt;, which manages errors).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is a simplified view of executing a callback:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-C" data-lang="C"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;if&lt;/span&gt; (&lt;span style="color:#00f"&gt;lua_type&lt;/span&gt;(L, narg) &lt;span style="color:#666"&gt;==&lt;/span&gt; LUA_TFUNCTION) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#408080;font-style:italic"&gt;// The callback function is the second argument (after hook identifier)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;lua_pushvalue&lt;/span&gt;(L, &lt;span style="color:#666"&gt;2&lt;/span&gt;); &lt;span style="color:#408080;font-style:italic"&gt;// Push function onto stack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// Call the function on the stack with 1 argument and 0 return values
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;if&lt;/span&gt; (&lt;span style="color:#00f"&gt;lua_pcall&lt;/span&gt;(L, &lt;span style="color:#666"&gt;1&lt;/span&gt;, &lt;span style="color:#666"&gt;0&lt;/span&gt;, &lt;span style="color:#666"&gt;0&lt;/span&gt;) &lt;span style="color:#666"&gt;!=&lt;/span&gt; LUA_OK) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;ESP_LOGE&lt;/span&gt;(TAG, &lt;span style="color:#ba2121"&gt;&amp;#34;Error running hook callback: %s&amp;#34;&lt;/span&gt;, &lt;span style="color:#00f"&gt;lua_tostring&lt;/span&gt;(L, &lt;span style="color:#666"&gt;-&lt;/span&gt;&lt;span style="color:#666"&gt;1&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;lua_pop&lt;/span&gt;(L, &lt;span style="color:#666"&gt;1&lt;/span&gt;); &lt;span style="color:#408080;font-style:italic"&gt;// Remove error message from stack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In the real system, function references and event userdata are stored safely inside the Lua registry for persistent, repeated invocation.&lt;/p&gt;
&lt;h2 id="improvements-and-future-work"&gt;Improvements and Future Work&lt;/h2&gt;
&lt;p&gt;My Lua VM implementation is progressing well, but a few key tasks remain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mutation vs. Allocation:&lt;/strong&gt; Currently, data passed through hooks reuses the same memory reference across calls to minimize heap allocations on the ESP32. However, this means users cannot easily save historical readings without copying them, which might prove confusing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Direct Property Access:&lt;/strong&gt; I want to support direct variable queries (like &lt;code&gt;robot.ground[1]&lt;/code&gt;) within the main update loop without requiring dedicated hooks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connecting Actuators:&lt;/strong&gt; The e-puck2 hardware relies on two chips: the ESP32 (handling radio and running our Lua scripts) and an STM32 main controller (managing motors, low-level sensors, and passing telemetry to the ESP32). The final missing piece is wiring Lua commands back across the internal bus to control the physical actuators on the STM32, which will be my main focus for the rest of August.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Fripuck devlog n°4: Static FlatBuffers</title><link>https://www.uhrbaan.ch/blog/fripuck-4/</link><pubDate>Wed, 05 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.uhrbaan.ch/blog/fripuck-4/</guid><description>&lt;p&gt;Now that I’m back from vacation, I’ve managed to make progress on several key areas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.uhrbaan.ch/blog/fripuck-4/#1-project-management"&gt;Project management&lt;/a&gt;, to get a clearer overview of what’s done and what’s left to achieve;&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.uhrbaan.ch/blog/fripuck-4/#2-smarter-packaging"&gt;smarter packaging system&lt;/a&gt; to handle multiple sensors more efficiently;&lt;/li&gt;
&lt;li&gt;Reducing memory usage by implementing &lt;a href="https://www.uhrbaan.ch/blog/fripuck-4/#3-static-flatbuffers"&gt;static FlatBuffers&lt;/a&gt; with a custom emitter.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="1-project-management"&gt;1. Project Management&lt;/h2&gt;
&lt;p&gt;First, I finally took the time to explore project management tools and came across &lt;a href="https://kaneo.app/"&gt;Kaneo&lt;/a&gt;, a minimal Kanban application that also includes a backlog and a Gantt chart. I wanted something easy to self-host (there’s a Dokploy template for it) with a small enough feature set that I wouldn’t waste time learning how to use it.&lt;/p&gt;
&lt;p&gt;If you want to follow the project’s progress, you can now check out my &lt;a href="https://tasks.uhrbaan.ch/public-project/mwl0lhju0opl8zdi771jzwpt"&gt;Kanban board&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Visually tracking what needs to be done and what’s already completed helps me prioritize features and stay motivated by seeing steady progress. I’m glad I finally set this up.&lt;/p&gt;
&lt;h2 id="2-smarter-packaging"&gt;2. Smarter Packaging&lt;/h2&gt;
&lt;p&gt;Previously, each sensor had its own &lt;code&gt;_pack()&lt;/code&gt; function. This function would take all the data the sensor had accumulated since the last &lt;code&gt;_pack()&lt;/code&gt; call and add it to a shared FlatBuffer (FB). A central &lt;code&gt;telemetry&lt;/code&gt; task would then call these &lt;code&gt;_pack()&lt;/code&gt; functions sequentially and send out the final serialized packet.&lt;/p&gt;
&lt;p&gt;While this worked for testing, it had several issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It required writing nearly identical code for each new sensor.&lt;/li&gt;
&lt;li&gt;There was no check to ensure the packaged data didn’t exceed the available space.&lt;/li&gt;
&lt;li&gt;Starvation was inevitable since sensors were always processed in the same order.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first issue was &lt;em&gt;partially&lt;/em&gt; fixed using a macro to auto-generate the &lt;code&gt;_pack()&lt;/code&gt; function, but the other two remained. To address them, I decided to scrap this approach and switch to a sensor-agnostic implementation with a priority system.&lt;/p&gt;
&lt;h3 id="generalizing-the-packaging"&gt;Generalizing the Packaging&lt;/h3&gt;
&lt;p&gt;When using FlatBuffers, you typically use specialized functions for each datatype defined in your &lt;code&gt;.fbs&lt;/code&gt; schema. To handle any sensor, we use the underlying &lt;code&gt;flatcc_&lt;/code&gt; functions, which abstract the datatype into an &lt;em&gt;ID&lt;/em&gt;, &lt;em&gt;alignment&lt;/em&gt;, and &lt;em&gt;size&lt;/em&gt;. This allows us to register a sensor in the telemetry system by providing just these three values, instead of an entire function, simplifying the user’s work.&lt;/p&gt;
&lt;p&gt;Now, we have a single &lt;code&gt;generic_pack()&lt;/code&gt; function replacing the ~10 individual pack functions. Since this generic function is provided by the telemetry task rather than the sensor task, it has more context, enabling finer control over how data is packaged.&lt;/p&gt;
&lt;p&gt;Sensor data is appended using this simplified workflow:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;flatcc_builder_start_vector&lt;/span&gt;(B, size, alignment, max_count); &lt;span style="color:#408080;font-style:italic"&gt;// Prepare to build scrap vector in heap
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;flatcc_builder_append_vector&lt;/span&gt;(B, byte_buf &lt;span style="color:#666"&gt;+&lt;/span&gt; (start_idx &lt;span style="color:#666"&gt;*&lt;/span&gt; size), count); &lt;span style="color:#408080;font-style:italic"&gt;// Copy section of sensor data to the scrap vector
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b00040"&gt;flatcc_builder_ref_t&lt;/span&gt; vec_ref &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#00f"&gt;flatcc_builder_end_vector&lt;/span&gt;(B); &lt;span style="color:#408080;font-style:italic"&gt;// Get offset of built scrap vector
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b00040"&gt;flatcc_builder_ref_t&lt;/span&gt;&lt;span style="color:#666"&gt;*&lt;/span&gt; table_slot_ref &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#00f"&gt;flatcc_builder_table_add_offset&lt;/span&gt;(B, id); &lt;span style="color:#408080;font-style:italic"&gt;// Get offset of the sensor&amp;#39;s final vector
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#666"&gt;*&lt;/span&gt;table_slot_ref &lt;span style="color:#666"&gt;=&lt;/span&gt; vec_ref; &lt;span style="color:#408080;font-style:italic"&gt;// Map the scrap vector to the sensor&amp;#39;s final vector
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// Repeat the above steps for each sensor, each with its own size, alignment, and ID
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;FripuckProtocol_Sensors_SensorBatch_end_as_root&lt;/span&gt;(B); &lt;span style="color:#408080;font-style:italic"&gt;// Finally commit the full FlatBuffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Currently, these three values (ID, alignment, size) must be manually extracted from the generated &lt;code&gt;flatcc&lt;/code&gt; files, which isn’t ideal. In theory, they could be calculated or referenced in the generated code, which would be a better long-term solution.&lt;/p&gt;
&lt;h3 id="priority-system"&gt;Priority System&lt;/h3&gt;
&lt;p&gt;To prevent starvation and ensure time-sensitive data gets priority, I implemented a priority system. Each sensor is assigned a priority and an age when registered. These determine the order in which sensors are processed and how much data they can package. The process is straightforward: at each iteration, &lt;code&gt;age_step&lt;/code&gt; is added to the sensor’s age to track the time since its last data was sent, and then the &lt;code&gt;age&lt;/code&gt; is added to the &lt;code&gt;priority&lt;/code&gt; to compute the final priority. The sensor with the highest priority is selected next.&lt;/p&gt;
&lt;p&gt;This system prevents starvation but isn’t perfect. Ideally, we’d also consider how much data a sensor has collected or how to optimally fill the FlatBuffer packet. However, this simple system currently meets my needs.&lt;/p&gt;
&lt;h3 id="putting-it-all-together"&gt;Putting It All Together&lt;/h3&gt;
&lt;p&gt;With these two new systems and a limit tracker, we can now create a simple packaging loop to package and send data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b00040"&gt;void&lt;/span&gt; &lt;span style="color:#00f"&gt;pack_loop&lt;/span&gt;(&lt;span style="color:#b00040"&gt;flatcc_builder_t&lt;/span&gt;&lt;span style="color:#666"&gt;*&lt;/span&gt; builder, &lt;span style="color:#b00040"&gt;uint32_t&lt;/span&gt; budget) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;for&lt;/span&gt; (; budget &lt;span style="color:#666"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#666"&gt;0&lt;/span&gt;;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#408080;font-style:italic"&gt;// Priority check
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;struct&lt;/span&gt; sensor_info&lt;span style="color:#666"&gt;*&lt;/span&gt; s &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#00f"&gt;update_age_and_pick_sensor&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;if&lt;/span&gt; (s &lt;span style="color:#666"&gt;==&lt;/span&gt; &lt;span style="color:#008000"&gt;NULL&lt;/span&gt;) &lt;span style="color:#008000;font-weight:bold"&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#408080;font-style:italic"&gt;// Manage remaining space and package sensor data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;uint32_t&lt;/span&gt; bytes_written &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#666"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;generic_pack&lt;/span&gt;(builder, &lt;span style="color:#666"&gt;&amp;amp;&lt;/span&gt;s&lt;span style="color:#666"&gt;-&amp;gt;&lt;/span&gt;fb_data, budget, &lt;span style="color:#666"&gt;&amp;amp;&lt;/span&gt;bytes_written);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; budget &lt;span style="color:#666"&gt;-=&lt;/span&gt; bytes_written;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Adding a simple timeout ensures the &lt;code&gt;pack_loop&lt;/code&gt; doesn’t block when only a few or slow sensors are running. This results in a much better system than before. However, it still doesn’t address the initial problem of high memory usage, which brings us to the next adaptation:&lt;/p&gt;
&lt;h2 id="3-static-flatbuffers"&gt;3. Static FlatBuffers&lt;/h2&gt;
&lt;p&gt;First, let’s recap how FlatBuffers works. According to &lt;a href="https://flatbuffers.dev/"&gt;the docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;FlatBuffers is an efficient cross-platform serialization library for C++, Java, Python, Go, and more.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Unlike traditional serialization formats (e.g., JSON or Protobuf), FlatBuffers does not serialize or deserialize data in the conventional sense. Instead, it directly constructs a binary buffer in memory, where data is organized for zero-copy access. This means you can read fields directly from the buffer without parsing or deserialization steps.
To build this buffer, &lt;a href="https://github.com/dvidelabs/flatcc"&gt;&lt;code&gt;flatcc&lt;/code&gt;&lt;/a&gt; (the C implementation of FlatBuffers) follows a two-step process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It first creates intermediate vectors and tables (temporary structures) for each object defined in your &lt;code&gt;.fbs&lt;/code&gt; schema.&lt;/li&gt;
&lt;li&gt;It then compacts these into a single, contiguous memory block ready for transmission.
A unique aspect of FlatBuffers is its bidirectional growth mechanism. Instead of growing the buffer in one direction, it starts at the center (offset 0) and grows:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Data (e.g., vectors, scalars) toward negative offsets (left).&lt;/li&gt;
&lt;li&gt;Metadata (e.g., VTables, which store field layouts for tables) toward positive offsets (right).
This minimizes alignment gaps and ensures the final buffer is tightly packed.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; ┌───────┬────────┐
│ FB buffer │ Emitter ctx
└───────┼────────┘
Data │ Vtable (metadata)
- negative offset ◄───── │ ─────► + positive offset
│
offset 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;But why does it matter ?&lt;/strong&gt;
By default, &lt;code&gt;flatcc&lt;/code&gt; allocates this final buffer on the heap, which is problematic for embedded systems with limited memory. Fortunately, flatcc allows us to provide a custom emitter—a callback function that redirects buffer construction to a static memory region of our choosing. This avoids dynamic allocation entirely. It’s also possible to define a custom allocator for temporary vectors and objects before serialization, but that’s overkill for our needs.&lt;/p&gt;
&lt;p&gt;The custom emitter consists of three parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;emitter context&lt;/strong&gt; (&lt;code&gt;ctx&lt;/code&gt;), which holds all the data the emitter function needs to build the serialized buffer.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;emitter function&lt;/strong&gt; (&lt;code&gt;custom_builder_emit_fun&lt;/code&gt;), called each time an object or vector needs to be added to the final buffer. It appends data and metadata to their respective ends of the buffer.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;buffer itself&lt;/strong&gt;, where the data is stored.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We define the buffer as a static byte array with a &amp;ldquo;center.&amp;rdquo; Typically, the center (offset 0) isn’t perfectly centered in the static buffer, as &lt;code&gt;flatcc&lt;/code&gt; usually generates more data (left side) than metadata/vtable (right side):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#bc7a00"&gt;#define STATIC_FB_BUFFER_SIZE (2 * 1024) &lt;/span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// 2KB static payload buffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#bc7a00"&gt;#define FB_BUFFER_OFFSET_ZERO (STATIC_FB_BUFFER_SIZE * 3 / 4) &lt;/span&gt;&lt;span style="color:#408080;font-style:italic"&gt;// Index of offset 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;static&lt;/span&gt; &lt;span style="color:#b00040"&gt;uint8_t&lt;/span&gt; fb_buffer[STATIC_FB_BUFFER_SIZE] &lt;span style="color:#666"&gt;=&lt;/span&gt; {&lt;span style="color:#666"&gt;0&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Next, we define the context passed through function calls. To build the buffer and correctly determine its size, we need to store a reference to the buffer, its capacity, the center offset, and the min/max offsets:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;typedef&lt;/span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;uint8_t&lt;/span&gt;&lt;span style="color:#666"&gt;*&lt;/span&gt; buf;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;size_t&lt;/span&gt; capacity;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;flatbuffers_soffset_t&lt;/span&gt; zero_offset; &lt;span style="color:#408080;font-style:italic"&gt;// Center (offset +0) of the buffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;flatbuffers_soffset_t&lt;/span&gt; min_offset; &lt;span style="color:#408080;font-style:italic"&gt;// Tracks the lowest negative offset emitted
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;flatbuffers_soffset_t&lt;/span&gt; max_offset; &lt;span style="color:#408080;font-style:italic"&gt;// Tracks the highest positive offset emitted
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;} &lt;span style="color:#b00040"&gt;static_emitter_context_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, the tricky part: the emitter function. It must strictly follow this definition:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b00040"&gt;int&lt;/span&gt; &lt;span style="color:#00f"&gt;custom_builder_emit_fun&lt;/span&gt;(&lt;span style="color:#b00040"&gt;void&lt;/span&gt;&lt;span style="color:#666"&gt;*&lt;/span&gt; emit_context,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;const&lt;/span&gt; &lt;span style="color:#b00040"&gt;flatcc_iovec_t&lt;/span&gt;&lt;span style="color:#666"&gt;*&lt;/span&gt; iov,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;int&lt;/span&gt; iov_count,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;flatbuffers_soffset_t&lt;/span&gt; offset,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;size_t&lt;/span&gt; len);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Let’s break down the arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;emit_context&lt;/code&gt;: A pointer to the context we defined earlier.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iov&lt;/code&gt;: An array containing data+size pairs of the information to copy to the buffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iov_count&lt;/code&gt;: The number of elements in that array.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;offset&lt;/code&gt;: The offset (positive or negative) where the data should be written.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;len&lt;/code&gt;: The combined length of all &lt;code&gt;iov&lt;/code&gt; entries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The workflow is straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Loop through the &lt;code&gt;iov&lt;/code&gt; entries.&lt;/li&gt;
&lt;li&gt;Calculate the final address in the buffer where the data should be copied.&lt;/li&gt;
&lt;li&gt;Update the offset before writing the next entry.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In code, this looks like (omitting bounds checks and minor details):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000;font-weight:bold"&gt;for&lt;/span&gt; (&lt;span style="color:#b00040"&gt;int&lt;/span&gt; i &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#666"&gt;0&lt;/span&gt;; i &lt;span style="color:#666"&gt;&amp;lt;&lt;/span&gt; iov_count; &lt;span style="color:#666"&gt;++&lt;/span&gt;i) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;size_t&lt;/span&gt; elem_len &lt;span style="color:#666"&gt;=&lt;/span&gt; iov[i].iov_len;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;if&lt;/span&gt; (elem_len &lt;span style="color:#666"&gt;==&lt;/span&gt; &lt;span style="color:#666"&gt;0&lt;/span&gt;) &lt;span style="color:#008000;font-weight:bold"&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#408080;font-style:italic"&gt;// Calculate the final address to write to
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;uint8_t&lt;/span&gt;&lt;span style="color:#666"&gt;*&lt;/span&gt; dest &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#666"&gt;&amp;amp;&lt;/span&gt;ctx&lt;span style="color:#666"&gt;-&amp;gt;&lt;/span&gt;buf[ctx&lt;span style="color:#666"&gt;-&amp;gt;&lt;/span&gt;zero_offset &lt;span style="color:#666"&gt;+&lt;/span&gt; offset];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#408080;font-style:italic"&gt;// Copy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;memcpy&lt;/span&gt;(dest, iov[i].iov_base, elem_len);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#408080;font-style:italic"&gt;// Update the offset
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; offset &lt;span style="color:#666"&gt;+=&lt;/span&gt; (&lt;span style="color:#b00040"&gt;flatbuffers_soffset_t&lt;/span&gt;)elem_len;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Once all data is written, the lowest negative offset points to the start of a valid FlatBuffer—no further processing needed!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;While I haven’t been able to work on the project as much as I’d have liked, I’m glad to see it moving forward again. I’m also happy to see my memory issues disappear, hopefully for good 😅.&lt;/p&gt;
&lt;p&gt;Over the next month, I’ll try to dedicate as much time as possible to the project. Right now, I’m focusing my efforts on getting the camera to work and laying the initial foundations for the laptop → e-puck commands. This involves coordinating the radio and controller chip, as well as the Lua VM I’ve started implementing.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C&lt;/code&gt; you next month!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project Github page&lt;/strong&gt;: &lt;a href="https://github.com/Uhrbaan/fripuck2"&gt;https://github.com/Uhrbaan/fripuck2&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Fripuck devlog n°3: Meeting the professors</title><link>https://www.uhrbaan.ch/blog/fripuck-3/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://www.uhrbaan.ch/blog/fripuck-3/</guid><description>&lt;p&gt;Over the last two months, I was busy with my exam sessions and wasn’t able to make as much progress as I would have liked. However, I did start planning a smarter serialization approach for sensor data. Currently, all samples are packed one after another, which causes FlatBuffers to use too much RAM. The core idea is to track how much data has been packed so far and use a priority system to decide which packets get packed first. I don’t have much to show yet, though.&lt;/p&gt;
&lt;p&gt;During that time, I also met with my professors to discuss the project, which forced me to clarify what I want to achieve with Fripuck. I identified &lt;strong&gt;four key improvements&lt;/strong&gt; I could bring:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data analysis&lt;/li&gt;
&lt;li&gt;Networking&lt;/li&gt;
&lt;li&gt;Asynchronous API&lt;/li&gt;
&lt;li&gt;On-board programming&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://gcdnb.pbrd.co/images/tIK0kPvzNYqP.png" alt="Slide from my presentation explaining why I am working on the project. It reads: There are 2 reasons why I feel the need to improve the e-puck2 robot: 1. During the robotics course, a project was rendered impossible due to low data resolution. 2. Personal interest in exploring embedded systems development, a topic not fully covered in the university curriculum. Improvements on 4 aspects: enabling easy data analysis, improved networking, asynchronous API, and enabling on-board robotics."&gt;&lt;/p&gt;
&lt;h3 id="data-analysis"&gt;Data Analysis&lt;/h3&gt;
&lt;p&gt;The current API and firmware for the robot focus on real-time data, and it is up to the user to store and analyze it. My goal is to allow the firmware to save multiple data points in batches and send them at once, providing higher time resolution. The API would then store this historical data, allowing users to query it.&lt;/p&gt;
&lt;h3 id="improved-networking"&gt;Improved Networking&lt;/h3&gt;
&lt;p&gt;A major annoyance when working with the robot is unstable networking. When 10 students work simultaneously on 10 different robots, the network can fail. The new API should be able to reconnect the robot as quickly as possible to avoid disrupting the user.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://gcdnb.pbrd.co/images/2Il63qnsW0uB.png" alt="Planned structure for the firmware/API"&gt;&lt;/p&gt;
&lt;h3 id="asynchronous-api"&gt;Asynchronous API&lt;/h3&gt;
&lt;p&gt;This is another step toward improving time resolution. Currently, the Python API used to communicate with the robot relies on a single &lt;code&gt;.go_on()&lt;/code&gt; call that sends instructions and waits for sampled data. This causes the program to spend most of its time waiting for a network response, making the robot feel unresponsive. My goal is to make data exchange happen in the background so it doesn’t interfere with the user’s code. I also want to decouple the sending and receiving of data.&lt;/p&gt;
&lt;h3 id="on-board-robotics"&gt;On-board Robotics&lt;/h3&gt;
&lt;p&gt;Currently, the robot can only be controlled remotely, or the user must modify the robot’s firmware (which is not suitable for the target audience). I would like to add a small on-board interpreter, likely using Lua (since it is lightweight and easy to embed), to configure the robot and execute simple commands. This would allow users to change robot-specific features without recompiling the firmware or let students test latency when running scripts on-board or over the network.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="going-forward"&gt;Going Forward&lt;/h2&gt;
&lt;p&gt;This summer, I’ll have plenty of time to work on this project and, hopefully, complete 80% of it. Right now, the most urgent tasks are to configure my new OS (I distro-hopped to openSUSE) and create a roadmap of the features I need to implement. On the academic side, I need to identify measurable goals to evaluate the success of my project.&lt;/p&gt;</description></item><item><title>Fripuck devlog n°2: April update</title><link>https://www.uhrbaan.ch/blog/fripuck-2/</link><pubDate>Wed, 29 Apr 2026 00:00:00 +0000</pubDate><guid>https://www.uhrbaan.ch/blog/fripuck-2/</guid><description>&lt;p&gt;This month I&amp;rsquo;ve been quite busy with University work and had less time to focus on Fripuck. Still, I&amp;rsquo;ve managed to add new sensors and solve some pesky errors !&lt;/p&gt;
&lt;h2 id="new-sensors"&gt;New sensors&lt;/h2&gt;
&lt;p&gt;The e-puck2 robot is equipped with a ring of 8 &lt;strong&gt;proximity sensors&lt;/strong&gt; (IR reflectors). I followed the architecture of the previous implementation. It cleverly turns the IR ligths of the sensors on and off at specific time intervals to limit interference and measure the ambient light levels.&lt;/p&gt;
&lt;p&gt;This is a significant step-up of my previous implementation, which kept the IR lights always on and scanned the sensors continuously. Not only did this produce interference in the results, but also increased power consumption.&lt;/p&gt;
&lt;p&gt;This month also brought three sensors living on the I²C connection: the &lt;strong&gt;Time-of-Flight&lt;/strong&gt; (ToF), the &lt;strong&gt;Inertial Measurement Unit&lt;/strong&gt; (IMU) and the &lt;strong&gt;ground sensors&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="ic-stability-issues"&gt;I²C stability issues&lt;/h2&gt;
&lt;p&gt;I²C is a communication protocol where a master can communicate to multiple slave on the same wire. On the e-puck2, three sensors are configured and transfer their data over I²C: the ToF, IMU and ground sensors.&lt;/p&gt;
&lt;p&gt;At first, the I²C connection seemed to work for the ToF, but wouldn&amp;rsquo;t for the other sensors, always returning &lt;code&gt;HAL_BUSY&lt;/code&gt; error. Looking online, I found that I²C can be quite unstable on some STM chips, especially when using the standard &lt;code&gt;HAL_I2C_Mem_Read/Write&lt;/code&gt; functions. I decided to copy the implementation of the &lt;code&gt;vl53l0x&lt;/code&gt; ToF api code, which used the &lt;code&gt;HAL_I2C_Master_Receive/Transmit&lt;/code&gt; functions directly.&lt;/p&gt;
&lt;p&gt;Here is how my custom &lt;code&gt;2c_read/write_reg&lt;/code&gt; was implemented, if it can be of use for anyone:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;30
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;31
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HAL_StatusTypeDef &lt;span style="color:#00f"&gt;i2c_read_reg&lt;/span&gt;(&lt;span style="color:#b00040"&gt;uint8_t&lt;/span&gt; dev_addr, &lt;span style="color:#b00040"&gt;uint8_t&lt;/span&gt; reg, &lt;span style="color:#b00040"&gt;uint8_t&lt;/span&gt; &lt;span style="color:#666"&gt;*&lt;/span&gt;buffer, &lt;span style="color:#b00040"&gt;uint16_t&lt;/span&gt; len)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HAL_StatusTypeDef res;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;osMutexAcquire&lt;/span&gt;(i2c_mutex, osWaitForever);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#408080;font-style:italic"&gt;// Announce which device/register will be sent to
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; res &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#00f"&gt;HAL_I2C_Master_Transmit&lt;/span&gt;(i2c_handle, (dev_addr &lt;span style="color:#666"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style="color:#666"&gt;1&lt;/span&gt;), &lt;span style="color:#666"&gt;&amp;amp;&lt;/span&gt;reg, &lt;span style="color:#666"&gt;1&lt;/span&gt;, &lt;span style="color:#666"&gt;100&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;if&lt;/span&gt; (res &lt;span style="color:#666"&gt;==&lt;/span&gt; HAL_OK)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#408080;font-style:italic"&gt;// Recieve data from the slave
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; res &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#00f"&gt;HAL_I2C_Master_Receive&lt;/span&gt;(i2c_handle, (dev_addr &lt;span style="color:#666"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style="color:#666"&gt;1&lt;/span&gt;), buffer, len, &lt;span style="color:#666"&gt;100&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;osMutexRelease&lt;/span&gt;(i2c_mutex);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;return&lt;/span&gt; res;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HAL_StatusTypeDef &lt;span style="color:#00f"&gt;i2c_write_reg&lt;/span&gt;(&lt;span style="color:#b00040"&gt;uint8_t&lt;/span&gt; dev_addr, &lt;span style="color:#b00040"&gt;uint8_t&lt;/span&gt; reg, &lt;span style="color:#b00040"&gt;uint8_t&lt;/span&gt; &lt;span style="color:#666"&gt;*&lt;/span&gt;buffer, &lt;span style="color:#b00040"&gt;uint16_t&lt;/span&gt; len)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#408080;font-style:italic"&gt;// Local buffer to combine reg + data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b00040"&gt;uint8_t&lt;/span&gt; tmp[len &lt;span style="color:#666"&gt;+&lt;/span&gt; &lt;span style="color:#666"&gt;1&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tmp[&lt;span style="color:#666"&gt;0&lt;/span&gt;] &lt;span style="color:#666"&gt;=&lt;/span&gt; reg;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;memcpy&lt;/span&gt;(&lt;span style="color:#666"&gt;&amp;amp;&lt;/span&gt;tmp[&lt;span style="color:#666"&gt;1&lt;/span&gt;], buffer, len);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;osMutexAcquire&lt;/span&gt;(i2c_mutex, osWaitForever);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HAL_StatusTypeDef res &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#00f"&gt;HAL_I2C_Master_Transmit&lt;/span&gt;(i2c_handle, (dev_addr &lt;span style="color:#666"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style="color:#666"&gt;1&lt;/span&gt;), tmp, len &lt;span style="color:#666"&gt;+&lt;/span&gt; &lt;span style="color:#666"&gt;1&lt;/span&gt;, &lt;span style="color:#666"&gt;100&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;osMutexRelease&lt;/span&gt;(i2c_mutex);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000;font-weight:bold"&gt;return&lt;/span&gt; res;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="python-api"&gt;Python API&lt;/h2&gt;
&lt;p&gt;The python API now internally exposes an Abstract Base Class that makes it very easy to implement the reception of new sensor data. This framework has been used to implement all the sensor management in the python API, significantly reducing code duplication.&lt;/p&gt;
&lt;h2 id="whats-next-"&gt;What&amp;rsquo;s next ?&lt;/h2&gt;
&lt;p&gt;Next month, I&amp;rsquo;ll be working more on my upcoming exams, so it will probably be a quite uneventful month.&lt;/p&gt;
&lt;p&gt;In the available time I have, I will try to come op with a good priority system to manage which sensor data gets packaged when and sent. Currently, if I let too many sensors run at once, the Flatbuffers packet will consume so much ram that the program crashes. My objective is to smartly cut up the incoming data streams to control the final size of my Flatbuffers packet and make sure no streams are getting starved.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;While this month didn&amp;rsquo;t bring as many features as I would have hoped, I am still happy with what I achieved considering the limited amount of free time at my disposal. I am also pleased to see the I²C issue fixed, which has been bothering me for some time now.&lt;/p&gt;</description></item><item><title>Devlog n°1: Fripuck march update</title><link>https://www.uhrbaan.ch/blog/fripuck-1/</link><pubDate>Mon, 30 Mar 2026 00:00:00 +0000</pubDate><guid>https://www.uhrbaan.ch/blog/fripuck-1/</guid><description>&lt;p&gt;Hi there 👋
I&amp;rsquo;m &lt;a href="https://tooting.ch/@leonardurban"&gt;Uhrbaan&lt;/a&gt;, and for my bachelor thesis, I am working on updating the &lt;a href="https://www.gctronic.com/doc/index.php/e-puck2"&gt;e-puck2&lt;/a&gt;&amp;rsquo;s codebase and API to better fit the needs of my university.&lt;/p&gt;
&lt;p&gt;This blog serves as a monthly update on the progress I make on that project over the next year.&lt;/p&gt;
&lt;h2 id="what-is-the-e-puck-"&gt;What is the e-puck ?&lt;/h2&gt;
&lt;p&gt;The robot was designed by the EPFL, who describe them the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The e-puck is an educational robot that helps generations of students learn about embedded systems and robotics. First developed at EPFL in 2004 by Francesco Mondada and Michael Bonani, a new version was released in 2018, produced by GCtronic in Ticino. [&lt;a href="https://www.epfl.ch/labs/mobots/robots-technologies/e-puck2/"&gt;source&lt;/a&gt;]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Essentially, they are small robots equipped with many small sensors to help students make their first steps into mobile robotics.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.gctronic.com/doc/images/5/52/epuck2_features.png" alt="The e-puck2 robot and its many sensors"&gt;&lt;/p&gt;
&lt;p&gt;The e-pucks used at my university are built and maintained by &lt;a href="https://www.gctronic.com/"&gt;GCtronic&lt;/a&gt;, a small Ticinese company. They are also the authors of the main code running on these robots and also produced a C API to talk to the robots over the network. This API was later replaced by a Python API made by a university student during their bachelor thesis (just like me), which later also introduced computer vision capabilities through &lt;a href="https://en.wikipedia.org/wiki/You_Only_Look_Once"&gt;YOLO&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="what-is-fripuck-"&gt;What is Fripuck ?&lt;/h2&gt;
&lt;p&gt;Fripuck is my addition to this educational tool (if it works out 😅) ! The idea to work on the e-puck2 as my bachelor thesis rose out of two frustrations I encountered while working with the robots: (1) the blocking nature of the API and (2) the focus on real-time robotics, limiting the ability for data analysis/signal processing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Good software is born of frustration&lt;/p&gt;
&lt;p&gt;— Someone, probably&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A second reason I started this project, and why I am not only modifying/rewriting the API but also the firmware, is that I got really interested in embedded development. The university sadly doesn&amp;rsquo;t have courses about embedded programming, so I figured the best I could do was learn that subject on my own, and with my limited time, doing it during my bachelor thesis seemed to be the best option.&lt;/p&gt;
&lt;p&gt;Fripuck itself is the combination of three pieces of software: the firmware of the STM32F4 chip that controls the robot and all the sensors, the firmware of the ESP32 responsible for the communication over Wi-Fi with the student&amp;rsquo;s computer, and finally the API (Python or Go for testing).
The name of the project is a combination of e-puck and Fribourg, the university with which I am doing my thesis.&lt;/p&gt;
&lt;h2 id="what-do-i-want-to-achieve-i-technically-already-started-the-project-last-semester-so-about-4-months-ago-just-as-an-exploratory-phase-this-helped-me-to-read-a-bit-through-the-existing-code-base-get-familiar-with-the-hardware-and-explore-the-limitations-of-the-chip-for-example-i-got-a-lua-vm-working-on-one-of-the-two-chips-and-controlling-the-leds-with-it"&gt;What do I want to achieve? I technically already started the project last semester, so about 4 months ago, just as an &amp;ldquo;exploratory&amp;rdquo; phase. This helped me to read a bit through the existing code base, get familiar with the hardware, and explore the limitations of the chip. For example, I got a Lua VM working on one of the two chips and controlling the LEDs with it.&lt;/h2&gt;
&lt;p&gt;For the moment, I am mostly working on re-implementing the firmware and the API to reach feature parity (mostly, some features I do not care about since the university doesn&amp;rsquo;t use them) with the old software, the &lt;em&gt;status quo&lt;/em&gt;, while still keeping some nice improvements, like the API being multi-threaded and asynchronous by default, and the firmware using a more modern build system/development environment (&lt;a href="https://platformio.org/"&gt;PlatformIO&lt;/a&gt;), moving away from a custom serialization protocol to use &lt;a href="https://flatbuffers.dev/"&gt;Flatbuffers&lt;/a&gt; and using a real-time operating system more common in the academic world (FreeRTOS over ChibiOS).&lt;/p&gt;
&lt;p&gt;The progress can be tracked on GitHub at &lt;a href="https://github.com/Uhrbaan/fripuck2"&gt;https://github.com/Uhrbaan/fripuck2&lt;/a&gt; (at the time of writing, the table is mostly empty and the &lt;code&gt;README.md&lt;/code&gt; isn&amp;rsquo;t even fully finished yet).&lt;/p&gt;
&lt;p&gt;Ideally, if I have the time, I would like to add &lt;em&gt;new&lt;/em&gt; features as well, like a full audio stream from the robot to the clients, which would enable voice commands; a Lua VM to make the robots work offline; or even audio playback, which could make the robots talk, maybe transforming them into AI assistants, who knows! 🤷
Another big goal would be to increase the video playback speed, but I doubt it is possible to achieve more than 10 or 15 frames per second.&lt;/p&gt;
&lt;h2 id="what-have-i-achieved-so-far"&gt;What have I achieved so far?&lt;/h2&gt;
&lt;p&gt;Right now, most of the foundations have been laid out. I have a rather precise vision of the architecture of the whole system; the firmware is working, the Python API is working, and the robot can send and receive packets serialized as FlatBuffers. The only important foundational work that is remaining is managing the commands coming from the client.&lt;/p&gt;
&lt;p&gt;Once that is working, there will be a lot of work to re-add all the sensors, although I will probably be able to use a lot of the existing code to achieve this.&lt;/p&gt;
&lt;h2 id="what-are-you-working-on-right-now"&gt;What are you working on right now?&lt;/h2&gt;
&lt;p&gt;Currently, I am testing the telemetry process (the robot reads a sensor, packages it, sends it over the network, and the client receives it) by implementing the simple time-of-flight sensor and sending that data over the network. Once that is working, I will proceed to work on the commands.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;So far, I&amp;rsquo;ve been really enjoying the process. C was the first language I ever learned, and I hope this project is going to elevate my C skills to the next level. I am also having fun discovering the world of embedded programming, although I don&amp;rsquo;t find it as welcoming as other domains, since documentation and tutorials aren&amp;rsquo;t as readily available. I often find myself having to read through multiple example projects to understand what I am supposed to do, but on the bright side, code reading and comprehension is equally as important of a skill to train as writing it (since AIs are going to take that away apparently 🤷).&lt;/p&gt;
&lt;p&gt;Next month, I&amp;rsquo;ll probably go a bit more into the architecture I&amp;rsquo;ve planned and some technical challenges I&amp;rsquo;ve come across. Anyway, if you stayed through the whole text, thank you for your time !&lt;/p&gt;</description></item><item><title>Thoughts on the Slimbook EVO 14</title><link>https://www.uhrbaan.ch/blog/slimbook-evo-1/</link><pubDate>Fri, 15 Aug 2025 00:00:00 +0000</pubDate><guid>https://www.uhrbaan.ch/blog/slimbook-evo-1/</guid><description>&lt;p&gt;I’ve been using the &lt;a href="https://slimbook.com/en/evo"&gt;Slimbook EVO 14&lt;/a&gt; for a few days now, and here are my impressions. This is also a test of &lt;a href="https://writefreely.org/"&gt;writefreely&lt;/a&gt;—let’s see how it goes! 😉&lt;/p&gt;
&lt;h2 id="my-old-laptop"&gt;My Old Laptop&lt;/h2&gt;
&lt;p&gt;As a computer science (and biology) student, I mainly use my laptop for taking notes and doing homework. Beyond that, I’m a Linux hobbyist and enjoy programming for fun.&lt;/p&gt;
&lt;p&gt;Before switching to the Slimbook, I used a &lt;a href="https://www.lenovo.com/ch/en/p/laptops/ideapad/ideapad-c-series/lenovo-ideapad-c340-14iwl/88ipc301187#tech_specs"&gt;Lenovo Ideapad C340 14&amp;rsquo;&amp;rsquo; Intel&lt;/a&gt; with 1TB of storage and 16GB of RAM. While it served me well for about six years, several issues became hard to ignore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Screen quality&lt;/strong&gt;: The 1080p resolution was too low for comfortable text reading, and the viewing angles were poor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Battery life&lt;/strong&gt;: The 40Wh battery degraded over time, lasting only about 1.5 hours in power-saving mode by the end.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Graphics performance&lt;/strong&gt;: The integrated GPU struggled with anything beyond lightweight games (like Minecraft at 720p) and limited external 4K displays to 30Hz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build quality&lt;/strong&gt;: The chassis cracked in places, and I had to use duct tape to hold the hinges together.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keyboard failure&lt;/strong&gt;: An entire row of keys stopped working, rendering the laptop unusable without an external keyboard.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="choosing-a-new-laptop"&gt;Choosing a New Laptop&lt;/h2&gt;
&lt;p&gt;My new laptop needed to meet the following criteria:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Better performance&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More RAM&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improved screen&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Under 1,000 CHF&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Good Linux support&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;From a European company&lt;/strong&gt; (&lt;a href="https://www.cnbc.com/2025/08/01/switzerland-economic-blow-with-surprise-39percent-us-tariff.html"&gt;here’s why&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These last two points narrowed my options to &lt;a href="https://www.tuxedocomputers.com/"&gt;Tuxedo&lt;/a&gt; and &lt;a href="https://slimbook.com/en/"&gt;Slimbook&lt;/a&gt;. Since I carry my laptop all day, I wanted something under 15&amp;rsquo;&amp;rsquo;. This left me with the Slimbook EVO and the Tuxedo InfinityBook—&lt;strong&gt;identical machines in terms of specs&lt;/strong&gt;. I chose the Slimbook EVO because it was cheaper and came with GNOME preinstalled.&lt;/p&gt;
&lt;h2 id="review"&gt;Review&lt;/h2&gt;
&lt;h3 id="hardware"&gt;Hardware&lt;/h3&gt;
&lt;p&gt;The Slimbook EVO feels well-built, especially compared to my old laptop. The aluminum chassis is sturdy and premium. The screen is a significant upgrade: better viewing angles and resolution, though the scaling is a bit awkward. At 100%, everything is too small; at 200%, too large. Fractional scaling works, but may affect battery life. The 120Hz refresh rate is smooth, but I disabled it to save power—it’s not essential for my workflow.&lt;/p&gt;
&lt;p&gt;The only downgrade from my previous laptop is the lack of a touchscreen, but since I use an external drawing tablet, it’s not a dealbreaker.&lt;/p&gt;
&lt;p&gt;Performance-wise, this laptop is a breath of fresh air. It boots in under five seconds, handles Minecraft at full resolution (with shaders!), and supports 4K displays at 60Hz. My model has 32GB of RAM and 500GB of storage. The extra RAM is a relief, and while the storage is half of what I had before, 500GB is more than enough for my needs.&lt;/p&gt;
&lt;h3 id="software"&gt;Software&lt;/h3&gt;
&lt;p&gt;Buying from a Linux-first vendor means most drivers are preinstalled. However, I decided to install Ubuntu 25 from scratch—perhaps not the smartest move. The installation went smoothly, but I had to manually install a few packages, like the Ethernet driver (&lt;a href="https://slimbook.com/en/blog/guides-2/post/ethernet-driver-installation-tutorial-on-evo-457"&gt;guide here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Facial recognition via &lt;a href="https://github.com/boltgolt/howdy"&gt;howdy&lt;/a&gt; doesn’t work yet, as it depends on a library not yet ported to Ubuntu 25. For now, I’ll stick with typing my password.&lt;/p&gt;
&lt;p&gt;I also replaced Slimbook’s &lt;code&gt;slimbook-battery&lt;/code&gt; (which uses TLP) with &lt;code&gt;power-profiles-daemon&lt;/code&gt; for better GNOME integration. I might revisit this later to see if TLP offers better battery life.&lt;/p&gt;
&lt;p&gt;Speaking of battery, I capped the charge at 80% in the BIOS to extend its lifespan. It’s reassuring that Slimbook sells replacement batteries, which could be useful down the line.&lt;/p&gt;
&lt;h3 id="final-thoughts"&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;I’m happy with my purchase and would recommend the Slimbook EVO 14—especially if you value Linux support and European manufacturing.&lt;/p&gt;</description></item></channel></rss>