Implement initial skywatcher rotator

Use GOTO mode when setting position.
Use cache for CPR (Counts Per Revolution) because it is hardcoded for the motor
This commit is contained in:
dernasherbrezon 2024-08-27 23:41:01 +01:00
parent 137231cb7b
commit 099cc7fc48
7 changed files with 309 additions and 2 deletions

View File

@ -67,7 +67,7 @@ dnl Beware of duplication should a backend directory include both rig and
dnl rotor definitions, e.g. "dummy". Optional backends will not be listed dnl rotor definitions, e.g. "dummy". Optional backends will not be listed
dnl here but will be added later, e.g. "winradio". dnl here but will be added later, e.g. "winradio".
RIG_BACKEND_LIST="rigs/adat rigs/alinco rigs/aor rigs/barrett rigs/codan rigs/dorji rigs/drake rigs/dummy rigs/elad rigs/flexradio rigs/icom rigs/icmarine rigs/jrc rigs/kachina rigs/kenwood rigs/kit rigs/lowe rigs/pcr rigs/prm80 rigs/racal rigs/rft rigs/rs rigs/skanti rigs/tapr rigs/tentec rigs/tuner rigs/uniden rigs/winradio rigs/wj rigs/yaesu rigs/gomspace rigs/mds rigs/anytone rigs/motorola rigs/commradio" RIG_BACKEND_LIST="rigs/adat rigs/alinco rigs/aor rigs/barrett rigs/codan rigs/dorji rigs/drake rigs/dummy rigs/elad rigs/flexradio rigs/icom rigs/icmarine rigs/jrc rigs/kachina rigs/kenwood rigs/kit rigs/lowe rigs/pcr rigs/prm80 rigs/racal rigs/rft rigs/rs rigs/skanti rigs/tapr rigs/tentec rigs/tuner rigs/uniden rigs/winradio rigs/wj rigs/yaesu rigs/gomspace rigs/mds rigs/anytone rigs/motorola rigs/commradio"
ROT_BACKEND_LIST="rotators/amsat rotators/apex rotators/ars rotators/celestron rotators/cnctrk rotators/grbltrk rotators/easycomm rotators/ether6 rotators/flir rotators/fodtrack rotators/gs232a rotators/heathkit rotators/m2 rotators/meade rotators/rotorez rotators/sartek rotators/saebrtrack rotators/spid rotators/ts7400 rotators/prosistel rotators/ioptron rotators/satel rotators/radant" ROT_BACKEND_LIST="rotators/amsat rotators/apex rotators/ars rotators/celestron rotators/cnctrk rotators/grbltrk rotators/easycomm rotators/ether6 rotators/flir rotators/fodtrack rotators/gs232a rotators/heathkit rotators/m2 rotators/meade rotators/rotorez rotators/sartek rotators/saebrtrack rotators/spid rotators/ts7400 rotators/prosistel rotators/ioptron rotators/satel rotators/skywatcher rotators/radant"
# Amplifiers are all in the amplifiers directory # Amplifiers are all in the amplifiers directory
AMP_BACKEND_LIST="amplifiers/elecraft amplifiers/gemini amplifiers/expert" AMP_BACKEND_LIST="amplifiers/elecraft amplifiers/gemini amplifiers/expert"
@ -898,6 +898,7 @@ rotators/spid/Makefile
rotators/ts7400/Makefile rotators/ts7400/Makefile
rotators/indi/Makefile rotators/indi/Makefile
rotators/satel/Makefile rotators/satel/Makefile
rotators/skywatcher/Makefile
rotators/radant/Makefile rotators/radant/Makefile
rigs/adat/Makefile rigs/adat/Makefile
rigs/alinco/Makefile rigs/alinco/Makefile

View File

@ -702,6 +702,18 @@
//! @endcond //! @endcond
#define ROT_MODEL_SAEBRTRACK ROT_MAKE_MODEL(ROT_SAEBRTRACK, 1) #define ROT_MODEL_SAEBRTRACK ROT_MAKE_MODEL(ROT_SAEBRTRACK, 1)
/**
* \brief A macro that returns the model number of the SKYWATCHER backend.
*
* \def ROT_MODEL_SKYWATCHER
*
* The SKYWATCHER backend can be used with SKYWATCHER * rotators.
*/
//! @cond Doxygen_Suppress
#define ROT_SKYWATCHER 28
#define ROT_BACKEND_SKYWATCHER "SkyWatcher"
//! @endcond
#define ROT_MODEL_SKYWATCHER ROT_MAKE_MODEL(ROT_SKYWATCHER, 1)
/** /**
* \brief Convenience type definition for a rotator model. * \brief Convenience type definition for a rotator model.

View File

@ -0,0 +1,12 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := skywatcher.c
LOCAL_MODULE := skywatcher
LOCAL_CFLAGS :=
LOCAL_C_INCLUDES := android include src
LOCAL_LDLIBS := -lhamlib -Lobj/local/$(TARGET_ARCH_ABI)
include $(BUILD_STATIC_LIBRARY)

View File

@ -0,0 +1,5 @@
noinst_LTLIBRARIES = libhamlib-skywatcher.la
libhamlib_skywatcher_la_SOURCES = skywatcher.c skywatcher.h
EXTRA_DIST = Android.mk

View File

@ -0,0 +1,249 @@
#include <stdlib.h>
#include "skywatcher.h"
#include "register.h"
#include "serial.h"
#define ERROR_CHECK(x) \
do { \
int __err_rc = (x); \
if (__err_rc != RIG_OK) { \
return __err_rc; \
} \
} while (0)
static const int skywatcher_error_codes_length = 9;
static const char *skywatcher_error_codes[skywatcher_error_codes_length] = {
"Unknown Command",
"Command Length Error",
"Motor not Stopped",
"Invalid Character",
"Not Initialized",
"Driver Sleeping",
"Unknown",
"PEC Training is running",
"No Valid PEC data"
};
typedef struct {
uint32_t cpr[2];
} skywatcher_priv_data;
static int skywatcher_cmd(ROT *rot, const char *cmd, char *response, size_t response_len) {
hamlib_port_t *port = ROTPORT(rot);
rig_flush(port);
size_t cmd_len = strlen(cmd);
ERROR_CHECK(write_block(port, (unsigned char *) cmd, cmd_len));
// echo from the device
int code = read_string(port, (unsigned char *) response, response_len, "\r", 1, 0, 1);
if (code < 0) {
return -code;
}
// the actual response
code = read_string(port, (unsigned char *) response, response_len, "\r", 1, 0, 1);
if (code < 0) {
return -code;
}
// nullify last \r
response[strlen(response) - 1] = '\0';
if (response[0] == '!') {
code = atoi(&response[1]);
if (code < skywatcher_error_codes_length) {
rig_debug(RIG_DEBUG_ERR, "Error code: %d Message: '%s'\n", code, skywatcher_error_codes[code]);
} else {
rig_debug(RIG_DEBUG_ERR, "Error response: '%s'\n", response);
}
return RIG_EPROTO;
}
// remove leading '='
memmove(response, response + 1, strlen(response) - 1);
response[strlen(response) - 1] = '\0';
return RIG_OK;
}
inline uint32_t skywatcher_convert24bit(long input) {
return ((input & 0x0000FF) << 16) | (input & 0x00FF00) | ((input & 0xFF0000) >> 16);
}
static const char *skywatcher_get_info(ROT *rot) {
static char info[32];
char str[16];
rig_debug(RIG_DEBUG_TRACE, "%s called\n", __func__);
// Read from 1st motor only. In practice both motors have the same version
if (skywatcher_cmd(rot, ":e1\r", str, sizeof(str)) != RIG_OK) {
return NULL;
}
SNPRINTF(info, sizeof(info), "V%s", str);
return info;
}
int skywatcher_get_spr(ROT *rot, int motor_index, uint32_t *result) {
skywatcher_priv_data *priv = ROTSTATE(rot)->priv;
if (priv->cpr[motor_index - 1] != 0) {
*result = priv->cpr[motor_index - 1];
return RIG_OK;
}
char str[16];
char req[16];
SNPRINTF(req, sizeof(req), ":a%d\r", motor_index);
ERROR_CHECK(skywatcher_cmd(rot, req, str, sizeof(str)));
priv->cpr[motor_index - 1] = skywatcher_convert24bit(strtol(str, NULL, 16));
*result = priv->cpr[motor_index - 1];
return RIG_OK;
}
int skywatcher_get_motor_position(ROT *rot, int motor_index, float *result) {
char str[16];
char req[16];
SNPRINTF(req, sizeof(req), ":j%d\r", motor_index);
ERROR_CHECK(skywatcher_cmd(rot, req, str, sizeof(str)));
long motor_ticks = skywatcher_convert24bit(strtol(str, NULL, 16)) - 0x800000;
uint32_t cpr;
ERROR_CHECK(skywatcher_get_spr(rot, motor_index, &cpr));
double ticks_per_angle = (double) cpr / 360.0;
*result = (float) ((double) motor_ticks / ticks_per_angle);
return RIG_OK;
}
static int skywatcher_get_position(ROT *rot, azimuth_t *az, elevation_t *el) {
rig_debug(RIG_DEBUG_TRACE, "%s called\n", __func__);
ERROR_CHECK(skywatcher_get_motor_position(rot, 1, (float *) az));
// normalize to 0 ~ 360
if (*az < 0.0f) {
*az = 360.0f + *az;
}
ERROR_CHECK(skywatcher_get_motor_position(rot, 2, (float *) el));
rig_debug(RIG_DEBUG_TRACE, "%s: (az, el) = (%.1f, %.1f)\n", __func__, *az, *el);
return RIG_OK;
}
static int skywatcher_stop(ROT *rot) {
rig_debug(RIG_DEBUG_TRACE, "%s called\n", __func__);
char str[16];
// don't wait for actual stop. Operation can be async
ERROR_CHECK(skywatcher_cmd(rot, ":K1\r", str, sizeof(str)));
ERROR_CHECK(skywatcher_cmd(rot, ":K2\r", str, sizeof(str)));
return RIG_OK;
}
int skywatcher_set_motor_position(ROT *rot, int motor_index, float angle) {
char str[16];
char req[16];
hamlib_port_t *rotp = ROTPORT(rot);
SNPRINTF(req, sizeof(req), ":f%d\r", motor_index);
int current_retry = 0;
int stopped = 0;
while (current_retry < rotp->retry) {
ERROR_CHECK(skywatcher_cmd(rot, req, str, sizeof(str)));
int status = str[1] - '0';
if (status & 0b10) {
rig_debug(RIG_DEBUG_ERR, "%s: motor is blocked\n", __func__);
return RIG_EPROTO;
}
if (status & 0b1) {
current_retry++;
//10ms sleep just to make sure motor is fully stopped
hl_usleep(10000);
continue;
}
stopped = 1;
break;
}
if (!stopped) {
return RIG_EPROTO;
}
SNPRINTF(req, sizeof(req), ":G%d00\r", motor_index);
ERROR_CHECK(skywatcher_cmd(rot, req, str, sizeof(str)));
uint32_t cpr;
ERROR_CHECK(skywatcher_get_spr(rot, motor_index, &cpr));
double ticks_per_angle = (double) cpr / 360.0;
uint32_t value = ((uint32_t) (ticks_per_angle * angle)) & 0xFFFFFF;
value = value | 0x800000;
SNPRINTF(req, sizeof(req), ":S%d%02X%02X%02X\r", motor_index, (value & 0xFF), (value & 0xFF00) >> 8, (value & 0xFF0000) >> 16);
ERROR_CHECK(skywatcher_cmd(rot, req, str, sizeof(str)));
// start motion, don't wait until it completes
SNPRINTF(req, sizeof(req), ":J%d\r", motor_index);
ERROR_CHECK(skywatcher_cmd(rot, req, str, sizeof(str)));
return RIG_OK;
}
static int skywatcher_set_position(ROT *rot, azimuth_t az, elevation_t el) {
rig_debug(RIG_DEBUG_TRACE, "%s called: %f %f\n", __func__, az, el);
ERROR_CHECK(skywatcher_stop(rot));
// stop is called for 1 then for 2 motor. do motor position in reverse because it awaits "stop motor" state.
// potentially saving 1 call to :f
ERROR_CHECK(skywatcher_set_motor_position(rot, 2, el));
ERROR_CHECK(skywatcher_set_motor_position(rot, 1, az));
return RIG_OK;
}
static int skywatcher_init(ROT *rot) {
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
skywatcher_priv_data *result = calloc(1, sizeof(skywatcher_priv_data));
if (result == NULL) {
return -RIG_ENOMEM;
}
memset(result->cpr, 0, sizeof(result->cpr));
ROTSTATE(rot)->priv = result;
return RIG_OK;
}
static int skywatcher_cleanup(ROT *rot) {
skywatcher_priv_data *priv = ROTSTATE(rot)->priv;
if (priv == NULL) {
return RIG_OK;
}
free(priv);
return RIG_OK;
}
/* ************************************************************************* */
/*
* Protocol documentation:
* https://inter-static.skywatcher.com/downloads/skywatcher_motor_controller_command_set.pdf
*/
const struct rot_caps skywatcher_rot_caps =
{
ROT_MODEL(ROT_MODEL_SKYWATCHER),
.model_name = "Sky-Watcher",
.mfg_name = "Sky-Watcher",
.version = "20240825.0",
.copyright = "LGPL",
.status = RIG_STATUS_STABLE,
.rot_type = ROT_TYPE_AZEL,
.port_type = RIG_PORT_SERIAL,
.serial_rate_min = 9600,
.serial_rate_max = 9600,
.serial_data_bits = 8,
.serial_stop_bits = 1,
.serial_parity = RIG_PARITY_NONE,
.serial_handshake = RIG_HANDSHAKE_NONE,
.write_delay = 0,
.post_write_delay = 0,
.timeout = 3500, /* worst case scenario */
.retry = 1,
.min_az = 0.0f,
.max_az = 360.0f,
.min_el = 0.0f,
.max_el = 180.0f,
.rot_init = skywatcher_init,
.rot_cleanup = skywatcher_cleanup,
.get_position = skywatcher_get_position,
.set_position = skywatcher_set_position,
.stop = skywatcher_stop,
.get_info = skywatcher_get_info,
};
DECLARE_INITROT_BACKEND(skywatcher) {
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
rot_register(&skywatcher_rot_caps);
return RIG_OK;
}

View File

@ -0,0 +1,26 @@
/*
* Hamlib Rotator backend - Celestron interface protocol
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef HAMLIB_SKYWATCHER_H
#define HAMLIB_SKYWATCHER_H 1
#include "rotator.h"
extern const struct rot_caps skywatcher_rot_caps;
#endif //HAMLIB_SKYWATCHER_H

View File

@ -93,6 +93,7 @@ DEFINE_INITROT_BACKEND(grbltrk);
DEFINE_INITROT_BACKEND(flir); DEFINE_INITROT_BACKEND(flir);
DEFINE_INITROT_BACKEND(apex); DEFINE_INITROT_BACKEND(apex);
DEFINE_INITROT_BACKEND(saebrtrack); DEFINE_INITROT_BACKEND(saebrtrack);
DEFINE_INITROT_BACKEND(skywatcher);
//! @endcond //! @endcond
@ -145,6 +146,7 @@ static struct
{ ROT_FLIR, ROT_BACKEND_FLIR, ROT_FUNCNAMA(flir) }, { ROT_FLIR, ROT_BACKEND_FLIR, ROT_FUNCNAMA(flir) },
{ ROT_APEX, ROT_BACKEND_APEX, ROT_FUNCNAMA(apex) }, { ROT_APEX, ROT_BACKEND_APEX, ROT_FUNCNAMA(apex) },
{ ROT_SAEBRTRACK, ROT_BACKEND_SAEBRTRACK, ROT_FUNCNAMA(saebrtrack)}, { ROT_SAEBRTRACK, ROT_BACKEND_SAEBRTRACK, ROT_FUNCNAMA(saebrtrack)},
{ ROT_SKYWATCHER, ROT_BACKEND_SKYWATCHER, ROT_FUNCNAMA(skywatcher)},
{ 0, NULL }, /* end */ { 0, NULL }, /* end */
}; };
@ -260,7 +262,7 @@ static int rot_lookup_backend(rot_model_t rot_model)
{ {
int i; int i;
for (i = 0; i < ROT_BACKEND_MAX && rot_backend_list[i].be_name; i++) for (i = 0; i < ROT_BACKEND_MAX && rot_backend_list[i].be_name; i++)
{ {
if (ROT_BACKEND_NUM(rot_model) == if (ROT_BACKEND_NUM(rot_model) ==
rot_backend_list[i].be_num) rot_backend_list[i].be_num)