{{{#!comment [[Include(wiki:802.11/beta-note)]] }}} [[TracNav(802.11/TOC)]] = Extending the Experiments Framework = The 802.11 Reference Design Experiments Framework provides a variety of commands to control and observe WARP v3 nodes in real time. However many experiments will require additional commands, especially when users extend the reference code with new behaviors. Starting in 802.11 Reference Design v1.4 we have greatly simplified the process of implementing extensions to the experiments framework. There are two kinds of framework extensions: * '''User Commands''': commands interact with the MAC High Framework and the upper-level MAC running in CPU High * '''Low Params''': a write-only path for setting parameters in the MAC Low Framework and the lower-level MAC running in CPU Low == User Commands == The User Command flow provides a simple way to send custom commands and data from your Python script to the WARP v3 nodes in your experimental network. This flow presents a simple pipe for exchanging arbitrary commands and responses between a Python script and the upper-level MAC in a WARP node running the 802.11 Reference Design. === Python === In Python user commands are constructed as: * Command ID: an arbitrary 24-bit integer * Command arguments: an optional list of {{{u32}}} arguments supplied by the Python code that is passed to the C code handler The command ID is used by the C code to select which code block should process the command payload. The Python and C code must use matching lists of command IDs. All user command IDs must be 24-bit integers (i.e. in ~[0, 2^24-1]). The framework does not reserve any command ID values. The command arguments are an optional payload supplied by the Python script. The wlan_exp framework sends this payload to the node with no modifications. The arguments must be constructed as a list of unsigned 32-bit integers ({{{u32}}} values). The Python and C code must implement complementary argument construction/parsing functions. The argument list may be omitted if a command requires no payload. The WARP node must respond to every user command. The reference C code implements this response by default. Each command response may contain a payload supplied by the user C code. If the command handler supplies a response payload, this payload is conveyed back from the node to the Python code automatically. When present the response payload is always a list of {{{u32}}} values. Your Python and C code must implement complementary response payload generation/processing functions. User commands are passed to the wlan_exp framework using the {{{send_user_command(cmd_id, args)}}} method. For example: {{{#!python #No command arguments, no response payload my_node.send_user_command(CMDID_USR_MYCMD, args=None) #3 arguments, no response payload my_node.send_user_command(CMDID_USR_MYCMD, args=[ARG0 ARG1 ARG2]) #2 arguments, with response payload cmd_resp = my_node.send_user_command(CMDID_USR_MYCMD, args=[ARG0 ARG1]) }}} For simple commands your Python script can call {{{send_user_command}}} directly. For more complicated commands it is suggested you implement your custom command functionality in a separate method. This will simplify debugging your code and reusing your custom commands across multiple experiments. The sample code below adopts the approach of a custom method for sending a user command. === MAC C Code === The 802.11 MAC code running on the WARP node handles user commands at two levels. If the command behavior is common to all MAC applications, the command is handled in the {{{process_user_cmd()}}} function in {{{wlan_exp_user.c}}}. You should modify {{{process_user_cmd()}}} to handle any new framework-level commands required for your application. Any command IDs not hanlded in {{{process_user_cmd()}}} will be passed through to the MAC application. If a command behavior depends on the upper-level MAC application, the command is handled in the {{{wlan_exp_process_user_cmd()}}} callback function in the top-level MAC. You should modify the {{{wlan_exp_process_user_cmd()}}} function in the {{{wlan_mac_ap.c}}}, {{{wlan_mac_sta.c}}} and {{{wlan_mac_ibss.c}}} files to implement any MAC-specific command behaviors. A user command consists of the following elements: === Example Command: Reading Tx Queue Status === {{{my_node}}} is an instance of a wlan_exp node object that has already been initialized. {{{#!python #Define a command ID (must be integer in [0, 65535]) CMDID_USER_TX_QUEUE_STATUS = 100 #Define a user command ID USER_CMDID_TX_QUEUE_STATUS = 100 #Define method for sending command and processing the response def get_tx_queue_status(node, queue_id=None): if(queue_id is not None): #Arguments must be a list of ints - listify/cast the scaler queue_id args = [int(queue_id)] else: args = None resp = node.send_user_command(USER_CMDID_TX_QUEUE_STATUS, args) #Response should be list of ints with 2 values per queue # Each pair of response values is (queue_id, queue_occupancy) # Sanity check the response, then convert to a list of 2-tuples and return if(len(resp) % 2 != 0): print('ERROR: Tx Queue Status command response lenth ({0} values) was not even!'.format(len(resp))) return else: #Group each pair of response values into 2-tuple and return list of 2-tuples ret = zip(*[iter(resp)]*2) return ret }}} This method takes one node object as its argument, sends the {{{USER_CMDID_TX_QUEUE_STATUS}}} command to the node, then parses the response. If the response is valid it returns the list of queue id/occupancy values. To use this function: {{{ #Assume n0 is a wlan_exp node object that has already been initialized q_stat = get_tx_queue_status(n0) #Print the queue status response if(q_stat): print('Tx Queue Status for node {0}'.format(n0.sn_str)) print('ID Occupancy') for q_id,q_occ in q_stat: print('{0:2d} {1:3d}'.format(q_id, q_occ)) else: print('No Tx Queue Status received for {0}'.format(n0_str)) }}} Each upper-level MAC application implements its own callback function for handling application-specific user commands. By default these functions are empty. You must modify the C code to implement the behaviors required for your custom command. In this example the custom command will query the lengths of Tx queues. Modify the {{{wlan_exp_user_ap_process_cmd()}}} function in {{{wlan_mac_ap.c}}}. {{{#!c #define USER_CMDID_TX_QUEUE_STATUS 100 int i; u32 req_queue_id; u32 q_id, q_occ; dl_entry* curr_station_info_entry; station_info* curr_station_info; }}} Add a new case for the new command ID to the {{{switch(cmd_id)}}} statement: {{{#!c case USER_CMDID_TX_QUEUE_STATUS: xil_printf("Got Tx queue status cmd\n"); if(command->header->num_args > 0) { req_queue_id = Xil_Ntohl(cmd_args_32[0]); xil_printf("Queue ID %d requested\n", req_queue_id); resp_args_32[resp_index++] = Xil_Htonl(req_queue_id); resp_args_32[resp_index++] = Xil_Htonl(queue_num_queued(req_queue_id)); } else { xil_printf("No queue ID requested - returning status of all queues\n"); //Gather status of all Tx queues: // Multicast queue (MCAST_QID) // Managment queue (MANAGEMENT_QID) // Queue per AID resp_args_32[resp_index++] = Xil_Htonl(MCAST_QID); resp_args_32[resp_index++] = Xil_Htonl(queue_num_queued(MCAST_QID)); resp_args_32[resp_index++] = Xil_Htonl(MANAGEMENT_QID); resp_args_32[resp_index++] = Xil_Htonl(queue_num_queued(MANAGEMENT_QID)); if(my_bss_info->associated_stations.length > 0) { curr_station_info_entry = NULL; //Iterating over the dl_list is potentially dangerous, as the list itself might change // if this function is interrupted. We protect against this by iterating over (at most) // the number of entries in the list at the time this loop starts. The iteration variable i // is *not* used for indexing the list - we still traverse entries with entry.next for(i=0; iassociated_stations.length; i++) { if(curr_station_info_entry == NULL) { //First iteration - get the head of the dl_list curr_station_info_entry = my_bss_info->associated_stations.first; } else { //Subsequent iteration - get the next entry in the dl_list curr_station_info_entry = dl_entry_next(curr_station_info_entry); } //Get the station info pointer from the list entry curr_station_info = (station_info*)(curr_station_info_entry->data); //Get the queue ID and queue occupancy q_id = AID_TO_QID(curr_station_info->AID); q_occ = queue_num_queued(q_id); xil_printf("Q: %2d %3d\n", q_id, q_occ); //Add the queue info to the response payload resp_args_32[resp_index++] = Xil_Htonl(q_id); resp_args_32[resp_index++] = Xil_Htonl(q_occ); //Break out of the for loop on the last station info entry or // if our response is already reached the max size allowed by the framework if( (curr_station_info_entry == my_bss_info->associated_stations.last) || (dl_entry_next(curr_station_info_entry) == NULL) || (resp_index >= max_resp_len) ) { break; } } } } //All done - resp_args_32 now contains queue status, res_index is length of response arguments array // Copy these values into the response header struct resp_hdr->length += (resp_index * sizeof(resp_args_32[0])); resp_hdr->num_args = resp_index; break; }}} == Low Params == The User Command flow described above is can configure and reading state in CPU High. Some applications will also require interacting with CPU Low.