Skip to content

ragtech: new driver for Ragtech UPSes (USB CDC-ACM, family 10)#3447

Open
juslex wants to merge 1 commit into
networkupstools:masterfrom
juslex:feat/ragtech-driver
Open

ragtech: new driver for Ragtech UPSes (USB CDC-ACM, family 10)#3447
juslex wants to merge 1 commit into
networkupstools:masterfrom
juslex:feat/ragtech-driver

Conversation

@juslex
Copy link
Copy Markdown

@juslex juslex commented May 17, 2026

Summary

Adds a new ragtech driver supporting the Ragtech "Easy Pro" family of
line-interactive UPS units (also sold under the NEP, TORO,
INNERGIE and OneUP brands in Brazil). Devices expose a USB CDC-ACM
serial interface (VID 0x04D8 / PID 0x000A, Microchip PIC firmware) and
speak a proprietary 6-byte register-access protocol.

  • Single standalone serial driver in drivers/ragtech.c (no shared
    sub-driver layer — Ragtech is not a Megatec/Qx variant).
  • 20 models from the OEM "family 10" device table populate ups.model,
    ups.realpower.nominal, output voltage/current scaling and battery
    voltage scaling.
  • input.voltage.nominal switches between 115V and 220V dynamically based
    on the measured input (TI vs M2 variants).
  • Reverse-engineered from strace of the OEM supsvc binary and the OEM
    devices.xml, cross-checked against
    antunesls/UPS_ESP32_tinySrv.

Protocol (decoded from OEM traffic)

Every command on the wire is 6 bytes: 0xAA OPCODE ADDR_HI ADDR_LO VALUE CHECKSUM
where CHECKSUM = (ADDR_HI + ADDR_LO + VALUE) & 0xFF.

OPCODE Meaning
0x01 Write byte
0x02 AND mask (atomic bit-clear)
0x03 OR mask (atomic bit-set)
0x04 Read range

Instant commands

  • shutdown.stayoff — byte-for-byte identical to the OEM supsvc sequence.
    Verified on hardware: cuts output and stays off until manual power-on
    in both AC and battery modes.
  • shutdown.stop — aborts an armed countdown. Verified on hardware.
  • shutdown.return — implemented as a shutdown.stayoff with a
    LOG_WARNING. The Easy 2000 TI firmware does not auto-restart on
    mains return; the operator must press the power button. The driver
    surfaces this consistently to upsmon callers rather than silently
    failing.
  • test.battery.start.deep — bit writes matching the OEM fullDischarge
    action. Implemented but not yet verified end-to-end (would empty the
    battery during testing).

Safety: shutdowns are opt-in by default

Because the firmware does not auto-restart, an unintended shutdown.return
(e.g. from upsmon on a low-battery event) would leave the UPS off until
somebody physically presses the power button. To avoid silent-fail
integrations, every shutdown.* and test.battery.start.deep instcmd is
gated behind allow_shutdown in ups.conf:

  • Without the flag: instcmds are not registered, direct socket requests are
    rejected with STAT_INSTCMD_INVALID, and upsdrv_shutdown exits with
    failure rather than cutting output.
  • With the flag: behaviour as designed.

The default-deny posture is conservative on purpose; a future Ragtech model
with working auto-restart could carry a firmware_returns capability in
the model table to flip the default.

Wire-level notes

  • ser_get_buf_len() is all-or-nothing: it discards already-collected
    bytes on timeout. CDC-ACM fragments replies into 1–8 byte chunks, so the
    driver uses ser_get_buf() in a manual loop.
  • ser_flush_in() does not call tcflush(); under O_NONBLOCK USB CDC
    bytes inside the tty layer survive its select+read loop. The driver uses
    ser_flush_io() (which calls tcflush(TCIOFLUSH)) before each TX.
  • The driver does not call ser_set_speed() on the CDC-ACM endpoint —
    tcsetattr(TCSANOW) pulses DTR on some Linux tty drivers, which the
    Ragtech firmware can interpret as a shutdown signal. CDC-ACM ignores
    baud at the wire anyway.
  • DTR/RTS are forced to 0 immediately after ser_open (the UPS reads
    non-zero levels as a remote-shutdown signal).
  • tcdrain() + ~50 ms of slack after every ser_send_buf() to let the
    firmware compose the reply.

Files changed

  • drivers/ragtech.c — new driver (single C file).
  • drivers/Makefile.am — registered in SERIAL_DRIVERLIST, ragtech_SOURCES, ragtech_LDADD.
  • docs/man/ragtech.txt — new AsciiDoc man page.
  • docs/man/Makefile.am — three registrations (.txt, MAN_SECTION_CMD_SYS, .html).
  • data/driver.list.in — 20 entries for the family-10 models (level "1",
    reverse engineering).
  • docs/nut.dict — added INNERGIE, NEP, OneUP, Ragtech, TORO and
    bumped the word count.
  • NEWS.adoc — new-driver entry under 2.8.6.

Driver status

DRV_EXPERIMENTAL at version 0.04. Only the Easy 2000 TI has been
validated end-to-end with hardware; the other 19 models share the same
register layout and scaling table but have not been hardware-tested yet.

Test plan

  • Polling status and ups.* variables on Easy 2000 TI — verified.
  • shutdown.stayoff — verified on hardware (AC and battery modes).
  • shutdown.stop — verified on hardware (aborts armed countdown).
  • iout_calib override via ups.conf — verified.
  • allow_shutdown default-deny posture — verified (instcmds not
    registered, upsdrv_shutdown exits with failure).
  • shutdown.return end-to-end — not verified (firmware does not
    auto-restart; surfaces as stayoff + warning).
  • test.battery.start.deep — implemented but not verified
    (deliberately not exercised during RE — would discharge battery).
  • Other 19 family-10 models — community testing requested.

Welcoming feedback on the shutdown.return design (currently warns and
stays off), and on whether to fold the model discrimination into a
per-model scaling table once a second model is verified.

@juslex juslex force-pushed the feat/ragtech-driver branch from 58ce033 to dd33df6 Compare May 17, 2026 17:07
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 17, 2026

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

@juslex juslex force-pushed the feat/ragtech-driver branch 2 times, most recently from f52069c to 03195a7 Compare May 17, 2026 17:31
@AppVeyorBot
Copy link
Copy Markdown

@AppVeyorBot
Copy link
Copy Markdown

Build nut 2.8.5.4730-master failed (commit be8146e980 by @juslex)

@juslex juslex force-pushed the feat/ragtech-driver branch from 03195a7 to a844e27 Compare May 17, 2026 18:06
@AppVeyorBot
Copy link
Copy Markdown

@AppVeyorBot
Copy link
Copy Markdown

@juslex juslex force-pushed the feat/ragtech-driver branch from a844e27 to c7f1687 Compare May 17, 2026 18:43
@AppVeyorBot
Copy link
Copy Markdown

@AppVeyorBot
Copy link
Copy Markdown

@AppVeyorBot
Copy link
Copy Markdown

@juslex juslex force-pushed the feat/ragtech-driver branch from c7f1687 to 8f1fdea Compare May 17, 2026 19:29
@AppVeyorBot
Copy link
Copy Markdown

Targets Brazilian-built Ragtech "Easy Pro" / NEP / TORO / INNERGIE / OneUP
devices that present themselves as USB CDC-ACM (VID 0x04D8, PID 0x000A,
Microchip PIC firmware). Validated end-to-end against an Easy 2000 TI
(reg 0x9A model id = 16) read out of a working OneUP Nitro 2000.

Protocol -- three opcodes observed in OEM traffic:

  0x01 ADDR_HI ADDR_LO VALUE CKSUM   write byte
  0x02 ADDR_HI ADDR_LO MASK  CKSUM   AND mask  (atomic bit-clear)
  0x04 ADDR_HI ADDR_LO COUNT CKSUM   read range
  CKSUM = (ADDR_HI + ADDR_LO + VALUE) & 0xFF

The CDC-ACM channel ignores baud at the wire but DTR/RTS are interpreted
by some Ragtech families as a remote shutdown signal -- the driver forces
both low after open and does NOT call ser_set_speed() to avoid the
tcsetattr() DTR pulse that some Linux tty drivers perform.

The full 30-byte main range (0x80..0x9D) plus V_IOUTCALIB (0xF3) and the
oscillator calibration pair (0x202/0x203) are read; output frequency is
interpolated per devices.xml formula rather than hard-coded.

Twenty models from the family-10 device table populate ups.model,
ups.realpower.nominal, output.voltage scaling, output.current scaling and
battery voltage scaling. input.voltage.nominal switches between 115V and
220V dynamically based on the measured input.

Instcmds implemented:

  shutdown.stayoff  -- aa 02 00 80 fe (clear AUTOSTART) + aa 01 00 98 N.
                       Byte-for-byte identical to the OEM supsvc sequence.
                       Validated: cuts output and stays off until manual
                       power-on, in both AC and battery modes.
  shutdown.return   -- falls back to shutdown.stayoff with a warning.
                       The atomic OR opcode that would set F_AUTOSTART
                       has not been captured yet; read-modify-write with
                       0x01 sends the bytes but does not trip the
                       firmware's shutdown state machine.
  shutdown.stop     -- aa 01 00 98 00 (write V_SHUTDOWNTIMER = 0).
                       Confirmed to abort an armed countdown.
  test.battery.start.deep -- bit writes on regs 0x90 / 0x95 (fullDischarge
                       in devices.xml). Flagged as unverified for the
                       same reason as shutdown.return.

Read primitive uses ser_get_buf() in a manual loop rather than
ser_get_buf_len() (which discards partial reads on timeout -- CDC fragments
replies into 1-8 byte chunks). ser_flush_io() is used before each TX to
clear the kernel CDC buffer (ser_flush_in() only does a select+read loop
that misses queued bytes under O_NONBLOCK).

Reverse engineering primarily from the OEM devices.xml table for register
layout / scaling / flags / actions, and from strace of the OEM supsvc
binary capturing both read polls and the LED/shutdown write sequences.
Cross-checked against UPS_ESP32_tinySrv (https://github.com/antunesls/UPS_ESP32_tinySrv).

Signed-off-by: juslex <66561713+juslex@users.noreply.github.com>
@juslex juslex force-pushed the feat/ragtech-driver branch from 8f1fdea to 92cd420 Compare May 17, 2026 22:09
@AppVeyorBot
Copy link
Copy Markdown

Build nut 2.8.5.4737-master completed (commit 6ac18c0a7b by @juslex)

@AppVeyorBot
Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants