Skip to content

Commit 1d02ff1

Browse files
committed
tunable param support
1 parent cbdd1ba commit 1d02ff1

15 files changed

Lines changed: 662 additions & 292 deletions

server/src/xbot2_gui_server/parameters.py

Lines changed: 14 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,19 @@ def __init__(self, srv: ServerBase, config=dict()) -> None:
2121

2222
self.requested_pages = ['Parameters']
2323

24-
self.rate = config.get('rate', 10.0)
25-
2624
self.srv = srv
2725

28-
self.srv.schedule_task(self.run())
29-
3026
self.srv.add_route('GET', '/parameters/info',
3127
self.parameters_get_info,
3228
'parameters_get_info')
3329

30+
self.srv.add_route('POST', '/parameters/set_value',
31+
self.parameters_set_value,
32+
'parameters_set_value')
33+
3434
# subscribe to stats
3535
self.get_info = rospy.ServiceProxy('xbotcore/get_parameter_info', GetParameterInfo)
36+
self.set_parameters = rospy.ServiceProxy('xbotcore/set_parameters', SetString)
3637

3738

3839
@utils.handle_exceptions
@@ -50,204 +51,19 @@ async def parameters_get_info(self, request):
5051
'descriptor': [json.dumps(yaml.safe_load(v)) for v in response.descriptor]
5152
}
5253
)
54+
55+
@utils.handle_exceptions
56+
async def parameters_set_value(self, request: web.Request):
5357

58+
body = await request.text()
5459

55-
@utils.handle_exceptions
56-
async def dashboard_robot_switch(self, request: web.Request):
57-
58-
# we need the command name (start or stop)
59-
command = request.match_info.get('command', None)
60-
61-
l : launcher.Launcher = self.get_launcher()
62-
63-
if command == 'start':
64-
65-
# start ecat
66-
await self.send_status('starting robot 1/3 (ecat)')
67-
68-
if not await l.start(process='ecat'):
69-
raise RuntimeError('could not start ecat')
70-
71-
# check slaves
72-
await self.send_status('starting robot 2/3 (ecat_slave_check)')
73-
74-
if not await l.start(process='wait_ecat_online'):
75-
raise RuntimeError('could not find slaves')
76-
77-
await self.send_status('starting robot 3/3 (xbot2)')
78-
79-
# start xbot2
80-
if not await l.start(process='xbot2',
81-
user_variants=['ec_imp']):
82-
raise RuntimeError('could not start xbot2')
83-
84-
# wait alive
85-
while not self.xbot2_alive:
86-
await asyncio.sleep(0.1)
87-
88-
self.active_state = 'ready'
89-
90-
elif command == 'stop':
91-
92-
# check xbot2 is down
93-
sdict = await l.status()
94-
95-
if sdict.get('xbot2', '') == 'Running':
96-
raise RuntimeError('xbot2 is up: have you pressed the emergency button?')
97-
98-
# kill ecat and hope for the best
99-
await self.send_status('stopping robot (ecat)')
100-
101-
if not await l.kill(process='ecat', graceful=True):
102-
raise RuntimeError('could not stop ecat')
103-
104-
self.active_state = 'inactive'
105-
106-
else:
107-
108-
raise KeyError(f'bad command {command}')
109-
110-
111-
return web.Response(text=json.dumps(
112-
{
113-
'success': True,
114-
'message': 'ok',
115-
}
116-
))
60+
print(body)
11761

62+
res = await utils.to_thread(self.set_parameters, request=body)
11863

119-
@utils.handle_exceptions
120-
async def dashboard_start(self, request: web.Request):
121-
122-
# check xbot2 alive
123-
if self.stats is None:
124-
raise RuntimeError('xbot2 is not alive')
125-
126-
# we need the task name and ctrl mode
127-
task_name = request.match_info.get('task_name', None)
128-
129-
if task_name is None:
130-
raise ValueError('no task name specified in url')
131-
132-
task = self.states[task_name]
133-
134-
# stop all plugins
135-
all_plugins = [t.name for t in self.stats.task_stats if t.name in self.all_plugins]
136-
plugins_to_start = plugins = task['plugin']
137-
plugins_to_stop = [p for p in all_plugins if p not in plugins_to_start]
138-
139-
for p in plugins_to_stop:
140-
await self.send_status(f'stopping plugin {p}')
141-
await self.plugin_switch(p, 0)
142-
143-
for p in plugins_to_stop:
144-
await self.send_status(f'waiting for plugin {p} to stop...')
145-
await asyncio.wait_for(self.wait_for_state(p, ['Stopped', 'Initialized']),
146-
timeout=10.0)
147-
148-
# start required plugins
149-
for p in plugins_to_start:
150-
await self.send_status(f'starting plugin {p}')
151-
await self.plugin_switch(p, 1)
152-
for p in plugins_to_start:
153-
await self.send_status(f'waiting for plugin {p} to start...')
154-
await self.wait_for_state(p, 'Running')
155-
156-
# kill all active processes
157-
process_to_start = task['process']
158-
process_to_stop = [p for p in self.all_processes if p not in process_to_start]
159-
for p in process_to_stop:
160-
await self.send_status(f'killing process {p}')
161-
l : launcher.Launcher = self.get_launcher()
162-
await asyncio.wait_for(l.kill(process=p, graceful=True),
163-
timeout=30)
164-
165-
# start required processes
166-
for p in process_to_start:
167-
await self.send_status(f'starting process {p}')
168-
l : launcher.Launcher = self.get_launcher()
169-
await asyncio.wait_for(l.start(process=p),
170-
timeout=30)
171-
172-
173-
await self.send_status(f'done')
174-
175-
self.active_state = task_name
176-
177-
return web.Response(text=json.dumps(
64+
return web.json_response(
17865
{
179-
'success': True,
180-
'message': 'ok',
66+
'success': res.success,
67+
'message': res.message
18168
}
182-
))
183-
184-
185-
async def plugin_switch(self, plugin_name, switch_flag):
186-
switch = rospy.ServiceProxy(f'xbotcore/{plugin_name}/switch', service_class=SetBool)
187-
await utils.to_thread(switch.wait_for_service, timeout=1.0)
188-
res = await utils.to_thread(switch, switch_flag)
189-
return res.success
190-
191-
192-
async def run(self):
193-
194-
while True:
195-
196-
# if not self.xbot2_alive:
197-
# self.active_state = 'inactive'
198-
# elif self.active_state == 'inactive':
199-
# self.active_state = 'ready'
200-
201-
# self.xbot2_alive = False
202-
203-
await self.srv.ws_send_to_all(
204-
{
205-
'type': 'dashboard_msg',
206-
'active_state': self.active_state
207-
}
208-
)
209-
210-
await asyncio.sleep(0.666)
211-
212-
213-
def on_stats_recv(self, msg):
214-
self.stats = msg
215-
self.xbot2_alive = True
216-
217-
218-
219-
async def wait_for_state(self, plugin_name, state):
220-
221-
if isinstance(state, str):
222-
state = [state]
223-
224-
idx = -1
225-
226-
for i, t in enumerate(self.stats.task_stats):
227-
if t.name in plugin_name:
228-
idx = i
229-
break
230-
231-
if idx == -1:
232-
raise KeyError(plugin_name)
233-
234-
while self.stats.task_stats[idx].state not in state:
235-
await asyncio.sleep(0.1)
236-
237-
238-
def get_launcher(self):
239-
if self.launcher is not None:
240-
return self.launcher
241-
for e in self.srv.extensions:
242-
if isinstance(e, launcher.Launcher):
243-
self.launcher = e
244-
return e
245-
raise ValueError('launcher unavailable')
246-
247-
248-
async def send_status(self, txt):
249-
print(txt)
250-
await self.srv.ws_send_to_all(
251-
dict(type='dashboard_msg',
252-
msg=txt)
25369
)

xbot2_gui/Common/SectionHeader.qml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ RowLayout {
1111
property alias font: title.font
1212
signal clicked()
1313
property alias iconText: iconLabel.text
14+
property int pixelSize: CommonProperties.font.h1
1415

1516
//
1617
id: root
@@ -21,7 +22,8 @@ RowLayout {
2122
id: iconLabel
2223

2324
font.family: CommonProperties.fontAwesome.solid.family
24-
font.pixelSize: CommonProperties.font.h1
25+
font.pixelSize: root.pixelSize
26+
visible: text !== ''
2527

2628
MouseArea {
2729
anchors.fill: parent
@@ -33,7 +35,7 @@ RowLayout {
3335
Label {
3436
id: title
3537
text: 'Title'
36-
font.pixelSize: CommonProperties.font.h1
38+
font.pixelSize: root.pixelSize
3739
Layout.fillWidth: true
3840
wrapMode: Text.Wrap
3941

xbot2_gui/Monitoring/CMakeLists.txt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,23 @@ qt_add_qml_module(xbot2_gui_monitoring
1111
Monitoring.qml
1212
RobotModelViewer.js
1313
RobotModelViewer.qml
14-
QML_FILES Parameters.qml
15-
QML_FILES Parameters.js
14+
Parameters.qml
15+
Parameters.js
16+
SOURCES
17+
Parameter/parameter_tree_item.h
18+
Parameter/parameter_tree_item.cpp
19+
Parameter/parameter_tree_model.h
20+
Parameter/parameter_tree_model.cpp
21+
QML_FILES Parameter/BoolDelegate.qml
22+
QML_FILES Parameter/RealDelegate.qml
23+
QML_FILES Parameter/VectorDelegate.qml
24+
QML_FILES Parameter/IntDelegate.qml
25+
QML_FILES Parameter/DiscreteValuesDelegate.qml
26+
QML_FILES Parameter/ParameterView.qml
1627
)
1728

29+
target_include_directories(xbot2_gui_monitoring PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Parameter)
30+
1831
target_link_libraries(xbot2_gui PRIVATE xbot2_gui_monitoringplugin)
1932

2033
add_subdirectory(BarPlot)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import QtQuick
2+
import QtQuick.Controls
3+
import QtQuick.Layouts
4+
5+
CheckBox {
6+
7+
readonly property bool value: checked
8+
9+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import QtQuick
2+
import QtQuick.Controls
3+
import QtQuick.Layouts
4+
5+
ComboBox {
6+
7+
readonly property string value: currentText
8+
9+
//
10+
id: control
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import QtQuick
2+
import QtQuick.Controls
3+
import QtQuick.Layouts
4+
5+
import Common
6+
7+
SpinBox {
8+
9+
property alias min: control.from
10+
property alias max: control.to
11+
12+
//
13+
id: control
14+
15+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import QtQuick
2+
import QtQuick.Controls
3+
import QtQuick.Layouts
4+
5+
import Common
6+
7+
Control {
8+
9+
function addChild(paramView) {
10+
paramView.parent = contentColumn
11+
console.log(`${viewName}: added child ${paramView}`)
12+
paramView.width = Qt.binding(() => { return contentColumn.width })
13+
}
14+
15+
function hasChild(name) {
16+
for(let i = 0; i < contentColumn.children.length; i++) {
17+
let child = contentColumn.children[i]
18+
if(child instanceof ParameterView &&
19+
child.viewName === name){
20+
return child
21+
}
22+
}
23+
return null
24+
}
25+
26+
function clear() {
27+
contentColumn.children = []
28+
}
29+
30+
property string viewName
31+
32+
property int treeDepth: 0
33+
34+
id: root
35+
36+
37+
38+
leftPadding: 8*root.treeDepth
39+
rightPadding: 0
40+
41+
contentItem: Column {
42+
43+
spacing: 16
44+
45+
SectionHeader {
46+
id: header
47+
width: parent.width
48+
text: root.viewName
49+
iconText: contentColumn.visible ? '\uf077' : '\uf078'
50+
pixelSize: CommonProperties.font.h3
51+
52+
onClicked: {
53+
contentColumn.visible = !contentColumn.visible
54+
}
55+
}
56+
57+
Column {
58+
id: contentColumn
59+
width: parent.width
60+
spacing: 8
61+
}
62+
63+
}
64+
65+
}

0 commit comments

Comments
 (0)