add RelayConditions endpoint and RelayCount to DeviceSettings and bugfix
This commit is contained in:
@@ -81,5 +81,15 @@ public class AlertConditionsController : ControllerBase
|
|||||||
}
|
}
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت شرایط هشدار رلهای (notificationType > 0) برای یک دستگاه
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("relay/{deviceId}")]
|
||||||
|
public async Task<ActionResult<object>> GetRelayConditions(int deviceId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var conditions = await alertConditionService.GetRelayConditionsAsync(deviceId, cancellationToken);
|
||||||
|
return Ok(new { conditions });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ public class TelemetryController : ControllerBase
|
|||||||
{
|
{
|
||||||
private readonly ITelemetryService telemetryService;
|
private readonly ITelemetryService telemetryService;
|
||||||
private readonly IAlertService alertService;
|
private readonly IAlertService alertService;
|
||||||
|
private readonly IAlertConditionService alertConditionService;
|
||||||
|
|
||||||
public TelemetryController(ITelemetryService telemetryService, IAlertService alertService)
|
public TelemetryController(ITelemetryService telemetryService, IAlertService alertService, IAlertConditionService alertConditionService)
|
||||||
{
|
{
|
||||||
this.telemetryService = telemetryService;
|
this.telemetryService = telemetryService;
|
||||||
this.alertService = alertService;
|
this.alertService = alertService;
|
||||||
|
this.alertConditionService = alertConditionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -25,7 +27,7 @@ public class TelemetryController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("AddData")]
|
[HttpGet("AddData")]
|
||||||
public async Task<ActionResult<string>> Create([FromQuery] TelemetryDto telementry, CancellationToken cancellationToken)
|
public async Task<ActionResult<string>> Create([FromQuery] TelemetryDto telementry, [FromQuery] string? lastUpdateDate, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|
||||||
telementry.TimestampUtc = DateTime.UtcNow;
|
telementry.TimestampUtc = DateTime.UtcNow;
|
||||||
@@ -43,28 +45,47 @@ public class TelemetryController : ControllerBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check and send alerts if needed (fire and forget)
|
// Check if relay conditions have been updated since lastUpdateDate
|
||||||
// _ = Task.Run(async () =>
|
if (deviceId > 0 && !string.IsNullOrEmpty(lastUpdateDate))
|
||||||
|
{
|
||||||
|
var parsedDate = ParseLastUpdateDate(lastUpdateDate);
|
||||||
|
// var relayConditions = await alertConditionService.GetUpdatedRelayConditionsAsync(deviceId, parsedDate, cancellationToken);
|
||||||
|
// if (relayConditions.Count > 0)
|
||||||
// {
|
// {
|
||||||
// try
|
// return Ok(new { conditions = relayConditions });
|
||||||
// {
|
|
||||||
// // Get deviceId from the saved telemetry record
|
|
||||||
// var deviceId = telementry.DeviceId;
|
|
||||||
// if (deviceId > 0)
|
|
||||||
// {
|
|
||||||
// await alertService.CheckAndSendAlertsAsync(deviceId, telementry, cancellationToken);
|
|
||||||
// }
|
// }
|
||||||
// }
|
|
||||||
// catch
|
var hasUpdates = await alertConditionService.HasUpdatedConditionsAsync(deviceId, parsedDate, cancellationToken);
|
||||||
// {
|
if(hasUpdates)
|
||||||
// // Log error but don't fail the request
|
return Ok("Relay");
|
||||||
// // Errors are logged in AlertService
|
}
|
||||||
// }
|
|
||||||
// }, cancellationToken);
|
|
||||||
|
|
||||||
return Ok(id + "");
|
return Ok(id + "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse date string in format: yyyy/MM/dd-HH:mm
|
||||||
|
/// </summary>
|
||||||
|
private static DateTime? ParseLastUpdateDate(string dateStr)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(dateStr))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Try parsing as yyyy/MM/dd-HH:mm
|
||||||
|
if (DateTime.TryParseExact(dateStr, "yyyy/MM/dd-HH:mm", null, System.Globalization.DateTimeStyles.None, out var result))
|
||||||
|
{
|
||||||
|
return DateTime.SpecifyKind(result, DateTimeKind.Utc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try parsing as yyyy-MM-dd HH:mm (ISO-like)
|
||||||
|
if (DateTime.TryParseExact(dateStr, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out var result2))
|
||||||
|
{
|
||||||
|
return DateTime.SpecifyKind(result2, DateTimeKind.Utc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("minmax")]
|
[HttpGet("minmax")]
|
||||||
public async Task<ActionResult<TelemetryMinMax>> MinMax([FromQuery] int deviceId, [FromQuery] DateTime? startUtc, [FromQuery] DateTime? endUtc, CancellationToken cancellationToken)
|
public async Task<ActionResult<TelemetryMinMax>> MinMax([FromQuery] int deviceId, [FromQuery] DateTime? startUtc, [FromQuery] DateTime? endUtc, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ public sealed class DeviceSettingsDto
|
|||||||
public int UploadIntervalMin { get; set; } = 5;
|
public int UploadIntervalMin { get; set; } = 5;
|
||||||
public string DevicePhoneNumber { get; set; } = string.Empty;
|
public string DevicePhoneNumber { get; set; } = string.Empty;
|
||||||
public Domain.SimCardType? SimCardType { get; set; }
|
public Domain.SimCardType? SimCardType { get; set; }
|
||||||
|
public int RelayCount { get; set; } = 4;
|
||||||
public string SmsRecievers { get; set; } = string.Empty;
|
public string SmsRecievers { get; set; } = string.Empty;
|
||||||
public string? TokenCode { get; set; }
|
public string? TokenCode { get; set; }
|
||||||
public string? VerificationCode { get; set; }
|
public string? VerificationCode { get; set; }
|
||||||
@@ -171,6 +172,28 @@ public sealed class AlertRuleDto
|
|||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO for relay alert conditions (notificationType > 0)
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RelayConditionDto
|
||||||
|
{
|
||||||
|
public int Enabled { get; set; }
|
||||||
|
public int TimeType { get; set; }
|
||||||
|
public string Relay { get; set; } = string.Empty;
|
||||||
|
public List<RelayRuleDto> Rules { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO for relay condition rules
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RelayRuleDto
|
||||||
|
{
|
||||||
|
public string Sensor { get; set; } = string.Empty;
|
||||||
|
public string Op { get; set; } = string.Empty;
|
||||||
|
public decimal V1 { get; set; }
|
||||||
|
public decimal? V2 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class CreateAlertConditionRequest
|
public sealed class CreateAlertConditionRequest
|
||||||
{
|
{
|
||||||
public required int DeviceId { get; set; }
|
public required int DeviceId { get; set; }
|
||||||
|
|||||||
@@ -8,5 +8,18 @@ public interface IAlertConditionService
|
|||||||
Task UpdateAsync(UpdateAlertConditionRequest request, CancellationToken cancellationToken);
|
Task UpdateAsync(UpdateAlertConditionRequest request, CancellationToken cancellationToken);
|
||||||
Task DeleteAsync(int id, CancellationToken cancellationToken);
|
Task DeleteAsync(int id, CancellationToken cancellationToken);
|
||||||
Task<bool> ToggleEnabledAsync(int id, bool isEnabled, CancellationToken cancellationToken);
|
Task<bool> ToggleEnabledAsync(int id, bool isEnabled, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت شرایط هشدار رلهای (notificationType > 0) برای یک دستگاه
|
||||||
|
/// </summary>
|
||||||
|
Task<IReadOnlyList<RelayConditionDto>> GetRelayConditionsAsync(int deviceId, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت شرایط هشدار رلهای که بعد از تاریخ مشخصی بهروزرسانی شدهاند
|
||||||
|
/// اگر هیچ رکوردی بهروزرسانی نشده باشد، لیست خالی برمیگرداند
|
||||||
|
/// </summary>
|
||||||
|
Task<IReadOnlyList<RelayConditionDto>> GetUpdatedRelayConditionsAsync(int deviceId, DateTime? lastUpdateDate, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<bool> HasUpdatedConditionsAsync(int deviceId, DateTime? lastUpdateDate, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,11 @@ public sealed class DeviceSettings
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public SimCardType? SimCardType { get; set; }
|
public SimCardType? SimCardType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تعداد رلهها
|
||||||
|
/// </summary>
|
||||||
|
public int RelayCount { get; set; } = 4;
|
||||||
|
|
||||||
[StringLength(120)]
|
[StringLength(120)]
|
||||||
public string SmsRecievers { get; set; } = string.Empty;
|
public string SmsRecievers { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|||||||
@@ -127,5 +127,60 @@ public sealed class AlertConditionService : IAlertConditionService
|
|||||||
await dbContext.SaveChangesAsync(cancellationToken);
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<RelayConditionDto>> GetRelayConditionsAsync(int deviceId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var conditions = await dbContext.AlertConditions
|
||||||
|
.Include(x => x.Rules)
|
||||||
|
.Where(x => x.DeviceId == deviceId && (int)x.NotificationType > 0)
|
||||||
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var result = conditions.Select(c => new RelayConditionDto
|
||||||
|
{
|
||||||
|
Enabled = c.IsEnabled ? 1 : 0,
|
||||||
|
TimeType = (int)c.TimeType,
|
||||||
|
Relay = "R" + (int)c.NotificationType,
|
||||||
|
Rules = c.Rules.OrderBy(r => r.Order).Select(r => new RelayRuleDto
|
||||||
|
{
|
||||||
|
Sensor = ((int)r.SensorType).ToString(),
|
||||||
|
Op = ((int)r.ComparisonType).ToString(),
|
||||||
|
V1 = r.Value1,
|
||||||
|
V2 = r.Value2
|
||||||
|
}).ToList()
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<RelayConditionDto>> GetUpdatedRelayConditionsAsync(int deviceId, DateTime? lastUpdateDate, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// اگر lastUpdateDate null باشه، یعنی کلاینت تا حالا هیچ دادهای نگرفته، همه رو برگردون
|
||||||
|
if (lastUpdateDate == null)
|
||||||
|
{
|
||||||
|
return await GetRelayConditionsAsync(deviceId, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasUpdates = await dbContext.AlertConditions
|
||||||
|
.AnyAsync(x => x.DeviceId == deviceId && (int)x.NotificationType > 0 && x.UpdatedAt > lastUpdateDate, cancellationToken);
|
||||||
|
|
||||||
|
if (!hasUpdates)
|
||||||
|
{
|
||||||
|
return Array.Empty<RelayConditionDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await GetRelayConditionsAsync(deviceId, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> HasUpdatedConditionsAsync(int deviceId, DateTime? lastUpdateDate, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if(lastUpdateDate == null) return true;
|
||||||
|
|
||||||
|
var hasUpdates = await dbContext.AlertConditions
|
||||||
|
.AnyAsync(x => x.DeviceId == deviceId && (int)x.NotificationType > 0 && x.UpdatedAt > lastUpdateDate, cancellationToken);
|
||||||
|
|
||||||
|
return hasUpdates;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,16 +112,20 @@ public sealed class AlertService : IAlertService
|
|||||||
// Check if all rules match (AND logic)
|
// Check if all rules match (AND logic)
|
||||||
var allRulesMatch = condition.Rules.All(rule => CheckRule(rule, telemetry));
|
var allRulesMatch = condition.Rules.All(rule => CheckRule(rule, telemetry));
|
||||||
|
|
||||||
if (allRulesMatch && condition.Rules.Any())
|
if (allRulesMatch && condition.Rules.Count != 0)
|
||||||
{
|
{
|
||||||
// All rules passed, send alert if cooldown period has passed
|
// All rules passed, send alert if cooldown period has passed
|
||||||
// await SendAlertForConditionAsync(condition, device, usersToAlert, telemetry, cancellationToken);
|
// await SendAlertForConditionAsync(condition, device, usersToAlert, telemetry, cancellationToken);
|
||||||
string? message = await GetAlertMessage(condition, device, device.User, telemetry, cancellationToken);
|
AlertNotification notif = await GetAlertMessage(condition, device, device.User, telemetry, cancellationToken);
|
||||||
if(message != null && message.Length > 0)
|
if(notif != null && !string.IsNullOrEmpty(notif.Message))
|
||||||
messages.Add(message);
|
{
|
||||||
|
messages.Add(notif.Message);
|
||||||
|
await dbContext.AlertNotifications.AddAsync(notif, cancellationToken);
|
||||||
|
}
|
||||||
// return message;
|
// return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
if(telemetry.OldPower == 0 && telemetry.Power == 1)
|
if(telemetry.OldPower == 0 && telemetry.Power == 1)
|
||||||
{
|
{
|
||||||
@@ -163,7 +167,7 @@ public sealed class AlertService : IAlertService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string?> GetAlertMessage(
|
private async Task<AlertNotification> GetAlertMessage(
|
||||||
Domain.AlertCondition condition,
|
Domain.AlertCondition condition,
|
||||||
Domain.Device device,
|
Domain.Device device,
|
||||||
User user,
|
User user,
|
||||||
@@ -204,12 +208,12 @@ public sealed class AlertService : IAlertService
|
|||||||
//MessageOutboxIds = messageOutboxIds,
|
//MessageOutboxIds = messageOutboxIds,
|
||||||
//ErrorMessage = errorMessage,
|
//ErrorMessage = errorMessage,
|
||||||
SentAt = sentAt,
|
SentAt = sentAt,
|
||||||
IsSent = true
|
//IsSent = true
|
||||||
};
|
};
|
||||||
|
|
||||||
dbContext.AlertNotifications.Add(notification);
|
// dbContext.AlertNotifications.Add(notification);
|
||||||
|
|
||||||
return message;
|
return notification;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendAlertForConditionAsync(
|
private async Task SendAlertForConditionAsync(
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public sealed class GreenHomeDbContext : DbContext
|
|||||||
b.Property(x => x.MinimumCallIntervalMinutes).HasDefaultValue(60);
|
b.Property(x => x.MinimumCallIntervalMinutes).HasDefaultValue(60);
|
||||||
b.Property(x => x.AreaSquareMeters).HasColumnType("decimal(18,2)");
|
b.Property(x => x.AreaSquareMeters).HasColumnType("decimal(18,2)");
|
||||||
b.Property(x => x.SmsRecievers).HasMaxLength(120);
|
b.Property(x => x.SmsRecievers).HasMaxLength(120);
|
||||||
|
b.Property(x => x.RelayCount).HasDefaultValue(4);
|
||||||
b.HasOne(x => x.Device)
|
b.HasOne(x => x.Device)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey(x => x.DeviceId)
|
.HasForeignKey(x => x.DeviceId)
|
||||||
|
|||||||
1251
src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.Designer.cs
generated
Normal file
1251
src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace GreenHome.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddRelayCountToDeviceSettings : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "RelayCount",
|
||||||
|
table: "DeviceSettings",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "RelayCount",
|
||||||
|
table: "DeviceSettings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -623,6 +623,11 @@ namespace GreenHome.Infrastructure.Migrations
|
|||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("nvarchar(100)");
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<int>("RelayCount")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasDefaultValue(4);
|
||||||
|
|
||||||
b.Property<int?>("SimCardType")
|
b.Property<int?>("SimCardType")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user