diff --git a/drivers/focuser/alluna_tcs2.cpp b/drivers/focuser/alluna_tcs2.cpp index 6ce97bee4a..653f1843f3 100644 --- a/drivers/focuser/alluna_tcs2.cpp +++ b/drivers/focuser/alluna_tcs2.cpp @@ -1,6 +1,5 @@ /* - Alluna TCS2 Focus, Dust Cover, Climate, Rotator, and Settings - (Dust Cover and Rotator are not implemented) + TCS2Focuser Alluna TCS2 Focuser Copyright(c) 2022 Peter Englmaier. All rights reserved. @@ -19,23 +18,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -/* TODO: - * - add fanpower - * - add focuser optipos and jogg/skip forward/backward - * - add handybox add/remove and screensaver config - * - set default serial port default to 19200 @ 8-N-1 - * - add rotator - * - make optional components optional (hide in gui) - * - add arbitrary command for special commands - * - cleanup for PR - * - * KNOWN PROBLEMS: - * https://indilib.org/forum/focusers-filter-wheels/14173-new-zwo-eaf-first-use-experience-and-questions.html#98014 - * Note: When I try to enable Debug in the IndiControlPanel it immediately sets it to Disable? - * Debug is controlled (set on/off) in Ekos and these individual driver debug buttons no longer work. This behaviour is "as expected". - * - */ - #include "alluna_tcs2.h" #include "indicom.h" @@ -46,163 +28,109 @@ #include #include -// create an instance of this driver -static std::unique_ptr allunaTCS2(new AllunaTCS2 ()); +static std::unique_ptr TCS2FocuserDriver(new TCS2Focuser()); -AllunaTCS2::AllunaTCS2() //: DustCapInterface() +TCS2Focuser::TCS2Focuser() { - LOG_DEBUG("Init AllunaTCS2"); // Let's specify the driver version setVersion(1, 0); // we know only about serial (USB) connections setSupportedConnections(CONNECTION_SERIAL); - // Connection parameters should be 19200 @ 8-N-1 - // FIXME: add some code to warn if settings are not ok // What capabilities do we support? FI::SetCapability(FOCUSER_CAN_ABORT | FOCUSER_CAN_ABS_MOVE | - FOCUSER_CAN_REL_MOVE ); //FIXME: maybe remove CAN_REL_MOVE + FOCUSER_CAN_REL_MOVE ); +} + +void TCS2Focuser::debugTriggered(bool enable) +{ + enable = false; + tty_set_debug(enable ? 1 : 0); } -bool AllunaTCS2::initProperties() + +bool TCS2Focuser::initProperties() { INDI::Focuser::initProperties(); - //INDI::DustCapInterface::initDustCapProperties(getDeviceName(), "groupname"); - - // Focuser temperature / ambient temperature, ekos uses first number of "FOCUS_TEMPERATURE" property - IUFillNumber(&TemperatureN[0], "TEMPERATURE_AMBIENT", "Focuser Temp [C]", "%6.2f", -100, 100, 0, 0); - // Primary mirror temperature - IUFillNumber(&TemperatureN[1], "TEMPERATURE_PRIMARY", "Primary Temp [C]", "%6.2f", -100, 100, 0, 0); - // Secondary mirror temperature - IUFillNumber(&TemperatureN[2], "TEMPERATURE_SECONDARY", "Secondary Temp [C]", "%6.2f", -100, 100, 0, 0); - // Ambient humidity - IUFillNumber(&TemperatureN[3], "HUMIDITY", "Humidity [%]", "%6.2f", 0, 100, 0, 0); - - IUFillNumberVector(&TemperatureNP, TemperatureN, 4, getDeviceName(), "FOCUS_TEMPERATURE", "Climate", - CLIMATE_TAB, IP_RO, 0, IPS_IDLE); - - // Climate control - IUFillSwitch(&ClimateControlS[CLIMATECONTROL_AUTO], "CLIMATE_AUTO", "On", ISS_OFF); - IUFillSwitch(&ClimateControlS[CLIMATECONTROL_MANUAL], "CLIMATE_MANUAL", "Off", ISS_ON); - IUFillSwitchVector(&ClimateControlSP, ClimateControlS, 2, getDeviceName(), "CLIMATE_CONTROL", "Climate Control", CLIMATE_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); - - IUFillSwitch(&PrimaryDewHeaterS[DEWHEATER_ON], "PRIMARY_HEATER_ON", "On", ISS_OFF); - IUFillSwitch(&PrimaryDewHeaterS[DEWHEATER_OFF], "PRIMARY_HEATER_OFF", "Off", ISS_ON); - IUFillSwitchVector(&PrimaryDewHeaterSP, PrimaryDewHeaterS, 2, getDeviceName(), "PRIMARY_HEATER", "Heat primary", CLIMATE_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); - IUFillSwitch(&SecondaryDewHeaterS[DEWHEATER_ON], "SECONDARY_HEATER_ON", "On", ISS_OFF); - IUFillSwitch(&SecondaryDewHeaterS[DEWHEATER_OFF], "SECONDARY_HEATER_OFF", "Off", ISS_ON); - IUFillSwitchVector(&SecondaryDewHeaterSP, SecondaryDewHeaterS, 2, getDeviceName(), "SECONDARY_HEATER", "Heat secondary", CLIMATE_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); - - IUFillNumber(&FanPowerN[0], "FANPOWER", "Fan power [0..255]", "%3.0f", 0, 100, 1, 100); - IUFillNumberVector(&FanPowerNP, FanPowerN, 1, getDeviceName(), "FANPOWER", "Fan Power", - CLIMATE_TAB, IP_RW, 60, IPS_IDLE); - - // Stepping Modes "SpeedStep" and "MicroStep" + + // Focuser temperature + IUFillNumber(&TemperatureN[0], "TEMPERATURE", "Celsius", "%6.2f", -100, 100, 0, 0); + IUFillNumberVector(&TemperatureNP, TemperatureN, 1, getDeviceName(), "FOCUS_TEMPERATURE", "Temperature", + MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE); + + // Stepping Modes IUFillSwitch(&SteppingModeS[STEPPING_SPEED], "STEPPING_SPEED", "SpeedStep", ISS_ON); IUFillSwitch(&SteppingModeS[STEPPING_MICRO], "STEPPING_MICRO", "MicroStep", ISS_OFF); IUFillSwitchVector(&SteppingModeSP, SteppingModeS, 2, getDeviceName(), "STEPPING_MODE", "Mode", - MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE); + STEPPING_TAB, IP_RW, ISR_1OFMANY, 0, IPS_OK); + + addDebugControl(); // Set limits as per documentation FocusAbsPosN[0].min = 0; - FocusAbsPosN[0].max = (steppingMode == STEPPING_MICRO) ? 22400 : 1400; // 22400 in microstep mode, 1400 in speedstep mode - FocusAbsPosN[0].step = 1; + FocusAbsPosN[0].max = 22400; + FocusAbsPosN[0].step = 1000; FocusRelPosN[0].min = 0; - FocusRelPosN[0].max = 1000; - FocusRelPosN[0].step = 1; - - // Maximum Position - FocusMaxPosN[0].value = FocusAbsPosN[0].max; - FocusMaxPosNP.p = IP_RO; - - // Dust Cover - IUFillSwitch(&CoverS[COVER_OPEN], "COVER_OPEN", "Open", ISS_OFF); - IUFillSwitch(&CoverS[COVER_CLOSED], "COVER_CLOSE", "Close", ISS_ON); - IUFillSwitchVector(&CoverSP, CoverS, 2, getDeviceName(), "COVER_CONTROL", "Cover Control", DUSTCOVER_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); - - setDriverInterface(FOCUSER_INTERFACE); //| DUSTCAP_INTERFACE); + FocusRelPosN[0].max = 999; + FocusRelPosN[0].step = 100; - addAuxControls(); + FocusSpeedN[0].min = 1; + FocusSpeedN[0].max = 254; + FocusSpeedN[0].step = 10; return true; } -const char *AllunaTCS2::getDefaultName() +const char *TCS2Focuser::getDefaultName() { - return "Alluna TCS2"; + return "Alluna TCS2 Focuser"; } -bool AllunaTCS2::updateProperties() +bool TCS2Focuser::updateProperties() { - LOG_INFO("updateProperties called"); - //INDI::DefaultDevice::updateProperties(); + if (isConnected()) + { + // Read these values before defining focuser interface properties + readPosition(); + } + INDI::Focuser::updateProperties(); if (isConnected()) { - // turn on green Connected-LED - if (sendCommand("Connect 1\n")) - ;//LOG_INFO("Turned on Connected-LED¨"); - else - LOG_ERROR("Cannot turn on Connected-LED"); + if (readTemperature()) + defineProperty(&TemperatureNP); + bool rc = getStartupValues(); - // Read these values before defining focuser interface properties - // Only ask for values in sync, because TimerHit is not running, yet. - if (readPosition()) { - LOGF_INFO("updateProperties: Focus position is %d of %d", (int)FocusAbsPosN[0].value, (int)FocusAbsPosN[0].max); - } - if (readStepping()) { - LOGF_INFO("updateProperties: Stepping mode is %s", (steppingMode==STEPPING_SPEED)?"SPEED":"micro"); - LOGF_INFO("updateProperties: Stepping mode is %s", (SteppingModeS[STEPPING_SPEED].s == ISS_ON)?"SPEED":"no"); - LOGF_INFO("updateProperties: Stepping mode is %s", (SteppingModeS[STEPPING_MICRO].s == ISS_ON)?"micro":"NO"); - } - readDustCover(); - readTemperature(); - readClimateControl(); - - // Focuser + // Settings defineProperty(&SteppingModeSP); - defineProperty(&FocusMaxPosNP); - defineProperty(&FocusAbsPosNP); - - // Climate - defineProperty(&TemperatureNP); - defineProperty(&ClimateControlSP); - defineProperty(&PrimaryDewHeaterSP); - defineProperty(&SecondaryDewHeaterSP); - defineProperty(&FanPowerNP); - - // Cover - defineProperty(&CoverSP); - - //if (rc) - LOG_INFO("AllunaTCS2 is ready."); - //else - // LOG_WARN("Failed to query startup values."); + + if (rc) + LOG_INFO("TCS2Focuser is ready."); + else + LOG_WARN("Failed to query startup values."); } else { - deleteProperty(TemperatureNP.name); + if (TemperatureNP.s == IPS_OK) + deleteProperty(TemperatureNP.name); + deleteProperty(SteppingModeSP.name); - deleteProperty(FocusMaxPosNP.name); - deleteProperty(FocusAbsPosNP.name); - deleteProperty(CoverSP.name); } return true; } -bool AllunaTCS2::Handshake() +bool TCS2Focuser::Handshake() { char cmd[DRIVER_LEN] = "HandShake\n", res[DRIVER_LEN] = {0}; - tcs.unlock(); bool rc = sendCommand(cmd, res, 0, 2); if (rc == false) @@ -211,38 +139,22 @@ bool AllunaTCS2::Handshake() return res[0] == '\r' && res[1] == '\n'; } -bool AllunaTCS2::sendCommand(const char * cmd, char * res, int cmd_len, int res_len) -{ - - if (tcs.try_lock() ) { - bool result; - result = sendCommandNoLock(cmd, res, cmd_len, res_len); - tcs.unlock(); - return result; - } else { - LOG_INFO("sendCommand: lock failed, abort"); - return false; - } - -} - -bool AllunaTCS2::sendCommandNoLock(const char * cmd, char * res, int cmd_len, int res_len) +bool TCS2Focuser::sendCommand(const char * cmd, char * res, int cmd_len, int res_len) { int nbytes_written = 0, nbytes_read = 0, rc = -1; - LOG_DEBUG("sendCommand: Send Command"); tcflush(PortFD, TCIOFLUSH); if (cmd_len > 0) { char hex_cmd[DRIVER_LEN * 3] = {0}; hexDump(hex_cmd, cmd, cmd_len); - LOGF_DEBUG("Byte string '%s'", hex_cmd); + LOGF_DEBUG("CMD <%s>", hex_cmd); rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written); } else { - LOGF_DEBUG("Char string '%s'", cmd); + LOGF_DEBUG("CMD <%s>", cmd); rc = tty_write_string(PortFD, cmd, &nbytes_written); } @@ -257,19 +169,16 @@ bool AllunaTCS2::sendCommandNoLock(const char * cmd, char * res, int cmd_len, in if (res == nullptr) return true; - if (res_len > 0) { - LOG_DEBUG("sendCommand: Read Answer Bytes"); + if (res_len > 0) rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read); - } else { - LOG_DEBUG("sendCommand: Read Answer String"); + else rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read); - } if (rc != TTY_OK) { char errstr[MAXRBUF] = {0}; tty_error_msg(rc, errstr, MAXRBUF); - LOGF_ERROR("203 Serial read error: %s.", errstr); + LOGF_ERROR("Serial read error: %s.", errstr); return false; } @@ -277,40 +186,34 @@ bool AllunaTCS2::sendCommandNoLock(const char * cmd, char * res, int cmd_len, in { char hex_res[DRIVER_LEN * 3] = {0}; hexDump(hex_res, res, res_len); - LOGF_DEBUG("Bytes '%s'", hex_res); + LOGF_DEBUG("RES <%s>", hex_res); } else { - LOGF_DEBUG("String '%s'", res); + LOGF_DEBUG("RES <%s>", res); } tcflush(PortFD, TCIOFLUSH); - LOG_DEBUG("sendCommand: Ende"); + return true; } - -bool AllunaTCS2::sendCommandOnly(const char * cmd, int cmd_len) +bool TCS2Focuser::sendCommandOnly(const char * cmd, int cmd_len) { int nbytes_written = 0, rc = -1; - if (! tcs.try_lock() ) { - LOGF_INFO("sendCommandOnly: %s: lock failed, abort", cmd); - return false; - } - LOG_DEBUG("sendCommandOnly: Anfang"); tcflush(PortFD, TCIOFLUSH); if (cmd_len > 0) { char hex_cmd[DRIVER_LEN * 3] = {0}; hexDump(hex_cmd, cmd, cmd_len); - LOGF_DEBUG("Bytes '%s'", hex_cmd); + LOGF_DEBUG("CMD <%s>", hex_cmd); rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written); } else { - LOGF_DEBUG("String '%s'", cmd); + LOGF_DEBUG("CMD <%s>", cmd); rc = tty_write_string(PortFD, cmd, &nbytes_written); } @@ -319,18 +222,15 @@ bool AllunaTCS2::sendCommandOnly(const char * cmd, int cmd_len) char errstr[MAXRBUF] = {0}; tty_error_msg(rc, errstr, MAXRBUF); LOGF_ERROR("Serial write error: %s.", errstr); - tcs.unlock(); return false; } - LOG_DEBUG("sendCommandOnly: Ende"); return true; } -bool AllunaTCS2::receiveNext(char * res, int res_len) +bool TCS2Focuser::receiveNext(char * res, int res_len) { int nbytes_read = 0, rc = -1; - LOG_DEBUG("receiveNext: Anfang"); if (res_len > 0) rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read); @@ -341,8 +241,7 @@ bool AllunaTCS2::receiveNext(char * res, int res_len) { char errstr[MAXRBUF] = {0}; tty_error_msg(rc, errstr, MAXRBUF); - LOGF_ERROR("285 Serial read error: %s.", errstr); - tcs.unlock(); + LOGF_ERROR("Serial read error: %s.", errstr); return false; } @@ -350,25 +249,23 @@ bool AllunaTCS2::receiveNext(char * res, int res_len) { char hex_res[DRIVER_LEN * 3] = {0}; hexDump(hex_res, res, res_len); - LOGF_DEBUG("Bytes '%s'", hex_res); + LOGF_DEBUG("RES <%s>", hex_res); } else { - LOGF_DEBUG("String '%s'", res); + LOGF_DEBUG("RES <%s>", res); } - LOG_DEBUG("receiveNext: Ende"); return true; } -void AllunaTCS2::receiveDone() +void TCS2Focuser::receiveDone() { - LOG_DEBUG("receiveDone"); tcflush(PortFD, TCIOFLUSH); - tcs.unlock(); } -void AllunaTCS2::hexDump(char * buf, const char * data, int size) + +void TCS2Focuser::hexDump(char * buf, const char * data, int size) { for (int i = 0; i < size; i++) sprintf(buf + 3 * i, "%02X ", static_cast(data[i])); @@ -377,212 +274,40 @@ void AllunaTCS2::hexDump(char * buf, const char * data, int size) buf[3 * size - 1] = '\0'; } -// client asks for list of all properties -void AllunaTCS2::ISGetProperties(const char *dev) -{ - INDI::Focuser::ISGetProperties(dev); - LOG_INFO("ISGetProperties called"); - // FIXME: do something like upclass does with controller class -} - -// client wants to change switch value (i.e. click on switch in GUI) -bool AllunaTCS2::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n) +bool TCS2Focuser::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n) { - if (dev != nullptr && !strcmp(dev, getDeviceName()) ) + if (dev != nullptr && strcmp(dev, getDeviceName()) == 0) { - LOGF_INFO("ISNewSwitch called for %s", name); - if (!strcmp(name, "CONNECTION") && !strcmp(names[0], "DISCONNECT") && states[0] == ISS_ON) - { - // turn off green Connected-LED - if (sendCommand("Connect 0\n")) - LOG_DEBUG("Turned off Connected-LED"); - else - LOG_ERROR("Cannot turn off Connected-LED"); - } - // Stepping Mode? + // Stepping Mode if (!strcmp(name, SteppingModeSP.name)) { IUUpdateSwitch(&SteppingModeSP, states, names, n); SteppingModeSP.s = IPS_OK; IDSetSwitch(&SteppingModeSP, nullptr); - - // write new stepping mode to tcs2 - setStepping((SteppingModeS[STEPPING_SPEED].s == ISS_ON) ? STEPPING_SPEED : STEPPING_MICRO); - // update maximum stepping position - FocusAbsPosN[0].max = (steppingMode == STEPPING_MICRO) ? 22400 : 1400; // 22400 in microstep mode, 1400 in speedstep mode - // update max position value - FocusMaxPosN[0].value = FocusAbsPosN[0].max; - // update maximum stepping postion for presets - SetFocuserMaxPosition( FocusAbsPosN[0].max ); // 22400 in microstep mode, 1400 in speedstep mode - // Update clients - IDSetNumber(&FocusAbsPosNP, nullptr); // not sure if this is necessary, because not shown in driver panel - IDSetNumber(&FocusMaxPosNP, nullptr ); - LOGF_INFO("Setting new max position to %d", (steppingMode == STEPPING_MICRO) ? 22400 : 1400 ); - - defineProperty(&FocusMaxPosNP); - defineProperty(&FocusAbsPosNP); - // read focuser position (depends on stepping mode) - readPosition(); - LOGF_INFO("Processed %s",name); return true; } - - // Cover Switch? - if (!strcmp(name, CoverSP.name)) - { - // Find out which state is requested by the client - const char *actionName = IUFindOnSwitchName(states, names, n); - // Do nothing, if state is already what it should be - int currentCoverIndex = IUFindOnSwitchIndex(&CoverSP); - if (!strcmp(actionName, CoverS[currentCoverIndex].name)) - { - DEBUGF(INDI::Logger::DBG_SESSION, "Cover is already %s", CoverS[currentCoverIndex].label); - CoverSP.s = IPS_IDLE; - IDSetSwitch(&CoverSP, NULL); - return true; - } - - // Otherwise, let us update the switch state - IUUpdateSwitch(&CoverSP, states, names, n); - currentCoverIndex = IUFindOnSwitchIndex(&CoverSP); - if ( setDustCover() ) { - isCoverMoving = true; - DEBUGF(INDI::Logger::DBG_SESSION, "Cover is now %s", CoverS[currentCoverIndex].label); - CoverSP.s = IPS_OK; - IDSetSwitch(&CoverSP, NULL); - return true; - } else { - DEBUG(INDI::Logger::DBG_SESSION, "Cannot get lock, try again"); - CoverSP.s = IPS_ALERT; - IDSetSwitch(&CoverSP, NULL); - } - } - - // Climate Control Switch? - if (!strcmp(name, ClimateControlSP.name)) - { - // Find out which state is requested by the client - const char *actionName = IUFindOnSwitchName(states, names, n); - // Do nothing, if state is already what it should be - int currentClimateControlIndex = IUFindOnSwitchIndex(&ClimateControlSP); - if (!strcmp(actionName, ClimateControlS[currentClimateControlIndex].name)) - { - DEBUGF(INDI::Logger::DBG_SESSION, "Climate Control is already %s", ClimateControlS[currentClimateControlIndex].label); - ClimateControlSP.s = IPS_IDLE; - IDSetSwitch(&ClimateControlSP, NULL); - return true; - } - - // Otherwise, let us update the switch state - IUUpdateSwitch(&ClimateControlSP, states, names, n); - currentClimateControlIndex = IUFindOnSwitchIndex(&ClimateControlSP); - if ( setClimateControl((currentClimateControlIndex==CLIMATECONTROL_AUTO) ? CLIMATECONTROL_MANUAL: CLIMATECONTROL_AUTO) ) { - DEBUGF(INDI::Logger::DBG_SESSION, "ClimateControl is now %s", CoverS[currentClimateControlIndex].label); - ClimateControlSP.s = IPS_OK; - IDSetSwitch(&ClimateControlSP, NULL); - return true; - } else { - DEBUG(INDI::Logger::DBG_SESSION, "Cannot get lock, try again"); - ClimateControlSP.s = IPS_ALERT; - IDSetSwitch(&ClimateControlSP, NULL); - } - } - - // PrimaryDewHeater Switch? - if (!strcmp(name, PrimaryDewHeaterSP.name)) - { - // Find out which state is requested by the client - const char *actionName = IUFindOnSwitchName(states, names, n); - // Do nothing, if state is already what it should be - int currentPrimaryDewHeaterIndex = IUFindOnSwitchIndex(&PrimaryDewHeaterSP); - if (!strcmp(actionName, PrimaryDewHeaterS[currentPrimaryDewHeaterIndex].name)) - { - DEBUGF(INDI::Logger::DBG_SESSION, "PrimaryDewHeater is already %s", PrimaryDewHeaterS[currentPrimaryDewHeaterIndex].label); - PrimaryDewHeaterSP.s = IPS_IDLE; - IDSetSwitch(&PrimaryDewHeaterSP, NULL); - return true; - } - - // Otherwise, let us update the switch state - IUUpdateSwitch(&PrimaryDewHeaterSP, states, names, n); - currentPrimaryDewHeaterIndex = IUFindOnSwitchIndex(&PrimaryDewHeaterSP); - if ( setPrimaryDewHeater((currentPrimaryDewHeaterIndex==DEWHEATER_OFF) ? DEWHEATER_ON: DEWHEATER_OFF) ) { - DEBUGF(INDI::Logger::DBG_SESSION, "PrimaryDewHeater is now %s", CoverS[currentPrimaryDewHeaterIndex].label); - PrimaryDewHeaterSP.s = IPS_OK; - IDSetSwitch(&PrimaryDewHeaterSP, NULL); - return true; - } else { - DEBUG(INDI::Logger::DBG_SESSION, "Cannot get lock, try again"); - PrimaryDewHeaterSP.s = IPS_ALERT; - IDSetSwitch(&PrimaryDewHeaterSP, NULL); - } - } - - // SecondaryDewHeater Switch? - if (!strcmp(name, SecondaryDewHeaterSP.name)) - { - // Find out which state is requested by the client - const char *actionName = IUFindOnSwitchName(states, names, n); - // Do nothing, if state is already what it should be - int currentSecondaryDewHeaterIndex = IUFindOnSwitchIndex(&SecondaryDewHeaterSP); - if (!strcmp(actionName, SecondaryDewHeaterS[currentSecondaryDewHeaterIndex].name)) - { - DEBUGF(INDI::Logger::DBG_SESSION, "SecondaryDewHeater is already %s", ClimateControlS[currentSecondaryDewHeaterIndex].label); - SecondaryDewHeaterSP.s = IPS_IDLE; - IDSetSwitch(&SecondaryDewHeaterSP, NULL); - return true; - } - - // Otherwise, let us update the switch state - IUUpdateSwitch(&SecondaryDewHeaterSP, states, names, n); - currentSecondaryDewHeaterIndex = IUFindOnSwitchIndex(&SecondaryDewHeaterSP); - if ( setSecondaryDewHeater((currentSecondaryDewHeaterIndex==DEWHEATER_OFF) ? DEWHEATER_ON: DEWHEATER_OFF) ) { - DEBUGF(INDI::Logger::DBG_SESSION, "SecondaryDewHeater is now %s", CoverS[currentSecondaryDewHeaterIndex].label); - SecondaryDewHeaterSP.s = IPS_OK; - IDSetSwitch(&SecondaryDewHeaterSP, NULL); - return true; - } else { - DEBUG(INDI::Logger::DBG_SESSION, "Cannot get lock, try again"); - SecondaryDewHeaterSP.s = IPS_ALERT; - IDSetSwitch(&SecondaryDewHeaterSP, NULL); - } - } - - // FanPower Number? - - } + return INDI::Focuser::ISNewSwitch(dev, name, states, names, n); } -// client wants to change number value -bool AllunaTCS2::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) +bool TCS2Focuser::getStartupValues() { - LOGF_INFO("ISNewSwitch called for %s\n", name); - if (dev != nullptr && strcmp(dev, getDeviceName()) == 0) - { - // xxxx - if (strcmp(name, "xxxx") == 0) - { - //IUUpdateNumber(&GuideRateNP, values, names, n); - //GuideRateNP.s = IPS_OK; - //IDSetNumber(&GuideRateNP, nullptr); - return true; - } + bool rc1 = readStepping(); + //bool rc2 = readFoo(); + //bool rc2 = readBar(); - } + //return (rc1 && rc2 && rc3); - return INDI::Focuser::ISNewNumber(dev, name, values, names, n); + return rc1; } -IPState AllunaTCS2::MoveAbsFocuser(uint32_t targetTicks) +IPState TCS2Focuser::MoveAbsFocuser(uint32_t targetTicks) { - LOGF_INFO("MoveAbsFocuser %d called", targetTicks); char cmd[DRIVER_LEN]; snprintf(cmd, DRIVER_LEN, "FocuserGoTo %d\r\n", targetTicks); bool rc = sendCommandOnly(cmd); if (rc == false) { - LOGF_ERROR("MoveAbsFocuser %d failed", targetTicks); return IPS_ALERT; } isFocuserMoving = true; @@ -590,152 +315,108 @@ IPState AllunaTCS2::MoveAbsFocuser(uint32_t targetTicks) return IPS_BUSY; } -IPState AllunaTCS2::MoveRelFocuser(FocusDirection dir, uint32_t ticks) +IPState TCS2Focuser::MoveRelFocuser(FocusDirection dir, uint32_t ticks) { m_TargetDiff = ticks * ((dir == FOCUS_INWARD) ? -1 : 1); return MoveAbsFocuser(FocusAbsPosN[0].value + m_TargetDiff); } -bool AllunaTCS2::AbortFocuser() +bool TCS2Focuser::AbortFocuser() { return sendCommand("FocuserStop\n"); } -void AllunaTCS2::TimerHit() +void TCS2Focuser::TimerHit() { - //LOG_INFO("TimerHit"); - if (!isConnected()) - return; // No need to reset timer if we are not connected anymore - - // try to read temperature, if it works no lock was present - if (readTemperature()) { + if (isConnected() == false) + { SetTimer(getCurrentPollingPeriod()); return; } - // if we could not read temperature, a lock is set and we need to check if there is input to be processed. - - bool actionInProgress = isFocuserMoving || isCoverMoving; - // expect and process device output while present - char res[DRIVER_LEN] = {0}; - // read a line, if available - while (actionInProgress && receiveNext(res)) + // when focuser is moving, do update position, but do nothing else + if (isFocuserMoving) { - int32_t pos; - - if ( res[1] == '#') { - switch (res[0]) - { - case 'A': // aux1 on (primary mirror heating) - break; - case 'B': // aux1 off (primary mirror heating) - break; - case 'C': // aux2 on (secondary mirror heating) - break; - case 'D': // aux2 off (secondary mirror heating) - break; - case 'E': // climate control ON - break; - case 'F': // climate control OFF - break; - case 'G': // fan slider return value - break; - case 'Q': // focuser home run start - break; - case 'U': // back focus minimum for optic "None" - break; - case 'V': // back focus maximum for optic "None" - break; - case 'W': // back focus minimum for optic "Corrector" - break; - case 'X': // back focus maximum for optic "Corrector" - break; - case 'Y': // back focus minimum for optic "Reducer" - break; - case 'Z': // back focus maximum for optic "Reducer" - break; - case 'a': // ambient temperature correction value - break; - case 'b': // primary temperature correction value - break; - case 'c': // secondary temperature correction value - break; - case 'K': // new focuser position - pos = 1e6; - sscanf(res, "K#%d", &pos); - //LOGF_INFO("TimerHit: new pos (%d)",pos); - if (pos != 1e6) { - FocusAbsPosN[0].value = pos; - } - FocusAbsPosNP.s = IPS_BUSY; - FocusRelPosNP.s = IPS_BUSY; - IDSetNumber(&FocusAbsPosNP, nullptr); - break; - case 'I': // starting to focus - LOG_INFO("TimerHit: starting to focus"); - break; - case 'J': // end of focusing - LOG_INFO("TimetHit: end of focusing"); - isFocuserMoving = false; - FocusAbsPosNP.s = IPS_OK; - IDSetNumber(&FocusAbsPosNP, nullptr); - receiveDone(); - break; - case 'O': // cover started moving - LOG_INFO("TimerHit: cover started moving"); - CoverSP.s = IPS_BUSY; - break; - case 'H': // cover stopped moving - LOG_INFO("TimetHit: cover stopped moving"); - isCoverMoving = false; - receiveDone(); - - //CoverS[COVER_OPEN ].s = - //CoverS[COVER_CLOSED].s = - CoverSP.s = IPS_OK; - - break; - default: // unexpected output - LOGF_INFO("TimerHit: unexpected response (%s)", res); + char res[DRIVER_LEN] = {0}; + + // read a line, if available + while (receiveNext(res)) + { + if (res[1]=='#') { + if (res[0] == 'K') { // new focuser position + int32_t pos = 1e6; + sscanf(res, "K#%d", &pos); + LOGF_DEBUG("TimerHit: new pos (%d)",pos); + if (pos == 1e6) { + break; + } + FocusAbsPosN[0].value = pos; + continue; + + } else if (res[0] == 'I') { // starting to focus + LOG_DEBUG("TimerHit: starting to focus"); + continue; + } else if (res[0] == 'J' ) { // end of focusing + LOG_DEBUG("TimetHit: end of focusing"); + isFocuserMoving = false; + break; + } + // we read something not expected + LOGF_ERROR("TimerHit: unexpected output1 (%s)", res); + isFocuserMoving = false; + } else { + // we read something not expected + LOGF_ERROR("TimerHit: unexpected output2 (%s)", res); + isFocuserMoving = false; } - } else { - LOGF_INFO("TimerHit: unexpected response (%s)", res); } - actionInProgress = isFocuserMoving || isCoverMoving; } + else + { + // What is the last read position? + double currentPosition = FocusAbsPosN[0].value; - // What is the last read position? - double currentPosition = FocusAbsPosN[0].value; + // Read the current position + readPosition(); - // Check if we have a pending motion - // if isMoving() is false, then we stopped, so we need to set the Focus Absolute - // and relative properties to OK - if ( (FocusAbsPosNP.s == IPS_BUSY || FocusRelPosNP.s == IPS_BUSY) ) - { - FocusAbsPosNP.s = IPS_OK; - FocusRelPosNP.s = IPS_OK; - IDSetNumber(&FocusAbsPosNP, nullptr); - IDSetNumber(&FocusRelPosNP, nullptr); - } - // If there was a different between last and current positions, let's update all clients - else if (currentPosition != FocusAbsPosN[0].value) - { - IDSetNumber(&FocusAbsPosNP, nullptr); + // Check if we have a pending motion + // if isMoving() is false, then we stopped, so we need to set the Focus Absolute + // and relative properties to OK + if ( (FocusAbsPosNP.s == IPS_BUSY || FocusRelPosNP.s == IPS_BUSY) ) + { + FocusAbsPosNP.s = IPS_OK; + FocusRelPosNP.s = IPS_OK; + IDSetNumber(&FocusAbsPosNP, nullptr); + IDSetNumber(&FocusRelPosNP, nullptr); + } + // If there was a different between last and current positions, let's update all clients + else if (currentPosition != FocusAbsPosN[0].value) + { + IDSetNumber(&FocusAbsPosNP, nullptr); + } + + // Read temperature periodically + if (TemperatureNP.s == IPS_OK && m_TemperatureCounter++ == DRIVER_TEMPERATURE_FREQ) + { + m_TemperatureCounter = 0; + if (readTemperature()) + IDSetNumber(&TemperatureNP, nullptr); + } } SetTimer(getCurrentPollingPeriod()); } -bool AllunaTCS2::isMoving() + + +bool TCS2Focuser::isMoving() { return isFocuserMoving; } -bool AllunaTCS2::readTemperature() +bool TCS2Focuser::readTemperature() { - // timestamp, when we updated temperatur - static std::chrono::system_clock::time_point last_temp_update = std::chrono::system_clock::now(); - static bool first_run = true; + char res[DRIVER_LEN] = {0}; // the command GetTemperatures will respond with 4 lines: // R#{ambient_temperature} @@ -743,104 +424,46 @@ bool AllunaTCS2::readTemperature() // T#{secondary_mirror_temperature} // d#{ambient-humidity} - std::chrono::duration seconds = std::chrono::system_clock::now() - last_temp_update; - if ( !first_run && seconds.count() < 300 ) // update all 300 seconds - { - if (tcs.try_lock()) { - tcs.unlock(); // we need to get lock, to make TimerHit behave the same when we block reading temperature - return true; // return true, if we could get the lock - } else { - return false; // return false, if we could not get the lock - } - } - else if ( sendCommandOnly("GetTemperatures\n") ) - { - TemperatureNP.s = IPS_BUSY; - isGetTemperature = true; - - // expect and process device output while present - char res[DRIVER_LEN] = {0}; - float value; - - // read a line, if available - while (isGetTemperature && receiveNext(res)) - { - switch (res[0]) - { - case 'R': // ambient temperature value - sscanf(res, "R#%f", &value); - TemperatureN[0].value = value; - break; - case 'S': // primary mirror temperature value - sscanf(res, "S#%f", &value); - TemperatureN[1].value = value; - break; - case 'T': // secondary mirror temperature value - sscanf(res, "T#%f", &value); - TemperatureN[2].value = value; - break; - case 'd': // ambient humidity value - sscanf(res, "d#%f", &value); - TemperatureN[3].value = value; - receiveDone(); - isGetTemperature=false; - TemperatureNP.s = IPS_OK; - IDSetNumber(&TemperatureNP, nullptr); - break; - default: // unexpected output - LOGF_ERROR("GetTemperatures: unexpected response (%s)", res); - } - } - first_run = false; - last_temp_update = std::chrono::system_clock::now(); - return true; - } - return false; -} - -bool AllunaTCS2::readPosition() -{ - char res[DRIVER_LEN] = {0}; + if (sendCommandOnly("GetTemperatures\n") == false) + return false; - if (sendCommand("GetFocuserPosition\n", res, 0) == false) + if (receiveNext(res) == false) return false; - int32_t pos = 1e6; - sscanf(res, "%d", &pos); + float temperature = -1000; + sscanf(res, "R#%f", &temperature); - if (pos == 1e6) + if (temperature < -100) return false; - FocusAbsPosN[0].value = pos; + TemperatureN[0].value = temperature; + TemperatureNP.s = IPS_OK; - FocusAbsPosNP.s = IPS_OK; - IDSetNumber(&FocusAbsPosNP, nullptr); // display in user interface + // ignore the remaining output + receiveDone(); return true; } -bool AllunaTCS2::readDustCover() +bool TCS2Focuser::readPosition() { char res[DRIVER_LEN] = {0}; - if (sendCommand("GetDustCover\n", res, 0) == false) + if (sendCommand("GetFocuserPosition\n", res, 0) == false) return false; - int32_t value = -1; - sscanf(res, "%d", &value); + int32_t pos = 1e6; + sscanf(res, "%d", &pos); - if (value == -1) + if (pos == 1e6) return false; - DEBUGF(INDI::Logger::DBG_SESSION, "Cover status read to be %s (%d)", (value==1)?"open":"closed", value); - CoverS[COVER_OPEN ].s = (value==1)?ISS_ON:ISS_OFF; - CoverS[COVER_CLOSED].s = (value!=1)?ISS_ON:ISS_OFF; - CoverSP.s = IPS_OK; + FocusAbsPosN[0].value = pos; return true; } -bool AllunaTCS2::readStepping() +bool TCS2Focuser::readStepping() { char res[DRIVER_LEN] = {0}; @@ -854,135 +477,27 @@ bool AllunaTCS2::readStepping() return false; // mode=1: Microstep, mode=0: Speedstep - steppingMode = (mode == 0) ? STEPPING_SPEED : STEPPING_MICRO; SteppingModeS[STEPPING_SPEED].s = (mode == 0) ? ISS_ON : ISS_OFF; SteppingModeS[STEPPING_MICRO].s = (mode == 0) ? ISS_OFF : ISS_ON; SteppingModeSP.s = IPS_OK; - // Set limits as per documentation - FocusAbsPosN[0].max = (steppingMode == STEPPING_MICRO) ? 22400 : 1400; // 22400 in microstep mode, 1400 in speedstep mode - LOGF_INFO("readStepping: set max position to %d",(int)FocusAbsPosN[0].max); - return true; -} - -bool AllunaTCS2::setStepping(SteppingMode mode) -{ - int value; - char cmd[DRIVER_LEN] = {0}; - steppingMode=mode; - value = (mode == STEPPING_SPEED) ? 0 : 1; - LOGF_INFO("Setting stepping mde to: %s", (mode==STEPPING_SPEED)?"SPEED":"micro"); - LOGF_INFO("Setting stepping mode to: %d", value); - snprintf(cmd, DRIVER_LEN, "SetFocuserMode %d\n", value); - return sendCommand(cmd); -} - -bool AllunaTCS2::setDustCover() -{ - char cmd[DRIVER_LEN] = {0}; - snprintf(cmd, DRIVER_LEN, "SetDustCover\n"); // opens/closes dust cover (state toggle) - return sendCommandOnly(cmd); // response is processed in TimerHit -} - -bool AllunaTCS2::readClimateControl() -{ - char res[DRIVER_LEN] = {0}; - - if (sendCommand("GetClimateControl\n", res, 0) == false) - return false; - - int32_t value = -1; - sscanf(res, "%d", &value); - - if (value == -1) - return false; - - DEBUGF(INDI::Logger::DBG_SESSION, "Climate Control status read to be %s (%d)", (value==1)?"automatic":"manual", value); - ClimateControlS[CLIMATECONTROL_AUTO ].s = (value==1)?ISS_ON:ISS_OFF; - ClimateControlS[CLIMATECONTROL_MANUAL].s = (value!=1)?ISS_ON:ISS_OFF; - ClimateControlSP.s = IPS_OK; - - return true; -} - -bool AllunaTCS2::setClimateControl(ClimateControlMode mode) -{ - char cmd[DRIVER_LEN] = {0}; - int value; - value = (mode == CLIMATECONTROL_AUTO) ? 1 : 0; - snprintf(cmd, DRIVER_LEN, "SetClimateControl %d\n", value); // enable/disable climate control - return sendCommand(cmd); -} - -bool AllunaTCS2::readPrimaryDewHeater() -{ - char res[DRIVER_LEN] = {0}; - - if (sendCommand("GetAux1\n", res, 0) == false) - return false; - - int32_t value = -1; - sscanf(res, "%d", &value); - - if (value == -1) - return false; - - DEBUGF(INDI::Logger::DBG_SESSION, "PrimaryDewHeater status read to be %s (%d)", (value==1)?"ON":"OFF", value); - PrimaryDewHeaterS[DEWHEATER_ON ].s = (value==1)?ISS_ON:ISS_OFF; - PrimaryDewHeaterS[DEWHEATER_OFF].s = (value!=1)?ISS_ON:ISS_OFF; - PrimaryDewHeaterSP.s = IPS_OK; - return true; } -bool AllunaTCS2::setPrimaryDewHeater(DewHeaterMode mode) +bool TCS2Focuser::setStepping(SteppingMode mode) { char cmd[DRIVER_LEN] = {0}; - int value; - value = (mode == DEWHEATER_ON) ? 1 : 0; - snprintf(cmd, DRIVER_LEN, "SetAux1 %d\n", value); // enable/disable heating + snprintf(cmd, DRIVER_LEN, "SetFocuserMode %d\n", mode); return sendCommand(cmd); } -bool AllunaTCS2::readSecondaryDewHeater() -{ - char res[DRIVER_LEN] = {0}; - - if (sendCommand("GetAux2\n", res, 0) == false) - return false; - - int32_t value = -1; - sscanf(res, "%d", &value); - - if (value == -1) - return false; - - DEBUGF(INDI::Logger::DBG_SESSION, "SecondaryDewHeater status read to be %s (%d)", (value==1)?"ON":"OFF", value); - SecondaryDewHeaterS[DEWHEATER_ON ].s = (value==1)?ISS_ON:ISS_OFF; - SecondaryDewHeaterS[DEWHEATER_OFF].s = (value!=1)?ISS_ON:ISS_OFF; - SecondaryDewHeaterSP.s = IPS_OK; - - return true; -} - -bool AllunaTCS2::setSecondaryDewHeater(DewHeaterMode mode) -{ - char cmd[DRIVER_LEN] = {0}; - int value; - value = (mode == DEWHEATER_ON) ? 1 : 0; - snprintf(cmd, DRIVER_LEN, "SetAux2 %d\n", value); // enable/disable heating - return sendCommand(cmd); -} - - -bool AllunaTCS2::saveConfigItems(FILE *fp) +bool TCS2Focuser::saveConfigItems(FILE *fp) { INDI::Focuser::saveConfigItems(fp); // We need to reserve and save stepping mode // so that the next time the driver is loaded, it is remembered and applied. - //IUSaveConfigSwitch(fp, &SteppingModeSP); -- not needed, because tcs2 stores state internally + IUSaveConfigSwitch(fp, &SteppingModeSP); return true; } - diff --git a/drivers/focuser/alluna_tcs2.h b/drivers/focuser/alluna_tcs2.h index 76a4b5018e..7a71cf5592 100644 --- a/drivers/focuser/alluna_tcs2.h +++ b/drivers/focuser/alluna_tcs2.h @@ -26,88 +26,54 @@ */ #pragma once -#include "indibase.h" + #include "indifocuser.h" -#include "indifocuserinterface.h" -#include "indidustcapinterface.h" -#include -#include -#include -class AllunaTCS2 : public INDI::Focuser //, public INDI::DustCapInterface +class TCS2Focuser : public INDI::Focuser { public: - AllunaTCS2(); + TCS2Focuser(); virtual bool Handshake() override; const char *getDefaultName() override; bool initProperties() override; bool updateProperties() override; - void ISGetProperties(const char *dev) override; bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override; - bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override; + + // If you define any Number properties, then you need to override ISNewNumber to repond to number property requests. + //bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override; + + void debugTriggered(bool enable) override; protected: - // From INDI::DefaultDevice void TimerHit() override; - bool saveConfigItems(FILE *fp) override; - // From INDI::Focuser IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override; IPState MoveAbsFocuser(uint32_t targetTicks) override; bool AbortFocuser() override; - private: - // while a multi response action is running (such as GetTemperatures), we cannot process additional commands - std::mutex tcs; + bool saveConfigItems(FILE *fp) override; + private: // focuser internal state bool isFocuserMoving = false; - // is getTemperature command in progress - bool isGetTemperature = false; - - // is setDustCover command in progress - bool isCoverMoving = false; - - // Climate + /////////////////////////////////////////////////////////////////////////////// + /// Properties + /////////////////////////////////////////////////////////////////////////////// INumberVectorProperty TemperatureNP; - INumber TemperatureN[4]; // note: last value is humidity, not temperature. - ISwitchVectorProperty ClimateControlSP; - ISwitch ClimateControlS[2]; - typedef enum - { - CLIMATECONTROL_AUTO, - CLIMATECONTROL_MANUAL, - } ClimateControlMode; - ISwitchVectorProperty PrimaryDewHeaterSP, SecondaryDewHeaterSP; - ISwitch PrimaryDewHeaterS[2], SecondaryDewHeaterS[2]; - typedef enum - { - DEWHEATER_ON, - DEWHEATER_OFF, - } DewHeaterMode; - INumberVectorProperty FanPowerNP; - INumber FanPowerN[1]; + INumber TemperatureN[1]; - // Focuser ISwitchVectorProperty SteppingModeSP; ISwitch SteppingModeS[2]; typedef enum { - STEPPING_MICRO = 1, - STEPPING_SPEED = 0, + STEPPING_SPEED, + STEPPING_MICRO, } SteppingMode; - SteppingMode steppingMode=STEPPING_MICRO; - - // Dust cover - ISwitch CoverS[2]; - ISwitchVectorProperty CoverSP; - enum { COVER_OPEN, COVER_CLOSED }; - /////////////////////////////////////////////////////////////////////////////// /// Read Data From Controller @@ -115,19 +81,11 @@ class AllunaTCS2 : public INDI::Focuser //, public INDI::DustCapInterface bool readTemperature(); bool readPosition(); bool readStepping(); - bool readDustCover(); - bool readClimateControl(); - bool readPrimaryDewHeater(); - bool readSecondaryDewHeater(); /////////////////////////////////////////////////////////////////////////////// /// Write Data to Controller /////////////////////////////////////////////////////////////////////////////// bool setStepping(SteppingMode mode); - bool setDustCover(void); // open/close dust cover - bool setClimateControl(ClimateControlMode mode); // turn on/off climate control - bool setPrimaryDewHeater(DewHeaterMode mode); // turn on/off climate control - bool setSecondaryDewHeater(DewHeaterMode mode); // turn on/off climate control /////////////////////////////////////////////////////////////////////////////// /// Utility Functions @@ -144,7 +102,6 @@ class AllunaTCS2 : public INDI::Focuser //, public INDI::DustCapInterface * @return True if successful, false otherwise. */ bool sendCommand(const char * cmd, char * res = nullptr, int cmd_len = -1, int res_len = -1); - bool sendCommandNoLock(const char * cmd, char * res = nullptr, int cmd_len = -1, int res_len = -1); bool sendCommandOnly(const char * cmd, int cmd_len = -1); bool receiveNext(char * res, int res_len = -1); void receiveDone(); @@ -158,6 +115,12 @@ class AllunaTCS2 : public INDI::Focuser //, public INDI::DustCapInterface */ void hexDump(char * buf, const char * data, int size); + /** + * @brief getStartupValues Call once during startup + * @return True if all values read OK, false otherwise + */ + bool getStartupValues(); + /** * @return is the focuser in motion? */ @@ -172,17 +135,14 @@ class AllunaTCS2 : public INDI::Focuser //, public INDI::DustCapInterface ///////////////////////////////////////////////////////////////////////////// /// Static Helper Values ///////////////////////////////////////////////////////////////////////////// - static constexpr const char * FOCUSER_TAB = "Focus"; - static constexpr const char * ROTATOR_TAB = "Rotate"; - static constexpr const char * CLIMATE_TAB = "Climate"; - static constexpr const char * DUSTCOVER_TAB = "Dust Cover"; + static constexpr const char * STEPPING_TAB = "Stepping"; // 'LF' is the stop char static const char DRIVER_STOP_CHAR { 0x0A }; // Update temperature every 10x POLLMS. For 500ms, we would // update the temperature one every 5 seconds. static constexpr const uint8_t DRIVER_TEMPERATURE_FREQ {10}; // Wait up to a maximum of 3 seconds for serial input - static constexpr const uint8_t DRIVER_TIMEOUT {5}; + static constexpr const uint8_t DRIVER_TIMEOUT {3}; // Maximum buffer for sending/receving. static constexpr const uint8_t DRIVER_LEN {64}; };