Merge pull request #1164 from andz82/master

Added FLIR/DirectedPerception PTU rotor backend
pull/1191/head
Michael Black 2022-12-08 22:30:24 -06:00 zatwierdzone przez GitHub
commit d369b60f6d
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
8 zmienionych plików z 713 dodań i 1 usunięć

Wyświetl plik

@ -48,7 +48,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"
ROT_BACKEND_LIST="rotators/amsat rotators/ars rotators/celestron rotators/cnctrk rotators/grbltrk rotators/easycomm rotators/ether6 rotators/fodtrack rotators/gs232a rotators/heathkit rotators/m2 rotators/meade rotators/rotorez rotators/sartek rotators/spid rotators/ts7400 rotators/prosistel rotators/ioptron rotators/satel rotators/radant"
ROT_BACKEND_LIST="rotators/amsat 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/spid rotators/ts7400 rotators/prosistel rotators/ioptron rotators/satel rotators/radant"
# Amplifiers are all in the amplifiers directory
AMP_BACKEND_LIST="amplifiers/elecraft amplifiers/gemini"
@ -834,6 +834,7 @@ rotators/cnctrk/Makefile
rotators/grbltrk/Makefile
rotators/easycomm/Makefile
rotators/ether6/Makefile
rotators/flir/Makefile
rotators/fodtrack/Makefile
rotators/gs232a/Makefile
rotators/heathkit/Makefile

Wyświetl plik

@ -650,6 +650,21 @@
#define ROT_MODEL_GRBLTRK_SER ROT_MAKE_MODEL(ROT_GRBLTRK, 1)
#define ROT_MODEL_GRBLTRK_NET ROT_MAKE_MODEL(ROT_GRBLTRK, 2)
/**
* \brief A macro that returns the model number of the FLIR backend.
*
* \def ROT_MODEL_FLIR
*
* The FLIR backend can be used with FLIR and DirectedPercepition
* rotators using the PTU protocol (e.g. PTU-D48). Currently only
* the serial interaface is supported and no ethernet.
*/
//! @cond Doxygen_Suppress
#define ROT_FLIR 25
#define ROT_BACKEND_FLIR "flir"
//! @endcond
#define ROT_MODEL_FLIR ROT_MAKE_MODEL(ROT_FLIR, 1)
/**
* \brief Convenience type definition for a rotator model.
*

Wyświetl plik

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

Wyświetl plik

@ -0,0 +1,6 @@
FLIRSRC = flir.c flir.h
noinst_LTLIBRARIES = libhamlib-flir.la
libhamlib_flir_la_SOURCES = $(FLIRSRC)
EXTRA_DIST = README.md Android.mk

Wyświetl plik

@ -0,0 +1,75 @@
# FLIR/DirectedPerception PTU Rotor Module
This module interfaces FLIR and DirectedPerception
rotor using the PTU protocol via serial.
This includes:
* PTU-D48(E)
* PTU-E46
* PTU-D100(E)
* PTU-D300(E)
Tested only with PTU-D48 yet and with one rotor per chain only.
## Usage
1. Connect the rotor via serial (RS232 or RS485)
2. Power up the rotor
3. The rotor must be calibrated after each power up. This can be accived
either using the rotctl `Reset` command (R) or manually via serial terminal
sending the `R\n` command.
4. To enable the rotor to fully turn +/- 180°, the softlock must be disabled.
This is included in the rotctl `Reset` commnad or manually via serial terminal
seinden the command `LD\n`. **WARNING:** Send this command only after the rotor is
calibrated, or you risk damage running into the hard endstops (at about +/-190°)
5. Start `rotctl` or `rotctld` with the arguments `-m 2501 -r <Serial
Interface>`
Have Fun.
### Hints
1. Setup the max. velocity, power and acceleration according to your antenna load.
This must be done via serial terminal, as the functions are not implemented yet.
2. Never use the maximum hold power, only use the low or off. If you use max or regular,
the rotor may easily overheat!
## PTU Protocol
* [Protocol Version 3.02 (2011)](https://flir.netx.net/file/asset/11556/original/attachment)
## Current Status
The current status is **ALPHA**. It is tested with DirectedPercepiton PTU-D48 (Firmware v2.13.4r0(D48-C14/E))
Linux with `rotctl` and `gpredict`.
### Implemented so far:
* init
* cleanup
* open
* close
* set_position
* get_position
* park
* stop
* reset
* move
* info
### Needs to be implemented:
#### Parameters
* velocity
* acceleration
* velocity profile
* user soft-limits
* power commands (move and hold)
* step mode
* reset on startup
#### Functions
* usage of chained rotors via RS485

Wyświetl plik

@ -0,0 +1,555 @@
/*
* Hamlib FLIR PTU rotor backend - main file
* Copyright (c) 2022 by Andreas Mueller (DC1MIL)
*
*
* 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 <hamlib/config.h>
#include <stdlib.h>
#include <string.h> /* String function definitions */
#include <math.h>
#include <sys/time.h>
#include "hamlib/rotator.h"
#include "register.h"
#include "idx_builtin.h"
#include "serial.h"
#include "flir.h"
#define FLIR_FUNC 0
#define FLIR_LEVEL ROT_LEVEL_SPEED
#define FLIR_PARM 0
#define FLIR_STATUS (ROT_STATUS_MOVING | ROT_STATUS_MOVING_AZ | ROT_STATUS_MOVING_LEFT | ROT_STATUS_MOVING_RIGHT | \
ROT_STATUS_MOVING_EL | ROT_STATUS_MOVING_UP | ROT_STATUS_MOVING_DOWN | \
ROT_STATUS_LIMIT_UP | ROT_STATUS_LIMIT_DOWN | ROT_STATUS_LIMIT_LEFT | ROT_STATUS_LIMIT_RIGHT)
struct flir_priv_data
{
azimuth_t az;
elevation_t el;
struct timeval tv; /* time last az/el update */
azimuth_t target_az;
elevation_t target_el;
rot_status_t status;
setting_t funcs;
value_t levels[RIG_SETTING_MAX];
value_t parms[RIG_SETTING_MAX];
char info[256];
struct ext_list *ext_funcs;
struct ext_list *ext_levels;
struct ext_list *ext_parms;
char *magic_conf;
float_t resolution_pp;
float_t resolution_tp;
};
static int flir_request(ROT *rot, char *request, char *response,
int resp_size)
{
int return_value = -RIG_EINVAL;
int retry_read = 0;
int read_char = 0;
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
rig_flush(&rot->state.rotport);
if (request)
{
return_value = write_block(&rot->state.rotport, (unsigned char *)request,
strlen(request));
if (return_value != RIG_OK)
{
rig_debug(RIG_DEBUG_VERBOSE, "%s request not OK\n", __func__);
return return_value;
}
}
//Is a direct response expected?
if (response != NULL)
{
while(retry_read < rot->state.rotport.retry)
{
memset(response, 0, (size_t)resp_size);
read_char = read_string(&rot->state.rotport, (unsigned char *)response, resp_size,
"\r\n", sizeof("\r\n"), 0, 1);
if(read_char > 0)
{
if(response[0] == '*')
{
rig_debug(RIG_DEBUG_VERBOSE, "accepted command %s\n", request);
return RIG_OK;
}
else
{
rig_debug(RIG_DEBUG_VERBOSE, "NOT accepted command %s\n", request);
return -RIG_ERJCTED;
}
}
retry_read++;
}
response = "";
rig_debug(RIG_DEBUG_VERBOSE, "timeout for command %s\n", request);
return -RIG_ETIMEOUT;
}
return return_value;
};
static int flir_init(ROT *rot)
{
struct flir_priv_data *priv;
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
rot->state.priv = (struct flir_priv_data *)
calloc(1, sizeof(struct flir_priv_data));
if (!rot->state.priv)
{
return -RIG_ENOMEM;
}
priv = rot->state.priv;
priv->az = priv->el = 0;
priv->target_az = priv->target_el = 0;
priv->magic_conf = strdup("ROTATOR");
priv->resolution_pp = 92.5714;
priv->resolution_tp = 46.2857;
return RIG_OK;
}
static int flir_cleanup(ROT *rot)
{
struct flir_priv_data *priv = (struct flir_priv_data *)
rot->state.priv;
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
free(priv->ext_funcs);
free(priv->ext_levels);
free(priv->ext_parms);
free(priv->magic_conf);
if (rot->state.priv)
{
free(rot->state.priv);
}
rot->state.priv = NULL;
return RIG_OK;
}
static int flir_open(ROT *rot)
{
struct flir_priv_data *priv;
char return_str[MAXBUF];
float_t resolution_pp, resolution_tp;
int return_value = RIG_OK;
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
priv = rot->state.priv;
// Disable ECHO
return_value = flir_request(rot, "ED\n", NULL, MAXBUF);
// Disable Verbose Mode
return_value = flir_request(rot, "FT\n", return_str, MAXBUF);
// Get PAN resolution in arcsecs
if(flir_request(rot, "PR\n", return_str, MAXBUF) == RIG_OK)
{
sscanf(return_str, "* %f", &resolution_pp);
rig_debug(RIG_DEBUG_VERBOSE, "PAN resolution: %f arcsecs per position\n", resolution_pp);
priv->resolution_pp = resolution_pp;
}
else
{
return_value = -RIG_EPROTO;
}
// Get TILT resolution in arcsecs
if(flir_request(rot, "TR\n", return_str, MAXBUF) == RIG_OK)
{
sscanf(return_str, "* %f", &resolution_tp);
rig_debug(RIG_DEBUG_VERBOSE, "TILT resolution: %f arcsecs per position\n", resolution_tp);
priv->resolution_tp = resolution_tp;
}
else
{
return_value = -RIG_EPROTO;
}
return return_value;
}
static int flir_close(ROT *rot)
{
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
return RIG_OK;
}
static int flir_set_conf(ROT *rot, token_t token, const char *val)
{
return -RIG_ENIMPL;
}
static int flir_get_conf(ROT *rot, token_t token, char *val)
{
return -RIG_ENIMPL;
}
static int flir_set_position(ROT *rot, azimuth_t az, elevation_t el)
{
int32_t t_pan_positions, t_tilt_positions;
char return_str[MAXBUF];
char cmd_str[MAXBUF];
struct flir_priv_data *priv = (struct flir_priv_data *)
rot->state.priv;
rig_debug(RIG_DEBUG_VERBOSE, "%s called: %.2f %.2f\n", __func__,
az, el);
priv->target_az = az;
priv->target_el = el;
t_pan_positions = (az * 3600) / priv->resolution_pp;
t_tilt_positions = - ((90.0 - el) * 3600) / priv->resolution_tp;
sprintf(cmd_str, "PP%d TP%d\n", t_pan_positions, t_tilt_positions);
return flir_request(rot, cmd_str, return_str, MAXBUF);
}
/*
* Get position of rotor
*/
static int flir_get_position(ROT *rot, azimuth_t *az, elevation_t *el)
{
int return_value = RIG_OK;
char return_str[MAXBUF];
int32_t pan_positions, tilt_positions;
struct flir_priv_data *priv = (struct flir_priv_data *)
rot->state.priv;
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
if(flir_request(rot, "PP\n", return_str, MAXBUF) == RIG_OK)
{
rig_debug(RIG_DEBUG_VERBOSE, "PP Return String: %s\n", return_str);
sscanf(return_str, "* %d", &pan_positions);
priv->az = (pan_positions * priv->resolution_pp) / 3600;
*az = priv->az;
}
else
{
rig_debug(RIG_DEBUG_VERBOSE, "PP Wrong Return String: %s\n", return_str);
return_value = -RIG_EPROTO;
}
if(flir_request(rot, "TP\n", return_str, MAXBUF) == RIG_OK)
{
rig_debug(RIG_DEBUG_VERBOSE, "TP Return String: %s\n", return_str);
sscanf(return_str, "* %d", &tilt_positions);
priv->el = 90.0 + ((tilt_positions * priv->resolution_tp) / 3600);
*el = priv->el;
}
else
{
rig_debug(RIG_DEBUG_VERBOSE, "PP Wrong Return String: %s\n", return_str);
return_value = -RIG_EPROTO;
}
return return_value;
}
static int flir_stop(ROT *rot)
{
int return_value = RIG_OK;
struct flir_priv_data *priv = (struct flir_priv_data *)
rot->state.priv;
azimuth_t az;
elevation_t el;
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
return_value = flir_request(rot, "H\n", NULL, MAXBUF);
// Wait 2s until rotor has stopped (Needs to be refactored)
hl_usleep(2000000);
return_value = flir_get_position(rot, &az, &el);
priv->target_az = priv->az = az;
priv->target_el = priv->el = el;
return return_value;
}
static int flir_park(ROT *rot)
{
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
/* Assume park Position is 0,90 */
flir_set_position(rot, 0, 90);
return RIG_OK;
}
static int flir_reset(ROT *rot, rot_reset_t reset)
{
int return_value = RIG_OK;
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
if (reset != 0)
{
return_value = flir_request(rot, "r\n", NULL, 0);
// After Reset: Disable Hard Limits
if(return_value == RIG_OK)
{
return_value = flir_request(rot, "LD\n", NULL, MAXBUF);
}
}
return return_value;
}
static int flir_move(ROT *rot, int direction, int speed)
{
struct flir_priv_data *priv = (struct flir_priv_data *)
rot->state.priv;
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
rig_debug(RIG_DEBUG_TRACE, "%s: Direction = %d, Speed = %d\n", __func__,
direction, speed);
switch (direction)
{
case ROT_MOVE_UP:
return flir_set_position(rot, priv->target_az, 90);
case ROT_MOVE_DOWN:
return flir_set_position(rot, priv->target_az, 0);
case ROT_MOVE_CCW:
return flir_set_position(rot, -180, priv->target_el);
case ROT_MOVE_CW:
return flir_set_position(rot, 180, priv->target_el);
default:
return -RIG_EINVAL;
}
return RIG_OK;
}
static const char *flir_get_info(ROT *rot)
{
char firmware_str[121];
char info_str[101];
struct flir_priv_data *priv = (struct flir_priv_data *)
rot->state.priv;
sprintf(priv->info, "No Info");
rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
if(flir_request(rot, "V\n", firmware_str, 120) != RIG_OK)
{
return "No Info available";
}
hl_usleep(500000);
if(flir_request(rot, "O\n", info_str, 100) != RIG_OK)
{
return "No Info available";
}
sprintf(priv->info, "Firmware: %s\nPower: %s", firmware_str, info_str);
return priv->info;
}
static int flir_set_func(ROT *rot, setting_t func, int status)
{
return -RIG_ENIMPL;
}
static int flir_get_func(ROT *rot, setting_t func, int *status)
{
return -RIG_ENIMPL;
}
static int flir_set_level(ROT *rot, setting_t level, value_t val)
{
return -RIG_ENIMPL;
}
static int flir_get_level(ROT *rot, setting_t level, value_t *val)
{
return -RIG_ENIMPL;
}
static int flir_set_ext_level(ROT *rot, token_t token, value_t val)
{
return -RIG_ENIMPL;
}
static int flir_get_ext_level(ROT *rot, token_t token, value_t *val)
{
return -RIG_ENIMPL;
}
static int flir_set_ext_func(ROT *rot, token_t token, int status)
{
return -RIG_ENIMPL;
}
static int flir_get_ext_func(ROT *rot, token_t token, int *status)
{
return -RIG_ENIMPL;
}
static int flir_set_parm(ROT *rot, setting_t parm, value_t val)
{
return -RIG_ENIMPL;
}
static int flir_get_parm(ROT *rot, setting_t parm, value_t *val)
{
return -RIG_ENIMPL;
}
static int flir_set_ext_parm(ROT *rot, token_t token, value_t val)
{
return -RIG_ENIMPL;
}
static int flir_get_ext_parm(ROT *rot, token_t token, value_t *val)
{
return -RIG_ENIMPL;
}
static int flir_get_status(ROT *rot, rot_status_t *status)
{
struct flir_priv_data *priv = (struct flir_priv_data *)
rot->state.priv;
*status = priv->status;
return RIG_OK;
}
/*
* flir rotator capabilities.
*/
struct rot_caps flir_caps =
{
ROT_MODEL(ROT_MODEL_FLIR),
.model_name = "PTU Serial",
.mfg_name = "FLIR",
.version = "20221126.0",
.copyright = "LGPL",
.status = RIG_STATUS_ALPHA,
.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 = 300,
.timeout = 400,
.retry = 3,
.min_az = -180.,
.max_az = 180.,
.min_el = 0.,
.max_el = 90.,
.priv = NULL, /* priv */
.has_get_func = FLIR_FUNC,
.has_set_func = FLIR_FUNC,
.has_get_level = FLIR_LEVEL,
.has_set_level = ROT_LEVEL_SET(FLIR_LEVEL),
.has_get_parm = FLIR_PARM,
.has_set_parm = ROT_PARM_SET(FLIR_PARM),
.level_gran = { [ROT_LVL_SPEED] = { .min = { .i = 1 }, .max = { .i = 4 }, .step = { .i = 1 } } },
.has_status = FLIR_STATUS,
.rot_init = flir_init,
.rot_cleanup = flir_cleanup,
.rot_open = flir_open,
.rot_close = flir_close,
.set_conf = flir_set_conf,
.get_conf = flir_get_conf,
.set_position = flir_set_position,
.get_position = flir_get_position,
.park = flir_park,
.stop = flir_stop,
.reset = flir_reset,
.move = flir_move,
.set_func = flir_set_func,
.get_func = flir_get_func,
.set_level = flir_set_level,
.get_level = flir_get_level,
.set_parm = flir_set_parm,
.get_parm = flir_get_parm,
.set_ext_func = flir_set_ext_func,
.get_ext_func = flir_get_ext_func,
.set_ext_level = flir_set_ext_level,
.get_ext_level = flir_get_ext_level,
.set_ext_parm = flir_set_ext_parm,
.get_ext_parm = flir_get_ext_parm,
.get_info = flir_get_info,
.get_status = flir_get_status,
};
DECLARE_INITROT_BACKEND(flir)
{
rig_debug(RIG_DEBUG_VERBOSE, "%s: _init called\n", __func__);
rot_register(&flir_caps);
rot_register(&netrotctl_caps);
return RIG_OK;
}

Wyświetl plik

@ -0,0 +1,46 @@
/*
* Hamlib FLIR PTU rotor backend - main header
* Copyright (c) 2022 by Andreas Mueller (DC1MIL)
*
*
* 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 _ROT_FLIR_H
#define _ROT_FLIR_H 1
#include "token.h"
/* backend conf */
#define TOK_CFG_ROT_MAGICCONF TOKEN_BACKEND(1)
#define TOK_CFG_ROT_STATIC_DATA TOKEN_BACKEND(2)
/* ext_level's and ext_parm's tokens */
#define TOK_EL_ROT_MAGICLEVEL TOKEN_BACKEND(1)
#define TOK_EL_ROT_MAGICFUNC TOKEN_BACKEND(2)
#define TOK_EL_ROT_MAGICOP TOKEN_BACKEND(3)
#define TOK_EP_ROT_MAGICPARM TOKEN_BACKEND(4)
#define TOK_EL_ROT_MAGICCOMBO TOKEN_BACKEND(5)
#define TOK_EL_ROT_MAGICEXTFUNC TOKEN_BACKEND(6)
#define MAXBUF 64
extern struct rot_caps flir_caps;
extern struct rot_caps netrotctl_caps;
#endif /* _ROT_FLIR_H */

Wyświetl plik

@ -90,6 +90,7 @@ DEFINE_INITROT_BACKEND(indi);
DEFINE_INITROT_BACKEND(androidsensor);
#endif
DEFINE_INITROT_BACKEND(grbltrk);
DEFINE_INITROT_BACKEND(flir);
//! @endcond
/**
@ -138,6 +139,7 @@ static struct
{ ROT_ANDROIDSENSOR, ROT_BACKEND_ANDROIDSENSOR, ROT_FUNCNAMA(androidsensor) },
#endif
{ ROT_GRBLTRK, ROT_BACKEND_GRBLTRK, ROT_FUNCNAMA(grbltrk) },
{ ROT_FLIR, ROT_BACKEND_FLIR, ROT_FUNCNAMA(flir) },
{ 0, NULL }, /* end */
};