Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
e737123
Initial commit. Renamed dir
litvidan Oct 1, 2025
61614de
init frontend
antonlukisha Oct 1, 2025
798f7de
main page
antonlukisha Oct 1, 2025
beb9b25
color fix
antonlukisha Oct 1, 2025
14fa959
Made device detection
litvidan Oct 2, 2025
21c8f20
Updated device detection
litvidan Oct 2, 2025
84abb10
Merge pull request #1 from litvidan/frontend/structure
antonlukisha Oct 2, 2025
b20b2ec
Merge branch 'device-detection' of github.com:litvidan/Bogatyri into …
antonlukisha Oct 2, 2025
ef130e6
add beacons
antonlukisha Oct 2, 2025
40cc021
bit fixes
antonlukisha Oct 2, 2025
6c3948e
Merge pull request #2 from litvidan/frontend/structure
antonlukisha Oct 2, 2025
4ce7c42
Sketches
georgiisafonkin Oct 2, 2025
7f2de06
add start/sto/change requests
antonlukisha Oct 2, 2025
1958661
wip: mqtt client and rest
georgiisafonkin Oct 2, 2025
a4fb9df
position estimator
antonlukisha Oct 2, 2025
7f285dc
resolve
antonlukisha Oct 2, 2025
2031a71
Merge pull request #3 from litvidan/frontend/structure
antonlukisha Oct 2, 2025
a910c59
mqtt rssi class
georgiisafonkin Oct 2, 2025
0141a26
structure fix
antonlukisha Oct 2, 2025
df14cfa
structure fix
antonlukisha Oct 2, 2025
5d8abdc
Merge pull request #4 from litvidan/frontend/structure
antonlukisha Oct 2, 2025
18c6b4a
structure fix
antonlukisha Oct 2, 2025
4965f2f
Added mqtt interation and monitor state
litvidan Oct 2, 2025
858fe91
Merge branch 'monitor' of github.com:litvidan/Bogatyri into monitor
antonlukisha Oct 2, 2025
7dcb705
dependence
antonlukisha Oct 2, 2025
a3ff085
Merge pull request #5 from litvidan/monitor
antonlukisha Oct 2, 2025
dfef867
fixes
georgiisafonkin Oct 2, 2025
249d4a2
fix
georgiisafonkin Oct 2, 2025
523fc91
fix
antonlukisha Oct 2, 2025
73c3918
Update README.md
litvidan Oct 2, 2025
177dd56
simulator removed
georgiisafonkin Oct 2, 2025
adf2fa7
Merge branch 'main' of github.com:litvidan/Bogatyri
georgiisafonkin Oct 2, 2025
4663dff
resolve
antonlukisha Oct 2, 2025
2000a9c
Merge branch 'main' into monitor
antonlukisha Oct 2, 2025
1421beb
Merge pull request #6 from litvidan/monitor
antonlukisha Oct 2, 2025
dfa8898
resolve
antonlukisha Oct 2, 2025
6574267
publish method added to subscriber
georgiisafonkin Oct 2, 2025
854cbab
hotfx
georgiisafonkin Oct 2, 2025
f436a39
resolve
antonlukisha Oct 2, 2025
5531e24
resolve
antonlukisha Oct 2, 2025
ddf329b
resolve
antonlukisha Oct 2, 2025
c19d311
docker settings
antonlukisha Oct 2, 2025
eee9550
fix docker
antonlukisha Oct 3, 2025
c9b11c8
fix docker
antonlukisha Oct 3, 2025
2558c46
Update README.md
litvidan Oct 3, 2025
48af631
Update README.md
litvidan Oct 3, 2025
088bc23
doc com
antonlukisha Oct 3, 2025
b0cc0c5
doc com
antonlukisha Oct 3, 2025
bd1ca9f
doc com
antonlukisha Oct 3, 2025
9986313
doc com
antonlukisha Oct 3, 2025
98bf96e
Merge branch 'hackyadro:main' into main
litvidan Oct 3, 2025
097d130
doc com
antonlukisha Oct 3, 2025
0ef9734
doc com
antonlukisha Oct 3, 2025
9ff45aa
doc com
antonlukisha Oct 3, 2025
06bb550
doc com
antonlukisha Oct 3, 2025
31eb587
doc com
antonlukisha Oct 3, 2025
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/
25 changes: 25 additions & 0 deletions Bogatyri/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM python:3.12-slim

WORKDIR /app

RUN apt-get update && apt-get install -y \
gcc \
usbutils \
usbip \
&& rm -rf /var/lib/apt/lists/*

COPY pyproject.toml poetry.lock* ./

RUN pip install poetry

RUN poetry config virtualenvs.create false

RUN touch README.md

RUN poetry install --no-root

COPY . .

EXPOSE 8000

CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
1 change: 1 addition & 0 deletions Bogatyri/backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

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

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions Bogatyri/backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[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)",
"mpremote (>=1.26.1,<2.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"
Empty file.
4 changes: 4 additions & 0 deletions Bogatyri/backend/src/domain/freq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from pydantic import BaseModel

class FrequencyRequest(BaseModel):
freq: int
6 changes: 6 additions & 0 deletions Bogatyri/backend/src/domain/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from pydantic import BaseModel

class Sensor(BaseModel):
name: str
rssi: int
is_running: bool = True
103 changes: 103 additions & 0 deletions Bogatyri/backend/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import uvicorn
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
import asyncio
import json
from src.services.RSSISubscriber import MqttSensorSubscriber
from src.models.schemas import FrequencyRequest, BeaconRequest
from src.services.monitor_state import MonitorState
from src.services.websocket_connection_manager import ConnectionManager
from src.utils.position_estimator import cords_estimator_from_rssi

app = FastAPI(title="Wanderer API")

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


manager = ConnectionManager()

# Создаём подписчика MQTT
mqtt_subscriber = MqttSensorSubscriber(
broker="mqtt",
port=1883,
topic="sensors/data"
)
monitor = MonitorState(mqtt_subscriber)

# ---- WebSocket ----

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

while True:
sensors_data = mqtt_subscriber.get_sensors()
if not len(sensors_data):
continue
beacons = monitor.get_beacons()
try:
x, y = cords_estimator_from_rssi([(beacons[s.name.strip('"\'')].x, beacons[s.name].y, s.rssi) for s in sensors_data])
except:
continue
response_data = {
"x": float(x),
"y": float(y)
}
await websocket.send_text(json.dumps(response_data, ensure_ascii=False))
await asyncio.sleep(1)

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


# ---- REST endpoints ----

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


@app.post("/beacons")
async def add_beacons(request: BeaconRequest):
try:
monitor.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:
monitor.stop_monitoring()
print(f"Stopping success!")
return {"status": "success", "is_stop": True}
except Exception as exception:
return {"status": "error", "is_stop": False}


if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
reload=True,
)
25 changes: 25 additions & 0 deletions Bogatyri/backend/src/models/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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

class Sensor(BaseModel):
name: str
rssi: int
is_running: bool = True
55 changes: 55 additions & 0 deletions Bogatyri/backend/src/services/RSSISubscriber.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import json
import threading
import paho.mqtt.client as mqtt

from src.domain.sensor import Sensor


class MqttSensorSubscriber:
def __init__(self, broker="mqtt", port=1883, topic="sensors/data"):
self.broker = broker
self.port = port
self.topic = topic
self.client = mqtt.Client()
self.client.on_connect = self.on_connect
self.client.on_message = self.on_message

self.sensors: dict[str, Sensor] = {}

# запуск в отдельном потоке
self.thread = threading.Thread(target=self._run, daemon=True)
self.thread.start()

def _run(self):
"""Внутренний метод для подключения и запуска цикла MQTT"""
self.client.connect(self.broker, self.port, 60)
self.client.loop_forever()

def on_connect(self, client, userdata, flags, rc):
print(f"[MQTT] Connected with result code {rc}")
client.subscribe(self.topic)

def on_message(self, client, userdata, msg):
try:
payload = msg.payload.decode("utf-8")
data = json.loads(payload)
sensor = Sensor(**data)
sensor.name = sensor.name.strip('"\'')
self.sensors[sensor.name] = sensor
print(f"[DATA] Received {sensor}")
except Exception as e:
print(f"[MQTT] Error parsing message: {e}")

def get_sensors(self):
"""Вернуть список сенсоров"""
return [s for s in self.sensors.values()]


def publish(self, topic: str, message: dict):
"""Отправка сообщения в топик"""
if isinstance(message, str):
payload = message
else:
payload = json.dumps(message, ensure_ascii=False)

self.client.publish(topic, payload)
Empty file.
90 changes: 90 additions & 0 deletions Bogatyri/backend/src/services/ble_device_tracker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
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):
self.bt = bluetooth.BLE()
self.bt.active(True)
self.bt.irq(self.bt_irq)

self.scan_duration = scan_duration
self.devices = {}

self.scanning = False

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
}
elif event == _IRQ_SCAN_DONE:
self.scanning = False

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

def print_devices(self):
now = time.time()
for mac, dev in self.devices.items():
if(dev['name'] != 'Unknown'):
print(f"\"{dev['name']}\" {-dev['rssi']}")
print("")

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

def read_params():
try:
with open('params.txt', 'r') as f:
val = f.read().strip()
return int(val)
except Exception:
return 1000

if __name__ == '__main__':
scan_duration = read_params()

tracker = BLEDeviceTracker(scan_duration=scan_duration)
tracker.run()
Loading