-
Notifications
You must be signed in to change notification settings - Fork 0
Telemetry Server
In Nest.js, everything is organized into modules, including the root AppModule itself. A module is a class annotated with @Module() that groups together related functionality. The decorator takes an object with four main fields:
-
imports— other modules whose exported providers are needed here -
controllers— the set of controllers defined in this module (handle different HTTP requests) -
providers— services/classes instantiated by Nest's DI container and usable within this module -
exports— the subset of providers this module makes available to other modules that import it
@Module({
imports: [SensorModule],
controllers: [DashboardController],
providers: [DashboardService],
exports: [DashboardService],
})
export class DashboardModule {}InfluxDB is a time-series database optimized for storing and querying timestamped data that is commonly used for metrics, sensor readings, and IoT telemetry. Data is organized into buckets, written as points (measurement + tags + fields + timestamp), and queried with Flux or InfluxQL.
We use v2 here.
Example write script using the Node.js client:
const { InfluxDB, Point } = require('@influxdata/influxdb-client');
const url = 'http://localhost:8086';
const token = process.env.INFLUX_TOKEN;
const org = 'my-org';
const bucket = 'my-bucket';
const client = new InfluxDB({ url, token });
const writeApi = client.getWriteApi(org, bucket);
const point = new Point('temperature')
.tag('sensor_id', 'sensor-01')
.floatField('value', 23.5);
writeApi.writePoint(point);
writeApi.close()
.then(() => console.log('Write finished'))
.catch(e => console.error(e));MQTT is a lightweight publish/subscribe messaging protocol built for constrained devices and low-bandwidth networks. Clients publish messages to a topic (a hierarchical string like home/livingroom/temperature), and other clients subscribe to topics (optionally with wildcards + and #) to receive them via a central broker.
A basic MQTT message consists of:
- Topic — the channel the message is published to
- Payload — the actual message data (often JSON)
- QoS level — 0 (at most once), 1 (at least once), 2 (exactly once)
- Retain flag — whether the broker keeps the last message on that topic for new subscribers
- Packet identifier — used internally for QoS 1/2 message tracking
Example payload:
{
"sensor_id": "sensor-01",
"value": 23.5,
"timestamp": "2026-08-27T10:00:00Z"
}We use the nest-mqtt module to read incoming MQTT messages from the pod
https://github.com/microud/nest-mqtt
| Header | Content | Subscriber | Publisher | Function |
|---|---|---|---|---|
hyped/${podId}/controls/levitation_height |
height: number |
Pod | Telemetry Server | Sets levitation height for a pod |
hyped/${podId}/controls/${control} |
control: string |
Pod | Telemetry Server | Sends Control Commands to pod |
hyped/+/measurement/+ |
Params: rawParams: string[] where rawParams[0] is podId and rawParams[1] is measurementId; payload: rawValue: number
|
Telemetry Server | Pod | Receives measurements and pipe it to /openmct/data/realtime; saves to the db |
hyped/+/state |
Params: rawParams: string[] where rawParams[0] is podId; payload: rawValue: PodStateType
|
Telemetry Server | Pod | Receives state and saves it to the db |
hyped/+/logs |
Params: rawParams: string[] where rawParams[0] is podId; payload: rawValue: Buffer | string
|
Telemetry Server | Pod | Receives logs from the pod and pipes it to /live-logs
|
The server is normally hosted at port ``. Here are the endpoints.
| Path | Type | Params | Return | Function | Module |
|---|---|---|---|---|---|
/ping |
GET | - | "pong" |
- | AppModule |
/openmct/dictionary/pods |
GET | - | string |
Returns the IDs of all the pods. It reads the IDS from a config file in Appendix 1 | OpenMCTModule |
/openmct/dictionary/pods/:podId |
GET | podId: string |
OpenMCTPod |
Returns the information of the pod at the moment, refer to Appendix 2 for the type | OpenMCTModule |
/openmct/dictionary/pods/:podId/measurements/:measurementKey |
GET |
podId: string, measurementKey: string
|
OpenMctMeasurement |
Returns this specific measurement from this specific pod at the moment, refer to Appendix 2 for the type | OpenMCTModule |
/openmct/data/historical/pods/:podId/measurements/:measurementKey |
GET |
podId: string, measurementKey: string; queries: start: string, end: string
|
{id: string, timestamp: string, value: OpenMctMeasurement}[] |
Returns the historical data of this specific measurement from this specific pod in the provided timeframe, refer to Appendix 2 and Appendix 3 for the type | OpenMCTDataModule |
/openmct/object-types |
GET | - | OpenMctObjectTypes |
Returns the obeject types, refer to Appendix 4 for the type | OpenMctModule |
/pods/:podId/controls/levitation-height |
POST |
podId: string; queries: height: number
|
true |
Sets levitation height with hyped/${podId}/controls/levitation_height
|
PodControlsModule |
/pods/:podId/controls/:control |
POST |
podId: string, control: string
|
true |
Publishes control commands to hyped/${podId}/controls/${control}
|
PodControlsModule |
Refer to Appendix 8 for endpoints on fault, public data, and remote logs
| Path | Incoming Messages | Outgoing Messages | Function | Module |
|---|---|---|---|---|
/openmct/data/realtime |
SUBSCRIBE_TO_MEASUREMENT, UNSUBSCRIBE_FROM_MEASUREMENT
|
MEASUREMENT_EVENT: MeasurementReading |
Subscribe/unsubscribe to measurements & receive specific measurements, refer to Appendix 5 and Appendix 6 for the types and messages | OpenMCTDataModule |
/openmct/faults/realtime |
SUBSCRIBE_TO_FAULTS, UNSUBSCRIBE_FROM_FAULTS
|
FAULT_EVENT: {fault: OpenMCTFault} |
Subscribe/unsubscribe to fault & receive faults, refer to Appendix 7 for the types and messages | FaultModule |
/live-logs |
_ | "log": string |
Subscribe to logs | AppModule |
Exerpt from packages/constants/src/pods/pods-data.generated.ts
// Auto-generated - DO NOT EDIT
// Generated from config/pods.yaml
export const podsYamlContent = "pods:\n the_podigal_son:\n label: 'The Podigal Son'\n mode: 'LEVITATION_ONLY'\n measurements:\n imd_iso_corrected:\n label: 'IMD Corrected Resistance'\n kind: 'resistance'\n unit: 'Ω'\n format: 'integer'\n limits:\n critical:\n low: 500\n high: 65535\n accelerometer_1:\n label: 'Accelerometer 1'\n kind: 'acceleration'\n unit: 'm/s²'\n format: 'float'\n limits:\n critical:\n low: -150\n high: 150\n accelerometer_2:\n label: 'Accelerometer 2'\n kind: 'acceleration'\n unit: 'm/s²'\n format: 'float'\n limits:\n critical:\n low: -150\n high: 150\n accelerometer_3:\n label: 'Accelerometer 3'\n kind: 'acceleration'\n unit: 'm/s²'\n format: 'float'\n limits:\n critical:\n low: -150\n high: 150\n accelerometer_4:\n label: 'Accelerometer 4'\n kind: 'acceleration'\n unit: 'm/s²'\n format: 'float'\n limits:\n critical:\n low: -150\n high: 150\n accelerometer_avg:\n label: 'Accelerometer Average'\n kind: 'acceleration'\n unit: 'm/s²'\n format: 'float'\n limits:\n critical:\n low: -150\n high: 150\n displacement:\n label: 'Displacement'\n kind: 'displacement'\n unit: 'm'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n velocity:\n label: 'Velocity'\n kind: 'velocity'\n unit: 'm/s'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 50\n acceleration:\n label: 'Acceleration'\n kind: 'acceleration'\n unit: 'm/s²'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 5\n pressure_back_pull:\n label: 'Pressure – Back Pull'\n kind: 'pressure'\n unit: 'bar'\n format: 'float'\n limits:\n critical:\n low: -0.2\n high: 5.5\n warning:\n low: -0.19\n high: 5.2\n pressure_front_pull:\n label: 'Pressure – Front Pull'\n kind: 'pressure'\n unit: 'bar'\n format: 'float'\n limits:\n critical:\n low: -0.2\n high: 5.5\n warning:\n low: -0.19\n high: 5.2\n pressure_front_push:\n label: 'Pressure – Front Push'\n kind: 'pressure'\n unit: 'bar'\n format: 'float'\n limits:\n critical:\n low: -0.2\n high: 5.5\n warning:\n low: -0.19\n high: 5.2\n pressure_back_push:\n label: 'Pressure – Back Push'\n kind: 'pressure'\n unit: 'bar'\n format: 'float'\n limits:\n critical:\n low: -0.2\n high: 5.5\n warning:\n low: -0.19\n high: 5.2\n pressure_brakes_reservoir:\n label: 'Pressure – Brakes Reservoir'\n kind: 'pressure'\n unit: 'bar'\n format: 'float'\n limits:\n critical:\n low: 3\n high: 7.4\n warning:\n low: 3.5\n high: 6.9\n pressure_active_suspension_reservoir:\n label: 'Pressure – Active Suspension Reservoir'\n kind: 'pressure'\n unit: 'bar'\n format: 'float'\n limits:\n critical:\n low: 3\n high: 7.4\n warning:\n low: 3.5\n high: 6.9\n pressure_front_brake:\n label: 'Pressure – Front Brake'\n kind: 'pressure'\n unit: 'bar'\n format: 'float'\n limits:\n critical:\n low: -0.2\n high: 4.2\n warning:\n low: -0.19\n high: 4\n pressure_back_brake:\n label: 'Pressure – Back Brake'\n kind: 'pressure'\n unit: 'bar'\n format: 'float'\n limits:\n critical:\n low: -0.2\n high: 4.2\n warning:\n low: -0.19\n high: 4\n thermistor_1:\n label: 'Thermistor 1'\n kind: 'temperature'\n unit: '°C'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n thermistor_2:\n label: 'Thermistor 2'\n kind: 'temperature'\n unit: '°C'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n thermistor_3:\n label: 'Thermistor 3'\n kind: 'temperature'\n unit: '°C'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n thermistor_4:\n label: 'Thermistor 4'\n kind: 'temperature'\n unit: '°C'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n thermistor_5:\n label: 'Thermistor 5'\n kind: 'temperature'\n unit: '°C'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n thermistor_6:\n label: 'Thermistor 6'\n kind: 'temperature'\n unit: '°C'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n thermistor_7:\n label: 'Thermistor 7'\n kind: 'temperature'\n unit: '°C'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n thermistor_8:\n label: 'Thermistor 8'\n kind: 'temperature'\n unit: '°C'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n thermistor_9:\n label: 'Thermistor 9'\n kind: 'temperature'\n unit: '°C'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n thermistor_10:\n label: 'Thermistor 10'\n kind: 'temperature'\n unit: '°C'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n thermistor_11:\n label: 'Thermistor 11'\n kind: 'temperature'\n unit: '°C'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n thermistor_12:\n label: 'Thermistor 12'\n kind: 'temperature'\n unit: '°C'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n hall_effect_1:\n label: 'Hall Effect 1'\n kind: 'magnetism'\n unit: 'mT'\n format: 'float'\n limits:\n critical:\n low: -100\n high: 100\n hall_effect_2:\n label: 'Hall Effect 2'\n kind: 'magnetism'\n unit: 'mT'\n format: 'float'\n limits:\n critical:\n low: -100\n high: 100\n keyence_1:\n label: 'Keyence 1'\n kind: 'keyence'\n unit: 'number of stripes'\n format: 'integer'\n limits:\n critical:\n low: 0\n high: 16\n warning:\n low: 5\n high: 10\n keyence_2:\n label: 'Keyence 2'\n kind: 'keyence'\n unit: 'number of stripes'\n format: 'integer'\n limits:\n critical:\n low: 0\n high: 16\n warning:\n low: 5\n high: 10\n power_line_resistance:\n label: 'Power Line Resistance'\n kind: 'resistance'\n unit: 'kΩ'\n format: 'integer'\n limits:\n critical:\n low: 0\n high: 100\n levitation_height_1:\n label: 'Levitation Height 1'\n kind: 'levitation'\n unit: 'mm'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n levitation_height_2:\n label: 'Levitation Height 2'\n kind: 'levitation'\n unit: 'mm'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n levitation_height_3:\n label: 'Levitation Height 3'\n kind: 'levitation'\n unit: 'mm'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n levitation_height_4:\n label: 'Levitation Height 4'\n kind: 'levitation'\n unit: 'mm'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n levitation_height_lateral_1:\n label: 'Levitation Height Lateral 1'\n kind: 'levitation'\n unit: 'mm'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n levitation_height_lateral_2:\n label: 'Levitation Height Lateral 2'\n kind: 'levitation'\n unit: 'mm'\n format: 'float'\n limits:\n critical:\n low: 0\n high: 100\n statuses:\n brake_clamp_status:\n label: 'Brake Clamp Status'\n kind: 'binary-status'\n format: 'enum'\n values:\n - value: 1\n label: 'CLAMPED'\n - value: 0\n label: 'UNCLAMPED'\n pod_raised_status:\n label: 'Pod Raised Status'\n kind: 'binary-status'\n format: 'enum'\n values:\n - value: 1\n label: 'RAISED'\n - value: 0\n label: 'LOWERED'\n battery_status:\n label: 'Battery Status'\n kind: 'binary-status'\n format: 'enum'\n values:\n - value: 1\n label: 'HEALTHY'\n - value: 0\n label: 'UNHEALTHY'\n motor_controller_status:\n label: 'Motor Controller Status'\n kind: 'binary-status'\n format: 'enum'\n values:\n - value: 1\n label: 'HEALTHY'\n - value: 0\n label: 'UNHEALTHY'\n high_power_status:\n label: 'High Power Status'\n kind: 'binary-status'\n format: 'enum'\n values:\n - value: 1\n label: 'ACTIVE'\n - value: 0\n label: 'OFF'\n";Excerpt from packages/types/src/openmct/openmct-dictionary.types.ts
/**
* Type of an Open MCT measurement.
*/
export type OpenMctMeasurement = {
name: string;
key: string;
type: string;
values: {
key: string;
name: string;
unit?: string;
format: string;
min?: number;
max?: number;
limits?: MeasurementLimits;
enumerations?: {
value: number;
string: string;
}[];
hints?: {
range?: number;
domain?: number;
};
source?: string;
units?: {
domain: string;
};
}[];
};
/**
* Type of an Open MCT pod.
*/
export type OpenMctPod = {
id: string;
name: string;
measurements: OpenMctMeasurement[];
};
/**
* Type of an Open MCT dictionary.
*/
export type OpenMctDictionary = Record<string, OpenMctPod>;Excerpt from packages/server/modules/openmct/data/historical/HistoricalTelemetryData.service.ts
public async getHistoricalReading(
podId: string,
measurementKey: string,
startTimestamp: string,
endTimestamp: string,
) {
const fluxStart = fluxDateTime(
new Date(Number.parseInt(startTimestamp)).toISOString(),
);
const fluxEnd = fluxDateTime(
new Date(Number.parseInt(endTimestamp)).toISOString(),
);
const query = flux`
from(bucket: "${INFLUX_TELEMETRY_BUCKET}")
|> range(start: ${fluxStart}, stop: ${fluxEnd})
|> filter(fn: (r) => r["measurementKey"] == "${measurementKey}")
|> filter(fn: (r) => r["podId"] == "${podId}")`;
try {
const data =
await this.influxService.query.collectRows<InfluxHistoricalRow>(query);
return data.map((row) => ({
id: row.measurementKey,
timestamp: new Date(row._time).getTime(),
value: row._value,
}));
} catch (e: unknown) {
this.logger.error(
`Failed to get historical reading for {${podId}/${measurementKey}}`,
e,
HistoricalTelemetryDataService.name,
);
throw new HttpException("Couldn't get historical reading", 500);
}
}Excerpt from packages/types/src/openmct/openmct-object-types.types.ts
export type OpenMctObjectType = {
id: string;
name: string;
description?: string;
icon: string;
};
export type OpenMctObjectTypes = OpenMctObjectType[];Excerpt from packages/server/src/modules/telemetry/MeasurementReading.types.ts
export const MeasurementReadingSchema = z.object({
podId: zodEnumFromObjKeys(pods),
measurementKey: z.string(),
timestamp: z.string(), // to handle nanoseconds timestamp
value: z.number(),
});
export type MeasurementReading = z.infer<typeof MeasurementReadingSchema>;Excerpt from packages/constants/src/socket/index.ts
export const EVENTS = {
SUBSCRIBE_TO_MEASUREMENT: 'SubscribeToMeasurement',
UNSUBSCRIBE_FROM_MEASUREMENT: 'UnsubscribeFromMeasurement',
SUBSCRIBE_TO_FAULTS: 'SubscribeToFaults',
UNSUBSCRIBE_FROM_FAULTS: 'UnsubscribeFromFaults',
};Excerpt from packages/types/src/openmct/openmct-fault.types.ts
export type OpenMctFault = {
type: null | string;
fault: {
acknowledged: boolean;
currentValueInfo: {
value: number;
rangeCondition: string;
monitoringResult: string;
};
id: string;
name: string;
namespace: string;
seqNum: number;
severity: string;
shelved: boolean;
shortDescription: string;
triggerTime: string;
triggerValueInfo: {
value: number;
rangeCondition: string;
monitoringResult: string;
};
};
};
export type HistoricalFaults = {
faultId: string;
timestamp: number;
openMctFault: OpenMctFault;
podId: string;
measurementKey: string;
}[];
export type OpenMctHistoricalFaults = {
timestamp: number;
fault: OpenMctFault;
}[];Excerpt from packages/server/src/modules/openmct/faults/Fault.controller.ts
@Controller('openmct/faults')
export class FaultsController {
constructor(private faultsService: FaultService) {}
@Post('acknowledge')
acknowledgeFault(
@Body() { faultId, comment }: { faultId: string; comment: string },
) {
return this.faultsService.acknowledgeFault(faultId, comment);
}
@Post('shelve')
shelveFault(
@Body()
{
faultId,
shelved,
shelveDuration,
comment,
}: {
faultId: string;
shelved: boolean;
shelveDuration: number;
comment: string;
},
) {
return this.faultsService.shelveFault(
faultId,
shelved,
shelveDuration,
comment,
);
}
}Excerpt from packages/server/src/modules/remote-logs/RemoteLogs.controller.ts
@Controller('logs')
export class RemoteLogsController {
constructor(private remoteLogsService: RemoteLogsService) {}
@Post()
logUIMessage(@Body() body: { message: string }) {
return this.remoteLogsService.logRemoteMessage(body.message);
}
@Post(':podId')
logUIMessageWithPodID(
@Param('podId') podId: string,
@Body() body: { message: string },
) {
return this.remoteLogsService.logRemoteMessageWithPodID(
podId,
body.message,
);
}
}Excerpt from packages/server/src/modules/public-data/PublicData.module.ts
@Module({
imports: [InfluxModule],
controllers: [PublicDataController],
providers: [PublicDataService, HistoricalTelemetryDataService],
})
export class PublicDataModule {}