Map in mongo & map loader & websocket update state & ctx state stablishment

This commit is contained in:
moxitech 2024-10-12 07:37:21 +07:00
parent 047a3ffd5c
commit 44e28d8e6b
21 changed files with 471 additions and 91 deletions

5
.env
View File

@ -14,4 +14,7 @@ POSTGRES_DB=moxitech
POSTGRES_USER=moxitech
POSTGRES_PASSWORD=moxitech
POSTGRES_PORT=5432
POSTGRES_HOST=postgres
POSTGRES_HOST=postgres
SOCKET_BASE_ADDRESS=9091
SOCKET_SERVICE_HOST=socket

7
DOCS.md Normal file
View File

@ -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

View File

@ -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

BIN
image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -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;

View File

@ -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;

View File

@ -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 = () => {
/>
</div>
</div>
<div className="mb-3">
<label>Параметры антенны:</label>
<div className="input-group mb-2">
<span className="input-group-text">Радиус антенны</span>
<input
type="number"
className="form-control"
value={formObjectData.antennaRadius}
name='antennaRadius'
onChange={handleAddInputChange}
/>
</div>
<div className="input-group mb-2">
<span className="input-group-text">Направление X</span>
<input
type="number"
className="form-control"
value={formObjectData.antennaDirection_x}
name='antennaDirection_x'
onChange={handleAddInputChange}
/>
</div>
<div className="input-group mb-2">
<span className="input-group-text">Направление Y</span>
<input
type="number"
className="form-control"
name='antennaDirection_y'
value={formObjectData.antennaDirection_y}
onChange={handleAddInputChange}
/>
</div>
<div className="input-group mb-2">
<span className="input-group-text">Направление Z</span>
<input
type="number"
className="form-control"
name='antennaDirection_z'
value={formObjectData.antennaDirection_z}
onChange={handleAddInputChange}
/>
</div>
<div className="input-group mb-2">
<span className="input-group-text">Модуляция</span>
<input
type="text"
className="form-control"
name='modulation'
value={formObjectData.modulation}
onChange={handleAddInputChange}
/>
</div>
<div className="input-group mb-2">
<span className="input-group-text">Пропускная способность</span>
<input
type="number"
className="form-control"
name='bandwidth'
value={formObjectData.bandwidth}
onChange={handleAddInputChange}
/>
</div>
<div className="input-group mb-2">
<span className="input-group-text">Скорость</span>
<input
type="number"
className="form-control"
name='dataRate'
value={formObjectData.dataRate}
onChange={handleAddInputChange}
/>
</div>
</div>
{objectType === 1 && (
<>
<div className="mb-3">
<label>Поле 1 для дрона:</label>
<label>Скорость перемещения:</label>
<input
type="text"
className="form-control"
name='droneField1'
value={formObjectData.droneField1}
name='speed'
value={formObjectData.speed}
onChange={handleAddInputChange}
/>
</div>
<div className="mb-3">
<label>Поле 2 для дрона:</label>
<label>Название Mesh сети:</label>
<input
type="text"
className="form-control"
name='droneField2'
value={formObjectData.droneField2}
name='meshName'
value={formObjectData.meshName}
onChange={handleAddInputChange}
/>
</div>
<div className="mb-3">
<label>Точки перемещения:</label>
<input
type="text"
className="form-control"
name='wayPoints'
value={formObjectData.wayPoints}
onChange={handleAddInputChange}
/>
</div>
</>
)}
{objectType === 2 && (
<>
<div className="mb-3">
<label>Поле 1 для базовой станции:</label>
<input
type="text"
className="form-control"
name='baseStationField1'
value={formObjectData.baseStationField1}
onChange={handleAddInputChange}
/>
</div>
<div className="mb-3">
<label>Поле 2 для базовой станции:</label>
<input
type="text"
className="form-control"
name='baseStationField2'
value={formObjectData.baseStationField2}
onChange={handleAddInputChange}
/>
</div>
</>
)}
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={closeAllModals}>Закрыть</button>
<button type="button" className="btn btn-primary" onClick={() => { alert(`Добавлено: ${JSON.stringify(formData)}`); }}>Добавить</button>
<button type="button" className="btn btn-primary" onClick={() => { modalAddDroneOrBaseStation(); }}>Добавить</button>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import '../DeviceGroups.css';
import '../css/DeviceGroups.css';
const DeviceGroups = () => {
const [groups, setGroups] = useState([]);

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { QRCodeCanvas } from "qrcode.react";
import '../Devices.css';
import '../css/Devices.css';
const Devices = () => {
return (

View File

@ -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");
// };
// }, []);

View File

@ -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();

View File

@ -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();

View File

@ -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
}

View File

@ -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")

View File

@ -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)

12
src/socket/Dockerfile Normal file
View File

@ -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"]

97
src/socket/cmd/main.go Normal file
View File

@ -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()
}
}

5
src/socket/go.mod Normal file
View File

@ -0,0 +1,5 @@
module moxitech/socket
go 1.22.7
require github.com/gorilla/websocket v1.5.3 // indirect

2
src/socket/go.sum Normal file
View File

@ -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=