Code Examples
Complete working examples demonstrating common patterns for building TelemetryOS applications with settings and render views.
Code Examples
Complete working examples demonstrating common patterns.
Weather Widget
A complete weather application with settings and display.
Project Structure
weather-widget/
├── src/
│ ├── views/
│ │ ├── Settings.tsx
│ │ └── Render.tsx
│ ├── App.tsx
│ └── index.tsx
├── telemetry.config.json
└── package.json
Configuration
telemetry.config.json:
{
"name": "weather-widget",
"version": "1.0.0",
"displayName": "Weather Widget",
"description": "Display weather forecasts for configured cities",
"mountPoints": {
"render": "/render",
"settings": "/settings"
},
"devServer": {
"runCommand": "vite --port 3000",
"url": "http://localhost:3000"
}
}Settings View
src/views/Settings.tsx:
import { useEffect, useState } from 'react';
import { configure, store } from '@telemetryos/sdk';
configure('weather-widget');
interface WeatherConfig {
city: string;
units: 'F' | 'C';
refreshInterval: number;
}
export function Settings() {
const [config, setConfig] = useState<WeatherConfig>({
city: '',
units: 'F',
refreshInterval: 300000 // 5 minutes
});
const [saving, setSaving] = useState(false);
useEffect(() => {
// Load existing configuration
store().instance.get<WeatherConfig>('config').then(saved => {
if (saved) setConfig(saved);
});
}, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSaving(true);
try {
await store().instance.set('config', config);
alert('Settings saved successfully!');
} catch (error) {
alert('Failed to save settings');
console.error(error);
} finally {
setSaving(false);
}
};
return (
<div className="settings-container">
<h2>Weather Settings</h2>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>City:</label>
<input
type="text"
value={config.city}
onChange={(e) => setConfig({ ...config, city: e.target.value })}
placeholder="Enter city name"
required
/>
</div>
<div className="form-group">
<label>Units:</label>
<select
value={config.units}
onChange={(e) => setConfig({ ...config, units: e.target.value as 'F' | 'C' })}
>
<option value="F">Fahrenheit</option>
<option value="C">Celsius</option>
</select>
</div>
<div className="form-group">
<label>Refresh Interval (seconds):</label>
<input
type="number"
value={config.refreshInterval / 1000}
onChange={(e) => setConfig({
...config,
refreshInterval: parseInt(e.target.value) * 1000
})}
min={60}
/>
</div>
<button type="submit" disabled={saving}>
{saving ? 'Saving...' : 'Save Settings'}
</button>
</form>
</div>
);
}Render View
src/views/Render.tsx:
import { useEffect, useState } from 'react';
import { configure, store, proxy } from '@telemetryos/sdk';
configure('weather-widget');
interface WeatherConfig {
city: string;
units: 'F' | 'C';
refreshInterval: number;
}
interface WeatherData {
temp: number;
condition: string;
humidity: number;
windSpeed: number;
}
export function Render() {
const [config, setConfig] = useState<WeatherConfig | null>(null);
const [weather, setWeather] = useState<WeatherData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
// Subscribe to configuration changes
const handler = (newConfig: WeatherConfig) => {
setConfig(newConfig);
if (newConfig?.city) {
fetchWeather(newConfig);
}
};
store().instance.subscribe('config', handler);
// Load initial configuration
store().instance.get<WeatherConfig>('config').then(saved => {
setLoading(false);
if (saved) {
setConfig(saved);
fetchWeather(saved);
}
});
return () => {
store().instance.unsubscribe('config', handler);
};
}, []);
useEffect(() => {
if (!config) return;
const interval = setInterval(() => {
fetchWeather(config);
}, config.refreshInterval);
return () => clearInterval(interval);
}, [config]);
const fetchWeather = async (cfg: WeatherConfig) => {
try {
setError(null);
const apiKey = 'your-api-key';
const units = cfg.units === 'F' ? 'imperial' : 'metric';
const url = `https://api.openweathermap.org/data/2.5/weather?q=${cfg.city}&units=${units}&appid=${apiKey}`;
const response = await proxy().fetch(url);
if (!response.ok) {
throw new Error('Weather fetch failed');
}
const data = await response.json();
setWeather({
temp: Math.round(data.main.temp),
condition: data.weather[0].main,
humidity: data.main.humidity,
windSpeed: Math.round(data.wind.speed)
});
} catch (err) {
setError('Failed to fetch weather data');
console.error(err);
}
};
if (loading) {
return <div className="render-container loading">Loading...</div>;
}
if (!config || !config.city) {
return (
<div className="render-container">
<p>Please configure a city in settings</p>
</div>
);
}
if (error) {
return (
<div className="render-container error">
<p>{error}</p>
</div>
);
}
if (!weather) {
return <div className="render-container loading">Fetching weather...</div>;
}
return (
<div className="render-container">
<h1>{config.city}</h1>
<div className="weather-display">
<div className="temperature">
{weather.temp}°{config.units}
</div>
<div className="condition">{weather.condition}</div>
<div className="details">
<div>Humidity: {weather.humidity}%</div>
<div>Wind: {weather.windSpeed} {config.units === 'F' ? 'mph' : 'km/h'}</div>
</div>
</div>
</div>
);
}Media Gallery
Display images from TelemetryOS media library.
import { useEffect, useState } from 'react';
import { configure, media, store } from '@telemetryos/sdk';
import type { MediaContent } from '@telemetryos/sdk';
configure('media-gallery');
export function MediaGallery() {
const [images, setImages] = useState<MediaContent[]>([]);
const [currentIndex, setCurrentIndex] = useState(0);
const [duration, setDuration] = useState(5000);
useEffect(() => {
// Load configuration
store().instance.get<{ tag: string; duration: number }>('config').then(config => {
if (config) {
setDuration(config.duration);
loadMedia(config.tag);
}
});
}, []);
useEffect(() => {
if (images.length === 0) return;
const timer = setInterval(() => {
setCurrentIndex((prev) => (prev + 1) % images.length);
}, duration);
return () => clearInterval(timer);
}, [images, duration]);
const loadMedia = async (tag: string) => {
try {
const items = await media().getAllByTag(tag);
const imageItems = items.filter(item =>
item.contentType.startsWith('image/')
);
setImages(imageItems);
} catch (error) {
console.error('Failed to load media:', error);
}
};
if (images.length === 0) {
return <div>No images found</div>;
}
const current = images[currentIndex];
return (
<div className="gallery">
<img
src={current.publicUrls[0]}
alt={current.name}
className="gallery-image"
/>
<div className="gallery-caption">
{current.name}
</div>
</div>
);
}Interactive Dashboard
Dashboard that embeds multiple widgets.
import { useEffect, useState } from 'react';
import { configure, applications } from '@telemetryos/sdk';
import type { Application } from '@telemetryos/sdk';
configure('dashboard-app');
export function Dashboard() {
const [widgets, setWidgets] = useState<Application[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadWidgets();
}, []);
const loadWidgets = async () => {
try {
// Find all dashboard widgets
const apps = await applications().getAllByMountPoint('dashboard-widget');
// Prepare dependencies
const widgetNames = apps.map(w => w.name);
const deps = await applications().setDependencies(widgetNames);
// Only use ready widgets
const readyWidgets = apps.filter(w => deps.ready.includes(w.name));
setWidgets(readyWidgets);
} catch (error) {
console.error('Failed to load widgets:', error);
} finally {
setLoading(false);
}
};
if (loading) {
return <div>Loading dashboard...</div>;
}
if (widgets.length === 0) {
return <div>No widgets available</div>;
}
return (
<div className="dashboard-grid">
{widgets.map(widget => (
<div key={widget.name} className="dashboard-widget">
<iframe
src={widget.mountPoints['dashboard-widget'].path}
title={widget.name}
frameBorder="0"
style={{
width: '100%',
height: '100%',
border: 'none'
}}
/>
</div>
))}
</div>
);
}Next Steps
- Mount Points - Understand application structure
- Storage Methods - Data persistence patterns
- Platform Methods - Integration methods
Updated 21 days ago
What’s Next