CSP Realtime Interface
The WebSocket interface allows bidirectional communication between customers and the satellite platform via the Open Cosmos Mission Operations Centre (MOC). This document explains how to interact with the WebSocket API, including message formats, authorization, error handling, and connection policies.
WebSocket Endpoint
To establish a connection, customers should use the following WebSocket URL format wss://app.open-cosmos.com/api/ops/v0/customer-gateway/ws/mission/{mission_id}/protocol/csp
The mission_id parameter should be replaced with the mission ID for the satellite mission. The mission ID is a unique identifier for the mission and can be found in the mission details in the Open Cosmos platform.
Restrictions
Allowed CSP Ports
Each mission has configured allowed CSP source and destination ports. If a customer attempts to use a port that is not allowed, the connection attempt will result in a bad handshake error. The configuration of allowed ports is done by the Open Cosmos Ground Segment team and can be updated on request.
Concurrent Connections
Customers may establish up to 3 concurrent WebSocket sessions per mission. Downlink data is broadcasted to all active connections.
Message Format
Customers must follow a structured message format for sending data over WebSockets, currently Open Cosmos satellites support CSP version 1 frame format. The standard message structure is as follows:
{
type: "message",
direction: "uplink",
packet: "...", // Serialized CSP packet from allowed source to allowed destination
}
For downlink messages, the structure is:
{
type: "message",
direction: "downlink",
packet: "...", // Serialized CSP packet from allowed destination to allowed source
}
Sending CSP Messages via WebSocket
The following Python example demonstrates how to send an uplink CSP message using the websockets library with an authorization token:
import json
import struct
import threading
import asyncio
from base64 import b64encode
from argparse import ArgumentParser
from typing import Callable, Union
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient
from websockets.sync.client import connect
# Constants
WSS_BASE_URL = "wss://app.open-cosmos.com/api/ops/v0/customer-gateway/ws"
AUTH_URL = "https://login.open-cosmos.com/oauth/token"
def retrieve_token(credentials_file: str) -> str:
"""Retrieve OAuth2 token from credentials file."""
with open(credentials_file) as fp:
credentials = json.load(fp)
client = BackendApplicationClient(client_id=credentials["client_id"])
session = OAuth2Session(client=client)
token_data = session.fetch_token(
token_url=AUTH_URL,
client_id=credentials["client_id"],
client_secret=credentials["client_secret"],
audience=credentials["audience"],
)
return token_data["access_token"]
def create_csp_packet(source: int, src_port: int, destination: int, dst_port: int, data: bytes) -> bytes:
"""Create a CSP packet with source, destination, ports, and payload."""
csp_header = (source << 25) | (src_port << 8) | (destination << 20) | (dst_port << 14)
return struct.pack("!I", csp_header) + data # 4-byte header + payload
def format_csp_message(packet: bytes) -> str:
"""Format CSP packet into a WebSocket JSON message."""
return json.dumps({
"type": "message",
"direction": "uplink",
"packet": b64encode(packet).decode("utf-8"),
})
class WebSocketClient:
"""WebSocket client for sending and receiving CSP packets."""
def __init__(self, mission_id: int, token: str, receive_callback: Callable[[Union[str, bytes]], None]):
self.url = f"{WSS_BASE_URL}/mission/{mission_id}/protocol/csp"
self.headers = {"Authorization": f"Bearer {token}"}
print(f"Connecting to {self.url}...")
self.ws = connect(self.url, additional_headers=self.headers)
self.receive_callback = receive_callback
self._start_receiving()
def _start_receiving(self):
"""Start a thread to receive messages asynchronously."""
def receive_loop():
while True:
msg = self.ws.recv()
self.receive_callback(msg)
thread = threading.Thread(target=receive_loop, daemon=True)
thread.start()
def send(self, data: str):
"""Send a message over WebSocket."""
print(f"Sending: {data}")
self.ws.send(data)
def parse_csp_address(address: str) -> tuple[int, int]:
"""Parse CSP address in the format 'address:port'."""
addr, _, port = address.partition(":")
addr, port = int(addr), int(port)
if not (0 <= addr <= 31):
raise ValueError("CSP Address must be between 0 and 31.")
if not (0 <= port <= 63):
raise ValueError("CSP Port must be between 0 and 63.")
return addr, port
def receive_callback(data: Union[str, bytes]):
"""Handle received messages."""
print(f"Received: {data}")
def main():
"""Main function to run the WebSocket client and send a CSP packet."""
parser = ArgumentParser(description="Send a CSP packet via WebSocket.")
parser.add_argument("credentials_file", type=str, help="Path to JSON credentials file.")
parser.add_argument("mission_id", type=int, help="Mission ID.")
parser.add_argument("ground_address", type=str, help="CSP ground address (e.g., 30:10).")
parser.add_argument("payload_address", type=str, help="CSP payload address (e.g., 10:5).")
parser.add_argument("data", type=str, help="Hexadecimal data payload.")
args = parser.parse_args()
# Authenticate
token = retrieve_token(args.credentials_file)
# Initialize WebSocket client
ws_client = WebSocketClient(args.mission_id, token, receive_callback)
# Parse addresses
ground_addr, ground_port = parse_csp_address(args.ground_address)
payload_addr, payload_port = parse_csp_address(args.payload_address)
# Create and send CSP packet
print(f"Sending CSP packet from {ground_addr}:{ground_port} to {payload_addr}:{payload_port}...")
packet = create_csp_packet(ground_addr, ground_port, payload_addr, payload_port, bytes.fromhex(args.data))
ws_client.send(format_csp_message(packet))
# Wait loop
while True:
pass
if __name__ == "__main__":
main()
You can run the script above with the following command:
python send_csp_packet.py data_cosmos_api_credentials.json 1 30:10 10:5 "0102030405060708"
The response can look like the following:
Connecting to wss://app.open-cosmos.com/api/ops/v0/customer-gateway/ws/mission/1/protocol/csp...
Sending CSP packet from 30:10 to 10:5...
Sending: {"type": "message", "direction": "uplink", "packet": "0102030405060708"}
Received: {"type":"message","direction":"downlink", "packet": "0102030405060709"}
Error Handling
If a message fails due to invalid formatting, length restrictions, or incorrect addresses, an error response is sent:
{
type: "error",
message: "error verifying csp packet: configuration error mission=1: not allowed to perform 'read' on 'resource '30' of type 'csp_source_address'",
}