Cannonball WebSocket

Real-time threat updates via WebSocket connection

WebSocket Connection

wss://api.skyspy.io/ws/cannonball/

The Cannonball WebSocket provides real-time threat detection optimized for mobile devices. It maintains a persistent connection for receiving threat updates based on your GPS position.

Connection

Connect to the WebSocket endpoint. Upon successful connection, you will receive a session confirmation:

{
  "type": "session_started",
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2024-01-15T12:30:00Z"
}

Client Messages

Position Update

Send your GPS position to receive filtered threats:

{
  "type": "position_update",
  "lat": 34.0522,
  "lon": -118.2437,
  "heading": 180,
  "accuracy": 10
}
FieldTypeRequiredDescription
typestringYesMust be position_update
latfloatYesLatitude
lonfloatYesLongitude
headingfloatNoDevice heading in degrees (0-360)
accuracyfloatNoGPS accuracy in meters

Set Threat Radius

Adjust the threat detection radius:

{
  "type": "set_radius",
  "radius_nm": 15.0
}
FieldTypeRequiredDescription
typestringYesMust be set_radius
radius_nmfloatYesDetection radius in nautical miles (default: 25)

Response:

{
  "type": "radius_updated",
  "radius_nm": 15.0
}

Get Threats

Request current threats without updating position:

{
  "type": "get_threats"
}

This returns threats based on your last known position.

Server Messages

Threats Response

Received after position update or get_threats request:

{
  "type": "threats",
  "data": [
    {
      "hex": "A12345",
      "callsign": "N123HP",
      "category": "Law Enforcement",
      "description": "LAPD helicopter",
      "distance_nm": 2.5,
      "bearing": 45.0,
      "relative_bearing": 135.0,
      "direction": "NE",
      "altitude": 1500,
      "ground_speed": 85,
      "vertical_rate": 0,
      "trend": "approaching",
      "threat_level": "warning",
      "is_law_enforcement": true,
      "is_helicopter": true,
      "confidence": "high",
      "aircraft_type": "EC130",
      "registration": "N123HP",
      "lat": 34.0622,
      "lon": -118.2337
    }
  ],
  "count": 1,
  "position": {
    "lat": 34.0522,
    "lon": -118.2437
  },
  "timestamp": "2024-01-15T12:30:00Z"
}

Threat Object Fields

FieldTypeDescription
hexstringAircraft ICAO hex code
callsignstringAircraft callsign
categorystringThreat category (e.g., "Law Enforcement", "Helicopter")
descriptionstringHuman-readable description
distance_nmfloatDistance in nautical miles
bearingfloatAbsolute bearing in degrees
relative_bearingfloatBearing relative to device heading
directionstringCardinal direction (N, NE, E, SE, S, SW, W, NW)
altitudeintegerAltitude in feet
ground_speedintegerGround speed in knots
vertical_rateintegerVertical rate in feet/minute
trendstringMovement trend: approaching, departing, holding, unknown
threat_levelstringThreat level: info, warning, critical
is_law_enforcementbooleanWhether aircraft is identified as law enforcement
is_helicopterbooleanWhether aircraft is a helicopter
confidencestringIdentification confidence level
aircraft_typestringAircraft type code
registrationstringAircraft registration
latfloatAircraft latitude
lonfloatAircraft longitude

Threat Update (Broadcast)

Received when threat data is updated server-side:

{
  "type": "threats",
  "data": [...],
  "count": 3,
  "position": {...},
  "timestamp": "2024-01-15T12:30:00Z"
}

Error Messages

{
  "type": "error",
  "message": "lat and lon are required"
}

Request/Response Pattern

You can also use a request/response pattern for specific queries:

Request Threats

{
  "type": "request",
  "request_id": "req-123",
  "request_type": "threats",
  "params": {}
}

Response:

{
  "type": "response",
  "request_id": "req-123",
  "request_type": "threats",
  "data": {
    "threats": [...],
    "count": 3,
    "position": {...}
  }
}

Request Session Info

{
  "type": "request",
  "request_id": "req-456",
  "request_type": "session-info",
  "params": {}
}

Response:

{
  "type": "response",
  "request_id": "req-456",
  "request_type": "session-info",
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "position": {"lat": 34.0522, "lon": -118.2437},
    "heading": 180,
    "radius_nm": 25.0
  }
}

Example: JavaScript Client

const ws = new WebSocket('wss://api.skyspy.io/ws/cannonball/');

ws.onopen = () => {
  console.log('Connected to Cannonball');

  // Set detection radius
  ws.send(JSON.stringify({
    type: 'set_radius',
    radius_nm: 15
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);

  switch (data.type) {
    case 'session_started':
      console.log('Session:', data.session_id);
      break;

    case 'threats':
      updateThreatsDisplay(data.data);
      break;

    case 'radius_updated':
      console.log('Radius set to:', data.radius_nm);
      break;

    case 'error':
      console.error('Error:', data.message);
      break;
  }
};

// Send position updates from GPS
function sendPosition(lat, lon, heading) {
  ws.send(JSON.stringify({
    type: 'position_update',
    lat: lat,
    lon: lon,
    heading: heading
  }));
}

// Example: Update position every 5 seconds
navigator.geolocation.watchPosition((position) => {
  sendPosition(
    position.coords.latitude,
    position.coords.longitude,
    position.coords.heading
  );
}, null, { enableHighAccuracy: true });

Disconnection

When disconnecting, the server automatically cleans up cached position data. No explicit disconnect message is required.

Performance Notes

  • Position updates are recommended every 5-30 seconds depending on movement speed
  • Threat lists are sorted by distance (closest first) then by threat level
  • The server caches position data for 60 seconds
  • Threats outside the configured radius are filtered out