The Dispense SDK integrates with KOS’s trouble system to provide automatic trouble tracking on hardware models. This guide covers Dispense SDK-specific trouble integration patterns.
For comprehensive trouble system documentation including resolution operations, bulk operations, and trouble handlers, see the Trouble System Integration guide.
TroubleAware Decorator
Hardware models in the Dispense SDK use the @kosTroubleAware() decorator to automatically receive trouble notifications.
What It Provides
The decorator adds:
- Automatic
TroubleContainerdependency injection - Trouble filtering based on model path
- Trouble grouping by type via
troublesByType - Pour deferral logic via
shouldDefer
Dispense SDK Models Using TroubleAware
HolderModel– Tracks holder-related troublesPumpModel– Tracks pump-related troublesBoardModel– Tracks board-related troubles
Accessing Troubles on Models
Models decorated with @kosTroubleAware() expose trouble information:
troubles Property
Array of all troubles affecting the model:
const holder = assembly.holderContainer.getModel("holder-path");
// Access all troubles for this holder
holder.troubles.forEach((trouble) => {
console.log(trouble.title); // User-friendly title
console.log(trouble.subtitle); // Additional detail
console.log(trouble.type); // Trouble type identifier
console.log(trouble.resolvable); // Can be resolved programmatically
console.log(trouble.color); // UI color indicator
console.log(trouble.rank); // Severity ranking
});troublesByType Property
Troubles indexed by type for quick access:
const holder = assembly.holderContainer.getModel("holder-path");
// Check for specific trouble types
const enjoyByTroubles = holder.troublesByType["EnjoyByTrouble"] || [];
const agitationTroubles =
holder.troublesByType["InsufficientAgitationTrouble"] || [];
if (enjoyByTroubles.length > 0) {
console.log("Ingredient approaching expiration date");
}shouldDefer Property
Boolean indicating if operations should be blocked:
const holder = assembly.holderContainer.getModel("holder-path");
if (holder.shouldDefer) {
console.warn("Cannot pour - holder has blocking troubles");
return;
}
// Safe to pour
await holder.performIntent({ intent: "PRIME" });HolderModel Convenience Methods
HolderModel provides trouble-specific helpers:
const holder = assembly.holderContainer.getModel("holder-path");
// Quick check for enjoy-by warnings
if (holder.hasEnjoyByWarning) {
showWarningDialog("Ingredient expiring soon");
}
// Check pour capability (abstracts trouble blocking logic)
if (holder.canPour) {
await holder.performIntent({ intent: "CALIBRATE" });
}SDK Trouble Mappers
The SDK registers extension points to connect device troubles to models.
EXTENSION_TROUBLE_MAPPER
Maps troubles to model paths based on the trouble’s iface (interface).
SDK Registrations: The Dispense SDK automatically registers mappers for:
"holder"– Maps holder troubles to HolderModel instances"pump"– Maps pump troubles to PumpModel instances"board"– Maps board troubles to BoardModel instances"container"– Maps container troubles to IngredientContainer
When to add custom mappers: Only needed when introducing new trouble ifaces not already handled by the SDK.
Example for custom iface:
import {
ExtensionManager,
EXTENSION_TROUBLE_MAPPER,
} from "@kosdev-code/kos-ui-sdk";
ExtensionManager[EXTENSION_TROUBLE_MAPPER].register(
"customComponent",
async (trouble: TroubleModel<CustomTroubleData>) => {
const result: string[] = [];
result.push(...trouble.data.componentPaths);
return result; // Model paths affected by this trouble
}
);EXTENSION_TROUBLE_DATA_MAPPER
Enriches trouble data with display-friendly context.
Purpose: Transforms or augments trouble data for UI display. Common use cases:
- Looking up related models to add names (e.g.,
ingredientId→ingredientName) - Formatting data (e.g., timestamps, units)
- Adding computed values
- Providing UI-specific context
How it works:
- Mappers registered for each of the trouble’s ifaces execute first
- Mappers registered for the specific trouble type execute second
- All returned data is merged into the trouble data
SDK Registrations: The Dispense SDK registers data mappers for:
"holder"iface – Adds holder names"pump"iface – Adds pump and ingredient names"container"iface – Adds ingredient names"FirmwareUpdateInProgressTrouble"type – Adds board/device names
Example 1: Mapper for an iface (applied to all troubles with that iface):
import {
ExtensionManager,
EXTENSION_TROUBLE_DATA_MAPPER,
getKosModelSync,
} from "@kosdev-code/kos-ui-sdk";
// Enrich all troubles with "container" iface
ExtensionManager[EXTENSION_TROUBLE_DATA_MAPPER].register(
"container",
async (trouble: TroubleModel<ContainerTroubleData>) => {
// Container iface has ingredientId, but not ingredientName
if (trouble.data.ingredients.length > 0) {
const ingredientId = trouble.data.ingredients[0];
const { model } = getKosModelSync<IngredientModel>(ingredientId);
return {
ingredientName: model?.displayName ?? (model?.name || ""),
};
}
return {};
}
);Example 2: Mapper for a specific trouble type:
// Enrich specific trouble type with custom context
ExtensionManager[EXTENSION_TROUBLE_DATA_MAPPER].register(
"FirmwareUpdateInProgressTrouble",
async (trouble: TroubleModel) => {
if (trouble.data?.deviceId) {
const { model: boardModel } = getKosModelSync<BoardModel>(
trouble.data.deviceId
);
return {
devices: boardModel ? boardModel.name : "",
};
}
return {};
}
);Registration Location
The SDK registers these mappers automatically when models are loaded:
- holder-model.ts:57-119 – Holder and container mappers
- pump-model.ts:31-34 – Pump mapper
- board-model.ts:31-33 – Board mapper
- dispense-registration-manager.ts:28-51 – Firmware update mapper
Using Troubles in UI
Displaying Trouble Information
import { useHolder } from "@kosdev-code/kos-dispense-sdk/ui";
function HolderCard({ holderPath }: { holderPath: string }) {
const holder = useHolder(holderPath);
if (!holder) return null;
return (
<div>
<h3>{holder.name}</h3>
{holder.hasEnjoyByWarning && (
<div className="warning">Ingredient expiring soon</div>
)}
{holder.troubles.map((trouble) => (
<div key={trouble.id} className={`trouble ${trouble.color}`}>
<strong>{trouble.title}</strong>
<p>{trouble.subtitle}</p>
</div>
))}
</div>
);
}Understanding Trouble Propagation
Automatic Assignment
- Device publishes trouble via WebSocket to
/kos/troublestopic TroubleContainerreceives trouble and createsTroubleModel- Framework executes registered
EXTENSION_TROUBLE_MAPPERfor the trouble’s iface - Mapper returns array of affected model paths
- TroubleContainer assigns trouble to those models
- Models decorated with
@kosTroubleAware()receive updates automatically
Real-time Updates
Troubles update via WebSocket:
- New troubles appear immediately on affected models
- Resolved troubles are removed automatically
troublesByTypeandshouldDeferupdate reactively
TroubleContainer Dependency
The decorator automatically adds TroubleContainer as a dependency – no manual configuration needed.
Best Practices
Use Abstraction Methods
Models provide abstraction methods like canPour and shouldDefer that encapsulate trouble logic:
const holder = assembly.holderContainer.getModel("holder-path");
// Use abstraction - don't assume why
if (holder.canPour) {
await holder.performIntent({ intent: "PRIME" });
}Display All Relevant Troubles
Show both blocking and warning troubles for context:
const allTroubles = holder.troubles;
const blockingTroubles = allTroubles.filter((t) => t.shouldDefer);
const warningTroubles = allTroubles.filter((t) => !t.shouldDefer);
// Different UI treatment for eachLeverage troublesByType
Use the indexed accessor for specific trouble types:
const enjoyByTroubles = holder.troublesByType["EnjoyByTrouble"] || [];
if (enjoyByTroubles.length > 0) {
showWarning("Ingredient expiring soon");
}Related Documentation
- Trouble System Integration – Complete trouble system guide including resolution, bulk operations, and handlers
- Hardware Component Models – Models using TroubleAware
- React Integration Patterns – Using troubles in React components
- Pouring Operations – Pour capability and blocking logic
