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/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; +};