<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://domoriks.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://domoriks.com/" rel="alternate" type="text/html" /><updated>2026-03-24T08:24:44+00:00</updated><id>https://domoriks.com/feed.xml</id><title type="html">Domoriks</title><subtitle>Open source decentralised home automation hardware and software. Custom DIN-rail modules, RS-485 bus communication, and seamless Home Assistant integration — no cloud, no lock-in.</subtitle><author><name>Domoriks Contributors</name></author><entry><title type="html">Designing DIN-Rail PCBs in KiCad: Lessons From the Relay Board</title><link href="https://domoriks.com/pcb-design-notes/" rel="alternate" type="text/html" title="Designing DIN-Rail PCBs in KiCad: Lessons From the Relay Board" /><published>2025-03-25T00:00:00+00:00</published><updated>2025-03-25T00:00:00+00:00</updated><id>https://domoriks.com/pcb-design-notes</id><content type="html" xml:base="https://domoriks.com/pcb-design-notes/"><![CDATA[<p>The relay board is physically simple: power rail, MCU, RS-485 transceiver, eight relay driver circuits, eight relays. But designing it to live comfortably inside a DIN-rail cabinet required thinking about things that don’t come up in hobbyist projects.</p>

<p>Here are the design decisions and the reasoning behind them.</p>

<h2 id="din-rail-form-factor">DIN-rail form factor</h2>

<p>A standard DIN-rail module unit is 17.5 mm wide. “4 units” means 70 mm — but in practice, DIN modules are 72 mm wide, with the 2 mm shared between adjacent modules. This means the PCB must be <strong>narrower than the enclosure</strong>, typically by about 4 mm on each side.</p>

<p>For the relay board I ended up at 72 mm wide × 90 mm tall. The PCB sits on a custom front panel (the part you can see when the cabinet is closed) that snaps onto the DIN rail clip.</p>

<p>The key constraint is <strong>height</strong>. DIN-rail cabinets use horizontal rail spacing, and most standard cabinets have rails spaced 125 mm apart. Your module (PCB + front panel + whatever protrudes from the back) must fit comfortably in 125 mm.</p>

<h2 id="relay-selection">Relay selection</h2>

<p>The relays are the physically dominant component. I went with the <strong>Omron G5LE-1</strong> (5V coil, SPDT, 10A @ 250V AC). They’re common, well-documented, available from multiple distributors, and the footprint is standard.</p>

<p>The coil is 5V, which is unusual for a 24V-powered board. I drive the relay coils from the onboard 5V rail (from a 24→5V DC/DC), switched by N-MOSFETs (BSS138) controlled by the STM32 GPIOs. Each MOSFET has a flyback diode (1N4148) to absorb the coil’s inductive kick when the relay de-energises.</p>

<p>I considered 24V coil relays to eliminate the 5V rail entirely, but the MCU still needs a low-voltage supply and the RS-485 transceiver runs at 3.3V, so a multi-output DC/DC was unavoidable regardless.</p>

<h2 id="creepage-and-clearance">Creepage and clearance</h2>

<p>This is the part that trips up most PCB designers who are new to mains-voltage boards.</p>

<p>IEC 60664-1 (the relevant standard for insulation coordination in low-voltage equipment) specifies minimum distances between conductors at different potentials:</p>

<ul>
  <li><strong>Creepage</strong> (distance along the PCB surface between conductors)</li>
  <li><strong>Clearance</strong> (shortest distance through air between conductors)</li>
</ul>

<p>For 230V AC mains on a PCB intended for use in a fixed installation (Pollution Degree 2, Overvoltage Category II), the minimum clearance is <strong>3 mm</strong> and creepage is typically <strong>6 mm</strong>.</p>

<p>This affects relay footprint design, conductor routing near relay contact pads, and the PCB edge clearance. It also means you cannot route any 3.3V signal traces near the relay contact pads without explicit spacing checks.</p>

<p>In KiCad, I set up a custom net class for the mains-side nets with expanded clearance rules and ran DRC to verify. It’s not perfect (KiCad doesn’t do full IEC 60664 calculations), but it catches obvious violations.</p>

<div class="my-6 border-l-4 rounded-r-xl px-5 py-4
  border-yellow-400 bg-yellow-800/50
  ">
  <p class="font-mono font-semibold text-sm mb-1
    text-yellow-200
    ">
    ⚠ 
    This is not a safety certification
  </p>
  <p class="text-sm text-zinc-300 leading-relaxed">The Domoriks relay board is open hardware for personal use. It has not been tested or certified to any safety standard. If you build one, have it inspected by a qualified electrician before installation. Mains voltages are dangerous.</p>
</div>

<h2 id="the-rs-485-driver">The RS-485 driver</h2>

<p>The <strong>SN65HVD3082E</strong> from TI is a robust choice: 15 kV ESD protection on the bus pins, ±12V common-mode range, and a low-power standby mode. I added a pair of <strong>TVS diodes</strong> (SMAJ6.5A) on the A and B lines for additional surge protection — the RS-485 cable runs close to mains wiring inside the cabinet, and induced transients are a real concern.</p>

<p>The driver is configured in half-duplex mode (single pair, direction controlled by the MCU’s DE/RE pins). The STM32’s USART in RS-485 mode handles the DE/RE assertion timing automatically, which is a nice hardware feature.</p>

<h2 id="pcb-stackup-and-copper-pour">PCB stackup and copper pour</h2>

<p>I used standard 2-layer FR4, 1 oz copper. The ground plane is on the bottom layer; signal and power traces on top. The key decision was the pour strategy near the relays: I excluded copper pour on the top layer inside the relay contact area to maintain creepage distances, and added a fiducial mark to help visual inspection.</p>

<h2 id="what-i-changed-in-revision-11">What I changed in revision 1.1</h2>

<p>The first prototype had two issues:</p>

<ol>
  <li>
    <p><strong>Power LED too bright</strong> — 3mm green LED with a 470Ω series resistor. Changed to 2.2kΩ. Now it’s visible without being aggressive in a dark cabinet.</p>
  </li>
  <li>
    <p><strong>DIP switch placement</strong> — I put it on the back of the PCB in revision 1.0. Inconvenient for address changes after installation. Moved to the front panel in 1.1, accessible from the front without removing the module.</p>
  </li>
</ol>

<p>The KiCad files in the repository are for revision 1.1.</p>]]></content><author><name>Domoriks</name></author><category term="hardware" /><category term="kicad" /><category term="pcb" /><summary type="html"><![CDATA[The relay board is physically simple: power rail, MCU, RS-485 transceiver, eight relay driver circuits, eight relays. But designing it to live comfortably inside a DIN-rail cabinet required thinking about things that don’t come up in hobbyist projects.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://picsum.photos/900/400?random=403" /><media:content medium="image" url="https://picsum.photos/900/400?random=403" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Building a Home Assistant Integration From Scratch</title><link href="https://domoriks.com/building-ha-integration/" rel="alternate" type="text/html" title="Building a Home Assistant Integration From Scratch" /><published>2025-03-10T00:00:00+00:00</published><updated>2025-03-10T00:00:00+00:00</updated><id>https://domoriks.com/building-ha-integration</id><content type="html" xml:base="https://domoriks.com/building-ha-integration/"><![CDATA[<p>The Domoriks Home Assistant integration is the part I spent the most time on. Getting hardware to talk Modbus RTU is relatively straightforward — writing a HA integration that feels like it belongs in the platform took more iteration.</p>

<p>Here’s what I learned.</p>

<h2 id="the-config_flow">The config_flow</h2>

<p>The thing that makes an integration feel native is the <code class="language-plaintext highlighter-rouge">config_flow</code> — the UI-based setup wizard. Without it, users have to edit <code class="language-plaintext highlighter-rouge">configuration.yaml</code>, which is not a friendly experience for something that should be plug-and-play.</p>

<p>HA’s config flow is a multi-step wizard driven by Python classes. Each step returns either a form schema (asking the user for input) or a result (success, error, or abort). The tricky part is that you need to validate user input on the server side and report errors back to the form in a way HA understands.</p>

<p>For Domoriks, the flow has three steps:</p>

<ol>
  <li><strong>Serial port</strong> — dropdown of detected ports + baud rate</li>
  <li><strong>Module discovery</strong> — non-interactive, shows discovered modules</li>
  <li><strong>Output naming</strong> — dynamic form generated per module per channel</li>
</ol>

<p>The dynamic form on step 3 was the interesting challenge. HA’s <code class="language-plaintext highlighter-rouge">vol.Schema</code> (voluptuous) is evaluated at class definition time, but the number of fields depends on what modules were found at step 2. The solution is to build the schema dictionary dynamically in the step handler and pass it to <code class="language-plaintext highlighter-rouge">vol.Schema()</code> at runtime.</p>

<h2 id="entity-lifecycle">Entity lifecycle</h2>

<p>HA entities need to be set up, maintained, and removed cleanly. Domoriks uses the <code class="language-plaintext highlighter-rouge">DataUpdateCoordinator</code> pattern:</p>

<ol>
  <li>The coordinator runs a background task that polls the bus on a fixed interval</li>
  <li>All entities subscribe to coordinator updates</li>
  <li>When the coordinator gets fresh data, all entities update their state in one cycle</li>
  <li>If the coordinator encounters a bus error, all entities are marked unavailable</li>
</ol>

<p>This is cleaner than having each entity poll independently, and it ensures all state updates arrive atomically (no partial updates where relay 1 is current and relay 8 is stale).</p>

<h2 id="the-gateway-device">The gateway device</h2>

<p>Something I added that isn’t in many integrations: a dedicated “gateway” device that represents the serial connection itself, not any physical module. It has:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">last_rx</code> and <code class="language-plaintext highlighter-rouge">last_tx</code> sensors (useful for confirming the bus is actually alive)</li>
  <li>A <code class="language-plaintext highlighter-rouge">bus_status</code> sensor that goes to <code class="language-plaintext highlighter-rouge">timeout</code> or <code class="language-plaintext highlighter-rouge">error</code> if modules stop responding</li>
  <li>A raw command text + button pair for debugging</li>
</ul>

<p>The raw command interface was particularly useful during development. Rather than writing a test script every time I wanted to send a Modbus frame, I could just type the hex bytes in a HA dashboard card and press the button. It’s now documented as a feature, but it started as a debugging tool.</p>

<h2 id="diagnostics">Diagnostics</h2>

<p>HA has a built-in diagnostics framework — implement <code class="language-plaintext highlighter-rouge">async_get_config_entry_diagnostics</code> and HA adds a <strong>Download Diagnostics</strong> button to the integration page automatically. The returned dict gets serialised to JSON.</p>

<p>I include bus stats (TX/RX counts, error rates), module states, and entity metadata in the diagnostics dump. When someone opens an issue, asking them to share their diagnostics JSON gives me almost everything I need to understand their setup.</p>

<h2 id="testing">Testing</h2>

<p>Integration testing against a real RS-485 bus from a CI environment is tricky. I solve this with a lightweight test harness: a Python script that simulates a Modbus RTU slave over a virtual serial port (<code class="language-plaintext highlighter-rouge">socat</code> creates a pty pair). Tests run against the fake device on every PR.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># In CI:</span>
socat PTY,link<span class="o">=</span>/tmp/ttyDMR0,rawer PTY,link<span class="o">=</span>/tmp/ttyDMR1,rawer &amp;
python tests/fake_modbus_slave.py <span class="nt">--port</span> /tmp/ttyDMR1 <span class="nt">--address</span> 1 &amp;
pytest tests/integration/ <span class="nt">--port</span> /tmp/ttyDMR0
</code></pre></div></div>

<p>It’s not perfect — it doesn’t simulate RS-485 bus errors or timing jitter — but it catches regressions in the integration logic.</p>

<h2 id="what-id-do-differently">What I’d do differently</h2>

<p>The output naming step in the config flow generates one field per channel per module. With two modules of 8 channels each, that’s 16 fields on one form. Workable, but not elegant. I’d like to split this into a per-module flow where you name one module at a time, like HA’s own Zigbee integration does.</p>

<p>If you’re building a HA integration and have questions about any of this, open a discussion on the repo. Happy to help.</p>]]></content><author><name>Domoriks</name></author><category term="home-assistant" /><category term="python" /><category term="tutorial" /><summary type="html"><![CDATA[The Domoriks Home Assistant integration is the part I spent the most time on. Getting hardware to talk Modbus RTU is relatively straightforward — writing a HA integration that feels like it belongs in the platform took more iteration.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://picsum.photos/900/400?random=402" /><media:content medium="image" url="https://picsum.photos/900/400?random=402" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Why I Built Domoriks</title><link href="https://domoriks.com/why-domoriks/" rel="alternate" type="text/html" title="Why I Built Domoriks" /><published>2025-02-15T00:00:00+00:00</published><updated>2025-02-15T00:00:00+00:00</updated><id>https://domoriks.com/why-domoriks</id><content type="html" xml:base="https://domoriks.com/why-domoriks/"><![CDATA[<p>I have a fairly standard Belgian semi-detached house, built in the 1970s. Standard wiring, standard distribution board, standard dumb switches on the walls.</p>

<p>When I decided to automate the lights, I spent a few weeks researching options. Most of them shared one or more of the following problems:</p>

<p><strong>Cloud dependency.</strong> A surprising number of “smart” products are dumb hardware with a cloud relay. If the company decides to shut down, pivots their business model, or just has an outage, your lights stop working. I own my house. I want to own my home automation too.</p>

<p><strong>Replacing the switches.</strong> Most smart switch solutions require replacing your existing switches with new “smart” ones. In Belgium, many people have built-in wall plates that are architecturally integrated — not something you want to rip out. And if you’re renting, forget it.</p>

<p><strong>Complexity hidden in simplicity.</strong> I tried a few “easy” solutions. They were easy until something went wrong, at which point the debugging surface was enormous — cloud, app, Wi-Fi, firmware, hub — and I had no visibility into any of it.</p>

<p><strong>Price.</strong> Eight “smart” bulbs from a major brand, plus the hub, comes to a lot of money. For what is, functionally, some relays and a wireless radio.</p>

<h2 id="what-i-wanted">What I wanted</h2>

<p>My requirements were simple:</p>

<ol>
  <li>Works entirely locally — no internet required</li>
  <li>Existing wall switches continue to work physically, not just as “smart” devices</li>
  <li>Lives inside my distribution cabinet, not on a wall somewhere</li>
  <li>Standard HA entities — I can automate them the usual way</li>
  <li>Open schematics and source — I can fix it myself in five years</li>
</ol>

<p>RS-485 with Modbus RTU is the obvious answer. It’s used in industrial automation for exactly this reason: simple, robust, long distances, no IP stack required. An STM32 with a handful of relays and a 485 transceiver is a very small, very understandable system.</p>

<h2 id="building-it">Building it</h2>

<p>The first relay board took about two evenings in KiCad. Ordered prototypes from JLCPCB. Soldered them by hand. Wrote a minimal Modbus RTU slave implementation from scratch — it’s maybe 400 lines of C.</p>

<p>The Home Assistant integration took longer. The existing Modbus integration in HA is powerful but complex to configure via YAML. I wanted something that felt native: UI setup, named entities, no <code class="language-plaintext highlighter-rouge">configuration.yaml</code> editing. Python, pymodbus, the config_flow API.</p>

<p>The result is Domoriks. It’s in my own distribution board right now, has been running for several months, and has worked correctly every day since commissioning. My regular wall switches still click satisfyingly. The HA dashboard has a switch for each circuit. Automations work the same as with any other entity.</p>

<h2 id="whats-next">What’s next</h2>

<p>The immediate plan is to tidy up the documentation, add the switch input module (for reading push buttons and toggle switches without the mains circuit involvement), and publish everything properly.</p>

<p>If you’re building something similar — or want to adapt this for your own house — the repos are all public and the licence is MIT. Open an issue if you have questions.</p>]]></content><author><name>Domoriks</name></author><category term="story" /><category term="hardware" /><category term="home-automation" /><summary type="html"><![CDATA[I have a fairly standard Belgian semi-detached house, built in the 1970s. Standard wiring, standard distribution board, standard dumb switches on the walls.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://picsum.photos/900/400?random=401" /><media:content medium="image" url="https://picsum.photos/900/400?random=401" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>