<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Friz64</title>
      <link>https://blog.friz64.de</link>
      <description>personal blog</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://blog.friz64.de/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Tue, 06 Jan 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>the Karlsruhe straight line challenge</title>
          <pubDate>Tue, 06 Jan 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.friz64.de/karlsruhe-straight-line-challenge/</link>
          <guid>https://blog.friz64.de/karlsruhe-straight-line-challenge/</guid>
          <description xml:base="https://blog.friz64.de/karlsruhe-straight-line-challenge/">&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Karlsruhe&quot;&gt;Karlsruhe&lt;&#x2F;a&gt; straight line challenge is carried out by
cycling as close as possible along one of the city&#x27;s radiating lines, starting from the palace
tower, until reaching a natural end.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;The city [Karlsruhe] was planned with the palace tower (&lt;em&gt;Schloss&lt;&#x2F;em&gt;) at the center and 32 streets
radiating out from it like the spokes of a wheel, or the ribs of a folding fan, so that one
nickname for Karlsruhe in German is the &quot;fan city&quot; (&lt;em&gt;Fächerstadt&lt;&#x2F;em&gt;).&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;What mainly differentiates this from the &quot;straight line missions&quot; you might have heard of,
is that you don&#x27;t get to choose where the line runs! This challenge is entirely within the spirit of
spontanous &quot;go for it and see what happens&quot; adventures (which is one area I personally get my kicks
from). This means that there is effectively zero planning, and you just get on a bike and go...
along the closest street&#x2F;path&#x2F;surface you deem suitable. How many meters you deviate isn&#x27;t &lt;em&gt;really&lt;&#x2F;em&gt;
what&#x27;s relevant.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a simple map of all available lines:&lt;&#x2F;p&gt;
&lt;script src=&quot;leaflet.js&quot;&gt;&lt;&#x2F;script&gt;
&lt;link href=&quot;leaflet.css&quot; rel=&quot;stylesheet&quot; &#x2F;&gt;
&lt;div id=&quot;map&quot; style=&quot;height: 600px&quot;&gt;&lt;&#x2F;div&gt;
&lt;script&gt;
    const center = [8.4044296, 49.0139710];
    
    const map = L.map(&#x27;map&#x27;).setView([center[1], center[0]], 12);

    L.tileLayer(&#x27;https:&#x2F;&#x2F;tile.openstreetmap.org&#x2F;{z}&#x2F;{x}&#x2F;{y}.png&#x27;, {
        attribution: &#x27;&amp;copy; &lt;a href=&quot;https:&#x2F;&#x2F;www.openstreetmap.org&#x2F;copyright&quot;&gt;OpenStreetMap&lt;&#x2F;a&gt; contributors&#x27;
    }).addTo(map);

    const features = [];

    const tau = 2 * Math.PI;
    const offset = -0.06806784;
    for (let angle = offset; angle &lt; tau + offset; angle += tau &#x2F; 32) {
        features.push({
            &#x27;type&#x27;: &#x27;Feature&#x27;,
            &#x27;geometry&#x27;: {
                &#x27;type&#x27;: &#x27;LineString&#x27;,
                &#x27;coordinates&#x27;: [center, [
                    center[0] + Math.cos(angle),
                    center[1] + Math.sin(angle) * 0.65587497, &#x2F;&#x2F; dw abt it ❤️
                ]]
            }
        });
    }

    L.geoJSON(
        { &#x27;type&#x27;: &#x27;FeatureCollection&#x27;, &#x27;features&#x27;: features },
        { style: { color: &#x27;#f00&#x27;, weight: 2 } }
    ).addTo(map);
&lt;&#x2F;script&gt;
&lt;p&gt;I recommend the Organic Maps app for your phone, which is the perfect tool for guiding you. If
you&#x27;re fancy you can even trace out a rough route beforehand, and then load it into the app.
I&#x27;ve found it to be a bit annoying having to constantly stop to &quot;live-plan&quot; the route.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;my-personal-adventures-contains-spoilers&quot;&gt;My personal adventures (contains spoilers!)&lt;a class=&quot;zola-anchor&quot; href=&quot;#my-personal-adventures-contains-spoilers&quot; aria-label=&quot;Anchor link for: my-personal-adventures-contains-spoilers&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I hope to update this list as I continue doing these. Because I still don&#x27;t own a bike, and I&#x27;m a
bit crazy, I did all of these using bike sharing bikes from KVV.nextbike (I&#x27;m sorry for getting them
dirty!). Yet this does have the nice side effect that I can just leave the bike in a participating
city, and ride the train home without it!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-northern-via-triumphalis-line&quot;&gt;The northern &lt;a href=&quot;https:&#x2F;&#x2F;de.wikipedia.org&#x2F;wiki&#x2F;Via_Triumphalis_(Karlsruhe)&quot;&gt;&quot;Via Triumphalis&quot;&lt;&#x2F;a&gt; line&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-northern-via-triumphalis-line&quot; aria-label=&quot;Anchor link for: the-northern-via-triumphalis-line&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Attempted 2025-06-28. This one starts out very easy, as you follow along a purpose-built bike
path for the first ~9km. You&#x27;re conveniently spit out at the B36&#x2F;L559 intersection near
Leopoldshafen, after which you&#x27;re on your own. Trekking through various bicycle-unfriendly fields,
the line will perfectly lead you over a railroad-crossing of the AVG branch line to KIT Campus Nord.
The remainder of the route will have you navigating various towns, farmland, as well as a cut-off
meander of the river Rhine. There&#x27;s some pretty ugly offroad sections. The &quot;natural end&quot; here is a a
nice camping&#x2F;lookout spot at the Rhine.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;karlsruhe-straight-line-challenge&#x2F;northern1.jpg&quot; alt=&quot;the AVG crossing&quot; &#x2F;&gt;&lt;&#x2F;th&gt;&lt;th&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;karlsruhe-straight-line-challenge&#x2F;northern2.jpg&quot; alt=&quot;nextbike at night&quot; &#x2F;&gt;&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;the-waldstrasse-line&quot;&gt;The &quot;Waldstraße&quot; line&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-waldstrasse-line&quot; aria-label=&quot;Anchor link for: the-waldstrasse-line&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Attempted 2025-09-06. This has you leave Karlsruhe through ZKM&#x2F;HfG, following Tram 4 to Oberreut,
after which you will encounter a glider airstrip, and the Epplesee lake. Durmersheim will have you
go right through a schoolyard, and lead you right into a fair bit of forestry. After Steinmauern
there&#x27;s a pretty well placed bridge over the Murg river, followed by a bunch of more countryside.
If you spot an old boat floating in the water (the &quot;Aalschokker Heini&quot;), you&#x27;ve also pretty much
reached the old Rhine bridge near Wintersdorf. That bridge is placed dead-center on the line, which
is an invitation to keep going to France, but I decided to call quits here.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;karlsruhe-straight-line-challenge&#x2F;waldstrasse.png&quot; alt=&quot;map showing GPS recording&quot; title=&quot;actual GPS recording&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;karlsruhe-straight-line-challenge&#x2F;waldstrasse1.jpg&quot; alt=&quot;nextbike on schoolyard&quot; &#x2F;&gt;&lt;&#x2F;th&gt;&lt;th&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;karlsruhe-straight-line-challenge&#x2F;waldstrasse2.jpg&quot; alt=&quot;the &amp;quot;Aalschokker Heini&amp;quot;&quot; &#x2F;&gt;&lt;&#x2F;th&gt;&lt;th&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;karlsruhe-straight-line-challenge&#x2F;waldstrasse3.jpg&quot; alt=&quot;Wintersdorf bridge&quot; &#x2F;&gt;&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h2 id=&quot;final-words&quot;&gt;Final words&lt;a class=&quot;zola-anchor&quot; href=&quot;#final-words&quot; aria-label=&quot;Anchor link for: final-words&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;If you&#x27;re mad enough to try one of these on your own, please don&#x27;t hesitate to tell me about it!!!
&lt;a href=&quot;https:&#x2F;&#x2F;friz64.de&quot;&gt;friz64.de&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>my ergonomic keyboard adventure - ergodox ez</title>
          <pubDate>Sun, 09 May 2021 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.friz64.de/ergodox-rawhid/</link>
          <guid>https://blog.friz64.de/ergodox-rawhid/</guid>
          <description xml:base="https://blog.friz64.de/ergodox-rawhid/">&lt;p&gt;After searching for a new keyboard for some while, the
&lt;a href=&quot;https:&#x2F;&#x2F;ergodox-ez.com&#x2F;&quot;&gt;ErgoDox EZ&lt;&#x2F;a&gt; caught my attention. It is an open-source,
ergonomic, split, mechanical keyboard.&lt;&#x2F;p&gt;
&lt;p&gt;We&#x27;ll be looking at the hardware itself, getting to know the ErgoDox, setting
up a custom firmware, communication between my PC and the EZ, the program
controlling lighting (including an &lt;strong&gt;live audio spectrum analyzer&lt;&#x2F;strong&gt;) and
displaying the current words per minute on the screen.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-hardware&quot;&gt;The hardware&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-hardware&quot; aria-label=&quot;Anchor link for: the-hardware&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The website configurator gives you quite a few options to personalize various
features of your keyboard. I went with the white version, a tilt kit (which lets
you adjust the angle of your keyboard), white wrist rests,
RGB underglow (called &quot;Shine&quot;), Cherry MX Brown switches and blank keycaps.&lt;&#x2F;p&gt;
&lt;p&gt;The wait began on Monday, April 5th, 2021 when I ordered the keyboard. After
which it took just over 2 weeks to get the shipping notification email in my
inbox on Tuesday, April 20th. While excitedly refreshing the UPS tracking
page more often than was necessary, I got the keyboard in the mail on
Thursday, April 22nd.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;small-gallery&quot;&gt;Small gallery&lt;a class=&quot;zola-anchor&quot; href=&quot;#small-gallery&quot; aria-label=&quot;Anchor link for: small-gallery&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;ergodox-rawhid&#x2F;desk.jpg&quot; alt=&quot;My desk setup&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I found the gap in between the two keyboard halves to be an excellent place for
the notebook you&#x27;re scribbling in.&lt;&#x2F;p&gt;
&lt;p&gt;In the picture on the right, I used the wrong side of the puller. Thanks
&lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;ergodox&#x2F;comments&#x2F;n8f4hq&#x2F;my_ergonomic_keyboard_adventure_ergodox_ez&#x2F;gxi7wcx&quot;&gt;u&#x2F;GnastyNoodlez&lt;&#x2F;a&gt;
for pointing that out. It&#x27;s staying in the post for fun!&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;ergodox-rawhid&#x2F;packaging.jpg&quot; alt=&quot;The packaging it came in&quot; &#x2F;&gt;&lt;&#x2F;th&gt;&lt;th&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;ergodox-rawhid&#x2F;key.jpg&quot; alt=&quot;A pulled keycap and the MX Brown switch&quot; &#x2F;&gt;&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;And here&#x27;s the LED strip on the back of one side. It will see heavy use later.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;ergodox-rawhid&#x2F;led_strip.jpg&quot; alt=&quot;Back of the left side, showing 15 LEDs&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Counting... 15 LEDs, so in total there are 30.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;learning-to-type-again&quot;&gt;Learning to type again&lt;a class=&quot;zola-anchor&quot; href=&quot;#learning-to-type-again&quot; aria-label=&quot;Anchor link for: learning-to-type-again&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;There is probably no better way of showcasing my progress than the WPM graph on
&lt;a href=&quot;https:&#x2F;&#x2F;monkeytype.com&#x2F;&quot;&gt;monkeytype.com&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;ergodox-rawhid&#x2F;wpm_graph.jpg&quot; alt=&quot;WPM graph, showing a reset followed by a steady climb&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Two days after I started using the keyboard, I woke up with the
magic ability to type comfortably. This period is represented by the
&quot;&lt;em&gt;valley&lt;&#x2F;em&gt;&quot; in this graph.&lt;&#x2F;p&gt;
&lt;p&gt;On a regular keyboard, every row of keys is offset from another. This is what&#x27;s
known as &lt;strong&gt;row-stagger&lt;&#x2F;strong&gt; and was carried over from the needs of typewriters.
The ErgoDox, on the other hand, is &lt;strong&gt;column-staggered&lt;&#x2F;strong&gt;, which adjusts each
&lt;em&gt;column&lt;&#x2F;em&gt; of keys for the different lengths of your individual fingers,
improving ergonomics.&lt;&#x2F;p&gt;
&lt;p&gt;When typing characters itself, which is what was measured on monkeytype, this
was the biggest hurdle. But what about special characters? Those are placed on
an extra layer (which we&#x27;ll get to now). For these it took around 2 weeks
for me to know off the top of my head and somewhat comfortably use.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-layout&quot;&gt;The layout&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-layout&quot; aria-label=&quot;Anchor link for: the-layout&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;ZSA (the company I bought my ErgoDox from) provides an online configurator called
Oryx. Using this tool I went through &lt;strong&gt;28&lt;&#x2F;strong&gt; iterations before ending up on the
perfect layout (until now). For the characters themselves, I use
&lt;a href=&quot;https:&#x2F;&#x2F;colemak.com&#x2F;&quot;&gt;Colemak&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Learning Colemak is a one-time investment that will allow you to enjoy faster
and pain-free typing for the rest of your life.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Note that this image still shows QWERTY as I apply it through software.
This helps with game support.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;ergodox-rawhid&#x2F;oryx.jpg&quot; alt=&quot;Base layer, with normal keys&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In total, the keyboard features 76 keys. This is not enough for everything you&#x27;d
want to do (e.g. FN keys). That&#x27;s the problem &lt;em&gt;layers&lt;&#x2F;em&gt; are here to solve. There
are numerous possible ways of switching between them, but I ended up
activating a certain layer while a specific key is held down (as marked in the
above image). You can take a closer look at my setup yourself in
&lt;a href=&quot;https:&#x2F;&#x2F;configure.zsa.io&#x2F;ergodox-ez&#x2F;layouts&#x2F;VYdw7&#x2F;latest&#x2F;0&quot;&gt;the configurator&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;compiling-my-own-firmware&quot;&gt;Compiling my own firmware&lt;a class=&quot;zola-anchor&quot; href=&quot;#compiling-my-own-firmware&quot; aria-label=&quot;Anchor link for: compiling-my-own-firmware&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Under the hood, Oryx generates and compiles C code, which you then flash
onto the microcontroller on your board. But what I wanted to do was take
advantage of some extra features of the powerful
&lt;a href=&quot;https:&#x2F;&#x2F;qmk.fm&#x2F;&quot;&gt;QMK firmware&lt;&#x2F;a&gt; to my disposal.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;The goal of the QMK software project is to develop a completely customizable,
powerful, and enjoyable firmware experience for any project - keyboard or
otherwise - and to provide helpful, encouraging, and kind support and feedback
for people with any software development experience.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;There is a button that lets you download the layout&#x27;s &lt;em&gt;source code&lt;&#x2F;em&gt;.
So I installed ZSA&#x27;s qmk fork on my computer, fed it the layout and,
after some time, managed to compile it! Apparently I had really
good luck that day, because it worked on the first try upon flashing it 🎉&lt;&#x2F;p&gt;
&lt;details&gt;
  &lt;summary&gt;Here&#x27;s what the keymap looks like in source-code form.&lt;&#x2F;summary&gt;
&lt;pre data-lang=&quot;c&quot; style=&quot;background-color:#1e1e1e;color:#dcdcdc;&quot; class=&quot;language-c &quot;&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span style=&quot;color:#569cd6;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span&gt;uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
&lt;&#x2F;span&gt;&lt;span&gt;  [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b5cea8;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;] = LAYOUT_ergodox_pretty(
&lt;&#x2F;span&gt;&lt;span&gt;    KC_ESCAPE,      KC_1,           KC_2,           KC_3,           KC_4,           KC_5,           KC_TRANSPARENT,                                 KC_TRANSPARENT, KC_6,           KC_7,           KC_8,           KC_9,           KC_0,           KC_MINUS,
&lt;&#x2F;span&gt;&lt;span&gt;    KC_TAB,         KC_Q,           KC_W,           KC_E,           KC_R,           KC_T,           LGUI(KC_D),                                     KC_TRANSPARENT, KC_Y,           KC_U,           KC_I,           KC_O,           KC_P,           KC_BSLASH,
&lt;&#x2F;span&gt;&lt;span&gt;    KC_LALT,        KC_A,           KC_S,           KC_D,           KC_F,           KC_G,                                                                           KC_H,           KC_J,           KC_K,           KC_L,           KC_SCOLON,      LT(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b5cea8;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;,KC_QUOTE),
&lt;&#x2F;span&gt;&lt;span&gt;    KC_LSHIFT,      KC_Z,           KC_X,           KC_C,           KC_V,           KC_B,           KC_HYPR,                                        KC_MEH,         KC_N,           KC_M,           KC_COMMA,       KC_DOT,         KC_SLASH,       KC_RSHIFT,
&lt;&#x2F;span&gt;&lt;span&gt;    KC_CAPSLOCK,    KC_LGUI,        KC_LEFT,        KC_RIGHT,       LCTL(KC_SPACE),                                                                                                 KC_RALT,        KC_UP,          KC_DOWN,        KC_DELETE,      KC_RCTRL,
&lt;&#x2F;span&gt;&lt;span&gt;                                                                                                    KC_MEDIA_PLAY_PAUSE,KC_HOME,        KC_PGUP,        KC_MEDIA_NEXT_TRACK,
&lt;&#x2F;span&gt;&lt;span&gt;                                                                                                                    KC_END,         KC_PGDOWN,
&lt;&#x2F;span&gt;&lt;span&gt;                                                                                    KC_SPACE,       MO(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b5cea8;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;),          KC_LCTRL,       KC_ESCAPE,      KC_ENTER,       KC_BSPACE
&lt;&#x2F;span&gt;&lt;span&gt;  ),
&lt;&#x2F;span&gt;&lt;span&gt;  [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b5cea8;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;] = LAYOUT_ergodox_pretty(
&lt;&#x2F;span&gt;&lt;span&gt;    KC_ESCAPE,      KC_F1,          KC_F2,          KC_F3,          KC_F4,          KC_F5,          KC_TRANSPARENT,                                 KC_TRANSPARENT, KC_F6,          KC_F7,          KC_F8,          KC_F9,          KC_F10,         KC_F11,
&lt;&#x2F;span&gt;&lt;span&gt;    KC_TRANSPARENT, KC_EXLM,        KC_AT,          KC_LCBR,        KC_RCBR,        KC_PIPE,        KC_TRANSPARENT,                                 KC_TRANSPARENT, KC_ASTR,        KC_7,           KC_8,           KC_9,           KC_MINUS,       KC_F12,
&lt;&#x2F;span&gt;&lt;span&gt;    KC_TRANSPARENT, KC_HASH,        KC_DLR,         KC_LPRN,        KC_RPRN,        KC_GRAVE,                                                                       KC_EQUAL,       KC_4,           KC_5,           KC_6,           KC_PLUS,        KC_TRANSPARENT,
&lt;&#x2F;span&gt;&lt;span&gt;    KC_TRANSPARENT, KC_PERC,        KC_CIRC,        KC_LBRACKET,    KC_RBRACKET,    KC_TILD,        KC_TRANSPARENT,                                 KC_TRANSPARENT, KC_AMPR,        KC_1,           KC_2,           KC_3,           KC_BSLASH,      KC_TRANSPARENT,
&lt;&#x2F;span&gt;&lt;span&gt;    KC_TRANSPARENT, KC_EQUAL,       KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT,                                                                                                 KC_TRANSPARENT, KC_DOT,         KC_0,           KC_EQUAL,       KC_TRANSPARENT,
&lt;&#x2F;span&gt;&lt;span&gt;                                                                                                    KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT,
&lt;&#x2F;span&gt;&lt;span&gt;                                                                                                                    KC_TRANSPARENT, KC_TRANSPARENT,
&lt;&#x2F;span&gt;&lt;span&gt;                                                                                    KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_0
&lt;&#x2F;span&gt;&lt;span&gt;  ),
&lt;&#x2F;span&gt;&lt;span&gt;  [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b5cea8;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;] = LAYOUT_ergodox_pretty(
&lt;&#x2F;span&gt;&lt;span&gt;    KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT,                                 KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, RESET,
&lt;&#x2F;span&gt;&lt;span&gt;    KC_TRANSPARENT, KC_TRANSPARENT, KC_MS_UP,       KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT,                                 KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT,
&lt;&#x2F;span&gt;&lt;span&gt;    KC_TRANSPARENT, KC_MS_LEFT,     KC_MS_DOWN,     KC_MS_RIGHT,    KC_TRANSPARENT, KC_TRANSPARENT,                                                                 KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT,
&lt;&#x2F;span&gt;&lt;span&gt;    KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT,                                 KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT,
&lt;&#x2F;span&gt;&lt;span&gt;    KC_TRANSPARENT, WEBUSB_PAIR,    KC_TRANSPARENT, KC_TRANSPARENT, KC_MS_BTN3,                                                                                                     KC_AUDIO_VOL_UP,KC_AUDIO_VOL_DOWN,KC_AUDIO_MUTE,  KC_TRANSPARENT, KC_TRANSPARENT,
&lt;&#x2F;span&gt;&lt;span&gt;                                                                                                    KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT,
&lt;&#x2F;span&gt;&lt;span&gt;                                                                                                                    KC_TRANSPARENT, KC_TRANSPARENT,
&lt;&#x2F;span&gt;&lt;span&gt;                                                                                    KC_MS_BTN1,     KC_MS_BTN2,     KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT, KC_TRANSPARENT
&lt;&#x2F;span&gt;&lt;span&gt;  ),
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;details&gt;
&lt;h2 id=&quot;the-protocol-raw-hid-and-c-implementation&quot;&gt;The protocol, raw HID and C implementation&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-protocol-raw-hid-and-c-implementation&quot; aria-label=&quot;Anchor link for: the-protocol-raw-hid-and-c-implementation&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;In order to connect my computer to the code running on my board, I needed a way
to live-communicate between the two. This is what&#x27;s called
&lt;a href=&quot;https:&#x2F;&#x2F;docs.qmk.fm&#x2F;#&#x2F;feature_rawhid&quot;&gt;raw HID&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Raw HID allows for bidirectional communication between QMK and the host
computer over an HID interface. This has many potential use cases, such as
switching keymaps on the fly or changing RGB LED colors and modes.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;On the EZ, the maximum size of such a packet is 32 bytes. In my protocol,
the first byte is used to denote the packet ID. Here&#x27;s an overview of all
packets sent from my custom program running on the host computer to QMK.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;ID&lt;&#x2F;th&gt;&lt;th&gt;Name&lt;&#x2F;th&gt;&lt;th&gt;Content (&lt;code&gt;arguments&lt;&#x2F;code&gt; -&amp;gt; &lt;code&gt;return&lt;&#x2F;code&gt;)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;No Op&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;()&lt;&#x2F;code&gt; -&amp;gt; &lt;code&gt;()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;td&gt;Set full HSV&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;(h: u8, s: u8, v: u8)&lt;&#x2F;code&gt; -&amp;gt; &lt;code&gt;()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;Update Cycle&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;(cycle: u8, brightnesses: [u8; 30])&lt;&#x2F;code&gt; -&amp;gt; &lt;code&gt;()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;&#x2F;td&gt;&lt;td&gt;Fetch WPM&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;()&lt;&#x2F;code&gt; -&amp;gt; &lt;code&gt;(wpm: u8)&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;&#x2F;td&gt;&lt;td&gt;Get user config&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;()&lt;&#x2F;code&gt; -&amp;gt; &lt;code&gt;(mode: u8, brightness: u8)&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;&#x2F;td&gt;&lt;td&gt;Set user config&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;(mode: u8, brightness: u8)&lt;&#x2F;code&gt; -&amp;gt; &lt;code&gt;()&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Okay, that&#x27;s a lot to take in at once. Let&#x27;s go through it all.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Set full HSV&lt;&#x2F;em&gt;: Allows you to set every LED to the same color, specified by HSV
(&lt;strong&gt;h&lt;&#x2F;strong&gt;ue, &lt;strong&gt;s&lt;&#x2F;strong&gt;aturation, &lt;strong&gt;v&lt;&#x2F;strong&gt;alue&#x2F;brightness&#x2F;lightness). This packet is used
for the &lt;em&gt;Constant white&lt;&#x2F;em&gt; and &lt;em&gt;Off&lt;&#x2F;em&gt; mode.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Update Cycle&lt;&#x2F;em&gt;: &quot;Spreads&quot; the color hues over every LED on the keyboard,
producing a rainbow-like pattern, offset by the &lt;code&gt;cycle&lt;&#x2F;code&gt;. The &lt;code&gt;brightnesses&lt;&#x2F;code&gt;
array specifies the brightness for every one of the 30 LEDs. This packet is
used for the &lt;em&gt;Spectrum analyzer&lt;&#x2F;em&gt; and &lt;em&gt;Color cycle&lt;&#x2F;em&gt; mode.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Fetch WPM&lt;&#x2F;em&gt;: Uses the &lt;a href=&quot;https:&#x2F;&#x2F;docs.qmk.fm&#x2F;#&#x2F;feature_wpm&quot;&gt;WPM calculation&lt;&#x2F;a&gt;
feature of QMK to return the current words per minute. This is going to be
continuously called and displayed to the user.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Get&#x2F;Set user config&lt;&#x2F;em&gt;: Used for storing the program state on the keyboard. Yes,
really! The configuration is stored in the board&#x27;s EEPROM and gets saved after
5 seconds of losing connection to the host program.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Everything described here is implemented in the
&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;Friz64&#x2F;ergodox-rawhid&#x2F;-&#x2F;blob&#x2F;3b5689360070cf9c2f10ae75f58fabf819a5f9c5&#x2F;rawhid-friz64&#x2F;keymap.c&quot;&gt;keymap.c&lt;&#x2F;a&gt;
file.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-host-program&quot;&gt;The host program&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-host-program&quot; aria-label=&quot;Anchor link for: the-host-program&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I decided to write this in the Rust programming language, as I am very
proficient with it and it was the perfect choice for my requirements. Take a
look at the source
&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;Friz64&#x2F;ergodox-rawhid&#x2F;-&#x2F;tree&#x2F;3b5689360070cf9c2f10ae75f58fabf819a5f9c5&#x2F;src&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Adjusting the settings is quick using a &lt;code&gt;StatusNotifierItem&lt;&#x2F;code&gt; user interface,
created using the &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;ksni&quot;&gt;&lt;code&gt;ksni&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; crate. Scroll on the
icon to change the brightness.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;ergodox-rawhid&#x2F;ui.jpg&quot; alt=&quot;user interface&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;wpm-display&quot;&gt;WPM display&lt;a class=&quot;zola-anchor&quot; href=&quot;#wpm-display&quot; aria-label=&quot;Anchor link for: wpm-display&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;First I went the route of generating an icon for each number 0-255 and
displaying that in my existing tray indicator, but that really didn&#x27;t work out.
So, I went the slightly less hacky way and wrote my first ever GNOME extension:
&lt;code&gt;wpmdisplay&lt;&#x2F;code&gt;. It exposes a DBus method &lt;code&gt;UpdateWPM&lt;&#x2F;code&gt;, taking the current words per
minute as a byte and adding that as an indicator to the GNOME status area. The
code is by far not ideal but... it works! &#x2F;shrug&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the
&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;Friz64&#x2F;ergodox-rawhid&#x2F;-&#x2F;blob&#x2F;3b5689360070cf9c2f10ae75f58fabf819a5f9c5&#x2F;wpmdisplay@friz64.de&#x2F;extension.js&quot;&gt;source code&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;modes&quot;&gt;Modes&lt;a class=&quot;zola-anchor&quot; href=&quot;#modes&quot; aria-label=&quot;Anchor link for: modes&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Wait, what exactly are these &lt;em&gt;modes&lt;&#x2F;em&gt; I was talking about? Well, the user
will have the choice between several lighting modes. Onto the good stuff!&lt;&#x2F;p&gt;
&lt;h4 id=&quot;constant-white&quot;&gt;Constant white&lt;a class=&quot;zola-anchor&quot; href=&quot;#constant-white&quot; aria-label=&quot;Anchor link for: constant-white&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;This displays the color white in the currently specified brightness.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.friz64.de&#x2F;ergodox-rawhid&#x2F;mode_white.jpg&quot; alt=&quot;Picture of the left side with all white LEDs&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Very cozy~!&lt;&#x2F;p&gt;
&lt;h4 id=&quot;spectrum-analyzer&quot;&gt;Spectrum analyzer&lt;a class=&quot;zola-anchor&quot; href=&quot;#spectrum-analyzer&quot; aria-label=&quot;Anchor link for: spectrum-analyzer&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Probably the coolest of the bunch.
Uses the &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;spectrum-analyzer&quot;&gt;&lt;code&gt;spectrum-analyzer&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; crate
to live-process the currently playing audio and display the frequency spectrum
on the keyboard. The lower frequencies are on the left and the higher ones on
the right. The louder the music in general, the faster the hue cycle moves.&lt;&#x2F;p&gt;
&lt;div&gt;
    &lt;a href=&quot;https:&#x2F;&#x2F;youtube.com&#x2F;watch?v=AClXINk_gM0&quot;&gt;
        &lt;figure class=&quot;yt-nonembed&quot;&gt;
            &lt;img src=&quot;https:&#x2F;&#x2F;i3.ytimg.com&#x2F;vi&#x2F;AClXINk_gM0&#x2F;maxresdefault.jpg&quot;&gt;
            &lt;figcaption&gt;Click to watch on YouTube&lt;&#x2F;figcaption&gt;
        &lt;&#x2F;figure&gt;
    &lt;&#x2F;a&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;This took some time to get right, as there are several things to consider. For
example, there are generally more lower frequencies in my audio
than higher ones, so the right side&#x27;s lights were noticeably less lit up. The
easy but very effective solution was to double the brightness of the higher
frequencies.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the source code:
&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;Friz64&#x2F;ergodox-rawhid&#x2F;-&#x2F;blob&#x2F;3b5689360070cf9c2f10ae75f58fabf819a5f9c5&#x2F;src&#x2F;analyzer.rs&quot;&gt;&lt;code&gt;analyzer.rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;color-cycle&quot;&gt;Color cycle&lt;a class=&quot;zola-anchor&quot; href=&quot;#color-cycle&quot; aria-label=&quot;Anchor link for: color-cycle&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Moves through the rainbow pattern at a constant rate.&lt;&#x2F;p&gt;
&lt;picture&gt;
    &lt;source srcset=&quot;mode_cycle.webp&quot; type=&quot;image&#x2F;webp&quot;&gt;
    &lt;img src=&quot;mode_cycle.gif&quot; alt=&quot;Video of the keyboard going through a full cycle&quot;&gt;
&lt;&#x2F;picture&gt;
&lt;h2 id=&quot;that-s-everything-for-this-post&quot;&gt;That&#x27;s everything for this post&lt;a class=&quot;zola-anchor&quot; href=&quot;#that-s-everything-for-this-post&quot; aria-label=&quot;Anchor link for: that-s-everything-for-this-post&quot;&gt;§&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Here&#x27;s the direct link to the entire
&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;Friz64&#x2F;ergodox-rawhid&quot;&gt;source repo&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Any questions? Possible improvements? What didn&#x27;t you like?
&lt;a href=&quot;&#x2F;about&quot;&gt;Let me know.&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Until next time! 👋&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
