WebSocket Server (Remote Control)
The MW75 WebSocket Server provides a remote control interface for third-party applications to manage MW75 device connections and receive real-time EEG data over WebSocket.
Overview
Unlike the WebSocket client mode (--ws
), which connects to an existing WebSocket server, the server mode creates a WebSocket server that external applications can connect to for remote control.
Use Cases:
Web applications controlling MW75 devices
Mobile apps requiring EEG data
Distributed systems with centralized device management
Remote monitoring and control scenarios
Starting the Server
Basic Usage
# Start on default port 8080
uv run -m mw75_streamer.server
# Start on custom port
uv run -m mw75_streamer.server --port 9000
# Listen on all interfaces (for remote access)
uv run -m mw75_streamer.server --host 0.0.0.0 --port 8080
# Enable verbose logging
uv run -m mw75_streamer.server --port 8080 --verbose
Connection Behavior
Server starts idle (no device connection)
Waits for client to connect and send commands
Only one client allowed at a time
Client controls when to connect/disconnect from MW75 device
Device automatically disconnects when client disconnects
Message Protocol
All messages use JSON format with this structure:
{
"id": "unique-message-id",
"type": "message-type",
"data": { }
}
Message Fields:
id
- Unique identifier (UUID) for the messagetype
- Message type (see below)data
- Type-specific data payload
Client Commands
Commands sent from client to server:
Connect Command
Request connection to MW75 device:
{
"id": "msg-1",
"type": "connect",
"data": {
"auto_reconnect": true,
"log_level": "ERROR"
}
}
Parameters:
auto_reconnect
(boolean, default: false) - Enable automatic reconnection on disconnectlog_level
(string, default: “ERROR”) - Log level: “DEBUG”, “INFO”, “WARNING”, or “ERROR”
Response: Server sends command_ack
followed by status updates
Disconnect Command
Request disconnection from MW75 device:
{
"id": "msg-2",
"type": "disconnect",
"data": {}
}
Response: Server sends command_ack
and disconnects from device
Status Query
Query current connection status:
{
"id": "msg-3",
"type": "status",
"data": {}
}
Response: Server sends current status information:
{
"id": "msg-3",
"type": "status",
"data": {
"device_state": "connected",
"auto_reconnect": true,
"log_level": "ERROR",
"battery_level": 85
}
}
Status Fields:
device_state
- Current connection state (see Connection States below)auto_reconnect
- Whether auto-reconnect is enabledlog_level
- Current log level filterbattery_level
- Device battery percentage (0-100), ornull
if not available
Ping/Pong
Keepalive mechanism (optional, server sends automatic heartbeats):
{
"id": "msg-4",
"type": "ping",
"data": {}
}
Response: Server sends pong
message
Server Messages
Messages sent from server to client:
Command Acknowledgement
Confirms command receipt:
{
"id": "msg-1",
"type": "command_ack",
"data": {
"command": "connect",
"message": "Connect command received, initiating device connection",
"auto_reconnect": true,
"log_level": "INFO"
}
}
Status Updates
Connection state changes:
{
"id": "uuid",
"type": "status",
"data": {
"state": "connected",
"message": "Successfully connected to MW75 device",
"timestamp": 1234567890.123,
"battery_level": 85
}
}
Status Update Fields:
state
- Current connection state (see Connection States below)message
- Human-readable status messagetimestamp
- Unix timestamp of status updatebattery_level
- Device battery percentage (0-100), ornull
if not available
Connection States:
idle
- No device connectionconnecting
- Attempting device connectionconnected
- Device connected and streamingdisconnecting
- Disconnecting from devicedisconnected
- Device disconnectedreconnecting
- Auto-reconnect in progresserror
- Error state
EEG Data
Real-time EEG packets (sent at ~500Hz when connected):
{
"id": "uuid",
"type": "eeg_data",
"data": {
"timestamp": 1234567890.123,
"event_id": 239,
"counter": 42,
"ref": 123.45,
"drl": 67.89,
"channels": {
"ch1": 10.5,
"ch2": 12.3,
"ch3": -5.2,
"ch4": 8.7,
"ch5": -2.1,
"ch6": 15.3,
"ch7": 4.8,
"ch8": -7.6,
"ch9": 11.2,
"ch10": 3.4,
"ch11": -9.8,
"ch12": 6.1
},
"feature_status": 0
}
}
Fields:
timestamp
- Unix timestamp (seconds with microsecond precision)event_id
- Always 239 for EEG datacounter
- Sequence counter (0-255, wraps around)ref
- Reference electrode value (µV)drl
- Driven Right Leg electrode value (µV)channels
- 12 EEG channels (µV), named ch1 through ch12feature_status
- Device feature status flags
Log Messages
Filtered log messages (based on client-requested log level):
{
"id": "uuid",
"type": "log",
"data": {
"level": "ERROR",
"message": "Connection error: timeout",
"logger": "mw75_streamer.device",
"timestamp": 1234567890.123
}
}
Log Levels:
DEBUG
- Detailed diagnostic informationINFO
- General informational messagesWARNING
- Warning messages (potential issues)ERROR
- Error messages (failures)
Error Messages
Error notifications:
{
"id": "uuid",
"type": "error",
"data": {
"code": "CONNECTION_FAILED",
"message": "Failed to connect to device",
"timestamp": 1234567890.123
}
}
Common Error Codes:
CLIENT_ALREADY_CONNECTED
- Another client is already connectedINVALID_JSON
- Malformed JSON receivedINVALID_MESSAGE
- Message structure invalidMISSING_TYPE
- Message missing ‘type’ fieldUNKNOWN_COMMAND
- Unknown command typeINVALID_LOG_LEVEL
- Invalid log level specifiedALREADY_CONNECTED
- Device already connectedBLE_ACTIVATION_FAILED
- MW75 device not found or BLE activation failedRFCOMM_CONNECTION_FAILED
- RFCOMM data connection failedCONNECTION_FAILED
- Device connection failedDISCONNECT_ERROR
- Error during disconnectionRECONNECT_FAILED
- Auto-reconnect attempt failedRECONNECT_EXHAUSTED
- Max reconnection attempts reachedDEVICE_ERROR
- Device-level errorMESSAGE_PROCESSING_ERROR
- Error processing message
Heartbeat Messages
Automatic keepalive (sent every 30 seconds):
{
"id": "uuid",
"type": "heartbeat",
"data": {
"timestamp": 1234567890.123,
"battery_level": 85
}
}
Heartbeat Fields:
timestamp
- Unix timestamp of heartbeatbattery_level
- Device battery percentage (0-100), ornull
if not available
These heartbeats help monitor connection health and provide periodic battery level updates. The WebSocket library automatically handles connection liveness and will close the connection if it becomes unresponsive.
Features
Auto-Reconnect
When enabled via the connect command:
Monitors device connection health
Automatically attempts reconnection on disconnect
Uses exponential backoff (1, 2, 4, 8, 16, 30 seconds max)
Maximum 10 reconnection attempts
Sends status updates during reconnection
Stops on client disconnect command
Log Level Filtering
Client can request specific log levels in the connect command:
DEBUG
- All logs (very verbose)INFO
- Informational and aboveWARNING
- Warnings and errors onlyERROR
- Errors only (default)
Logs are captured from the entire mw75_streamer
package and filtered before sending to client.
Battery Monitoring
The server automatically retrieves and reports the MW75 device battery level:
Battery level is obtained during device connection via BLE
Reported as percentage (0-100)
Included in all status messages
Periodically updated via heartbeat messages (every 30 seconds)
Returns
null
when device is not connected or battery level unavailable
Access Battery Level:
Query Status: Send
status
command to get current battery levelStatus Updates: Battery level included in all state change notifications
Heartbeats: Periodic updates every 30 seconds while connected
Single Client Policy
Only one client can connect at a time:
Subsequent connection attempts are rejected
Rejection includes
CLIENT_ALREADY_CONNECTED
errorClient must disconnect before another can connect
Server does not queue or manage multiple clients
Connection Lifecycle
Client connects to WebSocket server
Server sends welcome status message
Client sends
connect
commandServer acknowledges and begins device connection
Device connects, EEG data flows
Client sends
disconnect
or closes WebSocketServer disconnects device and cleans up
Python Client Example
Complete Example
import asyncio
import json
import uuid
import websockets
async def mw75_client():
uri = "ws://localhost:8080"
async with websockets.connect(uri) as ws:
# Send connect command
await ws.send(json.dumps({
"id": str(uuid.uuid4()),
"type": "connect",
"data": {
"auto_reconnect": True,
"log_level": "INFO"
}
}))
# Receive and process messages
async for message in ws:
data = json.loads(message)
msg_type = data.get("type")
if msg_type == "eeg_data":
channels = data["data"]["channels"]
print(f"Ch1: {channels['ch1']:.1f} µV")
elif msg_type == "status":
state = data["data"]["state"]
battery = data["data"].get("battery_level")
battery_str = f" (Battery: {battery}%)" if battery else ""
print(f"Status: {state}{battery_str}")
elif msg_type == "heartbeat":
battery = data["data"].get("battery_level")
if battery:
print(f"Heartbeat - Battery: {battery}%")
elif msg_type == "error":
code = data["data"]["code"]
msg = data["data"]["message"]
print(f"Error [{code}]: {msg}")
elif msg_type == "log":
level = data["data"]["level"]
msg = data["data"]["message"]
print(f"[{level}] {msg}")
asyncio.run(mw75_client())
See examples/websocket_server_client.py
for a complete working example.
JavaScript Client Example
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
// Connect to device
ws.send(JSON.stringify({
id: crypto.randomUUID(),
type: 'connect',
data: {
auto_reconnect: true,
log_level: 'ERROR'
}
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'eeg_data':
const ch1 = data.data.channels.ch1;
console.log(`Ch1: ${ch1.toFixed(1)} µV`);
break;
case 'status':
const battery = data.data.battery_level;
const batteryStr = battery ? ` (Battery: ${battery}%)` : '';
console.log(`Status: ${data.data.state}${batteryStr}`);
break;
case 'heartbeat':
if (data.data.battery_level) {
console.log(`Battery: ${data.data.battery_level}%`);
}
break;
case 'error':
console.error(`Error: ${data.data.message}`);
break;
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
Testing
Integration Test
Test with actual MW75 device:
# Terminal 1: Start server
uv run -m mw75_streamer.server --port 8080 --verbose
# Terminal 2: Run example client
python examples/websocket_server_client.py
The example client will connect, start device streaming, receive EEG packets, and disconnect.
API Reference
Server Class
WebSocket Server for Remote MW75 Control
Provides a WebSocket server that allows third-party applications to: - Connect/disconnect to MW75 device remotely - Receive real-time EEG data - Get status updates and logs - Configure auto-reconnect behavior
- class mw75_streamer.server.ws_server.DeviceState(value)[source]
Bases:
Enum
MW75 device connection states
- IDLE = 'idle'
- CONNECTING = 'connecting'
- CONNECTED = 'connected'
- DISCONNECTING = 'disconnecting'
- DISCONNECTED = 'disconnected'
- ERROR = 'error'
Comparison: Client vs Server Mode
The package offers two WebSocket modes:
- Client Mode (
--ws
) Streamer connects to existing WebSocket server
Sends EEG data to server
Simple one-way data streaming
Use for: sending data to existing infrastructure
- Server Mode (
-m mw75_streamer.server
) Streamer is the WebSocket server
Clients connect to control device and receive data
Two-way command/response protocol
Use for: remote control applications
Both modes can coexist - you can run the server mode and also use --ws
to forward data elsewhere simultaneously.
Troubleshooting
Connection Refused
Problem: Client cannot connect to server
Solutions:
Ensure server is running:
uv run -m mw75_streamer.server --port 8080
Check port availability:
lsof -i :8080
Verify firewall settings allow connections
Try localhost first:
ws://localhost:8080
Client Rejected
Problem: “CLIENT_ALREADY_CONNECTED” error
Solutions:
Only one client can connect at a time
Disconnect existing client first
Wait a few seconds after client disconnect
Restart server if needed
Device Won’t Connect
Problem: Status stays in “connecting” state
Solutions:
Ensure MW75 headphones are paired and powered on
Check Bluetooth connection in System Preferences
Run server with
--verbose
to see detailed logsSee Troubleshooting Guide for device-specific issues
No EEG Data
Problem: Connected but no eeg_data
messages
Solutions:
Check device state with status command
Ensure device successfully connected (status = “connected”)
Monitor for error messages
Check log messages for connection issues
Security Considerations
The WebSocket server provides no built-in authentication or encryption:
Recommendations:
Bind to
localhost
only for local applicationsUse a reverse proxy (nginx, Apache) for remote access
Implement TLS/SSL at proxy level for encryption
Add authentication at application level if needed
Run on isolated network for sensitive applications
Never expose directly to public internet
For production deployments, consider wrapping the server with proper security infrastructure.
Next Steps
See Quick Start Guide for basic MW75 usage
Read Protocol Documentation for technical details
Check Troubleshooting Guide for common issues
Review
examples/websocket_server_client.py
for complete client implementation