Badeværelsesventilatorer i Home Assistant: Bluetooth-proxy med XIAO ESP32-C3 og Fresh Intellivent Sky
XIAO ESP32-C3 som Bluetooth-proxy til Home Assistant, Fresh Intellivent Sky over BLE, og et nedbrud der lignede dårlig rækkevidde, men ikke var det.
To Fresh Intellivent Sky-ventilatorer på badeværelserne sender begge via Bluetooth Low Energy, og begge sidder længere fra Home Assistant-serveren, end dens indbyggede radio pålideligt kan nå. Løsningen var ikke en ny server eller et net af repeatere, men en billig mikrocontroller og ti minutter i en browser. Indlægget handler om opsætningen af Bluetooth-proxyen, om at få ventilatorerne ind i Home Assistant, og om en fejl, der lignede et hardwareproblem, men ikke var det.
Hardwaren: Seeed Studio XIAO ESP32-C3
XIAO ESP32-C3 er et kort på størrelse med en tommelfingernegl med wifi, Bluetooth 5 og et USB-C-stik. Mere skal du ikke tænke over. Jeg satte det i en USB-oplader i gangen mellem de to badeværelser og har ikke rørt det siden.

At flashe det er ikke det projekt, ordet normalt lyder som. ESPHomes installationsprogram i browseren har en færdig Bluetooth Proxy-firmware: Sæt kortet i computeren med USB, åbn siden i Chrome eller Edge, vælg kortet og Bluetooth Proxy-projektet, og firmwaren lægges på via WebSerial. Der er ingen YAML at skrive og intet ESPHome-dashboard at køre lokalt. Forbind kortet til wifi i den guidede opsætning, så dukker det op i Home Assistant inden for et minut.
Når det er tilføjet som en ESPHome-enhed, melder det sig selv som Bluetooth-adapter under Indstillinger → Enheder og tjenester → Bluetooth. Home Assistant bruger automatisk proxyen frem for sin egen adapter til de enheder, der er tættest på proxyen. Du skal ikke konfigurere noget.
Hvorfor en proxy i stedet for bare mere rækkevidde
Bluetooth-radioen i Home Assistant-serveren dækker cirka ét rum, måske to, hvis der er frit udsyn. Alt udenfor er et dødt område for rene BLE-integrationer. Der er intet mesh som i Zigbee og ingen repeatere: enheden er enten inden for rækkevidde eller ej. Et enkelt billigt ESP32-kort dækker et helt dødt område, og du kan sætte flere op, hvor der er USB-strøm. Hvert nyt kort giver bare mere dækning, uden parring eller opsætning pr. enhed.
Jeg har tidligere brugt proxyen sammen med en Google Find My-opsætning, som også bygger på Bluetooth. Det indlægs FAQ peger netop på en ESP32-proxy som det rigtige værktøj, når du vil have en position inden for sekunder i stedet for «når der engang går en telefon forbi».
Sådan kommer ventilatorerne ind i Home Assistant
Fresh Intellivent Sky er en fugtstyret badeværelsesventilator med egen Bluetooth-forbindelse, som normalt styres fra Fresh SmartLink-appen. Home Assistant har ingen officiel integration til den. Den, der virker, er et community-projekt, angoyd/freshintelliventHacs, som installeres via HACS som et brugerdefineret repository.

Med proxyen på plads går opsætningen næsten af sig selv: Så snart ventilatorens BLE-signal når en proxy, foreslår Home Assistant den under Indstillinger → Enheder og tjenester → Fundet med fresh_intellivent_sky som kilde. Bekræft, så opretter den en enhed med fem sensorer:
sensor.<navn>_humiditysensor.<navn>_temperaturesensor.<navn>_current_speedsensor.<navn>_mode(læsbar: Off, Trickle, Boost osv.)sensor.<navn>_mode_raw(den numeriske kode bag)
Den kan kun læses, ikke styres. Der er ingen fan.*-, switch.*- eller select.*-entitet, så intet i Home Assistant kan ændre ventilatorens hastighed eller starte boost. Automationer bygget på denne integration kan minde dig om at starte boost selv, men de kan ikke gøre det for dig. Det er en reel begrænsning, som er værd at kende, før du planlægger automationer.
Opdatering 26/08/2026: ikke længere hele historien — se opdateringsafsnittet nederst i indlægget.
Fugtautomationer, der virker, selvom ventilatoren kun kan læses
To ventilatorer med fem sensorer hver og ingen styring. Men luftfugtigheden, og hvor hurtigt den ændrer sig, er stadig nok til at bygge noget nyttigt. Det mønster, jeg endte med:
- En Derivative-hjælper (Indstillinger → Enheder og tjenester → Hjælpere, ingen YAML) på hver fugtsensor, som giver en værdi i %/min. En vedvarende stigning over cirka 3,3 %/min betyder, at nogen er i bad, og det opdages langt hurtigere end med en fast fugtgrænse.
- En automation med en fast grænse for reel skimmelrisiko: luftfugtighed over 70 % i mindst 45 minutter, uanset hvor hurtigt den stiger.
Begge sender kun en notifikation, jf. begrænsningen ovenfor. Men udløseren på ændringshastigheden fanger et bad inden for et par minutter, længe før en fast grænse på 65 % ville slå til.
Fejlen, der lignede et rækkeviddeproblem
Et par dage efter opsætningen gik alle fem sensorer på den ene ventilator til «unavailable», mens den anden blev ved med at melde normalt. Det oplagte gæt er signalet: proxyens placering, BLE-rækkevidden, eller at ventilatoren skal parres igen. Jeg tjekkede proxyens dækning først og fandt intet galt.
Den egentlige årsag lå i integrationens config entry, ikke i radioen. Home Assistant holder styr på opsætningsstatus for hver integration, og én af de mulige værdier er failed_unload. Den opstår, når integrationens egen kode til genindlæsning fejler midt i nedlukningen, og i praksis retter den sig ikke selv: kun en fuld genstart fjerner den. Loggen viste præcis det: en ændring af indstillingerne udløste en genindlæsning, nedlukningsdelen fejlede, og integrationen blev efterladt død. Den anden ventilator, som ikke var ramt, stod hele tiden som loaded.
Intet af det kan ses ud fra entitetens status alene. «Unavailable» ser ens ud, uanset om integrationen er gået ned, eller enheden er uden for rækkevidde. Sporet finder du under Indstillinger → Enheder og tjenester på selve integrationen eller i Home Assistant-loggen omkring det tidspunkt, hvor entiteten holdt op med at opdatere. En fuld genstart fjernede fejlen med det samme, og alle sensorer meldte igen inden for to minutter.
alias: "Bathroom: Fan Sensor Stale Alert"
description: >
Notifies if a bathroom fan's sensors go and stay unavailable for over an
hour — includes a one-tap Restart HA action button rather than
restarting automatically, since staleness can also mean the fan is
genuinely out of range or powered off, where a restart wouldn't help.
mode: parallel
trigger:
- platform: state
entity_id: sensor.example_fan_mode
to: "unavailable"
for:
hours: 1
action:
- action: notify.mobile_app_pixel_10
data:
title: "📡 Fan sensor offline"
message: "Unavailable for over an hour — likely a crashed integration entry, not range."
data:
actions:
- action: restart_ha_bathroom_fan
title: "Restart HA"
Knappen i notifikationen starter en lille automation, der lytter efter netop den mobile_app_notification_action-event og kalder homeassistant.restart. Det sker bevidst ikke automatisk: er ventilatoren reelt uden for rækkevidde, ville den bare få nytteløse genstarter i en løkke i stedet for den rigtige løsning.
Kortene på dashboardet
Begge ventilatorers kort bruger de samme glaskonstanter i card_mod som resten af dashboardet: en graf over fugtigheden, felter med temperatur, tilstand og hastighed, og et kompakt statusfelt, der kun vises, når ventilatoren kører, eller fugtigheden stiger. Så fylder et badeværelse, hvor der ikke sker noget, ikke på dashboardet. Kortet for ventilatoren, der fejlede, har også et lille reservefelt, «Connectivity: Unavailable», som bygger på samme mode-sensor. Det forsvinder, så snart sensorerne melder igen, uden at dashboardet skal redigeres.
Opdatering: skiftet til et repo, der faktisk kan noget
Syv uger senere skrev en fra Spittie.dk, et dansk site for Home Assistant-entusiaster, til mig om et repo, jeg ikke selv havde fundet: Spit68/Fresh-Intellivent-Sky. angoyd/freshintelliventHacs, integrationen ovenfor, kan stadig kun læse, uanset hvor mange gange man genindlæser den. Det nye repo er noget helt andet. Det installeres på samme måde via HACS, men giver et helt andet sæt entiteter.
Og der er markant flere: knapper til boost og pause med varighed og omdrejninger, en kontakt til konstant hastighed, omdrejninger pr. driftstilstand, nattilstand og stille timer med hver deres start- og sluttidspunkt, og følsomhed for fugt-, VOC- og lysdetektion. Automationer kan nu selv trykke på boost-knappen i stedet for bare at minde nogen om at gøre det.
Til gengæld forsvandt en brugbar fugtprocent. Det nye repo har ganske vist en humidity_sensor_raw-entitet, men den er slået fra som standard. Før jeg byggede videre på den, gik jeg kildekoden igennem efter en skjult formel. Der er ingen. sky_sensors.py pakker BLE-statusbeskeden direkte ud:
values = unpack("<2B5H3B", data)
self.humidity_raw = values[2]
Et råt 16-bit tal direkte fra enhedens eget register, uden division eller noget at sammenligne med. De to automationer ovenfor kunne derfor ikke bare flyttes over med et nyt entitetsnavn. Indtil jeg tænkte mig bedre om: Den gamle angoyd-integration læser slet ikke selv BLE-data, men bruger et separat bibliotek, pyfreshintellivent. Det biblioteks sensors.py læser samme karakteristik (528b80e8-…, som i protokoldokumentationen ligefrem hedder «Sensor data») på samme byte-position som humidity_raw ovenfor og regner den om:
if values[2] != 0:
self.humidity = round((log(values[2] / 10) * 10), 1)
En naturlig logaritme, ikke en lineær skalering. Formlen er ikke offentliggjort af Fresh, men fundet af en anden udvikler og brugt i årevis. Jeg prøvede den på de rå tal, jeg allerede havde: 858 giver 41,3 %, og 2800 til 3150 giver 39,5 til 58 %. Begge ligger midt i almindelig luftfugtighed for et badeværelse, så det er ikke et tilfældigt resultat. Jeg byggede den som to template-sensorer i Home Assistant og satte begge automationer tilbage til de oprindelige grænser, 70 % i 45 minutter for skimmelrisiko og 3,33 %/min for hurtig stigning, nu bare mod en anden sensor end før.
Selve skiftet var ikke kun et spørgsmål om at pege HACS mod et nyt repo. Da Bluetooth-proxyen blev geninstalleret (samme kort som tidligere i indlægget), mistede den forbindelsen til ESPHome i cirka 20 minutter. Det lignede endnu et nedbrud, men rettede sig selv. Og fordi automationer og dashboardkort havde den gamle integrations entity_id'er skrevet direkte ind, skulle hver reference findes og rettes i hånden. Det afslørede samtidig, at de to badeværelseskort i lang tid havde vist hinandens data, fordi titel og entiteter på et tidspunkt var blevet byttet om, uden at jeg havde opdaget det.
Opdatering: formlens pålidelighed er stadig uafklaret
Efter indlægget kom ud, hørte jeg fra udvikleren bag Spit68/Fresh-Intellivent-Sky. Han havde allerede lagt et solidt stykke arbejde i netop det problem: prøvet LaStradas log-formel ovenfor og en række andre, skilt en SKY-enhed ad for at se selve fugtsensoren, arbejdet ud fra dens datablad og endda givet en AI en bunke par af rå tal og målt fugtighed. Intet af det gav en formel, der var god nok til en rigtig procent-sensor. Derfor er humidity_sensor_raw slået fra som standard i hans repo i stedet for at vise et gæt forklædt som data. Respekt for at holde fast i det i stedet for at udgive noget, der ser rigtigt ud, men ikke er det.
Det vigtigste datapunkt: to SKY-enheder side om side, formentlig i samme luftfugtighed, kan vise rå tal så forskellige som 100 og 300. Det er ikke en fast forskydning, man kan kalibrere væk. Det tyder på, at det rå register slet ikke bruger samme skala fra enhed til enhed.
Det ændrer, hvad jeg faktisk har vist ovenfor. Log-formlen gav troværdige tal for netop min ventilator, og mine to automationer virker stadig, fordi grænserne passer til den ventilators eget normale niveau over tid. Ikke fordi formlen er en bevist, generel omregning fra rå tal til procent. Vil du gøre det samme med dine egne SKY-enheder, så brug tallet til at følge udviklingen, ikke som en kalibreret procent, indtil du har sammenlignet med et rigtigt hygrometer i dit eget badeværelse. Udvikleren er heller ikke færdig: Han har nye sensorer på vej til sammenligning, og dukker der en rigtig formel op, opdaterer jeg indlægget.
Værd at vide
Udvider du Home Assistants Bluetooth-rækkevidde til mere end én enhed, fx Govee-sensorer, andre BLE-trackere eller flere af disse ventilatorer, er det ligegyldigt for proxyen, hvad den videresender. Ét XIAO ESP32-C3-kort pr. dødt område dækker alt inden for rækkevidde, ikke kun den enhed, du satte det op til. Det er samme tankegang som en LoRa-gateway til en postkassesensor 200 meter væk: billig radiohardware, der er lavet til formålet, løser rækkeviddeproblemer, som ingen mængde konfiguration i Home Assistant kan.
Nye indlæg direkte i din RSS-læser: følg med via RSS I Google-søgning: vælg szimnau.dk som foretrukken kilde