/* * Hamlib KIT backend - FUNcube Dongle USB tuner description * Copyright (c) 2009-2011 by Stephane Fillod * * Derived from usbsoftrock-0.5: * Copyright (C) 2009 Andrew Nilsson (andrew.nilsson@gmail.com) * * Author: Stefano Speretta, Innovative Solutions In Space BV * * 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 * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_STDINT_H #include #endif #include #include #include #include "hamlib/rig.h" #include "token.h" #include "kit.h" /* * Compile this model only if libusb is available */ #if defined(HAVE_LIBUSB) && defined(HAVE_LIBUSB_H) #include #include #include "funcube.h" static int funcube_init(RIG *rig); static int funcubeplus_init(RIG *rig); static int funcube_cleanup(RIG *rig); static int funcube_set_freq(RIG *rig, vfo_t vfo, freq_t freq); static int funcube_get_freq(RIG *rig, vfo_t vfo, freq_t *freq); static int funcube_set_level(RIG *rig, vfo_t vfo, setting_t level, value_t val); static int funcube_get_level(RIG *rig, vfo_t vfo, setting_t level, value_t *val); static const char *funcube_get_info(RIG *rig); static const struct confparams funcube_cfg_params[] = { { RIG_CONF_END, NULL, } }; // functions used set / read frequency, working on FUNcube version 0 and 1 int set_freq_v0(libusb_device_handle *udh, unsigned int f, int timeout); int set_freq_v1(libusb_device_handle *udh, unsigned int f, int timeout); /* * Common data struct */ struct funcube_priv_data { freq_t freq; /* Hz */ }; /* * FUNcube Dongle description * * Based on Jan Axelson HID examples * http://www.lvr.com/ * */ const struct rig_caps funcube_caps = { .rig_model = RIG_MODEL_FUNCUBEDONGLE, .model_name = "FUNcube Dongle", .mfg_name = "AMSAT-UK", .version = "0.3", .copyright = "GPL", .status = RIG_STATUS_BETA, .rig_type = RIG_TYPE_TUNER, .ptt_type = RIG_PTT_RIG, .dcd_type = RIG_DCD_NONE, .port_type = RIG_PORT_USB, .write_delay = 0, .post_write_delay = 0, .timeout = 1000, .retry = 0, .has_get_func = RIG_FUNC_NONE, .has_set_func = RIG_FUNC_NONE, .has_get_level = RIG_LEVEL_ATT | RIG_LEVEL_STRENGTH | RIG_LEVEL_PREAMP, .has_set_level = RIG_LEVEL_ATT | RIG_LEVEL_PREAMP, .has_get_parm = RIG_PARM_NONE, .has_set_parm = RIG_PARM_NONE, .level_gran = {}, .parm_gran = {}, .ctcss_list = NULL, .dcs_list = NULL, .preamp = { 5, 10, 15, 20, 25, 30, RIG_DBLST_END, }, .attenuator = { 2, 5, RIG_DBLST_END, }, .max_rit = Hz(0), .max_xit = Hz(0), .max_ifshift = Hz(0), .targetable_vfo = 0, .transceive = RIG_TRN_OFF, .bank_qty = 0, .chan_desc_sz = 0, .chan_list = { RIG_CHAN_END, }, .rx_range_list1 = { {MHz(50),MHz(2500),RIG_MODE_USB,-1,-1,RIG_VFO_A}, RIG_FRNG_END, }, .tuning_steps = { {RIG_MODE_USB,kHz(1)}, RIG_TS_END, }, .filters = { {RIG_MODE_USB, kHz(192)}, RIG_FLT_END, }, .cfgparams = funcube_cfg_params, .rig_init = funcube_init, .rig_cleanup = funcube_cleanup, .set_freq = funcube_set_freq, .get_freq = funcube_get_freq, .get_level = funcube_get_level, .set_level = funcube_set_level, .get_info = funcube_get_info, }; const struct rig_caps funcubeplus_caps = { .rig_model = RIG_MODEL_FUNCUBEDONGLEPLUS, .model_name = "FUNcube Dongle Pro+", .mfg_name = "AMSAT-UK", .version = "0.3", .copyright = "GPL", .status = RIG_STATUS_BETA, .rig_type = RIG_TYPE_TUNER, .ptt_type = RIG_PTT_RIG, .dcd_type = RIG_DCD_NONE, .port_type = RIG_PORT_USB, .write_delay = 0, .post_write_delay = 0, .timeout = 1000, .retry = 0, .has_get_func = RIG_FUNC_NONE, .has_set_func = RIG_FUNC_NONE, .has_get_level = RIG_LEVEL_ATT | RIG_LEVEL_STRENGTH | RIG_LEVEL_PREAMP, .has_set_level = RIG_LEVEL_ATT | RIG_LEVEL_PREAMP, .has_get_parm = RIG_PARM_NONE, .has_set_parm = RIG_PARM_NONE, .level_gran = {}, .parm_gran = {}, .ctcss_list = NULL, .dcs_list = NULL, .preamp = { 5, 10, 15, 20, 25, 30, RIG_DBLST_END, }, .attenuator = { 2, 5, RIG_DBLST_END, }, .max_rit = Hz(0), .max_xit = Hz(0), .max_ifshift = Hz(0), .targetable_vfo = 0, .transceive = RIG_TRN_OFF, .bank_qty = 0, .chan_desc_sz = 0, .chan_list = { RIG_CHAN_END, }, .rx_range_list1 = { {kHz(150),MHz(1900),RIG_MODE_USB,-1,-1,RIG_VFO_A}, RIG_FRNG_END, }, .tuning_steps = { {RIG_MODE_USB,kHz(1)}, RIG_TS_END, }, .filters = { {RIG_MODE_USB, kHz(192)}, RIG_FLT_END, }, .cfgparams = funcube_cfg_params, .rig_init = funcubeplus_init, .rig_cleanup = funcube_cleanup, .set_freq = funcube_set_freq, .get_freq = funcube_get_freq, .get_level = funcube_get_level, .set_level = funcube_set_level, .get_info = funcube_get_info, }; int funcube_init(RIG *rig) { hamlib_port_t *rp = &rig->state.rigport; struct funcube_priv_data *priv; priv = (struct funcube_priv_data*)calloc(sizeof(struct funcube_priv_data), 1); if (!priv) { /* whoops! memory shortage! */ return -RIG_ENOMEM; } priv->freq = 0; rp->parm.usb.vid = VID; rp->parm.usb.pid = PID; rp->parm.usb.conf = FUNCUBE_CONFIGURATION; rp->parm.usb.iface = FUNCUBE_INTERFACE; rp->parm.usb.alt = FUNCUBE_ALTERNATIVE_SETTING; rp->parm.usb.vendor_name = VENDOR_NAME; rp->parm.usb.product = PRODUCT_NAME; rig->state.priv = (void*)priv; return RIG_OK; } int funcubeplus_init(RIG *rig) { hamlib_port_t *rp = &rig->state.rigport; struct funcube_priv_data *priv; priv = (struct funcube_priv_data*)calloc(sizeof(struct funcube_priv_data), 1); if (!priv) { /* whoops! memory shortage! */ return -RIG_ENOMEM; } priv->freq = 0; rp->parm.usb.vid = VID; rp->parm.usb.pid = PIDPLUS; rp->parm.usb.conf = FUNCUBE_CONFIGURATION; rp->parm.usb.iface = FUNCUBE_INTERFACE; rp->parm.usb.alt = FUNCUBE_ALTERNATIVE_SETTING; rp->parm.usb.vendor_name = VENDOR_NAME; rp->parm.usb.product = PRODUCT_NAMEPLUS; rig->state.priv = (void*)priv; return RIG_OK; } int funcube_cleanup(RIG *rig) { if (!rig) return -RIG_EINVAL; if (rig->state.priv) free(rig->state.priv); rig->state.priv = NULL; return RIG_OK; } /* Rem: not reentrant */ const char * funcube_get_info(RIG *rig) { static char buf[64]; libusb_device_handle *udh = rig->state.rigport.handle; struct libusb_device_descriptor desc; /* always succeeds since libusb-1.0.16 */ libusb_get_device_descriptor(libusb_get_device(udh), &desc); sprintf(buf, "Dev %04d", desc.bcdDevice); return buf; } int set_freq_v0(libusb_device_handle *udh, unsigned int f, int timeout) { int ret; int actual_length; unsigned char au8BufOut[64]; // endpoint size unsigned char au8BufIn[64]; // endpoint size // frequency is in Hz, while the dongle expects it in kHz f = f / 1000; au8BufOut[0]=REQUEST_SET_FREQ; // Command to Set Frequency on dongle au8BufOut[1]=(unsigned char)f; au8BufOut[2]=(unsigned char)(f>>8); au8BufOut[3]=(unsigned char)(f>>16); rig_debug(RIG_DEBUG_TRACE, "%s: HID packet set to %02x%02x%02x%02x\n", __func__, au8BufOut[0] & 0xFF, au8BufOut[1] & 0xFF, au8BufOut[2] & 0xFF, au8BufOut[3] & 0xFF); ret = libusb_interrupt_transfer(udh, OUTPUT_ENDPOINT, au8BufOut, sizeof(au8BufOut), &actual_length, timeout); if( ret < 0 ) { rig_debug (RIG_DEBUG_ERR, "%s: libusb_interrupt_transfer failed (%d): %s\n", __func__,ret, libusb_error_name (ret)); return -RIG_EIO; } ret = libusb_interrupt_transfer(udh, INPUT_ENDPOINT, au8BufIn, sizeof(au8BufIn), &actual_length, timeout); if( ret < 0 || actual_length != sizeof(au8BufIn) ) { rig_debug (RIG_DEBUG_ERR, "%s: libusb_interrupt_transfer failed (%d): %s\n", __func__, ret, libusb_error_name (ret)); return -RIG_EIO; } rig_debug(RIG_DEBUG_TRACE, "%s: Answer buf=%02x%02x\n", __func__, au8BufIn[0] & 0xFF, au8BufIn[1] & 0xFF); if (au8BufIn[1] != FUNCUBE_SUCCESS) { rig_debug (RIG_DEBUG_ERR, "%s: REQUEST_SET_FREQ not supported\n", __func__); return -RIG_EIO; } return RIG_OK; } int set_freq_v1(libusb_device_handle *udh, unsigned int f, int timeout) { int ret; int actual_length; unsigned char au8BufOut[64]; // endpoint size unsigned char au8BufIn[64]; // endpoint size au8BufOut[0]=REQUEST_SET_FREQ_HZ; // Command to Set Frequency in Hz on dongle au8BufOut[1]=(unsigned char)f; au8BufOut[2]=(unsigned char)(f>>8); au8BufOut[3]=(unsigned char)(f>>16); au8BufOut[4]=(unsigned char)(f>>24); rig_debug(RIG_DEBUG_TRACE, "%s: HID packet set to %02x%02x%02x%02x%02x\n", __func__, au8BufOut[0] & 0xFF, au8BufOut[1] & 0xFF, au8BufOut[2] & 0xFF, au8BufOut[3] & 0xFF, au8BufOut[4] & 0xFF); ret = libusb_interrupt_transfer(udh, OUTPUT_ENDPOINT, au8BufOut, sizeof(au8BufOut), &actual_length, timeout); if( ret < 0 ) { rig_debug (RIG_DEBUG_ERR, "%s: libusb_interrupt_transfer failed (%d): %s\n", __func__,ret, libusb_error_name (ret)); return -RIG_EIO; } ret = libusb_interrupt_transfer(udh, INPUT_ENDPOINT, au8BufIn, sizeof(au8BufIn), &actual_length, timeout); if( ret < 0 || actual_length != sizeof(au8BufIn) ) { rig_debug (RIG_DEBUG_ERR, "%s: libusb_interrupt_transfer failed (%d): %s\n", __func__, ret, libusb_error_name (ret)); return -RIG_EIO; } rig_debug(RIG_DEBUG_TRACE, "%s: Answer buf=%02x%02x%02x%02x%02x%02x\n", __func__, au8BufIn[0] & 0xFF, au8BufIn[1] & 0xFF, au8BufIn[2] & 0xFF, au8BufIn[3] & 0xFF, au8BufIn[4] & 0xFF, au8BufIn[5] & 0xFF); if (au8BufIn[1] != FUNCUBE_SUCCESS) { rig_debug (RIG_DEBUG_ERR, "%s: REQUEST_SET_FREQ_HZ not supported\n", __func__); return -RIG_EIO; } return RIG_OK; } int funcube_set_freq(RIG *rig, vfo_t vfo, freq_t freq) { struct funcube_priv_data *priv = (struct funcube_priv_data *)rig->state.priv; libusb_device_handle *udh = rig->state.rigport.handle; int ret; if ((ret = set_freq_v1(udh, freq, rig->state.rigport.timeout)) != RIG_OK) { if ((ret = set_freq_v0(udh, freq, rig->state.rigport.timeout)) == RIG_OK) { priv->freq = freq; } } else { priv->freq = freq; } return ret; } int get_freq_v0(RIG *rig, vfo_t vfo, freq_t *freq) { struct funcube_priv_data *priv = (struct funcube_priv_data *)rig->state.priv; rig_debug(RIG_DEBUG_TRACE, "%s: frequency is not read from the device, the value shown is the last successfully set.\n",__func__); *freq = priv->freq; return RIG_OK; } int get_freq_v1(RIG *rig, vfo_t vfo, freq_t *freq) { libusb_device_handle *udh = rig->state.rigport.handle; int ret; unsigned int f; int actual_length; unsigned char au8BufOut[64]; // endpoint size unsigned char au8BufIn[64]; // endpoint size au8BufOut[0]=REQUEST_GET_FREQ_HZ; // Command to Set Frequency on dongle rig_debug(RIG_DEBUG_TRACE, "%s: HID packet set to %02x%02x%02x%02x\n", __func__, au8BufOut[0] & 0xFF, au8BufOut[1] & 0xFF, au8BufOut[2] & 0xFF, au8BufOut[3] & 0xFF); ret = libusb_interrupt_transfer(udh, OUTPUT_ENDPOINT, au8BufOut, sizeof(au8BufOut), &actual_length, rig->state.rigport.timeout); if( ret < 0 ) { rig_debug (RIG_DEBUG_ERR, "%s: libusb_interrupt_transfer failed (%d): %s\n", __func__,ret, libusb_error_name (ret)); } ret = libusb_interrupt_transfer(udh, INPUT_ENDPOINT, au8BufIn, sizeof(au8BufIn), &actual_length, rig->state.rigport.timeout); if( ret < 0 || actual_length != sizeof(au8BufIn) ) { rig_debug (RIG_DEBUG_ERR, "%s: libusb_interrupt_transfer failed (%d): %s\n", __func__, ret, libusb_error_name (ret)); } rig_debug(RIG_DEBUG_TRACE, "%s: Answer buf=%02x%02x%02x%02x%02x%02x\n", __func__, au8BufIn[0] & 0xFF, au8BufIn[1] & 0xFF, au8BufIn[2] & 0xFF, au8BufIn[3] & 0xFF, au8BufIn[4] & 0xFF, au8BufIn[5] & 0xFF); if (au8BufIn[1] != FUNCUBE_SUCCESS) { rig_debug (RIG_DEBUG_ERR, "%s: REQUEST_GET_FREQ_HZ not supported\n", __func__); return -RIG_EIO; } f = (au8BufIn[2] & 0xFF) | ((au8BufIn[3] & 0xFF) << 8) | ((au8BufIn[4] & 0xFF) << 16) | ((au8BufIn[5] & 0xFF) << 24), *freq = f; return RIG_OK; } int funcube_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) { int ret; if ((ret = get_freq_v1(rig, vfo, freq)) != RIG_OK) { ret = get_freq_v0(rig, vfo, freq); } return ret; } int funcube_set_level(RIG *rig, vfo_t vfo, setting_t level, value_t val) { libusb_device_handle *udh = rig->state.rigport.handle; int ret; int actual_length; unsigned char au8BufOut[64]; // endpoint size unsigned char au8BufIn[64]; // endpoint size switch (level) { case RIG_LEVEL_PREAMP: au8BufOut[0]=REQUEST_SET_LNA_GAIN; // Command to Set LNA gain switch (val.i) { case 5: au8BufOut[1]=6; break; case 10: au8BufOut[1]=8; break; case 15: au8BufOut[1]=10; break; case 20: au8BufOut[1]=12; break; case 25: au8BufOut[1]=13; break; case 30: au8BufOut[1]=14; break; default: au8BufOut[1]=4; } break; case RIG_LEVEL_ATT: au8BufOut[0]=REQUEST_SET_LNA_GAIN; // Command to Set LNA gain switch (val.i) { case 2: au8BufOut[1]=1; break; case 5: au8BufOut[1]=0; break; default: au8BufOut[1]=4; } break; default: rig_debug(RIG_DEBUG_ERR,"%s: Unsupported level %d\n", __func__, level); return -RIG_EINVAL; } rig_debug(RIG_DEBUG_TRACE, "%s: HID packet set to %02x%02x%02x%02x\n", __func__, au8BufOut[0] & 0xFF, au8BufOut[1] & 0xFF, au8BufOut[2] & 0xFF, au8BufOut[3] & 0xFF); ret = libusb_interrupt_transfer(udh, OUTPUT_ENDPOINT, au8BufOut, sizeof(au8BufOut), &actual_length, rig->state.rigport.timeout); if( ret < 0 ) { rig_debug (RIG_DEBUG_ERR, "%s: libusb_interrupt_transfer failed (%d): %s\n", __func__,ret, libusb_error_name (ret)); } ret = libusb_interrupt_transfer(udh, INPUT_ENDPOINT, au8BufIn, sizeof(au8BufIn), &actual_length, rig->state.rigport.timeout); if( ret < 0 || actual_length != sizeof(au8BufIn) ) { rig_debug (RIG_DEBUG_ERR, "%s: libusb_interrupt_transfer failed (%d): %s\n", __func__, ret, libusb_error_name (ret)); } rig_debug(RIG_DEBUG_TRACE, "%s: Answer buf=%02x%02x\n", __func__, au8BufIn[0] & 0xFF, au8BufIn[1] & 0xFF); if (au8BufIn[1] != FUNCUBE_SUCCESS) { rig_debug (RIG_DEBUG_ERR, "%s: REQUEST_GET_FREQ_HZ not supported\n", __func__); return -RIG_EIO; } return RIG_OK; } int funcube_get_level(RIG *rig, vfo_t vfo, setting_t level, value_t *val) { libusb_device_handle *udh = rig->state.rigport.handle; int ret; int actual_length; unsigned char au8BufOut[64]; // endpoint size unsigned char au8BufIn[64]; // endpoint size switch (level) { case RIG_LEVEL_ATT: case RIG_LEVEL_PREAMP: au8BufOut[0]=REQUEST_GET_LNA_GAIN; // Command to Get LNA / ATT gain break; case RIG_LEVEL_STRENGTH: au8BufOut[0]=REQUEST_GET_RSSI; break; default: rig_debug(RIG_DEBUG_ERR,"%s: Unsupported level %d\n", __func__, level); return -RIG_EINVAL; } rig_debug(RIG_DEBUG_TRACE, "%s: HID packet set to %02x%02x%02x%02x\n", __func__, au8BufOut[0] & 0xFF, au8BufOut[1] & 0xFF, au8BufOut[2] & 0xFF, au8BufOut[3] & 0xFF); ret = libusb_interrupt_transfer(udh, OUTPUT_ENDPOINT, au8BufOut, sizeof(au8BufOut), &actual_length, rig->state.rigport.timeout); if( ret < 0 ) { rig_debug (RIG_DEBUG_ERR, "%s: libusb_interrupt_transfer failed (%d): %s\n", __func__,ret, libusb_error_name (ret)); } ret = libusb_interrupt_transfer(udh, INPUT_ENDPOINT, au8BufIn, sizeof(au8BufIn), &actual_length, rig->state.rigport.timeout); if( ret < 0 || actual_length != sizeof(au8BufIn) ) { rig_debug (RIG_DEBUG_ERR, "%s: libusb_interrupt_transfer failed (%d): %s\n", __func__, ret, libusb_error_name (ret)); } rig_debug(RIG_DEBUG_TRACE, "%s: Answer buf=%02x%02x%02x\n", __func__, au8BufIn[0] & 0xFF, au8BufIn[1] & 0xFF, au8BufIn[2] & 0xFF); if (au8BufIn[1] != FUNCUBE_SUCCESS) { rig_debug (RIG_DEBUG_ERR, "%s: REQUEST_GET_FREQ_HZ not supported\n", __func__); return -RIG_EIO; } switch (level) { case RIG_LEVEL_PREAMP: switch (au8BufIn[2]) { case 6: val->i = 5; break; case 8: val->i = 10; break; case 10: val->i = 15; break; case 12: val->i = 20; break; case 13: val->i = 25; break; case 14: val->i = 30; break; default: val->i = 0; } break; case RIG_LEVEL_ATT: switch (au8BufIn[2]) { case 0: val->i = 5; break; case 1: val->i = 2; break; default: val->i = 0; } break; case RIG_LEVEL_STRENGTH: val->i = (int)((float)au8BufIn[2] * 2.8 - 35); break; default: rig_debug(RIG_DEBUG_ERR,"%s: Unsupported level %d\n", __func__, level); return -RIG_EINVAL; } return RIG_OK; } #endif /* defined(HAVE_LIBUSB) && defined(HAVE_LIBUSB_H) */