Cooling My Garage Servers While I'm at the World Cup

Cooling My Garage Servers While I'm at the World Cup

The Itch

A few of my homelab boxes live in the garage. In Florida. The garage stays manageable most of the year, the rack vents to ambient, and the Everwell mini split bolted to the wall keeps things comfortable whenever I remember to turn it on. The "remember" part is the load-bearing word in that sentence. It's also my office where I work from so I "remember" when it gets hot lol

The 2026 World Cup is happening here. Some of it close enough that I'm not going to be the guy with three host cities on his doorstep and stay home watching it on a laptop. So I'm going. For chunks of weeks at a time. To matches. With my servers sitting in a room that can hit triple digits in June if the AC is off long enough.

None of those servers care about being warm, until they suddenly do, in the form of thermal throttling, then noisy fans, then the kind of silent permanent damage that you only discover three months later when a drive starts failing SMART checks. I'd rather not come back from a group stage match to a brick.

The Everwell only listens to its IR remote, which is sitting on my desk. So I needed something to act as a substitute for me. A small, dumb hand that holds the remote and can be told what to press by Home Assistant. That ended up being a BroadLink RM4 mini, plus a slightly larger pile of yak shaving than I expected.

The Plan

Phased so future me can see what's done and what's pending.

  • [x] Pair the BroadLink RM4 mini to WiFi via the vendor app
  • [x] Teach it the AC's power on/off codes, verify it actually toggles the mini-split
  • [x] Wire the RM4 into Home Assistant via the local Broadlink integration (no cloud)
  • [x] Build an automation that cools the garage when it gets too hot
  • [x] Verify the whole thing is reachable from outside the LAN
  • [ ] Soak-test for a few weeks of actual heat, before the first match

Five of six done. The sixth one is just time.

Picking the Hardware

The shortlist was small. I needed something cheap, IR only, with a real Home Assistant integration, and not so weird that it would require me to read someone's GitHub gist from 2021 to make it work.

Device Verdict
BroadLink RM4 mini ($25) What I picked. IR only, mature HA integration, well-documented
BroadLink RM4 Pro ($45) RF + IR. The RF half is wasted budget for an IR-only mini-split
SwitchBot Hub Mini Concept's the same but you're more married to their app
Sonoff IR blaster Cheaper, flakier reputation, dimmer HA community support
"Just buy a smart AC" Not happening. My skills don't go beyond this split ac in terms of being an AC tech, I installed it myself and it would have to work

The RM4 mini is a small black puck about the size of a film canister. USB-powered, 2.4 GHz Wi-Fi only, omnidirectional IR LED, plus a free bonus temperature + humidity sensor that you get whether you want it or not. (You want it. More on that later.)

Phase 1: Pairing the Thing

The pairing dance was the most "consumer IoT" part of the build. Three things to know up front:

  1. The BroadLink app makes you create a cloud account before it'll talk to the device. Annoying. You can drop the cloud later, you just can't skip it now.
  2. The RM4 mini is 2.4 GHz only. If you're on a mesh router with a single combined 5/2.4 SSID, the pairing wizard will absolutely fail and not tell you why. Temporarily turn off 5 GHz on your phone or join a dedicated 2.4 GHz SSID for the duration of the pairing.
  3. Region selection is permanent per account. Pick USA (or whatever's actually right for you) and don't fat-finger it.

The actual pairing was about 90 seconds. Plug the RM4 in, the LED on the front rapid-blinks at roughly 2 Hz, the app discovers it on the LAN, you hand it your Wi-Fi password, done. The app immediately starts showing the device's onboard temp and humidity sensor, which felt magical for a $25 puck.

Phase 2: Teaching It The AC

The Everwell remote is the "Full State Display" kind, the better of the two flavors. Snowflake icon, fan-speed bars, setpoint digits, all on the LCD. Every IR transmission encodes the entire state (mode, temp, fan, on/off) in one burst, which means discrete on/off codes exist. No toggle ambiguity, no "did I just turn the AC on twice."

So I went down the obvious path first. BroadLink app -> Add Appliance-> AC Remote Full Display -> let the app pattern-match against its codeset library.

This is where it got annoying. The "User Defined Panel" flow walks you through:

  1. Use the original remote to set the AC to a desired state
  2. Turn the AC off with the original remote
  3. Tell the app what state you set (mode, temp, fan speed)
  4. Tap "Have a test"

Under the hood it's guessing your AC's protocol from a library of known codesets and trying them in sequence. The hope is one matches and the test press turns your AC back on.

For Everwell, no dice. The test sends a signal that looks plausible to the app but doesn't speak my AC's specific dialect. Weirdly, it would recognize the off code but not the on. Off brand mini splits are a graveyard of "close but wrong" rebadges (Hisense, TCL, Midea, etc., all OEMing the same physical units with subtly different IR encodings).

The fix: stop pattern matching, start recording.

In the BroadLink app the option is buried at the bottom of the "Add Appliance" grid, called UserDefine. It makes an empty virtual remote with no semantics, just blank buttons. You add a button, hold the original remote a few inches from the RM4, press the physical button you want to clone, and the RM4 captures the raw IR burst byte for byte. Replay later equals the same bytes back out. Works on anything that emits IR.

Two captures were all I needed:

  • Power ON 74F, captured with the AC off, remote display showing Cool 74°F Fan Auto. Press Power. RM4 records the "turn on at this exact state" burst.
  • Power OFF, captured with the AC on, same close-range setup. Press Power. RM4 records the "turn off" burst.

One side effect to expect. At three inches the IR scatters in every direction, including across the room to the AC. So you ll often see the AC actually respond mid capture (turn on or off). I leaned into it: each capture's scatter happens to leave the AC in exactly the right state for the next capture (off → on → ready for off → off). Convenient.

The takeaway I wish someone had handed me before I spent twenty minutes in the state matcher: for off brand mini-splits, skip the codeset library and go straight to UserDefine. It's slower if you want every button on the remote, but you only ever need two or three buttons for "control AC from a stadium parking lot" use cases.

Phase 3: Adding It To Home Assistant

In the world of consumer IoT, this is supposed to be the easy part. Settings -> Devices & Services -> Add Integration -> Broadlink -> enter IP -> done.

It got to "enter IP," then died with Invalid authentication. The full error explained itself: the RM4 was locked to the BroadLink cloud account I'd paired it with. Anything not signed in as that account, including my own LAN's Home Assistant, gets rejected. The mitigation is right there in the dialog: open the BroadLink app, tap the device, tap the three-dot menu, scroll to the bottom, disable the lock. Once disabled, HA authenticates immediately and the integration adds cleanly.

Now the fun part. The IR codes I'd captured back in Phase 2 live in BroadLink's cloud, not on the device itself, and BroadLink does not give you any way to export them. So in HA I had to re learn the same two codes, this time directly into HA's own storage.

Developer Tools → Actions → remote.learn_command:

action: remote.learn_command
target:
  entity_id: remote.ac_garage
data:
  command_type: ir
  device: Garage AC
  command: power_on_74f

Click "Perform action," RM4 LED starts blinking (learn mode, 30-second window), point the Everwell remote at it, press Power, green toast. Repeat with command: power_off. Two minutes, both codes captured.

The relearn is a feature, not a chore. After this, the only thing in the loop is local hardware: HA -> LAN -> RM4 -> IR LED -> AC. The BroadLink cloud could vanish tomorrow and nothing about my AC control changes. That was the whole point.

The integration also auto-exposes the RM4's onboard temperature and humidity sensors as sensor.ac_garage_temperature and sensor.ac_garage_humidity. Free thermometer. Reads about 1-2°F warm because of self heat from the RM4's own electronics, but plenty accurate for "is the garage cooking" decisions.

Phase 4: The Automation

Two captured codes plus a temperature sensor is enough to close the loop. The automation I actually want:

When the garage gets too hot for too long, blast the AC for a while, then turn it off, then wait long enough that I'm not bouncing the compressor every other minute.

Translated into HA's automation YAML, in /config/automations.yaml:

- id: garage_ac_burst_cool
  alias: Garage AC burst cool when hot
  description: Burst cool the garage when temperature spikes
  trigger:
    - platform: numeric_state
      entity_id: sensor.ac_garage_temperature
      above: 92
      for:
        minutes: 5
  condition:
    - condition: time
      after: "09:00:00"
      before: "19:00:00"
  action:
    - service: remote.send_command
      target:
        entity_id: remote.ac_garage
      data:
        device: Garage AC
        command: power_on_74f
    - delay:
        minutes: 10
    - service: remote.send_command
      target:
        entity_id: remote.ac_garage
      data:
        device: Garage AC
        command: power_off
    - delay:
        minutes: 10
  mode: single

A few moving parts worth pointing at:

  • for: minutes: 5 on the trigger. The sensor has to read above 92°F continuously for five minutes before this fires. A brief spike (someone opens the garage door for 20 seconds, the IR scatter from another capture, whatever) doesn't kick the AC.
  • condition: time restricts firing to between 9am and 7pm. The garage barely gets above the threshold overnight, and I don't want the AC kicking on at 3am when the heat dump from the sun isn't even a factor yet.
  • The trailing delay: 10 min is the cooldown. While the automation is mid execution, including that final delay, mode: single blocks new trigger fires. So even if the temperature stays above 92°F the whole time, the automation can't start a second burst until those final ten minutes elapse.

I ran it manually first to verify the action chain works without waiting on the temperature trigger. The HA "Traces" view drew it as a clean timeline: trigger at T+0, send power_on_74f, wait, send power_off, wait, complete. The AC turned on, ran, turned off. Exactly as designed.

And then the next morning the automation fired for real, on a 92°F garage, and the AC didn't actually turn on. I'd lost a substitution before kickoff.

The Bug That Made Me Stare At A Graph For An Hour

This is the part of the post where I admit I almost shipped a broken thing.

The trace said success. Trigger fired at 9:55:34 AM, condition passed, both remote.send_command calls completed without errors, all four steps finished. From HA's point of view, everything was great. From the garage's point of view, nothing happened.

The smoking gun was the temperature graph. During the supposed 5-minute "on" window (this was an earlier version of the YAML with a shorter burst), the sensor stayed flat at 92.4°F. No dip. Not even a wobble. If the AC had actually run, even briefly, the sensor sitting on the RM4 across the room would have seen it.

So I tested the exact same command manually a few minutes later. AC turned on instantly. Sensor went 92.93 → 89.6°F over the next six minutes. The IR pipeline worked. The codes worked. Everything was fine. The 9:55 attempt had just silently lost the IR pulse somewhere.

The hard lesson: remote.send_command returning success only means HA spoke to the RM4 over the LAN. It does not mean the RM4 actually emitted clean IR. And it definitely doesn't mean the AC heard it and acted on it. IR is fire and forget. There is no ack. There is no retry. If a UDP packet between HA and the RM4 drops, or the RM4 IR pulse arrives during a moment the AC isn't paying attention, the burst silently fails and the automation still thinks it succeeded.

The next automatic fire later that day went perfectly. Trigger at 11:53:34 → AC kicked in at 11:55:34 → sensor went 92.39 → 89.42°F over the next 18 minutes. Curve looked exactly like what a working AC should look like.

So the pipeline works most of the time, not always. Which is to say: this is a real world IoT system, not a science experiment.

I haven't solved this yet. The roadmap of "what would actually fix it":

  1. Send the on command twice with a half-second gap. Tiny YAML change, free insurance against the single-packet-loss case.
  2. Verify by temperature. After sending power_on, sleep 90 seconds, check if the sensor dropped at all. If not, re-send. Self-healing automation.
  3. Add a second automation That does same commands as the first one. Hopefully two don't fail at the same time.
  4. Add notifications I already have infra for this, but I will add notifications while I'm still here working from home.
  5. Add a camera I will do some remote testing and verifying too, and I will disable notifications once I'm happy. I can still access everything via Wireguard and tweak it between the games.

For the World Cup trip I'll probably do (1) plus rest is a separate weekend project.

Phase 5: Remote Access

Last piece is being able to reach the dashboard from the airport, the stadium, the hotel, wherever. The good news: I already solved this problem a while back, just for the rest of the homelab instead of HA specifically. I wrote about that build in detail in WireGuard VPN to My Homelab: Automating a VPN Server with Terraform and Ansible. Short version: there's a small Ubuntu VM running WireGuard that terminates inbound UDP from anywhere on the internet (via a Cloudflare DDNS record plus a pfSense port-forward) and routes the client back onto my LAN. Phone, laptop, whatever, once the tunnel is up every internal service is reachable by its private hostname.

The reasoning for WireGuard over public ingress is the usual two:

  1. Auth surface. Exposing HA's login to the open internet means dealing with brute-force attempts, fail2ban tuning, MFA hardening, the whole list. WireGuard is a key-pair handshake. Without the key you don't even get a TCP connection, let alone an HTTP login page.
  2. Blast radius. If HA does ever get popped, I'd rather the failure mode be "the AC didn't turn on" than "someone pivoted from HA to the rest of the homelab over a public IP."

Because everything's behind WireGuard, there is no new HA-specific work for remote access. The same https://homeassistant.localdomain URL I use on the couch works from a hotel Wi-Fi the moment the tunnel comes up. Same login, same dashboard, same buttons. The whole stack is identical on LAN vs over WG, which is the entire point of running the VPN as a subnet router instead of per-service tunnels.

Verified it before declaring victory: phone on cellular, Wi-Fi off, tunnel up, dashboard loads. Done.

What I Learned

A few things, ordered by how much they surprised me:

  • Off brand mini splits will eat codeset libraries for breakfast. If your AC isn't a brand the vendor app recognizes, do not waste twenty minutes in the pattern-matching flow. Go straight to raw IR capture.
  • BroadLink's device lock is the single most surprising barrier to local control. It's a one-click fix once you find it, but until you find it the error message tells you nothing useful.
  • HA's remote.send_command is not a verification, it's an aspiration. It tells you HA sent the packet to the RM4, not that the AC received the IR. For anything important, build verification into the automation, not faith.
  • mode: single plus a trailing delay is the cleanest way to add a cooldown to an automation. No timers, no input booleans, no helper scripts. The automation stays "running" through the cooldown, and mode: single blocks new triggers for the duration. Simple.
  • The RM4's bundled temperature sensor is good enough to close the loop. I was ready to buy an Aqara puck and a Zigbee dongle to add a "real" thermometer. Turns out the $25 IR blaster already had one.

And one thing I didn't really learn so much as confirm: a $25 IR puck is genuinely all you need to bring a dumb mini-split into your homelab. The hardware is the cheap part. The software glue around it, and the part where you find out your "successful" automation didn't actually do anything, is where you spend the actual time.

I'm not 100% confident this rig will hold up across a full multi week absence. The first time it does, though, I'll write a follow up post titled "I watched a World Cup match and my garage stayed at 78°F." Until then, I have plane tickets and a thermostat substitute on the bench. Let's see if the IR blaster can hold the line.

I used this prompt to generate the featured image.

A small black cylindrical IR blaster sitting on a workbench in a sun-lit Florida garage, with a partially-visible server rack glowing softly in the background. The blaster is emitting a stylized infrared beam toward an off-camera wall-mounted mini-split air conditioner. On the wall behind the bench, a 2026 FIFA World Cup wall calendar with a few match dates circled in red marker. Warm late-afternoon sunlight slicing through a half-open garage door, palm fronds just barely visible outside. Cinematic homelab aesthetic, shallow depth of field, photorealistic, subtle blueprint-overlay graphic in one corner showing the signal flow: phone (on the road) → WireGuard → Home Assistant → IR blaster → AC. Square 1:1 aspect ratio.