Skip to content

Basic Usage Examples

This page provides practical examples for common Urban Sky SDK use cases. These examples build on the Getting Started guide and show real-world implementation patterns.

Simple Connection Example

The most basic use case - connecting to the telemetry feed and logging balloon updates:

javascript
// Load the SDK
const response = await fetch('https://sdk.atmosys.com/runtime/js/current/loader.js')
eval(await response.text())

const sdk = await UrbanSkySDK.init({
  apiToken: 'your-api-token',
})

// Listen for balloon updates
sdk.on('balloon:update', update => {
  console.log(`Balloon ${update.balloonId} updated with ${update.devices.length} devices`)
})

// Listen for unassigned device updates
sdk.on('unassigned:devices', update => {
  console.log('Unassigned devices update received:', update)

  update.devices.forEach(device => {
    console.log(`Unassigned device ${device.deviceId} (${device.deviceType}):`)
    console.log(`  Location: ${device.lat}, ${device.lng}`)
    console.log(`  Altitude: ${device.altitude}m`)
    console.log(`  Time: ${device.timestamp}`)
  })
})

// Handle connection events
sdk.on('connected', () => console.log('Connected to Urban Sky'))
sdk.on('disconnected', () => console.log('Disconnected'))
sdk.on('error', error => console.error('Error:', error))

// Connect
await sdk.connect()
python
import requests

# Load the SDK
exec(requests.get('https://sdk.atmosys.com/runtime/py/current/loader.py').text)

async def main():
    # Initialize and connect
    sdk = await UrbanSkySDK.init({
        'apiToken': 'your-api-token'
    })

    def on_balloon_update(update):
    print(f"Balloon {update['balloon_id']} updated with {len(update['devices'])} devices")

def on_unassigned_devices(update):
    print(f"Unassigned devices update received: {len(update['devices'])} devices")
    for device in update['devices']:
        print(f"  {device['device_type']} {device['device_id']} at {device['lat']}, {device['lng']}")

    def on_connected():
        print("Connected to Urban Sky")

    def on_disconnected():
        print("Disconnected")

    def on_error(error):
        print(f"Error: {error}")

    # Register handlers
    sdk.on('balloon:update', on_balloon_update)
    sdk.on('unassigned:devices', on_unassigned_devices)
    sdk.on('connected', on_connected)
    sdk.on('disconnected', on_disconnected)
    sdk.on('error', on_error)

import asyncio
asyncio.run(main())

Testing Your Implementation

Before processing real data, test your implementation using our test endpoint:

javascript
// Send a test balloon update to verify your implementation
async function sendTestUpdate() {
  const testResponse = await fetch('https://api.ops.atmosys.com/sdk/test/balloon', {
    method: 'POST',
    headers: {
      'x-api-token': 'your-api-token',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({}),
  })

  if (testResponse.ok) {
    console.log('✅ Test message sent - you should receive it via your SDK listener')
  } else {
    console.error('❌ Test failed:', await testResponse.text())
  }
}

// Set up SDK first
const sdk = await UrbanSkySDK.init({ apiToken: 'your-api-token' })

sdk.on('balloon:update', update => {
  console.log('🎈 Received test update:', update.balloonId)
  console.log('✅ Your implementation is working!')
})

// Send test update
await sendTestUpdate()
python
import requests

# Send a test balloon update to verify your implementation
def send_test_update(api_token):
    url = 'https://api.ops.atmosys.com/sdk/test/balloon'
    headers = {
        'x-api-token': api_token,
        'Content-Type': 'application/json'
    }

    try:
        response = requests.post(url, headers=headers, json={})
        if response.status_code == 200:
            print('✅ Test message sent - you should receive it via your SDK listener')
        else:
            print(f'❌ Test failed with status: {response.status_code}')
    except Exception as e:
        print(f'❌ Test request failed: {e}')

# Set up SDK first
async def main():
    sdk = await UrbanSkySDK.init({
        'apiToken': 'your-api-token'
    })

    def on_balloon_update(update):
    print(f"🎈 Received test update: {update['balloon_id']}")
    print('✅ Your implementation is working!')

    sdk.on('balloon:update', on_balloon_update)

    # Send test update
    send_test_update('your-api-token')

import asyncio
asyncio.run(main())

Filtering by Device Type

Filter balloon updates to only process specific device types:

javascript
sdk.on('balloon:update', update => {
  // Filter for payload devices only
  const payloadDevices = update.devices.filter(device => device.deviceType === 'PLD')

  if (payloadDevices.length > 0) {
    console.log(`Payload update for balloon ${update.balloonId}`)
    payloadDevices.forEach(device => {
      console.log(`  Payload at: ${device.lat}, ${device.lng}`)
      if (device.altitude) {
        console.log(`  Altitude: ${device.altitude}m`)
      }
    })
  }
})
python
def on_balloon_update(update):
    # Filter for payload devices only
    payload_devices = [device for device in update['devices']
                      if device['device_type'] == 'PLD']

    if payload_devices:
        print(f"Payload update for balloon {update['balloon_id']}")
        for device in payload_devices:
            print(f"  Payload at: {device['lat']}, {device['lng']}")
            if device.get('altitude'):
                print(f"  Altitude: {device['altitude']}m")

Tracking Multiple Balloons

Keep track of multiple balloons and their latest positions:

javascript
class BalloonTracker {
  constructor() {
    this.balloons = new Map()
  }

  updateBalloon(update) {
    const balloonId = update.balloonId

    // Get or create balloon record
    if (!this.balloons.has(balloonId)) {
      this.balloons.set(balloonId, {
        id: balloonId,
        missionId: update.missionId,
        devices: new Map(),
        lastUpdate: null,
      })
    }

    const balloon = this.balloons.get(balloonId)

    // Update device positions
    update.devices.forEach(device => {
      balloon.devices.set(device.deviceId, {
        ...device,
        receivedAt: new Date(),
      })
    })

    balloon.lastUpdate = new Date()

    console.log(`Tracking ${this.balloons.size} balloons`)
    console.log(`Balloon ${balloonId} has ${balloon.devices.size} devices`)
  }

  getBalloonStatus(balloonId) {
    return this.balloons.get(balloonId)
  }

  getAllBalloons() {
    return Array.from(this.balloons.values())
  }
}

const tracker = new BalloonTracker()

sdk.on('balloon:update', update => {
  tracker.updateBalloon(update)
})
python
from datetime import datetime
import requests

# Load the SDK
exec(requests.get('https://sdk.atmosys.com/runtime/py/current/loader.py').text)

class BalloonTracker:
    def __init__(self):
        self.balloons = {}

    def update_balloon(self, update):
        balloon_id = update['balloon_id']

        # Get or create balloon record
        if balloon_id not in self.balloons:
            self.balloons[balloon_id] = {
                'id': balloon_id,
                'mission_id': update['mission_id'],
                'devices': {},
                'last_update': None
            }

        balloon = self.balloons[balloon_id]

        # Update device positions
        for device in update['devices']:
            balloon['devices'][device['device_id']] = {
                **device,
                'received_at': datetime.now()
            }

        balloon['last_update'] = datetime.now()

        print(f"Tracking {len(self.balloons)} balloons")
        print(f"Balloon {balloon_id} has {len(balloon['devices'])} devices")

    def get_balloon_status(self, balloon_id):
        return self.balloons.get(balloon_id)

    def get_all_balloons(self):
        return list(self.balloons.values())

async def main():
    tracker = BalloonTracker()

    sdk = await UrbanSkySDK.init({
        'apiToken': 'your-api-token'
    })

    def on_balloon_update(update):
        tracker.update_balloon(update)

    sdk.on('balloon:update', on_balloon_update)

import asyncio
asyncio.run(main())

Distance and Speed Calculations

Calculate distance traveled and speed between updates:

javascript
class LocationTracker {
  constructor() {
    this.lastPositions = new Map()
  }

  // Haversine formula for distance calculation
  calculateDistance(lat1, lon1, lat2, lon2) {
    const R = 6371000 // Earth's radius in meters
    const φ1 = (lat1 * Math.PI) / 180
    const φ2 = (lat2 * Math.PI) / 180
    const Δφ = ((lat2 - lat1) * Math.PI) / 180
    const Δλ = ((lon2 - lon1) * Math.PI) / 180

    const a =
      Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
      Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))

    return R * c // Distance in meters
  }

  processUpdate(update) {
    update.devices.forEach(device => {
      const deviceKey = `${update.balloonId}-${device.deviceId}`
      const lastPos = this.lastPositions.get(deviceKey)

      if (lastPos) {
        const distance = this.calculateDistance(lastPos.lat, lastPos.lng, device.lat, device.lng)

        const timeDiff = (new Date(device.timestamp) - new Date(lastPos.timestamp)) / 1000
        const speed = distance / timeDiff // m/s

        console.log(`Device ${device.deviceId}:`)
        console.log(`  Distance traveled: ${distance.toFixed(1)}m`)
        console.log(`  Speed: ${speed.toFixed(2)} m/s (${(speed * 3.6).toFixed(1)} km/h)`)

        if (device.altitude && lastPos.altitude) {
          const altitudeChange = device.altitude - lastPos.altitude
          const verticalSpeed = altitudeChange / timeDiff
          console.log(`  Vertical speed: ${verticalSpeed.toFixed(2)} m/s`)
        }
      }

      // Store current position
      this.lastPositions.set(deviceKey, {
        lat: device.lat,
        lng: device.lng,
        altitude: device.altitude,
        timestamp: device.timestamp,
      })
    })
  }
}

const locationTracker = new LocationTracker()

sdk.on('balloon:update', update => {
  locationTracker.processUpdate(update)
})
python
import math
import requests
from datetime import datetime

# Load the SDK
exec(requests.get('https://sdk.atmosys.com/runtime/py/current/loader.py').text)

class LocationTracker:
    def __init__(self):
        self.last_positions = {}

    def calculate_distance(self, lat1, lon1, lat2, lon2):
        """Haversine formula for distance calculation"""
        R = 6371000  # Earth's radius in meters
        φ1 = math.radians(lat1)
        φ2 = math.radians(lat2)
        Δφ = math.radians(lat2 - lat1)
        Δλ = math.radians(lon2 - lon1)

        a = (math.sin(Δφ/2) ** 2 +
             math.cos(φ1) * math.cos(φ2) * math.sin(Δλ/2) ** 2)
        c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))

        return R * c  # Distance in meters

    def process_update(self, update):
        for device in update['devices']:
            device_key = f"{update['balloon_id']}-{device['device_id']}"
            last_pos = self.last_positions.get(device_key)

            if last_pos:
                distance = self.calculate_distance(
                    last_pos['lat'], last_pos['lng'],
                    device['lat'], device['lng']
                )

                time_diff = (datetime.fromisoformat(device['timestamp'].replace('Z', '+00:00')) -
                           datetime.fromisoformat(last_pos['timestamp'].replace('Z', '+00:00'))).total_seconds()

                if time_diff > 0:
                    speed = distance / time_diff  # m/s

                    print(f"Device {device['device_id']}:")
                    print(f"  Distance traveled: {distance:.1f}m")
                    print(f"  Speed: {speed:.2f} m/s ({speed * 3.6:.1f} km/h)")

                    if device.get('altitude') and last_pos.get('altitude'):
                        altitude_change = device['altitude'] - last_pos['altitude']
                        vertical_speed = altitude_change / time_diff
                        print(f"  Vertical speed: {vertical_speed:.2f} m/s")

            # Store current position
            self.last_positions[device_key] = {
                'lat': device['lat'],
                'lng': device['lng'],
                'altitude': device.get('altitude'),
                'timestamp': device['timestamp']
            }

async def main():
    location_tracker = LocationTracker()

    sdk = await UrbanSkySDK.init({
        'apiToken': 'your-api-token'
    })

    def on_balloon_update(update):
        location_tracker.process_update(update)

    sdk.on('balloon:update', on_balloon_update)

import asyncio
asyncio.run(main())

Data Logging and Storage

Log balloon data for later analysis:

javascript
class DataLogger {
  constructor() {
    this.data = []
    this.maxEntries = 10000 // Keep last 10,000 entries
  }

  logUpdate(update) {
    const timestamp = new Date().toISOString()

    const logEntry = {
      timestamp,
      balloonId: update.balloonId,
      missionId: update.missionId,
      devices: update.devices.map(device => ({
        deviceId: device.deviceId,
        deviceType: device.deviceType,
        lat: device.lat,
        lng: device.lng,
        altitude: device.altitude,
      })),
    }

    this.data.push(logEntry)

    // Keep only recent entries
    if (this.data.length > this.maxEntries) {
      this.data = this.data.slice(-this.maxEntries)
    }

    console.log(`Logged update for balloon ${update.balloonId}`)
  }

  getRecentUpdates(count = 100) {
    return this.data.slice(-count)
  }

  getBalloonUpdates(balloonId) {
    return this.data.filter(entry => entry.balloonId === balloonId)
  }

  getStats() {
    const balloonIds = new Set(this.data.map(entry => entry.balloonId))
    return {
      totalUpdates: this.data.length,
      uniqueBalloons: balloonIds.size,
      oldestUpdate: this.data[0]?.timestamp,
      newestUpdate: this.data[this.data.length - 1]?.timestamp,
    }
  }
}

const logger = new DataLogger()

sdk.on('balloon:update', update => {
  logger.logUpdate(update)

  // Display stats periodically
  if (logger.data.length % 100 === 0) {
    const stats = logger.getStats()
    console.log('📊 Stats:', stats)
  }
})
python
import requests
from datetime import datetime

# Load the SDK
exec(requests.get('https://sdk.atmosys.com/runtime/py/current/loader.py').text)

class DataLogger:
    def __init__(self):
        self.data = []
        self.max_entries = 10000  # Keep last 10,000 entries

    def log_update(self, update):
        timestamp = datetime.now().isoformat()

        log_entry = {
            'timestamp': timestamp,
            'balloon_id': update['balloon_id'],
            'mission_id': update['mission_id'],
            'devices': [{
                'device_id': device['device_id'],
                'device_type': device['device_type'],
                'lat': device['lat'],
                'lng': device['lng'],
                'altitude': device.get('altitude')
            } for device in update['devices']]
        }

        self.data.append(log_entry)

        # Keep only recent entries
        if len(self.data) > self.max_entries:
            self.data = self.data[-self.max_entries:]

        print(f"Logged update for balloon {update['balloon_id']}")

    def get_recent_updates(self, count=100):
        return self.data[-count:]

    def get_balloon_updates(self, balloon_id):
        return [entry for entry in self.data if entry['balloon_id'] == balloon_id]

    def get_stats(self):
        balloon_ids = set(entry['balloon_id'] for entry in self.data)
        return {
            'total_updates': len(self.data),
            'unique_balloons': len(balloon_ids),
            'oldest_update': self.data[0]['timestamp'] if self.data else None,
            'newest_update': self.data[-1]['timestamp'] if self.data else None
        }

async def main():
    logger = DataLogger()

    sdk = await UrbanSkySDK.init({
        'apiToken': 'your-api-token'
    })

    def on_balloon_update(update):
        logger.log_update(update)

        # Display stats periodically
        if len(logger.data) % 100 == 0:
            stats = logger.get_stats()
            print('📊 Stats:', stats)

    sdk.on('balloon:update', on_balloon_update)

import asyncio
asyncio.run(main())

Real-time Alerts

Set up alerts for specific conditions:

javascript
class AlertSystem {
  constructor() {
    this.alertRules = []
  }

  addRule(name, condition, action) {
    this.alertRules.push({ name, condition, action })
  }

  checkAlerts(update) {
    update.devices.forEach(device => {
      this.alertRules.forEach(rule => {
        if (rule.condition(device, update)) {
          rule.action(device, update, rule.name)
        }
      })
    })
  }
}

const alerts = new AlertSystem()

// Add alert rules
alerts.addRule(
  'High Altitude',
  device => device.altitude && device.altitude > 30000,
  (device, update, ruleName) => {
    console.warn(`🚨 ${ruleName}: Device ${device.deviceId} at ${device.altitude}m altitude`)
  },
)

alerts.addRule(
  'Rapid Descent',
  device => device.altitude && device.altitude < 5000,
  (device, update, ruleName) => {
    console.warn(
      `🚨 ${ruleName}: Device ${device.deviceId} descending - altitude ${device.altitude}m`,
    )
  },
)

sdk.on('balloon:update', update => {
  alerts.checkAlerts(update)
})
python
class AlertSystem:
    def __init__(self):
        self.alert_rules = []

    def add_rule(self, name, condition, action):
        self.alert_rules.append({
            'name': name,
            'condition': condition,
            'action': action
        })

    def check_alerts(self, update):
        for device in update['devices']:
            for rule in self.alert_rules:
                if rule['condition'](device, update):
                    rule['action'](device, update, rule['name'])

alerts = AlertSystem()

# Add alert rules
def high_altitude_condition(device, update):
    return device.get('altitude') and device['altitude'] > 30000

def high_altitude_action(device, update, rule_name):
    print(f"🚨 {rule_name}: Device {device['device_id']} at {device['altitude']}m altitude")

def rapid_descent_condition(device, update):
    return device.get('altitude') and device['altitude'] < 5000

def rapid_descent_action(device, update, rule_name):
    print(f"🚨 {rule_name}: Device {device['device_id']} descending - altitude {device['altitude']}m")

alerts.add_rule('High Altitude', high_altitude_condition, high_altitude_action)
alerts.add_rule('Rapid Descent', rapid_descent_condition, rapid_descent_action)

def on_balloon_update(update):
    alerts.check_alerts(update)

sdk.on('balloon:update', on_balloon_update)

Error Handling and Reconnection

Robust error handling for production applications:

javascript
class RobustSDKWrapper {
  constructor(config) {
    this.config = config
    this.sdk = null
    this.reconnectAttempts = 0
    this.maxReconnectAttempts = 10
    this.reconnectDelay = 5000
    this.isConnected = false
    this.eventHandlers = new Map()
  }

  async connect() {
    try {
      this.sdk = await UrbanSkySDK.init(this.config)
      this.setupEventHandlers()

      await this.sdk.connect()
      this.isConnected = true
      this.reconnectAttempts = 0

      console.log('✅ Connected to Urban Sky')
    } catch (error) {
      console.error('❌ Connection failed:', error.message)
      await this.handleReconnection()
    }
  }

  setupEventHandlers() {
    this.sdk.on('connected', () => {
      this.isConnected = true
      console.log('🔗 SDK connected')
    })

    this.sdk.on('disconnected', () => {
      this.isConnected = false
      console.log('🔌 SDK disconnected')
      this.handleReconnection()
    })

    this.sdk.on('error', error => {
      console.error('🚨 SDK error:', error)

      if (error.code === 'AUTH_INVALID_TOKEN') {
        console.error('❌ Authentication failed - check your API token')
        return // Don't retry on auth errors
      }

      this.handleReconnection()
    })

    // Forward balloon updates to registered handlers
    this.sdk.on('balloon:update', update => {
      this.eventHandlers.get('balloon:update')?.forEach(handler => {
        try {
          handler(update)
        } catch (error) {
          console.error('Error in balloon update handler:', error)
        }
      })
    })
  }

  async handleReconnection() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error('❌ Max reconnection attempts reached')
      return
    }

    this.reconnectAttempts++
    const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1) // Exponential backoff

    console.log(
      `🔄 Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`,
    )

    setTimeout(async () => {
      await this.connect()
    }, delay)
  }

  on(event, handler) {
    if (!this.eventHandlers.has(event)) {
      this.eventHandlers.set(event, [])
    }
    this.eventHandlers.get(event).push(handler)
  }

  disconnect() {
    if (this.sdk) {
      this.sdk.disconnect()
      this.isConnected = false
    }
  }
}

// Usage
const robustSDK = new RobustSDKWrapper({
  apiToken: 'your-api-token',
})

robustSDK.on('balloon:update', update => {
  console.log(`Balloon update: ${update.balloonId}`)
})

await robustSDK.connect()
python
import asyncio
from typing import Dict, List, Callable

class RobustSDKWrapper:
    def __init__(self, config):
        self.config = config
        self.sdk = None
        self.reconnect_attempts = 0
        self.max_reconnect_attempts = 10
        self.reconnect_delay = 5
        self.is_connected = False
        self.event_handlers = {}

    async def connect(self):
        try:
            self.sdk = UrbanSkySDK(**self.config)
            self.setup_event_handlers()

            await self.sdk.connect()
            self.is_connected = True
            self.reconnect_attempts = 0

            print("✅ Connected to Urban Sky")

        except Exception as error:
            print(f"❌ Connection failed: {error}")
            await self.handle_reconnection()

    def setup_event_handlers(self):
        def on_connected():
            self.is_connected = True
            print("🔗 SDK connected")

        def on_disconnected():
            self.is_connected = False
            print("🔌 SDK disconnected")
            asyncio.create_task(self.handle_reconnection())

        def on_error(error):
            print(f"🚨 SDK error: {error}")

            if hasattr(error, 'code') and error.code == 'AUTH_INVALID_TOKEN':
                print("❌ Authentication failed - check your API token")
                return  # Don't retry on auth errors

            asyncio.create_task(self.handle_reconnection())

        def on_balloon_update(update):
            handlers = self.event_handlers.get('balloon:update', [])
            for handler in handlers:
                try:
                    handler(update)
                except Exception as error:
                    print(f"Error in balloon update handler: {error}")

        self.sdk.on('connected', on_connected)
        self.sdk.on('disconnected', on_disconnected)
        self.sdk.on('error', on_error)
        self.sdk.on('balloon:update', on_balloon_update)

    async def handle_reconnection(self):
        if self.reconnect_attempts >= self.max_reconnect_attempts:
            print("❌ Max reconnection attempts reached")
            return

        self.reconnect_attempts += 1
        delay = self.reconnect_delay * (2 ** (self.reconnect_attempts - 1))  # Exponential backoff

        print(f"🔄 Reconnection attempt {self.reconnect_attempts}/{self.max_reconnect_attempts} in {delay}s")

        await asyncio.sleep(delay)
        await self.connect()

    def on(self, event, handler):
        if event not in self.event_handlers:
            self.event_handlers[event] = []
        self.event_handlers[event].append(handler)

    def disconnect(self):
        if self.sdk:
            self.sdk.disconnect()
            self.is_connected = False

# Usage
robust_sdk = RobustSDKWrapper({
    'api_token': 'your-api-token'
})

def on_balloon_update(update):
    print(f"Balloon update: {update['balloon_id']}")

robust_sdk.on('balloon:update', on_balloon_update)

await robust_sdk.connect()

Next Steps

These examples provide a foundation for building robust balloon tracking applications. For more advanced use cases, see:


Need help? Check our troubleshooting guide or contact support.