diff --git a/drivers.xml b/drivers.xml index 4bfadac863..bb85e52bf5 100644 --- a/drivers.xml +++ b/drivers.xml @@ -683,6 +683,10 @@ indi_pegasusindigo_wheel 1.0 + + indi_ioptron_wheel + 1.0 + diff --git a/drivers/auxiliary/CMakeLists.txt b/drivers/auxiliary/CMakeLists.txt index 10aed1199d..d7c750fb4f 100644 --- a/drivers/auxiliary/CMakeLists.txt +++ b/drivers/auxiliary/CMakeLists.txt @@ -25,6 +25,14 @@ add_executable(indi_wanderercover_v4_ec ${indi_wanderercover_v4_ec_SRC}) target_link_libraries(indi_wanderercover_v4_ec indidriver) install(TARGETS indi_wanderercover_v4_ec RUNTIME DESTINATION bin) +# ########## Astrolink-4############### +SET(indi_astrolink4_SRC + indi_astrolink4.cpp) + +add_executable(indi_astrolink4 ${indi_wanderercover_v4_ec_SRC}) +target_link_libraries(indi_astrolink4 indidriver) +install(TARGETS indi_astrolink4 RUNTIME DESTINATION bin) + # ########## Wanderer Box Plus V3 ############### SET(indi_wandererbox_plus_v3_SRC wandererbox_plus_v3.cpp) diff --git a/drivers/auxiliary/indi_astrolink4.cpp b/drivers/auxiliary/indi_astrolink4.cpp new file mode 100644 index 0000000000..a1fa91979b --- /dev/null +++ b/drivers/auxiliary/indi_astrolink4.cpp @@ -0,0 +1,1063 @@ +/******************************************************************************* + Copyright(c) 2019 astrojolo.com + . + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + . + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + . + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*******************************************************************************/ +#include "indi_astrolink4.h" +#include "config.h" + +#include "indicom.h" + +#define ASTROLINK4_LEN 100 +#define ASTROLINK4_TIMEOUT 3 + +////////////////////////////////////////////////////////////////////// +/// Delegates +////////////////////////////////////////////////////////////////////// +static std::unique_ptr indiAstrolink4(new IndiAstrolink4()); + +////////////////////////////////////////////////////////////////////// +///Constructor +////////////////////////////////////////////////////////////////////// +IndiAstrolink4::IndiAstrolink4() : FI(this), WI(this) +{ + setVersion(ASTROLINK4_VERSION_MAJOR, ASTROLINK4_VERSION_MINOR); +} + +const char * IndiAstrolink4::getDefaultName() +{ + return "AstroLink 4"; +} + +////////////////////////////////////////////////////////////////////// +/// Communication +////////////////////////////////////////////////////////////////////// +bool IndiAstrolink4::Handshake() +{ + PortFD = serialConnection->getPortFD(); + + char res[ASTROLINK4_LEN] = {0}; + if(sendCommand("#", res)) + { + if(strncmp(res, "#:AstroLink4mini", 15) != 0) + { + LOG_ERROR("Device not recognized."); + return false; + } + else + { + SetTimer(getCurrentPollingPeriod()); + return true; + } + } + return false; +} + +void IndiAstrolink4::TimerHit() +{ + if(!isConnected()) + return; + + sensorRead(); + SetTimer(getCurrentPollingPeriod()); +} + +////////////////////////////////////////////////////////////////////// +/// Overrides +////////////////////////////////////////////////////////////////////// +bool IndiAstrolink4::initProperties() +{ + INDI::DefaultDevice::initProperties(); + + setDriverInterface(AUX_INTERFACE | FOCUSER_INTERFACE | WEATHER_INTERFACE); + + FI::SetCapability(FOCUSER_CAN_ABS_MOVE | + FOCUSER_CAN_REL_MOVE | + FOCUSER_CAN_REVERSE | + FOCUSER_CAN_SYNC | + FOCUSER_CAN_ABORT | + FOCUSER_HAS_BACKLASH); + + FI::initProperties(FOCUS_TAB); + WI::initProperties(ENVIRONMENT_TAB, ENVIRONMENT_TAB); + + addDebugControl(); + addSimulationControl(); + addConfigurationControl(); + + // focuser settings + IUFillNumber(&FocuserSettingsN[FS_SPEED], "FS_SPEED", "Speed [pps]", "%.0f", 0, 4000, 50, 250); + IUFillNumber(&FocuserSettingsN[FS_STEP_SIZE], "FS_STEP_SIZE", "Step size [um]", "%.2f", 0, 100, 0.1, 5.0); + IUFillNumber(&FocuserSettingsN[FS_COMPENSATION], "FS_COMPENSATION", "Compensation [steps/C]", "%.2f", -1000, 1000, 1, 0); + IUFillNumber(&FocuserSettingsN[FS_COMP_THRESHOLD], "FS_COMP_THRESHOLD", "Compensation threshold [steps]", "%.0f", 1, 1000, + 10, 10); + IUFillNumberVector(&FocuserSettingsNP, FocuserSettingsN, 4, getDeviceName(), "FOCUSER_SETTINGS", "Focuser settings", + SETTINGS_TAB, IP_RW, 60, IPS_IDLE); + + IUFillSwitch(&FocuserModeS[FS_MODE_UNI], "FS_MODE_UNI", "Unipolar", ISS_ON); + IUFillSwitch(&FocuserModeS[FS_MODE_BI], "FS_MODE_BI", "Bipolar", ISS_OFF); + IUFillSwitch(&FocuserModeS[FS_MODE_MICRO], "FS_MODE_MICRO", "Microstep", ISS_OFF); + IUFillSwitchVector(&FocuserModeSP, FocuserModeS, 3, getDeviceName(), "FOCUSER_MODE", "Focuser mode", SETTINGS_TAB, IP_RW, + ISR_1OFMANY, 60, IPS_IDLE); + + IUFillSwitch(&FocuserCompModeS[FS_COMP_AUTO], "FS_COMP_AUTO", "AUTO", ISS_OFF); + IUFillSwitch(&FocuserCompModeS[FS_COMP_MANUAL], "FS_COMP_MANUAL", "MANUAL", ISS_ON); + IUFillSwitchVector(&FocuserCompModeSP, FocuserCompModeS, 2, getDeviceName(), "COMP_MODE", "Compensation mode", SETTINGS_TAB, + IP_RW, ISR_1OFMANY, 60, IPS_IDLE); + + IUFillSwitch(&FocuserManualS[FS_MANUAL_ON], "FS_MANUAL_ON", "ON", ISS_ON); + IUFillSwitch(&FocuserManualS[FS_MANUAL_OFF], "FS_MANUAL_OFF", "OFF", ISS_OFF); + IUFillSwitchVector(&FocuserManualSP, FocuserManualS, 2, getDeviceName(), "MANUAL_CONTROLLER", "Hand controller", + SETTINGS_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); + + // other settings + IUFillNumber(&OtherSettingsN[SET_AREF_COEFF], "SET_AREF_COEFF", "V ref coefficient", "%.3f", 0.9, 1.2, 0.001, 1.09); + IUFillNumber(&OtherSettingsN[SET_OVER_TIME], "SET_OVER_TIME", "Protection sensitivity [ms]", "%.0f", 10, 500, 10, 100); + IUFillNumber(&OtherSettingsN[SET_OVER_VOLT], "SET_OVER_VOLT", "Protection voltage [V]", "%.1f", 10, 14, 0.1, 14); + IUFillNumber(&OtherSettingsN[SET_OVER_AMP], "SET_OVER_AMP", "Protection current [A]", "%.1f", 1, 10, 0.1, 10); + IUFillNumberVector(&OtherSettingsNP, OtherSettingsN, 4, getDeviceName(), "OTHER_SETTINGS", "Device settings", SETTINGS_TAB, + IP_RW, 60, IPS_IDLE); + + IUFillSwitch(&BuzzerS[0], "BUZZER", "Buzzer", ISS_OFF); + IUFillSwitchVector(&BuzzerSP, BuzzerS, 1, getDeviceName(), "BUZZER", "ONOFF", SETTINGS_TAB, IP_RW, ISR_NOFMANY, 60, + IPS_IDLE); + + // focuser compensation + IUFillNumber(&CompensationValueN[0], "COMP_VALUE", "Compensation steps", "%.0f", -10000, 10000, 1, 0); + IUFillNumberVector(&CompensationValueNP, CompensationValueN, 1, getDeviceName(), "COMP_STEPS", "Compensation steps", + FOCUS_TAB, IP_RO, 60, IPS_IDLE); + + IUFillSwitch(&CompensateNowS[0], "COMP_NOW", "Compensate now", ISS_OFF); + IUFillSwitchVector(&CompensateNowSP, CompensateNowS, 1, getDeviceName(), "COMP_NOW", "Compensate now", FOCUS_TAB, IP_RW, + ISR_ATMOST1, 60, IPS_IDLE); + + IUFillNumber(&FocusPosMMN[0], "FOC_POS_MM", "Position [mm]", "%.3f", 0.0, 200.0, 0.001, 0.0); + IUFillNumberVector(&FocusPosMMNP, FocusPosMMN, 1, getDeviceName(), "FOC_POS_MM", "Position [mm]", FOCUS_TAB, IP_RO, 60, + IPS_IDLE); + + // power lines + IUFillText(&PowerControlsLabelsT[0], "POWER_LABEL_1", "Port 1", "Port 1"); + IUFillText(&PowerControlsLabelsT[1], "POWER_LABEL_2", "Port 2", "Port 2"); + IUFillText(&PowerControlsLabelsT[2], "POWER_LABEL_3", "Port 3", "Port 3"); + IUFillTextVector(&PowerControlsLabelsTP, PowerControlsLabelsT, 3, getDeviceName(), "POWER_CONTROL_LABEL", "Power Labels", + POWER_TAB, IP_WO, 60, IPS_IDLE); + + char portLabel1[MAXINDILABEL], portLabel2[MAXINDILABEL], portLabel3[MAXINDILABEL]; + + memset(portLabel1, 0, MAXINDILABEL); + int portRC1 = IUGetConfigText(getDeviceName(), PowerControlsLabelsTP.name, PowerControlsLabelsT[0].name, portLabel1, + MAXINDILABEL); + + IUFillSwitch(&Power1S[0], "PWR1BTN_ON", "ON", ISS_OFF); + IUFillSwitch(&Power1S[1], "PWR1BTN_OFF", "OFF", ISS_ON); + IUFillSwitchVector(&Power1SP, Power1S, 2, getDeviceName(), "DC1", portRC1 == -1 ? "Port 1" : portLabel1, POWER_TAB, IP_RW, + ISR_1OFMANY, 0, IPS_IDLE); + + memset(portLabel2, 0, MAXINDILABEL); + int portRC2 = IUGetConfigText(getDeviceName(), PowerControlsLabelsTP.name, PowerControlsLabelsT[1].name, portLabel2, + MAXINDILABEL); + + IUFillSwitch(&Power2S[0], "PWR2BTN_ON", "ON", ISS_OFF); + IUFillSwitch(&Power2S[1], "PWR2BTN_OFF", "OFF", ISS_ON); + IUFillSwitchVector(&Power2SP, Power2S, 2, getDeviceName(), "DC2", portRC2 == -1 ? "Port 2" : portLabel2, POWER_TAB, IP_RW, + ISR_1OFMANY, 0, IPS_IDLE); + + memset(portLabel3, 0, MAXINDILABEL); + int portRC3 = IUGetConfigText(getDeviceName(), PowerControlsLabelsTP.name, PowerControlsLabelsT[2].name, portLabel3, + MAXINDILABEL); + + IUFillSwitch(&Power3S[0], "PWR3BTN_ON", "ON", ISS_OFF); + IUFillSwitch(&Power3S[1], "PWR3BTN_OFF", "OFF", ISS_ON); + IUFillSwitchVector(&Power3SP, Power3S, 2, getDeviceName(), "DC3", portRC3 == -1 ? "Port 3" : portLabel3, POWER_TAB, IP_RW, + ISR_1OFMANY, 0, IPS_IDLE); + + IUFillSwitch(&PowerDefaultOnS[0], "POW_DEF_ON1", "DC1", ISS_OFF); + IUFillSwitch(&PowerDefaultOnS[1], "POW_DEF_ON2", "DC2", ISS_OFF); + IUFillSwitch(&PowerDefaultOnS[2], "POW_DEF_ON3", "DC3", ISS_OFF); + IUFillSwitchVector(&PowerDefaultOnSP, PowerDefaultOnS, 3, getDeviceName(), "POW_DEF_ON", "Power default ON", SETTINGS_TAB, + IP_RW, ISR_NOFMANY, 60, IPS_IDLE); + + // pwm + IUFillNumber(&PWMN[0], "PWM1_VAL", "A", "%3.0f", 0, 100, 10, 0); + IUFillNumber(&PWMN[1], "PWM2_VAL", "B", "%3.0f", 0, 100, 10, 0); + IUFillNumberVector(&PWMNP, PWMN, 2, getDeviceName(), "PWM", "PWM", POWER_TAB, IP_RW, 60, IPS_IDLE); + + // Auto pwm + IUFillSwitch(&AutoPWMDefaultOnS[0], "PWMA_A_DEF_ON", "A", ISS_OFF); + IUFillSwitch(&AutoPWMDefaultOnS[1], "PWMA_B_DEF_ON", "B", ISS_OFF); + IUFillSwitchVector(&AutoPWMDefaultOnSP, AutoPWMDefaultOnS, 2, getDeviceName(), "AUTO_PWM_DEF_ON", "Auto PWM default ON", + SETTINGS_TAB, + IP_RW, ISR_NOFMANY, 60, IPS_IDLE); + + ISState pwmAutoA = ISS_OFF; + IUGetConfigSwitch(getDeviceName(), AutoPWMDefaultOnSP.name, AutoPWMDefaultOnS[0].name, &pwmAutoA); + + ISState pwmAutoB = ISS_OFF; + IUGetConfigSwitch(getDeviceName(), AutoPWMDefaultOnSP.name, AutoPWMDefaultOnS[1].name, &pwmAutoB); + + IUFillSwitch(&AutoPWMS[0], "PWMA_A", "A", pwmAutoA); + IUFillSwitch(&AutoPWMS[1], "PWMA_B", "B", pwmAutoB); + IUFillSwitchVector(&AutoPWMSP, AutoPWMS, 2, getDeviceName(), "AUTO_PWM", "Auto PWM", POWER_TAB, IP_RW, ISR_NOFMANY, 60, + IPS_OK); + + IUFillNumber(&PowerDataN[POW_VIN], "VIN", "Input voltage", "%.1f", 0, 15, 10, 0); + IUFillNumber(&PowerDataN[POW_VREG], "VREG", "Regulated voltage", "%.1f", 0, 15, 10, 0); + IUFillNumber(&PowerDataN[POW_ITOT], "ITOT", "Total current", "%.1f", 0, 15, 10, 0); + IUFillNumber(&PowerDataN[POW_AH], "AH", "Energy consumed [Ah]", "%.1f", 0, 1000, 10, 0); + IUFillNumber(&PowerDataN[POW_WH], "WH", "Energy consumed [Wh]", "%.1f", 0, 10000, 10, 0); + IUFillNumberVector(&PowerDataNP, PowerDataN, 5, getDeviceName(), "POWER_DATA", "Power data", POWER_TAB, IP_RO, 60, + IPS_IDLE); + + // Environment Group + addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -15, 35, 15); + addParameter("WEATHER_HUMIDITY", "Humidity %", 0, 100, 15); + addParameter("WEATHER_DEWPOINT", "Dew Point (C)", 0, 100, 15); + + // Sensor 2 + IUFillNumber(&Sensor2N[0], "TEMP_2", "Temperature (C)", "%.1f", -50, 100, 1, 0); + IUFillNumberVector(&Sensor2NP, Sensor2N, 1, getDeviceName(), "SENSOR_2", "Sensor 2", ENVIRONMENT_TAB, IP_RO, 60, IPS_IDLE); + + // DC focuser + IUFillNumber(&DCFocTimeN[DC_PERIOD], "DC_PERIOD", "Time [ms]", "%.0f", 10, 5000, 10, 500); + IUFillNumber(&DCFocTimeN[DC_PWM], "DC_PWM", "PWM [%]", "%.0f", 10, 100, 10, 50); + IUFillNumberVector(&DCFocTimeNP, DCFocTimeN, 2, getDeviceName(), "DC_FOC_TIME", "DC Focuser", DCFOCUSER_TAB, IP_RW, 60, + IPS_OK); + + IUFillSwitch(&DCFocDirS[0], "DIR_IN", "IN", ISS_OFF); + IUFillSwitch(&DCFocDirS[1], "DIR_OUT", "OUT", ISS_ON); + IUFillSwitchVector(&DCFocDirSP, DCFocDirS, 2, getDeviceName(), "DC_FOC_DIR", "DC Focuser direction", DCFOCUSER_TAB, IP_RW, + ISR_1OFMANY, 60, IPS_OK); + + IUFillSwitch(&DCFocAbortS[0], "DC_FOC_ABORT", "STOP", ISS_OFF); + IUFillSwitchVector(&DCFocAbortSP, DCFocAbortS, 1, getDeviceName(), "DC_FOC_ABORT", "DC Focuser stop", DCFOCUSER_TAB, IP_RW, + ISR_ATMOST1, 60, IPS_IDLE); + + serialConnection = new Connection::Serial(this); + serialConnection->registerHandshake([&]() + { + return Handshake(); + }); + registerConnection(serialConnection); + + serialConnection->setDefaultPort("/dev/ttyUSB0"); + serialConnection->setDefaultBaudRate(serialConnection->B_115200); + + return true; +} + +bool IndiAstrolink4::updateProperties() +{ + // Call parent update properties first + INDI::DefaultDevice::updateProperties(); + + if (isConnected()) + { + defineProperty(&FocusPosMMNP); + FI::updateProperties(); + WI::updateProperties(); + defineProperty(&Power1SP); + defineProperty(&Power2SP); + defineProperty(&Power3SP); + defineProperty(&AutoPWMSP); + defineProperty(&Sensor2NP); + defineProperty(&PWMNP); + defineProperty(&PowerDataNP); + defineProperty(&FocuserSettingsNP); + defineProperty(&FocuserModeSP); + defineProperty(&FocuserCompModeSP); + defineProperty(&FocuserManualSP); + defineProperty(&CompensationValueNP); + defineProperty(&CompensateNowSP); + defineProperty(&PowerDefaultOnSP); + defineProperty(&AutoPWMDefaultOnSP); + defineProperty(&OtherSettingsNP); + defineProperty(&DCFocDirSP); + defineProperty(&DCFocTimeNP); + defineProperty(&DCFocAbortSP); + defineProperty(&PowerControlsLabelsTP); + defineProperty(&BuzzerSP); + } + else + { + deleteProperty(Power1SP.name); + deleteProperty(Power2SP.name); + deleteProperty(Power3SP.name); + deleteProperty(AutoPWMSP.name); + deleteProperty(Sensor2NP.name); + deleteProperty(PWMNP.name); + deleteProperty(PowerDataNP.name); + deleteProperty(FocuserSettingsNP.name); + deleteProperty(FocuserModeSP.name); + deleteProperty(CompensateNowSP.name); + deleteProperty(CompensationValueNP.name); + deleteProperty(PowerDefaultOnSP.name); + deleteProperty(AutoPWMDefaultOnSP.name); + deleteProperty(OtherSettingsNP.name); + deleteProperty(DCFocTimeNP.name); + deleteProperty(DCFocDirSP.name); + deleteProperty(DCFocAbortSP.name); + deleteProperty(BuzzerSP.name); + deleteProperty(FocuserCompModeSP.name); + deleteProperty(FocuserManualSP.name); + deleteProperty(FocusPosMMNP.name); + deleteProperty(PowerControlsLabelsTP.name); + FI::updateProperties(); + WI::updateProperties(); + } + + return true; +} + +bool IndiAstrolink4::ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n) +{ + if (dev && !strcmp(dev, getDeviceName())) + { + char cmd[ASTROLINK4_LEN] = {0}; + char res[ASTROLINK4_LEN] = {0}; + + // handle PWM + if (!strcmp(name, PWMNP.name)) + { + bool allOk = true; + if(PWMN[0].value != values[0]) + { + if(AutoPWMS[0].s == ISS_OFF) + { + sprintf(cmd, "B:0:%d", static_cast(values[0])); + allOk = allOk && sendCommand(cmd, res); + } + else + { + LOG_WARN("Cannot set PWM output, it is in AUTO mode."); + } + } + if(PWMN[1].value != values[1]) + { + if(AutoPWMS[1].s == ISS_OFF) + { + sprintf(cmd, "B:1:%d", static_cast(values[1])); + allOk = allOk && sendCommand(cmd, res); + } + else + { + LOG_WARN("Cannot set PWM output, it is in AUTO mode."); + } + } + PWMNP.s = (allOk) ? IPS_BUSY : IPS_ALERT; + if(allOk) + IUUpdateNumber(&PWMNP, values, names, n); + IDSetNumber(&PWMNP, nullptr); + IDSetSwitch(&AutoPWMSP, nullptr); + return true; + } + + // Focuser settings + if(!strcmp(name, FocuserSettingsNP .name)) + { + bool allOk = true; + std::map updates; + updates[U_SPEED] = doubleToStr(values[FS_SPEED]); + updates[U_ACC] = doubleToStr(values[FS_SPEED] * 2.0); + updates[U_STEPSIZE] = doubleToStr(values[FS_STEP_SIZE] * 100.0); + allOk = allOk && updateSettings("u", "U", updates); + updates.clear(); + updates[E_COMP_CYCLE] = "30"; // cycle [s] + updates[E_COMP_STEPS] = doubleToStr(values[FS_COMPENSATION] * 100.0); + updates[E_COMP_SENSR] = "0"; // sensor + updates[E_COMP_TRGR] = doubleToStr(values[FS_COMP_THRESHOLD]); + allOk = allOk && updateSettings("e", "E", updates); + if(allOk) + { + FocuserSettingsNP.s = IPS_BUSY; + IUUpdateNumber(&FocuserSettingsNP, values, names, n); + IDSetNumber(&FocuserSettingsNP, nullptr); + LOG_INFO(values[FS_COMPENSATION] > 0 ? "Temperature compensation is enabled." : "Temperature compensation is disabled."); + return true; + } + FocuserSettingsNP.s = IPS_ALERT; + return true; + } + + // Other settings + if(!strcmp(name, OtherSettingsNP .name)) + { + std::map updates; + updates[N_AREF_COEFF] = doubleToStr(values[SET_AREF_COEFF] * 1000.0); + updates[N_OVER_VOLT] = doubleToStr(values[SET_OVER_VOLT] * 10.0); + updates[N_OVER_AMP] = doubleToStr(values[SET_OVER_AMP] * 10.0); + updates[N_OVER_TIME] = doubleToStr(values[SET_OVER_TIME]); + if(updateSettings("n", "N", updates)) + { + OtherSettingsNP.s = IPS_BUSY; + IUUpdateNumber(&OtherSettingsNP, values, names, n); + IDSetNumber(&OtherSettingsNP, nullptr); + return true; + } + OtherSettingsNP.s = IPS_ALERT; + return true; + } + + // DC Focuser + if(!strcmp(name, DCFocTimeNP.name)) + { + IUUpdateNumber(&DCFocTimeNP, values, names, n); + IDSetNumber(&DCFocTimeNP, nullptr); + saveConfig(true); + sprintf(cmd, "G:%d:%.0f:%.0f", (DCFocDirS[0].s == ISS_ON) ? 1 : 0, DCFocTimeN[DC_PWM].value, DCFocTimeN[DC_PERIOD].value); + if(sendCommand(cmd, res)) + { + DCFocAbortS[0].s = ISS_OFF; + DCFocAbortSP.s = IPS_OK; + IDSetSwitch(&DCFocAbortSP, nullptr); + + DCFocTimeNP.s = IPS_BUSY; + return true; + } + DCFocTimeNP.s = IPS_ALERT; + return true; + } + + if (strstr(name, "FOCUS_")) + return FI::processNumber(dev, name, values, names, n); + if (strstr(name, "WEATHER_")) + return WI::processNumber(dev, name, values, names, n); + } + + return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n); +} + + +bool IndiAstrolink4::ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n) +{ + if (dev && !strcmp(dev, getDeviceName())) + { + char cmd[ASTROLINK4_LEN] = {0}; + char res[ASTROLINK4_LEN] = {0}; + + // handle power line 1 + if (!strcmp(name, Power1SP.name)) + { + sprintf(cmd, "C:0:%s", (strcmp(Power1S[0].name, names[0])) ? "0" : "1"); + bool allOk = sendCommand(cmd, res); + Power1SP.s = allOk ? IPS_BUSY : IPS_ALERT; + if(allOk) + IUUpdateSwitch(&Power1SP, states, names, n); + + IDSetSwitch(&Power1SP, nullptr); + return true; + } + + // handle power line 2 + if (!strcmp(name, Power2SP.name)) + { + sprintf(cmd, "C:1:%s", (strcmp(Power2S[0].name, names[0])) ? "0" : "1"); + bool allOk = sendCommand(cmd, res); + Power2SP.s = allOk ? IPS_BUSY : IPS_ALERT; + if(allOk) + IUUpdateSwitch(&Power2SP, states, names, n); + + IDSetSwitch(&Power2SP, nullptr); + return true; + } + + // handle power line 3 + if (!strcmp(name, Power3SP.name)) + { + sprintf(cmd, "C:2:%s", (strcmp(Power3S[0].name, names[0])) ? "0" : "1"); + bool allOk = sendCommand(cmd, res); + Power3SP.s = allOk ? IPS_BUSY : IPS_ALERT; + if(allOk) + IUUpdateSwitch(&Power3SP, states, names, n); + + IDSetSwitch(&Power3SP, nullptr); + return true; + } + + // compensate now + if(!strcmp(name, CompensateNowSP.name)) + { + sprintf(cmd, "S:%d", static_cast(CompensationValueN[0].value)); + bool allOk = sendCommand(cmd, res); + CompensateNowSP.s = allOk ? IPS_BUSY : IPS_ALERT; + if(allOk) + IUUpdateSwitch(&CompensateNowSP, states, names, n); + + IDSetSwitch(&CompensateNowSP, nullptr); + return true; + } + + // Auto PWM + if (!strcmp(name, AutoPWMSP.name)) + { + IUUpdateSwitch(&AutoPWMSP, states, names, n); + AutoPWMSP.s = (setAutoPWM()) ? IPS_OK : IPS_ALERT; + IDSetSwitch(&AutoPWMSP, nullptr); + return true; + } + + // DC Focuser + if (!strcmp(name, DCFocDirSP.name)) + { + DCFocDirSP.s = IPS_OK; + IUUpdateSwitch(&DCFocDirSP, states, names, n); + IDSetSwitch(&DCFocDirSP, nullptr); + return true; + } + + if (!strcmp(name, DCFocAbortSP.name)) + { + sprintf(cmd, "%s", "K"); + if(sendCommand(cmd, res)) + { + DCFocAbortSP.s = IPS_BUSY; + IUUpdateSwitch(&DCFocAbortSP, states, names, n); + IDSetSwitch(&DCFocAbortSP, nullptr); + } + DCFocAbortSP.s = IPS_ALERT; + return true; + } + + // Power default on + if(!strcmp(name, PowerDefaultOnSP.name)) + { + std::map updates; + updates[U_OUT1_DEF] = (states[0] == ISS_ON) ? "1" : "0"; + updates[U_OUT2_DEF] = (states[1] == ISS_ON) ? "1" : "0"; + updates[U_OUT3_DEF] = (states[2] == ISS_ON) ? "1" : "0"; + if(updateSettings("u", "U", updates)) + { + PowerDefaultOnSP.s = IPS_BUSY; + IUUpdateSwitch(&PowerDefaultOnSP, states, names, n); + IDSetSwitch(&PowerDefaultOnSP, nullptr); + return true; + } + PowerDefaultOnSP.s = IPS_ALERT; + return true; + } + + // Auto PWM default on + if (!strcmp(name, AutoPWMDefaultOnSP.name)) + { + IUUpdateSwitch(&AutoPWMDefaultOnSP, states, names, n); + AutoPWMDefaultOnSP.s = IPS_OK; + saveConfig(); + IDSetSwitch(&AutoPWMDefaultOnSP, nullptr); + return true; + } + + // Buzzer + if(!strcmp(name, BuzzerSP.name)) + { + if(updateSettings("j", "J", 1, (states[0] == ISS_ON) ? "1" : "0")) + { + BuzzerSP.s = IPS_BUSY; + IUUpdateSwitch(&BuzzerSP, states, names, n); + IDSetSwitch(&BuzzerSP, nullptr); + return true; + } + BuzzerSP.s = IPS_ALERT; + return true; + } + + // Manual mode + if(!strcmp(name, FocuserManualSP.name)) + { + sprintf(cmd, "F:%s", (strcmp(FocuserManualS[0].name, names[0])) ? "0" : "1"); + if(sendCommand(cmd, res)) + { + FocuserManualSP.s = IPS_BUSY; + IUUpdateSwitch(&FocuserManualSP, states, names, n); + IDSetSwitch(&FocuserManualSP, nullptr); + return true; + } + FocuserManualSP.s = IPS_ALERT; + return true; + } + + // Focuser Mode + if(!strcmp(name, FocuserModeSP.name)) + { + std::string value = "0"; + if(!strcmp(FocuserModeS[FS_MODE_UNI].name, names[0])) value = "0"; + if(!strcmp(FocuserModeS[FS_MODE_BI].name, names[0])) value = "1"; + if(!strcmp(FocuserModeS[FS_MODE_MICRO].name, names[0])) value = "2"; + if(updateSettings("u", "U", U_STEPPER_MODE, value.c_str())) + { + FocuserModeSP.s = IPS_BUSY; + IUUpdateSwitch(&FocuserModeSP, states, names, n); + IDSetSwitch(&FocuserModeSP, nullptr); + return true; + } + FocuserModeSP.s = IPS_ALERT; + return true; + } + + // Focuser compensation mode + if(!strcmp(name, FocuserCompModeSP.name)) + { + std::string value = "0"; + if(!strcmp(FocuserCompModeS[FS_COMP_AUTO].name, names[0])) value = "1"; + if(updateSettings("e", "E", E_COMP_AUTO, value.c_str())) + { + FocuserCompModeSP.s = IPS_BUSY; + IUUpdateSwitch(&FocuserCompModeSP, states, names, n); + IDSetSwitch(&FocuserCompModeSP, nullptr); + return true; + } + FocuserCompModeSP.s = IPS_ALERT; + return true; + } + + if (strstr(name, "FOCUS")) + return FI::processSwitch(dev, name, states, names, n); + + } + + return INDI::DefaultDevice::ISNewSwitch (dev, name, states, names, n); +} + +bool IndiAstrolink4::ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n) +{ + if (dev && !strcmp(dev, getDeviceName())) + { + // Power Labels + if (!strcmp(name, PowerControlsLabelsTP.name)) + { + IUUpdateText(&PowerControlsLabelsTP, texts, names, n); + PowerControlsLabelsTP.s = IPS_OK; + LOG_INFO("Power port labels saved. Driver must be restarted for the labels to take effect."); + saveConfig(); + IDSetText(&PowerControlsLabelsTP, nullptr); + return true; + } + } + return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n); +} + + +bool IndiAstrolink4::saveConfigItems(FILE *fp) +{ + INDI::DefaultDevice::saveConfigItems(fp); + FI::saveConfigItems(fp); + + IUSaveConfigNumber(fp, &DCFocTimeNP); + IUSaveConfigSwitch(fp, &DCFocDirSP); + IUSaveConfigText(fp, &PowerControlsLabelsTP); + IUSaveConfigSwitch(fp, &AutoPWMDefaultOnSP); + return true; +} + +////////////////////////////////////////////////////////////////////// +/// PWM outputs +////////////////////////////////////////////////////////////////////// +bool IndiAstrolink4::setAutoPWM() +{ + char cmd[ASTROLINK4_LEN] = {0}, res[ASTROLINK4_LEN] = {0}; + bool allOk = true; + + uint8_t valA = (AutoPWMS[0].s == ISS_ON) ? 255 : static_cast(PWMN[0].value); + uint8_t valB = (AutoPWMS[1].s == ISS_ON) ? 255 : static_cast(PWMN[1].value); + + snprintf(cmd, ASTROLINK4_LEN, "B:0:%d", valA); + allOk = allOk && sendCommand(cmd, res); + snprintf(cmd, ASTROLINK4_LEN, "B:1:%d", valB); + allOk = allOk && sendCommand(cmd, res); + + return allOk; +} + +////////////////////////////////////////////////////////////////////// +/// Focuser interface +////////////////////////////////////////////////////////////////////// +IPState IndiAstrolink4::MoveAbsFocuser(uint32_t targetTicks) +{ + int32_t backlash = 0; + if(backlashEnabled) + { + if((targetTicks > FocusAbsPosN[0].value) == (backlashSteps > 0)) + { + if(((int32_t)targetTicks + backlash) < 0 || (targetTicks + backlash) > FocusMaxPosN[0].value) + { + backlash = 0; + } + else + { + backlash = backlashSteps; + requireBacklashReturn = true; + } + } + } + char cmd[ASTROLINK4_LEN] = {0}, res[ASTROLINK4_LEN] = {0}; + snprintf(cmd, ASTROLINK4_LEN, "R:0:%u", targetTicks + backlash); + return (sendCommand(cmd, res)) ? IPS_BUSY : IPS_ALERT; +} + +IPState IndiAstrolink4::MoveRelFocuser(FocusDirection dir, uint32_t ticks) +{ + return MoveAbsFocuser(dir == FOCUS_INWARD ? FocusAbsPosN[0].value - ticks : FocusAbsPosN[0].value + ticks); +} + +bool IndiAstrolink4::AbortFocuser() +{ + char res[ASTROLINK4_LEN] = {0}; + return (sendCommand("H", res)); +} + +bool IndiAstrolink4::ReverseFocuser(bool enabled) +{ + return updateSettings("u", "U", U_REVERSED, (enabled) ? "1" : "0"); +} + +bool IndiAstrolink4::SyncFocuser(uint32_t ticks) +{ + char cmd[ASTROLINK4_LEN] = {0}, res[ASTROLINK4_LEN] = {0}; + snprintf(cmd, ASTROLINK4_LEN, "P:0:%u", ticks); + return sendCommand(cmd, res); +} + +bool IndiAstrolink4::SetFocuserMaxPosition(uint32_t ticks) +{ + if(updateSettings("u", "U", U_MAX_POS, std::to_string(ticks).c_str())) + { + FocuserSettingsNP.s = IPS_BUSY; + return true; + } + else + { + return false; + } +} + +bool IndiAstrolink4::SetFocuserBacklash(int32_t steps) +{ + backlashSteps = steps; + return true; +} + +bool IndiAstrolink4::SetFocuserBacklashEnabled(bool enabled) +{ + backlashEnabled = enabled; + return true; +} + +////////////////////////////////////////////////////////////////////// +/// Serial commands +////////////////////////////////////////////////////////////////////// +bool IndiAstrolink4::sendCommand(const char * cmd, char * res) +{ + int nbytes_read = 0, nbytes_written = 0, tty_rc = 0; + char command[ASTROLINK4_LEN]; + + if(isSimulation()) + { + if(strcmp(cmd, "#") == 0) sprintf(res, "%s\n", "#:AstroLink4mini"); + if(strcmp(cmd, "q") == 0) sprintf(res, "%s\n", + "q:1234:0:1.47:1:2.12:45.1:-12.81:1:-25.22:45:0:0:0:1:12.1:5.0:1.12:13.41:0:34:0:0"); + if(strcmp(cmd, "p") == 0) sprintf(res, "%s\n", "p:1234"); + if(strcmp(cmd, "i") == 0) sprintf(res, "%s\n", "i:0"); + if(strcmp(cmd, "n") == 0) sprintf(res, "%s\n", "n:1077:14.0:10.0:100"); + if(strcmp(cmd, "e") == 0) sprintf(res, "%s\n", "e:30:1200:1:0:20"); + if(strcmp(cmd, "u") == 0) sprintf(res, "%s\n", "u:25000:220:0:100:440:0:0:1:257:0:0:0:0:0:1:0:0"); + if(strncmp(cmd, "R", 1) == 0) sprintf(res, "%s\n", "R:"); + if(strncmp(cmd, "C", 1) == 0) sprintf(res, "%s\n", "C:"); + if(strncmp(cmd, "B", 1) == 0) sprintf(res, "%s\n", "B:"); + if(strncmp(cmd, "H", 1) == 0) sprintf(res, "%s\n", "H:"); + if(strncmp(cmd, "P", 1) == 0) sprintf(res, "%s\n", "P:"); + if(strncmp(cmd, "U", 1) == 0) sprintf(res, "%s\n", "U:"); + if(strncmp(cmd, "S", 1) == 0) sprintf(res, "%s\n", "S:"); + if(strncmp(cmd, "G", 1) == 0) sprintf(res, "%s\n", "G:"); + if(strncmp(cmd, "K", 1) == 0) sprintf(res, "%s\n", "K:"); + if(strncmp(cmd, "N", 1) == 0) sprintf(res, "%s\n", "N:"); + if(strncmp(cmd, "E", 1) == 0) sprintf(res, "%s\n", "E:"); + } + else + { + tcflush(PortFD, TCIOFLUSH); + sprintf(command, "%s\n", cmd); + LOGF_DEBUG("CMD %s", command); + if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK) + return false; + + if (!res) + { + tcflush(PortFD, TCIOFLUSH); + return true; + } + + if ( (tty_rc = tty_nread_section(PortFD, res, ASTROLINK4_LEN, stopChar, ASTROLINK4_TIMEOUT, &nbytes_read)) != TTY_OK + || nbytes_read == 1) + return false; + + tcflush(PortFD, TCIOFLUSH); + res[nbytes_read - 1] = '\0'; + LOGF_DEBUG("RES %s", res); + + if (tty_rc != TTY_OK) + { + char errorMessage[MAXRBUF]; + tty_error_msg(tty_rc, errorMessage, MAXRBUF); + LOGF_ERROR("Serial error: %s", errorMessage); + return false; + } + } + return (cmd[0] == res[0]); +} + +////////////////////////////////////////////////////////////////////// +/// Sensors +////////////////////////////////////////////////////////////////////// +bool IndiAstrolink4::sensorRead() +{ + char res[ASTROLINK4_LEN] = {0}; + if (sendCommand("q", res)) + { + std::vector result = split(res, ":"); + + float focuserPosition = std::stod(result[Q_STEPPER_POS]); + FocusAbsPosN[0].value = focuserPosition; + FocusPosMMN[0].value = focuserPosition * FocuserSettingsN[FS_STEP_SIZE].value / 1000.0; + float stepsToGo = std::stod(result[Q_STEPS_TO_GO]); + if(stepsToGo == 0) + { + if(requireBacklashReturn) + { + requireBacklashReturn = false; + MoveAbsFocuser(focuserPosition - backlashSteps); + } + FocusAbsPosNP.s = FocusRelPosNP.s = FocusPosMMNP.s = IPS_OK; + IDSetNumber(&FocusRelPosNP, nullptr); + } + else + { + FocusAbsPosNP.s = FocusRelPosNP.s = FocusPosMMNP.s = IPS_BUSY; + } + IDSetNumber(&FocusPosMMNP, nullptr); + IDSetNumber(&FocusAbsPosNP, nullptr); + PowerDataN[POW_ITOT].value = std::stod(result[Q_CURRENT]); + + if(result.size() > 5) + { + if(std::stod(result[Q_SENS1_TYPE]) > 0) + { + setParameterValue("WEATHER_TEMPERATURE", std::stod(result[Q_SENS1_TEMP])); + setParameterValue("WEATHER_HUMIDITY", std::stod(result[Q_SENS1_HUM])); + setParameterValue("WEATHER_DEWPOINT", std::stod(result[Q_SENS1_DEW])); + ParametersNP.setState(IPS_OK); + ParametersNP.apply(); + } + else + { + ParametersNP.setState(IPS_IDLE); + } + + if(std::stod(result[Q_SENS2_TYPE]) > 0) + { + Sensor2N[0].value = std::stod(result[Q_SENS2_TEMP]); + Sensor2NP.s = IPS_OK; + IDSetNumber(&Sensor2NP, nullptr); + } + else + { + Sensor2NP.s = IPS_IDLE; + } + + PWMN[0].value = std::stod(result[Q_PWM1]); + PWMN[1].value = std::stod(result[Q_PWM2]); + PWMNP.s = IPS_OK; + IDSetNumber(&PWMNP, nullptr); + + bool dcMotorMoving = (std::stod(result[Q_DC_MOVE]) > 0); + if(dcMotorMoving) + { + DCFocTimeNP.s = IPS_BUSY; + IDSetNumber(&DCFocTimeNP, nullptr); + } + else if (DCFocTimeNP.s == IPS_BUSY) + { + DCFocTimeNP.s = IPS_OK; + DCFocAbortSP.s = IPS_IDLE; + IDSetNumber(&DCFocTimeNP, nullptr); + IDSetSwitch(&DCFocAbortSP, nullptr); + } + + if(Power1SP.s != IPS_OK || Power2SP.s != IPS_OK || Power3SP.s != IPS_OK) + { + Power1S[0].s = (std::stod(result[Q_OUT1]) > 0) ? ISS_ON : ISS_OFF; + Power1S[1].s = (std::stod(result[Q_OUT1]) == 0) ? ISS_ON : ISS_OFF; + Power1SP.s = IPS_OK; + IDSetSwitch(&Power1SP, nullptr); + Power2S[0].s = (std::stod(result[Q_OUT2]) > 0) ? ISS_ON : ISS_OFF; + Power2S[1].s = (std::stod(result[Q_OUT2]) == 0) ? ISS_ON : ISS_OFF; + Power2SP.s = IPS_OK; + IDSetSwitch(&Power2SP, nullptr); + Power3S[0].s = (std::stod(result[Q_OUT3]) > 0) ? ISS_ON : ISS_OFF; + Power3S[1].s = (std::stod(result[Q_OUT3]) == 0) ? ISS_ON : ISS_OFF; + Power3SP.s = IPS_OK; + IDSetSwitch(&Power3SP, nullptr); + } + + CompensationValueN[0].value = std::stod(result[Q_COMP_DIFF]); + CompensateNowSP.s = CompensationValueNP.s = (CompensationValueN[0].value > 0) ? IPS_OK : IPS_IDLE; + CompensateNowS[0].s = (CompensationValueN[0].value > 0) ? ISS_OFF : ISS_ON; + IDSetNumber(&CompensationValueNP, nullptr); + IDSetSwitch(&CompensateNowSP, nullptr); + + PowerDataN[POW_VIN].value = std::stod(result[Q_VIN]); + PowerDataN[POW_VREG].value = std::stod(result[Q_VREG]); + PowerDataN[POW_AH].value = std::stod(result[Q_AH]); + PowerDataN[POW_WH].value = std::stod(result[Q_WH]); + + if(strcmp(result[Q_OP_FLAG].c_str(), "0")) + { + int opFlag = std::stoi(result[Q_OP_FLAG]); + LOGF_WARN("Protection triggered, outputs were disabled. Reason: %s was too high, value: %.1f", + (opFlag == 1) ? "voltage" : "current", std::stod(result[Q_OP_VALUE])); + } + } + + PowerDataNP.s = IPS_OK; + IDSetNumber(&PowerDataNP, nullptr); + + } + + // update settings data if was changed + if(FocuserSettingsNP.s != IPS_OK || FocuserModeSP.s != IPS_OK || PowerDefaultOnSP.s != IPS_OK || BuzzerSP.s != IPS_OK + || FocuserCompModeSP.s != IPS_OK) + { + if (sendCommand("u", res)) + { + std::vector result = split(res, ":"); + + FocuserModeS[FS_MODE_UNI].s = FocuserModeS[FS_MODE_BI].s = FocuserModeS[FS_MODE_MICRO].s = ISS_OFF; + if(!strcmp("0", result[U_STEPPER_MODE].c_str())) FocuserModeS[FS_MODE_UNI].s = ISS_ON; + if(!strcmp("1", result[U_STEPPER_MODE].c_str())) FocuserModeS[FS_MODE_BI].s = ISS_ON; + if(!strcmp("2", result[U_STEPPER_MODE].c_str())) FocuserModeS[FS_MODE_MICRO].s = ISS_ON; + FocuserModeSP.s = IPS_OK; + IDSetSwitch(&FocuserModeSP, nullptr); + + PowerDefaultOnS[0].s = (std::stod(result[U_OUT1_DEF]) > 0) ? ISS_ON : ISS_OFF; + PowerDefaultOnS[1].s = (std::stod(result[U_OUT2_DEF]) > 0) ? ISS_ON : ISS_OFF; + PowerDefaultOnS[2].s = (std::stod(result[U_OUT3_DEF]) > 0) ? ISS_ON : ISS_OFF; + PowerDefaultOnSP.s = IPS_OK; + IDSetSwitch(&PowerDefaultOnSP, nullptr); + + FocuserSettingsN[FS_SPEED].value = std::stod(result[U_SPEED]); + FocuserSettingsN[FS_STEP_SIZE].value = std::stod(result[U_STEPSIZE]) / 100.0; + FocusMaxPosN[0].value = std::stod(result[U_MAX_POS]); + FocuserSettingsNP.s = IPS_OK; + IDSetNumber(&FocuserSettingsNP, nullptr); + IDSetNumber(&FocusMaxPosNP, nullptr); + } + + if(sendCommand("j", res)) + { + std::vector result = split(res, ":"); + BuzzerS[0].s = (std::stod(result[1]) > 0) ? ISS_ON : ISS_OFF; + BuzzerSP.s = IPS_OK; + IDSetSwitch(&BuzzerSP, nullptr); + } + + if (sendCommand("e", res)) + { + std::vector result = split(res, ":"); + FocuserSettingsN[FS_COMPENSATION].value = std::stod(result[E_COMP_STEPS]) / 100.0; + FocuserSettingsN[FS_COMP_THRESHOLD].value = std::stod(result[E_COMP_TRGR]); + FocuserSettingsNP.s = IPS_OK; + IDSetNumber(&FocuserSettingsNP, nullptr); + + FocuserCompModeS[FS_COMP_MANUAL].s = (std::stod(result[E_COMP_AUTO]) == 0) ? ISS_ON : ISS_OFF; + FocuserCompModeS[FS_COMP_AUTO].s = (std::stod(result[E_COMP_AUTO]) > 0) ? ISS_ON : ISS_OFF; + FocuserCompModeSP.s = IPS_OK; + IDSetSwitch(&FocuserCompModeSP, nullptr); + } + } + + if(FocuserManualSP.s != IPS_OK) + { + if (sendCommand("f", res)) + { + std::vector result = split(res, ":"); + FocuserManualS[FS_MANUAL_OFF].s = (std::stod(result[1]) == 0) ? ISS_ON : ISS_OFF; + FocuserManualS[FS_MANUAL_ON].s = (std::stod(result[1]) > 0) ? ISS_ON : ISS_OFF; + FocuserManualSP.s = IPS_OK; + IDSetSwitch(&FocuserManualSP, nullptr); + } + } + + if(OtherSettingsNP.s != IPS_OK) + { + if (sendCommand("n", res)) + { + std::vector result = split(res, ":"); + OtherSettingsN[SET_AREF_COEFF].value = std::stod(result[N_AREF_COEFF]) / 1000.0; + OtherSettingsN[SET_OVER_TIME].value = std::stod(result[N_OVER_TIME]); + OtherSettingsN[SET_OVER_VOLT].value = std::stod(result[N_OVER_VOLT]) / 10.0; + OtherSettingsN[SET_OVER_AMP].value = std::stod(result[N_OVER_AMP]) / 10.0; + OtherSettingsNP.s = IPS_OK; + IDSetNumber(&OtherSettingsNP, nullptr); + } + } + + return true; +} + +////////////////////////////////////////////////////////////////////// +/// Helper functions +////////////////////////////////////////////////////////////////////// +std::vector IndiAstrolink4::split(const std::string &input, const std::string ®ex) +{ + // passing -1 as the submatch index parameter performs splitting + std::regex re(regex); + std::sregex_token_iterator + first{input.begin(), input.end(), re, -1}, + last; + return {first, last}; +} + +std::string IndiAstrolink4::doubleToStr(double val) +{ + char buf[10]; + sprintf(buf, "%.0f", val); + return std::string(buf); +} + +bool IndiAstrolink4::updateSettings(const char * getCom, const char * setCom, int index, const char * value) +{ + std::map values; + values[index] = value; + return updateSettings(getCom, setCom, values); +} + +bool IndiAstrolink4::updateSettings(const char * getCom, const char * setCom, std::map values) +{ + char cmd[ASTROLINK4_LEN] = {0}, res[ASTROLINK4_LEN] = {0}; + snprintf(cmd, ASTROLINK4_LEN, "%s", getCom); + if(sendCommand(cmd, res)) + { + std::string concatSettings = ""; + std::vector result = split(res, ":"); + if(result.size() >= values.size()) + { + result[0] = setCom; + for(std::map::iterator it = values.begin(); it != values.end(); ++it) + result[it->first] = it->second; + + for (const auto &piece : result) concatSettings += piece + ":"; + snprintf(cmd, ASTROLINK4_LEN, "%s", concatSettings.c_str()); + if(sendCommand(cmd, res)) return true; + } + } + return false; +} diff --git a/drivers/auxiliary/indi_astrolink4.h b/drivers/auxiliary/indi_astrolink4.h new file mode 100644 index 0000000000..7d5f862df5 --- /dev/null +++ b/drivers/auxiliary/indi_astrolink4.h @@ -0,0 +1,244 @@ +/******************************************************************************* + Copyright(c) 2019 astrojolo.com + . + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + . + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + . + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*******************************************************************************/ + +#ifndef ASTROLINK4_H +#define ASTROLINK4_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define Q_STEPPER_POS 1 +#define Q_STEPS_TO_GO 2 +#define Q_CURRENT 3 +#define Q_SENS1_TYPE 4 +#define Q_SENS1_TEMP 5 +#define Q_SENS1_HUM 6 +#define Q_SENS1_DEW 7 +#define Q_SENS2_TYPE 8 +#define Q_SENS2_TEMP 9 +#define Q_PWM1 10 +#define Q_PWM2 11 +#define Q_OUT1 12 +#define Q_OUT2 13 +#define Q_OUT3 14 +#define Q_VIN 15 +#define Q_VREG 16 +#define Q_AH 17 +#define Q_WH 18 +#define Q_DC_MOVE 19 +#define Q_COMP_DIFF 20 +#define Q_OP_FLAG 21 +#define Q_OP_VALUE 22 + +#define U_MAX_POS 1 +#define U_SPEED 2 +#define U_PWMSTOP 3 +#define U_PWMRUN 4 +#define U_ACC 5 +#define U_REVERSED 6 +#define U_STEPPER_MODE 7 +#define U_COMPSENS 8 +#define U_STEPSIZE 9 +#define U_PWMPRESC 10 +#define U_STEPPRESC 11 +#define U_BUZ_ENABLED 12 +#define U_HUM_SENS 13 +#define U_DC_REVERSED 14 +#define U_OUT1_DEF 15 +#define U_OUT2_DEF 16 +#define U_OUT3_DEF 17 + +#define E_COMP_CYCLE 1 +#define E_COMP_STEPS 2 +#define E_COMP_SENSR 3 +#define E_COMP_AUTO 4 +#define E_COMP_TRGR 5 + +#define N_AREF_COEFF 1 +#define N_OVER_VOLT 2 +#define N_OVER_AMP 3 +#define N_OVER_TIME 4 + +namespace Connection +{ +class Serial; +} + +class IndiAstrolink4 : public INDI::DefaultDevice, public INDI::FocuserInterface, public INDI::WeatherInterface +{ + +public: + IndiAstrolink4(); + virtual bool initProperties() override; + virtual bool updateProperties() override; + + virtual bool ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n) override; + virtual bool ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n) override; + virtual bool ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n) override; + +protected: + virtual const char *getDefaultName() override; + virtual void TimerHit() override; + virtual bool saveConfigItems(FILE *fp) override; + virtual bool sendCommand(const char * cmd, char * res); + + // Focuser Overrides + virtual IPState MoveAbsFocuser(uint32_t targetTicks) override; + virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override; + virtual bool AbortFocuser() override; + virtual bool ReverseFocuser(bool enabled) override; + virtual bool SyncFocuser(uint32_t ticks) override; + + virtual bool SetFocuserBacklash(int32_t steps) override; + virtual bool SetFocuserBacklashEnabled(bool enabled) override; + virtual bool SetFocuserMaxPosition(uint32_t ticks) override; + + // Weather Overrides + virtual IPState updateWeather() override + { + return IPS_OK; + } + + +private: + virtual bool Handshake(); + int PortFD = -1; + Connection::Serial *serialConnection { nullptr }; + bool updateSettings(const char * getCom, const char * setCom, int index, const char * value); + bool updateSettings(const char * getCom, const char * setCom, std::map values); + std::vector split(const std::string &input, const std::string ®ex); + std::string doubleToStr(double val); + bool sensorRead(); + bool setAutoPWM(); + int32_t calculateBacklash(uint32_t targetTicks); + char stopChar { 0xA }; // new line + bool backlashEnabled = false; + int32_t backlashSteps = 0; + bool requireBacklashReturn = false; + + IText PowerControlsLabelsT[3]; + ITextVectorProperty PowerControlsLabelsTP; + + ISwitch Power1S[2]; + ISwitchVectorProperty Power1SP; + ISwitch Power2S[2]; + ISwitchVectorProperty Power2SP; + ISwitch Power3S[2]; + ISwitchVectorProperty Power3SP; + + INumber Sensor2N[1]; + INumberVectorProperty Sensor2NP; + + INumber PWMN[2]; + INumberVectorProperty PWMNP; + + ISwitch AutoPWMS[2]; + ISwitchVectorProperty AutoPWMSP; + + INumber PowerDataN[5]; + INumberVectorProperty PowerDataNP; + enum + { + POW_VIN, POW_VREG, POW_ITOT, POW_AH, POW_WH + }; + + INumber FocusPosMMN[1]; + INumberVectorProperty FocusPosMMNP; + + INumber CompensationValueN[1]; + INumberVectorProperty CompensationValueNP; + ISwitch CompensateNowS[1]; + ISwitchVectorProperty CompensateNowSP; + + INumber FocuserSettingsN[4]; + INumberVectorProperty FocuserSettingsNP; + enum + { + FS_SPEED, FS_STEP_SIZE, FS_COMPENSATION, FS_COMP_THRESHOLD + }; + ISwitch FocuserModeS[3]; + ISwitchVectorProperty FocuserModeSP; + enum + { + FS_MODE_UNI, FS_MODE_BI, FS_MODE_MICRO + }; + + ISwitch FocuserCompModeS[2]; + ISwitchVectorProperty FocuserCompModeSP; + enum + { + FS_COMP_AUTO, FS_COMP_MANUAL + }; + + ISwitch FocuserManualS[2]; + ISwitchVectorProperty FocuserManualSP; + enum + { + FS_MANUAL_ON, FS_MANUAL_OFF + }; + + ISwitch PowerDefaultOnS[3]; + ISwitchVectorProperty PowerDefaultOnSP; + + ISwitch AutoPWMDefaultOnS[2]; + ISwitchVectorProperty AutoPWMDefaultOnSP; + + INumber OtherSettingsN[4]; + INumberVectorProperty OtherSettingsNP; + enum + { + SET_AREF_COEFF, SET_OVER_TIME, SET_OVER_VOLT, SET_OVER_AMP + }; + + INumber DCFocTimeN[2]; + INumberVectorProperty DCFocTimeNP; + enum + { + DC_PERIOD, DC_PWM + }; + + ISwitch DCFocDirS[2]; + ISwitchVectorProperty DCFocDirSP; + + ISwitch DCFocAbortS[1]; + ISwitchVectorProperty DCFocAbortSP; + + ISwitch BuzzerS[1]; + ISwitchVectorProperty BuzzerSP; + + static constexpr const char *POWER_TAB {"Power"}; + static constexpr const char *ENVIRONMENT_TAB {"Environment"}; + static constexpr const char *SETTINGS_TAB {"Settings"}; + static constexpr const char *DCFOCUSER_TAB {"DC Focuser"}; +}; + +#endif diff --git a/drivers/filter_wheel/CMakeLists.txt b/drivers/filter_wheel/CMakeLists.txt index 8975ff93cd..ed6069c164 100644 --- a/drivers/filter_wheel/CMakeLists.txt +++ b/drivers/filter_wheel/CMakeLists.txt @@ -78,3 +78,11 @@ add_executable(indi_pegasusindigo_wheel ${pegasus_indigo_SRC}) target_link_libraries(indi_pegasusindigo_wheel indidriver) install(TARGETS indi_pegasusindigo_wheel RUNTIME DESTINATION bin) + +# ############### iOptron Filter Wheel ################ +SET(ioptron_wheel_SRC + ioptron_wheel.cpp) + +add_executable(indi_ioptron_wheel ${ioptron_wheel_SRC}) +target_link_libraries(indi_ioptron_wheel indidriver) +install(TARGETS indi_ioptron_wheel RUNTIME DESTINATION bin) diff --git a/drivers/filter_wheel/ioptron_wheel.cpp b/drivers/filter_wheel/ioptron_wheel.cpp new file mode 100644 index 0000000000..0201e1f041 --- /dev/null +++ b/drivers/filter_wheel/ioptron_wheel.cpp @@ -0,0 +1,310 @@ +/******************************************************************************* + Copyright(c) 2024 Joe Zhou. All rights reserved. + + iOptron Filter Wheel + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*******************************************************************************/ + +#include "ioptron_wheel.h" + +#include "indicom.h" +#include +#include +#include +#include + +#define iEFW_TIMEOUT 4 + +/* +command : +:WMnn# Response: 1 +get position ,nn filter num。 + +:WP# Response: nn# or -1# +get curr position,nn =curr position,if is moving,return -1。 + +:WOnnsnnnnn# Response: 1 +set offset。nn =filter num , snnnnn=offset + +:WFnn# Response: snnnnn# +get offset 。nn = filter num , snnnnn=offset + +“:DeviceInfo#” Response: “nnnnnnnnnnnn#” +This command includes 12 digits. +The 7th and 8th digit means model of filter wheel. +Model number 99 is iEFW-15, 98 is iEFW18 + +:FW1# Response: nnnnnnnnnnnn# +get firmware version + + +*/ + + +static std::unique_ptr iefw_wheel(new iEFW()); + +iEFW::iEFW() +{ + setVersion(1, 0); + setFilterConnection(CONNECTION_SERIAL); +} + +const char *iEFW::getDefaultName() +{ + return "iOptron Wheel"; +} + +bool iEFW::initProperties() +{ + INDI::FilterWheel::initProperties(); +// serialConnection->setDefaultPort("/dev/ttyUSB3"); + serialConnection->setDefaultBaudRate(Connection::Serial::B_115200); + FilterSlotN[0].min = 1; + FilterSlotN[0].max = 8; + FirmwareTP[0].fill("FIRMWARE", "Firmware", "240101240101"); + FirmwareTP.fill(getDeviceName(), "FIRMWARE_ID", "iEFW Firmware", FILTER_TAB, IP_RO, 60, IPS_IDLE); + WheelIDTP[0].fill("MODEL", "Model", "iEFW"); + WheelIDTP.fill(getDeviceName(), "MODEL_ID", "iEFW Model", FILTER_TAB, IP_RO, 60, IPS_IDLE); + return true; +} + +bool iEFW::updateProperties() +{ + INDI::FilterWheel::updateProperties(); + + if (isConnected()) + { + defineProperty(HomeSP); + defineProperty(FirmwareTP); + defineProperty(WheelIDTP); + bool rc1=getiEFWInfo(); + bool rc2=getiEFWfirmwareInfo(); + getFilterPos(); + return (rc1 && rc2); + } + else + { + deleteProperty(HomeSP); + deleteProperty(FirmwareTP); + deleteProperty(WheelIDTP); + } + return true; +} + +bool iEFW::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) +{ + if (dev != nullptr && strcmp(dev, getDeviceName()) == 0) + { + if (HomeSP.isNameMatch(name)) + { + if (getiEFWID()) + { + LOG_INFO("Filter is at home position"); + HomeSP.setState(IPS_OK); + HomeSP.apply(); + } + else + HomeSP.setState(IPS_ALERT); + HomeSP.apply(); + return true; + } + } + + return INDI::FilterWheel::ISNewSwitch(dev, name, states, names, n); +} + +bool iEFW::Handshake() +{ + + return getiEFWID(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////// +bool iEFW::getiEFWInfo() +{ + int nbytes_written = 0, nbytes_read = 0, rc = -1; + char resp[16]; + int iefwpos, iefwmodel, iefwlast; + sleep(2); + tcflush(PortFD, TCIOFLUSH); + if ( (rc = tty_write(PortFD, ":DeviceInfo#", strlen(":DeviceInfo#"), &nbytes_written)) != TTY_OK) + { + char errstr[MAXRBUF] = {0}; + tty_error_msg(rc, errstr, MAXRBUF); + LOGF_ERROR( "Init send iEFW deviceinfo error: %s.", errstr); + return false; + }; + + if ( (rc = tty_read_section(PortFD, resp, '#', iEFW_TIMEOUT * 2, &nbytes_read)) != TTY_OK) + { + char errstr[MAXRBUF] = {0}; + tty_error_msg(rc, errstr, MAXRBUF); + LOGF_ERROR( "Init read iEFW deviceinfo error: %s.", errstr); + return false; + }; + tcflush(PortFD, TCIOFLUSH); + resp[nbytes_read] = '\0'; + sscanf(resp, "%6d%2d%4d", &iefwpos, &iefwmodel, &iefwlast); + if ((iefwmodel == 98)|| (iefwmodel == 99)) + { + if (iefwmodel==99) + { + FilterSlotN[0].max = 5; + WheelIDTP.setState(IPS_OK); + WheelIDTP[0].setText("iEFW-15"); + WheelIDTP.apply(); + }; + if (iefwmodel==98) + { + FilterSlotN[0].max = 8; + WheelIDTP.setState(IPS_OK); + WheelIDTP[0].setText("iEFW-18"); + WheelIDTP.apply(); + + }; + return true; + } + else + { + LOGF_ERROR( "iEFW getinfo Response: %s", resp); + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////// +bool iEFW::getiEFWfirmwareInfo() +{ + int nbytes_written = 0, nbytes_read = 0, rc = -1; + char errstr[MAXRBUF]; + char resp[16] = {0}; + char iefwfirminfo[16] = {0}; + tcflush(PortFD, TCIOFLUSH); + if ( (rc = tty_write(PortFD, ":FW1#", 5, &nbytes_written)) != TTY_OK) + { + tty_error_msg(rc, errstr, MAXRBUF); + LOGF_ERROR( "get iEFW FiremwareInfo error: %s.", errstr); + } + if ( (rc = tty_read_section(PortFD, resp, '#', iEFW_TIMEOUT, &nbytes_read)) != TTY_OK) + { + tty_error_msg(rc, errstr, MAXRBUF); + LOGF_ERROR( "get iEFW FirmwareInfo error: %s.", errstr); + return false; + } + tcflush(PortFD, TCIOFLUSH); + resp[nbytes_read] = '\0'; + sscanf(resp, "%12s", iefwfirminfo); + FirmwareTP.setState(IPS_OK); + FirmwareTP[0].setText(iefwfirminfo); + FirmwareTP.apply(); + LOGF_DEBUG("Success, response from iEFW is : %s", iefwfirminfo); + return true; +} + +int iEFW::getFilterPos() +{ + int nbytes_written = 0, nbytes_read = 0, rc = -1; + char errstr[MAXRBUF]; + char resp[16] = {0}; + int iefwpos = 1; + tcflush(PortFD, TCIOFLUSH); + if ( (rc = tty_write(PortFD, ":WP#", 4, &nbytes_written)) != TTY_OK) + { + tty_error_msg(rc, errstr, MAXRBUF); + LOGF_ERROR( "send iEFW filter pos Info error: %s.", errstr); + } + if ( (rc = tty_read_section(PortFD, resp, '#', iEFW_TIMEOUT, &nbytes_read)) != TTY_OK) + { + tty_error_msg(rc, errstr, MAXRBUF); + LOGF_ERROR( "read iEFW filter pos Info error: %s.", errstr); + return false; + } + tcflush(PortFD, TCIOFLUSH); + resp[nbytes_read] = '\0'; + LOGF_DEBUG("Success, response from iEFW is : %s", resp); + rc=sscanf(resp, "%2d", &iefwpos); + if (rc > 0) + { + if (iefwpos>=0) + { + CurrentFilter=iefwpos+1; + FilterSlotN[0].value = CurrentFilter; + FilterSlotNP.s = IPS_OK; + return CurrentFilter; + } + else + return -1; + } + else + return 999; + +} + + +bool iEFW::getiEFWID() +{ + return getiEFWInfo(); +} + +bool iEFW::SelectFilter(int f) +{ + int nbytes_written = 0, rc = -1; + char errstr[MAXRBUF]; + // char resp[16] = {0}; + // char iefwposinfo[16] = {0}; + int iefwpos=-1; + char cmd[7]={0}; + if (CurrentFilter == f) + { + SelectFilterDone(CurrentFilter); + return true; + } + f = f - 1; + + if (f < 0 || f > (FilterSlotN[0].max-1)) + return false; + + tcflush(PortFD, TCIOFLUSH); + snprintf(cmd,7, ":WM0%d#", f); + if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK) + { + tty_error_msg(rc, errstr, MAXRBUF); + LOGF_ERROR( "select iEFW send pos Info error: %s.", errstr); + } + tcflush(PortFD, TCIOFLUSH); + // check current position -1 is moving + do + { + usleep(100 * 1000); + iefwpos=getFilterPos(); + } + while (iefwpos == -1); + // return current position to indi + CurrentFilter = f + 1; + SelectFilterDone(CurrentFilter); + FilterSlotNP.s = IPS_OK; + IDSetNumber(&FilterSlotNP, "Selected filter position reached"); + LOGF_DEBUG("CurrentFilter set to %d", CurrentFilter); + return true; +} + +int iEFW::QueryFilter() +{ + return CurrentFilter; +} + diff --git a/drivers/filter_wheel/ioptron_wheel.h b/drivers/filter_wheel/ioptron_wheel.h new file mode 100644 index 0000000000..659348e621 --- /dev/null +++ b/drivers/filter_wheel/ioptron_wheel.h @@ -0,0 +1,51 @@ +/******************************************************************************* + Copyright(c) 2024 Joe Zhou. All rights reserved. + + iOptron Filter Wheel + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*******************************************************************************/ + +#pragma once + +#include "indifilterwheel.h" + +class iEFW : public INDI::FilterWheel +{ + public: + iEFW(); + virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override; + + protected: + const char *getDefaultName() override; + bool initProperties() override; + bool updateProperties() override; + bool Handshake() override; + int QueryFilter() override; + bool SelectFilter(int) override; +// void TimerHit() override; + int getFilterPos(); + // Firmware of the iEFW + INDI::PropertyText FirmwareTP {1}; + INDI::PropertyText WheelIDTP {1};; + + private: + + bool getiEFWID(); + bool getiEFWInfo(); + bool getiEFWfirmwareInfo(); + void initOffset(); + INDI::PropertySwitch HomeSP{1}; +}; diff --git a/drivers/focuser/CMakeLists.txt b/drivers/focuser/CMakeLists.txt index 85a55dc183..0d3407c3f1 100644 --- a/drivers/focuser/CMakeLists.txt +++ b/drivers/focuser/CMakeLists.txt @@ -81,6 +81,14 @@ add_executable(indi_rbfocus_focus ${rbfocus_SRC}) target_link_libraries(indi_rbfocus_focus indidriver) install(TARGETS indi_rbfocus_focus RUNTIME DESTINATION bin) +# ############### Astromech Focuser ################ +SET(astromech_focuser_SRC + astromech_focuser.cpp) + +add_executable(indi_astromechfoc ${astromech_focuser_SRC}) +target_link_libraries(indi_astromechfoc indidriver) +install(TARGETS indi_astromechfoc RUNTIME DESTINATION bin) + # ############### Moonlite Focuser ################ SET(moonlite_SRC moonlite.cpp) @@ -175,6 +183,14 @@ add_executable(indi_rainbowrsf_focus ${RSF_SRC}) target_link_libraries(indi_rainbowrsf_focus indidriver) install(TARGETS indi_rainbowrsf_focus RUNTIME DESTINATION bin) +# ############### Dream Focuser ################ +SET(dreamfocuser_SRC + dreamfocuser.cpp) + +add_executable(indi_dreamfocuser_focus ${dreamfocuser_SRC}) +target_link_libraries(indi_dreamfocuser_focus indidriver) +install(TARGETS indi_dreamfocuser_focus RUNTIME DESTINATION bin) + # ########## Lakeside ########### set(indilakeside_SRCS lakeside.cpp) diff --git a/drivers/focuser/astromech_focuser.cpp b/drivers/focuser/astromech_focuser.cpp new file mode 100644 index 0000000000..e7c5c19f05 --- /dev/null +++ b/drivers/focuser/astromech_focuser.cpp @@ -0,0 +1,319 @@ +/******************************************************************************* + Copyright(c) 2019 Christian Liska. All rights reserved. + + Implementation based on Lacerta MFOC driver + (written 2018 by Franck Le Rhun and Christian Liska). + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + . + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + . + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*******************************************************************************/ + +#include "astromech_focuser.h" +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static std::unique_ptr Astromechanics_foc(new astromechanics_foc()); + +// Delay for receiving messages +#define FOC_POSMAX_HARDWARE 32767 +#define FOC_POSMIN_HARDWARE 0 + +bool astromechanics_foc::Disconnect() +{ + SetApperture(0); + return true; +} + +void ISSnoopDevice(XMLEle *root) +{ + Astromechanics_foc->ISSnoopDevice(root); +} + +/************************************************************************************ + * +************************************************************************************/ +astromechanics_foc::astromechanics_foc() +{ + setVersion(0, 2); + FI::SetCapability(FOCUSER_CAN_ABS_MOVE | FOCUSER_CAN_REL_MOVE); +} + +/************************************************************************************ + * +************************************************************************************/ +const char *astromechanics_foc::getDefaultName() +{ + return "Astromechanics FOC"; +} + +/************************************************************************************ + * +************************************************************************************/ +bool astromechanics_foc::initProperties() +{ + INDI::Focuser::initProperties(); + + FocusMaxPosN[0].min = FOC_POSMIN_HARDWARE; + FocusMaxPosN[0].max = FOC_POSMAX_HARDWARE; + FocusMaxPosN[0].step = 500; + FocusMaxPosN[0].value = FOC_POSMAX_HARDWARE; + + FocusAbsPosN[0].min = FOC_POSMIN_HARDWARE; + FocusAbsPosN[0].max = FOC_POSMAX_HARDWARE; + FocusAbsPosN[0].step = 500; + FocusAbsPosN[0].value = 0; + + FocusRelPosN[0].min = FocusAbsPosN[0].min; + FocusRelPosN[0].max = FocusAbsPosN[0].max / 2; + FocusRelPosN[0].step = 250; + FocusRelPosN[0].value = 0; + + // Aperture + IUFillNumber(&AppertureN[0], "LENS_APP", "Index", "%.f", 0, 22, 1, 0); + IUFillNumberVector(&AppertureNP, AppertureN, 1, getDeviceName(), "LENS_APP_SETTING", "Apperture", MAIN_CONTROL_TAB, IP_RW, + 60, IPS_IDLE); + + serialConnection->setDefaultBaudRate(Connection::Serial::B_38400); + return true; +} + +/************************************************************************************ + * +************************************************************************************/ +bool astromechanics_foc::updateProperties() +{ + // Get Initial Position before we define it in the INDI::Focuser class + FocusAbsPosN[0].value = GetAbsFocuserPosition(); + + INDI::Focuser::updateProperties(); + + if (isConnected()) + { + defineProperty(&AppertureNP); + } + else + { + deleteProperty(AppertureNP.name); + } + + return true; +} + +/************************************************************************************ + * +************************************************************************************/ +bool astromechanics_foc::Handshake() +{ + char res[DRIVER_LEN] = {0}; + int position = 0; + for (int i = 0; i < 3; i++) + { + if (!sendCommand("P#", res)) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + else + { + sscanf(res, "%d", &position); + FocusAbsPosN[0].value = position; + FocusAbsPosNP.s = IPS_OK; + SetApperture(0); + return true; + } + } + + return false; +} + +/************************************************************************************ + * +************************************************************************************/ +bool astromechanics_foc::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) +{ + if (dev != nullptr && strcmp(dev, getDeviceName()) == 0) + { + if (strcmp(name, "LENS_APP_SETTING") == 0) + { + IUUpdateNumber(&AppertureNP, values, names, n); + AppertureNP.s = IPS_OK; + IDSetNumber(&AppertureNP, nullptr); + SetApperture(AppertureN[0].value); + return true; + } + } + + return INDI::Focuser::ISNewNumber(dev, name, values, names, n); +} + +/************************************************************************************ + * +************************************************************************************/ +IPState astromechanics_foc::MoveAbsFocuser(uint32_t targetTicks) +{ + char cmd[DRIVER_LEN] = {0}; + snprintf(cmd, DRIVER_LEN, "M%u#", targetTicks); + if (sendCommand(cmd)) + { + FocusAbsPosN[0].value = GetAbsFocuserPosition(); + return IPS_OK; + } + + return IPS_ALERT; +} + +/************************************************************************************ + * +************************************************************************************/ +IPState astromechanics_foc::MoveRelFocuser(FocusDirection dir, uint32_t ticks) +{ + // Clamp + int32_t offset = ((dir == FOCUS_INWARD) ? -1 : 1) * static_cast(ticks); + int32_t newPosition = FocusAbsPosN[0].value + offset; + newPosition = std::max(static_cast(FocusAbsPosN[0].min), std::min(static_cast(FocusAbsPosN[0].max), + newPosition)); + + FocusAbsPosNP.s = IPS_BUSY; + IDSetNumber(&FocusAbsPosNP, nullptr); + + return MoveAbsFocuser(newPosition); +} + +/************************************************************************************ + * +************************************************************************************/ +bool astromechanics_foc::SetApperture(uint32_t index) +{ + char cmd[DRIVER_LEN] = {0}; + snprintf(cmd, DRIVER_LEN, "A%u#", index); + return sendCommand(cmd); +} + +/************************************************************************************ + * +************************************************************************************/ +uint32_t astromechanics_foc::GetAbsFocuserPosition() +{ + char res[DRIVER_LEN] = {0}; + int position = 0; + + if (!sendCommand("P#", res)) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + else + { + sscanf(res, "%d", &position); + return position; + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////// +/// Send Command +///////////////////////////////////////////////////////////////////////////// +bool astromechanics_foc::sendCommand(const char * cmd, char * res, int cmd_len, int res_len) +{ + int nbytes_written = 0, nbytes_read = 0, rc = -1; + tcflush(PortFD, TCIOFLUSH); + + if (cmd_len > 0) + { + char hex_cmd[DRIVER_LEN * 3] = {0}; + hexDump(hex_cmd, cmd, cmd_len); + LOGF_DEBUG("CMD <%s>", hex_cmd); + rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written); + } + else + { + LOGF_DEBUG("CMD <%s>", cmd); + rc = tty_write_string(PortFD, cmd, &nbytes_written); + } + + if (rc != TTY_OK) + { + char errstr[MAXRBUF] = {0}; + tty_error_msg(rc, errstr, MAXRBUF); + LOGF_ERROR("Serial write error: %s.", errstr); + return false; + } + + if (res == nullptr) + return true; + + if (res_len > 0) + rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read); + else + { + // Read respose + 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("Serial read error: %s.", errstr); + return false; + } + + if (res_len > 0) + { + char hex_res[DRIVER_LEN * 3] = {0}; + hexDump(hex_res, res, res_len); + LOGF_DEBUG("RES <%s>", hex_res); + } + else + { + // Remove extra # + res[nbytes_read - 1] = 0; + LOGF_DEBUG("RES <%s>", res); + } + + tcflush(PortFD, TCIOFLUSH); + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +/// +///////////////////////////////////////////////////////////////////////////// +void astromechanics_foc::hexDump(char * buf, const char * data, int size) +{ + for (int i = 0; i < size; i++) + sprintf(buf + 3 * i, "%02X ", static_cast(data[i])); + + if (size > 0) + buf[3 * size - 1] = '\0'; +} + +///////////////////////////////////////////////////////////////////////////// +/// +///////////////////////////////////////////////////////////////////////////// +std::vector astromechanics_foc::split(const std::string &input, const std::string ®ex) +{ + // passing -1 as the submatch index parameter performs splitting + std::regex re(regex); + std::sregex_token_iterator + first{input.begin(), input.end(), re, -1}, + last; + return {first, last}; +} diff --git a/drivers/focuser/astromech_focuser.h b/drivers/focuser/astromech_focuser.h new file mode 100644 index 0000000000..adec20da9e --- /dev/null +++ b/drivers/focuser/astromech_focuser.h @@ -0,0 +1,58 @@ +/******************************************************************************* + Copyright(c) 2019 Christian Liska. All rights reserved. + + Implementation based on Lacerta MFOC driver + (written 2018 by Franck Le Rhun and Christian Liska). + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + . + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + . + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*******************************************************************************/ + +#pragma once + +#include "indifocuser.h" + +class astromechanics_foc : public INDI::Focuser +{ + public: + astromechanics_foc(); + bool initProperties() override; + bool updateProperties() override; + const char *getDefaultName() override; + virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override; + + protected: + virtual bool Disconnect() override; + virtual bool Handshake() override; + virtual IPState MoveAbsFocuser(uint32_t targetTicks) override; + virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override; + + private: + virtual uint32_t GetAbsFocuserPosition(); + virtual bool SetApperture(uint32_t index); + + INumberVectorProperty AppertureNP; + INumber AppertureN[1]; + + bool sendCommand(const char * cmd, char * res = nullptr, int cmd_len = -1, int res_len = -1); + void hexDump(char * buf, const char * data, int size); + std::vector split(const std::string &input, const std::string ®ex); + + // # is the stop char + static const char DRIVER_STOP_CHAR { 0x23 }; + // Wait up to a maximum of 3 seconds for serial input + static constexpr const uint8_t DRIVER_TIMEOUT {3}; + // Maximum buffer for sending/receving. + static constexpr const uint8_t DRIVER_LEN {64}; +}; diff --git a/drivers/focuser/dreamfocuser.cpp b/drivers/focuser/dreamfocuser.cpp new file mode 100644 index 0000000000..b7c2694fb5 --- /dev/null +++ b/drivers/focuser/dreamfocuser.cpp @@ -0,0 +1,799 @@ +/* + INDI Driver for DreamFocuser + + Copyright (C) 2016 Piotr Dlugosz + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dreamfocuser.h" +#include "config.h" +#include "connectionplugins/connectionserial.h" + +/* +COMMANDS: + +MMabcd0z - set position x +response - MMabcd0z + +MH00000z - stop +response - MH00000z + +MP00000z - read position +response - MPabcd0z + +MI00000z - is moving +response - MI000d0z - d = 1: yes, 0: no + +MT00000z - read temperature +response - MT00cd0z - temperature = ((c<<8)|d)/16.0 + +MA0000nz - read memory dword - n = address +response - MAabcd0z(?) + +MBabcdnz - write memory dword - abcd = content, n = address +response - MBabcd0z(?) + +MC0000nz - read memory word - n = address +response - + +MDab00nz - write memory word - ab = content, n = address +response - + +---- + +MR000d0z - move with speed d & 0b1111111 (0 - 127), direction d >> 7 (1 up, 0 down) +response - MR000d0z + +MW00000z - is calibrated +response - MW000d0z - d = 1: yes (absolute mode), 0: no (relative mode) + +MZabcd0z - calibrate toposition x +response - MZabcd0z + +MV00000z - firmware version +response - MV00cd0z - version: c.d + +MG00000z - park +response - MG00000z +*/ + +#define PARK_PARK 0 +#define PARK_UNPARK 1 + +// We declare an auto pointer to DreamFocuser. +static std::unique_ptr dreamFocuser(new DreamFocuser()); + +void ISPoll(void *p); + + +/**************************************************************** +** +** +*****************************************************************/ + +DreamFocuser::DreamFocuser() +{ + FI::SetCapability(FOCUSER_CAN_ABS_MOVE | FOCUSER_CAN_REL_MOVE | FOCUSER_CAN_ABORT | FOCUSER_CAN_SYNC); + + isAbsolute = false; + isMoving = false; + isParked = 0; + isVcc12V = false; + + setVersion(2, 1); + +} + +bool DreamFocuser::initProperties() +{ + INDI::Focuser::initProperties(); + + // Default speed + //FocusSpeedN[0].min = 0; + //FocusSpeedN[0].max = 127; + //FocusSpeedN[0].value = 50; + //IUUpdateMinMax(&FocusSpeedNP); + + // Max Position + // IUFillNumber(&MaxPositionN[0], "MAXPOSITION", "Ticks", "%.f", 1., 500000., 1000., 300000); + // IUFillNumberVector(&MaxPositionNP, MaxPositionN, 1, getDeviceName(), "MAXPOSITION", "Max Absolute Position", FOCUS_SETTINGS_TAB, IP_RW, 0, IPS_IDLE); + + // IUFillNumber(&MaxTravelN[0], "MAXTRAVEL", "Ticks", "%.f", 1., 500000., 1000., 300000.); + // IUFillNumberVector(&MaxTravelNP, MaxTravelN, 1, getDeviceName(), "MAXTRAVEL", "Max Relative Travel", FOCUS_SETTINGS_TAB, IP_RW, 0, IPS_IDLE ); + + // // Focus Sync + // IUFillSwitch(&SyncS[0], "SYNC", "Synchronize", ISS_OFF); + // IUFillSwitchVector(&SyncSP, SyncS, 1, getDeviceName(), "SYNC", "Synchronize", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE); + + // Focus Park + IUFillSwitch(&ParkS[PARK_PARK], "PARK", "Park", ISS_OFF); + IUFillSwitch(&ParkS[PARK_UNPARK], "UNPARK", "Unpark", ISS_OFF); + IUFillSwitchVector(&ParkSP, ParkS, 2, getDeviceName(), "PARK", "Park", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 0, IPS_IDLE); + + // 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); + + // Focuser humidity and dewpoint + IUFillNumber(&WeatherN[0], "FOCUS_HUMIDITY", "Humidity [%]", "%6.1f", 0, 100, 0, 0); + IUFillNumber(&WeatherN[1], "FOCUS_DEWPOINT", "Dew point [C]", "%6.1f", -100, 100, 0, 0); + IUFillNumberVector(&WeatherNP, WeatherN, 2, getDeviceName(), "FOCUS_WEATHER", "Weather", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE); + + // We init here the property we wish to "snoop" from the target device + IUFillSwitch(&StatusS[0], "ABSOLUTE", "Absolute", ISS_OFF); + IUFillSwitch(&StatusS[1], "MOVING", "Moving", ISS_OFF); + IUFillSwitch(&StatusS[2], "PARKED", "Parked", ISS_OFF); + IUFillSwitchVector(&StatusSP, StatusS, 3, getDeviceName(), "STATUS", "Status", MAIN_CONTROL_TAB, IP_RO, ISR_NOFMANY, 0, IPS_IDLE); + + // PresetN[0].min = PresetN[1].min = PresetN[2].min = FocusAbsPosN[0].min = -MaxPositionN[0].value; + // PresetN[0].max = PresetN[1].max = PresetN[2].max = FocusAbsPosN[0].max = MaxPositionN[0].value; + // strcpy(PresetN[0].format, "%6.0f"); + // strcpy(PresetN[1].format, "%6.0f"); + // strcpy(PresetN[2].format, "%6.0f"); + // PresetN[0].step = PresetN[1].step = PresetN[2].step = FocusAbsPosN[0].step = DREAMFOCUSER_STEP_SIZE; + + // Maximum position can't be changed from driver + FocusMaxPosNP.p = IP_RO; + + FocusAbsPosN[0].value = 0; + FocusRelPosN[0].min = -FocusMaxPosN[0].max; + FocusRelPosN[0].max = FocusMaxPosN[0].max; + FocusRelPosN[0].step = DREAMFOCUSER_STEP_SIZE; + FocusRelPosN[0].value = 5 * DREAMFOCUSER_STEP_SIZE; + + serialConnection->setDefaultPort("/dev/ttyACM0"); + serialConnection->setDefaultBaudRate(Connection::Serial::B_115200); + setDefaultPollingPeriod(500); + + return true; +} + +bool DreamFocuser::updateProperties() +{ + INDI::Focuser::updateProperties(); + + if (isConnected()) + { + //defineProperty(&SyncSP); + defineProperty(&ParkSP); + defineProperty(&TemperatureNP); + defineProperty(&WeatherNP); + defineProperty(&StatusSP); + //defineProperty(&MaxPositionNP); + //defineProperty(&MaxTravelNP); + } + else + { + //deleteProperty(SyncSP.name); + deleteProperty(ParkSP.name); + deleteProperty(TemperatureNP.name); + deleteProperty(WeatherNP.name); + deleteProperty(StatusSP.name); + //deleteProperty(MaxPositionNP.name); + //deleteProperty(MaxTravelNP.name); + } + return true; +} + + +/* +void DreamFocuser::ISGetProperties(const char *dev) +{ + if(dev && strcmp(dev,getDeviceName())) + { + defineProperty(&MaxPositionNP); + loadConfig(true, "MAXPOSITION"); + }; + return INDI::Focuser::ISGetProperties(dev); +} +*/ + + +//bool DreamFocuser::saveConfigItems(FILE *fp) +//{ +// INDI::Focuser::saveConfigItems(fp); + +// IUSaveConfigNumber(fp, &MaxPositionNP); +// IUSaveConfigNumber(fp, &MaxTravelNP); + +// return true; +//} + +//bool DreamFocuser::ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n) +//{ + +// if(strcmp(dev, getDeviceName()) == 0) +// { +// // Max Position +// if (!strcmp(MaxPositionNP.name, name)) +// { +// IUUpdateNumber(&MaxPositionNP, values, names, n); + +// if (MaxPositionN[0].value > 0) +// { +// PresetN[0].min = PresetN[1].min = PresetN[2].min = FocusAbsPosN[0].min = -MaxPositionN[0].value;; +// PresetN[0].max = PresetN[1].max = PresetN[2].max = FocusAbsPosN[0].max = MaxPositionN[0].value; +// IUUpdateMinMax(&FocusAbsPosNP); +// IUUpdateMinMax(&PresetNP); +// IDSetNumber(&FocusAbsPosNP, nullptr); + +// LOGF_DEBUG("Focuser absolute limits: min (%g) max (%g)", FocusAbsPosN[0].min, FocusAbsPosN[0].max); +// } + +// MaxPositionNP.s = IPS_OK; +// IDSetNumber(&MaxPositionNP, nullptr); +// return true; +// } + + +// // Max Travel +// if (!strcmp(MaxTravelNP.name, name)) +// { +// IUUpdateNumber(&MaxTravelNP, values, names, n); + +// if (MaxTravelN[0].value > 0) +// { +// FocusRelPosN[0].min = 0; +// FocusRelPosN[0].max = MaxTravelN[0].value; +// IUUpdateMinMax(&FocusRelPosNP); +// IDSetNumber(&FocusRelPosNP, nullptr); + +// LOGF_DEBUG("Focuser relative limits: min (%g) max (%g)", FocusRelPosN[0].min, FocusRelPosN[0].max); +// } + +// MaxTravelNP.s = IPS_OK; +// IDSetNumber(&MaxTravelNP, nullptr); +// return true; +// } + +// } + +// return INDI::Focuser::ISNewNumber(dev, name, values, names, n); + +//} + + +bool DreamFocuser::ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n) +{ + if(strcmp(dev, getDeviceName()) == 0) + { + // Park + if (!strcmp(ParkSP.name, name)) + { + IUUpdateSwitch(&ParkSP, states, names, n); + int index = IUFindOnSwitchIndex(&ParkSP); + IUResetSwitch(&ParkSP); + + if ( (isParked && (index == PARK_UNPARK)) || ( !isParked && (index == PARK_PARK)) ) + { + LOG_INFO("Park, issuing command."); + if ( setPark() ) + { + //ParkSP.s = IPS_OK; + FocusAbsPosNP.s = IPS_OK; + IDSetNumber(&FocusAbsPosNP, nullptr); + } + else + ParkSP.s = IPS_ALERT; + } + IDSetSwitch(&ParkSP, nullptr); + return true; + } + } + + return INDI::Focuser::ISNewSwitch(dev, name, states, names, n); +} + +bool DreamFocuser::SyncFocuser(uint32_t ticks) +{ + return setSync(ticks); +} + +/**************************************************************** +** +** +*****************************************************************/ + +bool DreamFocuser::getTemperature() +{ + if ( dispatch_command('T') ) + { + currentTemperature = ((short int)( (currentResponse.c << 8) | currentResponse.d )) / 10.; + currentHumidity = ((short int)( (currentResponse.a << 8) | currentResponse.b )) / 10.; + } + else + return false; + return true; +} + +bool DreamFocuser::Handshake() +{ + return getStatus(); +} + +bool DreamFocuser::getStatus() +{ + LOG_DEBUG("getStatus."); + if ( dispatch_command('I') ) + { + isMoving = ( currentResponse.d & 3 ) != 0 ? true : false; + //isZero = ( (currentResponse.d>>2) & 1 ) == 1; + isParked = (currentResponse.d>>3) & 3; + isVcc12V = ( (currentResponse.d>>5) & 1 ) == 1; + } + else + return false; + + if ( dispatch_command('W') ) // Is absolute? + isAbsolute = currentResponse.d == 1 ? true : false; + else + return false; + + return true; +} + + +bool DreamFocuser::getPosition() +{ + //int32_t pos; + + if ( dispatch_command('P') ) + currentPosition = (currentResponse.a << 24) | (currentResponse.b << 16) | (currentResponse.c << 8) | currentResponse.d; + else + return false; + + return true; +} + + +bool DreamFocuser::setPosition( int32_t position) +{ + if ( dispatch_command('M', position) ) + if ( ((currentResponse.a << 24) | (currentResponse.b << 16) | (currentResponse.c << 8) | currentResponse.d) == position ) + { + LOGF_DEBUG("Moving to position %d", position); + return true; + }; + return false; +} + +bool DreamFocuser::getMaxPosition() +{ + if ( dispatch_command('A', 0, 3) ) + { + currentMaxPosition = (currentResponse.a << 24) | (currentResponse.b << 16) | (currentResponse.c << 8) | currentResponse.d; + LOGF_DEBUG("getMaxPosition: %d", currentMaxPosition); + return true; + } + else + LOG_ERROR("getMaxPosition error"); + + return false; +} + + +bool DreamFocuser::setSync( uint32_t position) +{ + if ( dispatch_command('Z', position) ) + if ( static_cast((currentResponse.a << 24) | (currentResponse.b << 16) | (currentResponse.c << 8) | currentResponse.d) == position ) + { + LOGF_DEBUG("Syncing to position %d", position); + return true; + }; + LOG_ERROR("Sync failed."); + return false; +} + +bool DreamFocuser::setPark() +{ + if (isAbsolute == false) + { + LOG_ERROR("Focuser is not in Absolute mode. Please sync before to allow parking."); + return false; + } + + if ( dispatch_command('G') ) + { + LOG_INFO( "Focuser park command."); + return true; + } + LOG_ERROR("Park failed."); + return false; +} + +bool DreamFocuser::AbortFocuser() +{ + if ( dispatch_command('H') ) + { + LOG_INFO("Focusing aborted."); + return true; + }; + LOG_ERROR("Abort failed."); + return false; +} + + +/* +IPState DreamFocuser::MoveFocuser(FocusDirection dir, int speed, uint16_t duration) +{ + DreamFocuserCommand c; + unsigned char d = (unsigned char) (speed & 0b01111111) | (dir == FOCUS_INWARD) ? 0b10000000 : 0; + + if ( dispatch_command('R', d) ) + { + gettimeofday(&focusMoveStart,nullptr); + focusMoveRequest = duration/1000.0; + if ( read_response() ) + if ( ( currentResponse.k == 'R' ) && (currentResponse.d == d) ) + if (duration <= getCurrentPollingPeriod()) + { + usleep(getCurrentPollingPeriod() * 1000); + AbortFocuser(); + return IPS_OK; + } + else + return IPS_BUSY; + } + return IPS_ALERT; +} +*/ + +IPState DreamFocuser::MoveAbsFocuser(uint32_t ticks) +{ + LOGF_DEBUG("MoveAbsPosition: %d", ticks); + + if (isAbsolute == false) + { + LOG_ERROR("Focuser is not in Absolute mode. Please sync."); + return IPS_ALERT; + } + + if (isParked != 0) + { + LOG_ERROR("Please unpark before issuing any motion commands."); + return IPS_ALERT; + } + if ( setPosition(ticks) ) + { + FocusAbsPosNP.s = IPS_OK; + IDSetNumber(&FocusAbsPosNP, nullptr); + return IPS_OK; + } + return IPS_ALERT; +} + +IPState DreamFocuser::MoveRelFocuser(FocusDirection dir, uint32_t ticks) +{ + int32_t finalTicks = currentPosition + ((int32_t)ticks * (dir == FOCUS_INWARD ? -1 : 1)); + + LOGF_DEBUG("MoveRelPosition: %d", finalTicks); + + if (isParked != 0) + { + LOG_ERROR("Please unpark before issuing any motion commands."); + return IPS_ALERT; + } + + if ( setPosition(finalTicks) ) + { + FocusRelPosNP.s = IPS_OK; + IDSetNumber(&FocusRelPosNP, nullptr); + return IPS_OK; + } + return IPS_ALERT; +} + + +void DreamFocuser::TimerHit() +{ + + if ( ! isConnected() ) + return; + + int oldAbsStatus = FocusAbsPosNP.s; + int32_t oldPosition = currentPosition; + + if ( getMaxPosition() ) + { + if ( FocusMaxPosN[0].value != currentMaxPosition ) { + FocusMaxPosN[0].value = currentMaxPosition; + FocusMaxPosNP.s = IPS_OK; + IDSetNumber(&FocusMaxPosNP, nullptr); + SetFocuserMaxPosition(currentMaxPosition); + } + } + else + FocusMaxPosNP.s = IPS_ALERT; + + if ( getStatus() ) + { + + StatusSP.s = IPS_OK; + if ( isMoving ) + { + //LOG_INFO("Moving" ); + FocusAbsPosNP.s = IPS_BUSY; + StatusS[1].s = ISS_ON; + } + else + { + if ( FocusAbsPosNP.s != IPS_IDLE ) + FocusAbsPosNP.s = IPS_OK; + StatusS[1].s = ISS_OFF; + }; + + if ( isParked == 1 ) + { + ParkSP.s = IPS_BUSY; + StatusS[2].s = ISS_ON; + ParkS[0].s = ISS_ON; + } + else if ( isParked == 2 ) + { + ParkSP.s = IPS_OK; + StatusS[2].s = ISS_ON; + ParkS[0].s = ISS_ON; + } + else + { + StatusS[2].s = ISS_OFF; + ParkS[1].s = ISS_ON; + ParkSP.s = IPS_IDLE; + } + + if ( isAbsolute ) + { + StatusS[0].s = ISS_ON; + if ( FocusAbsPosN[0].min != 0 ) + { + FocusAbsPosN[0].min = 0; + IDSetNumber(&FocusAbsPosNP, nullptr); + } + } + else + { + if ( FocusAbsPosN[0].min == 0 ) + { + FocusAbsPosN[0].min = -FocusAbsPosN[0].max; + IDSetNumber(&FocusAbsPosNP, nullptr); + } + StatusS[0].s = ISS_OFF; + } + + } + else + StatusSP.s = IPS_ALERT; + + if ( getTemperature() ) + { + TemperatureNP.s = TemperatureN[0].value != currentTemperature ? IPS_BUSY : IPS_OK; + WeatherNP.s = WeatherN[1].value != currentHumidity ? IPS_BUSY : IPS_OK; + TemperatureN[0].value = currentTemperature; + WeatherN[0].value = currentHumidity; + WeatherN[1].value = pow(currentHumidity / 100, 1.0 / 8) * (112 + 0.9 * currentTemperature) + 0.1 * currentTemperature - 112; + } + else + { + TemperatureNP.s = IPS_ALERT; + WeatherNP.s = IPS_ALERT; + } + + if ( FocusAbsPosNP.s != IPS_IDLE ) + { + if ( getPosition() ) + { + if ( oldPosition != currentPosition ) + { + FocusAbsPosNP.s = IPS_BUSY; + StatusS[1].s = ISS_ON; + FocusAbsPosN[0].value = currentPosition; + } + else + { + StatusS[1].s = ISS_OFF; + FocusAbsPosNP.s = IPS_OK; + } + //if ( currentPosition < 0 ) + // FocusAbsPosNP.s = IPS_ALERT; + } + else + FocusAbsPosNP.s = IPS_ALERT; + } + + + if ((oldAbsStatus != FocusAbsPosNP.s) || (oldPosition != currentPosition)) + IDSetNumber(&FocusAbsPosNP, nullptr); + + IDSetNumber(&TemperatureNP, nullptr); + IDSetNumber(&WeatherNP, nullptr); + //IDSetSwitch(&SyncSP, nullptr); + IDSetSwitch(&StatusSP, nullptr); + IDSetSwitch(&ParkSP, NULL); + + SetTimer(getCurrentPollingPeriod()); + +} + + +/**************************************************************** +** +** +*****************************************************************/ + +unsigned char DreamFocuser::calculate_checksum(DreamFocuserCommand c) +{ + unsigned char z; + + // calculate checksum + z = (c.M + c.k + c.a + c.b + c.c + c.d + c.addr) & 0xff; + return z; +} + +bool DreamFocuser::send_command(char k, uint32_t l, unsigned char addr) +{ + DreamFocuserCommand c; + int err_code = 0, nbytes_written = 0; + char dreamFocuser_error[DREAMFOCUSER_ERROR_BUFFER]; + unsigned char *x = (unsigned char *)&l; + + switch(k) + { + case 'M': + case 'Z': + c.a = x[3]; + c.b = x[2]; + c.c = x[1]; + c.d = x[0]; + break; + case 'H': + case 'P': + case 'I': + case 'T': + case 'W': + case 'G': + case 'V': + c.a = 0; + c.b = 0; + c.c = 0; + c.d = 0; + break; + case 'R': + c.a = 0; + c.b = 0; + c.c = 0; + c.d = x[0]; + break; + case 'A': + case 'C': + c.a = 0; + c.b = 0; + c.c = 0; + c.d = x[0]; + break; + case 'B': + case 'D': + c.a = x[3]; + c.b = x[2]; + c.c = 0; + c.d = x[0]; + break; + default: + DEBUGF(INDI::Logger::DBG_ERROR, "Unknown command: '%c'", k); + return false; + } + c.k = k; + c.addr = addr; + c.z = calculate_checksum(c); + + LOGF_DEBUG("Sending command: c=%c, a=%hhu, b=%hhu, c=%hhu, d=%hhu ($%hhx), n=%hhu, z=%hhu", c.k, c.a, c.b, c.c, c.d, c.d, c.addr, c.z); + + tcflush(PortFD, TCIOFLUSH); + + if ( (err_code = tty_write(PortFD, (char *)&c, sizeof(c), &nbytes_written) != TTY_OK)) + { + tty_error_msg(err_code, dreamFocuser_error, DREAMFOCUSER_ERROR_BUFFER); + LOGF_ERROR("TTY error detected: %s", dreamFocuser_error); + return false; + } + + LOGF_DEBUG("Sending complete. Number of bytes written: %d", nbytes_written); + + return true; +} + +bool DreamFocuser::read_response() +{ + int err_code = 0, nbytes_read = 0, z; + char err_msg[DREAMFOCUSER_ERROR_BUFFER]; + + //LOG_DEBUG("Read response"); + + // Read a single response + if ( (err_code = tty_read(PortFD, (char *)¤tResponse, sizeof(currentResponse), 5, &nbytes_read)) != TTY_OK) + { + tty_error_msg(err_code, err_msg, 32); + LOGF_ERROR("TTY error detected: %s", err_msg); + return false; + } + LOGF_DEBUG("Response: %c, a=%hhu, b=%hhu, c=%hhu, d=%hhu ($%hhx), n=%hhu, z=%hhu", currentResponse.k, currentResponse.a, currentResponse.b, currentResponse.c, currentResponse.d, currentResponse.d, currentResponse.addr, currentResponse.z); + + if ( nbytes_read != sizeof(currentResponse) ) + { + LOGF_ERROR("Number of bytes read: %d, expected: %d", nbytes_read, sizeof(currentResponse)); + return false; + } + + z = calculate_checksum(currentResponse); + if ( z != currentResponse.z ) + { + LOGF_ERROR("Response checksum in not correct %hhu, expected: %hhu", currentResponse.z, z ); + return false; + } + + if ( currentResponse.k == '!' ) + { + LOG_ERROR("Focuser reported unrecognized command."); + return false; + } + + if ( currentResponse.k == '?' ) + { + LOG_ERROR("Focuser reported bad checksum."); + return false; + } + + return true; +} + +bool DreamFocuser::dispatch_command(char k, uint32_t l, unsigned char addr) +{ + LOG_DEBUG("send_command"); + if ( send_command(k, l, addr) ) + { + if ( read_response() ) + { + LOG_DEBUG("check currentResponse.k"); + if ( currentResponse.k == k ) + return true; + } + } + return false; +} + +/**************************************************************** +** +** +*****************************************************************/ + +const char * DreamFocuser::getDefaultName() +{ + return (char *)"DreamFocuser"; +} diff --git a/drivers/focuser/dreamfocuser.h b/drivers/focuser/dreamfocuser.h new file mode 100644 index 0000000000..5b8e18a6f6 --- /dev/null +++ b/drivers/focuser/dreamfocuser.h @@ -0,0 +1,114 @@ +/* + INDI Driver for DreamFocuser + + Copyright (C) 2016 Piotr Dlugosz + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef DREAMFOCUSER_H +#define DREAMFOCUSER_H + +#include + +#include +#include +#include + +using namespace std; + +#define DREAMFOCUSER_STEP_SIZE 32 +#define DREAMFOCUSER_ERROR_BUFFER 1024 + + +class DreamFocuser : public INDI::Focuser +{ + + public: + + struct DreamFocuserCommand + { + char M = 'M'; + char k; + unsigned char a; + unsigned char b; + unsigned char c; + unsigned char d; + unsigned char addr = '\0'; + unsigned char z; + }; + + DreamFocuser(); + + const char *getDefaultName() override; + virtual bool initProperties() override; + virtual bool updateProperties() override; + //virtual bool saveConfigItems(FILE *fp) override; + //virtual bool ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n) override; + virtual bool ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n) override; + + protected: + virtual bool Handshake() override; + virtual void TimerHit() override; + virtual bool SyncFocuser(uint32_t ticks) override; + + virtual IPState MoveAbsFocuser(uint32_t ticks) override; + virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override; + virtual bool AbortFocuser() override; + + private: + + INumber TemperatureN[1]; + INumberVectorProperty TemperatureNP; + + INumber WeatherN[2]; + INumberVectorProperty WeatherNP; + + ISwitch ParkS[2]; + ISwitchVectorProperty ParkSP; + + ISwitch StatusS[3]; + ISwitchVectorProperty StatusSP; + + //INumber SetBacklashN[1]; + //INumberVectorProperty SetBacklashNP; + + unsigned char calculate_checksum(DreamFocuserCommand c); + bool send_command(char k, uint32_t l = 0, unsigned char addr = 0); + bool read_response(); + bool dispatch_command(char k, uint32_t l = 0, unsigned char addr = 0); + + bool getTemperature(); + bool getStatus(); + bool getPosition(); + bool getMaxPosition(); + bool setPosition(int32_t position); + bool setSync(uint32_t position = 0); + bool setPark(); + + // Variables + float currentTemperature; + float currentHumidity; + int32_t currentPosition; + int32_t currentMaxPosition; + bool isAbsolute; + bool isMoving; + unsigned char isParked; + bool isVcc12V; + DreamFocuserCommand currentResponse; +}; + +#endif diff --git a/drivers/focuser/ieaffocus.cpp b/drivers/focuser/ieaffocus.cpp index d698c13831..a1421bf671 100644 --- a/drivers/focuser/ieaffocus.cpp +++ b/drivers/focuser/ieaffocus.cpp @@ -153,7 +153,8 @@ bool iEAFFocus::Ack() tcflush(PortFD, TCIOFLUSH); resp[nbytes_read] = '\0'; sscanf(resp, "%6d%2d%4d", &ieafpos, &ieafmodel, &ieaflast); - if (ieafmodel == 2) + //add iAFS Focuser + if ((ieafmodel == 2)||(ieafmodel == 3)) { return true; } diff --git a/drivers/spectrograph/CMakeLists.txt b/drivers/spectrograph/CMakeLists.txt index 6286e4a5f6..3b2dd79d9b 100644 --- a/drivers/spectrograph/CMakeLists.txt +++ b/drivers/spectrograph/CMakeLists.txt @@ -19,4 +19,15 @@ target_link_libraries(shelyak_usis indidriver ${JSONLIB}) install(TARGETS shelyak_usis RUNTIME DESTINATION bin) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/shelyak/shelyak_boards.json DESTINATION ${DATA_INSTALL_DIR}) +################################################################################################## +################################################################################################## +set( spectracyber_SRCS + spectracyber.cpp +) + +add_executable(indi_spectracyber ${spectracyber_SRCS}) +target_link_libraries(indi_spectracyber indidriver ${JSONLIB}) + +install(TARGETS indi_spectracyber RUNTIME DESTINATION bin) + ################################################################################# diff --git a/drivers/spectrograph/spectracyber.cpp b/drivers/spectrograph/spectracyber.cpp new file mode 100644 index 0000000000..fa0f33ed30 --- /dev/null +++ b/drivers/spectrograph/spectracyber.cpp @@ -0,0 +1,1048 @@ +/* + Kuwait National Radio Observatory + INDI Driver for SpectraCyber Hydrogen Line Spectrometer + Communication: RS232 <---> USB + + Copyright (C) 2009 Jasem Mutlaq (mutlaqja@ikarustech.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + Change Log: + + Format of BLOB data is: + + ########### ####### ########## ## ### + Julian_Date Voltage Freqnuency RA DEC + +*/ + +#include "spectracyber.h" + +#include "config.h" + +#include + +#include + +#include +#include +#include +#include +#include + +#define mydev "SpectraCyber" +#define BASIC_GROUP "Main Control" +#define OPTIONS_GROUP "Options" + +#define CONT_CHANNEL 0 +#define SPEC_CHANNEL 1 + +//const int SPECTROMETER_READ_BUFFER = 16; +const int SPECTROMETER_ERROR_BUFFER = 128; +const int SPECTROMETER_CMD_LEN = 5; +const int SPECTROMETER_CMD_REPLY = 4; + +//const double SPECTROMETER_MIN_FREQ = 46.4; +const double SPECTROMETER_REST_FREQ = 48.6; +//const double SPECTROMETER_MAX_FREQ = 51.2; +const double SPECTROMETER_RF_FREQ = 1371.805; + +const unsigned int SPECTROMETER_OFFSET = 0x050; + +/* 90 Khz Rest Correction */ +const double SPECTROMETER_REST_CORRECTION = 0.090; + +static const char *contFMT = ".ascii_cont"; +static const char *specFMT = ".ascii_spec"; + +// We declare an auto pointer to spectrometer. +std::unique_ptr spectracyber(new SpectraCyber()); + +/**************************************************************** +** +** +*****************************************************************/ +SpectraCyber::SpectraCyber() +{ + // Command pre-limiter + command[0] = '!'; + + telescopeID = nullptr; + + srand(time(nullptr)); + + buildSkeleton("indi_spectracyber_sk.xml"); + + // Optional: Add aux controls for configuration, debug & simulation + addAuxControls(); + + setVersion(1, 3); +} + +/**************************************************************** +** +** +*****************************************************************/ +void SpectraCyber::ISGetProperties(const char *dev) +{ + static int propInit = 0; + + INDI::DefaultDevice::ISGetProperties(dev); + + if (propInit == 0) + { + loadConfig(); + + propInit = 1; + + auto tProp = getText("ACTIVE_DEVICES"); + + if (tProp) + { + telescopeID = tProp.findWidgetByName("ACTIVE_TELESCOPE"); + + if (telescopeID && strlen(telescopeID->text) > 0) + { + IDSnoopDevice(telescopeID->text, "EQUATORIAL_EOD_COORD"); + } + } + } +} + +/**************************************************************** +** +** +*****************************************************************/ +bool SpectraCyber::ISSnoopDevice(XMLEle *root) +{ + if (IUSnoopNumber(root, &EquatorialCoordsRNP) != 0) + { + LOG_WARN("Error processing snooped EQUATORIAL_EOD_COORD_REQUEST value! No RA/DEC information available."); + + return true; + } + //else + // IDLog("Received RA: %g - DEC: %g\n", EquatorialCoordsRNP.np[0].value, EquatorialCoordsRNP.np[1].value); + + return false; +} + +/**************************************************************** +** +** +*****************************************************************/ +bool SpectraCyber::initProperties() +{ + + INDI::DefaultDevice::initProperties(); + + FreqNP = getNumber("Freq (Mhz)"); + if (!FreqNP) + LOG_ERROR("Error: Frequency property is missing. Spectrometer cannot be operated."); + + ScanNP = getNumber("Scan Parameters"); + if (!ScanNP) + LOG_ERROR("Error: Scan parameters property is missing. Spectrometer cannot be operated."); + + ChannelSP = getSwitch("Channels"); + if (!ChannelSP) + LOG_ERROR("Error: Channel property is missing. Spectrometer cannot be operated."); + + ScanSP = getSwitch("Scan"); + if (!ScanSP) + LOG_ERROR("Error: Channel property is missing. Spectrometer cannot be operated."); + + DataStreamBP = getBLOB("Data"); + if (!DataStreamBP) + LOG_ERROR("Error: BLOB data property is missing. Spectrometer cannot be operated."); + + if (DataStreamBP) + DataStreamBP[0].setBlob((char *)malloc(MAXBLEN * sizeof(char))); + + /**************************************************************************/ + // Equatorial Coords - SET + IUFillNumber(&EquatorialCoordsRN[0], "RA", "RA H:M:S", "%10.6m", 0., 24., 0., 0.); + IUFillNumber(&EquatorialCoordsRN[1], "DEC", "Dec D:M:S", "%10.6m", -90., 90., 0., 0.); + IUFillNumberVector(&EquatorialCoordsRNP, EquatorialCoordsRN, NARRAY(EquatorialCoordsRN), "", "EQUATORIAL_EOD_COORD", + "Equatorial AutoSet", "", IP_RW, 0, IPS_IDLE); + /**************************************************************************/ + + setDriverInterface(SPECTROGRAPH_INTERFACE); + + return true; +} + +/**************************************************************** +** +** +*****************************************************************/ +bool SpectraCyber::Connect() +{ + auto tProp = getText("DEVICE_PORT"); + + if (isConnected()) + return true; + + if (!tProp) + return false; + + if (isSimulation()) + { + LOGF_INFO("%s Spectrometer: Simulating connection to port %s.", type_name.c_str(), + tProp[0].getText()); + SetTimer(getCurrentPollingPeriod()); + return true; + } + + if (tty_connect(tProp[0].getText(), 2400, 8, 0, 1, &fd) != TTY_OK) + { + LOGF_ERROR("Error connecting to port %s. Make sure you have BOTH read and write permission to the port.", + tProp[0].getText()); + return false; + } + + // We perform initial handshake check by resetting all parameter and watching for echo reply + if (reset() == true) + { + LOG_INFO("Spectrometer is online. Retrieving preliminary data..."); + SetTimer(getCurrentPollingPeriod()); + return init_spectrometer(); + } + else + { + DEBUG(INDI::Logger::DBG_ERROR, + "Spectrometer echo test failed. Please recheck connection to spectrometer and try again."); + return false; + } +} + +/**************************************************************** +** +** +*****************************************************************/ +bool SpectraCyber::init_spectrometer() +{ + // Enable speed mode + if (isSimulation()) + { + LOGF_INFO("%s Spectrometer: Simulating spectrometer init.", type_name.c_str()); + return true; + } + + return true; +} + +/**************************************************************** +** +** +*****************************************************************/ +bool SpectraCyber::Disconnect() +{ + tty_disconnect(fd); + + return true; +} + +/**************************************************************** +** +** +*****************************************************************/ +bool SpectraCyber::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) +{ + if (strcmp(dev, getDeviceName()) != 0) + return false; + + static double cont_offset = 0; + static double spec_offset = 0; + + auto nProp = getNumber(name); + + if (!nProp) + return false; + + if (isConnected() == false) + { + resetProperties(); + LOG_ERROR("Spectrometer is offline. Connect before issuing any commands."); + return false; + } + + // IF Gain + if (nProp.isNameMatch("70 Mhz IF")) + { + double last_value = nProp[0].getValue(); + + if (!nProp.update(values, names, n)) + return false; + + if (dispatch_command(IF_GAIN) == false) + { + nProp[0].setValue(last_value); + nProp.setState(IPS_ALERT); + nProp.apply("Error dispatching IF gain command to spectrometer. Check logs."); + return false; + } + + nProp.setState(IPS_OK); + nProp.apply(); + + return true; + } + + // DC Offset + if (nProp.isNameMatch("DC Offset")) + { + if (!nProp.update(values, names, n)) + return false; + + // Check which offset change, if none, return gracefully + if (nProp[CONTINUUM_CHANNEL].getValue() != cont_offset) + { + if (dispatch_command(CONT_OFFSET) == false) + { + nProp[CONTINUUM_CHANNEL].setValue(cont_offset); + nProp.setState(IPS_ALERT); + nProp.apply("Error dispatching continuum DC offset command to spectrometer. Check logs."); + return false; + } + + cont_offset = nProp[CONTINUUM_CHANNEL].getValue(); + } + + if (nProp[SPECTRAL_CHANNEL].getValue() != spec_offset) + { + if (dispatch_command(SPEC_OFFSET) == false) + { + nProp[SPECTRAL_CHANNEL].setValue(spec_offset); + nProp.setState(IPS_ALERT); + nProp.apply("Error dispatching spectral DC offset command to spectrometer. Check logs."); + return false; + } + + spec_offset = nProp[SPECTRAL_CHANNEL].getValue(); + } + + // No Change, return + nProp.setState(IPS_OK); + nProp.apply(); + return true; + } + + // Freq Change + if (nProp.isNameMatch("Freq (Mhz)")) + return update_freq(values[0]); + + // Scan Options + if (nProp.isNameMatch("Scan Parameters")) + { + if (!nProp.update(values, names, n)) + return false; + + nProp.setState(IPS_OK); + nProp.apply(); + return true; + } + return true; +} + +/**************************************************************** +** +** +*****************************************************************/ +bool SpectraCyber::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) +{ + if (strcmp(dev, getDeviceName()) != 0) + return false; + + auto tProp = getText(name); + + if (!tProp) + return false; + + // Device Port Text + if (tProp.isNameMatch("DEVICE_PORT")) + { + if (!tProp.update(texts, names, n)) + return false; + + tProp.setState(IPS_OK); + tProp.apply("Port updated."); + + return true; + } + + // Telescope Source + if (tProp.isNameMatch("ACTIVE_DEVICES")) + { + telescopeID = tProp.findWidgetByName("ACTIVE_TELESCOPE"); + + if (telescopeID && strcmp(texts[0], telescopeID->text)) + { + if (!tProp.update(texts, names, n)) + return false; + + strncpy(EquatorialCoordsRNP.device, tProp[0].getText(), MAXINDIDEVICE); + + LOGF_INFO("Active telescope updated to %s. Please save configuration.", telescopeID->text); + + IDSnoopDevice(telescopeID->text, "EQUATORIAL_EOD_COORD"); + } + + tProp.setState(IPS_OK); + tProp.apply(); + return true; + } + + return true; +} + +/**************************************************************** +** +** +*****************************************************************/ +bool SpectraCyber::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) +{ + if (strcmp(dev, getDeviceName()) != 0) + return false; + + // First process parent! + if (INDI::DefaultDevice::ISNewSwitch(getDeviceName(), name, states, names, n) == true) + return true; + + auto sProp = getSwitch(name); + + if (!sProp) + return false; + + if (isConnected() == false) + { + resetProperties(); + LOG_ERROR("Spectrometer is offline. Connect before issuing any commands."); + return false; + } + + // Scan + if (sProp.isNameMatch("Scan")) + { + if (!FreqNP || !DataStreamBP) + return false; + + if (!sProp.update(states, names, n)) + return false; + + if (sProp[1].getState() == ISS_ON) + { + if (sProp.getState() == IPS_BUSY) + { + sProp.setState(IPS_IDLE); + FreqNP.setState(IPS_IDLE); + DataStreamBP.setState(IPS_IDLE); + + FreqNP.apply(); + DataStreamBP.apply(); + sProp.apply("Scan stopped."); + return false; + } + + sProp.setState(IPS_OK); + sProp.apply(); + return true; + } + + sProp.setState(IPS_BUSY); + DataStreamBP.setState(IPS_BUSY); + + // Compute starting freq = base_freq - low + if (sProp[SPEC_CHANNEL].getState() == ISS_ON) + { + start_freq = (SPECTROMETER_RF_FREQ + SPECTROMETER_REST_FREQ) - abs((int)ScanNP[0].getValue()) / 1000.; + target_freq = (SPECTROMETER_RF_FREQ + SPECTROMETER_REST_FREQ) + abs((int)ScanNP[1].getValue()) / 1000.; + sample_rate = ScanNP[2].getValue() * 5; + FreqNP[0].setValue(start_freq); + FreqNP.setState(IPS_BUSY); + FreqNP.apply(); + sProp.apply("Starting spectral scan from %g MHz to %g MHz in steps of %g KHz...", start_freq, + target_freq, sample_rate); + } + else + sProp.apply("Starting continuum scan @ %g MHz...", FreqNP[0].getValue()); + + return true; + } + + // Continuum Gain Control + if (sProp.isNameMatch("Continuum Gain")) + { + int last_switch = sProp.findOnSwitchIndex(); + + if (!sProp.update(states, names, n)) + return false; + + if (dispatch_command(CONT_GAIN) == false) + { + sProp.setState(IPS_ALERT); + sProp.reset(); + sProp[last_switch].setState(ISS_ON); + sProp.apply("Error dispatching continuum gain command to spectrometer. Check logs."); + return false; + } + + sProp.setState(IPS_OK); + sProp.apply(); + + return true; + } + + // Spectral Gain Control + if (sProp.isNameMatch("Spectral Gain")) + { + int last_switch = sProp.findOnSwitchIndex(); + + if (!sProp.update(states, names, n)) + return false; + + if (dispatch_command(SPEC_GAIN) == false) + { + sProp.setState(IPS_ALERT); + sProp.reset(); + sProp[last_switch].setState(ISS_ON); + sProp.apply("Error dispatching spectral gain command to spectrometer. Check logs."); + return false; + } + + sProp.setState(IPS_OK); + sProp.apply(); + + return true; + } + + // Continuum Integration Control + if (sProp.isNameMatch("Continuum Integration (s)")) + { + int last_switch = sProp.findOnSwitchIndex(); + + if (!sProp.update(states, names, n)) + return false; + + if (dispatch_command(CONT_TIME) == false) + { + sProp.setState(IPS_ALERT); + sProp.reset(); + sProp[last_switch].setState(ISS_ON); + sProp.apply("Error dispatching continuum integration command to spectrometer. Check logs."); + return false; + } + + sProp.setState(IPS_OK); + sProp.apply(); + + return true; + } + + // Spectral Integration Control + if (sProp.isNameMatch("Spectral Integration (s)")) + { + int last_switch = sProp.findOnSwitchIndex(); + + if (!sProp.update(states, names, n)) + return false; + + if (dispatch_command(SPEC_TIME) == false) + { + sProp.setState(IPS_ALERT); + sProp.reset(); + sProp[last_switch].setState(ISS_ON); + sProp.apply("Error dispatching spectral integration command to spectrometer. Check logs."); + return false; + } + + sProp.setState(IPS_OK); + sProp.apply(); + + return true; + } + + // Bandwidth Control + if (sProp.isNameMatch("Bandwidth (Khz)")) + { + int last_switch = sProp.findOnSwitchIndex(); + + if (!sProp.update(states, names, n)) + return false; + + if (dispatch_command(BANDWIDTH) == false) + { + sProp.setState(IPS_ALERT); + sProp.reset(); + sProp[last_switch].setState(ISS_ON); + sProp.apply("Error dispatching bandwidth change command to spectrometer. Check logs."); + return false; + } + + sProp.setState(IPS_OK); + sProp.apply(); + + return true; + } + + // Channel selection + if (sProp.isNameMatch("Channels")) + { + static int lastChannel; + + lastChannel = sProp.findOnSwitchIndex(); + + if (!sProp.update(states, names, n)) + return false; + + sProp.setState(IPS_OK); + if (ScanSP.getState() == IPS_BUSY && lastChannel != sProp.findOnSwitchIndex()) + { + abort_scan(); + sProp.apply("Scan aborted due to change of channel selection."); + } + else + sProp.apply(); + + return true; + } + + // Reset + if (sProp.isNameMatch("Reset")) + { + if (reset() == true) + { + sProp.setState(IPS_OK); + sProp.apply(); + } + else + { + sProp.setState(IPS_ALERT); + sProp.apply("Error dispatching reset parameter command to spectrometer. Check logs."); + return false; + } + return true; + } + + return true; +} + +bool SpectraCyber::dispatch_command(SpectrometerCommand command_type) +{ + char spectrometer_error[SPECTROMETER_ERROR_BUFFER]; + int err_code = 0, nbytes_written = 0, final_value = 0; + // Maximum of 3 hex digits in addition to null terminator + char hex[5]; + + tcflush(fd, TCIOFLUSH); + + switch (command_type) + { + // Intermediate Frequency Gain + case IF_GAIN: { + auto prop = getNumber("70 Mhz IF"); + if (!prop) + return false; + command[1] = 'A'; + command[2] = '0'; + // Equation is + // Value = ((X - 10) * 63) / 15.75, where X is the user selection (10dB to 25.75dB) + final_value = (int)((prop[0].getValue() - 10) * 63) / 15.75; + sprintf(hex, "%02X", (uint16_t)final_value); + command[3] = hex[0]; + command[4] = hex[1]; + break; + } + + // Continuum Gain + case CONT_GAIN: { + auto prop = getSwitch("Continuum Gain"); + if (!prop) + return false; + command[1] = 'G'; + command[2] = '0'; + command[3] = '0'; + final_value = prop.findOnSwitchIndex(); + sprintf(hex, "%d", (uint8_t)final_value); + command[4] = hex[0]; + break; + } + + // Continuum Integration + case CONT_TIME: { + auto prop = getSwitch("Continuum Integration (s)"); + if (!prop) + return false; + command[1] = 'I'; + command[2] = '0'; + command[3] = '0'; + final_value = prop.findOnSwitchIndex(); + sprintf(hex, "%d", (uint8_t)final_value); + command[4] = hex[0]; + break; + } + + // Spectral Gain + case SPEC_GAIN: { + auto prop = getSwitch("Spectral Gain"); + if (!prop) + return false; + command[1] = 'K'; + command[2] = '0'; + command[3] = '0'; + final_value = prop.findOnSwitchIndex(); + sprintf(hex, "%d", (uint8_t)final_value); + command[4] = hex[0]; + break; + } + + // Spectral Integration + case SPEC_TIME: { + auto prop = getSwitch("Spectral Integration (s)"); + if (!prop) + return false; + command[1] = 'L'; + command[2] = '0'; + command[3] = '0'; + final_value = prop.findOnSwitchIndex(); + sprintf(hex, "%d", (uint8_t)final_value); + command[4] = hex[0]; + break; + } + + // Continuum DC Offset + case CONT_OFFSET: { + auto prop = getNumber("DC Offset"); + if (!prop) + return false; + command[1] = 'O'; + final_value = (int)prop[CONTINUUM_CHANNEL].getValue() / 0.001; + sprintf(hex, "%03X", (uint32_t)final_value); + command[2] = hex[0]; + command[3] = hex[1]; + command[4] = hex[2]; + break; + } + + // Spectral DC Offset + case SPEC_OFFSET: { + auto prop = getNumber("DC Offset"); + if (!prop) + return false; + command[1] = 'J'; + final_value = (int)prop[SPECTRAL_CHANNEL].getValue() / 0.001; + sprintf(hex, "%03X", (uint32_t)final_value); + command[2] = hex[0]; + command[3] = hex[1]; + command[4] = hex[2]; + break; + } + + // FREQ + case RECV_FREQ: { + command[1] = 'F'; + // Each value increment is 5 Khz. Range is 050h to 3e8h. + // 050h corresponds to 46.4 Mhz (min), 3e8h to 51.2 Mhz (max) + // To compute the desired received freq, we take the diff (target - min) / 0.005 + // 0.005 Mhz = 5 Khz + // Then we add the diff to 050h (80 in decimal) to get the final freq. + // e.g. To set 50.00 Mhz, diff = 50 - 46.4 = 3.6 / 0.005 = 800 = 320h + // Freq = 320h + 050h (or 800 + 80) = 370h = 880 decimal + + final_value = (int)((FreqNP[0].getValue() + SPECTROMETER_REST_CORRECTION - FreqNP[0].getMin()) / 0.005 + + SPECTROMETER_OFFSET); + sprintf(hex, "%03X", (uint32_t)final_value); + if (isDebug()) + IDLog("Required Freq is: %.3f --- Min Freq is: %.3f --- Spec Offset is: %d -- Final Value (Dec): %d " + "--- Final Value (Hex): %s\n", + FreqNP[0].getValue(), FreqNP[0].getMin(), SPECTROMETER_OFFSET, final_value, hex); + command[2] = hex[0]; + command[3] = hex[1]; + command[4] = hex[2]; + break; + } + + // Read Channel + case READ_CHANNEL: { + command[1] = 'D'; + command[2] = '0'; + command[3] = '0'; + final_value = ChannelSP.findOnSwitchIndex(); + command[4] = (final_value == 0) ? '0' : '1'; + break; + } + + // Bandwidth + case BANDWIDTH: { + auto prop = getSwitch("Bandwidth (Khz)"); + if (!prop) + return false; + command[1] = 'B'; + command[2] = '0'; + command[3] = '0'; + final_value = prop.findOnSwitchIndex(); + //sprintf(hex, "%x", final_value); + command[4] = (final_value == 0) ? '0' : '1'; + break; + } + + // Reset + case RESET: + command[1] = 'R'; + command[2] = '0'; + command[3] = '0'; + command[4] = '0'; + break; + + // Noise source + case NOISE_SOURCE: + // TODO: Do something here? + break; + } + + if (isDebug()) + IDLog("Dispatching command #%s#\n", command); + + if (isSimulation()) + return true; + + if ((err_code = tty_write(fd, command, SPECTROMETER_CMD_LEN, &nbytes_written) != TTY_OK)) + { + tty_error_msg(err_code, spectrometer_error, SPECTROMETER_ERROR_BUFFER); + if (isDebug()) + IDLog("TTY error detected: %s\n", spectrometer_error); + return false; + } + + return true; +} + +int SpectraCyber::get_on_switch(ISwitchVectorProperty *sp) +{ + for (int i = 0; i < sp->nsp; i++) + if (sp->sp[i].s == ISS_ON) + return i; + + return -1; +} + +bool SpectraCyber::update_freq(double nFreq) +{ + double last_value = FreqNP[0].getValue(); + + if (nFreq < FreqNP[0].getMin() || nFreq > FreqNP[0].getMax()) + return false; + + FreqNP[0].setValue(nFreq); + + if (dispatch_command(RECV_FREQ) == false) + { + FreqNP[0].setValue(last_value); + FreqNP.setState(IPS_ALERT); + FreqNP.apply("Error dispatching RECV FREQ command to spectrometer. Check logs."); + return false; + } + + if (ScanSP.getState() != IPS_BUSY) + FreqNP.setState(IPS_OK); + + FreqNP.apply(); + + // Delay 0.5s for INT + usleep(500000); + return true; +} + +bool SpectraCyber::reset() +{ + int err_code = 0, nbytes_read = 0; + char response[4]; + char err_msg[SPECTROMETER_ERROR_BUFFER]; + + if (isDebug()) + IDLog("Attempting to write to spectrometer....\n"); + + dispatch_command(RESET); + + if (isDebug()) + IDLog("Attempting to read from spectrometer....\n"); + + // Read echo from spectrometer, we're expecting R000 + if ((err_code = tty_read(fd, response, SPECTROMETER_CMD_REPLY, 5, &nbytes_read)) != TTY_OK) + { + tty_error_msg(err_code, err_msg, 32); + if (isDebug()) + IDLog("TTY error detected: %s\n", err_msg); + return false; + } + + if (isDebug()) + IDLog("Response from Spectrometer: #%c# #%c# #%c# #%c#\n", response[0], response[1], response[2], response[3]); + + if (strstr(response, "R000")) + { + if (isDebug()) + IDLog("Echo test passed.\n"); + + loadDefaultConfig(); + + return true; + } + + if (isDebug()) + IDLog("Echo test failed.\n"); + return false; +} + +void SpectraCyber::TimerHit() +{ + if (!isConnected()) + return; + + char RAStr[16], DecStr[16]; + + switch (ScanSP.getState()) + { + case IPS_BUSY: + if (ChannelSP[CONT_CHANNEL].getState() == ISS_ON) + break; + + if (FreqNP[0].getValue() >= target_freq) + { + ScanSP.setState(IPS_OK); + FreqNP.setState(IPS_OK); + + FreqNP.apply(); + ScanSP.apply("Scan complete."); + SetTimer(getCurrentPollingPeriod()); + return; + } + + if (update_freq(FreqNP[0].getValue()) == false) + { + abort_scan(); + SetTimer(getCurrentPollingPeriod()); + return; + } + + FreqNP[0].setValue(FreqNP[0].getValue() + sample_rate / 1000.); + break; + + default: + break; + } + + switch (DataStreamBP.getState()) + { + case IPS_BUSY: { + if (ScanSP.getState() != IPS_BUSY) + { + DataStreamBP.setState(IPS_IDLE); + DataStreamBP.apply(); + break; + } + + if (read_channel() == false) + { + DataStreamBP.setState(IPS_ALERT); + + if (ScanSP.getState() == IPS_BUSY) + abort_scan(); + + DataStreamBP.apply(); + } + + JD = ln_get_julian_from_sys(); + + // Continuum + if (ChannelSP[0].getState() == ISS_ON) + DataStreamBP[0].setFormat(contFMT); + else + DataStreamBP[0].setFormat(specFMT); + + fs_sexa(RAStr, EquatorialCoordsRN[0].value, 2, 3600); + fs_sexa(DecStr, EquatorialCoordsRN[1].value, 2, 3600); + + if (telescopeID && strlen(telescopeID->text) > 0) + snprintf(bLine, MAXBLEN, "%.8f %.3f %.3f %s %s", JD, chanValue, FreqNP[0].getValue(), RAStr, DecStr); + else + snprintf(bLine, MAXBLEN, "%.8f %.3f %.3f", JD, chanValue, FreqNP[0].getValue()); + + auto bLineSize = strlen(bLine); + DataStreamBP[0].setBlobLen(bLineSize); + DataStreamBP[0].setSize(bLineSize); + memcpy(DataStreamBP[0].getBlob(), bLine, bLineSize); + + //IDLog("\nSTRLEN: %d -- BLOB:'%s'\n", strlen(bLine), (char *) DataStreamBP->bp[0].blob); + + DataStreamBP.apply(); + + break; + } + default: + break; + } + + SetTimer(getCurrentPollingPeriod()); +} + +void SpectraCyber::abort_scan() +{ + FreqNP.setState(IPS_IDLE); + ScanSP.setState(IPS_ALERT); + + ScanSP.reset(); + ScanSP[1].setState(ISS_ON); + + FreqNP.apply(); + ScanSP.apply("Scan aborted due to errors."); +} + +bool SpectraCyber::read_channel() +{ + int err_code = 0, nbytes_read = 0; + char response[SPECTROMETER_CMD_REPLY]; + char err_msg[SPECTROMETER_ERROR_BUFFER]; + + if (isSimulation()) + { + chanValue = ((double)rand()) / ((double)RAND_MAX) * 10.0; + return true; + } + + dispatch_command(READ_CHANNEL); + if ((err_code = tty_read(fd, response, SPECTROMETER_CMD_REPLY, 5, &nbytes_read)) != TTY_OK) + { + tty_error_msg(err_code, err_msg, 32); + if (isDebug()) + IDLog("TTY error detected: %s\n", err_msg); + return false; + } + + if (isDebug()) + IDLog("Response from Spectrometer: #%s#\n", response); + + int result = 0; + sscanf(response, "D%x", &result); + // We divide by 409.5 to scale the value to 0 - 10 VDC range + chanValue = result / 409.5; + + return true; +} + +const char *SpectraCyber::getDefaultName() +{ + return mydev; +} diff --git a/drivers/spectrograph/spectracyber.h b/drivers/spectrograph/spectracyber.h new file mode 100644 index 0000000000..af35f2cd40 --- /dev/null +++ b/drivers/spectrograph/spectracyber.h @@ -0,0 +1,117 @@ +/* + Kuwait National Radio Observatory + INDI Driver for SpectraCyber Hydrogen Line Spectrometer + Communication: RS232 <---> USB + + Copyright (C) 2009 Jasem Mutlaq (mutlaqja@ikarustech.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + Change Log: + 2009-09-17: Creating spectrometer class (JM) + +*/ + +#pragma once + +#include + +#include + +#define MAXBLEN 64 + +class SpectraCyber : public INDI::DefaultDevice +{ + public: + enum SpectrometerCommand + { + IF_GAIN, // IF Gain + CONT_GAIN, // Continuum Gain + SPEC_GAIN, // Spectral Gain + CONT_TIME, // Continuum Channel Integration Constant + SPEC_TIME, // Spectral Channel Integration Constant + NOISE_SOURCE, // Noise Source Control + CONT_OFFSET, // Continuum DC Offset + SPEC_OFFSET, // Spectral DC Offset + RECV_FREQ, // Receive Frequency + READ_CHANNEL, // Read Channel Value + BANDWIDTH, // Bandwidth + RESET // Reset All + }; + + enum SpectrometerChannel + { + CONTINUUM_CHANNEL, + SPECTRAL_CHANNEL + }; + enum SpectrometerError + { + NO_ERROR, + BAUD_RATE_ERROR, + FLASH_MEMORY_ERROR, + WRONG_COMMAND_ERROR, + WRONG_PARAMETER_ERROR, + FATAL_ERROR + }; + + SpectraCyber(); + + // Standard INDI interface functions + virtual void ISGetProperties(const char *dev) override; + virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override; + virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override; + virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override; + virtual bool ISSnoopDevice(XMLEle *root) override; + + protected: + virtual const char *getDefaultName() override; + + virtual bool Connect() override; + virtual bool Disconnect() override; + virtual void TimerHit() override; + + //void reset_all_properties(bool reset_to_idle=false); + bool update_freq(double nFreq); + + private: + INDI::PropertyNumber FreqNP {INDI::Property()}; + INDI::PropertyNumber ScanNP {INDI::Property()}; + INDI::PropertySwitch ScanSP {INDI::Property()}; + INDI::PropertySwitch ChannelSP {INDI::Property()}; + INDI::PropertyBlob DataStreamBP {INDI::Property()}; + IText *telescopeID; + + // Snooping On + INumber EquatorialCoordsRN[2]; + INumberVectorProperty EquatorialCoordsRNP; + + // Functions + virtual bool initProperties() override; + bool init_spectrometer(); + void abort_scan(); + bool read_channel(); + bool dispatch_command(SpectrometerCommand command); + int get_on_switch(ISwitchVectorProperty *sp); + bool reset(); + + // Variables + std::string type_name; + std::string default_port; + + int fd; + char bLine[MAXBLEN]; + char command[5]; + double start_freq, target_freq, sample_rate, JD, chanValue; +};