<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Cronoxyd]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://cronoxyd.de/</link><image><url>https://cronoxyd.de/favicon.png</url><title>Cronoxyd</title><link>https://cronoxyd.de/</link></image><generator>Ghost 5.24</generator><lastBuildDate>Wed, 15 Apr 2026 23:01:14 GMT</lastBuildDate><atom:link href="https://cronoxyd.de/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Order pickup number system]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I&apos;ve always enjoyed coding challenges; obviously at work but also every so often just for fun, usually from a coding-exercise site such as CodingBat or CodinGame. So I thought that I&apos;d try my hand at imagining, writing and finally solving a coding challenge myself:</p>
<h2 id="the-challenge">The challenge</h2>]]></description><link>https://cronoxyd.de/order-pickup-number-system/</link><guid isPermaLink="false">64739dbc1ab1f10001ceb60d</guid><category><![CDATA[Coding]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Sun, 28 May 2023 20:36:29 GMT</pubDate><media:content url="https://cronoxyd.de/content/images/2023/05/Order-pickup-number-system-cover.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://cronoxyd.de/content/images/2023/05/Order-pickup-number-system-cover.png" alt="Order pickup number system"><p>I&apos;ve always enjoyed coding challenges; obviously at work but also every so often just for fun, usually from a coding-exercise site such as CodingBat or CodinGame. So I thought that I&apos;d try my hand at imagining, writing and finally solving a coding challenge myself:</p>
<h2 id="the-challenge">The challenge</h2>
<p>You&apos;re tasked with implementing a module of an ordering system for a self-service restaurant. When a customer places an order, they shall receive a pickup number which is called when their order is ready for pickup at the counter. To avoid confusion, the order numbers shall have a configurable cooldown period after the food was picked up in which they may not be assigned again. The numbers of the orders which are currently prepared and the ones of the orders that are ready for pickup shall also be displayed to both the customers and the staff. The first number shall be configurable (at any time).</p>
<p>In coding terms, implement this interface:</p>
<pre><code class="language-csharp">public interface IOrderPickupNumberSystem
{
    /// &lt;summary&gt;
    /// Configures the (inclusive) lower minimum order number.
    /// &lt;/summary&gt;
    int MinOrderNumber { get; set; }

    TimeSpan OrderNumberCooldown { get; set; }

    /// &lt;summary&gt;
    /// Gets the next free order number and marks it as &quot;in preparation&quot;
    /// &lt;/summary&gt;
    int GetNextIdleOrderNumber();

    /// &lt;returns&gt;
    /// All order number which are currently being prepared.
    /// &lt;/returns&gt;
    int[] GetOrderNumbersInPreparation();

    /// &lt;returns&gt;
    /// All order number which are waiting for customer pickup.
    /// &lt;/returns&gt;    
    int[] GetOrderNumbersReadyForPickup();

    /// &lt;summary&gt;
    /// Marks the order with the specified number as &quot;ready for pickup&quot;.
    /// &lt;/summary&gt;
    void SetOrderReady(int orderNumber);

    /// &lt;summary&gt;
    /// Gets the total number of order numbers, regardless of status.
    /// &lt;/summary&gt;
    int GetOrderNumberPoolSize();

    /// &lt;summary&gt;
    /// Marks the order with the specified number as picked up and initiates the
    /// cooldown.
    /// &lt;/summary&gt;
    void SetOrderPickedUp(int orderNumber);
}
</code></pre>
<h2 id="implement-yourself">Implement yourself</h2>
<p>If you want to take the challenge and implement this yourself, clone the repo at the end of this post, delete the contents of the <code>OrderPickupNumberSystem</code> class and have Visual Studio add the method stubs of the interface. When you&apos;re finished run the unit tests in the separate testing project to verify your work.</p>
<h2 id="solution">Solution</h2>
<h3 id="the-data-structure">The data structure</h3>
<p>Firstly, this system of unique numbers that have data attached to them screams database. This is a classic primary key scenario and in any productive environment, it would be implemented as such. But for the purposes of this challenge, I decided to use next best thing: a dictionary. I always think of a dictionary as a mini in-memory database. Often, key and value are value data types, such as <code>int</code> and <code>string</code>, but they can really be anything. In this case, the key will represent the order number as an <code>int</code> and the value will be the status of the order number.</p>
<p>At first, one may think that an enum for the states of the order numbers is enough, but there is also the requirement for a configurable cooldown which an enum can definitely not satisfy. Usually, I like to use a strictly implicit approach with something like this, but this time I decided to include the <em>Cooldown</em> state in the enum and an extra <code>DateTime?</code> property for tracking the cooldown time. In the strictly implicit solution, there would be no extra state in the enum and the number would be considered in cooldown if the <code>DateTime?</code> property is set to a non-null value, taking precedence over the actual state denoted by the enum. But this also complicates working with the class.</p>
<h3 id="getting-the-next-order-number">Getting the next order number</h3>
<p>I will describe the implementation of <code>int GetNextIdleOrderNumber()</code> in an iterative fashion:</p>
<ol>
<li>At the core, the next order number is the minimum order number or the maximum existing order number + 1.</li>
<li>Before that, all existing order numbers must be checked for the first number in the <em>Idle</em> state that is greater than or equal to the minimum order number.</li>
<li>And at first, all existing order numbers must be refreshed in terms of the cooldown.</li>
</ol>
<h3 id="the-info-functions">The info functions</h3>
<p>For the <code>int GetOrderNumberPoolSize()</code>, <code>int[] GetOrderNumbersInPreparation()</code> and <code>int[] GetOrderNumbersReadyForPickup()</code> functions, we can simply use the <code>Count</code> property of the <code>Dictionary</code> and the <code>Linq</code> extensions respectively.</p>
<h3 id="the-setter-functions">The setter functions</h3>
<p>The setter functions, <code>void SetOrderReady(int orderNumber)</code> and <code>void SetOrderPickedUp(int orderNumber)</code>, get the existing info of the specified order number, checks if a transition is possible to the desired state, sets the new state and, if the order has been picked up, sets the <code>CooldownStart</code> property to <code>DateTime.Now</code>.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/cronoxyd/OrderPickupNumberSystem"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - cronoxyd/OrderPickupNumberSystem</div><div class="kg-bookmark-description">Contribute to cronoxyd/OrderPickupNumberSystem development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Order pickup number system"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">cronoxyd</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/987ff2906eaf3fcbf63f44528e8f37a2fad6c9ec61b4fe06977606c30c12d080/cronoxyd/OrderPickupNumberSystem" alt="Order pickup number system"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[.NET MAUI Blazor windows]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h1 id="net-maui-blazor-windows">.NET MAUI Blazor windows</h1>
<p>The .NET MAUI documentation details the creation of windows and states that .NET MAUI apps support having multiple windows, but what it omits is explaining how to access this functionality in the context of a .NET MAUI Blazor app.</p>
<p>Fundamentally, a .NET MAUI Blazor app is</p>]]></description><link>https://cronoxyd.de/net-maui-blazor-windows/</link><guid isPermaLink="false">646a6ba91ab1f10001ceb5fe</guid><category><![CDATA[Coding]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Sun, 21 May 2023 19:08:48 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h1 id="net-maui-blazor-windows">.NET MAUI Blazor windows</h1>
<p>The .NET MAUI documentation details the creation of windows and states that .NET MAUI apps support having multiple windows, but what it omits is explaining how to access this functionality in the context of a .NET MAUI Blazor app.</p>
<p>Fundamentally, a .NET MAUI Blazor app is just a normal MAUI app with a single page (<code>MainPage.xaml</code>) that has a <a href="https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.components.webview.maui.blazorwebview?view=net-maui-7.0">BlazorWebView</a> which then actually hosts the Razor content. That means that any newly created window will still have the same <code>MainPage.xaml</code> as its content and it is up to the Blazor-side to present the correct content.</p>
<h2 id="net-maui-app-startup">.NET MAUI app startup</h2>
<p>First, a little background about how a .NET MAUI Blazor application starts (in the boilerplate project):</p>
<ol>
<li>
<p><code>Program.cs</code></p>
<p>The entry point is <code>CreateMauiApp()</code> which references <code>App</code> by using it as a type argument in the generic method <code>UseMauiApp()</code>.</p>
</li>
<li>
<p><code>App.xaml(.cs)</code> (derives from <code>Microsoft.Maui.Controls.Application</code>)</p>
<p>Sets its <code>MainPage</code> property (inherited from its base class) to a new instance of <code>MainPage</code>. According to the <a href="https://learn.microsoft.com/dotnet/maui/fundamentals/windows#create-a-window?view=net-maui-7.0">.NET MAUI documentation</a> on windows, setting the <code>MainPage</code> property of an <code>Application</code> will cause a <code>Window</code> to be created.</p>
</li>
<li>
<p><code>MainPage.xaml(.cs)</code></p>
<p>Contains a <a href="https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.components.webview.maui.blazorwebview?view=net-maui-7.0"><code>BlazorWebView</code></a> that references the static <code>wwwroot/index.html</code> as the HTML boilerplate/skeleton and adds the <code>Main</code> type as the only <code>RootComponent</code> of the <code>BlazorWebView</code>.</p>
</li>
<li>
<p><code>Main.razor(.cs)</code></p>
<p>Contains a <code>Router</code> component that, according to the <a href="https://learn.microsoft.com/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-7.0">Blazor routing and navigation documentation</a>, scans the specified <code>AppAssembly</code> to &quot;gather route information for the app&apos;s components that have a RouteAttribute&quot;.</p>
</li>
</ol>
<p>So we need to provide a way for Razor components to create a new MAUI window with the possibility to specify an URI that references a specific page within the <code>AppAssembly</code> of the <code>Router</code>.</p>
<h2 id="implementation">Implementation</h2>
<p>The following details the implementation from the bottom up in terms of the startup described above:</p>
<h3 id="mainrazorcs-create"><code>Main.razor.cs</code> (create)</h3>
<p>Firstly, we need to provide a way to specify a page other that the index (i.e. the page that has the <code>/</code> route) in the <code>Main</code> razor component. So, we add a parameter to specify the URI of the page we want to view and navigate to that URI right after initializing the component:</p>
<pre><code class="language-diff">+public partial class Main : ComponentBase
+{
+    [Parameter]
+    public string PageUri { get; set; } = string.Empty;
+
+    protected override void OnInitialized()
+    {
+        if (!string.IsNullOrEmpty(PageUri))
+            Nav.NavigateTo(PageUri);
+    }
+}
</code></pre>
<h3 id="mainpagexaml"><code>MainPage.xaml</code></h3>
<p>Remove the <code>&lt;BlazorWebView.RootComponents&gt;</code> element from the XAML completly. We will populate this collection in the code-behind instead:</p>
<pre><code class="language-diff">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
&lt;ContentPage xmlns=&quot;http://schemas.microsoft.com/dotnet/2021/maui&quot;
             xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;
             xmlns:local=&quot;clr-namespace:RecEx.App&quot;
             x:Class=&quot;RecEx.App.MainPage&quot;
             BackgroundColor=&quot;{DynamicResource PageBackgroundColor}&quot;&gt;

    &lt;BlazorWebView x:Name=&quot;blazorWebView&quot; HostPage=&quot;wwwroot/index.html&quot;&gt;
-        &lt;BlazorWebView.RootComponents&gt;
-            &lt;RootComponent Selector=&quot;#app&quot; ComponentType=&quot;{x:Type local:Main}&quot; Parameters=&quot;{Binding Path=RootComponentParameters}&quot; /&gt;
-        &lt;/BlazorWebView.RootComponents&gt;
    &lt;/BlazorWebView&gt;

&lt;/ContentPage&gt;
</code></pre>
<h3 id="mainpagexamlcs-create"><code>MainPage.xaml.cs</code> (create)</h3>
<p>The items in the <code>RootComponents</code> collection provide a way to pass parameters to the specified component in the form of a <code>IDictionary&lt;string, object&gt;</code>. So we add an optional argument to the constructor to expose this to callers:</p>
<pre><code class="language-diff">+public partial class MainPage : ContentPage
+{
+    public MainPage(IDictionary&lt;string, object&gt; rootComponentParameters = null)
+    {
+        InitializeComponent();
+
+        blazorWebView.RootComponents.Add(new()
+        {
+            ComponentType = typeof(Main),
+            Parameters = rootComponentParameters,
+            Selector = &quot;#app&quot;
+        });
+    }
+}
</code></pre>
<h3 id="appxamlcs"><code>App.xaml.cs</code></h3>
<p>Finally, we add a static class that provides extension methods to open the window:</p>
<pre><code class="language-diff">public partial class App : Application
{
    public App()
    {
        InitializeComponent();

        MainPage = new MainPage();
    }
}

+public static class ApplicationExtensions
+{
+    public static Window OpenWindowFromPage(this Application app, Page page)
+    {
+        var windowObj = new Window(page);
+        windowObj.Parent = app.Windows[0];
+        app.OpenWindow(windowObj);
+        return windowObj;
+    }
+    public static void OpenInNewWindow(this Application app, string pageUri)
+    {
+        var windowPage = new MainPage(new Dictionary&lt;string, object&gt;()
+        {
+            {nameof(Main.PageUri), pageUri }
+        });
+        app.OpenWindowFromPage(windowPage);
+    }
+
+    public static void OpenInNewWindow(this ComponentBase component, string pageUri) =&gt;
+        Application.Current.OpenInNewWindow(pageUri);
+}
</code></pre>
<h2 id="usage">Usage</h2>
<p>To open a Razor page in a new MAUI window, simply call the <code>OpenInNewWindow</code> extension method from any component:</p>
<pre><code class="language-razor">@page &quot;/&quot;

&lt;h1&gt;.NET MAUI Blazor window demo&lt;/h1&gt;
&lt;button type=&quot;button&quot; @onclick=&quot;() =&gt; OpenCounterWindow()&quot;&gt;Open counter window&lt;/button&gt;

@code {
    protected void OpenCounterWindow()
    {
        this.OpenInNewWindow(&quot;/counter&quot;);
    }
}
</code></pre>
<h2 id="limitationscaveats">Limitations/caveats</h2>
<ol>
<li>There does not seem to be any support for modal windows (yet?). I&apos;ve experimented with the <code>Parent</code> property on <code>Window</code>, but to no avail.</li>
<li>In my tests, I could always briefly see the content of the <code>Index.razor</code> page because the <code>Router</code> loads it by default. In addition to being a cosmetic issue, it also means that any code on the <code>Index</code> page is executed before the specified page is loaded.</li>
</ol>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[.NET MAUI Blazor workbench app template]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>This guide details how to create .NET MAUI Blazor workbench-style app.</p>
<p>By workbench-style app I mean an application that is akin to most classic desktop apps, where there is:</p>
<ol>
<li>a fixed header that usually contains a menu stip or ribbon,</li>
<li>a main content area which often has a tree view</li></ol>]]></description><link>https://cronoxyd.de/net-maui-blazor-workbench-app-template/</link><guid isPermaLink="false">646a2b011ab1f10001ceb5e6</guid><category><![CDATA[Coding]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Sun, 21 May 2023 16:18:18 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>This guide details how to create .NET MAUI Blazor workbench-style app.</p>
<p>By workbench-style app I mean an application that is akin to most classic desktop apps, where there is:</p>
<ol>
<li>a fixed header that usually contains a menu stip or ribbon,</li>
<li>a main content area which often has a tree view or other navigation on the left and the actual content on the right and finally</li>
<li>a fixed footer that usually contains status messages and context information.</li>
</ol>
<p><img src="https://cronoxyd.de/content/images/2023/05/Workbench-app-layout.svg" alt="Workbench-app-layout" loading="lazy"></p>
<h2 id="starting-point">Starting point</h2>
<p>When creating a new .NET MAUI Blazor app, the <code>wwwroot</code> directory is populated with a boilerplate HTML page that hosts the Blazor content which consists of:</p>
<ul>
<li>wwwroot
<ul>
<li>css
<ul>
<li>bootstrap
<ul>
<li>(bootstrap assets)</li>
</ul>
</li>
<li>open-iconic
<ul>
<li>(open-iconic font assets)</li>
</ul>
</li>
<li>app.css</li>
</ul>
</li>
<li>index.html</li>
</ul>
</li>
</ul>
<p>Additionally, some Razor components also provide styling using a <code>.razor.css</code> file.</p>
<p>First, we&apos;re gonna focus on the <code>index.html</code> and its essential components:</p>
<pre><code class="language-html">&lt;html&gt;

&lt;head&gt;&lt;/head&gt;

&lt;body&gt;
    &lt;div class=&quot;status-bar-safe-area&quot;&gt;&lt;/div&gt;
    &lt;div id=&quot;app&quot;&gt;Loading...&lt;/div&gt;
    &lt;div id=&quot;blazor-error-ui&quot;&gt;
        An unhandled error has occurred.
        &lt;a href=&quot;&quot; class=&quot;reload&quot;&gt;Reload&lt;/a&gt;
        &lt;a class=&quot;dismiss&quot;&gt;&#x1F5D9;&lt;/a&gt;
    &lt;/div&gt;
    &lt;script src=&quot;_framework/blazor.webview.js&quot; autostart=&quot;false&quot;&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre>
<p>In conjuction with <code>MainLayout.razor</code>, we arrive at the following HTML skeleton:</p>
<pre><code class="language-html">&lt;html&gt;

&lt;head&gt;&lt;/head&gt;

&lt;body&gt;
    &lt;div class=&quot;status-bar-safe-area&quot;&gt;&lt;/div&gt;
    &lt;div id=&quot;app&quot;&gt;
        &lt;div class=&quot;page&quot;&gt;
            &lt;div class=&quot;sidebar&quot;&gt;
                &lt;NavMenu /&gt;
            &lt;/div&gt;

            &lt;main&gt;
                &lt;div class=&quot;top-row px-4&quot;&gt;
                    &lt;a href=&quot;https://docs.microsoft.com/aspnet/&quot; target=&quot;_blank&quot;&gt;About&lt;/a&gt;
                &lt;/div&gt;

                &lt;article class=&quot;content px-4&quot;&gt;
                    
                &lt;/article&gt;
            &lt;/main&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div id=&quot;blazor-error-ui&quot;&gt;
        An unhandled error has occurred.
        &lt;a href=&quot;&quot; class=&quot;reload&quot;&gt;Reload&lt;/a&gt;
        &lt;a class=&quot;dismiss&quot;&gt;&#x1F5D9;&lt;/a&gt;
    &lt;/div&gt;
    &lt;script src=&quot;_framework/blazor.webview.js&quot; autostart=&quot;false&quot;&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre>
<h2 id="cleanup">Cleanup</h2>
<h3 id="framework-internal-elements-and-styling">Framework-internal elements and styling</h3>
<p>The actual framework-relevant elements whose styling needs to remain are:</p>
<ul>
<li><code>div.status-bar-safe-area</code>,</li>
<li><code>div#app</code> and</li>
<li><code>blazor-error-ui</code>.</li>
</ul>
<p>So we pack their CSS styles into a special stylesheet (the <code>div#app</code> actually has no boilerplate code) and call it <code>blazor.css</code>:</p>
<pre><code class="language-css">#blazor-error-ui {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

.status-bar-safe-area {
    display: none;
}

@supports (-webkit-touch-callout: none) {
    .status-bar-safe-area {
        display: flex;
        position: sticky;
        top: 0;
        height: env(safe-area-inset-top);
        background-color: #f7f7f7;
        width: 100%;
        z-index: 1;
    }
}
</code></pre>
<h3 id="extraneous-styling">Extraneous styling</h3>
<p>Next, we delete all other styling, specifically:</p>
<ul>
<li><code>Shared\MainLayout.razor.css</code></li>
<li><code>Shared\NavMenu.razor.css</code></li>
</ul>
<h2 id="building-the-workbench-style">Building the workbench-style</h2>
<h3 id="html">HTML</h3>
<p>On the component, we overwrite <code>Shared\MainLayout.razor</code> with:</p>
<pre><code class="language-html">@inherits LayoutComponentBase

&lt;div class=&quot;page&quot;&gt;
    &lt;header&gt;
        &lt;nav&gt;
            &lt;a href=&quot;https://docs.microsoft.com/aspnet/&quot; target=&quot;_blank&quot;&gt;About&lt;/a&gt;
        &lt;/nav&gt;
    &lt;/header&gt;

    &lt;main&gt;
        &lt;aside class=&quot;sidebar&quot;&gt;
            &lt;NavMenu /&gt;
        &lt;/aside&gt;
        &lt;article class=&quot;content&quot;&gt;
            @Body
        &lt;/article&gt;
    &lt;/main&gt;

    &lt;footer&gt;

    &lt;/footer&gt;
&lt;/div&gt;
</code></pre>
<h3 id="css">CSS</h3>
<p>Finally, we overwrite <code>wwwroot\css\app.css</code> with the workbench style, which I will describe blockwise:</p>
<h4 id="box-sizing">Box sizing</h4>
<p>One quite common practice is to set the <code>box-sizing</code> CSS property of all elements to <code>border-box</code> which forces uniform handling of sizing, margins and paddings:</p>
<pre><code class="language-css">* {
    box-sizing: border-box;
}
</code></pre>
<h4 id="dom-root-elements">DOM root elements</h4>
<p>The DOM root elements, <code>&lt;html&gt;</code> and <code>&lt;body&gt;</code> need to be stripped of their default margin and padding:</p>
<pre><code class="language-css">html,
body {
    font-family: sans-serif;
    margin: 0;
    padding: 0;
}
</code></pre>
<p>The setting of the font family is optional of course.</p>
<h4 id="apppage-frame">App/page frame</h4>
<p>The <code>div#app</code> and <code>div.page</code> elements which reside in the <code>index.html</code> and <code>MainLayout.razor</code> respectively, define the frame for the actual workbench elements. And we&apos;re gonna be using the CSS grid system to define the sizes of the nested elements.</p>
<pre><code class="language-css">div#app,
div.page {
    height: 100vh;
    width: 100vw;
}
</code></pre>
<h4 id="header-footer-padding-optional">Header footer padding (optional)</h4>
<p>Since the header and footer often contain only single-row text or other similarly-sized content, we specify a small padding:</p>
<pre><code class="language-css">header,
footer {
    padding: 0.25rem;
}
</code></pre>
<h4 id="area-borders-optional">Area borders (optional)</h4>
<p>So differentiate the areas of the layout, we define some borders:</p>
<pre><code class="language-css">header {
    border-bottom: 1px solid lightgrey;
}

aside.sidebar {
    border-right: 1px solid lightgrey;
}

footer {
    border-top: 1px solid lightgrey;
}
</code></pre>
<h4 id="grid-systems">Grid systems</h4>
<p>The following are the most essential rules to create the workbench layout:</p>
<pre><code class="language-css">div.page {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr auto;
}

main {
    display: grid;
    grid-template-columns: 1fr 3fr;
    overflow: hidden;
}
</code></pre>
<p>Remember that the <code>div#page</code> element contains the <code>header</code>, <code>main</code> and <code>footer</code> elements and therefore needs to define a row-based layout. The <code>main</code> element then contains the navigation sidebar and the actual content area.</p>
<p>The <code>grid-template-rows</code> and <code>grid-template-columns</code> properties define relative and absolute sizes for the rows and columns within the grid.</p>
<p>The <code>fr</code> CSS unit was new to me when I first researched this topic. According to the <a href="https://developer.mozilla.org/docs/Web/CSS/flex_value">MDN documentation</a> is specifies &quot;a fraction of the leftover space in the grid container&quot;.</p>
<h4 id="scrolling-content">Scrolling content</h4>
<p>The final essential rules are for the content area that enable the scroll of overflowing content:</p>
<pre><code class="language-css">article.content {
    overflow-y: scroll;
    padding: 20px;
}
</code></pre>
<h4 id="preventing-statup-focus-border">Preventing statup focus border</h4>
<p>When the .NET MAUI app starts, it has the focus set on the first element of the content which, by default, is a <code>h1</code> heading on the <code>Index.razor</code> page. This causes a focus border to be drawn. We can disable this behavior:</p>
<pre><code class="language-css">h1:focus {
    outline: none;
}
</code></pre>
<h2 id="flexibility">Flexibility</h2>
<p>You may have noticed that the content of the Razor pages is confined to the <code>article.content</code> element which may not be suitable for every use-case. But you can easily define which elements are defined in the <code>MainLayout.razor</code> and which are defined in the individual pages. Only remember to move the <code>@Body</code> directive so that the content gets inserted again.</p>
<p>The following is an example of a layout that is more suitable for apps that shall support multiple views which have control over their own layout. Program-level code may then populate the <code>&lt;footer&gt;</code> element with global status messages such as the status of a database connection.</p>
<pre><code class="language-html">@inherits LayoutComponentBase

&lt;div class=&quot;page&quot;&gt;
    @Body

    &lt;footer&gt;

    &lt;/footer&gt;
&lt;/div&gt;
</code></pre>
<p>And then, the individual pages contain the <code>&lt;header&gt;</code> and <code>&lt;main&gt;</code> elements:</p>
<pre><code class="language-html">&lt;header&gt;
    &lt;nav&gt;
        (Menu strip)
    &lt;/nav&gt;
&lt;/header&gt;

&lt;main&gt;
    &lt;aside class=&quot;sidebar&quot;&gt;
        (Sidebar/explorer here)
    &lt;/aside&gt;
    &lt;article class=&quot;content&quot;&gt;
        (Editor/Content here)
    &lt;/article&gt;
&lt;/main&gt;
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[ESD]]></title><description><![CDATA[<p>While on vacation in <a href="https://goo.gl/maps/ozstcajZPpsonWxr7">Sainte-Maxime</a>, I had the luck to experience a single thunderstorm cell off in the distance and over the ocean. A first to me, I had to capture the beautiful display of ESD.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2022/10/DSC06116.jpg" class="kg-image" alt loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2022/10/DSC06116.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2022/10/DSC06116.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2022/10/DSC06116.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2022/10/DSC06116.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Thunderstorm cell ESD with ocean-strike</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2022/10/DSC06121.jpg" class="kg-image" alt loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2022/10/DSC06121.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2022/10/DSC06121.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2022/10/DSC06121.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2022/10/DSC06121.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Glow of a cloud-internal ESD</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2022/09/DSC06131.jpg" class="kg-image" alt loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2022/09/DSC06131.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2022/09/DSC06131.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2022/09/DSC06131.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2022/09/DSC06131.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Cloud-internal ESD</figcaption></figure>]]></description><link>https://cronoxyd.de/inpact/</link><guid isPermaLink="false">63377fc16db063000115f496</guid><category><![CDATA[Photography]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Sat, 01 Oct 2022 00:04:45 GMT</pubDate><media:content url="https://cronoxyd.de/content/images/2022/10/DSC06116-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://cronoxyd.de/content/images/2022/10/DSC06116-1.jpg" alt="ESD"><p>While on vacation in <a href="https://goo.gl/maps/ozstcajZPpsonWxr7">Sainte-Maxime</a>, I had the luck to experience a single thunderstorm cell off in the distance and over the ocean. A first to me, I had to capture the beautiful display of ESD.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2022/10/DSC06116.jpg" class="kg-image" alt="ESD" loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2022/10/DSC06116.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2022/10/DSC06116.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2022/10/DSC06116.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2022/10/DSC06116.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Thunderstorm cell ESD with ocean-strike</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2022/10/DSC06121.jpg" class="kg-image" alt="ESD" loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2022/10/DSC06121.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2022/10/DSC06121.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2022/10/DSC06121.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2022/10/DSC06121.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Glow of a cloud-internal ESD</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2022/09/DSC06131.jpg" class="kg-image" alt="ESD" loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2022/09/DSC06131.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2022/09/DSC06131.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2022/09/DSC06131.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2022/09/DSC06131.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Cloud-internal ESD</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[3D printed diffusers pt.2]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Starting from my research discussed in <a href="https://cronoxyd.de/3d-printed-diffusers">3D printed diffusors</a>, I designed somewhat of a hybrid that aims to consolidate all positive traits of the previous diffusers and incorporate the maxims I postulated at the end of the post.</p>
<p><img src="https://cronoxyd.de/content/images/2022/03/85C1-two-stage-sidelight-diffuser-model--15--inverted-.jpg" alt="Two stage sidelight diffuser model (15&#xB0; inverted)" loading="lazy"></p>
<p>The main points of this design are:</p>
<ol>
<li>Compactness - the sources of</li></ol>]]></description><link>https://cronoxyd.de/3d-printed-diffusors-pt-2/</link><guid isPermaLink="false">6240a0553193ed0001aeb915</guid><category><![CDATA[3D printing]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Sun, 27 Mar 2022 18:59:36 GMT</pubDate><media:content url="https://cronoxyd.de/content/images/2022/03/DSC06564.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://cronoxyd.de/content/images/2022/03/DSC06564.jpg" alt="3D printed diffusers pt.2"><p>Starting from my research discussed in <a href="https://cronoxyd.de/3d-printed-diffusers">3D printed diffusors</a>, I designed somewhat of a hybrid that aims to consolidate all positive traits of the previous diffusers and incorporate the maxims I postulated at the end of the post.</p>
<p><img src="https://cronoxyd.de/content/images/2022/03/85C1-two-stage-sidelight-diffuser-model--15--inverted-.jpg" alt="3D printed diffusers pt.2" loading="lazy"></p>
<p>The main points of this design are:</p>
<ol>
<li>Compactness - the sources of light are arranged at an angle at the top and bottom behind the screen so that the circle of light is maximized.</li>
<li>Alignment - the intermediate diffuser is curved so that the light on it is approximately in the correct position for the screen.</li>
<li>Refraction - the light changes media 4 times before exiting the diffuser.</li>
<li>Distance - the sources of light are as far as possible from the screen.</li>
</ol>
<table>
<thead>
<tr>
<th><img src="https://cronoxyd.de/content/images/2022/03/DSC06559.jpg" alt="3D printed diffusers pt.2" loading="lazy"></th>
<th><img src="https://cronoxyd.de/content/images/2022/03/DSC06568.jpg" alt="3D printed diffusers pt.2" loading="lazy"></th>
</tr>
</thead>
</table>
<p>The resulting illumination on the screen is very good compared to the trials:</p>
<p><img src="https://cronoxyd.de/content/images/2022/03/85C1-two-stage-sidelight-diffuser--15--inverted-.jpg" alt="3D printed diffusers pt.2" loading="lazy"></p>
<p>But looking at the solarized version of the screen image, two problems can be identified.</p>
<p><img src="https://cronoxyd.de/content/images/2022/03/85C1-two-stage-sidelight-diffuser--15--inverted-solarized-.jpg" alt="3D printed diffusers pt.2" loading="lazy"></p>
<ol>
<li>The layer times are very visible on the screen and the intermediate diffuser, causing stripes to appear.</li>
<li>There is falloff on the side.</li>
</ol>
<p>I plan to address these issues in the following ways:</p>
<ol>
<li>Since striping was never a problem with the diffusers in the first post where the screen was thicker than one layer, the screen and intermediate diffuser will be thickened to at least two layers.<br>
<img src="https://cronoxyd.de/content/images/2022/03/Slicer-wall-width.png" alt="3D printed diffusers pt.2" loading="lazy"></li>
<li>The sources of light will be rearranged to decrease distance from the sides.<br>
<img src="https://cronoxyd.de/content/images/2022/03/85C1-two-stage-sidelight-diffuser-light-source-plan--15--inverted-.jpg" alt="3D printed diffusers pt.2" loading="lazy"></li>
</ol>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[3D printed diffusers]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I wanted to upgrade my voltmeter clock with illuminated voltmeters that react to the ambient level of brightness. So I had to design a new case for the voltmeter mechanics and electrics that included light guides or rather diffusers. This posed and interesting challenge and learning curve that I detail</p>]]></description><link>https://cronoxyd.de/3d-printed-diffusers/</link><guid isPermaLink="false">623f67d63193ed0001aeb8bb</guid><category><![CDATA[3D printing]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Sat, 26 Mar 2022 19:39:51 GMT</pubDate><media:content url="https://cronoxyd.de/content/images/2022/03/85C1-double-sidelight-diffusor-model--Side-inverted-.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://cronoxyd.de/content/images/2022/03/85C1-double-sidelight-diffusor-model--Side-inverted-.png" alt="3D printed diffusers"><p>I wanted to upgrade my voltmeter clock with illuminated voltmeters that react to the ambient level of brightness. So I had to design a new case for the voltmeter mechanics and electrics that included light guides or rather diffusers. This posed and interesting challenge and learning curve that I detail in the following. Now, I have yet to finish my research with this and I will post again when I evaluated new designs.</p>
<h2 id="hypothesis-of-operation">Hypothesis of operation</h2>
<p>My first thought was to put as much plastic between the source of light and the screen as feasible. On the other hand, too much attenuation can lead to heavier falloff. I figured that the inverse square law where the intensity is inversely proportional to the square of the distance from the source (meaning that a point twice as far away receives only one quarter the light) would apply in plastic just as much as it does in air.</p>
<p><img src="https://cronoxyd.de/content/images/2022/03/Inverse-square-law-1.svg" alt="3D printed diffusers" loading="lazy"></p>
<p>So with that, I decided to try both approaches.</p>
<h2 id="designs">Designs</h2>
<p>In the following, I present my designs. The images of the outcome were taken indoors, both with the room light on and off. The settings for the camera were as follows:</p>
<table>
<thead>
<tr>
<th>&#xA0;</th>
<th>&#xA0;</th>
</tr>
</thead>
<tbody>
<tr>
<td>Camera maker</td>
<td>Sony</td>
</tr>
<tr>
<td>Camera model</td>
<td>ILCE-7M3</td>
</tr>
<tr>
<td>ISO speed</td>
<td>ISO-1000</td>
</tr>
<tr>
<td>F-stop</td>
<td>f/4.5</td>
</tr>
<tr>
<td>Exposure time</td>
<td>1/100 sec.</td>
</tr>
<tr>
<td>Exposure bias</td>
<td>0 step</td>
</tr>
<tr>
<td>Exposure program</td>
<td>Shutter priority</td>
</tr>
<tr>
<td>Metering mode</td>
<td>Pattern</td>
</tr>
<tr>
<td>Flash mode</td>
<td>No flash, compulsory</td>
</tr>
<tr>
<td>Focal length</td>
<td>50 mm</td>
</tr>
<tr>
<td>Lens maker</td>
<td>Sony</td>
</tr>
<tr>
<td>Lens model</td>
<td>SEL2870</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="v6">V6</h3>
<p>Reminiscent of a V-engine, this design aims to diffuse through both pointing the light source not directly at the screen as well as using generous amounts of plastic.</p>
<table>
<thead>
<tr>
<th><img src="https://cronoxyd.de/content/images/2022/03/85C1-V6-diffusor-model--ISO-.png" alt="3D printed diffusers" loading="lazy"></th>
<th><img src="https://cronoxyd.de/content/images/2022/03/85C1-V6-diffusor.jpg" alt="3D printed diffusers" loading="lazy"></th>
</tr>
</thead>
</table>
<p><strong>Rating:</strong> Very bad, because of the strong attenuation the falloff is severe.</p>
<!--kg-card-end: markdown-->
        <div class="kg-card kg-file-card kg-file-card-medium">
            <a class="kg-file-card-container" href="https://cronoxyd.de/content/files/2022/03/85C1-V6-diffuser.skp" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">85C1 V6 diffuser</div>
                    
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">85C1 V6 diffuser.skp</div>
                        <div class="kg-file-card-filesize">170 KB</div>
                    </div>
                </div>
                <div class="kg-file-card-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
                </div>
            </a>
        </div>
        <!--kg-card-begin: markdown--><h3 id="single-edge-sidelight">Single-edge sidelight</h3>
<p>Inspired by the <a href="https://en.wikipedia.org/wiki/Sony_Watchman">Sony Watchman</a>s CRT design, this design aims to both reflect and diffuse the source of light coming from the side.</p>
<table>
<thead>
<tr>
<th><img src="https://cronoxyd.de/content/images/2022/03/85C1-single-edge-sidelight-diffusor-model--ISO-.png" alt="3D printed diffusers" loading="lazy"></th>
<th><img src="https://cronoxyd.de/content/images/2022/03/85C1-single-edge-sidelight-diffusor.jpg" alt="3D printed diffusers" loading="lazy"></th>
</tr>
</thead>
</table>
<p><strong>Rating:</strong> Too much of a radial gradient, with light pooling at the edge. But the reach of the light is good, making it the opposite edge.</p>
<!--kg-card-end: markdown-->
        <div class="kg-card kg-file-card kg-file-card-medium">
            <a class="kg-file-card-container" href="https://cronoxyd.de/content/files/2022/03/85C1-single-edge-sidelight-diffuser.skp" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">85C1 single edge sidelight diffuser</div>
                    
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">85C1 single-edge sidelight diffuser.skp</div>
                        <div class="kg-file-card-filesize">166 KB</div>
                    </div>
                </div>
                <div class="kg-file-card-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
                </div>
            </a>
        </div>
        <!--kg-card-begin: markdown--><h3 id="sidelight">Sidelight</h3>
<p>A simple arrangement, with the source of light directly firing at the edge of the screen.</p>
<table>
<thead>
<tr>
<th><img src="https://cronoxyd.de/content/images/2022/03/85C1-sidelight-diffusor-model--ISO-.png" alt="3D printed diffusers" loading="lazy"></th>
<th><img src="https://cronoxyd.de/content/images/2022/03/85C1-sidelight-diffusor.jpg" alt="3D printed diffusers" loading="lazy"></th>
</tr>
</thead>
</table>
<p><strong>Rating:</strong> Falloff too heavy, light sources are very visible.</p>
<!--kg-card-end: markdown-->
        <div class="kg-card kg-file-card kg-file-card-medium">
            <a class="kg-file-card-container" href="https://cronoxyd.de/content/files/2022/03/85C1-sidelight-diffuser.skp" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">85C1 sidelight diffuser</div>
                    
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">85C1 sidelight diffuser.skp</div>
                        <div class="kg-file-card-filesize">160 KB</div>
                    </div>
                </div>
                <div class="kg-file-card-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
                </div>
            </a>
        </div>
        <!--kg-card-begin: markdown--><h3 id="double-sidelight">Double sidelight</h3>
<p>With an inner and outer diffuser, this design is meant to achieve diffusion through refraction.</p>
<table>
<thead>
<tr>
<th><img src="https://cronoxyd.de/content/images/2022/03/85C1-double-sidelight-diffusor-model--ISO-.png" alt="3D printed diffusers" loading="lazy"></th>
<th><img src="https://cronoxyd.de/content/images/2022/03/85C1-double-sidelight-diffusor.jpg" alt="3D printed diffusers" loading="lazy"></th>
</tr>
</thead>
</table>
<p><strong>Rating:</strong> Nice spread and milder falloff, but too much of a radial gradient.</p>
<!--kg-card-end: markdown-->
        <div class="kg-card kg-file-card kg-file-card-medium">
            <a class="kg-file-card-container" href="https://cronoxyd.de/content/files/2022/03/85C1-double-sidelight-diffuser.skp" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">85C1 double sidelight diffuser</div>
                    
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">85C1 double sidelight diffuser.skp</div>
                        <div class="kg-file-card-filesize">187 KB</div>
                    </div>
                </div>
                <div class="kg-file-card-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
                </div>
            </a>
        </div>
        <!--kg-card-begin: markdown--><h3 id="offset-sidelight">Offset sidelight</h3>
<p>Maximizing the size of the inner diffuser, this design is an &quot;amplified&quot; version of the <a href="#double-sidelight">double sidelight</a> version.</p>
<table>
<thead>
<tr>
<th><img src="https://cronoxyd.de/content/images/2022/03/85C1-offset-sidelight-diffusor-model--ISO-.png" alt="3D printed diffusers" loading="lazy"></th>
<th><img src="https://cronoxyd.de/content/images/2022/03/85C1-offset-sidelight-diffusor.jpg" alt="3D printed diffusers" loading="lazy"></th>
</tr>
</thead>
</table>
<p><strong>Rating:</strong> Best diffuser so far with a minimal radial gradient and even light distribution. But where the intensity drops off abruptly in the middle where the arch touches the faceplate.</p>
<!--kg-card-end: markdown-->
        <div class="kg-card kg-file-card kg-file-card-medium">
            <a class="kg-file-card-container" href="https://cronoxyd.de/content/files/2022/03/85C1-offset-sidelight-diffuser.skp" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">85C1 offset sidelight diffuser</div>
                    
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">85C1 offset sidelight diffuser.skp</div>
                        <div class="kg-file-card-filesize">202 KB</div>
                    </div>
                </div>
                <div class="kg-file-card-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
                </div>
            </a>
        </div>
        <!--kg-card-begin: markdown--><h2 id="conclusion">Conclusion</h2>
<ol>
<li>Using solid plastic as the diffuser is not feasible and induces the inverse square law too much.</li>
<li>Diffusion through refraction works well and reduces the usage of plastic.</li>
<li>Any solids through which light travels should not be thick enough that infill is necessary because the patterns are very visible (this is past knowledge and I  have avoided this in the designs presented above).</li>
</ol>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Using reflection to recursively read properties]]></title><description><![CDATA[<p>I&apos;m currently working on code that needs to retrieve the values of certain properties in an object recursively, without knowing the type of the object. So using reflection is required.</p><p>For demonstration, I&apos;m using these two classes:</p><pre><code class="language-csharp">public class TestItem
{
    public string Name { get; set; } = string.</code></pre>]]></description><link>https://cronoxyd.de/using-reflection-to-recursively-read-properties/</link><guid isPermaLink="false">61f5802d4cec640001305b6f</guid><category><![CDATA[Coding]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Sat, 29 Jan 2022 18:12:10 GMT</pubDate><content:encoded><![CDATA[<p>I&apos;m currently working on code that needs to retrieve the values of certain properties in an object recursively, without knowing the type of the object. So using reflection is required.</p><p>For demonstration, I&apos;m using these two classes:</p><pre><code class="language-csharp">public class TestItem
{
    public string Name { get; set; } = string.Empty;
}

public class Test
{
    public string Name { get; set; } = string.Empty;
    public List&lt;TestItem&gt; Items { get; set; } = new();
}</code></pre><p>And create instances of them:</p><pre><code class="language-csharp">var testInstance = new Test();
testInstance.Name = &quot;Camera&quot;;

testInstance.Items.Add(new() { Name = &quot;Microphone jack&quot; });
testInstance.Items.Add(new() { Name = &quot;Hotshoe&quot; });
testInstance.Items.Add(new() { Name = &quot;Bayonet&quot; });</code></pre><p>Finally, here is the method that recursively retrieves the values of the <code>Name</code> property:</p><pre><code class="language-csharp">using System.Diagnostics;
using System.Reflection;

public string[] GetNameValues(object obj, string propertyName)
{
    List&lt;string&gt; retVal = new();
    var objProperties = obj.GetType().GetProperties();

    foreach (var prop in objProperties)
    {
        var propVal = prop.GetValue(obj);
        if (propVal == null) {
            Debug.WriteLine($&quot;Skipping property \&quot;{prop.Name}\&quot; with null value&quot;);
        }

        if (prop.Name == &quot;Name&quot;) {
            Debug.WriteLine($&quot;Found property named \&quot;{propertyName}\&quot;&quot;);
            retVal.Add(propVal.ToString());
        } else if (typeof(string) != prop.PropertyType &amp;&amp; typeof(IEnumerable).IsAssignableFrom(prop.PropertyType)) {
            Debug.WriteLine($&quot;Property \&quot;{prop.Name}\&quot; is enumerable&quot;);
            
            foreach (var item in (IEnumerable)propVal)
            {
                retVal.AddRange(GetNameValues(item, propertyName));
            }
        }
    }

    return retVal.ToArray();
}</code></pre><p>Calling the above method yields the following return:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th><em>index</em></th>
<th>value</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Camera</td>
</tr>
<tr>
<td>1</td>
<td>Microphone jack</td>
</tr>
<tr>
<td>2</td>
<td>Hotshoe</td>
</tr>
<tr>
<td>3</td>
<td>Bayonet</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>And writes the following to the debug console:</p><pre><code class="language-text">Found property named &quot;Name&quot;
Property &quot;Items&quot; is enumerable
Found property named &quot;Name&quot;
Found property named &quot;Name&quot;
Found property named &quot;Name&quot;</code></pre>
        <div class="kg-card kg-file-card kg-file-card-medium">
            <a class="kg-file-card-container" href="https://cronoxyd.de/content/files/2022/01/ReflectionIEnumerable-2.ipynb" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">ReflectionIEnumerable</div>
                    
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">ReflectionIEnumerable.ipynb</div>
                        <div class="kg-file-card-filesize">3 KB</div>
                    </div>
                </div>
                <div class="kg-file-card-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
                </div>
            </a>
        </div>
        ]]></content:encoded></item><item><title><![CDATA[Syncing application settings using symbolic links]]></title><description><![CDATA[<p>Modern software now starts including features to sync settings (such as tool preferences) to a server, often the one from the software&apos;s vendor. This is pretty neat as it allows for better continuity when working across devices. But the majority of software still doesn&apos;t include this</p>]]></description><link>https://cronoxyd.de/syncing-application-settings-using-symbolic-links/</link><guid isPermaLink="false">61689fcac082010001e1a4a1</guid><category><![CDATA[Tips]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Thu, 14 Oct 2021 21:42:32 GMT</pubDate><media:content url="https://cronoxyd.de/content/images/2021/10/01-Syncing-application-settings-using-symbolic-links.png" medium="image"/><content:encoded><![CDATA[<img src="https://cronoxyd.de/content/images/2021/10/01-Syncing-application-settings-using-symbolic-links.png" alt="Syncing application settings using symbolic links"><p>Modern software now starts including features to sync settings (such as tool preferences) to a server, often the one from the software&apos;s vendor. This is pretty neat as it allows for better continuity when working across devices. But the majority of software still doesn&apos;t include this functionality. This is probably due to the fact that this is a continuous service that requires upkeep, maintenance and investment.<br>In this article, I will detail a method of implementing this synchronization of settings that worked quite well for me. To use this method for yourself you will need some cloud storage with local synchronization capabilities such as Microsoft OneDrive, Google Drive or Nextcloud. It is also advisable that the synchronization agent is running at system startup. You also need an operating system that supports symbolic links. In the case of Windows, you need an installation that runs on build 14972 or newer.<br>The following is a step-by-step manual on how to implement this method, explained by doing so for the software EPOS Gaming Suite, which is the configuration interface for my headset on Windows.</p><h2 id="step-1-finding-the-setting-files-of-the-target-software">Step 1: Finding the setting files of the target software</h2><p>There are 3 locations in which most software saves its settings and configuration files (on Windows):</p><ul><li><code>AppData</code> (which can be navigated to by hitting <code>Win</code>+<code>R</code>, entering <code>%APPDATA%</code> and then hitting Enter)</li><li><code>ProgramData</code> (which can be found under the path <code>C:\ProgramData</code>)</li><li><code>Documents</code> (which would contain a folder named after the application or the application&apos;s vendor)</li></ul><p>Some applications (especially old ones) save their settings in the Registry, in which case the implementation of this method would require a serialization of those registry entries, a hook to trigger it and then the reverse for remote changes.</p><p>For the EPOS Gaming Suite, the setting files are located in <code>C:\ProgramData\EPOS\Gaming Suite</code>. I chose to only sync the files in the subfolder <code>CUSTOMPRESET</code> because there were other directories with files that indicated being driver-specific. This choice also demonstrates the common sense and technical inclination you need to apply to successfully implement this method. Try to identify and exclude files that are machine-specific (such as driver configuration files) otherwise you may render the software unusable. Also try to identify the need to include a whole directory or just a couple of files. Here are a few things to look out for:</p><ul><li>A directory of files that indicate that more files may be created during runtime (such as <code>file0.xml</code>, <code>file1.xml</code>, <code>file2.xml</code> etc.).</li><li>Files that may contain info specific to that machine (such as `recentprojects.xml` or `driverconfig.ini`).</li><li>License files that could cause the software installation on a different machine to try and use the same license, causing a licensing conflict.</li></ul><h2 id="step-2-backup-up-the-original-files">Step 2: Backup up the original files</h2><p>Create a backup of the original files you located in step 1, just in case.</p><h2 id="step-3-setting-up-your-cloud-file-structure">Step 3: Setting up your cloud file structure</h2><p>This is of course up to you, but I chose to create a folder named <code>AppData</code> directly in the OneDrive folder. Then I activated the option <em>Always keep on this device</em> in the context menu for the entire folder. That is optional but good practice as it forces OneDrive to always keep the files up-to-date and to not delete the local copies during automatic cleanup.</p><h2 id="step-4-copying-the-original-setting-files-to-the-cloud-and-deleting-the-originals">Step 4: Copying the original setting files to the cloud and deleting the originals</h2><p>Firstly, make sure that the software you&apos;re doing this for isn&apos;t running (don&apos;t forget to check for running services or tray icons as well).Then copy the setting files you located earlier into a subfolder of the <code>AppData</code> folder. I like to create a folder with the software&apos;s name and then either place the files into that folder or create another subfolder named after the original subfolder and place the files there.</p><p>So in case of the EPOS Gaming suite the original path of the files is <code>C:\ProgramData\EPOS\Gaming Suite\CUSTOMPRESET</code> and the path in my OneDrive is <code>C:\Users\crono\OneDrive\AppData\EPOS Gaming Suite\CUSTOMPRESET</code>.</p><p>Now you can delete the original folder (or file(s)) to prepare for the symbolic link. For the EPOS Gaming Suite I only deleted the <code>CUSTOMPRESET</code> folder and left the other files alone.</p><h2 id="step-5-creating-the-symbolic-link">Step 5: Creating the symbolic link</h2><p>Now it is time to make the software think that nothing changed. This is achieved using symbolic links.</p><p>Depending on your choices from step 1 (whether to target the entire directory, a sub-directory or individual files), you will need to create one or several symbolic links to recreate the exact file structure.</p><p>For the EPOS Gaming Suite I invoked the following PowerShell command in the <code>C:\ProgramData\EPOS\Gaming Suite</code> directory:</p><pre><code class="language-powershell">New-Item -ItemType SymbolicLink -Name &quot;CUSTOMPRESET&quot; -Value &quot;C:\Users\crono\OneDrive\AppData\EPOS Gaming Suite\CUSTOMPRESET&quot;</code></pre><p>The <a href="https://docs.microsoft.com/powershell/module/microsoft.powershell.management/new-item"><code>New-Item</code></a> cmdlet automatically detects whether the specified <code>-Value</code> is a directory or a file.<br>In case you want to stick to CLI tools, you can use the <a href="https://docs.microsoft.com/windows-server/administration/windows-commands/mklink"><code>mklink</code></a> utility but you need to specify that it is a directory using the <code>/D</code> switch:</p><pre><code class="language-text">mklink /D &quot;CUSTOMPRESET&quot; &quot;C:\Users\crono\OneDrive\AppData\EPOS Gaming Suite\CUSTOMPRESET&quot;</code></pre><h2 id="step-6-testing-it-out">Step 6: Testing it out</h2><p>After creating the symbolic links, double check the directory structure. Then, start the software and check if your settings are still as before. Next, make a change to your settings and save. If OneDrive starts syncing one of the files in the <code>AppData</code> directory, the upload works! To fully test the implementation, do the same process on another machine and check if the settings of the applications remain in sync.</p><h2 id="limitations-and-final-words">Limitations and final words</h2><p>It is important to remember that this method is (most likely) not something the developer of the software thought about in their implementation. It is a bodge and may break at any time. You may need to update the symbolic links or the directory structure in the future and you may experience crashes and data loss if something goes wrong. Here is a list of things to look out for:</p><ul><li>Updates of the software. This can not only pose a problem because the software may change the format of its setting files or the directory structure of its user-specific data but it may also delete and recreate the folder structure during an update (which will also wipe out your symbolic links).</li><li>Concurrency conflicts. If you have implemented this on multiple machines on which you then run the software at the same time you may run into the problem that the one instance overwrites the data from the other. Remember that the content on screen is not necessarily the data on disk.</li><li>Working offline or with a slow connection. Living in rural Germany, a slow internet connection is sadly still widespread (yes, even in 2021). Therefore, the sync agent may not have finished syncing recent changes when you launch the software. So you could run into a situation where you launch your software while the agent is still syncing the settings files and then the old version gets read by the software. Unaware of this situation you make changes to some unrelated settings and upon saving, the software overwrites the settings file with your local changes, wiping out the remote changes. Remember that Windows also automatically pauses syncing when the battery is low.</li><li>Software running at system start. If, like my headset configuration interface, the software runs at system start, then the chance that the sync agent hasn&apos;t finished syncing the settings files dramatically increases. That wasn&apos;t really an issue with my headset software because I don&apos;t change the presets that often and if I notice a missing or old one, I&apos;ll simply restart the software.</li></ul><p>As you can see, this method is quite involved when setting it up and cannot be guaranteed to run steadily. But I implemented this on my computers and I have been quite happy with the results. My headset presets as well as my development settings and swatches in the Affinity suite are available on all my computers, which has been very neat.</p>]]></content:encoded></item><item><title><![CDATA[The UnoPermanente: Arrival and lessons learned]]></title><description><![CDATA[<p>About 2 weeks ago, the PCBs arrived. They look really good and the silkscreen is nice and sharp. Total damage was 24,45&#x20AC;, not including any of the components. I have to say, this already feels a lot more formal than an empty perf board.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2021/09/2021-06-02_21-18-46.jpg" class="kg-image" alt loading="lazy" width="2000" height="1500" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/2021-06-02_21-18-46.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/2021-06-02_21-18-46.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/2021-06-02_21-18-46.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2021/09/2021-06-02_21-18-46.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>The UnoPermanente PCB</figcaption></figure><p>I</p>]]></description><link>https://cronoxyd.de/the-unopermanente-arrival-and-lessons-learned/</link><guid isPermaLink="false">614750ab175cfa0001d21312</guid><category><![CDATA[Embedded]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Sat, 12 Jun 2021 15:10:00 GMT</pubDate><media:content url="https://cronoxyd.de/content/images/2021/09/2021-06-02_20-53-27-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://cronoxyd.de/content/images/2021/09/2021-06-02_20-53-27-1.jpg" alt="The UnoPermanente: Arrival and lessons learned"><p>About 2 weeks ago, the PCBs arrived. They look really good and the silkscreen is nice and sharp. Total damage was 24,45&#x20AC;, not including any of the components. I have to say, this already feels a lot more formal than an empty perf board.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2021/09/2021-06-02_21-18-46.jpg" class="kg-image" alt="The UnoPermanente: Arrival and lessons learned" loading="lazy" width="2000" height="1500" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/2021-06-02_21-18-46.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/2021-06-02_21-18-46.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/2021-06-02_21-18-46.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2021/09/2021-06-02_21-18-46.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>The UnoPermanente PCB</figcaption></figure><p>I ordered these PCBs with the full knowledge that I&#x2019;d make some mistakes and sought to learn from them. So here are the flaws in my design which I aim to fix in the next version:</p><!--kg-card-begin: markdown--><h2 id="power-supply-screw-terminal">Power supply screw terminal</h2>
<p>
    <img src="https://cronoxyd.de/content/images/2021/09/2021-06-02_20-57-47-edited.jpg" style="float: right; width: 50%; margin: 1rem;" alt="The UnoPermanente: Arrival and lessons learned">
The screw terminal for the power connection had much wider pin spacing than anticipated. Luckily, the pads were large enough to drill an additional hole and then make the connection using a solder bridge.
</p><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="incoming-ground-connection">Incoming ground connection</h2>
<p>
    <img src="https://cronoxyd.de/content/images/2021/09/2021-06-02_21-11-29-scaled.jpg" style="width: 50%; margin: 1rem; float: right;" alt="The UnoPermanente: Arrival and lessons learned">
    I failed to connect the ground of the incoming power supply to the rest of the circuit, rendering the whole PCB without power. A quite unnecessary mistake as it could have been avoided with a connection trace by hand of the final design. Still easy to fix with a little wire.
</p><p>In this picture you can also see the hole I had to drill for the screw terminal (top left).</p>
<p></p><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="reset-button-problems">Reset button problems</h2>
<p>
    <img src="https://cronoxyd.de/content/images/2021/09/2021-06-12_14-26-39-edited-1-930x620.jpg" style="width: 50%; float: right; margin: 1rem;" alt="The UnoPermanente: Arrival and lessons learned">
    This problem has nothing to do with the PCB but rather the socket for the ATmega that I soldered to it. During my initial testing, the reset button was only working sporadically and I spent quite some time before I figured out the problem: It was the spring contact in the socket. Notice the much larger gap of the last pin on the right compared to the one on the left in the photo. I reused this socket from another project and apparently damaged it when removing it from the old PCB.
</p><p>It was difficult to track down since, whenever I touched the probe of my multimeter to the pin of the ATmega, I&#x2019;d push it inwards slightly and it would make contact and work perfectly fine.</p>
<p></p><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="no-matching-enclosure">No matching enclosure</h2>
<p>
    <img src="https://cronoxyd.de/content/images/2021/09/EmptyBox.png" style="width: 50%; float: right; margin: 1rem;" alt="The UnoPermanente: Arrival and lessons learned">
    I honestly thought I designed the PCB around an enclosure, but apparently I didn&#x2019;t.
</p><!--kg-card-end: markdown--><p>Other than these problems, I could easily populate the PCB with the components. When I was soldering I already got the feeling I sought after for this project: Now, when I want to move a circuit to a permanent PCB, I no longer have to tediously solder all the bootstrap components and I could instead focus on the actual circuit I want to build around the ATmega.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2021/09/2021-06-02_20-53-27.jpg" class="kg-image" alt="The UnoPermanente: Arrival and lessons learned" loading="lazy" width="2000" height="1500" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/2021-06-02_20-53-27.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/2021-06-02_20-53-27.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/2021-06-02_20-53-27.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2021/09/2021-06-02_20-53-27.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>The UnoPermanente, fully populated.</figcaption></figure><h2 id="summary">Summary</h2><p>The flaws I described above can be summed up to the following guidelines for future PCB design:</p><ol><li>Measure all components: Using calipers or a ruler, measure the dimensions and pins of all components to confirm the compatibility with the design. This is especially true for components whose footprint is larger than the outermost leads (such as screw terminals).</li><li>Do a full signal trace of the design: Follow all traces and ensure that the signals (such as power or data) are present at the required connection points.</li><li>Print it out: Before submitting a design for production, print it out and lay the components on their respective place. Also cut it out and place it inside the intended enclosure (if applicable).</li></ol><h2 id="whats-next">What&apos;s next</h2><p>Even though there were some flaws in my first design I reckon I could&#x2019;ve done a lot worse. After fixing the problems, the PCB worked and I&#x2019;m overall happy with the result. I&#x2019;m not going to implement a circuit with this batch (even though I have one in mind) since I failed to design it for an enclosure. In my mind, if a circuit is worth soldering for permanent use, it also deserves an enclosure. So I&#x2019;ve selected an enclosure which I believe provides adequate space for small projects, the <a href="https://www.reichelt.de/at/de/kleingehaeuse-72-x-50-x-28-mm-geh-ks-28-p8160.html?&amp;nbc=1">Kemo Electronic GEH KS 28</a>. I selected this enclosure because it would allow for a <a href="https://cronoxyd.de/piggyback-pcb-sizing">piggyback PCB arrangement</a>. I already have a PCB designed for it, but I&#x2019;ll save all that for the next post.</p>]]></content:encoded></item><item><title><![CDATA[Fulree FW32D12A3]]></title><description><![CDATA[<p>The Fulree FW32D12A3 is a DC-DC converter that can convert from a range of input voltages to a stable 12V output. It can be found on eBay using the query &#x201C;12V3A stabilizer&#x201D; and purchased from multiple vendors.</p><p>The vendors often provide very sparse and superficial information, so I&</p>]]></description><link>https://cronoxyd.de/fulree-fw32d12a3/</link><guid isPermaLink="false">61473e80175cfa0001d212d8</guid><category><![CDATA[Embedded]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Mon, 31 May 2021 13:49:00 GMT</pubDate><media:content url="https://cronoxyd.de/content/images/2021/09/Fulree-FW32D12A3-demo-cropped.gif" medium="image"/><content:encoded><![CDATA[<img src="https://cronoxyd.de/content/images/2021/09/Fulree-FW32D12A3-demo-cropped.gif" alt="Fulree FW32D12A3"><p>The Fulree FW32D12A3 is a DC-DC converter that can convert from a range of input voltages to a stable 12V output. It can be found on eBay using the query &#x201C;12V3A stabilizer&#x201D; and purchased from multiple vendors.</p><p>The vendors often provide very sparse and superficial information, so I&#x2019;ll try to fill in the gaps. I&#x2019;ve used this converter successfully to stabilize an unstable 12V power supply so that a voltage-sensitive device could run off it.</p><h2 id="characteristics">Characteristics</h2><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th></th>
<th>Parameter</th>
<th>MIN</th>
<th>TYP</th>
<th>MAX</th>
<th>UNIT</th>
</tr>
</thead>
<tbody>
<tr>
<td>V<sub>I</sub></td>
<td>Input voltage</td>
<td>5</td>
<td></td>
<td>32</td>
<td>V</td>
</tr>
<tr>
<td>V<sub>O</sub></td>
<td>Output voltage</td>
<td></td>
<td>12</td>
<td></td>
<td>V</td>
</tr>
<tr>
<td>I<sub>cont</sub></td>
<td>Continous output current</td>
<td></td>
<td></td>
<td>3</td>
<td>A</td>
</tr>
<tr>
<td>V<sub>n</sub></td>
<td>Output noise voltage</td>
<td></td>
<td>10</td>
<td></td>
<td>kHz</td>
</tr>
<tr>
<td>&#x394;T<sub>A</sub> @ 3A</td>
<td>Temperature above ambient</td>
<td></td>
<td>32</td>
<td></td>
<td>&#xB0;C</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><h2 id="typical-performance">Typical performance</h2><figure class="kg-card kg-image-card"><img src="https://cronoxyd.de/content/images/2021/09/Fulree-FW32D12A3-voltage-power.png" class="kg-image" alt="Fulree FW32D12A3" loading="lazy" width="2000" height="1308" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/Fulree-FW32D12A3-voltage-power.png 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/Fulree-FW32D12A3-voltage-power.png 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/Fulree-FW32D12A3-voltage-power.png 1600w, https://cronoxyd.de/content/images/2021/09/Fulree-FW32D12A3-voltage-power.png 2000w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://cronoxyd.de/content/images/2021/09/Fulree-FW32D12A3-current-power.png" class="kg-image" alt="Fulree FW32D12A3" loading="lazy" width="2000" height="1301" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/Fulree-FW32D12A3-current-power.png 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/Fulree-FW32D12A3-current-power.png 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/Fulree-FW32D12A3-current-power.png 1600w, https://cronoxyd.de/content/images/2021/09/Fulree-FW32D12A3-current-power.png 2000w" sizes="(min-width: 720px) 720px"></figure><h2 id="noise">Noise</h2><p>The output of the power conditioner has irregular electrical noise of about 10 kHz with an amplitude of 1.82 V on its output.</p><figure class="kg-card kg-image-card"><img src="https://cronoxyd.de/content/images/2021/09/2021-04-10_17-31-17-01.jpeg" class="kg-image" alt="Fulree FW32D12A3" loading="lazy" width="2000" height="1500" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/2021-04-10_17-31-17-01.jpeg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/2021-04-10_17-31-17-01.jpeg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/2021-04-10_17-31-17-01.jpeg 1600w, https://cronoxyd.de/content/images/2021/09/2021-04-10_17-31-17-01.jpeg 2010w" sizes="(min-width: 720px) 720px"></figure><h2 id="usage-example">Usage example</h2><p>I came to this device because my new infotainment system in my car crashed every time I started the engine. That happens because the voltage drops to about 9 V when cranking the engine. So I put the Fulree inline with the constant power from the battery. To avoid cutting into the wiring of my car I ordered a ISO 10487 extension that I spliced the Fulree into.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2021/09/2021-04-18_18-47-40.jpg" class="kg-image" alt="Fulree FW32D12A3" loading="lazy" width="2000" height="1500" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/2021-04-18_18-47-40.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/2021-04-18_18-47-40.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/2021-04-18_18-47-40.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2021/09/2021-04-18_18-47-40.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Fulree on the power supply of the infotainment</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[The UnoPermanente]]></title><description><![CDATA[<p>I&#x2019;ve been tinkering with Arduino stuff for quite a while now and sometimes I like to promote the project from the breadboard to the solder stage. However, that always meant getting some perfboard and before I could solder the electronics of the project, I&#x2019;d first need</p>]]></description><link>https://cronoxyd.de/the-unopermanente/</link><guid isPermaLink="false">61473dc4175cfa0001d212c7</guid><category><![CDATA[Embedded]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Sat, 22 May 2021 13:40:00 GMT</pubDate><media:content url="https://cronoxyd.de/content/images/2021/09/UnoPermanente_pcb_inverted-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://cronoxyd.de/content/images/2021/09/UnoPermanente_pcb_inverted-1.png" alt="The UnoPermanente"><p>I&#x2019;ve been tinkering with Arduino stuff for quite a while now and sometimes I like to promote the project from the breadboard to the solder stage. However, that always meant getting some perfboard and before I could solder the electronics of the project, I&#x2019;d first need to boilerplate the PCB by soldering an ATmega328P circuit. So I took this opportunity to finally learn PCB design and I implemented a boilerplate PCB that already has the ATmega328P onboard with perfboard around it.</p><p>After trying out some PCB design software, I settled on <a href="https://fritzing.org/">Fritzing</a>. While it does have its quirks I find Fritzing to give just the right balance of simplicity and control, especially for a beginner. I designed the PCB straightaway in the PCB view and this is the design I came up with:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2021/09/UnoPermanente_pcb_inverted.png" class="kg-image" alt="The UnoPermanente" loading="lazy" width="696" height="657" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/UnoPermanente_pcb_inverted.png 600w, https://cronoxyd.de/content/images/2021/09/UnoPermanente_pcb_inverted.png 696w"><figcaption>UnoPermanente PCB (v20210522)</figcaption></figure><p>There is an LED connected to Pin 13 and a reset button. The empty corners are for mounting holes you can drill for your specific case.</p><p>I then used the suggested PCB manufacturer, <a href="https://aisler.net/">Aisler</a>, to order 3 PCBs. I&#x2019;ll update this post once they arrive and I&#x2019;ve soldered something on it. If this pilot is successful, I might extend it into a Permanente series with different processors and use cases. Also, you can check the project file out on <a href="https://github.com/cronoxyd/UnoPermanente">GitHub</a>.</p>]]></content:encoded></item><item><title><![CDATA[Piggyback PCB sizing]]></title><description><![CDATA[Often, when I’m soldering a project I use a piggyback PCB solution to help save space inside enclosures. I made some graphics to help with choosing the right enclosures.]]></description><link>https://cronoxyd.de/piggyback-pcb-sizing/</link><guid isPermaLink="false">61472e2b175cfa0001d21298</guid><category><![CDATA[Embedded]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Sat, 22 May 2021 12:39:00 GMT</pubDate><media:content url="https://cronoxyd.de/content/images/2021/09/piggyback_pcb_feature.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://cronoxyd.de/content/images/2021/09/piggyback_pcb_feature.jpg" alt="Piggyback PCB sizing"><p>Often, when I&#x2019;m soldering a project I use a piggyback PCB solution to help save space inside enclosures. I made the following graphics to help with choosing the right enclosures, maybe this helps with your project too:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2021/09/Piggyback-PCB-variant-1-1-1.svg" class="kg-image" alt="Piggyback PCB sizing" loading="lazy" width="81" height="150"><figcaption>Variant 1</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2021/09/Piggyback-PCB-variant-2-1.svg" class="kg-image" alt="Piggyback PCB sizing" loading="lazy" width="88" height="150"><figcaption>Variant 2</figcaption></figure><p>Variant 2 is for when soldering on perfboard where you have to push the plastic spacer of the male pin header all the way to the end in order to be able to solder on the side with the copper plating.</p><p>The following is an example assembly with an LCD, a push button, an LED and a brass standoff:</p><figure class="kg-card kg-image-card"><img src="https://cronoxyd.de/content/images/2021/09/Piggyback-PCB-example-assy.svg" class="kg-image" alt="Piggyback PCB sizing" loading="lazy" width="77" height="150"></figure>]]></content:encoded></item><item><title><![CDATA[Winter Drive]]></title><description><![CDATA[<p>This was one of the rare days where it was actually snowing consistently instead of going back and forth between drizzle and snow. I went out in the evening and took these pictures. The atmosphere was wonderful; only a few cars were driving and it was completely silent otherwise.</p><figure class="kg-card kg-image-card"><img src="https://cronoxyd.de/content/images/2021/09/DSC09474-2048x1368-1.jpg" class="kg-image" alt loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/DSC09474-2048x1368-1.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/DSC09474-2048x1368-1.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/DSC09474-2048x1368-1.jpg 1600w, https://cronoxyd.de/content/images/2021/09/DSC09474-2048x1368-1.jpg 2048w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://cronoxyd.de/content/images/2021/09/Crosswalk-Full-2048x1368-1.jpg" class="kg-image" alt loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/Crosswalk-Full-2048x1368-1.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/Crosswalk-Full-2048x1368-1.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/Crosswalk-Full-2048x1368-1.jpg 1600w, https://cronoxyd.de/content/images/2021/09/Crosswalk-Full-2048x1368-1.jpg 2048w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://cronoxyd.de/content/images/2021/09/Crosswalk-Lights-2048x1368-1.jpg" class="kg-image" alt loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/Crosswalk-Lights-2048x1368-1.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/Crosswalk-Lights-2048x1368-1.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/Crosswalk-Lights-2048x1368-1.jpg 1600w, https://cronoxyd.de/content/images/2021/09/Crosswalk-Lights-2048x1368-1.jpg 2048w" sizes="(min-width: 720px) 720px"></figure>]]></description><link>https://cronoxyd.de/winter-drive/</link><guid isPermaLink="false">614753eb175cfa0001d21362</guid><category><![CDATA[Photography]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Thu, 14 Jan 2021 16:14:00 GMT</pubDate><media:content url="https://cronoxyd.de/content/images/2021/09/untitled.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://cronoxyd.de/content/images/2021/09/untitled.jpg" alt="Winter Drive"><p>This was one of the rare days where it was actually snowing consistently instead of going back and forth between drizzle and snow. I went out in the evening and took these pictures. The atmosphere was wonderful; only a few cars were driving and it was completely silent otherwise.</p><figure class="kg-card kg-image-card"><img src="https://cronoxyd.de/content/images/2021/09/DSC09474-2048x1368-1.jpg" class="kg-image" alt="Winter Drive" loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/DSC09474-2048x1368-1.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/DSC09474-2048x1368-1.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/DSC09474-2048x1368-1.jpg 1600w, https://cronoxyd.de/content/images/2021/09/DSC09474-2048x1368-1.jpg 2048w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://cronoxyd.de/content/images/2021/09/Crosswalk-Full-2048x1368-1.jpg" class="kg-image" alt="Winter Drive" loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/Crosswalk-Full-2048x1368-1.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/Crosswalk-Full-2048x1368-1.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/Crosswalk-Full-2048x1368-1.jpg 1600w, https://cronoxyd.de/content/images/2021/09/Crosswalk-Full-2048x1368-1.jpg 2048w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://cronoxyd.de/content/images/2021/09/Crosswalk-Lights-2048x1368-1.jpg" class="kg-image" alt="Winter Drive" loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/Crosswalk-Lights-2048x1368-1.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/Crosswalk-Lights-2048x1368-1.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/Crosswalk-Lights-2048x1368-1.jpg 1600w, https://cronoxyd.de/content/images/2021/09/Crosswalk-Lights-2048x1368-1.jpg 2048w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[Voltmeter clock]]></title><description><![CDATA[<p>I saw this project quite a while ago and I just had to build it myself: a desk clock using analog voltmeters to display the time! The build ended up being pretty straightforward; the voltmeters are DC 5V meters that I got on eBay with an Arduino driving them. They</p>]]></description><link>https://cronoxyd.de/voltmeter-clock/</link><guid isPermaLink="false">6147560f175cfa0001d21382</guid><category><![CDATA[Embedded]]></category><dc:creator><![CDATA[Marcel Diskowski]]></dc:creator><pubDate>Tue, 07 Apr 2020 18:32:00 GMT</pubDate><media:content url="https://cronoxyd.de/content/images/2021/09/DCF77_SoftFall-1.gif" medium="image"/><content:encoded><![CDATA[<img src="https://cronoxyd.de/content/images/2021/09/DCF77_SoftFall-1.gif" alt="Voltmeter clock"><p>I saw this project quite a while ago and I just had to build it myself: a desk clock using analog voltmeters to display the time! The build ended up being pretty straightforward; the voltmeters are DC 5V meters that I got on eBay with an Arduino driving them. They respond perfectly to PWM when using a 10 &#xB5;F capacitor to smooth out the signal.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2021/09/DSC_0215_cropped.jpg" class="kg-image" alt="Voltmeter clock" loading="lazy" width="2000" height="2361" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/DSC_0215_cropped.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/DSC_0215_cropped.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/DSC_0215_cropped.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2021/09/DSC_0215_cropped.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Fully populated PCB of the Voltmeter clock</figcaption></figure><p>I got to thinking though when I put together the bill of materials that I didn&apos;t want to have a clock that I would need to set all the time. So I decided to use the <a href="https://wikipedia.org/wiki/DCF77">DCF77</a> radio signal, which has been used to set all sorts of clocks since the early 70s. Thankfully, there are ready-made modules and also Arduino libraries so integrating it into the project was very easy.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2021/09/DCF77.jpg" class="kg-image" alt="Voltmeter clock" loading="lazy" width="2000" height="1336" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/DCF77.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/DCF77.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/DCF77.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2021/09/DCF77.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>The finished clock</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cronoxyd.de/content/images/2021/09/Voltmeter-Clock-Iso-Drawing.jpg" class="kg-image" alt="Voltmeter clock" loading="lazy" width="2000" height="2000" srcset="https://cronoxyd.de/content/images/size/w600/2021/09/Voltmeter-Clock-Iso-Drawing.jpg 600w, https://cronoxyd.de/content/images/size/w1000/2021/09/Voltmeter-Clock-Iso-Drawing.jpg 1000w, https://cronoxyd.de/content/images/size/w1600/2021/09/Voltmeter-Clock-Iso-Drawing.jpg 1600w, https://cronoxyd.de/content/images/size/w2400/2021/09/Voltmeter-Clock-Iso-Drawing.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>An Iso perspective drawing of the clock</figcaption></figure><!--kg-card-begin: markdown--><h2 id="code">Code</h2>
<p><a href="https://github.com/cronoxyd/VoltmeterClock" style="text-align: center; display: block;"><img src="https://gh-card.dev/repos/cronoxyd/VoltmeterClock.svg?fullname=" alt="Voltmeter clock"></a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>