1
#!/usr/bin/python
2
# -*- coding: utf-8
3
4
#######################################################################
5
# Copyright 2010 Signove Corporation - All rights reserved.
6
# Contact: Signove Corporation (contact@signove.com)
7
#
8
# This library is free software; you can redistribute it and/or modify
9
# it under the terms of version 2.1 of the GNU Lesser General Public
10
# License as published by the Free Software Foundation.
11
#
12
# This library is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
# GNU Lesser General Public License for more details.
16
#
17
# You should have received a copy of the GNU Lesser General Public
18
# License along with this library; if not, write to the
19
# Free Software Foundation, Inc., 59 Temple Place, Suite 330,
20
# Boston, MA 02111-1307  USA
21
#
22
# If you have questions regarding the use of this file, please contact
23
# Signove at contact@signove.com.
24
#######################################################################
25
26
import os.path
27
import sys
28
import time
29
import termios
30
import socket
31
32
import glib
33
import gobject
34
import dbus
35
import dbus.mainloop.glib
36
import dbus.exceptions
37
import dbus.service
38
39
from fcntl import ioctl
40
from ctypes import c_long
41
42
from hdp.dummy_ieee10404 import *
43
44
# PTS suite specs
45
46
# FIXME sometimes destroy_channel causes a 'Bailing out test' and not
47
# destroying the application. This MAY be the cause of some funny
48
# problems. Also, failing upon channel destruction is more or less
49
# expected in some tests, because PTS default config is to take initiative
50
# in destroying channels. I suggest you create a 'destroy_channel_whatever'
51
# and use it in tests where PTS gives Pass veredict but it is spitting an
52
# error at IUT side. (No point in yelling errors where PTS finds none!)
53
54
# FIXME what is the point of asking create_app every time? I think that
55
# it should happen as soon as we enter the test. Destroy_app may be asked
56
# about, because it effectively ceases communication.
57
58
"""
59
Notes (HDP.SRC.CON)
60
===================
61
62
PICS flags for enabling tests (set to True)
63
-------------------------------------------
64
- TC_SRC_CON_BV_01_I
65
	- TSPC_HDP_4_1 (Source: Supports advertisement of HDP Service Record)
66
67
- TC_SRC_CON_BV_07_I
68
	- TSPC_HDP_2_7 (Source: Supports General Inquiry (C.5))
69
70
- TC_SRC_CON_BV_09_I
71
	- TSPC_HDP_2_9 (Source: Supports Initiation of Bonding (O))
72
73
- TC_SRC_CON_BV_10_I
74
	- TSPC_HDP_2_11 (Source: Supports use of Health Class of Device (O))
75
76
"""
77
hdp_src_con = { 'TC_SRC_CON_BV_01_I': ( 'create_app_source',
78
										'destroy_app'),
79
				'TC_SRC_CON_BV_02_I': ( 'set_discoverable', ),
80
				'TC_SRC_CON_BV_03_I': (	'create_app_source',
81
										'destroy_app'),
82
				'TC_SRC_CON_BV_04_I': ( 'create_app_source',
83
										'create_channel_reliable',
84
										'destroy_channel',
85
										'destroy_app'),
86
				'TC_SRC_CON_BV_05_I': (	'create_app_source',
87
										'destroy_app'),
88
				'TC_SRC_CON_BV_06_I': (	'create_app_source',
89
										'create_channel_reliable',
90
										'destroy_channel',
91
										'destroy_app'),
92
				'TC_SRC_CON_BV_07_I': ( 'discover', ),
93
				'TC_SRC_CON_BV_08_I': ( 'delete_pairing',
94
										'create_app_source',
95
										'destroy_app'),
96
				'TC_SRC_CON_BV_09_I': ( 'create_app_source',
97
										'create_channel_reliable',
98
										'destroy_channel',
99
										'destroy_app'),
100
				'TC_SRC_CON_BV_10_I': ( 'set_discoverable', ) }
101
102
"""
103
Notes (HDP.SRC.CC)
104
==================
105
106
PICS flags for enabling tests (set to True)
107
-------------------------------------------
108
109
- Tests BV_01_C and BV_03_C:
110
	- TSPC_HDP_6_2 (Source: Supports Inititiate creation of Control and Data Channel)
111
112
- Tests BV_02_C, BV_05_C and BI_12_C:
113
	- TSPC_HDP_6_3 (Source: SUpports Accept creation of Control and Data Channel)
114
115
- Tests BV_07_C and BV_09_C:
116
	- TSPC_HDP_3_7 (Source: Supports Streaming Data Channel (M,C.4)[Mandatory])
117
118
"""
119
hdp_src_cc = { 	'TC_SRC_CC_BV_01_C': (	'create_app_source',
120
										'create_channel_reliable',
121
										'destroy_channel',
122
										'destroy_app'),
123
			 	'TC_SRC_CC_BV_02_C': (  'create_app_source',
124
										'destroy_app'),
125
				'TC_SRC_CC_BV_03_C': (  'create_app_source',
126
										'create_channel_reliable',
127
										'destroy_channel',
128
										'destroy_app'),
129
				'TC_SRC_CC_BV_05_C': (	'create_app_source',
130
										'destroy_app'),
131
				'TC_SRC_CC_BV_07_C': (	'create_app_source',
132
										'create_channel_reliable',
133
										'create_channel_streaming',
134
										'destroy_channel',
135
										'destroy_channel',
136
										'destroy_app'),
137
				'TC_SRC_CC_BV_09_C': (	'create_app_source_streaming',
138
										'destroy_app'),
139
				'TC_SRC_CC_BI_12_C': ( 	'create_app_source',
140
										'destroy_app') }
141
142
143
"""
144
Notes (HDP.SRC.HCT)
145
===================
146
147
PICS flags for enabling tests (set to True)
148
-------------------------------------------
149
150
- Tests BV_03_I and BV_05_I:
151
	- TSPC_HDP_6_4 (Source: Supports Inititiate Reconnection of MDL (O)
152
153
- Tests BV_04_I, BV_06_C and BV_07_C:
154
	- TSPC_HDP_6_5 (Source: Supports Accept Reconnection of MDL (C.4)[Mandatory])
155
"""
156
157
hdp_src_hct = { 'TC_SRC_HCT_BV_01_I': ( 'create_app_source',
158
										'create_channel_reliable',
159
										'destroy_channel',
160
										'destroy_app'),
161
				'TC_SRC_HCT_BV_02_I': ( 'create_app_source',
162
										'destroy_app'),
163
				'TC_SRC_HCT_BV_03_I': ( 'create_app_source_reliable',
164
										'create_channel_reliable',
165
										'schedule_disconnection',
166
										'destroy_channel',
167
										'destroy_app'),
168
				'TC_SRC_HCT_BV_04_I': (	'create_app_source',
169
										'destroy_app'),
170
				'TC_SRC_HCT_BV_05_C': ( 'create_app_source_reliable',
171
										'create_channel_reliable',
172
										'schedule_disconnection',
173
										'destroy_channel',
174
										'destroy_app'),
175
				'TC_SRC_HCT_BV_06_C': (	'create_app_source',
176
									  	'destroy_app'),
177
				'TC_SRC_HCT_BV_07_C': (	'create_app_source_reliable',
178
										'destroy_app')
179
			}
180
181
"""
182
Notes (HDP.SRC.DE)
183
==================
184
185
PICS flags for enabling tests (set to True)
186
-------------------------------------------
187
188
- TC_SRC_DE_BV_01_I:
189
	- TSPC_HDP_7_1 (Source: Supports Initiation of Echo Test (O))
190
191
"""
192
hdp_src_de = {	'TC_SRC_DE_BV_01_I': (	'create_app_source',
193
										'echo', # no need to create a channel
194
										'destroy_app'),
195
				'TC_SRC_DE_BV_02_I': (	'create_app_source',
196
										'destroy_app') }
197
198
"""
199
Notes (HDP.SRC.DEP)
200
===================
201
202
PICS flags for enabling tests (set to True)
203
-------------------------------------------
204
205
- TC_SRC_DEP_BV_01_I and TC_SRC_DEP_BV_02_I:
206
	- TSPC_HDP_7_4 (Source: Supports IEEE 11073-20601 Agent Role (C.1))
207
208
Broken tests
209
------------
210
211
- All tests require an "associate request", which ATM is unknown.
212
"""
213
hdp_src_dep = {'TC_SRC_DEP_BV_01_I': ( 'create_app_source',
214
					'acquire_channel_src',
215
					'send_assoc',
216
					'send_config',
217
					'send_sample',
218
					'send_dissoc',
219
					'destroy_app'),
220
		'TC_SRC_DEP_BV_02_I': ( 'create_app_source',
221
					'acquire_channel_src',
222
					'send_assoc',
223
					'send_config',
224
					'send_sample',
225
					'send_dissoc',
226
					'send_assoc',
227
					'send_sample',
228
					'send_dissoc',
229
					'destroy_app')
230
		}
231
232
233
"""
234
Notes (HDP.SNK.CON)
235
===================
236
237
PICS flags for enabling tests (set to True)
238
-------------------------------------------
239
240
- TC_SNK_CON_BV_09_I
241
	- TSPC_HDP_8_9 (Sink: Supports Initiation of Bonding (O))
242
243
- TC_SNK_CON_BV_10_I
244
	- TSPC_HDP_8_11 (Sink: Supports use of Health Class of Device (O))
245
"""
246
247
hdp_snk_con = { 'TC_SNK_CON_BV_01_I': ( 'create_app_sink',
248
										'destroy_app'),
249
				'TC_SNK_CON_BV_02_I': ( 'create_app_sink',
250
										'destroy_app'),
251
				'TC_SNK_CON_BV_03_I': ( 'create_app_sink',
252
										'destroy_app'),
253
				'TC_SNK_CON_BV_04_I': ( 'delete_pairing',
254
										'pair',
255
										'create_app_sink',
256
										'create_channel_any',
257
										'destroy_channel',
258
										'destroy_app'),
259
				'TC_SNK_CON_BV_05_I': ('create_app_sink', 'destroy_app'),
260
				'TC_SNK_CON_BV_06_I': ( 'create_app_sink',
261
										'create_channel_any'),
262
				'TC_SNK_CON_BV_07_I': ( 'discover', ),
263
				'TC_SNK_CON_BV_08_I': ( 'delete_pairing',
264
										'create_app_sink',
265
										'destroy_app'),
266
				'TC_SNK_CON_BV_09_I': ( 'create_app_sink',
267
										'create_channel_any',
268
										'destroy_channel',
269
										'destroy_app'),
270
				'TC_SNK_CON_BV_10_I': ( 'set_discoverable', ) }
271
272
hdp_snk_cc = { 	'TC_SNK_CC_BV_01_C': ( 	'create_app_sink',
273
										'create_channel_any',
274
										'destroy_channel',
275
										'destroy_app' ),
276
				'TC_SNK_CC_BV_02_C': (	'create_app_sink',
277
										'destroy_app'),
278
				'TC_SNK_CC_BV_04_C': (	'create_app_sink',
279
										'create_channel_any',
280
										'destroy_app'),
281
				'TC_SNK_CC_BV_06_C': (	'create_app_sink',
282
										'destroy_app'),
283
				'TC_SNK_CC_BV_08_C': (	'create_app_sink',
284
										'create_channel_any',
285
										'create_channel_any',
286
										'destroy_app'),
287
				'TC_SNK_CC_BV_10_C': (	'create_app_sink',
288
										'destroy_app'),
289
				'TC_SNK_CC_BI_11_C': (	'create_app_sink',
290
										'destroy_app')}
291
292
"""
293
Notes (HDP.SNK.HCT)
294
===================
295
296
PICS flags for enabling tests (set to True)
297
-------------------------------------------
298
299
- TC_SNK_HCT_BV_03_I and TC_SNK_HCT_BV_05_C
300
	- TSPC_HDP_12_4 (Sink: SUpports Initiate Reconnection of MDL (O))
301
302
"""
303
hdp_snk_hct = { 'TC_SNK_HCT_BV_01_I': (	'create_app_sink',
304
										'create_channel_any',
305
										'destroy_channel',
306
										'destroy_app'),
307
				'TC_SNK_HCT_BV_02_I': ( 'create_app_sink',
308
										'destroy_app'),
309
				'TC_SNK_HCT_BV_03_I': ( 'create_app_sink',
310
										'create_channel_any',
311
										'schedule_disconnection',
312
										'destroy_channel',
313
										'destroy_app'),
314
				'TC_SNK_HCT_BV_04_I': ( 'create_app_sink',
315
										'destroy_app'),
316
				'TC_SNK_HCT_BV_05_C': ( 'create_app_sink',
317
										'create_channel_any',
318
										'schedule_disconnection',
319
										'destroy_channel',
320
										'destroy_app'),
321
				'TC_SNK_HCT_BV_06_C': ( 'create_app_sink',
322
										'destroy_app'),
323
				'TC_SNK_HCT_BV_07_C': ( 'create_app_sink',
324
										'destroy_app'),
325
			}
326
327
"""
328
Notes (HDP.SNK.DE)
329
==================
330
331
PICS flags for enabling tests (set to True)
332
-------------------------------------------
333
- TC_SNK_DE_BV_01_I
334
	- TSPC_HDP_13_1 (Sink: Supports Initiation of Echo Test (O))
335
"""
336
hdp_snk_de = { 	'TC_SNK_DE_BV_01_I': (	'create_app_sink',
337
										'echo',
338
										'destroy_app'),
339
				'TC_SNK_DE_BV_02_I': (	'create_app_sink',
340
										'destroy_app') }
341
342
"""
343
Notes (HDP.SNK.DEP)
344
===================
345
346
PICS flags for enabling tests (set to True)
347
-------------------------------------------
348
- TC_SNK_DEP_BV_03_I and TC_SNK_DEP_BV_04_I:
349
	- TSPC_HDP_13_5 (Sink: Supports IEEE 11073-20601 Manager Role (C.1))
350
351
Broken tests
352
------------
353
- Missing IEEE stack to be released
354
355
"""
356
hdp_snk_dep = {	'TC_SNK_DEP_BV_03_I': ('create_app_sink',
357
					'set_unknown_config',
358
					'create_channel_any',
359
					'acquire_channel',
360
					'destroy_app'),
361
		'TC_SNK_DEP_BV_04_I': ('create_app_sink',
362
					'set_unknown_config',
363
					'create_channel_any',
364
					'acquire_channel',
365
					'destroy_app'),
366
		}
367
368
hdp_snk_suites = {'SNK_CON': hdp_snk_con,
369
				  'SNK_CC': hdp_snk_cc,
370
				  'SNK_HCT': hdp_snk_hct,
371
				  'SNK_DE': hdp_snk_de,
372
				  'SNK_DEP': hdp_snk_dep}
373
374
hdp_src_suites = {'SRC_CON': hdp_src_con,
375
				  'SRC_CC': hdp_src_cc,
376
				  'SRC_HCT': hdp_src_hct,
377
				  'SRC_DE': hdp_src_de,
378
				  'SRC_DEP': hdp_src_dep}
379
380
381
groups = {'SRC': hdp_src_suites,
382
		  'SNK': hdp_snk_suites}
383
384
# Dictates if we report to the other side that IEEE configuration is unknown
385
unknown_config = False
386
387
def set_unknown_config():
388
	global unknown_config
389
	unknown_config = True
390
391
def is_unknown_config(data):
392
	global unknown_config
393
	res = unknown_config
394
	unknown_config = False # next time, it is not unknown anymore
395
	return res
396
397
watch_bitmap = glib.IO_IN | glib.IO_ERR | glib.IO_HUP | glib.IO_NVAL
398
399
def data_received_src(sk, evt):
400
	return data_received(sk, evt, mode=1)
401
402
def data_received(sk, evt, mode=0):
403
	data = None
404
	if evt & glib.IO_IN:
405
		try:
406
			data = sk.recv(1024)
407
		except IOError:
408
			data = ""
409
		if data:
410
			if not mode:
411
				print "Data received (sink mode)"
412
				response = parse_message_str(data, is_unknown_config(data))
413
				if response:
414
					sk.send(response)
415
					print "Response sent"
416
			else:
417
				print "Data received (src mode)"
418
419
	more = (evt == glib.IO_IN and data)
420
421
	if not more:
422
		print "EOF"
423
		try:
424
			sk.shutdown(2)
425
		except IOError:
426
			pass
427
		print "Closing"
428
		sk.close()
429
430
	return more
431
432
433
class Rejected(dbus.DBusException):
434
	_dbus_error_name = "org.bluez.Error.Rejected"
435
436
class PairAgent(dbus.service.Object):
437
438
	def __init__(self, bus, path, obs):
439
		dbus.service.Object.__init__(self, bus, path)
440
		self.obs = obs
441
442
	@dbus.service.method("org.bluez.Agent",
443
					in_signature="", out_signature="")
444
	def Release(self):
445
		print 'Release'
446
447
	@dbus.service.method("org.bluez.Agent",
448
					in_signature="os", out_signature="")
449
	def Authorize(self, device, uuid):
450
		print "Authorize (%s, %s)" % (device, uuid)
451
		authorize = raw_input("Authorize connection (Yes/no): ")
452
		if (authorize.lower() == "yes"):
453
			return
454
		raise Rejected("Connection rejected by user")
455
456
	@dbus.service.method("org.bluez.Agent",
457
					in_signature="o", out_signature="s")
458
	def RequestPinCode(self, device):
459
		print "RequestPinCode (%s)" % (device)
460
		pin = raw_input("Enter PIN Code: ")
461
		return pin
462
463
	@dbus.service.method("org.bluez.Agent",
464
					in_signature="o", out_signature="u")
465
	def RequestPasskey(self, device):
466
		print "RequestPasskey (%s)" % (device)
467
		passkey = raw_input("Enter passkey: ")
468
		return dbus.UInt32(passkey)
469
470
	@dbus.service.method("org.bluez.Agent",
471
					in_signature="ou", out_signature="")
472
	def DisplayPasskey(self, device, passkey):
473
		print "DisplayPasskey (%s, %d)" % (device, passkey)
474
475
	@dbus.service.method("org.bluez.Agent",
476
					in_signature="ou", out_signature="")
477
	def RequestConfirmation(self, device, passkey):
478
		print "RequestConfirmation (%s, %d)" % (device, passkey)
479
		confirm = raw_input("Confirm passkey (Yes/no): ")
480
		if (confirm.lower() == "yes"):
481
			return
482
		raise Rejected("Passkey doesn't match")
483
484
	@dbus.service.method("org.bluez.Agent",
485
					in_signature="s", out_signature="")
486
	def ConfirmModeChange(self, mode):
487
		print "ConfirmModeChange (%s)" % (mode)
488
		authorize = raw_input("Authorize mode change (Yes/no): ")
489
		if (authorize.lower() == "yes"):
490
			return
491
		raise Rejected("Mode change by user")
492
493
	@dbus.service.method("org.bluez.Agent",
494
					in_signature="", out_signature="")
495
	def Cancel(self):
496
		print "Cancel"
497
498
class HDPAutoPTS:
499
	""" Runs an interactive prompt for automatically test BlueZ against PTS.
500
	"""
501
	def __init__(self, bdaddr, tests, mainloop):
502
		glib.io_add_watch(sys.stdin, glib.IO_IN | glib.IO_PRI, self.tick)
503
504
		self.mainloop = mainloop
505
		self.device_bdaddr = bdaddr
506
		self.data_type = 4103
507
508
		# Test context
509
		self.app = None
510
		self.device = None
511
		self.channels = []
512
		self.sk = None
513
514
		# Test suite context
515
		self.tests = tests
516
		self.test = None
517
		self.cmd = None
518
		self.cmd_idx = 0
519
		self._device_found = False
520
		self.discovering = False
521
522
		self.bus = dbus.SystemBus()
523
		self.manager = dbus.Interface(self.bus.get_object("org.bluez", "/"),
524
				"org.bluez.Manager")
525
		self.hdp_manager = dbus.Interface(self.bus.get_object("org.bluez",
526
				"/org/bluez"), "org.bluez.HealthManager")
527
		self.agent = PairAgent(self.bus, '/test/agent', self)
528
529
		self.select_adapter()
530
531
	def select_adapter(self):
532
		""" Prompts user to select the adapter used for tests.
533
		"""
534
		sel = self.manager.DefaultAdapter()
535
		adapters = self.manager.ListAdapters()
536
537
		if not adapters:
538
			print "No adapters, test can not be run"
539
			sys.exit(1)
540
541
		print "Select an adapter [%s]:" % sel 
542
543
		for ad in adapters:
544
			print "\t%d. %s" % (adapters.index(ad) + 1, ad)
545
546
		while True:
547
			if len(adapters) <= 1:
548
				sel = adapters[0]
549
				print 'Auto-selected adapter %s.' % sel
550
				break
551
552
			try:
553
				print 'Select: ',
554
				cmd = sys.stdin.readline()
555
				if cmd == '\n':
556
					print
557
					break
558
				pos = int(cmd) - 1
559
				if pos < 0:
560
					raise TypeError
561
				sel = adapters[pos]
562
				print
563
				print 'Selected adapter', sel
564
				break
565
			except (TypeError, IndexError, ValueError):
566
				print "Wrong selection, choose a device from the list."
567
			except KeyboardInterrupt:
568
				sys.exit()
569
570
		self.adapter = dbus.Interface(self.bus.get_object("org.bluez", sel),
571
				"org.bluez.Adapter")
572
		self.bus.add_signal_receiver(self.device_found,
573
				dbus_interface="org.bluez.Adapter",
574
				signal_name="DeviceFound")
575
		self.bus.add_signal_receiver(self.property_changed,
576
				dbus_interface="org.bluez.Adapter",
577
				signal_name="PropertyChanged")
578
		self.bus.add_signal_receiver(self.channel_connected,
579
			signal_name="ChannelConnected",
580
			bus_name="org.bluez",
581
			path_keyword="device",
582
			interface_keyword="interface",
583
			dbus_interface="org.bluez.HealthDevice")
584
585
586
	def set_discoverable(self):
587
		self.adapter.SetProperty('Discoverable', dbus.Boolean(1))
588
		print 'Adapter set to General Discoverable mode...'
589
590
		cls = int(self.adapter.GetProperties()['Class'])
591
		if cls != 0x4a0914:
592
			print 'Make sure that device class is Health (0x4a0914).' \
593
			      'Configure with \'sudo hciconfig <IFACE> class 0x4a0914\'' \
594
			      'Check with \'sudo hciconfig -a\''
595
			print "0x%x" % cls
596
597
	def select_device(self):
598
		devices = self.adapter.ListDevices()
599
		select = None
600
601
		if len(devices) == 0:
602
			print "No devices available"
603
			sys.exit()
604
605
		for dev in devices:
606
			odev = dbus.Interface(self.bus.get_object("org.bluez", dev),
607
							"org.bluez.Device")
608
			addr = odev.GetProperties()["Address"].lower()
609
			if self.device_bdaddr == addr:
610
				select = dev
611
				break
612
		else:
613
			print "PTS device not found"
614
			sys.exit()
615
616
		print
617
		print "Connecting to", select
618
		self.device = dbus.Interface(self.bus.get_object("org.bluez", select),
619
						"org.bluez.HealthDevice")
620
621
	def echo(self):
622
		print 'Echoing...'
623
		self.select_device()
624
		ans = self.device.Echo()
625
		print 'Press OK on PTS side now. (Echo answer was %s)' % ans
626
627
	def pair(self):
628
		print 'Pairing..'
629
		self.pairing = True
630
		self.adapter.CreatePairedDevice(self.device_bdaddr, '/test/agent',
631
			'DisplayYesNo', reply_handler=self.pair_reply, error_handler=self.pair_error)
632
633
		slept = 0
634
		while self.pairing:
635
			time.sleep(1)
636
			slept += 1
637
			if slept >= 10:
638
				break
639
640
		self.pairing = False
641
642
	def delete_pairing(self):
643
		print 'Deleting pairing with %s' % self.device_bdaddr
644
		try:
645
			device = self.adapter.FindDevice(self.device_bdaddr)
646
			if device:
647
				self.adapter.RemoveDevice(device)
648
		except:
649
			pass
650
		print 'Pairing deleted. Input the PIN number whenever requested.'
651
652
	def device_found(self, addr, props):
653
		""" Receives DeviceFound signals, used for discovery.
654
		"""
655
		if not self.discovering or self._device_found:
656
			return
657
658
		if addr.lower() == self.device_bdaddr:
659
			print 'Device found! Confirm on PTS that it was found to continue.'
660
			self.discovering = False
661
			self._device_found = True
662
		else:
663
			print 'Found device', addr, 'ignoring...'
664
665
	def discover(self):
666
		print 'Discovering device...'
667
		self.discovering = True
668
		self.adapter.StartDiscovery()
669
670
		# Block cmd input until finished discovering or timeout (10s)
671
		slept = 0
672
		while self.discovering:
673
			time.sleep(1)
674
			slept += 1
675
			if slept >= 15:
676
				break
677
678
		print 'Discovering timed out. Next test will be ready to execute.'
679
680
	def property_changed(self, name, value):
681
		if not self.discovering:
682
			return
683
684
		if name == 'Discovering' and not value:
685
			self.discovering = False
686
			if self._device_found:
687
				print 'Finished discovery, device was found. Confirm on PTS if'\
688
					  ' you haven\'t already.'
689
690
	def pair_reply(self, device):
691
		print 'Finished pairing with', device
692
		self.pairing = False
693
694
	def pair_error(self, error):
695
		print 'Failed to pair,', error
696
		self.pairing = False
697
698
	def create_app(self, data_type, role, chanpref=''):
699
		if not self.hdp_manager:
700
			return False
701
702
		conf = {'DataType': dbus.types.UInt16(data_type),
703
				'Role': role}
704
705
		if chanpref in ['Any', 'Reliable', 'Streaming']:
706
			conf['ChannelType'] = chanpref
707
708
		self.app = self.hdp_manager.CreateApplication(conf)
709
		return self.app
710
711
	def create_app_source_streaming(self):
712
		return self.create_app(self.data_type, 'Source', 'Streaming')
713
714
	def create_app_source_reliable(self):
715
		return self.create_app(self.data_type, 'Source', 'Reliable')
716
717
	def create_app_sink(self):
718
		return self.create_app(self.data_type, 'Sink')
719
720
	def create_app_source(self):
721
		return self.create_app(self.data_type, 'Source')
722
723
	def destroy_app(self):
724
		print 'Destroying app'
725
		self.hdp_manager.DestroyApplication(self.app)
726
		self.app = None
727
728
	def toggle_dc(self, channel):
729
		channel = dbus.Interface(self.bus.get_object("org.bluez", channel),
730
						"org.bluez.HealthChannel")
731
		channel.Release()
732
		glib.timeout_add(3000, self.toggle_dc_2, channel)
733
		return False
734
735
	def toggle_dc_2(self, channel):
736
		retries = 3
737
		while retries > 0:
738
			try:
739
				fd = channel.Acquire()
740
			except:
741
				print 'Acquire() failed, retrying'
742
			retries -= 1
743
		glib.timeout_add(3000, self.toggle_dc_3, channel, fd)
744
		return False
745
746
	def toggle_dc_3(self, channel, fd):
747
		os.close(fd.take())
748
749
		try:
750
			channel.Release()
751
		except:
752
			print 'Release failed (probably channel is gone)'
753
754
	def schedule_disconnection(self):
755
		glib.timeout_add(3000, self.toggle_dc, self.channels[-1])
756
757
	def send_data(self):
758
		if len(self.channels) == 0:
759
			return
760
761
		channel = self.channels[-1]
762
		chan = dbus.Interface(self.bus.get_object("org.bluez", channel),
763
						"org.bluez.HealthChannel")
764
		fd = chan.Acquire()
765
		fd = fd.take()
766
		fd.write('Hello PTS')
767
768
	def set_unknown_config(self):
769
		set_unknown_config()
770
771
	def send_assoc(self):
772
		if not self.sk:
773
			print "No socket to send data"
774
			return
775
		self.sk.send(make_assoc_str())
776
777
	def send_config(self):
778
		self.sk.send(make_config_str())
779
780
	def send_sample(self):
781
		self.sk.send(make_sample_str())
782
783
	def send_dissoc(self):
784
		self.sk.send(make_release_req_str())
785
786
	def create_channel(self, type):
787
		self.select_device()
788
		try:
789
			channel = self.device.CreateChannel(self.app, type)
790
			self.channels.append(channel)
791
		except dbus.exceptions.DBusException, e:
792
			print 'Failed to create channel. You might have selected the' \
793
				' wrong device or the test already passed, check PTS.', e
794
795
	def channel_connected(self, channel, interface, device):
796
		print "Channel connected signal"
797
		if channel not in self.channels:
798
			self.device = device
799
			self.channels.append(channel)
800
			print "	...appended"
801
802
	def destroy_channel(self, channel=None):
803
		if not self.device or not self.channels:
804
			return
805
806
		if not channel:
807
			if len(self.channels) == 0:
808
				return
809
			channel = self.channels[-1]
810
811
		self.device.DestroyChannel(channel)
812
		self.channels.remove(channel)
813
814
	def acquire_channel_src(self, channel=None):
815
		self.acquire_channel(channel, 1)
816
817
	def acquire_channel(self, channel=None, mode=0):
818
		if not self.device or not self.channels:
819
			print "No channel to acquire"
820
			return
821
822
		if not channel:
823
			if len(self.channels) == 0:
824
				return
825
			channel = self.channels[-1]
826
827
		chan = dbus.Interface(self.bus.get_object("org.bluez", channel),
828
						"org.bluez.HealthChannel")
829
		fd = chan.Acquire()
830
		fd = fd.take()
831
832
		# encapsulate numericfd in Python socket object
833
		self.sk = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM)
834
835
		# fromfd() does dup() so we need to close the original
836
		os.close(fd)
837
838
		print "FD acquired"
839
		if mode:
840
			cb = data_received_src
841
		else:
842
			cb = data_received
843
		glib.io_add_watch(self.sk, watch_bitmap, cb)
844
845
	def release_channel(self, channel=None):
846
		self.sk = None
847
		if not self.device or not self.channels:
848
			return
849
850
		if not channel:
851
			if len(self.channels) == 0:
852
				return
853
			channel = self.channels[-1]
854
855
		chan = dbus.Interface(self.bus.get_object("org.bluez", channel),
856
						"org.bluez.HealthChannel")
857
		chan.Release()
858
859
	def create_channel_any(self):
860
		self.create_channel("Any")
861
862
	def create_channel_reliable(self):
863
		self.create_channel('Reliable')
864
865
	def create_channel_streaming(self):
866
		self.create_channel('Streaming')
867
868
	def tick(self, src, cond):
869
		available = c_long()
870
		ioctl(src, termios.FIONREAD, available)
871
		ch = src.read(available.value)
872
873
		if ch.lower().startswith('no'):
874
			self.skip()
875
		else:
876
			self.run()
877
878
		return True
879
880
	def next_cmd(self):
881
		cmds = self.test[1]
882
883
		if self.cmd_idx + 1 == len(cmds):
884
			print 'Test finished'
885
			self.cmd_idx = 0
886
			self.next_test()
887
		else:
888
			self.cmd_idx += 1
889
			self.cmd = cmds[self.cmd_idx]
890
			print 'Execute command %s ? (Yes/no) [Yes]' % self.cmd
891
892
	def next_test(self):
893
		if not self.test:
894
			self.mainloop.quit()
895
			return
896
897
		test_idx = self.tests.index(self.test)
898
		if test_idx + 1 == len(self.tests):
899
			print 'Finished all tests listed on file.'
900
			opt = raw_input('Do you want to start over? (Yes/no) [Yes]\n')
901
			if opt.lower().startswith('no'):
902
				self.mainloop.quit()
903
			else:
904
				self.test = self.tests[0]
905
				print 'Enter test %s (Yes/no) [Yes]' % self.test[0]
906
		else:
907
			self.test = self.tests[test_idx + 1]
908
			print 'Enter test %s ? (Yes/no) [Yes]' % self.test[0]
909
910
		self.cmd = None
911
912
	def run(self):
913
		if not self.test:
914
			self.test = self.tests[0]
915
			print 'Enter test %s ? (Yes/no) [Yes]' % self.test[0]
916
			return
917
918
		if not self.cmd:
919
			self.cmd = self.test[1][0]
920
			self.cmd_idx = 0
921
			print 'Execute command %s ? (Yes/no) [Yes]' % self.cmd
922
			return
923
924
		try:
925
			getattr(self, self.cmd)()
926
			self.next_cmd()
927
		except Exception, e:
928
			print 'Failed to call command', e, self.cmd
929
			self.skip()
930
931
	def skip(self):
932
		self.cmd_idx = 0
933
		self.cmd = None
934
		print 'Exiting test %s' % self.test[0]
935
		self.next_test()
936
937
def get_tests(fname):
938
	tests = []
939
	names = [l.replace('\n', '') for l in open(fname, 'r').readlines() if not
940
			l.startswith('[')]
941
942
	raw_tests = {}
943
	for g in groups.values():
944
		for s in g.values():
945
			for test_name, test in s.items():
946
				if test_name in names:
947
					raw_tests[test_name] = test
948
949
	for name in names:
950
		if name in raw_tests:
951
			tests.append((name, raw_tests[name]))
952
953
	return tests
954
955
def usage():
956
	print 'Usage: %s --list\t(list tests)' % sys.argv[0]
957
	print '       %s <PTS dongle bdaddr> <infile>\t(run tests listed on infile)' % sys.argv[0]
958
	sys.exit(0)
959
960
def list_tests():
961
	for gname, g in groups.items():
962
		for sname, s in g.items():
963
			print
964
			print '[Namespace [%s.%s]]' % (gname, sname)
965
966
			tests = s.keys()
967
			def test_sorter(a, b):
968
				if 'bi' in b.lower():
969
					return -1
970
				elif 'bi' in a.lower():
971
					return 1
972
				return cmp(a, b)
973
			tests.sort(test_sorter)
974
			print '\n'.join(tests)
975
	sys.exit(0)
976
977
def check_bdaddr(addr):
978
	if addr.count(':') == 5 and \
979
		not [c for c in addr.replace(':', '') if c.lower() > 'f']:
980
		return addr
981
982
if __name__ == '__main__':
983
	if len(sys.argv) < 2:
984
		usage()
985
	elif len(sys.argv) == 2:
986
		{'--list': list_tests, \
987
		 '--help': usage,}.get(sys.argv[1], usage)()
988
	else:
989
		pass
990
991
	bdaddr = check_bdaddr(sys.argv[1].lower())
992
	infile = sys.argv[2]
993
994
	if not bdaddr:
995
		print 'Invalid broadcast address:', sys.argv[1]
996
		sys.exit(0)
997
998
	if not os.path.exists(infile):
999
		print 'Input file %s does not exist.' % infile
1000
		sys.exit(0)
1001
1002
	tests = get_tests(infile)
1003
1004
	if not tests:
1005
		print 'Could not match any tests on input with our list.'
1006
		sys.exit(0)
1007
1008
	print 'Current test suite: [', ', '.join([t[0] for t in tests]), ']'
1009
1010
	dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
1011
	mainloop = gobject.MainLoop()
1012
	suite = HDPAutoPTS(bdaddr, tests, mainloop)
1013
1014
	try:
1015
		print 'Enter test suite? (Yes/no) [Yes]'
1016
		mainloop.run()
1017
	except KeyboardInterrupt:
1018
		print 'Bye'