From d6cd987361697065e7f23b716de6c379506726ec Mon Sep 17 00:00:00 2001 From: Willian Galvani Date: Tue, 17 Dec 2024 11:00:12 -0300 Subject: [PATCH 1/7] ardupilot: expose MAVLink data into data-lake --- src/libs/vehicle/ardupilot/ardupilot.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/vehicle/ardupilot/ardupilot.ts b/src/libs/vehicle/ardupilot/ardupilot.ts index 2e8269425..2476bc2e4 100644 --- a/src/libs/vehicle/ardupilot/ardupilot.ts +++ b/src/libs/vehicle/ardupilot/ardupilot.ts @@ -1,6 +1,7 @@ import { differenceInMilliseconds } from 'date-fns' import { unit } from 'mathjs' +import { setDataLakeVariableData } from '@/libs/actions/data-lake' import { sendMavlinkMessage } from '@/libs/communication/mavlink' import type { MAVLinkMessageDictionary, Package, Type } from '@/libs/connection/m2r/messages/mavlink2rest' import { @@ -294,6 +295,10 @@ export abstract class ArduPilotVehicle extends Vehicle.AbstractVehicle setDataLakeVariableData(path, value)) + // Update our internal messages this._messages.set(mavlink_message.message.type, { ...mavlink_message.message, epoch: new Date().getTime() }) From f4cd16b3918ace56f37b1235d888298bf8e234dd Mon Sep 17 00:00:00 2001 From: Willian Galvani Date: Tue, 17 Dec 2024 11:00:45 -0300 Subject: [PATCH 2/7] Create external api --- package.json | 3 +- src/libs/external-api/api.ts | 43 +++++++++++++ .../external-api/callback-rate-limiter.ts | 29 +++++++++ vite.config.ts | 63 ++++++++++++++++--- 4 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 src/libs/external-api/api.ts create mode 100644 src/libs/external-api/callback-rate-limiter.ts diff --git a/package.json b/package.json index f8674b64a..9a9f1144f 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "serve": "vite preview", "test:ci": "vitest --coverage --run", "test:unit": "vitest", - "typecheck": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false" + "typecheck": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false", + "build:lib": "BUILD_MODE=library vite build" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.4.0", diff --git a/src/libs/external-api/api.ts b/src/libs/external-api/api.ts new file mode 100644 index 000000000..2ad255286 --- /dev/null +++ b/src/libs/external-api/api.ts @@ -0,0 +1,43 @@ +import { CallbackRateLimiter } from './callback-rate-limiter' + +/** + * Current version of the Cockpit Widget API + */ +export const COCKPIT_WIDGET_API_VERSION = '0.0.0' + +/** + * Listens to updates for a specific datalake variable. + * This function sets up a message listener that receives updates from the parent window + * and forwards them to the callback function, respecting the specified rate limit. + * @param {string} variableId - The name of the datalake variable to listen to + * @param {Function} callback - The function to call when the variable is updated + * @param {number} maxRateHz - The maximum rate (in Hz) at which updates should be received. Default is 10 Hz + * @example + * ```typescript + * // Listen to updates at 5Hz + * listenToDatalakeVariable('cockpit-memory-usage', (value) => { + * console.log('Memory Usage:', value); + * }, 5); + * ``` + */ +export function listenToDatalakeVariable(variableId: string, callback: (data: any) => void, maxRateHz = 10): void { + // Convert Hz to minimum interval in milliseconds + const minIntervalMs = 1000 / maxRateHz + const rateLimiter = new CallbackRateLimiter(minIntervalMs) + + const message = { + type: 'cockpit:listenToDatalakeVariables', + variable: variableId, + maxRateHz: maxRateHz, + } + window.parent.postMessage(message, '*') + + window.addEventListener('message', function handler(event) { + if (event.data.type === 'cockpit:datalakeVariable' && event.data.variable === variableId) { + // Only call callback if we haven't exceeded the rate limit + if (rateLimiter.canCall()) { + callback(event.data.value) + } + } + }) +} diff --git a/src/libs/external-api/callback-rate-limiter.ts b/src/libs/external-api/callback-rate-limiter.ts new file mode 100644 index 000000000..1ebf35592 --- /dev/null +++ b/src/libs/external-api/callback-rate-limiter.ts @@ -0,0 +1,29 @@ +/** + * A simple rate limiter for callbacks that ensures a minimum time interval between calls + */ +export class CallbackRateLimiter { + private lastCallTime: number + + /** + * Creates a new CallbackRateLimiter + * @param {number} minIntervalMs - The minimum time (in milliseconds) that must pass between calls + */ + constructor(private minIntervalMs: number) {} + + /** + * Checks if enough time has passed to allow another call + * @returns {boolean} true if enough time has passed since the last call, false otherwise + */ + public canCall(): boolean { + const now = Date.now() + const lastCall = this.lastCallTime || 0 + const timeSinceLastCall = now - lastCall + + if (timeSinceLastCall >= this.minIntervalMs) { + this.lastCallTime = now + return true + } + + return false + } +} diff --git a/vite.config.ts b/vite.config.ts index 3bbdf9ef0..628b3a513 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -10,8 +10,10 @@ import { getVersion } from './src/libs/non-browser-utils' // Check if we're running in Electron mode or building the application const isElectron = process.env.ELECTRON === 'true' const isBuilding = process.argv.includes('build') +const isLibrary = process.env.BUILD_MODE === 'library' -export default defineConfig({ +// Base configuration that will be merged +const baseConfig = { plugins: [ (isElectron || isBuilding) && electron([ @@ -44,13 +46,15 @@ export default defineConfig({ vuetify({ autoImport: true, }), - VitePWA({ - registerType: 'autoUpdate', - devOptions: { - enabled: true, - }, - includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'masked-icon.svg'], - }), + // Only include PWA plugin when NOT building the library + !isLibrary && + VitePWA({ + registerType: 'autoUpdate', + devOptions: { + enabled: true, + }, + includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'masked-icon.svg'], + }), ].filter(Boolean), define: { 'process.env': {}, @@ -70,4 +74,47 @@ export default defineConfig({ server: { host: '0.0.0.0', }, +} + +// Library-specific configuration +const libraryConfig = { + build: { + lib: { + entry: path.resolve(__dirname, 'src/libs/external-api/api.ts'), + name: 'CockpitAPI', + formats: ['es', 'umd', 'iife'], + fileName: (format: string) => { + switch (format) { + case 'iife': + return 'cockpit-external-api.browser.js' + default: + return `cockpit-external-api.${format}.js` + } + }, + }, + rollupOptions: { + external: ['vue', 'vuetify'], + output: { + globals: { + vue: 'Vue', + vuetify: 'Vuetify', + }, + }, + }, + outDir: 'dist/lib', + // Add copyPublicDir: false to prevent copying public assets + copyPublicDir: false, + }, +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export default defineConfig((_configEnv) => { + if (isLibrary) { + // For library builds, merge the base config with library-specific settings + return { + ...baseConfig, + ...libraryConfig, + } as any + } + return baseConfig as any }) From 3992fa16eee76983cba97a80568f4a6b4b09975b Mon Sep 17 00:00:00 2001 From: Willian Galvani Date: Tue, 17 Dec 2024 11:01:59 -0300 Subject: [PATCH 3/7] Iframe: add support for external API --- src/components/widgets/IFrame.vue | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/components/widgets/IFrame.vue b/src/components/widgets/IFrame.vue index 7d1328a89..fd01ebfdc 100644 --- a/src/components/widgets/IFrame.vue +++ b/src/components/widgets/IFrame.vue @@ -3,6 +3,7 @@