Instrument Neutral Distributed Interface INDI  2.0.2
indi_rtlsdr.cpp
Go to the documentation of this file.
1 /*
2  indi_rtlsdr - a software defined radio driver for INDI
3  Copyright (C) 2017 Ilia Platone - Jasem Mutlaq
4  Collaborators:
5  - Ilia Platone <info@iliaplatone.com>
6  - Jasem Mutlaq - INDI library - <http://indilib.org>
7  - Monroe Pattillo - Fox Observatory - South Florida Amateur Astronomers Association <http://www.sfaaa.com/>
8 
9  This library is free software; you can redistribute it and/or
10  modify it under the terms of the GNU Lesser General Public
11  License as published by the Free Software Foundation; either
12  version 2 of the License, or (at your option) any later version.
13 
14  This library is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  Lesser General Public License for more details.
18 
19  You should have received a copy of the GNU Lesser General Public
20  License along with this library; if not, write to the Free Software
21  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 */
23 
24 #include "indi_rtlsdr.h"
25 #include <rtl-sdr.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <termios.h>
30 #include <indilogger.h>
31 #include <memory>
32 #include <deque>
33 #include <indicom.h>
34 
35 #define min(a, b) \
36  ({ \
37  __typeof__(a) _a = (a); \
38  __typeof__(b) _b = (b); \
39  _a < _b ? _a : _b; \
40  })
41 #define MAX_TRIES 20
42 #define SUBFRAME_SIZE (16384)
43 #define MIN_FRAME_SIZE (512)
44 #define MAX_FRAME_SIZE (SUBFRAME_SIZE * 16)
45 #define SPECTRUM_SIZE (256)
46 
47 static pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
48 static pthread_mutex_t condMutex = PTHREAD_MUTEX_INITIALIZER;
49 
50 void RTLSDR::Callback()
51 {
52  b_read = 0;
53  to_read = getSampleRate() * IntegrationRequest * getBPS() / 8;
55 
56  int len = min(MAX_FRAME_SIZE, to_read);
57  int olen = 0;
58  unsigned char *buf = (unsigned char *)malloc(len);
59  if((getSensorConnection() & CONNECTION_TCP) == 0)
60  rtlsdr_reset_buffer(rtl_dev);
61  else
62  tcflush(PortFD, TCOFLUSH);
63  setIntegrationTime(IntegrationRequest);
64  while (InIntegration)
65  {
66  if((getSensorConnection() & CONNECTION_TCP) == 0)
67  rtlsdr_read_sync(rtl_dev, buf, len, &olen);
68  else
69  olen = read(PortFD, buf, len);
70  if(olen < 0 )
72  else
73  {
74  buffer = buf;
75  n_read = olen;
76  grabData();
77  }
78  }
79 }
80 
81 static class Loader
82 {
83  std::deque<std::unique_ptr<RTLSDR>> receivers;
84  public:
85  Loader()
86  {
87  size_t numofConnectedReceivers = rtlsdr_get_device_count();
88  if (numofConnectedReceivers == 0)
89  {
90  //Try sending IDMessage as well?
91  IDLog("No USB RTLSDR receivers detected. Power on?");// Trying with TCP..");
92  IDMessage(nullptr, "No USB RTLSDR receivers detected. Power on?");// Trying with TCP..");
93  // receivers.push_back(std::unique_ptr<RTLSDR>(new RTLSDR(-1)));
94  return;
95  }
96 
97  for (size_t i = 0; i < numofConnectedReceivers; i++)
98  {
99  receivers.push_back(std::unique_ptr<RTLSDR>(new RTLSDR(i)));
100  }
101 
102  }
103 } loader;
104 
105 RTLSDR::RTLSDR(int32_t index)
106 {
107  InIntegration = false;
108  if(index < 0)
109  {
111  }
112 
113  receiverIndex = index;
114 
115  char name[MAXINDIDEVICE];
116  snprintf(name, MAXINDIDEVICE, "%s %s%c", getDefaultName(), index < 0 ? "TCP" : "USB", index < 0 ? '\0' : index + '1');
117  setDeviceName(name);
118 
119  // We set the Receiver capabilities
122 }
123 
125 {
126  if((getSensorConnection() & CONNECTION_TCP) == 0)
127  {
128  int r = rtlsdr_open(&rtl_dev, static_cast<uint32_t>(receiverIndex));
129  if (r < 0)
130  {
131  LOGF_ERROR("Failed to open rtlsdr device index %d.", receiverIndex);
132  return false;
133  }
134  }
135  return true;
136 }
137 /**************************************************************************************
138 ** Client is asking us to terminate connection to the device
139 ***************************************************************************************/
141 {
142  InIntegration = false;
143  if((getSensorConnection() & CONNECTION_TCP) == 0)
144  {
145  rtlsdr_close(rtl_dev);
146  }
147  PortFD = -1;
148 
149  setBufferSize(1);
150  pthread_mutex_lock(&condMutex);
151  streamPredicate = 1;
152  terminateThread = true;
153  pthread_cond_signal(&cv);
154  pthread_mutex_unlock(&condMutex);
155  LOG_INFO("RTL-SDR Receiver disconnected successfully!");
156  return true;
157 }
158 
159 /**************************************************************************************
160 ** INDI is asking us for our default device name
161 ***************************************************************************************/
163 {
164  return "RTL-SDR Receiver";
165 }
166 
167 /**************************************************************************************
168 ** INDI is asking us to init our properties.
169 ***************************************************************************************/
171 {
172  // Must init parent properties first!
174 
175  setMinMaxStep("SENSOR_INTEGRATION", "SENSOR_INTEGRATION_VALUE", 0.001, 600, 0.001, false);
176  setMinMaxStep("RECEIVER_SETTINGS", "RECEIVER_FREQUENCY", 2.4e+7, 2.0e+9, 1, false);
177  setMinMaxStep("RECEIVER_SETTINGS", "RECEIVER_SAMPLERATE", 2.5e+5, 2.0e+6, 2.5e+5, false);
178  setMinMaxStep("RECEIVER_SETTINGS", "RECEIVER_GAIN", 0.0, 25.0, 0.1, false);
179  setMinMaxStep("RECEIVER_SETTINGS", "RECEIVER_BANDWIDTH", 2.5e+5, 2.0e+6, 2.5e+5, false);
180  setMinMaxStep("RECEIVER_SETTINGS", "RECEIVER_BITSPERSAMPLE", 16, 16, 0, false);
181  setMinMaxStep("RECEIVER_SETTINGS", "RECEIVER_ANTENNA", 1, 1, 0, false);
183 
184  // Add Debug, Simulator, and Configuration controls
185  addAuxControls();
186 
188  return true;
189 }
190 
191 /********************************************************************************************
192 ** INDI is asking us to update the properties because there is a change in CONNECTION status
193 ** This fucntion is called whenever the device is connected or disconnected.
194 *********************************************************************************************/
196 {
197  // Call parent update properties first
199 
200  if (isConnected())
201  {
202  // Inital values
203  setupParams(1000000, 1420000000, 10);
204 
205  // Start the timer
207  }
208 
209  return true;
210 }
211 
212 /**************************************************************************************
213 ** Setting up Receiver parameters
214 ***************************************************************************************/
215 void RTLSDR::setupParams(float sr, float freq, float gain)
216 {
217  int r = 0;
218 
219  if((getSensorConnection() & CONNECTION_TCP) == 0)
220  {
221  r |= rtlsdr_set_tuner_gain_mode(rtl_dev, 1);
222  r |= rtlsdr_set_tuner_gain(rtl_dev, static_cast<int>(gain * 10));
223  r |= rtlsdr_set_center_freq(rtl_dev, static_cast<uint32_t>(freq));
224  r |= rtlsdr_set_sample_rate(rtl_dev, static_cast<uint32_t>(sr));
225  r |= rtlsdr_set_tuner_bandwidth(rtl_dev, static_cast<uint32_t>(sr));
226  if (r != 0)
227  {
228  LOG_INFO("Issue(s) setting parameters.");
229  }
230 
231  setBPS(16);
232  setGain(static_cast<double>(rtlsdr_get_tuner_gain(rtl_dev)) / 10.0);
233  setFrequency(static_cast<double>(rtlsdr_get_center_freq(rtl_dev)));
234  setSampleRate(static_cast<double>(rtlsdr_get_sample_rate(rtl_dev)));
235  setBandwidth(static_cast<double>(rtlsdr_get_sample_rate(rtl_dev)));
236  }
237  else
238  {
239  sendTcpCommand(CMD_SET_FREQ, static_cast<int>(freq));
240  sendTcpCommand(CMD_SET_SAMPLE_RATE, static_cast<int>(sr));
241  sendTcpCommand(CMD_SET_TUNER_GAIN_MODE, 0);
242  sendTcpCommand(CMD_SET_GAIN, static_cast<int>(gain * 10));
243  sendTcpCommand(CMD_SET_FREQ_COR, 0);
244  sendTcpCommand(CMD_SET_AGC_MODE, 0);
245  sendTcpCommand(CMD_SET_TUNER_GAIN_INDEX, 0);
246 
247  setBPS(16);
248  setGain(gain);
249  setFrequency(freq);
250  setSampleRate(sr);
251  setBandwidth(sr);
252  }
253 }
254 
255 bool RTLSDR::sendTcpCommand(int cmd, int value)
256 {
257  unsigned char tosend[5];
258  tosend[0] = static_cast<unsigned char>(cmd);
259  tosend[1] = value & 0xff;
260  value >>= 8;
261  tosend[2] = value & 0xff;
262  value >>= 8;
263  tosend[3] = value & 0xff;
264  value >>= 8;
265  tosend[4] = value & 0xff;
266  value >>= 8;
267  tcflush(PortFD, TCOFLUSH);
268  int count = 0;
269  while(count < 5)
270  {
271  count = write(PortFD, tosend, 5);
272  if (count < 0) return false;
273  }
274  return true;
275 }
276 
277 bool RTLSDR::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
278 {
279  bool r = false;
280  if (dev && !strcmp(dev, getDeviceName()) && !strcmp(name, ReceiverSettingsNP.name))
281  {
282  for(int i = 0; i < n; i++)
283  {
284  if (!strcmp(names[i], "RECEIVER_GAIN"))
285  {
286  setupParams(getSampleRate(), getFrequency(), values[i]);
287  }
288  else if (!strcmp(names[i], "RECEIVER_FREQUENCY"))
289  {
290  setupParams(getSampleRate(), values[i], getGain());
291  }
292  else if (!strcmp(names[i], "RECEIVER_SAMPLERATE"))
293  {
294  setupParams(values[i], getFrequency(), getGain());
295  setMinMaxStep("RECEIVER_SETTINGS", "RECEIVER_BANDWIDTH", getSampleRate(), getSampleRate(), getSampleRate(), false);
296  }
297  }
298  values[RECEIVER_GAIN] = getGain();
299  values[RECEIVER_BANDWIDTH] = getBandwidth();
300  values[RECEIVER_FREQUENCY] = getFrequency();
302  values[RECEIVER_BITSPERSAMPLE] = 16;
303  IUUpdateNumber(&ReceiverSettingsNP, values, names, n);
304  IDSetNumber(&ReceiverSettingsNP, nullptr);
305  }
306  return processNumber(dev, name, values, names, n) & !r;
307 }
308 
309 /**************************************************************************************
310 ** Client is asking us to start an exposure
311 ***************************************************************************************/
312 bool RTLSDR::StartIntegration(double duration)
313 {
314  IntegrationRequest = static_cast<float>(duration);
316 
317  LOG_INFO("Integration started...");
318  // Run threads
319  std::thread(&RTLSDR::Callback, this).detach();
320  gettimeofday(&IntStart, nullptr);
321  InIntegration = true;
322  return true;
323 
324  // We're done
325  return false;
326 }
327 
328 /**************************************************************************************
329 ** Client is asking us to abort a capture
330 ***************************************************************************************/
332 {
333  if (InIntegration)
334  {
335  InIntegration = false;
336  }
337  return true;
338 }
339 
340 /**************************************************************************************
341 ** How much longer until exposure is done?
342 ***************************************************************************************/
343 float RTLSDR::CalcTimeLeft()
344 {
345  float timesince;
346  float timeleft;
347  struct timeval now;
348  gettimeofday(&now, nullptr);
349 
350  timesince = (double)(now.tv_sec * 1000.0 + now.tv_usec / 1000) -
351  (double)(IntStart.tv_sec * 1000.0 + IntStart.tv_usec / 1000);
352  timesince = timesince / 1000;
353 
354  timeleft = IntegrationRequest - timesince;
355  return timeleft;
356 }
357 
358 /**************************************************************************************
359 ** Main device loop. We check for capture progress here
360 ***************************************************************************************/
362 {
363  long timeleft;
364 
365  if (isConnected() == false)
366  return; // No need to reset timer if we are not connected anymore
367 
368  if (InIntegration)
369  {
370  timeleft = static_cast<long>(CalcTimeLeft());
371  if (timeleft < 0.1)
372  {
373  /* We're done capturing */
374  LOG_INFO("Integration done, expecting data...");
375  timeleft = 0.0;
376  }
377 
378  // This is an over simplified timing method, check ReceiverSimulator and rtlsdrReceiver for better timing checks
379  setIntegrationLeft(timeleft);
380  }
381 
383  return;
384 }
385 
386 /**************************************************************************************
387 ** Create the spectrum
388 ***************************************************************************************/
390 {
391  if (InIntegration)
392  {
393  n_read = min(to_read, n_read);
394  continuum = getBuffer();
395  if (n_read > 0)
396  {
397  memcpy(continuum + b_read, buffer, n_read);
398  b_read += n_read;
399  to_read -= n_read;
400  }
401 
402  if (to_read <= 0)
403  {
404  InIntegration = false;
405  if(!streamPredicate)
406  {
407  LOG_INFO("Download complete.");
409  }
410  else
411  {
412  StartIntegration(1.0 / Streamer->getTargetFPS());
413  Streamer->newFrame(getBuffer(), getBufferSize());
414  }
415  }
416  }
417 }
418 
419 //Streamer API functions
420 
422 {
423  pthread_mutex_lock(&condMutex);
424  streamPredicate = 1;
425  StartIntegration(1.0 / Streamer->getTargetFPS());
426  pthread_mutex_unlock(&condMutex);
427  pthread_cond_signal(&cv);
428 
429  return true;
430 }
431 
433 {
434  pthread_mutex_lock(&condMutex);
435  streamPredicate = 0;
436  pthread_mutex_unlock(&condMutex);
437  pthread_cond_signal(&cv);
438 
439  return true;
440 }
441 
443 {
445  {
446  if(PortFD == -1)
447  {
448  LOG_ERROR("Failed to connect to rtl_tcp server.");
449  return false;
450  }
451  }
452 
453  streamPredicate = 0;
454  terminateThread = false;
455  LOG_INFO("RTL-SDR Receiver connected successfully!");
456  // Let's set a timer that checks teleReceivers status every POLLMS milliseconds.
457  // JM 2017-07-31 SetTimer already called in updateProperties(). Just call it once
458  //SetTimer(getCurrentPollingPeriod());
459 
460  return true;
461 }
462 
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
void setDeviceName(const char *dev)
Set the device name.
Definition: basedevice.cpp:815
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void setGain(double gain)
setGain Set gain of Receiver device.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
void setBandwidth(double bandwidth)
setBandwidth Set bandwidth of Receiver device.
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
virtual void setMinMaxStep(const char *property, const char *element, double min, double max, double step, bool sendToClient=true) override
setMinMaxStep for a number property element
void setFrequency(double freq)
setFrequency Set the frequency observed.
INumberVectorProperty ReceiverSettingsNP
Definition: indireceiver.h:190
double getSampleRate()
getSampleRate Get requested sample rate for the sensor in Hz.
Definition: indireceiver.h:141
double getFrequency()
getFrequency Get requested integration frequency for the sensor in Hz.
Definition: indireceiver.h:132
double getBandwidth()
getBandwidth Get requested integration bandwidth for the sensor in Hz.
Definition: indireceiver.h:114
void SetReceiverCapability(uint32_t cap)
SetReceiverCapability Set the Receiver capabilities. Al fields must be initialized.
void setSampleRate(double sr)
setSampleRate Set depth of Receiver device.
double getGain()
getGain Get requested integration gain for the sensor.
Definition: indireceiver.h:123
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
void setBPS(int bps)
setBPP Set depth of Sensor device.
void setBufferSize(int nbuf, bool allocMem=true)
setBufferSize Set desired buffer size. The function will allocate memory accordingly....
void setSensorConnection(const uint8_t &value)
setSensorConnection Set Sensor connection mode. Child class should call this in the constructor befor...
void setIntegrationLeft(double duration)
setIntegrationLeft Update Integration time left. Inform the client of the new Integration time left v...
uint8_t * getBuffer()
getBuffer Get raw buffer of the stream of the Sensor device.
std::unique_ptr< StreamManager > Streamer
void setIntegrationTime(double duration)
setIntegrationTime Set desired Sensor frame Integration duration for next Integration....
int PortFD
For Serial & TCP connections.
int getBufferSize() const
getContinuumBufferSize Get allocated continuum buffer size to hold the Sensor integrationd stream.
virtual bool IntegrationComplete()
Uploads target Device exposed buffer as FITS to the client. Dervied classes should class this functio...
int getBPS() const
getBPS Get Sensor depth (bits per sample).
void setIntegrationFileExtension(const char *ext)
setIntegrationExtension Set integration exntension
int to_read
Definition: indi_rtlsdr.h:44
bool InIntegration
Definition: indi_rtlsdr.h:46
bool AbortIntegration() override
Abort ongoing Integration.
RTLSDR(int32_t index)
bool StopStreaming() override
StopStreaming Stop live video streaming.
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
bool Disconnect() override
Disconnect from device.
rtlsdr_dev * rtl_dev
Definition: indi_rtlsdr.h:43
bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
uint8_t * buffer
Definition: indi_rtlsdr.h:47
void setupParams(float sr, float freq, float gain)
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
const char * getDefaultName() override
bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
bool Handshake() override
perform handshake with device to check communication
int n_read
Definition: indi_rtlsdr.h:48
void grabData()
bool StartIntegration(double duration) override
Start integration from the Sensor device.
int b_read
Definition: indi_rtlsdr.h:48
bool StartStreaming() override
StartStreaming Start live video streaming.
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
#define min(a, b)
Definition: indi_rtlsdr.cpp:35
#define MAX_FRAME_SIZE
Definition: indi_rtlsdr.cpp:44
#define MAXINDIDEVICE
Definition: indiapi.h:193
void IDLog(const char *fmt,...)
Definition: indicom.c:316
Implementations for common driver routines.
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDMessage(const char *dev, const char *fmt,...)
Definition: indidriver.c:960
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
#define LOG_INFO(txt)
Definition: indilogger.h:74
__u8 cmd[4]
Definition: pwc-ioctl.h:2
char name[MAXINDINAME]
Definition: indiapi.h:323