Skip to content

Balloon Updates Examples

This page demonstrates various ways to handle balloon position updates using the Urban Sky SDK.

Basic Balloon Tracking

JavaScript

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

// Simple balloon tracking
sdk.on('balloon:update', (update) => {
  console.log(`📍 Balloon ${update.balloonId} moved:`)
  
  update.devices.forEach(device => {
    console.log(`  Device ${device.deviceId}: ${device.lat}, ${device.lng}`)
  })
})

Python

python
import urllib.request

# Load the SDK
with urllib.request.urlopen('https://sdk.atmosys.com/runtime/py/current/loader.py') as response:
    exec(response.read().decode('utf-8'))

sdk = UrbanSkySDK(api_token='your-api-token-here')

def on_balloon_update(update):
    print(f"📍 Balloon {update['balloon_id']} moved:")
    
    for device in update['devices']:
        print(f"  Device {device['device_id']}: {device['lat']}, {device['lng']}")

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

Data Storage and Analysis

JavaScript with File System Storage

javascript
const fs = require('fs')
const path = require('path')

class BalloonDataLogger {
  constructor(dataDir = './balloon-data') {
    this.dataDir = dataDir
    this.maxEntriesPerBalloon = 1000
    
    // Ensure data directory exists
    if (!fs.existsSync(this.dataDir)) {
      fs.mkdirSync(this.dataDir, { recursive: true })
    }
  }
  
  getDataFilePath(balloonId) {
    return path.join(this.dataDir, `${balloonId}.json`)
  }
  
  loadBalloonData(balloonId) {
    const filePath = this.getDataFilePath(balloonId)
    
    if (!fs.existsSync(filePath)) {
      return []
    }
    
    try {
      const data = fs.readFileSync(filePath, 'utf8')
      return JSON.parse(data)
    } catch (error) {
      console.error(`Error loading data for balloon ${balloonId}:`, error)
      return []
    }
  }
  
  saveBalloonData(balloonId, data) {
    const filePath = this.getDataFilePath(balloonId)
    
    try {
      fs.writeFileSync(filePath, JSON.stringify(data, null, 2))
    } catch (error) {
      console.error(`Error saving data for balloon ${balloonId}:`, error)
    }
  }
  
  logBalloonUpdate(update) {
    const timestamp = new Date().toISOString()
    const balloonId = update.balloonId
    
    // Load existing data
    const existingData = this.loadBalloonData(balloonId)
    
    // Add new entry
    const newEntry = {
      timestamp,
      missionId: update.missionId,
      devices: update.devices.map(device => ({
        deviceId: device.deviceId,
        deviceType: device.deviceType,
        lat: device.lat,
        lng: device.lng,
        altitude: device.altitude,
        deviceTimestamp: device.timestamp
      }))
    }
    
    existingData.push(newEntry)
    
    // Keep only last N entries
    if (existingData.length > this.maxEntriesPerBalloon) {
      existingData.splice(0, existingData.length - this.maxEntriesPerBalloon)
    }
    
    // Save back to file
    this.saveBalloonData(balloonId, existingData)
    
    console.log(`Logged update for balloon ${balloonId} (${existingData.length} total entries)`)
  }
  
  getBalloonTrack(balloonId) {
    return this.loadBalloonData(balloonId)
  }
  
  getAllBalloons() {
    try {
      const files = fs.readdirSync(this.dataDir)
      return files
        .filter(file => file.endsWith('.json'))
        .map(file => path.basename(file, '.json'))
    } catch (error) {
      console.error('Error reading balloon data directory:', error)
      return []
    }
  }
  
  getStats() {
    const balloons = this.getAllBalloons()
    let totalUpdates = 0
    let oldestUpdate = null
    let newestUpdate = null
    
    balloons.forEach(balloonId => {
      const track = this.loadBalloonData(balloonId)
      totalUpdates += track.length
      
      if (track.length > 0) {
        const firstTimestamp = track[0].timestamp
        const lastTimestamp = track[track.length - 1].timestamp
        
        if (!oldestUpdate || firstTimestamp < oldestUpdate) {
          oldestUpdate = firstTimestamp
        }
        if (!newestUpdate || lastTimestamp > newestUpdate) {
          newestUpdate = lastTimestamp
        }
      }
    })
    
    return {
      balloonCount: balloons.length,
      totalUpdates,
      oldestUpdate,
      newestUpdate
    }
  }
  
  exportAllData(outputFile = 'balloon-data-export.json') {
    const allData = {}
    
    this.getAllBalloons().forEach(balloonId => {
      allData[balloonId] = this.loadBalloonData(balloonId)
    })
    
    try {
      fs.writeFileSync(outputFile, JSON.stringify(allData, null, 2))
      console.log(`Exported all data to ${outputFile}`)
    } catch (error) {
      console.error('Error exporting data:', error)
    }
  }
}

// Usage
const logger = new BalloonDataLogger('./balloon-tracking-data')

sdk.on('balloon:update', (update) => {
  logger.logBalloonUpdate(update)
  
  // Display stats every 10 updates
  const stats = logger.getStats()
  if (stats.totalUpdates % 10 === 0) {
    console.log(`📊 Tracking ${stats.balloonCount} balloons, ${stats.totalUpdates} total updates`)
  }
})

// Export data on shutdown
process.on('SIGINT', () => {
  console.log('Exporting data before shutdown...')
  logger.exportAllData()
  process.exit(0)
})

Python with File System Storage

javascript
import json
import os
from datetime import datetime

class BalloonDataLogger:
    def __init__(self, data_dir='./balloon-data'):
        self.data_dir = data_dir
        self.max_entries_per_balloon = 1000
        
        # Ensure data directory exists
        os.makedirs(self.data_dir, exist_ok=True)
    
    def get_data_file_path(self, balloon_id):
        return os.path.join(self.data_dir, f"{balloon_id}.json")
    
    def load_balloon_data(self, balloon_id):
        file_path = self.get_data_file_path(balloon_id)
        
        if not os.path.exists(file_path):
            return []
        
        try:
            with open(file_path, 'r') as f:
                return json.load(f)
        except Exception as error:
            print(f"Error loading data for balloon {balloon_id}: {error}")
            return []
    
    def save_balloon_data(self, balloon_id, data):
        file_path = self.get_data_file_path(balloon_id)
        
        try:
            with open(file_path, 'w') as f:
                json.dump(data, f, indent=2)
        except Exception as error:
            print(f"Error saving data for balloon {balloon_id}: {error}")
    
    def log_balloon_update(self, update):
        """Store a balloon update to file"""
        timestamp = datetime.now().isoformat()
        balloon_id = update['balloon_id']
        
        # Load existing data
        existing_data = self.load_balloon_data(balloon_id)
        
        # Add new entry
        new_entry = {
            'timestamp': timestamp,
            'mission_id': update['mission_id'],
            'devices': [{
                'device_id': d['device_id'],
                'device_type': d['device_type'],
                'lat': d['lat'],
                'lng': d['lng'],
                'altitude': d.get('altitude'),
                'device_timestamp': d['timestamp']
            } for d in update['devices']]
        }
        
        existing_data.append(new_entry)
        
        # Keep only last N entries
        if len(existing_data) > self.max_entries_per_balloon:
            existing_data = existing_data[-self.max_entries_per_balloon:]
        
        # Save back to file
        self.save_balloon_data(balloon_id, existing_data)
        
        print(f"Logged update for balloon {balloon_id} ({len(existing_data)} total entries)")
    
    def get_balloon_track(self, balloon_id):
        """Get position history for a balloon"""
        return self.load_balloon_data(balloon_id)
    
    def get_all_balloons(self):
        """Get list of all tracked balloons"""
        try:
            files = os.listdir(self.data_dir)
            return [os.path.splitext(f)[0] for f in files if f.endswith('.json')]
        except Exception as error:
            print(f"Error reading balloon data directory: {error}")
            return []
    
    def get_stats(self):
        """Get tracking statistics"""
        balloons = self.get_all_balloons()
        total_updates = 0
        oldest_update = None
        newest_update = None
        
        for balloon_id in balloons:
            track = self.load_balloon_data(balloon_id)
            total_updates += len(track)
            
            if track:
                first_timestamp = track[0]['timestamp']
                last_timestamp = track[-1]['timestamp']
                
                if not oldest_update or first_timestamp < oldest_update:
                    oldest_update = first_timestamp
                if not newest_update or last_timestamp > newest_update:
                    newest_update = last_timestamp
        
        return {
            'balloon_count': len(balloons),
            'total_updates': total_updates,
            'oldest_update': oldest_update,
            'newest_update': newest_update
        }
    
    def export_all_data(self, output_file='balloon-data-export.json'):
        """Export all data to a single JSON file"""
        all_data = {}
        
        for balloon_id in self.get_all_balloons():
            all_data[balloon_id] = self.load_balloon_data(balloon_id)
        
        try:
            with open(output_file, 'w') as f:
                json.dump(all_data, f, indent=2)
            print(f"Exported all data to {output_file}")
        except Exception as error:
            print(f"Error exporting data: {error}")

# Usage
logger = BalloonDataLogger('./balloon-tracking-data')

def on_balloon_update(update):
    logger.log_balloon_update(update)
    
    # Display stats every 10 updates
    stats = logger.get_stats()
    if stats['total_updates'] % 10 == 0:
        print(f"📊 Tracking {stats['balloon_count']} balloons, {stats['total_updates']} total updates")

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

# Export data on shutdown
import signal
import sys

def signal_handler(sig, frame):
    print('Exporting data before shutdown...')
    logger.export_all_data()
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)

Performance Monitoring

JavaScript Performance Tracker

javascript
class PerformanceTracker {
  constructor() {
    this.metrics = {
      updateCount: 0,
      startTime: Date.now(),
      lastUpdate: null,
      averageInterval: 0,
      updateIntervals: []
    }
  }
  
  trackUpdate(update) {
    const now = Date.now()
    this.metrics.updateCount++
    
    if (this.metrics.lastUpdate) {
      const interval = now - this.metrics.lastUpdate
      this.metrics.updateIntervals.push(interval)
      
      // Keep only last 100 intervals for average calculation
      if (this.metrics.updateIntervals.length > 100) {
        this.metrics.updateIntervals.shift()
      }
      
      this.metrics.averageInterval = this.metrics.updateIntervals.reduce((a, b) => a + b, 0) / this.metrics.updateIntervals.length
    }
    
    this.metrics.lastUpdate = now
  }
  
  getStats() {
    const now = Date.now()
    const runtimeSeconds = (now - this.metrics.startTime) / 1000
    const updatesPerSecond = this.metrics.updateCount / runtimeSeconds
    
    return {
      runtime: runtimeSeconds,
      totalUpdates: this.metrics.updateCount,
      updatesPerSecond: updatesPerSecond.toFixed(2),
      averageInterval: Math.round(this.metrics.averageInterval),
      lastUpdate: this.metrics.lastUpdate ? new Date(this.metrics.lastUpdate).toISOString() : null
    }
  }
}

// Usage
const perfTracker = new PerformanceTracker()

sdk.on('balloon:update', (update) => {
  perfTracker.trackUpdate(update)
  
  // Log performance stats every 50 updates
  if (perfTracker.metrics.updateCount % 50 === 0) {
    const stats = perfTracker.getStats()
    console.log('📈 Performance Stats:', stats)
  }
})

Error Recovery

Robust Connection Handling

javascript
class RobustSDKConnection {
  constructor(apiToken) {
    this.apiToken = apiToken
    this.sdk = null
    this.reconnectAttempts = 0
    this.maxReconnectAttempts = 5
    this.reconnectDelay = 1000 // Start with 1 second
    this.isConnecting = false
  }
  
  async connect() {
    if (this.isConnecting) return
    this.isConnecting = true
    
    try {
      this.sdk = await UrbanSkySDK.init({
        apiToken: this.apiToken
      })
      
      this.setupEventHandlers()
      
      console.log('✅ Connected successfully')
      this.reconnectAttempts = 0
      this.reconnectDelay = 1000
      
    } catch (error) {
      console.error('❌ Connection failed:', error)
      this.scheduleReconnect()
    } finally {
      this.isConnecting = false
    }
  }
  
  setupEventHandlers() {
    this.sdk.on('balloon:update', (update) => {
      this.onBalloonUpdate(update)
    })
    
    this.sdk.on('disconnected', () => {
      console.log('⚠️ Disconnected from Urban Sky')
      this.scheduleReconnect()
    })
    
    this.sdk.on('error', (error) => {
      console.error('❌ SDK Error:', error)
      if (error.code === 'AUTH_INVALID_TOKEN') {
        console.error('🔑 Invalid token - stopping reconnection attempts')
        return // Don't reconnect on auth errors
      }
      this.scheduleReconnect()
    })
  }
  
  scheduleReconnect() {
    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(`🔄 Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`)
    
    setTimeout(() => {
      this.connect()
    }, delay)
  }
  
  onBalloonUpdate(update) {
    // Your balloon update handling logic here
    console.log(`📍 Balloon ${update.balloonId} update received`)
  }
  
  disconnect() {
    if (this.sdk) {
      this.sdk.disconnect()
    }
  }
}

// Usage
const connection = new RobustSDKConnection('your-api-token')
connection.connect()

// Graceful shutdown (Node.js)
process.on('SIGINT', () => {
  connection.disconnect()
  process.exit(0)
})

Testing Your Implementation

Use our test endpoint to verify your balloon update handling:

JavaScript Test

javascript
// Send a test balloon update
async function sendTestUpdate() {
  const testResponse = await fetch('https://api.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 to receive the test message
sdk.on('balloon:update', (update) => {
  console.log('🎈 Received balloon update:', update.balloonId)
  // Your processing logic here
})

await sdk.connect()
await sendTestUpdate()

Python Test

python
import urllib.request
import json

def send_test_update(api_token):
    url = 'https://api.atmosys.com/sdk/test/balloon'
    headers = {
        'x-api-token': api_token,
        'Content-Type': 'application/json'
    }
    data = json.dumps({}).encode('utf-8')
    
    req = urllib.request.Request(url, data=data, headers=headers, method='POST')
    
    try:
        with urllib.request.urlopen(req) as response:
            if response.status == 200:
                print('✅ Test message sent - you should receive it via your SDK listener')
            else:
                print(f'❌ Test failed with status: {response.status}')
    except Exception as e:
        print(f'❌ Test request failed: {e}')

# Set up SDK to receive the test message
def on_balloon_update(update):
    print(f"🎈 Received balloon update: {update['balloon_id']}")
    # Your processing logic here

sdk.on('balloon:update', on_balloon_update)
await sdk.connect()
send_test_update('your-api-token')

Next Steps