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>
 </TypePattern>
&lt;/Patterns&gt;</s:String> &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/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> <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); 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(); await using var connection = CreateConnection();
var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Environment.Service.Data.Queries.SetDeviceLastUpdated.psql"); 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( INSERT INTO device(
name, name,
last_updated last_updated,
stopped_reporting
) )
VALUES ( VALUES (
@Name, @Name,
@LastUpdated @LastUpdated,
false
) )
ON CONFLICT (name) ON CONFLICT (name)
DO UPDATE 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 ChrisKaczor.HomeMonitor.Environment.Service.Data;
using RestSharp;
namespace ChrisKaczor.HomeMonitor.Environment.Service; 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 Timer? _timer;
private TimeSpan _warningInterval; 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) public Task StartAsync(CancellationToken cancellationToken)
{ {
WriteLog("DeviceCheckService started"); 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; return Task.CompletedTask;
} }
@@ -29,7 +24,7 @@ public class DeviceCheckService(Database _database, IConfiguration _configuratio
{ {
WriteLog("Checking devices started"); WriteLog("Checking devices started");
var devices = await _database.GetDevicesAsync(); var devices = await database.GetDevicesAsync();
foreach (var device in devices) foreach (var device in devices)
{ {
@@ -50,11 +45,9 @@ public class DeviceCheckService(Database _database, IConfiguration _configuratio
if (message.Length > 0) 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 telegramSender.SendMessageAsync(message);
await _restClient.GetAsync(restRequest);
} }
} }

View File

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

View File

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

View File

@@ -28,8 +28,10 @@
<EmbeddedResource Include="Data\Queries\CreateReading.psql" /> <EmbeddedResource Include="Data\Queries\CreateReading.psql" />
<EmbeddedResource Include="Data\Queries\GetDevices.psql" /> <EmbeddedResource Include="Data\Queries\GetDevices.psql" />
<EmbeddedResource Include="Data\Queries\GetDevice.psql" /> <EmbeddedResource Include="Data\Queries\GetDevice.psql" />
<EmbeddedResource Include="Data\Queries\SetDeviceStoppedReporting.psql" />
<EmbeddedResource Include="Data\Queries\SetDeviceLastUpdated.psql" /> <EmbeddedResource Include="Data\Queries\SetDeviceLastUpdated.psql" />
<EmbeddedResource Include="Data\Schema\1-Initial Schema.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" /> <EmbeddedResource Include="Data\Schema\2-Device Table.psql" />
</ItemGroup> </ItemGroup>
@@ -40,7 +42,7 @@
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" /> <PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.1" />
<PackageReference Include="MQTTnet.AspNetCore" Version="4.3.3.952" /> <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>
<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);
}
}