diff --git a/.editorconfig b/.editorconfig index e620b1b..dc806e6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,8 +11,8 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[*.{py,yaml,yml}] +[*.{py,yaml,yml,md}] indent_style = space -[*.{yaml,yml}] +[*.{yaml,yml,md}] indent_size = 2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b820aa6..f4b6d51 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,6 +26,8 @@ repos: rev: 2.3.54 hooks: - id: editorconfig-checker + args: + - "--exclude=Protocol.md" - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 @@ -38,7 +40,7 @@ repos: - id: mypy - repo: https://github.com/psf/black - rev: 21.9b0 + rev: 21.10b0 hooks: - id: black diff --git a/Protocol.md b/Protocol.md new file mode 100644 index 0000000..160f751 --- /dev/null +++ b/Protocol.md @@ -0,0 +1,85 @@ +# Protocol + +The protocol checksum is litle-endian CRC16-MODBUS, but the data itself seems to be big-endian (network order). +It is structured around 16bit words (read 4 returns 8 bytes). + +I have seen the "Transfer ID?" field containing 0xFF and 0x01, I am not yet sure of the putpose of this field. Most transactions it is set to 0xFF. + +## Reading + +### Read request + +```text + ┌──────────────┐ + ╔═╡ Transfer ID? │ + ║ └──────────────┘ + ║ ┌────────────────────┐ + ║ ╔═╡ Operation (3=read) │ + ║ ║ └────────────────────┘ + ║ ║ ┌───────────────┐ + ║ ║ ╔═╡ Start address │ + ║ ║ ║ └───────────────┘ + ║ ║ ║ ┌────────────────────────────────┐ + ║ ║ ║ ╔═╡ Number of 2-byte words to read │ + ║ ║ ║ ║ └────────────────────────────────┘ + ║ ║ ║ ║ ┌─────────────────┐ + ║ ║ ║ ║ ╔═╡ Transaction CRC │ + ║ ║ ║ ║ ║ └─────────────────┘ +┌╨─┬╨─┬──╨──┬──╨──┬──╨──┐ +│FF│03│00 0C│00 08│91 d1│ +└──┴──┴─────┴─────┴─────┘ +``` + +### Read response + +```text + ┌──────────────┐ + ╔═╡ Transfer ID? │ + ║ └──────────────┘ + ║ ┌────────────────────┐ + ║ ╔═╡ Operation (3=read) │ + ║ ║ └────────────────────┘ + ║ ║ ┌──────────────────────────┐ + ║ ║ ╔═╡ Number of bytes returned │ + ║ ║ ║ └──────────────────────────┘ + ║ ║ ║ ┌──────┐ ┌─────────────────┐ + ║ ║ ║ ╔═╡ Data │ │ Transaction CRC ╞═╗ + ║ ║ ║ ║ └──────┘ └─────────────────┘ ║ +┌╨─┬╨─┬╨─┬╨──────────────────────────────────────────────┬──╨──┐ +│FF│03│10│20 20 20 20 4D 4C 32 34 32 30 20 20 20 20 20 20│FD 17│ +└──┴──┴──┴───────────────────────────────────────────────┴─────┘ +``` + +This particular memory section contains the device SKU: ML2420 + +## Writing + +### Write request + +```text + ┌──────────────┐ + ╔═╡ Transfer ID? │ + ║ └──────────────┘ + ║ ┌────────────────────┐ + ║ ╔═╡ Operation (6=read) │ + ║ ║ └────────────────────┘ + ║ ║ ┌─────────┐ + ║ ║ ╔═╡ Address │ + ║ ║ ║ └─────────┘ + ║ ║ ║ ┌──────┐ + ║ ║ ║ ╔═╡ Data │ + ║ ║ ║ ║ └──────┘ + ║ ║ ║ ║ ┌─────────────────┐ + ║ ║ ║ ║ ╔═╡ Transaction CRC │ + ║ ║ ║ ║ ║ └─────────────────┘ +┌╨─┬╨─┬──╨──┬──╨──┬──╨──┐ +│FF│06│01 0A│00 01│7C 2A│ +└──┴──┴─────┴─────┴─────┘ +``` + +The data at 0x010A is a boolean controlling the load output switch. + +### Write response + +Seems to return exactly the same as the data written, +presumably to allow verifying that the data did indeed get written. diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..f39ad8e --- /dev/null +++ b/Readme.md @@ -0,0 +1,15 @@ +# SolarMPPT + + + +Python library for interracting with the rather generic MPPT solar charge controller I got from the hardware store. + +- [Biltema 25-5077](https://www.biltema.no/bil---mc/elektrisk-anlegg/solcellspaneler/mppt-regulator-20-a-2000045547) + +The Android app suggested for the bluetooth interface is +[SolarApp](https://play.google.com/store/apps/details?id=com.shuori.gfv2.guangfu) by srne +(I'm not currently able to find the bluetooth bridge on Biltema's website? +It's got BT-1 printed on the front, and is basically just a RS-232 to BTLE UART GATT) + +[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) +![example workflow](https://github.com/oddstr13/SolarMPPT/actions/workflows/pre-commit/badge.svg) diff --git a/solar_ble.py b/solar_ble.py index ac30fc7..6b98fab 100755 --- a/solar_ble.py +++ b/solar_ble.py @@ -15,24 +15,33 @@ INTERVAL = 15 write_device = "0000ffd1-0000-1000-8000-00805f9b34fb" # read_device = "0000fff1-0000-1000-8000-00805f9b34fb" - +# get(255, 12, 2) +# "ff 03 00 0c 00 02" CMD_GET_1 = b"\xff\x03\x00\x0c\x00\x02" # > ff 03 04 20 20 20 20 +# get(255, 12, 8) +# ff 03 00 0c 00 08 CMD_GET_MODEL = b"\xff\x03\x00\x0c\x00\x08" # > ff 03 10 20 20 20 20 4d 4c 32 34 32 30 20 20 20 20 20 20 # Device SKU: ML2420 +# get(255, 20, 4) +# ff 03 00 14 00 04 CMD_GET_VERSION = b"\xff\x03\x00\x14\x00\x04" # > ff 03 08 00 04 02 00 02 00 00 03 # CC ?? 11 22 33 ?? 44 55 66 # Version: 4.2.0 +# get(255, 24, 3) +# ff 03 00 18 00 03 CMD_GET_SERIAL = b"\xff\x03\x00\x18\x00\x03" # > ff 03 06 3c 13 02 67 00 01 # CC 11 22 33 33 ?? ?? # SN: 60-19-0615 +# get(255, 256, 7) +# ff 03 01 00 00 07 CMD_GET_BATTERY_STATE = b"\xff\x03\x01\x00\x00\x07" # > ff 03 0e 00 48 00 7e 00 1d 0e 0d 00 7e 00 1c 00 03 # CC 11 11 22 22 33 33 44 55 66 66 77 77 88 88 @@ -45,6 +54,8 @@ CMD_GET_BATTERY_STATE = b"\xff\x03\x01\x00\x00\x07" # 7: Load current: 0.28 A # 8: Load power: 3 W +# get(255, 263, 4) +# ff 03 01 07 00 04 CMD_GET_PANEL_STATUS = b"\xff\x03\x01\x07\x00\x04" # > ff 03 08 00 c8 00 14 00 04 00 01 # CC 11 11 22 22 33 33 ?? ?? @@ -53,6 +64,21 @@ CMD_GET_PANEL_STATUS = b"\xff\x03\x01\x07\x00\x04" # 3: Panel power: 4 W # Charging status? +# set(255, 266, 1 or 0) +# ff 06 01 0a 00 01 +CMD_ENABLE_LOAD = b"\xff\x06\x01\x0a\x00\x01" +CMD_DISABLE_LOAD = b"\xff\x06\x01\x0a\x00\x00" +REG_LOAD_ENABLE = 0x010A + +# get(255, 267, 21) +# ff 03 01 0b 00 15 +CMD_GET_LOAD_PARAMETERS = b"\xff\x03\x01\x0b\x00\x15" +# > ff 03 2a 00 7c 00 7f 00 51 00 20 00 0a 00 03 00 00 00 00 00 +# > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# > 00 00 00 00 00 + +# get(255, 288, 3) +# ff 03 01 20 00 03 CMD_GET_2 = b"\xff\x03\x01\x20\x00\x03" # > ff 03 06 80 02 00 00 00 00 # CC 11 22 33 33 33 33 @@ -60,6 +86,8 @@ CMD_GET_2 = b"\xff\x03\x01\x20\x00\x03" # 2: ?: 2 # 3: ?: 0 +# get(255, 57345, 33) +# ff 03 e0 01 00 21 CMD_GET_BATTERY_PARAMETERS = b"\xff\x03\xe0\x01\x00\x21" # > ff 03 42 07 d0 00 c8 ff 0c 00 02 00 a0 00 9b 00 92 00 90 00 # > 8a 00 84 00 7e 00 78 00 6f 00 6a 64 32 00 05 00 78 00 78 00 @@ -67,13 +95,8 @@ CMD_GET_BATTERY_PARAMETERS = b"\xff\x03\xe0\x01\x00\x21" # > 0f 00 05 00 05 00 04 01 00 # 33 * uint16 -# (0xff, 267, 21) -CMD_GET_LOAD_PARAMETERS = b"\xff\x03\x01\x0b\x00\x15" -# > ff 03 2a 00 7c 00 7f 00 51 00 20 00 0a 00 03 00 00 00 00 00 -# > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# > 00 00 00 00 00 - -# 01 03 f000 000a +# get(1, 61440, 10) +# 01 03 f0 00 00 0a CMD_GET_HISTORICAL_TODAY = b"\x01\x03\xf0\x00\x00\x0a" CMD_GET_HISTORICAL_YESTERDAY = b"\x01\x03\xf0\x01\x00\x0a" CMD_GET_HISTORICAL_D2 = b"\x01\x03\xf0\x02\x00\x0a" @@ -103,9 +126,7 @@ CMD_GET_HISTORICAL_D3 = b"\x01\x03\xf0\x03\x00\x0a" # production_power = 0 Wh # consumption_power = 0 Wh -CMD_ENABLE_LOAD = b"\xff\x06\x01\x0a\x00\x01" -CMD_DISABLE_LOAD = b"\xff\x06\x01\x0a\x00\x00" - +# ff 78 00 00 00 01 CMD_ = b"\xff\x78\x00\x00\x00\x01" # CMD_GET_BATTERY_STATE = b'\xff\x03\x01\x00\x00\x07'