Skip to content

sms_ser: fix status for newer line-interactive models, validate replies, reopen the port, send delays as the vendor software does [#1987] - #3701

Open
diogot wants to merge 19 commits into
networkupstools:masterfrom
diogot:sms_ser-premium-support
Open

diogot wants to merge 19 commits into
networkupstools:masterfrom
diogot:sms_ser-premium-support

Conversation

@diogot

@diogot diogot commented Sep 22, 2026 •

Copy link
Copy Markdown

Rework of the sms_ser driver (SMS Brazil single-phase protocol), developed
against an SMS Premium 1500 VA (PRM1500BiBiW, line-interactive, USB with a
built-in Prolific PL2303 serial adapter). Related discussion: #1987.

What was wrong

  • Newer line-interactive models (SMS Premium, SMS Gamer, as reported in
    SMS Manager III Support #1987) raise the bypass flag whenever their inverter runs. ups.status
    was picked by precedence with bypass first, so a power outage was reported
    as BYPASS and never as OB or LB; upsmon never reacted.
  • Replies were not checked (checksum, terminator), and the port was never
    reopened after a USB replug: the driver logged Short read from UPS
    forever on a stale handle.
  • Shutdown and test delays were sent in a format the UPS does not use: a
    delay of 64 went out as 0x40FF, shutdown.return sent four 0xFF
    bytes, and the test.battery.start.quick / .start names were swapped
    relative to their effect.
  • Measured values lost their decimal digit.

What this branch does (13 commits, each one functional change)

  • ups.status built from all flags: OL / OB, plus LB, RB, BOOST,
    and CAL with ups.test.result during a battery test. BYPASS is only
    reported for on-line models, decided from the topology byte of the status
    reply, as the vendor software does. LB is also raised while on battery
    if the charge is at or below battery.charge.low, when defined.
  • Replies validated (checksum and terminator); after repeated failures the
    port is closed and reopened by name, which handles a USB replug and the
    UPS powering itself down and up.
  • Delays sent as 16-bit big-endian values in the vendor format; new
    offdelay and ondelay settings with writable ups.delay.shutdown,
    ups.delay.start and ups.delay.reboot; new test.battery.start.deep
    (runs until the UPS reports low battery); test.battery.start.quick is
    now 10 seconds.
  • Seconds are converted to the time unit of the UPS: line-interactive
    models count in 0.6 s units (measured: a 10 second test used to run for
    6), on-line models in seconds. The restore delay of the shutdown command
    counts in minutes (measured: 1 gave 63 s, 3 gave 183 s) and is rounded up
    from the seconds given.
  • upsdrv_shutdown() queries the UPS type first, since the killpower path
    runs as a fresh driver instance that never polled.
  • ups.mfr, ups.model, ups.type, output.voltage.nominal published;
    battery.charger.status is discharging whenever on battery; decimals
    kept; frames hex-dumped at debug level 4.
  • Two opt-out flags as a way back for devices that behave differently:
    legacydelays (old parameter bytes for T, S, R) and novalidate
    (skip the reply check).
  • Man page, NEWS.adoc, UPGRADING.adoc (changed meaning of
    test.battery.start.quick and of the delays; malformed rebootdelay now
    fatal), data/driver.list.in, docs/nut.dict. DRIVER_VERSION bumped
    to 1.06.

Tested on hardware (SMS Premium 1500 VA)

  • Status reporting through mains cuts and battery tests; upsmon,
    upssched and a Synology DSM client all react to OB / OL.
  • The 0.6 s unit for battery tests and for both shutdown delays, the minute
    unit of the restore delay, shutdown.stop cancelling a pending shutdown,
    shutdown.return with and without mains (with mains the output returns
    after the restore delay; without it the UPS switches itself off about a
    minute after the cut and returns a few seconds after mains is back), and
    the killpower path (sms_ser -k) in both cases, all with only a lamp on
    the UPS output.
  • The UPS's own low-battery bit, raised 2 minutes before the output cut in a
    full drain; test.battery.start.deep.
  • Reopening the port after a USB replug and after the UPS powered itself
    down; a 14 hour soak run (10007 polls, no rejected reply, flat memory).
  • A build of the same patches against NUT 2.8.1 has been in production on a
    Proxmox host since 2026-09-22.

Tested on a simulator only

  • The legacydelays and novalidate flags (byte for byte against the
    stock driver's frames).
  • On-line models (<, > reply types) were not available at all; the
    code paths for them keep the previous behaviour where nothing better is
    known.

Asking for testers

Owners of other SMS models in #1987 will be asked to try the branch before a
merge (Gamer 1500 / 2000, Manager III, the author's original unit), in
particular for the raw replies at debug level 4 and for the effect of the
two flags. A NUT DDL dump for the Premium will follow in a separate PR.

Disclosure

Developed with Claude Code (model Claude Fable 5.1), with the author
directing the work, reviewing every change and running all hardware tests.
Each commit carries the same note.

Checklist

  • Changes described (above and in SMS Manager III Support #1987); applicable hardware and
    limitations stated
  • One functional change per commit
  • Use of AI disclosed in the commits and here
  • Code and docs revised for NUT style; plain ASCII in C sources and
    AsciiDoc
  • DRIVER_VERSION bumped (1.06)
  • USB VID/PID: not applicable, the driver talks to a serial device node
  • Data mapping aligned with docs/nut-names.txt
  • data/driver.list.in updated (SMS Premium 1500 VA)
  • No assumptions on integer sizes or endianness (16-bit values built
    byte by byte, uint16_t / size_t where due)
  • upsdebugx() / upslogx() / fatalx() for progress and errors
  • Coding style follows the file
  • No new files; make distcheck-light passes
  • NEWS.adoc and UPGRADING.adoc updated
  • docs/acknowledgements.txt: not applicable (no vendor backing)
  • docs/man/sms_ser.txt updated
  • make spellcheck passes, docs/nut.dict updated
  • The device dump for the SMS Premium 1500 VA is at SMS: add Premium 1500 VA (PRM1500BiBiW), sms_ser nut-ddl#68 (taken with this branch backported onto 2.8.1; the header explains what the stock driver shows instead)
  • CI farm results to be addressed after posting

@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

A ZIP file with standard source tarball and another tarball with pre-built docs for commit 113a9eb is temporarily available: NUT-tarballs-PR-3701.zip.

…networkupstools#1987]

The protocol is binary and the driver had no way to show what went over
the wire. Use upsdebug_hex() for every request sent and every reply read,
to help diagnose the models this driver meets in the field.

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
…upstools#1987]

The UPS reports tenths (e.g. 0x04ff for 127.9 V), but the long was divided
by 10 before becoming a double, so NUT always showed 127.00, 59.00 etc.

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
… fake BYPASS of line-interactive models [networkupstools#1987]

The status was picked by an exclusive if/else chain that tested the bypass
flag first. Newer line-interactive models (SMS Premium, Gamer) raise that
flag whenever the inverter runs, so an outage was reported as BYPASS and
neither OB nor LB ever reached upsmon. The vendor confirmed to a user in
issue networkupstools#1987 that these models have no bypass and the flag is not valid.

The first byte of the 'Q' reply tells the topology ('=' line-interactive,
'>' and '<' on-line types); the vendor's PowerView software uses it the
same way to decide whether to believe the bypass flag. Store it, and:

* always report OL or OB, plus LB, RB and BOOST as independent flags;
* report BYPASS only for the on-line types and never while on battery;
* report CAL and ups.test.result while the battery test flag is set, so
  upsmon can tell a test from an outage;
* also raise LB while on battery when the charge is at or below
  battery.charge.low, if the user defined it (the vendor software derives
  low battery from a threshold too);
* publish ups.mfr, ups.model and device.mfr (some clients, e.g. Synology
  DSM, refuse a server without ups.mfr and ups.model).

The unreachable OFF branch is gone with the chain.

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
…ailures, add output.voltage.nominal and ups.type [networkupstools#1987]

Move the send/receive sequence of the three queries into sms_query(),
which flushes stale input, checks that the whole request was written
(a return of -1 was not caught before), and rejects replies whose checksum
or terminator is wrong. The checksum rule is the one used for requests and
was checked against replies of three different models.

After MAXTRIES failed polls close the port and reopen it by name without
bailing out: a USB-serial adapter that was unplugged leaves a dead
descriptor and may return under another device node.

Publish ups.type from the topology byte and output.voltage.nominal from
the number in the voltage range label. Drop a stray write of that label
into battery.voltage.nominal, which was overwritten a few lines later.

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
… settings and test.battery.start.deep, bump version [networkupstools#1987]

According to the vendor's PowerView software, the 'S', 'T' and 'R' commands
carry 16-bit big-endian values with 0x00 in the unused pair. The driver put
the low byte of the delay in the first parameter and 0xFF in the others
(a delay of 64 went out as 0x40FF), and sent 'R' with four 0xFF bytes.

* shutdown.return (and so upsdrv_shutdown) now sends the new offdelay and
  ondelay settings, also writable as ups.delay.shutdown and ups.delay.start;
  ups.delay.reboot is now published too;
* delays given in ups.conf, by setvar or as a command argument are checked
  to be numbers that fit 16 bits; the argument of shutdown.reboot was parsed
  with the delay as the numeric base;
* test.battery.start.quick sent 'L' (test until battery low): it now runs
  the timed test for 10 seconds, and the new test.battery.start.deep sends
  'L'; test.battery.start still takes an optional duration;
* one helper sends a prepared command and checks the full write.

Not verified on hardware: the unit and effect of the delay values on the
UPS side; frames were checked against a protocol simulator only.

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
networkupstools#1987]

Describe the offdelay, ondelay and (so far undocumented) rebootdelay
settings, the instant commands, how ups.status is composed, why BYPASS is
not reported for line-interactive models, and what the battery level of
these devices means while charging.

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
… add SMS Premium 1500 VA [networkupstools#1987]

The UPGRADING note covers the behaviors that changed for existing users:
the meaning of test.battery.start.quick, the delays sent by the shutdown
commands, and the composition of ups.status.

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
…rkupstools#1987]

Timed battery tests on an SMS Premium 1500 (line-interactive), with the
durations read from the event log of the UPS: values of 10, 30 and 100 ran
for 6, 18 and 59 seconds, so the unit is a hundredth of a minute. This
matches the vendor software, whose "10 seconds" test sends 16 and which
multiplies its values by 0.6 only for the on-line type (so that type counts
in seconds). With the conversion, a request for 30 seconds ran for 30.

Apply it to the test duration and to the shutdown delays of 'S' and 'R'.
Only the test duration was measured; the shutdown delays are assumed to
use the same unit. The restore delay of 'R' is passed through unchanged.

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
…delays [networkupstools#1987]

The time unit of the delays depends on the UPS type, which is only known
after a status poll, and "driver -k" runs upsdrv_shutdown() in a fresh
instance that never polled.

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
…n battery [networkupstools#1987]

At the first poll of an outage the battery level can still read 100, and
the status was then reported as "floating".

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
… back [networkupstools#1987]

The delay format and the reply validation were checked with one model and
a protocol simulator, plus reply samples of two other models. Should a
device which worked with earlier versions not cope with either change,
these flags restore the earlier behavior without downgrading: the old
parameter bytes for 'T', 'S' and 'R', and no checksum/terminator check.
Both log a request to report the device.

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
…er time unit and fallback flags [networkupstools#1987]

Say that the delays are given in seconds and converted to the time unit of
the UPS, what was measured and what is assumed, and that the unit of the
restore delay is not known. Document the "legacydelays" and "novalidate"
flags and add them to the dictionary (the header count is as regenerated
by "make spellcheck-sortdict").

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
…store delay counts in minutes [networkupstools#1987]

Measured on an SMS Premium 1500 VA: a restore delay of 1 brought the
output back 63 s after the cut, 3 brought it back after 183 s, so the
UPS counts this field in minutes while the shutdown delays count in
0.6 s units. The ondelay setting and ups.delay.start are now in seconds
like the other delays, rounded up to whole minutes and never below one
minute when sent (the vendor software never sends 0). Default 60.

With mains absent the UPS switches itself off about a minute after the
cut and re-energises a few seconds after mains returns, whatever the
restore delay; noted in the man page.

Developed with Claude Code (model Claude Fable 5.1).

Signed-off-by: Diogo Tridapalli <diogot@users.noreply.github.com>
@diogot
diogot force-pushed the sms_ser-premium-support branch from d1fbe07 to d64e5f2 Compare September 22, 2026 14:25
@jimklimov jimklimov added need testing Code looks reasonable, but the feature would better be tested against hardware or OSes serial port Incorrect or missing readings On some devices driver-reported values are systemically off (e.g. x10, x0.1, const+Value, etc.) Connection stability issues Issues about driver<->device and/or networked connections (upsd<->upsmon...) going AWOL over time AI For good or bad, machine tools are upon us. Humans are still the responsible ones. labels Sep 23, 2026
@jimklimov jimklimov added this to the 2.8.6 milestone Sep 23, 2026
@jimklimov jimklimov added the HCL label Sep 23, 2026
@jimklimov jimklimov moved this from Todo to In Progress in NUT HCL/DDL Sep 23, 2026
Comment thread drivers/sms_ser.c Dismissed
@github-actions github-actions Bot deleted a comment from AppVeyorBot Oct 14, 2026
@github-actions github-actions Bot deleted a comment from AppVeyorBot Oct 14, 2026
@github-actions github-actions Bot deleted a comment from AppVeyorBot Oct 14, 2026
@github-actions github-actions Bot deleted a comment from AppVeyorBot Oct 14, 2026
@github-actions github-actions Bot deleted a comment from AppVeyorBot Oct 18, 2026
@karloscarrijo

Copy link
Copy Markdown

Tested on an SMS Gamer 1500 VA (GAM1500BiBiW, line-interactive, firmware 1.3), USB via a Prolific PL2303 (067b:23a3), built from d138b7d on Debian 13, offdelay=180.

Before (2.8.1 sms_ser 1.00): ups.status: BYPASS at rest and during an outage (input 0 V), so upsmon never saw OB.

With this PR (1.06):

At rest:

ups.status: OL
ups.type: line-interactive
input.voltage: 215.10
output.voltage: 112.10
battery.charge: 99.00
ups.load: 24.50
ups.delay.shutdown: 180
ups.delay.start: 60

Full outage, mains pulled for ~17 min, 4 secondaries + 1 local upsmon (2.8.1):

15:03:35  input.voltage: 0.00  ups.status: OB
15:06:44  secondaries shut down on their upssched timer
15:13:36  upscmd ups shutdown.return  -> OK, then host halts
~15:16:36 output cut (offdelay 180 s respected)
mains back -> output returned, all hosts booted

Also seen: OL BOOST on low mains, OL DISCHRG right after mains returns.

Not tested yet: LB and the battery tests.

Two small things on this model:

  • input.voltage.nominal reads EBiS115.
  • battery.charge stayed at 100 for the whole 10 min on battery (load ~18 %).

@github-actions github-actions Bot deleted a comment from AppVeyorBot Oct 23, 2026
@AppVeyorBot

Copy link
Copy Markdown

✅ Build nut 2.8.5.5896-master completed (commit 030d8c6986 by @jimklimov)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI For good or bad, machine tools are upon us. Humans are still the responsible ones. Connection stability issues Issues about driver<->device and/or networked connections (upsd<->upsmon...) going AWOL over time HCL Incorrect or missing readings On some devices driver-reported values are systemically off (e.g. x10, x0.1, const+Value, etc.) need testing Code looks reasonable, but the feature would better be tested against hardware or OSes serial port

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

5 participants