def test_close_raises_commerror_on_socket_close_exception(): with mock.patch.object(Socket, 'close') as mock_close: mock_close.side_effect = Exception with pytest.raises(CommError): driver = CIPDriver(CONNECT_PATH) driver._sock = Socket() driver.close()
def test_cip_get_module_info_raises_response_error_if_response_falsy(): with mock.patch.object(CIPDriver, 'generic_message') as mock_generic_message: mock_generic_message.return_value = False with pytest.raises(ResponseError): driver = CIPDriver(CONNECT_PATH) driver.get_module_info(1) assert mock_generic_message.called
def test__forward_close_returns_true_if_response(): driver = CIPDriver(CONNECT_PATH) driver._session = 1 response = ( b"o\x00\x1e\x00\x02\x16\x02\x0b\x00\x00\x00\x00_pycomm_" b"\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x02\x00\x00\x00" b"\x00\x00\xb2\x00\x0e\x00\xce\x00\x00\x00'\x04\t\x10\xd6\x9c\x06=\x00\x00" ) driver._sock = Mocket(response) assert driver._forward_close()
def discoverPLCs(): # Function to discover any PLC on the network ips, slots, progName = [], [], [] try: discovery = CIPDriver.discover() # Return list of all CIP devices on the network for device in discovery: # Go through discovery list and append any PLC#'s to a list if device['product_type'] == "Programmable Logic Controller": ips.append(device['ip_address']) if len(ips) > 0: # Print the discovered PLC's, if there are any ips.sort() # Sort the IP address in ascending order table = Table(box=box.ROUNDED) # Create table table.add_column('#', justify='center') # Add column table.add_column('Device Type', justify='center') # Add column table.add_column('IP Address', justify='center') # Add column table.add_column('Slot #', justify='center') # Add column table.add_column('Program Name', justify='center') # Add column for i, ip in enumerate(ips): # Add row for each PLC discovered slots.append('Unknown') progName.append('Unknown') for slot in range(1, 18): try: plc = LogixDriver(f'{plc}/{str(slot)}', init_tags=False) if plc.open(): slots[i] = slot progName[i] = plc.get_plc_name() plc.close() break except: continue table.add_row(str(i+1), 'Programmable Logic Controller', ip, str(slots[i]), progName[i]) print(table) else: print("No PLC's discovered on the network") except Exception: traceback.print_exc()
def test_discover(): from pycomm3 import CIPDriver devices = CIPDriver.discover() expected = [ {'encap_protocol_version': 1, 'ip_address': '192.168.1.237', 'vendor': 'Rockwell Automation/Allen-Bradley', 'product_type': 'Communications Adapter', 'product_code': 185, 'revision': {'major': 2, 'minor': 7}, 'serial': '73015738', 'product_name': '1763-L16BWA B/7.00', 'state': 0}, {'encap_protocol_version': 1, 'ip_address': '192.168.1.236', 'vendor': 'Rockwell Automation/Allen-Bradley', 'product_type': 'Communications Adapter', 'product_code': 191, 'revision': {'major': 20, 'minor': 19}, 'serial': 'c01ebe90', 'product_name': '1769-L23E-QBFC1 Ethernet Port', 'state': 3}, # {'encap_protocol_version': 1, 'ip_address': '192.168.1.125', 'vendor': 'Rockwell Software, Inc.', # 'product_type': 'Communications Adapter', 'product_code': 115, 'revision': {'major': 12, 'minor': 1}, # 'serial': '21ac1903', 'product_name': 'DESKTOP-907P98D', 'state': 255} ] # status can change based on number of connections or other reasons # just check to make sure it has a value then remove it from the # rest of the device info # for device in devices: # assert 'status' in device # del device['status'] # assert device in expected for device in devices: assert 'ip_address' in device assert 'vendor' in device assert 'product_type' in device assert 'product_code' in device assert 'revision' in device
def link_status(): message_path = '10.10.10.100/bp/2' with CIPDriver(message_path) as device: data = device.generic_message( service=Services.get_attribute_single, class_code=b'\xf6', # Values from RA Knowledgebase instance= 1, # For multiport devices, change to "2" for second port, "3" for third port. # For CompactLogix, front port is "1" and back port is "2". attribute=2, # Values from RA Knowledgebase data_type=INT, connected=False, unconnected_send=True, route_path=True, name='LinkStatus') # Prints the binary representation of the link status. The definition of the bits are: # Bit 0 - Link Status - 0 means inactive link (Link Lost), 1 means active link. # Bit 1 - Half/Full Duplex - 0 means half duplex, 1 means full duplex # Bit 2 to 4 - Binary representation of auto-negotiation and speed detection status: # 0 = Auto-negotiation in progress # 1 = Auto-negotiation and speed detection failed # 2 = Auto-negotiation failed, speed detected # 3 = Auto-negotiation successful and speed detected # 4 = Manually forced speed and duplex # Bit 5 - Setting Requires Reset - if 1, a manual setting requires resetting of the module # Bit 6 - Local Hardware Fault - 0 indicates no hardware faults, 1 indicates a fault detected. print(bin(data.value))
def test_context_manager_calls_open_close(): with mock.patch.object(CIPDriver, 'open') as mock_close, \ mock.patch.object(CIPDriver, 'close') as mock_open: with CIPDriver(CONNECT_PATH) as driver: ... assert mock_open.called assert mock_close.called
def read_params(): connection = connection_setup() drive_path = connection[0] conn_type = connection[1] if conn_type == 'p': connected_send = False unconnected_send = True route_path = False elif conn_type == 'd': connected_send = True unconnected_send = False route_path = True print("Enter a parameter to read:") param_to_read = input('>>> ') with CIPDriver(drive_path) as drive: param = drive.generic_message(service=Services.get_attribute_single, class_code=b'\x93', instance=int(param_to_read), attribute=b'\x09', data_type=INT, connected=connected_send, unconnected_send=unconnected_send, route_path=route_path, name='PF525_Param') print(param)
def upload_eds(): """ Uploads the EDS and ICO files from the device and saves the files. """ with CIPDriver('192.168.1.236') as driver: if initiate_transfer(driver): file_data = upload_file(driver) encoding = get_file_encoding(driver) if encoding == 'zlib': # in this case the file has both the eds and ico files in it files = decompress_eds(file_data) for filename, file_data in files.items(): file_path = SAVE_PATH / filename file_path.write_bytes(file_data) elif encoding == 'binary': file_name = get_file_name(driver) file_path = SAVE_PATH / file_name file_path.write_bytes(file_data) else: print('Unsupported Encoding') else: print('Failed to initiate transfer')
def test_context_manager_calls_open_close_with_exception(): with mock.patch.object(CIPDriver, 'open') as mock_close, \ mock.patch.object(CIPDriver, 'close') as mock_open: try: with CIPDriver(CONNECT_PATH) as driver: x = 1 / 0 except Exception: ... assert mock_open.called assert mock_close.called
def stopPlc(): with CIPDriver('10.0.111.5') as plc: data = plc.generic_message( service=b'\x07', class_code=b'\x24', instance=1, connected=False, unconnected_send=True, route_path=True, name='STOP_plc' ) print(data.value)
def get_mac_address(): with CIPDriver('10.10.10.100') as plc: response = plc.generic_message(service=Services.get_attribute_single, class_code=ClassCode.ethernet_link, instance=1, attribute=3, data_type=USINT[6], connected=False) if response: return ':'.join(f'{x:0>2x}' for x in response.value) else: print(f'error getting MAC address - {response.error}')
def write_pf525_parameter(): drive_path = '10.10.10.100/bp/1/enet/192.168.1.55' with CIPDriver(drive_path) as drive: drive.generic_message( service=Services.set_attribute_single, class_code=b'\x93', instance=41, # Parameter 41 = Accel Time attribute=b'\x09', request_data=INT.encode(500), # = 5 seconds * 100 connected=False, unconnected_send=True, route_path=True, name='pf525_param')
def test_get_module_info_returns_expected_identity_dict(): EXPECTED_DICT = { "vendor": "Rockwell Automation/Allen-Bradley", "product_type": "Programmable Logic Controller", "product_code": 89, "revision": { "major": 20, "minor": 19 }, "status": b"`0", "serial": "c00fa09b", "product_name": "1769-L23E-QBFC1 LOGIX5323E-QBFC1", } RESPONSE_BYTES = ( b"o\x00C\x00\x02\x13\x02\x0b\x00\x00\x00\x00_pycomm_\x00\x00\x00\x00\x00\x00\x00\x00\n" b"\x00\x02\x00\x00\x00\x00\x00\xb2\x003\x00\x81\x00\x00\x00\x01\x00\x0e\x00Y\x00\x14\x13" b"`0\x9b\xa0\x0f\xc0 1769-L23E-QBFC1 LOGIX5323E-QBFC1") driver = CIPDriver(CONNECT_PATH) driver._sock = Mocket(RESPONSE_BYTES) actual_response = driver.get_module_info(1) assert actual_response == EXPECTED_DICT
def get_mac_address(): with CIPDriver('10.10.10.100') as plc: response = plc.generic_message( service=CommonService.get_attribute_single, class_code=ClassCode.ethernet_link, instance=1, attribute=3, data_format=(('MAC', 'SINT[6]'), ), connected=False) if response: return ':'.join(f'{abs(x):0>2x}' for x in response.value['MAC']) else: print(f'error getting MAC address - {response.error}')
def test_get_module_info_returns_expected_identity_dict(): EXPECTED_DICT = { 'vendor': 'Rockwell Automation/Allen-Bradley', 'product_type': 'Programmable Logic Controller', 'product_code': 89, 'revision': { 'major': 20, 'minor': 19 }, 'status': b'`0', 'serial': 'c00fa09b', 'product_name': '1769-L23E-QBFC1 LOGIX5323E-QBFC1' } RESPONSE_BYTES = ( b'o\x00C\x00\x02\x13\x02\x0b\x00\x00\x00\x00_pycomm_\x00\x00\x00\x00\x00\x00\x00\x00\n' b'\x00\x02\x00\x00\x00\x00\x00\xb2\x003\x00\x81\x00\x00\x00\x01\x00\x0e\x00Y\x00\x14\x13' b'`0\x9b\xa0\x0f\xc0 1769-L23E-QBFC1 LOGIX5323E-QBFC1') driver = CIPDriver(CONNECT_PATH) driver._sock = Mocket(RESPONSE_BYTES) actual_response = driver.get_module_info(1) assert actual_response == EXPECTED_DICT
def read_pf525_parameter(): drive_path = '10.10.10.100/bp/1/enet/192.168.1.55' with CIPDriver(drive_path) as drive: param = drive.generic_message( service=Services.get_attribute_single, class_code=b'\x93', instance=41, # Parameter 41 = Accel Time attribute=b'\x09', data_type=INT, connected=False, unconnected_send=True, route_path=True, name='pf525_param') print(param)
def enbt_ok_led_status(): message_path = '10.10.10.100/bp/2' with CIPDriver(message_path) as device: data = device.generic_message( service=Services.get_attribute_single, class_code=b'\x01', # Values from RA Knowledgebase instance=1, # Values from RA Knowledgebase attribute=5, # Values from RA Knowledgebase data_type=INT, connected=False, unconnected_send=True, route_path=True, name='OK LED Status') # The LED Status is returned as a binary representation on bits 4, 5, 6, and 7. The decimal equivalents are: # 0 = Solid Red, 64 = Flashing Red, and 96 = Solid Green. The ENBT/EN2T do not display link lost through the OK LED. statuses = {0: 'solid red', 64: 'flashing red', 96: 'solid green'} print(statuses.get(data.value), 'unknown')
def stratix_power_status(): message_path = '10.10.10.100/bp/2/enet/192.168.1.1' with CIPDriver(message_path) as device: data = device.generic_message( service=b'\x0e', class_code=863, # use decimal representation of hex class code instance=1, attribute=8, connected=False, unconnected_send=True, route_path=True, data_type=INT, name='Power Status') # Returns a binary representation of the power status. Bit 0 is PWR A, Bit 1 is PWR B. If 1, power is applied. If 0, power is off. pwr_a = 'on' if data.value & 0b_1 else 'off' pwr_b = 'on' if data.value & 0b_10 else 'off' print(f'PWR A: {pwr_a}, PWR B: {pwr_b}')
def ip_config(): message_path = '10.10.10.100/bp/2' with CIPDriver(message_path) as plc: # L85 data = plc.generic_message(service=b'\x0e', class_code=b'\xf5', instance=1, attribute=3, connected=False, unconnected_send=True, route_path=True, data_type=INT, name='IP_config') statuses = {0b_0000: 'static', 0b_0001: 'BOOTP', 0b_0010: 'DHCP'} ip_status = data.value & 0b_1111 # only need the first 4 bits print(statuses.get(ip_status, 'unknown'))
def getIPConfig(): message_path = '10.0.111.5' with CIPDriver(message_path) as plc: # L85 data = plc.generic_message( service=b'\x0e', class_code=b'\xf5', instance=1, attribute=3, connected=False, unconnected_send=True, route_path=True, name='IP_config' ) statuses = { 0b_0000: 'static', 0b_0001: 'BOOTP', 0b_0010: 'DHCP' } print(data.value)
def test_close_raises_commerror_on_any_exception(mock_method, exception): """Raise a CommError if any CIPDriver methods raise exception. There are two CIPDriver methods called within close: CIPDriver._forward_close() CIPDriver._un_register_session() If those internal methods change, this test will break. I think that's acceptable and any changes to this method should make the author very aware that they have changed this method. """ with mock.patch.object(CIPDriver, mock_method) as mock_method: mock_method.side_effect = exception with pytest.raises(CommError): driver = CIPDriver(CONNECT_PATH) driver._target_is_connected = True driver._session = 1 driver.close()
def test__forward_open_returns_true_if_already_connected(): driver = CIPDriver(CONNECT_PATH) driver._target_is_connected = True assert driver._forward_open()
def test_open_returns_false_if_register_session_falsy(): driver = CIPDriver(CONNECT_PATH) driver._sock = Mocket() assert not driver.open()
def test_open_raises_commerror_on_connect_fail(): with mock.patch.object(Socket, 'connect') as mock_connect: mock_connect.side_effect = Exception driver = CIPDriver(CONNECT_PATH) with pytest.raises(CommError): driver.open()
def test_open_returns_true_if_register_session_truthy(): with mock.patch.object(CIPDriver, '_register_session') as mock_register: mock_register.return_value = 1 driver = CIPDriver(CONNECT_PATH) driver._sock = Mocket() assert driver.open()
def test__register_session_returns_configured_session(conf_session): driver = CIPDriver(CONNECT_PATH) driver._sock = Mocket(bytes(4) + UDINT.encode(conf_session) + bytes(20)) assert conf_session == driver._register_session()
def test__register_session_returns_none_if_no_response(): driver = CIPDriver(CONNECT_PATH) driver._sock = Mocket() assert driver._register_session() is None
def test__forward_open_returns_false_if_falsy_response(): driver = CIPDriver(CONNECT_PATH) driver._sock = Mocket() driver._session = 1 assert not driver._forward_open()
def test__forward_open_raises_commerror_if_session_is_zero(): driver = CIPDriver(CONNECT_PATH) with pytest.raises(CommError): driver._forward_open()