The db_read and read_area functions use a fixed return type which is a sequence of c_int8. This is done via the wordlen_to_ctypes map.
wordlen_to_ctypes = ADict({
S7WLBit: ctypes.c_int16,
S7WLByte: ctypes.c_int8,
S7WLWord: ctypes.c_int16,
S7WLDWord: ctypes.c_int32,
S7WLReal: ctypes.c_int32,
S7WLCounter: ctypes.c_int16,
S7WLTimer: ctypes.c_int16,
})
And then in the function
wordlen = snap7.snap7types.S7WLByte
type_ = snap7.snap7types.wordlen_to_ctypes[wordlen]
data = (type_ * size)()
result = self.library.Cli_ReadArea(self.pointer, area, dbnumber, start, size, wordlen, byref(data))
data is passed by reference to the library to be populated with data from the PLC
The size parameter is then used to determine how many c_int8 units to read.
It might be better to use c_uint8 as the data type so that a sequence of bytes is returned instead of signed integers. I think that the two functions are basically reading a series of bytes from a specific area starting at a given offset so returning something that is analogous to bytes instead of signed integers seems to better match the purpose.
I am not sure if the right thing to do is to modify each function that should return a sequence of bytes or to modify the wordlen_to_ctypes map so that S7WLByte points to c_uint8. Also would it then make sense that S7WLWord and S7WLDWord be changed to point at unsigned types? I haven't read through much of the library so I don't know what all of the use cases for wordlen_to_ctypes are.
Also it appears that in the Snap7 library that WordLen is not the actual word length but used an indicator of the S7 Data Type that is being read or written.
Here is an example from the Snap7 library where it converts to a WordLen value to a the size in bytes for that data type
found in s7_micro_client.cpp
switch (WordLength){
case S7WLBit : return 1; // S7 sends 1 byte per bit
case S7WLByte : return 1;
case S7WLChar : return 1;
case S7WLWord : return 2;
case S7WLDWord : return 4;
case S7WLInt : return 2;
case S7WLDInt : return 4;
case S7WLReal : return 4;
case S7WLCounter : return 2;
case S7WLTimer : return 2;
default : return 0;
}
Here is a modified version of read_area I have been using test against a S7-319 CPU reading REALs and INTs.
def read_area(self, area, dbnumber, start, size):
"""This is the main function to read data from a PLC.
With it you can read DB, Inputs, Outputs, Merkers, Timers and Counters.
:param dbnumber: The DB number, only used when area= S7AreaDB
:param start: offset to start reading
:param size: number of bytes to read
"""
assert area in snap7.snap7types.areas.values()
wordlen = snap7.snap7types.S7WLByte
logging.debug("reading area: %s dbnumber: %s start: %s: amount %s: "
"wordlen: %s" % (area, dbnumber, start, size, wordlen))
data = (c_uint8 * size)()
result = self.library.Cli_ReadArea(self.pointer, area, dbnumber, start,
size, wordlen, byref(data))
check_error(result, context="client")
return data
and here is how I am using it
result = client.read_area(S7AreaDB, 200, 16, 4)
bytes = ''.join([chr(x) for x in result])
real_num = struct.unpack('>f', bytes)
print(real_num)
result = client.read_area(S7AreaDB, 200, 2, 2)
bytes = ''.join([chr(x) for x in result])
int_num = struct.unpack('>h', bytes)
print(int_num)