-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TCP source for lightweight, extended data gathering (#134)
* Add TCP source for lightweight, extended data gathering Certain Omnik inverters (supposedly with a specific serial number, see sources below) reply with its statistics as binary data when a "magic" packet is sent to them on port 8899 over TCP. This data is more lightweight than an HTML page or JavaScript source with values embedded, and provides more statistics then either similar to what can be found on the LCD display. The data format and meaning has been extracted from various Python/C sources (see links below), with some of my own "work" to simplify checksum calculation and "the reverse hexadecimal serial number" is nothing more than its little-endian byte representation. Response data is parsed at once with a `ctypes.BigEndianStructure` instead of unpacking individual ranges of bytes through `struct.unpack`. Only minimal data massaging is needed to convert the struct to useful information. It provides the following extra fields: - Inverter temperature; - Voltage and current for the DC input strings (up to 3); - Voltage, current, frequency and power for all AC outputs (also up to 3); - Total number of runtime hours (not sure if this is just the Omnik being on, or the time it delivers >0W of power back to the network). No "device" (network module) data is found in any of the unparsed bits, nor is `solar_rater_power` available. Sources: 1. https://github.com/jbouwh/omnikdatalogger/blob/dev/apps/omnikdatalogger/omnik/InverterMsg.py 2. http://www.mb200d.nl/wordpress/2015/11/omniksol-4k-tl-wifi-kit/#more-590 3. https://github.com/Woutrrr/Omnik-Data-Logger 4. https://github.com/t3kpunk/Omniksol-PV-Logger * tcp: Replace print() with warning() logging * tcp: Reimplement message parsing to support length, type, and multiple messages * tests: Separate TCP packet tests into individual functions * tests: Simplify "Failed to open a TCP connection" test This variant breaks immediately, instead of blocking for a minute. * tests: Use OmnikInverterConnectionError type directly * tcp,tests: read()/recv() exceptions are (re-?)raised in wait_closed() * Move LOGGER setup to new const.py * examples: Move TCP changes to a new example * examples: Replace deprecated get_event_loop() with asyncio.run() * Remove assert from example again * Remove now-unused import from dependency
- Loading branch information
Showing
14 changed files
with
877 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# pylint: disable=W0621 | ||
"""Asynchronous TCP Python client for the Omnik Inverter.""" | ||
|
||
import asyncio | ||
import logging | ||
|
||
from omnikinverter import Inverter, OmnikInverter | ||
|
||
logging.basicConfig(level=logging.DEBUG) | ||
|
||
|
||
async def main() -> None: | ||
"""Locally gather statistics over TCP.""" | ||
async with OmnikInverter( | ||
host="examples.com", | ||
source_type="tcp", | ||
serial_number=123456789, | ||
) as client: | ||
inverter: Inverter = await client.inverter() | ||
# TCP backend (currently?) doesn't provide WiFi module statistics, | ||
# so we're not querying .device() here. | ||
print(inverter) | ||
print() | ||
print("-- INVERTER --") | ||
print(f"Serial Number: {inverter.serial_number}") | ||
print(f"Model: {inverter.model}") | ||
print(f"Firmware Main: {inverter.firmware}") | ||
print(f"Firmware Slave: {inverter.firmware_slave}") | ||
print(f"Current Power: {inverter.solar_current_power}W") | ||
print(f"Energy Production Today: {inverter.solar_energy_today}kWh") | ||
print(f"Energy Production Total: {inverter.solar_energy_total}kWh") | ||
|
||
optional_fields = [ | ||
("solar_rated_power", "Rated Power", "W"), | ||
("temperature", "Temperature", "°C"), | ||
("solar_hours_total", "Solar Hours Total", "h"), | ||
("dc_input_voltage", "DC Input Voltage", "V"), | ||
("dc_input_current", "DC Input Current", "A"), | ||
("ac_output_voltage", "AC Output Voltage", "V"), | ||
("ac_output_current", "AC Output Current", "A"), | ||
("ac_output_frequency", "AC Output Frequency", "Hz"), | ||
("ac_output_power", "AC Output Power", "W"), | ||
("inverter_active", "Inverter Active", ""), | ||
] | ||
|
||
for field, name, unit in optional_fields: | ||
if (val := getattr(inverter, field)) is not None: | ||
if isinstance(val, list): | ||
values = ", ".join( | ||
f"{v2:.1f}{unit}" for v2 in val if v2 is not None | ||
) | ||
print(f"{name}: {values}") | ||
elif isinstance(val, float): | ||
print(f"{name}: {val:.1f}{unit}") | ||
else: | ||
print(f"{name}: {val}{unit}") | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
"""Constants for the Omnik Inverter.""" | ||
import logging | ||
|
||
LOGGER = logging.getLogger(__package__) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.