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.
PyAware seems to already decode Serial Data. Look at porting that to portable C for decoding.
- 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 — PyAware itself is not open source), packaged as a PlatformIO / Arduino library with an optional thin C++ wrapper. Full protocol notes are in docs/PROTOCOL.md and the ESP32 wiring in 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, …):
#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/; build it standalone with
cd examples/Esp32SerialMonitor && pio run (it links the parent library via
lib_deps = symlink://../..). See 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)
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:
./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 …)
#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 documented0xa000frame, 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,
doubleis 32-bit float, so very largeWhaccumulators 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.pyas 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-includedTotalKacokWhTotalAcc(41519) — selpi just has two names for it.ACGeneratorPowerappears 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
- pyAWARE - not open source
- selpi - a Python set for seletronics
- splink-influx