ParamTab.py 8.76 KB
Newer Older
bucyril's avatar
bucyril committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#     ||          ____  _ __
#  +------+      / __ )(_) /_______________ _____  ___
#  | 0xBC |     / __  / / __/ ___/ ___/ __ `/_  / / _ \
#  +------+    / /_/ / / /_/ /__/ /  / /_/ / / /_/  __/
#   ||  ||    /_____/_/\__/\___/_/   \__,_/ /___/\___/
#
#  Copyright (C) 2011-2013 Bitcraze AB
#
#  Crazyflie Nano Quadcopter Client
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.

phfriedl's avatar
phfriedl committed
24
25
26
#  You should have received a copy of the GNU General Public License along with
#  this program; if not, write to the Free Software Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
bucyril's avatar
bucyril committed
27
28
29
30
31
32

"""
Shows all the parameters available in the Crazyflie and also gives the ability
to edit them.
"""

phfriedl's avatar
phfriedl committed
33
import logging
bucyril's avatar
bucyril committed
34

phfriedl's avatar
phfriedl committed
35
36
37
38
from PyQt5 import uic
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtCore import QAbstractItemModel, QModelIndex
from PyQt5.QtGui import QBrush, QColor
bucyril's avatar
bucyril committed
39

phfriedl's avatar
phfriedl committed
40
import cfclient
bucyril's avatar
bucyril committed
41
42
from cfclient.ui.tab import Tab

phfriedl's avatar
phfriedl committed
43
44
45
46
47
__author__ = 'Bitcraze AB'
__all__ = ['ParamTab']

param_tab_class = uic.loadUiType(
    cfclient.module_path + "/ui/tabs/paramTab.ui")[0]
bucyril's avatar
bucyril committed
48
49
50

logger = logging.getLogger(__name__)

phfriedl's avatar
phfriedl committed
51

bucyril's avatar
bucyril committed
52
53
class ParamChildItem(object):
    """Represents a leaf-node in the tree-view (one parameter)"""
phfriedl's avatar
phfriedl committed
54

bucyril's avatar
bucyril committed
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
    def __init__(self, parent, name, crazyflie):
        """Initialize the node"""
        self.parent = parent
        self.name = name
        self.ctype = None
        self.access = None
        self.value = ""
        self._cf = crazyflie
        self.is_updating = True

    def updated(self, name, value):
        """Callback from the param layer when a parameter has been updated"""
        self.value = value
        self.is_updating = False
        self.parent.model.refresh()

    def set_value(self, value):
        """Send the update value to the Crazyflie. It will automatically be
        read again after sending and then the updated callback will be
        called"""
        complete_name = "%s.%s" % (self.parent.name, self.name)
        self._cf.param.set_value(complete_name, value)
        self.is_updating = True

    def child_count(self):
        """Return the number of children this node has"""
        return 0


class ParamGroupItem(object):
    """Represents a parameter group in the tree-view"""
phfriedl's avatar
phfriedl committed
86

bucyril's avatar
bucyril committed
87
88
89
90
91
92
93
94
95
96
97
98
    def __init__(self, name, model):
        """Initialize the parent node"""
        super(ParamGroupItem, self).__init__()
        self.parent = None
        self.children = []
        self.name = name
        self.model = model

    def child_count(self):
        """Return the number of children this node has"""
        return len(self.children)

phfriedl's avatar
phfriedl committed
99

bucyril's avatar
bucyril committed
100
101
class ParamBlockModel(QAbstractItemModel):
    """Model for handling the parameters in the tree-view"""
phfriedl's avatar
phfriedl committed
102

bucyril's avatar
bucyril committed
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
    def __init__(self, parent):
        """Create the empty model"""
        super(ParamBlockModel, self).__init__(parent)
        self._nodes = []
        self._column_headers = ['Name', 'Type', 'Access', 'Value']
        self._red_brush = QBrush(QColor("red"))

    def set_toc(self, toc, crazyflie):
        """Populate the model with data from the param TOC"""

        # No luck using proxy sorting, so do it here instead...
        for group in sorted(toc.keys()):
            new_group = ParamGroupItem(group, self)
            for param in sorted(toc[group].keys()):
                new_param = ParamChildItem(new_group, param, crazyflie)
                new_param.ctype = toc[group][param].ctype
                new_param.access = toc[group][param].get_readable_access()
                crazyflie.param.add_update_callback(
                    group=group, name=param, cb=new_param.updated)
                new_group.children.append(new_param)
            self._nodes.append(new_group)

        self.layoutChanged.emit()

    def refresh(self):
        """Force a refresh of the view though the model"""
        self.layoutChanged.emit()

    def parent(self, index):
        """Re-implemented method to get the parent of the given index"""
        if not index.isValid():
            return QModelIndex()

        node = index.internalPointer()
        if node.parent is None:
            return QModelIndex()
        else:
            return self.createIndex(self._nodes.index(node.parent), 0,
                                    node.parent)

    def columnCount(self, parent):
        """Re-implemented method to get the number of columns"""
        return len(self._column_headers)

    def headerData(self, section, orientation, role):
        """Re-implemented method to get the headers"""
        if role == Qt.DisplayRole:
phfriedl's avatar
phfriedl committed
150
            return self._column_headers[section]
bucyril's avatar
bucyril committed
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191

    def rowCount(self, parent):
        """Re-implemented method to get the number of rows for a given index"""
        parent_item = parent.internalPointer()
        if parent.isValid():
            parent_item = parent.internalPointer()
            return parent_item.child_count()
        else:
            return len(self._nodes)

    def index(self, row, column, parent):
        """Re-implemented method to get the index for a specified
        row/column/parent combination"""
        if not self._nodes:
            return QModelIndex()
        node = parent.internalPointer()
        if not node:
            index = self.createIndex(row, column, self._nodes[row])
            self._nodes[row].index = index
            return index
        else:
            return self.createIndex(row, column, node.children[row])

    def data(self, index, role):
        """Re-implemented method to get the data for a given index and role"""
        node = index.internalPointer()
        parent = node.parent
        if not parent:
            if role == Qt.DisplayRole and index.column() == 0:
                return node.name
        elif role == Qt.DisplayRole:
            if index.column() == 0:
                return node.name
            if index.column() == 1:
                return node.ctype
            if index.column() == 2:
                return node.access
            if index.column() == 3:
                return node.value
        elif role == Qt.EditRole and index.column() == 3:
            return node.value
phfriedl's avatar
phfriedl committed
192
193
        elif (role == Qt.BackgroundRole and index.column() == 3 and
              node.is_updating):
bucyril's avatar
bucyril committed
194
195
            return self._red_brush

phfriedl's avatar
phfriedl committed
196
        return None
bucyril's avatar
bucyril committed
197
198
199
200
201

    def setData(self, index, value, role):
        """Re-implemented function called when a value has been edited"""
        node = index.internalPointer()
        if role == Qt.EditRole:
phfriedl's avatar
phfriedl committed
202
            new_val = str(value)
bucyril's avatar
bucyril committed
203
204
205
206
207
208
209
210
211
212
            # This will not update the value, only trigger a setting and
            # reading of the parameter from the Crazyflie
            node.set_value(new_val)
            return True
        return False

    def flags(self, index):
        """Re-implemented function for getting the flags for a certain index"""
        flag = super(ParamBlockModel, self).flags(index)
        node = index.internalPointer()
phfriedl's avatar
phfriedl committed
213
        if index.column() == 3 and node.parent and node.access == "RW":
bucyril's avatar
bucyril committed
214
215
216
217
218
            flag |= Qt.ItemIsEditable
        return flag

    def reset(self):
        """Reset the model"""
phfriedl's avatar
phfriedl committed
219
        super(ParamBlockModel, self).beginResetModel()
bucyril's avatar
bucyril committed
220
        self._nodes = []
phfriedl's avatar
phfriedl committed
221
        super(ParamBlockModel, self).endResetModel()
bucyril's avatar
bucyril committed
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
        self.layoutChanged.emit()


class ParamTab(Tab, param_tab_class):
    """
    Show all the parameters in the TOC and give the user the ability to edit
    them
    """
    _expand_all_signal = pyqtSignal()
    _connected_signal = pyqtSignal(str)
    _disconnected_signal = pyqtSignal(str)

    def __init__(self, tabWidget, helper, *args):
        """Create the parameter tab"""
        super(ParamTab, self).__init__(*args)
        self.setupUi(self)

        self.tabName = "Parameters"
        self.menuName = "Parameters"

        self.helper = helper
        self.tabWidget = tabWidget
        self.cf = helper.cf

        self.cf.connected.add_callback(self._connected_signal.emit)
        self._connected_signal.connect(self._connected)
        self.cf.disconnected.add_callback(self._disconnected_signal.emit)
        self._disconnected_signal.connect(self._disconnected)

        self._model = ParamBlockModel(None)
        self.paramTree.setModel(self._model)

    def _connected(self, link_uri):
        self._model.set_toc(self.cf.param.toc.toc, self.helper.cf)
        self.paramTree.expandAll()

    def _disconnected(self, link_uri):
phfriedl's avatar
phfriedl committed
259
        self._model.beginResetModel()
bucyril's avatar
bucyril committed
260
        self._model.reset()
phfriedl's avatar
phfriedl committed
261
        self._model.endResetModel()