예제 #1
0
class LogixDriverCOMServer:
    _reg_clsctx_ = pythoncom.CLSCTX_LOCAL_SERVER
    _public_methods_ = ['open', 'close', 'read_tag', 'write', ] #'get_plc_info', 'get_plc_name', 'get_tag_list']
    _readonlu_attrs_ = []
    _public_attrs_ = []
    # _readonly_attrs_ = ['tags', 'info', 'name']
    # _public_attrs_ = ['path', 'large_packets', 'init_tags', 'init_program_tags' ] + _readonly_attrs_

    _reg_clsid_ = CLSID
    _reg_desc_ = 'Pycomm3 - Python Ethernet/IP ControlLogix Library COM Server'
    _reg_progid_ = 'Pycomm3.COMServer'

    def __init__(self):
        self.plc: LogixDriver = None

    def open(self, path, init_tags=True, init_program_tags=False, init_info=True):
        self.plc = LogixDriver(path, init_tags=init_tags, init_program_tags=init_program_tags, init_info=init_info)
        self.plc.open()

    def close(self):
        self.plc.close()

    def read(self, tag):
        result = self.plc.read(tag)
        return result.value if result else None

    def write(self, *tag_values):
        return self.plc.write(*tag_values)
예제 #2
0
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()
예제 #3
0
def init(): # Funtion to initialize a connection to a PLC and retrive data from it
    seqs, tags = {}, {}
    ipOK, slotOK = False, False
    ipRegex = re.compile(r'''
    (\d{1,3}\.){3}\d{1,3}$ # IPv4
    | ([0-9abcdef]{4}:){7}[0-9abcdef]{4}$ # IPv6
    | ^cancel$ # Cancel the operation
    ''', re.VERBOSE|re.I)
    slotRegex = re.compile(r'''
    \d+$ # Any digit
    | ^cancel$ # Cancel the operation
    ''', re.VERBOSE|re.I)
    while ipOK == False:
        ip = re.match(ipRegex, input("PLC IP Address: ").strip()) # Request the PLC IP address and check it is the correct format
        if ip: # IP address format OK
            if ip.group().lower() == 'cancel':
                print('Operation cancelled\n')
                return None, seqs, tags
            else:
                ipOK = True # IP Address OK, allow user to enter slot
        else: # IP address format not OK
            print('Format of IP address was incorrect')
    while slotOK == False:
        slot = re.match(slotRegex, input("Rack slot: ").strip()) # Request the rack slot number
        if slot: # Slot format OK
            if slot.group().lower() == 'cancel':
                print('Operation cancelled\n')
                return None, seqs, tags
            else:
                slotOK = True
        else: # Slot format not OK
            print('Format of slot was incorrect')
    try:
        print(f"Initializing connection to {ip.group()}/{str(slot.group())}")
        plc = LogixDriver(f"{ip.group()}/{str(slot.group())}", init_tags=True, init_program_tags=True) # Set up the LogixDriver for the stated PLC. This is returned to be used within other functions
        if plc.open(): # Open the connection to the PLC and read data from it
            print("Connection to PLC established")
            plcInfo = plc.info # Write the PLC info to a variable
            del plcInfo['tasks'] # Remove tasks from plcInfo as it is not required
            del plcInfo['modules'] # Remove modules from plcInfo as it is not required
            programs = plcInfo.pop('programs') # Pop the 'programs' dictionary item into a variable
            for prog in programs.keys(): # Go through the programs and search for a sequence regex (Sxx where x are digits)
                mo1 = re.search(r'^S\d\d', prog)
                if mo1 != None:
                    mo2 = re.search(r'\d\d', mo1.group()) # Search for the digits in the matched object and use them as keys in the sequences dictionary
                    seqs.setdefault(mo2.group(), prog) # Assign the key (sequence number) and value (program name) to the sequences dictionary
            seqs = keySortDict(seqs) # Sort the sequence dictionary based on sequence number (keys)
            plcInfo.update({'revision':f"Major : {str(plcInfo['revision']['major'])} / Minor : {str(plcInfo['revision']['minor'])}"})
            table = Table(box=box.ROUNDED) # Create a table
            table.add_column('PLC Information', justify='left') # Add column
            table.add_column('Sequences', justify='left') # Add column
            textInfo = '\n'.join(f"{k.capitalize().replace('_', ' ').ljust(15)} |   {v}" for k, v in plcInfo.items()) # Combine the PLC information into one string
            textSeq = '\n'.join(f"{k} - {v}" for k, v in seqs.items()) # Combine the PLC sequences into one string
            table.add_row(textInfo, textSeq) # Add row
            print(table)
            for k, v in seqs.items(): # Loop for each sequence selected by the user
                maxStep = plc.read(f"zzSeq[{k}].MaxStepNo").value
                tags.setdefault(k, 
                    [f"zzSeq[{k}].MaxStepNo", 
                    f"Program:{v}.zzSteptimeLast[1]{{{maxStep}}}", 
                    f"Program:{v}.zzSteptimeLong[1]{{{maxStep}}}",
                    f"Program:{v}.zzSteptimeShort[1]{{{maxStep}}}",
                    f"Program:{v}.zzStepRefTime[xxTypexx,1]{{{maxStep}}}"]) # Add the sequence tags to the dictionary of tags
                print(f"Initialized step tags for sequence {k}: {v}, max step = {str(maxStep)}")
            plc.close()
        else:
            print('Failed to establish connection to PLC')
    except Exception:
        plc.close()
        traceback.print_exc()
    return plc, seqs, tags # Return the LogixDriver and a key sorted dictionary of sequence programs in the connected PLC
class allenbradley_logix(driver):
    '''
    This driver supports services specific to ControlLogix, CompactLogix, and Micro800 PLCs.
    The driver is based on the library pycomm3: https://github.com/ottowayi/pycomm3.

    Parameters:
    ip  : String    : ip address of the PLC
    '''

    def __init__(self, name: str, pipe: Optional[Pipe] = None):
        """
        :param name: (optional) Name for the driver
        :param pipe: (optional) Pipe used to communicate with the driver thread. See gateway.py
        """
        # Inherit
        driver.__init__(self, name, pipe)
        
        self.ip = '192.168.0.1'


    def connect(self) -> bool:
        """ Connect driver.
        
        : returns: True if connection stablished False if not
        """
        # Create connection
        try:
            self._connection = LogixDriver(self.ip)
            self._connection.open()
        except Exception as e:
            self.sendDebugInfo(f"Connection with {self.ip} cannot be stablished.")
            return False

        # Check connection status.
        if self._connection.connected:
            return True
        else:
            self.sendDebugInfo(f"Driver not connected.") 
            return False


    def disconnect(self):
        """ Disconnect driver.
        """
        if self._connection:
            self._connection.close()


    def addVariables(self, variables: dict):
        """ Add variables to the driver. Correctly added variables will be added to internal dictionary 'variables'.
        Any error adding a variable should be communicated to the server using sendDebugInfo() method.
        : param variables: Variables to add in a dict following the setup format. (See documentation) 
        """
        for var_id in list(variables.keys()):
            var_data = dict(variables[var_id])
            try:
                var_data['value'] = None
                self.variables[var_id] = var_data 
            except Exception as e:
                self.sendDebugInfo(f'SETUP: {e} \"{var_id}\"')
                

    def readVariables(self, variables: list) -> list:
        """ Read given variable values. In case that the read is not possible or generates an error BAD quality should be returned.
        : param variables: List of variable ids to be read. 
        : returns: list of tupples including (var_id, var_value, VariableQuality)
        """
        res = []
        try:
            values = self._connection.read(*variables)
            if not isinstance(values, list):
                values = [values]
            for (tag, value, _, error) in values:
                if error:
                    res.append((tag, None, VariableQuality.BAD))
                else:
                    res.append((tag, value, VariableQuality.GOOD))
        except Exception as e:
            self.sendDebugInfo(f'exception reading variable values: {e}')
        return res


    def writeVariables(self, variables: list) -> list:
        """ Write given variable values. In case that the write is not possible or generates an error BAD quality should be returned.
        : param variables: List of tupples with variable ids and the values to be written (var_id, var_value). 
        : returns: list of tupples including (var_id, var_value, VariableQuality)
        """
        res = []

        try:
            values = self._connection.write(*variables)
            if not isinstance(values, list):
                values = [values]
            for (tag, value, _, error) in values:
                if error:
                    res.append((tag, value, VariableQuality.BAD))
                else:
                    res.append((tag, value, VariableQuality.GOOD))
        except Exception as e:
            self.sendDebugInfo(f'exception writing variable values: {e}')

        return res