Add alerts when devices reconnect after stopping

This commit is contained in:
2025-10-20 19:08:21 -04:00
parent 98fa161eb1
commit 2f25286c21
10 changed files with 73 additions and 22 deletions

View File

@@ -193,4 +193,5 @@
</TypePattern>
&lt;/Patterns&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002EMemberReordering_002EMigrations_002ECSharpFileLayoutPatternRemoveIsAttributeUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Kaczor/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mqtt/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -100,12 +100,23 @@ public class Database(IConfiguration configuration)
return await connection.QueryFirstOrDefaultAsync<Device>(query, new { Name = deviceName }).ConfigureAwait(false);
}
public async Task SetDeviceLastUpdatedAsync(string deviceName, DateTimeOffset? lastUpdated)
public async Task<bool> SetDeviceLastUpdatedAsync(string deviceName, DateTimeOffset? lastUpdated)
{
await using var connection = CreateConnection();
var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Environment.Service.Data.Queries.SetDeviceLastUpdated.psql");
await connection.ExecuteAsync(query, new { Name = deviceName, LastUpdated = lastUpdated }).ConfigureAwait(false);
var stoppedReporting = await connection.QueryFirstAsync<bool>(query, new { Name = deviceName, LastUpdated = lastUpdated }).ConfigureAwait(false);
return stoppedReporting;
}
public async Task SetDeviceStoppedReportingAsync(string deviceName, bool stoppedReporting)
{
await using var connection = CreateConnection();
var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Environment.Service.Data.Queries.SetDeviceStoppedReporting.psql");
await connection.ExecuteAsync(query, new { Name = deviceName, StoppedReporting = stoppedReporting }).ConfigureAwait(false);
}
}

View File

@@ -1,11 +1,16 @@
INSERT INTO device(
name,
last_updated
last_updated,
stopped_reporting
)
VALUES (
@Name,
@LastUpdated
@LastUpdated,
false
)
ON CONFLICT (name)
DO UPDATE
SET last_updated = EXCLUDED.last_updated
SET last_updated = EXCLUDED.last_updated,
stopped_reporting = false
RETURNING
(SELECT stopped_reporting FROM device WHERE name = @Name);

View File

@@ -0,0 +1,6 @@
UPDATE
device
SET
stopped_reporting = @StoppedReporting
WHERE
name = @Name;

View File

@@ -0,0 +1,4 @@
ALTER TABLE
device
ADD COLUMN
stopped_reporting boolean NOT NULL DEFAULT false;

View File

@@ -1,26 +1,21 @@
using ChrisKaczor.HomeMonitor.Environment.Service.Data;
using RestSharp;
namespace ChrisKaczor.HomeMonitor.Environment.Service;
public class DeviceCheckService(Database _database, IConfiguration _configuration) : IHostedService
public class DeviceCheckService(Database database, IConfiguration configuration, TelegramSender telegramSender) : IHostedService
{
private Timer? _timer;
private TimeSpan _warningInterval;
private readonly string _botToken = _configuration["Telegram:BotToken"]!;
private readonly string _chatId = _configuration["Telegram:PersonalChatId"]!;
private readonly RestClient _restClient = new();
public Task StartAsync(CancellationToken cancellationToken)
{
WriteLog("DeviceCheckService started");
_warningInterval = TimeSpan.Parse(_configuration["Environment:DeviceWarningInterval"]!);
_warningInterval = TimeSpan.Parse(configuration["Environment:DeviceWarningInterval"]!);
var checkInterval = TimeSpan.Parse(_configuration["Environment:DeviceCheckInterval"]!);
var checkInterval = TimeSpan.Parse(configuration["Environment:DeviceCheckInterval"]!);
_timer = new Timer((state) => DoWork().Wait(), null, TimeSpan.Zero, checkInterval);
_timer = new Timer(_ => DoWork().Wait(cancellationToken), null, TimeSpan.Zero, checkInterval);
return Task.CompletedTask;
}
@@ -29,7 +24,7 @@ public class DeviceCheckService(Database _database, IConfiguration _configuratio
{
WriteLog("Checking devices started");
var devices = await _database.GetDevicesAsync();
var devices = await database.GetDevicesAsync();
foreach (var device in devices)
{
@@ -50,11 +45,9 @@ public class DeviceCheckService(Database _database, IConfiguration _configuratio
if (message.Length > 0)
{
var encodedMessage = Uri.EscapeDataString(message);
await database.SetDeviceStoppedReportingAsync(device.Name, true);
var restRequest = new RestRequest($"https://api.telegram.org/bot{_botToken}/sendMessage?chat_id={_chatId}&text={encodedMessage}");
await _restClient.GetAsync(restRequest);
await telegramSender.SendMessageAsync(message);
}
}

View File

@@ -17,11 +17,13 @@ public class MessageHandler : IHostedService
private readonly MqttFactory _mqttFactory;
private readonly string _topic;
private readonly HubConnection? _hubConnection;
private readonly TelegramSender _telegramSender;
public MessageHandler(IConfiguration configuration, Database database)
public MessageHandler(IConfiguration configuration, Database database, TelegramSender telegramSender)
{
_configuration = configuration;
_database = database;
_telegramSender = telegramSender;
_topic = _configuration["Mqtt:Topic"] ?? string.Empty;
@@ -77,9 +79,16 @@ public class MessageHandler : IHostedService
await _database.StoreMessageAsync(message);
await _database.SetDeviceLastUpdatedAsync(message.Name, message.Timestamp);
var hadStoppedReporting = await _database.SetDeviceLastUpdatedAsync(message.Name, message.Timestamp);
await SendMessage(message);
if (hadStoppedReporting)
{
var telegramMessage = $"Device now reporting: {message.Name}";
await _telegramSender.SendMessageAsync(telegramMessage);
}
}
private async Task SendMessage(DeviceMessage message)

View File

@@ -19,6 +19,7 @@ public static class Program
builder.Services.AddControllers();
builder.Services.AddTransient<Database>();
builder.Services.AddTransient<TelegramSender>();
builder.Services.AddHostedService<MessageHandler>();
builder.Services.AddHostedService<DeviceCheckService>();

View File

@@ -28,8 +28,10 @@
<EmbeddedResource Include="Data\Queries\CreateReading.psql" />
<EmbeddedResource Include="Data\Queries\GetDevices.psql" />
<EmbeddedResource Include="Data\Queries\GetDevice.psql" />
<EmbeddedResource Include="Data\Queries\SetDeviceStoppedReporting.psql" />
<EmbeddedResource Include="Data\Queries\SetDeviceLastUpdated.psql" />
<EmbeddedResource Include="Data\Schema\1-Initial Schema.psql" />
<EmbeddedResource Include="Data\Schema\3-Device Table - StoppedReporting.psql" />
<EmbeddedResource Include="Data\Schema\2-Device Table.psql" />
</ItemGroup>
@@ -40,7 +42,7 @@
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.1" />
<PackageReference Include="MQTTnet.AspNetCore" Version="4.3.3.952" />
<PackageReference Include="RestSharp" Version="111.1.0" />
<PackageReference Include="RestSharp" Version="112.1.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,19 @@
using RestSharp;
namespace ChrisKaczor.HomeMonitor.Environment.Service;
public class TelegramSender(IConfiguration configuration)
{
private readonly string _botToken = configuration["Telegram:BotToken"]!;
private readonly string _chatId = configuration["Telegram:PersonalChatId"]!;
private readonly RestClient _restClient = new();
public async Task SendMessageAsync(string message)
{
var encodedMessage = Uri.EscapeDataString(message);
var restRequest = new RestRequest($"https://api.telegram.org/bot{_botToken}/sendMessage?chat_id={_chatId}&text={encodedMessage}");
await _restClient.GetAsync(restRequest);
}
}