Spaces:
Running
Running
LearningManager::LearningManager() : | |
isLearningEnabled(false), | |
isFuelLearningEnabled(true), | |
isIgnitionLearningEnabled(true), | |
learningStartTime(0), | |
minLearningTime(300000), // 5 минут | |
minEngineTemp(60), // 60°C | |
maxEngineTemp(100), // 100°C | |
minThrottlePosition(5), // 5% | |
maxThrottlePosition(95), // 95% | |
totalLearningPoints(0), | |
knockEvents(0), | |
richMixtureEvents(0), | |
leanMixtureEvents(0) | |
{ | |
} | |
void LearningManager::begin() { | |
fuelTable.begin(); | |
ignitionTable.begin(); | |
// Загружаем сохраненные данные | |
load(); | |
// Устанавливаем параметры обучения | |
fuelTable.setLearningRate(0.01); | |
fuelTable.setCorrectionLimits(0.8, 1.2); | |
ignitionTable.setLearningRate(0.05); | |
ignitionTable.setCorrectionLimits(-5.0, 5.0); | |
ignitionTable.setKnockParameters(0.8, 2.0); | |
learningStartTime = millis(); | |
} | |
bool LearningManager::checkLearningConditions(float engineTemp, float throttlePosition) { | |
// Проверка времени работы двигателя | |
if (millis() - learningStartTime < minLearningTime) { | |
return false; | |
} | |
// Проверка температуры двигателя | |
if (engineTemp < minEngineTemp || engineTemp > maxEngineTemp) { | |
return false; | |
} | |
// Проверка положения дросселя | |
if (throttlePosition < minThrottlePosition || throttlePosition > maxThrottlePosition) { | |
return false; | |
} | |
return true; | |
} | |
void LearningManager::update(float load, float rpm, float lambda, float knockLevel, | |
float engineTemp, float throttlePosition) { | |
if (!isLearningEnabled || !checkLearningConditions(engineTemp, throttlePosition)) { | |
return; | |
} | |
// Обучение топливной карты | |
if (isFuelLearningEnabled) { | |
float lambdaError = lambda - 1.0; // Отклонение от стехиометрии | |
if (abs(lambdaError) > 0.02) { // Порог ошибки 2% | |
fuelTable.learn(load, rpm, lambdaError); | |
if (lambdaError > 0) { | |
richMixtureEvents++; | |
} else { | |
leanMixtureEvents++; | |
} | |
} | |
} | |
// Обучение карты УОЗ | |
if (isIgnitionLearningEnabled) { | |
ignitionTable.learn(load, rpm, knockLevel); | |
if (knockLevel > 0.8) { // Порог детонации | |
knockEvents++; | |
} | |
} | |
totalLearningPoints++; | |
} | |
float LearningManager::getFuelCorrection(float load, float rpm) { | |
return fuelTable.getCorrection(load, rpm); | |
} | |
float LearningManager::getIgnitionCorrection(float load, float rpm) { | |
return ignitionTable.getCorrection(load, rpm); | |
} | |
void LearningManager::setLearningParameters(float minTemp, float maxTemp, | |
float minThrottle, float maxThrottle) { | |
minEngineTemp = minTemp; | |
maxEngineTemp = maxTemp; | |
minThrottlePosition = minThrottle; | |
maxThrottlePosition = maxThrottle; | |
} | |
void LearningManager::setLearningRates(float fuelRate, float ignitionRate) { | |
fuelTable.setLearningRate(fuelRate); | |
ignitionTable.setLearningRate(ignitionRate); | |
} | |
bool LearningManager::save() { | |
bool fuelSaved = fuelTable.save(); | |
bool ignitionSaved = ignitionTable.save(); | |
// Сохраняем статистику | |
Preferences prefs; | |
prefs.begin("learning", false); | |
prefs.putUInt("total_points", totalLearningPoints); | |
prefs.putUInt("knock_events", knockEvents); | |
prefs.putUInt("rich_events", richMixtureEvents); | |
prefs.putUInt("lean_events", leanMixtureEvents); | |
prefs.end(); | |
return fuelSaved && ignitionSaved; | |
} | |
bool LearningManager::load() { | |
bool fuelLoaded = fuelTable.load(); | |
bool ignitionLoaded = ignitionTable.load(); | |
// Загружаем статистику | |
Preferences prefs; | |
prefs.begin("learning", true); | |
totalLearningPoints = prefs.getUInt("total_points", 0); | |
knockEvents = prefs.getUInt("knock_events", 0); | |
richMixtureEvents = prefs.getUInt("rich_events", 0); | |
leanMixtureEvents = prefs.getUInt("lean_events", 0); | |
prefs.end(); | |
return fuelLoaded && ignitionLoaded; | |
} | |
void LearningManager::reset() { | |
fuelTable.reset(); | |
ignitionTable.reset(); | |
totalLearningPoints = 0; | |
knockEvents = 0; | |
richMixtureEvents = 0; | |
leanMixtureEvents = 0; | |
save(); | |
} | |
void LearningManager::enable() { | |
isLearningEnabled = true; | |
learningStartTime = millis(); | |
} | |
void LearningManager::disable() { | |
isLearningEnabled = false; | |
} | |
uint32_t LearningManager::getTotalLearningPoints() const { | |
return totalLearningPoints; | |
} | |
uint32_t LearningManager::getKnockEvents() const { | |
return knockEvents; | |
} | |
uint32_t LearningManager::getRichMixtureEvents() const { | |
return richMixtureEvents; | |
} | |
uint32_t LearningManager::getLeanMixtureEvents() const { | |
return leanMixtureEvents; | |
} | |
float LearningManager::getLearningProgress() const { | |
// Максимальное количество точек обучения (16x16 ячеек, минимум 10 точек на ячейку) | |
const uint32_t maxPoints = 16 * 16 * 10; | |
return (float)totalLearningPoints / maxPoints * 100.0f; | |
} | |
String LearningManager::exportToJson() { | |
DynamicJsonDocument doc(8192); | |
// Основные параметры | |
doc["enabled"] = isLearningEnabled; | |
doc["fuel_enabled"] = isFuelLearningEnabled; | |
doc["ignition_enabled"] = isIgnitionLearningEnabled; | |
// Статистика | |
JsonObject stats = doc.createNestedObject("statistics"); | |
stats["total_points"] = totalLearningPoints; | |
stats["knock_events"] = knockEvents; | |
stats["rich_events"] = richMixtureEvents; | |
stats["lean_events"] = leanMixtureEvents; | |
stats["progress"] = getLearningProgress(); | |
// Топливная карта | |
JsonArray fuelMap = doc.createNestedArray("fuel_map"); | |
for (int i = 0; i < 16; i++) { | |
JsonArray row = fuelMap.createNestedArray(); | |
for (int j = 0; j < 16; j++) { | |
row.add(fuelTable.getCellValue(i, j)); | |
} | |
} | |
// Карта УОЗ | |
JsonArray ignitionMap = doc.createNestedArray("ignition_map"); | |
for (int i = 0; i < 16; i++) { | |
JsonArray row = ignitionMap.createNestedArray(); | |
for (int j = 0; j < 16; j++) { | |
row.add(ignitionTable.getCellValue(i, j)); | |
} | |
} | |
String output; | |
serializeJson(doc, output); | |
return output; | |
} | |
bool LearningManager::importFromJson(const String& json) { | |
DynamicJsonDocument doc(8192); | |
DeserializationError error = deserializeJson(doc, json); | |
if (error) { | |
return false; | |
} | |
// Временно отключаем обучение | |
bool wasEnabled = isLearningEnabled; | |
disable(); | |
// Сбрасываем текущие данные | |
reset(); | |
// Загружаем новые данные | |
if (doc.containsKey("enabled")) { | |
isLearningEnabled = doc["enabled"]; | |
} | |
if (doc.containsKey("statistics")) { | |
JsonObject stats = doc["statistics"]; | |
totalLearningPoints = stats["total_points"] | 0; | |
knockEvents = stats["knock_events"] | 0; | |
richMixtureEvents = stats["rich_events"] | 0; | |
leanMixtureEvents = stats["lean_events"] | 0; | |
} | |
// Восстанавливаем состояние обучения | |
if (wasEnabled) { | |
enable(); | |
} | |
// Сохраняем изменения | |
return save(); | |
} | |