From 4df537816f9acbec465a02676bcda83724b6fd90 Mon Sep 17 00:00:00 2001 From: alireza Date: Mon, 1 Jun 2026 18:16:30 +0330 Subject: [PATCH] refactor: restructure Program.cs and enhance Telemetry response format --- .../Controllers/TelemetryController.cs | 73 +++++-- src/GreenHome.Api/Program.cs | 194 ++++++++++-------- 2 files changed, 155 insertions(+), 112 deletions(-) diff --git a/src/GreenHome.Api/Controllers/TelemetryController.cs b/src/GreenHome.Api/Controllers/TelemetryController.cs index f519120..5a6ba73 100644 --- a/src/GreenHome.Api/Controllers/TelemetryController.cs +++ b/src/GreenHome.Api/Controllers/TelemetryController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using GreenHome.Application; +using System.Text; namespace GreenHome.Api.Controllers; @@ -29,38 +30,66 @@ public class TelemetryController : ControllerBase [HttpGet("AddData")] public async Task> Create([FromQuery] TelemetryDto telementry, [FromQuery] string? lastUpdateDate, CancellationToken cancellationToken) { - telementry.TimestampUtc = DateTime.UtcNow; var id = await telemetryService.AddAsync(telementry, cancellationToken); - string? message; - + var parts = new List { $"ID:{id}" }; var deviceId = telementry.DeviceId; - if(deviceId > 0) + + if (deviceId > 0) { - message = await alertService.CheckAndSendAlertsAsync(deviceId, telementry, cancellationToken); - if(!string.IsNullOrEmpty(message)) + // 1. Check alerts and power status + var alertResult = await alertService.CheckAndSendAlertsAsync(deviceId, telementry, cancellationToken); + if (alertResult != null) { - return Ok(message); + if (alertResult.Messages.Count > 0) + { + parts.Add($"A:{alertResult.Messages.Count}"); + parts.Add($"R:{alertResult.Recipients}"); + parts.Add($"M:{string.Join("@", alertResult.Messages)}"); + } + + if (alertResult.PowerStatus != null) + { + parts.Add($"P:{alertResult.PowerStatus}"); + } + } + + // 2. Check relay conditions update + if (!string.IsNullOrEmpty(lastUpdateDate)) + { + var parsedDate = ParseLastUpdateDate(lastUpdateDate); + var relayConditions = await alertConditionService.GetUpdatedRelayConditionsAsync(deviceId, parsedDate, cancellationToken); + if (relayConditions.Count > 0) + { + parts.Add("RU:1"); + parts.Add($"RL:{relayConditions.Count}"); + parts.Add($"RC:{EncodeRelayConditions(relayConditions)}"); + } } } - // Check if relay conditions have been updated since lastUpdateDate - if (deviceId > 0 && !string.IsNullOrEmpty(lastUpdateDate)) + return Ok(string.Join("|", parts)); + } + + /// + /// Encode relay conditions into a compact string format for STM32 parsing. + /// Format: E{enabled}T{timeType}R{relay}S{sensor}O{op}V{value1}[,{value2}] + /// Multiple conditions separated by |, multiple rules within a condition separated by & + /// + private static string EncodeRelayConditions(IReadOnlyList conditions) + { + return string.Join("|", conditions.Select(c => { - var parsedDate = ParseLastUpdateDate(lastUpdateDate); - // var relayConditions = await alertConditionService.GetUpdatedRelayConditionsAsync(deviceId, parsedDate, cancellationToken); - // if (relayConditions.Count > 0) - // { - // return Ok(new { conditions = relayConditions }); - // } - - var hasUpdates = await alertConditionService.HasUpdatedConditionsAsync(deviceId, parsedDate, cancellationToken); - if(hasUpdates) - return Ok("Relay"); - } - - return Ok(id + ""); + var rules = string.Join("&", c.Rules.Select(r => + { + var rule = $"S{r.Sensor}O{r.Op}V{r.V1}"; + if (r.V2.HasValue) + rule += $",{r.V2.Value}"; + return rule; + })); + return $"E{c.Enabled}T{c.TimeType}R{c.Relay}{rules}"; + })); } /// diff --git a/src/GreenHome.Api/Program.cs b/src/GreenHome.Api/Program.cs index 1e92c0d..3df7dbe 100644 --- a/src/GreenHome.Api/Program.cs +++ b/src/GreenHome.Api/Program.cs @@ -2,92 +2,104 @@ using FluentValidation; using GreenHome.AI.DeepSeek; using GreenHome.Application; using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using GreenHome.Sms.Ippanel; using GreenHome.VoiceCall.Avanak; using Microsoft.AspNetCore.StaticFiles; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.FileProviders; using Scalar.AspNetCore; + var builder = WebApplication.CreateBuilder(args); -// Add services to the container. -builder.Services.AddControllers(); -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddOpenApi(); +// ============================================================ +// 1. Service Registration +// ============================================================ -// Application/Infrastructure DI -builder.Services.AddAutoMapper(typeof(GreenHome.Application.MappingProfile)); -builder.Services.AddValidatorsFromAssemblyContaining(); - -// CORS Configuration -const string CorsPolicy = "DefaultCors"; -builder.Services.AddCors(options => -{ - options.AddPolicy(CorsPolicy, policy => - { - if (builder.Environment.IsDevelopment()) - { - // در محیط Development همه origin ها مجاز هستند - policy - .AllowAnyOrigin() - .AllowAnyHeader() - .AllowAnyMethod(); - } - else - { - // در محیط Production فقط دامنه‌های مشخص - policy - .WithOrigins( - "http://green.nabaksoft.ir", - "https://green.nabaksoft.ir", - "http://gh1.nabaksoft.ir", - "https://gh1.nabaksoft.ir" - ) - .AllowAnyHeader() - .AllowAnyMethod() - .AllowCredentials(); - } - }); -}); - -builder.Services.AddDbContext(options => -{ - options.UseSqlServer(builder.Configuration.GetConnectionString("Default")); -}); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); - -// SMS Service Configuration -builder.Services.AddIppanelSms(builder.Configuration); - -// Voice Call Service Configuration -builder.Services.AddAvanakVoiceCall(builder.Configuration); - -// AI Service Configuration -builder.Services.AddDeepSeek(builder.Configuration); +ConfigureCoreServices(builder); +ConfigureCors(builder); +ConfigureExternalServices(builder); var app = builder.Build(); -// Apply pending migrations automatically -using (var scope = app.Services.CreateScope()) +// ============================================================ +// 2. Pipeline Configuration +// ============================================================ + +ApplyMigrations(app); +ConfigureMiddleware(app); +ConfigureStaticFiles(app); +app.MapControllers(); + +app.Run(); + +// ============================================================ +// Service Registration Methods +// ============================================================ + +void ConfigureCoreServices(WebApplicationBuilder builder) { + builder.Services.AddControllers(); + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddOpenApi(); + + builder.Services.AddAutoMapper(typeof(GreenHome.Application.MappingProfile)); + builder.Services.AddValidatorsFromAssemblyContaining(); + + builder.Services.AddInfrastructure(builder.Configuration); +} + +void ConfigureCors(WebApplicationBuilder builder) +{ + const string CorsPolicy = "DefaultCors"; + + builder.Services.AddCors(options => + { + options.AddPolicy(CorsPolicy, policy => + { + if (builder.Environment.IsDevelopment()) + { + policy + .AllowAnyOrigin() + .AllowAnyHeader() + .AllowAnyMethod(); + } + else + { + policy + .WithOrigins( + "http://green.nabaksoft.ir", + "https://green.nabaksoft.ir", + "http://gh1.nabaksoft.ir", + "https://gh1.nabaksoft.ir" + ) + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + } + }); + }); +} + +void ConfigureExternalServices(WebApplicationBuilder builder) +{ + builder.Services.AddIppanelSms(builder.Configuration); + builder.Services.AddAvanakVoiceCall(builder.Configuration); + builder.Services.AddDeepSeek(builder.Configuration); +} + +// ============================================================ +// Middleware & Pipeline Methods +// ============================================================ + +void ApplyMigrations(WebApplication app) +{ + using var scope = app.Services.CreateScope(); var services = scope.ServiceProvider; + try { - var context = services.GetRequiredService(); + var context = services.GetRequiredService(); context.Database.Migrate(); } catch (Exception ex) @@ -97,28 +109,30 @@ using (var scope = app.Services.CreateScope()) } } -// Configure the HTTP request pipeline. -app.MapOpenApi(); -app.MapScalarApiReference(); -if (!app.Environment.IsDevelopment()) +void ConfigureMiddleware(WebApplication app) { - app.UseHttpsRedirection(); + app.MapOpenApi(); + app.MapScalarApiReference(); + + if (!app.Environment.IsDevelopment()) + { + app.UseHttpsRedirection(); + } + + app.UseCors("DefaultCors"); + app.UseAuthorization(); } -app.UseCors(CorsPolicy); -app.UseAuthorization(); - -var provider = new FileExtensionContentTypeProvider(); -provider.Mappings[".amr"] = "audio/amr"; - -app.UseStaticFiles(new StaticFileOptions +void ConfigureStaticFiles(WebApplication app) { - FileProvider = new PhysicalFileProvider( - Path.Combine(builder.Environment.ContentRootPath, "My_StaticFiles")), - RequestPath = "/My_StaticFiles", - ContentTypeProvider = provider -}); + var provider = new FileExtensionContentTypeProvider(); + provider.Mappings[".amr"] = "audio/amr"; -app.MapControllers(); - -app.Run(); \ No newline at end of file + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider( + Path.Combine(app.Environment.ContentRootPath, "My_StaticFiles")), + RequestPath = "/My_StaticFiles", + ContentTypeProvider = provider + }); +}