diff --git a/.env b/.env
index e7ce3b1..6a27ebd 100644
--- a/.env
+++ b/.env
@@ -14,4 +14,7 @@ POSTGRES_DB=moxitech
POSTGRES_USER=moxitech
POSTGRES_PASSWORD=moxitech
POSTGRES_PORT=5432
-POSTGRES_HOST=postgres
\ No newline at end of file
+POSTGRES_HOST=postgres
+
+SOCKET_BASE_ADDRESS=9091
+SOCKET_SERVICE_HOST=socket
\ No newline at end of file
diff --git a/DOCS.md b/DOCS.md
new file mode 100644
index 0000000..c58909a
--- /dev/null
+++ b/DOCS.md
@@ -0,0 +1,7 @@
+
+
+SOCKET MESSAGING:
+ // PORT CHANGE IN .env
+ localhost:9091/storeEvent?client_id=1&message={"me": "update"}
+ // CONNECTION:
+ ws://localhost:9091/ws/1
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 45e9a18..95f6bee 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -64,6 +64,18 @@ services:
networks:
- dns_net
restart: always
+
+ socket:
+ container_name: dns-socket
+ build:
+ context: ./src/socket
+ dockerfile: Dockerfile
+ env_file: ".env"
+ ports:
+ - "${SOCKET_BASE_ADDRESS}:${SOCKET_BASE_ADDRESS}"
+ networks:
+ - dns_net
+ restart: always
front:
container_name: dns-ui
diff --git a/image.png b/image.png
new file mode 100644
index 0000000..3ad6737
Binary files /dev/null and b/image.png differ
diff --git a/src/frontend/src/Services/SocketMessaging/MessageHook.js b/src/frontend/src/Services/SocketMessaging/MessageHook.js
new file mode 100644
index 0000000..4dafeae
--- /dev/null
+++ b/src/frontend/src/Services/SocketMessaging/MessageHook.js
@@ -0,0 +1,49 @@
+import { useEffect, useState, useRef } from 'react';
+
+const useWebSocket = (userToken) => {
+ const [message, setMessage] = useState(null);
+ const [isConnected, setIsConnected] = useState(false);
+ const ws = useRef(null);
+
+ useEffect(() => {
+ if (!userToken) return;
+
+ const connectWebSocket = () => {
+ const socketUrl = `ws://${process.env.SOCKET_SERVICE_HOST}:${process.env.SOCKET_BASE_ADDRESS}/ws/${userToken}`;
+ ws.current = new WebSocket(socketUrl);
+
+ ws.current.onopen = () => {
+ console.log('WebSocket connected');
+ setIsConnected(true);
+ };
+
+ ws.current.onmessage = (event) => {
+ const data = event.data;
+ console.log('Message received:', data);
+ setMessage(data);
+ };
+
+ ws.current.onclose = () => {
+ console.log('WebSocket disconnected, attempting to reconnect...');
+ setIsConnected(false);
+ setTimeout(connectWebSocket, 3000); // Reconnect after 3 seconds
+ };
+
+ ws.current.onerror = (error) => {
+ console.error('WebSocket error:', error);
+ };
+ };
+
+ connectWebSocket();
+
+ return () => {
+ if (ws.current) {
+ ws.current.close();
+ }
+ };
+ }, [userToken]);
+
+ return { message, isConnected };
+};
+
+export default useWebSocket;
\ No newline at end of file
diff --git a/src/frontend/src/Services/WebsocketHook.js b/src/frontend/src/Services/WebsocketHook.js
index 66480bc..807cb93 100644
--- a/src/frontend/src/Services/WebsocketHook.js
+++ b/src/frontend/src/Services/WebsocketHook.js
@@ -100,7 +100,19 @@ const useWebsocketConnection = (userToken, roomHash) => {
return { websocketStruct, sendMessage };
};
-export const spawnJsonSignal = (signal, name, coords) => {
+export const spawnJsonSignal =
+ ( signal,
+ name,
+ coords,
+ antennaRadius,
+ antennaDirections="0,0,0",
+ modulation="",
+ bandwidth=0,
+ dataRate=0,
+ wayPoints=[],
+ speed=0,
+ meshName=null
+ ) => {
if (![1, 2].includes(signal)) {
throw new Error('Invalid signal value. Expected 1 or 2.');
}
@@ -115,11 +127,20 @@ export const spawnJsonSignal = (signal, name, coords) => {
name,
params: {
coords,
+ antennaRadius,
+ antennaDirections,
+ modulation,
+ bandwidth,
+ dataRate,
+ wayPoints,
+ speed,
+ meshName
},
},
};
};
+
export const DeleteSignal = (name) => {
const signal = 3;
diff --git a/src/frontend/src/DeviceGroups.css b/src/frontend/src/css/DeviceGroups.css
similarity index 100%
rename from src/frontend/src/DeviceGroups.css
rename to src/frontend/src/css/DeviceGroups.css
diff --git a/src/frontend/src/Devices.css b/src/frontend/src/css/Devices.css
similarity index 100%
rename from src/frontend/src/Devices.css
rename to src/frontend/src/css/Devices.css
diff --git a/src/frontend/src/pages/Dashboard.js b/src/frontend/src/pages/Dashboard.js
index e0676ec..a5f1cf2 100644
--- a/src/frontend/src/pages/Dashboard.js
+++ b/src/frontend/src/pages/Dashboard.js
@@ -28,14 +28,23 @@ const Dashboard = () => {
const [objectType, setObjectType] = useState(1); // 1 = Drone, 2 = Base Station
const [formObjectData, setFormObjectData] = useState({
name: "",
- type: 1,
- coordinates_x: 0,
- coordinates_y: 0,
- coordinates_z: 0,
- droneField1: "",
- droneField2: "",
- baseStationField1: "",
- baseStationField2: ""
+ type: 0, // 1 = Drone, 2 = Base Station
+ coordinates_x: 0, // Координата X на карте
+ coordinates_y: 0, // Координата Y на карте
+ coordinates_z: 0, // Координата Z на карте
+
+ antennaRadius: 0, // Радиус антенны
+ antennaDirection_x: 0,// Направление антенны по X
+ antennaDirection_y: 0,// Направление антенны по Y
+ antennaDirection_z: 0,// Направление антенны по Z
+
+ modulation: "", // Модуляция
+ bandwidth: 0, // Пропускная способность
+ dataRate: 0, // Скорость передачи данных
+ // DRONE FIELDS
+ wayPoints: 0, // Точки следования
+ speed: 0, // Скорость
+ meshName: "", // Название сети
});
const [searchTerm, setSearchTerm] = useState("");
const handleSearch = () => {
@@ -76,18 +85,13 @@ const Dashboard = () => {
alert("Объект с указанным именем не найден");
}
};
-
-
const handleTypeChange = (type) => {
setObjectType(type);
setFormData({ ...formData, type });
};
-
const handleDeleteSignal = (name) => {
sendMessage(DeleteSignal(name));
};
-
-
// Одно состояние для управления всеми модальными окнами
const [modals, setModals] = useState({
isModalOpen: false,
@@ -96,7 +100,6 @@ const Dashboard = () => {
isModalSearchOpen: false,
isModalMapOpen: false,
});
-
// Функция открытия модального окна в зависимости от переданной строки
const openModal = (modalName) => {
setModals((prevModals) => ({
@@ -104,7 +107,6 @@ const Dashboard = () => {
[modalName]: true,
}));
};
-
// Функция закрытия всех модальных окон
const closeAllModals = () => {
setModals({
@@ -114,7 +116,7 @@ const Dashboard = () => {
isModalSearchOpen: false,
});
};
-
+ // Обработка websocket
useEffect(() => {
if (websocketStruct && websocketStruct.modulation) {
const modulation = websocketStruct.modulation;
@@ -327,9 +329,34 @@ const containsObjectRecursively = (parent, target) => {
sceneRef.current.add(drone.getObject());
}
};
- // Добавление дрона в сцену
-// Добавление базовой станции в сцену
-const handleAddBaseStation = () => {
+ const modalAddDroneOrBaseStation = () => {
+ //formObjectData
+ alert(`Добавлено: ${JSON.stringify(formObjectData)}`);
+ if (sceneRef.current) {
+ if (objectType === 1) {
+ const drone = new Drone(formObjectData.name);
+ drone.setPosition(formObjectData.coordinates_x, formObjectData.coordinates_y, formObjectData.coordinates_z);
+ setDrones((prevDrones) => [...prevDrones, drone]);
+ let json_signal = spawnJsonSignal(
+ 1,
+ formObjectData.name,
+ `${formObjectData.coordinates_x},${formObjectData.coordinates_y},${formObjectData.coordinates_z}`,
+ formObjectData.antennaRadius,
+ `${formObjectData.antennaDirection_x},${formObjectData.antennaDirection_y},${formObjectData.antennaDirection_z}`,
+ formObjectData.modulation,
+ formObjectData.bandwidth,
+ formObjectData.dataRate,
+ formObjectData.wayPoints,
+ formObjectData.speed,
+ formObjectData.meshName
+ );
+ sendMessage(json_signal)
+ sceneRef.current.add(drone.getObject());
+ }
+ }
+ }
+ // Добавление базовой станции в сцену
+ const handleAddBaseStation = () => {
if (sceneRef.current) {
const current = Date.now();
const base = new BaseStation(current / 1000);
@@ -350,7 +377,7 @@ const handleAddBaseStation = () => {
console.log(baseStation.map(b => b.getObject().children[0]));
sceneRef.current.add(base.getObject());
}
-};
+ };
// Обработчик ввода на форму высот
const handleInputChange = (event) => {
@@ -497,60 +524,120 @@ const handleAddBaseStation = () => {
/>
+
{objectType === 1 && (
<>
-
+
-
+
+
+
+
+
>
)}
- {objectType === 2 && (
- <>
-
-
-
-
-
-
-
-
- >
- )}
-
+
diff --git a/src/frontend/src/pages/DeviceGroups.js b/src/frontend/src/pages/DeviceGroups.js
index 8c2e3a2..c8e268a 100644
--- a/src/frontend/src/pages/DeviceGroups.js
+++ b/src/frontend/src/pages/DeviceGroups.js
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
-import '../DeviceGroups.css';
+import '../css/DeviceGroups.css';
const DeviceGroups = () => {
const [groups, setGroups] = useState([]);
diff --git a/src/frontend/src/pages/Devices.js b/src/frontend/src/pages/Devices.js
index 346abe6..3b0f2aa 100644
--- a/src/frontend/src/pages/Devices.js
+++ b/src/frontend/src/pages/Devices.js
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { QRCodeCanvas } from "qrcode.react";
-import '../Devices.css';
+import '../css/Devices.css';
const Devices = () => {
return (
diff --git a/src/frontend/src/pages/ex.js b/src/frontend/src/pages/ex.js
deleted file mode 100644
index b4a1359..0000000
--- a/src/frontend/src/pages/ex.js
+++ /dev/null
@@ -1,33 +0,0 @@
-
-// Инициализация 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");
- // };
- // }, []);
\ No newline at end of file
diff --git a/src/frontend/src/pages/model/BaseStation.js b/src/frontend/src/pages/model/BaseStation.js
index 01e9f54..873faab 100644
--- a/src/frontend/src/pages/model/BaseStation.js
+++ b/src/frontend/src/pages/model/BaseStation.js
@@ -1,9 +1,26 @@
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
class BaseStation {
- constructor(name, scale = 10) {
+ constructor(
+ name,
+ scale = 10,
+ antennaRadius = 1,
+ antennaDirection_x=0,
+ antennaDirection_y=0,
+ antennaDirection_z=0,
+ modulation="QAM-16",
+ bandwidth=0,
+ dataRate=0,
+ ) {
this.object = new THREE.Object3D();
this.name = name;
+ this.antennaRadius = antennaRadius;
+ this.antennaDirection_x = antennaDirection_x;
+ this.antennaDirection_y = antennaDirection_y;
+ this.antennaDirection_z = antennaDirection_z;
+ this.modulation = modulation;
+ this.bandwidth = bandwidth;
+ this.dataRate = dataRate;
// Инициализируем GLTFLoader
const loader = new GLTFLoader();
diff --git a/src/frontend/src/pages/model/Drone.js b/src/frontend/src/pages/model/Drone.js
index 16d4b7d..82c54cf 100644
--- a/src/frontend/src/pages/model/Drone.js
+++ b/src/frontend/src/pages/model/Drone.js
@@ -1,10 +1,32 @@
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
class Drone {
- constructor(name, scale = 0.5) {
+ constructor(name,
+ antennaRadius = 1,
+ antennaDirection_x=0,
+ antennaDirection_y=0,
+ antennaDirection_z=0,
+ modulation="QAM-16",
+ bandwidth=0,
+ dataRate=0,
+ wayPoints=[],
+ speed=0,
+ meshName=null,
+ scale = 0.5)
+ {
this.object = new THREE.Object3D();
this.name = name;
+ this.antennaRadius = antennaRadius;
+ this.antennaDirection_x = antennaDirection_x;
+ this.antennaDirection_y = antennaDirection_y;
+ this.antennaDirection_z = antennaDirection_z;
+ this.modulation = modulation;
+ this.bandwidth = bandwidth;
+ this.dataRate = dataRate;
+ this.wayPoints = wayPoints
+ this.speed = speed;
+ this.meshName = meshName;
// Инициализируем GLTFLoader
const loader = new GLTFLoader();
diff --git a/src/server/internal/database/mongo.go b/src/server/internal/database/mongo.go
index dfe5c3d..f6cbba2 100644
--- a/src/server/internal/database/mongo.go
+++ b/src/server/internal/database/mongo.go
@@ -7,6 +7,7 @@ import (
"sync"
"time"
+ "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
@@ -102,3 +103,65 @@ func (db *MongoDbInstance) InsertIntoSimulationsHistory(data interface{}) (primi
return insertedID, nil
}
+
+// MapUpload saves a [][]float64 map into the map_templates collection
+func (db *MongoDbInstance) MapUpload(mapData [][]float64) (primitive.ObjectID, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ collection := db.Db.Collection("map_templates")
+
+ mapDocument := bson.M{"map_data": mapData, "created_at": time.Now()}
+
+ result, err := collection.InsertOne(ctx, mapDocument)
+ if err != nil {
+ log.Printf("Error inserting map into collection 'map_templates': %v", err)
+ return primitive.NilObjectID, err
+ }
+
+ insertedID := result.InsertedID.(primitive.ObjectID)
+ log.Printf("Map successfully inserted with ID: %v", insertedID)
+
+ return insertedID, nil
+}
+
+// MapLoader loads a [][]float64 map from the map_templates collection
+func (db *MongoDbInstance) MapLoader(mapID primitive.ObjectID) ([][]float64, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ collection := db.Db.Collection("map_templates")
+
+ var result bson.M
+ err := collection.FindOne(ctx, bson.M{"_id": mapID}).Decode(&result)
+ if err != nil {
+ log.Printf("Error loading map from collection 'map_templates': %v", err)
+ return nil, err
+ }
+
+ mapData, ok := result["map_data"].(primitive.A)
+ if !ok {
+ log.Printf("Error asserting map data type")
+ return nil, mongo.ErrNoDocuments
+ }
+
+ heightMap := make([][]float64, len(mapData))
+ for i, row := range mapData {
+ rowArray, ok := row.(primitive.A)
+ if !ok {
+ log.Printf("Error asserting row type")
+ return nil, mongo.ErrNoDocuments
+ }
+ heightMap[i] = make([]float64, len(rowArray))
+ for j, val := range rowArray {
+ floatVal, ok := val.(float64)
+ if !ok {
+ log.Printf("Error asserting value type at row %d, column %d", i, j)
+ return nil, mongo.ErrNoDocuments
+ }
+ heightMap[i][j] = floatVal
+ }
+ }
+
+ return heightMap, nil
+}
diff --git a/src/server/internal/database/postgres.go b/src/server/internal/database/postgres.go
index 8b02534..9118033 100644
--- a/src/server/internal/database/postgres.go
+++ b/src/server/internal/database/postgres.go
@@ -57,6 +57,11 @@ func (db *GormDbInstance) AutoMakeSuperUser() error {
db.DB.Create(&database.User{Username: "admin", Password: "admin", IsAdmin: true})
fmt.Println("Superuser has updated! :: \n Login: admin \n Password: admin")
}
+ db.DB.Where("username = ?", "user").First(&user)
+ if user.ID == 0 {
+ db.DB.Create(&database.User{Username: "user", Password: "user", IsAdmin: false})
+ fmt.Println("User has added! :: \n Login: user \n Password: user")
+ }
return nil
} else {
return fmt.Errorf("DB is nil poiner")
diff --git a/src/server/package/math/simulator/simulator.go b/src/server/package/math/simulator/simulator.go
index 4aa7b0a..91b9970 100644
--- a/src/server/package/math/simulator/simulator.go
+++ b/src/server/package/math/simulator/simulator.go
@@ -1,10 +1,14 @@
package simulator
import (
+ "log"
"math"
"math/rand"
+ "moxitech/dns/internal/database"
"moxitech/dns/package/math/distance"
"sync"
+
+ "go.mongodb.org/mongo-driver/bson/primitive"
)
type Map struct {
@@ -246,11 +250,18 @@ func CalculateDataRate(modulation string, bandwidth float64) float64 {
// MakeExampleMap создает статическую карту
func MakeExampleMap() Map {
+ mapID, err := primitive.ObjectIDFromHex("6709c34cdeb5f8e46f450c9f")
+ if err != nil {
+ log.Fatalf("Ошибка при преобразовании строки в ObjectID: %v", err)
+ }
+
+ database.Instance.MapLoader(mapID)
heightData := [][]float64{
{0, 10, 15, 20},
{5, 15, 25, 30},
{10, 20, 35, 40},
{15, 25, 40, 50},
+ {20, 30, 25, 50},
}
// Определяем карту высот
@@ -263,7 +274,7 @@ func MakeExampleMap() Map {
return mapObj
}
-// Создает полностью случаную карту
+// Создает полностью случайную карту
func MakeRandomMap() Map {
// Задаём случайное количество высот
numRows := rand.Intn(50) + 1 // Случайное количество строк (от 1 до 50)
diff --git a/src/socket/Dockerfile b/src/socket/Dockerfile
new file mode 100644
index 0000000..041f1dd
--- /dev/null
+++ b/src/socket/Dockerfile
@@ -0,0 +1,12 @@
+# Build stage
+FROM golang:1.22.7-alpine3.20 AS builder
+WORKDIR /var/socket
+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/socket/main /main
+CMD ["./main"]
diff --git a/src/socket/cmd/main.go b/src/socket/cmd/main.go
new file mode 100644
index 0000000..dcd1ca2
--- /dev/null
+++ b/src/socket/cmd/main.go
@@ -0,0 +1,97 @@
+package main
+
+import (
+ "fmt"
+ "net/http"
+ "os"
+ "sync"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+var (
+ clients = make(map[int]*websocket.Conn) // Список активных клиентов
+ events = make(map[int]string) // Локальная карта для хранения сообщений
+ mutex sync.Mutex // Мьютекс для управления конкурентным доступом к карте
+ upgrader = websocket.Upgrader{
+ CheckOrigin: func(r *http.Request) bool { return true },
+ }
+)
+
+func main() {
+ http.HandleFunc("/storeEvent", handleStoreEvent)
+ http.HandleFunc("/ws/", handleWebSocket)
+
+ // Запуск горутины для обработки сообщений каждую секунду
+ go processEvents()
+ fmt.Printf("Server started on %v \n", os.Getenv("SOCKET_BASE_ADDRESS"))
+ http.ListenAndServe(fmt.Sprintf("0.0.0.0:%v", os.Getenv("SOCKET_BASE_ADDRESS")), nil)
+}
+
+// Обработчик для сохранения событий
+func handleStoreEvent(w http.ResponseWriter, r *http.Request) {
+ clientID := r.URL.Query().Get("client_id")
+ message := r.URL.Query().Get("message")
+
+ if clientID == "" || message == "" {
+ http.Error(w, "client_id and message are required", http.StatusBadRequest)
+ return
+ }
+
+ var id int
+ _, err := fmt.Sscanf(clientID, "%d", &id)
+ if err != nil {
+ http.Error(w, "Invalid client_id", http.StatusBadRequest)
+ return
+ }
+
+ mutex.Lock()
+ events[id] = message
+ mutex.Unlock()
+
+ w.WriteHeader(http.StatusOK)
+}
+
+// Обработчик WebSocket соединений
+func handleWebSocket(w http.ResponseWriter, r *http.Request) {
+ clientIDStr := r.URL.Path[len("/ws/"):]
+ var clientID int
+ _, err := fmt.Sscanf(clientIDStr, "%d", &clientID)
+ if err != nil {
+ http.Error(w, "Invalid client ID", http.StatusBadRequest)
+ return
+ }
+
+ conn, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ http.Error(w, "Failed to upgrade to WebSocket", http.StatusInternalServerError)
+ return
+ }
+
+ mutex.Lock()
+ clients[clientID] = conn
+ mutex.Unlock()
+}
+
+// Функция для обработки событий и отправки сообщений подключенным клиентам
+func processEvents() {
+ for {
+ time.Sleep(1 * time.Second)
+
+ mutex.Lock()
+ for id, message := range events {
+ if conn, ok := clients[id]; ok {
+ err := conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("Client %d: %s", id, message)))
+ if err != nil {
+ fmt.Printf("Error sending message to client %d: %v\n", id, err)
+ conn.Close()
+ delete(clients, id)
+ }
+ // Удаляем событие после отправки
+ delete(events, id)
+ }
+ }
+ mutex.Unlock()
+ }
+}
diff --git a/src/socket/go.mod b/src/socket/go.mod
new file mode 100644
index 0000000..b5e83fb
--- /dev/null
+++ b/src/socket/go.mod
@@ -0,0 +1,5 @@
+module moxitech/socket
+
+go 1.22.7
+
+require github.com/gorilla/websocket v1.5.3 // indirect
diff --git a/src/socket/go.sum b/src/socket/go.sum
new file mode 100644
index 0000000..25a9fc4
--- /dev/null
+++ b/src/socket/go.sum
@@ -0,0 +1,2 @@
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=