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(); | |
| } | |