Skip to content
Merged
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
3 changes: 2 additions & 1 deletion pymodbus/client/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from ..pdu import mei_message as pdu_mei
from ..pdu import other_message as pdu_other_msg
from ..pdu import register_message as pdu_reg
from ..pdu.pdu import ModbusPDU, pack_bitstring, unpack_bitstring
from ..pdu.pdu import ModbusPDU
from ..pdu.utils import pack_bitstring, unpack_bitstring


T = TypeVar("T", covariant=False)
Expand Down
91 changes: 54 additions & 37 deletions pymodbus/datastore/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
from ..constants import ExcCodes
from ..exceptions import NoSuchIdException
from ..logging import Log
from ..simulator.simdata import DataType
from ..simulator.simdevice import SimDevice
from .sequential import ModbusSequentialDataBlock
from .simulator import ModbusSimulatorContext
from .sparse import ModbusSparseDataBlock


# pylint: disable=missing-type-doc

class ModbusDeviceContext:
class ModbusDeviceContext: # pylint: disable=too-few-public-methods
"""Create a modbus data model with data stored in a block.

:param di: discrete inputs initializer ModbusDataBlock
Expand All @@ -37,33 +38,27 @@ def __init__(self, *_args,
"i": ir,
"h": hr,
}

async def async_OLD_getValues(self, func_code, address, count=1) -> list[int] | list[bool] | ExcCodes:
"""Get `count` values from datastore.

:param func_code: The function we are working with
:param address: The starting address
:param count: The number of values to retrieve
:returns: The requested values from a:a+c
"""
address += 1
Log.debug("getValues: fc-[{}] address-{}: count-{}", func_code, address, count)
if dt := self.store[self._fx_mapper.get(func_code, "x")]:
return await dt.async_OLD_getValues(address, count)
return ExcCodes.ILLEGAL_ADDRESS

async def async_OLD_setValues(self, func_code, address, values) -> None | ExcCodes:
"""Set the datastore with the supplied values.

:param func_code: The function we are working with
:param address: The starting address
:param values: The new values to be set
"""
address += 1
Log.debug("setValues[{}] address-{}: count-{}", func_code, address, len(values))
if dt := self.store[self._fx_mapper.get(func_code, "x")]:
return await dt.async_OLD_setValues(address, values)
return ExcCodes.ILLEGAL_ADDRESS
if not di:
di = ModbusSequentialDataBlock(0, values=0)
if not co:
co = ModbusSequentialDataBlock(0, values=0)
if not ir:
ir = ModbusSequentialDataBlock(0, values=0)
if not hr:
hr = ModbusSequentialDataBlock(0, values=0)
for entry in di.simdata:
entry.datatype = DataType.BITS
for entry in co.simdata:
entry.datatype = DataType.BITS
self.simdevice = SimDevice(0, simdata=(
di.simdata,
co.simdata,
ir.simdata,
hr.simdata))
Log.warning("ModbusDeviceContext is depreacated "
"and will be removed in v4.\n"
"Please convert to SimData/SimDevice.\n"
"Please read https://pymodbus.readthedocs.io/en/dev/source/upgrade_40.html#convert-to-simdata-simdevice")


class ModbusServerContext:
Expand All @@ -79,12 +74,30 @@ def __init__(self, devices=None, single=True):
"""Initialize a new instance of a modbus server context.

:param devices: A dictionary of client contexts
:param single: Set to true to treat this as a single context
:param single: Deprecated

dev_id=0 is automatically used when devices= is a ModbusDeviceContext
and not a dict.
"""
self.single = single
self._devices: dict = devices or {}
if self.single:
self._devices = {0: self._devices}
_ = single
if not devices:
raise TypeError("devices= cannot be None")
self._devices: dict[int, ModbusDeviceContext]
self.simdevices: list[SimDevice] = []
if isinstance(devices, dict):
self._devices = devices
for dev_id, entry in devices.items():
if not isinstance(entry, ModbusSimulatorContext):
entry.id = dev_id
self.simdevices.append(entry)
else:
self._devices = {0: devices}
if not isinstance(devices, ModbusSimulatorContext):
self.simdevices = [devices.simdevice]
Log.warning("ModbusServerContext is depreacated "
"and will be removed in v4.\n"
"Please convert to SimData/SimDevice.\n"
"Please read https://pymodbus.readthedocs.io/en/dev/source/upgrade_40.html#convert-to-simdata-simdevice")

def __get_device(self, device_id: int) -> ModbusDeviceContext:
"""Return device object."""
Expand All @@ -106,7 +119,9 @@ async def async_getValues(self, device_id: int, func_code: int, address: int, co
:returns: The requested values from a:a+c
"""
dev = self.__get_device(device_id)
return await dev.async_OLD_getValues(func_code, address, count)
if isinstance(dev, ModbusSimulatorContext):
return await dev.async_OLD_getValues(func_code, address, count)
return ExcCodes.DEVICE_BUSY

async def async_setValues(self, device_id: int, func_code: int, address: int, values: list[int] | list[bool] ) -> None | ExcCodes:
"""Set the datastore with the supplied values.
Expand All @@ -117,7 +132,9 @@ async def async_setValues(self, device_id: int, func_code: int, address: int, va
:param values: The new values to be set
"""
dev = self.__get_device(device_id)
return await dev.async_OLD_setValues(func_code, address, values)
if isinstance(dev, ModbusSimulatorContext): # pragma: no cover
return await dev.async_OLD_setValues(func_code, address, values)
return ExcCodes.DEVICE_BUSY

def device_ids(self):
"""Get the configured device ids."""
Expand Down
43 changes: 8 additions & 35 deletions pymodbus/datastore/sequential.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Modbus Sequential Datastore."""
# pylint: disable=missing-type-doc
from __future__ import annotations

from ..constants import ExcCodes
from ..logging import Log
from ..simulator.simdata import DataType, SimData


class ModbusSequentialDataBlock:
class ModbusSequentialDataBlock: # pylint: disable=too-few-public-methods
"""Creates a sequential modbus datastore."""

def __init__(self, address, values):
Expand All @@ -14,35 +14,8 @@ def __init__(self, address, values):
:param address: The starting address of the datastore
:param values: Either a list or a dictionary of values
"""
self.address = address
if hasattr(values, "__iter__"):
self.values = list(values)
else:
self.values = [values]

async def async_OLD_getValues(self, address, count=1) -> list[int] | list[bool] | ExcCodes:
"""Return the requested values of the datastore.

:param address: The starting address
:param count: The number of values to retrieve
:returns: The requested values from a:a+c
"""
start = address - self.address
if start < 0 or len(self.values) < start+count:
return ExcCodes.ILLEGAL_ADDRESS
return self.values[start : start + count]

async def async_OLD_setValues(self, address, values) -> None | ExcCodes:
"""Set the requested values of the datastore.

:param address: The starting address
:param values: The new values to be set
"""
if not isinstance(values, list):
values = [values]
start = address - self.address
if start < 0 or len(self.values) < start+len(values):
return ExcCodes.ILLEGAL_ADDRESS
self.values[start : start + len(values)] = values
return None

Log.warning("ModbusSequentialDataBlock is depreacated "
"and will be removed in v4.\n"
"Please convert to SimData/SimDevice.\n"
"Please read https://pymodbus.readthedocs.io/en/dev/source/upgrade_40.html#convert-to-simdata-simdevice")
self.simdata = [SimData(address, values=values, datatype=DataType.REGISTERS)]
5 changes: 5 additions & 0 deletions pymodbus/datastore/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Any

from ..constants import ExcCodes
from ..logging import Log


WORD_SIZE = 16
Expand Down Expand Up @@ -487,6 +488,10 @@ def __init__(
self.registerType_id_to_name: list[str] = []
if config:
Setup(self).setup(config, custom_actions)
Log.warning("ModbusSimulatorContext is depreacated "
"and will be removed in v4.\n"
"Please convert to SimData/SimDevice.\n"
"Please read https://pymodbus.readthedocs.io/en/dev/source/upgrade_40.html#convert-to-simdata-simdevice")

# --------------------------------------------
# Simulator server interface
Expand Down
63 changes: 10 additions & 53 deletions pymodbus/datastore/sparse.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,30 @@
"""Modbus Sparse Datastore."""
# pylint: disable=missing-type-doc
from __future__ import annotations

from ..constants import ExcCodes
from ..exceptions import ParameterException
from ..logging import Log
from ..simulator.simdata import DataType, SimData


class ModbusSparseDataBlock:
class ModbusSparseDataBlock: # pylint: disable=too-few-public-methods
"""A sparse modbus datastore, silently redirected to ModbusSequentialBlock."""

def __init__(self, values=None, mutable=True):
"""Initialize a sparse datastore."""
self.values: dict[int, list[int]] = {}
_ = mutable
self.simdata: list[SimData] = []
self._process_values(values)
self.mutable = mutable

async def async_OLD_getValues(self, address, count=1) -> list[int] | list[bool] | ExcCodes:
"""Return the requested values of the datastore.
:param address: The starting address
:param count: The number of values to retrieve
:returns: The requested values from a:a+c
"""
try:
values = [self.values[i] for i in range(address, address + count)]
except KeyError:
return ExcCodes.ILLEGAL_ADDRESS
return values # type: ignore[return-value]
Log.warning("ModbusSparseDataBlock is depreacated "
"and will be removed in v4.\n"
"Please convert to SimData/SimDevice.\n"
"Please read https://pymodbus.readthedocs.io/en/dev/source/upgrade_40.html#convert-to-simdata-simdevice")

def _process_values(self, values):
"""Process values."""

def _process_as_dict(values):
for idx, val in iter(values.items()):
if isinstance(val, (list, tuple)):
for i, v_item in enumerate(val):
self.values[idx + i] = v_item
else:
self.values[idx] = int(val) # type: ignore[assignment]
self.simdata.append(SimData(idx, values=val, datatype=DataType.REGISTERS))

if isinstance(values, dict):
_process_as_dict(values)
Expand All @@ -52,33 +39,3 @@ def _process_as_dict(values):
)
_process_as_dict(values)

async def async_OLD_setValues(self, address, values) -> None | ExcCodes:
"""Set the requested values of the datastore.
:param address: The register starting address
:param values: The new values to be set.
Values can be given in different formats:
- a single register value or
- a list or tuple of contiguous register values, starting at
given starting register address or
- a dictionary of address:value(s) pairs, where value can be a
single register or a list or tuple of contiguous registers.
"""
try:
if isinstance(values, dict):
new_offsets = list(set(values.keys()) - set(self.values.keys()))
if new_offsets and not self.mutable:
raise ParameterException(f"Offsets {new_offsets} not in range")
self._process_values(values)
else:
if not isinstance(values, (list, tuple)):
values = [values]
for idx, val in enumerate(values):
if address + idx not in self.values and not self.mutable:
raise ParameterException("Offset {address+idx} not in range")
self.values[address + idx] = val
except KeyError:
return ExcCodes.ILLEGAL_ADDRESS
return None

3 changes: 2 additions & 1 deletion pymodbus/pdu/bit_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from ..datastore import ModbusServerContext
from .decoders import DecodePDU
from .exceptionresponse import ExceptionResponse
from .pdu import ModbusPDU, pack_bitstring, unpack_bitstring
from .pdu import ModbusPDU
from .utils import pack_bitstring, unpack_bitstring


class ReadCoilsRequest(ModbusPDU):
Expand Down
3 changes: 2 additions & 1 deletion pymodbus/pdu/diag_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from ..datastore import ModbusServerContext
from .decoders import DecodePDU
from .device import ModbusControlBlock
from .pdu import ModbusPDU, pack_bitstring
from .pdu import ModbusPDU
from .utils import pack_bitstring


_MCB = ModbusControlBlock()
Expand Down
2 changes: 1 addition & 1 deletion pymodbus/pdu/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from abc import ABC, abstractmethod

from ..exceptions import ParameterException
from .pdu import pack_bitstring, unpack_bitstring
from .utils import pack_bitstring, unpack_bitstring


class ModbusEvent(ABC):
Expand Down
49 changes: 0 additions & 49 deletions pymodbus/pdu/pdu.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from __future__ import annotations

import asyncio
import struct

from ..datastore import ModbusServerContext
from ..exceptions import ModbusIOException, NotImplementedException
Expand Down Expand Up @@ -107,51 +106,3 @@ def calculateRtuFrameSize(cls, data: bytes) -> int:
raise NotImplementedException(
f"Cannot determine RTU frame size for {cls.__name__}"
)


def pack_bitstring(bits: list[bool], align_byte=True) -> bytes:
"""Create a bytestring out of a list of bits.

example::

bits = [True, False, False, False] +
[False, False, False, True] +
[True, False, True, False] +
[False, False, False, False]
result = pack_bitstring(bits)
bytes 0x05 0x81
"""
ret = b""
i = packed = 0
t_bits = bits
bits_extra = 8 if align_byte else 16
if (extra := len(bits) % bits_extra):
t_bits += [False] * (bits_extra - extra)
for byte_inx in range(0, len(t_bits), 8):
for bit in reversed(t_bits[byte_inx:byte_inx+8]):
packed <<= 1
if bit:
packed += 1
i += 1
if i == 8:
ret += struct.pack(">B", packed)
i = packed = 0
return ret


def unpack_bitstring(data: bytes) -> list[bool]:
"""Create bit list out of a bytestring.

example::

bytes 0x05 0x81
result = unpack_bitstring(bytes)

[True, False, True, False] + [False, False, False, False]
[True, False, False, False] + [False, False, False, True]
"""
res = []
for _, t_byte in enumerate(data):
for bit in (1, 2, 4, 8, 16, 32, 64, 128):
res.append(bool(t_byte & bit))
return res
Loading