chore: HERV 통합 저장소 재초기화 커밋
손상된 .git 히스토리(missing tree)로 재초기화 후 작업트리 전체 커밋. .claude/ 만 제외(로컬 에이전트 설정). 구 저장소 백업(.git_corrupt_backup/) 포함. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using ErvProtocol;
|
||||
|
||||
namespace ErvCollector.Storage
|
||||
{
|
||||
// InfluxDB v2 라인 프로토콜 기록 (외부 라이브러리 없이 HTTP /api/v2/write)
|
||||
//
|
||||
// measurement:
|
||||
// erv_global,site=<site> power,run_mode,auto_state,fan_mode,sub_mode,hood,
|
||||
// hyst_preset,hyst_pm25,hyst_pm10,hyst_voc,hyst_co2,error_code
|
||||
// erv_room,site=<site>,room=<1..4> damper,pm25,pm10,voc,co2,air_quality,led_dim,
|
||||
// load_score,final_volume
|
||||
public sealed class InfluxLineWriter : IDisposable
|
||||
{
|
||||
readonly HttpClient _http = new() { Timeout = TimeSpan.FromSeconds(10) };
|
||||
readonly string _writeUrl;
|
||||
|
||||
public event Action<string>? OnError;
|
||||
|
||||
public InfluxLineWriter(string url, string org, string bucket, string token)
|
||||
{
|
||||
_writeUrl = $"{url.TrimEnd('/')}/api/v2/write" +
|
||||
$"?org={Uri.EscapeDataString(org)}&bucket={Uri.EscapeDataString(bucket)}&precision=ns";
|
||||
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", token);
|
||||
}
|
||||
|
||||
public async Task WriteAsync(string site, StatusRecord s, DateTime nowUtc)
|
||||
{
|
||||
long ts = ToUnixNanos(nowUtc);
|
||||
var sb = new StringBuilder(512);
|
||||
|
||||
// erv_global
|
||||
sb.Append("erv_global,site=").Append(site).Append(' ')
|
||||
.Append("power=").Append(s.Power).Append("i,")
|
||||
.Append("run_mode=").Append(s.RunMode).Append("i,")
|
||||
.Append("auto_state=").Append(s.AutoState).Append("i,")
|
||||
.Append("fan_mode=").Append(s.FanMode).Append("i,")
|
||||
.Append("sub_mode=").Append(s.SubMode).Append("i,")
|
||||
.Append("hood=").Append(s.Hood).Append("i,")
|
||||
.Append("hyst_preset=").Append(s.HystPreset).Append("i,")
|
||||
.Append("hyst_pm25=").Append(s.HystPm25).Append("i,")
|
||||
.Append("hyst_pm10=").Append(s.HystPm10).Append("i,")
|
||||
.Append("hyst_voc=").Append(s.HystVoc).Append("i,")
|
||||
.Append("hyst_co2=").Append(s.HystCo2).Append("i,")
|
||||
.Append("error_code=").Append(s.ErrorCode).Append("i,")
|
||||
.Append("reset=").Append(s.Reset).Append('i')
|
||||
.Append(' ').Append(ts).Append('\n');
|
||||
|
||||
// erv_room (4실)
|
||||
for (int r = 0; r < 4; r++)
|
||||
{
|
||||
var rm = s.Rooms[r];
|
||||
sb.Append("erv_room,site=").Append(site).Append(",room=").Append(r + 1).Append(' ')
|
||||
.Append("damper=").Append(rm.Damper).Append("i,")
|
||||
.Append("pm25=").Append(rm.Pm25).Append("i,")
|
||||
.Append("pm10=").Append(rm.Pm10).Append("i,")
|
||||
.Append("voc=").Append(rm.Voc).Append("i,")
|
||||
.Append("co2=").Append(rm.Co2).Append("i,")
|
||||
.Append("air_quality=").Append(rm.AirQuality).Append("i,")
|
||||
.Append("led_dim=").Append(rm.LedDim).Append("i,")
|
||||
.Append("load_score=").Append(rm.LoadScore).Append("i,")
|
||||
.Append("final_volume=").Append(rm.FinalVolume).Append('i')
|
||||
.Append(' ').Append(ts).Append('\n');
|
||||
}
|
||||
|
||||
// erv_vsp (9엔트리: 환기1~4, 바이패스, 공청1~4)
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
var v = s.Vsp[i];
|
||||
sb.Append("erv_vsp,site=").Append(site).Append(",vsp=").Append(VspInfo.Keys[i]).Append(' ')
|
||||
.Append("sa=").Append(v.Sa).Append("i,")
|
||||
.Append("ea=").Append(v.Ea).Append('i')
|
||||
.Append(' ').Append(ts).Append('\n');
|
||||
}
|
||||
|
||||
// erv_hyst (프리셋별 임계값: eco/normal/turbo)
|
||||
string[] presetKey = { "eco", "normal", "turbo" };
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var h = s.HystTable[i];
|
||||
sb.Append("erv_hyst,site=").Append(site).Append(",preset=").Append(presetKey[i]).Append(' ')
|
||||
.Append("pm25=").Append(h.Pm25).Append("i,")
|
||||
.Append("pm10=").Append(h.Pm10).Append("i,")
|
||||
.Append("voc=").Append(h.Voc).Append("i,")
|
||||
.Append("co2=").Append(h.Co2).Append('i')
|
||||
.Append(' ').Append(ts).Append('\n');
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var content = new StringContent(sb.ToString(), Encoding.UTF8);
|
||||
using var resp = await _http.PostAsync(_writeUrl, content);
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
{
|
||||
string body = await resp.Content.ReadAsStringAsync();
|
||||
OnError?.Invoke($"Influx write HTTP {(int)resp.StatusCode}: {body}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke($"Influx write FAIL: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
static long ToUnixNanos(DateTime utc)
|
||||
{
|
||||
long ticks = utc.ToUniversalTime().Ticks - DateTime.UnixEpoch.Ticks; // 100ns ticks
|
||||
return ticks * 100L;
|
||||
}
|
||||
|
||||
public void Dispose() => _http.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user