From 2a55c9cfd18d4898f81cdae9477b998814d5d9c3 Mon Sep 17 00:00:00 2001 From: James McKenzie Date: Tue, 24 Jan 2023 23:28:19 +0000 Subject: add stock lx200basic as new telescope lxd650 --- .gitignore | 8 + indi-lxd650/CMakeLists.txt | 33 + indi-lxd650/Makefile | 278 +++++ indi-lxd650/config.h.cmake | 10 + indi-lxd650/indi_lxd650.xml.cmake | 16 + indi-lxd650/lx200driver.cpp | 2165 +++++++++++++++++++++++++++++++++++++ indi-lxd650/lx200driver.h | 317 ++++++ indi-lxd650/lxd650.cpp | 429 ++++++++ indi-lxd650/lxd650.h | 59 + 9 files changed, 3315 insertions(+) create mode 100644 indi-lxd650/CMakeLists.txt create mode 100644 indi-lxd650/Makefile create mode 100644 indi-lxd650/config.h.cmake create mode 100644 indi-lxd650/indi_lxd650.xml.cmake create mode 100644 indi-lxd650/lx200driver.cpp create mode 100644 indi-lxd650/lx200driver.h create mode 100644 indi-lxd650/lxd650.cpp create mode 100644 indi-lxd650/lxd650.h diff --git a/.gitignore b/.gitignore index 09fcdd7..4fef8cf 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,11 @@ indi-celestronaux/config.h indi-celestronaux/indi_celestronaux.xml indi-celestronaux/indi_celestron_aux indi-celestronaux/install_manifest.txt +indi-lxd650/CMakeCache.txt +indi-lxd650/CMakeFiles/ +indi-lxd650/Makefile +indi-lxd650/cmake_install.cmake +indi-lxd650/config.h +indi-lxd650/indi_lxd650 +indi-lxd650/indi_lxd650.xml + diff --git a/indi-lxd650/CMakeLists.txt b/indi-lxd650/CMakeLists.txt new file mode 100644 index 0000000..4e60670 --- /dev/null +++ b/indi-lxd650/CMakeLists.txt @@ -0,0 +1,33 @@ +########### Celestron AUX INDI driver ############## +PROJECT(indi-lxd650 C CXX) +cmake_minimum_required(VERSION 3.0) + +include(GNUInstallDirs) + +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/") +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake_modules/") + +find_package(INDI REQUIRED) +find_package(Nova REQUIRED) +find_package(ZLIB REQUIRED) +find_package(GSL REQUIRED) + +set(CAUX_VERSION_MAJOR 1) +set(CAUX_VERSION_MINOR 2) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h ) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/indi_lxd650.xml.cmake ${CMAKE_CURRENT_BINARY_DIR}/indi_lxd650.xml ) + +include_directories( ${CMAKE_CURRENT_BINARY_DIR}) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR}) +include_directories( ${INDI_INCLUDE_DIR}) +include_directories( ${NOVA_INCLUDE_DIR}) +include_directories( ${EV_INCLUDE_DIR}) + +include(CMakeCommon) + +add_executable(indi_lxd650 lxd650.cpp lx200driver.cpp) +target_link_libraries(indi_lxd650 ${INDI_LIBRARIES} ${NOVA_LIBRARIES} ${GSL_LIBRARIES}) +install(TARGETS indi_lxd650 RUNTIME DESTINATION bin) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/indi_lxd650.xml DESTINATION ${INDI_DATA_DIR}) diff --git a/indi-lxd650/Makefile b/indi-lxd650/Makefile new file mode 100644 index 0000000..881c4be --- /dev/null +++ b/indi-lxd650/Makefile @@ -0,0 +1,278 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 3.17 + +# Default target executed when no arguments are given to make. +default_target: all + +.PHONY : default_target + +# Allow only one "make -f Makefile2" at a time, but pass parallelism. +.NOTPARALLEL: + + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + + +# Disable VCS-based implicit rules. +% : %,v + + +# Disable VCS-based implicit rules. +% : RCS/% + + +# Disable VCS-based implicit rules. +% : RCS/%,v + + +# Disable VCS-based implicit rules. +% : SCCS/s.% + + +# Disable VCS-based implicit rules. +% : s.% + + +.SUFFIXES: .hpux_make_needs_suffix_list + + +# Command-line flag to silence nested $(MAKE). +$(VERBOSE)MAKESILENT = -s + +# Suppress display of executed commands. +$(VERBOSE).SILENT: + + +# A target that is always out of date. +cmake_force: + +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /usr/bin/cmake + +# The command to remove a file. +RM = /usr/bin/cmake -E rm -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /root/projects/telescope/indi_mount_driver/indi-lxd650 + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /root/projects/telescope/indi_mount_driver/indi-lxd650 + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target install/strip +install/strip: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." + /usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip + +# Special rule for the target install/strip +install/strip/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." + /usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip/fast + +# Special rule for the target install/local +install/local: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." + /usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local + +# Special rule for the target install/local +install/local/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." + /usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local/fast + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake cache editor..." + /usr/bin/ccmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache + +.PHONY : edit_cache/fast + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." + /usr/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache + +.PHONY : rebuild_cache/fast + +# Special rule for the target list_install_components +list_install_components: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Available install components are: \"Unspecified\"" +.PHONY : list_install_components + +# Special rule for the target list_install_components +list_install_components/fast: list_install_components + +.PHONY : list_install_components/fast + +# Special rule for the target install +install: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." + /usr/bin/cmake -P cmake_install.cmake +.PHONY : install + +# Special rule for the target install +install/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." + /usr/bin/cmake -P cmake_install.cmake +.PHONY : install/fast + +# The main all target +all: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /root/projects/telescope/indi_mount_driver/indi-lxd650/CMakeFiles /root/projects/telescope/indi_mount_driver/indi-lxd650/CMakeFiles/progress.marks + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 all + $(CMAKE_COMMAND) -E cmake_progress_start /root/projects/telescope/indi_mount_driver/indi-lxd650/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 clean +.PHONY : clean + +# The main clean target +clean/fast: clean + +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +#============================================================================= +# Target rules for targets named indi_lxd650 + +# Build rule for target. +indi_lxd650: cmake_check_build_system + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 indi_lxd650 +.PHONY : indi_lxd650 + +# fast build rule for target. +indi_lxd650/fast: + $(MAKE) $(MAKESILENT) -f CMakeFiles/indi_lxd650.dir/build.make CMakeFiles/indi_lxd650.dir/build +.PHONY : indi_lxd650/fast + +lx200driver.o: lx200driver.cpp.o + +.PHONY : lx200driver.o + +# target to build an object file +lx200driver.cpp.o: + $(MAKE) $(MAKESILENT) -f CMakeFiles/indi_lxd650.dir/build.make CMakeFiles/indi_lxd650.dir/lx200driver.cpp.o +.PHONY : lx200driver.cpp.o + +lx200driver.i: lx200driver.cpp.i + +.PHONY : lx200driver.i + +# target to preprocess a source file +lx200driver.cpp.i: + $(MAKE) $(MAKESILENT) -f CMakeFiles/indi_lxd650.dir/build.make CMakeFiles/indi_lxd650.dir/lx200driver.cpp.i +.PHONY : lx200driver.cpp.i + +lx200driver.s: lx200driver.cpp.s + +.PHONY : lx200driver.s + +# target to generate assembly for a file +lx200driver.cpp.s: + $(MAKE) $(MAKESILENT) -f CMakeFiles/indi_lxd650.dir/build.make CMakeFiles/indi_lxd650.dir/lx200driver.cpp.s +.PHONY : lx200driver.cpp.s + +lxd650.o: lxd650.cpp.o + +.PHONY : lxd650.o + +# target to build an object file +lxd650.cpp.o: + $(MAKE) $(MAKESILENT) -f CMakeFiles/indi_lxd650.dir/build.make CMakeFiles/indi_lxd650.dir/lxd650.cpp.o +.PHONY : lxd650.cpp.o + +lxd650.i: lxd650.cpp.i + +.PHONY : lxd650.i + +# target to preprocess a source file +lxd650.cpp.i: + $(MAKE) $(MAKESILENT) -f CMakeFiles/indi_lxd650.dir/build.make CMakeFiles/indi_lxd650.dir/lxd650.cpp.i +.PHONY : lxd650.cpp.i + +lxd650.s: lxd650.cpp.s + +.PHONY : lxd650.s + +# target to generate assembly for a file +lxd650.cpp.s: + $(MAKE) $(MAKESILENT) -f CMakeFiles/indi_lxd650.dir/build.make CMakeFiles/indi_lxd650.dir/lxd650.cpp.s +.PHONY : lxd650.cpp.s + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... edit_cache" + @echo "... install" + @echo "... install/local" + @echo "... install/strip" + @echo "... list_install_components" + @echo "... rebuild_cache" + @echo "... indi_lxd650" + @echo "... lx200driver.o" + @echo "... lx200driver.i" + @echo "... lx200driver.s" + @echo "... lxd650.o" + @echo "... lxd650.i" + @echo "... lxd650.s" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/indi-lxd650/config.h.cmake b/indi-lxd650/config.h.cmake new file mode 100644 index 0000000..be71a44 --- /dev/null +++ b/indi-lxd650/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 CAUX_VERSION_MAJOR @CAUX_VERSION_MAJOR@ +#define CAUX_VERSION_MINOR @CAUX_VERSION_MINOR@ + +#endif // CONFIG_H diff --git a/indi-lxd650/indi_lxd650.xml.cmake b/indi-lxd650/indi_lxd650.xml.cmake new file mode 100644 index 0000000..3d54e30 --- /dev/null +++ b/indi-lxd650/indi_lxd650.xml.cmake @@ -0,0 +1,16 @@ +# 1 "../indi-celestronaux/indi_celestronaux.xml.cmake" +# 1 "" +# 1 "" +# 31 "" +# 1 "/usr/include/stdc-predef.h" 1 3 4 +# 32 "" 2 +# 1 "../indi-celestronaux/indi_celestronaux.xml.cmake" + + + + + indi_lxd650 + @LXD650_VERSION_MAJOR@.@LXD650_VERSION_MINOR@ + + + diff --git a/indi-lxd650/lx200driver.cpp b/indi-lxd650/lx200driver.cpp new file mode 100644 index 0000000..6d26e6b --- /dev/null +++ b/indi-lxd650/lx200driver.cpp @@ -0,0 +1,2165 @@ +#if 0 +LX200 Driver +Copyright (C) 2003 Jasem Mutlaq (mutlaqja@ikarustech.com) + +This library is free software; +you can redistribute it and / or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; +either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; +without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; +if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA + +#endif + +#include "lx200driver.h" + +#include "indicom.h" +#include "indilogger.h" + +#include +#include + +#ifndef _WIN32 +#include +#endif + +#ifdef __FreeBSD__ +#include +#endif + +/* Add mutex */ + +#include + +#define LX200_TIMEOUT 5 /* FD timeout in seconds */ +#define RB_MAX_LEN 64 + + +int eq_format; /* For possible values see enum TEquatorialFormat */ +int geo_format = LX200_GEO_SHORT_FORMAT; /* For possible values see enum TGeographicFormat */ +char lx200Name[MAXINDIDEVICE]; +/* ESN DEBUG */ +unsigned int DBG_SCOPE = 8; + +/* Add mutex to communications */ +std::mutex lx200CommsLock; + +void setLX200Debug(const char *deviceName, unsigned int debug_level) +{ + strncpy(lx200Name, deviceName, MAXINDIDEVICE); + DBG_SCOPE = debug_level; +} + +int check_lx200_connection(int in_fd) +{ + const struct timespec timeout = {0, 50000000L}; + int i = 0; + char ack[1] = { 0x06 }; + char MountAlign[64]; + int nbytes_read = 0; + + DEBUGDEVICE(lx200Name, INDI::Logger::DBG_DEBUG, "Testing telescope connection using ACK..."); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + if (in_fd <= 0) + return -1; + + for (i = 0; i < 2; i++) + { + // Meade Telescope Serial Command Protocol Revision 2010.10 + // ACK <0x06> Query of alignment mounting mode. + // Returns: + // A If scope in AltAz Mode + // D If scope is currently in the Downloader [Autostar II & Autostar] + // L If scope in Land Mode + // P If scope in Polar Mode + if (write(in_fd, ack, 1) < 0) + return -1; + tty_read(in_fd, MountAlign, 1, LX200_TIMEOUT, &nbytes_read); + if (nbytes_read == 1) + { + DEBUGDEVICE(lx200Name, INDI::Logger::DBG_DEBUG, "Testing successful!"); + return 0; + } + nanosleep(&timeout, nullptr); + } + + DEBUGDEVICE(lx200Name, INDI::Logger::DBG_DEBUG, "Failure. Telescope is not responding to ACK!"); + return -1; +} + +/********************************************************************** +* GET +**********************************************************************/ + +char ACK(int fd) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + + char ack[1] = { 0x06 }; + char MountAlign[2]; + int nbytes_write = 0, nbytes_read = 0, error_type; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%#02X>", ack[0]); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // ACK <0x06> Query of alignment mounting mode. + // Returns: + // A If scope in AltAz Mode + // D If scope is currently in the Downloader [Autostar II & Autostar] + // L If scope in Land Mode + // P If scope in Polar Mode + nbytes_write = write(fd, ack, 1); + + if (nbytes_write < 0) + return -1; + + error_type = tty_read(fd, MountAlign, 1, LX200_TIMEOUT, &nbytes_read); + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%c>", MountAlign[0]); + + if (nbytes_read == 1) + return MountAlign[0]; + else + return error_type; +} + +int getCommandSexa(int fd, double *value, const char *cmd) +{ + char read_buffer[RB_MAX_LEN] = {0}; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + tcflush(fd, TCIFLUSH); + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", cmd); + + if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, read_buffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + tcflush(fd, TCIFLUSH); + if (error_type != TTY_OK) + return error_type; + + read_buffer[nbytes_read - 1] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", read_buffer); + + if (f_scansexa(read_buffer, value)) + { + DEBUGDEVICE(lx200Name, DBG_SCOPE, "Unable to parse response"); + return -1; + } + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "VAL [%g]", *value); + + tcflush(fd, TCIFLUSH); + return 0; +} + +int getCommandInt(int fd, int *value, const char *cmd) +{ + char read_buffer[RB_MAX_LEN] = {0}; + float temp_number; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + tcflush(fd, TCIFLUSH); + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", cmd); + + if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, read_buffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + tcflush(fd, TCIFLUSH); + if (error_type != TTY_OK) + return error_type; + + read_buffer[nbytes_read - 1] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", read_buffer); + + /* Float */ + if (strchr(read_buffer, '.')) + { + if (sscanf(read_buffer, "%f", &temp_number) != 1) + return -1; + + *value = static_cast(temp_number); + } + /* Int */ + else if (sscanf(read_buffer, "%d", value) != 1) + return -1; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "VAL [%d]", *value); + + return 0; +} + +int getCommandString(int fd, char *data, const char *cmd) +{ + char *term; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", cmd); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, data, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + tcflush(fd, TCIFLUSH); + + if (error_type != TTY_OK) + return error_type; + + term = strchr(data, '#'); + if (term) + *term = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", data); + + return 0; +} + +int isSlewComplete(int fd) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + /* update for classic lx200, total string returned is 33 bytes */ + char data[33] = { 0 }; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + const char *cmd = ":D#"; + + /* update for slew complete lx200 classic 3.2. roms */ + int i; + + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", cmd); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :D# + // Requests a string of bars indicating the distance to the current target location. + // Returns: + // LX200's – a string of bar characters indicating the distance. + // Autostars and Autostar II – a string containing one bar until a slew is complete, then a null string is returned + // + // 10Micron Mount Command Protocol software version 2.14.11 2016.11 + // :D# + // Requests a string indicating the progress of the current slew operation. + // Returns: + // the string “■#”, where the block character has ascii code 127 (0x7F), if a slew is in + // progress or a slew has ended from less than the settle time set in command :Sstm. + // the string “#” if a slew has been completed or no slew is underway. + if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, data, 33, '#', LX200_TIMEOUT, &nbytes_read); + tcflush(fd, TCIOFLUSH); + + if (error_type != TTY_OK) + return error_type; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", data); + /* update for slewComplete + + The below should handle classic lx200, autostar and autostar 2 + classic returns string of 33 bytes, and non space (0x20) before terminator is not done yet + autostar and autostar 2 return a few bytes, with '#' terminator + first char + */ + for(i = 0; i < 33; i++) + { + if(data[i] == '#') return 1; + if(data[i] != 0x20) return 0; + } + return 1; + /* out for slewComplete update + if (data[0] == '#') + return 1; + else + return 0; + END out for slewComplete update */ +} + +int getCalendarDate(int fd, char *date) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int dd, mm, yy, YYYY; + int error_type; + int nbytes_read = 0; + char mell_prefix[3] = {0}; + int len = 0; + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :GC# + // Get current date. + // Returns: MM/DD/YY# + // The current local calendar date for the telescope. + // + // 10Micron Mount Command Protocol software version 2.14.11 2016.11 + // :GC# + // Get current date. Returns the current date formatted as follows: + // Emulation and precision Return value + // LX200 emulation, low and high precision MM/DD/YY# (month, day, year) + // Extended emulation, low and high precision MM:DD:YY# (month, day, year) – note that the separator character is ':' instead of '/'. + // Any emulation, ultra precision YYYY-MM-DD# (year, month, day) – note that the separator character is '-' instead of '/'. + if ((error_type = getCommandString(fd, date, ":GC#"))) + return error_type; + len = strnlen(date, 32); + if (len == 10) + { + /* 10Micron Ultra Precision mode calendar date format is YYYY-MM-DD */ + nbytes_read = sscanf(date, "%4d-%2d-%2d", &YYYY, &mm, &dd); + if (nbytes_read < 3) + return -1; + /* We're done, date is already in ISO format */ + } + else + { + /* Meade format is MM/DD/YY */ + nbytes_read = sscanf(date, "%d%*c%d%*c%d", &mm, &dd, &yy); + if (nbytes_read < 3) + return -1; + /* We consider years 50 or more to be in the last century, anything less in the 21st century.*/ + if (yy > 50) + strncpy(mell_prefix, "19", 3); + else + strncpy(mell_prefix, "20", 3); + /* We need to have it in YYYY-MM-DD ISO format */ + snprintf(date, 32, "%s%02d-%02d-%02d", mell_prefix, yy, mm, dd); + } + return (0); +} + +int getTimeFormat(int fd, int *format) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + int tMode; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":Gc#"); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :Gc# + // Get Clock Format + // Returns: 12# or 24# + // Depending on the current telescope format setting. + if ((error_type = tty_write_string(fd, ":Gc#", &nbytes_write)) != TTY_OK) + return error_type; + + if ((error_type = tty_nread_section(fd, read_buffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read)) != TTY_OK) + return error_type; + + tcflush(fd, TCIFLUSH); + + if (nbytes_read < 1) + return error_type; + + read_buffer[nbytes_read - 1] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", read_buffer); + + // The Losmandy Gemini puts () around it's time format + if (strstr(read_buffer, "(")) + nbytes_read = sscanf(read_buffer, "(%d)", &tMode); + else + nbytes_read = sscanf(read_buffer, "%d", &tMode); + + if (nbytes_read < 1) + return -1; + else + *format = tMode; + + return 0; +} + +int getSiteName(int fd, char *siteName, int siteNum) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char *term; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :GM# // Get Site 1 Name // Returns: # // A ‘#’ terminated string with the name of the requested site. + // :GN# // Get Site 2 Name // Returns: # // A ‘#’ terminated string with the name of the requested site. + // :GO# // Get Site 3 Name // Returns: # // A ‘#’ terminated string with the name of the requested site. + // :GP# // Get Site 4 Name // Returns: # // A ‘#’ terminated string with the name of the requested site. + switch (siteNum) + { + case 1: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":GM#"); + if ((error_type = tty_write_string(fd, ":GM#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case 2: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":GN#"); + if ((error_type = tty_write_string(fd, ":GN#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case 3: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":GO#"); + if ((error_type = tty_write_string(fd, ":GO#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case 4: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":GP#"); + if ((error_type = tty_write_string(fd, ":GP#", &nbytes_write)) != TTY_OK) + return error_type; + break; + default: + return -1; + } + + error_type = tty_nread_section(fd, siteName, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + tcflush(fd, TCIFLUSH); + + if (nbytes_read < 1) + return error_type; + + siteName[nbytes_read - 1] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", siteName); + + term = strchr(siteName, ' '); + if (term) + *term = '\0'; + + term = strchr(siteName, '<'); + if (term) + strcpy(siteName, "unused site"); + + DEBUGFDEVICE(lx200Name, INDI::Logger::DBG_DEBUG, "Site Name <%s>", siteName); + + return 0; +} + +int getSiteLatitude(int fd, int *dd, int *mm, double *ssf) +{ + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :Gt# + // Get Current Site Latitude + // Returns: sDD*MM# + // The latitude of the current site. Positive inplies North latitude. + // + // 10Micron Mount Command Protocol software version 2.14.11 2016.11 + // :Gt# + // Get current site latitude. + // Returns the latitude of the current site formatted as follows: + // Emulation and precision Return value + // Any emulation, low precision sDD*MM# (sign, degrees, minutes) + // LX200 emulation, high precision sDD*MM# (sign, degrees, minutes) + // Extended emulation, high precision sDD*MM:SS# (sign, degrees, arcminutes, arcseconds) + // Any emulation, ultra precision sDD:MM:SS.S# (sign, degrees, arcminutes, arcseconds, tenths of arcsecond) + // Positive implies north latitude. + return getSiteLatitudeAlt( fd, dd, mm, ssf, ":Gt#"); +} + +// Meade classic handset defines longitude as 0 to 360 WESTWARD. However, +// Meade API expresses East Longitudes as negative, West Longitudes as positive. +// Source: https://www.meade.com/support/LX200CommandSet.pdf from 2002 at :Gg# +// (And also 10Micron has East Longitudes expressed as negative.) +// Also note that this is the opposite of cartography where East is positive. +int getSiteLongitude(int fd, int *ddd, int *mm, double *ssf) +{ + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :Gg# + // Get Current Site Longitude + // Returns: sDDD*MM# + // The current site Longitude. East Longitudes are expressed as negative + // + // 10Micron Mount Command Protocol software version 2.14.11 2016.11 + // :Gg# + // Get current site longitude. Note: East Longitudes are expressed as negative. Returns + // the current site longitude formatted as follows: + // Emulation and precision Return value + // Any emulation, low precision or LX200 sDDD*MM# (sign, degrees, arcminutes) + // emulation, high precision + // Extended emulation, high precision sDDD*MM:SS# (sign, degrees, arcminutes, arcseconds) + // Any emulation, ultra precision sDDD:MM:SS.S# (sign, degrees, arcminutes, arcseconds, tenths of arcsecond) + return getSiteLongitudeAlt(fd, ddd, mm, ssf, ":Gg#"); +} + + +int getSiteLatitudeAlt(int fd, int *dd, int *mm, double *ssf, const char *cmd) +{ + // :Gt# see getSiteLatitude() + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", cmd); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + tcflush(fd, TCIFLUSH); + + if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, read_buffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + + tcflush(fd, TCIFLUSH); + + if (nbytes_read < 1) + return error_type; + + read_buffer[nbytes_read - 1] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", read_buffer); + + *ssf = 0.0; + if (sscanf(read_buffer, "%d%*c%d:%lf", dd, mm, ssf) < 2) + { + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Unable to parse %s response", cmd); + return -1; + } + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "VAL [%d,%d,%.1lf]", *dd, *mm, *ssf); + + int new_geo_format; + switch (nbytes_read) + { + case 9: + case 10: + new_geo_format = LX200_GEO_LONG_FORMAT; + break; + case 11: + case 12: + new_geo_format = LX200_GEO_LONGER_FORMAT; + break; + default: + new_geo_format = LX200_GEO_SHORT_FORMAT; + break; + } + if (new_geo_format != geo_format) + { + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Updated geographic precision from setting %d to %d", geo_format, new_geo_format); + geo_format = new_geo_format; + } + + return 0; +} + +int getSiteLongitudeAlt(int fd, int *ddd, int *mm, double *ssf, const char *cmd) +{ + // :Gg# see getSiteLongitude() + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", cmd); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + if ((error_type = tty_write_string(fd, cmd, &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, read_buffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + + tcflush(fd, TCIFLUSH); + + if (nbytes_read < 1) + return error_type; + + read_buffer[nbytes_read - 1] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", read_buffer); + + *ssf = 0.0; + if (sscanf(read_buffer, "%d%*c%d:%lf", ddd, mm, ssf) < 2) + { + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Unable to parse %s response", cmd); + return -1; + } + *ddd *= -1.0; // Convert LX200Longitude to CartographicLongitude + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "VAL in CartographicLongitude format [%d,%d,%.1lf]", *ddd, *mm, *ssf); + + int new_geo_format; + switch (nbytes_read) + { + case 10: + case 11: + new_geo_format = LX200_GEO_LONG_FORMAT; + break; + case 12: + case 13: + new_geo_format = LX200_GEO_LONGER_FORMAT; + break; + default: + new_geo_format = LX200_GEO_SHORT_FORMAT; + break; + } + if (new_geo_format != geo_format) + { + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Updated geographic precision from setting %d to %d", geo_format, new_geo_format); + geo_format = new_geo_format; + } + + return 0; +} + +int getTrackFreq(int fd, double *value) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + float Freq; + char read_buffer[RB_MAX_LEN] = {0}; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":GT#"); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :GT# + // Get tracking rate + // Returns: TT.T# + // Current Track Frequency expressed in hertz assuming a synchonous motor design where a 60.0 Hz motor clock + // would produce 1 revolution of the telescope in 24 hours. + if ((error_type = tty_write_string(fd, ":GT#", &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, read_buffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + tcflush(fd, TCIFLUSH); + + if (nbytes_read < 1) + return error_type; + + read_buffer[nbytes_read] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", read_buffer); + + if (sscanf(read_buffer, "%f#", &Freq) < 1) + { + DEBUGDEVICE(lx200Name, DBG_SCOPE, "Unable to parse response"); + return -1; + } + + *value = static_cast(Freq); + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "VAL [%g]", *value); + + return 0; +} + +int getHomeSearchStatus(int fd, int *status) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":h?#"); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :h?# + // Autostar, Autostar II and LX 16” Query Home Status + // Returns: + // 0 Home Search Failed + // 1 Home Search Found + // 2 Home Search in Progress + // LX200 Not Supported + if ((error_type = tty_write_string(fd, ":h?#", &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, read_buffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + tcflush(fd, TCIFLUSH); + + if (nbytes_read < 1) + return error_type; + + read_buffer[1] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", read_buffer); + + if (read_buffer[0] == '0') + *status = 0; + else if (read_buffer[0] == '1') + *status = 1; + else if (read_buffer[0] == '2') + *status = 1; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "VAL [%d]", *status); + + return 0; +} + +int getOTATemp(int fd, double *value) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + float temp; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":fT#"); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :fT# + // Autostar II – Return Optical Tube Assembly Temperature + // Max/RCX – Return OTA Temperature + // Returns # - a ‘#’ terminated signed ASCII real number indicating the Celsius ambient temperature. + // All others – Not supported + if ((error_type = tty_write_string(fd, ":fT#", &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, read_buffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + + if (nbytes_read < 1) + return error_type; + + read_buffer[nbytes_read - 1] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", read_buffer); + + if (sscanf(read_buffer, "%f", &temp) < 1) + { + DEBUGDEVICE(lx200Name, DBG_SCOPE, "Unable to parse response"); + return -1; + } + + *value = static_cast(temp); + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "VAL [%g]", *value); + + return 0; +} + +/********************************************************************** +* SET +**********************************************************************/ + +int setStandardProcedure(int fd, const char *data) +{ + char bool_return[2]; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", data); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + tcflush(fd, TCIFLUSH); + + if ((error_type = tty_write_string(fd, data, &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_read(fd, bool_return, 1, LX200_TIMEOUT, &nbytes_read); + + tcflush(fd, TCIFLUSH); + + if (nbytes_read < 1) + return error_type; + + if (bool_return[0] == '0') + { + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s> failed.", data); + return -1; + } + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s> successful.", data); + + return 0; +} + +int setCommandInt(int fd, int data, const char *cmd) +{ + char read_buffer[RB_MAX_LEN] = {0}; + int error_type; + int nbytes_write = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + snprintf(read_buffer, sizeof(read_buffer), "%s%d#", cmd, data); + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", read_buffer); + + tcflush(fd, TCIFLUSH); + + if ((error_type = tty_write_string(fd, read_buffer, &nbytes_write)) != TTY_OK) + { + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s> failed.", read_buffer); + return error_type; + } + + tcflush(fd, TCIFLUSH); + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s> successful.", read_buffer); + + return 0; +} + +int setMinElevationLimit(int fd, int min) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :SoDD*# + // Set lowest elevation to which the telescope will slew + // Returns: + // 0 – Invalid + // 1 - Valid + // + // 10Micron adds a sign and limits but removes the * in their docs. + // :SosDD# + // Set the minimum altitude above the horizon to which the telescope will slew to sDD degrees. + // Valid values are between –5 and +45 degrees. + snprintf(read_buffer, sizeof(read_buffer), ":So%02d*#", min); + + return (setStandardProcedure(fd, read_buffer)); +} + +int setMaxElevationLimit(int fd, int max) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :ShDD# + // Set the maximum object elevation limit to DD# + // Returns: + // 0 – Invalid + // 1 - Valid + snprintf(read_buffer, sizeof(read_buffer), ":Sh%02d#", max); + + return (setStandardProcedure(fd, read_buffer)); +} + +int setMaxSlewRate(int fd, int slewRate) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + if (slewRate < 2 || slewRate > 8) + return -1; + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :SwN# + // Set maximum slew rate to N degrees per second. N is the range (2..8) + // Returns: + // 0 – Invalid + // 1 - Valid + snprintf(read_buffer, sizeof(read_buffer), ":Sw%d#", slewRate); + + return (setStandardProcedure(fd, read_buffer)); +} + +int setObjectRA(int fd, double ra, bool addSpace) +{ + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :SrHH:MM.T# + // :SrHH:MM:SS# + // Set target object RA to HH:MM.T or HH:MM:SS depending on the current precision setting. + // Returns: + // 0 – Invalid + // 1 - Valid + // + // 10Micron Mount Command Protocol software version 2.14.11 2016.11 + // :SrHH:MM.T# or :SrHH:MM:SS# or :SrHH:MM:SS.S# or :SrHH:MM:SS.SS# + // Set target object RA to HH:MM.T (hours, minutes and tenths of minutes), HH:MM:SS + // (hours, minutes, seconds), HH:MM:SS.S (hours, minutes, seconds and tenths of second) + // or HH:MM:SS.SS (hours, minutes, seconds and hundredths of second). + // Returns: + // 0 invalid + // 1 valid + // + // We support these formats: + // LX200_EQ_SHORT_FORMAT :SrHH:MM.T# (hours, minutes and tenths of minutes) + // LX200_EQ_LONG_FORMAT :SrHH:MM:SS# (hours, minutes, seconds) + // LX200_EQ_LONGER_FORMAT :SrHH:MM:SS.SS# (hours, minutes, seconds and hundredths of second) + // Add space is used to add space between the command the and rest of the arguments. + // i.e. :Sr HH:MM:SS# for example since some mounts require space. + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + + int h, m, s; + char read_buffer[22] = {0}; + char cmd[8] = {0}; + if (addSpace) + strcpy(cmd, "Sr "); + else + strcpy(cmd, "Sr"); + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + switch (eq_format) + { + case LX200_EQ_SHORT_FORMAT: + int frac_m; + getSexComponents(ra, &h, &m, &s); + frac_m = (s / 60.0) * 10.; + snprintf(read_buffer, sizeof(read_buffer), ":%s%02d:%02d.%01d#", cmd, h, m, frac_m); + break; + case LX200_EQ_LONG_FORMAT: + getSexComponents(ra, &h, &m, &s); + snprintf(read_buffer, sizeof(read_buffer), ":%s%02d:%02d:%02d#", cmd, h, m, s); + break; + case LX200_EQ_LONGER_FORMAT: + double d_s; + getSexComponentsIID(ra, &h, &m, &d_s); + snprintf(read_buffer, sizeof(read_buffer), ":%s%02d:%02d:%05.02f#", cmd, h, m, d_s); + break; + default: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Unknown controller_format <%d>", eq_format); + return -1; + } + + return (setStandardProcedure(fd, read_buffer)); +} + +int setObjectDEC(int fd, double dec, bool addSpace) +{ + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :SdsDD*MM# + // Set target object declination to sDD*MM or sDD*MM:SS depending on the current precision setting + // Returns: + // 1 - Dec Accepted + // 0 – Dec invalid + // + // 10Micron Mount Command Protocol software version 2.14.11 2016.11 + // :SdsDD*MM# or :SdsDD*MM:SS# or :Sd sDD*MM:SS.S# + // Set target object declination to sDD*MM (sign, degrees, arcminutes), sDD*MM:SS + // (sign, degrees, arcminutes, arcseconds) or sDD*MM:SS.S (sign, degrees, arcminutes, + // arcseconds and tenths of arcsecond) + // Returns: + // 0 invalid + // 1 valid + // + // We support these formats: + // LX200_EQ_SHORT_FORMAT :SdsDD*MM# (sign, degrees, arcminutes) + // LX200_EQ_LONG_FORMAT :SdsDD*MM:SS# (sign, degrees, arcminutes, arcseconds) + // LX200_EQ_LONGER_FORMAT :Sd sDD*MM:SS.S# (sign, degrees, arcminutes, arcseconds, tenths of arcsecond) + // Add space is used to add space between the command the and rest of the arguments. + // i.e. :Sd DD:MM:SS# for example since some mounts require space. + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + char cmd[8] = {0}; + if (addSpace) + strcpy(cmd, "Sd "); + else + strcpy(cmd, "Sd"); + int d, m, s; + char read_buffer[22] = {0}; + + switch (eq_format) + { + case LX200_EQ_SHORT_FORMAT: + getSexComponents(dec, &d, &m, &s); + /* case with negative zero */ + if (!d && dec < 0) + snprintf(read_buffer, sizeof(read_buffer), ":%s-%02d*%02d#", cmd, d, m); + else + snprintf(read_buffer, sizeof(read_buffer), ":%s%+03d*%02d#", cmd, d, m); + break; + case LX200_EQ_LONG_FORMAT: + getSexComponents(dec, &d, &m, &s); + /* case with negative zero */ + if (!d && dec < 0) + snprintf(read_buffer, sizeof(read_buffer), ":%s-%02d*%02d:%02d#", cmd, d, m, s); + else + snprintf(read_buffer, sizeof(read_buffer), ":%s%+03d*%02d:%02d#", cmd, d, m, s); + break; + case LX200_EQ_LONGER_FORMAT: + double d_s; + getSexComponentsIID(dec, &d, &m, &d_s); + /* case with negative zero */ + if (!d && dec < 0) + snprintf(read_buffer, sizeof(read_buffer), ":%s-%02d*%02d:%04.1f#", cmd, d, m, d_s); + else + snprintf(read_buffer, sizeof(read_buffer), ":%s%+03d*%02d:%04.1f#", cmd, d, m, d_s); + break; + default: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Unknown controller_format <%d>", eq_format); + return -1; + } + + return (setStandardProcedure(fd, read_buffer)); +} + +int setCommandXYZ(int fd, int x, int y, int z, const char *cmd, bool addSpace) +{ + char read_buffer[RB_MAX_LEN] = {0}; + snprintf(read_buffer, sizeof(read_buffer), addSpace ? "%s %02d:%02d:%02d#" : "%s%02d:%02d:%02d#", cmd, x, y, z); + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + return (setStandardProcedure(fd, read_buffer)); +} + +int setAlignmentMode(int fd, unsigned int alignMode) +{ + int error_type; + int nbytes_write = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :AL# // Sets telescope to Land alignment mode // Returns: nothing + // :AP# // Sets telescope to Polar alignment mode // Returns: nothing + // :AA# // Sets telescope the AltAz alignment mode // Returns: nothing + switch (alignMode) + { + case LX200_ALIGN_POLAR: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":AP#"); + if ((error_type = tty_write_string(fd, ":AP#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_ALIGN_ALTAZ: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":AA#"); + if ((error_type = tty_write_string(fd, ":AA#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_ALIGN_LAND: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":AL#"); + if ((error_type = tty_write_string(fd, ":AL#", &nbytes_write)) != TTY_OK) + return error_type; + break; + } + + tcflush(fd, TCIFLUSH); + return 0; +} + +int setCalenderDate(int fd, int dd, int mm, int yy, bool addSpace) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + const struct timespec timeout = {0, 10000000L}; + char read_buffer[RB_MAX_LEN]; + char dummy_buffer[RB_MAX_LEN]; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + yy = yy % 100; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :SCMM/DD/YY# + // Change Handbox Date to MM/DD/YY + // Returns: + // D = '0' if the date is invalid. The string is the null string. + // D = '1' for valid dates and the string is "Updating Planetary Data# #" + // Note: For LX200GPS/Autostar II this is the UTC data! + // + // 10Micron, the extended formats are documented here but not yet implemented. + // :SCMM/DD/YY# or :SCMM/DD/YYYY# or :SCYYYY-MM-DD# + // Set date to MM/DD/YY (month, day, year), MM/DD/YYYY (month, day, year) or YYYY-MM-DD (year, month, day). + // The date is expressed in local time. Returns: + // 0 if the date is invalid + // The string "1Updating Planetary Data. # #" if the date is valid. + // The string "1<32 spaces>#<32 spaces>#" in extended LX200 emulation mode. + // The character "1" without additional strings in ultra-precision mode (regardless of emulation). + snprintf(read_buffer, sizeof(read_buffer), addSpace ? ":SC %02d/%02d/%02d#" : ":SC%02d/%02d/%02d#", mm, dd, yy); + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", read_buffer); + + tcflush(fd, TCIFLUSH); + + if ((error_type = tty_write_string(fd, read_buffer, &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, read_buffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + // Read the next section whih has 24 blanks and then a # + // Can't just use the tcflush to clear the stream because it doesn't seem to work correctly on sockets + tty_nread_section(fd, dummy_buffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + + tcflush(fd, TCIFLUSH); + + if (nbytes_read < 1) + { + DEBUGDEVICE(lx200Name, DBG_SCOPE, "Unable to parse response"); + return error_type; + } + + read_buffer[1] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", read_buffer); + + if (read_buffer[0] == '0') + return -1; + + /* Sleep 10ms before flushing. This solves some issues with LX200 compatible devices. */ + nanosleep(&timeout, nullptr); + tcflush(fd, TCIFLUSH); + + return 0; +} + +int setUTCOffset(int fd, double hours) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :SGsHH.H# + // Set the number of hours added to local time to yield UTC + // Returns: + // 0 – Invalid + // 1 - Valid + snprintf(read_buffer, sizeof(read_buffer), ":SG%+04.01lf#", hours); + + return (setStandardProcedure(fd, read_buffer)); +} + +// Meade classic handset defines longitude as 0 to 360 WESTWARD. However, +// Meade API expresses East Longitudes as negative, West Longitudes as positive. +// Source: https://www.meade.com/support/LX200CommandSet.pdf from 2002 at :Gg# +// (And also 10Micron has East Longitudes expressed as negative.) +// Also note that this is the opposite of cartography where East is positive. +int setSiteLongitude(int fd, double CartographicLongitude, bool addSpace) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int d, m, s; + char read_buffer[RB_MAX_LEN] = {0}; + double LX200Longitude = -1.0 * CartographicLongitude; + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :SgDDD*MM# + // Set current site’s longitude to DDD*MM an ASCII position string + // Returns: + // 0 – Invalid + // 1 - Valid + // + // 10Micron Mount Command Protocol software version 2.14.11 2016.11 + // :SgsDDD*MM# or :SgsDDD*MM:SS# or :SgsDDD*MM:SS.S# + // Set current site’s longitude to sDDD*MM (sign, degrees, arcminutes), sDDD*MM:SS + // (sign, degrees, arcminutes, arcseconds) or sDDD*MM:SS.S (sign, degrees, arcminutes, + // arcseconds and tenths of arcsecond). Note: East Longitudes are expressed as negative. + // Returns: + // 0 invalid + // 1 valid + switch (geo_format) + { + case LX200_GEO_SHORT_FORMAT: // d m + getSexComponents(LX200Longitude, &d, &m, &s); + snprintf(read_buffer, sizeof(read_buffer), addSpace ? ":Sg %03d*%02d#" : ":Sg%03d*%02d#", d, m); + break; + case LX200_GEO_LONG_FORMAT: // d m s + getSexComponents(LX200Longitude, &d, &m, &s); + snprintf(read_buffer, sizeof(read_buffer), addSpace ? ":Sg %03d*%02d:%02d#" : ":Sg%03d*%02d:%02d#", d, m, s); + break; + case LX200_GEO_LONGER_FORMAT: // d m s.f with f being tenths + double s_f; + getSexComponentsIID(LX200Longitude, &d, &m, &s_f); + snprintf(read_buffer, sizeof(read_buffer), addSpace ? ":Sg %03d*%02d:%04.01lf#" : ":Sg%03d*%02d:%04.01lf#", d, m, s_f); + break; + default: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Unknown geographic format <%d>", geo_format); + return -1; + } + + return (setStandardProcedure(fd, read_buffer)); +} + +int setSiteLatitude(int fd, double Lat, bool addSpace) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int d, m, s; + char read_buffer[RB_MAX_LEN] = {0}; + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :StsDD*MM# + // Sets the current site latitude to sDD*MM# + // Returns: + // 0 – Invalid + // 1 - Valid + // + // 10Micron Mount Command Protocol software version 2.14.11 2016.11 + // :StsDD*MM# or :StsDD*MM:SS# or :StsDD*MM:SS.S# + // Sets the current site latitude to sDD*MM (sign, degrees, arcminutes), sDD*MM:SS + // (sign, degrees, arcminutes, arcseconds), or sDD*MM:SS.S (sign, degrees, arcminutes, + // arcseconds and tenths of arcsecond) + // Returns: + // 0 invalid + // 1 valid + switch (geo_format) + { + case LX200_GEO_SHORT_FORMAT: // d m + getSexComponents(Lat, &d, &m, &s); + snprintf(read_buffer, sizeof(read_buffer), addSpace ? ":St %+03d*%02d#" : ":St%+03d*%02d#", d, m); + break; + case LX200_GEO_LONG_FORMAT: // d m s + getSexComponents(Lat, &d, &m, &s); + snprintf(read_buffer, sizeof(read_buffer), addSpace ? ":St %+03d*%02d:%02d#" : ":St%+03d*%02d:%02d#", d, m, s); + break; + case LX200_GEO_LONGER_FORMAT: // d m s.f with f being tenths + double s_f; + getSexComponentsIID(Lat, &d, &m, &s_f); + snprintf(read_buffer, sizeof(read_buffer), addSpace ? ":St %+03d*%02d:%04.01lf#" : ":St%+03d*%02d:%04.01lf#", d, m, s_f); + break; + default: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "Unknown geographic format <%d>", geo_format); + return -1; + } + + return (setStandardProcedure(fd, read_buffer)); +} + +int setObjAz(int fd, double az) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int d, m, s; + char read_buffer[RB_MAX_LEN] = {0}; + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + getSexComponents(az, &d, &m, &s); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :SzDDD*MM# + // Sets the target Object Azimuth [LX 16” and Autostar II only] + // Returns: + // 0 – Invalid + // 1 - Valid + snprintf(read_buffer, sizeof(read_buffer), ":Sz%03d*%02d#", d, m); + + return (setStandardProcedure(fd, read_buffer)); +} + +int setObjAlt(int fd, double alt) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int d, m, s; + char read_buffer[RB_MAX_LEN] = {0}; + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + getSexComponents(alt, &d, &m, &s); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :SasDD*MM# + // Set target object altitude to sDD*MM# or sDD*MM’SS# [LX 16”, Autostar, Autostar II] + // Returns: + // 1 Object within slew range + // 0 Object out of slew range + snprintf(read_buffer, sizeof(read_buffer), ":Sa%+02d*%02d#", d, m); + + return (setStandardProcedure(fd, read_buffer)); +} + +int setSiteName(int fd, char *siteName, int siteNum) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :SM# for site 1 + // :SN# for site 2 + // :SO# for site 3 + // :SP# for site 4 + // Set site name to be . LX200s only accept 3 character strings. Other scopes accept up to 15 characters. + // Returns: + // 0 – Invalid + // 1 - Valid + switch (siteNum) + { + case 1: + snprintf(read_buffer, sizeof(read_buffer), ":SM%s#", siteName); + break; + case 2: + snprintf(read_buffer, sizeof(read_buffer), ":SN%s#", siteName); + break; + case 3: + snprintf(read_buffer, sizeof(read_buffer), ":SO%s#", siteName); + break; + case 4: + snprintf(read_buffer, sizeof(read_buffer), ":SP%s#", siteName); + break; + default: + return -1; + } + + return (setStandardProcedure(fd, read_buffer)); +} + +int setSlewMode(int fd, int slewMode) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int error_type; + int nbytes_write = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :RS# // Set Slew rate to max (fastest) // Returns: Nothing + // :RM# // Set Slew rate to Guiding Rate (slowest) // Returns: Nothing + // :RC# // Set Slew rate to Centering rate (2nd slowest) // Returns: Nothing + // :RG# // Set Slew rate to Find Rate (2nd Fastest) // Returns: Nothing + switch (slewMode) + { + case LX200_SLEW_MAX: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":RS#"); + if ((error_type = tty_write_string(fd, ":RS#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_SLEW_FIND: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":RM#"); + if ((error_type = tty_write_string(fd, ":RM#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_SLEW_CENTER: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":RC#"); + if ((error_type = tty_write_string(fd, ":RC#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_SLEW_GUIDE: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":RG#"); + if ((error_type = tty_write_string(fd, ":RG#", &nbytes_write)) != TTY_OK) + return error_type; + break; + default: + break; + } + + tcflush(fd, TCIFLUSH); + return 0; +} + +int setFocuserMotion(int fd, int motionType) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int error_type; + int nbytes_write = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :F+# // Start Focuser moving inward (toward objective) // Returns: None + // :F-# // Start Focuser moving outward (away from objective) // Returns: None + switch (motionType) + { + case LX200_FOCUSIN: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":F+#"); + if ((error_type = tty_write_string(fd, ":F+#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_FOCUSOUT: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":F-#"); + if ((error_type = tty_write_string(fd, ":F-#", &nbytes_write)) != TTY_OK) + return error_type; + break; + } + + tcflush(fd, TCIFLUSH); + return 0; +} + +int setFocuserSpeedMode(int fd, int speedMode) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int error_type; + int nbytes_write = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :FQ# // Halt Focuser Motion // Returns: Nothing + // :FS# // Set Focus speed to slowest setting // Returns: Nothing + // :FF# // Set Focus speed to fastest setting // Returns: Nothing + switch (speedMode) + { + case LX200_HALTFOCUS: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":FQ#"); + if ((error_type = tty_write_string(fd, ":FQ#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_FOCUSSLOW: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":FS#"); + if ((error_type = tty_write_string(fd, ":FS#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_FOCUSFAST: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":FF#"); + if ((error_type = tty_write_string(fd, ":FF#", &nbytes_write)) != TTY_OK) + return error_type; + break; + } + + tcflush(fd, TCIFLUSH); + return 0; +} + +int setGPSFocuserSpeed(int fd, int speed) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char speed_str[8]; + int error_type; + int nbytes_write = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :FQ# // Halt Focuser Motion // Returns: Nothing + if (speed == 0) + { + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":FQ#"); + if ((error_type = tty_write_string(fd, ":FQ#", &nbytes_write)) != TTY_OK) + return error_type; + + tcflush(fd, TCIFLUSH); + return 0; + } + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :F# Autostar, Autostar II – set focuser speed to where is an ASCII digit 1..4 + // Returns: Nothing + // All others – Not Supported + snprintf(speed_str, 8, ":F%d#", speed); + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", speed_str); + + if ((error_type = tty_write_string(fd, speed_str, &nbytes_write)) != TTY_OK) + return error_type; + + tcflush(fd, TCIFLUSH); + return 0; +} + +int setTrackFreq(int fd, double trackF) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + // Meade Telescope Serial Command Protocol Revision 2002.10 + // :STTT.T# + // Sets the current tracking rate to TTT.T hertz, assuming a model where a 60.0 Hertz synchronous motor will cause the RA + // axis to make exactly one revolution in 24 hours. + // Returns: + // 0 – Invalid + // 1 - Valid + // Note: the definition :STTT.T# does not match the text. + // + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :STdddd.ddddddd# [Autostar II Only] + // Sets the current tracking rate to ddd.dddd hertz, assuming a model where a 60.0000 Hertz synchronous motor will cause + // the RA axis to make exactly one revolution in 24 hours. + // Returns: + // 0 – Invalid + // 2 – Valid + // Note1: the definition :STdddd.ddddddd# looks bogus and does not match the text. + // Note2: the 'Valid' response value of 2 looks bogus. + // Note3: its appendix A lists :STDDD.DDD# which differs from both the previous definition as well as the text. + // + // 10Micron Mount Command Protocol software version 2.14.11 2016.11 + // :STDDD.DDD# + // Set the tracking rate to DDD.DDD, where DDD.DDD is a decimal number which is + // four times the tracking rate expressed in arcseconds per second of time. + // Returns: + // 0 invalid + // 1 valid + // + // Note: given the above definition mess the choice was made to implement :STTTT.T# which is probably what the 2002.10 spec intended. + snprintf(read_buffer, sizeof(read_buffer), ":ST%05.01lf#", trackF); + + return (setStandardProcedure(fd, read_buffer)); +} + +int setPreciseTrackFreq(int fd, double trackF) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + + // TODO see spec of setTrackFreq where none describe a :STdd.ddddd# + snprintf(read_buffer, sizeof(read_buffer), ":ST%08.5f#", trackF); + + /* Add mutex */ + /* std::unique_lock guard(lx200CommsLock); */ + + return (setStandardProcedure(fd, read_buffer)); +} + +/********************************************************************** +* Misc +*********************************************************************/ + +int Slew(int fd) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char slewNum[2]; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":MS#"); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :MS# + // Slew to Target Object + // Returns: + // 0 Slew is Possible + // 1# Object Below Horizon w/string message + // 2# Object Below Higher w/string message + if ((error_type = tty_write_string(fd, ":MS#", &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_read(fd, slewNum, 1, LX200_TIMEOUT, &nbytes_read); + + if (nbytes_read < 1) + { + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES ERROR <%d>", error_type); + return error_type; + } + + /* We don't need to read the string message, just return corresponding error code */ + tcflush(fd, TCIFLUSH); + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%c>", slewNum[0]); + + error_type = slewNum[0] - '0'; + if ((error_type >= 0) && (error_type <= 9)) + { + return error_type; + } + else + { + return -1; + } +} + +int MoveTo(int fd, int direction) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int nbytes_write = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :Mn# // Move Telescope North at current slew rate // Returns: Nothing + // :Mw# // Move Telescope West at current slew rate // Returns: Nothing + // :Me# // Move Telescope East at current slew rate // Returns: Nothing + // :Ms# // Move Telescope South at current slew rate // Returns: Nothing + switch (direction) + { + case LX200_NORTH: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":Mn#"); + tty_write_string(fd, ":Mn#", &nbytes_write); + break; + case LX200_WEST: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":Mw#"); + tty_write_string(fd, ":Mw#", &nbytes_write); + break; + case LX200_EAST: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":Me#"); + tty_write_string(fd, ":Me#", &nbytes_write); + break; + case LX200_SOUTH: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":Ms#"); + tty_write_string(fd, ":Ms#", &nbytes_write); + break; + default: + break; + } + + tcflush(fd, TCIFLUSH); + return 0; +} + +int SendPulseCmd(int fd, int direction, int duration_msec, bool wait_after_command, int max_wait_ms) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int nbytes_write = 0; + char cmd[20]; + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :MgnDDDD# + // :MgsDDDD# + // :MgeDDDD# + // :MgwDDDD# + // Guide telescope in the commanded direction (nsew) for the number of milliseconds indicated by the unsigned number + // passed in the command. These commands support serial port driven guiding. + // Returns – Nothing + // LX200 – Not Supported + switch (direction) + { + case LX200_NORTH: + sprintf(cmd, ":Mgn%04d#", duration_msec); + break; + case LX200_SOUTH: + sprintf(cmd, ":Mgs%04d#", duration_msec); + break; + case LX200_EAST: + sprintf(cmd, ":Mge%04d#", duration_msec); + break; + case LX200_WEST: + sprintf(cmd, ":Mgw%04d#", duration_msec); + break; + default: + return 1; + } + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", cmd); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + tty_write_string(fd, cmd, &nbytes_write); + + tcflush(fd, TCIFLUSH); + + if(wait_after_command){ + if (duration_msec > max_wait_ms) + duration_msec = max_wait_ms; + struct timespec duration_ns = {.tv_sec = 0,.tv_nsec = duration_msec*1000000}; + nanosleep(&duration_ns, NULL); + } + return 0; +} + +int HaltMovement(int fd, int direction) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int error_type; + int nbytes_write = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :Qn# // Halt northward Slews // Returns: Nothing + // :Qw# // Halt westward Slews // Returns: Nothing + // :Qe# // Halt eastward Slews // Returns: Nothing + // :Qs# // Halt southward Slews // Returns: Nothing + // :Q# // Halt all current slewing // Returns: Nothing + switch (direction) + { + case LX200_NORTH: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":Qn#"); + if ((error_type = tty_write_string(fd, ":Qn#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_WEST: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":Qw#"); + if ((error_type = tty_write_string(fd, ":Qw#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_EAST: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":Qe#"); + if ((error_type = tty_write_string(fd, ":Qe#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_SOUTH: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":Qs#"); + if ((error_type = tty_write_string(fd, ":Qs#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_ALL: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":Q#"); + if ((error_type = tty_write_string(fd, ":Q#", &nbytes_write)) != TTY_OK) + return error_type; + break; + default: + return -1; + } + + tcflush(fd, TCIFLUSH); + return 0; +} + +int abortSlew(int fd) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int error_type; + int nbytes_write = 0; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":Q#"); + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :Q# // Halt all current slewing // Returns: Nothing + if ((error_type = tty_write_string(fd, ":Q#", &nbytes_write)) != TTY_OK) + return error_type; + + tcflush(fd, TCIFLUSH); + return 0; +} + +int Sync(int fd, char *matchedObject) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + const struct timespec timeout = {0, 10000000L}; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":CM#"); + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :CM# + // Synchronizes the telescope's position with the currently selected database object's coordinates. + // Returns: + // LX200's - a "#" terminated string with the name of the object that was synced. + // Autostars & Autostar II - At static string: " M31 EX GAL MAG 3.5 SZ178.0'#" + // + // 10Micron Mount Command Protocol software version 2.14.11 2016.11 + // :CM# + // Synchronizes the position of the mount with the coordinates of the currently selected target. + // Starting with version 2.8.15, this command has two possible behaviours depending on + // the value passed to the last :CMCFGn# command. By default after startup, or after + // the :CMCFG0# command has been given, the synchronization works by offsetting the + // axis angles. If the :CMCFG1# command has been given, it works like the :CMS# + // command, but returning the strings below. + // Returns: + // the string “Coordinates matched #” if the coordinates have been synchronized + // the string “Match fail: dist. too large#” if the coordinates have not been synchronized + if ((error_type = tty_write_string(fd, ":CM#", &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, matchedObject, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + + if (nbytes_read < 1) + return error_type; + + matchedObject[nbytes_read - 1] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", matchedObject); + + /* Sleep 10ms before flushing. This solves some issues with LX200 compatible devices. */ + nanosleep(&timeout, nullptr); + tcflush(fd, TCIFLUSH); + + return 0; +} + +int selectSite(int fd, int siteNum) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int error_type; + int nbytes_write = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2002.10 + // :W# + // Set current site to , an ASCII digit in the range 0..3 + // Returns: Nothing + // + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :W# + // Set current site to , an ASCII digit in the range 1..4 + // Returns: Nothing + // + // So Meade changed their mind on the offset :( + // The azwing comments below implements of the 2002.10 versions. + // TODO: auto determine which spec version to use ! + switch (siteNum) + { + case 1: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":W0#"); + if ((error_type = tty_write_string(fd, ":W0#", &nbytes_write)) != TTY_OK) //azwing index starts at 0 not 1 + return error_type; + break; + case 2: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":W1#"); + if ((error_type = tty_write_string(fd, ":W1#", &nbytes_write)) != TTY_OK) //azwing index starts at 0 not 1 + return error_type; + break; + case 3: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":W2#"); + if ((error_type = tty_write_string(fd, ":W2#", &nbytes_write)) != TTY_OK) //azwing index starts at 0 not 1 + return error_type; + break; + case 4: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":W3#"); + if ((error_type = tty_write_string(fd, ":W3#", &nbytes_write)) != TTY_OK) //azwing index starts at 0 not 1 + return error_type; + break; + default: + return -1; + } + + tcflush(fd, TCIFLUSH); + return 0; +} + +int selectCatalogObject(int fd, int catalog, int NNNN) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + int error_type; + int nbytes_write = 0; + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :LSNNNN# + // Select star NNNN as the current target object from the currently selected catalog + // Returns: Nothing + // Autostar II & AutoStar – Available in later firmwares + // + // :LCNNNN# + // Set current target object to deep sky catalog object number NNNN + // Returns : Nothing + // Autostar II & Autostar – Implemented in later firmware revisions + // + // :LMNNNN# + // Set current target object to Messier Object NNNN, an ASCII expressed decimal number. + // Returns: Nothing. + // Autostar II and Autostar – Implemented in later versions. + switch (catalog) + { + case LX200_STAR_C: + snprintf(read_buffer, sizeof(read_buffer), ":LS%d#", NNNN); + break; + case LX200_DEEPSKY_C: + snprintf(read_buffer, sizeof(read_buffer), ":LC%d#", NNNN); + break; + case LX200_MESSIER_C: + snprintf(read_buffer, sizeof(read_buffer), ":LM%d#", NNNN); + break; + default: + return -1; + } + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", read_buffer); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + + if ((error_type = tty_write_string(fd, read_buffer, &nbytes_write)) != TTY_OK) + return error_type; + + tcflush(fd, TCIFLUSH); + return 0; +} + +int selectSubCatalog(int fd, int catalog, int subCatalog) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + char read_buffer[RB_MAX_LEN] = {0}; + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :LsD# + // Select star catalog D, an ASCII integer where D specifies: + // 0 STAR library (Not supported on Autostar I & II) + // 1 SAO library + // 2 GCVS library + // 3 Hipparcos (Autostar I & 2) + // 4 HR (Autostar I & 2) + // 5 HD (Autostar I & 2) + // Returns: + // 1 Catalog Available + // 2 Catalog Not Found + // + // :LoD# + // Select deep sky Library where D specifies + // 0 - Objects CNGC / NGC in Autostar & Autostar II + // 1 - Objects IC + // 2 – UGC + // 3 – Caldwell (Autostar & Autostar II) + // 4 – Arp (LX200GPS/RCX) + // 5 – Abell (LX200GPS/RCX) + // Returns: + // 1 Catalog available + // 0 Catalog Not found + switch (catalog) + { + case LX200_STAR_C: + snprintf(read_buffer, sizeof(read_buffer), ":LsD%d#", subCatalog); + break; + case LX200_DEEPSKY_C: + snprintf(read_buffer, sizeof(read_buffer), ":LoD%d#", subCatalog); + break; + case LX200_MESSIER_C: + return 1; + default: + return 0; + } + + return (setStandardProcedure(fd, read_buffer)); +} + +int getLX200EquatorialFormat() +{ + return eq_format; +} + +int getLX200GeographicFormat() +{ + return geo_format; +} + +int checkLX200EquatorialFormat(int fd) +{ + char read_buffer[RB_MAX_LEN] = {0}; + eq_format = LX200_EQ_LONG_FORMAT; + int error_type; + int nbytes_write = 0, nbytes_read = 0; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":GR#"); + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + tcflush(fd, TCIFLUSH); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :GR# + // Get Telescope RA + // Returns: HH:MM.T# or HH:MM:SS# + // Depending which precision is set for the telescope + // + // 10Micron Mount Command Protocol software version 2.14.11 2016.11 + // :GR# + // Get telescope right ascension. Returns the current telescope right ascension formatted as follows: + // Emulation and precision Return value + // Any emulation, low precision HH:MM.M# (hours, minutes and tenths of minutes) + // LX200 emulation, high precision HH:MM:SS# (hours, minutes, seconds) + // Extended emulation, high precision HH:MM:SS.S# (hours, minutes, seconds and tenths of seconds) + // Any emulation, ultra precision HH:MM:SS.SS# (hours, minutes, seconds and hundredths of seconds) + if ((error_type = tty_write_string(fd, ":GR#", &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, read_buffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + + if (nbytes_read < 1) + { + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES ERROR <%d>", error_type); + return error_type; + } + + read_buffer[nbytes_read - 1] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", read_buffer); + + // 10micron returns on U2 15:46:18.03 . Prevent setting it to a lower precision later by detecting this mode here. + if (nbytes_read >= 11 && read_buffer[8] == '.') + { + eq_format = LX200_EQ_LONGER_FORMAT; + DEBUGDEVICE(lx200Name, DBG_SCOPE, "Equatorial coordinate format is ultra high precision."); + return 0; + } + + /* If it's short format, try to toggle to high precision format */ + if (read_buffer[5] == '.') + { + DEBUGDEVICE(lx200Name, DBG_SCOPE, "Detected low precision equatorial format, attempting to switch to high precision."); + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :U# + // Toggle between low/hi precision positions + // Low - RA displays and messages HH:MM.T sDD*MM + // High - Dec/Az/El displays and messages HH:MM:SS sDD*MM:SS + // Returns Nothing + // + // 10Micron Mount Command Protocol software version 2.14.11 2016.11 + // :U# + // Toggle between low and high precision modes. This controls the format of some values + // that are returned by the mount. In extened LX200 emulation mode, switches always to + // high precision (does not toggle). + // Low precision: RA returned as HH:MM.T (hours, minutes and tenths of minutes), + // Dec/Az/Alt returned as sDD*MM (sign, degrees, arcminutes). + // High precision: RA returned as HH:MM:SS (hours, minutes, seconds), Dec/Az/Alt + // returned as sDD*MM:SS (sign, degrees, arcminutes, arcseconds). + // Returns: nothing + // :U0# + // Set low precision mode. + // Returns: nothing + // :U1# + // Set high precision mode. + // Returns: nothing + // :U2# + // Set ultra precision mode. In ultra precision mode, extra decimal digits are returned for + // some commands, and there is no more difference between different emulation modes. + // Returns: nothing + // Available from version 2.10. + if ((error_type = tty_write_string(fd, ":U#", &nbytes_write)) != TTY_OK) + return error_type; + } + else if (read_buffer[8] == '.') + { + eq_format = LX200_EQ_LONGER_FORMAT; + DEBUGDEVICE(lx200Name, DBG_SCOPE, "Equatorial coordinate format is ultra high precision."); + return 0; + } + else + { + eq_format = LX200_EQ_LONG_FORMAT; + DEBUGDEVICE(lx200Name, DBG_SCOPE, "Equatorial coordinate format is high precision."); + return 0; + } + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":GR#"); + + tcflush(fd, TCIFLUSH); + + if ((error_type = tty_write_string(fd, ":GR#", &nbytes_write)) != TTY_OK) + return error_type; + + error_type = tty_nread_section(fd, read_buffer, RB_MAX_LEN, '#', LX200_TIMEOUT, &nbytes_read); + + if (nbytes_read < 1) + { + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES ERROR <%d>", error_type); + return error_type; + } + + read_buffer[nbytes_read - 1] = '\0'; + + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "RES <%s>", read_buffer); + + if (read_buffer[5] == '.') + { + eq_format = LX200_EQ_SHORT_FORMAT; + DEBUGDEVICE(lx200Name, DBG_SCOPE, "Equatorial coordinate format is low precision."); + } + else + { + eq_format = LX200_EQ_LONG_FORMAT; + DEBUGDEVICE(lx200Name, DBG_SCOPE, "Equatorial coordinate format is high precision."); + } + + tcflush(fd, TCIFLUSH); + + return 0; +} + +int selectTrackingMode(int fd, int trackMode) +{ + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "<%s>", __FUNCTION__); + int error_type; + int nbytes_write = 0; + + /* Add mutex */ + std::unique_lock guard(lx200CommsLock); + + // Meade Telescope Serial Command Protocol Revision 2010.10 + // :TQ# Selects sidereal tracking rate Returns: Nothing + // :TS# Select Solar tracking rate. [LS Only] Returns: Nothing + // :TL# Set Lunar Tracking Rate Returns: Nothing + // :TM# Select custom tracking rate [no-op in Autostar II] Returns: Nothing + switch (trackMode) + { + case LX200_TRACK_SIDEREAL: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":TQ#"); + if ((error_type = tty_write_string(fd, ":TQ#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_TRACK_SOLAR: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":TS#"); + if ((error_type = tty_write_string(fd, ":TS#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_TRACK_LUNAR: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":TL#"); + if ((error_type = tty_write_string(fd, ":TL#", &nbytes_write)) != TTY_OK) + return error_type; + break; + case LX200_TRACK_MANUAL: + DEBUGFDEVICE(lx200Name, DBG_SCOPE, "CMD <%s>", ":TM#"); + if ((error_type = tty_write_string(fd, ":TM#", &nbytes_write)) != TTY_OK) + return error_type; + break; + default: + return -1; + } + + tcflush(fd, TCIFLUSH); + return 0; +} + +int setLocalTime(int fd, int x, int y, int z, bool addSpace) +{ + return setCommandXYZ(fd, x, y, z, ":SL", addSpace); +} + +int setSDTime(int fd, int x, int y, int z, bool addSpace) +{ + return setCommandXYZ(fd, x, y, z, ":SS", addSpace); +} diff --git a/indi-lxd650/lx200driver.h b/indi-lxd650/lx200driver.h new file mode 100644 index 0000000..1f0872a --- /dev/null +++ b/indi-lxd650/lx200driver.h @@ -0,0 +1,317 @@ +/* + LX200 Driver + Copyright (C) 2003 Jasem Mutlaq (mutlaqja@ikarustech.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#pragma once + +/* Slew speeds */ +enum TSlew +{ + LX200_SLEW_MAX, + LX200_SLEW_FIND, + LX200_SLEW_CENTER, + LX200_SLEW_GUIDE +}; +/* Alignment modes */ +enum TAlign +{ + LX200_ALIGN_POLAR, + LX200_ALIGN_ALTAZ, + LX200_ALIGN_LAND +}; +/* Directions */ +enum TDirection +{ + LX200_NORTH, + LX200_WEST, + LX200_EAST, + LX200_SOUTH, + LX200_ALL +}; +/* Formats of Equatorial Right Ascension and Declination */ +enum TEquatorialFormat +{ + LX200_EQ_SHORT_FORMAT, + LX200_EQ_LONG_FORMAT, + LX200_EQ_LONGER_FORMAT +}; +/* Formats of Geographic Latitude and Longitude */ +enum TGeographicFormat +{ + LX200_GEO_SHORT_FORMAT, + LX200_GEO_LONG_FORMAT, + LX200_GEO_LONGER_FORMAT +}; +/* Time Format */ +enum TTimeFormat +{ + LX200_24, + LX200_AM, + LX200_PM +}; +/* Focus operation */ +enum TFocusMotion +{ + LX200_FOCUSIN, + LX200_FOCUSOUT +}; +enum TFocusSpeed +{ + LX200_HALTFOCUS = 0, + LX200_FOCUSSLOW, + LX200_FOCUSFAST +}; +/* Library catalogs */ +enum TCatalog +{ + LX200_STAR_C, + LX200_DEEPSKY_C +}; +/* Frequency mode */ +enum StarCatalog +{ + LX200_STAR, + LX200_SAO, + LX200_GCVS +}; +/* Deep Sky Catalogs */ +enum DeepSkyCatalog +{ + LX200_NGC, + LX200_IC, + LX200_UGC, + LX200_CALDWELL, + LX200_ARP, + LX200_ABELL, + LX200_MESSIER_C +}; +/* Mount tracking frequency, in Hz */ +enum TFreq +{ + LX200_TRACK_SIDEREAL, + LX200_TRACK_SOLAR, + LX200_TRACK_LUNAR, + LX200_TRACK_MANUAL +}; + +#define MaxReticleDutyCycle 15 +#define MaxFocuserSpeed 4 + +/* GET formatted sexagisemal value from device, return as double */ +#define getLX200RA(fd, x) getCommandSexa(fd, x, ":GR#") +#define getLX200DEC(fd, x) getCommandSexa(fd, x, ":GD#") +#define getObjectRA(fd, x) getCommandSexa(fd, x, ":Gr#") +#define getObjectDEC(fd, x) getCommandSexa(fd, x, ":Gd#") +#define getLocalTime12(fd, x) getCommandSexa(fd, x, ":Ga#") +#define getLocalTime24(fd, x) getCommandSexa(fd, x, ":GL#") +#define getSDTime(fd, x) getCommandSexa(fd, x, ":GS#") +#define getLX200Alt(fd, x) getCommandSexa(fd, x, ":GA#") +#define getLX200Az(fd, x) getCommandSexa(fd, x, ":GZ#") + +/* GET String from device and store in supplied buffer x */ +#define getObjectInfo(fd, x) getCommandString(fd, x, ":LI#") +#define getVersionDate(fd, x) getCommandString(fd, x, ":GVD#") +#define getVersionTime(fd, x) getCommandString(fd, x, ":GVT#") +#define getFullVersion(fd, x) getCommandString(fd, x, ":GVF#") +#define getVersionNumber(fd, x) getCommandString(fd, x, ":GVN#") +#define getProductName(fd, x) getCommandString(fd, x, ":GVP#") +#define turnGPS_StreamOn(fd) getCommandString(fd, x, ":gps#") + +/* GET Int from device and store in supplied pointer to integer x */ +#define getUTCOffset(fd, x) getCommandInt(fd, x, ":GG#") +#define getMaxElevationLimit(fd, x) getCommandInt(fd, x, ":Go#") +#define getMinElevationLimit(fd, x) getCommandInt(fd, x, ":Gh#") + +/* Generic set, x is an integer */ +#define setReticleDutyFlashCycle(fd, x) setCommandInt(fd, x, ":BD") +#define setReticleFlashRate(fd, x) setCommandInt(fd, x, ":B") +#define setFocuserSpeed(fd, x) setCommandInt(fd, x, ":F") +#define setSlewSpeed(fd, x) setCommandInt(fd, x, ":Sw") + +/* GPS Specefic */ +#define turnGPSOn(fd) write(fd, ":g+#", 4) +#define turnGPSOff(fd) write(fd, ":g-#", 4) +#define alignGPSScope(fd) write(fd, ":Aa#", 4) +#define gpsSleep(fd) write(fd, ":hN#", 4) +#define gpsWakeUp(fd) write(fd, ":hW#", 4); +#define gpsRestart(fd) write(fd, ":I#", 3); +#define updateGPS_System(fd) setStandardProcedure(fd, ":gT#") +#define enableDecAltPec(fd) write(fd, ":QA+#", 4) +#define disableDecAltPec(fd) write(fd, ":QA-#", 4) +#define enableRaAzPec(fd) write(fd, ":QZ+#", 4) +#define disableRaAzPec(fd) write(fd, ":QZ-#", 4) +#define activateAltDecAntiBackSlash(fd) write(fd, ":$BAdd#", 7) +#define activateAzRaAntiBackSlash(fd) write(fd, ":$BZdd#", 7) +#define SelenographicSync(fd) write(fd, ":CL#", 4); + +#define slewToAltAz(fd) setStandardProcedure(fd, ":MA#") +#define toggleTimeFormat(fd) write(fd, ":H#", 3) +#define increaseReticleBrightness(fd) write(fd, ":B+#", 4) +#define decreaseReticleBrightness(fd) write(fd, ":B-#", 4) +#define turnFanOn(fd) write(fd, ":f+#", 4) +#define turnFanOff(fd) write(fd, ":f-#", 4) +#define seekHomeAndSave(fd) write(fd, ":hS#", 4) +#define seekHomeAndSet(fd) write(fd, ":hF#", 4) +#define turnFieldDeRotatorOn(fd) write(fd, ":r+#", 4) +#define turnFieldDeRotatorOff(fd) write(fd, ":r-#", 4) +#define slewToPark(fd) write(fd, ":hP#", 4) +#define initTelescope(fd) write(fd, ":I#", 3) + +/************************************************************************** + Basic I/O - OBSELETE +**************************************************************************/ +/*int openPort(const char *portID); +int portRead(char *buf, int nbytes, int timeout); +int portWrite(const char * buf); +int LX200readOut(int timeout); +int Connect(const char* device); +void Disconnect();*/ + +/************************************************************************** + Diagnostics + **************************************************************************/ +char ACK(int fd); +/*int testTelescope(); +int testAP();*/ +int check_lx200_connection(int fd); + +/************************************************************************** + Get Commands: store data in the supplied buffer. Return 0 on success or -1 on failure + **************************************************************************/ + +/* Get Double from Sexagisemal */ +int getCommandSexa(int fd, double *value, const char *cmd); +/* Get String */ +int getCommandString(int fd, char *data, const char *cmd); +/* Get Int */ +int getCommandInt(int fd, int *value, const char *cmd); +/* Get tracking frequency */ +int getTrackFreq(int fd, double *value); +/* Get site Latitude */ +int getSiteLatitude(int fd, int *dd, int *mm, double *ssf); +/* Get site Longitude */ +int getSiteLongitude(int fd, int *ddd, int *mm, double *ssf); +/* Get site Latitude */ +int getSiteLatitudeAlt(int fd, int *dd, int *mm, double *ssf, const char *cmd); +/* Get site Longitude */ +int getSiteLongitudeAlt(int fd, int *ddd, int *mm, double *ssf, const char *cmd); +/* Get Calender data */ +int getCalendarDate(int fd, char *date); +/* Get site Name */ +int getSiteName(int fd, char *siteName, int siteNum); +/* Get Home Search Status */ +int getHomeSearchStatus(int fd, int *status); +/* Get OTA Temperature */ +int getOTATemp(int fd, double *value); +/* Get time format: 12 or 24 */ +int getTimeFormat(int fd, int *format); +/* Get RA, DEC from Sky Commander controller */ +int updateSkyCommanderCoord(int fd, double *ra, double *dec); +/* Get RA, DEC from Intelliscope/SkyWizard controllers */ +int updateIntelliscopeCoord(int fd, double *ra, double *dec); + +/************************************************************************** + Set Commands + **************************************************************************/ + +/* Set Int */ +int setCommandInt(int fd, int data, const char *cmd); +/* Set Sexigesimal */ +int setCommandXYZ(int fd, int x, int y, int z, const char *cmd, bool addSpace = false); +/* Common routine for Set commands */ +int setStandardProcedure(int fd, const char *writeData); +/* Set Slew Mode */ +int setSlewMode(int fd, int slewMode); +/* Set Alignment mode */ +int setAlignmentMode(int fd, unsigned int alignMode); +/* Set Object RA */ +int setObjectRA(int fd, double ra, bool addSpace = false); +/* set Object DEC */ +int setObjectDEC(int fd, double dec, bool addSpace = false); +/* Set Calender date */ +int setCalenderDate(int fd, int dd, int mm, int yy, bool addSpace = false); +/* Set UTC offset */ +int setUTCOffset(int fd, double hours); +/* Set Track Freq */ +int setTrackFreq(int fd, double trackF); +/* Replacement, for the above offering more precision, controlled by LX200_HAS_PRECISE_TRACKING_FREQ will work on OnStep (and AutoStar II) */ +int setPreciseTrackFreq(int fd, double trackF); +/* Set current site longitude */ +int setSiteLongitude(int fd, double Long, bool addSpace = false); +/* Set current site latitude */ +int setSiteLatitude(int fd, double Lat, bool addSpace = false); +/* Set Object Azimuth */ +int setObjAz(int fd, double az); +/* Set Object Altitude */ +int setObjAlt(int fd, double alt); +/* Set site name */ +int setSiteName(int fd, char *siteName, int siteNum); +/* Set maximum slew rate */ +int setMaxSlewRate(int fd, int slewRate); +/* Set focuser motion */ +int setFocuserMotion(int fd, int motionType); +/* SET GPS Focuser raneg (1 to 4) */ +int setGPSFocuserSpeed(int fd, int speed); +/* Set focuser speed mode */ +int setFocuserSpeedMode(int fd, int speedMode); +/* Set minimum elevation limit */ +int setMinElevationLimit(int fd, int min); +/* Set maximum elevation limit */ +int setMaxElevationLimit(int fd, int max); + +/* Set X:Y:Z */ +int setLocalTime(int fd, int x, int y, int z, bool addSpace = false); +int setSDTime(int fd, int x, int y, int z, bool addSpace = false); + + +/************************************************************************** + Motion Commands + **************************************************************************/ +/* Slew to the selected coordinates */ +int Slew(int fd); +/* Synchronize to the selected coordinates and return the matching object if any */ +int Sync(int fd, char *matchedObject); +/* Abort slew in all axes */ +int abortSlew(int fd); +/* Move into one direction, two valid directions can be stacked */ +int MoveTo(int fd, int direction); +/* Halt movement in a particular direction */ +int HaltMovement(int fd, int direction); +/* Select the tracking mode */ +int selectTrackingMode(int fd, int trackMode); +/* Is Slew complete? 0 if complete, 1 if in progress, otherwise return an error */ +int isSlewComplete(int fd); +/* Send Pulse-Guide command (timed guide move), two valid directions can be stacked */ +int SendPulseCmd(int fd, int direction, int duration_msec, bool wait_after_command=false, int max_wait_ms=1000); + +/************************************************************************** + Other Commands + **************************************************************************/ +/* Determines LX200 RA/DEC format, tries to set to long if found short */ +int checkLX200EquatorialFormat(int fd); +/* return the controller_format enum value */ +int getLX200EquatorialFormat(); +/* Select a site from the LX200 controller */ +int selectSite(int fd, int siteNum); +/* Select a catalog object */ +int selectCatalogObject(int fd, int catalog, int NNNN); +/* Select a sub catalog */ +int selectSubCatalog(int fd, int catalog, int subCatalog); +/* Set Debug */ +void setLX200Debug(const char *deviceName, unsigned int debug_level); diff --git a/indi-lxd650/lxd650.cpp b/indi-lxd650/lxd650.cpp new file mode 100644 index 0000000..1e0c4a4 --- /dev/null +++ b/indi-lxd650/lxd650.cpp @@ -0,0 +1,429 @@ +#if 0 +LX200 Basic Driver +Copyright (C) 2015 Jasem Mutlaq (mutlaqja@ikarustech.com) + +This library is free software; +you can redistribute it and / or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; +either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; +without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; +if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA + +#endif + +#include "lxd650.h" + +#include "indicom.h" +#include "lx200driver.h" + +#include +#include + +#include +#include +#include +#include + + +using namespace INDI::AlignmentSubsystem; + +/* Simulation Parameters */ +#define SLEWRATE 1 /* slew rate, degrees/s */ +#define SIDRATE 0.004178 /* sidereal rate, degrees/s */ + +/* Our telescope auto pointer */ +static std::unique_ptr telescope(new LXD650()); + +/************************************************************************************** +** LX200 Basic constructor +***************************************************************************************/ +LXD650::LXD650() +{ + setVersion(2, 1); + + DBG_SCOPE = INDI::Logger::getInstance().addDebugLevel("Scope Verbose", "SCOPE"); + + SetTelescopeCapability(TELESCOPE_CAN_SYNC | TELESCOPE_CAN_GOTO | TELESCOPE_CAN_ABORT, 4); + + LOG_DEBUG("Initializing from LX200 Basic device..."); +} + +/************************************************************************************** +** +***************************************************************************************/ +void LXD650::debugTriggered(bool enable) +{ + INDI_UNUSED(enable); + setLX200Debug(getDeviceName(), DBG_SCOPE); +} + +/************************************************************************************** +** +***************************************************************************************/ +const char *LXD650::getDefaultName() +{ + return "LX200 Basic"; +} + +/************************************************************************************** +** +***************************************************************************************/ +bool LXD650::initProperties() +{ + /* Make sure to init parent properties first */ + INDI::Telescope::initProperties(); + + // Slew threshold + IUFillNumber(&SlewAccuracyN[0], "SlewRA", "RA (arcmin)", "%10.6m", 0., 60., 1., 3.0); + IUFillNumber(&SlewAccuracyN[1], "SlewDEC", "Dec (arcmin)", "%10.6m", 0., 60., 1., 3.0); + IUFillNumberVector(&SlewAccuracyNP, SlewAccuracyN, NARRAY(SlewAccuracyN), getDeviceName(), "Slew Accuracy", "", + OPTIONS_TAB, IP_RW, 0, IPS_IDLE); + + addAuxControls(); + + currentRA = get_local_sidereal_time(LocationN[LOCATION_LONGITUDE].value); + currentDEC = LocationN[LOCATION_LATITUDE].value > 0 ? 90 : -90; + + return true; +} + +/************************************************************************************** +** +***************************************************************************************/ +bool LXD650::updateProperties() +{ + INDI::Telescope::updateProperties(); + + if (isConnected()) + { + defineProperty(&SlewAccuracyNP); + + // We don't support NSWE controls + deleteProperty(MovementNSSP.name); + deleteProperty(MovementWESP.name); + + getBasicData(); + } + else + { + deleteProperty(SlewAccuracyNP.name); + } + + return true; +} + +/************************************************************************************** +** +***************************************************************************************/ +bool LXD650::Handshake() +{ + if (getLX200RA(PortFD, ¤tRA) != 0) + { + LOG_ERROR("Error communication with telescope."); + return false; + } + + return true; +} + +/************************************************************************************** +** +***************************************************************************************/ +bool LXD650::isSlewComplete() +{ + const double dx = targetRA - currentRA; + const double dy = targetDEC - currentDEC; + return fabs(dx) <= (SlewAccuracyN[0].value / (900.0)) && fabs(dy) <= (SlewAccuracyN[1].value / 60.0); +} + +/************************************************************************************** +** +***************************************************************************************/ +bool LXD650::ReadScopeStatus() +{ + if (!isConnected()) + return false; + + if (isSimulation()) + { + mountSim(); + return true; + } + + if (getLX200RA(PortFD, ¤tRA) < 0 || getLX200DEC(PortFD, ¤tDEC) < 0) + { + EqNP.s = IPS_ALERT; + IDSetNumber(&EqNP, "Error reading RA/DEC."); + return false; + } + + if (TrackState == SCOPE_SLEWING) + { + // Check if LX200 is done slewing + if (isSlewComplete()) + { + TrackState = SCOPE_TRACKING; + LOG_INFO("Slew is complete. Tracking..."); + } + } + + NewRaDec(currentRA, currentDEC); + + return true; +} + +/************************************************************************************** +** +***************************************************************************************/ +bool LXD650::Goto(double r, double d) +{ + targetRA = r; + targetDEC = d; + char RAStr[64] = {0}, DecStr[64] = {0}; + + fs_sexa(RAStr, targetRA, 2, 3600); + fs_sexa(DecStr, targetDEC, 2, 3600); + + // If moving, let's stop it first. + if (EqNP.s == IPS_BUSY) + { + if (!isSimulation() && abortSlew(PortFD) < 0) + { + AbortSP.s = IPS_ALERT; + IDSetSwitch(&AbortSP, "Abort slew failed."); + return false; + } + + AbortSP.s = IPS_OK; + EqNP.s = IPS_IDLE; + IDSetSwitch(&AbortSP, "Slew aborted."); + IDSetNumber(&EqNP, nullptr); + + // sleep for 100 mseconds + usleep(100000); + } + + if (!isSimulation()) + { + if (setObjectRA(PortFD, targetRA) < 0 || (setObjectDEC(PortFD, targetDEC)) < 0) + { + EqNP.s = IPS_ALERT; + IDSetNumber(&EqNP, "Error setting RA/DEC."); + return false; + } + + int err = 0; + + /* Slew reads the '0', that is not the end of the slew */ + if ((err = Slew(PortFD))) + { + EqNP.s = IPS_ALERT; + IDSetNumber(&EqNP, "Error Slewing to JNow RA %s - DEC %s\n", RAStr, DecStr); + slewError(err); + return false; + } + } + + TrackState = SCOPE_SLEWING; + //EqNP.s = IPS_BUSY; + + LOGF_INFO("Slewing to RA: %s - DEC: %s", RAStr, DecStr); + return true; +} + +/************************************************************************************** +** +***************************************************************************************/ +bool LXD650::Sync(double ra, double dec) +{ + char syncString[256] = {0}; + + if (!isSimulation() && (setObjectRA(PortFD, ra) < 0 || (setObjectDEC(PortFD, dec)) < 0)) + { + EqNP.s = IPS_ALERT; + IDSetNumber(&EqNP, "Error setting RA/DEC. Unable to Sync."); + return false; + } + + if (!isSimulation() && ::Sync(PortFD, syncString) < 0) + { + EqNP.s = IPS_ALERT; + IDSetNumber(&EqNP, "Synchronization failed."); + return false; + } + + currentRA = ra; + currentDEC = dec; + + LOG_INFO("Synchronization successful."); + + EqNP.s = IPS_OK; + + NewRaDec(currentRA, currentDEC); + + return true; +} + +/************************************************************************************** +** +***************************************************************************************/ +bool LXD650::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) +{ + if (dev != nullptr && strcmp(dev, getDeviceName()) == 0) + { + if (!strcmp(name, SlewAccuracyNP.name)) + { + if (IUUpdateNumber(&SlewAccuracyNP, values, names, n) < 0) + return false; + + SlewAccuracyNP.s = IPS_OK; + + if (SlewAccuracyN[0].value < 3 || SlewAccuracyN[1].value < 3) + IDSetNumber(&SlewAccuracyNP, "Warning: Setting the slew accuracy too low may result in a dead lock"); + + IDSetNumber(&SlewAccuracyNP, nullptr); + return true; + } + } + + return INDI::Telescope::ISNewNumber(dev, name, values, names, n); +} + +/************************************************************************************** +** +***************************************************************************************/ +bool LXD650::Abort() +{ + if (!isSimulation() && abortSlew(PortFD) < 0) + { + LOG_ERROR("Failed to abort slew."); + return false; + } + + EqNP.s = IPS_IDLE; + TrackState = SCOPE_IDLE; + IDSetNumber(&EqNP, nullptr); + + LOG_INFO("Slew aborted."); + return true; +} + +/************************************************************************************** +** +***************************************************************************************/ +void LXD650::getBasicData() +{ + // Make sure short + checkLX200EquatorialFormat(PortFD); + + // Get current RA/DEC + getLX200RA(PortFD, ¤tRA); + getLX200DEC(PortFD, ¤tDEC); + + IDSetNumber(&EqNP, nullptr); +} + +/************************************************************************************** +** +***************************************************************************************/ +void LXD650::mountSim() +{ + static struct timeval ltv; + struct timeval tv; + double dt, da, dx; + int nlocked; + + /* update elapsed time since last poll, don't presume exactly POLLMS */ + gettimeofday(&tv, nullptr); + + if (ltv.tv_sec == 0 && ltv.tv_usec == 0) + ltv = tv; + + dt = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec) / 1e6; + ltv = tv; + da = SLEWRATE * dt; + + /* Process per current state. We check the state of EQUATORIAL_COORDS and act acoordingly */ + switch (TrackState) + { + case SCOPE_TRACKING: + /* RA moves at sidereal, Dec stands still */ + currentRA += (SIDRATE * dt / 15.); + break; + + case SCOPE_SLEWING: + /* slewing - nail it when both within one pulse @ SLEWRATE */ + nlocked = 0; + + dx = targetRA - currentRA; + + if (fabs(dx) <= da) + { + currentRA = targetRA; + nlocked++; + } + else if (dx > 0) + currentRA += da / 15.; + else + currentRA -= da / 15.; + + dx = targetDEC - currentDEC; + if (fabs(dx) <= da) + { + currentDEC = targetDEC; + nlocked++; + } + else if (dx > 0) + currentDEC += da; + else + currentDEC -= da; + + if (nlocked == 2) + { + TrackState = SCOPE_TRACKING; + } + + break; + + default: + break; + } + + NewRaDec(currentRA, currentDEC); +} + +/************************************************************************************** +** +***************************************************************************************/ +void LXD650::slewError(int slewCode) +{ + EqNP.s = IPS_ALERT; + + if (slewCode == 1) + IDSetNumber(&EqNP, "Object below horizon."); + else if (slewCode == 2) + IDSetNumber(&EqNP, "Object below the minimum elevation limit."); + else + IDSetNumber(&EqNP, "Slew failed."); +} + +/************************************************************************************** +** +***************************************************************************************/ +bool LXD650::saveConfigItems(FILE *fp) +{ + INDI::Telescope::saveConfigItems(fp); + IUSaveConfigNumber(fp, &SlewAccuracyNP); + return true; +} diff --git a/indi-lxd650/lxd650.h b/indi-lxd650/lxd650.h new file mode 100644 index 0000000..ea6b144 --- /dev/null +++ b/indi-lxd650/lxd650.h @@ -0,0 +1,59 @@ +/* + LX200 Basic Driver + Copyright (C) 2005 Jasem Mutlaq (mutlaqja@ikarustech.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#pragma once + +#include "inditelescope.h" + +class LXD650 : public INDI::Telescope +{ + public: + LXD650(); + ~LXD650() override = default; + + virtual const char *getDefaultName() override; + virtual bool Handshake() override; + virtual bool ReadScopeStatus() override; + virtual bool initProperties() override; + virtual bool updateProperties() override; + virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override; + + protected: + virtual bool Abort() override; + virtual bool Goto(double, double) override; + virtual bool Sync(double ra, double dec) override; + + virtual void debugTriggered(bool enable) override; + virtual bool saveConfigItems(FILE *fp) override; + + void getBasicData(); + + protected: + bool isSlewComplete(); + void slewError(int slewCode); + void mountSim(); + + INumber SlewAccuracyN[2]; + INumberVectorProperty SlewAccuracyNP; + + double targetRA = 0, targetDEC = 0; + double currentRA = 0, currentDEC = 0; + uint32_t DBG_SCOPE = 0; +}; -- cgit v1.2.3