add voice service call service and more
This commit is contained in:
319
src/GreenHome.Infrastructure/AlertService.cs
Normal file
319
src/GreenHome.Infrastructure/AlertService.cs
Normal file
@@ -0,0 +1,319 @@
|
||||
using GreenHome.Application;
|
||||
using GreenHome.Sms.Ippanel;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text.Json;
|
||||
using static GreenHome.Sms.Ippanel.IppanelSmsService;
|
||||
|
||||
namespace GreenHome.Infrastructure;
|
||||
|
||||
public sealed class AlertService : IAlertService
|
||||
{
|
||||
private readonly GreenHomeDbContext dbContext;
|
||||
private readonly IDeviceSettingsService deviceSettingsService;
|
||||
private readonly ISmsService smsService;
|
||||
private readonly ILogger<AlertService> logger;
|
||||
private const int AlertCooldownMinutes = 10;
|
||||
|
||||
private sealed record AlertInfo(
|
||||
string Type,
|
||||
string Message,
|
||||
string ParameterName,
|
||||
decimal Value,
|
||||
string Status
|
||||
);
|
||||
|
||||
public AlertService(
|
||||
GreenHomeDbContext dbContext,
|
||||
IDeviceSettingsService deviceSettingsService,
|
||||
ISmsService smsService,
|
||||
ILogger<AlertService> logger)
|
||||
{
|
||||
this.dbContext = dbContext;
|
||||
this.deviceSettingsService = deviceSettingsService;
|
||||
this.smsService = smsService;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public async Task CheckAndSendAlertsAsync(int deviceId, TelemetryDto telemetry, CancellationToken cancellationToken)
|
||||
{
|
||||
var settings = await deviceSettingsService.GetByDeviceIdAsync(deviceId, cancellationToken);
|
||||
if (settings == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var device = await dbContext.Devices
|
||||
.Include(d => d.User)
|
||||
.FirstOrDefaultAsync(d => d.Id == deviceId, cancellationToken);
|
||||
|
||||
if (device == null || device.User == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var alerts = CollectAlerts(telemetry, settings, device.DeviceName);
|
||||
|
||||
foreach (var alert in alerts)
|
||||
{
|
||||
await SendAlertIfNeededAsync(deviceId, device.User.Id, device.DeviceName, alert, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private List<AlertInfo> CollectAlerts(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName)
|
||||
{
|
||||
var alerts = new List<AlertInfo>();
|
||||
|
||||
CheckTemperatureAlert(telemetry, settings, deviceName, alerts);
|
||||
CheckHumidityAlert(telemetry, settings, deviceName, alerts);
|
||||
CheckSoilAlert(telemetry, deviceName, alerts);
|
||||
CheckGasAlert(telemetry, settings, deviceName, alerts);
|
||||
CheckLuxAlert(telemetry, settings, deviceName, alerts);
|
||||
|
||||
return alerts;
|
||||
}
|
||||
|
||||
private void CheckTemperatureAlert(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName, List<AlertInfo> alerts)
|
||||
{
|
||||
if (telemetry.TemperatureC > settings.MaxTemperature)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Temperature",
|
||||
Message: $"هشدار: دمای گلخانه {deviceName} به {telemetry.TemperatureC} درجه رسیده که از حداکثر مجاز ({settings.MaxTemperature}) بیشتر است.",
|
||||
ParameterName: "دما",
|
||||
Value: telemetry.TemperatureC,
|
||||
Status: "بالاتر"
|
||||
));
|
||||
}
|
||||
else if (telemetry.TemperatureC < settings.MinTemperature)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Temperature",
|
||||
Message: $"هشدار: دمای گلخانه {deviceName} به {telemetry.TemperatureC} درجه رسیده که از حداقل مجاز ({settings.MinTemperature}) کمتر است.",
|
||||
ParameterName: "دما",
|
||||
Value: telemetry.TemperatureC,
|
||||
Status: "پایینتر"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckHumidityAlert(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName, List<AlertInfo> alerts)
|
||||
{
|
||||
if (telemetry.HumidityPercent > settings.MaxHumidityPercent)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Humidity",
|
||||
Message: $"هشدار: رطوبت گلخانه {deviceName} به {telemetry.HumidityPercent}% رسیده که از حداکثر مجاز ({settings.MaxHumidityPercent}%) بیشتر است.",
|
||||
ParameterName: "رطوبت",
|
||||
Value: telemetry.HumidityPercent,
|
||||
Status: "بالاتر"
|
||||
));
|
||||
}
|
||||
else if (telemetry.HumidityPercent < settings.MinHumidityPercent)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Humidity",
|
||||
Message: $"هشدار: رطوبت گلخانه {deviceName} به {telemetry.HumidityPercent}% رسیده که از حداقل مجاز ({settings.MinHumidityPercent}%) کمتر است.",
|
||||
ParameterName: "رطوبت",
|
||||
Value: telemetry.HumidityPercent,
|
||||
Status: "پایینتر"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckSoilAlert(TelemetryDto telemetry, string deviceName, List<AlertInfo> alerts)
|
||||
{
|
||||
if (telemetry.SoilPercent > 100)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Soil",
|
||||
Message: $"هشدار: رطوبت خاک گلخانه {deviceName} مقدار نامعتبر ({telemetry.SoilPercent}%) دارد.",
|
||||
ParameterName: "رطوبت خاک",
|
||||
Value: telemetry.SoilPercent,
|
||||
Status: "بالاتر"
|
||||
));
|
||||
}
|
||||
else if (telemetry.SoilPercent < 0)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Soil",
|
||||
Message: $"هشدار: رطوبت خاک گلخانه {deviceName} مقدار نامعتبر ({telemetry.SoilPercent}%) دارد.",
|
||||
ParameterName: "رطوبت خاک",
|
||||
Value: telemetry.SoilPercent,
|
||||
Status: "پایینتر"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckGasAlert(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName, List<AlertInfo> alerts)
|
||||
{
|
||||
if (telemetry.GasPPM > settings.MaxGasPPM)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Gas",
|
||||
Message: $"هشدار: گاز گلخانه {deviceName} به {telemetry.GasPPM} PPM رسیده که از حداکثر مجاز ({settings.MaxGasPPM}) بیشتر است.",
|
||||
ParameterName: "گاز Co",
|
||||
Value: telemetry.GasPPM,
|
||||
Status: "بالاتر"
|
||||
));
|
||||
}
|
||||
else if (telemetry.GasPPM < settings.MinGasPPM)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Gas",
|
||||
Message: $"هشدار: گاز گلخانه {deviceName} به {telemetry.GasPPM} PPM رسیده که از حداقل مجاز ({settings.MinGasPPM}) کمتر است.",
|
||||
ParameterName: "گاز Co",
|
||||
Value: telemetry.GasPPM,
|
||||
Status: "پایینتر"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckLuxAlert(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName, List<AlertInfo> alerts)
|
||||
{
|
||||
if (telemetry.Lux > settings.MaxLux)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Lux",
|
||||
Message: $"هشدار: نور گلخانه {deviceName} به {telemetry.Lux} لوکس رسیده که از حداکثر مجاز ({settings.MaxLux}) بیشتر است.",
|
||||
ParameterName: "نور",
|
||||
Value: telemetry.Lux,
|
||||
Status: "بالاتر"
|
||||
));
|
||||
}
|
||||
else if (telemetry.Lux < settings.MinLux)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Lux",
|
||||
Message: $"هشدار: نور گلخانه {deviceName} به {telemetry.Lux} لوکس رسیده که از حداقل مجاز ({settings.MinLux}) کمتر است.",
|
||||
ParameterName: "نور",
|
||||
Value: telemetry.Lux,
|
||||
Status: "پایینتر"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendAlertIfNeededAsync(
|
||||
int deviceId,
|
||||
int userId,
|
||||
string deviceName,
|
||||
AlertInfo alert,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Check if alert was sent in the last 10 minutes
|
||||
var cooldownTime = DateTime.UtcNow.AddMinutes(-AlertCooldownMinutes);
|
||||
var recentAlert = await dbContext.AlertNotifications
|
||||
.Where(a => a.DeviceId == deviceId &&
|
||||
a.UserId == userId &&
|
||||
a.AlertType == alert.Type &&
|
||||
a.SentAt >= cooldownTime)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (recentAlert != null)
|
||||
{
|
||||
logger.LogInformation("Alert skipped due to cooldown: DeviceId={DeviceId}, AlertType={AlertType}", deviceId, alert.Type);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get user to send SMS
|
||||
var user = await dbContext.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
|
||||
|
||||
if (user == null || string.IsNullOrWhiteSpace(user.Mobile))
|
||||
{
|
||||
logger.LogWarning("User not found or mobile is empty: UserId={UserId}", userId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send SMS and collect response/errors
|
||||
string? messageOutboxIdsJson = null;
|
||||
string? errorMessage = null;
|
||||
bool isSent = false;
|
||||
|
||||
try
|
||||
{
|
||||
var smsResponse = await smsService.SendPatternSmsAsync(new PatternSmsRequest
|
||||
{
|
||||
Recipients = [user.Mobile],
|
||||
PatternCode = "64di3w9kb0fxvif",
|
||||
Variables = new Dictionary<string, string> {
|
||||
{ "name", deviceName },
|
||||
{ "parameter", alert.ParameterName },
|
||||
{ "value", alert.Value.ToString("F1") },
|
||||
{ "status", alert.Status },
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
if (smsResponse != null)
|
||||
{
|
||||
// Check if SMS was sent successfully
|
||||
if (smsResponse.Meta.Status && smsResponse.Data != null && smsResponse.Data.MessageOutboxIds != null && smsResponse.Data.MessageOutboxIds.Count > 0)
|
||||
{
|
||||
// Success - save message outbox IDs
|
||||
messageOutboxIdsJson = JsonSerializer.Serialize(smsResponse.Data.MessageOutboxIds);
|
||||
isSent = true;
|
||||
logger.LogInformation("Alert SMS sent: DeviceId={DeviceId}, UserId={UserId}, AlertType={AlertType}, OutboxIds={OutboxIds}",
|
||||
deviceId, userId, alert.Type, messageOutboxIdsJson);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Failed - save error from meta
|
||||
var errors = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(smsResponse.Meta.Message))
|
||||
{
|
||||
errors.Add(smsResponse.Meta.Message);
|
||||
}
|
||||
if (smsResponse.Meta.Errors != null && smsResponse.Meta.Errors.Count > 0)
|
||||
{
|
||||
foreach (var error in smsResponse.Meta.Errors)
|
||||
{
|
||||
errors.Add($"{error.Key}: {string.Join(", ", error.Value)}");
|
||||
}
|
||||
}
|
||||
if (errors.Count == 0)
|
||||
{
|
||||
errors.Add("SMS sending failed with unknown error");
|
||||
}
|
||||
errorMessage = string.Join(" | ", errors);
|
||||
isSent = false;
|
||||
logger.LogWarning("Alert SMS failed: DeviceId={DeviceId}, UserId={UserId}, AlertType={AlertType}, Error={Error}",
|
||||
deviceId, userId, alert.Type, errorMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = "SMS service returned null response";
|
||||
isSent = false;
|
||||
logger.LogWarning("Alert SMS returned null: DeviceId={DeviceId}, UserId={UserId}, AlertType={AlertType}",
|
||||
deviceId, userId, alert.Type);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = $"Exception: {ex.Message}";
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
errorMessage += $" | InnerException: {ex.InnerException.Message}";
|
||||
}
|
||||
isSent = false;
|
||||
logger.LogError(ex, "Failed to send alert SMS: DeviceId={DeviceId}, UserId={UserId}, AlertType={AlertType}", deviceId, userId, alert.Type);
|
||||
}
|
||||
|
||||
// Save notification to database
|
||||
var notification = new Domain.AlertNotification
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
UserId = userId,
|
||||
AlertType = alert.Type,
|
||||
Message = alert.Message,
|
||||
MessageOutboxIds = messageOutboxIdsJson,
|
||||
ErrorMessage = errorMessage,
|
||||
SentAt = DateTime.UtcNow,
|
||||
IsSent = isSent
|
||||
};
|
||||
|
||||
dbContext.AlertNotifications.Add(notification);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user