#include "controller_impl.h" #include #include #include #include #include #include #include #include #include #include #include #define SERVER_PORT 5000 #define SERVER_IP "127.0.0.1" static int serverSocket = -1; static std::vector clientSockets; void initSockets() {} void cleanupSockets() {} void initServer() { initSockets(); serverSocket = socket(AF_INET, SOCK_STREAM, 0); if(serverSocket < 0) { std::cerr << "Socket creation failed\n"; return; } sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(SERVER_PORT); int opt = 1; setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)); if(bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { std::cerr << "Bind failed\n"; return; } if(listen(serverSocket, 5) < 0) { std::cerr << "Listen failed\n"; return; } std::cout << "Server initialized\n"; } void acceptClient(int clientId) { sockaddr_in clientAddr; socklen_t addrLen = sizeof(clientAddr); int clientSock = accept(serverSocket, (sockaddr*)&clientAddr, &addrLen); if(clientSock < 0) { std::cerr << "Accept failed for client " << clientId << "\n"; return; } clientSockets.push_back(clientSock); std::cout << "Client " << clientId << " connected\n"; } void closeServer() { for(auto sock : clientSockets) { close(sock); } close(serverSocket); cleanupSockets(); } // Load target coordinates from file // File format: one line per UAV with "x,y,z" coordinates // Returns number of targets loaded int loadTargets(const char* filename, double* targets, int maxClients) { FILE* file = fopen(filename, "r"); if (!file) { std::cerr << "Failed to open config file: " << filename << "\n"; return 0; } int count = 0; char line[256]; bool inTargets = false; // Simple YAML parser for targets section // Expects format: // targets: // - [x, y, z] // - [x, y, z] while (fgets(line, sizeof(line), file) && count < maxClients) { // Check if we've entered the targets section if (strstr(line, "targets:") != nullptr) { inTargets = true; continue; } // If we hit another top-level key (no leading whitespace), exit targets section if (inTargets && line[0] != ' ' && line[0] != '\t' && line[0] != '\n' && line[0] != '#') { break; } // Parse target entries: " - [x, y, z]" if (inTargets) { double x, y, z; // Try to match the array format if (sscanf(line, " - [%lf, %lf, %lf]", &x, &y, &z) == 3) { // MATLAB uses column-major order, so for a maxClients x 3 matrix: // Column 1 (x): indices 0, 1, 2, ... // Column 2 (y): indices maxClients, maxClients+1, ... // Column 3 (z): indices 2*maxClients, 2*maxClients+1, ... targets[count + 0 * maxClients] = x; targets[count + 1 * maxClients] = y; targets[count + 2 * maxClients] = z; std::cout << "Loaded target " << (count + 1) << ": " << x << "," << y << "," << z << "\n"; count++; } } } fclose(file); return count; } // --------------------------------------------------------------------------- // Scenario CSV loading // --------------------------------------------------------------------------- // Split a CSV data row into fields, respecting quoted commas. // Inserts NUL terminators into `line` in place. // Returns the number of fields found. static int splitCSVRow(char* line, char** fields, int maxFields) { int n = 0; bool inQuote = false; if (n < maxFields) fields[n++] = line; for (char* p = line; *p && *p != '\n' && *p != '\r'; ++p) { if (*p == '"') { inQuote = !inQuote; } else if (*p == ',' && !inQuote && n < maxFields) { *p = '\0'; fields[n++] = p + 1; } } // NUL-terminate the last field (strip trailing newline) for (char* p = fields[n - 1]; *p; ++p) { if (*p == '\n' || *p == '\r') { *p = '\0'; break; } } return n; } // Trim leading/trailing ASCII whitespace and remove enclosing double-quotes. // Modifies the string in place and returns the start of the trimmed content. static char* trimField(char* s) { // Trim leading whitespace while (*s == ' ' || *s == '\t') ++s; // Remove enclosing quotes size_t len = strlen(s); if (len >= 2 && s[0] == '"' && s[len - 1] == '"') { s[len - 1] = '\0'; ++s; } // Trim trailing whitespace char* end = s + strlen(s) - 1; while (end > s && (*end == ' ' || *end == '\t')) { *end-- = '\0'; } return s; } // Open scenario.csv, skip the header row, return the data row in `line`. // Returns 1 on success, 0 on failure. static int readScenarioDataRow(const char* filename, char* line, int lineSize) { FILE* f = fopen(filename, "r"); if (!f) { std::cerr << "loadScenario: cannot open " << filename << "\n"; return 0; } // Skip header row if (!fgets(line, lineSize, f)) { fclose(f); return 0; } // Read data row int ok = (fgets(line, lineSize, f) != NULL); fclose(f); return ok ? 1 : 0; } // Load guidance parameters from scenario.csv into flat params[NUM_SCENARIO_PARAMS]. // Index mapping (0-based): // 0-13 : timestep, maxIter, minAlt, discretizationStep, protectedRange, // initialStepSize, barrierGain, barrierExponent, collisionRadius, // comRange, alphaDist, betaDist, alphaTilt, betaTilt // 14-16: domainMin (east, north, up) // 17-19: domainMax (east, north, up) // 20-21: objectivePos (east, north) // 22-25: objectiveVar (2x2 col-major: v11, v12, v21, v22) // 26 : sensorPerformanceMinimum // Returns 1 on success, 0 on failure. int loadScenario(const char* filename, double* params) { char line[4096]; if (!readScenarioDataRow(filename, line, sizeof(line))) return 0; char copy[4096]; strncpy(copy, line, sizeof(copy) - 1); copy[sizeof(copy) - 1] = '\0'; char* fields[32]; int nf = splitCSVRow(copy, fields, 32); if (nf < 19) { fprintf(stderr, "loadScenario: expected >=19 columns, got %d\n", nf); return 0; } // Scalar fields (columns 0–13) for (int i = 0; i < 14; i++) { params[i] = atof(trimField(fields[i])); } // domainMin: column 14, format "east, north, up" { char tmp[256]; strncpy(tmp, fields[14], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; char* t = trimField(tmp); if (sscanf(t, "%lf , %lf , %lf", ¶ms[14], ¶ms[15], ¶ms[16]) != 3) { fprintf(stderr, "loadScenario: failed to parse domainMin: %s\n", t); return 0; } } // domainMax: column 15 { char tmp[256]; strncpy(tmp, fields[15], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; char* t = trimField(tmp); if (sscanf(t, "%lf , %lf , %lf", ¶ms[17], ¶ms[18], ¶ms[19]) != 3) { fprintf(stderr, "loadScenario: failed to parse domainMax: %s\n", t); return 0; } } // objectivePos: column 16 { char tmp[256]; strncpy(tmp, fields[16], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; char* t = trimField(tmp); if (sscanf(t, "%lf , %lf", ¶ms[20], ¶ms[21]) != 2) { fprintf(stderr, "loadScenario: failed to parse objectivePos: %s\n", t); return 0; } } // objectiveVar: column 17, format "v11, v12, v21, v22" { char tmp[256]; strncpy(tmp, fields[17], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; char* t = trimField(tmp); if (sscanf(t, "%lf , %lf , %lf , %lf", ¶ms[22], ¶ms[23], ¶ms[24], ¶ms[25]) != 4) { fprintf(stderr, "loadScenario: failed to parse objectiveVar: %s\n", t); return 0; } } // sensorPerformanceMinimum: column 18 { char tmp[64]; strncpy(tmp, fields[18], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; params[26] = atof(trimField(tmp)); } printf("Loaded scenario: domain [%g,%g,%g] to [%g,%g,%g]\n", params[14], params[15], params[16], params[17], params[18], params[19]); return 1; } // Load initial UAV positions from scenario.csv (column 19: initialPositions). // targets is a column-major [maxClients x 3] array (same layout as loadTargets): // targets[i + 0*maxClients] = east // targets[i + 1*maxClients] = north // targets[i + 2*maxClients] = up // Returns number of positions loaded. int loadInitialPositions(const char* filename, double* targets, int maxClients) { char line[4096]; if (!readScenarioDataRow(filename, line, sizeof(line))) return 0; char copy[4096]; strncpy(copy, line, sizeof(copy) - 1); copy[sizeof(copy) - 1] = '\0'; char* fields[32]; int nf = splitCSVRow(copy, fields, 32); if (nf < 20) { fprintf(stderr, "loadInitialPositions: expected >=20 columns, got %d\n", nf); return 0; } // Column 19: initialPositions flat "x1,y1,z1, x2,y2,z2, ..." char tmp[1024]; strncpy(tmp, fields[19], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; char* t = trimField(tmp); double vals[3 * 4]; // up to MAX_CLIENTS triples int parsed = 0; char* tok = strtok(t, ","); while (tok && parsed < 3 * maxClients) { vals[parsed++] = atof(tok); tok = strtok(nullptr, ","); } int count = parsed / 3; for (int i = 0; i < count; i++) { targets[i + 0 * maxClients] = vals[i * 3 + 0]; // east targets[i + 1 * maxClients] = vals[i * 3 + 1]; // north targets[i + 2 * maxClients] = vals[i * 3 + 2]; // up } printf("Loaded %d initial position(s) from scenario\n", count); return count; } // Load obstacle bounding-box corners from scenario.csv. // Columns used: 20 (numObstacles), 21 (obstacleMin flat), 22 (obstacleMax flat). // obstacleMin and obstacleMax are column-major [maxObstacles x 3] arrays: // obstacleMin[obs + 0*maxObstacles] = east_min // obstacleMin[obs + 1*maxObstacles] = north_min // obstacleMin[obs + 2*maxObstacles] = up_min // Returns number of obstacles loaded (0 if none or on error). int loadObstacles(const char* filename, double* obstacleMin, double* obstacleMax, int maxObstacles) { char line[4096]; if (!readScenarioDataRow(filename, line, sizeof(line))) return 0; char copy[4096]; strncpy(copy, line, sizeof(copy) - 1); copy[sizeof(copy) - 1] = '\0'; char* fields[32]; int nf = splitCSVRow(copy, fields, 32); if (nf < 23) return 0; // Column 20: numObstacles int n = (int)atof(trimField(fields[20])); if (n <= 0) return 0; if (n > maxObstacles) { fprintf(stderr, "loadObstacles: %d obstacles exceeds MAX_OBSTACLES=%d\n", n, maxObstacles); n = maxObstacles; } // Column 21: obstacleMin flat "x0,y0,z0, x1,y1,z1, ..." { char tmp[1024]; strncpy(tmp, fields[21], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; char* t = trimField(tmp); double vals[3 * 8]; // up to MAX_OBSTACLES triples int parsed = 0; char* tok = strtok(t, ","); while (tok && parsed < 3 * n) { vals[parsed++] = atof(tok); tok = strtok(nullptr, ","); } if (parsed < 3 * n) { fprintf(stderr, "loadObstacles: obstacleMin has fewer values than expected\n"); return 0; } for (int obs = 0; obs < n; obs++) { obstacleMin[obs + 0 * maxObstacles] = vals[obs * 3 + 0]; // east obstacleMin[obs + 1 * maxObstacles] = vals[obs * 3 + 1]; // north obstacleMin[obs + 2 * maxObstacles] = vals[obs * 3 + 2]; // up } } // Column 22: obstacleMax flat { char tmp[1024]; strncpy(tmp, fields[22], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; char* t = trimField(tmp); double vals[3 * 8]; int parsed = 0; char* tok = strtok(t, ","); while (tok && parsed < 3 * n) { vals[parsed++] = atof(tok); tok = strtok(nullptr, ","); } if (parsed < 3 * n) { fprintf(stderr, "loadObstacles: obstacleMax has fewer values than expected\n"); return 0; } for (int obs = 0; obs < n; obs++) { obstacleMax[obs + 0 * maxObstacles] = vals[obs * 3 + 0]; obstacleMax[obs + 1 * maxObstacles] = vals[obs * 3 + 1]; obstacleMax[obs + 2 * maxObstacles] = vals[obs * 3 + 2]; } } printf("Loaded %d obstacle(s) from scenario\n", n); return n; } // Message type names for logging static const char* messageTypeName(uint8_t msgType) { switch (msgType) { case 1: return "TARGET"; case 2: return "ACK"; case 3: return "READY"; case 4: return "RTL"; case 5: return "LAND"; case 6: return "GUIDANCE_TOGGLE"; case 7: return "REQUEST_POSITION"; case 8: return "POSITION"; default: return "UNKNOWN"; } } // Send a single-byte message type to a client int sendMessageType(int clientId, int msgType) { if (clientId <= 0 || clientId > (int)clientSockets.size()) return 0; uint8_t msg = (uint8_t)msgType; ssize_t sent = send(clientSockets[clientId - 1], &msg, 1, 0); if (sent != 1) { std::cerr << "Send failed for client " << clientId << "\n"; return 0; } std::cout << "Sent " << messageTypeName(msg) << " to client " << clientId << "\n"; return 1; } // Send TARGET message with coordinates (1 byte type + 24 bytes coords) int sendTarget(int clientId, const double* coords) { if (clientId <= 0 || clientId > (int)clientSockets.size()) return 0; // Build message: 1 byte type + 3 doubles (little-endian) uint8_t buffer[1 + 3 * sizeof(double)]; buffer[0] = 1; // TARGET = 1 memcpy(buffer + 1, coords, 3 * sizeof(double)); ssize_t sent = send(clientSockets[clientId - 1], buffer, sizeof(buffer), 0); if (sent != sizeof(buffer)) { std::cerr << "Send target failed for client " << clientId << "\n"; return 0; } std::cout << "Sent TARGET to client " << clientId << ": " << coords[0] << "," << coords[1] << "," << coords[2] << "\n"; return 1; } // Wait for a specific message type from ALL clients simultaneously using select() // Returns 1 if all clients responded with expected message type, 0 on failure int waitForAllMessageType(int numClients, int expectedType) { if (numClients <= 0 || numClients > (int)clientSockets.size()) return 0; uint8_t expected = (uint8_t)expectedType; std::vector completed(numClients, false); int completedCount = 0; while (completedCount < numClients) { // Build fd_set for select() fd_set readfds; FD_ZERO(&readfds); int maxfd = -1; for (int i = 0; i < numClients; i++) { if (!completed[i]) { FD_SET(clientSockets[i], &readfds); if (clientSockets[i] > maxfd) maxfd = clientSockets[i]; } } // Wait for any socket to have data int ready = select(maxfd + 1, &readfds, nullptr, nullptr, nullptr); if (ready <= 0) return 0; // Check each socket for (int i = 0; i < numClients; i++) { if (!completed[i] && FD_ISSET(clientSockets[i], &readfds)) { uint8_t msgType; int len = recv(clientSockets[i], &msgType, 1, 0); if (len == 0) { std::cerr << "waitForAllMessageType: client " << (i + 1) << " disconnected while waiting for " << messageTypeName(expected) << "\n"; return 0; } if (len < 0) { std::cerr << "waitForAllMessageType: recv error for client " << (i + 1) << " while waiting for " << messageTypeName(expected) << "\n"; return 0; } std::cout << "Received " << messageTypeName(msgType) << " from client " << (i + 1) << "\n"; if (msgType == expected) { completed[i] = true; completedCount++; } } } } return 1; } // Wait for user to press Enter void waitForUserInput() { std::cout << "Press Enter to continue...\n"; std::cin.ignore(std::numeric_limits::max(), '\n'); } // Broadcast GUIDANCE_TOGGLE to all clients void sendGuidanceToggle(int numClients) { for (int i = 1; i <= numClients; i++) { sendMessageType(i, 6); // GUIDANCE_TOGGLE = 6 } } // Send REQUEST_POSITION to all clients int sendRequestPositions(int numClients) { for (int i = 1; i <= numClients; i++) { if (!sendMessageType(i, 7)) return 0; // REQUEST_POSITION = 7 } return 1; } // Receive POSITION response (1 byte type + 24 bytes ENU) from all clients. // Stores results in column-major order: positions[client + 0*maxClients] = x (east), // positions[client + 1*maxClients] = y (north), // positions[client + 2*maxClients] = z (up) int recvPositions(int numClients, double* positions, int maxClients) { if (numClients <= 0 || numClients > (int)clientSockets.size()) return 0; for (int i = 0; i < numClients; i++) { // Expect: POSITION byte (1) + 3 doubles (24) uint8_t msgType; if (recv(clientSockets[i], &msgType, 1, MSG_WAITALL) != 1) { std::cerr << "recvPositions: failed to read msg type from client " << (i + 1) << "\n"; return 0; } if (msgType != 8) { // POSITION = 8 std::cerr << "recvPositions: expected POSITION(8), got " << (int)msgType << " from client " << (i + 1) << "\n"; return 0; } double coords[3]; if (recv(clientSockets[i], coords, sizeof(coords), MSG_WAITALL) != (ssize_t)sizeof(coords)) { std::cerr << "recvPositions: failed to read coords from client " << (i + 1) << "\n"; return 0; } // Store column-major (MATLAB layout): col 0 = east, col 1 = north, col 2 = up positions[i + 0 * maxClients] = coords[0]; // east (x) positions[i + 1 * maxClients] = coords[1]; // north (y) positions[i + 2 * maxClients] = coords[2]; // up (z) std::cout << "Position from client " << (i + 1) << ": " << coords[0] << "," << coords[1] << "," << coords[2] << "\n"; } return 1; } // Sleep for the given number of milliseconds void sleepMs(int ms) { struct timespec ts; ts.tv_sec = ms / 1000; ts.tv_nsec = (ms % 1000) * 1000000L; nanosleep(&ts, nullptr); }