From 716f3661a9442d147a23f0574275ff17f103cd08 Mon Sep 17 00:00:00 2001 From: Efstathios Chrysikos Date: Sun, 7 Jul 2024 09:28:51 +0300 Subject: [PATCH] PegasusAstro FalconV2 Driver (#2079) --- drivers.xml | 4 + drivers/rotator/CMakeLists.txt | 8 + drivers/rotator/pegasus_falconv2.cpp | 428 +++++++++++++++++++++++++++ drivers/rotator/pegasus_falconv2.h | 111 +++++++ 4 files changed, 551 insertions(+) create mode 100644 drivers/rotator/pegasus_falconv2.cpp create mode 100644 drivers/rotator/pegasus_falconv2.h diff --git a/drivers.xml b/drivers.xml index 3c7711a63d..6275aac3fd 100644 --- a/drivers.xml +++ b/drivers.xml @@ -437,6 +437,10 @@ indi_falcon_rotator 1.0 + + indi_falconv2_rotator + 1.0 + indi_integra_focus 1.1 diff --git a/drivers/rotator/CMakeLists.txt b/drivers/rotator/CMakeLists.txt index 7a614262dc..f9e2bc0710 100644 --- a/drivers/rotator/CMakeLists.txt +++ b/drivers/rotator/CMakeLists.txt @@ -54,6 +54,14 @@ add_executable(indi_falcon_rotator ${falcon_SRC}) target_link_libraries(indi_falcon_rotator indidriver) install(TARGETS indi_falcon_rotator RUNTIME DESTINATION bin) +# ############### Falcon V2 Rotator ################ +SET(falconv2_SRC + pegasus_falconv2.cpp) + +add_executable(indi_falconv2_rotator ${falconv2_SRC}) +target_link_libraries(indi_falconv2_rotator indidriver) +install(TARGETS indi_falconv2_rotator RUNTIME DESTINATION bin) + # ############### Wanderer Rotator Lite ################ SET(WandererRotatorLite_SRC wanderer_rotator_lite.cpp) diff --git a/drivers/rotator/pegasus_falconv2.cpp b/drivers/rotator/pegasus_falconv2.cpp new file mode 100644 index 0000000000..9174d46372 --- /dev/null +++ b/drivers/rotator/pegasus_falconv2.cpp @@ -0,0 +1,428 @@ +/******************************************************************************* + Copyright(c) 2024 Chrysikos Efstathios. All rights reserved. + + Pegasus FalconV2 Rotator + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program 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 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. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Corrections by T. Schriber 2022 following + 'FalconV2 Rotator Serial Command Language Firmware >=v.1.3 (review Sep 2020)' +*******************************************************************************/ + +#include "pegasus_falconv2.h" +#include "indicom.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// We declare an auto pointer to PegasusFalconV2. +static std::unique_ptr falconv2(new PegasusFalconV2()); + +PegasusFalconV2::PegasusFalconV2() +{ + setVersion(1, 0); + lastStatusData.reserve(7); +} + +bool PegasusFalconV2::initProperties() +{ + INDI::Rotator::initProperties(); + + SetCapability(ROTATOR_CAN_ABORT | + ROTATOR_CAN_REVERSE | + ROTATOR_CAN_SYNC); + + addAuxControls(); + + //////////////////////////////////////////////////////////////////////////// + /// Main Control Panel + //////////////////////////////////////////////////////////////////////////// + // Reload Firmware + ReloadFirmwareSP[0].fill("RELOAD", "Reload", ISS_OFF); + ReloadFirmwareSP.fill(getDeviceName(), "RELOAD_FIRMWARE", "Firmware", MAIN_CONTROL_TAB, + IP_RW, ISR_ATMOST1, + 60, IPS_IDLE); + + // Derotate + DerotateNP[0].fill("INTERVAL", "Interval (ms)", "%.f", 0, 10000, 1000, 0); + DerotateNP.fill(getDeviceName(), "ROTATOR_DEROTATE", "Derotation", MAIN_CONTROL_TAB, IP_RW, + 60, IPS_IDLE); + + // Firmware + FirmwareTP[0].fill("VERSION", "Version", "NA"); + FirmwareTP.fill(getDeviceName(), "FIRMWARE_INFO", "Firmware", MAIN_CONTROL_TAB, IP_RO, 60, + IPS_IDLE); + + return true; +} + +bool PegasusFalconV2::updateProperties() +{ + INDI::Rotator::updateProperties(); + + if (isConnected()) + { + // Main Control + defineProperty(DerotateNP); + defineProperty(FirmwareTP); + defineProperty(ReloadFirmwareSP); + + } + else + { + // Main Control + deleteProperty(DerotateNP); + deleteProperty(FirmwareTP); + deleteProperty(ReloadFirmwareSP); + } + + return true; +} + +const char * PegasusFalconV2::getDefaultName() +{ + return "Pegasus FalconV2"; +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +bool PegasusFalconV2::Handshake() +{ + return getFirmware(); +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +bool PegasusFalconV2::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) +{ + if (dev && !strcmp(dev, getDeviceName())) + { + // De-rotation + if (DerotateNP.isNameMatch(name)) + { + const uint32_t ms = static_cast(values[0]); + if (setDerotation(ms)) + { + DerotateNP[0].setValue(values[0]); + if (values[0] > 0) + LOGF_INFO("De-rotation is enabled and set to 1 step per %u milliseconds.", ms); + else + LOG_INFO("De-rotaiton is disabled."); + DerotateNP.setState(IPS_OK); + } + else + DerotateNP.setState(IPS_ALERT); + DerotateNP.apply(); + return true; + } + } + return Rotator::ISNewNumber(dev, name, values, names, n); +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +bool PegasusFalconV2::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n) +{ + if (dev && !strcmp(dev, getDeviceName())) + { + // ReloadFirmware + if (ReloadFirmwareSP.isNameMatch(name)) + { + ReloadFirmwareSP.setState(reloadFirmware() ? IPS_OK : IPS_ALERT); + ReloadFirmwareSP.apply(); + LOG_INFO("Reloading firmware..."); + return true; + } + } + + return Rotator::ISNewSwitch(dev, name, states, names, n); +} + +////////////////////////////////////////////////////////////////////// +/// move to degrees (Command "MD:nn.nn"; Response "MD:nn.nn") +////////////////////////////////////////////////////////////////////// +IPState PegasusFalconV2::MoveRotator(double angle) +{ + char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}; + snprintf(cmd, DRIVER_LEN, "MD:%.2f", angle); + if (sendCommand(cmd, res)) + { + return (!strncmp(res, cmd, 8) ? IPS_BUSY : IPS_ALERT); + //Restrict length to 8 chars for correct compare + } + + return IPS_ALERT; +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +bool PegasusFalconV2::AbortRotator() +{ + char res[DRIVER_LEN] = {0}; + if (sendCommand("FH", res)) + { + return (!strcmp(res, "FH:1")); + } + + return false; +} + +////////////////////////////////////////////////////////////////////// +/// reverse action ("FN:0" disabled, "FN:1" enabled) +////////////////////////////////////////////////////////////////////// +bool PegasusFalconV2::ReverseRotator(bool enabled) +{ + char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}; + snprintf(cmd, DRIVER_LEN, "FN:%d", enabled ? 1 : 0); + if (sendCommand(cmd, res)) + { + return (!strncmp(res, cmd, 4)); //Restrict length to 4 chars! + } + + return false; +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +bool PegasusFalconV2::SyncRotator(double angle) +{ + char cmd[DRIVER_LEN] = {0}; + snprintf(cmd, DRIVER_LEN, "SD:%.2f", angle); + return sendCommand(cmd, nullptr); +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +bool PegasusFalconV2::reloadFirmware() +{ + return sendCommand("FQ"); +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +bool PegasusFalconV2::setDerotation(uint32_t ms) +{ + char cmd[DRIVER_LEN] = {0}; + snprintf(cmd, DRIVER_LEN, "DR:%d", ms); + return sendCommand(cmd, nullptr); +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +bool PegasusFalconV2::saveConfigItems(FILE * fp) +{ + INDI::Rotator::saveConfigItems(fp); + DerotateNP.save(fp); + return true; +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +void PegasusFalconV2::TimerHit() +{ + if (!isConnected()) + return; + getStatusData(); + SetTimer(getCurrentPollingPeriod()); +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +bool PegasusFalconV2::getFirmware() +{ + char res[DRIVER_LEN] = {0}; + if (sendCommand("FV", res)) + { + FirmwareTP[0].setText(res + 3); + return true; + } + + return false; +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +bool PegasusFalconV2::getStatusData() +{ + char res[DRIVER_LEN] = {0}; + if (sendCommand("FA", res)) + { + std::vector result = split(res, ":"); + if (result.size() != 6) + { + LOG_WARN("Received wrong number of detailed sensor data. Retrying..."); + return false; + } + + if (result == lastStatusData) + return true; + + // Position + const double position = std::stod(result[1]); + // Is running? + const IPState motionState = std::stoi(result[2]) == 1 ? IPS_BUSY : IPS_OK; + + // Update Absolute Position property if either position changes, or status changes. + if (std::abs(position - GotoRotatorN[0].value) > 0.01 || GotoRotatorNP.s != motionState) + { + GotoRotatorN[0].value = position; + GotoRotatorNP.s = motionState; + IDSetNumber(&GotoRotatorNP, nullptr); + } + + + const bool reversed = std::stoi(result[5]) == 1; + const bool wasReversed = ReverseRotatorS[INDI_ENABLED].s == ISS_ON; + if (reversed != wasReversed) + { + ReverseRotatorS[INDI_ENABLED].s = reversed ? ISS_ON : ISS_OFF; + ReverseRotatorS[INDI_DISABLED].s = reversed ? ISS_OFF : ISS_ON; + IDSetSwitch(&ReverseRotatorSP, nullptr); + } + + lastStatusData = result; + return true; + } + + return false; +} + +///////////////////////////////////////////////////////////////////////////// +/// Send Command +///////////////////////////////////////////////////////////////////////////// +bool PegasusFalconV2::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); + + char formatted_command[DRIVER_LEN] = {0}; + snprintf(formatted_command, DRIVER_LEN, "%s\n", cmd); + rc = tty_write_string(PortFD, formatted_command, &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 + 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 \r + res[nbytes_read - 1] = 0; + LOGF_DEBUG("RES <%s>", res); + } + + tcflush(PortFD, TCIOFLUSH); + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +/// +///////////////////////////////////////////////////////////////////////////// +void PegasusFalconV2::hexDump(char * buf, const char * data, uint32_t size) +{ + for (uint32_t i = 0; i < size; i++) + sprintf(buf + 3 * i, "%02X ", static_cast(data[i])); + + if (size > 0) + buf[3 * size - 1] = '\0'; +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +std::vector PegasusFalconV2::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}; +} + +////////////////////////////////////////////////////////////////////// +/// +////////////////////////////////////////////////////////////////////// +void PegasusFalconV2::cleanupResponse(char *response) +{ + std::string s(response); + s.erase(std::remove_if(s.begin(), s.end(), + [](unsigned char x) + { + return std::isspace(x); + }), s.end()); + strncpy(response, s.c_str(), DRIVER_LEN); +} diff --git a/drivers/rotator/pegasus_falconv2.h b/drivers/rotator/pegasus_falconv2.h new file mode 100644 index 0000000000..03d1f25ae9 --- /dev/null +++ b/drivers/rotator/pegasus_falconv2.h @@ -0,0 +1,111 @@ +/******************************************************************************* + Copyright(c) 2024 Chrysikos Efstathios. All rights reserved. + + Pegasus FalconV2 Rotator + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program 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 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. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. +*******************************************************************************/ + +#pragma once + +#include "indirotator.h" +#include + +class PegasusFalconV2 : public INDI::Rotator +{ + public: + PegasusFalconV2(); + + 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; + + protected: + const char *getDefaultName() override; + virtual bool saveConfigItems(FILE *fp) override; + + // Event loop + virtual void TimerHit() override; + + // Rotator Overrides + virtual IPState MoveRotator(double angle) override; + virtual bool ReverseRotator(bool enabled) override; + virtual bool SyncRotator(double angle) override; + virtual bool AbortRotator() override; + + private: + bool Handshake() override; + + /////////////////////////////////////////////////////////////////////////////// + /// Query Functions + /////////////////////////////////////////////////////////////////////////////// + bool getFirmware(); + bool getStatusData(); + + /////////////////////////////////////////////////////////////////////////////// + /// Device Control Functions + /////////////////////////////////////////////////////////////////////////////// + bool reloadFirmware(); + bool setDerotation(uint32_t ms); + + /////////////////////////////////////////////////////////////////////////////// + /// Communication Functions + /////////////////////////////////////////////////////////////////////////////// + /** + * @brief sendCommand Send a string command to device. + * @param cmd Command to be sent. Can be either NULL TERMINATED or just byte buffer. + * @param res If not nullptr, the function will wait for a response from the device. If nullptr, it returns true immediately + * after the command is successfully sent. + * @param cmd_len if -1, it is assumed that the @a cmd is a null-terminated string. Otherwise, it would write @a cmd_len bytes from + * the @a cmd buffer. + * @param res_len if -1 and if @a res is not nullptr, the function will read until it detects the default delimiter DRIVER_STOP_CHAR + * up to DRIVER_LEN length. Otherwise, the function will read @a res_len from the device and store it in @a res. + * @return True if successful, false otherwise. + */ + bool sendCommand(const char * cmd, char * res = nullptr, int cmd_len = -1, int res_len = -1); + void hexDump(char * buf, const char * data, uint32_t size); + std::vector split(const std::string &input, const std::string ®ex); + + /** + * @brief cleanupResponse Removes all spaces + * @param response buffer + */ + void cleanupResponse(char *response); + + //////////////////////////////////////////////////////////////////////////////////// + /// Properties + //////////////////////////////////////////////////////////////////////////////////// + /// Reboot Device + INDI::PropertySwitch ReloadFirmwareSP {1}; + /// Derotation + INDI::PropertyNumber DerotateNP {1}; + /// Firmware + INDI::PropertyText FirmwareTP {1}; + + std::vector lastStatusData; + + ///////////////////////////////////////////////////////////////////////////// + /// Static Helper Values + ///////////////////////////////////////////////////////////////////////////// + static constexpr const uint8_t DRIVER_STOP_CHAR {0xA}; + static constexpr const uint8_t DRIVER_TIMEOUT {3}; + static constexpr const uint8_t DRIVER_LEN {128}; +};