Skip to content

Commit a0e08f5

Browse files
authored
Merge pull request #14 from miklosbagi/nut-driver-reorg
nut-driver/: this repo is the driver's source of truth
2 parents a16f921 + 26ddda1 commit a0e08f5

7 files changed

Lines changed: 103 additions & 67 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@ on:
77
branches: ["main"]
88

99
jobs:
10+
# NUT's own docs/developers.txt describes this indent invocation as
11+
# something that "goes a long way towards" the house style, not a
12+
# strictly CI-enforced formatter -- upstream itself doesn't gate on a
13+
# zero-diff result. We hold ourselves to that bar anyway, deliberately
14+
# stricter than what's actually required: it's cheap, objective, and
15+
# removes any ambiguity before this driver is ever proposed upstream.
16+
nut-driver-style:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Install GNU indent
22+
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends indent
23+
24+
- name: Check nut-driver/sigenergy_modbus.c formatting
25+
run: |
26+
indent -kr -i8 -T FILE -l1000 -nhnl -o /tmp/formatted.c nut-driver/sigenergy_modbus.c
27+
if ! diff -u nut-driver/sigenergy_modbus.c /tmp/formatted.c; then
28+
echo "::error::nut-driver/sigenergy_modbus.c doesn't match 'indent -kr -i8 -T FILE -l1000 -nhnl' -- run that command locally and commit the result."
29+
exit 1
30+
fi
31+
1032
ci:
1133
runs-on: ubuntu-latest
1234
steps:

‎.github/workflows/codeql.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ jobs:
2828
- name: Fetch upstream NUT and vendor in the driver
2929
run: |
3030
git clone --depth 1 --branch master https://github.com/networkupstools/nut.git
31-
cp driver/sigenergy_modbus.c nut/drivers/sigenergy_modbus.c
31+
cp nut-driver/sigenergy_modbus.c nut/drivers/sigenergy_modbus.c
3232
cd nut
33-
git apply ../driver/sigenergy_modbus-makefile.patch
33+
git apply ../nut-driver/sigenergy_modbus-makefile.patch
3434
3535
- uses: github/codeql-action/init@v3
3636
with:

‎README.hu.md‎

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,26 @@ rendszert UPS-ként lehessen felügyelni a Modbus TCP interfészén keresztül
2020
azonosítás, közvetlenül a plant és inverter Modbus regisztereiből.
2121

2222
**Állapot: még nincs upstreamben.** A
23-
[`driver/sigenergy_modbus.c`](driver/sigenergy_modbus.c) a NUT saját
24-
driver API-ja és kódolási szabályai szerint íródott, azzal a céllal, hogy
25-
végül közvetlenül bekerüljön a
26-
[`networkupstools/nut`](https://github.com/networkupstools/nut) projektbe
27-
— de az ehhez tartozó PR még nincs megnyitva. Ez a repó azért létezik,
28-
hogy a drivert *már most* tesztelhesd, a saját SigenStorodon, anélkül
29-
hogy erre várnod kellene. Ez az image forrásból építi a NUT-ot (alapból
30-
az upstream `master` ágból), és beépíti ezt a drivert a
31-
[`driver/sigenergy_modbus-makefile.patch`](driver/sigenergy_modbus-makefile.patch)
32-
segítségével — amint a driver bekerül az upstreambe, ez a repó feleslegessé
33-
válik mindenki számára, aki elég friss NUT-verziót futtat ahhoz, hogy az
34-
már tartalmazza.
23+
[`nut-driver/sigenergy_modbus.c`](nut-driver/sigenergy_modbus.c) a NUT
24+
saját driver API-ja és kódolási szabályai szerint íródott, azzal a
25+
céllal, hogy végül közvetlenül bekerüljön a
26+
[`networkupstools/nut`](https://github.com/networkupstools/nut)
27+
projektbe. Ez a repó a driver tényleges forrása — nem egy fork vagy PR
28+
ág lemásolt változata: a `nut-driver/` itt fejlesztve és verziózva van,
29+
és időről időre kerül csak exportálásra egy fork-ba
30+
(`miklosbagi/nut`), pusztán az upstream felé történő javaslattétel
31+
előkészítéseként. Ez szándékos sorrend, nem csupán kényelmi szempont:
32+
semmi nem garantálja, hogy az upstream elfogadja az új drivert, így a
33+
`sigennut` pontosan ugyanúgy működik tovább, függetlenül attól, hogy azt
34+
a PR-t elfogadják, elutasítják, vagy soha nem dől el — legrosszabb
35+
esetben ez egy tartósan karbantartott "NUT plusz ez az egy driver" image
36+
marad, nem valami, ami leáll működni, ha az upstream nemet mond. Ez az
37+
image forrásból építi a NUT-ot (alapból az upstream `master` ágból), és
38+
beépíti ezt a drivert a
39+
[`nut-driver/sigenergy_modbus-makefile.patch`](nut-driver/sigenergy_modbus-makefile.patch)
40+
segítségével — ha/amint a driver bekerül az upstreambe, ez a patch-lépés
41+
(de maga a driver nem) feleslegessé válik mindenki számára, aki elég
42+
friss NUT-verziót futtat ahhoz, hogy az már tartalmazza.
3543

3644
Tervezésénél fogva csak olvas: ez a driver sehol nem tartalmaz
3745
`modbus_write_*` hívást. Nem konfigurálja át az invertert, nem ütemez
@@ -91,8 +99,8 @@ A `docker/Dockerfile` egy kétlépcsős build:
9199

92100
1. **builder** — sekély (shallow) klónozást végez az upstream NUT-ból
93101
(`ARG NUT_REF`, alapból `master`), bemásolja a
94-
`driver/sigenergy_modbus.c`-t, alkalmazza a
95-
`driver/sigenergy_modbus-makefile.patch`-et (hangosan elbukik a build,
102+
`nut-driver/sigenergy_modbus.c`-t, alkalmazza a
103+
`nut-driver/sigenergy_modbus-makefile.patch`-et (hangosan elbukik a build,
96104
ha az upstream `drivers/Makefile.am`-ja túl sokat változott ahhoz,
97105
hogy a patch alkalmazható legyen — ez jobb, mintha csendben olyan
98106
image készülne, amelybe a driver nincs bekötve), majd konfigurálja és
@@ -120,7 +128,7 @@ pontosan `0440` móddal, `nut:nut` tulajdonossal rendelkezik, majd lefut az
120128

121129
GPL-2.0-or-later — lásd [LICENSE](LICENSE). Ugyanaz, mint maga a
122130
[NUT](https://github.com/networkupstools/nut), és a
123-
[`driver/sigenergy_modbus.c`](driver/sigenergy_modbus.c) saját fejléce,
131+
[`nut-driver/sigenergy_modbus.c`](nut-driver/sigenergy_modbus.c) saját fejléce,
124132
mivel ez az image mindkettőt újraterjeszti.
125133

126134
A `docker/files/startup.sh` a

‎README.md‎

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,24 @@ over its Modbus TCP interface — `battery.charge`, `ups.status`
1919
`ups.realpower`, and device identity, straight from the plant and inverter
2020
Modbus registers.
2121

22-
**Status: not upstream yet.** [`driver/sigenergy_modbus.c`](driver/sigenergy_modbus.c)
22+
**Status: not upstream yet.** [`nut-driver/sigenergy_modbus.c`](nut-driver/sigenergy_modbus.c)
2323
is written against NUT's own driver API and coding standards, aimed at
2424
eventual inclusion in [`networkupstools/nut`](https://github.com/networkupstools/nut)
25-
directly — but that PR hasn't been opened yet. This repo exists so you can
26-
test the driver *now*, against your own SigenStor, without waiting on that.
27-
This image builds NUT from source (upstream `master`, by default) and
28-
vendors this driver into it via [`driver/sigenergy_modbus-makefile.patch`](driver/sigenergy_modbus-makefile.patch)
29-
— once the driver is merged upstream, this repo becomes unnecessary for
30-
anyone running a NUT version recent enough to include it.
25+
directly. This repo is that driver's actual source of truth, not a
26+
downstream copy of some fork or PR branch — `nut-driver/` is developed
27+
and versioned here, and periodically exported to a fork
28+
(`miklosbagi/nut`) purely as a staging area for proposing it upstream.
29+
That's a deliberate ordering, not just convenience: there's no guarantee
30+
upstream accepts a new driver, and this way `sigennut` keeps working
31+
exactly the same regardless of whether that PR is accepted, rejected, or
32+
never resolved — worst case, this becomes a permanently-maintained
33+
"NUT plus this one driver" image, not something that stops working if
34+
upstream says no. This image builds NUT from source (upstream `master`,
35+
by default) and vendors this driver into it via
36+
[`nut-driver/sigenergy_modbus-makefile.patch`](nut-driver/sigenergy_modbus-makefile.patch)
37+
— if/once the driver is merged upstream, that patch step (but not the
38+
driver itself) becomes unnecessary for anyone running a NUT version
39+
recent enough to include it.
3140

3241
Read-only by design: this driver contains no `modbus_write_*` call
3342
anywhere. It won't reconfigure your inverter, schedule charging, or touch
@@ -106,8 +115,8 @@ its full scenario library and HTTP control API.
106115
`docker/Dockerfile` is a two-stage build:
107116

108117
1. **builder** — shallow-clones upstream NUT (`ARG NUT_REF`, default
109-
`master`), copies in `driver/sigenergy_modbus.c`, applies
110-
`driver/sigenergy_modbus-makefile.patch` (fails the build loudly if
118+
`master`), copies in `nut-driver/sigenergy_modbus.c`, applies
119+
`nut-driver/sigenergy_modbus-makefile.patch` (fails the build loudly if
111120
upstream's `drivers/Makefile.am` has drifted too far for the patch to
112121
apply — better than silently shipping an image without the driver
113122
wired in), then configures and compiles just this one driver with the
@@ -153,8 +162,8 @@ config file is exactly mode `0440` owned by `nut:nut`, then runs
153162

154163
GPL-2.0-or-later — see [LICENSE](LICENSE). Same as
155164
[NUT](https://github.com/networkupstools/nut) itself and
156-
[`driver/sigenergy_modbus.c`](driver/sigenergy_modbus.c)'s own header,
157-
since this image redistributes both.
165+
[`nut-driver/sigenergy_modbus.c`](nut-driver/sigenergy_modbus.c)'s own
166+
header, since this image redistributes both.
158167

159168
`docker/files/startup.sh` is copied from
160169
[`gpdm/nut-upsd`](https://github.com/gpdm/nut/tree/master/nut-upsd) under

‎docker/Dockerfile‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,18 @@
1111
#
1212
# Unlike gpdm's image (which apk-installs stock NUT only), this one
1313
# clones upstream NUT fresh at build time, vendors in
14-
# ../driver/sigenergy_modbus.c (this driver isn't in NUT yet), and
15-
# applies ../driver/sigenergy_modbus-makefile.patch to wire it into the
16-
# build -- then, same as gpdm, apk-installs stock NUT for upsd/
14+
# ../nut-driver/sigenergy_modbus.c (this driver isn't in NUT yet), and
15+
# applies ../nut-driver/sigenergy_modbus-makefile.patch to wire it into
16+
# the build -- then, same as gpdm, apk-installs stock NUT for upsd/
1717
# upsdrvctl/the nut user, and splices in the freshly compiled driver
1818
# binary on top of it.
19+
#
20+
# nut-driver/ is deliberately this repo's own source of truth for the
21+
# driver, not a build-time dependency on any fork/PR -- this image
22+
# keeps working exactly the same way regardless of what happens to any
23+
# upstream submission (accepted, rejected, or never resolved). Fork
24+
# branches are just a periodic export target for actually proposing the
25+
# driver upstream, not something this build relies on.
1926

2027
ARG NUT_REF=master
2128

@@ -36,8 +43,8 @@ WORKDIR /src
3643

3744
RUN git clone --depth 1 --branch "${NUT_REF}" https://github.com/networkupstools/nut.git
3845

39-
COPY driver/sigenergy_modbus.c nut/drivers/sigenergy_modbus.c
40-
COPY driver/sigenergy_modbus-makefile.patch /tmp/sigenergy_modbus-makefile.patch
46+
COPY nut-driver/sigenergy_modbus.c nut/drivers/sigenergy_modbus.c
47+
COPY nut-driver/sigenergy_modbus-makefile.patch /tmp/sigenergy_modbus-makefile.patch
4148

4249
WORKDIR /src/nut
4350

File renamed without changes.
Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* as a single UPS via its Modbus TCP interface.
44
*
55
* Copyright (C)
6-
* 2026 Miklos Bagi <mb_sigennut@mbag.at>
6+
* 2026 Miklos Bagi <mb_nutdco@mbag.at>
77
*
88
* This program is free software; you can redistribute it and/or modify
99
* it under the terms of the GNU General Public License as published by
@@ -23,19 +23,17 @@
2323
/*
2424
* Register addresses and their meanings are sourced from Sigenergy's
2525
* own official Modbus Protocol spec (V2.9), cross-checked against real
26-
* device captures. See ../../nut-sigenergy/docs/driver-coding-standards.md
27-
* and ../../sigennut/docs/register-map.md (sibling research repos) for
28-
* the full provenance, including one still-open item this driver's
29-
* OL/OB mapping depends on:
26+
* device captures on a live installation. One item this driver's OL/OB
27+
* mapping depends on is still open:
3028
*
31-
* R11 (from sigennut's PRD): `on_off_grid_status` (30009) is documented
32-
* by the spec as the plant's real on/off-grid signal, and that mapping
33-
* is used directly below -- but it has not yet been validated against
34-
* an actual real-world grid-loss/grid-return transition on the specific
35-
* installation this was developed against (no grid was ever wired to
36-
* that unit). Treat OL/OB as spec-correct-by-design but not yet
37-
* field-proven; revisit if a real transition is ever observed to behave
38-
* differently than expected.
29+
* `on_off_grid_status` (30009) is documented by the spec as the plant's
30+
* real on/off-grid signal, and that mapping is used directly below --
31+
* but it has not yet been validated against an actual real-world
32+
* grid-loss/grid-return transition on the specific installation this
33+
* was developed against (no grid was ever wired to that unit). Treat
34+
* OL/OB as spec-correct-by-design but not yet field-proven; revisit if
35+
* a real transition is ever observed to behave differently than
36+
* expected.
3937
*
4038
* This is a deliberately minimal first driver (project outline "Phase
4139
* 1"): OL / OB / LB / CHRG / DISCHRG, battery.charge, input.voltage,
@@ -92,7 +90,7 @@
9290
upsdrv_info_t upsdrv_info = {
9391
DRIVER_NAME,
9492
DRIVER_VERSION,
95-
"Miklos Bagi <mb_sigennut@mbag.at>",
93+
"Miklos Bagi <mb_nutdco@mbag.at>",
9694
DRV_EXPERIMENTAL,
9795
{ NULL }
9896
};
@@ -114,8 +112,7 @@ static int errcount = 0;
114112
* here). No IPv6 bracket handling needed: Sigenergy's Modbus TCP
115113
* interface is IPv4/hostname only.
116114
*/
117-
static int parse_host_port(const char *input, char *host, size_t host_buf_size,
118-
char *port, size_t port_buf_size, uint16_t default_port)
115+
static int parse_host_port(const char *input, char *host, size_t host_buf_size, char *port, size_t port_buf_size, uint16_t default_port)
119116
{
120117
const char *colon;
121118
size_t host_size, port_size;
@@ -175,8 +172,7 @@ static int read_regs(int unit, int addr, int count, uint16_t *dest)
175172
}
176173

177174
if (modbus_read_registers(mbctx, addr, count, dest) == -1) {
178-
upslogx(LOG_ERR, "read_regs: unit %d addr %d count %d: %s",
179-
unit, addr, count, modbus_strerror(errno));
175+
upslogx(LOG_ERR, "read_regs: unit %d addr %d count %d: %s", unit, addr, count, modbus_strerror(errno));
180176
errcount++;
181177
return -1;
182178
}
@@ -267,7 +263,13 @@ void upsdrv_initinfo(void)
267263
* "Low-Battery Policy" section: BESS reserve is a policy choice,
268264
* not something to blindly inherit from the device. */
269265
if (getval("low_battery_soc")) {
270-
low_battery_soc = strtod(getval("low_battery_soc"), NULL);
266+
char *soc_endptr;
267+
const char *soc_arg = getval("low_battery_soc");
268+
269+
low_battery_soc = strtod(soc_arg, &soc_endptr);
270+
if (soc_endptr == soc_arg || *soc_endptr != '\0') {
271+
fatalx(EXIT_FAILURE, "upsdrv_initinfo: low_battery_soc '%s' is not a valid number", soc_arg);
272+
}
271273
} else if (read_regs(plant_addr, REG_ESS_DISCHARGE_CUTOFF_SOC, 1, &reg) == 0 && reg > 0) {
272274
low_battery_soc = reg / 10.0;
273275
}
@@ -284,9 +286,7 @@ void upsdrv_updateinfo(void)
284286

285287
errcount = 0;
286288

287-
if (read_regs(plant_addr, REG_ON_OFF_GRID_STATUS, 1, &grid_status) == -1
288-
|| read_regs(plant_addr, REG_ESS_SOC, 1, &soc) == -1
289-
|| read_regs(plant_addr, REG_ESS_POWER, 2, ess_power_regs) == -1) {
289+
if (read_regs(plant_addr, REG_ON_OFF_GRID_STATUS, 1, &grid_status) == -1 || read_regs(plant_addr, REG_ESS_SOC, 1, &soc) == -1 || read_regs(plant_addr, REG_ESS_POWER, 2, ess_power_regs) == -1) {
290290
dstate_datastale();
291291
return;
292292
}
@@ -330,12 +330,7 @@ void upsdrv_updateinfo(void)
330330
* spec's Appendix 2-13 enumerations is deferred, see this file's
331331
* header comment (R2/Phase 3). general_alarm7 (30281) is
332332
* deliberately excluded here, see header comment. */
333-
if (read_regs(plant_addr, REG_GENERAL_ALARM1, 1, &alarm1) == 0
334-
&& read_regs(plant_addr, REG_GENERAL_ALARM2, 1, &alarm2) == 0
335-
&& read_regs(plant_addr, REG_GENERAL_ALARM3, 1, &alarm3) == 0
336-
&& read_regs(plant_addr, REG_GENERAL_ALARM4, 1, &alarm4) == 0
337-
&& read_regs(plant_addr, REG_GENERAL_ALARM5, 1, &alarm5) == 0
338-
&& read_regs(plant_addr, REG_GENERAL_ALARM6, 1, &alarm6) == 0) {
333+
if (read_regs(plant_addr, REG_GENERAL_ALARM1, 1, &alarm1) == 0 && read_regs(plant_addr, REG_GENERAL_ALARM2, 1, &alarm2) == 0 && read_regs(plant_addr, REG_GENERAL_ALARM3, 1, &alarm3) == 0 && read_regs(plant_addr, REG_GENERAL_ALARM4, 1, &alarm4) == 0 && read_regs(plant_addr, REG_GENERAL_ALARM5, 1, &alarm5) == 0 && read_regs(plant_addr, REG_GENERAL_ALARM6, 1, &alarm6) == 0) {
339334
if (alarm1 || alarm2 || alarm3 || alarm4 || alarm5 || alarm6) {
340335
status_set("ALARM");
341336
}
@@ -367,12 +362,7 @@ void upsdrv_shutdown(void)
367362

368363
void upsdrv_help(void)
369364
{
370-
printf("\nSigenergy-specific options:\n"
371-
" plant_addr: Modbus unit id for plant-level registers (default: %d)\n"
372-
" inverter_addr: Modbus unit id for inverter-level registers (default: %d)\n"
373-
" low_battery_soc: LB threshold in %% state-of-charge (default: device's own\n"
374-
" configured reserve floor if nonzero, else %.0f)\n",
375-
DEFAULT_PLANT_ADDR, DEFAULT_INVERTER_ADDR, DEFAULT_LOW_BATTERY_SOC);
365+
printf("\nSigenergy-specific options:\n" " plant_addr: Modbus unit id for plant-level registers (default: %d)\n" " inverter_addr: Modbus unit id for inverter-level registers (default: %d)\n" " low_battery_soc: LB threshold in %% state-of-charge (default: device's own\n" " configured reserve floor if nonzero, else %.0f)\n", DEFAULT_PLANT_ADDR, DEFAULT_INVERTER_ADDR, DEFAULT_LOW_BATTERY_SOC);
376366
}
377367

378368
void upsdrv_tweak_prognames(void)

0 commit comments

Comments
 (0)