@@ -1354,6 +1354,12 @@ class DB:
13541354
13551355 Probably most usecases there is just one row
13561356
1357+ Note:
1358+ This class has some of the semantics of a dict. In particular, the membership operators
1359+ (``in``, ``not it``), the access operator (``[]``), as well as the :func:`~DB.keys()` and
1360+ :func:`~DB.items()` methods work as usual. Iteration, on the other hand, happens on items
1361+ instead of keys (much like :func:`~DB.items()` method).
1362+
13571363 Attributes:
13581364 bytearray_: buffer data from the PLC.
13591365 specification: layout of the DB Rows.
@@ -1364,14 +1370,14 @@ class DB:
13641370
13651371 Examples:
13661372 >>> db1[0]['testbool1'] = test
1367- >>> db1.write() # puts data in plc
1373+ >>> db1.write(client ) # puts data in plc
13681374 """
13691375 bytearray_ : Optional [bytearray ] = None # data from plc
1370- specification : Optional [str ] = None # layout of db rows
1371- row_size : Optional [int ] = None # bytes size of a db row
1372- layout_offset : Optional [ int ] = None # at which byte in row specification should
1373- # we start reading the data
1374- db_offset : Optional [ int ] = None # at which byte in db should we start reading?
1376+ specification : Optional [str ] = None # layout of db rows
1377+ id_field : Optional [str ] = None # ID field of the rows
1378+ row_size : int = 0 # bytes size of a db row
1379+ layout_offset : int = 0 # at which byte in row specification should
1380+ db_offset : int = 0 # at which byte in db should we start reading?
13751381
13761382 # first fields could be be status data.
13771383 # and only the last part could be control data
@@ -1380,8 +1386,8 @@ class DB:
13801386
13811387 def __init__ (self , db_number : int , bytearray_ : bytearray ,
13821388 specification : str , row_size : int , size : int , id_field : Optional [str ] = None ,
1383- db_offset : Optional [ int ] = 0 , layout_offset : Optional [ int ] = 0 , row_offset : Optional [ int ] = 0 ,
1384- area : Optional [ Areas ] = Areas .DB ):
1389+ db_offset : int = 0 , layout_offset : int = 0 , row_offset : int = 0 ,
1390+ area : Areas = Areas .DB ):
13851391 """ Creates a new instance of the `Row` class.
13861392
13871393 Args:
@@ -1415,7 +1421,7 @@ def __init__(self, db_number: int, bytearray_: bytearray,
14151421 self .make_rows ()
14161422
14171423 def make_rows (self ):
1418- """ Make each row for the DB. """
1424+ """ Make each row for the DB."""
14191425 id_field = self .id_field
14201426 row_size = self .row_size
14211427 specification = self .specification
@@ -1442,14 +1448,66 @@ def make_rows(self):
14421448 self .index [key ] = row
14431449
14441450 def __getitem__ (self , key : str , default : Optional [None ] = None ) -> Union [None , int , float , str , bool , datetime ]:
1451+ """Access a row of the table through its index.
1452+
1453+ Rows (values) are of type :class:`DB_Row`.
1454+
1455+ Notes:
1456+ This method has the same semantics as :class:`dict` access.
1457+ """
14451458 return self .index .get (key , default )
14461459
14471460 def __iter__ (self ):
1461+ """Iterate over the items contained in the table, in the physical order they are contained
1462+ in memory.
1463+
1464+ Notes:
1465+ This method does not have the same semantics as :class:`dict` iteration. Instead, it
1466+ has the same semantics as the :func:`~DB.items` method, yielding ``(index, row)``
1467+ tuples.
1468+ """
14481469 yield from self .index .items ()
14491470
14501471 def __len__ (self ):
1472+ """Return the number of rows contained in the DB.
1473+
1474+ Notes:
1475+ If more than one row has the same index value, it is only counted once.
1476+ """
14511477 return len (self .index )
14521478
1479+ def __contains__ (self , key ):
1480+ """Return whether the given key is the index of a row in the DB."""
1481+ return key in self .index
1482+
1483+ def keys (self ):
1484+ """Return a *view object* of the keys that are used as indices for the rows in the
1485+ DB.
1486+ """
1487+ yield from self .index .keys ()
1488+
1489+ def items (self ):
1490+ """Return a *view object* of the items (``(index, row)`` pairs) that are used as indices
1491+ for the rows in the DB.
1492+ """
1493+ yield from self .index .items ()
1494+
1495+ def export (self ):
1496+ """Export the object to an :class:`OrderedDict`, where each item in the dictionary
1497+ has an index as the key, and the value of the DB row associated with that index
1498+ as a value, represented itself as a :class:`dict` (as returned by :func:`DB_Row.export`).
1499+
1500+ The outer dictionary contains the rows in the physical order they are contained in
1501+ memory.
1502+
1503+ Notes:
1504+ This function effectively returns a snapshot of the DB.
1505+ """
1506+ ret = OrderedDict ()
1507+ for (k , v ) in self .items ():
1508+ ret [k ] = v .export ()
1509+ return ret
1510+
14531511 def set_data (self , bytearray_ : bytearray ):
14541512 """Set the new buffer data from the PLC to the current instance.
14551513
@@ -1463,6 +1521,63 @@ def set_data(self, bytearray_: bytearray):
14631521 raise TypeError (f"Value bytearray_: { bytearray_ } is not from type bytearray" )
14641522 self ._bytearray = bytearray_
14651523
1524+ def read (self , client : Client ):
1525+ """Reads all the rows from the PLC to the :obj:`bytearray` of this instance.
1526+
1527+ Args:
1528+ client: :obj:`Client` snap7 instance.
1529+
1530+ Raises:
1531+ :obj:`ValueError`: if the `row_size` is less than 0.
1532+ """
1533+ if self .row_size < 0 :
1534+ raise ValueError ("row_size must be greater equal zero." )
1535+
1536+ total_size = self .size * (self .row_size + self .row_offset )
1537+ if self .area == Areas .DB : # note: is it worth using the upload method?
1538+ bytearray_ = client .db_read (self .db_number , self .db_offset , total_size )
1539+ else :
1540+ bytearray_ = client .read_area (self .area , 0 , self .db_offset , total_size )
1541+
1542+ # replace data in bytearray
1543+ for i , b in enumerate (bytearray_ ):
1544+ self ._bytearray [i + self .db_offset ] = b
1545+
1546+ # todo: optimize by only rebuilding the index instead of all the DB_Row objects
1547+ self .index .clear ()
1548+ self .make_rows ()
1549+
1550+ def write (self , client ):
1551+ """Writes all the rows from the :obj:`bytearray` of this instance to the PLC
1552+
1553+ Notes:
1554+ When the row_offset property has been set to something other than None while
1555+ constructing this object, this operation is not guaranteed to be atomic.
1556+
1557+ Args:
1558+ client: :obj:`Client` snap7 instance.
1559+
1560+ Raises:
1561+ :obj:`ValueError`: if the `row_size` is less than 0.
1562+ """
1563+ if self .row_size < 0 :
1564+ raise ValueError ("row_size must be greater equal zero." )
1565+
1566+ # special case: we have a row offset, so we must write each row individually
1567+ # this is because we don't want to change the data before the offset
1568+ if self .row_offset :
1569+ for _ , v in self .index .items ():
1570+ v .write (client )
1571+ return
1572+
1573+ total_size = self .size * (self .row_size + self .row_offset )
1574+ data = self ._bytearray [self .db_offset :self .db_offset + total_size ]
1575+
1576+ if self .area == Areas .DB :
1577+ client .db_write (self .db_number , self .db_offset , data )
1578+ else :
1579+ client .write_area (self .area , 0 , self .db_offset , data )
1580+
14661581
14671582class DB_Row :
14681583 """
@@ -1755,7 +1870,7 @@ def read(self, client: Client) -> None:
17551870 if self .area == Areas .DB :
17561871 bytearray_ = client .db_read (db_nr , self .db_offset , self .row_size )
17571872 else :
1758- bytearray_ = client .read_area (self .area , 0 , 0 , self .row_size )
1873+ bytearray_ = client .read_area (self .area , 0 , self . db_offset , self .row_size )
17591874
17601875 data = self .get_bytearray ()
17611876 # replace data in bytearray
0 commit comments