Skip to content

Commit

Permalink
Allow multiple subscriptions on one websocket connection
Browse files Browse the repository at this point in the history
  • Loading branch information
ugyballoons committed Sep 23, 2024
1 parent d8b6fc6 commit c773f04
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 48 deletions.
93 changes: 47 additions & 46 deletions python/lsst/ts/rubintv/handlers/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,53 +39,20 @@ async def data_websocket(

while True:
raw: str = await websocket.receive_text()
logger.info("websocket server recvd:", raw=raw)
try:
data: dict = json.loads(raw)
except json.JSONDecodeError as e:
logger.error("JSON not well formed", error=e)
logger.info("Ws recvd:", raw=raw)
validated = await validate_raw_message(raw)
if validated is None:
continue
if not await is_valid_client_request(data):
logger.error("Not valid request", data=data)
continue
r_client_id = uuid.UUID(data["clientID"])
if "messageType" not in data:
logger.warn(
"No message type found in data from client",
client_id=r_client_id,
data=data,
)
continue
message_type = data["messageType"]
match message_type:
case "service":
if "message" in data:
service = data["message"]
logger.info(
"Attaching:",
client_id=r_client_id,
service=service,
)
await attach_service(r_client_id, service, websocket)
else:
logger.warn(
"No service found in message from client",
client_id=r_client_id,
)
continue
case "historicalStatus":
historical_busy = await websocket.app.state.historical.is_busy()
await websocket.send_json(
{
"dataType": MessageType.HISTORICAL_STATUS.value,
"payload": historical_busy,
}
)
async with services_lock:
if "historicalStatus" not in services_clients:
services_clients["historicalStatus"] = [r_client_id]
else:
services_clients["historicalStatus"].append(r_client_id)
r_client_id, data = validated

if "message" in data:
service = data["message"]
logger.info("Attaching:", id=r_client_id, service=service)
await attach_service(r_client_id, service, websocket)
else:
logger.warn("No message:", client_id=r_client_id, data=data)
continue

except WebSocketDisconnect:
async with clients_lock:
if websocket in websocket_to_client:
Expand All @@ -99,6 +66,19 @@ async def data_websocket(
logger.exception("Caught surprise exception")


async def validate_raw_message(raw: str) -> tuple[uuid.UUID, dict] | None:
try:
data: dict = json.loads(raw)
except json.JSONDecodeError as e:
logger.error("JSON not well formed", error=e)
return None
if not await is_valid_client_request(data):
logger.error("Not valid request", data=data)
return None
r_client_id = uuid.UUID(data["clientID"])
return r_client_id, data


async def remove_client_from_services(client_id: uuid.UUID) -> None:
logger.info("Removing client from services list...", client_id=client_id)
async with services_lock:
Expand All @@ -123,6 +103,10 @@ async def remove_client_from_services(client_id: uuid.UUID) -> None:
async def attach_service(
client_id: uuid.UUID, service_loc_cam: str, websocket: WebSocket
) -> None:
if service_loc_cam == "historicalStatus":
await attach_historical_busy_service(client_id, websocket)
return

if not await is_valid_service(service_loc_cam):
logger.error(
"Not valid service",
Expand Down Expand Up @@ -164,6 +148,23 @@ async def attach_service(
services_clients[service_loc_cam] = [client_id]


async def attach_historical_busy_service(
client_id: uuid.UUID, websocket: WebSocket
) -> None:
historical_busy = await websocket.app.state.historical.is_busy()
await websocket.send_json(
{
"dataType": MessageType.HISTORICAL_STATUS.value,
"payload": historical_busy,
}
)
async with services_lock:
if "historicalStatus" not in services_clients:
services_clients["historicalStatus"] = [client_id]
else:
services_clients["historicalStatus"].append(client_id)


async def is_valid_client_request(data: dict) -> bool:
try:
client_id = uuid.UUID(data["clientID"])
Expand Down
104 changes: 104 additions & 0 deletions src/js/components/MosaicView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { useState, useEffect } from "react"
import PropTypes from "prop-types"
import { WebsocketClient } from "../modules/ws-service-client"
import {
cameraType,
channelDataType,
eventType,
metadataType,
mosaicSingleView,
} from "./componentPropTypes"
import { simpleGet } from "../modules/utils"

const commonColumns = ["seqNum"]

export default function MosaicView({ camera, initialDate }) {
const [date, setDate] = useState(initialDate)

const initialViews = camera.mosaic_view_meta.map(view => (
{
...view,
latestEvent: {},
latestMetadata: {}
}))
const [views, setViews] = useState(initialViews)

useEffect(() => {
function handleCurrentEvent (event) {
const { datestamp, data, dataType } = event.detail
console.log('Found:', datestamp, data, dataType)
}
window.addEventListener('channel', handleCurrentEvent)

// Cleanup the event listener on component unmount
return () => {
window.removeEventListener('channel', handleCurrentEvent)
}
}) // Only reattach the event listener if the date changes

return (
<div className="viewsArea">
<h3 className="viewsTitle">Mosaic View: <span className="date">{date}</span></h3>
<ul className="views">
{views.map((view) => {
return (
<li key={view.channel} className="view">
<SingleView camera={camera} view={view} />
</li>
)
})}
</ul>
</div>
)
}
MosaicView.propTypes = {
camera: cameraType,
initialDate: PropTypes.string,
}

function SingleView({ camera, view }) {
return (
<>
<CurrentImage camera={camera} event={view.latestEvent} />
<SingleViewColumns viewMetaColumns={view.metaColumns} metadata={view.latestMetadata} />
</>
)
}
SingleView.propTypes = {
camera: cameraType,
view: mosaicSingleView,
}

function CurrentImage({ camera, event }) {
return (
<div className="viewImage">
<img className="placeholder"/>
</div>
)
}
CurrentImage.propTypes = {
camera: cameraType,
event: eventType,
}

function SingleViewColumns({ viewMetaColumns, metadata }) {
const columns = [...commonColumns, ...viewMetaColumns]
return (
<ul className="viewMeta">
{columns.map((column) => {
const value = metadata[column] ? metadata[column] : "No value set"
return (
<li key={column} className="viewMetaCol">
<div className="colName">{column}</div>
<div className="colValue">{value}</div>
</li>
)
})}
</ul>
)
}
SingleViewColumns.propTypes = {
viewMetaColumns: PropTypes.arrayOf(PropTypes.string),
metadata: metadataType,
}

4 changes: 2 additions & 2 deletions src/js/modules/ws-service-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ export class WebsocketClient {
#getSubscriptionPayload(subscriptionType, servicePageType, pageID) {
let payload
if (subscriptionType === "historicalStatus") {
payload = { messageType: subscriptionType }
payload = { message: subscriptionType }
} else {
const message = [servicePageType, pageID].join(" ")
payload = { messageType: "service", message }
payload = { message }
}
return payload
}
Expand Down

0 comments on commit c773f04

Please sign in to comment.