Appearance
Alarms Module Application Guide
This guide explains how to use the Alarms module of the Coldwave Cloud Backend from an application developer perspective. It focuses on how to:
- Configure alarms on top of device properties.
- Show alarm state and history in dashboards and detail views.
- Attach messages to alarm occurrences.
- Integrate WebSocket events and push notifications.
The guide assumes that you are familiar with the general data model (devices, services, properties, schemas) and with the frontend state model described in the previous chapters.
What the Alarms module does (and does not)
The Alarms module lets you define logical expressions over device properties. These expressions are evaluated automatically when the underlying property values change. When an expression becomes true, the corresponding alarm is active for that device/service; when it becomes false, the alarm is inactive.
Key points:
- Alarms are defined centrally in the backend and apply to one or more devices/services.
- Expressions can reference properties by schema name (e.g.
ride-speed) or by their identifier notation (e.g.0x2000.UINT8). - Expressions support nested boolean logic, arithmetic, and time-based conditions (e.g. “maintenance == 1 for at least 30 seconds”).
- The module maintains status history per alarm configuration and per device/service.
- Optional push notifications can be triggered for active alarms.
The Alarms module is not meant to replace low-level safety logic in the device. It is best used for monitoring, operations, maintenance, and notification logic on top of already safe devices.
Core concepts
From an application perspective, there are five important concepts:
- Alarm configuration (expression + metadata)
- Watcher identifier (the ID of a configuration)
- Alarm status (active/inactive per device + history)
- Alarm messages (comments attached to a specific occurrence)
- Subscriptions (push notifications)
Alarm configuration
An alarm configuration defines what to monitor and how to represent it.
Important fields:
watcherIdentifier– generated ID of the configuration (string, 16–20 chars).expression– nested JSON expression that uses properties, constants and operators.severity– integer where lower numbers mean more severe (e.g.0 = critical,1 = high).data– free-form key/value object for your own UI metadata (icon names, tags, etc.).notification– optional push notification template (title/body, optionally with i18n).whitelist/blacklist– optional device lists to include/exclude devices.
Configurations are created and managed via:
POST /api/v1/alarms/config– create configuration.GET /api/v1/alarms/config– list all configurations.GET /api/v1/alarms/config/:watcherIdentifier– get one configuration.PUT /api/v1/alarms/config/:watcherIdentifier– update configuration.DELETE /api/v1/alarms/config/:watcherIdentifier– delete configuration (cascades to status, messages, subscriptions).
In most setups, configurations are managed by an admin UI or by backend scripts, not by end users. As an application developer, you typically:
- Read configurations to show the list of possible alarms and their severity.
- Optionally offer configuration UIs to power users (e.g. “Alarm editor”).
Expressions
Expressions are JSON trees. A few patterns you will frequently encounter:
- CompareExpression – compares two values, returns boolean.
- LogicExpression – combines boolean expressions (
AND,OR,NOT). - ComputeExpression – numeric computation (
ADD,SUBTRACT,MULTIPLY, etc.). - AccumulateExpression / DURATION – checks that a condition is true for a given duration.
Simple example: “speed is less than rated speed”
json
{
"type": "LESSER",
"operatorA": {
"type": "SUBTRACT",
"operatorA": { "property": "ride-speed" },
"operatorB": { "property": "rated-speed-min" }
},
"operatorB": -0.15
}Time-based example: “maintenance == 1 for at least 30 seconds”
json
{
"type": "DURATION",
"operatorA": {
"type": "EQUAL",
"operatorA": { "property": "maintenance" },
"operatorB": 1
},
"operatorB": 30000
}Property references can use either:
- Schema names – e.g.
"property": "ride-speed", or - Identifier notation – e.g.
"property": "0x2000.UINT8".
For application-level UIs, using schema names is usually the most readable approach. For long-lived integrations, identifier notation is more stable against schema renames.
Watcher identifier
When you create a configuration, the backend returns a watcherIdentifier. This is the primary key for:
- Referencing the configuration via API.
- Grouping status and messages that belong to this configuration.
- Identifying the alarm in WebSocket events and in your own state model.
Keep watcherIdentifier in your frontend state so you can correlate status entries and configuration details.
Alarm status and occurrences
For each (serviceIdentifier, deviceIdentifier, watcherIdentifier) triple, the backend tracks whether the alarm is currently active or inactive, and stores the recent history (up to a fixed number of changes).
Key points:
- Status is represented as an alternating sequence of
active = true/falseentries. - Only a limited number of changes are stored (most recent ~100 changes).
- From the sequence you can derive:
- Total time in alarm vs. OK state.
- Last activation and deactivation timestamps.
- Number of occurrences in a time window.
Status endpoints:
GET /api/v1/alarms/status– list all known alarm status across devices.GET /api/v1/alarms/status/:serviceIdentifier– status for a given service.GET /api/v1/alarms/status/:deviceIdentifier– status for a given device.GET /api/v1/alarms/status/:serviceIdentifier/:deviceIdentifier/:watcherIdentifier– status for a specific configuration on a specific device.DELETE /api/v1/alarms/status/:deviceIdentifier– delete all status for a device (admin / housekeeping).
The response format is nested; using a higher depth (e.g. depth=5) expands devices, services, configurations and status entries in one call.
Alarm messages
Each time an alarm becomes active or inactive, this is an occurrence. For each occurrence, you can attach messages similar to notes:
- Message fields:
timestamp,id,author,message. - Useful for: manual acknowledgement, investigation notes, hand-over between shifts.
Endpoints are nested under the corresponding status:
GET /api/v1/alarms/status/:serviceIdentifier/:deviceIdentifier/:watcherIdentifier/:occurrenceIdentifier/messagesPOST /api/v1/alarms/status/:serviceIdentifier/:deviceIdentifier/:watcherIdentifier/:occurrenceIdentifier/messagesGET /api/v1/alarms/status/:.../messages/:messageIdentifierPUT /api/v1/alarms/status/:.../messages/:messageIdentifierDELETE /api/v1/alarms/status/:.../messages/:messageIdentifierDELETE /api/v1/alarms/status/:.../messages– delete all messages for an occurrence.
In a UI you typically expose these messages in an “Alarm detail” view, separate from the global device notes.
Subscriptions (push notifications)
The subscription API connects alarms to mobile push services (Android/iOS). It is used mainly by mobile apps and integration backends.
POST /api/v1/alarms/subscribe– register a push token with filters:consumer–"android"or"ios".info– the push token.minSeverity/maxSeverity– filter by severity range.deviceIdentifier[]/serviceIdentifier[]– optional scope.developer– mark subscription as developer for test notifications.user– optional username for tracing.
POST /api/v1/alarms/unsubscribe– remove a subscription by token.GET /api/v1/alarms/subscribe– list all subscriptions (admin tools).GET /api/v1/alarms/subscribe/:info– get a single subscription.POST /api/v1/alarms/subscribe/test– send test notifications to developer subscriptions.
Suggested frontend state model for alarms
Building on the generic AppState from the state model chapter, you can extend it as follows (TypeScript-style pseudocode):
ts
type DeviceId = string;
type ServiceId = string;
type WatcherId = string;
type OccurrenceId = number;
type AlarmMessageId = number;
interface AlarmConfig {
watcherIdentifier: WatcherId;
expression: any; // expression tree
severity: number;
data?: Record<string, unknown>;
notification?: {
title?: string;
body?: string;
translations?: Record<string, { title: string; body: string }>;
};
whitelist?: DeviceId[];
blacklist?: DeviceId[];
}
interface AlarmStatusEntry {
occurrenceIdentifier: OccurrenceId;
active: boolean;
timestamp: number;
lastChange: number; // or similar, depending on the API fields
}
interface AlarmStatus {
watcherIdentifier: WatcherId;
serviceIdentifier: ServiceId;
deviceIdentifier: DeviceId;
active: boolean;
lastChange: number;
history: AlarmStatusEntry[];
}
interface AlarmMessage {
messageIdentifier: AlarmMessageId;
occurrenceIdentifier: OccurrenceId;
deviceIdentifier: DeviceId;
serviceIdentifier: ServiceId;
watcherIdentifier: WatcherId;
timestamp: number;
author: string;
message: string;
}
interface AppState {
// existing fields...
alarmConfigs: Record<WatcherId, AlarmConfig>;
alarmStatusByDevice: Record<DeviceId, AlarmStatus[]>;
alarmMessagesByOccurrence: Record<
string, // `${deviceId}:${serviceId}:${watcherId}:${occurrenceId}`
AlarmMessage[]
>;
}This structure lets you:
- Quickly render per-device alarm views and fleet-wide alarm dashboards.
- Fetch and cache configuration once, then join it with status and messages.
- React to WebSocket events in small, focused reducers.
Typical application flows
1. Fleet alarm overview
For an “Alarms” overview page:
Fetch all alarm configurations:
bashcurl --location '<<URL>>/api/v1/alarms/config' --header "Authorization: Bearer ${TOKEN}"Fetch current status with sufficient depth:
bashcurl --location '<<URL>>/api/v1/alarms/status?depth=5&fixTypo=true' --header "Authorization: Bearer ${TOKEN}"Map the nested response into
alarmStatusByDeviceandAlarmStatusobjects.Join with
alarmConfigs[watcherIdentifier]to get severity and labels.Render a table or tiles:
- Columns: device, service, alarm label (from
dataor expression description), severity, active/inactive, duration, last change. - Color-code by severity and active state.
- Columns: device, service, alarm label (from
2. Device alarm tab
On the device detail page, a dedicated “Alarms” tab can show:
- Active alarms for this device (grouped by service).
- Inactive alarms with recent history (e.g. last 24 hours).
- For a selected alarm occurrence:
- Status timeline (when it went active / inactive).
- Messages (comments, acknowledgements).
Implementation steps:
- Use
GET /api/v1/alarms/status/:deviceIdentifierto load all alarms for the device. - For the selected
(serviceIdentifier, watcherIdentifier, occurrenceIdentifier), call the.../messagesendpoints to load and manage messages. - Use WebSocket events (see below) to update active/inactive state in real time.
3. Alarm configuration UI (optional)
If your product should allow users to configure alarms themselves:
- Offer a configuration UI that builds the
expressionas JSON (e.g. expression builder). - Map schema properties of a service to selectable fields, using names as labels.
- Use
POST /api/v1/alarms/configandPUT /api/v1/alarms/config/:watcherIdentifierto create and update configurations. - Consider keeping configuration changes behind a “power user” or admin role.
In many cases, configuration is managed by integrators or OEMs and not exposed to all users.
WebSocket integration
The Alarms module emits several event types over the WebSocket:
ALARM_CONFIG_ADDALARM_CONFIG_UPDATEALARM_CONFIG_DELETEALARM_STATUS_ACTIVEALARM_STATUS_INACTIVEALARM_STATUS_DELETE_DEVICE
All of these require appropriate read permissions for alarm.config or alarm.status.
Typical payload fields for status events include:
type– one of the event types above.watcherIdentifier– alarm configuration ID.deviceIdentifier– affected device.serviceIdentifier– affected service.active–true/false(for status events).timestamporlastChange– when the change happened.
Example WebSocket reducer (TypeScript-style pseudocode):
ts
function applyAlarmEvent(state: AppState, event: any) {
switch (event.type) {
case "ALARM_CONFIG_ADD":
case "ALARM_CONFIG_UPDATE": {
const cfg: AlarmConfig = {
watcherIdentifier: event.watcherIdentifier,
expression: event.expression,
severity: event.severity,
data: event.data,
notification: event.notification,
whitelist: event.whitelist,
blacklist: event.blacklist,
};
state.alarmConfigs[cfg.watcherIdentifier] = cfg;
break;
}
case "ALARM_CONFIG_DELETE": {
delete state.alarmConfigs[event.watcherIdentifier];
// Optional: also clean up status/messages for this watcher.
break;
}
case "ALARM_STATUS_ACTIVE":
case "ALARM_STATUS_INACTIVE": {
const deviceId: DeviceId = event.deviceIdentifier;
const serviceId: ServiceId = event.serviceIdentifier;
const watcherId: WatcherId = event.watcherIdentifier;
if (!state.alarmStatusByDevice[deviceId]) {
state.alarmStatusByDevice[deviceId] = [];
}
const list = state.alarmStatusByDevice[deviceId];
let status = list.find(
(s) =>
s.watcherIdentifier === watcherId &&
s.serviceIdentifier === serviceId
);
if (!status) {
status = {
watcherIdentifier: watcherId,
serviceIdentifier: serviceId,
deviceIdentifier: deviceId,
active: false,
lastChange: 0,
history: [],
};
list.push(status);
}
status.active = event.type === "ALARM_STATUS_ACTIVE";
status.lastChange = event.timestamp ?? Date.now();
status.history.push({
occurrenceIdentifier: event.occurrenceIdentifier,
active: status.active,
timestamp: status.lastChange,
lastChange: status.lastChange,
});
break;
}
case "ALARM_STATUS_DELETE_DEVICE": {
const deviceId: DeviceId = event.deviceIdentifier;
delete state.alarmStatusByDevice[deviceId];
break;
}
}
}Integrate applyAlarmEvent into your global WebSocket dispatcher along with handlers for notes, property updates, etc.
Push notifications in mobile apps
For a mobile app (Android/iOS) that wants to receive alarm notifications:
- Obtain the push token from the platform (FCM/APNs).
- Call
POST /api/v1/alarms/subscribewith:consumer:"android"or"ios".info: the push token.- Optional:
minSeverity,maxSeverity,deviceIdentifier,serviceIdentifier.
- Store the subscription on your side (for debugging or account linking).
- Handle incoming notifications in the app:
- Open the relevant device + alarm detail view based on payload fields.
- Optionally use deep links to jump directly into the alarm view.
- On logout or token rotation, call
POST /api/v1/alarms/unsubscribe.
For developers, you can:
- Mark subscriptions as
developer: true. - Use
POST /api/v1/alarms/subscribe/testwith aseverityto trigger test notifications.
Permissions and multi-tenant behaviour
The Alarms module is governed by several resources:
alarm.config– manage configurations (create, read, update, delete).alarm.status– read and delete status information.alarm.status.messages– manage messages attached to occurrences.alarm.subscribe– manage push subscriptions.
In multi-tenant setups:
- Device access control defines which devices a user can see; alarm status and messages follow the same scoping.
- You can build roles such as:
- Viewer – read
alarm.status, read configurations (no editing), read messages. - Operator – everything from viewer + create messages + maybe acknowledge via messages.
- Admin – manage configurations, delete status, manage subscriptions.
- Viewer – read
Summary
- Alarm configurations define expressions over properties, plus severity and metadata.
watcherIdentifierlinks configuration, status, messages, WebSocket events and subscriptions.- Status endpoints and events let you build real-time alarm dashboards and per-device alarm views.
- Messages attach human context to specific alarm occurrences.
- Subscriptions connect alarms to push notifications for mobile apps.
With these building blocks, you can add rich alarm functionality to your Coldwave-based applications without embedding complex monitoring logic in every client.