What would you do if you suddently woke up in an unknown place? I guess you would look around and then figure out where you are by exploring the place you are in. If there are different rooms, you would go into each one of them an see what they contain, right? Taking this same approach, Suka wakes up in the arena, and starts scanning around itself. Then it determines where it has the most chance to scan a big proportion of the arena and goes on until all of it has been discovered.
Path | Description |
---|---|
client/robotclient.c | This is the file containing the main functions |
client/constants.m | File defining a number of useful global variables used by many functions |
client/classes.c | Here we define a few fundamental structures helpful to handle the positioning of the robot and the mapping in general |
client/makefile | Builds both the robot client and the test files we made during the project |
client/gsyst.c | Defines functions for the interactions with the motors and sensors |
client/servercom.c | Functions for the communication with the server |
client/map.c | Map related functions for scanning the arena |
client/spot.c | Determines in which spot to be in order to scan optimaly |
client/path.c | The pathfinding functions helping the robot to move around the arena |
Our makefile detects the system architecture and compiles accordingly.
This helps if you use docker to compile your program, which you should
because the robot is very slow at compiling. The makefile will know
wether it's executing in docker/your computer or the robot using
the uname -m
bash command.
The order in which we present the structure of the makefile is meant to help
understanding, but is different from the actual file.
ARCH=$(shell uname -m)
ifeq ($(ARCH),x86_64) #this means we are on the docker platform
CC=arm-linux-gnueabi-gcc
CFLAGS=-W -Wall -ansi -pedantic
LDFLAGS=-I/src/ev3dev-c/source/ev3/ -lm /src/ev3dev-c/lib/libev3dev-c.a
BT= #we didn't manage to make the bluetooth work when compiling from docker.
endif
ifeq ($(ARCH),armv5tejl) #this means we are compiling on the brick
CC=gcc
CFLAGS=-W -Wall -ansi -pedantic
LDFLAGS=
BT=-lbluetooth #we didn't manage to make the bluetooth work when compiling from docker.
endif
%.o: %.c
is triggered whenever a .o file is requested,
and depends on the homonymous .c file. This is meant to pre-compile our modules.
%.o: %.c constants.m
$(CC) -o $@ -c $< $(LDFLAGS)
The compililation of the main executable is then pretty straight forward :
EXEC=robotclient
all: $(EXEC)
$(EXEC): $(EXEC).o gsyst.o path.o spot.o servercom.o map.o
$(CC) $^ $(LDFLAGS) -o $(EXEC)
$(EXEC).o: robotclient.c gsyst.h constants.m path.h spot.h servercom.h map.h
$(CC) -o $@ -c $< $(LDFLAGS)
During the project, we kept a number of test files for each module in order to test out their functions.
module_name: module_name_test.o module1.o module2.o module3.o module4.o
$(CC) $^ $(LDFLAGS) -o spot
module_name_test.o: module_name_test.c module1.h module2.h module3.h module4.h constants.m
$(CC) -o $@ -c $< $(LDFLAGS)
Finally, a cleaning command is made to delete the binary files (useful to do before a git add .
to avoid including them in your repo)
.PHONY: clean mrproper #tells that clean and mrproper are not filenames
clean:
rm -rf *.o
mrproper: clean
rm -rf $(EXEC)
rm -rf module_name
Upon receiving the start signal, the robot starts by creating a thread that will send the position to the
server every 2 second through the use of pthread_create(&positioning, NULL, thSendPosition, NULL)
and a second thread to receive any secial instructions sent from the server : pthread_create(&receiving, NULL, thReceiveFromServer, NULL)
Then, the initial map is generated! char* map = get_new_local_map(width, height);
The map's width and height is given as arguments when launching the program.
Each character represents a tile -- or pixel -- for wether it is a
free tile, a wall or a yet unknown space in the arena.
/* Code for the rest of the program on the large arena */
// position the robot at one extremity of the map
start_position = tCoord_new(width/2, height - 300*MM_TO_PIX_SIZE); //This automaticaly creates a virtual wall behind the robot
// scan a first time to get the surroundings :
scan(robotPosition, start_position, width, height, map);
tCoord previousSpots[4*width*height/(DIST_MIN_FROM_WALLS*DIST_MIN_FROM_WALLS)]; //A list of all the spots ou robot is going to go
size_t size_previousSpots = 0;
while(!mapComplete(map))
{ // determine a new spot in order to maximise the new area to explore
tCoord spot = getNewSpot(Point_to_tCoord(fPoint_to_Point(robotPosition), start_position),
width,
height,
map);
previousSpots[size_previousSpots++] = spot;
if(getPathTo(spot)) // writes into a file the checkpoints we have to go by in order to avoid obstacles
{
printf("failed to find the path to the new coordinates");
exit(EXIT_FAILURE);
}
FILE *path = fopen(PATH_PATH, "r"); //PATH_PATH is the path to the fie containing the checkpoints
int pathLen = countlines(path);
fclose(path);
int i;
for(i=0; i<pathLen; i++)
{
tCoord checkPoint = getCheckpoint(i, pathLen); //pops the next node from the file.
if(moveTo(checkPoint, start_position, map))
{
printf("failed to move to the next coordinates");
exit(EXIT_FAILURE);
}
}
scan(robotPosition, start_position, width, height, map); //finally, when we get to the right spot, scan and start over
}
DONE_EXPLORING = 1; //signals the threads to stop
sendMap();
We have made several useful structures to represent coordinates for either :
tCoord
Point
fPoint
They are used extensively in the project and we encourage looking at how we define and use them in classes.h and classes.c.
Here we show the core functions that makes the robot do it's magic.
int refresh_distance()
refreshes the global variable
distance
with the output of the sonar.
int refresh_angle()
refreshes the global variable
angle
with the output of the gyroscope. In order to
increase presision, angle
takes the average value of
several consecutive measures.
int init_mov_motors()
loads the pointers to use the motors
into some global variables.
void start_straight(int speed_increment)
makes the robot go forward or
backward, depending on the sign of the increment.
void start_turn(int speed_increment)
makes the robot turn on itself one way or
another, depending on the sign of the increment.
void stop_mov_motors()
stops the robot.
int turn_to_angle(float ang)
makes the robot turn until angle == ang
(obviously we don't really test if those floats are equal).
void continue_until(float goal)
robot advances until distance < goal
int grab(int instruct)
uses the front mechanism in one way or the other depending on the instruction.
During the scan, the robot turns on itself and continuously asks for the output of the gyroscope and sonar. Then it modifies the map according to some rules :
SCANNING_MAX_DISTANCE
For the sake of comprehension, the next snippet of code is a simplified (yet functional) version of the scan function. It does not necessarily respond to all of the previous criterias
int scan(tCoord robot_coord_on_map, char* map, int width_n, int height)
{
Point O = Point_new(0,0);
int mm_to_pixel_size = 10*PIXEL_SIZE; //can convert a size in mm to the length of a pixel
float distance_before_setting_a_new_wall = SCANNING_MAX_DISTANCE - .4; //-.4 because of rounding errors
Point measured_point; //coordinates of the tile/pixel the sonar hits into.
// last_point will take the value of previously measured point.
Point last_point = Point_new(0,0); //initialised like O so as to know if nothing has been measured before
refresh_angle(); //refreshes the global variable angle
float starting_scan_angle = angle;
start_turn(SCANNING_SPEED); //The robot starts turning
while(abs(starting_scan_angle-angle)
With this simple scanning function, we had the following problems :
The second problem was due to refresh_angle()
recording
a certain number of consecutives values from the gyroscope before refreshing
angle
with a precise value. Therefore, we chose to
create a twin function devoid of that feature and use it here.
These jumps seem to happen on areas further away from the robot, so the rule of "Do not overwrite known pixels if they are outside your precision radius"
In order to help with both the first and the second issue, we proceeded to optimise a lot of the code involved during scanning. None of the following changes make an enormous speed increase, but are significant.
free_pixels_between(...)
and
free_pixels_in_trigon(...)
were very poorly designed in that regard
Kill two birds with one stone ait'?
It can happen that two measures can land in the same tile. In that case, the simplified code would start rewritting over the same pixels.
This is an option we didn't try extensively but the ideas is to record the duration between the measure of the angle and the distance. Then, a function would correct the current angle with the knowledge of the rotation speed
Between two rounds of scanning, the robot needs to get to it's next viewpoint, preferably without hitting anything. This is why we need to find the right path using a "greedy" version of the Dijkstra algorithm (some people might say that we cut corners...)
To communicate with the game server, we have implemented the protocol described in the project repository. We used threads to let the robot listen for server messages and periodically send its position.
ssize_t read_from_server(int sock, char *msg, size_t maxSize) {
printf("meh\n");
ssize_t bytes_read = read(sock, msg, maxSize);
printf("meh?\n");
if (bytes_read <= 0) {
fprintf(stderr, "Server unexpectedly closed connection...\n");
close(sock);
exit(EXIT_FAILURE);
}
printf("[DEBUG] received %d bytes\n", bytes_read);
return bytes_read;
}
int send_to_server(int sock, char *data, size_t size) {
return write(sock, data, size);
}
int send_POSITION(int sock, int16_t x, int16_t y){
char string[9];
*((uint16_t *) string) = msgId++;
string[2] = TEAM_ID;
string[3] = 0xFF;
string[4] = MSG_POSITION;
string[5] = x;
string[6] = 0x00;
string[7] = y;
string[8] = 0x00;
return send_to_server(sock, string, 9);
}
int send_MAPDATA(int sock, int16_t x, int16_t y, uint8_t R, uint8_t G, uint8_t B){
char string[12];
*((uint16_t *) string) = msgId++;
string[2] = TEAM_ID;
string[3] = 0xFF;
string[4] = MSG_MAPDATA;
string[5] = x;
string[6] = 0x00;
string[7] = y;
string[8] = 0x00;
string[9] = R; // red value of pixel
string[10] = G; // green value of pixel
string[11] = B; // blue value of pixel
return send_to_server(sock, string, 12);
}
int send_MAPDONE(int sock) {
char string[5];
*((uint16_t *) string) = msgId++;
string[2] = TEAM_ID;
string[3] = 0xFF;
string[4] = MSG_MAPDONE;
return send_to_server(sock, string, 5);
}
void *thSendPosition() {
while(!DONE_EXPLORING) {
Sleep(2000);
setPosition();
send_POSITION(s, posX, posY);
}
pthread_exit(NULL);
}