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));
}