Skip to content

Commit f632bc5

Browse files
Merge pull request #14 from jupyter-robotics/dev
Improve error handling and support multiple responses
2 parents dc6d6a4 + bfc9db9 commit f632bc5

File tree

6 files changed

+113
-35
lines changed

6 files changed

+113
-35
lines changed

ipynao/_frontend.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
"""
1010

1111
module_name = "ipynao"
12-
module_version = "^0.3.0"
12+
module_version = "^0.4.0"

ipynao/nao_robot.py

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def _create_msg(self, method_name, *args, **kwargs):
3232
# convert tuple to list to avoid empty arg values
3333
data['args'] = list(args)
3434
data['kwargs'] = kwargs
35+
data['requestID'] = self.widget.request_id
36+
self.widget.request_id += 1
3537
return data
3638

3739
def call_service(self, method_name, *args, **kwargs):
@@ -41,14 +43,19 @@ def call_service(self, method_name, *args, **kwargs):
4143
async def async_call_service(self, method_name, *args, **kwargs):
4244
data = self._create_msg(method_name, *args, **kwargs)
4345
self.widget.send(data)
46+
request_id = data['requestID']
4447

4548
try:
4649
self.output.clear_output()
4750
self.output.append_stdout('Calling service... \n')
48-
await self.widget.wait_for_change('counter', self.output)
51+
await self.widget.wait_for_change('counter', self.output, request_id)
4952
except Exception as e:
5053
return e
51-
return self.widget.response['data']
54+
55+
response = self.widget.response[request_id]['data']
56+
del self.widget.response[request_id]
57+
58+
return response
5259

5360

5461
def __getattr__(self, method_name):
@@ -69,7 +76,8 @@ class NaoRobotWidget(DOMWidget):
6976
connected = Unicode('Disconnected').tag(sync=True)
7077
status = Unicode('Not busy').tag(sync=True)
7178
counter = Integer(0).tag(sync=True)
72-
response = None
79+
response = {}
80+
request_id = 0
7381

7482

7583
def __init__(self, **kwargs):
@@ -79,24 +87,35 @@ def __init__(self, **kwargs):
7987

8088
def _handle_frontend_msg(self, model, msg, buffer):
8189
print('Received frontend msg: ', msg)
82-
self.response = msg
90+
request_id = msg['requestID']
91+
self.response[request_id] = {
92+
'isError': msg['isError'],
93+
'data': msg['data']
94+
}
8395

8496

85-
def wait_for_change(widget, value_name, output=Output()):
97+
def wait_for_change(widget, value_name, output=Output(), request_id=0):
8698
future = asyncio.Future()
87-
widget.response = None
99+
widget.response[request_id] = {
100+
'isError': False,
101+
'data': None
102+
}
88103

89104
def get_value_change(change):
90-
widget.unobserve(get_value_change, names=value_name)
91-
if (widget.response != None):
92-
if (widget.response['isError']):
93-
future.set_exception(Exception(widget.response['data']))
94-
output.append_stderr(widget.response['data'])
105+
response = widget.response[request_id]
106+
107+
if (response['data'] != None):
108+
widget.unobserve(get_value_change, names=value_name)
109+
110+
if (response['isError']):
111+
future.set_exception(Exception(response['data']))
112+
output.append_stderr(response['data'])
95113
else:
96-
future.set_result(widget.response['data'])
97-
output.append_stdout(widget.response['data'])
114+
future.set_result(response['data'])
115+
output.append_stdout(response['data'])
116+
98117
else:
99-
future.set_result(change)
118+
future.set_result(change)
100119

101120
widget.observe(get_value_change, names=value_name)
102121
return future
@@ -107,18 +126,24 @@ def connect(self, ip_address='nao.local', port='80'):
107126
data['command'] = str('connect')
108127
data['ipAddress'] = str(ip_address)
109128
data['port'] = str(port)
129+
data['requestID'] = self.request_id
110130
self.send(data)
131+
self.request_id += 1
111132

112133

113134
def disconnect(self):
114135
data = {}
115136
data['command'] = str('disconnect')
137+
data['requestID'] = self.request_id
116138
self.send(data)
139+
self.request_id += 1
117140

118141

119142
def service(self, service_name, output=Output()):
120143
data = {}
121144
data['command'] = str('createService')
122145
data['service'] = str(service_name)
146+
data['requestID'] = self.request_id
123147
self.send(data)
148+
self.request_id += 1
124149
return NaoRobotService(self, service_name, output)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ipynao",
3-
"version": "0.3.0",
3+
"version": "0.4.0",
44
"description": "A widget library for controlling Nao",
55
"keywords": [
66
"jupyter",

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ classifiers = [
3434
dependencies = [
3535
"ipywidgets>=7.0.0",
3636
]
37-
version = "0.3.0"
37+
version = "0.4.0"
3838

3939
[project.optional-dependencies]
4040
docs = [
@@ -104,7 +104,7 @@ file = [
104104
]
105105

106106
[tool.tbump.version]
107-
current = "0.3.0"
107+
current = "0.4.0"
108108
regex = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)((?P<channel>a|b|rc|.dev)(?P<release>\\d+))?"
109109

110110
[tool.tbump.git]

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ ipympl>=0.8.2
2727
ipycanvas>=0.9.1
2828

2929
# Python: ipynao library for Nao robot
30-
ipynao>=0.3.0
30+
ipynao>=0.4.0
3131

3232
# For examples with images
3333
Pillow

src/widget.ts

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class NaoRobotModel extends DOMWidgetModel {
6666
}
6767
}
6868

69-
async connect(ipAddress: string, port: string) {
69+
async connect(ipAddress: string, port: string, requestID: number) {
7070
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
7171

7272
this.changeStatus('Establishing connection');
@@ -97,25 +97,31 @@ export class NaoRobotModel extends DOMWidgetModel {
9797

9898
// Handle connection failure
9999
if (!this.qiSession.isConnected()) {
100+
this.disconnect();
100101
console.error('Connection to ', ipAddress, ' could not be established.');
101-
this.changeStatus('Unavailable');
102+
this.changeStatus(
103+
'Connection to ' + ipAddress + ' could not be established.'
104+
);
102105
}
103106
}
104107

105108
disconnect() {
106-
this.qiSession.disconnect();
109+
if (this.qiSession && this.qiSession.isConnected()) {
110+
this.qiSession.disconnect();
111+
}
107112
this._services = {};
108113
this.set('connected', 'Disconnected');
109114
this.save_changes();
110115
this.changeStatus('Unavailable');
111116
}
112117

113-
private async checkConnection() {
118+
private async checkConnection(requestID: number) {
114119
// Cannot reconnect without initial connection
115120
if (!this._ipAddress) {
116121
this.send({
117122
isError: true,
118123
data: 'Cannot connect without IP Address.',
124+
requestID: requestID,
119125
});
120126
this.set('counter', this.get('counter') + 1);
121127
this.save_changes();
@@ -124,32 +130,47 @@ export class NaoRobotModel extends DOMWidgetModel {
124130

125131
// Reconnect if possible
126132
if (!this.qiSession.isConnected()) {
127-
await this.connect(this._ipAddress, this._port);
133+
this.disconnect();
134+
await this.connect(this._ipAddress, this._port, requestID);
128135
}
129136
return true;
130137
}
131138

132-
private async createService(serviceName: string) {
133-
const isConnected: boolean = await this.checkConnection();
139+
private async createService(serviceName: string, requestID: number) {
140+
const isConnected: boolean = await this.checkConnection(requestID);
134141
if (!isConnected) {
135142
return;
136143
}
137144

138145
// Skip if service exists already
139-
if (this._services[serviceName] !== undefined) {
146+
if (this._services[serviceName]) {
140147
console.log('Service ' + serviceName + ' exists.');
141148
return;
142149
}
143150

144151
this.changeStatus('Creating service ' + serviceName);
145152
const servicePromise = this.qiSession.service(serviceName);
146153

154+
// TODO: This func is not async in the kernel. To show error messages
155+
// the request ID is the next one which is used to call the service
147156
const naoService = await servicePromise
148157
.then((resolution: any) => {
158+
this.send({
159+
isError: false,
160+
data: true, // TODO: resolution ?? true,
161+
requestID: requestID + 1, // Note above
162+
});
149163
return resolution;
150164
})
151165
.catch((rejection: string) => {
152166
this.changeStatus(rejection);
167+
this.send({
168+
isError: true,
169+
data: rejection,
170+
requestID: requestID + 1, // Note above
171+
});
172+
this.set('counter', this.get('counter') + 1);
173+
this.save_changes();
153174
return rejection;
154175
});
155176

@@ -164,27 +185,49 @@ export class NaoRobotModel extends DOMWidgetModel {
164185
serviceName: string,
165186
methodName: string,
166187
args: any,
167-
_kwargs: any
188+
_kwargs: any,
189+
requestID: number
168190
) {
169-
const isConnected: boolean = await this.checkConnection();
191+
const isConnected: boolean = await this.checkConnection(requestID);
170192
if (!isConnected) {
171193
return;
172194
}
173195

174196
// Wait for service to become available
175197
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
198+
this.changeStatus('Waiting for service ' + serviceName);
176199

177200
// Timeout after ~10 seconds
178201
for (let i = 0; i < 100; i++) {
179-
if (this._services[serviceName] !== undefined) {
202+
if (this._services[serviceName]) {
180203
console.log('Service available after ', i / 10.0, ' seconds.');
204+
this.changeStatus(serviceName + ' available');
181205
break;
182206
}
183207
await sleep(100);
184208
}
185209

186-
if (this._services[serviceName][methodName] === undefined) {
187-
this.changeStatus(methodName + ' does not exist for ' + serviceName);
210+
if (!this._services[serviceName]) {
211+
this.changeStatus(serviceName + ' not available');
212+
this.send({
213+
isError: true,
214+
data: serviceName + ' not available',
215+
requestID: requestID,
216+
});
217+
this.set('counter', this.get('counter') + 1);
218+
this.save_changes();
219+
return;
220+
}
221+
222+
if (!this._services[serviceName][methodName]) {
223+
this.changeStatus(`${methodName} does not exist for ${serviceName}`);
224+
this.send({
225+
isError: true,
226+
data: `${methodName} does not exist for ${serviceName}`,
227+
requestID: requestID,
228+
});
229+
this.set('counter', this.get('counter') + 1);
230+
this.save_changes();
188231
return;
189232
}
190233

@@ -197,13 +240,15 @@ export class NaoRobotModel extends DOMWidgetModel {
197240
this.send({
198241
isError: false,
199242
data: resolution ?? true,
243+
requestID: requestID,
200244
});
201245
})
202246
.catch((rejection: string) => {
203247
this.changeStatus(rejection);
204248
this.send({
205249
isError: true,
206250
data: rejection,
251+
requestID: requestID,
207252
});
208253
});
209254

@@ -216,23 +261,31 @@ export class NaoRobotModel extends DOMWidgetModel {
216261

217262
switch (cmd) {
218263
case 'connect':
219-
await this.connect(commandData['ipAddress'], commandData['port']);
264+
await this.connect(
265+
commandData['ipAddress'],
266+
commandData['port'],
267+
commandData['requestID']
268+
);
220269
break;
221270

222271
case 'disconnect':
223272
this.disconnect();
224273
break;
225274

226275
case 'createService':
227-
this.createService(commandData['service']);
276+
await this.createService(
277+
commandData['service'],
278+
commandData['requestID']
279+
);
228280
break;
229281

230282
case 'callService':
231283
await this.callService(
232284
commandData['service'],
233285
commandData['method'],
234286
commandData['args'],
235-
commandData['kwargs']
287+
commandData['kwargs'],
288+
commandData['requestID']
236289
);
237290
break;
238291
}

0 commit comments

Comments
 (0)