Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,16 @@ FROM ubuntu:noble@sha256:c4a8d5503dfb2a3eb8ab5f807da5bc69a85730fb49b5cfca2330194
ARG DEBIAN_FRONTEND="noninteractive"


RUN dpkg --add-architecture arm64 && apt update
RUN apt install --no-install-recommends -y libgpiod-dev:arm64
# Розділені deb822-джерела: amd64 з archive.ubuntu.com, arm64 з
# ports.ubuntu.com. Обидва файли обов'язкові в build context, інакше
# COPY провалить збірку. Без arch-обмежень apt тягнув би arm64 з
# archive.ubuntu.com (там його немає -> 404).
COPY ubuntu-amd64.sources /etc/apt/sources.list.d/ubuntu.sources
COPY ubuntu-arm64.sources /etc/apt/sources.list.d/ubuntu-arm64.sources

RUN dpkg --add-architecture arm64 && \
apt-get update && \
apt-get install --no-install-recommends -y libgpiod-dev:arm64
# Пакети зі списку apt-packages.in (по одному на рядок, коментарі через #).
#
# Прапори й чистка:
Expand Down
15 changes: 15 additions & 0 deletions .devcontainer/ubuntu-amd64.sources
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Стандартні Ubuntu-репозиторії для нативних amd64 пакетів devcontainer.
Types: deb
URIs: http://archive.ubuntu.com/ubuntu
Suites: noble noble-updates noble-backports
Components: main universe restricted multiverse
Architectures: amd64
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

# Security updates винесено окремо, бо вони публікуються на іншому mirror.
Types: deb
URIs: http://security.ubuntu.com/ubuntu
Suites: noble-security
Components: main universe restricted multiverse
Architectures: amd64
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
7 changes: 7 additions & 0 deletions .devcontainer/ubuntu-arm64.sources
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ARM64 пакети Ubuntu публікуються через ports.ubuntu.com.
Types: deb
URIs: http://ports.ubuntu.com/ubuntu-ports
Suites: noble noble-updates noble-backports noble-security
Components: main universe restricted multiverse
Architectures: arm64
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
10 changes: 10 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions homework_12/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Логи C2 мають лишатися на машинi, де запускався compose stack.
edge/logs/

# Викладацькi demo-файли та solution code не є частиною student-facing repo.
_teacher/
1 change: 1 addition & 0 deletions homework_12/edge/c2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
32 changes: 32 additions & 0 deletions homework_12/edge/c2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 3.20)
project(c2_service CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

find_package(MAVSDK REQUIRED)

include(FetchContent)

FetchContent_Declare(nlohmann_json
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
)
set(JSON_BuildTests OFF CACHE BOOL "" FORCE)
set(JSON_Install OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(nlohmann_json)

add_executable(c2_service
src/main.cpp
src/fc_link.cpp
src/c2_controller.cpp
)

target_include_directories(c2_service PRIVATE ${CMAKE_SOURCE_DIR}/src)

target_link_libraries(c2_service PRIVATE
MAVSDK::mavsdk
nlohmann_json::nlohmann_json
)

target_compile_options(c2_service PRIVATE -Wall -Wextra)
52 changes: 52 additions & 0 deletions homework_12/edge/c2/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Етап 1: збирання
# MAVSDK у цьому ДЗ зафiксовано на конкретнiй версiї, щоб не шукати пакет вручну.
ARG MAVSDK_VERSION=2.12.9

FROM ubuntu:22.04 AS builder

ARG MAVSDK_VERSION
ENV DEBIAN_FRONTEND=noninteractive

# Блок залежностей надано: iнструменти збирання, CMake та wget для MAVSDK.
RUN apt-get update && apt-get install -y --no-install-recommends \
g++ cmake git make wget ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# MAVSDK встановлюється з готового .deb пакета для Ubuntu 22.04.
RUN wget -qO /tmp/mavsdk-dev.deb \
"https://github.com/mavlink/MAVSDK/releases/download/v${MAVSDK_VERSION}/libmavsdk-dev_${MAVSDK_VERSION}_ubuntu22.04_amd64.deb" \
&& dpkg -i /tmp/mavsdk-dev.deb \
&& rm /tmp/mavsdk-dev.deb

WORKDIR /src
COPY . .

# Заповнити команду CMake-збирання./////////////////////
RUN cmake -S . -B build && cmake --build build

# Етап 2: запуск
# Для простоти ДЗ той самий MAVSDK .deb встановлюється i в образ запуску.//////////////////////
FROM ubuntu:22.04

ARG MAVSDK_VERSION
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y --no-install-recommends \
wget ca-certificates \
&& rm -rf /var/lib/apt/lists/*

RUN wget -qO /tmp/mavsdk.deb \
"https://github.com/mavlink/MAVSDK/releases/download/v${MAVSDK_VERSION}/libmavsdk-dev_${MAVSDK_VERSION}_ubuntu22.04_amd64.deb" \
&& dpkg -i /tmp/mavsdk.deb \
&& rm /tmp/mavsdk.deb \
&& apt-get clean

RUN mkdir -p /etc/c2 /var/log/c2

WORKDIR /app
COPY --from=builder /src/build/c2_service .

HEALTHCHECK --interval=5s --timeout=2s --retries=3 \
CMD test -f /tmp/c2_healthy || exit 1

CMD ["./c2_service"]
156 changes: 156 additions & 0 deletions homework_12/edge/c2/src/c2_controller.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#include "c2_controller.hpp"
#include "fc_link.hpp" // MAVSDK обгортка, API описано у fc_link.hpp
#include "udp_socket.hpp" // UDP прийом, API описано у udp_socket.hpp
#include "log.hpp"
#include <nlohmann/json.hpp> // Розбiр JSON з точками маршруту вiд auto_stub

#include <fstream>
#include <iostream>
#include <string>

using json = nlohmann::json;

static constexpr uint16_t STUB_PORT = 14560;
static const std::string LOG_FILE_PATH = "/var/log/c2/c2.log";

static const std::string state_to_string(C2State state)
{
switch (state) {
case C2State::DISARMED: {
return "DISARMED";
}
case C2State::ARMED_HOLD: {
return "ARMED_HOLD";
}
case C2State::ARMED_GUIDED: {
return "ARMED_GUIDED";
}
case C2State::ARMED_MANUAL: {
return "ARMED_MANUAL";
}
}
return "UNKNOWN";
}

struct C2Controller::Impl {
C2State state = C2State::DISARMED;

// TODO: додати FcLink, UdpSocket, лог-файл та прапорцi стану.
// FcLink потребує fc_port у конструкторi Impl.
// UdpSocket має слухати STUB_PORT.

FcLink fc;
UdpSocket updSocket;
std::ofstream log_file;
bool healthy_written = false;

Impl(uint16_t fc_port)
: fc(fc_port)
, updSocket(STUB_PORT)
{
log_file.open(LOG_FILE_PATH, std::ios::app);
};

void transition(C2State next)
{
if (next == state) {
return;
}

log(std::string("[C2] state: ") + state_to_string(state) + " -> " + state_to_string(next));
state = next;
}

void log(const std::string& message)
{
LOG(message);

if (!log_file.is_open()) {
return;
}
log_file << message << std::endl;
}

void close_log_file()
{
if (log_file.is_open()) {
log_file.close();
}
}
};

C2Controller::C2Controller(uint16_t fc_port)
: impl_(std::make_unique<Impl>(fc_port))
{
}

C2Controller::~C2Controller()
{
this->impl_->close_log_file();
};

void C2Controller::tick()
{
if (!this->impl_->healthy_written && this->impl_->fc.is_connected()) {
std::ofstream("/tmp/c2_healthy").close();
this->impl_->healthy_written = true;
}

C2State state = C2State::DISARMED;
if (this->impl_->fc.is_armed() == true) {
switch (this->impl_->fc.flight_mode()) {
case FcLink::FlightMode::Unknown:
return;

case FcLink::FlightMode::Manual:
// не заважати ручному керуванню
state = C2State::ARMED_MANUAL;
break;

case FcLink::FlightMode::Hold:
// один раз на вхiд у стан надiслати fc.hold()
state = C2State::ARMED_HOLD;
break;

case FcLink::FlightMode::Guided:
// передавати
// розбирати JSON i викликати fc.go_to_ned(north, east)
state = C2State::ARMED_GUIDED;
break;
}
}

const bool entering_hold = (state == C2State::ARMED_HOLD && this->impl_->state != C2State::ARMED_HOLD);
this->impl_->transition(state);
if (entering_hold) {
this->impl_->fc.hold();
}

char buf[1024];
ssize_t n = this->impl_->updSocket.recv(buf, sizeof(buf) - 1);
if (n <= 0) {
return;
}
buf[n] = '\0';

if (this->impl_->state == C2State::ARMED_GUIDED) {
try {
auto waypoint = json::parse(buf);
float north = waypoint.at("north_m").get<float>();
float east = waypoint.at("east_m").get<float>();
this->impl_->fc.go_to_ned(north, east);
this->impl_->log("[C2] fwd: north=" + std::to_string(north) + " east=" + std::to_string(east));
}
catch (const std::exception& e) {
DEBUG("Error: waypoint failed " << e.what());
}
}
else {
this->impl_->log(std::string("[C2] blocked: waypoint in ") + state_to_string(this->impl_->state));
}
}

C2State C2Controller::current_state() const
{
return impl_->state;
}
32 changes: 32 additions & 0 deletions homework_12/edge/c2/src/c2_controller.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once
#include <cstdint>
#include <memory>
#include <string>

enum class C2State {
DISARMED,
ARMED_HOLD,
ARMED_GUIDED,
ARMED_MANUAL,
};

// C2-сервiс для арбiтражу команд.
// Читає MAVLink-телеметрiю з fc_sim через MAVSDK та точки маршруту
// вiд auto_stub у JSON через UDP. Вирiшує, хто може командувати FC.
class C2Controller {
public:
explicit C2Controller(uint16_t fc_port);
~C2Controller();

// Обробити одну iтерацiю: передати або блокувати команди
// залежно вiд поточного стану. Викликати з main loop приблизно на 10 Hz.
void tick();

// Read-only доступ до поточного стану C2.
// Корисно для перевiрок, тестiв або майбутньої дiагностики.
C2State current_state() const;

private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
Loading
Loading