Skip to content

Conversation

@DTTerastar
Copy link
Collaborator

@DTTerastar DTTerastar commented Dec 17, 2025

Summary by CodeRabbit

  • New Features

    • Introduced new RxTxAdjRssiOptimizer for enhanced signal parameter optimization.
    • Optimizers now use configurable TX RSSI reference values instead of fixed constants.
  • Bug Fixes

    • Improved error handling and logging throughout optimization processes.
  • Chores

    • Updated default TX RSSI configuration parameters (TxRefRssiMin: -80, TxRefRssiMax: -40).

✏️ Tip: You can customize this high-level summary in your review settings.

@DTTerastar DTTerastar had a problem deploying to CI - release environment December 17, 2025 04:43 — with GitHub Actions Failure
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 17, 2025

📝 Walkthrough

Walkthrough

This PR introduces a new RSSI reference optimizer (RxTxAdjRssiOptimizer), refactors CombinedOptimizer with enhanced logging and error handling, and replaces hardcoded RSSI constants with a configurable txRefRssi parameter across multiple optimizers. Configuration defaults for TxRefRssiMin and TxRefRssiMax bounds are also adjusted.

Changes

Cohort / File(s) Summary
Configuration Updates
src/Models/Config.cs
Updated default values: TxRefRssiMin from -70 to -80, TxRefRssiMax from -50 to -40
New Optimizer Implementation
src/Optimizers/RxTxAdjRssiOptimizer.cs
New optimizer class implementing IOptimizer using bounded BFGS solver for Rx/Tx node adjustment; includes asymmetric distance error function, parameter clamping, regularization, and finite-difference gradient computation
Optimizer Orchestration Refactor
src/Optimizers/CombinedOptimizer.cs
Added logging, early-exit for insufficient nodes, replaced direct RSSI/Absorption retrieval with deviceParams dictionary; introduced helper methods (CreateDeviceParamsFromVector, refactored CalculateError), expanded error handling with exception logging, and improved iteration/completion tracking
TxRefRssi Parameterization
src/Optimizers/TwoStageRxAdjAbsorptionOptimizer.cs, src/Optimizers/WeightedJointRxAdjAbsorptionOptimizer.cs
Replaced hardcoded -59 RSSI constant with runtime txRefRssi parameter in distance calculations and RSSI predictions
Minor Formatting
src/Optimizers/GlobalAbsorptionRxTxOptimizer.cs
Added newline at end of file (no functional changes)

Sequence Diagram

sequenceDiagram
    participant Caller
    participant CombinedOptimizer
    participant RxTxAdjRssiOptimizer
    participant BfgsBMinimizer
    
    Caller->>CombinedOptimizer: Optimize(OptimizationSnapshot)
    
    alt Sufficient nodes available
        CombinedOptimizer->>RxTxAdjRssiOptimizer: Optimize(snapshot)
        activate RxTxAdjRssiOptimizer
        Note over RxTxAdjRssiOptimizer: Collect measurements,<br/>map Rx/Tx parameters
        RxTxAdjRssiOptimizer->>BfgsBMinimizer: Solve with bounds & gradient
        activate BfgsBMinimizer
        Note over BfgsBMinimizer: Minimize weighted distance<br/>+ regularization error
        BfgsBMinimizer-->>RxTxAdjRssiOptimizer: Optimization vector
        deactivate BfgsBMinimizer
        Note over RxTxAdjRssiOptimizer: Clamp to bounds,<br/>store results
        RxTxAdjRssiOptimizer-->>CombinedOptimizer: OptimizationResults
        deactivate RxTxAdjRssiOptimizer
        
        Note over CombinedOptimizer: Build deviceParams from<br/>optimization vector
        CombinedOptimizer->>CombinedOptimizer: CreateDeviceParamsFromVector()
        CombinedOptimizer->>CombinedOptimizer: CalculateError(deviceParams)
        Note over CombinedOptimizer: Log completion
        CombinedOptimizer-->>Caller: OptimizationResults
    else Insufficient nodes
        Note over CombinedOptimizer: Log early exit
        CombinedOptimizer-->>Caller: Default results
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • RxTxAdjRssiOptimizer.cs: New optimizer with complex BFGS-based optimization logic, asymmetric distance error function, parameter clamping, and finite-difference gradient computation; correctness of weighting calculations and bounds enforcement is critical
  • CombinedOptimizer.cs: Significant refactoring of optimization flow with new helper methods and error handling; verify the deviceParams dictionary lifecycle and integration with individual optimizer results
  • TxRefRssi substitution across optimizers: Ensure the replacement of hardcoded -59 with txRefRssi parameter is consistent in calculations across TwoStageRxAdjAbsorptionOptimizer and WeightedJointRxAdjAbsorptionOptimizer, and that new config bounds are properly applied

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'WIP' is vague and generic, providing no meaningful information about the substantial changes in the pull request, such as optimizer refactoring or RSSI configuration updates. Replace the title with a descriptive summary of the main change, such as 'Refactor optimizers with new RxTxAdjRssiOptimizer and update RSSI defaults' to clearly indicate the primary objective.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fancy-optimize

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Optimizers/CombinedOptimizer.cs (1)

137-268: Critical: OptimizeNodeAbsorptions method has severe syntax errors preventing compilation.

This method has multiple overlapping code fragments that break the build. The pipeline failures confirm errors at lines 174, 180, 203, 204, 213, and others.

Key issues:

  1. Lines 140-173: Incomplete objective function lambda with mixed code
  2. Lines 175-212: Malformed gradient function with duplicate variable declarations (gradient declared at 183 and 188)
  3. Lines 216 and 229: Duplicate solver declarations
  4. Missing/mismatched braces throughout
  5. Method signature says it returns Dictionary<string, double> but the code attempts to return a tuple at line 268

This method needs to be completely rewritten. Here's a structural outline:

private (Dictionary<string, DeviceParameters> DeviceParams, double Error) OptimizeNodeAbsorptions(
    List<Measure> allNodes, 
    List<string> uniqueDeviceIds,
    Dictionary<string, double> rxAdjRssiDict, 
    Dictionary<(string, string), double> pathAbsorptionDict, 
    ConfigOptimization optimization, 
    Dictionary<string, NodeSettings> existingSettings)
{
    // Build initial guess vector
    var initialGuess = Vector<double>.Build.Dense(uniqueDeviceIds.Count * 2);
    for (int i = 0; i < uniqueDeviceIds.Count; i++)
    {
        existingSettings.TryGetValue(uniqueDeviceIds[i], out var nodeSettings);
        initialGuess[i] = Math.Clamp(nodeSettings?.Calibration?.RxAdjRssi ?? 0, 
            optimization.RxAdjRssiMin, optimization.RxAdjRssiMax);
        initialGuess[i + uniqueDeviceIds.Count] = Math.Clamp(
            nodeSettings?.Calibration?.Absorption ?? 3.0,
            optimization.AbsorptionMin, optimization.AbsorptionMax);
    }

    // Define objective with gradient
    var objGradient = ObjectiveFunction.Gradient(
        x => {
            var deviceParams = CreateDeviceParamsFromVector(x, uniqueDeviceIds, optimization);
            return CalculateError(allNodes, deviceParams);
        },
        x => {
            var gradient = Vector<double>.Build.Dense(x.Count);
            double h = 1e-5;
            var deviceParams = CreateDeviceParamsFromVector(x, uniqueDeviceIds, optimization);
            double baseError = CalculateError(allNodes, deviceParams);

            for (int i = 0; i < x.Count; i++)
            {
                var xPlus = x.Clone();
                xPlus[i] += h;
                var paramsPlus = CreateDeviceParamsFromVector(xPlus, uniqueDeviceIds, optimization);
                gradient[i] = (CalculateError(allNodes, paramsPlus) - baseError) / h;
            }
            return gradient;
        }
    );

    try
    {
        var solver = new ConjugateGradientMinimizer(1e-3, 1000);
        var result = solver.FindMinimum(objGradient, initialGuess);
        
        var deviceParams = CreateDeviceParamsFromVector(result.MinimizingPoint, uniqueDeviceIds, optimization);
        return (deviceParams, result.FunctionInfoAtMinimum.Value);
    }
    catch (Exception ex)
    {
        _logger.Error(ex, "Optimization failed");
        // Return defaults...
    }
}
🧹 Nitpick comments (2)
src/Optimizers/RxTxAdjRssiOptimizer.cs (2)

224-227: Hardcoded initial guess may fall outside configured bounds.

The initial txRefRssi is hardcoded to -59, but the bounds are configurable via TxRefRssiMin/TxRefRssiMax. If the bounds don't include -59, this could cause issues with the optimizer.

Apply a consistent approach using bounds midpoint like you did for absorption:

         foreach (var txId in uniqueTxIds)
         {
-            initialGuess[txIndexMap[txId]] = -59;
+            initialGuess[txIndexMap[txId]] = (optimization.TxRefRssiMin + optimization.TxRefRssiMax) / 2.0;
         }

263-266: Use named placeholder for structured logging.

Serilog prefers named placeholders for better structured logging support.

         catch (Exception ex)
         {
-            Log.Error("Error optimizing all nodes: {0}", ex.Message);
+            Log.Error(ex, "Error optimizing all nodes");
         }

Passing the exception as the first argument allows Serilog to capture the full stack trace.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1185b3 and 129857d.

📒 Files selected for processing (6)
  • src/Models/Config.cs (1 hunks)
  • src/Optimizers/CombinedOptimizer.cs (7 hunks)
  • src/Optimizers/GlobalAbsorptionRxTxOptimizer.cs (1 hunks)
  • src/Optimizers/RxTxAdjRssiOptimizer.cs (1 hunks)
  • src/Optimizers/TwoStageRxAdjAbsorptionOptimizer.cs (2 hunks)
  • src/Optimizers/WeightedJointRxAdjAbsorptionOptimizer.cs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Place backend C# ASP.NET Core code under src/ (controllers, services, models, utils)

Files:

  • src/Optimizers/GlobalAbsorptionRxTxOptimizer.cs
  • src/Optimizers/WeightedJointRxAdjAbsorptionOptimizer.cs
  • src/Optimizers/RxTxAdjRssiOptimizer.cs
  • src/Models/Config.cs
  • src/Optimizers/CombinedOptimizer.cs
  • src/Optimizers/TwoStageRxAdjAbsorptionOptimizer.cs
{src,tests}/**/*.cs

📄 CodeRabbit inference engine (AGENTS.md)

{src,tests}/**/*.cs: C#: Use spaces with an indent size of 4
C#: Use PascalCase for types and methods
C#: Use camelCase for local variables and parameters

Files:

  • src/Optimizers/GlobalAbsorptionRxTxOptimizer.cs
  • src/Optimizers/WeightedJointRxAdjAbsorptionOptimizer.cs
  • src/Optimizers/RxTxAdjRssiOptimizer.cs
  • src/Models/Config.cs
  • src/Optimizers/CombinedOptimizer.cs
  • src/Optimizers/TwoStageRxAdjAbsorptionOptimizer.cs
🧠 Learnings (1)
📚 Learning: 2025-09-16T00:51:01.127Z
Learnt from: CR
Repo: ESPresense/ESPresense-companion PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T00:51:01.127Z
Learning: Applies to src/**/Controllers/StateController.cs : StateController must populate Device.ConfiguredRefRssi from DeviceSettings during API calls

Applied to files:

  • src/Models/Config.cs
🧬 Code graph analysis (2)
src/Optimizers/RxTxAdjRssiOptimizer.cs (2)
src/Models/Config.cs (1)
  • Config (8-58)
src/Models/OptimizationSnapshot.cs (1)
  • Measure (40-57)
src/Optimizers/CombinedOptimizer.cs (1)
src/Models/OptimizationSnapshot.cs (1)
  • Measure (40-57)
🪛 GitHub Actions: Build and test
src/Optimizers/CombinedOptimizer.cs

[error] 174-174: CS1513: } expected

🪛 GitHub Actions: Deploy to Docker
src/Optimizers/CombinedOptimizer.cs

[error] 174-174: /App/src/Optimizers/CombinedOptimizer.cs(174,14): error CS1513: } expected


[error] 180-180: /App/src/Optimizers/CombinedOptimizer.cs(180,17): error CS1524: Expected catch or finally


[error] 203-203: /App/src/Optimizers/CombinedOptimizer.cs(203,18): error CS1002: ; expected


[error] 204-204: /App/src/Optimizers/CombinedOptimizer.cs(204,34): error CS1026: ) expected


[error] 204-204: /App/src/Optimizers/CombinedOptimizer.cs(204,36): error CS1002: ; expected


[error] 213-213: /App/src/Optimizers/CombinedOptimizer.cs(213,9): error CS1519: Invalid token ')' in class, record, struct, or interface member declaration


[error] 220-220: /App/src/Optimizers/CombinedOptimizer.cs(220,9): error CS1519: Invalid token 'for' in class, record, struct, or interface member declaration


[error] 220-220: /App/src/Optimizers/CombinedOptimizer.cs(220,20): error CS8124: Tuple must contain at least two elements


[error] 220-220: /App/src/Optimizers/CombinedOptimizer.cs(220,34): error CS1026: ) expected


[error] 226-226: /App/src/Optimizers/CombinedOptimizer.cs(226,29): error CS1519: Invalid token '=' in class, record, struct, or interface member declaration


[error] 226-226: /App/src/Optimizers/CombinedOptimizer.cs(226,41): error CS1519: Invalid token '(' in class, record, struct, or interface member declaration


[error] 226-226: /App/src/Optimizers/CombinedOptimizer.cs(226,55): error CS8124: Tuple must contain at least two elements


[error] 226-226: /App/src/Optimizers/CombinedOptimizer.cs(226,98): error CS1026: ) expected


[error] 239-239: /App/src/Optimizers/CombinedOptimizer.cs(239,26): error CS1002: ; expected


[error] 239-239: /App/src/Optimizers/CombinedOptimizer.cs(239,28): error CS1001: Identifier expected


[error] 241-241: /App/src/Optimizers/CombinedOptimizer.cs(241,31): error CS1001: Identifier expected


[error] 255-255: /App/src/Optimizers/CombinedOptimizer.cs(255,9): error CS1022: Type or namespace definition, or end-of-file expected


[error] 269-269: /App/src/Optimizers/CombinedOptimizer.cs(269,5): error CS1022: Type or namespace definition, or end-of-file expected


[error] 271-271: /App/src/Optimizers/CombinedOptimizer.cs(271,5): error CS0106: The modifier 'private' is not valid for this item


[error] 299-299: /App/src/Optimizers/CombinedOptimizer.cs(299,5): error CS0106: The modifier 'private' is not valid for this item


[error] 348-348: /App/src/Optimizers/CombinedOptimizer.cs(348,1): error CS1022: Type or namespace definition, or end-of-file expected

🪛 GitHub Check: build
src/Optimizers/CombinedOptimizer.cs

[failure] 213-213:
Invalid token ')' in a member declaration


[failure] 204-204:
} expected


[failure] 204-204:
; expected


[failure] 204-204:
; expected


[failure] 204-204:
) expected


[failure] 203-203:
} expected


[failure] 203-203:
; expected

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (4)
src/Optimizers/GlobalAbsorptionRxTxOptimizer.cs (1)

341-341: LGTM!

This is a cosmetic change adding a newline at the end of the file, which is a good practice for version control.

src/Models/Config.cs (1)

137-138: Verify the widened TxRefRssi bounds are intentional.

The default range has been expanded from [-70, -50] (20 dBm) to [-80, -40] (40 dBm). This doubles the optimization search space for TxRefRssi.

Ensure this change is intentional and won't cause optimization instability or convergence issues with the wider bounds.

src/Optimizers/RxTxAdjRssiOptimizer.cs (2)

12-21: LGTM!

Clean class structure with proper constructor injection and property implementation.


65-89: LGTM!

Good use of inverse variance weighting with appropriate safeguards against extreme weights and proper normalization.

Comment on lines +48 to 56
if (deviceParams.TryGetValue(deviceId, out var parameters))
{
results.Nodes[deviceId] = new ProposedValues
{
RxAdjRssi = rxAdjRssi,
Absorption = absorption,
RxAdjRssi = parameters.RxAdjRssi,
Absorption = parameters.Absorption,
Error = error
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: deviceParams is undefined in this scope.

The variable deviceParams is referenced but never declared within the Optimize method. This will cause a compilation error. Based on the method structure, you likely need to use the result from OptimizeNodeAbsorptions or refactor the flow.

The optimization results should come from the previous steps. Consider:

-            // Step 2: Optimize node-specific absorptions while keeping RxAdjRssi constant
-            var nodeAbsorptions = OptimizeNodeAbsorptions(allNodes, uniqueDeviceIds, rxAdjRssiDict, pathAbsorptionDict, optimization, existingSettings);
+            // Step 2: Optimize node-specific absorptions while keeping RxAdjRssi constant
+            var (deviceParams, finalError) = OptimizeNodeAbsorptions(allNodes, uniqueDeviceIds, rxAdjRssiDict, pathAbsorptionDict, optimization, existingSettings);
+            error = finalError;

             // Process and store results
             foreach (var deviceId in uniqueDeviceIds)
             {
                 if (deviceParams.TryGetValue(deviceId, out var parameters))

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/Optimizers/CombinedOptimizer.cs around lines 48 to 56, deviceParams is
referenced but not declared; replace it with the actual variable produced
earlier in the method (likely the output of OptimizeNodeAbsorptions or the node
parameter dictionary returned by previous steps), or store the returned
node-parameter map into a local variable before this loop and use that variable
in TryGetValue; ensure the variable is correctly typed and in scope, and if the
OptimizeNodeAbsorptions call can fail propagate or handle its result before
accessing its dictionary.

Comment on lines +271 to +297
private Dictionary<string, DeviceParameters> CreateDeviceParamsFromVector(Vector<double> x, List<string> uniqueDeviceIds, ConfigOptimization optimization)
{
return nodes.Select(n =>
var deviceParams = new Dictionary<string, DeviceParameters>();

for (int i = 0; i < uniqueDeviceIds.Count; i++)
{
var distance = n.Rx.Location.DistanceTo(n.Tx.Location);
var rxAdjRssi = rxAdjRssiDict[n.Rx.Id];
var txAdjRssi = rxAdjRssiDict[n.Tx.Id];
double absorption;
var rxAdjRssi = x[i];
var absorption = x[i + uniqueDeviceIds.Count];

if (pathAbsorptionDict != null)
// Enforce constraints by clamping values to valid ranges
rxAdjRssi = Math.Clamp(rxAdjRssi,
optimization?.RxAdjRssiMin ?? -20,
optimization?.RxAdjRssiMax ?? 20);

absorption = Math.Clamp(absorption,
optimization?.AbsorptionMin ?? 1.5,
optimization?.AbsorptionMax ?? 4.5);

deviceParams[uniqueDeviceIds[i]] = new DeviceParameters
{
var pathKey = (Min(n.Rx.Id, n.Tx.Id), Max(n.Rx.Id, n.Tx.Id));
absorption = pathAbsorptionDict[pathKey];
}
else if (nodeAbsorptionDict != null)
RxAdjRssi = rxAdjRssi,
Absorption = absorption
};
}

return deviceParams;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing DeviceParameters class definition.

The DeviceParameters class is used throughout but is not defined in this file. Either define it as a nested class or ensure it exists in the Models namespace.

Add the class definition (as a nested class or in a separate file):

private class DeviceParameters
{
    public double RxAdjRssi { get; set; }
    public double Absorption { get; set; }
    public double? TxRefRssi { get; set; }  // Add for txRefRssi support
}
🧰 Tools
🪛 GitHub Actions: Deploy to Docker

[error] 271-271: /App/src/Optimizers/CombinedOptimizer.cs(271,5): error CS0106: The modifier 'private' is not valid for this item

🤖 Prompt for AI Agents
In src/Optimizers/CombinedOptimizer.cs around lines 271 to 297, the
DeviceParameters type used to build deviceParams is missing; add a
DeviceParameters definition (either as a private nested class in this file or as
a public/internal class in the Models namespace) with at least the properties
double RxAdjRssi { get; set; }, double Absorption { get; set; } and a nullable
double? TxRefRssi { get; set; } so callers compile and txRefRssi support is
available.

Comment on lines +327 to +328
// Calculate distance based on RSSI
var calculatedDistance = Math.Pow(10, (-59 + rxAdjRssi + txAdjRssi - node.Rssi) / (10.0d * absorption));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistency: Hardcoded -59 contradicts the PR objective.

The PR aims to replace hardcoded RSSI constants with configurable txRefRssi, but this calculation still uses the hardcoded value -59. For consistency with other optimizers, consider using the transmitter's TxRefRssi parameter.

-                // Calculate distance based on RSSI
-                var calculatedDistance = Math.Pow(10, (-59 + rxAdjRssi + txAdjRssi - node.Rssi) / (10.0d * absorption));
+                // Calculate distance based on RSSI using txRefRssi from tx parameters
+                var txRefRssi = txParams.TxRefRssi ?? -59;
+                var calculatedDistance = Math.Pow(10, (txRefRssi + rxAdjRssi + txAdjRssi - node.Rssi) / (10.0d * absorption));

Note: This requires DeviceParameters to include a TxRefRssi property, which appears to be missing from the current definition.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/Optimizers/CombinedOptimizer.cs around lines 327-328, the distance
calculation still uses a hardcoded -59 RSSI constant which contradicts the PR
goal to use configurable transmitter reference RSSI. Replace the -59 literal
with the transmitter reference value (e.g., node.DeviceParameters.TxRefRssi or
the appropriate TxRefRssi field on the transmitter/DeviceParameters object),
add/ensure a TxRefRssi property exists on DeviceParameters, and handle
missing/null values by falling back to a sensible default or by validating and
throwing a clear exception; update any callers/tests that construct
DeviceParameters to provide TxRefRssi.

Comment on lines +25 to +35
var or = new OptimizationResults();
var optimization = _state.Config?.Optimization;

// Group all valid measurements
var allRxNodes = os.ByRx().SelectMany(g => g).ToList();

if (allRxNodes.Count < 3)
{
Log.Warning("Not enough valid measurements for optimization");
return or;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential NullReferenceException if Config is null.

The optimization variable is accessed throughout the method (lines 62, 203-206, 212-213, 243-244, 256) without null checks. If _state.Config is null, this will throw.

Apply this diff to add null safety:

         var or = new OptimizationResults();
         var optimization = _state.Config?.Optimization;
+        if (optimization == null)
+        {
+            Log.Warning("Configuration not available for optimization");
+            return or;
+        }

         // Group all valid measurements
-        var allRxNodes = os.ByRx().SelectMany(g => g).ToList();
+        var allMeasurements = os.ByRx().SelectMany(g => g).ToList();

-        if (allRxNodes.Count < 3)
+        if (allMeasurements.Count < 3)
         {
             Log.Warning("Not enough valid measurements for optimization");
             return or;
         }

Also consider renaming allRxNodes to allMeasurements throughout the file for clarity, as it contains all measurements, not just Rx nodes.

🤖 Prompt for AI Agents
In src/Optimizers/RxTxAdjRssiOptimizer.cs around lines 25-35, the code grabs
_state.Config?.Optimization into a local but doesn't handle a null
Config/Optimization anywhere else (used later at lines 62, 203-206, 212-213,
243-244, 256) which can cause NullReferenceExceptions; add an explicit
null-safety check right after retrieving optimization (e.g., if
(_state?.Config?.Optimization == null) { Log.Warning("Optimization config
missing"); return or set default Optimization values }) and update all
subsequent uses to rely on this guarded variable; also rename the local
allRxNodes to allMeasurements and replace that identifier throughout the file to
better reflect it contains all measurements, not just Rx nodes.

Comment on lines +120 to +125
double rxAdjRssi = x[rxBaseIndex];
double absorption = x[rxBaseIndex + 1];
double txRefRssi = x[txBaseIndex];

double calculatedDistance = Math.Pow(10, (txRefRssi + rxAdjRssi - node.Rssi) / (10.0 * absorption));
double mapDistance = node.Rx.Location.DistanceTo(node.Tx.Location);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential division by zero if absorption approaches zero.

The distance calculation (txRefRssi + rxAdjRssi - node.Rssi) / (10.0 * absorption) will fail if absorption is zero. While bounds should prevent this, consider adding a defensive check.

                     double absorption = x[rxBaseIndex + 1];
                     double txRefRssi = x[txBaseIndex];

-                    double calculatedDistance = Math.Pow(10, (txRefRssi + rxAdjRssi - node.Rssi) / (10.0 * absorption));
+                    double safeAbsorption = Math.Max(absorption, 0.1);
+                    double calculatedDistance = Math.Pow(10, (txRefRssi + rxAdjRssi - node.Rssi) / (10.0 * safeAbsorption));

The same pattern applies in the gradient calculation (lines 166, 177).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
double rxAdjRssi = x[rxBaseIndex];
double absorption = x[rxBaseIndex + 1];
double txRefRssi = x[txBaseIndex];
double calculatedDistance = Math.Pow(10, (txRefRssi + rxAdjRssi - node.Rssi) / (10.0 * absorption));
double mapDistance = node.Rx.Location.DistanceTo(node.Tx.Location);
double rxAdjRssi = x[rxBaseIndex];
double absorption = x[rxBaseIndex + 1];
double txRefRssi = x[txBaseIndex];
double safeAbsorption = Math.Max(absorption, 0.1);
double calculatedDistance = Math.Pow(10, (txRefRssi + rxAdjRssi - node.Rssi) / (10.0 * safeAbsorption));
double mapDistance = node.Rx.Location.DistanceTo(node.Tx.Location);

for (int i = 0; i < nodesToUse.Length; i++)
{
double dist = Math.Pow(10, (-59 + x[0] - nodesToUse[i].Node.Rssi) / (10.0 * fixedAbsorption));
double dist = Math.Pow(10, (txRefRssi + x[0] - nodesToUse[i].Node.Rssi) / (10.0 * fixedAbsorption));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: txRefRssi is undefined and will cause a compilation error.

The variable txRefRssi is used in the Stage 1 distance calculation but is never declared in this file. This applies to all three usages (lines 73, 100, and 102).

Define txRefRssi before the Stage 1 optimization block, retrieving it from the node settings or using a default:

             Log.Information("Bounds: RxAdj [{0}, {1}], Absorption [{2}, {3}]", rxAdjMin, rxAdjMax, absorptionMin, absorptionMax);

             try
             {
+                // Get txRefRssi from node settings or use default
+                double txRefRssi = nodeSettings?.Calibration?.TxRefRssi ?? -59;
+
                 // Stage 1: Optimize RxAdjRssi with fixed absorption, close nodes only
                 double fixedAbsorption = absorptionMiddle;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/Optimizers/TwoStageRxAdjAbsorptionOptimizer.cs around lines 73, 100 and
102, txRefRssi is referenced but not declared which causes a compilation error;
declare and initialize txRefRssi before the Stage 1 optimization block by
reading it from the relevant node/settings (e.g., nodesToUse[i].Node or a
config/settings object) and fall back to a sensible default value if not
present, then use that variable in the existing distance calculations so the
references at lines 73, 100 and 102 compile and behave predictably.

Comment on lines 58 to 62
double Distance(Vector<double> x, Measure dn)
{
double exponent = (-59 + x[0] - dn.Rssi) / (10.0d * x[1]);
double exponent = (txRefRssi + x[0] - dn.Rssi) / (10.0d * x[1]);
return (x[1] > 0 && !double.IsInfinity(exponent)) ? Math.Pow(10, exponent) : double.MaxValue;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: txRefRssi is undefined and will cause a compilation error.

The variable txRefRssi is used in the exponent calculation but is never declared or initialized in this scope. This will fail to compile.

Based on the PR context (replacing hardcoded -59 with configurable txRefRssi), you likely need to retrieve this from the node settings or optimization config:

+            double txRefRssi = nodeSettings?.Calibration?.TxRefRssi ?? -59;
+
             double Distance(Vector<double> x, Measure dn)
             {
                 double exponent = (txRefRssi + x[0] - dn.Rssi) / (10.0d * x[1]);
                 return (x[1] > 0 && !double.IsInfinity(exponent)) ? Math.Pow(10, exponent) : double.MaxValue;
             }

Alternatively, if txRefRssi should come from the transmitter device, you may need to retrieve it per-measurement from the Measure object (e.g., dn.RefRssi or similar).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/Optimizers/WeightedJointRxAdjAbsorptionOptimizer.cs around lines 58 to
62, the local function uses txRefRssi which is undefined and causes a compile
error; fix it by obtaining txRefRssi from the correct source and wiring it into
this scope — either read it from the optimizer/config (add a field or method
parameter and pass the configured txRefRssi into the function) or pull it from
the Measure if that object contains the reference RSSI (e.g., use dn.RefRssi
per-measurement); update the Distance signature/closure accordingly so txRefRssi
is defined and used instead of the missing identifier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants