#include "learning_manager.h" #include 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(); }