diff --git a/README.md b/README.md index b007997..4753df7 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ srne_v3.9 = SRNE inverters - Untested victron_gx_3.3 = Victron GX Devices - Untested solark_v1.1 = SolarArk 8/12K Inverters - Untested hdhk_16ch_ac_module = some chinese current monitoring device :P +srne_2021_v1.96 = SRNE inverters 2021+ (tested at ASF48100S200-H) eg4_v58 = eg4 inverters ( EG4-6000XP ) - confirmed working eg4_3000ehv_v1 = eg4 inverters ( EG4_3000EHV ) diff --git a/classes/protocol_settings.py b/classes/protocol_settings.py index 6549109..f1801e0 100644 --- a/classes/protocol_settings.py +++ b/classes/protocol_settings.py @@ -1,6 +1,7 @@ import csv from dataclasses import dataclass from enum import Enum +import glob from typing import Union from defs.common import strtoint import itertools @@ -300,7 +301,12 @@ def load__json(self, file : str = '', settings_dir : str = ''): if not file: file = self.protocol + '.json' - path = settings_dir + '/' + file + path = self.find_protocol_file(file, settings_dir) + + #if path does not exist; nothing to load. skip. + if not path: + print("ERROR: '"+file+"' not found") + return with open(path) as f: self.codes = json.loads(f.read()) @@ -618,6 +624,23 @@ def calculate_registry_ranges(self, map : list[registry_map_entry], max_register ranges.append((min(registers), max(registers)-min(registers)+1)) ## APPENDING A TUPLE! return ranges + def find_protocol_file(self, file : str, base_dir : str = '' ) -> str: + + path = base_dir + '/' + file + if os.path.exists(path): + return path + + suffix = file.split('_', 1)[0] + + path = base_dir + '/' + suffix +'/' + file + if os.path.exists(path): + return path + + #find file by name, recurisvely. last resort + search_pattern = os.path.join(base_dir, '**', file) + matches = glob.glob(search_pattern, recursive=True) + return matches[0] if matches else None + def load_registry_map(self, registry_type : Registry_Type, file : str = '', settings_dir : str = ''): if not settings_dir: @@ -629,10 +652,10 @@ def load_registry_map(self, registry_type : Registry_Type, file : str = '', sett else: file = self.protocol + '.'+registry_type.name.lower()+'_registry_map.csv' - path = settings_dir + '/' + file + path = self.find_protocol_file(file, settings_dir) #if path does not exist; nothing to load. skip. - if not os.path.exists(path): + if not path: return self.registry_map[registry_type] = self.load__registry(path, registry_type) diff --git a/classes/transports/modbus_rtu.py b/classes/transports/modbus_rtu.py index 84f2097..8e94e72 100644 --- a/classes/transports/modbus_rtu.py +++ b/classes/transports/modbus_rtu.py @@ -1,6 +1,14 @@ import logging from classes.protocol_settings import Registry_Type, protocol_settings -from pymodbus.client.sync import ModbusSerialClient + +import inspect + + +try: + from pymodbus.client.sync import ModbusSerialClient +except ImportError: + from pymodbus.client import ModbusSerialClient + from .modbus_base import modbus_base from configparser import SectionProxy from defs.common import find_usb_serial_port, get_usb_serial_port_info, strtoint @@ -11,6 +19,8 @@ class modbus_rtu(modbus_base): baudrate : int = 9600 client : ModbusSerialClient + pymodbus_slave_arg = 'unit' + def __init__(self, settings : SectionProxy, protocolSettings : protocol_settings = None): #logger = logging.getLogger(__name__) #logging.basicConfig(level=logging.DEBUG) @@ -33,16 +43,34 @@ def __init__(self, settings : SectionProxy, protocolSettings : protocol_settings address : int = settings.getint("address", 0) self.addresses = [address] - self.client = ModbusSerialClient(method='rtu', port=self.port, - baudrate=int(self.baudrate), - stopbits=1, parity='N', bytesize=8, timeout=2 - ) + # pymodbus compatability; unit was renamed to address + if 'slave' in inspect.signature(ModbusSerialClient.read_holding_registers).parameters: + self.pymodbus_slave_arg = 'slave' + + + # Get the signature of the __init__ method + init_signature = inspect.signature(ModbusSerialClient.__init__) + + if 'method' in init_signature.parameters: + self.client = ModbusSerialClient(method='rtu', port=self.port, + baudrate=int(self.baudrate), + stopbits=1, parity='N', bytesize=8, timeout=2 + ) + else: + self.client = ModbusSerialClient(port=self.port, + baudrate=int(self.baudrate), + stopbits=1, parity='N', bytesize=8, timeout=2 + ) def read_registers(self, start, count=1, registry_type : Registry_Type = Registry_Type.INPUT, **kwargs): if 'unit' not in kwargs: kwargs = {'unit': int(self.addresses[0]), **kwargs} + #compatability + if self.pymodbus_slave_arg != 'unit': + kwargs['slave'] = kwargs.pop('unit') + if registry_type == Registry_Type.INPUT: return self.client.read_input_registers(start, count, **kwargs) elif registry_type == Registry_Type.HOLDING: @@ -55,6 +83,10 @@ def write_register(self, register : int, value : int, **kwargs): if 'unit' not in kwargs: kwargs = {'unit': self.addresses[0], **kwargs} + #compatability + if self.pymodbus_slave_arg != 'unit': + kwargs['slave'] = kwargs.pop('unit') + self.client.write_register(register, value, **kwargs) #function code 0x06 writes to holding register def connect(self): diff --git a/documentation/.scripts/README.md b/documentation/.scripts/README.md new file mode 100644 index 0000000..b5e9fc2 --- /dev/null +++ b/documentation/.scripts/README.md @@ -0,0 +1 @@ +folder for scripts releated to documentation \ No newline at end of file diff --git a/documentation/.scripts/generate_indexes.py b/documentation/.scripts/generate_indexes.py new file mode 100644 index 0000000..159e742 --- /dev/null +++ b/documentation/.scripts/generate_indexes.py @@ -0,0 +1,94 @@ +import os +import urllib + + +def extract_first_header(file_path): + """Extract the first header from a markdown file.""" + with open(file_path, "r", encoding="utf-8") as file: + for line in file: + line = line.strip() + if line.startswith("#"): + return line.replace('#', '') + return None + + +def generate_readme(directory : str, folder_order : str = [], output_file : str ="README.md"): + with open(directory+'/'+output_file, "w", encoding="utf-8") as readme: + readme.write("# README Index\n\n") + readme.write("This README file contains an index of all files in the documentation directory.\n\n") + readme.write("## File List\n\n") + + note_file : str = directory+'/note.md' + if os.path.exists(note_file): + readme.write("\n## Additional Notes\n\n") + with open(note_file, "r", encoding="utf-8") as note: + readme.write(note.read()) + + + previous_folder = "" + + folder_lines : dict[str, list[str]] = {} + + for root, dirs, files in os.walk(directory): + relative_folder = os.path.relpath(root, directory).replace("\\", "/") #use linux path structure + + #exclude . folders + if relative_folder[0] == '.': + continue + + if relative_folder != previous_folder: + # Create a bold header for each new folder + folder_lines[relative_folder] = [] + folder_lines[relative_folder].append(f"**{relative_folder}**\n\n") + + previous_folder = relative_folder + + #generate index in folder + generate_readme(directory+"/"+relative_folder) + + for file in files: + file_path = os.path.relpath(os.path.join(root, file), directory).replace("\\", "/") #use linux path structure + file_path = urllib.parse.quote(file_path) + + if file == "README.md": #skip + continue + + if file.endswith(".md"): + first_header = extract_first_header(os.path.join(root, file)) + if first_header: + folder_lines[relative_folder].append(f"- [{file}]({file_path}) - {first_header}") + else: + folder_lines[relative_folder].append(f"- [{file}]({file_path})") + else: + folder_lines[relative_folder].append(f"- [{file}]({file_path})") + + # Add an extra line break between different folders + if files: + folder_lines[relative_folder].append("") + + #write output + for folder in folder_lines: + if folder in folder_order: #skip ordered folders for the end + continue + + for line in folder_lines[folder]: + readme.write(line + "\n") + + #write ordered output + for folder in folder_order: + if folder not in folder_lines: #not found + continue + + for line in folder_lines[folder]: + readme.write(line + "\n") + + + +if __name__ == "__main__": + # Change the working directory to the location of the script + script_dir = os.path.dirname(os.path.abspath(__file__)) + os.chdir(script_dir) + + # Specify the directory you want to index + directory_to_index = "../" + generate_readme(directory_to_index, ["3rdparty", "3rdparty/protocols"]) \ No newline at end of file diff --git a/documentation/3rdparty/README.md b/documentation/3rdparty/README.md new file mode 100644 index 0000000..f38d852 --- /dev/null +++ b/documentation/3rdparty/README.md @@ -0,0 +1,33 @@ +# README Index + +This README file contains an index of all files in the documentation directory. + +## File List + +**protocols** + + +- [CAN-Bus-protocol-PYLON-low-voltage-V1.2-20180408.pdf](protocols/CAN-Bus-protocol-PYLON-low-voltage-V1.2-20180408.pdf) +- [converter.txt](protocols/converter.txt) +- [EG4-3000-EHV - MODBUS Communication Protocol.pdf](protocols/EG4-3000-EHV%20-%20MODBUS%20Communication%20Protocol.pdf) +- [EG4-6000XP-MODBUS-Communication-Protocol.pdf](protocols/EG4-6000XP-MODBUS-Communication-Protocol.pdf) +- [Growatt Modbus Protocol v1.24.pdf](protocols/Growatt%20Modbus%20Protocol%20v1.24.pdf) +- [Growatt PV Inverter Modbus RS485 RTU Protocol V3.04.pdf](protocols/Growatt%20PV%20Inverter%20Modbus%20RS485%20RTU%20Protocol%20V3.04.pdf) +- [Growatt PV Inverter Modbus RS485 RTU Protocol V3.15.pdf](protocols/Growatt%20PV%20Inverter%20Modbus%20RS485%20RTU%20Protocol%20V3.15.pdf) +- [hdhk_16ch_ac_module_modbus_rtu.jpeg](protocols/hdhk_16ch_ac_module_modbus_rtu.jpeg) +- [hdhk_16ch_ac_module_modbus_rtu_chinese.pdf](protocols/hdhk_16ch_ac_module_modbus_rtu_chinese.pdf) +- [hdhk_16ch_ac_module_modbus_rtu_translated_english.pdf](protocols/hdhk_16ch_ac_module_modbus_rtu_translated_english.pdf) +- [MAX Series Modbus RTU Protocol.pdf](protocols/MAX%20Series%20Modbus%20RTU%20Protocol.pdf) +- [note.md](protocols/note.md) +- [OffGrid-Modbus-RS485RS232-RTU-Protocol-V0.14-20210420.pdf](protocols/OffGrid-Modbus-RS485RS232-RTU-Protocol-V0.14-20210420.pdf) +- [PACE-BMS-Modbus-Protocol-for-RS485-V1.3-20170627.pdf](protocols/PACE-BMS-Modbus-Protocol-for-RS485-V1.3-20170627.pdf) +- [PACE-BMS-RS485-communication-protocol-20180615.pdf](protocols/PACE-BMS-RS485-communication-protocol-20180615.pdf) +- [PACE-CAN-communication-protocal(PACE-CAN-TY)-20161216-.pdf](protocols/PACE-CAN-communication-protocal%EF%BC%88PACE-CAN-TY%EF%BC%89-20161216-.pdf) +- [PYLON LFP Battery communication protocol - RS485 V2.8 20161216.pdf](protocols/PYLON%20LFP%20Battery%20communication%20protocol%20-%20RS485%20V2.8%2020161216.pdf) +- [RS485-protocol-pylon-low-voltage-V3.3-20180821.pdf](protocols/RS485-protocol-pylon-low-voltage-V3.3-20180821.pdf) +- [Sigineer-Solar-Inverter-RS485-Port-Modbus-RTU-Protocol-v0.11-20200302.pdf](protocols/Sigineer-Solar-Inverter-RS485-Port-Modbus-RTU-Protocol-v0.11-20200302.pdf) +- [Sol-Ark ModBus V1.1.pdf](protocols/Sol-Ark%20ModBus%20V1.1.pdf) +- [SRNE_MODBUS_v3.9.pdf](protocols/SRNE_MODBUS_v3.9.pdf) +- [Victron VE-Bus-products-MK2-Protocol-3-14.pdf](protocols/Victron%20VE-Bus-products-MK2-Protocol-3-14.pdf) +- [Victron-CCGX-Modbus-TCP-register-list-3.30.xlsx](protocols/Victron-CCGX-Modbus-TCP-register-list-3.30.xlsx) + diff --git a/docs/CAN-Bus-protocol-PYLON-low-voltage-V1.2-20180408.pdf b/documentation/3rdparty/protocols/CAN-Bus-protocol-PYLON-low-voltage-V1.2-20180408.pdf similarity index 100% rename from docs/CAN-Bus-protocol-PYLON-low-voltage-V1.2-20180408.pdf rename to documentation/3rdparty/protocols/CAN-Bus-protocol-PYLON-low-voltage-V1.2-20180408.pdf diff --git a/docs/EG4-3000-EHV - MODBUS Communication Protocol.pdf b/documentation/3rdparty/protocols/EG4-3000-EHV - MODBUS Communication Protocol.pdf similarity index 100% rename from docs/EG4-3000-EHV - MODBUS Communication Protocol.pdf rename to documentation/3rdparty/protocols/EG4-3000-EHV - MODBUS Communication Protocol.pdf diff --git a/docs/EG4-6000XP-MODBUS-Communication-Protocol.pdf b/documentation/3rdparty/protocols/EG4-6000XP-MODBUS-Communication-Protocol.pdf similarity index 100% rename from docs/EG4-6000XP-MODBUS-Communication-Protocol.pdf rename to documentation/3rdparty/protocols/EG4-6000XP-MODBUS-Communication-Protocol.pdf diff --git a/docs/Growatt Modbus Protocol v1.24.pdf b/documentation/3rdparty/protocols/Growatt Modbus Protocol v1.24.pdf similarity index 100% rename from docs/Growatt Modbus Protocol v1.24.pdf rename to documentation/3rdparty/protocols/Growatt Modbus Protocol v1.24.pdf diff --git a/docs/Growatt PV Inverter Modbus RS485 RTU Protocol V3.04.pdf b/documentation/3rdparty/protocols/Growatt PV Inverter Modbus RS485 RTU Protocol V3.04.pdf similarity index 100% rename from docs/Growatt PV Inverter Modbus RS485 RTU Protocol V3.04.pdf rename to documentation/3rdparty/protocols/Growatt PV Inverter Modbus RS485 RTU Protocol V3.04.pdf diff --git a/docs/Growatt PV Inverter Modbus RS485 RTU Protocol V3.15.pdf b/documentation/3rdparty/protocols/Growatt PV Inverter Modbus RS485 RTU Protocol V3.15.pdf similarity index 100% rename from docs/Growatt PV Inverter Modbus RS485 RTU Protocol V3.15.pdf rename to documentation/3rdparty/protocols/Growatt PV Inverter Modbus RS485 RTU Protocol V3.15.pdf diff --git a/docs/MAX Series Modbus RTU Protocol.pdf b/documentation/3rdparty/protocols/MAX Series Modbus RTU Protocol.pdf similarity index 100% rename from docs/MAX Series Modbus RTU Protocol.pdf rename to documentation/3rdparty/protocols/MAX Series Modbus RTU Protocol.pdf diff --git a/docs/OffGrid-Modbus-RS485RS232-RTU-Protocol-V0.14-20210420.pdf b/documentation/3rdparty/protocols/OffGrid-Modbus-RS485RS232-RTU-Protocol-V0.14-20210420.pdf similarity index 100% rename from docs/OffGrid-Modbus-RS485RS232-RTU-Protocol-V0.14-20210420.pdf rename to documentation/3rdparty/protocols/OffGrid-Modbus-RS485RS232-RTU-Protocol-V0.14-20210420.pdf diff --git a/docs/PACE-BMS-Modbus-Protocol-for-RS485-V1.3-20170627.pdf b/documentation/3rdparty/protocols/PACE-BMS-Modbus-Protocol-for-RS485-V1.3-20170627.pdf similarity index 100% rename from docs/PACE-BMS-Modbus-Protocol-for-RS485-V1.3-20170627.pdf rename to documentation/3rdparty/protocols/PACE-BMS-Modbus-Protocol-for-RS485-V1.3-20170627.pdf diff --git a/docs/PACE-BMS-RS485-communication-protocol-20180615.pdf b/documentation/3rdparty/protocols/PACE-BMS-RS485-communication-protocol-20180615.pdf similarity index 100% rename from docs/PACE-BMS-RS485-communication-protocol-20180615.pdf rename to documentation/3rdparty/protocols/PACE-BMS-RS485-communication-protocol-20180615.pdf diff --git "a/docs/PACE-CAN-communication-protocal\357\274\210PACE-CAN-TY\357\274\211-20161216-.pdf" "b/documentation/3rdparty/protocols/PACE-CAN-communication-protocal\357\274\210PACE-CAN-TY\357\274\211-20161216-.pdf" similarity index 100% rename from "docs/PACE-CAN-communication-protocal\357\274\210PACE-CAN-TY\357\274\211-20161216-.pdf" rename to "documentation/3rdparty/protocols/PACE-CAN-communication-protocal\357\274\210PACE-CAN-TY\357\274\211-20161216-.pdf" diff --git a/docs/PYLON LFP Battery communication protocol - RS485 V2.8 20161216.pdf b/documentation/3rdparty/protocols/PYLON LFP Battery communication protocol - RS485 V2.8 20161216.pdf similarity index 100% rename from docs/PYLON LFP Battery communication protocol - RS485 V2.8 20161216.pdf rename to documentation/3rdparty/protocols/PYLON LFP Battery communication protocol - RS485 V2.8 20161216.pdf diff --git a/documentation/3rdparty/protocols/README.md b/documentation/3rdparty/protocols/README.md new file mode 100644 index 0000000..733053a --- /dev/null +++ b/documentation/3rdparty/protocols/README.md @@ -0,0 +1,23 @@ +# README Index + +This README file contains an index of all files in the documentation directory. + +## File List + + +## Additional Notes + + +Protocol Documentation +--- +[V3.14](Growatt%20PV%20Inverter%20Modbus%20RS485%20RTU%20Protocol%20V3.14.pdf) + +Source: https://github.com/jrbenito/canadianSolar-pvoutput/blob/732efe68b71f67129f5b31442f82f2be0d79e605/docs/ + +Note: The original file name doesn't match the version specified in the document. + +---- +[V3.04](Growatt%20PV%20Inverter%20Modbus%20RS485%20RTU%20Protocol%20V3.04.pdf) + +Source: http://www.growatt.pl/dokumenty/Inne/Growatt%20PV%20Inverter%20Modbus%20RS485%20RTU%20Protocol%20V3.04.pdf + diff --git a/docs/RS485-protocol-pylon-low-voltage-V3.3-20180821.pdf b/documentation/3rdparty/protocols/RS485-protocol-pylon-low-voltage-V3.3-20180821.pdf similarity index 100% rename from docs/RS485-protocol-pylon-low-voltage-V3.3-20180821.pdf rename to documentation/3rdparty/protocols/RS485-protocol-pylon-low-voltage-V3.3-20180821.pdf diff --git a/docs/SRNE_MODBUS_v3.9.pdf b/documentation/3rdparty/protocols/SRNE_MODBUS_v3.9.pdf similarity index 100% rename from docs/SRNE_MODBUS_v3.9.pdf rename to documentation/3rdparty/protocols/SRNE_MODBUS_v3.9.pdf diff --git a/docs/Sigineer-Solar-Inverter-RS485-Port-Modbus-RTU-Protocol-v0.11-20200302.pdf b/documentation/3rdparty/protocols/Sigineer-Solar-Inverter-RS485-Port-Modbus-RTU-Protocol-v0.11-20200302.pdf similarity index 100% rename from docs/Sigineer-Solar-Inverter-RS485-Port-Modbus-RTU-Protocol-v0.11-20200302.pdf rename to documentation/3rdparty/protocols/Sigineer-Solar-Inverter-RS485-Port-Modbus-RTU-Protocol-v0.11-20200302.pdf diff --git a/docs/Sol-Ark ModBus V1.1.pdf b/documentation/3rdparty/protocols/Sol-Ark ModBus V1.1.pdf similarity index 100% rename from docs/Sol-Ark ModBus V1.1.pdf rename to documentation/3rdparty/protocols/Sol-Ark ModBus V1.1.pdf diff --git a/docs/Victron VE-Bus-products-MK2-Protocol-3-14.pdf b/documentation/3rdparty/protocols/Victron VE-Bus-products-MK2-Protocol-3-14.pdf similarity index 100% rename from docs/Victron VE-Bus-products-MK2-Protocol-3-14.pdf rename to documentation/3rdparty/protocols/Victron VE-Bus-products-MK2-Protocol-3-14.pdf diff --git a/docs/Victron-CCGX-Modbus-TCP-register-list-3.30.xlsx b/documentation/3rdparty/protocols/Victron-CCGX-Modbus-TCP-register-list-3.30.xlsx similarity index 100% rename from docs/Victron-CCGX-Modbus-TCP-register-list-3.30.xlsx rename to documentation/3rdparty/protocols/Victron-CCGX-Modbus-TCP-register-list-3.30.xlsx diff --git a/docs/converter.txt b/documentation/3rdparty/protocols/converter.txt similarity index 100% rename from docs/converter.txt rename to documentation/3rdparty/protocols/converter.txt diff --git a/docs/hdhk_16ch_ac_module_modbus_rtu.jpeg b/documentation/3rdparty/protocols/hdhk_16ch_ac_module_modbus_rtu.jpeg similarity index 100% rename from docs/hdhk_16ch_ac_module_modbus_rtu.jpeg rename to documentation/3rdparty/protocols/hdhk_16ch_ac_module_modbus_rtu.jpeg diff --git a/docs/hdhk_16ch_ac_module_modbus_rtu_chinese.pdf b/documentation/3rdparty/protocols/hdhk_16ch_ac_module_modbus_rtu_chinese.pdf similarity index 100% rename from docs/hdhk_16ch_ac_module_modbus_rtu_chinese.pdf rename to documentation/3rdparty/protocols/hdhk_16ch_ac_module_modbus_rtu_chinese.pdf diff --git a/docs/hdhk_16ch_ac_module_modbus_rtu_translated_english.pdf b/documentation/3rdparty/protocols/hdhk_16ch_ac_module_modbus_rtu_translated_english.pdf similarity index 100% rename from docs/hdhk_16ch_ac_module_modbus_rtu_translated_english.pdf rename to documentation/3rdparty/protocols/hdhk_16ch_ac_module_modbus_rtu_translated_english.pdf diff --git a/docs/README.md b/documentation/3rdparty/protocols/note.md similarity index 100% rename from docs/README.md rename to documentation/3rdparty/protocols/note.md diff --git a/documentation/README.md b/documentation/README.md new file mode 100644 index 0000000..9b6944e --- /dev/null +++ b/documentation/README.md @@ -0,0 +1,66 @@ +# README Index + +This README file contains an index of all files in the documentation directory. + +## File List + +**dashboards** + + +- [grafana.md](dashboards/grafana.md) +- [homeassistant.md](dashboards/homeassistant.md) +- [nodered.md](dashboards/nodered.md) - Setting up PythonProtocolGateway on a RasPi with NodeRed Dashboard 2.0 + +**devices** + + +- [EG4.md](devices/EG4.md) - EG4 to MQTT +- [Growatt.md](devices/Growatt.md) - Growatt To MQTT +- [Sigineer.md](devices/Sigineer.md) - Sigineer to MQTT +- [SOK.md](devices/SOK.md) - SOK to MQTT +- [SolArk.md](devices/SolArk.md) - SolArk to MQTT + +**usage** + + +- [creating_and_editing_protocols.md](usage/creating_and_editing_protocols.md) - Creating and Editing Protocols ‐ JSON ‐ CSV +- [protocols.md](usage/protocols.md) - Custom / Editing Protocols +- [transports.md](usage/transports.md) - Transports + +**usage/configuration_examples** + + +- [modbus_rtu_to_modbus_tcp.md](usage/configuration_examples/modbus_rtu_to_modbus_tcp.md) - ModBus RTU to ModBus TCP +- [modbus_rtu_to_mqtt.md](usage/configuration_examples/modbus_rtu_to_mqtt.md) - ModBus RTU to MQTT + +**3rdparty** + + + +**3rdparty/protocols** + + +- [CAN-Bus-protocol-PYLON-low-voltage-V1.2-20180408.pdf](3rdparty/protocols/CAN-Bus-protocol-PYLON-low-voltage-V1.2-20180408.pdf) +- [converter.txt](3rdparty/protocols/converter.txt) +- [EG4-3000-EHV - MODBUS Communication Protocol.pdf](3rdparty/protocols/EG4-3000-EHV%20-%20MODBUS%20Communication%20Protocol.pdf) +- [EG4-6000XP-MODBUS-Communication-Protocol.pdf](3rdparty/protocols/EG4-6000XP-MODBUS-Communication-Protocol.pdf) +- [Growatt Modbus Protocol v1.24.pdf](3rdparty/protocols/Growatt%20Modbus%20Protocol%20v1.24.pdf) +- [Growatt PV Inverter Modbus RS485 RTU Protocol V3.04.pdf](3rdparty/protocols/Growatt%20PV%20Inverter%20Modbus%20RS485%20RTU%20Protocol%20V3.04.pdf) +- [Growatt PV Inverter Modbus RS485 RTU Protocol V3.15.pdf](3rdparty/protocols/Growatt%20PV%20Inverter%20Modbus%20RS485%20RTU%20Protocol%20V3.15.pdf) +- [hdhk_16ch_ac_module_modbus_rtu.jpeg](3rdparty/protocols/hdhk_16ch_ac_module_modbus_rtu.jpeg) +- [hdhk_16ch_ac_module_modbus_rtu_chinese.pdf](3rdparty/protocols/hdhk_16ch_ac_module_modbus_rtu_chinese.pdf) +- [hdhk_16ch_ac_module_modbus_rtu_translated_english.pdf](3rdparty/protocols/hdhk_16ch_ac_module_modbus_rtu_translated_english.pdf) +- [MAX Series Modbus RTU Protocol.pdf](3rdparty/protocols/MAX%20Series%20Modbus%20RTU%20Protocol.pdf) +- [note.md](3rdparty/protocols/note.md) +- [OffGrid-Modbus-RS485RS232-RTU-Protocol-V0.14-20210420.pdf](3rdparty/protocols/OffGrid-Modbus-RS485RS232-RTU-Protocol-V0.14-20210420.pdf) +- [PACE-BMS-Modbus-Protocol-for-RS485-V1.3-20170627.pdf](3rdparty/protocols/PACE-BMS-Modbus-Protocol-for-RS485-V1.3-20170627.pdf) +- [PACE-BMS-RS485-communication-protocol-20180615.pdf](3rdparty/protocols/PACE-BMS-RS485-communication-protocol-20180615.pdf) +- [PACE-CAN-communication-protocal(PACE-CAN-TY)-20161216-.pdf](3rdparty/protocols/PACE-CAN-communication-protocal%EF%BC%88PACE-CAN-TY%EF%BC%89-20161216-.pdf) +- [PYLON LFP Battery communication protocol - RS485 V2.8 20161216.pdf](3rdparty/protocols/PYLON%20LFP%20Battery%20communication%20protocol%20-%20RS485%20V2.8%2020161216.pdf) +- [RS485-protocol-pylon-low-voltage-V3.3-20180821.pdf](3rdparty/protocols/RS485-protocol-pylon-low-voltage-V3.3-20180821.pdf) +- [Sigineer-Solar-Inverter-RS485-Port-Modbus-RTU-Protocol-v0.11-20200302.pdf](3rdparty/protocols/Sigineer-Solar-Inverter-RS485-Port-Modbus-RTU-Protocol-v0.11-20200302.pdf) +- [Sol-Ark ModBus V1.1.pdf](3rdparty/protocols/Sol-Ark%20ModBus%20V1.1.pdf) +- [SRNE_MODBUS_v3.9.pdf](3rdparty/protocols/SRNE_MODBUS_v3.9.pdf) +- [Victron VE-Bus-products-MK2-Protocol-3-14.pdf](3rdparty/protocols/Victron%20VE-Bus-products-MK2-Protocol-3-14.pdf) +- [Victron-CCGX-Modbus-TCP-register-list-3.30.xlsx](3rdparty/protocols/Victron-CCGX-Modbus-TCP-register-list-3.30.xlsx) + diff --git a/documentation/dashboards/README.md b/documentation/dashboards/README.md new file mode 100644 index 0000000..5a44987 --- /dev/null +++ b/documentation/dashboards/README.md @@ -0,0 +1,6 @@ +# README Index + +This README file contains an index of all files in the documentation directory. + +## File List + diff --git a/documentation/dashboards/grafana.md b/documentation/dashboards/grafana.md new file mode 100644 index 0000000..e69de29 diff --git a/documentation/dashboards/homeassistant.md b/documentation/dashboards/homeassistant.md new file mode 100644 index 0000000..e69de29 diff --git a/documentation/dashboards/nodered.md b/documentation/dashboards/nodered.md new file mode 100644 index 0000000..32ae6c4 --- /dev/null +++ b/documentation/dashboards/nodered.md @@ -0,0 +1,82 @@ +# Setting up PythonProtocolGateway on a RasPi with NodeRed Dashboard 2.0 +This is a simple how-to on setting up a Rasperry Pi node, attempting to be as copy/pastable as possible. As we stand on the shoulders of giants, I will refer to external how-tos for some of the steps. They did it better than I could anyway. + +## Requirements +- a multicore raspberry pi. Though you can run it on a classic raspi 2/pi zero w, a pi zero 2 is the minimum for stablility. +- a recent version of raspberry pi os. + +## Install Raspberry Pi OS +[Just like it says on the tin](https://www.raspberrypi.com/documentation/computers/getting-started.html#install-an-operating-system). Throughout this, I will use some conventions assuming you used the hostname Solar, the username is Solar, and the password is SolarPowered. The rest of this doc assumes you are SSHed in using PuTTY (for windows users) or a terminal of your choice (for the rest of us heathens). The hostname will be `solar.local`, username `solar`, and password `SolarPowered` + +## install dependencies +`sudo apt update && sudo apt upgrade && sudo apt install tmux git mosquitto nginx` + +## Configure Mosquitto MQTT server +Using your favorite text editor, add these lines to `/etc/mosquitto/mosquitto.conf` + +``` +listener 1883 + +allow_anonymous false +password_file /etc/mosquitto/passwd + +``` +Create a new `mosquitto` password file while adding an mqtt user `sudo mosquitto_passwd -c /etc/mosquitto/passwd solar` - to add another user later, drop the `-c`. Start the service with `sudo systemctl start mosquitto`, set the service to start on boot with `sudo systemctl enable mosquitto`. + +## Download/configure PythonProtocolGateway +### Download +`mkdir ~/src/ && cd ~/src && git clone https://github.com/HotNoob/PythonProtocolGateway.git` +### Configure +[Follow the wiki](https://github.com/HotNoob/PythonProtocolGateway/wiki) + +## Install/configure NodeRed service +### Install +[Install how-to](https://nodered.org/docs/getting-started/raspberrypi) - As of the 6 August 2024, you can just paste in `bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)` + +## install NodeRed Nodes +1. [Log in.](http://solar.local:1880/) +2. Click the hambuger menu. +3. Go to `Manage palette`. +4. Under the `Install` tab, search for `@flowfuse/node-red-dashboard`. Click `Install`. +5. Optional: Under the `Install` tab, search for `node-red-contrib-influxdb`, click `Install`. + +## Optional: Install/Enable InfluxDB +If you want to do some historical logging, InfluxDB is a better solution than the node-red internal db. +1. Install influx `sudo apt install influxdb influxdb-client && sudo systemctl enable influxdb && sudo systemctl start ` +2. Create the `solar` db in influx `echo "CREATE DATABASE solar" | influx` +3. Drag an `InfluxDB Out Node` out into the workspace, connect a wire from the `mqtt in` node to the `InfluxDB Out` node. Double click the node, click the `+`, give it a name and the name of the database from above (solar). Click add. It will return you to the node setup, where you will give it a name and a measurement (`solar` in my setup), click `save` or `done`. + +### TODO: create some nodes that consume the DB info. + +## Create your flow or import the example flow (for EG4 users) +### Import example +Go to the hamburger, down to `Import`, click `select a file to import`, find `nodered-example-flow.json` in the repo. You should be left with something like this. +![example flow](https://github.com/user-attachments/assets/c2c284f8-e40f-4e05-bcb7-e054e32dad4c) + +### Create your own flow +1. Drag a `debug` node out - we will be using this throughout to see how the data flows. You can see the debug output by dragging a wire from an output to the debug's input and turning it on. +2. Drag an `mqtt in` node out to the workspace. click the pencil to create a new mqtt-broker-node. Give it a name, enter the hostname in the server field (`solar.local` in our example), click security, add the username and password, click `add` orf `update`. Under `Topic`, enter `home/inverter`. Click done. +3. Drag a json node out onto the workspace, connect the input (left) side of the `json` node to the output of the `mqtt in` node. +4. For each of the things you want on the dashboard, add in a `function` node. This is to filter out the thing you want displayed, in this example, battery percentage. Drag a wire from the `json` node to the function you just created. +``` +msg.payload = parseInt(msg.payload.battery_percentage); +return msg; +``` +click done. +4. From here on out, you will be setting up the wigets you want to see. Checkout the [flowfuse dashboard wiki](https://dashboard.flowfuse.com/getting-started.html) for more info. For each of the functions you just created, create a `chart`, `gauge`, or `text` node to display the things the way you want them displayed. You will need to create a group and page node on the first, the ui will help you throuhg that. + +## Edit the nginx config file to point at the dashboard +Use sudo and your favorite editor to edit `/etc/nginx/sites-enabled/default`. jump down to the `server_name _` section and replace everything between the `{` and `}` so it's like below. + +``` server_name _; + location / { + include proxy_params; + rewrite ^/(.*) /dashboard/$1 break; + proxy_pass http://127.0.0.1:1880; + } +``` + +You should now be able to browse to [Solar.local/Home](http://solar.local/Home/) + +--- +source: https://github.com/yNosGR/PythonProtocolGateway/blob/NodeRed_howto/NodeRed.MD \ No newline at end of file diff --git a/documentation/devices/EG4.md b/documentation/devices/EG4.md new file mode 100644 index 0000000..de39be6 --- /dev/null +++ b/documentation/devices/EG4.md @@ -0,0 +1,34 @@ +# EG4 to MQTT + +## Hardware +1. USB to RS485 Adapter (RJ45) from EG4 **or** USB to RS485 Adapter & RJ45 Ethernet cable ( or 3 wires ) +2. Connect RJ45 ethernet cable to an avaiable RS485/Modbus port: + +![image](https://github.com/HotNoob/PythonProtocolGateway/assets/2180145/c387d8af-5864-4795-9958-3161d23501f1) + +
+ Example Image + +![327825986-94315fea-abad-4c9c-942d-aa5ad4b47802](https://github.com/HotNoob/PythonProtocolGateway/assets/2180145/f8bee2f2-4f7c-4fd8-a437-2f03af1ba2b0) + +
+ +3. Connect appropriate wires to USB RS485 Adapter + +## Configuration +Follow configuration example for ModBus RTU to MQTT +https://github.com/HotNoob/PythonProtocolGateway/wiki/Configuration-Examples#modbus-rtu-to-mqtt + +#### EG4 6000XP, EG4 18Kpv +``` +protocol_version = eg4_v58 +baud = 19200 +``` + +#### EG4 3000EHV inverters +``` +protocol_version = eg4_3000ehv_v1 +``` + +protocols may not be limited to the models listed. + diff --git a/documentation/devices/Growatt.md b/documentation/devices/Growatt.md new file mode 100644 index 0000000..1b2d4f0 --- /dev/null +++ b/documentation/devices/Growatt.md @@ -0,0 +1,122 @@ +# Growatt To MQTT + +## Hardware +1. USB-B or USB-A cable +2. for models with a DB9 port a DB9 RS232 adapter is required. "Before use RS232 communication, you should make sure the follow PIN1 and PIN2 are OFF" +3. Connect cable to wifi dongle port; if a alternative usb port exists, try connecting to that one first. + +## Configuration +Follow configuration example for ModBus RTU to MQTT +https://github.com/HotNoob/PythonProtocolGateway/wiki/Configuration-Examples#modbus-rtu-to-mqtt + +``` +protocol_version = v0.14 +``` + +## HomeAssistant Cards +Here are some example cards. If you want to use them, you will have to change the variable names and others to reflect your configs. + +## PV1 & PV2 Card +![pv watts](https://github.com/HotNoob/PythonProtocolGateway/assets/2180145/372980f9-f2d6-48e5-9acd-ee519badb61f) +
+ code + +``` +type: horizontal-stack +cards: + - type: gauge + needle: false + name: PV1 Voltage + entity: sensor.growatt_inverter_pv1_voltage + severity: + green: 150 + yellow: 50 + red: 0 + - type: gauge + entity: sensor.growatt_inverter_pv2_voltage + name: PV2 Voltage + severity: + green: 125 + yellow: 50 + red: 0 + - type: gauge + needle: false + entity: sensor.growatt_inverter_pv1_watts + name: PV1 Watts + severity: + green: 750 + yellow: 250 + red: 0 + - type: gauge + entity: sensor.growatt_inverter_pv2_watts + name: PV2 Watts + severity: + green: 750 + yellow: 250 + red: 0 +``` +
+ +## Output Card +![output](https://github.com/HotNoob/PythonProtocolGateway/assets/2180145/9a129dad-73bc-4401-9746-d7a0dd22cf0a) +
+ code + +``` +type: horizontal-stack +cards: + - type: gauge + needle: true + entity: sensor.growatt_inverter_output_voltage + name: Output Voltage + max: 270 + min: 210 + segments: + - from: 0 + color: '#db4437' + - from: 220 + color: '#ffa600' + - from: 235 + color: '#43a047' + - from: 245 + color: '#ffa600' + - from: 250 + color: '#db4437' + - type: gauge + entity: sensor.growatt_inverter_output_hz + name: Output Hertz + unit: hz + needle: true + max: 62 + min: 58 + segments: + - from: 0 + color: '#db4437' + - from: 59 + color: '#ffa600' + - from: 59.5 + color: '#43a047' + - from: 60.5 + color: '#ffa600' + - from: 61 + color: '#db4437' + - type: gauge + needle: false + entity: sensor.growatt_inverter_output_wattage + name: Output Watts + severity: + green: 0 + yellow: 1200 + red: 8000 + max: 12000 + - type: gauge + entity: sensor.growatt_inverter_output_current + name: Output Current + severity: + green: 0 + yellow: 10 + red: 40 + max: 50 + +``` +
\ No newline at end of file diff --git a/documentation/devices/README.md b/documentation/devices/README.md new file mode 100644 index 0000000..5a44987 --- /dev/null +++ b/documentation/devices/README.md @@ -0,0 +1,6 @@ +# README Index + +This README file contains an index of all files in the documentation directory. + +## File List + diff --git a/documentation/devices/SOK.md b/documentation/devices/SOK.md new file mode 100644 index 0000000..5414453 --- /dev/null +++ b/documentation/devices/SOK.md @@ -0,0 +1,18 @@ +# SOK to MQTT + +``` +transport=modbus_rtu +protocol_version=pace_bms_v1.3 +``` +The Battery's RS485 Protocol must be set to: `PACE_MODBUS` + +Plugs into the RS485A port + +This protocol is only able to read the battery that is directly connected to it; a modbus hub can be used to help fix this limitation. + +SOK, jakiper 48v100AH battery and other PACE BMS batteries. + +![pace-bms](https://github.com/HotNoob/InverterModBusToMQTT/assets/2180145/1ea28956-5d74-4bdb-9732-341d492d15c3) + +### rs485a pinout ### +Pin 1,2,3 or Pin 8,7,6 \ No newline at end of file diff --git a/documentation/devices/Sigineer.md b/documentation/devices/Sigineer.md new file mode 100644 index 0000000..62cecd8 --- /dev/null +++ b/documentation/devices/Sigineer.md @@ -0,0 +1,47 @@ +# Sigineer to MQTT + +## Hardware +1. USB-B or USB-A cable +2. Connect usb cable. + +## Configuration +Follow configuration example for ModBus RTU to MQTT +https://github.com/HotNoob/PythonProtocolGateway/wiki/Configuration-Examples#modbus-rtu-to-mqtt + +``` +protocol_version = sigineer_v0.11 +``` + + +## Home Assistant Cards + +### Voltage Card +![sigineer output](https://github.com/HotNoob/PythonProtocolGateway/assets/2180145/55900744-6aaf-4b44-bf3e-46976fdffce2) + +
+code + +``` +type: horizontal-stack +cards: + - type: gauge + needle: false + name: Battery + entity: sensor.sigineer_battery_voltage + - type: gauge + entity: sensor.sigineer_output_voltage + name: Output + - type: gauge + needle: false + entity: sensor.sigineer_bus_voltage + name: Bus + - type: gauge + entity: sensor.sigineer_grid_voltage + name: Grid + severity: + green: 750 + yellow: 250 + red: 0 + +``` +
\ No newline at end of file diff --git a/documentation/devices/SolArk.md b/documentation/devices/SolArk.md new file mode 100644 index 0000000..3b889a7 --- /dev/null +++ b/documentation/devices/SolArk.md @@ -0,0 +1,19 @@ +# SolArk to MQTT + +Currently untested / unconfirmed. ppg version 1.1.3 and higher + +## Hardware +1. USB to RS485 Adapter & RJ45 Ethernet cable ( or 3 wires ) +2. Connect RJ45 ethernet cable to an available RS485/Modbus port: +3. Connect appropriate wires to USB RS485 Adapter. see sol ark documentation + +![image](https://github.com/HotNoob/PythonProtocolGateway/assets/2180145/1d14a542-25ab-4233-917b-304c5bfe2ef2) + + +## Configuration +Follow configuration example for ModBus RTU to MQTT +https://github.com/HotNoob/PythonProtocolGateway/wiki/Configuration-Examples#modbus-rtu-to-mqtt + +``` +protocol_version = solark_v1.1 +``` \ No newline at end of file diff --git a/documentation/usage/README.md b/documentation/usage/README.md new file mode 100644 index 0000000..2df2285 --- /dev/null +++ b/documentation/usage/README.md @@ -0,0 +1,12 @@ +# README Index + +This README file contains an index of all files in the documentation directory. + +## File List + +**configuration_examples** + + +- [modbus_rtu_to_modbus_tcp.md](configuration_examples/modbus_rtu_to_modbus_tcp.md) - ModBus RTU to ModBus TCP +- [modbus_rtu_to_mqtt.md](configuration_examples/modbus_rtu_to_mqtt.md) - ModBus RTU to MQTT + diff --git a/documentation/usage/configuration_examples/README.md b/documentation/usage/configuration_examples/README.md new file mode 100644 index 0000000..5a44987 --- /dev/null +++ b/documentation/usage/configuration_examples/README.md @@ -0,0 +1,6 @@ +# README Index + +This README file contains an index of all files in the documentation directory. + +## File List + diff --git a/documentation/usage/configuration_examples/modbus_rtu_to_modbus_tcp.md b/documentation/usage/configuration_examples/modbus_rtu_to_modbus_tcp.md new file mode 100644 index 0000000..0f564ce --- /dev/null +++ b/documentation/usage/configuration_examples/modbus_rtu_to_modbus_tcp.md @@ -0,0 +1,44 @@ +### ModBus RTU to ModBus TCP +untested; in dev +``` +[general] +log_level = DEBUG + +[transport.0] #name must be unique, ie: transport.modbus +#rs485 / modbus device +#protocol config files are located in protocols/ +protocol_version = v0.14 +analyze_protocol = false +write = false + +#modbus address +address = 1 +port = {{serial port, likely /dev/ttyUSB0}} +baudrate = 9600 + +#modbus tcp/tls/udp example +#host = 192.168.0.7 +#port = 502 +#override protocol reader +#transport = modbus_tcp + +#the 'transport' that we want to share this with +bridge = transport.1 + +manufacturer = {{Your device's manufacturer here}} +model = {{Your device's model number here}} +#optional; leave blank to autofetch serial from device +serial_number = + +read_interval = 10 +error_interval = 60 + + +[transport.1] +#modbus tcp +protocol_version = v0.14 +transport=modbus_tcp +write = true +host = 127.0.0.1 +port = 502 +``` \ No newline at end of file diff --git a/documentation/usage/configuration_examples/modbus_rtu_to_mqtt.md b/documentation/usage/configuration_examples/modbus_rtu_to_mqtt.md new file mode 100644 index 0000000..1dc4fba --- /dev/null +++ b/documentation/usage/configuration_examples/modbus_rtu_to_mqtt.md @@ -0,0 +1,49 @@ +### ModBus RTU to MQTT +``` +[general] +log_level = DEBUG + +[transport.0] #name must be unique, ie: transport.modbus +#rs485 / modbus device +#protocol config files are located in protocols/ +protocol_version = v0.14 +analyze_protocol = false +write = false + +#modbus address +address = 1 +port = {{serial port, likely /dev/ttyUSB0}} +baudrate = 9600 + +#modbus tcp/tls/udp example +#host = 192.168.0.7 +#port = 502 +#override protocol reader +#transport = modbus_tcp + +#the 'transport' that we want to share this with +bridge = transport.1 + +manufacturer = {{Your device's manufacturer here}} +model = {{Your device's model number here}} +#optional; leave blank to autofetch serial from device +serial_number = + +read_interval = 10 +error_interval = 60 + + +[transport.1] +#connect mqtt +transport=mqtt +host = {{mqtt ip / host}} +port = 1883 +user = {{mqtt username here}} +pass = {{mqtt password}} +base_topic = home/inverter/ +error_topic = /error +json = false +discovery_enabled = true +discovery_topic = homeassistant +``` + diff --git a/documentation/usage/creating_and_editing_protocols.md b/documentation/usage/creating_and_editing_protocols.md new file mode 100644 index 0000000..670aaf7 --- /dev/null +++ b/documentation/usage/creating_and_editing_protocols.md @@ -0,0 +1,74 @@ +# Creating and Editing Protocols ‐ JSON ‐ CSV + +"Protocols" are defined through 2 file types. + +The first is .json, which contains the default settings and optionally codes. + +The .csv files hold the registry or address definitions. + +# CSV + +CSV = comma seperated values... spreadsheets. +delimeter for csv can be , or ; ( not both ) + +| variable name | data type | register|documented name|description|writable|values | +| -- | -- | -- | -- | -- | -- | -- | + + +### variable name +provides a user-friendly name to work with, while retaining the original documented name; since a lot of variable names are up to interpretation. + +### documented name +Original variable name provided from protocol documention; when variable name is not specified, this name is used. + +### data type +Defines the expected data type for the register / map entry + +| Type | Description | +| -- | -- | +| USHORT | A two byte ( 16 bit ) positive number. For protocols that return 2 byte values, this is the default type. +| BYTE | A single byte ( 8 bit ) positive number. +| UINT | A four byte ( 32 bit ) positive number. +| INT | A four byte ( 32 bit ) signed number (positive or negative) +| 16BIT_FLAGS | two bytes split into 16 bits, each bit represents on/off flag which is defined as b#. this will translate into 16x 0/1s if no "codes" are defined. +| 8BIT_FLAGS | A single byte split into 8 bit flags. see 16BIT_FLAGS +| 32BIT_FLAGS | four bytes split into 32 bit flags. see 16BIT_FLAGS +| #bit | A unsigned number comprised of # of bits. for example, 3bit is a 3 bit positive number (0 to 7). +| ASCII | ascii text representation of data. +| ASCII.# | for protocols with an undefined "registry" size, the length can be specified. ie: ASCII.7 will return a 7 character long string. + +### register +Register defines the location or for other protocols the main command / id. +The registers are defined in decimal form. a prefix of "x" is acceptable for hexadecimal. + +For **ASCII** or other MultiRegister Data Types, register ranges can be defined: +``` +7~14 +``` +a prefix of "r" will specify that the registers be "read" in backwards order. + +#### register bit +bit offsets are specified with .b#, # being the bit offset + +``` +#.b# +``` + +#### register byte +byte offsets are specified with .#, # being the byte offset + +``` +#.# +``` + +#### writable +mainly for registers / entries that support writing, such as the holding register for modbus + +``` +R = Read Only +RD = Read Disabled +W = Write +``` + + + diff --git a/documentation/usage/protocols.md b/documentation/usage/protocols.md new file mode 100644 index 0000000..9068598 --- /dev/null +++ b/documentation/usage/protocols.md @@ -0,0 +1,48 @@ +## Custom / Editing Protocols +Custom protocols can be created by naming them with name.custom. this will ensure that they do not get overwritten when updating. + +for example, say that you want to modify the eg4_v58 protocol without having to worry about updates overwritting it: + +``` +copy eg4_v58.json eg4_v58.custom.json +copy eg4_v58.input_registry_map.csv eg4_v58.input_registry_map.custom.csv +copy eg4_v58.holding_registry_map.csv eg4_v58.holding_registry_map.custom.csv +``` + +in the configuration for your transport: +``` +protocol_version = eg4_v58.custom +``` + +## Protocol Configuration - CSV / JSON +{protocol_name}.json contains default settings, releated to the transport. +{protocol_name}.{registry_type}_registry_map.csv contains configuration for specific registry "type". +{protocol_name}.registry_map.csv contains configuration for generic "registers". + +### csv format: +https://github.com/HotNoob/PythonProtocolGateway/wiki/Creating-and-Editing-Protocols-%E2%80%90-JSON-%E2%80%90-CSV#csv + +## egv_v58 +``` +protocol_version = eg4_v58 +``` +[Devices\EG4 to MQTT](https://github.com/HotNoob/PythonProtocolGateway/wiki/Devices%5CEG4-to-MQTT) + +## v0.14 +``` +protocol_version = v0.14 +``` +[Devices\Growatt To MQTT](https://github.com/HotNoob/PythonProtocolGateway/wiki/Devices%5CGrowatt-To-MQTT) + +## sigineer_v0.11 + +``` +protocol_version = sigineer_v0.11 +``` +[Devices\Sigineer to MQTT](https://github.com/HotNoob/PythonProtocolGateway/wiki/Devices%5CSigineer-to-MQTT) + +## pace_bms_v1.3 +``` +protocol_version = pace_bms_v1.3 +``` +[Devices\SOK to MQTT](https://github.com/HotNoob/PythonProtocolGateway/wiki/Devices%5CSOK-to-MQTT) \ No newline at end of file diff --git a/documentation/usage/transports.md b/documentation/usage/transports.md new file mode 100644 index 0000000..195aa75 --- /dev/null +++ b/documentation/usage/transports.md @@ -0,0 +1,219 @@ +# Transports + +A transport is configured by creating a configuration section, starting with "transport." +``` +[transport.0] +{transport config here} +``` +or +``` +[transport.growatt] +{transport config here} +``` + +the section header is dual purpose and acts as naming mechanism for parameters like "bridge" + +## Protocol +Transports using ambigious open ended protocols, such as ModBus, Canbus, or register / address based protocols require a map to be defined. this is done so in the /protocols/ folder through csv tables. + +``` +protocol = v0.14 +``` + +see [Protocol Wiki](https://github.com/HotNoob/PythonProtocolGateway/wiki/Protocols) for more on this. + +Other transport protocols such as MQTT, do not require this configuration, as the variable names are provided during transmision and therefore do not need interpretation. + +### Reading +Some transports are Event based, such as MQTT, while others are require active reading. +for protocols that require active reading such as ModBus, the scan interval is set via "read_interval" + +``` +read_interval = 10 +``` + +### Writing +For ambigious sensitive protocols/transports such as ModBus, a safety mechanism is in place to help prevent "bricking" your devices or causing potentially catostrophic damages. + +In order to write, the configuration csv file must be at least 90% verifiable. Alternatively a manual verification method will be implemented in the future. This mainly entails that the current values in the writeable register ( probably holding ), be within the value range specified in the csv. + +Finally, to enable writing for a transport: +``` +write_enabled = true +``` + +Finally, to write, "read" data on any bridged transport. In most cases this will likely be MQTT. + +### Custom Transport +custom transports can be created by naming them name.custom and creating the appropriate .py file. +for example a custom mqtt transport: +copy classes/transports/mqtt.py to classes/transports/mqtt.custom.py + +to use this custom transport: +``` +transport=mqtt.custom +``` + +naming your transport with .custom will ensure that it won't be overwritten when updating. + +# Base +These are parameters that apply to all transports +``` +transport = +device_name = +manufacturer = +model = +serial_number = +bridge = +write_enabled = False +Interval = 10 +Error_Interval = 60 +``` +### transport +Transport is the type or method of reading and writing data +``` +transport = modbus_rtu +``` + + +### device_name +device_name is used as an identifier and is passed on where applicable. +if left blank, device_name will be set to manufacturer + model +``` +device_name = My Solar Inverter 1 +``` + +### manufacturer +manufacturer is used as an identifier and is passed on where applicable. +``` +manufacturer = Growatt +``` + +### model +model is used as an identifier and is passed on where applicable. +``` +model = SPF 12000T +``` + +### serial_number +serial_number is used as an identifier and is passed on where applicable. +If left empty, serial number may be automatically fetched if protocol supports it. +``` +serial_number = +``` + +### bridge +bridge determines which transports to translate data to +this value can be a csv to specify multiple transports +if bridge is set to "broadcast", data will be broadcasted / sent over all configured transports +``` +bridge = transport.mqtt +``` + +### write_enabled +write_enabled allows writting to this transport if enabled. +many protocols have this disabled by default and require accurate registry maps to enable writing, as misconfiguration can have fatal unintended consequences. +by default mqtt allows writing. +``` +write_enabled = True +``` + +#Interval +Interval ( seconds ), sets the frequency of how often data is actively read from a transport. +If interval is not set, data is only sent passively as events occur. +``` +Interval = 10 +``` + +# MQTT +``` +###required +Transport = MQTT +Host +Port +User +Pass +Base_Topic = +discovery_enabled = True +discovery_topic = homeassistant +``` + +``` +###optional +Json = False +Error_Topic = /error +holding_register_prefix = +input_register_prefix = +``` + +## MQTT Read +mqtt "reads" data via the "write" topic. +data that is "read" on the mqtt transport is "written" on any bridged transports. +i know... confusing :P + +during the initialization process, MQTT subscribes to "write enabled" variables / topics, based on the bridged transport's protocol. +the writable topics are given a prefix of "/write/" +``` +{base topic}/write/{variable name} +``` + +## MQTT Write +by default mqtt writes data from the bridged transport. + +# ModBus_RTU +``` +###required +transport = modbus_rtu +protocol_version = +Port = +BaudRate = +Address = +``` +### port +Port is the path to the communications port. on Windows this would be COM#, for Linux /dev/ttyUSB# + +these port numbers can change upon restarting, therefore alternatively, the port can be specified via hardware ids (v1.1.2+): +``` +port = [0x1a86:0x7523::1-4] +``` + +The hardware ids format is: +``` +[vendor id:product id:serial number:location] +``` + +for convience the hardware ids are outputted during script startup. +``` +Serial Port : COM11 = [0x1a86:0x7523::1-4] +``` + +### analyze_protocol +needs a lot of work. on the todo to improve. low priority +``` +analyze_protocol = true +``` + +when this mode runs, it attempt to read all of the registers of your inverter and attempt to determine which protocol best fits. +the higher the value, the more likely that the protocol matches. + +``` +=== growatt_2020_v1.24 - 710 === +input register : 405 of 695 +holding register : 305 of 561 +=== sigineer_v0.11 - 62 === +input register : 31 of 150 +holding register : 31 of 63 +=== v0.14 - 60 === +input register : 19 of 63 +holding register : 41 of 101 +``` + +the results above suggests that "growatt_2020_v1.24" is the most likely protocol for the inverter. + +### analyze_protocol_save_load +``` +analyze_protocol = true +analyze_protocol_save_load = true +``` +When enabled, the analyzer will save dump files containing the raw data found while scanning + diff --git a/protocols/eg4_3000ehv_v1.holding_registry_map.csv b/protocols/eg4/eg4_3000ehv_v1.holding_registry_map.csv similarity index 100% rename from protocols/eg4_3000ehv_v1.holding_registry_map.csv rename to protocols/eg4/eg4_3000ehv_v1.holding_registry_map.csv diff --git a/protocols/eg4_3000ehv_v1.json b/protocols/eg4/eg4_3000ehv_v1.json similarity index 100% rename from protocols/eg4_3000ehv_v1.json rename to protocols/eg4/eg4_3000ehv_v1.json diff --git a/protocols/eg4_v58.holding_registry_map.csv b/protocols/eg4/eg4_v58.holding_registry_map.csv similarity index 100% rename from protocols/eg4_v58.holding_registry_map.csv rename to protocols/eg4/eg4_v58.holding_registry_map.csv diff --git a/protocols/eg4_v58.input_registry_map.csv b/protocols/eg4/eg4_v58.input_registry_map.csv similarity index 100% rename from protocols/eg4_v58.input_registry_map.csv rename to protocols/eg4/eg4_v58.input_registry_map.csv diff --git a/protocols/eg4_v58.json b/protocols/eg4/eg4_v58.json similarity index 100% rename from protocols/eg4_v58.json rename to protocols/eg4/eg4_v58.json diff --git a/protocols/growatt_2020_v1.24.holding_registry_map.csv b/protocols/growatt/growatt_2020_v1.24.holding_registry_map.csv similarity index 100% rename from protocols/growatt_2020_v1.24.holding_registry_map.csv rename to protocols/growatt/growatt_2020_v1.24.holding_registry_map.csv diff --git a/protocols/growatt_2020_v1.24.input_registry_map.csv b/protocols/growatt/growatt_2020_v1.24.input_registry_map.csv similarity index 100% rename from protocols/growatt_2020_v1.24.input_registry_map.csv rename to protocols/growatt/growatt_2020_v1.24.input_registry_map.csv diff --git a/protocols/growatt_2020_v1.24.json b/protocols/growatt/growatt_2020_v1.24.json similarity index 100% rename from protocols/growatt_2020_v1.24.json rename to protocols/growatt/growatt_2020_v1.24.json diff --git a/protocols/v0.14.holding_registry_map.csv b/protocols/growatt/v0.14.holding_registry_map.csv similarity index 100% rename from protocols/v0.14.holding_registry_map.csv rename to protocols/growatt/v0.14.holding_registry_map.csv diff --git a/protocols/v0.14.input_registry_map.csv b/protocols/growatt/v0.14.input_registry_map.csv similarity index 100% rename from protocols/v0.14.input_registry_map.csv rename to protocols/growatt/v0.14.input_registry_map.csv diff --git a/protocols/v0.14.json b/protocols/growatt/v0.14.json similarity index 100% rename from protocols/v0.14.json rename to protocols/growatt/v0.14.json diff --git a/protocols/pace_bms_v1.3.holding_registry_map.csv b/protocols/pace/pace_bms_v1.3.holding_registry_map.csv similarity index 100% rename from protocols/pace_bms_v1.3.holding_registry_map.csv rename to protocols/pace/pace_bms_v1.3.holding_registry_map.csv diff --git a/protocols/pace_bms_v1.3.json b/protocols/pace/pace_bms_v1.3.json similarity index 100% rename from protocols/pace_bms_v1.3.json rename to protocols/pace/pace_bms_v1.3.json diff --git a/protocols/pylon_rs485_v3.3.json b/protocols/pylon/pylon_rs485_v3.3.json similarity index 100% rename from protocols/pylon_rs485_v3.3.json rename to protocols/pylon/pylon_rs485_v3.3.json diff --git a/protocols/pylon_rs485_v3.3.registry_map.csv b/protocols/pylon/pylon_rs485_v3.3.registry_map.csv similarity index 100% rename from protocols/pylon_rs485_v3.3.registry_map.csv rename to protocols/pylon/pylon_rs485_v3.3.registry_map.csv diff --git a/protocols/sigineer_v0.11.holding_registry_map.csv b/protocols/sigineer/sigineer_v0.11.holding_registry_map.csv similarity index 100% rename from protocols/sigineer_v0.11.holding_registry_map.csv rename to protocols/sigineer/sigineer_v0.11.holding_registry_map.csv diff --git a/protocols/sigineer_v0.11.input_registry_map.csv b/protocols/sigineer/sigineer_v0.11.input_registry_map.csv similarity index 100% rename from protocols/sigineer_v0.11.input_registry_map.csv rename to protocols/sigineer/sigineer_v0.11.input_registry_map.csv diff --git a/protocols/sigineer_v0.11.json b/protocols/sigineer/sigineer_v0.11.json similarity index 100% rename from protocols/sigineer_v0.11.json rename to protocols/sigineer/sigineer_v0.11.json diff --git a/protocols/solark_v1.1.holding_registry_map.csv b/protocols/solark/solark_v1.1.holding_registry_map.csv similarity index 100% rename from protocols/solark_v1.1.holding_registry_map.csv rename to protocols/solark/solark_v1.1.holding_registry_map.csv diff --git a/protocols/solark_v1.1.json b/protocols/solark/solark_v1.1.json similarity index 100% rename from protocols/solark_v1.1.json rename to protocols/solark/solark_v1.1.json diff --git a/protocols/srne_2021_v1.96.holding_registry_map.csv b/protocols/srne_2021_v1.96.holding_registry_map.csv new file mode 100644 index 0000000..ce9db32 --- /dev/null +++ b/protocols/srne_2021_v1.96.holding_registry_map.csv @@ -0,0 +1,59 @@ +variable name,data type,register,documented name,description,writable,values,unit,note +,BYTE,0x000B,Product type,,R,"{""0"": ""domestic controller"", ""1"": ""controller for street light"", ""3"": ""grid-connected inverter"", ""4"": ""all-in-one solar charger inverter"", ""5"": ""power frequency off-grid""}",, +,,0x0014,Software version,,R,,0.01, +,,0x0016,Hardware version,,R,,0.01, +,BYTE,0x001A,RS485 address,,R,1~247,, +,,0x001C,RS485 version,,R,,0.01, +,ASCII,0x0035~0x0048,Product SN,,R,,, +,,0x0100,Battery capacity SOC,,R,0~100,%, +,,0x0101,Battery voltage,,R,,0.1V, +,SHORT,0x0102,Battery current,,R,,0.1A, +,SHORT,0x0103,Battery temperature,,R,,0.1C, +,,0x0107,PV1 voltage,,R,,0.1V, +,,0x0108,PV1 current,,R,,0.1A, +,,0x0109,PV1 power,,R,,W, +,,0x010F,PV2 voltage,,R,,0.1V, +,,0x0110,PV2 current,,R,,0.1A, +,,0x0111,PV2 power,,R,,W, +,BYTE,0x010B,Device Charge state,,R,"{""0"": ""Charge off"", ""1"": ""Quick charge"", ""2"": ""Const voltage charge"", ""4"": ""Float charge"", ""6"": ""Li battery activate"", ""8"": ""Full""}",, +,,0x010E,Device Total charging power,,R,,W, +,BYTE,0x0210,Device state,,R,"{""0"": ""Initialization"", ""1"": ""Standby"", ""2"": ""AC power operation"", ""3"": ""Inverter operation""}",, +,,0x0212,Device Bus Voltage Sum,,R,,0.1V, +,,0x0213,Grid phase-A voltage,,R,,0.1V, +,,0x0214,Grid phase-A current,,R,,0.1A, +,,0x022A,Grid phase-B voltage,,R,,0.1V, +,,0x0238,Grid phase-B current,,R,,0.1A, +,,0x022B,Grid phase-C voltage,,R,,0.1V, +,,0x0239,Grid phase-C current,,R,,0.1A, +,,0x0215,Grid frequency,,R,,0.01Hz, +,,0x0216,Inverter phase-A output voltage,,R,,0.1V, +,,0x0217,Inverter phase-A inductive current,,R,,0.1A, +,,0x022C,Inverter phase-B output voltage,,R,,0.1V, +,,0x022E,Inverter phase-B inductive current,,R,,0.1A, +,,0x022D,Inverter phase-C output voltage,,R,,0.1V, +,,0x022F,Inverter phase-C inductive current,,R,,0.1A, +,,0x0218,Inverter frequency,,R,,0.01Hz, +,,0x0219,Load Phase-A current,,R,,0.1A, +,,0x021B,Load Phase-A active power,,R,,W, +,,0x021C,Load Phase-A apparent power,,R,,VA, +,,0x021F,Load Phase-A ratio,,R,0~100,%, +,,0x0230,Load Phase-B current,,R,,0.1A, +,,0x0232,Load Phase-B active power,,R,,W, +,,0x0234,Load Phase-B apparent power,,R,,VA, +,,0x0236,Load Phase-B ratio,,R,0~100,%, +,,0x0231,Load Phase-C current,,R,,0.1A, +,,0x0233,Load Phase-C active power,,R,,W, +,,0x0235,Load Phase-C apparent power,,R,,VA, +,,0x0237,Load Phase-C ratio,,R,0~100,%, +,BYTE,0xF02C,Stats GenerateEnergyToGridTday,,R,,0.1kWh, +,BYTE,0xF02D,Stats BatChgTday,,R,,1AH, +,BYTE,0xF02E,Stats BatDischgTday,,R,,1AH, +,BYTE,0xF02F,Stats GenerateEnergyTday,,R,,0.1kWh, +,BYTE,0xF030,Stats UsedEnergyTday,,R,,0.1kWh, +,BYTE,0xF031,Stats WorkDaysTotal,,R,,1d, +,BYTE,0xF03C,Stats GridChgEnergyTday,,R,,1AH, +,BYTE,0xF03D,Stats LoadConsumLineTday,,R,,0.1kWh, +,BYTE,0xF03E,Stats InvWorkTimeTday,,R,,1min, +,BYTE,0xF03F,Stats GridWorkTimeTday,,R,,1min, +,BYTE,0xF04A,Stats InvWorkTimeTotal,,R,,1h, +,BYTE,0xF04B,Stats GridWorkTimeTotal,,R,,1h, \ No newline at end of file diff --git a/protocols/victron_gx_3.3.json b/protocols/srne_2021_v1.96.json similarity index 100% rename from protocols/victron_gx_3.3.json rename to protocols/srne_2021_v1.96.json diff --git a/protocols/victron/victron_gx_3.3.json b/protocols/victron/victron_gx_3.3.json new file mode 100644 index 0000000..ee874b1 --- /dev/null +++ b/protocols/victron/victron_gx_3.3.json @@ -0,0 +1,5 @@ +{ + "transport" : "modbus_rtu", + "send_holding_register": true, + "send_input_register" : false +} \ No newline at end of file diff --git a/protocols/victron_gx_v3.3.holding_registry_map.csv b/protocols/victron/victron_gx_v3.3.holding_registry_map.csv similarity index 100% rename from protocols/victron_gx_v3.3.holding_registry_map.csv rename to protocols/victron/victron_gx_v3.3.holding_registry_map.csv diff --git a/pytests/test_protocol_settings.py b/pytests/test_protocol_settings.py index df8cd34..dba99ac 100644 --- a/pytests/test_protocol_settings.py +++ b/pytests/test_protocol_settings.py @@ -1,6 +1,8 @@ import sys import os import pytest +import glob + #move up a folder for tests sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) @@ -8,11 +10,17 @@ from classes.protocol_settings import protocol_settings # List of protocols to test -protocols = [os.path.splitext(f)[0] for f in os.listdir("protocols") if f.endswith('.json')] +# Create the search pattern to find .json files recursively +search_pattern = os.path.join("protocols", '**', '*.json') +# Use glob to find all files matching the pattern +files = glob.glob(search_pattern, recursive=True) +# Extract file names without extension +protocols = [os.path.splitext(os.path.basename(f))[0] for f in files] # Parameterized test function @pytest.mark.parametrize("protocol", protocols) def test_protocol_setting(protocol : str): + print(protocol) protocolSettings : protocol_settings = protocol_settings(protocol) diff --git a/requirements.txt b/requirements.txt index 0533626..b649569 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -pymodbus==2.3.0 -paho-mqtt \ No newline at end of file +pymodbus==3.7.0 +paho-mqtt +pyserial \ No newline at end of file