<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: The Refactored Road</title>
    <description>The latest articles on DEV Community by The Refactored Road (@ndygen).</description>
    <link>https://dev.to/ndygen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1749369%2Fdc576384-ef56-4258-a0f1-06d76fc61940.png</url>
      <title>DEV Community: The Refactored Road</title>
      <link>https://dev.to/ndygen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ndygen"/>
    <language>en</language>
    <item>
      <title>Solar-Aware Appliance Scheduling with Homey — How I Built It</title>
      <dc:creator>The Refactored Road</dc:creator>
      <pubDate>Mon, 06 Apr 2026 16:32:17 +0000</pubDate>
      <link>https://dev.to/ndygen/solar-aware-appliance-scheduling-with-homey-how-i-built-it-4chm</link>
      <guid>https://dev.to/ndygen/solar-aware-appliance-scheduling-with-homey-how-i-built-it-4chm</guid>
      <description>&lt;h2&gt;
  
  
  Since last time
&lt;/h2&gt;

&lt;p&gt;A week ago I wrote about &lt;a href="https://refactoredroad.blogspot.com/2026/03/my-washing-machine-now-picks-its-own.html" rel="noopener noreferrer"&gt;teaching my washing machine to pick its own schedule&lt;/a&gt; — one of my favorite smart home projects so far. At the end of that post I mentioned wanting cumulative savings tracking. That shipped in v1.3.0 about two days later — the device dashboard now shows per-schedule and lifetime savings.&lt;/p&gt;

&lt;p&gt;A few other things landed between then and now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;v1.3.0&lt;/strong&gt; — Savings tracking, Frank Energie all-in pricing (spot + tax + markup + VAT), and a bunch of bug fixes including a UTC offset issue that served yesterday's prices during summer time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v1.4.0&lt;/strong&gt; — Flow triggers for price changes and status changes, so you can build automations like "notify me when the price drops below X."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v1.5.0&lt;/strong&gt; — A "schedule cheapest start by time" action card. Instead of "within 6 hours," you say "before 14:00" and the scheduler figures out the window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v1.5.1&lt;/strong&gt; — The rounding bug from the last post came back in disguise: low-power appliances like a phone charger showed EUR 0.00 savings because the rounding happened too early in the pipeline. Fixed by deferring all rounding to the display layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All useful, all driven by actually living with the app. But then I looked at my roof.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solar problem
&lt;/h2&gt;

&lt;p&gt;I have solar panels. When the sun is shining, I'm generating electricity that I either use or export to the grid. The feed-in tariff — what my energy company pays me for exported power — is about EUR 0.07/kWh. Meanwhile, buying from the grid costs EUR 0.15–0.35/kWh depending on the hour.&lt;/p&gt;

&lt;p&gt;The old Power Profiler didn't know about any of this. It looked at grid prices and scheduled my washing machine at 3 AM because that's when electricity is cheapest. Technically correct. But if the sun is going to produce 3 kW of surplus at noon, running the washing machine then effectively costs me EUR 0.07/kWh (the feed-in I'm giving up) instead of EUR 0.25/kWh from the grid at 3 AM.&lt;/p&gt;

&lt;p&gt;Simple fix, right? Just tell each device "solar hours are cheap." Except there's a catch.&lt;/p&gt;

&lt;p&gt;I have a washing machine, a dryer, and a dishwasher. If all three independently decide that the noon slot is basically free, they'll all schedule into it. Their combined demand — maybe 4.5 kW — exceeds my 3 kW solar surplus, and the difference comes from the grid at full price. None of them accounted for the others. I call this the stampede problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Effective pricing
&lt;/h2&gt;

&lt;p&gt;The solution is a centralized pricing service — a Virtual Energy Supplier — that computes an &lt;em&gt;effective price&lt;/em&gt; per time slot. The effective price blends what your cheap sources (solar) and the grid contribute, weighted by how much capacity is actually available.&lt;/p&gt;

&lt;p&gt;The math works like a waterfall:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take all cheap sources (PV panels, future: battery), sorted by cost&lt;/li&gt;
&lt;li&gt;Subtract base load — your fridge, router, standby devices eat into PV first&lt;/li&gt;
&lt;li&gt;Subtract capacity already reserved by other scheduled appliances&lt;/li&gt;
&lt;li&gt;Whatever cheap capacity is left goes to this appliance&lt;/li&gt;
&lt;li&gt;The rest comes from the grid&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The blended price is then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;effectivePrice = (cheapKwh × cheapCost + gridKwh × gridPrice) / totalKwh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a zero-cost source like solar, the "cost" is the feed-in tariff — the opportunity cost of not exporting. This is economically correct: using your own solar isn't free, it's worth whatever you'd have earned by selling it.&lt;/p&gt;

&lt;p&gt;One guard: the effective price never exceeds the grid price. If the math somehow produces a higher number (it shouldn't, but floating point), we cap it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowing your base load
&lt;/h2&gt;

&lt;p&gt;Here's something I didn't think about initially: not all solar production is available for scheduled appliances. Your house has a base load — the fridge cycling, the router, standby power, HVAC — that eats into PV output before anything else gets a turn.&lt;/p&gt;

&lt;p&gt;My house draws about 200–400W overnight, 500–800W during the day. A fixed estimate of "500W base load" works okay, but it's wrong for every specific half-hour of the day.&lt;/p&gt;

&lt;p&gt;If you have a smart meter connected to Homey (most Dutch homes have a P1 dongle), the app now automatically detects it and learns your actual base load pattern. The formula is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;baseLoad = gridPower + pvPower
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where gridPower is your smart meter reading (positive = importing, negative = exporting) and pvPower is your inverter reading. This works regardless of whether you're importing or exporting. The app builds a 48-slot profile (one per 30 minutes) using an exponential moving average that adapts over about two weeks of observations.&lt;/p&gt;

&lt;p&gt;No smart meter? The app falls back to a configurable constant (default 400W). Less accurate, but the system still works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solcast and correction factors
&lt;/h2&gt;

&lt;p&gt;For solar forecasts, I integrated &lt;a href="https://toolkit.solcast.com.au" rel="noopener noreferrer"&gt;Solcast&lt;/a&gt;. Their Hobbyist plan gives you 10 API calls per day for free — enough for two forecast fetches and two actuals fetches, with budget to spare.&lt;/p&gt;

&lt;p&gt;The forecasts are good, but they're not perfect for your specific installation. Maybe your panels face slightly north-west and get shaded by a tree after 15:00. Solcast doesn't know that.&lt;/p&gt;

&lt;p&gt;So the app learns. For each 30-minute slot, it compares what Solcast predicted with what your inverter actually produced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;correctionFactor = actualYield / solcastEstimate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ratio is smoothed with an EMA (window of 14 observations, so roughly two weeks). After a few days, the app knows that Solcast overestimates your afternoon production by 15% and adjusts accordingly. The correction factor is capped at 3.0× to prevent one cloudy outlier from making the forecast permanently pessimistic.&lt;/p&gt;

&lt;p&gt;One thing I got wrong initially: I recorded an API call against the daily budget &lt;em&gt;before&lt;/em&gt; the fetch completed. If the request failed due to a network error, the call was wasted. Now it only counts on success — or on a 429 rate limit response, since that means the API actually processed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stampede problem
&lt;/h2&gt;

&lt;p&gt;Back to our three appliances all wanting the noon slot. The Virtual Energy Supplier solves this with a reservation ledger.&lt;/p&gt;

&lt;p&gt;When prices or forecasts update, the app triggers a full replan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Release all existing capacity reservations&lt;/li&gt;
&lt;li&gt;Sort all scheduled devices by deadline (earliest first — highest priority)&lt;/li&gt;
&lt;li&gt;For each device, compute effective prices with the current reservations factored in, find the optimal window, and reserve that capacity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key insight: device #2 sees device #1's reservation already deducted from the available surplus. If the noon slot only has 1.5 kW left after the washing machine reserved its share, the dryer computes a higher effective price for that slot and might pick a different time instead.&lt;/p&gt;

&lt;p&gt;Sequential scheduling by deadline priority means the most urgent device gets first pick of the cheap slots. Less urgent devices adapt. No coordination protocol, no negotiation — just a deterministic ordering that produces good results.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dashboard
&lt;/h2&gt;

&lt;p&gt;All this math is invisible to the user if they don't care about it. The scheduler just picks better times. But if you do want to see what's happening, there's a new Virtual Energy Provider device — a dashboard that shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current effective price vs. grid price&lt;/li&gt;
&lt;li&gt;Feed-in tariff&lt;/li&gt;
&lt;li&gt;PV surplus and available capacity&lt;/li&gt;
&lt;li&gt;Household base load&lt;/li&gt;
&lt;li&gt;Number of scheduled appliances&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also comes with flow cards. You can trigger automations when surplus exceeds a threshold ("turn on the EV charger when surplus &amp;gt; 2000W"), when the effective price drops below a value, or when the spot price goes negative (yes, that happens in the Netherlands — you get paid to consume).&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;The biggest lesson: solar scheduling isn't a pricing problem — it's a resource allocation problem. Giving each device a "solar discount" is simple but wrong. You need a central arbiter that knows total supply, base demand, and existing commitments.&lt;/p&gt;

&lt;p&gt;The second lesson: base load matters more than you'd think. Without accounting for the 600W my house draws at noon, I was overestimating available PV by about 20%. That's the difference between "washing machine runs on solar" and "washing machine runs on solar plus a bit of grid at full price."&lt;/p&gt;

&lt;p&gt;Third: correction factors are essential. Weather-based forecasts for solar are impressively good at the regional level and consistently wrong for your specific roof. The EMA correction is cheap to compute and makes the scheduling decisions noticeably better after just a few days.&lt;/p&gt;

&lt;p&gt;v1.6.0 is currently in the &lt;a href="https://homey.app/a/net.dongen.power-profiler/test" rel="noopener noreferrer"&gt;Test channel&lt;/a&gt;. If you have a Homey Pro with solar panels and want to try it, I'd love feedback. Next up: battery storage support and getting this through Homey's app certification.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://refactoredroad.blogspot.com/2026/04/teaching-my-homey-about-solar-panels.html" rel="noopener noreferrer"&gt;The Refactored Road&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solar</category>
      <category>smarthome</category>
      <category>automation</category>
      <category>iot</category>
    </item>
    <item>
      <title>I Built a $15 Smart Home Controller (and Why Phones Are Bad Dashboards)</title>
      <dc:creator>The Refactored Road</dc:creator>
      <pubDate>Sat, 04 Apr 2026 09:13:06 +0000</pubDate>
      <link>https://dev.to/ndygen/i-built-a-15-smart-home-controller-and-why-phones-are-bad-dashboards-2cff</link>
      <guid>https://dev.to/ndygen/i-built-a-15-smart-home-controller-and-why-phones-are-bad-dashboards-2cff</guid>
      <description>&lt;p&gt;In &lt;a href="https://refactoredroad.blogspot.com/2026/03/my-washing-machine-picks-its-own.html" rel="noopener noreferrer"&gt;my previous post&lt;/a&gt; I wrote about how my washing machine and dryer pick their own schedule based on energy prices. That post was about the concept — a Homey app that finds the cheapest window to run your appliances. What I didn't mention was the thing on the kitchen wall that makes it actually usable.&lt;/p&gt;

&lt;p&gt;Because here's the truth about smart home automation: if the only way to interact with it is through an app on your phone, it won't survive contact with your household.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Apps
&lt;/h2&gt;

&lt;p&gt;I call it the spouse test. If your partner needs to unlock their phone, find the right app, navigate to the right screen, and tap three buttons just to start the dryer at a cheap time — they're going to press the button on the dryer instead. And they'd be right to.&lt;/p&gt;

&lt;p&gt;A physical device on the wall changes that dynamic entirely. It's always on, always showing the current state, and requires exactly one tap to do the thing. No login, no loading spinner, no "update available" popup. It's the difference between a light switch and a lighting app.&lt;/p&gt;

&lt;p&gt;So I decided to build one. A small touch screen near the laundry area that shows energy prices, appliance status, and lets you schedule a run with a single tap.&lt;/p&gt;

&lt;h2&gt;
  
  
  $15 of Hardware, Infinite Ambition
&lt;/h2&gt;

&lt;p&gt;The ESP-2432S028R — affectionately known as the "Cheap Yellow Display" or CYD — is one of those products that shouldn't exist at its price point. For about $15, you get an ESP32 microcontroller, a 2.8-inch color TFT display with touch input, WiFi, and enough GPIO pins to feel dangerous.&lt;/p&gt;

&lt;p&gt;The screen is 320 by 240 pixels. That's not a lot. For context, the icon for your weather app is probably bigger than this entire display. But for a single-purpose device that shows two appliance cards and a price indicator, it's plenty.&lt;/p&gt;

&lt;p&gt;The ESP32 handles WiFi, MQTT communication with my Homey hub, NTP time sync, and over-the-air firmware updates. All on a chip that draws about half a watt. The whole thing runs off a USB-C cable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Pivot
&lt;/h2&gt;

&lt;p&gt;I didn't start with custom firmware. Like any reasonable person, I started with ESPHome — the YAML-based framework that lets you configure ESP32 devices without writing C++. Define your sensors, your display layout, your automations, and ESPHome generates the firmware for you.&lt;/p&gt;

&lt;p&gt;It worked. For about two hours.&lt;/p&gt;

&lt;p&gt;The problem was MQTT topic structure. My Homey app publishes appliance data on specific topics with JSON payloads — state, pricing, scheduling, savings data. ESPHome's MQTT integration is designed for Home Assistant's auto-discovery format, and bending it to work with custom topic structures felt like writing C++ with extra steps. Worse steps, actually, because you're debugging generated code you didn't write.&lt;/p&gt;

&lt;p&gt;So I pivoted to PlatformIO with the Arduino framework. Full C++ control, direct access to proven libraries like TFT_eSPI for the display and espMqttClient for MQTT, and — crucially — the ability to structure my code the way the problem demanded rather than the way a YAML schema allowed.&lt;/p&gt;

&lt;p&gt;Was it more work? Absolutely. Was it the right call? Without question. Some projects fit neatly into a configuration-driven framework. This one needed a real codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a UI on 320 by 240 Pixels
&lt;/h2&gt;

&lt;p&gt;Designing a touch interface for a 2.8-inch screen is an exercise in brutal prioritization. There's no room for nice-to-haves. Every pixel has a job.&lt;/p&gt;

&lt;p&gt;The layout I landed on has three elements. A top bar showing the current time, date, energy price, and WiFi status. Two appliance cards — one for the washer, one for the dryer — each showing their current state with a color-coded badge, key stats, and an action button. That's it.&lt;/p&gt;

&lt;p&gt;The action button is context-sensitive. If the appliance is idle, it says PLAN and opens a scheduling modal. If it's already scheduled, it says CANCEL. The modal lets you pick a deadline: 4, 8, 12, or 24 hours from now. Tap your choice and the system finds the cheapest slot within that window.&lt;/p&gt;

&lt;p&gt;Color does a lot of heavy lifting on a small screen. Green badge means idle and ready. Blue means scheduled. Yellow means the system is still learning this appliance's power profile. Red means something needs attention. You can read the state of both appliances from across the room without your glasses.&lt;/p&gt;

&lt;p&gt;One design choice I'm particularly happy with: when an appliance is scheduled, the card shows the start time, the expected price per cycle, and how much you're saving compared to running it right now. That last number — "Saving €0.38" — turns out to be incredibly motivating. It makes the abstract concept of energy optimization tangible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bugs That Taught Me Things
&lt;/h2&gt;

&lt;p&gt;Embedded development has a way of humbling you. Here are three problems that took longer to solve than I'd like to admit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dual SPI buses.&lt;/strong&gt; The CYD board has two SPI peripherals: one for the display (ILI9341) and one for the touch controller (XPT2046). Most example code assumes they share a bus. They don't — and they can't, because the display's SPI runs at 40 MHz while the touch controller maxes out at 2.5 MHz. Sharing a bus means reconfiguring speed on every swap, which causes timing glitches. The fix was putting them on separate hardware SPI buses (HSPI and VSPI), each with their own pins. Obvious in hindsight. Took a full afternoon to figure out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SPI reentrancy crashes.&lt;/strong&gt; This one was subtle. MQTT messages arrive asynchronously via callbacks. My first implementation updated the display directly from the MQTT callback — parse the JSON, update the card, done. It worked great until a message arrived while the display was mid-draw. Two SPI transactions on the same bus at once: instant crash, no useful stack trace.&lt;/p&gt;

&lt;p&gt;The solution is almost embarrassingly simple: the MQTT callback sets a boolean flag. The main loop checks the flag, and if it's set, redraws the screen. No concurrent SPI access, no crashes. It's the embedded equivalent of "don't update the DOM from a web worker" — except the consequence isn't a console warning, it's a hard reset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Touch calibration.&lt;/strong&gt; Every CYD unit has slightly different touch calibration values. The raw coordinates from the XPT2046 don't map 1:1 to screen pixels — they need to be scaled and offset. My first unit worked perfectly with the default calibration. My second unit registered taps about 30 pixels to the left. The fix was a calibration routine and storing per-unit values, but the debugging process involved a lot of tapping one spot and watching a dot appear somewhere else. It felt like playing Operation with an invisible board.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;The finished device sits on the wall near our washing machine. It shows the current energy price, the status of both appliances, and lets you schedule a run with two taps: hit PLAN, pick your deadline. The system finds the cheapest slot within your window and confirms the schedule.&lt;/p&gt;

&lt;p&gt;It updates over-the-air, reconnects automatically if WiFi drops, and uses about as much power as a phone charger. The total hardware cost was under €20.&lt;/p&gt;

&lt;p&gt;But the real measure of success isn't technical. It's that my partner uses it without thinking about it. There's no app to open, no concept to explain. Tap the card, pick when you need it done. The rest happens automatically.&lt;/p&gt;

&lt;p&gt;Sometimes the best smart home upgrade isn't smarter software — it's a simple screen on the wall that does exactly one thing well.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://refactoredroad.blogspot.com/2026/04/i-built-dedicated-smart-home-controller.html" rel="noopener noreferrer"&gt;The Refactored Road&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>smarthome</category>
      <category>embedded</category>
      <category>mqtt</category>
    </item>
    <item>
      <title>My Washing Machine Picks Its Own Schedule (and Saves Money)</title>
      <dc:creator>The Refactored Road</dc:creator>
      <pubDate>Mon, 30 Mar 2026 15:09:11 +0000</pubDate>
      <link>https://dev.to/ndygen/my-washing-machine-picks-its-own-schedule-and-saves-money-3ape</link>
      <guid>https://dev.to/ndygen/my-washing-machine-picks-its-own-schedule-and-saves-money-3ape</guid>
      <description>&lt;p&gt;My electricity price changes every hour. Some hours it’s 0.05 EUR/kWh. Other hours it’s 0.38. The difference between running the dishwasher at 4 PM versus 2 AM can easily be half a euro — per cycle, every day.&lt;/p&gt;

&lt;p&gt;I have a washing machine, a dishwasher, and a dryer. They run almost daily. None of them came with a “wait for cheap power” button.&lt;/p&gt;

&lt;p&gt;So I built one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I run a &lt;a href="https://homey.app" rel="noopener noreferrer"&gt;Homey&lt;/a&gt; smart home hub. Each appliance is plugged into a smart plug that reports real-time power consumption. The idea was simple: if I know &lt;em&gt;how much&lt;/em&gt; power an appliance uses and &lt;em&gt;when&lt;/em&gt; electricity is cheapest, I can schedule the run automatically.&lt;/p&gt;

&lt;p&gt;The result is &lt;a href="https://homey.app/en-us/app/net.dongen.power-profiler/Power-Profiler/" rel="noopener noreferrer"&gt;Power Profiler&lt;/a&gt;, a Homey app that watches your appliances, learns their patterns, and triggers a flow at the cheapest moment. Here’s how it works — and what I learned building it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teaching a smart plug to recognize a wash cycle
&lt;/h2&gt;

&lt;p&gt;A smart plug gives you one number: watts. Right now, my dishwasher is drawing 3 watts. Boring. But when it starts a cycle, that number jumps to 2,200 during the heating phase, drops to 50 during a pause, spikes again for the rinse, and eventually settles back to idle.&lt;/p&gt;

&lt;p&gt;The challenge is knowing when a cycle starts and — more importantly — when it actually ends. My first attempt was simple: if power goes above 50 watts, the cycle started. If it drops below 50, it ended.&lt;/p&gt;

&lt;p&gt;That lasted about one wash.&lt;/p&gt;

&lt;p&gt;The problem is mid-cycle dips. A washing machine drops to near-zero between the wash and spin phases. The dishwasher pauses between wash and rinse. My naive detector saw these pauses and thought each one was a separate cycle.&lt;/p&gt;

&lt;p&gt;The fix was a &lt;strong&gt;cooldown period&lt;/strong&gt;: a 2-minute grace window after power drops. If the appliance kicks back in during those 2 minutes, it’s still the same cycle. If it stays quiet, the cycle is done. This one change turned messy data into clean recordings.&lt;/p&gt;

&lt;p&gt;The finite state machine has three states: &lt;strong&gt;idle&lt;/strong&gt; (waiting), &lt;strong&gt;active&lt;/strong&gt; (recording), and &lt;strong&gt;cooldown&lt;/strong&gt; (waiting to see if it’s really over). Simple enough to reason about, robust enough to handle the real world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three cycles and you’re profiled
&lt;/h2&gt;

&lt;p&gt;After three complete runs, the app has enough data to build a &lt;em&gt;power profile&lt;/em&gt; — a minute-by-minute picture of what the appliance does during a full cycle.&lt;/p&gt;

&lt;p&gt;And this is where the appliances show their personalities.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;dishwasher&lt;/strong&gt; runs for about 3 hours and 20 minutes. That surprised me — I always thought of it as a quick appliance. It heats water to 2,200 watts in bursts, runs pumps at moderate power, pauses, rinses, and dries. Three profiled cycles averaging 199 minutes each, using about 1 kWh per run.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;washing machine&lt;/strong&gt; is faster but more dramatic. About 2 hours and 20 minutes on a standard program, peaking at 2,285 watts when the heating element kicks in. The power curve looks like a mountain range — big spikes for heating, quiet valleys during soaking, and a final burst for the spin cycle.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;dryer&lt;/strong&gt; is the gentle one. A steady 550 watts for the duration of the run — no dramatic spikes, just a long, patient hum. It’s still being profiled, so it’s not scheduling yet. Three cycles and it’ll join the team.&lt;/p&gt;

&lt;p&gt;The profile captures more than just averages. For each minute, the app records the minimum, maximum, and average power across all recorded cycles. And it keeps learning — every new cycle gets added to a rolling buffer of the last 20 runs. Run your dishwasher on eco mode a few times, then switch to intensive? The profile gradually shifts to reflect your actual usage, not just the first three cycles you happened to record. Three cycles gets you started. Twenty keeps you accurate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the cheapest hour
&lt;/h2&gt;

&lt;p&gt;Here’s where the money comes in. In the Netherlands, &lt;a href="https://refactoredroad.blogspot.com/2026/03/my-solar-panels-only-work-30-why-air.html" rel="noopener noreferrer"&gt;day-ahead electricity prices&lt;/a&gt; are published every afternoon. Prices for each hour of the next day, straight from the EPEX spot market. Some hours are 5 cents per kWh. Others are 35. Occasionally they go negative — yes, you can get &lt;em&gt;paid&lt;/em&gt; to use electricity.&lt;/p&gt;

&lt;p&gt;The algorithm is a sliding window. Take the power profile and slide it across every possible start time within your deadline. For each position, multiply the minute-by-minute power draw by the energy price at that moment. The cheapest total wins.&lt;/p&gt;

&lt;p&gt;Here’s what that looks like in practice, step by step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You trigger “Schedule cheapest start within 12 hours”&lt;/strong&gt; — say, at 8 PM. That gives the app a window from 8 PM tonight until 8 AM tomorrow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The app grabs the power profile&lt;/strong&gt; — for the dishwasher, that’s 199 minutes of minute-by-minute power data, learned from three previous cycles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It grabs tonight’s energy prices&lt;/strong&gt; — a price for each hour (or quarter-hour, depending on your provider) within the 12-hour window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It tries every possible start time.&lt;/strong&gt; “What if I start at 8:00 PM? 8:01? 8:02?” For each one, it overlays the power profile onto the price timeline and calculates the total cost: watts times price, minute by minute, summed up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cheapest start time wins.&lt;/strong&gt; Say starting at 1:15 AM costs EUR 0.18, while starting at 8 PM would have cost EUR 0.34. The app picks 1:15 AM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A timer is set.&lt;/strong&gt; The app counts down to 1:15 AM. When it fires, it triggers your Homey flow — which turns on the smart plug, and the dishwasher starts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The dishwasher’s 3.5-hour cycle needs a big window — it can’t just squeeze into any single cheap hour. The algorithm has to find the best &lt;em&gt;stretch&lt;/em&gt; of hours, weighing the expensive heating minutes against the cheaper idle phases. The washing machine’s 2.5-hour cycle has a bit more flexibility, but its high-power heating spikes mean the price during those specific minutes matters a lot.&lt;/p&gt;

&lt;p&gt;The math is almost embarrassingly simple. No machine learning, no neural networks. Just a loop that tries every start time and picks the cheapest one. It runs in milliseconds. Sometimes brute force is the right answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The things that bit me
&lt;/h2&gt;

&lt;p&gt;The algorithm worked quickly. Getting the details right took longer. During testing, I displayed estimated cost with two decimal places — sensible for money, except when the dishwasher runs at 2 AM during cheap hours and the total cost is EUR 0.003, which rounds to EUR 0.00. Looks like the calculation is broken. Then there was the schedule that showed “Tomorrow, 23:00” for tonight’s run — a timezone comparison bug where UTC and local time disagreed about which day it was. Three lines to fix, two hours to find. The kind of bugs that only show up at midnight, in a timezone you didn’t consider.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when you press “go”
&lt;/h2&gt;

&lt;p&gt;Here’s the actual user experience.&lt;/p&gt;

&lt;p&gt;You install the app, pick your energy provider (EasyEnergy, EnergyZero, or one of three others), and add a device. The app shows all your smart plugs — pick the one under your dishwasher. Done. You now have a “Dishwasher Profiler” on your Homey dashboard.&lt;/p&gt;

&lt;p&gt;For the next few days, just use your appliances normally. The app watches. After three cycles, your profile is ready. The dashboard shows average cycle duration, energy per run, and a cycle count.&lt;/p&gt;

&lt;p&gt;Now you create a Homey flow: “Schedule cheapest start within 12 hours.” The app slides your profile across tonight’s prices and picks the winner. Your dashboard shows “Next start: 02:15” and “Estimated cost: EUR 0.1825.”&lt;/p&gt;

&lt;p&gt;At 2:15 AM, the app fires a trigger. Your flow turns on the dishwasher. The dishwasher starts. You’re asleep.&lt;/p&gt;

&lt;p&gt;Is it life-changing money? No. But it’s money I save by doing absolutely nothing. The app watches, learns, waits, and acts. I just load the dishes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’d do differently
&lt;/h2&gt;

&lt;p&gt;If I started over, I’d track cumulative savings from day one. Right now, users can see their total energy and cost, but not “how much you saved compared to running at peak.” That comparison would make the value instantly visible. This will be a future improvement.&lt;/p&gt;

&lt;p&gt;But the core idea — watch, learn, schedule — holds up. It’s the kind of automation that disappears into the background, which is exactly where the best smart home tech should be.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://refactoredroad.blogspot.com/2026/03/my-washing-machine-now-picks-its-own.html" rel="noopener noreferrer"&gt;The Refactored Road&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>homeautomation</category>
      <category>iot</category>
      <category>smarthome</category>
      <category>energy</category>
    </item>
  </channel>
</rss>
