Skip to content

Commit c73242d

Browse files
euquiqdjdd87
authored andcommitted
added REALTIME option for viewing snapshots thru web
Now you can watch any camera for new valid snapshots in realtime. It uses SignalR for keeping each web client updated on any new snapshot. It is faster than any external notification (like -say- Telegram) so it is an interesting addition i.e. "to see who's at your door" or similar. I also renamed some webpages so they make more sense.
1 parent 8de4e7c commit c73242d

14 files changed

+5431
-12
lines changed

SynoAI/Controllers/CameraController.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
using SynoAI.Extensions;
1212
using System.Linq;
1313
using System.Threading.Tasks;
14+
using Microsoft.AspNetCore.SignalR;
15+
using SynoAI.Hubs;
1416

1517
namespace SynoAI.Controllers
1618
{
@@ -21,14 +23,18 @@ namespace SynoAI.Controllers
2123
[Route("[controller]")]
2224
public class CameraController : ControllerBase
2325
{
26+
// euquiq: Needed for connecting into the SignalR hub and send valid Snapshot for rt web monitoring
27+
private readonly IHubContext<SynoAIHub> _hubContext;
28+
2429
private readonly IAIService _aiService;
2530
private readonly ISynologyService _synologyService;
2631
private readonly ILogger<CameraController> _logger;
2732

2833
private static ConcurrentDictionary<string, DateTime> _lastCameraChecks = new ConcurrentDictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);
2934

30-
public CameraController(IAIService aiService, ISynologyService synologyService, ILogger<CameraController> logger)
35+
public CameraController(IAIService aiService, ISynologyService synologyService, ILogger<CameraController> logger, IHubContext<SynoAIHub> hubContext)
3136
{
37+
_hubContext = hubContext;
3238
_aiService = aiService;
3339
_synologyService = synologyService;
3440
_logger = logger;
@@ -125,6 +131,9 @@ public async void Get(string id)
125131
// Process and save the snapshot
126132
ProcessedImage processedImage = SnapshotManager.DressImage(camera, snapshot, predictions, validPredictions, _logger);
127133

134+
// Inform eventual web users about this new Snapshot, for the "realtime" option thru Web
135+
await _hubContext.Clients.All.SendAsync("ReceiveSnapshot", camera.Name, processedImage.FileName);
136+
128137
// Send Notifications
129138
await SendNotifications(camera, processedImage, labels);
130139
_logger.LogInformation($"{id}: Valid object found in snapshot {snapshotCount} of {Config.MaxSnapshots} at EVENT TIME {overallStopwatch.ElapsedMilliseconds}ms.");

SynoAI/Controllers/HomeController.cs

+14-4
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,30 @@ public class HomeController : Controller
1717
static readonly string[] byteSizes = { "bytes", "Kb", "Mb", "Gb", "Tb" };
1818

1919
/// <summary>
20-
/// Called by the user from web browser - General view (up to 24 hours)
20+
/// Called by the user from web browser - General view (up to 24 latest hours)
2121
/// </summary>
2222
[Route("")]
2323
public IActionResult Index()
2424
{
2525
return View();
2626
}
2727

28+
/// <summary>
29+
/// Called by the user from web browser - View snapshots in realtime for a camera
30+
/// </summary>
31+
[Route("{cameraname}/RT")]
32+
public IActionResult Realtime(string cameraname)
33+
{
34+
ViewData["camera"] = cameraname;
35+
return View();
36+
}
37+
2838

2939
/// <summary>
3040
/// Called by the user from web browser - Zoom into one hour of snapshots
3141
/// </summary>
3242
[Route("{cameraname}/{year}/{month}/{day}/{hour}")]
33-
public IActionResult Camera(string cameraname, string year, string month, string day, string hour)
43+
public IActionResult Hour(string cameraname, string year, string month, string day, string hour)
3444
{
3545
ViewData["camera"] = cameraname;
3646
ViewData["year"] = year;
@@ -42,10 +52,10 @@ public IActionResult Camera(string cameraname, string year, string month, string
4252

4353

4454
/// <summary>
45-
/// Called by the user from web browser - Zoom into a minute of snapshots: Image gallery
55+
/// Called by the user from web browser - Zoom into a minute of snapshots and show it's snapshots
4656
/// </summary>
4757
[Route("{cameraname}/{year}/{month}/{day}/{hour}/{minute}")]
48-
public IActionResult Gallery(string cameraname, string year, string month, string day, string hour,string minute)
58+
public IActionResult Minute(string cameraname, string year, string month, string day, string hour,string minute)
4959
{
5060
ViewData["camera"] = cameraname;
5161

SynoAI/Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ WORKDIR /src
99
COPY ["SynoAI.csproj", "./"]
1010
RUN dotnet restore "SynoAI.csproj"
1111
COPY . .
12+
1213
WORKDIR "/src/."
1314
RUN dotnet build "SynoAI.csproj" -c Release -o /app/build
1415

SynoAI/Hubs/SynoAIHub.cs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Microsoft.AspNetCore.SignalR;
2+
using System.Threading.Tasks;
3+
4+
namespace SynoAI.Hubs
5+
{
6+
public class SynoAIHub : Hub
7+
{
8+
// euquiq: This class just needs to be present for SignalR
9+
}
10+
}

SynoAI/Models/Graph.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace SynoAI.Models
88
/// </summary>
99

1010
/// <summary>
11-
/// Class holding definition for one data point (extracted from filenames inside a captures/camera folder)
11+
/// Class holding definition for one data point (extracted from filenames inside a Captures/camera folder)
1212
/// </summary>
1313
public class GraphPoint
1414
{

SynoAI/Startup.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.OpenApi.Models;
88
using SynoAI.Services;
99
using System.Threading.Tasks;
10+
using SynoAI.Hubs;
1011

1112
namespace SynoAI
1213
{
@@ -25,6 +26,9 @@ public void ConfigureServices(IServiceCollection services)
2526
});
2627

2728
services.AddRazorPages();
29+
30+
// euquiq: Needed for realtime update from each camera valid snapshot into client's web browser
31+
services.AddSignalR();
2832
}
2933

3034
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -38,14 +42,21 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IConfigu
3842
app.UseSwagger();
3943
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "SynoAI v1"));
4044
}
45+
46+
// euquiq: Allows /wwwroot's static files (mainly our Javascript code for RT monitoring the cameras)
47+
app.UseStaticFiles();
4148

4249
app.UseRouting();
4350

4451
app.UseAuthorization();
4552

4653
app.UseEndpoints(endpoints =>
4754
{
48-
endpoints.MapControllers();
55+
// euquiq: Used by SignalR to contact each online client with snapshot updates
56+
endpoints.MapHub<SynoAIHub>("/synoaiHub");
57+
58+
// euquiq: Web interface mapped inside HomeController.cs
59+
endpoints.MapControllers();
4960
});
5061

5162
Task task = synologyService.InitialiseAsync();

SynoAI/Views/Home/Camera.cshtml SynoAI/Views/Home/Hour.cshtml

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
graphDraw.GraphXAxisHeight = 24;
88
graphDraw.GraphYAxisWidth = 24;
99
graphDraw.GraphYSteps = 5;
10+
string camera = ViewData["camera"].ToString();
1011
}
1112
<!DOCTYPE html>
1213
<html lang="en">
@@ -25,7 +26,7 @@
2526
</head>
2627
<body>
2728
<h2>SynoAI Report</h2>
28-
<h4>Data for camera @ViewData["camera"] on date @ViewData["year"]-@ViewData["month"]-@ViewData["day"] @@ @ViewData["hour"] Hs. presented on @DateTime.Now</h4>
29+
<h4>Data for camera @camera <a href="/@camera/RT">(View in realtime)</a> on date @ViewData["year"]-@ViewData["month"]-@ViewData["day"] @@ @ViewData["hour"] Hs. presented on @DateTime.Now</h4>
2930
<hr>
3031
@{
3132
DateTime fecha = new DateTime(Int16.Parse(ViewData["year"].ToString()),Int16.Parse(ViewData["month"].ToString()),Int16.Parse(ViewData["day"].ToString()),Int16.Parse(ViewData["hour"].ToString()),59,59);
@@ -43,7 +44,7 @@
4344
</div>
4445
@foreach (SynoAI.Models.GraphPoint graphPoint in data.GraphPoints)
4546
{
46-
<a href="/@ViewData["camera"]/@graphPoint.Date.Year/@graphPoint.Date.Month/@graphPoint.Date.Day/@graphPoint.Date.Hour/@graphPoint.Date.Minute">
47+
<a href="/@camera/@graphPoint.Date.Year/@graphPoint.Date.Month/@graphPoint.Date.Day/@graphPoint.Date.Hour/@graphPoint.Date.Minute">
4748
<div class="p" title="@graphPoint.Predictions Snapshots @@ @graphPoint.Date.ToString(@"HH\:mm") Hs." style="height:@graphDraw.GraphBarHeight(data.yMax, graphPoint.Predictions)px; width:@graphDraw.GraphColsWidth(data.GraphPoints.Count())px"></div>
4849
<div class="o" title="@graphPoint.Objects Objects @@ @graphPoint.Date.ToString(@"HH\:mm") Hs." style="height:@graphDraw.GraphBarHeight(data.yMax, graphPoint.Objects)px;width:@graphDraw.GraphColsWidth(data.GraphPoints.Count())px"></div>
4950
</a>

SynoAI/Views/Home/Index.cshtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
{
3232
GraphData data = SynoAI.Controllers.HomeController.GetData(camera.Name, DateTime.Now, false);
3333
<p>
34-
Camera: <b>@camera.Name</b><br />
34+
Camera: <b>@camera.Name <a href="/@camera.Name/RT">(View in realtime)</a></b><br />
3535
Threshold: <b>@camera.Threshold.ToString()</b>% certainity<br />
3636
Objects: <b> @SynoAI.Controllers.HomeController.GetTypes(camera)</b><br />
3737
Snapshots: <b> @data.Snapshots</b> images, distributed inside <b>@data.HoursCounter</b> Hours.<br />

SynoAI/Views/Home/Gallery.cshtml SynoAI/Views/Home/Minute.cshtml

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
@using System
33
@{
44
DateTime date = (DateTime)ViewData["date"];
5+
string camera = ViewData["camera"].ToString();
56
}
67

78
<!DOCTYPE html>
@@ -14,7 +15,7 @@
1415
</head>
1516
<body>
1617
<h2>SynoAI Report</h2>
17-
<h4>Snapshots for camera @ViewData["camera"] on date @date.ToString(@"yyyy-MM-dd") @@ @date.ToString(@"HH\:mm") Hs. presented on @DateTime.Now</h4>
18+
<h4>Snapshots for camera <a href="/@camera/RT">(View in realtime)</a> on date @date.ToString(@"yyyy-MM-dd") @@ @date.ToString(@"HH\:mm") Hs. presented on @DateTime.Now</h4>
1819
<hr>
1920
@{
2021
List<String> snapshots = SynoAI.Controllers.HomeController.GetSnapshots(ViewData["camera"].ToString(), date);
@@ -29,7 +30,7 @@
2930
{
3031
snapWidth = 210;
3132
}
32-
string camera = ViewData["camera"].ToString();
33+
3334
foreach (string filename in snapshots)
3435
{
3536
<a href="/@camera/@filename">

SynoAI/Views/Home/Realtime.cshtml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@using SynoAI.Models
2+
@using System
3+
@{
4+
string camera = ViewData["camera"].ToString();
5+
}
6+
7+
<!DOCTYPE html>
8+
<html lang="en">
9+
<head>
10+
<title>SynoAI</title>
11+
<script src="~/js/signalr/dist/browser/signalr.js"></script>
12+
<script src="~/js/rtimage.js"></script>
13+
</head>
14+
<body>
15+
<h2>SynoAI Report</h2>
16+
<h4>Realtime image prediction for camera @camera</h4>
17+
<hr>
18+
<img id="camera-@camera" style="width:100%; height:100%;" />
19+
</body>
20+
</html>

SynoAI/wwwroot/js/rtimage.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// euquiq: In charge of receiving the snapshot from SynoAI SignalR's Hub and populating the <img>
2+
3+
"use strict";
4+
5+
const connection = new signalR.HubConnectionBuilder()
6+
.withUrl("/synoaiHub")
7+
.build();
8+
9+
connection.on("ReceiveSnapshot", (cameraName, fileName) => {
10+
var image = document.getElementById("camera-" + cameraName);
11+
if (image != null) {
12+
image.alt = fileName;
13+
image.src = "/" + cameraName + "/" + fileName;
14+
}
15+
});
16+
17+
connection.onclose(async () => {
18+
await start();
19+
});
20+
21+
async function start() {
22+
try {
23+
await connection.start();
24+
console.log("SignalR Connected.");
25+
} catch (err) {
26+
console.log(err);
27+
setTimeout(start, 5000);
28+
}
29+
};
30+
31+
// Start the connection.
32+
start();

0 commit comments

Comments
 (0)