Skip to content
Merged
18 changes: 13 additions & 5 deletions src/libs/monaco-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,13 @@ function rebuildVariablesCache(): void {
}

/**
* Register completion providers for JavaScript.
* Register completion providers for JavaScript and plaintext.
* The provider checks the model's registered completion type to determine behavior.
*/
function registerCompletionProviders(): void {
// Data lake variable completion provider - triggered on '{' (for '{{' syntax)
// Behavior depends on the completion type registered for each editor model
monaco.languages.registerCompletionItemProvider('javascript', {
const dataLakeCompletionProvider: monaco.languages.CompletionItemProvider = {
triggerCharacters: ['{'],
provideCompletionItems: (model, position) => {
const textUntilPosition = model.getValueInRange({
Expand Down Expand Up @@ -193,8 +193,9 @@ function registerCompletionProviders(): void {
endColumn: position.column,
}

const nonLegacy = Object.entries(cachedVariablesMap).filter(([, v]) => !(v.name || '').includes('(Legacy)'))
return {
suggestions: Object.entries(cachedVariablesMap).map(([id, variable]) => ({
suggestions: nonLegacy.map(([id, variable]) => ({
label: variable.name || id,
kind: monaco.languages.CompletionItemKind.Variable,
documentation: `${variable.type}${variable.description ? ` - ${variable.description}` : ''} (${id})`,
Expand All @@ -212,8 +213,9 @@ function registerCompletionProviders(): void {
endColumn: position.column,
}

const nonLegacy = Object.entries(cachedVariablesMap).filter(([, v]) => !(v.name || '').includes('(Legacy)'))
return {
suggestions: Object.entries(cachedVariablesMap).map(([id, variable]) => ({
suggestions: nonLegacy.map(([id, variable]) => ({
label: variable.name || id,
kind: monaco.languages.CompletionItemKind.Variable,
insertText: ` ${id} }}`,
Expand All @@ -226,7 +228,10 @@ function registerCompletionProviders(): void {

return { suggestions: [] }
},
})
}

monaco.languages.registerCompletionItemProvider('javascript', dataLakeCompletionProvider)
monaco.languages.registerCompletionItemProvider('plaintext', dataLakeCompletionProvider)
}

// =============================================================================
Expand All @@ -248,6 +253,8 @@ export interface EditorOptions {
* - undefined: No data lake completions
*/
dataLakeCompletionType?: DataLakeCompletionType
/** Optional Monaco editor construction option overrides (e.g. lineNumbers, fontSize, padding) */
editorOverrides?: monaco.editor.IStandaloneEditorConstructionOptions
}

/**
Expand Down Expand Up @@ -278,6 +285,7 @@ export function createMonacoEditor(
padding: { top: 12, bottom: 12 },
autoClosingBrackets: options.language === 'javascript' ? 'never' : 'languageDefined',
autoClosingQuotes: options.language === 'javascript' ? 'never' : 'languageDefined',
...options.editorOverrides,
})

// Register completion type for this editor's model
Expand Down
90 changes: 69 additions & 21 deletions src/libs/sensors-logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import { useAppInterfaceStore } from '@/stores/appInterface'
import { useMainVehicleStore } from '@/stores/mainVehicle'
import { useMissionStore } from '@/stores/mission'

import { getDataLakeVariableData, getDataLakeVariableInfo } from './actions/data-lake'
import { settingsManager } from './settings-management'
import { unitAbbreviation } from './units'
import { degrees } from './utils'
import { findDataLakeInputsInString, replaceDataLakeInputsInString } from './utils-data-lake'

/**
* Variables data can be datalogged
Expand Down Expand Up @@ -71,10 +73,6 @@ type VeryGenericData = {
* Linux epoch stating when this value was last changed
*/
lastChanged?: number
/**
* Current view of the variable
*/
hideLabel?: boolean
}

/**
Expand All @@ -88,7 +86,7 @@ export type ExtendedVariablesData = {
[key: string]: {
value: string
lastChanged: number
hideLabel?: boolean
displayName: string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
displayName: string
displayName?: string

If we make this optional then if left unspecified it can default to the key, and if specified as an empty string it could replace the previous hideLabel: true usage (with the special behaviour of no name section being rendered).

Not a blocker, just could make the code slightly cleaner / require fewer changes to the existing definitions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one I prefer to stay as it is. I believe the right approach is to remove all non-data-lake sensor/telemetry logging approaches.

}
}

Expand Down Expand Up @@ -324,27 +322,30 @@ class DataLogger {

/* eslint-disable vue/max-len, prettier/prettier, max-len */
let variablesData: ExtendedVariablesData = {
[DatalogVariable.roll]: { value: `${degrees(vehicleStore.attitude.roll)?.toFixed(1)} °`, ...timeNowObj },
[DatalogVariable.pitch]: { value: `${degrees(vehicleStore.attitude.pitch)?.toFixed(1)} °`, ...timeNowObj },
[DatalogVariable.heading]: { value: `${degrees(vehicleStore.attitude.yaw)?.toFixed(1)} °`, ...timeNowObj },
[DatalogVariable.depth]: { value: `${depthValue} ${depthUnit}`, ...timeNowObj },
[DatalogVariable.mode]: { value: vehicleStore.mode || 'Unknown', ...timeNowObj },
[DatalogVariable.batteryVoltage]: { value: `${vehicleStore.powerSupply.voltage?.toFixed(2)} V` || 'Unknown', ...timeNowObj },
[DatalogVariable.batteryCurrent]: { value: `${vehicleStore.powerSupply.current?.toFixed(2)} A` || 'Unknown', ...timeNowObj },
[DatalogVariable.gpsVisibleSatellites]: { value: vehicleStore.statusGPS.visibleSatellites?.toFixed(0) || 'Unknown', ...timeNowObj },
[DatalogVariable.gpsFixType]: { value: vehicleStore.statusGPS.fixType, ...timeNowObj },
[DatalogVariable.latitude]: { value: `${vehicleStore.coordinates.latitude?.toFixed(6)} °` || 'Unknown', ...timeNowObj },
[DatalogVariable.longitude]: { value: `${vehicleStore.coordinates.longitude?.toFixed(6)} °` || 'Unknown', ...timeNowObj },
[DatalogVariable.missionName]: { value: missionStore.missionName || 'Cockpit', hideLabel: true, ...timeNowObj },
[DatalogVariable.time]: { value: format(timeNow, 'HH:mm:ss O'), hideLabel: true, ...timeNowObj },
[DatalogVariable.date]: { value: format(timeNow, 'LLL dd, yyyy'), hideLabel: true, ...timeNowObj },
[DatalogVariable.instantaneousPower]: { value: `${vehicleStore.instantaneousWatts?.toFixed(1)} W` || 'Unknown', ...timeNowObj },
[DatalogVariable.roll]: { displayName: DatalogVariable.roll, value: `${degrees(vehicleStore.attitude.roll)?.toFixed(1)} °`, ...timeNowObj },
[DatalogVariable.pitch]: { displayName: DatalogVariable.pitch, value: `${degrees(vehicleStore.attitude.pitch)?.toFixed(1)} °`, ...timeNowObj },
[DatalogVariable.heading]: { displayName: DatalogVariable.heading, value: `${degrees(vehicleStore.attitude.yaw)?.toFixed(1)} °`, ...timeNowObj },
[DatalogVariable.depth]: { displayName: DatalogVariable.depth, value: `${depthValue} ${depthUnit}`, ...timeNowObj },
[DatalogVariable.mode]: { displayName: DatalogVariable.mode, value: vehicleStore.mode || 'Unknown', ...timeNowObj },
[DatalogVariable.batteryVoltage]: { displayName: DatalogVariable.batteryVoltage, value: `${vehicleStore.powerSupply.voltage?.toFixed(2)} V` || 'Unknown', ...timeNowObj },
[DatalogVariable.batteryCurrent]: { displayName: DatalogVariable.batteryCurrent, value: `${vehicleStore.powerSupply.current?.toFixed(2)} A` || 'Unknown', ...timeNowObj },
[DatalogVariable.gpsVisibleSatellites]: { displayName: DatalogVariable.gpsVisibleSatellites, value: vehicleStore.statusGPS.visibleSatellites?.toFixed(0) || 'Unknown', ...timeNowObj },
[DatalogVariable.gpsFixType]: { displayName: DatalogVariable.gpsFixType, value: vehicleStore.statusGPS.fixType, ...timeNowObj },
[DatalogVariable.latitude]: { displayName: DatalogVariable.latitude, value: `${vehicleStore.coordinates.latitude?.toFixed(6)} °` || 'Unknown', ...timeNowObj },
[DatalogVariable.longitude]: { displayName: DatalogVariable.longitude, value: `${vehicleStore.coordinates.longitude?.toFixed(6)} °` || 'Unknown', ...timeNowObj },
[DatalogVariable.missionName]: { displayName: '', value: missionStore.missionName || 'Cockpit', ...timeNowObj },
[DatalogVariable.time]: { displayName: '', value: format(timeNow, 'HH:mm:ss O'), ...timeNowObj },
[DatalogVariable.date]: { displayName: '', value: format(timeNow, 'LLL dd, yyyy'), ...timeNowObj },
[DatalogVariable.instantaneousPower]: { displayName: DatalogVariable.instantaneousPower, value: `${vehicleStore.instantaneousWatts?.toFixed(1)} W` || 'Unknown', ...timeNowObj },
}

/* eslint-enable vue/max-len, prettier/prettier, max-len */
const veryGenericData = this.collectVeryGenericData(timeNowObj)
variablesData = { ...variablesData, ...veryGenericData }

const dataLakeData = this.collectDataLakeData(variablesData, timeNowObj)
variablesData = { ...variablesData, ...dataLakeData }
Copy link
Contributor

@ES-Alexander ES-Alexander Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this have conflicts/overrides?

I think the ExtendedVariablesData type should probably be expanded to have an optional display name field, with IDs used as the keys. Finishing implementing part 1 of #2281 may also help avoid conflicts (I think the remaining steps are adding a prefix for the internal variables, and possibly adding a default prefix for user calls to the variable creation function).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's what's causing the need for what you mentioned in the previous comment.

Despite being desirable to enter at some point, the standardization is not a must here for this functionality. We can work step by step.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that standardisation isn't a requirement for this, but while it isn't implemented it's likely worth at least a comment or an Issue mentioning we know it's a potential source of conflicts, just in case a user actually runs into issues with it down the line :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely.


const logPoint: CockpitStandardLogPoint = {
epoch: timeNow.getTime(),
data: structuredClone(variablesData),
Expand Down Expand Up @@ -372,12 +373,59 @@ class DataLogger {
this.veryGenericIndicators.forEach((indicator) => {
result[indicator.displayName] = {
value: indicator.variableValue,
displayName: indicator.displayName,
lastChanged: data.lastChanged,
}
})
return result
}

/**
* Collects data from data lake variables in the telemetry display grid that are not already covered
* by standard variables or VeryGenericIndicator data.
* Also resolves {{ variableId }} templates in custom messages.
* @param {ExtendedVariablesData} currentData - Already collected variables data (standard + VGI)
* @param {{ lastChanged: number }} timeInfo - Timestamp info for the log point
* @param {number} timeInfo.lastChanged - Linux epoch stating when this value was last changed
* @returns {ExtendedVariablesData} Resolved data lake variable values
*/
collectDataLakeData(currentData: ExtendedVariablesData, timeInfo: { lastChanged: number }): ExtendedVariablesData {
const result: ExtendedVariablesData = {}

for (const gridEntries of Object.values(this.telemetryDisplayData)) {
for (const entry of gridEntries) {
if (currentData[entry]) continue

if (findDataLakeInputsInString(entry).length > 0) {
result[entry] = {
value: replaceDataLakeInputsInString(entry),
displayName: '',
...timeInfo,
}
continue
}

const variableInfo = getDataLakeVariableInfo(entry)
if (variableInfo) {
const value = getDataLakeVariableData(entry)
let displayName = variableInfo.name ?? entry

// If the variable is a MAVLink variable, remove the (MAVLink / System: <systemId> / Component: <componentId>) suffix
if (entry.includes('mavlink/') && displayName.includes('(')) {
displayName = displayName.substring(0, displayName.lastIndexOf('(')).trim()
}
result[entry] = {
value: value !== undefined ? String(value) : 'N/A',
displayName,
...timeInfo,
}
}
}
}

return result
}

/**
* Removes the requester from the log requesters list. If there are no requesters left, logging will stop.
* @param {string} requesterId The ID of the requester to stop
Expand Down Expand Up @@ -572,7 +620,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text`
.map((variable) => {
const variableData = logPoint.data[variable]
if (variableData) {
return `${variableData.hideLabel ? '' : `${variable}:`} ${variableData.value}`
return variableData.displayName ? `${variableData.displayName}: ${variableData.value}` : variableData.value
} else {
return `${variable}`
}
Expand Down
Loading
Loading