All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH BlueZ 1/2] test: Drive test-mesh with a string-based menu
@ 2019-04-18  6:31 Inga Stotland
  2019-04-18  6:31 ` [PATCH BlueZ 2/2] test: Enable test-mesh to send raw vendor commands Inga Stotland
  2019-04-19 16:10 ` [PATCH BlueZ 1/2] test: Drive test-mesh with a string-based menu Gix, Brian
  0 siblings, 2 replies; 3+ messages in thread
From: Inga Stotland @ 2019-04-18  6:31 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: brian.gix, johan.hedberg, luiz.dentz, Inga Stotland

Switch to  string interactive commands to drive testing
of bluetooth-meshd. Re-work the menu to allow global setting of
destination address and AppKey index for outbound mesh messages.
---
 test/test-mesh | 539 +++++++++++++++++++++++++++++--------------------
 1 file changed, 317 insertions(+), 222 deletions(-)

diff --git a/test/test-mesh b/test/test-mesh
index fd02207bc..02f52a269 100755
--- a/test/test-mesh
+++ b/test/test-mesh
@@ -18,23 +18,26 @@
 #
 #      The test imitates a device with 2 elements:
 #            element 0: OnOff Server model
+#                       Sample Vendor model
 #            element 1: OnOff Client model
 #
 # The main menu:
-#       1 - set node ID (token)
-#       2 - join mesh network
-#       3 - attach mesh node
-#       4 - remove node
-#       5 - client menu
-#       6 - exit
+#       token
+#       join
+#       attach
+#       remove
+#       dest
+#       app-index
+#       client-menu
+#       exit
 #
 # The main menu options explained:
-#     1 - set token
+#     token
 #            Set the unique node token.
 #            The token can be set from command line arguments as
 #            well.
 #
-#     2 - join
+#     join
 #            Request provisioning of a device to become a node
 #            on a mesh network. The test generates device UUID
 #            which is displayed and will need to be provided to
@@ -49,7 +52,7 @@
 #            'token' is returned to the application and is used
 #            for the runtime of the test.
 #
-#     3 - attach
+#     attach
 #            Attach the application to bluetoothd-daemon as a node.
 #            For the call to be successful, the valid node token must
 #            be already set, either from command arguments or by
@@ -57,16 +60,24 @@
 #            successfully executing "join" operation in the same test
 #            run.
 #
-#     4 - remove
+#     remove
 #           Permanently removes any node configuration from daemon
 #           and persistent storage. After this operation, the node
 #           is permanently forgotten by the daemon and the associated
 #           node token is no longer valid.
 #
-#     5 - client menu
+#     dest
+#           Set destination address to send messages: 4 hex digits
+#
+#     app-index
+#           Set AppKey index to indicate which application key to use
+#           to encode outgoing messages: up to 3 hex digits
+#
+#     client-menu
 #           Enter On/Off client submenu.
 #
-#     6 - exit
+#     quit
+#           Exits the test.
 #
 ###################################################################
 import sys
@@ -128,16 +139,41 @@ mainloop = None
 node = None
 mesh_net = None
 
-menu_level = 0
 dst_addr = 0x0000
 app_idx = 0
 
 # Node token housekeeping
 token = None
 have_token = False
+attached = False
+
+# Menu housekeeping
+MAIN_MENU = 0
+ON_OFF_CLIENT_MENU = 1
+
+INPUT_NONE = 0
+INPUT_TOKEN = 1
+INPUT_DEST_ADDRESS = 2
+INPUT_APP_KEY_INDEX = 3
+
+menus = []
+current_menu = None
 
 user_input = 0
+input_error = False
 
+def raise_error(str_value):
+	global input_error
+
+	input_error = True
+	print(set_error(str_value))
+
+def clear_error():
+	global input_error
+	input_error = False
+
+def is_error():
+	return input_error
 
 def app_exit():
 	global mainloop
@@ -149,11 +185,28 @@ def app_exit():
 				model.timer.cancel()
 	mainloop.quit()
 
+def set_token(str_value):
+	global token
+	global have_token
+
+	if len(str_value) != 16:
+		raise_error('Expected 16 digits')
+		return
+
+	try:
+		input_number = int(str_value, 16)
+	except ValueError:
+		raise_error('Not a valid hexadecimal number')
+		return
+
+	token = numpy.uint64(input_number)
+	have_token = True
+
 def array_to_string(b_array):
-	str = ""
+	str_value = ""
 	for b in b_array:
-		str += "%02x" % b
-	return str
+		str_value += "%02x" % b
+	return str_value
 
 def generic_error_cb(error):
 	print(set_error('D-Bus call failed: ') + str(error))
@@ -177,6 +230,14 @@ def join_cb():
 def join_error_cb(reason):
 	print('Join procedure failed: ', reason)
 
+def remove_node_cb():
+	global attached
+	global have_token
+
+	print(set_yellow('Node removed'))
+	attached = False
+	have_token = False
+
 def unwrap(item):
 	if isinstance(item, dbus.Boolean):
 		return bool(item)
@@ -197,7 +258,11 @@ def unwrap(item):
 	return item
 
 def attach_app_cb(node_path, dict_array):
-	print('Mesh application registered ', node_path)
+	global attached
+
+	attached = True
+
+	print(set_yellow('Mesh app registered: ') + set_green(node_path))
 
 	obj = bus.get_object(MESH_SERVICE_NAME, node_path)
 
@@ -223,17 +288,6 @@ def interfaces_removed_cb(object_path, interfaces):
 		print('Service was removed')
 		app_exit()
 
-def send_response(path, dest, key, data):
-		node.Send(path, dest, key, data, reply_handler=generic_reply_cb,
-						error_handler=generic_error_cb)
-
-def send_publication(path, model_id, data):
-		print('Send publication ', end='')
-		print(data)
-		node.Publish(path, model_id, data,
-						reply_handler=generic_reply_cb,
-						error_handler=generic_error_cb)
-
 def print_state(state):
 	print('State is ', end='')
 	if state == 0:
@@ -315,13 +369,15 @@ class Application(dbus.service.Object):
 	def JoinComplete(self, value):
 		global token
 		global have_token
+		global attach
 
-		print('JoinComplete with token ' + set_green(hex(value)))
+		print(set_yellow('Joined mesh network with token ') +
+				set_green(format(value, '16x')))
 
 		token = value
 		have_token = True
-
-		attach(token)
+		if attached == False:
+			attach(token)
 
 	@dbus.service.method(MESH_APPLICATION_IFACE,
 					in_signature="s", out_signature="")
@@ -348,14 +404,28 @@ class Element(dbus.service.Object):
 				ids.append(id)
 		return ids
 
+	def _get_v_models(self):
+		ids = []
+		for model in self.models:
+			id = model.get_id()
+			v = model.get_vendor()
+			if v != VENDOR_ID_NONE:
+				vendor_id = (v, id)
+				ids.append(vendor_id)
+		return ids
+
 	def get_properties(self):
-		return {
-			MESH_ELEMENT_IFACE: {
-				'Index': dbus.Byte(self.index),
-				'Models': dbus.Array(
-					self._get_sig_models(), signature='q')
-			}
-		}
+		vendor_models = self._get_v_models()
+		sig_models = self._get_sig_models()
+
+		props = {'Index' : dbus.Byte(self.index)}
+		if len(sig_models) != 0:
+			props['Models'] = dbus.Array(sig_models, signature='q')
+		if len(vendor_models) != 0:
+			props['VendorModels'] = dbus.Array(vendor_models,
+							signature='(qq)')
+		#print(props)
+		return { MESH_ELEMENT_IFACE: props }
 
 	def add_model(self, model):
 		model.set_path(self.path)
@@ -381,8 +451,8 @@ class Element(dbus.service.Object):
 					in_signature="qa{sv}", out_signature="")
 
 	def UpdateModelConfiguration(self, model_id, config):
-		print('UpdateModelConfig ', end='')
-		print(hex(model_id))
+		print(('Update Model Config '), end='')
+		print(format(model_id, '04x'))
 		for model in self.models:
 			if model_id == model.get_id():
 				model.set_config(config)
@@ -420,6 +490,18 @@ class Model():
 	def set_publication(self, period):
 		self.pub_period = period
 
+	def send_publication(self, data):
+		print('Send publication ', end='')
+		print(data)
+		node.Publish(self.path, self.model_id, data,
+						reply_handler=generic_reply_cb,
+						error_handler=generic_error_cb)
+
+	def send_message(self, dest, key, data):
+		node.Send(self.path, dest, key, data,
+						reply_handler=generic_reply_cb,
+						error_handler=generic_error_cb)
+
 	def set_config(self, config):
 		if 'Bindings' in config:
 			self.bindings = config.get('Bindings')
@@ -432,13 +514,15 @@ class Model():
 			print(' ms')
 
 	def print_bindings(self):
-		print(set_cyan('Model'), set_cyan('%04x' % self.model_id),
-			set_cyan('is bound to application key(s): '), end = '')
+		print(set_cyan('Model'), set_cyan('%03x' % self.model_id),
+						set_cyan('is bound to: '))
 
 		if len(self.bindings) == 0:
 			print(set_cyan('** None **'))
+			return
+
 		for b in self.bindings:
-			print(set_cyan('%04x' % b), set_cyan(', '))
+			print(set_green('%03x' % b) + ' ')
 
 ########################
 # On Off Server Model
@@ -479,7 +563,7 @@ class OnOffServer(Model):
 			print_state(self.state)
 
 		rsp_data = struct.pack('<HB', 0x8204, self.state)
-		send_response(self.path, source, key, rsp_data)
+		self.send_message(source, key, rsp_data)
 
 	def set_publication(self, period):
 
@@ -494,11 +578,10 @@ class OnOffServer(Model):
 
 		self.timer.start(period/1000, self.publish)
 
-
 	def publish(self):
 		print('Publish')
 		data = struct.pack('<HB', 0x8204, self.state)
-		send_publication(self.path, self.model_id, data)
+		self.send_publication(data)
 
 ########################
 # On Off Client Model
@@ -512,25 +595,20 @@ class OnOffClient(Model):
 				 0x8204 } # status
 		print('OnOff Client')
 
-	def _reply_cb(state):
-		print('State ', end='');
-		print(state)
-
-	def _send_message(self, dest, key, data, reply_cb):
-		print('OnOffClient send data')
-		node.Send(self.path, dest, key, data, reply_handler=reply_cb,
-				  error_handler=generic_error_cb)
+	def _send_message(self, dest, key, data):
+		print('OnOffClient send command')
+		self.send_message(dest, key, data)
 
 	def get_state(self, dest, key):
 		opcode = 0x8201
 		data = struct.pack('<H', opcode)
-		self._send_message(dest, key, data, self._reply_cb)
+		self._send_message(dest, key, data)
 
 	def set_state(self, dest, key, state):
 		opcode = 0x8202
-		print('State:', state)
+		print('Set state:', state)
 		data = struct.pack('<HB', opcode, state)
-		self._send_message(dest, key, data, self._reply_cb)
+		self._send_message(dest, key, data)
 
 	def process_message(self, source, key, data):
 		print('OnOffClient process message len = ', end = '')
@@ -541,7 +619,7 @@ class OnOffClient(Model):
 			# The opcode is not recognized by this model
 			return
 
-		opcode, state=struct.unpack('<HB',bytes(data))
+		opcode, state = struct.unpack('<HB',bytes(data))
 
 		if opcode != 0x8204 :
 			# The opcode is not recognized by this model
@@ -556,9 +634,54 @@ class OnOffClient(Model):
 		print(set_green(state_str), set_yellow('from'),
 						set_green('%04x' % source))
 
+########################
+# Sample Vendor Model
+########################
+class SampleVendor(Model):
+	def __init__(self, model_id):
+		Model.__init__(self, model_id)
+		self.vendor = 0x05F1 # Linux Foundation Company ID
+
 ########################
 # Menu functions
 ########################
+class MenuItem():
+	def __init__(self, desc, func):
+		self.desc = desc
+		self.func = func
+
+class Menu():
+	def __init__(self, title, menu):
+		self.title = title
+		self.menu = menu
+
+	def show(self):
+		print(set_cyan('*** ' + self.title.upper() + ' ***'))
+		for k, v in self.menu.items():
+			print(set_green(k), set_cyan(v.desc))
+
+	def process_cmd(self, str_value):
+		if is_error():
+			self.show()
+			clear_error()
+			return
+
+		cmds = []
+		for key in self.menu.keys():
+			if key.startswith(str_value):
+				cmds.append(key)
+
+		if len(cmds) == 0:
+			print(set_error('Unknown menu option: '), str_value)
+			self.show()
+			return
+		if len(cmds) > 1:
+			for cmd in cmds:
+			     print(set_cyan(cmd + '?'))
+			return
+
+		self.menu.get(cmds[0]).func()
+
 class MenuHandler(object):
 	def __init__(self, callback):
 		self.cb = callback
@@ -579,221 +702,184 @@ class MenuHandler(object):
 		return True
 
 def process_input(input_str):
-	if menu_level == 0:
-		process_main_menu(input_str)
-	elif menu_level == 1:
-		process_client_menu(input_str)
-	else:
-		print(set_error('BUG: bad menu level'))
+	str_value = input_str.strip()
+
+	# Allow entering empty lines for better output visibility
+	if len(str_value) == 0:
+		return
+
+	current_menu.process_cmd(str_value)
 
 def switch_menu(level):
-	global menu_level
+	global current_menu
 
-	if level > 1:
+	if level >= len(menus):
 		return
 
-	if level == 0:
-		main_menu()
-	elif level == 1:
-		client_menu()
-
-	menu_level = level
+	current_menu = menus[level]
+	current_menu.show()
 
 ########################
-# Main menu functions
+# Main menu class
 ########################
-def process_main_menu(input_str):
-	global token
-	global user_input
-	global have_token
+class MainMenu(Menu):
+	def __init__(self):
+		menu_items = {
+			'token': MenuItem(' - set node ID (token)',
+						self.__cmd_set_token),
+			'join': MenuItem(' - join mesh network',
+						self.__cmd_join),
+			'attach': MenuItem(' - attach mesh node',
+						self.__cmd_attach),
+			'remove': MenuItem(' - delete node',
+						self.__cmd_remove),
+			'dest': MenuItem(' - set destination address',
+						self.__cmd_set_dest),
+			'app-index': MenuItem(' - set AppKey index',
+						self.__cmd_set_app_idx),
+			'client-menu': MenuItem(' - On/Off client menu',
+						self.__cmd_client_menu),
+			'quit': MenuItem(' - exit the test', app_exit)
+		}
 
-	str = input_str.strip()
+		Menu.__init__(self, 'Main Menu', menu_items)
 
-	if user_input == 1:
-		res = set_token(str)
-		user_input = 0
+	def __cmd_client_menu(self):
+		if attached != True:
+			print(set_error('Disallowed: node is not attached'))
+			return
+		switch_menu(ON_OFF_CLIENT_MENU)
 
-		if res == False:
-			main_menu()
+	def __cmd_set_token(self):
+		global user_input
 
-		return
+		if have_token == True:
+			print('Token already set')
+			return
 
-	# Allow entering empty lines for better output visibility
-	if len(str) == 0:
-		return
+		user_input = INPUT_TOKEN
+		print(set_cyan('Enter 16-digit hex node ID:'))
 
-	if str.isdigit() == False:
-		main_menu()
-		return
+	def __cmd_set_dest(self):
+		global user_input
 
-	opt = int(str)
+		user_input = INPUT_DEST_ADDRESS
+		print(set_cyan('Enter 4-digit hex destination address:'))
 
-	if opt > 6:
-		print(set_error('Unknown menu option: '), opt)
-		main_menu()
-	elif opt == 1:
-		if have_token:
-			print('Token already set')
-			return
+	def __cmd_set_app_idx(self):
+		global user_input
 
-		user_input = 1;
-		print(set_cyan('Enter 16-digit hex node ID:'))
-	elif opt == 2:
+		user_input = INPUT_APP_KEY_INDEX;
+		print(set_cyan('Enter app key index (up to 3 digit hex):'))
+
+	def __cmd_join(self):
 		if agent == None:
 			print(set_error('Provisioning agent not found'))
 			return
 
-		join_mesh()
-	elif opt == 3:
+		uuid = bytearray.fromhex("0a0102030405060708090A0B0C0D0E0F")
+		random.shuffle(uuid)
+		uuid_str = array_to_string(uuid)
+		caps = ["out-numeric"]
+		oob = ["other"]
+
+		print(set_yellow('Joining with UUID ') + set_green(uuid_str))
+		mesh_net.Join(app.get_path(), uuid,
+			reply_handler=join_cb,
+			error_handler=join_error_cb)
+
+	def __cmd_attach(self):
 		if have_token == False:
 			print(set_error('Token is not set'))
-			main_menu()
+			self.show()
 			return
 
 		attach(token)
-	elif opt == 4:
+
+	def __cmd_remove(self):
 		if have_token == False:
 			print(set_error('Token is not set'))
-			main_menu()
+			self.show()
 			return
 
-		print('Remove mesh node')
-		mesh_net.Leave(token, reply_handler=generic_reply_cb,
+		print('Removing mesh node')
+		mesh_net.Leave(token, reply_handler=remove_node_cb,
 					error_handler=generic_error_cb)
-		have_token = False
-	elif opt == 5:
-		switch_menu(1)
-	elif opt == 6:
-		app_exit()
-
-
-def main_menu():
-	print(set_cyan('*** MAIN MENU ***'))
-	print(set_cyan('1 - set node ID (token)'))
-	print(set_cyan('2 - join mesh network'))
-	print(set_cyan('3 - attach mesh node'))
-	print(set_cyan('4 - remove node'))
-	print(set_cyan('5 - client menu'))
-	print(set_cyan('6 - exit'))
 
-def set_token(str):
-	global token
-	global have_token
-
-	if len(str) != 16:
-		print(set_error('Expected 16 digits'))
-		return False
-
-	try:
-		input_number = int(str, 16)
-	except ValueError:
-		print(set_error('Not a valid hexadecimal number'))
-		return False
-
-	token = numpy.uint64(input_number)
-	have_token = True
-
-	return True
-
-def join_mesh():
-	uuid = bytearray.fromhex("0a0102030405060708090A0B0C0D0E0F")
-
-	caps = ["out-numeric"]
-	oob = ["other"]
-
-	random.shuffle(uuid)
-	uuid_str = array_to_string(uuid)
-	print('Joining with UUID ' + set_green(uuid_str))
+	def process_cmd(self, str_value):
+		global user_input
+		global dst_addr
+		global app_idx
+
+		if user_input == INPUT_TOKEN:
+			set_token(str_value)
+		elif user_input == INPUT_DEST_ADDRESS:
+			res = set_value(str_value, 4, 4)
+			if is_error() != True:
+				dst_addr = res
+				print(set_yellow("Destination address: ") +
+					set_green(format(dst_addr, '04x')))
+		elif user_input == INPUT_APP_KEY_INDEX:
+			res = set_value(str_value, 1, 3)
+			if is_error() != True:
+				app_idx = res
+				print(set_yellow("Application index: ") +
+					set_green(format(app_idx, '03x')))
+
+		if user_input != INPUT_NONE:
+			user_input = INPUT_NONE
+			if is_error() != True:
+				return
 
-	mesh_net.Join(app.get_path(), uuid,
-			reply_handler=join_cb,
-			error_handler=join_error_cb)
+		Menu.process_cmd(self, str_value)
 
 ##############################
-# On/Off Client menu functions
+# On/Off Client menu class
 ##############################
-def process_client_menu(input_str):
-	global user_input
-	global dst_addr
-	global app_idx
-
-	res = -1
-	str = input_str.strip()
-
-	if user_input == 1:
-		res = set_value(str)
-		if res != -1:
-			dst_addr = res
-	elif user_input == 2:
-		res = set_value(str)
-		if res != -1:
-			app_idx = res
-
-	if user_input != 0:
-		user_input = 0
-		if res == -1:
-			client_menu()
-		return
-
-	# Allow entering empty lines for better output visibility
-	if len(str) == 0:
-		return
+class ClientMenu(Menu):
+	def __init__(self):
+		menu_items = {
+			'get-state': MenuItem(' - get server state',
+						self.__cmd_get_state),
+			'off': MenuItem(' - set state OFF',
+						self.__cmd_set_state_off),
+			'on': MenuItem(' - set state ON',
+						self.__cmd_set_state_on),
+			'back': MenuItem(' - back to main menu',
+						self.__cmd_main_menu),
+			'quit': MenuItem(' - exit the test', app_exit)
+		}
 
-	if str.isdigit() == False:
-		client_menu()
-		return
+		Menu.__init__(self, 'On/Off Clien Menu', menu_items)
 
-	opt = int(str)
+	def __cmd_main_menu(self):
+		switch_menu(MAIN_MENU)
 
-	if opt > 7:
-		print(set_error('Unknown menu option: '), opt)
-		client_menu()
-		return
+	def __cmd_get_state(self):
+		app.elements[1].models[0].get_state(dst_addr, app_idx)
 
-	if opt >= 3 and opt <= 5 and dst_addr == 0x0000:
-		print(set_error('Destination address not set!'))
-		return
+	def __cmd_set_state_off(self):
+		app.elements[1].models[0].set_state(dst_addr, app_idx, 0)
 
-	if opt == 1:
-		user_input = 1;
-		print(set_cyan('Enter 4-digit hex destination address:'))
-	elif opt == 2:
-		user_input = 2;
-		app.elements[1].models[0].print_bindings()
-		print(set_cyan('Choose application key index:'))
-	elif opt == 3:
-		app.elements[1].models[0].get_state(dst_addr, app_idx)
-	elif opt == 4 or opt == 5:
-		app.elements[1].models[0].set_state(dst_addr, app_idx, opt - 4)
-	elif opt == 6:
-		switch_menu(0)
-	elif opt == 7:
-		app_exit()
+	def __cmd_set_state_on(self):
+		app.elements[1].models[0].set_state(dst_addr, app_idx, 1)
 
-def client_menu():
-	print(set_cyan('*** ON/OFF CLIENT MENU ***'))
-	print(set_cyan('1 - set destination address'))
-	print(set_cyan('2 - set application key index'))
-	print(set_cyan('3 - get state'))
-	print(set_cyan('4 - set state OFF'))
-	print(set_cyan('5 - set state ON'))
-	print(set_cyan('6 - back to main menu'))
-	print(set_cyan('7 - exit'))
 
-def set_value(str):
+def set_value(str_value, min, max):
 
-	if len(str) != 4:
-		print(set_error('Expected 4 digits'))
+	if len(str_value) > max or len(str_value) < min:
+		raise_error('Bad input length %d' % len(str_value))
 		return -1
 
 	try:
-		value = int(str, 16)
+		value = int(str_value, 16)
 	except ValueError:
-		print(set_error('Not a valid hexadecimal number'))
+		raise_error('Not a valid hexadecimal number')
 		return -1
 
 	return value
 
+
 ########################
 # Main entry
 ########################
@@ -806,6 +892,8 @@ def main():
 	global mainloop
 	global app
 	global mesh_net
+	global menu
+	global current_menu
 
 	if len(sys.argv) > 1 :
 		set_token(sys.argv[1])
@@ -827,14 +915,21 @@ def main():
 	print(set_yellow('Register OnOff Server model on element 0'))
 	first_ele.add_model(OnOffServer(0x1000))
 
+	print(set_yellow('Register Vendor model on element 0'))
+	first_ele.add_model(SampleVendor(0x0001))
+
 	print(set_yellow('Register OnOff Client model on element 1'))
 	second_ele.add_model(OnOffClient(0x1001))
+
 	app.add_element(first_ele)
 	app.add_element(second_ele)
 
 	mainloop = GLib.MainLoop()
 
-	main_menu()
+	menus.append(MainMenu())
+	menus.append(ClientMenu())
+	switch_menu(MAIN_MENU)
+
 	event_catcher = MenuHandler(process_input);
 	mainloop.run()
 
-- 
2.17.2


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH BlueZ 2/2] test: Enable test-mesh to send raw vendor commands
  2019-04-18  6:31 [PATCH BlueZ 1/2] test: Drive test-mesh with a string-based menu Inga Stotland
@ 2019-04-18  6:31 ` Inga Stotland
  2019-04-19 16:10 ` [PATCH BlueZ 1/2] test: Drive test-mesh with a string-based menu Gix, Brian
  1 sibling, 0 replies; 3+ messages in thread
From: Inga Stotland @ 2019-04-18  6:31 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: brian.gix, johan.hedberg, luiz.dentz, Inga Stotland

This adds a sample vendor model to the first element of the
mesh node. A new menu entry allows to generate and send a raw
vendor command.
---
 test/test-mesh | 33 ++++++++++++++++++++++++++++++++-
 1 file changed, 32 insertions(+), 1 deletion(-)

diff --git a/test/test-mesh b/test/test-mesh
index 02f52a269..7201669a8 100755
--- a/test/test-mesh
+++ b/test/test-mesh
@@ -73,6 +73,16 @@
 #           Set AppKey index to indicate which application key to use
 #           to encode outgoing messages: up to 3 hex digits
 #
+#     vendor-send
+#           Allows to send an arbitrary endor message.
+#           The destination is set based on previously executed "dest"
+#           command (if not set, the outbound message will fail).
+#           User is prompted to enter hex bytearray payload.
+#           The message is originated from the vendor model registered
+#           on element 0. For the command to succeed, the AppKey index
+#           that is set by executing "app-key" must correspond to the
+#           application key to which the Sample Vendor model is bound.
+#
 #     client-menu
 #           Enter On/Off client submenu.
 #
@@ -155,6 +165,7 @@ INPUT_NONE = 0
 INPUT_TOKEN = 1
 INPUT_DEST_ADDRESS = 2
 INPUT_APP_KEY_INDEX = 3
+INPUT_MESSAGE_PAYLOAD = 4
 
 menus = []
 current_menu = None
@@ -542,7 +553,6 @@ class OnOffServer(Model):
 
 	def process_message(self, source, key, data):
 		datalen = len(data)
-		print('OnOff Server process message len: ', datalen)
 
 		if datalen != 2 and datalen != 3:
 			# The opcode is not recognized by this model
@@ -737,6 +747,8 @@ class MainMenu(Menu):
 						self.__cmd_set_dest),
 			'app-index': MenuItem(' - set AppKey index',
 						self.__cmd_set_app_idx),
+			'vendor-send': MenuItem(' - send raw vendor message',
+						self.__cmd_vendor_msg),
 			'client-menu': MenuItem(' - On/Off client menu',
 						self.__cmd_client_menu),
 			'quit': MenuItem(' - exit the test', app_exit)
@@ -772,6 +784,12 @@ class MainMenu(Menu):
 		user_input = INPUT_APP_KEY_INDEX;
 		print(set_cyan('Enter app key index (up to 3 digit hex):'))
 
+	def __cmd_vendor_msg(self):
+		global user_input
+
+		user_input = INPUT_MESSAGE_PAYLOAD;
+		print(set_cyan('Enter message payload (hex):'))
+
 	def __cmd_join(self):
 		if agent == None:
 			print(set_error('Provisioning agent not found'))
@@ -806,6 +824,17 @@ class MainMenu(Menu):
 		mesh_net.Leave(token, reply_handler=remove_node_cb,
 					error_handler=generic_error_cb)
 
+	def __send_vendor_msg(self, str_value):
+		try:
+			msg_data = bytearray.fromhex(str_value)
+		except ValueError:
+			raise_error('Not a valid hexadecimal input')
+			return
+
+		print(set_yellow('Send data: ' + set_green(str_value)))
+		app.elements[0].models[1].send_message(dst_addr, app_idx,
+							msg_data)
+
 	def process_cmd(self, str_value):
 		global user_input
 		global dst_addr
@@ -825,6 +854,8 @@ class MainMenu(Menu):
 				app_idx = res
 				print(set_yellow("Application index: ") +
 					set_green(format(app_idx, '03x')))
+		elif  user_input == INPUT_MESSAGE_PAYLOAD:
+			self.__send_vendor_msg(str_value)
 
 		if user_input != INPUT_NONE:
 			user_input = INPUT_NONE
-- 
2.17.2


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH BlueZ 1/2] test: Drive test-mesh with a string-based menu
  2019-04-18  6:31 [PATCH BlueZ 1/2] test: Drive test-mesh with a string-based menu Inga Stotland
  2019-04-18  6:31 ` [PATCH BlueZ 2/2] test: Enable test-mesh to send raw vendor commands Inga Stotland
@ 2019-04-19 16:10 ` Gix, Brian
  1 sibling, 0 replies; 3+ messages in thread
From: Gix, Brian @ 2019-04-19 16:10 UTC (permalink / raw)
  To: linux-bluetooth, Stotland, Inga; +Cc: luiz.dentz, johan.hedberg

Patch set applied

On Wed, 2019-04-17 at 23:31 -0700, Inga Stotland wrote:
> Switch to  string interactive commands to drive testing
> of bluetooth-meshd. Re-work the menu to allow global setting of
> destination address and AppKey index for outbound mesh messages.
> ---
>  test/test-mesh | 539 +++++++++++++++++++++++++++++--------------------
>  1 file changed, 317 insertions(+), 222 deletions(-)
> 
> diff --git a/test/test-mesh b/test/test-mesh
> index fd02207bc..02f52a269 100755
> --- a/test/test-mesh
> +++ b/test/test-mesh
> @@ -18,23 +18,26 @@
>  #
>  #      The test imitates a device with 2 elements:
>  #            element 0: OnOff Server model
> +#                       Sample Vendor model
>  #            element 1: OnOff Client model
>  #
>  # The main menu:
> -#       1 - set node ID (token)
> -#       2 - join mesh network
> -#       3 - attach mesh node
> -#       4 - remove node
> -#       5 - client menu
> -#       6 - exit
> +#       token
> +#       join
> +#       attach
> +#       remove
> +#       dest
> +#       app-index
> +#       client-menu
> +#       exit
>  #
>  # The main menu options explained:
> -#     1 - set token
> +#     token
>  #            Set the unique node token.
>  #            The token can be set from command line arguments as
>  #            well.
>  #
> -#     2 - join
> +#     join
>  #            Request provisioning of a device to become a node
>  #            on a mesh network. The test generates device UUID
>  #            which is displayed and will need to be provided to
> @@ -49,7 +52,7 @@
>  #            'token' is returned to the application and is used
>  #            for the runtime of the test.
>  #
> -#     3 - attach
> +#     attach
>  #            Attach the application to bluetoothd-daemon as a node.
>  #            For the call to be successful, the valid node token must
>  #            be already set, either from command arguments or by
> @@ -57,16 +60,24 @@
>  #            successfully executing "join" operation in the same test
>  #            run.
>  #
> -#     4 - remove
> +#     remove
>  #           Permanently removes any node configuration from daemon
>  #           and persistent storage. After this operation, the node
>  #           is permanently forgotten by the daemon and the associated
>  #           node token is no longer valid.
>  #
> -#     5 - client menu
> +#     dest
> +#           Set destination address to send messages: 4 hex digits
> +#
> +#     app-index
> +#           Set AppKey index to indicate which application key to use
> +#           to encode outgoing messages: up to 3 hex digits
> +#
> +#     client-menu
>  #           Enter On/Off client submenu.
>  #
> -#     6 - exit
> +#     quit
> +#           Exits the test.
>  #
>  ###################################################################
>  import sys
> @@ -128,16 +139,41 @@ mainloop = None
>  node = None
>  mesh_net = None
>  
> -menu_level = 0
>  dst_addr = 0x0000
>  app_idx = 0
>  
>  # Node token housekeeping
>  token = None
>  have_token = False
> +attached = False
> +
> +# Menu housekeeping
> +MAIN_MENU = 0
> +ON_OFF_CLIENT_MENU = 1
> +
> +INPUT_NONE = 0
> +INPUT_TOKEN = 1
> +INPUT_DEST_ADDRESS = 2
> +INPUT_APP_KEY_INDEX = 3
> +
> +menus = []
> +current_menu = None
>  
>  user_input = 0
> +input_error = False
>  
> +def raise_error(str_value):
> +	global input_error
> +
> +	input_error = True
> +	print(set_error(str_value))
> +
> +def clear_error():
> +	global input_error
> +	input_error = False
> +
> +def is_error():
> +	return input_error
>  
>  def app_exit():
>  	global mainloop
> @@ -149,11 +185,28 @@ def app_exit():
>  				model.timer.cancel()
>  	mainloop.quit()
>  
> +def set_token(str_value):
> +	global token
> +	global have_token
> +
> +	if len(str_value) != 16:
> +		raise_error('Expected 16 digits')
> +		return
> +
> +	try:
> +		input_number = int(str_value, 16)
> +	except ValueError:
> +		raise_error('Not a valid hexadecimal number')
> +		return
> +
> +	token = numpy.uint64(input_number)
> +	have_token = True
> +
>  def array_to_string(b_array):
> -	str = ""
> +	str_value = ""
>  	for b in b_array:
> -		str += "%02x" % b
> -	return str
> +		str_value += "%02x" % b
> +	return str_value
>  
>  def generic_error_cb(error):
>  	print(set_error('D-Bus call failed: ') + str(error))
> @@ -177,6 +230,14 @@ def join_cb():
>  def join_error_cb(reason):
>  	print('Join procedure failed: ', reason)
>  
> +def remove_node_cb():
> +	global attached
> +	global have_token
> +
> +	print(set_yellow('Node removed'))
> +	attached = False
> +	have_token = False
> +
>  def unwrap(item):
>  	if isinstance(item, dbus.Boolean):
>  		return bool(item)
> @@ -197,7 +258,11 @@ def unwrap(item):
>  	return item
>  
>  def attach_app_cb(node_path, dict_array):
> -	print('Mesh application registered ', node_path)
> +	global attached
> +
> +	attached = True
> +
> +	print(set_yellow('Mesh app registered: ') + set_green(node_path))
>  
>  	obj = bus.get_object(MESH_SERVICE_NAME, node_path)
>  
> @@ -223,17 +288,6 @@ def interfaces_removed_cb(object_path, interfaces):
>  		print('Service was removed')
>  		app_exit()
>  
> -def send_response(path, dest, key, data):
> -		node.Send(path, dest, key, data, reply_handler=generic_reply_cb,
> -						error_handler=generic_error_cb)
> -
> -def send_publication(path, model_id, data):
> -		print('Send publication ', end='')
> -		print(data)
> -		node.Publish(path, model_id, data,
> -						reply_handler=generic_reply_cb,
> -						error_handler=generic_error_cb)
> -
>  def print_state(state):
>  	print('State is ', end='')
>  	if state == 0:
> @@ -315,13 +369,15 @@ class Application(dbus.service.Object):
>  	def JoinComplete(self, value):
>  		global token
>  		global have_token
> +		global attach
>  
> -		print('JoinComplete with token ' + set_green(hex(value)))
> +		print(set_yellow('Joined mesh network with token ') +
> +				set_green(format(value, '16x')))
>  
>  		token = value
>  		have_token = True
> -
> -		attach(token)
> +		if attached == False:
> +			attach(token)
>  
>  	@dbus.service.method(MESH_APPLICATION_IFACE,
>  					in_signature="s", out_signature="")
> @@ -348,14 +404,28 @@ class Element(dbus.service.Object):
>  				ids.append(id)
>  		return ids
>  
> +	def _get_v_models(self):
> +		ids = []
> +		for model in self.models:
> +			id = model.get_id()
> +			v = model.get_vendor()
> +			if v != VENDOR_ID_NONE:
> +				vendor_id = (v, id)
> +				ids.append(vendor_id)
> +		return ids
> +
>  	def get_properties(self):
> -		return {
> -			MESH_ELEMENT_IFACE: {
> -				'Index': dbus.Byte(self.index),
> -				'Models': dbus.Array(
> -					self._get_sig_models(), signature='q')
> -			}
> -		}
> +		vendor_models = self._get_v_models()
> +		sig_models = self._get_sig_models()
> +
> +		props = {'Index' : dbus.Byte(self.index)}
> +		if len(sig_models) != 0:
> +			props['Models'] = dbus.Array(sig_models, signature='q')
> +		if len(vendor_models) != 0:
> +			props['VendorModels'] = dbus.Array(vendor_models,
> +							signature='(qq)')
> +		#print(props)
> +		return { MESH_ELEMENT_IFACE: props }
>  
>  	def add_model(self, model):
>  		model.set_path(self.path)
> @@ -381,8 +451,8 @@ class Element(dbus.service.Object):
>  					in_signature="qa{sv}", out_signature="")
>  
>  	def UpdateModelConfiguration(self, model_id, config):
> -		print('UpdateModelConfig ', end='')
> -		print(hex(model_id))
> +		print(('Update Model Config '), end='')
> +		print(format(model_id, '04x'))
>  		for model in self.models:
>  			if model_id == model.get_id():
>  				model.set_config(config)
> @@ -420,6 +490,18 @@ class Model():
>  	def set_publication(self, period):
>  		self.pub_period = period
>  
> +	def send_publication(self, data):
> +		print('Send publication ', end='')
> +		print(data)
> +		node.Publish(self.path, self.model_id, data,
> +						reply_handler=generic_reply_cb,
> +						error_handler=generic_error_cb)
> +
> +	def send_message(self, dest, key, data):
> +		node.Send(self.path, dest, key, data,
> +						reply_handler=generic_reply_cb,
> +						error_handler=generic_error_cb)
> +
>  	def set_config(self, config):
>  		if 'Bindings' in config:
>  			self.bindings = config.get('Bindings')
> @@ -432,13 +514,15 @@ class Model():
>  			print(' ms')
>  
>  	def print_bindings(self):
> -		print(set_cyan('Model'), set_cyan('%04x' % self.model_id),
> -			set_cyan('is bound to application key(s): '), end = '')
> +		print(set_cyan('Model'), set_cyan('%03x' % self.model_id),
> +						set_cyan('is bound to: '))
>  
>  		if len(self.bindings) == 0:
>  			print(set_cyan('** None **'))
> +			return
> +
>  		for b in self.bindings:
> -			print(set_cyan('%04x' % b), set_cyan(', '))
> +			print(set_green('%03x' % b) + ' ')
>  
>  ########################
>  # On Off Server Model
> @@ -479,7 +563,7 @@ class OnOffServer(Model):
>  			print_state(self.state)
>  
>  		rsp_data = struct.pack('<HB', 0x8204, self.state)
> -		send_response(self.path, source, key, rsp_data)
> +		self.send_message(source, key, rsp_data)
>  
>  	def set_publication(self, period):
>  
> @@ -494,11 +578,10 @@ class OnOffServer(Model):
>  
>  		self.timer.start(period/1000, self.publish)
>  
> -
>  	def publish(self):
>  		print('Publish')
>  		data = struct.pack('<HB', 0x8204, self.state)
> -		send_publication(self.path, self.model_id, data)
> +		self.send_publication(data)
>  
>  ########################
>  # On Off Client Model
> @@ -512,25 +595,20 @@ class OnOffClient(Model):
>  				 0x8204 } # status
>  		print('OnOff Client')
>  
> -	def _reply_cb(state):
> -		print('State ', end='');
> -		print(state)
> -
> -	def _send_message(self, dest, key, data, reply_cb):
> -		print('OnOffClient send data')
> -		node.Send(self.path, dest, key, data, reply_handler=reply_cb,
> -				  error_handler=generic_error_cb)
> +	def _send_message(self, dest, key, data):
> +		print('OnOffClient send command')
> +		self.send_message(dest, key, data)
>  
>  	def get_state(self, dest, key):
>  		opcode = 0x8201
>  		data = struct.pack('<H', opcode)
> -		self._send_message(dest, key, data, self._reply_cb)
> +		self._send_message(dest, key, data)
>  
>  	def set_state(self, dest, key, state):
>  		opcode = 0x8202
> -		print('State:', state)
> +		print('Set state:', state)
>  		data = struct.pack('<HB', opcode, state)
> -		self._send_message(dest, key, data, self._reply_cb)
> +		self._send_message(dest, key, data)
>  
>  	def process_message(self, source, key, data):
>  		print('OnOffClient process message len = ', end = '')
> @@ -541,7 +619,7 @@ class OnOffClient(Model):
>  			# The opcode is not recognized by this model
>  			return
>  
> -		opcode, state=struct.unpack('<HB',bytes(data))
> +		opcode, state = struct.unpack('<HB',bytes(data))
>  
>  		if opcode != 0x8204 :
>  			# The opcode is not recognized by this model
> @@ -556,9 +634,54 @@ class OnOffClient(Model):
>  		print(set_green(state_str), set_yellow('from'),
>  						set_green('%04x' % source))
>  
> +########################
> +# Sample Vendor Model
> +########################
> +class SampleVendor(Model):
> +	def __init__(self, model_id):
> +		Model.__init__(self, model_id)
> +		self.vendor = 0x05F1 # Linux Foundation Company ID
> +
>  ########################
>  # Menu functions
>  ########################
> +class MenuItem():
> +	def __init__(self, desc, func):
> +		self.desc = desc
> +		self.func = func
> +
> +class Menu():
> +	def __init__(self, title, menu):
> +		self.title = title
> +		self.menu = menu
> +
> +	def show(self):
> +		print(set_cyan('*** ' + self.title.upper() + ' ***'))
> +		for k, v in self.menu.items():
> +			print(set_green(k), set_cyan(v.desc))
> +
> +	def process_cmd(self, str_value):
> +		if is_error():
> +			self.show()
> +			clear_error()
> +			return
> +
> +		cmds = []
> +		for key in self.menu.keys():
> +			if key.startswith(str_value):
> +				cmds.append(key)
> +
> +		if len(cmds) == 0:
> +			print(set_error('Unknown menu option: '), str_value)
> +			self.show()
> +			return
> +		if len(cmds) > 1:
> +			for cmd in cmds:
> +			     print(set_cyan(cmd + '?'))
> +			return
> +
> +		self.menu.get(cmds[0]).func()
> +
>  class MenuHandler(object):
>  	def __init__(self, callback):
>  		self.cb = callback
> @@ -579,221 +702,184 @@ class MenuHandler(object):
>  		return True
>  
>  def process_input(input_str):
> -	if menu_level == 0:
> -		process_main_menu(input_str)
> -	elif menu_level == 1:
> -		process_client_menu(input_str)
> -	else:
> -		print(set_error('BUG: bad menu level'))
> +	str_value = input_str.strip()
> +
> +	# Allow entering empty lines for better output visibility
> +	if len(str_value) == 0:
> +		return
> +
> +	current_menu.process_cmd(str_value)
>  
>  def switch_menu(level):
> -	global menu_level
> +	global current_menu
>  
> -	if level > 1:
> +	if level >= len(menus):
>  		return
>  
> -	if level == 0:
> -		main_menu()
> -	elif level == 1:
> -		client_menu()
> -
> -	menu_level = level
> +	current_menu = menus[level]
> +	current_menu.show()
>  
>  ########################
> -# Main menu functions
> +# Main menu class
>  ########################
> -def process_main_menu(input_str):
> -	global token
> -	global user_input
> -	global have_token
> +class MainMenu(Menu):
> +	def __init__(self):
> +		menu_items = {
> +			'token': MenuItem(' - set node ID (token)',
> +						self.__cmd_set_token),
> +			'join': MenuItem(' - join mesh network',
> +						self.__cmd_join),
> +			'attach': MenuItem(' - attach mesh node',
> +						self.__cmd_attach),
> +			'remove': MenuItem(' - delete node',
> +						self.__cmd_remove),
> +			'dest': MenuItem(' - set destination address',
> +						self.__cmd_set_dest),
> +			'app-index': MenuItem(' - set AppKey index',
> +						self.__cmd_set_app_idx),
> +			'client-menu': MenuItem(' - On/Off client menu',
> +						self.__cmd_client_menu),
> +			'quit': MenuItem(' - exit the test', app_exit)
> +		}
>  
> -	str = input_str.strip()
> +		Menu.__init__(self, 'Main Menu', menu_items)
>  
> -	if user_input == 1:
> -		res = set_token(str)
> -		user_input = 0
> +	def __cmd_client_menu(self):
> +		if attached != True:
> +			print(set_error('Disallowed: node is not attached'))
> +			return
> +		switch_menu(ON_OFF_CLIENT_MENU)
>  
> -		if res == False:
> -			main_menu()
> +	def __cmd_set_token(self):
> +		global user_input
>  
> -		return
> +		if have_token == True:
> +			print('Token already set')
> +			return
>  
> -	# Allow entering empty lines for better output visibility
> -	if len(str) == 0:
> -		return
> +		user_input = INPUT_TOKEN
> +		print(set_cyan('Enter 16-digit hex node ID:'))
>  
> -	if str.isdigit() == False:
> -		main_menu()
> -		return
> +	def __cmd_set_dest(self):
> +		global user_input
>  
> -	opt = int(str)
> +		user_input = INPUT_DEST_ADDRESS
> +		print(set_cyan('Enter 4-digit hex destination address:'))
>  
> -	if opt > 6:
> -		print(set_error('Unknown menu option: '), opt)
> -		main_menu()
> -	elif opt == 1:
> -		if have_token:
> -			print('Token already set')
> -			return
> +	def __cmd_set_app_idx(self):
> +		global user_input
>  
> -		user_input = 1;
> -		print(set_cyan('Enter 16-digit hex node ID:'))
> -	elif opt == 2:
> +		user_input = INPUT_APP_KEY_INDEX;
> +		print(set_cyan('Enter app key index (up to 3 digit hex):'))
> +
> +	def __cmd_join(self):
>  		if agent == None:
>  			print(set_error('Provisioning agent not found'))
>  			return
>  
> -		join_mesh()
> -	elif opt == 3:
> +		uuid = bytearray.fromhex("0a0102030405060708090A0B0C0D0E0F")
> +		random.shuffle(uuid)
> +		uuid_str = array_to_string(uuid)
> +		caps = ["out-numeric"]
> +		oob = ["other"]
> +
> +		print(set_yellow('Joining with UUID ') + set_green(uuid_str))
> +		mesh_net.Join(app.get_path(), uuid,
> +			reply_handler=join_cb,
> +			error_handler=join_error_cb)
> +
> +	def __cmd_attach(self):
>  		if have_token == False:
>  			print(set_error('Token is not set'))
> -			main_menu()
> +			self.show()
>  			return
>  
>  		attach(token)
> -	elif opt == 4:
> +
> +	def __cmd_remove(self):
>  		if have_token == False:
>  			print(set_error('Token is not set'))
> -			main_menu()
> +			self.show()
>  			return
>  
> -		print('Remove mesh node')
> -		mesh_net.Leave(token, reply_handler=generic_reply_cb,
> +		print('Removing mesh node')
> +		mesh_net.Leave(token, reply_handler=remove_node_cb,
>  					error_handler=generic_error_cb)
> -		have_token = False
> -	elif opt == 5:
> -		switch_menu(1)
> -	elif opt == 6:
> -		app_exit()
> -
> -
> -def main_menu():
> -	print(set_cyan('*** MAIN MENU ***'))
> -	print(set_cyan('1 - set node ID (token)'))
> -	print(set_cyan('2 - join mesh network'))
> -	print(set_cyan('3 - attach mesh node'))
> -	print(set_cyan('4 - remove node'))
> -	print(set_cyan('5 - client menu'))
> -	print(set_cyan('6 - exit'))
>  
> -def set_token(str):
> -	global token
> -	global have_token
> -
> -	if len(str) != 16:
> -		print(set_error('Expected 16 digits'))
> -		return False
> -
> -	try:
> -		input_number = int(str, 16)
> -	except ValueError:
> -		print(set_error('Not a valid hexadecimal number'))
> -		return False
> -
> -	token = numpy.uint64(input_number)
> -	have_token = True
> -
> -	return True
> -
> -def join_mesh():
> -	uuid = bytearray.fromhex("0a0102030405060708090A0B0C0D0E0F")
> -
> -	caps = ["out-numeric"]
> -	oob = ["other"]
> -
> -	random.shuffle(uuid)
> -	uuid_str = array_to_string(uuid)
> -	print('Joining with UUID ' + set_green(uuid_str))
> +	def process_cmd(self, str_value):
> +		global user_input
> +		global dst_addr
> +		global app_idx
> +
> +		if user_input == INPUT_TOKEN:
> +			set_token(str_value)
> +		elif user_input == INPUT_DEST_ADDRESS:
> +			res = set_value(str_value, 4, 4)
> +			if is_error() != True:
> +				dst_addr = res
> +				print(set_yellow("Destination address: ") +
> +					set_green(format(dst_addr, '04x')))
> +		elif user_input == INPUT_APP_KEY_INDEX:
> +			res = set_value(str_value, 1, 3)
> +			if is_error() != True:
> +				app_idx = res
> +				print(set_yellow("Application index: ") +
> +					set_green(format(app_idx, '03x')))
> +
> +		if user_input != INPUT_NONE:
> +			user_input = INPUT_NONE
> +			if is_error() != True:
> +				return
>  
> -	mesh_net.Join(app.get_path(), uuid,
> -			reply_handler=join_cb,
> -			error_handler=join_error_cb)
> +		Menu.process_cmd(self, str_value)
>  
>  ##############################
> -# On/Off Client menu functions
> +# On/Off Client menu class
>  ##############################
> -def process_client_menu(input_str):
> -	global user_input
> -	global dst_addr
> -	global app_idx
> -
> -	res = -1
> -	str = input_str.strip()
> -
> -	if user_input == 1:
> -		res = set_value(str)
> -		if res != -1:
> -			dst_addr = res
> -	elif user_input == 2:
> -		res = set_value(str)
> -		if res != -1:
> -			app_idx = res
> -
> -	if user_input != 0:
> -		user_input = 0
> -		if res == -1:
> -			client_menu()
> -		return
> -
> -	# Allow entering empty lines for better output visibility
> -	if len(str) == 0:
> -		return
> +class ClientMenu(Menu):
> +	def __init__(self):
> +		menu_items = {
> +			'get-state': MenuItem(' - get server state',
> +						self.__cmd_get_state),
> +			'off': MenuItem(' - set state OFF',
> +						self.__cmd_set_state_off),
> +			'on': MenuItem(' - set state ON',
> +						self.__cmd_set_state_on),
> +			'back': MenuItem(' - back to main menu',
> +						self.__cmd_main_menu),
> +			'quit': MenuItem(' - exit the test', app_exit)
> +		}
>  
> -	if str.isdigit() == False:
> -		client_menu()
> -		return
> +		Menu.__init__(self, 'On/Off Clien Menu', menu_items)
>  
> -	opt = int(str)
> +	def __cmd_main_menu(self):
> +		switch_menu(MAIN_MENU)
>  
> -	if opt > 7:
> -		print(set_error('Unknown menu option: '), opt)
> -		client_menu()
> -		return
> +	def __cmd_get_state(self):
> +		app.elements[1].models[0].get_state(dst_addr, app_idx)
>  
> -	if opt >= 3 and opt <= 5 and dst_addr == 0x0000:
> -		print(set_error('Destination address not set!'))
> -		return
> +	def __cmd_set_state_off(self):
> +		app.elements[1].models[0].set_state(dst_addr, app_idx, 0)
>  
> -	if opt == 1:
> -		user_input = 1;
> -		print(set_cyan('Enter 4-digit hex destination address:'))
> -	elif opt == 2:
> -		user_input = 2;
> -		app.elements[1].models[0].print_bindings()
> -		print(set_cyan('Choose application key index:'))
> -	elif opt == 3:
> -		app.elements[1].models[0].get_state(dst_addr, app_idx)
> -	elif opt == 4 or opt == 5:
> -		app.elements[1].models[0].set_state(dst_addr, app_idx, opt - 4)
> -	elif opt == 6:
> -		switch_menu(0)
> -	elif opt == 7:
> -		app_exit()
> +	def __cmd_set_state_on(self):
> +		app.elements[1].models[0].set_state(dst_addr, app_idx, 1)
>  
> -def client_menu():
> -	print(set_cyan('*** ON/OFF CLIENT MENU ***'))
> -	print(set_cyan('1 - set destination address'))
> -	print(set_cyan('2 - set application key index'))
> -	print(set_cyan('3 - get state'))
> -	print(set_cyan('4 - set state OFF'))
> -	print(set_cyan('5 - set state ON'))
> -	print(set_cyan('6 - back to main menu'))
> -	print(set_cyan('7 - exit'))
>  
> -def set_value(str):
> +def set_value(str_value, min, max):
>  
> -	if len(str) != 4:
> -		print(set_error('Expected 4 digits'))
> +	if len(str_value) > max or len(str_value) < min:
> +		raise_error('Bad input length %d' % len(str_value))
>  		return -1
>  
>  	try:
> -		value = int(str, 16)
> +		value = int(str_value, 16)
>  	except ValueError:
> -		print(set_error('Not a valid hexadecimal number'))
> +		raise_error('Not a valid hexadecimal number')
>  		return -1
>  
>  	return value
>  
> +
>  ########################
>  # Main entry
>  ########################
> @@ -806,6 +892,8 @@ def main():
>  	global mainloop
>  	global app
>  	global mesh_net
> +	global menu
> +	global current_menu
>  
>  	if len(sys.argv) > 1 :
>  		set_token(sys.argv[1])
> @@ -827,14 +915,21 @@ def main():
>  	print(set_yellow('Register OnOff Server model on element 0'))
>  	first_ele.add_model(OnOffServer(0x1000))
>  
> +	print(set_yellow('Register Vendor model on element 0'))
> +	first_ele.add_model(SampleVendor(0x0001))
> +
>  	print(set_yellow('Register OnOff Client model on element 1'))
>  	second_ele.add_model(OnOffClient(0x1001))
> +
>  	app.add_element(first_ele)
>  	app.add_element(second_ele)
>  
>  	mainloop = GLib.MainLoop()
>  
> -	main_menu()
> +	menus.append(MainMenu())
> +	menus.append(ClientMenu())
> +	switch_menu(MAIN_MENU)
> +
>  	event_catcher = MenuHandler(process_input);
>  	mainloop.run()
>  

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2019-04-19 18:32 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-04-18  6:31 [PATCH BlueZ 1/2] test: Drive test-mesh with a string-based menu Inga Stotland
2019-04-18  6:31 ` [PATCH BlueZ 2/2] test: Enable test-mesh to send raw vendor commands Inga Stotland
2019-04-19 16:10 ` [PATCH BlueZ 1/2] test: Drive test-mesh with a string-based menu Gix, Brian

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.