Event-Driven Communication – Bringing Physical Devices to Life

Beyond Traditional Web Communication

Most web applications are built around a simple pattern: users click buttons, forms submit data, pages load content. This request-response model works well for managing abstract information, but it falls short when your application needs to stay synchronized with physical devices that operate continuously in the real world.

Think about the difference between editing a document and monitoring a production system. With a document, changes only happen when users actively make them. With physical systems, events happen constantly—sensors report readings, operations complete, system state changes, equipment detects problems. Physical systems don’t wait for your application to ask “what’s happening?”—they need to actively communicate their changing state.

KOS bridges this gap through event-driven communication that makes your application feel naturally connected to the physical systems it represents.

From Polling to Push: A Different Mental Model

Traditional Web Pattern: Pull-Based Communication

In traditional web applications, the client controls when information flows:

User Interface → "What's the current temperature?" → Server
User Interface ← "23°C" ← Server
(5 seconds later)
User Interface → "What's the current temperature?" → Server  
User Interface ← "23°C" ← Server
(5 seconds later)
User Interface → "What's the current temperature?" → Server
User Interface ← "24°C" ← Server

This pattern creates applications that feel disconnected from reality. Users become accustomed to clicking refresh buttons and accepting delayed information as inevitable limitations of device control interfaces.

KOS Pattern: Push-Based Communication

KOS reverses this relationship. Instead of your application asking for updates, devices actively communicate their changing state:

Device: "Temperature changed to 24°C" → KOS Model → UI Updates Automatically
Device: "Operation started" → KOS Model → UI Shows Progress
Device: "Error detected" → KOS Model → UI Displays Alert

This creates applications that feel alive and responsive because they’re reacting to real events rather than polling for changes.

The Baseline + Delta Architecture

KOS uses a sophisticated communication pattern that combines the best of both approaches: HTTP-like semantics for initial data loading, followed by real-time delta updates for ongoing synchronization.

Phase 1: Baseline Loading

When your application starts up, models establish baseline knowledge using familiar HTTP-style requests:

import { kosModel, kosTopicHandler, IKosDataModel } from "@kosdev-code/kos-ui-sdk";
@kosModel({ modelTypeId: "sensor-monitor-model", singleton: false })
export class SensorMonitorModelImpl implements IKosDataModel {
  sensors: SensorReading[] = [];
  // Load complete current state during model initialization
  async load(): Promise<void> {
    // Baseline request: get current state of all sensors
    const [error, currentReadings] = await this.sensorService.getCurrentReadings();
    if (!error && currentReadings) {
      this.sensors = currentReadings;
      this.logger.info(`Loaded ${currentReadings.length} sensor baselines`);
    }
  }
}

The baseline establishes complete current state—all sensor readings, device statuses, and configuration values that existed when your application started. This ensures your application begins with accurate, comprehensive information about the physical world.

Phase 2: Delta Updates Through Topics

After the baseline is established, models receive incremental updates through topic handlers that respond to real-time device events:

const TOPIC_SENSOR_UPDATE = "/app/system/sensors/update";
@kosModel({ modelTypeId: "sensor-monitor-model", singleton: false }) 
export class SensorMonitorModelImpl implements IKosDataModel {
  // Real-time updates for individual sensor changes
  @kosTopicHandler({
    topic: TOPIC_SENSOR_UPDATE,
    websocket: true,
  })
  handleSensorUpdate(update: SensorUpdate): void {
    // Find and update specific sensor in baseline data
    const sensor = this.sensors.find(s => s.id === update.sensorId);
    if (sensor) {
      sensor.reading = update.newReading;
      sensor.timestamp = update.timestamp;
      // UI automatically updates to reflect new reading
      this.logger.debug(`Sensor ${update.sensorId} updated: ${update.newReading}`);
    }
  }
}

Delta updates are small, focused messages that carry only the information that has changed. Instead of transferring complete system state every few seconds, KOS sends tiny updates the moment changes occur.

Topic Handlers: Your Event Processing Pipeline

Topic handlers are the mechanism through which KOS models receive and process events from the physical world. They transform raw device communication into meaningful application behavior.

Understanding Topics as Event Channels

Topics in KOS are like specialized communication channels, each carrying specific types of information from devices:

// Static topic constants for different event types
const TOPIC_TEMPERATURE_READING = "/app/system/sensors/temperature";
const TOPIC_DOOR_STATE = "/app/system/doors/state";
const TOPIC_MAINTENANCE_ALERT = "/app/system/maintenance/alert";
const TOPIC_OPERATION_COMPLETE = "/app/system/operations/complete";
@kosModel({ modelTypeId: "facility-monitor-model", singleton: false })
export class FacilityMonitorModelImpl implements IKosDataModel {
  // Different handlers for different types of events
  @kosTopicHandler({
    topic: TOPIC_TEMPERATURE_READING,
    websocket: true,
  })
  handleTemperatureChange(reading: TemperatureReading): void {
    this.currentTemperature = reading.value;
    // Trigger business logic based on physical conditions
    if (reading.value > this.maxTemperature) {
      this.activateCooling();
    }
  }
  @kosTopicHandler({
    topic: TOPIC_DOOR_STATE,
    websocket: true,
  })
  handleDoorState(state: DoorState): void {
    this.isDoorOpen = state.isOpen;
    // Automatic safety response to physical door position
    if (state.isOpen) {
      this.pauseOperations();
      this.showMaintenanceMode();
    }
  }
}

Each topic handler creates a direct connection between physical device events and your application logic. When a temperature sensor detects a change, your model responds immediately. When a door opens, safety procedures activate automatically.

Dynamic Topics for Device-Specific Communication

Some situations require topics that adapt to specific device instances. KOS provides PropKey support for creating dynamic topic paths:

import { kosModel, kosTopicHandler, createPropKey, IKosDataModel } from "@kosdev-code/kos-ui-sdk";
const PROP_DEVICE_ID = createPropKey<DeviceModel>("deviceId");
const TOPIC_DEVICE_STATUS = `/app/system/device/${PROP_DEVICE_ID}/status`;
@kosModel({ modelTypeId: "device-model", singleton: false })
export class DeviceModelImpl implements IKosDataModel {
  deviceId: string;
  status: string = 'unknown';
  // Topic path dynamically includes this device's ID
  @kosTopicHandler({
    topic: TOPIC_DEVICE_STATUS,
    websocket: true,
  })
  handleStatusUpdate(status: DeviceStatus): void {
    this.status = status.state;
    if (status.state === 'error') {
      this.handleDeviceError(status.errorDetails);
    }
  }
  constructor(modelId: string, options: DeviceOptions) {
    this.deviceId = options.deviceId;
  }
}

This pattern enables precise communication where each device model receives only the events relevant to its specific device, reducing noise and improving performance.

Advanced Event Processing Patterns

KOS topic handlers support sophisticated event processing patterns that help you manage the complexities of real-world device communication.

Debouncing: Handling Rapid-Fire Events

Physical sensors often generate readings faster than user interfaces can meaningfully display them. Debouncing collects rapid events and processes them as batches:

const TOPIC_SENSOR_READINGS = "/app/system/sensors/readings";
@kosModel({ modelTypeId: "sensor-dashboard-model", singleton: false })
export class SensorDashboardModelImpl implements IKosDataModel {
  // Collect 1 second of rapid sensor readings
  @kosTopicHandler({
    topic: TOPIC_SENSOR_READINGS,
    websocket: true,
    debounce: 1000,
  })
  handleSensorReadings(readings: SensorReading[]): void {
    // Process batch of readings for more efficient UI updates
    const averageReading = readings.reduce((sum, r) => sum + r.value, 0) / readings.length;
    const latestReading = readings[readings.length - 1];
    this.averageTemperature = averageReading;
    this.currentTemperature = latestReading.value;
    this.readingHistory.push(...readings); // Preserve all data
    this.logger.info(`Processed ${readings.length} temperature readings`);
  }
}

The debounced handler receives an array containing all the readings that arrived during the debounce window. This prevents overwhelming the UI with hundreds of individual updates while preserving all the data for analysis.

Throttling: Limiting Processing Frequency

For performance-sensitive operations, throttling ensures processing happens at a manageable frequency:

const TOPIC_INVENTORY_SCAN = "/app/system/inventory/scan";
@kosModel({ modelTypeId: "inventory-processor-model", singleton: false })
export class InventoryProcessorModelImpl implements IKosDataModel {
  // Process inventory scans every 500ms maximum
  @kosTopicHandler({
    topic: TOPIC_INVENTORY_SCAN,
    websocket: true,
    throttle: 500,
  })
  handleInventoryScans(scans: InventoryScan[]): void {
    this.logger.info(`Processing ${scans.length} inventory scans together`);
    // Bulk process multiple scans for efficiency
    const bulkUpdate = this.consolidateScans(scans);
    this.updateInventoryFromBulk(bulkUpdate);
  }
}

Throttling is particularly useful for operations that benefit from batch processing or when you need to prevent expensive operations from overwhelming system resources.

Event Filtering: Focus on Relevant Information

Physical devices can generate large volumes of events, but your application may only care about specific conditions:

const TOPIC_SYSTEM_ALERTS = "/app/system/alerts";
@kosModel({ modelTypeId: "critical-alert-handler-model", singleton: false })
export class CriticalAlertHandlerImpl implements IKosDataModel {
  @kosTopicHandler({
    topic: TOPIC_SYSTEM_ALERTS,
    websocket: true,
    filter: (alert: SystemAlert) => alert.severity === "critical",
    transform: (alert: SystemAlert) => ({
      ...alert,
      processedAt: new Date(),
      escalated: true,
    }),
  })
  handleCriticalAlert(alert: ProcessedAlert): void {
    this.logger.error(`CRITICAL ALERT: ${alert.message}`);
    this.sendEmergencyNotification(alert);
    this.activateEmergencyProtocols();
  }
}

Filtering ensures your models only process events that require their specific attention, reducing computational overhead and keeping business logic focused.

Real-World Communication Scenarios

Manufacturing Operations: Coordinated Event Processing

Consider a complete manufacturing operation that involves multiple physical systems working together:

const TOPIC_OPERATION_START = "/app/system/operations/start";
const TOPIC_COMPONENT_STATUS = "/app/system/components/status";
const TOPIC_OPERATION_PROGRESS = "/app/system/operations/progress";
const TOPIC_OPERATION_COMPLETE = "/app/system/operations/complete";
@kosModel({ modelTypeId: "operation-coordinator-model", singleton: true })
export class OperationCoordinatorModelImpl implements IKosDataModel {
  currentOperation?: ManufacturingOperation;
  componentStatuses: Map<string, ComponentStatus> = new Map();
  // Operation initiation
  @kosTopicHandler({
    topic: TOPIC_OPERATION_START,
    websocket: true,
  })
  handleOperationStart(operation: ManufacturingOperation): void {
    this.currentOperation = operation;
    this.showOperationProgress(operation);
    this.logger.info(`Started operation: ${operation.operationType}`);
  }
  // Individual component status updates
  @kosTopicHandler({
    topic: TOPIC_COMPONENT_STATUS,
    websocket: true,
  })
  handleComponentStatus(status: ComponentStatus): void {
    this.componentStatuses.set(status.componentId, status);
    // Update overall operation status based on component coordination
    if (this.currentOperation && this.allComponentsActive()) {
      this.updateOperationPhase('active-processing');
    }
  }
  // Real-time progress updates
  @kosTopicHandler({
    topic: TOPIC_OPERATION_PROGRESS,
    websocket: true,
  })
  handleOperationProgress(progress: OperationProgress): void {
    if (this.currentOperation) {
      this.currentOperation.progress = progress.percentage;
      this.currentOperation.estimatedTimeRemaining = progress.timeRemaining;
      // UI automatically updates with current progress
    }
  }
  // Operation completion
  @kosTopicHandler({
    topic: TOPIC_OPERATION_COMPLETE,
    websocket: true,
  })
  handleOperationComplete(result: OperationResult): void {
    if (this.currentOperation) {
      this.currentOperation = undefined;
      this.hideOperationProgress();
      this.updateResourceLevels(result.consumedResources);
      this.logger.info(`Completed operation: ${result.outputQuantity} units`);
    }
  }
}

This example shows how multiple topic handlers coordinate to manage a complex physical operation. Each handler responds to specific events in the manufacturing process, creating an application that accurately reflects the real-time state of the physical system.

Sensor Data: Batching High-Frequency Updates

Sensor systems often generate high-frequency data streams. Debouncing batches these updates for efficient processing:

const TOPIC_SENSOR_DATA = "/app/system/sensors/data";
@kosModel({ modelTypeId: "sensor-monitor-model", singleton: false })
export class SensorMonitorModelImpl implements IKosDataModel {
  latestReadings: Map<string, SensorReading> = new Map();
  // Batch sensor readings for efficient processing
  @kosTopicHandler({
    topic: TOPIC_SENSOR_DATA,
    websocket: true,
    debounce: 2000, // Collect 2 seconds of readings
  })
  handleSensorReadings(readings: SensorReading[]): void {
    // Update latest reading for each sensor
    readings.forEach(reading => {
      this.latestReadings.set(reading.sensorId, reading);
    });
    // UI updates once with batch instead of hundreds of times
    this.logger.debug(`Processed ${readings.length} sensor readings`);
  }
}

This pattern prevents UI thrashing from high-frequency sensor updates while preserving all the data for your application’s needs.

Building Responsive Physical Interfaces

The event-driven communication patterns in KOS create applications that feel naturally connected to the physical world. Instead of generic web interfaces that happen to control hardware, you build experiences that reflect the unique characteristics and real-time nature of physical devices.

When a user sees a temperature reading in your interface, they know it’s current. When they watch a manufacturing operation, they see actual progress. When maintenance is needed, alerts appear immediately. This creates user interfaces that feel trustworthy and responsive because they’re built on communication patterns designed specifically for physical system integration.

The combination of baseline loading, delta updates, and intelligent event processing transforms web applications from tools that occasionally check on devices into live dashboards that maintain continuous synchronization with the physical world they represent.


Ready to see these communication patterns in action? Continue with Real-Time Data Synchronization to learn how KOS models handle complex data flows, or explore Advanced Topic Patterns for sophisticated event processing scenarios involving multiple devices and coordinated operations.