kopia lustrzana https://github.com/Hamlib/Hamlib
Implement initial skywatcher rotator
Use GOTO mode when setting position. Use cache for CPR (Counts Per Revolution) because it is hardcoded for the motorpull/1603/head
rodzic
137231cb7b
commit
099cc7fc48
|
@ -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 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"
|
||||
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
|
||||
AMP_BACKEND_LIST="amplifiers/elecraft amplifiers/gemini amplifiers/expert"
|
||||
|
||||
|
@ -898,6 +898,7 @@ rotators/spid/Makefile
|
|||
rotators/ts7400/Makefile
|
||||
rotators/indi/Makefile
|
||||
rotators/satel/Makefile
|
||||
rotators/skywatcher/Makefile
|
||||
rotators/radant/Makefile
|
||||
rigs/adat/Makefile
|
||||
rigs/alinco/Makefile
|
||||
|
|
|
@ -702,6 +702,18 @@
|
|||
//! @endcond
|
||||
#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.
|
||||
|
|
|
@ -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)
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
noinst_LTLIBRARIES = libhamlib-skywatcher.la
|
||||
libhamlib_skywatcher_la_SOURCES = skywatcher.c skywatcher.h
|
||||
|
||||
EXTRA_DIST = Android.mk
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -93,6 +93,7 @@ DEFINE_INITROT_BACKEND(grbltrk);
|
|||
DEFINE_INITROT_BACKEND(flir);
|
||||
DEFINE_INITROT_BACKEND(apex);
|
||||
DEFINE_INITROT_BACKEND(saebrtrack);
|
||||
DEFINE_INITROT_BACKEND(skywatcher);
|
||||
|
||||
//! @endcond
|
||||
|
||||
|
@ -145,6 +146,7 @@ static struct
|
|||
{ ROT_FLIR, ROT_BACKEND_FLIR, ROT_FUNCNAMA(flir) },
|
||||
{ ROT_APEX, ROT_BACKEND_APEX, ROT_FUNCNAMA(apex) },
|
||||
{ ROT_SAEBRTRACK, ROT_BACKEND_SAEBRTRACK, ROT_FUNCNAMA(saebrtrack)},
|
||||
{ ROT_SKYWATCHER, ROT_BACKEND_SKYWATCHER, ROT_FUNCNAMA(skywatcher)},
|
||||
{ 0, NULL }, /* end */
|
||||
};
|
||||
|
||||
|
@ -260,7 +262,7 @@ static int rot_lookup_backend(rot_model_t rot_model)
|
|||
{
|
||||
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) ==
|
||||
rot_backend_list[i].be_num)
|
||||
|
|
Ładowanie…
Reference in New Issue