From 8ff1ba25a1a020626547ca62c5fb97ad85859a69 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 4 Jun 2015 17:29:18 -0300 Subject: [PATCH 001/117] rtl_ais.c stereo.bl_len now computes fine on PC. Droop compensation added. Change default samplerate to 24k, audio data drops 3db about 8khz (still low). It decode AIS mesages piping data to aisdecoder. Works fine on Windows, but aisdecoder must be recoded to accept data from standard input and, set stdin mode to binary. --- ais/rtl_ais.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/ais/rtl_ais.c b/ais/rtl_ais.c index 5389500..224eead 100644 --- a/ais/rtl_ais.c +++ b/ais/rtl_ais.c @@ -35,8 +35,11 @@ #include #include #include - #include +#ifdef WIN32 + #include +#endif + #include #include @@ -77,6 +80,10 @@ struct downsample_state int16_t lp_i_hist[10][6]; int16_t lp_q_hist[10][6]; pthread_rwlock_t rw; + //droop compensation + int16_t droop_i_hist[9]; + int16_t droop_q_hist[9]; + }; struct demod_state @@ -88,6 +95,7 @@ struct demod_state int now_r, now_j; int pre_r, pre_j; int dc_avg; // really should get its own struct + }; struct upsample_stereo @@ -122,7 +130,7 @@ void usage(void) "\t[-r right_frequency (default: 162.025M)]\n" "\t left freq < right freq\n" "\t frequencies must be within 1.2MHz\n" - "\t[-s sample_rate (default: 12k)]\n" + "\t[-s sample_rate (default: 24k)]\n" "\t minimum value, might be up to 2x specified\n" "\t[-o output_rate (default: 48k)]\n" "\t must be equal or greater than twice -s value\n" @@ -146,6 +154,20 @@ static void sighandler(int signum) do_exit = 1; rtlsdr_cancel_async(dev); } +int cic_9_tables[][10] = { + {0,}, + {9, -156, -97, 2798, -15489, 61019, -15489, 2798, -97, -156}, + {9, -128, -568, 5593, -24125, 74126, -24125, 5593, -568, -128}, + {9, -129, -639, 6187, -26281, 77511, -26281, 6187, -639, -129}, + {9, -122, -612, 6082, -26353, 77818, -26353, 6082, -612, -122}, + {9, -120, -602, 6015, -26269, 77757, -26269, 6015, -602, -120}, + {9, -120, -582, 5951, -26128, 77542, -26128, 5951, -582, -120}, + {9, -119, -580, 5931, -26094, 77505, -26094, 5931, -580, -119}, + {9, -119, -578, 5921, -26077, 77484, -26077, 5921, -578, -119}, + {9, -119, -577, 5917, -26067, 77473, -26067, 5917, -577, -119}, + {9, -199, -362, 5303, -25505, 77489, -25505, 5303, -362, -199}, +}; + void rotate_90(int16_t *buf, int len) /* 90 rotation is 1+0j, 0+1j, -1+0j, 0-1j @@ -218,14 +240,43 @@ void fifth_order(int16_t *data, int length, int16_t *hist) hist[5] = f; } +void generic_fir(int16_t *data, int length, int *fir, int16_t *hist) +/* Okay, not at all generic. Assumes length 9, fix that eventually. */ +{ + int d, temp, sum; + for (d=0; d> 15 ; + hist[0] = hist[1]; + hist[1] = hist[2]; + hist[2] = hist[3]; + hist[3] = hist[4]; + hist[4] = hist[5]; + hist[5] = hist[6]; + hist[6] = hist[7]; + hist[7] = hist[8]; + hist[8] = temp; + } +} + void downsample(struct downsample_state *d) { int i, ds_p; ds_p = d->downsample_passes; - for (i=0; ibuf, (d->len_in >> i), d->lp_i_hist[i]); fifth_order(d->buf+1, (d->len_in >> i)-1, d->lp_q_hist[i]); } + // droop compensation + generic_fir(d->buf, d->len_in >> ds_p,cic_9_tables[ds_p], d->droop_i_hist); + generic_fir(d->buf+1, (d->len_in>> ds_p)-1,cic_9_tables[ds_p], d->droop_q_hist); } void multiply(int ar, int aj, int br, int bj, int *cr, int *cj) @@ -280,6 +331,7 @@ void demodulate(struct demod_state *d) int16_t *result = d->result; pcm = polar_disc_fast(buf[0], buf[1], d->pre_r, d->pre_j); + result[0] = (int16_t)pcm; for (i = 2; i < (d->buf_len-1); i += 2) { // add the other atan types? @@ -370,6 +422,7 @@ static void *demod_thread_fn(void *arg) dc_block_filter(&left_demod);} //if (oversample) { // downsample(&left);} + //fprintf(stderr,"\nUpsample result_len:%d stereo.bl_len:%d :%f\n",left_demod.result_len,stereo.bl_len,(float)stereo.bl_len/(float)left_demod.result_len); arbitrary_upsample(left_demod.result, stereo.buf_left, left_demod.result_len, stereo.bl_len); rotate_m90(right.buf, right.len_in); downsample(&right); @@ -392,6 +445,7 @@ void downsample_init(struct downsample_state *dss) int i, j; dss->buf = malloc(dss->len_in * sizeof(int16_t)); dss->rate_out = dss->rate_in / dss->downsample; + //dss->downsample_passes = (int)log2(dss->downsample); dss->len_out = dss->len_in / dss->downsample; for (i=0; i<10; i++) { for (j=0; j<6; j++) { @@ -416,7 +470,9 @@ void stereo_init(struct upsample_stereo *us) int main(int argc, char **argv) { +#ifndef WIN32 struct sigaction sigact; +#endif char *filename = NULL; int r, opt; int i, gain = AUTO_GAIN; /* tenths of a dB */ @@ -426,7 +482,7 @@ int main(int argc, char **argv) int custom_ppm = 0; int left_freq = 161975000; int right_freq = 162025000; - int sample_rate = 12000; + int sample_rate = 24000; int output_rate = 48000; int dongle_freq, dongle_rate, delta; int edge = 0; @@ -550,7 +606,8 @@ int main(int argc, char **argv) left_demod.result_len = left_demod.buf_len / 2; right_demod.buf_len = left_demod.buf_len; right_demod.result_len = left_demod.result_len; - stereo.bl_len = (int)((long)(DEFAULT_BUF_LENGTH/2) * (long)output_rate / (long)dongle_rate); +// stereo.bl_len = (int)((long)(DEFAULT_BUF_LENGTH/2) * (long)output_rate / (long)dongle_rate); -> Doesn't work on Linux + stereo.bl_len = (int)((double)(DEFAULT_BUF_LENGTH/2) * (double)output_rate / (double)dongle_rate); stereo.br_len = stereo.bl_len; stereo.result_len = stereo.br_len * 2; stereo.rate = output_rate; @@ -575,6 +632,7 @@ int main(int argc, char **argv) fprintf(stderr, "Failed to open rtlsdr device #%d.\n", dev_index); exit(1); } +#ifndef WIN32 sigact.sa_handler = sighandler; sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; @@ -582,9 +640,15 @@ int main(int argc, char **argv) sigaction(SIGTERM, &sigact, NULL); sigaction(SIGQUIT, &sigact, NULL); sigaction(SIGPIPE, &sigact, NULL); - +#else + signal(SIGINT, sighandler); + signal(SIGTERM, sighandler); +#endif if (strcmp(filename, "-") == 0) { /* Write samples to stdout */ file = stdout; +#ifdef WIN32 + setmode(fileno(stdout), O_BINARY); // Binary mode, avoid text mode +#endif setvbuf(stdout, NULL, _IONBF, 0); } else { file = fopen(filename, "wb"); @@ -593,7 +657,6 @@ int main(int argc, char **argv) exit(1); } } - /* Set the tuner gain */ if (gain == AUTO_GAIN) { verbose_auto_gain(dev); @@ -605,7 +668,9 @@ int main(int argc, char **argv) if (!custom_ppm) { verbose_ppm_eeprom(dev, &ppm_error); } + verbose_ppm_set(dev, ppm_error); + //r = rtlsdr_set_agc_mode(dev, 1); /* Set the tuner frequency */ From d2c44ed9560bae42a73c74de468c821415e7a1ce Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:45:17 -0300 Subject: [PATCH 002/117] compile aisdecoder files too --- ais/build.sh | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/ais/build.sh b/ais/build.sh index 4497f52..9da0a99 100755 --- a/ais/build.sh +++ b/ais/build.sh @@ -1,12 +1,28 @@ #!/bin/sh - # todo, a real makefile -files="rtl_ais.c convenience.c" -flags="-Wall -O2" -includes="-I/usr/include/libusb-1.0" -libs="-lusb-1.0 -lrtlsdr -lpthread -lm" +#point this to the correct path +RTLSDR_PATH="../../src" + +files="$RTLSDR_PATH/rtl_ais.c $RTLSDR_PATH/convenience/convenience.c \ + $RTLSDR_PATH/aisdecoder/aisdecoder.c $RTLSDR_PATH/aisdecoder/sounddecoder.c \ + $RTLSDR_PATH/aisdecoder/lib/receiver.c + $RTLSDR_PATH/aisdecoder/lib/protodec.c + $RTLSDR_PATH/aisdecoder/lib/hmalloc.c + $RTLSDR_PATH/aisdecoder/lib/filter.c " + +flags="-Wall -O2 " +includes="-I/usr/include/libusb-1.0 -I../../include -I ../../src/convenience -I ../../src/aisdecoder -I ../../src/aisdecoder/lib" +libs="-L/usr/lib -L. -lusb-1.0 -lrtlsdr -lpthread -lm " + +UNAME=$(uname) +if [ "$UNAME" != "Linux" ] +then +# Conditional section for Windows + libs="$libs -lWs2_32" +fi rm -f rtl_ais -gcc -o rtl_ais $files $flags $includes $libs +echo gcc -o rtl_ais $files $flags $includes $libs +gcc -o rtl_ais $files $flags $includes $libs From cf11901a7803794adbf4b70bfbbb0f6aa3731ca3 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:47:07 -0300 Subject: [PATCH 003/117] use built-in aisdecoder --- ais/rtl_ais.c | 138 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 115 insertions(+), 23 deletions(-) diff --git a/ais/rtl_ais.c b/ais/rtl_ais.c index 224eead..3730030 100644 --- a/ais/rtl_ais.c +++ b/ais/rtl_ais.c @@ -46,6 +46,7 @@ #include #include "convenience.h" +#include "aisdecoder/aisdecoder.h" #define DEFAULT_ASYNC_BUF_NUMBER 12 #define DEFAULT_BUF_LENGTH (16 * 16384) @@ -63,7 +64,8 @@ int merged_len; FILE *file; int oversample = 0; int dc_filter = 1; - +int use_internal_aisdecoder=1; +int seconds_for_decoder_stats=0; /* signals are not threadsafe by default */ #define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m) #define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m) @@ -131,7 +133,7 @@ void usage(void) "\t left freq < right freq\n" "\t frequencies must be within 1.2MHz\n" "\t[-s sample_rate (default: 24k)]\n" - "\t minimum value, might be up to 2x specified\n" + "\t maximum value, might be down to 12k\n" "\t[-o output_rate (default: 48k)]\n" "\t must be equal or greater than twice -s value\n" "\t[-E toggle edge tuning (default: off)]\n" @@ -140,10 +142,25 @@ void usage(void) "\t[-d device_index (default: 0)]\n" "\t[-g tuner_gain (default: automatic)]\n" "\t[-p ppm_error (default: 0)]\n" - "\tfilename (a '-' dumps samples to stdout)\n" + "\t[-R enable RTL chip AGC (default: off)]\n" + "\t[-A turn off built-in AIS decoder (default: on)]\n" + "\t use this option to output samples to file or stdout.\n" + "\tBuilt-in AIS decoder options:\n" + "\t[-h host (default: localhost)]\n" + "\t[-P port (default: 10110)]\n" + "\t[-n log NMEA sentences to console (stderr) (default off)]\n" + "\t[-L log sound levels to console (stderr) (default off)]\n\n" + "\t[-S seconds_for_decoder_stats (default 0=off)]\n\n" + "\tWhen the built-in AIS decoder is disabled the samples are sent to\n" + "\tto [outputfile] (a '-' dumps samples to stdout)\n" "\t omitting the filename also uses stdout\n\n" "\tOutput is stereo 2x16 bit signed ints\n\n" - "rtl_ais | play -t raw -r 48k -es -b 16 -c 2 -V1 -\n" + "\tExmaples:\n" + "\tReceive AIS traffic,sent UDP NMEA sentences to localhost port 10110\n" + "\t and log the senteces to console:\n\n" + "\trtl_ais -n\n\n" + "\tTune two fm stations and play one on each channel:\n\n" + "\trtl_ais -l233.15M -r233.20M -A | play -r48k -traw -es -b16 -c2 -V1 - " "\n"); exit(1); } @@ -395,13 +412,16 @@ static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) safe_cond_signal(&ready, &ready_m); } -void output(void) +void pre_output(void) { int i; for (i=0; i int_16 + // stereo.result_len -> number of samples for each channel + run_rtlais_decoder(stereo.result,stereo.result_len); + } + else{ + output(); + } } rtlsdr_cancel_async(dev); + free_ais_decoder(); return 0; } @@ -479,6 +508,7 @@ int main(int argc, char **argv) int dev_index = 0; int dev_given = 0; int ppm_error = 0; + int rtl_agc=0; int custom_ppm = 0; int left_freq = 161975000; int right_freq = 162025000; @@ -486,10 +516,16 @@ int main(int argc, char **argv) int output_rate = 48000; int dongle_freq, dongle_rate, delta; int edge = 0; +/* Aisdecoder */ + int show_levels=0; + int debug_nmea = 0; + char * port=NULL; + char * host=NULL; + pthread_cond_init(&ready, NULL); pthread_mutex_init(&ready_m, NULL); - while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:h")) != -1) + while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RAP:h:nLS:?")) != -1) { switch (opt) { case 'l': @@ -524,7 +560,28 @@ int main(int argc, char **argv) ppm_error = atoi(optarg); custom_ppm = 1; break; + case 'R': + rtl_agc=1; + break; + case 'A': + use_internal_aisdecoder=0; + break; + case 'P': + port=strdup(optarg); + break; case 'h': + host=strdup(optarg); + break; + case 'L': + show_levels=1; + break; + case 'S': + seconds_for_decoder_stats=atoi(optarg); + break; + case 'n': + debug_nmea = 1; + break; + case '?': default: usage(); return 2; @@ -541,7 +598,13 @@ int main(int argc, char **argv) usage(); return 2; } - + if(host==NULL){ + host=strdup("localhost"); + } + if(port==NULL){ + port=strdup("10110"); + } + /* precompute rates */ dongle_freq = left_freq/2 + right_freq/2; if (edge) { @@ -590,6 +653,16 @@ int main(int argc, char **argv) } else { fprintf(stderr, "DC filter disabled.\n"); } + if (rtl_agc) { + fprintf(stderr, "RTL AGC enabled.\n"); + } else { + fprintf(stderr, "RTL AGC disabled.\n"); + } + if (use_internal_aisdecoder) { + fprintf(stderr, "Internal AIS decoder enabled.\n"); + } else { + fprintf(stderr, "Internal AIS decoder disabled.\n"); + } fprintf(stderr, "Buffer size: %0.2f mS\n", 1000 * (double)DEFAULT_BUF_LENGTH / (double)dongle_rate); fprintf(stderr, "Downsample factor: %i\n", both.downsample * left.downsample); fprintf(stderr, "Low pass: %i Hz\n", left.rate_out); @@ -643,17 +716,29 @@ int main(int argc, char **argv) #else signal(SIGINT, sighandler); signal(SIGTERM, sighandler); -#endif - if (strcmp(filename, "-") == 0) { /* Write samples to stdout */ - file = stdout; -#ifdef WIN32 - setmode(fileno(stdout), O_BINARY); // Binary mode, avoid text mode -#endif - setvbuf(stdout, NULL, _IONBF, 0); - } else { - file = fopen(filename, "wb"); - if (!file) { - fprintf(stderr, "Failed to open %s\n", filename); + + #endif + if(!use_internal_aisdecoder){ + if (strcmp(filename, "-") == 0) { /* Write samples to stdout */ + file = stdout; + #ifdef WIN32 + setmode(fileno(stdout), O_BINARY); // Binary mode, avoid text mode + #endif + setvbuf(stdout, NULL, _IONBF, 0); + } else { + file = fopen(filename, "wb"); + if (!file) { + fprintf(stderr, "Failed to open %s\n", filename); + exit(1); + } + } + } + else{ // Internal AIS decoder + int ret=init_ais_decoder(host,port,show_levels,debug_nmea,stereo.bl_len,seconds_for_decoder_stats); + if(ret != 0){ + fprintf(stderr,"Error initializing built-in AIS decoder\n"); + rtlsdr_cancel_async(dev); + rtlsdr_close(dev); exit(1); } } @@ -664,15 +749,22 @@ int main(int argc, char **argv) gain = nearest_gain(dev, gain); verbose_gain_set(dev, gain); } - + if(rtl_agc){ + int r = rtlsdr_set_agc_mode(dev, 1); + if(r<0) { + fprintf(stderr,"Error seting RTL AGC mode ON"); + exit(1); + } + else { + fprintf(stderr,"RTL AGC mode ON\n"); + } + } if (!custom_ppm) { verbose_ppm_eeprom(dev, &ppm_error); } verbose_ppm_set(dev, ppm_error); - - //r = rtlsdr_set_agc_mode(dev, 1); - + /* Set the tuner frequency */ verbose_set_frequency(dev, dongle_freq); From 6da04e9f2d1c99cddae8ec3d068afae119de99a2 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:51:46 -0300 Subject: [PATCH 004/117] ais decoder for rtl_ais --- ais/aisdecoder/aisdecoder.c | 152 ++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 ais/aisdecoder/aisdecoder.c diff --git a/ais/aisdecoder/aisdecoder.c b/ais/aisdecoder/aisdecoder.c new file mode 100644 index 0000000..452107a --- /dev/null +++ b/ais/aisdecoder/aisdecoder.c @@ -0,0 +1,152 @@ +/* + * main.cpp -- AIS Decoder + * + * Copyright (C) 2013 + * Astra Paging Ltd / AISHub (info@aishub.net) + * + * AISDecoder is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * AISDecoder uses parts of GNUAIS project (http://gnuais.sourceforge.net/) + * + */ +/* This is a stripped down version for use with rtl_ais*/ + +#ifndef WIN32 +#include +#include +#include +#else +// Horrible hack for compiling freeaddrinfo() and getaddrinfo() with MSys, fix this please +#define WIN32_VER_TMP _WIN32_WINNT +#define _WIN32_WINNT 0x0502 +#include +#include +#undef _WIN32_WINNT +#define _WIN32_WINNT WIN32_VER_TMP + +#endif +#include +#include +#include +#include +#include +#include +//#include "config.h" +#include "sounddecoder.h" +#include "callbacks.h" + +#define MAX_BUFFER_LENGTH 2048 +//#define MAX_BUFFER_LENGTH 8190 + +static char buffer[MAX_BUFFER_LENGTH]; +static unsigned int buffer_count=0; +#ifdef WIN32 + WSADATA wsaData; +#endif +static int debug_nmea; +static int sock; +static struct addrinfo* addr=NULL; + +static int initSocket(const char *host, const char *portname); + + +void sound_level_changed(float level, int channel, unsigned char high) { + if (high != 0) + fprintf(stderr, "Level on ch %d too high: %.0f %%\n", channel, level); + else + fprintf(stderr, "Level on ch %d: %.0f %%\n", channel, level); +} + +void nmea_sentence_received(const char *sentence, + unsigned int length, + unsigned char sentences, + unsigned char sentencenum) { + if (sentences == 1) { + if (sendto(sock, sentence, length, 0, addr->ai_addr, addr->ai_addrlen) == -1) abort(); + if (debug_nmea) fprintf(stderr, "%s", sentence); + } else { + if (buffer_count + length < MAX_BUFFER_LENGTH) { + memcpy(&buffer[buffer_count], sentence, length); + buffer_count += length; + } else { + buffer_count=0; + } + + if (sentences == sentencenum && buffer_count > 0) { + if (sendto(sock, buffer, buffer_count, 0, addr->ai_addr, addr->ai_addrlen) == -1) abort(); + if (debug_nmea) fprintf(stderr, "%s", buffer); + buffer_count=0; + }; + } +} +int init_ais_decoder(char * host, char * port ,int show_levels,int _debug_nmea,int buf_len,int time_print_stats){ + debug_nmea=_debug_nmea; + if(debug_nmea) + fprintf(stderr,"Log NMEA sentences to console ON\n"); + else + fprintf(stderr,"Log NMEA sentences to console OFF\n"); + + if (!initSocket(host, port)) { + return EXIT_FAILURE; + } + if (show_levels) on_sound_level_changed=sound_level_changed; + on_nmea_sentence_received=nmea_sentence_received; + initSoundDecoder(buf_len,time_print_stats); + return 0; +} + +void run_rtlais_decoder(short * buff, int len) +{ + run_mem_decoder(buff,len,MAX_BUFFER_LENGTH); +} +int free_ais_decoder(void) +{ + freeSoundDecoder(); + freeaddrinfo(addr); +#ifdef WIN32 + WSACleanup(); +#endif + return 0; +} + + +int initSocket(const char *host, const char *portname) { + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family=AF_UNSPEC; + hints.ai_socktype=SOCK_DGRAM; + hints.ai_protocol=IPPROTO_UDP; +#ifndef WIN32 + hints.ai_flags=AI_ADDRCONFIG; +#else + + int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (iResult != 0) { + printf("WSAStartup failed: %d\n", iResult); + return 0; + } +#endif + int err=getaddrinfo(host, portname, &hints, &addr); + if (err!=0) { + fprintf(stderr, "Failed to resolve remote socket address!\n"); +#ifdef WIN32 + WSACleanup(); +#endif + return 0; + } + + sock=socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (sock==-1) { + fprintf(stderr, "%s",strerror(errno)); +#ifdef WIN32 + WSACleanup(); +#endif + return 0; + } + fprintf(stderr,"AIS data will be sent to %s port %s\n",host,portname); + return 1; +} + From 54775f800a151f11ed0624f0c57d0dcad9f942af Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:52:35 -0300 Subject: [PATCH 005/117] ais decoder for rtl_ais --- ais/aisdecoder/aisdecoder.h | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 ais/aisdecoder/aisdecoder.h diff --git a/ais/aisdecoder/aisdecoder.h b/ais/aisdecoder/aisdecoder.h new file mode 100644 index 0000000..e6a827c --- /dev/null +++ b/ais/aisdecoder/aisdecoder.h @@ -0,0 +1,7 @@ +#ifndef __AIS_RL_AIS_INC_ +#define __AIS_RL_AIS_INC_ +int init_ais_decoder(char * host, char * port,int show_levels,int _debug_nmea,int buf_len,int time_print_stats); +void run_rtlais_decoder(short * buff, int len); +int free_ais_decoder(void); +#endif + From 20ceb5231b06ec2b44b73adab72759bb0e994959 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:53:28 -0300 Subject: [PATCH 006/117] Create sounddecoder.c --- ais/aisdecoder/sounddecoder.c | 128 ++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 ais/aisdecoder/sounddecoder.c diff --git a/ais/aisdecoder/sounddecoder.c b/ais/aisdecoder/sounddecoder.c new file mode 100644 index 0000000..d12a30d --- /dev/null +++ b/ais/aisdecoder/sounddecoder.c @@ -0,0 +1,128 @@ +/* + * sounddecoder.cpp + * + * This file is part of AISDecoder. + * + * Copyright (C) 2013 + * Astra Paging Ltd / AISHub (info@aishub.net) + * + * AISDecoder is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * AISDecoder uses parts of GNUAIS project (http://gnuais.sourceforge.net/) + * + */ + +#include +#include +//#include "config.h" +#ifdef WIN32 + #include +#endif + +#include "receiver.h" +#include "hmalloc.h" + +#define MAX_FILENAME_SIZE 512 +#define ERROR_MESSAGE_LENGTH 1024 +#include "sounddecoder.h" + + +char errorSoundDecoder[ERROR_MESSAGE_LENGTH]; + +static struct receiver *rx_a=NULL; +static struct receiver *rx_b=NULL; + +static short *buffer=NULL; +static int buffer_l=0; +static int buffer_read=0; +static int channels=0; +static Sound_Channels sound_channels; +static FILE *fp=NULL; +static void readBuffers(); +static time_t tprev=0; +static int time_print_stats=0; + +int initSoundDecoder(int buf_len,int _time_print_stats) +{ + sound_channels=SOUND_CHANNELS_STEREO; + channels = sound_channels == SOUND_CHANNELS_MONO ? 1 : 2; + time_print_stats=_time_print_stats; + tprev=time(NULL); // for decoder statistics + buffer = (short *) hmalloc(channels*sizeof(short)*buf_len); + rx_a = init_receiver('A', 2, 0); + rx_b = init_receiver('B', 2, 1); + return 1; +} + +void run_mem_decoder(short * buf, int len,int max_buf_len) +{ + int offset=0; + int bytes_in_len=len*channels; + char * p=(char *) buf; + while(bytes_in_len > max_buf_len ) + { + memcpy(buffer,p+offset,max_buf_len); + buffer_read=max_buf_len/(channels*sizeof(short)); + bytes_in_len-=max_buf_len; + offset+=max_buf_len; + readBuffers(); + } + memcpy(buffer,p+offset,bytes_in_len); + buffer_read=bytes_in_len/(channels*sizeof(short)); + readBuffers(); + + if(time_print_stats && (time(NULL)-tprev >= time_print_stats)) + { + struct demod_state_t *d = rx_a->decoder; + tprev=time(NULL); + fprintf(stderr, + "A: Received correctly: %d packets, wrong CRC: %d packets, wrong size: %d packets\n", + d->receivedframes, d->lostframes, + d->lostframes2); + d = rx_b->decoder; + fprintf(stderr, + "B: Received correctly: %d packets, wrong CRC: %d packets, wrong size: %d packets\n", + d->receivedframes, d->lostframes, + d->lostframes2); + } +} +void runSoundDecoder(int *stop) { + while (!*stop) { + buffer_read = fread(buffer, channels * sizeof(short), buffer_l, fp); + readBuffers(); + } +} + +static void readBuffers() { + if (buffer_read <= 0) return; + if (rx_a != NULL && sound_channels != SOUND_CHANNELS_RIGHT) + receiver_run(rx_a, buffer, buffer_read); + + if (rx_b != NULL && + (sound_channels == SOUND_CHANNELS_STEREO || sound_channels == SOUND_CHANNELS_RIGHT) + ) receiver_run(rx_b, buffer, buffer_read); +} + +void freeSoundDecoder(void) { + if (fp != NULL) { + fclose(fp); + fp=NULL; + } + + if (rx_a != NULL) { + free_receiver(rx_a); + rx_a=NULL; + } + + if (rx_b != NULL) { + free_receiver(rx_b); + rx_b=NULL; + } + if (buffer != NULL) { + hfree(buffer); + buffer = NULL; + } +} From a7ad26c873d1d1ced236d7bd00f54ac995e810cd Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:54:00 -0300 Subject: [PATCH 007/117] Create sounddecoder.h --- ais/aisdecoder/sounddecoder.h | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 ais/aisdecoder/sounddecoder.h diff --git a/ais/aisdecoder/sounddecoder.h b/ais/aisdecoder/sounddecoder.h new file mode 100644 index 0000000..e53521d --- /dev/null +++ b/ais/aisdecoder/sounddecoder.h @@ -0,0 +1,37 @@ +#ifndef SOUNDDECODER_H +#define SOUNDDECODER_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SOUND_CHANNELS_MONO, + SOUND_CHANNELS_STEREO, + SOUND_CHANNELS_LEFT, + SOUND_CHANNELS_RIGHT +} Sound_Channels; + +typedef enum { +#ifdef HAVE_ALSA + DRIVER_ALSA, +#endif +#ifdef HAVE_PULSEAUDIO + DRIVER_PULSE, +#endif +#ifdef WIN32 + DRIVER_WINMM, +#endif + DRIVER_FILE +} Sound_Driver; + +extern char errorSoundDecoder[]; +int initSoundDecoder(int buf_len,int _time_print_stats); +void runSoundDecoder(int *stop); +void freeSoundDecoder(void); +void run_mem_decoder(short * buf, int len,int max_buf_len); + +#ifdef __cplusplus +} +#endif +#endif // SOUNDDECODER_H From bfd260a346e98173f1165dc4e0951375be55fff4 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:55:09 -0300 Subject: [PATCH 008/117] receiver.c --- ais/aisdecoder/lib/receiver.c | 147 ++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 ais/aisdecoder/lib/receiver.c diff --git a/ais/aisdecoder/lib/receiver.c b/ais/aisdecoder/lib/receiver.c new file mode 100644 index 0000000..adc2c3f --- /dev/null +++ b/ais/aisdecoder/lib/receiver.c @@ -0,0 +1,147 @@ + +/* + * receiver.c + * + * (c) Ruben Undheim 2008 + * (c) Heikki Hannikainen 2008 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +//#include "config.h" + +#include +#include + +#include "receiver.h" +#include "hmalloc.h" +#include "filter.h" + +static int sound_levellog=1; + +receiver_on_level_changed on_sound_level_changed=NULL; + +static float coeffs[]={ + 2.5959e-55, 2.9479e-49, 1.4741e-43, 3.2462e-38, 3.1480e-33, + 1.3443e-28, 2.5280e-24, 2.0934e-20, 7.6339e-17, 1.2259e-13, + 8.6690e-11, 2.6996e-08, 3.7020e-06, 2.2355e-04, 5.9448e-03, + 6.9616e-02, 3.5899e-01, 8.1522e-01, 8.1522e-01, 3.5899e-01, + 6.9616e-02, 5.9448e-03, 2.2355e-04, 3.7020e-06, 2.6996e-08, + 8.6690e-11, 1.2259e-13, 7.6339e-17, 2.0934e-20, 2.5280e-24, + 1.3443e-28, 3.1480e-33, 3.2462e-38, 1.4741e-43, 2.9479e-49, + 2.5959e-55 +}; +#define COEFFS_L 36 + + +struct receiver *init_receiver(char name, int num_ch, int ch_ofs) +{ + struct receiver *rx; + + rx = (struct receiver *) hmalloc(sizeof(struct receiver)); + memset(rx, 0, sizeof(struct receiver)); + + rx->filter = filter_init(COEFFS_L, coeffs); + + rx->decoder = hmalloc(sizeof(struct demod_state_t)); + protodec_initialize(rx->decoder, NULL, name); + + rx->name = name; + rx->lastbit = 0; + rx->num_ch = num_ch; + rx->ch_ofs = ch_ofs; + rx->pll = 0; + rx->pllinc = 0x10000 / 5; + rx->prev = 0; + rx->last_levellog = 0; + + return rx; +} + +void free_receiver(struct receiver *rx) +{ + if (rx) { + filter_free(rx->filter); + hfree(rx); + } +} + +#define INC 16 +#define FILTERED_LEN 8192 + +void receiver_run(struct receiver *rx, short *buf, int len) +{ + float out; + int curr, bit; + char b; + short maxval = 0; + int level_distance; + float level; + int rx_num_ch = rx->num_ch; + float filtered[FILTERED_LEN]; + int i; + + /* len is number of samples available in buffer for each + * channels - something like 1024, regardless of number of channels */ + + buf += rx->ch_ofs; + + if (len > FILTERED_LEN) + abort(); + + maxval = filter_run_buf(rx->filter, buf, filtered, rx_num_ch, len); + + for (i = 0; i < len; i++) { + out = filtered[i]; + curr = (out > 0); + + if ((curr ^ rx->prev) == 1) { + if (rx->pll < (0x10000 / 2)) { + rx->pll += rx->pllinc / INC; + } else { + rx->pll -= rx->pllinc / INC; + } + } + rx->prev = curr; + + rx->pll += rx->pllinc; + + if (rx->pll > 0xffff) { + /* slice */ + bit = (out > 0); + /* nrzi decode */ + b = !(bit ^ rx->lastbit); + /* feed to the decoder */ + protodec_decode(&b, 1, rx->decoder); + + rx->lastbit = bit; + rx->pll &= 0xffff; + } + } + + /* calculate level, and log it */ + level = (float)maxval / (float)32768 * (float)100; + level_distance = time(NULL) - rx->last_levellog; + + if (level > 95.0 && (level_distance >= 30 || level_distance >= sound_levellog)) { + if (on_sound_level_changed != NULL) on_sound_level_changed(level, rx->ch_ofs, 1); + time(&rx->last_levellog); + } else if (sound_levellog != 0 && level_distance >= sound_levellog) { + if (on_sound_level_changed != NULL) on_sound_level_changed(level, rx->ch_ofs, 0); + time(&rx->last_levellog); + } +} + From ea908bcf4b50356494f6011f13040c1202d8a829 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:55:43 -0300 Subject: [PATCH 009/117] receiver.h --- ais/aisdecoder/lib/receiver.h | 59 +++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 ais/aisdecoder/lib/receiver.h diff --git a/ais/aisdecoder/lib/receiver.h b/ais/aisdecoder/lib/receiver.h new file mode 100644 index 0000000..f2b0a5a --- /dev/null +++ b/ais/aisdecoder/lib/receiver.h @@ -0,0 +1,59 @@ + +/* + * receiver.h + * + * (c) Ruben Undheim 2008 + * (c) Heikki Hannikainen 2008 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef INC_RECEIVER_H +#define INC_RECEIVER_H +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#include "protodec.h" +#include "callbacks.h" + +struct receiver { + struct filter *filter; + char name; + int lastbit; + int num_ch; + int ch_ofs; + unsigned int pll; + unsigned int pllinc; + struct demod_state_t *decoder; + int prev; + time_t last_levellog; +}; + +extern struct receiver *init_receiver(char name, int num_ch, int ch_ofs); +extern void free_receiver(struct receiver *rx); + +extern void receiver_run(struct receiver *rx, short *buf, int len); + +#ifdef __cplusplus +} +#endif +#endif From c35a3120679c90050cabd77029a25d6d9f437623 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:56:23 -0300 Subject: [PATCH 010/117] protodec.c --- ais/aisdecoder/lib/protodec.c | 369 ++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 ais/aisdecoder/lib/protodec.c diff --git a/ais/aisdecoder/lib/protodec.c b/ais/aisdecoder/lib/protodec.c new file mode 100644 index 0000000..885b4a9 --- /dev/null +++ b/ais/aisdecoder/lib/protodec.c @@ -0,0 +1,369 @@ + +/* + * protodec.c + * + * (c) Ruben Undheim 2008 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include /* String function definitions */ +#include "callbacks.h" +//#include "config.h" + +#include "protodec.h" +#include "hmalloc.h" + +decoder_on_nmea_sentence_received on_nmea_sentence_received=NULL; + +#ifdef DMALLOC +#include +#endif + +void protodec_initialize(struct demod_state_t *d, struct serial_state_t *serial, char chanid) +{ + memset(d, 0, sizeof(struct demod_state_t)); + + d->chanid = chanid; + d->serial = serial; + + d->receivedframes = 0; + d->lostframes = 0; + d->lostframes2 = 0; + + protodec_reset(d); + + d->seqnr = 0; + + d->buffer = hmalloc(DEMOD_BUFFER_LEN); + d->rbuffer = hmalloc(DEMOD_BUFFER_LEN); + d->nmea = hmalloc(NMEABUFFER_LEN); +} + +void protodec_deinit(struct demod_state_t *d) +{ + hfree(d->buffer); + hfree(d->rbuffer); + hfree(d->nmea); +} + +void protodec_reset(struct demod_state_t *d) +{ + d->state = ST_SKURR; + d->nskurr = 0; + d->ndata = 0; + d->npreamble = 0; + d->nstartsign = 0; + d->nstopsign = 0; + d->antallpreamble = 0; + d->antallenner = 0; + d->last = 0; + d->bitstuff = 0; + d->bufferpos = 0; +} + +/* + * Calculates CRC-checksum + */ + +unsigned short protodec_sdlc_crc(const unsigned char *data, unsigned len) +{ + unsigned short c, crc = 0xffff; + + while (len--) + for (c = 0x100 + *data++; c > 1; c >>= 1) + if ((crc ^ c) & 1) + crc = (crc >> 1) ^ 0x8408; + else + crc >>= 1; + return ~crc; + +} + +int protodec_calculate_crc(int length_bits, struct demod_state_t *d) +{ + int length_bytes; + unsigned char *buf; + int buflen; + int i, j, x; + unsigned char tmp; + + if (length_bits <= 0) { + return 0; + } + + length_bytes = length_bits / 8; + buflen = length_bytes + 2; + + /* what is this? */ + buf = (unsigned char *) hmalloc(sizeof(*buf) * buflen); + for (j = 0; j < buflen; j++) { + tmp = 0; + for (i = 0; i < 8; i++) + tmp |= (((d->buffer[i + 8 * j]) << (i))); + buf[j] = tmp; + } + + /* ok, here's the actual CRC calculation */ + unsigned short crc = protodec_sdlc_crc(buf, buflen); + //DBG(printf("CRC: %04x\n",crc)); + + /* what is this? */ + memset(d->rbuffer, 0, DEMOD_BUFFER_LEN); + for (j = 0; j < length_bytes; j++) { + for (i = 0; i < 8; i++) { + x = j * 8 + i; + if (x >= DEMOD_BUFFER_LEN) { + hfree(buf); + return 0; + } else { + d->rbuffer[x] = (buf[j] >> (7 - i)) & 1; + } + } + } + + hfree(buf); + + return (crc == 0x0f47); +} + +unsigned long protodec_henten(int from, int size, unsigned char *frame) +{ + int i = 0; + unsigned long tmp = 0; + + for (i = 0; i < size; i++) + tmp |= (frame[from + i]) << (size - 1 - i); + + return tmp; +} + + +void protodec_generate_nmea(struct demod_state_t *d, int bufferlen, int fillbits, time_t received_t) +{ + int senlen; + int pos; + int k, offset; + int m; + unsigned char sentences, sentencenum, nmeachk, letter; + + //6bits to nmea-ascii. One sentence len max 82char + //inc. head + tail.This makes inside datamax 62char multipart, 62 single + senlen = 56; //this is normally not needed.For testing only. May be fixed number + if (bufferlen <= (senlen * 6)) { + sentences = 1; + } else { + sentences = bufferlen / (senlen * 6); + //sentences , if overflow put one more + if (bufferlen % (senlen * 6) != 0) + sentences++; + }; + + sentencenum = 0; + pos = 0; + offset = (sentences>1) ? 15 : 14; + do { + k = offset; //leave room for nmea header + while (k < senlen + offset && bufferlen > pos) { + letter = (unsigned char)protodec_henten(pos, 6, d->rbuffer); + // 6bit-to-ascii conversion by IEC + letter += (letter < 40) ? 48 : 56; + d->nmea[k] = letter; + pos += 6; + k++; + } + sentencenum++; + + memcpy(&d->nmea[0], "!AIVDM,0,0,", 11); + d->nmea[7] += sentences; + d->nmea[9] += sentencenum; + + memcpy(&d->nmea[k], ",0*00\0", 6); + + if (sentences > 1) { + d->nmea[11] = '0' + d->seqnr; + d->nmea[12] = ','; + d->nmea[13] = d->chanid; + d->nmea[14] = ','; + if (sentencenum == sentences) d->nmea[k + 1] = '0' + fillbits; + } else { + d->nmea[11] = ','; + d->nmea[12] = d->chanid; + d->nmea[13] = ','; + } + + m = 1; + nmeachk = d->nmea[m++]; + while (d->nmea[m] != '*') nmeachk ^= d->nmea[m++]; + + sprintf(&d->nmea[k + 3], "%02X\r\n", nmeachk); + if (on_nmea_sentence_received != NULL) + on_nmea_sentence_received(d->nmea, k+7, sentences, sentencenum); + } while (sentencenum < sentences); +} + +void protodec_getdata(int bufferlen, struct demod_state_t *d) +{ + unsigned char type = protodec_henten(0, 6, d->rbuffer); + if (type < 1 || type > MAX_AIS_PACKET_TYPE /* 9 */) + return; +// unsigned long mmsi = protodec_henten(8, 30, d->rbuffer); + int fillbits = 0; + int k; + time_t received_t; + time(&received_t); + + if (bufferlen % 6 > 0) { + fillbits = 6 - (bufferlen % 6); + for (k = bufferlen; k < bufferlen + fillbits; k++) + d->rbuffer[k] = 0; + + bufferlen = bufferlen + fillbits; + } + + protodec_generate_nmea(d, bufferlen, fillbits, received_t); + + d->seqnr++; + if (d->seqnr > 9) + d->seqnr = 0; + + if (type < 1 || type > MAX_AIS_PACKET_TYPE) + return; // unsupported packet type +} + +void protodec_decode(char *in, int count, struct demod_state_t *d) +{ + int i = 0; + int bufferlength, correct; + + while (i < count) { + switch (d->state) { + case ST_DATA: + if (d->bitstuff) { + if (in[i] == 1) { + d->state = ST_STOPSIGN; + d->ndata = 0; + d->bitstuff = 0; + } else { + d->ndata++; + d->last = in[i]; + d->bitstuff = 0; + } + } else { + if (in[i] == d->last && in[i] == 1) { + d->antallenner++; + if (d->antallenner == 4) { + d->bitstuff = 1; + d->antallenner = 0; + } + + } else + d->antallenner = 0; + + d->buffer[d->bufferpos] = in[i]; + d->bufferpos++; + d->ndata++; + + if (d->bufferpos >= 449) { + protodec_reset(d); + } + } + break; + + case ST_SKURR: + if (in[i] != d->last) + d->antallpreamble++; + else + d->antallpreamble = 0; + d->last = in[i]; + if (d->antallpreamble > 14 && in[i] == 0) { + d->state = ST_PREAMBLE; + d->nskurr = 0; + d->antallpreamble = 0; + } + d->nskurr++; + break; + + case ST_PREAMBLE: + if (in[i] != d->last && d->nstartsign == 0) { + d->antallpreamble++; + } else { + if (in[i] == 1) { + if (d->nstartsign == 0) { + d->nstartsign = 3; + d->last = in[i]; + } else if (d->nstartsign == 5) { + d->nstartsign++; + d->npreamble = 0; + d->antallpreamble = 0; + d->state = ST_STARTSIGN; + } else { + d->nstartsign++; + } + + } else { + if (d->nstartsign == 0) { + d->nstartsign = 1; + } else { + protodec_reset(d); + } + } + } + d->npreamble++; + break; + + case ST_STARTSIGN: + if (d->nstartsign >= 7) { + if (in[i] == 0) { + d->state = ST_DATA; + d->nstartsign = 0; + d->antallenner = 0; + memset(d->buffer, 0, DEMOD_BUFFER_LEN); + d->bufferpos = 0; + } else { + protodec_reset(d); + } + + } else if (in[i] == 0) { + protodec_reset(d); + } + d->nstartsign++; + break; + + case ST_STOPSIGN: + bufferlength = d->bufferpos - 6 - 16; + if (in[i] == 0 && bufferlength > 0) { + correct = protodec_calculate_crc(bufferlength, d); + if (correct) { + d->receivedframes++; + protodec_getdata(bufferlength, d); + } else { + d->lostframes++; + } + } else { + d->lostframes2++; + } + protodec_reset(d); + break; + + + } + d->last = in[i]; + i++; + } +} + From 769535714e6c3cdb764c9e5909e36ba132175fba Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:56:57 -0300 Subject: [PATCH 011/117] protodec.h --- ais/aisdecoder/lib/protodec.h | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 ais/aisdecoder/lib/protodec.h diff --git a/ais/aisdecoder/lib/protodec.h b/ais/aisdecoder/lib/protodec.h new file mode 100644 index 0000000..84c513e --- /dev/null +++ b/ais/aisdecoder/lib/protodec.h @@ -0,0 +1,65 @@ + +/* + * protodec.h + * + * (c) Ruben Undheim 2008 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef INC_PROTODEC_H +#define INC_PROTODEC_H + +#define ST_SKURR 1 +#define ST_PREAMBLE 2 +#define ST_STARTSIGN 3 +#define ST_DATA 4 +#define ST_STOPSIGN 5 + +#define DEMOD_BUFFER_LEN 450 +#define MAX_AIS_PACKET_TYPE 27 +#define NMEABUFFER_LEN 100 + +struct demod_state_t { + char chanid; + int state; + unsigned int offset; + int nskurr, npreamble, nstartsign, ndata, nstopsign; + + int antallenner; + unsigned char *buffer; + unsigned char *rbuffer; + char *tbuffer; + int bufferpos; + char last; + int antallpreamble; + int bitstuff; + int receivedframes; + int lostframes; + int lostframes2; + unsigned char seqnr; + + struct serial_state_t *serial; + + char *nmea; +}; + +void protodec_initialize(struct demod_state_t *d, struct serial_state_t *serial, char chanid); +void protodec_reset(struct demod_state_t *d); +void protodec_getdata(int bufferlengde, struct demod_state_t *d); +void protodec_decode(char *in, int count, struct demod_state_t *d); + +#endif From c368c831e4b66490bafb34f06c739c51e18c0d80 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:57:38 -0300 Subject: [PATCH 012/117] Create callbacks.h --- ais/aisdecoder/lib/callbacks.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 ais/aisdecoder/lib/callbacks.h diff --git a/ais/aisdecoder/lib/callbacks.h b/ais/aisdecoder/lib/callbacks.h new file mode 100644 index 0000000..c818762 --- /dev/null +++ b/ais/aisdecoder/lib/callbacks.h @@ -0,0 +1,20 @@ +#ifndef CALLBACKS_H +#define CALLBACKS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*receiver_on_level_changed)(float level, int channel, unsigned char high); +typedef void (*decoder_on_nmea_sentence_received)(const char *sentence, + unsigned int length, + unsigned char sentences, + unsigned char sentencenum); + +extern receiver_on_level_changed on_sound_level_changed; +extern decoder_on_nmea_sentence_received on_nmea_sentence_received; + +#ifdef __cplusplus +} +#endif +#endif // CALLBACKS_H From 65b9676472d87239f682851ac987bbf07989f486 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:58:27 -0300 Subject: [PATCH 013/117] hmalloc.h --- ais/aisdecoder/lib/hmalloc.h | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 ais/aisdecoder/lib/hmalloc.h diff --git a/ais/aisdecoder/lib/hmalloc.h b/ais/aisdecoder/lib/hmalloc.h new file mode 100644 index 0000000..30007b0 --- /dev/null +++ b/ais/aisdecoder/lib/hmalloc.h @@ -0,0 +1,42 @@ +/* + * (c) Heikki Hannikainen, OH7LZB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef HMALLOC_H +#define HMALLOC_H + +#include +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Replacements for malloc, realloc and free, which never fail, + * and might keep statistics on memory allocation... + */ + +extern void *hmalloc(size_t size); +extern void *hrealloc(void *ptr, size_t size); +extern void hfree(void *ptr); + +extern char *hstrdup(const char *s); +#ifdef __cplusplus +} +#endif +#endif + From ce5b37efb3cc307063d0639077bd146c346ba2fc Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:58:57 -0300 Subject: [PATCH 014/117] hmalloc.c --- ais/aisdecoder/lib/hmalloc.c | 74 ++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 ais/aisdecoder/lib/hmalloc.c diff --git a/ais/aisdecoder/lib/hmalloc.c b/ais/aisdecoder/lib/hmalloc.c new file mode 100644 index 0000000..910d238 --- /dev/null +++ b/ais/aisdecoder/lib/hmalloc.c @@ -0,0 +1,74 @@ +/* + * (c) Heikki Hannikainen, OH7LZB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Replacements for malloc, realloc and free, which never fail, + * and might keep statistics on memory allocation... + * + * GPL'ed, by Heikki Hannikainen + */ + +#include +#include +#include + +#include "hmalloc.h" + +#ifdef DMALLOC +#include +#endif + +int mem_panic = 0; + +void *hmalloc(size_t size) { + void *p; + if (!(p = malloc(size))) { + if (mem_panic) exit(1); + mem_panic = 1; + exit(1); + } + + return p; +} + +void *hrealloc(void *ptr, size_t size) { + void *p; + + if (!(p = realloc(ptr, size))) { + if (mem_panic) exit(1); + mem_panic = 1; + exit(1); + } + + return p; +} + +void hfree(void *ptr) { + if (ptr) free(ptr); +} + +char *hstrdup(const char *s) { + char *p; + + p = (char*)hmalloc(strlen(s)+1); + strcpy(p, s); + + return p; +} + From 453b488b740a061a400e448c8018a6b30b8f3c94 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:59:20 -0300 Subject: [PATCH 015/117] filter.h --- ais/aisdecoder/lib/filter.h | 72 +++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 ais/aisdecoder/lib/filter.h diff --git a/ais/aisdecoder/lib/filter.h b/ais/aisdecoder/lib/filter.h new file mode 100644 index 0000000..beac695 --- /dev/null +++ b/ais/aisdecoder/lib/filter.h @@ -0,0 +1,72 @@ +/* + * filter.h -- FIR filter + * + * Copyright (C) 2001, 2002, 2003, 2004 + * Tomi Manninen (oh2bns@sral.fi) + * + * This file is part of gMFSK. + * + * gMFSK is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gMFSK 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gMFSK; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _FILTER_H +#define _FILTER_H + +#define BufferLen 1024 + +/* ---------------------------------------------------------------------- */ + +#ifdef __OPTIMIZE__ + +#ifdef __i386__ +#include "filter-i386.h" +#endif /* __i386__ */ + + +#ifndef __HAVE_ARCH_MAC +static __inline__ float filter_mac(const float *a, const float *b, int size) +{ + float sum = 0; + int i; + + for (i = 0; i < size; i++) + sum += a[i] * b[i]; + + return sum; +} +#endif /* __HAVE_ARCH_MAC */ + +#endif /* __OPTIMIZE__ */ + + +/* ---------------------------------------------------------------------- */ + +struct filter { + int length; + float *taps; + float buffer[BufferLen]; + int pointer; +}; + +extern struct filter *filter_init(int len, float *taps); +extern void filter_free(struct filter *f); + +extern void filter_run(struct filter *f, float in, float *out); +extern short filter_run_buf(struct filter *f, short *in, float *out, int step, int len); + +/* ---------------------------------------------------------------------- */ + +#endif /* _FILTER_H */ From c420fb51bcfedb370cc3835eed22144c8e21eb33 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 12:59:45 -0300 Subject: [PATCH 016/117] filter.c --- ais/aisdecoder/lib/filter.c | 145 ++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 ais/aisdecoder/lib/filter.c diff --git a/ais/aisdecoder/lib/filter.c b/ais/aisdecoder/lib/filter.c new file mode 100644 index 0000000..3909dd3 --- /dev/null +++ b/ais/aisdecoder/lib/filter.c @@ -0,0 +1,145 @@ +/* + * filter.c -- FIR filter + * + * Copyright (C) 2001, 2002, 2003 + * Tomi Manninen (oh2bns@sral.fi) + * + * This file is part of gMFSK. + * + * gMFSK is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * gMFSK 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gMFSK; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include + +#include "hmalloc.h" +#include "filter.h" + +#undef DEBUG + +#ifdef DEBUG +#include +#endif + +/* ---------------------------------------------------------------------- */ + +/* + * This gets used when not optimising + */ +#ifndef __OPTIMIZE__ +float filter_mac(const float *a, const float *b, int size) +{ + float sum = 0; + int i; + + for (i = 0; i < size; i++) + sum += a[i] * b[i]; + + return sum; +} +#endif + +/* ---------------------------------------------------------------------- */ + +struct filter *filter_init(int len, float *taps) +{ + struct filter *f; + + f = (struct filter *) hmalloc(sizeof(struct filter)); + memset(f, 0, sizeof(struct filter)); + + f->taps = (float *) hmalloc(len * sizeof(float)); + memcpy(f->taps, taps, len * sizeof(float)); + + f->length = len; + f->pointer = f->length; + + return f; +} + +void filter_free(struct filter *f) +{ + if (f) { + hfree(f->taps); + hfree(f); + } +} + +/* ---------------------------------------------------------------------- */ + +void filter_run(struct filter *f, float in, float *out) +{ + float *ptr = f->buffer + f->pointer++; + + *ptr = in; + + // TODO: optimize: pass filter length as constant to enable + // using optimized __mac_c and fix the number of rounds there! + #ifndef __HAVE_ARCH_MAC + *out = filter_mac(ptr - f->length, f->taps, f->length); + #else + *out = mac(ptr - f->length, f->taps, f->length); + #endif + //*out = filter_mac(ptr - f->length, f->taps, 53); + + if (f->pointer == BufferLen) { + memcpy(f->buffer, + f->buffer + BufferLen - f->length, + f->length * sizeof(float)); + f->pointer = f->length; + } +} + +short filter_run_buf(struct filter *f, short *in, float *out, int step, int len) +{ + int id = 0; + int od = 0; + short maxval = 0; + int pointer = f->pointer; + float *buffer = f->buffer; + + while (od < len) { + buffer[pointer] = in[id]; + + // look for peak volume + if (in[id] > maxval) + maxval = in[id]; + + #ifndef __HAVE_ARCH_MAC + out[od] = filter_mac(&buffer[pointer - f->length], f->taps, f->length); + #else + out[od] = mac(&buffer[pointer - f->length], f->taps, f->length); + #endif + pointer++; + + /* the buffer is much smaller than the incoming chunks */ + if (pointer == BufferLen) { + memcpy(buffer, + buffer + BufferLen - f->length, + f->length * sizeof(float)); + pointer = f->length; + } + + id += step; + od++; + } + + f->pointer = pointer; + + return maxval; +} + +/* ---------------------------------------------------------------------- */ From 49a5aa68ab4d8b7e59992c7e7246f0bcc5d6bca7 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 13:00:32 -0300 Subject: [PATCH 017/117] filter-i386.h --- ais/aisdecoder/lib/filter-i386.h | 275 +++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 ais/aisdecoder/lib/filter-i386.h diff --git a/ais/aisdecoder/lib/filter-i386.h b/ais/aisdecoder/lib/filter-i386.h new file mode 100644 index 0000000..0cde026 --- /dev/null +++ b/ais/aisdecoder/lib/filter-i386.h @@ -0,0 +1,275 @@ +/* + * filter-i386.h -- optimized filter routines + * + * Copyright (C) 1996 + * Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* ---------------------------------------------------------------------- */ + +#ifndef _FILTER_I386_H +#define _FILTER_I386_H + +/* ---------------------------------------------------------------------- */ + +#define __HAVE_ARCH_MAC +#define mac(a,b,size) \ +(__builtin_constant_p(size) ? __mac_c((a),(b),(size)) : __mac_g((a),(b),(size))) + +#include + +extern inline float __mac_g(const float *a, const float *b, + unsigned int size) +{ + float sum = 0; + unsigned int i; + + for (i = 0; i < size; i++) + sum += (*a++) * (*b++); + return sum; +} + +extern inline float __mac_c(const float *a, const float *b, + unsigned int size) +{ + float f; + + /* + * inspired from Phil Karn, KA9Q's home page + */ + switch (size) { + case 53: + asm volatile ("flds (%1);\n\t" + "fmuls (%2);\n\t" + "flds 4(%1);\n\t" + "fmuls 4(%2);\n\t" + "flds 8(%1);\n\t" + "fmuls 8(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 12(%1);\n\t" + "fmuls 12(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 16(%1);\n\t" + "fmuls 16(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 20(%1);\n\t" + "fmuls 20(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 24(%1);\n\t" + "fmuls 24(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 28(%1);\n\t" + "fmuls 28(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 32(%1);\n\t" + "fmuls 32(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 36(%1);\n\t" + "fmuls 36(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 40(%1);\n\t" + "fmuls 40(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 44(%1);\n\t" + "fmuls 44(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 48(%1);\n\t" + "fmuls 48(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 52(%1);\n\t" + "fmuls 52(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 56(%1);\n\t" + "fmuls 56(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 60(%1);\n\t" + "fmuls 60(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 64(%1);\n\t" + "fmuls 64(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 68(%1);\n\t" + "fmuls 68(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 72(%1);\n\t" + "fmuls 72(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 76(%1);\n\t" + "fmuls 76(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 80(%1);\n\t" + "fmuls 80(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 84(%1);\n\t" + "fmuls 84(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 88(%1);\n\t" + "fmuls 88(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 92(%1);\n\t" + "fmuls 92(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 96(%1);\n\t" + "fmuls 96(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 100(%1);\n\t" + "fmuls 100(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 104(%1);\n\t" + "fmuls 104(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 108(%1);\n\t" + "fmuls 108(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 112(%1);\n\t" + "fmuls 112(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 116(%1);\n\t" + "fmuls 116(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 120(%1);\n\t" + "fmuls 120(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 124(%1);\n\t" + "fmuls 124(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 128(%1);\n\t" + "fmuls 128(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 132(%1);\n\t" + "fmuls 132(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 136(%1);\n\t" + "fmuls 136(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 140(%1);\n\t" + "fmuls 140(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 144(%1);\n\t" + "fmuls 144(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 148(%1);\n\t" + "fmuls 148(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 152(%1);\n\t" + "fmuls 152(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 156(%1);\n\t" + "fmuls 156(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 160(%1);\n\t" + "fmuls 160(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 164(%1);\n\t" + "fmuls 164(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 168(%1);\n\t" + "fmuls 168(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 172(%1);\n\t" + "fmuls 172(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 176(%1);\n\t" + "fmuls 176(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 180(%1);\n\t" + "fmuls 180(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 184(%1);\n\t" + "fmuls 184(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 188(%1);\n\t" + "fmuls 188(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 192(%1);\n\t" + "fmuls 192(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 196(%1);\n\t" + "fmuls 196(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 200(%1);\n\t" + "fmuls 200(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 204(%1);\n\t" + "fmuls 204(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "flds 208(%1);\n\t" + "fmuls 208(%2);\n\t" + "fxch %%st(2);\n\t" + "faddp;\n\t" + "faddp;\n\t":"=t" (f):"r"(a), "r"(b):"memory"); + return f; + + default: + fprintf(stderr, + "Warning: optimize __mac_c(..., ..., %d)\n", size); + return __mac_g(a, b, size); + } +} + +/* ---------------------------------------------------------------------- */ +#endif /* _FILTER_I386_H */ From 0014a8392eafba5963e7bffb06bf0026b7f165e4 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 13:16:36 -0300 Subject: [PATCH 018/117] build script --- ais/build.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ais/build.sh b/ais/build.sh index 9da0a99..b59805d 100755 --- a/ais/build.sh +++ b/ais/build.sh @@ -2,17 +2,17 @@ # todo, a real makefile #point this to the correct path -RTLSDR_PATH="../../src" +RTLSDR_PATH="/tmp/rtl-sdr-exp/src" -files="$RTLSDR_PATH/rtl_ais.c $RTLSDR_PATH/convenience/convenience.c \ - $RTLSDR_PATH/aisdecoder/aisdecoder.c $RTLSDR_PATH/aisdecoder/sounddecoder.c \ - $RTLSDR_PATH/aisdecoder/lib/receiver.c - $RTLSDR_PATH/aisdecoder/lib/protodec.c - $RTLSDR_PATH/aisdecoder/lib/hmalloc.c - $RTLSDR_PATH/aisdecoder/lib/filter.c " +files="rtl_ais.c convenience.c \ + ./aisdecoder/aisdecoder.c ./aisdecoder/sounddecoder.c \ + ./aisdecoder/lib/receiver.c + ./aisdecoder/lib/protodec.c + ./aisdecoder/lib/hmalloc.c + ./aisdecoder/lib/filter.c " flags="-Wall -O2 " -includes="-I/usr/include/libusb-1.0 -I../../include -I ../../src/convenience -I ../../src/aisdecoder -I ../../src/aisdecoder/lib" +includes="-I/usr/include/libusb-1.0 -I./aisdecoder -I ./aisdecoder/lib" libs="-L/usr/lib -L. -lusb-1.0 -lrtlsdr -lpthread -lm " UNAME=$(uname) From d2569884f01e015794f6f2cb8d1f6fcee99ef54e Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 13:24:58 -0300 Subject: [PATCH 019/117] rtl_ais built-in ais decoder. --- ais/rtl_ais.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ais/rtl_ais.c b/ais/rtl_ais.c index 3730030..6668473 100644 --- a/ais/rtl_ais.c +++ b/ais/rtl_ais.c @@ -61,7 +61,7 @@ static rtlsdr_dev_t *dev = NULL; /* todo, less globals */ int16_t *merged; int merged_len; -FILE *file; +FILE *file=NULL; int oversample = 0; int dc_filter = 1; int use_internal_aisdecoder=1; @@ -743,7 +743,7 @@ int main(int argc, char **argv) } } /* Set the tuner gain */ - if (gain == AUTO_GAIN) { +if if (gain == AUTO_GAIN) { verbose_auto_gain(dev); } else { gain = nearest_gain(dev, gain); @@ -789,7 +789,9 @@ int main(int argc, char **argv) pthread_mutex_destroy(&ready_m); if (file != stdout) { - fclose(file);} + if(file) + fclose(file); + } rtlsdr_close(dev); return r >= 0 ? r : -r; From f517bd7c472a74ea798ca19ac9071f23598c6cf1 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Fri, 3 Jul 2015 13:26:59 -0300 Subject: [PATCH 020/117] rtl_ais built-in aisdecoder --- ais/rtl_ais.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ais/rtl_ais.c b/ais/rtl_ais.c index 6668473..aacfa29 100644 --- a/ais/rtl_ais.c +++ b/ais/rtl_ais.c @@ -743,7 +743,7 @@ int main(int argc, char **argv) } } /* Set the tuner gain */ -if if (gain == AUTO_GAIN) { + if (gain == AUTO_GAIN) { verbose_auto_gain(dev); } else { gain = nearest_gain(dev, gain); @@ -789,7 +789,6 @@ if if (gain == AUTO_GAIN) { pthread_mutex_destroy(&ready_m); if (file != stdout) { - if(file) fclose(file); } From f0e9d48188f19f9883e429e0ff74313e7c2b37aa Mon Sep 17 00:00:00 2001 From: dgiardini Date: Sat, 4 Jul 2015 12:08:15 -0300 Subject: [PATCH 021/117] Update rtl_ais.c --- ais/rtl_ais.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ais/rtl_ais.c b/ais/rtl_ais.c index aacfa29..834d285 100644 --- a/ais/rtl_ais.c +++ b/ais/rtl_ais.c @@ -146,7 +146,7 @@ void usage(void) "\t[-A turn off built-in AIS decoder (default: on)]\n" "\t use this option to output samples to file or stdout.\n" "\tBuilt-in AIS decoder options:\n" - "\t[-h host (default: localhost)]\n" + "\t[-h host (default: 127.0.0.1)]\n" "\t[-P port (default: 10110)]\n" "\t[-n log NMEA sentences to console (stderr) (default off)]\n" "\t[-L log sound levels to console (stderr) (default off)]\n\n" @@ -156,7 +156,7 @@ void usage(void) "\t omitting the filename also uses stdout\n\n" "\tOutput is stereo 2x16 bit signed ints\n\n" "\tExmaples:\n" - "\tReceive AIS traffic,sent UDP NMEA sentences to localhost port 10110\n" + "\tReceive AIS traffic,sent UDP NMEA sentences to 127.0.0.1 port 10110\n" "\t and log the senteces to console:\n\n" "\trtl_ais -n\n\n" "\tTune two fm stations and play one on each channel:\n\n" @@ -599,7 +599,7 @@ int main(int argc, char **argv) return 2; } if(host==NULL){ - host=strdup("localhost"); + host=strdup("127.0.0.1"); } if(port==NULL){ port=strdup("10110"); From 43c8e9c30f224dd401bf66d5019125380007d5c2 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Mon, 6 Jul 2015 13:08:19 -0300 Subject: [PATCH 022/117] fclose() NULL file fixed --- ais/rtl_ais.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ais/rtl_ais.c b/ais/rtl_ais.c index 834d285..71cea78 100644 --- a/ais/rtl_ais.c +++ b/ais/rtl_ais.c @@ -789,7 +789,8 @@ int main(int argc, char **argv) pthread_mutex_destroy(&ready_m); if (file != stdout) { - fclose(file); + if(file) + fclose(file); } rtlsdr_close(dev); From 8151c6757ed7cfcf3924cb6ef8dc7c7f4f3c3600 Mon Sep 17 00:00:00 2001 From: Nuno Goncalves Date: Sun, 12 Jul 2015 00:46:30 +0100 Subject: [PATCH 023/117] Clean tree Removes all unrelated files. Signed-off-by: Nuno Goncalves --- {ais/aisdecoder => aisdecoder}/aisdecoder.c | 0 {ais/aisdecoder => aisdecoder}/aisdecoder.h | 0 .../aisdecoder => aisdecoder}/lib/callbacks.h | 0 .../lib/filter-i386.h | 0 {ais/aisdecoder => aisdecoder}/lib/filter.c | 0 {ais/aisdecoder => aisdecoder}/lib/filter.h | 0 {ais/aisdecoder => aisdecoder}/lib/hmalloc.c | 0 {ais/aisdecoder => aisdecoder}/lib/hmalloc.h | 0 {ais/aisdecoder => aisdecoder}/lib/protodec.c | 0 {ais/aisdecoder => aisdecoder}/lib/protodec.h | 0 {ais/aisdecoder => aisdecoder}/lib/receiver.c | 0 {ais/aisdecoder => aisdecoder}/lib/receiver.h | 0 {ais/aisdecoder => aisdecoder}/sounddecoder.c | 0 {ais/aisdecoder => aisdecoder}/sounddecoder.h | 0 ais/build.sh => build.sh | 0 ais/convenience.c => convenience.c | 0 ais/convenience.h => convenience.h | 0 heatmap/Vera.ttf | Bin 65932 -> 0 bytes heatmap/flatten.py | 52 -- heatmap/heatmap.py | 549 ------------------ heatmap/raw_iq.py | 103 ---- rtl-sdl/8-bit-arch.pcx | Bin 46605 -> 0 bytes rtl-sdl/build.sh | 13 - rtl-sdl/din1451alt.ttf | Bin 52208 -> 0 bytes rtl-sdl/rtl_power_lite.c | 395 ------------- rtl-sdl/sdl1.png | Bin 43153 -> 0 bytes rtl-sdl/sdl2.png | Bin 47395 -> 0 bytes rtl-sdl/waterfall.c | 531 ----------------- ais/rtl_ais.c => rtl_ais.c | 0 29 files changed, 1643 deletions(-) rename {ais/aisdecoder => aisdecoder}/aisdecoder.c (100%) rename {ais/aisdecoder => aisdecoder}/aisdecoder.h (100%) rename {ais/aisdecoder => aisdecoder}/lib/callbacks.h (100%) rename {ais/aisdecoder => aisdecoder}/lib/filter-i386.h (100%) rename {ais/aisdecoder => aisdecoder}/lib/filter.c (100%) rename {ais/aisdecoder => aisdecoder}/lib/filter.h (100%) rename {ais/aisdecoder => aisdecoder}/lib/hmalloc.c (100%) rename {ais/aisdecoder => aisdecoder}/lib/hmalloc.h (100%) rename {ais/aisdecoder => aisdecoder}/lib/protodec.c (100%) rename {ais/aisdecoder => aisdecoder}/lib/protodec.h (100%) rename {ais/aisdecoder => aisdecoder}/lib/receiver.c (100%) rename {ais/aisdecoder => aisdecoder}/lib/receiver.h (100%) rename {ais/aisdecoder => aisdecoder}/sounddecoder.c (100%) rename {ais/aisdecoder => aisdecoder}/sounddecoder.h (100%) rename ais/build.sh => build.sh (100%) rename ais/convenience.c => convenience.c (100%) rename ais/convenience.h => convenience.h (100%) delete mode 100644 heatmap/Vera.ttf delete mode 100644 heatmap/flatten.py delete mode 100644 heatmap/heatmap.py delete mode 100644 heatmap/raw_iq.py delete mode 100644 rtl-sdl/8-bit-arch.pcx delete mode 100755 rtl-sdl/build.sh delete mode 100644 rtl-sdl/din1451alt.ttf delete mode 100644 rtl-sdl/rtl_power_lite.c delete mode 100644 rtl-sdl/sdl1.png delete mode 100644 rtl-sdl/sdl2.png delete mode 100644 rtl-sdl/waterfall.c rename ais/rtl_ais.c => rtl_ais.c (100%) diff --git a/ais/aisdecoder/aisdecoder.c b/aisdecoder/aisdecoder.c similarity index 100% rename from ais/aisdecoder/aisdecoder.c rename to aisdecoder/aisdecoder.c diff --git a/ais/aisdecoder/aisdecoder.h b/aisdecoder/aisdecoder.h similarity index 100% rename from ais/aisdecoder/aisdecoder.h rename to aisdecoder/aisdecoder.h diff --git a/ais/aisdecoder/lib/callbacks.h b/aisdecoder/lib/callbacks.h similarity index 100% rename from ais/aisdecoder/lib/callbacks.h rename to aisdecoder/lib/callbacks.h diff --git a/ais/aisdecoder/lib/filter-i386.h b/aisdecoder/lib/filter-i386.h similarity index 100% rename from ais/aisdecoder/lib/filter-i386.h rename to aisdecoder/lib/filter-i386.h diff --git a/ais/aisdecoder/lib/filter.c b/aisdecoder/lib/filter.c similarity index 100% rename from ais/aisdecoder/lib/filter.c rename to aisdecoder/lib/filter.c diff --git a/ais/aisdecoder/lib/filter.h b/aisdecoder/lib/filter.h similarity index 100% rename from ais/aisdecoder/lib/filter.h rename to aisdecoder/lib/filter.h diff --git a/ais/aisdecoder/lib/hmalloc.c b/aisdecoder/lib/hmalloc.c similarity index 100% rename from ais/aisdecoder/lib/hmalloc.c rename to aisdecoder/lib/hmalloc.c diff --git a/ais/aisdecoder/lib/hmalloc.h b/aisdecoder/lib/hmalloc.h similarity index 100% rename from ais/aisdecoder/lib/hmalloc.h rename to aisdecoder/lib/hmalloc.h diff --git a/ais/aisdecoder/lib/protodec.c b/aisdecoder/lib/protodec.c similarity index 100% rename from ais/aisdecoder/lib/protodec.c rename to aisdecoder/lib/protodec.c diff --git a/ais/aisdecoder/lib/protodec.h b/aisdecoder/lib/protodec.h similarity index 100% rename from ais/aisdecoder/lib/protodec.h rename to aisdecoder/lib/protodec.h diff --git a/ais/aisdecoder/lib/receiver.c b/aisdecoder/lib/receiver.c similarity index 100% rename from ais/aisdecoder/lib/receiver.c rename to aisdecoder/lib/receiver.c diff --git a/ais/aisdecoder/lib/receiver.h b/aisdecoder/lib/receiver.h similarity index 100% rename from ais/aisdecoder/lib/receiver.h rename to aisdecoder/lib/receiver.h diff --git a/ais/aisdecoder/sounddecoder.c b/aisdecoder/sounddecoder.c similarity index 100% rename from ais/aisdecoder/sounddecoder.c rename to aisdecoder/sounddecoder.c diff --git a/ais/aisdecoder/sounddecoder.h b/aisdecoder/sounddecoder.h similarity index 100% rename from ais/aisdecoder/sounddecoder.h rename to aisdecoder/sounddecoder.h diff --git a/ais/build.sh b/build.sh similarity index 100% rename from ais/build.sh rename to build.sh diff --git a/ais/convenience.c b/convenience.c similarity index 100% rename from ais/convenience.c rename to convenience.c diff --git a/ais/convenience.h b/convenience.h similarity index 100% rename from ais/convenience.h rename to convenience.h diff --git a/heatmap/Vera.ttf b/heatmap/Vera.ttf deleted file mode 100644 index 58cd6b5e61eff273e920942e28041f8ddcf1e1b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65932 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

B-* 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 2: - help() - -path = sys.argv[1] - -sums = defaultdict(float) -counts = defaultdict(int) - -def frange(start, stop, step): - i = 0 - f = start - while f <= stop: - f = start + step*i - yield f - i += 1 - -for line in open(path): - line = line.strip().split(', ') - low = int(line[2]) - high = int(line[3]) - step = float(line[4]) - weight = int(line[5]) - dbm = [float(d) for d in line[6:]] - for f,d in zip(frange(low, high, step), dbm): - sums[f] += d*weight - counts[f] += weight - -ave = defaultdict(float) -for f in sums: - ave[f] = sums[f] / counts[f] - -for f in sorted(ave): - print(','.join([str(f), str(ave[f])])) - - diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py deleted file mode 100644 index 9d7bc80..0000000 --- a/heatmap/heatmap.py +++ /dev/null @@ -1,549 +0,0 @@ -#! /usr/bin/env python - -from PIL import Image, ImageDraw, ImageFont -import os, sys, gzip, math, argparse, colorsys, datetime -from collections import defaultdict -from itertools import * - -urlretrieve = lambda a, b: None -try: - import urllib.request - urlretrieve = urllib.request.urlretrieve -except: - import urllib - urlretrieve = urllib.urlretrieve - -# todo: -# matplotlib powered --interactive -# arbitrary freq marker spacing -# ppm -# blue-less marker grid -# fast summary thing -# gain normalization - -vera_url = "https://github.com/keenerd/rtl-sdr-misc/raw/master/heatmap/Vera.ttf" -vera_path = os.path.join(sys.path[0], "Vera.ttf") - -parser = argparse.ArgumentParser(description='Convert rtl_power CSV files into graphics.') -parser.add_argument('input_path', metavar='INPUT', type=str, - help='Input CSV file. (may be a .csv.gz)') -parser.add_argument('output_path', metavar='OUTPUT', type=str, - help='Output image. (various extensions supported)') -parser.add_argument('--offset', dest='offset_freq', default=None, - help='Shift the entire frequency range, for up/down converters.') -parser.add_argument('--ytick', dest='time_tick', default=None, - help='Place ticks along the Y axis every N seconds/minutes/hours/days.') -parser.add_argument('--db', dest='db_limit', nargs=2, default=None, - help='Minimum and maximum db values.') -parser.add_argument('--compress', dest='compress', default=0, - help='Apply a gradual asymptotic time compression. Values > 1 are the new target height, values < 1 are a scaling factor.') -slicegroup = parser.add_argument_group('Slicing', - 'Efficiently render a portion of the data. (optional) Frequencies can take G/M/k suffixes. Timestamps look like "YYYY-MM-DD HH:MM:SS" Durations take d/h/m/s suffixes.') -slicegroup.add_argument('--low', dest='low_freq', default=None, - help='Minimum frequency for a subrange.') -slicegroup.add_argument('--high', dest='high_freq', default=None, - help='Maximum frequency for a subrange.') -slicegroup.add_argument('--begin', dest='begin_time', default=None, - help='Timestamp to start at.') -slicegroup.add_argument('--end', dest='end_time', default=None, - help='Timestamp to stop at.') -slicegroup.add_argument('--head', dest='head_time', default=None, - help='Duration to use, starting at the beginning.') -slicegroup.add_argument('--tail', dest='tail_time', default=None, - help='Duration to use, stopping at the end.') - -# hack, http://stackoverflow.com/questions/9025204/ -for i, arg in enumerate(sys.argv): - if (arg[0] == '-') and arg[1].isdigit(): - sys.argv[i] = ' ' + arg -args = parser.parse_args() - -if not os.path.isfile(vera_path): - urlretrieve(vera_url, vera_path) - -try: - font = ImageFont.truetype(vera_path, 10) -except: - print('Please download the Vera.ttf font and place it in the current directory.') - sys.exit(1) - - -def frange(start, stop, step): - i = 0 - while (i*step + start <= stop): - yield i*step + start - i += 1 - -def min_filter(row): - size = 3 - result = [] - for i in range(size): - here = row[i] - near = row[0:i] + row[i+1:size] - if here > min(near): - result.append(here) - continue - result.append(min(near)) - for i in range(size-1, len(row)): - here = row[i] - near = row[i-(size-1):i] - if here > min(near): - result.append(here) - continue - result.append(min(near)) - return result - -def floatify(zs): - # nix errors with -inf, windows errors with -1.#J - zs2 = [] - previous = 0 # awkward for single-column rows - for z in zs: - try: - z = float(z) - except ValueError: - z = previous - if math.isinf(z): - z = previous - if math.isnan(z): - z = previous - zs2.append(z) - previous = z - return zs2 - -def freq_parse(s): - suffix = 1 - if s.lower().endswith('k'): - suffix = 1e3 - if s.lower().endswith('m'): - suffix = 1e6 - if s.lower().endswith('g'): - suffix = 1e9 - if suffix != 1: - s = s[:-1] - return float(s) * suffix - -def duration_parse(s): - suffix = 1 - if s.lower().endswith('s'): - suffix = 1 - if s.lower().endswith('m'): - suffix = 60 - if s.lower().endswith('h'): - suffix = 60 * 60 - if s.lower().endswith('d'): - suffix = 24 * 60 * 60 - if suffix != 1 or s.lower().endswith('s'): - s = s[:-1] - return float(s) * suffix - -def date_parse(s): - if '-' not in s: - return datetime.datetime.fromtimestamp(int(s)) - return datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S') - -def gzip_wrap(path): - "hides silly CRC errors" - iterator = gzip.open(path, 'rb') - running = True - while running: - try: - line = next(iterator) - if type(line) == bytes: - line = line.decode('utf-8') - yield line - except IOError: - running = False - -def reparse(label, fn): - if args.__getattribute__(label) is None: - return - args.__setattr__(label, fn(args.__getattribute__(label))) - -path = args.input_path -output = args.output_path - -raw_data = lambda: open(path) -if path.endswith('.gz'): - raw_data = lambda: gzip_wrap(path) - -reparse('low_freq', freq_parse) -reparse('high_freq', freq_parse) -reparse('offset_freq', freq_parse) -if args.offset_freq is None: - args.offset_freq = 0 -reparse('time_tick', duration_parse) -reparse('begin_time', date_parse) -reparse('end_time', date_parse) -reparse('head_time', duration_parse) -reparse('tail_time', duration_parse) -reparse('head_time', lambda s: datetime.timedelta(seconds=s)) -reparse('tail_time', lambda s: datetime.timedelta(seconds=s)) -args.compress = float(args.compress) - -if args.begin_time and args.tail_time: - print("Can't combine --begin and --tail") - sys.exit(2) -if args.end_time and args.head_time: - print("Can't combine --end and --head") - sys.exit(2) -if args.head_time and args.tail_time: - print("Can't combine --head and --tail") - sys.exit(2) - -print("loading") - -def slice_columns(columns, low_freq, high_freq): - start_col = 0 - stop_col = len(columns) - if args.low_freq is not None and low <= args.low_freq <= high: - start_col = sum(f args.end_time: - break - times.add(t) - columns = list(frange(low, high, step)) - start_col, stop_col = slice_columns(columns, args.low_freq, args.high_freq) - f_key = (columns[start_col], columns[stop_col], step) - zs = line[6+start_col:6+stop_col+1] - if not zs: - continue - if f_key not in f_cache: - freq2 = list(frange(*f_key))[:len(zs)] - freqs.update(freq2) - #freqs.add(f_key[1]) # high - #labels.add(f_key[0]) # low - f_cache.add(f_key) - - if not args.db_limit: - zs = floatify(zs) - min_z = min(min_z, min(zs)) - max_z = max(max_z, max(zs)) - - if start is None: - start = date_parse(t) - stop = date_parse(t) - if args.head_time is not None and args.end_time is None: - args.end_time = start + args.head_time - -if args.tail_time is not None: - times = [t for t in times if date_parse(t) >= (stop - args.tail_time)] - start = date_parse(min(times)) - -freqs = list(sorted(list(freqs))) -times = list(sorted(list(times))) -labels = list(sorted(list(labels))) - -if len(labels) == 1: - delta = (max(freqs) - min(freqs)) / (len(freqs) / 500.0) - delta = round(delta / 10**int(math.log10(delta))) * 10**int(math.log10(delta)) - delta = int(delta) - lower = int(math.ceil(min(freqs) / delta) * delta) - labels = list(range(lower, int(max(freqs)), delta)) - -def compression(y, decay): - return int(round((1/decay)*math.exp(y*decay) - 1/decay)) - -height = len(times) -height2 = height -if args.compress: - if args.compress > height: - args.compress = 0 - print("Image too short, disabling compression") - if 0 < args.compress < 1: - args.compress *= height - if args.compress: - args.compress = -1 / args.compress - height2 = compression(height, args.compress) - -print("x: %i, y: %i, z: (%f, %f)" % (len(freqs), height2, min_z, max_z)) - -def rgb2(z): - g = (z - min_z) / (max_z - min_z) - return (int(g*255), int(g*255), 50) - -def rgb3(z): - g = (z - min_z) / (max_z - min_z) - c = colorsys.hsv_to_rgb(0.65-(g-0.08), 1, 0.2+g) - return (int(c[0]*256),int(c[1]*256),int(c[2]*256)) - -def collate_row(x_size): - # this is more fragile than the old code - # sensitive to timestamps that are out of order - old_t = None - row = [0.0] * x_size - for line in raw_data(): - line = [s.strip() for s in line.strip().split(',')] - #line = [line[0], line[1]] + [float(s) for s in line[2:] if s] - line = [s for s in line if s] - t = line[0] + ' ' + line[1] - if '-' not in line[0]: - t = line[0] - if t not in times: - continue # happens with live files and time cropping - if old_t is None: - old_t = t - low = int(line[2]) + args.offset_freq - high = int(line[3]) + args.offset_freq - step = float(line[4]) - columns = list(frange(low, high, step)) - start_col, stop_col = slice_columns(columns, args.low_freq, args.high_freq) - if args.low_freq and columns[stop_col] < args.low_freq: - continue - if args.high_freq and columns[start_col] > args.high_freq: - continue - start_freq = columns[start_col] - if args.low_freq: - start_freq = max(args.low_freq, start_freq) - # sometimes fails? skip or abort? - x_start = freqs.index(start_freq) - zs = floatify(line[6+start_col:6+stop_col+1]) - if t != old_t: - yield old_t, row - row = [0.0] * x_size - old_t = t - for i in range(len(zs)): - x = x_start + i - if x >= x_size: - continue - row[x] = zs[i] - yield old_t, row - -print("drawing") -tape_height = 25 -img = Image.new("RGB", (len(freqs), tape_height + height2)) -pix = img.load() -x_size = img.size[0] -average = [0.0] * len(freqs) -tally = 0 -old_y = None -for t, zs in collate_row(x_size): - y = times.index(t) - if not args.compress: - for x in range(len(zs)): - pix[x,y+tape_height] = rgb2(zs[x]) - continue - # ugh - y = height2 - compression(height - y, args.compress) - if old_y is None: - old_y = y - if old_y != y: - for x in range(len(average)): - pix[x,old_y+tape_height] = rgb2(average[x]/tally) - tally = 0 - average = [0.0] * len(freqs) - old_y = y - for x in range(len(zs)): - average[x] += zs[x] - tally += 1 - - -def closest_index(n, m_list, interpolate=False): - "assumes sorted m_list, returns two points for interpolate" - i = len(m_list) // 2 - jump = len(m_list) // 2 - while jump > 1: - i_down = i - jump - i_here = i - i_up = i + jump - if i_down < 0: - i_down = i - if i_up >= len(m_list): - i_up = i - e_down = abs(m_list[i_down] - n) - e_here = abs(m_list[i_here] - n) - e_up = abs(m_list[i_up] - n) - e_best = min([e_down, e_here, e_up]) - if e_down == e_best: - i = i_down - if e_up == e_best: - i = i_up - if e_here == e_best: - i = i_here - jump = jump // 2 - if not interpolate: - return i - if n < m_list[i] and i > 0: - return i-1, i - if n > m_list[i] and i < len(m_list)-1: - return i, i+1 - return i, i - -def word_aa(label, pt, fg_color, bg_color): - f = ImageFont.truetype(vera_path, pt*3) - s = f.getsize(label) - s = (s[0], pt*3 + 3) # getsize lies, manually compute - w_img = Image.new("RGB", s, bg_color) - w_draw = ImageDraw.Draw(w_img) - w_draw.text((0, 0), label, font=f, fill=fg_color) - return w_img.resize((s[0]//3, s[1]//3), Image.ANTIALIAS) - -def blend(percent, c1, c2): - "c1 and c2 are RGB tuples" - # probably isn't gamma correct - r = c1[0] * percent + c2[0] * (1 - percent) - g = c1[1] * percent + c2[1] * (1 - percent) - b = c1[2] * percent + c2[2] * (1 - percent) - c3 = map(int, map(round, [r,g,b])) - return tuple(c3) - -def tape_lines(interval, y1, y2, used=set()): - "returns the number of lines" - low_f = (min(freqs) // interval) * interval - high_f = (1 + max(freqs) // interval) * interval - hits = 0 - blur = lambda p: blend(p, (255, 255, 0), (0, 0, 0)) - for i in range(int(low_f), int(high_f), int(interval)): - if not (min(freqs) < i < max(freqs)): - continue - hits += 1 - if i in used: - continue - x1,x2 = closest_index(i, freqs, interpolate=True) - if x1 == x2: - draw.line([x1,y1,x1,y2], fill='black') - else: - percent = (i - freqs[x1]) / float(freqs[x2] - freqs[x1]) - draw.line([x1,y1,x1,y2], fill=blur(percent)) - draw.line([x2,y1,x2,y2], fill=blur(1-percent)) - used.add(i) - return hits - -def tape_text(interval, y, used=set()): - low_f = (min(freqs) // interval) * interval - high_f = (1 + max(freqs) // interval) * interval - for i in range(int(low_f), int(high_f), int(interval)): - if i in used: - continue - if not (min(freqs) < i < max(freqs)): - continue - x = closest_index(i, freqs) - s = str(i) - if interval >= 1e6: - s = '%iM' % (i/1e6) - elif interval > 1000: - s = '%ik' % ((i/1e3) % 1000) - if s.startswith('0'): - s = '%iM' % (i/1e6) - else: - s = '%i' % (i%1000) - if s.startswith('0'): - s = '%ik' % ((i/1e3) % 1000) - if s.startswith('0'): - s = '%iM' % (i/1e6) - w = word_aa(s, tape_pt, 'black', 'yellow') - img.paste(w, (x - w.size[0]//2, y)) - used.add(i) - -def shadow_text(x, y, s, font, fg_color='white', bg_color='black'): - draw.text((x+1, y+1), s, font=font, fill=bg_color) - draw.text((x, y), s, font=font, fill=fg_color) - -print("labeling") -tape_pt = 10 -draw = ImageDraw.Draw(img) -font = ImageFont.load_default() -pixel_width = step - -draw.rectangle([0,0,img.size[0],tape_height], fill='yellow') -min_freq = min(freqs) -max_freq = max(freqs) -delta = max_freq - min_freq -width = len(freqs) -label_base = 9 - -for i in range(label_base, 0, -1): - interval = int(10**i) - low_f = (min_freq // interval) * interval - high_f = (1 + max_freq // interval) * interval - hits = len(range(int(low_f), int(high_f), interval)) - if hits >= 4: - label_base = i - break -label_base = 10**label_base - -for scale,y in [(1,10), (5,15), (10,19), (50,22), (100,24), (500, 25)]: - hits = tape_lines(label_base/scale, y, tape_height) - pixels_per_hit = width / hits - if pixels_per_hit > 50: - tape_text(label_base/scale, y-tape_pt) - if pixels_per_hit < 10: - break - -if args.time_tick: - label_format = "%H:%M:%S" - if args.time_tick % (60*60*24) == 0: - label_format = "%Y-%m-%d" - elif args.time_tick % 60 == 0: - label_format = "%H:%M" - label_next = datetime.datetime(start.year, start.month, start.day, start.hour) - tick_delta = datetime.timedelta(seconds = args.time_tick) - while label_next < start: - label_next += tick_delta - last_y = -100 - for y,t in enumerate(times): - label_time = date_parse(t) - if label_time < label_next: - continue - if args.compress: - y = height2 - compression(height - y, args.compress) - if y - last_y > 15: - shadow_text(2, y+tape_height, label_next.strftime(label_format), font) - last_y = y - label_next += tick_delta - - -duration = stop - start -duration = duration.days * 24*60*60 + duration.seconds + 30 -pixel_height = duration / len(times) -hours = int(duration / 3600) -minutes = int((duration - 3600*hours) / 60) -margin = 2 -if args.time_tick: - margin = 60 -shadow_text(margin, img.size[1] - 45, 'Duration: %i:%02i' % (hours, minutes), font) -shadow_text(margin, img.size[1] - 35, 'Range: %.2fMHz - %.2fMHz' % (min(freqs)/1e6, (max(freqs)+pixel_width)/1e6), font) -shadow_text(margin, img.size[1] - 25, 'Pixel: %.2fHz x %is' % (pixel_width, int(round(pixel_height))), font) -shadow_text(margin, img.size[1] - 15, 'Started: {0}'.format(start), font) -# bin size - -print("saving") -img.save(output) - - - - - - diff --git a/heatmap/raw_iq.py b/heatmap/raw_iq.py deleted file mode 100644 index b762ae2..0000000 --- a/heatmap/raw_iq.py +++ /dev/null @@ -1,103 +0,0 @@ -#! /usr/bin/env python - -""" -takes raw iq, turns into heatmap -extremely crude, lacks features like windowing -""" - -import sys, math, struct -import numpy -from PIL import Image - -def help(): - print("raw_iq.py bins averages sample-type input.raw") - print(" sample_types: u1 (uint8), s1 (int8), s2 (int16)") - sys.exit() - -def byte_reader(path, sample): - dtype = None - offset = 0 - scale = 2**7 - if sample == 'u1': - dtype = numpy.uint8 - offset = -127 - if sample == 's1': - dtype = numpy.int8 - if sample == 's2': - dtype = numpy.int16 - scale = 2**15 - raw = numpy.fromfile(path, dtype).astype(numpy.float64) - raw += offset - raw /= scale - return raw[0::2] + 1j * raw[1::2] - -def psd(data, bin_count, averages): - "really basic, lacks windowing" - length = len(data) - table = [numpy.zeros(bin_count)] - ave = 0 - for i in range(0, length, bin_count): - sub_data = numpy.array(data[i:i+bin_count]) - dc_bias = sum(sub_data) / len(sub_data) - #sub_data -= dc_bias - fft = numpy.fft.fft(sub_data) - if len(fft) != bin_count: - continue - table[-1] = table[-1] + numpy.real(numpy.conjugate(fft)*fft) - ave += 1 - if ave >= averages: - ave = max(1, ave) - row = table[-1] - row = numpy.concatenate((row[bin_count//2:], row[:bin_count//2])) - # spurious warnings - table[-1] = 10 * numpy.log10(row / ave) - table.append(numpy.zeros(bin_count)) - ave = 0 - if ave != 0: - row = table[-1] - row = numpy.concatenate((row[bin_count//2:], row[:bin_count//2])) - table[-1] = 10 * numpy.log10(row / ave) - if ave == 0: - table.pop(-1) - return table - -def rgb2(z, lowest, highest): - g = (z - lowest) / (highest - lowest) - return (int(g*255), int(g*255), 50) - -def heatmap(table): - lowest = -1 - highest = -100 - for row in table: - lowest = min(lowest, min(z for z in row if not math.isinf(z))) - highest = max(highest, max(row)) - img = Image.new("RGB", (len(table[0]), len(table))) - pix = img.load() - for y,row in enumerate(table): - for x,val in enumerate(row): - if not val >= lowest: # fast nan/-inf test - val = lowest - pix[x,y] = rgb2(val, lowest, highest) - return img - -if __name__ == '__main__': - try: - _, bin_count, averages, sample, path = sys.argv - bin_count = int(bin_count) - bin_count = int(2**(math.ceil(math.log(bin_count, 2)))) - averages = int(averages) - except: - help() - print("loading data") - data = byte_reader(path, sample) - print("estimated size: %i x %i" % (bin_count, - int(len(data) / (bin_count*averages)))) - print("crunching fft") - fft_table = psd(data, bin_count, averages) - print("drawing image") - img = heatmap(fft_table) - print("saving image") - img.save(path + '.png') - - - diff --git a/rtl-sdl/8-bit-arch.pcx b/rtl-sdl/8-bit-arch.pcx deleted file mode 100644 index cd54e3112607dd3157f56cfda5e5fd3ead395256..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46605 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 diff --git a/rtl-sdl/rtl_power_lite.c b/rtl-sdl/rtl_power_lite.c deleted file mode 100644 index 3984cf5..0000000 --- a/rtl-sdl/rtl_power_lite.c +++ /dev/null @@ -1,395 +0,0 @@ -/* - * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver - * Copyright (C) 2012 by Steve Markgraf - * Copyright (C) 2012 by Hoernchen - * Copyright (C) 2012 by Kyle Keen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -// a quick and horrible hack job of rtl_power.c -// 1024 element FFT -// no downsampling -// dedicated thread -// external flags for retune, gain change, data ready, quit -// todo, preface with fft_ - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include "rtl-sdr.h" - -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) - -#define FFT_LEVEL 10 -#define FFT_STACK 4 -#define FFT_SIZE (1 << FFT_LEVEL) -#define DEFAULT_BUF_LENGTH (2 * FFT_SIZE * FFT_STACK) -#define BUFFER_DUMP (1<<12) -#define DEFAULT_ASYNC_BUF_NUMBER 32 -#define SAMPLE_RATE 3200000 -#define PRESCALE 8 -#define POSTSCALE 2 -#define FREQ_MIN 27000000 -#define FREQ_MAX 1700000000 - -struct buffer -{ - // each buffer should have one writer and one reader thread - // the reader waits for the cond - int16_t buf[DEFAULT_BUF_LENGTH]; - int len; - pthread_rwlock_t rw; - pthread_cond_t ready; - pthread_mutex_t ready_m; - int ready_fast; -}; - -// shared items - -static volatile int do_exit = 0; -static rtlsdr_dev_t *dev = NULL; -static struct buffer fft_out; -static int frequency = 97000000; - -// local items - -struct buffer rtl_out; -struct buffer fft_tmp; - -int16_t* Sinewave; -double* power_table; -int N_WAVE, LOG2_N_WAVE; -int next_power; -int16_t *fft_buf; -int *window_coefs; - -pthread_t dongle_thread; -pthread_t fft_thread; - -#define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m) -#define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m) - -// some functions from convenience.c - -void gain_default(void) -{ - int count; - int* gains; - count = rtlsdr_get_tuner_gains(dev, NULL); - if (count <= 0) - {return;} - gains = malloc(sizeof(int) * count); - count = rtlsdr_get_tuner_gains(dev, gains); - rtlsdr_set_tuner_gain(dev, gains[count-1]); - free(gains); -} - -void gain_increase(void) -{ - int i, g, count; - int* gains; - count = rtlsdr_get_tuner_gains(dev, NULL); - if (count <= 0) - {return;} - gains = malloc(sizeof(int) * count); - count = rtlsdr_get_tuner_gains(dev, gains); - g = rtlsdr_get_tuner_gain(dev); - for (i=0; i<(count-1); i++) - { - if (gains[i] == g) - { - rtlsdr_set_tuner_gain(dev, gains[i+1]); - break; - } - } - free(gains); -} - -void gain_decrease(void) -{ - int i, g, count; - int* gains; - count = rtlsdr_get_tuner_gains(dev, NULL); - if (count <= 0) - {return;} - gains = malloc(sizeof(int) * count); - count = rtlsdr_get_tuner_gains(dev, gains); - g = rtlsdr_get_tuner_gain(dev); - for (i=1; i FREQ_MAX) - {frequency = FREQ_MAX;} - rtlsdr_set_center_freq(dev, frequency); -} - -// fft stuff - -void sine_table(int size) -{ - int i; - double d; - LOG2_N_WAVE = size; - N_WAVE = 1 << LOG2_N_WAVE; - Sinewave = malloc(sizeof(int16_t) * N_WAVE*3/4); - power_table = malloc(sizeof(double) * N_WAVE); - for (i=0; i> 14; - b = c & 0x01; - return (c >> 1) + b; -} - -int fix_fft(int16_t iq[], int m) -/* interleaved iq[], 0 <= n < 2**m, changes in place */ -{ - int mr, nn, i, j, l, k, istep, n, shift; - int16_t qr, qi, tr, ti, wr, wi; - n = 1 << m; - if (n > N_WAVE) - {return -1;} - mr = 0; - nn = n - 1; - /* decimation in time - re-order data */ - for (m=1; m<=nn; ++m) { - l = n; - do - {l >>= 1;} - while (mr+l > nn); - mr = (mr & (l-1)) + l; - if (mr <= m) - {continue;} - // real = 2*m, imag = 2*m+1 - tr = iq[2*m]; - iq[2*m] = iq[2*mr]; - iq[2*mr] = tr; - ti = iq[2*m+1]; - iq[2*m+1] = iq[2*mr+1]; - iq[2*mr+1] = ti; - } - l = 1; - k = LOG2_N_WAVE-1; - while (l < n) { - shift = 1; - istep = l << 1; - for (m=0; m>= 1; wi >>= 1;} - for (i=m; i>= 1; qi >>= 1;} - iq[2*j] = qr - tr; - iq[2*j+1] = qi - ti; - iq[2*i] = qr + tr; - iq[2*i+1] = qi + ti; - } - } - --k; - l = istep; - } - return 0; -} - -void remove_dc(int16_t *data, int length) -/* works on interleaved data */ -{ - int i; - int16_t ave; - long sum = 0L; - for (i=0; i < length; i+=2) { - sum += data[i]; - } - ave = (int16_t)(sum / (long)(length)); - if (ave == 0) { - return;} - for (i=0; i < length; i+=2) { - data[i] -= ave; - } -} - -int32_t real_conj(int16_t real, int16_t imag) -/* real(n * conj(n)) */ -{ - return ((int32_t)real*(int32_t)real + (int32_t)imag*(int32_t)imag); -} - -// threading stuff - -void rtl_callback_fn(unsigned char *buf, uint32_t len, void *ctx) -{ - int i; - if (do_exit) - {return;} - pthread_rwlock_wrlock(&rtl_out.rw); - for (i=0; irw, NULL); - pthread_cond_init(&buf->ready, NULL); - pthread_mutex_init(&buf->ready_m, NULL); - return 0; -} - -int buffer_cleanup(struct buffer* buf) -{ - pthread_rwlock_destroy(&buf->rw); - pthread_cond_destroy(&buf->ready); - pthread_mutex_destroy(&buf->ready_m); - return 0; -} - -static int fft_launch(void) -{ - sine_table(FFT_LEVEL); - - buffer_init(&rtl_out); - buffer_init(&fft_tmp); - buffer_init(&fft_out); - - rtlsdr_open(&dev, 0); // todo, verbose_device_search() - - // settings - rtlsdr_reset_buffer(dev); - rtlsdr_set_center_freq(dev, frequency); - rtlsdr_set_sample_rate(dev, SAMPLE_RATE); - rtlsdr_set_tuner_gain_mode(dev, 1); - gain_default(); - - pthread_create(&dongle_thread, NULL, &dongle_thread_fn, NULL); - pthread_create(&fft_thread, NULL, &fft_thread_fn, NULL); - return 0; -} - -static int fft_cleanup(void) -{ - do_exit = 1; - usleep(10000); - rtlsdr_cancel_async(dev); - pthread_join(dongle_thread, NULL); - safe_cond_signal(&rtl_out.ready, &rtl_out.ready_m); - pthread_join(fft_thread, NULL); - safe_cond_signal(&fft_out.ready, &fft_out.ready_m); - - rtlsdr_close(dev); - - buffer_cleanup(&rtl_out); - buffer_cleanup(&fft_tmp); - buffer_cleanup(&fft_out); - - return 0; -} - -// vim: tabstop=4:softtabstop=4:shiftwidth=4:expandtab diff --git a/rtl-sdl/sdl1.png b/rtl-sdl/sdl1.png deleted file mode 100644 index ac83a23ce9fb60d00c48bfad2cd150609853b9b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43153 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 diff --git a/rtl-sdl/waterfall.c b/rtl-sdl/waterfall.c deleted file mode 100644 index e58fb4c..0000000 --- a/rtl-sdl/waterfall.c +++ /dev/null @@ -1,531 +0,0 @@ - -/* - -SDL powered waterfall - -at the moment this everything is hard-coded for a single platform -the BeagleboneBlack with an LCD7 touchscreen (framebuffer mode) - -it can run on other platforms, but will not autodetect anything -the keybinds are laid out for the touchscreen face buttons - -on the BBB: -full screen double buffered blits seem to perform at 140 fps (cpu limited) - -to run automatically: -@reboot sleep 1 && cd /the/install/path && ./waterfall - -todo: -benchmark against fftw3 -replace defines with options -autodetect things like screen resolution -change the displayed bandwidth -audio demodulation -fix screen blanking -a real make file - -*/ - -#include -#include - -#include "SDL/SDL.h" -#include "SDL/SDL_image.h" -#include "SDL/SDL_ttf.h" - -#include "rtl_power_lite.c" - -#define SCREEN_WIDTH 800 -#define SCREEN_HEIGHT 480 -char* font_path = "./din1451alt.ttf"; -#define FONT_SIZE 24 -#define FRAME_MS 30 -#define FRAME_LINES 10 -#define MAX_STRING 100 -#define BIG_JUMP 50000000 - -#if SDL_BYTEORDER == SDL_BIG_ENDIAN -static const Uint32 r_mask = 0xFF000000; -static const Uint32 g_mask = 0x00FF0000; -static const Uint32 b_mask = 0x0000FF00; -static const Uint32 a_mask = 0x000000FF; -#else -static const Uint32 r_mask = 0x000000FF; -static const Uint32 g_mask = 0x0000FF00; -static const Uint32 b_mask = 0x00FF0000; -static const Uint32 a_mask = 0xFF000000; -#endif - -static SDL_Surface* img_surface; -static SDL_Surface* scroll_surface; -static SDL_Surface* future_surface; -static const SDL_VideoInfo* info = 0; -SDL_Surface* screen; -TTF_Font *font; -int do_flip; // todo, cond -int credits_toggle; - -struct text_bin -{ - char string[MAX_STRING]; - int x, y; - int i; - int dirty; - SDL_Surface* surf_fg; - SDL_Surface* surf_bg; -}; - -struct text_bin credits[6]; -struct text_bin freq_labels[5]; - -int init_video() -{ - if (SDL_Init(SDL_INIT_VIDEO) < 0) - { - fprintf(stderr, "Video initialization failed: %s\n", - SDL_GetError()); - return 0; - } - - info = SDL_GetVideoInfo(); - - if( !info ) { - fprintf( stderr, "Video query failed: %s\n", - SDL_GetError( ) ); - return 0; - } - - return 1; -} - -int set_video( Uint16 width, Uint16 height, int bpp, int flags) -{ - if (init_video()) - { - if((screen = SDL_SetVideoMode(width,height,bpp,flags))==0) - { - fprintf( stderr, "Video mode set failed: %s\n", - SDL_GetError( ) ); - return 0; - } - } - return 1; -} - -int init_ttf() -{ - if (TTF_Init() != 0) - { - fprintf( stderr, "TTF init failed: %s\n", - SDL_GetError( ) ); - return 1; - } - font = TTF_OpenFont(font_path, FONT_SIZE); - if (font == NULL) - { - fprintf( stderr, "TTF load failed: %s\n", - TTF_GetError( ) ); - return 1; - } - return 0; -} - -void quit( int code ) -{ - SDL_FreeSurface(scroll_surface); - SDL_FreeSurface(future_surface); - SDL_FreeSurface(img_surface); - - TTF_Quit( ); - SDL_Quit( ); - - exit( code ); -} - -void handle_key_down(SDL_keysym* keysym) -{ - switch(keysym->sym) - { - case SDLK_ESCAPE: - quit(0); - break; - case SDLK_RETURN: - credits_toggle = !credits_toggle; - break; - case SDLK_DOWN: - case SDLK_UP: - case SDLK_LEFT: - case SDLK_RIGHT: - default: - break; - } -} - -void process_events( void ) -{ - SDL_Event event; - - while( SDL_PollEvent( &event ) ) { - - switch( event.type ) { - case SDL_KEYDOWN: - handle_key_down( &event.key.keysym ); - break; - case SDL_QUIT: - quit( 0 ); - break; - } - } -} - -void init() -{ - SDL_Surface* tmp; - int i; - SDL_Color colors[256]; - - tmp = SDL_CreateRGBSurface(SDL_HWSURFACE, SCREEN_WIDTH, - SCREEN_HEIGHT, 8, r_mask, g_mask, b_mask, a_mask); - scroll_surface = SDL_DisplayFormat(tmp); - SDL_FreeSurface(tmp); - - tmp = SDL_CreateRGBSurface(SDL_HWSURFACE, SCREEN_WIDTH, - SCREEN_HEIGHT, 8, r_mask, g_mask, b_mask, a_mask); - future_surface = SDL_DisplayFormat(tmp); - SDL_FreeSurface(tmp); - - img_surface = IMG_Load("8-bit-arch.pcx"); - for (i = 0; i < SDL_NUMEVENTS; ++i) - { - if (i != SDL_KEYDOWN && i != SDL_QUIT) - { - SDL_EventState(i, SDL_IGNORE); - } - } - - for(i=0; i<256; i++) - { - colors[i].r = i; - colors[i].g = i; - colors[i].b = 50; - } - colors[0].r = 0; colors[0].g = 0; colors[0].b = 0; - colors[255].r = 255; colors[255].g = 255; colors[255].b = 255; - - SDL_SetPalette(future_surface, SDL_LOGPAL|SDL_PHYSPAL, colors, 0, 256); - - SDL_ShowCursor(SDL_DISABLE); -} - -void putpixel(SDL_Surface *surface, int x, int y, uint32_t pixel) -/* taken from some stackoverflow post */ -{ - int bpp = surface->format->BytesPerPixel; - /* Here p is the address to the pixel we want to set */ - Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; - - switch (bpp) { - case 1: - *p = pixel; - break; - - case 2: - *(uint16_t *)p = pixel; - break; - - case 3: - if (SDL_BYTEORDER == SDL_BIG_ENDIAN) { - p[0] = (pixel >> 16) & 0xff; - p[1] = (pixel >> 8) & 0xff; - p[2] = pixel & 0xff; - } - else { - p[0] = pixel & 0xff; - p[1] = (pixel >> 8) & 0xff; - p[2] = (pixel >> 16) & 0xff; - } - break; - - case 4: - *(uint32_t *)p = pixel; - break; - - default: - break; /* shouldn't happen, but avoids warnings */ - } -} - -int pretty_text(SDL_Surface* surface, struct text_bin* text) -{ - SDL_Color fg_color = {255, 255, 255}; - SDL_Color bg_color = {0, 0, 0}; - SDL_Rect fg_rect = {text->x + 0, text->y + 0, SCREEN_WIDTH, SCREEN_HEIGHT}; - SDL_Rect bg_rect = {text->x + 2, text->y + 2, SCREEN_WIDTH, SCREEN_HEIGHT}; - - if (text->dirty) - { - // this leaks, but freeing segfaults? - // in practice, it leaks an MB an hour under very heavy use - //if (text->surf_fg != NULL) - // {SDL_FreeSurface(text->surf_fg);} - //if (text->surf_bg != NULL) - // {SDL_FreeSurface(text->surf_bg);} - text->surf_fg = TTF_RenderText_Solid(font, text->string, fg_color); - text->surf_bg = TTF_RenderText_Solid(font, text->string, bg_color); - text->dirty = 0; - } - - SDL_BlitSurface(text->surf_bg, NULL, surface, &bg_rect); - SDL_BlitSurface(text->surf_fg, NULL, surface, &fg_rect); - - return 0; -} - -void build_credits(void) -{ - int i; - int xs[] = {300, 300, 300, 300, 300, 300}; - int ys[] = {100, 150, 200, 250, 300, 350}; - strncpy(credits[0].string, "board: BeagleBone Black", MAX_STRING); - strncpy(credits[1].string, "display: CircuitCo LCD7", MAX_STRING); - strncpy(credits[2].string, "radio: rtl-sdr", MAX_STRING); - strncpy(credits[3].string, "graphics: SDL", MAX_STRING); - strncpy(credits[4].string, "os: Arch Linux ARM", MAX_STRING); - strncpy(credits[5].string, "glue: Kyle Keen", MAX_STRING); - for (i=0; i<6; i++) - { - credits[i].x = xs[i]; - credits[i].y = ys[i]; - credits[i].dirty = 1; - } -} - -void show_credits(SDL_Surface* surface) -{ - int i; - for (i=0; i<6; i++) - {pretty_text(surface, &(credits[i]));} -} - -void build_labels(void) -{ - // very similar to the lines code - int f, i, x, drift, center; - drift = (frequency % 1000000) / (SAMPLE_RATE / FFT_SIZE); - center = frequency - (frequency % 1000000); - for (i=-2; i<=2; i++) - { - x = SCREEN_WIDTH / 2 + -drift + i * 1000000 / (SAMPLE_RATE / FFT_SIZE); - f = center + i * 1000000; - freq_labels[i+2].x = x - FONT_SIZE/2; - freq_labels[i+2].y = 10; - if (freq_labels[i+2].i == f) - {continue;} - freq_labels[i+2].dirty = 1; - freq_labels[i+2].i = f; - snprintf(freq_labels[i+2].string, MAX_STRING, "%i", f/1000000); - } -} - -void static_events(void) -{ - SDL_Rect blank = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}; - uint8_t* keystate = SDL_GetKeyState(NULL); - if (keystate[SDLK_LEFT]) - { - frequency -= BIG_JUMP; - frequency_set(); - build_labels(); - SDL_FillRect(scroll_surface, &blank, 0); - } - if (keystate[SDLK_RIGHT]) - { - frequency += BIG_JUMP; - frequency_set(); - build_labels(); - SDL_FillRect(scroll_surface, &blank, 0); - } - if (keystate[SDLK_UP]) - {gain_decrease();} - if (keystate[SDLK_DOWN]) - {gain_increase();} -} - -uint32_t frame_callback(uint32_t interval, void* param) -{ - do_flip = 1; - return interval; -} - -uint32_t rgb(uint32_t i) -{ - return ((b_mask/255)*20 | (r_mask/255)*i | (g_mask/255)*i); -} - -int mouse_stuff(void) -// returns X scroll offset -// kind of crap with variable framerate -{ - static double prev_x = -100; - static double velo = 0; - double deaccel = 10; - int x, y, buttons; - buttons = SDL_GetMouseState(&x, &y); - if (buttons & SDL_BUTTON_LMASK) - { - if (prev_x < 0) - { - prev_x = x; - } - velo = x - prev_x; - prev_x = x; - //fprintf(stdout, "%i %f\n", x, velo); - } else { - prev_x = -100; - if (velo > deaccel) - {velo -= deaccel;} - if (velo < -deaccel) - {velo += deaccel;} - if (velo >= -deaccel && velo <= deaccel) - {velo *= 0.5;} - } - return (int)round(velo); -} - -int main( int argc, char* argv[] ) -{ - int i, c, x, y, v, line; - int blits = 0; - uint32_t pixel = 0; - struct text_bin text; - SDL_Rect ScrollFrom = {0, 1, SCREEN_WIDTH, SCREEN_HEIGHT}; - if (!set_video(SCREEN_WIDTH, SCREEN_HEIGHT, 8, - SDL_HWSURFACE | SDL_HWACCEL | SDL_HWPALETTE /*| SDL_FULLSCREEN*/)) - quit(1); - init_ttf(); - //SDL_Init(SDL_INIT_TIMER); - - SDL_WM_SetCaption("Demo", ""); - - init(); - - build_credits(); - build_labels(); - - strncpy(text.string, "<< >> - + ?", MAX_STRING); - text.x = 30; - text.y = 450; - text.dirty = 1; - - SDL_BlitSurface(img_surface, NULL, scroll_surface, NULL); - //SDL_AddTimer(FRAME_MS, frame_callback, NULL); - - fft_launch(); - y = 0; - SDL_LockSurface(future_surface); - while(1) - { - process_events(); - //safe_cond_wait(&fft_out.ready, &fft_out.ready_m); - if (!fft_out.ready_fast) - { - usleep(1000); - continue; - } - fft_out.ready_fast = 0; - pthread_rwlock_rdlock(&fft_out.rw); - for (x=0; x 254) - {c = 254;} - if (c < 1) - {c = 1;} - //fprintf(stdout, "%i ", fft_out.buf[i]); - putpixel(future_surface, x, y, 40*fft_out.buf[i] + 1); - pixel++; - } - // lines every 100KHz - line = (frequency % 100000) / (SAMPLE_RATE / FFT_SIZE); - for (i=-15; i<15; i++) - { - if (y%4) - {break;} - x = SCREEN_WIDTH / 2 + -line + i * 100000 / (SAMPLE_RATE / FFT_SIZE); - if (x < 0) - {continue;} - if (x > SCREEN_WIDTH) - {continue;} - putpixel(future_surface, x, y, 0xFF); - } - //fprintf(stdout, "\n"); - pthread_rwlock_unlock(&fft_out.rw); - y++; - if (!do_flip && y <= FRAME_LINES) - {continue;} - static_events(); - v = mouse_stuff(); - if (v != 0) - { - frequency += (-v * SAMPLE_RATE / FFT_SIZE); - frequency_set(); - build_labels(); - } - SDL_UnlockSurface(future_surface); - // scroll - ScrollFrom.x = -v; - ScrollFrom.y = y; - ScrollFrom.w = SCREEN_WIDTH; - ScrollFrom.h = SCREEN_HEIGHT; - SDL_BlitSurface(scroll_surface, &ScrollFrom, scroll_surface, NULL); - // nuke edges - if (v > 0) - { - ScrollFrom.x = 0; - ScrollFrom.y = 0; - } - if (v < 0) - { - ScrollFrom.x = SCREEN_WIDTH+v; - ScrollFrom.y = 0; - } - if (v != 0) - { - ScrollFrom.w = abs(v); - ScrollFrom.h = SCREEN_HEIGHT-y; - SDL_FillRect(scroll_surface, &ScrollFrom, 0); - } - // new stuff - ScrollFrom.x = v; - ScrollFrom.y = SCREEN_HEIGHT - y; - ScrollFrom.w = SCREEN_WIDTH; - ScrollFrom.h = SCREEN_HEIGHT; - SDL_BlitSurface(future_surface, NULL, scroll_surface, &ScrollFrom); - SDL_BlitSurface(scroll_surface, NULL, screen, NULL); - // overlay - pretty_text(screen, &text); - if (credits_toggle) - {show_credits(screen);} - for (i=0; i<5; i++) - {pretty_text(screen, &freq_labels[i]);} - pretty_text(screen, &text); - SDL_Flip(screen); - // only way to keep the BBB from blanking the screen - // (the 10 minute timeout can not be changed by any known means) - if (blits % 2000 == 0) - {system("setterm -blank poke");} - blits++; - do_flip = 0; - y = 0; - SDL_LockSurface(future_surface); - } - quit(0); - fft_cleanup(); - - return 0; -} - - -// vim:set tabstop=4 softtabstop=4 shiftwidth=4 expandtab smarttab: diff --git a/ais/rtl_ais.c b/rtl_ais.c similarity index 100% rename from ais/rtl_ais.c rename to rtl_ais.c From c6cf5e7798875e8a6ee7b33e7869d04f977ab580 Mon Sep 17 00:00:00 2001 From: Nuno Goncalves Date: Sun, 12 Jul 2015 01:14:55 +0100 Subject: [PATCH 024/117] Remove libusb.h include libusb.h is not called directly Signed-off-by: Nuno Goncalves --- rtl_ais.c | 1 - 1 file changed, 1 deletion(-) diff --git a/rtl_ais.c b/rtl_ais.c index 71cea78..d3cd349 100644 --- a/rtl_ais.c +++ b/rtl_ais.c @@ -42,7 +42,6 @@ #include -#include #include #include "convenience.h" From 2948205fbecf799ff4e5f166914029aeda00bf7d Mon Sep 17 00:00:00 2001 From: Nuno Goncalves Date: Sun, 12 Jul 2015 01:18:23 +0100 Subject: [PATCH 025/117] Build with a Makefile Remove custom build script and use a Makefile Signed-off-by: Nuno Goncalves --- Makefile | 26 ++++++++++++++++++++++++++ build.sh | 28 ---------------------------- 2 files changed, 26 insertions(+), 28 deletions(-) create mode 100644 Makefile delete mode 100755 build.sh diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f90b5f7 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +CFLAGS?=-O2 -g -Wall -W $(shell pkg-config --cflags librtlsdr) +CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib +LDFLAGS+=$(shell pkg-config --libs librtlsdr) -lpthread -lm +CC?=gcc +SOURCES= \ + rtl_ais.c convenience.c \ + ./aisdecoder/aisdecoder.c \ + ./aisdecoder/sounddecoder.c \ + ./aisdecoder/lib/receiver.c \ + ./aisdecoder/lib/protodec.c \ + ./aisdecoder/lib/hmalloc.c \ + ./aisdecoder/lib/filter.c + +OBJECTS=$(SOURCES:.c=.o) +EXECUTABLE=rtl_ais + +all: $(SOURCES) $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + $(CC) $(OBJECTS) -o $@ $(LDFLAGS) + +.c.o: + $(CC) -c $< -o $@ $(CFLAGS) + +clean: + rm -f $(OBJECTS) $(EXECUTABLE) diff --git a/build.sh b/build.sh deleted file mode 100755 index b59805d..0000000 --- a/build.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh -# todo, a real makefile - -#point this to the correct path -RTLSDR_PATH="/tmp/rtl-sdr-exp/src" - -files="rtl_ais.c convenience.c \ - ./aisdecoder/aisdecoder.c ./aisdecoder/sounddecoder.c \ - ./aisdecoder/lib/receiver.c - ./aisdecoder/lib/protodec.c - ./aisdecoder/lib/hmalloc.c - ./aisdecoder/lib/filter.c " - -flags="-Wall -O2 " -includes="-I/usr/include/libusb-1.0 -I./aisdecoder -I ./aisdecoder/lib" -libs="-L/usr/lib -L. -lusb-1.0 -lrtlsdr -lpthread -lm " - -UNAME=$(uname) -if [ "$UNAME" != "Linux" ] -then -# Conditional section for Windows - libs="$libs -lWs2_32" -fi - -rm -f rtl_ais -echo gcc -o rtl_ais $files $flags $includes $libs -gcc -o rtl_ais $files $flags $includes $libs - From d1084f6514cb161266f5bc5c6662070ad7c87560 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Mon, 13 Jul 2015 19:38:56 -0300 Subject: [PATCH 026/117] add minimal support for MSys --- Makefile | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f90b5f7..b3cdf45 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,26 @@ -CFLAGS?=-O2 -g -Wall -W $(shell pkg-config --cflags librtlsdr) +CFLAGS?=-O2 -g -Wall -W CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib -LDFLAGS+=$(shell pkg-config --libs librtlsdr) -lpthread -lm +LDFLAGS+=-lpthread -lm + +UNAME := $(shell uname) +ifeq ($(UNAME),Linux) + +#Conditional for Linux +CFLAGS+= $(shell pkg-config --cflags librtlsdr) +LDFLAGS+=$(shell pkg-config --libs librtlsdr) + +else + +#Conditional for Windows +#### point this to your correct path ### +RTLSDR_PATH="/c/tmp/rtl-sdr/" +RTLSDR_LIB=$(RTLSDR_PATH)/build/src/ +######################################## +CFLAGS+=-I $(RTLSDR_PATH)/include +LDFLAGS+=-L $(RTLSDR_LIB) -L/usr/lib -lusb-1.0 -lrtlsdr -lWs2_32 + +endif + CC?=gcc SOURCES= \ rtl_ais.c convenience.c \ From 50608033aa13c2ac374ae605c9837e2648276414 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Mon, 13 Jul 2015 20:31:26 -0300 Subject: [PATCH 027/117] README file added --- README | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 README diff --git a/README b/README new file mode 100644 index 0000000..33b5b52 --- /dev/null +++ b/README @@ -0,0 +1,94 @@ +---------------------------------------------------------------------- +rtl_ais, a simple AIS tuner and generic dual-frequency FM demodulator +OS: Linux-Windows. +---------------------------------------------------------------------- + +------------ +COMMAND LINE +------------ +Use: rtl_ais [options] [outputfile] + [-l left_frequency (default: 161.975M)] + [-r right_frequency (default: 162.025M)] + left freq < right freq + frequencies must be within 1.2MHz + [-s sample_rate (default: 24k)] + maximum value, might be down to 12k + [-o output_rate (default: 48k)] + must be equal or greater than twice -s value + [-E toggle edge tuning (default: off)] + [-D toggle DC filter (default: on)] + [-d device_index (default: 0)] + [-g tuner_gain (default: automatic)] + [-p ppm_error (default: 0)] + [-R enable RTL chip AGC (default: off)] + [-A turn off built-in AIS decoder (default: on)] + use this option to output samples to file or stdout. + Built-in AIS decoder options: + [-h host (default: 127.0.0.1)] + [-P port (default: 10110)] + [-n log NMEA sentences to console (stderr) (default off)] + [-L log sound levels to console (stderr) (default off)] + [-S seconds_for_decoder_stats (default 0=off)] + When the built-in AIS decoder is disabled the samples are sent to + to [outputfile] (a '-' dumps samples to stdout) + omitting the filename also uses stdout + Output is stereo 2x16 bit signed ints + Exmaples: + Receive AIS traffic,sent UDP NMEA sentences to 127.0.0.1 port 10110 + and log the senteces to console: + rtl_ais -n + Tune two fm stations and play one on each channel: + rtl_ais -l233.15M -r233.20M -A | play -r48k -traw -es -b16 -c2 -V1 - + +--------- +COMPILING +--------- +Make sure you have the following dependencies: + - librtlsdr + - libusb + - libpthread + +Get the source code: + git clone https://github.com/dgiardini/rtl-ais +Change to the source dir + cd rtl-ais +then type + make + +Test running + ./rtl_ais + +For compiling a MS Windows executable you will have a working MSYS/MinGW environment. +Edit the Makefile, and modify the lines: + +#### point this to your correct path ### +RTLSDR_PATH="/c/tmp/rtl-sdr/" +RTLSDR_LIB=$(RTLSDR_PATH)/build/src/ +######################################## +------- +INSTALL +------- +On Linux, copy the rtl_ais file to a binary path, i.e. /usr/bin, or /usr/local/bin. +On Windows, put the librtlsdr.dll and libusb-1.0.dll files in the same directory +with rtl_ais.exe. You'll need the zadig driver installed too. + +------------ +KNOWN ISSUES +------------ +The [-p ppm error] parameter is essential for rtl_ais to work. +The ppm error is the frequency deviation in parts per million from the desired tuning +frequency, and the real tuned frequency due to the crystal oscillator deviation. This +figure is different for each device, it's very important to know this value and pass this parameter to rtl_ais. + +Some instructions for get the ppm error are here: + +http://www.rtl-sdr.com/how-to-calibrate-rtl-sdr-using-kalibrate-rtl-on-linux + +and here (using SDR#): + +http://www.atouk.com/SDRSharpQuickStart.html#adjusting + +and here (using HDSDR ad AIS traffic) + +http://www.cruisersforum.com/forums/f134/new-rtlsdr-plugin-102929-11.html#post1844966 + From cf29a362ac1c9646490e4696d6240eaed1caf5ea Mon Sep 17 00:00:00 2001 From: dgiardini Date: Tue, 14 Jul 2015 14:41:19 -0300 Subject: [PATCH 028/117] Fixed some typos --- README | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README b/README index 33b5b52..e376c61 100644 --- a/README +++ b/README @@ -58,8 +58,8 @@ then type Test running ./rtl_ais -For compiling a MS Windows executable you will have a working MSYS/MinGW environment. -Edit the Makefile, and modify the lines: +For compiling a MS Windows executable you will need a working MSYS/MinGW environment. +Edit the Makefile, and modify these lines: #### point this to your correct path ### RTLSDR_PATH="/c/tmp/rtl-sdr/" From 741578cdeb6a3f69d2d31e9e7ffc82f7f598ac13 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Tue, 14 Jul 2015 14:56:58 -0300 Subject: [PATCH 029/117] Update README --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index e376c61..df485d4 100644 --- a/README +++ b/README @@ -33,7 +33,7 @@ Use: rtl_ais [options] [outputfile] to [outputfile] (a '-' dumps samples to stdout) omitting the filename also uses stdout Output is stereo 2x16 bit signed ints - Exmaples: + Examples: Receive AIS traffic,sent UDP NMEA sentences to 127.0.0.1 port 10110 and log the senteces to console: rtl_ais -n From 5dd3f746aba84f9e2bbe30c277bd4474a76ce01d Mon Sep 17 00:00:00 2001 From: dgiardini Date: Tue, 14 Jul 2015 14:57:52 -0300 Subject: [PATCH 030/117] typo fixed --- rtl_ais.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtl_ais.c b/rtl_ais.c index d3cd349..f8fd394 100644 --- a/rtl_ais.c +++ b/rtl_ais.c @@ -154,7 +154,7 @@ void usage(void) "\tto [outputfile] (a '-' dumps samples to stdout)\n" "\t omitting the filename also uses stdout\n\n" "\tOutput is stereo 2x16 bit signed ints\n\n" - "\tExmaples:\n" + "\tExamples:\n" "\tReceive AIS traffic,sent UDP NMEA sentences to 127.0.0.1 port 10110\n" "\t and log the senteces to console:\n\n" "\trtl_ais -n\n\n" From 8d4c4a0c9916a8580bbaf2630352f5f49e7a05ab Mon Sep 17 00:00:00 2001 From: Alessandro Staniscia Date: Wed, 22 Jul 2015 17:57:32 +0200 Subject: [PATCH 031/117] small fix for OSX compilation --- Makefile | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index b3cdf45..cef9ce4 100644 --- a/Makefile +++ b/Makefile @@ -4,20 +4,36 @@ LDFLAGS+=-lpthread -lm UNAME := $(shell uname) ifeq ($(UNAME),Linux) - #Conditional for Linux CFLAGS+= $(shell pkg-config --cflags librtlsdr) LDFLAGS+=$(shell pkg-config --libs librtlsdr) else +# +#ADD THE CORRECT PATH FOR LIBUSB AND RTLSDR +#TODO: +# CMAKE will be much better or create a conditional pkg-config + + +# RTLSDR +RTLSDR_INCLUDE=/tmp/rtl-sdr/include +RTLSDR_LIB=/tmp/rtl-sdr/build/src + +# LIBUSB +LIBUSB_INCLUDE=/tmp/libusb/include/libusb-1.0 +LIBUSB_LIB=/tmp/libusb/lib + +ifeq ($(UNAME),Darwin) +#Conditional for OSX +CFLAGS+= -I/usr/local/include/ -I$(LIBUSB_INCLUDE) -I$(RTLSDR_INCLUDE) +LDFLAGS+= -L/usr/local/lib -L$(LIBUSB_LIB) -L$(RTLSDR_LIB) -lrtlsdr -lusb-1.0 +else #Conditional for Windows -#### point this to your correct path ### -RTLSDR_PATH="/c/tmp/rtl-sdr/" -RTLSDR_LIB=$(RTLSDR_PATH)/build/src/ -######################################## -CFLAGS+=-I $(RTLSDR_PATH)/include -LDFLAGS+=-L $(RTLSDR_LIB) -L/usr/lib -lusb-1.0 -lrtlsdr -lWs2_32 +CFLAGS+=-I $(LIBUSB_INCLUDE) -I $(RTLSDR_INCLUDE) +LDFLAGS+=-L$(LIBUSB_INCLUDE) -L$(RTLSDR_LIB) -L/usr/lib -lusb-1.0 -lrtlsdr -lWs2_32 +endif + endif From 5a03d3505639d2594f488ddc5391be1235c0130e Mon Sep 17 00:00:00 2001 From: dgiardini Date: Mon, 27 Jul 2015 10:37:08 -0300 Subject: [PATCH 032/117] Create TODO --- TODO | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 0000000..b99e716 --- /dev/null +++ b/TODO @@ -0,0 +1,6 @@ +---- +TODO +---- +- Auto calibration mode +- Compile as library +- Add audio gain control From 8c2b9d3bcbf36356df5a6d5cce5a504a0b0f8573 Mon Sep 17 00:00:00 2001 From: klapligehesten Date: Tue, 10 May 2016 21:47:20 +0200 Subject: [PATCH 033/117] Add files via upload --- aisdecoder/aisdecoder.c | 37 +++++++++++++++++++++++++++++-------- aisdecoder/aisdecoder.h | 2 +- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/aisdecoder/aisdecoder.c b/aisdecoder/aisdecoder.c index 452107a..0f9a8c4 100644 --- a/aisdecoder/aisdecoder.c +++ b/aisdecoder/aisdecoder.c @@ -34,9 +34,11 @@ #include #include #include +#include //#include "config.h" #include "sounddecoder.h" #include "callbacks.h" +#include "../tcp_listener/tcp_listener.h" #define MAX_BUFFER_LENGTH 2048 //#define MAX_BUFFER_LENGTH 8190 @@ -48,10 +50,12 @@ static unsigned int buffer_count=0; #endif static int debug_nmea; static int sock; +static int use_tcp = 0; + static struct addrinfo* addr=NULL; static int initSocket(const char *host, const char *portname); - +int send_nmea( const char *sentence, unsigned int length); void sound_level_changed(float level, int channel, unsigned char high) { if (high != 0) @@ -65,7 +69,7 @@ void nmea_sentence_received(const char *sentence, unsigned char sentences, unsigned char sentencenum) { if (sentences == 1) { - if (sendto(sock, sentence, length, 0, addr->ai_addr, addr->ai_addrlen) == -1) abort(); + if (send_nmea( sentence, length) == -1) abort(); if (debug_nmea) fprintf(stderr, "%s", sentence); } else { if (buffer_count + length < MAX_BUFFER_LENGTH) { @@ -76,22 +80,39 @@ void nmea_sentence_received(const char *sentence, } if (sentences == sentencenum && buffer_count > 0) { - if (sendto(sock, buffer, buffer_count, 0, addr->ai_addr, addr->ai_addrlen) == -1) abort(); + if (send_nmea( buffer, buffer_count) == -1) abort(); if (debug_nmea) fprintf(stderr, "%s", buffer); buffer_count=0; }; } } -int init_ais_decoder(char * host, char * port ,int show_levels,int _debug_nmea,int buf_len,int time_print_stats){ + +int send_nmea( const char *sentence, unsigned int length) { + if( use_tcp) { + return add_nmea_ais_message(sentence, length); + } + else { + return sendto(sock, sentence, length, 0, addr->ai_addr, addr->ai_addrlen); + } +} + +int init_ais_decoder(char * host, char * port ,int show_levels,int _debug_nmea,int buf_len,int time_print_stats, int use_tcp_listener, int tcp_keep_ais_time){ debug_nmea=_debug_nmea; + use_tcp = use_tcp_listener; if(debug_nmea) fprintf(stderr,"Log NMEA sentences to console ON\n"); else fprintf(stderr,"Log NMEA sentences to console OFF\n"); - - if (!initSocket(host, port)) { - return EXIT_FAILURE; - } + if( !use_tcp_listener) { + if (!initSocket(host, port)) { + return EXIT_FAILURE; + } + } + else { + if (!initTcpSocket(port, debug_nmea, tcp_keep_ais_time)) { + return EXIT_FAILURE; + } + } if (show_levels) on_sound_level_changed=sound_level_changed; on_nmea_sentence_received=nmea_sentence_received; initSoundDecoder(buf_len,time_print_stats); diff --git a/aisdecoder/aisdecoder.h b/aisdecoder/aisdecoder.h index e6a827c..6d56b95 100644 --- a/aisdecoder/aisdecoder.h +++ b/aisdecoder/aisdecoder.h @@ -1,6 +1,6 @@ #ifndef __AIS_RL_AIS_INC_ #define __AIS_RL_AIS_INC_ -int init_ais_decoder(char * host, char * port,int show_levels,int _debug_nmea,int buf_len,int time_print_stats); +int init_ais_decoder(char * host, char * port,int show_levels,int _debug_nmea,int buf_len,int time_print_stats, int use_tcp_listener, int tcp_keep_ais_time); void run_rtlais_decoder(short * buff, int len); int free_ais_decoder(void); #endif From ebb5002ceaaf9ba800d548860f544a804eeb36ce Mon Sep 17 00:00:00 2001 From: klapligehesten Date: Tue, 10 May 2016 21:48:34 +0200 Subject: [PATCH 034/117] Add files via upload --- Makefile | 5 ++--- rtl_ais.c | 28 ++++++++++++++++++++++------ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index cef9ce4..3e3c2d5 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ CFLAGS?=-O2 -g -Wall -W -CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib +CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib -I./tcp_listener LDFLAGS+=-lpthread -lm UNAME := $(shell uname) ifeq ($(UNAME),Linux) #Conditional for Linux CFLAGS+= $(shell pkg-config --cflags librtlsdr) -LDFLAGS+=$(shell pkg-config --libs librtlsdr) +LDFLAGS+=$(shell pkg-config --libs librtlsdr) -L./tcp_listener -ltcp_listener else # @@ -23,7 +23,6 @@ RTLSDR_LIB=/tmp/rtl-sdr/build/src LIBUSB_INCLUDE=/tmp/libusb/include/libusb-1.0 LIBUSB_LIB=/tmp/libusb/lib - ifeq ($(UNAME),Darwin) #Conditional for OSX CFLAGS+= -I/usr/local/include/ -I$(LIBUSB_INCLUDE) -I$(RTLSDR_INCLUDE) diff --git a/rtl_ais.c b/rtl_ais.c index f8fd394..06a23aa 100644 --- a/rtl_ais.c +++ b/rtl_ais.c @@ -147,6 +147,8 @@ void usage(void) "\tBuilt-in AIS decoder options:\n" "\t[-h host (default: 127.0.0.1)]\n" "\t[-P port (default: 10110)]\n" + "\t[-T use TCP communication ( -h is ignored)\n" + "\t[-t time to keep ais messages in sec, using tcp listener (default: 15)\n" "\t[-n log NMEA sentences to console (stderr) (default off)]\n" "\t[-L log sound levels to console (stderr) (default off)]\n\n" "\t[-S seconds_for_decoder_stats (default 0=off)]\n\n" @@ -166,9 +168,16 @@ void usage(void) static void sighandler(int signum) { - fprintf(stderr, "Signal caught, exiting!\n"); - do_exit = 1; - rtlsdr_cancel_async(dev); + switch( signum) { + case 13: // Ignore sig 13, because of write to closed socket when running TCP + fprintf(stderr, "Broken pipe signal caught\n"); + break; + default: + closeTcpSocket(); + fprintf(stderr, "Signal %d caught, exiting!\n", signum); + do_exit = 1; + rtlsdr_cancel_async(dev); + } } int cic_9_tables[][10] = { {0,}, @@ -520,11 +529,12 @@ int main(int argc, char **argv) int debug_nmea = 0; char * port=NULL; char * host=NULL; - + int use_tcp_listener = 0; + int tcp_keep_ais_time = 15; // using tcp lister time to keep ais messages in sec. pthread_cond_init(&ready, NULL); pthread_mutex_init(&ready_m, NULL); - while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RAP:h:nLS:?")) != -1) + while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RATt:P:h:nLSi:?")) != -1) { switch (opt) { case 'l': @@ -568,6 +578,12 @@ int main(int argc, char **argv) case 'P': port=strdup(optarg); break; + case 'T': + use_tcp_listener=1; + break; + case 't': + tcp_keep_ais_time = atoi(optarg); + break; case 'h': host=strdup(optarg); break; @@ -733,7 +749,7 @@ int main(int argc, char **argv) } } else{ // Internal AIS decoder - int ret=init_ais_decoder(host,port,show_levels,debug_nmea,stereo.bl_len,seconds_for_decoder_stats); + int ret=init_ais_decoder(host,port,show_levels,debug_nmea,stereo.bl_len,seconds_for_decoder_stats, use_tcp_listener, tcp_keep_ais_time); if(ret != 0){ fprintf(stderr,"Error initializing built-in AIS decoder\n"); rtlsdr_cancel_async(dev); From 59f5577504064fd075e52dd0a22f02a2ac661f57 Mon Sep 17 00:00:00 2001 From: Peter Schultz Date: Tue, 10 May 2016 22:03:55 +0200 Subject: [PATCH 035/117] Create Makefile Makefile. TODO: Has to be included in rtl-ais Makefile --- tcp_listener/Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tcp_listener/Makefile diff --git a/tcp_listener/Makefile b/tcp_listener/Makefile new file mode 100644 index 0000000..61ea5c9 --- /dev/null +++ b/tcp_listener/Makefile @@ -0,0 +1,8 @@ +libtcp_listener.a: libtcp_listener.o + ar rcs $@ $^ + +libtcp_listener.o: tcp_listener.c + gcc -c -o $@ $< + +clean: + rm -f *.o *.a From c0a521c8e75f608d6228a40eeca4eacf748b94f4 Mon Sep 17 00:00:00 2001 From: Peter Schultz Date: Tue, 10 May 2016 22:09:48 +0200 Subject: [PATCH 036/117] Add files via upload Use tcp listener service insted of udp. These files are compiles into a library and a tcp server has the advantage that more clients can access AIS data from the same server. The tcp_listener is tested under raspbian jessie on a raspberry pi with 5 concurrent connections. TODO: Fix to run under OSX and Windows. --- tcp_listener/tcp_listener.c | 375 ++++++++++++++++++++++++++++++++++++ tcp_listener/tcp_listener.h | 16 ++ 2 files changed, 391 insertions(+) create mode 100644 tcp_listener/tcp_listener.c create mode 100644 tcp_listener/tcp_listener.h diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c new file mode 100644 index 0000000..8dbcc3f --- /dev/null +++ b/tcp_listener/tcp_listener.c @@ -0,0 +1,375 @@ +// ------------------------------------------------------------ +// tcp_listener.c +// Written by Peter Schultz, hp@hpes.dk +// ------------------------------------------------------------ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct t_sockIo { + int sock; + pthread_t thread_t; + int sesion_active; + struct sockaddr_in cli_addr; + char from_ip[20]; + struct t_sockIo *next; + +} TCP_SOCK, *P_TCP_SOCK; + +static int sockfd; +static int _debug_nmea = 0; +static int _debug = 0; +static int shutdown_in_progress = 0; +static int _tcp_keep_ais_time = 15; +pthread_mutex_t lock; + +// Linked list vars. +P_TCP_SOCK head = (P_TCP_SOCK) NULL; +P_TCP_SOCK end = (P_TCP_SOCK) NULL; + +pthread_t tcp_listener_thread; + +typedef struct t_ais_mess { + char message[100]; // max on nmea message is 83 char's + int length; + struct timeval timestamp; + struct t_ais_mess *next; + +} AIS_MESS, *P_AIS_MESS; + +// Linked list ais messages. +P_AIS_MESS ais_head = (P_AIS_MESS) NULL; +P_AIS_MESS ais_end = (P_AIS_MESS) NULL; + +pthread_mutex_t ais_lock; + +// Local Prototypes +P_TCP_SOCK init_node(); +void add_node(P_TCP_SOCK new_node); +void delete_node(P_TCP_SOCK p); +int accept_c(P_TCP_SOCK p_tcp_sock); +int error_category(int rc); +static void *tcp_listener_fn(void *arg); +void *handle_remote_close(void *arg); +void delete_ais_node(P_AIS_MESS p); + +#include "tcp_listener.h" + +int initTcpSocket(const char *portnumber, int debug_nmea, int tcp_keep_ais_time) { + int portno; + _debug_nmea = debug_nmea; + _tcp_keep_ais_time = tcp_keep_ais_time; + struct sockaddr_in serv_addr; + + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + fprintf(stderr, "Failed to create socket! error %d\n", errno); + return 0; + } + bzero((char *) &serv_addr, sizeof(serv_addr)); + portno = atoi(portnumber); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = INADDR_ANY; + serv_addr.sin_port = htons(portno); + if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { + fprintf(stderr, "Failed to bind socket! error %d\n", errno); + return 0; + } + + if (listen(sockfd, MAX_TCP_CONNECTIONS) < 0) { + fprintf(stderr, "listen failed with error %d\n", errno); + return 0; + } + + pthread_create(&tcp_listener_thread, NULL, tcp_listener_fn, (void *) NULL); + + return 1; +} + +void closeTcpSocket() { + + // The easy solution is sleep for a while before shutdown completes + shutdown_in_progress = 1; + sleep(3); + shutdown( sockfd,2); + close( sockfd); +} + +// ------------------------------------------------------------ +// The main listener loop thread +// ------------------------------------------------------------ +static void *tcp_listener_fn(void *arg) { + int rc; + P_TCP_SOCK t; + + while (1) { + + t = init_node(); + + if( !shutdown_in_progress) + rc = accept_c(t); + + if (rc == -1) + break; + + if (rc == -2) { + close(t->sock); + free(t); + continue; + } + + add_node(t); + + pthread_create(&t->thread_t, NULL, handle_remote_close, (void *) t); + + } +} + +// ------------------------------------------------------------ +// thread func for hanling client close +// ------------------------------------------------------------ +void *handle_remote_close(void *arg) { + unsigned char buff[100]; + int rc; + P_TCP_SOCK t = (P_TCP_SOCK) arg; + P_AIS_MESS ais_temp = ais_head; + struct timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + setsockopt(t->sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + + // send saved ais_messages to new socket + while (ais_temp != NULL) { + rc = send(t->sock, ais_temp->message, ais_temp->length, 0); + if( _debug) + fprintf( stdout, "%d: Send to %s, <%.*s>, rc=%d\n",ais_temp->timestamp.tv_sec, t->from_ip, ais_temp->length, ais_temp->message, rc); + ais_temp = ais_temp->next; + } + + while(1){ + + rc = recv(t->sock, buff, 99, 0); + if( rc < 0) { + if( _debug) + fprintf( stdout, "Some socket error happend %d\n", errno); + } + else if( rc == 0) { + if( _debug) + fprintf( stdout, "client gracefully closed the socket\n"); + break; + } + else { + if( _debug) + fprintf( stdout, "Something receiced from client <%.*s>\n", rc, buff ); + } + } + close(t->sock); + delete_node(t); +} + + + +// ------------------------------------------------------------ +// Accept call +// ------------------------------------------------------------ +int accept_c(P_TCP_SOCK p_tcp_sock) { + + int optval = 1; // Keep alive + socklen_t optlen = sizeof(optval); + socklen_t clilen = sizeof(p_tcp_sock->cli_addr); + + /* wait for connection on local port.*/ + if ((p_tcp_sock->sock = accept(sockfd, (struct sockaddr*) &p_tcp_sock->cli_addr, &clilen)) < 0) { + fprintf(stderr, "Failed to accept socket!, error = %d\n", errno); + error_category(errno); + } + + if (setsockopt(p_tcp_sock->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) { + fprintf(stderr, "Failed to set option keepalive!, error = %d\n", errno); + error_category(errno); + } + + if (_debug) { + sprintf(p_tcp_sock->from_ip, "%.*s", 19, inet_ntoa(p_tcp_sock->cli_addr.sin_addr)); + fprintf(stdout, "connect from %s\n", p_tcp_sock->from_ip); + } + + p_tcp_sock->sesion_active = 1; + + return 0; +} + +// ------------------------------------------------------------ +// send ais message to all clients +// ------------------------------------------------------------ +int add_nmea_ais_message(const char * mess, unsigned int length) { + + P_AIS_MESS new_node; + P_AIS_MESS temp; + P_AIS_MESS temp_1; + struct timeval now; + gettimeofday(&now, NULL); + + pthread_mutex_lock(&ais_lock); + // allocate an add the new message + new_node = (P_AIS_MESS) malloc(sizeof(AIS_MESS)); + strncpy(new_node->message, mess, length); + new_node->length = length; + gettimeofday(&new_node->timestamp, NULL); + if (ais_head == NULL) { + ais_head = new_node; + ais_end = new_node; + } + ais_end->next = new_node; + new_node->next = NULL; + ais_end = new_node; + + // now get rid of old messages + temp = ais_head; + while (temp != NULL) { + if ( (now.tv_sec - temp->timestamp.tv_sec) > _tcp_keep_ais_time) { + temp_1 = temp->next; + delete_ais_node(temp); + temp = temp_1; + } else { + temp = temp->next; + } + } + + pthread_mutex_unlock(&ais_lock); + + return 0; +} + +// ------------------------------------------------------------ +// deletes the specified node pointed to by 'p' from the list +// ------------------------------------------------------------ +void delete_ais_node(P_AIS_MESS p) { + P_AIS_MESS temp; + P_AIS_MESS prev; + + temp = p; + prev = ais_head; + + if (temp == prev) { + ais_head = ais_head->next; + if (ais_end == temp) + ais_end = ais_end->next; + } else { + while (prev->next != temp) { + prev = prev->next; + } + prev->next = temp->next; + if (ais_end == temp) + ais_end = prev; + } + free(p); +} + +// ------------------------------------------------------------ +// initnode : Allocates a theads data structure +// ------------------------------------------------------------ +P_TCP_SOCK init_node() { + P_TCP_SOCK ptr; + + ptr = (P_TCP_SOCK) malloc(sizeof(TCP_SOCK)); + + memset(ptr, 0, sizeof(TCP_SOCK)); + + if (ptr == NULL) + return (P_TCP_SOCK) NULL; + else { + return ptr; + } +} + +// ------------------------------------------------------------ +// adding to end of list. +// ------------------------------------------------------------ +void add_node(P_TCP_SOCK new_node) { + pthread_mutex_lock(&lock); + if (head == NULL) { + head = new_node; + end = new_node; + } + end->next = new_node; + new_node->next = NULL; + end = new_node; + pthread_mutex_unlock(&lock); +} + +// ------------------------------------------------------------ +// deletes the specified node pointed to by 'p' from the list +// ------------------------------------------------------------ +void delete_node(P_TCP_SOCK p) { + P_TCP_SOCK temp; + P_TCP_SOCK prev; + + pthread_mutex_lock(&lock); + temp = p; + prev = head; + + if (temp == prev) { + head = head->next; + if (end == temp) + end = end->next; + } else { + while (prev->next != temp) { + prev = prev->next; + } + prev->next = temp->next; + if (end == temp) + end = prev; + } + free(p); + pthread_mutex_unlock(&lock); + +} + +// ------------------------------------------------------------------ +// Return error category. Some errors we can live with, some we can't +// ------------------------------------------------------------------ +int error_category(int rc) { + + switch (rc) { + // Fatal errors + case EINVAL: // The listen function was not invoked prior to accept. + case ENOTSOCK: // The descriptor is not a socket. + case EOPNOTSUPP: // The referenced socket is not a type that supports connection-oriented service. + case EPROTONOSUPPORT: // The specified protocol is not supported. + case EPROTOTYPE: + case EFAULT: // The addrlen parameter is too small or addr is not a valid part of the user address space. + case EADDRINUSE: // The specified address is already in use. + return -1; + + // Retry errors + case ENETDOWN: // The network subsystem has failed. + case EINTR: // The (blocking) call was canceled through WSACancelBlockingCall. + case EINPROGRESS:// A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function. + case EMFILE: // The queue is nonempty upon entry to accept and there are no descriptors available. + case ENOBUFS: // No buffer space is available. + case EWOULDBLOCK: // The socket is marked as nonblocking and no connections are present to be accepted. + case EALREADY: // A nonblocking connect call is in progress on the specified socket. + case EADDRNOTAVAIL: // The specified address is not available from the local machine. + case EAFNOSUPPORT: // Addresses in the specified family cannot be used with this socket. + case ECONNREFUSED: // The attempt to connect was forcefully rejected. + case EISCONN: // The socket is already connected (connection-oriented sockets only). + case ENETUNREACH: // The network cannot be reached from this host at this time. + case ETIMEDOUT: // Attempt to connect timed out without establishing a connection. + case EACCES:// Attempt to connect datagram socket to broadcast address failed because setsockopt option SO_BROADCAST is not enabled. + case ECONNRESET: + return -2; + + default: + // Fatal error + return -1; + + } +} + diff --git a/tcp_listener/tcp_listener.h b/tcp_listener/tcp_listener.h new file mode 100644 index 0000000..1695d9b --- /dev/null +++ b/tcp_listener/tcp_listener.h @@ -0,0 +1,16 @@ +// ------------------------------------------------------- +// tcp_listener.h +// Written by Peter Schultz, hp@hpes.dk +// ------------------------------------------------------- +#ifndef __TCP_LISTENER_H_ +#define __TCP_LISTENER_H_ + +#define MAX_TCP_CONNECTIONS 100 + +// Prototypes +int initTcpSocket( const char *portnumber, int debug_nmea, int tcp_keep_ais_time); +int add_nmea_ais_message(const char * mess, unsigned int length); +void closeTcpSocket(); + +#endif + From 95e30119c430ec9a8b4241c7562bed52087ed27f Mon Sep 17 00:00:00 2001 From: Peter Schultz Date: Tue, 10 May 2016 22:16:15 +0200 Subject: [PATCH 037/117] Create README --- tcp_listener/README | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tcp_listener/README diff --git a/tcp_listener/README b/tcp_listener/README new file mode 100644 index 0000000..0ce6977 --- /dev/null +++ b/tcp_listener/README @@ -0,0 +1,9 @@ +Use tcp listener service insted of udp. +These files are compiles into a library and a tcp server has the advantage that more clients can access AIS data from the same server. +The tcp_listener is tested under raspbian jessie on a raspberry pi with 5 concurrent connections. +Tested on OpenCPN. + +TODO: Fix to run under OSX and Windows. + Makefile must be included in rtl-ais Malefile + Merge of rtl-ais.c, aisdecoder.c and aisdecoder.h if it is found usefull. + From c4afe3fcdb32980658d5276d61401bb8afcc0e8f Mon Sep 17 00:00:00 2001 From: Peter Schultz Date: Tue, 10 May 2016 22:36:09 +0200 Subject: [PATCH 038/117] Update README --- tcp_listener/README | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tcp_listener/README b/tcp_listener/README index 0ce6977..90456e5 100644 --- a/tcp_listener/README +++ b/tcp_listener/README @@ -2,8 +2,11 @@ Use tcp listener service insted of udp. These files are compiles into a library and a tcp server has the advantage that more clients can access AIS data from the same server. The tcp_listener is tested under raspbian jessie on a raspberry pi with 5 concurrent connections. Tested on OpenCPN. +Rtl-ais runs excactly as before using udp if the -T option is not supplyed. -TODO: Fix to run under OSX and Windows. - Makefile must be included in rtl-ais Malefile +TODO: Fix to run under OSX and Windows. (I'll do the windows part if others find it usefull) + Makefile must be included in rtl-ais Makefile Merge of rtl-ais.c, aisdecoder.c and aisdecoder.h if it is found usefull. + Daemonize rtl-ais. (I'll do it if others find it usefull) + From deba4a20eceec5529838c0ebe39062355e1acf2b Mon Sep 17 00:00:00 2001 From: Peter Schultz Date: Tue, 10 May 2016 22:43:41 +0200 Subject: [PATCH 039/117] Update README --- tcp_listener/README | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tcp_listener/README b/tcp_listener/README index 90456e5..d992d18 100644 --- a/tcp_listener/README +++ b/tcp_listener/README @@ -4,6 +4,14 @@ The tcp_listener is tested under raspbian jessie on a raspberry pi with 5 concur Tested on OpenCPN. Rtl-ais runs excactly as before using udp if the -T option is not supplyed. +New options: +Use: rtl_ais [options] [outputfile] +... + [-T use TCP communication ( -h is ignored) + [-t time to keep ais messages in sec, using tcp listener (default: 15) +... + + TODO: Fix to run under OSX and Windows. (I'll do the windows part if others find it usefull) Makefile must be included in rtl-ais Makefile Merge of rtl-ais.c, aisdecoder.c and aisdecoder.h if it is found usefull. From 8e4cd047e141df59378788fd184e7c5ca3172310 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Tue, 10 May 2016 18:57:23 -0300 Subject: [PATCH 040/117] Update tcp_listener.c --- tcp_listener/tcp_listener.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c index 8dbcc3f..e8b692f 100644 --- a/tcp_listener/tcp_listener.c +++ b/tcp_listener/tcp_listener.c @@ -2,9 +2,6 @@ // tcp_listener.c // Written by Peter Schultz, hp@hpes.dk // ------------------------------------------------------------ -#include -#include -#include #include #include #include @@ -13,6 +10,15 @@ #include #include +#if defined (__WIN32__) + #include + #include +#else + #include + #include + #include +#endif + typedef struct t_sockIo { int sock; pthread_t thread_t; @@ -96,7 +102,11 @@ void closeTcpSocket() { // The easy solution is sleep for a while before shutdown completes shutdown_in_progress = 1; +#if defined (__WIN32__) + Sleep(3000); +#else sleep(3); +#endif shutdown( sockfd,2); close( sockfd); } @@ -336,7 +346,9 @@ void delete_node(P_TCP_SOCK p) { // Return error category. Some errors we can live with, some we can't // ------------------------------------------------------------------ int error_category(int rc) { - +#if defined (__WIN32__) + return -1; // Just work as sis +#else switch (rc) { // Fatal errors case EINVAL: // The listen function was not invoked prior to accept. @@ -371,5 +383,6 @@ int error_category(int rc) { return -1; } +#endif } From 2a0a7400d10c9b52ee3ed5c1906c69add9c3a7aa Mon Sep 17 00:00:00 2001 From: dgiardini Date: Tue, 10 May 2016 18:59:27 -0300 Subject: [PATCH 041/117] Correct getopt param --- rtl_ais.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtl_ais.c b/rtl_ais.c index 06a23aa..b7c8dc2 100644 --- a/rtl_ais.c +++ b/rtl_ais.c @@ -534,7 +534,7 @@ int main(int argc, char **argv) pthread_cond_init(&ready, NULL); pthread_mutex_init(&ready_m, NULL); - while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RATt:P:h:nLSi:?")) != -1) + while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RATt:P:h:nLS:?")) != -1) { switch (opt) { case 'l': From bfd2e6984026a0d553954a6c4a6d9c315720319b Mon Sep 17 00:00:00 2001 From: dgiardini Date: Tue, 10 May 2016 19:01:09 -0300 Subject: [PATCH 042/117] Add tcplistener to makefile --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3e3c2d5..59b5b81 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,8 @@ SOURCES= \ ./aisdecoder/lib/receiver.c \ ./aisdecoder/lib/protodec.c \ ./aisdecoder/lib/hmalloc.c \ - ./aisdecoder/lib/filter.c + ./aisdecoder/lib/filter.c \ + ./tcp_listener/tcp_listener.c OBJECTS=$(SOURCES:.c=.o) EXECUTABLE=rtl_ais From 3e982c3b3c839dca543e2ff02a490323ad40b985 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Tue, 10 May 2016 19:07:34 -0300 Subject: [PATCH 043/117] Update README --- README | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README b/README index df485d4..0b1b018 100644 --- a/README +++ b/README @@ -26,6 +26,8 @@ Use: rtl_ais [options] [outputfile] Built-in AIS decoder options: [-h host (default: 127.0.0.1)] [-P port (default: 10110)] + [-T use TCP communication as tcp listener ( -h is ignored)] + [-t time to keep ais messages in sec, using tcp listener (default: 15)] [-n log NMEA sentences to console (stderr) (default off)] [-L log sound levels to console (stderr) (default off)] [-S seconds_for_decoder_stats (default 0=off)] From 54096446bf6e8570861e1bd350b44fa39878d075 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Tue, 10 May 2016 19:08:01 -0300 Subject: [PATCH 044/117] Update README --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 0b1b018..05f8d50 100644 --- a/README +++ b/README @@ -1,6 +1,6 @@ ---------------------------------------------------------------------- rtl_ais, a simple AIS tuner and generic dual-frequency FM demodulator -OS: Linux-Windows. +OS: Linux-Windows-OSX. ---------------------------------------------------------------------- ------------ From 828dfcb61afc46121c0670418e3a4f2a09eb5a1e Mon Sep 17 00:00:00 2001 From: Peter Schultz Date: Thu, 12 May 2016 09:43:35 +0200 Subject: [PATCH 045/117] removed libtcp_listener.a linking is static. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 59b5b81..e9cde77 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ UNAME := $(shell uname) ifeq ($(UNAME),Linux) #Conditional for Linux CFLAGS+= $(shell pkg-config --cflags librtlsdr) -LDFLAGS+=$(shell pkg-config --libs librtlsdr) -L./tcp_listener -ltcp_listener +LDFLAGS+=$(shell pkg-config --libs librtlsdr) else # From b200d7ae4ddf8d29d19ed879260a4927bd39be2d Mon Sep 17 00:00:00 2001 From: Peter Schultz Date: Thu, 12 May 2016 22:22:57 +0200 Subject: [PATCH 046/117] Fix CLOSE_WAIT in special caces --- tcp_listener/tcp_listener.c | 132 +++++++++++++++++++++++------------- 1 file changed, 85 insertions(+), 47 deletions(-) diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c index e8b692f..72fe202 100644 --- a/tcp_listener/tcp_listener.c +++ b/tcp_listener/tcp_listener.c @@ -32,8 +32,8 @@ typedef struct t_sockIo { static int sockfd; static int _debug_nmea = 0; static int _debug = 0; -static int shutdown_in_progress = 0; static int _tcp_keep_ais_time = 15; +static int portno; pthread_mutex_t lock; // Linked list vars. @@ -65,11 +65,12 @@ int error_category(int rc); static void *tcp_listener_fn(void *arg); void *handle_remote_close(void *arg); void delete_ais_node(P_AIS_MESS p); +void remove_old_ais_messages( ); #include "tcp_listener.h" int initTcpSocket(const char *portnumber, int debug_nmea, int tcp_keep_ais_time) { - int portno; + _debug_nmea = debug_nmea; _tcp_keep_ais_time = tcp_keep_ais_time; struct sockaddr_in serv_addr; @@ -100,15 +101,15 @@ int initTcpSocket(const char *portnumber, int debug_nmea, int tcp_keep_ais_time) void closeTcpSocket() { - // The easy solution is sleep for a while before shutdown completes - shutdown_in_progress = 1; + // wait for socket shutdown complete + shutdown( sockfd,2); #if defined (__WIN32__) - Sleep(3000); + sleep(3000); #else sleep(3); #endif - shutdown( sockfd,2); - close( sockfd); + close(sockfd); + } // ------------------------------------------------------------ @@ -118,14 +119,15 @@ static void *tcp_listener_fn(void *arg) { int rc; P_TCP_SOCK t; + fprintf(stderr, "Tcp listen port %d\nAis message timeout with %d\n", portno, _tcp_keep_ais_time); + while (1) { t = init_node(); - if( !shutdown_in_progress) - rc = accept_c(t); + rc = accept_c(t); - if (rc == -1) + if ( rc == -1) break; if (rc == -2) { @@ -139,6 +141,8 @@ static void *tcp_listener_fn(void *arg) { pthread_create(&t->thread_t, NULL, handle_remote_close, (void *) t); } + shutdown( sockfd,2); + close(sockfd); } // ------------------------------------------------------------ @@ -148,13 +152,17 @@ void *handle_remote_close(void *arg) { unsigned char buff[100]; int rc; P_TCP_SOCK t = (P_TCP_SOCK) arg; - P_AIS_MESS ais_temp = ais_head; + P_AIS_MESS ais_temp; struct timeval timeout; timeout.tv_sec = 10; timeout.tv_usec = 0; setsockopt(t->sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + // get rid of old messages before send + remove_old_ais_messages(); + // send saved ais_messages to new socket + ais_temp = ais_head; while (ais_temp != NULL) { rc = send(t->sock, ais_temp->message, ais_temp->length, 0); if( _debug) @@ -166,8 +174,13 @@ void *handle_remote_close(void *arg) { rc = recv(t->sock, buff, 99, 0); if( rc < 0) { + + // check timeout + if (errno == EAGAIN) + continue; if( _debug) fprintf( stdout, "Some socket error happend %d\n", errno); + break; } else if( rc == 0) { if( _debug) @@ -177,8 +190,10 @@ void *handle_remote_close(void *arg) { else { if( _debug) fprintf( stdout, "Something receiced from client <%.*s>\n", rc, buff ); + break; } } + shutdown(t->sock, 2); close(t->sock); delete_node(t); } @@ -197,16 +212,17 @@ int accept_c(P_TCP_SOCK p_tcp_sock) { /* wait for connection on local port.*/ if ((p_tcp_sock->sock = accept(sockfd, (struct sockaddr*) &p_tcp_sock->cli_addr, &clilen)) < 0) { fprintf(stderr, "Failed to accept socket!, error = %d\n", errno); - error_category(errno); + if( errno == 22) return -1; + return error_category(errno); } if (setsockopt(p_tcp_sock->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) { fprintf(stderr, "Failed to set option keepalive!, error = %d\n", errno); - error_category(errno); + return error_category(errno); } + sprintf(p_tcp_sock->from_ip, "%.*s", 19, inet_ntoa(p_tcp_sock->cli_addr.sin_addr)); if (_debug) { - sprintf(p_tcp_sock->from_ip, "%.*s", 19, inet_ntoa(p_tcp_sock->cli_addr.sin_addr)); fprintf(stdout, "connect from %s\n", p_tcp_sock->from_ip); } @@ -215,23 +231,51 @@ int accept_c(P_TCP_SOCK p_tcp_sock) { return 0; } +// ------------------------------------------------------------ +// Remove messages older than timeout +// ------------------------------------------------------------ +void remove_old_ais_messages( ) { + struct timeval now; + P_AIS_MESS temp_1; + P_AIS_MESS temp; + gettimeofday(&now, NULL); + + temp = ais_head; + + while (temp != NULL) { + if ((int) (now.tv_sec - temp->timestamp.tv_sec) > _tcp_keep_ais_time) { + if( _debug) + fprintf(stdout, "remove mess <%.*s>, timeout %ld\n", temp->length, temp->message, (long) (now.tv_sec - temp->timestamp.tv_sec)); + temp_1 = temp->next; + pthread_mutex_lock(&ais_lock); + delete_ais_node(temp); + pthread_mutex_unlock(&ais_lock); + temp = temp_1; + } else { + temp = temp->next; + } + } +} + // ------------------------------------------------------------ // send ais message to all clients // ------------------------------------------------------------ int add_nmea_ais_message(const char * mess, unsigned int length) { P_AIS_MESS new_node; - P_AIS_MESS temp; - P_AIS_MESS temp_1; - struct timeval now; - gettimeofday(&now, NULL); + + // remove eventually old messages + remove_old_ais_messages(); pthread_mutex_lock(&ais_lock); + // allocate an add the new message new_node = (P_AIS_MESS) malloc(sizeof(AIS_MESS)); strncpy(new_node->message, mess, length); new_node->length = length; gettimeofday(&new_node->timestamp, NULL); + + if (ais_head == NULL) { ais_head = new_node; ais_end = new_node; @@ -240,18 +284,6 @@ int add_nmea_ais_message(const char * mess, unsigned int length) { new_node->next = NULL; ais_end = new_node; - // now get rid of old messages - temp = ais_head; - while (temp != NULL) { - if ( (now.tv_sec - temp->timestamp.tv_sec) > _tcp_keep_ais_time) { - temp_1 = temp->next; - delete_ais_node(temp); - temp = temp_1; - } else { - temp = temp->next; - } - } - pthread_mutex_unlock(&ais_lock); return 0; @@ -351,35 +383,41 @@ int error_category(int rc) { #else switch (rc) { // Fatal errors - case EINVAL: // The listen function was not invoked prior to accept. - case ENOTSOCK: // The descriptor is not a socket. + case EINVAL: // The listen function was not invoked prior to accept. + case ENOTSOCK: // The descriptor is not a socket. case EOPNOTSUPP: // The referenced socket is not a type that supports connection-oriented service. - case EPROTONOSUPPORT: // The specified protocol is not supported. + case EPROTONOSUPPORT: // The specified protocol is not supported. case EPROTOTYPE: - case EFAULT: // The addrlen parameter is too small or addr is not a valid part of the user address space. + case EFAULT: // The addrlen parameter is too small or addr is not a valid part of the user address space. case EADDRINUSE: // The specified address is already in use. + if( _debug) + fprintf( stderr, "Socket fatal error: %d\n", rc); return -1; - // Retry errors - case ENETDOWN: // The network subsystem has failed. - case EINTR: // The (blocking) call was canceled through WSACancelBlockingCall. - case EINPROGRESS:// A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function. - case EMFILE: // The queue is nonempty upon entry to accept and there are no descriptors available. + // Retry errors + case ENETDOWN: // The network subsystem has failed. + case EINTR: // The (blocking) call was canceled through. + case EINPROGRESS: // A blocking call is in progress, or the service provider is still processing a callback function. + case EMFILE: // The queue is nonempty upon entry to accept and there are no descriptors available. case ENOBUFS: // No buffer space is available. - case EWOULDBLOCK: // The socket is marked as nonblocking and no connections are present to be accepted. + case EWOULDBLOCK: // The socket is marked as nonblocking and no connections are present to be accepted. case EALREADY: // A nonblocking connect call is in progress on the specified socket. - case EADDRNOTAVAIL: // The specified address is not available from the local machine. - case EAFNOSUPPORT: // Addresses in the specified family cannot be used with this socket. - case ECONNREFUSED: // The attempt to connect was forcefully rejected. - case EISCONN: // The socket is already connected (connection-oriented sockets only). - case ENETUNREACH: // The network cannot be reached from this host at this time. - case ETIMEDOUT: // Attempt to connect timed out without establishing a connection. - case EACCES:// Attempt to connect datagram socket to broadcast address failed because setsockopt option SO_BROADCAST is not enabled. + case EADDRNOTAVAIL: // The specified address is not available from the local machine. + case EAFNOSUPPORT: // Addresses in the specified family cannot be used with this socket. + case ECONNREFUSED: // The attempt to connect was forcefully rejected. + case EISCONN: // The socket is already connected (connection-oriented sockets only). + case ENETUNREACH: // The network cannot be reached from this host at this time. + case ETIMEDOUT: // Attempt to connect timed out without establishing a connection. + case EACCES: // Attempt to connect datagram socket to broadcast address failed because setsockopt option SO_BROADCAST is not enabled. case ECONNRESET: + if( _debug) + fprintf( stderr, "Socket retry error: %d\n", rc); return -2; default: // Fatal error + if( _debug) + fprintf( stderr, "Socket unknown error: %d\n", rc); return -1; } From 06ce5221eb88a7354a4b75348b1a5ee6d555c9a6 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Sat, 14 May 2016 11:03:04 -0300 Subject: [PATCH 047/117] Revert "Fix CLOSE_WAIT in special caces" --- tcp_listener/tcp_listener.c | 132 +++++++++++++----------------------- 1 file changed, 47 insertions(+), 85 deletions(-) diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c index 72fe202..e8b692f 100644 --- a/tcp_listener/tcp_listener.c +++ b/tcp_listener/tcp_listener.c @@ -32,8 +32,8 @@ typedef struct t_sockIo { static int sockfd; static int _debug_nmea = 0; static int _debug = 0; +static int shutdown_in_progress = 0; static int _tcp_keep_ais_time = 15; -static int portno; pthread_mutex_t lock; // Linked list vars. @@ -65,12 +65,11 @@ int error_category(int rc); static void *tcp_listener_fn(void *arg); void *handle_remote_close(void *arg); void delete_ais_node(P_AIS_MESS p); -void remove_old_ais_messages( ); #include "tcp_listener.h" int initTcpSocket(const char *portnumber, int debug_nmea, int tcp_keep_ais_time) { - + int portno; _debug_nmea = debug_nmea; _tcp_keep_ais_time = tcp_keep_ais_time; struct sockaddr_in serv_addr; @@ -101,15 +100,15 @@ int initTcpSocket(const char *portnumber, int debug_nmea, int tcp_keep_ais_time) void closeTcpSocket() { - // wait for socket shutdown complete - shutdown( sockfd,2); + // The easy solution is sleep for a while before shutdown completes + shutdown_in_progress = 1; #if defined (__WIN32__) - sleep(3000); + Sleep(3000); #else sleep(3); #endif - close(sockfd); - + shutdown( sockfd,2); + close( sockfd); } // ------------------------------------------------------------ @@ -119,15 +118,14 @@ static void *tcp_listener_fn(void *arg) { int rc; P_TCP_SOCK t; - fprintf(stderr, "Tcp listen port %d\nAis message timeout with %d\n", portno, _tcp_keep_ais_time); - while (1) { t = init_node(); - rc = accept_c(t); + if( !shutdown_in_progress) + rc = accept_c(t); - if ( rc == -1) + if (rc == -1) break; if (rc == -2) { @@ -141,8 +139,6 @@ static void *tcp_listener_fn(void *arg) { pthread_create(&t->thread_t, NULL, handle_remote_close, (void *) t); } - shutdown( sockfd,2); - close(sockfd); } // ------------------------------------------------------------ @@ -152,17 +148,13 @@ void *handle_remote_close(void *arg) { unsigned char buff[100]; int rc; P_TCP_SOCK t = (P_TCP_SOCK) arg; - P_AIS_MESS ais_temp; + P_AIS_MESS ais_temp = ais_head; struct timeval timeout; timeout.tv_sec = 10; timeout.tv_usec = 0; setsockopt(t->sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); - // get rid of old messages before send - remove_old_ais_messages(); - // send saved ais_messages to new socket - ais_temp = ais_head; while (ais_temp != NULL) { rc = send(t->sock, ais_temp->message, ais_temp->length, 0); if( _debug) @@ -174,13 +166,8 @@ void *handle_remote_close(void *arg) { rc = recv(t->sock, buff, 99, 0); if( rc < 0) { - - // check timeout - if (errno == EAGAIN) - continue; if( _debug) fprintf( stdout, "Some socket error happend %d\n", errno); - break; } else if( rc == 0) { if( _debug) @@ -190,10 +177,8 @@ void *handle_remote_close(void *arg) { else { if( _debug) fprintf( stdout, "Something receiced from client <%.*s>\n", rc, buff ); - break; } } - shutdown(t->sock, 2); close(t->sock); delete_node(t); } @@ -212,17 +197,16 @@ int accept_c(P_TCP_SOCK p_tcp_sock) { /* wait for connection on local port.*/ if ((p_tcp_sock->sock = accept(sockfd, (struct sockaddr*) &p_tcp_sock->cli_addr, &clilen)) < 0) { fprintf(stderr, "Failed to accept socket!, error = %d\n", errno); - if( errno == 22) return -1; - return error_category(errno); + error_category(errno); } if (setsockopt(p_tcp_sock->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) { fprintf(stderr, "Failed to set option keepalive!, error = %d\n", errno); - return error_category(errno); + error_category(errno); } - sprintf(p_tcp_sock->from_ip, "%.*s", 19, inet_ntoa(p_tcp_sock->cli_addr.sin_addr)); if (_debug) { + sprintf(p_tcp_sock->from_ip, "%.*s", 19, inet_ntoa(p_tcp_sock->cli_addr.sin_addr)); fprintf(stdout, "connect from %s\n", p_tcp_sock->from_ip); } @@ -231,51 +215,23 @@ int accept_c(P_TCP_SOCK p_tcp_sock) { return 0; } -// ------------------------------------------------------------ -// Remove messages older than timeout -// ------------------------------------------------------------ -void remove_old_ais_messages( ) { - struct timeval now; - P_AIS_MESS temp_1; - P_AIS_MESS temp; - gettimeofday(&now, NULL); - - temp = ais_head; - - while (temp != NULL) { - if ((int) (now.tv_sec - temp->timestamp.tv_sec) > _tcp_keep_ais_time) { - if( _debug) - fprintf(stdout, "remove mess <%.*s>, timeout %ld\n", temp->length, temp->message, (long) (now.tv_sec - temp->timestamp.tv_sec)); - temp_1 = temp->next; - pthread_mutex_lock(&ais_lock); - delete_ais_node(temp); - pthread_mutex_unlock(&ais_lock); - temp = temp_1; - } else { - temp = temp->next; - } - } -} - // ------------------------------------------------------------ // send ais message to all clients // ------------------------------------------------------------ int add_nmea_ais_message(const char * mess, unsigned int length) { P_AIS_MESS new_node; - - // remove eventually old messages - remove_old_ais_messages(); + P_AIS_MESS temp; + P_AIS_MESS temp_1; + struct timeval now; + gettimeofday(&now, NULL); pthread_mutex_lock(&ais_lock); - // allocate an add the new message new_node = (P_AIS_MESS) malloc(sizeof(AIS_MESS)); strncpy(new_node->message, mess, length); new_node->length = length; gettimeofday(&new_node->timestamp, NULL); - - if (ais_head == NULL) { ais_head = new_node; ais_end = new_node; @@ -284,6 +240,18 @@ int add_nmea_ais_message(const char * mess, unsigned int length) { new_node->next = NULL; ais_end = new_node; + // now get rid of old messages + temp = ais_head; + while (temp != NULL) { + if ( (now.tv_sec - temp->timestamp.tv_sec) > _tcp_keep_ais_time) { + temp_1 = temp->next; + delete_ais_node(temp); + temp = temp_1; + } else { + temp = temp->next; + } + } + pthread_mutex_unlock(&ais_lock); return 0; @@ -383,41 +351,35 @@ int error_category(int rc) { #else switch (rc) { // Fatal errors - case EINVAL: // The listen function was not invoked prior to accept. - case ENOTSOCK: // The descriptor is not a socket. + case EINVAL: // The listen function was not invoked prior to accept. + case ENOTSOCK: // The descriptor is not a socket. case EOPNOTSUPP: // The referenced socket is not a type that supports connection-oriented service. - case EPROTONOSUPPORT: // The specified protocol is not supported. + case EPROTONOSUPPORT: // The specified protocol is not supported. case EPROTOTYPE: - case EFAULT: // The addrlen parameter is too small or addr is not a valid part of the user address space. + case EFAULT: // The addrlen parameter is too small or addr is not a valid part of the user address space. case EADDRINUSE: // The specified address is already in use. - if( _debug) - fprintf( stderr, "Socket fatal error: %d\n", rc); return -1; - // Retry errors - case ENETDOWN: // The network subsystem has failed. - case EINTR: // The (blocking) call was canceled through. - case EINPROGRESS: // A blocking call is in progress, or the service provider is still processing a callback function. - case EMFILE: // The queue is nonempty upon entry to accept and there are no descriptors available. + // Retry errors + case ENETDOWN: // The network subsystem has failed. + case EINTR: // The (blocking) call was canceled through WSACancelBlockingCall. + case EINPROGRESS:// A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function. + case EMFILE: // The queue is nonempty upon entry to accept and there are no descriptors available. case ENOBUFS: // No buffer space is available. - case EWOULDBLOCK: // The socket is marked as nonblocking and no connections are present to be accepted. + case EWOULDBLOCK: // The socket is marked as nonblocking and no connections are present to be accepted. case EALREADY: // A nonblocking connect call is in progress on the specified socket. - case EADDRNOTAVAIL: // The specified address is not available from the local machine. - case EAFNOSUPPORT: // Addresses in the specified family cannot be used with this socket. - case ECONNREFUSED: // The attempt to connect was forcefully rejected. - case EISCONN: // The socket is already connected (connection-oriented sockets only). - case ENETUNREACH: // The network cannot be reached from this host at this time. - case ETIMEDOUT: // Attempt to connect timed out without establishing a connection. - case EACCES: // Attempt to connect datagram socket to broadcast address failed because setsockopt option SO_BROADCAST is not enabled. + case EADDRNOTAVAIL: // The specified address is not available from the local machine. + case EAFNOSUPPORT: // Addresses in the specified family cannot be used with this socket. + case ECONNREFUSED: // The attempt to connect was forcefully rejected. + case EISCONN: // The socket is already connected (connection-oriented sockets only). + case ENETUNREACH: // The network cannot be reached from this host at this time. + case ETIMEDOUT: // Attempt to connect timed out without establishing a connection. + case EACCES:// Attempt to connect datagram socket to broadcast address failed because setsockopt option SO_BROADCAST is not enabled. case ECONNRESET: - if( _debug) - fprintf( stderr, "Socket retry error: %d\n", rc); return -2; default: // Fatal error - if( _debug) - fprintf( stderr, "Socket unknown error: %d\n", rc); return -1; } From 65f8d417a5e5b2b784ffbbd8de8bc7bddf317412 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Sat, 14 May 2016 11:38:03 -0300 Subject: [PATCH 048/117] Fix CLOSE_WAIT in special caces - Solved conflict made by myself - Add Peter's changes again - Keep portable on windows --- tcp_listener/tcp_listener.c | 131 +++++++++++++++++++++++------------- 1 file changed, 85 insertions(+), 46 deletions(-) diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c index e8b692f..15b749f 100644 --- a/tcp_listener/tcp_listener.c +++ b/tcp_listener/tcp_listener.c @@ -32,8 +32,8 @@ typedef struct t_sockIo { static int sockfd; static int _debug_nmea = 0; static int _debug = 0; -static int shutdown_in_progress = 0; static int _tcp_keep_ais_time = 15; +static int portno; pthread_mutex_t lock; // Linked list vars. @@ -65,11 +65,12 @@ int error_category(int rc); static void *tcp_listener_fn(void *arg); void *handle_remote_close(void *arg); void delete_ais_node(P_AIS_MESS p); +void remove_old_ais_messages( ); #include "tcp_listener.h" int initTcpSocket(const char *portnumber, int debug_nmea, int tcp_keep_ais_time) { - int portno; + _debug_nmea = debug_nmea; _tcp_keep_ais_time = tcp_keep_ais_time; struct sockaddr_in serv_addr; @@ -100,15 +101,16 @@ int initTcpSocket(const char *portnumber, int debug_nmea, int tcp_keep_ais_time) void closeTcpSocket() { - // The easy solution is sleep for a while before shutdown completes - shutdown_in_progress = 1; + // wait for socket shutdown complete + shutdown( sockfd,2); #if defined (__WIN32__) Sleep(3000); + closesocket(sockfd); #else sleep(3); + close(sockfd); #endif - shutdown( sockfd,2); - close( sockfd); + } // ------------------------------------------------------------ @@ -118,14 +120,15 @@ static void *tcp_listener_fn(void *arg) { int rc; P_TCP_SOCK t; + fprintf(stderr, "Tcp listen port %d\nAis message timeout with %d\n", portno, _tcp_keep_ais_time); + while (1) { t = init_node(); - if( !shutdown_in_progress) - rc = accept_c(t); + rc = accept_c(t); - if (rc == -1) + if ( rc == -1) break; if (rc == -2) { @@ -139,6 +142,8 @@ static void *tcp_listener_fn(void *arg) { pthread_create(&t->thread_t, NULL, handle_remote_close, (void *) t); } + shutdown( sockfd,2); + close(sockfd); } // ------------------------------------------------------------ @@ -148,13 +153,17 @@ void *handle_remote_close(void *arg) { unsigned char buff[100]; int rc; P_TCP_SOCK t = (P_TCP_SOCK) arg; - P_AIS_MESS ais_temp = ais_head; + P_AIS_MESS ais_temp; struct timeval timeout; timeout.tv_sec = 10; timeout.tv_usec = 0; setsockopt(t->sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + // get rid of old messages before send + remove_old_ais_messages(); + // send saved ais_messages to new socket + ais_temp = ais_head; while (ais_temp != NULL) { rc = send(t->sock, ais_temp->message, ais_temp->length, 0); if( _debug) @@ -166,8 +175,13 @@ void *handle_remote_close(void *arg) { rc = recv(t->sock, buff, 99, 0); if( rc < 0) { + + // check timeout + if (errno == EAGAIN) + continue; if( _debug) fprintf( stdout, "Some socket error happend %d\n", errno); + break; } else if( rc == 0) { if( _debug) @@ -177,8 +191,10 @@ void *handle_remote_close(void *arg) { else { if( _debug) fprintf( stdout, "Something receiced from client <%.*s>\n", rc, buff ); + break; } } + shutdown(t->sock, 2); close(t->sock); delete_node(t); } @@ -197,16 +213,17 @@ int accept_c(P_TCP_SOCK p_tcp_sock) { /* wait for connection on local port.*/ if ((p_tcp_sock->sock = accept(sockfd, (struct sockaddr*) &p_tcp_sock->cli_addr, &clilen)) < 0) { fprintf(stderr, "Failed to accept socket!, error = %d\n", errno); - error_category(errno); + if( errno == 22) return -1; + return error_category(errno); } if (setsockopt(p_tcp_sock->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) { fprintf(stderr, "Failed to set option keepalive!, error = %d\n", errno); - error_category(errno); + return error_category(errno); } + sprintf(p_tcp_sock->from_ip, "%.*s", 19, inet_ntoa(p_tcp_sock->cli_addr.sin_addr)); if (_debug) { - sprintf(p_tcp_sock->from_ip, "%.*s", 19, inet_ntoa(p_tcp_sock->cli_addr.sin_addr)); fprintf(stdout, "connect from %s\n", p_tcp_sock->from_ip); } @@ -215,23 +232,51 @@ int accept_c(P_TCP_SOCK p_tcp_sock) { return 0; } +// ------------------------------------------------------------ +// Remove messages older than timeout +// ------------------------------------------------------------ +void remove_old_ais_messages( ) { + struct timeval now; + P_AIS_MESS temp_1; + P_AIS_MESS temp; + gettimeofday(&now, NULL); + + temp = ais_head; + + while (temp != NULL) { + if ((int) (now.tv_sec - temp->timestamp.tv_sec) > _tcp_keep_ais_time) { + if( _debug) + fprintf(stdout, "remove mess <%.*s>, timeout %ld\n", temp->length, temp->message, (long) (now.tv_sec - temp->timestamp.tv_sec)); + temp_1 = temp->next; + pthread_mutex_lock(&ais_lock); + delete_ais_node(temp); + pthread_mutex_unlock(&ais_lock); + temp = temp_1; + } else { + temp = temp->next; + } + } +} + // ------------------------------------------------------------ // send ais message to all clients // ------------------------------------------------------------ int add_nmea_ais_message(const char * mess, unsigned int length) { P_AIS_MESS new_node; - P_AIS_MESS temp; - P_AIS_MESS temp_1; - struct timeval now; - gettimeofday(&now, NULL); + + // remove eventually old messages + remove_old_ais_messages(); pthread_mutex_lock(&ais_lock); + // allocate an add the new message new_node = (P_AIS_MESS) malloc(sizeof(AIS_MESS)); strncpy(new_node->message, mess, length); new_node->length = length; gettimeofday(&new_node->timestamp, NULL); + + if (ais_head == NULL) { ais_head = new_node; ais_end = new_node; @@ -240,18 +285,6 @@ int add_nmea_ais_message(const char * mess, unsigned int length) { new_node->next = NULL; ais_end = new_node; - // now get rid of old messages - temp = ais_head; - while (temp != NULL) { - if ( (now.tv_sec - temp->timestamp.tv_sec) > _tcp_keep_ais_time) { - temp_1 = temp->next; - delete_ais_node(temp); - temp = temp_1; - } else { - temp = temp->next; - } - } - pthread_mutex_unlock(&ais_lock); return 0; @@ -351,35 +384,41 @@ int error_category(int rc) { #else switch (rc) { // Fatal errors - case EINVAL: // The listen function was not invoked prior to accept. - case ENOTSOCK: // The descriptor is not a socket. + case EINVAL: // The listen function was not invoked prior to accept. + case ENOTSOCK: // The descriptor is not a socket. case EOPNOTSUPP: // The referenced socket is not a type that supports connection-oriented service. - case EPROTONOSUPPORT: // The specified protocol is not supported. + case EPROTONOSUPPORT: // The specified protocol is not supported. case EPROTOTYPE: - case EFAULT: // The addrlen parameter is too small or addr is not a valid part of the user address space. + case EFAULT: // The addrlen parameter is too small or addr is not a valid part of the user address space. case EADDRINUSE: // The specified address is already in use. + if( _debug) + fprintf( stderr, "Socket fatal error: %d\n", rc); return -1; - // Retry errors - case ENETDOWN: // The network subsystem has failed. - case EINTR: // The (blocking) call was canceled through WSACancelBlockingCall. - case EINPROGRESS:// A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function. - case EMFILE: // The queue is nonempty upon entry to accept and there are no descriptors available. + // Retry errors + case ENETDOWN: // The network subsystem has failed. + case EINTR: // The (blocking) call was canceled through. + case EINPROGRESS: // A blocking call is in progress, or the service provider is still processing a callback function. + case EMFILE: // The queue is nonempty upon entry to accept and there are no descriptors available. case ENOBUFS: // No buffer space is available. - case EWOULDBLOCK: // The socket is marked as nonblocking and no connections are present to be accepted. + case EWOULDBLOCK: // The socket is marked as nonblocking and no connections are present to be accepted. case EALREADY: // A nonblocking connect call is in progress on the specified socket. - case EADDRNOTAVAIL: // The specified address is not available from the local machine. - case EAFNOSUPPORT: // Addresses in the specified family cannot be used with this socket. - case ECONNREFUSED: // The attempt to connect was forcefully rejected. - case EISCONN: // The socket is already connected (connection-oriented sockets only). - case ENETUNREACH: // The network cannot be reached from this host at this time. - case ETIMEDOUT: // Attempt to connect timed out without establishing a connection. - case EACCES:// Attempt to connect datagram socket to broadcast address failed because setsockopt option SO_BROADCAST is not enabled. + case EADDRNOTAVAIL: // The specified address is not available from the local machine. + case EAFNOSUPPORT: // Addresses in the specified family cannot be used with this socket. + case ECONNREFUSED: // The attempt to connect was forcefully rejected. + case EISCONN: // The socket is already connected (connection-oriented sockets only). + case ENETUNREACH: // The network cannot be reached from this host at this time. + case ETIMEDOUT: // Attempt to connect timed out without establishing a connection. + case EACCES: // Attempt to connect datagram socket to broadcast address failed because setsockopt option SO_BROADCAST is not enabled. case ECONNRESET: + if( _debug) + fprintf( stderr, "Socket retry error: %d\n", rc); return -2; default: // Fatal error + if( _debug) + fprintf( stderr, "Socket unknown error: %d\n", rc); return -1; } From b873b74e6868a9084cc455b278229b1735892163 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Sat, 14 May 2016 20:18:52 -0300 Subject: [PATCH 049/117] Bugs fixed for Windows - WSAStartup() added; the socket can't be opened on Windows whitout this. - Mutex inited, the program doesn't crash anymore on every new connection. - Use closesocket() intedad close() for sockets. Function close() is useles for sockets on Windows becasue remains open.Fixed. - Tested and working with OpenCPN --- tcp_listener/tcp_listener.c | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c index 15b749f..d8a5a34 100644 --- a/tcp_listener/tcp_listener.c +++ b/tcp_listener/tcp_listener.c @@ -34,7 +34,7 @@ static int _debug_nmea = 0; static int _debug = 0; static int _tcp_keep_ais_time = 15; static int portno; -pthread_mutex_t lock; +pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;; // Linked list vars. P_TCP_SOCK head = (P_TCP_SOCK) NULL; @@ -54,7 +54,7 @@ typedef struct t_ais_mess { P_AIS_MESS ais_head = (P_AIS_MESS) NULL; P_AIS_MESS ais_end = (P_AIS_MESS) NULL; -pthread_mutex_t ais_lock; +pthread_mutex_t ais_lock=PTHREAD_MUTEX_INITIALIZER;; // Local Prototypes P_TCP_SOCK init_node(); @@ -74,7 +74,12 @@ int initTcpSocket(const char *portnumber, int debug_nmea, int tcp_keep_ais_time) _debug_nmea = debug_nmea; _tcp_keep_ais_time = tcp_keep_ais_time; struct sockaddr_in serv_addr; - +#if defined (__WIN32__) + WSADATA wsaData; + WORD wVersionRequested; + wVersionRequested = MAKEWORD(2, 2); + WSAStartup(wVersionRequested, &wsaData); +#endif if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Failed to create socket! error %d\n", errno); return 0; @@ -132,18 +137,26 @@ static void *tcp_listener_fn(void *arg) { break; if (rc == -2) { +#if defined (__WIN32__) + closesocket(t->sock); +#else close(t->sock); +#endif free(t); continue; } - add_node(t); - pthread_create(&t->thread_t, NULL, handle_remote_close, (void *) t); } - shutdown( sockfd,2); - close(sockfd); + shutdown( sockfd,2); +#if defined (__WIN32__) + closesocket(t->sock); +#else + close(t->sock); +#endif + + } // ------------------------------------------------------------ @@ -195,7 +208,11 @@ void *handle_remote_close(void *arg) { } } shutdown(t->sock, 2); - close(t->sock); +#if defined (__WIN32__) + closesocket(t->sock); +#else + close(t->sock); +#endif delete_node(t); } From 95410e7e1c5e9eaeaec35dee44d75ef6a5c93260 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Sat, 14 May 2016 20:24:30 -0300 Subject: [PATCH 050/117] Clean Windows rtl_ais.exe too --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e9cde77..710a16c 100644 --- a/Makefile +++ b/Makefile @@ -59,4 +59,4 @@ $(EXECUTABLE): $(OBJECTS) $(CC) -c $< -o $@ $(CFLAGS) clean: - rm -f $(OBJECTS) $(EXECUTABLE) + rm -f $(OBJECTS) $(EXECUTABLE) $(EXECUTABLE).exe From a54ab00723aa48fd9a7efc3593d1b0fe27b85915 Mon Sep 17 00:00:00 2001 From: Sean D'Epagnier Date: Thu, 2 Jun 2016 15:54:51 -0400 Subject: [PATCH 051/117] initial support for using rtl_ais as a library This is implemented and used in the rtlsdr_pi opencpn plugin The send tcp, and library queues should be combined and code shared the aisdecoder should be encapsulated with a context like rtl_ais to allow for multiple interfaces to work in the same process --- Makefile | 2 +- aisdecoder/aisdecoder.c | 75 ++++- aisdecoder/aisdecoder.h | 1 + aisdecoder/sounddecoder.c | 4 +- convenience.h | 4 + main.c | 217 +++++++++++++ rtl_ais.c | 636 +++++++++++++++----------------------- rtl_ais.h | 46 +++ 8 files changed, 591 insertions(+), 394 deletions(-) create mode 100644 main.c create mode 100644 rtl_ais.h diff --git a/Makefile b/Makefile index 710a16c..12c0133 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ endif CC?=gcc SOURCES= \ - rtl_ais.c convenience.c \ + main.c rtl_ais.c convenience.c \ ./aisdecoder/aisdecoder.c \ ./aisdecoder/sounddecoder.c \ ./aisdecoder/lib/receiver.c \ diff --git a/aisdecoder/aisdecoder.c b/aisdecoder/aisdecoder.c index 0f9a8c4..75d9ae9 100644 --- a/aisdecoder/aisdecoder.c +++ b/aisdecoder/aisdecoder.c @@ -37,7 +37,7 @@ #include //#include "config.h" #include "sounddecoder.h" -#include "callbacks.h" +#include "lib/callbacks.h" #include "../tcp_listener/tcp_listener.h" #define MAX_BUFFER_LENGTH 2048 @@ -53,6 +53,58 @@ static int sock; static int use_tcp = 0; static struct addrinfo* addr=NULL; +// messages can be retrived from a different thread +static pthread_mutex_t message_mutex; + +// queue of decoded ais messages +struct ais_message { + char *buffer; + struct ais_message *next; +} *ais_messages_head, *ais_messages_tail, *last_message; + +static void append_message(const char *buffer) +{ + struct ais_message *m = malloc(sizeof *m); + + m->buffer = strdup(buffer); + m->next = NULL; + pthread_mutex_lock(&message_mutex); + + // enqueue + if(!ais_messages_head) + ais_messages_head = m; + else + ais_messages_tail->next = m; + ais_messages_tail = m; + pthread_mutex_unlock(&message_mutex); +} + +static void free_message(struct ais_message *m) +{ + if(m) { + free(m->buffer); + free(m); + } +} + +const char *aisdecoder_next_message() +{ + free_message(last_message); + last_message = NULL; + + pthread_mutex_lock(&message_mutex); + if(!ais_messages_head) { + pthread_mutex_unlock(&message_mutex); + return NULL; + } + + // dequeue + last_message = ais_messages_head; + ais_messages_head = ais_messages_head->next; + + pthread_mutex_unlock(&message_mutex); + return last_message->buffer; +} static int initSocket(const char *host, const char *portname); int send_nmea( const char *sentence, unsigned int length); @@ -68,6 +120,8 @@ void nmea_sentence_received(const char *sentence, unsigned int length, unsigned char sentences, unsigned char sentencenum) { + append_message(sentence); + if (sentences == 1) { if (send_nmea( sentence, length) == -1) abort(); if (debug_nmea) fprintf(stderr, "%s", sentence); @@ -91,20 +145,22 @@ int send_nmea( const char *sentence, unsigned int length) { if( use_tcp) { return add_nmea_ais_message(sentence, length); } - else { + else if(sock) { return sendto(sock, sentence, length, 0, addr->ai_addr, addr->ai_addrlen); } + return 0; } int init_ais_decoder(char * host, char * port ,int show_levels,int _debug_nmea,int buf_len,int time_print_stats, int use_tcp_listener, int tcp_keep_ais_time){ debug_nmea=_debug_nmea; use_tcp = use_tcp_listener; + pthread_mutex_init(&message_mutex, NULL); if(debug_nmea) fprintf(stderr,"Log NMEA sentences to console ON\n"); else fprintf(stderr,"Log NMEA sentences to console OFF\n"); if( !use_tcp_listener) { - if (!initSocket(host, port)) { + if (host && port && !initSocket(host, port)) { return EXIT_FAILURE; } } @@ -125,6 +181,19 @@ void run_rtlais_decoder(short * buff, int len) } int free_ais_decoder(void) { + pthread_mutex_destroy(&message_mutex); + + // free all stored messa ages + free_message(last_message); + last_message = NULL; + + while(ais_messages_head) { + struct ais_message *m = ais_messages_head; + ais_messages_head = ais_messages_head->next; + + free_message(m); + } + freeSoundDecoder(); freeaddrinfo(addr); #ifdef WIN32 diff --git a/aisdecoder/aisdecoder.h b/aisdecoder/aisdecoder.h index 6d56b95..3322ac9 100644 --- a/aisdecoder/aisdecoder.h +++ b/aisdecoder/aisdecoder.h @@ -2,6 +2,7 @@ #define __AIS_RL_AIS_INC_ int init_ais_decoder(char * host, char * port,int show_levels,int _debug_nmea,int buf_len,int time_print_stats, int use_tcp_listener, int tcp_keep_ais_time); void run_rtlais_decoder(short * buff, int len); +const char *aisdecoder_next_message(); int free_ais_decoder(void); #endif diff --git a/aisdecoder/sounddecoder.c b/aisdecoder/sounddecoder.c index d12a30d..713acb7 100644 --- a/aisdecoder/sounddecoder.c +++ b/aisdecoder/sounddecoder.c @@ -22,8 +22,8 @@ #include #endif -#include "receiver.h" -#include "hmalloc.h" +#include "lib/receiver.h" +#include "lib/hmalloc.h" #define MAX_FILENAME_SIZE 512 #define ERROR_MESSAGE_LENGTH 1024 diff --git a/convenience.h b/convenience.h index 088e755..d194b87 100644 --- a/convenience.h +++ b/convenience.h @@ -17,6 +17,10 @@ /* a collection of user friendly tools */ +#include +//struct rtlsdr_dev_t; +//typedef struct rtlsdr_dev_t rtlsdr_dev_t; + /*! * Convert standard suffixes (k, M, G) to double * diff --git a/main.c b/main.c new file mode 100644 index 0000000..15544b9 --- /dev/null +++ b/main.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2012 by Kyle Keen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +typedef void* rtlsdr_dev_t; +#include "convenience.h" +#include "rtl_ais.h" + +void usage(void) +{ + fprintf(stderr, + "rtl_ais, a simple AIS tuner\n" + "\t and generic dual-frequency FM demodulator\n\n" + "(probably not a good idea to use with e4000 tuners)\n" + "Use: rtl_ais [options] [outputfile]\n" + "\t[-l left_frequency (default: 161.975M)]\n" + "\t[-r right_frequency (default: 162.025M)]\n" + "\t left freq < right freq\n" + "\t frequencies must be within 1.2MHz\n" + "\t[-s sample_rate (default: 24k)]\n" + "\t maximum value, might be down to 12k\n" + "\t[-o output_rate (default: 48k)]\n" + "\t must be equal or greater than twice -s value\n" + "\t[-E toggle edge tuning (default: off)]\n" + "\t[-D toggle DC filter (default: on)]\n" + //"\t[-O toggle oversampling (default: off)\n" + "\t[-d device_index (default: 0)]\n" + "\t[-g tuner_gain (default: automatic)]\n" + "\t[-p ppm_error (default: 0)]\n" + "\t[-R enable RTL chip AGC (default: off)]\n" + "\t[-A turn off built-in AIS decoder (default: on)]\n" + "\t use this option to output samples to file or stdout.\n" + "\tBuilt-in AIS decoder options:\n" + "\t[-h host (default: 127.0.0.1)]\n" + "\t[-P port (default: 10110)]\n" + "\t[-n log NMEA sentences to console (stderr) (default off)]\n" + "\t[-L log sound levels to console (stderr) (default off)]\n\n" + "\t[-S seconds_for_decoder_stats (default 0=off)]\n\n" + "\tWhen the built-in AIS decoder is disabled the samples are sent to\n" + "\tto [outputfile] (a '-' dumps samples to stdout)\n" + "\t omitting the filename also uses stdout\n\n" + "\tOutput is stereo 2x16 bit signed ints\n\n" + "\tExamples:\n" + "\tReceive AIS traffic,sent UDP NMEA sentences to 127.0.0.1 port 10110\n" + "\t and log the senteces to console:\n\n" + "\trtl_ais -n\n\n" + "\tTune two fm stations and play one on each channel:\n\n" + "\trtl_ais -l233.15M -r233.20M -A | play -r48k -traw -es -b16 -c2 -V1 - " + "\n"); + exit(1); +} + +static volatile int do_exit = 0; +static void sighandler(int signum) +{ + signum = signum; + fprintf(stderr, "Signal caught, exiting!\n"); + do_exit = 1; +} + +int main(int argc, char **argv) +{ +#ifndef WIN32 + struct sigaction sigact; + + sigact.sa_handler = sighandler; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGPIPE, &sigact, NULL); +#else + signal(SIGINT, sighandler); + signal(SIGTERM, sighandler); +#endif + int opt; + + struct rtl_ais_config config; + rtl_ais_default_config(&config); + + config.host = strdup("127.0.0.1"); + config.port = strdup("10110"); + + while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RAP:h:nLS:?")) != -1) + { + switch (opt) { + case 'l': + config.left_freq = (int)atofs(optarg); + break; + case 'r': + config.right_freq = (int)atofs(optarg); + break; + case 's': + config.sample_rate = (int)atofs(optarg); + break; + case 'o': + config.output_rate = (int)atofs(optarg); + break; + case 'E': + config.edge = !config.edge; + break; + case 'D': + config.dc_filter = !config.dc_filter; + break; + case 'O': + config.oversample = !config.oversample; + break; + case 'd': + config.dev_index = verbose_device_search(optarg); + config.dev_given = 1; + break; + case 'g': + config.gain = (int)(atof(optarg) * 10); + break; + case 'p': + config.ppm_error = atoi(optarg); + config.custom_ppm = 1; + break; + case 'R': + config.rtl_agc=1; + break; + case 'A': + config.use_internal_aisdecoder=0; + break; + case 'P': + config.port=strdup(optarg); + break; + case 'T': + config.use_tcp_listener=1; + break; + case 't': + config.tcp_keep_ais_time = atoi(optarg); + break; + case 'h': + config.host=strdup(optarg); + break; + case 'L': + config.show_levels=1; + break; + case 'S': + config.seconds_for_decoder_stats=atoi(optarg); + break; + case 'n': + config.debug_nmea = 1; + break; + case '?': + default: + usage(); + return 2; + } + } + + if (argc <= optind) { + config.filename = "-"; + } else { + config.filename = argv[optind]; + } + + if (config.edge) { + fprintf(stderr, "Edge tuning enabled.\n"); + } else { + fprintf(stderr, "Edge tuning disabled.\n"); + } + if (config.dc_filter) { + fprintf(stderr, "DC filter enabled.\n"); + } else { + fprintf(stderr, "DC filter disabled.\n"); + } + if (config.rtl_agc) { + fprintf(stderr, "RTL AGC enabled.\n"); + } else { + fprintf(stderr, "RTL AGC disabled.\n"); + } + if (config.use_internal_aisdecoder) { + fprintf(stderr, "Internal AIS decoder enabled.\n"); + } else { + fprintf(stderr, "Internal AIS decoder disabled.\n"); + } + + struct rtl_ais_context *ctx = rtl_ais_start(&config); + if(!ctx) { + fprintf(stderr, "\nrtl_ais_start failed, exiting...\n"); + exit(1); + } + + // loop printing received ais messages to console + while(!do_exit && rtl_ais_isactive(ctx)) { + const char *str; + while((str = rtl_ais_next_message(ctx))) + puts(str); + + usleep(50000); + } + + rtl_ais_cleanup(ctx); + return 0; +} diff --git a/rtl_ais.c b/rtl_ais.c index b7c8dc2..7420ce4 100644 --- a/rtl_ais.c +++ b/rtl_ais.c @@ -26,24 +26,22 @@ * alsa integration * better upsampler (libsamplerate?) * windows support - * ais decoder */ #include -#include -#include #include #include +#include #include -#include #ifdef WIN32 #include #endif - #include #include + +#include "rtl_ais.h" #include "convenience.h" #include "aisdecoder/aisdecoder.h" @@ -51,20 +49,6 @@ #define DEFAULT_BUF_LENGTH (16 * 16384) #define AUTO_GAIN -100 -static pthread_t demod_thread; -static pthread_cond_t ready; -static pthread_mutex_t ready_m; -static volatile int do_exit = 0; -static rtlsdr_dev_t *dev = NULL; - -/* todo, less globals */ -int16_t *merged; -int merged_len; -FILE *file=NULL; -int oversample = 0; -int dc_filter = 1; -int use_internal_aisdecoder=1; -int seconds_for_decoder_stats=0; /* signals are not threadsafe by default */ #define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m) #define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m) @@ -110,75 +94,6 @@ struct upsample_stereo int rate; }; -/* complex iq pairs */ -struct downsample_state both; -struct downsample_state left; -struct downsample_state right; -/* iq pairs and real mono */ -struct demod_state left_demod; -struct demod_state right_demod; -/* real stereo pairs (upsampled) */ -struct upsample_stereo stereo; - -void usage(void) -{ - fprintf(stderr, - "rtl_ais, a simple AIS tuner\n" - "\t and generic dual-frequency FM demodulator\n\n" - "(probably not a good idea to use with e4000 tuners)\n" - "Use: rtl_ais [options] [outputfile]\n" - "\t[-l left_frequency (default: 161.975M)]\n" - "\t[-r right_frequency (default: 162.025M)]\n" - "\t left freq < right freq\n" - "\t frequencies must be within 1.2MHz\n" - "\t[-s sample_rate (default: 24k)]\n" - "\t maximum value, might be down to 12k\n" - "\t[-o output_rate (default: 48k)]\n" - "\t must be equal or greater than twice -s value\n" - "\t[-E toggle edge tuning (default: off)]\n" - "\t[-D toggle DC filter (default: on)]\n" - //"\t[-O toggle oversampling (default: off)\n" - "\t[-d device_index (default: 0)]\n" - "\t[-g tuner_gain (default: automatic)]\n" - "\t[-p ppm_error (default: 0)]\n" - "\t[-R enable RTL chip AGC (default: off)]\n" - "\t[-A turn off built-in AIS decoder (default: on)]\n" - "\t use this option to output samples to file or stdout.\n" - "\tBuilt-in AIS decoder options:\n" - "\t[-h host (default: 127.0.0.1)]\n" - "\t[-P port (default: 10110)]\n" - "\t[-T use TCP communication ( -h is ignored)\n" - "\t[-t time to keep ais messages in sec, using tcp listener (default: 15)\n" - "\t[-n log NMEA sentences to console (stderr) (default off)]\n" - "\t[-L log sound levels to console (stderr) (default off)]\n\n" - "\t[-S seconds_for_decoder_stats (default 0=off)]\n\n" - "\tWhen the built-in AIS decoder is disabled the samples are sent to\n" - "\tto [outputfile] (a '-' dumps samples to stdout)\n" - "\t omitting the filename also uses stdout\n\n" - "\tOutput is stereo 2x16 bit signed ints\n\n" - "\tExamples:\n" - "\tReceive AIS traffic,sent UDP NMEA sentences to 127.0.0.1 port 10110\n" - "\t and log the senteces to console:\n\n" - "\trtl_ais -n\n\n" - "\tTune two fm stations and play one on each channel:\n\n" - "\trtl_ais -l233.15M -r233.20M -A | play -r48k -traw -es -b16 -c2 -V1 - " - "\n"); - exit(1); -} - -static void sighandler(int signum) -{ - switch( signum) { - case 13: // Ignore sig 13, because of write to closed socket when running TCP - fprintf(stderr, "Broken pipe signal caught\n"); - break; - default: - closeTcpSocket(); - fprintf(stderr, "Signal %d caught, exiting!\n", signum); - do_exit = 1; - rtlsdr_cancel_async(dev); - } -} int cic_9_tables[][10] = { {0,}, {9, -156, -97, 2798, -15489, 61019, -15489, 2798, -97, -156}, @@ -194,7 +109,7 @@ int cic_9_tables[][10] = { }; -void rotate_90(int16_t *buf, int len) +static void rotate_90(int16_t *buf, int len) /* 90 rotation is 1+0j, 0+1j, -1+0j, 0-1j or [0, 1, -3, 2, -4, -5, 7, -6] */ { @@ -214,7 +129,7 @@ void rotate_90(int16_t *buf, int len) } } -void rotate_m90(int16_t *buf, int len) +static void rotate_m90(int16_t *buf, int len) /* -90 rotation is 1+0j, 0-1j, -1+0j, 0+1j or [0, 1, 3, -2, -4, -5, -7, 6] */ { @@ -234,7 +149,7 @@ void rotate_m90(int16_t *buf, int len) } } -void fifth_order(int16_t *data, int length, int16_t *hist) +static void fifth_order(int16_t *data, int length, int16_t *hist) /* for half of interleaved data */ { int i; @@ -265,7 +180,7 @@ void fifth_order(int16_t *data, int length, int16_t *hist) hist[5] = f; } -void generic_fir(int16_t *data, int length, int *fir, int16_t *hist) +static void generic_fir(int16_t *data, int length, const int *fir, int16_t *hist) /* Okay, not at all generic. Assumes length 9, fix that eventually. */ { int d, temp, sum; @@ -290,7 +205,7 @@ void generic_fir(int16_t *data, int length, int *fir, int16_t *hist) } } -void downsample(struct downsample_state *d) +static void downsample(struct downsample_state *d) { int i, ds_p; ds_p = d->downsample_passes; @@ -304,13 +219,14 @@ void downsample(struct downsample_state *d) generic_fir(d->buf+1, (d->len_in>> ds_p)-1,cic_9_tables[ds_p], d->droop_q_hist); } -void multiply(int ar, int aj, int br, int bj, int *cr, int *cj) +static void multiply(int ar, int aj, int br, int bj, int *cr, int *cj) { *cr = ar*br - aj*bj; *cj = aj*br + ar*bj; } -int polar_discriminant(int ar, int aj, int br, int bj) +#if 0 // not used +static int polar_discriminant(int ar, int aj, int br, int bj) { int cr, cj; double angle; @@ -318,8 +234,9 @@ int polar_discriminant(int ar, int aj, int br, int bj) angle = atan2((double)cj, (double)cr); return (int)(angle / 3.14159 * (1<<14)); } +#endif -int fast_atan2(int y, int x) +static int fast_atan2(int y, int x) /* pre scaled for int16 */ { int yabs, angle; @@ -342,14 +259,14 @@ int fast_atan2(int y, int x) return angle; } -int polar_disc_fast(int ar, int aj, int br, int bj) +static int polar_disc_fast(int ar, int aj, int br, int bj) { int cr, cj; multiply(ar, aj, br, -bj, &cr, &cj); return fast_atan2(cj, cr); } -void demodulate(struct demod_state *d) +static void demodulate(struct demod_state *d) { int i, pcm; int16_t *buf = d->buf; @@ -368,7 +285,7 @@ void demodulate(struct demod_state *d) d->pre_j = buf[d->buf_len - 1]; } -void dc_block_filter(struct demod_state *d) +static void dc_block_filter(struct demod_state *d) { int i, avg; int64_t sum = 0; @@ -384,7 +301,7 @@ void dc_block_filter(struct demod_state *d) d->dc_avg = avg; } -void arbitrary_upsample(int16_t *buf1, int16_t *buf2, int len1, int len2) +static void arbitrary_upsample(int16_t *buf1, int16_t *buf2, int len1, int len2) /* linear interpolation, len1 < len2 */ { int i = 1; @@ -407,76 +324,109 @@ void arbitrary_upsample(int16_t *buf1, int16_t *buf2, int len1, int len2) } } -static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) +struct rtl_ais_context { - int i; - if (do_exit) { + int active, dc_filter, use_internal_aisdecoder; + + pthread_t demod_thread; + pthread_t rtlsdr_thread; + + pthread_cond_t ready; + pthread_mutex_t ready_m; + + rtlsdr_dev_t *dev; + FILE *file; + + /* complex iq pairs */ + struct downsample_state both; + struct downsample_state left; + struct downsample_state right; + /* iq pairs and real mono */ + struct demod_state left_demod; + struct demod_state right_demod; + /* real stereo pairs (upsampled) */ + struct upsample_stereo stereo; +}; + +static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *arg) +{ + struct rtl_ais_context *ctx = arg; + unsigned i; + if (!ctx->active) { return;} - pthread_rwlock_wrlock(&both.rw); - for (i=0; iboth.rw); + for (i=0; iboth.buf[i] = ((int16_t)buf[i]) - 127; + + pthread_rwlock_unlock(&ctx->both.rw); + safe_cond_signal(&ctx->ready, &ctx->ready_m); } -void pre_output(void) +static void *rtlsdr_thread_fn(void *arg) { - int i; - for (i=0; idev, rtlsdr_callback, arg, + DEFAULT_ASYNC_BUF_NUMBER, + DEFAULT_BUF_LENGTH); + + ctx->active = 0; + return 0; } -void output(void) + +static void pre_output(struct rtl_ais_context *ctx) { - fwrite(stereo.result, 2, stereo.result_len, file); + int i; + for (i=0; istereo.bl_len; i++) { + ctx->stereo.result[i*2] = ctx->stereo.buf_left[i]; + ctx->stereo.result[i*2+1] = ctx->stereo.buf_right[i]; + } } static void *demod_thread_fn(void *arg) { - while (!do_exit) { - safe_cond_wait(&ready, &ready_m); - pthread_rwlock_wrlock(&both.rw); - downsample(&both); - memcpy(left.buf, both.buf, 2*both.len_out); - memcpy(right.buf, both.buf, 2*both.len_out); - pthread_rwlock_unlock(&both.rw); - rotate_90(left.buf, left.len_in); - downsample(&left); - memcpy(left_demod.buf, left.buf, 2*left.len_out); - demodulate(&left_demod); - if (dc_filter) { - dc_block_filter(&left_demod);} + struct rtl_ais_context *ctx = arg; + while (ctx->active) { + safe_cond_wait(&ctx->ready, &ctx->ready_m); + pthread_rwlock_wrlock(&ctx->both.rw); + downsample(&ctx->both); + memcpy(ctx->left.buf, ctx->both.buf, 2*ctx->both.len_out); + memcpy(ctx->right.buf, ctx->both.buf, 2*ctx->both.len_out); + pthread_rwlock_unlock(&ctx->both.rw); + rotate_90(ctx->left.buf, ctx->left.len_in); + downsample(&ctx->left); + memcpy(ctx->left_demod.buf, ctx->left.buf, 2*ctx->left.len_out); + demodulate(&ctx->left_demod); + if (ctx->dc_filter) { + dc_block_filter(&ctx->left_demod);} //if (oversample) { // downsample(&left);} //fprintf(stderr,"\nUpsample result_len:%d stereo.bl_len:%d :%f\n",left_demod.result_len,stereo.bl_len,(float)stereo.bl_len/(float)left_demod.result_len); - arbitrary_upsample(left_demod.result, stereo.buf_left, left_demod.result_len, stereo.bl_len); - rotate_m90(right.buf, right.len_in); - downsample(&right); - memcpy(right_demod.buf, right.buf, 2*right.len_out); - demodulate(&right_demod); - if (dc_filter) { - dc_block_filter(&right_demod);} + arbitrary_upsample(ctx->left_demod.result, ctx->stereo.buf_left, ctx->left_demod.result_len, ctx->stereo.bl_len); + rotate_m90(ctx->right.buf, ctx->right.len_in); + downsample(&ctx->right); + memcpy(ctx->right_demod.buf, ctx->right.buf, 2*ctx->right.len_out); + demodulate(&ctx->right_demod); + if (ctx->dc_filter) { + dc_block_filter(&ctx->right_demod);} //if (oversample) { // downsample(&right);} - arbitrary_upsample(right_demod.result, stereo.buf_right, right_demod.result_len, stereo.br_len); - pre_output(); - if(use_internal_aisdecoder){ + arbitrary_upsample(ctx->right_demod.result, ctx->stereo.buf_right, ctx->right_demod.result_len, ctx->stereo.br_len); + pre_output(ctx); + if(ctx->use_internal_aisdecoder){ // stereo.result -> int_16 // stereo.result_len -> number of samples for each channel - run_rtlais_decoder(stereo.result,stereo.result_len); + run_rtlais_decoder(ctx->stereo.result,ctx->stereo.result_len); } else{ - output(); + fwrite(ctx->stereo.result, 2, ctx->stereo.result_len, ctx->file); } } - rtlsdr_cancel_async(dev); - free_ais_decoder(); + + free_ais_decoder(); return 0; } -void downsample_init(struct downsample_state *dss) +static void downsample_init(struct downsample_state *dss) /* simple ints should be already set */ { int i, j; @@ -492,140 +442,60 @@ void downsample_init(struct downsample_state *dss) pthread_rwlock_init(&dss->rw, NULL); } -void demod_init(struct demod_state *ds) +static void demod_init(struct demod_state *ds) { ds->buf = malloc(ds->buf_len * sizeof(int16_t)); ds->result = malloc(ds->result_len * sizeof(int16_t)); } -void stereo_init(struct upsample_stereo *us) +static void stereo_init(struct upsample_stereo *us) { us->buf_left = malloc(us->bl_len * sizeof(int16_t)); us->buf_right = malloc(us->br_len * sizeof(int16_t)); us->result = malloc(us->result_len * sizeof(int16_t)); } -int main(int argc, char **argv) +void rtl_ais_default_config(struct rtl_ais_config *config) { -#ifndef WIN32 - struct sigaction sigact; -#endif - char *filename = NULL; - int r, opt; - int i, gain = AUTO_GAIN; /* tenths of a dB */ - int dev_index = 0; - int dev_given = 0; - int ppm_error = 0; - int rtl_agc=0; - int custom_ppm = 0; - int left_freq = 161975000; - int right_freq = 162025000; - int sample_rate = 24000; - int output_rate = 48000; - int dongle_freq, dongle_rate, delta; - int edge = 0; -/* Aisdecoder */ - int show_levels=0; - int debug_nmea = 0; - char * port=NULL; - char * host=NULL; - int use_tcp_listener = 0; - int tcp_keep_ais_time = 15; // using tcp lister time to keep ais messages in sec. - pthread_cond_init(&ready, NULL); - pthread_mutex_init(&ready_m, NULL); - - while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RATt:P:h:nLS:?")) != -1) - { - switch (opt) { - case 'l': - left_freq = (int)atofs(optarg); - break; - case 'r': - right_freq = (int)atofs(optarg); - break; - case 's': - sample_rate = (int)atofs(optarg); - break; - case 'o': - output_rate = (int)atofs(optarg); - break; - case 'E': - edge = !edge; - break; - case 'D': - dc_filter = !dc_filter; - break; - case 'O': - oversample = !oversample; - break; - case 'd': - dev_index = verbose_device_search(optarg); - dev_given = 1; - break; - case 'g': - gain = (int)(atof(optarg) * 10); - break; - case 'p': - ppm_error = atoi(optarg); - custom_ppm = 1; - break; - case 'R': - rtl_agc=1; - break; - case 'A': - use_internal_aisdecoder=0; - break; - case 'P': - port=strdup(optarg); - break; - case 'T': - use_tcp_listener=1; - break; - case 't': - tcp_keep_ais_time = atoi(optarg); - break; - case 'h': - host=strdup(optarg); - break; - case 'L': - show_levels=1; - break; - case 'S': - seconds_for_decoder_stats=atoi(optarg); - break; - case 'n': - debug_nmea = 1; - break; - case '?': - default: - usage(); - return 2; - } - } - - if (argc <= optind) { - filename = "-"; - } else { - filename = argv[optind]; - } + config->gain = AUTO_GAIN; /* tenths of a dB */ + config->dev_index = 0; + config->dev_given = 0; + config->ppm_error = 0; + config->rtl_agc=0; + config->custom_ppm = 0; + config->left_freq = 161975000; + config->right_freq = 162025000; + config->sample_rate = 24000; + config->output_rate = 48000; + + config->edge = 0; + config->use_tcp_listener = 0, config->tcp_keep_ais_time = 15; + + /* Aisdecoder */ + config->show_levels=0; + config->debug_nmea = 0; + + config->host=NULL; + config->port=NULL; + + config->filename = "-"; +} - if (left_freq > right_freq) { - usage(); - return 2; - } - if(host==NULL){ - host=strdup("127.0.0.1"); - } - if(port==NULL){ - port=strdup("10110"); - } - - /* precompute rates */ - dongle_freq = left_freq/2 + right_freq/2; - if (edge) { - dongle_freq -= sample_rate/2;} - delta = right_freq - left_freq; - if (delta > 1.2e6) { +struct rtl_ais_context *rtl_ais_start(struct rtl_ais_config *config) +{ + if (config->left_freq > config->right_freq) + return NULL; + + struct rtl_ais_context *ctx = malloc(sizeof(struct rtl_ais_context)); + ctx->active = 1; + + /* precompute rates */ + int dongle_freq, dongle_rate, delta, i; + dongle_freq = config->left_freq/2 + config->right_freq/2; + if (config->edge) { + dongle_freq -= config->sample_rate/2;} + delta = config->right_freq - config->left_freq; + if (delta > 1.2e6) { fprintf(stderr, "Frequencies may be at most 1.2MHz apart."); exit(1); } @@ -635,137 +505,105 @@ int main(int argc, char **argv) } i = (int)log2(2.4e6 / delta); dongle_rate = delta * (1<both.rate_in = dongle_rate; + ctx->both.rate_out = delta * 2; + i = (int)log2(ctx->both.rate_in/ctx->both.rate_out); + ctx->both.downsample_passes = i; + ctx->both.downsample = 1 << i; + ctx->left.rate_in = ctx->both.rate_out; + i = (int)log2(ctx->left.rate_in / config->sample_rate); + ctx->left.downsample_passes = i; + ctx->left.downsample = 1 << i; + ctx->left.rate_out = ctx->left.rate_in / ctx->left.downsample; - right.rate_in = left.rate_in; - right.rate_out = left.rate_out; - right.downsample = left.downsample; - right.downsample_passes = left.downsample_passes; + ctx->right.rate_in = ctx->left.rate_in; + ctx->right.rate_out = ctx->left.rate_out; + ctx->right.downsample = ctx->left.downsample; + ctx->right.downsample_passes = ctx->left.downsample_passes; - if (left.rate_out > output_rate) { + if (ctx->left.rate_out > config->output_rate) { fprintf(stderr, "Channel bandwidth too high or output bandwidth too low."); exit(1); } - stereo.rate = output_rate; - - if (edge) { - fprintf(stderr, "Edge tuning enabled.\n"); - } else { - fprintf(stderr, "Edge tuning disabled.\n"); - } - if (dc_filter) { - fprintf(stderr, "DC filter enabled.\n"); - } else { - fprintf(stderr, "DC filter disabled.\n"); - } - if (rtl_agc) { - fprintf(stderr, "RTL AGC enabled.\n"); - } else { - fprintf(stderr, "RTL AGC disabled.\n"); - } - if (use_internal_aisdecoder) { - fprintf(stderr, "Internal AIS decoder enabled.\n"); - } else { - fprintf(stderr, "Internal AIS decoder disabled.\n"); - } - fprintf(stderr, "Buffer size: %0.2f mS\n", 1000 * (double)DEFAULT_BUF_LENGTH / (double)dongle_rate); - fprintf(stderr, "Downsample factor: %i\n", both.downsample * left.downsample); - fprintf(stderr, "Low pass: %i Hz\n", left.rate_out); - fprintf(stderr, "Output: %i Hz\n", output_rate); + fprintf(stderr, "Buffer size: %0.2f mS\n", 1000 * (double)DEFAULT_BUF_LENGTH / (double)dongle_rate); + fprintf(stderr, "Downsample factor: %i\n", ctx->both.downsample * ctx->left.downsample); + fprintf(stderr, "Low pass: %i Hz\n", ctx->left.rate_out); + fprintf(stderr, "Output: %i Hz\n", config->output_rate); /* precompute lengths */ - both.len_in = DEFAULT_BUF_LENGTH; - both.len_out = both.len_in / both.downsample; - left.len_in = both.len_out; - right.len_in = both.len_out; - left.len_out = left.len_in / left.downsample; - right.len_out = right.len_in / right.downsample; - left_demod.buf_len = left.len_out; - left_demod.result_len = left_demod.buf_len / 2; - right_demod.buf_len = left_demod.buf_len; - right_demod.result_len = left_demod.result_len; + ctx->both.len_in = DEFAULT_BUF_LENGTH; + ctx->both.len_out = ctx->both.len_in / ctx->both.downsample; + ctx->left.len_in = ctx->both.len_out; + ctx->right.len_in = ctx->both.len_out; + ctx->left.len_out = ctx->left.len_in / ctx->left.downsample; + ctx->right.len_out = ctx->right.len_in / ctx->right.downsample; + ctx->left_demod.buf_len = ctx->left.len_out; + ctx->left_demod.result_len = ctx->left_demod.buf_len / 2; + ctx->right_demod.buf_len = ctx->left_demod.buf_len; + ctx->right_demod.result_len = ctx->left_demod.result_len; // stereo.bl_len = (int)((long)(DEFAULT_BUF_LENGTH/2) * (long)output_rate / (long)dongle_rate); -> Doesn't work on Linux - stereo.bl_len = (int)((double)(DEFAULT_BUF_LENGTH/2) * (double)output_rate / (double)dongle_rate); - stereo.br_len = stereo.bl_len; - stereo.result_len = stereo.br_len * 2; - stereo.rate = output_rate; + ctx->stereo.bl_len = (int)((double)(DEFAULT_BUF_LENGTH/2) * (double)config->output_rate / (double)dongle_rate); + ctx->stereo.br_len = ctx->stereo.bl_len; + ctx->stereo.result_len = ctx->stereo.br_len * 2; + ctx->stereo.rate = config->output_rate; - if (!dev_given) { - dev_index = verbose_device_search("0"); - } + if (!config->dev_given) { + config->dev_index = verbose_device_search("0"); + } - if (dev_index < 0) { + if (config->dev_index < 0) { exit(1); } - downsample_init(&both); - downsample_init(&left); - downsample_init(&right); - demod_init(&left_demod); - demod_init(&right_demod); - stereo_init(&stereo); + downsample_init(&ctx->both); + downsample_init(&ctx->left); + downsample_init(&ctx->right); + demod_init(&ctx->left_demod); + demod_init(&ctx->right_demod); + stereo_init(&ctx->stereo); - r = rtlsdr_open(&dev, (uint32_t)dev_index); + int r = rtlsdr_open(&ctx->dev, (uint32_t)config->dev_index); if (r < 0) { - fprintf(stderr, "Failed to open rtlsdr device #%d.\n", dev_index); + fprintf(stderr, "Failed to open rtlsdr device #%d.\n", config->dev_index); exit(1); } -#ifndef WIN32 - sigact.sa_handler = sighandler; - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0; - sigaction(SIGINT, &sigact, NULL); - sigaction(SIGTERM, &sigact, NULL); - sigaction(SIGQUIT, &sigact, NULL); - sigaction(SIGPIPE, &sigact, NULL); -#else - signal(SIGINT, sighandler); - signal(SIGTERM, sighandler); - - #endif - if(!use_internal_aisdecoder){ - if (strcmp(filename, "-") == 0) { /* Write samples to stdout */ - file = stdout; + + if(!config->use_internal_aisdecoder){ + if (strcmp(config->filename, "-") == 0) { /* Write samples to stdout */ + ctx->file = stdout; #ifdef WIN32 setmode(fileno(stdout), O_BINARY); // Binary mode, avoid text mode #endif setvbuf(stdout, NULL, _IONBF, 0); } else { - file = fopen(filename, "wb"); - if (!file) { - fprintf(stderr, "Failed to open %s\n", filename); - exit(1); + ctx->file = fopen(config->filename, "wb"); + if (!ctx->file) { + fprintf(stderr, "Failed to open %s\n", config->filename); + exit(1); } } } else{ // Internal AIS decoder - int ret=init_ais_decoder(host,port,show_levels,debug_nmea,stereo.bl_len,seconds_for_decoder_stats, use_tcp_listener, tcp_keep_ais_time); + int ret=init_ais_decoder(config->host,config->port,config->show_levels,config->debug_nmea,ctx->stereo.bl_len,config->seconds_for_decoder_stats, config->use_tcp_listener, config->tcp_keep_ais_time); if(ret != 0){ fprintf(stderr,"Error initializing built-in AIS decoder\n"); - rtlsdr_cancel_async(dev); - rtlsdr_close(dev); + rtlsdr_cancel_async(ctx->dev); + rtlsdr_close(ctx->dev); exit(1); } } + ctx->use_internal_aisdecoder = config->use_internal_aisdecoder; + /* Set the tuner gain */ - if (gain == AUTO_GAIN) { - verbose_auto_gain(dev); + if (config->gain == AUTO_GAIN) { + verbose_auto_gain(ctx->dev); } else { - gain = nearest_gain(dev, gain); - verbose_gain_set(dev, gain); + config->gain = nearest_gain(ctx->dev, config->gain); + verbose_gain_set(ctx->dev, config->gain); } - if(rtl_agc){ - int r = rtlsdr_set_agc_mode(dev, 1); + if(config->rtl_agc){ + int r = rtlsdr_set_agc_mode(ctx->dev, 1); if(r<0) { fprintf(stderr,"Error seting RTL AGC mode ON"); exit(1); @@ -774,42 +612,64 @@ int main(int argc, char **argv) fprintf(stderr,"RTL AGC mode ON\n"); } } - if (!custom_ppm) { - verbose_ppm_eeprom(dev, &ppm_error); + if (!config->custom_ppm) { + verbose_ppm_eeprom(ctx->dev, &config->ppm_error); } - verbose_ppm_set(dev, ppm_error); + verbose_ppm_set(ctx->dev, config->ppm_error); /* Set the tuner frequency */ - verbose_set_frequency(dev, dongle_freq); + verbose_set_frequency(ctx->dev, config->dongle_freq); /* Set the sample rate */ - verbose_set_sample_rate(dev, dongle_rate); + verbose_set_sample_rate(ctx->dev, config->dongle_rate); /* Reset endpoint before we start reading from it (mandatory) */ - verbose_reset_buffer(dev); - - pthread_create(&demod_thread, NULL, demod_thread_fn, (void *)(NULL)); - rtlsdr_read_async(dev, rtlsdr_callback, (void *)(NULL), - DEFAULT_ASYNC_BUF_NUMBER, - DEFAULT_BUF_LENGTH); - - if (do_exit) { - fprintf(stderr, "\nUser cancel, exiting...\n");} - else { - fprintf(stderr, "\nLibrary error %d, exiting...\n", r);} - rtlsdr_cancel_async(dev); - safe_cond_signal(&ready, &ready_m); - pthread_cond_destroy(&ready); - pthread_mutex_destroy(&ready_m); - - if (file != stdout) { - if(file) - fclose(file); + verbose_reset_buffer(ctx->dev); + + pthread_cond_init(&ctx->ready, NULL); + pthread_mutex_init(&ctx->ready_m, NULL); + + /* create two threads */ + pthread_create(&ctx->demod_thread, NULL, demod_thread_fn, ctx); + pthread_create(&ctx->rtlsdr_thread, NULL, rtlsdr_thread_fn, ctx); + + return ctx; +} + +int rtl_ais_isactive(struct rtl_ais_context *ctx) +{ + return ctx->active; +} + +const char *rtl_ais_next_message(struct rtl_ais_context *ctx) +{ + ctx = ctx; //unused for now + return aisdecoder_next_message(); +} + +void rtl_ais_cleanup(struct rtl_ais_context *ctx) +{ + rtlsdr_cancel_async(ctx->dev); + ctx->active = 0; + + pthread_join(ctx->demod_thread, NULL); + pthread_join(ctx->rtlsdr_thread, NULL); + + if (ctx->file != stdout) { + if(ctx->file) + fclose(ctx->file); } - rtlsdr_close(dev); - return r >= 0 ? r : -r; + rtlsdr_cancel_async(ctx->dev); + safe_cond_signal(&ctx->ready, &ctx->ready_m); + pthread_cond_destroy(&ctx->ready); + pthread_mutex_destroy(&ctx->ready_m); + + rtlsdr_close(ctx->dev); + + free(ctx); } + // vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab diff --git a/rtl_ais.h b/rtl_ais.h new file mode 100644 index 0000000..4f27758 --- /dev/null +++ b/rtl_ais.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 by Kyle Keen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct rtl_ais_config +{ + int gain, dev_index, dev_given, ppm_error, rtl_agc, custom_ppm; + int left_freq, right_freq, sample_rate, output_rate, dongle_freq; + int dongle_rate, delta, edge; + + int oversample, dc_filter, use_internal_aisdecoder; + int seconds_for_decoder_stats; + int use_tcp_listener, tcp_keep_ais_time; + /* Aisdecoder */ + int show_levels, debug_nmea; + char *port, *host, *filename; +}; + +struct rtl_ais_context; + +void rtl_ais_default_config(struct rtl_ais_config *config); +struct rtl_ais_context *rtl_ais_start(struct rtl_ais_config *config); +int rtl_ais_isactive(struct rtl_ais_context *ctx); +const char *rtl_ais_next_message(struct rtl_ais_context *ctx); +void rtl_ais_cleanup(struct rtl_ais_context *ctx); + +#ifdef __cplusplus +} +#endif From 28e6a291ee8a53de99d03d2b3117833b594c50f2 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 2 Jun 2016 20:06:20 -0300 Subject: [PATCH 052/117] Add again missing arguments "T" and "t" --- main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.c b/main.c index 15544b9..835fe17 100644 --- a/main.c +++ b/main.c @@ -101,7 +101,7 @@ int main(int argc, char **argv) config.host = strdup("127.0.0.1"); config.port = strdup("10110"); - while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RAP:h:nLS:?")) != -1) + while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RATtP:h:nLS:?")) != -1) { switch (opt) { case 'l': From ce6bccef491919c274a7b09fca4c57631500bc7c Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 2 Jun 2016 20:08:49 -0300 Subject: [PATCH 053/117] Fix: Dongle_freq and dongle_rate are local --- rtl_ais.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rtl_ais.c b/rtl_ais.c index 7420ce4..b77be4d 100644 --- a/rtl_ais.c +++ b/rtl_ais.c @@ -1,7 +1,7 @@ /* * Copyright (C) 2012 by Kyle Keen * - * This program is free software: you can redistribute it and/or modify + * This program is free software: you can redistribsetute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. @@ -619,10 +619,10 @@ struct rtl_ais_context *rtl_ais_start(struct rtl_ais_config *config) verbose_ppm_set(ctx->dev, config->ppm_error); /* Set the tuner frequency */ - verbose_set_frequency(ctx->dev, config->dongle_freq); + verbose_set_frequency(ctx->dev, dongle_freq); /* Set the sample rate */ - verbose_set_sample_rate(ctx->dev, config->dongle_rate); + verbose_set_sample_rate(ctx->dev, dongle_rate); /* Reset endpoint before we start reading from it (mandatory) */ verbose_reset_buffer(ctx->dev); From 6c98544350f0cb84b605619357af0c5a48333b71 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Sat, 2 Jul 2016 12:55:33 -0300 Subject: [PATCH 054/117] Use internal aisdecoder by default --- rtl_ais.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtl_ais.c b/rtl_ais.c index b77be4d..f2b1094 100644 --- a/rtl_ais.c +++ b/rtl_ais.c @@ -470,7 +470,7 @@ void rtl_ais_default_config(struct rtl_ais_config *config) config->edge = 0; config->use_tcp_listener = 0, config->tcp_keep_ais_time = 15; - + config->use_internal_aisdecoder=1; /* Aisdecoder */ config->show_levels=0; config->debug_nmea = 0; From 5aba6c6efa0134716acb9eba0f3bc532bcd27484 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Wed, 17 Aug 2016 00:11:04 -0300 Subject: [PATCH 055/117] Initialize config->seconds_for_decoder_stats=0 --- rtl_ais.c | 1 + 1 file changed, 1 insertion(+) diff --git a/rtl_ais.c b/rtl_ais.c index f2b1094..7b21630 100644 --- a/rtl_ais.c +++ b/rtl_ais.c @@ -471,6 +471,7 @@ void rtl_ais_default_config(struct rtl_ais_config *config) config->edge = 0; config->use_tcp_listener = 0, config->tcp_keep_ais_time = 15; config->use_internal_aisdecoder=1; + config->seconds_for_decoder_stats=0; /* Aisdecoder */ config->show_levels=0; config->debug_nmea = 0; From b2dbee5ea4aad10475fd8f2088169517adf4a0a4 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 18 Aug 2016 14:48:38 -0300 Subject: [PATCH 056/117] Avoid duplicatd messages to console --- main.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/main.c b/main.c index 835fe17..3221e6c 100644 --- a/main.c +++ b/main.c @@ -202,16 +202,25 @@ int main(int argc, char **argv) fprintf(stderr, "\nrtl_ais_start failed, exiting...\n"); exit(1); } - - // loop printing received ais messages to console - while(!do_exit && rtl_ais_isactive(ctx)) { - const char *str; - while((str = rtl_ais_next_message(ctx))) - puts(str); - - usleep(50000); + /* + aidecoder.c appends the messages to a queue that can be used for a + routine if rtl_ais is compiled as lib. Here we only loop and dequeue + the messages, and the puts() sentence that print the message is + commented out. If the -n parameter is used the messages are printed from + nmea_sentence_received() in aidecoder.c + */ + while(!do_exit && rtl_ais_isactive(ctx)) { + const char *str; + if(config.use_internal_aisdecoder) + { + // dequeue + while((str = rtl_ais_next_message(ctx))) + { + //puts(str); or code somethig that fits your needs + } + } + usleep(50000); } - rtl_ais_cleanup(ctx); return 0; } From c8a2579b9ac7752d3791fb6c751f83dbe4a122ef Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 18 Aug 2016 14:52:48 -0300 Subject: [PATCH 057/117] Avid duplicated messages to console --- main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.c b/main.c index 3221e6c..24ee70e 100644 --- a/main.c +++ b/main.c @@ -216,7 +216,7 @@ int main(int argc, char **argv) // dequeue while((str = rtl_ais_next_message(ctx))) { - //puts(str); or code somethig that fits your needs + //puts(str); or code something that fits your needs } } usleep(50000); From dcbdb7f125b3a41069ae886d7d0f98a52742386b Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 18 Aug 2016 15:24:55 -0300 Subject: [PATCH 058/117] Change pthread_join() for pthread detach(); or can't be stopped using Ctrl^C on Linux --- rtl_ais.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtl_ais.c b/rtl_ais.c index 7b21630..69848d6 100644 --- a/rtl_ais.c +++ b/rtl_ais.c @@ -654,8 +654,8 @@ void rtl_ais_cleanup(struct rtl_ais_context *ctx) rtlsdr_cancel_async(ctx->dev); ctx->active = 0; - pthread_join(ctx->demod_thread, NULL); - pthread_join(ctx->rtlsdr_thread, NULL); + pthread_detach(ctx->demod_thread); + pthread_detach(ctx->rtlsdr_thread); if (ctx->file != stdout) { if(ctx->file) From 8d21c8d21dff1c452a492ed547e41ad144f5286a Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 18 Aug 2016 18:01:48 -0300 Subject: [PATCH 059/117] init dc_avg=0; now starts receiving messages inmediatly. More init missing options fixed --- rtl_ais.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rtl_ais.c b/rtl_ais.c index 69848d6..9b40435 100644 --- a/rtl_ais.c +++ b/rtl_ais.c @@ -446,6 +446,7 @@ static void demod_init(struct demod_state *ds) { ds->buf = malloc(ds->buf_len * sizeof(int16_t)); ds->result = malloc(ds->result_len * sizeof(int16_t)); + ds->dc_avg=0; } static void stereo_init(struct upsample_stereo *us) @@ -467,7 +468,7 @@ void rtl_ais_default_config(struct rtl_ais_config *config) config->right_freq = 162025000; config->sample_rate = 24000; config->output_rate = 48000; - + config->dc_filter=1; config->edge = 0; config->use_tcp_listener = 0, config->tcp_keep_ais_time = 15; config->use_internal_aisdecoder=1; @@ -521,7 +522,7 @@ struct rtl_ais_context *rtl_ais_start(struct rtl_ais_config *config) ctx->right.rate_out = ctx->left.rate_out; ctx->right.downsample = ctx->left.downsample; ctx->right.downsample_passes = ctx->left.downsample_passes; - + ctx->dc_filter=config->dc_filter; if (ctx->left.rate_out > config->output_rate) { fprintf(stderr, "Channel bandwidth too high or output bandwidth too low."); exit(1); From 671ac02b3fcf9bbc450eb4f2f07cd290841ed0fd Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 18 Aug 2016 18:42:42 -0300 Subject: [PATCH 060/117] Some missing strings re-added. --- main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.c b/main.c index 24ee70e..82300f8 100644 --- a/main.c +++ b/main.c @@ -52,6 +52,8 @@ void usage(void) "\tBuilt-in AIS decoder options:\n" "\t[-h host (default: 127.0.0.1)]\n" "\t[-P port (default: 10110)]\n" + "\t[-T use TCP communication, rtl-ais is tcp server ( -h is ignored)\n" +- "\t[-t time to keep ais messages in sec, using tcp listener (default: 15)\n" "\t[-n log NMEA sentences to console (stderr) (default off)]\n" "\t[-L log sound levels to console (stderr) (default off)]\n\n" "\t[-S seconds_for_decoder_stats (default 0=off)]\n\n" From e262cb526d005cf717a8928d95e80e0779111d0a Mon Sep 17 00:00:00 2001 From: dgiardini Date: Tue, 30 Aug 2016 11:49:17 -0300 Subject: [PATCH 061/117] spurious character supressed --- main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.c b/main.c index 82300f8..cc90bf8 100644 --- a/main.c +++ b/main.c @@ -53,7 +53,7 @@ void usage(void) "\t[-h host (default: 127.0.0.1)]\n" "\t[-P port (default: 10110)]\n" "\t[-T use TCP communication, rtl-ais is tcp server ( -h is ignored)\n" -- "\t[-t time to keep ais messages in sec, using tcp listener (default: 15)\n" + "\t[-t time to keep ais messages in sec, using tcp listener (default: 15)\n" "\t[-n log NMEA sentences to console (stderr) (default off)]\n" "\t[-L log sound levels to console (stderr) (default off)]\n\n" "\t[-S seconds_for_decoder_stats (default 0=off)]\n\n" From 0726fd38dc3f4ceb304e278e14ea5a95008a7573 Mon Sep 17 00:00:00 2001 From: Dashie Date: Mon, 27 Mar 2017 16:49:53 +0200 Subject: [PATCH 062/117] Fix missing includes --- tcp_listener/tcp_listener.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c index d8a5a34..f740a68 100644 --- a/tcp_listener/tcp_listener.c +++ b/tcp_listener/tcp_listener.c @@ -9,6 +9,7 @@ #include #include #include +#include #if defined (__WIN32__) #include @@ -17,6 +18,7 @@ #include #include #include + #include #endif typedef struct t_sockIo { From a2a8e7818cee9efc8e53416ddc1a2e8cfb73dffe Mon Sep 17 00:00:00 2001 From: Vincent Arkesteijn Date: Thu, 27 Apr 2017 15:08:35 +0200 Subject: [PATCH 063/117] fixed shifting of filter history in fifth_order() fifth_order() filters and decimates by two, and therefore always shifts by two input samples for every output sample. However, for the first output sample, it shifted the state from the previous call to fifth_order() by only one input sample, and it never used the last sample in its input buffer. --- rtl_ais.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rtl_ais.c b/rtl_ais.c index 9b40435..b341356 100644 --- a/rtl_ais.c +++ b/rtl_ais.c @@ -154,12 +154,12 @@ static void fifth_order(int16_t *data, int length, int16_t *hist) { int i; int16_t a, b, c, d, e, f; - a = hist[1]; - b = hist[2]; - c = hist[3]; - d = hist[4]; - e = hist[5]; - f = data[0]; + a = hist[2]; + b = hist[3]; + c = hist[4]; + d = hist[5]; + e = data[0]; + f = data[2]; /* a downsample should improve resolution, so don't fully shift */ data[0] = (a + (b+e)*5 + (c+d)*10 + f) >> 4; for (i=4; i> 4; } /* archive */ From 252f33b128f01fd1a6c3c4678a2d2ca58a0c453d Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 26 Jul 2018 15:25:07 -0300 Subject: [PATCH 064/117] Missing colon for "t" parameter added --- main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.c b/main.c index cc90bf8..3abc2ca 100644 --- a/main.c +++ b/main.c @@ -103,7 +103,7 @@ int main(int argc, char **argv) config.host = strdup("127.0.0.1"); config.port = strdup("10110"); - while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RATtP:h:nLS:?")) != -1) + while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RATt:P:h:nLS:?")) != -1) { switch (opt) { case 'l': From efd05adf88481d35b148e81766f3fa6b6331204f Mon Sep 17 00:00:00 2001 From: Bertold Van den Bergh Date: Sat, 16 Feb 2019 03:14:51 +0100 Subject: [PATCH 065/117] Add command line option to output packet timing information --- aisdecoder/aisdecoder.c | 4 ++-- aisdecoder/aisdecoder.h | 2 +- aisdecoder/lib/protodec.c | 12 +++++++++--- aisdecoder/lib/protodec.h | 11 +++++++---- aisdecoder/lib/receiver.c | 8 +++++--- aisdecoder/lib/receiver.h | 3 ++- aisdecoder/sounddecoder.c | 6 +++--- aisdecoder/sounddecoder.h | 2 +- main.c | 6 +++++- rtl_ais.c | 4 +++- rtl_ais.h | 2 ++ 11 files changed, 40 insertions(+), 20 deletions(-) diff --git a/aisdecoder/aisdecoder.c b/aisdecoder/aisdecoder.c index 75d9ae9..5aeb9ab 100644 --- a/aisdecoder/aisdecoder.c +++ b/aisdecoder/aisdecoder.c @@ -151,7 +151,7 @@ int send_nmea( const char *sentence, unsigned int length) { return 0; } -int init_ais_decoder(char * host, char * port ,int show_levels,int _debug_nmea,int buf_len,int time_print_stats, int use_tcp_listener, int tcp_keep_ais_time){ +int init_ais_decoder(char * host, char * port ,int show_levels,int _debug_nmea,int buf_len,int time_print_stats, int use_tcp_listener, int tcp_keep_ais_time, int add_sample_num){ debug_nmea=_debug_nmea; use_tcp = use_tcp_listener; pthread_mutex_init(&message_mutex, NULL); @@ -171,7 +171,7 @@ int init_ais_decoder(char * host, char * port ,int show_levels,int _debug_nmea,i } if (show_levels) on_sound_level_changed=sound_level_changed; on_nmea_sentence_received=nmea_sentence_received; - initSoundDecoder(buf_len,time_print_stats); + initSoundDecoder(buf_len,time_print_stats,add_sample_num); return 0; } diff --git a/aisdecoder/aisdecoder.h b/aisdecoder/aisdecoder.h index 3322ac9..33f8d9e 100644 --- a/aisdecoder/aisdecoder.h +++ b/aisdecoder/aisdecoder.h @@ -1,6 +1,6 @@ #ifndef __AIS_RL_AIS_INC_ #define __AIS_RL_AIS_INC_ -int init_ais_decoder(char * host, char * port,int show_levels,int _debug_nmea,int buf_len,int time_print_stats, int use_tcp_listener, int tcp_keep_ais_time); +int init_ais_decoder(char * host, char * port,int show_levels,int _debug_nmea,int buf_len,int time_print_stats, int use_tcp_listener, int tcp_keep_ais_time, int add_sample_num); void run_rtlais_decoder(short * buff, int len); const char *aisdecoder_next_message(); int free_ais_decoder(void); diff --git a/aisdecoder/lib/protodec.c b/aisdecoder/lib/protodec.c index 885b4a9..a6855ff 100644 --- a/aisdecoder/lib/protodec.c +++ b/aisdecoder/lib/protodec.c @@ -34,7 +34,7 @@ decoder_on_nmea_sentence_received on_nmea_sentence_received=NULL; #include #endif -void protodec_initialize(struct demod_state_t *d, struct serial_state_t *serial, char chanid) +void protodec_initialize(struct demod_state_t *d, struct serial_state_t *serial, char chanid, int add_sample_num) { memset(d, 0, sizeof(struct demod_state_t)); @@ -48,6 +48,7 @@ void protodec_initialize(struct demod_state_t *d, struct serial_state_t *serial, protodec_reset(d); d->seqnr = 0; + d->add_sample_num = add_sample_num; d->buffer = hmalloc(DEMOD_BUFFER_LEN); d->rbuffer = hmalloc(DEMOD_BUFFER_LEN); @@ -210,7 +211,11 @@ void protodec_generate_nmea(struct demod_state_t *d, int bufferlen, int fillbits nmeachk = d->nmea[m++]; while (d->nmea[m] != '*') nmeachk ^= d->nmea[m++]; - sprintf(&d->nmea[k + 3], "%02X\r\n", nmeachk); + if (d->add_sample_num){ + sprintf(&d->nmea[k + 3], "%02X,%lu\r\n", nmeachk, d->startsample); + }else{ + sprintf(&d->nmea[k + 3], "%02X\r\n", nmeachk); + } if (on_nmea_sentence_received != NULL) on_nmea_sentence_received(d->nmea, k+7, sentences, sentencenum); } while (sentencenum < sentences); @@ -245,7 +250,7 @@ void protodec_getdata(int bufferlen, struct demod_state_t *d) return; // unsupported packet type } -void protodec_decode(char *in, int count, struct demod_state_t *d) +void protodec_decode(char *in, int count, struct demod_state_t *d, unsigned long samplenum) { int i = 0; int bufferlength, correct; @@ -330,6 +335,7 @@ void protodec_decode(char *in, int count, struct demod_state_t *d) if (d->nstartsign >= 7) { if (in[i] == 0) { d->state = ST_DATA; + d->startsample = samplenum; d->nstartsign = 0; d->antallenner = 0; memset(d->buffer, 0, DEMOD_BUFFER_LEN); diff --git a/aisdecoder/lib/protodec.h b/aisdecoder/lib/protodec.h index 84c513e..61f82dc 100644 --- a/aisdecoder/lib/protodec.h +++ b/aisdecoder/lib/protodec.h @@ -31,7 +31,7 @@ #define DEMOD_BUFFER_LEN 450 #define MAX_AIS_PACKET_TYPE 27 -#define NMEABUFFER_LEN 100 +#define NMEABUFFER_LEN 128 struct demod_state_t { char chanid; @@ -51,15 +51,18 @@ struct demod_state_t { int lostframes; int lostframes2; unsigned char seqnr; - + + unsigned long startsample; + int add_sample_num; + struct serial_state_t *serial; char *nmea; }; -void protodec_initialize(struct demod_state_t *d, struct serial_state_t *serial, char chanid); +void protodec_initialize(struct demod_state_t *d, struct serial_state_t *serial, char chanid, int add_sample_num); void protodec_reset(struct demod_state_t *d); void protodec_getdata(int bufferlengde, struct demod_state_t *d); -void protodec_decode(char *in, int count, struct demod_state_t *d); +void protodec_decode(char *in, int count, struct demod_state_t *d, unsigned long samplenum); #endif diff --git a/aisdecoder/lib/receiver.c b/aisdecoder/lib/receiver.c index adc2c3f..cba1e7e 100644 --- a/aisdecoder/lib/receiver.c +++ b/aisdecoder/lib/receiver.c @@ -47,7 +47,7 @@ static float coeffs[]={ #define COEFFS_L 36 -struct receiver *init_receiver(char name, int num_ch, int ch_ofs) +struct receiver *init_receiver(char name, int num_ch, int ch_ofs, int add_sample_num) { struct receiver *rx; @@ -57,7 +57,7 @@ struct receiver *init_receiver(char name, int num_ch, int ch_ofs) rx->filter = filter_init(COEFFS_L, coeffs); rx->decoder = hmalloc(sizeof(struct demod_state_t)); - protodec_initialize(rx->decoder, NULL, name); + protodec_initialize(rx->decoder, NULL, name, add_sample_num); rx->name = name; rx->lastbit = 0; @@ -105,6 +105,8 @@ void receiver_run(struct receiver *rx, short *buf, int len) maxval = filter_run_buf(rx->filter, buf, filtered, rx_num_ch, len); for (i = 0; i < len; i++) { + rx->samplenum++; + out = filtered[i]; curr = (out > 0); @@ -125,7 +127,7 @@ void receiver_run(struct receiver *rx, short *buf, int len) /* nrzi decode */ b = !(bit ^ rx->lastbit); /* feed to the decoder */ - protodec_decode(&b, 1, rx->decoder); + protodec_decode(&b, 1, rx->decoder, rx->samplenum); rx->lastbit = bit; rx->pll &= 0xffff; diff --git a/aisdecoder/lib/receiver.h b/aisdecoder/lib/receiver.h index f2b0a5a..63eb4c5 100644 --- a/aisdecoder/lib/receiver.h +++ b/aisdecoder/lib/receiver.h @@ -46,9 +46,10 @@ struct receiver { struct demod_state_t *decoder; int prev; time_t last_levellog; + unsigned long samplenum; }; -extern struct receiver *init_receiver(char name, int num_ch, int ch_ofs); +extern struct receiver *init_receiver(char name, int num_ch, int ch_ofs, int add_sample_num); extern void free_receiver(struct receiver *rx); extern void receiver_run(struct receiver *rx, short *buf, int len); diff --git a/aisdecoder/sounddecoder.c b/aisdecoder/sounddecoder.c index 713acb7..79b97ce 100644 --- a/aisdecoder/sounddecoder.c +++ b/aisdecoder/sounddecoder.c @@ -45,15 +45,15 @@ static void readBuffers(); static time_t tprev=0; static int time_print_stats=0; -int initSoundDecoder(int buf_len,int _time_print_stats) +int initSoundDecoder(int buf_len,int _time_print_stats, int add_sample_num) { sound_channels=SOUND_CHANNELS_STEREO; channels = sound_channels == SOUND_CHANNELS_MONO ? 1 : 2; time_print_stats=_time_print_stats; tprev=time(NULL); // for decoder statistics buffer = (short *) hmalloc(channels*sizeof(short)*buf_len); - rx_a = init_receiver('A', 2, 0); - rx_b = init_receiver('B', 2, 1); + rx_a = init_receiver('A', 2, 0, add_sample_num); + rx_b = init_receiver('B', 2, 1, add_sample_num); return 1; } diff --git a/aisdecoder/sounddecoder.h b/aisdecoder/sounddecoder.h index e53521d..93a469a 100644 --- a/aisdecoder/sounddecoder.h +++ b/aisdecoder/sounddecoder.h @@ -26,7 +26,7 @@ typedef enum { } Sound_Driver; extern char errorSoundDecoder[]; -int initSoundDecoder(int buf_len,int _time_print_stats); +int initSoundDecoder(int buf_len,int _time_print_stats, int add_sample_num); void runSoundDecoder(int *stop); void freeSoundDecoder(void); void run_mem_decoder(short * buf, int len,int max_buf_len); diff --git a/main.c b/main.c index 3abc2ca..4bfc5f4 100644 --- a/main.c +++ b/main.c @@ -55,6 +55,7 @@ void usage(void) "\t[-T use TCP communication, rtl-ais is tcp server ( -h is ignored)\n" "\t[-t time to keep ais messages in sec, using tcp listener (default: 15)\n" "\t[-n log NMEA sentences to console (stderr) (default off)]\n" + "\t[-I add sample index to NMEA messages (default off)]\n" "\t[-L log sound levels to console (stderr) (default off)]\n\n" "\t[-S seconds_for_decoder_stats (default 0=off)]\n\n" "\tWhen the built-in AIS decoder is disabled the samples are sent to\n" @@ -103,7 +104,7 @@ int main(int argc, char **argv) config.host = strdup("127.0.0.1"); config.port = strdup("10110"); - while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RATt:P:h:nLS:?")) != -1) + while ((opt = getopt(argc, argv, "l:r:s:o:EODd:g:p:RATIt:P:h:nLS:?")) != -1) { switch (opt) { case 'l': @@ -144,6 +145,9 @@ int main(int argc, char **argv) case 'A': config.use_internal_aisdecoder=0; break; + case 'I': + config.add_sample_num = 1; + break; case 'P': config.port=strdup(optarg); break; diff --git a/rtl_ais.c b/rtl_ais.c index b341356..d553537 100644 --- a/rtl_ais.c +++ b/rtl_ais.c @@ -481,6 +481,8 @@ void rtl_ais_default_config(struct rtl_ais_config *config) config->port=NULL; config->filename = "-"; + + config->add_sample_num = 0; } struct rtl_ais_context *rtl_ais_start(struct rtl_ais_config *config) @@ -587,7 +589,7 @@ struct rtl_ais_context *rtl_ais_start(struct rtl_ais_config *config) } } else{ // Internal AIS decoder - int ret=init_ais_decoder(config->host,config->port,config->show_levels,config->debug_nmea,ctx->stereo.bl_len,config->seconds_for_decoder_stats, config->use_tcp_listener, config->tcp_keep_ais_time); + int ret=init_ais_decoder(config->host,config->port,config->show_levels,config->debug_nmea,ctx->stereo.bl_len,config->seconds_for_decoder_stats, config->use_tcp_listener, config->tcp_keep_ais_time, config->add_sample_num); if(ret != 0){ fprintf(stderr,"Error initializing built-in AIS decoder\n"); rtlsdr_cancel_async(ctx->dev); diff --git a/rtl_ais.h b/rtl_ais.h index 4f27758..bec41ce 100644 --- a/rtl_ais.h +++ b/rtl_ais.h @@ -31,6 +31,8 @@ struct rtl_ais_config /* Aisdecoder */ int show_levels, debug_nmea; char *port, *host, *filename; + + int add_sample_num; }; struct rtl_ais_context; From eb62441d7056b3ac1d5666bf8cccbe4d084dd249 Mon Sep 17 00:00:00 2001 From: Bertold Van den Bergh Date: Wed, 20 Feb 2019 20:31:30 +0100 Subject: [PATCH 066/117] Fix extended NMEA for packets that require more than one sentence --- aisdecoder/lib/protodec.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/aisdecoder/lib/protodec.c b/aisdecoder/lib/protodec.c index a6855ff..d23bff1 100644 --- a/aisdecoder/lib/protodec.c +++ b/aisdecoder/lib/protodec.c @@ -211,13 +211,14 @@ void protodec_generate_nmea(struct demod_state_t *d, int bufferlen, int fillbits nmeachk = d->nmea[m++]; while (d->nmea[m] != '*') nmeachk ^= d->nmea[m++]; + int inc; if (d->add_sample_num){ - sprintf(&d->nmea[k + 3], "%02X,%lu\r\n", nmeachk, d->startsample); + inc = sprintf(&d->nmea[k + 3], "%02X,%lu\r\n", nmeachk, d->startsample); }else{ - sprintf(&d->nmea[k + 3], "%02X\r\n", nmeachk); + inc = sprintf(&d->nmea[k + 3], "%02X\r\n", nmeachk); } if (on_nmea_sentence_received != NULL) - on_nmea_sentence_received(d->nmea, k+7, sentences, sentencenum); + on_nmea_sentence_received(d->nmea, k+3+inc, sentences, sentencenum); } while (sentencenum < sentences); } From 6884447348b5798984455656f6d9d0fa54b1657d Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 21 Feb 2019 14:56:47 -0300 Subject: [PATCH 067/117] Move variable declaration for C compliance --- aisdecoder/lib/protodec.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/aisdecoder/lib/protodec.c b/aisdecoder/lib/protodec.c index d23bff1..9b80f51 100644 --- a/aisdecoder/lib/protodec.c +++ b/aisdecoder/lib/protodec.c @@ -158,9 +158,11 @@ void protodec_generate_nmea(struct demod_state_t *d, int bufferlen, int fillbits { int senlen; int pos; - int k, offset; + int k, offset; int m; - unsigned char sentences, sentencenum, nmeachk, letter; + int inc; + + unsigned char sentences, sentencenum, nmeachk, letter; //6bits to nmea-ascii. One sentence len max 82char //inc. head + tail.This makes inside datamax 62char multipart, 62 single @@ -211,7 +213,6 @@ void protodec_generate_nmea(struct demod_state_t *d, int bufferlen, int fillbits nmeachk = d->nmea[m++]; while (d->nmea[m] != '*') nmeachk ^= d->nmea[m++]; - int inc; if (d->add_sample_num){ inc = sprintf(&d->nmea[k + 3], "%02X,%lu\r\n", nmeachk, d->startsample); }else{ From c838eac5733b0402593fb2388f4523e4b6161a67 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 21 Feb 2019 18:55:18 -0300 Subject: [PATCH 068/117] switch -I added --- README | 1 + 1 file changed, 1 insertion(+) diff --git a/README b/README index 05f8d50..fbd6fe8 100644 --- a/README +++ b/README @@ -30,6 +30,7 @@ Use: rtl_ais [options] [outputfile] [-t time to keep ais messages in sec, using tcp listener (default: 15)] [-n log NMEA sentences to console (stderr) (default off)] [-L log sound levels to console (stderr) (default off)] + [-I add sample index to NMEA mesages (default off)] [-S seconds_for_decoder_stats (default 0=off)] When the built-in AIS decoder is disabled the samples are sent to to [outputfile] (a '-' dumps samples to stdout) From 3c15243bfbc38eb4297ce610cdb79ae83775c818 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 3 Jul 2019 20:17:36 -0700 Subject: [PATCH 069/117] Add missing include for musl --- tcp_listener/tcp_listener.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c index f740a68..8d9fbb1 100644 --- a/tcp_listener/tcp_listener.c +++ b/tcp_listener/tcp_listener.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include From 1b6ac5b0f99bba55d7eade95c501f1c59c9e6bce Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Thu, 3 Oct 2019 11:06:06 -0700 Subject: [PATCH 070/117] treewide: Remove bzero and usleep Both were removed in POSIX 2008 and are optionally unavailable with uClibc-ng. Signed-off-by: Rosen Penev --- main.c | 3 ++- tcp_listener/tcp_listener.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/main.c b/main.c index 4bfc5f4..b4830ad 100644 --- a/main.c +++ b/main.c @@ -216,6 +216,7 @@ int main(int argc, char **argv) nmea_sentence_received() in aidecoder.c */ while(!do_exit && rtl_ais_isactive(ctx)) { + struct timespec five = { 0, 50 * 1000 * 1000}; const char *str; if(config.use_internal_aisdecoder) { @@ -225,7 +226,7 @@ int main(int argc, char **argv) //puts(str); or code something that fits your needs } } - usleep(50000); + nanosleep(&five, NULL); } rtl_ais_cleanup(ctx); return 0; diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c index 8d9fbb1..34d8175 100644 --- a/tcp_listener/tcp_listener.c +++ b/tcp_listener/tcp_listener.c @@ -87,7 +87,7 @@ int initTcpSocket(const char *portnumber, int debug_nmea, int tcp_keep_ais_time) fprintf(stderr, "Failed to create socket! error %d\n", errno); return 0; } - bzero((char *) &serv_addr, sizeof(serv_addr)); + memset((char *) &serv_addr, 0, sizeof(serv_addr)); portno = atoi(portnumber); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; From 5e25eabc34c6dbd379e6676c886a46672ff8c292 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 3 Oct 2019 16:04:59 -0300 Subject: [PATCH 071/117] Check for nanosleep() availability --- main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main.c b/main.c index b4830ad..23cf1c2 100644 --- a/main.c +++ b/main.c @@ -216,7 +216,9 @@ int main(int argc, char **argv) nmea_sentence_received() in aidecoder.c */ while(!do_exit && rtl_ais_isactive(ctx)) { + #if _POSIX_C_SOURCE >= 199309L // nanosleep available() struct timespec five = { 0, 50 * 1000 * 1000}; + #endif const char *str; if(config.use_internal_aisdecoder) { @@ -226,7 +228,11 @@ int main(int argc, char **argv) //puts(str); or code something that fits your needs } } + #if _POSIX_C_SOURCE >= 199309L // nanosleep available() nanosleep(&five, NULL); + #else + usleep(50000); + #endif } rtl_ais_cleanup(ctx); return 0; From a3338e82c993eeebb708e41153ac669209fd6549 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 3 Oct 2019 16:25:34 -0300 Subject: [PATCH 072/117] Fix compliation "error multiple definition of `__mac_g'". Credits to TrevorSpartacus @ reddit --- aisdecoder/lib/filter-i386.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aisdecoder/lib/filter-i386.h b/aisdecoder/lib/filter-i386.h index 0cde026..afdc645 100644 --- a/aisdecoder/lib/filter-i386.h +++ b/aisdecoder/lib/filter-i386.h @@ -32,7 +32,7 @@ #include -extern inline float __mac_g(const float *a, const float *b, +__attribute__ ((gnu_inline)) extern inline float __mac_g(const float *a, const float *b, unsigned int size) { float sum = 0; @@ -43,7 +43,7 @@ extern inline float __mac_g(const float *a, const float *b, return sum; } -extern inline float __mac_c(const float *a, const float *b, +__attribute__ ((gnu_inline)) extern inline float __mac_c(const float *a, const float *b, unsigned int size) { float f; From 6a5fc2cf4a63c46fe2f9a22487b822b1f4e21d9e Mon Sep 17 00:00:00 2001 From: Keonsoon Hwang Date: Mon, 30 Dec 2019 13:32:10 +0900 Subject: [PATCH 073/117] fix buffer overflow --- tcp_listener/tcp_listener.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c index 34d8175..982cc34 100644 --- a/tcp_listener/tcp_listener.c +++ b/tcp_listener/tcp_listener.c @@ -47,6 +47,7 @@ pthread_t tcp_listener_thread; typedef struct t_ais_mess { char message[100]; // max on nmea message is 83 char's + char *plmess; int length; struct timeval timestamp; struct t_ais_mess *next; @@ -181,7 +182,10 @@ void *handle_remote_close(void *arg) { // send saved ais_messages to new socket ais_temp = ais_head; while (ais_temp != NULL) { - rc = send(t->sock, ais_temp->message, ais_temp->length, 0); + if (ais_temp->plmess != NULL) + rc = send(t->sock, ais_temp->plmess, ais_temp->length, 0); + else + rc = send(t->sock, ais_temp->message, ais_temp->length, 0); if( _debug) fprintf( stdout, "%d: Send to %s, <%.*s>, rc=%d\n",ais_temp->timestamp.tv_sec, t->from_ip, ais_temp->length, ais_temp->message, rc); ais_temp = ais_temp->next; @@ -292,7 +296,18 @@ int add_nmea_ais_message(const char * mess, unsigned int length) { // allocate an add the new message new_node = (P_AIS_MESS) malloc(sizeof(AIS_MESS)); - strncpy(new_node->message, mess, length); + if (length>=sizeof(new_node->message)) { + new_node->plmess = malloc(length); + if(new_node->plmess == NULL) { + free(new_node); + return -1; + } + strncpy(new_node->plmess, mess, length); + new_node->message[0] = 0; // Just in case + } else { + new_node->plmess = NULL; + strncpy(new_node->message, mess, length); + } new_node->length = length; gettimeofday(&new_node->timestamp, NULL); @@ -332,6 +347,8 @@ void delete_ais_node(P_AIS_MESS p) { if (ais_end == temp) ais_end = prev; } + if (p->plmess != NULL) + free(p->plmess); free(p); } From b8e4efd230b120bb463f630f270dd0598af6a72b Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Wed, 19 Feb 2020 13:18:42 -0500 Subject: [PATCH 074/117] Add gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4db8416 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +rtl_ais From b935caccccd9d733895fad54b8424d9908d90e2a Mon Sep 17 00:00:00 2001 From: dgiardini Date: Wed, 19 Feb 2020 15:33:35 -0300 Subject: [PATCH 075/117] Get rid of warnings --- main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/main.c b/main.c index 23cf1c2..4c43d51 100644 --- a/main.c +++ b/main.c @@ -20,6 +20,7 @@ #include #include #include +#include typedef void* rtlsdr_dev_t; #include "convenience.h" From 4861507586733eb70e58c977cc63e440d55a05ae Mon Sep 17 00:00:00 2001 From: dgiardini Date: Wed, 19 Feb 2020 15:39:13 -0300 Subject: [PATCH 076/117] Avoid compiling warnings --- tcp_listener/tcp_listener.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c index 982cc34..18b5141 100644 --- a/tcp_listener/tcp_listener.c +++ b/tcp_listener/tcp_listener.c @@ -119,7 +119,7 @@ void closeTcpSocket() { sleep(3); close(sockfd); #endif - + return 0; } // ------------------------------------------------------------ @@ -127,6 +127,7 @@ void closeTcpSocket() { // ------------------------------------------------------------ static void *tcp_listener_fn(void *arg) { int rc; + arg=arg; // not used, avoid compiling warnings P_TCP_SOCK t; fprintf(stderr, "Tcp listen port %d\nAis message timeout with %d\n", portno, _tcp_keep_ais_time); @@ -174,8 +175,7 @@ void *handle_remote_close(void *arg) { struct timeval timeout; timeout.tv_sec = 10; timeout.tv_usec = 0; - setsockopt(t->sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); - + setsockopt(t->sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); // get rid of old messages before send remove_old_ais_messages(); @@ -187,13 +187,13 @@ void *handle_remote_close(void *arg) { else rc = send(t->sock, ais_temp->message, ais_temp->length, 0); if( _debug) - fprintf( stdout, "%d: Send to %s, <%.*s>, rc=%d\n",ais_temp->timestamp.tv_sec, t->from_ip, ais_temp->length, ais_temp->message, rc); + fprintf( stdout, "%ld: Send to %s, <%.*s>, rc=%d\n",ais_temp->timestamp.tv_sec, t->from_ip, ais_temp->length, ais_temp->message, rc); ais_temp = ais_temp->next; } while(1){ - rc = recv(t->sock, buff, 99, 0); + rc = recv(t->sock, (char *)buff, 99, 0); if( rc < 0) { // check timeout @@ -221,6 +221,7 @@ void *handle_remote_close(void *arg) { close(t->sock); #endif delete_node(t); + return 0; } @@ -241,7 +242,7 @@ int accept_c(P_TCP_SOCK p_tcp_sock) { return error_category(errno); } - if (setsockopt(p_tcp_sock->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) { + if (setsockopt(p_tcp_sock->sock, SOL_SOCKET, SO_KEEPALIVE,(char *) &optval, optlen) < 0) { fprintf(stderr, "Failed to set option keepalive!, error = %d\n", errno); return error_category(errno); } @@ -417,6 +418,7 @@ void delete_node(P_TCP_SOCK p) { // ------------------------------------------------------------------ int error_category(int rc) { #if defined (__WIN32__) + rc=rc; // Not used, avoid compiling warnings return -1; // Just work as sis #else switch (rc) { From 36aef504280c8b4a9c09c1785e9ea2f8f34d1983 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Wed, 19 Feb 2020 15:42:14 -0300 Subject: [PATCH 077/117] Avoid compiling warnings --- aisdecoder/lib/protodec.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/aisdecoder/lib/protodec.c b/aisdecoder/lib/protodec.c index 9b80f51..90d44c7 100644 --- a/aisdecoder/lib/protodec.c +++ b/aisdecoder/lib/protodec.c @@ -156,14 +156,14 @@ unsigned long protodec_henten(int from, int size, unsigned char *frame) void protodec_generate_nmea(struct demod_state_t *d, int bufferlen, int fillbits, time_t received_t) { - int senlen; - int pos; - int k, offset; - int m; - int inc; - - unsigned char sentences, sentencenum, nmeachk, letter; - + int senlen; + int pos; + int k, offset; + int m; + int inc; + + unsigned char sentences, sentencenum, nmeachk, letter; + received_t=received_t; // not used here, avoid compiling warnings //6bits to nmea-ascii. One sentence len max 82char //inc. head + tail.This makes inside datamax 62char multipart, 62 single senlen = 56; //this is normally not needed.For testing only. May be fixed number From 7644b9a1cdd1e5b048b449b46b2a059d8192ee1c Mon Sep 17 00:00:00 2001 From: dgiardini Date: Wed, 19 Feb 2020 15:46:41 -0300 Subject: [PATCH 078/117] Avoid compiling warnings --- tcp_listener/tcp_listener.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcp_listener/tcp_listener.c b/tcp_listener/tcp_listener.c index 18b5141..e1fe652 100644 --- a/tcp_listener/tcp_listener.c +++ b/tcp_listener/tcp_listener.c @@ -119,7 +119,6 @@ void closeTcpSocket() { sleep(3); close(sockfd); #endif - return 0; } // ------------------------------------------------------------ @@ -160,6 +159,7 @@ static void *tcp_listener_fn(void *arg) { #else close(t->sock); #endif + return 0; } From bd68526d2a255f9757525e17eb88c2a54a9e8abc Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Wed, 19 Feb 2020 13:55:24 -0500 Subject: [PATCH 079/117] Add make install for linux --- Makefile | 7 +++++++ README | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 12c0133..3f01019 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ CFLAGS?=-O2 -g -Wall -W CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib -I./tcp_listener LDFLAGS+=-lpthread -lm +ifeq ($(PREFIX),) + PREFIX := /usr/local +endif UNAME := $(shell uname) ifeq ($(UNAME),Linux) #Conditional for Linux @@ -60,3 +63,7 @@ $(EXECUTABLE): $(OBJECTS) clean: rm -f $(OBJECTS) $(EXECUTABLE) $(EXECUTABLE).exe + +install: + install -d $(DESTDIR)$(PREFIX)/bin + install -m 755 $(DESTDIR)$(EXECUTABLE) "$(PREFIX)/bin/" \ No newline at end of file diff --git a/README b/README index fbd6fe8..ef748ea 100644 --- a/README +++ b/README @@ -71,7 +71,7 @@ RTLSDR_LIB=$(RTLSDR_PATH)/build/src/ ------- INSTALL ------- -On Linux, copy the rtl_ais file to a binary path, i.e. /usr/bin, or /usr/local/bin. +On Linux, sudo make install On Windows, put the librtlsdr.dll and libusb-1.0.dll files in the same directory with rtl_ais.exe. You'll need the zadig driver installed too. From 6d9ee6487648400df1f54fdfc153c4d31154477f Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Mon, 2 Mar 2020 09:08:49 -0500 Subject: [PATCH 080/117] Update README format and include hardware information --- README => README.md | 98 ++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 36 deletions(-) rename README => README.md (56%) diff --git a/README b/README.md similarity index 56% rename from README rename to README.md index ef748ea..4e3ab9a 100644 --- a/README +++ b/README.md @@ -1,11 +1,18 @@ ----------------------------------------------------------------------- -rtl_ais, a simple AIS tuner and generic dual-frequency FM demodulator -OS: Linux-Windows-OSX. ----------------------------------------------------------------------- +`rtl-ais`, a simple AIS tuner and generic dual-frequency FM demodulator +----------------------------------------------------------------------- +rtl-ais provides the `rtl_ais` command, which decodes AIS data from Software Defined Radio (SDR) and outputs `AIVDM` / `AIVDO` sentences. + +| OS support | | +|------------|---| +| Linux | ✅ | +| Windows | ✅ | +| OSX | ✅ | + + +Command Line ------------ -COMMAND LINE ------------- +``` Use: rtl_ais [options] [outputfile] [-l left_frequency (default: 161.975M)] [-r right_frequency (default: 162.025M)] @@ -42,56 +49,75 @@ Use: rtl_ais [options] [outputfile] rtl_ais -n Tune two fm stations and play one on each channel: rtl_ais -l233.15M -r233.20M -A | play -r48k -traw -es -b16 -c2 -V1 - +``` + +Compiling --------- -COMPILING ---------- Make sure you have the following dependencies: - librtlsdr - libusb - libpthread -Get the source code: - git clone https://github.com/dgiardini/rtl-ais -Change to the source dir - cd rtl-ais -then type - make - -Test running - ./rtl_ais +```console +$ # Get the source code: +$ git clone https://github.com/dgiardini/rtl-ais +$ # Change to the source dir +$ cd rtl-ais +$ make +$ # Test running the command +$ ./rtl_ais +``` For compiling a MS Windows executable you will need a working MSYS/MinGW environment. -Edit the Makefile, and modify these lines: +Edit the `Makefile`, and modify these lines: +```Makefile #### point this to your correct path ### RTLSDR_PATH="/c/tmp/rtl-sdr/" RTLSDR_LIB=$(RTLSDR_PATH)/build/src/ ######################################## -------- -INSTALL +``` + + +Installing +---------- +* On Linux, `sudo make install` +* On Windows, put the `librtlsdr.dll` and `libusb-1.0.dll` files in the same directory +with `rtl_ais.exe`. You'll need the `zadig` driver installed too. + + +Running ------- -On Linux, sudo make install -On Windows, put the librtlsdr.dll and libusb-1.0.dll files in the same directory -with rtl_ais.exe. You'll need the zadig driver installed too. ------------- -KNOWN ISSUES ------------- -The [-p ppm error] parameter is essential for rtl_ais to work. -The ppm error is the frequency deviation in parts per million from the desired tuning -frequency, and the real tuned frequency due to the crystal oscillator deviation. This -figure is different for each device, it's very important to know this value and pass this parameter to rtl_ais. +rtl-ais uses software defined radio (SDR). The specific +hardware we use for this is a DVB-T dongle. A good starting point is: +https://www.rtl-sdr.com/about-rtl-sdr -Some instructions for get the ppm error are here: +You'll need also an antenna, and be located near (some miles) the +passing vessels. -http://www.rtl-sdr.com/how-to-calibrate-rtl-sdr-using-kalibrate-rtl-on-linux +You'll also need to do some procedure to get the tunning error for the +specfic dongle you have (aka ppm error), and pass that number as parameter +of rtl-ais. -and here (using SDR#): -http://www.atouk.com/SDRSharpQuickStart.html#adjusting +Testing +------- -and here (using HDSDR ad AIS traffic) +TODO: something like +https://github.com/freerange/ais-on-sdr/wiki/Testing-AISDecoder#with-an-audio-file -http://www.cruisersforum.com/forums/f134/new-rtlsdr-plugin-102929-11.html#post1844966 +Known Issues +------------ +* The `[-p ppm error]` parameter is essential for rtl_ais to work. + * The ppm error is the frequency deviation in parts per million from the desired tuning +frequency, and the real tuned frequency due to the crystal oscillator deviation. This +figure is different for each device, it's very important to know this value and pass this parameter to rtl_ais. + * Some instructions for get the ppm error are here: + http://www.rtl-sdr.com/how-to-calibrate-rtl-sdr-using-kalibrate-rtl-on-linux + * and here (using SDR#): + http://www.atouk.com/SDRSharpQuickStart.html#adjusting + * and here (using HDSDR ad AIS traffic) + http://www.cruisersforum.com/forums/f134/new-rtlsdr-plugin-102929-11.html#post1844966 From 29a51d929f422d77441b3e9353d0046822f470c1 Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Sat, 8 Aug 2020 13:07:08 -0400 Subject: [PATCH 081/117] Add Debian package builder and related procedure. --- .gitignore | 2 ++ debianPackage/DEBIAN/control | 11 +++++++++++ debianPackage/readme.md | 13 +++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 debianPackage/DEBIAN/control create mode 100644 debianPackage/readme.md diff --git a/.gitignore b/.gitignore index 4db8416..7b24d69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.o rtl_ais +debianPackage/usr +debianPackage/DEBIAN/*.deb \ No newline at end of file diff --git a/debianPackage/DEBIAN/control b/debianPackage/DEBIAN/control new file mode 100644 index 0000000..d21d5ed --- /dev/null +++ b/debianPackage/DEBIAN/control @@ -0,0 +1,11 @@ +Package: rtl-ais +Version: 0.3 +Section: custom +Priority: optional +Architecture: any +Essential: no +Installed-Size: 1024 +Maintainer: "dgiardini" +Uploaders: "Frederic Guilbault" +Homepage: https://github.com/dgiardini/rtl-ais +Description: AIS tuner and generic dual-frequency FM demodulator diff --git a/debianPackage/readme.md b/debianPackage/readme.md new file mode 100644 index 0000000..85abeb0 --- /dev/null +++ b/debianPackage/readme.md @@ -0,0 +1,13 @@ + + +Step 1, make sure you have the required dependencys : +```sudo apt-get install build-essential``` + +Step 2, copy the compiled binary into the package location (implying rtl-ais have been compiled already): +```mkdir -p ./usr/bin && cp ../rtl_ais ./usr/bin/rtl_ais``` + +Step 3, build the package : +```dpkg-deb --build ./ rtl_ais_0.3-1_$(arch).deb``` + +Step 4, renaming package : +```dpkg-deb --build``` \ No newline at end of file From f39a98950819f280eb7fa52e53b25fad8d170ffb Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Sat, 8 Aug 2020 13:20:29 -0400 Subject: [PATCH 082/117] Fix control file --- .gitignore | 2 +- debianPackage/DEBIAN/control | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 7b24d69..164a328 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ *.o rtl_ais debianPackage/usr -debianPackage/DEBIAN/*.deb \ No newline at end of file +debianPackage/*.deb \ No newline at end of file diff --git a/debianPackage/DEBIAN/control b/debianPackage/DEBIAN/control index d21d5ed..9a34f7d 100644 --- a/debianPackage/DEBIAN/control +++ b/debianPackage/DEBIAN/control @@ -2,10 +2,10 @@ Package: rtl-ais Version: 0.3 Section: custom Priority: optional -Architecture: any +Architecture: all Essential: no Installed-Size: 1024 Maintainer: "dgiardini" -Uploaders: "Frederic Guilbault" +Uploaders: "Frederic Guilbault <2@0464.ca>" Homepage: https://github.com/dgiardini/rtl-ais Description: AIS tuner and generic dual-frequency FM demodulator From bcf17b7d5f3c176583a877f20afe7960f87ec325 Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Sat, 8 Aug 2020 13:33:55 -0400 Subject: [PATCH 083/117] Fix errors in debian packaging documentation. --- .gitignore | 2 +- debianPackage/readme.md | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 164a328..39281c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ *.o rtl_ais debianPackage/usr -debianPackage/*.deb \ No newline at end of file +debianPackage/*.deb diff --git a/debianPackage/readme.md b/debianPackage/readme.md index 85abeb0..7b71159 100644 --- a/debianPackage/readme.md +++ b/debianPackage/readme.md @@ -8,6 +8,3 @@ Step 2, copy the compiled binary into the package location (implying rtl-ais hav Step 3, build the package : ```dpkg-deb --build ./ rtl_ais_0.3-1_$(arch).deb``` - -Step 4, renaming package : -```dpkg-deb --build``` \ No newline at end of file From 1beaeeae139486af09568cfecf28c4e397390f4e Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Mon, 31 Aug 2020 19:13:23 -0400 Subject: [PATCH 084/117] Add licence file ( Github love licence files ) --- LICENCE | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 LICENCE diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..bf101e2 --- /dev/null +++ b/LICENCE @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2012 by Kyle Keen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ \ No newline at end of file From 00c2fc7d257115abfcb8017b80230efe10d2307b Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Mon, 31 Aug 2020 21:35:39 -0400 Subject: [PATCH 085/117] Fix make install, DESTDIR is for the target not source location. --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3f01019..dcca06f 100644 --- a/Makefile +++ b/Makefile @@ -65,5 +65,6 @@ clean: rm -f $(OBJECTS) $(EXECUTABLE) $(EXECUTABLE).exe install: - install -d $(DESTDIR)$(PREFIX)/bin - install -m 755 $(DESTDIR)$(EXECUTABLE) "$(PREFIX)/bin/" \ No newline at end of file + install -d -m 755 $(DESTDIR)/$(PREFIX)/bin + install -m 755 $(EXECUTABLE) "$(DESTDIR)/$(PREFIX)/bin/" + From d093381fdc8224c89f3a4f2cd232c53e52e6cfc2 Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Tue, 1 Sep 2020 08:18:09 -0400 Subject: [PATCH 086/117] Rework debian packaging to support source packages --- debian/changelog | 6 ++++++ debian/compat | 1 + {debianPackage/DEBIAN => debian}/control | 12 ++++++++---- debian/copyright | 16 ++++++++++++++++ debian/readme.md | 12 ++++++++++++ debian/rules | 7 +++++++ debianPackage/readme.md | 10 ---------- 7 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 debian/changelog create mode 100644 debian/compat rename {debianPackage/DEBIAN => debian}/control (68%) create mode 100644 debian/copyright create mode 100644 debian/readme.md create mode 100755 debian/rules delete mode 100644 debianPackage/readme.md diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..883277f --- /dev/null +++ b/debian/changelog @@ -0,0 +1,6 @@ +rtl-ais (0.3) UNRELEASED; urgency=low + + [ Frederic Guilbault ] + * Initial release. + + -- frederic guilbault <2@0464.ca> Sat, 29 Aug 2020 15:09:41 -0400 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..f599e28 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +10 diff --git a/debianPackage/DEBIAN/control b/debian/control similarity index 68% rename from debianPackage/DEBIAN/control rename to debian/control index 9a34f7d..82b4d1c 100644 --- a/debianPackage/DEBIAN/control +++ b/debian/control @@ -1,11 +1,15 @@ +Source: rtl-ais +Maintainer: "dgiardini" +Uploaders: "Frederic Guilbault <2@0464.ca>" +Section: custom +Priority: optional +Standards-Version: 0.3 +Build-Depends: debhelper (>=9),librtlsdr-dev + Package: rtl-ais -Version: 0.3 Section: custom Priority: optional Architecture: all Essential: no -Installed-Size: 1024 -Maintainer: "dgiardini" -Uploaders: "Frederic Guilbault <2@0464.ca>" Homepage: https://github.com/dgiardini/rtl-ais Description: AIS tuner and generic dual-frequency FM demodulator diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..bf101e2 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2012 by Kyle Keen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ \ No newline at end of file diff --git a/debian/readme.md b/debian/readme.md new file mode 100644 index 0000000..50a2c91 --- /dev/null +++ b/debian/readme.md @@ -0,0 +1,12 @@ +# Build rtl_ais into a debian package + +First, make sure you have the required dependencies : + ``` +sudo apt-get install build-essential devscripts lintian +``` + +Step 2, copy the compiled binary into the package location (implying rtl-ais have been compiled already): +``` +debuild -i -us -uc -b # for local package +debuild -S # for source package (required by launchpad) +``` diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..48f8ee5 --- /dev/null +++ b/debian/rules @@ -0,0 +1,7 @@ +#!/usr/bin/make -f +export DH_VERBOSE = 1 +%: + dh $@ + +override_dh_auto_install: + $(MAKE) DESTDIR=$$(pwd)/debian/rtl-ais PREFIX=/usr install diff --git a/debianPackage/readme.md b/debianPackage/readme.md deleted file mode 100644 index 7b71159..0000000 --- a/debianPackage/readme.md +++ /dev/null @@ -1,10 +0,0 @@ - - -Step 1, make sure you have the required dependencys : -```sudo apt-get install build-essential``` - -Step 2, copy the compiled binary into the package location (implying rtl-ais have been compiled already): -```mkdir -p ./usr/bin && cp ../rtl_ais ./usr/bin/rtl_ais``` - -Step 3, build the package : -```dpkg-deb --build ./ rtl_ais_0.3-1_$(arch).deb``` From 5a5abc61e24bd287078bd32a8b4a6094e7ea2d9d Mon Sep 17 00:00:00 2001 From: dgiardini Date: Tue, 1 Sep 2020 11:29:28 -0300 Subject: [PATCH 087/117] Update LICENCE Added credits to AisHub and GNUAIS --- LICENCE | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/LICENCE b/LICENCE index bf101e2..0ad6d5b 100644 --- a/LICENCE +++ b/LICENCE @@ -1,6 +1,8 @@ /* - * Copyright (C) 2012 by Kyle Keen - * + * Copyright (C) 2012 by Kyle Keen + * rtl-ais uses code from AISDecoder Copyright (C) 2013 Astra Paging Ltd / AISHub (info@aishub.net) + * AISDecoder uses parts of GNUAIS project (http://gnuais.sourceforge.net/) + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or @@ -13,4 +15,4 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - */ \ No newline at end of file + */ From 1975fddce06fa745cae4adf549d3548ec8ee07b6 Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Fri, 2 Oct 2020 22:12:32 -0400 Subject: [PATCH 088/117] small modifications to make the debian package compliant with standards --- debian/control | 7 ++++--- debian/source/format | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 debian/source/format diff --git a/debian/control b/debian/control index 82b4d1c..4369f57 100644 --- a/debian/control +++ b/debian/control @@ -1,9 +1,9 @@ Source: rtl-ais -Maintainer: "dgiardini" -Uploaders: "Frederic Guilbault <2@0464.ca>" +Maintainer: dgiardini +Uploaders: Frederic Guilbault <2@0464.ca> Section: custom Priority: optional -Standards-Version: 0.3 +Standards-Version: 4.5.0 Build-Depends: debhelper (>=9),librtlsdr-dev Package: rtl-ais @@ -11,5 +11,6 @@ Section: custom Priority: optional Architecture: all Essential: no +Depends: ${shlibs:Depends}, ${misc:Depends}, Homepage: https://github.com/dgiardini/rtl-ais Description: AIS tuner and generic dual-frequency FM demodulator diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..9f67427 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) \ No newline at end of file From a28765a1f891f40e0e822ff50b4aec33b3066e07 Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Mon, 5 Oct 2020 13:18:38 -0400 Subject: [PATCH 089/117] Force ubuntu release as a workaround to librtlsdr0 version bug --- debian/changelog | 2 +- debian/control | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 883277f..6420e96 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -rtl-ais (0.3) UNRELEASED; urgency=low +rtl-ais (0.3.ppa2) focal; urgency=low [ Frederic Guilbault ] * Initial release. diff --git a/debian/control b/debian/control index 4369f57..0562e98 100644 --- a/debian/control +++ b/debian/control @@ -1,7 +1,7 @@ Source: rtl-ais Maintainer: dgiardini Uploaders: Frederic Guilbault <2@0464.ca> -Section: custom +Section: comm Priority: optional Standards-Version: 4.5.0 Build-Depends: debhelper (>=9),librtlsdr-dev From 6de456383c3a9e46809f5233ae4722b2841f2234 Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Mon, 5 Oct 2020 17:23:53 -0400 Subject: [PATCH 090/117] fix bad category --- debian/control | 1 - 1 file changed, 1 deletion(-) diff --git a/debian/control b/debian/control index 0562e98..117482c 100644 --- a/debian/control +++ b/debian/control @@ -7,7 +7,6 @@ Standards-Version: 4.5.0 Build-Depends: debhelper (>=9),librtlsdr-dev Package: rtl-ais -Section: custom Priority: optional Architecture: all Essential: no From 26b13c920d533c3fd9a9631b207eae8aa03e68a3 Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Mon, 5 Oct 2020 17:32:33 -0400 Subject: [PATCH 091/117] Add missing dep --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 117482c..1535ebb 100644 --- a/debian/control +++ b/debian/control @@ -4,7 +4,7 @@ Uploaders: Frederic Guilbault <2@0464.ca> Section: comm Priority: optional Standards-Version: 4.5.0 -Build-Depends: debhelper (>=9),librtlsdr-dev +Build-Depends: debhelper (>=9),librtlsdr-dev, pkg-config Package: rtl-ais Priority: optional From 128011442413be67aefe01d7bb3f50ae426d21cf Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Tue, 13 Oct 2020 10:54:24 -0400 Subject: [PATCH 092/117] Build different package for respective arch. --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 1535ebb..70ef01b 100644 --- a/debian/control +++ b/debian/control @@ -8,7 +8,7 @@ Build-Depends: debhelper (>=9),librtlsdr-dev, pkg-config Package: rtl-ais Priority: optional -Architecture: all +Architecture: any Essential: no Depends: ${shlibs:Depends}, ${misc:Depends}, Homepage: https://github.com/dgiardini/rtl-ais From d77aaf4482fa7df95c03e50e6e26506e94b6275c Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Tue, 13 Oct 2020 11:04:48 -0400 Subject: [PATCH 093/117] Add missing runtime dependency --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 70ef01b..ad69d38 100644 --- a/debian/control +++ b/debian/control @@ -10,6 +10,6 @@ Package: rtl-ais Priority: optional Architecture: any Essential: no -Depends: ${shlibs:Depends}, ${misc:Depends}, +Depends: ${shlibs:Depends}, ${misc:Depends}, librtlsdr0 Homepage: https://github.com/dgiardini/rtl-ais Description: AIS tuner and generic dual-frequency FM demodulator From 29a7a8586f550de3ff2368f7e0967951fdd4bac0 Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Mon, 9 Nov 2020 12:01:38 -0500 Subject: [PATCH 094/117] Change build OS --- debian/changelog | 2 +- debian/control | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 6420e96..2440006 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -rtl-ais (0.3.ppa2) focal; urgency=low +rtl-ais (0.3.ppa1) bionic; urgency=low [ Frederic Guilbault ] * Initial release. diff --git a/debian/control b/debian/control index ad69d38..345c0e2 100644 --- a/debian/control +++ b/debian/control @@ -7,9 +7,9 @@ Standards-Version: 4.5.0 Build-Depends: debhelper (>=9),librtlsdr-dev, pkg-config Package: rtl-ais +Section: comm Priority: optional Architecture: any -Essential: no Depends: ${shlibs:Depends}, ${misc:Depends}, librtlsdr0 Homepage: https://github.com/dgiardini/rtl-ais Description: AIS tuner and generic dual-frequency FM demodulator From 6211704f6289462324a4c9491792eb3659a31e3a Mon Sep 17 00:00:00 2001 From: Frederic Guilbault <2@0464.ca> Date: Mon, 9 Nov 2020 12:03:15 -0500 Subject: [PATCH 095/117] Add ref to licence file location (requested by debuild linter ) --- debian/copyright | 5 ++++- debian/files | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 debian/files diff --git a/debian/copyright b/debian/copyright index bf101e2..885261e 100644 --- a/debian/copyright +++ b/debian/copyright @@ -13,4 +13,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - */ \ No newline at end of file + */ + On Debian systems, the full text of the GNU General Public + License version 2 can be found in the file + `/usr/share/common-licenses/GPL-2'. \ No newline at end of file diff --git a/debian/files b/debian/files new file mode 100644 index 0000000..b5d57d9 --- /dev/null +++ b/debian/files @@ -0,0 +1 @@ +rtl-ais_0.3.ppa13_source.buildinfo comm optional From 0535890ef62fa40bf6071cb91ed3bc400ba54079 Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Tue, 18 May 2021 18:18:09 -0700 Subject: [PATCH 096/117] Added first cut of Dockerfile for building rtl-ais --- Dockerfile | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..959af16 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM debian:stretch-slim + +ENV VER=${VER:-master} \ + REPO=https://github.com/dgiardini/rtl-ais.git \ + APP=/usr/src/app \ + GIT_SSL_NO_VERIFY=true + +WORKDIR $APP + +RUN apt-get update && apt-get install -y \ +git \ +rtl-sdr \ +librtlsdr-dev \ +make \ +build-essential \ +pkg-config \ +libusb-1.0-0-dev \ +&& git clone -b $VER $REPO $APP \ +&& make + +CMD $APP/rtl_ais -n + +#EXPOSE 10110/udp + From a60426d05e5ec0c309b2cab78f9b922c29ddd593 Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Tue, 18 May 2021 19:13:57 -0700 Subject: [PATCH 097/117] Removed reference to hardcoded git repository. Now copying in the git repository from the host, and building that. --- Dockerfile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 959af16..83e4e34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,13 @@ +# Can't use buster-slim because it needs librtlsdr-dev 0.5.3 +# See https://github.com/dgiardini/rtl-ais/issues/32 FROM debian:stretch-slim -ENV VER=${VER:-master} \ - REPO=https://github.com/dgiardini/rtl-ais.git \ - APP=/usr/src/app \ - GIT_SSL_NO_VERIFY=true +ENV APP=/usr/src/app WORKDIR $APP +COPY . $APP + RUN apt-get update && apt-get install -y \ git \ rtl-sdr \ @@ -15,8 +16,8 @@ make \ build-essential \ pkg-config \ libusb-1.0-0-dev \ -&& git clone -b $VER $REPO $APP \ -&& make +&& make \ +&& rm -rf /var/lib/apt/lists/* CMD $APP/rtl_ais -n From 0800467de80f06ff77a04f6cb7d86a043a241d7f Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Wed, 19 May 2021 21:09:11 -0700 Subject: [PATCH 098/117] Cleaned up dockerfile, removing unused packages after build. Image reduced from ~350 MB to 75 MB. --- Dockerfile | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 83e4e34..dc96d47 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ # Can't use buster-slim because it needs librtlsdr-dev 0.5.3 # See https://github.com/dgiardini/rtl-ais/issues/32 FROM debian:stretch-slim +LABEL "name"="rtl-ais" \ + "description"="AIS ship decoding using an RTL-SDR dongle" \ + "author"="Bryan Klofas KF6ZEO" ENV APP=/usr/src/app @@ -9,17 +12,16 @@ WORKDIR $APP COPY . $APP RUN apt-get update && apt-get install -y \ -git \ -rtl-sdr \ -librtlsdr-dev \ -make \ -build-essential \ -pkg-config \ -libusb-1.0-0-dev \ -&& make \ -&& rm -rf /var/lib/apt/lists/* + rtl-sdr \ + librtlsdr-dev \ + libusb-1.0-0-dev \ + make \ + build-essential \ + pkg-config \ + && make \ + && apt-get remove -y make build-essential pkg-config \ + && apt-get autoremove -y \ + && rm -rf /var/lib/apt/lists/* CMD $APP/rtl_ais -n -#EXPOSE 10110/udp - From c99eae08ad1d9eaf50face6646e344b7b7d4b3b2 Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Tue, 1 Jun 2021 21:31:47 -0700 Subject: [PATCH 099/117] Updated readme file with example Docker commands and container build instructions --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 4e3ab9a..8bfeb23 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,38 @@ specfic dongle you have (aka ppm error), and pass that number as parameter of rtl-ais. +Docker Container +---------------- +Run rtl-ais in a docker container. No dependencies to install. Total container size is approximately 75 MB. + +Two options: Either download and run a pre-built docker container, or you can built the container locally. + + 1. To test things out: `docker run -it --rm --device=/dev/bus/usb ghcr.io/bklofas/rtl-ais:latest` + * This image will run by default `./rtl_ais -n`, showing the received packets on STDOUT. + * Make sure at least one RTL-SDR dongle is connected. + * Startup messages and decoded packets will display in the terminal. Ctrl-C to kill. + + 1. For a more permanent setup, run the container in the background and add any options you want: `docker run -d --name rtl-ais --restart=unless-stopped --log-driver=local --network=host --device=/dev/bus/usb ghcr.io/bklofas/rtl-ais:latest ./rtl_ais -n -d 00000002 -h 127.0.0.1 -P 10110` + * -d: Start this container in daemon/background mode. + * --name: Name this anything you want. + * --restart: Automatically restart the container if something happens (reboot, USB problem), unless you have manually stopped the container (with `docker stop rtl-ais`). + * --log-driver: By default, docker uses the json log driver which will fill up your harddrive, depending on how busy your station is. local log driver defaults to 100MB of saved logs, and automatically rotates them. + * --network: Allows the container to talk to the internet, if you are sending the packets to an online service. + * --device: Allows the container to talk to the USB bus to access the RTL-SDR dongle. + * ./rtl_ais: Same command-line options as above. + * View the startup messages and decoded packets with `docker logs --follow rtl-ais` + +Building the container: + + * `git clone https://github.com/bklofas/rtl-ais.git` the repository, then from the folder `docker build -t rtl-ais .` + * Or, build the container without cloning the repository: `docker build https://github.com/bklofas/rtl-ais.git` + +Other tips and tricks: + + * If you have the `-n` flag, view the decoded AIS packets in real-time with `docker logs --follow rtl-ais` + * If you are only sending packets to one internet service (such as martinetraffic.com), you can use the `-h` and `-P` options that they provide. + + Testing ------- From f3b6d47ad94aafa95100bb1348b0f157a3143119 Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Sun, 6 Jun 2021 21:55:23 -0700 Subject: [PATCH 100/117] Cleaned up the README file, adding some more documentation --- README.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8bfeb23..ff68aab 100644 --- a/README.md +++ b/README.md @@ -104,22 +104,26 @@ of rtl-ais. Docker Container ---------------- -Run rtl-ais in a docker container. No dependencies to install. Total container size is approximately 75 MB. +Now you can run rtl-ais in a docker container. No dependencies to install. Total container size is approximately 75 MB. Get/install docker [here](https://docs.docker.com/get-docker/). -Two options: Either download and run a pre-built docker container, or you can built the container locally. +Two options for obtaining the container: Either download and run a pre-built container, or build the container locally. - 1. To test things out: `docker run -it --rm --device=/dev/bus/usb ghcr.io/bklofas/rtl-ais:latest` - * This image will run by default `./rtl_ais -n`, showing the received packets on STDOUT. + 1. Just to test things out: `docker run -it --rm --device=/dev/bus/usb ghcr.io/bklofas/rtl-ais:latest` + * This downloads a pre-built container from the Github container registry. + * This image will run by default `./rtl_ais -n`, showing the received packets on STDOUT. All other default values. + * You can add other ./rtl-sdr options, see below. * Make sure at least one RTL-SDR dongle is connected. - * Startup messages and decoded packets will display in the terminal. Ctrl-C to kill. + * Startup messages and decoded packets will display in the terminal. + * Ctrl-C to kill. + * Using the `--rm` flag will delete the container when you kill it. Otherwise, it will stay around until you prune. 1. For a more permanent setup, run the container in the background and add any options you want: `docker run -d --name rtl-ais --restart=unless-stopped --log-driver=local --network=host --device=/dev/bus/usb ghcr.io/bklofas/rtl-ais:latest ./rtl_ais -n -d 00000002 -h 127.0.0.1 -P 10110` * -d: Start this container in daemon/background mode. * --name: Name this anything you want. - * --restart: Automatically restart the container if something happens (reboot, USB problem), unless you have manually stopped the container (with `docker stop rtl-ais`). - * --log-driver: By default, docker uses the json log driver which will fill up your harddrive, depending on how busy your station is. local log driver defaults to 100MB of saved logs, and automatically rotates them. - * --network: Allows the container to talk to the internet, if you are sending the packets to an online service. - * --device: Allows the container to talk to the USB bus to access the RTL-SDR dongle. + * --restart=unless-stopped: Automatically restart the container if something happens (reboot, USB problem), unless you have manually stopped the container (with `docker stop rtl-ais`). + * --log-driver=local: By default, docker uses the json log driver which may fill up your harddrive, depending on how busy your station is. `local` log driver defaults to 100MB of saved logs, and automatically rotates them. + * --network=host: Allows the container to talk to the internet, if you are sending the packets to an online service. + * --device=: Allows the container to talk to the USB bus to access the RTL-SDR dongle. * ./rtl_ais: Same command-line options as above. * View the startup messages and decoded packets with `docker logs --follow rtl-ais` @@ -131,7 +135,7 @@ Building the container: Other tips and tricks: * If you have the `-n` flag, view the decoded AIS packets in real-time with `docker logs --follow rtl-ais` - * If you are only sending packets to one internet service (such as martinetraffic.com), you can use the `-h` and `-P` options that they provide. + * If you are only sending packets to one internet service (such as marinetraffic.com), you can use the `-h` and `-P` options that they send you. Testing From 23e3400bc92aedd4d88c3f4cdff8d0abab50d03c Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Wed, 9 Jun 2021 22:57:16 -0700 Subject: [PATCH 101/117] Added first cut of the github actions file for building the containers on github servers --- .github/workflows/container.yml | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/container.yml diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 0000000..5a575f0 --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,44 @@ +name: Docker Builds + on: + workflow_dispatch: + + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Cache Docker layers + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push to ghcr + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7 + push: ${{ github.event_name != 'pull_request' }} + tags: | + ghcr.io/bklofas/rtl-ais:latest + From 4c5004cb871461c05cdf701e53dacf40517a0706 Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Thu, 10 Jun 2021 21:25:48 -0700 Subject: [PATCH 102/117] One too much indentation for the workflow_dispatch --- .github/workflows/container.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 5a575f0..a95fc4c 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -1,6 +1,6 @@ name: Docker Builds - on: - workflow_dispatch: +on: + workflow_dispatch: jobs: From 50cdccb9b106434828c9045b156407e8849fba55 Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Thu, 10 Jun 2021 21:44:01 -0700 Subject: [PATCH 103/117] Workflow dispatch enabling? --- .github/workflows/container.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index a95fc4c..5f56c4e 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -1,7 +1,6 @@ name: Docker Builds -on: - workflow_dispatch: +on: workflow_dispatch jobs: build: From 0dccad4c8526c958f4ffc7068cd58d634c30ece4 Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Thu, 10 Jun 2021 21:52:08 -0700 Subject: [PATCH 104/117] Will brackets work? --- .github/workflows/container.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 5f56c4e..f8d5c4f 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -1,6 +1,6 @@ name: Docker Builds -on: workflow_dispatch +on: [workflow_dispatch] jobs: build: From ef0517ef952c63a95402ce376f91a28ddf5f3000 Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Mon, 14 Jun 2021 22:51:20 -0700 Subject: [PATCH 105/117] Updated the README file with some multi-arch clarifications --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ff68aab..75d1502 100644 --- a/README.md +++ b/README.md @@ -104,13 +104,14 @@ of rtl-ais. Docker Container ---------------- -Now you can run rtl-ais in a docker container. No dependencies to install. Total container size is approximately 75 MB. Get/install docker [here](https://docs.docker.com/get-docker/). +Now you can run rtl-ais in a docker container. No dependencies to install. Total container size is 55 to 75 MB, depending on the host architecture. Get/install docker [here](https://docs.docker.com/get-docker/). Two options for obtaining the container: Either download and run a pre-built container, or build the container locally. 1. Just to test things out: `docker run -it --rm --device=/dev/bus/usb ghcr.io/bklofas/rtl-ais:latest` * This downloads a pre-built container from the Github container registry. - * This image will run by default `./rtl_ais -n`, showing the received packets on STDOUT. All other default values. + * Architectures supported are i386, amd64, arm32v6, arm32v7, and arm64. Tested on amd64, arm32v7, and arm64. arm packages run on all RaspberryPi flavors. Your client will automatically download the appropriate architecture. + * This image will run by default `./rtl_ais -n`, showing the received packets on STDOUT. Uses all other default values. * You can add other ./rtl-sdr options, see below. * Make sure at least one RTL-SDR dongle is connected. * Startup messages and decoded packets will display in the terminal. @@ -120,22 +121,24 @@ Two options for obtaining the container: Either download and run a pre-built con 1. For a more permanent setup, run the container in the background and add any options you want: `docker run -d --name rtl-ais --restart=unless-stopped --log-driver=local --network=host --device=/dev/bus/usb ghcr.io/bklofas/rtl-ais:latest ./rtl_ais -n -d 00000002 -h 127.0.0.1 -P 10110` * -d: Start this container in daemon/background mode. * --name: Name this anything you want. - * --restart=unless-stopped: Automatically restart the container if something happens (reboot, USB problem), unless you have manually stopped the container (with `docker stop rtl-ais`). + * --restart=unless-stopped: Automatically restart the container if something happens (reboot, USB problem), unless you have manually stopped the container. * --log-driver=local: By default, docker uses the json log driver which may fill up your harddrive, depending on how busy your station is. `local` log driver defaults to 100MB of saved logs, and automatically rotates them. * --network=host: Allows the container to talk to the internet, if you are sending the packets to an online service. * --device=: Allows the container to talk to the USB bus to access the RTL-SDR dongle. - * ./rtl_ais: Same command-line options as above. + * ./rtl_ais: Same command-line options as non-containerized. * View the startup messages and decoded packets with `docker logs --follow rtl-ais` + * Stop the container with `docker stop rtl-ais` Building the container: - * `git clone https://github.com/bklofas/rtl-ais.git` the repository, then from the folder `docker build -t rtl-ais .` - * Or, build the container without cloning the repository: `docker build https://github.com/bklofas/rtl-ais.git` + * Clone the repository with `git clone https://github.com/dgiardini/rtl-ais.git` , then from the folder `docker build -t rtl-ais .` + * Or, build the container without cloning the repository: `docker build https://github.com/dgiardini/rtl-ais.git` Other tips and tricks: * If you have the `-n` flag, view the decoded AIS packets in real-time with `docker logs --follow rtl-ais` - * If you are only sending packets to one internet service (such as marinetraffic.com), you can use the `-h` and `-P` options that they send you. + * If you are only sending packets to one internet service (such as marinetraffic.com), you can use the `-h` and `-P` options that they provide. + * You are encouraged to send your decoded packets to multiple online services. Check out AIS packet multiplexer [kplex](http://www.stripydog.com/kplex/), pre-built containers are available [here](https://ghcr.io/bklofas/kplex). Testing From e61e3503151f6861354847356a9f26776d99937b Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Mon, 14 Jun 2021 23:00:09 -0700 Subject: [PATCH 106/117] Updated README with clarification for multi-arch docker packages --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ff68aab..75d1502 100644 --- a/README.md +++ b/README.md @@ -104,13 +104,14 @@ of rtl-ais. Docker Container ---------------- -Now you can run rtl-ais in a docker container. No dependencies to install. Total container size is approximately 75 MB. Get/install docker [here](https://docs.docker.com/get-docker/). +Now you can run rtl-ais in a docker container. No dependencies to install. Total container size is 55 to 75 MB, depending on the host architecture. Get/install docker [here](https://docs.docker.com/get-docker/). Two options for obtaining the container: Either download and run a pre-built container, or build the container locally. 1. Just to test things out: `docker run -it --rm --device=/dev/bus/usb ghcr.io/bklofas/rtl-ais:latest` * This downloads a pre-built container from the Github container registry. - * This image will run by default `./rtl_ais -n`, showing the received packets on STDOUT. All other default values. + * Architectures supported are i386, amd64, arm32v6, arm32v7, and arm64. Tested on amd64, arm32v7, and arm64. arm packages run on all RaspberryPi flavors. Your client will automatically download the appropriate architecture. + * This image will run by default `./rtl_ais -n`, showing the received packets on STDOUT. Uses all other default values. * You can add other ./rtl-sdr options, see below. * Make sure at least one RTL-SDR dongle is connected. * Startup messages and decoded packets will display in the terminal. @@ -120,22 +121,24 @@ Two options for obtaining the container: Either download and run a pre-built con 1. For a more permanent setup, run the container in the background and add any options you want: `docker run -d --name rtl-ais --restart=unless-stopped --log-driver=local --network=host --device=/dev/bus/usb ghcr.io/bklofas/rtl-ais:latest ./rtl_ais -n -d 00000002 -h 127.0.0.1 -P 10110` * -d: Start this container in daemon/background mode. * --name: Name this anything you want. - * --restart=unless-stopped: Automatically restart the container if something happens (reboot, USB problem), unless you have manually stopped the container (with `docker stop rtl-ais`). + * --restart=unless-stopped: Automatically restart the container if something happens (reboot, USB problem), unless you have manually stopped the container. * --log-driver=local: By default, docker uses the json log driver which may fill up your harddrive, depending on how busy your station is. `local` log driver defaults to 100MB of saved logs, and automatically rotates them. * --network=host: Allows the container to talk to the internet, if you are sending the packets to an online service. * --device=: Allows the container to talk to the USB bus to access the RTL-SDR dongle. - * ./rtl_ais: Same command-line options as above. + * ./rtl_ais: Same command-line options as non-containerized. * View the startup messages and decoded packets with `docker logs --follow rtl-ais` + * Stop the container with `docker stop rtl-ais` Building the container: - * `git clone https://github.com/bklofas/rtl-ais.git` the repository, then from the folder `docker build -t rtl-ais .` - * Or, build the container without cloning the repository: `docker build https://github.com/bklofas/rtl-ais.git` + * Clone the repository with `git clone https://github.com/dgiardini/rtl-ais.git` , then from the folder `docker build -t rtl-ais .` + * Or, build the container without cloning the repository: `docker build https://github.com/dgiardini/rtl-ais.git` Other tips and tricks: * If you have the `-n` flag, view the decoded AIS packets in real-time with `docker logs --follow rtl-ais` - * If you are only sending packets to one internet service (such as marinetraffic.com), you can use the `-h` and `-P` options that they send you. + * If you are only sending packets to one internet service (such as marinetraffic.com), you can use the `-h` and `-P` options that they provide. + * You are encouraged to send your decoded packets to multiple online services. Check out AIS packet multiplexer [kplex](http://www.stripydog.com/kplex/), pre-built containers are available [here](https://ghcr.io/bklofas/kplex). Testing From 09ca36ce6b8120c0e3b5ad878b9ee6c826de8ae4 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Sat, 3 Jul 2021 14:09:39 -0300 Subject: [PATCH 107/117] Add exotic librtlsdr path for some RPI distros --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dcca06f..eeb4d23 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CFLAGS?=-O2 -g -Wall -W CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib -I./tcp_listener -LDFLAGS+=-lpthread -lm +LDFLAGS+=-lpthread -lm -L /usr/lib/arm-linux-gnueabihf/ ifeq ($(PREFIX),) PREFIX := /usr/local From 605c29c704ec1be7ee24c9d859e165ad5e8d8769 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Thu, 18 May 2023 13:35:06 -0300 Subject: [PATCH 108/117] Support for broadcast addresses --- aisdecoder/aisdecoder.c | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/aisdecoder/aisdecoder.c b/aisdecoder/aisdecoder.c index 5aeb9ab..5c78bc9 100644 --- a/aisdecoder/aisdecoder.c +++ b/aisdecoder/aisdecoder.c @@ -123,7 +123,10 @@ void nmea_sentence_received(const char *sentence, append_message(sentence); if (sentences == 1) { - if (send_nmea( sentence, length) == -1) abort(); + if (send_nmea( sentence, length) == -1){ + fprintf(stderr,"Error sending UDP packet with NMEA message: %s\n", strerror(errno)); + abort(); + } if (debug_nmea) fprintf(stderr, "%s", sentence); } else { if (buffer_count + length < MAX_BUFFER_LENGTH) { @@ -134,7 +137,10 @@ void nmea_sentence_received(const char *sentence, } if (sentences == sentencenum && buffer_count > 0) { - if (send_nmea( buffer, buffer_count) == -1) abort(); + if (send_nmea( buffer, buffer_count) == -1){ + fprintf(stderr,"Error sending UDP packet with NMEA message (buffer_count=%d):%s\n",buffer_count, strerror(errno)); + abort(); + } if (debug_nmea) fprintf(stderr, "%s", buffer); buffer_count=0; }; @@ -203,8 +209,27 @@ int free_ais_decoder(void) } + +/* Check if the host is broacast address. I suppose there are better options than this :-| */ +int isBroadcastAddress (const char *ipAddress) { + // Find the last dot in the IP address + const char *lastDot = strrchr(ipAddress, '.'); + if (lastDot != NULL) { + // Extract the last octet after the dot + const char *lastOctet = lastDot + 1; + // Check if the last octet is "255" + if (strcmp(lastOctet, "255") == 0) { + return 1; // Last digits are 255 + } + } + return 0; //Last digits are not 255 +} + + + int initSocket(const char *host, const char *portname) { struct addrinfo hints; + int enable_broadcast=1; memset(&hints, 0, sizeof(hints)); hints.ai_family=AF_UNSPEC; hints.ai_socktype=SOCK_DGRAM; @@ -212,7 +237,6 @@ int initSocket(const char *host, const char *portname) { #ifndef WIN32 hints.ai_flags=AI_ADDRCONFIG; #else - int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("WSAStartup failed: %d\n", iResult); @@ -236,6 +260,14 @@ int initSocket(const char *host, const char *portname) { #endif return 0; } + if(isBroadcastAddress(host)){ + fprintf(stderr, "Broadcast address detected. Setting SO_BROADCAST option to socket.\n"); + // Enable sending broadcast packets + if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &enable_broadcast, sizeof(enable_broadcast)) < 0) { + perror("Failed to set socket option SO_BROADCAST:"); + exit(1); + } + } fprintf(stderr,"AIS data will be sent to %s port %s\n",host,portname); return 1; } From 35f2011ca3f7077563f95bc239d450251ff9c221 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Wed, 24 May 2023 06:55:33 -0300 Subject: [PATCH 109/117] Try to detect if pkg-config fails, default to -l librtlsdr --- Makefile | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index eeb4d23..62a5165 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -CFLAGS?=-O2 -g -Wall -W -CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib -I./tcp_listener -LDFLAGS+=-lpthread -lm -L /usr/lib/arm-linux-gnueabihf/ +CFLAGS?=-O2 -g -Wall -Wimplicit-fallthrough=0 +CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib -I./tcp_listener +LDFLAGS+=-lpthread -lm -L /usr/lib/arm-linux-gnueabihf/ -L /usr/lib/i386-linux-gnu/ ifeq ($(PREFIX),) PREFIX := /usr/local @@ -9,7 +9,16 @@ UNAME := $(shell uname) ifeq ($(UNAME),Linux) #Conditional for Linux CFLAGS+= $(shell pkg-config --cflags librtlsdr) -LDFLAGS+=$(shell pkg-config --libs librtlsdr) +LD_LIBRTLSDR=$(shell pkg-config --libs librtlsdr) +#Ugly hack. Check if the output of pkg-config is long enough to be valid +LD_LIBRTLSDR_LENGTH := $(shell echo "$(LD_LIBRTLSDR)" | wc -c) +ifeq ($(shell test $(LD_LIBRTLSDR_LENGTH) -gt 13; echo $$?),0) +#The pkg-config output seem to be ok, let's use it + LDFLAGS+=$(shell pkg-config --libs librtlsdr) +else +#The pkg-config output seem to be too short, use the default lib name and default paths + LDFLAGS+=-lrtlsdr +endif else # @@ -68,3 +77,4 @@ install: install -d -m 755 $(DESTDIR)/$(PREFIX)/bin install -m 755 $(EXECUTABLE) "$(DESTDIR)/$(PREFIX)/bin/" + From 88d062f00c903ba51212478d99e2f72a3eeeb309 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Wed, 24 May 2023 07:18:40 -0300 Subject: [PATCH 110/117] Extra paths for the linker --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 62a5165..b532bf5 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CFLAGS?=-O2 -g -Wall -Wimplicit-fallthrough=0 CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib -I./tcp_listener -LDFLAGS+=-lpthread -lm -L /usr/lib/arm-linux-gnueabihf/ -L /usr/lib/i386-linux-gnu/ - +LD_EXTRA_PATHS= -L /usr/lib/arm-linux-gnueabihf/ -L /usr/lib/i386-linux-gnu/ -L /usr/lib/x86_64-linux-gnu/ +LDFLAGS+=-lpthread -lm $(LD_EXTRA_PATHS) ifeq ($(PREFIX),) PREFIX := /usr/local endif From 3bcc835cf4735b090adf55e2569c1d0359cad140 Mon Sep 17 00:00:00 2001 From: dgiardini Date: Sun, 4 Jun 2023 17:26:03 -0300 Subject: [PATCH 111/117] Keep all warinings on --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b532bf5..4119bc8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -CFLAGS?=-O2 -g -Wall -Wimplicit-fallthrough=0 +CFLAGS?=-O2 -g -Wall CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib -I./tcp_listener LD_EXTRA_PATHS= -L /usr/lib/arm-linux-gnueabihf/ -L /usr/lib/i386-linux-gnu/ -L /usr/lib/x86_64-linux-gnu/ LDFLAGS+=-lpthread -lm $(LD_EXTRA_PATHS) From 52933695d09d3b23e88cb4f33502dc3107b74686 Mon Sep 17 00:00:00 2001 From: Pavel Kalian Date: Fri, 12 Jan 2024 12:55:38 -0300 Subject: [PATCH 112/117] Add paths for Homebrew on Apple Silicon to the Makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4119bc8..bbfae8e 100644 --- a/Makefile +++ b/Makefile @@ -37,8 +37,8 @@ LIBUSB_LIB=/tmp/libusb/lib ifeq ($(UNAME),Darwin) #Conditional for OSX -CFLAGS+= -I/usr/local/include/ -I$(LIBUSB_INCLUDE) -I$(RTLSDR_INCLUDE) -LDFLAGS+= -L/usr/local/lib -L$(LIBUSB_LIB) -L$(RTLSDR_LIB) -lrtlsdr -lusb-1.0 +CFLAGS+= -I/usr/local/include/ -I/opt/homebrew/include -I$(LIBUSB_INCLUDE) -I$(RTLSDR_INCLUDE) +LDFLAGS+= -L/usr/local/lib -L/opt/homebrew/lib -L$(LIBUSB_LIB) -L$(RTLSDR_LIB) -lrtlsdr -lusb-1.0 else #Conditional for Windows CFLAGS+=-I $(LIBUSB_INCLUDE) -I $(RTLSDR_INCLUDE) From d1ab768879b4cdc574c08adc6332cafebe41c91d Mon Sep 17 00:00:00 2001 From: JTaveau Date: Wed, 17 Jul 2024 16:45:23 +0200 Subject: [PATCH 113/117] fix building error --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index bbfae8e..5d7d5fb 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CFLAGS?=-O2 -g -Wall CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib -I./tcp_listener -LD_EXTRA_PATHS= -L /usr/lib/arm-linux-gnueabihf/ -L /usr/lib/i386-linux-gnu/ -L /usr/lib/x86_64-linux-gnu/ +LD_EXTRA_PATHS= -L /usr/lib/arm-linux-gnueabihf -L /usr/lib/i386-linux-gnu -L /usr/lib/x86_64-linux-gnu LDFLAGS+=-lpthread -lm $(LD_EXTRA_PATHS) ifeq ($(PREFIX),) PREFIX := /usr/local @@ -74,7 +74,7 @@ clean: rm -f $(OBJECTS) $(EXECUTABLE) $(EXECUTABLE).exe install: - install -d -m 755 $(DESTDIR)/$(PREFIX)/bin - install -m 755 $(EXECUTABLE) "$(DESTDIR)/$(PREFIX)/bin/" + install -d -m 755 $(PREFIX)/bin + install -m 755 $(EXECUTABLE) "$(PREFIX)/bin/" From 631bb9ebc2051d7a1e5a465cb583996bdf72b1a9 Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Thu, 6 Mar 2025 21:32:02 -0800 Subject: [PATCH 114/117] Updated github actions for building new container --- .github/workflows/container.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index f8d5c4f..f0a6660 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -7,16 +7,19 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3.3.0 + with: + image: tonistiigi/binfmt:qemu-v8.1.5 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Cache Docker layers - uses: actions/cache@v2 + id: cache-docker + uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} @@ -25,19 +28,20 @@ jobs: - name: Log in to GHCR if: github.event_name != 'pull_request' - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} + password: ${{ secrets.RTLAIS_GITHUB_TOKEN }} - name: Build and push to ghcr - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile - platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7 + platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v6,linux/arm/v7 push: ${{ github.event_name != 'pull_request' }} + outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=AIS recevier using RTL-SDR dongle tags: | ghcr.io/bklofas/rtl-ais:latest From 66eca38a79110a41e5e0469577237bcfc77500af Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Thu, 6 Mar 2025 22:14:50 -0800 Subject: [PATCH 115/117] Updated debian strech -> bookworm --- Dockerfile | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index dc96d47..507f40e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,5 @@ -# Can't use buster-slim because it needs librtlsdr-dev 0.5.3 -# See https://github.com/dgiardini/rtl-ais/issues/32 -FROM debian:stretch-slim + +FROM debian:bookworm-slim LABEL "name"="rtl-ais" \ "description"="AIS ship decoding using an RTL-SDR dongle" \ "author"="Bryan Klofas KF6ZEO" @@ -11,17 +10,17 @@ WORKDIR $APP COPY . $APP -RUN apt-get update && apt-get install -y \ +RUN apt-get -y update && apt -y upgrade && apt-get -y install --no-install-recommends \ rtl-sdr \ librtlsdr-dev \ libusb-1.0-0-dev \ make \ build-essential \ pkg-config \ - && make \ - && apt-get remove -y make build-essential pkg-config \ - && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* + +RUN make && \ + make install -CMD $APP/rtl_ais -n +CMD ["/usr/src/app/rtl_ais", "-n"] From 5f63c41f1b6bc93c8dfc3a6c4432774a15522c4f Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Fri, 7 Mar 2025 11:15:58 -0800 Subject: [PATCH 116/117] Updated Dockerfile with build/run containers --- Dockerfile | 57 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/Dockerfile b/Dockerfile index 507f40e..ce98370 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,26 +1,47 @@ +# ------------------- +# The build container +# ------------------- +FROM debian:bookworm-slim AS build -FROM debian:bookworm-slim -LABEL "name"="rtl-ais" \ - "description"="AIS ship decoding using an RTL-SDR dongle" \ - "author"="Bryan Klofas KF6ZEO" +WORKDIR /usr/src/app + +COPY . /usr/src/app + +# Upgrade bookworm and install dependencies +RUN apt-get -y update && apt -y upgrade && apt-get -y install --no-install-recommends \ + rtl-sdr \ + librtlsdr-dev \ + libusb-1.0-0-dev \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +# Build rtl_ais +RUN make && \ + make install -ENV APP=/usr/src/app -WORKDIR $APP +# ------------------------- +# The application container +# ------------------------- +FROM debian:bookworm-slim -COPY . $APP +LABEL org.opencontainers.image.title="rtl-ais" +LABEL org.opencontainers.image.description="AIS decoding using RTL-SDR dongle" +LABEL org.opencontainers.image.authors="Bryan Klofas KF6ZEO bklofas@gmail" +LABEL org.opencontainers.image.source="https://github.com/bklofas/rtl-ais" +# Upgrade bookworm and install dependencies RUN apt-get -y update && apt -y upgrade && apt-get -y install --no-install-recommends \ - rtl-sdr \ - librtlsdr-dev \ - libusb-1.0-0-dev \ - make \ - build-essential \ - pkg-config \ - && rm -rf /var/lib/apt/lists/* - -RUN make && \ - make install + tini \ + rtl-sdr \ + librtlsdr-dev \ + libusb-1.0-0-dev &&\ + rm -rf /var/lib/apt/lists/* + +COPY --from=build /usr/src/app /app + +# Use tini as init. +ENTRYPOINT ["/usr/bin/tini", "--"] -CMD ["/usr/src/app/rtl_ais", "-n"] +CMD ["/app/rtl_ais", "-n"] From 60a6992bffb6af5be3a615e42e8d9dda58116ec3 Mon Sep 17 00:00:00 2001 From: Bryan Klofas Date: Fri, 7 Mar 2025 12:27:52 -0800 Subject: [PATCH 117/117] Changed directory for rtl_ais so previous arguments work --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index ce98370..eafd98d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,10 +38,10 @@ RUN apt-get -y update && apt -y upgrade && apt-get -y install --no-install-recom libusb-1.0-0-dev &&\ rm -rf /var/lib/apt/lists/* -COPY --from=build /usr/src/app /app +COPY --from=build /usr/src/app / # Use tini as init. ENTRYPOINT ["/usr/bin/tini", "--"] -CMD ["/app/rtl_ais", "-n"] +CMD ["/rtl_ais", "-n"]