-
Notifications
You must be signed in to change notification settings - Fork 131
refactor: migrate sensors from TypeScript to JSON data #1258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,183 @@ | ||||||||
| #!/usr/bin/env node | ||||||||
| /** | ||||||||
| * Parse sensors.ts and convert to JSON using regex | ||||||||
| * This doesn't require importing the TypeScript modules | ||||||||
| */ | ||||||||
|
|
||||||||
| import { readFileSync, writeFileSync } from 'fs'; | ||||||||
| import { join, dirname } from 'path'; | ||||||||
| import { fileURLToPath } from 'url'; | ||||||||
|
|
||||||||
| const __filename = fileURLToPath(import.meta.url); | ||||||||
| const __dirname = dirname(__filename); | ||||||||
|
|
||||||||
| const sensorsPath = join(__dirname, '../src/app/data/catalogs/sensors.ts'); | ||||||||
| const outputPath = join(__dirname, '../src/app/data/catalogs/sensors.json'); | ||||||||
|
|
||||||||
| // Read the file | ||||||||
| const content = readFileSync(sensorsPath, 'utf-8'); | ||||||||
|
|
||||||||
| // Extract the sensors object | ||||||||
| const match = content.match(/export const sensors = <SensorList>\{([\s\S]*?)\n\};/); | ||||||||
|
|
||||||||
| if (!match) { | ||||||||
| console.error('Could not find sensors object'); | ||||||||
| process.exit(1); | ||||||||
| } | ||||||||
|
|
||||||||
| const sensorsContent = match[1]; | ||||||||
|
|
||||||||
| // Split into individual sensor blocks | ||||||||
| const sensorBlocks = []; | ||||||||
| let currentBlock = ''; | ||||||||
| let braceCount = 0; | ||||||||
| let inSensor = false; | ||||||||
| let currentKey = ''; | ||||||||
|
|
||||||||
| const lines = sensorsContent.split('\n'); | ||||||||
|
|
||||||||
| for (const line of lines) { | ||||||||
| const trimmed = line.trim(); | ||||||||
|
|
||||||||
| // Check if this is the start of a sensor definition | ||||||||
| const sensorMatch = trimmed.match(/^(\w+):\s*new\s+(RfSensor|DetailedSensor)\(\{/); | ||||||||
|
|
||||||||
| if (sensorMatch) { | ||||||||
| if (currentBlock) { | ||||||||
| sensorBlocks.push({ key: currentKey, type: currentBlock.type, content: currentBlock.content }); | ||||||||
| } | ||||||||
| currentKey = sensorMatch[1]; | ||||||||
| currentBlock = { type: sensorMatch[2], content: '' }; | ||||||||
| inSensor = true; | ||||||||
| braceCount = 1; | ||||||||
| continue; | ||||||||
| } | ||||||||
|
|
||||||||
| if (inSensor) { | ||||||||
| // Count braces | ||||||||
| for (const char of line) { | ||||||||
| if (char === '{') { | ||||||||
| braceCount++; | ||||||||
| } | ||||||||
| if (char === '}') { | ||||||||
| braceCount--; | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| if (braceCount === 0) { | ||||||||
| // End of sensor definition | ||||||||
| inSensor = false; | ||||||||
| } else { | ||||||||
| currentBlock.content += line + '\n'; | ||||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| // Don't forget the last block | ||||||||
| if (currentBlock && currentKey) { | ||||||||
| sensorBlocks.push({ key: currentKey, type: currentBlock.type, content: currentBlock.content }); | ||||||||
| } | ||||||||
|
|
||||||||
| // Parse each sensor block | ||||||||
| const sensors = {}; | ||||||||
|
|
||||||||
| for (const block of sensorBlocks) { | ||||||||
| const sensor = { | ||||||||
| _sensorType: block.type, | ||||||||
| }; | ||||||||
|
|
||||||||
| const lines = block.content.split('\n'); | ||||||||
|
|
||||||||
| for (const line of lines) { | ||||||||
| const trimmed = line.trim(); | ||||||||
|
|
||||||||
| if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) { | ||||||||
| continue; | ||||||||
| } | ||||||||
|
|
||||||||
| // Match property: value pattern - more flexible to capture full values | ||||||||
| const propMatch = trimmed.match(/^(\w+):\s*(.+)$/); | ||||||||
|
|
||||||||
| if (propMatch) { | ||||||||
| let [, key, value] = propMatch; | ||||||||
|
|
||||||||
| // Remove comments at the end (but only after quoted strings or other values) | ||||||||
| // This regex looks for comments that start after a quote, comma, or closing bracket | ||||||||
| value = value.replace(/(['"\],}])\s*\/\/.*$/, '$1'); | ||||||||
|
|
||||||||
| // Remove trailing comma | ||||||||
| value = value.replace(/,\s*$/, ''); | ||||||||
|
|
||||||||
| // Remove type assertions like <Degrees> | ||||||||
| value = value.replace(/<\w+>/g, ''); | ||||||||
|
|
||||||||
| // Remove 'as Type' assertions but preserve content | ||||||||
| value = value.replace(/\s+as\s+\w+/g, ''); | ||||||||
|
|
||||||||
| value = value.trim(); | ||||||||
|
|
||||||||
| // Parse the value | ||||||||
| try { | ||||||||
| // Handle arrays | ||||||||
| if (value.startsWith('[')) { | ||||||||
| // Clean array content | ||||||||
| const cleaned = value.replace(/\s+as\s+\w+/g, ''); | ||||||||
|
|
||||||||
| // Try to parse array content | ||||||||
| // Handle enum arrays like [CommLink.AEHF, CommLink.WGS] | ||||||||
| const arrayMatch = cleaned.match(/\[(.*)\]/); | ||||||||
|
|
||||||||
| if (arrayMatch) { | ||||||||
| const content = arrayMatch[1]; | ||||||||
|
|
||||||||
| // Check if it contains enum values | ||||||||
| if (content.includes('.')) { | ||||||||
| // It's an enum array, split and keep as strings | ||||||||
| sensor[key] = content.split(',').map(item => item.trim()); | ||||||||
| } else { | ||||||||
| // Try to parse as JSON | ||||||||
| try { | ||||||||
| sensor[key] = JSON.parse(cleaned); | ||||||||
| } catch { | ||||||||
| sensor[key] = content.split(',').map(item => { | ||||||||
| const trimmed = item.trim(); | ||||||||
|
|
||||||||
| return isNaN(Number(trimmed)) ? trimmed : parseFloat(trimmed); | ||||||||
| }); | ||||||||
|
Comment on lines
+142
to
+146
|
||||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
| // Handle booleans | ||||||||
| else if (value === 'true') { | ||||||||
| sensor[key] = true; | ||||||||
| } else if (value === 'false') { | ||||||||
| sensor[key] = false; | ||||||||
| } | ||||||||
| // Handle numbers | ||||||||
| else if (/^-?\d+\.?\d*$/.test(value)) { | ||||||||
| sensor[key] = parseFloat(value); | ||||||||
| } | ||||||||
| // Handle strings | ||||||||
| else if (value.startsWith("'") || value.startsWith('"')) { | ||||||||
| sensor[key] = value.replace(/^['"]|['"]$/g, ''); | ||||||||
| } | ||||||||
| // Handle enum values (keep as string) | ||||||||
| else { | ||||||||
| sensor[key] = value; | ||||||||
| } | ||||||||
| } catch (e) { | ||||||||
| // If parsing fails, store as string | ||||||||
|
||||||||
| // If parsing fails, store as string | |
| // If parsing fails, store as string and log a warning | |
| console.warn(`Warning: Failed to parse value for key "${key}". Storing as string. Value: ${JSON.stringify(value)}. Error: ${e.message}`); |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,74 @@ | ||||||||
| # Sensor Data Management | ||||||||
|
|
||||||||
| ## Overview | ||||||||
|
|
||||||||
| Sensors are now loaded from a JSON file (`sensors.json`) instead of being hardcoded in TypeScript. This allows for easier migration to remote data sources in the future without requiring code changes. | ||||||||
|
|
||||||||
| ## Files | ||||||||
|
|
||||||||
| - **sensors.json**: Contains all sensor data in JSON format | ||||||||
| - **sensorLoader.ts**: Loads sensors from JSON and creates sensor objects | ||||||||
| - **sensors.ts**: Legacy TypeScript file (kept for type definitions and enums) | ||||||||
|
||||||||
|
|
||||||||
| ## Architecture | ||||||||
|
|
||||||||
| 1. `sensors.json` contains all sensor configurations as plain JSON objects | ||||||||
| 2. `sensorLoader.ts` reads the JSON file and instantiates `DetailedSensor` or `RfSensor` objects | ||||||||
| 3. All application code imports sensors from `sensorLoader.ts` instead of `sensors.ts` | ||||||||
|
|
||||||||
| ## Adding or Modifying Sensors | ||||||||
|
|
||||||||
| ### Option 1: Edit JSON Directly | ||||||||
|
|
||||||||
| Edit `sensors.json` and add/modify sensor entries: | ||||||||
|
|
||||||||
| ```json | ||||||||
| { | ||||||||
| "NEWSENSOR": { | ||||||||
| "_sensorType": "RfSensor", | ||||||||
| "objName": "NEWSENSOR", | ||||||||
| "name": "New Sensor Name", | ||||||||
| "lat": 40.0, | ||||||||
| "lon": -75.0, | ||||||||
| "alt": 0.1, | ||||||||
| ... | ||||||||
| } | ||||||||
| } | ||||||||
| ``` | ||||||||
|
|
||||||||
| ### Option 2: Edit TypeScript and Regenerate | ||||||||
|
|
||||||||
| 1. Edit `sensors.ts` to add/modify sensor definitions | ||||||||
| 2. Run the conversion script: | ||||||||
| ```bash | ||||||||
| node scripts/parse-sensors.mjs | ||||||||
| ``` | ||||||||
| 3. This will regenerate `sensors.json` from `sensors.ts` | ||||||||
|
|
||||||||
| ## Migrating to Remote Sources | ||||||||
|
|
||||||||
| To load sensors from a remote URL: | ||||||||
|
|
||||||||
| 1. Modify `sensorLoader.ts` to fetch JSON from a remote endpoint | ||||||||
| 2. Keep the same JSON schema | ||||||||
| 3. No changes needed in application code | ||||||||
|
|
||||||||
| Example: | ||||||||
| ```typescript | ||||||||
| // In sensorLoader.ts | ||||||||
| const response = await fetch('https://api.example.com/sensors.json'); | ||||||||
| const sensorsData = await response.json(); | ||||||||
| ``` | ||||||||
|
|
||||||||
|
||||||||
| **Note:** The current implementation in `sensorLoader.ts` loads sensors synchronously from a local JSON file. To support remote loading as shown above, you would need to make the loader function (`loadSensorsFromJson()`) asynchronous and update all calling code to use `await` or handle Promises. This is a future enhancement and not currently supported. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex pattern for removing comments (
value.replace(/(['"\],}])\s*\/\/.*$/, '$1')) at line 106 is too restrictive and may not catch all inline comments. For example, it won't remove comments that appear after numeric values or unquoted identifiers (e.g.,maxEl: 90 // comment). Consider using a more comprehensive pattern like/\s*\/\/.*$/after proper string/array parsing.