From f60e85c6a8c4c53b36d64c37c4c193cfbfc0c53b Mon Sep 17 00:00:00 2001 From: ali313 Date: Tue, 13 Oct 2026 15:49:24 +0330 Subject: [PATCH] Update src/GreenHome.Api/Controllers/TelemetryController.cs Temporary: Reset device --- .../Controllers/TelemetryController.cs | 392 +++++++++--------- 1 file changed, 196 insertions(+), 196 deletions(-) diff --git a/src/GreenHome.Api/Controllers/TelemetryController.cs b/src/GreenHome.Api/Controllers/TelemetryController.cs index 4672181..1a7e198 100644 --- a/src/GreenHome.Api/Controllers/TelemetryController.cs +++ b/src/GreenHome.Api/Controllers/TelemetryController.cs @@ -1,196 +1,196 @@ -using Microsoft.AspNetCore.Mvc; -using GreenHome.Application; -using Microsoft.AspNetCore.Http; -using System.Text; -using System.Text.RegularExpressions; - -namespace GreenHome.Api.Controllers; - -[ApiController] -[Route("api/[controller]")] -public class TelemetryController : ControllerBase -{ - private readonly ITelemetryService telemetryService; - private readonly IAlertService alertService; - private readonly IAlertConditionService alertConditionService; - - public TelemetryController(ITelemetryService telemetryService, IAlertService alertService, IAlertConditionService alertConditionService) - { - this.telemetryService = telemetryService; - this.alertService = alertService; - this.alertConditionService = alertConditionService; - } - - [HttpGet] - public async Task>> List([FromQuery] int? deviceId, [FromQuery] DateTime? startUtc, [FromQuery] DateTime? endUtc, [FromQuery] DateTime? sinceUtc, [FromQuery] int page = 1, [FromQuery] int pageSize = 20, CancellationToken cancellationToken = default) - { - var filter = new TelemetryFilter { DeviceId = deviceId, StartDateUtc = startUtc, EndDateUtc = endUtc, SinceUtc = sinceUtc, Page = page, PageSize = pageSize }; - var result = await telemetryService.ListAsync(filter, cancellationToken); - return Ok(result); - } - - [HttpGet("AddData")] - public async Task> Create([FromQuery] TelemetryDto telementry, [FromQuery] string? lastUpdateDate, CancellationToken cancellationToken) - { - telementry.TimestampUtc = DateTime.UtcNow; - telementry.RelayStates = ParseRelayStates(Request.Query); - var id = await telemetryService.AddAsync(telementry, cancellationToken); - - var parts = new List { $"ID:{id}" }; - var deviceId = telementry.DeviceId; - - if (deviceId > 0) - { - // 1. Check alerts and power status - var alertResult = await alertService.CheckAndSendAlertsAsync(deviceId, telementry, cancellationToken); - if (alertResult != null) - { - 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)}"); - } - } - } - - return Ok(string.Join("|", parts)); - } - - /// - /// Encode relay conditions into a compact string format for STM32 parsing. - /// Format (relays separated by |): - /// E{enabled}R{relayIndex}{groups} - /// Group (OR between groups): - /// G{action} where action = 0 (turn on) | 1 (turn off) - /// followed by its conditions joined by & (AND inside a group): - /// Sensor condition: S{sensor}O{op}V{value1}[,{value2}] - /// Time condition: T{start}-{end} (minutes from midnight) - /// Time ranges with start > end cross midnight (e.g. T1200-360 = 20:00..06:00); - /// the device must treat that as (now >= start || now < end). - /// - private static string EncodeRelayConditions(IReadOnlyList conditions) - { - return string.Join("|", conditions.Select(c => - { - var groups = new List(); - groups.AddRange(c.On.Select(g => EncodeGroup(g, 0))); - groups.AddRange(c.Off.Select(g => EncodeGroup(g, 1))); - return $"E{c.Enabled}R{c.RelayIndex}{string.Concat(groups)}"; - })); - } - - private static string EncodeGroup(RelayGroupDto group, int action) - { - return $"G{action}{string.Join("&", group.Conditions.Select(EncodeCondition))}"; - } - - private static string EncodeCondition(RelayRuleDto condition) - { - if (condition.Kind == 1) - { - return $"T{condition.Start ?? 0}-{condition.End ?? 0}"; - } - - var encoded = $"S{condition.Sensor}O{condition.Op}V{condition.V1}"; - if (condition.V2.HasValue) - encoded += $",{condition.V2.Value}"; - - return encoded; - } - - /// - /// Collect relay states from query params r1, r2, ... (missing relay = off/0). - /// - private static string? ParseRelayStates(IQueryCollection query) - { - var entries = new List<(int Index, string Value)>(); - foreach (var key in query.Keys) - { - var match = Regex.Match(key, @"^r(\d+)$", RegexOptions.IgnoreCase); - if (!match.Success || !int.TryParse(match.Groups[1].Value, out var index) || index <= 0) - continue; - - entries.Add((index, query[key].ToString() == "1" ? "1" : "0")); - } - - if (entries.Count == 0) - return null; - - var states = new string[entries.Max(e => e.Index)]; - Array.Fill(states, "0"); - foreach (var entry in entries) - states[entry.Index - 1] = entry.Value; - - return string.Join(",", states); - } - - /// - /// Parse date string in format: yyyy/MM/dd-HH:mm - /// - 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")] - public async Task> MinMax([FromQuery] int deviceId, [FromQuery] DateTime? startUtc, [FromQuery] DateTime? endUtc, CancellationToken cancellationToken) - { - var result = await telemetryService.GetMinMaxAsync(deviceId, startUtc, endUtc, cancellationToken); - return Ok(result); - } - - [HttpGet("days")] - public async Task>> Days([FromQuery] int deviceId, [FromQuery] int year, [FromQuery] int month, CancellationToken cancellationToken) - { - var result = await telemetryService.GetMonthDaysAsync(deviceId, year, month, cancellationToken); - return Ok(result); - } - - [HttpGet("months")] - public async Task>> Months([FromQuery] int deviceId, [FromQuery] int year, CancellationToken cancellationToken) - { - var result = await telemetryService.GetActiveMonthsAsync(deviceId, year, cancellationToken); - return Ok(result); - } - - [HttpGet("aggregate")] - public async Task>> Aggregate([FromQuery] int deviceId, [FromQuery] DateTime? startUtc, [FromQuery] DateTime? endUtc, [FromQuery] int bucketMinutes = 15, CancellationToken cancellationToken = default) - { - var result = await telemetryService.GetAggregatedAsync(deviceId, startUtc, endUtc, bucketMinutes, cancellationToken); - return Ok(result); - } -} +using Microsoft.AspNetCore.Mvc; +using GreenHome.Application; +using Microsoft.AspNetCore.Http; +using System.Text; +using System.Text.RegularExpressions; + +namespace GreenHome.Api.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class TelemetryController : ControllerBase +{ + private readonly ITelemetryService telemetryService; + private readonly IAlertService alertService; + private readonly IAlertConditionService alertConditionService; + + public TelemetryController(ITelemetryService telemetryService, IAlertService alertService, IAlertConditionService alertConditionService) + { + this.telemetryService = telemetryService; + this.alertService = alertService; + this.alertConditionService = alertConditionService; + } + + [HttpGet] + public async Task>> List([FromQuery] int? deviceId, [FromQuery] DateTime? startUtc, [FromQuery] DateTime? endUtc, [FromQuery] DateTime? sinceUtc, [FromQuery] int page = 1, [FromQuery] int pageSize = 20, CancellationToken cancellationToken = default) + { + var filter = new TelemetryFilter { DeviceId = deviceId, StartDateUtc = startUtc, EndDateUtc = endUtc, SinceUtc = sinceUtc, Page = page, PageSize = pageSize }; + var result = await telemetryService.ListAsync(filter, cancellationToken); + return Ok(result); + } + + [HttpGet("AddData")] + public async Task> Create([FromQuery] TelemetryDto telementry, [FromQuery] string? lastUpdateDate, CancellationToken cancellationToken) + { + telementry.TimestampUtc = DateTime.UtcNow; + telementry.RelayStates = ParseRelayStates(Request.Query); + var id = await telemetryService.AddAsync(telementry, cancellationToken); + + var parts = new List { $"ID:{id}" }; + var deviceId = telementry.DeviceId; + return Ok("RST"); + if (deviceId > 0) + { + // 1. Check alerts and power status + var alertResult = await alertService.CheckAndSendAlertsAsync(deviceId, telementry, cancellationToken); + if (alertResult != null) + { + 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)}"); + } + } + } + + return Ok(string.Join("|", parts)); + } + + /// + /// Encode relay conditions into a compact string format for STM32 parsing. + /// Format (relays separated by |): + /// E{enabled}R{relayIndex}{groups} + /// Group (OR between groups): + /// G{action} where action = 0 (turn on) | 1 (turn off) + /// followed by its conditions joined by & (AND inside a group): + /// Sensor condition: S{sensor}O{op}V{value1}[,{value2}] + /// Time condition: T{start}-{end} (minutes from midnight) + /// Time ranges with start > end cross midnight (e.g. T1200-360 = 20:00..06:00); + /// the device must treat that as (now >= start || now < end). + /// + private static string EncodeRelayConditions(IReadOnlyList conditions) + { + return string.Join("|", conditions.Select(c => + { + var groups = new List(); + groups.AddRange(c.On.Select(g => EncodeGroup(g, 0))); + groups.AddRange(c.Off.Select(g => EncodeGroup(g, 1))); + return $"E{c.Enabled}R{c.RelayIndex}{string.Concat(groups)}"; + })); + } + + private static string EncodeGroup(RelayGroupDto group, int action) + { + return $"G{action}{string.Join("&", group.Conditions.Select(EncodeCondition))}"; + } + + private static string EncodeCondition(RelayRuleDto condition) + { + if (condition.Kind == 1) + { + return $"T{condition.Start ?? 0}-{condition.End ?? 0}"; + } + + var encoded = $"S{condition.Sensor}O{condition.Op}V{condition.V1}"; + if (condition.V2.HasValue) + encoded += $",{condition.V2.Value}"; + + return encoded; + } + + /// + /// Collect relay states from query params r1, r2, ... (missing relay = off/0). + /// + private static string? ParseRelayStates(IQueryCollection query) + { + var entries = new List<(int Index, string Value)>(); + foreach (var key in query.Keys) + { + var match = Regex.Match(key, @"^r(\d+)$", RegexOptions.IgnoreCase); + if (!match.Success || !int.TryParse(match.Groups[1].Value, out var index) || index <= 0) + continue; + + entries.Add((index, query[key].ToString() == "1" ? "1" : "0")); + } + + if (entries.Count == 0) + return null; + + var states = new string[entries.Max(e => e.Index)]; + Array.Fill(states, "0"); + foreach (var entry in entries) + states[entry.Index - 1] = entry.Value; + + return string.Join(",", states); + } + + /// + /// Parse date string in format: yyyy/MM/dd-HH:mm + /// + 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")] + public async Task> MinMax([FromQuery] int deviceId, [FromQuery] DateTime? startUtc, [FromQuery] DateTime? endUtc, CancellationToken cancellationToken) + { + var result = await telemetryService.GetMinMaxAsync(deviceId, startUtc, endUtc, cancellationToken); + return Ok(result); + } + + [HttpGet("days")] + public async Task>> Days([FromQuery] int deviceId, [FromQuery] int year, [FromQuery] int month, CancellationToken cancellationToken) + { + var result = await telemetryService.GetMonthDaysAsync(deviceId, year, month, cancellationToken); + return Ok(result); + } + + [HttpGet("months")] + public async Task>> Months([FromQuery] int deviceId, [FromQuery] int year, CancellationToken cancellationToken) + { + var result = await telemetryService.GetActiveMonthsAsync(deviceId, year, cancellationToken); + return Ok(result); + } + + [HttpGet("aggregate")] + public async Task>> Aggregate([FromQuery] int deviceId, [FromQuery] DateTime? startUtc, [FromQuery] DateTime? endUtc, [FromQuery] int bucketMinutes = 15, CancellationToken cancellationToken = default) + { + var result = await telemetryService.GetAggregatedAsync(deviceId, startUtc, endUtc, bucketMinutes, cancellationToken); + return Ok(result); + } +}