def log_all_params(): for index in range(10000): r = request(uavcan.protocol.param.GetSet.Request(index=index)) if not r.name: break logger.info('Param %-30r %r' % (r.name.decode(), getattr(r.value, uavcan.get_active_union_field(r.value))))
def handle_param_write(self, req): request = uavcan.protocol.param.GetSet.Request( name=req.name, value=uavcan.protocol.param.Value( integer_value=req.value ), ) _uav_outgoing.put(request) try: response = _uav_incoming_param.get( block=True, timeout=PARAMETER_REQ_TIMEOUT, ) if ( uavcan.get_active_union_field( response.value ) != "empty" ): return ( SUCCESS if response.value.integer_value == req.value else FAILURE ) except queue.Empty: pass return FAILURE
def __init__(self, parent, node, target_node_id): super(ConfigParams, self).__init__(parent) self.setTitle('Configuration parameters (double click to change)') self._node = node self._target_node_id = target_node_id self._read_all_button = make_icon_button( 'refresh', 'Fetch all config parameters from the node', self, text='Fetch All', on_clicked=self._do_reload) opcodes = uavcan.protocol.param.ExecuteOpcode.Request() self._save_button = \ make_icon_button('database', 'Commit configuration to the non-volatile storage [OPCODE_SAVE]', self, text='Store All', on_clicked=partial(self._do_execute_opcode, opcodes.OPCODE_SAVE)) self._erase_button = \ make_icon_button('eraser', 'Clear the non-volatile configuration storage [OPCODE_ERASE]', self, text='Erase All', on_clicked=partial(self._do_execute_opcode, opcodes.OPCODE_ERASE)) columns = [ BasicTable.Column('Idx', lambda m: m[0]), BasicTable.Column('Name', lambda m: m[1].name, resize_mode=QHeaderView.Stretch), BasicTable.Column( 'Type', lambda m: uavcan.get_active_union_field(m[1].value).replace( '_value', '')), BasicTable.Column('Value', lambda m: render_union(m[1].value), resize_mode=QHeaderView.Stretch), BasicTable.Column('Default', lambda m: render_union(m[1].default_value)), BasicTable.Column('Min', lambda m: render_union(m[1].min_value)), BasicTable.Column('Max', lambda m: render_union(m[1].max_value)), ] self._table = BasicTable(self, columns, multi_line_rows=True, font=get_monospace_font()) self._table.cellDoubleClicked.connect( lambda row, col: self._do_edit_param(row)) self._table.on_enter_pressed = self._on_cell_enter_pressed self._params = [] layout = QVBoxLayout(self) controls_layout = QHBoxLayout(self) controls_layout.addWidget(self._read_all_button, 1) controls_layout.addWidget(self._save_button, 1) controls_layout.addWidget(self._erase_button, 1) layout.addLayout(controls_layout) layout.addWidget(self._table) self.setLayout(layout)
def _extract_struct_fields(m): if isinstance(m, uavcan.transport.CompoundValue): out = CompactMessage(uavcan.get_uavcan_data_type(m).full_name) for field_name, field in uavcan.get_fields(m).items(): if uavcan.is_union( m) and uavcan.get_active_union_field(m) != field_name: continue val = _extract_struct_fields(field) if val is not None: out._add_field(field_name, val) return out elif isinstance(m, uavcan.transport.ArrayValue): # cannot say I'm breaking the rules container = bytes if uavcan.get_uavcan_data_type( m).is_string_like else list # if I can glue them back together return container( filter(lambda x: x is not None, (_extract_struct_fields(item) for item in m))) elif isinstance(m, uavcan.transport.PrimitiveValue): return m.value elif isinstance(m, (int, float, bool)): return m elif isinstance(m, uavcan.transport.VoidValue): pass else: raise ValueError(':(')
def _do_send(self): value_type = uavcan.get_active_union_field(self._param_struct.value) try: if value_type == "integer_value": if hasattr(self._value_widget, "value"): value = int(self._value_widget.value()) else: value = int(self._value_widget.text()) self._param_struct.value.integer_value = value elif value_type == "real_value": value = float(self._value_widget.text()) self._param_struct.value.real_value = value elif value_type == "boolean_value": value = bool(self._value_widget.isChecked()) self._param_struct.value.boolean_value = value elif value_type == "string_value": value = self._value_widget.text() self._param_struct.value.string_value = value else: raise RuntimeError("This is not happening!") except Exception as ex: show_error("Format error", "Could not parse value", ex, self) return try: request = uavcan.protocol.param.GetSet.Request(name=self._param_struct.name, value=self._param_struct.value) logger.info("Sending param set request: %s", request) self._node.request(request, self._target_node_id, self._on_response, priority=REQUEST_PRIORITY) except Exception as ex: show_error("Node error", "Could not send param set request", ex, self) else: self.show_message("Set request sent")
def _do_send(self): value_type = uavcan.get_active_union_field(self._param_struct.value) try: if value_type == 'integer_value': if hasattr(self._value_widget, 'value'): value = int(self._value_widget.value()) else: value = int(self._value_widget.text()) self._param_struct.value.integer_value = value elif value_type == 'real_value': value = float(self._value_widget.text()) self._param_struct.value.real_value = value elif value_type == 'boolean_value': value = bool(self._value_widget.isChecked()) self._param_struct.value.boolean_value = value elif value_type == 'string_value': value = self._value_widget.text() self._param_struct.value.string_value = value else: raise RuntimeError('This is not happening!') except Exception as ex: show_error('Format error', 'Could not parse value', ex, self) return try: request = uavcan.protocol.param.GetSet.Request(name=self._param_struct.name, value=self._param_struct.value) logger.info('Sending param set request: %s', request) self._node.request(request, self._target_node_id, self._on_response, priority=REQUEST_PRIORITY) except Exception as ex: show_error('Node error', 'Could not send param set request', ex, self) else: self.show_message('Set request sent')
def handle_param_list(self, req): params = [] index = 0 ClearQueue(_uav_incoming_param) while True: request = uavcan.protocol.param.GetSet.Request( index=index ) _uav_outgoing.put(request) try: response = _uav_incoming_param.get( block=True, timeout=PARAMETER_REQ_TIMEOUT, ) if ( uavcan.get_active_union_field( response.value ) != "empty" ): params.append( ascii_list_to_str(response.name) ) index += 1 rospy.loginfo(index) else: return [SUCCESS, params] except queue.Empty: return [FAILURE, params]
def _to_json_compatible_object_impl(obj): # Decomposing PrimitiveValue to value and type. This is ugly but it's by design... if isinstance(obj, PrimitiveValue): obj = obj.value # CompoundValue if isinstance(obj, CompoundValue): output = dict() for field_name, field in uavcan.get_fields(obj).items(): if uavcan.is_union( obj) and uavcan.get_active_union_field(obj) != field_name: continue if isinstance(field, VoidValue): continue output[field_name] = to_json_compatible_object(field) return output # ArrayValue elif isinstance(obj, ArrayValue): t = uavcan.get_uavcan_data_type(obj) if t.value_type.category == t.value_type.CATEGORY_PRIMITIVE: def is_nice_character(ch): if ch.is_printable() or ch.isspace(): return True if ch in b'\n\r\t': return True return False # Catch a string masquerading as an array if t.is_string_like and all(map(is_nice_character, obj)): return obj.decode() # Return the array! output = [] for x in obj: output.append(to_json_compatible_object(x)) return output # Primitive types elif isinstance(obj, float): return obj elif isinstance(obj, bool): return obj elif isinstance(obj, int): return obj # Non-printable types elif isinstance(obj, VoidValue): pass # Unknown types else: raise ValueError( 'Cannot generate JSON-compatible object representation for %r' % type(obj))
def render_union(u): value = get_union_value(u) if 'boolean' in uavcan.get_active_union_field(u): return bool(value) if isinstance(value, int): return value if isinstance(value, float): return round_float(value) if 'uavcan.protocol.param.Empty' in str(value): return '' return value
def log_all_params(): for index in range(10000): r = request( uavcan.protocol.param.GetSet.Request(index=index)) if not r.name: break logger.info( 'Param %-30r %r' % (r.name.decode(), getattr(r.value, uavcan.get_active_union_field( r.value))))
def render_union(u): value = get_union_value(u) if "boolean" in uavcan.get_active_union_field(u): return bool(value) if isinstance(value, int): return value if isinstance(value, float): return round_float(value) if "uavcan.protocol.param.Empty" in str(value): return "" return value
def __init__(self, parent, node, target_node_id): super(ConfigParams, self).__init__(parent) self.setTitle("Configuration parameters (double click to change)") self._node = node self._target_node_id = target_node_id self._read_all_button = make_icon_button( "refresh", "Fetch all config parameters from the node", self, text="Fetch All", on_clicked=self._do_reload ) opcodes = uavcan.protocol.param.ExecuteOpcode.Request() self._save_button = make_icon_button( "database", "Commit configuration to the non-volatile storage [OPCODE_SAVE]", self, text="Store All", on_clicked=partial(self._do_execute_opcode, opcodes.OPCODE_SAVE), ) self._erase_button = make_icon_button( "eraser", "Clear the non-volatile configuration storage [OPCODE_ERASE]", self, text="Erase All", on_clicked=partial(self._do_execute_opcode, opcodes.OPCODE_ERASE), ) columns = [ BasicTable.Column("Idx", lambda m: m[0]), BasicTable.Column("Name", lambda m: m[1].name, resize_mode=QHeaderView.Stretch), BasicTable.Column("Type", lambda m: uavcan.get_active_union_field(m[1].value).replace("_value", "")), BasicTable.Column("Value", lambda m: render_union(m[1].value), resize_mode=QHeaderView.Stretch), BasicTable.Column("Default", lambda m: render_union(m[1].default_value)), BasicTable.Column("Min", lambda m: render_union(m[1].min_value)), BasicTable.Column("Max", lambda m: render_union(m[1].max_value)), ] self._table = BasicTable(self, columns, multi_line_rows=True, font=get_monospace_font()) self._table.cellDoubleClicked.connect(lambda row, col: self._do_edit_param(row)) self._table.on_enter_pressed = self._on_cell_enter_pressed self._params = [] layout = QVBoxLayout(self) controls_layout = QHBoxLayout(self) controls_layout.addWidget(self._read_all_button, 1) controls_layout.addWidget(self._save_button, 1) controls_layout.addWidget(self._erase_button, 1) layout.addLayout(controls_layout) layout.addWidget(self._table) self.setLayout(layout)
def _assign(self, value_union): value = get_union_value(value_union) if uavcan.get_active_union_field(value_union) == "real_value": value = round_float(value) if hasattr(self._value_widget, "setValue"): self._value_widget.setValue(value) self._update_callback(value) elif hasattr(self._value_widget, "setChecked"): self._value_widget.setChecked(bool(value)) self._update_callback(bool(value)) else: self._value_widget.setText(str(value)) self._update_callback(value)
def _assign(self, value_union): value = get_union_value(value_union) if uavcan.get_active_union_field(value_union) == 'real_value': value = round_float(value) if hasattr(self._value_widget, 'setValue'): self._value_widget.setValue(value) self._update_callback(value) elif hasattr(self._value_widget, 'setChecked'): self._value_widget.setChecked(bool(value)) self._update_callback(bool(value)) else: self._value_widget.setText(str(value)) self._update_callback(value)
def set_param(name, value, union_field=None): union_field = union_field or { int: 'integer_value', float: 'real_value', bool: 'boolean_value', str: 'string_value' }[type(value)] logger.info('Setting parameter %r field %r value %r', name, union_field, value) req = uavcan.protocol.param.GetSet.Request() req.name.encode(name) setattr(req.value, union_field, value) r = request(req) enforce(uavcan.get_active_union_field(r.value) == union_field, 'Union field mismatch in param set response for %r', name) enforce(getattr(r.value, union_field) == value, 'The node refused to set parameter %r', name)
def set_param(name, value, union_field=None): union_field = union_field or { int: 'integer_value', float: 'real_value', bool: 'boolean_value', str: 'string_value' }[type(value)] logger.info('Setting parameter %r field %r value %r', name, union_field, value) req = uavcan.protocol.param.GetSet.Request() req.name.encode(name) setattr(req.value, union_field, value) r = request(req) enforce( uavcan.get_active_union_field(r.value) == union_field, 'Union field mismatch in param set response for %r', name) enforce( getattr(r.value, union_field) == value, 'The node refused to set parameter %r', name)
def _extract_struct_fields(m): if isinstance(m, uavcan.transport.CompoundValue): out = CompactMessage(uavcan.get_uavcan_data_type(m).full_name) for field_name, field in uavcan.get_fields(m).items(): if uavcan.is_union(m) and uavcan.get_active_union_field(m) != field_name: continue val = _extract_struct_fields(field) if val is not None: out._add_field(field_name, val) return out elif isinstance(m, uavcan.transport.ArrayValue): # cannot say I'm breaking the rules container = bytes if uavcan.get_uavcan_data_type(m).is_string_like else list # if I can glue them back together return container(filter(lambda x: x is not None, (_extract_struct_fields(item) for item in m))) elif isinstance(m, uavcan.transport.PrimitiveValue): return m.value elif isinstance(m, (int, float, bool)): return m elif isinstance(m, uavcan.transport.VoidValue): pass else: raise ValueError(':(')
def _to_yaml_impl(obj, indent_level=0, parent=None, name=None, uavcan_type=None): buf = StringIO() def write(fmt, *args): buf.write((fmt % args) if len(args) else fmt) def indent_newline(): buf.write(os.linesep + ' ' * 2 * indent_level) # Decomposing PrimitiveValue to value and type. This is ugly but it's by design... if isinstance(obj, PrimitiveValue): uavcan_type = uavcan.get_uavcan_data_type(obj) obj = obj.value # CompoundValue if isinstance(obj, CompoundValue): first_field = True # Rendering all fields than can be rendered for field_name, field in uavcan.get_fields(obj).items(): if uavcan.is_union( obj) and uavcan.get_active_union_field(obj) != field_name: continue if isinstance(field, VoidValue): continue if (first_field and indent_level > 0) or not first_field: indent_newline() first_field = False rendered_field = _to_yaml_impl(field, indent_level=indent_level + 1, parent=obj, name=field_name) write('%s: %s', field_name, rendered_field) # Special case - empty non-union struct is rendered as empty map if first_field and not uavcan.is_union(obj): if indent_level > 0: indent_newline() write('{}') # ArrayValue elif isinstance(obj, ArrayValue): t = uavcan.get_uavcan_data_type(obj) if t.value_type.category == t.value_type.CATEGORY_PRIMITIVE: def is_nice_character(ch): if 32 <= ch <= 126: return True if ch in b'\n\r\t': return True return False as_bytes = '[%s]' % ', '.join([ _to_yaml_impl( x, indent_level=indent_level + 1, uavcan_type=t.value_type) for x in obj ]) if t.is_string_like and all(map(is_nice_character, obj)): write('%r # ', obj.decode()) write(as_bytes) else: if len(obj) == 0: write('[]') else: for x in obj: indent_newline() write( '- %s', _to_yaml_impl(x, indent_level=indent_level + 1, uavcan_type=t.value_type)) # Primitive types elif isinstance(obj, float): assert uavcan_type is not None float_fmt = { 16: '%.4f', 32: '%.6f', 64: '%.9f', }[uavcan_type.bitlen] write(float_fmt, obj) elif isinstance(obj, bool): write('%s', 'true' if obj else 'false') elif isinstance(obj, int): write('%s', obj) if parent is not None and name is not None: resolved_name = value_to_constant_name(parent, name) if isinstance(resolved_name, str): write(' # %s', resolved_name) # Non-printable types elif isinstance(obj, VoidValue): pass # Unknown types else: raise ValueError('Cannot generate YAML representation for %r' % type(obj)) return buf.getvalue()
def _to_yaml_impl(obj, indent_level=0, parent=None, name=None, uavcan_type=None): buf = StringIO() def write(fmt, *args): buf.write((fmt % args) if len(args) else fmt) def indent_newline(): buf.write(os.linesep + ' ' * 2 * indent_level) # Decomposing PrimitiveValue to value and type. This is ugly but it's by design... if isinstance(obj, PrimitiveValue): uavcan_type = uavcan.get_uavcan_data_type(obj) obj = obj.value # CompoundValue if isinstance(obj, CompoundValue): first_field = True # Rendering all fields than can be rendered for field_name, field in uavcan.get_fields(obj).items(): if uavcan.is_union(obj) and uavcan.get_active_union_field(obj) != field_name: continue if isinstance(field, VoidValue): continue if (first_field and indent_level > 0) or not first_field: indent_newline() first_field = False rendered_field = _to_yaml_impl(field, indent_level=indent_level + 1, parent=obj, name=field_name) write('%s: %s', field_name, rendered_field) # Special case - empty non-union struct is rendered as empty map if first_field and not uavcan.is_union(obj): if indent_level > 0: indent_newline() write('{}') # ArrayValue elif isinstance(obj, ArrayValue): t = uavcan.get_uavcan_data_type(obj) if t.value_type.category == t.value_type.CATEGORY_PRIMITIVE: def is_nice_character(ch): if 32 <= ch <= 126: return True if ch in b'\n\r\t': return True return False as_bytes = '[%s]' % ', '.join([_to_yaml_impl(x, indent_level=indent_level + 1, uavcan_type=t.value_type) for x in obj]) if t.is_string_like and all(map(is_nice_character, obj)): write('%r # ', obj.decode()) write(as_bytes) else: if len(obj) == 0: write('[]') else: for x in obj: indent_newline() write('- %s', _to_yaml_impl(x, indent_level=indent_level + 1, uavcan_type=t.value_type)) # Primitive types elif isinstance(obj, float): assert uavcan_type is not None float_fmt = { 16: '%.4f', 32: '%.6f', 64: '%.9f', }[uavcan_type.bitlen] write(float_fmt, obj) elif isinstance(obj, bool): write('%s', 'true' if obj else 'false') elif isinstance(obj, int): write('%s', obj) if parent is not None and name is not None: resolved_name = value_to_constant_name(parent, name) if isinstance(resolved_name, str): write(' # %s', resolved_name) # Non-printable types elif isinstance(obj, VoidValue): pass # Unknown types else: raise ValueError('Cannot generate YAML representation for %r' % type(obj)) return buf.getvalue()
def get_union_value(u): return getattr(u, uavcan.get_active_union_field(u))
def __init__(self, parent, node, target_node_id, param_struct, update_callback): super(ConfigParamEditWindow, self).__init__(parent) self.setWindowTitle('Edit configuration parameter') self.setModal(True) self._node = node self._target_node_id = target_node_id self._param_struct = param_struct self._update_callback = update_callback min_val = get_union_value(param_struct.min_value) if 'uavcan.protocol.param.Empty' in str(min_val): min_val = None max_val = get_union_value(param_struct.max_value) if 'uavcan.protocol.param.Empty' in str(max_val): max_val = None value = get_union_value(param_struct.value) self._value_widget = None value_type = uavcan.get_active_union_field(param_struct.value) if value_type == 'integer_value': min_val = min_val if min_val is not None else -0x8000000000000000 max_val = max_val if max_val is not None else 0x7FFFFFFFFFFFFFFF if min_val >= -0x80000000 and \ max_val <= +0x7FFFFFFF: self._value_widget = QSpinBox(self) self._value_widget.setMaximum(max_val) self._value_widget.setMinimum(min_val) self._value_widget.setValue(value) if value_type == 'real_value': min_val = round_float( min_val) if min_val is not None else -3.4028235e+38 max_val = round_float( max_val) if max_val is not None else 3.4028235e+38 value = round_float(value) if value_type == 'boolean_value': self._value_widget = QCheckBox(self) self._value_widget.setChecked(bool(value)) if self._value_widget is None: self._value_widget = QLineEdit(self) self._value_widget.setText(str(value)) self._value_widget.setFont(get_monospace_font()) layout = QGridLayout(self) def add_const_field(label, *values): row = layout.rowCount() layout.addWidget(QLabel(label, self), row, 0) if len(values) == 1: layout.addWidget(FieldValueWidget(self, values[0]), row, 1) else: sub_layout = QHBoxLayout(self) for idx, v in enumerate(values): sub_layout.addWidget(FieldValueWidget(self, v)) layout.addLayout(sub_layout, row, 1) add_const_field('Name', param_struct.name) add_const_field( 'Type', uavcan.get_active_union_field(param_struct.value).replace( '_value', '')) add_const_field('Min/Max', min_val, max_val) add_const_field('Default', render_union(param_struct.default_value)) layout.addWidget(QLabel('Value', self), layout.rowCount(), 0) layout.addWidget(self._value_widget, layout.rowCount() - 1, 1) fetch_button = make_icon_button('refresh', 'Read parameter from the node', self, text='Fetch', on_clicked=self._do_fetch) set_default_button = make_icon_button('fire-extinguisher', 'Restore default value', self, text='Restore', on_clicked=self._restore_default) send_button = make_icon_button('flash', 'Send parameter to the node', self, text='Send', on_clicked=self._do_send) cancel_button = make_icon_button( 'remove', 'Close this window; unsent changes will be lost', self, text='Cancel', on_clicked=self.close) controls_layout = QGridLayout(self) controls_layout.addWidget(fetch_button, 0, 0) controls_layout.addWidget(send_button, 0, 1) controls_layout.addWidget(set_default_button, 1, 0) controls_layout.addWidget(cancel_button, 1, 1) layout.addLayout(controls_layout, layout.rowCount(), 0, 1, 2) self._status_bar = QStatusBar(self) self._status_bar.setSizeGripEnabled(False) layout.addWidget(self._status_bar, layout.rowCount(), 0, 1, 2) left, top, right, bottom = layout.getContentsMargins() bottom = 0 layout.setContentsMargins(left, top, right, bottom) self.setLayout(layout)
def __init__(self, parent, node, target_node_id, param_struct, update_callback): super(ConfigParamEditWindow, self).__init__(parent) self.setWindowTitle("Edit configuration parameter") self.setModal(True) self._node = node self._target_node_id = target_node_id self._param_struct = param_struct self._update_callback = update_callback min_val = get_union_value(param_struct.min_value) if "uavcan.protocol.param.Empty" in str(min_val): min_val = None max_val = get_union_value(param_struct.max_value) if "uavcan.protocol.param.Empty" in str(max_val): max_val = None value = get_union_value(param_struct.value) self._value_widget = None value_type = uavcan.get_active_union_field(param_struct.value) if value_type == "integer_value": min_val = min_val if min_val is not None else -0x8000000000000000 max_val = max_val if max_val is not None else 0x7FFFFFFFFFFFFFFF if min_val >= -0x80000000 and max_val <= +0x7FFFFFFF: self._value_widget = QSpinBox(self) self._value_widget.setMaximum(max_val) self._value_widget.setMinimum(min_val) self._value_widget.setValue(value) if value_type == "real_value": min_val = round_float(min_val) if min_val is not None else -3.4028235e38 max_val = round_float(max_val) if max_val is not None else 3.4028235e38 value = round_float(value) if value_type == "boolean_value": self._value_widget = QCheckBox(self) self._value_widget.setChecked(bool(value)) if self._value_widget is None: self._value_widget = QLineEdit(self) self._value_widget.setText(str(value)) self._value_widget.setFont(get_monospace_font()) layout = QGridLayout(self) def add_const_field(label, *values): row = layout.rowCount() layout.addWidget(QLabel(label, self), row, 0) if len(values) == 1: layout.addWidget(FieldValueWidget(self, values[0]), row, 1) else: sub_layout = QHBoxLayout(self) for idx, v in enumerate(values): sub_layout.addWidget(FieldValueWidget(self, v)) layout.addLayout(sub_layout, row, 1) add_const_field("Name", param_struct.name) add_const_field("Type", uavcan.get_active_union_field(param_struct.value).replace("_value", "")) add_const_field("Min/Max", min_val, max_val) add_const_field("Default", render_union(param_struct.default_value)) layout.addWidget(QLabel("Value", self), layout.rowCount(), 0) layout.addWidget(self._value_widget, layout.rowCount() - 1, 1) fetch_button = make_icon_button( "refresh", "Read parameter from the node", self, text="Fetch", on_clicked=self._do_fetch ) set_default_button = make_icon_button( "fire-extinguisher", "Restore default value", self, text="Restore", on_clicked=self._restore_default ) send_button = make_icon_button( "flash", "Send parameter to the node", self, text="Send", on_clicked=self._do_send ) cancel_button = make_icon_button( "remove", "Close this window; unsent changes will be lost", self, text="Cancel", on_clicked=self.close ) controls_layout = QGridLayout(self) controls_layout.addWidget(fetch_button, 0, 0) controls_layout.addWidget(send_button, 0, 1) controls_layout.addWidget(set_default_button, 1, 0) controls_layout.addWidget(cancel_button, 1, 1) layout.addLayout(controls_layout, layout.rowCount(), 0, 1, 2) self._status_bar = QStatusBar(self) self._status_bar.setSizeGripEnabled(False) layout.addWidget(self._status_bar, layout.rowCount(), 0, 1, 2) left, top, right, bottom = layout.getContentsMargins() bottom = 0 layout.setContentsMargins(left, top, right, bottom) self.setLayout(layout)