Integrate AI on Alarm
This commit is contained in:
parent
ed87a4b371
commit
e7f8aacc34
|
|
@ -5,6 +5,7 @@ using InnovEnergy.App.Backend.Database;
|
|||
using InnovEnergy.App.Backend.DataTypes;
|
||||
using InnovEnergy.App.Backend.DataTypes.Methods;
|
||||
using InnovEnergy.App.Backend.Relations;
|
||||
using InnovEnergy.App.Backend.Services;
|
||||
using InnovEnergy.App.Backend.Websockets;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
@ -737,6 +738,87 @@ public class Controller : ControllerBase
|
|||
: Unauthorized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an AI-generated diagnosis for a single error/alarm description.
|
||||
/// Responses are cached in memory — repeated calls for the same error code
|
||||
/// do not hit OpenAI again.
|
||||
/// </summary>
|
||||
[HttpGet(nameof(DiagnoseError))]
|
||||
public async Task<ActionResult<DiagnosticResponse>> DiagnoseError(Int64 installationId, string errorDescription, Token authToken)
|
||||
{
|
||||
var user = Db.GetSession(authToken)?.User;
|
||||
if (user == null)
|
||||
return Unauthorized();
|
||||
|
||||
var installation = Db.GetInstallationById(installationId);
|
||||
if (installation is null || !user.HasAccessTo(installation))
|
||||
return Unauthorized();
|
||||
|
||||
// AI diagnostics are scoped to SodistoreHome and SodiStoreMax only
|
||||
if (installation.Product != (int)ProductType.SodioHome &&
|
||||
installation.Product != (int)ProductType.SodiStoreMax)
|
||||
return BadRequest("AI diagnostics not available for this product.");
|
||||
|
||||
if (!DiagnosticService.IsEnabled)
|
||||
return StatusCode(503, "AI diagnostics not configured.");
|
||||
|
||||
var result = await DiagnosticService.DiagnoseAsync(installationId, errorDescription);
|
||||
|
||||
if (result is null)
|
||||
return StatusCode(500, "Diagnosis failed – please try again later.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test endpoint for AlarmKnowledgeBase - no authentication required.
|
||||
/// Tests multiple Sinexcel and Growatt alarms to verify the knowledge base works.
|
||||
/// Remove this endpoint in production if not needed.
|
||||
/// </summary>
|
||||
[HttpGet(nameof(TestAlarmKnowledgeBase))]
|
||||
public ActionResult TestAlarmKnowledgeBase()
|
||||
{
|
||||
var testCases = new[]
|
||||
{
|
||||
// Sinexcel alarms
|
||||
"Fan fault",
|
||||
"Abnormal grid voltage",
|
||||
"Battery 1not connected",
|
||||
"Inverter power tube fault",
|
||||
"Island protection",
|
||||
// Growatt alarms
|
||||
"Warning 300",
|
||||
"Warning 500",
|
||||
"Error 408",
|
||||
"AFCI Fault",
|
||||
// Unknown alarm (should return null - would call OpenAI)
|
||||
"Some unknown alarm XYZ123"
|
||||
};
|
||||
|
||||
var results = new List<object>();
|
||||
foreach (var alarm in testCases)
|
||||
{
|
||||
var diagnosis = AlarmKnowledgeBase.TryGetDiagnosis(alarm);
|
||||
results.Add(new
|
||||
{
|
||||
Alarm = alarm,
|
||||
FoundInKnowledgeBase = diagnosis != null,
|
||||
Explanation = diagnosis?.Explanation ?? "NOT FOUND - Would call OpenAI API",
|
||||
CausesCount = diagnosis?.Causes.Count ?? 0,
|
||||
NextStepsCount = diagnosis?.NextSteps.Count ?? 0
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
TestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
TotalTests = testCases.Length,
|
||||
FoundInKnowledgeBase = results.Count(r => ((dynamic)r).FoundInKnowledgeBase),
|
||||
WouldCallOpenAI = results.Count(r => !((dynamic)r).FoundInKnowledgeBase),
|
||||
Results = results
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPut(nameof(UpdateFolder))]
|
||||
public ActionResult<Folder> UpdateFolder([FromBody] Folder folder, Token authToken)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System.Diagnostics;
|
|||
using Flurl.Http;
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
using InnovEnergy.App.Backend.Database;
|
||||
using InnovEnergy.App.Backend.Services;
|
||||
using InnovEnergy.App.Backend.Websockets;
|
||||
using InnovEnergy.App.Backend.DeleteOldData;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
|
|
@ -24,6 +25,7 @@ public static class Program
|
|||
|
||||
Watchdog.NotifyReady();
|
||||
Db.Init();
|
||||
DiagnosticService.Initialize();
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
RabbitMqManager.InitializeEnvironment();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"ApiKey": "sk-your-openai-api-key-here"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,188 @@
|
|||
using System.Collections.Concurrent;
|
||||
using Flurl.Http;
|
||||
using InnovEnergy.App.Backend.Database;
|
||||
using InnovEnergy.App.Backend.DataTypes;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Calls OpenAI to generate plain-English diagnostics for errors/warnings.
|
||||
/// Caches responses in-memory keyed by error description so the same
|
||||
/// error code is only sent to the API once.
|
||||
/// </summary>
|
||||
public static class DiagnosticService
|
||||
{
|
||||
private static string _apiKey = "";
|
||||
|
||||
/// <summary>In-memory cache: errorDescription → parsed response.</summary>
|
||||
private static readonly ConcurrentDictionary<string, DiagnosticResponse> Cache = new();
|
||||
|
||||
// ── initialisation ──────────────────────────────────────────────
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "openAiConfig.json");
|
||||
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
// Fallback: look relative to the working directory (useful in dev)
|
||||
configPath = Path.Combine("Resources", "openAiConfig.json");
|
||||
}
|
||||
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
Console.Error.WriteLine("[DiagnosticService] openAiConfig.json not found – AI diagnostics disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
var json = File.ReadAllText(configPath);
|
||||
var config = JsonConvert.DeserializeObject<OpenAiConfig>(json);
|
||||
|
||||
if (config is null || string.IsNullOrWhiteSpace(config.ApiKey))
|
||||
{
|
||||
Console.Error.WriteLine("[DiagnosticService] ApiKey is empty – AI diagnostics disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
_apiKey = config.ApiKey;
|
||||
Console.WriteLine("[DiagnosticService] initialised.");
|
||||
}
|
||||
|
||||
public static bool IsEnabled => !string.IsNullOrEmpty(_apiKey);
|
||||
|
||||
// ── public entry-point ──────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Returns a diagnosis for <paramref name="errorDescription"/>.
|
||||
/// First checks the static AlarmKnowledgeBase for known Sinexcel/Growatt alarms.
|
||||
/// Falls back to in-memory cache, then calls OpenAI only for unknown alarms.
|
||||
/// </summary>
|
||||
public static async Task<DiagnosticResponse?> DiagnoseAsync(Int64 installationId, string errorDescription)
|
||||
{
|
||||
// 1. Check the static knowledge base first (no API call needed)
|
||||
var knownDiagnosis = AlarmKnowledgeBase.TryGetDiagnosis(errorDescription);
|
||||
if (knownDiagnosis is not null)
|
||||
{
|
||||
Console.WriteLine($"[DiagnosticService] Found diagnosis in knowledge base for: {errorDescription}");
|
||||
return knownDiagnosis;
|
||||
}
|
||||
|
||||
// 2. If AI is not enabled, we can't proceed further
|
||||
if (!IsEnabled) return null;
|
||||
|
||||
// 3. Check in-memory cache for previously fetched AI diagnoses
|
||||
if (Cache.TryGetValue(errorDescription, out var cached))
|
||||
return cached;
|
||||
|
||||
// 4. Gather context from the DB for AI prompt
|
||||
var installation = Db.GetInstallationById(installationId);
|
||||
if (installation is null) return null;
|
||||
|
||||
var productName = ((ProductType)installation.Product).ToString();
|
||||
|
||||
var recentDescriptions = Db.Errors
|
||||
.Where(e => e.InstallationId == installationId)
|
||||
.OrderByDescending(e => e.Date + " " + e.Time) // Date/Time stored as strings in DB
|
||||
.Select(e => e.Description)
|
||||
.Distinct() // deduplicate — same error repeated adds no signal
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
||||
// 5. Build prompt and call OpenAI API (only for unknown alarms)
|
||||
Console.WriteLine($"[DiagnosticService] Calling OpenAI for unknown alarm: {errorDescription}");
|
||||
var prompt = BuildPrompt(errorDescription, productName, recentDescriptions);
|
||||
var response = await CallOpenAiAsync(prompt);
|
||||
|
||||
if (response is null) return null;
|
||||
|
||||
// 6. Store in cache for future requests
|
||||
Cache.TryAdd(errorDescription, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
// ── prompt ──────────────────────────────────────────────────────
|
||||
|
||||
private static string BuildPrompt(string errorDescription, string productName, List<string> recentErrors)
|
||||
{
|
||||
var recentList = recentErrors.Count > 0
|
||||
? string.Join(", ", recentErrors)
|
||||
: "none";
|
||||
|
||||
return $@"You are a technician for Innovenergy {productName} battery energy storage systems.
|
||||
These are lithium-ion BESS units with a BMS, PV inverter, and grid inverter.
|
||||
|
||||
Error: {errorDescription}
|
||||
Other recent errors: {recentList}
|
||||
|
||||
Explain in plain English for a homeowner. List likely causes and next steps.
|
||||
Reply with ONLY valid JSON, no markdown:
|
||||
{{""explanation"":""2-3 sentences"",""causes"":[""...""],""nextSteps"":[""...""]}}
|
||||
";
|
||||
}
|
||||
|
||||
// ── OpenAI HTTP call ────────────────────────────────────────────
|
||||
|
||||
private static readonly string OpenAiUrl = "https://api.openai.com/v1/chat/completions";
|
||||
|
||||
private static async Task<DiagnosticResponse?> CallOpenAiAsync(string userPrompt)
|
||||
{
|
||||
try
|
||||
{
|
||||
var requestBody = new
|
||||
{
|
||||
model = "gpt-4o-mini", // cost-efficient, fast; swap to "gpt-4" if quality needs tuning
|
||||
messages = new[]
|
||||
{
|
||||
new { role = "user", content = userPrompt }
|
||||
},
|
||||
max_tokens = 400,
|
||||
temperature = 0.2 // low temperature for factual consistency
|
||||
};
|
||||
|
||||
var responseText = await OpenAiUrl
|
||||
.SetHeader("Authorization", $"Bearer {_apiKey}")
|
||||
.SetHeader("Content-Type", "application/json")
|
||||
.PostJsonAsync(requestBody)
|
||||
.ReceiveString();
|
||||
|
||||
// parse OpenAI envelope
|
||||
var envelope = JsonConvert.DeserializeObject<dynamic>(responseText);
|
||||
var content = (string?) envelope?.choices?[0]?.message?.content;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
Console.Error.WriteLine("[DiagnosticService] OpenAI returned empty content.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// parse the JSON the model produced
|
||||
var diagnostic = JsonConvert.DeserializeObject<DiagnosticResponse>(content);
|
||||
return diagnostic;
|
||||
}
|
||||
catch (FlurlHttpException httpEx)
|
||||
{
|
||||
Console.Error.WriteLine($"[DiagnosticService] HTTP error {httpEx.Response?.StatusCode}: {await httpEx.Response?.GetStringAsync()}");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[DiagnosticService] {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── config / response models ────────────────────────────────────────────────
|
||||
|
||||
public class OpenAiConfig
|
||||
{
|
||||
public string ApiKey { get; set; } = "";
|
||||
}
|
||||
|
||||
public class DiagnosticResponse
|
||||
{
|
||||
public string Explanation { get; set; } = "";
|
||||
public IReadOnlyList<string> Causes { get; set; } = Array.Empty<string>();
|
||||
public IReadOnlyList<string> NextSteps { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Card,
|
||||
CircularProgress,
|
||||
Container,
|
||||
Divider,
|
||||
Grid,
|
||||
|
|
@ -17,7 +19,7 @@ import { AxiosError, AxiosResponse } from 'axios/index';
|
|||
import routes from '../../../Resources/routes.json';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { TokenContext } from '../../../contexts/tokenContext';
|
||||
import { ErrorMessage } from '../../../interfaces/S3Types';
|
||||
import { ErrorMessage, DiagnosticResponse } from '../../../interfaces/S3Types';
|
||||
import Button from '@mui/material/Button';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
|
|
@ -45,6 +47,11 @@ function Log(props: LogProps) {
|
|||
const tokencontext = useContext(TokenContext);
|
||||
const { removeToken } = tokencontext;
|
||||
|
||||
const [diagnosis, setDiagnosis] = useState<DiagnosticResponse | null>(null);
|
||||
const [diagnosisLoading, setDiagnosisLoading] = useState(false);
|
||||
const [diagnosisExpanded, setDiagnosisExpanded] = useState(false);
|
||||
const [diagnosedError, setDiagnosedError] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
axiosConfig
|
||||
.get(`/GetAllErrorsForInstallation?id=${props.id}`)
|
||||
|
|
@ -71,6 +78,34 @@ function Log(props: LogProps) {
|
|||
});
|
||||
}, [updateCount]);
|
||||
|
||||
// fetch AI diagnosis for the first unseen error (or warning if no unseen errors)
|
||||
useEffect(() => {
|
||||
const target = errors.find(e => !e.seen) || warnings.find(w => !w.seen);
|
||||
|
||||
if (!target) {
|
||||
setDiagnosis(null);
|
||||
setDiagnosedError('');
|
||||
return;
|
||||
}
|
||||
|
||||
// already have a diagnosis for this exact description — skip
|
||||
if (target.description === diagnosedError && diagnosis) return;
|
||||
|
||||
setDiagnosisLoading(true);
|
||||
axiosConfig
|
||||
.get(`/DiagnoseError?installationId=${props.id}&errorDescription=${encodeURIComponent(target.description)}`)
|
||||
.then((res: AxiosResponse<DiagnosticResponse>) => {
|
||||
setDiagnosis(res.data);
|
||||
setDiagnosedError(target.description);
|
||||
})
|
||||
.catch(() => {
|
||||
setDiagnosis(null);
|
||||
})
|
||||
.finally(() => {
|
||||
setDiagnosisLoading(false);
|
||||
});
|
||||
}, [errors, warnings]);
|
||||
|
||||
const handleErrorButtonPressed = () => {
|
||||
setErrorButtonPressed(!errorButtonPressed);
|
||||
};
|
||||
|
|
@ -108,6 +143,7 @@ function Log(props: LogProps) {
|
|||
};
|
||||
|
||||
const warningDescriptionMap: { [key: string]: string } = {
|
||||
// BMS warnings
|
||||
"TaM1": "TaM1: BMS temperature high",
|
||||
"TbM1": "TbM1: Battery temperature high",
|
||||
"VBm1": "VBm1: Bus voltage low",
|
||||
|
|
@ -124,10 +160,131 @@ function Log(props: LogProps) {
|
|||
"MPMM": "MPMM: Midpoint wiring problem",
|
||||
"TCdi": "TCdi: Temperature difference between strings high",
|
||||
"LMPW": "LMPW: String voltages unbalance warning",
|
||||
"TOCW": "TOCW: Top of Charge requested"
|
||||
"TOCW": "TOCW: Top of Charge requested",
|
||||
|
||||
// Sinexcel warnings (WARNING/INFO severity)
|
||||
"Inverted sequenceof grid voltage": "Grid phase sequence reversed",
|
||||
"Excessivelyhigh ambient temperature": "Ambient temperature too high",
|
||||
"Excessive radiator temperature": "Radiator/heatsink temperature high",
|
||||
"Island protection": "Island protection active (auto-recovers)",
|
||||
"Battery 1over voltage": "Battery 1 voltage too high",
|
||||
"Battery 1under voltage": "Battery 1 voltage too low",
|
||||
"Battery 1discharge end": "Battery 1 discharge complete (auto-recovers)",
|
||||
"Battery 1inverted": "Battery 1 polarity reversed!",
|
||||
"Battery 2over voltage": "Battery 2 voltage too high",
|
||||
"Battery 2under voltage": "Battery 2 voltage too low",
|
||||
"Battery 2discharge end": "Battery 2 discharge complete (auto-recovers)",
|
||||
"Battery 2inverted": "Battery 2 polarity reversed!",
|
||||
"PV 1notaccessed": "PV string 1 not accessible",
|
||||
"PV 1over voltage": "PV string 1 voltage too high",
|
||||
"PV 2notaccessed": "PV string 2 not accessible",
|
||||
"PV 2over voltage": "PV string 2 voltage too high",
|
||||
"DC busover voltage": "DC bus voltage too high",
|
||||
"DC busunder voltage": "DC bus voltage too low",
|
||||
"Inverter soft start failure": "Inverter soft-start failed",
|
||||
"Battery 1soft start failure": "Battery 1 soft-start failed",
|
||||
"Battery 2soft start failure": "Battery 2 soft-start failed",
|
||||
"Output voltageDC overlimit": "DC component in output voltage high",
|
||||
"Output currentDC overlimit": "DC component in output current high",
|
||||
"Poorgrounding": "Poor ground connection detected",
|
||||
"PV 1soft startfailure": "PV 1 soft-start failed",
|
||||
"PV 2soft startfailure": "PV 2 soft-start failed",
|
||||
"PCBover temperature": "PCB temperature too high",
|
||||
"DC converter over temperature": "DC converter temperature high",
|
||||
"Busslow over voltage": "Slow bus over-voltage",
|
||||
"DC converter over voltage": "DC converter voltage high",
|
||||
"DC converter over current": "DC converter current high",
|
||||
"DC converter resonator over current": "DC converter resonator overcurrent",
|
||||
"PV 1insufficient power": "PV 1 power insufficient (auto-recovers)",
|
||||
"PV 2insufficient power": "PV 2 power insufficient (auto-recovers)",
|
||||
"Battery 1insufficient power": "Battery 1 power insufficient (auto-recovers)",
|
||||
"Battery 2insufficiency power": "Battery 2 power insufficient",
|
||||
"Lithium battery 1 chargeforbidden": "Lithium battery 1 charging forbidden",
|
||||
"Lithium battery 1 dischargeforbidden": "Lithium battery 1 discharging forbidden",
|
||||
"Lithium battery 2 chargeforbidden": "Lithium battery 2 charging forbidden",
|
||||
"Lithium battery 2 dischargeforbidden": "Lithium battery 2 discharging forbidden",
|
||||
"Lithium battery 1full": "Lithium battery 1 fully charged",
|
||||
"Lithium battery 1 dischargeend": "Lithium battery 1 discharge end",
|
||||
"Lithium battery 2full": "Lithium battery 2 fully charged",
|
||||
"Lithium battery 2 dischargeend": "Lithium battery 2 discharge end",
|
||||
"Inverter over temperaturealarm": "Inverter over-temperature alarm",
|
||||
"Inverter over temperature": "Inverter temperature high",
|
||||
"DC converter over temperaturealarm": "DC converter over-temperature alarm",
|
||||
"Systemderating": "System power derating active",
|
||||
"PVaccessmethod erroralarm": "PV access method error",
|
||||
"Parallelmodule missing": "Parallel module missing",
|
||||
"Duplicatemachine numbersforparallel modules": "Duplicate parallel module IDs",
|
||||
"Para meterconflictin parallelmodule": "Parameter conflict in parallel modules",
|
||||
"Reservedalarms 4": "Reserved alarm 4",
|
||||
"InverterSealPulse": "Inverter seal pulse active",
|
||||
"PV 3over voltage": "PV 3 voltage too high",
|
||||
"PV 3average current anomaly": "PV 3 current anomaly",
|
||||
"PV 4over voltage": "PV 4 voltage too high",
|
||||
"PV 4average current anomaly": "PV 4 current anomaly",
|
||||
"PV 3soft startfailure": "PV 3 soft-start failed",
|
||||
"PV 4soft startfailure": "PV 4 soft-start failed",
|
||||
"Batteryaccessmethod error": "Battery access method error",
|
||||
"Reservedalarms 5": "Reserved alarm 5",
|
||||
"Battery 1backup prohibited": "Battery 1 backup prohibited",
|
||||
"Battery 2backup prohibited": "Battery 2 backup prohibited",
|
||||
"Bus soft startfailure": "Bus soft-start failed",
|
||||
"Insufficient photovoltaic power": "Insufficient PV power",
|
||||
"Photovoltaic 1 over current": "PV 1 overcurrent",
|
||||
"Photovoltaic 2 over current": "PV 2 overcurrent",
|
||||
"Photovoltaic 3 over current": "PV 3 overcurrent",
|
||||
"Photovoltaic 4 over current": "PV 4 overcurrent",
|
||||
"Battery 1over current": "Battery 1 overcurrent",
|
||||
"Battery 2over current": "Battery 2 overcurrent",
|
||||
"Battery 1charging sealingwave": "Battery 1 charge limiting",
|
||||
"Battery 2charging sealingwave": "Battery 2 charge limiting",
|
||||
|
||||
// Growatt warnings
|
||||
"Warning 200": "String fault",
|
||||
"Warning 201": "PV string/PID terminals abnormal",
|
||||
"Warning 203": "PV1 or PV2 short circuited",
|
||||
"Warning 208": "DC fuse blown",
|
||||
"Warning 209": "DC input voltage too high",
|
||||
"Warning 219": "PID function abnormal",
|
||||
"Warning 220": "PV string disconnected",
|
||||
"Warning 221": "PV string current unbalanced",
|
||||
"Warning 300": "No grid connection / grid power failure",
|
||||
"Warning 301": "Grid voltage out of range",
|
||||
"Warning 302": "Grid frequency out of range",
|
||||
"Warning 303": "System overload",
|
||||
"Warning 308": "Meter disconnected",
|
||||
"Warning 309": "Meter L/N reversed",
|
||||
"Warning 310": "N-PE voltage abnormal",
|
||||
"Warning 311": "Phase sequence error (auto-adjusts)",
|
||||
"Warning 400": "Fan failure",
|
||||
"Warning 401": "Meter abnormal",
|
||||
"Warning 402": "Optimizer communication abnormal",
|
||||
"Warning 407": "Over-temperature",
|
||||
"Warning 408": "NTC temperature sensor broken",
|
||||
"Warning 411": "Sync signal abnormal",
|
||||
"Warning 412": "Grid connection requirements not met",
|
||||
"Warning 500": "Inverter-battery communication failed",
|
||||
"Warning 501": "Battery disconnected",
|
||||
"Warning 502": "Battery voltage too high",
|
||||
"Warning 503": "Battery voltage too low",
|
||||
"Warning 504": "Battery terminals reversed",
|
||||
"Warning 505": "Lead-acid battery temp sensor disconnected",
|
||||
"Warning 506": "Battery temperature out of range",
|
||||
"Warning 507": "BMS fault: charging/discharging failed",
|
||||
"Warning 508": "Lithium battery overload protection",
|
||||
"Warning 509": "BMS communication abnormal",
|
||||
"Warning 510": "BAT SPD function abnormal",
|
||||
"Warning 600": "Output DC component bias abnormal",
|
||||
"Warning 601": "High DC in output voltage",
|
||||
"Warning 602": "Off-grid output voltage too low",
|
||||
"Warning 603": "Off-grid output voltage too high",
|
||||
"Warning 604": "Off-grid output overcurrent",
|
||||
"Warning 605": "Off-grid bus voltage too low",
|
||||
"Warning 606": "Off-grid output overload",
|
||||
"Warning 609": "Balanced circuit abnormal"
|
||||
};
|
||||
|
||||
const errorDescriptionMap: { [key: string]: string } = {
|
||||
// BMS errors
|
||||
"Tam": "Tam: Recoverable, BMS temperature too low",
|
||||
"TaM2": "TaM2: Recoverable, BMS temperature too high",
|
||||
"Tbm": "Tbm: Recoverable, Battery temperature too low",
|
||||
|
|
@ -153,12 +310,204 @@ function Log(props: LogProps) {
|
|||
"HTFS": "HTFS: Recoverable, Unrecoverable: Heater Fuse Blown",
|
||||
"DATA": "DATA: Recoverable, Unrecoverable: Parameters out of range",
|
||||
"LMPA": "LMPA: Unrecoverable, String voltages unbalance alarm",
|
||||
"HEBT": "HEBT: Recoverable, oss of heartbeat"
|
||||
"HEBT": "HEBT: Recoverable, oss of heartbeat",
|
||||
|
||||
// Sinexcel errors (ERROR severity - require manual intervention)
|
||||
"Abnormal grid voltage": "Grid voltage abnormal",
|
||||
"Abnormal grid frequency": "Grid frequency abnormal",
|
||||
"Grid voltage phase loss": "Grid phase loss detected",
|
||||
"Abnormal output voltage": "Output voltage abnormal",
|
||||
"Abnormal output frequency": "Output frequency abnormal",
|
||||
"Abnormalnullline": "Null/neutral line abnormal",
|
||||
"Insulation fault": "Insulation fault detected",
|
||||
"Leakage protection fault": "Leakage/ground fault protection tripped",
|
||||
"Auxiliary power fault": "Auxiliary power supply fault",
|
||||
"Fan fault": "Cooling fan fault",
|
||||
"Model capacity fault": "Model/capacity configuration fault",
|
||||
"Abnormal lightning arrester": "Surge protection device abnormal",
|
||||
"Battery 1not connected": "Battery 1 not connected",
|
||||
"Battery 2not connected": "Battery 2 not connected",
|
||||
"AbnormalPV 1current sharing": "PV 1 current sharing abnormal",
|
||||
"AbnormalPV 2current sharing": "PV 2 current sharing abnormal",
|
||||
"DC bus voltage unbalance": "DC bus voltage unbalance",
|
||||
"System output overload": "System output overloaded",
|
||||
"Inverter overload": "Inverter overloaded",
|
||||
"Inverter overload timeout": "Inverter overload timeout",
|
||||
"Battery 1overload timeout": "Battery 1 overload timeout",
|
||||
"Battery 2overload timeout": "Battery 2 overload timeout",
|
||||
"DSP 1para meter setting fault": "DSP 1 parameter setting fault",
|
||||
"DSP 2para meter setting fault": "DSP 2 parameter setting fault",
|
||||
"DSPversion compatibility fault": "DSP version compatibility fault",
|
||||
"CPLDversion compatibility fault": "CPLD version compatibility fault",
|
||||
"CPLD communication fault": "CPLD communication fault",
|
||||
"DSP communication fault": "DSP communication fault",
|
||||
"Relayself-checkfails": "Relay self-check failed",
|
||||
"Abnormal inverter": "Abnormal inverter condition",
|
||||
"Balancedcircuit overload timeout": "Balance circuit overload timeout",
|
||||
"PV 1overload timeout": "PV 1 overload timeout",
|
||||
"PV 2overload timeout": "PV 2 overload timeout",
|
||||
"Abnormaloff-grid output voltage": "Off-grid output voltage abnormal",
|
||||
"Parallel communicationalarm": "Parallel communication alarm",
|
||||
"Inverter relayopen": "Inverter relay open",
|
||||
"PV 3not connected": "PV 3 not connected",
|
||||
"PV 4not connected": "PV 4 not connected",
|
||||
"PV 3overload timeout": "PV 3 overload timeout",
|
||||
"PV 4overload timeout": "PV 4 overload timeout",
|
||||
"Abnormal diesel generator voltage": "Diesel generator voltage abnormal",
|
||||
"Abnormal diesel generator frequency": "Diesel generator frequency abnormal",
|
||||
"Diesel generator voltageoutof phase": "Diesel generator out of phase",
|
||||
"Lead battery temperature abnormality": "Lead battery temperature abnormal",
|
||||
"Abnormal grid current": "Grid current abnormal",
|
||||
"Generator overload": "Generator overloaded",
|
||||
"Opencircuitof power grid relay": "Grid relay open circuit",
|
||||
"Shortcircuitof power grid relay": "Grid relay short circuit",
|
||||
"generator Relayopencircuit": "Generator relay open circuit",
|
||||
"generator Relayshortcircuit": "Generator relay short circuit",
|
||||
"Load power overload": "Load power overload",
|
||||
"Abnormal leakage self-check": "Leakage self-check abnormal",
|
||||
|
||||
// Sinexcel PROTECTION errors (require service - do not restart)
|
||||
"PV 1power tube fault": "PV 1 power tube fault - Contact Service",
|
||||
"PV 2power tube fault": "PV 2 power tube fault - Contact Service",
|
||||
"Battery 1power tube fault": "Battery 1 power tube fault - Contact Service",
|
||||
"Battery 2power tube fault": "Battery 2 power tube fault - Contact Service",
|
||||
"Inverter power tube fault": "Inverter power tube fault - Contact Service",
|
||||
"Hardware bus over voltage": "Hardware bus overvoltage - Contact Service",
|
||||
"Hardware over current": "Hardware overcurrent - Contact Service",
|
||||
"DC converter hardware over voltage": "DC converter hardware overvoltage - Contact Service",
|
||||
"DC converter hardware over current": "DC converter hardware overcurrent - Contact Service",
|
||||
"Inverter relayshort circuit": "Inverter relay short circuit - Contact Service",
|
||||
"Reverse meter connection": "Meter connected in reverse - Contact Service",
|
||||
"PV 3power tube failure": "PV 3 power tube failure - Contact Service",
|
||||
"PV 4power tube Failure": "PV 4 power tube failure - Contact Service",
|
||||
"PV 3reverse connection": "PV 3 reverse connection - Contact Service",
|
||||
"PV 4reverse connection": "PV 4 reverse connection - Contact Service",
|
||||
"Diesel generator voltage reverse sequence": "Generator phase reversed - Contact Service",
|
||||
|
||||
// Growatt errors (PROTECTION severity)
|
||||
"Error 309": "Grid ROCOF abnormal",
|
||||
"Error 311": "Export limitation fail-safe",
|
||||
"Error 400": "DCI bias abnormal",
|
||||
"Error 402": "High DC in output current",
|
||||
"Error 404": "Bus voltage sampling abnormal",
|
||||
"Error 405": "Relay fault",
|
||||
"Error 408": "Over-temperature protection",
|
||||
"Error 409": "Bus voltage abnormal",
|
||||
"Error 411": "Internal communication failure",
|
||||
"Error 412": "Temperature sensor disconnected",
|
||||
"Error 413": "IGBT drive fault",
|
||||
"Error 414": "EEPROM error",
|
||||
"Error 415": "Auxiliary power supply abnormal",
|
||||
"Error 416": "DC/AC overcurrent protection",
|
||||
"Error 417": "Communication protocol mismatch",
|
||||
"Error 418": "DSP/COM firmware mismatch",
|
||||
"Error 419": "DSP software/hardware mismatch",
|
||||
"Error 421": "CPLD abnormal",
|
||||
"Error 422": "Redundancy sampling inconsistent",
|
||||
"Error 423": "PWM pass-through signal failure",
|
||||
"Error 425": "AFCI self-test failure",
|
||||
"Error 426": "PV current sampling abnormal",
|
||||
"Error 427": "AC current sampling abnormal",
|
||||
"Error 429": "BUS soft-boot failure",
|
||||
"Error 430": "EPO fault",
|
||||
"Error 431": "Monitoring chip BOOT verification failed",
|
||||
"Error 500": "BMS-inverter communication failed",
|
||||
"Error 501": "BMS: battery charge/discharge failed",
|
||||
"Error 503": "Battery voltage exceeds threshold",
|
||||
"Error 504": "Battery temperature out of range",
|
||||
"Error 506": "Battery open-circuited",
|
||||
"Error 507": "Battery overload protection",
|
||||
"Error 508": "BUS2 voltage abnormal",
|
||||
"Error 509": "BAT charge overcurrent protection",
|
||||
"Error 510": "BAT discharge overcurrent protection",
|
||||
"Error 511": "BAT soft start failed",
|
||||
"Error 601": "Off-grid bus voltage low",
|
||||
"Error 602": "Abnormal voltage at off-grid terminal",
|
||||
"Error 603": "Off-grid soft start failed",
|
||||
"Error 604": "Off-grid output voltage abnormal",
|
||||
"Error 605": "Balanced circuit self-test failed",
|
||||
"Error 606": "High DC in output voltage",
|
||||
"Error 608": "Off-grid parallel signal abnormal",
|
||||
"AFCI Fault": "Arc fault detected - Check PV connections",
|
||||
"GFCI High": "High leakage current detected",
|
||||
"PV Voltage High": "DC input voltage exceeds limit"
|
||||
};
|
||||
|
||||
return (
|
||||
<Container maxWidth="xl">
|
||||
<Grid container>
|
||||
|
||||
{/* AI Diagnosis banner — shown when loading or a diagnosis is available */}
|
||||
{(diagnosisLoading || diagnosis) && (
|
||||
<Grid item xs={12} md={12}>
|
||||
<Card sx={{ marginTop: '20px', borderLeft: '4px solid #1976d2' }}>
|
||||
<Box sx={{ padding: '16px' }}>
|
||||
|
||||
{/* loading state */}
|
||||
{diagnosisLoading && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||
<CircularProgress size={22} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<FormattedMessage id="ai_analyzing" defaultMessage="AI is analyzing..." />
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* diagnosis result */}
|
||||
{diagnosis && !diagnosisLoading && (
|
||||
<>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: '8px', mb: 1 }}>
|
||||
<Typography variant="subtitle2" fontWeight="bold" color="primary">
|
||||
<FormattedMessage id="ai_diagnosis" defaultMessage="AI Diagnosis" />
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{diagnosedError}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Typography variant="body2">
|
||||
{diagnosis.explanation}
|
||||
</Typography>
|
||||
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => setDiagnosisExpanded(!diagnosisExpanded)}
|
||||
sx={{ textTransform: 'none', p: 0, mt: 1 }}
|
||||
>
|
||||
<FormattedMessage
|
||||
id={diagnosisExpanded ? 'ai_show_less' : 'ai_show_details'}
|
||||
defaultMessage={diagnosisExpanded ? 'Show less' : 'Show details'}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
{diagnosisExpanded && (
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<Typography variant="caption" fontWeight="bold">
|
||||
<FormattedMessage id="ai_likely_causes" defaultMessage="Likely causes:" />
|
||||
</Typography>
|
||||
<ul style={{ margin: '4px 0', paddingLeft: '20px' }}>
|
||||
{diagnosis.causes.map((cause, i) => (
|
||||
<li key={i}><Typography variant="caption">{cause}</Typography></li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<Typography variant="caption" fontWeight="bold">
|
||||
<FormattedMessage id="ai_next_steps" defaultMessage="Suggested next steps:" />
|
||||
</Typography>
|
||||
<ol style={{ margin: '4px 0', paddingLeft: '20px' }}>
|
||||
{diagnosis.nextSteps.map((step, i) => (
|
||||
<li key={i}><Typography variant="caption">{step}</Typography></li>
|
||||
))}
|
||||
</ol>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Card>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<Grid item xs={12} md={12}>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
|
|
|||
|
|
@ -31,3 +31,9 @@ export interface Action {
|
|||
description: string;
|
||||
testingMode: boolean;
|
||||
}
|
||||
|
||||
export interface DiagnosticResponse {
|
||||
explanation: string;
|
||||
causes: string[];
|
||||
nextSteps: string[];
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue