Sometimes a sensor lies about what model it is: the SEN55 that said SEN54
A few AIR-1 owners reported the same thing: every reading worked except NOx. Their logs all had this pair of lines:
[D][sen5x:125]: Type: SEN54
[E][sen5x:139]: NOx requires a SEN55
The module inside an AIR-1 is a Sensirion SEN55. It says so on the sticker. But ESPHome doesn’t read stickers, it asks the module what it is over I2C at boot, and on these units the module answers SEN54. A SEN54 has no NOx element, so ESPHome shuts the NOx sensor off. Reasonable behavior, wrong answer.
NOx never worked on these units
The check that disables NOx on a SEN54-reporting module has been in the ESPHome component since its first commit in May 2022, so this isn’t something an update broke. The log wording users were pasting is newer, from the 2026.2.0 release, which is probably why the reports started showing up: the message got short and clear enough to notice. But on these units, NOx had never published a value. Nobody caught it for years because a sensor that’s disabled and a sensor that never publishes look identical in Home Assistant: one entity, permanently unavailable.
Was the hardware lying or the register?
The SEN54 and the SEN55 share a case, an interface, and firmware. The difference is which sensing elements are physically populated, and the product name is programmed at the factory. So a module that answers SEN54 might be a mislabeled SEN55, or it might actually be a SEN54 under a SEN55 sticker, with no NOx element at all.
The AIR-1 gets these modules through DigiKey. That’s what made this confusing: the lazy explanation, somebody relabeling cheaper SEN54s as SEN55s, doesn’t hold up when the modules come from an authorized distributor. And you can’t settle it by reading datasheets. You have to ask the NOx element itself.
The test was a small patch to the sen5x component: a model: option that overrides what the module reports. Force model: sen55, let the NOx sensor run, and the hardware answers the question for you. Real numbers after the warm-up period means the element is there and the register is wrong. Permanently unknown means it really is a SEN54 inside.
My own AIR-1 turned out to be one of the affected units. I forced the model, waited out the conditioning period, and got real NOx values. The register is wrong. The hardware is fine.
Shipping it twice
The fix went out on two tracks. The workaround: the patched component lives in a repo the firmware can pull with an external_components: block, and the AIR-1 beta config forces model: sen55. On units that report SEN55 correctly, the override matches and changes nothing.
The real fix: a PR to ESPHome adding model: as a proper config option, with a docs PR to match. Filed and merged the same day, tagged for the 2026.7.2 patch release. Once that ships, the vendored component gets deleted and everything this post describes collapses into one line of YAML: model: sen55.
What three rounds of review taught me
Most of what I learned came out of code review.
Every unique log string costs flash on every device that compiles the component. My first version had two long warning messages explaining that forced readings might be invalid. A bot posted the bill, 449 bytes, and a maintainer pointed me at the logging guidelines. The final message is Detected SEN54, using SEN55. The explanation moved to the documentation, which is where they tell you it belongs.
The same principle killed my setup error logs: they repeated information the component already reports through its failed-state config dump. Deleted, not shortened.
And one Windows-specific lesson: the docs lint failed because my edit round-tripped the file through Python on Windows, which quietly converted every line ending to CRLF. The linter’s error message was clear about it. My terminal was not.
If your AIR-1 shows those two log lines, the workaround is on beta now, and the real fix is in ESPHome 2026.7.2 when it ships.
Related posts

Building an air quality LED indicator for the AIR-1
The AIR-1 has three onboard RGB LEDs, so I made them show air quality at a glance. The feature itself is small. Getting there took longer than it should have, because I kept fixing things that weren't the problem. These are the dead ends and where it ended up.

Simplifying the use of hidden networks in ESPHome
If your Wi-Fi network is hidden, the wifi config every ESPHome guide hands you won't connect, and nothing in the logs says why. The option that fixes it existed, it just wasn't accepted in the short form. It turned out there was no reason for that, so I sent a patch.

Improving the ESPHome captive portal
The screen that gets an ESPHome device onto WiFi lists the same network once per access point, so a mesh shows up three or four times at different signal strengths. Tapping the strong one does nothing the weak one doesn't, because the device only ever gets the network name, not the access point you picked. I checked the source, then sent a 220-byte fix upstream to collapse the list to one row per network.