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