@canboat/visual-analyzer
Version:
NMEA 2000 data visualization utility (requires SK Server >= 2.15)
268 lines • 12.8 kB
JavaScript
;
/* eslint-disable @typescript-eslint/no-explicit-any */
Object.defineProperty(exports, "__esModule", { value: true });
const canboatjs_1 = require("@canboat/canboatjs");
const n2k_signalk_1 = require("@signalk/n2k-signalk");
const server_1 = require("./server");
const recording_service_1 = require("./recording-service");
const PLUGIN_ID = 'canboat-visual-analyzer';
const PLUGIN_NAME = 'Canboat Visual Analyzer';
module.exports = function (app) {
let onStop = [];
//let dbusSetValue: any
let canboatParser;
let n2kMapper;
let recordingService;
const plugin = {
id: PLUGIN_ID,
name: PLUGIN_NAME,
description: 'Canboat Visual Analyzer',
schema: () => {
return {
title: PLUGIN_NAME,
type: 'object',
properties: {},
};
},
stop: () => {
onStop.forEach((f) => f());
onStop = [];
},
start: (_options) => {
canboatParser = new canboatjs_1.FromPgn({
checkForInvalidFields: true,
useCamel: true, // Default value
useCamelCompat: false,
returnNonMatches: true,
createPGNObjects: true,
includeInputData: true,
includeRawData: true,
includeByteMapping: true,
});
canboatParser.on('error', (error) => {
;
app.debug('Canboat Parser error:', error);
});
n2kMapper = new n2k_signalk_1.N2kMapper({});
recordingService = new recording_service_1.RecordingService(`${app.config.configPath}/visual-analyzer`);
const anyapp = app;
recordingService.on('started', (status) => {
console.log('Recording started:', status);
anyapp.emit('recording:started', status);
});
recordingService.on('stopped', (status) => {
console.log('Recording stopped:', status);
anyapp.emit('recording:stopped', status);
});
recordingService.on('error', (error) => {
console.error('Recording error:', error);
anyapp.emit('recording:error', {
error: error.message,
});
});
recordingService.on('progress', (status) => {
anyapp.emit('recording:progress', status);
});
recordingService.on('error', (error) => {
console.error('Recording error:', error);
anyapp.emit('recording:error', {
error: error.message,
});
});
anyapp.on('canboatjs:rawoutput', (output) => {
if (recordingService.getStatus().isRecording) {
if (recordingService.getStatus().format === 'passthrough') {
recordingService.recordMessage(output, undefined);
}
else {
try {
const pgn = canboatParser.parse(output);
if (pgn) {
recordingService.recordMessage(undefined, pgn);
}
}
catch (error) {
console.debug('Failed to parse raw NMEA data:', error);
}
}
}
});
},
registerWithRouter: (router) => {
router.post('/api/send-n2k', (req, res) => {
try {
const values = req.body.values;
if (!values) {
return res.status(400).json({
success: false,
error: 'Missing required field: values',
});
}
const pgnDataArray = [];
for (const value of values) {
// Check if input is a string (NMEA 2000 format) or JSON
if (typeof value === 'string') {
const lines = value.split(/\r?\n/).filter((line) => line.trim());
if (lines.length === 0) {
return res.status(400).json({
success: false,
error: 'No valid lines found in input',
});
}
try {
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine) {
try {
const parsed = canboatParser.parseString(trimmedLine);
if (parsed) {
pgnDataArray.push(parsed);
}
else {
console.warn(`Unable to parse line: ${trimmedLine}`);
}
}
catch (lineParseError) {
const errorMessage = lineParseError instanceof Error ? lineParseError.message : 'Unknown error';
console.warn(`Error parsing line "${trimmedLine}": ${errorMessage}`);
// Continue processing other lines instead of failing
}
}
}
if (pgnDataArray.length === 0) {
return res.status(400).json({
success: false,
error: 'Unable to parse any NMEA 2000 strings from input',
});
}
console.log(`Parsed ${pgnDataArray.length} NMEA 2000 messages from ${lines.length} lines using canboatjs`);
}
catch (canboatParseError) {
const errorMessage = canboatParseError instanceof Error ? canboatParseError.message : 'Unknown error';
return res.status(400).json({
success: false,
error: 'Error parsing NMEA 2000 strings: ' + errorMessage,
});
}
}
else if (typeof value === 'object') {
// Value is already a JSON object
pgnDataArray.push(value);
}
else {
return res.status(400).json({
success: false,
error: 'Value must be a string (JSON or NMEA 2000 format) or object',
});
}
}
// Process each parsed message
const results = [];
for (const pgnData of pgnDataArray) {
console.log('Processing NMEA 2000 message for transmission:', {
pgn: pgnData.pgn,
data: pgnData,
});
app.emit('nmea2000JsonOut', pgnData);
}
// Return success response in SignalK format
res.json({
success: true,
message: `${pgnDataArray.length} message(s) processed successfully`,
messagesProcessed: pgnDataArray.length,
results: results, // Include detailed results for each message
});
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error('Error processing input test request:', error);
res.status(500).json({
success: false,
error: errorMessage,
});
}
});
router.post('/api/transform/signalk', (req, res) => {
(0, server_1.translateToSignalK)(req, res, canboatParser, n2kMapper);
});
// Recording API routes
router.get('/api/recording/status', (req, res) => {
try {
const status = recordingService.getStatus();
res.json({ success: true, result: status });
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
res.status(500).json({ success: false, error: errorMessage });
}
});
router.post('/api/recording/start', (req, res) => {
try {
const { fileName, format } = req.body.value;
const result = recordingService.startRecording({ fileName, format });
res.json({
success: true,
fileName: result.fileName,
message: 'Recording started successfully',
});
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
res.status(400).json({ success: false, error: errorMessage });
}
});
router.post('/api/recording/stop', (req, res) => {
try {
const result = recordingService.stopRecording();
res.json({ success: true, message: 'Recording stopped successfully', finalStats: result });
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
res.status(400).json({ success: false, error: errorMessage });
}
});
router.get('/api/recording/files', (req, res) => {
try {
const files = recordingService.getRecordedFiles();
res.json({
success: true,
results: files, // Include detailed results for each message
});
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
res.status(500).json({ success: false, error: errorMessage });
}
});
router.delete('/api/recording/files/:fileName', (req, res) => {
try {
recordingService.deleteRecordedFile(req.params.fileName);
res.json({ success: true, message: 'File deleted successfully' });
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
res.status(400).json({ success: false, error: errorMessage });
}
});
router.get('/api/recording/files/:fileName/download', (req, res) => {
try {
const filePath = recordingService.getRecordedFilePath(req.params.fileName);
res.download(filePath, req.params.fileName, (err) => {
if (err) {
console.error('Download error:', err);
if (!res.headersSent) {
res.status(500).json({ success: false, error: 'Download failed' });
}
}
});
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
res.status(404).json({ success: false, error: errorMessage });
}
});
},
};
return plugin;
};
//# sourceMappingURL=plugin.js.map