OpenOPC.py
上传用户:liufeng210
上传日期:2016-03-28
资源大小:164k
文件大小:42k
源码类别:

PlugIns编程

开发平台:

Python

  1. ###########################################################################
  2. #
  3. # OpenOPC for Python Library Module
  4. #
  5. # Copyright (c) 2007-2008 Barry Barnreiter (barry_b@users.sourceforge.net)
  6. #
  7. ###########################################################################
  8. import os
  9. import sys
  10. import time
  11. import types
  12. import string
  13. import socket
  14. import re
  15. import Queue
  16. __version__ = '1.1.3'
  17. callback_queue = Queue.Queue()
  18. # Win32 only modules not needed for 'open' protocol mode
  19. if os.name == 'nt':
  20.    try:
  21.       import win32com.client
  22.       import win32com.server.util
  23.       import win32event
  24.       import pythoncom
  25.       import pywintypes
  26.       import SystemHealth
  27.       
  28.       # Win32 variant types
  29.       vt = dict([(pythoncom.__dict__[vtype], vtype) for vtype in pythoncom.__dict__.keys() if vtype[:2] == "VT"])
  30.       # Allow gencache to create the cached wrapper objects
  31.       win32com.client.gencache.is_readonly = False
  32.     
  33.       # Under p2exe the call in gencache to __init__() does not happen
  34.       # so we use Rebuild() to force the creation of the gen_py folder
  35.       win32com.client.gencache.Rebuild()
  36.    # So we can work on Windows in "open" protocol mode without the need for the win32com modules
  37.    except ImportError:
  38.       win32com_found = False
  39.    else:
  40.       win32com_found = True
  41. else:
  42.    win32com_found = False
  43. # OPC Constants
  44. SOURCE_CACHE = 1
  45. SOURCE_DEVICE = 2
  46. OPC_STATUS = (0, 'Running', 'Failed', 'NoConfig', 'Suspended', 'Test')
  47. BROWSER_TYPE = (0, 'Hierarchical', 'Flat')
  48. ACCESS_RIGHTS = (0, 'Read', 'Write', 'Read/Write')
  49. OPC_QUALITY = ('Bad', 'Uncertain', 'Unknown', 'Good')
  50. OPC_CLASS = 'Matrikon.OPC.Automation;Graybox.OPC.DAWrapper;HSCOPC.Automation;RSI.OPCAutomation;OPC.Automation'
  51. OPC_SERVER = 'Hci.TPNServer;HwHsc.OPCServer;opc.deltav.1;AIM.OPC.1;Yokogawa.ExaopcDAEXQ.1;OSI.DA.1;OPC.PHDServerDA.1;Aspen.Infoplus21_DA.1;National Instruments.OPCLabVIEW;RSLinx OPC Server;KEPware.KEPServerEx.V4;Matrikon.OPC.Simulation;Prosys.OPC.Simulation'
  52. OPC_CLIENT = 'OpenOPC'
  53. def quality_str(quality_bits):
  54.    """Convert OPC quality bits to a descriptive string"""
  55.    quality = (quality_bits >> 6) & 3
  56.    return OPC_QUALITY[quality]
  57. def type_check(tags):
  58.    """Perform a type check on a list of tags"""
  59.    
  60.    if type(tags) in (types.ListType, types.TupleType):
  61.       single = False
  62.    elif tags == None:
  63.       tags = []
  64.       single = False
  65.    else:
  66.       tags = [tags]
  67.       single = True
  68.    if len([t for t in tags if type(t) not in types.StringTypes]) == 0:
  69.       valid = True
  70.    else:
  71.       valid = False
  72.    return tags, single, valid
  73. def wild2regex(string):
  74.    """Convert a Unix wildcard glob into a regular expression"""
  75.    return string.replace('.','.').replace('*','.*').replace('?','.').replace('!','^')
  76. def tags2trace(tags):
  77.    """Convert a list tags into a formatted string suitable for the trace callback log"""
  78.    arg_str = ''
  79.    for i,t in enumerate(tags[1:]):
  80.       if i > 0: arg_str += ','
  81.       arg_str += '%s' % t
  82.    return arg_str
  83. def exceptional(func, alt_return=None, alt_exceptions=(Exception,), final=None, catch=None):
  84.    """Turns exceptions into an alternative return value"""
  85.    def _exceptional(*args, **kwargs):
  86.       try:
  87.          try:
  88.             return func(*args, **kwargs)
  89.          except alt_exceptions:
  90.             return alt_return
  91.          except:
  92.             if catch: return catch(sys.exc_info(), lambda:func(*args, **kwargs))
  93.             raise
  94.       finally:
  95.          if final: final()
  96.    return _exceptional
  97. def get_sessions(host='localhost', port=7766):
  98.    """Return sessions in OpenOPC Gateway Service as GUID:host hash"""
  99.    
  100.    import Pyro.core
  101.    Pyro.core.initClient(banner = 0)
  102.    server_obj = Pyro.core.getProxyForURI("PYROLOC://%s:%s/opc" % (host, port))
  103.    return server_obj.get_clients()
  104. def open_client(host='localhost', port=7766):
  105.    """Connect to the specified OpenOPC Gateway Service"""
  106.    
  107.    import Pyro.core
  108.    Pyro.core.initClient(banner=0)
  109.    server_obj = Pyro.core.getProxyForURI("PYROLOC://%s:%s/opc" % (host, port))
  110.    return server_obj.create_client()
  111. class TimeoutError(Exception):
  112.     def __init__(self, txt):
  113.         Exception.__init__(self, txt)
  114. class OPCError(Exception):
  115.     def __init__(self, txt):
  116.         Exception.__init__(self, txt)
  117. class GroupEvents:
  118.     def OnDataChange(self, TransactionID, NumItems, ClientHandles, ItemValues, Qualities, TimeStamps):
  119.         callback_queue.put((TransactionID, ClientHandles, ItemValues, Qualities, TimeStamps))
  120.    
  121. class client():
  122.    def __init__(self, opc_class=None, client_name=None):
  123.       """Instantiate OPC automation class"""
  124.       pythoncom.CoInitialize()
  125.       if opc_class == None:
  126.          if os.environ.has_key('OPC_CLASS'):
  127.             opc_class = os.environ['OPC_CLASS']
  128.          else:
  129.             opc_class = OPC_CLASS
  130.       opc_class_list = opc_class.split(';')
  131.       for i,c in enumerate(opc_class_list):
  132.          try:
  133.             self._opc = win32com.client.gencache.EnsureDispatch(c, 0)
  134.             self.opc_class = c
  135.             break
  136.          except pythoncom.com_error, err:
  137.             if i == len(opc_class_list)-1:
  138.                error_msg = 'Dispatch: %s' % self._get_error_str(err)
  139.                raise OPCError, error_msg
  140.             
  141.       self._event = win32event.CreateEvent(None,0,0,None)
  142.       self.opc_server = None
  143.       self.opc_host = None
  144.       self.client_name = client_name
  145.       self._groups = {}
  146.       self._group_tags = {}
  147.       self._group_valid_tags = {}
  148.       self._group_server_handles = {}
  149.       self._group_handles_tag = {}
  150.       self._open_serv = None
  151.       self._open_self = None
  152.       self._open_host = None
  153.       self._open_port = None
  154.       self._open_guid = None
  155.       self._prev_serv_time = None
  156.       self._tx_id = 0
  157.       self.trace = None
  158.       self.cpu = None
  159.    def set_trace(self, trace):
  160.       if self._open_serv == None:
  161.          self.trace = trace
  162.    def connect(self, opc_server=None, opc_host='localhost'):
  163.       """Connect to the specified OPC server"""
  164.       pythoncom.CoInitialize()
  165.       
  166.       if opc_server == None:
  167.          # Initial connect using environment vars
  168.          if self.opc_server == None:
  169.             if os.environ.has_key('OPC_SERVER'):
  170.                opc_server = os.environ['OPC_SERVER']
  171.             else:
  172.                opc_server = OPC_SERVER
  173.          # Reconnect using previous server name
  174.          else:
  175.             opc_server = self.opc_server
  176.             opc_host = self.opc_host
  177.       opc_server_list = opc_server.split(';')
  178.       connected = False
  179.       for s in opc_server_list:
  180.          try:
  181.             if self.trace: self.trace('Connect(%s,%s)' % (s, opc_host))
  182.             self._opc.Connect(s, opc_host)
  183.          except pythoncom.com_error, err:
  184.             if len(opc_server_list) == 1:
  185.                error_msg = 'Connect: %s' % self._get_error_str(err)
  186.                raise OPCError, error_msg
  187.          else:
  188.             # Set client name since some OPC servers use it for security
  189.             if self.client_name == None:
  190.                 if os.environ.has_key('OPC_CLIENT'):
  191.                    self._opc.ClientName = os.environ['OPC_CLIENT']
  192.                 else:
  193.                    self._opc.ClientName = OPC_CLIENT
  194.             else:
  195.                 self._opc.ClientName = self.client_name
  196.             connected = True
  197.             break
  198.       if not connected:
  199.          raise OPCError, 'Connect: Cannot connect to any of the servers in the OPC_SERVER list'
  200.       # With some OPC servers, the next OPC call immediately after Connect()
  201.       # will occationally fail.  Sleeping for 1/100 second seems to fix this.
  202.       time.sleep(0.01)
  203.       self.opc_server = opc_server
  204.       if opc_host == 'localhost':
  205.          opc_host = socket.gethostname()
  206.       self.opc_host = opc_host
  207.       # On reconnect we need to remove the old group names from OpenOPC's internal
  208.       # cache since they are now invalid
  209.       self._groups = {}
  210.       self._group_tags = {}
  211.       self._group_valid_tags = {}
  212.       self._group_server_handles = {}
  213.       self._group_handles_tag = {}
  214.    def close(self, del_object=True):
  215.       """Disconnect from the currently connected OPC server"""
  216.       try:
  217.          pythoncom.CoInitialize()
  218.          self.remove(self.groups())
  219.          if self.trace: self.trace('Disconnect()')
  220.          self._opc.Disconnect()
  221.       except pythoncom.com_error, err:
  222.          error_msg = 'Disconnect: %s' % self._get_error_str(err)
  223.          raise OPCError, error_msg
  224.       finally:
  225.          # Remove this object from the open gateway service
  226.          if self._open_serv and del_object:
  227.             self._open_serv.release_client(self._open_self)
  228.    def iread(self, tags=None, group=None, size=None, pause=0, source='hybrid', update=-1, timeout=5000, sync=False, include_error=False, rebuild=False):
  229.       """Iterable version of read()"""
  230.       def add_items(tags):
  231.          names = list(tags)
  232.          names.insert(0,0)
  233.          errors = []
  234.           
  235.          if self.trace: self.trace('Validate(%s)' % tags2trace(names))
  236.           
  237.          try:
  238.             errors = opc_items.Validate(len(names)-1, names)
  239.          except:
  240.             pass
  241.              
  242.          valid_tags = []
  243.          valid_values = []
  244.          client_handles = []
  245.          if not self._group_handles_tag.has_key(sub_group):
  246.             self._group_handles_tag[sub_group] = {}
  247.             n = 0
  248.          else:
  249.             n = max(self._group_handles_tag[sub_group]) + 1
  250.           
  251.          for i, tag in enumerate(tags):
  252.             if errors[i] == 0:
  253.                valid_tags.append(tag)
  254.                client_handles.append(n)
  255.                self._group_handles_tag[sub_group][n] = tag 
  256.                n += 1
  257.             elif include_error:
  258.                error_msgs[tag] = self._opc.GetErrorString(errors[i])
  259.          client_handles.insert(0,0)
  260.          valid_tags.insert(0,0)
  261.          server_handles = []
  262.          errors = []
  263.          if self.trace: self.trace('AddItems(%s)' % tags2trace(valid_tags))
  264.        
  265.          try:
  266.             server_handles, errors = opc_items.AddItems(len(client_handles)-1, valid_tags, client_handles)
  267.          except:
  268.             pass
  269.              
  270.          valid_tags_tmp = []
  271.          server_handles_tmp = []
  272.          valid_tags.pop(0)
  273.          if not self._group_server_handles.has_key(sub_group):
  274.             self._group_server_handles[sub_group] = {}
  275.        
  276.          for i, tag in enumerate(valid_tags):
  277.             if errors[i] == 0:
  278.                valid_tags_tmp.append(tag)
  279.                server_handles_tmp.append(server_handles[i])
  280.                self._group_server_handles[sub_group][tag] = server_handles[i]
  281.             elif include_error:
  282.                error_msgs[tag] = self._opc.GetErrorString(errors[i])
  283.        
  284.          valid_tags = valid_tags_tmp
  285.          server_handles = server_handles_tmp
  286.          return valid_tags, server_handles
  287.       def remove_items(tags):
  288.          if self.trace: self.trace('RemoveItems(%s)' % tags2trace(['']+tags))
  289.          server_handles = [self._group_server_handles[sub_group][tag] for tag in tags]
  290.          server_handles.insert(0,0)
  291.          errors = []
  292.          try:
  293.             errors = opc_items.Remove(len(server_handles)-1, server_handles)
  294.          except pythoncom.com_error, err:
  295.             error_msg = 'RemoveItems: %s' % self._get_error_str(err)
  296.             raise OPCError, error_msg
  297.       try:
  298.          self._update_tx_time()
  299.          pythoncom.CoInitialize()
  300.          if include_error:
  301.             sync = True
  302.             
  303.          if sync:
  304.             update = -1
  305.          tags, single, valid = type_check(tags)
  306.          if not valid:
  307.             raise TypeError, "iread(): 'tags' parameter must be a string or a list of strings"
  308.          # Group exists
  309.          if self._groups.has_key(group) and not rebuild:
  310.             num_groups = self._groups[group]
  311.             data_source = SOURCE_CACHE
  312.          # Group non-existant
  313.          else:
  314.             if size:
  315.                # Break-up tags into groups of 'size' tags
  316.                tag_groups = [tags[i:i+size] for i in range(0, len(tags), size)]
  317.             else:
  318.                tag_groups = [tags]
  319.                
  320.             num_groups = len(tag_groups)
  321.             data_source = SOURCE_DEVICE
  322.          results = []
  323.          for gid in range(num_groups):
  324.             if gid > 0 and pause > 0: time.sleep(pause/1000.0)
  325.             
  326.             error_msgs = {}
  327.             opc_groups = self._opc.OPCGroups
  328.             opc_groups.DefaultGroupUpdateRate = update
  329.             # Anonymous group
  330.             if group == None:
  331.                try:
  332.                   if self.trace: self.trace('AddGroup()')
  333.                   opc_group = opc_groups.Add()
  334.                except pythoncom.com_error, err:
  335.                   error_msg = 'AddGroup: %s' % self._get_error_str(err)
  336.                   raise OPCError, error_msg
  337.                sub_group = group
  338.                new_group = True
  339.             else:
  340.                sub_group = '%s.%d' % (group, gid)
  341.                # Existing named group
  342.                try:
  343.                   if self.trace: self.trace('GetOPCGroup(%s)' % sub_group)
  344.                   opc_group = opc_groups.GetOPCGroup(sub_group)
  345.                   new_group = False
  346.                # New named group
  347.                except:
  348.                   try:
  349.                      if self.trace: self.trace('AddGroup(%s)' % sub_group)
  350.                      opc_group = opc_groups.Add(sub_group)
  351.                   except pythoncom.com_error, err:
  352.                      error_msg = 'AddGroup: %s' % self._get_error_str(err)
  353.                      raise OPCError, error_msg
  354.                   self._groups[str(group)] = len(tag_groups)
  355.                   new_group = True
  356.                   
  357.             opc_items = opc_group.OPCItems
  358.             if new_group:
  359.                opc_group.IsSubscribed = 1
  360.                opc_group.IsActive = 1
  361.                win32com.client.WithEvents(opc_group,GroupEvents)
  362.                tags = tag_groups[gid]
  363.                
  364.                valid_tags, server_handles = add_items(tags)
  365.                
  366.                self._group_tags[sub_group] = tags
  367.                self._group_valid_tags[sub_group] = valid_tags
  368.             # Rebuild existing group
  369.             elif rebuild:
  370.                tags = tag_groups[gid]
  371.                valid_tags = self._group_valid_tags[sub_group]
  372.                add_tags = [t for t in tags if t not in valid_tags]
  373.                del_tags = [t for t in valid_tags if t not in tags]
  374.                if len(add_tags) > 0:
  375.                   valid_tags, server_handles = add_items(add_tags)
  376.                   valid_tags = self._group_valid_tags[sub_group] + valid_tags
  377.                if len(del_tags) > 0:
  378.                   remove_items(del_tags)
  379.                   valid_tags = [t for t in valid_tags if t not in del_tags]
  380.                self._group_tags[sub_group] = tags
  381.                self._group_valid_tags[sub_group] = valid_tags
  382.                
  383.                if source == 'hybrid': data_source = SOURCE_DEVICE
  384.             # Existing group
  385.             else:
  386.                tags = self._group_tags[sub_group]
  387.                valid_tags = self._group_valid_tags[sub_group]
  388.                if sync:
  389.                   server_handles = [item.ServerHandle for item in opc_items]
  390.             tag_value = {}
  391.             tag_quality = {}
  392.             tag_time = {}
  393.             tag_error = {}
  394.                
  395.             # Sync Read
  396.             if sync:   
  397.                values = []
  398.                errors = []
  399.                qualities = []
  400.                timestamps= []
  401.                
  402.                if len(valid_tags) > 0:
  403.                    server_handles.insert(0,0)
  404.                    
  405.                    if source != 'hybrid':
  406.                       data_source = SOURCE_CACHE if source == 'cache' else SOURCE_DEVICE
  407.                    if self.trace: self.trace('SyncRead(%s)' % data_source)
  408.                    
  409.                    try:
  410.                       values, errors, qualities, timestamps = opc_group.SyncRead(data_source, len(server_handles)-1, server_handles)
  411.                    except pythoncom.com_error, err:
  412.                       error_msg = 'SyncRead: %s' % self._get_error_str(err)
  413.                       raise OPCError, error_msg
  414.                    for i,tag in enumerate(valid_tags):
  415.                       tag_value[tag] = values[i]
  416.                       tag_quality[tag] = qualities[i]
  417.                       tag_time[tag] = timestamps[i]
  418.                       tag_error[tag] = errors[i]
  419.             # Async Read
  420.             else:
  421.                if len(valid_tags) > 0:
  422.                   if self._tx_id >= 0xFFFF:
  423.                       self._tx_id = 0
  424.                   self._tx_id += 1
  425.       
  426.                   if source != 'hybrid':
  427.                      data_source = SOURCE_CACHE if source == 'cache' else SOURCE_DEVICE
  428.                   if self.trace: self.trace('AsyncRefresh(%s)' % data_source)
  429.                   try:
  430.                      opc_group.AsyncRefresh(data_source, self._tx_id)
  431.                   except pythoncom.com_error, err:
  432.                      error_msg = 'AsyncRefresh: %s' % self._get_error_str(err)
  433.                      raise OPCError, error_msg
  434.                   tx_id = 0
  435.                   start = time.time() * 1000
  436.                   
  437.                   while tx_id != self._tx_id:
  438.                      now = time.time() * 1000
  439.                      if now - start > timeout:
  440.                         raise TimeoutError, 'Callback: Timeout waiting for data'
  441.                      if callback_queue.empty():
  442.                         pythoncom.PumpWaitingMessages()
  443.                      else:
  444.                         tx_id, handles, values, qualities, timestamps = callback_queue.get()
  445.                                                 
  446.                   for i,h in enumerate(handles):
  447.                      tag = self._group_handles_tag[sub_group][h]
  448.                      tag_value[tag] = values[i]
  449.                      tag_quality[tag] = qualities[i]
  450.                      tag_time[tag] = timestamps[i]
  451.                         
  452.             for tag in tags:
  453.                if tag_value.has_key(tag):
  454.                   if (not sync and len(valid_tags) > 0) or (sync and tag_error[tag] == 0):
  455.                      value = tag_value[tag]
  456.                      if type(value) == pywintypes.TimeType:
  457.                         value = str(value)
  458.                      quality = quality_str(tag_quality[tag])
  459.                      timestamp = str(tag_time[tag])
  460.                   else:
  461.                      value = None
  462.                      quality = 'Error'
  463.                      timestamp = None
  464.                   if include_error:
  465.                      error_msgs[tag] = self._opc.GetErrorString(tag_error[tag]).strip('rn')
  466.                else:
  467.                   value = None
  468.                   quality = 'Error'
  469.                   timestamp = None
  470.                   if include_error and not error_msgs.has_key(tag):
  471.                      error_msgs[tag] = ''
  472.                if single:
  473.                   if include_error:
  474.                      yield (value, quality, timestamp, error_msgs[tag])
  475.                   else:
  476.                      yield (value, quality, timestamp)
  477.                else:
  478.                   if include_error:
  479.                      yield (tag, value, quality, timestamp, error_msgs[tag])
  480.                   else:
  481.                      yield (tag, value, quality, timestamp)
  482.             if group == None:
  483.                try:
  484.                    if self.trace: self.trace('RemoveGroup(%s)' % opc_group.Name)
  485.                    opc_groups.Remove(opc_group.Name)
  486.                except pythoncom.com_error, err:
  487.                   error_msg = 'RemoveGroup: %s' % self._get_error_str(err)
  488.                   raise OPCError, error_msg
  489.       except pythoncom.com_error, err:
  490.          error_msg = 'read: %s' % self._get_error_str(err)
  491.          raise OPCError, error_msg
  492.    def read(self, tags=None, group=None, size=None, pause=0, source='hybrid', update=-1, timeout=5000, sync=False, include_error=False, rebuild=False):
  493.       """Return list of (value, quality, time) tuples for the specified tag(s)"""
  494.       tags_list, single, valid = type_check(tags)
  495.       if not valid:
  496.          raise TypeError, "read(): 'tags' parameter must be a string or a list of strings"
  497.       num_health_tags = len([t for t in tags_list if t[:1] == '@'])
  498.       num_opc_tags = len([t for t in tags_list if t[:1] != '@'])
  499.       if num_health_tags > 0:
  500.          if num_opc_tags > 0:
  501.             raise TypeError, "read(): system health and OPC tags cannot be included in the same group"
  502.          results = self._read_health(tags)
  503.       else:
  504.          results = self.iread(tags, group, size, pause, source, update, timeout, sync, include_error, rebuild)
  505.       if single:
  506.          return list(results)[0]
  507.       else:
  508.          return list(results)
  509.    def _read_health(self, tags):
  510.       """Return values of special system health monitoring tags"""
  511.       self._update_tx_time()
  512.       tags, single, valid = type_check(tags)
  513.       
  514.       time_str = time.strftime('%x %H:%M:%S')
  515.       results = []
  516.       
  517.       for t in tags:
  518.          if   t == '@MemFree':      value = SystemHealth.mem_free()
  519.          elif t == '@MemUsed':      value = SystemHealth.mem_used()
  520.          elif t == '@MemTotal':     value = SystemHealth.mem_total()
  521.          elif t == '@MemPercent':   value = SystemHealth.mem_percent()
  522.          elif t == '@DiskFree':     value = SystemHealth.disk_free()
  523.          elif t == '@SineWave':     value = SystemHealth.sine_wave()
  524.          elif t == '@SawWave':      value = SystemHealth.saw_wave()
  525.          elif t == '@CpuUsage':
  526.             if self.cpu == None:
  527.                 self.cpu = SystemHealth.CPU()
  528.                 time.sleep(0.1)
  529.             value = self.cpu.get_usage()
  530.             
  531.          else:
  532.             value = None
  533.          
  534.             m = re.match('@TaskMem((.*?))', t)
  535.             if m:
  536.                image_name = m.group(1)
  537.                value = SystemHealth.task_mem(image_name)
  538.             m = re.match('@TaskCpu((.*?))', t)
  539.             if m:
  540.                image_name = m.group(1)
  541.                value = SystemHealth.task_cpu(image_name)
  542.                
  543.             m = re.match('@TaskExists((.*?))', t)
  544.             if m:
  545.                image_name = m.group(1)
  546.                value = SystemHealth.task_exists(image_name)
  547.                
  548.          if value == None:
  549.             quality = 'Error'
  550.          else:
  551.             quality = 'Good'
  552.                
  553.          if single:
  554.             results.append((value, quality, time_str))
  555.          else:
  556.             results.append((t, value, quality, time_str))
  557.     
  558.       return results
  559.    def iwrite(self, tag_value_pairs, size=None, pause=0, include_error=False):
  560.       """Iterable version of write()"""
  561.       try:
  562.          self._update_tx_time()
  563.          pythoncom.CoInitialize()
  564.          def _valid_pair(p):
  565.             if type(p) in (types.ListType, types.TupleType) and len(p) >= 2 and type(p[0]) in types.StringTypes:
  566.                return True
  567.             else:
  568.                return False
  569.          if type(tag_value_pairs) not in (types.ListType, types.TupleType):
  570.             raise TypeError, "write(): 'tag_value_pairs' parameter must be a (tag, value) tuple or a list of (tag,value) tuples"
  571.          if tag_value_pairs == None:
  572.             tag_value_pairs = ['']
  573.             single = False
  574.          elif type(tag_value_pairs[0]) in types.StringTypes:
  575.             tag_value_pairs = [tag_value_pairs]
  576.             single = True
  577.          else:
  578.             single = False
  579.          invalid_pairs = [p for p in tag_value_pairs if not _valid_pair(p)]
  580.          if len(invalid_pairs) > 0:
  581.             raise TypeError, "write(): 'tag_value_pairs' parameter must be a (tag, value) tuple or a list of (tag,value) tuples"
  582.             
  583.          names = [tag[0] for tag in tag_value_pairs]
  584.          tags = [tag[0] for tag in tag_value_pairs]
  585.          values = [tag[1] for tag in tag_value_pairs]
  586.          # Break-up tags & values into groups of 'size' tags
  587.          if size:
  588.             name_groups = [names[i:i+size] for i in range(0, len(names), size)]
  589.             tag_groups = [tags[i:i+size] for i in range(0, len(tags), size)]
  590.             value_groups = [values[i:i+size] for i in range(0, len(values), size)]
  591.          else:
  592.             name_groups = [names]
  593.             tag_groups = [tags]
  594.             value_groups = [values]
  595.             
  596.          num_groups = len(tag_groups)
  597.          status = []
  598.                  
  599.          for gid in range(num_groups):
  600.             if gid > 0 and pause > 0: time.sleep(pause/1000.0)
  601.             opc_groups = self._opc.OPCGroups
  602.             opc_group = opc_groups.Add()
  603.             opc_items = opc_group.OPCItems
  604.             names = name_groups[gid]
  605.             tags = tag_groups[gid]
  606.             values = value_groups[gid]
  607.             
  608.             names.insert(0,0)
  609.             errors = []
  610.             
  611.             try:
  612.                errors = opc_items.Validate(len(names)-1, names)
  613.             except:
  614.                pass
  615.                
  616.             n = 1
  617.             valid_tags = []
  618.             valid_values = []
  619.             client_handles = []
  620.             error_msgs = {}
  621.             
  622.             for i, tag in enumerate(tags):
  623.                if errors[i] == 0:
  624.                   valid_tags.append(tag)
  625.                   valid_values.append(values[i])
  626.                   client_handles.append(n)
  627.                   error_msgs[tag] = ''
  628.                   n += 1
  629.                elif include_error:
  630.                   error_msgs[tag] = self._opc.GetErrorString(errors[i])
  631.             client_handles.insert(0,0)
  632.             valid_tags.insert(0,0)
  633.             server_handles = []
  634.             errors = []
  635.        
  636.             try:
  637.                server_handles, errors = opc_items.AddItems(len(client_handles)-1, valid_tags, client_handles)
  638.             except:
  639.                pass
  640.                
  641.             valid_tags_tmp = []
  642.             valid_values_tmp = []
  643.             server_handles_tmp = []
  644.             valid_tags.pop(0)
  645.          
  646.             for i, tag in enumerate(valid_tags):
  647.                if errors[i] == 0:
  648.                   valid_tags_tmp.append(tag)
  649.                   valid_values_tmp.append(valid_values[i])
  650.                   server_handles_tmp.append(server_handles[i])
  651.                   error_msgs[tag] = ''
  652.                elif include_error:
  653.                   error_msgs[tag] = self._opc.GetErrorString(errors[i])
  654.          
  655.             valid_tags = valid_tags_tmp
  656.             valid_values = valid_values_tmp
  657.             server_handles = server_handles_tmp
  658.             server_handles.insert(0,0)
  659.             valid_values.insert(0,0)
  660.             errors = []
  661.             if len(valid_values) > 1:
  662.                try:
  663.                   errors = opc_group.SyncWrite(len(server_handles)-1, server_handles, valid_values)
  664.                except:
  665.                   pass
  666.             n = 0
  667.             for tag in tags:
  668.                if tag in valid_tags:
  669.                   if errors[n] == 0:
  670.                      status = 'Success'
  671.                   else:
  672.                      status = 'Error'
  673.                   if include_error:  error_msgs[tag] = self._opc.GetErrorString(errors[n])
  674.                   n += 1
  675.                else:
  676.                   status = 'Error'
  677.                # OPC servers often include newline and carriage return characters
  678.                # in their error message strings, so remove any found.
  679.                if include_error:  error_msgs[tag] = error_msgs[tag].strip('rn')
  680.                if single:
  681.                   if include_error:
  682.                      yield (status, error_msgs[tag])
  683.                   else:
  684.                      yield status
  685.                else:
  686.                   if include_error:
  687.                      yield (tag, status, error_msgs[tag])
  688.                   else:
  689.                      yield (tag, status)
  690.             opc_groups.Remove(opc_group.Name)
  691.       except pythoncom.com_error, err:
  692.          error_msg = 'write: %s' % self._get_error_str(err)
  693.          raise OPCError, error_msg
  694.    def write(self, tag_value_pairs, size=None, pause=0, include_error=False):
  695.       """Write list of (tag, value) pair(s) to the server"""
  696.       if type(tag_value_pairs) in (types.ListType, types.TupleType) and type(tag_value_pairs[0]) in (types.ListType, types.TupleType):
  697.          single = False
  698.       else:
  699.          single = True
  700.       status = self.iwrite(tag_value_pairs, size, pause, include_error)
  701.       if single:
  702.          return list(status)[0]
  703.       else:
  704.          return list(status)
  705.    def groups(self):
  706.       """Return a list of active tag groups"""
  707.       return self._groups.keys()
  708.    def remove(self, groups):
  709.       """Remove the specified tag group(s)"""
  710.       try:
  711.          pythoncom.CoInitialize()
  712.          opc_groups = self._opc.OPCGroups
  713.          if type(groups) in types.StringTypes:
  714.             groups = [groups]
  715.             single = True
  716.          else:
  717.             single = False
  718.             
  719.          status = []
  720.          for group in groups:
  721.             if self._groups.has_key(group):
  722.                for i in range(self._groups[group]):
  723.                   sub_group = '%s.%d' % (group, i)
  724.                   
  725.                   try:
  726.                      if self.trace: self.trace('RemoveGroup(%s)' % sub_group)
  727.                      errors = opc_groups.Remove(sub_group)
  728.                   except pythoncom.com_error, err:
  729.                      error_msg = 'RemoveGroup: %s' % self._get_error_str(err)
  730.                      raise OPCError, error_msg
  731.                      
  732.                   del(self._group_tags[sub_group])
  733.                   del(self._group_valid_tags[sub_group])
  734.                   del(self._group_handles_tag[sub_group])
  735.                   del(self._group_server_handles[sub_group])
  736.                del(self._groups[group])
  737.       except pythoncom.com_error, err:
  738.          error_msg = 'remove: %s' % self._get_error_str(err)
  739.          raise OPCError, error_msg
  740.       
  741.    def iproperties(self, tags, id=None):
  742.       """Iterable version of properties()"""
  743.       try:
  744.          self._update_tx_time()
  745.          pythoncom.CoInitialize()
  746.          tags, single_tag, valid = type_check(tags)
  747.          if not valid:
  748.             raise TypeError, "properties(): 'tags' parameter must be a string or a list of strings"
  749.          try:
  750.             id.remove(0)
  751.             include_name = True
  752.          except:
  753.             include_name = False
  754.          if id != None:
  755.             descriptions= []
  756.             
  757.             if isinstance(id, list) or isinstance(id, tuple):
  758.                property_id = list(id)
  759.                single_property = False
  760.             else:
  761.                property_id = [id]
  762.                single_property = True
  763.             for i in property_id:
  764.                descriptions.append('Property id %d' % i)
  765.          else:
  766.             single_property = False
  767.          properties = []
  768.          for tag in tags:
  769.             if id == None:
  770.                descriptions = []
  771.                property_id = []
  772.                count, property_id, descriptions, datatypes = self._opc.QueryAvailableProperties(tag)
  773.                # Remove bogus negative property id (not sure why this sometimes happens)
  774.                tag_properties = map(None, property_id, descriptions)
  775.                property_id = [p for p, d in tag_properties if p > 0]
  776.                descriptions = [d for p, d in tag_properties if p > 0]
  777.             property_id.insert(0, 0)
  778.             values = []
  779.             errors = []
  780.             values, errors = self._opc.GetItemProperties(tag, len(property_id)-1, property_id)
  781.  
  782.             property_id.pop(0)
  783.             values = [str(v) if type(v) == pywintypes.TimeType else v for v in values]
  784.             # Replace variant id with type strings
  785.             try:
  786.                i = property_id.index(1)
  787.                values[i] = vt[values[i]]
  788.             except:
  789.                pass
  790.             # Replace quality bits with quality strings
  791.             try:
  792.                i = property_id.index(3)
  793.                values[i] = quality_str(values[i])
  794.             except:
  795.                pass
  796.             # Replace access rights bits with strings
  797.             try:
  798.                i = property_id.index(5)
  799.                values[i] = ACCESS_RIGHTS[values[i]]
  800.             except:
  801.                pass
  802.             if id != None:
  803.                if single_property:
  804.                   if single_tag:
  805.                      tag_properties = values
  806.                   else:
  807.                      tag_properties = [values]
  808.                else:
  809.                   tag_properties = map(None, property_id, values)
  810.             else:
  811.                tag_properties = map(None, property_id, descriptions, values)
  812.                tag_properties.insert(0, (0, 'Item ID (virtual property)', tag))
  813.             if include_name:    tag_properties.insert(0, (0, tag))
  814.             if not single_tag:  tag_properties = [tuple([tag] + list(p)) for p in tag_properties]
  815.             
  816.             for p in tag_properties: yield p
  817.       except pythoncom.com_error, err:
  818.          error_msg = 'properties: %s' % self._get_error_str(err)
  819.          raise OPCError, error_msg
  820.    def properties(self, tags, id=None):
  821.       """Return list of property tuples (id, name, value) for the specified tag(s) """
  822.       if type(tags) not in (types.ListType, types.TupleType) and type(id) not in (types.NoneType, types.ListType, types.TupleType):
  823.          single = True
  824.       else:
  825.          single = False
  826.       props = self.iproperties(tags, id)
  827.       if single:
  828.          return list(props)[0]
  829.       else:
  830.          return list(props)
  831.    def ilist(self, paths='*', recursive=False, flat=False, include_type=False):
  832.       """Iterable version of list()"""
  833.       try:
  834.          self._update_tx_time()
  835.          pythoncom.CoInitialize()
  836.          
  837.          try:
  838.             browser = self._opc.CreateBrowser()
  839.          # For OPC servers that don't support browsing
  840.          except:
  841.             return
  842.          paths, single, valid = type_check(paths)
  843.          if not valid:
  844.             raise TypeError, "list(): 'paths' parameter must be a string or a list of strings"
  845.          if len(paths) == 0: paths = ['*']
  846.          nodes = {}
  847.          for path in paths:
  848.             
  849.             if flat:
  850.                browser.MoveToRoot()
  851.                browser.Filter = ''
  852.                browser.ShowLeafs(True)
  853.                pattern = re.compile('^%s$' % wild2regex(path) , re.IGNORECASE)
  854.                matches = filter(pattern.search, browser)
  855.                if include_type:  matches = [(x, node_type) for x in matches]
  856.                for node in matches: yield node
  857.                continue
  858.                
  859.             queue = []
  860.             queue.append(path)
  861.             while len(queue) > 0:
  862.                tag = queue.pop(0)
  863.             
  864.                browser.MoveToRoot()
  865.                browser.Filter = ''
  866.                pattern = None
  867.                path_str = '/'
  868.                path_list = tag.replace('.','/').split('/')
  869.                path_list = [p for p in path_list if len(p) > 0]
  870.                found_filter = False
  871.                path_postfix = '/'
  872.                for i, p in enumerate(path_list):
  873.                   if found_filter:
  874.                      path_postfix += p + '/'
  875.                   elif p.find('*') >= 0:
  876.                      pattern = re.compile('^%s$' % wild2regex(p) , re.IGNORECASE)
  877.                      found_filter = True
  878.                   elif len(p) != 0:
  879.                      pattern = re.compile('^.*$')
  880.                      browser.ShowBranches()
  881.                      # Branch node, so move down
  882.                      if len(browser) > 0:
  883.                         try:
  884.                            browser.MoveDown(p)
  885.                            path_str += p + '/'
  886.                         except:
  887.                            if i < len(path_list)-1: return
  888.                            pattern = re.compile('^%s$' % wild2regex(p) , re.IGNORECASE)
  889.                      # Leaf node, so append all remaining path parts together
  890.                      # to form a single search expression
  891.                      else:
  892.                         p = string.join(path_list[i:], '.')
  893.                         pattern = re.compile('^%s$' % wild2regex(p) , re.IGNORECASE)
  894.                         break
  895.          
  896.                browser.ShowBranches()
  897.                if len(browser) == 0:
  898.                   browser.ShowLeafs(False)
  899.                   lowest_level = True
  900.                   node_type = 'Leaf'
  901.                else:
  902.                   lowest_level = False
  903.                   node_type = 'Branch'
  904.                matches = filter(pattern.search, browser)
  905.                
  906.                if not lowest_level and recursive:
  907.                   queue += [path_str + x + path_postfix for x in matches]
  908.                else:
  909.                   if lowest_level:  matches = [exceptional(browser.GetItemID,x)(x) for x in matches]
  910.                   if include_type:  matches = [(x, node_type) for x in matches]
  911.                   for node in matches:
  912.                      if not nodes.has_key(node): yield node
  913.                      nodes[node] = True
  914.       except pythoncom.com_error, err:
  915.          error_msg = 'list: %s' % self._get_error_str(err)
  916.          raise OPCError, error_msg
  917.    def list(self, paths='*', recursive=False, flat=False, include_type=False):
  918.       """Return list of item nodes at specified path(s) (tree browser)"""
  919.       nodes = self.ilist(paths, recursive, flat, include_type)
  920.       return list(nodes)
  921.    def servers(self, opc_host='localhost'):
  922.       """Return list of available OPC servers"""
  923.       
  924.       try:
  925.          pythoncom.CoInitialize()
  926.          servers = self._opc.GetOPCServers(opc_host)
  927.          servers = [s for s in servers if s != None]
  928.          return servers
  929.       except pythoncom.com_error, err:
  930.          error_msg = 'servers: %s' % self._get_error_str(err)
  931.          raise OPCError, error_msg
  932.    def info(self):
  933.       """Return list of (name, value) pairs about the OPC server"""
  934.       try:
  935.          self._update_tx_time()
  936.          pythoncom.CoInitialize()
  937.          info_list = []
  938.          if self._open_serv:
  939.             mode = 'OpenOPC'
  940.          else:
  941.             mode = 'DCOM'
  942.          
  943.          info_list += [('Protocol', mode)]
  944.          if mode == 'OpenOPC':
  945.             info_list += [('Gateway Host', '%s:%s' % (self._open_host, self._open_port))]
  946.             info_list += [('Gateway Version', '%s' % __version__)]
  947.          info_list += [('Class', self.opc_class)]
  948.          info_list += [('Client Name', self._opc.ClientName)]
  949.          info_list += [('OPC Host', self.opc_host)]
  950.          info_list += [('OPC Server', self._opc.ServerName)]
  951.          info_list += [('State', OPC_STATUS[self._opc.ServerState])]
  952.          info_list += [('Version', '%d.%d (Build %d)' % (self._opc.MajorVersion, self._opc.MinorVersion, self._opc.BuildNumber))]
  953.          try:
  954.             browser = self._opc.CreateBrowser()
  955.             browser_type = BROWSER_TYPE[browser.Organization]
  956.          except:
  957.             browser_type = 'Not Supported'
  958.          info_list += [('Browser', browser_type)]
  959.          info_list += [('Start Time', str(self._opc.StartTime))]
  960.          info_list += [('Current Time', str(self._opc.CurrentTime))]
  961.          info_list += [('Vendor', self._opc.VendorInfo)]
  962.          return info_list
  963.       except pythoncom.com_error, err:
  964.          error_msg = 'info: %s' % self._get_error_str(err)
  965.          raise OPCError, error_msg
  966.    def ping(self):
  967.       """Check if we are still talking to the OPC server"""
  968.       try:
  969.          # Convert OPC server time to milliseconds
  970.          opc_serv_time = int(float(self._opc.CurrentTime) * 1000000.0)
  971.          if opc_serv_time == self._prev_serv_time:
  972.             return False
  973.          else:
  974.             self._prev_serv_time = opc_serv_time
  975.             return True
  976.       except pythoncom.com_error:
  977.          return False
  978.       
  979.    def _get_error_str(self, err):
  980.       """Return the error string for a OPC or COM error code"""
  981.       hr, msg, exc, arg = err
  982.       
  983.       if exc == None:
  984.          error_str = str(msg)
  985.       else:
  986.          scode = exc[5]
  987.          try:
  988.             opc_err_str = self._opc.GetErrorString(scode).strip('rn')
  989.          except:
  990.             opc_err_str = None
  991.          try:
  992.             com_err_str = pythoncom.GetScodeString(scode).strip('rn')
  993.          except:
  994.             com_err_str = None
  995.          # OPC error codes and COM error codes are overlapping concepts,
  996.          # so we combine them together into a single error message.
  997.          
  998.          if opc_err_str == None and com_err_str == None:
  999.             error_str = str(scode)
  1000.          elif opc_err_str == com_err_str:
  1001.             error_str = opc_err_str
  1002.          elif opc_err_str == None:
  1003.             error_str = com_err_str
  1004.          elif com_err_str == None:
  1005.             error_str = opc_err_str
  1006.          else:
  1007.             error_str = '%s (%s)' % (opc_err_str, com_err_str)
  1008.                  
  1009.       return error_str
  1010.    def _update_tx_time(self):
  1011.       """Update the session's last transaction time in the Gateway Service"""
  1012.       if self._open_serv:
  1013.          self._open_serv._tx_times[self._open_guid] = time.time()
  1014.    def __getitem__(self, key):
  1015.       """Read single item (tag as dictionary key)"""
  1016.       value, quality, time = self.read(key)
  1017.       return value
  1018.       
  1019.    def __setitem__(self, key, value):
  1020.       """Write single item (tag as dictionary key)"""
  1021.       self.write((key, value))
  1022.       return