Exemple #1
0
    def _read_health(self, tags):
        """Return values of special system health monitoring tags"""

        self._update_tx_time()
        tags, single, valid = type_check(tags)

        time_str = time.strftime("%x %H:%M:%S")
        results = []

        for t in tags:
            if t == "@MemFree":
                value = SystemHealth.mem_free()
            elif t == "@MemUsed":
                value = SystemHealth.mem_used()
            elif t == "@MemTotal":
                value = SystemHealth.mem_total()
            elif t == "@MemPercent":
                value = SystemHealth.mem_percent()
            elif t == "@DiskFree":
                value = SystemHealth.disk_free()
            elif t == "@SineWave":
                value = SystemHealth.sine_wave()
            elif t == "@SawWave":
                value = SystemHealth.saw_wave()

            elif t == "@CpuUsage":
                if self.cpu == None:
                    self.cpu = SystemHealth.CPU()
                    time.sleep(0.1)
                value = self.cpu.get_usage()

            else:
                value = None

                m = re.match("@TaskMem\((.*?)\)", t)
                if m:
                    image_name = m.group(1)
                    value = SystemHealth.task_mem(image_name)

                m = re.match("@TaskCpu\((.*?)\)", t)
                if m:
                    image_name = m.group(1)
                    value = SystemHealth.task_cpu(image_name)

                m = re.match("@TaskExists\((.*?)\)", t)
                if m:
                    image_name = m.group(1)
                    value = SystemHealth.task_exists(image_name)

            if value == None:
                quality = "Error"
            else:
                quality = "Good"

            if single:
                results.append((value, quality, time_str))
            else:
                results.append((t, value, quality, time_str))

        return results
Exemple #2
0
   def read(self, tags=None, group=None, size=None, pause=0, source='hybrid', update=-1, timeout=5000, sync=False, include_error=False, rebuild=False):
      """Return list of (value, quality, time) tuples for the specified tag(s)"""

      tags_list, single, valid = type_check(tags)
      if not valid:
         raise TypeError("read(): 'tags' parameter must be a string or a list of strings")

      num_health_tags = len([t for t in tags_list if t[:1] == '@'])
      num_opc_tags = len([t for t in tags_list if t[:1] != '@'])

      if num_health_tags > 0:
         if num_opc_tags > 0:
            raise TypeError("read(): system health and OPC tags cannot be included in the same group")
         results = self._read_health(tags)
      else:
         results = self.iread(tags, group, size, pause, source, update, timeout, sync, include_error, rebuild)

      if single:
         return list(results)[0]
      else:
         return list(results)
Exemple #3
0
   def iread(self, tags=None, group=None, size=None, pause=0, source='hybrid', update=-1, timeout=5000, sync=False, include_error=False, rebuild=False):
      """
      Iterable version of read()
      """
      def add_items(tags):
         names = list(tags)

         names.insert(0,0)
         errors = []
          
         if self.trace: self.trace('Validate(%s)' % tags2trace(names))
          
         try:
            errors = opc_items.Validate(len(names)-1, names)
         except:
            pass
             
         valid_tags = []
         valid_values = []
         client_handles = []

         if not sub_group in self._group_handles_tag:
            self._group_handles_tag[sub_group] = {}
            n = 0
         elif len(self._group_handles_tag[sub_group]) > 0:
            n = max(self._group_handles_tag[sub_group]) + 1
         else:
            n = 0
          
         for i, tag in enumerate(tags):
            if errors[i] == 0:
               valid_tags.append(tag)
               client_handles.append(n)
               self._group_handles_tag[sub_group][n] = tag 
               n += 1
            elif include_error:
               error_msgs[tag] = self._opc.GetErrorString(errors[i])
             
            if self.trace and errors[i] != 0: self.trace('%s failed validation' % tag)

         client_handles.insert(0,0)
         valid_tags.insert(0,0)
         server_handles = []
         errors = []

         if self.trace: self.trace('AddItems(%s)' % tags2trace(valid_tags))
       
         try:
            server_handles, errors = opc_items.AddItems(len(client_handles)-1, valid_tags, client_handles)
         except:
            pass
             
         valid_tags_tmp = []
         server_handles_tmp = []
         valid_tags.pop(0)

         if not sub_group in self._group_server_handles:
            self._group_server_handles[sub_group] = {}
       
         for i, tag in enumerate(valid_tags):
            if errors[i] == 0:
               valid_tags_tmp.append(tag)
               server_handles_tmp.append(server_handles[i])
               self._group_server_handles[sub_group][tag] = server_handles[i]
            elif include_error:
               error_msgs[tag] = self._opc.GetErrorString(errors[i])
       
         valid_tags = valid_tags_tmp
         server_handles = server_handles_tmp

         return valid_tags, server_handles

      def remove_items(tags):
         if self.trace: self.trace('RemoveItems(%s)' % tags2trace(['']+tags))
         server_handles = [self._group_server_handles[sub_group][tag] for tag in tags]
         server_handles.insert(0,0)
         errors = []

         try:
            errors = opc_items.Remove(len(server_handles)-1, server_handles)
         except pythoncom.com_error as err:
            error_msg = 'RemoveItems: %s' % self._get_error_str(err)
            raise OPCError(error_msg)

      try:
         self._update_tx_time()
         pythoncom.CoInitialize()
         
         sync = True
         update = -1
         tags, single, valid = type_check(tags) #doesnt need to be called everytime, we check on first pass
         
         if not valid:
            raise TypeError("iread(): 'tags' parameter must be a string or a list of strings")
         
         #start here
         # Group exists
         if group in self._groups and not rebuild:
            num_groups = self._groups[group]
            data_source = SOURCE_CACHE #? need to specify this every time?

         # Group non-existant
         else:
            if size:
               # Break-up tags into groups of 'size' tags
               tag_groups = [tags[i:i+size] for i in range(0, len(tags), size)]
            else:
               tag_groups = [tags]
               
            num_groups = len(tag_groups)
            data_source = SOURCE_DEVICE

         results = []

         for gid in range(num_groups):
            if gid > 0 and pause > 0: time.sleep(pause/1000.0)
            
            error_msgs = {}
            opc_groups = self._opc.OPCGroups
            opc_groups.DefaultGroupUpdateRate = update  #?? need to specify this everytime?

            # Anonymous group
            if group == None:
               try:
                  if self.trace: self.trace('AddGroup()')
                  opc_group = opc_groups.Add()
               except pythoncom.com_error as err:
                  error_msg = 'AddGroup: %s' % self._get_error_str(err)
                  raise OPCError(error_msg)
               sub_group = group
               new_group = True
            else:
               sub_group = '%s.%d' % (group, gid)

               # Existing named group
               try:
                  if self.trace: self.trace('GetOPCGroup(%s)' % sub_group)
                  opc_group = opc_groups.GetOPCGroup(sub_group)
                  new_group = False

               # New named group
               except:
                  try:
                     if self.trace: self.trace('AddGroup(%s)' % sub_group)
                     opc_group = opc_groups.Add(sub_group)
                  except pythoncom.com_error as err:
                     error_msg = 'AddGroup: %s' % self._get_error_str(err)
                     raise OPCError(error_msg)
                  self._groups[str(group)] = len(tag_groups)
                  new_group = True
                  
            opc_items = opc_group.OPCItems

            if new_group:
                #opc_group.IsSubscribed = 1
                opc_group.IsActive = 1 # do we need to specify this every time?

                tags = tag_groups[gid]
               
                valid_tags, server_handles = add_items(tags)
               
                self._group_tags[sub_group] = tags
                self._group_valid_tags[sub_group] = valid_tags

            # Rebuild existing group
            elif rebuild:
               tags = tag_groups[gid]

               valid_tags = self._group_valid_tags[sub_group]
               add_tags = [t for t in tags if t not in valid_tags]
               del_tags = [t for t in valid_tags if t not in tags]

               if len(add_tags) > 0:
                  valid_tags, server_handles = add_items(add_tags)
                  valid_tags = self._group_valid_tags[sub_group] + valid_tags

               if len(del_tags) > 0:
                  remove_items(del_tags)
                  valid_tags = [t for t in valid_tags if t not in del_tags]

               self._group_tags[sub_group] = tags
               self._group_valid_tags[sub_group] = valid_tags
               
               if source == 'hybrid': data_source = SOURCE_DEVICE

            # Existing group
            else:
               tags = self._group_tags[sub_group]
               valid_tags = self._group_valid_tags[sub_group]
               if sync:
                  server_handles = [item.ServerHandle for item in opc_items] # grabs server handles from an existing group. do these actually change?
                  #server_handles = self._group_server_handles[sub_group]
                  if self.trace: self.trace('Server Handles(%s)' % self._group_server_handles[sub_group] )
            #End Here
            #--------

            tag_value = {}
            tag_quality = {}
            tag_time = {}
            tag_error = {}
               
            # Sync Read
            if sync:   
               values = []
               errors = []
               qualities = []
               timestamps= []
               
               if len(valid_tags) > 0:
                   server_handles.insert(0,0)
                   
                   if source != 'hybrid':
                      data_source = SOURCE_CACHE if source == 'cache' else SOURCE_DEVICE

                   if self.trace: self.trace('SyncRead(%s)' % data_source)
                   
                   try:
                      values, errors, qualities, timestamps = opc_group.SyncRead(data_source, len(server_handles)-1, server_handles)
                   except pythoncom.com_error as err:
                      error_msg = 'SyncRead: %s' % self._get_error_str(err)
                      raise OPCError(error_msg)

                   for i,tag in enumerate(valid_tags):
                      tag_value[tag] = values[i]
                      tag_quality[tag] = qualities[i]
                      tag_time[tag] = timestamps[i]
                      tag_error[tag] = errors[i]

            
            for tag in tags:
               if tag in tag_value:
                  if (sync and tag_error[tag] == 0):
                     value = tag_value[tag]
                     if type(value) == pywintypes.TimeType:
                        value = str(value)
                     quality = quality_str(tag_quality[tag])
                     #look to apply time conversion here!??
                     timestamp = str(tag_time[tag])
                  else:
                     value = None
                     quality = 'Error'
                     timestamp = None
                  if include_error:
                     error_msgs[tag] = self._opc.GetErrorString(tag_error[tag]).strip('\r\n')
               else:
                  value = None
                  quality = 'Error'
                  timestamp = None
                  if include_error and not tag in error_msgs:
                     error_msgs[tag] = ''

               if single:
                  if include_error:
                     yield (value, quality, timestamp, error_msgs[tag])
                  else:
                     yield (value, quality, timestamp)
               else:
                  if include_error:
                     yield (tag, value, quality, timestamp, error_msgs[tag])
                  else:
                     yield (tag, value, quality, timestamp)

            if group == None:
               try:
                  if self.trace: self.trace('RemoveGroup(%s)' % opc_group.Name)
                  opc_groups.Remove(opc_group.Name)

               except pythoncom.com_error as err:
                  error_msg = 'RemoveGroup: %s' % self._get_error_str(err)
                  raise OPCError(error_msg)

      except pythoncom.com_error as err:
         error_msg = 'read: %s' % self._get_error_str(err)
         raise OPCError(error_msg)
Exemple #4
0
   def iread(self, tags=None, group=None, size=None, pause=0, source='hybrid', update=-1, timeout=5000, sync=False, include_error=False, rebuild=False):
      """
      Iterable version of read()
      """
      def add_items(tags):
         names = list(tags)

         names.insert(0,0)
         errors = []
          
         if self.trace: self.trace('Validate(%s)' % tags2trace(names))
          
         try:
            errors = opc_items.Validate(len(names)-1, names)
         except:
            pass
             
         valid_tags = []
         valid_values = []
         client_handles = []

         if not sub_group in self._group_handles_tag:
            self._group_handles_tag[sub_group] = {}
            n = 0
         elif len(self._group_handles_tag[sub_group]) > 0:
            n = max(self._group_handles_tag[sub_group]) + 1
         else:
            n = 0
          
         for i, tag in enumerate(tags):
            if errors[i] == 0:
               valid_tags.append(tag)
               client_handles.append(n)
               self._group_handles_tag[sub_group][n] = tag 
               n += 1
            elif include_error:
               error_msgs[tag] = self._opc.GetErrorString(errors[i])
             
            if self.trace and errors[i] != 0: self.trace('%s failed validation' % tag)

         client_handles.insert(0,0)
         valid_tags.insert(0,0)
         server_handles = []
         errors = []

         if self.trace: self.trace('AddItems(%s)' % tags2trace(valid_tags))
       
         try:
            server_handles, errors = opc_items.AddItems(len(client_handles)-1, valid_tags, client_handles)
         except:
            pass
             
         valid_tags_tmp = []
         server_handles_tmp = []
         valid_tags.pop(0)

         if not sub_group in self._group_server_handles:
            self._group_server_handles[sub_group] = {}
       
         for i, tag in enumerate(valid_tags):
            if errors[i] == 0:
               valid_tags_tmp.append(tag)
               server_handles_tmp.append(server_handles[i])
               self._group_server_handles[sub_group][tag] = server_handles[i]
            elif include_error:
               error_msgs[tag] = self._opc.GetErrorString(errors[i])
       
         valid_tags = valid_tags_tmp
         server_handles = server_handles_tmp

         return valid_tags, server_handles

      def remove_items(tags):
         if self.trace: self.trace('RemoveItems(%s)' % tags2trace(['']+tags))
         server_handles = [self._group_server_handles[sub_group][tag] for tag in tags]
         server_handles.insert(0,0)
         errors = []

         try:
            errors = opc_items.Remove(len(server_handles)-1, server_handles)
         except pythoncom.com_error as err:
            error_msg = 'RemoveItems: %s' % self._get_error_str(err)
            raise OPCError(error_msg)

   
      try:
         self._update_tx_time()
         pythoncom.CoInitialize()
         
         if include_error:
            sync = True
            
         if sync:
            update = -1

         tags, single, valid = type_check(tags)
         if not valid:
            raise TypeError("iread(): 'tags' parameter must be a string or a list of strings")

         # Group exists
         if group in self._groups and not rebuild:
            num_groups = self._groups[group]
            data_source = SOURCE_CACHE

         # Group non-existant
         else:
            if size:
               # Break-up tags into groups of 'size' tags
               tag_groups = [tags[i:i+size] for i in range(0, len(tags), size)]
            else:
               tag_groups = [tags]
               
            num_groups = len(tag_groups)
            data_source = SOURCE_DEVICE

         results = []

         for gid in range(num_groups):
            if gid > 0 and pause > 0: time.sleep(pause/1000.0)
            
            error_msgs = {}
            opc_groups = self._opc.OPCGroups
            opc_groups.DefaultGroupUpdateRate = update

            # Anonymous group
            if group == None:
               try:
                  if self.trace: self.trace('AddGroup()')
                  opc_group = opc_groups.Add()
               except pythoncom.com_error as err:
                  error_msg = 'AddGroup: %s' % self._get_error_str(err)
                  raise OPCError(error_msg)
               sub_group = group
               new_group = True
            else:
               sub_group = '%s.%d' % (group, gid)

               # Existing named group
               try:
                  if self.trace: self.trace('GetOPCGroup(%s)' % sub_group)
                  opc_group = opc_groups.GetOPCGroup(sub_group)
                  new_group = False

               # New named group
               except:
                  try:
                     if self.trace: self.trace('AddGroup(%s)' % sub_group)
                     opc_group = opc_groups.Add(sub_group)
                  except pythoncom.com_error as err:
                     error_msg = 'AddGroup: %s' % self._get_error_str(err)
                     raise OPCError(error_msg)
                  self._groups[str(group)] = len(tag_groups)
                  new_group = True
                  
            opc_items = opc_group.OPCItems

            if new_group:
               opc_group.IsSubscribed = 1
               opc_group.IsActive = 1
               if not sync:
                  if self.trace: self.trace('WithEvents(%s)' % opc_group.Name)
                  #global current_client
                  #current_client = self
                  GroupEvents.client = self
                  self._group_hooks[opc_group.Name] = win32com.client.WithEvents(opc_group, GroupEvents)
                  #self._group_hooks[opc_group.Name] = win32com.client.WithEvents(opc_group, GroupEvent)

               tags = tag_groups[gid]
               
               valid_tags, server_handles = add_items(tags)
               
               self._group_tags[sub_group] = tags
               self._group_valid_tags[sub_group] = valid_tags

            # Rebuild existing group
            elif rebuild:
               tags = tag_groups[gid]

               valid_tags = self._group_valid_tags[sub_group]
               add_tags = [t for t in tags if t not in valid_tags]
               del_tags = [t for t in valid_tags if t not in tags]

               if len(add_tags) > 0:
                  valid_tags, server_handles = add_items(add_tags)
                  valid_tags = self._group_valid_tags[sub_group] + valid_tags

               if len(del_tags) > 0:
                  remove_items(del_tags)
                  valid_tags = [t for t in valid_tags if t not in del_tags]

               self._group_tags[sub_group] = tags
               self._group_valid_tags[sub_group] = valid_tags
               
               if source == 'hybrid': data_source = SOURCE_DEVICE

            # Existing group
            else:
               tags = self._group_tags[sub_group]
               valid_tags = self._group_valid_tags[sub_group]
               if sync:
                  server_handles = [item.ServerHandle for item in opc_items]

            tag_value = {}
            tag_quality = {}
            tag_time = {}
            tag_error = {}
               
            # Sync Read
            if sync:   
               values = []
               errors = []
               qualities = []
               timestamps= []
               
               if len(valid_tags) > 0:
                   server_handles.insert(0,0)
                   
                   if source != 'hybrid':
                      data_source = SOURCE_CACHE if source == 'cache' else SOURCE_DEVICE

                   if self.trace: self.trace('SyncRead(%s)' % data_source)
                   
                   try:
                      values, errors, qualities, timestamps = opc_group.SyncRead(data_source, len(server_handles)-1, server_handles)
                   except pythoncom.com_error as err:
                      error_msg = 'SyncRead: %s' % self._get_error_str(err)
                      raise OPCError(error_msg)

                   for i,tag in enumerate(valid_tags):
                      tag_value[tag] = values[i]
                      tag_quality[tag] = qualities[i]
                      tag_time[tag] = timestamps[i]
                      tag_error[tag] = errors[i]

            # Async Read
            else:
               if len(valid_tags) > 0:
                  if self._tx_id >= 0xFFFF:
                      self._tx_id = 0
                  self._tx_id += 1
      
                  if source != 'hybrid':
                     data_source = SOURCE_CACHE if source == 'cache' else SOURCE_DEVICE

                  if self.trace: self.trace('AsyncRefresh(%s)' % data_source)

                  try:
                     opc_group.AsyncRefresh(data_source, self._tx_id)
                     #svr_tx_id = opc_group.AsyncRefresh(data_source, self._tx_id)
                     #opc_group.AsyncRead(data_source, self._tx_id)
                     #opc_group.AsyncRead(len(server_handles)-1, server_handles, self._tx_id)
                  except pythoncom.com_error as err:
                     error_msg = 'AsyncRefresh: %s' % self._get_error_str(err)
                     raise OPCError(error_msg)

                  tx_id = 0
                  start = time.time() * 1000
                  
                  while tx_id != self._tx_id:
                     now = time.time() * 1000
                     if now - start > timeout:
                        raise TimeoutError('Callback: Timeout waiting for data')

                     if self.callback_queue.empty():
                        pythoncom.PumpWaitingMessages()
                     else:
                        tx_id, handles, values, qualities, timestamps = self.callback_queue.get()
                                                
                  for i,h in enumerate(handles):
                     tag = self._group_handles_tag[sub_group][h]
                     tag_value[tag] = values[i]
                     tag_quality[tag] = qualities[i]
                     tag_time[tag] = timestamps[i]
            
            for tag in tags:
               if tag in tag_value:
                  if (not sync and len(valid_tags) > 0) or (sync and tag_error[tag] == 0):
                     value = tag_value[tag]
                     if type(value) == pywintypes.TimeType:
                        value = str(value)
                     quality = quality_str(tag_quality[tag])
                     timestamp = str(tag_time[tag])
                  else:
                     value = None
                     quality = 'Error'
                     timestamp = None
                  if include_error:
                     error_msgs[tag] = self._opc.GetErrorString(tag_error[tag]).strip('\r\n')
               else:
                  value = None
                  quality = 'Error'
                  timestamp = None
                  if include_error and not tag in error_msgs:
                     error_msgs[tag] = ''

               if single:
                  if include_error:
                     yield (value, quality, timestamp, error_msgs[tag])
                  else:
                     yield (value, quality, timestamp)
               else:
                  if include_error:
                     yield (tag, value, quality, timestamp, error_msgs[tag])
                  else:
                     yield (tag, value, quality, timestamp)

            if group == None:
               try:
                  if not sync and opc_group.Name in self._group_hooks:
                     if self.trace: self.trace('CloseEvents(%s)' % opc_group.Name)
                     self._group_hooks[opc_group.Name].close()

                  if self.trace: self.trace('RemoveGroup(%s)' % opc_group.Name)
                  opc_groups.Remove(opc_group.Name)

               except pythoncom.com_error as err:
                  error_msg = 'RemoveGroup: %s' % self._get_error_str(err)
                  raise OPCError(error_msg)

      except pythoncom.com_error as err:
         error_msg = 'read: %s' % self._get_error_str(err)
         raise OPCError(error_msg)
Exemple #5
0
    def ilist(self,
              paths="*",
              recursive=False,
              flat=False,
              include_type=False):
        """Iterable version of list()"""

        try:
            self._update_tx_time()
            pythoncom.CoInitialize()

            try:
                browser = self._opc.CreateBrowser()
            # For OPC servers that don't support browsing
            except:
                return

            paths, single, valid = type_check(paths)
            if not valid:
                raise TypeError(
                    "list(): 'paths' parameter must be a string or a list of strings"
                )

            if len(paths) == 0:
                paths = ["*"]
            nodes = {}

            for path in paths:

                if flat:
                    browser.MoveToRoot()
                    browser.Filter = ""
                    browser.ShowLeafs(True)

                    pattern = re.compile("^%s$" % wild2regex(path),
                                         re.IGNORECASE)
                    matches = filter(pattern.search, browser)
                    if include_type:
                        matches = [(x, node_type) for x in matches]

                    for node in matches:
                        yield node
                    continue

                queue = []
                queue.append(path)

                while len(queue) > 0:
                    tag = queue.pop(0)

                    browser.MoveToRoot()
                    browser.Filter = ""
                    pattern = None

                    path_str = "/"
                    path_list = tag.replace(".", "/").split("/")
                    path_list = [p for p in path_list if len(p) > 0]
                    found_filter = False
                    path_postfix = "/"

                    for i, p in enumerate(path_list):
                        if found_filter:
                            path_postfix += p + "/"
                        elif p.find("*") >= 0:
                            pattern = re.compile("^%s$" % wild2regex(p),
                                                 re.IGNORECASE)
                            found_filter = True
                        elif len(p) != 0:
                            pattern = re.compile("^.*$")
                            browser.ShowBranches()

                            # Branch node, so move down
                            if len(browser) > 0:
                                try:
                                    browser.MoveDown(p)
                                    path_str += p + "/"
                                except:
                                    if i < len(path_list) - 1:
                                        return
                                    pattern = re.compile(
                                        "^%s$" % wild2regex(p), re.IGNORECASE)

                            # Leaf node, so append all remaining path parts together
                            # to form a single search expression
                            else:
                                p = string.join(path_list[i:], ".")
                                pattern = re.compile("^%s$" % wild2regex(p),
                                                     re.IGNORECASE)
                                break

                    browser.ShowBranches()

                    if len(browser) == 0:
                        browser.ShowLeafs(False)
                        lowest_level = True
                        node_type = "Leaf"
                    else:
                        lowest_level = False
                        node_type = "Branch"

                    matches = filter(pattern.search, browser)

                    if not lowest_level and recursive:
                        queue += [path_str + x + path_postfix for x in matches]
                    else:
                        if lowest_level:
                            matches = [
                                exceptional(browser.GetItemID, x)(x)
                                for x in matches
                            ]
                        if include_type:
                            matches = [(x, node_type) for x in matches]
                        for node in matches:
                            if not node in nodes:
                                yield node
                            nodes[node] = True

        except pythoncom.com_error as err:
            error_msg = "list: %s" % self._get_error_str(err)
            raise OPCError(error_msg)
Exemple #6
0
    def iproperties(self, tags, id=None):
        """Iterable version of properties()"""

        try:
            self._update_tx_time()
            pythoncom.CoInitialize()

            tags, single_tag, valid = type_check(tags)
            if not valid:
                raise TypeError(
                    "properties(): 'tags' parameter must be a string or a list of strings"
                )

            try:
                id.remove(0)
                include_name = True
            except:
                include_name = False

            if id != None:
                descriptions = []

                if isinstance(id, list) or isinstance(id, tuple):
                    property_id = list(id)
                    single_property = False
                else:
                    property_id = [id]
                    single_property = True

                for i in property_id:
                    descriptions.append("Property id %d" % i)
            else:
                single_property = False

            properties = []

            for tag in tags:

                if id == None:
                    descriptions = []
                    property_id = []
                    (
                        count,
                        property_id,
                        descriptions,
                        datatypes,
                    ) = self._opc.QueryAvailableProperties(tag)

                    # Remove bogus negative property id (not sure why this sometimes happens)
                    tag_properties = list(
                        map(lambda x, y: (x, y), property_id, descriptions))
                    property_id = [p for p, d in tag_properties if p > 0]
                    descriptions = [d for p, d in tag_properties if p > 0]

                property_id.insert(0, 0)
                values = []
                errors = []
                values, errors = self._opc.GetItemProperties(
                    tag,
                    len(property_id) - 1, property_id)

                property_id.pop(0)
                values = [
                    str(v) if type(v) == pywintypes.TimeType else v
                    for v in values
                ]

                # Replace variant id with type strings
                try:
                    i = property_id.index(1)
                    values[i] = vt[values[i]]
                except:
                    pass

                # Replace quality bits with quality strings
                try:
                    i = property_id.index(3)
                    values[i] = quality_str(values[i])
                except:
                    pass

                # Replace access rights bits with strings
                try:
                    i = property_id.index(5)
                    values[i] = ACCESS_RIGHTS[values[i]]
                except:
                    pass

                if id != None:
                    if single_property:
                        if single_tag:
                            tag_properties = values
                        else:
                            tag_properties = [values]
                    else:
                        tag_properties = list(
                            map(lambda x, y: (x, y), property_id, values))
                else:
                    tag_properties = list(
                        map(lambda x, y, z: (x, y, z), property_id,
                            descriptions, values))
                    tag_properties.insert(
                        0, (0, "Item ID (virtual property)", tag))

                if include_name:
                    tag_properties.insert(0, (0, tag))
                if not single_tag:
                    tag_properties = [
                        tuple([tag] + list(p)) for p in tag_properties
                    ]

                for p in tag_properties:
                    yield p

        except pythoncom.com_error as err:
            error_msg = "properties: %s" % self._get_error_str(err)
            raise OPCError(error_msg)