summaryrefslogtreecommitdiffstats
path: root/indi-celestronaux
diff options
context:
space:
mode:
authorJames McKenzie <root@ka-ata-killa.panaceas.james.local>2022-11-27 08:06:02 +0000
committerJames McKenzie <root@ka-ata-killa.panaceas.james.local>2022-11-27 08:06:02 +0000
commit2ba3c5bbf6f8c182c92bfaaf120a8b07937f53f7 (patch)
tree248c3eb1dadff3a7a4d660e9b9ea5a930f3b4e31 /indi-celestronaux
downloadindi_mount_driver-2ba3c5bbf6f8c182c92bfaaf120a8b07937f53f7.tar.gz
indi_mount_driver-2ba3c5bbf6f8c182c92bfaaf120a8b07937f53f7.tar.bz2
indi_mount_driver-2ba3c5bbf6f8c182c92bfaaf120a8b07937f53f7.zip
first
Diffstat (limited to 'indi-celestronaux')
-rw-r--r--indi-celestronaux/.gitignore1
-rw-r--r--indi-celestronaux/CMakeLists.txt33
-rw-r--r--indi-celestronaux/README.md102
-rw-r--r--indi-celestronaux/auxproto.cpp467
-rw-r--r--indi-celestronaux/auxproto.h169
-rw-r--r--indi-celestronaux/celestronaux.cpp2907
-rw-r--r--indi-celestronaux/celestronaux.h442
-rw-r--r--indi-celestronaux/config.h.cmake10
-rw-r--r--indi-celestronaux/indi-celestronaux.spec79
-rw-r--r--indi-celestronaux/indi_celestronaux.xml.cmake33
-rw-r--r--indi-celestronaux/simulator/nse_simulator.py259
-rw-r--r--indi-celestronaux/simulator/nse_telescope.py760
12 files changed, 5262 insertions, 0 deletions
diff --git a/indi-celestronaux/.gitignore b/indi-celestronaux/.gitignore
new file mode 100644
index 0000000..bee8a64
--- /dev/null
+++ b/indi-celestronaux/.gitignore
@@ -0,0 +1 @@
+__pycache__
diff --git a/indi-celestronaux/CMakeLists.txt b/indi-celestronaux/CMakeLists.txt
new file mode 100644
index 0000000..9a3cd27
--- /dev/null
+++ b/indi-celestronaux/CMakeLists.txt
@@ -0,0 +1,33 @@
+########### Celestron AUX INDI driver ##############
+PROJECT(indi-celestronaux 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_celestronaux.xml.cmake ${CMAKE_CURRENT_BINARY_DIR}/indi_celestronaux.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_celestron_aux auxproto.cpp celestronaux.cpp)
+target_link_libraries(indi_celestron_aux ${INDI_LIBRARIES} ${NOVA_LIBRARIES} ${GSL_LIBRARIES})
+install(TARGETS indi_celestron_aux RUNTIME DESTINATION bin)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/indi_celestronaux.xml DESTINATION ${INDI_DATA_DIR})
diff --git a/indi-celestronaux/README.md b/indi-celestronaux/README.md
new file mode 100644
index 0000000..0b831d8
--- /dev/null
+++ b/indi-celestronaux/README.md
@@ -0,0 +1,102 @@
+Celestron AUX protocol driver
+-------------------------------------
+
+Authors: Paweł T. Jochym <jochym@wolf.ifj.edu.pl>
+ Fabrizio Pollastri <https://github.com/fabriziop>
+
+*Do not use this driver unattended! This is BETA software! Always have your power switch ready! It is NOT SUITABLE for autonomous operation yet!*
+*There are no slew limits implemented at this stage.*
+
+This is eqmod-style driver for the NexStar and other Celestron AUX-protocol
+mounts. It works over serial link to PC/AUX ports or HC serial port
+and over WiFi to the NexStar-Evolution or SkyFi-equipped mount.
+
+The driver is in the beta stage.
+It is functional and should work as intended but it is not complete.
+I am using it and will be happy to help any brave testers.
+I of course welcome any feedback/contribution.
+
+What works:
+- N-star alignment (with INDI alignment module)
+- Basic tracking, slew, park/unpark
+- GPS simulation. If you have HC connected and you have active gps driver
+ it can simulate Celestron GPS device and serve GPS data to HC. Works quite
+ nicely on RaspberryPi with a GPS module. You can actually use it as
+ a replacement for the Celestron GPS.
+- Cordwrap control
+
+What does not work/is not implemented:
+- Joystick control
+- Slew limits
+- HC interaction (tracking HC motor commands to function as joystick)
+- Probably many other things
+
+Install
+-------
+
+The driver is not included in the PPA distribution yet - due to its beta
+state. So to use it you need to compile it from the source yourself.
+
+You can make a stand-alone compilation or build the debian packages for your
+system. It should be fairly easy. Let me know if something in the following
+guide is wrong or if you have problem with compiling the driver.
+
+Get the source
+==============
+
+You can get the source from the SVN repository of the system on sourceforge
+maintained by the INDI project (see the website of the project) or get it
+from the github mirror of the sourceforge repository maintained by the author
+of this driver. Both will do fine. The github repository lets you track the
+development of the driver more closely in the nse branch of the repository,
+since only master branch is uploaded back to the upstream SVN repository.
+
+- Make some working directory and change into it.
+- Get the source from the github master branch - it takes a while
+ the repo is 64MB in size.
+
+Compiling on any linux system
+=============================
+
+The compilation is simple. You will need indi libraries installed. The best way
+is to install libindi-dev package from the PPA. You may also want to have
+indi-gpsd and gpsd packages installed (not strictly required). If you cannot use
+the PPA you need to install libindi-dev from your distribution or compile the
+indi libraries yourself using instructions from the INDI website. I have not
+tested the backward compatibility but the driver should compile and work at
+least with the 1.8.4 version of the library. My recommendation: use PPA if you
+can. To compile the driver you will need also: cmake, cdbs, libindi-dev,
+libnova-dev, zlib1g-dev. Run following commands (you can select other install
+prefix):
+
+```sh
+mkdir -p ~/Projects/build/indi-caux
+cd ~/Projects/build/indi-caux
+cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Debug ~/Projects/indi-3rdparty/indi-celestronaux
+make
+```
+You can run `make install` optionally at the end if you like to have the driver
+properly installed.
+
+
+Building debian/ubuntu packages
+===============================
+
+To build the debian package you will need the debian packaging tools:
+`build-essential, devscripts, debhelper, fakeroot`
+
+Create `package` directory at the same level as indilib directory with the
+cloned source. Then execute:
+
+```sh
+mkdir -p ~/Projects/build/deb-indi-caux
+cd ~/Projects/build/deb-indi-caux
+cp -r ~/Projects/indi-3rdparty/indi-celestronaux .
+cp -r ~/Projects/indi-3rdparty/debian//indi-celestronaux debian
+cp -r ~/Projects/indi-3rdparty/cmake_modules indi-celestronaux/
+fakeroot debian/rules binary
+fakeroot debian/rules clean
+```
+this should produce two packages in the main build directory (above `package`),
+which you can install with `sudo dpkg -i indi-celestronaux_*.deb`.
+
diff --git a/indi-celestronaux/auxproto.cpp b/indi-celestronaux/auxproto.cpp
new file mode 100644
index 0000000..9891e10
--- /dev/null
+++ b/indi-celestronaux/auxproto.cpp
@@ -0,0 +1,467 @@
+/*
+ Celestron Aux Command
+
+ Copyright (C) 2020 Paweł T. Jochym
+ Copyright (C) 2020 Fabrizio Pollastri
+ Copyright (C) 2021 Jasem Mutlaq
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+*/
+
+#include "auxproto.h"
+
+#include <indilogger.h>
+#include <math.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#define READ_TIMEOUT 1 // s
+#define CTS_TIMEOUT 100 // ms
+#define RTS_DELAY 50 // ms
+
+#define BUFFER_SIZE 512
+int MAX_CMD_LEN = 32;
+
+uint8_t AUXCommand::DEBUG_LEVEL = 0;
+char AUXCommand::DEVICE_NAME[64] = {0};
+//////////////////////////////////////////////////
+/////// Utility functions
+//////////////////////////////////////////////////
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void logBytes(unsigned char *buf, int n, const char *deviceName, uint32_t debugLevel)
+{
+ char hex_buffer[BUFFER_SIZE] = {0};
+ for (int i = 0; i < n; i++)
+ sprintf(hex_buffer + 3 * i, "%02X ", buf[i]);
+
+ if (n > 0)
+ hex_buffer[3 * n - 1] = '\0';
+
+ DEBUGFDEVICE(deviceName, debugLevel, "[%s]", hex_buffer);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void AUXCommand::logResponse()
+{
+ char hex_buffer[BUFFER_SIZE] = {0}, part1[BUFFER_SIZE] = {0}, part2[BUFFER_SIZE] = {0}, part3[BUFFER_SIZE] = {0};
+ for (size_t i = 0; i < m_Data.size(); i++)
+ sprintf(hex_buffer + 3 * i, "%02X ", m_Data[i]);
+
+ if (m_Data.size() > 0)
+ hex_buffer[3 * m_Data.size() - 1] = '\0';
+
+ const char * c = commandName(m_Command);
+ const char * s = moduleName(m_Source);
+ const char * d = moduleName(m_Destination);
+
+ if (c != nullptr)
+ snprintf(part1, BUFFER_SIZE, "<%12s>", c);
+ else
+ snprintf(part1, BUFFER_SIZE, "<%02x>", m_Command);
+
+ if (s != nullptr)
+ snprintf(part2, BUFFER_SIZE, "%5s ->", s);
+ else
+ snprintf(part2, BUFFER_SIZE, "%02x ->", m_Source);
+
+ if (s != nullptr)
+ snprintf(part3, BUFFER_SIZE, "%5s", d);
+ else
+ snprintf(part3, BUFFER_SIZE, "%02x", m_Destination);
+
+ if (m_Data.size() > 0)
+ DEBUGFDEVICE(DEVICE_NAME, DEBUG_LEVEL, "RES %s%s%s [%s]", part1, part2, part3, hex_buffer);
+ else
+ DEBUGFDEVICE(DEVICE_NAME, DEBUG_LEVEL, "RES %s%s%s", part1, part2, part3);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void AUXCommand::logCommand()
+{
+ char hex_buffer[BUFFER_SIZE] = {0}, part1[BUFFER_SIZE] = {0}, part2[BUFFER_SIZE] = {0}, part3[BUFFER_SIZE] = {0};
+ for (size_t i = 0; i < m_Data.size(); i++)
+ sprintf(hex_buffer + 3 * i, "%02X ", m_Data[i]);
+
+ if (m_Data.size() > 0)
+ hex_buffer[3 * m_Data.size() - 1] = '\0';
+
+ const char * c = commandName(m_Command);
+ const char * s = moduleName(m_Source);
+ const char * d = moduleName(m_Destination);
+
+ if (c != nullptr)
+ snprintf(part1, BUFFER_SIZE, "<%12s>", c);
+ else
+ snprintf(part1, BUFFER_SIZE, "<%02x>", m_Command);
+
+ if (s != nullptr)
+ snprintf(part2, BUFFER_SIZE, "%5s ->", s);
+ else
+ snprintf(part2, BUFFER_SIZE, "%02x ->", m_Source);
+
+ if (s != nullptr)
+ snprintf(part3, BUFFER_SIZE, "%5s", d);
+ else
+ snprintf(part3, BUFFER_SIZE, "%02x", m_Destination);
+
+ if (m_Data.size() > 0)
+ DEBUGFDEVICE(DEVICE_NAME, DEBUG_LEVEL, "CMD %s%s%s [%s]", part1, part2, part3, hex_buffer);
+ else
+ DEBUGFDEVICE(DEVICE_NAME, DEBUG_LEVEL, "CMD %s%s%s", part1, part2, part3);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void AUXCommand::setDebugInfo(const char *deviceName, uint8_t debugLevel)
+{
+ strncpy(DEVICE_NAME, deviceName, 64);
+ DEBUG_LEVEL = debugLevel;
+}
+////////////////////////////////////////////////
+////// AUXCommand class
+////////////////////////////////////////////////
+
+AUXCommand::AUXCommand()
+{
+ m_Data.reserve(MAX_CMD_LEN);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+AUXCommand::AUXCommand(const AUXBuffer &buf)
+{
+ m_Data.reserve(MAX_CMD_LEN);
+ parseBuf(buf);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+AUXCommand::AUXCommand(AUXCommands command, AUXTargets source, AUXTargets destination, const AUXBuffer &data)
+{
+ m_Command = command;
+ m_Source = source;
+ m_Destination = destination;
+ m_Data.reserve(MAX_CMD_LEN);
+ m_Data = data;
+ len = 3 + m_Data.size();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+AUXCommand::AUXCommand(AUXCommands command, AUXTargets source, AUXTargets destination)
+{
+ m_Command = command;
+ m_Source = source;
+ m_Destination = destination;
+ m_Data.reserve(MAX_CMD_LEN);
+ len = 3 + m_Data.size();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+const char * AUXCommand::commandName(AUXCommands command) const
+{
+ if (m_Source == GPS || m_Destination == GPS)
+ {
+ switch (command)
+ {
+ case GPS_GET_LAT:
+ return "GPS_GET_LAT";
+ case GPS_GET_LONG:
+ return "GPS_GET_LONG";
+ case GPS_GET_DATE:
+ return "GPS_GET_DATE";
+ case GPS_GET_YEAR:
+ return "GPS_GET_YEAR";
+ case GPS_GET_TIME:
+ return "GPS_GET_TIME";
+ case GPS_TIME_VALID:
+ return "GPS_TIME_VALID";
+ case GPS_LINKED:
+ return "GPS_LINKED";
+ case GET_VER:
+ return "GET_VER";
+ default :
+ return nullptr;
+ }
+ }
+ else
+ {
+ switch (command)
+ {
+ case MC_GET_POSITION:
+ return "MC_GET_POSITION";
+ case MC_GOTO_FAST:
+ return "MC_GOTO_FAST";
+ case MC_SET_POSITION:
+ return "MC_SET_POSITION";
+ case MC_SET_POS_GUIDERATE:
+ return "MC_SET_POS_GUIDERATE";
+ case MC_SET_NEG_GUIDERATE:
+ return "MC_SET_NEG_GUIDERATE";
+ case MC_LEVEL_START:
+ return "MC_LEVEL_START";
+ case MC_SLEW_DONE:
+ return "MC_SLEW_DONE";
+ case MC_GOTO_SLOW:
+ return "MC_GOTO_SLOW";
+ case MC_SEEK_INDEX:
+ return "MC_SEEK_INDEX";
+ case MC_MOVE_POS:
+ return "MC_MOVE_POS";
+ case MC_MOVE_NEG:
+ return "MC_MOVE_NEG";
+ case MC_ENABLE_CORDWRAP:
+ return "MC_ENABLE_CORDWRAP";
+ case MC_DISABLE_CORDWRAP:
+ return "MC_DISABLE_CORDWRAP";
+ case MC_SET_CORDWRAP_POS:
+ return "MC_SET_CORDWRAP_POS";
+ case MC_POLL_CORDWRAP:
+ return "MC_POLL_CORDWRAP";
+ case MC_GET_CORDWRAP_POS:
+ return "MC_GET_CORDWRAP_POS";
+ case GET_VER:
+ return "GET_VER";
+ default :
+ return nullptr;
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+int AUXCommand::responseDataSize()
+{
+ if (m_Source == GPS || m_Destination == GPS)
+ {
+ switch (m_Command)
+ {
+ case GPS_GET_LAT:
+ case GPS_GET_LONG:
+ case GPS_GET_TIME:
+ return 3;
+ case GPS_GET_DATE:
+ case GPS_GET_YEAR:
+ case GET_VER:
+ return 2;
+ case GPS_TIME_VALID:
+ case GPS_LINKED:
+ return 1;
+ default :
+ return -1;
+ }
+ }
+ else
+ {
+ switch (m_Command)
+ {
+ case MC_GET_POSITION:
+ case MC_GET_CORDWRAP_POS:
+ return 3;
+ case GET_VER:
+ return 4;
+ case MC_SLEW_DONE:
+ case MC_SEEK_DONE:
+ case MC_LEVEL_DONE:
+ case MC_POLL_CORDWRAP:
+ return 1;
+ case MC_GOTO_FAST:
+ case MC_SET_POSITION:
+ case MC_SET_POS_GUIDERATE:
+ case MC_SET_NEG_GUIDERATE:
+ case MC_LEVEL_START:
+ case MC_GOTO_SLOW:
+ case MC_MOVE_POS:
+ case MC_MOVE_NEG:
+ case MC_ENABLE_CORDWRAP:
+ case MC_DISABLE_CORDWRAP:
+ case MC_SET_CORDWRAP_POS:
+ return 0;
+ case MC_SEEK_INDEX:
+ return -1;
+ default :
+ return -1;
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+const char * AUXCommand::moduleName(AUXTargets n)
+{
+ switch (n)
+ {
+ case ANY :
+ return "ANY";
+ case MB :
+ return "MB";
+ case HC :
+ return "HC";
+ case HCP :
+ return "HC+";
+ case AZM :
+ return "AZM";
+ case ALT :
+ return "ALT";
+ case APP :
+ return "APP";
+ case GPS :
+ return "GPS";
+ case WiFi:
+ return "WiFi";
+ case BAT :
+ return "BAT";
+ case CHG :
+ return "CHG";
+ case LIGHT :
+ return "LIGHT";
+ default :
+ return nullptr;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void AUXCommand::fillBuf(AUXBuffer &buf)
+{
+ buf.resize(len + 3);
+
+ buf[0] = 0x3b;
+ buf[1] = len;
+ buf[2] = m_Source;
+ buf[3] = m_Destination;
+ buf[4] = m_Command;
+ for (uint32_t i = 0; i < m_Data.size(); i++)
+ {
+ buf[i + 5] = m_Data[i];
+ }
+ buf.back() = checksum(buf);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void AUXCommand::parseBuf(AUXBuffer buf)
+{
+ len = buf[1];
+ m_Source = (AUXTargets)buf[2];
+ m_Destination = (AUXTargets)buf[3];
+ m_Command = (AUXCommands)buf[4];
+ m_Data = AUXBuffer(buf.begin() + 5, buf.end() - 1);
+ valid = (checksum(buf) == buf.back());
+ if (valid == false)
+ {
+ DEBUGFDEVICE(DEVICE_NAME, DEBUG_LEVEL, "Checksum error: %02x vs. %02x", checksum(buf), buf.back());
+ };
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void AUXCommand::parseBuf(AUXBuffer buf, bool do_checksum)
+{
+ (void)do_checksum;
+
+ len = buf[1];
+ m_Source = (AUXTargets)buf[2];
+ m_Destination = (AUXTargets)buf[3];
+ m_Command = (AUXCommands)buf[4];
+ if (buf.size() > 5)
+ m_Data = AUXBuffer(buf.begin() + 5, buf.end());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+unsigned char AUXCommand::checksum(AUXBuffer buf)
+{
+ int l = buf[1];
+ int cs = 0;
+ for (int i = 1; i < l + 2; i++)
+ {
+ cs += buf[i];
+ }
+ return (unsigned char)(((~cs) + 1) & 0xFF);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+/// Return 8, 16, or 24bit value as dictacted by the data response size.
+/////////////////////////////////////////////////////////////////////////////////////
+uint32_t AUXCommand::getData()
+{
+ uint32_t value = 0;
+ switch (m_Data.size())
+ {
+ case 3:
+ value = (m_Data[0] << 16) | (m_Data[1] << 8) | m_Data[2];
+ break;
+
+ case 2:
+ value = (m_Data[0] << 8) | m_Data[1];
+ break;
+
+ case 1:
+ value = m_Data[0];
+ break;
+ }
+
+ return value;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+/// Set encoder position in steps.
+/////////////////////////////////////////////////////////////////////////////////////
+void AUXCommand::setData(uint32_t value, uint8_t bytes)
+{
+ m_Data.resize(bytes);
+ switch (bytes)
+ {
+ case 1:
+ len = 4;
+ m_Data[0] = static_cast<uint8_t>(value >> 0 & 0xff);
+ break;
+ case 2:
+ len = 5;
+ m_Data[1] = static_cast<uint8_t>(value >> 0 & 0xff);
+ m_Data[0] = static_cast<uint8_t>(value >> 8 & 0xff);
+ break;
+
+ case 3:
+ default:
+ len = 6;
+ m_Data[2] = static_cast<uint8_t>(value >> 0 & 0xff);
+ m_Data[1] = static_cast<uint8_t>(value >> 8 & 0xff);
+ m_Data[0] = static_cast<uint8_t>(value >> 16 & 0xff);
+ break;
+ }
+}
diff --git a/indi-celestronaux/auxproto.h b/indi-celestronaux/auxproto.h
new file mode 100644
index 0000000..3593eee
--- /dev/null
+++ b/indi-celestronaux/auxproto.h
@@ -0,0 +1,169 @@
+/*
+ Celestron Aux Command
+
+ Copyright (C) 2020 Paweł T. Jochym
+ Copyright (C) 2020 Fabrizio Pollastri
+ Copyright (C) 2021 Jasem Mutlaq
+
+ 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 <vector>
+#include <stddef.h>
+#include <stdint.h>
+
+typedef std::vector<uint8_t> AUXBuffer;
+
+enum AUXCommands
+{
+ MC_GET_POSITION = 0x01,
+ MC_GOTO_FAST = 0x02,
+ MC_SET_POSITION = 0x04,
+ MC_SET_POS_GUIDERATE = 0x06,
+ MC_SET_NEG_GUIDERATE = 0x07,
+ MC_LEVEL_START = 0x0b,
+ MC_LEVEL_DONE = 0x12,
+ MC_SLEW_DONE = 0x13,
+ MC_GOTO_SLOW = 0x17,
+ MC_SEEK_DONE = 0x18,
+ MC_SEEK_INDEX = 0x19,
+ MC_MOVE_POS = 0x24,
+ MC_MOVE_NEG = 0x25,
+ MC_AUX_GUIDE = 0x26,
+ MC_AUX_GUIDE_ACTIVE = 0x27,
+ MC_ENABLE_CORDWRAP = 0x38,
+ MC_DISABLE_CORDWRAP = 0x39,
+ MC_SET_CORDWRAP_POS = 0x3a,
+ MC_POLL_CORDWRAP = 0x3b,
+ MC_GET_CORDWRAP_POS = 0x3c,
+ MC_SET_AUTOGUIDE_RATE = 0x46,
+ MC_GET_AUTOGUIDE_RATE = 0x47,
+ GET_VER = 0xfe,
+ GPS_GET_LAT = 0x01,
+ GPS_GET_LONG = 0x02,
+ GPS_GET_DATE = 0x03,
+ GPS_GET_YEAR = 0x04,
+ GPS_GET_TIME = 0x33,
+ GPS_TIME_VALID = 0x36,
+ GPS_LINKED = 0x37
+};
+
+enum AUXTargets
+{
+ ANY = 0x00,
+ MB = 0x01,
+ HC = 0x04,
+ HCP = 0x0d,
+ AZM = 0x10,
+ ALT = 0x11,
+ APP = 0x20,
+ GPS = 0xb0,
+ WiFi = 0xb5,
+ BAT = 0xb6,
+ CHG = 0xb7,
+ LIGHT = 0xbf
+};
+
+#define CAUX_DEFAULT_IP "1.2.3.4"
+#define CAUX_DEFAULT_PORT 2000
+
+void logBytes(unsigned char *buf, int n, const char *deviceName, uint32_t debugLevel);
+
+class AUXCommand
+{
+ public:
+ AUXCommand();
+ AUXCommand(const AUXBuffer &buf);
+ AUXCommand(AUXCommands command, AUXTargets source, AUXTargets destination, const AUXBuffer &data);
+ AUXCommand(AUXCommands command, AUXTargets source, AUXTargets destination);
+
+ ///////////////////////////////////////////////////////////////////////////////
+ /// Buffer Management
+ ///////////////////////////////////////////////////////////////////////////////
+ void fillBuf(AUXBuffer &buf);
+ void parseBuf(AUXBuffer buf);
+ void parseBuf(AUXBuffer buf, bool do_checksum);
+
+ ///////////////////////////////////////////////////////////////////////////////
+ /// Getters
+ ///////////////////////////////////////////////////////////////////////////////
+ const AUXTargets &source() const
+ {
+ return m_Source;
+ }
+ const AUXTargets &destination() const
+ {
+ return m_Destination;
+ }
+ const AUXBuffer &data() const
+ {
+ return m_Data;
+ }
+ AUXCommands command() const
+ {
+ return m_Command;
+ }
+ size_t dataSize() const
+ {
+ return m_Data.size();
+ }
+ const char * commandName() const
+ {
+ return commandName(m_Command);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ /// Set and Get data
+ ///////////////////////////////////////////////////////////////////////////////
+ /**
+ * @brief getData Parses data packet and convert it to a 32bit unsigned integer
+ * @param bytes How many bytes to interpret the data.
+ * @return
+ */
+ uint32_t getData();
+ void setData(uint32_t value, uint8_t bytes = 3);
+
+ ///////////////////////////////////////////////////////////////////////////////
+ /// Check sum
+ ///////////////////////////////////////////////////////////////////////////////
+ uint8_t checksum(AUXBuffer buf);
+
+ ///////////////////////////////////////////////////////////////////////////////
+ /// Logging
+ ///////////////////////////////////////////////////////////////////////////////
+ const char * commandName(AUXCommands command) const;
+ const char * moduleName(AUXTargets n);
+ int responseDataSize();
+ void logResponse();
+ void logCommand();
+ static void setDebugInfo(const char *deviceName, uint8_t debugLevel);
+
+ static uint8_t DEBUG_LEVEL;
+ static char DEVICE_NAME[64];
+
+ private:
+ uint8_t len {0};
+ bool valid {false};
+
+ AUXCommands m_Command;
+ AUXTargets m_Source, m_Destination;
+ AUXBuffer m_Data;
+
+
+
+};
diff --git a/indi-celestronaux/celestronaux.cpp b/indi-celestronaux/celestronaux.cpp
new file mode 100644
index 0000000..ad8afe9
--- /dev/null
+++ b/indi-celestronaux/celestronaux.cpp
@@ -0,0 +1,2907 @@
+/*
+ Celestron Aux Mount Driver.
+
+ Copyright (C) 2020 Paweł T. Jochym
+ Copyright (C) 2020 Fabrizio Pollastri
+ Copyright (C) 2020-2022 Jasem Mutlaq
+
+ 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
+
+ JM 2022.07.07: Added Wedge support.
+*/
+
+#include <algorithm>
+#include <math.h>
+#include <queue>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <thread>
+#include <chrono>
+
+#include <alignment/DriverCommon.h>
+#include "celestronaux.h"
+#include "config.h"
+
+#define DEBUG_PID
+
+using namespace INDI::AlignmentSubsystem;
+
+static std::unique_ptr<CelestronAUX> telescope_caux(new CelestronAUX());
+
+double anglediff(double a, double b)
+{
+ // Signed angle difference
+ double d;
+ b = fmod(b, 360.0);
+ a = fmod(a, 360.0);
+ d = fmod(a - b + 360.0, 360.0);
+ if (d > 180)
+ d = 360.0 - d;
+ return std::abs(d) * ((a - b >= 0 && a - b <= 180) || (a - b <= -180 && a - b >= -360) ? 1 : -1);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+CelestronAUX::CelestronAUX()
+ : ScopeStatus(IDLE),
+ DBG_CAUX(INDI::Logger::getInstance().addDebugLevel("AUX", "CAUX")),
+ DBG_SERIAL(INDI::Logger::getInstance().addDebugLevel("Serial", "CSER"))
+{
+ setVersion(CAUX_VERSION_MAJOR, CAUX_VERSION_MINOR);
+ SetTelescopeCapability(TELESCOPE_CAN_PARK |
+ TELESCOPE_CAN_SYNC |
+ TELESCOPE_CAN_GOTO |
+ TELESCOPE_CAN_ABORT |
+ TELESCOPE_HAS_TIME |
+ TELESCOPE_HAS_LOCATION |
+ TELESCOPE_CAN_CONTROL_TRACK |
+ TELESCOPE_HAS_TRACK_MODE |
+ TELESCOPE_HAS_TRACK_RATE
+ , 8);
+
+ //Both communication available, Serial and network (tcp/ip).
+ setTelescopeConnection(CONNECTION_TCP | CONNECTION_SERIAL);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+CelestronAUX::~CelestronAUX()
+{
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::Handshake()
+{
+ LOGF_DEBUG("CAUX: connect %d (%s)", PortFD, (getActiveConnection() == serialConnection) ? "serial" : "net");
+ if (PortFD > 0)
+ {
+ if (getActiveConnection() == serialConnection)
+ {
+ if (PortTypeSP[PORT_AUX_PC].getState() == ISS_ON)
+ {
+ serialConnection->setDefaultBaudRate(Connection::Serial::B_19200);
+ if (!tty_set_speed(B19200))
+ return false;
+ m_IsRTSCTS = detectRTSCTS();
+ }
+ else
+ {
+ serialConnection->setDefaultBaudRate(Connection::Serial::B_9600);
+ if (!tty_set_speed(B9600))
+ {
+ LOG_ERROR("Cannot set serial speed to 9600 baud.");
+ return false;
+ }
+
+ // wait for speed to settle
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+
+ LOG_INFO("Setting serial speed to 9600 baud.");
+
+ // detect if connectd to HC port or to mount USB port
+ // ask for HC version
+ char version[10];
+ if ((m_isHandController = detectHC(version, 10)))
+ LOGF_INFO("Detected Hand Controller (v%s) serial connection.", version);
+ else
+ LOG_INFO("Detected Mount USB serial connection.");
+ }
+ }
+ else
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
+
+ // read firmware version, if read ok, detected scope
+ LOG_DEBUG("Communicating with mount motor controllers...");
+ if (getVersion(AZM) && getVersion(ALT))
+ {
+ LOG_INFO("Got response from target ALT or AZM.");
+ }
+ else
+ {
+ LOG_ERROR("Got no response from target ALT or AZM.");
+ LOG_ERROR("Cannot continue without connection to motor controllers.");
+ return false;
+ }
+
+ LOG_DEBUG("Connection ready. Starting Processing.");
+
+ // set mount type to alignment subsystem
+ //SetApproximateMountAlignmentFromMountType(static_cast<MountType>(MountTypeSP.findOnSwitchIndex()));
+ // tell the alignment math plugin to reinitialise
+ Initialise(this);
+
+ // update cordwrap position at each init of the alignment subsystem
+ if (isConnected())
+ syncCoordWrapPosition();
+
+ return true;
+ }
+ else
+ {
+ return false ;
+ }
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::Disconnect()
+{
+ Abort();
+ return INDI::Telescope::Disconnect();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+const char *CelestronAUX::getDefaultName()
+{
+ return "Celestron AUX";
+}
+
+/////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////
+void CelestronAUX::ISGetProperties(const char *dev)
+{
+ INDI::Telescope::ISGetProperties(dev);
+ defineProperty(&PortTypeSP);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::initProperties()
+{
+ INDI::Telescope::initProperties();
+ setDriverInterface(getDriverInterface() | GUIDER_INTERFACE);
+
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Main Control Tab
+ /////////////////////////////////////////////////////////////////////////////////////
+ // Mount type
+ int configMountType = ALTAZ;
+ IUGetConfigOnSwitchIndex(getDeviceName(), "MOUNT_TYPE", &configMountType);
+
+ // Detect Equatorial Mounts
+ if (strstr(getDeviceName(), "CGX") ||
+ strstr(getDeviceName(), "CGEM") ||
+ strstr(getDeviceName(), "Advanced VX") ||
+ strstr(getDeviceName(), "Wedge"))
+ {
+ // Force equatorial for such mounts
+ configMountType = EQUATORIAL;
+ m_IsWedge = (strstr(getDeviceName(), "Wedge") != nullptr);
+ }
+
+ if (configMountType == EQUATORIAL)
+ SetApproximateMountAlignment(m_Location.latitude >= 0 ? NORTH_CELESTIAL_POLE : SOUTH_CELESTIAL_POLE);
+ else
+ SetApproximateMountAlignment(ZENITH);
+
+ MountTypeSP[EQUATORIAL].fill("EQUATORIAL", "Equatorial", configMountType == EQUATORIAL ? ISS_ON : ISS_OFF);
+ MountTypeSP[ALTAZ].fill("ALTAZ", "AltAz", configMountType == ALTAZ ? ISS_ON : ISS_OFF);
+ MountTypeSP.fill(getDeviceName(), "MOUNT_TYPE", "Mount Type", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
+
+ // Track Modes for Equatorial Mount
+ if (MountTypeSP[EQUATORIAL].getState() == ISS_ON)
+ {
+ AddTrackMode("TRACK_SIDEREAL", "Sidereal", true);
+ AddTrackMode("TRACK_SOLAR", "Solar");
+ AddTrackMode("TRACK_LUNAR", "Lunar");
+ AddTrackMode("TRACK_CUSTOM", "Custom");
+
+ SetTelescopeCapability(GetTelescopeCapability() | TELESCOPE_HAS_PIER_SIDE, 8);
+ }
+
+ // Horizontal Coords
+ HorizontalCoordsNP[AXIS_AZ].fill("AZ", "Az D:M:S", "%10.6m", 0.0, 360.0, 0.0, 0);
+ HorizontalCoordsNP[AXIS_ALT].fill("ALT", "Alt D:M:S", "%10.6m", -90., 90.0, 0.0, 0);
+ HorizontalCoordsNP.fill(getDeviceName(), "HORIZONTAL_COORD", "Horizontal Coord", MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
+
+ // Homing
+ HomeSP[HOME_AXIS1].fill("AXIS1", "AZ/RA", ISS_OFF);
+ HomeSP[HOME_AXIS2].fill("AXIS2", "AL/DE", ISS_OFF);
+ HomeSP[HOME_ALL].fill("ALL", "All", ISS_OFF);
+ HomeSP.fill(getDeviceName(), "HOME", "Home", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
+
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Cord Wrap Tab
+ /////////////////////////////////////////////////////////////////////////////////////
+
+ // Cord wrap Toggle
+ CordWrapToggleSP[INDI_ENABLED].fill("INDI_ENABLED", "Enabled", ISS_OFF);
+ CordWrapToggleSP[INDI_DISABLED].fill("INDI_DISABLED", "Disabled", ISS_ON);
+ CordWrapToggleSP.fill(getDeviceName(), "CORDWRAP", "Cord Wrap", CORDWRAP_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
+
+ // Cord Wrap Position
+ CordWrapPositionSP[CORDWRAP_N].fill("CORDWRAP_N", "North", ISS_ON);
+ CordWrapPositionSP[CORDWRAP_E].fill("CORDWRAP_E", "East", ISS_OFF);
+ CordWrapPositionSP[CORDWRAP_S].fill("CORDWRAP_S", "South", ISS_OFF);
+ CordWrapPositionSP[CORDWRAP_W].fill("CORDWRAP_W", "West", ISS_OFF);
+ CordWrapPositionSP.fill(getDeviceName(), "CORDWRAP_POS", "CW Position", CORDWRAP_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
+
+ // Cord Wrap / Park Base
+ CordWrapBaseSP[CW_BASE_ENC].fill("CW_BASE_ENC", "Encoders", ISS_ON);
+ CordWrapBaseSP[CW_BASE_SKY].fill("CW_BASE_SKY", "Alignment positions", ISS_OFF);
+ CordWrapBaseSP.fill(getDeviceName(), "CW_BASE", "CW Position Base", CORDWRAP_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
+
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Options
+ /////////////////////////////////////////////////////////////////////////////////////
+ // GPS Emulation
+ GPSEmuSP[GPSEMU_OFF].fill("GPSEMU_OFF", "OFF", ISS_OFF);
+ GPSEmuSP[GPSEMU_ON].fill("GPSEMU_ON", "ON", ISS_ON);
+ GPSEmuSP.fill(getDeviceName(), "GPSEMU", "GPS Emu", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
+
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Guide Tab
+ /////////////////////////////////////////////////////////////////////////////////////
+
+ // Guide Properties
+ initGuiderProperties(getDeviceName(), GUIDE_TAB);
+ // Rate rate
+ GuideRateNP[AXIS_AZ].fill("GUIDE_RATE_WE", "W/E Rate", "%.f", 0, 1, .1, 0.5);
+ GuideRateNP[AXIS_ALT].fill("GUIDE_RATE_NS", "N/S Rate", "%.f", 0, 1, .1, 0.5);
+ GuideRateNP.fill(getDeviceName(), "GUIDE_RATE", "Guiding Rate", GUIDE_TAB, IP_RW, 0, IPS_IDLE);
+
+ setDriverInterface(getDriverInterface() | GUIDER_INTERFACE);
+
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Connection
+ /////////////////////////////////////////////////////////////////////////////////////
+ IUGetConfigOnSwitchIndex(getDeviceName(), "PORT_TYPE", &m_ConfigPortType);
+ PortTypeSP[PORT_AUX_PC].fill("PORT_AUX_PC", "AUX/PC", m_ConfigPortType == PORT_AUX_PC ? ISS_ON : ISS_OFF);
+ PortTypeSP[PORT_HC_USB].fill("PORT_HC_USB", "USB/HC", m_ConfigPortType == PORT_AUX_PC ? ISS_OFF : ISS_ON);
+ PortTypeSP.fill(getDeviceName(), "PORT_TYPE", "Port Type", CONNECTION_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Mount Information
+ /////////////////////////////////////////////////////////////////////////////////////
+
+ // Raw encoder values
+ EncoderNP[AXIS_AZ].fill("AXIS_AZ", "Axis 1", "%.f", 0, STEPS_PER_REVOLUTION, 0, 0);
+ EncoderNP[AXIS_ALT].fill("AXIS_ALT", "Axis 2", "%.f", 0, STEPS_PER_REVOLUTION, 0, 0);
+ EncoderNP.fill(getDeviceName(), "TELESCOPE_ENCODER_STEPS", "Encoders", MOUNTINFO_TAB, IP_RW, 60, IPS_IDLE);
+
+ // Encoder -> Angle values
+ AngleNP[AXIS_AZ].fill("AXIS_AZ", "Axis 1", "%.2f", 0, 360, 0, 0);
+ AngleNP[AXIS_ALT].fill("AXIS_ALT", "Axis 2", "%.2f", -90, 90, 0, 0);
+ AngleNP.fill(getDeviceName(), "TELESCOPE_ENCODER_ANGLES", "Angles", MOUNTINFO_TAB, IP_RO, 60, IPS_IDLE);
+
+ // PID Control
+ Axis1PIDNP[Propotional].fill("Propotional", "Propotional", "%.2f", 0, 500, 10, GAIN_STEPS);
+ Axis1PIDNP[Derivative].fill("Derivative", "Derivative", "%.2f", 0, 500, 10, 0);
+ Axis1PIDNP[Integral].fill("Integral", "Integral", "%.2f", 0, 500, 10, 0);
+ Axis1PIDNP.fill(getDeviceName(), "AXIS1_PID", "Axis1 PID", MOUNTINFO_TAB, IP_RW, 60, IPS_IDLE);
+
+ Axis2PIDNP[Propotional].fill("Propotional", "Propotional", "%.2f", 0, 500, 10, GAIN_STEPS);
+ Axis2PIDNP[Derivative].fill("Derivative", "Derivative", "%.2f", 0, 100, 10, 0);
+ Axis2PIDNP[Integral].fill("Integral", "Integral", "%.2f", 0, 100, 10, 1);
+ Axis2PIDNP.fill(getDeviceName(), "AXIS2_PID", "Axis2 PID", MOUNTINFO_TAB, IP_RW, 60, IPS_IDLE);
+
+ // Firmware Info
+ FirmwareTP[FW_HC].fill("HC version", "", nullptr);
+ FirmwareTP[FW_MB].fill("Mother Board version", "", nullptr);
+ FirmwareTP[FW_AZM].fill("Ra/AZM version", "", nullptr);
+ FirmwareTP[FW_ALT].fill("Dec/ALT version", "", nullptr);
+ FirmwareTP[FW_WiFi].fill("WiFi version", "", nullptr);
+ FirmwareTP[FW_BAT].fill("Battery version", "", nullptr);
+ FirmwareTP[FW_GPS].fill("GPS version", "", nullptr);
+ FirmwareTP.fill(getDeviceName(), "Firmware Info", "Firmware Info", MOUNTINFO_TAB, IP_RO, 0, IPS_IDLE);
+
+ // Gain Rate
+ // GainNP[AXIS_AZ].fill("AXIS_AZ", "Axis1", "%.f", -10000, 10000, 500, 0);
+ // GainNP[AXIS_ALT].fill("AXIS_ALT", "Axis2", "%.f", -10000, 10000, 500, 0);
+ // GainNP.fill(getDeviceName(), "TRACK_GAIN", "Gain", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE);
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Initial Configuration
+ /////////////////////////////////////////////////////////////////////////////////////
+
+ // Set Debug Information for AUX Commands
+ AUXCommand::setDebugInfo(getDeviceName(), DBG_CAUX);
+
+ // Add debug controls so we may debug driver if necessary
+ addDebugControl();
+
+ // Add alignment properties
+ InitAlignmentProperties(this);
+
+ // set alignment system be on the first time by default
+ getSwitch("ALIGNMENT_SUBSYSTEM_ACTIVE")->sp[0].s = ISS_ON;
+
+ // Default connection options
+ serialConnection->setDefaultBaudRate(Connection::Serial::B_19200);
+ tcpConnection->setDefaultHost(CAUX_DEFAULT_IP);
+ tcpConnection->setDefaultPort(CAUX_DEFAULT_PORT);
+
+ SetParkDataType(PARK_AZ_ALT_ENCODER);
+
+ // to update cordwrap pos at each init of alignment subsystem
+ IDSnoopDevice(getDeviceName(), "ALIGNMENT_SUBSYSTEM_MATH_PLUGIN_INITIALISE");
+
+ // JM 2020-09-23 Make it easier for users to connect by default via WiFi if they
+ // selected the Celestron WiFi labeled driver.
+ if (strstr(getDeviceName(), "WiFi"))
+ setActiveConnection(tcpConnection);
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void CelestronAUX::formatVersionString(char *s, int n, uint8_t *verBuf)
+{
+ if (verBuf[0] == 0 && verBuf[1] == 0 && verBuf[2] == 0 && verBuf[3] == 0)
+ snprintf(s, n, "Unknown");
+ else
+ snprintf(s, n, "%d.%02d.%d", verBuf[0], verBuf[1], verBuf[2] * 256 + verBuf[3]);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::updateProperties()
+{
+ INDI::Telescope::updateProperties();
+
+ if (isConnected())
+ {
+ // Main Control Panel
+ defineProperty(&MountTypeSP);
+ //defineProperty(&GainNP);
+ if (MountTypeSP[ALTAZ].getState() == ISS_ON)
+ defineProperty(&HorizontalCoordsNP);
+ defineProperty(&HomeSP);
+
+ // Guide
+ defineProperty(&GuideNSNP);
+ defineProperty(&GuideWENP);
+ defineProperty(&GuideRateNP);
+
+ // Cord wrap Enabled?
+ if (MountTypeSP[ALTAZ].getState() == ISS_ON)
+ {
+ getCordWrapEnabled();
+ CordWrapToggleSP[INDI_ENABLED].s = m_CordWrapActive ? ISS_ON : ISS_OFF;
+ CordWrapToggleSP[INDI_DISABLED].s = m_CordWrapActive ? ISS_OFF : ISS_ON;
+ defineProperty(&CordWrapToggleSP);
+
+ // Cord wrap Position?
+ getCordWrapPosition();
+ double cordWrapAngle = range360(m_CordWrapPosition / STEPS_PER_DEGREE);
+ LOGF_INFO("Cord Wrap position angle %.2f", cordWrapAngle);
+ CordWrapPositionSP[static_cast<int>(std::floor(cordWrapAngle / 90))].s = ISS_ON;
+ defineProperty(&CordWrapPositionSP);
+ defineProperty(&CordWrapBaseSP);
+ }
+
+ defineProperty(&GPSEmuSP);
+
+ // Encoders
+ defineProperty(&EncoderNP);
+ defineProperty(&AngleNP);
+ if (MountTypeSP[ALTAZ].getState() == ISS_ON)
+ {
+ defineProperty(&Axis1PIDNP);
+ defineProperty(&Axis2PIDNP);
+ }
+
+ getVersions();
+ // display firmware versions
+ char fwText[16] = {0};
+ formatVersionString(fwText, 10, m_HCVersion);
+ FirmwareTP[FW_HC].setText(fwText);
+ formatVersionString(fwText, 10, m_MainBoardVersion);
+ FirmwareTP[FW_MB].setText(fwText);
+ formatVersionString(fwText, 10, m_AzimuthVersion);
+ FirmwareTP[FW_AZM].setText(fwText);
+ formatVersionString(fwText, 10, m_AltitudeVersion);
+ FirmwareTP[FW_ALT].setText(fwText);
+ formatVersionString(fwText, 10, m_WiFiVersion);
+ FirmwareTP[FW_WiFi].setText(fwText);
+ formatVersionString(fwText, 10, m_BATVersion);
+ FirmwareTP[FW_BAT].setText(fwText);
+ formatVersionString(fwText, 10, m_GPSVersion);
+ FirmwareTP[FW_GPS].setText(fwText);
+ defineProperty(&FirmwareTP);
+
+ if (InitPark())
+ {
+ // If loading parking data is successful, we just set the default parking values.
+ SetAxis1ParkDefault(0);
+ SetAxis2ParkDefault(0);
+ }
+ else
+ {
+ // Otherwise, we set all parking data to default in case no parking data is found.
+ SetAxis1Park(0);
+ SetAxis2Park(0);
+ SetAxis1ParkDefault(0);
+ SetAxis2ParkDefault(0);
+ }
+ }
+ else
+ {
+ deleteProperty(MountTypeSP.getName());
+ //deleteProperty(GainNP.getName());
+ if (MountTypeSP[ALTAZ].getState() == ISS_ON)
+ deleteProperty(HorizontalCoordsNP.getName());
+ deleteProperty(HomeSP.getName());
+
+ deleteProperty(GuideNSNP.name);
+ deleteProperty(GuideWENP.name);
+ deleteProperty(GuideRateNP.getName());
+
+ if (MountTypeSP[ALTAZ].getState() == ISS_ON)
+ {
+ deleteProperty(CordWrapToggleSP.getName());
+ deleteProperty(CordWrapPositionSP.getName());
+ deleteProperty(CordWrapBaseSP.getName());
+ }
+
+ deleteProperty(GPSEmuSP.getName());
+
+ deleteProperty(EncoderNP.getName());
+ deleteProperty(AngleNP.getName());
+
+ if (MountTypeSP[ALTAZ].getState() == ISS_ON)
+ {
+ deleteProperty(Axis1PIDNP.getName());
+ deleteProperty(Axis2PIDNP.getName());
+ }
+
+ deleteProperty(FirmwareTP.getName());
+ }
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::saveConfigItems(FILE *fp)
+{
+ INDI::Telescope::saveConfigItems(fp);
+ SaveAlignmentConfigProperties(fp);
+
+ IUSaveConfigSwitch(fp, &MountTypeSP);
+ IUSaveConfigSwitch(fp, &PortTypeSP);
+ IUSaveConfigSwitch(fp, &CordWrapToggleSP);
+ IUSaveConfigSwitch(fp, &CordWrapPositionSP);
+ IUSaveConfigSwitch(fp, &CordWrapBaseSP);
+ IUSaveConfigSwitch(fp, &GPSEmuSP);
+
+ if (MountTypeSP[ALTAZ].getState() == ISS_ON)
+ {
+ IUSaveConfigNumber(fp, &Axis1PIDNP);
+ IUSaveConfigNumber(fp, &Axis2PIDNP);
+ }
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::ISSnoopDevice(XMLEle *root)
+{
+ const char *propName = findXMLAttValu(root, "name");
+
+ // update cordwrap position at each init of the alignment subsystem
+ if (!strcmp(propName, "ALIGNMENT_SUBSYSTEM_MATH_PLUGIN_INITIALISE") && telescope_caux->isConnected())
+ telescope_caux->syncCoordWrapPosition();
+
+ return Telescope::ISSnoopDevice(root);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[],
+ char *formats[], char *names[], int n)
+{
+ if (strcmp(dev, getDeviceName()) == 0)
+ {
+ // Process alignment properties
+ ProcessAlignmentBLOBProperties(this, name, sizes, blobsizes, blobs, formats, names, n);
+ }
+ // Pass it up the chain
+ return INDI::Telescope::ISNewBLOB(dev, name, sizes, blobsizes, blobs, formats, names, n);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
+{
+ if (strcmp(dev, getDeviceName()) == 0)
+ {
+ // Gain
+ // if (GainNP.isNameMatch(name))
+ // {
+ // GainNP.update(values, names, n);
+ // GainNP.setState(IPS_OK);
+ // GainNP.apply();
+ // return true;
+ // }
+
+ // Axis1 PID
+ if (Axis1PIDNP.isNameMatch(name))
+ {
+ Axis1PIDNP.update(values, names, n);
+ Axis1PIDNP.setState(IPS_OK);
+ Axis1PIDNP.apply();
+ saveConfig(true, Axis1PIDNP.getName());
+ return true;
+ }
+
+ // Axis2 PID
+ if (Axis2PIDNP.isNameMatch(name))
+ {
+ Axis2PIDNP.update(values, names, n);
+ Axis2PIDNP.setState(IPS_OK);
+ Axis2PIDNP.apply();
+ saveConfig(true, Axis2PIDNP.getName());
+ return true;
+ }
+
+ // Horizontal Coords
+ if (HorizontalCoordsNP.isNameMatch(name))
+ {
+ double az = 0, alt = 0;
+ for (int i = 0; i < 2; i++)
+ {
+ if (HorizontalCoordsNP[AXIS_AZ].isNameMatch(names[i]))
+ az = values[i];
+ else if (HorizontalCoordsNP[AXIS_ALT].isNameMatch(names[i]))
+ alt = values[i];
+ }
+
+ // Convert Celestial Alt-Az to Equatorial
+ INDI::IHorizontalCoordinates celestialAzAlt = {az, alt};
+ INDI::IEquatorialCoordinates celestialEquatorial;
+ INDI::HorizontalToEquatorial(&celestialAzAlt, &m_Location, ln_get_julian_from_sys(), &celestialEquatorial);
+
+ if (Goto(celestialEquatorial.rightascension, celestialEquatorial.declination))
+ {
+ // State already set in Goto so no need to set state here
+ char azStr[32], alStr[32];
+ fs_sexa(azStr, celestialAzAlt.azimuth, 2, 3600);
+ fs_sexa(alStr, celestialAzAlt.altitude, 2, 3600);
+ LOGF_INFO("Slewing to AZ: %s AL: %s", azStr, alStr);
+ }
+ else
+ {
+ HorizontalCoordsNP.setState(IPS_ALERT);
+ HorizontalCoordsNP.apply();
+ }
+
+ return true;
+ }
+
+ // Guide Rate
+ if (GuideRateNP.isNameMatch(name))
+ {
+ GuideRateNP.update(values, names, n);
+ GuideRateNP.setState(IPS_OK);
+ GuideRateNP.apply();
+ return true;
+ }
+
+ // Encoder Values
+ if (EncoderNP.isNameMatch(name))
+ {
+ uint32_t axisSteps1 {0}, axisSteps2 {0};
+ for (int i = 0; i < n; i++)
+ {
+ if (!strcmp(names[i], EncoderNP[AXIS_AZ].getName()))
+ axisSteps1 = values[i];
+ else if (!strcmp(names[i], EncoderNP[AXIS_ALT].getName()))
+ axisSteps2 = values[i];
+ }
+
+ slewTo(AXIS_AZ, axisSteps1);
+ slewTo(AXIS_ALT, axisSteps2);
+ TrackState = SCOPE_SLEWING;
+ EncoderNP.setState(IPS_OK);
+ EncoderNP.apply();
+ return true;
+ }
+
+ // Process Guide Properties
+ processGuiderProperties(name, values, names, n);
+
+ // Process Alignment Properties
+ ProcessAlignmentNumberProperties(this, name, values, names, n);
+ }
+
+ return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
+{
+ if (strcmp(dev, getDeviceName()) == 0)
+ {
+ // mount type
+ if (MountTypeSP.isNameMatch(name))
+ {
+ // Get current type
+ MountType currentMountType = IUFindOnSwitchIndex(&MountTypeSP) ? ALTAZ : EQUATORIAL;
+
+ MountTypeSP.update(states, names, n);
+ MountTypeSP.setState(IPS_OK);
+ MountTypeSP.apply();
+
+ // Get target type
+ MountType targetMountType = IUFindOnSwitchIndex(&MountTypeSP) ? ALTAZ : EQUATORIAL;
+
+ // If different then update
+ if (currentMountType != targetMountType)
+ {
+ LOG_INFO("Mount type updated. You must restart the driver for changes to take effect.");
+ saveConfig(true, MountTypeSP.getName());
+ }
+
+ return true;
+ }
+
+ // Port Type
+ if (PortTypeSP.isNameMatch(name))
+ {
+ PortTypeSP.update(states, names, n);
+ PortTypeSP.setState(IPS_OK);
+ PortTypeSP.apply();
+ if (m_ConfigPortType != IUFindOnSwitchIndex(&PortTypeSP))
+ {
+ m_ConfigPortType = IUFindOnSwitchIndex(&PortTypeSP);
+ saveConfig(true, PortTypeSP.getName());
+ }
+ return true;
+ }
+
+ // Cord Wrap Toggle
+ if (CordWrapToggleSP.isNameMatch(name))
+ {
+ CordWrapToggleSP.update(states, names, n);
+ const bool toEnable = CordWrapToggleSP[INDI_ENABLED].s == ISS_ON;
+ LOGF_INFO("Cord Wrap is %s.", toEnable ? "enabled" : "disabled");
+ setCordWrapEnabled(toEnable);
+ getCordWrapEnabled();
+ CordWrapToggleSP.setState(IPS_OK);
+ CordWrapToggleSP.apply();
+
+ return true;
+ }
+
+ // Cord Wrap Position
+ if (CordWrapPositionSP.isNameMatch(name))
+ {
+ CordWrapPositionSP.update(states, names, n);
+ LOGF_DEBUG("Cord Wrap Position is %s.", CordWrapPositionSP.findOnSwitch()->getLabel());
+ CordWrapPositionSP.setState(IPS_OK);
+ CordWrapPositionSP.apply();
+ switch (CordWrapPositionSP.findOnSwitchIndex())
+ {
+ case CORDWRAP_N:
+ m_RequestedCordwrapPos = 0;
+ break;
+ case CORDWRAP_E:
+ m_RequestedCordwrapPos = 90;
+ break;
+ case CORDWRAP_S:
+ m_RequestedCordwrapPos = 180;
+ break;
+ case CORDWRAP_W:
+ m_RequestedCordwrapPos = 270;
+ break;
+ default:
+ m_RequestedCordwrapPos = 0;
+ break;
+ }
+
+ syncCoordWrapPosition();
+ return true;
+ }
+
+ // Park position base
+ if (CordWrapBaseSP.isNameMatch(name))
+ {
+ CordWrapBaseSP.update(states, names, n);
+ CordWrapBaseSP.setState(IPS_OK);
+ CordWrapBaseSP.apply();
+ return true;
+ }
+
+ // GPS Emulation
+ if (GPSEmuSP.isNameMatch(name))
+ {
+ GPSEmuSP.update(states, names, n);
+ GPSEmuSP.setState(IPS_OK);
+ GPSEmuSP.apply();
+ m_GPSEmulation = GPSEmuSP[GPSEMU_ON].s == ISS_ON;
+ return true;
+ }
+
+ // Homing/Leveling
+ if (HomeSP.isNameMatch(name))
+ {
+ HomeSP.update(states, names, n);
+ bool rc = false;
+ switch (HomeSP.findOnSwitchIndex())
+ {
+ case HOME_AXIS1:
+ rc = goHome(AXIS_AZ);
+ break;
+ case HOME_AXIS2:
+ rc = goHome(AXIS_ALT);
+ break;
+ case HOME_ALL:
+ rc = goHome(AXIS_AZ) && goHome(AXIS_ALT);
+ break;
+ }
+
+ if (rc)
+ {
+ HomeSP.setState(IPS_BUSY);
+ LOG_INFO("Homing in progress...");
+ }
+ else
+ {
+ HomeSP.reset();
+ HomeSP.setState(IPS_ALERT);
+ LOG_ERROR("Failed to start homing.");
+ }
+
+ HomeSP.apply();
+ return true;
+ }
+
+ // Process alignment properties
+ ProcessAlignmentSwitchProperties(this, name, states, names, n);
+ }
+
+ return INDI::Telescope::ISNewSwitch(dev, name, states, names, n);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
+{
+ if (!strcmp(dev, getDeviceName()))
+ ProcessAlignmentTextProperties(this, name, texts, names, n);
+
+ return INDI::Telescope::ISNewText(dev, name, texts, names, n);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::Park()
+{
+ slewTo(AXIS_AZ, GetAxis1Park());
+ slewTo(AXIS_ALT, GetAxis2Park());
+ TrackState = SCOPE_PARKING;
+ LOG_INFO("Parking in progress...");
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::UnPark()
+{
+ SetParked(false);
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+INDI::IHorizontalCoordinates CelestronAUX::AltAzFromRaDec(double ra, double dec, double ts)
+{
+ // Call the alignment subsystem to translate the celestial reference frame coordinate
+ // into a telescope reference frame coordinate
+ TelescopeDirectionVector TDV;
+ INDI::IHorizontalCoordinates AltAz;
+
+ if (TransformCelestialToTelescope(ra, dec, ts, TDV))
+ // The alignment subsystem has successfully transformed my coordinate
+ AltitudeAzimuthFromTelescopeDirectionVector(TDV, AltAz);
+ else
+ {
+ LOG_DEBUG("AltAzFromRaDec - TransformCelestialToTelescope failed");
+
+ INDI::IEquatorialCoordinates EquatorialCoordinates { ra, dec };
+ INDI::EquatorialToHorizontal(&EquatorialCoordinates, &m_Location, ln_get_julian_from_sys(), &AltAz);
+ TDV = TelescopeDirectionVectorFromAltitudeAzimuth(AltAz);
+ switch (GetApproximateMountAlignment())
+ {
+ case ZENITH:
+ break;
+
+ case NORTH_CELESTIAL_POLE:
+ // Rotate the TDV coordinate system clockwise (negative) around the y axis by 90 minus
+ // the (positive)observatory latitude. The vector itself is rotated anticlockwise
+ TDV.RotateAroundY(m_Location.latitude - 90.0);
+ break;
+
+ case SOUTH_CELESTIAL_POLE:
+ // Rotate the TDV coordinate system anticlockwise (positive) around the y axis by 90 plus
+ // the (negative)observatory latitude. The vector itself is rotated clockwise
+ TDV.RotateAroundY(m_Location.latitude + 90.0);
+ break;
+ }
+ AltitudeAzimuthFromTelescopeDirectionVector(TDV, AltAz);
+ }
+
+ return AltAz;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+double CelestronAUX::getNorthAz()
+{
+ INDI::IGeographicCoordinates location;
+ double northAz;
+ if (!GetDatabaseReferencePosition(location))
+ northAz = 0.;
+ else
+ northAz = DegreesToAzimuth(AltAzFromRaDec(get_local_sidereal_time(m_Location.longitude), 0., 0.).azimuth);
+ LOGF_DEBUG("North Azimuth = %lf", northAz);
+ return northAz;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void CelestronAUX::syncCoordWrapPosition()
+{
+ // No coord wrap for equatorial mounts.
+ if (MountTypeSP[EQUATORIAL].getState() == ISS_ON)
+ return;
+
+ uint32_t coordWrapPosition = 0;
+ if (CordWrapBaseSP[CW_BASE_SKY].s == ISS_ON)
+ coordWrapPosition = range360(m_RequestedCordwrapPos + getNorthAz()) * STEPS_PER_DEGREE;
+ else
+ coordWrapPosition = range360(m_RequestedCordwrapPos) * STEPS_PER_DEGREE;
+ setCordWrapPosition(coordWrapPosition);
+ getCordWrapPosition();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command)
+{
+ int rate = IUFindOnSwitchIndex(&SlewRateSP) + 1;
+ m_AxisDirection[AXIS_ALT] = (dir == DIRECTION_NORTH) ? FORWARD : REVERSE;
+ m_AxisStatus[AXIS_ALT] = (command == MOTION_START) ? SLEWING : STOPPED;
+ ScopeStatus = SLEWING_MANUAL;
+ TrackState = SCOPE_SLEWING;
+ if (command == MOTION_START)
+ {
+ return slewByRate(AXIS_ALT, ((m_AxisDirection[AXIS_ALT] == FORWARD) ? 1 : -1) * rate);
+ }
+ else
+ return stopAxis(AXIS_ALT);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command)
+{
+ int rate = IUFindOnSwitchIndex(&SlewRateSP) + 1;
+ if (isNorthHemisphere())
+ m_AxisDirection[AXIS_AZ] = (dir == DIRECTION_WEST) ? FORWARD : REVERSE;
+ else
+ m_AxisDirection[AXIS_AZ] = (dir == DIRECTION_WEST) ? REVERSE : FORWARD;
+ m_AxisStatus[AXIS_AZ] = (command == MOTION_START) ? SLEWING : STOPPED;
+ ScopeStatus = SLEWING_MANUAL;
+ TrackState = SCOPE_SLEWING;
+ if (command == MOTION_START)
+ {
+ return slewByRate(AXIS_AZ, ((m_AxisDirection[AXIS_AZ] == FORWARD) ? 1 : -1) * rate);
+ }
+ else
+ return stopAxis(AXIS_AZ);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+IPState CelestronAUX::GuideNorth(uint32_t ms)
+{
+ int8_t rate = static_cast<int8_t>(GuideRateNP[AXIS_ALT].getValue() * 100);
+ guidePulse(AXIS_DE, ms, rate);
+ return IPS_BUSY;
+}
+
+IPState CelestronAUX::GuideSouth(uint32_t ms)
+{
+ int8_t rate = static_cast<int8_t>(GuideRateNP[AXIS_ALT].getValue() * 100);
+ guidePulse(AXIS_DE, ms, -rate);
+ return IPS_BUSY;
+}
+
+IPState CelestronAUX::GuideEast(uint32_t ms)
+{
+ int8_t rate = static_cast<int8_t>(GuideRateNP[AXIS_AZ].getValue() * 100);
+ guidePulse(AXIS_RA, ms, -rate);
+ return IPS_BUSY;
+}
+
+IPState CelestronAUX::GuideWest(uint32_t ms)
+{
+ int8_t rate = static_cast<int8_t>(GuideRateNP[AXIS_AZ].getValue() * 100);
+ guidePulse(AXIS_RA, ms, rate);
+ return IPS_BUSY;
+}
+
+bool CelestronAUX::guidePulse(INDI_EQ_AXIS axis, uint32_t ms, int8_t rate)
+{
+ // For Equatorial mounts, use regular guiding.
+ if (MountTypeSP[EQUATORIAL].getState() == ISS_ON)
+ {
+ uint8_t ticks = std::min(255u, ms / 10);
+ AUXBuffer data(2);
+ data[0] = rate;
+ data[1] = ticks;
+ AUXCommand cmd(MC_AUX_GUIDE, APP, axis == AXIS_DE ? ALT : AZM, data);
+ return sendAUXCommand(cmd);
+ }
+ // For Alt-Az mounts in tracking state, add to guide delta
+ else if (TrackState == SCOPE_TRACKING)
+ {
+ double arcsecs = TRACKRATE_SIDEREAL * ms / 1000.0 * rate / 100.;
+ double steps = arcsecs * STEPS_PER_ARCSEC;
+ m_GuideOffset[axis] += steps;
+ }
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void CelestronAUX::resetTracking()
+{
+ // getEncoder(AXIS_AZ);
+ // getEncoder(AXIS_ALT);
+ // m_TrackStartSteps[AXIS_AZ] = EncoderNP[AXIS_AZ].getValue();
+ // m_TrackStartSteps[AXIS_ALT] = EncoderNP[AXIS_ALT].getValue();
+
+ m_Controllers[AXIS_AZ].reset(new PID(1, 100000, -100000, Axis1PIDNP[Propotional].getValue(),
+ Axis1PIDNP[Derivative].getValue(), Axis1PIDNP[Integral].getValue()));
+ m_Controllers[AXIS_AZ]->setIntegratorLimits(-2000, 2000);
+ m_Controllers[AXIS_ALT].reset(new PID(1, 100000, -100000, Axis2PIDNP[Propotional].getValue(),
+ Axis2PIDNP[Derivative].getValue(), Axis2PIDNP[Integral].getValue()));
+ m_Controllers[AXIS_ALT]->setIntegratorLimits(-2000, 2000);
+ m_TrackingElapsedTimer.restart();
+ m_GuideOffset[AXIS_AZ] = m_GuideOffset[AXIS_ALT] = 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::isTrackingRequested()
+{
+ return (ISS_ON == IUFindSwitch(&CoordSP, "TRACK")->s);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::ReadScopeStatus()
+{
+ if (!isConnected())
+ return false;
+
+ if (!getStatus(AXIS_AZ))
+ return false;
+ if (!getStatus(AXIS_ALT))
+ return false;
+
+ double axis1 = EncoderNP[AXIS_AZ].getValue();
+ double axis2 = EncoderNP[AXIS_ALT].getValue();
+
+ if (!getEncoder(AXIS_AZ))
+ {
+ if (EncoderNP.getState() != IPS_ALERT)
+ {
+ EncoderNP.setState(IPS_ALERT);
+ AngleNP.setState(IPS_ALERT);
+ EncoderNP.apply();
+ AngleNP.apply();
+ }
+ return false;
+ }
+ if (!getEncoder(AXIS_ALT))
+ {
+ if (EncoderNP.getState() != IPS_ALERT)
+ {
+ EncoderNP.setState(IPS_ALERT);
+ AngleNP.setState(IPS_ALERT);
+ EncoderNP.apply();
+ AngleNP.apply();
+ }
+ return false;
+ }
+
+ // Mount Alt-Az Coords
+ if (MountTypeSP[ALTAZ].getState() == ISS_ON)
+ {
+ EncodersToAltAz(m_MountCurrentAltAz);
+ }
+ // Mount RA/DE Coords.
+ else
+ {
+ TelescopePierSide pierSide = currentPierSide;
+ EncodersToRADE(m_MountCurrentRADE, pierSide);
+ setPierSide(pierSide);
+ }
+
+ // Send to client if updated
+ if (std::abs(axis1 - EncoderNP[AXIS_AZ].getValue()) > 1 || std::abs(axis2 - EncoderNP[AXIS_ALT].getValue()) > 1)
+ {
+ EncoderNP.setState(IPS_OK);
+ EncoderNP.apply();
+ if (MountTypeSP[ALTAZ].getState() == ISS_ON)
+ {
+ AngleNP[AXIS_AZ].setValue(m_MountCurrentAltAz.azimuth);
+ AngleNP[AXIS_ALT].setValue(m_MountCurrentAltAz.altitude);
+ }
+ else
+ {
+ AngleNP[AXIS_RA].setValue(range360(range24(get_local_sidereal_time(m_Location.longitude) -
+ m_MountCurrentRADE.rightascension) * 15));
+ AngleNP[AXIS_DE].setValue(rangeDec(m_MountCurrentRADE.declination));
+ }
+ AngleNP.setState(IPS_OK);
+ AngleNP.apply();
+ }
+
+ // Get sky coordinates
+ mountToSkyCoords();
+ char RAStr[32] = {0}, DecStr[32] = {0};
+ fs_sexa(RAStr, m_SkyCurrentRADE.rightascension, 2, 3600);
+ fs_sexa(DecStr, m_SkyCurrentRADE.declination, 2, 3600);
+
+ INDI::IHorizontalCoordinates celestialAzAlt;
+ INDI::EquatorialToHorizontal(&m_SkyCurrentRADE, &m_Location, ln_get_julian_from_sys(), &celestialAzAlt);
+
+ char AzStr[32] = {0}, AltStr[32] = {0}, HAStr[32] = {0};
+ fs_sexa(AzStr, celestialAzAlt.azimuth, 2, 3600);
+ fs_sexa(AltStr, celestialAzAlt.altitude, 2, 3600);
+ double HA = rangeHA(ln_get_julian_from_sys() - m_SkyCurrentRADE.rightascension);
+ fs_sexa(HAStr, HA, 2, 3600);
+ DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Mount -> Sky RA: %s DE: %s AZ: %s AL: %s HA: %s Pier: %s",
+ RAStr,
+ DecStr,
+ AzStr,
+ AltStr,
+ HAStr,
+ MountTypeSP[ALTAZ].getState() == ISS_ON ? "NA" : getPierSideStr(currentPierSide));
+
+ if (std::abs(celestialAzAlt.azimuth - HorizontalCoordsNP[AXIS_AZ].getValue()) > 0.1 ||
+ std::abs(celestialAzAlt.altitude - HorizontalCoordsNP[AXIS_ALT].getValue()) > 0.1)
+ {
+ HorizontalCoordsNP[AXIS_AZ].setValue(celestialAzAlt.azimuth);
+ HorizontalCoordsNP[AXIS_ALT].setValue(celestialAzAlt.altitude);
+ HorizontalCoordsNP.apply();
+ }
+
+ if (TrackState == SCOPE_SLEWING)
+ {
+ if (isSlewing() == false)
+ {
+ // Stages are GOTO --> SLEWING FAST --> APPRAOCH --> SLEWING SLOW --> TRACKING
+ if (ScopeStatus == SLEWING_FAST)
+ {
+ ScopeStatus = APPROACH;
+ return Goto(m_SkyGOTOTarget.rightascension, m_SkyGOTOTarget.declination);
+ }
+
+ // If tracking was engaged, start it.
+ if (isTrackingRequested())
+ {
+ // Goto has finished start tracking
+ ScopeStatus = TRACKING;
+ if (HorizontalCoordsNP.getState() == IPS_BUSY)
+ {
+ HorizontalCoordsNP.setState(IPS_OK);
+ HorizontalCoordsNP.apply();
+ }
+ TrackState = SCOPE_TRACKING;
+ resetTracking();
+
+ // For equatorial mounts, engage tracking.
+ if (MountTypeSP[EQUATORIAL].getState() == ISS_ON)
+ SetTrackMode(IUFindOnSwitchIndex(&TrackModeSP));
+ LOG_INFO("Tracking started.");
+ }
+ else
+ {
+ TrackState = SCOPE_IDLE;
+ ScopeStatus = IDLE;
+ }
+ }
+ }
+ else if (TrackState == SCOPE_PARKING)
+ {
+ if (isSlewing() == false)
+ {
+ SetParked(true);
+ SetTrackEnabled(false);
+ }
+ }
+
+ NewRaDec(m_SkyCurrentRADE.rightascension, m_SkyCurrentRADE.declination);
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void CelestronAUX::EncodersToAltAz(INDI::IHorizontalCoordinates &coords)
+{
+ coords.azimuth = DegreesToAzimuth(EncodersToDegrees(EncoderNP[AXIS_AZ].getValue()));
+ coords.altitude = EncodersToDegrees(EncoderNP[AXIS_ALT].getValue());
+ DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Axis1 encoder %10.f -> AZ %.4f°", EncoderNP[AXIS_AZ].getValue(),
+ coords.azimuth);
+ DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Axis2 encoder %10.f -> AL %.4f°", EncoderNP[AXIS_ALT].getValue(),
+ coords.altitude);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::Goto(double ra, double dec)
+{
+ char RAStr[32] = {0}, DecStr[32] = {0};
+ fs_sexa(RAStr, ra, 2, 3600);
+ fs_sexa(DecStr, dec, 2, 3600);
+
+ // In case we have appaoch ongoing
+ if (ScopeStatus == APPROACH)
+ {
+ DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Slow Iterative GOTO RA %s DE %s", RAStr, DecStr);
+ }
+ // Fast GOTO
+ else
+ {
+ if (TrackState != SCOPE_IDLE)
+ Abort();
+
+ m_GuideOffset[AXIS_AZ] = m_GuideOffset[AXIS_ALT] = 0;
+ m_SkyGOTOTarget.rightascension = ra;
+ m_SkyGOTOTarget.declination = dec;
+
+ DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Fast GOTO RA %s DE %s", RAStr, DecStr);
+
+ if (isTrackingRequested())
+ {
+ m_SkyTrackingTarget = m_SkyGOTOTarget;
+ }
+ }
+
+ uint32_t axis1Steps {0}, axis2Steps {0};
+
+ TelescopeDirectionVector TDV;
+ INDI::IEquatorialCoordinates MountRADE { ra, dec };
+
+ // Transform Celestial to Telescope coordinates.
+ // We have no good way to estimate how long will the mount takes to reach target (with deceleration,
+ // and not just speed). So we will use iterative GOTO once the first GOTO is complete.
+ if (TransformCelestialToTelescope(ra, dec, 0.0, TDV))
+ {
+ // For Alt-Az Mounts, we get the Mount AltAz coords
+ if (MountTypeSP[MOUNT_ALTAZ].getState() == ISS_ON)
+ {
+ INDI::IHorizontalCoordinates MountAltAz { 0, 0 };
+ AltitudeAzimuthFromTelescopeDirectionVector(TDV, MountAltAz);
+ // Converts to steps and we're done.
+ axis1Steps = DegreesToEncoders(AzimuthToDegrees(MountAltAz.azimuth));
+ axis2Steps = DegreesToEncoders(MountAltAz.altitude);
+
+ // For logging purposes
+ INDI::HorizontalToEquatorial(&MountAltAz, &m_Location, ln_get_julian_from_sys(), &MountRADE);
+ }
+ // For Equatorial mounts
+ else
+ {
+ EquatorialCoordinatesFromTelescopeDirectionVector(TDV, MountRADE);
+ // Converts to steps and we're done.
+ RADEToEncoders(MountRADE, axis1Steps, axis2Steps);
+ }
+ }
+ // Conversion failed, use values as is
+ else
+ {
+ if (MountTypeSP[EQUATORIAL].getState() == ISS_ON)
+ {
+ RADEToEncoders(MountRADE, axis1Steps, axis2Steps);
+ }
+ else
+ {
+ INDI::IHorizontalCoordinates MountAltAz { 0, 0 };
+ INDI::EquatorialToHorizontal(&MountRADE, &m_Location, ln_get_julian_from_sys(), &MountAltAz);
+ TDV = TelescopeDirectionVectorFromAltitudeAzimuth(MountAltAz);
+ switch (GetApproximateMountAlignment())
+ {
+ case ZENITH:
+ break;
+
+ case NORTH_CELESTIAL_POLE:
+ // Rotate the TDV coordinate system clockwise (negative) around the y axis by 90 minus
+ // the (positive)observatory latitude. The vector itself is rotated anticlockwise
+ TDV.RotateAroundY(m_Location.latitude - 90.0);
+ break;
+
+ case SOUTH_CELESTIAL_POLE:
+ // Rotate the TDV coordinate system anticlockwise (positive) around the y axis by 90 plus
+ // the (negative)observatory latitude. The vector itself is rotated clockwise
+ TDV.RotateAroundY(m_Location.latitude + 90.0);
+ break;
+ }
+ AltitudeAzimuthFromTelescopeDirectionVector(TDV, MountAltAz);
+
+ // Converts to steps and we're done.
+ axis1Steps = DegreesToEncoders(AzimuthToDegrees(MountAltAz.azimuth));
+ axis2Steps = DegreesToEncoders(MountAltAz.altitude);
+ }
+ }
+
+ fs_sexa(RAStr, MountRADE.rightascension, 2, 3600);
+ fs_sexa(DecStr, MountRADE.declination, 2, 3600);
+ DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "Sky -> Mount RA %s (%ld) DE %s (%ld)", RAStr, axis1Steps, DecStr,
+ axis2Steps);
+
+ // Slew to physical steps.
+ slewTo(AXIS_AZ, axis1Steps, ScopeStatus != APPROACH);
+ slewTo(AXIS_ALT, axis2Steps, ScopeStatus != APPROACH);
+
+ ScopeStatus = (ScopeStatus == APPROACH) ? SLEWING_SLOW : SLEWING_FAST;
+ TrackState = SCOPE_SLEWING;
+
+ if (MountTypeSP[ALTAZ].getState() == ISS_ON && HorizontalCoordsNP.getState() != IPS_BUSY)
+ {
+ HorizontalCoordsNP.setState(IPS_BUSY);
+ HorizontalCoordsNP.apply();
+
+ }
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::Sync(double ra, double dec)
+{
+ // Compute a telescope direction vector from the current encoders
+ if (!getEncoder(AXIS_AZ))
+ return false;
+ if (!getEncoder(AXIS_ALT))
+ return false;
+
+
+ AlignmentDatabaseEntry NewEntry;
+ NewEntry.ObservationJulianDate = ln_get_julian_from_sys();
+ NewEntry.RightAscension = ra;
+ NewEntry.Declination = dec;
+
+
+ if (MountTypeSP[MOUNT_ALTAZ].getState() == ISS_ON)
+ {
+ INDI::IHorizontalCoordinates MountAltAz { 0, 0 };
+ MountAltAz.azimuth = DegreesToAzimuth(EncodersToDegrees(EncoderNP[AXIS_AZ].getValue()));
+ MountAltAz.altitude = EncodersToDegrees(EncoderNP[AXIS_ALT].getValue());
+ NewEntry.TelescopeDirection = TelescopeDirectionVectorFromAltitudeAzimuth(MountAltAz);
+ }
+ else
+ {
+ INDI::IEquatorialCoordinates MountRADE {0, 0};
+ TelescopePierSide pierSide;
+ EncodersToRADE(MountRADE, pierSide);
+ NewEntry.TelescopeDirection = TelescopeDirectionVectorFromEquatorialCoordinates(MountRADE);
+ }
+ NewEntry.PrivateDataSize = 0;
+
+ DEBUGF(INDI::AlignmentSubsystem::DBG_ALIGNMENT, "New sync point Date %lf RA %lf DEC %lf TDV(x %lf y %lf z %lf)",
+ NewEntry.ObservationJulianDate, NewEntry.RightAscension, NewEntry.Declination, NewEntry.TelescopeDirection.x,
+ NewEntry.TelescopeDirection.y, NewEntry.TelescopeDirection.z);
+
+ if (!CheckForDuplicateSyncPoint(NewEntry))
+ {
+ GetAlignmentDatabase().push_back(NewEntry);
+
+ // Tell the client about size change
+ UpdateSize();
+
+ // Tell the math plugin to reinitialise
+ Initialise(this);
+
+ // Force read before restarting
+ ReadScopeStatus();
+
+ // Sync cord wrap
+ syncCoordWrapPosition();
+
+ // The tracking seconds should be reset to restart the drift compensation
+ resetTracking();
+
+ return true;
+ }
+ return false;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+///
+//////////////////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::mountToSkyCoords()
+{
+ double RightAscension, Declination;
+
+ // TODO for Alt-Az Mounts on a Wedge, we need a watch to set this.
+ if (MountTypeSP[ALTAZ].getState() == ISS_ON)
+ {
+ INDI::IHorizontalCoordinates AltAz = m_MountCurrentAltAz;
+ TelescopeDirectionVector TDV = TelescopeDirectionVectorFromAltitudeAzimuth(AltAz);
+ if (!TransformTelescopeToCelestial(TDV, RightAscension, Declination))
+ {
+ TelescopeDirectionVector RotatedTDV(TDV);
+ switch (GetApproximateMountAlignment())
+ {
+ case ZENITH:
+ break;
+
+ case NORTH_CELESTIAL_POLE:
+ // Rotate the TDV coordinate system anticlockwise (positive) around the y axis by 90 minus
+ // the (positive)observatory latitude. The vector itself is rotated clockwise
+ RotatedTDV.RotateAroundY(90.0 - m_Location.latitude);
+ AltitudeAzimuthFromTelescopeDirectionVector(RotatedTDV, AltAz);
+ break;
+
+ case SOUTH_CELESTIAL_POLE:
+ // Rotate the TDV coordinate system clockwise (negative) around the y axis by 90 plus
+ // the (negative)observatory latitude. The vector itself is rotated anticlockwise
+ RotatedTDV.RotateAroundY(-90.0 - m_Location.latitude);
+ AltitudeAzimuthFromTelescopeDirectionVector(RotatedTDV, AltAz);
+ break;
+ }
+
+ INDI::IEquatorialCoordinates EquatorialCoordinates;
+ INDI::HorizontalToEquatorial(&AltAz, &m_Location, ln_get_julian_from_sys(), &EquatorialCoordinates);
+ RightAscension = EquatorialCoordinates.rightascension;
+ Declination = EquatorialCoordinates.declination;
+ }
+ }
+ else
+ {
+ INDI::IEquatorialCoordinates EquatorialCoordinates = m_MountCurrentRADE;
+ TelescopeDirectionVector TDV = TelescopeDirectionVectorFromEquatorialCoordinates(EquatorialCoordinates);
+ if (!TransformTelescopeToCelestial(TDV, RightAscension, Declination))
+ {
+ RightAscension = EquatorialCoordinates.rightascension;
+ Declination = EquatorialCoordinates.declination;
+ }
+ }
+
+ m_SkyCurrentRADE.rightascension = RightAscension;
+ m_SkyCurrentRADE.declination = Declination;
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void CelestronAUX::TimerHit()
+{
+ INDI::Telescope::TimerHit();
+
+ switch (TrackState)
+ {
+ case SCOPE_SLEWING:
+ break;
+
+ case SCOPE_TRACKING:
+ {
+ // Check if manual motion in progress but we stopped
+ if (m_ManualMotionActive && isSlewing() == false)
+ {
+ m_ManualMotionActive = false;
+ // If we slewed manually using NSWE keys, then we need to restart tracking
+ // whatever point we are AT now. We need to update the SkyTrackingTarget accordingly.
+ m_SkyTrackingTarget.rightascension = EqN[AXIS_RA].value;
+ m_SkyTrackingTarget.declination = EqN[AXIS_DE].value;
+ resetTracking();
+ }
+ // If we're manually moving by WESN controls, update the tracking coordinates.
+ if (m_ManualMotionActive)
+ {
+ break;
+ }
+ // We only engage ACTIVE tracking if the mount is Alt-Az.
+ // For Equatorial mount, we simply use user-selected tracking mode and let it passively track.
+ else if (MountTypeSP[MOUNT_ALTAZ].getState() == ISS_ON)
+ {
+ TelescopeDirectionVector TDV;
+ INDI::IHorizontalCoordinates targetMountAxisCoordinates { 0, 0 };
+
+ // Start by transforming tracking target celestial coordinates to telescope coordinates.
+ if (TransformCelestialToTelescope(m_SkyTrackingTarget.rightascension, m_SkyTrackingTarget.declination,
+ 0, TDV))
+ {
+ // If mount is Alt-Az then that's all we need to do
+ AltitudeAzimuthFromTelescopeDirectionVector(TDV, targetMountAxisCoordinates);
+ }
+ // If transformation failed.
+ else
+ {
+ INDI::IEquatorialCoordinates EquatorialCoordinates { 0, 0 };
+ EquatorialCoordinates.rightascension = m_SkyTrackingTarget.rightascension;
+ EquatorialCoordinates.declination = m_SkyTrackingTarget.declination;
+ INDI::EquatorialToHorizontal(&EquatorialCoordinates, &m_Location, ln_get_julian_from_sys(), &targetMountAxisCoordinates);
+ }
+
+ // Now add the guiding offsets.
+ targetMountAxisCoordinates.azimuth += m_GuideOffset[AXIS_AZ];
+ targetMountAxisCoordinates.altitude += m_GuideOffset[AXIS_ALT];
+
+ // If we had guiding pulses active, mark them as complete
+ if (GuideWENP.s == IPS_BUSY)
+ GuideComplete(AXIS_RA);
+ if (GuideNSNP.s == IPS_BUSY)
+ GuideComplete(AXIS_DE);
+
+ // Next get current alt-az
+ INDI::IHorizontalCoordinates currentAltAz { 0, 0 };
+ currentAltAz.azimuth = DegreesToAzimuth(EncodersToDegrees(EncoderNP[AXIS_AZ].getValue()));
+ currentAltAz.altitude = EncodersToDegrees(EncoderNP[AXIS_ALT].getValue());
+
+ // Offset in degrees
+ double offsetAngle[2] = {0, 0};
+ offsetAngle[AXIS_AZ] = (targetMountAxisCoordinates.azimuth - currentAltAz.azimuth);
+ offsetAngle[AXIS_ALT] = (targetMountAxisCoordinates.altitude - currentAltAz.altitude);
+
+ int32_t offsetSteps[2] = {0, 0};
+ int32_t targetSteps[2] = {0, 0};
+ double trackRates[2] = {0, 0};
+
+ offsetSteps[AXIS_AZ] = offsetAngle[AXIS_AZ] * STEPS_PER_DEGREE;
+ offsetSteps[AXIS_ALT] = offsetAngle[AXIS_ALT] * STEPS_PER_DEGREE;
+
+ // Only apply trackinf IF we're still on the same side of the curve
+ // If we switch over, let's settle for a bit
+ if (m_LastOffset[AXIS_AZ] * offsetSteps[AXIS_AZ] >= 0 || m_OffsetSwitchSettle[AXIS_AZ]++ > 3)
+ {
+ m_OffsetSwitchSettle[AXIS_AZ] = 0;
+ m_LastOffset[AXIS_AZ] = offsetSteps[AXIS_AZ];
+ targetSteps[AXIS_AZ] = DegreesToEncoders(AzimuthToDegrees(targetMountAxisCoordinates.azimuth));
+ trackRates[AXIS_AZ] = m_Controllers[AXIS_AZ]->calculate(targetSteps[AXIS_AZ], EncoderNP[AXIS_AZ].getValue());
+
+ LOGF_DEBUG("Tracking AZ Now: %.f Target: %d Offset: %d Rate: %.2f", EncoderNP[AXIS_AZ].getValue(), targetSteps[AXIS_AZ],
+ offsetSteps[AXIS_AZ], trackRates[AXIS_AZ]);
+#ifdef DEBUG_PID
+ LOGF_DEBUG("Tracking AZ P: %f I: %f D: %f",
+ m_Controllers[AXIS_AZ]->propotionalTerm(),
+ m_Controllers[AXIS_AZ]->integralTerm(),
+ m_Controllers[AXIS_AZ]->derivativeTerm());
+#endif
+
+ // Use PID to determine appropiate tracking rate
+ trackByRate(AXIS_AZ, trackRates[AXIS_AZ]);
+ }
+
+ // Only apply trackinf IF we're still on the same side of the curve
+ // If we switch over, let's settle for a bit
+ if (m_LastOffset[AXIS_ALT] * offsetSteps[AXIS_ALT] >= 0 || m_OffsetSwitchSettle[AXIS_ALT]++ > 3)
+ {
+ m_OffsetSwitchSettle[AXIS_ALT] = 0;
+ m_LastOffset[AXIS_ALT] = offsetSteps[AXIS_ALT];
+ targetSteps[AXIS_ALT] = DegreesToEncoders(targetMountAxisCoordinates.altitude);
+ trackRates[AXIS_ALT] = m_Controllers[AXIS_ALT]->calculate(targetSteps[AXIS_ALT], EncoderNP[AXIS_ALT].getValue());
+
+ LOGF_DEBUG("Tracking AL Now: %.f Target: %d Offset: %d Rate: %.2f", EncoderNP[AXIS_ALT].getValue(), targetSteps[AXIS_ALT],
+ offsetSteps[AXIS_ALT], trackRates[AXIS_ALT]);
+#ifdef DEBUG_PID
+ LOGF_DEBUG("Tracking AL P: %f I: %f D: %f",
+ m_Controllers[AXIS_ALT]->propotionalTerm(),
+ m_Controllers[AXIS_ALT]->integralTerm(),
+ m_Controllers[AXIS_ALT]->derivativeTerm());
+#endif
+ trackByRate(AXIS_ALT, trackRates[AXIS_ALT]);
+ }
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ // Check if seeking index or leveling is done
+ if (HomeSP.getState() == IPS_BUSY)
+ {
+ isHomingDone(AXIS_AZ);
+ isHomingDone(AXIS_ALT);
+
+ if (!m_HomingProgress[AXIS_AZ] && !m_HomingProgress[AXIS_ALT])
+ {
+ LOG_INFO("Homing complete.");
+ HomeSP.reset();
+ HomeSP.setState(IPS_OK);
+ HomeSP.apply();
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::updateLocation(double latitude, double longitude, double elevation)
+{
+ // Update INDI Alignment Subsystem Location
+ UpdateLocation(latitude, longitude, elevation);
+
+ // Do we really need this in update Location??
+ // take care of latitude for north or south emisphere
+ //SetApproximateMountAlignmentFromMountType(static_cast<MountType>(IUFindOnSwitchIndex(&MountTypeSP)));
+ // tell the alignment math plugin to reinitialise
+ //Initialise(this);
+
+ // update cordwrap position at each init of the alignment subsystem
+ syncCoordWrapPosition();
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+/// 0 to 360 degrees
+/////////////////////////////////////////////////////////////////////////////////////
+double CelestronAUX::EncodersToDegrees(uint32_t steps)
+{
+ return range360(steps * DEGREES_PER_STEP);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+uint32_t CelestronAUX::DegreesToEncoders(double degree)
+{
+ return round(range360(degree) * STEPS_PER_DEGREE);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+double CelestronAUX::DegreesToAzimuth(double degree)
+{
+ if (isNorthHemisphere())
+ return degree;
+ else
+ return range360(degree + 180);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+double CelestronAUX::AzimuthToDegrees(double degree)
+{
+ if (isNorthHemisphere())
+ return degree;
+ else
+ return range360(degree + 180);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+/// 0 to 24 hours
+/////////////////////////////////////////////////////////////////////////////////////
+double CelestronAUX::EncodersToHours(uint32_t steps)
+{
+ double value = steps * HOURS_PER_STEP;
+ // North hemisphere
+ if (isNorthHemisphere())
+ return range24(value);
+ else
+ return range24(24 - value);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+uint32_t CelestronAUX::HoursToEncoders(double hour)
+{
+ return DegreesToEncoders(hour * 15);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void CelestronAUX::EncodersToRADE(INDI::IEquatorialCoordinates &coords, TelescopePierSide &pierSide)
+{
+ auto haEncoder = (EncoderNP[AXIS_RA].getValue() / STEPS_PER_REVOLUTION) * 360.0;
+ auto deEncoder = 360.0 - (EncoderNP[AXIS_DE].getValue() / STEPS_PER_REVOLUTION) * 360.0;
+
+ double de = 0, ha = 0;
+ // Northern Hemisphere
+ if (LocationN[LOCATION_LATITUDE].value >= 0)
+ {
+ if (m_IsWedge)
+ {
+ pierSide = PIER_UNKNOWN;
+ if (deEncoder >= 270)
+ de = 360 - deEncoder;
+ else if (deEncoder >= 90)
+ de = deEncoder - 180;
+ else
+ de = -deEncoder;
+
+ if (haEncoder >= 180)
+ ha = -((360 - haEncoder) / 360.0) * 24.0 ;
+ else
+ ha = (haEncoder / 360.0) * 24.0 ;
+ }
+ // "Normal" Pointing State (East, looking West)
+ else if (deEncoder >= 0)
+ {
+ de = std::min(90 - deEncoder, 90.0);
+ ha = -6.0 + (haEncoder / 360.0) * 24.0 ;
+ pierSide = PIER_EAST;
+ }
+ // "Reversed" Pointing State (West, looking East)
+ else
+ {
+ de = 90 + deEncoder;
+ ha = 6.0 + (haEncoder / 360.0) * 24.0 ;
+ pierSide = PIER_WEST;
+ }
+ }
+ else
+ {
+ if (m_IsWedge)
+ {
+ pierSide = PIER_UNKNOWN;
+ if (deEncoder >= 270)
+ de = deEncoder - 360;
+ else if (deEncoder >= 90)
+ de = 180 - deEncoder;
+ else
+ de = deEncoder;
+
+ if (haEncoder >= 180)
+ ha = -((360 - haEncoder) / 360.0) * 24.0 ;
+ else
+ ha = (haEncoder / 360.0) * 24.0 ;
+ }
+ // East
+ else if (deEncoder <= 0)
+ {
+ de = std::max(-90 - deEncoder, -90.0);
+ ha = -6.0 - (haEncoder / 360.0) * 24.0 ;
+ pierSide = PIER_WEST;
+ }
+ // West
+ else
+ {
+ de = -90 + deEncoder;
+ ha = 6.0 - (haEncoder / 360.0) * 24.0 ;
+ pierSide = PIER_EAST;
+ }
+ }
+
+ double lst = get_local_sidereal_time(LocationN[LOCATION_LONGITUDE].value);
+ double ra = range24(lst - ha);
+
+ coords.rightascension = ra;
+ coords.declination = de;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+/// HA encoders at 0 (HA = LST). When going WEST, steps increase from 0 to STEPS_PER_REVOLUTION {16777216}
+/// counter clock-wise.
+/// HA 0 to 6 hours range: 0 to 4194304
+/// HA 0 to -6 hours range: 16777216 to 12582912
+/////////////////////////////////////////////////////////////////////////////////////
+void CelestronAUX::RADEToEncoders(const INDI::IEquatorialCoordinates &coords, uint32_t &haEncoder, uint32_t &deEncoder)
+{
+ double lst = get_local_sidereal_time(LocationN[LOCATION_LONGITUDE].value);
+ double dHA = rangeHA(lst - coords.rightascension);
+ double de = 0, ha = 0;
+ // Northern Hemisphere
+ if (LocationN[LOCATION_LATITUDE].value >= 0)
+ {
+ if (m_IsWedge)
+ {
+ if (coords.declination < 0)
+ de = -coords.declination;
+ else
+ de = 360 - coords.declination;
+
+ if (dHA < 0)
+ ha = 360 - ((dHA / -24.0) * 360.0);
+ else
+ ha = (dHA / 24.0) * 360.0;
+
+ }
+ // "Normal" Pointing State (East, looking West)
+ else if (dHA <= 0)
+ {
+ de = -(coords.declination - 90.0);
+ ha = (dHA + 6.0) * 360.0 / 24.0;
+ }
+ // "Reversed" Pointing State (West, looking East)
+ else
+ {
+ de = coords.declination - 90.0;
+ ha = (dHA - 6.0) * 360.0 / 24.0;
+ }
+ }
+ else
+ {
+ if (m_IsWedge)
+ {
+ if (coords.declination >= 0)
+ de = coords.declination;
+ else
+ de = 360 + coords.declination;
+
+ if (dHA < 0)
+ ha = 360 - ((dHA / -24.0) * 360.0);
+ else
+ ha = (dHA / 24.0) * 360.0;
+
+ }
+ // "Normal" Pointing State (East, looking West)
+ else if (dHA <= 0)
+ {
+ de = -(coords.declination + 90.0);
+ ha = -(dHA + 6.0) * 360.0 / 24.0;
+ }
+ // "Reversed" Pointing State (West, looking East)
+ else
+ {
+ de = (coords.declination + 90.0);
+ ha = -(dHA - 6.0) * 360 / 24.0;
+ }
+ }
+
+ haEncoder = (range360(ha) / 360.0) * STEPS_PER_REVOLUTION;
+ deEncoder = (360.0 - range360(de)) / 360.0 * STEPS_PER_REVOLUTION;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+/// -90 to +90
+/////////////////////////////////////////////////////////////////////////////////////
+double CelestronAUX::EncodersToDE(uint32_t steps, TelescopePierSide pierSide)
+{
+ double degrees = EncodersToDegrees(steps);
+ double de = 0;
+ if (m_IsWedge)
+ de = degrees;
+ else if ((isNorthHemisphere() && pierSide == PIER_WEST) || (!isNorthHemisphere() && pierSide == PIER_EAST))
+ de = degrees - 270;
+ else
+ de = 90 - degrees;
+
+ return rangeDec(de);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+double CelestronAUX::DEToEncoders(double de)
+{
+ double degrees = 0;
+ if (m_IsWedge)
+ degrees = de;
+ else if ((isNorthHemisphere() && m_TargetPierSide == PIER_WEST) || (!isNorthHemisphere() && m_TargetPierSide == PIER_EAST))
+ degrees = 270 + de;
+ else
+ degrees = 90 - de;
+ return DegreesToEncoders(degrees);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::isSlewing()
+{
+ return m_AxisStatus[AXIS_AZ] == SLEWING || m_AxisStatus[AXIS_ALT] == SLEWING;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::slewTo(INDI_HO_AXIS axis, uint32_t steps, bool fast)
+{
+ // Stop first.
+ trackByRate(axis, 0);
+ AUXCommand command(fast ? MC_GOTO_FAST : MC_GOTO_SLOW, APP, axis == AXIS_AZ ? AZM : ALT);
+ m_AxisStatus[axis] = SLEWING;
+ command.setData(steps, 3);
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ return true;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::slewByRate(INDI_HO_AXIS axis, int8_t rate)
+{
+ // Stop first.
+ trackByRate(axis, 0);
+ AUXCommand command(rate >= 0 ? MC_MOVE_POS : MC_MOVE_NEG, APP, axis == AXIS_AZ ? AZM : ALT);
+ command.setData(std::abs(rate), 1);
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ return true;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::goHome(INDI_HO_AXIS axis)
+{
+ AUXCommand command(axis == AXIS_AZ ? MC_SEEK_INDEX : MC_LEVEL_START, APP, axis == AXIS_AZ ? AZM : ALT);
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::isHomingDone(INDI_HO_AXIS axis)
+{
+ AUXCommand command(axis == AXIS_AZ ? MC_SEEK_DONE : MC_LEVEL_DONE, APP, axis == AXIS_AZ ? AZM : ALT);
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::getVersion(AUXTargets target)
+{
+ AUXCommand firmver(GET_VER, APP, target);
+ if (! sendAUXCommand(firmver))
+ return false;
+ if (! readAUXResponse(firmver))
+ return false;
+ return true;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void CelestronAUX::getVersions()
+{
+ if (!m_isHandController)
+ {
+ // Do not ask HC/MB for the version over AUX channel
+ // We got HC version from detectHC
+ getVersion(MB);
+ getVersion(HC);
+ getVersion(HCP);
+ }
+ getVersion(AZM);
+ getVersion(ALT);
+ getVersion(GPS);
+ getVersion(WiFi);
+ getVersion(BAT);
+
+ // These are the same as battery controller
+ // Probably the same chip inside the mount
+ //getVersion(CHG);
+ //getVersion(LIGHT);
+ //getVersion(ANY);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::setCordWrapEnabled(bool enable)
+{
+
+ AUXCommand command(enable ? MC_ENABLE_CORDWRAP : MC_DISABLE_CORDWRAP, APP, AZM);
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ return true;
+};
+
+bool CelestronAUX::getCordWrapEnabled()
+{
+ AUXCommand command(MC_POLL_CORDWRAP, APP, AZM);
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ return m_CordWrapActive;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::setCordWrapPosition(uint32_t steps)
+{
+ AUXCommand command(MC_SET_CORDWRAP_POS, APP, AZM);
+ command.setData(steps, 3);
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ return true;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+uint32_t CelestronAUX::getCordWrapPosition()
+{
+ AUXCommand command(MC_GET_CORDWRAP_POS, APP, AZM);
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ return m_CordWrapPosition;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::stopAxis(INDI_HO_AXIS axis)
+{
+ m_AxisStatus[axis] = STOPPED;
+ trackByRate(axis, 0);
+ AUXCommand command(MC_MOVE_POS, APP, (axis == AXIS_ALT) ? ALT : AZM);
+ command.setData(0, 1);
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ return true;
+
+}
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::Abort()
+{
+ // getEncoder(AXIS_AZ);
+ // getEncoder(AXIS_ALT);
+ // double ms = m_TrackingElapsedTimer.elapsed();
+ // LOGF_INFO("*** Elapsed %.f ms", ms);
+ // LOGF_INFO("*** Axis1 start: %.f finish: %.f steps/s: %.4f", m_TrackStartSteps[AXIS_AZ], EncoderNP[AXIS_AZ].getValue(), std::abs(EncoderNP[AXIS_AZ].getValue() - m_TrackStartSteps[AXIS_AZ]) / (ms/1000.));
+ // LOGF_INFO("*** Axis2 start: %.f finish: %.f steps/s: %.4f", m_TrackStartSteps[AXIS_ALT], EncoderNP[AXIS_ALT].getValue(), std::abs(EncoderNP[AXIS_ALT].getValue() - m_TrackStartSteps[AXIS_ALT]) / (ms/1000.));
+ stopAxis(AXIS_AZ);
+ stopAxis(AXIS_ALT);
+ m_GuideOffset[AXIS_AZ] = m_GuideOffset[AXIS_ALT] = 0;
+ TrackState = SCOPE_IDLE;
+
+ if (HorizontalCoordsNP.getState() != IPS_IDLE)
+ {
+ HorizontalCoordsNP.setState(IPS_IDLE);
+ HorizontalCoordsNP.apply();
+ }
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+/// Rate is Celestron specific and roughly equals 80 ticks per 1 motor step
+/// rate = 80 would cause the motor to spin at a rate of 1 step/s
+/// Have to check if 80 is specific to my Evolution 6" or not.
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::trackByRate(INDI_HO_AXIS axis, int32_t rate)
+{
+ if (std::abs(rate) > 0 && rate == m_LastTrackRate[axis])
+ return true;
+
+ m_LastTrackRate[axis] = rate;
+ AUXCommand command(rate < 0 ? MC_SET_NEG_GUIDERATE : MC_SET_POS_GUIDERATE, APP, axis == AXIS_AZ ? AZM : ALT);
+ // 24bit rate
+ command.setData(std::abs(rate), 3);
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::trackByMode(INDI_HO_AXIS axis, uint8_t mode)
+{
+ AUXCommand command(isNorthHemisphere() ? MC_SET_POS_GUIDERATE : MC_SET_NEG_GUIDERATE, APP, axis == AXIS_AZ ? AZM : ALT);
+ switch (mode)
+ {
+ case TRACK_SOLAR:
+ command.setData(AUX_SOLAR, 2);
+ break;
+ case TRACK_LUNAR:
+ command.setData(AUX_LUNAR, 2);
+ break;
+ case TRACK_SIDEREAL:
+ default:
+ command.setData(AUX_SIDEREAL, 2);
+ break;
+ }
+
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::SetTrackEnabled(bool enabled)
+{
+ if (enabled)
+ {
+ TrackState = SCOPE_TRACKING;
+ resetTracking();
+ m_SkyTrackingTarget.rightascension = EqN[AXIS_RA].value;
+ m_SkyTrackingTarget.declination = EqN[AXIS_DE].value;
+
+ if (IUFindOnSwitchIndex(&TrackModeSP) == TRACK_CUSTOM)
+ return SetTrackRate(TrackRateN[AXIS_AZ].value, TrackRateN[AXIS_ALT].value);
+ else
+ return SetTrackMode(IUFindOnSwitchIndex(&TrackModeSP));
+ }
+ else
+ {
+ TrackState = SCOPE_IDLE;
+ trackByRate(AXIS_AZ, 0);
+ trackByRate(AXIS_ALT, 0);
+
+ if (HorizontalCoordsNP.getState() != IPS_IDLE)
+ {
+ HorizontalCoordsNP.setState(IPS_IDLE);
+ HorizontalCoordsNP.apply();
+ }
+ }
+
+ return true;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::SetTrackRate(double raRate, double deRate)
+{
+ m_TrackRates[AXIS_AZ] = raRate;
+ m_TrackRates[AXIS_ALT] = deRate;
+
+ if (TrackState == SCOPE_TRACKING)
+ {
+
+ double steps[2] = {0, 0};
+ // rate = (steps) * gain
+ steps[AXIS_AZ] = raRate * STEPS_PER_ARCSEC * GAIN_STEPS;
+ steps[AXIS_ALT] = deRate * STEPS_PER_ARCSEC * GAIN_STEPS;
+ trackByRate(AXIS_AZ, steps[AXIS_AZ]);
+ trackByRate(AXIS_ALT, steps[AXIS_ALT]);
+ }
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::SetTrackMode(uint8_t mode)
+{
+ if (mode == TRACK_SIDEREAL)
+ m_TrackRates[AXIS_AZ] = TRACKRATE_SIDEREAL;
+ else if (mode == TRACK_SOLAR)
+ m_TrackRates[AXIS_AZ] = TRACKRATE_SOLAR;
+ else if (mode == TRACK_LUNAR)
+ m_TrackRates[AXIS_AZ] = TRACKRATE_LUNAR;
+ else if (mode == TRACK_CUSTOM)
+ {
+ m_TrackRates[AXIS_AZ] = TrackRateN[AXIS_RA].value;
+ m_TrackRates[AXIS_ALT] = TrackRateN[AXIS_DE].value;
+ }
+
+ if (TrackState == SCOPE_TRACKING)
+ {
+ if (mode == TRACK_CUSTOM)
+ {
+ double steps[2] = {0, 0};
+ // rate = (steps) * gain
+ steps[AXIS_AZ] = m_TrackRates[AXIS_AZ] * STEPS_PER_ARCSEC * GAIN_STEPS;
+ steps[AXIS_ALT] = m_TrackRates[AXIS_ALT] * STEPS_PER_ARCSEC * GAIN_STEPS;
+ trackByRate(AXIS_AZ, steps[AXIS_AZ]);
+ trackByRate(AXIS_ALT, steps[AXIS_ALT]);
+ }
+ else
+ trackByMode(AXIS_AZ, mode);
+ }
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::getStatus(INDI_HO_AXIS axis)
+{
+ if (m_AxisStatus[axis] == SLEWING && ScopeStatus != SLEWING_MANUAL)
+ {
+ AUXCommand command(MC_SLEW_DONE, APP, axis == AXIS_AZ ? AZM : ALT);
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ }
+
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::getEncoder(INDI_HO_AXIS axis)
+{
+ AUXCommand command(MC_GET_POSITION, APP, axis == AXIS_AZ ? AZM : ALT);
+ sendAUXCommand(command);
+ readAUXResponse(command);
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+/// This is simple GPS emulation for HC.
+/// If HC asks for the GPS we reply with data from our GPS/Site info.
+/// We send reply blind (not reading any response) to avoid processing loop.
+/// That is why the readAUXResponse calls are commented out.
+/// This is OK since we are not going to do anything with the response anyway.
+/////////////////////////////////////////////////////////////////////////////////////
+void CelestronAUX::emulateGPS(AUXCommand &m)
+{
+ if (m.destination() != GPS)
+ return;
+
+ LOGF_DEBUG("GPS: Got 0x%02x", m.command());
+ if (m_GPSEmulation == false)
+ return;
+
+ switch (m.command())
+ {
+ case GET_VER:
+ {
+ LOGF_DEBUG("GPS: GET_VER from 0x%02x", m.source());
+ AUXBuffer dat(2);
+ dat[0] = 0x01;
+ dat[1] = 0x02;
+ AUXCommand cmd(GET_VER, GPS, m.source(), dat);
+ sendAUXCommand(cmd);
+ //readAUXResponse(cmd);
+ break;
+ }
+ case GPS_GET_LAT:
+ case GPS_GET_LONG:
+ {
+ LOGF_DEBUG("GPS: Sending LAT/LONG Lat:%f Lon:%f", LocationN[LOCATION_LATITUDE].value,
+ LocationN[LOCATION_LONGITUDE].value);
+ AUXCommand cmd(m.command(), GPS, m.source());
+ if (m.command() == GPS_GET_LAT)
+ cmd.setData(STEPS_PER_DEGREE * LocationN[LOCATION_LATITUDE].value);
+ else
+ cmd.setData(STEPS_PER_DEGREE * LocationN[LOCATION_LONGITUDE].value);
+ sendAUXCommand(cmd);
+ //readAUXResponse(cmd);
+ break;
+ }
+ case GPS_GET_TIME:
+ {
+ LOGF_DEBUG("GPS: GET_TIME from 0x%02x", m.source());
+ time_t gmt;
+ struct tm *ptm;
+ AUXBuffer dat(3);
+
+ time(&gmt);
+ ptm = gmtime(&gmt);
+ dat[0] = unsigned(ptm->tm_hour);
+ dat[1] = unsigned(ptm->tm_min);
+ dat[2] = unsigned(ptm->tm_sec);
+ AUXCommand cmd(GPS_GET_TIME, GPS, m.source(), dat);
+ sendAUXCommand(cmd);
+ //readAUXResponse(cmd);
+ break;
+ }
+ case GPS_GET_DATE:
+ {
+ LOGF_DEBUG("GPS: GET_DATE from 0x%02x", m.source());
+ time_t gmt;
+ struct tm *ptm;
+ AUXBuffer dat(2);
+
+ time(&gmt);
+ ptm = gmtime(&gmt);
+ dat[0] = unsigned(ptm->tm_mon + 1);
+ dat[1] = unsigned(ptm->tm_mday);
+ AUXCommand cmd(GPS_GET_DATE, GPS, m.source(), dat);
+ sendAUXCommand(cmd);
+ //readAUXResponse(cmd);
+ break;
+ }
+ case GPS_GET_YEAR:
+ {
+ LOGF_DEBUG("GPS: GET_YEAR from 0x%02x", m.source());
+ time_t gmt;
+ struct tm *ptm;
+ AUXBuffer dat(2);
+
+ time(&gmt);
+ ptm = gmtime(&gmt);
+ dat[0] = unsigned(ptm->tm_year + 1900) >> 8;
+ dat[1] = unsigned(ptm->tm_year + 1900) & 0xFF;
+ LOGF_DEBUG("GPS: Sending: %d [%d,%d]", ptm->tm_year, dat[0], dat[1]);
+ AUXCommand cmd(GPS_GET_YEAR, GPS, m.source(), dat);
+ sendAUXCommand(cmd);
+ //readAUXResponse(cmd);
+ break;
+ }
+ case GPS_LINKED:
+ {
+ LOGF_DEBUG("GPS: LINKED from 0x%02x", m.source());
+ AUXBuffer dat(1);
+
+ dat[0] = unsigned(1);
+ AUXCommand cmd(GPS_LINKED, GPS, m.source(), dat);
+ sendAUXCommand(cmd);
+ //readAUXResponse(cmd);
+ break;
+ }
+ default:
+ LOGF_DEBUG("GPS: Got 0x%02x", m.command());
+ break;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::processResponse(AUXCommand &m)
+{
+ m.logResponse();
+
+ if ((m.destination() == GPS) && (m.source() != APP))
+ {
+ // Avoid infinite loop by not responding to ourselves
+ emulateGPS(m);
+ }
+ else if (m.destination() == APP)
+ switch (m.command())
+ {
+ case MC_GET_POSITION:
+ switch (m.source())
+ {
+ case ALT:
+ EncoderNP[AXIS_ALT].setValue(m.getData());
+ break;
+ case AZM:
+ EncoderNP[AXIS_AZ].setValue(m.getData());
+ break;
+ default:
+ break;
+ }
+ break;
+ case MC_SLEW_DONE:
+ switch (m.source())
+ {
+ case ALT:
+ m_AxisStatus[AXIS_ALT] = (m.getData() == 0xff) ? STOPPED : SLEWING;
+ break;
+ case AZM:
+ m_AxisStatus[AXIS_AZ] = (m.getData() == 0xff) ? STOPPED : SLEWING;
+ break;
+ default:
+ break;
+ }
+ break;
+ case MC_POLL_CORDWRAP:
+ if (m.source() == AZM)
+ m_CordWrapActive = m.getData() == 0xff;
+ break;
+ case MC_GET_CORDWRAP_POS:
+ if (m.source() == AZM)
+ m_CordWrapPosition = m.getData();
+ break;
+
+ case MC_GET_AUTOGUIDE_RATE:
+ switch (m.source())
+ {
+ case ALT:
+ GuideRateNP[AXIS_ALT].setValue(m.getData() / 255.);
+ break;
+ case AZM:
+ GuideRateNP[AXIS_AZ].setValue(m.getData() * 255.);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case MC_SET_AUTOGUIDE_RATE:
+ // Nothing to do.
+ break;
+
+ case MC_AUX_GUIDE:
+ // Nothing to do
+ break;
+
+ case MC_LEVEL_DONE:
+ m_HomingProgress[AXIS_ALT] = m.getData() == 0x00;
+ break;
+
+ case MC_SEEK_DONE:
+ m_HomingProgress[AXIS_AZ] = m.getData() == 0x00;
+ break;
+
+ case MC_AUX_GUIDE_ACTIVE:
+ switch (m.source())
+ {
+ case ALT:
+ if (m.getData() == 0x00)
+ GuideComplete(AXIS_DE);
+ break;
+ case AZM:
+ if (m.getData() == 0x00)
+ GuideComplete(AXIS_RA);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case GET_VER:
+ {
+ uint8_t *verBuf = nullptr;
+ switch (m.source())
+ {
+ case MB:
+ verBuf = m_MainBoardVersion;
+ break;
+ case ALT:
+ verBuf = m_AltitudeVersion;
+ break;
+ case AZM:
+ verBuf = m_AzimuthVersion;
+ break;
+ case HCP:
+ case HC:
+ verBuf = m_HCVersion;
+ break;
+ case BAT:
+ verBuf = m_BATVersion;
+ break;
+ case WiFi:
+ verBuf = m_WiFiVersion;
+ break;
+ case GPS:
+ verBuf = m_GPSVersion;
+ break;
+ case APP:
+ LOGF_DEBUG("Got echo of GET_VERSION from %s", m.moduleName(m.destination()));
+ break;
+ default:
+ break;
+ }
+ if (verBuf != nullptr)
+ {
+ size_t verBuflen = 4;
+ if ( verBuflen != m.dataSize())
+ {
+ LOGF_DEBUG("Data and buffer size mismatch for GET_VER: buf[%d] vs data[%d]", verBuflen, m.dataSize());
+ }
+ for (int i = 0; i < (int)std::min(verBuflen, m.dataSize()); i++)
+ verBuf[i] = m.data()[i];
+ }
+ }
+ break;
+ default:
+ break;
+
+ }
+ else
+ {
+ DEBUGF(DBG_CAUX, "Got msg not for me (%s). Ignoring.", m.moduleName(m.destination()));
+ }
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::serialReadResponse(AUXCommand c)
+{
+ int n;
+ unsigned char buf[32];
+ char hexbuf[24];
+ AUXCommand cmd;
+
+ // We are not connected. Nothing to do.
+ if ( PortFD <= 0 )
+ return false;
+
+ if (m_IsRTSCTS || !m_isHandController)
+ {
+ // if connected to AUX or PC ports, receive AUX command response.
+ // search for packet preamble (0x3b)
+ do
+ {
+ if (aux_tty_read((char*)buf, 1, READ_TIMEOUT, &n) != TTY_OK)
+ return false;
+ }
+ while (buf[0] != 0x3b);
+
+ // packet preamble is found, now read packet length.
+ if (aux_tty_read((char*)(buf + 1), 1, READ_TIMEOUT, &n) != TTY_OK)
+ return false;
+
+ // now packet length is known, read the rest of the packet.
+ if (aux_tty_read((char*)(buf + 2), buf[1] + 1, READ_TIMEOUT, &n)
+ != TTY_OK || n != buf[1] + 1)
+ {
+ LOG_DEBUG("Did not got whole packet. Dropping out.");
+ return false;
+ }
+
+ AUXBuffer b(buf, buf + (n + 2));
+ hex_dump(hexbuf, b, b.size());
+ DEBUGF(DBG_SERIAL, "RES <%s>", hexbuf);
+ cmd.parseBuf(b);
+ }
+ else
+ {
+ // if connected to HC serial, build up the AUX command response from
+ // given AUX command and passthrough response without checksum.
+ // read passthrough response
+ if ((tty_read(PortFD, (char *)buf + 5, response_data_size + 1, READ_TIMEOUT, &n) !=
+ TTY_OK) || (n != response_data_size + 1))
+ return false;
+
+ // if last char is not '#', there was an error.
+ if (buf[response_data_size + 5] != '#')
+ {
+ LOGF_ERROR("Resp. char %d is %2.2x ascii %c", n, buf[n + 5], (char)buf[n + 5]);
+ AUXBuffer b(buf, buf + (response_data_size + 5));
+ hex_dump(hexbuf, b, b.size());
+ LOGF_ERROR("RES <%s>", hexbuf);
+ return false;
+ }
+
+ buf[0] = 0x3b;
+ buf[1] = response_data_size + 1;
+ buf[2] = c.destination();
+ buf[3] = c.source();
+ buf[4] = c.command();
+
+ AUXBuffer b(buf, buf + (response_data_size + 5));
+ hex_dump(hexbuf, b, b.size());
+ DEBUGF(DBG_SERIAL, "RES (%d B): <%s>", (int)b.size(), hexbuf);
+ cmd.parseBuf(b, false);
+ }
+
+ // Got the packet, process it
+ // n:length field >=3
+ // The buffer of n+2>=5 bytes contains:
+ // 0x3b <n>=3> <from> <to> <type> <n-3 bytes> <xsum>
+
+ DEBUGF(DBG_SERIAL, "Got %d bytes: ; payload length field: %d ; MSG:", n, buf[1]);
+ logBytes(buf, n + 2, getDeviceName(), DBG_SERIAL);
+ processResponse(cmd);
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::tcpReadResponse()
+{
+ int n, i;
+ unsigned char buf[BUFFER_SIZE] = {0};
+ AUXCommand cmd;
+
+ // We are not connected. Nothing to do.
+ if ( PortFD <= 0 )
+ return false;
+
+ timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 50000;
+ setsockopt(PortFD, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval));
+
+ // Drain the channel
+ while ((n = recv(PortFD, buf, sizeof(buf), MSG_DONTWAIT | MSG_PEEK)) > 0)
+ {
+ // DEBUGF(DBG_CAUX, "Got %d bytes: ", n);
+ // for (i = 0; i < n; i++)
+ // IDLog("%02x ", buf[i]);
+
+ for (i = 0; i < n;)
+ {
+ if (buf[i] == 0x3b)
+ {
+ int shft;
+ shft = i + buf[i + 1] + 3;
+ if (shft <= n)
+ {
+ AUXBuffer b(buf + i, buf + shft);
+ cmd.parseBuf(b);
+
+ char hexbuf[32 * 3] = {0};
+ hex_dump(hexbuf, b, b.size());
+ DEBUGF(DBG_SERIAL, "RES <%s>", hexbuf);
+
+ processResponse(cmd);
+ }
+ else
+ {
+ DEBUGF(DBG_SERIAL, "Partial message recv. dropping (i=%d %d/%d)", i, shft, n);
+ logBytes(buf + i, n - i, getDeviceName(), DBG_SERIAL);
+ recv(PortFD, buf, n, MSG_DONTWAIT);
+ break;
+ }
+ i = shft;
+ }
+ else
+ {
+ i++;
+ }
+ }
+ // Actually consume data we parsed. Leave the rest for later.
+ if (i > 0)
+ {
+ n = recv(PortFD, buf, i, MSG_DONTWAIT);
+ //DEBUGF(DBG_CAUX, "Consumed %d/%d bytes", n, i);
+ }
+ }
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::readAUXResponse(AUXCommand c)
+{
+ if (getActiveConnection() == serialConnection)
+ return serialReadResponse(c);
+ else
+ return tcpReadResponse();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+int CelestronAUX::sendBuffer(AUXBuffer buf)
+{
+ if ( PortFD > 0 )
+ {
+ int n;
+
+ if (aux_tty_write((char*)buf.data(), buf.size(), CTS_TIMEOUT, &n) != TTY_OK)
+ return 0;
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ if (n == -1)
+ LOG_ERROR("CAUX::sendBuffer");
+ if ((unsigned)n != buf.size())
+ LOGF_WARN("sendBuffer: incomplete send n=%d size=%d", n, (int)buf.size());
+
+ char hexbuf[32 * 3] = {0};
+ hex_dump(hexbuf, buf, buf.size());
+ DEBUGF(DBG_SERIAL, "CMD <%s>", hexbuf);
+
+ return n;
+ }
+ else
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::sendAUXCommand(AUXCommand &command)
+{
+ AUXBuffer buf;
+ command.logCommand();
+
+ if (m_IsRTSCTS || !m_isHandController || getActiveConnection() != serialConnection)
+ // Direct connection (AUX/PC/USB port)
+ command.fillBuf(buf);
+ else
+ {
+ // connection is through HC serial and destination is not HC,
+ // convert AUX command to a passthrough command
+
+ // fixed len = 8
+ buf.resize(8);
+ // prefix
+ buf[0] = 0x50;
+ // length
+ buf[1] = 1 + command.dataSize();
+ // destination
+ buf[2] = command.destination();
+ buf[3] = command.command(); // command id
+ for (size_t i = 0; i < command.dataSize(); i++) // payload
+ {
+ buf[i + 4] = command.data()[i];
+ }
+ buf[7] = response_data_size = command.responseDataSize();
+ }
+
+ tcflush(PortFD, TCIOFLUSH);
+ return (sendBuffer(buf) == static_cast<int>(buf.size()));
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Wrap functions around the standard driver communication functions tty_read
+// and tty_write.
+// When the communication is serial, these wrap functions implement the
+// Celestron hardware handshake used by telescope serial ports AUX and PC.
+// When the communication is by network, these wrap functions are trasparent.
+// Read and write calls are passed, as is, to the standard functions tty_read
+// and tty_write.
+// 16-Feb-2020 Fabrizio Pollastri <mxgbot@gmail.com>
+////////////////////////////////////////////////////////////////////////////////
+void CelestronAUX::setRTS(bool rts)
+{
+ if (ioctl(PortFD, TIOCMGET, &m_ModemControl) == -1)
+ LOGF_ERROR("Error getting handshake lines %s(%d).", strerror(errno), errno);
+ if (rts)
+ m_ModemControl |= TIOCM_RTS;
+ else
+ m_ModemControl &= ~TIOCM_RTS;
+ if (ioctl(PortFD, TIOCMSET, &m_ModemControl) == -1)
+ LOGF_ERROR("Error setting handshake lines %s(%d).", strerror(errno), errno);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::waitCTS(float timeout)
+{
+ float step = timeout / 20.;
+ for (; timeout >= 0; timeout -= step)
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(step)));
+ if (ioctl(PortFD, TIOCMGET, &m_ModemControl) == -1)
+ {
+ LOGF_ERROR("Error getting handshake lines %s(%d).", strerror(errno), errno);
+ return 0;
+ }
+ if (m_ModemControl & TIOCM_CTS)
+ return 1;
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::detectRTSCTS()
+{
+ setRTS(1);
+ bool retval = waitCTS(300.);
+ setRTS(0);
+ return retval;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::detectHC(char *version, size_t size)
+{
+ AUXBuffer b;
+ char buf[3];
+
+ b.resize(1);
+ b[0] = 'V';
+
+ // We are not connected. Nothing to do.
+ if ( PortFD <= 0 )
+ return false;
+
+ // send get firmware version command
+ if (sendBuffer(b) != (int)b.size())
+ return false;
+
+ // read response
+ int n;
+ if (aux_tty_read((char*)buf, 3, READ_TIMEOUT, &n) != TTY_OK)
+ return false;
+
+ // non error response must end with '#'
+ if (buf[2] != '#')
+ return false;
+
+ // return printable HC version
+ // fill in the version field
+ m_HCVersion[0] = static_cast<uint8_t>(buf[0]);
+ m_HCVersion[1] = static_cast<uint8_t>(buf[1]);
+ m_HCVersion[2] = 0;
+ m_HCVersion[3] = 0;
+ snprintf(version, size, "%d.%02d", static_cast<uint8_t>(buf[0]), static_cast<uint8_t>(buf[1]));
+
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+int CelestronAUX::aux_tty_read(char *buf, int bufsiz, int timeout, int *n)
+{
+ int errcode;
+ DEBUGF(DBG_SERIAL, "aux_tty_read: %d", PortFD);
+
+ // if hardware flow control is required, set RTS to off to receive: PC port
+ // bahaves as half duplex.
+ if (m_IsRTSCTS)
+ setRTS(0);
+
+ if((errcode = tty_read(PortFD, buf, bufsiz, timeout, n)) != TTY_OK)
+ {
+ char errmsg[MAXRBUF] = {0};
+ tty_error_msg(errcode, errmsg, MAXRBUF);
+ LOGF_ERROR("%s", errmsg);
+ }
+
+ return errcode;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+int CelestronAUX::aux_tty_write(char *buf, int bufsiz, float timeout, int *n)
+{
+ int errcode, ne;
+ char errmsg[MAXRBUF];
+
+ //DEBUGF(DBG_CAUX, "aux_tty_write: %d", PortFD);
+
+ // if hardware flow control is required, set RTS to on then wait for CTS
+ // on to write: PC port bahaves as half duplex. RTS may be already on.
+ if (m_IsRTSCTS)
+ {
+ DEBUG(DBG_SERIAL, "aux_tty_write: set RTS");
+ setRTS(1);
+ DEBUG(DBG_SERIAL, "aux_tty_write: wait CTS");
+ if (!waitCTS(timeout))
+ {
+ LOGF_ERROR("Error getting handshake lines %s(%d).\n", strerror(errno), errno);
+ return TTY_TIME_OUT;
+ }
+ }
+
+ errcode = tty_write(PortFD, buf, bufsiz, n);
+
+ if (errcode != TTY_OK)
+ {
+ tty_error_msg(errcode, errmsg, MAXRBUF);
+ LOGF_ERROR("%s", errmsg);
+ return errcode;
+ }
+
+ // if hardware flow control is required, Wait for tx complete, set RTS to
+ // off, to receive (half duplex).
+ if (m_IsRTSCTS)
+ {
+ DEBUG(DBG_SERIAL, "aux_tty_write: clear RTS");
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ setRTS(0);
+
+ // ports requiring hardware flow control echo all sent characters,
+ // verify them.
+ DEBUG(DBG_SERIAL, "aux_tty_write: verify echo");
+ if ((errcode = tty_read(PortFD, errmsg, *n, READ_TIMEOUT, &ne)) != TTY_OK)
+ {
+ tty_error_msg(errcode, errmsg, MAXRBUF);
+ LOGF_ERROR("%s", errmsg);
+ return errcode;
+ }
+
+ if (*n != ne)
+ return TTY_WRITE_ERROR;
+
+ for (int i = 0; i < ne; i++)
+ if (buf[i] != errmsg[i])
+ return TTY_WRITE_ERROR;
+ }
+
+ return TTY_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+bool CelestronAUX::tty_set_speed(speed_t speed)
+{
+ struct termios tty_setting;
+
+ if (tcgetattr(PortFD, &tty_setting))
+ {
+ LOGF_ERROR("Error getting tty attributes %s(%d).", strerror(errno), errno);
+ return false;
+ }
+
+ if (cfsetspeed(&tty_setting, speed))
+ {
+ LOGF_ERROR("Error setting serial speed %s(%d).", strerror(errno), errno);
+ return false;
+ }
+
+ if (tcsetattr(PortFD, TCSANOW, &tty_setting))
+ {
+ LOGF_ERROR("Error setting tty attributes %s(%d).", strerror(errno), errno);
+ return false;
+ }
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+///
+/////////////////////////////////////////////////////////////////////////////////////
+void CelestronAUX::hex_dump(char *buf, AUXBuffer data, size_t size)
+{
+ for (size_t i = 0; i < size; i++)
+ sprintf(buf + 3 * i, "%02X ", data[i]);
+
+ if (size > 0)
+ buf[3 * size - 1] = '\0';
+}
+
diff --git a/indi-celestronaux/celestronaux.h b/indi-celestronaux/celestronaux.h
new file mode 100644
index 0000000..6a79280
--- /dev/null
+++ b/indi-celestronaux/celestronaux.h
@@ -0,0 +1,442 @@
+/*
+ Celestron Aux Mount Driver.
+
+ Copyright (C) 2020 Paweł T. Jochym
+ Copyright (C) 2020 Fabrizio Pollastri
+ Copyright (C) 2020-2022 Jasem Mutlaq
+
+ 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
+
+ JM 2022.07.07: Added Wedge support.
+*/
+
+#pragma once
+
+#include <indicom.h>
+#include <indiguiderinterface.h>
+#include <inditelescope.h>
+#include <indielapsedtimer.h>
+#include <connectionplugins/connectionserial.h>
+#include <connectionplugins/connectiontcp.h>
+#include <alignment/AlignmentSubsystemForDrivers.h>
+#include <indipropertyswitch.h>
+#include <indipropertynumber.h>
+#include <indipropertytext.h>
+#include <pid.h>
+#include <termios.h>
+
+#include "auxproto.h"
+
+class CelestronAUX :
+ public INDI::Telescope,
+ public INDI::GuiderInterface,
+ public INDI::AlignmentSubsystem::AlignmentSubsystemForDrivers
+{
+ public:
+ CelestronAUX();
+ ~CelestronAUX() override;
+
+ virtual bool ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[],
+ char *formats[], char *names[], int n) override;
+ virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override;
+ virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override;
+ virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override;
+ virtual bool ISSnoopDevice(XMLEle *root) override;
+
+ // Type defs
+ enum ScopeStatus_t
+ {
+ IDLE,
+ SLEWING_FAST,
+ APPROACH,
+ SLEWING_SLOW,
+ SLEWING_MANUAL,
+ TRACKING
+ };
+ ScopeStatus_t ScopeStatus;
+
+ enum AxisStatus
+ {
+ STOPPED,
+ SLEWING
+ };
+
+ enum AxisDirection
+ {
+ FORWARD,
+ REVERSE
+ };
+
+ // Previous motion direction
+ // TODO: Switch to AltAz from N-S/W-E
+ typedef enum
+ {
+ PREVIOUS_NS_MOTION_NORTH = DIRECTION_NORTH,
+ PREVIOUS_NS_MOTION_SOUTH = DIRECTION_SOUTH,
+ PREVIOUS_NS_MOTION_UNKNOWN = -1
+ } PreviousNSMotion_t;
+ typedef enum
+ {
+ PREVIOUS_WE_MOTION_WEST = DIRECTION_WEST,
+ PREVIOUS_WE_MOTION_EAST = DIRECTION_EAST,
+ PREVIOUS_WE_MOTION_UNKNOWN = -1
+ } PreviousWEMotion_t;
+
+ // Public to enable setting from ISSnoop
+ void syncCoordWrapPosition();
+
+ protected:
+ virtual void ISGetProperties(const char *dev) override;
+ virtual bool initProperties() override;
+ virtual bool updateProperties() override;
+ virtual bool saveConfigItems(FILE *fp) override;
+ virtual bool Handshake() override;
+ virtual bool Disconnect() override;
+
+ virtual const char *getDefaultName() override;
+ INDI::IHorizontalCoordinates AltAzFromRaDec(double ra, double dec, double ts);
+
+ virtual bool Sync(double ra, double dec) override;
+ virtual bool Goto(double ra, double dec) override;
+ virtual bool Abort() override;
+ virtual bool Park() override;
+ virtual bool UnPark() override;
+
+ virtual IPState GuideNorth(uint32_t ms) override;
+ virtual IPState GuideSouth(uint32_t ms) override;
+ virtual IPState GuideEast(uint32_t ms) override;
+ virtual IPState GuideWest(uint32_t ms) override;
+
+ //virtual bool HandleGetAutoguideRate(INDI_HO_AXIS axis, uint8_t rate);
+ //virtual bool HandleSetAutoguideRate(INDI_EQ_AXIS axis);
+ //virtual bool HandleGuidePulse(INDI_EQ_AXIS axis);
+ //virtual bool HandleGuidePulseDone(INDI_EQ_AXIS axis, bool done);
+
+ // TODO: Switch to AltAz from N-S/W-E
+ virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override;
+ virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override;
+
+ virtual bool ReadScopeStatus() override;
+ virtual void TimerHit() override;
+ virtual bool updateLocation(double latitude, double longitude, double elevation) override;
+
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Motion Control
+ /////////////////////////////////////////////////////////////////////////////////////
+ bool stopAxis(INDI_HO_AXIS axis);
+ bool isSlewing();
+
+ /**
+ * @brief SlewTo Go to a 24bit encoder position.
+ * @param axis AZ or ALT
+ * @param steps Encoder microsteps
+ * @param fast If true, use fast command to reach target. If false, use slow command.
+ * @return True if successful, false otherwise.
+ */
+ bool slewTo(INDI_HO_AXIS axis, uint32_t steps, bool fast = true);
+
+ /**
+ * @brief SlewByRate Slew an axis using variable rate speed.
+ * @param axis AZ or ALT
+ * @param rate -9 to +9. 0 means stop.
+ * For AZ, negative means left while positive means right.
+ * For Alt, negative is down while positive is up.
+ * @return True if successful, false otherwise.
+ */
+ bool slewByRate(INDI_HO_AXIS axis, int8_t rate);
+
+ // Go to index position or level
+ bool goHome(INDI_HO_AXIS axis);
+ bool isHomingDone(INDI_HO_AXIS axis);
+ bool m_HomingProgress[2] = {false, false};
+
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Tracking
+ /////////////////////////////////////////////////////////////////////////////////////
+ bool SetTrackEnabled(bool enabled) override;
+ bool SetTrackMode(uint8_t mode) override;
+ bool SetTrackRate(double raRate, double deRate) override;
+ void resetTracking();
+
+ /**
+ * @brief TrackByRate Set axis tracking rate in arcsecs/sec.
+ * @param axis AZ or ALT
+ * @param rate arcsecs/s. Zero would stop tracking.
+ * For AZ, negative means left while positive means right.
+ * For Alt, negative is down while positive is up.
+ * @return True if successful, false otherwise.
+ */
+ bool trackByRate(INDI_HO_AXIS axis, int32_t rate);
+
+ /**
+ * @brief trackByRate Track using specific mode (sidereal, solar, or lunar)
+ * @param axis AZ or ALT
+ * @param mode sidereal, solar, or lunar
+ * @return True if successful, false otherwise.
+ */
+ bool trackByMode(INDI_HO_AXIS axis, uint8_t mode);
+ bool isTrackingRequested();
+
+ bool getStatus(INDI_HO_AXIS axis);
+ bool getEncoder(INDI_HO_AXIS axis);
+
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Coord Wrap
+ /////////////////////////////////////////////////////////////////////////////////////
+ bool setCordWrapEnabled(bool enable);
+ bool getCordWrapEnabled();
+ bool setCordWrapPosition(uint32_t steps);
+ uint32_t getCordWrapPosition();
+
+ private:
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Misc
+ /////////////////////////////////////////////////////////////////////////////////////
+ double getNorthAz();
+ bool isNorthHemisphere() const
+ {
+ return m_Location.latitude >= 0;
+ }
+ bool getVersion(AUXTargets target);
+ void getVersions();
+ void hex_dump(char *buf, AUXBuffer data, size_t size);
+
+
+ double AzimuthToDegrees(double degree);
+ double DegreesToAzimuth(double degree);
+
+ double EncodersToDegrees(uint32_t steps);
+ uint32_t DegreesToEncoders(double degrees);
+
+ double EncodersToHours(uint32_t steps);
+ uint32_t HoursToEncoders(double hour);
+
+ double EncodersToDE(uint32_t steps, TelescopePierSide pierSide);
+ double DEToEncoders(double de);
+
+ void EncodersToAltAz(INDI::IHorizontalCoordinates &coords);
+ void EncodersToRADE(INDI::IEquatorialCoordinates &coords, TelescopePierSide &pierSide);
+ void RADEToEncoders(const INDI::IEquatorialCoordinates &coords, uint32_t &haEncoder, uint32_t &deEncoder);
+
+ /**
+ * @brief mountToSkyCoords Convert mount coordinates to equatorial sky coordinates
+ * @return True if successful, false otherwise.
+ * @note This works for both Alt-Az and Equatorial Mounts.
+ */
+ bool mountToSkyCoords();
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Guiding
+ /////////////////////////////////////////////////////////////////////////////////////
+ bool guidePulse(INDI_EQ_AXIS axis, uint32_t ms, int8_t rate);
+
+
+ private:
+ // Axis Information
+ AxisStatus m_AxisStatus[2] {STOPPED, STOPPED};
+ AxisDirection m_AxisDirection[2] {FORWARD, FORWARD};
+
+ // Guiding offset in steps
+ // For each pulse, we modify the offset so that we can add it to our current tracking traget
+ int32_t m_GuideOffset[2] = {0, 0};
+ double m_TrackRates[2] = {TRACKRATE_SIDEREAL, 0};
+
+ // approach distance
+ double Approach {1};
+ TelescopePierSide m_TargetPierSide {PIER_UNKNOWN};
+
+ // Tracking targets
+ INDI::IEquatorialCoordinates m_SkyTrackingTarget { 0, 0 };
+ INDI::IEquatorialCoordinates m_SkyGOTOTarget { 0, 0 };
+
+ // Actual Sky Equatorial Coordinates
+ INDI::IEquatorialCoordinates m_SkyCurrentRADE {0, 0};
+
+ // Current Mount Alt/Az or RA/DE
+ INDI::IEquatorialCoordinates m_MountCurrentRADE {0, 0};
+ INDI::IHorizontalCoordinates m_MountCurrentAltAz {0, 0};
+
+ INDI::ElapsedTimer m_TrackingElapsedTimer;
+
+
+ /////////////////////////////////////////////////////////////////////////////////////
+ /// Auxiliary Command Communication
+ /////////////////////////////////////////////////////////////////////////////////////
+ bool sendAUXCommand(AUXCommand &command);
+ void closeConnection();
+ void emulateGPS(AUXCommand &m);
+ bool serialReadResponse(AUXCommand c);
+ bool tcpReadResponse();
+ bool readAUXResponse(AUXCommand c);
+ bool processResponse(AUXCommand &cmd);
+ int sendBuffer(AUXBuffer buf);
+ void formatVersionString(char *s, int n, uint8_t *verBuf);
+
+ // GPS Emulation
+ bool m_GPSEmulation {false};
+
+ // Firmware
+ uint8_t m_MainBoardVersion[4] {0};
+ uint8_t m_AltitudeVersion[4] {0};
+ uint8_t m_AzimuthVersion[4] {0};
+ uint8_t m_HCVersion[4] {0};
+ uint8_t m_BATVersion[4] {0};
+ uint8_t m_WiFiVersion[4] {0};
+ uint8_t m_GPSVersion[4] {0};
+
+ // Coord Wrap
+ bool m_CordWrapActive {false};
+ int32_t m_CordWrapPosition {0};
+ uint32_t m_RequestedCordwrapPos;
+
+ // Manual Slewing NSWE
+ bool m_ManualMotionActive { false };
+
+ // Debug
+ uint32_t DBG_CAUX {0};
+ uint32_t DBG_SERIAL {0};
+
+ ///////////////////////////////////////////////////////////////////////////////
+ /// Communication
+ ///////////////////////////////////////////////////////////////////////////////
+ int m_ModemControl {0};
+ void setRTS(bool rts);
+ bool waitCTS(float timeout);
+ bool detectRTSCTS();
+ bool detectHC(char *version, size_t size);
+ int response_data_size;
+ int aux_tty_read(char *buf, int bufsiz, int timeout, int *n);
+ int aux_tty_write (char *buf, int bufsiz, float timeout, int *n);
+ bool tty_set_speed(speed_t speed);
+
+ // connection
+ bool m_IsRTSCTS {false};
+ bool m_isHandController {false};
+
+ ///////////////////////////////////////////////////////////////////////////////
+ /// Celestron AUX Properties
+ ///////////////////////////////////////////////////////////////////////////////
+
+ // Firmware
+ INDI::PropertyText FirmwareTP {7};
+ enum {FW_HC, FW_MB, FW_AZM, FW_ALT, FW_WiFi, FW_BAT, FW_GPS};
+ // Mount type
+ INDI::PropertySwitch MountTypeSP {2};
+ enum
+ {
+ MOUNT_EQUATORIAL,
+ MOUNT_ALTAZ
+ };
+
+ // Mount Cord wrap Toogle
+ INDI::PropertySwitch CordWrapToggleSP {2};
+
+ // Mount Coord wrap Position
+ INDI::PropertySwitch CordWrapPositionSP {4};
+ enum { CORDWRAP_N, CORDWRAP_E, CORDWRAP_S, CORDWRAP_W };
+
+ // Cordwrap base (0-encoder/True directions)
+ // Use 0-encoders / Sky directions as base for parking and cordwrap
+ INDI::PropertySwitch CordWrapBaseSP {2};
+ enum {CW_BASE_ENC, CW_BASE_SKY};
+
+ // GPS emulator
+ INDI::PropertySwitch GPSEmuSP {2};
+ enum { GPSEMU_OFF, GPSEMU_ON };
+
+ // Horizontal Coords
+ INDI::PropertyNumber HorizontalCoordsNP {2};
+
+ // Guide Rate
+ INDI::PropertyNumber GuideRateNP {2};
+
+ // Encoders
+ INDI::PropertyNumber EncoderNP {2};
+ // Angles
+ INDI::PropertyNumber AngleNP {2};
+
+ int32_t m_LastTrackRate[2] = {-1, -1};
+ double m_TrackStartSteps[2] = {0, 0};
+ double m_LastOffset[2] = {0, 0};
+ uint8_t m_OffsetSwitchSettle[2] = {0, 0};
+ bool m_IsWedge {false};
+
+ // PID controllers
+ INDI::PropertyNumber Axis1PIDNP {3};
+ INDI::PropertyNumber Axis2PIDNP {3};
+ enum
+ {
+ Propotional,
+ Derivative,
+ Integral
+ };
+
+ std::unique_ptr<PID> m_Controllers[2];
+
+ INDI::PropertySwitch PortTypeSP {2};
+ enum
+ {
+ PORT_AUX_PC,
+ PORT_HC_USB,
+ };
+
+ int m_ConfigPortType {PORT_AUX_PC};
+
+ // Home/Level
+ INDI::PropertySwitch HomeSP {3};
+ enum
+ {
+ HOME_AXIS1,
+ HOME_AXIS2,
+ HOME_ALL
+ };
+ //INDI::PropertyNumber GainNP {2};
+ ///////////////////////////////////////////////////////////////////////////////
+ /// Static Const Private Variables
+ ///////////////////////////////////////////////////////////////////////////////
+
+ private:
+
+ // One definition rule (ODR) constants
+ // AUX commands use 24bit integer as a representation of angle in units of
+ // fractional revolutions. Thus 2^24 steps makes full revolution.
+ static constexpr int32_t STEPS_PER_REVOLUTION {16777216};
+ static constexpr double STEPS_PER_DEGREE {STEPS_PER_REVOLUTION / 360.0};
+ static constexpr double STEPS_PER_ARCSEC {STEPS_PER_DEGREE / 3600.0};
+ static constexpr double DEGREES_PER_STEP {360.0 / STEPS_PER_REVOLUTION};
+
+ static constexpr double STEPS_PER_HOUR {STEPS_PER_REVOLUTION / 24.0};
+ static constexpr double HOURS_PER_STEP {24.0 / STEPS_PER_REVOLUTION};
+
+ // Measured rate that would result in 1 step/sec
+ static constexpr uint32_t GAIN_STEPS {80};
+
+ // MC_SET_POS_GUIDERATE & MC_SET_NEG_GUIDERATE use 24bit number rate in
+ static constexpr uint8_t RATE_PER_ARCSEC {4};
+
+ static constexpr uint32_t BUFFER_SIZE {10240};
+ // seconds
+ static constexpr uint8_t READ_TIMEOUT {1};
+ // ms
+ static constexpr uint8_t CTS_TIMEOUT {100};
+ // Coord Wrap
+ static constexpr const char *CORDWRAP_TAB {"Coord Wrap"};
+ static constexpr const char *MOUNTINFO_TAB {"Mount Info"};
+ // Track modes
+ static constexpr uint16_t AUX_SIDEREAL {0xffff};
+ static constexpr uint16_t AUX_SOLAR {0xfffe};
+ static constexpr uint16_t AUX_LUNAR {0xfffd};
+
+
+};
diff --git a/indi-celestronaux/config.h.cmake b/indi-celestronaux/config.h.cmake
new file mode 100644
index 0000000..be71a44
--- /dev/null
+++ b/indi-celestronaux/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-celestronaux/indi-celestronaux.spec b/indi-celestronaux/indi-celestronaux.spec
new file mode 100644
index 0000000..20ba305
--- /dev/null
+++ b/indi-celestronaux/indi-celestronaux.spec
@@ -0,0 +1,79 @@
+%define __cmake_in_source_build %{_vpath_builddir}
+Name: indi-celestronaux
+Version:1.9.8.git
+Release: %(date -u +%%Y%%m%%d%%H%%M%%S)%{?dist}
+Summary: Instrument Neutral Distributed Interface 3rd party drivers
+
+License: LGPLv2
+# See COPYRIGHT file for a description of the licenses and files covered
+
+URL: https://indilib.org
+Source0: https://github.com/indilib/indi-3rdparty/archive/master.tar.gz
+
+BuildRequires: cmake
+BuildRequires: libfli-devel
+BuildRequires: libnova-devel
+BuildRequires: qt5-qtbase-devel
+BuildRequires: systemd
+BuildRequires: gphoto2-devel
+BuildRequires: LibRaw-devel
+BuildRequires: indi-libs
+BuildRequires: indi-devel
+BuildRequires: libtiff-devel
+BuildRequires: cfitsio-devel
+BuildRequires: zlib-devel
+BuildRequires: gsl-devel
+BuildRequires: libcurl-devel
+BuildRequires: libjpeg-turbo-devel
+BuildRequires: fftw-devel
+BuildRequires: libftdi-devel
+BuildRequires: gpsd-devel
+BuildRequires: libdc1394-devel
+BuildRequires: boost-devel
+BuildRequires: boost-regex
+
+BuildRequires: gmock
+
+BuildRequires: pkgconfig(fftw3)
+BuildRequires: pkgconfig(cfitsio)
+BuildRequires: pkgconfig(libcurl)
+BuildRequires: pkgconfig(gsl)
+BuildRequires: pkgconfig(libjpeg)
+BuildRequires: pkgconfig(libusb-1.0)
+BuildRequires: pkgconfig(zlib)
+
+
+%description
+INDI is a distributed control protocol designed to operate
+astronomical instrumentation. INDI is small, flexible, easy to parse,
+and scalable. It supports common DCS functions such as remote control,
+data acquisition, monitoring, and a lot more. This is a 3rd party driver.
+
+
+%prep -v
+%autosetup -v -p1 -n indi-3rdparty-master
+
+%build
+# This package tries to mix and match PIE and PIC which is wrong and will
+# trigger link errors when LTO is enabled.
+# Disable LTO
+%define _lto_cflags %{nil}
+
+cd indi-celestronaux
+%cmake -DINDI_DATA_DIR=/usr/share/indi .
+make VERBOSE=1 %{?_smp_mflags} -j4
+
+%install
+cd indi-celestronaux
+make DESTDIR=%{buildroot} install
+
+%files
+%license LICENSE
+%{_bindir}/*
+%{_datadir}/indi/indi_celestronaux.xml
+
+
+%changelog
+* Sun Jul 19 2020 Jim Howard <jh.xsnrg+fedora@gmail.com> 1.8.7.git-1
+- update to build from git for copr, credit to Sergio Pascual and Christian Dersch for prior work on spec files
+
diff --git a/indi-celestronaux/indi_celestronaux.xml.cmake b/indi-celestronaux/indi_celestronaux.xml.cmake
new file mode 100644
index 0000000..749b0e2
--- /dev/null
+++ b/indi-celestronaux/indi_celestronaux.xml.cmake
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<driversList>
+<devGroup group="Telescopes">
+ <device label="Celestron AUX" manufacturer="Celestron">
+ <driver name="Celestron AUX">indi_celestron_aux</driver>
+ <version>@CAUX_VERSION_MAJOR@.@CAUX_VERSION_MINOR@</version>
+ </device>
+ <device label="Celestron WiFi" manufacturer="Celestron">
+ <driver name="Celestron AUX">indi_celestron_aux</driver>
+ <version>@CAUX_VERSION_MAJOR@.@CAUX_VERSION_MINOR@</version>
+ </device>
+ <device label="Evolution WiFi" manufacturer="Celestron">
+ <driver name="Celestron AUX">indi_celestron_aux</driver>
+ <version>@CAUX_VERSION_MAJOR@.@CAUX_VERSION_MINOR@</version>
+ </device>
+ <device label="Evolution WiFi Wedge" manufacturer="Celestron">
+ <driver name="Celestron AUX">indi_celestron_aux</driver>
+ <version>@CAUX_VERSION_MAJOR@.@CAUX_VERSION_MINOR@</version>
+ </device>
+ <device label="CGEM II WiFi" manufacturer="Celestron">
+ <driver name="Celestron AUX">indi_celestron_aux</driver>
+ <version>@CAUX_VERSION_MAJOR@.@CAUX_VERSION_MINOR@</version>
+ </device>
+ <device label="CGX WiFi" manufacturer="Celestron">
+ <driver name="Celestron AUX">indi_celestron_aux</driver>
+ <version>@CAUX_VERSION_MAJOR@.@CAUX_VERSION_MINOR@</version>
+ </device>
+ <device label="Advanced VX WiFi" manufacturer="Celestron">
+ <driver name="Celestron AUX">indi_celestron_aux</driver>
+ <version>@CAUX_VERSION_MAJOR@.@CAUX_VERSION_MINOR@</version>
+ </device>
+</devGroup>
+</driversList>
diff --git a/indi-celestronaux/simulator/nse_simulator.py b/indi-celestronaux/simulator/nse_simulator.py
new file mode 100644
index 0000000..5a3ceb6
--- /dev/null
+++ b/indi-celestronaux/simulator/nse_simulator.py
@@ -0,0 +1,259 @@
+#!/bin/env python3
+
+import asyncio
+import signal
+import socket
+import sys
+from socket import SOL_SOCKET, SO_BROADCAST, SO_REUSEADDR
+from nse_telescope import NexStarScope, repr_angle
+
+import curses
+
+telescope=None
+
+async def broadcast(sport=2000, dport=55555, host='255.255.255.255', seconds_to_sleep=5):
+ sck = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sck.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
+ sck.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
+ # Fake msg. The app does not care for the payload
+ msg = 110*b'X'
+ sck.bind(('',sport))
+ telescope.print_msg('Broadcasting to port {0}'.format(dport))
+ telescope.print_msg('sleeping for: {0} seconds'.format(seconds_to_sleep))
+ while True :
+ bn = sck.sendto(msg,(host,dport))
+ await asyncio.sleep(seconds_to_sleep)
+ telescope.print_msg('Stopping broadcast')
+
+async def timer(seconds_to_sleep=1,telescope=None):
+ from time import time
+ t=time()
+ while True :
+ await asyncio.sleep(seconds_to_sleep)
+ if telescope :
+ telescope.tick(time()-t)
+ t=time()
+
+async def handle_port2000(reader, writer):
+ '''
+ This function handles initial communication with the WiFly module and
+ delegates the real job of simulating the scope to the NexStarScope class.
+ It also handles all the dirty details of actual communication.
+ '''
+
+ # The WiFly module is initially in the transparent mode and just passes
+ # the data to the serial connection. Unless the '$$$' sequence is detected.
+ # Then it switches to the command mode until the exit command is issued.
+ # The $$$ should be guarded by the 1s silence.
+ transparent=True
+ retry = 5
+ global telescope
+ # Endless comm loop.
+ connected=False
+ while True :
+ data = await reader.read(1024)
+ if not data :
+ writer.close()
+ telescope.print_msg('Connection closed. Closing server.')
+ return
+ elif not connected :
+ telescope.print_msg('App from {0} connected.'.format(writer.get_extra_info('peername')))
+ connected=True
+ retry = 5
+ addr = writer.get_extra_info('peername')
+ #print("-> Scope received %r from %r." % (data, addr))
+ resp = b''
+ if transparent :
+ if data[:3]==b'$$$' :
+ # Enter command mode
+ transparent = False
+ telescope.print_msg('App from {0} connected.'.format(addr))
+ resp = b'CMD\r\n'
+ else :
+ # pass it on to the scope for handling
+ resp = telescope.handle_msg(data)
+ else :
+ # We are in command mode detect exit and get out.
+ # Otherwise just echo what we got and ack.
+ message = b''
+ try :
+ message = data.decode('ascii').strip()
+ except UnicodeError :
+ # The data is invalid ascii - ignore it
+ pass
+ if message == 'exit' :
+ # get out of the command mode
+ transparent = True
+ resp = data + b'\r\nEXIT\r\n'
+ else :
+ resp = data + b'\r\nAOK\r\n<2.40-CEL> '
+ if resp :
+ #print("<- Server sending: %r" % resp )
+ writer.write(resp)
+ await writer.drain()
+
+#def signal_handler(signal, frame):
+# loop.stop()
+# sys.exit(0)
+
+#signal.signal(signal.SIGINT, signal_handler)
+
+def to_be(n, size):
+ b=bytearray(size)
+ i=size-1
+ while i >= 0:
+ b[i] = n % 256
+ n = n >> 8
+ i -= 1
+ return b
+def from_be(b):
+ n=0
+ for i in range(len(b)):
+ n = (n << 8) + b[i]
+ return n
+def to_le(n, size):
+ b=bytearray(size)
+ i=0
+ while i < size:
+ b[i] = n % 256
+ n = n >> 8
+ i += 1
+ return b
+def from_le(b):
+ n=0
+ for i in range(len(b)-1, -1, -1):
+ n = (n << 8) + b[i]
+ return n
+
+
+def handle_stellarium_cmd(tel, d):
+ import time
+ p=0
+ while p < len(d)-2:
+ psize=from_le(d[p:p+2])
+ if (psize > len(d) - p):
+ break
+ ptype=from_le(d[p+2:p+4])
+ if ptype == 0:
+ micros=from_le(d[p+4:p+12])
+ if abs((micros/1000000.0) - int(time.time())) > 60.0:
+ tel.print_msg('Client clock differs for more than one minute: '+str(int(micros/1000000.0))+'/'+str(int(time.time())))
+ targetraint=from_le(d[p+12:p+16])
+ targetdecint=from_le(d[p+16:p+20])
+ if (targetdecint > (4294967296 / 2)):
+ targetdecint = - (4294967296 - targetdecint)
+ targetra=(targetraint * 24.0) / 4294967296.0
+ targetdec=(targetdecint * 360.0) / 4294967296.0
+ tel.print_msg('GoTo {} {}'.format(repr_angle(targetra/360),
+ repr_angle(targetdec/360)))
+ p+=psize
+ else:
+ # No such cmd
+ tel.print_msg('Stellarium: unknown command ({})'.format(ptype))
+ p+=psize
+ return p
+
+def make_stellarium_status(tel,obs):
+ import math
+ import time
+ import ephem
+ from math import pi
+
+ alt=tel.alt
+ azm=tel.azm
+ obs.date=ephem.now()
+ rajnow, decjnow=obs.radec_of(azm*2*pi, alt*2*pi)
+ rajnow/=2*pi
+ decjnow/=2*pi
+ status=0
+ msg=bytearray(24)
+ msg[0:2]=to_le(24, 2)
+ msg[2:4]=to_le(0, 2)
+ tstamp=int(time.time())
+ msg[4:12]=to_le(tstamp, 8)
+ msg[12:16]=to_le(int(math.floor(rajnow * 4294967296.0)), 4)
+ msg[16:20]=to_le(int(math.floor(decjnow * 4294967296.0)), 4)
+ msg[20:24]=to_le(status, 4)
+ return msg
+
+
+connections = []
+
+async def report_scope_pos(sleep=0.1, scope=None, obs=None):
+ while True:
+ await asyncio.sleep(sleep)
+ for tr in connections:
+ tr.write(make_stellarium_status(scope,obs))
+
+
+class StellariumServer(asyncio.Protocol):
+
+ def __init__(self, *arg, **kwarg):
+ import ephem
+ global telescope
+
+ self.obs = ephem.Observer()
+ self.obs.lon, self.obs.lat = '20:02', '50:05'
+ self.telescope=telescope
+ asyncio.Protocol.__init__(self,*arg,**kwarg)
+
+ def connection_made(self, transport):
+ peername = transport.get_extra_info('peername')
+ if self.telescope is not None:
+ self.telescope.print_msg('Stellarium from {}\n'.format(peername))
+ self.transport = transport
+ connections.append(transport)
+
+ def connection_lost(self, exc):
+ try:
+ connections.remove(self.transport)
+ self.telescope.print_msg('Stellarium connection closed\n')
+ except ValueError:
+ pass
+
+ def data_received(self,data):
+ if self.telescope is not None:
+ handle_stellarium_cmd(telescope,data)
+
+def main(stdscr):
+ import ephem
+
+ global telescope
+
+ obs = ephem.Observer()
+ obs.lon, obs.lat = '20:02', '50:05'
+
+ if len(sys.argv) >1 and sys.argv[1]=='t':
+ telescope = NexStarScope(stdscr=None)
+ else :
+ telescope = NexStarScope(stdscr=stdscr)
+
+ loop = asyncio.get_event_loop()
+
+ scope = loop.run_until_complete(
+ asyncio.start_server(handle_port2000, host='', port=2000))
+
+ stell = loop.run_until_complete(
+ loop.create_server(StellariumServer,host='',port=10001))
+
+ telescope.print_msg('NSE simulator strted on {}.'.format(scope.sockets[0].getsockname()))
+ telescope.print_msg('Hit CTRL-C to stop.')
+
+ asyncio.ensure_future(broadcast())
+ asyncio.ensure_future(timer(0.1,telescope))
+ asyncio.ensure_future(report_scope_pos(0.1,telescope,obs))
+
+ try :
+ loop.run_forever()
+ except KeyboardInterrupt :
+ pass
+ telescope.print_msg('Simulator shutting down')
+ scope.close()
+ loop.run_until_complete(scope.wait_closed())
+ stell.close()
+ loop.run_until_complete(stell.wait_closed())
+
+ #loop.run_until_complete(asyncio.wait([broadcast(), timer(0.2), scope]))
+ loop.close()
+
+curses.wrapper(main)
diff --git a/indi-celestronaux/simulator/nse_telescope.py b/indi-celestronaux/simulator/nse_telescope.py
new file mode 100644
index 0000000..bd54ba9
--- /dev/null
+++ b/indi-celestronaux/simulator/nse_telescope.py
@@ -0,0 +1,760 @@
+#!/bin/env python3
+
+import struct
+import sys
+from math import pi
+
+import curses
+from collections import deque
+import binascii
+
+# ID tables
+targets={'ANY':0x00,
+ 'MB' :0x01,
+ 'HC' :0x04,
+ 'UKN1':0x05,
+ 'HC+':0x0d,
+ 'AZM':0x10,
+ 'ALT':0x11,
+ 'APP':0x20,
+ 'GPS':0xb0,
+ 'UKN2': 0xb4,
+ 'WiFi':0xb5,
+ 'BAT':0xb6,
+ 'CHG':0xb7,
+ 'LIGHT':0xbf
+ }
+trg_names={value:key for key, value in targets.items()}
+
+control={
+ 'HC' :0x04,
+ 'HC+':0x0d,
+ 'APP':0x20,
+ }
+
+commands={
+ 'MC_GET_POSITION':0x01,
+ 'MC_GOTO_FAST':0x02,
+ 'MC_SET_POSITION':0x04,
+ 'MC_GET_???':0x05,
+ 'MC_SET_POS_GUIDERATE':0x06,
+ 'MC_SET_NEG_GUIDERATE':0x07,
+ 'MC_LEVEL_START':0x0b,
+ 'MC_SET_POS_BACKLASH':0x10,
+ 'MC_SET_NEG_BACKLASH':0x11,
+ 'MC_SLEW_DONE':0x13,
+ 'MC_GOTO_SLOW':0x17,
+ 'MC_AT_INDEX':0x18,
+ 'MC_SEEK_INDEX':0x19,
+ 'MC_SET_MAXRATE':0x20,
+ 'MC_GET_MAXRATE':0x21,
+ 'MC_ENABLE_MAXRATE':0x22,
+ 'MC_MAXRATE_ENABLED':0x23,
+ 'MC_MOVE_POS':0x24,
+ 'MC_MOVE_NEG':0x25,
+ 'MC_ENABLE_CORDWRAP':0x38,
+ 'MC_DISABLE_CORDWRAP':0x39,
+ 'MC_SET_CORDWRAP_POS':0x3a,
+ 'MC_POLL_CORDWRAP':0x3b,
+ 'MC_GET_CORDWRAP_POS':0x3c,
+ 'MC_GET_POS_BACKLASH':0x40,
+ 'MC_GET_NEG_BACKLASH':0x41,
+ 'MC_GET_AUTOGUIDE_RATE':0x47,
+ 'MC_GET_APPROACH':0xfc,
+ 'MC_SET_APPROACH':0xfd,
+ 'GET_VER':0xfe,
+ }
+cmd_names={value:key for key, value in commands.items()}
+
+ACK_CMDS=[0x02,0x04,0x06,0x24,]
+
+MC_ALT=0x11
+MC_AZM=0x10
+
+trg_cmds = {
+ 'BAT': {
+ 0x10:'GET_VOLTAGE',
+ 0x18:'GET_SET_CURRENT',
+ },
+ 'CHG': {
+ 0x10: 'GET_SET_MODE',
+ },
+ 'LIGHT': {
+ 0x10:'GET_SET_LEVEL',
+ },
+}
+
+RATES = {
+ 0 : 0.0,
+ 1 : 1/(360*60),
+ 2 : 2/(360*60),
+ 3 : 5/(360*60),
+ 4 : 15/(360*60),
+ 5 : 30/(360*60),
+ 6 : 1/360,
+ 7 : 2/360,
+ 8 : 5/360,
+ 9 : 10/360
+}
+
+def print_command(cmd):
+ if cmd[2] in (0x10, 0x20):
+ try :
+ return 'Command: %s->%s [%s] len:%d: data:%r' % (
+ trg_names[cmd[1]],
+ trg_names[cmd[2]],
+ cmd_names[cmd[3]], cmd[0], cmd[4:-1])
+ except KeyError :
+ pass
+ try :
+ return 'Command: %s->%s [%02x] len:%d: data:%r' % (
+ trg_names[cmd[1]],
+ trg_names[cmd[2]],
+ cmd[3], cmd[0], cmd[4:-1])
+ except KeyError :
+ pass
+
+ return 'Command: %02x->%02x [%02x] len:%d: data:%r' % (
+ cmd[1], cmd[2], cmd[3], cmd[0], cmd[4:-1])
+
+
+def decode_command(cmd):
+ return (cmd[3], cmd[1], cmd[2], cmd[0], cmd[4:-1], cmd[-1])
+
+def split_cmds(data):
+ # split the data to commands
+ # the initial byte b'\03b' is removed from commands
+ cmds = []
+ b = 0
+ while True :
+ try :
+ p = data.index(b';', b)
+ cmds.append(data[p+1:p+abs(int(data[p+1]))+3])
+ b += abs(int(data[p+1]))+3
+ except ValueError:
+ return cmds
+
+def make_checksum(data):
+ return ((~sum([c for c in bytes(data)]) + 1) ) & 0xFF
+
+
+def ack_cmd(cmd):
+ c,f,t,l,d,cs=decode_command(cmd)
+ rsp=b''
+ if c in ACK_CMDS :
+ rsp=b';\x03'+bytes((t,f,c))
+ rsp+=bytes((make_checksum(rsp[1:]),))
+ return rsp
+
+def f2dms(f):
+ '''
+ Convert fraction of the full rotation to DMS triple (degrees).
+ '''
+ s= 1 if f>0 else -1
+ d=360*abs(f)
+ dd=int(d)
+ mm=int((d-dd)*60)
+ ss=(d-dd-mm/60)*3600
+ return dd,mm,ss
+
+def parse_pos(d):
+ '''
+ Parse first three bytes into the DMS string
+ '''
+ if len(d)>=3 :
+ pos=struct.unpack('!i',b'\x00'+d[:3])[0]/2**24
+ return u'%03d°%02d\'%04.1f"' % f2dms(pos)
+ else :
+ return u''
+
+def repr_pos(alt,azm):
+ return u'(%03d°%02d\'%04.1f", %03d°%02d\'%04.1f")' % (f2dms(alt) + f2dms(azm))
+
+def repr_angle(a):
+ return u'%03d°%02d\'%04.1f"' % f2dms(a)
+
+
+def unpack_int3(d):
+ return struct.unpack('!i',b'\x00'+d[:3])[0]/2**24
+
+def pack_int3(f):
+ return struct.pack('!i',int(f*(2**24)))[1:]
+
+def unpack_int2(d):
+ return struct.unpack('!i',b'\x00\x00'+d[:2])[0]
+
+def pack_int2(v):
+ return struct.pack('!i',int(v))[-2:]
+
+
+
+class NexStarScope:
+
+ __mcfw_ver=(7,11,5100//256,5100%256)
+ __hcfw_ver=(5,28,5300//256,5300%256)
+ __mbfw_ver=(1,0,0,1)
+
+ trg=('MB', 'HC', 'UKN1', 'HC+', 'AZM', 'ALT', 'APP',
+ 'GPS', 'WiFi', 'BAT', 'CHG', 'LIGHT')
+
+ def __init__(self, ALT=0.0, AZM=0.0, tui=True, stdscr=None):
+ self.tui=tui
+ self.alt=ALT
+ self.azm=AZM
+ self.trg_alt=self.alt
+ self.trg_azm=self.azm
+ self.alt_rate=0
+ self.azm_rate=0
+ self.alt_approach=0
+ self.azm_approach=0
+ self.last_cmd=''
+ self.slewing=False
+ self.guiding=False
+ self.goto=False
+ self.alt_guiderate=0.0
+ self.azm_guiderate=0.0
+ self.alt_maxrate=4000
+ self.azm_maxrate=4000
+ self.use_maxrate=False
+ self.cmd_log=deque(maxlen=30)
+ self.msg_log=deque(maxlen=10)
+ self.bat_current=2468
+ self.bat_voltage=12345678
+ self.lt_logo=64
+ self.lt_tray=128
+ self.lt_wifi=255
+ self.charge=False
+ self.cordwrap = False
+ self.cordwrap_pos = 0
+ self._other_handlers = {
+ 0x10: NexStarScope.cmd_0x10,
+ 0x18: NexStarScope.cmd_0x18,
+ 0xfe: NexStarScope.fw_version,
+ }
+ self._mc_handlers = {
+ 0x01 : NexStarScope.get_position,
+ 0x02 : NexStarScope.goto_fast,
+ 0x04 : NexStarScope.set_position,
+ 0x05 : NexStarScope.cmd_0x05,
+ 0x06 : NexStarScope.set_pos_guiderate,
+ 0x07 : NexStarScope.set_neg_guiderate,
+ 0x0b : NexStarScope.level_start,
+ 0x10 : NexStarScope.set_pos_backlash,
+ 0x11 : NexStarScope.set_neg_backlash,
+ 0x13 : NexStarScope.slew_done,
+ 0x17 : NexStarScope.goto_slow,
+ 0x18 : NexStarScope.at_index,
+ 0x19 : NexStarScope.seek_index,
+ 0x20 : NexStarScope.set_maxrate,
+ 0x21 : NexStarScope.get_maxrate,
+ 0x22 : NexStarScope.enable_maxrate,
+ 0x23 : NexStarScope.maxrate_enabled,
+ 0x24 : NexStarScope.move_pos,
+ 0x25 : NexStarScope.move_neg,
+ 0x38 : NexStarScope.enable_cordwrap,
+ 0x39 : NexStarScope.disable_cordwrap,
+ 0x3a : NexStarScope.set_cordwrap_pos,
+ 0x3b : NexStarScope.get_cordwrap,
+ 0x3c : NexStarScope.get_cordwrap_pos,
+ 0x40 : NexStarScope.get_pos_backlash,
+ 0x41 : NexStarScope.get_neg_backlash,
+ 0x47 : NexStarScope.get_autoguide_rate,
+ 0xfc : NexStarScope.get_approach,
+ 0xfd : NexStarScope.set_approach,
+ 0xfe : NexStarScope.fw_version,
+ }
+ if tui : self.init_dsp(stdscr)
+
+ def send_ack(self, data, snd, rcv):
+ return b''
+
+ def set_maxrate(self, data, snd, rcv):
+ if rcv==0x10 :
+ self.alt_maxrate=unpack_int2(data)
+ else :
+ self.azm_maxrate=unpack_int2(data)
+ return b''
+
+ def get_maxrate(self, data, snd, rcv):
+ if len(data) == 0 :
+ #return pack_int2(self.alt_maxrate) + pack_int2(self.azm_maxrate)
+ return bytes.fromhex('0fa01194')
+ else :
+ return b''
+
+ def enable_maxrate(self, data, snd, rcv):
+ self.use_maxrate=bool(data[0])
+ return b''
+
+ def maxrate_enabled(self, data, snd, rcv):
+ if self.use_maxrate :
+ return b'\x01'
+ else :
+ return b'\x00'
+
+ def cmd_0x10(self, data, snd, rcv):
+ if rcv == 0xbf : # LIGHT
+ if len(data)==2 : # Set level
+ if data[0]==0 :
+ self.lt_tray=data[1]
+ elif data[0]==1 :
+ self.lt_logo=data[1]
+ else :
+ self.lt_wifi=data[1]
+ return b''
+ elif len(data)==1: # Get level
+ if data[0]==0 :
+ return bytes([int(self.lt_tray%256)])
+ elif data[0]==1 :
+ return bytes([int(self.lt_logo%256)])
+ else :
+ return bytes([int(self.lt_wifi%256)])
+ else :
+ return b''
+ elif rcv == 0xb7 : # CHG
+ if len(data):
+ self.charge = bool(data[0])
+ return b''
+ else :
+ return bytes([int(self.charge)])
+ elif rcv == 0xb6 : # BAT
+ self.bat_voltage*=0.99
+ v=struct.pack('!i',int(self.bat_voltage))
+ return bytes.fromhex('0102') + v
+ else :
+ return b''
+
+ def cmd_0x18(self, data, snd, rcv):
+ if rcv == 0xb6 : # BAT
+ if len(data):
+ i=data[0]*256+data[1]
+ i=min(5000,i)
+ i=max(2000,i)
+ self.bat_current=i
+ return struct.pack('!i',int(self.bat_current))[-2:]
+
+
+ def get_position(self, data, snd, rcv):
+ try :
+ trg = trg_names[rcv]
+ except KeyError :
+ trg = '???'
+ if trg == 'ALT':
+ return pack_int3(self.alt)
+ else :
+ return pack_int3(self.azm)
+
+ def goto_fast(self, data, snd, rcv):
+ self.last_cmd='GOTO_FAST'
+ self.slewing=True
+ self.goto=True
+ self.guiding=False
+ self.alt_guiderate=0
+ self.azm_guiderate=0
+ if rcv==MC_ALT :
+ r=self.alt_maxrate/(360e3)
+ else :
+ r=self.azm_maxrate/(360e3)
+ a=unpack_int3(data)
+ if trg_names[rcv] == 'ALT':
+ self.trg_alt=a
+ if a-self.alt < 0 :
+ r = -r
+ self.alt_rate = r
+ else :
+ self.trg_azm=a%1.0
+ if self.trg_azm - self.azm < 0 :
+ r = -r
+ if abs(self.trg_azm - self.azm) > 0.5 :
+ r = -r
+ self.azm_rate = r
+ return b''
+
+ def set_position(self,data, snd, rcv):
+ return b''
+
+ def cmd_0x05(self, data, snd, rcv):
+ return bytes.fromhex('1685')
+
+ def set_pos_guiderate(self, data, snd, rcv):
+ # The 1.1 factor is experimental to fit the actual hardware
+ a=1.1*(2**24/1000/360/60/60)*unpack_int3(data) # (transform to rot/sec)
+ self.guiding = a>0
+ if trg_names[rcv] == 'ALT':
+ self.alt_guiderate=a
+ else :
+ self.azm_guiderate=a
+ return b''
+
+ def set_neg_guiderate(self, data, snd, rcv):
+ # The 1.1 factor is experimental to fit the actual hardware
+ a=1.1*(2**24/1000/360/60/60)*unpack_int3(data) # (transform to rot/sec)
+ self.guiding = a>0
+ if trg_names[rcv] == 'ALT':
+ self.alt_guiderate=-a
+ else :
+ self.azm_guiderate=-a
+ return b''
+
+ def level_start(self, data, snd, rcv):
+ return b''
+
+ def set_pos_backlash(self, data, snd, rcv):
+ return b''
+
+ def set_neg_backlash(self, data, snd, rcv):
+ return b''
+
+ def goto_slow(self, data, snd, rcv):
+ self.last_cmd='GOTO_SLOW'
+ self.slewing=True
+ self.goto=True
+ self.guiding=False
+ self.alt_guiderate=0
+ self.azm_guiderate=0
+ r=0.2/360
+ a=unpack_int3(data)
+ if trg_names[rcv] == 'ALT':
+ self.trg_alt=a
+ if self.alt < a :
+ self.alt_rate = r
+ else :
+ self.alt_rate = -r
+ else :
+ self.trg_azm=a%1.0
+ f = 1 if abs(self.azm - self.trg_azm)<0.5 else -1
+ if self.azm < self.trg_azm :
+ self.azm_rate = f*r
+ else :
+ self.azm_rate = -f*r
+ return b''
+
+ def slew_done(self, data, snd, rcv):
+ if rcv == MC_ALT :
+ return b'\x00' if self.alt_rate else b'\xff'
+ if rcv == MC_AZM :
+ return b'\x00' if self.azm_rate else b'\xff'
+
+ def at_index(self, data, snd, rcv):
+ return b'\x00'
+
+ def seek_index(self, data, snd, rcv):
+ return b''
+
+ def move_pos(self, data, snd, rcv):
+ self.last_cmd='MOVE_POS'
+ self.slewing=True
+ self.goto=False
+ r=RATES[int(data[0])]
+ if trg_names[rcv] == 'ALT':
+ self.alt_rate = r
+ else :
+ self.azm_rate = r
+ return b''
+
+ def move_neg(self, data, snd, rcv):
+ self.last_cmd='MOVE_NEG'
+ self.slewing=True
+ self.goto=False
+ r=RATES[int(data[0])]
+ if trg_names[rcv] == 'ALT':
+ self.alt_rate = -r
+ else :
+ self.azm_rate = -r
+ return b''
+
+ def enable_cordwrap(self, data, snd, rcv):
+ self.cordwrap = True
+ return b''
+
+ def disable_cordwrap(self, data, snd, rcv):
+ self.cordwrap = False
+ return b''
+
+ def set_cordwrap_pos(self, data, snd, rcv):
+ self.cordwrap_pos=struct.unpack('!i',b'\x00'+data[:3])[0]
+ return b''
+
+ def get_cordwrap(self, data, snd, rcv):
+ return b'\xFF' if self.cordwrap else b'\x00'
+
+ def get_cordwrap_pos(self, data, snd, rcv):
+ return pack_int3(self.cordwrap_pos)
+
+ def get_pos_backlash(self, data, snd, rcv):
+ return b'\x00'
+
+ def get_neg_backlash(self, data, snd, rcv):
+ return b'\x00'
+
+ def get_autoguide_rate(self, data, snd, rcv):
+ return b'\xf0'
+
+ def get_approach(self, data, snd, rcv):
+ try :
+ trg = trg_names[rcv]
+ except KeyError :
+ trg = '???'
+ if trg == 'ALT':
+ return bytes((self.alt_approach,))
+ else :
+ return bytes((self.azm_approach,))
+
+ def set_approach(self, data, snd, rcv):
+ try :
+ trg = trg_names[rcv]
+ except KeyError :
+ trg = '???'
+ if trg == 'ALT':
+ self.alt_approach=data[0]
+ else :
+ self.azm_approach=data[0]
+ return b''
+
+ def fw_version(self, data, snd, rcv):
+ try :
+ trg = trg_names[rcv]
+ except KeyError :
+ trg = '???'
+ if trg in ('ALT','AZM'):
+ return bytes(NexStarScope.__mcfw_ver)
+ elif trg in ('MB', ):
+ return bytes(NexStarScope.__mbfw_ver)
+ elif trg in ('HC', 'HC+'):
+ return bytes(NexStarScope.__hcfw_ver)
+ else :
+ return b''
+
+ def init_dsp(self,stdscr):
+ self.scr=stdscr
+ if stdscr :
+ self.cmd_log_w=curses.newwin(self.cmd_log.maxlen+2,60,0,50)
+ self.state_w=curses.newwin(1,80,0,0)
+ self.state_w.border()
+ self.pos_w=curses.newwin(4,25,1,0)
+ self.pos_w.border()
+ self.trg_w=curses.newwin(4,25,1,25)
+ self.trg_w.border()
+ self.rate_w=curses.newwin(4,25,5,0)
+ self.guide_w=curses.newwin(4,25,5,25)
+ self.other_w=curses.newwin(8,50,9,0)
+ self.msg_w=curses.newwin(self.msg_log.maxlen+2,50,17,0)
+ stdscr.refresh()
+
+ def update_dsp(self):
+ if self.scr :
+ mode = 'Idle'
+ if self.guiding : mode = 'Guiding'
+ if self.slewing : mode = 'Slewing'
+ self.state_w.clear()
+ self.state_w.addstr(0,1,'State: %8s' % mode)
+ self.state_w.refresh()
+ self.pos_w.clear()
+ self.pos_w.border()
+ self.pos_w.addstr(0,1,'Position:')
+ self.pos_w.addstr(1,3,'Alt: ' + repr_angle(self.alt))
+ self.pos_w.addstr(2,3,'Azm: ' + repr_angle(self.azm))
+ self.pos_w.refresh()
+ self.trg_w.clear()
+ self.trg_w.border()
+ self.trg_w.addstr(0,1,'Target:')
+ self.trg_w.addstr(1,3,'Alt: ' + repr_angle(self.trg_alt))
+ self.trg_w.addstr(2,3,'Azm: ' + repr_angle(self.trg_azm))
+ self.trg_w.refresh()
+ self.rate_w.clear()
+ self.rate_w.border()
+ self.rate_w.addstr(0,1,'Move rate:')
+ self.rate_w.addstr(1,3,'Alt: %+8.4f °/s' % (self.alt_rate*360))
+ self.rate_w.addstr(2,3,'Azm: %+8.4f °/s' % (self.azm_rate*360))
+ self.rate_w.refresh()
+ self.guide_w.clear()
+ self.guide_w.border()
+ self.guide_w.addstr(0,1,'Guide rate:')
+ self.guide_w.addstr(1,3,'Alt: %+8.4f "/s' % (self.alt_guiderate*360*60*60))
+ self.guide_w.addstr(2,3,'Azm: %+8.4f "/s' % (self.azm_guiderate*360*60*60))
+ self.guide_w.refresh()
+ self.other_w.clear()
+ self.other_w.border()
+ self.other_w.addstr(0,1,'Other:')
+ self.other_w.addstr(1,3,'BAT: %9.6f V' % (self.bat_voltage/1e6))
+ self.other_w.addstr(2,3,'CHG: %5.3f A' % (self.bat_current/1e3))
+ self.other_w.addstr(3,3,'LIGHTS: Logo: %d Tray: %d WiFi: %d' % (self.lt_logo, self.lt_tray, self.lt_wifi))
+ self.other_w.addstr(4,3,'Charge: %s' % ('On' if self.charge else 'Auto'))
+ if self.use_maxrate :
+ self.other_w.addstr(5,1,'*')
+ self.other_w.addstr(5,3,'Max rate ALT:%.2f AZM:%.2f' % (self.alt_maxrate/1e3, self.azm_maxrate/1e3))
+ self.other_w.addstr(6,3,'Cordwrap: %3s' % ('On' if self.cordwrap else 'Off'))
+ self.other_w.addstr(6,20,'Pos: ' + repr_angle(self.cordwrap_pos))
+ self.other_w.refresh()
+ self.cmd_log_w.clear()
+ self.cmd_log_w.border()
+ self.cmd_log_w.addstr(0,1,'Commands log')
+ for n,cmd in enumerate(self.cmd_log) :
+ self.cmd_log_w.addstr(n+1,1,cmd)
+ self.cmd_log_w.refresh()
+ self.msg_w.border()
+ self.msg_w.addstr(0,1,'Msg:')
+ for n,cmd in enumerate(self.msg_log) :
+ self.msg_w.addstr(n+1,1,cmd)
+ self.msg_w.refresh()
+
+ def show_status(self):
+ if self.tui and self.scr:
+ self.update_dsp()
+ else :
+ mode = 'Idle'
+ if self.guiding : mode = 'Guiding'
+ if self.slewing : mode = 'Slewing'
+ print('\r%8s: %s -> %s S(%+5.2f %+5.2f) G(%+.4f %+.4f) CMD: %10s' % (
+ mode, repr_pos(self.alt, self.azm),
+ repr_pos(self.trg_alt, self.trg_azm),
+ self.alt_rate*360, self.azm_rate*360, # deg/sec
+ self.alt_guiderate*360*60*60, self.azm_guiderate*360*60*60, # arcsec/sec
+ self.last_cmd), end='')
+ sys.stdout.flush()
+
+ def tick(self, interval):
+ eps=1e-6 # 0.1" precission
+ maxrate = 5/360
+ if self.last_cmd=='GOTO_FAST' :
+ eps*=100
+
+ self.alt += (self.alt_rate + self.alt_guiderate)*interval
+ self.azm += (self.azm_rate + self.azm_guiderate)*interval
+ if self.slewing and self.goto:
+ # AZM
+ r=self.trg_azm - self.azm
+ if abs(r)>0.5 :
+ r = -r
+ s=1 if r>0 else -1
+ mr=min(maxrate,abs(self.azm_rate))
+ if mr*interval > abs(r) :
+ mr/=2
+ self.azm_rate=s*min(mr,abs(r))
+ self.azm_rate=s*mr
+
+ # ALT
+ r=self.trg_alt - self.alt
+ s=1 if r>0 else -1
+ mr=min(maxrate,abs(self.alt_rate))
+ if mr*interval > abs(r) :
+ mr/=2
+ self.alt_rate=s*min(mr,abs(r))
+ self.alt_rate=s*mr
+
+ if abs(self.azm_rate) < eps and abs(self.alt_rate) < eps :
+ self.slewing=False
+ self.goto=False
+ if abs(self.azm_rate) < eps :
+ self.azm_rate=0
+ if abs(self.alt_rate) < eps :
+ self.alt_rate=0
+ self.show_status()
+
+ @property
+ def alt(self):
+ return self.__alt
+
+ @alt.setter
+ def alt(self,ALT):
+ self.__alt=ALT
+ # Altitude movement limits
+ #self.__alt = min(self.__alt,0.23)
+ #self.__alt = max(self.__alt,-0.03)
+
+ @property
+ def azm(self):
+ return self.__azm
+
+ @azm.setter
+ def azm(self,AZM):
+ self.__azm=AZM % 1.0
+
+ @property
+ def trg_alt(self):
+ return self.__trg_alt
+
+ @trg_alt.setter
+ def trg_alt(self,ALT):
+ self.__trg_alt=ALT
+ # Altitude movement limits
+ #self.__trg_alt = min(self.__trg_alt,0.24)
+ #self.__trg_alt = max(self.__trg_alt,-0.01)
+
+ @property
+ def trg_azm(self):
+ return self.__trg_azm
+
+ @trg_azm.setter
+ def trg_azm(self,AZM):
+ self.__trg_azm=AZM % 1.0
+
+ def handle_cmd(self, cmd):
+ #print("-> Scope received %s." % (print_command(cmd)))
+ try :
+ c,f,t,l,d,s=decode_command(cmd)
+ except IndexError :
+ self.print_msg("Malformed command: %r" % (cmd,))
+ return b''
+ resp=b''
+ if make_checksum(cmd[:-1]) != s :
+ self.print_msg("Wrong checksum. Ignoring.")
+ else :
+ resp = b';' + cmd
+ if t in (0x10, 0x11):
+ handlers=self._mc_handlers
+ try :
+ s=('%s[%s] ' % (trg_names[t], cmd_names[c])) + ''.join('%02x' % b for b in d)
+ except KeyError :
+ s=('MC[%02x]' % c) + ' ' + ''.join('%02x' % b for b in d)
+ else :
+ handlers=self._other_handlers
+ try :
+ s=('%s[%s]' % (trg_names[t],trg_cmds[trg_names[t]][c])) + ' ' + ''.join('%02x' % b for b in d)
+ except KeyError :
+ s=('%02x[%02x]' % (t,c)) + ' ' + ''.join('%02x' % b for b in d)
+ sr=b''
+ if c in handlers :
+ r = handlers[c](self,d,f,t)
+ sr = r
+ r = bytes((len(r)+3,t,f,c)) + r
+ resp += b';' + r + bytes((make_checksum(r),))
+ #print('Response: %r' % resp)
+ else :
+ #print('Scope got unknown command %02x' % c)
+ #print("-> Scope received %s." % (print_command(cmd)))
+ s = '** ' + s
+ pass
+
+ if 'MC_GET_POSITION' in s :
+ return resp
+
+ s += '-> ' + ''.join('%02x' % x for x in sr)
+
+ if self.cmd_log :
+ if s != self.cmd_log[-1] :
+ self.cmd_log.append(s)
+ else :
+ self.cmd_log.append(s)
+
+ return resp
+
+ def print_msg(self, msg):
+ if self.msg_log :
+ if msg != self.msg_log[-1] :
+ self.msg_log.append(msg)
+ else :
+ self.msg_log.append(msg)
+
+
+
+ def handle_msg(self, msg):
+ '''
+ React to message. Get AUX command(s) in the message and react to it.
+ Return a message simulating real AUX scope response.
+ '''
+ return b''.join([self.handle_cmd(cmd) for cmd in split_cmds(msg)])
+
+
+
+