diff --git a/Changelog.md b/Changelog.md index c6ffe015..0a0e8610 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,6 @@ +**V1.13.12 - Updates** +- Added basic test mode that can be run via terminal connection. + **V1.13.11 - Updates** - Fixed DEBUG macro usage. - Clarified some Meade documentation. diff --git a/Version.h b/Version.h index b3d4510f..03b470da 100644 --- a/Version.h +++ b/Version.h @@ -3,4 +3,4 @@ // Also, numbers are interpreted as simple numbers. _ __ _ // So 1.8 is actually 1.08, meaning that 1.12 is a later version than 1.8. \_(..)_/ -#define VERSION "V1.13.11" +#define VERSION "V1.13.12" diff --git a/matrix_build.py b/matrix_build.py index 6dc9622c..ee0c035e 100644 --- a/matrix_build.py +++ b/matrix_build.py @@ -57,6 +57,7 @@ "FOCUS_STEPPER_TYPE": STEPPER_TYPES, "FOCUS_DRIVER_TYPE": DRIVER_TYPES, "DISPLAY_TYPE": DISPLAY_TYPES, + "TEST_VERIFY_MODE": BOOLEAN_VALUES, "DEBUG_LEVEL": ["DEBUG_NONE", "DEBUG_ANY"], "RA_MOTOR_CURRENT_RATING": "1", "RA_OPERATING_CURRENT_SETTING": "1", diff --git a/src/Mount.cpp b/src/Mount.cpp index f498c018..f3628e8d 100644 --- a/src/Mount.cpp +++ b/src/Mount.cpp @@ -413,23 +413,56 @@ void Mount::configureFocusStepper(byte pin1, byte pin2, int maxSpeed, int maxAcc #if RA_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART || DEC_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART \ || AZ_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART || ALT_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART \ || FOCUS_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART - #if UART_CONNECTION_TEST_TXRX == 1 -bool Mount::connectToDriver(TMC2209Stepper *driver, const char *driverKind) + #if (UART_CONNECTION_TEST_TXRX == 1) || (TEST_VERIFY_MODE == 1) +bool Mount::connectToDriver(const String &driverKind, uint16_t *rmsCurrent) { - LOG(DEBUG_STEPPERS, "[STEPPERS]: Testing UART Connection to %s driver...", driverKind); - for (int i = 0; i < UART_CONNECTION_TEST_RETRIES; i++) + MappedDict::DictEntry_t lookupTable[] = { + #if RA_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART + {"RA", _driverRA}, + #endif + #if DEC_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART + {"DEC", _driverDEC}, + #endif + #if ALT_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART + {"ALT", _driverALT}, + #endif + #if AZ_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART + {"AZ", _driverAZ}, + #endif + #if FOCUS_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART + {"FOC", _driverFocus}, + #endif + }; + auto driverLookup = MappedDict(lookupTable, ARRAY_SIZE(lookupTable)); + + TMC2209Stepper *driver = nullptr; + driverLookup.tryGet(driverKind, &driver); + + if (driver != nullptr) { - if (driver->test_connection() == 0) + LOG(DEBUG_STEPPERS, "[STEPPERS]: Testing UART Connection to %s driver...", driverKind.c_str()); + for (int i = 0; i < UART_CONNECTION_TEST_RETRIES; i++) { - LOG(DEBUG_STEPPERS, "[STEPPERS]: UART connection to %s driver successful.", driverKind); - return true; - } - else - { - delay(500); + if (driver->test_connection() == 0) + { + LOG(DEBUG_STEPPERS, "[STEPPERS]: UART connection to %s driver successful.", driverKind.c_str()); + if (rmsCurrent != nullptr) + { + *rmsCurrent = driver->rms_current(); + } + return true; + } + else + { + delay(500); + } } + LOG(DEBUG_STEPPERS, "[STEPPERS]: UART connection to %s driver failed.", driverKind.c_str()); + } + if (rmsCurrent != nullptr) + { + *rmsCurrent = 0; } - LOG(DEBUG_STEPPERS, "[STEPPERS]: UART connection to %s driver failed.", driverKind); return false; } #endif @@ -448,7 +481,7 @@ void Mount::configureRAdriver(Stream *serial, float rsense, byte driveraddress, _driverRA->begin(); bool UART_Rx_connected = false; #if UART_CONNECTION_TEST_TXRX == 1 - UART_Rx_connected = connectToDriver(_driverRA, "RA"); + UART_Rx_connected = connectToDriver("RA"); if (!UART_Rx_connected) { digitalWrite(RA_EN_PIN, @@ -487,7 +520,7 @@ void Mount::configureRAdriver(uint16_t RA_SW_RX, uint16_t RA_SW_TX, float rsense _driverRA->pdn_disable(true); bool UART_Rx_connected = false; #if UART_CONNECTION_TEST_TXRX == 1 - UART_Rx_connected = connectToDriver(_driverRA, "RA"); + UART_Rx_connected = connectToDriver("RA"); if (!UART_Rx_connected) { digitalWrite(RA_EN_PIN, @@ -531,7 +564,7 @@ void Mount::configureDECdriver(Stream *serial, float rsense, byte driveraddress, _driverDEC->begin(); bool UART_Rx_connected = false; #if UART_CONNECTION_TEST_TXRX == 1 - UART_Rx_connected = connectToDriver(_driverDEC, "DEC"); + UART_Rx_connected = connectToDriver("DEC"); if (!UART_Rx_connected) { digitalWrite(DEC_EN_PIN, @@ -570,7 +603,7 @@ void Mount::configureDECdriver(uint16_t DEC_SW_RX, uint16_t DEC_SW_TX, float rse _driverDEC->pdn_disable(true); bool UART_Rx_connected = false; #if UART_CONNECTION_TEST_TXRX == 1 - UART_Rx_connected = connectToDriver(_driverDEC, "DEC"); + UART_Rx_connected = connectToDriver("DEC"); if (!UART_Rx_connected) { digitalWrite(DEC_EN_PIN, @@ -614,7 +647,7 @@ void Mount::configureAZdriver(Stream *serial, float rsense, byte driveraddress, _driverAZ->begin(); bool UART_Rx_connected = false; #if UART_CONNECTION_TEST_TXRX == 1 - UART_Rx_connected = connectToDriver(_driverAZ, "AZ"); + UART_Rx_connected = connectToDriver("AZ"); if (!UART_Rx_connected) { digitalWrite(AZ_EN_PIN, @@ -652,7 +685,7 @@ void Mount::configureAZdriver(uint16_t AZ_SW_RX, uint16_t AZ_SW_TX, float rsense _driverAZ->pdn_disable(true); bool UART_Rx_connected = false; #if UART_CONNECTION_TEST_TXRX == 1 - UART_Rx_connected = connectToDriver(_driverAZ, "AZ"); + UART_Rx_connected = connectToDriver("AZ"); if (!UART_Rx_connected) { digitalWrite(AZ_EN_PIN, @@ -695,7 +728,7 @@ void Mount::configureALTdriver(Stream *serial, float rsense, byte driveraddress, _driverALT->begin(); bool UART_Rx_connected = false; #if UART_CONNECTION_TEST_TXRX == 1 - UART_Rx_connected = connectToDriver(_driverALT, "ALT"); + UART_Rx_connected = connectToDriver("ALT"); if (!UART_Rx_connected) { digitalWrite(ALT_EN_PIN, @@ -733,7 +766,7 @@ void Mount::configureALTdriver(uint16_t ALT_SW_RX, uint16_t ALT_SW_TX, float rse _driverALT->pdn_disable(true); #if UART_CONNECTION_TEST_TXRX == 1 bool UART_Rx_connected = false; - UART_Rx_connected = connectToDriver(_driverALT, "ALT"); + UART_Rx_connected = connectToDriver("ALT"); if (!UART_Rx_connected) { digitalWrite(ALT_EN_PIN, @@ -778,10 +811,10 @@ void Mount::configureFocusDriver(Stream *serial, float rsense, byte driveraddres _driverFocus->begin(); #if UART_CONNECTION_TEST_TXRX == 1 bool UART_Rx_connected = false; - UART_Rx_connected = connectToDriver(_driverFocus, "Focus"); + UART_Rx_connected = connectToDriver("FOC"); if (!UART_Rx_connected) { - digitalWrite(ALT_EN_PIN, + digitalWrite(FOCUS_EN_PIN, HIGH); //Disable motor for safety reasons if UART connection fails to avoid operating at incorrect rms_current } #endif @@ -824,7 +857,7 @@ void Mount::configureFocusDriver( _driverFocus->pdn_disable(true); #if UART_CONNECTION_TEST_TXRX == 1 bool UART_Rx_connected = false; - UART_Rx_connected = connectToDriver(_driverFocus, "Focus"); + UART_Rx_connected = connectToDriver("FOC"); if (!UART_Rx_connected) { digitalWrite(FOCUS_EN_PIN, diff --git a/src/Mount.hpp b/src/Mount.hpp index 8a2d63ec..80d53351 100644 --- a/src/Mount.hpp +++ b/src/Mount.hpp @@ -154,8 +154,6 @@ class Mount void initializeVariables(); - static Mount instance(); - // Configure the RA stepper motor. This also sets up the TRK stepper on the same pins. void configureRAStepper(byte pin1, byte pin2, uint32_t maxSpeed, uint32_t maxAcceleration); @@ -182,7 +180,8 @@ class Mount #if RA_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART || DEC_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART \ || AZ_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART || ALT_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART \ || FOCUS_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART - bool connectToDriver(TMC2209Stepper *driver, const char *driverKind); + bool connectToDriver(const String &driverKind, uint16_t *rmsCurrent = nullptr); + #endif #if RA_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART // Configure the RA Driver (TMC2209 UART only) diff --git a/src/Utility.cpp b/src/Utility.cpp index 46002547..7937dda4 100644 --- a/src/Utility.cpp +++ b/src/Utility.cpp @@ -226,6 +226,46 @@ float atanf(float x) return static_cast(atan(static_cast(x))); } +String *splitStringBy(String str, char splitChar) +{ + unsigned int count = 1; // At least one string if input is non-empty + + // Count occurrences of splitChar to determine the number of splits + for (unsigned int i = 0; i < str.length(); i++) + { + if (str[i] == splitChar) + { + count++; + } + } + + // Dynamically allocate memory for the resulting array + String *array = new String[count + 1]; // +1 for the nullptr terminator + unsigned int r = 0; // Start of the substring + unsigned int t = 0; // Index in the result array + + // Iterate through the string to split it + for (unsigned int i = 0; i < str.length(); i++) + { + if (str[i] == splitChar) + { + array[t++] = str.substring(r, i); // Store substring + r = i + 1; // Move start to next character + } + } + + // Add the last part of the string + if (r < str.length()) + { + array[t++] = str.substring(r); + } + + // Mark the end of the array with "" + array[t] = ""; + + return array; +} + #if defined(ESP32) int freeMemory() { diff --git a/src/Utility.hpp b/src/Utility.hpp index cebf3a08..d8801ebd 100644 --- a/src/Utility.hpp +++ b/src/Utility.hpp @@ -173,5 +173,11 @@ int sign(long num); int fsign(float num); #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) +#define CASERETURN(c, r) \ + case c: \ + return r + +// Return an array of String* with a nullptr sentinel +String *splitStringBy(String str, char splitChar); #endif diff --git a/src/b_setup.hpp b/src/b_setup.hpp index 310716d2..f2b5dac7 100644 --- a/src/b_setup.hpp +++ b/src/b_setup.hpp @@ -12,6 +12,10 @@ PUSH_NO_WARNINGS POP_NO_WARNINGS #endif +#if TEST_VERIFY_MODE == 1 + #include "testmenu.hpp" +#endif + #ifndef NEW_STEPPER_LIB #include "InterruptCallback.hpp" #endif @@ -111,7 +115,21 @@ void setup() #endif #endif - LOG(DEBUG_ANY, "[SYSTEM]: Hello, universe, this is OAT %s!", VERSION); +#if TEST_VERIFY_MODE == 1 + #ifdef OAM + Serial.print(F("Booting OAM Firmware ")); + #else + Serial.print(F("Booting OAT Firmware ")); + #endif + Serial.print(VERSION); + Serial.println(F(" ...")); +#else + #ifdef OAM + LOG(DEBUG_ANY, "[SYSTEM]: Hello, universe, this is OAM Firmware %s!", VERSION); + #else + LOG(DEBUG_ANY, "[SYSTEM]: Hello, universe, this is OAT Firmware %s!", VERSION); + #endif +#endif #if (INFO_DISPLAY_TYPE != INFO_DISPLAY_TYPE_NONE) LOG(DEBUG_ANY, "[SYSTEM]: Get OLED info screen ready..."); @@ -522,4 +540,7 @@ void setup() delay(250); mount.getInfoDisplay()->setConsoleMode(false); #endif +#if TEST_VERIFY_MODE == 1 + TestMenu::getCurrentMenu()->display(); +#endif } diff --git a/src/f_serial.hpp b/src/f_serial.hpp index 9d6e8dd4..ba4283f4 100644 --- a/src/f_serial.hpp +++ b/src/f_serial.hpp @@ -1,5 +1,10 @@ #pragma once +#if TEST_VERIFY_MODE == 1 + #include "testmenu.hpp" + #include "testmenudef.hpp" +#endif + #include "b_setup.hpp" #if SUPPORT_SERIAL_CONTROL == 1 @@ -21,6 +26,10 @@ void serialLoop() #if (WIFI_ENABLED == 1) wifiControl.loop(); #endif + + #if TEST_VERIFY_MODE == 1 + mainTestMenu.tick(); + #endif } ////////////////////////////////////////////////// @@ -32,6 +41,71 @@ void serialEvent() } #endif + #if TEST_VERIFY_MODE == 1 + +void processTestState() +{ + static char buffer[32]; + static unsigned int index = 0; + switch (TestMenu::getMenuState()) + { + case testMenuState_t::CLEAR: + TestMenu::setMenuState(testMenuState_t::WAITING_ON_INPUT); + break; + + case testMenuState_t::WAITING_ON_INPUT: + while (Serial.available() > 0) + { + if (Serial.readBytes(buffer, 1) == 1) + { + if ((buffer[0] >= '0') && (buffer[0] <= '9')) + { + Serial.println(buffer[0]); + int pressedKey = buffer[0] - '0'; + TestMenu::getCurrentMenu()->onKeyPressed(pressedKey); + } + } + } + break; + + case testMenuState_t::WAITING_ON_COMMAND: + while (Serial.available() > 0) + { + char ch; + if (Serial.readBytes(&ch, 1) == 1) + { + if (isascii(ch)) + { + buffer[index] = ch; + if (ch == '#') + { + buffer[index + 1] = '\0'; + TestMenu::getCurrentMenu()->onCommandReceived(buffer); + TestMenu::setMenuState(testMenuState_t::WAITING_ON_INPUT); + TestMenu::getCurrentMenu()->display(); + index = 0; + } + else + { + index++; + if (index > ARRAY_SIZE(buffer) - 1) + { + Serial.println(F("Buffer overflow, too many chars received")); + index = 0; + } + } + } + } + } + break; + } +} + +void processSerialData() +{ + processTestState(); +} + #else // ESP needs to call this in a loop :_( void processSerialData() { @@ -43,11 +117,11 @@ void processSerialData() if (buffer[0] == 0x06) { LOG(DEBUG_SERIAL, "[SERIAL]: Received: ACK request, replying P"); - // When not debugging, print the result to the serial port . - // When debugging, only print the result to Serial if we're on seperate ports. - #if (DEBUG_LEVEL == DEBUG_NONE) || (DEBUG_SEPARATE_SERIAL == 1) + // When not debugging, print the result to the serial port . + // When debugging, only print the result to Serial if we're on seperate ports. + #if (DEBUG_LEVEL == DEBUG_NONE) || (DEBUG_SEPARATE_SERIAL == 1) Serial.print('P'); - #endif + #endif } else { @@ -58,11 +132,11 @@ void processSerialData() if (retVal != "") { LOG(DEBUG_SERIAL, "[SERIAL]: RepliedWith: [%s]", retVal.c_str()); - // When not debugging, print the result to the serial port . - // When debugging, only print the result to Serial if we're on seperate ports. - #if (DEBUG_LEVEL == DEBUG_NONE) || (DEBUG_SEPARATE_SERIAL == 1) + // When not debugging, print the result to the serial port . + // When debugging, only print the result to Serial if we're on seperate ports. + #if (DEBUG_LEVEL == DEBUG_NONE) || (DEBUG_SEPARATE_SERIAL == 1) Serial.print(retVal); - #endif + #endif } else { @@ -75,4 +149,5 @@ void processSerialData() } } + #endif #endif diff --git a/src/testmenu.cpp b/src/testmenu.cpp new file mode 100644 index 00000000..6620f38c --- /dev/null +++ b/src/testmenu.cpp @@ -0,0 +1,663 @@ +#include "../Configuration.hpp" +#include "Utility.hpp" +#include "Mount.hpp" +#include "MeadeCommandProcessor.hpp" + +#if TEST_VERIFY_MODE == 1 + + #include "MappedDict.hpp" + #include "testmenu.hpp" + +extern Mount mount; + +TestMenu *TestMenu::_currentMenu = nullptr; +TestMenuItem *TestMenu::_backItem = nullptr; +testMenuState_t TestMenu::_menuState = testMenuState_t::CLEAR; +testMenuInternalState TestMenu::_internalState = testMenuInternalState::IDLE; + +inline testMenuInternalState operator|=(testMenuInternalState &a, testMenuInternalState b) +{ + return a = static_cast(static_cast(a) | static_cast(b)); +}; + +inline testMenuInternalState operator|=(testMenuInternalState &a, int b) +{ + return a = static_cast(static_cast(a) | b); +}; + +long TestMenu::_targetRA = 0; +long TestMenu::_startRA = 0; +long TestMenu::_targetDEC = 0; +long TestMenu::_startDEC = 0; +long TestMenu::_startAZ = 0; +long TestMenu::_targetAZ = 0; +long TestMenu::_startALT = 0; +long TestMenu::_targetALT = 0; + +String getMenuLabel(menuText_t labelId) +{ + switch (labelId) + { + CASERETURN(MENU_BACK, F("Back")); + CASERETURN(MENU_CONNECT_RA, F("Connect to RA Driver")); + CASERETURN(MENU_CONNECT_DEC, F("Connect to DEC Driver")); + CASERETURN(MENU_CONNECT_ALT, F("Connect to ALT Driver")); + CASERETURN(MENU_CONNECT_AZ, F("Connect to AZ Driver")); + CASERETURN(MENU_CONNECT_FOC, F("Connect to FOCUS Driver")); + CASERETURN(MENU_PRIMARY_RA_CW, F("Move RA Axis 1h clockwise")); + CASERETURN(MENU_PRIMARY_RA_CCW, F("Move RA Axis 1h counter-clockwise")); + CASERETURN(MENU_PRIMARY_DEC_UP, F("Move DEC Axis 15deg up")); + CASERETURN(MENU_PRIMARY_DEC_DOWN, F("Move DEC Axis 15deg down")); + CASERETURN(MENU_TOGGLE_TRK, F("Stop/Start Tracking")); + CASERETURN(MENU_SECONDARY_RATE_1, F("Set distance to 0.1 arcmin")); + CASERETURN(MENU_SECONDARY_RATE_2, F("Set distance to 0.5 arcmin")); + CASERETURN(MENU_SECONDARY_RATE_3, F("Set distance to 2 arcmin")); + CASERETURN(MENU_SECONDARY_RATE_4, F("Set distance to 5 arcmin")); + CASERETURN(MENU_SECONDARY_RATE_5, F("Set distance to 15 arcmin")); + CASERETURN(MENU_SECONDARY_ALT_UP, F("Move ALT Axis Up")); + CASERETURN(MENU_SECONDARY_ALT_DOWN, F("Move ALT Axis Down")); + CASERETURN(MENU_SECONDARY_AZ_LEFT, F("Move AZ Axis Left")); + CASERETURN(MENU_SECONDARY_AZ_RIGHT, F("Move AZ Axis Right")); + CASERETURN(MENU_FACTORY_RESET, F("Factory Reset (Erase EEPROM)")); + CASERETURN(MENU_PASSTHROUGH_COMMAND, F("Issue LX200 Command")); + CASERETURN(MENU_MAIN_LIST_HARDWARE, F("List Hardware")); + CASERETURN(MENU_MAIN_CONNECT_DRIVERS, F("Connect Drivers")); + CASERETURN(MENU_MAIN_PRIMARY_AXIS_MOVES, F("Primary Axis Moves (RA/DEC)")); + CASERETURN(MENU_MAIN_SECONDARY_AXIS_MOVES, F("Secondary Axis Moves (ALT/AZ)")); + CASERETURN(MENU_PRIMARY_SET_HOME, F("Set current as Home")); + CASERETURN(MENU_PRIMARY_GO_HOME, F("Go Home")); + default: + return F("Unknown"); + } +} + +String getMenuAction(menuText_t labelId) +{ + switch (labelId) + { + CASERETURN(MENU_BACK, F("Action:Back")); + CASERETURN(MENU_CONNECT_RA, F("Action:Connect|RA")); + CASERETURN(MENU_CONNECT_DEC, F("Action:Connect|DEC")); + CASERETURN(MENU_CONNECT_ALT, F("Action:Connect|ALT")); + CASERETURN(MENU_CONNECT_AZ, F("Action:Connect|AZ")); + CASERETURN(MENU_CONNECT_FOC, F("Action:Connect|FOC")); + CASERETURN(MENU_PRIMARY_RA_CW, F("Action:MoveRAAxis|CW")); + CASERETURN(MENU_PRIMARY_RA_CCW, F("Action:MoveRAAxis|CCW")); + CASERETURN(MENU_PRIMARY_SET_HOME, F("Action:SetHome")); + CASERETURN(MENU_PRIMARY_GO_HOME, F("Action:GoHome")); + CASERETURN(MENU_PRIMARY_DEC_UP, F("Action:MoveDECAxis|UP")); + CASERETURN(MENU_PRIMARY_DEC_DOWN, F("Action:MoveDECAxis|DOWN")); + CASERETURN(MENU_TOGGLE_TRK, F("Action:ToggleTRK")); + + CASERETURN(MENU_SECONDARY_RATE_1, F("Action:SetSecDist|0.1")); + CASERETURN(MENU_SECONDARY_RATE_2, F("Action:SetSecDist|0.5")); + CASERETURN(MENU_SECONDARY_RATE_3, F("Action:SetSecDist|2")); + CASERETURN(MENU_SECONDARY_RATE_4, F("Action:SetSecDist|5")); + CASERETURN(MENU_SECONDARY_RATE_5, F("Action:SetSecDist|15")); + + CASERETURN(MENU_SECONDARY_ALT_UP, F("Action:MoveALTAxis|UP")); + CASERETURN(MENU_SECONDARY_ALT_DOWN, F("Action:MoveALTAxis|DOWN")); + CASERETURN(MENU_SECONDARY_AZ_LEFT, F("Action:MoveAZAxis|LEFT")); + CASERETURN(MENU_SECONDARY_AZ_RIGHT, F("Action:MoveAZAxis|RIGHT")); + CASERETURN(MENU_FACTORY_RESET, F("Action:FactoryReset")); + CASERETURN(MENU_PASSTHROUGH_COMMAND, F("Action:PassthroughCmd")); + CASERETURN(MENU_MAIN_LIST_HARDWARE, F("Action:ListHardware")); + CASERETURN(MENU_MAIN_CONNECT_DRIVERS, F("Submenu:ConnectDrivers")); + CASERETURN(MENU_MAIN_PRIMARY_AXIS_MOVES, F("Submenu:PrimaryAxisMoves")); + CASERETURN(MENU_MAIN_SECONDARY_AXIS_MOVES, F("Submenu:SecondaryAxisMoves")); + default: + return F("Unknown"); + } +} +TestMenuItem::TestMenuItem(menuText_t labelId, TestMenu *subMenu) +{ + _key = -1; + _label = getMenuLabel(labelId); + _action = getMenuAction(labelId); + _isSubMenu = subMenu != nullptr; + _subMenu = subMenu; +} + +void TestMenuItem::display() const +{ + Serial.print(" ["); + Serial.print(_key); + Serial.print("] "); + Serial.println(_label); +} + +int TestMenuItem::getKey() const +{ + return _key; +} + +void TestMenuItem::setKey(int key) +{ + _key = key; +} + +String TestMenuItem::getAction() const +{ + return _action; +} + +TestMenu *TestMenuItem::getSubMenu() const +{ + return _subMenu; +} + +void TestMenu::setParentMenu(TestMenu *parentMenu) +{ + _parentMenu = parentMenu; +} + +TestMenu::TestMenu(int level, String name, String parent, TestMenuItem *choices, int numChoices, TestMenu *parentMenu) +{ + _lastTick = 0; + _level = level; + _name = name; + _parent = parent; + _choices = choices; + _numChoices = numChoices; + _parentMenu = parentMenu; + _secondaryDistance = 1; + if (_currentMenu == nullptr) + { + _backItem = new TestMenuItem(MENU_BACK); + TestMenu::_backItem->setKey(0); + } + _currentMenu = this; // Set the last created menu as the current menu + for (int i = 0; i < _numChoices; i++) + { + if (_choices[i].getSubMenu() != nullptr) + { + _choices[i].getSubMenu()->setParentMenu(this); + } + } +} +String getComponent(const String &comp) +{ + MappedDict::DictEntry_t lookupTable[] = { + {F("AUTO_AZ_ALT"), F("AZ and ALT steppers (AutoPA)")}, + {F("AUTO_AZ"), F("AZ stepper")}, + {F("AUTO_ALT"), F("ALT stepper")}, + {F("GPS"), F("GPS receiver")}, + {F("GYRO"), F("Digital Level")}, + {F("LCD_KEYPAD"), F("LCD display and keypad")}, + {F("LCD_I2C_MCP23008"), F("LCD display (MCP23008)")}, + {F("LCD_I2C_MCP23017"), F("LCD display (MCP23017)")}, + {F("LCD_JOY_I2C_SSD1306"), F("LCD display (SSD1306) with joystick")}, + {F("INFO_I2C_SSD1306_128x64"), F("Info display (SSD1306)")}, + {F("INFO_UNKNOWN"), F("Info display (unknown type)")}, + {F("FOC"), F("Focuser stepper")}, + {F("HSAH"), F("RA Hall Sensor Auto-Homing")}, + {F("HSAV"), F("DEC Hall Sensor Auto-Homing")}, + {F("ENDSW_RA"), F("End switches on RA")}, + {F("ENDSW_DEC"), F("End switches on DEC")}, + {F("ENDSW_RA_DEC"), F("End switches on RA and DEC")}, + }; + auto driverLookup = MappedDict(lookupTable, ARRAY_SIZE(lookupTable)); + + String rtn; + if (driverLookup.tryGet(comp, &rtn)) + { + return rtn; + } + return F("Unknown component"); +} + +void printStepperInfo(StepperAxis axis, String info) +{ + String *splitInfo = splitStringBy(info, '|'); + String *stp = splitInfo; + Serial.println(axis == RA_STEPS ? "RA Info" : "DEC Info"); + Serial.println(F("--------")); + Serial.print(F(" Stepper type: ")); + Serial.println(*stp); + stp++; + Serial.print(F(" Gear: ")); + Serial.print(*stp); + Serial.println(F("-tooth")); + stp++; + if (*stp == "400") + { + Serial.println(F(" Resolution: 0.9 deg (400 steps/revolution)")); + } + else if (*stp == "200") + { + Serial.println(F(" Resolution: 1.8 deg (200 steps/revolution)")); + } + else + { + Serial.print(F(" Resolution: ")); + Serial.print(*stp); + Serial.println(F(" steps/revolution")); + } + Serial.print(F(" Slew Microsteps: ")); + Serial.println(axis == RA_STEPS ? RA_SLEW_MICROSTEPPING : DEC_SLEW_MICROSTEPPING); + if (axis == RA_STEPS) + { + Serial.print(F("Tracking Microsteps: ")); + Serial.println(RA_TRACKING_MICROSTEPPING); + Serial.print(F(" Tracking Speed: ")); + Serial.println(mount.getSpeed(TRACKING)); + } + else + { + Serial.print(F(" Guiding Microsteps: ")); + Serial.println(DEC_GUIDE_MICROSTEPPING); + } + + Serial.print(F(" Steps/degree: ")); + Serial.println(mount.getStepsPerDegree(axis)); + delete[] splitInfo; +} + +void TestMenu::listHardware() const +{ + Serial.println(F("Firmware is configured to support these hardware components:")); + String *hw = splitStringBy(mount.getMountHardwareInfo(), ','); + String *p = hw; + int index = 0; + Serial.print(F(" Mount: ")); + #ifdef OAM + Serial.println(F("OpenAstroMount (OAM)")); + #else + Serial.println(F("OpenAstroTracker (OAT)")); + #endif + + while (p->length() > 0) + { + switch (index) + { + case 0: + Serial.print(F(" Board: ")); + Serial.println(*p); + Serial.print(F(" Stepper library: ")); + #ifdef NEW_STEPPER_LIB + Serial.println(F("InterruptAccelStepper (new)")); + #else + Serial.println(F("AccelStepper (old)")); + #endif + break; + case 1: + printStepperInfo(RA_STEPS, *p); + break; + case 2: + printStepperInfo(DEC_STEPS, *p); + Serial.println(F("Add-Ons")); + Serial.println(F("--------")); + break; + default: + if (!p->startsWith("NO_")) + { + String component = getComponent(*p); + Serial.print(F(" Component: ")); + Serial.println(component); + } + break; + } + p++; + index++; + } + delete[] hw; +} + +void TestMenu::connectDriver(String axisStr) +{ + #if RA_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART || DEC_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART \ + || AZ_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART || ALT_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART \ + || FOCUS_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART + uint16_t current = 0; + Serial.print(F("Connecting to ")); + Serial.print(axisStr); + Serial.print(F(" driver....")); + bool connected = mount.connectToDriver(axisStr, ¤t); + Serial.println(connected ? "OK" : "FAIL"); + if (connected) + { + Serial.print(F("Stepper is configured to use: ")); + Serial.println(String(current) + " mA"); + } + #else + Serial.print(F("ERROR: Can only connect to TMC2209 UART drivers.")); + #endif +} + +void TestMenu::onCommandReceived(String s) +{ + Serial.println(s); + if (s.startsWith(":") && s.endsWith("#")) + { + s = s.substring(0, s.length() - 1); + } + + String reply = MeadeCommandProcessor::instance()->processCommand(s); + + if (reply.length() > 0) + { + Serial.println(F("-- Command Response --------")); + Serial.println(reply); + Serial.println(F("----------------------------")); + } +} + +void TestMenu::onKeyPressed(int key) +{ + if ((key == TestMenu::_backItem->getKey()) && (_parentMenu)) + { + _currentMenu = _parentMenu; + _currentMenu->display(); + return; + } + + Serial.println(); + for (int i = 0; i < _numChoices; i++) + { + if (_choices[i].getKey() == key) + { + if (_choices[i].getAction().startsWith("Submenu:")) + { + _currentMenu = _choices[i].getSubMenu(); + _currentMenu->display(); + return; + } + String cmd = _choices[i].getAction(); + int sep = cmd.indexOf(':'); + String verb = cmd.substring(0, sep); + String action = cmd.substring(sep + 1); + String actionArg = ""; + int argSep = action.indexOf('|'); + if (argSep > 0) + { + actionArg = action.substring(argSep + 1); + action = action.substring(0, argSep); + } + if (verb == "Action") + { + if (action == F("ListHardware")) + { + listHardware(); + _currentMenu->display(); + } + else if (action.startsWith("Connect")) + { + connectDriver(actionArg); + } + else if (action == F("SetHome")) + { + mount.setHome(false); + _currentMenu->display(); + } + else if (action == F("GoHome")) + { + _startDEC = mount.getCurrentStepperPosition(DEC_STEPS); + _startRA = mount.getCurrentStepperPosition(RA_STEPS); + _targetDEC = 0; + _targetRA = 0; + mount.startSlewingToHome(); + _internalState |= (DISPLAY_RA | DISPLAY_DEC); + } + else if (action == F("SetSecDist")) + { + _secondaryDistance = actionArg.toFloat(); + _currentMenu->display(); + } + else if (action == F("MoveRAAxis")) + { + long stepsPerDegree = mount.getStepsPerDegree(RA_STEPS); + String output = F("Moving RA axis by 1hr (15 degrees, "); + output += stepsPerDegree * 15; + output += " steps) " + actionArg; + Serial.println(output); + TestMenu::_startRA = mount.getCurrentStepperPosition(RA_STEPS); + long steps = (actionArg == "CCW" ? -1 : 1) * stepsPerDegree * 15; + TestMenu::_targetRA = TestMenu::_startRA + steps; + mount.moveStepperBy(RA_STEPS, steps); + _internalState |= DISPLAY_RA; + } + else if (action == F("MoveDECAxis")) + { + long stepsPerDegree = mount.getStepsPerDegree(DEC_STEPS); + String output = F("Moving DEC axis by 15 degrees ("); + output += stepsPerDegree * 15; + output += " steps) " + actionArg; + Serial.println(output); + TestMenu::_startDEC = mount.getCurrentStepperPosition(DEC_STEPS); + long steps = (actionArg == "DOWN" ? -1 : 1) * stepsPerDegree * 15; + TestMenu::_targetDEC = TestMenu::_startDEC + steps; + mount.moveStepperBy(DEC_STEPS, steps); + _internalState |= DISPLAY_DEC; + } + #if AZ_STEPPER_TYPE != STEPPER_TYPE_NONE + else if (action == F("MoveAZAxis")) + { + String output = String(F("Moving AZ axis by ")) + String(_secondaryDistance, 1) + String(F(" arcMins (")); + output += String(AZIMUTH_STEPS_PER_ARC_MINUTE * _secondaryDistance, 0) + " steps) " + actionArg; + Serial.println(output); + float arcmins = actionArg == "LEFT" ? _secondaryDistance : -_secondaryDistance; + TestMenu::_startAZ = mount.getCurrentStepperPosition(AZIMUTH_STEPS); + TestMenu::_targetAZ = TestMenu::_startAZ + arcmins * AZIMUTH_STEPS_PER_ARC_MINUTE; + mount.moveBy(AZIMUTH_STEPS, arcmins); + _internalState |= DISPLAY_AZ; + } + #endif + #if ALT_STEPPER_TYPE != STEPPER_TYPE_NONE + else if (action == F("MoveALTAxis")) + { + String output = String(F("Moving ALT axis by ")) + String(_secondaryDistance, 1) + String(F(" arcMins (")); + output += String(ALTITUDE_STEPS_PER_ARC_MINUTE * _secondaryDistance, 0) + " steps) " + actionArg; + Serial.println(output); + float arcmins = actionArg == "UP" ? _secondaryDistance : -_secondaryDistance; + TestMenu::_startALT = mount.getCurrentStepperPosition(ALTITUDE_STEPS); + TestMenu::_targetALT = TestMenu::_startALT + arcmins * ALTITUDE_STEPS_PER_ARC_MINUTE; + mount.moveBy(ALTITUDE_STEPS, arcmins); + _internalState |= DISPLAY_ALT; + } + #endif + else if (action == F("ToggleTRK")) + { + if (mount.isSlewingTRK()) + { + mount.stopSlewing(TRACKING); + Serial.println(F("Tracking stopped.")); + } + else + { + mount.startSlewing(TRACKING); + Serial.println(F("Tracking started.")); + } + _currentMenu->display(); + } + else if (action == F("FactoryReset")) + { + mount.clearConfiguration(); + Serial.println(F("Mount reset, EEPROM erased.")); + _currentMenu->display(); + } + else if (action = F("PassthroughCmd")) + { + Serial.print(F("Enter LX200-OAT command to send to mount: ")); + TestMenu::setMenuState(testMenuState_t::WAITING_ON_COMMAND); + } + } + if (TestMenu::getMenuState() == testMenuState_t::CLEAR) + { + _currentMenu->display(); + } + return; + } + } + Serial.println(F("Invalid key pressed.")); + + _currentMenu->display(); +} + +void TestMenu::displayStepperPos() const +{ + char buffer[64]; + snprintf_P(buffer, + sizeof(buffer), + (const char *) F(" RA: %8ld%c ALT: %8ld%c TRK: %8ld%c"), + mount.getCurrentStepperPosition(RA_STEPS), + mount.isAxisRunning(RA_STEPS) ? '^' : ' ', + mount.getCurrentStepperPosition(ALTITUDE_STEPS), + mount.isAxisRunning(ALTITUDE_STEPS) ? '^' : ' ', + mount.getCurrentStepperPosition(TRACKING), + mount.isSlewingTRK() ? '^' : ' '); + Serial.println(buffer); + + snprintf_P(buffer, + sizeof(buffer), + (const char *) F(" DEC: %8ld%c AZ: %8ld%c FOC: %8ld%c"), + mount.getCurrentStepperPosition(DEC_STEPS), + mount.isAxisRunning(DEC_STEPS) ? '^' : ' ', + mount.getCurrentStepperPosition(AZIMUTH_STEPS), + mount.isAxisRunning(AZIMUTH_STEPS) ? '^' : ' ', + mount.getCurrentStepperPosition(FOCUS_STEPS), + mount.isAxisRunning(FOCUS_STEPS) ? '^' : ' '); + Serial.println(buffer); +} + +void TestMenu::display() const +{ + Serial.println(""); + + if (_level == 0) + { + Serial.println(F("**************************************")); + #ifdef OAM + Serial.println(F("*** OpenAstroMount (OAM) Test Menu ***")); + #else + Serial.println(F("** OpenAstroTracker (OAT) Test Menu **")); + #endif + Serial.print(F("************* ")); + Serial.print(freeMemory()); + Serial.println(F(" bytes *************")); + displayStepperPos(); + Serial.println(F("**************************************")); + } + else + { + Serial.print(F("---------------- ")); + Serial.print(freeMemory()); + Serial.println(F(" bytes -------------")); + displayStepperPos(); + Serial.println(F("-----------------------------------------")); + Serial.print(" "); + Serial.print(_name); + Serial.println(F(" Menu")); + Serial.println(F("--------------------------")); + } + + for (int i = 0; i < _numChoices; i++) + { + _choices[i].setKey(i + 1); + _choices[i].display(); + } + + if (_parentMenu) + { + Serial.println(); + TestMenu::_backItem->display(); + } + Serial.print(F("Your choice:")); +} + +void TestMenu::tick() +{ + if (millis() > _lastTick + 250) + { + _lastTick = millis(); + if (_internalState != testMenuInternalState::IDLE) + { + if (_internalState & DISPLAY_RA) + { + Serial.print(F("RA : ")); + Serial.print(mount.getCurrentStepperPosition(RA_STEPS)); + Serial.print(" ("); + Serial.print(String(100.0 * (mount.getCurrentStepperPosition(RA_STEPS) - TestMenu::_startRA) + / (TestMenu::_targetRA - TestMenu::_startRA), + 0)); + Serial.print(F("%) ")); + if (!mount.isAxisRunning(RA_STEPS)) + { + _internalState = static_cast(static_cast(_internalState) & ~static_cast(DISPLAY_RA)); + } + } + + if (_internalState & DISPLAY_DEC) + { + Serial.print(F("DEC: ")); + Serial.print(mount.getCurrentStepperPosition(DEC_STEPS)); + Serial.print(" ("); + Serial.print(String(100.0 * (mount.getCurrentStepperPosition(DEC_STEPS) - TestMenu::_startDEC) + / (TestMenu::_targetDEC - TestMenu::_startDEC), + 0)); + Serial.print(F("%) ")); + if (!mount.isAxisRunning(DEC_STEPS)) + { + _internalState = static_cast(static_cast(_internalState) & ~static_cast(DISPLAY_DEC)); + } + } + + #if AZ_STEPPER_TYPE != STEPPER_TYPE_NONE + if (_internalState & DISPLAY_AZ) + { + Serial.print(F("AZ: ")); + Serial.print(mount.getCurrentStepperPosition(AZIMUTH_STEPS)); + Serial.print(" ("); + Serial.print(String(100.0 * (mount.getCurrentStepperPosition(AZIMUTH_STEPS) - TestMenu::_startAZ) + / (TestMenu::_targetAZ - TestMenu::_startAZ), + 0)); + Serial.print(F("%) ")); + if (!mount.isAxisRunning(AZIMUTH_STEPS)) + { + _internalState = static_cast(static_cast(_internalState) & ~static_cast(DISPLAY_AZ)); + } + } + #endif + + #if ALT_STEPPER_TYPE != STEPPER_TYPE_NONE + if (_internalState & DISPLAY_ALT) + { + Serial.print(F("ALT: ")); + Serial.print(mount.getCurrentStepperPosition(ALTITUDE_STEPS)); + Serial.print(" ("); + Serial.print(String(100.0 * (mount.getCurrentStepperPosition(ALTITUDE_STEPS) - TestMenu::_startALT) + / (TestMenu::_targetALT - TestMenu::_startALT), + 0)); + Serial.print(F("%) ")); + if (!mount.isAxisRunning(ALTITUDE_STEPS)) + { + _internalState = static_cast(static_cast(_internalState) & ~static_cast(DISPLAY_ALT)); + } + } + #endif + + Serial.println(); + + if (_internalState == testMenuInternalState::IDLE) + { + _currentMenu->display(); + } + } + } +} + +testMenuState_t TestMenu::getMenuState() +{ + return TestMenu::_menuState; +} + +void TestMenu::setMenuState(testMenuState_t state) +{ + TestMenu::_menuState = state; +} + +TestMenu *TestMenu::getCurrentMenu() +{ + return TestMenu::_currentMenu; +} + +#endif // TEST_VERIFY_MODE \ No newline at end of file diff --git a/src/testmenu.hpp b/src/testmenu.hpp new file mode 100644 index 00000000..ead525cc --- /dev/null +++ b/src/testmenu.hpp @@ -0,0 +1,114 @@ +#pragma once +#include + +#if TEST_VERIFY_MODE == 1 + +enum menuText_t +{ + MENU_BACK, + MENU_CONNECT_RA, + MENU_CONNECT_DEC, + MENU_CONNECT_ALT, + MENU_CONNECT_AZ, + MENU_CONNECT_FOC, + MENU_PRIMARY_RA_CW, + MENU_PRIMARY_RA_CCW, + MENU_PRIMARY_DEC_UP, + MENU_PRIMARY_DEC_DOWN, + MENU_PRIMARY_SET_HOME, + MENU_PRIMARY_GO_HOME, + MENU_TOGGLE_TRK, + MENU_SECONDARY_RATE_1, + MENU_SECONDARY_RATE_2, + MENU_SECONDARY_RATE_3, + MENU_SECONDARY_RATE_4, + MENU_SECONDARY_RATE_5, + MENU_SECONDARY_ALT_UP, + MENU_SECONDARY_ALT_DOWN, + MENU_SECONDARY_AZ_LEFT, + MENU_SECONDARY_AZ_RIGHT, + MENU_FACTORY_RESET, + MENU_PASSTHROUGH_COMMAND, + MENU_MAIN_LIST_HARDWARE, + MENU_MAIN_CONNECT_DRIVERS, + MENU_MAIN_PRIMARY_AXIS_MOVES, + MENU_MAIN_SECONDARY_AXIS_MOVES, +}; + +enum testMenuState_t +{ + CLEAR, + WAITING_ON_INPUT, + WAITING_ON_COMMAND, +}; + +// Flag as to what to display to terminal +enum testMenuInternalState +{ + IDLE = 0, + DISPLAY_RA = 1 << 0, + DISPLAY_DEC = 1 << 1, + DISPLAY_AZ = 1 << 2, + DISPLAY_ALT = 1 << 3, +}; + +class TestMenu; + +class TestMenuItem +{ + int _key; + String _label; + String _action; + TestMenu *_subMenu; + bool _isSubMenu; + + public: + TestMenuItem(menuText_t labelId, TestMenu *subMenu = nullptr); + void display() const; + int getKey() const; + void setKey(int key); + String getAction() const; + TestMenu *getSubMenu() const; +}; + +class TestMenu +{ + int _level; + unsigned long _lastTick; + String _name; + String _parent; + TestMenuItem *_choices; + int _numChoices; + TestMenu *_parentMenu; + float _secondaryDistance; + + static long _targetRA; + static long _startRA; + static long _targetDEC; + static long _startDEC; + static long _startAZ; + static long _targetAZ; + static long _startALT; + static long _targetALT; + + static testMenuState_t _menuState; + static testMenuInternalState _internalState; + static TestMenu *_currentMenu; + static TestMenuItem *_backItem; + + public: + TestMenu(int level, String name, String parent, TestMenuItem *choices, int numChoices, TestMenu *parentMenu = nullptr); + void onKeyPressed(int key); + void onCommandReceived(String s); + void display() const; + void displayStepperPos() const; + void setParentMenu(TestMenu *parentMenu); + static TestMenu *getCurrentMenu(); + static testMenuState_t getMenuState(); + static void setMenuState(testMenuState_t state); + + void listHardware() const; + void connectDriver(String axisStr); + void tick(); +}; +#endif diff --git a/src/testmenudef.hpp b/src/testmenudef.hpp new file mode 100644 index 00000000..eb959707 --- /dev/null +++ b/src/testmenudef.hpp @@ -0,0 +1,72 @@ +#if TEST_VERIFY_MODE == 1 + #include "testmenu.hpp" + +TestMenuItem connectMenuItems[] = { + #if RA_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART + TestMenuItem(MENU_CONNECT_RA), + #endif + #if DEC_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART + TestMenuItem(MENU_CONNECT_DEC), + #endif + #if ALT_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART + TestMenuItem(MENU_CONNECT_ALT), + #endif + #if AZ_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART + TestMenuItem(MENU_CONNECT_AZ), + #endif + #if FOCUS_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART + TestMenuItem(MENU_CONNECT_FOC), + #endif +}; +TestMenu connectDriversMenu(1, "ConnectDrivers", "Main menu", connectMenuItems, sizeof(connectMenuItems) / sizeof(connectMenuItems[0])); + +TestMenuItem primaryAxisMenuItems[] = { + TestMenuItem(MENU_PRIMARY_RA_CW), + TestMenuItem(MENU_PRIMARY_RA_CCW), + TestMenuItem(MENU_PRIMARY_DEC_UP), + TestMenuItem(MENU_PRIMARY_DEC_DOWN), + TestMenuItem(MENU_PRIMARY_SET_HOME), + TestMenuItem(MENU_PRIMARY_GO_HOME), + TestMenuItem(MENU_TOGGLE_TRK), +}; +TestMenu primaryAxisMenu( + 1, "PrimaryAxisMoves", "Move Primary Axes", primaryAxisMenuItems, sizeof(primaryAxisMenuItems) / sizeof(primaryAxisMenuItems[0])); + +TestMenuItem secondaryAxisMenuItems[] = { + TestMenuItem(MENU_SECONDARY_RATE_1), + TestMenuItem(MENU_SECONDARY_RATE_2), + TestMenuItem(MENU_SECONDARY_RATE_3), + TestMenuItem(MENU_SECONDARY_RATE_4), + TestMenuItem(MENU_SECONDARY_RATE_5), + #if ALT_STEPPER_TYPE != STEPPER_TYPE_NONE + TestMenuItem(MENU_SECONDARY_ALT_UP), + TestMenuItem(MENU_SECONDARY_ALT_DOWN), + #endif + #if AZ_STEPPER_TYPE != STEPPER_TYPE_NONE + TestMenuItem(MENU_SECONDARY_AZ_LEFT), + TestMenuItem(MENU_SECONDARY_AZ_RIGHT), + #endif +}; +TestMenu secondaryAxisMenu(1, + "SecondaryAxisMoves", + "Move Secondary Axes", + secondaryAxisMenuItems, + sizeof(secondaryAxisMenuItems) / sizeof(secondaryAxisMenuItems[0])); + +TestMenuItem menuItems[] = { + TestMenuItem(MENU_MAIN_LIST_HARDWARE), + #if RA_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART || DEC_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART \ + || AZ_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART || ALT_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART \ + || FOCUS_DRIVER_TYPE == DRIVER_TYPE_TMC2209_UART + TestMenuItem(MENU_MAIN_CONNECT_DRIVERS, &connectDriversMenu), + #endif + TestMenuItem(MENU_MAIN_PRIMARY_AXIS_MOVES, &primaryAxisMenu), + #if (AZ_STEPPER_TYPE != STEPPER_TYPE_NONE) || (ALT_STEPPER_TYPE != STEPPER_TYPE_NONE) + TestMenuItem(MENU_MAIN_SECONDARY_AXIS_MOVES, &secondaryAxisMenu), + #endif + TestMenuItem(MENU_PASSTHROUGH_COMMAND), + TestMenuItem(MENU_FACTORY_RESET), +}; + +TestMenu mainTestMenu(0, "OAT/OAM Testing menu", "", menuItems, sizeof(menuItems) / sizeof(menuItems[0])); +#endif \ No newline at end of file