### Imports and Setup for pysolarmanv5_async Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5_async.html Initial imports and module-level setup for the asynchronous solarman client implementation. ```python """pysolarmanv5_async.py""" import errno import struct import asyncio from multiprocessing import Event from umodbus.client.serial import rtu from .pysolarmanv5 import NoSocketAvailableError, PySolarmanV5 # Disable `invalid-overridden-method` rule. The class `PySolarmanV5Async` overrides # (=redefines) non-async methods from `PySolarmanV5`. # The override in this case is not a problem in practice. # This could be avoided by changing the class hierarchy. # One way would be to introduce a common base class for async and sync classes # that would capture attributes and common (internal) sync methods. ``` -------------------------------- ### pysolarmanv5 Installation Source: https://pysolarmanv5.readthedocs.io/en/latest Instructions for installing the pysolarmanv5 Python module, including stable and development versions. ```APIDOC ## Installation To install the latest stable version of pysolarmanv5 from PyPi, run: ```bash pip install pysolarmanv5 ``` To install the latest development version from git, run: ```bash pip install git+https://github.com/jmccrohan/pysolarmanv5.git ``` This will also install the tools from the utils directory. ``` -------------------------------- ### Socket Setup and Data Transmission Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5_async.html Methods for initializing the socket and queuing received data for processing. ```python def _socket_setup(self, *args, **kwargs) -> None: """Socket setup method PySolarmanV5Async handles socket creation separately to base PySolarmanV5 class """ ``` ```python def _send_data(self, data: bytes) -> None: """ Sends the data received from the socket to the receiver. :param data: :return: """ if self.data_wanted_ev.is_set(): if not self.data_queue.empty(): _ = self.data_queue.get_nowait() self.data_queue.put_nowait(data) self.data_wanted_ev.clear() ``` -------------------------------- ### Configure Socket and Background Receiver Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Sets up the socket, polling mechanism, and starts a background thread for data reception. ```python def _socket_setup(self, sock: Any, auto_reconnect: bool): """Socket setup method""" if isinstance(sock, socket.socket) or sock is None: self.sock = sock if sock else self._create_socket() if self.sock is None: raise NoSocketAvailableError("No socket available") if _WIN_PLATFORM: self._poll = selectors.DefaultSelector() else: self._poll = selectors.PollSelector() self._sock_fd = self.sock.fileno() self._auto_reconnect = False if sock else auto_reconnect self._data_queue = Queue(maxsize=1) self._data_wanted = Event() self._reader_exit = Event() self._reader_thr = Thread(target=self._data_receiver, daemon=True) self._reader_thr.start() self.log.debug("Socket setup completed... %s", self.sock) ``` -------------------------------- ### Initialize PySolarmanV5 and Read Input Registers Source: https://pysolarmanv5.readthedocs.io/en/latest/api_reference.html Instantiate the PySolarmanV5 class with the data logger's IP address and serial number. Then, read a specified quantity of input registers starting from a given address using Modbus function code 4. Ensure the IP address and serial number are correct for your device. ```python from pysolarmanv5 import PySolarmanV5 modbus = PySolarmanV5("192.168.1.10", 123456789) print(modbus.read_input_registers(register_addr=33022, quantity=6)) ``` -------------------------------- ### Read Input Registers with PySolarmanV5 Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Use the read_input_registers method to fetch data from the Solarman V5 data logger. Specify the starting register address and the quantity of registers to read. This method is useful for retrieving current operational data from the inverter. ```python print(modbus.read_input_registers(register_addr=33022, quantity=6)) ``` -------------------------------- ### Read Input Registers (Function Code 4) Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5_async.html Reads a specified number of input registers starting from a given address. ```APIDOC ## GET /api/registers/input ### Description Reads input registers from a Modbus slave device. ### Method GET ### Endpoint /api/registers/input ### Parameters #### Query Parameters - **register_addr** (int) - Required - Modbus register start address. - **quantity** (int) - Required - Number of registers to query. ### Response #### Success Response (200) - **values** (list[int]) - List containing the values of the queried registers. #### Response Example ```json { "values": [10, 20, 30] } ``` ``` -------------------------------- ### Calculate V5 Frame Checksum Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Calculates the checksum for a V5 frame, excluding the start, end, and checksum bytes. This is used internally for frame integrity checks. ```python def _calculate_v5_frame_checksum(frame: bytes) -> int: """ Calculate checksum on all frame bytes except head, end and checksum :param frame: V5 frame for checksum calculation :type frame: bytes :return: Checksum value of V5 frame :rtype: int """ return PySolarmanV5._calculate_checksum(frame[1:-2]) ``` -------------------------------- ### Reconnect to Data Logger Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Attempts to re-establish a connection to the data logger. Closes the existing socket, and if auto-reconnect is enabled, creates a new socket and starts the data receiver thread. ```python def _reconnect(self): """ Reconnect to the data logger if needed """ # Close the old socket. Closing failures can be ignored and just logged. try: if self.sock: self.sock.shutdown(socket.SHUT_RDWR) self.sock.close() except Exception: # pylint: disable=broad-exception-caught self.log.debug("Closing socket failed", exc_info=True) finally: self.sock = None self._reader_exit.set() if self._auto_reconnect: self.log.debug( "Auto-Reconnect enabled. Trying to establish a new connection" ) if self._sock_fd: self._poll.unregister(self._sock_fd) self._sock_fd = None self.sock = self._create_socket() if self.sock: self._sock_fd = self.sock.fileno() self._reader_exit.clear() self._reader_thr = Thread(target=self._data_receiver, daemon=True) self._reader_thr.start() self.log.debug("Auto-Reconnect successful.") else: self.log.debug("No socket available! Reconnect failed.") self.sock = None else: self.log.debug("Auto-Reconnect inactive.") self.sock = None ``` -------------------------------- ### Validate Received V5 Frame Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Checks if a received V5 frame is valid by verifying its start bytes and sequence number against the last sent frame. ```python if not frame.startswith(self.v5_start): self.log.debug("[%s] V5_MISMATCH: %s", self.serial, frame.hex(" ")) return False if frame[5] != self.sequence_number: self.log.debug("[%s] V5_SEQ_NO_MISMATCH: %s", self.serial, frame.hex(" ")) return False return True ``` -------------------------------- ### Get Response Control Code Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html The _get_response_code static method converts a V5 request control code to its corresponding V5 response control code by subtracting 0x30. This is an internal utility for protocol handling. ```python @staticmethod def _get_response_code(code: int) -> int: """ Get response control code from request control code :param code: V5 request control code :type code: int :return: V5 response control code :rtype: int """ return code - 0x30 ``` -------------------------------- ### Read Discrete Inputs (Modbus Function Code 2) Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Reads discrete inputs from a Modbus slave and returns a list of their values. Requires the slave ID, starting register address, and quantity. ```python def read_discrete_inputs(self, register_addr, quantity): """Read discrete inputs from modbus slave and return list of input values (Modbus function code 2) :param register_addr: Modbus register start address :type register_addr: int :param quantity: Number of registers to query :type quantity: int :return: register values :rtype: list[int] """ mb_request_frame = rtu.read_discrete_inputs( self.mb_slave_id, register_addr, quantity ) ``` -------------------------------- ### Initialize and Read Registers with PySolarmanV5Async Source: https://pysolarmanv5.readthedocs.io/en/latest/api_reference.html Demonstrates how to instantiate the client, establish connections, and perform asynchronous register reads. ```python >>> import asyncio >>> from pysolarmanv5 import PySolarmanV5Async >>> modbus = PySolarmanV5Async("192.168.1.10", 123456789) >>> modbus2 = PySolarmanV5Async("192.168.1.11", 123456790) >>> loop = asyncio.get_event_loop() >>> loop.run_until_complete(asyncio.gather(*[modbus.connect(), modbus2.connect()], return_exceptions=True) >>> >>> print(loop.run_until_complete(modbus.read_input_registers(register_addr=33022, quantity=6))) >>> print(loop.run_until_complete(modbus2.read_input_registers(register_addr=33022, quantity=6))) ``` -------------------------------- ### Basic Synchronous Client Usage Source: https://pysolarmanv5.readthedocs.io/en/latest/examples.html Demonstrates how to initialize a synchronous PySolarmanV5 client and perform various register read operations. ```python 1"""A basic client demonstrating how to use pysolarmanv5.""" 2 3from pysolarmanv5 import PySolarmanV5 4 5 6def main(): 7 """Create new PySolarman instance, using IP address and S/N of data logger 8 9 Only IP address and S/N of data logger are mandatory parameters. If port, 10 mb_slave_id, and verbose are omitted, they will default to 8899, 1 and 0 11 respectively. 12 """ 13 modbus = PySolarmanV5( 14 "192.168.1.24", 123456789, port=8899, mb_slave_id=1, verbose=False 15 ) 16 17 """Query six input registers, results as a list""" 18 print(modbus.read_input_registers(register_addr=33022, quantity=6)) 19 20 """Query six holding registers, results as list""" 21 print(modbus.read_holding_registers(register_addr=43000, quantity=6)) 22 23 """Query single input register, result as an int""" 24 print(modbus.read_input_register_formatted(register_addr=33035, quantity=1)) 25 26 """Query single input register, apply scaling, result as a float""" 27 print( 28 modbus.read_input_register_formatted(register_addr=33035, quantity=1, scale=0.1) 29 ) 30 31 """Query two input registers, shift first register up by 16 bits, result as a signed int, """ 32 print( 33 modbus.read_input_register_formatted(register_addr=33079, quantity=2, signed=1) 34 ) 35 36 """Query single holding register, apply bitmask and bitshift left (extract bit1 from register)""" 37 print( 38 modbus.read_holding_register_formatted( 39 register_addr=43110, quantity=1, bitmask=0x2, bitshift=1 40 ) 41 ) 42 43 modbus.disconnect() 44 45 46if __name__ == "__main__": 47 main() ``` -------------------------------- ### Read Holding Registers (Function Code 3) Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5_async.html Reads a specified number of holding registers starting from a given address. ```APIDOC ## GET /api/registers/holding ### Description Reads holding registers from a Modbus slave device. ### Method GET ### Endpoint /api/registers/holding ### Parameters #### Query Parameters - **register_addr** (int) - Required - Modbus register start address. - **quantity** (int) - Required - Number of registers to query. ### Response #### Success Response (200) - **values** (list[int]) - List containing the values of the queried registers. #### Response Example ```json { "values": [100, 200, 300] } ``` ``` -------------------------------- ### Asynchronous Client Usage Source: https://pysolarmanv5.readthedocs.io/en/latest/examples.html Demonstrates how to initialize an asynchronous PySolarmanV5Async client and perform non-blocking register read operations. ```python 1"""A basic client demonstrating how to use the async version of pysolarmanv5.""" 2 3from pysolarmanv5 import PySolarmanV5Async 4import asyncio 5 6 7async def main(): 8 """Create new PySolarman instance, using IP address and S/N of data logger 9 10 Only IP address and S/N of data logger are mandatory parameters. If port, 11 mb_slave_id, and verbose are omitted, they will default to 8899, 1 and 0 12 respectively. 13 """ 14 modbus = PySolarmanV5Async( 15 "192.168.1.121", 16 1234567890, 17 port=8899, 18 mb_slave_id=1, 19 verbose=False, 20 auto_reconnect=True, 21 ) 22 await modbus.connect() 23 24 """Query six input registers, results as a list""" 25 print(await modbus.read_input_registers(register_addr=33022, quantity=6)) 26 27 """Query six holding registers, results as list""" 28 print(await modbus.read_holding_registers(register_addr=60, quantity=6)) 29 30 """Query single input register, result as an int""" 31 print(await modbus.read_input_register_formatted(register_addr=33035, quantity=1)) 32 33 """Query single input register, apply scaling, result as a float""" 34 35 print( 36 await modbus.read_input_register_formatted( 37 register_addr=33035, quantity=1, scale=0.1 38 ) 39 ) 40 41 """Query two input registers, shift first register up by 16 bits, result as a signed int, """ 42 print( 43 await modbus.read_input_register_formatted( 44 register_addr=33079, quantity=2, signed=1 45 ) 46 ) 47 48 """Query single holding register, apply bitmask and bitshift left (extract bit1 from register)""" 49 print( 50 await modbus.read_holding_register_formatted( 51 register_addr=500, quantity=1, bitmask=0x2, bitshift=1 52 ) 53 ) 54 55 await modbus.disconnect() 56 57 58if __name__ == "__main__": 59 asyncio.run(main()) ``` -------------------------------- ### Run Solarman TCP Proxy Source: https://pysolarmanv5.readthedocs.io/en/latest/utilities.html Displays help information for the TCP proxy utility. ```bash user@host:~ $ solarman-tcp-proxy -h usage: solarman tcp proxy [-h] [-b BIND] [-p PORT] -l LOGGER -s SERIAL A Modbus TCP Proxy for Solarman loggers options: -h, --help show this help message and exit -b, --bind BIND The address to listen on -p, --port PORT The TCP port to listen on -l, --logger LOGGER The IP address of the logger -s, --serial SERIAL The serial number of the logger ``` -------------------------------- ### Read Coils Source: https://pysolarmanv5.readthedocs.io/en/latest/api_reference.html Reads a specified number of coils from a Modbus slave starting at a given address. This corresponds to Modbus function code 1. ```APIDOC ## read_coils ### Description Reads coils from modbus slave and return list of coil values (Modbus function code 1). ### Method GET (or equivalent for Modbus) ### Endpoint Not applicable (function call) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```python read_coils(_register_addr_=1, _quantity_=10) ``` ### Response #### Success Response (200) - **register values** (list[int]) - A list containing the values of the queried coils. #### Response Example ```json [0, 1, 0, 1, 1, 0, 0, 1, 0, 1] ``` ``` -------------------------------- ### Run Solarman Scan Utility Source: https://pysolarmanv5.readthedocs.io/en/latest/utilities.html Displays help information for the network broadcast scanner. ```bash user@host:~ $ solarman-scan -h usage: solarman-scan [-h] broadcast Scanner for IGEN/Solarman dataloggers positional arguments: broadcast Network broadcast address options: -h, --help show this help message and exit ``` -------------------------------- ### Write Multiple Coils Source: https://pysolarmanv5.readthedocs.io/en/latest/api_reference.html Writes multiple coil values to a Modbus slave starting at a specified address. This corresponds to Modbus function code 15. ```APIDOC ## write_multiple_coils ### Description Writes multiple coil values to modbus slave (Modbus function code 15). ### Method POST (or equivalent for Modbus) ### Endpoint Not applicable (function call) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **register_addr** (int) - Required - Modbus register start address. - **values** (list[int]) - Required - Values to write; `1` (On) or `0` (Off). ### Request Example ```python write_multiple_coils(_register_addr_=10, _values_=[1, 0, 1, 1, 0]) ``` ### Response #### Success Response (200) - **values written** (list[int]) - The list of values that were written to the coils. #### Response Example ```json [1, 0, 1, 1, 0] ``` ``` -------------------------------- ### PySolarmanV5 Class Initialization Source: https://pysolarmanv5.readthedocs.io/en/latest/api_reference.html Initializes the PySolarmanV5 class to establish a TCP connection to a Solarman V5 data logging stick. ```APIDOC ## PySolarmanV5 Class ### Description The PySolarmanV5 class establishes a TCP connection to a Solarman V5 data logging stick and exposes methods to send/receive Modbus RTU requests and responses. ### Parameters - **address** (str) - IP address or hostname of data logging stick - **serial** (int) - Serial number of the data logging stick (not inverter!) - **port** (int, optional) - TCP port to connect to data logging stick, defaults to 8899 - **mb_slave_id** (int, optional) - Inverter Modbus slave ID, defaults to 1 - **socket_timeout** (int, optional) - Socket timeout duration in seconds, defaults to 60 - **v5_error_correction** (bool, optional) - Enable naive error correction for V5 frames, defaults to False - **logger** (Logger, optional) - Python logging facility - **socket** (Socket, optional) - TCP Socket connection to data logging stick. If **socket** argument is provided, **address** argument is unused (however, it is still required as a positional argument) - **auto_reconnect** (Boolean, optional) - Activates the auto-reconnect functionality. PySolarmanV5 will try to keep the connection open. The default is False. Not compatible with custom sockets. (Deprecated since version v2.4.0) - **verbose** (bool, optional) - Enable verbose logging, defaults to False. Use **logger** instead. For compatibility purposes, **verbose**, if enabled, will create a logger, and set the logging level to DEBUG. ### Raises - **NoSocketAvailableError** - If no network socket is available ### Basic Example ```python >>> from pysolarmanv5 import PySolarmanV5 >>> modbus = PySolarmanV5("192.168.1.10", 123456789) >>> print(modbus.read_input_registers(register_addr=33022, quantity=6)) ``` ``` -------------------------------- ### Run Solarman Decoder Source: https://pysolarmanv5.readthedocs.io/en/latest/utilities.html Displays help information for the frame decoding utility. ```bash user@host:~ $ solarman-decoder -h usage: solarman-decoder [-h] frame_hex [frame_hex ...] Decode a Solarman V5 frame positional arguments: frame_hex The bytes of the frame to decode in hexadecimal format e.g. a5 17 ... options: -h, --help show this help message and exit ``` -------------------------------- ### Read Discrete Inputs Source: https://pysolarmanv5.readthedocs.io/en/latest/api_reference.html Reads a specified number of discrete inputs from a Modbus slave starting at a given address. This corresponds to Modbus function code 2. ```APIDOC ## read_discrete_inputs ### Description Reads discrete inputs from modbus slave and return list of input values (Modbus function code 2). ### Method GET (or equivalent for Modbus) ### Endpoint Not applicable (function call) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```python read_discrete_inputs(_register_addr_=10, _quantity_=5) ``` ### Response #### Success Response (200) - **register values** (list[int]) - A list containing the values of the queried discrete inputs. #### Response Example ```json [1, 0, 1, 1, 0] ``` ``` -------------------------------- ### Initialize PySolarmanV5 Connection Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Instantiate the PySolarmanV5 class to establish a connection to a Solarman V5 data logger. Provide the IP address, serial number, and optional parameters like port, slave ID, and socket timeout. If a logger is not provided, a default logger is created. ```python from pysolarmanv5 import PySolarmanV5 modbus = PySolarmanV5("192.168.1.10", 123456789) ``` -------------------------------- ### Read Coils (Modbus Function Code 1) Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Reads coils from a Modbus slave and returns a list of their values. Requires the slave ID, starting register address, and quantity. ```python def read_coils(self, register_addr, quantity): """Read coils from modbus slave and return list of coil values (Modbus function code 1) :param register_addr: Modbus register start address :type register_addr: int :param quantity: Number of registers to query :type quantity: int :return: register values :rtype: list[int] """ mb_request_frame = rtu.read_coils(self.mb_slave_id, register_addr, quantity) modbus_values = self._get_modbus_response(mb_request_frame) return modbus_values ``` -------------------------------- ### Asynchronous Connection Management Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5_async.html Methods for establishing, maintaining, and closing connections to the Solarman V5 data logging stick. ```APIDOC ## connect() ### Description Establishes a TCP connection to the data logging stick. ### Method Async Method ### Response - **None** (None) - Returns None on success. - **Raises** (NoSocketAvailableError) - Raised when the connection cannot be established. ## reconnect() ### Description Attempts to reconnect to the data logging stick. If auto-reconnect is enabled, this is called automatically upon connection failure. ### Method Async Method ### Response - **None** (None) - Returns None on success. - **Raises** (NoSocketAvailableError) - Raised when the connection cannot be re-established. ## disconnect() ### Description Closes the active socket connection and signals the reader task to exit. ### Method Async Method ### Response - **None** (None) - Returns None on success. ``` -------------------------------- ### Helper Methods Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Utility methods for processing V5 protocol data. ```APIDOC ## Helper Methods ### `_get_response_code(code: int) -> int` #### Description Get response control code from request control code. #### Parameters - **code** (int) - V5 request control code #### Returns - **int** - V5 response control code ### `_calculate_checksum(data: bytes) -> int` #### Description Calculate checksum on all bytes. #### Parameters - **data** (bytes) - data for checksum calculation #### Returns - **int** - Checksum value of all bytes ``` -------------------------------- ### Get Modbus Response Values Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5_async.html Retrieves Modbus response values for a given Modbus RTU request frame. It handles potential double CRC errors by attempting to re-parse the response. ```python async def _get_modbus_response(self, mb_request_frame): """Returns mb response values for a given mb_request_frame :param mb_request_frame: Modbus RTU frame to parse :type mb_request_frame: bytes :return: Modbus RTU decoded values :rtype: list[int] """ mb_response_frame = await self._send_receive_modbus_frame(mb_request_frame) try: modbus_values = rtu.parse_response_adu(mb_response_frame, mb_request_frame) except struct.error as e: response = self._handle_double_crc(mb_response_frame) if len(response) != len(mb_response_frame): modbus_values = rtu.parse_response_adu(response, mb_request_frame) else: raise e return modbus_values ``` -------------------------------- ### Get Next Sequence Number Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Retrieves the next sequence number for outgoing packets. If no sequence number is set, it generates a random one. Otherwise, it increments the current sequence number. ```python def _get_next_sequence_number(self) -> int: """ Get the next sequence number for use in outgoing packets If ``sequence_number`` is None, generate a random int as initial value. :return: Sequence number :rtype: int """ if self.sequence_number is None: self.sequence_number = randrange(0x01, 0xFF) else: self.sequence_number = (self.sequence_number + 1) & 0xFF return self.sequence_number ``` -------------------------------- ### Run Solarman RTU Proxy Source: https://pysolarmanv5.readthedocs.io/en/latest/utilities.html Displays help information for the RTU over TCP proxy utility. ```bash user@host:~ $ solarman-rtu-proxy -h usage: solarman rtu proxy [-h] [-b BIND] [-p PORT] -l LOGGER -s SERIAL A Modbus RTU over TCP Proxy for Solarman loggers options: -h, --help show this help message and exit -b, --bind BIND The address to listen on -p, --port PORT The TCP port to listen on -l, --logger LOGGER The IP address of the logger -s, --serial SERIAL The serial number of the logger ``` -------------------------------- ### Create Socket Connection Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Initializes a new socket connection using the instance's address and port settings. ```python def _create_socket(self): """Creates and returns a socket""" try: sock = socket.create_connection( (self.address, self.port), self.socket_timeout ) except OSError: self.log.debug("Socket creation failed", exc_info=True) return None return sock ``` -------------------------------- ### PySolarmanV5 Methods Source: https://pysolarmanv5.readthedocs.io/en/latest/api_reference.html Methods available for interacting with the Solarman V5 data logging stick. ```APIDOC ## PySolarmanV5 Methods ### disconnect() Disconnect the socket and set a signal for the reader thread to exit. - **Returns**: None ### twos_complement(_val_, _num_bits_) Calculate 2s Complement. - **Parameters**: - **val** (int) - Value to calculate - **num_bits** (int) - Number of bits - **Returns**: 2s Complement value (int) ### read_input_registers(_register_addr_, _quantity_) Read input registers from modbus slave (Modbus function code 4). - **Parameters**: - **register_addr** (int) - Modbus register start address - **quantity** (int) - Number of registers to query - **Returns**: List containing register values (list[int]) ### read_holding_registers(_register_addr_, _quantity_) Read holding registers from modbus slave (Modbus function code 3). - **Parameters**: - **register_addr** (int) - Modbus register start address - **quantity** (int) - Number of registers to query - **Returns**: List containing register values (list[int]) ### read_input_register_formatted(_register_addr_, _quantity_, _**kwargs_) Read input registers from modbus slave and format as single value (Modbus function code 4). - **Parameters**: - **register_addr** (int) - Modbus register start address - **quantity** (int) - Number of registers to query - **scale** (int, optional) - Scaling factor - **signed** (bool, optional) - Signed value (2s complement) - **bitmask** (int, optional) - Bitmask value - **bitshift** (int, optional) - Bitshift value - **Returns**: Formatted register value (int) ### read_holding_register_formatted(_register_addr_, _quantity_, _**kwargs_) Read holding registers from modbus slave and format as single value (Modbus function code 3). - **Parameters**: - **register_addr** (int) - Modbus register start address - **quantity** (int) - Number of registers to query - **scale** (int, optional) - Scaling factor - **signed** (bool, optional) - Signed value (2s complement) - **bitmask** (int, optional) - Bitmask value - **bitshift** (int, optional) - Bitshift value - **Returns**: Formatted register value (int) ### write_holding_register(_register_addr_, _value_) Write a single holding register to modbus slave (Modbus function code 6). - **Parameters**: - **register_addr** (int) - Modbus register address - **value** (int) - value to write - **Returns**: value written (int) ### write_multiple_holding_registers(_register_addr_, _values_) Write list of multiple values to series of holding registers on modbus slave (Modbus function code 16). - **Parameters**: - **register_addr** (int) - Modbus register start address - **values** (list[int]) - values to write - **Returns**: values written (list[int]) ``` -------------------------------- ### Response Frame Format (Server) Source: https://pysolarmanv5.readthedocs.io/en/latest/solarmanv5_protocol.html Illustrates the general format of a server response frame in the Solarman V5 protocol. ```APIDOC ## Response Frame Format (Server) ### Description Illustrates the general format of a server response frame in the Solarman V5 protocol. ### Frame Diagram (Note: The provided text does not contain a detailed breakdown of the server response frame's fields, only a reference to a diagram. Therefore, a detailed markdown structure for parameters cannot be generated.) Any values shown in diagrams are in Network Byte Order. ``` -------------------------------- ### Read Holding Registers (Modbus Function Code 3) Source: https://pysolarmanv5.readthedocs.io/en/latest/api_reference.html This method reads a specified number of holding registers from a Modbus slave device, starting at a given address. It utilizes Modbus function code 3. Ensure the register address and quantity are valid for your device. ```python read_holding_registers(_register_addr_ , _quantity_) ``` -------------------------------- ### Socket Management Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Methods for creating, setting up, and managing network sockets for communication. ```APIDOC ## Socket Management ### Description This section covers the methods used for establishing and configuring network sockets required for communication with the solar monitoring device. It includes socket creation, setup, and error handling related to socket availability. ### Methods - `_create_socket() -> Optional[socket.socket]` - `_socket_setup(sock: Optional[socket.socket], auto_reconnect: bool)` ### Parameters #### `_create_socket` No parameters. #### `_socket_setup` - **sock** (Optional[socket.socket]) - An existing socket object to use, or None to create a new one. - **auto_reconnect** (bool) - Whether to automatically attempt to reconnect if the socket is lost. ### Returns - `_create_socket`: A newly created socket object, or None if creation fails. ### Raises - `NoSocketAvailableError`: If no socket can be created or provided during `_socket_setup`. ``` -------------------------------- ### Read Input Registers (Modbus Function Code 4) Source: https://pysolarmanv5.readthedocs.io/en/latest/api_reference.html This method reads a specified number of input registers from a Modbus slave device, starting at a given address. It utilizes Modbus function code 4. Ensure the register address and quantity are valid for your device. ```python read_input_registers(_register_addr_ , _quantity_) ``` -------------------------------- ### Connection Management Source: https://pysolarmanv5.readthedocs.io/en/latest/api_reference.html Methods for establishing and managing the TCP connection to the data logging stick. ```APIDOC ## connect() ### Description Connect to the data logging stick. ### Returns - None ### Raises - NoSocketAvailableError: When connection cannot be established ## reconnect() ### Description Reconnect to the data logging stick. Called automatically if the auto-reconnect option is enabled. ### Returns - None ## disconnect() ### Description Disconnect the socket and set a signal for the reader thread to exit. ### Returns - None ``` -------------------------------- ### Solarman V5 Header Details Source: https://pysolarmanv5.readthedocs.io/en/latest/solarmanv5_protocol.html Detailed breakdown of the 11-byte header, including its fields and their meanings. ```APIDOC ## Header The Header is always 11 bytes and is composed of: * **Start** (_one byte_) – Denotes the start of the V5 frame. Always `0xA5`. * **Length** (_two bytes_) – Payload length. * **Control Code** (_two bytes_) – Describes the type of V5 frame: * `HANDSHAKE` (`0x4110`): Used for initial handshake in server mode. * `DATA` (`0x4210`): Used for sending data in server mode. * `INFO` (`0x4310`): Used for sending stick firmware, IP, and SSID info in server mode. * `REQUEST` (`0x4510`): For Modbus RTU requests in client mode. * `RESPONSE` (`0x1510`): For Modbus RTU responses in client mode. * `HEARTBEAT` (`0x4710`): Keepalive packets in both modes. * `REPORT` (`0x4810`) _Responses are described as_ `request code - 0x3000` _which can be seen in Modbus RTU response - request pair:_ `0x4510 - 0x3000 = 0x1510` * **Sequence Number** (_two bytes_) – This field acts as a two-way sequence number. On outgoing requests, the first byte of this field is echoed back in the same position on incoming responses. pysolarmanv5 exploits this property to detect invalid responses. This is done by initializing this byte to a random value, and incrementing for each subsequent request. The second byte is incremented by the data logging stick for every response sent (either to Solarman Cloud or local requests). * **Logger Serial Number** (_four bytes_) – Serial number of the data logging stick. ``` -------------------------------- ### Write Multiple Holding Registers (Modbus Function Code 16) Source: https://pysolarmanv5.readthedocs.io/en/latest/api_reference.html Writes a list of values to a series of holding registers on the Modbus slave device using function code 16. Ensure the starting register address and the list of values are correct for your device's configuration. ```python write_multiple_holding_registers(_register_addr_ , _values_) ``` -------------------------------- ### Solarman V5 Frame Structure Source: https://pysolarmanv5.readthedocs.io/en/latest/solarmanv5_protocol.html Overview of the Solarman V5 frame composition, including Header, Payload, and Trailer. ```APIDOC ## Solarman V5 Frame Structure The Solarman V5 frame is composed of three parts: * **Header**: Contains metadata about the frame. * **Payload**: Includes the Modbus RTU frame and other data. * **Trailer**: Typically used for error checking or frame termination. All Solarman V5 fields are encoded Little Endian, with the exception of the Modbus RTU frame, which is encoded Big Endian. ``` -------------------------------- ### Run Solarman Unicast Scanner Source: https://pysolarmanv5.readthedocs.io/en/latest/utilities.html Executes a unicast scan on the specified network interface. ```bash user@host:~ $ solarman-uni-scan wlan0 ``` -------------------------------- ### Create V5 Time Response Frame Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Constructs a V5 time response frame. This is used to send the current time back to the data logger. ```python response_frame = self._v5_header( 10, self._get_response_code(frame[4]), frame[5:7] ) + bytearray( struct.pack(" bytearray: """ Construct V5 trailer :param data: data for checksum calculation :type data: bytes :return: V5 frame trailer :rtype: bytearray """ return bytearray( struct.pack(" int` - `_format_response(modbus_values: list[int], **kwargs) -> int` ### Parameters #### `twos_complement` - **val** (int) - The value to calculate the two's complement for. - **num_bits** (int) - The number of bits to consider for the two's complement calculation. #### `_format_response` - **modbus_values** (list[int]) - A list of Modbus register values (16 bits each). - **scale** (int, optional) - Scaling factor to apply to the formatted value. Defaults to 1. - **signed** (bool, optional) - Whether the value should be treated as signed (using two's complement). Defaults to False. - **bitmask** (int, optional) - A bitmask to apply to the formatted value. - **bitshift** (int, optional) - A bitshift to apply to the formatted value. ### Returns - `twos_complement`: The calculated two's complement value. - `_format_response`: The formatted register value as a single integer. ``` -------------------------------- ### Construct V5 Frame Header Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Constructs the header for a V5 frame, including length, control code, and sequence number. This is essential for initiating communication with the data logger. ```python def _v5_header(self, length: int, control: int, seq: bytes) -> bytearray: """ Construct V5 header :param length: V5 frame length :type length: int :param control: V5 control code :type control: int :param seq: V5 sequence number :type seq: bytes :return: V5 frame header :rtype: bytearray """ return bytearray( self.v5_start + struct.pack(" bytearray` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **modbus_frame** (bytes) - Required - The Modbus RTU frame to encode. ### Request Example ```python # Example usage (assuming instance of PySolarmanV5 class) modbus_data = b'\x01\x03\x00\x00\x00\x01\xc4\x0b' encoded_frame = instance._v5_frame_encoder(modbus_data) print(encoded_frame) ``` ### Response #### Success Response (bytearray) - **bytearray** - The V5 encoded frame. #### Response Example ```python # Example response structure (actual bytes will vary) b'\xa5\x00\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15' ``` ## V5 Frame Decoding ### Description Decodes a V5 data logging stick frame and returns a Modbus RTU frame. Performs validation checks on the V5 frame structure, checksum, sequence number, and control codes. ### Method `_v5_frame_decoder(v5_frame: bytes) -> bytearray` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **v5_frame** (bytes) - Required - The V5 frame to decode. ### Request Example ```python # Example usage (assuming instance of PySolarmanV5 class) v5_data = b'\xa5\x00\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15' modbus_frame = instance._v5_frame_decoder(v5_data) print(modbus_frame) ``` ### Response #### Success Response (bytearray) - **bytearray** - The decoded Modbus RTU frame. #### Response Example ```python # Example response structure (actual bytes will vary) b'\x01\x03\x00\x00\x00\x01\xc4\x0b' ``` ### Error Handling - **V5FrameError**: Raised if the V5 frame fails validation checks (e.g., invalid start/end bytes, incorrect checksum, mismatched sequence number, incorrect control code, incorrect frame type, or insufficient Modbus RTU frame length). ``` -------------------------------- ### Send and Receive V5 Frame Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5.html Sends a frame to the data logger and waits for a response. Handles potential connection issues and timeouts. ```python self.log.debug("[%s] SENT: %s", self.serial, frame.hex(" ")) if not self._reader_thr.is_alive(): raise NoSocketAvailableError("Connection already closed.") self.sock.sendall(frame) self._data_wanted.set() self._last_frame = frame response_frame = b"" try: response_frame = self._data_queue.get(timeout=self.socket_timeout) if response_frame == b"": raise NoSocketAvailableError("Connection closed on read") self._data_wanted.clear() except (queue.Empty, TimeoutError): self.log.debug("Got exception when receiving frame", exc_info=True) raise except OSError as exc: self.log.debug("Got exception when receiving frame", exc_info=True) if exc.errno == errno.EHOSTUNREACH: raise TimeoutError from exc raise self.log.debug("[%s] RECD: %s", self.serial, response_frame.hex(" ")) return response_frame ``` -------------------------------- ### POST /send_raw_modbus_frame_parsed Source: https://pysolarmanv5.readthedocs.io/en/latest/_modules/pysolarmanv5/pysolarmanv5_async.html Sends a raw Modbus frame and returns the parsed response as a list of integers. ```APIDOC ## POST /send_raw_modbus_frame_parsed ### Description Sends a raw Modbus frame and returns the parsed Modbus response list. ### Method POST ### Parameters #### Request Body - **mb_request_frame** (bytearray) - Required - The raw Modbus request frame. ### Response #### Success Response (200) - **response** (list[int]) - The decoded Modbus RTU values. ```