Skip to content

Commit

Permalink
Settings ui and service
Browse files Browse the repository at this point in the history
  • Loading branch information
DTTerastar committed Sep 9, 2024
1 parent 923ea14 commit c5fb826
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 4 deletions.
75 changes: 75 additions & 0 deletions Controllers/SettingsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Text.Json;

[ApiController]
[Route("api/settings")]
public class SettingsController : ControllerBase
{
private const string SettingsFilePath = "settings.json";

[HttpGet]
public ActionResult<Settings> GetSettings()
{
if (!System.IO.File.Exists(SettingsFilePath))
{
return new Settings();
}

var json = System.IO.File.ReadAllText(SettingsFilePath);
return JsonSerializer.Deserialize<Settings>(json);
}

[HttpPost]
public IActionResult SaveSettings([FromBody] Settings settings)
{
var json = JsonSerializer.Serialize(settings);
System.IO.File.WriteAllText(SettingsFilePath, json);
return Ok();
}
}

public class Settings
{
public Updating Updating { get; set; }
public Scanning Scanning { get; set; }
public Counting Counting { get; set; }
public Filtering Filtering { get; set; }
public Calibration Calibration { get; set; }
}

public class Updating
{
public bool AutoUpdate { get; set; }
public bool PreRelease { get; set; }
}

public class Scanning
{
public int? ForgetAfterMs { get; set; }
}

public class Counting
{
public string IdPrefixes { get; set; }
public double? StartCountingDistance { get; set; }
public double? StopCountingDistance { get; set; }
public int? IncludeDevicesAge { get; set; }
}

public class Filtering
{
public string IncludeIds { get; set; }
public string ExcludeIds { get; set; }
public double? MaxReportDistance { get; set; }
public double? EarlyReportDistance { get; set; }
public int? SkipReportAge { get; set; }
}

public class Calibration
{
public int? RssiAt1m { get; set; }
public int? RssiAdjustment { get; set; }
public double? AbsorptionFactor { get; set; }
public int? IBeaconRssiAt1m { get; set; }
}
1 change: 1 addition & 0 deletions src/ui/src/lib/images/settings.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 30 additions & 3 deletions src/ui/src/lib/stores.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { readable, writable, derived } from 'svelte/store';
import { base } from '$app/paths';
import type { Device, Config, Node } from './types';
import type { Device, Config, Node, Settings } from './types';

export const showAll: SvelteStore<boolean> = writable(false);
export const config = writable<Config>();
Expand Down Expand Up @@ -54,8 +54,8 @@ export const devices = readable<Device[]>([], function start(set) {
function fetchDevices() {
fetch(`${base}/api/state/devices`)
.then((d) => d.json())
.then((r) => {
deviceMap = new Map(r.map((device) => [device.id, device]));
.then((r: Device[]) => {
deviceMap = new Map(r.map((device: Device) => [device.id, device]));
updateDevicesFromMap();
})
.catch((ex) => {
Expand Down Expand Up @@ -133,3 +133,30 @@ export const calibration = readable({}, function start(set) {
clearInterval(interval);
};
});

export const settings = (() => {
const { subscribe, set, update } = writable<Settings | null>(null);

return {
subscribe,
set,
update,
load: async () => {
const response = await fetch(`${base}/api/settings`);
if (!response.ok) throw new Error("Something went wrong loading settings (error="+response.status+" "+response.statusText+")");
const data = await response.json();
set(data);
},
save: async (newSettings: Settings) => {
const response = await fetch(`${base}/api/settings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newSettings),
});
const data = await response.json();
set(data);
},
};
})();
32 changes: 31 additions & 1 deletion src/ui/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,36 @@ export interface Release {
name: string;
}


export type Settings = {
updating: {
autoUpdate: boolean;
preRelease: boolean;
};
scanning: {
forgetAfterMs: number | null;
};
counting: {
idPrefixes: string | null;
startCountingDistance: number | null;
stopCountingDistance: number | null;
includeDevicesAge: number | null;
};
filtering: {
includeIds: string | null;
excludeIds: string | null;
maxReportDistance: number | null;
earlyReportDistance: number | null;
skipReportAge: number | null;
};
calibration: {
rssiAt1m: number | null;
rssiAdjustment: number | null;
absorptionFactor: number | null;
iBeaconRssiAt1m: number | null;
};
};

export function isNode(d: Device | Node | null): d is Node {
return (d as Node)?.telemetry !== undefined;
}
}
6 changes: 6 additions & 0 deletions src/ui/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import nodes from '$lib/images/nodes.svg';
import devices from '$lib/images/devices.svg';
import calibration from '$lib/images/calibration.svg';
import settings from '$lib/images/settings.svg';
initializeStores();
Expand Down Expand Up @@ -62,6 +63,11 @@
<span>Calibration</span>
</AppRailAnchor>

<AppRailAnchor href="{base}/settings" name="settings" selected={current == `${base}/settings`}>
<img src={settings} class="px-4" alt="Settings" />
<span>Settings</span>
</AppRailAnchor>

<svelte:fragment slot="trail">
<AppRailAnchor regionIcon="w-8" href="https://github.com/ESPresense/ESPresense-companion" target="_blank">
<img src={github} class="px-4" alt="GitHub" />
Expand Down
156 changes: 156 additions & 0 deletions src/ui/src/routes/settings/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<script lang="ts">
import { onMount } from 'svelte';
import { settings } from '$lib/stores';
let loading = true;
let error: string | null = null;
onMount(async () => {
try {
await settings.load();
} catch (e) {
error = e.message;
} finally {
loading = false;
}
});
async function handleUpdate() {
try {
await settings.save($settings);
} catch (e) {
error = `Error updating settings: ${e.message}`;
}
}
</script>


<h1 class="h1">Settings</h1>
<div class="container mx-auto p-8">
<h2 class="text-1xl">Globally Set Node Settings</h2>
<p>These settings will be set in every node, even new nodes.</p>
{#if loading}
<div class="card 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 p-4 variant-filled-error">
<p>Error: {error}</p>
</div>
{:else if $settings}
<div class="card p-4 variant-filled-surface">
<section class="p-4">
<h3 class="h3 mb-4">Updating</h3>
<label class="label">
<input type="checkbox" class="checkbox" bind:checked={$settings.updating.autoUpdate} />
<span>Automatically update</span>
</label>
<label class="label">
<input type="checkbox" class="checkbox" bind:checked={$settings.updating.preRelease} />
<span>Include pre-released versions in auto-update</span>
</label>
</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-secondary" on:click={handleUpdate}>Update</button>
</div>
</div>
{:else}
<div class="card p-4 variant-filled-warning">
<p>No settings available.</p>
</div>
{/if}
</div>

0 comments on commit c5fb826

Please sign in to comment.