diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85e7c1d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea/ diff --git a/REST-API-cliente-mqtt-consultas/Dockerfile b/REST-API-cliente-mqtt-consultas/Dockerfile new file mode 100644 index 0000000..4048d0e --- /dev/null +++ b/REST-API-cliente-mqtt-consultas/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.11-slim +WORKDIR / + +COPY . . +COPY requirements.txt requirements.txt + +RUN pip install -r requirements.txt +RUN apt-get update && apt-get install -y tzdata + +EXPOSE 8000 \ No newline at end of file diff --git a/REST-API-cliente-mqtt-consultas/__pycache__/main.cpython-311.pyc b/REST-API-cliente-mqtt-consultas/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000..9ddd7ad Binary files /dev/null and b/REST-API-cliente-mqtt-consultas/__pycache__/main.cpython-311.pyc differ diff --git a/REST-API-cliente-mqtt-consultas/docker-compose.yaml b/REST-API-cliente-mqtt-consultas/docker-compose.yaml new file mode 100644 index 0000000..e3a41ac --- /dev/null +++ b/REST-API-cliente-mqtt-consultas/docker-compose.yaml @@ -0,0 +1,27 @@ +services: + mongodb: + image: mongo:latest + restart: always + ports: + - "27017:27017" + environment: + MONGO_INITDB_ROOT_USERNAME: admin + MONGO_INITDB_ROOT_PASSWORD: secret + TZ: America/Sao_Paulo + volumes: + - mongo_data:/data/db + web: + build: + dockerfile: Dockerfile + ports: + - "8000:8000" + environment: + - DB_URI=mongodb://admin:secret@mongodb:27017 + - DB_NAME=meu_banco + - COLLECTION_NAME=minha_colecao + command: uvicorn main:app --host 0.0.0.0 --port 8000 + depends_on: + - mongodb + +volumes: + mongo_data: \ No newline at end of file diff --git a/REST-API-cliente-mqtt-consultas/main.py b/REST-API-cliente-mqtt-consultas/main.py new file mode 100644 index 0000000..231e2e4 --- /dev/null +++ b/REST-API-cliente-mqtt-consultas/main.py @@ -0,0 +1,129 @@ +from repositories.MongoEnvironmentalDataRepository import MongoEnvironmentalDataRepository +from models.EnvironmentalData import EnvironmentalData +from datetime import datetime, time +from bson.json_util import dumps +import json +import os +from dotenv import load_dotenv +from contextlib import asynccontextmanager +import pytz +from fastapi import FastAPI, HTTPException + +from fastapi_mqtt import FastMQTT, MQTTConfig + +load_dotenv() + +uri = os.getenv("DB_URI") +db_name = os.getenv("DB_NAME") +collection_name = os.getenv("COLLECTION_NAME") +#taskkill /f /im python.exe + +mqtt_config = MQTTConfig( + host="lsdi.ufma.br", + port=1883, + keepalive=60 +) +fast_mqtt = FastMQTT(config=mqtt_config) + +@asynccontextmanager +async def _lifespan(_app: FastAPI): + await fast_mqtt.mqtt_startup() + yield + await fast_mqtt.mqtt_shutdown() + +app = FastAPI(lifespan=_lifespan) +repository = MongoEnvironmentalDataRepository(uri, db_name, collection_name) + +@fast_mqtt.on_connect() +def connect(client, flags, rc, properties): + client.subscribe("mhub/MHUB_SALAS/service_topic/HMSoft") # subscribing mqtt topic + print("Connected: ", client, flags, rc, properties) + +@app.get("/") +async def root(): + return "Server está funcionando" + +@app.get("/instante") +async def get_medicao_instante(data: datetime = datetime.now()) -> str: + if data.tzinfo is None: # Data sem fuso horario + sao_paulo = pytz.timezone("America/Sao_Paulo") # Assume que a data recebida é UTC-3 + data = sao_paulo.localize(data).astimezone(pytz.utc) # Converte pra UTC + else: + data = data.astimezone(pytz.utc) + + env_data_dict = await repository.get_by_instante(data) + if env_data_dict == dict(): + raise HTTPException(status_code=404, detail="Dados não encontrados") + + timestamp = env_data_dict["timestamp"] + + env_data_dict["timestamp"] = timestamp.astimezone( # Convertendo o dado de volta pra UTC-3 + pytz.timezone("America/Sao_Paulo") + ).isoformat() + + env_data_dict.pop("_id") # Manda o dado sem id + return dumps(env_data_dict) + +@app.get("/recente") +async def get_mais_recente() -> str: + env_data_dict = await repository.get_mais_recente() # Isso faz sentido? + if not env_data_dict: + raise HTTPException(status_code=404, detail="Dados não encontrados") + + timestamp = env_data_dict["timestamp"] + env_data_dict["timestamp"] = timestamp.astimezone( + pytz.timezone("America/Sao_Paulo") + ).isoformat() + + env_data_dict.pop("_id") + return dumps(env_data_dict) + +@app.get("/intervalo") +async def get_medicao_intervalo(inicio: datetime = datetime.combine(datetime.now(), time.min), + fim: datetime = datetime.combine(datetime.now(), time.max)) -> str: + if inicio.tzinfo is None: + sao_paulo = pytz.timezone("America/Sao_Paulo") + inicio = sao_paulo.localize(inicio).astimezone(pytz.utc) + else: + inicio = inicio.astimezone(pytz.utc) + + if fim.tzinfo is None: + sao_paulo = pytz.timezone("America/Sao_Paulo") + fim = sao_paulo.localize(fim).astimezone(pytz.utc) + else: + fim = fim.astimezone(pytz.utc) + + env_data_list = await repository.get_by_intervalo(inicio, fim) + if env_data_list == list(): + raise HTTPException(status_code=404, detail="Dados não encontrados") + + for env_data in env_data_list: + timestamp = env_data["timestamp"] + env_data["timestamp"] = timestamp.astimezone( # Convertendo de volta pra UTC-3 + pytz.timezone("America/Sao_Paulo") + ).isoformat() + + for env_data in env_data_list: # Removendo IDs + env_data.pop("_id") + + return dumps(env_data_list) + + +@fast_mqtt.on_message() +async def message(client, topic, payload, qos, properties): + msg = payload.decode() + print("Received message: ", topic, msg, qos, properties) + data = json.loads(msg) # Transformando pra dicionario + service_values = data.get("serviceValue", []) + + env_data = EnvironmentalData( + timestamp=datetime.now(pytz.utc), + temperatura=service_values[0], + umidade=service_values[1], + gas=service_values[2] + ) + await repository.insert_one(env_data) + +@fast_mqtt.on_disconnect() +def disconnect(client, packet, exc=None): + print("Disconnected") \ No newline at end of file diff --git a/SensorData.py b/REST-API-cliente-mqtt-consultas/models/EnvironmentalData.py similarity index 76% rename from SensorData.py rename to REST-API-cliente-mqtt-consultas/models/EnvironmentalData.py index 18e4e1f..31e9aac 100644 --- a/SensorData.py +++ b/REST-API-cliente-mqtt-consultas/models/EnvironmentalData.py @@ -1,12 +1,10 @@ -from pydantic import BaseModel -from datetime import datetime - - -class EnvironmentalData(BaseModel): - timestamp: datetime = datetime.now() - local: str | None = None - temperatura: float | None = None - umidade: float | None = None - gas: bool | None = None - luminosidade: float | None = None +from pydantic import BaseModel +from datetime import datetime + +class EnvironmentalData(BaseModel): + timestamp: datetime + temperatura: float | None = None + umidade: float | None = None + gas: bool | None = None + luminosidade: float | None = None ruido: float | None = None \ No newline at end of file diff --git a/REST-API-cliente-mqtt-consultas/models/__init__.py b/REST-API-cliente-mqtt-consultas/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/REST-API-cliente-mqtt-consultas/models/__pycache__/EnvironmentalData.cpython-311.pyc b/REST-API-cliente-mqtt-consultas/models/__pycache__/EnvironmentalData.cpython-311.pyc new file mode 100644 index 0000000..c43567d Binary files /dev/null and b/REST-API-cliente-mqtt-consultas/models/__pycache__/EnvironmentalData.cpython-311.pyc differ diff --git a/REST-API-cliente-mqtt-consultas/models/__pycache__/__init__.cpython-311.pyc b/REST-API-cliente-mqtt-consultas/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..1644fd1 Binary files /dev/null and b/REST-API-cliente-mqtt-consultas/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/REST-API-cliente-mqtt-consultas/repositories/EnvironmentalDataRepository.py b/REST-API-cliente-mqtt-consultas/repositories/EnvironmentalDataRepository.py new file mode 100644 index 0000000..b0fbc57 --- /dev/null +++ b/REST-API-cliente-mqtt-consultas/repositories/EnvironmentalDataRepository.py @@ -0,0 +1,22 @@ +from abc import ABC, abstractmethod +from datetime import datetime +from models.EnvironmentalData import EnvironmentalData + +# Classe abstrata de persistência e recuperação de dados do ambiente +class EnvironmentalDataRepository(ABC): + + @abstractmethod + def insert_one(self, data: EnvironmentalData) -> any: + pass + + @abstractmethod + def get_by_intervalo(self, inicio: datetime, fim: datetime) -> list[EnvironmentalData]: + pass + + @abstractmethod + def get_mais_recente(self) -> EnvironmentalData: + pass + + @abstractmethod + def get_by_instante(self, data: datetime) -> EnvironmentalData: + pass diff --git a/REST-API-cliente-mqtt-consultas/repositories/MongoEnvironmentalDataRepository.py b/REST-API-cliente-mqtt-consultas/repositories/MongoEnvironmentalDataRepository.py new file mode 100644 index 0000000..6a9f9e5 --- /dev/null +++ b/REST-API-cliente-mqtt-consultas/repositories/MongoEnvironmentalDataRepository.py @@ -0,0 +1,36 @@ +from repositories.EnvironmentalDataRepository import EnvironmentalDataRepository +from models.EnvironmentalData import EnvironmentalData +from datetime import datetime +from pymongo import AsyncMongoClient + +class MongoEnvironmentalDataRepository(EnvironmentalDataRepository): + def __init__(self, uri: str, db_name: str, collection_name: str) -> None: + self.client = AsyncMongoClient(uri, tz_aware=True) + self.db = self.client[db_name] + self.collection = self.db.get_collection(collection_name) + + async def insert_one(self, data: EnvironmentalData) -> str: + resp = await self.collection.insert_one(data.model_dump(exclude_none=True)) + return str(resp.inserted_id) + + async def get_by_intervalo(self, inicio: datetime, fim: datetime) -> list[dict]: + filtro = { + "timestamp": { + "$gte": inicio, + "$lte": fim + } + } + cursor = self.collection.find(filtro) + documentos = await cursor.to_list() + return documentos if documentos else list() + + async def get_by_instante(self, data: datetime) -> dict: + anterior = await self.collection.find_one( + {"timestamp": {"$lte": data}}, + sort=[("timestamp", -1)] + ) + return anterior if anterior else dict() + + async def get_mais_recente(self) -> dict: + doc = await self.collection.find_one(sort=[("timestamp", -1)]) + return doc if doc else dict() diff --git a/REST-API-cliente-mqtt-consultas/repositories/__init__.py b/REST-API-cliente-mqtt-consultas/repositories/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/REST-API-cliente-mqtt-consultas/repositories/__pycache__/EnvironmentalDataRepository.cpython-311.pyc b/REST-API-cliente-mqtt-consultas/repositories/__pycache__/EnvironmentalDataRepository.cpython-311.pyc new file mode 100644 index 0000000..b402a11 Binary files /dev/null and b/REST-API-cliente-mqtt-consultas/repositories/__pycache__/EnvironmentalDataRepository.cpython-311.pyc differ diff --git a/REST-API-cliente-mqtt-consultas/repositories/__pycache__/MongoEnvironmentalDataRepository.cpython-311.pyc b/REST-API-cliente-mqtt-consultas/repositories/__pycache__/MongoEnvironmentalDataRepository.cpython-311.pyc new file mode 100644 index 0000000..33789d7 Binary files /dev/null and b/REST-API-cliente-mqtt-consultas/repositories/__pycache__/MongoEnvironmentalDataRepository.cpython-311.pyc differ diff --git a/REST-API-cliente-mqtt-consultas/repositories/__pycache__/__init__.cpython-311.pyc b/REST-API-cliente-mqtt-consultas/repositories/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..37ee7f3 Binary files /dev/null and b/REST-API-cliente-mqtt-consultas/repositories/__pycache__/__init__.cpython-311.pyc differ diff --git a/REST-API-cliente-mqtt-consultas/requirements.txt b/REST-API-cliente-mqtt-consultas/requirements.txt new file mode 100644 index 0000000..f823caa Binary files /dev/null and b/REST-API-cliente-mqtt-consultas/requirements.txt differ diff --git a/db.py b/db.py deleted file mode 100644 index dce4cfe..0000000 --- a/db.py +++ /dev/null @@ -1,7 +0,0 @@ -from pymongo.mongo_client import MongoClient -from pymongo.server_api import ServerApi -import os - -uri = os.getenv("MONGODB_URI") -# Create a new client and connect to the server -client = MongoClient(uri, server_api=ServerApi('1')) \ No newline at end of file diff --git a/main.py b/main.py deleted file mode 100644 index 3f0c264..0000000 --- a/main.py +++ /dev/null @@ -1,92 +0,0 @@ -from fastapi import HTTPException - -from fastapi import FastAPI - -from db import uri, client -from datetime import datetime -from SensorData import EnvironmentalData -from bson.json_util import dumps -import json - -db = client["sensor_data"] -collection = db.get_collection("monitoramento_ambiental") -app = FastAPI() -from datetime import timedelta -#taskkill /f /im python.exe - -def query_media_dia(local, inicio, fim): - # Retorna um documento que tem a média da temperatura, umidade, luminosidade e ruído daquele dia específico - return [ - {"$match": { - "local": local, - "timestamp": {"$gte": inicio, "$lte": fim} - }}, - { - "$group": { - "_id": None, - "temp_avg": { - "$avg": "$temperatura" - }, - "umi_avg": { - "$avg": "$umidade" - }, - "lumi_avg": { - "$avg": "$luminosidade" - }, - "ruido_avg": { - "$avg": "$ruido" - } - } - } - ] - - - -def query_document_at_datetime(local: str, data: datetime): - INTERVALO = 2 - margem = timedelta(seconds=INTERVALO) - inicio = data - margem - fim = data + margem - - print(margem) - print(inicio) - print(fim) - # Retorna um documento daquele local que esteja dentro do intervalo inicio-fim - return { - "local": local, - "timestamp": { - "$gte": inicio, - "$lte": fim - } - } - - -@app.get("/media") -async def get_medicao_media_dia(local: str, data: datetime): - inicio = datetime.combine(data.date(), datetime.min.time()) - fim = datetime.combine(data.date(), datetime.max.time()) - result = json.loads(dumps(collection.aggregate(query_media_dia(local, inicio, fim)))) - - if result: - return result[0] - else: - raise HTTPException(status_code=404, detail="Medição não encontrada nessa data") - -@app.get("/instante") -async def get_medicao_instante(local: str, data: datetime): - result = collection.find_one(query_document_at_datetime(local, data)) - if result is None: - raise HTTPException(status_code=404, detail="Medição não encontrada nesse instante") - return json.loads(dumps(result)) - -@app.post("/novo") -async def insert(s: EnvironmentalData): - try: - resp = collection.insert_one(s.dict(exclude_none=True)) - return str(resp.inserted_id) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@app.get("/teste") -async def teste(): - print(uri) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e2078d3..0000000 --- a/requirements.txt +++ /dev/null @@ -1,16 +0,0 @@ -annotated-types==0.7.0 -anyio==4.9.0 -click==8.2.0 -colorama==0.4.6 -dnspython==2.7.0 -fastapi==0.115.12 -h11==0.16.0 -idna==3.10 -pydantic==2.11.4 -pydantic_core==2.33.2 -pymongo==4.13.0 -sniffio==1.3.1 -starlette==0.46.2 -typing-inspection==0.4.0 -typing_extensions==4.13.2 -uvicorn==0.34.2