182 lines
7.3 KiB
Markdown
182 lines
7.3 KiB
Markdown
# SPPro Access
|
|
|
|
Build a system that can connect to an SPPro Serial Port to get sensible data out of it to display it on a serial console and be used in other projects.
|
|
|
|
Serial decoding is ported to portable C from the open-source [neerolyte/selpi](https://github.com/neerolyte/selpi) project.
|
|
|
|
* Hardware necessary to connect to SPPro
|
|
* https://www.selectronic.com.au/documents/TechNotes/TN0050_02%20SP%20PRO%20Serial%20Port%20Pin-out.pdf
|
|
* How to connect this to an ESP32
|
|
* Parse Serial Data
|
|
* A simple C parser, portable to allow parsing
|
|
|
|
This repo is a **portable C parser** for the SP PRO serial protocol (ported from
|
|
[neerolyte/selpi](https://github.com/neerolyte/selpi)), packaged as a **PlatformIO /
|
|
Arduino library** with an optional thin C++ wrapper. Full protocol notes are in
|
|
[docs/PROTOCOL.md](docs/PROTOCOL.md) and the ESP32 wiring in
|
|
[docs/HARDWARE.md](docs/HARDWARE.md).
|
|
|
|
## Layout
|
|
|
|
```
|
|
src/ sppro.h / sppro.c portable C core: CRC, frame build/parse,
|
|
md5.h / md5.c register map + scaling, MD5 login, session layer
|
|
Sppro.h optional header-only C++/Arduino wrapper (any Stream)
|
|
examples/ Esp32SerialMonitor/ ESP32 example (.ino + platformio.ini)
|
|
docs/ PROTOCOL.md wire protocol, CRC, login, register map + scaling
|
|
HARDWARE.md RS-232 pinout and MAX3232 -> ESP32 wiring
|
|
extras/ host/main.c Linux serial-console dashboard (termios)
|
|
tests/test_sppro.c host known-answer tests (no hardware)
|
|
Makefile host build (test / console)
|
|
library.json, library.properties, keywords.txt PlatformIO + Arduino manifests
|
|
```
|
|
|
|
The core (`src/sppro.*`) is C99, **no dynamic allocation and no I/O** — it builds/parses
|
|
byte buffers and converts raw words to units. I/O is supplied by the caller through a
|
|
small `sppro_transport_t` read/write callback, so the same code runs on a host, an MCU,
|
|
or anything with a serial port. Only `src/` and `examples/` are part of the published
|
|
library; `extras/` is development tooling (Arduino ignores it).
|
|
|
|
## Use on Arduino / ESP32 (PlatformIO or Arduino IDE)
|
|
|
|
Add the library (`lib_deps = SPPro` in PlatformIO once published, or *Sketch → Include
|
|
Library* in the Arduino IDE), then use the `Sppro` wrapper, which works over **any**
|
|
Arduino `Stream` (HardwareSerial, SoftwareSerial, WiFiClient, …):
|
|
|
|
```cpp
|
|
#include <Sppro.h>
|
|
Sppro sp(Serial2); // wraps any Stream
|
|
|
|
void setup() {
|
|
Serial2.begin(57600, SERIAL_8N1, 16, 17); // via a MAX3232 level shifter
|
|
sp.login(""); // "" = no serial password
|
|
sp.readScales();
|
|
}
|
|
void loop() {
|
|
Serial.println(sp.value("BatteryVolts")); // scaled value, or NAN on error
|
|
delay(5000);
|
|
}
|
|
```
|
|
|
|
A complete ESP32 example is in
|
|
[`examples/Esp32SerialMonitor/`](examples/Esp32SerialMonitor); build it standalone with
|
|
`cd examples/Esp32SerialMonitor && pio run` (it links the parent library via
|
|
`lib_deps = symlink://../..`). See [docs/HARDWARE.md](docs/HARDWARE.md) — the SP PRO port
|
|
is RS-232 (±12 V) and **needs a MAX3232 level shifter**; never wire it straight to a GPIO.
|
|
|
|
## Build & test on a host (no hardware)
|
|
|
|
```sh
|
|
cd extras
|
|
make test # build and run the host unit tests
|
|
make host # build ./build/sppro-console
|
|
```
|
|
|
|
Then, with a real SP PRO + USB-RS232 adapter:
|
|
|
|
```sh
|
|
./build/sppro-console /dev/ttyUSB0 [serial_password] [interval_seconds]
|
|
```
|
|
|
|
It logs in, reads the device scale factors, and prints a refreshing dashboard of battery
|
|
voltage, state of charge, load/solar/generator power, and energy totals. The serial-port
|
|
password is set on the inverter (front panel → Settings → Communications); pass `""` if
|
|
none is configured, or set `SPPRO_PASSWORD`.
|
|
|
|
## Use the pure C core (non-Arduino: ESP-IDF, STM32, Linux, Zephyr …)
|
|
|
|
```c
|
|
#include "sppro.h"
|
|
|
|
sppro_transport_t t = { my_read, my_write, my_ctx }; /* your serial callbacks */
|
|
sppro_session_login(&t, ""); /* MD5 challenge/response */
|
|
|
|
sppro_scales_t scales;
|
|
sppro_session_read_scales(&t, &scales);
|
|
|
|
double soc, vbat;
|
|
sppro_session_read(&t, sppro_reg_by_name("BattSocPercent"), &scales, &soc);
|
|
sppro_session_read(&t, sppro_reg_by_name("BatteryVolts"), &scales, &vbat);
|
|
```
|
|
|
|
`extras/host/main.c` is a full worked example of the two callbacks (termios). For
|
|
environments with their own protocol loop, the pure helpers (`sppro_build_query`,
|
|
`sppro_parse_query_response`, `sppro_decode`, `sppro_crc16`, `sppro_login_response`) can
|
|
be used without the session/transport layer.
|
|
|
|
## Status & caveats
|
|
|
|
- Verified by host unit tests (`cd extras && make test`): CRC against selpi's documented
|
|
`0xa000` frame, MD5 known-answers, frame round-trips, and the scaling formulas. The
|
|
ESP32 example + C++ wrapper are verified to compile/link via PlatformIO. **Not yet
|
|
verified against physical hardware** — confirm decoded values against the inverter's own
|
|
display.
|
|
- On 8-bit AVR, `double` is 32-bit float, so very large `Wh` accumulators lose some
|
|
precision (fine for display). Use the 32-bit-int register types directly if you need
|
|
exact counts.
|
|
- Read-only monitoring is the focus. Write framing (`sppro_build_write` /
|
|
`sppro_session_write`) exists because login needs it, but no inverter-setting writes
|
|
are wired into the demos.
|
|
- The register table is a useful subset; add rows from selpi `memory/variable.py` as
|
|
needed (decoding is table-driven).
|
|
|
|
## Not-yet-mapped registers (future work)
|
|
|
|
`SPPRO_REGISTERS` covers all the live measurements plus the most useful energy totals.
|
|
The following entries exist in selpi `memory/variable.py` but are **not** in the table
|
|
yet. All are decodable by the existing converters — adding them is pure data (new rows),
|
|
no new code.
|
|
|
|
Scale factor (already used internally via `sppro_scales_t`, just not exposed as a row):
|
|
|
|
| Name | Addr | Type | Conv |
|
|
|---|---|---|---|
|
|
| CommonScaleForInternalVoltages | 41005 | u16 | raw |
|
|
|
|
Battery "out" energy accumulators (all `u32`, `dc_wh`):
|
|
|
|
| Name | Addr |
|
|
|---|---|
|
|
| BattOutkWhAcc | 41143 |
|
|
| QuickView_BattOutkWhAcc | 41178 |
|
|
| BattOutkWhPreviousAcc | 41356 |
|
|
| BattOutkWh7DayAcc / …AccAvg | 41358 / 41360 |
|
|
| BattOutkWh30DayAcc / …AccAvg | 41362 / 41364 |
|
|
| BattOutkWh365DayAcc / …AccAvg | 41366 / 41368 |
|
|
| BattOutkWhYearAcc / …AccAvg | 41370 / 41372 |
|
|
| BattOutkWhResetableAcc / …AccAvg | 41374 / 41376 |
|
|
|
|
Other DC energy:
|
|
|
|
| Name | Addr | Type | Conv |
|
|
|---|---|---|---|
|
|
| DCkWhOut | 41257 | u32 | dc_wh |
|
|
| Shunt1WhTotalAcc | 41305 | s32 | dc_wh |
|
|
| Shunt1WhTodayAcc | 41146 | s16 | dc_wh |
|
|
|
|
AC energy (input / export / load / solar):
|
|
|
|
| Name | Addr | Type | Conv |
|
|
|---|---|---|---|
|
|
| ACInputWhTotalAcc | 41459 | u32 | ac_wh |
|
|
| ACInputWhTodayAcc | 41151 | u16 | ac_wh |
|
|
| ACExportWhTotalAcc | 41499 | u32 | ac_wh |
|
|
| ACExportWhTodayAcc | 41154 | u16 | ac_wh |
|
|
| ACLoadWhAcc | 41150 | u16 | ac_wh |
|
|
| ACSolarWhTotalAcc | 41519 | u32 | ac_wh |
|
|
| ACSolarWhTodayAcc | 41157 | s16 | ac_wh |
|
|
|
|
Two quirks when transcribing these:
|
|
|
|
- `ACSolarWhTotalAcc` (41519) is the **same address** as the already-included
|
|
`TotalKacokWhTotalAcc` (41519) — selpi just has two names for it.
|
|
- `ACGeneratorPower` appears twice in selpi's MAP; the second definition wins (`s16`,
|
|
`ac_w_signed`) — that is the one already in the table. The first (`u32`, `ac_w`) is dead.
|
|
|
|
## See Also
|
|
|
|
* selpi - a Python set for seletronics
|
|
* https://github.com/neerolyte/selpi
|
|
* splink-influx
|
|
* https://github.com/angus-g/splink-influx
|