Exemplo n.º 1
0
 def add_attributes(self, node_or_edge, xml_obj, data, default):
     # Add attrvalues to node or edge
     attvalues = Element('attvalues')
     if len(data) == 0:
         return data
     if 'start' in data or 'end' in data:
         mode = 'dynamic'
     else:
         mode = 'static'
     for k, v in list(data.items()):
         attr_id = self.get_attr_id(make_str(k), self.xml_type[type(v)],
                                    node_or_edge, default, mode)
         if type(v) == list:
             # dynamic data
             for val, start, end in v:
                 e = Element("attvalue")
                 e.attrib['for'] = attr_id
                 e.attrib['value'] = make_str(val)
                 e.attrib['start'] = make_str(start)
                 e.attrib['end'] = make_str(end)
                 attvalues.append(e)
         else:
             # static data
             e = Element("attvalue")
             e.attrib['for'] = attr_id
             e.attrib['value'] = make_str(v)
             attvalues.append(e)
     xml_obj.append(attvalues)
     return data
Exemplo n.º 2
0
    def add_nodes(self, G, graph_element):
        nodes_element = Element('nodes')
        for node, data in G.nodes_iter(data=True):
            node_data = data.copy()
            #
            node_id = node_data.pop('id', make_str(node))
            kw = {'id': node_id}
            label = node_data.pop('label', make_str(node))
            kw['label'] = label

            pid = node_data.pop('pid', False)
            if pid:
                kw['pid'] = pid

            # add node element with attributes
            node_element = Element("node", **kw)

            # add node element and attr subelements
            default = G.graph.get('node_default', {})
            node_data = self.add_parents(node_element, node_data)
            node_data = self.add_slices(node_element, node_data)
            node_data = self.add_viz(node_element, node_data)
            node_data = self.add_attributes("node", node_element, node_data,
                                            default)
            nodes_element.append(node_element)
        graph_element.append(nodes_element)
Exemplo n.º 3
0
    def add_nodes(self, G, graph_element):
        nodes_element = Element('nodes')
        for node,data in G.nodes_iter(data=True):
            node_data=data.copy()
            # 
            node_id=node_data.pop('id',make_str(node))
            kw={'id':node_id}
            label=node_data.pop('label',make_str(node))
            kw['label']=label

            pid=node_data.pop('pid',False)
            if pid:
                kw['pid']=pid
                
            # add node element with attributes                
            node_element = Element("node", **kw)

            # add node element and attr subelements
            default=G.graph.get('node_default',{})
            node_data=self.add_parents(node_element, node_data)
            node_data=self.add_slices(node_element, node_data)
            node_data=self.add_viz(node_element,node_data)
            node_data=self.add_attributes("node", node_element, 
                                          node_data, default)
            nodes_element.append(node_element)
        graph_element.append(nodes_element)
Exemplo n.º 4
0
 def add_attributes(self, node_or_edge, xml_obj, data, default):
     # Add attrvalues to node or edge
     attvalues=Element('attvalues')
     if len(data)==0:
         return data
     if 'start' in data or 'end' in data:
         mode='dynamic'
     else:
         mode='static'
     for k,v in list(data.items()):
         attr_id = self.get_attr_id(make_str(k), self.xml_type[type(v)],
                                    node_or_edge, default, mode)
         if type(v)==list:
             # dynamic data
             for val,start,end in v:
                 e=Element("attvalue")                
                 e.attrib['for']=attr_id
                 e.attrib['value']=make_str(val)
                 e.attrib['start']=make_str(start)
                 e.attrib['end']=make_str(end)
                 attvalues.append(e)                    
         else:
             # static data
             e=Element("attvalue")
             e.attrib['for']=attr_id
             e.attrib['value']=make_str(v)
             attvalues.append(e)
     xml_obj.append(attvalues)
     return data
Exemplo n.º 5
0
 def get_attr_id(self, title, attr_type, edge_or_node, default, mode):
     # find the id of the attribute or generate a new id
     try:
         return self.attr[edge_or_node][mode][title]
     except KeyError:
         # generate new id
         self.attr_id.next()
         new_id=str( self.attr_id ) 
         self.attr[edge_or_node][mode][title] = new_id
         
         attr_kwargs = {"id":new_id, "title":title, "type":attr_type}
         attribute=Element("attribute",**attr_kwargs)
         # add subelement for data default value if present
         default_title=default.get(title)
         if default_title is not None:
             default_element=Element("default")
             default_element.text=make_str(default_title)
             attribute.append(default_element)
         # new insert it into the XML
         attributes_element=None
         for a in self.graph_element.findall("attributes"):
             # find existing attributes element by class and mode
             a_class=a.get('class')
             a_mode=a.get('mode','static') # default mode is static
             if a_class==edge_or_node and a_mode==mode:
                 attributes_element=a
         if attributes_element is None:
             # create new attributes element 
             attr_kwargs = {"mode":mode,"class":edge_or_node}
             attributes_element=Element('attributes', **attr_kwargs)
             self.graph_element.insert(0,attributes_element)
         attributes_element.append(attribute)
     return new_id
Exemplo n.º 6
0
    def get_attr_id(self, title, attr_type, edge_or_node, default, mode):
        # find the id of the attribute or generate a new id
        try:
            return self.attr[edge_or_node][mode][title]
        except KeyError:
            # generate new id
            self.attr_id.next()
            new_id = str(self.attr_id)
            self.attr[edge_or_node][mode][title] = new_id

            attr_kwargs = {"id": new_id, "title": title, "type": attr_type}
            attribute = Element("attribute", **attr_kwargs)
            # add subelement for data default value if present
            default_title = default.get(title)
            if default_title is not None:
                default_element = Element("default")
                default_element.text = make_str(default_title)
                attribute.append(default_element)
            # new insert it into the XML
            attributes_element = None
            for a in self.graph_element.findall("attributes"):
                # find existing attributes element by class and mode
                a_class = a.get('class')
                a_mode = a.get('mode', 'static')  # default mode is static
                if a_class == edge_or_node and a_mode == mode:
                    attributes_element = a
            if attributes_element is None:
                # create new attributes element
                attr_kwargs = {"mode": mode, "class": edge_or_node}
                attributes_element = Element('attributes', **attr_kwargs)
                self.graph_element.insert(0, attributes_element)
            attributes_element.append(attribute)
        return new_id
Exemplo n.º 7
0
def _list_ctrls(device):
    global _ctrls_cache
    
    device = utils.make_str(device)

    if device in _ctrls_cache:
        return _ctrls_cache[device]
    
    output = ''
    started = time.time()
    p = subprocess.Popen('v4l2-ctl -d "%(device)s" --list-ctrls' % {
            'device': device}, shell=True, stdout=subprocess.PIPE, bufsize=1)

    fd = p.stdout.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

    while True:
        try:
            data = p.stdout.read(1024)
            if not data:
                break
        
        except IOError:
            data = ''
            time.sleep(0.01)

        output += data

        if len(output) > 10240:
            logging.warn('v4l2-ctl command returned more than 10k of output')
            break

        if time.time() - started > 3:
            logging.warn('v4l2-ctl command ran for more than 3 seconds')
            break

    try:
        # try to kill the v4l2-ctl subprocess
        p.kill()

    except:
        pass # nevermind

    controls = {}
    for line in output.split('\n'):
        if not line:
            continue
        
        match = re.match('^\s*(\w+)\s+\(\w+\)\s*\:\s*(.+)\s*', line)
        if not match:
            continue
        
        (control, properties) = match.groups()
        properties = dict([v.split('=', 1) for v in properties.split(' ') if v.count('=')])
        controls[control] = properties
    
    _ctrls_cache[device] = controls
    
    return controls
Exemplo n.º 8
0
    def init(self, xml_or_file):
        text = xml_or_file.read(
        ) if hasattr(xml_or_file, 'read') else xml_or_file
        self.xml = mod_utils.make_str(text)

        self.valid = False
        self.error = None

        self.gpx = mod_gpx.GPX()
Exemplo n.º 9
0
def device_present(device):
    device = utils.make_str(device)

    try:
        st = os.stat(device)
        return stat.S_ISCHR(st.st_mode)

    except:
        return False
Exemplo n.º 10
0
def device_present(device):
    device = utils.make_str(device)
    
    try:
        st = os.stat(device)
        return stat.S_ISCHR(st.st_mode)

    except:
        return False
Exemplo n.º 11
0
    def init(self, xml_or_file):
        text = xml_or_file.read() if hasattr(xml_or_file,
                                             'read') else xml_or_file
        self.xml = mod_utils.make_str(text)

        self.valid = False
        self.error = None

        self.gpx = mod_gpx.GPX()
Exemplo n.º 12
0
    def add_edges(self, G, graph_element):
        def edge_key_data(G):
            # helper function to unify multigraph and graph edge iterator
            if G.is_multigraph():
                for u, v, key, data in G.edges_iter(data=True, keys=True):
                    edge_data = data.copy()
                    edge_data.update(key=key)
                    edge_id = edge_data.pop('id', None)
                    if edge_id is None:
                        edge_id = self.edge_id.next()
                    yield u, v, edge_id, edge_data
            else:
                for u, v, data in G.edges_iter(data=True):
                    edge_data = data.copy()
                    edge_id = edge_data.pop('id', None)
                    if edge_id is None:
                        edge_id = next(self.edge_id)
                    yield u, v, edge_id, edge_data

        edges_element = Element('edges')
        for u, v, key, edge_data in edge_key_data(G):
            kw = {'id': make_str(key)}
            edge_weight = edge_data.pop('weight', False)
            if edge_weight:
                kw['weight'] = make_str(edge_weight)
            edge_type = edge_data.pop('type', False)
            if edge_type:
                kw['type'] = make_str(edge_type)
            edge_element = Element("edge",
                                   source=make_str(u),
                                   target=make_str(v),
                                   **kw)
            default = G.graph.get('edge_default', {})
            edge_data = self.add_viz(edge_element, edge_data)
            edge_data = self.add_attributes("edge", edge_element, edge_data,
                                            default)
            edges_element.append(edge_element)
        graph_element.append(edges_element)
Exemplo n.º 13
0
    def add_edges(self, G, graph_element):        
        def edge_key_data(G):
            # helper function to unify multigraph and graph edge iterator
            if G.is_multigraph():
                for u,v,key,data in G.edges_iter(data=True,keys=True):
                    edge_data=data.copy()
                    edge_data.update(key=key) 
                    edge_id=edge_data.pop('id',None) 
                    if edge_id is None: 
                        edge_id=self.edge_id.next() 
                    yield u,v,edge_id,edge_data 
            else:
                for u,v,data in G.edges_iter(data=True):
                    edge_data=data.copy()
                    edge_id=edge_data.pop('id',None)
                    if edge_id is None:
                        edge_id=next(self.edge_id)
                    yield u,v,edge_id,edge_data

        edges_element = Element('edges')
        for u,v,key,edge_data in edge_key_data(G):
            kw={'id':make_str(key)}
            edge_weight=edge_data.pop('weight',False)                
            if edge_weight:
                kw['weight']=make_str(edge_weight)
            edge_type=edge_data.pop('type',False)                
            if edge_type:
                kw['type']=make_str(edge_type)
            edge_element = Element("edge",
                                   source=make_str(u),target=make_str(v), 
                                   **kw)
            default=G.graph.get('edge_default',{})
            edge_data=self.add_viz(edge_element,edge_data)
            edge_data=self.add_attributes("edge", edge_element, 
                                          edge_data, default)
            edges_element.append(edge_element)                
        graph_element.append(edges_element)
Exemplo n.º 14
0
def find_persistent_device(device):
    device = utils.make_str(device)

    try:
        devs_by_id = os.listdir(_DEV_V4L_BY_ID)

    except OSError:
        return device

    for p in devs_by_id:
        p = os.path.join(_DEV_V4L_BY_ID, p)
        if os.path.realpath(p) == device:
            return p

    return device
Exemplo n.º 15
0
def find_persistent_device(device):
    device = utils.make_str(device)
    
    try:
        devs_by_id = os.listdir(_DEV_V4L_BY_ID)

    except OSError:
        return device
    
    for p in devs_by_id:
        p = os.path.join(_DEV_V4L_BY_ID, p)
        if os.path.realpath(p) == device:
            return p
    
    return device
Exemplo n.º 16
0
def _get_ctrl(device, control):
    global _ctrl_values_cache

    device = utils.make_str(device)

    if not device_present(device):
        return None

    if device in _ctrl_values_cache and control in _ctrl_values_cache[device]:
        return _ctrl_values_cache[device][control]

    controls = _list_ctrls(device)
    properties = controls.get(control)
    if properties is None:
        logging.debug('control %(control)s not found for device %(device)s' % {
            'control': control,
            'device': device
        })

        return None

    value = int(properties['value'])

    # adjust the value range
    if 'min' in properties and 'max' in properties:
        min_value = int(properties['min'])
        max_value = int(properties['max'])

        value = int(
            round((value - min_value) * 100.0 / (max_value - min_value)))

    else:
        logging.warn(
            'min and max values not found for control %(control)s of device %(device)s'
            % {
                'control': control,
                'device': device
            })

    logging.debug('control %(control)s of device %(device)s is %(value)s%%' % {
        'control': control,
        'device': device,
        'value': value
    })

    return value
Exemplo n.º 17
0
def _get_ctrl(device, control):
    global _ctrl_values_cache

    device = utils.make_str(device)

    if not device_present(device):
        return None

    if device in _ctrl_values_cache and control in _ctrl_values_cache[device]:
        return _ctrl_values_cache[device][control]

    controls = _list_ctrls(device)
    properties = controls.get(control)
    if properties is None:
        logging.warn("control %(control)s not found for device %(device)s" % {"control": control, "device": device})

        return None

    value = int(properties["value"])

    # adjust the value range
    if "min" in properties and "max" in properties:
        min_value = int(properties["min"])
        max_value = int(properties["max"])

        value = int(round((value - min_value) * 100.0 / (max_value - min_value)))

    else:
        logging.warn(
            "min and max values not found for control %(control)s of device %(device)s"
            % {"control": control, "device": device}
        )

    logging.debug(
        "control %(control)s of device %(device)s is %(value)s%%"
        % {"control": control, "device": device, "value": value}
    )

    return value
Exemplo n.º 18
0
def _get_ctrl(device, control):
    global _ctrl_values_cache
    
    device = utils.make_str(device)
    
    if not device_present(device):
        return None
    
    if device in _ctrl_values_cache and control in _ctrl_values_cache[device]:
        return _ctrl_values_cache[device][control]
    
    controls = _list_ctrls(device)
    properties = controls.get(control)
    if properties is None:
        logging.debug('control %(control)s not found for device %(device)s' % {
                'control': control, 'device': device})
        
        return None
    
    value = int(properties['value'])
    
    # adjust the value range
    if 'min' in properties and 'max' in properties:
        min_value = int(properties['min'])
        max_value = int(properties['max'])
        
        value = int(round((value - min_value) * 100.0 / (max_value - min_value)))
    
    else:
        logging.warn('min and max values not found for control %(control)s of device %(device)s' % {
                'control': control, 'device': device})
    
    logging.debug('control %(control)s of device %(device)s is %(value)s%%' % {
            'control': control, 'device': device, 'value': value})
    
    return value
Exemplo n.º 19
0
def _list_ctrls(device):
    global _ctrls_cache

    device = utils.make_str(device)

    if device in _ctrls_cache:
        return _ctrls_cache[device]

    output = ''
    started = time.time()
    p = subprocess.Popen('v4l2-ctl -d %(device)s --list-ctrls' %
                         {'device': pipes.quote(device)},
                         shell=True,
                         stdout=subprocess.PIPE,
                         bufsize=1)

    fd = p.stdout.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

    while True:
        try:
            data = p.stdout.read(1024)
            if not data:
                break

        except IOError:
            data = ''
            time.sleep(0.01)

        output += data

        if len(output) > 10240:
            logging.warn('v4l2-ctl command returned more than 10k of output')
            break

        if time.time() - started > 3:
            logging.warn('v4l2-ctl command ran for more than 3 seconds')
            break

    try:
        # try to kill the v4l2-ctl subprocess
        p.kill()

    except:
        pass  # nevermind

    controls = {}
    for line in output.split('\n'):
        if not line:
            continue

        match = re.match('^\s*(\w+)\s+\(\w+\)\s*:\s*(.+)\s*', line)
        if not match:
            continue

        (control, properties) = match.groups()
        properties = dict(
            [v.split('=', 1) for v in properties.split(' ') if v.count('=')])
        controls[control] = properties

    _ctrls_cache[device] = controls

    return controls
Exemplo n.º 20
0
def _set_ctrl(device, control, value):
    global _ctrl_values_cache

    device = utils.make_str(device)

    if not device_present(device):
        return

    controls = _list_ctrls(device)
    properties = controls.get(control)
    if properties is None:
        logging.debug('control %(control)s not found for device %(device)s' % {
            'control': control,
            'device': device
        })

        return

    _ctrl_values_cache.setdefault(device, {})[control] = value

    # adjust the value range
    if 'min' in properties and 'max' in properties:
        min_value = int(properties['min'])
        max_value = int(properties['max'])

        value = int(round(min_value + value * (max_value - min_value) / 100.0))

    else:
        logging.warn(
            'min and max values not found for control %(control)s of device %(device)s'
            % {
                'control': control,
                'device': device
            })

    logging.debug(
        'setting control %(control)s of device %(device)s to %(value)s' % {
            'control': control,
            'device': device,
            'value': value
        })

    output = ''
    started = time.time()
    cmd = 'v4l2-ctl -d %(device)s --set-ctrl %(control)s=%(value)s' % {
        'device': pipes.quote(device),
        'control': pipes.quote(control),
        'value': pipes.quote(str(value))
    }
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, bufsize=1)

    fd = p.stdout.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

    while True:
        try:
            data = p.stdout.read(1024)
            if not data:
                break

        except IOError:
            data = ''
            time.sleep(0.01)

        output += data

        if len(output) > 10240:
            logging.warn('v4l2-ctl command returned more than 10k of output')
            break

        if time.time() - started > 3:
            logging.warn('v4l2-ctl command ran for more than 3 seconds')
            break

    try:
        # try to kill the v4l2-ctl subprocess
        p.kill()

    except:
        pass  # nevermind
Exemplo n.º 21
0
def list_resolutions(device):
    global _resolutions_cache
    
    device = utils.make_str(device)
    
    if device in _resolutions_cache:
        return _resolutions_cache[device]
    
    logging.debug('listing resolutions of device %(device)s...' % {'device': device})
    
    resolutions = set()
    output = ''
    started = time.time()
    p = subprocess.Popen('v4l2-ctl -d "%(device)s" --list-formats-ext | grep -vi stepwise | grep -oE "[0-9]+x[0-9]+" || true' % {
            'device': device}, shell=True, stdout=subprocess.PIPE, bufsize=1)

    fd = p.stdout.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

    while True:
        try:
            data = p.stdout.read(1024)
            if not data:
                break

        except IOError:
            data = ''
            time.sleep(0.01)

        output += data

        if len(output) > 10240:
            logging.warn('v4l2-ctl command returned more than 10k of output')
            break
        
        if time.time() - started > 3:
            logging.warn('v4l2-ctl command ran for more than 3 seconds')
            break
    
    try:
        # try to kill the v4l2-ctl subprocess
        p.kill()
    
    except:
        pass # nevermind

    for pair in output.split('\n'):
        pair = pair.strip()
        if not pair:
            continue
        
        width, height = pair.split('x')
        width = int(width)
        height = int(height)

        if (width, height) in resolutions:
            continue # duplicate resolution

        if width < 96 or height < 96: # some reasonable minimal values
            continue
        
        if width % 16 or height % 16: # ignore non-modulo 16 resolutions
            continue

        resolutions.add((width, height))
        
        logging.debug('found resolution %(width)sx%(height)s for device %(device)s' % {
                'device': device, 'width': width, 'height': height})
    
    if not resolutions:
        logging.debug('no resolutions found for device %(device)s, using common values' % {'device': device})

        # no resolution returned by v4l2-ctl call, add common default resolutions
        resolutions = utils.COMMON_RESOLUTIONS

    resolutions = list(sorted(resolutions, key=lambda r: (r[0], r[1])))
    _resolutions_cache[device] = resolutions
    
    return resolutions
Exemplo n.º 22
0
def _set_ctrl(device, control, value):
    global _ctrl_values_cache
    
    device = utils.make_str(device)
    
    if not device_present(device):
        return

    controls = _list_ctrls(device)
    properties = controls.get(control)
    if properties is None:
        logging.debug('control %(control)s not found for device %(device)s' % {
                'control': control, 'device': device})
        
        return
    
    _ctrl_values_cache.setdefault(device, {})[control] = value

    # adjust the value range
    if 'min' in properties and 'max' in properties:
        min_value = int(properties['min'])
        max_value = int(properties['max'])
        
        value = int(round(min_value + value * (max_value - min_value) / 100.0))
    
    else:
        logging.warn('min and max values not found for control %(control)s of device %(device)s' % {
                'control': control, 'device': device})
    
    logging.debug('setting control %(control)s of device %(device)s to %(value)s' % {
            'control': control, 'device': device, 'value': value})

    output = ''
    started = time.time()
    p = subprocess.Popen('v4l2-ctl -d "%(device)s" --set-ctrl %(control)s=%(value)s' % {
            'device': device, 'control': control, 'value': value}, shell=True, stdout=subprocess.PIPE, bufsize=1)

    fd = p.stdout.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

    while True:
        try:
            data = p.stdout.read(1024)
            if not data:
                break
        
        except IOError:
            data = ''
            time.sleep(0.01)

        output += data

        if len(output) > 10240:
            logging.warn('v4l2-ctl command returned more than 10k of output')
            break

        if time.time() - started > 3:
            logging.warn('v4l2-ctl command ran for more than 3 seconds')
            break

    try:
        # try to kill the v4l2-ctl subprocess
        p.kill()

    except:
        pass # nevermind
Exemplo n.º 23
0
def list_resolutions(device):
    import motionctl

    global _resolutions_cache

    device = utils.make_str(device)

    if device in _resolutions_cache:
        return _resolutions_cache[device]

    logging.debug('listing resolutions of device %(device)s...' %
                  {'device': device})

    resolutions = set()
    output = ''
    started = time.time()
    cmd = 'v4l2-ctl -d %(device)s --list-formats-ext | grep -vi stepwise | grep -oE "[0-9]+x[0-9]+" || true' % {
        'device': pipes.quote(device)
    }

    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, bufsize=1)

    fd = p.stdout.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

    while True:
        try:
            data = p.stdout.read(1024)
            if not data:
                break

        except IOError:
            data = ''
            time.sleep(0.01)

        output += data

        if len(output) > 10240:
            logging.warn('v4l2-ctl command returned more than 10k of output')
            break

        if time.time() - started > _V4L2_TIMEOUT:
            logging.warn('v4l2-ctl command ran for more than %s seconds' %
                         _V4L2_TIMEOUT)
            break

    try:
        # try to kill the v4l2-ctl subprocess
        p.kill()

    except:
        pass  # nevermind

    for pair in output.split('\n'):
        pair = pair.strip()
        if not pair:
            continue

        width, height = pair.split('x')
        width = int(width)
        height = int(height)

        if (width, height) in resolutions:
            continue  # duplicate resolution

        if width < 96 or height < 96:  # some reasonable minimal values
            continue

        if not motionctl.resolution_is_valid(width, height):
            continue

        resolutions.add((width, height))

        logging.debug(
            'found resolution %(width)sx%(height)s for device %(device)s' % {
                'device': device,
                'width': width,
                'height': height
            })

    if not resolutions:
        logging.debug(
            'no resolutions found for device %(device)s, using common values' %
            {'device': device})

        # no resolution returned by v4l2-ctl call, add common default resolutions
        resolutions = utils.COMMON_RESOLUTIONS
        resolutions = [
            r for r in resolutions if motionctl.resolution_is_valid(*r)
        ]

    resolutions = list(sorted(resolutions, key=lambda r: (r[0], r[1])))
    _resolutions_cache[device] = resolutions

    return resolutions