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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Bogatyri/backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
.pytype/
.idea/
1 change: 1 addition & 0 deletions Bogatyri/backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

927 changes: 927 additions & 0 deletions Bogatyri/backend/poetry.lock

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions Bogatyri/backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[project]
name = "yadro_bacons"
version = "0.1.0"
description = ""
authors = [
{name = "Tony",email = "a.lukisha@g.nsu.ru"}
]
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi (>=0.118.0,<0.119.0)",
"uvicorn[standard] (>=0.37.0,<0.38.0)",
"websockets (>=15.0.1,<16.0.0)",
"numpy (>=2.3.3,<3.0.0)",
"scipy (>=1.16.2,<2.0.0)",
"mqtt (>=0.0.1,<0.0.2)",
"paho-mqtt (>=2.1.0,<3.0.0)",
]


[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
5 changes: 5 additions & 0 deletions Bogatyri/backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fastapi==0.118.0 ; python_version >= "3.13"
uvicorn==0.37.0 ; python_version >= "3.13"
websockets==15.0 ; python_version >= "3.13"
pydantic-core==2.33.2 ; python_version >= "3.13"
pydantic==2.11.9 ; python_version >= "3.13"
100 changes: 100 additions & 0 deletions Bogatyri/backend/src/controller_interaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import time
import bluetooth
import ubinascii
from micropython import const

_IRQ_SCAN_RESULT = const(5)
_IRQ_SCAN_DONE = const(6)

class BLEDeviceTracker:
def __init__(self, scan_duration=10000, update_interval=15):
self.bt = bluetooth.BLE()
self.bt.active(True)
self.bt.irq(self.bt_irq)

self.scan_duration = scan_duration # время сканирования в мс
self.update_interval = update_interval # время между циклами обновления в секундах

# Словарь для хранения устройств: ключ - MAC, значения - dict с именем, rssi, last_seen
self.devices = {}

self.scanning = False

# Добавить инициализацию mqtt-подключения

def decode_name(self, adv_data):
i = 0
name = None

while i < len(adv_data):
length = adv_data[i]
if length == 0:
break
if i + length >= len(adv_data):
break
ad_type = adv_data[i + 1]

if ad_type == 0x08 or ad_type == 0x09:
start = i + 2
end = start + length - 1
try:
name = bytes(adv_data[start:end]).decode('utf-8')
except Exception:
name = None
break
i += length + 1
return name

def bt_irq(self, event, data):
if event == _IRQ_SCAN_RESULT:
addr_type, addr, adv_type, rssi, adv_data = data
mac = ubinascii.hexlify(addr, ':').decode().upper()
name = self.decode_name(adv_data)
if name is None:
# Игнорируем устройства без имени
return
else:
# Добавляем новое устройство или обновляем существующее
self.devices[mac] = {
'name': name,
'rssi': rssi,
'last_seen': time.time()
}
elif event == _IRQ_SCAN_DONE:
self.scanning = False
print("Scan complete")

def start_scan(self):
self.devices.clear()
self.scanning = True
self.bt.gap_scan(self.scan_duration, 30000, 30000, True)

def print_devices(self):
print("Tracked devices:")
now = time.time()
for mac, dev in self.devices.items():
age = now - dev['last_seen']
if(dev['name'] != 'Unknown'):
print(f"Name: {dev['name']}, MAC: {mac}, RSSI: {dev['rssi']}, last seen {age:.1f} sec ago")
print("-----")

def publish_devices(self):
'''
То же самое, что print_devices, но данные шлются в топик mosquitto mqtt
'''
pass

def listener_broker(self):
pass

def run(self):
while True:
self.start_scan()
while self.scanning:
time.sleep_ms(100)
self.print_devices()
time.sleep(self.update_interval)


tracker = BLEDeviceTracker(scan_duration=10000, update_interval=1)
tracker.run()
150 changes: 150 additions & 0 deletions Bogatyri/backend/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import uvicorn
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import asyncio
import json
import random
import paho.mqtt.client as mqtt

from src.models.schemas import FrequencyRequest, BeaconRequest, Message

app = FastAPI(title="Wanderer API")

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)



class ConnectionManager:
def __init__(self):
self.active_connections = []

async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)

def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)

async def send_personal_message(self, message: str, websocket: WebSocket):
await websocket.send_text(message)

async def broadcast(self, message: str):
for connection in self.active_connections:
try:
await connection.send_text(message)
except:
self.disconnect(connection)


manager = ConnectionManager()


class WandererSimulator:
def __init__(self):
self.coords = {}
self.generate_coords()

def generate_coords(self):
self.coords = {"x": round(random.uniform(1, 9), 2), "y": round(random.uniform(1, 6), 2)}

def update_coords(self):
self.coords["x"] = round(max(0.5, min(9.5, self.coords["x"] + random.uniform(-3.3, 3.3))), 2)
self.coords["y"] = round(max(0.5, min(6.5, self.coords["y"] + random.uniform(-3.3, 3.3))), 2)
return self.coords


class WandererMQTTSubscriber:
def __init__(self):
self.broker = "localhost"
self.port = 1883
self.topic = "data/devices"
self.client = mqtt.Client()
self.client.on_message=self.on_message
self.client.connect(self.broker, self.port, 60)

self.devices_data = {}

def on_message(self, client, userdata, msg):

msg = Message(**msg.payload().decode())
self.devices_data = msg.data


def on_connect(client, userdata, flags, rc):
print("Подключено к брокеру, код возврата:", rc)
# client.subscribe(self.topic)

def update_coords(self):
self.coords["x"] = round(max(0.5, min(9.5, self.coords["x"] + random.uniform(-3.3, 3.3))), 2)
self.coords["y"] = round(max(0.5, min(6.5, self.coords["y"] + random.uniform(-3.3, 3.3))), 2)
return self.coords



wanderer_simulator = WandererSimulator()


@app.websocket("/ws/wanderer")
async def websocket_wanderer(websocket: WebSocket):
await manager.connect(websocket)
try:
await websocket.send_text(json.dumps(wanderer_simulator.coords))
print("Client connected")

while True:
coords_data = wanderer_simulator.update_coords()
await websocket.send_text(json.dumps(coords_data))
await asyncio.sleep(2)

except WebSocketDisconnect:
manager.disconnect(websocket)
print("Client disconnected")
except Exception as exception:
print(f"WebSocket error: {exception}")
manager.disconnect(websocket)

@app.post("/start")
async def start_route(request: FrequencyRequest):
try:
#await start_route(request.freq)
print(f"Starting success!")
return {"status": "success", "is_start": True}
except Exception as exception:
print(f"Starting error: {exception}")
return {"status": "error", "is_start": False}

@app.post("/beacons")
async def add_beacons(request: BeaconRequest):
try:
#await add_beacons(request.beacons)
print(f"Starting success!")
return {"status": "success"}
except Exception as exception:
print(f"Starting error: {exception}")
return {"status": "error"}

@app.post("/stop")
async def stop_route():
try:
#await stop_route()
print(f"Stopping success!")
return {"status": "success", "is_stop": True}
except Exception as exception:
print(f"Stopping error: {exception}")
return {"status": "error", "is_stop": False}

if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
reload=True,
ws="auto"
)
20 changes: 20 additions & 0 deletions Bogatyri/backend/src/models/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from pydantic import BaseModel

class FrequencyRequest(BaseModel):
freq: int

class BeaconConfig(BaseModel):
x: float
y: float
name: str

class BeaconRequest(BaseModel):
beacons: list[BeaconConfig]

class Message(BaseModel):
data: dict

class PositionResponse(BaseModel):
x: float
y: float
accuracy: float
Loading