refactor: restructure Program.cs and enhance Telemetry response format

This commit is contained in:
2026-06-01 18:16:30 +03:30
parent 97a946ebea
commit 4df537816f
2 changed files with 155 additions and 112 deletions

View File

@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using GreenHome.Application; using GreenHome.Application;
using System.Text;
namespace GreenHome.Api.Controllers; namespace GreenHome.Api.Controllers;
@@ -29,38 +30,66 @@ public class TelemetryController : ControllerBase
[HttpGet("AddData")] [HttpGet("AddData")]
public async Task<ActionResult<string>> Create([FromQuery] TelemetryDto telementry, [FromQuery] string? lastUpdateDate, CancellationToken cancellationToken) public async Task<ActionResult<string>> Create([FromQuery] TelemetryDto telementry, [FromQuery] string? lastUpdateDate, CancellationToken cancellationToken)
{ {
telementry.TimestampUtc = DateTime.UtcNow; telementry.TimestampUtc = DateTime.UtcNow;
var id = await telemetryService.AddAsync(telementry, cancellationToken); var id = await telemetryService.AddAsync(telementry, cancellationToken);
string? message; var parts = new List<string> { $"ID:{id}" };
var deviceId = telementry.DeviceId; var deviceId = telementry.DeviceId;
if(deviceId > 0)
if (deviceId > 0)
{ {
message = await alertService.CheckAndSendAlertsAsync(deviceId, telementry, cancellationToken); // 1. Check alerts and power status
if(!string.IsNullOrEmpty(message)) 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}");
} }
} }
// Check if relay conditions have been updated since lastUpdateDate // 2. Check relay conditions update
if (deviceId > 0 && !string.IsNullOrEmpty(lastUpdateDate)) if (!string.IsNullOrEmpty(lastUpdateDate))
{ {
var parsedDate = ParseLastUpdateDate(lastUpdateDate); var parsedDate = ParseLastUpdateDate(lastUpdateDate);
// var relayConditions = await alertConditionService.GetUpdatedRelayConditionsAsync(deviceId, parsedDate, cancellationToken); var relayConditions = await alertConditionService.GetUpdatedRelayConditionsAsync(deviceId, parsedDate, cancellationToken);
// if (relayConditions.Count > 0) if (relayConditions.Count > 0)
// { {
// return Ok(new { conditions = relayConditions }); parts.Add("RU:1");
// } parts.Add($"RL:{relayConditions.Count}");
parts.Add($"RC:{EncodeRelayConditions(relayConditions)}");
var hasUpdates = await alertConditionService.HasUpdatedConditionsAsync(deviceId, parsedDate, cancellationToken); }
if(hasUpdates) }
return Ok("Relay");
} }
return Ok(id + ""); return Ok(string.Join("|", parts));
}
/// <summary>
/// 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 &
/// </summary>
private static string EncodeRelayConditions(IReadOnlyList<RelayConditionDto> conditions)
{
return string.Join("|", conditions.Select(c =>
{
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}";
}));
} }
/// <summary> /// <summary>

View File

@@ -2,32 +2,63 @@ using FluentValidation;
using GreenHome.AI.DeepSeek; using GreenHome.AI.DeepSeek;
using GreenHome.Application; using GreenHome.Application;
using GreenHome.Infrastructure; using GreenHome.Infrastructure;
using GreenHome.Infrastructure.Data;
using GreenHome.Sms.Ippanel; using GreenHome.Sms.Ippanel;
using GreenHome.VoiceCall.Avanak; using GreenHome.VoiceCall.Avanak;
using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Scalar.AspNetCore; using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Add services to the container. // ============================================================
builder.Services.AddControllers(); // 1. Service Registration
builder.Services.AddEndpointsApiExplorer(); // ============================================================
builder.Services.AddOpenApi();
// Application/Infrastructure DI ConfigureCoreServices(builder);
builder.Services.AddAutoMapper(typeof(GreenHome.Application.MappingProfile)); ConfigureCors(builder);
builder.Services.AddValidatorsFromAssemblyContaining<GreenHome.Application.DeviceDtoValidator>(); ConfigureExternalServices(builder);
// CORS Configuration var app = builder.Build();
const string CorsPolicy = "DefaultCors";
builder.Services.AddCors(options => // ============================================================
// 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<GreenHome.Application.DeviceDtoValidator>();
builder.Services.AddInfrastructure(builder.Configuration);
}
void ConfigureCors(WebApplicationBuilder builder)
{
const string CorsPolicy = "DefaultCors";
builder.Services.AddCors(options =>
{
options.AddPolicy(CorsPolicy, policy => options.AddPolicy(CorsPolicy, policy =>
{ {
if (builder.Environment.IsDevelopment()) if (builder.Environment.IsDevelopment())
{ {
// در محیط Development همه origin ها مجاز هستند
policy policy
.AllowAnyOrigin() .AllowAnyOrigin()
.AllowAnyHeader() .AllowAnyHeader()
@@ -35,7 +66,6 @@ builder.Services.AddCors(options =>
} }
else else
{ {
// در محیط Production فقط دامنه‌های مشخص
policy policy
.WithOrigins( .WithOrigins(
"http://green.nabaksoft.ir", "http://green.nabaksoft.ir",
@@ -48,46 +78,28 @@ builder.Services.AddCors(options =>
.AllowCredentials(); .AllowCredentials();
} }
}); });
}); });
}
builder.Services.AddDbContext<GreenHome.Infrastructure.GreenHomeDbContext>(options => void ConfigureExternalServices(WebApplicationBuilder builder)
{ {
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")); builder.Services.AddIppanelSms(builder.Configuration);
}); builder.Services.AddAvanakVoiceCall(builder.Configuration);
builder.Services.AddScoped<GreenHome.Application.IDeviceService, GreenHome.Infrastructure.DeviceService>(); builder.Services.AddDeepSeek(builder.Configuration);
builder.Services.AddScoped<GreenHome.Application.ITelemetryService, GreenHome.Infrastructure.TelemetryService>(); }
builder.Services.AddScoped<GreenHome.Application.IDeviceSettingsService, GreenHome.Infrastructure.DeviceSettingsService>();
builder.Services.AddScoped<GreenHome.Application.IAuthService, GreenHome.Infrastructure.AuthService>();
builder.Services.AddScoped<GreenHome.Application.IAlertService, GreenHome.Infrastructure.AlertService>();
builder.Services.AddScoped<GreenHome.Application.IAlertConditionService, GreenHome.Infrastructure.AlertConditionService>();
builder.Services.AddScoped<GreenHome.Application.ISunCalculatorService, GreenHome.Infrastructure.SunCalculatorService>();
builder.Services.AddScoped<GreenHome.Application.IAIQueryService, GreenHome.Infrastructure.AIQueryService>();
builder.Services.AddScoped<GreenHome.Application.IDailyReportService, GreenHome.Infrastructure.DailyReportService>();
builder.Services.AddScoped<GreenHome.Application.IAlertLogService, GreenHome.Infrastructure.AlertLogService>();
builder.Services.AddScoped<GreenHome.Application.IUserDailyReportService, GreenHome.Infrastructure.UserDailyReportService>();
builder.Services.AddScoped<GreenHome.Application.IChecklistService, GreenHome.Infrastructure.ChecklistService>();
builder.Services.AddScoped<GreenHome.Application.IMonthlyReportService, GreenHome.Infrastructure.MonthlyReportService>();
builder.Services.AddScoped<GreenHome.Application.IDevicePostService, GreenHome.Infrastructure.DevicePostService>();
builder.Services.AddScoped<GreenHome.Application.IDeviceTokenService, GreenHome.Infrastructure.DeviceTokenService>();
// SMS Service Configuration // ============================================================
builder.Services.AddIppanelSms(builder.Configuration); // Middleware & Pipeline Methods
// ============================================================
// Voice Call Service Configuration void ApplyMigrations(WebApplication app)
builder.Services.AddAvanakVoiceCall(builder.Configuration);
// AI Service Configuration
builder.Services.AddDeepSeek(builder.Configuration);
var app = builder.Build();
// Apply pending migrations automatically
using (var scope = app.Services.CreateScope())
{ {
using var scope = app.Services.CreateScope();
var services = scope.ServiceProvider; var services = scope.ServiceProvider;
try try
{ {
var context = services.GetRequiredService<GreenHome.Infrastructure.GreenHomeDbContext>(); var context = services.GetRequiredService<GreenHomeDbContext>();
context.Database.Migrate(); context.Database.Migrate();
} }
catch (Exception ex) catch (Exception ex)
@@ -97,28 +109,30 @@ using (var scope = app.Services.CreateScope())
} }
} }
// Configure the HTTP request pipeline. void ConfigureMiddleware(WebApplication app)
app.MapOpenApi();
app.MapScalarApiReference();
if (!app.Environment.IsDevelopment())
{ {
app.MapOpenApi();
app.MapScalarApiReference();
if (!app.Environment.IsDevelopment())
{
app.UseHttpsRedirection(); app.UseHttpsRedirection();
}
app.UseCors("DefaultCors");
app.UseAuthorization();
} }
app.UseCors(CorsPolicy); void ConfigureStaticFiles(WebApplication app)
app.UseAuthorization();
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".amr"] = "audio/amr";
app.UseStaticFiles(new StaticFileOptions
{ {
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".amr"] = "audio/amr";
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider( FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "My_StaticFiles")), Path.Combine(app.Environment.ContentRootPath, "My_StaticFiles")),
RequestPath = "/My_StaticFiles", RequestPath = "/My_StaticFiles",
ContentTypeProvider = provider ContentTypeProvider = provider
}); });
}
app.MapControllers();
app.Run();