AI Diagnostics for Off-Grid Living: Solar Panel + Battery Health Checker
Last February, in the dead of an Alaskan winter, my battery bank voltage dropped to 11.2V at 3 AM. The inverter alarm woke me up. It was -28°F outside...
Last February, in the dead of an Alaskan winter, my battery bank voltage dropped to 11.2V at 3 AM. The inverter alarm woke me up. It was -28°F outside, the woodstove was burning low, and I had maybe 20 minutes before the circulating pump lost power and the pipes started their slow march toward freezing.
I got the generator running. Everything was fine. But lying in bed afterward, heart still thumping, I thought: I built AutoDetective.ai to diagnose car problems using AI. Why am I not using the same pattern to monitor the system that actually keeps me alive?
So I built one. A diagnostic tool for off-grid solar and battery systems that uses the same AI-driven pattern recognition I developed for automotive diagnostics. And now I'm going to show you how it works.
The Off-Grid Problem Nobody Talks About
Here's what the solar panel YouTube influencers don't tell you: off-grid power systems degrade, and they degrade in ways that are hard to catch until something goes wrong.
A solar panel loses maybe 0.5% efficiency per year. That's nothing in isolation. But compound it across eight panels over five years, factor in a few micro-cracks from thermal cycling in extreme cold, add some snow-shading losses that you don't notice because the panels are on the roof and you're not climbing up there at -15°F, and suddenly your system that was designed with 20% headroom has no headroom at all.
Batteries are worse. Lead-acid batteries — which are still what most off-grid systems in Alaska run because lithium doesn't love extreme cold — develop sulfation gradually. Capacity drops from 400Ah to 350Ah to 300Ah, and you don't notice because you've been unconsciously adjusting your consumption patterns. Until one long cloudy stretch pushes you past the edge.
I know this because I've lived it. My cabin runs a 2.4kW solar array with a 24V battery bank, a 3000W inverter, and a backup generator for the dark months. I've replaced batteries I should have caught earlier. I've had panels underperforming for months before I figured out why. Every one of those problems had warning signs in the data. I just wasn't reading the data.
The AutoDetective Pattern
When I built AutoDetective.ai, the core insight was straightforward: most diagnostic problems follow a symptom-to-cause pattern that experienced mechanics already know, but that AI can formalize and scale.
You describe symptoms. The system maps those symptoms against known failure patterns. It generates a ranked list of probable causes with specific diagnostic steps. The magic isn't in the AI generating novel insights — it's in the AI consistently applying expert-level pattern matching without fatigue, bias, or the tendency to overlook the obvious.
The same pattern applies perfectly to power system diagnostics. Solar panels, charge controllers, batteries, and inverters all produce telemetry data. That data contains patterns. Those patterns map to specific problems. An AI system can watch the data continuously and flag anomalies before they become emergencies.
System Architecture
My solar diagnostic tool has three layers: data collection, pattern analysis, and the AI diagnostic engine. Let me walk through each one.
Data Collection
Most modern charge controllers expose data via serial or Modbus. I run a Victron SmartSolar MPPT 150/35, which talks Victron's VE.Direct protocol over a serial connection. A Raspberry Pi Zero W sits next to the charge controller, reads the data stream, and posts it to my server every 60 seconds.
var SerialPort = require("serialport");
var axios = require("axios");
var port = new SerialPort({
path: "/dev/ttyUSB0",
baudRate: 19200
});
var buffer = "";
port.on("data", function(data) {
buffer += data.toString();
// VE.Direct uses checksum-terminated blocks
var blocks = buffer.split("\r\n\r\n");
if (blocks.length > 1) {
var block = blocks[0];
buffer = blocks.slice(1).join("\r\n\r\n");
var parsed = parseVEDirect(block);
if (parsed) {
sendReading(parsed);
}
}
});
function parseVEDirect(block) {
var data = {};
var lines = block.split("\r\n");
for (var i = 0; i < lines.length; i++) {
var parts = lines[i].split("\t");
if (parts.length === 2) {
data[parts[0]] = parts[1];
}
}
return {
timestamp: new Date().toISOString(),
batteryVoltage: parseFloat(data["V"]) / 1000,
batteryCurrent: parseFloat(data["I"]) / 1000,
panelVoltage: parseFloat(data["VPV"]) / 1000,
panelPower: parseInt(data["PPV"]),
chargeState: data["CS"],
loadCurrent: parseFloat(data["IL"]) / 1000,
yieldToday: parseInt(data["H20"]) / 100,
maxPowerToday: parseInt(data["H21"]),
errorCode: data["ERR"]
};
}
function sendReading(reading) {
axios.post("https://internal-tools.grizzlypeaksoftware.com/solar/reading", reading, {
headers: { "Authorization": "Bearer " + process.env.SOLAR_API_TOKEN }
}).catch(function(err) {
console.error("Failed to send reading:", err.message);
// Queue for retry - readings are too valuable to lose
appendToLocalQueue(reading);
});
}
The key detail: I never throw away a failed reading. If the network is down — which happens more than I'd like to admit with satellite internet in Alaska — readings get queued locally and synced when connectivity returns. Power system data has long-term diagnostic value. Every data point matters.
Data Storage and Aggregation
On the server side, readings go into PostgreSQL. One reading per minute generates about 525,000 rows per year. That's nothing for PostgreSQL.
var db = require("./db/postgres");
function storeReading(reading) {
var sql = "INSERT INTO solar_readings " +
"(timestamp, battery_voltage, battery_current, panel_voltage, " +
"panel_power, charge_state, load_current, yield_today, " +
"max_power_today, error_code) " +
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
var params = [
reading.timestamp,
reading.batteryVoltage,
reading.batteryCurrent,
reading.panelVoltage,
reading.panelPower,
reading.chargeState,
reading.loadCurrent,
reading.yieldToday,
reading.maxPowerToday,
reading.errorCode
];
return db.query(sql, params);
}
function getDailyAggregates(days) {
var sql = "SELECT " +
"DATE(timestamp) as date, " +
"MIN(battery_voltage) as min_voltage, " +
"MAX(battery_voltage) as max_voltage, " +
"AVG(battery_voltage) as avg_voltage, " +
"MAX(panel_power) as peak_power, " +
"SUM(CASE WHEN charge_state = '5' THEN 1 ELSE 0 END) as float_minutes, " +
"MAX(yield_today) as daily_yield, " +
"COUNT(CASE WHEN error_code != '0' THEN 1 END) as error_count " +
"FROM solar_readings " +
"WHERE timestamp > NOW() - INTERVAL '" + days + " days' " +
"GROUP BY DATE(timestamp) " +
"ORDER BY date DESC";
return db.query(sql);
}
The aggregation query is where the diagnostic value lives. Individual readings tell you what's happening right now. Aggregated trends tell you what's going wrong over time.
The Diagnostic Engine
This is where the AutoDetective pattern gets applied. I define a set of diagnostic rules that check for known failure patterns, then hand ambiguous cases to an AI model for deeper analysis.
Rule-Based Checks
The first layer is deterministic. These are the things I know for certain indicate a problem.
function runDiagnostics(aggregates, latestReading) {
var issues = [];
// Check 1: Battery voltage trending down over 7 days
var voltages = aggregates.slice(0, 7).map(function(d) {
return d.avg_voltage;
});
var voltageTrend = calculateTrend(voltages);
if (voltageTrend < -0.05) {
issues.push({
severity: "warning",
component: "battery",
code: "BAT_VOLTAGE_DECLINING",
message: "Average battery voltage declining " +
Math.abs(voltageTrend).toFixed(3) + "V/day over the past week",
data: { trend: voltageTrend, voltages: voltages }
});
}
// Check 2: Battery not reaching float stage
var recentFloat = aggregates.slice(0, 3);
var noFloat = recentFloat.every(function(d) {
return d.float_minutes < 30;
});
if (noFloat) {
issues.push({
severity: "warning",
component: "system",
code: "NO_FLOAT_CHARGE",
message: "Batteries haven't reached float charge in 3+ days. " +
"Possible undersized array, excessive load, or panel obstruction.",
data: { floatMinutes: recentFloat.map(function(d) { return d.float_minutes; }) }
});
}
// Check 3: Peak power degradation vs historical baseline
var recentPeak = average(aggregates.slice(0, 7).map(function(d) {
return d.peak_power;
}));
var historicalPeak = average(aggregates.slice(30, 37).map(function(d) {
return d.peak_power;
}));
if (historicalPeak > 0 && recentPeak < historicalPeak * 0.75) {
issues.push({
severity: "alert",
component: "solar",
code: "PANEL_OUTPUT_DEGRADED",
message: "Peak solar output down " +
Math.round((1 - recentPeak / historicalPeak) * 100) +
"% vs 30 days ago. Check for snow, shading, or panel damage.",
data: { recentPeak: recentPeak, historicalPeak: historicalPeak }
});
}
// Check 4: Overnight voltage drop (indicates battery capacity loss)
var overnightDrop = latestReading.batteryVoltage -
getMinVoltage(aggregates[0]);
if (aggregates[0].min_voltage < 23.5) { // 24V system critical low
issues.push({
severity: "critical",
component: "battery",
code: "BAT_DEEP_DISCHARGE",
message: "Battery voltage dropped to " +
aggregates[0].min_voltage.toFixed(1) +
"V overnight. Risk of sulfation damage.",
data: { minVoltage: aggregates[0].min_voltage }
});
}
// Check 5: Charge controller errors
if (latestReading.errorCode !== "0") {
var errorDesc = getVictronError(latestReading.errorCode);
issues.push({
severity: "critical",
component: "chargeController",
code: "CONTROLLER_ERROR",
message: "Charge controller error: " + errorDesc,
data: { errorCode: latestReading.errorCode }
});
}
return issues;
}
function calculateTrend(values) {
// Simple linear regression slope
var n = values.length;
var sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
for (var i = 0; i < n; i++) {
sumX += i;
sumY += values[i];
sumXY += i * values[i];
sumX2 += i * i;
}
return (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
}
function average(arr) {
var filtered = arr.filter(function(v) { return v != null && !isNaN(v); });
if (filtered.length === 0) return 0;
var sum = 0;
for (var i = 0; i < filtered.length; i++) {
sum += filtered[i];
}
return sum / filtered.length;
}
These checks catch the obvious stuff. Voltage declining? Batteries not reaching full charge? Solar output dropping? Controller throwing errors? These are the "check engine light" equivalents for a power system.
AI-Powered Deep Diagnostics
When the rule-based checks find something, or when I want a broader analysis, the AI layer kicks in. This is where the AutoDetective pattern really shines.
var Anthropic = require("@anthropic-ai/sdk");
var client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
function runAIDiagnostic(issues, aggregates, systemProfile) {
var prompt = buildDiagnosticPrompt(issues, aggregates, systemProfile);
return client.messages.create({
model: "claude-3-5-haiku-20241022",
max_tokens: 2000,
messages: [
{
role: "user",
content: prompt
}
],
system: "You are an expert off-grid solar power system diagnostician. " +
"Analyze the provided telemetry data and diagnostic flags to identify " +
"the most likely root causes. Prioritize safety-critical issues. " +
"Provide specific, actionable recommendations. Be direct and technical — " +
"the user is an engineer, not a consumer."
}).then(function(response) {
return response.content[0].text;
});
}
function buildDiagnosticPrompt(issues, aggregates, systemProfile) {
var prompt = "## System Profile\n";
prompt += "- Array: " + systemProfile.arraySize + " (" +
systemProfile.panelCount + "x " + systemProfile.panelWatts + "W panels)\n";
prompt += "- Battery: " + systemProfile.batteryType + ", " +
systemProfile.batteryCapacity + "Ah at " +
systemProfile.systemVoltage + "V\n";
prompt += "- Inverter: " + systemProfile.inverterSize + "W\n";
prompt += "- Location: " + systemProfile.location + "\n";
prompt += "- Average daily load: " + systemProfile.avgDailyLoad + "Wh\n\n";
prompt += "## Active Diagnostic Flags\n";
for (var i = 0; i < issues.length; i++) {
prompt += "- [" + issues[i].severity.toUpperCase() + "] " +
issues[i].code + ": " + issues[i].message + "\n";
}
prompt += "\n## 14-Day Telemetry Summary\n";
prompt += "| Date | Min V | Max V | Avg V | Peak W | Float Min | Yield kWh | Errors |\n";
prompt += "|------|-------|-------|-------|--------|-----------|-----------|--------|\n";
var days = Math.min(aggregates.length, 14);
for (var j = 0; j < days; j++) {
var d = aggregates[j];
prompt += "| " + d.date + " | " + d.min_voltage.toFixed(1) +
" | " + d.max_voltage.toFixed(1) +
" | " + d.avg_voltage.toFixed(1) +
" | " + d.peak_power +
" | " + d.float_minutes +
" | " + (d.daily_yield / 1000).toFixed(2) +
" | " + d.error_count + " |\n";
}
prompt += "\nAnalyze this data. Identify root causes for the flagged issues. " +
"Recommend specific actions in priority order.";
return prompt;
}
I use Claude 3.5 Haiku for this because it's fast and cheap. Diagnostic analysis doesn't need frontier-model reasoning — it needs pattern matching against a well-structured prompt. Haiku handles that beautifully at a fraction of the cost.
Real Diagnostic Output
Here's an actual diagnostic report from my system during a stretch of cloudy weather in January:
SOLAR DIAGNOSTIC REPORT — January 18, 2026
============================================
ACTIVE ISSUES:
[WARNING] NO_FLOAT_CHARGE
Batteries haven't reached float charge in 5 days.
[WARNING] BAT_VOLTAGE_DECLINING
Average battery voltage declining 0.087V/day over the past week.
[ALERT] PANEL_OUTPUT_DEGRADED
Peak solar output down 62% vs 30 days ago.
AI ANALYSIS:
The combination of reduced solar output and declining battery voltage
is consistent with snow accumulation on the array. At your latitude
(62°N) in January, solar altitude is only 5-8°, meaning even partial
snow coverage has a disproportionate impact.
However, the 62% output reduction exceeds what snow alone typically
causes on a tilted array. Recommend checking for:
1. Snow/ice dam formation at the bottom edge of panels
2. Partial shading from nearby trees (shadow angles shift in deep winter)
3. One or more strings underperforming (check string voltages individually)
PRIORITY ACTION: Run generator for equalization charge before battery
voltage drops below 23.0V. Sulfation risk increases significantly
below this threshold for flooded lead-acid batteries at low temperatures.
That analysis was spot-on. I had a snow dam at the bottom of two panels that I hadn't noticed because the panels face south and I approach the cabin from the north side. The AI caught the pattern — output reduction too severe for simple snow coverage — that I might have missed for another week.
What the Data Teaches You
After six months of continuous monitoring, the patterns become obvious in hindsight.
Seasonal baselines matter. Comparing January performance to July performance is meaningless in Alaska. You need to compare January 2026 to January 2025. The diagnostic tool now maintains rolling 12-month baselines for each metric.
Battery capacity loss is gradual until it isn't. My data showed a slow decline in overnight voltage holdover — maybe 0.02V per month. Then over two weeks, it dropped 0.3V. That inflection point was the battery telling me it was approaching end of life. The AI flagged it as "accelerating capacity loss consistent with sulfation accumulation" three weeks before I would have noticed it manually.
Panel micro-cracks show up as current anomalies. You won't see them in voltage readings. You see them as slightly lower current output compared to adjacent strings under the same irradiance conditions. This is exactly the kind of subtle, multi-variable comparison that AI excels at.
function detectStringImbalance(stringReadings) {
var currents = stringReadings.map(function(s) { return s.current; });
var avgCurrent = average(currents);
var issues = [];
for (var i = 0; i < currents.length; i++) {
var deviation = Math.abs(currents[i] - avgCurrent) / avgCurrent;
if (deviation > 0.15) {
issues.push({
severity: "warning",
component: "solar",
code: "STRING_IMBALANCE",
message: "String " + (i + 1) + " current deviates " +
Math.round(deviation * 100) + "% from average. " +
"Possible micro-crack, connection issue, or bypass diode failure.",
data: {
string: i + 1,
current: currents[i],
average: avgCurrent,
deviation: deviation
}
});
}
}
return issues;
}
Extending This to Your Setup
You don't need to live off-grid in Alaska to use this pattern. Grid-tied solar systems benefit from the same diagnostics. Battery backup systems (increasingly common with utilities becoming less reliable) need monitoring. Even RV and van-life solar setups can use a simplified version.
The key components that transfer to any setup:
Data collection from your charge controller. Most modern controllers — Victron, Renogy, EPEver — expose data via serial, Bluetooth, or Wi-Fi. Get it into a database.
Baseline establishment. Run for 30 days without any diagnostics. Let the system learn what "normal" looks like for your specific installation.
Rule-based checks for the obvious stuff. Voltage thresholds, charge state tracking, error code monitoring. These don't need AI.
AI analysis for the subtle stuff. Pattern degradation over time, multi-variable anomalies, seasonal deviation from baseline. This is where the AI earns its keep.
The total cost of this system — a Raspberry Pi Zero W ($15), a USB-to-serial cable ($8), and Claude Haiku API calls running maybe $0.50/month — is negligible compared to the cost of a dead battery bank ($800+) or a surprise generator repair in January.
The Bigger Picture
I built AutoDetective.ai because I realized that diagnostic expertise shouldn't require an expert to be present. The same insight applies everywhere: car engines, HVAC systems, industrial equipment, and yes, the solar panel setup keeping my cabin warm at 62° north latitude.
The pattern is always the same. Collect data. Establish baselines. Apply deterministic rules for known failure modes. Hand the ambiguous cases to an AI model with enough context to reason about them. Present the results in a way that tells the human what to do, not just what's wrong.
I started this project because a 3 AM voltage alarm scared me. Six months later, I haven't had another surprise. The system tells me about problems when they're small and cheap to fix, not when they're big and expensive and it's -28°F outside.
That's not just good engineering. Out here, it might be survival.
Shane Larson is a software engineer and the founder of Grizzly Peak Software. He builds AI-powered diagnostic tools from his off-grid cabin in Caswell Lakes, Alaska, where the solar panels work harder than he does — at least during the summer months.