First off, thanks for this project — I used your frame framing/decode as the base for my setup. 🙏
I ran into a model that never worked with passive listening: the NHS Premium PDV GII 2200VA (USB-CDC, /dev/ttyACM0, USB ID 0925:1241, board v18 / firmware v80). Reading the port passively just returns 0 bytes — same result with NUT (nhs_ser, nutdrv_qx, blazer_*).
Root cause: this UPS is reply-only. It does not stream anything on its own — it only answers after you send it a specific proprietary command.
How I found the command: I straced the official "NHS Control" software (nhsupsserver) with -e trace=%desc (needed to catch the readv/writev, which a plain strace -e read,write misses) and read the bytes it writes to the serial port.
What works:
- Serial:
2400 8N1, CLOCAL (DTR/RTS don't matter), raw.
- Send
FF 05 01 06 FE → device replies with the STATUS frame (FF 15 44 …(21B)… FE, type 0x44 = 'D').
- Send
FF 09 53 03 00 00 00 5F FE → device info frame (type 0x53 = 'S').
So on these models the missing piece is simply writing the poll command before reading.
One calibration difference I hit: on my unit the battery voltage decoded as b[4]/5 (raw 136 → 27.2 V), whereas the parser here uses b[4]+b[5]/100. Load and temperature matched directly. Might be a per-family scale factor worth noting.
I implemented an active-polling variant (Python poller → MQTT Discovery for Home Assistant) and documented the full capture method here, in case it's useful or you'd like to fold polling support upstream:
👉 https://github.com/Alltec12/nhs2mqtt-homeassistant
Happy to help test on other NHS models or open a PR if you'd want active-polling support in nhs2mqtt itself. Thanks again!
First off, thanks for this project — I used your frame framing/decode as the base for my setup. 🙏
I ran into a model that never worked with passive listening: the NHS Premium PDV GII 2200VA (USB-CDC,
/dev/ttyACM0, USB ID0925:1241, board v18 / firmware v80). Reading the port passively just returns 0 bytes — same result with NUT (nhs_ser,nutdrv_qx,blazer_*).Root cause: this UPS is reply-only. It does not stream anything on its own — it only answers after you send it a specific proprietary command.
How I found the command: I
straced the official "NHS Control" software (nhsupsserver) with-e trace=%desc(needed to catch thereadv/writev, which a plainstrace -e read,writemisses) and read the bytes it writes to the serial port.What works:
2400 8N1,CLOCAL(DTR/RTS don't matter), raw.FF 05 01 06 FE→ device replies with the STATUS frame (FF 15 44 …(21B)… FE, type0x44= 'D').FF 09 53 03 00 00 00 5F FE→ device info frame (type0x53= 'S').So on these models the missing piece is simply writing the poll command before reading.
One calibration difference I hit: on my unit the battery voltage decoded as
b[4]/5(raw 136 → 27.2 V), whereas the parser here usesb[4]+b[5]/100. Load and temperature matched directly. Might be a per-family scale factor worth noting.I implemented an active-polling variant (Python poller → MQTT Discovery for Home Assistant) and documented the full capture method here, in case it's useful or you'd like to fold polling support upstream:
👉 https://github.com/Alltec12/nhs2mqtt-homeassistant
Happy to help test on other NHS models or open a PR if you'd want active-polling support in
nhs2mqttitself. Thanks again!