From ce81b871c52406caa739cdf83ee2a320780f1b11 Mon Sep 17 00:00:00 2001 From: Darrell Date: Sun, 29 Sep 2024 21:53:01 -0400 Subject: [PATCH] ShowUntracked checkbox (#755) --- agent.sh | 10 +- src/Controllers/StateController.cs | 14 ++- .../GlobalEventDispatcher.cs | 7 +- src/Models/Device.cs | 10 +- src/Services/DeviceTracker.cs | 11 ++- src/Services/MultiScenarioLocator.cs | 2 +- src/ui/src/lib/stores.ts | 98 +++++++++++-------- src/ui/src/routes/devices/+page.svelte | 5 + tests/DeviceTrackerTests.cs | 3 +- 9 files changed, 97 insertions(+), 63 deletions(-) rename src/{Controllers => Events}/GlobalEventDispatcher.cs (81%) diff --git a/agent.sh b/agent.sh index 064b954f..51e2c476 100755 --- a/agent.sh +++ b/agent.sh @@ -2,15 +2,15 @@ export WORKSPACE_BASE=$(pwd) -sudo docker run \ - -it \ - --pull=always \ - --add-host host.docker.internal:host-gateway \ +docker pull ghcr.io/all-hands-ai/runtime:0.9-nikolaik + +docker run -it --pull=always \ + -e SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.9-nikolaik \ -e SANDBOX_USER_ID=$(id -u) \ - -e LLM_OLLAMA_BASE_URL="http://host.docker.internal:11434" \ -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \ -v $WORKSPACE_BASE:/opt/workspace_base \ -v /var/run/docker.sock:/var/run/docker.sock \ -p 3000:3000 \ + --add-host host.docker.internal:host-gateway \ --name openhands-app-$(date +%Y%m%d%H%M%S) \ ghcr.io/all-hands-ai/openhands:0.9 \ No newline at end of file diff --git a/src/Controllers/StateController.cs b/src/Controllers/StateController.cs index 38b4a87a..2206eac1 100644 --- a/src/Controllers/StateController.cs +++ b/src/Controllers/StateController.cs @@ -40,9 +40,11 @@ public IEnumerable GetNodes(bool includeTele = true) // GET: api/rooms [HttpGet("api/state/devices")] - public IEnumerable GetDevices() + public IEnumerable GetDevices([FromQuery] bool showUntracked = false) { - return _state.Devices.Values.Where(a => a is { Track: true }); + IEnumerable d = _state.Devices.Values; + if (!showUntracked) d = d.Where(a => a is { Track: true }); + return d; } // GET: api/config @@ -81,7 +83,7 @@ public Calibration GetCalibration() [ApiExplorerSettings(IgnoreApi = true)] [Route("/ws")] - public async Task Get() + public async Task Get([FromQuery] bool showUntracked = false) { if (!HttpContext.WebSockets.IsWebSocketRequest) { @@ -99,7 +101,11 @@ void EnqueueAndSignal(T value) void OnConfigChanged(object? sender, Config e) => EnqueueAndSignal(new { type = "configChanged" }); void OnCalibrationChanged(object? sender, CalibrationEventArgs e) => EnqueueAndSignal(new { type = "calibrationChanged", data = e.Calibration }); void OnNodeStateChanged(object? sender, NodeStateEventArgs e) => EnqueueAndSignal(new { type = "nodeStateChanged", data = e.NodeState }); - void OnDeviceChanged(object? sender, DeviceEventArgs e) => EnqueueAndSignal(new { type = "deviceChanged", data = e.Device }); + void OnDeviceChanged(object? sender, DeviceEventArgs e) + { + if (showUntracked || (e.Device?.Track ?? false) || e.TrackChanged) + EnqueueAndSignal(new { type = "deviceChanged", data = e.Device }); + }; _config.ConfigChanged += OnConfigChanged; _eventDispatcher.CalibrationChanged += OnCalibrationChanged; diff --git a/src/Controllers/GlobalEventDispatcher.cs b/src/Events/GlobalEventDispatcher.cs similarity index 81% rename from src/Controllers/GlobalEventDispatcher.cs rename to src/Events/GlobalEventDispatcher.cs index ed398ef7..ef33cec6 100644 --- a/src/Controllers/GlobalEventDispatcher.cs +++ b/src/Events/GlobalEventDispatcher.cs @@ -13,9 +13,9 @@ public void OnNodeStateChanged(NodeState state) NodeStateChanged?.Invoke(this, new NodeStateEventArgs(state)); } - public void OnDeviceChanged(Device device) + public void OnDeviceChanged(Device device, bool trackChanged) { - DeviceStateChanged?.Invoke(this, new DeviceEventArgs(device)); + DeviceStateChanged?.Invoke(this, new DeviceEventArgs(device, trackChanged)); } public void OnCalibrationChanged(Calibration calibration) @@ -29,9 +29,10 @@ public class NodeStateEventArgs(NodeState state) : EventArgs public NodeState NodeState { get; } = state; } -public class DeviceEventArgs(Device device) : EventArgs +public class DeviceEventArgs(Device device, bool trackChanged) : EventArgs { public Device Device { get; } = device; + public bool TrackChanged { get; } = trackChanged; } public class CalibrationEventArgs(Calibration calibration) : EventArgs diff --git a/src/Models/Device.cs b/src/Models/Device.cs index 42b344f4..bf4bd87f 100644 --- a/src/Models/Device.cs +++ b/src/Models/Device.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Text; using System.Text.Json.Serialization; using ESPresense.Converters; using MathNet.Spatial.Euclidean; @@ -16,10 +17,11 @@ public Device(string id, string? discoveryId, TimeSpan timeout) public override string ToString() { - if (Track) - return $"{nameof(Id)}: {Id}"; - else - return $"Untracked {nameof(Id)}: {Id}"; + StringBuilder sb = new(); + sb.Append($"{nameof(Id)}: {Id}"); + if (!string.IsNullOrEmpty(Name)) sb.Append($", {nameof(Name)}: {Name}"); + if (!Track) sb.Append($", {nameof(Track)}: {Track}"); + return sb.ToString(); } public string Id { get; init; } diff --git a/src/Services/DeviceTracker.cs b/src/Services/DeviceTracker.cs index 07ad2136..18d8eedc 100644 --- a/src/Services/DeviceTracker.cs +++ b/src/Services/DeviceTracker.cs @@ -1,10 +1,11 @@ using System.Threading.Channels; +using ESPresense.Controllers; using ESPresense.Models; using Serilog; namespace ESPresense.Services; -public class DeviceTracker(State state, MqttCoordinator mqtt, TelemetryService tele) : BackgroundService +public class DeviceTracker(State state, MqttCoordinator mqtt, TelemetryService tele, GlobalEventDispatcher globalEventDispatcher) : BackgroundService { private readonly Channel _toProcessChannel = Channel.CreateUnbounded(); private readonly Channel _toLocateChannel = Channel.CreateUnbounded(); @@ -91,9 +92,11 @@ private async Task ProcessDevicesAsync(CancellationToken stoppingToken) await foreach (var device in _toProcessChannel.Reader.ReadAllAsync(stoppingToken)) { if (stoppingToken.IsCancellationRequested) break; - await CheckDeviceAsync(device); + var trackChanged = await CheckDeviceAsync(device); if (device.Track) await _toLocateChannel.Writer.WriteAsync(device, stoppingToken); + else + globalEventDispatcher.OnDeviceChanged(device, trackChanged); } } @@ -131,7 +134,7 @@ private bool ShouldTrackDevice(Device device) return false; } - private async Task CheckDeviceAsync(Device device) + private async Task CheckDeviceAsync(Device device) { var wasTracked = device.Track; if (device.Check) @@ -155,7 +158,9 @@ private async Task CheckDeviceAsync(Device device) foreach (var ad in device.HassAutoDiscovery) await ad.Delete(mqtt); } + return true; } + return false; } public IAsyncEnumerable GetConsumingEnumerable(CancellationToken cancellationToken) diff --git a/src/Services/MultiScenarioLocator.cs b/src/Services/MultiScenarioLocator.cs index 25424a4f..dd67d4ec 100644 --- a/src/Services/MultiScenarioLocator.cs +++ b/src/Services/MultiScenarioLocator.cs @@ -67,7 +67,7 @@ await mqtt.EnqueueAsync($"espresense/companion/{device.Id}/attributes", }, SerializerSettings.NullIgnore) ); - globalEventDispatcher.OnDeviceChanged(device); + globalEventDispatcher.OnDeviceChanged(device, false); if (state?.Config?.History?.Enabled ?? false) { foreach (var ds in device.Scenarios.Where(ds => ds.Confidence != 0)) diff --git a/src/ui/src/lib/stores.ts b/src/ui/src/lib/stores.ts index 621e97bf..c2ab32ef 100644 --- a/src/ui/src/lib/stores.ts +++ b/src/ui/src/lib/stores.ts @@ -43,54 +43,68 @@ async function getConfig() { getConfig(); -export const devices = readable([], function start(set) { - let deviceMap = new Map(); +export const showUntracked = writable(false); + +export const devices = derived<[typeof showUntracked], Device[]>( + [showUntracked], + ([$showUntracked], set) => { + let deviceMap = new Map(); + var q = (new URLSearchParams({ + showUntracked: $showUntracked ? "true" : "false" + })).toString(); + + function updateDevicesFromMap() { + const devicesArray = Array.from(deviceMap.values()); + set(devicesArray); + } + + function fetchDevices() { + + fetch(`${base}/api/state/devices?${q}`) + .then((d) => d.json()) + .then((r) => { + deviceMap = new Map(r.map((device: Device) => [device.id, device])); + updateDevicesFromMap(); + }) + .catch((ex) => { + console.error('Error fetching devices:', ex); + }); + } - function updateDevicesFromMap() { - var a = Array.from(deviceMap.values()); - set(a); - } + fetchDevices(); - function fetchDevices() { - fetch(`${base}/api/state/devices`) - .then((d) => d.json()) - .then((r) => { - deviceMap = new Map(r.map((device) => [device.id, device])); - updateDevicesFromMap(); - }) - .catch((ex) => { - console.log(ex); + const interval = setInterval(fetchDevices, 60000); + + function setupWebsocket() { + const loc = new URL(`${base}/ws?${q}`, window.location.href); + const new_uri = (loc.protocol === 'https:' ? 'wss:' : 'ws:') + '//' + loc.host + loc.pathname + loc.search; + const socket = new WebSocket(new_uri); + + socket.addEventListener('message', async function (event) { + const eventData = JSON.parse(event.data); + if (eventData.type === 'deviceChanged' && eventData.data?.id) { + deviceMap.set(eventData.data.id, eventData.data); + updateDevicesFromMap(); + } else if (eventData.type === 'configChanged') { + getConfig(); + } else if (eventData.type === 'time') { + relative.set(eventData.data); + } else { + console.log('Unhandled websocket event:', event.data); + } }); - } - fetchDevices(); + return socket; + } - const interval = setInterval(() => { - fetchDevices(); - }, 60000); - - function setupWebsocket() { - var loc = new URL(`${base}/ws`, window.location.href); - var new_uri = (loc.protocol === 'https:' ? 'wss:' : 'ws:') + '//' + loc.host + loc.pathname; - socket = new WebSocket(new_uri); - socket.addEventListener('message', async function (event) { - var eventData = JSON.parse(event.data); - if (eventData.type === 'deviceChanged' && eventData.data?.id) { - deviceMap.set(eventData.data.id, eventData.data); - updateDevicesFromMap(); - } else if (eventData.type == 'configChanged') { - getConfig(); - } else if (eventData.type == 'time') relative.set(eventData.data); - else console.log(event.data); - }); - } + const socket = setupWebsocket(); - setupWebsocket(); - - return function stop() { - clearInterval(interval); - }; -}); + return () => { + clearInterval(interval); + socket.close(); + }; + } +); export const nodes = readable([], function start(set) { var errors = 0; diff --git a/src/ui/src/routes/devices/+page.svelte b/src/ui/src/routes/devices/+page.svelte index d94d01df..eb1be6fd 100644 --- a/src/ui/src/routes/devices/+page.svelte +++ b/src/ui/src/routes/devices/+page.svelte @@ -1,6 +1,8 @@ @@ -9,5 +11,8 @@

Devices

+
+ Show Untracked +
detail(d.detail)} />
diff --git a/tests/DeviceTrackerTests.cs b/tests/DeviceTrackerTests.cs index e9846aaf..de43ba53 100644 --- a/tests/DeviceTrackerTests.cs +++ b/tests/DeviceTrackerTests.cs @@ -1,4 +1,5 @@ using System.Reflection; +using ESPresense.Controllers; using ESPresense.Locators; using ESPresense.Models; using ESPresense.Services; @@ -29,7 +30,7 @@ public void TestMultiScenarioLocator() { var configLoader = new ConfigLoader("config"); var mqtt = new MqttCoordinator(configLoader, null, null); - var locator = new DeviceTracker(new State(configLoader), mqtt, new TelemetryService(mqtt)); + var locator = new DeviceTracker(new State(configLoader), mqtt, new TelemetryService(mqtt), new GlobalEventDispatcher()); // Use testData to test locator... // Assert.That(result, Is.EqualTo(expectedResult)); }