Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
DTTerastar committed Oct 26, 2024
1 parent 1009dad commit a5e43f4
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 33 deletions.
6 changes: 3 additions & 3 deletions src/Controllers/StateController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ public Calibration GetCalibration()
{
var rxNs = _nsd.Get(rxId);
var rxM = txM.GetOrAdd(rx.Rx?.Name ?? rxId);
if (txNs.TxRefRssi is not null) rxM["tx_ref_rssi"] = txNs.TxRefRssi.Value;
if (rxNs.RxAdjRssi is not null) rxM["rx_adj_rssi"] = rxNs.RxAdjRssi.Value;
if (rxNs.Absorption is not null) rxM["absorption"] = rxNs.Absorption.Value;
if (txNs.Calibration.TxRefRssi is not null) rxM["tx_ref_rssi"] = txNs.Calibration.TxRefRssi.Value;
if (rxNs.Calibration.RxAdjRssi is not null) rxM["rx_adj_rssi"] = rxNs.Calibration.RxAdjRssi.Value;
if (rxNs.Calibration.Absorption is not null) rxM["absorption"] = rxNs.Calibration.Absorption.Value;
rxM["expected"] = rx.Expected;
rxM["actual"] = rx.Distance;
rxM["rssi"] = rx.Rssi;
Expand Down
72 changes: 53 additions & 19 deletions src/Models/NodeSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,62 @@ public class NodeSettings(string id)
[StringLength(64)]
public string? Id { get; set; } = id;

[JsonPropertyName("absorption")]
[JsonProperty("absorption")]
[Range(1, 10)]
public double? Absorption { get; set; }
public UpdatingSettings Updating { get; set; } = new UpdatingSettings();
public ScanningSettings Scanning { get; set; } = new ScanningSettings();
public CountingSettings Counting { get; set; } = new CountingSettings();
public FilteringSettings Filtering { get; set; } = new FilteringSettings();
public CalibrationSettings Calibration { get; set; } = new CalibrationSettings();

[JsonPropertyName("rx_adj_rssi")]
[JsonProperty("rx_adj_rssi")]
[Range(-127, 128)]
public int? RxAdjRssi { get; set; }
public NodeSettings Clone()
{
return new NodeSettings(id)
{
Updating = Updating.Clone(),
Scanning = Scanning.Clone(),
Counting = Counting.Clone(),
Filtering = Filtering.Clone(),
Calibration = Calibration.Clone()
};
}
}

[JsonPropertyName("tx_ref_rssi")]
[JsonProperty("tx_ref_rssi")]
[Range(-127, 128)]
public int? TxRefRssi { get; set; }
public class UpdatingSettings
{
public bool? AutoUpdate { get; set; }
public bool? PreRelease { get; set; }
public UpdatingSettings Clone() => (UpdatingSettings)MemberwiseClone();
}

[JsonPropertyName("max_distance")]
[JsonProperty("max_distance")]
[Range(0, 100)]
public class ScanningSettings
{
public int? ForgetAfterMs { get; set; }
public ScanningSettings Clone() => (ScanningSettings)MemberwiseClone();
}

public class CountingSettings
{
public string? IdPrefixes { get; set; }
public double? StartCountingDistance { get; set; }
public double? StopCountingDistance { get; set; }
public int? IncludeDevicesAge { get; set; }
public CountingSettings Clone() => (CountingSettings)MemberwiseClone();
}

public class FilteringSettings
{
public string? IncludeIds { get; set; }
public string? ExcludeIds { get; set; }
public double? MaxDistance { get; set; }
public double? EarlyReportDistance { get; set; }
public int? SkipReportAge { get; set; }
public FilteringSettings Clone() => (FilteringSettings)MemberwiseClone();
}

public NodeSettings Clone()
{
return (NodeSettings)MemberwiseClone();
}
public class CalibrationSettings
{
public int? RssiAt1m { get; set; }
public int? RxAdjRssi { get; set; }
public double? Absorption { get; set; }
public int? TxRefRssi { get; set; }
public CalibrationSettings Clone() => (CalibrationSettings)MemberwiseClone();
}
6 changes: 3 additions & 3 deletions src/Models/OptimizationResults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public double Evaluate(List<OptimizationSnapshot> oss, NodeSettingsStore nss)
var rx = nss.Get(m.Rx.Id);

RxNodes.TryGetValue(m.Rx.Id, out var pv);
double rxAdjRssi = pv?.RxAdjRssi ?? rx.RxAdjRssi ?? 0;
double txPower = tx.TxRefRssi ?? -59;
double pathLossExponent = pv?.Absorption ?? rx.Absorption ?? 3;
double rxAdjRssi = pv?.RxAdjRssi ?? rx.Calibration.RxAdjRssi ?? 0;
double txPower = tx.Calibration.TxRefRssi ?? -59;
double pathLossExponent = pv?.Absorption ?? rx.Calibration.Absorption ?? 3;
double distance = m.Rx.Location.DistanceTo(m.Tx.Location);
double predictedRssi = txPower + rxAdjRssi - 10 * pathLossExponent * Math.Log10(distance);

Expand Down
4 changes: 2 additions & 2 deletions src/Optimizers/OptimizationRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
Log.Information("Optimizer set {0,-20} to Absorption: {1:0.00} RxAdj: {2:00} Error: {3}", id, result.Absorption, result.RxAdjRssi, result.Error);
var a = _nsd.Get(id);
if (optimization == null) continue;
if (result.Absorption != null && result.Absorption > optimization.AbsorptionMin && result.Absorption < optimization.AbsorptionMax) a.Absorption = result.Absorption;
if (result.RxAdjRssi != null && result.RxAdjRssi > optimization.RxAdjRssiMin && result.RxAdjRssi < optimization.RxAdjRssiMax) a.RxAdjRssi = result.RxAdjRssi == null ? 0 : (int?)Math.Round(result.RxAdjRssi.Value);
if (result.Absorption != null && result.Absorption > optimization.AbsorptionMin && result.Absorption < optimization.AbsorptionMax) a.Calibration.Absorption = result.Absorption;
if (result.RxAdjRssi != null && result.RxAdjRssi > optimization.RxAdjRssiMin && result.RxAdjRssi < optimization.RxAdjRssiMax) a.Calibration.RxAdjRssi = result.RxAdjRssi == null ? 0 : (int?)Math.Round(result.RxAdjRssi.Value);
await _nsd.Set(id, a);
}

Expand Down
12 changes: 6 additions & 6 deletions src/Services/NodeSettingsStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public NodeSettings Get(string id)
public async Task Set(string id, NodeSettings ds)
{
var old = Get(id);
if (ds.Absorption == null || ds.Absorption != old.Absorption)
await mqtt.EnqueueAsync($"espresense/rooms/{id}/absorption/set", $"{ds.Absorption:0.00}");
if (ds.RxAdjRssi == null || ds.RxAdjRssi != old.RxAdjRssi)
await mqtt.EnqueueAsync($"espresense/rooms/{id}/rx_adj_rssi/set", $"{ds.RxAdjRssi}");
if (ds.TxRefRssi == null || ds.TxRefRssi != old.TxRefRssi)
await mqtt.EnqueueAsync($"espresense/rooms/{id}/tx_ref_rssi/set", $"{ds.TxRefRssi}");
if (ds.Absorption == null || ds.Calibration.Absorption != old.Calibration.Absorption)

Check failure on line 18 in src/Services/NodeSettingsStore.cs

View workflow job for this annotation

GitHub Actions / build

'NodeSettings' does not contain a definition for 'Absorption' and no accessible extension method 'Absorption' accepting a first argument of type 'NodeSettings' could be found (are you missing a using directive or an assembly reference?)
await mqtt.EnqueueAsync($"espresense/rooms/{id}/absorption/set", $"{ds.Calibration.Absorption:0.00}");
if (ds.RxAdjRssi == null || ds.Calibration.RxAdjRssi != old.Calibration.RxAdjRssi)

Check failure on line 20 in src/Services/NodeSettingsStore.cs

View workflow job for this annotation

GitHub Actions / build

'NodeSettings' does not contain a definition for 'RxAdjRssi' and no accessible extension method 'RxAdjRssi' accepting a first argument of type 'NodeSettings' could be found (are you missing a using directive or an assembly reference?)
await mqtt.EnqueueAsync($"espresense/rooms/{id}/rx_adj_rssi/set", $"{ds.Calibration.RxAdjRssi}");
if (ds.TxRefRssi == null || ds.Calibration.TxRefRssi != old.Calibration.TxRefRssi)

Check failure on line 22 in src/Services/NodeSettingsStore.cs

View workflow job for this annotation

GitHub Actions / build

'NodeSettings' does not contain a definition for 'TxRefRssi' and no accessible extension method 'TxRefRssi' accepting a first argument of type 'NodeSettings' could be found (are you missing a using directive or an assembly reference?)
await mqtt.EnqueueAsync($"espresense/rooms/{id}/tx_ref_rssi/set", $"{ds.Calibration.TxRefRssi}");
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
Expand Down
155 changes: 155 additions & 0 deletions src/ui/src/lib/GlobalSettings.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<script lang="ts">
import TriStateCheckbox from '$lib/TriStateCheckbox.svelte';
import { settings } from '$lib/stores';
import { onMount } from 'svelte';
let loading = true;
let error: string | null = null;
onMount(async () => {
try {
await settings.load();
} catch (e: unknown) {
error = e instanceof Error ? e.message : 'An unknown error occurred';
} finally {
loading = false;
}
});
async function handleUpdate() {
try {
if ($settings) {
await settings.save($settings);
}
} catch (e: unknown) {
error = e instanceof Error ? `Error updating settings: ${e.message}` : 'An unknown error occurred while updating settings';
}
}
</script>

{#if loading}
<div class="card m-2 p-4 variant-filled-surface">
<div class="flex items-center space-x-4">
<span class="loading loading-spinner loading-lg" />
<p>Loading settings...</p>
</div>
</div>
{:else if error}
<div class="card m-2 p-4 variant-filled-error">
<p>Error: {error}</p>
</div>
{:else if $settings}
<div class="card m-2 p-4 variant-filled-surface">
<section class="mb-8">
<h2 class="text-2xl font-semibold mb-4">Updating</h2>
<div class="space-y-4 ml-4">
<div class="flex items-center space-x-2">
<TriStateCheckbox id="auto-update" bind:checked={$settings.updating.autoUpdate} />
<label for="auto-update">Automatically update</label>
</div>
<div class="flex items-center space-x-2">
<TriStateCheckbox id="pre-release" bind:checked={$settings.updating.preRelease} />
<label for="pre-release">Include pre-released versions in auto-update</label>
</div>
</div>
</section>

<section class="mb-8">
<h2 class="text-2xl font-semibold mb-4">Scanning</h2>
<div class="space-y-4 ml-4">
<label class="label">
<span>Forget beacon if not seen for (in milliseconds):</span>
<input type="number" class="input" min="0" bind:value={$settings.scanning.forgetAfterMs} placeholder="150000" />
</label>
</div>
</section>

<section class="mb-8">
<h2 class="text-2xl font-semibold mb-4">Counting</h2>
<div class="space-y-4 ml-4">
<label class="label">
<span>Include id prefixes (space separated):</span>
<input type="text" class="input" bind:value={$settings.counting.idPrefixes} placeholder="" />
</label>

<label class="label">
<span>Start counting devices less than distance (in meters):</span>
<input type="number" class="input" step="0.01" min="0" bind:value={$settings.counting.startCountingDistance} placeholder="2.00" />
</label>

<label class="label">
<span>Stop counting devices greater than distance (in meters):</span>
<input type="number" class="input" step="0.01" min="0" bind:value={$settings.counting.stopCountingDistance} placeholder="4.00" />
</label>

<label class="label">
<span>Include devices with age less than (in ms):</span>
<input type="number" class="input" min="0" bind:value={$settings.counting.includeDevicesAge} placeholder="30000" />
</label>
</div>
</section>

<section class="mb-8">
<h2 class="text-2xl font-semibold mb-4">Filtering</h2>
<div class="space-y-4 ml-4">
<label class="label">
<span>Include only sending these ids to mqtt (eg. apple:iphone10-6 apple:iphone13-2):</span>
<input type="text" class="input" bind:value={$settings.filtering.includeIds} placeholder="" />
</label>

<label class="label">
<span>Exclude sending these ids to mqtt (eg. exp:20 apple:iphone10-6):</span>
<input type="text" class="input" bind:value={$settings.filtering.excludeIds} placeholder="" />
</label>

<label class="label">
<span>Max report distance (in meters):</span>
<input type="number" class="input" step="0.01" min="0" bind:value={$settings.filtering.maxReportDistance} placeholder="16.00" />
</label>

<label class="label">
<span>Report early if beacon has moved more than this distance (in meters):</span>
<input type="number" class="input" step="0.01" min="0" bind:value={$settings.filtering.earlyReportDistance} placeholder="0.50" />
</label>

<label class="label">
<span>Skip reporting if message age is less that this (in milliseconds):</span>
<input type="number" class="input" min="0" bind:value={$settings.filtering.skipReportAge} placeholder="5000" />
</label>
</div>
</section>

<section class="mb-8">
<h2 class="text-2xl font-semibold mb-4">Calibration</h2>
<div class="space-y-4 ml-4">
<label class="label">
<span>Rssi expected from a 0dBm transmitter at 1 meter (NOT used for iBeacons or Eddystone):</span>
<input type="number" class="input" bind:value={$settings.calibration.rssiAt1m} placeholder="-65" />
</label>

<label class="label">
<span>Rssi adjustment for receiver (use only if you know this device has a weak antenna):</span>
<input type="number" class="input" bind:value={$settings.calibration.rssiAdjustment} placeholder="0" />
</label>

<label class="label">
<span>Factor used to account for absorption, reflection, or diffraction:</span>
<input type="number" class="input" step="0.01" min="0" bind:value={$settings.calibration.absorptionFactor} placeholder="3.50" />
</label>

<label class="label">
<span>Rssi expected from this tx power at 1m (used for node iBeacon):</span>
<input type="number" class="input" bind:value={$settings.calibration.iBeaconRssiAt1m} placeholder="-59" />
</label>
</div>
</section>

<div class="flex justify-end mt-8">
<button class="btn variant-filled-primary" on:click={handleUpdate}>Update Settings</button>
</div>
</div>
{:else}
<div class="card p-4 variant-filled-warning">
<p>No settings available.</p>
</div>
{/if}
38 changes: 38 additions & 0 deletions src/ui/src/lib/TriStateCheckbox.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
export let checked: boolean | null = false;
export let id: string;
function handleClick(event: Event) {
const cb = event.target as HTMLInputElement;
if (cb.readOnly) {
cb.checked = false;
cb.readOnly = false;
checked = false;
} else if (!cb.checked) {
cb.readOnly = true;
cb.indeterminate = true;
checked = null;
} else {
checked = true;
}
}
$: ariaChecked = checked === null ? 'mixed' : checked;
</script>

<input
type="checkbox"
class="checkbox"
{id}
on:click={handleClick}
checked={checked === true}
indeterminate={checked === null}
readOnly={checked === null}
aria-checked={ariaChecked}
/>

<style>
input[type="checkbox"]:indeterminate {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
}
</style>
1 change: 1 addition & 0 deletions src/ui/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
{ href: '/devices', name: 'devices', icon: devices, alt: 'Devices' },
{ href: '/nodes', name: 'nodes', icon: nodes, alt: 'Nodes' },
{ href: '/calibration', name: 'calibration', icon: calibration, alt: 'Calibration' },
{ href: '/settings', name: 'settings', icon: settings, alt: 'Settings' }
];
</script>

Expand Down
14 changes: 14 additions & 0 deletions src/ui/src/routes/settings/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script lang="ts">
import GlobalSettings from '$lib/GlobalSettings.svelte';
</script>

<svelte:head>
<title>ESPresense Companion: Settings</title>
</svelte:head>

<div class="container mx-auto p-2">
<h1 class="text-3xl font-bold my-2 px-2">Settings</h1>
<p class="mb-6 text-lg px-2">These settings will be applied to every node, including new nodes.</p>

<GlobalSettings />
</div>

0 comments on commit a5e43f4

Please sign in to comment.