Appearance
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
- Error Handling Guide - Comprehensive error handling strategies
- JavaScript SDK - Full JavaScript API reference
- Python SDK - Full Python API reference