#include #include #ifdef FEATURE_ENABLE_OLED #include #endif #include #include #include #include #include "common.h" #include "lubeapp.h" #include "webui.h" #include "config.h" #include "globals.h" #ifdef FEATURE_ENABLE_CAN #include "can.h" #endif #ifdef FEATURE_ENABLE_GPS #include "gps.h" #endif #include "dtc.h" #ifdef FEATURE_ENABLE_REMOTE_DEBUG #include #include "rmtdbghelp.h" #else #define debugV Serial.println #define debugE Serial.println #endif #ifdef FEATURE_ENABLE_WIFI_CLIENT #include const char *ssid = QUOTE(WIFI_SSID); const char *password = QUOTE(WIFI_PASSWORD); const uint32_t connectTimeoutMs = 5000; ESP8266WiFiMulti wifiMulti; #endif bool startSetupMode = false; Globals_t globals; uint32_t TravelDistance_highRes; volatile uint32_t wheel_pulse = 0; CRGB leds[1]; // Function-Prototypes void IRAM_ATTR trigger_ISR(); void LED_Process(uint8_t override = false, CRGB setColor = CRGB::White); #ifdef FEATURE_ENABLE_OLED U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(-1); void Display_Process(); #endif void Button_Process(); void toggleWiFiAP(boolean shutdown = false); void SystemShutdown(); uint32_t Process_Impulse_WheelSpeed(); void EEPROMCyclicPDS_callback(); void initGlobals(); #ifdef FEATURE_ENABLE_REMOTE_DEBUG RemoteDebug Debug; String IpAddress2String(const IPAddress &ipAddress); void processCmdRemoteDebug(); void RemoteDebug_formatCFG(); void RemoteDebug_formatPersistence(); void RemotDebug_printSystemInfo(); void RemoteDebug_printWifiInfo(); void RemoteDebug_CheckEEPOM(); void RemoteDebug_dumpConfig(); void RemoteDebug_dumpPersistance(); void RemoteDebug_ShowDTCs(); #endif #ifdef FEATURE_ENABLE_WIFI_CLIENT void wifiMaintainConnectionTicker_callback(); Ticker WiFiMaintainConnectionTicker(wifiMaintainConnectionTicker_callback, 1000, 0, MILLIS); #endif Ticker EEPROMCyclicPDSTicker(EEPROMCyclicPDS_callback, 60000, 0, MILLIS); void setup() { system_update_cpu_freq(SYS_CPU_80MHZ); snprintf(globals.DeviceName, 32, HOST_NAME, ESP.getChipId()); WiFi.persistent(false); ClearAllDTC(); // Init DTC-Storage #ifdef FEATURE_ENABLE_WIFI_CLIENT WiFi.mode(WIFI_STA); WiFi.setHostname(globals.DeviceName); wifiMulti.addAP(QUOTE(WIFI_SSID), QUOTE(WIFI_PASSWORD)); WiFiMaintainConnectionTicker.start(); #else WiFi.mode(WIFI_OFF); #endif Serial.begin(115200); Serial.setDebugOutput(true); Serial.println("Souko's ChainLube Mk1"); Serial.println(globals.DeviceName); InitEEPROM(); GetConfig_EEPROM(); GetPersistence_EEPROM(); #ifdef FEATURE_ENABLE_OLED u8x8.begin(); u8x8.setFont(u8x8_font_chroma48medium8_r); #endif FastLED.addLeds(leds, 1); // GRB ordering is assumed switch (LubeConfig.SpeedSource) { case SOURCE_IMPULSE: pinMode(GPIO_TRIGGER, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(GPIO_TRIGGER), trigger_ISR, FALLING); break; #ifdef FEATURE_ENABLE_GPS case SOURCE_GPS: Init_GPS(); break; #endif case SOURCE_TIME: break; #ifdef FEATURE_ENABLE_CAN case SOURCE_CAN: Init_CAN(); break; #endif default: debugE("Source Setting N/A"); break; } pinMode(GPIO_BUTTON, INPUT_PULLUP); pinMode(GPIO_PUMP, OUTPUT); #ifdef FEATURE_ENABLE_REMOTE_DEBUG Debug.begin(globals.DeviceName); Debug.setResetCmdEnabled(true); Debug.showProfiler(false); Debug.showColors(true); Debug.setPassword(QUOTE(ADMIN_PASSWORD)); Debug.setSerialEnabled(true); Debug.showDebugLevel(true); Debug.setHelpProjectsCmds(helpCmd); Debug.setCallBackProjectCmds(&processCmdRemoteDebug); #endif ArduinoOTA.setPort(8266); ArduinoOTA.setHostname(globals.DeviceName); ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD)); #ifdef FEATURE_ENABLE_OLED ArduinoOTA.onStart([]() { u8x8.clearDisplay(); u8x8.drawString(0, 0, "OTA-Update"); u8x8.refreshDisplay(); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { static bool refreshed = false; if (!refreshed) { u8x8.clearDisplay(); refreshed = true; u8x8.drawString(0, 0, "OTA Upload"); } uint32_t percent = progress / (total / 100); u8x8.setCursor(0, 1); u8x8.printf("%d %%", percent); u8x8.refreshDisplay(); }); ArduinoOTA.onEnd([]() { u8x8.clearDisplay(); u8x8.drawString(0, 0, "OTA-Restart"); u8x8.refreshDisplay(); }); #endif ArduinoOTA.begin(); #ifdef FEATURE_ENABLE_OLED u8x8.clearDisplay(); u8x8.drawString(0, 0, "KTM ChainLube V1"); u8x8.refreshDisplay(); #endif initWebUI(); initGlobals(); EEPROMCyclicPDSTicker.start(); Serial.println("Setup Done"); } void loop() { uint32_t wheelDistance = 0; switch (LubeConfig.SpeedSource) { case SOURCE_IMPULSE: wheelDistance = Process_Impulse_WheelSpeed(); break; #ifdef FEATURE_ENABLE_CAN case SOURCE_CAN: wheelDistance = Process_CAN_WheelSpeed(); break; #endif case SOURCE_TIME: break; #ifdef FEATURE_ENABLE_GPS case SOURCE_GPS: wheelDistance = Process_GPS_WheelSpeed(); break; #endif } RunLubeApp(wheelDistance); EEPROMCyclicPDSTicker.update(); #ifdef FEATURE_ENABLE_OLED Display_Process(); #endif Button_Process(); LED_Process(); EEPROM_Process(); ArduinoOTA.handle(); #ifdef FEATURE_ENABLE_REMOTE_DEBUG Debug.handle(); #endif #ifdef FEATURE_ENABLE_WIFI_CLIENT WiFiMaintainConnectionTicker.update(); #endif if (globals.systemStatus == sysStat_Shutdown) SystemShutdown(); yield(); } String IpAddress2String(const IPAddress &ipAddress) { return String(ipAddress[0]) + String(".") + String(ipAddress[1]) + String(".") + String(ipAddress[2]) + String(".") + String(ipAddress[3]); } void initGlobals() { globals.purgePulses = 0; globals.requestEEAction = EE_IDLE; globals.resumeStatus = sysStat_Normal; globals.systemStatus = sysStat_Startup; } #ifdef FEATURE_ENABLE_REMOTE_DEBUG void processCmdRemoteDebug() { String lastCmd = Debug.getLastCommand(); if (lastCmd == "sysinfo") RemotDebug_printSystemInfo(); else if (lastCmd == "netinfo") RemoteDebug_printWifiInfo(); else if (lastCmd == "formatCFG") RemoteDebug_formatCFG(); else if (lastCmd == "formatPDS") RemoteDebug_formatPersistence(); else if (lastCmd == "checkEE") RemoteDebug_CheckEEPOM(); else if (lastCmd == "dumpEE1k") dumpEEPROM(0, 1024); else if (lastCmd == "dumpEE") dumpEEPROM(0, EEPROM_SIZE_BYTES); else if (lastCmd == "resetPageEE") MovePersistencePage_EEPROM(true); else if (lastCmd == "dumpCFG") RemoteDebug_dumpConfig(); else if (lastCmd == "dumpPDS") RemoteDebug_dumpPersistance(); else if (lastCmd == "saveEE") globals.requestEEAction == EE_ALL_SAVE; else if (lastCmd == "showdtc") RemoteDebug_ShowDTCs(); } void RemoteDebug_formatCFG() { debugA("Formatting Config-EEPROM and reseting to default"); FormatConfig_EEPROM(); } void RemoteDebug_formatPersistence() { debugA("Formatting Persistence-EEPROM and reseting to default"); FormatPersistence_EEPROM(); } void RemotDebug_printSystemInfo() { debugA("Souko's ChainOiler Mk1"); debugA("Hostname: %s", globals.DeviceName); FlashMode_t ideMode = ESP.getFlashChipMode(); debugA("Sdk version: %s", ESP.getSdkVersion()); debugA("Core Version: %s", ESP.getCoreVersion().c_str()); debugA("Boot Version: %u", ESP.getBootVersion()); debugA("Boot Mode: %u", ESP.getBootMode()); debugA("CPU Frequency: %u MHz", ESP.getCpuFreqMHz()); debugA("Reset reason: %s", ESP.getResetReason().c_str()); debugA("Flash Size: %d", ESP.getFlashChipRealSize()); debugA("Flash Size IDE: %d", ESP.getFlashChipSize()); debugA("Flash ide mode: %s", (ideMode == FM_QIO ? "QIO" : ideMode == FM_QOUT ? "QOUT" : ideMode == FM_DIO ? "DIO" : ideMode == FM_DOUT ? "DOUT" : "UNKNOWN")); debugA("OTA-Pass: %s", QUOTE(ADMIN_PASSWORD)); debugA("Git-Revison: %s", GIT_REV); debugA("Sw-Version: %d.%02d", SW_VERSION_MAJOR, SW_VERSION_MINOR); } void RemoteDebug_dumpConfig() { debugA("DistancePerLube_Default: %d", LubeConfig.DistancePerLube_Default); debugA("DistancePerLube_Rain: %d", LubeConfig.DistancePerLube_Rain); debugA("tankCapacity_ml: %d", LubeConfig.tankCapacity_ml); debugA("amountPerDose_µl: %d", LubeConfig.amountPerDose_µl); debugA("TankRemindAtPercentage: %d", LubeConfig.TankRemindAtPercentage); debugA("PulsePerRevolution: %d", LubeConfig.PulsePerRevolution); debugA("TireWidth_mm: %d", LubeConfig.TireWidth_mm); debugA("TireWidthHeight_Ratio: %d", LubeConfig.TireWidth_mm); debugA("RimDiameter_Inch: %d", LubeConfig.RimDiameter_Inch); debugA("DistancePerRevolution_mm: %d", LubeConfig.DistancePerRevolution_mm); debugA("BleedingPulses: %d", LubeConfig.BleedingPulses); debugA("SpeedSource: %d", LubeConfig.SpeedSource); #ifdef FEATURE_ENABLE_GPS debugA("GPSBaudRate: %d", LubeConfig.GPSBaudRate); #endif #ifdef FEATURE_ENABLE_CAN debugA("CANSource: %d", LubeConfig.CANSource); #endif debugA("checksum: 0x%08X", LubeConfig.checksum); } void RemoteDebug_dumpPersistance() { debugA("writeCycleCounter: %d", PersistenceData.writeCycleCounter); debugA("tankRemain_µl: %d", PersistenceData.tankRemain_µl); debugA("TravelDistance_highRes_mm: %d", PersistenceData.TravelDistance_highRes_mm); debugA("checksum: %d", PersistenceData.checksum); debugA("PSD Adress: 0x%04X", getPersistanceAddress()); } void RemoteDebug_printWifiInfo() { } void RemoteDebug_CheckEEPOM() { uint32_t checksum = PersistenceData.checksum; PersistenceData.checksum = 0; if (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) == checksum) { debugA("PersistenceData EEPROM Checksum OK\n"); } else { debugA("PersistenceData EEPROM Checksum BAD\n"); } PersistenceData.checksum = checksum; checksum = LubeConfig.checksum; LubeConfig.checksum = 0; if (Checksum_EEPROM((uint8_t *)&LubeConfig, sizeof(LubeConfig)) == checksum) { debugA("LubeConfig EEPROM Checksum OK\n"); } else { debugA("LubeConfig EEPROM Checksum BAD\n"); } LubeConfig.checksum = checksum; } #endif #ifdef FEATURE_ENABLE_WIFI_CLIENT void wifiMaintainConnectionTicker_callback() { static uint32_t WiFiFailCount = 0; const uint32_t WiFiFailMax = 20; if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED) { return; } else { if (WiFiFailCount < WiFiFailMax) { WiFiFailCount++; } else { debugV("WiFi not connected! - Start AP"); toggleWiFiAP(); } } } #endif void EEPROMCyclicPDS_callback() { StorePersistence_EEPROM(); } void trigger_ISR() { wheel_pulse++; } void LED_Process(uint8_t override, CRGB SetColor) { typedef enum { LED_Startup, LED_Normal, LED_Confirm_Normal, LED_Rain, LED_Confirm_Rain, LED_Purge, LED_Error, LED_Override } tLED_Status; static tSystem_Status oldSysStatus = sysStat_Startup; static tLED_Status LED_Status = LED_Startup; static CRGB LED_override_color = 0; static tLED_Status LED_ResumeOverrideStatus = LED_Startup; uint8_t color = 0; uint32_t timer = 0; static uint32_t timestamp = 0; timer = millis(); if (override == 1) { if (LED_Status != LED_Override) { LED_ResumeOverrideStatus = LED_Status; debugV("Override LED_Status"); } LED_Status = LED_Override; LED_override_color = SetColor; } if (override == 2) { if (LED_Status == LED_Override) { LED_Status = LED_ResumeOverrideStatus; debugV("Resume LED_Status"); } } if (oldSysStatus != globals.systemStatus) { switch (globals.systemStatus) { case sysStat_Startup: LED_Status = LED_Startup; debugV("sysStat: Startup"); break; case sysStat_Normal: timestamp = timer + 3500; LED_Status = LED_Confirm_Normal; debugV("sysStat: Normal"); break; case sysStat_Rain: timestamp = timer + 3500; LED_Status = LED_Confirm_Rain; debugV("sysStat: Rain"); break; case sysStat_Purge: LED_Status = LED_Purge; debugV("sysStat: Purge"); break; case sysStat_Error: LED_Status = LED_Error; debugV("sysStat: Error"); break; case sysStat_Shutdown: default: break; } oldSysStatus = globals.systemStatus; } uint32_t percentage = PersistenceData.tankRemain_µl / (LubeConfig.tankCapacity_ml * 10); switch (LED_Status) { case LED_Startup: FastLED.setBrightness(255); if (percentage < LubeConfig.TankRemindAtPercentage) leds[0] = CRGB::OrangeRed; else leds[0] = CRGB::White; break; case LED_Confirm_Normal: FastLED.setBrightness(255); leds[0] = timer % 250 > 125 ? CRGB(0, 255, 0) : CRGB(0, 4, 0); if (timestamp < timer) { LED_Status = LED_Normal; FastLED.setBrightness(64); debugV("LED_Status: Confirm -> Normal"); } break; case LED_Normal: if (timer % 2000 > 1950) leds[0] = CRGB(0, 255, 0); else if (WiFi.getMode() != WIFI_OFF && timer % 2000 > 1800 && timer % 2000 < 1850) leds[0] = CRGB(255, 128, 0); else leds[0] = CRGB(0, 4, 0); break; case LED_Confirm_Rain: FastLED.setBrightness(255); leds[0] = timer % 250 > 125 ? CRGB(0, 0, 255) : CRGB(0, 0, 4); if (timestamp < timer) { LED_Status = LED_Rain; FastLED.setBrightness(64); debugV("LED_Status: Confirm -> Rain"); } break; case LED_Rain: if (timer % 2000 > 1950) leds[0] = CRGB(0, 0, 255); else if (WiFi.getMode() != WIFI_OFF && timer % 2000 > 1800 && timer % 2000 < 1850) leds[0] = CRGB(255, 128, 0); else leds[0] = CRGB(0, 0, 4); break; case LED_Purge: timer = timer % 500; color = timer / 2; leds[0] = CRGB::DeepPink; if (timer < 250) FastLED.setBrightness(color); else FastLED.setBrightness(250 - color); break; case LED_Error: leds[0] = timer % 500 > 250 ? CRGB::Red : CRGB::Black; break; case LED_Override: leds[0] = LED_override_color; break; default: break; } FastLED.show(); } #ifdef FEATURE_ENABLE_OLED void Display_Process() { static tSystem_Status oldSysStatus = sysStat_Startup; if (oldSysStatus != globals.systemStatus) { u8x8.clearDisplay(); u8x8.drawString(0, 0, "KTM ChainLube V1"); oldSysStatus = globals.systemStatus; } u8x8.setCursor(0, 1); uint32_t DistRemain = globals.systemStatus == sysStat_Normal ? LubeConfig.DistancePerLube_Default : LubeConfig.DistancePerLube_Rain; DistRemain -= TravelDistance_highRes / 1000; u8x8.printf(PSTR("Mode: %10s\n"), globals.systemStatustxt); if (globals.systemStatus == sysStat_Error) { u8x8.printf(PSTR("last DTC: %6d\n"), getlastDTC(false)); } else { u8x8.printf(PSTR("next Lube: %4dm\n"), DistRemain); u8x8.printf(PSTR("Tank: %8dml\n"), PersistenceData.tankRemain_µl / 1000); u8x8.printf(PSTR("WiFi: %10s\n"), (WiFi.getMode() == WIFI_AP ? "AP" : WiFi.getMode() == WIFI_OFF ? "OFF" : WiFi.getMode() == WIFI_STA ? "CLIENT" : "UNKNOWN")); u8x8.printf(PSTR("Source: %8s\n"), SpeedSourceString[LubeConfig.SpeedSource]); u8x8.printf("%s\n", WiFi.localIP().toString().c_str()); } u8x8.refreshDisplay(); } #endif void Button_Process() { #define BUTTON_ACTION_DELAY_TOGGLEMODE 500 #define BUTTON_ACTION_DELAY_PURGE 3500 #define BUTTON_ACTION_DELAY_WIFI 6500 #define BUTTON_ACTION_DELAY_NOTHING 9500 typedef enum buttonAction_e { BTN_INACTIVE, BTN_NOTHING, BTN_TOGGLEMODE, BTN_TOGGLEWIFI, BTN_STARTPURGE } buttonAction_t; static uint32_t buttonTimestamp = 0; static buttonAction_t buttonAction = BTN_INACTIVE; if (digitalRead(GPIO_BUTTON) == LOW) { if (buttonTimestamp == 0) buttonTimestamp = millis(); if (buttonTimestamp + BUTTON_ACTION_DELAY_NOTHING < millis()) { LED_Process(1, CRGB::White); buttonAction = BTN_NOTHING; } else if (buttonTimestamp + BUTTON_ACTION_DELAY_WIFI < millis()) { LED_Process(1, CRGB::Yellow); buttonAction = BTN_TOGGLEWIFI; } else if (buttonTimestamp + BUTTON_ACTION_DELAY_PURGE < millis()) { LED_Process(1, CRGB::DeepPink); buttonAction = BTN_STARTPURGE; } else if (buttonTimestamp + BUTTON_ACTION_DELAY_TOGGLEMODE < millis()) { CRGB color = globals.systemStatus == sysStat_Normal ? CRGB::Blue : CRGB::Green; LED_Process(1, color); buttonAction = BTN_TOGGLEMODE; } } else { if (buttonAction != BTN_INACTIVE) { switch (buttonAction) { case BTN_TOGGLEWIFI: toggleWiFiAP(); debugV("Starting WiFi AP"); break; case BTN_STARTPURGE: globals.systemStatus = sysStat_Purge; globals.purgePulses = LubeConfig.BleedingPulses; debugV("Starting Purge"); break; case BTN_TOGGLEMODE: switch (globals.systemStatus) { case sysStat_Normal: globals.systemStatus = sysStat_Rain; globals.resumeStatus = sysStat_Rain; break; case sysStat_Rain: globals.systemStatus = sysStat_Normal; globals.resumeStatus = sysStat_Normal; break; default: break; } debugV("Toggling Mode"); break; case BTN_NOTHING: default: debugV("Nothing or invalid"); break; } LED_Process(2); } buttonAction = BTN_INACTIVE; buttonTimestamp = 0; } } void toggleWiFiAP(boolean shutdown) { if (WiFi.getMode() != WIFI_OFF) { WiFi.mode(WIFI_OFF); debugV("WiFi turned off"); #ifdef FEATURE_ENABLE_WIFI_CLIENT WiFiMaintainConnectionTicker.stop(); #endif } else { WiFi.mode(WIFI_AP); WiFi.softAPConfig(IPAddress(WIFI_AP_IP_GW), IPAddress(WIFI_AP_IP_GW), IPAddress(255, 255, 255, 0)); WiFi.softAP(globals.DeviceName, QUOTE(WIFI_AP_PASSWORD)); #ifdef FEATURE_ENABLE_WIFI_CLIENT WiFiMaintainConnectionTicker.stop(); debugV("WiFi AP started, stopped Maintain-Timer"); #else debugV("WiFi AP started"); #endif } } void SystemShutdown() { StoreConfig_EEPROM(); ESP.restart(); } uint32_t Process_Impulse_WheelSpeed() { uint32_t add_milimeters; // Calculate traveled Distance in mm add_milimeters = (wheel_pulse * (LubeConfig.DistancePerRevolution_mm / LubeConfig.PulsePerRevolution)); wheel_pulse = 0; return add_milimeters; } #ifdef FEATURE_ENABLE_REMOTE_DEBUG void RemoteDebug_ShowDTCs() { char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx char buff_active[9]; for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++) { if (DTCStorage[i].Number < DTC_LAST_DTC) { sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d", DTCStorage[i].timestamp / 86400000, // Days DTCStorage[i].timestamp / 360000 % 24, // Hours DTCStorage[i].timestamp / 60000 % 60, // Minutes DTCStorage[i].timestamp / 1000 % 60, // Seconds DTCStorage[i].timestamp % 1000); // milliseconds if (DTCStorage[i].active == DTC_ACTIVE) strcpy(buff_active, "active"); else if (DTCStorage[i].active == DTC_PREVIOUS) strcpy(buff_active, "previous"); else strcpy(buff_active, "none"); debugA("%s \t %6d \t %s", buff_timestamp, DTCStorage[i].Number, buff_active); } } } #endif