diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d9281f88..f0ecf95d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,6 +187,7 @@ option(WITH_ATIK "Install Atik Driver" On) option(WITH_TOUPBASE "Install Toupbase Driver" On) option(WITH_DREAMFOCUSER "Install DreamFocuser Driver" On) option(WITH_AVALON "Install Avalon StarGO Driver" On) +option(WITH_AVALONUD "Install AvalonUD Drivers" On) option(WITH_BEEFOCUS "Install Bee Focuser Driver" On) option(WITH_SHELYAK "Install Shelyak Spectrograph Driver" On) option(WITH_SKYWALKER "Install AOK SkyWalker Mount Driver" On) @@ -696,6 +697,11 @@ if (WITH_AVALON) add_subdirectory(indi-avalon) endif(WITH_AVALON) +# AvalonUD +if (WITH_AVALONUD) +add_subdirectory(indi-avalonud) +endif(WITH_AVALONUD) + # Pentax if (WITH_PENTAX) find_package(PENTAX) diff --git a/cmake_modules/FindZMQ.cmake b/cmake_modules/FindZMQ.cmake new file mode 100644 index 000000000..1a4137bab --- /dev/null +++ b/cmake_modules/FindZMQ.cmake @@ -0,0 +1,49 @@ +# - Try to find ZMQ +# Once done this will define +# +# ZMQ_FOUND - system has ZMQ +# ZMQ_INCLUDE_DIR - the ZMQ include directory +# ZMQ_LIBRARIES - Link these to use ZMQ + +if (ZMQ_INCLUDE_DIR AND ZMQ_LIBRARIES) + + # in cache already + set(ZMQ_FOUND TRUE) + message(STATUS "Found libzmq: ${ZMQ_LIBRARIES}") + +else (ZMQ_INCLUDE_DIR AND ZMQ_LIBRARIES) + + find_path(ZMQ_INCLUDE_DIR + NAMES zmq.h + ${_obIncDir} + ${GNUWIN32_DIR}/include + ) + + find_library(ZMQ_LIBRARIES + NAMES zmq + PATHS + ${_obLinkDir} + ${GNUWIN32_DIR}/lib + ) + + + if(ZMQ_INCLUDE_DIR AND ZMQ_LIBRARIES) + set(ZMQ_FOUND TRUE) + else(ZMQ_INCLUDE_DIR AND ZMQ_LIBRARIES) + set(ZMQ_FOUND FALSE) + endif(ZMQ_INCLUDE_DIR AND ZMQ_LIBRARIES) + + + if (ZMQ_FOUND) + if (NOT ZMQ_FIND_QUIETLY) + message(STATUS "Found ZMQ ${ZMQ_LIBRARIES}") + endif (NOT ZMQ_FIND_QUIETLY) + else (ZMQ_FOUND) + if (ZMQ_FIND_REQUIRED) + message(STATUS "ZMQ not found. Please install libzmq development package.") + endif (ZMQ_FIND_REQUIRED) + endif (ZMQ_FOUND) + + mark_as_advanced(ZMQ_INCLUDE_DIR ZMQ_LIBRARIES) + +endif (ZMQ_INCLUDE_DIR AND ZMQ_LIBRARIES) diff --git a/indi-avalonud/AUTHORS b/indi-avalonud/AUTHORS new file mode 100644 index 000000000..78722c044 --- /dev/null +++ b/indi-avalonud/AUTHORS @@ -0,0 +1 @@ +AvalonInstruments SW Team (service AT avalon-instruments DOT com) diff --git a/indi-avalonud/CMakeLists.txt b/indi-avalonud/CMakeLists.txt new file mode 100644 index 000000000..4b6c0a4ae --- /dev/null +++ b/indi-avalonud/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.0) +project(indi-avalonud CXX C) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake_modules/") + +include(GNUInstallDirs) + +set (AVALONUD_VERSION_MAJOR 2) +set (AVALONUD_VERSION_MINOR 2) + +find_package(INDI REQUIRED) +find_package(Threads REQUIRED) +find_package(ZMQ REQUIRED) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/config.h +) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/indi_avalonud.xml.cmake + ${CMAKE_CURRENT_BINARY_DIR}/indi_avalonud.xml +) + +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) +include_directories( ${INDI_INCLUDE_DIR} ) +include_directories( ${Threads_INCLUDE_DIR} ) +include_directories( ${ZMQ_INCLUDE_DIR} ) + +include(CMakeCommon) + +########### AVALONUD TELESCOPE ########### + +add_executable( + indi_avalonud_telescope + ${CMAKE_CURRENT_SOURCE_DIR}/indi_avalonud_telescope.cpp +) +target_link_libraries(indi_avalonud_telescope ${INDI_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${ZMQ_LIBRARIES}) + +install(TARGETS indi_avalonud_telescope RUNTIME DESTINATION bin) + +########### AVALONUD FOCUSER ########### + +add_executable( + indi_avalonud_focuser + ${CMAKE_CURRENT_SOURCE_DIR}/indi_avalonud_focuser.cpp +) +target_link_libraries(indi_avalonud_focuser ${INDI_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${ZMQ_LIBRARIES}) + +install(TARGETS indi_avalonud_focuser RUNTIME DESTINATION bin) + +########### AVALONUD AUX ########### + +add_executable( + indi_avalonud_aux + ${CMAKE_CURRENT_SOURCE_DIR}/indi_avalonud_aux.cpp +) +target_link_libraries(indi_avalonud_aux ${INDI_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${ZMQ_LIBRARIES}) + +install(TARGETS indi_avalonud_aux RUNTIME DESTINATION bin) + +#################################### + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/indi_avalonud.xml DESTINATION ${INDI_DATA_DIR}) diff --git a/indi-avalonud/ChangeLog b/indi-avalonud/ChangeLog new file mode 100644 index 000000000..290d23f35 --- /dev/null +++ b/indi-avalonud/ChangeLog @@ -0,0 +1,3 @@ +ChangeLog: + +[2.2] Nov 2023 Initial Release diff --git a/indi-avalonud/INSTALL b/indi-avalonud/INSTALL new file mode 100644 index 000000000..8c19c5893 --- /dev/null +++ b/indi-avalonud/INSTALL @@ -0,0 +1,10 @@ +Avalon Unified Driver INSTALL +============================= + +1) $ tar -xzf avalonud.tar.gz +2) $ mkdir indi-avalonud_build +3) $ cd indi-avalonud_build +4) $ cmake -DCMAKE_INSTALL_PREFIX=/usr . ../indi-avalonud +5) $ sudo make install + +Refer to README for instructions on using the driver. diff --git a/indi-avalonud/README b/indi-avalonud/README new file mode 100644 index 000000000..9522971c3 --- /dev/null +++ b/indi-avalonud/README @@ -0,0 +1,42 @@ +Avalon Unified Drivers +====================== + +This package provides the INDI driver for mounts equipped with AvalonInstruments StarGO+ and StarGO2 controllers + +Requirements +============ + ++ libindi1 >= v1.9.6 + + You need to install both libindi1 and libindi-dev to build this package. + + ++ libzmq >= v4.3.1 + + You need to install libzmq3-dev to build this package. + +Installation +============ + + See INSTALL + +How to Use +========== + + You can use the AvalonUD Drivers in any INDI-compatible client such as KStars or Stellarium. + + To run the driver for the telescope from the command line: + + $ indiserver indi_avalonud_telescope + + or to start also the drivers for the focuser and the auxiliaries: + + $ indiserver indi_avalonud_telescope indi_avalonud_focuser indi_avalonud_aux + + You can then connect to the drivers from any client. + If you're using KStars, the drivers will be automatically listed in KStars' Device Manager + and the only configuration required is to enter the controller IP address in the Connection tab + of each driver before connecting. + + The drivers sonnects to ports 5450 and 5451 on the controller. + diff --git a/indi-avalonud/config.h.cmake b/indi-avalonud/config.h.cmake new file mode 100644 index 000000000..412f34fd0 --- /dev/null +++ b/indi-avalonud/config.h.cmake @@ -0,0 +1,10 @@ +#ifndef CONFIG_H +#define CONFIG_H + +/* Define INDI Data Dir */ +#cmakedefine INDI_DATA_DIR "@INDI_DATA_DIR@" +/* Define Driver version */ +#define AVALONUD_VERSION_MAJOR @AVALONUD_VERSION_MAJOR@ +#define AVALONUD_VERSION_MINOR @AVALONUD_VERSION_MINOR@ + +#endif // CONFIG_H diff --git a/indi-avalonud/indi-avalonud.spec b/indi-avalonud/indi-avalonud.spec new file mode 100644 index 000000000..375dd4e7d --- /dev/null +++ b/indi-avalonud/indi-avalonud.spec @@ -0,0 +1,83 @@ +%define __cmake_in_source_build %{_vpath_builddir} +Name: indi-avalonud +Version:2.0.2.git +Release: %(date -u +%%Y%%m%%d%%H%%M%%S)%{?dist} +Summary: Instrument Neutral Distributed Interface 3rd party drivers + +License: LGPLv2 +# See COPYRIGHT file for a description of the licenses and files covered + +URL: https://indilib.org +Source0: https://github.com/indilib/indi-3rdparty/archive/master.tar.gz + +BuildRequires: cmake +BuildRequires: libfli-devel +BuildRequires: libnova-devel +BuildRequires: qt5-qtbase-devel +BuildRequires: systemd +BuildRequires: gphoto2-devel +BuildRequires: LibRaw-devel +BuildRequires: indi-libs +BuildRequires: indi-devel +BuildRequires: libtiff-devel +BuildRequires: cfitsio-devel +BuildRequires: zlib-devel +BuildRequires: gsl-devel +BuildRequires: libcurl-devel +BuildRequires: libjpeg-turbo-devel +BuildRequires: fftw-devel +BuildRequires: libftdi-devel +BuildRequires: gpsd-devel +BuildRequires: libdc1394-devel +BuildRequires: boost-devel +BuildRequires: boost-regex +BuildRequires: libasi + +BuildRequires: gmock + +BuildRequires: pkgconfig(fftw3) +BuildRequires: pkgconfig(cfitsio) +BuildRequires: pkgconfig(libcurl) +BuildRequires: pkgconfig(gsl) +BuildRequires: pkgconfig(libjpeg) +BuildRequires: pkgconfig(libusb-1.0) +BuildRequires: pkgconfig(zlib) + +BuildRequires: libzmq-devel +Requires: libzmq + + +%description +INDI is a distributed control protocol designed to operate +astronomical instrumentation. INDI is small, flexible, easy to parse, +and scalable. It supports common DCS functions such as remote control, +data acquisition, monitoring, and a lot more. This is a 3rd party driver. + + +%prep -v +%autosetup -v -p1 -n indi-3rdparty-master + +%build +# This package tries to mix and match PIE and PIC which is wrong and will +# trigger link errors when LTO is enabled. +# Disable LTO +%define _lto_cflags %{nil} + +cd indi-avalonud +%cmake -DINDI_DATA_DIR=/usr/share/indi . +make VERBOSE=1 %{?_smp_mflags} -j4 + +%install +cd indi-avalonud +make DESTDIR=%{buildroot} install + +%files +%doc indi-apogee/AUTHORS indi-apogee/README +%{_bindir}/* +%{_datadir}/indi + + +%changelog +* Sun Jul 19 2020 Jim Howard 1.8.7.git-1 +- update to build from git for copr, credit to Sergio Pascual and Christian Dersch for prior work on spec files + diff --git a/indi-avalonud/indi_avalonud.xml.cmake b/indi-avalonud/indi_avalonud.xml.cmake new file mode 100644 index 000000000..6a7d34bef --- /dev/null +++ b/indi-avalonud/indi_avalonud.xml.cmake @@ -0,0 +1,21 @@ + + + + + indi_avalonud_telescope + @AVALONUD_VERSION_MAJOR@.@AVALONUD_VERSION_MINOR@ + + + + + indi_avalonud_focuser + @AVALONUD_VERSION_MAJOR@.@AVALONUD_VERSION_MINOR@ + + + + + indi_avalonud_aux + @AVALONUD_VERSION_MAJOR@.@AVALONUD_VERSION_MINOR@ + + + diff --git a/indi-avalonud/indi_avalonud_aux.cpp b/indi-avalonud/indi_avalonud_aux.cpp new file mode 100644 index 000000000..751c5f862 --- /dev/null +++ b/indi-avalonud/indi_avalonud_aux.cpp @@ -0,0 +1,822 @@ +/* + Avalon Unified Driver Aux + + Copyright (C) 2020,2023 + + 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 "config.h" + +#include "indicom.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "indi_avalonud_aux.h" + + +using json = nlohmann::json; + +const int IPport = 5450; + +static char device_str[MAXINDIDEVICE] = "AvalonUD AUX"; + +const char *STATUS_TAB = "Status"; + + +static std::unique_ptr aux(new AUDAUX()); + + +void ISGetProperties(const char *dev) +{ + aux->ISGetProperties(dev); +} + +void ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) +{ + aux->ISNewSwitch(dev, name, states, names, n); +} + +void ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) +{ + aux->ISNewText(dev, name, texts, names, n); +} + +void ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) +{ + aux->ISNewNumber(dev, name, values, names, n); +} + +void ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], + char *names[], int n) +{ + INDI_UNUSED(dev); + INDI_UNUSED(name); + INDI_UNUSED(sizes); + INDI_UNUSED(blobsizes); + INDI_UNUSED(blobs); + INDI_UNUSED(formats); + INDI_UNUSED(names); + INDI_UNUSED(n); +} + +void ISSnoopDevice(XMLEle *root) +{ + aux->ISSnoopDevice(root); +} + +AUDAUX::AUDAUX() +{ + setVersion(AVALONUD_VERSION_MAJOR,AVALONUD_VERSION_MINOR); + + features = 0; + + context = zmq_ctx_new(); +} + +AUDAUX::~AUDAUX() +{ +// zmq_ctx_term(context); +} + +bool AUDAUX::initProperties() +{ + INDI::DefaultDevice::initProperties(); + + ConfigTP[0].fill("ADDRESS", "Address", "127.0.0.1"); + ConfigTP.fill(getDeviceName(), "DEVICE_ADDRESS", "Server", CONNECTION_TAB, IP_RW, 60, IPS_IDLE); + + HWTypeTP[0].fill("HW_TYPE", "Controller Type", ""); + HWTypeTP.fill(getDeviceName(), "HW_TYPE_INFO", "Type", INFO_TAB, IP_RO, 60, IPS_IDLE); + + HWIdentifierTP[0].fill("HW_IDENTIFIER", "HW Identifier", ""); + HWIdentifierTP.fill(getDeviceName(), "HW_IDENTIFIER_INFO", "Identifier", INFO_TAB, IP_RO, 60, IPS_IDLE); + + LowLevelSWTP[LLSW_NAME].fill("LLSW_NAME", "Name", ""); + LowLevelSWTP[LLSW_VERSION].fill("LLSW_VERSION", "Version", "--"); + LowLevelSWTP.fill(getDeviceName(), "LLSW_INFO", "LowLevel SW", INFO_TAB, IP_RO, 60, IPS_IDLE); + + SystemManagementSP[SMNT_SHUTDOWN].fill("SHUTDOWN","Shutdown",ISS_OFF); + SystemManagementSP[SMNT_REBOOT].fill("REBOOT","Reboot",ISS_OFF); + SystemManagementSP.fill(getDeviceName(), "SYSTEM_MANAGEMENT", "System Mngm", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE); + + OUTPort1SP[POWER_ON].fill("POWER_ON","On",ISS_OFF); + OUTPort1SP[POWER_OFF].fill("POWER_OFF","Off",ISS_OFF); + OUTPort1SP.fill(getDeviceName(), "OUT_PORT1", "OUT Port #1", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_OK); + + OUTPort2SP[POWER_ON].fill("POWER_ON","On",ISS_OFF); + OUTPort2SP[POWER_OFF].fill("POWER_OFF","Off",ISS_OFF); + OUTPort2SP.fill(getDeviceName(), "OUT_PORT2", "OUT Port #2", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_OK); + + OUTPortPWMDUTYCYCLENP[0].fill("DUTYCYCLE", "Output [%]", "%.f", 40.0, 100.0, 1.0, 50.0); + OUTPortPWMDUTYCYCLENP.fill(getDeviceName(), "OUT_PORTPWM_DUTYCYCLE", "OUT Port PWM", MAIN_CONTROL_TAB, IP_RW, 60, IPS_OK); + + OUTPortPWMSP[POWER_ON].fill("POWER_ON","On",ISS_OFF); + OUTPortPWMSP[POWER_OFF].fill("POWER_OFF","Off",ISS_OFF); + OUTPortPWMSP.fill(getDeviceName(), "OUT_PORTPWM", "OUT Port PWM", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_OK); + + USBPort1SP[POWER_ON].fill("POWER_ON","On",ISS_OFF); + USBPort1SP[POWER_OFF].fill("POWER_OFF","Off",ISS_OFF); + USBPort1SP.fill(getDeviceName(), "USB_PORT1", "USB3 Port #1", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_OK); + + USBPort2SP[POWER_ON].fill("POWER_ON","On",ISS_OFF); + USBPort2SP[POWER_OFF].fill("POWER_OFF","Off",ISS_OFF); + USBPort2SP.fill(getDeviceName(), "USB_PORT2", "USB3 Port #2", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_OK); + + USBPort3SP[POWER_ON].fill("POWER_ON","On",ISS_OFF); + USBPort3SP[POWER_OFF].fill("POWER_OFF","Off",ISS_OFF); + USBPort3SP.fill(getDeviceName(), "USB_PORT3", "USB2 Port #3", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_OK); + + USBPort4SP[POWER_ON].fill("POWER_ON","On",ISS_OFF); + USBPort4SP[POWER_OFF].fill("POWER_OFF","Off",ISS_OFF); + USBPort4SP.fill(getDeviceName(), "USB_PORT4", "USB2 Port #4", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_OK); + + PSUNP[PSU_VOLTAGE].fill("VOLTAGE","Voltage (V)","%.2f",0,0,0,0); + PSUNP[PSU_CURRENT].fill("CURRENT","Current (A)","%.2f",0,0,0,0); + PSUNP[PSU_POWER].fill("POWER","Power (W)","%.2f",0,0,0,0); + PSUNP[PSU_CHARGE].fill("CHARGE","Charge (Ah)","%.3f",0,0,0,0); + PSUNP.fill(getDeviceName(), "PSU","Power Supply", STATUS_TAB, IP_RO, 60, IPS_IDLE); + + SMNP[SM_FEEDTIME].fill("FEEDTIME","Feed Time (%)","%.1f",0,0,0,0); + SMNP[SM_BUFFERLOAD].fill("BUFFERLOAD","Buffer Load (%)","%.1f",0,0,0,0); + SMNP[SM_UPTIME].fill("UPTIME","Up Time (s)","%.0f",0,0,0,0); + SMNP.fill(getDeviceName(), "STEPMACHINE","stepMachine", STATUS_TAB, IP_RO, 60, IPS_IDLE); + + CPUNP[0].fill("TEMPERATURE","Temperature (Cel)","%.1f",0,0,0,0); + CPUNP.fill(getDeviceName(), "CPU","CPU", STATUS_TAB, IP_RO, 60, IPS_IDLE); + + addDebugControl(); + setDefaultPollingPeriod(5000); + addPollPeriodControl(); + + pthread_mutex_init( &connectionmutex, NULL ); + + return true; +} + +void AUDAUX::ISGetProperties(const char *dev) +{ + INDI::DefaultDevice::ISGetProperties(dev); + + defineProperty(ConfigTP); + loadConfig(true,ConfigTP.getName()); +} + +bool AUDAUX::updateProperties() +{ + if (isConnected()) + { + readStatus(); + } + + INDI::DefaultDevice::updateProperties(); + + if (isConnected()) + { + // Settings + defineProperty(SystemManagementSP); + if ( features & 0x0010 ) { + defineProperty(OUTPort1SP); + } + if ( features & 0x0020 ) { + defineProperty(OUTPort2SP); + } + if ( features & 0x0040 ) { + defineProperty(OUTPortPWMDUTYCYCLENP); + defineProperty(OUTPortPWMSP); + } + defineProperty(USBPort1SP); + defineProperty(USBPort2SP); + defineProperty(USBPort3SP); + defineProperty(USBPort4SP); + defineProperty(HWTypeTP); + defineProperty(HWIdentifierTP); + defineProperty(LowLevelSWTP); + if ( features & 0x0004 ) { + defineProperty(PSUNP); + } + defineProperty(SMNP); + defineProperty(CPUNP); + + LOG_INFO("AUX is ready"); + } + else + { + deleteProperty(SystemManagementSP); + if ( features & 0x0010 ) { + deleteProperty(OUTPort1SP); + } + if ( features & 0x0020 ) { + deleteProperty(OUTPort2SP); + } + if ( features & 0x0040 ) { + deleteProperty(OUTPortPWMDUTYCYCLENP); + deleteProperty(OUTPortPWMSP); + } + deleteProperty(USBPort1SP); + deleteProperty(USBPort2SP); + deleteProperty(USBPort3SP); + deleteProperty(USBPort4SP); + deleteProperty(HWTypeTP); + deleteProperty(HWIdentifierTP); + deleteProperty(LowLevelSWTP); + if ( features & 0x0004 ) { + deleteProperty(PSUNP); + } + deleteProperty(SMNP); + deleteProperty(CPUNP); + } + + return true; +} + +bool AUDAUX::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) +{ + if (!strcmp(dev,getDeviceName())) + { + // TCP Server settings + if (ConfigTP.isNameMatch(name)) + { + if ( isConnected() && strcmp(IPaddress,texts[0]) ) { + DEBUG(INDI::Logger::DBG_WARNING, "Please Disconnect before changing IP address"); + return false; + } + ConfigTP.update(texts, names, n); + ConfigTP.setState(IPS_OK); + ConfigTP.apply(); + return true; + } + } + + return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n); +} + +bool AUDAUX::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n) +{ + char *answer; + + if (dev != nullptr && strcmp(dev, getDeviceName()) == 0) + { + // Shutdown/Reboot + if (SystemManagementSP.isNameMatch(name)) + { + SystemManagementSP.update(states, names, n); + int index = SystemManagementSP.findOnSwitchIndex(); + + SystemManagementSP.setState(IPS_BUSY); + SystemManagementSP.reset(); + SystemManagementSP.apply(); + + if ( isConnected() ) + { + switch (index) { + case SMNT_SHUTDOWN : + if ( time(NULL) - shutdown_time <= 10 ) { + sendCommand("SHUTDOWN"); + SystemManagementSP.setState(IPS_ALERT); + } else { + DEBUG(INDI::Logger::DBG_WARNING, "Are you sure you want to shutdown?"); + DEBUG(INDI::Logger::DBG_WARNING, "After shutdown only power cycling could restart the controller!"); + DEBUG(INDI::Logger::DBG_WARNING, "To proceed press again within 10 seconds..."); + SystemManagementSP.setState(IPS_BUSY); + } + reboot_time = 0; + shutdown_time = time(NULL); + break; + case SMNT_REBOOT : + if ( time(NULL) - reboot_time <= 10 ) { + sendCommand("REBOOT"); + SystemManagementSP.setState(IPS_ALERT); + } else { + DEBUG(INDI::Logger::DBG_WARNING, "Are you sure you want to reboot?"); + DEBUG(INDI::Logger::DBG_WARNING, "To proceed press again within 10 seconds..."); + SystemManagementSP.setState(IPS_BUSY); + } + reboot_time = time(NULL); + shutdown_time = 0; + break; + } + } + SystemManagementSP.apply(); + return true; + } + + // AUX Ports power + if (OUTPort1SP.isNameMatch(name)) + { + OUTPort1SP.update(states, names, n); + int index = OUTPort1SP.findOnSwitchIndex(); + + if ( index != -1 ) { + if ( isConnected() ) { + OUTPort1SP.setState(IPS_BUSY); + OUTPort1SP.apply(); + answer = sendCommand("SETPARAM POWER_PORT_OUT1 %d", (1-index) ); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION, "Port OUT1 switch completed"); + OUTPort1SP.setState(IPS_OK); + } else { + DEBUGF(INDI::Logger::DBG_WARNING, "Port OUT1 switch failed due to %s",answer); + free(answer); + OUTPort1SP.setState(IPS_ALERT); + } + OUTPort1SP.apply(); + } + } + return true; + } + if (OUTPort2SP.isNameMatch(name)) + { + OUTPort2SP.update(states, names, n); + int index = OUTPort2SP.findOnSwitchIndex(); + + if ( index != -1 ) { + if ( isConnected() ) { + OUTPort2SP.setState(IPS_BUSY); + OUTPort2SP.apply(); + answer = sendCommand("SETPARAM POWER_PORT_OUT2 %d", (1-index) ); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION, "Port OUT2 switch completed"); + OUTPort2SP.setState(IPS_OK); + } else { + DEBUGF(INDI::Logger::DBG_WARNING, "Port OUT2 switch failed due to %s",answer); + free(answer); + OUTPort2SP.setState(IPS_ALERT); + } + OUTPort2SP.apply(); + } + } + return true; + } + if (OUTPortPWMSP.isNameMatch(name)) + { + OUTPortPWMSP.update(states, names, n); + int index = OUTPortPWMSP.findOnSwitchIndex(); + + if ( index != -1 ) { + if ( isConnected() ) { + OUTPortPWMSP.setState(IPS_BUSY); + OUTPortPWMSP.apply(); + answer = sendCommand("SETPARAM POWER_PORT_OUTPWM %d", (1-index) ); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION, "Port OUTPWM switch completed"); + OUTPortPWMSP.setState(IPS_OK); + } else { + DEBUGF(INDI::Logger::DBG_WARNING, "Port OUTPWM switch failed due to %s",answer); + free(answer); + OUTPortPWMSP.setState(IPS_ALERT); + } + OUTPortPWMSP.apply(); + } + } + return true; + } + + // USB Ports power + if (USBPort1SP.isNameMatch(name)) + { + USBPort1SP.update(states, names, n); + int index = USBPort1SP.findOnSwitchIndex(); + + if ( index != -1 ) { + if ( isConnected() ) { + USBPort1SP.setState(IPS_BUSY); + USBPort1SP.apply(); + answer = sendCommand("SETPARAM POWER_PORT_USB1 %d", (1-index) ); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION, "Port USB #1 switch completed"); + USBPort1SP.setState(IPS_OK); + } else { + DEBUGF(INDI::Logger::DBG_WARNING, "Port USB #1 switch failed due to %s",answer); + free(answer); + USBPort1SP.setState(IPS_ALERT); + } + USBPort1SP.apply(); + } + } + return true; + } + if (USBPort2SP.isNameMatch(name)) + { + USBPort2SP.update(states, names, n); + int index = USBPort2SP.findOnSwitchIndex(); + + if ( index != -1 ) { + if ( isConnected() ) { + USBPort2SP.setState(IPS_BUSY); + USBPort2SP.apply(); + answer = sendCommand("SETPARAM POWER_PORT_USB2 %d", (1-index) ); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION, "Port USB #2 switch completed"); + USBPort2SP.setState(IPS_OK); + } else { + DEBUGF(INDI::Logger::DBG_WARNING, "Port USB #2 switch failed due to %s",answer); + free(answer); + USBPort2SP.setState(IPS_ALERT); + } + USBPort2SP.apply(); + } + } + return true; + } + if (USBPort3SP.isNameMatch(name)) + { + USBPort3SP.update(states, names, n); + int index = USBPort3SP.findOnSwitchIndex(); + + if ( index != -1 ) { + if ( isConnected() ) { + USBPort3SP.setState(IPS_BUSY); + USBPort3SP.apply(); + answer = sendCommand("SETPARAM POWER_PORT_USB3 %d", (1-index) ); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION, "Port USB #3 switch completed"); + USBPort3SP.setState(IPS_OK); + } else { + DEBUGF(INDI::Logger::DBG_WARNING, "Port USB #3 switch failed due to %s",answer); + free(answer); + USBPort3SP.setState(IPS_ALERT); + } + USBPort3SP.apply(); + } + } + return true; + } + if (USBPort4SP.isNameMatch(name)) + { + USBPort4SP.update(states, names, n); + int index = USBPort4SP.findOnSwitchIndex(); + + if ( index != -1 ) { + if ( isConnected() ) { + USBPort4SP.setState(IPS_BUSY); + USBPort4SP.apply(); + answer = sendCommand("SETPARAM POWER_PORT_USB4 %d", (1-index) ); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION, "Port USB #4 switch completed"); + USBPort4SP.setState(IPS_OK); + } else { + DEBUGF(INDI::Logger::DBG_WARNING, "Port USB #4 switch failed due to %s",answer); + free(answer); + USBPort4SP.setState(IPS_ALERT); + } + USBPort4SP.apply(); + } + } + return true; + } + + // this is executed at any command except reboot and shutdown + reboot_time = 0; + shutdown_time = 0; + } + + return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n); +} + +bool AUDAUX::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n) +{ + // first check if it's for our device + if (dev != nullptr && strcmp(dev, getDeviceName()) == 0) + { + if (OUTPortPWMDUTYCYCLENP.isNameMatch(name)) + { + OUTPortPWMDUTYCYCLENP.update(values, names, n); + + OUTPortPWMDUTYCYCLENP.setState(IPS_BUSY); + if ( isConnected() ) + sendCommand("SETPARAM POWER_PORT_OUTPWM_DUTYCYCLE %.0f", OUTPortPWMDUTYCYCLENP[0].value/100.0*255.0 ); + OUTPortPWMDUTYCYCLENP.setState(IPS_OK); + + OUTPortPWMDUTYCYCLENP.apply(); + return true; + } + } + + return DefaultDevice::ISNewNumber(dev, name, values, names, n); +} + + +bool AUDAUX::Connect() +{ + char addr[1024], *answer; + int timeout = 500; + + if (isConnected()) + return true; + + IPaddress = strdup(ConfigTP[0].text); + + DEBUGF(INDI::Logger::DBG_SESSION, "Attempting to connect %s aux...",IPaddress); + + requester = zmq_socket(context, ZMQ_REQ); + zmq_setsockopt(requester, ZMQ_RCVTIMEO, &timeout, sizeof(timeout) ); + snprintf( addr, sizeof(addr), "tcp://%s:%d", IPaddress, IPport ); + zmq_connect(requester, addr); + + answer = sendRequest("DISCOVER"); + if ( answer ) { + if ( !strcmp(answer,"stepMachine") ) { + free(answer); + answer = sendRequest("INFOALL"); + if ( answer ) { + json j; + std::string sHWt,sHWi,sFWv; + + j = json::parse(answer,nullptr,false); + free(answer); + if ( j.is_discarded() || + !j.contains("HWType") || + !j.contains("HWFeatures") || + !j.contains("HWIdentifier") || + !j.contains("firmwareVersion") ) + { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Communication with %s AUX failed",IPaddress); + free(IPaddress); + return false; + } + + j["HWType"].get_to(sHWt); + HWTypeTP[0].setText(sHWt); + HWTypeTP.apply(); + j["HWFeatures"].get_to(features); + j["HWIdentifier"].get_to(sHWi); + HWIdentifierTP[0].setText(sHWi); + HWIdentifierTP.apply(); + LowLevelSWTP[LLSW_NAME].setText("stepMachine"); + j["firmwareVersion"].get_to(sFWv); + LowLevelSWTP[LLSW_VERSION].setText(sFWv); + LowLevelSWTP.apply(); + } + if ( !(features & 0x0074) ) { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "AUX features not supported by %s hardware",IPaddress); + free(IPaddress); + return false; + } + } else { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Failed to connect %s aux",IPaddress); + free(answer); + free(IPaddress); + return false; + } + } else { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Failed to connect %s aux",IPaddress); + free(IPaddress); + return false; + } + + tid = SetTimer(getCurrentPollingPeriod()); + + DEBUGF(INDI::Logger::DBG_SESSION, "Successfully connected %s aux",IPaddress); + return true; +} + +bool AUDAUX::Disconnect() +{ + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Disconnect called before driver connection"); + return true; + } + + DEBUG(INDI::Logger::DBG_SESSION, "Attempting to disconnect aux..."); + + zmq_close(requester); + + RemoveTimer( tid ); + + free(IPaddress); + + DEBUG(INDI::Logger::DBG_SESSION, "Successfully disconnected aux"); + + return true; +} + +void AUDAUX::TimerHit() +{ + if (isConnected() == false) + return; + + // Read the current status + readStatus(); + + if ( SystemManagementSP.getState() == IPS_BUSY ) { + if ( ( time(NULL) - reboot_time > 10 ) && ( time(NULL) - shutdown_time > 10 ) ) { + SystemManagementSP.setState(IPS_OK); + SystemManagementSP.apply(); + DEBUG(INDI::Logger::DBG_SESSION, "Reboot/Shutdown command cleared"); + } + } + + SetTimer(getCurrentPollingPeriod()); +} + +bool AUDAUX::readStatus() +{ + char *answer; + + answer = sendRequest("HOUSEKEEPINGS"); + if ( answer ) { + json j; + int value; + + j = json::parse(answer,nullptr,false); + free(answer); + + if ( j.is_discarded() ) + return false; + + if ( features & 0x0004 ) { + if ( j.contains("voltage_V") ) + j["voltage_V"].get_to(PSUNP[PSU_VOLTAGE].value); + if ( j.contains("current_A") ) + j["current_A"].get_to(PSUNP[PSU_CURRENT].value); + if ( j.contains("power_W") ) + j["power_W"].get_to(PSUNP[PSU_POWER].value); + if ( j.contains("charge_Ah") ) + j["charge_Ah"].get_to(PSUNP[PSU_CHARGE].value); + PSUNP.apply(); + } + if ( j.contains("feedtime_perc") ) + j["feedtime_perc"].get_to(SMNP[SM_FEEDTIME].value); + if ( j.contains("bufferload_perc") ) + j["bufferload_perc"].get_to(SMNP[SM_BUFFERLOAD].value); + if ( j.contains("uptime_sec") ) + j["uptime_sec"].get_to(SMNP[SM_UPTIME].value); + SMNP.apply(); + if ( j.contains("cputemp_celsius") ) + j["cputemp_celsius"].get_to(CPUNP[0].value); + CPUNP.apply(); + + if ( features & 0x0010 ) { + if ( j.contains("POWER_PORT_OUT1") ) { + j["POWER_PORT_OUT1"].get_to(value); + OUTPort1SP[POWER_ON].setState((value?ISS_ON:ISS_OFF)); + OUTPort1SP[POWER_OFF].setState((value?ISS_OFF:ISS_ON)); + OUTPort1SP.apply(); + } + } + if ( features & 0x0020 ) { + if ( j.contains("POWER_PORT_OUT2") ) { + j["POWER_PORT_OUT2"].get_to(value); + OUTPort2SP[POWER_ON].setState((value?ISS_ON:ISS_OFF)); + OUTPort2SP[POWER_OFF].setState((value?ISS_OFF:ISS_ON)); + OUTPort2SP.apply(); + } + } + if ( features & 0x0040 ) { + if ( j.contains("POWER_PORT_OUTPWM_DUTYCYCLE") ) { + j["POWER_PORT_OUTPWM_DUTYCYCLE"].get_to(OUTPortPWMDUTYCYCLENP[0].value); + OUTPortPWMDUTYCYCLENP[0].value *= 100.0 / 255.0; + OUTPortPWMDUTYCYCLENP.apply(); + } + if ( j.contains("POWER_PORT_OUTPWM") ) { + j["POWER_PORT_OUTPWM"].get_to(value); + OUTPortPWMSP[POWER_ON].setState((value?ISS_ON:ISS_OFF)); + OUTPortPWMSP[POWER_OFF].setState((value?ISS_OFF:ISS_ON)); + OUTPortPWMSP.apply(); + } + } + + if ( j.contains("POWER_PORT_USB1") ) { + j["POWER_PORT_USB1"].get_to(value); + USBPort1SP[POWER_ON].setState((value?ISS_ON:ISS_OFF)); + USBPort1SP[POWER_OFF].setState((value?ISS_OFF:ISS_ON)); + USBPort1SP.apply(); + } + if ( j.contains("POWER_PORT_USB2") ) { + j["POWER_PORT_USB2"].get_to(value); + USBPort2SP[POWER_ON].setState((value?ISS_ON:ISS_OFF)); + USBPort2SP[POWER_OFF].setState((value?ISS_OFF:ISS_ON)); + USBPort2SP.apply(); + } + if ( j.contains("POWER_PORT_USB3") ) { + j["POWER_PORT_USB3"].get_to(value); + USBPort3SP[POWER_ON].setState((value?ISS_ON:ISS_OFF)); + USBPort3SP[POWER_OFF].setState((value?ISS_OFF:ISS_ON)); + USBPort3SP.apply(); + } + if ( j.contains("POWER_PORT_USB4") ) { + j["POWER_PORT_USB4"].get_to(value); + USBPort4SP[POWER_ON].setState((value?ISS_ON:ISS_OFF)); + USBPort4SP[POWER_OFF].setState((value?ISS_OFF:ISS_ON)); + USBPort4SP.apply(); + } + return true; + } + + return false; +} + +bool AUDAUX::saveConfigItems(FILE *fp) +{ + // We need to reserve and save address mode + // so that the next time the driver is loaded, it is remembered and applied. + ConfigTP.save(fp); + + return INDI::DefaultDevice::saveConfigItems(fp); +} + +const char *AUDAUX::getDefaultName() +{ + return device_str; +} + +char* AUDAUX::sendCommand(const char *fmt, ... ) +{ + va_list ap; + char buffer[4096], answer[4096], addr[1024]; + int rc,retries; + zmq_pollitem_t item; + + va_start( ap, fmt ); + vsnprintf( buffer, sizeof(buffer), fmt, ap ); + va_end( ap ); + + pthread_mutex_lock( &connectionmutex ); + retries = 3; + do { + zmq_send(requester, buffer, strlen(buffer), 0); + item = { requester, 0, ZMQ_POLLIN, 0 }; + rc = zmq_poll( &item, 1, 500 ); // ms + if ( ( rc >= 0 ) && ( item.revents & ZMQ_POLLIN ) ) { + // communication succeeded + rc = zmq_recv(requester, answer, sizeof(answer), 0); + if ( rc >= 0 ) { + pthread_mutex_unlock( &connectionmutex ); + answer[MIN(rc,(int)sizeof(answer)-1)] = '\0'; + if ( !strncmp(answer,"OK",2) ) + return NULL; + if ( !strncmp(answer,"ERROR:",6) ) + return strdup(answer+6); + return strdup("SYNTAXERROR"); + } + } + zmq_close(requester); + requester = zmq_socket(context, ZMQ_REQ); + snprintf( addr, sizeof(addr), "tcp://%s:%d", IPaddress, IPport ); + zmq_connect(requester, addr); + } while ( --retries ); + pthread_mutex_unlock( &connectionmutex ); + DEBUG(INDI::Logger::DBG_WARNING, "No answer from driver"); + return strdup("COMMUNICATIONERROR"); +} + +char* AUDAUX::sendRequest(const char *fmt, ... ) +{ + va_list ap; + char buffer[4096], answer[4096], addr[1024]; + int rc,retries; + zmq_pollitem_t item; + + va_start( ap, fmt ); + vsnprintf( buffer, sizeof(buffer), fmt, ap ); + va_end( ap ); + + pthread_mutex_lock( &connectionmutex ); + retries = 3; + do { + zmq_send(requester, buffer, strlen(buffer), 0); + item = { requester, 0, ZMQ_POLLIN, 0 }; + rc = zmq_poll( &item, 1, 500 ); // ms + if ( ( rc >= 0 ) && ( item.revents & ZMQ_POLLIN ) ) { + // communication succeeded + rc = zmq_recv(requester, answer, sizeof(answer), 0); + if ( rc >= 0 ) { + pthread_mutex_unlock( &connectionmutex ); + answer[MIN(rc,(int)sizeof(answer)-1)] = '\0'; + return strdup(answer); + } + } + zmq_close(requester); + requester = zmq_socket(context, ZMQ_REQ); + snprintf( addr, sizeof(addr), "tcp://%s:%d", IPaddress, IPport ); + zmq_connect(requester, addr); + } while ( --retries ); + pthread_mutex_unlock( &connectionmutex ); + DEBUG(INDI::Logger::DBG_WARNING, "No answer from driver"); + return strdup("COMMUNICATIONERROR"); +} diff --git a/indi-avalonud/indi_avalonud_aux.h b/indi-avalonud/indi_avalonud_aux.h new file mode 100644 index 000000000..4387fd2bf --- /dev/null +++ b/indi-avalonud/indi_avalonud_aux.h @@ -0,0 +1,113 @@ +/* + Avalon Unified Driver Aux + + Copyright (C) 2020,2023 + + 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 +*/ + +#pragma once + +#include +#include +#include "defaultdevice.h" + +#define MIN(a,b) (((a)<=(b))?(a):(b)) + +class AUDAUX : public INDI::DefaultDevice +{ +public: + AUDAUX(); + ~AUDAUX(); + + virtual bool Connect() override; + virtual bool Disconnect() override; + + const char *getDefaultName() override; + + virtual bool initProperties() override; + virtual bool updateProperties() override; + virtual void ISGetProperties(const char *dev) 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 ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override; + +protected: + virtual void TimerHit() override; + + virtual bool saveConfigItems(FILE *fp) override; + +private: + + int tid; + unsigned int features; + + INDI::PropertyText ConfigTP {1}; + enum { + LLSW_NAME, + LLSW_VERSION, + LLSW_N + }; + INDI::PropertyText LowLevelSWTP {LLSW_N}; + INDI::PropertyText HWTypeTP {1}; + INDI::PropertyText HWIdentifierTP {1}; + enum { + SMNT_SHUTDOWN, + SMNT_REBOOT, + SMNT_N + }; + INDI::PropertySwitch SystemManagementSP {SMNT_N}; + INDI::PropertyNumber OUTPortPWMDUTYCYCLENP {1}; + enum { + POWER_ON, + POWER_OFF, + POWER_N + }; + INDI::PropertySwitch OUTPortPWMSP {POWER_N}; + INDI::PropertySwitch OUTPort1SP {POWER_N}; + INDI::PropertySwitch OUTPort2SP {POWER_N}; + INDI::PropertySwitch USBPort1SP {POWER_N}; + INDI::PropertySwitch USBPort2SP {POWER_N}; + INDI::PropertySwitch USBPort3SP {POWER_N}; + INDI::PropertySwitch USBPort4SP {POWER_N}; + enum { + PSU_VOLTAGE, + PSU_CURRENT, + PSU_POWER, + PSU_CHARGE, + PSU_N + }; + INDI::PropertyNumber PSUNP {PSU_N}; + enum { + SM_FEEDTIME, + SM_BUFFERLOAD, + SM_UPTIME, + SM_N + }; + INDI::PropertyNumber SMNP {SM_N}; + INDI::PropertyNumber CPUNP {1}; + + bool readStatus(); + + char* IPaddress; + char* sendCommand(const char*,...); + char* sendRequest(const char*,...); + + void *context,*requester; + time_t reboot_time,shutdown_time; + + pthread_mutex_t connectionmutex; +}; diff --git a/indi-avalonud/indi_avalonud_focuser.cpp b/indi-avalonud/indi_avalonud_focuser.cpp new file mode 100644 index 000000000..99a349d81 --- /dev/null +++ b/indi-avalonud/indi_avalonud_focuser.cpp @@ -0,0 +1,526 @@ +/* + Avalon Unified Driver Focuser + + Copyright (C) 2020,2023 + + 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 "config.h" + +#include "indicom.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "indi_avalonud_focuser.h" + + +#define STEPMACHINE_DRIVER_NUM 2 + + +using json = nlohmann::json; + +const int IPport = 5450; + +static char device_str[MAXINDIDEVICE] = "AvalonUD Focuser"; + + +static std::unique_ptr focuser(new AUDFOCUSER()); + +void ISGetProperties(const char *dev) +{ + focuser->ISGetProperties(dev); +} + +void ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) +{ + focuser->ISNewSwitch(dev, name, states, names, n); +} + +void ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) +{ + focuser->ISNewText(dev, name, texts, names, n); +} + +void ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) +{ + focuser->ISNewNumber(dev, name, values, names, n); +} + +void ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], + char *names[], int n) +{ + INDI_UNUSED(dev); + INDI_UNUSED(name); + INDI_UNUSED(sizes); + INDI_UNUSED(blobsizes); + INDI_UNUSED(blobs); + INDI_UNUSED(formats); + INDI_UNUSED(names); + INDI_UNUSED(n); +} + +void ISSnoopDevice(XMLEle *root) +{ + focuser->ISSnoopDevice(root); +} + +AUDFOCUSER::AUDFOCUSER() +{ + setVersion(AVALONUD_VERSION_MAJOR,AVALONUD_VERSION_MINOR); + + context = zmq_ctx_new(); + setSupportedConnections( CONNECTION_NONE ); + FI::SetCapability(FOCUSER_CAN_ABORT | + FOCUSER_CAN_ABS_MOVE | + FOCUSER_CAN_REL_MOVE | + FOCUSER_CAN_SYNC); +} + +AUDFOCUSER::~AUDFOCUSER() +{ +// zmq_ctx_term(context); +} + +bool AUDFOCUSER::initProperties() +{ + INDI::Focuser::initProperties(); + + ConfigTP[0].fill("ADDRESS", "Address", "127.0.0.1"); + ConfigTP.fill(getDeviceName(), "DEVICE_ADDRESS", "Server", CONNECTION_TAB, IP_RW, 60, IPS_IDLE); + + // HW type + HWTypeTP[0].fill("HW_TYPE", "Controller Type", ""); + HWTypeTP.fill(getDeviceName(), "HW_TYPE_INFO", "Type", INFO_TAB, IP_RO, 60, IPS_IDLE); + + // HW identifier + HWIdentifierTP[0].fill("HW_IDENTIFIER", "HW Identifier", ""); + HWIdentifierTP.fill(getDeviceName(), "HW_IDENTIFIER_INFO", "Identifier", INFO_TAB, IP_RO, 60, IPS_IDLE); + + // low level SW info + LowLevelSWTP[LLSW_NAME].fill("LLSW_NAME", "Name", ""); + LowLevelSWTP[LLSW_VERSION].fill("LLSW_VERSION", "Version", "--"); + LowLevelSWTP.fill(getDeviceName(), "LLSW_INFO", "LowLevel SW", INFO_TAB, IP_RO, 60, IPS_IDLE); + + addDebugControl(); + + // Set limits as per documentation + FocusAbsPosN[0].min = 0; + FocusAbsPosN[0].max = 999999; + FocusAbsPosN[0].step = 1000; + + FocusRelPosN[0].min = 0; + FocusRelPosN[0].max = 999; + FocusRelPosN[0].step = 100; + + FocusSpeedN[0].min = 1; + FocusSpeedN[0].max = 254; + FocusSpeedN[0].step = 10; + + pthread_mutex_init( &connectionmutex, NULL ); + + return true; +} + +void AUDFOCUSER::ISGetProperties(const char *dev) +{ + INDI::Focuser::ISGetProperties(dev); + + defineProperty(ConfigTP); + loadConfig(true,ConfigTP.getName()); +} + +bool AUDFOCUSER::updateProperties() +{ + if (isConnected()) + { + // Read these values before defining focuser interface properties + readPosition(); + } + + INDI::Focuser::updateProperties(); + + if (isConnected()) + { + // Settings + defineProperty(HWTypeTP); + defineProperty(HWIdentifierTP); + defineProperty(LowLevelSWTP); + + LOG_INFO("Focuser is ready"); + } + else + { + deleteProperty(HWTypeTP); + deleteProperty(HWIdentifierTP); + deleteProperty(LowLevelSWTP); + } + + return true; +} + +bool AUDFOCUSER::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) +{ + if (!strcmp(dev,getDeviceName())) + { + // TCP Server settings + if (ConfigTP.isNameMatch(name)) + { + if ( isConnected() && strcmp(IPaddress,texts[0]) ) { + DEBUG(INDI::Logger::DBG_WARNING, "Please Disconnect before changing IP address"); + return false; + } + ConfigTP.update(texts, names, n); + ConfigTP.setState(IPS_OK); + ConfigTP.apply(); + return true; + } + } + + return INDI::Focuser::ISNewText(dev, name, texts, names, n); +} + +bool AUDFOCUSER::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n) +{ + return INDI::Focuser::ISNewSwitch(dev, name, states, names, n); +} + +bool AUDFOCUSER::Connect() +{ + char addr[1024], *answer; + int timeout = 500; + + if (isConnected()) + return true; + + IPaddress = strdup(ConfigTP[0].text); + + DEBUGF(INDI::Logger::DBG_SESSION, "Attempting to connect %s focuser...",IPaddress); + + requester = zmq_socket(context, ZMQ_REQ); + zmq_setsockopt(requester, ZMQ_RCVTIMEO, &timeout, sizeof(timeout) ); + snprintf( addr, sizeof(addr), "tcp://%s:%d", IPaddress, IPport ); + zmq_connect(requester, addr); + + answer = sendRequest("DISCOVER"); + if ( answer ) { + if ( !strcmp(answer,"stepMachine") ) { + free(answer); + answer = sendRequest("INFOALL"); + if ( answer ) { + json j; + std::string sHWt,sHWi,sFWv; + + j = json::parse(answer,nullptr,false); + free(answer); + if ( j.is_discarded() || + !j.contains("HWType") || + !j.contains("HWFeatures") || + !j.contains("HWIdentifier") || + !j.contains("firmwareVersion") ) + { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Communication with %s focuser failed",IPaddress); + free(IPaddress); + return false; + } + + j["HWType"].get_to(sHWt); + HWTypeTP[0].setText(sHWt); + HWTypeTP.apply(); + j["HWFeatures"].get_to(features); + j["HWIdentifier"].get_to(sHWi); + HWIdentifierTP[0].setText(sHWi); + HWIdentifierTP.apply(); + LowLevelSWTP[LLSW_NAME].setText("stepMachine"); + j["firmwareVersion"].get_to(sFWv); + LowLevelSWTP[LLSW_VERSION].setText(sFWv); + LowLevelSWTP.apply(); + } + if ( !(features & 0x0100) ) { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Focuser features not supported by %s hardware",IPaddress); + free(IPaddress); + return false; + } + } else { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Failed to connect %s focuser",IPaddress); + free(answer); + free(IPaddress); + return false; + } + } else { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Failed to connect %s focuser",IPaddress); + free(IPaddress); + return false; + } + + tid = SetTimer(getCurrentPollingPeriod()); + + DEBUGF(INDI::Logger::DBG_SESSION, "Successfully connected %s focuser",IPaddress); + return true; +} + +bool AUDFOCUSER::Disconnect() +{ + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Disconnect called before driver connection"); + return true; + } + + DEBUG(INDI::Logger::DBG_SESSION, "Attempting to disconnect focuser..."); + + zmq_close(requester); + + RemoveTimer( tid ); + + free(IPaddress); + + DEBUG(INDI::Logger::DBG_SESSION, "Successfully disconnected focuser"); + + return true; +} + +IPState AUDFOCUSER::MoveAbsFocuser(uint32_t targetTicks) +{ + char *answer; + + if ( !isConnected() ) { + DEBUG(INDI::Logger::DBG_WARNING,"Positioning required before driver connection"); + return IPS_ALERT; + } + + DEBUGF(INDI::Logger::DBG_SESSION,"Start positioning focuser at %ustep ...",targetTicks); + answer = sendCommand("ABSOLUTE %d %d 0 0",STEPMACHINE_DRIVER_NUM,targetTicks); + if ( !answer ) { + DEBUGF(INDI::Logger::DBG_SESSION,"Start positioning focuser at %ustep completed",targetTicks); + return IPS_BUSY; + } + DEBUGF(INDI::Logger::DBG_WARNING,"Start positioning focuser at %ustep failed due to %s",targetTicks,answer); + free(answer); + return IPS_ALERT; +} + +IPState AUDFOCUSER::MoveRelFocuser(FocusDirection dir, uint32_t ticks) +{ + char *answer; + + if ( !isConnected() ) { + DEBUG(INDI::Logger::DBG_WARNING,"Positioning required before driver connection"); + return IPS_OK; + } + + DEBUGF(INDI::Logger::DBG_SESSION,"Start moving focuser of %ustep ...",ticks); + if ( dir == FOCUS_INWARD ) + answer = sendCommand("RELATIVE %d -%lu 0 0",STEPMACHINE_DRIVER_NUM,ticks); + else + answer = sendCommand("RELATIVE %d %lu 0 0",STEPMACHINE_DRIVER_NUM,ticks); + if ( !answer ) { + DEBUGF(INDI::Logger::DBG_SESSION,"Start moving focuser of %ustep completed",ticks); + return IPS_BUSY; + } + DEBUGF(INDI::Logger::DBG_WARNING,"Start moving focuser of %ustep failed due to %s",ticks,answer); + free(answer); + return IPS_ALERT; +} + +bool AUDFOCUSER::SyncFocuser(uint32_t ticks) +{ + char *answer; + + if ( !isConnected() ) { + DEBUG(INDI::Logger::DBG_WARNING,"Sync required before driver connection"); + return true; + } + + DEBUGF(INDI::Logger::DBG_SESSION,"Sync focuser position to %ustep ...",ticks); + answer = sendCommand("SYNC %d %ld",STEPMACHINE_DRIVER_NUM,ticks); + if ( !answer ) { + DEBUGF(INDI::Logger::DBG_SESSION,"Sync focuser position to %ustep completed",ticks); + return true; + } + DEBUGF(INDI::Logger::DBG_SESSION,"Sync focuser position to %ustep failed due to %s",ticks,answer); + free(answer); + return false; +} + +bool AUDFOCUSER::AbortFocuser() +{ + DEBUG(INDI::Logger::DBG_SESSION, "Focuser abort ..."); + + if (isConnected()) { + char *answer; + answer = sendCommand("STOP %d",STEPMACHINE_DRIVER_NUM); + free(answer); + } else { + DEBUG(INDI::Logger::DBG_WARNING,"Abort required before driver connection"); + } + + DEBUG(INDI::Logger::DBG_SESSION, "Focuser abort completed"); + + return true; +} + +void AUDFOCUSER::TimerHit() +{ + if (isConnected() == false) + return; + + // Read the current position + readPosition(); + + // Check if we have a pending motion + // if isMoving() is false, then we stopped, so we need to set the Focus Absolute + // and relative properties to OK + if ( (FocusAbsPosNP.s == IPS_BUSY || FocusRelPosNP.s == IPS_BUSY) && ( statusCode >= STILL ) ) + { + FocusAbsPosNP.s = IPS_OK; + IDSetNumber(&FocusAbsPosNP, nullptr); + FocusRelPosNP.s = IPS_OK; + IDSetNumber(&FocusRelPosNP, nullptr); + } + // If there was a different between last and current positions, let's update all clients + else if (currentPosition != FocusAbsPosN[0].value) + { + FocusAbsPosN[0].value = currentPosition; + IDSetNumber(&FocusAbsPosNP, nullptr); + } + + SetTimer(getCurrentPollingPeriod()); +} + +bool AUDFOCUSER::readPosition() +{ + char *answer; + + answer = sendRequest("STATUS %d",STEPMACHINE_DRIVER_NUM); + if ( answer ) { + json j; + + j = json::parse(answer,nullptr,false); + free(answer); + if ( j.is_discarded() || + !j.contains("position_step") || + !j.contains("statusCode") ) + { + DEBUG(INDI::Logger::DBG_WARNING,"Status communication error"); + return false; + } + j["position_step"].get_to(currentPosition); + j["statusCode"].get_to(statusCode); + return true; + } + + return false; +} + +bool AUDFOCUSER::saveConfigItems(FILE *fp) +{ + // We need to reserve and save address mode + // so that the next time the driver is loaded, it is remembered and applied. + ConfigTP.save(fp); + + return INDI::Focuser::saveConfigItems(fp); +} + +const char *AUDFOCUSER::getDefaultName() +{ + return device_str; +} + +char* AUDFOCUSER::sendCommand(const char *fmt, ... ) +{ + va_list ap; + char buffer[4096], answer[4096], addr[1024]; + int rc,retries; + zmq_pollitem_t item; + + va_start( ap, fmt ); + vsnprintf( buffer, sizeof(buffer), fmt, ap ); + va_end( ap ); + + pthread_mutex_lock( &connectionmutex ); + retries = 3; + do { + zmq_send(requester, buffer, strlen(buffer), 0); + item = { requester, 0, ZMQ_POLLIN, 0 }; + rc = zmq_poll( &item, 1, 500 ); // ms + if ( ( rc >= 0 ) && ( item.revents & ZMQ_POLLIN ) ) { + // communication succeeded + rc = zmq_recv(requester, answer, sizeof(answer), 0); + if ( rc >= 0 ) { + pthread_mutex_unlock( &connectionmutex ); + answer[MIN(rc,(int)sizeof(answer)-1)] = '\0'; + if ( !strncmp(answer,"OK",2) ) + return NULL; + if ( !strncmp(answer,"ERROR:",6) ) + return strdup(answer+6); + return strdup("SYNTAXERROR"); + } + } + zmq_close(requester); + requester = zmq_socket(context, ZMQ_REQ); + snprintf( addr, sizeof(addr), "tcp://%s:%d", IPaddress, IPport ); + zmq_connect(requester, addr); + } while ( --retries ); + pthread_mutex_unlock( &connectionmutex ); + DEBUG(INDI::Logger::DBG_WARNING, "No answer from driver"); + return strdup("COMMUNICATIONERROR"); +} + +char* AUDFOCUSER::sendRequest(const char *fmt, ... ) +{ + va_list ap; + char buffer[4096], answer[4096], addr[1024]; + int rc,retries; + zmq_pollitem_t item; + + va_start( ap, fmt ); + vsnprintf( buffer, sizeof(buffer), fmt, ap ); + va_end( ap ); + + pthread_mutex_lock( &connectionmutex ); + retries = 3; + do { + zmq_send(requester, buffer, strlen(buffer), 0); + item = { requester, 0, ZMQ_POLLIN, 0 }; + rc = zmq_poll( &item, 1, 500 ); // ms + if ( ( rc >= 0 ) && ( item.revents & ZMQ_POLLIN ) ) { + // communication succeeded + rc = zmq_recv(requester, answer, sizeof(answer), 0); + if ( rc >= 0 ) { + pthread_mutex_unlock( &connectionmutex ); + answer[MIN(rc,(int)sizeof(answer)-1)] = '\0'; + return strdup(answer); + } + } + zmq_close(requester); + requester = zmq_socket(context, ZMQ_REQ); + snprintf( addr, sizeof(addr), "tcp://%s:%d", IPaddress, IPport ); + zmq_connect(requester, addr); + } while ( --retries ); + pthread_mutex_unlock( &connectionmutex ); + DEBUG(INDI::Logger::DBG_WARNING, "No answer from driver"); + return strdup("COMMUNICATIONERROR"); +} diff --git a/indi-avalonud/indi_avalonud_focuser.h b/indi-avalonud/indi_avalonud_focuser.h new file mode 100644 index 000000000..fabf741ed --- /dev/null +++ b/indi-avalonud/indi_avalonud_focuser.h @@ -0,0 +1,83 @@ +/* + Avalon Unified Driver Focuser + + Copyright (C) 2020,2023 + + 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 +*/ + +#pragma once + +#include +#include "indifocuser.h" + +#define MIN(a,b) (((a)<=(b))?(a):(b)) + +class AUDFOCUSER : public INDI::Focuser +{ +public: + AUDFOCUSER(); + ~AUDFOCUSER(); + + virtual bool Connect() override; + virtual bool Disconnect() override; + + const char *getDefaultName() override; + + virtual bool initProperties() override; + virtual bool updateProperties() override; + virtual void ISGetProperties(const char *dev) 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; + +protected: + virtual void TimerHit() override; + + virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override; + virtual IPState MoveAbsFocuser(uint32_t targetTicks) override; + virtual bool SyncFocuser(uint32_t ticks) override; + virtual bool AbortFocuser() override; + + virtual bool saveConfigItems(FILE *fp) override; + +private: + enum STATUSCODE { UNRESPONSIVE, FAILED, UPPERLIMIT, LOWERLIMIT, SLEWING, TRACKING, STILL, OFF }; + + int tid; + unsigned int features; + + INDI::PropertyText ConfigTP {1}; + enum { + LLSW_NAME, + LLSW_VERSION, + LLSW_N + }; + INDI::PropertyText LowLevelSWTP {LLSW_N}; + INDI::PropertyText HWTypeTP {1}; + INDI::PropertyText HWIdentifierTP {1}; + + bool readPosition(); + + char* IPaddress; + char* sendCommand(const char*,...); + char* sendRequest(const char*,...); + + void *context,*requester; + int64_t currentPosition; + int statusCode; + + pthread_mutex_t connectionmutex; +}; diff --git a/indi-avalonud/indi_avalonud_telescope.cpp b/indi-avalonud/indi_avalonud_telescope.cpp new file mode 100644 index 000000000..d12bd2347 --- /dev/null +++ b/indi-avalonud/indi_avalonud_telescope.cpp @@ -0,0 +1,1429 @@ +/* + Avalon Unified Driver Telescope + + Copyright (C) 2020,2023 + + 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 "config.h" + +#include "indicom.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "indi_avalonud_telescope.h" + + +using json = nlohmann::json; + +const int IPport = 5451; + +static char device_str[MAXINDIDEVICE] = "AvalonUD Telescope"; + + +static std::unique_ptr telescope(new AUDTELESCOPE()); + +void ISInit() +{ + static int isInit =0; + + if (isInit == 1) + return; + + isInit = 1; + if(telescope.get() == 0) telescope.reset(new AUDTELESCOPE()); +} + +void ISGetProperties(const char *dev) +{ + telescope->ISGetProperties(dev); +} + +void ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int num) +{ + telescope->ISNewSwitch(dev, name, states, names, num); +} + +void ISNewText(const char *dev, const char *name, char *texts[], char *names[], int num) +{ + telescope->ISNewText(dev, name, texts, names, num); +} + +void ISNewNumber(const char *dev, const char *name, double values[], char *names[], int num) +{ + telescope->ISNewNumber(dev, name, values, names, num); +} + +void ISNewBLOB (const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int num) +{ + INDI_UNUSED(dev); + INDI_UNUSED(name); + INDI_UNUSED(sizes); + INDI_UNUSED(blobsizes); + INDI_UNUSED(blobs); + INDI_UNUSED(formats); + INDI_UNUSED(names); + INDI_UNUSED(num); +} + +void ISSnoopDevice (XMLEle *root) +{ + telescope->ISSnoopDevice(root); +} + +/**************************************************************** +** +** +*****************************************************************/ +AUDTELESCOPE::AUDTELESCOPE() +{ + setVersion(AVALONUD_VERSION_MAJOR,AVALONUD_VERSION_MINOR); + + context = zmq_ctx_new(); + setTelescopeConnection( CONNECTION_NONE ); + SetTelescopeCapability( TELESCOPE_CAN_GOTO | + TELESCOPE_CAN_SYNC | + TELESCOPE_CAN_PARK | + TELESCOPE_CAN_ABORT | + TELESCOPE_HAS_TIME | + TELESCOPE_HAS_LOCATION | + TELESCOPE_HAS_TRACK_MODE | + TELESCOPE_CAN_CONTROL_TRACK | + TELESCOPE_HAS_TRACK_RATE | + TELESCOPE_HAS_PIER_SIDE, + 4 ); + SetParkDataType(PARK_HA_DEC); +} + +/**************************************************************** +** +** +*****************************************************************/ +AUDTELESCOPE::~AUDTELESCOPE() +{ +// zmq_ctx_term(context); +} + +/**************************************************************** +** +** +*****************************************************************/ +bool AUDTELESCOPE::initProperties() +{ + INDI::Telescope::initProperties(); + + ConfigTP[0].fill("ADDRESS", "Address", "127.0.0.1"); + ConfigTP.fill(getDeviceName(), "DEVICE_ADDRESS", "Server", CONNECTION_TAB, IP_RW, 60, IPS_IDLE); + + MountModeSP[MM_EQUATORIAL].fill("MOUNT_EQUATORIAL", "Equatorial", ISS_OFF); + MountModeSP[MM_ALTAZ].fill("MOUNT_ALTAZ", "AltAz", ISS_OFF); + MountModeSP.fill(getDeviceName(), "MOUNT_TYPE", "Mount Type", MAIN_CONTROL_TAB, IP_RO, ISR_1OFMANY, 0, IPS_IDLE); + + LocalEqNP[LEQ_HA].fill("HA","HA (hh:mm:ss)","%010.6m",-12,12,0,0); + LocalEqNP[LEQ_DEC].fill("DEC","DEC (dd:mm:ss)","%010.6m",-90,90,0,0); + LocalEqNP.fill(getDeviceName(), "LOCAL_EQUATORIAL_EOD_COORD","Local Eq. Coordinates", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE); + + AltAzNP[ALTAZ_AZ].fill("Az","Az (deg)","%.2f",-180,180,0,0); + AltAzNP[ALTAZ_ALT].fill("Alt","Alt (deg)","%.2f",-90,90,0,0); + AltAzNP.fill(getDeviceName(), "AZALT_EOD_COORD", "Azimuthal Coordinates", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE); + + HomeSP[HOME_SYNC].fill("SYNCHOME","Sync Home position",ISS_OFF); + HomeSP[HOME_SLEW].fill("SLEWHOME","Slew to Home position",ISS_OFF); + HomeSP.fill(getDeviceName(), "TELESCOPE_HOME","Home", SITE_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE); + + TTimeNP[TTIME_JD].fill("JD","JD (days)","%.6f",0,0,0,0); + TTimeNP[TTIME_UTC].fill("UTC","UTC (hh:mm:ss)","%09.6m",0,24,0,0); + TTimeNP[TTIME_LST].fill("LST","LST (hh:mm:ss)","%09.6m",0,24,0,0); + TTimeNP.fill(getDeviceName(), "TELESCOPE_TIME","Time", SITE_TAB, IP_RO, 60, IPS_IDLE); + + IUFillSwitch(&ParkOptionS[PARK_CURRENT], "PARK_CURRENT", "Set Park (Current)", ISS_OFF); + IUFillSwitch(&ParkOptionS[PARK_DEFAULT], "PARK_DEFAULT", "Restore Park (Default)", ISS_OFF); + IUFillSwitchVector(&ParkOptionSP, ParkOptionS, 2, getDeviceName(), "TELESCOPE_PARK_OPTION", "Park Options", SITE_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE); + + // Since we have 4 slew rates, let's fill them out + IUFillSwitch(&SlewRateS[SLEW_GUIDE], "SLEW_GUIDE", "Guide", ISS_OFF); + IUFillSwitch(&SlewRateS[SLEW_CENTERING], "SLEW_CENTER", "Center", ISS_OFF); + IUFillSwitch(&SlewRateS[SLEW_FIND], "SLEW_FIND", "Find", ISS_OFF); + IUFillSwitch(&SlewRateS[SLEW_MAX], "SLEW_MAX", "Max", ISS_ON); + IUFillSwitchVector(&SlewRateSP, SlewRateS, 4, getDeviceName(), "TELESCOPE_SLEW_RATE", "Slew Rate", MOTION_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); + + // Add Tracking Modes. If you have SOLAR, LUNAR..etc, add them here as well. + AddTrackMode("TRACK_SIDEREAL", "Sidereal", true); + AddTrackMode("TRACK_SOLAR", "Solar"); + AddTrackMode("TRACK_LUNAR", "Lunar"); + AddTrackMode("TRACK_CUSTOM", "Custom"); + + // Mount Meridian Flip + MeridianFlipSP[MFLIP_ON].fill("FLIP_ON", "On", ISS_OFF); + MeridianFlipSP[MFLIP_OFF].fill("FLIP_OFF", "Off", ISS_ON); + MeridianFlipSP.fill(getDeviceName(), "MOUNT_MERIDIAN_FLIP", "Mount Meridian Flip", SITE_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); + + // Mount Meridian Flip HA + MeridianFlipHANP[0].fill("FLIP_HA", "Flip HA (deg)", "%.2f", -30.0, 30.0, 0.1, 0.0); + MeridianFlipHANP.fill(getDeviceName(), "MOUNT_MERIDIAN_FLIP_HA", "Mount Meridian Flip HA", SITE_TAB, IP_RW, 60, IPS_IDLE); + + // HW type + HWTypeTP[0].fill("HW_TYPE", "Controller Type", ""); + HWTypeTP.fill(getDeviceName(), "HW_TYPE_INFO", "Type", INFO_TAB, IP_RO, 60, IPS_IDLE); + + // HW info + HWModelTP[0].fill("HW_MODEL", "Mount Model", ""); + HWModelTP.fill(getDeviceName(), "HW_MODEL_INFO", "Model", INFO_TAB, IP_RO, 60, IPS_IDLE); + + // HW identifier + HWIdentifierTP[0].fill("HW_IDENTIFIER", "HW Identifier", ""); + HWIdentifierTP.fill(getDeviceName(), "HW_IDENTIFIER_INFO", "Identifier", INFO_TAB, IP_RO, 60, IPS_IDLE); + + // high level info + HighLevelSWTP[HLSW_NAME].fill("HLSW_NAME", "Name", ""); + HighLevelSWTP[HLSW_VERSION].fill("HLSW_VERSION", "Version", "--"); + HighLevelSWTP.fill(getDeviceName(), "HLSW_INFO", "HighLevel SW", INFO_TAB, IP_RO, 60, IPS_IDLE); + + // low level SW info + LowLevelSWTP[LLSW_NAME].fill("LLSW_NAME", "Name", ""); + LowLevelSWTP[LLSW_VERSION].fill("LLSW_VERSION", "Version", "--"); + LowLevelSWTP.fill(getDeviceName(), "LLSW_INFO", "LowLevel SW", INFO_TAB, IP_RO, 60, IPS_IDLE); + + TrackState = SCOPE_IDLE; + previousTrackState = SCOPE_IDLE; + fTracking = false; + fFirstTime = true; + lastErrorMsg = NULL; + +// SetTrackMode(TRACK_SIDEREAL); + trackspeedra = TRACKRATE_SIDEREAL; + trackspeeddec = 0; + + initGuiderProperties(getDeviceName(), GUIDE_TAB); + setDriverInterface(getDriverInterface() | GUIDER_INTERFACE); + + addDebugControl(); + addConfigurationControl(); + + pthread_mutex_init( &connectionmutex, NULL ); + + return true; +} + +/**************************************************************** +** +** +*****************************************************************/ +void AUDTELESCOPE::ISGetProperties(const char *dev) +{ + INDI::Telescope::ISGetProperties(dev); + + defineProperty(ConfigTP); + loadConfig(true,ConfigTP.getName()); + + deleteProperty(ParkOptionS[2].name); + deleteProperty(ParkOptionS[3].name); +} + +/**************************************************************** +** +** +*****************************************************************/ +bool AUDTELESCOPE::updateProperties() +{ + if (isConnected()) + { + defineProperty(HomeSP); + } + else + { + deleteProperty(HomeSP.getName()); + } + + INDI::Telescope::updateProperties(); + if (isConnected()) + { + defineProperty(MountModeSP); + defineProperty(&GuideNSNP); + defineProperty(&GuideWENP); + + defineProperty(LocalEqNP); + defineProperty(AltAzNP); + defineProperty(TTimeNP); + defineProperty(MeridianFlipSP); + defineProperty(MeridianFlipHANP); + defineProperty(HWTypeTP); + defineProperty(HWModelTP); + defineProperty(HWIdentifierTP); + defineProperty(HighLevelSWTP); + defineProperty(LowLevelSWTP); + } + else + { + deleteProperty(MountModeSP); + deleteProperty(GuideNSNP.name); + deleteProperty(GuideWENP.name); + + deleteProperty(LocalEqNP); + deleteProperty(AltAzNP); + deleteProperty(TTimeNP); + deleteProperty(MeridianFlipSP); + deleteProperty(MeridianFlipHANP); + deleteProperty(HWTypeTP); + deleteProperty(HWModelTP); + deleteProperty(HWIdentifierTP); + deleteProperty(HighLevelSWTP); + deleteProperty(LowLevelSWTP); + } + + return true; +} + +/**************************************************************** +** +** +*****************************************************************/ +bool AUDTELESCOPE::Connect() +{ + char *answer; + char addr[1024]; + int timeout = 500; + + + if (isConnected()) + return true; + + IPaddress = strdup(ConfigTP[0].text); + + DEBUGF(INDI::Logger::DBG_SESSION, "Attempting to connect %s telescope...",IPaddress); + + requester = zmq_socket(context, ZMQ_REQ); + zmq_setsockopt(requester, ZMQ_RCVTIMEO, &timeout, sizeof(timeout) ); + snprintf( addr, sizeof(addr), "tcp://%s:%d", IPaddress, IPport ); + zmq_connect(requester, addr); + + answer = sendRequest("ASTRO_INFO"); + if ( answer ) { + json j; + std::string sHWt,sHWm,sHWi,sLLSW,sLLSWv,sHLSW,sHLSWv; + + j = json::parse(answer,nullptr,false); + free(answer); + if ( j.is_discarded() || + !j.contains("HWType") || + !j.contains("HWModel") || + !j.contains("HWIdentifier") || + !j.contains("lowLevelSW") || + !j.contains("lowLevelSWVersion") || + !j.contains("highLevelSW") || + !j.contains("highLevelSWVersion") ) + { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Communication with %s telescope failed",IPaddress); + free(IPaddress); + return false; + } + + j["HWType"].get_to(sHWt); + HWTypeTP[0].setText(sHWt); + HWTypeTP.apply(); + j["HWModel"].get_to(sHWm); + HWModelTP[0].setText(sHWm); + HWModelTP.apply(); + j["HWIdentifier"].get_to(sHWi); + HWIdentifierTP[0].setText(sHWi); + HWIdentifierTP.apply(); + j["lowLevelSW"].get_to(sLLSW); + LowLevelSWTP[LLSW_NAME].setText(sLLSW); + j["lowLevelSWVersion"].get_to(sLLSWv); + LowLevelSWTP[LLSW_VERSION].setText(sLLSWv); + LowLevelSWTP.apply(); + j["highLevelSW"].get_to(sHLSW); + HighLevelSWTP[HLSW_NAME].setText(sHLSW); + j["highLevelSWVersion"].get_to(sHLSWv); + HighLevelSWTP[HLSW_VERSION].setText(sHLSWv); + HighLevelSWTP.apply(); + } else { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Failed to connect %s telescope",IPaddress); + free(IPaddress); + return false; + } + + answer = sendRequest("ASTRO_GETMERIDIANFLIPHA"); + if ( answer && !strncmp(answer,"OK:",3) ) { + MeridianFlipHANP[0].value = atof(answer+3); + free( answer ); + MeridianFlipHANP.apply(); + } else { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Failed to connect %s telescope",IPaddress); + free(IPaddress); + return false; + } + + answer = sendRequest("ASTRO_GETMOUNTMODE"); + if ( answer && !strncmp(answer,"OK:",3) ) { + if ( !strcmp(answer,"OK:ALTAZ") ) + mounttype = MM_ALTAZ; + else + mounttype = MM_EQUATORIAL; + free( answer ); + MountModeSP[mounttype].setState(ISS_ON); + MountModeSP[(mounttype?0:1)].setState(ISS_OFF); + MountModeSP.setState(IPS_OK); + MountModeSP.setPermission(IP_RO); + MountModeSP.apply(); + } else { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Failed to connect %s telescope",IPaddress); + free(IPaddress); + return false; + } + + answer = sendRequest("ASTRO_GETLOCATION"); + if ( answer ) { + json j; + + j = json::parse(answer,nullptr,false); + free(answer); + if ( j.is_discarded() || + !j.contains("longitude") || + !j.contains("latitude") || + !j.contains("elevation") ) + { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Communication with %s telescope failed",IPaddress); + free(IPaddress); + return false; + } + j["longitude"].get_to(LocationN[LOCATION_LONGITUDE].value); + j["latitude"].get_to(LocationN[LOCATION_LATITUDE].value); + j["elevation"].get_to(LocationN[LOCATION_ELEVATION].value); + IDSetNumber(&LocationNP, nullptr); + } else { + zmq_close(requester); + DEBUGF(INDI::Logger::DBG_ERROR, "Failed to connect %s telescope",IPaddress); + free(IPaddress); + return false; + } + + northernHemisphere = 1; + + slewState = IPS_IDLE; + + tid = SetTimer(getCurrentPollingPeriod()); + + DEBUGF(INDI::Logger::DBG_SESSION, "Successfully connected %s telescope",IPaddress); + return true; +} + +/**************************************************************** +** +** +*****************************************************************/ +bool AUDTELESCOPE::Disconnect() +{ + if (!isConnected()) + return true; + + DEBUG(INDI::Logger::DBG_SESSION, "Attempting to disconnect telescope..."); + + zmq_close(requester); + + RemoveTimer( tid ); + + free(IPaddress); + + DEBUG(INDI::Logger::DBG_SESSION, "Successfully disconnected telescope"); + + return true; +} + +/**************************************************************** +** +** +*****************************************************************/ +bool AUDTELESCOPE::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) +{ + if (!strcmp(dev,getDeviceName())) + { + // TCP Server settings + if (ConfigTP.isNameMatch(name)) + { + if ( isConnected() && strcmp(IPaddress,texts[0]) ) { + DEBUG(INDI::Logger::DBG_WARNING, "Please Disconnect before changing IP address"); + return false; + } + ConfigTP.update(texts, names, n); + ConfigTP.setState(IPS_OK); + ConfigTP.apply(); + return true; + } + } + + return INDI::Telescope::ISNewText(dev, name, texts, names, n); +} + +bool AUDTELESCOPE::ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n) +{ + // first check if it's for our device + if(!strcmp(dev,getDeviceName())) + { + + // Meridian Flip HourAngle + if(MeridianFlipHANP.isNameMatch(name)) + { + MeridianFlipHANP.setState(IPS_BUSY); + MeridianFlipHANP.update(values, names, n); + + if ( isConnected() ) + { + if ( setMeridianFlipHA(values[0]) ) + MeridianFlipHANP.setState(IPS_OK); + else + MeridianFlipHANP.setState(IPS_ALERT); + } + MeridianFlipHANP.apply(); + return true; + } + + // Guiding + if (!strcmp(name, GuideNSNP.name) || !strcmp(name, GuideWENP.name)) + { + processGuiderProperties(name, values, names, n); + return true; + } + } + + return INDI::Telescope::ISNewNumber(dev, name, values, names, n); +} + +bool AUDTELESCOPE::ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n) +{ + if(!strcmp(dev,getDeviceName())) + { + + // Home + if (HomeSP.isNameMatch(name)) + { + HomeSP.update(states, names, n); + int index = HomeSP.findOnSwitchIndex(); + + HomeSP.setState(IPS_BUSY); + HomeSP.reset(); + HomeSP.apply(); + + if ( isConnected() ) + { + switch (index) { + case HOME_SYNC : + if ( SyncHome() ) + HomeSP.setState(IPS_OK); + else + HomeSP.setState(IPS_ALERT); + break; + case HOME_SLEW : + if ( SlewToHome() ) + HomeSP.setState(IPS_OK); + else + HomeSP.setState(IPS_ALERT); + break; + } + } + HomeSP.apply(); + return true; + } + + // Meridian flip + if (MeridianFlipSP.isNameMatch(name)) + { + MeridianFlipSP.setState(IPS_BUSY); + MeridianFlipSP.update(states, names, n); + + if ( isConnected() ) + { + int targetState = MeridianFlipSP.findOnSwitchIndex(); + if ( meridianFlipEnable((targetState==0)?true:false) ) + MeridianFlipSP.setState(IPS_OK); + else + MeridianFlipSP.setState(IPS_ALERT); + } + MeridianFlipSP.apply(); + return true; + } + } + + return INDI::Telescope::ISNewSwitch(dev, name, states, names, n); +} + +bool AUDTELESCOPE::updateLocation(double latitude, double longitude, double elevation) +{ + char *answer; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Location update called before driver connection"); + return false; + } + + DEBUG(INDI::Logger::DBG_SESSION,"Location update ..." ); + answer = sendCommand("ASTRO_SETLOCATION %.8f %.8f %.1f",longitude,latitude,elevation); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION,"Location update completed" ); + return true; + } + DEBUGF(INDI::Logger::DBG_WARNING,"Location update failed due to %s", answer ); + free(answer); + return false; +} + +bool AUDTELESCOPE::updateTime(ln_date *utc, double utc_offset) +{ + char *answer, buffer[256]; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Time update called before driver connection"); + return false; + } + + INDI_UNUSED(utc_offset); + + snprintf( buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%08.6fZ", utc->years, utc->months, utc->days, utc->hours, utc->minutes, utc->seconds ); + DEBUGF(INDI::Logger::DBG_SESSION,"Time update to %s ...", buffer ); + answer = sendCommandOnce("ASTRO_SETUTCDATE %s",buffer); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION,"Time update completed" ); + return true; + } + DEBUGF(INDI::Logger::DBG_WARNING,"Time update to %s failed due to %s", buffer, answer ); + free(answer); + return false; +} + +bool AUDTELESCOPE::ReadScopeStatus() +{ + char *answer; + int sts,pierside,exposureready,meridianflip; + double utc,lst,jd,ha,ra,dec,az,alt,meridianflipha; + + + answer = sendRequest("ASTRO_STATUS"); + if ( answer ) { + json j; + + j = json::parse(answer,nullptr,false); + free(answer); + if ( j.is_discarded() || + !j.contains("UTC") || + !j.contains("JD") || + !j.contains("LST") || + !j.contains("HA") || + !j.contains("RA") || + !j.contains("Dec") || + !j.contains("Az") || + !j.contains("Alt") || + !j.contains("globalStatus") || + !j.contains("meridianFlip") || + !j.contains("pierSide") || + !j.contains("meridianFlipHA") || + !j.contains("exposureReady") ) + { + DEBUG(INDI::Logger::DBG_WARNING,"Status communication error"); + return false; + } + j["UTC"].get_to(utc); + j["JD"].get_to(jd); + j["LST"].get_to(lst); + j["HA"].get_to(ha); + j["RA"].get_to(ra); + j["Dec"].get_to(dec); + j["Az"].get_to(az); + j["Alt"].get_to(alt); + j["globalStatus"].get_to(sts); + j["meridianFlip"].get_to(meridianflip); + j["pierSide"].get_to(pierside); + j["meridianFlipHA"].get_to(meridianflipha); + j["exposureReady"].get_to(exposureready); + if ( j.contains("errorMsg") ) { + std::string msg; + j["errorMsg"].get_to(msg); + if ( msg.length() > 0 ) { + if ( !lastErrorMsg || ( lastErrorMsg && strcmp(msg.c_str(),lastErrorMsg) ) ) { + // the error message is written only once until it changes + DEBUGF(INDI::Logger::DBG_WARNING,"Failed due to %s",msg.c_str()); + if ( lastErrorMsg ) + free( lastErrorMsg ); + lastErrorMsg = strdup(msg.c_str()); + } + } else { + if ( lastErrorMsg ) + free( lastErrorMsg ); + lastErrorMsg = NULL; + } + } else { + if ( lastErrorMsg ) + free( lastErrorMsg ); + lastErrorMsg = NULL; + } + + previousTrackState = TrackState; + + NewRaDec( ra, dec ); + switch ( sts ) { + case 0 : + TrackState = SCOPE_IDLE; + break; + case 1 : + TrackState = SCOPE_SLEWING; + break; + case 2 : + TrackState = SCOPE_TRACKING; + slewState = IPS_IDLE; + break; + case 3 : + TrackState = SCOPE_PARKING; + break; + case 4 : + TrackState = SCOPE_PARKED; + break; + } + + if ( fFirstTime ) { + SetParked( (TrackState==SCOPE_PARKED) ); + fFirstTime = false; + } else if ( previousTrackState != TrackState ) { + if ( TrackState == SCOPE_PARKED ) + SetParked(true); + else if ( previousTrackState == SCOPE_PARKED ) + SetParked(false); + } + + if ( pierside >= 0 ) { + if ( meridianflip && ( MeridianFlipSP[MFLIP_ON].getState() == ISS_OFF ) ) { + MeridianFlipSP[MFLIP_ON].setState(ISS_ON); + MeridianFlipSP[MFLIP_OFF].setState(ISS_OFF); + } + if ( !meridianflip && ( MeridianFlipSP[MFLIP_ON].getState() == ISS_ON ) ) { + MeridianFlipSP[MFLIP_ON].setState(ISS_OFF); + MeridianFlipSP[MFLIP_OFF].setState(ISS_ON); + } + } else { + MeridianFlipSP[MFLIP_ON].setState(ISS_OFF); + MeridianFlipSP[MFLIP_OFF].setState(ISS_ON); + } + MeridianFlipSP.apply(); + + MeridianFlipHANP[0].value = meridianflipha; + MeridianFlipHANP.apply(); + setPierSide((TelescopePierSide)pierside); + + if (LocalEqNP[LEQ_HA].value != ha || LocalEqNP[LEQ_DEC].value != dec || LocalEqNP.getState() != EqNP.s) + { + LocalEqNP[LEQ_HA].value = ha; + LocalEqNP[LEQ_DEC].value = dec; + LocalEqNP.setState(slewState); + LocalEqNP.apply(); + } + + if (AltAzNP[ALTAZ_AZ].value != az || AltAzNP[ALTAZ_ALT].value != alt || AltAzNP.getState() != EqNP.s) + { + AltAzNP[ALTAZ_AZ].value = az; + AltAzNP[ALTAZ_ALT].value = alt; + AltAzNP.setState(slewState); + AltAzNP.apply(); + } + + if (TTimeNP[TTIME_JD].value != utc || TTimeNP[TTIME_UTC].value != jd || TTimeNP[TTIME_LST].value != lst) + { + TTimeNP[TTIME_JD].value = jd; + TTimeNP[TTIME_UTC].value = utc; + TTimeNP[TTIME_LST].value = lst; + TTimeNP.setState(IPS_OK); + TTimeNP.apply(); + } + + return true; + } + + return false; +} + +bool AUDTELESCOPE::meridianFlipEnable(int enable) +{ + char *answer; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Set meridian flip called before driver connection"); + return false; + } + + DEBUGF(INDI::Logger::DBG_SESSION,"Set meridian flip to %s ...",(enable?"ENABLED":"DISABLED")); + answer = sendCommand("ASTRO_SETMERIDIANFLIP %d",enable); + if ( !answer ) { + DEBUGF(INDI::Logger::DBG_SESSION,"Set meridian flip to %s completed",(enable?"ENABLED":"DISABLED")); + return true; + } + DEBUGF(INDI::Logger::DBG_WARNING,"Set meridian flip to %s failed due to %s",(enable?"ENABLED":"DISABLED"),answer); + free(answer); + return false; +} + +bool AUDTELESCOPE::setMeridianFlipHA(double angle) +{ + char *answer; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Set meridian flip HA called before driver connection"); + return false; + } + + DEBUGF(INDI::Logger::DBG_SESSION,"Set meridian flip HA to %.3fdeg ...",angle); + answer = sendCommand("ASTRO_SETMERIDIANFLIPHA %.4f",angle); + if ( !answer ) { + DEBUGF(INDI::Logger::DBG_SESSION,"Set meridian flip HA to %.3fdeg completed",angle); + return true; + } + DEBUGF(INDI::Logger::DBG_WARNING,"Set meridian flip HA to %.3fdeg failed due to %s",angle,answer); + free(answer); + return false; +} + +bool AUDTELESCOPE::Sync(double ra, double dec) +{ + char *answer; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Sync called before driver connection"); + return false; + } + + DEBUGF(INDI::Logger::DBG_SESSION,"Sync to RA:%.3fhours Dec:%.3fdeg ...",ra,dec); + answer = sendCommand("ASTRO_SYNC %.8f %.8f",ra,dec); + if ( !answer ) { + DEBUGF(INDI::Logger::DBG_SESSION,"Sync to RA:%.3fhours Dec:%.3fdeg completed",ra,dec); + return true; + } + DEBUGF(INDI::Logger::DBG_WARNING,"Sync to RA:%.3fhours Dec:%.3fdeg failed due to %s",ra,dec,answer); + free(answer); + return false; +} + +bool AUDTELESCOPE::Park() +{ + char *answer; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Start telescope park called before driver connection"); + return false; + } + + TrackState = SCOPE_PARKING; + LOG_INFO("Start telescope park..."); + answer = sendCommand("ASTRO_PARK"); + if ( !answer ) { + ParkSP.s = IPS_BUSY; + IDSetSwitch(&ParkSP, NULL); + TrackState = SCOPE_PARKING; + DEBUG(INDI::Logger::DBG_SESSION,"Start telescope park completed"); + return true; + } + ParkSP.s = IPS_ALERT; + IDSetSwitch(&ParkSP, NULL); + TrackState = SCOPE_IDLE; + DEBUGF(INDI::Logger::DBG_WARNING,"Start telescope park failed due to %s",answer); + free(answer); + return false; +} + +bool AUDTELESCOPE::UnPark() +{ + char *answer; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Unparking telescope called before driver connection"); + return false; + } + + LOG_INFO("Unparking telescope..."); + answer = sendCommand("ASTRO_UNPARK"); + if ( !answer ) { + SetParked( false ); + DEBUG(INDI::Logger::DBG_SESSION,"Unparking telescope completed"); + return true; + } + DEBUGF(INDI::Logger::DBG_WARNING,"Unparking telescope failed due to %s",answer); + free(answer); + return false; +} + +bool AUDTELESCOPE::SetCurrentPark() +{ + char *answer; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Set park position called before driver connection"); + return false; + } + + LOG_INFO("Set park position..."); + answer = sendCommand("ASTRO_SETPARK"); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION,"Set park position completed"); + return true; + } + DEBUGF(INDI::Logger::DBG_WARNING,"Set park position failed due to %s",answer); + free(answer); + return false; +} + +bool AUDTELESCOPE::SetDefaultPark() +{ + char *answer; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Restore park position called before driver connection"); + return false; + } + + LOG_INFO("Restore park position..."); + answer = sendCommand("ASTRO_RESTOREPARK"); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION,"Restore park position completed"); + return true; + } + DEBUGF(INDI::Logger::DBG_WARNING,"Restore park position failed due to %s",answer); + free(answer); + return false; +} + +bool AUDTELESCOPE::SyncHome() +{ + char *answer; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Sync home position called before driver connection"); + return false; + } + + LOG_INFO("Sync home position..."); + answer = sendCommand("ASTRO_SYNCHOME"); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION,"Sync home position completed"); + return true; + } + DEBUGF(INDI::Logger::DBG_WARNING,"Sync home position failed due to %s",answer); + free(answer); + return false; +} + +bool AUDTELESCOPE::SlewToHome() +{ + char *answer; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Slew to home position called before driver connection"); + return false; + } + + LOG_INFO("Start slew to home position..."); + answer = sendCommand("ASTRO_POINTHOME"); + if ( !answer ) { + DEBUG(INDI::Logger::DBG_SESSION,"Start slew to home position completed"); + return true; + } + DEBUGF(INDI::Logger::DBG_WARNING,"Start slew to home position failed due to %s",answer); + free(answer); + return false; +} + +bool AUDTELESCOPE::Goto(double ra,double dec) +{ + ISwitch *sw; + + sw = IUFindSwitch(&CoordSP, "TRACK"); + if ((sw != nullptr) && (sw->s == ISS_ON)) { + return Slew(ra,dec,true); + } else { + return Slew(ra,dec,false); + } +} + +bool AUDTELESCOPE::Slew(double ra,double dec,int track) +{ + char *answer = NULL; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Start telescope slew called before driver connection"); + return false; + } + + slewState = IPS_BUSY; + DEBUGF(INDI::Logger::DBG_SESSION,"Start telescope slew to RA:%.4fhours Dec:%.3fdeg and %stracking ...",ra,dec,(track?"":"NO ")); + if ( track ) { + // point and track + answer = sendCommand("ASTRO_POINT %.8f %.8f %.8f %.8f",ra,dec,trackspeedra/3600.0,trackspeeddec/3600.0); + } else { + // point only + answer = sendCommand("ASTRO_POINT %.8f %.8f 0 0",ra,dec); + } + if ( !answer ) { + targetRA = ra; + targetDEC = dec; + TrackState = SCOPE_SLEWING; + slewState = IPS_OK; + DEBUGF(INDI::Logger::DBG_SESSION,"Start telescope slew to RA:%.4fhours Dec:%.3fdeg and %stracking completed",ra,dec,(track?"":"NO ")); + return true; + } + TrackState = SCOPE_IDLE; + slewState = IPS_ALERT; + DEBUGF(INDI::Logger::DBG_WARNING,"Start telescope slew to RA:%.4fhours Dec:%.3fdeg and %stracking failed due to %s",ra,dec,(track?"":"NO "),answer); + free(answer); + return false; +} + +bool AUDTELESCOPE::SetTrackMode(uint8_t mode) +{ + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Set tracking mode called before driver connection"); + return false; + } + + DEBUGF(INDI::Logger::DBG_SESSION,"Set tracking mode to %d...",mode); + switch (mode) { + case TRACK_SIDEREAL: + trackspeedra = TRACKRATE_SIDEREAL; + trackspeeddec = 0; + break; + case TRACK_SOLAR: + trackspeedra = TRACKRATE_SOLAR; + trackspeeddec = 0; + break; + case TRACK_LUNAR: + trackspeedra = TRACKRATE_LUNAR; + trackspeeddec = 0; + break; + case TRACK_CUSTOM: + trackspeedra = TrackRateN[AXIS_RA].value; + trackspeeddec = TrackRateN[AXIS_DE].value; + break; + } + if ( TrackState == SCOPE_TRACKING ) + SetTrackRate( trackspeedra, trackspeeddec ); + DEBUGF(INDI::Logger::DBG_SESSION,"Set tracking mode to %d completed",mode); + + return true; +} + +bool AUDTELESCOPE::SetTrackRate(double raRate, double deRate) +{ + char *answer; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Tracking change called before driver connection"); + return false; + } + + DEBUGF(INDI::Logger::DBG_SESSION,"Tracking change to RA:%f\"/s Dec:%f\"/s ...",raRate,deRate); + TrackStateSP.s = IPS_BUSY; + answer = sendCommand("ASTRO_TRACK %.8f %.8f",raRate/3600.0,deRate/3600.0); + if ( !answer ) { + if ( ( raRate == 0 ) && ( deRate == 0 ) ) + TrackStateSP.s = IPS_IDLE; + else + TrackStateSP.s = IPS_OK; + DEBUGF(INDI::Logger::DBG_SESSION,"Tracking change to RA:%f\"/s Dec:%f\"/s completed",raRate,deRate); + return true; + } + TrackStateSP.s = IPS_ALERT; + DEBUGF(INDI::Logger::DBG_WARNING,"Tracking change to RA:%f\"/s Dec:%f\"/s failed due to %s",raRate,deRate,answer); + free(answer); + return false; +} + +bool AUDTELESCOPE::SetTrackEnabled(bool enabled) +{ + int rc; + + DEBUGF(INDI::Logger::DBG_SESSION,"Change tracking to %s...",((enabled)?"ENABLED":"DISABLED")); + if ( enabled ) { + int mode = IUFindOnSwitchIndex(&TrackModeSP); + SetTrackMode(mode); + if ( TrackState != SCOPE_TRACKING ) + rc = SetTrackRate( trackspeedra, trackspeeddec ); + else + rc = true; + } else { + rc = SetTrackRate( 0, 0 ); + } + DEBUGF(INDI::Logger::DBG_SESSION,"Change tracking to %s completed",((enabled)?"ENABLED":"DISABLED")); + return rc; +} + +bool AUDTELESCOPE::MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) +{ + char *answer = NULL; + string speed[] = {"SLEWGUIDE","SLEWCENTER","SLEWFIND","SLEWMAX"}; + int speedIndex; + + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "MoveNS called before driver connection"); + return false; + } + + speedIndex = IUFindOnSwitchIndex(&SlewRateSP); + MovementNSSP.s = IPS_BUSY; + if ( command == MOTION_START ) { + // force tracking after motion + fTracking = true; + if ( dir == DIRECTION_NORTH ) + answer = sendCommand("ASTRO_SLEW * (%.8f+%s)",trackspeeddec/3600.0,speed[speedIndex].c_str()); + else + answer = sendCommand("ASTRO_SLEW * (%.8f-%s)",trackspeeddec/3600.0,speed[speedIndex].c_str()); + } else { + if ( fTracking ) + answer = sendCommand("ASTRO_TRACK * %.8f",trackspeeddec/3600.0); + else + answer = sendCommand("ASTRO_SLEW * 0"); + } + if ( !answer ) { + if ( command == MOTION_START ) + MovementNSSP.s = IPS_OK; + else + MovementNSSP.s = IPS_IDLE; + return true; + } + MovementNSSP.s = IPS_ALERT; + DEBUGF(INDI::Logger::DBG_WARNING,"MoveNS command failed due to %s",answer); + free(answer); + return false; +} + +bool AUDTELESCOPE::MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) +{ + char *answer = NULL; + string speed[] = {"SLEWGUIDE","SLEWCENTER","SLEWFIND","SLEWMAX"}; + int speedIndex; + + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "MoveWE called before driver connection"); + return false; + } + + speedIndex = IUFindOnSwitchIndex(&SlewRateSP); + MovementWESP.s = IPS_BUSY; + if ( command == MOTION_START ) { + // force tracking after motion + fTracking = true; + if ( dir == DIRECTION_WEST ) + answer = sendCommand("ASTRO_SLEW (%.8f+%s) *",trackspeedra/3600.0,speed[speedIndex].c_str()); + else + answer = sendCommand("ASTRO_SLEW (%.8f-%s) *",trackspeedra/3600.0,speed[speedIndex].c_str()); + } else { + if ( fTracking ) + answer = sendCommand("ASTRO_TRACK %.8f *",trackspeedra/3600.0); + else + answer = sendCommand("ASTRO_SLEW 0 *"); + } + if ( !answer ) { + if ( command == MOTION_START ) + MovementWESP.s = IPS_OK; + else + MovementWESP.s = IPS_IDLE; + return true; + } + MovementWESP.s = IPS_ALERT; + DEBUGF(INDI::Logger::DBG_WARNING,"MoveWE command failed due to %s",answer); + free(answer); + return false; +} + +IPState AUDTELESCOPE::GuideNorth(uint32_t ms) +{ + char *answer = NULL; + IPState rc; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "GuideNorth called before driver connection"); + return IPS_ALERT; + } + + if ( ms == 0 ) + return IPS_OK; + + answer = sendCommand("ASTRO_GUIDE * %u",ms); + if ( !answer ) { + rc = IPS_OK; + } else { + rc = IPS_ALERT; + DEBUGF(INDI::Logger::DBG_WARNING,"GuideNorth command failed due to %s",answer); + free(answer); + } + GuideComplete(INDI_EQ_AXIS::AXIS_DE); + return rc; +} + +IPState AUDTELESCOPE::GuideSouth(uint32_t ms) +{ + char *answer = NULL; + IPState rc; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "GuideSouth called before driver connection"); + return IPS_ALERT; + } + + if ( ms == 0 ) + return IPS_OK; + + answer = sendCommand("ASTRO_GUIDE * -%u",ms); + if ( !answer ) { + rc = IPS_OK; + } else { + rc = IPS_ALERT; + DEBUGF(INDI::Logger::DBG_WARNING,"GuideSouth command failed due to %s",answer); + free(answer); + } + GuideComplete(INDI_EQ_AXIS::AXIS_DE); + return rc; +} + +IPState AUDTELESCOPE::GuideEast(uint32_t ms) +{ + char *answer = NULL; + IPState rc; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "GuideEast called before driver connection"); + return IPS_ALERT; + } + + if ( ms == 0 ) + return IPS_OK; + + answer = sendCommand("ASTRO_GUIDE %u *",ms); + if ( !answer ) { + rc = IPS_OK; + } else { + rc = IPS_ALERT; + DEBUGF(INDI::Logger::DBG_WARNING,"GuideEast command failed due to %s",answer); + free(answer); + } + GuideComplete(INDI_EQ_AXIS::AXIS_RA); + return rc; +} + +IPState AUDTELESCOPE::GuideWest(uint32_t ms) +{ + char *answer = NULL; + IPState rc; + + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "GuideWest called before driver connection"); + return IPS_ALERT; + } + + if ( ms == 0 ) + return IPS_OK; + + answer = sendCommand("ASTRO_GUIDE -%u *",ms); + if ( !answer ) { + rc = IPS_OK; + } else { + rc = IPS_ALERT; + DEBUGF(INDI::Logger::DBG_WARNING,"GuideWest command failed due to %s",answer); + free(answer); + } + GuideComplete(INDI_EQ_AXIS::AXIS_RA); + return rc; +} + +bool AUDTELESCOPE::Abort() +{ + if (!isConnected()) { + DEBUG(INDI::Logger::DBG_WARNING, "Abort called before driver connection"); + return false; + } + + AbortSP.s = IPS_OK; + + DEBUG(INDI::Logger::DBG_SESSION, "Telescope abort ..."); + + if (isConnected()) { + char *answer; + answer = sendCommand("ASTRO_STOP"); + free(answer); + } + + AbortSP.s = IPS_IDLE; + IUResetSwitch(&AbortSP); + IDSetSwitch(&AbortSP, NULL); + + slewState = IPS_IDLE; + + DEBUG(INDI::Logger::DBG_SESSION, "Telescope abort completed"); + + return true; +} + +void AUDTELESCOPE::TimerHit() +{ + if (isConnected() == false) + return; + + ReadScopeStatus(); + IDSetNumber(&EqNP, NULL); + + SetTimer(getCurrentPollingPeriod()); +} + +bool AUDTELESCOPE::saveConfigItems(FILE *fp) +{ + ConfigTP.save(fp); + MeridianFlipSP.save(fp); + MeridianFlipHANP.save(fp); + + return INDI::Telescope::saveConfigItems(fp); +} + +const char *AUDTELESCOPE::getDefaultName() +{ + return device_str; +} + +char* AUDTELESCOPE::sendCommand(const char *fmt, ...) +{ + va_list ap; + char buffer[4096], answer[4096], addr[1024]; + int rc,retries; + zmq_pollitem_t item; + + va_start( ap, fmt ); + vsnprintf( buffer, sizeof(buffer), fmt, ap ); + va_end( ap ); + + pthread_mutex_lock( &connectionmutex ); + retries = 3; + do { + zmq_send(requester, buffer, strlen(buffer), 0); + item = { requester, 0, ZMQ_POLLIN, 0 }; + rc = zmq_poll( &item, 1, 500 ); // ms + if ( ( rc >= 0 ) && ( item.revents & ZMQ_POLLIN ) ) { + // communication succeeded + rc = zmq_recv(requester, answer, sizeof(answer), 0); + if ( rc >= 0 ) { + pthread_mutex_unlock( &connectionmutex ); + answer[MIN(rc,(int)sizeof(answer)-1)] = '\0'; + if ( !strncmp(answer,"OK",2) ) + return NULL; + if ( !strncmp(answer,"ERROR:",6) ) + return strdup(answer+6); + return strdup("SYNTAXERROR"); + } + } + zmq_close(requester); + requester = zmq_socket(context, ZMQ_REQ); + snprintf( addr, sizeof(addr), "tcp://%s:%d", IPaddress, IPport ); + zmq_connect(requester, addr); + } while ( --retries ); + pthread_mutex_unlock( &connectionmutex ); + DEBUG(INDI::Logger::DBG_WARNING, "No answer from driver"); + return strdup("COMMUNICATIONERROR"); +} + +char* AUDTELESCOPE::sendCommandOnce(const char *fmt, ...) +{ + va_list ap; + char buffer[4096], answer[4096], addr[1024]; + int rc; + zmq_pollitem_t item; + + va_start( ap, fmt ); + vsnprintf( buffer, sizeof(buffer), fmt, ap ); + va_end( ap ); + + pthread_mutex_lock( &connectionmutex ); + zmq_send(requester, buffer, strlen(buffer), 0); + item = { requester, 0, ZMQ_POLLIN, 0 }; + rc = zmq_poll( &item, 1, 500 ); // ms + if ( ( rc >= 0 ) && ( item.revents & ZMQ_POLLIN ) ) { + // communication succeeded + rc = zmq_recv(requester, answer, sizeof(answer), 0); + if ( rc >= 0 ) { + pthread_mutex_unlock( &connectionmutex ); + answer[MIN(rc,(int)sizeof(answer)-1)] = '\0'; + if ( !strncmp(answer,"OK",2) ) + return NULL; + if ( !strncmp(answer,"ERROR:",6) ) + return strdup(answer+6); + return strdup("SYNTAXERROR"); + } + } + zmq_close(requester); + requester = zmq_socket(context, ZMQ_REQ); + snprintf( addr, sizeof(addr), "tcp://%s:%d", IPaddress, IPport ); + zmq_connect(requester, addr); + pthread_mutex_unlock( &connectionmutex ); + DEBUG(INDI::Logger::DBG_WARNING, "No answer from driver"); + return strdup("COMMUNICATIONERROR"); +} + +char* AUDTELESCOPE::sendRequest(const char *fmt, ...) +{ + va_list ap; + char buffer[4096], answer[4096], addr[1024]; + int rc,retries; + zmq_pollitem_t item; + + va_start( ap, fmt ); + vsnprintf( buffer, sizeof(buffer), fmt, ap ); + va_end( ap ); + + pthread_mutex_lock( &connectionmutex ); + retries = 3; + do { + zmq_send(requester, buffer, strlen(buffer), 0); + item = { requester, 0, ZMQ_POLLIN, 0 }; + rc = zmq_poll( &item, 1, 500 ); // ms + if ( ( rc >= 0 ) && ( item.revents & ZMQ_POLLIN ) ) { + // communication succeeded + rc = zmq_recv(requester, answer, sizeof(answer), 0); + if ( rc >= 0 ) { + pthread_mutex_unlock( &connectionmutex ); + answer[MIN(rc,(int)sizeof(answer)-1)] = '\0'; + return strdup(answer); + } + } + zmq_close(requester); + requester = zmq_socket(context, ZMQ_REQ); + snprintf( addr, sizeof(addr), "tcp://%s:%d", IPaddress, IPport ); + zmq_connect(requester, addr); + } while ( --retries ); + pthread_mutex_unlock( &connectionmutex ); + DEBUG(INDI::Logger::DBG_WARNING, "No answer from driver"); + return strdup("COMMUNICATIONERROR"); +} diff --git a/indi-avalonud/indi_avalonud_telescope.h b/indi-avalonud/indi_avalonud_telescope.h new file mode 100644 index 000000000..d6cd55381 --- /dev/null +++ b/indi-avalonud/indi_avalonud_telescope.h @@ -0,0 +1,181 @@ +/* + Avalon Unified Driver Telescope + + Copyright (C) 2020,2023 + + 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 AUDTELESCOPE_H +#define AUDTELESCOPE_H + +#include + +#include +#include +#include +#include + + +#define MIN(a,b) (((a)<=(b))?(a):(b)) + + +using namespace std; + + +class AUDTELESCOPE : public INDI::Telescope, public INDI::GuiderInterface +{ + + enum FLIP { FLIP_ON, FLIP_OFF }; + enum + { + MOUNT_EQUATORIAL, + MOUNT_ALTAZ + }; + +public: + AUDTELESCOPE(); + ~AUDTELESCOPE(); + + // Standard INDI interface functions + virtual bool Connect() override; + virtual bool Disconnect() override; + + const char *getDefaultName() override; + virtual bool initProperties() override; + virtual bool updateProperties() override; + virtual bool saveConfigItems(FILE *fp) override; + virtual void ISGetProperties(const char *dev) 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 ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n) override; + +protected: + virtual bool ReadScopeStatus() override; + virtual bool Sync(double, double) override; + virtual bool Goto(double, double) override; + virtual bool Abort() override; + virtual void TimerHit() override; + + // Parking + virtual bool SetCurrentPark() override; + virtual bool SetDefaultPark() override; + virtual bool Park() override; + virtual bool UnPark() override; + + virtual bool SetTrackMode(uint8_t mode) override; + virtual bool SetTrackRate(double raRate, double deRate) override; + virtual bool SetTrackEnabled(bool enabled) override; + + virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override; + virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override; + + virtual bool updateTime(ln_date *utc, double utc_offset) override; + virtual bool updateLocation(double latitude, double longitude, double elevation) override; + + // Guide + virtual IPState GuideNorth(uint32_t ms) override; + virtual IPState GuideSouth(uint32_t ms) override; + virtual IPState GuideEast(uint32_t ms) override; + virtual IPState GuideWest(uint32_t ms) override; + +private: + int tid; + int mounttype; + + double targetRA; + double targetDEC; + + enum { + MM_EQUATORIAL, + MM_ALTAZ, + MM_N + }; + INDI::PropertySwitch MountModeSP {MM_N}; + INDI::PropertyText ConfigTP {1}; + enum { + LEQ_HA, + LEQ_DEC, + LEQ_N + }; + INDI::PropertyNumber LocalEqNP {LEQ_N}; + enum { + ALTAZ_AZ, + ALTAZ_ALT, + ALTAZ_N + }; + INDI::PropertyNumber AltAzNP {ALTAZ_N}; + enum { + TTIME_JD, + TTIME_UTC, + TTIME_LST, + TTIME_N + }; + INDI::PropertyNumber TTimeNP {TTIME_N}; + enum { + HOME_SYNC, + HOME_SLEW, + HOME_N + }; + INDI::PropertySwitch HomeSP {HOME_N}; + enum { + MFLIP_ON, + MFLIP_OFF, + MFLIP_N + }; + INDI::PropertySwitch MeridianFlipSP {MFLIP_N}; + INDI::PropertyNumber MeridianFlipHANP {1}; + + INDI::PropertyText HWTypeTP {1}; + INDI::PropertyText HWModelTP {1}; + INDI::PropertyText HWIdentifierTP {1}; + enum { + LLSW_NAME, + LLSW_VERSION, + LLSW_N + }; + INDI::PropertyText LowLevelSWTP {LLSW_N}; + enum { + HLSW_NAME, + HLSW_VERSION, + HLSW_N + }; + INDI::PropertyText HighLevelSWTP {HLSW_N}; + + bool Slew(double, double, int); + bool meridianFlipEnable(int); + bool setMeridianFlipHA(double); + bool SyncHome(); + bool SlewToHome(); + + // Variables + bool fFirstTime,fTracking; + int northernHemisphere; + IPState slewState; + TelescopeStatus previousTrackState; + double trackspeedra,trackspeeddec; + + char* IPaddress; + char* sendCommand(const char*,...); + char* sendCommandOnce(const char*,...); + char* sendRequest(const char*,...); + + void *context,*requester; + char *lastErrorMsg; + + pthread_mutex_t connectionmutex; +}; + +#endif diff --git a/scripts/requisites-install.sh b/scripts/requisites-install.sh index ad42fffaf..1b0e3f041 100755 --- a/scripts/requisites-install.sh +++ b/scripts/requisites-install.sh @@ -19,7 +19,8 @@ case "$OS" in git \ cfitsio libnova libusb curl \ gsl jpeg fftw \ - ffmpeg libftdi libraw libdc1394 libgphoto2 opencv + ffmpeg libftdi libraw libdc1394 libgphoto2 opencv \ + zmq ;; Linux) . /etc/os-release @@ -37,7 +38,8 @@ case "$OS" in libftdi1-dev libavcodec-dev libavdevice-dev libavformat-dev libswscale-dev \ libgps-dev libraw-dev libdc1394-dev libgphoto2-dev \ libboost-dev libboost-regex-dev liblimesuite-dev libopencv-dev libopencv-imgproc \ - libopencv-highgui + libopencv-highgui \ + libzmq3-dev ;; fedora) $(command -v sudo) dnf upgrade -y @@ -54,8 +56,8 @@ case "$OS" in ffmpeg-devel \ libftdi-devel \ gpsd-devel LibRaw-devel libdc1394-devel libgphoto2-devel \ - boost-devel - + boost-devel \ + zeromq-devel ;; centos) # CentOS 8 dont have libnova-devel package @@ -75,7 +77,8 @@ case "$OS" in openssh git \ cmake gcc-c++ zlib-devel \ cfitsio-devel libnova-devel libusb-devel libcurl-devel \ - gsl-devel libjpeg-devel fftw-devel libtheora-devel opencv-devel + gsl-devel libjpeg-devel fftw-devel libtheora-devel opencv-devel \ + zeromq-devel ;; *) echo "Unknown Linux Distribution: $ID"