├── README.md ├── s7get.py └── s7getDB.py /README.md: -------------------------------------------------------------------------------- 1 | # s7-get 2 | 3 | Python scripts that allow to send read/write **s7communication** requests to **Siemens** S7 PLCs. 4 | 5 | The different arguments can be given directly in command line. 6 | 7 | Here is how to use it: *s7get.py -a `address` -m `mode` -n `number` -d `data` ip_address* 8 | 9 | > * -a Address from which data will be read/written 10 | > * -m [r|w] Chosen mode of the program, whether you want to read or write on the PLC 11 | > * -n Number of data you want to read/write 12 | > * -d Data in binary to write (e.g. 1001) 13 | 14 | S7getDB is the same tool but allows to write on the DB section instead of digital outputs. 15 | The db number can be specified with the -db option. 16 | 17 | This tool is based on Snap7 library, as well as the Python wrapper. 18 | Install docs can be found here : http://python-snap7.readthedocs.org/en/latest/installation.html 19 | -------------------------------------------------------------------------------- /s7get.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2015, Alexandrine Torrents 4 | # Mathieu Chevalier 5 | # All rights reserved. 6 | 7 | import snap7 8 | import argparse 9 | 10 | GREEN = "\033[32;1m" 11 | RED = "\033[31;1m" 12 | CRESET = "\033[0m" 13 | 14 | """ 15 | For testing purposes only 16 | """ 17 | 18 | 19 | class MockS7(object): 20 | def __init__(self, string): 21 | self.content = bytearray(string) 22 | 23 | def read_area(self, area, dbnumber, start, size): 24 | return self.content[start:][:size] 25 | 26 | def write_area(self, area, dbnumber, start, data): 27 | size = len(data) 28 | self.content[start:start + size] = data 29 | 30 | def __repr__(self): 31 | return f"MockS7({repr(self.content)})" 32 | 33 | 34 | def read_s7(s7, start_bit, bit_length): 35 | """ 36 | start_bit = 4 37 | bit_length = 14 38 | |0|1|0|1|1|0|0|1| |0|1|1|1|0|1|0|0| |0|1|0|1|1|1|0|1| 39 | ^---start_bit = 4 ^---end_bit = 17 = start_bit + bit_length - 1 40 | 41 | """ 42 | start_byte = start_bit // 8 # the byte where the first bit is located 43 | end_byte = (start_bit + bit_length - 1) // 8 # the byte where the last bit is located 44 | byte_length = end_byte - start_byte + 1 # the number of bytes to retrieve 45 | s = s7.read_area(snap7.types.areas['PA'], 0, start_byte, byte_length) 46 | 47 | relative_start_bit = start_bit - start_byte * 8 # all subsequent offsets are relative to the start of "s" 48 | print('===Outputs===') 49 | 50 | for current_bit_offset in range(relative_start_bit, relative_start_bit + bit_length): 51 | current_byte_offset = current_bit_offset // 8 # offset of the byte where the current bit is located 52 | bit_offset_in_current_byte = current_bit_offset % 8 # offset of the current bit inside the current byte (0-7) 53 | current_bit_value = (s[current_byte_offset] >> bit_offset_in_current_byte) & 1 54 | print(f"Output {current_bit_offset}: {current_bit_value}") 55 | 56 | 57 | def write_s7(s7, start_bit, bit_string): 58 | """ 59 | start_bit = 4 60 | bit_length = 14 61 | |0|1|0|1|1|0|0|1| |0|1|1|1|0|1|0|0| |0|1|0|1|1|1|0|1| 62 | ^---start_bit = 4 ^---end_bit = 17 = start_bit + bit_length - 1 63 | 64 | """ 65 | assert all(i in "01" for i in bit_string) 66 | bit_length = len(bit_string) 67 | 68 | start_byte = start_bit // 8 # the byte where the first bit is located 69 | end_byte = (start_bit + bit_length - 1) // 8 # the byte where the last bit is located 70 | byte_length = end_byte - start_byte + 1 # the number of bytes to retrieve 71 | s = s7.read_area(snap7.types.areas['PA'], 0, start_byte, byte_length) 72 | 73 | relative_start_bit = start_bit - start_byte * 8 # all subsequent offsets are relative to the start of "s" 74 | 75 | for current_bit_offset, current_bit_value in zip(range(relative_start_bit, relative_start_bit + bit_length), 76 | bit_string): 77 | current_byte_offset = current_bit_offset // 8 # offset of the byte where the current bit is located 78 | bit_offset_in_current_byte = current_bit_offset % 8 # offset of the current bit inside the current byte (0-7) 79 | if current_bit_value == "0": 80 | s[current_byte_offset] &= (1 << bit_offset_in_current_byte) ^ 0xFF 81 | elif current_bit_value == "1": 82 | s[current_byte_offset] |= (1 << bit_offset_in_current_byte) 83 | else: 84 | assert False 85 | s7.write_area(snap7.types.areas['PA'], 0, start_byte, s) 86 | print(GREEN + "[+] " + CRESET + str(bit_string) + " has been written correctly from address " + str(start_bit)) 87 | 88 | 89 | def main(): 90 | # Parse command line arguments 91 | parser = argparse.ArgumentParser(description='Read or write on Siemens PLCs.') 92 | parser.add_argument('-a', metavar='
', type=int, required=True, dest='address', 93 | help='Address from which data will be read/written') 94 | parser.add_argument('-m', metavar='', required=True, dest='mode', choices=['r', 'w'], 95 | help='[r|w] Chosen mode: read or write on the PLC') 96 | parser.add_argument('-n', metavar='', type=int, dest='number', 97 | help='Number of data to read/write') 98 | parser.add_argument('-d', metavar='', type=str, dest='data', 99 | help='Data in binary to write (e.g. 1001)') 100 | parser.add_argument('ip_address', type=str) 101 | args = parser.parse_args() 102 | 103 | s7 = snap7.client.Client() 104 | s7.connect(args.ip_address, 0, 1) 105 | # s7 = MockS7("Lorem ipsum".encode()) 106 | 107 | if args.mode == "r": 108 | if not args.number: 109 | parser.error("-n parameter misused or absent") 110 | read_s7(s7, args.address, args.number) 111 | elif args.mode == "w": 112 | data = args.data 113 | if len(data) == 0: 114 | parser.error("[-d] parameter misused or absent") 115 | if not all(i in "01" for i in data): 116 | parser.error("Data to be written shall only contain 0s and 1s") 117 | write_s7(s7, args.address, data) 118 | 119 | 120 | if __name__ == "__main__": 121 | main() 122 | -------------------------------------------------------------------------------- /s7getDB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2015, Alexandrine Torrents 4 | # Mathieu Chevalier 5 | # All rights reserved. 6 | 7 | import snap7 8 | import argparse 9 | 10 | GREEN = "\033[32;1m" 11 | RED = "\033[31;1m" 12 | CRESET = "\033[0m" 13 | 14 | # Parse command line arguments 15 | parser = argparse.ArgumentParser(description='Read or write on Siemens PLCs.') 16 | parser.add_argument('-a', metavar='
', type=int, nargs=1, 17 | required=True, dest='address', 18 | help='Address from which data will be read/written') 19 | parser.add_argument('-m', metavar='', nargs=1, required=True, 20 | dest='mode', choices=['r', 'w'], 21 | help='[r|w] Chosen mode: read or write on the PLC') 22 | parser.add_argument('-n', metavar='', type=int, nargs=1, 23 | required=True, dest='number', 24 | help='Number of data to read/write') 25 | parser.add_argument('-d', metavar='', nargs=1, dest='data', 26 | help='Data in binary to write (e.g. 1001)') 27 | parser.add_argument('ip_address', nargs=1) 28 | args = parser.parse_args() 29 | 30 | HOST = args.ip_address[0] # IP Address 31 | PORT = 102 # 32 | MODE = args.mode[0] # Read/Write 33 | ADDRESS = args.address[0] # Offset in bits 34 | NUMBER = args.number[0] # Number of bits to read/write 35 | 36 | s7 = snap7.client.Client() 37 | s7.connect(HOST, 0, 1) 38 | 39 | def read(): 40 | result = [] 41 | output = [] 42 | start = int(ADDRESS)/8 43 | offset = int(ADDRESS) % 8 # Where the start address is located in the byte 44 | offset_bis = 8 - offset # How many bits are going to be read in the first byte 45 | new_number = int(NUMBER) - offset_bis # New number to calculate the remaining bytes that need to be read 46 | if new_number <= 0: 47 | size = 1 48 | else: 49 | size = (new_number-1)/8 + 2 50 | s = s7.read_area(snap7.types.areas['DB'], 1, start, size) 51 | result = [bits[::-1] for bits in ['0' * (8 - len(bin(x)[2:])) + bin(x)[2:] for x in s]] 52 | for k in range(offset, min(8, offset + int(NUMBER))): 53 | output.append(result[0][k]) # to write the bits value of the first byte 54 | if 8 < offset + int(NUMBER): 55 | size_result = len(result) 56 | for l in range(1, size_result): 57 | for m in range(0, 8): 58 | output.append(result[l][m]) # all the remaining bytes 59 | print '===Outputs===' 60 | for j in range(0, int(NUMBER)): 61 | print "Output " + str(int(ADDRESS) + j) + " : " + output[j] 62 | return None 63 | 64 | def write(): 65 | DATA = args.data[0] # Data to be written 66 | for i in DATA: 67 | if i != '0' and i != '1': 68 | parser.error("Data to be written shall only contain 0s and 1s") 69 | if len(DATA) == 0: 70 | parser.error("[-d] parameter misused or absent") 71 | if len(DATA) != NUMBER: 72 | parser.error("The number of bits to write, specified in [-n], is different from the size of the data given in [-d]") 73 | to_be_written = [] 74 | data = [] 75 | start = int(ADDRESS)/8 76 | offset = int(ADDRESS) % 8 # Where the start address is located in the byte 77 | offset_bis = 8-offset # How many bits are going to be read in the first byte 78 | new_number = int(NUMBER) - offset_bis # New number to calculate the remaining bytes that need to be read 79 | if new_number <= 0: 80 | size = 1 81 | else: 82 | size = (new_number-1)/8 + 2 83 | to_be_written.append(DATA[:offset_bis]) 84 | to_be_written += [DATA[i + offset_bis: i + offset_bis + 8] for i in range(0, len(DATA) - offset_bis, 8)] 85 | s = s7.read_area(snap7.types.areas['DB'], 1, start, size) 86 | read_values = [bits[::-1] for bits in ['0' * (8 - len(bin(x)[2:])) + bin(x)[2:] for x in s]] 87 | if offset != 0: 88 | data.append(read_values[0][:offset]) 89 | data += to_be_written[::] 90 | if new_number % 8 != 0: 91 | data.append(read_values[size - 1][new_number % 8:]) 92 | if len(data[0]) != 8: 93 | data[0:2] = [''.join(data[0:2])] 94 | if len(data[-1]) != 8: 95 | data[len(data) - 2:len(data)] = [''.join(data[len(data) - 2:len(data)])] 96 | data1 = [int(byte[::-1], 2) for byte in data] 97 | data = bytearray(data1) 98 | s = s7.write_area(snap7.types.areas['DB'], 1, start, data) 99 | print GREEN + "[+] " + CRESET + str(DATA) + " has been written correctly from address " + str(ADDRESS) 100 | return None 101 | 102 | def main(): 103 | if MODE == "r": 104 | read() 105 | elif MODE == "w": 106 | write() 107 | 108 | if __name__ == "__main__": 109 | main() 110 | --------------------------------------------------------------------------------