diff --git a/client/component/src/ConnectedPlot.tsx b/client/component/src/ConnectedPlot.tsx index ba14b38..828faee 100644 --- a/client/component/src/ConnectedPlot.tsx +++ b/client/component/src/ConnectedPlot.tsx @@ -147,12 +147,20 @@ interface ClientScatterParametersMessage { pointSize: number; } +/** + * A client status message + */ +interface ClientConfigMessage { + config: Map; +} + type ClientMessage = | ClientStatusMessage | ClientSelectionMessage | ClientLineParametersMessage | ClientScatterParametersMessage | ClearSelectionsMessage + | ClientConfigMessage | BatonRequestMessage | BatonDonateMessage; @@ -224,6 +232,8 @@ interface ConnectedPlotProps { hostname?: string; /** The port */ port?: string; + /** A configuration map */ + config?: object; /** The universally unique identifier */ uuid: string; } @@ -238,6 +248,7 @@ function ConnectedPlot({ plotId = 'plot_0', hostname = '127.0.0.1', port = '80', + config, uuid, }: ConnectedPlotProps) { const [plotProps, setPlotProps] = useState(); @@ -246,6 +257,7 @@ function ConnectedPlot({ useState(defaultPlotConfig); const [scatterData, setScatterData] = useState(); + const [configMap, setConfigMap] = useState(config); const mountState = useRef(''); const plotServerURL = `ws://${hostname}:${port}/plot/${uuid}/${plotId}`; const { sendMessage, lastMessage, readyState, getWebSocket } = useWebSocket( @@ -271,6 +283,10 @@ function ConnectedPlot({ }; }, []); + if (config != configMap) { + setConfigMap(config); + } + const sendClientMessage = useCallback( (data: ClientMessage) => { console.log('%s: sending', plotId, data); @@ -338,6 +354,12 @@ function ConnectedPlot({ } }, [getWebSocket, plotId, readyState, sendStatusMessage]); + useEffect(() => { + if (readyState === ReadyState.OPEN && configMap) { + sendClientMessage({config: configMap} as ClientConfigMessage); + } + }, [readyState, configMap]); + const clearLineData = () => { setLineData([]); setLinePlotConfig(defaultPlotConfig); diff --git a/client/example/src/App.tsx b/client/example/src/App.tsx index a7cc798..73735ff 100644 --- a/client/example/src/App.tsx +++ b/client/example/src/App.tsx @@ -131,6 +131,7 @@ class AppMain extends React.Component { ...heatmapProps.plotConfig, title: 'Sample Heatmap Plot (no toolbar)', }; + const config = {pvName: 'MY_DEVICE'}; const host = import.meta.env.VITE_WS_HOST ?? window.location.hostname; const port = import.meta.env.VITE_WS_PORT ?? window.location.port; @@ -157,6 +158,7 @@ class AppMain extends React.Component { uuid={this.uuid} hostname={host} port={port} + config={config} />
diff --git a/server/davidia/models/messages.py b/server/davidia/models/messages.py index e2a2923..4e72a9f 100644 --- a/server/davidia/models/messages.py +++ b/server/davidia/models/messages.py @@ -1,3 +1,4 @@ +from pydantic import BaseModel from enum import auto from typing import Any from uuid import uuid4 @@ -343,6 +344,20 @@ class ClearPlotMessage(DvDModel): plot_id: str +class ConfigModel(DvDModel): + """Class for representing any config""" + +class EpicsPVAccessConfig(ConfigModel): + pvName: str + +class ProfileToolConfig(ConfigModel): + profile: str + +class ClientConfigMessage(DvDModel): + """Class for representing a client config""" + + config: EpicsPVAccessConfig | ProfileToolConfig + class ClientStatusMessage(DvDModel): """Class for representing a client status""" @@ -375,13 +390,12 @@ class ClientScatterParametersMessage(DvDModel): ClientMessage = ( - ClientStatusMessage + ClearSelectionsMessage + | ClientConfigMessage + | ClientStatusMessage | ClientSelectionMessage | ClientLineParametersMessage | ClientScatterParametersMessage - | ClearSelectionsMessage - | BatonRequestMessage - | BatonDonateMessage ) @@ -395,6 +409,7 @@ class ClientScatterParametersMessage(DvDModel): BatonRequestMessage, SelectionsMessage, ClearSelectionsMessage, + ClientConfigMessage, ClientStatusMessage, ClientSelectionMessage, ClientLineParametersMessage, @@ -411,6 +426,8 @@ class ClientScatterParametersMessage(DvDModel): SurfaceData, TableData, PlotConfig, + EpicsPVAccessConfig, + ProfileToolConfig, ) + ALL_MESSAGES if __name__ == "__main__": diff --git a/server/davidia/server/plotserver.py b/server/davidia/server/plotserver.py index d7426af..2594b62 100644 --- a/server/davidia/server/plotserver.py +++ b/server/davidia/server/plotserver.py @@ -19,6 +19,7 @@ _PlotDataMessage, ClearPlotMessage, ClearSelectionsMessage, + ClientConfigMessage, ClientSelectionMessage, ClientStatusMessage, ClientLineParametersMessage, @@ -242,7 +243,7 @@ async def send_baton_approval_request(self, message: BatonRequestMessage) -> Non """Sends message to current baton holder to request baton Parameters ---------- - message : ClientMessage + message : BatonRequestMessage """ requester = message.requester if self.baton is None: @@ -262,7 +263,7 @@ async def take_baton(self, message: BatonDonateMessage) -> bool: """Updates baton and sends new baton messages Parameters ---------- - message : ClientMessage + message : BatonDonateMessage Returns ------- @@ -801,6 +802,9 @@ async def handle_client(server: PlotServer, plot_id: str, socket: WebSocket, uui continue match received_message: + case ClientConfigMessage(): + logger.debug("Received config from client for %s: %s", plot_id, received_message) + pass # TODO update plot server state (does client need baton?) case ClientStatusMessage(): status = received_message.status if status == StatusType.ready: @@ -819,7 +823,6 @@ async def handle_client(server: PlotServer, plot_id: str, socket: WebSocket, uui "Websocket closing for %s:%s", client.name, client.uuid ) update_all = await server.remove_client(plot_id, client) - break case BatonRequestMessage(): await server.send_baton_approval_request(received_message) case BatonDonateMessage(): @@ -837,7 +840,7 @@ async def handle_client(server: PlotServer, plot_id: str, socket: WebSocket, uui client.uuid, received_message, ) - is_valid = client.uuid == server.baton + is_valid = received_message is not None and client.uuid == server.baton if is_valid: omit = client # omit originating client else: @@ -847,6 +850,7 @@ async def handle_client(server: PlotServer, plot_id: str, socket: WebSocket, uui ) if is_valid: + assert isinstance(received_message, ClientMessage) await server.prepare_client( plot_id, received_message, omit_client=omit )