├── .gitignore ├── test.wav ├── output.h ├── scan.sh ├── mqttout.c ├── CMakeLists.txt ├── fileout.c ├── soundfile.c ├── msk.c ├── alsa.c ├── netout.c ├── acarsdec.h ├── sdrplay.c ├── acars.c ├── soapy.c ├── README.md ├── label.c ├── rtl.c ├── air.c ├── acarsdec.c ├── cJSON.h ├── syndrom.h └── output.c /.gitignore: -------------------------------------------------------------------------------- 1 | acarsdec 2 | acarsserv 3 | .*.swp 4 | *.o 5 | -------------------------------------------------------------------------------- /test.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TLeconte/acarsdec/HEAD/test.wav -------------------------------------------------------------------------------- /output.h: -------------------------------------------------------------------------------- 1 | extern int Netoutinit(char *Rawaddr); 2 | extern void Netoutpp(acarsmsg_t * msg); 3 | extern void Netoutsv(acarsmsg_t * msg, char * idstation, int chn, struct timeval tv); 4 | extern void Netoutjson(char *jsonbuf); 5 | 6 | extern FILE *Fileoutinit(char* logfilename); 7 | extern FILE *Fileoutrotate(FILE *fd); 8 | 9 | -------------------------------------------------------------------------------- /scan.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | let fm=130 4 | while [ "$fm" -lt 137 ] ; do 5 | let f0=0; let f1=125 ; let f2=250 ; let f3=375 ; let f4=500 ; let f5=625 ; let f6=750 ; let f7=875 6 | echo $fm 7 | while [ "$f0" -lt 125 ] ; do 8 | echo $f0 9 | ./acarsdec -l scanlog -s $fm.$f0 $fm.$f1 $fm.$f2 $fm.$f3 $fm.$f4 $fm.$f5 $fm.$f6 $fm.$f7 & 10 | sleep 300 11 | killall acarsdec 12 | let f0=f0+25;let f1=f1+25;let f2=f2+25;let f3=f3+25; f4=f4+25;let f5=f5+25;let f6=f6+25;let f7=f7+25; 13 | done 14 | let fm=fm+1; 15 | done 16 | grep -e "(F:" scanlog | cut -c 8-14 | sort | uniq -c 17 | -------------------------------------------------------------------------------- /mqttout.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "MQTTAsync.h" 5 | 6 | 7 | static MQTTAsync client; 8 | static MQTTAsync_message pubmsg = MQTTAsync_message_initializer; 9 | static char *msgtopic; 10 | 11 | int MQTTinit(char **urls, char * client_id, char *topic, char *user,char *passwd) 12 | { 13 | int rc; 14 | MQTTAsync_createOptions create_opts = MQTTAsync_createOptions_initializer; 15 | MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; 16 | 17 | create_opts.maxBufferedMessages=200; 18 | create_opts.sendWhileDisconnected=1; 19 | create_opts.allowDisconnectedSendAtAnyTime=1; 20 | create_opts.deleteOldestMessages=1; 21 | 22 | MQTTAsync_createWithOptions(&client, urls[0], client_id, MQTTCLIENT_PERSISTENCE_NONE, NULL, &create_opts); 23 | 24 | conn_opts.keepAliveInterval = 60; 25 | conn_opts.cleansession = 1; 26 | conn_opts.automaticReconnect = 1; 27 | conn_opts.password = passwd; 28 | conn_opts.username = user; 29 | if(urls[1]) conn_opts.serverURIs = urls; 30 | 31 | if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) { 32 | return(rc); 33 | } 34 | 35 | if(topic == NULL) { 36 | msgtopic=malloc(strlen(client_id)+strlen("acarsdec")+2); 37 | if(msgtopic==NULL) 38 | return -1; 39 | sprintf(msgtopic,"acarsdec/%s",client_id); 40 | } else 41 | msgtopic=topic; 42 | 43 | return rc; 44 | } 45 | 46 | 47 | int MQTTsend(char *msgtxt) 48 | { 49 | 50 | pubmsg.payload = msgtxt; 51 | pubmsg.payloadlen = strlen(msgtxt); 52 | pubmsg.qos = 0; 53 | pubmsg.retained = 0; 54 | 55 | return MQTTAsync_sendMessage(client, msgtopic, &pubmsg,NULL); 56 | 57 | } 58 | 59 | void MQTTend() 60 | { 61 | MQTTAsync_disconnect(client,NULL); 62 | MQTTAsync_destroy(&client); 63 | } 64 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.2) 2 | project (acarsdec C) 3 | 4 | add_compile_options(-Ofast -march=native) 5 | 6 | add_executable(acarsdec acars.c acarsdec.c cJSON.c label.c msk.c output.c netout.c fileout.c ) 7 | 8 | find_package(PkgConfig) 9 | if(PKG_CONFIG_FOUND) 10 | pkg_check_modules(LIBACARS libacars-2>=2.0.0) 11 | if(LIBACARS_FOUND) 12 | message ( STATUS "Using libacars") 13 | add_definitions(-DHAVE_LIBACARS ) 14 | target_link_libraries(acarsdec ${LIBACARS_LIBRARIES}) 15 | target_include_directories(acarsdec PUBLIC ${LIBACARS_INCLUDE_DIRS}) 16 | link_directories(${LIBACARS_LIBRARY_DIRS}) 17 | else() 18 | message ( STATUS "Not using libacars") 19 | endif() 20 | endif() 21 | 22 | find_library(MQTT paho-mqtt3a) 23 | if(MQTT) 24 | message ( STATUS "Using MQTT") 25 | add_definitions(-DWITH_MQTT ) 26 | target_sources( acarsdec PRIVATE mqttout.c) 27 | target_link_libraries(acarsdec ${MQTT}) 28 | else() 29 | message ( STATUS "Not using MQTT") 30 | endif() 31 | 32 | find_library(LIBSNDFILE sndfile) 33 | if(LIBSNDFILE) 34 | message ( STATUS "Using libsnd") 35 | add_definitions(-DWITH_SNDFILE ) 36 | target_sources( acarsdec PRIVATE soundfile.c) 37 | target_link_libraries(acarsdec ${LIBSNDFILE}) 38 | else() 39 | message ( STATUS "Not using libsndfile") 40 | endif() 41 | 42 | option(rtl "Compiling for rtl sdr" ) 43 | if(rtl) 44 | find_library(LIBRTL rtlsdr) 45 | if(NOT LIBRTL) 46 | message (FATAL_ERROR "librtlsdr path not found") 47 | endif() 48 | add_definitions(-DWITH_RTL ) 49 | target_sources( acarsdec PRIVATE rtl.c) 50 | target_link_libraries( acarsdec ${LIBRTL}) 51 | endif() 52 | 53 | option(airspy "Compiling for airspy sdr" ) 54 | if(airspy) 55 | find_library(LIBAIR airspy) 56 | if(NOT LIBAIR) 57 | message ( FATAL_ERROR "libairspy path not found") 58 | endif() 59 | add_definitions(-DWITH_AIR ) 60 | target_sources( acarsdec PRIVATE air.c) 61 | target_link_libraries( acarsdec ${LIBAIR}) 62 | endif() 63 | 64 | option(sdrplay "Compiling for sdrplay sdr" ) 65 | if(sdrplay) 66 | find_library(LIBPLAY mirsdrapi-rsp) 67 | if(NOT LIBPLAY) 68 | message ( FATAL_ERROR "libmirsdrapi-rsp path not found") 69 | endif() 70 | add_definitions(-DWITH_SDRPLAY ) 71 | target_sources( acarsdec PRIVATE sdrplay.c) 72 | target_link_libraries( acarsdec ${LIBPLAY}) 73 | endif() 74 | 75 | option(soapy "Compiling for soapy sdr" ) 76 | if(soapy) 77 | find_library(LIBSOAPY SoapySDR) 78 | if(NOT LIBSOAPY) 79 | message ( FATAL_ERROR "libSoapySDR path not found") 80 | endif() 81 | add_definitions(-DWITH_SOAPY ) 82 | target_sources( acarsdec PRIVATE soapy.c) 83 | target_link_libraries( acarsdec ${LIBSOAPY}) 84 | endif() 85 | 86 | if(NOT rtl AND NOT airspy AND NOT sdrplay AND NOT soapy) 87 | message ("No sdr option set ! are you sure ?") 88 | endif() 89 | 90 | target_link_libraries( acarsdec pthread m ) 91 | 92 | install(TARGETS acarsdec 93 | RUNTIME DESTINATION bin 94 | ) 95 | -------------------------------------------------------------------------------- /fileout.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | extern int hourly,daily; 12 | 13 | static char *filename_prefix = NULL; 14 | static char *extension = NULL; 15 | static size_t prefix_len; 16 | static struct tm current_tm; 17 | 18 | static FILE *open_outfile() { 19 | char *filename = NULL; 20 | char *fmt = NULL; 21 | size_t tlen = 0; 22 | FILE *fd; 23 | 24 | if(hourly || daily) { 25 | time_t t = time(NULL); 26 | gmtime_r(&t, ¤t_tm); 27 | char suffix[16]; 28 | if(hourly) { 29 | fmt = "_%Y%m%d_%H"; 30 | } else { // daily 31 | fmt = "_%Y%m%d"; 32 | } 33 | tlen = strftime(suffix, sizeof(suffix), fmt, ¤t_tm); 34 | if(tlen == 0) { 35 | fprintf(stderr, "*open_outfile(): strfime returned 0\n"); 36 | return NULL; 37 | } 38 | filename = calloc(prefix_len + tlen + 2, sizeof(char)); 39 | if(filename == NULL) { 40 | fprintf(stderr, "open_outfile(): failed to allocate memory\n"); 41 | return NULL; 42 | } 43 | sprintf(filename, "%s%s%s", filename_prefix, suffix, extension); 44 | } else { 45 | filename = strdup(filename_prefix); 46 | } 47 | 48 | if((fd = fopen(filename, "a+")) == NULL) { 49 | fprintf(stderr, "Could not open output file %s: %s\n", filename, strerror(errno)); 50 | free(filename); 51 | return NULL; 52 | } 53 | free(filename); 54 | return fd; 55 | } 56 | 57 | FILE* Fileoutinit(char* logfilename) 58 | { 59 | FILE *fd; 60 | 61 | filename_prefix = logfilename; 62 | prefix_len = strlen(filename_prefix); 63 | if(hourly || daily) { 64 | char *basename = strrchr(filename_prefix, '/'); 65 | if(basename != NULL) { 66 | basename++; 67 | } else { 68 | basename = filename_prefix; 69 | } 70 | char *ext = strrchr(filename_prefix, '.'); 71 | if(ext != NULL && (ext <= basename || ext[1] == '\0')) { 72 | ext = NULL; 73 | } 74 | if(ext) { 75 | extension = strdup(ext); 76 | *ext = '\0'; 77 | } else { 78 | extension = strdup(""); 79 | } 80 | } 81 | if((fd=open_outfile()) == NULL) 82 | return NULL; 83 | 84 | return fd; 85 | } 86 | 87 | FILE* Fileoutrotate(FILE *fd) 88 | { 89 | struct tm new_tm; 90 | time_t t = time(NULL); 91 | gmtime_r(&t, &new_tm); 92 | if((hourly && new_tm.tm_hour != current_tm.tm_hour) || 93 | (daily && new_tm.tm_mday != current_tm.tm_mday)) { 94 | fclose(fd); 95 | return open_outfile(); 96 | } 97 | return fd; 98 | } 99 | 100 | -------------------------------------------------------------------------------- /soundfile.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Thierry Leconte (f4dwv) 3 | * 4 | * 5 | * This code is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU Library General Public License version 2 7 | * published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | * 18 | */ 19 | #ifdef WITH_SNDFILE 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "acarsdec.h" 26 | 27 | #define MAXNBFRAMES 4096 28 | static SNDFILE *insnd; 29 | 30 | int initSoundfile(char **argv, int optind) 31 | { 32 | SF_INFO infsnd; 33 | int n; 34 | 35 | infsnd.format = 0; 36 | insnd = sf_open(argv[optind], SFM_READ, &infsnd); 37 | if (insnd == NULL) { 38 | fprintf(stderr, "could not open %s\n", argv[optind]); 39 | return (1); 40 | } 41 | nbch = infsnd.channels; 42 | if (nbch > MAXNBCHANNELS) { 43 | fprintf(stderr, "Too much input channels : %d\n", nbch); 44 | return (1); 45 | } 46 | if(infsnd.samplerate!=INTRATE) { 47 | fprintf(stderr, "unsupported sample rate : %d (must be %d)\n",infsnd.samplerate,INTRATE); 48 | return (1); 49 | } 50 | 51 | for (n = 0; n < nbch; n++) { 52 | channel[n].dm_buffer=malloc(sizeof(float)*MAXNBFRAMES); 53 | } 54 | 55 | return (0); 56 | } 57 | 58 | int runSoundfileSample(void) 59 | { 60 | int nbi, n, i; 61 | sample_t sndbuff[MAXNBFRAMES * MAXNBCHANNELS]; 62 | 63 | do { 64 | 65 | nbi = sf_read_float(insnd, sndbuff, MAXNBFRAMES * nbch); 66 | 67 | if (nbi == 0) { 68 | return -1; 69 | } 70 | 71 | for (n = 0; n < nbch; n++) { 72 | int len = nbi / nbch; 73 | for (i = 0; i < len; i++) 74 | channel[n].dm_buffer[i]=sndbuff[n + i * nbch]; 75 | 76 | demodMSK(&(channel[n]),len); 77 | } 78 | 79 | } while (1); 80 | return 0; 81 | } 82 | 83 | #ifdef DEBUG 84 | static SNDFILE *outsnd; 85 | void initSndWrite(void) 86 | { 87 | SF_INFO infsnd; 88 | 89 | infsnd.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; 90 | infsnd.samplerate = INTRATE; 91 | infsnd.channels = 1; 92 | outsnd = sf_open("data.wav", SFM_WRITE, &infsnd); 93 | if (outsnd == NULL) { 94 | fprintf(stderr, "could not open data\n "); 95 | return; 96 | } 97 | 98 | } 99 | 100 | void SndWrite(float *in) 101 | { 102 | sf_write_float(outsnd, in, 1); 103 | } 104 | 105 | void SndWriteClose(void) 106 | { 107 | sf_close(outsnd); 108 | } 109 | #endif 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /msk.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Thierry Leconte 3 | * 4 | * 5 | * This code is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU Library General Public License version 2 7 | * published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Library General Public License for more details. 13 | * 14 | */ 15 | #define _GNU_SOURCE 16 | #include 17 | #include 18 | #include 19 | #include "acarsdec.h" 20 | 21 | pthread_mutex_t chmtx; 22 | pthread_cond_t chprcd,chcscd; 23 | int chmsk,tmsk; 24 | 25 | #define FLEN ((INTRATE/1200)+1) 26 | #define MFLTOVER 12 27 | #define FLENO (FLEN*MFLTOVER+1) 28 | static float h[FLENO]; 29 | 30 | int initMsk(channel_t * ch) 31 | { 32 | int i; 33 | 34 | ch->MskPhi = ch->MskClk = 0; 35 | ch->MskS = 0; 36 | 37 | ch->MskDf = 0; 38 | 39 | ch->idx = 0; 40 | ch->inb = calloc(FLEN, sizeof(float complex)); 41 | if(ch->inb == NULL) 42 | return -1; 43 | 44 | if(ch->chn==0) 45 | for (i = 0; i < FLENO; i++) { 46 | h[i] = cosf(2.0*M_PI*600.0/INTRATE/MFLTOVER*(i-(FLENO-1)/2)); 47 | if(h[i]<0) h[i]=0; 48 | } 49 | 50 | return 0; 51 | } 52 | 53 | static inline void putbit(float v, channel_t * ch) 54 | { 55 | ch->outbits >>= 1; 56 | if (v > 0) { 57 | ch->outbits |= 0x80; 58 | } 59 | 60 | ch->nbits--; 61 | if (ch->nbits <= 0) 62 | decodeAcars(ch); 63 | } 64 | 65 | const float PLLG=38e-4; 66 | const float PLLC=0.52; 67 | void demodMSK(channel_t *ch,int len) 68 | { 69 | /* MSK demod */ 70 | int n; 71 | int idx=ch->idx; 72 | double p=ch->MskPhi; 73 | 74 | for(n=0;nMskDf; 82 | p+=s; 83 | if (p >= 2.0*M_PI) p -= 2.0*M_PI; 84 | 85 | /* mixer */ 86 | in = ch->dm_buffer[n]; 87 | #ifdef DEBUG 88 | if(ch->chn==1) SndWrite(&in); 89 | #endif 90 | ch->inb[idx] = in * cexp(-p*I); 91 | idx=(idx+1)%FLEN; 92 | 93 | 94 | /* bit clock */ 95 | ch->MskClk+=s; 96 | if (ch->MskClk >=3*M_PI/2.0-s/2) { 97 | double dphi; 98 | float vo,lvl; 99 | 100 | ch->MskClk -= 3*M_PI/2.0; 101 | 102 | /* matched filter */ 103 | o=MFLTOVER*(ch->MskClk/s+0.5); 104 | if(o>MFLTOVER) o=MFLTOVER; 105 | for (v = 0, j = 0; j < FLEN; j++,o+=MFLTOVER) { 106 | v += h[o]*ch->inb[(j+idx)%FLEN]; 107 | } 108 | 109 | /* normalize */ 110 | lvl=cabsf(v); 111 | v/=lvl+1e-8; 112 | ch->MskLvlSum += lvl * lvl / 4; 113 | ch->MskBitCount++; 114 | 115 | if(ch->MskS&1) { 116 | vo=cimagf(v); 117 | if(vo>=0) dphi=-crealf(v); else dphi=crealf(v); 118 | } else { 119 | vo=crealf(v); 120 | if(vo>=0) dphi=cimagf(v); else dphi=-cimagf(v); 121 | } 122 | if(ch->MskS&2) { 123 | putbit(-vo, ch); 124 | } else { 125 | putbit(vo, ch); 126 | } 127 | ch->MskS++; 128 | 129 | /* PLL filter */ 130 | ch->MskDf=PLLC*ch->MskDf+(1.0-PLLC)*PLLG*dphi; 131 | } 132 | } 133 | 134 | ch->idx=idx; 135 | ch->MskPhi=p; 136 | 137 | } 138 | 139 | -------------------------------------------------------------------------------- /alsa.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Thierry Leconte 3 | * 4 | * 5 | * This code is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU Library General Public License version 2 7 | * published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | * 18 | */ 19 | #ifdef WITH_ALSA 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "acarsdec.h" 26 | 27 | #define MAXNBFRAMES 4096 28 | 29 | static snd_pcm_t *capture_handle; 30 | int initAlsa(char **argv, int optind) 31 | { 32 | snd_pcm_hw_params_t *hw_params; 33 | int err, n; 34 | unsigned int Fs; 35 | 36 | if ((err = snd_pcm_open(&capture_handle, argv[optind], 37 | SND_PCM_STREAM_CAPTURE, 0)) < 0) { 38 | fprintf(stderr, "Alsa cannot open audio device %s (%s)\n", 39 | argv[optind], snd_strerror(err)); 40 | return 1; 41 | } 42 | 43 | if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { 44 | fprintf(stderr, 45 | "Alsa cannot allocate hardware parameter structure (%s)\n", 46 | snd_strerror(err)); 47 | return 1; 48 | } 49 | 50 | if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) { 51 | fprintf(stderr, 52 | "Alsa cannot initialize hardware parameter structure (%s)\n", 53 | snd_strerror(err)); 54 | return 1; 55 | } 56 | 57 | if ((err = 58 | snd_pcm_hw_params_set_access(capture_handle, hw_params, 59 | SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { 60 | fprintf(stderr, "Alsa cannot set access type (%s)\n", 61 | snd_strerror(err)); 62 | return 1; 63 | } 64 | 65 | if ((err = 66 | snd_pcm_hw_params_set_format(capture_handle, hw_params, SND_PCM_FORMAT_FLOAT)) < 0) { 67 | fprintf(stderr, "Alsa cannot set sample format (%s)\n", 68 | snd_strerror(err)); 69 | return 1; 70 | } 71 | 72 | snd_pcm_hw_params_set_rate_resample(capture_handle, hw_params, 0); 73 | Fs = INTRATE; 74 | n = 1; 75 | if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &Fs, &n)) < 0) { 76 | fprintf(stderr, "Alsa cannot set sample rate (%s)\n", 77 | snd_strerror(err)); 78 | return 1; 79 | } 80 | if (snd_pcm_hw_params_get_channels(hw_params, &nbch) != 0) { 81 | fprintf(stderr, "Alsa cannot get number of channels\n"); 82 | return 1; 83 | } 84 | if (nbch > 1) { 85 | fprintf(stderr, "Alsa too much channels : %d\n", nbch); 86 | return 1; 87 | 88 | } 89 | if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) { 90 | fprintf(stderr, "Alsa cannot set parameters (%s)\n", 91 | snd_strerror(err)); 92 | return 1; 93 | } 94 | snd_pcm_hw_params_free(hw_params); 95 | 96 | if ((err = snd_pcm_prepare(capture_handle)) < 0) { 97 | fprintf(stderr, 98 | "Alsa cannot prepare audio interface for use (%s)\n", 99 | snd_strerror(err)); 100 | return 1; 101 | } 102 | 103 | channel[0].chn = 0; 104 | channel[0].dm_buffer=malloc(MAXNBFRAMES*sizeof(float)); 105 | 106 | return (0); 107 | } 108 | 109 | int runAlsaSample(void) 110 | { 111 | int r, n, i; 112 | 113 | do { 114 | r = snd_pcm_readi(capture_handle, channel[0].dm_buffer,MAXNBFRAMES); 115 | if (r <= 0) { 116 | fprintf(stderr, 117 | "Alsa cannot read from interface (%s)\n", 118 | snd_strerror(r)); 119 | return -1; 120 | } 121 | 122 | demodMSK(&(channel[0]),r); 123 | 124 | 125 | } while (1); 126 | return 0; 127 | } 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /netout.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "acarsdec.h" 13 | 14 | static int sockfd = -1; 15 | static char *netOutputRawaddr = NULL; 16 | 17 | 18 | int Netoutinit(char *Rawaddr) 19 | { 20 | char *addr; 21 | char *port; 22 | struct addrinfo hints, *servinfo, *p; 23 | int rv; 24 | 25 | netOutputRawaddr = Rawaddr; 26 | 27 | memset(&hints, 0, sizeof hints); 28 | if (Rawaddr[0] == '[') { 29 | hints.ai_family = AF_INET6; 30 | addr = Rawaddr + 1; 31 | port = strstr(addr, "]"); 32 | if (port == NULL) { 33 | fprintf(stderr, "Invalid IPV6 address\n"); 34 | return -1; 35 | } 36 | *port = 0; 37 | port++; 38 | if (*port != ':') 39 | port = "5555"; 40 | else 41 | port++; 42 | } else { 43 | hints.ai_family = AF_UNSPEC; 44 | addr = Rawaddr; 45 | port = strstr(addr, ":"); 46 | if (port == NULL) 47 | port = "5555"; 48 | else { 49 | *port = 0; 50 | port++; 51 | } 52 | } 53 | 54 | hints.ai_socktype = SOCK_DGRAM; 55 | 56 | if ((rv = getaddrinfo(addr, port, &hints, &servinfo)) != 0) { 57 | fprintf(stderr, "Invalid/unknown address %s\n", addr); 58 | return -1; 59 | } 60 | 61 | for (p = servinfo; p != NULL; p = p->ai_next) { 62 | if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { 63 | continue; 64 | } 65 | 66 | if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { 67 | close(sockfd); 68 | continue; 69 | } 70 | break; 71 | } 72 | if (p == NULL) { 73 | fprintf(stderr, "failed to connect\n"); 74 | return -1; 75 | } 76 | 77 | freeaddrinfo(servinfo); 78 | 79 | return 0; 80 | } 81 | 82 | static int Netwrite(const void *buf, size_t count) { 83 | if (!netOutputRawaddr) { 84 | return -1; 85 | } 86 | 87 | int res; 88 | res = write(sockfd, buf, count); 89 | if (res == -1) { 90 | perror("Netwrite"); 91 | close(sockfd); 92 | // retry the write if the reconnect succeeds 93 | if (Netoutinit(netOutputRawaddr) == 0) { 94 | res = write(sockfd, buf, count); 95 | } 96 | } 97 | return res; 98 | } 99 | 100 | 101 | void Netoutpp(acarsmsg_t * msg) 102 | { 103 | char pkt[3600]; // max. 16 blocks * 220 characters + extra space for msg prefix 104 | char *pstr; 105 | int res; 106 | 107 | char *txt = strdup(msg->txt); 108 | for (pstr = txt; *pstr != 0; pstr++) 109 | if (*pstr == '\n' || *pstr == '\r') 110 | *pstr = ' '; 111 | 112 | snprintf(pkt, sizeof(pkt), "AC%1c %7s %1c %2s %1c %4s %6s %s", 113 | msg->mode, msg->addr, msg->ack, msg->label, msg->bid ? msg->bid : '.', msg->no, 114 | msg->fid, txt); 115 | 116 | if (netOutputRawaddr) { 117 | res=Netwrite(pkt, strlen(pkt)); 118 | } 119 | free(txt); 120 | } 121 | 122 | void Netoutsv(acarsmsg_t * msg, char *idstation, int chn, struct timeval tv) 123 | { 124 | char pkt[3600]; // max. 16 blocks * 220 characters + extra space for msg prefix 125 | struct tm tmp; 126 | int res; 127 | 128 | gmtime_r(&(tv.tv_sec), &tmp); 129 | 130 | snprintf(pkt, sizeof(pkt), 131 | "%8s %1d %02d/%02d/%04d %02d:%02d:%02d %1d %03d %1c %7s %1c %2s %1c %4s %6s %s", 132 | idstation, chn + 1, tmp.tm_mday, tmp.tm_mon + 1, 133 | tmp.tm_year + 1900, tmp.tm_hour, tmp.tm_min, tmp.tm_sec, 134 | msg->err, (int)(msg->lvl), msg->mode, msg->addr, msg->ack, msg->label, 135 | msg->bid ? msg->bid : '.', msg->no, msg->fid, msg->txt); 136 | 137 | if (netOutputRawaddr) { 138 | res=Netwrite(pkt, strlen(pkt)); 139 | } 140 | } 141 | 142 | void Netoutjson(char *jsonbuf) 143 | { 144 | char pkt[3600]; 145 | int res; 146 | 147 | snprintf(pkt, sizeof(pkt), "%s\n", jsonbuf); 148 | if (netOutputRawaddr) { 149 | res=Netwrite(pkt, strlen(pkt)); 150 | } 151 | } 152 | 153 | 154 | -------------------------------------------------------------------------------- /acarsdec.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Thierry Leconte 3 | * 4 | * 5 | * This code is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU Library General Public License version 2 7 | * published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | * 18 | */ 19 | #include 20 | #include 21 | #include 22 | #include 23 | #ifdef HAVE_LIBACARS 24 | #include 25 | #include 26 | #endif 27 | 28 | #define ACARSDEC_VERSION "3.7" 29 | 30 | #define MAXNBCHANNELS 16 31 | #define INTRATE 12500 32 | 33 | #define NETLOG_NONE 0 34 | #define NETLOG_PLANEPLOTTER 1 35 | #define NETLOG_NATIVE 2 36 | #define NETLOG_JSON 3 37 | #define NETLOG_MQTT 4 38 | 39 | #define OUTTYPE_NONE 0 40 | #define OUTTYPE_ONELINE 1 41 | #define OUTTYPE_STD 2 42 | #define OUTTYPE_MONITOR 3 43 | #define OUTTYPE_JSON 4 44 | #define OUTTYPE_ROUTEJSON 5 45 | 46 | typedef float sample_t; 47 | 48 | typedef struct mskblk_s { 49 | struct mskblk_s *prev; 50 | int chn; 51 | struct timeval tv; 52 | int len; 53 | int err; 54 | float lvl; 55 | char txt[250]; 56 | unsigned char crc[2]; 57 | } msgblk_t; 58 | 59 | typedef struct { 60 | int chn; 61 | 62 | #if defined(WITH_RTL) || defined(WITH_AIR) 63 | int Fr; 64 | float complex *wf; 65 | #endif 66 | #if defined(WITH_AIR) 67 | float complex D; 68 | #endif 69 | #if defined(WITH_SDRPLAY) || defined(WITH_SOAPY) 70 | float Fr; 71 | float complex *oscillator; 72 | float complex D; 73 | int counter; 74 | #endif 75 | 76 | float *dm_buffer; 77 | double MskPhi; 78 | double MskDf; 79 | float MskClk; 80 | double MskLvlSum; 81 | int MskBitCount; 82 | unsigned int MskS,idx; 83 | float complex *inb; 84 | 85 | unsigned char outbits; 86 | int nbits; 87 | 88 | enum { WSYN, SYN2, SOH1, TXT, CRC1,CRC2, END } Acarsstate; 89 | msgblk_t *blk; 90 | 91 | pthread_t th; 92 | } channel_t; 93 | 94 | typedef struct { 95 | char da[5]; 96 | char sa[5]; 97 | char eta[5]; 98 | char gout[5]; 99 | char gin[5]; 100 | char woff[5]; 101 | char won[5]; 102 | } oooi_t; 103 | 104 | typedef struct { 105 | char mode; 106 | char addr[8]; 107 | char ack; 108 | char label[3]; 109 | char bid; 110 | char no[5]; 111 | char fid[7]; 112 | char sublabel[3]; 113 | char mfi[3]; 114 | char bs, be; 115 | char *txt; 116 | int err; 117 | float lvl; 118 | #ifdef HAVE_LIBACARS 119 | char msn[4]; 120 | char msn_seq; 121 | la_proto_node *decoded_tree; 122 | la_reasm_status reasm_status; 123 | #endif 124 | } acarsmsg_t; 125 | 126 | extern channel_t channel[MAXNBCHANNELS]; 127 | extern unsigned int nbch; 128 | extern unsigned long wrktot; 129 | extern unsigned long wrkmask; 130 | extern pthread_mutex_t datamtx; 131 | extern pthread_cond_t datawcd; 132 | 133 | extern int signalExit; 134 | 135 | extern int inpmode; 136 | extern int verbose; 137 | extern int outtype; 138 | extern int netout; 139 | extern int airflt; 140 | extern int emptymsg; 141 | extern int mdly; 142 | extern int hourly, daily; 143 | 144 | extern int ppm; 145 | extern int lnaState; 146 | extern int GRdB; 147 | extern int initOutput(char*,char *); 148 | 149 | #ifdef HAVE_LIBACARS 150 | extern int skip_reassembly; 151 | #endif 152 | #ifdef WITH_ALSA 153 | extern int initAlsa(char **argv,int optind); 154 | extern int runAlsaSample(void); 155 | #endif 156 | #ifdef WITH_SNDFILE 157 | extern int initSoundfile(char **argv,int optind); 158 | extern int runSoundfileSample(void); 159 | #endif 160 | #ifdef WITH_RTL 161 | extern int initRtl(char **argv,int optind); 162 | extern int runRtlSample(void); 163 | extern int runRtlCancel(void); 164 | extern int runRtlClose(void); 165 | extern int rtlMult; 166 | #endif 167 | #ifdef WITH_AIR 168 | extern int initAirspy(char **argv,int optind); 169 | extern int runAirspySample(void); 170 | #endif 171 | #ifdef WITH_SOAPY 172 | extern int initSoapy(char **argv,int optind); 173 | extern int soapySetAntenna(const char *antenna); 174 | extern int runSoapySample(void); 175 | extern int runSoapyClose(void); 176 | extern int rateMult; 177 | extern int freq; 178 | extern double gain; 179 | #else 180 | extern int gain; 181 | #endif 182 | #ifdef WITH_MQTT 183 | extern int MQTTinit(char **urls, char * client_id, char *topic, char *user,char *passwd); 184 | extern int MQTTsend(char *msgtxt); 185 | extern void MQTTend(); 186 | #endif 187 | 188 | extern int initRaw(char **argv,int optind); 189 | extern int runRawSample(void); 190 | extern int initMsk(channel_t *); 191 | extern void demodMSK(channel_t *ch,int len); 192 | 193 | 194 | extern int initAcars(channel_t *); 195 | extern void decodeAcars(channel_t *); 196 | extern int deinitAcars(void); 197 | 198 | extern int DecodeLabel(acarsmsg_t *msg,oooi_t *oooi); 199 | 200 | extern void outputmsg(const msgblk_t*); 201 | -------------------------------------------------------------------------------- /sdrplay.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Thierry Leconte 3 | * 4 | * 5 | * This code is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU Library General Public License version 2 7 | * published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | * 18 | * Taken the input variant from rtl.c, a variant for use with the 19 | * sdrplay was created 20 | * J van Katwijk, Lazy Chair Computing (J.vanKatwijk@gmail.com) 21 | */ 22 | #ifdef WITH_SDRPLAY 23 | 24 | #define _GNU_SOURCE 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "acarsdec.h" 33 | #include 34 | 35 | #define SDRPLAY_MULT 160 36 | #define SDRPLAY_INRATE (INTRATE * SDRPLAY_MULT) 37 | 38 | extern void *compute_thread (void *arg); 39 | 40 | static int hwVersion; 41 | static 42 | unsigned int chooseFc (uint32_t *Fd, uint32_t nbch) { 43 | int n; 44 | int ne; 45 | int Fc; 46 | do { 47 | ne = 0; 48 | for (n = 0; n < nbch - 1; n++) { 49 | if (Fd [n] > Fd [n + 1]) { 50 | uint32_t t; 51 | t = Fd [n + 1]; 52 | Fd [n + 1] = Fd [n]; 53 | Fd [n] = t; 54 | ne = 1; 55 | } 56 | } 57 | } while (ne != 0); 58 | 59 | if ((Fd [nbch - 1] - Fd [0]) > SDRPLAY_INRATE - 4 * INTRATE) { 60 | fprintf(stderr, "Frequencies too far apart\n"); 61 | return -1; 62 | } 63 | 64 | for (Fc = Fd [nbch - 1] + 2 * INTRATE; 65 | Fc > Fd [0] - 2 * INTRATE; Fc --) { 66 | for (n = 0; n < nbch; n++) { 67 | if (abs (Fc - Fd [n]) > SDRPLAY_INRATE / 2 - 2 * INTRATE) 68 | break; 69 | if (abs (Fc - Fd [n]) < 2 * INTRATE) 70 | break; 71 | if (n > 0 && Fc - Fd[n - 1] == Fd[n] - Fc) 72 | break; 73 | } 74 | if (n == nbch) 75 | break; 76 | } 77 | 78 | return Fc; 79 | } 80 | 81 | static 82 | int RSP1_Table [] = {0, 24, 19, 43}; 83 | 84 | static 85 | int RSP1A_Table [] = {0, 6, 12, 18, 20, 26, 32, 38, 57, 62}; 86 | 87 | static 88 | int RSP2_Table [] = {0, 10, 15, 21, 24, 34, 39, 45, 64}; 89 | 90 | static 91 | int RSPduo_Table [] = {0, 6, 12, 18, 20, 26, 32, 38, 57, 62}; 92 | 93 | static 94 | int get_lnaGRdB (int hwVersion, int lnaState) { 95 | switch (hwVersion) { 96 | case 1: 97 | return RSP1_Table [lnaState]; 98 | 99 | case 2: 100 | return RSP2_Table [lnaState]; 101 | 102 | default: 103 | return RSP1A_Table [lnaState]; 104 | } 105 | } 106 | // 107 | unsigned int Fc; 108 | int initSdrplay (char **argv, int optind) { 109 | int r, n; 110 | char *argF; 111 | unsigned int F0, minFc = 140000000, maxFc = 0; 112 | unsigned int Fd [MAXNBCHANNELS]; 113 | int result; 114 | uint32_t i; 115 | uint deviceIndex, numofDevs; 116 | mir_sdr_DeviceT devDesc [4]; 117 | mir_sdr_ErrT err; 118 | 119 | nbch = 0; 120 | while ((argF = argv [optind]) && nbch < MAXNBCHANNELS) { 121 | Fd [nbch] = 122 | ((int)(1000000 * atof (argF) + INTRATE / 2) / INTRATE) * 123 | INTRATE; 124 | optind++; 125 | if (Fd [nbch] < 118000000 || Fd [nbch] > 138000000) { 126 | fprintf (stderr, "WARNING: Invalid frequency %d\n", Fd [nbch]); 127 | continue; 128 | } 129 | 130 | channel [nbch]. chn = nbch; 131 | channel [nbch]. Fr = (float)Fd [nbch]; 132 | if (Fd [nbch] < minFc) 133 | minFc = Fd[nbch]; 134 | if (Fd [nbch] > maxFc) 135 | maxFc = Fd[nbch]; 136 | nbch++; 137 | } 138 | 139 | if (nbch > MAXNBCHANNELS) 140 | fprintf (stderr, 141 | "WARNING: too much frequencies, taking only the %d firsts\n", 142 | MAXNBCHANNELS); 143 | 144 | if (nbch == 0) { 145 | fprintf(stderr, "Need a least one frequencies\n"); 146 | return 1; 147 | } 148 | 149 | Fc = chooseFc (Fd, nbch); 150 | 151 | for (n = 0; n < nbch; n++) { 152 | channel_t *ch = &(channel[n]); 153 | ch -> counter = 0; 154 | int ind; 155 | double correctionPhase; 156 | ch -> D = 0; 157 | ch -> oscillator = (float complex *)malloc (SDRPLAY_MULT * sizeof (float complex)); 158 | ch -> dm_buffer = (float *)malloc (512 * sizeof (float)); 159 | 160 | correctionPhase = (ch -> Fr - (float)Fc) / (float)(SDRPLAY_INRATE) * 2.0 * M_PI; 161 | fprintf (stderr, "Fc = %d, phase = %f (%f)\n", 162 | Fc, correctionPhase, ch -> Fr - (float)Fc); 163 | for (ind = 0; ind < SDRPLAY_MULT; ind++) 164 | ch -> oscillator [ind] = cexpf (correctionPhase * ind * -I) / SDRPLAY_MULT; 165 | } 166 | 167 | float ver; 168 | result = mir_sdr_ApiVersion (&ver); 169 | if (ver != MIR_SDR_API_VERSION) { 170 | fprintf (stderr, "wrong api version %f %d\n", ver, result); 171 | return -1; 172 | } 173 | 174 | mir_sdr_GetDevices (devDesc, &numofDevs, (uint32_t) 4); 175 | if (numofDevs == 0) { 176 | fprintf (stderr, "Sorry, no device found\n"); 177 | exit (2); 178 | } 179 | 180 | deviceIndex = 0; 181 | hwVersion = devDesc [deviceIndex]. hwVer; 182 | fprintf (stderr, "%s %s\n", 183 | devDesc [deviceIndex]. DevNm, devDesc [deviceIndex]. SerNo); 184 | err = mir_sdr_SetDeviceIdx (deviceIndex); 185 | if (err != mir_sdr_Success) { 186 | fprintf (stderr, "Cannot start with device\n"); 187 | return 1; 188 | } 189 | 190 | if (GRdB == -100) 191 | fprintf (stderr, "SDRplay device selects freq %d and sets autogain\n", Fc); 192 | else 193 | fprintf (stderr, "SDRplay device selects freq %d and sets %d as gain reduction\n", 194 | Fc, get_lnaGRdB (hwVersion, lnaState) + GRdB); 195 | 196 | return 0; 197 | } 198 | 199 | static 200 | int current_index = 0; 201 | static 202 | void myStreamCallback (int16_t *xi, 203 | int16_t *xq, 204 | uint32_t firstSampleNum, 205 | int32_t grChanged, 206 | int32_t rfChanged, 207 | int32_t fsChanged, 208 | uint32_t numSamples, 209 | uint32_t reset, 210 | uint32_t hwRemoved, 211 | void *cbContext) { 212 | int n, i; 213 | int local_ind; 214 | 215 | for (n = 0; n < nbch; n ++) { 216 | local_ind = current_index; 217 | channel_t *ch = &(channel [n]); 218 | float complex D = ch -> D; 219 | for (i = 0; i < numSamples; i ++) { 220 | float r = ((float)(xi [i])); 221 | float g = ((float)(xq [i])); 222 | float complex v = r + g * I; 223 | D += v * ch -> oscillator [local_ind ++]; 224 | if (local_ind >= SDRPLAY_MULT) { 225 | ch -> dm_buffer [ch -> counter ++] = cabsf (D) / 4; 226 | local_ind = 0; 227 | D = 0; 228 | if (ch -> counter >= 512) { 229 | demodMSK (ch, 512); 230 | ch -> counter = 0; 231 | } 232 | } 233 | } 234 | ch -> D = D; 235 | } 236 | current_index = (current_index + numSamples) % SDRPLAY_MULT; 237 | } 238 | 239 | static 240 | void myGainChangeCallback (uint32_t gRdB, 241 | uint32_t lnaGRdB, 242 | void *cbContext) { 243 | (void)gRdB; 244 | (void)lnaGRdB; 245 | (void)cbContext; 246 | } 247 | 248 | int runSdrplaySample (void) { 249 | int result ; 250 | int gRdBSystem = 0; 251 | int samplesPerPacket; 252 | int MHz_1 = 1000000; 253 | int localGRdB = (20 <= GRdB) && (GRdB <= 59) ? GRdB : 20; 254 | 255 | result = mir_sdr_StreamInit (&localGRdB, 256 | ((double) (SDRPLAY_INRATE)) / MHz_1, 257 | ((double) Fc) / MHz_1, 258 | mir_sdr_BW_1_536, 259 | mir_sdr_IF_Zero, 260 | lnaState, 261 | &gRdBSystem, 262 | mir_sdr_USE_RSP_SET_GR, 263 | &samplesPerPacket, 264 | (mir_sdr_StreamCallback_t)myStreamCallback, 265 | (mir_sdr_GainChangeCallback_t)myGainChangeCallback, 266 | NULL); 267 | 268 | if (result != mir_sdr_Success) { 269 | fprintf (stderr, "Error %d on streamInit\n", result); 270 | return -1; 271 | } 272 | if (GRdB == -100) { 273 | result = mir_sdr_AgcControl (mir_sdr_AGC_100HZ, 274 | -30, 0, 0, 0, 0, lnaState); 275 | if (result != mir_sdr_Success) 276 | fprintf (stderr, "Error %d on AgcControl\n", result); 277 | } 278 | 279 | mir_sdr_SetPpm ((float)ppm); 280 | mir_sdr_SetDcMode (4, 1); 281 | mir_sdr_SetDcTrackTime (63); 282 | // 283 | mir_sdr_DCoffsetIQimbalanceControl (0, 1); 284 | while (1) 285 | sleep(2); 286 | 287 | mir_sdr_ReleaseDeviceIdx (); 288 | return 0; 289 | } 290 | 291 | #endif 292 | -------------------------------------------------------------------------------- /acars.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Thierry Leconte 3 | * 4 | * 5 | * This code is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU Library General Public License version 2 7 | * published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Library General Public License for more details. 13 | * 14 | * 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "acarsdec.h" 21 | 22 | #define SYN 0x16 23 | #define SOH 0x01 24 | #define STX 0x02 25 | #define ETX 0x83 26 | #define ETB 0x97 27 | #define DLE 0x7f 28 | 29 | /* message queue */ 30 | static pthread_mutex_t blkq_mtx; 31 | static pthread_cond_t blkq_wcd; 32 | static msgblk_t *blkq_s,*blkq_e; 33 | static pthread_t blkth_id; 34 | 35 | static int acars_shutdown; 36 | 37 | #include "syndrom.h" 38 | 39 | static int fixprerr(msgblk_t * blk, const unsigned short crc, int *pr, int pn) 40 | { 41 | int i; 42 | 43 | if (pn > 0) { 44 | /* try to recursievly fix parity error */ 45 | for (i = 0; i < 8; i++) { 46 | if (fixprerr(blk, crc ^ syndrom[i + 8 * (blk->len - *pr + 1)], pr + 1, pn - 1)) { 47 | blk->txt[*pr] ^= (1 << i); 48 | return 1; 49 | } 50 | } 51 | return 0; 52 | } else { 53 | /* end of recursion : no more parity error */ 54 | if (crc == 0) 55 | return 1; 56 | 57 | /* test remainding error in crc */ 58 | for (i = 0; i < 2 * 8; i++) 59 | if (syndrom[i] == crc) { 60 | return 1; 61 | } 62 | return 0; 63 | } 64 | } 65 | 66 | static int fixdberr(msgblk_t * blk, const unsigned short crc) 67 | { 68 | int i,j,k; 69 | 70 | /* test remainding error in crc */ 71 | for (i = 0; i < 2 * 8; i++) 72 | if (syndrom[i] == crc) { 73 | return 1; 74 | } 75 | 76 | /* test double error in bytes */ 77 | for (k = 0; k < blk->len ; k++) { 78 | int bo=8*(blk->len-k+1); 79 | for (i = 0; i < 8; i++) 80 | for (j = 0; j < 8; j++) { 81 | if(i==j) continue; 82 | if((crc^syndrom[i+bo]^syndrom[j+bo])==0) { 83 | blk->txt[k] ^= (1 << i); 84 | blk->txt[k] ^= (1 << j); 85 | return 1; 86 | } 87 | } 88 | } 89 | return 0; 90 | } 91 | 92 | #define MAXPERR 3 93 | static void *blk_thread(void *arg) 94 | { 95 | do { 96 | msgblk_t *blk; 97 | int i, pn; 98 | unsigned short crc; 99 | int pr[MAXPERR]; 100 | 101 | if (verbose) 102 | fprintf(stderr, "blk_starting\n"); 103 | 104 | /* get a message */ 105 | pthread_mutex_lock(&blkq_mtx); 106 | while ((blkq_e == NULL) && !acars_shutdown) 107 | pthread_cond_wait(&blkq_wcd, &blkq_mtx); 108 | 109 | if (acars_shutdown) { 110 | pthread_mutex_unlock(&blkq_mtx); 111 | break; 112 | } 113 | 114 | blk = blkq_e; 115 | blkq_e = blk->prev; 116 | if (blkq_e == NULL) 117 | blkq_s = NULL; 118 | pthread_mutex_unlock(&blkq_mtx); 119 | 120 | if (verbose) 121 | fprintf(stderr, "get message #%d\n", blk->chn + 1); 122 | 123 | /* handle message */ 124 | if (blk->len < 13) { 125 | if (verbose) 126 | fprintf(stderr, "#%d too short\n", blk->chn + 1); 127 | free(blk); 128 | continue; 129 | } 130 | 131 | /* force STX/ETX */ 132 | blk->txt[12] &= (ETX | STX); 133 | blk->txt[12] |= (ETX & STX); 134 | 135 | /* parity check */ 136 | pn = 0; 137 | for (i = 0; i < blk->len; i++) { 138 | if ((numbits[(unsigned char)(blk->txt[i])] & 1) == 0) { 139 | if (pn < MAXPERR) { 140 | pr[pn] = i; 141 | } 142 | pn++; 143 | } 144 | } 145 | if (pn > MAXPERR) { 146 | if (verbose) 147 | fprintf(stderr, 148 | "#%d too many parity errors: %d\n", 149 | blk->chn + 1, pn); 150 | free(blk); 151 | continue; 152 | } 153 | if (pn > 0 && verbose) 154 | fprintf(stderr, "#%d parity error(s): %d\n", 155 | blk->chn + 1, pn); 156 | blk->err = pn; 157 | 158 | /* crc check */ 159 | crc = 0; 160 | for (i = 0; i < blk->len; i++) { 161 | update_crc(crc, blk->txt[i]); 162 | 163 | } 164 | update_crc(crc, blk->crc[0]); 165 | update_crc(crc, blk->crc[1]); 166 | if (crc && verbose) 167 | fprintf(stderr, "#%d crc error\n", blk->chn + 1); 168 | 169 | /* try to fix error */ 170 | if(pn) { 171 | if (fixprerr(blk, crc, pr, pn) == 0) { 172 | if (verbose) 173 | fprintf(stderr, "#%d not able to fix errors\n", blk->chn + 1); 174 | free(blk); 175 | continue; 176 | } 177 | if (verbose) 178 | fprintf(stderr, "#%d errors fixed\n", blk->chn + 1); 179 | } else { 180 | 181 | 182 | if (crc) { 183 | if(fixdberr(blk, crc) == 0) { 184 | if (verbose) 185 | fprintf(stderr, "#%d not able to fix errors\n", blk->chn + 1); 186 | free(blk); 187 | continue; 188 | } 189 | if (verbose) 190 | fprintf(stderr, "#%d errors fixed\n", blk->chn + 1); 191 | } 192 | } 193 | 194 | /* redo parity checking and removing */ 195 | pn = 0; 196 | for (i = 0; i < blk->len; i++) { 197 | if ((numbits[(unsigned char)(blk->txt[i])] & 1) == 0) { 198 | pn++; 199 | } 200 | blk->txt[i] &= 0x7f; 201 | } 202 | if (pn) { 203 | fprintf(stderr, "#%d parity check problem\n", 204 | blk->chn + 1); 205 | free(blk); 206 | continue; 207 | } 208 | 209 | outputmsg(blk); 210 | 211 | free(blk); 212 | 213 | } while (1); 214 | return NULL; 215 | } 216 | 217 | 218 | int initAcars(channel_t * ch) 219 | { 220 | if(ch->chn==0) { 221 | /* init global message queue */ 222 | pthread_mutex_init(&blkq_mtx, NULL); 223 | pthread_cond_init(&blkq_wcd, NULL); 224 | blkq_e=blkq_s=NULL; 225 | pthread_create(&blkth_id , NULL, blk_thread, NULL); 226 | 227 | acars_shutdown = 0; 228 | } 229 | 230 | ch->outbits = 0; 231 | ch->nbits = 8; 232 | ch->Acarsstate = WSYN; 233 | 234 | ch->blk = NULL; 235 | 236 | return 0; 237 | } 238 | 239 | static void resetAcars(channel_t * ch) 240 | { 241 | ch->Acarsstate = WSYN; 242 | ch->MskDf = 0; 243 | ch->nbits = 1; 244 | } 245 | 246 | void decodeAcars(channel_t * ch) 247 | { 248 | unsigned char r = ch->outbits; 249 | 250 | switch (ch->Acarsstate) { 251 | 252 | case WSYN: 253 | if (r == SYN) { 254 | ch->Acarsstate = SYN2; 255 | ch->nbits = 8; 256 | return; 257 | } 258 | if (r == (unsigned char)~SYN) { 259 | ch->MskS ^= 2; 260 | ch->Acarsstate = SYN2; 261 | ch->nbits = 8; 262 | return; 263 | } 264 | ch->nbits = 1; 265 | return; 266 | 267 | case SYN2: 268 | if (r == SYN) { 269 | ch->Acarsstate = SOH1; 270 | ch->nbits = 8; 271 | return; 272 | } 273 | if (r == (unsigned char)~SYN) { 274 | ch->MskS ^= 2; 275 | ch->nbits = 8; 276 | return; 277 | } 278 | resetAcars(ch); 279 | return; 280 | 281 | case SOH1: 282 | if (r == SOH) { 283 | if(ch->blk == NULL) { 284 | ch->blk = malloc(sizeof(msgblk_t)); 285 | if(ch->blk == NULL) { 286 | resetAcars(ch); 287 | return; 288 | } 289 | } 290 | gettimeofday(&(ch->blk->tv), NULL); 291 | ch->Acarsstate = TXT; 292 | ch->blk->chn = ch->chn; 293 | ch->blk->len = 0; 294 | ch->blk->err = 0; 295 | ch->nbits = 8; 296 | ch->MskLvlSum = 0; 297 | ch->MskBitCount = 0; 298 | return; 299 | } 300 | resetAcars(ch); 301 | return; 302 | 303 | case TXT: 304 | 305 | ch->blk->txt[ch->blk->len] = r; 306 | ch->blk->len++; 307 | if ((numbits[(unsigned char)r] & 1) == 0) { 308 | ch->blk->err++; 309 | 310 | if (ch->blk->err > MAXPERR + 1) { 311 | if (verbose) 312 | fprintf(stderr, 313 | "#%d too many parity errors\n", 314 | ch->chn + 1); 315 | resetAcars(ch); 316 | return; 317 | } 318 | } 319 | if (r == ETX || r == ETB) { 320 | ch->Acarsstate = CRC1; 321 | ch->nbits = 8; 322 | return; 323 | } 324 | if (ch->blk->len > 20 && r == DLE) { 325 | if (verbose) 326 | fprintf(stderr, "#%d miss txt end\n", 327 | ch->chn + 1); 328 | ch->blk->len -= 3; 329 | ch->blk->crc[0] = ch->blk->txt[ch->blk->len]; 330 | ch->blk->crc[1] = ch->blk->txt[ch->blk->len + 1]; 331 | ch->Acarsstate = CRC2; 332 | goto putmsg_lbl; 333 | } 334 | if (ch->blk->len > 240) { 335 | if (verbose) 336 | fprintf(stderr, "#%d too long\n", ch->chn + 1); 337 | resetAcars(ch); 338 | return; 339 | } 340 | ch->nbits = 8; 341 | return; 342 | 343 | case CRC1: 344 | ch->blk->crc[0] = r; 345 | ch->Acarsstate = CRC2; 346 | ch->nbits = 8; 347 | return; 348 | case CRC2: 349 | ch->blk->crc[1] = r; 350 | putmsg_lbl: 351 | ch->blk->lvl = 10*log10(ch->MskLvlSum / ch->MskBitCount); 352 | 353 | if (verbose) 354 | fprintf(stderr, "put message #%d\n", ch->chn + 1); 355 | 356 | pthread_mutex_lock(&blkq_mtx); 357 | ch->blk->prev = NULL; 358 | if (blkq_s) 359 | blkq_s->prev = ch->blk; 360 | blkq_s = ch->blk; 361 | if (blkq_e == NULL) 362 | blkq_e = blkq_s; 363 | pthread_cond_signal(&blkq_wcd); 364 | pthread_mutex_unlock(&blkq_mtx); 365 | 366 | ch->blk=NULL; 367 | ch->Acarsstate = END; 368 | ch->nbits = 8; 369 | return; 370 | case END: 371 | resetAcars(ch); 372 | ch->nbits = 8; 373 | return; 374 | } 375 | } 376 | 377 | 378 | int deinitAcars(void) 379 | { 380 | pthread_mutex_lock(&blkq_mtx); 381 | acars_shutdown = 1; 382 | pthread_mutex_unlock(&blkq_mtx); 383 | pthread_cond_signal(&blkq_wcd); 384 | 385 | pthread_join(blkth_id, NULL); 386 | 387 | return 0; 388 | } 389 | -------------------------------------------------------------------------------- /soapy.c: -------------------------------------------------------------------------------- 1 | #ifdef WITH_SOAPY 2 | 3 | #define _GNU_SOURCE 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "acarsdec.h" 18 | 19 | static SoapySDRDevice *dev = NULL; 20 | static SoapySDRStream *stream = NULL; 21 | static int16_t* soapyInBuf = NULL; 22 | static int soapyInBufSize = 0; 23 | static int soapyInRate = 0; 24 | static int watchdogCounter = 50; 25 | static int current_index = 0; 26 | static pthread_mutex_t cbMutex = PTHREAD_MUTEX_INITIALIZER; 27 | 28 | #define SOAPYOUTBUFSZ 1024 29 | 30 | static unsigned int chooseFc(unsigned int *Fd, unsigned int nbch) 31 | { 32 | int n; 33 | int ne; 34 | int Fc; 35 | do { 36 | ne = 0; 37 | for (n = 0; n < nbch - 1; n++) { 38 | if (Fd[n] > Fd[n + 1]) { 39 | unsigned int t; 40 | t = Fd[n + 1]; 41 | Fd[n + 1] = Fd[n]; 42 | Fd[n] = t; 43 | ne = 1; 44 | } 45 | } 46 | } while (ne); 47 | 48 | if ((Fd[nbch - 1] - Fd[0]) > soapyInRate - 4 * INTRATE) { 49 | fprintf(stderr, "Frequencies too far apart\n"); 50 | return 0; 51 | } 52 | 53 | for (Fc = Fd[nbch - 1] + 2 * INTRATE; Fc > Fd[0] - 2 * INTRATE; Fc--) { 54 | for (n = 0; n < nbch; n++) { 55 | if (abs(Fc - Fd[n]) > soapyInRate / 2 - 2 * INTRATE) 56 | break; 57 | if (abs(Fc - Fd[n]) < 2 * INTRATE) 58 | break; 59 | if (n > 0 && Fc - Fd[n - 1] == Fd[n] - Fc) 60 | break; 61 | } 62 | if (n == nbch) 63 | break; 64 | } 65 | 66 | return Fc; 67 | } 68 | 69 | int initSoapy(char **argv, int optind) 70 | { 71 | int r, n; 72 | char *argF; 73 | unsigned int Fc; 74 | unsigned int Fd[MAXNBCHANNELS]; 75 | 76 | if (argv[optind] == NULL) { 77 | fprintf(stderr, "Need device string (ex: driver=rtltcp,rtltcp=127.0.0.1) after -d\n"); 78 | exit(1); 79 | } 80 | 81 | dev = SoapySDRDevice_makeStrArgs(argv[optind]); 82 | if(dev == NULL) { 83 | fprintf(stderr, "Error opening SoapySDR device using string \"%s\": %s", argv[optind], SoapySDRDevice_lastError()); 84 | return -1; 85 | } 86 | optind++; 87 | 88 | soapyInBufSize = SOAPYOUTBUFSZ * rateMult * 2; 89 | soapyInRate = INTRATE * rateMult; 90 | 91 | soapyInBuf = malloc(sizeof(int16_t) * soapyInBufSize); 92 | 93 | if (gain == -10.0) { 94 | if (verbose) 95 | fprintf(stderr, "Tuner gain: AGC\n"); 96 | r = SoapySDRDevice_setGainMode(dev, SOAPY_SDR_RX, 0, 1); 97 | if (r != 0) 98 | fprintf(stderr, "WARNING: Failed to turn on AGC: %s\n", SoapySDRDevice_lastError()); 99 | } else { 100 | r = SoapySDRDevice_setGainMode(dev, SOAPY_SDR_RX, 0, 0); 101 | if (r != 0) 102 | fprintf(stderr, "WARNING: Failed to turn off AGC: %s\n", SoapySDRDevice_lastError()); 103 | if (verbose) 104 | fprintf(stderr, "Setting gain to: %f\n", gain); 105 | r = SoapySDRDevice_setGain(dev, SOAPY_SDR_RX, 0, gain); 106 | if (r != 0) 107 | fprintf(stderr, "WARNING: Failed to set gain: %s\n", SoapySDRDevice_lastError()); 108 | } 109 | 110 | if (ppm != 0) { 111 | r = SoapySDRDevice_setFrequencyCorrection(dev, SOAPY_SDR_RX, 0, ppm); 112 | if (r != 0) 113 | fprintf(stderr, "WARNING: Failed to set freq correction: %s\n", SoapySDRDevice_lastError()); 114 | } 115 | 116 | nbch = 0; 117 | while ((argF = argv[optind]) && nbch < MAXNBCHANNELS) { 118 | Fd[nbch] = ((int)(1000000 * atof(argF) + INTRATE / 2) / INTRATE) * INTRATE; 119 | optind++; 120 | if (Fd[nbch] < 118000000 || Fd[nbch] > 138000000) { 121 | fprintf(stderr, "WARNING: frequency not in range 118-138 MHz: %d\n", 122 | Fd[nbch]); 123 | continue; 124 | } 125 | channel[nbch].chn = nbch; 126 | channel[nbch].Fr = (float)Fd[nbch]; 127 | nbch++; 128 | }; 129 | if (nbch > MAXNBCHANNELS) 130 | fprintf(stderr, 131 | "WARNING: too many frequencies, using only the first %d\n", 132 | MAXNBCHANNELS); 133 | 134 | if (nbch == 0) { 135 | fprintf(stderr, "Need a least one frequency\n"); 136 | return 1; 137 | } 138 | 139 | if(freq == 0) 140 | freq = chooseFc(Fd, nbch); 141 | 142 | if(freq == 0) 143 | return 1; 144 | 145 | for (n = 0; n < nbch; n++) { 146 | if (Fd[n] < freq - soapyInRate/2 || Fd[n] > freq + soapyInRate/2) { 147 | fprintf(stderr, "WARNING: frequency not in tuned range %d-%d: %d\n", 148 | freq - soapyInRate/2, freq + soapyInRate/2, Fd[n]); 149 | continue; 150 | } 151 | } 152 | 153 | for (n = 0; n < nbch; n++) { 154 | channel_t *ch = &(channel[n]); 155 | int ind; 156 | float AMFreq; 157 | 158 | ch->counter = 0; 159 | ch->D = 0; 160 | ch->oscillator = malloc(rateMult * sizeof(float complex)); 161 | ch->dm_buffer = malloc(SOAPYOUTBUFSZ*sizeof(float)); 162 | 163 | AMFreq = (ch->Fr - (float)freq) / (float)(soapyInRate) * 2.0 * M_PI; 164 | for (ind = 0; ind < rateMult; ind++) { 165 | ch->oscillator[ind] = cexpf(AMFreq*ind*-I)/rateMult; 166 | } 167 | } 168 | 169 | if (verbose) 170 | fprintf(stderr, "Set center freq. to %dHz\n", (int)freq); 171 | r = SoapySDRDevice_setFrequency(dev, SOAPY_SDR_RX, 0, freq, NULL); 172 | if (r != 0) 173 | fprintf(stderr, "WARNING: Failed to set frequency: %s\n", SoapySDRDevice_lastError()); 174 | 175 | if (verbose) 176 | fprintf(stderr, "Setting sample rate: %.4f MS/s\n", soapyInRate / 1e6); 177 | r = SoapySDRDevice_setSampleRate(dev, SOAPY_SDR_RX, 0, soapyInRate); 178 | if (r != 0) 179 | fprintf(stderr, "WARNING: Failed to set sample rate: %s\n", SoapySDRDevice_lastError()); 180 | 181 | stream = SoapySDRDevice_setupStream(dev, SOAPY_SDR_RX, SOAPY_SDR_CS16, NULL, 0, NULL); 182 | 183 | return 0; 184 | } 185 | 186 | int soapySetAntenna(const char *antenna) { 187 | if (dev == NULL) { 188 | fprintf(stderr, "soapySetAntenna: SoapySDR not init'd\n"); 189 | return 1; 190 | } 191 | 192 | if (antenna == NULL) { 193 | fprintf(stderr, "soapySetAntenna: antenna is NULL\n"); 194 | return 1; 195 | } 196 | 197 | if (SoapySDRDevice_setAntenna(dev, SOAPY_SDR_RX, 0, antenna) != 0) { 198 | fprintf(stderr, "soapySetAntenna: SoapySDRDevice_setAntenna failed (check antenna validity)\n"); 199 | return 1; 200 | } 201 | 202 | return 0; 203 | } 204 | 205 | static void *readThreadEntryPoint(void *arg) { 206 | int n; 207 | int res = 0; 208 | int flags = 0; 209 | long long timens = 0; 210 | void* bufs[] = { soapyInBuf }; 211 | 212 | SoapySDRDevice_activateStream(dev, stream, 0, 0, 0); 213 | 214 | while(!signalExit) { 215 | pthread_mutex_lock(&cbMutex); 216 | watchdogCounter = 50; 217 | pthread_mutex_unlock(&cbMutex); 218 | 219 | flags = 0; 220 | res = SoapySDRDevice_readStream(dev, stream, bufs, soapyInBufSize/2, &flags, &timens, 10000000); 221 | if(res <= 0) { 222 | fprintf(stderr, "WARNING: Failed to read SoapySDR stream (%d): %s\n", res, SoapySDRDevice_lastError()); 223 | pthread_mutex_lock(&cbMutex); 224 | signalExit = 1; 225 | pthread_mutex_unlock(&cbMutex); 226 | return NULL; 227 | } 228 | 229 | int n, i; 230 | int local_ind; 231 | 232 | for (n = 0; n < nbch; n++) { 233 | local_ind = current_index; 234 | channel_t *ch = &(channel[n]); 235 | float complex D = ch->D; 236 | 237 | for (i = 0; i < res*2; i+=2) { 238 | float r = (float)soapyInBuf[i]; 239 | float g = (float)soapyInBuf[i+1]; 240 | float complex v = r + g*I; 241 | D += v * ch->oscillator[local_ind++] / 32768.0; 242 | if (local_ind >= rateMult) { 243 | ch->dm_buffer[ch->counter++] = cabsf(D); 244 | local_ind = 0; 245 | D = 0; 246 | if (ch->counter >= SOAPYOUTBUFSZ) { 247 | demodMSK(ch, SOAPYOUTBUFSZ); 248 | ch->counter = 0; 249 | } 250 | } 251 | } 252 | ch->D = D; 253 | } 254 | current_index = (current_index + res) % rateMult; 255 | } 256 | 257 | pthread_mutex_lock(&cbMutex); 258 | signalExit = 1; 259 | pthread_mutex_unlock(&cbMutex); 260 | return NULL; 261 | } 262 | 263 | int runSoapySample(void) 264 | { 265 | pthread_t readThread; 266 | pthread_create(&readThread, NULL, readThreadEntryPoint, NULL); 267 | 268 | pthread_mutex_lock(&cbMutex); 269 | 270 | while (!signalExit) { 271 | if (--watchdogCounter <= 0) { 272 | fprintf(stderr, "No data from SoapySDR for 5 seconds, exiting ...\n"); 273 | break; 274 | } 275 | pthread_mutex_unlock(&cbMutex); 276 | usleep(100 * 1000); // 0.1 seconds 277 | pthread_mutex_lock(&cbMutex); 278 | } 279 | 280 | pthread_mutex_unlock(&cbMutex); 281 | 282 | int count = 100; // 10 seconds 283 | int err = 0; 284 | // Wait on reader thread exit 285 | while (count-- > 0 && (err = pthread_tryjoin_np(readThread, NULL))) { 286 | usleep(100 * 1000); // 0.1 seconds 287 | } 288 | if (err) { 289 | fprintf(stderr, "Receive thread termination failed, will raise SIGKILL to ensure we die!\n"); 290 | raise(SIGKILL); 291 | return 1; 292 | } 293 | return 0; 294 | } 295 | 296 | int runSoapyClose(void) { 297 | int res = 0; 298 | if (soapyInBuf) { 299 | free(soapyInBuf); 300 | soapyInBuf = NULL; 301 | } 302 | if (stream) { 303 | res = SoapySDRDevice_closeStream(dev, stream); 304 | stream = NULL; 305 | if (res != 0) 306 | fprintf(stderr, "WARNING: Failed to close SoapySDR stream: %s\n", SoapySDRDevice_lastError()); 307 | 308 | res = SoapySDRDevice_deactivateStream(dev, stream, 0, 0); 309 | stream = NULL; 310 | if (res != 0) 311 | fprintf(stderr, "WARNING: Failed to deactivate SoapySDR stream: %s\n", SoapySDRDevice_lastError()); 312 | } 313 | if (dev) { 314 | res = SoapySDRDevice_unmake(dev); 315 | dev = NULL; 316 | if (res != 0) 317 | fprintf(stderr, "WARNING: Failed to close SoapySDR device: %s\n", SoapySDRDevice_lastError()); 318 | } 319 | 320 | return res; 321 | } 322 | 323 | #endif 324 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACARSDEC 3.x 2 | 3 | This is a legacy repository of the original code of acarsdec by T.Leconte 4 | 5 | For newer versions see : 6 | https://github.com/f00b4r0/acarsdec/ 7 | 8 | Acarsdec is a multi-channels acars decoder with built-in rtl_sdr, airspy front end or sdrplay device. 9 | 10 | ## Features : 11 | 12 | * up to 8 channels decoded simultaneously 13 | * error detection AND correction 14 | * input via [rtl_sdr](https://sdr.osmocom.org/trac/wiki/rtl-sdr), 15 | or [airspy](https://airspy.com/) or [sdrplay](https://www.sdrplay.com) software defined radios (SDR) 16 | * logging data over UDP in planeplotter or acarsserv formats to store in an sqlite database, or JSON for custom processing. 17 | * decoding of ARINC-622 ATS applications (ADS-C, CPDLC) via [libacars](https://github.com/szpajder/libacars) library 18 | 19 | Multi-channel decoding is particularly useful with broadband devices such 20 | as the RTLSDR dongle, the AIRspy and the SDRplay device. 21 | It allows the user to directly monitor to up to 8 different frequencies simultaneously with very low cost hardware. 22 | 23 | ## Usage 24 | 25 | For RTL-SDR: 26 | 27 | `acarsdec [-o lv] [-t time] [-A] [-b filter ] [-e] [-n|N|j ipaddr:port] [-i stationid] [-l logfile [-H|-D]] -r rtldevicenumber f1 [f2] [... fN] | -s f1 [f2] [... fN]` 28 | 29 | 30 | For Airspy R2 / Mini: 31 | 32 | > acarsdec [-o lv] [-t time] [-A] [-b filter ] [-e] [-n|N|j ipaddr:port] [-i stationid] [-l logfile [-H|-D]] [-g gain] -s airspydevicenumber f1 [f2] [... fN] | -s f1 [f2] [... fN] 33 | 34 | -o lv : output format : 0 : no log, 1 : one line by msg, 2 : full (default), 3 : monitor mode, 4 : msg JSON, 5 : route JSON 35 | 36 | -t time : set forget time (TTL) in seconds in monitor mode(default=600s) 37 | 38 | -A : don't display uplink messages (ie : only aircraft messages) 39 | 40 | -e : don't output empty messages (ie : _d,Q0, etc ...) 41 | 42 | -l logfile : append log messages to logfile (Default : stdout) 43 | 44 | -H : rotate log file once every hour 45 | 46 | -D : rotate log file once every day 47 | 48 | -n ipaddr:port : send acars messages to addr:port via UDP in planeplotter compatible format 49 | 50 | -N ipaddr:port : send acars messages to addr:port via UDP in acarsdec format 51 | 52 | -j ipaddr:port : send acars messages to addr:port via UDP in JSON format 53 | 54 | -i station id: id use in acarsdec network format. 55 | 56 | -b filter: filter output by label (ex: -b "H1:Q0" : only output messages with label H1 or Q0" 57 | 58 | for the RTLSDR device 59 | 60 | -r rtldevice f1 [f2] ... [fN] : decode from rtl dongle number or S/N "rtldevice" receiving at VHF frequencies "f1" and optionally "f2" to "fN" in Mhz (ie : -r 0 131.525 131.725 131.825 ). Frequencies must be within the same 2MHz. 61 | 62 | -g gain : set rtl gain in db (0 to 49.6; >52 and -10 will result in AGC; default is AGC) 63 | 64 | -p ppm : set rtl ppm frequency correction 65 | 66 | for the AirSpy device 67 | -g gain : set airspy gain (0..21) 68 | 69 | -s airspydevice f1 [f2] ... [fN] : decode from airspy device number or S/N "airspydevice" receiving at VHF frequencies "f1" and optionally "f2" to "fN" in Mhz (ie : -s 131.525 131.725 131.825 ). Frequencies must be within the same 2MHz. 70 | 71 | for the SDRplay device 72 | 73 | -s f1 [f2] ... [fN] : decode from SDRplay receiving at VHF frequencies "f1" and optionally "f2" to "fN" in Mhz (ie : -s 131.525 131.725 131.825 ). Frequencies must be within the same 2MHz. 74 | 75 | -L lnaState: set the lnaState (depends on the selected SDRPlay hardware) 76 | 77 | -G GRdB: set the Gain Reduction in dB's. -100 is used for agc. 78 | 79 | for the SoapySDR device 80 | 81 | -d devicestring f1 [f2] ... [fN] : decode from a SoapySDR device at VHF frequencies f1 and optionally f2 to fN in Mhz (ie : -d driver=rtltcp 131.525 131.725 131.825 ). 82 | 83 | -g gain : set gain in db (-10 will result in AGC; default is AGC) 84 | 85 | -p ppm : set rtl ppm frequency correction 86 | 87 | -c freq : set center frequency to tune to 88 | 89 | -m rateMult : set sample rate multiplier: 160 for 2 MS/s or 192 for 2.4 MS/s (default: 160) 90 | 91 | ## Examples 92 | 93 | Decoding from rtl dongle number 0 on 3 frequencies , sending aircraft messages only to 192.168.1.1 on port 5555 94 | and no other loging : 95 | 96 | `acarsdec -A -N 192.168.1.1:5555 -o0 -r 0 131.525 131.725 131.825` 97 | 98 | Decoding from airspy on 3 frequencies with verbose logging : 99 | 100 | `acarsdec -s 131.525 131.725 131.825` 101 | 102 | ### Output formats examples 103 | 104 | #### One line by mesg format (-o 1) 105 | 106 | #2 (L: -5 E:0) 25/12/2016 16:26:40 .EC-JBA IB3166 X B9 J80A /EGLL.TI2/000EGLLAABB2 107 | #3 (L: 8 E:0) 25/12/2016 16:26:44 .G-OZBF ZB494B 2 Q0 S12A 108 | #3 (L: 0 E:0) 25/12/2016 16:26:44 .F-HZDP XK773C 2 16 M38A LAT N 47.176/LON E 2.943 109 | 110 | 111 | #### Full message format (-o 2) 112 | 113 | [#1 (F:131.825 L: 4 E:0) 25/12/2016 16:27:45 -------------------------------- 114 | Aircraft reg: .A6-EDY Flight id: EK0205 115 | Mode : 2 Label : SA Id : 4 Ack : ! 116 | Message no: S31A : 117 | 0EV162743VS/ 118 | 119 | [#3 (F:131.825 L: 3 E:0) 25/12/2016 16:28:08 -------------------------------- 120 | Aircraft reg: .F-GSPZ Flight id: AF0940 121 | Mode : 2 Label : B2 Id : 1 Ack : ! 122 | Message no: L07A : 123 | /PIKCLYA.OC1/CLA 1627 161225 EGGX CLRNCE 606 124 | AFR940 CLRD TO MUHA VIA ETIKI 125 | RANDOM ROUTE 126 | 46N020W 45N027W 44N030W 40N040W 36N050W 127 | 34N055W 32N060W 27N070W 128 | FM ETIKI/1720 MNTN F340 M083 129 | ATC/LEVEL CHANGE 130 | END O 131 | 132 | #### Monitoring mode (-o 3) 133 | 134 | Acarsdec monitor 135 | Aircraft Flight Nb Channels Last First 136 | .CN-RNV AT852X 9 .x. 16:25:58 16:21:05 137 | .F-HBMI ZI0321 1 .x. 16:25:52 16:25:52 138 | .F-GSPZ AF0940 6 ..x 16:25:21 16:22:30 139 | .D-ABUF DE0252 1 .x. 16:25:20 16:25:20 140 | 141 | 142 | #### JSON mode (-o 4) 143 | 144 | {"timestamp":1516206744.1849549,"channel":2,"freq":130.025,"level":-22,"error":0,"mode":"2","label":"H1","block_id":"6","ack":false,"tail":".N842UA","flight":"UA1412","msgno":"D04G","text":"#DFB9102,0043,188/9S101,0039,181/S0101,0043,188/0S100,0039,182/T1100,0043,188/1T099,0039,182/T2099,0043,189/2T098,0039,182/T3098,0043,189/3T097,0039,182/T4098,0043,189/4T097,0039,183/T5098,0043,189/5T097,0039,1","end":true,"station_id":"sigint"} 145 | {"timestamp":1516206745.249615,"channel":2,"freq":130.025,"level":-24,"error":2,"mode":"2","label":"RA","block_id":"R","ack":false,"tail":".N842UA","flight":"","msgno":"","text":"QUHDQWDUA?1HOWGOZIT\r\n ** PART 01 OF 01 **\r\nHOWGOZIT 1412-17 SJC\r\nCI: 17 RLS: 01 \r\nSJC 1615/1625 171A\r\nBMRNG 1630 37 159-\r\nTIPRE 1638 37 145\r\nINSLO 1701 37 125\r\nGAROT 1726 37 106\r\nEKR 1800 ","end":true,"station_id":"sigint"} 146 | 147 | #### JSON route mode (-o 5) (wait for a while before an output) 148 | 149 | {"timestamp":1543677178.9600339,"flight":"BA750P","depa":"EGLL","dsta":"LFSB"} 150 | 151 | 152 | #### with libacars and ARINC 622 decoding 153 | 154 | [#2 (F:131.725 L:-33 E:0) 30/11/2018 19:45:46.645 -------------------------------- 155 | Mode : 2 Label : H1 Id : 3 Nak 156 | Aircraft reg: G-OOBE Flight id: BY01WH 157 | No: F57A 158 | #M1B/B6 LPAFAYA.ADS.G-OOBE0720BD17DFD188CAEAE01F0C50F3715C88200D2344EFF62F08CA8883238E3FF7748768C00E0C88D9FFFC0F08A9847FFCFC16 159 | ADS-C message: 160 | Basic report: 161 | Lat: 46.0385513 162 | Lon: -5.6569290 163 | Alt: 36012 ft 164 | Time: 2744.000 sec past hour (:45:44.000) 165 | Position accuracy: <0.05 nm 166 | NAV unit redundancy: OK 167 | TCAS: OK 168 | Flight ID data: 169 | Flight ID: TOM1WH 170 | Predicted route: 171 | Next waypoint: 172 | Lat: 49.5972633 173 | Lon: -1.7255402 174 | Alt: 36008 ft 175 | ETA: 2179 sec 176 | Next+1 waypoint: 177 | Lat: 49.9999809 178 | Lon: -1.5020370 179 | Alt: 30348 ft 180 | Earth reference data: 181 | True track: 35.2 deg 182 | Ground speed: 435.5 kt 183 | Vertical speed: -16 ft/min 184 | Air reference data: 185 | True heading: 24.3 deg 186 | Mach speed: 0.7765 187 | Vertical speed: -16 ft/min 188 | 189 | 190 | ## Compilation 191 | acarsdec must compile directly on any modern Linux distrib. 192 | 193 | It needs cmake and a C compiler. 194 | 195 | It depends on some external libraries : 196 | * libusb 197 | * librtlsdr for software radio rtl dongle input (http://sdr.osmocom.org/trac/wiki/rtl-sdr) 198 | * libairspy for airspy software radio input 199 | * libmirsdrapi-rsp for sdrplay software radio input 200 | * optionaly libacars for decoding ATS applications (https://github.com/szpajder/libacars) 201 | 202 | > :warning: Raspberry Pi users : read Troubleshooting first 203 | 204 | For rtl_sdr : 205 | ``` 206 | mkdir build 207 | cd build 208 | cmake .. -Drtl=ON 209 | make 210 | sudo make install 211 | ``` 212 | 213 | For airspy : 214 | ``` 215 | mkdir build 216 | cd build 217 | cmake .. -Dairspy=ON 218 | make 219 | sudo make install 220 | ``` 221 | 222 | For sdrplay : 223 | ``` 224 | mkdir build 225 | cd build 226 | cmake .. -Dsdrplay=ON 227 | make 228 | sudo make install 229 | ``` 230 | 231 | Notes : 232 | * Airspy version will set the R820T tuner bandwidth to suit given frequencies. See : (https://tleconte.github.io/R820T/r820IF.html) 233 | * libacars support is optional. If the library (version 2.0.0 or later) is installed and can be located with pkg-config, it will be enabled. 234 | * If you have call cmake .. -Dxxx one time, the option will be sticky . Remove build dir and redo to change sdr option. 235 | 236 | ## Troubleshooting 237 | It seems that the default compile options `-march=native` is problematic on Raspberry Pi. 238 | 239 | In CMakeLists.txt change the line : 240 | 241 | `add_compile_options(-Ofast -march=native)` 242 | 243 | to : 244 | 245 | for PI 2B : `add_compile_options(-Ofast -mcpu=cortex-a7 -mfpu=neon-vfpv4)` 246 | 247 | for PI 3B : `add_compile_options(-Ofast -mcpu=cortex-a53 -mfpu=neon-fp-armv8)` 248 | 249 | for PI 4B : `add_compile_options(-Ofast -mcpu=cortex-a72 -mfpu=neon-fp-armv8)` 250 | 251 | then rebuild (remove anyting in build directory then follow Compilation procedure) 252 | 253 | # Acarsserv 254 | 255 | acarsserv is a companion program for acarsdec. It listens to acars messages on UDP coming from one or more acarsdec processes and stores them in a sqlite database. 256 | 257 | See : [acarsserv](https://github.com/TLeconte/acarsserv) 258 | 259 | ## Copyrights 260 | acarsdec and acarsserv are Copyright Thierry Leconte 2015-2018 261 | 262 | These code are free software; you can redistribute it and/or modify 263 | it under the terms of the GNU Library General Public License version 2 264 | published by the Free Software Foundation. 265 | 266 | They include [cJSON](https://github.com/DaveGamble/cJSON) Copyright (c) 2009-2017 Dave Gamble and cJSON contributors 267 | -------------------------------------------------------------------------------- /label.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "acarsdec.h" 6 | 7 | static char *lblfilter[1024]; 8 | 9 | void build_label_filter(char *arg) 10 | { 11 | int i=0; 12 | char *aptr; 13 | 14 | lblfilter[0]=NULL; 15 | if(arg==NULL) return; 16 | 17 | aptr=strtok(strdup(arg),":"); 18 | while(aptr) { 19 | lblfilter[i]=aptr; i++; 20 | aptr=strtok(NULL,":"); 21 | } 22 | lblfilter[i]=NULL; 23 | } 24 | 25 | int label_filter(char *lbl) 26 | { 27 | int i; 28 | 29 | if(lblfilter[0]==NULL) return 1; 30 | 31 | i=0; 32 | while(lblfilter[i]) { 33 | if(strcmp(lbl,lblfilter[i])==0) return 1; 34 | i++; 35 | } 36 | 37 | return 0; 38 | } 39 | 40 | static int label_q1(char *txt,oooi_t *oooi) 41 | { 42 | memcpy(oooi->sa,txt,4); 43 | memcpy(oooi->gout,&(txt[4]),4); 44 | memcpy(oooi->woff,&(txt[8]),4); 45 | memcpy(oooi->won,&(txt[12]),4); 46 | memcpy(oooi->gin,&(txt[16]),4); 47 | memcpy(oooi->da,&(txt[24]),4); 48 | return 1; 49 | } 50 | static int label_q2(char *txt,oooi_t *oooi) 51 | { 52 | memcpy(oooi->sa,txt,4); 53 | memcpy(oooi->eta,&(txt[4]),4); 54 | return 1; 55 | } 56 | static int label_qa(char *txt,oooi_t *oooi) 57 | { 58 | memcpy(oooi->sa,txt,4); 59 | memcpy(oooi->gout,&(txt[4]),4); 60 | return 1; 61 | } 62 | static int label_qb(char *txt,oooi_t *oooi) 63 | { 64 | memcpy(oooi->sa,txt,4); 65 | memcpy(oooi->woff,&(txt[4]),4); 66 | return 1; 67 | } 68 | static int label_qc(char *txt,oooi_t *oooi) 69 | { 70 | memcpy(oooi->sa,txt,4); 71 | memcpy(oooi->won,&(txt[4]),4); 72 | return 1; 73 | } 74 | static int label_qd(char *txt,oooi_t *oooi) 75 | { 76 | memcpy(oooi->sa,txt,4); 77 | memcpy(oooi->gin,&(txt[4]),4); 78 | return 1; 79 | } 80 | static int label_qe(char *txt,oooi_t *oooi) 81 | { 82 | memcpy(oooi->sa,txt,4); 83 | memcpy(oooi->gout,&(txt[4]),4); 84 | memcpy(oooi->da,&(txt[8]),4); 85 | return 1; 86 | } 87 | static int label_qf(char *txt,oooi_t *oooi) 88 | { 89 | memcpy(oooi->sa,txt,4); 90 | memcpy(oooi->woff,&(txt[4]),4); 91 | memcpy(oooi->da,&(txt[8]),4); 92 | return 1; 93 | } 94 | static int label_qg(char *txt,oooi_t *oooi) 95 | { 96 | memcpy(oooi->sa,txt,4); 97 | memcpy(oooi->gout,&(txt[4]),4); 98 | memcpy(oooi->gin,&(txt[8]),4); 99 | return 1; 100 | } 101 | static int label_qh(char *txt,oooi_t *oooi) 102 | { 103 | memcpy(oooi->sa,txt,4); 104 | memcpy(oooi->gout,&(txt[4]),4); 105 | return 1; 106 | } 107 | static int label_qk(char *txt,oooi_t *oooi) 108 | { 109 | memcpy(oooi->sa,txt,4); 110 | memcpy(oooi->won,&(txt[4]),4); 111 | memcpy(oooi->da,&(txt[8]),4); 112 | return 1; 113 | } 114 | static int label_ql(char *txt,oooi_t *oooi) 115 | { 116 | memcpy(oooi->da,txt,4); 117 | memcpy(oooi->gin,&(txt[8]),4); 118 | memcpy(oooi->sa,&(txt[13]),4); 119 | return 1; 120 | } 121 | static int label_qm(char *txt,oooi_t *oooi) 122 | { 123 | memcpy(oooi->da,txt,4); 124 | memcpy(oooi->sa,&(txt[8]),4); 125 | return 1; 126 | } 127 | static int label_qn(char *txt,oooi_t *oooi) 128 | { 129 | memcpy(oooi->da,&(txt[4]),4); 130 | memcpy(oooi->eta,&(txt[8]),4); 131 | return 1; 132 | } 133 | static int label_qp(char *txt,oooi_t *oooi) 134 | { 135 | memcpy(oooi->sa,txt,4); 136 | memcpy(oooi->da,&(txt[4]),4); 137 | memcpy(oooi->gout,&(txt[8]),4); 138 | return 1; 139 | } 140 | static int label_qq(char *txt,oooi_t *oooi) 141 | { 142 | memcpy(oooi->sa,txt,4); 143 | memcpy(oooi->da,&(txt[4]),4); 144 | memcpy(oooi->woff,&(txt[8]),4); 145 | return 1; 146 | } 147 | static int label_qr(char *txt,oooi_t *oooi) 148 | { 149 | memcpy(oooi->sa,txt,4); 150 | memcpy(oooi->da,&(txt[4]),4); 151 | memcpy(oooi->won,&(txt[8]),4); 152 | return 1; 153 | } 154 | static int label_qs(char *txt,oooi_t *oooi) 155 | { 156 | memcpy(oooi->sa,txt,4); 157 | memcpy(oooi->da,&(txt[4]),4); 158 | memcpy(oooi->gin,&(txt[8]),4); 159 | return 1; 160 | } 161 | static int label_qt(char *txt,oooi_t *oooi) 162 | { 163 | memcpy(oooi->sa,txt,4); 164 | memcpy(oooi->da,&(txt[4]),4); 165 | memcpy(oooi->gout,&(txt[8]),4); 166 | memcpy(oooi->gin,&(txt[12]),4); 167 | return 1; 168 | } 169 | 170 | static int label_20(char *txt,oooi_t *oooi) 171 | { 172 | if(memcmp(txt,"RST",3)) return 0; 173 | memcpy(oooi->sa,&(txt[22]),4); 174 | memcpy(oooi->da,&(txt[26]),4); 175 | return 1; 176 | } 177 | static int label_21(char *txt,oooi_t *oooi) 178 | { 179 | if(txt[6]!=',') return 0; 180 | memcpy(oooi->sa,&(txt[7]),4); 181 | if(txt[11]!=',') return 0; 182 | memcpy(oooi->da,&(txt[12]),4); 183 | return 1; 184 | } 185 | static int label_26(char *txt,oooi_t *oooi) 186 | { 187 | char *p; 188 | if(memcmp(txt,"VER/077",7)) return 0; 189 | p=strchr(txt,'\n'); if(p==NULL) return 0; 190 | p++; 191 | if(memcmp(p,"SCH/",4)) return 0; 192 | p=strchr(p+4,'/'); if(p==NULL) return 0; 193 | memcpy(oooi->sa,p+1,4); 194 | memcpy(oooi->da,p+6,4); 195 | p=strchr(p,'\n'); if(p==NULL) return 1; 196 | p++; 197 | if(memcmp(p,"ETA/",4)) return 0; 198 | memcpy(oooi->eta,p+4,4); 199 | return 1; 200 | } 201 | static int label_2N(char *txt,oooi_t *oooi) 202 | { 203 | if(memcmp(txt,"TKO01",5)) return 0; 204 | if(txt[11]!='/') return 0; 205 | memcpy(oooi->sa,&(txt[20]),4); 206 | memcpy(oooi->da,&(txt[24]),4); 207 | return 1; 208 | } 209 | 210 | static int label_2Z(char *txt,oooi_t *oooi) 211 | { 212 | memcpy(oooi->da,txt,4); 213 | return 1; 214 | } 215 | static int label_33(char *txt,oooi_t *oooi) 216 | { 217 | if(txt[0]!=',') return 0; 218 | if(txt[20]!=',') return 0; 219 | memcpy(oooi->sa,&(txt[21]),4); 220 | if(txt[25]!=',') return 0; 221 | memcpy(oooi->da,&(txt[26]),4); 222 | return 1; 223 | } 224 | static int label_39(char *txt,oooi_t *oooi) 225 | { 226 | if(memcmp(txt,"GTA01",5)) return 0; 227 | if(txt[15]!='/') return 0; 228 | memcpy(oooi->sa,&(txt[24]),4); 229 | memcpy(oooi->da,&(txt[28]),4); 230 | return 1; 231 | } 232 | static int label_44(char *txt,oooi_t *oooi) 233 | { 234 | if(txt[0]=='0') { 235 | if(txt[1]!='0') return 0; 236 | txt+=2; 237 | } 238 | if(memcmp(txt,"POS0",4) && memcmp(txt,"ETA0",4)) return 0; 239 | if(txt[4]!='2' && txt[4]!='3') return 0; 240 | if(txt[23]!=',') return 0; 241 | memcpy(oooi->da,&(txt[24]),4); 242 | if(txt[28]!=',') return 0; 243 | memcpy(oooi->eta,&(txt[29]),4); 244 | if(txt[33]!=',') return 0; 245 | if(txt[38]!=',') return 0; 246 | if(txt[43]!=',') return 0; 247 | memcpy(oooi->eta,&(txt[44]),4); 248 | return 1; 249 | } 250 | static int label_45(char *txt,oooi_t *oooi) 251 | { 252 | if(txt[0]!='A') return 0; 253 | memcpy(oooi->da,&(txt[1]),4); 254 | return 1; 255 | } 256 | static int label_10(char *txt,oooi_t *oooi) 257 | { 258 | if(memcmp(txt,"ARR01",5)) return 0; 259 | memcpy(oooi->da,&(txt[12]),4); 260 | memcpy(oooi->eta,&(txt[16]),4); 261 | return 1; 262 | } 263 | static int label_11(char *txt,oooi_t *oooi) 264 | { 265 | if(memcmp(&(txt[13]),"/DS ",4)) return 0; 266 | memcpy(oooi->da,&(txt[17]),4); 267 | if(memcmp(&(txt[21]),"/ETA ",5)) return 0; 268 | memcpy(oooi->eta,&(txt[26]),4); 269 | return 1; 270 | } 271 | static int label_12(char *txt,oooi_t *oooi) 272 | { 273 | if(txt[4]!=',') return 0; 274 | memcpy(oooi->sa,txt,4); 275 | memcpy(oooi->da,&(txt[5]),4); 276 | return 1; 277 | } 278 | 279 | static int label_15(char *txt,oooi_t *oooi) 280 | { 281 | if(memcmp(txt,"FST01",5)) return 0; 282 | memcpy(oooi->sa,&(txt[5]),4); 283 | memcpy(oooi->da,&(txt[9]),4); 284 | return 1; 285 | } 286 | static int label_17(char *txt,oooi_t *oooi) 287 | { 288 | if(memcmp(txt,"ETA ",4)) return 0; 289 | memcpy(oooi->eta,&(txt[4]),4); 290 | if(txt[8]!=',') return 0; 291 | memcpy(oooi->sa,&(txt[9]),4); 292 | if(txt[13]!=',') return 0; 293 | memcpy(oooi->da,&(txt[14]),4); 294 | return 1; 295 | } 296 | static int label_1G(char *txt,oooi_t *oooi) 297 | { 298 | if(txt[4]!=',') return 0; 299 | memcpy(oooi->sa,txt,4); 300 | memcpy(oooi->da,&(txt[5]),4); 301 | return 1; 302 | } 303 | static int label_80(char *txt,oooi_t *oooi) 304 | { 305 | if(memcmp(&(txt[6]),"/DEST/",5)) return 0; 306 | memcpy(oooi->da,&(txt[12]),4); 307 | return 1; 308 | } 309 | static int label_83(char *txt,oooi_t *oooi) 310 | { 311 | if(txt[4]!=',') return 0; 312 | memcpy(oooi->sa,txt,4); 313 | memcpy(oooi->da,&(txt[5]),4); 314 | return 1; 315 | } 316 | static int label_8D(char *txt,oooi_t *oooi) 317 | { 318 | if(txt[4]!=',') return 0; 319 | if(txt[35]!=',') return 0; 320 | memcpy(oooi->sa,&(txt[36]),4); 321 | if(txt[40]!=',') return 0; 322 | memcpy(oooi->da,&(txt[41]),4); 323 | return 1; 324 | } 325 | static int label_8e(char *txt,oooi_t *oooi) 326 | { 327 | if(txt[4]!=',') return 0; 328 | memcpy(oooi->da,txt,4); 329 | memcpy(oooi->eta,&(txt[5]),4); 330 | return 1; 331 | } 332 | static int label_8s(char *txt,oooi_t *oooi) 333 | { 334 | if(txt[4]!=',') return 0; 335 | memcpy(oooi->da,txt,4); 336 | memcpy(oooi->eta,&(txt[5]),4); 337 | return 1; 338 | } 339 | 340 | int DecodeLabel(acarsmsg_t *msg,oooi_t *oooi) 341 | { 342 | int ov=0; 343 | 344 | memset(oooi,0,sizeof(oooi_t)); 345 | 346 | switch(msg->label[0]) { 347 | case '1' : 348 | if(msg->label[1]=='0') 349 | ov=label_10(msg->txt,oooi); 350 | if(msg->label[1]=='1') 351 | ov=label_11(msg->txt,oooi); 352 | if(msg->label[1]=='2') 353 | ov=label_12(msg->txt,oooi); 354 | if(msg->label[1]=='5') 355 | ov=label_15(msg->txt,oooi); 356 | if(msg->label[1]=='7') 357 | ov=label_17(msg->txt,oooi); 358 | if(msg->label[1]=='G') 359 | ov=label_1G(msg->txt,oooi); 360 | break; 361 | case '2' : 362 | if(msg->label[1]=='0') 363 | ov=label_20(msg->txt,oooi); 364 | if(msg->label[1]=='1') 365 | ov=label_21(msg->txt,oooi); 366 | if(msg->label[1]=='6') 367 | ov=label_26(msg->txt,oooi); 368 | if(msg->label[1]=='N') 369 | ov=label_2N(msg->txt,oooi); 370 | if(msg->label[1]=='Z') 371 | ov=label_2Z(msg->txt,oooi); 372 | break; 373 | case '3' : 374 | if(msg->label[1]=='3') 375 | ov=label_33(msg->txt,oooi); 376 | if(msg->label[1]=='9') 377 | ov=label_39(msg->txt,oooi); 378 | break; 379 | case '4' : 380 | if(msg->label[1]=='4') 381 | ov=label_44(msg->txt,oooi); 382 | if(msg->label[1]=='5') 383 | ov=label_45(msg->txt,oooi); 384 | break; 385 | case '8' : 386 | if(msg->label[1]=='0') 387 | ov=label_80(msg->txt,oooi); 388 | if(msg->label[1]=='3') 389 | ov=label_83(msg->txt,oooi); 390 | if(msg->label[1]=='D') 391 | ov=label_8D(msg->txt,oooi); 392 | if(msg->label[1]=='E') 393 | ov=label_8e(msg->txt,oooi); 394 | if(msg->label[1]=='S') 395 | ov=label_8s(msg->txt,oooi); 396 | break; 397 | case 'R' : 398 | if(msg->label[1]=='B') 399 | ov=label_26(msg->txt,oooi); 400 | break; 401 | case 'Q' : 402 | switch(msg->label[1]) { 403 | case '1':ov=label_q1(msg->txt,oooi);break; 404 | case '2':ov=label_q2(msg->txt,oooi);break; 405 | case 'A':ov=label_qa(msg->txt,oooi);break; 406 | case 'B':ov=label_qb(msg->txt,oooi);break; 407 | case 'C':ov=label_qc(msg->txt,oooi);break; 408 | case 'D':ov=label_qd(msg->txt,oooi);break; 409 | case 'E':ov=label_qe(msg->txt,oooi);break; 410 | case 'F':ov=label_qf(msg->txt,oooi);break; 411 | case 'G':ov=label_qg(msg->txt,oooi);break; 412 | case 'H':ov=label_qh(msg->txt,oooi);break; 413 | case 'K':ov=label_qk(msg->txt,oooi);break; 414 | case 'L':ov=label_ql(msg->txt,oooi);break; 415 | case 'M':ov=label_qm(msg->txt,oooi);break; 416 | case 'N':ov=label_qn(msg->txt,oooi);break; 417 | case 'P':ov=label_qp(msg->txt,oooi);break; 418 | case 'Q':ov=label_qq(msg->txt,oooi);break; 419 | case 'R':ov=label_qr(msg->txt,oooi);break; 420 | case 'S':ov=label_qs(msg->txt,oooi);break; 421 | case 'T':ov=label_qt(msg->txt,oooi);break; 422 | } 423 | break; 424 | } 425 | 426 | return ov; 427 | } 428 | -------------------------------------------------------------------------------- /rtl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Thierry Leconte 3 | * 4 | * 5 | * This code is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU Library General Public License version 2 7 | * published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | * 18 | */ 19 | #ifdef WITH_RTL 20 | 21 | #define _GNU_SOURCE 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "acarsdec.h" 29 | #include 30 | #include 31 | 32 | // set the sameple rate by changing RTMULT 33 | // 2.5Ms/s is the best but could be over limit for some hardware 34 | // 2.0Ms/s is safer 35 | // rtlMult 160 // 2.0000 Ms/s 36 | // rtlMult 192 // 2.4000 Ms/s 37 | // rtlMult 200 // 2.5000 Ms/s 38 | 39 | #define RTLMULTMAX 320 // this is well beyond the rtl-sdr capabilities 40 | 41 | static rtlsdr_dev_t *dev = NULL; 42 | static int status = 0; 43 | static int rtlInBufSize = 0; 44 | static int rtlInRate = 0; 45 | 46 | static int watchdogCounter = 50; 47 | static pthread_mutex_t cbMutex = PTHREAD_MUTEX_INITIALIZER; 48 | 49 | #define RTLOUTBUFSZ 1024 50 | 51 | 52 | /* function verbose_device_search by Kyle Keen 53 | * from http://cgit.osmocom.org/rtl-sdr/tree/src/convenience/convenience.c 54 | */ 55 | int verbose_device_search(char *s) 56 | { 57 | int i, device_count, device, offset; 58 | char *s2; 59 | char vendor[256], product[256], serial[256]; 60 | device_count = rtlsdr_get_device_count(); 61 | if (!device_count) { 62 | fprintf(stderr, "No supported devices found.\n"); 63 | return -1; 64 | } 65 | if (verbose) 66 | fprintf(stderr, "Found %d device(s):\n", device_count); 67 | for (i = 0; i < device_count; i++) { 68 | rtlsdr_get_device_usb_strings(i, vendor, product, serial); 69 | if (verbose) 70 | fprintf(stderr, " %d: %s, %s, SN: %s\n", i, vendor, 71 | product, serial); 72 | } 73 | if (verbose) 74 | fprintf(stderr, "\n"); 75 | /* does string look like raw id number */ 76 | device = (int)strtol(s, &s2, 0); 77 | if (s2[0] == '\0' && device >= 0 && device < device_count) { 78 | if (verbose) 79 | fprintf(stderr, "Using device %d: %s\n", 80 | device, 81 | rtlsdr_get_device_name((uint32_t) device)); 82 | return device; 83 | } 84 | /* does string exact match a serial */ 85 | for (i = 0; i < device_count; i++) { 86 | rtlsdr_get_device_usb_strings(i, vendor, product, serial); 87 | if (strcmp(s, serial) != 0) { 88 | continue; 89 | } 90 | device = i; 91 | if (verbose) 92 | fprintf(stderr, "Using device %d: %s\n", 93 | device, 94 | rtlsdr_get_device_name((uint32_t) device)); 95 | return device; 96 | } 97 | /* does string prefix match a serial */ 98 | for (i = 0; i < device_count; i++) { 99 | rtlsdr_get_device_usb_strings(i, vendor, product, serial); 100 | if (strncmp(s, serial, strlen(s)) != 0) { 101 | continue; 102 | } 103 | device = i; 104 | if (verbose) 105 | fprintf(stderr, "Using device %d: %s\n", 106 | device, 107 | rtlsdr_get_device_name((uint32_t) device)); 108 | return device; 109 | } 110 | /* does string suffix match a serial */ 111 | for (i = 0; i < device_count; i++) { 112 | rtlsdr_get_device_usb_strings(i, vendor, product, serial); 113 | offset = strlen(serial) - strlen(s); 114 | if (offset < 0) { 115 | continue; 116 | } 117 | if (strncmp(s, serial + offset, strlen(s)) != 0) { 118 | continue; 119 | } 120 | device = i; 121 | if (verbose) 122 | fprintf(stderr, "Using device %d: %s\n", 123 | device, 124 | rtlsdr_get_device_name((uint32_t) device)); 125 | return device; 126 | } 127 | fprintf(stderr, "No matching devices found.\n"); 128 | return -1; 129 | } 130 | 131 | static unsigned int chooseFc(unsigned int *Fd, unsigned int nbch) 132 | { 133 | int n; 134 | int ne; 135 | int Fc; 136 | do { 137 | ne = 0; 138 | for (n = 0; n < nbch - 1; n++) { 139 | if (Fd[n] > Fd[n + 1]) { 140 | unsigned int t; 141 | t = Fd[n + 1]; 142 | Fd[n + 1] = Fd[n]; 143 | Fd[n] = t; 144 | ne = 1; 145 | } 146 | } 147 | } while (ne); 148 | 149 | if ((Fd[nbch - 1] - Fd[0]) > rtlInRate - 4 * INTRATE) { 150 | fprintf(stderr, "Frequencies too far apart\n"); 151 | return 0; 152 | } 153 | 154 | for (Fc = Fd[nbch - 1] + 2 * INTRATE; Fc > Fd[0] - 2 * INTRATE; Fc--) { 155 | for (n = 0; n < nbch; n++) { 156 | if (abs(Fc - Fd[n]) > rtlInRate / 2 - 2 * INTRATE) 157 | break; 158 | if (abs(Fc - Fd[n]) < 2 * INTRATE) 159 | break; 160 | if (n > 0 && Fc - Fd[n - 1] == Fd[n] - Fc) 161 | break; 162 | } 163 | if (n == nbch) 164 | break; 165 | } 166 | 167 | return Fc; 168 | } 169 | 170 | int nearest_gain(int target_gain) 171 | { 172 | int i, err1, err2, count, close_gain; 173 | int *gains; 174 | count = rtlsdr_get_tuner_gains(dev, NULL); 175 | if (count <= 0) 176 | return 0; 177 | gains = malloc(sizeof(int) * count); 178 | if(gains == NULL) 179 | return 0; 180 | count = rtlsdr_get_tuner_gains(dev, gains); 181 | close_gain = gains[0]; 182 | for (i = 0; i < count; i++) { 183 | err1 = abs(target_gain - close_gain); 184 | err2 = abs(target_gain - gains[i]); 185 | if (err2 < err1) { 186 | close_gain = gains[i]; 187 | } 188 | } 189 | free(gains); 190 | return close_gain; 191 | } 192 | 193 | int initRtl(char **argv, int optind) 194 | { 195 | int r, n; 196 | int dev_index; 197 | char *argF; 198 | unsigned int Fc; 199 | unsigned int Fd[MAXNBCHANNELS]; 200 | 201 | if (argv[optind] == NULL) { 202 | fprintf(stderr, "Need device name or index (ex: 0) after -r\n"); 203 | exit(1); 204 | } 205 | dev_index = verbose_device_search(argv[optind]); 206 | optind++; 207 | 208 | if (rtlMult > RTLMULTMAX) { 209 | fprintf(stderr, "rtlMult can't be larger than 360\n"); 210 | return 1; 211 | } 212 | 213 | rtlInBufSize = RTLOUTBUFSZ * rtlMult * 2; 214 | rtlInRate = INTRATE * rtlMult; 215 | 216 | r = rtlsdr_open(&dev, dev_index); 217 | if (r < 0) { 218 | fprintf(stderr, "Failed to open rtlsdr device\n"); 219 | return r; 220 | } 221 | 222 | if (gain > 520 || gain == -100) { 223 | if (verbose) 224 | fprintf(stderr, "Tuner gain: AGC\n"); 225 | r = rtlsdr_set_tuner_gain_mode(dev, 0); 226 | } else { 227 | rtlsdr_set_tuner_gain_mode(dev, 1); 228 | gain = nearest_gain(gain); 229 | if (verbose) 230 | fprintf(stderr, "Tuner gain: %f\n", (float)gain / 10.0); 231 | r = rtlsdr_set_tuner_gain(dev, gain); 232 | } 233 | if (r < 0) 234 | fprintf(stderr, "WARNING: Failed to set gain.\n"); 235 | 236 | if (ppm != 0) { 237 | r = rtlsdr_set_freq_correction(dev, ppm); 238 | if (r < 0) 239 | fprintf(stderr, 240 | "WARNING: Failed to set freq. correction\n"); 241 | } 242 | 243 | nbch = 0; 244 | while ((argF = argv[optind]) && nbch < MAXNBCHANNELS) { 245 | Fd[nbch] = 246 | ((int)(1000000 * atof(argF) + INTRATE / 2) / INTRATE) * 247 | INTRATE; 248 | optind++; 249 | if (Fd[nbch] < 118000000 || Fd[nbch] > 138000000) { 250 | fprintf(stderr, "WARNING: Invalid frequency %d\n", 251 | Fd[nbch]); 252 | continue; 253 | } 254 | channel[nbch].chn = nbch; 255 | channel[nbch].Fr = (float)Fd[nbch]; 256 | nbch++; 257 | }; 258 | if (nbch > MAXNBCHANNELS) 259 | fprintf(stderr, 260 | "WARNING: too many frequencies, using only the first %d\n", 261 | MAXNBCHANNELS); 262 | 263 | if (nbch == 0) { 264 | fprintf(stderr, "Need a least one frequency\n"); 265 | return 1; 266 | } 267 | 268 | Fc = chooseFc(Fd, nbch); 269 | if (Fc == 0) 270 | return 1; 271 | 272 | for (n = 0; n < nbch; n++) { 273 | channel_t *ch = &(channel[n]); 274 | int ind; 275 | float AMFreq; 276 | 277 | ch->wf = malloc(rtlMult * sizeof(float complex)); 278 | ch->dm_buffer=malloc(RTLOUTBUFSZ*sizeof(float)); 279 | if( ch->wf == NULL || ch->dm_buffer == NULL) { 280 | fprintf(stderr, "ERROR : malloc\n"); 281 | return 1; 282 | } 283 | AMFreq = (ch->Fr - (float)Fc) / (float)(rtlInRate) * 2.0 * M_PI; 284 | for (ind = 0; ind < rtlMult; ind++) { 285 | ch->wf[ind]=cexpf(AMFreq*ind*-I)/rtlMult/127.5; 286 | } 287 | } 288 | 289 | if (verbose) 290 | fprintf(stderr, "Set center freq. to %dHz\n", (int)Fc); 291 | 292 | r = rtlsdr_set_center_freq(dev, Fc); 293 | if (r < 0) { 294 | fprintf(stderr, "WARNING: Failed to set center freq.\n"); 295 | return 1; 296 | } 297 | 298 | fprintf(stderr, "Setting sample rate: %.4f MS/s\n", rtlInRate / 1e6); 299 | r = rtlsdr_set_sample_rate(dev, (unsigned) rtlInRate); 300 | if (r < 0) { 301 | fprintf(stderr, "WARNING: Failed to set sample rate.\n"); 302 | return 1; 303 | } 304 | 305 | r = rtlsdr_reset_buffer(dev); 306 | if (r < 0) { 307 | fprintf(stderr, "WARNING: Failed to reset buffers.\n"); 308 | return 1; 309 | } 310 | 311 | return 0; 312 | } 313 | 314 | static void in_callback(unsigned char *rtlinbuff, uint32_t nread, void *ctx) 315 | { 316 | int n; 317 | 318 | pthread_mutex_lock(&cbMutex); 319 | watchdogCounter = 50; 320 | pthread_mutex_unlock(&cbMutex); 321 | 322 | if (nread != rtlInBufSize) { 323 | fprintf(stderr, "warning: partial read\n"); 324 | return; 325 | 326 | } 327 | status=0; 328 | 329 | // code requires this relationship set in initRtl: 330 | // rtlInBufSize = RTLOUTBUFSZ * rtlMult * 2; 331 | 332 | float complex vb[RTLMULTMAX]; 333 | int i = 0; 334 | for (int m = 0; m < RTLOUTBUFSZ; m++) { 335 | for (int ind = 0; ind < rtlMult; ind++) { 336 | float r, g; 337 | 338 | r = (float)rtlinbuff[i] - 127.37f; i++; 339 | g = (float)rtlinbuff[i] - 127.37f; i++; 340 | 341 | vb[ind]=r+g*I; 342 | } 343 | 344 | for (n = 0; n < nbch; n++) { 345 | channel_t *ch = &(channel[n]); 346 | float complex D,*wf; 347 | 348 | wf = ch->wf; 349 | D = 0; 350 | for (int ind = 0; ind < rtlMult; ind++) { 351 | D += vb[ind] * wf[ind]; 352 | } 353 | ch->dm_buffer[m]=cabsf(D); 354 | } 355 | } 356 | 357 | for (n = 0; n < nbch; n++) { 358 | channel_t *ch = &(channel[n]); 359 | demodMSK(ch,RTLOUTBUFSZ); 360 | } 361 | } 362 | 363 | static void *readThreadEntryPoint(void *arg) { 364 | rtlsdr_read_async(dev, in_callback, NULL, 4, rtlInBufSize); 365 | pthread_mutex_lock(&cbMutex); 366 | signalExit = 1; 367 | pthread_mutex_unlock(&cbMutex); 368 | return NULL; 369 | } 370 | 371 | int runRtlSample(void) 372 | { 373 | pthread_t readThread; 374 | pthread_create(&readThread, NULL, readThreadEntryPoint, NULL); 375 | 376 | pthread_mutex_lock(&cbMutex); 377 | 378 | while (!signalExit) { 379 | if (--watchdogCounter <= 0) { 380 | fprintf(stderr, "No data from the SDR for 5 seconds, exiting ...\n"); 381 | runRtlCancel(); // watchdog triggered after 5 seconds of no data from SDR 382 | break; 383 | } 384 | pthread_mutex_unlock(&cbMutex); 385 | usleep(100 * 1000); // 0.1 seconds 386 | pthread_mutex_lock(&cbMutex); 387 | } 388 | 389 | pthread_mutex_unlock(&cbMutex); 390 | 391 | int count = 100; // 10 seconds 392 | int err = 0; 393 | // Wait on reader thread exit 394 | while (count-- > 0 && (err = pthread_tryjoin_np(readThread, NULL))) { 395 | usleep(100 * 1000); // 0.1 seconds 396 | } 397 | if (err) { 398 | fprintf(stderr, "Receive thread termination failed, will raise SIGKILL to ensure we die!\n"); 399 | raise(SIGKILL); 400 | return 1; 401 | } 402 | 403 | return 0; 404 | } 405 | 406 | int runRtlCancel(void) { 407 | if (dev) { 408 | rtlsdr_cancel_async(dev); // interrupt read_async 409 | } 410 | return 0; 411 | } 412 | 413 | int runRtlClose(void) { 414 | int res = 0; 415 | if (dev) { 416 | res = rtlsdr_close(dev); 417 | dev = NULL; 418 | } 419 | if (res) { 420 | fprintf(stderr, "rtlsdr_close: %d\n", res); 421 | } 422 | 423 | return res; 424 | } 425 | 426 | 427 | #endif 428 | -------------------------------------------------------------------------------- /air.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Thierry Leconte 3 | * 4 | * 5 | * This code is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU Library General Public License version 2 7 | * published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | * 18 | */ 19 | #ifdef WITH_AIR 20 | 21 | #define _GNU_SOURCE 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "acarsdec.h" 31 | 32 | static unsigned int AIRMULT; 33 | static unsigned int AIRINRATE; 34 | 35 | static struct airspy_device* device = NULL; 36 | extern void *compute_thread(void *arg); 37 | 38 | static const unsigned int r820t_hf[]={1953050,1980748,2001344,2032592,2060291,2087988}; 39 | static const unsigned int r820t_lf[]={525548,656935,795424,898403,1186034,1502073,1715133,1853622}; 40 | 41 | static unsigned int chooseFc(unsigned int minF,unsigned int maxF,int filter) 42 | { 43 | unsigned int bw=maxF-minF+2*INTRATE; 44 | unsigned int off=0; 45 | int i,j; 46 | 47 | if(filter) { 48 | for(i=7;i>=0;i--) 49 | if((r820t_hf[5]-r820t_lf[i])>=bw) break; 50 | 51 | if(i<0) return 0; 52 | 53 | for(j=5;j>=0;j--) 54 | if((r820t_hf[j]-r820t_lf[i])<=bw) break; 55 | j++; 56 | 57 | off=(r820t_hf[j]+r820t_lf[i])/2-AIRINRATE/4; 58 | 59 | airspy_r820t_write(device, 10, 0xB0 | (15-j)); 60 | airspy_r820t_write(device, 11, 0xE0 | (15-i)); 61 | } 62 | 63 | return(((maxF+minF)/2+off+INTRATE/2)/INTRATE*INTRATE); 64 | } 65 | 66 | int initAirspy(char **argv, int optind) 67 | { 68 | int n; 69 | char *argF; 70 | int Fc,minFc=140000000,maxFc=0; 71 | int Fd[MAXNBCHANNELS]; 72 | int result; 73 | uint32_t i,count; 74 | uint32_t * supported_samplerates; 75 | uint64_t airspy_serial = 0; 76 | int airspy_device_count = 0; 77 | uint64_t *airspy_device_list = NULL; 78 | 79 | 80 | // Request the total number of libairspy devices connected, allocate space, then request the list. 81 | result = airspy_device_count = airspy_list_devices(NULL, 0); 82 | if(result < 1) { 83 | if(result == 0) { 84 | fprintf(stderr, "No airspy devices found.\n"); 85 | } else { 86 | fprintf(stderr, "airspy_list_devices() failed: %s (%d).\n", airspy_error_name(result), result); 87 | } 88 | airspy_exit(); 89 | return -1; 90 | } 91 | 92 | airspy_device_list = (uint64_t *)malloc(sizeof(uint64_t)*airspy_device_count); 93 | if (airspy_device_list == NULL) return -1; 94 | result = airspy_list_devices(airspy_device_list, airspy_device_count); 95 | if (result != airspy_device_count) { 96 | fprintf(stderr, "airspy_list_devices() failed.\n"); 97 | free(airspy_device_list); 98 | airspy_exit(); 99 | return -1; 100 | } 101 | 102 | 103 | // clear errno to catch invalid input. 104 | errno = 0; 105 | // Attempt to interpret first argument as a specific device serial. 106 | airspy_serial = strtoull(argv[optind], &argF, 16); 107 | 108 | // If strtoull result is an integer from 0 to airspy_device_count: 109 | // 1. Attempt to open airspy serial indicated. 110 | // 2. If successful, consume argument and continue. 111 | // If still no device and strtoull successfully finds a 16bit hex value, then: 112 | // 1. Attempt to open a specific airspy device using value as a serialnumber. 113 | // 2. If succesful, consume argument and continue. 114 | // If still no device and strtoull result fails 115 | // 1. Iterate over list of airspy devices and attempt to open each one. 116 | // 2. If opened succesfully, do not consume argument and continue. 117 | // Else: 118 | // 1. Give up. 119 | 120 | if ( (argv[optind] != argF) && (errno == 0)) { 121 | if ( (airspy_serial < airspy_device_count) ) { 122 | if(verbose) { 123 | fprintf(stderr, "Attempting to open airspy device slot #%lu with serial %016lx.\n", airspy_serial, airspy_device_list[airspy_serial]); 124 | } 125 | result = airspy_open_sn(&device, airspy_device_list[airspy_serial]); 126 | if (result == AIRSPY_SUCCESS) { 127 | optind++; // consume parameter 128 | } 129 | } else { 130 | if (verbose) { 131 | fprintf(stderr, "Attempting to open airspy serial 0x%016lx\n", airspy_serial); 132 | } 133 | result = airspy_open_sn(&device, airspy_serial); 134 | if (result == AIRSPY_SUCCESS) { 135 | optind++; // consume parameter 136 | } 137 | } 138 | } 139 | 140 | if (device == NULL) { 141 | for(n = 0; n < airspy_device_count; n++) { 142 | if (verbose) { 143 | fprintf(stderr, "Attempting to open airspy device #%d.\n", n); 144 | } 145 | result = airspy_open_sn(&device, airspy_device_list[n]); 146 | if (result == AIRSPY_SUCCESS) 147 | break; 148 | } 149 | } 150 | memset(airspy_device_list, 0, sizeof(uint64_t)*airspy_device_count); 151 | free(airspy_device_list); 152 | airspy_device_list = NULL; 153 | 154 | if (device == NULL) { 155 | result = airspy_open(&device); 156 | if (result != AIRSPY_SUCCESS) { 157 | fprintf(stderr, "Failed to open any airspy device.\n"); 158 | airspy_exit(); 159 | return -1; 160 | } 161 | } 162 | 163 | /* parse args */ 164 | nbch = 0; 165 | while ((argF = argv[optind]) && nbch < MAXNBCHANNELS) { 166 | Fd[nbch] = 167 | ((int)(1000000 * atof(argF) + INTRATE / 2) / INTRATE) * 168 | INTRATE; 169 | optind++; 170 | if (Fd[nbch] < 118000000 || Fd[nbch] > 138000000) { 171 | fprintf(stderr, "WARNING: Invalid frequency %d\n", 172 | Fd[nbch]); 173 | continue; 174 | } 175 | channel[nbch].chn = nbch; 176 | channel[nbch].Fr = Fd[nbch]; 177 | if(Fd[nbch]maxFc) maxFc= Fd[nbch]; 179 | nbch++; 180 | }; 181 | if (nbch > MAXNBCHANNELS) 182 | fprintf(stderr, 183 | "WARNING: too many frequencies, taking only the first %d\n", 184 | MAXNBCHANNELS); 185 | 186 | if (nbch == 0) { 187 | fprintf(stderr, "Need a least one frequency\n"); 188 | return 1; 189 | } 190 | 191 | /* init airspy */ 192 | 193 | result = airspy_set_sample_type(device, AIRSPY_SAMPLE_FLOAT32_REAL); 194 | if( result != AIRSPY_SUCCESS ) { 195 | fprintf(stderr,"airspy_set_sample_type() failed: %s (%d)\n", airspy_error_name(result), result); 196 | airspy_close(device); 197 | airspy_exit(); 198 | return -1; 199 | } 200 | 201 | airspy_get_samplerates(device, &count, 0); 202 | supported_samplerates = (uint32_t *) malloc(count * sizeof(uint32_t)); 203 | if(supported_samplerates == NULL ) { 204 | fprintf(stderr,"malloc error\n"); 205 | airspy_close(device); 206 | airspy_exit(); 207 | return -1; 208 | } 209 | airspy_get_samplerates(device, supported_samplerates, count); 210 | for(i=0;i 10000000) continue; 212 | AIRINRATE=supported_samplerates[i]; 213 | AIRMULT=AIRINRATE/INTRATE; 214 | if((AIRMULT*INTRATE)==AIRINRATE) break; 215 | } 216 | 217 | if(i>=count) { 218 | fprintf(stderr,"did not find needed sampling rate\n"); 219 | airspy_close(device); 220 | airspy_exit(); 221 | return -1; 222 | } 223 | 224 | free(supported_samplerates); 225 | 226 | if (verbose) 227 | fprintf(stderr,"Using %d sampling rate\n",AIRINRATE); 228 | 229 | 230 | result = airspy_set_samplerate(device, i); 231 | if( result != AIRSPY_SUCCESS ) { 232 | fprintf(stderr,"airspy_set_samplerate() failed: %s (%d)\n", airspy_error_name(result), result); 233 | airspy_close(device); 234 | airspy_exit(); 235 | return -1; 236 | } 237 | 238 | /* enable packed samples */ 239 | airspy_set_packing(device, 1); 240 | 241 | result = airspy_set_linearity_gain(device, gain); 242 | if( result != AIRSPY_SUCCESS ) { 243 | fprintf(stderr,"airspy_set_vga_gain() failed: %s (%d)\n", airspy_error_name(result), result); 244 | } 245 | 246 | Fc=chooseFc(minFc,maxFc,AIRINRATE==5000000); 247 | if(Fc==0) { 248 | fprintf(stderr, "Frequencies too far apart\n"); 249 | return 1; 250 | } 251 | 252 | result = airspy_set_freq(device, Fc); 253 | if( result != AIRSPY_SUCCESS ) { 254 | fprintf(stderr,"airspy_set_freq() failed: %s (%d)\n", airspy_error_name(result), result); 255 | airspy_close(device); 256 | airspy_exit(); 257 | return -1; 258 | } 259 | if (verbose) 260 | fprintf(stderr, "Set freq. to %d hz\n", Fc); 261 | 262 | /* computes mixers osc. */ 263 | for (n = 0; n < nbch; n++) { 264 | channel_t *ch = &(channel[n]); 265 | int i; 266 | double AMFreq,Ph; 267 | 268 | ch->wf = malloc(AIRMULT * sizeof(float complex)); 269 | ch->dm_buffer = malloc(512 * sizeof(double)); 270 | if(ch->wf == NULL || ch->dm_buffer == NULL ) { 271 | fprintf(stderr,"malloc error\n"); 272 | airspy_close(device); 273 | airspy_exit(); 274 | return -1; 275 | } 276 | ch->D=0; 277 | 278 | AMFreq = 2.0*M_PI*(double)(Fc-ch->Fr+AIRINRATE/4)/(double)(AIRINRATE); 279 | for (i = 0, Ph=0; i < AIRMULT; i++) { 280 | ch->wf[i]=cexpf(Ph*-I)/AIRMULT; 281 | Ph+=AMFreq; 282 | if(Ph>2.0*M_PI) Ph-=2.0*M_PI; 283 | if(Ph<-2.0*M_PI) Ph+=2.0*M_PI; 284 | } 285 | } 286 | 287 | return 0; 288 | } 289 | 290 | int ind=0; 291 | static int rx_callback(airspy_transfer_t* transfer) 292 | { 293 | float* pt_rx_buffer; 294 | int n,i; 295 | int bo,be,ben,nbk; 296 | 297 | pt_rx_buffer = (float *)(transfer->samples); 298 | 299 | bo=AIRMULT-ind; 300 | nbk=(transfer->sample_count-bo)/AIRMULT; 301 | be=nbk*AIRMULT+bo; 302 | ben=transfer->sample_count-be; 303 | 304 | for(n=0;nD; 311 | 312 | /* compute */ 313 | m=0;k=0; 314 | for (i=ind; i < AIRMULT;i++,k++) { 315 | S = pt_rx_buffer[k]; 316 | D += ch->wf[i] * S; 317 | } 318 | ch->dm_buffer[m++]=cabsf(D); 319 | 320 | for (bn=0; bnwf[i] * S; 325 | } 326 | ch->dm_buffer[m++]=cabsf(D); 327 | } 328 | 329 | D=0; 330 | for (i=0; iwf[i] * S; 333 | } 334 | ch->D=D; 335 | 336 | demodMSK(ch,m); 337 | } 338 | ind=ben; 339 | 340 | return 0; 341 | } 342 | 343 | 344 | int runAirspySample(void) 345 | { 346 | int result; 347 | 348 | result = airspy_start_rx(device, rx_callback, NULL); 349 | if( result != AIRSPY_SUCCESS ) { 350 | fprintf(stderr,"airspy_start_rx() failed: %s (%d)\n", airspy_error_name(result), result); 351 | airspy_close(device); 352 | airspy_exit(); 353 | return -1; 354 | } 355 | 356 | while(airspy_is_streaming(device) == AIRSPY_TRUE) { 357 | sleep(2); 358 | } 359 | 360 | return 0; 361 | } 362 | 363 | #endif 364 | -------------------------------------------------------------------------------- /acarsdec.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Thierry Leconte 3 | * 4 | * 5 | * This code is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU Library General Public License version 2 7 | * published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | * 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #ifdef HAVE_LIBACARS 29 | #include 30 | #endif 31 | #include "acarsdec.h" 32 | extern void build_label_filter(char *arg); 33 | 34 | channel_t channel[MAXNBCHANNELS]; 35 | unsigned int nbch; 36 | 37 | char *idstation = NULL; 38 | int inmode = 0; 39 | int verbose = 0; 40 | int outtype = OUTTYPE_STD; 41 | int netout = NETLOG_NONE; 42 | int airflt = 0; 43 | int emptymsg = 0; 44 | int mdly=600; 45 | int hourly = 0; 46 | int daily = 0; 47 | 48 | int signalExit = 0; 49 | 50 | #ifdef HAVE_LIBACARS 51 | int skip_reassembly = 0; 52 | #endif 53 | 54 | #ifdef WITH_RTL 55 | int gain = -100; 56 | int ppm = 0; 57 | int rtlMult = 160; 58 | #endif 59 | 60 | #ifdef WITH_AIR 61 | int gain = 18; 62 | #endif 63 | 64 | #ifdef WITH_SDRPLAY 65 | int lnaState = 2; 66 | int GRdB = 20; 67 | int ppm = 0; 68 | #endif 69 | #ifdef WITH_SOAPY 70 | char *antenna=NULL; 71 | double gain = -10.0; 72 | int ppm = 0; 73 | int rateMult = 160; 74 | int freq = 0; 75 | #endif 76 | 77 | #ifdef WITH_MQTT 78 | char *mqtt_urls[16]; 79 | int mqtt_nburls=0; 80 | char *mqtt_topic=NULL; 81 | char *mqtt_user=NULL; 82 | char *mqtt_passwd=NULL; 83 | #endif 84 | 85 | char *Rawaddr = NULL; 86 | char *logfilename = NULL; 87 | 88 | static void usage(void) 89 | { 90 | fprintf(stderr, 91 | "Acarsdec/acarsserv %s Copyright (c) 2022 Thierry Leconte\n", ACARSDEC_VERSION); 92 | #ifdef HAVE_LIBACARS 93 | fprintf(stderr, "(libacars %s)\n", LA_VERSION); 94 | #endif 95 | fprintf(stderr, 96 | "\nUsage: acarsdec [-o lv] [-t time] [-A] [-b 'labels,..'] [-e] [-i station_id] [-n|-j|-N ipaddr:port] [-l logfile [-H|-D]]"); 97 | #ifdef HAVE_LIBACARS 98 | fprintf(stderr, " [--skip-reassembly] "); 99 | #endif 100 | #ifdef WITH_MQTT 101 | fprintf(stderr, " [ -M mqtt_url"); 102 | fprintf(stderr, " [-T mqtt_topic] |"); 103 | fprintf(stderr, " [-U mqtt_user |"); 104 | fprintf(stderr, " -P mqtt_passwd]]|"); 105 | #endif 106 | #ifdef WITH_ALSA 107 | fprintf(stderr, " -a alsapcmdevice |"); 108 | #endif 109 | #ifdef WITH_SNDFILE 110 | fprintf(stderr, " -f inputwavfile |"); 111 | #endif 112 | #ifdef WITH_RTL 113 | fprintf(stderr, 114 | " [-g gain] [-p ppm] -r rtldevicenumber f1 [f2] ... [fN]"); 115 | #endif 116 | #ifdef WITH_AIR 117 | fprintf(stderr, 118 | "[-g linearity_gain] -s airspydevicenumber f1 [f2] ... [fN]"); 119 | #endif 120 | #ifdef WITH_SDRPLAY 121 | fprintf (stderr, " [-L lnaState] [-G GRdB] [-p ppm] -s f1 [f2] .. [fN]"); 122 | #endif 123 | #ifdef WITH_SOAPY 124 | fprintf (stderr, " [--antenna antenna] [-g gain] [-p ppm] [-c freq] -d devicestring f1 [f2] .. [fN]"); 125 | #endif 126 | fprintf(stderr, "\n\n"); 127 | #ifdef HAVE_LIBACARS 128 | fprintf(stderr, " --skip-reassembly\t: disable reassembling fragmented ACARS messages\n"); 129 | #endif 130 | fprintf(stderr, 131 | " -i stationid\t\t: station id used in acarsdec network format.\n"); 132 | fprintf(stderr, 133 | " -A\t\t\t: don't output uplink messages (ie : only aircraft messages)\n"); 134 | fprintf(stderr, 135 | " -e\t\t\t: don't output empty messages (ie : _d,Q0, etc ...)\n"); 136 | fprintf(stderr, 137 | " -b filter\t\t: filter output by label (ex: -b \"H1:Q0\" : only output messages with label H1 or Q0)\n"); 138 | fprintf(stderr, 139 | " -o lv\t\t\t: output format : 0 : no log, 1 : one line by msg, 2 : full (default) , 3 : monitor , 4 : msg JSON, 5 : route JSON\n"); 140 | fprintf(stderr, 141 | " -t time\t\t: set forget time (TTL) in seconds for monitor mode (default=600s)\n"); 142 | fprintf(stderr, 143 | " -l logfile\t\t: append log messages to logfile (Default : stdout).\n"); 144 | fprintf(stderr, 145 | " -H\t\t\t: rotate log file once every hour\n"); 146 | fprintf(stderr, 147 | " -D\t\t\t: rotate log file once every day\n"); 148 | fprintf(stderr, "\n"); 149 | fprintf(stderr, 150 | " -n ipaddr:port\t\t: send acars messages to addr:port on UDP in planeplotter compatible format\n"); 151 | fprintf(stderr, 152 | " -N ipaddr:port\t\t: send acars messages to addr:port on UDP in acarsdec native format\n"); 153 | fprintf(stderr, 154 | " -j ipaddr:port\t\t: send acars messages to addr:port on UDP in acarsdec json format\n"); 155 | #ifdef WITH_MQTT 156 | fprintf(stderr, " -M mqtt_url\t\t: Url of MQTT broker\n"); 157 | fprintf(stderr, " -T mqtt_topic\t\t: Optionnal MQTT topic (default : acarsdec/${station_id})\n"); 158 | fprintf(stderr, " -U mqtt_user\t\t: Optional MQTT username\n"); 159 | fprintf(stderr, " -P mqtt_passwd\t\t: Optional MQTT password\n"); 160 | #endif 161 | fprintf(stderr, "\n"); 162 | 163 | #ifdef WITH_ALSA 164 | fprintf(stderr, 165 | " -a alsapcmdevice\t: decode from soundcard input alsapcmdevice (ie: hw:0,0)\n"); 166 | #endif 167 | #ifdef WITH_SNDFILE 168 | fprintf(stderr, 169 | " -f inputwavfile\t: decode from a wav file at %d sampling rate\n",INTRATE); 170 | #endif 171 | #ifdef WITH_RTL 172 | fprintf(stderr, 173 | " -g gain\t\t: set rtl gain in db (0 to 49.6; >52 and -10 will result in AGC; default is AGC)\n"); 174 | fprintf(stderr, " -p ppm\t\t\t: set rtl ppm frequency correction\n"); 175 | fprintf(stderr, " -m rtlMult\t\t\t: set rtl sample rate multiplier: 160 for 2 MS/s or 192 for 2.4 MS/s (default: 160)\n"); 176 | fprintf(stderr, 177 | " -r rtldevice f1 [f2]...[f%d]\t: decode from rtl dongle number or S/N rtldevice receiving at VHF frequencies f1 and optionally f2 to f%d in Mhz (ie : -r 0 131.525 131.725 131.825 )\n", MAXNBCHANNELS, MAXNBCHANNELS); 178 | #endif 179 | #ifdef WITH_AIR 180 | fprintf(stderr, 181 | " -g linearity_gain\t: set linearity gain [0-21] default : 18\n"); 182 | fprintf(stderr, 183 | " -s airspydevice f1 [f2]...[f%d]\t: decode from airspy dongle number or hex serial number receiving at VHF frequencies f1 and optionally f2 to f%d in Mhz (ie : -s 131.525 131.725 131.825 )\n", MAXNBCHANNELS, MAXNBCHANNELS); 184 | #endif 185 | #ifdef WITH_SDRPLAY 186 | fprintf (stderr, 187 | "-L lnaState: set the lnaState (depends on the device)\n"\ 188 | "-G Gain reducction in dB's, range 20 .. 59 (-100 is autogain)\n"\ 189 | " -s f1 [f2]...[f%d]\t: decode from sdrplay receiving at VHF frequencies f1 and optionally f2 to f%d in Mhz (ie : -s 131.525 131.725 131.825 )\n", MAXNBCHANNELS, MAXNBCHANNELS); 190 | #endif 191 | #ifdef WITH_SOAPY 192 | fprintf(stderr, 193 | " --antenna antenna\t: set antenna port to use\n"); 194 | fprintf(stderr, 195 | " -g gain\t\t: set gain in db (-10 will result in AGC; default is AGC)\n"); 196 | fprintf(stderr, " -p ppm\t\t\t: set ppm frequency correction\n"); 197 | fprintf(stderr, " c freq\t\t\t: set center frequency to tune to\n"); 198 | fprintf(stderr, " -m rateMult\t\t\t: set sample rate multiplier: 160 for 2 MS/s or 192 for 2.4 MS/s (default: 160)\n"); 199 | fprintf (stderr, 200 | " -d devicestring f1 [f2] .. [f%d]\t: decode from a SoapySDR device located by devicestring at VHF frequencies f1 and optionally f2 to f%d in Mhz (ie : -d driver=rtltcp 131.525 131.725 131.825 )\n", MAXNBCHANNELS, MAXNBCHANNELS); 201 | #endif 202 | 203 | fprintf(stderr, 204 | " Up to %d channels may be simultaneously decoded\n", MAXNBCHANNELS); 205 | exit(1); 206 | } 207 | 208 | static void sigintHandler(int signum) 209 | { 210 | fprintf(stderr, "Received signal %s, exiting.\n", strsignal(signum)); 211 | #ifdef DEBUG 212 | SndWriteClose(); 213 | #endif 214 | #ifdef WITH_RTL 215 | signalExit = 1; 216 | runRtlCancel(); 217 | #elif WITH_SOAPY 218 | signalExit = 1; 219 | #else 220 | exit(0); 221 | #endif 222 | } 223 | 224 | int main(int argc, char **argv) 225 | { 226 | int c; 227 | int res, n; 228 | struct sigaction sigact; 229 | struct option long_opts[] = { 230 | { "verbose", no_argument, NULL, 'v' }, 231 | { "skip-reassembly", no_argument, NULL, 1 }, 232 | #ifdef WITH_SOAPY 233 | { "antenna", required_argument, NULL, 2}, 234 | #endif 235 | { NULL, 0, NULL, 0 } 236 | }; 237 | char sys_hostname[HOST_NAME_MAX+1]; 238 | char *lblf=NULL; 239 | 240 | gethostname(sys_hostname, HOST_NAME_MAX); 241 | sys_hostname[HOST_NAME_MAX]=0; 242 | idstation = strdup(sys_hostname); 243 | 244 | res = 0; 245 | while ((c = getopt_long(argc, argv, "HDvarfdsRo:t:g:m:Aep:n:N:j:l:c:i:L:G:b:M:P:U:T:", long_opts, NULL)) != EOF) { 246 | 247 | switch (c) { 248 | case 'v': 249 | verbose = 1; 250 | break; 251 | case 'o': 252 | outtype = atoi(optarg); 253 | break; 254 | case 't': 255 | mdly = atoi(optarg); 256 | break; 257 | case 'b': 258 | lblf=optarg; 259 | break; 260 | #ifdef HAVE_LIBACARS 261 | case 1: 262 | skip_reassembly = 1; 263 | break; 264 | #endif 265 | #ifdef WITH_ALSA 266 | case 'a': 267 | res = initAlsa(argv, optind); 268 | inmode = 1; 269 | break; 270 | #endif 271 | #ifdef WITH_SNDFILE 272 | case 'f': 273 | res = initSoundfile(argv, optind); 274 | inmode = 2; 275 | break; 276 | #endif 277 | #ifdef WITH_RTL 278 | case 'r': 279 | res = initRtl(argv, optind); 280 | inmode = 3; 281 | break; 282 | case 'p': 283 | ppm = atoi(optarg); 284 | break; 285 | case 'g': 286 | gain = 10 * atof(optarg); 287 | break; 288 | case 'm': 289 | rtlMult = atoi(optarg); 290 | break; 291 | #endif 292 | #ifdef WITH_SDRPLAY 293 | case 's': 294 | res = initSdrplay (argv, optind); 295 | inmode = 5; 296 | break; 297 | case 'p': 298 | ppm = atoi(optarg); 299 | break; 300 | case 'L': 301 | lnaState = atoi(optarg); 302 | break; 303 | case 'G': 304 | GRdB = atoi(optarg); 305 | break; 306 | #endif 307 | #ifdef WITH_SOAPY 308 | case 2: 309 | antenna = optarg; 310 | break; 311 | case 'd': 312 | res = initSoapy(argv, optind); 313 | inmode = 6; 314 | break; 315 | case 'p': 316 | ppm = atoi(optarg); 317 | break; 318 | case 'c': 319 | freq = atoi(optarg); 320 | break; 321 | case 'g': 322 | gain = atof(optarg); 323 | break; 324 | case 'm': 325 | rateMult = atoi(optarg); 326 | break; 327 | #endif 328 | #ifdef WITH_AIR 329 | case 's': 330 | res = initAirspy(argv, optind); 331 | inmode = 4; 332 | break; 333 | case 'g': 334 | gain = atoi(optarg); 335 | break; 336 | #endif 337 | #ifdef WITH_MQTT 338 | case 'M': 339 | if(mqtt_nburls<15) { 340 | mqtt_urls[mqtt_nburls]=strdup(optarg); 341 | mqtt_nburls++; 342 | mqtt_urls[mqtt_nburls]=NULL; 343 | netout = NETLOG_MQTT; 344 | } 345 | break; 346 | case 'U': 347 | mqtt_user = strdup(optarg); 348 | break; 349 | case 'P': 350 | mqtt_passwd = strdup(optarg); 351 | break; 352 | case 'T': 353 | mqtt_topic = strdup(optarg); 354 | break; 355 | #endif 356 | case 'n': 357 | Rawaddr = optarg; 358 | netout = NETLOG_PLANEPLOTTER; 359 | break; 360 | case 'N': 361 | Rawaddr = optarg; 362 | netout = NETLOG_NATIVE; 363 | break; 364 | case 'j': 365 | Rawaddr = optarg; 366 | netout = NETLOG_JSON; 367 | break; 368 | case 'A': 369 | airflt = 1; 370 | break; 371 | case 'e': 372 | emptymsg = 1; 373 | break; 374 | case 'l': 375 | logfilename = optarg; 376 | break; 377 | case 'H': 378 | hourly = 1; 379 | break; 380 | case 'D': 381 | daily = 1; 382 | break; 383 | case 'i': 384 | free(idstation); 385 | idstation = strdup(optarg); 386 | break; 387 | 388 | default: 389 | usage(); 390 | } 391 | } 392 | 393 | if (inmode == 0) { 394 | fprintf(stderr, "Need at least one of -a|-f|-r|-R|-d options\n"); 395 | usage(); 396 | } 397 | 398 | 399 | if (res) { 400 | fprintf(stderr, "Unable to init input\n"); 401 | exit(res); 402 | } 403 | 404 | if(hourly && daily) { 405 | fprintf(stderr, "Options: -H and -D are exclusive\n"); 406 | exit(1); 407 | } 408 | 409 | build_label_filter(lblf); 410 | 411 | res = initOutput(logfilename, Rawaddr); 412 | if (res) { 413 | fprintf(stderr, "Unable to init output\n"); 414 | exit(res); 415 | } 416 | 417 | #ifdef WITH_MQTT 418 | if (netout == NETLOG_MQTT) { 419 | res = MQTTinit(mqtt_urls,idstation,mqtt_topic,mqtt_user,mqtt_passwd); 420 | if (res) { 421 | fprintf(stderr, "Unable to init MQTT\n"); 422 | exit(res); 423 | } 424 | } 425 | #endif 426 | 427 | #ifdef WITH_SOAPY 428 | if (antenna) { 429 | if (verbose) fprintf(stderr, "Setting soapy antenna to %s\n", antenna); 430 | res = soapySetAntenna(antenna); 431 | if (res) { 432 | fprintf(stderr, "Unable to set antenna for SoapySDR\n"); 433 | exit(res); 434 | } 435 | } 436 | #endif 437 | 438 | sigact.sa_handler = sigintHandler; 439 | sigemptyset(&sigact.sa_mask); 440 | sigact.sa_flags = 0; 441 | sigaction(SIGINT, &sigact, NULL); 442 | sigaction(SIGTERM, &sigact, NULL); 443 | sigaction(SIGQUIT, &sigact, NULL); 444 | 445 | for (n = 0; n < nbch; n++) { 446 | channel[n].chn = n; 447 | 448 | res = initMsk(&(channel[n])); 449 | if (res) 450 | break; 451 | res = initAcars(&(channel[n])); 452 | if (res) 453 | break; 454 | } 455 | 456 | if (res) { 457 | fprintf(stderr, "Unable to init internal decoders\n"); 458 | exit(res); 459 | } 460 | 461 | #ifdef DEBUG 462 | if (inmode !=2 ) { 463 | initSndWrite(); 464 | } 465 | #endif 466 | 467 | if (verbose) 468 | fprintf(stderr, "Decoding %d channels\n", nbch); 469 | 470 | /* main decoding */ 471 | switch (inmode) { 472 | #ifdef WITH_ALSA 473 | case 1: 474 | res = runAlsaSample(); 475 | break; 476 | #endif 477 | #ifdef WITH_SNDFILE 478 | case 2: 479 | res = runSoundfileSample(); 480 | break; 481 | #endif 482 | #ifdef WITH_RTL 483 | case 3: 484 | runRtlSample(); 485 | res = runRtlClose(); 486 | break; 487 | #endif 488 | #ifdef WITH_AIR 489 | case 4: 490 | res = runAirspySample(); 491 | break; 492 | #endif 493 | #ifdef WITH_SDRPLAY 494 | case 5: 495 | res = runSdrplaySample (); 496 | break; 497 | #endif 498 | #ifdef WITH_SOAPY 499 | case 6: 500 | runSoapySample(); 501 | res = runSoapyClose(); 502 | break; 503 | #endif 504 | default: 505 | res = -1; 506 | } 507 | 508 | fprintf(stderr, "exiting ...\n"); 509 | 510 | deinitAcars(); 511 | 512 | #ifdef WITH_MQTT 513 | MQTTend(); 514 | #endif 515 | exit(res); 516 | 517 | } 518 | -------------------------------------------------------------------------------- /cJSON.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2017 Dave Gamble and cJSON contributors 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #ifndef cJSON__h 24 | #define cJSON__h 25 | 26 | #ifdef __cplusplus 27 | extern "C" 28 | { 29 | #endif 30 | 31 | /* project version */ 32 | #define CJSON_VERSION_MAJOR 1 33 | #define CJSON_VERSION_MINOR 7 34 | #define CJSON_VERSION_PATCH 1 35 | 36 | #include 37 | 38 | /* cJSON Types: */ 39 | #define cJSON_Invalid (0) 40 | #define cJSON_False (1 << 0) 41 | #define cJSON_True (1 << 1) 42 | #define cJSON_NULL (1 << 2) 43 | #define cJSON_Number (1 << 3) 44 | #define cJSON_String (1 << 4) 45 | #define cJSON_Array (1 << 5) 46 | #define cJSON_Object (1 << 6) 47 | #define cJSON_Raw (1 << 7) /* raw json */ 48 | 49 | #define cJSON_IsReference 256 50 | #define cJSON_StringIsConst 512 51 | 52 | /* The cJSON structure: */ 53 | typedef struct cJSON 54 | { 55 | /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ 56 | struct cJSON *next; 57 | struct cJSON *prev; 58 | /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ 59 | struct cJSON *child; 60 | 61 | /* The type of the item, as above. */ 62 | int type; 63 | 64 | /* The item's string, if type==cJSON_String and type == cJSON_Raw */ 65 | char *valuestring; 66 | /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ 67 | int valueint; 68 | /* The item's number, if type==cJSON_Number */ 69 | double valuedouble; 70 | 71 | /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ 72 | char *string; 73 | } cJSON; 74 | 75 | typedef struct cJSON_Hooks 76 | { 77 | void *(*malloc_fn)(size_t sz); 78 | void (*free_fn)(void *ptr); 79 | } cJSON_Hooks; 80 | 81 | typedef int cJSON_bool; 82 | 83 | #if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) 84 | #define __WINDOWS__ 85 | #endif 86 | #ifdef __WINDOWS__ 87 | 88 | /* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 2 define options: 89 | 90 | CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols 91 | CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) 92 | CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol 93 | 94 | For *nix builds that support visibility attribute, you can define similar behavior by 95 | 96 | setting default visibility to hidden by adding 97 | -fvisibility=hidden (for gcc) 98 | or 99 | -xldscope=hidden (for sun cc) 100 | to CFLAGS 101 | 102 | then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does 103 | 104 | */ 105 | 106 | /* export symbols by default, this is necessary for copy pasting the C and header file */ 107 | #if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) 108 | #define CJSON_EXPORT_SYMBOLS 109 | #endif 110 | 111 | #if defined(CJSON_HIDE_SYMBOLS) 112 | #define CJSON_PUBLIC(type) type __stdcall 113 | #elif defined(CJSON_EXPORT_SYMBOLS) 114 | #define CJSON_PUBLIC(type) __declspec(dllexport) type __stdcall 115 | #elif defined(CJSON_IMPORT_SYMBOLS) 116 | #define CJSON_PUBLIC(type) __declspec(dllimport) type __stdcall 117 | #endif 118 | #else /* !WIN32 */ 119 | #if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) 120 | #define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type 121 | #else 122 | #define CJSON_PUBLIC(type) type 123 | #endif 124 | #endif 125 | 126 | /* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. 127 | * This is to prevent stack overflows. */ 128 | #ifndef CJSON_NESTING_LIMIT 129 | #define CJSON_NESTING_LIMIT 1000 130 | #endif 131 | 132 | /* returns the version of cJSON as a string */ 133 | CJSON_PUBLIC(const char*) cJSON_Version(void); 134 | 135 | /* Supply malloc, realloc and free functions to cJSON */ 136 | CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); 137 | 138 | /* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ 139 | /* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ 140 | CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); 141 | /* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ 142 | /* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ 143 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); 144 | 145 | /* Render a cJSON entity to text for transfer/storage. */ 146 | CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); 147 | /* Render a cJSON entity to text for transfer/storage without any formatting. */ 148 | CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); 149 | /* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ 150 | CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); 151 | /* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ 152 | /* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ 153 | CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); 154 | /* Delete a cJSON entity and all subentities. */ 155 | CJSON_PUBLIC(void) cJSON_Delete(cJSON *c); 156 | 157 | /* Returns the number of items in an array (or object). */ 158 | CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); 159 | /* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ 160 | CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); 161 | /* Get item "string" from object. Case insensitive. */ 162 | CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); 163 | CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); 164 | CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); 165 | /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ 166 | CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); 167 | 168 | /* Check if the item is a string and return its valuestring */ 169 | CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item); 170 | 171 | /* These functions check the type of an item */ 172 | CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); 173 | CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); 174 | CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); 175 | CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); 176 | CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); 177 | CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); 178 | CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); 179 | CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); 180 | CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); 181 | CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); 182 | 183 | /* These calls create a cJSON item of the appropriate type. */ 184 | CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); 185 | CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); 186 | CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); 187 | CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); 188 | CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); 189 | CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); 190 | /* raw json */ 191 | CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); 192 | CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); 193 | CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); 194 | 195 | /* Create a string where valuestring references a string so 196 | * it will not be freed by cJSON_Delete */ 197 | CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); 198 | /* Create an object/arrray that only references it's elements so 199 | * they will not be freed by cJSON_Delete */ 200 | CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); 201 | CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); 202 | 203 | /* These utilities create an Array of count items. */ 204 | CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); 205 | CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); 206 | CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); 207 | CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count); 208 | 209 | /* Append item to the specified array/object. */ 210 | CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item); 211 | CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); 212 | /* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. 213 | * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before 214 | * writing to `item->string` */ 215 | CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); 216 | /* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ 217 | CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); 218 | CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); 219 | 220 | /* Remove/Detatch items from Arrays/Objects. */ 221 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); 222 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); 223 | CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); 224 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); 225 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); 226 | CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); 227 | CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); 228 | 229 | /* Update array items. */ 230 | CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ 231 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); 232 | CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); 233 | CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); 234 | CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); 235 | 236 | /* Duplicate a cJSON item */ 237 | CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); 238 | /* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will 239 | need to be released. With recurse!=0, it will duplicate any children connected to the item. 240 | The item->next and ->prev pointers are always zero on return from Duplicate. */ 241 | /* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. 242 | * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ 243 | CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); 244 | 245 | 246 | CJSON_PUBLIC(void) cJSON_Minify(char *json); 247 | 248 | /* Helper functions for creating and adding items to an object at the same time. 249 | * They return the added item or NULL on failure. */ 250 | CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); 251 | CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); 252 | CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); 253 | CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); 254 | CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); 255 | CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); 256 | CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); 257 | CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); 258 | CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); 259 | 260 | /* When assigning an integer value, it needs to be propagated to valuedouble too. */ 261 | #define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) 262 | /* helper for the cJSON_SetNumberValue macro */ 263 | CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); 264 | #define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) 265 | 266 | /* Macro for iterating over an array or object */ 267 | #define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) 268 | 269 | /* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ 270 | CJSON_PUBLIC(void *) cJSON_malloc(size_t size); 271 | CJSON_PUBLIC(void) cJSON_free(void *object); 272 | 273 | #ifdef __cplusplus 274 | } 275 | #endif 276 | 277 | #endif 278 | -------------------------------------------------------------------------------- /syndrom.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Thierry Leconte 3 | */ 4 | static const unsigned char numbits[256]={ 5 | 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, 6 | 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, 7 | 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, 8 | 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, 9 | 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, 10 | 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, 11 | 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, 12 | 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8 13 | }; 14 | 15 | const unsigned short crc_ccitt_table[256] = { 16 | 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 17 | 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 18 | 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 19 | 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 20 | 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 21 | 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 22 | 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 23 | 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 24 | 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 25 | 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 26 | 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 27 | 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 28 | 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 29 | 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 30 | 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 31 | 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 32 | 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 33 | 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 34 | 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 35 | 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 36 | 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 37 | 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 38 | 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 39 | 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 40 | 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 41 | 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 42 | 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 43 | 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 44 | 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 45 | 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 46 | 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 47 | 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 48 | }; 49 | #define update_crc(crc,c) crc= (crc>> 8)^crc_ccitt_table[(crc^(c))&0xff]; 50 | 51 | 52 | static const unsigned short syndrom[]={ 53 | 0x1189,0x2312,0x4624,0x8c48,0x1081,0x2102,0x4204,0x8408, 54 | 0x19d8,0x33b0,0x6760,0xcec0,0x9591,0x2333,0x4666,0x8ccc, 55 | 0x5adc,0xb5b8,0x6361,0xc6c2,0x8595,0x33b,0x676,0xcec, 56 | 0x1cbb,0x3976,0x72ec,0xe5d8,0xc3a1,0x8f53,0x16b7,0x2d6e, 57 | 0xb44,0x1688,0x2d10,0x5a20,0xb440,0x6091,0xc122,0x8a55, 58 | 0x42b,0x856,0x10ac,0x2158,0x42b0,0x8560,0x2d1,0x5a2, 59 | 0x9fd5,0x37bb,0x6f76,0xdeec,0xb5c9,0x6383,0xc706,0x861d, 60 | 0x81bf,0xb6f,0x16de,0x2dbc,0x5b78,0xb6f0,0x65f1,0xcbe2, 61 | 0x4dfd,0x9bfa,0x3fe5,0x7fca,0xff94,0xf739,0xe663,0xc4d7, 62 | 0x2c27,0x584e,0xb09c,0x6929,0xd252,0xacb5,0x517b,0xa2f6, 63 | 0x5591,0xab22,0x5e55,0xbcaa,0x7145,0xe28a,0xcd05,0x921b, 64 | 0x8555,0x2bb,0x576,0xaec,0x15d8,0x2bb0,0x5760,0xaec0, 65 | 0x5ad,0xb5a,0x16b4,0x2d68,0x5ad0,0xb5a0,0x6351,0xc6a2, 66 | 0x7eea,0xfdd4,0xf3b9,0xef63,0xd6d7,0xa5bf,0x436f,0x86de, 67 | 0x482a,0x9054,0x28b9,0x5172,0xa2e4,0x4dd9,0x9bb2,0x3f75, 68 | 0x8e10,0x1431,0x2862,0x50c4,0xa188,0x4b01,0x9602,0x2415, 69 | 0x100f,0x201e,0x403c,0x8078,0x8e1,0x11c2,0x2384,0x4708, 70 | 0xf8e7,0xf9df,0xfbaf,0xff4f,0xf68f,0xe50f,0xc20f,0x8c0f, 71 | 0x9349,0x2e83,0x5d06,0xba0c,0x7c09,0xf812,0xf835,0xf87b, 72 | 0xdf56,0xb6bd,0x656b,0xcad6,0x9dbd,0x336b,0x66d6,0xcdac, 73 | 0x376c,0x6ed8,0xddb0,0xb371,0x6ef3,0xdde6,0xb3dd,0x6fab, 74 | 0xa95d,0x5aab,0xb556,0x62bd,0xc57a,0x82e5,0xddb,0x1bb6, 75 | 0x89c9,0x1b83,0x3706,0x6e0c,0xdc18,0xb021,0x6853,0xd0a6, 76 | 0x5b44,0xb688,0x6501,0xca02,0x9c15,0x303b,0x6076,0xc0ec, 77 | 0x47b,0x8f6,0x11ec,0x23d8,0x47b0,0x8f60,0x16d1,0x2da2, 78 | 0xcd50,0x92b1,0x2d73,0x5ae6,0xb5cc,0x6389,0xc712,0x8635, 79 | 0x5248,0xa490,0x4131,0x8262,0xcd5,0x19aa,0x3354,0x66a8, 80 | 0xce1e,0x942d,0x204b,0x4096,0x812c,0xa49,0x1492,0x2924, 81 | 0xf931,0xfa73,0xfcf7,0xf1ff,0xebef,0xdfcf,0xb78f,0x670f, 82 | 0x20f3,0x41e6,0x83cc,0xf89,0x1f12,0x3e24,0x7c48,0xf890, 83 | 0xc534,0x8279,0xce3,0x19c6,0x338c,0x6718,0xce30,0x9471, 84 | 0x7762,0xeec4,0xd599,0xa323,0x4e57,0x9cae,0x314d,0x629a, 85 | 0x4063,0x80c6,0x99d,0x133a,0x2674,0x4ce8,0x99d0,0x3bb1, 86 | 0x51dd,0xa3ba,0x4f65,0x9eca,0x3585,0x6b0a,0xd614,0xa439, 87 | 0xd39,0x1a72,0x34e4,0x69c8,0xd390,0xaf31,0x5673,0xace6, 88 | 0xac4f,0x508f,0xa11e,0x4a2d,0x945a,0x20a5,0x414a,0x8294, 89 | 0xba5f,0x7caf,0xf95e,0xfaad,0xfd4b,0xf287,0xed1f,0xd22f, 90 | 0xaac8,0x5d81,0xbb02,0x7e15,0xfc2a,0xf045,0xe89b,0xd927, 91 | 0x4aee,0x95dc,0x23a9,0x4752,0x8ea4,0x1559,0x2ab2,0x5564, 92 | 0xe3a,0x1c74,0x38e8,0x71d0,0xe3a0,0xcf51,0x96b3,0x2577, 93 | 0x9ed7,0x35bf,0x6b7e,0xd6fc,0xa5e9,0x43c3,0x8786,0x71d, 94 | 0xa2ac,0x4d49,0x9a92,0x3d35,0x7a6a,0xf4d4,0xe1b9,0xcb63, 95 | 0x6fc4,0xdf88,0xb701,0x6613,0xcc26,0x905d,0x28ab,0x5156, 96 | 0x8047,0x89f,0x113e,0x227c,0x44f8,0x89f0,0x1bf1,0x37e2, 97 | 0x363b,0x6c76,0xd8ec,0xb9c9,0x7b83,0xf706,0xe61d,0xc42b, 98 | 0x8f66,0x16dd,0x2dba,0x5b74,0xb6e8,0x65c1,0xcb82,0x9f15, 99 | 0x6bf,0xd7e,0x1afc,0x35f8,0x6bf0,0xd7e0,0xa7d1,0x47b3, 100 | 0x4d7a,0x9af4,0x3df9,0x7bf2,0xf7e4,0xe7d9,0xc7a3,0x8757, 101 | 0xdc90,0xb131,0x6a73,0xd4e6,0xa1dd,0x4bab,0x9756,0x26bd, 102 | 0x9455,0x20bb,0x4176,0x82ec,0xdc9,0x1b92,0x3724,0x6e48, 103 | 0x5bc,0xb78,0x16f0,0x2de0,0x5bc0,0xb780,0x6711,0xce22, 104 | 0x7fe2,0xffc4,0xf799,0xe723,0xc657,0x84bf,0x16f,0x2de, 105 | 0xc463,0x80d7,0x9bf,0x137e,0x26fc,0x4df8,0x9bf0,0x3ff1, 106 | 0x5159,0xa2b2,0x4d75,0x9aea,0x3dc5,0x7b8a,0xf714,0xe639, 107 | 0xcf15,0x963b,0x2467,0x48ce,0x919c,0x2b29,0x5652,0xaca4, 108 | 0x47e3,0x8fc6,0x179d,0x2f3a,0x5e74,0xbce8,0x71c1,0xe382, 109 | 0xd5d2,0xa3b5,0x4f7b,0x9ef6,0x35fd,0x6bfa,0xd7f4,0xa7f9, 110 | 0xf54a,0xe285,0xcd1b,0x9227,0x2c5f,0x58be,0xb17c,0x6ae9, 111 | 0xedab,0xd347,0xae9f,0x552f,0xaa5e,0x5cad,0xb95a,0x7aa5, 112 | 0x1b34,0x3668,0x6cd0,0xd9a0,0xbb51,0x7eb3,0xfd66,0xf2dd, 113 | 0x77bc,0xef78,0xd6e1,0xa5d3,0x43b7,0x876e,0x6cd,0xd9a, 114 | 0x7f90,0xff20,0xf651,0xe4b3,0xc177,0x8aff,0x1def,0x3bde, 115 | 0x94f6,0x21fd,0x43fa,0x87f4,0x7f9,0xff2,0x1fe4,0x3fc8, 116 | 0x922d,0x2c4b,0x5896,0xb12c,0x6a49,0xd492,0xa135,0x4a7b, 117 | 0xfa75,0xfcfb,0xf1e7,0xebdf,0xdfaf,0xb74f,0x668f,0xcd1e, 118 | 0x24d0,0x49a0,0x9340,0x2e91,0x5d22,0xba44,0x7c99,0xf932, 119 | 0xd6a9,0xa543,0x4297,0x852e,0x24d,0x49a,0x934,0x1268, 120 | 0x381d,0x703a,0xe074,0xc8f9,0x99e3,0x3bd7,0x77ae,0xef5c, 121 | 0xcb5c,0x9ea9,0x3543,0x6a86,0xd50c,0xa209,0x4c03,0x9806, 122 | 0x9822,0x3855,0x70aa,0xe154,0xcab9,0x9d63,0x32d7,0x65ae, 123 | 0x288,0x510,0xa20,0x1440,0x2880,0x5100,0xa200,0x4c11, 124 | 0x842,0x1084,0x2108,0x4210,0x8420,0x51,0xa2,0x144, 125 | 0x611e,0xc23c,0x8c69,0x10c3,0x2186,0x430c,0x8618,0x421, 126 | 0xf99e,0xfb2d,0xfe4b,0xf487,0xe11f,0xca2f,0x9c4f,0x308f, 127 | 0x7d0e,0xfa1c,0xfc29,0xf043,0xe897,0xd93f,0xba6f,0x7ccf, 128 | 0xe903,0xda17,0xbc3f,0x706f,0xe0de,0xc9ad,0x9b4b,0x3e87, 129 | 0x3272,0x64e4,0xc9c8,0x9b81,0x3f13,0x7e26,0xfc4c,0xf089, 130 | 0x50a7,0xa14e,0x4a8d,0x951a,0x2225,0x444a,0x8894,0x1939, 131 | 0xd1e5,0xabdb,0x5fa7,0xbf4e,0x768d,0xed1a,0xd225,0xac5b, 132 | 0xb072,0x68f5,0xd1ea,0xabc5,0x5f9b,0xbf36,0x767d,0xecfa, 133 | 0x5025,0xa04a,0x4885,0x910a,0x2a05,0x540a,0xa814,0x5839, 134 | 0x76ff,0xedfe,0xd3ed,0xafcb,0x5787,0xaf0e,0x560d,0xac1a, 135 | 0xf0e,0x1e1c,0x3c38,0x7870,0xf0e0,0xe9d1,0xdbb3,0xbf77, 136 | 0xe971,0xdaf3,0xbdf7,0x73ff,0xe7fe,0xc7ed,0x87cb,0x787, 137 | 0x62e7,0xc5ce,0x838d,0xf0b,0x1e16,0x3c2c,0x7858,0xf0b0, 138 | 0x93d3,0x2fb7,0x5f6e,0xbedc,0x75a9,0xeb52,0xdeb5,0xb57b, 139 | 0xe485,0xc11b,0x8a27,0x1c5f,0x38be,0x717c,0xe2f8,0xcde1, 140 | 0xd341,0xae93,0x5537,0xaa6e,0x5ccd,0xb99a,0x7b25,0xf64a, 141 | 0x535e,0xa6bc,0x4569,0x8ad2,0x1db5,0x3b6a,0x76d4,0xeda8, 142 | 0xbba8,0x7f41,0xfe82,0xf515,0xe23b,0xcc67,0x90df,0x29af, 143 | 0x29f9,0x53f2,0xa7e4,0x47d9,0x8fb2,0x1775,0x2eea,0x5dd4, 144 | 0x6a67,0xd4ce,0xa18d,0x4b0b,0x9616,0x243d,0x487a,0x90f4, 145 | 0x17d3,0x2fa6,0x5f4c,0xbe98,0x7521,0xea42,0xdc95,0xb13b, 146 | 0xe401,0xc013,0x8837,0x187f,0x30fe,0x61fc,0xc3f8,0x8fe1, 147 | 0x116d,0x22da,0x45b4,0x8b68,0x1ec1,0x3d82,0x7b04,0xf608, 148 | 0xb8f2,0x79f5,0xf3ea,0xefc5,0xd79b,0xa727,0x465f,0x8cbe, 149 | 0xd425,0xa05b,0x48a7,0x914e,0x2a8d,0x551a,0xaa34,0x5c79, 150 | 0x767b,0xecf6,0xd1fd,0xabeb,0x5fc7,0xbf8e,0x770d,0xee1a, 151 | 0xcd22,0x9255,0x2cbb,0x5976,0xb2ec,0x6dc9,0xdb92,0xbf35, 152 | 0x2dd,0x5ba,0xb74,0x16e8,0x2dd0,0x5ba0,0xb740,0x6691, 153 | 0xd6a,0x1ad4,0x35a8,0x6b50,0xd6a0,0xa551,0x42b3,0x8566, 154 | 0xcc51,0x90b3,0x2977,0x52ee,0xa5dc,0x43a9,0x8752,0x6b5, 155 | 0x43c0,0x8780,0x711,0xe22,0x1c44,0x3888,0x7110,0xe220, 156 | 0xc64f,0x848f,0x10f,0x21e,0x43c,0x878,0x10f0,0x21e0, 157 | 0xba35,0x7c7b,0xf8f6,0xf9fd,0xfbeb,0xffc7,0xf79f,0xe72f, 158 | 0x6694,0xcd28,0x9241,0x2c93,0x5926,0xb24c,0x6c89,0xd912, 159 | 0xd2cb,0xad87,0x531f,0xa63e,0x446d,0x88da,0x19a5,0x334a, 160 | 0x780d,0xf01a,0xe825,0xd85b,0xb8a7,0x795f,0xf2be,0xed6d, 161 | 0xdb9d,0xbf2b,0x7647,0xec8e,0xd10d,0xaa0b,0x5c07,0xb80e, 162 | 0x4fb7,0x9f6e,0x36cd,0x6d9a,0xdb34,0xbe79,0x74e3,0xe9c6, 163 | 0xc17b,0x8ae7,0x1ddf,0x3bbe,0x777c,0xeef8,0xd5e1,0xa3d3, 164 | 0xcd95,0x933b,0x2e67,0x5cce,0xb99c,0x7b29,0xf652,0xe4b5, 165 | 0xc3e9,0x8fc3,0x1797,0x2f2e,0x5e5c,0xbcb8,0x7161,0xe2c2, 166 | 0x7a0c,0xf418,0xe021,0xc853,0x98b7,0x397f,0x72fe,0xe5fc, 167 | 0xca16,0x9c3d,0x306b,0x60d6,0xc1ac,0x8b49,0x1e83,0x3d06, 168 | 0x757d,0xeafa,0xdde5,0xb3db,0x6fa7,0xdf4e,0xb68d,0x650b, 169 | 0xa817,0x583f,0xb07e,0x68ed,0xd1da,0xaba5,0x5f5b,0xbeb6, 170 | 0x6496,0xc92c,0x9a49,0x3c83,0x7906,0xf20c,0xec09,0xd003, 171 | 0xf1db,0xeba7,0xdf5f,0xb6af,0x654f,0xca9e,0x9d2d,0x324b, 172 | 0x68af,0xd15e,0xaaad,0x5d4b,0xba96,0x7d3d,0xfa7a,0xfce5, 173 | 0x5d95,0xbb2a,0x7e45,0xfc8a,0xf105,0xea1b,0xdc27,0xb05f, 174 | 0xc379,0x8ee3,0x15d7,0x2bae,0x575c,0xaeb8,0x5561,0xaac2, 175 | 0xee85,0xd51b,0xa227,0x4c5f,0x98be,0x396d,0x72da,0xe5b4, 176 | 0xd34b,0xae87,0x551f,0xaa3e,0x5c6d,0xb8da,0x79a5,0xf34a, 177 | 0xfc04,0xf019,0xe823,0xd857,0xb8bf,0x796f,0xf2de,0xedad, 178 | 0x46d8,0x8db0,0x1371,0x26e2,0x4dc4,0x9b88,0x3f01,0x7e02, 179 | 0x5a83,0xb506,0x621d,0xc43a,0x8065,0x8db,0x11b6,0x236c, 180 | 0xb6c9,0x6583,0xcb06,0x9e1d,0x342b,0x6856,0xd0ac,0xa949, 181 | 0x5b7b,0xb6f6,0x65fd,0xcbfa,0x9fe5,0x37db,0x6fb6,0xdf6c, 182 | 0xcd0f,0x920f,0x2c0f,0x581e,0xb03c,0x6869,0xd0d2,0xa9b5, 183 | 0xf83a,0xf865,0xf8db,0xf9a7,0xfb5f,0xfeaf,0xf54f,0xe28f, 184 | 0x9e21,0x3453,0x68a6,0xd14c,0xaa89,0x5d03,0xba06,0x7c1d, 185 | 0x3015,0x602a,0xc054,0x88b9,0x1963,0x32c6,0x658c,0xcb18, 186 | 0x471c,0x8e38,0x1461,0x28c2,0x5184,0xa308,0x4e01,0x9c02, 187 | 0xdaaa,0xbd45,0x729b,0xe536,0xc27d,0x8ceb,0x11c7,0x238e, 188 | 0xa8a,0x1514,0x2a28,0x5450,0xa8a0,0x5951,0xb2a2,0x6d55, 189 | 0x2b58,0x56b0,0xad60,0x52d1,0xa5a2,0x4355,0x86aa,0x545, 190 | 0xdee6,0xb5dd,0x63ab,0xc756,0x86bd,0x56b,0xad6,0x15ac, 191 | 0x82e6,0xddd,0x1bba,0x3774,0x6ee8,0xddd0,0xb3b1,0x6f73, 192 | 0x82ba,0xd65,0x1aca,0x3594,0x6b28,0xd650,0xa4b1,0x4173, 193 | 0x1a53,0x34a6,0x694c,0xd298,0xad21,0x5253,0xa4a6,0x415d, 194 | 0x6004,0xc008,0x8801,0x1813,0x3026,0x604c,0xc098,0x8921, 195 | 0x4644,0x8c88,0x1101,0x2202,0x4404,0x8808,0x1801,0x3002, 196 | 0x466,0x8cc,0x1198,0x2330,0x4660,0x8cc0,0x1191,0x2322, 197 | 0x634,0xc68,0x18d0,0x31a0,0x6340,0xc680,0x8511,0x233, 198 | 0x77a1,0xef42,0xd695,0xa53b,0x4267,0x84ce,0x18d,0x31a, 199 | 0xb4f4,0x61f9,0xc3f2,0x8ff5,0x17fb,0x2ff6,0x5fec,0xbfd8, 200 | 0xb11f,0x6a2f,0xd45e,0xa0ad,0x494b,0x9296,0x2d3d,0x5a7a, 201 | 0xe8c7,0xd99f,0xbb2f,0x7e4f,0xfc9e,0xf12d,0xea4b,0xdc87, 202 | 0xb25b,0x6ca7,0xd94e,0xba8d,0x7d0b,0xfa16,0xfc3d,0xf06b, 203 | 0xece4,0xd1d9,0xaba3,0x5f57,0xbeae,0x754d,0xea9a,0xdd25, 204 | 0xa1c6,0x4b9d,0x973a,0x2665,0x4cca,0x9994,0x3b39,0x7672, 205 | 0xa39b,0x4f27,0x9e4e,0x348d,0x691a,0xd234,0xac79,0x50e3, 206 | 0x2af9,0x55f2,0xabe4,0x5fd9,0xbfb2,0x7775,0xeeea,0xd5c5, 207 | 0x6a64,0xd4c8,0xa181,0x4b13,0x9626,0x245d,0x48ba,0x9174, 208 | 0x2548,0x4a90,0x9520,0x2251,0x44a2,0x8944,0x1a99,0x3532, 209 | 0xce69,0x94c3,0x2197,0x432e,0x865c,0x4a9,0x952,0x12a4, 210 | 0xfe09,0xf403,0xe017,0xc83f,0x986f,0x38cf,0x719e,0xe33c, 211 | 0x9d3f,0x326f,0x64de,0xc9bc,0x9b69,0x3ec3,0x7d86,0xfb0c, 212 | 0xc9e9,0x9bc3,0x3f97,0x7f2e,0xfe5c,0xf4a9,0xe143,0xca97, 213 | 0x7a06,0xf40c,0xe009,0xc803,0x9817,0x383f,0x707e,0xe0fc, 214 | 0x654c,0xca98,0x9d21,0x3253,0x64a6,0xc94c,0x9a89,0x3d03, 215 | 0x880d,0x180b,0x3016,0x602c,0xc058,0x88a1,0x1953,0x32a6, 216 | 0xdb6d,0xbecb,0x7587,0xeb0e,0xde0d,0xb40b,0x6007,0xc00e, 217 | 0xb838,0x7861,0xf0c2,0xe995,0xdb3b,0xbe67,0x74df,0xe9be, 218 | 0xbd73,0x72f7,0xe5ee,0xc3cd,0x8f8b,0x1707,0x2e0e,0x5c1c, 219 | 0x41a1,0x8342,0xe95,0x1d2a,0x3a54,0x74a8,0xe950,0xdab1, 220 | 0xb4c2,0x6195,0xc32a,0x8e45,0x149b,0x2936,0x526c,0xa4d8, 221 | 0xe5aa,0xc345,0x8e9b,0x1527,0x2a4e,0x549c,0xa938,0x5a61, 222 | 0xab5,0x156a,0x2ad4,0x55a8,0xab50,0x5eb1,0xbd62,0x72d5, 223 | 0xe22c,0xcc49,0x9083,0x2917,0x522e,0xa45c,0x40a9,0x8152, 224 | 0xeb8c,0xdf09,0xb603,0x6417,0xc82e,0x984d,0x388b,0x7116, 225 | 0x4e8f,0x9d1e,0x322d,0x645a,0xc8b4,0x9979,0x3ae3,0x75c6, 226 | 0x7cb1,0xf962,0xfad5,0xfdbb,0xf367,0xeedf,0xd5af,0xa34f, 227 | 0xa47e,0x40ed,0x81da,0xba5,0x174a,0x2e94,0x5d28,0xba50, 228 | 0x9a5d,0x3cab,0x7956,0xf2ac,0xed49,0xd283,0xad17,0x523f, 229 | 0x89fa,0x1be5,0x37ca,0x6f94,0xdf28,0xb641,0x6493,0xc926, 230 | 0x585c,0xb0b8,0x6961,0xd2c2,0xad95,0x533b,0xa676,0x44fd, 231 | 0x98b1,0x3973,0x72e6,0xe5cc,0xc389,0x8f03,0x1617,0x2c2e, 232 | 0xa49a,0x4125,0x824a,0xc85,0x190a,0x3214,0x6428,0xc850, 233 | 0x3b77,0x76ee,0xeddc,0xd3a9,0xaf43,0x5697,0xad2e,0x524d, 234 | 0x703,0xe06,0x1c0c,0x3818,0x7030,0xe060,0xc8d1,0x99b3, 235 | 0x329c,0x6538,0xca70,0x9cf1,0x31f3,0x63e6,0xc7cc,0x8789, 236 | 0x5ed7,0xbdae,0x734d,0xe69a,0xc525,0x825b,0xca7,0x194e, 237 | 0xa26c,0x4cc9,0x9992,0x3b35,0x766a,0xecd4,0xd1b9,0xab63, 238 | 0xa9c8,0x5b81,0xb702,0x6615,0xcc2a,0x9045,0x289b,0x5136, 239 | 0x4aed,0x95da,0x23a5,0x474a,0x8e94,0x1539,0x2a72,0x54e4, 240 | 0x3ca1,0x7942,0xf284,0xed19,0xd223,0xac57,0x50bf,0xa17e, 241 | 0xb4bf,0x616f,0xc2de,0x8dad,0x134b,0x2696,0x4d2c,0x9a58, 242 | 0x4dc8,0x9b90,0x3f31,0x7e62,0xfcc4,0xf199,0xeb23,0xde57, 243 | 0x4a09,0x9412,0x2035,0x406a,0x80d4,0x9b9,0x1372,0x26e4, 244 | 0x9d8b,0x3307,0x660e,0xcc1c,0x9029,0x2843,0x5086,0xa10c, 245 | 0x3a46,0x748c,0xe918,0xda21,0xbc53,0x70b7,0xe16e,0xcacd, 246 | 0x2708,0x4e10,0x9c20,0x3051,0x60a2,0xc144,0x8a99,0x1d23, 247 | 0x8c6f,0x10cf,0x219e,0x433c,0x8678,0x4e1,0x9c2,0x1384, 248 | 0x9b7d,0x3eeb,0x7dd6,0xfbac,0xff49,0xf683,0xe517,0xc23f, 249 | 0xa8f9,0x59e3,0xb3c6,0x6f9d,0xdf3a,0xb665,0x64db,0xc9b6, 250 | 0x6ae6,0xd5cc,0xa389,0x4f03,0x9e06,0x341d,0x683a,0xd074, 251 | 0x8252,0xcb5,0x196a,0x32d4,0x65a8,0xcb50,0x9eb1,0x3573, 252 | 0x7115,0xe22a,0xcc45,0x909b,0x2927,0x524e,0xa49c,0x4129, 253 | 0x475d,0x8eba,0x1565,0x2aca,0x5594,0xab28,0x5e41,0xbc82, 254 | 0x8927,0x1a5f,0x34be,0x697c,0xd2f8,0xade1,0x53d3,0xa7a6, 255 | 0x5534,0xaa68,0x5cc1,0xb982,0x7b15,0xf62a,0xe445,0xc09b, 256 | 0x77f2,0xefe4,0xd7d9,0xa7a3,0x4757,0x8eae,0x154d,0x2a9a, 257 | 0xd4ea,0xa1c5,0x4b9b,0x9736,0x267d,0x4cfa,0x99f4,0x3bf9, 258 | 0x4880,0x9100,0x2a11,0x5422,0xa844,0x5899,0xb132,0x6a75, 259 | 0x8440,0x91,0x122,0x244,0x488,0x910,0x1220,0x2440, 260 | 0x4280,0x8500,0x211,0x422,0x844,0x1088,0x2110,0x4220, 261 | 0x844a,0x85,0x10a,0x214,0x428,0x850,0x10a0,0x2140, 262 | 0xedda,0xd3a5,0xaf5b,0x56a7,0xad4e,0x528d,0xa51a,0x4225, 263 | 0x793a,0xf274,0xecf9,0xd1e3,0xabd7,0x5fbf,0xbf7e,0x76ed, 264 | 0x9ea0,0x3551,0x6aa2,0xd544,0xa299,0x4d23,0x9a46,0x3c9d, 265 | 0xa594,0x4339,0x8672,0x4f5,0x9ea,0x13d4,0x27a8,0x4f50, 266 | 0xd208,0xac01,0x5013,0xa026,0x485d,0x90ba,0x2965,0x52ca, 267 | 0x8c9a,0x1125,0x224a,0x4494,0x8928,0x1a41,0x3482,0x6904, 268 | 0x3b5f,0x76be,0xed7c,0xd2e9,0xadc3,0x5397,0xa72e,0x464d, 269 | 0xaa49,0x5c83,0xb906,0x7a1d,0xf43a,0xe065,0xc8db,0x99a7, 270 | 0xdf6f,0xb6cf,0x658f,0xcb1e,0x9e2d,0x344b,0x6896,0xd12c, 271 | 0x9b2e,0x3e4d,0x7c9a,0xf934,0xfa79,0xfce3,0xf1d7,0xebbf, 272 | 0xc8e7,0x99df,0x3baf,0x775e,0xeebc,0xd569,0xa2c3,0x4d97, 273 | 0x9379,0x2ee3,0x5dc6,0xbb8c,0x7f09,0xfe12,0xf435,0xe07b, 274 | 0xeed5,0xd5bb,0xa367,0x4edf,0x9dbe,0x336d,0x66da,0xcdb4, 275 | 0x81ce,0xb8d,0x171a,0x2e34,0x5c68,0xb8d0,0x79b1,0xf362, 276 | 0x2ff3,0x5fe6,0xbfcc,0x7789,0xef12,0xd635,0xa47b,0x40e7, 277 | 0xc53b,0x8267,0xcdf,0x19be,0x337c,0x66f8,0xcdf0,0x93f1, 278 | 0x8f95,0x173b,0x2e76,0x5cec,0xb9d8,0x7ba1,0xf742,0xe695, 279 | 0xc3ab,0x8f47,0x169f,0x2d3e,0x5a7c,0xb4f8,0x61e1,0xc3c2, 280 | 0x1b1a,0x3634,0x6c68,0xd8d0,0xb9b1,0x7b73,0xf6e6,0xe5dd, 281 | 0xbfc0,0x7791,0xef22,0xd655,0xa4bb,0x4167,0x82ce,0xd8d, 282 | 0xc6b3,0x8577,0x2ff,0x5fe,0xbfc,0x17f8,0x2ff0,0x5fe0, 283 | 0x87d6,0x7bd,0xf7a,0x1ef4,0x3de8,0x7bd0,0xf7a0,0xe751, 284 | 0xb33c,0x6e69,0xdcd2,0xb1b5,0x6b7b,0xd6f6,0xa5fd,0x43eb, 285 | 0xfb5c,0xfea9,0xf543,0xe297,0xcd3f,0x926f,0x2ccf,0x599e, 286 | 0x9812,0x3835,0x706a,0xe0d4,0xc9b9,0x9b63,0x3ed7,0x7dae, 287 | 0x330b,0x6616,0xcc2c,0x9049,0x2883,0x5106,0xa20c,0x4c09, 288 | 0xbee0,0x75d1,0xeba2,0xdf55,0xb6bb,0x6567,0xcace,0x9d8d, 289 | 0xe7b0,0xc771,0x86f3,0x5f7,0xbee,0x17dc,0x2fb8,0x5f70, 290 | 0xb56c,0x62c9,0xc592,0x8335,0xe7b,0x1cf6,0x39ec,0x73d8, 291 | 0xa9df,0x5baf,0xb75e,0x66ad,0xcd5a,0x92a5,0x2d5b,0x5ab6, 292 | 0x2ed3,0x5da6,0xbb4c,0x7e89,0xfd12,0xf235,0xec7b,0xd0e7, 293 | 0xe438,0xc061,0x88d3,0x19b7,0x336e,0x66dc,0xcdb8,0x9361, 294 | 0xbd2f,0x724f,0xe49e,0xc12d,0x8a4b,0x1c87,0x390e,0x721c 295 | }; 296 | -------------------------------------------------------------------------------- /output.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #ifdef HAVE_LIBACARS 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #endif 17 | #include "acarsdec.h" 18 | #include "cJSON.h" 19 | #include "output.h" 20 | 21 | extern int label_filter(char *lbl); 22 | 23 | extern int inmode; 24 | extern char *idstation; 25 | 26 | static FILE *fdout; 27 | 28 | static char *jsonbuf=NULL; 29 | #define JSONBUFLEN 30000 30 | 31 | #define IS_DOWNLINK_BLK(bid) ((bid) >= '0' && (bid) <= '9') 32 | 33 | #ifdef HAVE_LIBACARS 34 | #define LA_ACARS_REASM_TABLE_CLEANUP_INTERVAL 1000 35 | static la_reasm_ctx *reasm_ctx = NULL; 36 | // ACARS reassembly timeouts 37 | static struct timeval const acars_reasm_timeout_downlink = { .tv_sec = 660, .tv_usec = 0 }; // VGT4 38 | static struct timeval const acars_reasm_timeout_uplink = { .tv_sec = 90, .tv_usec = 0 }; // VAT4 39 | 40 | typedef struct { 41 | char *addr, *label, *msn; 42 | } acars_key; 43 | 44 | static uint32_t acars_key_hash(void const *key) { 45 | acars_key *k = (acars_key *)key; 46 | uint32_t h = la_hash_string(k->addr, LA_HASH_INIT); 47 | h = la_hash_string(k->label, h); 48 | h = la_hash_string(k->msn, h); 49 | return h; 50 | } 51 | 52 | static bool acars_key_compare(void const *key1, void const *key2) { 53 | acars_key *k1 = (acars_key *)key1; 54 | acars_key *k2 = (acars_key *)key2; 55 | return (!strcmp(k1->addr, k2->addr) && 56 | !strcmp(k1->label, k2->label) && 57 | !strcmp(k1->msn, k2->msn)); 58 | } 59 | 60 | static void acars_key_destroy(void *ptr) { 61 | if(ptr == NULL) { 62 | return; 63 | } 64 | acars_key *key = (acars_key *)ptr; 65 | free(key->addr); 66 | free(key->label); 67 | free(key->msn); 68 | free(key); 69 | } 70 | 71 | static void *acars_tmp_key_get(void const *msg) { 72 | acarsmsg_t *amsg = (acarsmsg_t *)msg; 73 | acars_key *key = calloc(1, sizeof(acars_key)); 74 | if(key == NULL) 75 | return NULL; 76 | key->addr = amsg->addr; 77 | key->label = amsg->label; 78 | key->msn = amsg->msn; 79 | return (void *)key; 80 | } 81 | 82 | static void *acars_key_get(void const *msg) { 83 | acarsmsg_t *amsg = (acarsmsg_t *)msg; 84 | acars_key *key = calloc(1, sizeof(acars_key)); 85 | if(key == NULL) 86 | return NULL; 87 | key->addr = strdup(amsg->addr); 88 | key->label = strdup(amsg->label); 89 | key->msn = strdup(amsg->msn); 90 | return (void *)key; 91 | } 92 | 93 | static la_reasm_table_funcs acars_reasm_funcs = { 94 | .get_key = acars_key_get, 95 | .get_tmp_key = acars_tmp_key_get, 96 | .hash_key = acars_key_hash, 97 | .compare_keys = acars_key_compare, 98 | .destroy_key = acars_key_destroy 99 | }; 100 | 101 | #endif // HAVE_LIBACARS 102 | 103 | static inline void cls(void) 104 | { 105 | printf("\x1b[H\x1b[2J"); 106 | } 107 | 108 | int initOutput(char *logfilename, char *Rawaddr) 109 | { 110 | if (outtype != OUTTYPE_NONE && logfilename) { 111 | if((fdout=Fileoutinit(logfilename)) == NULL) 112 | return -1; 113 | } else { 114 | fdout = stdout; 115 | } 116 | 117 | if (Rawaddr) 118 | if(Netoutinit(Rawaddr)) 119 | return -1; 120 | 121 | if (outtype == OUTTYPE_MONITOR ) { 122 | verbose=0; 123 | cls(); 124 | fflush(stdout); 125 | } 126 | 127 | if (outtype == OUTTYPE_JSON || outtype == OUTTYPE_ROUTEJSON || netout==NETLOG_JSON || netout==NETLOG_MQTT ) { 128 | jsonbuf = malloc(JSONBUFLEN+1); 129 | if(jsonbuf == NULL) 130 | return -1; 131 | } 132 | #ifdef HAVE_LIBACARS 133 | reasm_ctx = skip_reassembly ? NULL : la_reasm_ctx_new(); 134 | #endif 135 | return 0; 136 | } 137 | 138 | static void printtime(struct timeval tv) 139 | { 140 | struct tm tmp; 141 | 142 | gmtime_r(&(tv.tv_sec), &tmp); 143 | 144 | fprintf(fdout, "%02d:%02d:%02d.%03ld", 145 | tmp.tm_hour, tmp.tm_min, tmp.tm_sec, tv.tv_usec/1000); 146 | } 147 | 148 | static void printdate(struct timeval tv) 149 | { 150 | struct tm tmp; 151 | 152 | if (tv.tv_sec + tv.tv_usec == 0) 153 | return; 154 | 155 | gmtime_r(&(tv.tv_sec), &tmp); 156 | 157 | fprintf(fdout, "%02d/%02d/%04d ", 158 | tmp.tm_mday, tmp.tm_mon + 1, tmp.tm_year + 1900); 159 | printtime(tv); 160 | } 161 | 162 | static void printmsg(acarsmsg_t * msg, int chn, struct timeval tv) 163 | { 164 | oooi_t oooi; 165 | 166 | #if defined (WITH_RTL) || defined (WITH_AIR) || defined (WITH_SOAPY) 167 | if (inmode >= 3) 168 | fprintf(fdout, "\n[#%1d (F:%3.3f L:%+5.1f E:%1d) ", chn + 1, 169 | channel[chn].Fr / 1000000.0, msg->lvl, msg->err); 170 | else 171 | #endif 172 | fprintf(fdout, "\n[#%1d (L:%+5.1f E:%1d) ", chn + 1, msg->lvl, msg->err); 173 | 174 | if (inmode != 2) 175 | printdate(tv); 176 | 177 | fprintf(fdout, " --------------------------------\n"); 178 | fprintf(fdout, "Mode : %1c ", msg->mode); 179 | fprintf(fdout, "Label : %2s ", msg->label); 180 | 181 | if(msg->bid) { 182 | fprintf(fdout, "Id : %1c ", msg->bid); 183 | if(msg->ack=='!') fprintf(fdout, "Nak\n"); else fprintf(fdout, "Ack : %1c\n", msg->ack); 184 | fprintf(fdout, "Aircraft reg: %s ", msg->addr); 185 | if(IS_DOWNLINK_BLK(msg->bid)) { 186 | fprintf(fdout, "Flight id: %s\n", msg->fid); 187 | fprintf(fdout, "No: %4s", msg->no); 188 | } 189 | if(msg->sublabel[0] != '\0') { 190 | fprintf(fdout, "\nSublabel: %s", msg->sublabel); 191 | if(msg->mfi[0] != '\0') { 192 | fprintf(fdout, " MFI: %s", msg->mfi); 193 | } 194 | } 195 | #ifdef HAVE_LIBACARS 196 | if (!skip_reassembly) { 197 | fprintf(fdout, "\nReassembly: %s", la_reasm_status_name_get(msg->reasm_status)); 198 | } 199 | #endif 200 | } 201 | 202 | fprintf(fdout, "\n"); 203 | if(msg->txt[0]) fprintf(fdout, "%s\n", msg->txt); 204 | if (msg->be == 0x17) fprintf(fdout, "ETB\n"); 205 | 206 | if(DecodeLabel(msg,&oooi)) { 207 | fprintf(fdout, "##########################\n"); 208 | if(oooi.da[0]) fprintf(fdout,"Destination Airport : %s\n",oooi.da); 209 | if(oooi.sa[0]) fprintf(fdout,"Departure Airport : %s\n",oooi.sa); 210 | if(oooi.eta[0]) fprintf(fdout,"Estimation Time of Arrival : %s\n",oooi.eta); 211 | if(oooi.gout[0]) fprintf(fdout,"Gate out Time : %s\n",oooi.gout); 212 | if(oooi.gin[0]) fprintf(fdout,"Gate in Time : %s\n",oooi.gin); 213 | if(oooi.woff[0]) fprintf(fdout,"Wheels off Tme : %s\n",oooi.woff); 214 | if(oooi.won[0]) fprintf(fdout,"Wheels on Time : %s\n",oooi.won); 215 | } 216 | #ifdef HAVE_LIBACARS 217 | if(msg->decoded_tree != NULL) { 218 | la_vstring *vstr = la_proto_tree_format_text(NULL, msg->decoded_tree); 219 | fprintf(fdout, "%s\n", vstr->str); 220 | la_vstring_destroy(vstr, true); 221 | } 222 | #endif 223 | fflush(fdout); 224 | } 225 | 226 | 227 | static int buildjson(acarsmsg_t * msg, int chn, struct timeval tv) 228 | { 229 | 230 | oooi_t oooi; 231 | #if defined (WITH_RTL) || defined (WITH_AIR) || defined (WITH_SOAPY) 232 | float freq = channel[chn].Fr / 1000000.0; 233 | #else 234 | float freq = 0; 235 | #endif 236 | cJSON *json_obj; 237 | int ok = 0; 238 | char convert_tmp[8]; 239 | 240 | json_obj = cJSON_CreateObject(); 241 | if (json_obj == NULL) 242 | return ok; 243 | 244 | double t = (double)tv.tv_sec + ((double)tv.tv_usec)/1e6; 245 | cJSON_AddNumberToObject(json_obj, "timestamp", t); 246 | if(idstation[0]) cJSON_AddStringToObject(json_obj, "station_id", idstation); 247 | cJSON_AddNumberToObject(json_obj, "channel", chn); 248 | snprintf(convert_tmp, sizeof(convert_tmp), "%3.3f", freq); 249 | cJSON_AddRawToObject(json_obj, "freq", convert_tmp); 250 | snprintf(convert_tmp, sizeof(convert_tmp), "%2.1f", msg->lvl); 251 | cJSON_AddRawToObject(json_obj, "level", convert_tmp); 252 | cJSON_AddNumberToObject(json_obj, "error", msg->err); 253 | snprintf(convert_tmp, sizeof(convert_tmp), "%c", msg->mode); 254 | cJSON_AddStringToObject(json_obj, "mode", convert_tmp); 255 | cJSON_AddStringToObject(json_obj, "label", msg->label); 256 | 257 | if(msg->bid) { 258 | snprintf(convert_tmp, sizeof(convert_tmp), "%c", msg->bid); 259 | cJSON_AddStringToObject(json_obj, "block_id", convert_tmp); 260 | 261 | if(msg->ack == '!') { 262 | cJSON_AddFalseToObject(json_obj, "ack"); 263 | } else { 264 | snprintf(convert_tmp, sizeof(convert_tmp), "%c", msg->ack); 265 | cJSON_AddStringToObject(json_obj, "ack", convert_tmp); 266 | } 267 | 268 | cJSON_AddStringToObject(json_obj, "tail", msg->addr); 269 | if(IS_DOWNLINK_BLK(msg->bid)) { 270 | cJSON_AddStringToObject(json_obj, "flight", msg->fid); 271 | cJSON_AddStringToObject(json_obj, "msgno", msg->no); 272 | } 273 | } 274 | if(msg->txt[0]) 275 | cJSON_AddStringToObject(json_obj, "text", msg->txt); 276 | 277 | if (msg->be == 0x17) 278 | cJSON_AddTrueToObject(json_obj, "end"); 279 | 280 | if(DecodeLabel(msg, &oooi)) { 281 | if(oooi.sa[0]) 282 | cJSON_AddStringToObject(json_obj, "depa", oooi.sa); 283 | if(oooi.da[0]) 284 | cJSON_AddStringToObject(json_obj, "dsta", oooi.da); 285 | if(oooi.eta[0]) 286 | cJSON_AddStringToObject(json_obj, "eta", oooi.eta); 287 | if(oooi.gout[0]) 288 | cJSON_AddStringToObject(json_obj, "gtout", oooi.gout); 289 | if(oooi.gin[0]) 290 | cJSON_AddStringToObject(json_obj, "gtin", oooi.gin); 291 | if(oooi.woff[0]) 292 | cJSON_AddStringToObject(json_obj, "wloff", oooi.woff); 293 | if(oooi.won[0]) 294 | cJSON_AddStringToObject(json_obj, "wlin", oooi.won); 295 | } 296 | 297 | if (msg->sublabel[0] != '\0') { 298 | cJSON_AddStringToObject(json_obj, "sublabel", msg->sublabel); 299 | if (msg->mfi[0] != '\0') { 300 | cJSON_AddStringToObject(json_obj, "mfi", msg->mfi); 301 | } 302 | } 303 | #ifdef HAVE_LIBACARS 304 | if (!skip_reassembly) { 305 | cJSON_AddStringToObject(json_obj, "assstat", la_reasm_status_name_get(msg->reasm_status)); 306 | } 307 | if(msg->decoded_tree != NULL) { 308 | la_vstring *vstr = la_proto_tree_format_json(NULL, msg->decoded_tree); 309 | cJSON_AddRawToObject(json_obj, "libacars", vstr->str); 310 | la_vstring_destroy(vstr, true); 311 | } 312 | #endif 313 | 314 | cJSON *app_info = cJSON_AddObjectToObject(json_obj, "app"); 315 | if (app_info) { 316 | cJSON_AddStringToObject(app_info, "name", "acarsdec"); 317 | cJSON_AddStringToObject(app_info, "ver", ACARSDEC_VERSION); 318 | } 319 | 320 | 321 | ok = cJSON_PrintPreallocated(json_obj, jsonbuf, JSONBUFLEN, 0); 322 | cJSON_Delete(json_obj); 323 | return ok; 324 | } 325 | 326 | 327 | static void printoneline(acarsmsg_t * msg, int chn, struct timeval tv) 328 | { 329 | char txt[60]; 330 | char *pstr; 331 | 332 | strncpy(txt, msg->txt, 59); 333 | txt[59] = 0; 334 | for (pstr = txt; *pstr != 0; pstr++) 335 | if (*pstr == '\n' || *pstr == '\r') 336 | *pstr = ' '; 337 | 338 | fprintf(fdout, "#%1d (L:%+5.1f E:%1d) ", chn + 1, msg->lvl, msg->err); 339 | 340 | if (inmode != 2) 341 | printdate(tv); 342 | fprintf(fdout, " %7s %6s %1c %2s %4s ", msg->addr, msg->fid, msg->mode, msg->label, msg->no); 343 | fprintf(fdout, "%s", txt); 344 | fprintf(fdout, "\n"); 345 | fflush(fdout); 346 | } 347 | 348 | typedef struct flight_s flight_t; 349 | struct flight_s { 350 | flight_t *next; 351 | char addr[8]; 352 | char fid[7]; 353 | struct timeval ts,tl; 354 | int chm; 355 | int nbm; 356 | int rt; 357 | oooi_t oooi; 358 | }; 359 | static flight_t *flight_head=NULL; 360 | 361 | static flight_t *addFlight(acarsmsg_t * msg, int chn, struct timeval tv) 362 | { 363 | flight_t *fl,*fld,*flp; 364 | oooi_t oooi; 365 | 366 | fl=flight_head; 367 | flp=NULL; 368 | while(fl) { 369 | if(strcmp(msg->addr,fl->addr)==0) break; 370 | flp=fl; 371 | fl=fl->next; 372 | } 373 | 374 | if(fl==NULL) { 375 | fl=calloc(1,sizeof(flight_t)); 376 | if(fl==NULL) 377 | return (NULL); 378 | strncpy(fl->addr,msg->addr,8); 379 | fl->nbm=0; 380 | fl->ts=tv; 381 | fl->chm=0; 382 | fl->rt=0; 383 | fl->next=NULL; 384 | } 385 | 386 | strncpy(fl->fid,msg->fid,7); 387 | fl->tl=tv; 388 | fl->chm|=(1<nbm+=1; 390 | 391 | if(DecodeLabel(msg,&oooi)) { 392 | if(oooi.da[0]) memcpy(fl->oooi.da,oooi.da,5); 393 | if(oooi.sa[0]) memcpy(fl->oooi.sa,oooi.sa,5); 394 | if(oooi.eta[0]) memcpy(fl->oooi.eta,oooi.eta,5); 395 | if(oooi.gout[0]) memcpy(fl->oooi.gout,oooi.gout,5); 396 | if(oooi.gin[0]) memcpy(fl->oooi.gin,oooi.gin,5); 397 | if(oooi.woff[0]) memcpy(fl->oooi.woff,oooi.woff,5); 398 | if(oooi.won[0]) memcpy(fl->oooi.won,oooi.won,5); 399 | } 400 | 401 | if(flp) { 402 | flp->next=fl->next; 403 | fl->next=flight_head; 404 | } 405 | flight_head=fl; 406 | 407 | flp=NULL;fld=fl; 408 | while(fld) { 409 | if(fld->tl.tv_sec<(tv.tv_sec-mdly)) { 410 | if(flp) { 411 | flp->next=fld->next; 412 | free(fld); 413 | fld=flp->next; 414 | } else { 415 | flight_head=fld->next; 416 | free(fld); 417 | fld=flight_head; 418 | } 419 | } else { 420 | flp=fld; 421 | fld=fld->next; 422 | } 423 | } 424 | 425 | return(fl); 426 | } 427 | 428 | static int routejson(flight_t *fl,struct timeval tv) 429 | { 430 | if(fl==NULL) 431 | return 0; 432 | 433 | if(fl->rt==0 && fl->fid[0] && fl->oooi.sa[0] && fl->oooi.da[0]) { 434 | 435 | cJSON *json_obj; 436 | int ok; 437 | 438 | json_obj = cJSON_CreateObject(); 439 | if (json_obj == NULL) 440 | return 0; 441 | 442 | double t = (double)tv.tv_sec + ((double)tv.tv_usec)/1e6; 443 | cJSON_AddNumberToObject(json_obj, "timestamp", t); 444 | if(idstation[0]) cJSON_AddStringToObject(json_obj, "station_id", idstation); 445 | cJSON_AddStringToObject(json_obj, "flight", fl->fid); 446 | cJSON_AddStringToObject(json_obj, "depa", fl->oooi.sa); 447 | cJSON_AddStringToObject(json_obj, "dsta", fl->oooi.da); 448 | 449 | ok = cJSON_PrintPreallocated(json_obj, jsonbuf, JSONBUFLEN, 0); 450 | cJSON_Delete(json_obj); 451 | 452 | fl->rt=ok; 453 | return ok; 454 | } else 455 | return 0; 456 | } 457 | 458 | static void printmonitor(acarsmsg_t * msg, int chn, struct timeval tv) 459 | { 460 | flight_t *fl; 461 | 462 | cls(); 463 | 464 | printf(" Acarsdec monitor "); printtime(tv); 465 | printf("\n Aircraft Flight Nb Channels First DEP ARR ETA\n"); 466 | 467 | fl=flight_head; 468 | while(fl) { 469 | int i; 470 | 471 | printf(" %-8s %-7s %3d ", fl->addr, fl->fid,fl->nbm); 472 | for(i=0;ichm&(1<ts); 475 | if(fl->oooi.sa[0]) printf(" %4s ",fl->oooi.sa); else printf(" "); 476 | if(fl->oooi.da[0]) printf(" %4s ",fl->oooi.da); else printf(" "); 477 | if(fl->oooi.eta[0]) printf(" %4s ",fl->oooi.eta); else printf(" "); 478 | printf("\n"); 479 | 480 | fl=fl->next; 481 | } 482 | 483 | fflush(stdout); 484 | } 485 | 486 | void outputmsg(const msgblk_t * blk) 487 | { 488 | acarsmsg_t msg; 489 | int i, j, k; 490 | int jok=0; 491 | int outflg=0; 492 | flight_t *fl; 493 | 494 | /* fill msg struct */ 495 | memset(&msg, 0, sizeof(msg)); 496 | msg.lvl = blk->lvl; 497 | msg.err = blk->err; 498 | 499 | k = 0; 500 | msg.mode = blk->txt[k]; 501 | k++; 502 | 503 | for (i = 0, j = 0; i < 7; i++, k++) { 504 | if (blk->txt[k] != '.') { 505 | msg.addr[j] = blk->txt[k]; 506 | j++; 507 | } 508 | } 509 | msg.addr[j] = '\0'; 510 | 511 | /* ACK/NAK */ 512 | msg.ack = blk->txt[k]; 513 | if(msg.ack == 0x15) // NAK is nonprintable 514 | msg.ack = '!'; 515 | k++; 516 | 517 | msg.label[0] = blk->txt[k]; 518 | k++; 519 | msg.label[1] = blk->txt[k]; 520 | if(msg.label[1]==0x7f) msg.label[1]='d'; 521 | k++; 522 | msg.label[2] = '\0'; 523 | 524 | msg.bid = blk->txt[k]; 525 | k++; 526 | 527 | bool down = IS_DOWNLINK_BLK(msg.bid); 528 | #ifdef HAVE_LIBACARS 529 | la_msg_dir msg_dir = down ? LA_MSG_DIR_AIR2GND : LA_MSG_DIR_GND2AIR; 530 | msg.reasm_status = LA_REASM_SKIPPED; // default value (valid for message with empty text) 531 | #endif 532 | 533 | /* txt start */ 534 | msg.bs = blk->txt[k]; 535 | k++; 536 | 537 | if (airflt && !down) 538 | return; 539 | if(label_filter(msg.label)==0) 540 | return; 541 | 542 | /* txt end */ 543 | msg.be = blk->txt[blk->len - 1]; 544 | 545 | if (msg.bs != 0x03) { 546 | if (down) { 547 | /* message no */ 548 | for (i = 0; i < 4 && k < blk->len - 1; i++, k++) { 549 | msg.no[i] = blk->txt[k]; 550 | } 551 | msg.no[i] = '\0'; 552 | #ifdef HAVE_LIBACARS 553 | /* The 3-char prefix is used in reassembly hash table key, so we need */ 554 | /* to store the MSN separately as prefix and seq character. */ 555 | for (i = 0; i < 3; i++) 556 | msg.msn[i] = msg.no[i]; 557 | msg.msn[3] = '\0'; 558 | msg.msn_seq = msg.no[3]; 559 | #endif 560 | /* Flight id */ 561 | for (i = 0; i < 6 && k < blk->len - 1; i++, k++) { 562 | msg.fid[i] = blk->txt[k]; 563 | } 564 | msg.fid[i] = '\0'; 565 | 566 | outflg=1; 567 | } 568 | int txt_len = blk->len - k - 1; 569 | #ifdef HAVE_LIBACARS 570 | 571 | // Extract sublabel and MFI if present 572 | int offset = la_acars_extract_sublabel_and_mfi(msg.label, msg_dir, 573 | blk->txt + k, txt_len, msg.sublabel, msg.mfi); 574 | if(offset > 0) { 575 | k += offset; 576 | txt_len -= offset; 577 | } 578 | 579 | la_reasm_table *acars_rtable = NULL; 580 | if(msg.bid != 0 && reasm_ctx != NULL) { // not a squitter && reassembly engine is enabled 581 | acars_rtable = la_reasm_table_lookup(reasm_ctx, &la_DEF_acars_message); 582 | if(acars_rtable == NULL) { 583 | acars_rtable = la_reasm_table_new(reasm_ctx, &la_DEF_acars_message, 584 | acars_reasm_funcs, LA_ACARS_REASM_TABLE_CLEANUP_INTERVAL); 585 | } 586 | 587 | // The sequence number at which block id wraps at. 588 | // - downlinks: none (MSN always goes from 'A' up to 'P') 589 | // - uplinks: 590 | // - for VHF Category A (mode=2): wraps after block id 'Z' 591 | // - for VHF Category B (mode!=2): wraps after block id 'W' 592 | // (blocks 'X'-'Z' are reserved for empty ACKs) 593 | 594 | int seq_num_wrap = SEQ_WRAP_NONE; 595 | if(!down) 596 | seq_num_wrap = msg.mode == '2' ? 'Z' + 1 - 'A' : 'X' - 'A'; 597 | 598 | msg.reasm_status = la_reasm_fragment_add(acars_rtable, 599 | &(la_reasm_fragment_info){ 600 | .msg_info = &msg, 601 | .msg_data = (uint8_t *)(blk->txt + k), 602 | .msg_data_len = txt_len, 603 | .total_pdu_len = 0, // not used 604 | .seq_num = down ? msg.msn_seq - 'A' : msg.bid - 'A', 605 | .seq_num_first = down ? 0 : SEQ_FIRST_NONE, 606 | .seq_num_wrap = seq_num_wrap, 607 | .is_final_fragment = msg.be != 0x17, // ETB means "more fragments" 608 | .rx_time = blk->tv, 609 | .reasm_timeout = down ? acars_reasm_timeout_downlink : acars_reasm_timeout_uplink 610 | }); 611 | } 612 | uint8_t *reassembled_msg = NULL; 613 | if(msg.reasm_status == LA_REASM_COMPLETE && 614 | la_reasm_payload_get(acars_rtable, &msg, &reassembled_msg) > 0) { 615 | // reassembled_msg is a newly allocated byte buffer, which is guaranteed to 616 | // be NULL-terminated, so we can cast it to char * directly. 617 | msg.txt = (char *)reassembled_msg; 618 | } else { 619 | #endif // HAVE_LIBACARS 620 | msg.txt = calloc(txt_len + 1, sizeof(char)); 621 | if(msg.txt && txt_len > 0) { 622 | memcpy(msg.txt, blk->txt + k, txt_len); 623 | } 624 | #ifdef HAVE_LIBACARS 625 | } 626 | #endif 627 | } else { // empty message text 628 | msg.txt = calloc(1, sizeof(char)); 629 | } 630 | 631 | #ifdef HAVE_LIBACARS 632 | if(msg.txt != NULL && msg.txt[0] != '\0') { 633 | bool decode_apps = true; 634 | // Inhibit higher layer application decoding if reassembly is enabled and 635 | // is now in progress (ie. the message is not yet complete) 636 | if(reasm_ctx != NULL && (msg.reasm_status == LA_REASM_IN_PROGRESS || 637 | msg.reasm_status == LA_REASM_DUPLICATE)) { 638 | decode_apps = false; 639 | } 640 | if(decode_apps) { 641 | msg.decoded_tree = la_acars_apps_parse_and_reassemble(msg.addr, msg.label, 642 | msg.txt, msg_dir, reasm_ctx, blk->tv); 643 | } 644 | } 645 | #endif 646 | 647 | if(outflg) 648 | fl=addFlight(&msg,blk->chn,blk->tv); 649 | 650 | if(emptymsg && ( msg.txt == NULL || msg.txt[0] == '\0')) 651 | return; 652 | 653 | if(jsonbuf) { 654 | if(outtype == OUTTYPE_ROUTEJSON ) { 655 | if(fl) 656 | jok=routejson(fl,blk->tv); 657 | } else { 658 | jok=buildjson(&msg, blk->chn, blk->tv); 659 | } 660 | } 661 | 662 | if((hourly || daily) && outtype != OUTTYPE_NONE && (fdout=Fileoutrotate(fdout))==NULL) { 663 | _exit(1); 664 | } 665 | switch (outtype) { 666 | case OUTTYPE_NONE: 667 | break; 668 | case OUTTYPE_ONELINE: 669 | printoneline(&msg, blk->chn, blk->tv); 670 | break; 671 | case OUTTYPE_STD: 672 | printmsg(&msg, blk->chn, blk->tv); 673 | break; 674 | case OUTTYPE_MONITOR: 675 | printmonitor(&msg, blk->chn, blk->tv); 676 | break; 677 | case OUTTYPE_ROUTEJSON: 678 | case OUTTYPE_JSON: 679 | if(jok) { 680 | fprintf(fdout, "%s\n", jsonbuf); 681 | fflush(fdout); 682 | } 683 | break; 684 | } 685 | 686 | switch (netout) { 687 | case NETLOG_PLANEPLOTTER: 688 | Netoutpp(&msg); 689 | break; 690 | case NETLOG_NATIVE: 691 | Netoutsv(&msg, idstation, blk->chn, blk->tv); 692 | break; 693 | case NETLOG_JSON: 694 | if(jok) Netoutjson(jsonbuf); 695 | break; 696 | #ifdef WITH_MQTT 697 | case NETLOG_MQTT: 698 | MQTTsend(jsonbuf); 699 | break; 700 | #endif 701 | } 702 | free(msg.txt); 703 | #ifdef HAVE_LIBACARS 704 | la_proto_tree_destroy(msg.decoded_tree); 705 | #endif 706 | } 707 | --------------------------------------------------------------------------------