diff --git a/Software/platformio.ini b/Software/platformio.ini index 35594ced..44b93849 100644 --- a/Software/platformio.ini +++ b/Software/platformio.ini @@ -41,6 +41,7 @@ build_flags = -D HTTPCLIENT_1_1_COMPATIBLE=0 ; This flag is used for Stroke Engine. -D DEBUG_TALKATIVE +targets = upload, monitor extends = common platform = espressif32 board = esp32dev diff --git a/Software/src/constants/Config.h b/Software/src/constants/Config.h index 988ae749..370f880d 100644 --- a/Software/src/constants/Config.h +++ b/Software/src/constants/Config.h @@ -14,8 +14,15 @@ namespace Config { */ namespace Driver { + // TODO: Configure values using saved data retrieved from EEPROM (or similar) + + // Whether the motor should set home inverted from default + constexpr bool invert = false; + // Top linear speed of the device. - constexpr float maxSpeedMmPerSecond = 900.0f; + + constexpr float maxSpeedMmPerSecond = 900.0f; // TODO: Still need to get these set dynamically on boot. + // e.g. OSSM::getAdvancedSettingValue(AdvancedConfigurationSettingName::MaxSpeed); // This should match the step/rev of your stepper or servo. // N.b. the iHSV57 has a table on the side for setting the DIP switches diff --git a/Software/src/constants/Menu.h b/Software/src/constants/Menu.h index 606ce0a4..8a765a50 100644 --- a/Software/src/constants/Menu.h +++ b/Software/src/constants/Menu.h @@ -11,6 +11,7 @@ enum Menu { UpdateOSSM, WiFiSetup, Help, + AdvancedConfiguration, Restart, NUM_OPTIONS }; @@ -21,6 +22,7 @@ static String menuStrings[Menu::NUM_OPTIONS] = { UserConfig::language.Update, UserConfig::language.WiFiSetup, UserConfig::language.GetHelp, + UserConfig::language.AdvancedConfiguration, UserConfig::language.Restart}; #endif // OSSM_SOFTWARE_MENU_H diff --git a/Software/src/constants/UserConfig.h b/Software/src/constants/UserConfig.h index 1826dbf1..3acb8e71 100644 --- a/Software/src/constants/UserConfig.h +++ b/Software/src/constants/UserConfig.h @@ -2,7 +2,7 @@ #define OSSM_SOFTWARE_USERCONFIG_H #include "constants/copy/en-us.h" -#include "constants/copy/fr.h" +//#include "constants/copy/fr.h" // Commented to help with memory issues namespace UserConfig { // TODO: restore user overrides. diff --git a/Software/src/constants/copy/en-us.h b/Software/src/constants/copy/en-us.h index 795aac69..87fd7a8c 100644 --- a/Software/src/constants/copy/en-us.h +++ b/Software/src/constants/copy/en-us.h @@ -3,55 +3,114 @@ #include "structs/LanguageStruct.h" +static const String enUs_DeepThroatTrainerSync PROGMEM = "DeepThroat Sync"; +static const String enUs_Error PROGMEM = "Error"; +static const String enUs_GetHelp PROGMEM = "Get Help"; +static const String enUs_GetHelpLine1 PROGMEM = "On Discord,"; +static const String enUs_GetHelpLine2 PROGMEM = "or GitHub"; +static const String enUs_Homing PROGMEM = "Homing"; +static const String enUs_HomingTookTooLong PROGMEM = + "Homing took too long. Please check your wiring and try again."; +static const String enUs_Idle PROGMEM = "Initializing"; +static const String enUs_InDevelopment PROGMEM = + "This feature is in development."; +static const String enUs_MeasuringStroke PROGMEM = "Measuring Stroke"; +static const String enUs_NoInternalLoop PROGMEM = + "No display handler implemented."; +static const String enUs_Restart PROGMEM = "Restart"; +static const String enUs_Settings PROGMEM = "Settings"; +static const String enUs_SimplePenetration PROGMEM = "Simple Penetration"; +static const String enUs_Skip PROGMEM = "Click to exit"; +static const String enUs_Speed PROGMEM = "Speed"; +static const String enUs_SpeedWarning PROGMEM = + "Decrease the speed to begin playing."; +static const String enUs_StateNotImplemented PROGMEM = + "State: %u not implemented."; +static const String enUs_Stroke PROGMEM = "Stroke"; +static const String enUs_StrokeEngine PROGMEM = "Stroke Engine"; +static const String enUs_StrokeTooShort PROGMEM = + "Stroke too short. Please check you drive belt."; +static const String enUs_Update PROGMEM = "Update"; +static const String enUs_UpdateMessage PROGMEM = + "Update is in progress. This may take up to 60s."; +static const String enUs_WiFi PROGMEM = "Wi-Fi"; +static const String enUs_WiFiSetup PROGMEM = "Wi-Fi Setup"; +static const String enUs_WiFiSetupLine1 PROGMEM = "Connect to"; +static const String enUs_WiFiSetupLine2 PROGMEM = "'Ossm Setup'"; +static const String enUs_YouShouldNotBeHere PROGMEM = "You should not be here."; +static const String enUs_AdvancedConfiguration PROGMEM = "Advanced Settings"; + +// stroke engine progmem +static const String enUs_StrokeEngineDescriptions[] PROGMEM = { + "Acceleration, coasting, deceleration equally split; no sensation.", + "Speed shifts with sensation; balances faster strokes.", + "Sensation varies acceleration; from robotic to gradual.", + "Full and half depth strokes alternate; sensation affects speed.", + "Stroke depth increases per cycle; sensation sets count.", + "Pauses between strokes; sensation adjusts length.", + "Modifies length, maintains speed; sensation influences direction."}; + +static const String enUs_StrokeEngineNames[] PROGMEM = { + "Simple Stroke", "Teasing Pounding", "Robo Stroke", "Half'n'Half", + "Deeper", "Stop'n'Go", "Insist"}; + +static const String enUs_AdvancedConfigurationSettingNames[] PROGMEM = { + "Adv. Settings ReadMe", "Motor Direction", "Steps Per Revolution", + "Pulley Teeth", "Max Speed", "Rail Length", + "Rapid Homing", "Homing Sensitivity", "Reset to Defaults", + "Apply Settings"}; + +static const String enUs_AdvancedConfigurationSettingDescriptions[] PROGMEM = { + "Long press to edit. \nShort press to set value. \nChanges revert " + "unless applied.", + "Homing dir toggle. \nFull depth should be penetration end away from " + "OSSM Head.", + "Set to match your motor register or dip switch setting.", + "Specify the number of teeth on your pulley.", + "Speed limit in millimeters per second.", + "The length of your rail in millimeters.", + "(Future)Feature toggle to run a faster homing procedure", + "This is a multiplier against idle current. \nHigher value will " + "overcome more friction.", + "Restore settings to default and restart OSSM? \n Yes: Long press \n " + "No: Short press", + "Save changes and restart OSSM? \n Yes: Long press \n No: Short press"}; + // English copy static const LanguageStruct enUs = { - .DeepThroatTrainerSync = "DeepThroat Sync", - .Error = "Error", - .GetHelp = "Get Help", - .GetHelpLine1 = "On Discord,", - .GetHelpLine2 = "or GitHub", - .Homing = "Homing", - .HomingTookTooLong = - "Homing took too long. Please check your wiring and try again.", - .Idle = "Initializing", - .InDevelopment = "This feature is in development.", - .MeasuringStroke = "Measuring Stroke", - .NoInternalLoop = "No display handler implemented.", - .Restart = "Restart", - .Settings = "Settings", - .SimplePenetration = "Simple Penetration", - .Skip = "Click to exit", - .Speed = "Speed", - .SpeedWarning = "Decrease the speed to begin playing.", - .StateNotImplemented = "State: %u not implemented.", - .Stroke = "Stroke", - .StrokeEngine = "Stroke Engine", - .StrokeTooShort = "Stroke too short. Please check you drive belt.", - .Update = "Update", - .UpdateMessage = "Update is in progress. This may take up to 60s.", - .WiFi = "Wi-Fi", - .WiFiSetup = "Wi-Fi Setup", - .WiFiSetupLine1 = "Connect to", - .WiFiSetupLine2 = "'Ossm Setup'", - .YouShouldNotBeHere = "You should not be here.", - .StrokeEngineDescriptions = { - "Acceleration, coasting, deceleration equally split; no sensation.", - "Speed shifts with sensation; balances faster strokes.", - "Sensation varies acceleration; from robotic to gradual.", - "Full and half depth strokes alternate; sensation affects speed.", - "Stroke depth increases per cycle; sensation sets count.", - "Pauses between strokes; sensation adjusts length.", - "Modifies length, maintains speed; sensation influences direction." - }, - .StrokeEngineNames = { - "Simple Stroke", - "Teasing Pounding", - "Robo Stroke", - "Half'n'Half", - "Deeper", - "Stop'n'Go", - "Insist" - }, -}; + .DeepThroatTrainerSync = enUs_DeepThroatTrainerSync, + .Error = enUs_Error, + .GetHelp = enUs_GetHelp, + .GetHelpLine1 = enUs_GetHelpLine1, + .GetHelpLine2 = enUs_GetHelpLine2, + .Homing = enUs_Homing, + .HomingTookTooLong = enUs_HomingTookTooLong, + .Idle = enUs_Idle, + .InDevelopment = enUs_InDevelopment, + .MeasuringStroke = enUs_MeasuringStroke, + .NoInternalLoop = enUs_NoInternalLoop, + .Restart = enUs_Restart, + .Settings = enUs_Settings, + .SimplePenetration = enUs_SimplePenetration, + .Skip = enUs_Skip, + .Speed = enUs_Speed, + .SpeedWarning = enUs_SpeedWarning, + .StateNotImplemented = enUs_StateNotImplemented, + .Stroke = enUs_Stroke, + .StrokeEngine = enUs_StrokeEngine, + .StrokeTooShort = enUs_StrokeTooShort, + .Update = enUs_Update, + .UpdateMessage = enUs_UpdateMessage, + .WiFi = enUs_WiFi, + .WiFiSetup = enUs_WiFiSetup, + .WiFiSetupLine1 = enUs_WiFiSetupLine1, + .WiFiSetupLine2 = enUs_WiFiSetupLine2, + .YouShouldNotBeHere = enUs_YouShouldNotBeHere, + .StrokeEngineDescriptions = enUs_StrokeEngineDescriptions, + .StrokeEngineNames = enUs_StrokeEngineNames, + .AdvancedConfiguration = enUs_AdvancedConfiguration, + .AdvancedConfigurationSettingNames = enUs_AdvancedConfigurationSettingNames, + .AdvancedConfigurationSettingDescriptions = + enUs_AdvancedConfigurationSettingDescriptions}; #endif // OSSM_SOFTWARE_EN_US_H diff --git a/Software/src/constants/copy/fr.h b/Software/src/constants/copy/fr.h index bcdee486..43d4e3bc 100644 --- a/Software/src/constants/copy/fr.h +++ b/Software/src/constants/copy/fr.h @@ -3,60 +3,115 @@ #include "structs/LanguageStruct.h" -// TODO: Requires validation by a native french speaker. -// These have been translated by Google Translate. -static const LanguageStruct fr = { - .DeepThroatTrainerSync = "DeepThroat Sync", - .Error = "Erreur", - .GetHelp = "Aide", - .GetHelpLine1 = "Sur Discord,", - .GetHelpLine2 = "ou GitHub", - .Homing = "FR - Homing", - .HomingTookTooLong = - "Le homing a pris trop de temps.Veuillez vérifier votre câblage et " - "réessayer.", - .Idle = "Inactif", - .InDevelopment = "Ceci est en développement.", - .MeasuringStroke = "Mesure de la course", - .NoInternalLoop = "Aucun gestionnaire d'affichage implémenté.", - .Restart = "Redémarrage", - .Settings = "Paramètres", - .SimplePenetration = "Pénétration simple", - .Skip = "Quitter ->", - .Speed = "Vitesse", - .SpeedWarning = "Réduisez la vitesse pour commencer à jouer.", - .StateNotImplemented = "État: %u non implémenté.", - .Stroke = "Coup", - .StrokeEngine = "Stroke Engine", - .StrokeTooShort = - "Course trop courte. Veuillez vérifier votre courroie d'entraînement.", - .Update = "Mettre à jour", - .UpdateMessage = - "La mise à jour est en cours. Ça peut prendre jusqu'à 60 secondes.", - .WiFi = "Wi-Fi", - .WiFiSetup = "Config. Wi-Fi", - .WiFiSetupLine1 = "Se connecter à", - .WiFiSetupLine2 = "'Ossm Setup'", - .YouShouldNotBeHere = "Vous ne devriez pas être ici.", - .StrokeEngineDescriptions = { - "Accélération, roulement, décélération également répartis ; sans sensation.", - "La vitesse change avec la sensation ; équilibre les coups rapides.", - "La sensation varie l'accélération ; de robotique à progressive.", - "Alternance de coups pleins et à demi-profondeur ; la sensation affecte la vitesse.", - "La profondeur des coups augmente à chaque cycle ; la sensation définit le nombre.", - "Pauses entre les coups ; la sensation ajuste la longueur.", - "Modifie la longueur, maintient la vitesse ; la sensation influe sur la direction.", - }, - .StrokeEngineNames = { - "Simple Stroke", - "Teasing Pounding", - "Robo Stroke", - "Half'n'Half", - "Deeper", - "Stop'n'Go", - "Insist", - } -}; +static const String fr_DeepThroatTrainerSync PROGMEM = "DeepThroat Sync"; +static const String fr_Error PROGMEM = "Erreur"; +static const String fr_GetHelp PROGMEM = "Aide"; +static const String fr_GetHelpLine1 PROGMEM = "Sur Discord,"; +static const String fr_GetHelpLine2 PROGMEM = "ou GitHub"; +static const String fr_Homing PROGMEM = "Calibrage"; +static const String fr_HomingTookTooLong PROGMEM = + "Le calibrage a pris trop de temps. Vérifiez le câblage et réessayez."; +static const String fr_Idle PROGMEM = "Initialisation"; +static const String fr_InDevelopment PROGMEM = + "Cette fonctionnalité est en développement."; +static const String fr_MeasuringStroke PROGMEM = "Mesure de la course"; +static const String fr_NoInternalLoop PROGMEM + "Aucun gestionnaire d'affichage implémenté."; +static const String fr_Restart PROGMEM = "Redémarrer"; +static const String fr_Settings PROGMEM = "Paramètres"; +static const String fr_SimplePenetration PROGMEM = "Pénétration simple"; +static const String fr_Skip PROGMEM = "Cliquez pour quitter"; +static const String fr_Speed PROGMEM = "Vitesse"; +static const String fr_SpeedWarning PROGMEM = + "Réduisez la vitesse pour commencer."; +static const String fr_StateNotImplemented PROGMEM = "État: %u non implémenté."; +static const String fr_Stroke PROGMEM = "Course"; +static const String fr_StrokeEngine PROGMEM = "Moteur de course"; +static const String fr_StrokeTooShort PROGMEM = + "Course trop courte. Vérifiez votre courroie d'entraînement."; +static const String fr_Update PROGMEM = "Mise à jour"; +static const String fr_UpdateMessage PROGMEM = + "Mise à jour en cours. Cela peut prendre jusqu'à 60s."; +static const String fr_WiFi PROGMEM = "Wi-Fi"; +static const String fr_WiFiSetup PROGMEM = "Configuration Wi-Fi"; +static const String fr_WiFiSetupLine1 PROGMEM = "Connectez-vous à"; +static const String fr_WiFiSetupLine2 PROGMEM = "'Ossm Setup'"; +static const String fr_YouShouldNotBeHere PROGMEM = + "Vous ne devriez pas être ici."; +static const String fr_AdvancedConfiguration PROGMEM = "Configuration avancée"; + +static const String fr_StrokeEngineDescriptions[] PROGMEM = { + "Accélération, glissement, décélération également répartis; sans " + "sensation.", + "La vitesse varie avec la sensation; équilibre les courses rapides.", + "La sensation varie l'accélération; de robotique à progressive.", + "Alterne courses complètes et mi-profondeur; sensation affecte la vitesse.", + "La profondeur augmente par cycle; sensation définit le nombre.", + "Pauses entre les courses; sensation ajuste la durée.", + "Modifie la longueur, maintient la vitesse; sensation influence la " + "direction."}; + +static const String fr_StrokeEngineNames[] PROGMEM = { + "Course Simple", "Frappe Taquine", "Course Robot", "Demi-Course", + "Plus Profond", "Arrêt-Reprise", "Insistant"}; +static const String fr_AdvancedConfigurationSettingNames[] PROGMEM = { + "Lisez-moi config. av.", "Direction moteur", "Pas par révolution", + "Dents poulie", "Vitesse max", "Longueur rail", + "Calibrage rapide", "Sensibilité calibrage", "Réinit. par défaut", + "Appliquer param."}; + +static const String fr_AdvancedConfigurationSettingDescriptions[] PROGMEM = { + "Appui long pour éditer.\nAppui court pour définir valeur.\nChangements " + "annulés si non appliqués.", + "Bascule direction calibrage.\nProfondeur max doit être côté pénétration " + "loin de la tête OSSM.", + "Réglez selon votre registre moteur ou réglage des commutateurs DIP.", + "Spécifiez le nombre de dents sur votre poulie.", + "Limite de vitesse en millimètres par seconde.", + "La longueur de votre rail en millimètres.", + "(Futur) Option pour exécuter une procédure de calibrage plus rapide", + "Multiplicateur du courant au repos.\nValeur plus élevée surmonte plus de " + "friction.", + "Restaurer paramètres par défaut et redémarrer OSSM?\nOui: Appui " + "long\nNon: Appui court", + "Sauvegarder changements et redémarrer OSSM?\nOui: Appui long\nNon: Appui " + "court"}; + +static const LanguageStruct fr = { + .DeepThroatTrainerSync = fr_DeepThroatTrainerSync, + .Error = fr_Error, + .GetHelp = fr_GetHelp, + .GetHelpLine1 = fr_GetHelpLine1, + .GetHelpLine2 = fr_GetHelpLine2, + .Homing = fr_Homing, + .HomingTookTooLong = fr_HomingTookTooLong, + .Idle = fr_Idle, + .InDevelopment = fr_InDevelopment, + .MeasuringStroke = fr_MeasuringStroke, + .NoInternalLoop = fr_NoInternalLoop, + .Restart = fr_Restart, + .Settings = fr_Settings, + .SimplePenetration = fr_SimplePenetration, + .Skip = fr_Skip, + .Speed = fr_Speed, + .SpeedWarning = fr_SpeedWarning, + .StateNotImplemented = fr_StateNotImplemented, + .Stroke = fr_Stroke, + .StrokeEngine = fr_StrokeEngine, + .StrokeTooShort = fr_StrokeTooShort, + .Update = fr_Update, + .UpdateMessage = fr_UpdateMessage, + .WiFi = fr_WiFi, + .WiFiSetup = fr_WiFiSetup, + .WiFiSetupLine1 = fr_WiFiSetupLine1, + .WiFiSetupLine2 = fr_WiFiSetupLine2, + .YouShouldNotBeHere = fr_YouShouldNotBeHere, + .StrokeEngineDescriptions = fr_StrokeEngineDescriptions, + .StrokeEngineNames = fr_StrokeEngineNames, + .AdvancedConfiguration = fr_AdvancedConfiguration, + .AdvancedConfigurationSettingNames = fr_AdvancedConfigurationSettingNames, + .AdvancedConfigurationSettingDescriptions = + fr_AdvancedConfigurationSettingDescriptions}; #endif // OSSM_SOFTWARE_FR_H diff --git a/Software/src/extensions/u8g2Extensions.h b/Software/src/extensions/u8g2Extensions.h index dbb3d8c0..2048f4f3 100644 --- a/Software/src/extensions/u8g2Extensions.h +++ b/Software/src/extensions/u8g2Extensions.h @@ -32,7 +32,7 @@ static int getUTF8CharLength(const unsigned char c) { * display. */ namespace drawStr { - static void centered(int y, String str) { + static void centered(int y, const String& str) { // Convert the String object to a UTF-8 string. // The c_str() function ensures we're passing a null-terminated string, // which is required by getStrWidth(). @@ -54,7 +54,7 @@ namespace drawStr { * @param y * @param str */ - static void multiLine(int x, int y, String string, int lineHeight = 12) { + static void multiLine(int x, int y, const String& string, int lineHeight = 12) { const char *str = string.c_str(); // Set the font for the text to be displayed. display.setFont(Config::Font::base); @@ -162,9 +162,9 @@ namespace drawStr { } } - static void title(String str) { + static void title(const String& str) { display.setFont(Config::Font::bold); - centered(8, std::move(str)); + centered(8, str); } }; diff --git a/Software/src/ossm/OSSM.AdvancedConfiguration.cpp b/Software/src/ossm/OSSM.AdvancedConfiguration.cpp new file mode 100644 index 00000000..1356bb74 --- /dev/null +++ b/Software/src/ossm/OSSM.AdvancedConfiguration.cpp @@ -0,0 +1,378 @@ +#include "OSSM.h" + +#include "extensions/u8g2Extensions.h" +#include "utils/analog.h" +#include "utils/format.h" +#include "nvs_flash.h" +#include + +#define STORAGE_NAMESPACE "AdvancedConfigurationSettings" +Preferences advancedConfigurationPrefs; + + +void saveSettings(AdvancedConfigurationSettings& settings) { + advancedConfigurationPrefs.begin("ACP", false); + bool tpInit = advancedConfigurationPrefs.isKey("nvsInit"); + String settingNameStr = ""; + if (tpInit == false) { + advancedConfigurationPrefs.putBool("nvsInit", true); + } + for (auto& setting : settings.settings) { + settingNameStr = getSettingName(setting.name).c_str(); + if(settingNameStr != ""){ + ESP_LOGD("saveSettings", "Adding setting %s with value %f", settingNameStr.c_str(), setting.currentValue()); + advancedConfigurationPrefs.putFloat(settingNameStr.c_str(), setting.savedValue.value_or(setting.defaultValue)); + ESP_LOGD("getSavedValue", "Found setting %s with value %f", settingNameStr.c_str(), advancedConfigurationPrefs.getFloat(settingNameStr.c_str(), -1.0f)); + } + } + advancedConfigurationPrefs.end(); +} + +std::optional getSavedValue(AdvancedConfigurationSettingName settingName) { + advancedConfigurationPrefs.begin("ACP", true); + float returnValue = -1.0f; + bool tpInit = advancedConfigurationPrefs.isKey("nvsInit"); + if (tpInit == false) { + ESP_LOGD("getSavedValue", "No settings found in NVS"); + return std::nullopt; + } + String settingNameStr = getSettingName(settingName).c_str(); + if(settingNameStr != "" && advancedConfigurationPrefs.isKey(settingNameStr.c_str())){ + ESP_LOGD("getSavedValue", "Found setting %s with value %f", settingNameStr.c_str(), advancedConfigurationPrefs.getFloat(settingNameStr.c_str(), returnValue)); + returnValue = advancedConfigurationPrefs.getFloat(settingNameStr.c_str(), returnValue); + } + advancedConfigurationPrefs.end(); + + return returnValue < 0 ? std::nullopt : std::make_optional(returnValue); +} + +AdvancedConfigurationSettings OSSM::getAdvancedSettings(){ + AdvancedConfigurationSettings config; + config.settings.push_back({ + .name = ReadMe, + .type = DataType::INFO, + .minValue = 0.0f, + .maxValue = 0.0f, + .settingPrecision = 0.0f, + .defaultValue = 0.0f, + .savedValue = std::nullopt + }); + + config.settings.push_back({ + .name = ToggleHomeDirection, + .type = DataType::BOOL, + .minValue = static_cast(false), + .maxValue = static_cast(true), + .settingPrecision = 1.0f, + .defaultValue = static_cast(false), + .savedValue = getSavedValue(ToggleHomeDirection) + }); + + config.settings.push_back({ + .name = StepsPerRev, + .type = DataType::NUMBER, + .minValue = 800.0f, + .maxValue = 51200.0f, + .settingPrecision = 200.0f, + .defaultValue = 800.0f, + .savedValue = getSavedValue(StepsPerRev) + }); + + config.settings.push_back({ + .name = PulleyTeeth, + .type = DataType::NUMBER, + .minValue = 8.0f, + .maxValue = 32.0f, + .settingPrecision = 1.0f, + .defaultValue = 20.0f, + .savedValue = getSavedValue(PulleyTeeth) + }); + + config.settings.push_back({ + .name = MaxSpeed, + .type = DataType::NUMBER, + .minValue = 0.0f, + .maxValue = 2000.0f, + .settingPrecision = 10.0f, + .defaultValue = 900.0f, + .savedValue = getSavedValue(MaxSpeed) + }); + + config.settings.push_back({ + .name = RailLength, + .type = DataType::NUMBER, + .minValue = 50.0f, + .maxValue = 1000.0f, + .settingPrecision = 5.0f, + .defaultValue = 350.0f, + .savedValue = getSavedValue(RailLength) + }); + + config.settings.push_back({ + .name = RapidHoming, + .type = DataType::BOOL, + .minValue = static_cast(false), + .maxValue = static_cast(true), + .settingPrecision = 1.0f, + .defaultValue = static_cast(false), + .savedValue = getSavedValue(RapidHoming) + }); + + config.settings.push_back({ + .name = HomingCurrentLimit, + .type = DataType::NUMBER, + .minValue = 1.0f, + .maxValue = 20.0f, + .settingPrecision = .05f, + .defaultValue = 1.5f, + .savedValue = getSavedValue(HomingCurrentLimit) + }); + + config.settings.push_back({ + .name = ResetToDefaults, + .type = DataType::ACTION, + .minValue = 0.0f, + .maxValue = 0.0f, + .settingPrecision = 0.0f, + .defaultValue = 0.0f, + .savedValue = std::nullopt + }); + + config.settings.push_back({ + .name = Apply, + .type = DataType::ACTION, + .minValue = 0.0f, + .maxValue = 0.0f, + .settingPrecision = 0.0f, + .defaultValue = 0.0f, + .savedValue = std::nullopt + }); + + return config; +} + +void OSSM::drawAdvancedConfigurationEditingTask(void *pvParameters) { + // parse ossm from the parameters + OSSM *ossm = (OSSM *)pvParameters; + + // Initialize the settings if they haven't been loaded yet + // TODO: Remove this once we are loading on boot. + if (ossm->advancedConfigurationSettings.settings.empty()) { + ossm->advancedConfigurationSettings = ossm->getAdvancedSettings(); + } + + // Load in the single setting we're editing + Setting currentAdvancedConfigurationSetting; + for (const auto& setting : ossm->advancedConfigurationSettings.settings) { + if ((int)setting.name == (int)ossm->activeAdvancedConfigurationSetting) { + currentAdvancedConfigurationSetting = setting; + break; + } + } + auto isInCorrectState = [](OSSM *ossm) { + // Add any states that you want to support here. + return ossm->sm->is("advancedConfiguration.settings.edit.editing"_s); + }; + bool isInActionState = currentAdvancedConfigurationSetting.type == DataType::ACTION; + bool isInInfoState = currentAdvancedConfigurationSetting.type == DataType::INFO; + + bool shouldUpdateDisplay = true; + bool actionPending = true; + String settingName = + UserConfig::language.AdvancedConfigurationSettingNames[(int)currentAdvancedConfigurationSetting.name]; + + static float valueForStorage = 0; + static float valueToEncoderClicks = 0; + + ossm->encoder.setAcceleration(10); + + // Initialize the edit view for the current setting + ossm->encoder.setBoundaries(currentAdvancedConfigurationSetting.minValue / currentAdvancedConfigurationSetting.settingPrecision, + currentAdvancedConfigurationSetting.maxValue / currentAdvancedConfigurationSetting.settingPrecision, + currentAdvancedConfigurationSetting.type == DataType::BOOL); + ossm->encoder.setEncoderValue((currentAdvancedConfigurationSetting.currentValue() / currentAdvancedConfigurationSetting.settingPrecision) - 1); + + while (isInCorrectState(ossm) && actionPending) { + + if(isInActionState) { + // Perform the action + switch (currentAdvancedConfigurationSetting.name) { + case ResetToDefaults: + // Reset the setting to the default value + displayMutex.lock(); + ossm->display.clearBuffer(); + //Draw the screen + drawStr::title(settingName); + drawStr::multiLine(0, 32, "Applying default settings... \nRestarting OSSM..."); + ossm->display.sendBuffer(); + displayMutex.unlock(); + + // TODO: Reset the settings to their default values + for (auto& setting : ossm->advancedConfigurationSettings.settings) { + setting.savedValue = std::nullopt; + } + saveSettings(ossm->advancedConfigurationSettings); + vTaskDelay(1000); + ESP.restart(); + + break; + case Apply: + // Save the setting to Flash + displayMutex.lock(); + ossm->display.clearBuffer(); + //Draw the screen + drawStr::title(settingName); + drawStr::multiLine(0, 32, "Saving settings... \nRestarting OSSM..."); + ossm->display.sendBuffer(); + displayMutex.unlock(); + + saveSettings(ossm->advancedConfigurationSettings); + + vTaskDelay(1000); + ESP.restart(); + + break; + default: + break; + } + actionPending = false; + } + else if (isInInfoState){ + // Do nothing, tell the user they should click to return. + displayMutex.lock(); + ossm->display.clearBuffer(); + //Draw the screen + drawStr::title(settingName); + drawStr::multiLine(0, 32, "Nothing to see here.. \nShort press to go back."); + ossm->display.sendBuffer(); + displayMutex.unlock(); + + actionPending = false; + } + + else { + shouldUpdateDisplay = + shouldUpdateDisplay || valueToEncoderClicks != ossm->encoder.readEncoder(); + + valueForStorage = (ossm->encoder.readEncoder()) * currentAdvancedConfigurationSetting.settingPrecision; + valueToEncoderClicks = ossm->encoder.readEncoder(); + + if (!shouldUpdateDisplay) { + vTaskDelay(100); + continue; + } + + // Search through ossm->advancedConfigurationSettings for the setting we're editing and update it + for (auto& setting : ossm->advancedConfigurationSettings.settings) { + if ((int)setting.name == (int)ossm->activeAdvancedConfigurationSetting) { + setting.savedValue = valueForStorage; + break; + } + } + + displayMutex.lock(); + ossm->display.clearBuffer(); + + String defaultValueString = " Default value: " + + (currentAdvancedConfigurationSetting.type == DataType::BOOL ? + (currentAdvancedConfigurationSetting.defaultValue == 0 ? "False" : "True") : + String(currentAdvancedConfigurationSetting.defaultValue, currentAdvancedConfigurationSetting.settingPrecision < 1 ? 2 : 0)); + String currentValueString = " Value: " + + (currentAdvancedConfigurationSetting.type == DataType::BOOL ? + (valueForStorage == 0 ? "False" : "True") : + String(valueForStorage, currentAdvancedConfigurationSetting.settingPrecision < 1 ? 2 : 0)); + + //Draw the screen + drawStr::title(settingName); + drawStr::multiLine(0, 32, defaultValueString.c_str()); + drawStr::multiLine(0, 44, currentValueString.c_str()); + drawShape::settingBar("", valueToEncoderClicks, 128, 0, RIGHT_ALIGNED, 0, + 0, + currentAdvancedConfigurationSetting.maxValue / currentAdvancedConfigurationSetting.settingPrecision); + + ossm->display.sendBuffer(); + displayMutex.unlock(); + } + + vTaskDelay(25); //8x faster than the normal menu, for improved feel of responsiveness when scrolling to input large values. + } + + vTaskDelete(nullptr); +}; + +void OSSM::drawAdvancedConfigurationMenuTask(void *pvParameters) { + // parse ossm from the parameters + OSSM *ossm = (OSSM *)pvParameters; + + if (ossm->advancedConfigurationSettings.settings.empty()) { + ossm->advancedConfigurationSettings = ossm->getAdvancedSettings(); + } + + size_t numberOfSettings = ossm->advancedConfigurationSettings.settings.size(); + + auto isInCorrectState = [](OSSM *ossm) { + return ossm->sm->is("advancedConfiguration.settings"_s); + }; + + int currentSelection = 0; + int nextSelection = 0; + bool shouldUpdateDisplay = true; + String settingName = "nextSelection"; + String settingDescription = + UserConfig::language.AdvancedConfigurationSettingDescriptions[nextSelection]; + + ossm->encoder.setAcceleration(10); + ossm->encoder.setBoundaries(0, numberOfSettings * 3 - 1, true); + ossm->encoder.setEncoderValue(nextSelection * 3); + + while (isInCorrectState(ossm)) { + nextSelection = ossm->encoder.readEncoder() / 3; + shouldUpdateDisplay = + shouldUpdateDisplay || currentSelection != nextSelection; + if (!shouldUpdateDisplay) { + vTaskDelay(100); + continue; + } + ossm->activeAdvancedConfigurationSetting = (AdvancedConfigurationSettingName)nextSelection; + + settingName = UserConfig::language.AdvancedConfigurationSettingNames[nextSelection].c_str(); + + if (nextSelection < numberOfSettings) { + settingDescription = + UserConfig::language.AdvancedConfigurationSettingDescriptions[nextSelection]; + } else { + settingDescription = "No description available"; + } + + currentSelection = nextSelection; + + displayMutex.lock(); + ossm->display.clearBuffer(); + + // Draw the title + drawStr::title(settingName); + drawStr::multiLine(0, 20, settingDescription); + drawShape::scroll(100 * nextSelection / numberOfSettings); + + ossm->display.sendBuffer(); + displayMutex.unlock(); + + + vTaskDelay(200); + } + + vTaskDelete(nullptr); +}; + +void OSSM::drawAdvancedConfiguration() { + int stackSize = 3 * configMINIMAL_STACK_SIZE; + xTaskCreate(drawAdvancedConfigurationMenuTask, "drawAdvancedConfigurationMenuTask", stackSize, + this, 1, &drawAdvancedConfigurationMenuTaskH); +} + +void OSSM::drawAdvancedConfigurationEditing() { + int stackSize = 3 * configMINIMAL_STACK_SIZE; + xTaskCreate(drawAdvancedConfigurationEditingTask, "drawAdvancedConfigurationEditingTask", stackSize, + this, 1, &drawAdvancedConfigurationEditingTaskH); +} + diff --git a/Software/src/ossm/OSSM.Homing.cpp b/Software/src/ossm/OSSM.Homing.cpp index c23e5f28..8520e11f 100644 --- a/Software/src/ossm/OSSM.Homing.cpp +++ b/Software/src/ossm/OSSM.Homing.cpp @@ -38,7 +38,7 @@ void OSSM::startHomingTask(void *pvParameters) { // Stroke Engine and Simple Penetration treat this differently. ossm->stepper->enableOutputs(); - ossm->stepper->setDirectionPin(Pins::Driver::motorDirectionPin, false); + ossm->stepper->setDirectionPin(Pins::Driver::motorDirectionPin, Config::Driver::invert); int16_t sign = ossm->sm->is("homing.backward"_s) ? 1 : -1; int32_t targetPositionInSteps = diff --git a/Software/src/ossm/OSSM.cpp b/Software/src/ossm/OSSM.cpp index 6ac7e2f8..5a4d70d1 100644 --- a/Software/src/ossm/OSSM.cpp +++ b/Software/src/ossm/OSSM.cpp @@ -120,6 +120,25 @@ void OSSM::drawHello() { &drawHelloTaskH); } +void OSSM::initializeAdvancedSettingsTask(void *pvParameters) { + // parse ossm from the parameters + OSSM *ossm = (OSSM *)pvParameters; + + if (ossm->advancedConfigurationSettings.settings.empty()) { + ossm->advancedConfigurationSettings = ossm->getAdvancedSettings(); + } + + // delete the task + vTaskDelete(nullptr); +} + +void OSSM::initializeAdvancedSettings() { + // 3 x minimum stack + int stackSize = 3 * configMINIMAL_STACK_SIZE; + xTaskCreate(initializeAdvancedSettingsTask, "initializeAdvancedSettings", stackSize, this, 1, + &initializeAdvancedSettingsTaskH); +} + void OSSM::drawError() { // Throw the e-break on the stepper try { diff --git a/Software/src/ossm/OSSM.h b/Software/src/ossm/OSSM.h index 18989216..b42a220b 100644 --- a/Software/src/ossm/OSSM.h +++ b/Software/src/ossm/OSSM.h @@ -13,6 +13,7 @@ #include "constants/Menu.h" #include "constants/Pins.h" #include "services/tasks.h" +#include "structs/AdvancedConfigurationSettings.h" #include "structs/SettingPercents.h" #include "utils/RecusiveMutex.h" #include "utils/StateLogger.h" @@ -57,6 +58,7 @@ class OSSM { struct OSSMStateMachine { auto operator()() const { // Action definitions to make the table easier to read. + auto initialize = [](OSSM &o) { o.initializeAdvancedSettings(); }; auto drawHello = [](OSSM &o) { o.drawHello(); }; auto drawMenu = [](OSSM &o) { o.drawMenu(); }; auto startHoming = [](OSSM &o) { @@ -110,6 +112,12 @@ class OSSM { o.stepper->disableOutputs(); }; auto drawHelp = [](OSSM &o) { o.drawHelp(); }; + auto drawAdvancedConfiguration = [](OSSM &o) { + o.drawAdvancedConfiguration(); + }; + auto drawAdvancedConfigurationEditing = [](OSSM &o) { + o.drawAdvancedConfigurationEditing(); + }; auto drawWiFi = [](OSSM &o) { o.drawWiFi(); }; auto drawUpdate = [](OSSM &o) { o.drawUpdate(); }; auto drawNoUpdate = [](OSSM &o) { o.drawNoUpdate(); }; @@ -140,8 +148,8 @@ class OSSM { return o.isStrokeTooShort(); }; - auto isOption = [](Menu option) { - return [option](OSSM &o) { return o.menuOption == option; }; + auto isOption = [](const Menu &option) { + return [&option](OSSM &o) { return o.menuOption == option; }; }; auto isPreflightSafe = [](OSSM &o) { @@ -163,11 +171,15 @@ class OSSM { auto setNotHomed = [](OSSM &o) { o.isHomed = false; }; return make_transition_table( - // clang-format off + // clang-format off + + //Always start by drawing the hello screen and then initializing settings + *"idle"_s + done / drawHello = "initialize"_s, + #ifdef DEBUG_SKIP_HOMING - *"idle"_s + done / drawHello = "menu"_s, + "initialize"_s + done / initialize = "menu"_s, #else - *"idle"_s + done / drawHello = "homing"_s, + "initialize"_s + done / initialize = "homing"_s, #endif "homing"_s / startHoming = "homing.forward"_s, @@ -185,6 +197,7 @@ class OSSM { "menu.idle"_s + buttonPress[(isOption(Menu::UpdateOSSM))] = "update"_s, "menu.idle"_s + buttonPress[(isOption(Menu::WiFiSetup))] = "wifi"_s, "menu.idle"_s + buttonPress[isOption(Menu::Help)] = "help"_s, + "menu.idle"_s + buttonPress[isOption(Menu::AdvancedConfiguration)] = "advancedConfiguration"_s, "menu.idle"_s + buttonPress[(isOption(Menu::Restart))] = "restart"_s, "simplePenetration"_s [isNotHomed] = "homing"_s, @@ -218,6 +231,12 @@ class OSSM { "help"_s / drawHelp = "help.idle"_s, "help.idle"_s + buttonPress = "menu"_s, + "advancedConfiguration"_s / drawAdvancedConfiguration = "advancedConfiguration.settings"_s, + "advancedConfiguration.settings"_s + buttonPress = "menu"_s, + "advancedConfiguration.settings"_s + longPress = "advancedConfiguration.settings.edit"_s, + "advancedConfiguration.settings.edit"_s / drawAdvancedConfigurationEditing = "advancedConfiguration.settings.edit.editing"_s, + "advancedConfiguration.settings.edit.editing"_s + buttonPress = "advancedConfiguration"_s, + "error"_s / drawError = "error.idle"_s, "error.idle"_s + buttonPress / drawHelp = "error.help"_s, "error.help"_s + buttonPress / restart = X, @@ -263,6 +282,11 @@ class OSSM { .depth = 50, .pattern = StrokePatterns::SimpleStroke}; + AdvancedConfigurationSettingName activeAdvancedConfigurationSetting = + AdvancedConfigurationSettingName::ReadMe; + AdvancedConfigurationSettings advancedConfigurationSettings = + AdvancedConfigurationSettings(); + unsigned long sessionStartTime = 0; int sessionStrokeCount = 0; double sessionDistanceMeters = 0; @@ -290,6 +314,10 @@ class OSSM { void drawHelp(); + void initializeAdvancedSettings(); + void drawAdvancedConfiguration(); + void drawAdvancedConfigurationEditing(); + void drawWiFi(); void drawMenu(); @@ -315,6 +343,11 @@ class OSSM { static void drawPlayControlsTask(void *pvParameters); static void drawPatternControlsTask(void *pvParameters); + static void drawAdvancedConfigurationMenuTask(void *pvParameters); + static void drawAdvancedConfigurationEditingTask(void *pvParameters); + static void initializeAdvancedSettingsTask(void *pvParameters); + static AdvancedConfigurationSettings getAdvancedSettings(); + void drawUpdate(); void drawNoUpdate(); @@ -340,6 +373,19 @@ class OSSM { sm = nullptr; // The state machine WiFiManager wm; + + float getAdvancedSettingValue( + AdvancedConfigurationSettingName settingName) { + for (auto &setting : advancedConfigurationSettings.settings) { + if (setting.name == settingName) { + ESP_LOGD("Initialize", "Configuring setting %s with value %f", + getSettingName(setting.name).c_str(), + setting.currentValue()); + return setting.currentValue(); + } + } + return 0.0f; // Default value if setting not found + } }; #endif // OSSM_SOFTWARE_OSSM_H diff --git a/Software/src/services/tasks.h b/Software/src/services/tasks.h index 07076383..1fe8abb4 100644 --- a/Software/src/services/tasks.h +++ b/Software/src/services/tasks.h @@ -17,6 +17,9 @@ static TaskHandle_t drawPlayControlsTaskH = nullptr; static TaskHandle_t drawPatternControlsTaskH = nullptr; static TaskHandle_t wmTaskH = nullptr; static TaskHandle_t drawPreflightTaskH = nullptr; +static TaskHandle_t drawAdvancedConfigurationMenuTaskH = nullptr; +static TaskHandle_t drawAdvancedConfigurationEditingTaskH = nullptr; +static TaskHandle_t initializeAdvancedSettingsTaskH = nullptr; static TaskHandle_t runHomingTaskH = nullptr; static TaskHandle_t runSimplePenetrationTaskH = nullptr; diff --git a/Software/src/structs/AdvancedConfigurationSettings.h b/Software/src/structs/AdvancedConfigurationSettings.h new file mode 100644 index 00000000..5045581b --- /dev/null +++ b/Software/src/structs/AdvancedConfigurationSettings.h @@ -0,0 +1,71 @@ +#ifndef SOFTWARE_ADVANCEDCONFIGURATIONSETTINGS_H +#define SOFTWARE_ADVANCEDCONFIGURATIONSETTINGS_H + +#include +#include + +enum class DataType { + NUMBER, + BOOL, + INFO, + ACTION +}; + +enum AdvancedConfigurationSettingName { + ReadMe, + //Add additional settings below this line only + ToggleHomeDirection, + StepsPerRev, + PulleyTeeth, + MaxSpeed, + RailLength, + RapidHoming, + HomingCurrentLimit, + //Add additional settings above this line only + ResetToDefaults, + Apply, + SettingCount +}; + + +inline std::string getSettingName(AdvancedConfigurationSettingName name) { + //Max 15 characters for use as NVS key + switch (name) { + case AdvancedConfigurationSettingName::ToggleHomeDirection: return "ToggleHomeDir"; + case AdvancedConfigurationSettingName::StepsPerRev: return "StepsPerRev"; + case AdvancedConfigurationSettingName::PulleyTeeth: return "PulleyTeeth"; + case AdvancedConfigurationSettingName::MaxSpeed: return "MaxSpeed"; + case AdvancedConfigurationSettingName::RailLength: return "RailLength"; + case AdvancedConfigurationSettingName::RapidHoming: return "RapidHoming"; + case AdvancedConfigurationSettingName::HomingCurrentLimit: return "HomingCurLimit"; + default: return ""; + } +} + +struct Setting { + AdvancedConfigurationSettingName name; + DataType type; + float minValue; + float maxValue; + float settingPrecision; + float defaultValue; + std::optional savedValue; + float currentValue() const { + return savedValue.value_or(defaultValue); + } +}; + +struct AdvancedConfigurationSettings { + std::vector settings; +}; + +struct SavedSetting { + AdvancedConfigurationSettingName name; + std::optional value; +}; + +struct SavedSettings { + std::vector savedValues; +}; + +#endif // SOFTWARE_ADVANCEDCONFIGURATIONSETTINGS_H diff --git a/Software/src/structs/LanguageStruct.h b/Software/src/structs/LanguageStruct.h index 8a5847a4..0c2b4811 100644 --- a/Software/src/structs/LanguageStruct.h +++ b/Software/src/structs/LanguageStruct.h @@ -2,36 +2,39 @@ #define OSSM_SOFTWARE_LANGUAGESTRUCT_H struct LanguageStruct { - String DeepThroatTrainerSync; - String Error; - String GetHelp; - String GetHelpLine1; - String GetHelpLine2; - String Homing; - String HomingTookTooLong; - String Idle; - String InDevelopment; - String MeasuringStroke; - String NoInternalLoop; - String Restart; - String Settings; - String SimplePenetration; - String Skip; - String Speed; - String SpeedWarning; - String StateNotImplemented; - String Stroke; - String StrokeEngine; - String StrokeTooShort; - String Update; - String UpdateMessage; - String WiFi; - String WiFiSetup; - String WiFiSetupLine1; - String WiFiSetupLine2; - String YouShouldNotBeHere; - String StrokeEngineDescriptions[7]; - String StrokeEngineNames[7]; + const String& DeepThroatTrainerSync; + const String& Error; + const String& GetHelp; + const String& GetHelpLine1; + const String& GetHelpLine2; + const String& Homing; + const String& HomingTookTooLong; + const String& Idle; + const String& InDevelopment; + const String& MeasuringStroke; + const String& NoInternalLoop; + const String& Restart; + const String& Settings; + const String& SimplePenetration; + const String& Skip; + const String& Speed; + const String& SpeedWarning; + const String& StateNotImplemented; + const String& Stroke; + const String& StrokeEngine; + const String& StrokeTooShort; + const String& Update; + const String& UpdateMessage; + const String& WiFi; + const String& WiFiSetup; + const String& WiFiSetupLine1; + const String& WiFiSetupLine2; + const String& YouShouldNotBeHere; + const String (&StrokeEngineDescriptions)[7]; + const String (&StrokeEngineNames)[7]; + const String& AdvancedConfiguration; + const String (&AdvancedConfigurationSettingNames)[10]; + const String (&AdvancedConfigurationSettingDescriptions)[10]; }; #endif // OSSM_SOFTWARE_LANGUAGESTRUCT_H diff --git a/Software/src/utils/StrokeEngineHelper.h b/Software/src/utils/StrokeEngineHelper.h index 2c5fbd38..f7098be0 100644 --- a/Software/src/utils/StrokeEngineHelper.h +++ b/Software/src/utils/StrokeEngineHelper.h @@ -23,7 +23,7 @@ static motorProperties servoMotor{ .stepsPerMillimeter = Config::Driver::motorStepPerRevolution / (Config::Driver::pulleyToothCount * Config::Driver::beltPitchMm), - .invertDirection = true, + .invertDirection = !Config::Driver::invert, .enableActiveLow = true, .stepPin = Pins::Driver::motorStepPin, .directionPin = Pins::Driver::motorDirectionPin, @@ -39,4 +39,4 @@ static float calculateSensation(float sensationPercentage) { } -#endif \ No newline at end of file +#endif