First version getting ready to release
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
# Connecting an ESP32 to the SP PRO serial port
|
||||
|
||||
The SP PRO serial port is **RS-232** (±12 V signalling), not RS-485 and not logic-level
|
||||
UART. You therefore **must** put an RS-232 ↔ 3.3 V level shifter between the inverter and
|
||||
the ESP32 — wiring the SP PRO directly to an ESP32 GPIO will damage the ESP32.
|
||||
|
||||
Reference: Selectronic Tech Note
|
||||
[TN0050 "SP PRO Serial Port Pin-out"](https://www.selectronic.com.au/documents/TechNotes/TN0050_02%20SP%20PRO%20Serial%20Port%20Pin-out.pdf),
|
||||
cross-checked with the [selpi `connecting.md`](https://github.com/neerolyte/selpi/blob/main/docs/connecting.md)
|
||||
(which connects via an FT232 USB-to-RS232 cable at 57600 baud).
|
||||
|
||||
## SP PRO RJ45 pinout (TN0050)
|
||||
|
||||
| RJ45 pin | Signal | Use |
|
||||
|---|---|---|
|
||||
| 1 | +12 V isolated (1 A) | optional supply — **do not** feed the ESP32 directly |
|
||||
| 2 | DTR | not needed |
|
||||
| **3** | **TXD** | SP PRO → us |
|
||||
| **4** | **GND** | common ground (required) |
|
||||
| 5 | GND | ground (redundant) |
|
||||
| **6** | **RXD** | us → SP PRO |
|
||||
| 7 | DCD | not needed |
|
||||
| 8 | — | not connected |
|
||||
|
||||
Only **TXD, RXD and GND** are needed. Settings: **57600 baud, 8N1, no flow control.**
|
||||
|
||||
## Level shifter: MAX3232
|
||||
|
||||
A **MAX3232** (3.3 V RS-232 transceiver with internal charge pump) is the right part —
|
||||
it is the 3.3 V-capable successor to the MAX232. RS-485 transceivers (MAX3485 etc.) are
|
||||
**not** suitable here. Add the four 0.1 µF charge-pump capacitors per its datasheet.
|
||||
|
||||
## Wiring
|
||||
|
||||
```
|
||||
SP PRO (RS-232) MAX3232 ESP32 (3.3V UART2)
|
||||
--------------- --------- ------------------
|
||||
RJ45 pin 3 TXD -------> R1IN
|
||||
R1OUT -------------> GPIO16 (RX2)
|
||||
RJ45 pin 6 RXD <------- T1OUT
|
||||
T1IN <------------- GPIO17 (TX2)
|
||||
RJ45 pin 4 GND -------+- GND ----------------+- GND (common!)
|
||||
| |
|
||||
3.3V ----------------- VCC (4x 0.1uF charge-pump caps)
|
||||
```
|
||||
|
||||
- **TXD of one side always goes to RXD of the other** — note the crossover above.
|
||||
- **Common ground is mandatory.** RS-232 is single-ended and needs a shared reference.
|
||||
- RS-232 has no direction-control line (unlike RS-485), so there is nothing equivalent to
|
||||
DE/RE to manage.
|
||||
|
||||
## ESP32 UART
|
||||
|
||||
Use **UART2** to keep UART0 free for USB logging/flashing:
|
||||
|
||||
```cpp
|
||||
Serial2.begin(57600, SERIAL_8N1, /*RX=*/16, /*TX=*/17);
|
||||
```
|
||||
|
||||
GPIO16/17 are safe general-purpose pins on most ESP32 dev boards. (On a few modules with
|
||||
PSRAM these pins are reserved — pick two other free UART-capable GPIOs and update the
|
||||
sketch if so.) See `esp32/sppro_esp32.ino`.
|
||||
|
||||
## Quick bring-up checks
|
||||
|
||||
1. Confirm the SP PRO serial port is enabled and note its **baud** and **password**
|
||||
(front panel → Settings → Communications).
|
||||
2. Loopback-test the MAX3232: short its TTL TX→RX and echo bytes through the ESP32.
|
||||
3. With it wired to the SP PRO, the first query at `0xa000` should echo back; the
|
||||
`sppro-console` demo (or the sketch) will report "connected" once login succeeds.
|
||||
4. If login fails, the usual causes are wrong/blank password, swapped TX/RX, or missing
|
||||
common ground.
|
||||
@@ -0,0 +1,137 @@
|
||||
# SP PRO serial protocol
|
||||
|
||||
This documents the Selectronic SP PRO serial "memory" protocol as implemented in
|
||||
`src/sppro.c`. It is a **port of the open-source Python project
|
||||
[neerolyte/selpi](https://github.com/neerolyte/selpi)** (`memory/*.py`), cross-checked
|
||||
against the Go project [angus-g/splink-influx](https://github.com/angus-g/splink-influx).
|
||||
|
||||
> **Note on PyAware.** The project README pointed at PyAware as prior art. PyAware
|
||||
> (PyPI, by Ampcontrol Group) is **not open source** — its PyPI metadata references a
|
||||
> private SVN repository — so it could not be ported directly. `selpi` implements the
|
||||
> same wire protocol and is fully open, so it was used as the reference instead.
|
||||
|
||||
## Physical layer
|
||||
|
||||
| Setting | Value |
|
||||
|---|---|
|
||||
| Interface | **RS-232** (not RS-485), DB9 or RJ45 jack on the SP PRO |
|
||||
| Baud | **57600** (Port 1; Port 2 defaults to 9600) |
|
||||
| Framing | 8 data bits, no parity, 1 stop bit (8N1) |
|
||||
| Flow control | none |
|
||||
|
||||
See [HARDWARE.md](HARDWARE.md) for pinout and ESP32 wiring.
|
||||
|
||||
## Frames
|
||||
|
||||
All multi-byte fields are **little-endian**. A frame is built from an 8-byte header:
|
||||
|
||||
```
|
||||
byte 0 message type: 'Q' (0x51) query/read, or 'W' (0x57) write
|
||||
byte 1 word count minus one (0x00 = 1 word, 0xff = 256 words)
|
||||
bytes 2..5 memory address (uint32, little-endian)
|
||||
bytes 6..7 CRC-16 over bytes 0..5 (little-endian)
|
||||
```
|
||||
|
||||
### Query (read)
|
||||
Request is just the 8-byte header. The response echoes those 8 bytes, appends the
|
||||
requested data (`words * 2` bytes), then a final CRC-16 over the whole response:
|
||||
|
||||
```
|
||||
request : Q | len-1 | addr[4] | crc[2]
|
||||
response: Q | len-1 | addr[4] | crc[2] | data[words*2] | crc[2]
|
||||
```
|
||||
|
||||
Worked example (documented in selpi) — read 1 word at `0xa000`:
|
||||
|
||||
```
|
||||
request : 51 00 00 a0 00 00 9d 4b
|
||||
response: 51 00 00 a0 00 00 9d 4b 01 00 d8 19 -> data word = 0x0001
|
||||
```
|
||||
|
||||
### Write
|
||||
Header (with its own CRC over bytes 0..5), then the data words, then a CRC-16 over the
|
||||
whole message. The device echoes the entire request back to confirm:
|
||||
|
||||
```
|
||||
request : W | len-1 | addr[4] | crc[2] | data[words*2] | crc[2]
|
||||
response: <exact echo of the request>
|
||||
```
|
||||
|
||||
### CRC-16
|
||||
|
||||
Reflected CRC-CCITT ("Kermit"): 256-entry table, init `0x0000`,
|
||||
`crc = (crc >> 8) ^ table[(crc ^ byte) & 0xff]`, emitted little-endian. A received
|
||||
frame is valid when the CRC computed over the **entire** message (data + trailing CRC)
|
||||
equals `0`. Implemented as `sppro_crc16()`.
|
||||
|
||||
## Login (MD5 challenge / response)
|
||||
|
||||
Reading data registers requires authenticating first:
|
||||
|
||||
1. **Query** 8 words (16 bytes) at `0x1F0000` → challenge `seed`.
|
||||
2. Build `md5( seed[16] + password_padded_to_32_bytes )`. The password is the SP PRO's
|
||||
serial-port password (blank if none); it is padded/truncated to 32 bytes with
|
||||
**spaces**.
|
||||
3. **Swap each adjacent byte pair** of the 16-byte digest (a Selectronic endianness
|
||||
quirk) → 16-byte response.
|
||||
4. **Write** the 16-byte response back to `0x1F0000`.
|
||||
5. **Query** 1 word at `0x1F0010`; value `1` means authenticated.
|
||||
|
||||
Pure helper: `sppro_login_response()`. Full handshake: `sppro_session_login()`.
|
||||
|
||||
## Scaling
|
||||
|
||||
Raw register words are integers; engineering units come from per-device scale factors
|
||||
read once at startup from six consecutive words at `41000`:
|
||||
|
||||
| Address | Name |
|
||||
|---|---|
|
||||
| 41000 | CommonScaleForAcVolts |
|
||||
| 41001 | CommonScaleForAcCurrent |
|
||||
| 41002 | CommonScaleForDcVolts |
|
||||
| 41003 | CommonScaleForDcCurrent |
|
||||
| 41004 | CommonScaleForTemperature |
|
||||
| 41005 | CommonScaleForInternalVoltages |
|
||||
|
||||
With `MAGIC = 32768.0` (from selpi `memory/converter.py`):
|
||||
|
||||
| Conversion | Formula |
|
||||
|---|---|
|
||||
| `ac_w` | `raw * AcVolts * AcCurrent / (MAGIC * 800)` |
|
||||
| `ac_w_signed` | `raw * AcVolts * AcCurrent / (MAGIC * 100)` |
|
||||
| `ac_wh` | `raw * 24 * AcVolts * AcCurrent / (MAGIC * 100)` |
|
||||
| `dc_w` | `raw * DcVolts * DcCurrent / (MAGIC * 100)` |
|
||||
| `dc_wh` | `raw * 24 * DcVolts * DcCurrent / (MAGIC * 100)` |
|
||||
| `dc_v` | `raw * DcVolts / (MAGIC * 10)` |
|
||||
| `temperature` | `raw * Temperature / MAGIC` |
|
||||
| `percent` | `raw / 256` |
|
||||
| `shunt_name` | enum → string (`sppro_shunt_name`) |
|
||||
|
||||
## Register map
|
||||
|
||||
The curated subset shipped in `SPPRO_REGISTERS` (transcribed from selpi `memory/variable.py`):
|
||||
|
||||
| Name | Address | Type | Conversion | Units |
|
||||
|---|---|---|---|---|
|
||||
| BatteryVolts | 0xa05c | u16 | dc_v | V |
|
||||
| BatteryTemperature | 0xa03c | u16 | temperature | C |
|
||||
| DCBatteryPower | 0xa02f | s32 | dc_w | W |
|
||||
| BattSocPercent | 41089 | u16 | percent | % |
|
||||
| LoadAcPower | 41107 | u32 | ac_w | W |
|
||||
| CombinedKacoAcPowerHiRes | 0xa3a8 | u32 | ac_w | W (AC solar) |
|
||||
| ACGeneratorPower | 41098 | s16 | ac_w_signed | W |
|
||||
| Shunt1Power / Shunt2Power | 0xa088 / 0xa089 | s16 | dc_w | W |
|
||||
| Shunt1Name / Shunt2Name | 0xc109 / 0xc10a | s16 | shunt_name | — |
|
||||
| DCkWhInToday / DCkWhOutToday | 41135 / 41137 | u32 | dc_wh | Wh |
|
||||
| BattInkWhTotalAcc / BattOutkWhTotalAcc | 41354 / 41381 | u32 | dc_wh | Wh |
|
||||
| ACLoadkWhTotalAcc | 41438 | u32 | ac_wh | Wh |
|
||||
| TotalKacokWhTotalAcc | 41519 | u32 | ac_wh | Wh |
|
||||
|
||||
selpi's `memory/variable.py` lists many more accumulators (7/30/365-day, yearly,
|
||||
resettable); add rows to `SPPRO_REGISTERS` as needed — the table is the single source
|
||||
of truth and decoding is data-driven.
|
||||
|
||||
## Credits
|
||||
|
||||
- [neerolyte/selpi](https://github.com/neerolyte/selpi) — primary reference (MIT-style, see repo).
|
||||
- [angus-g/splink-influx](https://github.com/angus-g/splink-influx) — independent confirmation.
|
||||
Reference in New Issue
Block a user