<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>cerkit.com</title>
	<atom:link href="https://cerkit.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://cerkit.com</link>
	<description></description>
	<lastBuildDate>Sat, 16 May 2026 00:26:50 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://cerkit.com/wp-content/uploads/2026/03/cropped-cerkit-logo-round-1-32x32.png</url>
	<title>cerkit.com</title>
	<link>https://cerkit.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>From Codes to Creativity: Why I Refactored My Generative Music System</title>
		<link>https://cerkit.com/from-codes-to-creativity-why-i-refactored-my-generative-music-system/</link>
					<comments>https://cerkit.com/from-codes-to-creativity-why-i-refactored-my-generative-music-system/#respond</comments>
		
		<dc:creator><![CDATA[cerkit]]></dc:creator>
		<pubDate>Sat, 16 May 2026 00:26:49 +0000</pubDate>
				<category><![CDATA[Art]]></category>
		<category><![CDATA[Gaming]]></category>
		<category><![CDATA[Programming]]></category>
		<guid isPermaLink="false">https://cerkit.com/?p=100646</guid>

					<description><![CDATA[<p>Discover why I transitioned my generative music system from rigid Camelot numbers to a flexible, theme-based naming convention. Learn how this refactor improved my game development workflow, allowing for unlimited scalability and seamless integration with Bitwig Studio for a more creative and sustainable interactive music engine.</p>
<p>The post <a href="https://cerkit.com/from-codes-to-creativity-why-i-refactored-my-generative-music-system/">From Codes to Creativity: Why I Refactored My Generative Music System</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>In game development, sometimes the systems that seem &#8220;clever&#8221; at the start become the very bottlenecks that stifle your creativity later on. This week, I hit one of those walls with my generative music engine and decided it was time for a major refactor.</p>



<h2 class="wp-block-heading" id="user-content-the-problem-with-camelot-numbers">The Problem with Camelot Numbers</h2>



<p>Up until now, I’ve been using the&nbsp;<strong>Camelot Wheel</strong>&nbsp;system to categorize my music. If you’re a DJ, you know the drill:&nbsp;<code>1A</code>,&nbsp;<code>2A</code>,&nbsp;<code>3B</code>, etc. It’s a great way to handle harmonic mixing, and it felt like a logical way to group tracks that should play together.</p>



<p>However, as my game world expanded, this system started to feel&#8230; restrictive. I only had a few Camelot numbers in active use, and whenever I wanted to add a new &#8220;vibe&#8221; or &#8220;biome&#8221; to the game, I found myself mentally mapping abstract numbers to actual musical themes. It wasn&#8217;t sustainable. I didn&#8217;t want to remember that <code>2A</code> was my &#8220;Berlin School&#8221; theme; I wanted it to just be called <strong>Berlin</strong>.</p>



<h2 class="wp-block-heading" id="user-content-the-solution-theme-based-mapping">The Solution: Theme-Based Mapping</h2>



<p>I’ve officially ditched the Camelot numbers in favor of&nbsp;<strong>Theme Names</strong>.</p>



<p>Instead of a folder named&nbsp;<code>1A</code>, I now have folders named&nbsp;<code>Ambient-001</code>,&nbsp;<code>Berlin-Industrial</code>, or&nbsp;<code>Deep-Space-Drone</code>. This might seem like a small semantic change, but it has completely opened up the workflow.</p>



<h3 class="wp-block-heading" id="user-content-why-this-is-better">Why this is better:</h3>



<ol class="wp-block-list">
<li><strong>Unlimited Scalability</strong>: I’m no longer bound by a fixed wheel of codes. I can create as many themes as I want, with descriptive names that actually mean something to me as a composer.</li>



<li><strong>Instant Integration</strong>: My mapping is now handled directly by the filesystem. If I want a new theme in the game, I just create a folder with that name in my <code>Assets/Music/Themes/</code> directory.</li>



<li><strong>Creative Freedom</strong>: It allows me to group related tracks much more intuitively. I can have <code>Ambient-Day</code> and <code>Ambient-Night</code> without trying to find &#8220;empty&#8221; Camelot slots for them.</li>
</ol>



<h2 class="wp-block-heading" id="user-content-the-bitwig-studio-to-godot-pipeline">The Bitwig Studio to Godot Pipeline</h2>



<p>The real magic of this transition is how it interacts with my DAW. Using&nbsp;<strong>Bitwig Studio</strong>, I can now create a set of related stems (Drone, Pads, Textures, Melodic) and export them as&nbsp;<code>.ogg</code>&nbsp;files directly into these theme folders.</p>



<p>Because my&nbsp;<code>MusicManager</code>&nbsp;in Godot is set up to scan these folders and automatically categorize tracks based on their filenames (using a simple&nbsp;<code>0-3</code>&nbsp;numbering convention for stem types), the transition from &#8220;Finished Loop&#8221; in Bitwig to &#8220;Interactive Music&#8221; in Godot takes seconds.</p>



<h2 class="wp-block-heading" id="user-content-final-thoughts">Final Thoughts</h2>



<p>Refactoring a core system can be scary, but it’s almost always worth it when it removes friction from your creative process. By moving from&nbsp;<strong>Camelot Numbers</strong>&nbsp;to&nbsp;<strong>Theme Names</strong>, I’ve traded a rigid technical constraint for a flexible, human-readable system that lets me focus on what matters most: making the game sound great.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><em>Stay tuned for more updates on the development of Fifty Things!</em></p><p>The post <a href="https://cerkit.com/from-codes-to-creativity-why-i-refactored-my-generative-music-system/">From Codes to Creativity: Why I Refactored My Generative Music System</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://cerkit.com/from-codes-to-creativity-why-i-refactored-my-generative-music-system/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Dynamic Generative Music: Bringing the Camelot Wheel to Godot</title>
		<link>https://cerkit.com/dynamic-generative-music-bringing-the-camelot-wheel-to-godot/</link>
					<comments>https://cerkit.com/dynamic-generative-music-bringing-the-camelot-wheel-to-godot/#respond</comments>
		
		<dc:creator><![CDATA[cerkit]]></dc:creator>
		<pubDate>Thu, 14 May 2026 00:07:40 +0000</pubDate>
				<category><![CDATA[Gaming]]></category>
		<category><![CDATA[Music]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[retro]]></category>
		<guid isPermaLink="false">https://cerkit.com/?p=100640</guid>

					<description><![CDATA[<p>Discover how to use the DJ's Camelot Wheel for generative game music. Explore our implementation of a synchronized 4-stem audio engine in Godot that delivers infinite musical variety with a tiny memory footprint.</p>
<p>The post <a href="https://cerkit.com/dynamic-generative-music-bringing-the-camelot-wheel-to-godot/">Dynamic Generative Music: Bringing the Camelot Wheel to Godot</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>I&#8217;ve been working on the development of a new game (working title is <em>50 Things</em>). In our latest development update for <em>50 Things</em>, we’ve pivoted our audio architecture toward a more dynamic, generative approach. Instead of static, looping background tracks, we’ve implemented a <strong>Camelot-based Music System</strong> that creates a unique soundscape every time you play.</p>



<h2 class="wp-block-heading" id="user-content-what-is-the-camelot-system">What is the Camelot System?</h2>



<p>The&nbsp;<strong>Camelot Wheel</strong>&nbsp;is a tool used by DJs to mix tracks harmonically. Every musical key is assigned a number and a letter (e.g.,&nbsp;<code>8A</code>&nbsp;for A minor,&nbsp;<code>9A</code>&nbsp;for E minor). By matching these numbers, you ensure that multiple audio stems will always sound harmonious when played together.</p>



<p>In our game, we use this to drive a &#8220;Generative&#8221; engine.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img fetchpriority="high" decoding="async" width="1024" height="994" src="https://cerkit.com/wp-content/uploads/2026/05/image-1024x994.png" alt="" class="wp-image-100642" style="aspect-ratio:1.030200827839951;width:532px;height:auto" srcset="https://cerkit.com/wp-content/uploads/2026/05/image-1024x994.png 1024w, https://cerkit.com/wp-content/uploads/2026/05/image-300x291.png 300w, https://cerkit.com/wp-content/uploads/2026/05/image-768x746.png 768w, https://cerkit.com/wp-content/uploads/2026/05/image.png 1030w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">The &#8220;Camelot Wheel&#8221; for music mixing</figcaption></figure>
</div>


<h2 class="wp-block-heading" id="user-content-why-generative-music">Why Generative Music?</h2>



<p>Standard game music can become repetitive. By breaking a song down into four distinct &#8220;stems,&#8221; we can mix and match variations in real-time:</p>



<ol class="wp-block-list">
<li><strong>SubDrone</strong>: The foundational low-end.</li>



<li><strong>MainPads</strong>: The harmonic atmosphere.</li>



<li><strong>Textures</strong>: Environmental noise and field recordings.</li>



<li><strong>Melodic</strong>: Leads, arpeggios, and high-frequency flair.</li>
</ol>



<p>By having, for example, 3 different pad variations and 5 different lead variations for a single key, the game can generate 15 different versions of the same song!</p>



<h2 class="wp-block-heading" id="user-content-technical-implementation-in-godot">Technical Implementation in Godot</h2>



<p>We built this system using&nbsp;<strong>Godot Mono (C#)</strong>&nbsp;with a focus on performance and flexibility.</p>



<h3 class="wp-block-heading" id="user-content-1-the-centralized-json-catalog">1. The Centralized JSON Catalog</h3>



<p>We moved away from embedding metadata in WAV files. Instead, we use a single <code>track_catalog.json</code>. This file acts as the &#8220;brain&#8221; of the system, telling the game which files belong to which Camelot key and stem type.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;tracks&quot;: &#x5B;
    {
      &quot;FileName&quot;: &quot;abyss_2A.ogg&quot;,
      &quot;TrackName&quot;: &quot;Abyssal Pad&quot;,
      &quot;CamelotNumber&quot;: &quot;2A&quot;,
      &quot;StemType&quot;: 0
    }
  ]
}
</pre></div>


<h3 class="wp-block-heading" id="user-content-2-the-musicmanager-singleton">2. The MusicManager (Singleton)</h3>



<p>The&nbsp;<code>MusicManager</code>&nbsp;is a global autoload that handles the heavy lifting. It parses the JSON, caches the audio files to prevent frame-stutter, and—most importantly—triggers all four stems at the exact same micro-second to ensure perfect synchronization.</p>



<p>We also added support for multiple formats like&nbsp;<strong>OGG Vorbis</strong>, which drastically reduces our memory footprint (from 30MB WAVs down to 3MB OGGs!) while maintaining high quality and gapless loops.</p>



<h3 class="wp-block-heading" id="user-content-3-the-trigger-system">3. The Trigger System</h3>



<p>To bring the music into the world, we created a&nbsp;<strong><code>GenerativeMusicTrigger</code></strong>&nbsp;scene. It’s a simple collision area that developers can drop anywhere in a level. When the player walks into the trigger, it tells the&nbsp;<code>MusicManager</code>&nbsp;to &#8220;Play 2A,&#8221; and the engine instantly builds a harmonious mix from the available catalog.</p>



<h2 class="wp-block-heading" id="user-content-summary">Summary</h2>



<p>This pivot to a JSON-driven Camelot system gives us:</p>



<ul class="wp-block-list">
<li><strong>Infinite Variety</strong>: No two playthroughs sound exactly the same.</li>



<li><strong>Harmonic Consistency</strong>: The music always sounds &#8220;right&#8221; because it follows music theory rules.</li>



<li><strong>Efficiency</strong>: Smaller file sizes and smart caching mean better performance on all hardware.</li>
</ul>



<p>Stay tuned for more updates as we continue to expand the soundscape of&nbsp;<em>50 Things</em>!</p><p>The post <a href="https://cerkit.com/dynamic-generative-music-bringing-the-camelot-wheel-to-godot/">Dynamic Generative Music: Bringing the Camelot Wheel to Godot</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://cerkit.com/dynamic-generative-music-bringing-the-camelot-wheel-to-godot/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Star Wars: Outer Rim on Mac &#8211; Tabletop Simulator Guide</title>
		<link>https://cerkit.com/star-wars-outer-rim-on-mac-tabletop-simulator-guide/</link>
					<comments>https://cerkit.com/star-wars-outer-rim-on-mac-tabletop-simulator-guide/#respond</comments>
		
		<dc:creator><![CDATA[cerkit]]></dc:creator>
		<pubDate>Sun, 29 Mar 2026 18:13:34 +0000</pubDate>
				<category><![CDATA[Gaming]]></category>
		<category><![CDATA[Star Wars]]></category>
		<guid isPermaLink="false">https://cerkit.com/?p=100602</guid>

					<description><![CDATA[<p>I love being a galactic scoundrel in Star Wars: Outer Rim, but I've always dreaded the massive table setup and cleanup. Here’s how I moved my smuggling career to my Mac using Tabletop Simulator—and why I’m never going back to physical bags of tokens again.</p>
<p>The post <a href="https://cerkit.com/star-wars-outer-rim-on-mac-tabletop-simulator-guide/">Star Wars: Outer Rim on Mac – Tabletop Simulator Guide</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>I have a confession to make: I want to spend my weekends as a notorious scoundrel, running illegal cargo across the galaxy, dodging Imperial patrols, and hunting down bounties for the highest bidder. Unfortunately, my actual weekends usually involve more laundry and less hyperspace.</p>



<p>I have owned the physical version of <em>Star Wars: Outer Rim</em> for a while now, and it perfectly captures that gritty, scoundrel lifestyle. The gameplay loop of upgrading your ship, hiring crew members like Chewbacca or Hondo Ohnaka, and taking risky jobs is incredibly fun. It’s exactly the kind of thematic experience I want from a Star Wars board game.</p>



<p>However, there is a giant, logic-defying asteroid in the way: the setup.</p>



<h3 class="wp-block-heading">The Barrier to Entry: The Infamous &#8220;Tabletop Shrapnel&#8221;</h3>



<p>I truly love the <em>game</em>, but I never, ever look forward to the setup prior to playing. <em>Outer Rim</em> has a massive table presence. The galaxy map is made of individual crescent-shaped pieces that need to be aligned. There are numerous market decks that all require shuffling—cargo, mods, luxury goods, jobs, bounties, ships. On top of that, you have hundreds of tiny tokens for damage, credits, contacts, and reputation. It takes me a solid 20 minutes to set up, and I always feel a little overwhelmed before my first turn.</p>



<p>And don’t even get me started on the take-down. Sweeping those hundreds of tiny components back into their tiny plastic baggies while my friends are already asking about what we’re going to eat is the ultimate anti-climax. This setup and teardown fatigue has, more than once, caused us to choose a simpler, &#8220;cleaner&#8221; game instead.</p>



<p>That is exactly why the digital version on <strong>Tabletop Simulator</strong> on my Mac has been an absolute revelation. Star Wars Outer Rim Tabletop Simulator is a fun way to play.</p>



<h3 class="wp-block-heading">Making it Virtual: Getting Started on your Mac</h3>



<p>Tabletop Simulator (TTS) isn’t a standalone game; it’s a physics toolbox that lets you play board games digitally. If you are a Mac user, this is the most effective way to play <em>Outer Rim</em>. Here is how you can get it up and running:</p>



<p>The first step is getting Tabletop Simulator itself. You will need to have the Steam client installed on your Mac. If you don&#8217;t already have Steam, visit store.steampowered.com and download it. Once Steam is running, search for &#8220;Tabletop Simulator.&#8221; It frequently goes on sale during Steam’s seasonal events. Purchase the game and Steam will immediately download and install it into your library.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="579" src="https://cerkit.com/wp-content/uploads/2026/03/Image-3-29-26-at-1.01-PM-1024x579.png" alt="Star Wars: Outer Rim - Han Solo's late game stats" class="wp-image-100605" srcset="https://cerkit.com/wp-content/uploads/2026/03/Image-3-29-26-at-1.01-PM-1024x579.png 1024w, https://cerkit.com/wp-content/uploads/2026/03/Image-3-29-26-at-1.01-PM-300x170.png 300w, https://cerkit.com/wp-content/uploads/2026/03/Image-3-29-26-at-1.01-PM-768x435.png 768w, https://cerkit.com/wp-content/uploads/2026/03/Image-3-29-26-at-1.01-PM-1536x869.png 1536w, https://cerkit.com/wp-content/uploads/2026/03/Image-3-29-26-at-1.01-PM.png 1870w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading">Installing &#8220;Star Wars: Outer Rim&#8221; into TTS</h3>



<p>This is where the magic of the community happens. Once Tabletop Simulator is installed, you need to find the specific community mod for <em>Star Wars: Outer Rim</em>. While on the Tabletop Simulator page in your Steam Library, look for the &#8220;Workshop&#8221; tab located just below the &#8220;Play&#8221; button. Click it to open the library of user-created mods. In the main Workshop search bar, type &#8220;Star Wars Outer Rim&#8221;. You will see several versions. I highly recommend looking for a version marked &#8220;scripted&#8221;—these are the ones created by amazing community coders that handle the tedious bits for you. When you find a highly rated version that looks good, simply click on the thumbnail to visit its page, and then click the large green &#8220;+ Subscribe&#8221; button. Steam will immediately begin downloading that mod’s assets into your TTS game file.</p>



<h3 class="wp-block-heading">Hyperspace in a Single Click</h3>



<p>Once subscribed, launch Tabletop Simulator. Click on &#8220;Create&#8221; (or &#8220;Join&#8221; for multiplayer), select your game type (Singleplayer works great for learning), and then click on the <strong>Workshop</strong> tab. You will see a thumbnail of the <em>Star Wars: Outer Rim</em> mod you just subscribed to. Click it, and the game will begin to load.</p>



<p>Because I use a highly scripted mod, the annoying setup and take-down that I dreaded are completely gone. The mod randomizes the contact tokens, shuffles every market deck, and builds the intricate galaxy map on its own. In the base game, I might dread shuffling that Databank deck, but in TTS, I just right-click and search. This digital setup removes all the friction, allowing me to dive straight into the fun of upgrading my <em>YT-1300</em> and navigating the dangerous criminal underworld of the Outer Rim.</p><p>The post <a href="https://cerkit.com/star-wars-outer-rim-on-mac-tabletop-simulator-guide/">Star Wars: Outer Rim on Mac – Tabletop Simulator Guide</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://cerkit.com/star-wars-outer-rim-on-mac-tabletop-simulator-guide/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Aliens in the Studio: Prepping for Magic City Art Connection</title>
		<link>https://cerkit.com/aliens-in-the-studio-prepping-for-magic-city-art-connection/</link>
					<comments>https://cerkit.com/aliens-in-the-studio-prepping-for-magic-city-art-connection/#respond</comments>
		
		<dc:creator><![CDATA[cerkit]]></dc:creator>
		<pubDate>Sun, 29 Mar 2026 03:27:38 +0000</pubDate>
				<category><![CDATA[Music]]></category>
		<guid isPermaLink="false">https://cerkit.com/?p=100594</guid>

					<description><![CDATA[<p>Get a sneak peek into the practice session for the upcoming performance at Magic City Art Connection in Birmingham, AL.</p>
<p>The post <a href="https://cerkit.com/aliens-in-the-studio-prepping-for-magic-city-art-connection/">Aliens in the Studio: Prepping for Magic City Art Connection</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>The countdown is on! I’m incredibly excited to announce that I’ll be performing live at the <strong><a href="https://magiccityart.com/" target="_blank" rel="noreferrer noopener">Magic City Art Connection</a></strong> in Birmingham, Alabama. This festival is always a highlight for the local arts scene, and being a part of the lineup this year is a huge honor.</p>



<h3 class="wp-block-heading">A Look Behind the Scenes</h3>



<p>Preparations are in full swing. Today, <strong><a href="https://www.youtube.com/@ozhalljr" target="_blank" rel="noopener" title="">O.Z. Hall, Jr.</a></strong> and I got together for an intensive practice session to dial in our sound and transition through the set. There’s something special about the energy that builds when the synthesizers start humming and the sequences begin to lock in.</p>



<p>While we were deep in the gear, I decided to capture the moment—but with a bit of a twist.</p>



<h3 class="wp-block-heading">Enter the &#8220;Crow-Headed Aliens&#8221;</h3>



<p>I’ve been experimenting with 360-degree photography to capture the vibe of our practice space. However, a standard view of cables and knobs felt a bit too &#8220;human.&#8221; To spice things up, I’ve digitally altered the images to reflect the otherworldly nature of the music we’re creating.</p>



<p>In this immersive view, you’ll find two crow-headed aliens at the helm of the synthesizers, navigating through a landscape of electronic sound.</p>



<p><strong>Experience the 360 view here:</strong>&nbsp;<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong><a target="_blank" rel="noreferrer noopener" href="https://cerkit.com/cerkit360/caldera-dreams-360">Caldera Dreams &#8211; 360 Practice Space</a></strong></p>



<p><em>(Note: If you’re on mobile, you can move your phone around to look around the room; on a desktop, just click and drag! Access it from a VR headset to be completely immersed!)</em></p>



<h3 class="wp-block-heading">Join Us in Birmingham at the Magic City Art Connection</h3>



<p>We are hard at work making sure this performance is a unique sonic experience. If you’re in the Birmingham area during the festival, I’d love to see you there.</p>



<p>Stay tuned for more updates as we get closer to the event. Check out the official <strong><a href="https://magiccityart.com/" target="_blank" rel="noreferrer noopener">Magic City Art Connection website</a></strong> for the full schedule and ticket info (the 2026 performance schedule should be released around the first of April).</p><p>The post <a href="https://cerkit.com/aliens-in-the-studio-prepping-for-magic-city-art-connection/">Aliens in the Studio: Prepping for Magic City Art Connection</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://cerkit.com/aliens-in-the-studio-prepping-for-magic-city-art-connection/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Building a Left-Handed Piano MIDI &#8220;Man-in-the-Middle&#8221; Device</title>
		<link>https://cerkit.com/building-a-left-handed-piano-midi-man-in-the-middle-device/</link>
					<comments>https://cerkit.com/building-a-left-handed-piano-midi-man-in-the-middle-device/#respond</comments>
		
		<dc:creator><![CDATA[cerkit]]></dc:creator>
		<pubDate>Wed, 18 Mar 2026 23:27:10 +0000</pubDate>
				<category><![CDATA[Electronics]]></category>
		<category><![CDATA[Hardware]]></category>
		<category><![CDATA[Music]]></category>
		<guid isPermaLink="false">https://cerkit.com/?p=100578</guid>

					<description><![CDATA[<p>Learn how to build a custom Arduino MEGA MIDI Man-in-the-Middle (MITM) device that inverts incoming notes, letting you play any standard keyboard as a perfectly mirrored "left-handed" piano.</p>
<p>The post <a href="https://cerkit.com/building-a-left-handed-piano-midi-man-in-the-middle-device/">Building a Left-Handed Piano MIDI “Man-in-the-Middle” Device</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Have you ever wondered what it would be like to play the piano mirrored? What if the low notes were on your right hand and the high melodies were on your left?</p>



<p>I recently built a hardware-based MIDI &#8220;Man-in-the-Middle&#8221; (MITM) device that does exactly this: it inverts MIDI notes in real-time, allowing you to play any standard MIDI controller as a &#8220;Left-Handed&#8221; piano. In this post, I&#8217;ll break down how this project works, the technical details, and how you can build one yourself!</p>



<h2 class="wp-block-heading" id="user-content-the-concept-inverting-the-keyboard">The Concept: Inverting the Keyboard</h2>



<p>The theory behind a left-handed piano is to flip the keyboard layout around a central pivot point. For this project, I chose&nbsp;<strong>D4 (MIDI Note 62)</strong>&nbsp;as the pivot.</p>



<p>By applying a simple mathematical formula, we can invert the incoming notes:</p>



<p><code>Inverted Note = 124 - Original Note</code></p>



<p>With this formula:</p>



<ul class="wp-block-list">
<li><strong>D4 (62)</strong>&nbsp;remains&nbsp;<strong>D4 (62)</strong></li>



<li><strong>C4 (60)</strong>&nbsp;becomes&nbsp;<strong>E4 (64)</strong></li>



<li><strong>A0 (21)</strong>&nbsp;becomes&nbsp;<strong>G8 (103)</strong></li>
</ul>



<p>This creates a perfect mirror image of the keyboard!</p>



<h2 class="wp-block-heading" id="user-content-the-hardware-arduino-as-a-midi-mitm">The Hardware: Arduino as a MIDI MITM</h2>



<p>To achieve this without noticeable latency, I used an Arduino MEGA 2560 to sit horizontally between the MIDI controller and the sound module (or DAW). The MEGA is great for this because it features multiple hardware serial ports (<code>Serial1</code>,&nbsp;<code>Serial2</code>, etc.), allowing high-speed, reliable MIDI handling while still being able to output debug information over USB (<code>Serial</code>).</p>



<p>The flow looks like this:</p>



<ol class="wp-block-list">
<li><strong>MIDI IN</strong>: The controller sends Note On/Off messages to the Arduino&#8217;s&nbsp;<code>Serial1</code>.</li>



<li><strong>Processing</strong>: The Arduino runs a robust state machine to parse the incoming MIDI bytes. It applies our inversion formula to note data while letting other messages (like Clock or CC) pass through untouched.</li>



<li><strong>MIDI OUT</strong>: The modified MIDI data is sent out to the synthesizer via&nbsp;<code>Serial2</code>.</li>
</ol>



<h2 class="wp-block-heading" id="user-content-overcoming-technical-hurdles">Overcoming Technical Hurdles</h2>



<p>It might sound simple to just subtract the note number, but MIDI is a serial protocol with some quirks that make real-time processing tricky. Here are a few things the code has to handle gracefully:</p>



<h3 class="wp-block-heading" id="user-content-1-zero-latency-parsing">1. Zero-Latency Parsing</h3>



<p>The code uses a non-blocking state-machine-based parser to process MIDI bytes exactly as they arrive. This ensures there&#8217;s no perceptible processing delay when playing fast passages.</p>



<h3 class="wp-block-heading" id="user-content-2-state-management-for-stuck-notes">2. State Management for &#8220;Stuck Notes&#8221;</h3>



<p>What happens if you press a key, the translation logic changes (e.g., a hardware bypass switch is flipped), and then you release the key? If we inverted the release message based on the&nbsp;<em>current</em>&nbsp;state rather than the state when the key was pressed, we might send a Note Off for the wrong key, resulting in the dreaded &#8220;stuck note.&#8221;</p>



<p>To solve this, the software maintains an internal array&nbsp;<code>activeNotes[128]</code>&nbsp;to track the exact inverted note that was triggered for every physical key. When a key is released, it looks up the specific note in the array to turn off.</p>



<h3 class="wp-block-heading" id="user-content-3-midi-running-status">3. MIDI Running Status</h3>



<p>MIDI devices often use &#8220;Running Status&#8221; to save bandwidth. If multiple Note On messages are sent in a row, the status byte (<code>0x90</code>) is omitted for subsequent notes. The parser has to keep track of the last seen status byte to correctly interpret incoming data bytes.</p>



<h2 class="wp-block-heading" id="user-content-bill-of-materials-bom">Bill of Materials (BOM)</h2>



<p>To build this project, you will need the following components:</p>



<ul class="wp-block-list">
<li><strong>Arduino MEGA 2560:</strong>&nbsp;The core processor board providing ample hardware serial ports.</li>



<li><strong>MIDI Breakout Shield:</strong>&nbsp;A <a href="https://amzn.to/4sTa12H" target="_blank" rel="noopener" title="">board with 5-pin DIN connectors</a> and an optoisolator circuit.</li>



<li><strong>4x Jumper Wires:</strong>&nbsp;For connecting the shield to the MEGA&#8217;s alternate hardware serial pins.</li>



<li><strong>USB Cable (A-to-B):</strong>&nbsp;To power the device and monitor the real-time serial output.</li>



<li><strong>2x Standard MIDI Cables:</strong>&nbsp;For connecting your keyboard and your synthesizer/audio interface.</li>
</ul>



<h2 class="wp-block-heading" id="user-content-wiring-diagram">Wiring Diagram</h2>



<p>Because we want to reserve the MEGA&#8217;s primary serial port (Pin 0/1) for PC debugging, we do not simply stack the MIDI shield on top. Instead, we use jumper wires to route the MIDI data to&nbsp;<code>Serial1</code>&nbsp;(RX1) and&nbsp;<code>Serial2</code>&nbsp;(TX2).</p>



<figure class="wp-block-image size-large"><img decoding="async" width="750" height="1024" src="https://cerkit.com/wp-content/uploads/2026/03/image-750x1024.png" alt="" class="wp-image-100585" srcset="https://cerkit.com/wp-content/uploads/2026/03/image-750x1024.png 750w, https://cerkit.com/wp-content/uploads/2026/03/image-220x300.png 220w, https://cerkit.com/wp-content/uploads/2026/03/image-768x1048.png 768w, https://cerkit.com/wp-content/uploads/2026/03/image.png 1026w" sizes="(max-width: 750px) 100vw, 750px" /></figure>



<p><em>Note: The shield&#8217;s RX pin receives data from the MIDI IN optoisolator and connects to the Arduino&#8217;s RX1. The Arduino&#8217;s TX2 pin sends data to the shield&#8217;s TX pin to be transmitted via the MIDI OUT jack.</em></p>



<h2 class="wp-block-heading" id="user-content-the-code">The Code</h2>



<p>Here is a snippet showing the core logic of the full message handler, demonstrating how the note inversion, state management, and serial debugging are applied. This version leverages the robust hardware serial processing available on the Arduino MEGA 2560:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: cpp; title: ; notranslate">
void handleFullMessage() {
  uint8_t status = midiMsg&#x5B;0];
  uint8_t type = status &amp; 0xF0;
  uint8_t channel = status &amp; 0x0F;
  uint8_t data1 = midiMsg&#x5B;1];
  uint8_t data2 = midiMsg&#x5B;2];

  // Active Logic: Invert Note On/Off
  if (type == 0x90 || type == 0x80) {
    uint8_t originalNote = data1;
    uint8_t velocity = data2;

    // Inversion formula: 124 - original_note
    int invertedNote = INVERSION_OFFSET - (int)originalNote;
    if (invertedNote &lt; 0)
      invertedNote = 0;
    if (invertedNote &gt; 127)
      invertedNote = 127;

    if (type == 0x90 &amp;&amp; velocity &gt; 0) {
      // Note On
      activeNotes&#x5B;originalNote] = (int8_t)invertedNote;
      Serial2.write(0x90 | channel);
      Serial2.write((uint8_t)invertedNote);
      Serial2.write(velocity);

      Serial.print(&quot;Note On: &quot;);
      Serial.print(originalNote);
      Serial.print(&quot; -&gt; &quot;);
      Serial.println(invertedNote);
    } else {
      // Note Off (type 0x80 or 0x90 with velocity 0)
      int8_t noteToSend = activeNotes&#x5B;originalNote];
      if (noteToSend == -1) {
        // Failure case: received Note Off without Note On.
        // Just invert it and send anyway to be safe.
        noteToSend = (int8_t)invertedNote;
      }

      Serial2.write(type | channel);
      Serial2.write((uint8_t)noteToSend);
      Serial2.write(velocity);

      activeNotes&#x5B;originalNote] = -1;

      Serial.print(&quot;Note Off: &quot;);
      Serial.print(originalNote);
      Serial.print(&quot; -&gt; &quot;);
      Serial.println((uint8_t)noteToSend);
    }
  } else {
    // Pass through CC and other multi-byte messages
    for (int i = 0; i &lt; expectedBytes; i++) {
      Serial2.write(midiMsg&#x5B;i]);
    }
  }
}
</pre></div>


<h2 class="wp-block-heading" id="user-content-conclusion">Conclusion</h2>



<p>Building a custom hardware MIDI processor is deeply satisfying and opens up a ton of creative possibilities. By putting a simple Arduino between your keyboard and your synth, you can fundamentally alter how you interact with your instrument.</p>



<p>If you&#8217;re interested in giving this a try, grab an Arduino, a couple of MIDI jacks, and start flipping those notes! Happy playing!</p>



<div class="wp-block-uagb-image uagb-block-8b4f7b5c wp-block-uagb-image--layout-default wp-block-uagb-image--effect-static wp-block-uagb-image--align-none"><figure class="wp-block-uagb-image__figure"><img decoding="async" srcset="https://cerkit.com/wp-content/uploads/2026/03/left-handed-keyboard-stylized-diagram-1024x572.jpg ,https://cerkit.com/wp-content/uploads/2026/03/left-handed-keyboard-stylized-diagram-scaled.jpg 780w, https://cerkit.com/wp-content/uploads/2026/03/left-handed-keyboard-stylized-diagram-scaled.jpg 360w" sizes="auto, (max-width: 480px) 150px" src="https://cerkit.com/wp-content/uploads/2026/03/left-handed-keyboard-stylized-diagram-1024x572.jpg" alt="" class="uag-image-100582" width="2752" height="1536" title="left-handed-keyboard-stylized-diagram" loading="lazy" role="img"/></figure></div><p>The post <a href="https://cerkit.com/building-a-left-handed-piano-midi-man-in-the-middle-device/">Building a Left-Handed Piano MIDI “Man-in-the-Middle” Device</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://cerkit.com/building-a-left-handed-piano-midi-man-in-the-middle-device/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Reimagining the Keys: The Left-Handed Keyboard Inversion VST</title>
		<link>https://cerkit.com/reimagining-the-keys-the-left-handed-keyboard-inversion-vst/</link>
					<comments>https://cerkit.com/reimagining-the-keys-the-left-handed-keyboard-inversion-vst/#respond</comments>
		
		<dc:creator><![CDATA[cerkit]]></dc:creator>
		<pubDate>Sat, 14 Mar 2026 23:02:03 +0000</pubDate>
				<category><![CDATA[Music]]></category>
		<category><![CDATA[Programming]]></category>
		<guid isPermaLink="false">https://cerkit.com/?p=100569</guid>

					<description><![CDATA[<p>Discover how mirroring the piano keyboard around the axis of D can transform the playing experience for left-handed musicians. Learn the music theory and technical implementation behind this MIDI inversion VST designed for better ergonomics and creative flow.</p>
<p>The post <a href="https://cerkit.com/reimagining-the-keys-the-left-handed-keyboard-inversion-vst/">Reimagining the Keys: The Left-Handed Keyboard Inversion VST</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>As a left-handed musician, I’ve often felt the subtle &#8220;right-hand bias&#8221; built into the architecture of the piano. The traditional layout—where the right hand handles the high-flying melodies and the left hand provides the lower accompaniment—can sometimes feel at odds with the natural dexterity of a southpaw.</p>



<p>That’s why I developed the&nbsp;<strong>Left-Handed Keyboard Inversion VST</strong>. This MIDI effect plugin flips the script (and the scales) by mirroring the entire keyboard, allowing for a completely different ergonomic experience.</p>



<p>Check out a <a href="https://youtu.be/2JRnKirnmDc" target="_blank" rel="noopener" title="">short video</a> demonstrating the inverted keyboard.</p>



<h2 class="wp-block-heading" id="user-content-the-theory-mirroring-around-the-axis-of-d">The Theory: Mirroring Around the Axis of D</h2>



<p>The concept is based on the inherent symmetry of the piano keyboard. If you look at the pattern of black and white keys, the note&nbsp;<strong>D</strong>&nbsp;sits exactly in the center of the two black keys. This makes it a perfect axis of symmetry.</p>



<p>The plugin works by applying a simple but powerful mathematical formula to every MIDI note you play:</p>



<p><code>Inverted MIDI Note = 124 - Original MIDI Note</code></p>



<p>By using&nbsp;<strong>124</strong>&nbsp;as the sum, the pivot point becomes&nbsp;<strong>D4</strong>&nbsp;(MIDI note 62). When you play a D4, you get a D4. But as you move physically &#8220;up&#8221; the keyboard to the right, the pitches move &#8220;down&#8221; into the bass register.</p>



<h2 class="wp-block-heading" id="user-content-how-it-works-in-practice">How It Works in Practice</h2>



<p>When the plugin is active, your physical relationship with the instrument is reversed:</p>



<ul class="wp-block-list">
<li><strong>Left Hand (Physical Low End):</strong>&nbsp;Now plays the high-frequency melody notes.</li>



<li><strong>Right Hand (Physical High End):</strong>&nbsp;Now handles the low-frequency bass notes.</li>
</ul>



<p>This isn&#8217;t just a gimmick; it’s an ergonomic shift. It places the most complex, dexterous part of the performance—the melody—firmly in the hands of the most dexterous part of the musician: the left hand.</p>



<h2 class="wp-block-heading" id="user-content-technical-implementation">Technical Implementation</h2>



<p>The plugin is built using the&nbsp;<strong>JUCE framework</strong>&nbsp;and operates as a pure MIDI effect. It handles:</p>



<ul class="wp-block-list">
<li><strong>Real-time Note Translation:</strong>&nbsp;Every&nbsp;<code>Note On</code>&nbsp;and&nbsp;<code>Note Off</code>&nbsp;message is intercepted and recalculated instantly.</li>



<li><strong>State Tracking:</strong>&nbsp;It intelligently tracks active notes to ensure that if you release a key, the correct inverted &#8220;Note Off&#8221; is sent, preventing hanging notes even if you toggle the bypass mid-performance.</li>



<li><strong>Zero Latency:</strong>&nbsp;Because it’s a simple mathematical transformation of MIDI data, it introduces no perceptible delay to your playing.</li>
</ul>



<h2 class="wp-block-heading" id="user-content-why-invert">Why Invert?</h2>



<p>For many left-handed pianists, this creates a fascinating new way to interact with virtual instruments. It allows you to leverage your natural hand-dominance in a way that the standard piano layout doesn&#8217;t easily permit. Whether you&#8217;re looking for a fresh creative spark or a more natural flow for your lead lines, the keyboard inversion provides a literal new perspective on your music.</p><p>The post <a href="https://cerkit.com/reimagining-the-keys-the-left-handed-keyboard-inversion-vst/">Reimagining the Keys: The Left-Handed Keyboard Inversion VST</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://cerkit.com/reimagining-the-keys-the-left-handed-keyboard-inversion-vst/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>The Three Me&#8217;s &#8211; A Strategy for Making Good Decisions</title>
		<link>https://cerkit.com/the-three-mes-a-strategy-for-making-good-decisions/</link>
					<comments>https://cerkit.com/the-three-mes-a-strategy-for-making-good-decisions/#respond</comments>
		
		<dc:creator><![CDATA[cerkit]]></dc:creator>
		<pubDate>Wed, 04 Mar 2026 03:11:59 +0000</pubDate>
				<category><![CDATA[Philosophy]]></category>
		<category><![CDATA[avoiding regret]]></category>
		<category><![CDATA[decision making]]></category>
		<category><![CDATA[future self]]></category>
		<category><![CDATA[life strategies]]></category>
		<category><![CDATA[making good decisions]]></category>
		<category><![CDATA[mental models]]></category>
		<category><![CDATA[personal growth]]></category>
		<category><![CDATA[problem solving]]></category>
		<category><![CDATA[productivity hacks]]></category>
		<category><![CDATA[self improvement]]></category>
		<guid isPermaLink="false">https://cerkit.com/?p=100547</guid>

					<description><![CDATA[<p>The Three Me's" is a proven strategy for making good decisions. Explore this mental framework to navigate dilemmas, balance priorities, and build a better future. (163 characters)</p>
<p>The post <a href="https://cerkit.com/the-three-mes-a-strategy-for-making-good-decisions/">The Three Me’s – A Strategy for Making Good Decisions</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>When I&#8217;m faced with a decision, dilemma, or a situation that requires additional consideration, it is easy to get overwhelmed by the immediate pressure of choosing the &#8220;right&#8221; path. Over time, I&#8217;ve developed a mental framework to help navigate these choices: I like to think of myself as three different people. There is the Past Me, the Current Me, and the Future Me. By taking a step back and consulting all three of these perspectives, I can cut through the noise and gain a much clearer view of the best path forward.</p>



<p>First, I check in with the Current Me. This step is about defining the immediate reality of the situation and asking myself what I genuinely want the outcome to be right now. The Current Me represents my immediate desires, constraints, capabilities, and emotions. It is important to acknowledge what I want in the present moment, but it is equally important not to let this be the only voice in the room. The present is often heavily influenced by fleeting feelings or the appeal of short-term convenience, which is why the other two perspectives are so vital.</p>



<p>Next, I consult the Past Me. This version of myself holds the library of my experiences, successes, and, most importantly, my mistakes. I ask the Past Me what he would do based on everything he has learned so far. This is the voice of hard-earned wisdom. Has he been in a similar situation before? How did it play out? I treat this perspective as my personal advisor. If the Past Me gives a warning based on a previous misstep, I make sure to listen closely. Ignoring the lessons of the past is a quick way to repeat them.</p>



<p>Finally, I project forward and consider the Future Me. I think about what this older version of myself would think about the decision that I&#8217;m trying to make today. This step is entirely about long-term consequences. If the Future Me would look back and be angry, stressed, or disappointed with the Past Me (what is currently the Current Me), I take special care to re-evaluate my options and make the right decision. The ultimate goal is to set the Future Me up for success and peace of mind, rather than leaving him to clean up a mess.</p>



<p>Ultimately, this framework is about balancing the desires of the present with the lessons of the past and the hopes for the future. It forces you to step outside of the immediate pressure of a dilemma and view your choices across the continuous timeline of your life. This strategy has served me incredibly well, and by checking in with the Three Me&#8217;s, I find that I have fewer regrets as the years speed by.</p>



<figure class="wp-block-pullquote"><blockquote><p>The &#8216;Three Me’s&#8217; has helped me trade short-term impulses for long-term peace of mind. But I’m curious—how do <strong>you</strong> navigate tough choices? Do you have a mental framework or a &#8216;gut check&#8217; ritual that never lets you down? Drop your best decision-making hacks in the comments below; I’d love to learn from your experience!</p></blockquote></figure>



<p></p><p>The post <a href="https://cerkit.com/the-three-mes-a-strategy-for-making-good-decisions/">The Three Me’s – A Strategy for Making Good Decisions</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://cerkit.com/the-three-mes-a-strategy-for-making-good-decisions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Building a Dynamic Console UI with .NET 10, MQTT, and Node-RED – Part 7: Persisting the Pi Calculus Session State with a Minimal API and PostgreSQL</title>
		<link>https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-7-persisting-the-pi-calculus-session-state-with-a-minimal-api-and-postgresql/</link>
					<comments>https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-7-persisting-the-pi-calculus-session-state-with-a-minimal-api-and-postgresql/#respond</comments>
		
		<dc:creator><![CDATA[cerkit]]></dc:creator>
		<pubDate>Wed, 25 Feb 2026 10:14:47 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<guid isPermaLink="false">https://cerkit.com/?p=100502</guid>

					<description><![CDATA[<p>Discover how to add durable state persistence to a real-time Pi Calculus architecture. Learn to capture dynamic MQTT session data from Node-RED using a lightning-fast .NET 10 Minimal API and store complex JSON UI payloads natively in PostgreSQL using Entity Framework Core.</p>
<p>The post <a href="https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-7-persisting-the-pi-calculus-session-state-with-a-minimal-api-and-postgresql/">Building a Dynamic Console UI with .NET 10, MQTT, and Node-RED – Part 7: Persisting the Pi Calculus Session State with a Minimal API and PostgreSQL</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>In&nbsp;<a href="https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-6-securing-the-pi-calculus-ecosystem-with-mqtt-authentication/">Part 6</a>, we secured our Pi Calculus ecosystem. We stopped unauthorized clients in their tracks by implementing strict MQTT authentication across our Node-RED orchestration layer, our&nbsp;<code>pi-console</code>&nbsp;terminal client, and our&nbsp;<code>pi-wasm</code>&nbsp;Blazor browser application. With secure, credentialed access over WebSockets, our dynamic handshakes were finally safe.</p>



<p>But as our system matured, a new challenge emerged:&nbsp;<strong>State Persistence</strong>.</p>



<p>Our Node-RED flows were doing an excellent job of instantly spinning up dynamic, private MQTT channels (the&nbsp;<code>νz</code>&nbsp;in our Pi Calculus model) and feeding custom UI layouts to connected clients. However, this orchestration was entirely ephemeral. If Node-RED restarted, or if we needed to audit historical connection records, that session data was gone forever.</p>



<p>We needed a backend capable of durably recording every handshake and saving the exact layout payloads delivered to each device. To solve this, we added&nbsp;<strong><code>pi-functions</code></strong>: a .NET 10 Minimal API that acts as a serverless-style backend, writing session states directly to a PostgreSQL database.</p>



<h2 class="wp-block-heading" id="user-content-enter-pi-functions-and-minimal-apis">Enter&nbsp;<code>pi-functions</code>&nbsp;and Minimal APIs</h2>



<p>When clients connect and request a UI, Node-RED negotiates the private channel. We wanted to seamlessly map that negotiation into a database. Rather than building a bulky web application, .NET 10 Minimal APIs provided the perfect &#8220;serverless&#8221; development experience—lightweight, extremely fast, and highly focused.</p>



<p>We created a simple&nbsp;Program.cs&nbsp;that spins up an ASP.NET Core web application, registers Entity Framework Core for our data access layer, and exposes essential HTTP endpoints:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
// Function 1: Node-RED calls this to save a handshake state
app.MapPost(&quot;/api/state&quot;, async (SessionState state, PiCalculusDbContext db) =&gt;
{
    // 1. Check if this client already has a session in the DB
    var existingSession = await db.SessionStates
        .FirstOrDefaultAsync(s =&gt; s.ClientId == state.ClientId);

    if (existingSession is not null)
    {
        // 2. UPDATE: The client exists, just update their active properties
        existingSession.Status = state.Status;
        existingSession.ActiveChannel = state.ActiveChannel;
        existingSession.CurrentUiState = state.CurrentUiState;
        existingSession.LastUpdatedAt = DateTimeOffset.UtcNow;
    }
    else
    {
        // 3. INSERT: Brand new client
        db.SessionStates.Add(state);
    }

    // 4. Save changes
    await db.SaveChangesAsync();
    
    return Results.Ok(state);
});
</pre></div>


<p>This acts as a seamless webhook for Node-RED. During the MQTT handshake process, an HTTP Request node can silently POST the session details to&nbsp;<code>http://pi-functions:8080/api/state</code>.</p>



<h2 class="wp-block-heading" id="user-content-the-magic-of-postgresqls-jsonb">The Magic of PostgreSQL&#8217;s JSONB</h2>



<p>Our UI layouts and dynamic menus are JSON objects pushed from Node-RED down the MQTT pipe. Creating rigid relational database tables to represent the infinite possibilities of a dynamic UI would be tedious and fragile.</p>



<p>Instead, we utilized&nbsp;<strong>PostgreSQL 16</strong>. Postgres offers native support for the&nbsp;<code>JSONB</code>&nbsp;data type, which stores JSON data in a decomposed binary format. This makes it incredibly fast to process and query, without the overhead of parsing raw text on the fly.</p>



<p>By simply tagging our&nbsp;<code>CurrentUiState</code>&nbsp;string property using the Fluent API in Entity Framework Core, we mapped it directly to a native JSONB column:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    // Add an index to ClientId so your Node-RED lookups are blazing fast
    modelBuilder.Entity&lt;SessionState&gt;()
        .HasIndex(s =&gt; s.ClientId)
        .IsUnique();

    // Store the raw Node-RED UI payload natively.
    modelBuilder.Entity&lt;SessionState&gt;()
        .Property(b =&gt; b.CurrentUiState)
        .HasColumnType(&quot;jsonb&quot;);
}
</pre></div>


<p>Now, the entire&nbsp;<code>pi-console</code>&nbsp;or&nbsp;<code>pi-wasm</code>&nbsp;configuration schema is durably saved exactly as it was requested, ready to be analyzed or restored at a moment&#8217;s notice.</p>



<h2 class="wp-block-heading" id="user-content-orchestrating-the-ecosystem-with-podman">Orchestrating the Ecosystem with Podman</h2>



<p>With a new API and a database to manage, our local developer environment grew from two containers to four. Here&#8217;s a sample of what that Podman Compose architecture looks like (with credentials replaced by placeholders):</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
  postgres:
    image: postgres:16
    container_name: postgres
    restart: unless-stopped
    ports:
      - &quot;5432:5432&quot;
    environment:
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=&lt;YOUR_STRONG_PASSWORD&gt;
      - POSTGRES_DB=PiCalculusDb
    volumes:
      - postgres_data:/var/lib/postgresql/data

  pi-functions:
    build: 
      context: ../Development/pi-console
      dockerfile: pi-functions/Dockerfile
    container_name: pi-functions
    restart: unless-stopped
    ports:
      - &quot;5001:8080&quot;
    environment:
      - ConnectionStrings__DefaultConnection=Host=postgres;Database=PiCalculusDb;Username=admin;Password=&lt;YOUR_STRONG_PASSWORD&gt;
    depends_on:
      - postgres
</pre></div>


<p>Podman Compose easily links the&nbsp;<code>pi-functions</code>&nbsp;bridge directly to the Postgres host&nbsp;<code>postgres:5432</code>. It builds the container straight from our C# workspace using a multi-stage&nbsp;Dockerfile.</p>



<h2 class="wp-block-heading" id="user-content-the-evolution-of-the-pi-calculus-ecosystem">The Evolution of the Pi Calculus Ecosystem</h2>



<p>What started as a fun terminal experiment using&nbsp;<code>Spectre.Console</code>&nbsp;has bloomed into a highly decoupled, real-time distributed application framework.</p>



<p>By treating our UI configurations as data that moves over dynamic channels (the Pi Calculus model), we completely abstracted the concept of a graphical layout away from the client application. Now, with the addition of&nbsp;<code>pi-functions</code>&nbsp;and PostgreSQL, that ephemeral real-time orchestration is given memory and historical context.</p>



<p>Whether we are connecting through an SSH terminal in&nbsp;<code>pi-console</code>, or booting up the WebAssembly clone in&nbsp;<code>pi-wasm</code>, our central backend immediately authenticates the client, provisions a secure dynamic channel, saves the current UI layout state in Postgres via our minimal API, and paints the screen with the exact commands needed.</p>



<p>The ecosystem is robust, secure, and fully stateful. Next time, we&#8217;ll dive into building out custom sensor modules and watching the telemetry flow back upstream!</p><p>The post <a href="https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-7-persisting-the-pi-calculus-session-state-with-a-minimal-api-and-postgresql/">Building a Dynamic Console UI with .NET 10, MQTT, and Node-RED – Part 7: Persisting the Pi Calculus Session State with a Minimal API and PostgreSQL</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-7-persisting-the-pi-calculus-session-state-with-a-minimal-api-and-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Building a Dynamic Console UI with .NET 10, MQTT, and Node-RED – Part 6: Securing the Pi Calculus Ecosystem with MQTT Authentication</title>
		<link>https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-6-securing-the-pi-calculus-ecosystem-with-mqtt-authentication/</link>
					<comments>https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-6-securing-the-pi-calculus-ecosystem-with-mqtt-authentication/#comments</comments>
		
		<dc:creator><![CDATA[cerkit]]></dc:creator>
		<pubDate>Mon, 23 Feb 2026 16:23:25 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<guid isPermaLink="false">https://cerkit.com/?p=100494</guid>

					<description><![CDATA[<p>Secure your Pi Calculus ecosystem! Learn how to lock down a Mosquitto broker and implement authenticated MQTT connections using secrets.json for a .NET 10 console UI and appsettings.json for a Blazor WebAssembly client.</p>
<p>The post <a href="https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-6-securing-the-pi-calculus-ecosystem-with-mqtt-authentication/">Building a Dynamic Console UI with .NET 10, MQTT, and Node-RED – Part 6: Securing the Pi Calculus Ecosystem with MQTT Authentication</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>In <a href="https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-5-blazor-webassembly-shared-libraries-and-targeted-handshakes/">Part 5</a>, we evolved our dynamic console application into a versatile multi-client ecosystem. We seamlessly brought our &#8220;Qubit BBS&#8221; dashboard experience from the terminal to the browser using a new .NET 10 Blazor WebAssembly client (<code>pi-wasm</code>). We decoupled our core orchestration logic into the <code>Pi.Shared</code> library and routed customized UI configurations via Targeted Handshakes over MQTT WebSockets.</p>



<p>But as our Pi Calculus ecosystem grew, we realized a critical flaw:&nbsp;<strong>Our MQTT connections were entirely unauthenticated.</strong></p>



<p>Anyone with a basic MQTT client could theoretically connect to our broker, intercept the dynamic Handshake topics, or inject unauthorized layout JSON into our applications. It was time to lock down our Node-RED backend and secure our .NET clients.</p>



<h2 class="wp-block-heading" id="user-content-mosquitto-password-protection">Mosquitto Password Protection</h2>



<p>The first step was securing our Mosquitto MQTT broker. By default, Mosquitto allows anonymous connections. We updated our&nbsp;<code>mosquitto.conf</code>&nbsp;file to disable anonymous mode and point to a generated password file:</p>



<pre class="wp-block-code"><code># Global Authentication Settings
allow_anonymous false
password_file /mosquitto/config/passwd</code></pre>



<p>Next, we used the&nbsp;<code>mosquitto_passwd</code>&nbsp;utility to generate a secure user (<code>pi_user</code>) and a corresponding encrypted password hash. A quick restart of the Docker container, and our broker was officially sealed.</p>



<p>Of course, the immediate side-effect was that our poor&nbsp;<code>pi-console</code>&nbsp;and&nbsp;<code>pi-wasm</code>&nbsp;clients started throwing&nbsp;<code>NotAuthorized</code>&nbsp;exceptions! The brokers wouldn&#8217;t let them in to perform their Pi Calculus orchestration. We needed to teach our shared&nbsp;<code>.NET 10</code>&nbsp;library how to handle credentials.</p>



<h2 class="wp-block-heading" id="user-content-teaching-pishared-to-authenticate">Teaching Pi.Shared to Authenticate</h2>



<p>Inside our&nbsp;<code>Pi.Shared</code>&nbsp;library, we updated our central orchestration engine, the&nbsp;MqttService. We added new&nbsp;<code>Username</code>&nbsp;and&nbsp;<code>Password</code>&nbsp;properties to the class, and updated our runtime connection block to append&nbsp;<code>.WithCredentials()</code>&nbsp;to the&nbsp;<code>MqttClientOptionsBuilder</code>:</p>



<pre class="wp-block-code"><code>if (!string.IsNullOrEmpty(Username) &amp;&amp; !string.IsNullOrEmpty(Password))
{
    mqttClientOptionsBuilder = mqttClientOptionsBuilder.WithCredentials(Username, Password);
}</code></pre>



<p>Now, the service was capable of authenticating. But&nbsp;<em>where</em>&nbsp;would it get those credentials? Hardcoding passwords directly into our&nbsp;Program.cs&nbsp;files or the shared library is a monumental security anti-pattern, especially since we track this project in a public GitHub repository.</p>



<p>We needed a strategy to load settings at runtime without ever letting Git see them. Because our clients run in two entirely different environments (a local terminal vs. a browser sandbox), we had to implement two distinct credential delivery mechanisms.</p>



<h2 class="wp-block-heading" id="user-content-securing-the-terminal-secretsjson">Securing the Terminal:&nbsp;secrets.json</h2>



<p>For the native macOS terminal application (<code>pi-console</code>), we have full access to the local machine&#8217;s file system. We opted for a&nbsp;secrets.json&nbsp;file.</p>



<p>We created a&nbsp;<code>.secrets</code>&nbsp;folder at the root of our local workspace and added a&nbsp;secrets.json&nbsp;file containing our credentials:</p>



<pre class="wp-block-code"><code>{
  "MqttIpAddress": "localhost",
  "MqttPort": 9001,
  "Username": "pi_user",
  "Password": "super_secret_password"
}</code></pre>



<p>Since the&nbsp;secrets.json&nbsp;file sits at the root of the solution, but the&nbsp;<code>dotnet run</code>&nbsp;execution happens deep inside the&nbsp;<code>bin/Debug/net10.0/</code>&nbsp;folder, our&nbsp;<code>MqttService</code>&nbsp;needed to be smart enough to find it. We implemented a directory traversal loop to search upwards from the&nbsp;<code>AppContext.BaseDirectory</code>&nbsp;until it located the&nbsp;<code>.secrets</code>&nbsp;directory. Once found, it parses the JSON and populates the&nbsp;<code>Username</code>&nbsp;and&nbsp;<code>Password</code>&nbsp;fields.</p>



<p>Most importantly, we added&nbsp;<code>.secrets/</code>&nbsp;to our&nbsp;<code>.gitignore</code>&nbsp;file, ensuring our Mosquitto passwords never accidentally get pushed to GitHub.</p>



<h2 class="wp-block-heading" id="user-content-securing-the-browser-appsettingsjson">Securing the Browser:&nbsp;<code>appsettings.json</code></h2>



<p>Our Blazor WebAssembly client (<code>pi-wasm</code>) presented a completely different challenge. Since it runs inside the strict sandbox of your web browser, it has absolutely zero access to your local machine&#8217;s file system. It can&#8217;t directory-traverse its way to&nbsp;<code>secrets.json</code>.</p>



<p>Instead, Blazor WASM applications rely on configuration files served by the hosting web server. For&nbsp;<code>pi-wasm</code>, we created an&nbsp;<code>appsettings.json</code>&nbsp;file directly inside the&nbsp;<code>wwwroot</code>&nbsp;folder—the static web directory that gets bundled and sent to the browser:</p>



<pre class="wp-block-code"><code>{
    "Mqtt": {
    "Username": "pi_user",
    "Password": "super_secret_password"
    }
}</code></pre>



<p>In <code>pi-wasm/Program.cs</code>, we tell our Dependency Injection container to pull the credentials directly from Blazor&#8217;s configuration builder:</p>



<pre class="wp-block-code"><code>service.Username = builder.Configuration&#91;"Mqtt:Username"];
service.Password = builder.Configuration&#91;"Mqtt:Password"];</code></pre>



<p>When the user launches the website, the browser downloads&nbsp;<code>appsettings.json</code>, parses out the MQTT variables, injects them into the&nbsp;<code>MqttService</code>, and establishes an authenticated WebSocket connection.</p>



<p>Just like with the console app, we immediately added&nbsp;<code>pi-wasm/wwwroot/appsettings.json</code>&nbsp;to our&nbsp;<code>.gitignore</code>&nbsp;to protect the file from entering source control.</p>



<h2 class="wp-block-heading" id="user-content-connected-and-secured">Connected and Secured</h2>



<p>With both clients updated, our Pi Calculus orchestration system is fully authenticated. Node-RED requires a password to serve handshakes, and our&nbsp;<code>.NET 10</code>&nbsp;UI clients dynamically inject their credentials based on their execution environment.</p>



<p>Whether you&#8217;re hitting the Qubit BBS from your local terminal or browsing it over WebSockets, the dynamic UI remains snappy, responsive, and—finally—secure.</p>



<p>Stay tuned as we continue expanding our MQTT UI orchestrator. You can follow along by cloning the&nbsp;<a href="https://github.com/cerkit/pi-console">pi-console Repo on GitHub</a>!</p><p>The post <a href="https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-6-securing-the-pi-calculus-ecosystem-with-mqtt-authentication/">Building a Dynamic Console UI with .NET 10, MQTT, and Node-RED – Part 6: Securing the Pi Calculus Ecosystem with MQTT Authentication</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-6-securing-the-pi-calculus-ecosystem-with-mqtt-authentication/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Building a Dynamic Console UI with .NET 10, MQTT, and Node-RED – Part 5: Blazor WebAssembly, Shared Libraries, and Targeted Handshakes</title>
		<link>https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-5-blazor-webassembly-shared-libraries-and-targeted-handshakes/</link>
					<comments>https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-5-blazor-webassembly-shared-libraries-and-targeted-handshakes/#comments</comments>
		
		<dc:creator><![CDATA[cerkit]]></dc:creator>
		<pubDate>Sun, 22 Feb 2026 17:18:30 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<guid isPermaLink="false">https://cerkit.com/?p=100487</guid>

					<description><![CDATA[<p>Expand your .NET 10 dynamic console UI to the web! Learn to build a Blazor WebAssembly client, use MQTT WebSockets, and route targeted Node-RED handshakes.</p>
<p>The post <a href="https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-5-blazor-webassembly-shared-libraries-and-targeted-handshakes/">Building a Dynamic Console UI with .NET 10, MQTT, and Node-RED – Part 5: Blazor WebAssembly, Shared Libraries, and Targeted Handshakes</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>In&nbsp;<a href="https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-4-dynamic-menu-actions-and-thread-safe-ui-updates/">Part 4</a>, we bridged the gap between our dynamic UI rendering loop and our headless Node-RED backend by implementing isolated&nbsp;<code>ActionTopics</code>, targeted&nbsp;<code>PanelUpdates</code>, and a dedicated execution processor. Our terminal console dashboard was finally fully interactive, firing off backend commands without breaking the beautiful, live Spectre.Console UI.</p>



<p>But what if we aren&#8217;t at our terminal? What if we want that exact same &#8220;Qubit BBS&#8221; dashboard experience—dynamically orchestrated via Pi Calculus—but available in a web browser from anywhere?</p>



<p>In this fifth installment, we evolved our single-client console application into a versatile multi-client ecosystem. We decoupled our core logic into a reusable&nbsp;<code>Pi.Shared</code>&nbsp;library, launched a brand-new .NET 10 Blazor WebAssembly client (<code>pi-wasm</code>), and implemented &#8220;Targeted Handshakes&#8221; to route custom UI configs to multiple active clients simultaneously.</p>



<h2 class="wp-block-heading" id="user-content-the-pishared-core-library">The&nbsp;<code>Pi.Shared</code>&nbsp;Core Library</h2>



<p>To support multiple UI frontends without duplicating our complex orchestration and MQTT logic, the first step was a major architectural refactor.</p>



<p>We created a new .NET 10 Class Library called&nbsp;<code>Pi.Shared</code>. Into this library, we migrated:</p>



<ul class="wp-block-list">
<li>The core data <code>Models</code> (<code>MenuItem</code>, <code>UiConfigData</code>, etc.)</li>



<li>The MqttService responsible for backend communication</li>



<li>The DynamicUiOrchestratorService responsible for handling the Pi Calculus handshakes and session states</li>
</ul>



<p>To decouple the orchestrator from&nbsp;<code>Spectre.Console</code>&nbsp;directly, we introduced an&nbsp;</p>



<p>IUiService&nbsp;interface containing abstractions like&nbsp;UpdatePanel&nbsp;and&nbsp;UpdateMenu. Now, our backend logic simply calls&nbsp;<code>_uiService.UpdatePanel()</code>, completely agnostic to whether those pixels are rendering in a native terminal or a web browser.</p>



<h2 class="wp-block-heading" id="user-content-enter-pi-wasm-the-blazor-webassembly-client">Enter&nbsp;<code>pi-wasm</code>: The Blazor WebAssembly Client</h2>



<p>With our core engine safely abstracted, we generated a new&nbsp;<code>.NET 10 Blazor WebAssembly</code>&nbsp;standalone project:&nbsp;<code>pi-wasm</code>.</p>



<p>Implementing the new client was incredibly straightforward. We simply added a project reference to&nbsp;<code>Pi.Shared</code>&nbsp;and implemented&nbsp;</p>



<p>IUiService&nbsp;inside a new&nbsp;BlazorUiService&nbsp;class.</p>



<p>Instead of writing out CLI blocks, the Blazor UI binds strongly-typed component state directly to CSS Grid elements, maintaining the exact visual layout of our original &#8220;Qubit BBS&#8221; mockup (Header, Operations Menu, Output, and Status panels).</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="867" src="https://cerkit.com/wp-content/uploads/2026/02/image-3-1024x867.png" alt="UI Display for the pi-wasm Blazor interface" class="wp-image-100491" style="aspect-ratio:1.1811231215396782;width:665px;height:auto" srcset="https://cerkit.com/wp-content/uploads/2026/02/image-3-1024x867.png 1024w, https://cerkit.com/wp-content/uploads/2026/02/image-3-300x254.png 300w, https://cerkit.com/wp-content/uploads/2026/02/image-3-768x650.png 768w, https://cerkit.com/wp-content/uploads/2026/02/image-3.png 1122w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading" id="user-content-translating-spectreconsole-to-html">Translating Spectre.Console to HTML</h3>



<p>One unique challenge: Our Node-RED responses and orchestrator states were heavily utilizing&nbsp;<code>Spectre.Console</code>&nbsp;markup tags like&nbsp;<code>[green]ONLINE[/]</code>&nbsp;to inject colors into the text stream.</p>



<p>To keep the backend blissfully unaware of the frontend rendering engine, we built a lightweight regex-based&nbsp;</p>



<p>SpectreConsoleParser&nbsp;in the WASM app. It seamlessly intercepts incoming Spectre tags and translates them into HTML&nbsp;<code>&lt;span&gt;</code>&nbsp;elements with inline CSS coloring. The parsed string is then injected into the UI via Blazor&#8217;s&nbsp;<code>MarkupString</code>, giving our browser UI the exact same color profiles as the CLI.</p>



<h2 class="wp-block-heading" id="user-content-bypassing-tcp-ghosts-with-websockets">Bypassing TCP Ghosts with WebSockets</h2>



<p>During testing, we encountered the &#8220;Tale of Two Brokers&#8221;. Our browser-based <code>pi-wasm</code> client securely connected to the Docker Mosquitto instance via WebSockets (<code>localhost:9001</code>) and instantly fetched the default menus. However, our native Mac <code>pi-console</code> application was randomly dropping packets over the standard TCP port (<code>1883</code>).</p>



<p>It turned out the host OS had a standalone TCP broker intercepting traffic, preventing packets from crossing the Docker network bridge!</p>



<p>The solution? We standardized&nbsp;<em>both</em>&nbsp;clients to connect explicitly over&nbsp;<strong>MQTT via WebSockets</strong>&nbsp;to bypass the native network conflicts.</p>



<h2 class="wp-block-heading" id="user-content-targeted-handshakes">Targeted Handshakes</h2>



<p>With both&nbsp;<code>pi-console</code>&nbsp;AND&nbsp;<code>pi-wasm</code>&nbsp;successfully connected to the same Node-RED backend simultaneously, we faced our final Pi Calculus challenge: How do we send a&nbsp;<em>different</em>&nbsp;UI configuration to the Console app vs the Browser app?</p>



<p>We achieved this by implementing&nbsp;<strong>Targeted Handshakes</strong>.</p>



<ol class="wp-block-list">
<li>During MqttService initialization in Program.cs, each client is statically assigned a unique <code>ClientId</code> (e.g., <code>"pi-console"</code> or <code>"pi-wasm"</code>).</li>



<li>When announcing their presence on startup (<code>pi-console/client/startup</code>), clients now include their ID in the JSON payload: <code>{"clientId": "pi-console"}</code>.</li>



<li>Node-RED parses this ID and dynamically routes the ensuing Pi Calculus handshake to a specific targeted listener: <code>pi-console/handshake/{clientId}</code>.</li>



<li>Node-RED then looks up the <code>clientId</code> against a dictionary in pi-console-configs.json and pushes the tailored UI layout parameters and menu options down the secure session channel.</li>
</ol>



<p>Thanks to this targeted routing, our Node-RED backend can serve entirely different, customized dashboards to different clients—all utilizing the exact same shared&nbsp;<code>.NET 10</code>&nbsp;orchestration engine!</p>



<p>Stay tuned as we continue to push the limits of dynamic MQTT UI orchestration!</p>



<p>Clone the <code><a href="https://github.com/cerkit/pi-console" target="_blank" rel="noopener" title="">pi-console</a></code> Repo on GitHub and develop your own UI orchestrations (Node-RED flows in the Architecture file in the repo)</p><p>The post <a href="https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-5-blazor-webassembly-shared-libraries-and-targeted-handshakes/">Building a Dynamic Console UI with .NET 10, MQTT, and Node-RED – Part 5: Blazor WebAssembly, Shared Libraries, and Targeted Handshakes</a> first appeared on <a href="https://cerkit.com">cerkit.com</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://cerkit.com/building-a-dynamic-console-ui-with-net-10-mqtt-and-node-red-part-5-blazor-webassembly-shared-libraries-and-targeted-handshakes/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
