Caltech MVWT API Written by Jon Gibbs ([email protected]) and David Tung ([email protected]) Introduction This API is intended for programmers to control robots on the Caltech MVWT testbed. It is derived from the code written by Steve Waydo, Bob Christie, and by the Cornell Roboflag team. It contains documentation, an example program, and all necessary header files to create a new program to interact with the testbed. The header files include a point to point controller for the Steelebots. There are also troubleshooting notes in this document. Section 1: Motivation Section 2: What your program needs to have Section 3: Send/Receive Data Structures Section 4: Communication Class and Functions Section 5: Control Functions Section 6: Program Flow: The Example Program Section 7: MVServer Section 1: Motivation: The Caltech Multi-Vehicle Wireless Testbed is an experimental platform for validating theoretical advances in multiple-vehicle coordination and cooperation, real-time networked control system, and distributed computation [1]. Before this API existed, the testbed was only equipped to run the Cornell RoboFlag game. Programmers who wanted to write custom code had to ether write onboard controllers for each robot or go through the RoboFlag architecture: Custom Code Team 1 Network GUI Master GUI Arbiter MVServer Team 2 Network Custom Code GUI Vision System Hardware Test bed Writing code not intended for the RoboFlag game through the RoboFlag architecture is inefficient and problematic. The biggest problem is that sometimes the arbiter may override custom code because it enforces the rules of the RoboFlag game. Writing onboard controllers is extremely tedious and some of the robots have limited computation power. This API is intended to address these issues and meets the following objectives: 1) Create interface for non-RoboFlag programmers to control multiple robots without writing onboard code. 2) Provide ability to control different types of Robots simultaneously. 3) Provide basic point to point control. The API has a new architecture that is easier to use for non-RoboFlag projects: Custom Code API Functions MVServer Hardware Test bed Vision System Section 2: What your program needs to have: This API is intended for users who want to write Windows programs to control robots in the testbed. There are two libraries and one header file that one needs in order to be able to communicate with MVServer: Ws2_32.lib and roboflagR.lib. Ws2_32.lib can be included in a VisualC++ 6 workspace by following these steps (Note: The library can be obtained from Microsoft’s online MSDN library): 1. Right click on the project and then select settings: 2. Select the “Link” tab and add Ws2_32.lib to the library modules field under then click OK. Note: Ws2_32.lib is a library for basic windows socket programming. This API uses an object defined in this library for communication with MVServer. The second library needed is roboflagR.lib. It should be placed in your project folder. Add it by creating a new folder in your workspace. Right click that folder and select “Add Files to Folder”. Select roboflagR.lib and you should be set. The header file “SendRecieve.h” included in this API must also be added. You should add it to the “Header Files” folder within your project following the same steps you used to add the roboflagR library. Section 3: Send/Receive Data Structures Currently there are data structures that your program must use in order to communicate with MVServer. Note: If you wish to change these structures, see Section 7. The send structure: struct ObjectVelocity { float xVel, yVel, rotVel; }; struct EntityCommands:ObjectVelocity { int kick, dribble; }; struct ObjectCommands { EntityCommands robots[NUM_OF_TEAMS*NUM_OF_ROBOTS], obstacles[NUM_OF_OBSTACLES]; }; ObjectCommands is the data structure your program will send to MVServer which will send it to the robots. The constants NUM_OF_TEAMS, NUM_OF_ROBOTS, and NUM_OF_OBSTACLES are defined inside of the SendRecieve.h file and the time of writing were consistent with the numbers defined inside of the MVServer program. If the numbers are not consistent, MVServer will print an error message similar to ERROR: packets dropped, size = 40. Troubleshooting: If the robots are not responding and MVServer displays an error message similar to: ERROR: packets dropped, size = 40, then the constants NUM_OF_TEAMS, NUM_OF_ROBOTS, and NUM_OF_OBSTACLES are not consistent with what is defined inside the MVServer program. In order for commands to get to the robots, the constants must be the same across MVServer and in SendReceive.h, even if part of the array is not used. The receive data structure: struct LocationCommon { float xPos, yPos, rotation; }; struct MovingObject:LocationCommon { bool isFound, isRotationFound; }; struct RawVision { MovingObject robots[NUM_OF_TEAMS*NUM_OF_ROBOTS], balls[NUM_OF_BALLS], obstacles[NUM_OF_OBSTACLES]; float time; }; RawVision is the structure that your program will receive from MVServer. It contains position information members (xPos, yPos, rotation), for each robot in the robots array. Section 4: Communication Class and Functions 1) Class: Client The client class is used to create a connection with the MVServer and communicate with it. Class Constructor: Client(CommType OurclientType, const char* hostname, int port); Arguments: CommType OurclientType: set this to SEND_RECEIVE to both send and receive data const char* hostname: set this to the IP address of the MVServer int port: set this to the MVServer port number Example: int MVserverPort = 4545; char MVserverIP[16] = "192.168.1.231"; Client * client = new Client(SEND_RECEIVE, MVserverIP, MVserverPort); 2) Function: openConnection The openConnection function is a member of the Client class and it opens the connection with the MVServer. Function Prototype: bool openConnection( ); Return Value: True is connection successfully established, false is could not connect to the MVServer. Example: if(!client->openConnection()) { cerr << "ERROR: Could not connect to the Server" << endl; return 1; } 3) Function: receive The receive function is a member of the Client class and it receives initial data from the MVServer. Function prototype: bool receive( void* message, int size ); Arguments: void* message: the data received from MVServer int size: the size of the data received from MVServer Returns Value: True if it successfully received data from MVServer, false if it did not successfully receive data from MVServer. Example: if(!client->receive(&visionData, sizeof(visionData))) { cerr << "ERROR: Could not receive initial position data from the Server" << endl; return 1; } 4) Function: receiveLatest The receive function is a member of the Client class and it receives data from the MVServer. Function prototype: bool receiveLatest(void *msg, int msgsize); Arguments: void* msg: the data received from MVServer int msgsize: the size of the data received from MVServer Returns Value: True if it successfully received data from MVServer, false if it did not successfully receive data from MVServer. Example: if(!client->receiveLatest(&visionData, sizeof(visionData))) { cerr << "ERROR: Could not receive data from the Server" << endl; return 1; } 5) Function: send The send function is a member of the Client class and it sends commands to the MVServer. Function Prototype: bool send( void* message, int size ); Arguments: void* message: the data sent to the MVServer int size: the size of the data sent to the MVServer Return Value: True if it successfully sent data to MVServer, false if it did not successfully send data to MVServer. Example: client->send(&objectCommands, sizeof(objectCommands)); Section 5: Control Functions These functions provide direct velocity control and point to point control. They have been tested to work smoothly and accurately while RFController is running on the Steelebots. Velocity Control 1) Function: setVelocityCommand Function Prototype: ObjectCommands setVelocityCommand(float xVel, float yVel, int robotNum, ObjectCommands myObjectCommand); This function allows the programmer to set the x-velocity and y-velocity of a robot. Arguments: float xVel: x-velocity command float yVel: y-velocity command int robotNum: Robot number ObjectCommands myObjectCommand: ObjectCommands structure that is updated Return Value: ObjectCommands structure that was passed to it as an argument with updated x-velocity and y-velocity commands for the specified robot. Example: ObjectCommands objectCommands; objectCommands = setVelocityCommand(xVelCmd[i], yVelCmd[i], i, objectCommands); Point to Point Control Two functions made available to users to control robots from point to point: • getErrors: obtain errors between current position and target position • P2PvelocityCommand: given these errors, obtain velocity commands to achieve target position 1) Function: getErrors This function provides x-position and y-position error between robot’s current position and target position Function Prototype: LocationCommon getErrors(RawVision visionInfo, float x_target, float y_target, int robotNum); Arguments: RawVision visionInfo: RawVision structure containing position of robots float x_target: Target x-position float y_target: Target y-position int robotNum: Robot number Return Value: LocationCommon structure with x-position error and y-position error for specified robot. Example: LocationCommon Errors = getErrors(visionData, x_ref, y_ref, i); 2) Function: P2PvelocityCommand Function Prototype: ObjectCommands P2PvelocityCommand(LocationCommon CurrentDeltas, int robotNum, ObjectCommands myObjectCommand); This function provides velocity commands to achieve a target position. It is a proportional controller that sets x-velocity and y-velocity commands proportional to xposition error and y-position error. Arguments: LocationCommon CurrentDeltas: LocationCommons structure with position errors obtained in getErrors function int robotNum: Robot number ObjectCommands myObjectCommand: ObjectCommands structure Return Value: ObjectCommands structure that was passed to it as an argument with updated x-velocity and y-velocity commands for the specified robot to make it compensate for position error. Example: objectCommands = P2PvelocityCommand(Errors, i, objectCommands); Section 6: Program Flow: The Example Program This section is devoted to program flow. There are a couple constraints on the order that certain functions should be called and how often they are called. The basic order of the example program is as follows: 1) Create the object of the client class: Client * client = new Client(SEND_RECEIVE, MVserverIP, MVserverPort); 2) Open connection with MVServer: if(!client->openConnection()) 3) Declare RawVision and ObjectCommands structures then receive initial position data from MVServer. ObjectCommands objectCommands; RawVision visionData; // receive initial positions for objects if(!client->receive(&visionData, sizeof(visionData))) 4) Begin the while loop. First the position data from the server is received. The ObjectCommands structure is updated for each vehicle and is then sent back to MVServer. Note: It is important that no keyboard inputs are taken at every iteration inside this loop because it needs to run at a high frequency. if(!client->receiveLatest(&visionData, sizeof(visionData))) Note: The difference is that now you must call the receivelatest function instead of the receive function that was used to get initial information client->send(&objectCommands, sizeof(objectCommands)); After the while loop finishes, the connection should be closed. Section 7: MVServer The MVServer must run on Linux. Below is the documentation for the init_arbiter_connection() function in the MVServer code, which opens a connection with your Windows program (referred to as arbiter in MVServer). Function: init_arbiter_connection() 1) To create the socket for the arbiter connection socket(AF_INET, SOCK_STREAM, 0) is called. If it returns -1 there is an error. 2) Set the Arbiter listener for reuse by calling the function setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)). If this function returns -1, there is an error. 3) Sets up the socket address that we bind the socket to. We specify the type of socket, the IP used, and the port used. AF_INET is a type of socket. The port is a parameter. htons and htonl are library functions. struct sockaddr_in sa; memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(port); sa.sin_addr.s_addr = htonl(INADDR_ANY); 4) The bind function attempts associate that socket with a port on the local machine. bind(listen_fd, (struct sockaddr *)&sa, sizeof(sa)) 5) We listen for a connection and take only the first one specified by the second parameter. listen_fd is the integer file descriptor returned by the socket function earlier in the code. listen(listen_fd, 1) 6) FD_ZERO clears a set of file descriptors. FD_SET will add a given descriptor to the set of file descriptors. 7) We finally wait for the arbiter to connect. It uses a timeout to wait for the connection select(MAX(listen_fd, STDIN_FILENO) + 1, &readfds, NULL, NULL, NULL) If this function returns -1, there is an error. 8) FD_ISSET tests to see if a descriptor is part of the set. We don’t know what the significance of putting a file descriptor in the set is. If the user presses any key, the select function will time out and we think that STDIN_FILENO becomes mysteriously part of the set. We think that STDIN_FILENO becomes a part of the set if there is any keyboard input in the console. If there is any keyboard input the read function empties up to DATA_BUFFSIZE bytes of the stdin buffer. read(STDIN_FILENO, &data_buffer, DATA_BUFFSIZE) 9) Finally the accept function accepts a connection on the socket. sock_fd = accept(listen_fd, (struct sockaddr *)&arbiter_addr, &aa_size)). If sock_fd equals -1, then there is an error. 10) The variable delay is a parameter that decides whether or not to use Nagle’s algorithm. Nagle’s algorithm accumulates small packets into one large for better efficiency in a congested network. 11) The function setsockopt is manipulating the Nagle’s algorithm option. In this case, it is disabling Nagle’s algorithm. setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (int *)&dflag, sizeof(int)) 12) Last we must do the arbiter handshake. For the handshake protocol, it receives a message from the arbiter and then sends a message in return. After this handshake then the socket variable arbiter_sock is set to sock_fd and the function is finished. Note: In order to change the Send/Receive data structures, the user must modify datatypes.h and compile it with MVServer. You must then put that same data structure in “SendReceive.h” and then compile your new program. Make sure that the size of the arrays in both header files is the same too. Reference: [1] Z. Jin, S. Waydo, E. Wildanger, M. Lammers, H. Scholze, P. Foley, D. Held, and R. Murray. MVWT-II: The Second Generation Caltech Multi-Vehicle Wireless
© Copyright 2026 Paperzz