forked from andiburger/growatt2mqtt
-
-
Notifications
You must be signed in to change notification settings - Fork 22
Several medium sized improvements #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
44db2f3
* improve eg4 handling
jaredmauch 30902c8
s/type/trasport
jaredmauch 8afe3f0
revert
jaredmauch d5632a5
attempt to fix issue with analyze_protocol = true
jaredmauch f9d5fc2
attempt to fix issue with analyze_protocol = true
jaredmauch a52c418
attempt to fix issue with analyze_protocol = true
jaredmauch 0248882
attempt to fix issue with analyze_protocol = true
jaredmauch e8be5e9
Fix pymodbus 3.7+ compatibility issues
jaredmauch 7f6e1ed
remove testing files from branch
jaredmauch fcad1f2
Fix UnboundLocalError and improve validation robustness
jaredmauch 7650f0b
Fix analyze_protocol validation timing issue
jaredmauch 9c69c2e
Fix analyze_protocol to use configured protocol register ranges
jaredmauch e36fa93
address connection issue
jaredmauch 663e76d
address issue with analyze_protocol
jaredmauch 55e2a68
address issue with analyze_protocol
jaredmauch 635e3ba
address issue with analyze_protocol
jaredmauch e8d7c56
address issue with analyze_protocol
jaredmauch 5374391
address issue with analyze_protocol
jaredmauch 20e18b1
address issue with analyze_protocol
jaredmauch 1970e23
address issue with analyze_protocol
jaredmauch d3b4166
address issue with analyze_protocol
jaredmauch bbf19e3
address issue with analyze_protocol
jaredmauch 021736f
address issue with analyze_protocol baudrate
jaredmauch c0510f6
address issue with analyze_protocol baudrate
jaredmauch 224d4ed
restore file accidentally deleted in 7f6e1ed
jaredmauch d6d14d1
sync over 4db83627f1ec637851e5acf0906003b0341b4344
jaredmauch 64fb1ef
Merge branch 'main' of github.com:jaredmauch/PythonProtocolGateway
jaredmauch af51f7d
cleanup logging, place DEBUG level messages behind debug
jaredmauch df9c9c6
cleanup logging, place DEBUG level messages behind debug
jaredmauch a8f606a
influxdb floating point fixup
jaredmauch fb49a90
promote serial number from inverter to device
jaredmauch e7d78b0
promote serial number from inverter to device
jaredmauch d03a843
simplify device_serial_number
jaredmauch 950fd4b
Merge branch 'v1.1.10-pr92' into main
HotNoob File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,163 @@ | ||
| import sys | ||
| from configparser import SectionProxy | ||
| from typing import TextIO | ||
| import time | ||
|
|
||
| from defs.common import strtobool | ||
|
|
||
| from ..protocol_settings import Registry_Type, WriteMode, registry_map_entry | ||
| from .transport_base import transport_base | ||
|
|
||
|
|
||
| class influxdb_out(transport_base): | ||
| ''' InfluxDB v1 output transport that writes data to an InfluxDB server ''' | ||
| host: str = "localhost" | ||
| port: int = 8086 | ||
| database: str = "solar" | ||
| username: str = "" | ||
| password: str = "" | ||
| measurement: str = "device_data" | ||
| include_timestamp: bool = True | ||
| include_device_info: bool = True | ||
| batch_size: int = 100 | ||
| batch_timeout: float = 10.0 | ||
|
|
||
| client = None | ||
| batch_points = [] | ||
| last_batch_time = 0 | ||
|
|
||
| def __init__(self, settings: SectionProxy): | ||
| self.host = settings.get("host", fallback=self.host) | ||
| self.port = settings.getint("port", fallback=self.port) | ||
| self.database = settings.get("database", fallback=self.database) | ||
| self.username = settings.get("username", fallback=self.username) | ||
| self.password = settings.get("password", fallback=self.password) | ||
| self.measurement = settings.get("measurement", fallback=self.measurement) | ||
| self.include_timestamp = strtobool(settings.get("include_timestamp", fallback=self.include_timestamp)) | ||
| self.include_device_info = strtobool(settings.get("include_device_info", fallback=self.include_device_info)) | ||
| self.batch_size = settings.getint("batch_size", fallback=self.batch_size) | ||
| self.batch_timeout = settings.getfloat("batch_timeout", fallback=self.batch_timeout) | ||
|
|
||
| self.write_enabled = True # InfluxDB output is always write-enabled | ||
| super().__init__(settings) | ||
|
|
||
| def connect(self): | ||
| """Initialize the InfluxDB client connection""" | ||
| self._log.info("influxdb_out connect") | ||
|
|
||
| try: | ||
| from influxdb import InfluxDBClient | ||
|
|
||
| # Create InfluxDB client | ||
| self.client = InfluxDBClient( | ||
| host=self.host, | ||
| port=self.port, | ||
| username=self.username if self.username else None, | ||
| password=self.password if self.password else None, | ||
| database=self.database | ||
| ) | ||
|
|
||
| # Test connection | ||
| self.client.ping() | ||
|
|
||
| # Create database if it doesn't exist | ||
| databases = self.client.get_list_database() | ||
| if not any(db['name'] == self.database for db in databases): | ||
| self._log.info(f"Creating database: {self.database}") | ||
| self.client.create_database(self.database) | ||
|
|
||
| self.connected = True | ||
| self._log.info(f"Connected to InfluxDB at {self.host}:{self.port}") | ||
|
|
||
| except ImportError: | ||
| self._log.error("InfluxDB client not installed. Please install with: pip install influxdb") | ||
| self.connected = False | ||
| except Exception as e: | ||
| self._log.error(f"Failed to connect to InfluxDB: {e}") | ||
| self.connected = False | ||
|
|
||
| def write_data(self, data: dict[str, str], from_transport: transport_base): | ||
| """Write data to InfluxDB""" | ||
| if not self.write_enabled or not self.connected: | ||
| return | ||
|
|
||
| self._log.info(f"write data from [{from_transport.transport_name}] to influxdb_out transport") | ||
| self._log.info(data) | ||
|
|
||
| # Prepare tags for InfluxDB | ||
| tags = {} | ||
|
|
||
| # Add device information as tags if enabled | ||
| if self.include_device_info: | ||
| tags.update({ | ||
| "device_identifier": from_transport.device_identifier, | ||
| "device_name": from_transport.device_name, | ||
| "device_manufacturer": from_transport.device_manufacturer, | ||
| "device_model": from_transport.device_model, | ||
| "device_serial_number": from_transport.device_serial_number, | ||
| "transport": from_transport.transport_name | ||
| }) | ||
|
|
||
| # Prepare fields (the actual data values) | ||
| fields = {} | ||
| for key, value in data.items(): | ||
| # Try to convert to numeric values for InfluxDB | ||
| try: | ||
| # Try to convert to float first | ||
| float_val = float(value) | ||
| # If it's an integer, store as int | ||
| if float_val.is_integer(): | ||
| fields[key] = int(float_val) | ||
| else: | ||
| fields[key] = float_val | ||
| except (ValueError, TypeError): | ||
| # If conversion fails, store as string | ||
| fields[key] = str(value) | ||
|
|
||
| # Create InfluxDB point | ||
| point = { | ||
| "measurement": self.measurement, | ||
| "tags": tags, | ||
| "fields": fields | ||
| } | ||
|
|
||
| # Add timestamp if enabled | ||
| if self.include_timestamp: | ||
| point["time"] = int(time.time() * 1e9) # Convert to nanoseconds | ||
|
|
||
| # Add to batch | ||
| self.batch_points.append(point) | ||
|
|
||
| # Check if we should flush the batch | ||
| current_time = time.time() | ||
| if (len(self.batch_points) >= self.batch_size or | ||
| (current_time - self.last_batch_time) >= self.batch_timeout): | ||
| self._flush_batch() | ||
|
|
||
| def _flush_batch(self): | ||
| """Flush the batch of points to InfluxDB""" | ||
| if not self.batch_points: | ||
| return | ||
|
|
||
| try: | ||
| self.client.write_points(self.batch_points) | ||
| self._log.info(f"Wrote {len(self.batch_points)} points to InfluxDB") | ||
| self.batch_points = [] | ||
| self.last_batch_time = time.time() | ||
| except Exception as e: | ||
| self._log.error(f"Failed to write batch to InfluxDB: {e}") | ||
| self.connected = False | ||
|
|
||
| def init_bridge(self, from_transport: transport_base): | ||
| """Initialize bridge - not needed for InfluxDB output""" | ||
| pass | ||
|
|
||
| def __del__(self): | ||
| """Cleanup on destruction - flush any remaining points""" | ||
| if self.batch_points: | ||
| self._flush_batch() | ||
| if self.client: | ||
| try: | ||
| self.client.close() | ||
| except Exception: | ||
| pass |
This file contains hidden or 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,109 @@ | ||
| import json | ||
| import sys | ||
| from configparser import SectionProxy | ||
| from typing import TextIO | ||
|
|
||
| from defs.common import strtobool | ||
|
|
||
| from ..protocol_settings import Registry_Type, WriteMode, registry_map_entry | ||
| from .transport_base import transport_base | ||
|
|
||
|
|
||
| class json_out(transport_base): | ||
| ''' JSON output transport that writes data to a file or stdout ''' | ||
| output_file: str = "stdout" | ||
| pretty_print: bool = True | ||
| append_mode: bool = False | ||
| include_timestamp: bool = True | ||
| include_device_info: bool = True | ||
|
|
||
| file_handle: TextIO = None | ||
|
|
||
| def __init__(self, settings: SectionProxy): | ||
| self.output_file = settings.get("output_file", fallback=self.output_file) | ||
| self.pretty_print = strtobool(settings.get("pretty_print", fallback=self.pretty_print)) | ||
| self.append_mode = strtobool(settings.get("append_mode", fallback=self.append_mode)) | ||
| self.include_timestamp = strtobool(settings.get("include_timestamp", fallback=self.include_timestamp)) | ||
| self.include_device_info = strtobool(settings.get("include_device_info", fallback=self.include_device_info)) | ||
|
|
||
| self.write_enabled = True # JSON output is always write-enabled | ||
| super().__init__(settings) | ||
|
|
||
| def connect(self): | ||
| """Initialize the output file handle""" | ||
| self._log.info("json_out connect") | ||
|
|
||
| if self.output_file.lower() == "stdout": | ||
| self.file_handle = sys.stdout | ||
| else: | ||
| try: | ||
| mode = "a" if self.append_mode else "w" | ||
| self.file_handle = open(self.output_file, mode, encoding='utf-8') | ||
| self.connected = True | ||
| except Exception as e: | ||
| self._log.error(f"Failed to open output file {self.output_file}: {e}") | ||
| self.connected = False | ||
| return | ||
|
|
||
| self.connected = True | ||
|
|
||
| def write_data(self, data: dict[str, str], from_transport: transport_base): | ||
| """Write data as JSON to the output file""" | ||
| if not self.write_enabled or not self.connected: | ||
| return | ||
|
|
||
| self._log.info(f"write data from [{from_transport.transport_name}] to json_out transport") | ||
| self._log.info(data) | ||
|
|
||
| # Prepare the JSON output structure | ||
| output_data = {} | ||
|
|
||
| # Add device information if enabled | ||
| if self.include_device_info: | ||
| output_data["device"] = { | ||
| "identifier": from_transport.device_identifier, | ||
| "name": from_transport.device_name, | ||
| "manufacturer": from_transport.device_manufacturer, | ||
| "model": from_transport.device_model, | ||
| "serial_number": from_transport.device_serial_number, | ||
| "transport": from_transport.transport_name | ||
| } | ||
|
|
||
| # Add timestamp if enabled | ||
| if self.include_timestamp: | ||
| import time | ||
| output_data["timestamp"] = time.time() | ||
|
|
||
| # Add the actual data | ||
| output_data["data"] = data | ||
|
|
||
| # Convert to JSON | ||
| if self.pretty_print: | ||
| json_string = json.dumps(output_data, indent=2, ensure_ascii=False) | ||
| else: | ||
| json_string = json.dumps(output_data, ensure_ascii=False) | ||
|
|
||
| # Write to file | ||
| try: | ||
| if self.output_file.lower() != "stdout": | ||
| # For files, add a newline and flush | ||
| self.file_handle.write(json_string + "\n") | ||
| self.file_handle.flush() | ||
| else: | ||
| # For stdout, just print | ||
| print(json_string) | ||
| except Exception as e: | ||
| self._log.error(f"Failed to write to output: {e}") | ||
| self.connected = False | ||
|
|
||
| def init_bridge(self, from_transport: transport_base): | ||
| """Initialize bridge - not needed for JSON output""" | ||
| pass | ||
|
|
||
| def __del__(self): | ||
| """Cleanup file handle on destruction""" | ||
| if self.file_handle and self.output_file.lower() != "stdout": | ||
| try: | ||
| self.file_handle.close() | ||
| except: | ||
| pass |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm. i dont remember enough to know if this is ok... :P