Appearance
Building a WebSocket Coupling (template)
Use this guide to turn the CouplingWebSocket library and WebSocketTestServer sample into a coupling for your own external tool. It mirrors the XMPP message schema, but uses a lightweight WebSocket transport.
When to choose WebSocket
- XMPP ports/brokers aren’t available or are blocked by firewalls.
- You want a small, dependency-light host you can bundle with your simulation.
- You need to run on the same machine as EMRALD with minimal setup.
Project pieces
- CouplingWebSocket/WebApiCoupling.cs – EMRALD-side client helper implementing
ISimMessaging. Handles connect, app discovery, connection GUIDs, and action/event send/receive. - CouplingWebSocket/WebSocketClient.cs – raw WebSocket transport:
GetAppOptions,CreateConnection,SendActionMsg, and event dispatch. - WebSocketTestServer – reference host you can copy:
Program.cs– listener, command dispatch (GetAppOptions,CreateConnection,SendActionMsg).ConnectionStateMachine.cs+ConnectionStates.cs– EMRALD-style state flow (Idle → Loading → Waiting → Running → Done/Error) that interprets action messages and emits events.ExampleSim.cs– stub simulation you replace with your solver.
Quick start (using the sample)
- Build and run
WebSocketTestServer(dotnet run). Default endpoint:ws://localhost:8465/. - In EMRALD, set the external sim’s coupling type to WebSocket and point the URL at the server.
- Run a model that sends external sim messages; watch events flow back in the UI (same as XMPP workflow).
Adapting to your tool
- Replace the simulator
SwapExampleSimwith your API wrapper. Expose methods for load/start/step/pause/stop and any watched variables. - Map EMRALD actions (see comments in
ConnectionStates.cs):atOpenSim→ load model/config, returnetSimLoaded.atTimer→ schedule a callback; emitetTimerwhen reached.atCompModify→ apply parameter/variable changes.atContinue/atCancelSim/atTerminate→ control run/stop/cleanup.
- Emit events from your sim via the state machine:
- Value changes →
etCompEvwith name/value/time. - Timer reached →
etTimer. - Done →
etEndSim. - Status/health →
etStatus/etPing.
- Value changes →
- Watch variables
HonorwatchItemspassed inCreateConnection; send only the requested variables to cut chatter. Each watch item is an object{ "name", "type", "WatchEventCriteria" }wheretypeis the variable's EMRALD type andWatchEventCriteriais an optional fParser boolean expression string (e.g."(valve_12 > 5) & (valve_12 < 10)"). WhenWatchEventCriteriais present, only call back while the expression evaluates true; when it is omitted, report on every change. SeeWatchEventCriteriaEvaluator.csin the sample for a reference expression evaluator. - Timekeeping
Two times travel in each message and they are not the same: the wrapper'sglobalRunTimeis global (this sim's start time + its local elapsed time) and EMRALD uses it to detect stale/out-of-order messages, while eachSimEvent.timeis the sim's local elapsed time (no offset). EMRALD adds the sim's start time to localizeSimEvent.timeback to global, so stamping aSimEventwith global time double-counts the offset and pushes EMRALD's clock ahead (its messages then look perpetually stale). In the sample,GlobalSimTimefeeds the wrapper andLocalSimTimefeeds eachSimEvent. - Threading
The sample processes a connection on a single logical loop. If you add background threads, funnel all outbound messages through the state machine to preserve ordering.
Message flow reference
Client → {"command":"GetAppOptions"}
Server → {"names":["AppA","AppB", ...]}
Client → {"command":"CreateConnection","appName":"AppA","watchItems":[
{"name":"var1","type":"double"},
{"name":"valve_12","type":"double","WatchEventCriteria":"(valve_12 > 5) & (valve_12 < 10)"}
]}
Server → {"conID":"<guid>"}
// watchItems are objects: name, EMRALD type, and an optional WatchEventCriteria fParser
// expression string. With an expression, the server only calls back while it evaluates
// true; without one, it reports on every change.
Client → {"command":"SendActionMsg","conID":"<guid>","action":<TMsgWrapper JSON>}
Server → {"conID":"<guid>","message":<TMsgWrapper JSON>} // events: etSimLoaded, etCompEv, etTimer, etEndSim, etStatus, etc.Configuration in EMRALD
- External simulation: set Coupling Type = WebSocket.
- Coupling URL:
ws://host:port/of your server. - Optional: coupling password (if your host enforces one) is passed through
WebApiCoupling.connectionPassword.
Testing checklist
- Start server; verify
GetAppOptionsreturns your app name. atOpenSimloads and repliesetSimLoaded.atTimertriggersetTimerat the right global time.atCompModifychanges are reflected in subsequentetCompEv.atCancelSimstops cleanly;atTerminatedisposes resources.
Where to look in code (quick map)
- Transport & handshake:
CouplingWebSocket/WebSocketClient.cs - EMRALD client adapter:
CouplingWebSocket/WebApiCoupling.cs - Host entry point:
WebSocketTestServer/Program.cs - State logic:
WebSocketTestServer/ConnectionStateMachine.cs,ConnectionStates.cs - Simulation stub:
WebSocketTestServer/ExampleSim.cs
