<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Hamhire Tech Tips</title>
    <link>https://hamhire.tech/en/index.html</link>
    <description>Recent content on Hamhire Tech Tips</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-US</language>
    <copyright>Licensed under CC BY-NC-ND 4.0</copyright>
    <lastBuildDate>Sat, 07 Nov 2026 20:00:00 +0800</lastBuildDate>
    
        <atom:link href="https://hamhire.tech/en/index.xml" rel="self" type="application/rss+xml" />
    
    
    
    
    
        <item>
        <title>Nixie Indicator, Behind the Scenes — Making a Presentation Video</title>
        <link>https://hamhire.tech/en/posts/projects/nixie-indicator-presentation.html</link>
        <pubDate>Sat, 07 Nov 2026 20:00:00 +0800</pubDate>
        
        <guid>https://hamhire.tech/en/posts/projects/nixie-indicator-presentation.html</guid>
        <description>Hamhire Tech Tips https://hamhire.tech/en/posts/projects/nixie-indicator-presentation.html -&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As the hardware and software took shape, I wanted material to share on social media and video platforms. The project had already accumulated a set of 3D models. They provided an opportunity to try producing a polished presentation video and introduction images for the device.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This article walks through refining the models, synthesizing music, and creating animation with an agent. Here is the finished landscape-format video.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;videoblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;video src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/nixie-indicator-demo-1080p.mp4&#34; width=&#34;960&#34; poster=&#34;/posts/projects/images/nixie-indicator/nixie-indicator-demo-poster.jpg&#34; controls=&#34;&#34;&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_modeling&#34;&gt;Modeling&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The engineering models provided a useful starting point. I imported the two PCBs, enclosure, and fasteners from a STEP assembly into Blender, unified units and coordinates, and retained the relationships between parts. I could then compose shots of the complete device or isolate boards and enclosure parts for exploded views.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/enclosure-assembly.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/enclosure-assembly.jpg&#34; alt=&#34;Quarter-section assembly and interior views of the upper and lower shells&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 1. Existing engineering models, including an assembly section and both enclosure halves&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Models intended to verify dimensions and positions still needed another refinement pass for close-up shots. Surfaces, edges, and internal structures became much more apparent through the camera. I began with the tube interiors, then worked through PCB materials and other parts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_refining_the_nixie_tubes&#34;&gt;Refining the Nixie Tubes&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The tubes were the focus of the device’s close-ups, and I wanted to reproduce their internal structure as closely as possible. The official datasheet offers limited dimensions, mainly the overall outline, character size, and pin positions. It provides too little information to reconstruct the electrodes, mesh, and supports directly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview image-compact&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-dgm01-dimensions.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-dgm01-dimensions.jpg&#34; alt=&#34;Official DGM01 outline and pin-dimension drawing&#34; width=&#34;618&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 2. Tube outline and pin dimensions in the DGM01 datasheet&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Drawing source is page 3 of the &lt;a href=&#34;https://github.com/Sadudu/DGM01/blob/e749a54683d2fc35f26a48ac460406e522a0e515/DGM01%20datasheet_EN.pdf&#34; rel=&#34;noopener&#34; target=&#34;_blank&#34;&gt;iNixie Lab DGM01 datasheet&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I asked the agent to compare photographs of the actual tube and consult material on other tubes, including the IN-18, to understand the structure. My own observations supplied further corrections. The digits occupy different planes, so viewing angle changes how the electrodes, mesh, and supports occlude one another. The ten digit shapes, their depth order, and their mounting gradually approached the present result through repeated adjustments.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Some details have little impact at a distance but matter in close-up. The main vertical stroke of &lt;code&gt;1&lt;/code&gt; is thicker than the other strokes, and &lt;code&gt;5&lt;/code&gt; has distinctive bends and openings. A generic font struggled to reproduce that character. I eventually had the curves fitted to photographs, then refined the metal clips, insulating supports, mesh cage, and internal leads.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The glass needed separate attention. Alongside the cylindrical outline, I added inner and outer walls, a curved top, and the narrowed base so that its edges could reflect and refract light. At close range, those details and the internal metalwork together determine the tube’s sense of thickness.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;The glow comes from excited gas around the electrodes and has a soft, mist-like appearance.&lt;/strong&gt; I therefore asked the agent to model the metal electrodes and gas discharge separately. The metal stays non-emissive, while a separate volumetric emission layer around each character controls its glow.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Glow and glass highlights had to be judged together. Excessively bright reflections obscured digits and mesh. A very dark image lost the glass curvature. Soft lighting from the side and rear outlined the tube while keeping the orange-red digits prominent.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-tube-modeling.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-tube-modeling.jpg&#34; alt=&#34;Solid Blender view with glass and glow hidden on the left and the digit 3 rendered from the same camera on the right&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 3. Tube-interior modeling and the illuminated result&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_pcbs_and_other_components&#34;&gt;PCBs and Other Components&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The board outlines and component positions were available already, but materials required several iterations. Plastic packages, metal leads, connectors, and the board surface each needed different reflectivity and roughness for convincing close-up detail.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The PCB surface also needed more than a flat black plane. Using the actual design files, I had the agent reproduce subtle relief from traces and copper areas, add silkscreen, and refine the surface appearance of the solder mask and copper regions. Changes in lighting and camera angle then made that relief visible.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;The initial models lacked solder joints, making the components look as if they had simply been placed on the boards.&lt;/strong&gt; I separated the pads and solder, added joint shapes around SMD leads and through-hole connections, and adjusted the silver-gray reflections. Joining the components visually to the board made the assembly much more convincing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-pcb-materials.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-pcb-materials.jpg&#34; alt=&#34;Control-board and display-board close-ups showing materials, surface relief, and solder joints&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 4. Traces, silkscreen, and solder joints on the two boards&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I refined other parts similarly, adding a matte grain to the enclosure, knurling and chamfers to the knob, and separate appearances for the LCD’s dark reflective surface and active display. Materials continued to evolve with the lighting in individual scenes.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_rhythm_and_storyboarding&#34;&gt;Rhythm and Storyboarding&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For pacing, I drew on a workflow shared by He Tongxue in a YouTube Short. Starting with a rough visual concept, I established the musical style and overall rhythm before refining shots and scenes. A video only a few seconds long has limited room, so duration, changes in pace, and transitions needed early decisions. As shots developed, I adjusted the music again.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Set the musical rhythm.&lt;/strong&gt; Establish a beat and place accents for illumination, expansion, and reassembly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Plan the shots.&lt;/strong&gt; Allocate time between close-ups and wider views, choose cameras and movement, and refine the musical timing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Preview with untextured models.&lt;/strong&gt; Check pace, framing, and part motion.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Test-render key frames.&lt;/strong&gt; Use final materials and lighting on representative frames to refine details.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Render the sequence.&lt;/strong&gt; Export the full frame sequence and combine it with the soundtrack.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_synthesizing_the_music&#34;&gt;Synthesizing the Music&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Before creating music, I had a rough set of shots in mind, including the glowing tubes, PCB close-ups, and an exploded assembly. I compared four soundtrack styles, chose one, and continued refining the shots around it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The agent wrote scripts to generate audio waveforms directly. &lt;strong&gt;Sound can be represented as a waveform over time, and simple electronic timbres can be constructed programmatically.&lt;/strong&gt; Frequency controls pitch, envelopes shape how a sound starts and fades, and noise, filters, and multiple voices can be combined into a rhythmic track.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I settled on a mechanical electronic style at 150 BPM. The script generated drums, bass, short plucked tones, and metallic percussion, then mixed them on a common timeline. As the animation developed, I adjusted their placement. A digit lighting up gets a short cue. The exploded view reduces rhythmic density. Reassembly builds momentum, with a brief pause before the final accent as the complete device lights up.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/presentation-music-tracks.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/presentation-music-tracks.en.svg&#34; alt=&#34;Seven synthesized voices and their mixed waveform aligned with the close-up, expansion, reassembly, and illumination phases&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 5. Soundtrack voices and the mixed waveform&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For a short piece with a clearly defined sound, &lt;strong&gt;scripted synthesis is inexpensive and highly controllable&lt;/strong&gt;. Moving a cue to the instant a part settles or extending a background layer takes a change to timing and envelopes. Waveform generation, mixing, and export happen locally, allowing the soundtrack to evolve alongside the storyboard.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_designing_the_shots&#34;&gt;Designing the Shots&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The storyboard turns the overall rhythm into individual shots, deciding cuts, slower movements, holds, and actions that coincide with musical accents. I then worked out the content of each scene.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tube close-ups&lt;/strong&gt; open with the internal layers, glass, and glow.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PCB close-ups&lt;/strong&gt; move gently across components to show the two boards&amp;#39; details.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Expansion and reassembly&lt;/strong&gt; reveal the enclosure and board structure, then return to a complete, illuminated device.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Landscape and portrait versions needed separate camera arrangements. A landscape exploded view can spread parts sideways, while a portrait composition needs more vertical layers. Close-ups also require repositioning the subject because cropping can remove the very detail the shot is meant to show.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/presentation-storyboard.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/presentation-storyboard.en.svg&#34; alt=&#34;Vertical storyboard timeline with shot descriptions and actual rendered frames&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 6. Shot sequence and pacing for the landscape video&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_scene_design&#34;&gt;Scene Design&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_the_exploded_assembly&#34;&gt;The Exploded Assembly&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;An exploded view reveals structure normally hidden by the enclosure. Shells, boards, the front screen, and fasteners need enough separation to be readable while retaining a clear relationship to their assembled positions. I kept the complete assembly as the starting point and chose expansion directions and distances around the camera.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A spacious static layout still needs a feasible animation path. The upper shell’s two tube openings require it to rise along the tube axes before moving sideways. Screws behind the front board need to travel along their mounting directions, with timing offset slightly from the board’s return. Reflections and shadows can obscure those relationships in a final render, making an untextured preview easier to inspect.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I used those previews to check for obvious intersections, whether the upper shell stayed in frame, and whether each part remained legible at full expansion. Once the motion worked, I restored the finished materials. The sequence expands continuously, pauses briefly, and reassembles, giving the viewer time to understand the structure.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-exploded-modeling.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-exploded-modeling.jpg&#34; alt=&#34;Fully expanded assembly in Blender on the left and the same frame rendered with final materials on the right&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 7. Untextured exploded view and the matching final render&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_the_complete_device_and_lighting&#34;&gt;The Complete Device and Lighting&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The closing shot returns to the complete device. Both tubes, the front screen, and the right-hand knob need to be visible, and the black enclosure needs separation from its surroundings. I wanted a dark image in which outlines and surface changes describe the form, with the brightest areas reserved for the glowing digits and screen.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The enclosure uses the matte grain of black PA12S, and the knob has knurling and beveled edges. The screen retains a reflective black surface while off and shows the quota interface when the assembled device lights up. Those material differences give the lights distinct surfaces to reveal.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Lighting changes with expansion and reassembly. At full expansion, the foreground lights reach their set brightness so the interior is visible. Background lightboxes then fade, followed by the wall and floor during reassembly, leaving the finished device against black. Local tube lighting follows the tubes as they move, keeping the glass within its illumination.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;The black device needs a clear outline while the glow retains its color.&lt;/strong&gt; Increasing the brightness of the entire scene makes glass and metal compete for attention. I focused on where light fell and whether reassembly, illumination, and the fading background formed a coherent sequence.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-lighting.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-lighting.jpg&#34; alt=&#34;Blender view of the device, lights, and camera on the left, with the final device showing 26 on the right&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 8. Scene lighting and the illuminated device&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_rendering_and_output&#34;&gt;Rendering and Output&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Before final rendering, I checked timing and motion with an untextured animation. Comparing several frames from expansion, the hold, and reassembly made it easier to see whether the shell cleared the tubes, parts stayed in frame, and the return sequence worked.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-white-animation.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-white-animation.jpg&#34; alt=&#34;Six preview frames showing the complete device, expansion, full separation, and reassembly&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 9. Untextured animation preview, read left to right and top to bottom&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Next came &lt;strong&gt;key-frame test renders&lt;/strong&gt;. I selected representative frames from each scene and rendered them with the intended lighting and materials. Glass reflections, glow color, PCB details, and dark enclosure surfaces all needed inspection. A material can look different as the camera or lights move, so I adjusted it against multiple frames before committing to the complete sequence.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Close-ups emphasize glass and metal, while exploded and whole-device shots must handle more surfaces and darker regions. I used scene-specific sampling and denoising, rendered full frame sequences locally on the GPU with Cycles, and combined them with the music.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Color handling also mattered. Exposure and display transforms influence the saturation of the orange-red glow. The rendered PNGs had already undergone display mapping, so the editing project needed consistent handling to avoid applying another display transform and changing color or contrast.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-material-keyframes.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/presentation-material-keyframes.jpg&#34; alt=&#34;Illuminated digit 2 and tube glass on the left, with enclosure grain, screen, and knurled knob on the right&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 10. Final material close-ups of the tubes, enclosure, and knob&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I also used the scenes to produce introduction images for platforms such as Xiaohongshu. Reframing the whole device, selected details, and internal structure for image layouts, with short explanations, provided another way to browse and share the project.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_reflections&#34;&gt;Reflections&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;What stood out was &lt;strong&gt;how smoothly AI participated across the process, and GPT’s current spatial understanding&lt;/strong&gt;. Refining tube interiors, arranging expansion paths, moving cameras, and positioning lights could all progress through descriptions, previews, and further adjustments.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My role centered on &lt;strong&gt;creative direction and visual judgment&lt;/strong&gt;. The character of the image, details worth emphasizing, shot duration, and balance between reflections and glow all needed decisions. The agent quickly turned those requirements into results I could watch and revise. Repeated comparison and selection brought the image closer to what I had in mind.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
- https://hamhire.tech/en/posts/projects/nixie-indicator-presentation.html - Licensed under CC BY-NC-ND 4.0</description>
        </item>
    
    
    
        <item>
        <title>Nixie Indicator, Part 2 — Giving the Glowing Digits Useful Data</title>
        <link>https://hamhire.tech/en/posts/projects/nixie-indicator-software.html</link>
        <pubDate>Fri, 06 Nov 2026 20:00:00 +0800</pubDate>
        
        <guid>https://hamhire.tech/en/posts/projects/nixie-indicator-software.html</guid>
        <description>Hamhire Tech Tips https://hamhire.tech/en/posts/projects/nixie-indicator-software.html -&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Once both tubes lit up, the device had the appearance I wanted. Turning a pair of glowing digits into something I would glance at every day still required software. The original idea of putting AI to work on its own quota would only come together when the allowance on my computer actually appeared inside the tubes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This article covers the firmware and desktop companion, including data sources, the meaning of a two-digit display, and how a small screen and one knob bring configuration and everyday use together.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-opening.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-opening.en.svg&#34; alt=&#34;Simplified desktop quota card and device model displaying the same remaining allowance of 26 percent&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 1. The same quota on the computer and on the desk, shown schematically&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_the_data_path&#34;&gt;The Data Path&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Initially, I wanted the device to speak the ChatGPT desktop application’s native protocol and retrieve quota information itself. For more conventional monitoring, I also wanted Prometheus queries. Once connected to the network, the device could obtain data from either service.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The first attempt proved less convenient than expected. With its default startup configuration, the application only listened on the loopback interface, beyond the reach of an ESP32 on the LAN. The native protocol also introduced long-term uncertainty. Reflashing the device whenever an integration changed would be cumbersome.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I added a desktop companion to decouple collection from display. It calls the application-specific protocol locally, handles connections and queries, and exposes the results as standardized Metrics over the LAN. Native-protocol connections and account-related operations stay on the computer. The ESP32 retrieves configured metrics without understanding each source’s internal protocol.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Changes to an upstream protocol can then be handled in the client’s collector while retaining the metric interface. The device needs no firmware update for those adaptations. CPU load, memory usage, temperature, and game frame rate can use the same path. The device still handles retrieval, validity, and display, with those responsibilities separated from any particular application.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The final design supports two ways to connect.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Direct Metrics access.&lt;/strong&gt; Read values from the desktop companion or another exporter, suitable for displaying current values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prometheus.&lt;/strong&gt; Query an existing monitoring service for historical trends and server-side calculations as well as current values.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;The desktop companion is one data source, and the device remains a general-purpose metric display.&lt;/strong&gt; An existing monitoring service can supply it independently. Direct access also works without deploying Prometheus.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-data-flow.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-data-flow.en.svg&#34; alt=&#34;Desktop collection reaches the device through direct Metrics access or Prometheus and is displayed by the tubes and LCD&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 2. A metric’s journey through direct access or Prometheus&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_firmware_design_and_implementation&#34;&gt;Firmware Design and Implementation&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_choosing_the_framework&#34;&gt;Choosing the Framework&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As in the hardware work, Codex and I used &lt;strong&gt;incremental, spec-driven development&lt;/strong&gt;. We documented responsibilities, interfaces, and constraints before implementing and validating each stage, and recorded subsequent changes in the design documents. The basic stack consists of the following components.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PlatformIO and ESP-IDF&lt;/strong&gt; organize the project, build firmware, and provide networking and peripheral support.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LVGL and esp_lvgl_port&lt;/strong&gt; handle widgets, menus, and screen updates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;esp_lcd and SPI DMA&lt;/strong&gt; connect rendering to the physical LCD.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;NVS&lt;/strong&gt; stores network settings, metric rules, and display preferences. Live values and temporary state stay in memory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Wokwi&lt;/strong&gt; provides firmware simulation for early display and input checks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I had mainly used Arduino in earlier projects. AI substantially reduced the work of establishing and maintaining the project framework, making it easier to switch to &lt;strong&gt;ESP-IDF&lt;/strong&gt; and gain finer control over tasks, peripherals, and low-level configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Firmware tasks run on the &lt;strong&gt;FreeRTOS&lt;/strong&gt; integrated into ESP-IDF. I organized the architecture around &lt;strong&gt;separate domains&lt;/strong&gt;, first defining responsibilities, state ownership, and public interfaces, then arranging their interactions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Networking&lt;/strong&gt; collects data and supports remote access and management.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Device control&lt;/strong&gt; accepts values and requests and determines hardware output centrally.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;User interaction&lt;/strong&gt; displays metrics and device state and forwards user actions to the control side.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Explicit interfaces keep changes focused when adding pages or integrating data sources.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-firmware-architecture.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-firmware-architecture.en.svg&#34; alt=&#34;Interaction entry points, application coordination, business domains, and underlying support&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 3. Firmware domains and their supporting layers&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I covered the main logic with unit tests, used simulation to check interaction and the data path, and moved to hardware testing once the end-to-end flow broadly worked.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_gui_and_menu_interaction&#34;&gt;GUI and Menu Interaction&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The first firmware design questions concerned the GUI and system menu. What should be visible every day, where should settings live, and how should navigation work? I used LVGL to draw the LCD interface and organized pages around those usage scenarios.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;LVGL handles interface composition and rendering.&lt;/strong&gt; The main metric, trend chart, and system menu are separate views. Menu rows, focus outlines, and status icons reuse common components so that visual changes and interaction feedback remain consistent.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Between the GUI and the screen, &lt;code&gt;esp_lvgl_port&lt;/code&gt; and &lt;code&gt;esp_lcd&lt;/code&gt; coordinate rendering with &lt;strong&gt;SPI DMA pixel transfers&lt;/strong&gt;. LVGL produces regions to refresh, the driver submits transfers, and completion signals let the interface reuse the buffers. Coordinating drawing and transmission became an important part of the screen implementation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The knob handles everyday metric selection, brightness adjustment, and self-tests. Requests go through a common control layer, and values retrieved over the network use the same output path.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-menu-flow.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-menu-flow.en.svg&#34; alt=&#34;Interface illustrations showing a long press to enter the menu, rotation to select, and a short press to confirm&#34; width=&#34;1180&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 4. Browsing metrics, entering the menu, and selecting a primary metric&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;During acceptance checks, I considered whether the interaction matched familiar devices, alongside layout, information hierarchy, and appearance, then refined it through use.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout callout-warning media-aside&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Character coverage and language support&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;openblock media-aside-copy&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Alongside Chinese and English, we added Japanese and German. An embedded display needs its glyphs packaged in the firmware, so translating strings covers only part of the work. Noto Sans fonts were converted into LVGL font data. Japanese requires the appropriate glyphs, while long German words require layout checks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Custom metric titles need their own character coverage. We extracted characters from core, general, and industry-specific Chinese and Japanese metric vocabularies, then added letters, numbers, units, and required symbols.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Fonts occupy a substantial share of the firmware&lt;/strong&gt;, especially when Chinese and Japanese glyphs are combined with multiple sizes. Fixed UI strings use subsets of the characters actually needed. Custom titles draw on the vocabulary lists. Large sizes retain only digits and essential symbols, and some Chinese and Japanese fonts use lossless compression. Character coverage, type size, and readability need to be balanced to leave room for other features and updates.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock media-aside-image image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/software-menu-ja.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/software-menu-ja.svg&#34; alt=&#34;System menu in Japanese&#34; width=&#34;280&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;The Japanese menu&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_controlling_the_tubes&#34;&gt;Controlling the Tubes&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://hamhire.tech/en/posts/projects/nixie-indicator-hardware.html&#34;&gt;Part 1&lt;/a&gt; described the mapping between digits and physical tube pins. Firmware uses that mapping to generate serial data on a dedicated SPI bus, while PWM on BLANK controls brightness. The LCD uses a separate SPI bus so that large screen transfers do not occupy the tube-update bus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Metric values can remain unchanged for long periods. The firmware therefore cycles through the cathodes once an hour and also provides display rest and a digit-by-digit self-test.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Tube maintenance and cathode poisoning&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Each digit is a separate cathode. During discharge, ion bombardment causes &lt;strong&gt;sputtering and material loss&lt;/strong&gt; on the active cathode. Released material can deposit on other cathodes. When only a few digits are used for long periods, deposits on less frequently lit cathodes can interfere with discharge and produce partially unlit digits or incomplete strokes, a condition known as &lt;strong&gt;cathode poisoning&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Periodically lighting every digit is a common preventive measure and the reason for the hourly cycling routine.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.daliborfarny.com/v2/en/puri-nixie-clock/nixie-tube-health&#34; rel=&#34;noopener&#34; target=&#34;_blank&#34;&gt;Dalibor Farny’s tube-maintenance documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The tubes show a two-digit integer from 00 to 99. The LCD supplies the complete value, name, unit, and status. Fractional values are converted to integers, and out-of-range values are limited to the two-digit display range. The original value remains on the LCD.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-value-display.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-value-display.en.svg&#34; alt=&#34;Leading zeros, fractional values, out-of-range values, and missing-data states&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 5. Value handling on the tubes and LCD&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Failure to retrieve data must remain distinct from a zero value.&lt;/strong&gt; For quota display, a temporarily unreachable service and an exhausted allowance require different states. An old value may be retained within the rule’s allowed interval, but its status must show that it has become stale.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_wi_fi_setup&#34;&gt;Wi-Fi Setup&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Provisioning uses a conventional &lt;strong&gt;SoftAP flow&lt;/strong&gt;. The device opens a temporary hotspot, a phone connects and submits home Wi-Fi credentials through a web page, and the device joins the target network. The LCD provides the setup entry point and status.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-wifi-flow.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-wifi-flow.en.svg&#34; alt=&#34;Hotspot creation, phone setup, network connection, saved configuration, and phone reconnection&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 6. Moving from the temporary hotspot to home Wi-Fi&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;After a successful connection, the device saves the configuration and stops the hotspot. The phone rejoins the same Wi-Fi and opens the management page at the device’s new address. Failed attempts can be retried after correcting the settings. Routine disconnections trigger automatic reconnection, and changing networks starts provisioning again from the menu.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-wifi-setup.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-wifi-setup.en.svg&#34; alt=&#34;Setup QR code, Wi-Fi form, and success page, with example network names and addresses&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 7. Connecting to the hotspot, entering Wi-Fi credentials, and completing setup&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_firmware_updates&#34;&gt;Firmware Updates&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With network access available, I included OTA updates so that new firmware could be uploaded through a browser or the desktop app. The current flow selects a local file and uploads it over the LAN. USB flashing remains available for maintenance.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The design uses &lt;strong&gt;two application slots&lt;/strong&gt; in flash. One runs the current firmware and the other receives the new image. The current version stays intact while the alternate slot is written and validated. Only then does the boot target change. An interrupted transfer or failed validation leaves the original version available.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;On its first boot, the new firmware checks basic functions such as storage, control tasks, and the GUI before confirming the version. A failed trial boot uses the rollback mechanism to return to the valid older image. After confirmation, the slots exchange roles and the next update writes to the other slot. Reserving room for two images adds a practical constraint to the font-size budget discussed earlier.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-firmware-update.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-firmware-update.en.svg&#34; alt=&#34;Write and validate the alternate slot, trial boot the image, confirm success or roll back&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 8. Updating and rolling back with two application slots&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Possible OTA extensions&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The desktop companion could later check releases, show release notes, and download matching firmware before passing it to the existing upload flow. The device could also fetch firmware from an update service, with signature verification and stable or testing channels. Those remain future extensions. The current focus is LAN updates.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_remote_management_and_the_desktop_companion&#34;&gt;Remote Management and the Desktop Companion&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_web_management&#34;&gt;Web Management&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Once on the LAN, the device provides a web interface for settings that are awkward to edit on a small screen. Page assets ship with the firmware and are accessible through the device address. A browser authorization request must be approved on the device before management access is granted.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The interface handles display preferences, data sources, firmware uploads, and PromQL configuration with result previews. A common management API carries the requests, while the device owns the configuration rules. The desktop companion reuses that API to keep the two entry points consistent.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The metric page places settings beside the preview. PromQL, display name, unit, and chart style are configured on the left. The current value and historical trend appear on the right. Editing a query and checking its result together makes it easier to verify that the intended information is being collected.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-web-metrics.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-web-metrics.en.svg&#34; alt=&#34;PromQL and display settings beside the current value and historical chart&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 9. Metric configuration and query preview in the browser&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_desktop_implementation&#34;&gt;Desktop Implementation&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The companion focuses on &lt;strong&gt;collector configuration and quick device management&lt;/strong&gt;, using &lt;strong&gt;Tauri 2, Rust, and TypeScript&lt;/strong&gt;. The UI handles configuration and inspection. A Rust backend collects data, serves Metrics, and connects to the device. Collection runs separately from the window, allowing the tray application to continue working after the window closes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Each source appears as a separate card, with its enable switch, collection state, and current values.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;System information&lt;/strong&gt; covers CPU and memory usage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hardware monitoring&lt;/strong&gt; covers GPU load, graphics memory, and available temperature sensors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;FPS collection&lt;/strong&gt; follows the foreground application’s frame rate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Codex observation&lt;/strong&gt;, currently labeled Beta, retrieves local quota information.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Returning to the original purpose, the Codex collector reads the quota on the computer and exports a remaining percentage for the returned quota window. The device consumes an ordinary metric. Application-protocol and account-related handling remain in the client. The displayed value is the remaining proportion of a quota window, rather than an exact token-by-token balance.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;After pairing, the app lets me choose metrics from the local results, reorder them, select the primary metric, preview retrieval, and open device settings or firmware updates. Local collection settings and device settings are stored separately, with both available from the client.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-desktop.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/software-desktop.en.svg&#34; alt=&#34;Enable collectors and inspect values on the left, then choose and order displayed metrics on the right&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 10. Collector configuration and quick device management&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Adding data sources&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A new source can have an independent collector module that feeds the common scheduler and Metrics endpoint. Protocol adaptations stay within that module while reusing the device’s existing display path.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The device can independently connect to other Metrics services or Prometheus for different hardware and monitoring scenarios.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_party_mode&#34;&gt;Party Mode&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The tubes can also be a small desktop toy. I added a “party mode” to the companion with simple random-number, countdown, and manual-value controls.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Digital dice.&lt;/strong&gt; Set the range to 1–6 and watch the numbers change rapidly, slow down, and stop on the result. Other ranges work for drawing lots or guessing games.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Timed challenges.&lt;/strong&gt; A glowing countdown adds a little pressure to a board game or short challenge.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Score display.&lt;/strong&gt; Submit a number manually to show the current score.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The modes share the same number-sequence playback capability. The client prepares the numbers and timing, and the device plays them. Leaving party mode restores the regular metric display. The device gains a way for friends to participate beyond simply glancing at a value.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/software-party-scene.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/software-party-scene.jpg&#34; alt=&#34;Illustrated party scene with the device showing 06 beside dice and board-game pieces&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 11. Bringing the glowing digits to the game table&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_reflections&#34;&gt;Reflections&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Software engineering is a capability LLMs have been improving for years. In this project, AI could handle a fairly complete implementation across the firmware and desktop app. It made me consider where a designer’s attention should go as the cost of writing code falls.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Human involvement increasingly centers on design decisions and engineering standards.&lt;/strong&gt; The split between client and device, domain boundaries, and ownership of state all affect future maintenance. The DRY principle also matters. Shared business rules need a common implementation so that similar features do not gradually diverge. Naming, interfaces, error handling, and testing need consistent requirements through each iteration and acceptance check.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;That is the value of documenting boundaries and agreements first, implementing incrementally, and recording validated changes. &lt;strong&gt;As code generation becomes easier, explicit rules become more important for engineering consistency.&lt;/strong&gt; Adding feature after feature without that discipline can accumulate locally functional implementations and defer the maintenance cost.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Low-level SDK adaptation, toolchain configuration, and environment setup can now also be delegated to the agent. Trying an unfamiliar framework costs less effort, allowing choices to follow the project’s needs more closely. My move from Arduino to ESP-IDF reflects that change. The saved effort can go toward architectural trade-offs, interaction, and acceptance testing, making it more feasible for one person to maintain firmware, a web interface, and a desktop companion together.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The rhythm of hobby development has changed as well. Previously, a project could consume my full attention throughout my spare time, from the idea to detailed implementation. Now I spend more time considering options, expressing requirements, and checking results. While the agent writes code or runs tests, I can watch a video and return when it has something to discuss. The project still needs sustained attention, but using fragments of spare time has become easier.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/software-agent-workflow.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/software-agent-workflow.jpg&#34; alt=&#34;Concept illustration of a designer watching a video while following the agent’s implementation and test progress&#34; width=&#34;1320&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 12. From design decisions to agent execution&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The &lt;a href=&#34;https://hamhire.tech/en/posts/projects/nixie-indicator-presentation.html&#34;&gt;companion article&lt;/a&gt; covers turning the hardware project into a polished presentation video and producing introduction images.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
- https://hamhire.tech/en/posts/projects/nixie-indicator-software.html - Licensed under CC BY-NC-ND 4.0</description>
        </item>
    
    
    
        <item>
        <title>Nixie Indicator, Part 1 — Turning an Idea into Hardware with AI</title>
        <link>https://hamhire.tech/en/posts/projects/nixie-indicator-hardware.html</link>
        <pubDate>Thu, 05 Nov 2026 20:00:00 +0800</pubDate>
        
        <guid>https://hamhire.tech/en/posts/projects/nixie-indicator-hardware.html</guid>
        <description>Hamhire Tech Tips https://hamhire.tech/en/posts/projects/nixie-indicator-hardware.html -&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;It had been a while since I last built something new. I recently remembered the Nixie displays in &lt;em&gt;Metro 2033&lt;/em&gt;, which I played years ago. Their Cold War-era, Soviet clock-shop aesthetic left a lasting impression. The glowing digits inside glass tubes have a distinctive character, and I had wanted to make a Nixie device of my own ever since.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A series of Codex usage-limit resets gave me an opportunity to start. The PCB design demonstration released with GPT 6 Astra in September 2026 had also stuck with me. I decided to try building a small desktop device that would use Nixie tubes to display my remaining token allowance, and see how far AI could take a hardware idea toward a physical object. Spending spare AI quota to build a quota display seemed fitting, putting AI to work on its own allowance.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Before starting, I set one &lt;strong&gt;rule for the project&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;I would make every change to the design files by describing what I wanted to the agent.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As the human in the loop, I would define requirements, choose between options, inspect the results, and validate the assembled hardware. Feedback from each stage would guide the next round.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I called the project &lt;em&gt;Nixie Indicator&lt;/em&gt;. This first article covers the circuits, PCB, enclosure, and hardware bring-up. Firmware, the desktop companion, and the presentation video follow in the next two articles.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;videoblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;video src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/nixie-indicator-demo-1080p.mp4&#34; width=&#34;960&#34; poster=&#34;/posts/projects/images/nixie-indicator/nixie-indicator-demo-poster.jpg&#34; controls=&#34;&#34;&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_choosing_the_design&#34;&gt;Choosing the Design&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Codex and I worked incrementally, using &lt;strong&gt;spec-driven development&lt;/strong&gt;. I started with rough notes on the idea and candidate approaches, then wrote a project overview and roadmap covering the goals, modules, and order of work. We refined the design in stages, recording agreed interfaces and constraints in formal design documents. Updating those documents alongside the implementation gradually gave the project an organized engineering context.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;To help the agent understand my development and project-management habits, I also gave it my earlier custom keyboard and Techno Keyer projects as references. &lt;strong&gt;Providing existing projects was an important way to shape its context.&lt;/strong&gt; Concrete examples communicated my preferred organization and design conventions and reduced repeated explanations.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The main components gradually settled into place.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Nixie tubes.&lt;/strong&gt; Two DGM01 tubes provide the two-digit display.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MCU.&lt;/strong&gt; An ESP32-S3, which I had used in several other projects, retrieves data over Wi-Fi.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LCD.&lt;/strong&gt; A small screen provides metric names, units, status, and settings. The idea came from the CWO networked Morse-code desk device I had bought from its maker, Xifengzui.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;USB-C.&lt;/strong&gt; One connector handles both 5 V power and firmware flashing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rotary encoder.&lt;/strong&gt; A side-mounted knob handles selection and confirmation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I chose DGM01 tubes after watching Sadudu revive Nixie-tube manufacturing in China by making them himself. I admired the work and wanted to use his tubes in my own project.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://www.bilibili.com/video/BV14G4y1S7ou/&#34;&gt;Sadudu’s Nixie-tube manufacturing video&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I split the main electronics into two stacked boards for three reasons.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Separate the high- and low-voltage areas.&lt;/strong&gt; The display board contains the high-voltage supply and Nixie drivers. The control board holds the MCU, USB interface, and low-voltage supply.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reduce the desktop footprint.&lt;/strong&gt; Stacking the boards saves horizontal space.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Allow future variants.&lt;/strong&gt; A different display board could accommodate tubes such as the Soviet IN-18 while reusing as much of the control board as possible.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The upper display board carries the two tubes. Two sets of headers connect it to the lower control board for low-voltage power and control signals. The LCD sits vertically on a separate front board.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/pcb-boards-exploded.jpg&#34; alt=&#34;Exploded view of the display board, control board, and LCD front board&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 1. Exploded board assembly, with the display board above the control and LCD boards&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_circuit_design&#34;&gt;Circuit Design&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_power_supply&#34;&gt;Power Supply&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I settled on a single 5 V USB-C input. One branch feeds an AMS1117-3.3 regulator for the MCU, display logic, and LCD. The other passes through a controlled switch to an off-the-shelf high-voltage module on the display board.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The high-voltage module is the XGGF-HV-tiny from Acorn Workshop, a supplier recommended by Sadudu. I designed the downstream circuits around its nominal output of approximately 170 V, concentrating on the interface, enable control, and mounting space. Startup behavior and operation with both tubes connected would be checked on the actual hardware.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The low-voltage input switch, module enable, and display blanking are controlled separately. Startup follows this sequence.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;Keep the outputs blanked while preparing the display data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switch on the low-voltage input branch, then enable the high-voltage module.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Release blanking to display the digits.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The boards share a common ground. Their physical separation provides no electrical isolation. I included a separate discharge path for the high-voltage supply and enclosed the pins and solder joints. Residual voltage can remain after the display goes dark and needs to be measured separately.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/power-schematics/usb-power.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/power-schematics/usb-power.jpg&#34; alt=&#34;Complete control-board USB and power schematic&#34; width=&#34;960&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 2. Control-board power schematic, including USB input, 3.3 V regulation, and the high-voltage module input switch&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_driving_the_nixie_tubes&#34;&gt;Driving the Nixie Tubes&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The two tubes have twenty digit cathodes in total. Three 74HC595 shift registers expand the outputs, and twenty MMBTA42 high-voltage transistors switch the individual cathodes. Each tube has its own anode current-limiting resistor.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The ESP32 sends serial data and latches the outputs together. Four of the twenty-four available shift-register outputs are unused. Normal operation selects one digit per tube.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Brightness is controlled by PWM on the shared blanking signal. The final frequency was chosen by comparing results on the assembled board.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/nixie-control-timing.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/nixie-indicator/nixie-control-timing.en.svg&#34; alt=&#34;Serial control of three 74HC595 registers and shared blanking for brightness control&#34; width=&#34;1000&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 3. Serial data, latching, and PWM brightness control, shown schematically&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_pcb_design&#34;&gt;PCB Design&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I started with component placement, establishing functional areas, positions, and assembly relationships before routing. Getting the overall arrangement right made the connections easier to plan and reduced the need to move large groups of components during routing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_component_placement&#34;&gt;Component Placement&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The first layouts quickly showed that functional requirements and connectivity were insufficient on their own. The agent’s initial placement was fairly arbitrary. I had to specify grid alignment, functional grouping, proximity between related components, and space between modules.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;After a round or two, the placement began to meet my expectations. Decoupling capacitors, interface protection, and driver resistors needed to sit near their associated connections. I also asked the agent to place equivalent driver transistors according to the actual tube pin positions, then revise the schematic channel assignments to suit their positions relative to the 74HC595s. The wiring could follow the physical layout, with firmware generating display data using the final mapping.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The stacked structure introduced another set of constraints. Both main boards face upward, so components on top of the control board face components on the underside of the display board. An apparently empty area in a two-dimensional layout could sit directly below a high-voltage module or connector in the assembled device. Tube centers, inter-board connectors, and mounting holes also had to stay aligned across the boards.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/pcb-layout-pair.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/pcb-layout-pair.jpg&#34; alt=&#34;Control-board component side on the left and display-board underside placement on the right&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 4. Component placement on the control board and the underside of the display board&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_routing_iterations&#34;&gt;Routing Iterations&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Routing was substantially more complex than placement and took more time. I saw plenty of &lt;strong&gt;awkward traces and changes that solved one problem while creating another&lt;/strong&gt;. A route could look reasonable locally and still fit poorly into the board as a whole. I gradually stopped expecting a perfect first pass. Words like “reasonable,” “tidy,” and “compact” were insufficient. I needed to inspect the board, identify specific problems, and iterate with Codex.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The first scripts focused on individual connections. The overall layout included &lt;strong&gt;unnecessary detours&lt;/strong&gt;, &lt;strong&gt;unexpected right angles and acute corners&lt;/strong&gt;, and other awkward paths. A round took roughly half an hour, with &lt;strong&gt;considerable token consumption&lt;/strong&gt;. I could watch my &lt;strong&gt;Pro 100 weekly allowance slowly falling&lt;/strong&gt; as the iterations continued.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/display-routing-comparison.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/display-routing-comparison.jpg&#34; alt=&#34;First routing pass and final layout, with three-times enlarged trace-end details below&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 5. The first complete routing pass and the final display-board layout&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;KiCad’s &lt;strong&gt;text-based project files&lt;/strong&gt; let an agent read and modify component coordinates, nets, and traces. We gradually established a repeatable process.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Read.&lt;/strong&gt; Use project files and design documents to understand placement and routing constraints.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Modify.&lt;/strong&gt; Adjust component positions and generate routing paths with Python scripts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check.&lt;/strong&gt; Run rule checks and export images for my review before continuing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Routing became a &lt;strong&gt;geometry and path-planning problem&lt;/strong&gt;, reducing the amount of individual clicking and dragging in the GUI.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I recorded recurring issues as explicit routing rules.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Keep related traces parallel where possible, with consistent bends and fewer short fragments or pointless reversals.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Prefer same-layer connections and avoid repeated vias merely to shorten a local route.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apply the appropriate constraints to high-voltage nets, power, and ordinary signals. Convenient connectivity should never depend on indiscriminately reducing trace widths or clearances.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Another effective change was to work in stages on local areas. Finishing one group of related components and connections before routing shared signals and power was easier to control than repeatedly revising the whole board. Confirmed areas were retained where possible, and each new change was checked for its effect on existing connections and return paths.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;According to my rough notes, it took about ten rounds for routing to approach the intended result. Script execution and automated visual checks eventually fell below ten minutes per round. I still inspected the paths and assembly relationships, while recurring rules were incorporated into the scripts for subsequent iterations.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout callout-success&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;KiCad’s &lt;strong&gt;design rule checking (DRC)&lt;/strong&gt; was valuable throughout. Explicit trace-width and clearance requirements, checked after each round, exposed unconnected nets and routing violations introduced by the agent and &lt;strong&gt;constrained unintended changes&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I also checked the two boards together, including connector alignment and mounting positions, and kept components and metal structures on the opposite board clear of high-voltage areas. The side-mounted encoder affected the upper board as well. Its installed height and position required an edge cutout, followed by adjustments to nearby components and traces.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_enclosure_and_assembly_design&#34;&gt;Enclosure and Assembly Design&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Once the PCBs took shape, I used a three-dimensional assembly to check component heights, connector fit, and assembly clearances. Interference that was difficult to see in two dimensions became much clearer. Uncertain bends and fit allowances still needed confirmation with real parts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Many common components can &lt;strong&gt;reuse the 3D models supplied with KiCad footprints&lt;/strong&gt;. For unusual parts such as Nixie tubes, supplying the necessary datasheets and dimensions was enough for the agent to produce models that worked surprisingly well.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Board dimensions and spacing evolved during layout and assembly design. I wanted a compact device with enough room for components and connectors between the boards. The tubes sit on top, the screen faces forward, USB faces backward, and the knob projects from the right side. The resulting enclosure is 3D-printed in black PA12S. The upper shell includes the complete front frame, while the lower shell wraps around the bottom, sides, and rear.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I wanted a seamless front and no exposed top fasteners. The board assembly therefore mounts inside the upper shell, with enclosure seams along the sides, rear, and underside.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/enclosure-assembly.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/enclosure-assembly.jpg&#34; alt=&#34;Quarter-section assembly and interior views of both enclosure halves&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 6. Enclosure section and interior views of the upper and lower shells&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I worked through the fasteners layer by layer using section views. Mounting posts inside the upper shell contain heat-set inserts. Short screws secure the front of the display board, while brass standoffs clamp the rear, creating four suspension points. The rear standoffs also maintain the board spacing and accept the lower-shell screws at their lower ends. &lt;strong&gt;The board mounting and lower-shell attachment are independent&lt;/strong&gt;, so removing the lower shell leaves the upper shell and electronics together.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I then checked screw engagement through each layer, clearance to blind-hole bottoms, and tool access. Reinforcement at the mounting-post roots also had to leave a clear insertion path for the lower shell. Section views let me evaluate those relationships together.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For example, the inter-board connectors and front-board cable can remain connected during enclosure removal, but the projecting encoder restricts how the lower shell can move. After removing the knob and external retaining hardware, the lower shell needs a slight tilt and translation to clear the shaft, rear USB connector, and support posts. &lt;strong&gt;Assembly design needs to cover insertion and removal as well as the final fit.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/enclosure-mount-section.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/enclosure-mount-section.jpg&#34; alt=&#34;Section views of the rear standoffs and front mounting screws&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 7. Board suspension, with rear clamping and independent front display-board mounts&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With design documents and assembly models available, having the agent produce an assembly manual was straightforward. Fastener relationships, assembly order, and precautions already had a documented basis. Model views turned those decisions into illustrated instructions. The same models later supported the presentation video, whose materials and camera work are covered in the companion article.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_assembly_and_bring_up&#34;&gt;Assembly and Bring-up&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;JLCPCB manufactured the boards and assembled the basic components. After delivery, I hand-soldered the remaining parts and fitted the Nixie pin sockets. My first check was for shorts with a multimeter. I then tested the modules progressively before assembling the high-voltage display board, tubes, and other main parts for full-device bring-up.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout callout-warning&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;An unexpected issue during dimming&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;After the tubes were working, I noticed a faint noise at some brightness settings and compared several PWM frequencies. Overall, 500 Hz worked best. The noise became noticeably quieter at 100% brightness.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;At 5 kHz it became more obvious. At 16 kHz the noise disappeared, but brightness control became much less effective. At 25 kHz dimming clearly failed. I kept 500 Hz. The exact source of the noise has yet to be confirmed by measurement.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/pcb-lit-assembly.jpg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/projects/images/nixie-indicator/pcb-lit-assembly.jpg&#34; alt=&#34;Rendered illustration of the boards, LCD, and illuminated tubes with the enclosure removed&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 8. Illustration of the powered board assembly&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_reflections&#34;&gt;Reflections&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The project showed me how far AI’s hardware engineering capabilities have progressed. PCB design still required repeated refinement, especially routing. Connectivity was only the beginning, and tidy paths and practical assembly took further work. Given clear requirements, specifications, and rule checks, the agent could nevertheless perform a substantial amount of detailed design.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Its three-dimensional understanding surprised me even more. Datasheet dimensions were enough to create usable models for simple parts. Enclosure splits, board suspension, and assembly clearances could all be developed through discussion. &lt;strong&gt;Based on this project, I feel its spatial understanding and modeling already exceed those of most people.&lt;/strong&gt; The progression from dimensions to models and then to relationships between parts was more mature than I expected.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In this collaboration, &lt;strong&gt;AI increasingly felt like an extension of my hands&lt;/strong&gt;. An octopus can coordinate its arms through higher-level intent without directing every movement individually. Likewise, I could express a design intention and have the agent carry out the detailed operations. My preferences and aesthetic judgment still shaped the result. I remained involved through requirements, inspection, and physical testing, even though every file change went through the agent.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;AI brought a substantial productivity improvement to this kind of hardware project.&lt;/strong&gt; Previously, similar work could occupy weeks of personal time, starting with simulation and verification and progressing through gradual refinement. Now, two or three concentrated days, plus sustained token consumption, can potentially bring the main design to a manufacturable stage. Simulation, checks, and physical verification remain necessary, while drawing, modeling, and repeated revisions take much less time. An idea that starts in spare time has a better chance of becoming a physical device.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://hamhire.tech/en/posts/projects/nixie-indicator-software.html&#34;&gt;Part 2&lt;/a&gt; covers firmware and the desktop companion, including how the device retrieves quota information and turns it into glowing digits.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
- https://hamhire.tech/en/posts/projects/nixie-indicator-hardware.html - Licensed under CC BY-NC-ND 4.0</description>
        </item>
    
    
    
        <item>
        <title>My Homelab, Part 4 — Putting AI Ops into Practice</title>
        <link>https://hamhire.tech/en/posts/homelab/ai-devops-practice.html</link>
        <pubDate>Thu, 15 Oct 2026 20:00:00 +0800</pubDate>
        
        <guid>https://hamhire.tech/en/posts/homelab/ai-devops-practice.html</guid>
        <description>Hamhire Tech Tips https://hamhire.tech/en/posts/homelab/ai-devops-practice.html -&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Years of building and maintaining my Homelab have gradually produced infrastructure spanning local systems and cloud services, along with reasonably standardized maintenance procedures. Environment documentation, configuration management, change execution, and acceptance checks now have a documented basis.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The current approach draws on my years of experience operating SaaS systems.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Declarative configuration and GitOps manage desired state. Monitoring and alerts reveal actual behavior. SOPs record pre-change checks, execution steps, and recovery procedures. I adapted those practices to the scale and maintenance budget of my personal environment.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The diagram below summarizes the infrastructure layers and the engineering governance and observability that span them.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/ai-devops-practice/homelab-layers.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/ai-devops-practice/homelab-layers.en.svg&#34; alt=&#34;Physical infrastructure, virtualization and storage, orchestration, applications, cloud services, and cross-layer governance and observability&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 1. Infrastructure layers and the practices that span them&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The main difficulty in long-term maintenance is &lt;strong&gt;the sustained attention it requires&lt;/strong&gt;, concentrated in several areas.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Every layer needs regular maintenance.&lt;/strong&gt; Proxmox VE, Ceph, Kubernetes, and the services running on them all need upgrades, compatibility checks, and configuration changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Each upgrade needs its own research.&lt;/strong&gt; Release notes must be read, breaking changes identified, and possible effects on configurations, data, and services assessed before choosing an upgrade path.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Execution and troubleshooting take effort.&lt;/strong&gt; My deployments have custom settings that can behave differently from standard environments. Diagnosis needs the actual configuration, logs, and runtime state, followed by verification of the fix.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I used to &lt;strong&gt;set aside several days of leave each year&lt;/strong&gt; to maintain the whole environment. Researching versions and upgrade paths, executing changes, observing the results, and handling failures generally required continuous involvement. Standards reduced repeated investigation, but execution and verification still consumed attention.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As AI improved at understanding code, planning tasks, and using tools, I began considering how much more maintenance I could delegate to an agent. Production decisions still require human judgment about business impact. My own Homelab gives me room to experiment, provided I have &lt;strong&gt;cold backups of important data and clear recovery paths&lt;/strong&gt;. The agent can perform more of the actual work while I retain &lt;strong&gt;key decisions and acceptance checks&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This article focuses on &lt;strong&gt;engineering discipline and context management&lt;/strong&gt;, sharing how I helped an agent understand an existing environment and participate in maintenance, along with the problems and experience that followed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_context_management_and_engineering_practices&#34;&gt;Context Management and Engineering Practices&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Before introducing an agent, I already had a fairly complete &lt;strong&gt;declarative Git repository&lt;/strong&gt;, with Kustomize and Argo CD managing service deployments. It also contained many scripts for physical machines and VMs, covering initialization, configuration, and routine maintenance. Those accumulated configurations and scripts gave AI a starting point for reading the environment, preparing changes, and reusing operating procedures.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/ai-devops-practice/context-workflow.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/ai-devops-practice/context-workflow.en.svg&#34; alt=&#34;Configuration, maintenance plans, authorized execution, verification, and updates form a continuous cycle&#34; width=&#34;1100&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 2. A maintained cycle between actual configuration and operational work&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_maintaining_context_documents&#34;&gt;Maintaining Context Documents&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Context is grounded in &lt;strong&gt;the current environment’s actual configuration&lt;/strong&gt;. It records resource relationships, operating conditions, and decision background, and evolves with deployment changes. I use a few maintenance rules.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Record evidence and scope.&lt;/strong&gt; Document configuration sources, script prerequisites, and acceptance criteria. Mark unconfirmed information for verification before applying old procedures to the current environment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Update documents with changes.&lt;/strong&gt; After deployment and verification, record versions, topology, configuration differences, and operating procedures.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Centralize shared conventions.&lt;/strong&gt; &lt;code&gt;AGENTS.md&lt;/code&gt; defines directory responsibilities, authorization boundaries, and verification requirements. Domain-specific documents hold environment details.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Environment documents are organized by responsibility.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&#34;tableblock frame-all grid-all stretch&#34;&gt;
&lt;colgroup&gt;
&lt;col style=&#34;width: 14.2857%;&#34;/&gt;
&lt;col style=&#34;width: 28.5714%;&#34;/&gt;
&lt;col style=&#34;width: 57.1429%;&#34;/&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Domain&lt;/th&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Directory&lt;/th&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Responsibilities&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Network&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;docs/network/&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Device roles, topology, subnets, and connections&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Virtualization&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;docs/hypervisor/&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Hosts, VM placement, resource allocation, and lifecycle&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Storage&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;docs/storage/&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Clusters, disks, data placement, and mounts&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Kubernetes&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;docs/kubernetes/&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Cluster composition, dependencies, maintenance, and troubleshooting evidence&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Cloud resources&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;&lt;code&gt;docs/cloud/&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Cloud hosts, managed services, domains, and access relationships&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The documents also preserve context absent from configuration files, such as reasons for customizations, areas maintained manually, and recovery conditions.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_declarative_kubernetes_management&#34;&gt;Declarative Kubernetes Management&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I generally use &lt;strong&gt;Kustomize&lt;/strong&gt; for services I deploy myself, retaining flexibility over configuration. Directories group services by responsibility. Single-component services remain flat, while multi-component services split where needed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-text hljs&#34; data-lang=&#34;text&#34;&gt;.
├── apps/
│   └── &amp;lt;group&amp;gt;/
│       └── app.&amp;lt;service&amp;gt;.yml    # Application points to the service entry
└── kubernetes/
    └── &amp;lt;group&amp;gt;/
        ├── &amp;lt;single-component-service&amp;gt;/
        │   ├── kustomization.yml  # Resources, namespace, and images
        │   ├── deploy.yml         # Workload
        │   ├── service.yml        # Service access
        │   └── config/            # ConfigMap generator input files
        └── &amp;lt;multi-component-service&amp;gt;/
            ├── kustomization.yml  # Aggregate components and shared settings
            ├── &amp;lt;component-one&amp;gt;/
            │   ├── kustomization.yml
            │   ├── deploy.yml
            │   └── service.yml
            └── &amp;lt;component-two&amp;gt;/
                ├── kustomization.yml
                ├── statefulset.yml
                └── pvc.yml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Matching groups.&lt;/strong&gt; &lt;code&gt;apps/&lt;/code&gt; and &lt;code&gt;kubernetes/&lt;/code&gt; use the same responsibility groups. Each Application’s &lt;code&gt;source.path&lt;/code&gt; points to the directory containing the service’s &lt;code&gt;kustomization.yml&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flat by default.&lt;/strong&gt; A single-component service keeps its resources in one directory, adding routes, certificates, storage, or permissions as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Split by component.&lt;/strong&gt; Independently describable components get their own subdirectories. The parent aggregates them and maintains shared settings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Separate manifests from configuration content.&lt;/strong&gt; Resource files describe deployment structure. &lt;code&gt;config/&lt;/code&gt; holds files used by &lt;code&gt;configMapGenerator&lt;/code&gt;, whose output the workload references.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Clear entry points and hierarchy help the agent locate the change scope, inspect rendered manifests, and extend the existing structure with less duplication and scattered configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_accessing_the_environment_through_tools&#34;&gt;Accessing the Environment Through Tools&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For potentially risky operations, my principle is to &lt;strong&gt;use mature tools with explicit rules and auditable execution&lt;/strong&gt;. I keep direct environmental access behind those tools, which constrain entry points and scope and retain execution records. The agent invokes them within granted authorization.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/homelab/images/ai-devops-practice/ai-ssh-tool-access.png&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/homelab/images/ai-devops-practice/ai-ssh-tool-access.png&#34; alt=&#34;An agent accesses Homelab nodes through an SSH tool that manages keys and authorization&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 3. Authorized tool access, with sensitive keys retained by the tool&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Repository access lets an agent read configuration, prepare patches, and draft plans. Actual maintenance also requires visibility into runtime state and authorized access to nodes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I manage physical-node and VM SSH connections centrally through XPipe and expose remote operations to the agent through MCP. Connections initially used to inspect configuration and build context also support later authorized maintenance. The agent can check settings, execute commands, and collect results through those tools.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout callout-warning&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Authorization boundaries and key isolation&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Central key management.&lt;/strong&gt; XPipe retains sensitive SSH keys. The agent can operate without handling private keys or placing them in project files or conversation context.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Authorized operations.&lt;/strong&gt; The agent uses approved connections. Configuration inspection and changes receive separate authorization, and connectivity alone grants no blanket permission to modify resources.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Human confirmation for critical actions.&lt;/strong&gt; Operations involving persistent data, node destruction, or other irreversible changes need an explicit target, impact assessment, and recovery arrangements before execution.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With tools connected, the workflow becomes reading documentation and current state, listing the plan and impact, confirming it, executing in stages, and collecting acceptance evidence. When assumptions fail, the plan needs revision before continuing.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_documenting_previously_unrecorded_configuration&#34;&gt;Documenting Previously Unrecorded Configuration&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My earlier environment documentation was sparse, mostly because I had put it off. It was my own Homelab, and its complexity was manageable. I knew the purpose of subnets and VLANs, routing and access boundaries, service locations, entry points, and storage dependencies well enough to keep much of it in my head.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Once AI became involved, I granted the necessary &lt;strong&gt;configuration-reading access&lt;/strong&gt; through tools. For routers and switches, I exported configuration backups and asked the agent to organize networks, routes, and access rules. It combined those with the declarative repository to document service access, deployments, and storage dependencies. I checked the results and supplied background.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Using AI to prepare environment documentation was much easier than I expected.&lt;/strong&gt; Router and switch backups were particularly effective. The agent could connect facts spread across interfaces, VLANs, routes, and access rules. Workload, Service, ingress, and storage manifests similarly revealed access paths and dependencies. Documentation finally stopped feeling like a task that required saving up energy for a dedicated session.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I added explanations absent from the configuration, including the reasons for network divisions, access boundaries, and customizations. Combining configuration facts with human context allowed the documentation to explain both operation and intent. Credentials stay outside the general context.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/homelab/images/ai-devops-practice/config-context-documentation.png&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/homelab/images/ai-devops-practice/config-context-documentation.png&#34; alt=&#34;Router and switch configuration and design notes are organized into environment documentation by an AI agent&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 4. Configuration facts and human design notes become environment documentation&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout callout-context&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Archiving and removing obsolete material&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Historical material provides traceability and reference. Current operating instructions require current evidence.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Retired devices, invalid settings, and old procedures need separate archives to avoid contaminating current context and leading the agent to incorrect conclusions or actions. Historical references should be checked against present conditions before use.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Material fully superseded by current documentation, with no remaining reference or traceability value, can be removed to reduce redundant context.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_ai_ops_in_practice&#34;&gt;AI Ops in Practice&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;After establishing the initial context, I delegated several routine tasks to see how much research, execution, and troubleshooting the agent could handle. They included Kubernetes upgrades, declarative Grafana management, and GitLab and Argo CD upgrades. The first two cases are covered below, followed by VM and shared-script practices.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/ai-devops-practice/practice-paths.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/ai-devops-practice/practice-paths.en.svg&#34; alt=&#34;Runtime upgrades and declarative configuration management both write verified results back into the project&#34; width=&#34;1100&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 5. Operational work and maintained context reinforce one another&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_upgrading_kubernetes_across_minor_versions&#34;&gt;Upgrading Kubernetes Across Minor Versions&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The cluster had gone nearly two years without systematic upgrade maintenance. Kubernetes evolves quickly, while a kubeadm environment still requires research into version changes, supporting-component adjustments, node upgrades, and verification. I decided to try delegating this upgrade to AI.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The work progressed from 1.32 to 1.36 one minor version at a time and included replacing the node operating systems.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I first asked the agent to read environment documents, maintenance scripts, and cluster state and prepare a plan. &lt;strong&gt;The aim was to connect research, compatibility assessment, risk identification, and execution.&lt;/strong&gt; I reviewed the plan and made key decisions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inventory the environment and research releases.&lt;/strong&gt; Check Kubernetes, the container runtime, networking, and storage components. Read target-version notes and identify breaking changes and required adaptations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check compatibility and plan the route.&lt;/strong&gt; Determine which supporting components need upgrades first, then sequence control-plane, worker, and OS changes. Follow the &lt;a href=&#34;https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/&#34;&gt;official kubeadm upgrade documentation&lt;/a&gt; for minor-version steps.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Identify risks and confirm the plan.&lt;/strong&gt; The agent collects possible outages, configuration changes, and recovery arrangements. I choose the maintenance window, acceptable impact, and recovery points for my Homelab.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Execute through authorized tools.&lt;/strong&gt; Adapt supporting components and upgrade nodes in stages, collecting results. Unexpected behavior calls for diagnosis using configuration and logs before revising later steps.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Verify before advancing.&lt;/strong&gt; Check nodes and real workloads after each upgrade, test new Pods&amp;#39; networking and storage mounts, and verify reboot behavior after OS replacement. Record changes and conclusions before moving to the next release.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/ai-devops-practice/kubernetes-ai-upgrade-process.en.png&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/ai-devops-practice/kubernetes-ai-upgrade-process.en.png&#34; alt=&#34;Release research, compatibility checks, human risk confirmation, authorized execution, and verification&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 6. Upgrade stages and human decision points&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Working with AI during OS replacement&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Before starting the new systems, I had the agent adapt a complete set of maintenance scripts from previous deployments. Existing procedures were reusable, with dependencies and configuration differences checked for the new OS.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I then created blank VMs from a template and authorized their SSH connections through XPipe. The agent used the documented context and scripts to initialize the systems, install dependencies, and join nodes. I supplied the system entry points, while sensitive keys stayed in the tool.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My Homelab can tolerate brief interruptions during maintenance, so some choices reflected the cost of maintaining a personal environment. I remained involved in outage decisions and exception handling.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;The biggest change was being able to step away from the terminal.&lt;/strong&gt; The agent could turn research into operations, then inspect results and troubleshoot. I mainly reviewed plans and confirmed key stages. According to my notes, the main upgrade work took about a day, excluding preparation of the documentation.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_declarative_observability_management&#34;&gt;Declarative Observability Management&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I started the second case with Grafana. The old version’s dashboards were mostly edited in the UI and stored in the database, outside the project’s context. I opened them when needed and added panels, but rarely documented why metrics were chosen, how dashboards should be organized, or what remained to improve.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;After upgrading, I tried bringing existing dashboards into declarative management with Git Sync.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Git Sync connects Grafana dashboards to Git&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Git Sync synchronizes dashboards and folders with Git, supporting UI editing and repository JSON files. Configuration previously confined to the database becomes something the agent can read, compare, and modify, with design notes maintained alongside it. Collection settings and alert rules remain in their respective declarative deployments.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I asked AI to reassess the dashboards against the actual environment, distinguishing upstream content from local adaptations and identifying the needs of networks, hosts, storage, and services. It located configuration, compared content, and prepared changes. I supplied usage habits and design intentions, then we decided which information to retain and which monitoring gaps to address.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;The scope expanded to metric collection and alert rules.&lt;/strong&gt; I also involved AI in organizing, writing, and maintaining rules across the Homelab. Examples include checking collection coverage against device and service responsibilities, organizing host, storage, certificate, and availability rules, and making alert severity and notification wording consistent.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With dashboards in Git Sync and collection and alerting in declarative manifests, the agent can follow relationships from targets to metrics, panels, and alerts. I mainly confirm whether an alert deserves attention, whether its conditions match actual use, and whether deployment produces the intended result.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Being able to organize the observability design as a whole mattered more than bulk JSON edits.&lt;/strong&gt; Configuration relationships, design notes, and improvements I had lacked time to address now have a maintained home. New services and network changes can draw on that context to extend monitoring and alerts together.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/ai-devops-practice/agent-observability-governance.en.png&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/ai-devops-practice/agent-observability-governance.en.png&#34; alt=&#34;Individual maintenance compared with agent-assisted management of dashboards, collectors, and alerts, with human design decisions and verification&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 7. An agent handles repeated work across dashboards, collection, and alerts&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_vm_management_and_shared_scripts&#34;&gt;VM Management and Shared Scripts&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I traditionally kept deployment and maintenance scripts in &lt;code&gt;scripts/&lt;/code&gt;. OS initialization, network setup, disk expansion, runtime installation, and monitoring integration gradually accumulated reusable procedures.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Managing scripts as part of the project brings several benefits.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Traceability and review.&lt;/strong&gt; Git preserves changes and their reasons. Script diffs can be checked before execution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reuse.&lt;/strong&gt; The agent can compare scripts with actual settings, extract shared steps, and express node differences such as addresses, interfaces, and disks as explicit parameters.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Iteration.&lt;/strong&gt; OS and deployment changes can adapt existing scripts, with troubleshooting conclusions and verification results recorded for future work.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;AI helps compare, extract, and update scripts. I review critical changes before authorized execution. Each task leaves scripts and explanations that become usable context for the next.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_reflections&#34;&gt;Reflections&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For me, &lt;strong&gt;AI has become something like an extension of my hands&lt;/strong&gt;, taking on many tedious tasks previously handled manually. Research, configuration comparisons, repeated operations, and troubleshooting require less continuous attention. I mainly contribute design discussions, plan approval, and acceptance checks. Execution may take just as long, but being able to step away from the terminal already makes a substantial difference in a personal Homelab.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The experience strengthened a few priorities.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reduce continuous supervision&lt;/strong&gt;, leaving more attention for design and important decisions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use engineering practices and maintained context as the foundation&lt;/strong&gt;, keeping configuration, documentation, and scripts synchronized.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Accumulate operational experience&lt;/strong&gt;, recording changes and troubleshooting results for subsequent tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I still decide whether an outage is acceptable, which upgrade route to take, and whether to continue after unexpected results. I can also spend more time and energy exploring new projects.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
- https://hamhire.tech/en/posts/homelab/ai-devops-practice.html - Licensed under CC BY-NC-ND 4.0</description>
        </item>
    
    
    
        <item>
        <title>Code Meeseeks — Building an AI Code Review Tool for the Reviewer</title>
        <link>https://hamhire.tech/en/posts/projects/code-meeseeks.html</link>
        <pubDate>Fri, 31 Jul 2026 20:00:00 +0800</pubDate>
        
        <guid>https://hamhire.tech/en/posts/projects/code-meeseeks.html</guid>
        <description>Hamhire Tech Tips https://hamhire.tech/en/posts/projects/code-meeseeks.html -&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;As AI-generated code became more common in my work, the amount of code needing review grew too. Code review took increasing time and effort. Each review still required understanding the background, tracing the affected modules, and deciding whether the implementation made sense.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;To reduce that burden, I tried building a desktop tool with Claude and named it &lt;strong&gt;Code Meeseeks&lt;/strong&gt;. Based on the open-source PR-Agent, it brings pending PRs, code reading, AI analysis, follow-up questions, and comment drafts into one client. The project is open source and has had a small trial within my team.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The name comes from Mr. Meeseeks in &lt;em&gt;Rick and Morty&lt;/em&gt;, summoned to help complete a specific task. That seemed fitting for a review assistant.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Source code is on &lt;a href=&#34;https://github.com/huhamhire/code-meeseeks&#34;&gt;GitHub&lt;/a&gt;, with installation and usage information on the &lt;a href=&#34;https://huhamhire.github.io/code-meeseeks/&#34;&gt;project website&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_breaking_down_the_requirements&#34;&gt;Breaking Down the Requirements&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Internal teams, open-source communities, and commercial vendors already offer many team-oriented review tools. I also wanted a tool for the individual reviewer, helping me understand and verify each change. Several needs shaped it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;exampleblock card-grid columns-2&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;div class=&#34;openblock card card-blue&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;🔍 Build context for the reviewer&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A substantial part of manual review happens before judging an issue. A complex PR in a complex project requires understanding the author’s goal, relationships between changes, and effects on existing behavior. Larger changes increase that reading and verification work.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Reviewers have different familiarity with projects and modules. Familiar code can draw on memory. Unfamiliar modules require following implementations and call relationships. Team-level tools struggle to explain each change at the level each reviewer needs. I wanted explanations that could follow my own progress and build context gradually.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;openblock card card-green&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;🎯 Retain personal review priorities&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Reviewers focus on different concerns, including business correctness, edge cases, error handling, performance, and compatibility. I wanted to state my priorities and direct analysis toward them.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Rule preferences also need adjustment, including code quality, module responsibilities, design style, and project conventions. I wanted to maintain those requirements and match them by repository, combining project standards with my own focus.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;openblock card card-purple&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;💳 Reuse AI subscription allowances&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I wanted to use existing AI subscriptions, for example by running reviews through an installed, authenticated Codex CLI or Claude Code. Reusing those accounts and allowances could reduce the additional cost of model APIs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Model integration therefore needed local CLI support alongside common model APIs and compatible endpoints, leaving a choice based on available tools and accounts.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;openblock card card-orange&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;💬 Keep interacting during review&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;While reading an explanation, I wanted to ask follow-up questions using actual code and existing analysis, deepening my understanding of a module, call relationship, or specific risk. Later analysis should carry the earlier discussion and code references.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I also wanted to retain decision-making over published feedback. AI produces candidate findings, which I verify, edit, and select before posting to the original platform. Local repository copies, analysis history, and drafts make it easier to return after switching tasks.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For the review itself, I wanted an editor experience close to VS Code, with smooth Diff reading, file navigation, code positioning, and local search for related implementations.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/review-story.en.png&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/review-story.en.png&#34; alt=&#34;Three-panel comic showing a growing review workload, understanding module relationships, and asking follow-up questions with code context&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Those requirements led me to a desktop client that places Diffs, findings, and conversation together, allowing an individual to read, analyze, and prepare feedback on existing PRs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Local storage and model access are configured separately.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Repository copies and drafts stay on the computer. Review context is sent to the model service chosen by the user. The model and deployment arrangement need to fit the code’s permitted use.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A desktop client also means maintaining installers, a local runtime, and cross-platform behavior. Personal settings live on different computers, while shared rules need an agreed update and distribution process. During early validation, I accepted those costs to establish the individual review workflow.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_design_decisions&#34;&gt;Design Decisions&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_building_on_mature_review_capabilities&#34;&gt;Building on Mature Review Capabilities&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Reusing existing review capabilities was the project’s central design choice.&lt;/strong&gt; PR-Agent, contributed to the community by Qodo, already supports change descriptions, issue analysis, and PR questions. Code Meeseeks uses it directly as the review engine.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout callout-success&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;My experience with Qodo&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I had tried Qodo reviews on GitHub and liked its change overviews, file-change grouping, and code suggestions. Establishing the structure of a change before reading the important code reduced the effort of building context and strongly influenced my decision to reuse PR-Agent.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;An existing review engine lets me focus design and implementation effort on how the reviewer reads results, investigates issues, and confirms and publishes feedback.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The engine runs through an embedded Python runtime. Installers include the required environment to reduce dependency setup for users.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The workflow also follows Qodo’s approach to reading changes, generating findings, asking follow-up questions, and confirming feedback. A typical session proceeds as follows.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;Fetch pending PRs and organize them by repository.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open a PR, read the Diff, and use &lt;code&gt;/describe&lt;/code&gt; for an overview and file-change explanations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;code&gt;/review&lt;/code&gt; to inspect potential issues and their code locations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select code or quote a suggestion and continue with &lt;code&gt;/ask&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Turn useful findings into drafts, edit them, and publish to the code platform.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/pr-agent-workflow.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/pr-agent-workflow.en.svg&#34; alt=&#34;PR-Agent workflow from reading changes through descriptions, candidate findings, follow-up investigation, and human confirmation&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Generated findings first enter a candidate list and can be edited, discarded, or turned into comment drafts. The reviewer confirms published content. With automatic pre-review enabled, pending PRs can be analyzed in advance so results are ready when the client opens.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_local_repository_caching&#34;&gt;Local Repository Caching&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Online repository APIs commonly provide PR Diffs as the immediate review context. Understanding unchanged code, cross-file calls, or history requires further searches and reads, limiting what a single request can supply.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The client therefore caches repository mirrors locally and prepares worktrees for the versions involved in a PR. After the first fetch, incremental synchronization keeps them current. Reading and analysis reuse those repositories. File contents are read at the relevant commits and Diffs calculated locally, also supporting fast searches, historical comparisons, and blame analysis.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Local repositories support further context interaction too. A reviewer can explore related implementations from a changed line, and an agent running through a local CLI can inspect worktree files to investigate beyond the Diff and into historical context.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_agent_design&#34;&gt;Agent Design&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;PR-Agent supplies the basic descriptions, reviews, and questions. I added capabilities around the experience I had with Qodo’s commercial version and the local client’s context interactions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;exampleblock card-grid columns-2&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;div class=&#34;openblock card card-blue&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;🧩 Architecture diagrams and design explanations&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/agent-architecture.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/agent-architecture.en.svg&#34; alt=&#34;Change analysis produces a module-relationship diagram and design explanation&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I added architecture output similar to what I had used in Qodo’s commercial product. &lt;code&gt;/describe&lt;/code&gt; generates Mermaid diagrams rendered in the client to explain affected modules and relationships.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Alternative implementation ideas and their reasoning also help the reviewer examine design trade-offs against the code.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;openblock card card-green&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;🔎 References and re-review&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/agent-followup.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/agent-followup.en.svg&#34; alt=&#34;Code locations and existing suggestions feed follow-up investigation and revised findings&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Selected code and its location can accompany a question. Existing suggestions can also provide context for another review pass.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Local CLI mode can inspect repository files to check a concern. Results can retain, replace, or close the original finding.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;openblock card card-purple&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;📚 Rule-based context&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/agent-context.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/agent-context.en.svg&#34; alt=&#34;Repository-matched rules combine with repository conventions, memory, and preferences&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Markdown rules can be matched by repository and further scoped by project and target branch. Different repositories can use their own conventions and priorities.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The client also reads the reviewed repository’s &lt;code&gt;AGENTS.md&lt;/code&gt;, combined with working conventions, long-term memory, and personal preferences. Shared agreements can live in Git, while personal priorities evolve with use.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;openblock card card-orange&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;⚙️ Continuous tasks and execution control&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/agent-control.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/agent-control.en.svg&#34; alt=&#34;Task planning, tool calls, analysis history, human intervention, and authorization checks for writes&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The agent organizes tool calls from natural-language requests, retains task progress and analysis history, and allows changes of direction or cancellation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;New PR commits flag a code-version change, and later conversation can recognize that earlier analysis may be stale. AutoPilot can prepare reviews in advance. Runtime authorization checks control write operations.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Multiple analysis rounds increase model use and waiting time. The client records usage and offers policies such as follow-up limits so the depth can be adjusted to the task.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_visual_design_and_interaction_close_to_vs_code&#34;&gt;Visual Design and Interaction Close to VS Code&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I used VS Code’s visual style and interaction conventions to make the client familiar to developers. The stack is Electron, React, and TypeScript, with Monaco for code reading, a file tree, side-by-side and unified Diffs, syntax highlighting, and inline comments.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;PRs and files occupy the left, code changes the center, and AI analysis and conversation the right. Findings link to locations, selected code can enter a question, and comment drafts can be edited near the relevant lines. Reading, analysis, and feedback preparation connect within the same workspace.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Themes, monospaced fonts, and type size are adjustable. A command palette and keyboard shortcuts expose common actions, following editor conventions to reduce the adjustment required when switching tools.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/shared/images/meebox/screenshot.zh-CN.png&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/shared/images/meebox/screenshot.zh-CN.png&#34; alt=&#34;The client reviewing its own project’s PR, with change explanations and unpublished comments&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 1. Code reading, change descriptions, and inline drafts in Code Meeseeks&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The screenshot shows one of the project’s own PRs, using the Chinese interface. The right side explains changes and architecture, the left provides the Diff, and inline drafts remain near their code. The ease of moving between analysis and implementation directly affects the effort of verifying findings.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_multiple_code_hosting_platforms&#34;&gt;Multiple Code Hosting Platforms&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Internal collaboration at work often uses self-hosted Bitbucket Server. GitLab and GitHub are also common integration targets. I designed a shared adaptation layer so the same review workflow could operate across them.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In my personal experience, Bitbucket offers a relatively good review collaboration experience in enterprise settings. Its interaction and collaboration design also influenced Code Meeseeks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The client integrates with GitHub, Bitbucket Server / Data Center, and GitLab, including applicable self-hosted instances. Platform adapters fetch PRs, retrieve changes, and handle comments and review actions. Code reading, AI analysis, and draft processing reuse the same higher-level workflow.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/platform-adapters.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/projects/images/code-meeseeks/platform-adapters.en.svg&#34; alt=&#34;GitHub, Bitbucket, and GitLab connect through a shared adapter layer to code reading, AI analysis, and comment drafts&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Review states, inline comments, and API capabilities differ across platforms. Available actions reflect what each platform supports. Keeping those differences in adapters also makes platform integrations and review workflows easier to maintain independently.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_tool_integration&#34;&gt;Tool Integration&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;An optional local HTTP API and companion CLI let external agents or scripts discover PRs, read changes, and invoke reviews. The desktop interface and external tools share the same local review workflow, leaving room for future integrations.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The local API listens on loopback by default and requires token authentication. Operations follow its exposed action scope. GUI, CLI, and agent interfaces share underlying service logic to reduce differences in behavior.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_early_impressions&#34;&gt;Early Impressions&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The project has run for a month, with a small trial in the team. Feedback has been positive and has exposed practical details that needed work.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For me, the clearest benefit is &lt;strong&gt;understanding changes more efficiently&lt;/strong&gt;. Reading an overview before checking key files and candidate issues is easier than constructing all the context from scratch. Some PRs that previously took thirty or forty minutes can now be reviewed in under ten.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Those timings reflect personal impressions from early use. PR size, complexity, and my familiarity vary, and I have no standardized quantitative study yet.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Useful suggestions still need checking against the code. When their assumptions are unclear, I can ask questions, add constraints, and then decide whether to use them. Editing and discarding remain part of incorporating AI output into my review habits.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Client interaction has also become an ongoing area of work. Preserving drafts across page changes, inline positioning, platform differences, and context updates after new commits all affect whether a review proceeds smoothly. Connecting a model leaves many such details to address.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_thoughts_on_customized_tools&#34;&gt;Thoughts on Customized Tools&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In my experience, an agent such as Codex, paired with a capable underlying model and a well-tuned harness, can already produce good code reviews. Context organization, tool use, and task execution mechanisms influence both the process and results alongside the model.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For me, Code Meeseeks adds value through further customization of interaction. Organizing pending PRs, code reading, contextual questions, and comment preparation in a familiar interface brings existing review capabilities closer to my own working habits.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Building it also changed my impression of vibe coding. Early in the year, models and agents still felt strained by somewhat complex projects. By mid-2026, in my experience, they had become more than capable of completing moderately sized projects with a meaningful degree of complexity.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
- https://hamhire.tech/en/posts/projects/code-meeseeks.html - Licensed under CC BY-NC-ND 4.0</description>
        </item>
    
    
    
        <item>
        <title>My Homelab, Part 3 — Seven Years with a Ceph Storage Cluster</title>
        <link>https://hamhire.tech/en/posts/homelab/nas-03.html</link>
        <pubDate>Sun, 26 Jul 2026 19:50:00 +0800</pubDate>
        
        <guid>https://hamhire.tech/en/posts/homelab/nas-03.html</guid>
        <description>Hamhire Tech Tips https://hamhire.tech/en/posts/homelab/nas-03.html -&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;When I first built my Ceph cluster, I planned to write about the experience. I later decided to give it a longer period of stability testing in a home environment before looking back. The cluster has now run for roughly seven years, with disks and nodes gradually replaced along the way.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This article shares my Homelab storage choices and my experience of Ceph’s performance, stability, and maintenance. The failures and performance impressions come from my own use, as a reference for readers with similar requirements.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_choosing_a_storage_approach&#34;&gt;Choosing a Storage Approach&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A NAS provides file services over a network, with many possible storage implementations underneath. Commercial appliances can use conventional RAID or offer mixed-capacity schemes. A self-built NAS might use Linux software RAID, ZFS, or Unraid. Ceph can underpin file services too, although it is itself a distributed storage system.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;My earlier storage setup&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Before Ceph, I used a hardware RAID card with a JBOD enclosure in 2015. Three issues became apparent over time.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Wasted space with mixed-capacity disks.&lt;/strong&gt; Arrays were organized around smaller members, leaving extra capacity on newer, larger disks underused.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Slow rebuilds.&lt;/strong&gt; Replacing a failed disk meant a lengthy rebuild that also affected everyday reads and writes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multiple-disk failure risk during rebuilding.&lt;/strong&gt; While the degraded array rebuilt, another failure could exceed its redundancy and cause data loss.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_comparing_common_storage_options&#34;&gt;Comparing Common Storage Options&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Common home-storage choices occupy different product and technology layers.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;RAID&lt;/strong&gt; organizes disks through a hardware controller or operating system and commonly underpins NAS storage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Synology&lt;/strong&gt; integrates storage management, file sharing, and applications in a NAS appliance. SHR-2 pools are the comparison point here.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ZFS&lt;/strong&gt; combines a filesystem with volume management and is a common foundation for a self-built NAS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Unraid&lt;/strong&gt; is an operating system for self-built storage, managing a main array and separate pools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ceph&lt;/strong&gt; provides distributed block, file, and object storage interfaces.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/storage-comparison.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/storage-comparison.en.svg&#34; alt=&#34;Disk redundancy inside a single host compared with Ceph replicas distributed across hosts&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The capacity bars show aggregate proportions, with metadata and recovery reserves excluded. They do not represent physical partitions. Different replica colors distinguish capacity overhead, with no primary/secondary relationship implied.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Single-disk redundancy leaves no redundancy after one disk fails. Another failure during rebuilding can then cause data loss. For home storage that runs for years while disks age, that is a significant reliability concern. I therefore use configurations able to preserve data through at least two disk failures as the baseline.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The configurations are RAID 6, Synology SHR-2, ZFS RAIDZ2, Unraid dual parity, and Ceph with three replicas.&lt;/p&gt;
&lt;/div&gt;
&lt;table class=&#34;tableblock frame-all grid-all stretch&#34;&gt;
&lt;colgroup&gt;
&lt;col style=&#34;width: 16.6666%;&#34;/&gt;
&lt;col style=&#34;width: 16.6666%;&#34;/&gt;
&lt;col style=&#34;width: 16.6666%;&#34;/&gt;
&lt;col style=&#34;width: 16.6666%;&#34;/&gt;
&lt;col style=&#34;width: 16.6666%;&#34;/&gt;
&lt;col style=&#34;width: 16.667%;&#34;/&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&#34;tableblock halign-left valign-top&#34;&gt;Aspect&lt;/th&gt;
&lt;th class=&#34;tableblock halign-center valign-top&#34;&gt;RAID&lt;/th&gt;
&lt;th class=&#34;tableblock halign-center valign-top&#34;&gt;Synology&lt;/th&gt;
&lt;th class=&#34;tableblock halign-center valign-top&#34;&gt;ZFS&lt;/th&gt;
&lt;th class=&#34;tableblock halign-center valign-top&#34;&gt;Unraid&lt;/th&gt;
&lt;th class=&#34;tableblock halign-center valign-top&#34;&gt;Ceph&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Deployment&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Single host&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Single host&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Single host&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Single host&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Distributed&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Configuration&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;RAID 6&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;SHR-2&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;RAIDZ2&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Dual parity&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Three replicas across hosts&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Principle&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Striping and dual parity&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Combined capacity partitions and dual-disk redundancy&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Copy-on-write and dual parity&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Independent data disks and two parity disks&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Distributed object replicas&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Block storage&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Local block device, with a separate service for network export&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
iSCSI LUNs through SAN Manager&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
zvols, with a separate service for network export&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Additional iSCSI or similar service required&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Distributed block devices through RBD&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Object storage&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Separate object-storage service&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Additional application, depending on model support&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Separate object-storage service&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Additional object-storage application&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
S3-compatible interface through RGW&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Mixed capacities&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Usually limited by the smallest member&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Subject to capacity combinations&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Smallest-member constraint within a vdev&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Both parity disks must be at least as large as the largest data disk&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Weighted placement, subject to failure domains&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Expansion&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Implementation-dependent&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Disk-type and capacity constraints&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Layout- and version-dependent&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Add data disks&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Add OSDs&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Two-disk failures&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Within an array&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Within a pool&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Within each vdev&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Within the main array&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Data remains if three replicas were healthy beforehand&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Failure domain&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Disk&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Disk&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Disk&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Disk&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Customizable hosts, racks, PDUs, data centers, and more&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Node maintenance&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✗&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✗&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✗&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✗&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Replica, quorum, and access-service HA requirements apply&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Scale across hosts&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✗&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✗&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✗&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✗&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Hardware replacement&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Rebuild disk by disk, controller compatibility affects whole-host migration&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;△&lt;br/&gt;
Rebuild disk by disk, model constraints affect whole-host migration&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Replace disks individually, or move the original pool to another host&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Rebuild individual disks, or move existing disks to another host&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;✓&lt;br/&gt;
Replace disks or nodes gradually, redistributing affected data&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Recovery&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Array rebuild&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Array rebuild&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Resilver&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Reconstruct from remaining disks and parity&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Recover from other replicas&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Rebuild load&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Array members&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Affected pool members&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Affected vdev&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Remaining array members and parity disks&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Affected OSDs, nodes, and network&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Maintenance scope&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Host, controller, disks&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;NAS host and pool&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Host, pool, vdevs&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Host, array, parity disks&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Nodes, network, cluster services&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;tableblock halign-left valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Capacity overhead&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Equivalent to two member disks&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Dual-disk redundancy, with capacity-combination constraints&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Equivalent to two disks per vdev&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Two parity disks&lt;/p&gt;&lt;/td&gt;
&lt;td class=&#34;tableblock halign-center valign-top&#34;&gt;&lt;p class=&#34;tableblock&#34;&gt;Configurable&lt;br/&gt;
1/2 for two replicas, 2/3 for three&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;paragraph small&#34;&gt;
&lt;p&gt;✓ Supported, △ conditional or requires additional design, ✗ absent from the deployment itself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Three healthy replicas retain data after two data disks fail, but continued I/O also depends on the minimum replica count and MON quorum. With &lt;code&gt;min_size=2&lt;/code&gt;, a PG with only one surviving replica stops I/O. See the &lt;a href=&#34;https://docs.ceph.com/en/reef/rados/operations/pools/&#34;&gt;pool documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Capacity overhead depends on redundancy policy. Node count does not determine replica count. Three nodes can use two replicas, whose extra copy consumes 1/2 of raw capacity, but that configuration lacks two-disk fault tolerance. Three replicas spend 2/3 on extra copies. &lt;a href=&#34;https://docs.ceph.com/en/reef/rados/operations/erasure-code/&#34;&gt;Erasure coding&lt;/a&gt; can reduce that overhead. Metadata and recovery reserves further reduce usable capacity.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For ordinary home storage such as photos, media, file sharing, and backups, a single-host system is usually sufficient. It costs less in hardware, power, and maintenance and is simpler to manage than a multi-node cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_why_i_chose_ceph&#34;&gt;Why I Chose Ceph&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Ceph is an open-source distributed storage project originating in Sage Weil’s research. Inktank later commercialized it, and Red Hat acquired Inktank in 2014. In 2022, &lt;a href=&#34;https://ceph.io/en/news/blog/2022/red-hats-ceph-team-is-moving-to-ibm/&#34;&gt;Red Hat’s Ceph team moved to IBM&lt;/a&gt;. Development continues through the community and multiple vendors.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Its foundation is RADOS distributed object storage. Objects map through placement groups (PGs) and CRUSH rules to OSDs, the storage processes managing disks, with replicas retained according to policy. RBD, CephFS, and RGW expose block, file, and object interfaces. The &lt;a href=&#34;https://docs.ceph.com/en/reef/architecture/&#34;&gt;architecture documentation&lt;/a&gt; explains the layers. The diagram uses three replicas across hosts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/ceph-replicas.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/ceph-replicas.en.svg&#34; alt=&#34;Objects map through PGs and CRUSH to three replicas on separate hosts&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My choice came down to several requirements.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mixed capacities and natural replacement cycles.&lt;/strong&gt; Mixed-capacity support is essential. Disks should be replaced individually as they age, with old and new capacities continuing to work together through weighted placement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hardware renewal with limited migration.&lt;/strong&gt; Disk and node replacements can proceed gradually, with the cluster recovering and rebalancing affected data. This minimizes movement and avoids migrating the entire dataset to another storage system just to replace hardware.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Availability and capacity trade-offs.&lt;/strong&gt; Critical services need access during single-node maintenance. I accept capacity overhead and maintenance costs, using different redundancy levels for different data classes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The redundancy overhead is acceptable to me. My household dataset is manageable, HDD storage remains relatively inexpensive, and even in 2026 I still have a stock of enterprise disks formerly used for Chia farming.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout callout-context&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I also evaluated &lt;a href=&#34;https://docs.gluster.org/en/latest/&#34;&gt;GlusterFS&lt;/a&gt; early on. Its distributed-filesystem focus seemed best suited to primarily file-oriented use. My Homelab needed VM block storage, file sharing, and object storage together, which led me to Ceph.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_performance_in_practice&#34;&gt;Performance in Practice&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I initially tried two ways to accelerate HDDs with NVMe, using bcache at the block layer and placing BlueStore metadata on a fast device. Each offered benefits and introduced different failure dependencies.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_experiment_with_bcache&#34;&gt;Experiment with bcache&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;bcache sits beneath the block device used by an OSD, using NVMe as a cache for HDD capacity.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/bcache-layout.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/bcache-layout.en.svg&#34; alt=&#34;bcache manages an NVMe cache and HDD backing devices beneath the OSDs&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Cache hits read from NVMe. Misses read from HDD and populate the cache according to policy. Writeback first writes to NVMe and later destages to HDD. Writethrough waits for the HDD write.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The benefit depends on access patterns and hit rate. My NAS contains many infrequently accessed files. Reading cold data for the first time still requires the HDD, limiting the improvement for that workload.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In actual use, I &lt;strong&gt;encountered potential stability issues&lt;/strong&gt; and also had to consider cache-device failure. In writeback mode, NVMe may hold data awaiting destaging, making it a critical part of the storage path. Sharing one cache device across several backing disks can affect multiple devices at once and complicate diagnosis and recovery.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_experiment_with_bluestore_metadata_acceleration&#34;&gt;Experiment with BlueStore Metadata Acceleration&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Another approach keeps data on HDD while placing BlueStore’s &lt;code&gt;block.db&lt;/code&gt; on NVMe. &lt;code&gt;block.db&lt;/code&gt; holds RocksDB metadata, and &lt;code&gt;block.wal&lt;/code&gt; holds the write-ahead log. With a separate DB device, WAL normally uses that device too. See the &lt;a href=&#34;https://docs.ceph.com/en/reef/rados/configuration/bluestore-config-ref/&#34;&gt;BlueStore configuration reference&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/bluestore-layout.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/bluestore-layout.en.svg&#34; alt=&#34;HDD data devices paired with separate DB and WAL areas on NVMe&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A single NVMe can contain separate DB/WAL areas for several HDD OSDs. Each OSD coordinates access, using metadata to locate HDD data blocks and managing data persistence alongside metadata and log updates. Metadata already cached in memory needs no NVMe read.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The earlier FileStore backend depended on a local filesystem such as XFS and an additional journal. BlueStore manages block devices directly, reducing intermediate filesystem overhead and supporting checksums, compression, and metadata placement on faster devices. See the &lt;a href=&#34;https://docs.ceph.com/en/reef/rados/configuration/storage-devices/&#34;&gt;storage backend documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Moving metadata provides no file-content cache. Cold reads still incur HDD seek and media latency.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout callout-danger&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;The largest risk is losing data on associated OSDs when a shared NVMe fails.&lt;/strong&gt;&lt;br/&gt;
Loss of dirty cache data or BlueStore DB/WAL can make an OSD’s data unrecoverable even with an intact HDD. One NVMe serving multiple OSDs concentrates the impact. Recovery depends on complete copies elsewhere in the cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Both approaches increased stability concerns and maintenance complexity in my environment, with limited improvements for cold reads. I eventually moved away from them. My experience informed that personal choice without establishing a universal defect in either design.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_the_final_choice_of_separate_pools_by_medium&#34;&gt;The Final Choice of Separate Pools by Medium&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I separated HDDs and NVMe into independent pools, using device classes and CRUSH rules to control placement. Workloads needing higher IOPS, throughput, and lower latency use NVMe directly. Capacity-oriented workloads use HDDs, with placement and migration arranged at the application side.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/ceph-storage-pools.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/ceph-storage-pools.en.svg&#34; alt=&#34;NVMe pools for VMs and databases, HDD pools for files, backups, and object storage&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My storage network runs at 10GbE. Tests and long-term use show that the HDD pool meets my needs for NAS files, VM image backups, and capacity-oriented object storage. Small files, random access, concurrency, and recovery load still affect the experience, so these results do not imply that every workload saturates 10GbE.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Here is an early CephFS file read/write test.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/cephfs-dd-test.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/cephfs-dd-test.en.svg&#34; alt=&#34;Early dd results showing 854 MB/s writes and 2.8 GB/s reads, including cache effects&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The test ran in a CephFS mount with a roughly 2.46 GB file. It reported 854 MB/s for writing and 2.8 GB/s for reading. Cache effects are included, so those values cannot be equated with sustained HDD persistence or network throughput. Together with everyday use, the result supports the suitability of file access performance for my NAS needs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;In mid-2025, before the subsequent broad rise in SSD prices, I replaced a node with an all-flash U.2 NVMe configuration, continuing the move toward flash storage.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout callout-success&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;Planning a gradual move to 25GbE&lt;/strong&gt;&lt;br/&gt;
I chose a 25GbE SFP28 NIC during that replacement. SFP28 and SFP+ share a compatible physical form factor. A 25G NIC supporting 10G operation can connect to an existing SFP+ 10G switch, allowing upgrades in stages. See &lt;a href=&#34;https://networking-docs.nvidia.com/cablemanagfaq/cable-and-connector-definitions&#34;&gt;NVIDIA’s connector definitions&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My DAC cables are rated for 25G and currently run at 10G with the existing switch. The NIC and cables can be reused when the switch is upgraded. NIC speed support and switch compatibility with the chosen DAC still need checking. The performance experience described here is based on the 10G network.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_stability_in_practice&#34;&gt;Stability in Practice&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For seven years, the cluster has served everyday storage through disk and node replacements, hardware failures, and several whole-rack power losses. I have experienced no loss of already-written data. The following observations come from that period of operation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_power_stability&#34;&gt;Power Stability&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I initially debated adding a UPS, mainly because I worried about data loss after sudden power cuts. A UPS also brings trade-offs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lithium battery safety.&lt;/strong&gt; Thermal runaway can cause fires, with risk depending on chemistry, battery management, and product design.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lead-acid weight.&lt;/strong&gt; Weight affects handling and installation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Long-term capacity loss.&lt;/strong&gt; Both types age, reducing backup runtime and requiring checks and eventual replacement.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.eaton.com/us/en-us/products/backup-power-ups-surge-it-power-distribution/backup-power-ups/lithium-ion-batteries-/lithium-ion-explained.html&#34;&gt;Eaton’s UPS battery documentation&lt;/a&gt; discusses differences in safety and lifetime.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My tests produced no data loss. Over the years, the rack lost power roughly three or four times, again with no loss of already-written data. Those experiences made me comfortable with power-loss recovery in my own setup.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My requirement is that &lt;strong&gt;writes still incomplete at the instant of power loss may fail&lt;/strong&gt;, while data confirmed as durable should remain. VM and database behavior still depends on transaction commits, synchronization, and devices honoring flush requests.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The tests and incidents cover a limited set of failures. Ceph replication works alongside device power-loss protection, correct persistence semantics, UPS protection, and independent backups. Replicas in the same rack also share the rack’s power-failure exposure.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_common_failures&#34;&gt;Common Failures&lt;/h3&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;HBA failures.&lt;/strong&gt; I used second-hand server parts. Home cooling was also limited by noise considerations, which may have contributed. Server components&amp;#39; expected airflow needs attention in a home chassis.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disk-backplane failures.&lt;/strong&gt; A chassis bought from a small manufacturer developed a front drive-cage backplane fault. Offline-disk diagnosis needs to include backplanes, cables, controllers, and power.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Aging HDD replacement.&lt;/strong&gt; I replaced disks as reallocated sectors reached warning levels, as part of planned maintenance before complete failure.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/posts/homelab/images/nas-03/osd-offline.png&#34;&gt;&lt;img src=&#34;https://hamhire.tech/posts/homelab/images/nas-03/osd-offline.png&#34; alt=&#34;Ceph Dashboard showing osd.8 out and down while the other listed OSDs remain in and up&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&#34;title&#34;&gt;图 1. An offline OSD, with &lt;code&gt;osd.8&lt;/code&gt; on &lt;code&gt;pve-03&lt;/code&gt; shown as &lt;code&gt;out/down&lt;/code&gt; and the other listed OSDs &lt;code&gt;in/up&lt;/code&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Cross-node replication keeps data available on other nodes through such local failures. An HBA or backplane can affect several disks simultaneously, so failure-domain planning needs to consider more than individual disks. Maintenance also requires checking the health of other nodes to avoid overlapping failures.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_ceph_best_practices_for_a_homelab&#34;&gt;Ceph Best Practices for a Homelab&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_a_dedicated_storage_network&#34;&gt;A Dedicated Storage Network&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I separate the PVE and Ceph networks. PVE carries compute-side management and workload traffic. Ceph carries storage access, OSD replication, heartbeats, and recovery.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/storage-network.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/storage-network.en.svg&#34; alt=&#34;Three nodes connected separately to PVE and Ceph networks, with storage and recovery traffic using Ceph&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The separation is especially useful during recovery. Failed disks, returning nodes, and rebalancing generate extra transfers. Keeping that traffic apart reduces bandwidth contention and makes the storage and compute networks easier to observe individually.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My design has only those two networks. Client access and OSD-to-OSD traffic share the Ceph network, with no further split between &lt;code&gt;public_network&lt;/code&gt; and &lt;code&gt;cluster_network&lt;/code&gt;. I chose to keep home-network complexity manageable. See the &lt;a href=&#34;https://docs.ceph.com/en/reef/rados/configuration/network-config-ref/&#34;&gt;network configuration documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Actual isolation also depends on physical links. VLANs separate logical networks but still share bandwidth on a common uplink. Capacity planning needs to cover both routine access and recovery.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_pool_and_replica_policies&#34;&gt;Pool and Replica Policies&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I divide pools by medium and data class and choose redundancy separately. Three considerations guide that arrangement.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Medium and workload.&lt;/strong&gt; Low-latency, higher-I/O workloads use NVMe. Capacity-oriented workloads use HDDs. CRUSH rules constrain placement to the appropriate media.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data importance and capacity cost.&lt;/strong&gt; Replica count is per pool. Three replicas retain more redundancy headroom. Two improve capacity efficiency but leave less headroom after a failure. The decision depends on importance, recovery cost, and independent backups.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Replicas and failure domains.&lt;/strong&gt; Tolerating a whole node offline requires replicas across hosts. Multiple copies on one host share its failure exposure. Separate pool names alone provide no physical-device isolation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/pool-replica-policy.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/pool-replica-policy.en.svg&#34; alt=&#34;HDD and NVMe pools can each use two or three replicas according to workload requirements&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Devices need free space for recovery and rebalancing. Dividing raw capacity by replica count alone overestimates usable space. Pools sharing OSDs also share disk capacity and I/O resources.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Consider &lt;code&gt;size&lt;/code&gt; and &lt;code&gt;min_size&lt;/code&gt; together. Three replicas with &lt;code&gt;min_size=2&lt;/code&gt; can continue I/O after one copy is lost, assuming quorum and other requirements remain satisfied. With two replicas and the same minimum, losing one stops affected I/O. Reducing replicas changes maintenance availability as well as capacity overhead. See the &lt;a href=&#34;https://docs.ceph.com/en/reef/rados/operations/pools/&#34;&gt;pool documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_using_osd_noout&#34;&gt;Using OSD noout&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For a home cluster whose alerts can be monitored and acted on promptly, I favor &lt;code&gt;noout&lt;/code&gt;. Temporary network problems, reboots, or connection issues can otherwise cause a recoverable OSD to be automatically marked out, trigger data migration, and require another rebalance when it returns.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;code&gt;noout&lt;/code&gt; prevents down OSDs from being automatically marked out after the timeout. It neither keeps them online nor blocks all recovery and movement. The &lt;a href=&#34;https://docs.ceph.com/en/reef/rados/troubleshooting/troubleshooting-osd/&#34;&gt;OSD troubleshooting documentation&lt;/a&gt; covers setting and clearing it during maintenance.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-shell hljs&#34; data-lang=&#34;shell&#34;&gt;# Cluster-wide noout affects automatic out behavior across the cluster
ceph osd set noout

# Clear after temporary maintenance and state verification
ceph osd unset noout&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For planned work, version support may allow narrowing the scope to affected OSDs or hosts. Retaining cluster-wide noout means actively taking responsibility for failures.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout callout-danger&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Pay particular attention to OSD down, degraded or undersized PGs, disk health, and capacity alerts. A short outage may justify waiting for a device to return. Permanent failures need replacement or removal so the cluster can restore copies. Continued application access provides no reason to leave the cluster degraded indefinitely.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;If the first failed disk remains unattended and further replica-bearing devices fail, redundancy can gradually disappear, making data unavailable or lost. If prompt alert response is impractical, I advise keeping automatic recovery enabled, with sufficient reserve capacity, instead of retaining global noout long-term.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_independent_backups_for_important_data&#34;&gt;Independent Backups for Important Data&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Replicas address device and node failures. Recovering accidental deletion, erroneous overwrites, or malicious deletion requires a separate backup strategy. I retain static backups of cold data and back up selected critical data to cloud object storage.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Independent cold-data copies allow recovery without depending entirely on the original Ceph cluster. Cloud object storage adds a separate location so a rack-level incident cannot affect every copy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock image-preview&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;a class=&#34;image&#34; href=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/backup-layout.en.svg&#34;&gt;&lt;img src=&#34;https://hamhire.tech/en/posts/homelab/images/nas-03/backup-layout.en.svg&#34; alt=&#34;Ceph data backed up independently, with selected critical data also stored in cloud object storage&#34; width=&#34;1200&#34;/&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;The 3-2-1 backup principle&lt;/strong&gt;&lt;br/&gt;
Retain at least three copies of important data, across at least two types of storage media, with one copy off-site. The three include the original and two backups. Copies inside one Ceph cluster cannot substitute for independent backups. See &lt;a href=&#34;https://www.cisa.gov/sites/default/files/publications/data_backup_options.pdf&#34;&gt;CISA’s backup guidance&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Cloud backups store selected important data in object storage. I separate routine backup credentials from administrative credentials and limit access to required buckets and prefixes. Daily writers should especially lack permission to delete historical versions or change versioning settings. Versioning retains earlier copies after accidental overwrites or deletions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Versioning also needs permissions and retention policies. Backup completion, availability of historical versions, and actual file recovery all need periodic checks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout callout-success&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;An off-site cluster is another possibility&lt;/strong&gt;&lt;br/&gt;
Ceph supports cross-cluster synchronization through &lt;a href=&#34;https://docs.ceph.com/en/reef/rbd/rbd-mirroring/&#34;&gt;RBD mirroring&lt;/a&gt;, &lt;a href=&#34;https://docs.ceph.com/en/reef/cephfs/cephfs-mirroring/&#34;&gt;CephFS snapshot mirroring&lt;/a&gt;, and &lt;a href=&#34;https://docs.ceph.com/en/reef/radosgw/multisite/&#34;&gt;RGW multisite&lt;/a&gt;. With another cluster elsewhere and enough inter-site bandwidth, a home setup could even start entertaining multi-data-center ambitions 😝&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Snapshots or retained historical versions are still needed alongside synchronization to recover from accidental deletion and overwriting.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_looking_ahead&#34;&gt;Looking Ahead&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;After seven years of continuous 24×7 use, hardware failures, aging-disk replacements, and node retirements, Ceph has demonstrated its long-term design reliability in my own application. I have yet to find a better alternative that fits my needs and intend to keep using it for the long term.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
- https://hamhire.tech/en/posts/homelab/nas-03.html - Licensed under CC BY-NC-ND 4.0</description>
        </item>
    
    
    
        <item>
        <title>Techno Keyer — Bringing Retro Displays and Morse Code Together</title>
        <link>https://hamhire.tech/en/posts/projects/techno-keyer.html</link>
        <pubDate>Sat, 05 Oct 2024 20:00:00 +0800</pubDate>
        
        <guid>https://hamhire.tech/en/posts/projects/techno-keyer.html</guid>
        <description>Hamhire Tech Tips https://hamhire.tech/en/posts/projects/techno-keyer.html -&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Since getting into amateur radio in 2023, I have tried various parts of the hobby and earned my Class A and Class B operator certificates. CW was naturally on the list. I bought a dual-lever paddle and spent some time practicing sending and copying Morse code. As a programmer with a fondness for automation, I soon wanted to automate the repetitive work of sending it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A basic transmitter keyer is essentially an automated switch, so the idea sounded manageable. Around the same time, I acquired a BOE vacuum fluorescent display (VFD) module to experiment with. Its distinctive glow, familiar from electronics of the 1980s and 1990s, suggested a small device with a retro-technology feel. I called it &lt;em&gt;Techno Keyer&lt;/em&gt;. The name seemed to suit the era, even though I had yet to be born back then :-)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_tldr&#34;&gt;TL;DR&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This article covers the design and implementation of &lt;em&gt;Techno Keyer&lt;/em&gt;. The firmware and circuit schematics are available on GitHub for anyone interested in the project.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/huhamhire/techno-keyer&#34; rel=&#34;noopener&#34; target=&#34;_blank&#34;&gt;Techno Keyer on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;videoblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;video src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/cw_encode.mp4&#34; width=&#34;800&#34; height=&#34;342&#34; poster=&#34;/posts/projects/images/techno-keyer/cw_encode_poster.jpg&#34; autoplay=&#34;&#34; controls=&#34;&#34;&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_transmit_mode&#34;&gt;Transmit Mode&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The initial goal was straightforward. Read characters from a USB keyboard, encode them as Morse code, and open and close the radio’s keying circuit to send them automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_keyboard_input&#34;&gt;Keyboard Input&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Reading a USB keyboard was the first requirement for an automatic keyer, and it became my first obstacle. I initially assumed that a USB-capable MCU would handle it. I built a breadboard prototype around an ESP32-S3 development board and tried the EspUsbHost project from GitHub. An older Logitech membrane keyboard worked as expected.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Reference project &lt;a href=&#34;https://github.com/tanakamasayuki/EspUsbHost&#34; rel=&#34;noopener&#34; target=&#34;_blank&#34;&gt;EspUsbHost&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A spare mechanical keyboard gave a different result. It would fail to initialize properly, so there was no usable key input. Debug output showed the initial connection being detected, followed by failures and repeated restarts. Comparing my results with issues in similar projects pointed to keyboard compatibility in the USB-host setup I was using.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The ESP32-S3 USB-OTG host supports Low Speed and Full Speed devices. I treated the failure as a compatibility issue in my setup and looked for a practical way to handle the keyboards I wanted to use. &lt;a href=&#34;https://docs.espressif.com/projects/esp-usb/en/latest/esp32s3/usb_host.html&#34;&gt;Espressif USB Host documentation&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Following approaches used in other community projects, I tried a CH9350L USB keyboard-and-mouse to serial controller, a chip also used in KVM devices. I bought a development module on Taobao and connected it to the prototype. Keyboard input then worked, although the extra chip raised the cost of the design. Other builders have also used USB-to-PS/2 keyboard connections for similar projects.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/CH9350L.jpg&#34; alt=&#34;CH9350L module&#34; width=&#34;600&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_keying_the_radio&#34;&gt;Keying the Radio&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Once keyboard input worked, the next step was to make the CW output behave like a conventional key. I wanted broad compatibility and a simple control circuit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A key is a switch that opens and closes a circuit. On the usual 3.5 mm key connector, the sleeve is ground. With a paddle, the other two contacts represent the dit and dah inputs. With a straight key, the tip provides the keying contact.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/TRS-Pinout_keyer.png&#34; alt=&#34;Key connector pinout&#34; width=&#34;500&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;M0UKD’s simple touch-key circuit uses an NPN transistor as the switching element and was reported to work with several Icom radios. A MOSFET could serve a similar purpose.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/Simple_touch_key.jpg&#34; alt=&#34;Simple transistor keying circuit&#34; width=&#34;400&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A semiconductor switch can respond quickly, but directly connecting the circuits raised a few concerns. Key-input voltages vary between radio designs. The Icom interface I considered used 3.3 V, while other equipment, especially older radios, could expose the circuit to higher voltages. Techno Keyer also has its own USB supply, so connecting the grounds directly could create a ground loop between devices.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I chose an &lt;strong&gt;optically isolated solid-state relay&lt;/strong&gt; as the external switching element. It separates the keyer’s circuit from the radio’s keying circuit. An indicator LED and a buzzer provide visual and audible feedback. The resulting circuit is shown below.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/schematic_keyer_out.jpg&#34; alt=&#34;Transmit output circuit&#34; width=&#34;640&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_encoding_and_sending&#34;&gt;Encoding and Sending&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With the hardware approach established, I needed to translate keyboard characters into Morse code. Morse predates ASCII, and its letter and number patterns do not follow a convenient numeric sequence. I used a lookup table to keep the implementation easy to extend.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I represented dits as 0 and dahs as 1. Since a pattern may begin with zero, I stored each pattern in reverse bit order in an eight-bit value, with a leading 1 as an end marker. For example, A is &lt;code&gt;.-&lt;/code&gt;, represented as &lt;code&gt;0b110&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-cpp hljs&#34; data-lang=&#34;cpp&#34;&gt;// Morse code table
// 0 -&amp;gt; Dit, 1 -&amp;gt; Dah, Reverse order, Highest bit is 1
const struct morseChar morseTable[] = {
        {&amp;#39;A&amp;#39;, 0b110},       // .-
        {&amp;#39;B&amp;#39;, 0b10001},     // -...
        {&amp;#39;C&amp;#39;, 0b10101},     // -.-.
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Another approach used in related projects is a binary tree whose branches represent dits and dahs. Traversing the tree locates a character. It offers a compact representation, but I found the table easier to extend for my needs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock callout&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A compact tree can be stored as a “magic string”, with each character’s position representing its Morse pattern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-text hljs&#34; data-lang=&#34;text&#34;&gt;**ETIANMSURWDKGOHVF*L*PJBXCYZQ**54S3***2**+***J16=/***H*7*G*8*90************?_****\&amp;#34;**.****@***&amp;#39;**-********;!*)*****,****:****&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Sending a character then becomes a table lookup followed by a series of bit shifts. Each bit determines which signal to send until the end marker is reached.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-cpp hljs&#34; data-lang=&#34;cpp&#34;&gt;/**
 * Send one char
 * @param c
 */
void MorseEncoder::sendChar(char c) {
    if (c == &amp;#39; &amp;#39;) {
        vTaskDelay(7 * _dotTimeMs / portTICK_PERIOD_MS);
    } else {
        uint8_t code = _codec-&amp;gt;getCode(c);
        while (code != 1) {
            if (code &amp;amp; 1) {
                _sendSignal(DAH);
            } else {
                _sendSignal(DIT);
            }
            code = code &amp;gt;&amp;gt; 1;
        }
        vTaskDelay(2 * _dotTimeMs / portTICK_PERIOD_MS);
    }
}

/**
 * Send morse signal
 * @param sig
 */
void MorseEncoder::_sendSignal(uint8_t sig) {
    uint16_t sigTimeMs = sig == DIT ?
            _dotTimeMs : _dotTimeMs * 3;

    digitalWrite(CW_PIN, HIGH);
    _buzzer -&amp;gt; on();
    vTaskDelay(sigTimeMs / portTICK_PERIOD_MS);

    digitalWrite(CW_PIN, LOW);
    _buzzer -&amp;gt; off();
    vTaskDelay(_dotTimeMs / portTICK_PERIOD_MS);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I assembled a simple breadboard prototype to test the result.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/test_board.jpg&#34; alt=&#34;Breadboard prototype&#34; width=&#34;720&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_receive_mode&#34;&gt;Receive Mode&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;After implementing the basic transmitter, I decided to add decoding as well.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_audio_processing&#34;&gt;Audio Processing&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Receiving is more involved than switching a keying circuit. The input is analog audio, with interference and background noise from the radio path. Reliable decoding benefits from conditioning the signal to extract the frequency range of interest. A typical processing chain includes noise reduction, filtering, gain, decoding, and debouncing. The radio can handle the first three stages, leaving &lt;em&gt;Techno Keyer&lt;/em&gt; to focus on decoding.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/Input_Signal_Pre-Conditioning_tn.jpg&#34; alt=&#34;Audio signal conditioning&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;CW audio is commonly set to a tone between 500 and 1000 Hz, which can be isolated with a relatively narrow filter. I used an LM567 tone decoder to detect the selected frequency, setting its operating range through the surrounding circuit. An SPI-controlled digital potentiometer lets the MCU adjust the center frequency from the menu.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/schematic_audio_decode.jpg&#34; alt=&#34;Audio decoding circuit&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_decoding_the_signal&#34;&gt;Decoding the Signal&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Morse timing is based on the length of a dit. A dah lasts three units. The gap between elements is one unit, the gap between characters is three units, and the gap between words is seven units.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/PARIS_Morse_Code_Timing.jpg&#34; alt=&#34;Morse-code timing&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Human sending is less precise than computer-generated timing. Even the same operator’s elements vary in length. The decoder therefore needs to distinguish short and long elements despite those variations. I mainly followed K4ICY’s implementation, updating the classification threshold as signals arrive and using the geometric mean of the estimated dit and dah durations as a reference.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Reference project &lt;a href=&#34;http://www.k4icy.com/cw_decoder.html&#34; rel=&#34;noopener&#34; target=&#34;_blank&#34;&gt;K4ICY CW Decoder&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/cw_decoder_element_space_classification.jpg&#34; alt=&#34;Classifying Morse elements and gaps&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Updating the classifications dynamically lets the decoder adapt fairly quickly to different sending speeds. I generated test audio with different simulated operator skill levels. With a clear audio input and reasonably consistent sending, the results were generally good. Machine decoding also handled fast signals at 40 WPM.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;videoblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;video src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/cw_decode.mp4&#34; width=&#34;800&#34; height=&#34;450&#34; poster=&#34;/posts/projects/images/techno-keyer/cw_decode_poster.jpg&#34; controls=&#34;&#34;&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_assembly_and_bring_up&#34;&gt;Assembly and Bring-up&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_controlling_the_vfd&#34;&gt;Controlling the VFD&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The main challenge with the VFD was finding documentation. The Taobao seller supplied limited information and rudimentary example code. I eventually found the relevant manual and used its command descriptions to implement the display controls over SPI.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/VFD_command.jpg&#34; alt=&#34;VFD commands&#34; width=&#34;720&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_circuit_design&#34;&gt;Circuit Design&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I combined the hardware into a two-layer PCB. Using a ready-made ESP32 module kept fabrication costs down and saved me from designing an antenna or moving to a four-layer board. The trade-off was limited space. I also placed all surface-mount components on one side to simplify assembly, making the component side fairly dense.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/huhamhire/techno-keyer/blob/master/docs/schematics.pdf&#34; rel=&#34;noopener&#34; target=&#34;_blank&#34;&gt;Circuit schematics&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For the rotary encoder, I deliberately chose a right-angle version that could be operated from the edge of the PCB. It avoided an extra support structure or separate board for a conventionally mounted encoder.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/pcb_design.jpg&#34; alt=&#34;PCB layout&#34; width=&#34;720&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/pcb-components.jpg&#34; alt=&#34;PCB components&#34; width=&#34;720&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_enclosure_design&#34;&gt;Enclosure Design&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Once the core functions worked, I wanted a proper enclosure. While looking at other VFD projects, I found a triangular-prism clock whose appearance appealed to me.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/vfd_clock.jpg&#34; alt=&#34;VFD clock used as a design reference&#34; width=&#34;720&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I designed a similar triangular-prism enclosure around the dimensions of my VFD module. The central support and side covers were CNC-machined from aluminum alloy for a metal finish, while the front and rear panels were 3D-printed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/keyer_assemble.jpg&#34; alt=&#34;Exploded assembly view&#34; width=&#34;720&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Clear acrylic would have been my preferred material for the front panel. A dark-tinted translucent sheet could also have given the display a more polished appearance. I had yet to find a suitable supplier for such a small custom part, so I used transparent 3D-printing material for the time being.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I was pleased with the finished device. Twenty-first-century integrated circuits drive a twentieth-century vacuum fluorescent display to send a nineteenth-century communication code. Combining those technologies gave the project the retro character I had hoped for.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/techno-keyer/techno-keyer.jpg&#34; alt=&#34;Completed Techno Keyer&#34; width=&#34;720&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;This is BD4WEI, 73&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
- https://hamhire.tech/en/posts/projects/techno-keyer.html - Licensed under CC BY-NC-ND 4.0</description>
        </item>
    
    
    
        <item>
        <title>Dvorak Matrix — Building My Own Custom Keyboard from Scratch</title>
        <link>https://hamhire.tech/en/posts/projects/dvorak-keyboard.html</link>
        <pubDate>Tue, 12 Dec 2023 20:20:00 +0800</pubDate>
        
        <guid>https://hamhire.tech/en/posts/projects/dvorak-keyboard.html</guid>
        <description>Hamhire Tech Tips https://hamhire.tech/en/posts/projects/dvorak-keyboard.html -&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;After ten years of typing in Dvorak, I still wanted a keyboard that supported my layout natively. Commercial options were limited, so I had been relying on operating-system remapping to adapt the keyboards I used.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;By the end of the three pandemic years, the free Dell keyboard I had used in the office for years had shiny keycaps and worn-off legends. The custom-keyboard market was thriving, but much of the customization involved choosing combinations of standard parts. It reminded me of assembling the miniature four-wheel-drive cars I played with as a child. I wanted to try designing the keyboard itself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Custom PCBs had become affordable, and 3D printing and CNC machining were increasingly accessible. I decided to build a keyboard of my own.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_choosing_a_layout&#34;&gt;Choosing a Layout&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My reasons for switching from QWERTY to Dvorak years earlier were mostly about typing efficiency and comfort.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Speed and efficiency.&lt;/strong&gt; Dvorak places frequently used letters on the home row to reduce finger travel.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Less finger fatigue.&lt;/strong&gt; Shorter movements appealed to me as someone who spends long hours typing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Typing comfort.&lt;/strong&gt; Its letter arrangement aims to make common sequences more comfortable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Workload across both hands.&lt;/strong&gt; The layout aims to distribute typing work more evenly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/layout-compare.jpg&#34; alt=&#34;Keyboard layout comparison&#34; width=&#34;600&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The heat maps show frequently used keys concentrated on the home row, with all five vowels under the left hand. The benefits may be less apparent when entering Chinese, but as a programmer who types a great deal of English, I had found Dvorak comfortable over the previous ten years.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Keyboard shortcuts are its biggest inconvenience for me. Many applications position their shortcuts around QWERTY, and those combinations become awkward after remapping. Key-mapping tools or dedicated shortcuts on a Dvorak-oriented keyboard can compensate.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_defining_the_requirements&#34;&gt;Defining the Requirements&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Around 2015, I bought two TypeMatrix 2030 keyboards. Their built-in support for Dvorak was the main attraction.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/tmx-2030_gallery-1.png&#34; alt=&#34;TypeMatrix 2030&#34; width=&#34;600&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Several aspects of the design worked well for me.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Native Dvorak support&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dedicated alternatives for common copy-and-paste shortcuts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Centrally placed Backspace, Delete, and Enter keys that either hand could reach&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switching between Dvorak and QWERTY&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A compact footprint with a layered numeric keypad&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A regular ortholinear grid whose appearance I preferred to staggered rows&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My reservations came from using them.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Basic construction with membrane switches and a simple tray-style case&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;More right-side multimedia keys than I needed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No provision for spill resistance&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A price of US$110 each that felt high for the build quality&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Both keyboards eventually failed after water spills. The conductive membrane traces were damaged, especially by hot water, leaving individual keys unusable. Repairing those fine traces on plastic film was impractical.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I liked the TypeMatrix layout enough that I would have kept using it if the keyboards had survived. My own design therefore started from its layout and functions, with changes to suit my preferences.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I had also used standard-height mechanical keyboards, but disliked their long travel for sustained typing. Quiet operation mattered more to me than tactile feedback, and linear switches felt comfortable for prolonged use. After trying several switches, I settled on low-profile linear switches.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The basic goals were as follows.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Follow the TypeMatrix 2030 layout approach&lt;/p&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Support Dvorak&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keep Enter and other editing keys in the center&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remove unnecessary multimedia keys&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Support switching between layout layers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use low-profile linear switches&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use a wired connection&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/dvorak-mx-layout.png&#34; alt=&#34;First layout revision&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;My first revision followed the original closely, including 0.75U-high function keys, several unusual 2U keys, and a 5 × 1.25U spacebar. Finding ready-made keycaps for those dimensions was difficult, especially for low-profile switches. Custom PBT tooling made little sense for a one-off keyboard. After validating the first revision’s circuit and firmware, I adjusted the layout to fit commercially available keycaps.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;http://www.keyboard-layout-editor.com/&#34;&gt;Keyboard Layout Editor&lt;/a&gt; can be used to design the basic layout online.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The function keys became 1U. A 0.25U gap between the function row and the main typing area accommodated the centered 2U Backspace and 2.5U Enter keys. I changed the spacebar to the common 6.25U size and removed less-used keys such as right Alt and right Ctrl. The revised layout could use off-the-shelf keycaps.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/dvorak-mx-layout-v2.png&#34; alt=&#34;Second layout revision&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_circuit_design&#34;&gt;Circuit Design&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With the layout decided, I moved on to the electronics. I wanted a straightforward keyboard without elaborate extras or key backlighting. The familiar ATmega32U4 had enough GPIO pins for the design. An STM32 was another possibility, but I saw little benefit in the extra complexity and cost for this project.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/schematic_v2.png&#34; alt=&#34;Keyboard schematic&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_switch_matrix&#34;&gt;Switch Matrix&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A keyboard’s input mechanism is a matrix of switches. The MCU scans rows and columns through its GPIO pins to determine which switches are closed. I used a column-to-row diode orientation, placing a diode in series with every switch to prevent unwanted current paths when several keys are held simultaneously.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Since I had already defined the physical layout, drawing the matrix mainly involved arranging each switch and diode in its intended row and column.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/schematic-matrix.png&#34; alt=&#34;Switch matrix&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_usb_interface&#34;&gt;USB Interface&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A USB Type-C connector carries power and data. The keyboard has modest data-rate requirements, so the surrounding circuit mainly addresses ESD protection, shielding, and supply filtering.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;ulist&#34;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A USBLC6-2SC6 diode array provides ESD protection. Discrete protection devices could serve the same purpose, with a different board-area trade-off.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A ferrite bead and capacitors filter the supply, with a resettable fuse in the power path.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A 1 MΩ resistor and capacitor connect the connector shield to ground. The host’s USB grounding also contributes to the shielding arrangement.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/schematic-usb.png&#34; alt=&#34;USB interface circuit&#34; width=&#34;600&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The keyboard could operate without all of those protective components, but I wanted protection against faults and accidental static discharge. For a keyboard I was building for myself, the small additional cost was worthwhile.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The Type-C connector’s A5 and B5 pins are the configuration-channel contacts. For this USB-powered device, each needs its own pull-down resistor. Sharing one resistor can cause compatibility problems with some cables and power sources.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_mcu_connections&#34;&gt;MCU Connections&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The MCU circuit connects power, ground, data, and a crystal, with most remaining GPIO pins reserved for the switch matrix. Assigning matrix pins after considering the physical PCB layout helps reduce crossed traces and unnecessary vias.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/schematic-mcu.png&#34; alt=&#34;MCU circuit&#34; width=&#34;600&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A reset control is needed for firmware flashing. I left two small pads that can be bridged with a metal tool when necessary, avoiding a separate switch that would rarely be used.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_status_leds&#34;&gt;Status LEDs&lt;/h3&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/schematic-led.png&#34; alt=&#34;Status LED circuit&#34; width=&#34;400&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;With GPIO pins left over, I added a few LEDs for lock and layout status. Each LED needs a current-limiting resistor. The resistance depends on the LED and desired brightness, and I preferred subdued indicators that would remain comfortable to look at.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_pcb_layout&#34;&gt;PCB Layout&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The layout exported by Keyboard Layout Editor can be imported into online plate-generation tools to obtain a CAD drawing. That provides precise switch positions for placing the PCB footprints.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://plate.keeb.io/&#34;&gt;Keyboard Plate Generator&lt;/a&gt;
&lt;a href=&#34;https://kbplate.ai03.com/&#34;&gt;ai03 Plate Generator&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/plate_layout.png&#34; alt=&#34;Switch plate layout&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The switches and through-hole LEDs occupy the front of the board. Surface-mount components on that side would interfere with assembly. Double-sided SMT assembly also costs more, so I placed all surface-mount components on the back.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/pcb-design.png&#34; alt=&#34;PCB layout&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I wanted flexibility in switch selection, covering Kailh Choc v1/v2 and Cherry low-profile switches. Combining pad and hole positions, following examples from other GitHub projects, let me create a compatible footprint. The arrangement ruled out hot-swap sockets. I already preferred soldered switches, so that trade-off suited me.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;A standard 1U key pitch is 0.75 inches, or 19.05 mm. A 0.79375 mm grid, equal to 19.05 mm divided by 24, made component placement convenient. I used 0.25 mm signal traces, approximately 10 mil, and 0.40 mm power traces for additional current margin. The actual load was modest because the keyboard had no backlighting.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I also paid attention to several routing details.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;Route the USB data pair together with matched lengths and a continuous reference plane.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provide ground copper around the USB connector, MCU, and crystal where appropriate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keep the crystal close to the MCU.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I then placed the component footprints and routed the connections. Most switch footprints were rotated 90 degrees to simplify routing. Vertical traces generally ran on the top layer and horizontal traces on the bottom, reducing vias. Keeping components on the grid made the layout tidy. Rotating the MCU 45 degrees helped with pin access.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/pcb-mcu.png&#34; alt=&#34;MCU routing&#34; width=&#34;400&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I put the USB connector at the top center for convenient cable placement. Its protection circuitry occupied enough space that the neighboring switches needed different orientations. A mirrored arrangement made the result look orderly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/pcb-usb.png&#34; alt=&#34;USB connector routing&#34; width=&#34;400&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The ortholinear layout kept placement regular, but larger keys needed stabilizers. Mounting conventional stabilizers on the plate would add height to a low-profile keyboard. Several commercial designs instead used low-profile stabilizers mounted directly to the PCB. Wire stabilizers used on other keyboard designs offered another possibility. I made a small test PCB to compare the mounting arrangements.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/pcb_stablizer.jpg&#34; alt=&#34;Stabilizer test board&#34; width=&#34;480&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The low-profile MX-style stabilizers mounted on the PCB felt best and were easiest to work with. Their height also matched the switches. I added the corresponding mounting holes at the larger keys.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Finally, I reduced excess board area and tightened the space around the through-hole LEDs to control size, cost, and weight. After adding a few optional screw holes and silkscreen markings, the PCB design was complete.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/pcb-3d-front.png&#34; alt=&#34;PCB front&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/pcb-3d-back.png&#34; alt=&#34;PCB back&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_enclosure_design&#34;&gt;Enclosure Design&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The circuit and PCB still needed an enclosure to become a usable keyboard. Before drawing the case, I had to choose a mounting structure.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_choosing_a_mount&#34;&gt;Choosing a Mount&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Keyboard builders have developed several common mounting arrangements, each with different mechanical characteristics.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/mount-style.png&#34; alt=&#34;Keyboard mounting styles&#34; width=&#34;1080&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tray mount.&lt;/strong&gt; The PCB attaches directly to posts in a one-piece case. It has few parts and can be inexpensive to manufacture and assemble. Support-point placement can make the feel vary across the keyboard.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Case mount.&lt;/strong&gt; In top-mount and bottom-mount designs, the plate attaches to the upper or lower case. The plate distributes typing loads and structural support, with its material strongly influencing the feel. The PCB carries less mechanical load.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sandwich mount.&lt;/strong&gt; An enlarged plate is clamped between the upper and lower case. Assembly is straightforward, but the mating parts need consistent dimensions. Rigid clamping produces a relatively firm feel.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Plateless mount.&lt;/strong&gt; The PCB itself supports the switches, using an arrangement similar to tray or top mounting. It can feel softer, while placing more mechanical load on the PCB.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Integrated plate.&lt;/strong&gt; The upper case and plate form a single part, reducing the part count. Its rigid structure can also amplify typing noise.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Gasket mount.&lt;/strong&gt; Cushioning material separates the plate assembly from the case, softening the feel compared with a rigid sandwich mount. The additional materials and mounting details can increase cost.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I prefer a relatively firm but consistent typing feel. Top mount suited that preference best. Its parts were more involved to design, but I wanted to try it.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect2&#34;&gt;
&lt;h3 id=&#34;_mechanical_design&#34;&gt;Mechanical Design&lt;/h3&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Having chosen low-profile switches, I wanted to minimize the enclosure height. The upper and lower parts needed to locate and support the keyboard without interfering with its components. Keeping the design simple also reduced material and weight. A smaller internal cavity helped limit hollow typing noise.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/case-4.png&#34; alt=&#34;Enclosure side view&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I followed a workflow I knew well. I first drew precise, layered outlines in CAD, then built the solid models in SolidWorks from those profiles. Although I could have done the whole job in a 3D tool, I liked the precision and efficiency of command-driven CAD. Drawing the parts brought back memories of mechanical-drawing classes at university more than a decade earlier, including drawing by hand.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/cad-design.png&#34; alt=&#34;CAD design&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Exporting the PCB model let me check component clearances in the mechanical assembly. Collision checks were useful when reducing the case thickness and positioning reinforcement ribs. The USB opening also required careful checking because it spans the upper and lower case.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/case-5.png&#34; alt=&#34;USB connector opening&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The enclosure has three main parts, the upper case, switch plate, and lower case. Their fastening points follow the top-mount arrangement. I considered ordering the plate from a specialist keyboard supplier, but ultimately chose the same aluminum alloy and anodized finish as the upper case, so I had both CNC-machined together. Cutouts and recessed areas reduced weight where material was unnecessary. A slightly wider left border holds three holes for the through-hole LEDs, avoiding separate light pipes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I allowed roughly 0.1–0.2 mm for fit between mating parts. The lower case would be 3D-printed in ABS. Its tolerances could be looser, but the material’s flexibility made the actual fit manageable. Two lengths of M2 flat-head screws hold the assembly together, with threaded holes in the aluminum upper case.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/case-6.png&#34; alt=&#34;Enclosure layers&#34; width=&#34;1080&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_firmware&#34;&gt;Firmware&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The hardware needed firmware to implement its input behavior. For a wired ATmega32U4 keyboard, QMK was an established framework that already provided the core keyboard functions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;quoteblock&#34;&gt;
&lt;blockquote&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://qmk.fm/&#34;&gt;QMK Firmware&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Writing code is my day job, so starting a QMK project from its documentation was straightforward. With a nonstandard layout, I had expected to work directly in code from the outset. I also prefer organizing the implementation in a way I can comfortably read and maintain.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/firmware.png&#34; alt=&#34;Firmware development&#34; width=&#34;1080&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;QMK supplies keycode definitions, layer switching and combinations, shortcut handling, matrix scanning, and much of the other basic behavior. My main work was defining the keymaps and layout-switching logic. MCU pin assignments go in &lt;code&gt;info.json&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I used four layers for the default Dvorak layout, an alternative QWERTY layout, function combinations, and a numeric keypad.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-c hljs&#34; data-lang=&#34;c&#34;&gt;enum mx_layers {
    _DVORAK = 0,
    _QWERTY,
    _FN,
    _NUMPAD
};

enum mx_keycodes {
    TG_DV = SAFE_RANGE,
    NUM_00
};

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    // Dvorak Layout (Default)
    [_DVORAK] = LAYOUT_dvorak_matrix(
        KC_ESC, KC_F1,  KC_F2,  KC_F3,  KC_F4,  KC_F5,  KC_DEL, KC_F6,  KC_F7,  KC_F8,  KC_F9,  KC_F10, KC_F11, KC_F12,
        KC_GRV, KC_1,   KC_2,   KC_3,   KC_4,   KC_5,   KC_BSPC,KC_6,   KC_7,   KC_8,   KC_9,   KC_0,   KC_LBRC,KC_RBRC,
        KC_TAB, KC_QUOT,KC_COMM,KC_DOT, KC_P,   KC_Y,           KC_F,   KC_G,   KC_C,   KC_R,   KC_L,   KC_SLSH,KC_EQL,
                KC_A,   KC_O,   KC_E,   KC_U,   KC_I,   KC_ENT, KC_D,   KC_H,   KC_T,   KC_N,   KC_S,   KC_MINS,
        KC_LSFT,KC_SCLN,KC_Q,   KC_J,   KC_K,   KC_X,           KC_B,   KC_M,   KC_W,   KC_V,   KC_Z,   KC_BSLS,KC_RSFT,
        KC_LCTL,KC_LGUI,        KC_LALT,                KC_SPC,                         KC_HOME,KC_UP,  KC_END, KC_PGUP,
        MO(_FN),                                                                        KC_LEFT,KC_DOWN,KC_RGHT,KC_PGDN
    ),
    // Qwerty Layout
    [_QWERTY] = LAYOUT_dvorak_matrix(
        KC_ESC, KC_F1,  KC_F2,  KC_F3,  KC_F4,  KC_F5,  KC_DEL, KC_F6,  KC_F7,  KC_F8,  KC_F9,  KC_F10, KC_F11, KC_F12,
        KC_GRV, KC_1,   KC_2,   KC_3,   KC_4,   KC_5,   KC_BSPC,KC_6,   KC_7,   KC_8,   KC_9,   KC_0,   KC_MINS,KC_EQL,
        KC_TAB, KC_Q,   KC_W,   KC_E,   KC_R,   KC_T,           KC_Y,   KC_U,   KC_I,   KC_O,   KC_P,   KC_LBRC,KC_RBRC,
                KC_A,   KC_S,   KC_D,   KC_F,   KC_G,   KC_ENT, KC_H,   KC_J,   KC_K,   KC_L,   KC_SCLN,KC_QUOT,
        KC_LSFT,KC_Z,   KC_X,   KC_C,   KC_V,   KC_B,           KC_N,   KC_M,   KC_COMM,KC_DOT, KC_SLSH,KC_BSLS,KC_RSFT,
        KC_LCTL,KC_LGUI,        KC_LALT,                KC_SPC,                         KC_HOME,KC_UP,  KC_END, KC_PGUP,
        MO(_FN),                                                                        KC_LEFT,KC_DOWN,KC_RGHT,KC_PGDN
    ),
    // Functions
    [_FN] = LAYOUT_dvorak_matrix(
        _______,TG_DV,  _______,_______,_______,_______,KC_INS, _______,_______,_______,_______,KC_PSCR,_______,TG(_NUMPAD),
        _______,_______,_______,_______,_______,_______,KC_VOLU,_______,_______,_______,_______,_______,_______,_______,
        _______,_______,_______,_______,_______,_______,        _______,_______,_______,_______,KC_PSLS,KC_PAST,KC_PMNS,
                _______,_______,_______,_______,_______,KC_VOLD,_______,_______,_______,KC_P7,  KC_P8,  KC_P9,
        KC_CAPS,_______,_______,_______,_______,_______,        _______,_______,_______,KC_P4,  KC_P5,  KC_P6,  KC_PPLS,
        _______,C(KC_INS),      S(KC_INS),              KC_MUTE,                        KC_P1,  KC_P2,  KC_P3,  KC_PENT,
        _______,                                                                        KC_P0, NUM_00,  KC_PDOT,KC_PENT
    ),
    // Number Pad
    [_NUMPAD] = LAYOUT_dvorak_matrix(
        _______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,
        _______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,
        _______,_______,_______,_______,_______,_______,        _______,_______,_______,_______,KC_PSLS,KC_PAST,KC_PMNS,
                _______,_______,_______,_______,_______,_______,_______,_______,_______,KC_P7,  KC_P8,  KC_P9,
        _______,_______,_______,_______,_______,_______,        _______,_______,_______,KC_P4,  KC_P5,  KC_P6,  KC_PPLS,
        _______,_______,        _______,                _______,                        KC_P1,  KC_P2,  KC_P3,  KC_PENT,
        _______,                                                                        KC_P0, NUM_00,  KC_PDOT,KC_PENT
    ),
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The custom key handlers switch between Dvorak and QWERTY and provide a double-zero key, similar to the &lt;code&gt;00&lt;/code&gt; key on some calculators. Following TypeMatrix’s approach, Fn combinations on the left side provide copy and paste shortcuts that remain convenient with Dvorak.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;listingblock&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;pre class=&#34;highlightjs highlight&#34;&gt;&lt;code class=&#34;language-c hljs&#34; data-lang=&#34;c&#34;&gt;bool process_record_user(uint16_t keycode, keyrecord_t *record) {
// Debug Output
#ifdef CONSOLE_ENABLE
    uprintf(&amp;#34;KL: kc: 0x%04X, col: %2u, row: %2u, pressed: %u, time: %5u, int: %u, count: %u\n&amp;#34;, keycode, record-&amp;gt;event.key.col, record-&amp;gt;event.key.row, record-&amp;gt;event.pressed, record-&amp;gt;event.time, record-&amp;gt;tap.interrupted, record-&amp;gt;tap.count);
#endif
    switch (keycode) {
        case TG_DV:
            // Toggle Dvorak Layer
            if (record-&amp;gt;event.pressed) {
                if (IS_LAYER_ON_STATE(default_layer_state, _QWERTY)) {
                    default_layer_set(1 &amp;lt;&amp;lt; _DVORAK);
                    set_single_persistent_default_layer(_DVORAK);
                } else {
                    default_layer_set(1 &amp;lt;&amp;lt; _QWERTY);
                    set_single_persistent_default_layer(_QWERTY);
                }
            }
            return false;
        case NUM_00:
            // Double Zero
            if (record-&amp;gt;event.pressed) {
                SEND_STRING(&amp;#34;00&amp;#34;);
            }
            return false;
        default:
            return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;After compiling the firmware with QMK, I used QMK Toolbox and the PCB’s reset pads to flash it onto the MCU.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_manufacturing_and_assembly&#34;&gt;Manufacturing and Assembly&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I ordered the PCB and SMT assembly through JLCPCB. The assembly service could place the surface-mount components along with the board order. Since I designed the circuit in KiCad, my workflow involved importing it into EasyEDA, associating the fabrication parts, checking the result, and uploading the board and assembly files. At order confirmation, I paid particular attention to the orientation and polarity of the MCU, crystal, ICs, and diodes. Automatic placement still needed a manual check.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/product-pcb.png&#34; alt=&#34;Manufactured PCB&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;For the upper case and plate, I used JLC’s 3D Monkey CNC service with 6061 aluminum and a space-gray anodized finish. The same service printed the ABS lower case. Threaded features required separate 2D drawings with machining notes, which were straightforward to prepare in SolidWorks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/top-case-design.png&#34; alt=&#34;CNC manufacturing drawing&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I also considered softer 5-series aluminum and pilot holes that could be tapped during screw installation as a possible way to reduce machining cost.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Once the orders were placed, there was a short wait. The PCB arrived relatively quickly. CNC machining and 3D printing took a little longer, possibly because I ordered during the National Day holiday and there was a backlog. Everything was finished in about a week.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The machined upper case and plate fit together neatly. Snapping switches into the plate was satisfying, a little like assembling Lego.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/assemble_1.jpg&#34; alt=&#34;Machined parts and installed switches&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Next, I positioned the switches in the plate, soldered them to the PCB, and installed the stabilizers and wires. The Kailh 1350 stem design limited keycap choices, particularly with switches rotated 90 degrees. I ultimately chose Cherry low-profile switches for their cross-shaped stems, keycap compatibility, and relatively quiet operation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/assemble_2.jpg&#34; alt=&#34;Keyboard assembly&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The unusual Dvorak arrangement needed uniform-profile keycaps, with the same shape across rows. I chose a set from the NuPhy Air96 that suited the anodized case. Their PBT material also appealed to me for long-term use and resistance to developing a shiny surface.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/nuphy-air96-keycaps.png&#34; alt=&#34;NuPhy Air96 keycaps&#34; width=&#34;600&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The finished space-gray aluminum case and predominantly gray keycaps looked good together.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;imageblock text-center&#34;&gt;
&lt;div class=&#34;content&#34;&gt;
&lt;img src=&#34;https://hamhire.tech/posts/projects/images/dvorak-mx/assemble_3.jpg&#34; alt=&#34;Completed keyboard&#34; width=&#34;800&#34;/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;sect1&#34;&gt;
&lt;h2 id=&#34;_afterword&#34;&gt;Afterword&lt;/h2&gt;
&lt;div class=&#34;sectionbody&#34;&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;I planned to use the keyboard for a while before deciding what to change in another revision. The Fn combinations felt slightly awkward because I had been conservative about key spacing. I also wanted a rotary control, but finding a smooth, non-detented encoder that fit the low-profile height was proving harder than expected.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;The project gave me a taste of the enjoyment that technology creators such as He Tongxue and Zhihui Jun find in designing their own devices. I had never taken an electronic product through the entire process from an initial idea to manufacturing. Completing it over roughly two weeks of spare time was satisfying. It reminded me of the resourceful engineers in Jules Verne’s nineteenth-century novels, solving one practical problem after another through engineering.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;Access to affordable manufacturing also made a strong impression. For a small personal project, turning an idea into a physical object was remarkably accessible. I wondered what further designs might become possible if custom integrated-circuit fabrication became similarly approachable one day.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;div class=&#34;paragraph&#34;&gt;
&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;olist arabic&#34;&gt;
&lt;ol class=&#34;arabic&#34;&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://www.keyboard.university/&#34;&gt;Keyboard University&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://wiki.ai03.com/books/pcb-design&#34;&gt;ai03 PCB Design Guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.qmk.fm/#/&#34;&gt;QMK Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://zhuanlan.zhihu.com/p/133532919&#34;&gt;Keyboard design reference on Zhihu (Chinese)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
- https://hamhire.tech/en/posts/projects/dvorak-keyboard.html - Licensed under CC BY-NC-ND 4.0</description>
        </item>
    
    
  </channel>
</rss>
