Skip to content

Commit

Permalink
working session-based mqtt connection BE & FE
Browse files Browse the repository at this point in the history
  • Loading branch information
linomp committed Mar 28, 2024
1 parent d5a6a4c commit bee040b
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 49 deletions.
8 changes: 2 additions & 6 deletions mvp/client/ui/.env.development
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
VITE_DEBUG=
VITE_API_BASE=http://localhost:8000
VITE_MQTT_HOST=
VITE_MQTT_TOPIC_PREFIX=
VITE_MQTT_USERNAME=
VITE_MQTT_PASSWORD=
VITE_DEBUG=1
VITE_API_BASE=http://localhost:8000
3 changes: 1 addition & 2 deletions mvp/client/ui/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
build
node_modules
.env.development.secret
node_modules
4 changes: 1 addition & 3 deletions mvp/client/ui/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
import { onMount } from "svelte";
import { Route, Router } from "svelte-navigator";
import { GameParametersService, OpenAPI } from "./api/generated/";
import { globalSettings, mqttClient } from "src/stores/stores";
import { globalSettings } from "src/stores/stores";
import { isUndefinedOrNull } from "src/shared/utils";
import HomePage from "src/pages/HomePage.svelte";
import Spinner from "src/components/graphical/Spinner.svelte";
import Preloader from "src/components/Preloader.svelte";
import { getClient } from "./mqtt/client";
OpenAPI.BASE = import.meta.env.VITE_API_BASE;
Expand All @@ -16,7 +15,6 @@
globalSettings.set(
await GameParametersService.getParametersGameParametersGet(),
);
mqttClient.set(await getClient());
} catch (error) {
alert(
"Error fetching game settings or connecting to MQTT broker. Please refresh the page.",
Expand Down
2 changes: 2 additions & 0 deletions mvp/client/ui/src/api/generated/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise';
export { OpenAPI } from './core/OpenAPI';
export type { OpenAPIConfig } from './core/OpenAPI';

export type { GameMetrics } from './models/GameMetrics';
export type { GameParametersDTO } from './models/GameParametersDTO';
export type { GameSessionDTO } from './models/GameSessionDTO';
export type { HTTPValidationError } from './models/HTTPValidationError';
export type { MachineStateDTO } from './models/MachineStateDTO';
export type { MqttFrontendConnectionDetails } from './models/MqttFrontendConnectionDetails';
export type { OperationalParameters } from './models/OperationalParameters';
export type { ValidationError } from './models/ValidationError';

Expand Down
15 changes: 15 additions & 0 deletions mvp/client/ui/src/api/generated/models/GameMetrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

export type GameMetrics = {
active_sessions: number;
total_started_games: number;
total_ended_games: number;
total_abandoned_games: number;
total_seconds_played: number;
avg_game_duration: number;
min_game_duration?: number;
max_game_duration?: number;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

export type MqttFrontendConnectionDetails = {
host: string;
port: number;
username: string;
password: string;
base_topic: string;
};
39 changes: 39 additions & 0 deletions mvp/client/ui/src/api/generated/services/SessionsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,38 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { GameMetrics } from '../models/GameMetrics';
import type { GameSessionDTO } from '../models/GameSessionDTO';
import type { MqttFrontendConnectionDetails } from '../models/MqttFrontendConnectionDetails';

import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';

export class SessionsService {

/**
* Get Mqtt Connection Details
* @param sessionId
* @returns MqttFrontendConnectionDetails Successful Response
* @throws ApiError
*/
public static getMqttConnectionDetailsSessionsMqttConnectionDetailsGet(
sessionId: string,
): CancelablePromise<MqttFrontendConnectionDetails> {
return __request(OpenAPI, {
method: 'GET',
url: '/sessions/mqtt-connection-details',
query: {
'session_id': sessionId,
},
errors: {
404: `Not found`,
422: `Validation Error`,
},
});
}

/**
* Create Session
* @returns GameSessionDTO Successful Response
Expand Down Expand Up @@ -47,6 +71,21 @@ sessionId: string,
});
}

/**
* Get Metrics
* @returns GameMetrics Successful Response
* @throws ApiError
*/
public static getMetricsSessionsMetricsGet(): CancelablePromise<GameMetrics> {
return __request(OpenAPI, {
method: 'GET',
url: '/sessions/metrics',
errors: {
404: `Not found`,
},
});
}

/**
* Advance
* @param sessionId
Expand Down
13 changes: 12 additions & 1 deletion mvp/client/ui/src/components/StartSessionButton.svelte
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
<script lang="ts">
import { getClient } from "src/messaging/mqttFunctions";
import { SessionsService, type GameSessionDTO } from "../api/generated";
import { isUndefinedOrNull } from "src/shared/utils";
import { gameSession } from "src/stores/stores";
import { gameSession, mqttClient } from "src/stores/stores";
export let updateGameSession: (newGameSessionDto: GameSessionDTO) => void;
const startSession = async () => {
try {
// Get game session
const newGameSessionDto =
await SessionsService.createSessionSessionsPost();
updateGameSession(newGameSessionDto);
// Get MQTT connection details
const mqttConnectionDetails =
await SessionsService.getMqttConnectionDetailsSessionsMqttConnectionDetailsGet(
newGameSessionDto.id,
);
// Build MQTT client
mqttClient.set(await getClient(mqttConnectionDetails));
} catch (error) {
console.error("Error fetching session:", error);
}
Expand Down
43 changes: 43 additions & 0 deletions mvp/client/ui/src/messaging/mqttFunctions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import mqtt from "mqtt";
import type { MqttFrontendConnectionDetails } from "src/api/generated";

export const getClient = async (connectionDetails: MqttFrontendConnectionDetails): Promise<mqtt.MqttClient> => {

const brokerUrl = `wss://${connectionDetails.host}:${connectionDetails.port}/mqtt`;

try {

if (import.meta.env.VITE_DEBUG) {
console.log(`Connecting to MQTT broker at ${brokerUrl}`);
console.log(`Base topic: `, connectionDetails.base_topic);
}

const client = await mqtt.connectAsync(
brokerUrl,
{
username: connectionDetails.username,
password: connectionDetails.password,
}
);

client.subscribe(`${connectionDetails.base_topic}/#`,
(err) => {
if (err) {
console.error("Error subscribing to topic: ", err);
}
}
);

if (import.meta.env.VITE_DEBUG) {
client.on("message", (topic, message) => {
console.log(`Received message on topic ${topic}: ${message.toString()}`);
});
}

return client;

} catch (error) {
console.error(`Error connecting to MQTT broker ${brokerUrl}: `, error);
throw error;
}
}
37 changes: 0 additions & 37 deletions mvp/client/ui/src/mqtt/client.ts

This file was deleted.

29 changes: 29 additions & 0 deletions mvp/server/messaging/MqttFrontendConnectionDetails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os

from dotenv import load_dotenv
from pydantic import BaseModel

load_dotenv()

MQTT_HOST = os.environ.get("MQTT_HOST", "test.mosquitto.org")
MQTT_WSS_PORT = int(os.environ.get("MQTT_WSS_PORT", 1883))
MQTT_FE_USER = os.environ.get("MQTT_FE_USER")
MQTT_FE_PASSWORD = os.environ.get("MQTT_FE_PASSWORD")
MQTT_TOPIC_PREFIX = os.environ.get("MQTT_TOPIC_PREFIX", "pdmgame/clients")


class MqttFrontendConnectionDetails(BaseModel):
host: str
port: int
username: str
password: str
base_topic: str

def __init__(self, session_id: str):
super().__init__(
host=MQTT_HOST,
port=MQTT_WSS_PORT,
username=MQTT_FE_USER,
password=MQTT_FE_PASSWORD,
base_topic=f"{MQTT_TOPIC_PREFIX}/{session_id}"
)
12 changes: 12 additions & 0 deletions mvp/server/routers/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from mvp.server.core.game.GameMetrics import GameMetrics
from mvp.server.core.game.GameSession import GameSession
from mvp.server.core.game.GameSessionDTO import GameSessionDTO
from mvp.server.messaging.MqttFrontendConnectionDetails import MqttFrontendConnectionDetails
from mvp.server.messaging.mqtt_client import MqttClient

sessions: dict[str, GameSession] = {}
Expand All @@ -30,6 +31,16 @@ def get_session_dependency(session_id: str) -> GameSession:
return session


@router.get("/mqtt-connection-details", response_model=MqttFrontendConnectionDetails)
async def get_mqtt_connection_details(session_id: str) -> MqttFrontendConnectionDetails:
session = sessions.get(session_id)

if session is None or session.is_abandoned() or session.is_game_over:
raise HTTPException(status_code=404, detail="Invalid session")

return MqttFrontendConnectionDetails(session_id)


@router.on_event("startup")
@repeat_every(seconds=SESSION_CLEANUP_INTERVAL_SECONDS, wait_first=False)
async def cleanup_inactive_sessions():
Expand Down Expand Up @@ -77,6 +88,7 @@ async def get_session(session: GameSession = Depends(get_session_dependency)) ->
@router.put("/turns", response_model=GameSessionDTO)
async def advance(session: GameSession = Depends(get_session_dependency)) -> GameSessionDTO:
# TODO: pass mqtt client somehow, to report machine op. parameters as they are updated
mqttClient.publish_parameter(session.id, "test", 25.0)
await session.advance_one_turn()

if session.is_game_over:
Expand Down

0 comments on commit bee040b

Please sign in to comment.