Skip to content

Commit 955513e

Browse files
Merge pull request #2547 from iNavFlight/maintenance-9.x
Maintenance 9.x
2 parents a869ede + 1a85fc5 commit 955513e

28 files changed

+1488
-428
lines changed

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ inav-configurator/
3939
│ └── transpiler/ # Logic conditions compiler
4040
├── src/css/ # Stylesheets
4141
│ └── tabs/ # Per-tab styles
42-
├── locale/ # i18n translations (en, ja, uk, zh_CN)
42+
├── locale/ # i18n translations (en, ja, ru, uk, zh_CN)
4343
├── resources/ # 3D models, OSD fonts, SITL binaries
4444
├── index.html # Single-page app entry
4545
├── forge.config.js # Electron Forge build config

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,21 @@ Example (note the double -- ):
113113
To be able to open Inspector, set environment variable `NODE_ENV` to `development` or set the flag directly when run `npm start`:
114114

115115
```NODE_ENV=development npm start``` or ```$env:NODE_ENV="development" | npm start``` for Windows PowerShell
116+
Chrome Devtools will be available on http://localhost:9222. This can also be used with an MCP plugin
116117

117118
Or use VScode and start a debug session `Debug Configurator` (Just hit F5!)
118119

119120
To debug the main thread (source files in `js/main`), just set a breakpoint in VScode.
120121

122+
123+
To capture a debug log from a packaged version (such as from a user), they can run it as:
124+
```.\inav-configurator.exe --enable-logging --log-file=inav-log.txt```
125+
or to log to the console:
126+
```.\inav-configurator.exe --enable-logging --log-file=inav-log.txt```
127+
128+
129+
130+
121131
## Different map providers
122132

123133
INAV Configurator allows you to choose between OpenStreetMap, Esri World Imagery (Aerial View), and MapProxy map providers.

forge.config.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,38 @@ export default {
4949
// Remove SITL binaries for other platforms/architectures to reduce package size
5050
postPackage: async (forgeConfig, options) => {
5151
for (const outputPath of options.outputPaths) {
52-
const sitlPath = path.join(outputPath, 'resources', 'sitl');
53-
if (!fs.existsSync(sitlPath)) continue;
52+
let sitlPath;
53+
54+
if (options.platform === 'darwin') {
55+
// macOS app bundle structure: <outputDir>/<AppName>.app/Contents/Resources/sitl
56+
// Find the .app directory
57+
const appBundles = fs.readdirSync(outputPath).filter(f => f.endsWith('.app'));
58+
if (appBundles.length === 0) {
59+
console.log(`postPackage: No .app bundle found in ${outputPath}`);
60+
continue;
61+
}
62+
sitlPath = path.join(outputPath, appBundles[0], 'Contents', 'Resources', 'sitl');
63+
} else {
64+
// Windows/Linux: <outputPath>/resources/sitl
65+
sitlPath = path.join(outputPath, 'resources', 'sitl');
66+
}
67+
68+
console.log(`postPackage: Checking SITL path for ${options.platform}: ${sitlPath}`);
69+
if (!fs.existsSync(sitlPath)) {
70+
console.log(`postPackage: SITL path not found, skipping: ${sitlPath}`);
71+
continue;
72+
}
5473

5574
if (options.platform === 'win32') {
75+
console.log('postPackage: Removing non-Windows SITL binaries (linux, macos)');
5676
fs.rmSync(path.join(sitlPath, 'linux'), { recursive: true, force: true });
5777
fs.rmSync(path.join(sitlPath, 'macos'), { recursive: true, force: true });
5878
} else if (options.platform === 'darwin') {
79+
console.log('postPackage: Removing non-macOS SITL binaries (linux, windows)');
5980
fs.rmSync(path.join(sitlPath, 'linux'), { recursive: true, force: true });
6081
fs.rmSync(path.join(sitlPath, 'windows'), { recursive: true, force: true });
6182
} else if (options.platform === 'linux') {
83+
console.log('postPackage: Removing non-Linux SITL binaries (macos, windows)');
6284
fs.rmSync(path.join(sitlPath, 'macos'), { recursive: true, force: true });
6385
fs.rmSync(path.join(sitlPath, 'windows'), { recursive: true, force: true });
6486
// Remove wrong architecture

js/connection/connectionBle.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ class ConnectionBle extends Connection {
4848
this._writeCharacteristic = false;
4949
this._device = false;
5050
this._deviceDescription = false;
51-
this._onCharateristicValueChangedListeners = [];
5251
this._onDisconnectListeners = [];
5352
this._reconnects = 0;
5453
this._handleOnCharateristicValueChanged = false;
@@ -166,11 +165,13 @@ class ConnectionBle extends Connection {
166165
buffer[i] = event.target.value.getUint8(i);
167166
}
168167

169-
this._onCharateristicValueChangedListeners.forEach(listener => {
170-
listener({
171-
connectionId: 0xFF,
172-
data: buffer
173-
});
168+
const info = {
169+
connectionId: 0xFF,
170+
data: buffer
171+
};
172+
173+
this._onReceiveListeners.forEach(listener => {
174+
listener(info);
174175
});
175176
};
176177

@@ -242,11 +243,11 @@ class ConnectionBle extends Connection {
242243
}
243244

244245
addOnReceiveCallback(callback){
245-
this._onCharateristicValueChangedListeners.push(callback);
246+
this._onReceiveListeners.push(callback);
246247
}
247248

248249
removeOnReceiveCallback(callback){
249-
this._onCharateristicValueChangedListeners = this._onCharateristicValueChangedListeners.filter(listener => listener !== callback);
250+
this._onReceiveListeners = this._onReceiveListeners.filter(listener => listener !== callback);
250251
}
251252

252253
addOnReceiveErrorCallback(callback) {

js/defaults_dialog_entries.js

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -453,18 +453,6 @@ var defaultsDialogData = [
453453
{
454454
key: "yaw_rate",
455455
value: 3
456-
},
457-
{
458-
key: "nav_fw_pos_z_p",
459-
value: 35
460-
},
461-
{
462-
key: "nav_fw_pos_z_i",
463-
value: 5
464-
},
465-
{
466-
key: "nav_fw_pos_z_d",
467-
value: 10
468456
},
469457
{
470458
key: "nav_fw_pos_xy_p",
@@ -530,6 +518,26 @@ var defaultsDialogData = [
530518
key: "fw_ff_yaw",
531519
value: 255
532520
},
521+
{
522+
key: "nav_fw_pos_z_p",
523+
value: 22
524+
},
525+
{
526+
key: "nav_fw_pos_z_i",
527+
value: 6
528+
},
529+
{
530+
key: "nav_fw_pos_z_d",
531+
value: 2
532+
},
533+
{
534+
key: "nav_fw_pos_z_FF",
535+
value: 25
536+
},
537+
{
538+
key: "set nav_fw_alt_control_response",
539+
value: 45
540+
},
533541
{
534542
key: "airmode_type",
535543
value: "STICK_CENTER_ONCE"
@@ -574,6 +582,13 @@ var defaultsDialogData = [
574582
key: "nav_fw_launch_climb_angle",
575583
value: 25
576584
},
585+
/*
586+
* TPA
587+
*/
588+
{
589+
key: "tpa_rate",
590+
value: 80
591+
},
577592
],
578593
},
579594
{
@@ -660,18 +675,6 @@ var defaultsDialogData = [
660675
key: "yaw_rate",
661676
value: 3
662677
},
663-
{
664-
key: "nav_fw_pos_z_p",
665-
value: 25
666-
},
667-
{
668-
key: "nav_fw_pos_z_i",
669-
value: 5
670-
},
671-
{
672-
key: "nav_fw_pos_z_d",
673-
value: 8
674-
},
675678
{
676679
key: "nav_fw_pos_xy_p",
677680
value: 75
@@ -736,6 +739,26 @@ var defaultsDialogData = [
736739
key: "fw_ff_yaw",
737740
value: 100
738741
},
742+
{
743+
key: "nav_fw_pos_z_p",
744+
value: 25
745+
},
746+
{
747+
key: "nav_fw_pos_z_i",
748+
value: 6
749+
},
750+
{
751+
key: "nav_fw_pos_z_d",
752+
value: 5
753+
},
754+
{
755+
key: "nav_fw_pos_z_FF",
756+
value: 25
757+
},
758+
{
759+
key: "set nav_fw_alt_control_response",
760+
value: 45
761+
},
739762
{
740763
key: "airmode_type",
741764
value: "STICK_CENTER_ONCE"
@@ -780,6 +803,13 @@ var defaultsDialogData = [
780803
key: "nav_fw_launch_climb_angle",
781804
value: 25
782805
},
806+
/*
807+
* TPA
808+
*/
809+
{
810+
key: "tpa_rate",
811+
value: 80
812+
},
783813
],
784814
},
785815
{

js/localization.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import i18next from 'i18next';
66
import store from './store'
77

88

9-
const availableLanguages = ['en', 'ja', 'uk', 'zh_CN'];
9+
const availableLanguages = ['en', 'ja', 'ru', 'uk', 'zh_CN'];
1010

1111
const i18n = {};
1212

js/transpiler/transpiler/analyzer.js

Lines changed: 116 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,14 @@ class SemanticAnalyzer {
197197
// Variable assignment - allowed for var variables
198198
// (let reassignment already caught above)
199199
} else if (!this.isValidWritableProperty(stmt.target)) {
200-
// Use original target (with inav. prefix) for writability check
201-
this.addError(`Cannot assign to '${stmt.target}'. Not a valid INAV writable property.`, line);
200+
// Check if it's an intermediate object and provide helpful error
201+
const betterError = this.getImprovedWritabilityError(stmt.target, line);
202+
if (betterError) {
203+
this.addError(betterError, line);
204+
} else {
205+
// Use original target (with inav. prefix) for writability check
206+
this.addError(`Cannot assign to '${stmt.target}'. Not a valid INAV writable property.`, line);
207+
}
202208
}
203209

204210
// Check if value references are valid
@@ -368,7 +374,111 @@ class SemanticAnalyzer {
368374
extractGvarIndex(gvarStr) {
369375
return this.propertyAccessChecker.extractGvarIndex(gvarStr);
370376
}
371-
377+
378+
/**
379+
* Generate improved error message for invalid writable property assignments
380+
* Detects intermediate objects and suggests correct nested properties
381+
* @param {string} target - Property path (e.g., "inav.override.flightAxis.yaw")
382+
* @param {number} line - Line number for error reporting
383+
* @returns {string|null} Improved error message or null if no improvement available
384+
*/
385+
getImprovedWritabilityError(target, line) {
386+
// Strip 'inav.' prefix if present
387+
const normalizedTarget = target.startsWith('inav.') ? target.substring(5) : target;
388+
const parts = normalizedTarget.split('.');
389+
390+
// Only applies to override namespace for now
391+
if (parts[0] !== 'override') {
392+
return null;
393+
}
394+
395+
// Check if trying to assign to a 3-level intermediate object
396+
// E.g., override.flightAxis.yaw (should be override.flightAxis.yaw.angle or .rate)
397+
if (parts.length === 3) {
398+
const overrideDef = this.getOverrideDefinition(parts[1], parts[2]);
399+
400+
if (overrideDef && overrideDef.type === 'object' && overrideDef.properties) {
401+
const availableProps = Object.keys(overrideDef.properties);
402+
const suggestions = availableProps.map(p => `inav.override.${parts[1]}.${parts[2]}.${p}`).join(', ');
403+
return `Cannot assign to '${target}' - it's an object, not a property. Available properties: ${suggestions}`;
404+
}
405+
}
406+
407+
// Check if trying to assign to a 2-level intermediate object
408+
// E.g., override.vtx (should be override.vtx.power, etc.)
409+
// or override.flightAxis (should be override.flightAxis.roll.angle, etc.)
410+
if (parts.length === 2) {
411+
const categoryDef = this.getOverrideCategoryDefinition(parts[1]);
412+
413+
if (categoryDef && categoryDef.type === 'object' && categoryDef.properties) {
414+
const propKeys = Object.keys(categoryDef.properties);
415+
416+
// Check if properties are simple (like vtx.power) or nested (like flightAxis.roll.angle)
417+
const firstProp = categoryDef.properties[propKeys[0]];
418+
419+
if (firstProp && firstProp.type === 'object' && firstProp.properties) {
420+
// Deeply nested (like flightAxis.roll.angle)
421+
const nestedPropKeys = Object.keys(firstProp.properties);
422+
const suggestions = propKeys.slice(0, 2).flatMap(p => {
423+
const nested = categoryDef.properties[p];
424+
if (nested && nested.properties) {
425+
const nestedKeys = Object.keys(nested.properties);
426+
if (nestedKeys.length > 0) {
427+
const firstNestedProp = nestedKeys[0];
428+
return [`inav.override.${parts[1]}.${p}.${firstNestedProp}`];
429+
}
430+
}
431+
return [];
432+
}).join(', ');
433+
return `Cannot assign to '${target}' - it's an object, not a property. Examples: ${suggestions}, ...`;
434+
} else {
435+
// Simple properties (like vtx.power, vtx.band, vtx.channel)
436+
const suggestions = propKeys.map(p => `inav.override.${parts[1]}.${p}`).join(', ');
437+
return `Cannot assign to '${target}' - it's an object, not a property. Available properties: ${suggestions}`;
438+
}
439+
}
440+
}
441+
442+
return null;
443+
}
444+
445+
/**
446+
* Get override definition for a specific property
447+
* @private
448+
*/
449+
getOverrideDefinition(category, property) {
450+
try {
451+
// Access raw API definitions, not processed structure
452+
const overrideDefs = apiDefinitions.override;
453+
if (!overrideDefs) return null;
454+
455+
// For nested objects like flightAxis, check if the property itself has properties
456+
if (overrideDefs[category] && overrideDefs[category].properties) {
457+
return overrideDefs[category].properties[property];
458+
}
459+
460+
return null;
461+
} catch (error) {
462+
return null;
463+
}
464+
}
465+
466+
/**
467+
* Get override category definition
468+
* @private
469+
*/
470+
getOverrideCategoryDefinition(category) {
471+
try {
472+
// Access raw API definitions, not processed structure
473+
const overrideDefs = apiDefinitions.override;
474+
if (!overrideDefs) return null;
475+
476+
return overrideDefs[category];
477+
} catch (error) {
478+
return null;
479+
}
480+
}
481+
372482
/**
373483
* Check for common unsupported JavaScript features
374484
*/
@@ -525,12 +635,9 @@ class SemanticAnalyzer {
525635
let stmtIndex = 0;
526636
for (const stmt of ast.statements) {
527637
if (stmt && stmt.type === 'EventHandler') {
528-
// Each if statement gets a unique key - we want to detect multiple
529-
// assignments within the SAME if block, not across different if
530-
// statements that happen to have the same condition
531-
const handlerKey = stmt.handler === 'ifthen' ?
532-
`ifthen:${stmtIndex}` :
533-
stmt.handler;
638+
// Each handler gets a unique key - we want to detect multiple
639+
// assignments within the SAME handler, not across different handlers
640+
const handlerKey = `${stmt.handler}:${stmtIndex}`;
534641

535642
if (!handlerAssignments.has(handlerKey)) {
536643
handlerAssignments.set(handlerKey, new Map());

0 commit comments

Comments
 (0)