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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.idea/
9 changes: 9 additions & 0 deletions REST-API-cliente-mqtt-consultas/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3.11-slim
WORKDIR /

COPY . .
COPY requirements.txt requirements.txt

RUN pip install -r requirements.txt; apt-get update && apt-get install -y tzdata

EXPOSE 8000
Binary file not shown.
44 changes: 44 additions & 0 deletions REST-API-cliente-mqtt-consultas/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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

broker:
image: eclipse-mosquitto:latest
command: mosquitto -c /mosquitto/config/mosquitto.conf -v
volumes:
- ./mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf

publisher:
image: python:latest
volumes:
- ./:/usr/src/app
working_dir: /usr/src/app

command: bash -c "pip install -r requirements.txt; python publisher.py"

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


volumes:
mongo_data:
155 changes: 155 additions & 0 deletions REST-API-cliente-mqtt-consultas/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
from repositories.MongoEnvironmentalDataRepository import MongoEnvironmentalDataRepository
from models.EnvironmentalData import EnvironmentalData
from datetime import datetime, time
import time as delay
from bson.json_util import dumps
import threading
import json
import sys
import os
from dotenv import load_dotenv
from contextlib import asynccontextmanager
import pytz
from fastapi import FastAPI, HTTPException
import paho.mqtt.client as mqtt

import logging

# Configura o logging global
logging.basicConfig(level=logging.INFO)

load_dotenv()

uri = os.getenv("DB_URI")
db_name = os.getenv("DB_NAME")
collection_name = os.getenv("COLLECTION_NAME")
mouuid_ets = "D4:36:39:DB:25:34"
mouuid_reunioes = "D4:36:39:DB:34:68"
#taskkill /f /im python.exe

host = "broker"
port = 1883
topic = "mhub/MHUB_SALAS/service_topic/HMSoft"
cliente = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)

def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
logging.info(f"Failed to connect: {reason_code}. loop_forever() will retry connection")
else:
logging.info("Connected to broker")
client.subscribe("mhub/MHUB_SALAS/service_topic/HMSoft") # subscribing mqtt topic

def on_disconnect(client, userdata, flags, reason_code, properties):
if reason_code != 0:
logging.info(f"Unexpected MQTT Broker disconnection! Reason code: {reason_code}")
logging.info(f"Trying to reconnect")

def on_log(client, userdata, level, buf):
logging.info(f"{buf}----SYSTEM MESSAGE----")

def on_message(client, userdata, message: mqtt.MQTTMessage):
msg = message.payload.decode()
data = json.loads(msg) # Transformando pra dicionario
service_values = data.get("serviceValue", [])
local = "ets" if data.get("mouuid") == mouuid_ets else "reunioes"
env_data = EnvironmentalData(
local=local,
timestamp=datetime.now(pytz.utc),
temperatura=service_values[0],
umidade=service_values[1],
gas=service_values[2]
)
repository.insert_one(env_data)

cliente.on_connect = on_connect
cliente.on_log = on_log
cliente.on_disconnect = on_disconnect
cliente.on_message = on_message

app = FastAPI()
repository = MongoEnvironmentalDataRepository(uri, db_name, collection_name)

def start_mqtt():
while True:
try:
cliente.connect(host)
break
except Exception:
logging.info("Failed to connect to broker")
logging.info("Trying again")
delay.sleep(5)
cliente.loop_forever() # Isso bloqueia, por isso usamos em uma thread

@app.on_event("startup")
def startup_event():
logging.info("Iniciando MQTT em thread separada...")
threading.Thread(target=start_mqtt, daemon=True).start()

@app.get("/")
async def root():
return "Server está funcionando"

@app.get("/instante")
async def get_medicao_instante(local:str, 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 = repository.get_by_instante(local, data)
if env_data_dict == dict():
raise HTTPException(status_code=404, detail="Dados não encontrados")
print(env_data_dict)
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(local: str) -> str:
env_data_dict = repository.get_mais_recente(local)
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(local:str, 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 = repository.get_by_intervalo(local, 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)
21 changes: 10 additions & 11 deletions SensorData.py → ...qtt-consultas/models/EnvironmentalData.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
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):
local: str
timestamp: datetime
temperatura: float | None = None
umidade: float | None = None
gas: bool | None = None
luminosidade: float | None = None
ruido: float | None = None
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
listener 1883 0.0.0.0
allow_anonymous true
92 changes: 92 additions & 0 deletions REST-API-cliente-mqtt-consultas/publisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import json
import random
import logging
import paho.mqtt.client as mqtt
import time
broker = "broker"
topic = "mhub/MHUB_SALAS/service_topic/HMSoft"
logging.basicConfig(level=logging.INFO)

def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
logging.info(f"Failed to connect: {reason_code}. loop_forever() will retry connection")
else:
logging.info("Connected to broker")

def publish():
try:
while True:
msg = {"className": "br.ufma.lsdi.cddl.message.Message",
"delivered": False,
"deliveredFailed": False,
"measurementTime": 1753982313077,
"qocEvaluated": False,
"serviceByteArray": [-84, -19, 0, 5, 117, 114, 0, 19, 91, 76, 106, 97, 118, 97, 46, 108, 97, 110,
103, 46, 79, 98, 106, 101, 99, 116, 59, -112, -50, 88, -97, 16, 115, 41, 108, 2,
0, 0, 120, 112, 0, 0, 0, 5, 115, 114, 0, 16, 106, 97, 118, 97, 46, 108, 97, 110,
103, 46, 68, 111, 117, 98, 108, 101, -128, -77, -62, 74, 41, 107, -5, 4, 2, 0,
1, 68, 0, 5, 118, 97, 108, 117, 101, 120, 114, 0, 16, 106, 97, 118, 97, 46, 108,
97, 110, 103, 46, 78, 117, 109, 98, 101, 114, -122, -84, -107, 29, 11, -108,
-32, -117, 2, 0, 0, 120, 112, 64, 55, -77, 51, 51, 51, 51, 51, 115, 113, 0, 126,
0, 2, 64, 75, -128, 0, 0, 0, 0, 0, 115, 113, 0, 126, 0, 2, 0, 0, 0, 0, 0, 0, 0,
0, 115, 113, 0, 126, 0, 2, 64, 98, -64, 0, 0, 0, 0, 0, 115, 113, 0, 126, 0, 2,
-65, -16, 0, 0, 0, 0, 0, 0],
"serviceName": "HMSoft",
"topic": "mhub/MHUB_SALAS/service_topic/HMSoft",
"uuid": "15766b43-4e30-48ca-9e1a-c2cb0810a9fb",
"dup": False,
"messageId": 0, "mutable": True, "payload": [], "qos": 1, "retained": False}

ets_mouuid = "D4:36:39:DB:25:34"
reunioes_mouuid = "D4:36:39:DB:34:68"
msg["mouuid"] = random.choice((ets_mouuid, reunioes_mouuid))
msg["serviceValue"] = [random.uniform(25, 30), random.uniform(50, 60), random.choice((0, 1)),
random.uniform(100, 150)]
cliente.publish(topic, json.dumps(msg), qos=1)
time.sleep(10)
finally:
cliente.loop_stop()
cliente.disconnect()

def on_disconnect(client, userdata, flags, reason_code, properties):
if reason_code != 0:
logging.info(f"Unexpected MQTT Broker disconnection! Reason code: {reason_code}")
logging.info(f"Trying to reconnect")
# Implement reconnection logic here
'''interval = 1
while True:
try:
cliente.reconnect()
break
except Exception:
print("Reconnect failed")
print(f"Trying again in {interval} seconds")
time.sleep(interval)
interval *= 2'''

else:
logging.info("Disconnected from MQTT Broker gracefully.")

def on_publish(client, userdata, mid, reason_code, properties):
logging.info(f"message {mid} sent successfully")

def on_log(client, userdata, level, buf):
logging.info(f"{buf}----SYSTEM MESSAGE----")

cliente = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
cliente.on_connect = on_connect
cliente.on_disconnect = on_disconnect
cliente.on_publish = on_publish
cliente.on_log = on_log
#cliente.reconnect_delay_set(min_delay=5, max_delay=5)

while True:
try:
cliente.connect(broker)
break
except Exception:
logging.info("Failed to connect to broker")
time.sleep(5)

cliente.loop_start()
publish()
Original file line number Diff line number Diff line change
@@ -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, local, inicio: datetime, fim: datetime) -> list[EnvironmentalData]:
pass

@abstractmethod
def get_mais_recente(self, local) -> EnvironmentalData:
pass

@abstractmethod
def get_by_instante(self,local, data: datetime) -> EnvironmentalData:
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from repositories.EnvironmentalDataRepository import EnvironmentalDataRepository
from models.EnvironmentalData import EnvironmentalData
from datetime import datetime
from pymongo import MongoClient

class MongoEnvironmentalDataRepository(EnvironmentalDataRepository):
def __init__(self, uri: str, db_name: str, collection_name: str) -> None:
self.client = MongoClient(uri, tz_aware=True)
self.db = self.client[db_name]
self.collection = self.db.get_collection(collection_name)

def insert_one(self, data: EnvironmentalData) -> str:
resp = self.collection.insert_one(data.model_dump(exclude_none=True))
return str(resp.inserted_id)

def get_by_intervalo(self, local: str, inicio: datetime, fim: datetime) -> list[dict]:
filtro = {
"timestamp": {
"$gte": inicio,
"$lte": fim
},
"local": local
}
cursor = self.collection.find(filtro)
documentos = cursor.to_list()
return documentos if documentos else list()

def get_by_instante(self, local: str, data: datetime) -> dict:
anterior = self.collection.find_one(
{"timestamp": {"$lte": data}, "local":local},

sort=[("timestamp", -1)]
)
return anterior if anterior else dict()

def get_mais_recente(self, local: str) -> dict:
doc = self.collection.find_one({"local":local}, sort=[("timestamp", -1)])
return doc if doc else dict()
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added REST-API-cliente-mqtt-consultas/requirements.txt
Binary file not shown.
Loading