mirror of
https://github.com/Rhinemann/IoT-Systems.git
synced 2026-03-14 20:50:39 +02:00
add: store template
This commit is contained in:
parent
1f92b43879
commit
abd6bf0abe
3
store/.gitignore
vendored
Normal file
3
store/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
venv
|
||||||
|
__pycache__
|
||||||
|
.idea
|
||||||
11
store/Dockerfile
Normal file
11
store/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Use the official Python image as the base image
|
||||||
|
FROM python:latest
|
||||||
|
# Set the working directory inside the container
|
||||||
|
WORKDIR /app
|
||||||
|
# Copy the requirements.txt file and install dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
# Copy the entire application into the container
|
||||||
|
COPY . .
|
||||||
|
# Run the main.py script inside the container when it starts
|
||||||
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]
|
||||||
27
store/README.md
Normal file
27
store/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Store
|
||||||
|
## Instructions for Starting the Project
|
||||||
|
To start the Store, follow these steps:
|
||||||
|
1. Clone the repository to your local machine:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Toolf/store.git
|
||||||
|
cd store
|
||||||
|
```
|
||||||
|
2. Create and activate a virtual environment (optional but recommended):
|
||||||
|
```bash
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate # On Windows, use: venv\Scripts\activate
|
||||||
|
```
|
||||||
|
3. Install the project dependencies:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
4. Run the system:
|
||||||
|
```bash
|
||||||
|
python ./main.py
|
||||||
|
```
|
||||||
|
## Common Commands
|
||||||
|
### 1. Saving Requirements
|
||||||
|
To save the project dependencies to the requirements.txt file:
|
||||||
|
```bash
|
||||||
|
pip freeze > requirements.txt
|
||||||
|
```
|
||||||
16
store/config.py
Normal file
16
store/config.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def try_parse(type, value: str):
|
||||||
|
try:
|
||||||
|
return type(value)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Configuration for POSTGRES
|
||||||
|
POSTGRES_HOST = os.environ.get("POSTGRES_HOST") or "localhost"
|
||||||
|
POSTGRES_PORT = try_parse(int, os.environ.get("POSTGRES_PORT")) or 5432
|
||||||
|
POSTGRES_USER = os.environ.get("POSTGRES_USER") or "user"
|
||||||
|
POSTGRES_PASSWORD = os.environ.get("POSTGRES_PASS") or "pass"
|
||||||
|
POSTGRES_DB = os.environ.get("POSTGRES_DB") or "test_db"
|
||||||
11
store/docker/db/structure.sql
Normal file
11
store/docker/db/structure.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE processed_agent_data (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
road_state VARCHAR(255) NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
x FLOAT,
|
||||||
|
y FLOAT,
|
||||||
|
z FLOAT,
|
||||||
|
latitude FLOAT,
|
||||||
|
longitude FLOAT,
|
||||||
|
timestamp TIMESTAMP
|
||||||
|
);
|
||||||
60
store/docker/docker-compose.yaml
Normal file
60
store/docker/docker-compose.yaml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
version: "3.9"
|
||||||
|
name: "road_vision__database"
|
||||||
|
services:
|
||||||
|
postgres_db:
|
||||||
|
image: postgres:latest
|
||||||
|
container_name: postgres_db
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: user
|
||||||
|
POSTGRES_PASSWORD: pass
|
||||||
|
POSTGRES_DB: test_db
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
- ./db/structure.sql:/docker-entrypoint-initdb.d/structure.sql
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
networks:
|
||||||
|
db_network:
|
||||||
|
|
||||||
|
|
||||||
|
pgadmin:
|
||||||
|
container_name: pgadmin4
|
||||||
|
image: dpage/pgadmin4
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
PGADMIN_DEFAULT_EMAIL: admin@admin.com
|
||||||
|
PGADMIN_DEFAULT_PASSWORD: root
|
||||||
|
volumes:
|
||||||
|
- pgadmin-data:/var/lib/pgadmin
|
||||||
|
ports:
|
||||||
|
- "5050:80"
|
||||||
|
networks:
|
||||||
|
db_network:
|
||||||
|
|
||||||
|
|
||||||
|
store:
|
||||||
|
container_name: store
|
||||||
|
build: ..
|
||||||
|
depends_on:
|
||||||
|
- postgres_db
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: user
|
||||||
|
POSTGRES_PASSWORD: pass
|
||||||
|
POSTGRES_DB: test_db
|
||||||
|
POSTGRES_HOST: postgres_db
|
||||||
|
POSTGRES_PORT: 5432
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
networks:
|
||||||
|
db_network:
|
||||||
|
|
||||||
|
|
||||||
|
networks:
|
||||||
|
db_network:
|
||||||
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
pgadmin-data:
|
||||||
170
store/main.py
Normal file
170
store/main.py
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from typing import Set, Dict, List, Any
|
||||||
|
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, Body
|
||||||
|
from sqlalchemy import (
|
||||||
|
create_engine,
|
||||||
|
MetaData,
|
||||||
|
Table,
|
||||||
|
Column,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
Float,
|
||||||
|
DateTime,
|
||||||
|
)
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from sqlalchemy.sql import select
|
||||||
|
from datetime import datetime
|
||||||
|
from pydantic import BaseModel, field_validator
|
||||||
|
from config import (
|
||||||
|
POSTGRES_HOST,
|
||||||
|
POSTGRES_PORT,
|
||||||
|
POSTGRES_DB,
|
||||||
|
POSTGRES_USER,
|
||||||
|
POSTGRES_PASSWORD,
|
||||||
|
)
|
||||||
|
|
||||||
|
# FastAPI app setup
|
||||||
|
app = FastAPI()
|
||||||
|
# SQLAlchemy setup
|
||||||
|
DATABASE_URL = f"postgresql+psycopg2://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}"
|
||||||
|
engine = create_engine(DATABASE_URL)
|
||||||
|
metadata = MetaData()
|
||||||
|
# Define the ProcessedAgentData table
|
||||||
|
processed_agent_data = Table(
|
||||||
|
"processed_agent_data",
|
||||||
|
metadata,
|
||||||
|
Column("id", Integer, primary_key=True, index=True),
|
||||||
|
Column("road_state", String),
|
||||||
|
Column("user_id", Integer),
|
||||||
|
Column("x", Float),
|
||||||
|
Column("y", Float),
|
||||||
|
Column("z", Float),
|
||||||
|
Column("latitude", Float),
|
||||||
|
Column("longitude", Float),
|
||||||
|
Column("timestamp", DateTime),
|
||||||
|
)
|
||||||
|
SessionLocal = sessionmaker(bind=engine)
|
||||||
|
|
||||||
|
|
||||||
|
# SQLAlchemy model
|
||||||
|
class ProcessedAgentDataInDB(BaseModel):
|
||||||
|
id: int
|
||||||
|
road_state: str
|
||||||
|
user_id: int
|
||||||
|
x: float
|
||||||
|
y: float
|
||||||
|
z: float
|
||||||
|
latitude: float
|
||||||
|
longitude: float
|
||||||
|
timestamp: datetime
|
||||||
|
|
||||||
|
|
||||||
|
# FastAPI models
|
||||||
|
class AccelerometerData(BaseModel):
|
||||||
|
x: float
|
||||||
|
y: float
|
||||||
|
z: float
|
||||||
|
|
||||||
|
|
||||||
|
class GpsData(BaseModel):
|
||||||
|
latitude: float
|
||||||
|
longitude: float
|
||||||
|
|
||||||
|
|
||||||
|
class AgentData(BaseModel):
|
||||||
|
user_id: int
|
||||||
|
accelerometer: AccelerometerData
|
||||||
|
gps: GpsData
|
||||||
|
timestamp: datetime
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@field_validator("timestamp", mode="before")
|
||||||
|
def check_timestamp(cls, value):
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
return value
|
||||||
|
try:
|
||||||
|
return datetime.fromisoformat(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
raise ValueError(
|
||||||
|
"Invalid timestamp format. Expected ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessedAgentData(BaseModel):
|
||||||
|
road_state: str
|
||||||
|
agent_data: AgentData
|
||||||
|
|
||||||
|
|
||||||
|
# WebSocket subscriptions
|
||||||
|
subscriptions: Dict[int, Set[WebSocket]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
# FastAPI WebSocket endpoint
|
||||||
|
@app.websocket("/ws/{user_id}")
|
||||||
|
async def websocket_endpoint(websocket: WebSocket, user_id: int):
|
||||||
|
await websocket.accept()
|
||||||
|
if user_id not in subscriptions:
|
||||||
|
subscriptions[user_id] = set()
|
||||||
|
subscriptions[user_id].add(websocket)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
await websocket.receive_text()
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
subscriptions[user_id].remove(websocket)
|
||||||
|
|
||||||
|
|
||||||
|
# Function to send data to subscribed users
|
||||||
|
async def send_data_to_subscribers(user_id: int, data):
|
||||||
|
if user_id in subscriptions:
|
||||||
|
for websocket in subscriptions[user_id]:
|
||||||
|
await websocket.send_json(json.dumps(data))
|
||||||
|
|
||||||
|
|
||||||
|
# FastAPI CRUDL endpoints
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/processed_agent_data/")
|
||||||
|
async def create_processed_agent_data(data: List[ProcessedAgentData]):
|
||||||
|
# Insert data to database
|
||||||
|
# Send data to subscribers
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@app.get(
|
||||||
|
"/processed_agent_data/{processed_agent_data_id}",
|
||||||
|
response_model=ProcessedAgentDataInDB,
|
||||||
|
)
|
||||||
|
def read_processed_agent_data(processed_agent_data_id: int):
|
||||||
|
# Get data by id
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/processed_agent_data/", response_model=list[ProcessedAgentDataInDB])
|
||||||
|
def list_processed_agent_data():
|
||||||
|
# Get list of data
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@app.put(
|
||||||
|
"/processed_agent_data/{processed_agent_data_id}",
|
||||||
|
response_model=ProcessedAgentDataInDB,
|
||||||
|
)
|
||||||
|
def update_processed_agent_data(processed_agent_data_id: int, data: ProcessedAgentData):
|
||||||
|
# Update data
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete(
|
||||||
|
"/processed_agent_data/{processed_agent_data_id}",
|
||||||
|
response_model=ProcessedAgentDataInDB,
|
||||||
|
)
|
||||||
|
def delete_processed_agent_data(processed_agent_data_id: int):
|
||||||
|
# Delete by id
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
uvicorn.run(app, host="127.0.0.1", port=8000)
|
||||||
BIN
store/requirements.txt
Normal file
BIN
store/requirements.txt
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user