Add base math, height map, todo, docker files, frontend, backend server, dbs, gorm ...etc...

This commit is contained in:
moxitech 2024-09-28 00:57:36 +07:00
parent 081646757e
commit 19096a5d76
62 changed files with 22326 additions and 13 deletions

11
.env
View File

@ -0,0 +1,11 @@
SERVER_BASE_ADDRESS=0.0.0.0:8080
MONGO_INITDB_ROOT_USERNAME=moxitech
MONGO_INITDB_ROOT_PASSWORD=moxitech
MONGO_INITDB_DATABASE=moxitech
POSTGRES_DB=moxitech
POSTGRES_USER=moxitech
POSTGRES_PASSWORD=moxitech
POSTGRES_PORT=5432
POSTGRES_HOST=postgres

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.env
*/*/node_modules

View File

@ -0,0 +1,31 @@
#DEV
build:
docker compose build
dev: build
docker compose up -d --force-recreate
devf: dev
docker compose logs -f
up:
docker compose up -d --force-recreate
upf: up
docker compose logs -f
logs:
docker compose logs -f
stop:
echo "!!!!!!!!!!!!!!!!!EXTERMINATUS!!!!!!!!!!!!!!!!!"
docker compose stop
start:
docker compose start
drop:
docker-compose down --volumes

89
TODO.md Normal file
View File

@ -0,0 +1,89 @@
#### Главная
Карта и страница
1. Размонтирование UseRef
2. Удлиннить и переместить в левый нижний угол линейку и добавить возможность ее отключения
3. Окно настроек карты (размер, цвета высот, цвета фона, выбор шаблона карты с сервера и тд)
* Мертвые зоны карты - недоступность сигнала (x0y0 -> x1y1)
4. Загрузка JSON
Логика
5. Окно БПЛА =>
6. Контексное меню редактирования данных БПЛА (конкретные координаты карты начала маршрута, массив перемещений)
7. Контексное меню сетевых параметров (ввод параметров антенны (частота и тд), настройки утухания и тд)
8. Окно базовых станций =>
9. Направление
10. Радиус
11. Частота
12. Эмуляция движения относительно времени =>
13. Задание скорости отдельных компонентов
14. Время остановки эмуляции
Quic select - быстрые действия
1. Быстрый выбор на карте мышью точки начала и на shift до отжатия точек движения
2. Выбор групп БПЛА
3. при зажатом шифте после выбора можно перетаскивать компоненты
Сервер =>
1. Websocket соединение +> комната
2. на Websocket соединении комната содержит до 8 участников
3. Если ВСЕ участники покинули комнату но не завершили модулирование, соединение закроется через TIME_EXPIRE_ROOM минут с автосейвом по временной метке
4. Отложенное вычисление: вычисления которые можно отложить - запускаем в фоне для ускорения модуляции
Структура данных сервера websocket ::
```
Соединение:
Пользователи: [
Пользователь: {
Id:
TimeActivate: ts(int64)
IsActive: bool
IsCreator? bool
...
}
],
Карта: {
Имя <DbTemplateName|CustomNameFromUser>
Координаты: [{...}],
}
БПЛА: [
БПЛА: {
Id:
Name:
Checked:{UserId, expire}
{coords...}
{params...}
}
]
Базовые_станции: [
BaseStation: {
Id:
Name:
Checked:{UserId, expire}
{coords...}
{params...}
}
]
ts_update:
```
При выборе объекта, если пользователь не убрал выделение, он остаеться выделенным x(120) секунд на сервере, после чего с объектом могут взаимодействовать другие пользователи
При нажатии на начало вычислений, отображается статус расчета и вскоре становиться активной кнопка просмотр записи
#### Настройки
1. Базовые настройки пользователя
2. Если админ +>
3. Настройки групп пользователей (сколько максимум и тд)
3. Настройки времени остановки и автосейва при покидании комнат
4. Настройки выгрузки и сохранения данных
#### Проведенные вычисления
1. Таблица проведенных вычислений
2. Подгрузка из истории

View File

@ -0,0 +1,82 @@
version: '3.9'
networks:
dns_net:
driver: bridge
services:
mongo:
image: mongo
container_name: dns-mongo-db
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE}
ports:
- '27017:27017'
networks:
- dns_net
volumes:
- mongodata:/data/db
restart: always
postgres:
image: postgres:13
container_name: dns-postgres-db
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "${POSTGRES_PORT}:5432"
networks:
- dns_net
restart: always
# nginx:
# image: nginx:alpine
# container_name: geotouchka-nginx-service
# ports:
# - "${NGINX_PORT}:80"
# volumes:
# # - ./frontend/geotouchka-web-application/build:/usr/share/nginx/html
# - ./nginx.conf:/etc/nginx/nginx.conf
# networks:
# - dns_net
# depends_on:
# - site
# restart: always
server:
container_name: dns-server
build:
context: ./src/server
dockerfile: Dockerfile
env_file: ".env"
ports:
- "8080:8080"
depends_on:
- postgres
networks:
- dns_net
restart: always
front:
container_name: dns-ui
build:
context: ./src/frontend
dockerfile: Dockerfile
env_file: ".env"
ports:
- "3000:3000"
networks:
- dns_net
restart: always
volumes:
postgres_data:
mongodata:
driver: local

19
nginx.conf Normal file
View File

@ -0,0 +1,19 @@
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name localhost;
add_header 'Access-Control-Allow-Origin' 'http://site' always;
location / {
proxy_pass http://site:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}

20
src/frontend/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
# Use an official Node.js runtime as a parent image
FROM node:18-alpine
# Set the working directory in the container to /app
WORKDIR /app
# Copy package.json and package-lock.json to the container
COPY package*.json ./
# Install any needed dependencies
RUN npm install
# Bundle app source
COPY . .
# Make port 3000 available to the world outside this container
EXPOSE 3000
# Define the main command to run the application
CMD [ "npm", "start" ]

70
src/frontend/README.md Normal file
View File

@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

20170
src/frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

49
src/frontend/package.json Normal file
View File

@ -0,0 +1,49 @@
{
"name": "dsn-application",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"leaflet": "^1.9.4",
"leaflet-defaulticon-compatibility": "^0.1.2",
"qrcode.react": "^4.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-leaflet": "^4.2.1",
"react-router-dom": "^6.26.2",
"react-scripts": "5.0.1",
"socket.io": "^4.8.0",
"socket.io-client": "^4.8.0",
"three": "^0.169.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"sass": "^1.79.3"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

23
src/frontend/server.js Normal file
View File

@ -0,0 +1,23 @@
// EMULATOR
const io = require("socket.io")(4000, {
cors: {
origin: "*", // Разрешить все источники
},
});
const devices = [
{ id: 1, name: "Устройство 1", lat: 59.9343, lng: 30.3351, timestamp: Date.now() }, // Санкт-Петербург
{ id: 2, name: "Устройство 2", lat: 59.9400, lng: 30.3000, timestamp: Date.now() }, // Санкт-Петербург
];
setInterval(() => {
// Имитируем обновление местоположения
devices.forEach(device => {
device.lat += (Math.random() - 0.5) * 0.01;
device.lng += (Math.random() - 0.5) * 0.01;
device.timestamp = Date.now();
});
io.emit("deviceLocationUpdate", devices); // Отправляем обновленные данные клиенту
}, 5000); // Обновляем данные каждые 5 секунд

38
src/frontend/src/App.css Normal file
View File

@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

61
src/frontend/src/App.js Normal file
View File

@ -0,0 +1,61 @@
import React, { useState } from 'react';
import DeviceGroups from "./pages/DeviceGroups";
import Devices from "./pages/Devices";
import Sidebar from "./Sidebar";
import Dashboard from './pages/Dashboard';
import UserAccount from './pages/UserAccount'; // Импортируем компонент UserAccount
import Login from './pages/Login'; // Импортируем страницу логина
import Connections from './pages/Connections'; // Импортируем страницу подключений
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
const App = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false); // Статус авторизации
const [activeTab, setActiveTab] = useState('map'); // По умолчанию активная вкладка "Карта"
// Функция для выхода из системы
const handleLogout = () => {
setIsLoggedIn(false);
};
const handleLogin = () => {
setIsLoggedIn(true); // Устанавливаем авторизацию
};
return (
<Router>
<Routes>
<Route
path="/login"
element={isLoggedIn ? <Navigate to="/app" /> : <Login onLogin={handleLogin} />}
/>
<Route
path="/app"
element={isLoggedIn ? (
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
<div style={{ display: 'flex', flex: 1 }}>
<Sidebar
onSelectTab={setActiveTab}
activeTab={activeTab}
onLogout={handleLogout} // Передаем функцию handleLogout через пропс onLogout
/>
<div style={{ padding: '20px', flex: 1 }}>
{activeTab === 'map' && <Dashboard />} {/* Подключаем компонент Dashboard */}
{activeTab === 'connection' && <Connections />} {/* Страница подключений */}
{activeTab === 'account' && <UserAccount />} {/* Подключаем компонент UserAccount */}
{activeTab === 'groups' && <DeviceGroups />} {/* Группы устройств */}
{activeTab === 'devices' && <Devices />} {/* Устройства */}
</div>
</div>
</div>
) : (
<Navigate to="/login" />
)}
/>
{/* Перенаправляем на /login по умолчанию */}
<Route path="*" element={<Navigate to="/login" />} />
</Routes>
</Router>
);
};
export default App;

View File

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@ -0,0 +1,34 @@
.section {
margin-bottom: 20px;
form {
input {
margin-right: 10px;
padding: 8px;
border-radius: 4px;
border: 1px solid #ddd;
}
button {
padding: 8px 12px;
background-color: #2980b9;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
background-color: #3498db;
}
}
}
ul {
list-style-type: none;
padding: 0;
li {
margin-top: 10px;
}
}
}

View File

@ -0,0 +1,48 @@
.device-groups {
padding: 20px;
}
.device-groups h2 {
margin-bottom: 20px;
}
.group-creation {
margin-bottom: 20px;
}
.group-creation input {
padding: 5px;
margin-right: 10px;
}
.group-creation button {
padding: 5px 10px;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin-bottom: 20px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.active {
background-color: #e8f5e9;
}
.inactive {
background-color: #ffebee;
}
li div {
margin-bottom: 10px;
}
li button {
margin-right: 10px;
}

View File

@ -0,0 +1,38 @@
.devices {
padding: 20px;
}
.devices h2 {
margin-bottom: 20px;
}
.devices table {
width: 100%;
border-collapse: collapse;
}
.devices table, .devices th, .devices td {
border: 1px solid #ddd;
}
.devices th, .devices td {
padding: 8px;
text-align: left;
}
.devices th {
background-color: #34495e;
color: white;
}
.devices tr:hover {
background-color: #f1f1f1;
}
.buttons {
margin-top: 20px;
}
.buttons button {
margin-right: 10px;
}

View File

@ -0,0 +1,70 @@
// styles/Login.scss
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
.login-box {
background-color: #fff;
padding: 40px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
max-width: 400px;
width: 100%;
display: flex;
flex-direction: column; // Выровнять элементы по колонке
h2 {
text-align: center;
margin-bottom: 20px;
color: #333;
}
form {
display: flex;
flex-direction: column;
.input-group {
display: flex;
flex-direction: column;
margin-bottom: 20px;
label {
margin-bottom: 8px;
color: #666;
font-size: 14px;
}
input {
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
transition: border-color 0.3s ease;
&:focus {
outline: none;
border-color: #007bff;
}
}
}
button {
padding: 12px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s ease;
&:hover {
background-color: #0056b3;
}
}
}
}
}

View File

@ -0,0 +1,58 @@
.sidebar {
width: 200px;
height: 100vh;
background-color: #2c3e50;
color: white;
padding: 20px;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.5);
}
.sidebar h2 {
font-size: 24px;
margin-bottom: 20px;
}
.sidebar ul {
list-style-type: none;
padding: 0;
}
.sidebar li {
margin: 15px 0;
cursor: pointer;
padding: 10px;
border-radius: 5px;
transition: background-color 0.3s;
}
.sidebar li:hover {
background-color: #34495e;
}
.sidebar li.active {
background-color: #2980b9;
color: white;
}
/* Стили для кнопки "Выйти" */
.logout-button {
display: block;
margin-top: 20px;
padding: 10px;
width: 100%;
background-color: #e74c3c;
color: white;
border: none;
border-radius: 5px;
text-align: center;
cursor: pointer;
transition: background-color 0.3s;
}
.logout-button:hover {
background-color: #c0392b;
}
.logout-button:focus {
outline: none;
}

View File

@ -0,0 +1,49 @@
import React, { useState } from 'react';
import './Sidebar.css';
const Sidebar = ({ onSelectTab, activeTab, onLogout }) => {
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false);
const toggleSubMenu = () => {
setIsSubMenuOpen(!isSubMenuOpen);
// Переключение на вкладку "Аккаунт пользователя"
if (!isSubMenuOpen) {
onSelectTab('account');
}
};
return (
<div className="sidebar">
<h2>Меню</h2>
<ul>
<li className={activeTab === 'map' ? 'active' : ''} onClick={() => onSelectTab('map')}>
Главная
</li>
<li className={activeTab === 'connection' ? 'active' : ''} onClick={() => onSelectTab('connection')}>
Настройки
</li>
<li className={activeTab === 'connection' ? 'active' : ''} onClick={() => onSelectTab('connection')}>
Проведенные вычисления
</li>
<li className={activeTab === 'account' ? 'active' : ''} onClick={toggleSubMenu}>
Аккаунт пользователя
</li>
{isSubMenuOpen && (
<>
<li className={activeTab === 'groups' ? 'active' : ''} onClick={() => onSelectTab('groups')}>
Шаблоны
</li>
<li className={activeTab === 'devices' ? 'active' : ''} onClick={() => onSelectTab('devices')}>
Действия
</li>
</>
)}
</ul>
<button className="logout-button" onClick={onLogout}>
Выйти
</button>
</div>
);
};
export default Sidebar;

View File

@ -0,0 +1,87 @@
.user-account {
padding: 20px;
background-color: #f4f4f4;
border-radius: 10px;
max-width: 400px;
margin: 0 auto;
h1 {
text-align: center;
margin-bottom: 20px;
}
.user-info, .edit-form {
display: flex;
flex-direction: column;
gap: 10px;
label {
display: flex;
justify-content: space-between;
font-size: 16px;
}
input {
padding: 8px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 5px;
}
p {
font-size: 18px;
margin: 10px 0;
}
.buttons {
display: flex;
justify-content: space-between;
button {
padding: 10px 20px;
font-size: 14px;
cursor: pointer;
border: none;
border-radius: 5px;
background-color: #3498db;
color: white;
transition: background-color 0.3s ease;
&:hover {
background-color: #2980b9;
}
&:last-child {
background-color: #e74c3c;
&:hover {
background-color: #c0392b;
}
}
}
}
}
.edit-button {
margin-top: 20px;
padding: 10px 25px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
background-color: #2ecc71; /* Зелёный цвет */
color: white;
border: none;
border-radius: 5px;
transition: background-color 0.3s ease, transform 0.2s ease;
&:hover {
background-color: #27ae60;
transform: scale(1.05); /* Небольшой зум при наведении */
}
&:active {
background-color: #1e8449;
transform: scale(0.98); /* Легкий уменьшенный эффект при нажатии */
}
}
}

View File

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

17
src/frontend/src/index.js Normal file
View File

@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,83 @@
import React, { useState } from 'react';
import '../Connections.scss';
const Connections = () => {
const [webhooks, setWebhooks] = useState([]);
const [websocketUrl, setWebsocketUrl] = useState('');
const addWebhook = (newWebhook) => {
setWebhooks([...webhooks, newWebhook]);
};
const handleWebhookSubmit = (e) => {
e.preventDefault();
const newWebhook = {
url: e.target.webhookUrl.value,
groups: e.target.groups.value.split(','),
};
addWebhook(newWebhook);
e.target.reset();
};
return (
<div>
<h2>Подключения</h2>
{/* Webhook подключение */}
{/* <div className="section">
<h3>Подключения Webhooks</h3>
<form onSubmit={handleWebhookSubmit}>
<input type="text" name="webhookUrl" placeholder="URL Webhook" required />
<input type="text" name="groups" placeholder="Группы (через запятую)" required />
<button type="submit">Добавить Webhook</button>
</form>
<ul>
{webhooks.map((webhook, index) => (
<li key={index}>
Webhook: {webhook.url} (Группы: {webhook.groups.join(', ')})
</li>
))}
</ul>
</div> */}
{/* WebSocket подключение
<div className="section">
<h3>Подключения WebSockets</h3>
<form
onSubmit={(e) => {
e.preventDefault();
setWebsocketUrl(e.target.websocketUrl.value);
}}
>
<input type="text" name="websocketUrl" placeholder="URL WebSocket" required />
<button type="submit">Подключиться</button>
</form>
{websocketUrl && <p>Подключено по WebSocket: {websocketUrl}</p>}
</div> */}
{/* WebSocket подключение */}
<div className="section">
<h3>Настройки уведомлений</h3>
<form
onSubmit={(e) => {
e.preventDefault();
setWebsocketUrl(e.target.websocketUrl.value);
}}
>
<input type="checkbox" id='notification' name="websocketUrl" required />
<label for="notification">Уведомления о начале работы</label>
<hr/>
<input type="checkbox" id='notification' name="websocketUrl" required />
<label for="notification">Уведомления о готовности вычислений</label>
<hr/>
<button type="submit">Подключиться</button>
</form>
{websocketUrl && <p>Подключено по WebSocket: {websocketUrl}</p>}
</div>
</div>
);
};
export default Connections;

View File

@ -0,0 +1,264 @@
import React, { useEffect, useRef, useState } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader';
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
const Dashboard = () => {
const mountRef = useRef(null);
const [heightData, setHeightData] = useState([
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6],
[3, 4, 5, 6, 7],
[4, 5, 6, 7, 8],
[5, 6, 7, 8, 9],
]);
const [formData, setFormData] = useState({ x: '', y: '', height: '' });
const [mapSettings, setMapSettings] = useState({
maxHeight: 10,
coordX: 0,
coordY: 0,
});
useEffect(() => {
let localRef = null;
let width = mountRef.current.clientWidth;
let height = mountRef.current.clientHeight;
// Сцена
const scene = new THREE.Scene();
// Камера
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
camera.position.set(0, 20, 30);
// Рендерер
const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
mountRef.current.appendChild(renderer.domElement);
// Плоскость для высотной карты
const geometry = new THREE.PlaneGeometry(20, 20, 4, 4);
const vertices = geometry.attributes.position.array;
const colors = [];
const color = new THREE.Color();
// Находим минимальные и максимальные значения высот
const minHeight = Math.min(...heightData.flat());
const maxHeight = Math.max(mapSettings.maxHeight, ...heightData.flat());
for (let i = 0; i < vertices.length; i += 3) {
const x = Math.floor(i / 3) % 5;
const y = Math.floor(i / (3 * 5));
const heightValue = heightData[y][x];
vertices[i + 2] = heightValue;
const normalizedHeight = (heightValue - minHeight) / (maxHeight - minHeight);
color.setHSL(0.7 * (1 - normalizedHeight), 1, 0.5);
colors.push(color.r, color.g, color.b);
}
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
const material = new THREE.MeshBasicMaterial({
vertexColors: true,
side: THREE.DoubleSide,
wireframe: false,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.rotation.x = -Math.PI / 2;
scene.add(mesh);
// Оси координат X, Y и высота
const axesHelper = new THREE.AxesHelper(10);
scene.add(axesHelper);
// Линейка и числовые обозначения
const createRuler = (scene) => {
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x000000 });
// Ось X
const xPoints = [];
xPoints.push(new THREE.Vector3(0, 0.1, 0)); // Левый нижний угол
xPoints.push(new THREE.Vector3(20, 0.1, 0)); // Правая сторона
const xGeometry = new THREE.BufferGeometry().setFromPoints(xPoints);
const xLine = new THREE.Line(xGeometry, lineMaterial);
scene.add(xLine);
// Ось Y
const yPoints = [];
yPoints.push(new THREE.Vector3(0, 0.1, 0)); // Левый нижний угол
yPoints.push(new THREE.Vector3(0, 0.1, 20)); // Верхняя сторона
const yGeometry = new THREE.BufferGeometry().setFromPoints(yPoints);
const yLine = new THREE.Line(yGeometry, lineMaterial);
scene.add(yLine);
// Числовые обозначения
const fontLoader = new FontLoader();
fontLoader.load('https://threejs.org/examples/fonts/helvetiker_regular.typeface.json', (font) => {
// Числа на оси X
for (let i = 0; i <= 20; i += 5) {
const textGeometry = new TextGeometry(i.toString(), {
font: font,
size: 0.5,
height: 0.1,
});
const textMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
const textMesh = new THREE.Mesh(textGeometry, textMaterial);
textMesh.position.set(i, 0.1, -0.5);
scene.add(textMesh);
}
// Числа на оси Y
for (let i = 0; i <= 20; i += 5) {
const textGeometry = new TextGeometry(i.toString(), {
font: font,
size: 0.5,
height: 0.1,
});
const textMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
const textMesh = new THREE.Mesh(textGeometry, textMaterial);
textMesh.position.set(-0.5, 0.1, i);
scene.add(textMesh);
}
});
};
createRuler(scene);
// Настройка OrbitControls для взаимодействия с картой
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// Добавляем свет
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 50, 50).normalize();
scene.add(light);
const animate = () => {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
animate();
if (mountRef.current) localRef = mountRef.current;
return () => {
mountRef.current.removeChild(renderer.domElement);
};
}, [heightData, mapSettings]);
const handleInputChange = (event) => {
const { name, value } = event.target;
setFormData({
...formData,
[name]: value,
});
};
const handleAddHeight = (event) => {
event.preventDefault();
const { x, y, height } = formData;
if (x >= 0 && y >= 0 && x < heightData[0].length && y < heightData.length) {
const updatedData = [...heightData];
updatedData[y][x] = parseFloat(height);
setHeightData(updatedData);
setFormData({ x: '', y: '', height: '' });
} else {
alert('Координаты вне допустимого диапазона!');
}
};
const handleSettingsChange = (event) => {
const { name, value } = event.target;
setMapSettings({
...mapSettings,
[name]: value,
});
};
return (
<div style={{ flex: 1 }}>
<h1>Drone Network Simulator - окно разработки</h1>
{/* 3D Карта высот */}
<div ref={mountRef} style={{ width: '100%', height: '500px' }} />
{/* Форма для добавления новых высот */}
<form onSubmit={handleAddHeight} style={{ marginTop: '20px' }}>
<div>
<label>Координата X:</label>
<input
type="number"
name="x"
value={formData.x}
onChange={handleInputChange}
required
/>
</div>
<div>
<label>Координата Y:</label>
<input
type="number"
name="y"
value={formData.y}
onChange={handleInputChange}
required
/>
</div>
<div>
<label>Высота:</label>
<input
type="number"
name="height"
value={formData.height}
onChange={handleInputChange}
required
/>
</div>
<button type="submit">Добавить высоту</button>
</form>
{/* Форма для изменения настроек карты */}
<form style={{ marginTop: '20px' }}>
<div>
<label>Максимальная высота:</label>
<input
type="number"
name="maxHeight"
value={mapSettings.maxHeight}
onChange={handleSettingsChange}
required
/>
</div>
<div>
<label>Координата X (камера):</label>
<input
type="number"
name="coordX"
value={mapSettings.coordX}
onChange={handleSettingsChange}
required
/>
</div>
<div>
<label>Координата Y (камера):</label>
<input
type="number"
name="coordY"
value={mapSettings.coordY}
onChange={handleSettingsChange}
required
/>
</div>
</form>
</div>
);
};
export default Dashboard;

View File

@ -0,0 +1,87 @@
import React, { useState } from 'react';
import '../DeviceGroups.css';
const DeviceGroups = () => {
const [groups, setGroups] = useState([]);
const [newGroupName, setNewGroupName] = useState('');
const createGroup = () => {
if (newGroupName) {
const newGroup = {
name: newGroupName,
devices: [],
active: true,
};
setGroups([...groups, newGroup]);
setNewGroupName('');
}
};
const deleteGroup = (groupName) => {
setGroups(groups.filter(group => group.name !== groupName));
};
const toggleGroupStatus = (groupName) => {
setGroups(groups.map(group =>
group.name === groupName ? { ...group, active: !group.active } : group
));
};
const addDeviceToGroup = (groupName, device) => {
setGroups(groups.map(group =>
group.name === groupName ? { ...group, devices: [...group.devices, device] } : group
));
};
const removeDeviceFromGroup = (groupName, device) => {
setGroups(groups.map(group =>
group.name === groupName ? { ...group, devices: group.devices.filter(d => d !== device) } : group
));
};
return (
<div className="device-groups">
<h2>Группы устройств</h2>
<div className="group-creation">
<input
type="text"
value={newGroupName}
onChange={(e) => setNewGroupName(e.target.value)}
placeholder="Название новой группы"
/>
<button onClick={createGroup}>Создать группу</button>
</div>
<ul>
{groups.map(group => (
<li key={group.name} className={group.active ? 'active' : 'inactive'}>
<div>
<span>{group.name}</span>
<button onClick={() => deleteGroup(group.name)}>Удалить</button>
<button onClick={() => toggleGroupStatus(group.name)}>
{group.active ? 'Деактивировать' : 'Активировать'}
</button>
</div>
<div>
<h4>Устройства в группе:</h4>
<ul>
{group.devices.map((device, index) => (
<li key={index}>
{device} <button onClick={() => removeDeviceFromGroup(group.name, device)}>Удалить</button>
</li>
))}
</ul>
<button onClick={() => addDeviceToGroup(group.name, 'Новое устройство')}>
Добавить устройство
</button>
</div>
</li>
))}
</ul>
</div>
);
};
export default DeviceGroups;

View File

@ -0,0 +1,93 @@
import React, { useState } from 'react';
import { QRCodeCanvas } from "qrcode.react";
import '../Devices.css';
const Devices = () => {
const [devices, setDevices] = useState([]);
const [selectedDevice, setSelectedDevice] = useState(null);
const [deviceName, setDeviceName] = useState('');
const [deviceStatus, setDeviceStatus] = useState('');
const addDevice = () => {
if (deviceName) {
const newDevice = { name: deviceName, status: deviceStatus || 'Неактивен', id: Date.now() };
setDevices([...devices, newDevice]);
setDeviceName('');
setDeviceStatus('');
}
};
const removeDevice = () => {
if (selectedDevice) {
setDevices(devices.filter(device => device.id !== selectedDevice.id));
setSelectedDevice(null);
}
};
const editDevice = () => {
if (selectedDevice) {
const updatedDevices = devices.map(device =>
device.id === selectedDevice.id
? { ...device, name: deviceName, status: deviceStatus }
: device
);
setDevices(updatedDevices);
setSelectedDevice(null);
setDeviceName('');
setDeviceStatus('');
}
};
const generateQR = (device) => {
return (
<QRCodeCanvas value={JSON.stringify(device)} size={128} />
);
};
return (
<div className="devices">
<h2>Устройства</h2>
<input
type="text"
placeholder="Название устройства"
value={deviceName}
onChange={(e) => setDeviceName(e.target.value)}
/>
<input
type="text"
placeholder="Статус устройства"
value={deviceStatus}
onChange={(e) => setDeviceStatus(e.target.value)}
/>
<table>
<thead>
<tr>
<th>Название</th>
<th>Статус</th>
<th>QR Код</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
{devices.map((device) => (
<tr key={device.id} onClick={() => setSelectedDevice(device)}>
<td>{device.name}</td>
<td>{device.status}</td>
<td>{generateQR(device)}</td>
<td>
<button onClick={editDevice}>Редактировать</button>
</td>
</tr>
))}
</tbody>
</table>
<div className="buttons">
<button onClick={addDevice}>Добавить</button>
<button onClick={removeDevice} disabled={!selectedDevice}>Удалить выбранное</button>
<button onClick={editDevice} disabled={!selectedDevice}>Сохранить изменения</button>
</div>
</div>
);
};
export default Devices;

View File

@ -0,0 +1,52 @@
import React, { useState } from 'react';
import '../Login.scss';
const Login = ({ onLogin }) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
// Simple login/password check
if (username === 'admin' && password === 'admin') {
onLogin(); // Call function on successful login
} else {
setError('Неверный логин или пароль');
}
};
return (
<div className="login-container">
<div className="login-box"> {/* Added a wrapper for styling */}
<h2>Вход</h2>
{error && <p className="error">{error}</p>}
<form onSubmit={handleSubmit}>
<div className="input-group">
<label htmlFor="username">Логин</label>
<input
id="username"
type="text"
placeholder="Логин"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div className="input-group">
<label htmlFor="password">Пароль</label>
<input
id="password"
type="password"
placeholder="Пароль"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button type="submit">Войти</button>
</form>
</div>
</div>
);
};
export default Login;

View File

@ -0,0 +1,84 @@
import React, { useState } from 'react';
import '../UserAccount.scss';
const UserAccount = () => {
const [userData, setUserData] = useState({
username: 'Мамут Рахал',
email: 'yatupoidayn@mail.ru',
phone: '+666',
});
const [isEditing, setIsEditing] = useState(false);
const [formData, setFormData] = useState(userData);
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prevData) => ({
...prevData,
[name]: value,
}));
};
const handleSave = () => {
setUserData(formData);
setIsEditing(false);
};
const handleCancel = () => {
setFormData(userData);
setIsEditing(false);
};
return (
<div className="user-account">
<h1>Данные</h1>
{isEditing ? (
<div className="edit-form">
<label>
Имя пользователя:
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
/>
</label>
<label>
Никнейм авторизации:
<input
type="text"
name="email"
value={formData.email}
onChange={handleChange}
/>
</label>
<label>
Пароль:
<input
type="password"
name="phone"
value={formData.phone}
onChange={handleChange}
/>
</label>
<div className="buttons">
<button onClick={handleSave}>Сохранить</button>
<button onClick={handleCancel}>Отмена</button>
</div>
</div>
) : (
<div className="user-info">
<p><strong>Имя:</strong> {userData.username}</p>
<p><strong>Email:</strong> {userData.email}</p>
<p><strong>Телефон:</strong> {userData.phone}</p>
<button className="edit-button" onClick={() => setIsEditing(true)}>
Редактировать
</button>
</div>
)}
</div>
);
};
export default UserAccount;

View File

@ -0,0 +1,33 @@
// Инициализация WebSocket соединения
// const socket = io("ws://localhost:8080");
// useEffect(() => {
// // Получаем местоположение пользователя
// if (navigator.geolocation) {
// navigator.geolocation.getCurrentPosition(
// (position) => {
// const { latitude, longitude } = position.coords;
// setUserLocation([latitude, longitude]); // Устанавливаем координаты пользователя
// setLocationLoaded(true); // Флаг, что местоположение загружено
// },
// (error) => {
// console.error("Ошибка при получении геолокации:", error);
// setLocationLoaded(true); // Продолжаем даже при ошибке
// }
// );
// } else {
// console.error("Геолокация не поддерживается вашим браузером");
// setLocationLoaded(true); // Продолжаем даже если геолокация недоступна
// }
// // Получаем данные устройств в реальном времени
// socket.on("deviceLocationUpdate", (data) => {
// setDevices(data);
// });
// return () => {
// socket.off("deviceLocationUpdate");
// };
// }, []);

1
src/frontend/src/pages/gui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

12
src/server/Dockerfile Normal file
View File

@ -0,0 +1,12 @@
# Build stage
FROM golang:1.22.7-alpine3.20 AS builder
WORKDIR /var/server
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o main cmd/main.go
# Run stage
FROM alpine:3.20.3
COPY --from=builder /var/server/main /main
CMD ["./main"]

View File

@ -1,5 +1,18 @@
package main
func main() {
import (
"moxitech/dns/internal/server"
"os"
)
func main() {
D_EXPORT_VARS()
err := server.SpawnServer()
if err != nil {
panic(err)
}
}
func D_EXPORT_VARS() {
os.Setenv("SERVER_BASE_ADDRESS", "0.0.0.0:8080")
}

View File

@ -0,0 +1,7 @@
package database
type SystemUser struct {
Username string `json:"username"`
Password string `json:"password"`
IsAdmin bool `json:"is_admin"`
}

View File

@ -0,0 +1 @@
package dto

View File

@ -0,0 +1,47 @@
package dto
type Connection struct {
Id int `json:"room_id"`
User []User `json:"room_users"`
Map Map `json:"room_map"`
UAVs []UAV `json:"room_UAVs"`
BaseStations []BaseStation `json:"room_BSs"`
TsCreateRoom int64 `json:"room_ts"`
TsUpdateRoom int64 `json:"room_up_ts"`
}
type User struct {
Id int `json:"id"`
TimeActivate int64 `json:"ts"`
IsActive bool `json:"ia"`
IsCreator bool `json:"creator"`
}
type Map struct {
Name string `json:"name"`
Coordinates Coords `json:"coords"`
}
type UAV struct {
Id int64 `json:"id"`
Name string `json:"name"`
Checked Checked `json:"checked"`
Coordinates Coords `json:"coords"`
// TODO: направление, параметры и тд
}
type BaseStation struct {
Id int64 `json:"id"`
Name string `json:"name"`
Checked Checked `json:"checked"`
Coordinates Coords `json:"coords"`
// TODO: радиус, направление и тд
}
type Coords struct {
X int64 `json:"X"`
Y int64 `json:"Y"`
}
type Checked struct {
Initiator int `json:"initiator"`
Expire int64 `json:"expire_ts"` // устанавливает сервер
}

View File

@ -0,0 +1 @@
package dto

View File

@ -0,0 +1,6 @@
package math
type Drone struct {
X, Y float64
GainRx float64
}

View File

@ -0,0 +1,7 @@
package math
type Station struct {
X, Y float64
Frequency, GainTx float64
MaxLoss float64
}

View File

@ -3,16 +3,31 @@ module moxitech/dns
go 1.22.7
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/fasthttp/websocket v1.5.10 // indirect
github.com/gofiber/contrib/websocket v1.3.2 // indirect
github.com/gofiber/fiber/v2 v2.52.5 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.17.10 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/fasthttp v1.56.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
gorm.io/driver/postgres v1.5.9 // indirect
gorm.io/gorm v1.25.12 // indirect
)

View File

@ -1,11 +1,34 @@
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fasthttp/websocket v1.5.10 h1:bc7NIGyrg1L6sd5pRzCIbXpro54SZLEluZCu0rOpcN4=
github.com/fasthttp/websocket v1.5.10/go.mod h1:BwHeuXGWzCW1/BIKUKD3+qfCl+cTdsHu/f243NcAI/Q=
github.com/gofiber/contrib/websocket v1.3.2 h1:AUq5PYeKwK50s0nQrnluuINYeep1c4nRCJ0NWsV3cvg=
github.com/gofiber/contrib/websocket v1.3.2/go.mod h1:07u6QGMsvX+sx7iGNCl5xhzuUVArWwLQ3tBIH24i+S8=
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0=
github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@ -13,15 +36,44 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/fasthttp v1.56.0 h1:bEZdJev/6LCBlpdORfrLu/WOZXXxvrUQSiyniuaoW8U=
github.com/valyala/fasthttp v1.56.0/go.mod h1:sReBt3XZVnudxuLOx4J/fMrJVorWRiWY2koQKgABiVI=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

View File

@ -0,0 +1 @@
package database

View File

@ -0,0 +1,40 @@
package database
import (
"fmt"
"os"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type GormDbInstance struct {
DB *gorm.DB
}
// GiveMeConnectionString создает строку подключения к базе данных
func (db *GormDbInstance) GiveMeConnectionString() string {
str := fmt.Sprintf("postgresql://%s:%s@%s:%s/%s",
os.Getenv("POSTGRES_USER"),
os.Getenv("POSTGRES_PASSWORD"),
os.Getenv("POSTGRES_HOST"),
os.Getenv("POSTGRES_PORT"),
os.Getenv("POSTGRES_DB"),
)
return str
}
// NewDBConnection создает новое подключение к базе данных
func (db *GormDbInstance) NewDBConnection() (*gorm.DB, error) {
cs := db.GiveMeConnectionString()
dbInstance, err := gorm.Open(postgres.Open(cs), &gorm.Config{})
if err != nil {
return nil, fmt.Errorf("failed to open database connection: %w", err)
}
return dbInstance, nil
}
// AutoMigrate выполняет автоматическую миграцию
func (db *GormDbInstance) AutoMigrate(dbInstance *gorm.DB) error {
return dbInstance.AutoMigrate()
}

View File

@ -1,9 +1,15 @@
package server
func SpawnServer() {
}
func RunCollector() {
import (
"os"
"github.com/gofiber/fiber/v2"
)
func SpawnServer() error {
app := fiber.New()
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Welcome to Drone Network Simulator server-side API")
})
return app.Listen(os.Getenv("SERVER_BASE_ADDRESS"))
}

View File

@ -0,0 +1 @@
package server

View File

@ -0,0 +1,29 @@
package coverage
import (
"fmt"
"math"
model "moxitech/dns/entity/math"
loss "moxitech/dns/package/math"
)
// CalculateCoverage Расчет зон радиопокрытия
func CalculateCoverage(stations []model.Station, drones []model.Drone) {
for _, drone := range drones {
for _, station := range stations {
distance := CalculateDistance(drone.X, drone.Y, station.X, station.Y)
signalLoss := loss.FreeSpaceLossWithGain(station.Frequency, distance, station.GainTx, drone.GainRx)
if signalLoss < station.MaxLoss {
fmt.Printf("БПЛА в пределах зоны покрытия базовой станции: %.2f дБ\n", signalLoss)
} else {
fmt.Println("БПЛА вне зоны покрытия")
}
}
}
}
// Вычисление расстояния
func CalculateDistance(x1, y1, x2, y2 float64) float64 {
return math.Sqrt(math.Pow(x2-x1, 2) + math.Pow(y2-y1, 2))
}

View File

@ -0,0 +1,20 @@
package datarate
import (
"fmt"
"math"
)
// CalculateDataRate: Функция для расчёта скорости передачи данных в зависимости от SNR
func CalculateDataRate(snr float64) float64 {
// Формула Шеннона для пропускной способности канала
bandwidth := 20.0 // 20 МГц
return bandwidth * math.Log2(1+snr)
}
func Test() {
snr := 10.0 // отношение сигнал/шум в дБ
dataRate := CalculateDataRate(math.Pow(10, snr/10)) // SNR нужно преобразовать в линейную шкалу
fmt.Printf("Скорость передачи данных: %.2f Мбит/с\n", dataRate)
}

View File

@ -0,0 +1,23 @@
package loss
import "math"
// Функция расчета затухания в свободном пространстве
// Принимает частоту в МГц и расстояние в км
//
// frequency := 2400.0 // 2.4 ГГц
// distance := 10.0 // 10 км
func FreeSpaceLoss(frequencyMHz, distanceKm float64) float64 {
return 20*math.Log10(frequencyMHz) + 20*math.Log10(distanceKm) + 92.45
}
// Функция расчета затухания в свободном пространстве с усилением антенн
// Частота в МГц, расстояние в км, коэффициенты усиления передающей и приёмной антенны
// frequency := 2400.0 // 2.4 ГГц
// distance := 10.0 // 10 км
// gainTx := 10.0 // Усиление передающей антенны в дБ
// gainRx := 10.0 // Усиление приёмной антенны в дБ
func FreeSpaceLossWithGain(frequencyMHz, distanceKm, gainTx, gainRx float64) float64 {
loss := 20*math.Log10(frequencyMHz) + 20*math.Log10(distanceKm) + 92.45
return loss - gainTx - gainRx
}

View File

@ -0,0 +1,88 @@
package optimize
import (
"fmt"
"math/rand"
)
// Этот код оптимизирует размещение базовых станций,
// минимизируя расстояние между ними и целевой областью.
// TODO -> больше параметров для оптимизации, таких как покрытие сигнала
const populationSize = 50
const generations = 100
const mutationRate = 0.01
// Структура решения, представляющая координаты базовых станций
type StationCoordinate struct {
X, Y float64
}
// Оценка решения (чем меньше значение, тем лучше)
func Fitness(solution StationCoordinate) float64 {
// Например, минимизация расстояния между станцией и каким-то целевым объектом
return solution.X*solution.X + solution.Y*solution.Y
}
// Случайное решение
func RandomSolution() StationCoordinate {
return StationCoordinate{
X: rand.Float64() * 100, // случайное число от 0 до 100
Y: rand.Float64() * 100,
}
}
// Кроссовер (скрещивание)
func Crossover(a, b StationCoordinate) StationCoordinate {
return StationCoordinate{
X: (a.X + b.X) / 2,
Y: (a.Y + b.Y) / 2,
}
}
// Мутация решения
func Mutate(solution *StationCoordinate) {
if rand.Float64() < mutationRate {
solution.X += rand.NormFloat64() * 10
}
if rand.Float64() < mutationRate {
solution.Y += rand.NormFloat64() * 10
}
}
func Test() {
// Создаем начальную популяцию
population := make([]StationCoordinate, populationSize)
for i := range population {
population[i] = RandomSolution()
}
// Основной цикл алгоритма
for gen := 0; gen < generations; gen++ {
// Оценка популяции
bestFitness := Fitness(population[0])
bestSolution := population[0]
for _, solution := range population {
f := Fitness(solution)
if f < bestFitness {
bestFitness = f
bestSolution = solution
}
}
fmt.Printf("Поколение %d, Лучшее решение: (%.2f, %.2f) с оценкой %.2f\n",
gen, bestSolution.X, bestSolution.Y, bestFitness)
// Селекция и кроссовер
newPopulation := make([]StationCoordinate, populationSize)
for i := range newPopulation {
parent1 := population[rand.Intn(populationSize)]
parent2 := population[rand.Intn(populationSize)]
child := Crossover(parent1, parent2)
Mutate(&child)
newPopulation[i] = child
}
population = newPopulation
}
}

66
tech_reference.md Normal file
View File

@ -0,0 +1,66 @@
### Программное обеспечение для моделирования сети, обеспечивающая автономные перелёты БПЛА на территории РФ
#### Решение задачи на которое направлена разработка программного обеспечение является анализ возможности обеспеечение бесшовной связи:
1. Расчет зон радиопокрытия Красноярского края.
2. Выбор мест размещения базовых станций (БС) для беспилотных летательных аппаратов (БПЛА) с целью радиопокрытия заданной территории.
3. Расчет параметров радиоканала
#### ЗАДАНИЕ
Расчет зон радиопокрытия:
Это включает вычисление параметров радиоканала, таких как затухание сигнала, усиление антенн, отношение сигнал-шум, и другие.
Подход: Использовать модели распространения радиоволн (например, модель Хатага–Окамура или модель Лос-Анжелес). Можно использовать существующие библиотеки для расчетов затухания радиосигналов, таких как go-waves (если есть) или реализовать свою реализацию на основе теоретических моделей.
Шаги:
Получить данные о высоте местности и координатах наземных станций.
Рассчитать коэффициенты затухания сигнала для каждой базовой станции с учётом высоты, рельефа и других параметров.
Определение мест размещения базовых станций:
Это задача оптимизации. Нужно расставить станции таким образом, чтобы минимизировать количество станций и максимизировать покрытие, обеспечивая связь с каждым БПЛА.
Подход: Использовать генетические алгоритмы или метод имитации отжига для оптимального размещения станций на карте.
Шаги:
Создать модель для оценки качества покрытия от конкретного набора станций.
Прогонять алгоритм оптимизации, чтобы найти лучшее распределение станций по карте.
Расчет параметров радиоканала:
Для каждой связи между БПЛА и наземной станцией нужно рассчитать параметры радиоканала: уровень сигнала, скорость передачи данных, коэффициенты усиления антенн и т.д.
Подход: Применить формулу свободного пространства (Free-space path loss) для расчета затухания сигнала, а затем использовать полученные данные для оценки скорости передачи данных и других параметров.
Шаги:
Реализовать формулу расчета уровня сигнала с учетом дальности и характеристик антенн.
Рассчитать скорость передачи данных на основе уровня сигнала и модуляции.
Моделирование движения БПЛА:
Это динамическая задача, требующая отслеживания перемещений большого количества БПЛА в реальном времени и моделирования связи с базовыми станциями.
Подход: Использовать симулятор физических движений, чтобы моделировать движение БПЛА по заданным траекториям. Затем в реальном времени рассчитывать качество связи для каждого БПЛА на основе его положения.
Шаги:
Создать функцию для обновления положения каждого БПЛА в реальном времени.
На основе нового положения пересчитывать параметры радиосигнала и обновлять статус связи с ближайшей станцией.
Реализация транзитной передачи данных:
Если БПЛА не может напрямую связаться с базовой станцией, должна быть реализована возможность передачи данных через другие БПЛА.
Подход: Построить сеть связи по типу mesh с использованием алгоритмов маршрутизации. Для этого можно использовать стандартные алгоритмы поиска маршрутов (например, алгоритм Дейкстры или AODV).
Шаги:
Реализовать алгоритм поиска маршрутов между БПЛА.
При отсутствии связи с наземной станцией находить ближайший БПЛА, который может передать данные.
Отчёты по результатам моделирования:
После завершения моделирования необходимо выводить отчёт с анализом параметров радиоканала, временных интервалов связи/отсутствия связи и других данных.
Подход: Реализовать генерацию отчетов с использованием стандартных инструментов, таких как Go templates для создания HTML или текстовых отчетов.
###### MATH
Для реализации остальной математической логики, связанной с расчётом зон радиопокрытия, размещением базовых станций и параметров радиоканала, начнём по порядку. Мы разобьём задачу на несколько этапов:
Расчёт зон радиопокрытия с использованием затухания сигнала и коэффициентов усиления.
Оптимизация размещения базовых станций с помощью генетического алгоритма или другого метода оптимизации.
Расчёт параметров радиоканала (модуляция, скорость передачи данных).
Моделирование движения БПЛА с возможностью передачи данных через другие БПЛА.