Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ __pycache__/
*.egg-info/
build/

dist/
dist/

*.code-workspace
87 changes: 81 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -165,20 +165,20 @@ type.

TCP
^^^
The following is how to open and initialize a TCP Device, where the slave ID is set to 1, the IP address of the TCP
The following is how to open and initialize a TCP Device, where the Unit ID is set to 1, the IP address of the TCP
device is
127.0.0.1, and the port is 8502::

>>> import sunspec2.modbus.client as client
>>> d = client.SunSpecModbusClientDeviceTCP(slave_id=1, ipaddr='127.0.0.1', ipport=8502)
>>> d = client.SunSpecModbusClientDeviceTCP(unit_id=1, ipaddr='127.0.0.1', ipport=8502)

RTU
^^^
The following to open and initialize a RTU Device, where the slave ID is set to 1, and the name of the serial port is
The following to open and initialize a RTU Device, where the Unit ID is set to 1, and the name of the serial port is
COM2::

>>> import sunspec2.modbus.client as client
>>> d = client.SunSpecModbusClientDeviceRTU(slave_id=1, name="COM2")
>>> d = client.SunSpecModbusClientDeviceRTU(unit_id=1, name="COM2")

Device Image
^^^^^^^^^^^^
Expand Down Expand Up @@ -209,7 +209,7 @@ model ID. The first key is the model ID as an int, the second key is the model n
that a device may contain more than one model with the same model ID, the dictionary keys refer to a list of model
objects with that ID. Both keys refer to the same model list for a model ID.

>>> d = client.SunSpecModbusClientDeviceTCP(slave_id=1, ipaddr='127.0.0.1', ipport=8502)
>>> d = client.SunSpecModbusClientDeviceTCP(unit_id=1, ipaddr='127.0.0.1', ipport=8502)
>>> d.scan()


Expand Down Expand Up @@ -261,6 +261,81 @@ After assigning the value on the point object, "Ena", write() must be called in
consider it a good Modbus practice to read after every write to check if the operation was successful, but it is not
required. In this example, we perform a read() after a write().

Accessing Multiple Unit IDs (Single Connection)
-----------------------------------------------
For devices that support multiple unit IDs through a single connection, you can use the
``scan_units()`` method to discover SunSpec models on multiple units, and then access them through the ``Units`` collection.

This is particularly useful for devices that only allow one TCP connection but have multiple devices connected
via serial with different unit IDs.

Scanning Multiple Unit IDs
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Use ``scan_units(unit_ids)`` to discover SunSpec models on multiple unit IDs: ::

>>> import sunspec2.modbus.client as client
>>> d = client.SunSpecModbusClientDeviceTCP(unit_id=1, ipaddr='192.168.1.100', ipport=502)
>>> d.connect()

# Scan multiple units for SunSpec models
>>> d.scan_units([1, 2, 3])

# Access models from the default unit (unit_id=1) directly
>>> manufacturer = d.common[0].Mn.value
>>> power = d.inverter[0].W.value

# Access models from specific units via Units collection
>>> unit1_common = d.Units[1].common[0]
>>> unit2_inverter = d.Units[2].inverter[0]
>>> unit3_meter = d.Units[3].meter[0]

# Both approaches work for the default unit
>>> assert d.common[0].Mn.value == d.Units[1].common[0].Mn.value

Reading from Different Unit IDs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can also read raw register data from specific unit IDs using ``read_unit(unit_id, addr, count)``: ::

# Read raw register data from different units
>>> data1 = d.read(40000, 10) # Default unit (unit_id=1)
>>> data2 = d.read_unit(2, 40000, 10) # Unit ID 2
>>> data3 = d.read_unit(3, 40000, 10) # Unit ID 3

Writing to Different Unit IDs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Use ``write_unit(unit_id, addr, data)`` to write to a specific unit ID: ::

>>> # Write to unit_id=2
>>> d.write_unit(2, 40100, b'\\x00\\x01\\x00\\x02')

# Write to unit_id=3
>>> d.write_unit(3, 40100, b'\\x00\\x03\\x00\\x04')

Example: Multi-Unit Setup
^^^^^^^^^^^^^^^^^^^^^^^^^
Here's a complete example for accessing multiple units through a single TCP connection: ::

>>> import sunspec2.modbus.client as client
>>>
>>> # Create a single TCP connection to the device gateway
>>> d = client.SunSpecModbusClientDeviceTCP(unit_id=1, ipaddr='192.168.1.100', ipport=502)
>>> d.connect()
>>>
>>> # Read SunSpec identifier from multiple units
>>> unit1_sunspec = d.read(40000, 2) # Default unit_id=1
>>> unit2_sunspec = d.read_unit(2, 40000, 2) # Unit ID 2
>>> unit3_sunspec = d.read_unit(3, 40000, 2) # Unit ID 3
>>>
>>> print(f"Unit 1 SunSpec ID: {unit1_sunspec}")
>>> print(f"Unit 2 SunSpec ID: {unit2_sunspec}")
>>> print(f"Unit 3 SunSpec ID: {unit3_sunspec}")
>>>
>>> # Close the connection when done
>>> d.close()

The ``read_unit()`` and ``write_unit()`` methods are available for both TCP and RTU device types, providing consistent
functionality across different connection types.

Additional Information
----------------------
The groups and points in a group are contained in ordered groups and points dictionaries if needed. Repeating groups are
Expand Down Expand Up @@ -296,7 +371,7 @@ This section will go over the full steps on how to set a volt-var curve.

Initialize device, and run device discovery with scan(): ::

>>> d = client.SunSpecModbusClientDeviceRTU(slave_id=1, name="COM2")
>>> d = client.SunSpecModbusClientDeviceRTU(unit_id=1, name="COM2")
>>> d.scan()

Confirm that model 705 (DERVoltVar) is on the device: ::
Expand Down
8 changes: 4 additions & 4 deletions scripts/suns.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
-o: output mode for data (text, xml)
-x: export model description (slang, xml)
-t: transport type: tcp or rtu (default: tcp)
-a: modbus slave address (default: 1)
-a: modbus unit identifier (default: 1)
-i: ip address to use for modbus tcp (default: localhost)
-P: port number for modbus tcp (default: 502)
-p: serial port for modbus rtu (default: /dev/ttyUSB0)
Expand Down Expand Up @@ -46,7 +46,7 @@
help='transport type: rtu, tcp, file [default: tcp]')
parser.add_option('-a', metavar=' ', type='int',
default=1,
help='modbus slave address [default: 1]')
help='modbus unit identifier [default: 1]')
parser.add_option('-i', metavar=' ',
default='localhost',
help='ip address to use for modbus tcp [default: localhost]')
Expand All @@ -72,10 +72,10 @@

try:
if options.t == 'tcp':
sd = client.SunSpecModbusClientDeviceTCP(slave_id=options.a, ipaddr=options.i, ipport=options.P,
sd = client.SunSpecModbusClientDeviceTCP(unit_id=options.a, ipaddr=options.i, ipport=options.P,
timeout=options.T)
elif options.t == 'rtu':
sd = client.SunSpecModbusClientDeviceRTU(slave_id=options.a, name=options.p, baudrate=options.b,
sd = client.SunSpecModbusClientDeviceRTU(unit_id=options.a, name=options.p, baudrate=options.b,
parity=options.R, timeout=options.T)
elif options.t == 'file':
sd = file_client.FileClientDevice(filename=options.m)
Expand Down
90 changes: 84 additions & 6 deletions sunspec2/docs/pysunspec.rst
Original file line number Diff line number Diff line change
Expand Up @@ -146,20 +146,20 @@ type.

TCP
^^^
The following is how to open and initialize a TCP Device, where the slave ID is set to 1, the IP address of the TCP
The following is how to open and initialize a TCP Device, where the Unit ID is set to 1, the IP address of the TCP
device is
127.0.0.1, and the port is 8502::

>>> import sunspec2.modbus.client as client
>>> d = client.SunSpecModbusClientDeviceTCP(slave_id=1, ipaddr='127.0.0.1', ipport=8502)
>>> d = client.SunSpecModbusClientDeviceTCP(unit_id=1, ipaddr='127.0.0.1', ipport=8502)

RTU
^^^
The following to open and initialize a RTU Device, where the slave ID is set to 1, and the name of the serial port is
The following to open and initialize a RTU Device, where the Unit ID is set to 1, and the name of the serial port is
COM2::

>>> import sunspec2.modbus.client as client
>>> d = client.SunSpecModbusClientDeviceRTU(slave_id=1, name="COM2")
>>> d = client.SunSpecModbusClientDeviceRTU(unit_id=1, name="COM2")

Device Image
^^^^^^^^^^^^
Expand Down Expand Up @@ -190,7 +190,7 @@ model ID. The first key is the model ID as an int, the second key is the model n
that a device may contain more than one model with the same model ID, the dictionary keys refer to a list of model
objects with that ID. Both keys refer to the same model list for a model ID.

>>> d = client.SunSpecModbusClientDeviceTCP(slave_id=1, ipaddr='127.0.0.1', ipport=8502)
>>> d = client.SunSpecModbusClientDeviceTCP(unit_id=1, ipaddr='127.0.0.1', ipport=8502)
>>> d.scan()


Expand Down Expand Up @@ -242,6 +242,84 @@ After assigning the value on the point object, "Ena", write() must be called in
consider it a good Modbus practice to read after every write to check if the operation was successful, but it is not
required. In this example, we perform a read() after a write().

Accessing Multiple Unit IDs (Single Connection)
-----------------------------------------------
For devices that support multiple unit IDs through a single connection, you can use the
``scan_units()`` method to discover SunSpec models on multiple units, and then access them through the ``Units`` collection.

This is particularly useful for devices that only allow one TCP connection but have multiple devices connected
via serial with different unit IDs.

Scanning Multiple Unit IDs
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Use ``scan_units(unit_ids)`` to discover SunSpec models on multiple unit IDs: ::

>>> import sunspec2.modbus.client as client
>>> d = client.SunSpecModbusClientDeviceTCP(unit_id=1, ipaddr='192.168.1.100', ipport=502)
>>> d.connect()

# Scan multiple units for SunSpec models
>>> d.scan_units([1, 2, 3])

# Access models from the default unit (unit_id=1) directly
>>> manufacturer = d.common[0].Mn.value
>>> power = d.inverter[0].W.value

# Access models from specific units via Units collection
>>> unit1_common = d.Units[1].common[0]
>>> unit2_inverter = d.Units[2].inverter[0]
>>> unit3_meter = d.Units[3].meter[0]

# Both approaches work for the default unit
>>> assert d.common[0].Mn.value == d.Units[1].common[0].Mn.value

Reading from Different Unit IDs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can also read raw register data from specific unit IDs using ``read_unit(unit_id, addr, count)``: ::

# Read raw register data from different units
>>> data1 = d.read(40000, 10) # Default unit (unit_id=1)
>>> data2 = d.read_unit(2, 40000, 10) # Unit ID 2
>>> data3 = d.read_unit(3, 40000, 10) # Unit ID 3

Writing to Different Unit IDs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Use ``write_unit(unit_id, addr, data)`` to write to a specific unit ID: ::

>>> # Write to unit_id=2
>>> d.write_unit(2, 40100, b'\\x00\\x01\\x00\\x02')

# Write to unit_id=3
>>> d.write_unit(3, 40100, b'\\x00\\x03\\x00\\x04')

Example: Multi-Unit Setup
^^^^^^^^^^^^^^^^^^^^^^^^^
Here's a complete example for accessing multiple units through a single TCP connection: ::

>>> import sunspec2.modbus.client as client
>>>
>>> # Create a single TCP connection to the device gateway
>>> d = client.SunSpecModbusClientDeviceTCP(unit_id=1, ipaddr='192.168.1.100', ipport=502)
>>> d.connect()
>>>
>>> # Scan multiple units for SunSpec models
>>> d.scan_units([1, 2, 3])
>>>
>>> # Access models from the default unit directly
>>> print(f"Default unit manufacturer: {d.common[0].Mn.value}")
>>> print(f"Default unit power: {d.inverter[0].W.value}")
>>>
>>> # Access models from specific units
>>> print(f"Unit 1 manufacturer: {d.Units[1].common[0].Mn.value}")
>>> print(f"Unit 2 power: {d.Units[2].inverter[0].W.value}")
>>> print(f"Unit 3 energy: {d.Units[3].meter[0].TotWhExp.value}")
>>>
>>> # Close the connection when done
>>> d.close()

The ``scan_units()``, ``read_unit()`` and ``write_unit()`` methods are available for both TCP and RTU device types, providing consistent
functionality across different connection types.

Additional Information
----------------------
The groups and points in a group are contained in ordered groups and points dictionaries if needed. Repeating groups are
Expand Down Expand Up @@ -277,7 +355,7 @@ This section will go over the full steps on how to set a volt-var curve.

Initialize device, and run device discovery with scan(): ::

>>> d = client.SunSpecModbusClientDeviceRTU(slave_id=1, name="COM2")
>>> d = client.SunSpecModbusClientDeviceRTU(unit_id=1, name="COM2")
>>> d.scan()

Confirm that model 705 (DERVoltVar) is on the device: ::
Expand Down
Loading