Skip to content

Commit 6ba139a

Browse files
authored
Convert fla electron files to typescript (#826)
* Convert fla electron files to typescript * Rename fla utils file * Remove unused ts comment
1 parent d79c70c commit 6ba139a

6 files changed

Lines changed: 391 additions & 191 deletions

File tree

Lines changed: 133 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,105 @@
22
This file contains the logic for parsing different types of log files on the main electron process.
33
*/
44

5+
import { WebContents } from "electron"
56
import fs from "fs"
67
import readline from "readline"
78

8-
import createRecentLogsManager from "../settings/recentLogManager"
9+
import createRecentLogsManager from "./utils/recentLogManager"
910

1011
import {
11-
clearUnitCache,
1212
buildDefaultMessageFilters,
13-
calculateMeanValues,
1413
calcGPSOffset,
14+
calculateMeanValues,
15+
clearUnitCache,
1516
convertTimeUStoUTC,
1617
expandBATMessages,
1718
expandESCMessages,
19+
getUnit,
1820
processFlightModes,
1921
sortObjectByKeys,
20-
getUnit,
21-
} from "./utils/fla-utils"
22+
} from "./utils/flaUtils"
23+
24+
// Type definitions
25+
interface FormatMessage {
26+
length: number
27+
name: string
28+
type: number
29+
format: string
30+
fields: string[]
31+
units?: string
32+
multiplier?: string
33+
}
34+
35+
interface MessageObject {
36+
name: string
37+
type?: number
38+
TimeUS?: number
39+
[key: string]: string | number | undefined
40+
}
41+
42+
interface Messages {
43+
[messageName: string]:
44+
| MessageObject[]
45+
| { [key: string]: FormatMessage }
46+
| { [key: string]: string }
47+
| string
48+
| null
49+
}
50+
51+
interface ParseResult {
52+
success: boolean
53+
error?: string
54+
summary?: LogSummary
55+
}
56+
57+
interface LogSummary {
58+
formatMessages: { [key: string]: FormatMessage }
59+
utcAvailable: boolean
60+
logEvents: MessageObject[]
61+
flightModeMessages: MessageObject[]
62+
logType: string
63+
messageFilters: Record<string, unknown>
64+
messageMeans: Record<
65+
string,
66+
{ mean: string; max: string; min: string }
67+
> | null
68+
aircraftType: string | null
69+
}
70+
71+
interface Dataset {
72+
label: string
73+
yAxisID: string
74+
x: Float64Array
75+
y: Float32Array
76+
}
77+
78+
type LogType = "dataflash" | "fgcs_telemetry" | "mp_telemetry" | null
2279

2380
const UPDATE_THROTTLE_MS = 100 // Update every 100ms
2481
const recentLogsManager = createRecentLogsManager()
25-
let logData = null
26-
let defaultMessageFilters = {}
27-
28-
async function parseDataflashLogFile(rl, fileStream, fileSize, webContents) {
82+
let logData: Messages | null = null
83+
let defaultMessageFilters: Record<string, unknown> = {}
84+
85+
async function parseDataflashLogFile(
86+
rl: readline.Interface,
87+
fileStream: fs.ReadStream,
88+
fileSize: number,
89+
webContents: WebContents,
90+
): Promise<Messages> {
2991
// https://ardupilot.org/copter/docs/logmessages.html
3092
// https://github.com/ArduPilot/ardupilot/tree/master/libraries/AP_Logger
3193

3294
return new Promise((resolve, reject) => {
3395
const stringTypes = new Set(["n", "N", "Z", "M"])
34-
let aircraftType = null
96+
let aircraftType: string | null = null
3597
let lastUpdateTime = 0
3698

37-
const formatMessages = {}
38-
const messages = {}
39-
const units = {}
99+
const formatMessages: { [key: string]: FormatMessage } = {}
100+
const messages: Messages = {}
101+
const units: { [key: string]: string } = {}
40102

41-
rl.on("line", (line) => {
103+
rl.on("line", (line: string) => {
42104
// Skip empty lines early
43105
if (!line || line.length < 3) return
44106

@@ -116,7 +178,7 @@ async function parseDataflashLogFile(rl, fileStream, fileSize, webContents) {
116178
messages[messageName] = []
117179
}
118180

119-
const messageObj = {
181+
const messageObj: MessageObject = {
120182
name: messageName,
121183
type: formatMessage.type,
122184
}
@@ -144,7 +206,7 @@ async function parseDataflashLogFile(rl, fileStream, fileSize, webContents) {
144206
}
145207
}
146208

147-
messages[messageName].push(messageObj)
209+
;(messages[messageName] as MessageObject[]).push(messageObj)
148210
}
149211
}
150212

@@ -168,26 +230,26 @@ async function parseDataflashLogFile(rl, fileStream, fileSize, webContents) {
168230
resolve(messages)
169231
})
170232

171-
rl.on("error", (err) => {
233+
rl.on("error", (err: Error) => {
172234
console.error("Error reading log file:", err)
173235
reject(err)
174236
})
175237
})
176238
}
177239

178240
async function parseFgcsTelemetryLogFile(
179-
rl,
180-
fileStream,
181-
fileSize,
182-
webContents,
183-
) {
184-
const formatMessages = {}
185-
const messages = {}
241+
rl: readline.Interface,
242+
fileStream: fs.ReadStream,
243+
fileSize: number,
244+
webContents: WebContents,
245+
): Promise<Messages> {
246+
const formatMessages: { [key: string]: FormatMessage } = {}
247+
const messages: Messages = {}
186248

187249
let lastUpdateTime = 0
188250

189251
return new Promise((resolve, reject) => {
190-
rl.on("line", (line) => {
252+
rl.on("line", (line: string) => {
191253
if (!line || line.length < 5 || line.includes("==")) {
192254
return
193255
}
@@ -210,7 +272,7 @@ async function parseFgcsTelemetryLogFile(
210272

211273
// Get field names and cache format
212274
if (!formatMessages[messageName]) {
213-
const fields = []
275+
const fields: string[] = []
214276
for (let i = 0; i < messageData.length; i++) {
215277
const keyVal = messageData[i]?.trim()
216278
if (keyVal) {
@@ -220,10 +282,16 @@ async function parseFgcsTelemetryLogFile(
220282
}
221283
}
222284
}
223-
formatMessages[messageName] = { fields }
285+
formatMessages[messageName] = {
286+
fields,
287+
length: 0,
288+
name: messageName,
289+
type: 0,
290+
format: "",
291+
}
224292
}
225293

226-
const messageObj = {
294+
const messageObj: MessageObject = {
227295
TimeUS: Math.round(timestamp * 1000), // Use round instead of parseInt for better precision
228296
name: messageName,
229297
}
@@ -247,7 +315,7 @@ async function parseFgcsTelemetryLogFile(
247315
messages[messageName] = []
248316
}
249317

250-
messages[messageName].push(messageObj)
318+
;(messages[messageName] as MessageObject[]).push(messageObj)
251319

252320
const now = Date.now()
253321
if (now - lastUpdateTime > UPDATE_THROTTLE_MS) {
@@ -267,18 +335,18 @@ async function parseFgcsTelemetryLogFile(
267335
resolve(messages)
268336
})
269337

270-
rl.on("error", (err) => {
338+
rl.on("error", (err: Error) => {
271339
console.error("Error reading log file:", err)
272340
reject(err)
273341
})
274342
})
275343
}
276344

277-
function determineLogFileType(filePath, firstLine) {
345+
function determineLogFileType(filePath: string, firstLine: string): LogType {
278346
const reFileExtension = /(?:\.([^.]+))?$/ // https://stackoverflow.com/a/680982
279-
const ext = reFileExtension.exec(filePath)[1]
347+
const ext = reFileExtension.exec(filePath)?.[1]
280348

281-
const extensionToTypeMap = {
349+
const extensionToTypeMap: { [key: string]: LogType } = {
282350
log: "dataflash",
283351
ftlog: "fgcs_telemetry",
284352
// exclude txt from map as txt's are ambiguous
@@ -304,21 +372,21 @@ function determineLogFileType(filePath, firstLine) {
304372
}
305373

306374
// New function to get recent files
307-
export function getRecentFiles() {
375+
export function getRecentFiles(): string[] {
308376
return recentLogsManager.getRecentLogs()
309377
}
310378

311379
// New function to clear recent files
312-
export function clearRecentFiles() {
380+
export function clearRecentFiles(): void {
313381
recentLogsManager.clearRecentLogs()
314382
}
315383

316-
async function getFirstLine(pathToFile) {
384+
async function getFirstLine(pathToFile: string): Promise<string> {
317385
// https://stackoverflow.com/a/60193465/23139916
318386
const readable = fs.createReadStream(pathToFile)
319387
const reader = readline.createInterface({ input: readable })
320-
const line = await new Promise((resolve) => {
321-
reader.on("line", (line) => {
388+
const line = await new Promise<string>((resolve) => {
389+
reader.on("line", (line: string) => {
322390
reader.close()
323391
resolve(line)
324392
})
@@ -328,10 +396,14 @@ async function getFirstLine(pathToFile) {
328396
}
329397

330398
// function to process and save the log file data
331-
function processAndSaveLogData(loadedLogMessages, logType) {
399+
function processAndSaveLogData(
400+
loadedLogMessages: Messages,
401+
logType: string,
402+
): LogSummary {
332403
clearUnitCache() // Clear cache when loading new file
333-
const aircraftType = loadedLogMessages.aircraftType
334-
delete loadedLogMessages.aircraftType
404+
const aircraftType =
405+
(loadedLogMessages["aircraftType"] as string | null) || null
406+
delete loadedLogMessages["aircraftType"]
335407

336408
const initialFilters = buildDefaultMessageFilters(loadedLogMessages)
337409

@@ -351,7 +423,7 @@ function processAndSaveLogData(loadedLogMessages, logType) {
351423

352424
// Convert TimeUS to TimeUTC if GPS data is available
353425
let finalMessages = { ...expandedMessages }
354-
let gpsOffset = null
426+
let gpsOffset: number | null = null
355427
let utcAvailable = false
356428
if (finalMessages.GPS && logType === "dataflash") {
357429
gpsOffset = calcGPSOffset(finalMessages)
@@ -375,7 +447,7 @@ function processAndSaveLogData(loadedLogMessages, logType) {
375447
return {
376448
formatMessages: finalFormats,
377449
utcAvailable,
378-
logEvents: finalMessages["EV"] || [],
450+
logEvents: (finalMessages["EV"] as MessageObject[]) || [],
379451
flightModeMessages,
380452
logType,
381453
messageFilters: defaultMessageFilters,
@@ -384,7 +456,10 @@ function processAndSaveLogData(loadedLogMessages, logType) {
384456
}
385457
}
386458

387-
export default async function openFile(event, filePath) {
459+
export default async function openFile(
460+
event: { sender: WebContents },
461+
filePath: string | null,
462+
): Promise<ParseResult> {
388463
if (filePath == null) {
389464
return { success: false, error: "No file path provided" }
390465
}
@@ -410,7 +485,7 @@ export default async function openFile(event, filePath) {
410485
crlfDelay: Infinity,
411486
})
412487

413-
let messages = null
488+
let messages: Messages | null = null
414489

415490
if (logType === "dataflash") {
416491
messages = await parseDataflashLogFile(
@@ -446,14 +521,19 @@ export default async function openFile(event, filePath) {
446521
} else {
447522
return { success: false, error: "Failed to parse log file" }
448523
}
449-
} catch (err) {
524+
} catch (err: unknown) {
450525
console.error("Error parsing log file:", err)
451-
return { success: false, error: err.message || "Unknown parsing error" }
526+
const errorMessage =
527+
err instanceof Error ? err.message : "Unknown parsing error"
528+
return { success: false, error: errorMessage }
452529
}
453530
}
454531

455532
// on-demand retrieval of messages
456-
export async function getMessages(_event, requestedMessages) {
533+
export async function getMessages(
534+
_event: unknown,
535+
requestedMessages: string[],
536+
): Promise<Dataset[] | { success: false; error: string }> {
457537
// each requestedMessage should be of the form `${requestedMessageName}/${requestedFieldName}`
458538
// like ['ARM/ArmState', 'ARSP/Airspeed']
459539

@@ -472,9 +552,10 @@ export async function getMessages(_event, requestedMessages) {
472552
return []
473553
}
474554

475-
const formatMessages = logData.format || {}
476-
const units = logData.units || {}
477-
const datasets = []
555+
const formatMessages =
556+
(logData.format as { [key: string]: FormatMessage }) || {}
557+
const units = (logData.units as { [key: string]: string }) || {}
558+
const datasets: Dataset[] = []
478559

479560
// Loop through the list of requested messages and transform each of them
480561
for (
@@ -494,7 +575,7 @@ export async function getMessages(_event, requestedMessages) {
494575
const fmt = formatMessages[categoryName]
495576
const hasField = fmt?.fields.includes(fieldName)
496577

497-
const series = logData[categoryName]
578+
const series = logData[categoryName] as MessageObject[]
498579

499580
if (!hasField || !Array.isArray(series) || series.length === 0) {
500581
// Skip unknown or unavailable labels

gcs/electron/main.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@ import fs from "node:fs"
1616
import path from "node:path"
1717
import packageInfo from "../package.json"
1818

19-
import openFile, {
20-
clearRecentFiles,
21-
getRecentFiles,
22-
getMessages,
23-
// @ts-expect-error - no types available
24-
} from "./fla"
19+
import openFile, { clearRecentFiles, getMessages, getRecentFiles } from "./fla"
2520
import registerAboutIPC, {
2621
destroyAboutWindow,
2722
openAboutPopout,
@@ -42,15 +37,7 @@ import registerVibeStatusIPC, {
4237
} from "./modules/vibeStatusWindow"
4338
import registerVideoIPC, { destroyVideoWindow } from "./modules/videoWindow"
4439
import { readParamsFile } from "./utils/paramsFile"
45-
// The built directory structure
46-
//
47-
// ├─┬─┬ dist
48-
// │ │ └── index.html
49-
// │ │
50-
// │ ├─┬ dist-electron
51-
// │ │ ├── main.js
52-
// │ │ └── preload.js
53-
// │
40+
5441
process.env.DIST = path.join(__dirname, "../dist")
5542
process.env.VITE_PUBLIC = app.isPackaged
5643
? process.env.DIST

0 commit comments

Comments
 (0)