acarsdec

an ACARS decoder
git clone git://r-36.net/acarsdec
Log | Files | Refs | README

commit 45bafeee14982a9d10e1e3dd33b08beeb93eee6f
Author: Christoph Lohmann <20h@r-36.net>
Date:   Wed, 13 Jun 2012 15:05:11 +0200

Initial commit of acarsdec.

Diffstat:
Makefile | 21+++++++++++++++++++++
PROTOCOL | 30++++++++++++++++++++++++++++++
README | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
README.mod | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
acarsdec.h | 36++++++++++++++++++++++++++++++++++++
example/acars.mp3 | 0
example/acars.wav | 0
getbits.c | 192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
getmesg.c | 218+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
input.c | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
main.c | 235+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
rc.d/acarsdec.archlinux | 44++++++++++++++++++++++++++++++++++++++++++++
rc.d/acarsdec.conf.d.archlinux | 3+++
serv.c | 311+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
stdinsrv.py | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
version.h | 3+++
16 files changed, 1543 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,21 @@ + +INCLUDES=-I. + +CFLAGS= -g -O2 -Wall $(INCLUDES) + +OBJS= getbits.o input.o getmesg.o main.o serv.o + +acarsdec: $(OBJS) + $(CC) -o $@ $(OBJS) -lm -lsndfile -lasound + +main.o: main.c version.h + +clean: + rm -f *.o acarsdec + +getbits.o: acarsdec.h getbits.c +getmesg.o: acarsdec.h getmesg.c +main.o: acarsdec.h main.c +input.o: acarsdec.h input.c +serv.o: acarsdec.h serv.c + diff --git a/PROTOCOL b/PROTOCOL @@ -0,0 +1,30 @@ +PROTOCOL FOR -p FLAG + +Format: + +msg ::= "MESG\n" + descs + content + "END MESG\n"; +descs ::= n * desc; +desc ::= name + ": " + value + "\n"; +name ::= UTF-8-String; +value ::= UTF-8-String; +content ::= "CONTENT: " + UTF-8-String + "\n.\n"; + +The content can be multi-line, so parse for "\n.\n". + +Example: + +---[START]--- +MESG +Mode: G +REG: .ZK-OKB +LABEL: BA +BLKID: 55 +MSGNO: L34A +FLIGHTID: NZ0039 +CONTENT: /MSTEC7X.DR1.ZK-OKB6A40B74740B55EBD70E7E2 +. +TIMESTAMP: 2010-06-26T11:29:35+00:00 +END MESG + +---[END]--- + diff --git a/README b/README @@ -0,0 +1,69 @@ + +ACARSDEC + +Acarsdec is an open source, realtime ACARS demodulator and position decoder for Linux. + +Aircraft Communication Addressing and Reporting System (or ACARS) is a digital datalink system for transmission of small messages between aircraft and ground stations via VHF radio. + +HOW DOES IT WORK ? + +To receive ACARS you need at least an AM VHF air band receiver tuned to one of these frequencies : + +131.725 Europe primary +131.525 European secondary +131.550 USA primary +130.025 USA secondary +131.450 Japan primary +(these are the most common, google is your friend for other frequencies) + +Audio output from this receiver is send to the soundcard input of your PC under Linux. +Then, acarsdec will demodulate the signals sent by aircrafts and print the received messages on its standart output in airnav log text format. + +BUILDING IT +On a Linux system, you will need libsnd librairy, alsa audio system and gcc/make installed. +Then just type : +make + +USING IT +acarsdec could be called with the following options : +acarsdec [-LR][-s noport] -d alsapcmdevice | -f sndfile + -f sndfile : decode from file sndfile (ie: a .wav file) + -d alsapcmdevice : decode from soundcard input alsapcmdevice (ie: hw:0,0) + [-LR] : diseable left or right channel decoding of stereo signal (save cpu) + [-s noport ] : "xastir" mode : act as an APRS local server, on port : noport (see below) + +Input could be mono or stereo but with 48Khz sampling frequency. +If stereo, acarsdec will demod the 2 channels independantly (if no L ou R options specified) + +Typical usage for realtime decoding is : +acarsdec -d hw:0 + +Be sure that correct record level is set for the used soundcard input. +For testing, you could try to record your receiver output at 48khz sampling frequency with any audio recording tool. +Save as wav file, then decode it by : +acarsdec -f audiofile.wav. + + +USING IT WITH XASTIR +acarsdec have a special output mode to use it with APRS position reporting plotting program : xastir (www.xastir.org). +In this mode, acarsdec acts as a very basic local aprsd server. +ACARS messages, and in particular, position report messages are converted to APRS format, so you can plot aircraft positions on a map. + +PS: position decoding is in experimental stage. Mail me if you find errors or lack of position reporting. + +start acarsdec with the following option : +acarsdec -d hw:0 -s 14000 + +Then in xastir, choose : Interface->Interface Control->Add +Select : Internet Server, then Add +Set Host at 127.0.0.1, Port 14000, Don't allow transmitting, then Ok. +This will add an interface in the Interface Control dialog. + +Then select this interface and press start. +To check that acarsdec send messages to xastir, select View->Incoming traffic +ACARS messages look like that in xastir : +F-XXYZ>ACARS:>Fid:AFXXXX Lbl:Q0 + +Lots of ACARS messages are messages without position report, so be patient before seeing aircraft plotted on the map. + + diff --git a/README.mod b/README.mod @@ -0,0 +1,62 @@ +INTRODUCTION + +This acarsdec was modified by + Christoph Lohmann <20h@r-36.net> + +In addition to this, stdinsrv.py was added, for additionaly functionality. +See the examples for how to use these possibilities. + + +INSTALLATION + + % make + % cp acarsdec /usr/bin + % cp stdinsrv.py /usr/bin/stdinsrv + + +STARTUP + +For a daemon mode, using stdinsrv, see the rc.d/* files. For now there +are the example files for an Archlinux installation. + + +EXAMPLES + +Reading from first alsa device, only processing the right stereo channel, +outputting to stdout, in a parseable format and multiplexing it to the +network on port 5102. + + % acarsdec -d hw0,0 -p -R | stdinsrv -p 5102 + +Decoding a recorded ACARS example wav file (48000!) on stdin, to the original +acarsdec format: + + % cat examples/acars.wav | acarsdec -t + + +COMPLEX EXAMPLE + +Server: ACARS receiver -> sound in +Client: We want to hear the ACARS signal and see the decoded message for + a comparison, whether the acarsdec decodes them right. (See the + examples folder for listening to an example ACARS message.) + +Client: + % socat - TCP-L:5467 | aplay & + +Server: + % mkfifo /tmp/acarsdec + % arecord -f dat | tee /tmp/acarsdec | socat - TCP:$ClientIP:5467 & + % acarsdec -f /tmp/acarsdec -p | stdinsrv -p 5102 + +Client: + % socat - TCP:$ServerIP:5102 + + +OTHER FLAGS + +There is flag -v (verbose), which forces stdout output during the -s mode. +The -e flag enables some debugging output, you might find interesting. + +Have fun! + diff --git a/acarsdec.h b/acarsdec.h @@ -0,0 +1,36 @@ +typedef struct { + unsigned char mode; + unsigned char addr[8]; + unsigned char ack; + unsigned char label[3]; + unsigned char bid; + unsigned char no[5]; + unsigned char fid[7]; + char txt[256]; +} msg_t; + +extern int initsample(char *sourcename, int src); +extern int getsample(short *sample, int nb); +extern void endsample(void); + +extern void init_bits(void); +extern void resetbits(int ch); +extern int getbit(short in, unsigned char *outbits, int ch); + +extern void init_mesg(void); +extern int getmesg(unsigned char r, msg_t * msg, int ch); + +extern int init_serv(short port); +extern int send_mesg(msg_t *msg); +extern void end_serv(void); + +enum { + IN_FILE = 0, + IN_ALSA = 1, + IN_STDIN = 2, + + OUT_NET = 0x01, + OUT_PRINT = 0x02, + OUT_PROTO = 0x04, +}; + diff --git a/example/acars.mp3 b/example/acars.mp3 Binary files differ. diff --git a/example/acars.wav b/example/acars.wav Binary files differ. diff --git a/getbits.c b/getbits.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2007 by Thierry Leconte (F4DWV) + * + * $Id: getbits.c,v 1.4 2007/04/15 15:06:54 f4dwv Exp $ + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <stdlib.h> +#include <stdio.h> +#include <math.h> + +#include "acarsdec.h" + + +#define Fe 48000.0 +#define Freqh 4800.0/Fe*2.0*M_PI +#define Freql 2400.0/Fe*2.0*M_PI +#define BITLEN ((int)Fe/1200) + +static float h[BITLEN]; + +static struct bstat_s { + float hsample[BITLEN]; + float lsample[BITLEN]; + float isample[BITLEN]; + float qsample[BITLEN]; + float csample[BITLEN]; + int is; + int clock; + float lin; + float phih,phil; + float dfh,dfl; + float pC,ppC; + int sgI, sgQ; + float ea; +} bstat[2]; + + +void init_bits(void) +{ + int i; + for (i = 0; i < BITLEN; i++) + h[i] = sin(2.0 * M_PI * (float) i / (float) BITLEN); + + for (i = 0; i < BITLEN; i++) { + bstat[0].hsample[i] = bstat[0].lsample[i] = + bstat[0].isample[i] = bstat[0].qsample[i] = + bstat[0].csample[i] = 0.0; + bstat[1].hsample[i] = bstat[1].lsample[i] = + bstat[1].isample[i] = bstat[1].qsample[i] = + bstat[1].csample[i] = 0.0; + } + bstat[0].is = bstat[0].clock = bstat[0].sgI = bstat[0].sgQ = 0; + bstat[1].is = bstat[1].clock = bstat[1].sgI = bstat[1].sgQ = 0; + bstat[0].phih = bstat[0].phil = bstat[0].dfh = bstat[0].dfl = + bstat[0].pC = bstat[0].ppC = bstat[0].ea = 0.0; + bstat[1].phih = bstat[1].phil = bstat[1].dfh = bstat[1].dfl = + bstat[1].pC = bstat[1].ppC = bstat[1].ea = 0.0; + bstat[0].lin=bstat[1].lin=1.0; + +} + +void resetbits(int ch) +{ + bstat[ch].sgI = bstat[ch].sgQ = 0; +} + +#define VFOPLL 0.7e-3 +#define BITPLL 0.2 + +int getbit(short sample, unsigned char *outbits, int ch) +{ + int i, bt; + float in, in2; + float C; + float I, Q; + float oscl, osch; + struct bstat_s *st; + + bt = 0; + st = &bstat[ch]; + + in = (float) sample; + st->lin = 0.003 * fabs(in) + 0.997 * st->lin; + in /= st->lin; + in2 = in * in; + + st->is--; + if (st->is < 0) + st->is = BITLEN - 1; + + /* VFOs */ + st->phih += Freqh - VFOPLL * st->dfh; + if (st->phih >= 4.0 * M_PI) + st->phih -= 4.0 * M_PI; + st->hsample[st->is] = in2 * sin(st->phih); + for (i = 0, st->dfh = 0.0; i < BITLEN / 2; i++) { + st->dfh += st->hsample[(st->is + i) % BITLEN]; + } + osch = cos(st->phih / 2.0); + + st->phil += Freql - VFOPLL * st->dfl; + if (st->phil >= 4.0 * M_PI) + st->phil -= 4.0 * M_PI; + st->lsample[st->is] = in2 * sin(st->phil); + for (i = 0, st->dfl = 0.0; i < BITLEN / 2; i++) { + st->dfl += st->lsample[(st->is + i) % BITLEN]; + } + oscl = cos(st->phil / 2.0); + + /* mix */ + st->isample[st->is] = in * (oscl + osch); + st->qsample[st->is] = in * (oscl - osch); + st->csample[st->is] = oscl * osch; + + + /* bit clock */ + st->clock++; + if (st->clock >= BITLEN/4 + st->ea) { + st->clock = 0; + + /* clock filter */ + for (i = 0, C = 0.0; i < BITLEN; i++) { + C += h[i] * st->csample[(st->is + i) % BITLEN]; + } + + if (st->pC < C && st->pC < st->ppC) { + + /* integrator */ + for (i = 0, Q = 0.0; i < BITLEN; i++) { + Q += st->qsample[(st->is + i) % BITLEN]; + } + + if (st->sgQ == 0) { + if (Q < 0) + st->sgQ = -1; + else + st->sgQ = 1; + } + + *outbits = + ((*outbits) >> 1) | (unsigned + char) ((Q * st->sgQ > + 0) ? 0x80 : 0); + bt = 1; + + st->ea = -BITPLL * (C - st->ppC); + if(st->ea > 2.0) st->ea=2.0; + if(st->ea < -2.0) st->ea=-2.0; + } + if (st->pC > C && st->pC > st->ppC) { + + /* integrator */ + for (i = 0, I = 0.0; i < BITLEN; i++) { + I += st->isample[(st->is + i) % BITLEN]; + } + + if (st->sgI == 0) { + if (I < 0) + st->sgI = -1; + else + st->sgI = 1; + } + + *outbits = + ((*outbits) >> 1) | (unsigned + char) ((I * st->sgI > + 0) ? 0x80 : 0); + bt = 1; + + st->ea = BITPLL * (C - st->ppC); + if(st->ea > 2.0) st->ea=2.0; + if(st->ea < -2.0) st->ea=-2.0; + } + st->ppC = st->pC; + st->pC = C; + } + return bt; +} diff --git a/getmesg.c b/getmesg.c @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2007 by Thierry Leconte (F4DWV) + * + * $Id: getmesg.c,v 1.3 2007/03/28 06:26:05 f4dwv Exp $ + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "acarsdec.h" + +#define SYN 0x16 +#define SOH 0x01 +struct mstat_s { + enum { HEADL, HEADF, BSYNC1, BSYNC2, SYN1, SYN2, SOH1, TXT, CRC1, + CRC2, END } state; + int ind; + unsigned short crc; + char txt[243]; +} mstat[2]; + + +/* CCITT 16 CRC */ +#define POLY 0x1021 +static void update_crc(unsigned short *crc, unsigned char ch) +{ + unsigned char v; + unsigned int i; + unsigned short flag; + + v = 1; + for (i = 0; i < 8; i++) { + flag = (*crc & 0x8000); + *crc = *crc << 1; + + if (ch & v) + *crc = *crc + 1; + + if (flag != 0) + *crc = *crc ^ POLY; + + v = v << 1; + } +} + +static int build_mesg(char *txt, int len, msg_t * msg) +{ + int i, k; + char r; + + /* remove special chars */ + for (i = 0; i < len; i++) { + r = txt[i]; + if (r < ' ' && r != 0x0d && r != 0x0a) + r = 0xa4; + txt[i] = r; + } + txt[i] = '\0'; + + /* fill msg struct */ + k = 0; + msg->mode = txt[k]; + k++; + + for (i = 0; i < 7; i++, k++) { + msg->addr[i] = txt[k]; + } + msg->addr[7] = '\0'; + + /* ACK/NAK */ + msg->ack = txt[k]; + k++; + + msg->label[0] = txt[k]; + k++; + msg->label[1] = txt[k]; + k++; + msg->label[2] = '\0'; + + msg->bid = txt[k]; + k++; + + k++; + + for (i = 0; i < 4; i++, k++) { + msg->no[i] = txt[k]; + } + msg->no[4] = '\0'; + + for (i = 0; i < 6; i++, k++) { + msg->fid[i] = txt[k]; + } + msg->fid[6] = '\0'; + + strcpy(msg->txt, &(txt[k])); + + return 1; +} + +void init_mesg(void) +{ + mstat[0].state = mstat[1].state = HEADL; +} + +int getmesg(unsigned char r, msg_t * msg, int ch) +{ + struct mstat_s *st; + + st = &(mstat[ch]); + + do { + switch (st->state) { + case HEADL: + if (r == 0xff) { + st->state = HEADF; + return 8; + } + resetbits(ch); + return 8; + break; + case HEADF: + if (r != 0xff) { + int i; + unsigned char m; + + for (i = 0, m = 1; i < 7; i++, m = m << 1) { + if (!(r & m)) + break; + } + if (i < 2) { + st->state = HEADL; + break; + } + st->state = BSYNC1; + st->ind = 0; + if (i != 2) + return (i - 2); + break; + } + return 6; + case BSYNC1: + if (r != 0x80 + '+') + st->ind++; + st->state = BSYNC2; + return 8; + case BSYNC2: + if (r != '*') + st->ind++; + st->state = SYN1; + return 8; + case SYN1: + if (r != SYN) + st->ind++; + st->state = SYN2; + return 8; + case SYN2: + if (r != SYN) + st->ind++; + st->state = SOH1; + return 8; + case SOH1: + if (r != SOH) + st->ind++; + if (st->ind > 2) { + st->state = HEADL; + break; + } + st->state = TXT; + st->ind = 0; + st->crc = 0; + return 8; + case TXT: + update_crc(&st->crc, r); + r = r & 0x7f; + if (r == 0x03 || r == 0x17) { + st->state = CRC1; + return 8; + } + st->txt[st->ind] = r; + st->ind++; + if (st->ind > 243) { + st->state = HEADL; + break; + } + return 8; + case CRC1: + update_crc(&st->crc, r); + st->state = CRC2; + return 8; + case CRC2: + update_crc(&st->crc, r); + st->state = END; + return 8; + case END: + st->state = HEADL; + if (st->crc == 0) { + build_mesg(st->txt, st->ind, msg); + return 0; + } + return 8; + } + } while (1); +} diff --git a/input.c b/input.c @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2007 by Thierry Leconte (F4DWV) + * (c) 2010 by Christoph Lohmann <20h@r-36.net> + * + * $Id: input.c,v 1.3 2007/03/29 16:21:49 f4dwv Exp $ + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <stdlib.h> +#include <sndfile.h> +#include <alsa/asoundlib.h> + +#include "acarsdec.h" + +static int source = 0; +static int nbch = 0; + +static SNDFILE *inwav; +static int initstdin(void) +{ + SF_INFO infwav; + +/* open wav input file */ + infwav.format = 0; + inwav = sf_open_fd(STDIN_FILENO, SFM_READ, &infwav, SF_TRUE); + if (inwav == NULL) { + fprintf(stderr, "could not open stdin\n"); + return (0); + } + if (infwav.samplerate != 48000) { + fprintf(stderr, + "Bad Input File sample rate: %d. Must be 48000\n", + infwav.samplerate); + return (0); + } + nbch=infwav.channels; + return (infwav.channels); +} + +static SNDFILE *inwav; +static int initsnd(char *filename) +{ + SF_INFO infwav; + +/* open wav input file */ + infwav.format = 0; + inwav = sf_open(filename, SFM_READ, &infwav); + if (inwav == NULL) { + fprintf(stderr, "could not open %s\n", filename); + return (0); + } + if (infwav.samplerate != 48000) { + fprintf(stderr, + "Bad Input File sample rate: %d. Must be 48000\n", + infwav.samplerate); + return (0); + } + nbch=infwav.channels; + return (infwav.channels); +} + +static snd_pcm_t *capture_handle; +static int initalsa(char *filename) +{ + snd_pcm_hw_params_t *hw_params; + int err; + + if ((err = + snd_pcm_open(&capture_handle, filename, + SND_PCM_STREAM_CAPTURE, 0)) < 0) { + fprintf(stderr, "cannot open audio device %s (%s)\n", + filename, snd_strerror(err)); + return 0; + } + + if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { + fprintf(stderr, + "cannot allocate hardware parameter structure (%s)\n", + snd_strerror(err)); + return 0; + } + + if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) { + fprintf(stderr, + "cannot initialize hardware parameter structure (%s)\n", + snd_strerror(err)); + return 0; + } + + if ((err = + snd_pcm_hw_params_set_access(capture_handle, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED)) < + 0) { + fprintf(stderr, "cannot set access type (%s)\n", + snd_strerror(err)); + return 0; + } + + if ((err = + snd_pcm_hw_params_set_format(capture_handle, hw_params, + SND_PCM_FORMAT_S16)) < 0) { + fprintf(stderr, "cannot set sample format (%s)\n", + snd_strerror(err)); + return 0; + } + + if ((err = + snd_pcm_hw_params_set_rate(capture_handle, hw_params, 48000, + 0)) < 0) { + fprintf(stderr, "cannot set sample rate (%s)\n", + snd_strerror(err)); + return 0; + } + + for(nbch=2;nbch>0;nbch--) { + if (snd_pcm_hw_params_set_channels(capture_handle, hw_params, nbch)==0) + break; + } + + if (nbch ==0) { + fprintf(stderr, "cannot set number of channels\n"); + return 0; + } + + if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) { + fprintf(stderr, "cannot set parameters (%s)\n", + snd_strerror(err)); + return 0; + } + snd_pcm_hw_params_free(hw_params); + + if ((err = snd_pcm_prepare(capture_handle)) < 0) { + fprintf(stderr, + "cannot prepare audio interface for use (%s)\n", + snd_strerror(err)); + return 0; + } + return nbch; +} + +/* open input source*/ +int initsample(char *sourcename, int src) +{ + source = src; + switch(src) { + case IN_STDIN: + return initstdin(); + case IN_FILE: + return initsnd(sourcename); + case IN_ALSA: + return initalsa(sourcename); + } + + return 0; +} + +int getsample(short *sample, int nb) +{ + int r = -1; + + switch(source) { + case IN_STDIN: + r = sf_read_short(inwav, sample, nb); + break; + case IN_FILE: + r = sf_read_short(inwav, sample, nb); + if (r == 0) + r = -1; /* this is the end */ + break; + case IN_ALSA: + r = snd_pcm_readi(capture_handle, sample, nb/nbch); + if (r <= 0) + fprintf(stderr, + "cannot read from interface (%s)\n", + snd_strerror(r)); + r=r*nbch; + break; + } + return r; +} + +void endsample(void) +{ + switch(source) { + case IN_FILE: + case IN_STDIN: + sf_close(inwav); + break; + case IN_ALSA: + snd_pcm_close(capture_handle); + break; + } +} diff --git a/main.c b/main.c @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2007 by Thierry Leconte (F4DWV) + * (c) 2010 by Christoph Lohmann <20h@r-36.net> + * + * $Id: main.c,v 1.5 2007/04/22 16:14:41 f4dwv Exp $ + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <time.h> + +#include "version.h" +#include "acarsdec.h" + +extern int optind, opterr; +extern char *optarg; + +static void usage(void) +{ + fprintf(stderr, "%s\n", version); + fprintf(stderr, "Usage: acarsdec [-vep][-LR][-s noport] -d " + "alsapcmdevice | -f sndfile | -t\n"); + fprintf(stderr, " -f sndfile :\t\tdecode from file sndfile " + "(ie: a .wav file)\n"); + fprintf(stderr, " -d alsapcmdevice :\tdecode from soundcard " + "input alsapcmdevice (ie: hw:0,0)\n"); + fprintf(stderr, " -t :\t\t\tread from stdin.\n"); + fprintf(stderr, " [-v] :\t\t\tbe verbose.\n"); + fprintf(stderr, " [-p] :\t\t\toutput should be parseable protocol.\n"); + fprintf(stderr, " [-e] :\t\t\tdo debug?\n"); + fprintf(stderr, " [-LR] :\t\tdisable left or right channel " + "decoding of stereo signal\n"); + fprintf(stderr, " [-s noport ] :\t\tact as an APRS local server, " + "on port : noport\n"); + fprintf(stderr, "Input could be mono or stereo but with 48Khz " + "sampling frequency.\nIf stereo, acarsdec will " + "demod the 2 channels independantly (if no L ou " + "R options specified)\n\n"); + exit(1); +} + +void print_mesg(msg_t * msg) +{ + time_t t; + struct tm *tmp; + char pos[128]; + + printf("ACARS mode: %c", msg->mode); + printf(" Aircraft reg: %s\n", msg->addr); + printf("Message label: %s", msg->label); + printf(" Block id: %d", (int) msg->bid); + printf(" Msg. no: %s\n", msg->no); + printf("Flight id: %s\n", msg->fid); + printf("Message content:-\n%s", msg->txt); + + if (posconv(msg->txt, msg->label, pos)==0) + printf("\nAPRS : Addr:%s Fid:%s Lbl:%s pos:%s\n", msg->addr, + msg->fid,msg->label,pos); + + t = time(NULL); + tmp = gmtime(&t); + printf("\n--------------------------------------------" + "--------------[%02d/%02d/%04d %02d:%02d]\n\n", + tmp->tm_mday, tmp->tm_mon + 1, tmp->tm_year + 1900, + tmp->tm_hour, tmp->tm_min); + fflush(stdout); + +} + +void print_proto(msg_t * msg) +{ + time_t t; + struct tm *tmp; + char pos[128]; + char timestamp[128]; + + printf("MESG\n"); + printf("Mode: %c\n", msg->mode); + printf("REG: %s\n", msg->addr); + printf("LABEL: %s\n", msg->label); + printf("BLKID: %d\n", (int) msg->bid); + printf("MSGNO: %s\n", msg->no); + printf("FLIGHTID: %s\n", msg->fid); + printf("CONTENT: %s\n.\n", msg->txt); + if (posconv(msg->txt, msg->label, pos)==0) { + printf("APRS-ADDR: %s\n", msg->addr); + printf("APRS-FID: %s\n", msg->fid); + printf("APRS-LABEL: %s\n", msg->label); + printf("APRS-POS: %s\n", pos); + } + t = time(NULL); + tmp = gmtime(&t); + strftime(timestamp, sizeof(timestamp) - 1, + "%FT%T+00:00", tmp); + printf("TIMESTAMP: %s\n", timestamp); + printf("END MESG\n\n"); + fflush(stdout); +} + +void do_output(int type, msg_t *msg) +{ + + if(type & OUT_NET) + send_mesg(msg); + if(type & OUT_PRINT) + print_mesg(msg); + if(type & OUT_PROTO) + print_proto(msg); +} + +int main(int argc, char **argv) +{ + int c; + unsigned char r[2]; + msg_t msg[2]; + int nbit[2] = {0, 0}; + int nrbit[2] = {8, 8}; + int nbch=0; + int esel[2] = {1, 1}; + short port=0; + int debug=0; + int output = 0; + int i; + + while ((c = getopt(argc, argv, "ptevd:f:RLs:")) != EOF) { + switch (c) { + case 'd': + nbch = initsample(optarg, IN_ALSA); + break; + case 'f': + nbch = initsample(optarg, IN_FILE); + break; + case 't': + nbch = initsample("stdin", IN_STDIN); + break; + case 'L': + esel[0] = 0; + break; + case 'R': + esel[1] = 0; + break; + case 's': + port=atoi(optarg); + output |= OUT_NET; + break; + case 'v': + output |= OUT_PRINT; + break; + case 'p': + output |= OUT_PROTO; + break; + case 'e': + debug++; + break; + default: + usage(); + exit(1); + } + } + + if (output == 0) + output = OUT_PRINT; + + if (nbch == 0) { + usage(); + exit(1); + } + + if (port) { + if (init_serv(port)) + exit(1); + if (debug) + fprintf(stderr, "Server initialized.\n"); + } + +/* main loop */ + init_bits(); + init_mesg(); + + if(debug) + fprintf(stderr, "Starting receive loop.\n"); + do { + short sample[4096]; + int ind, len; + + len = getsample(sample, 4096); + if (debug) + fprintf(stderr, "Got sample: %d\n", len); + if (len < 0) + break; + + for (ind = 0; ind < len;) { + for (i = 0; i < nbch; i++,ind++) { + if (esel[i]) { + nbit[i] += getbit(sample[ind], &r[i], + 0); + if (nbit[i] >= nrbit[i]) { + nrbit[i] = getmesg(r[i], + &msg[i], 0); + nbit[i] = 0; + if (nrbit[i] == 0) { + do_output(output, + &msg[i]); + nrbit[i] = 8; + } + } + } + } + } + } while (1); + + + if(port) + end_serv(); + + endsample(); + + exit(0); +} diff --git a/rc.d/acarsdec.archlinux b/rc.d/acarsdec.archlinux @@ -0,0 +1,44 @@ +#!/bin/bash + +CONF=/etc/conf.d/acarsdec + +. /etc/rc.conf +. /etc/rc.d/functions + +[ -f $CONF ] && . $CONF + +ACARSDECBIN=/usr/bin/acarsdec +STDINSRV=/usr/bin/stdinsrv +APID=`pidof -x $ACARSDECBIN` +case "$1" in + force) + stat_busy "Killing acarsdec by force." + kill $APID &> /dev/null + stat_done + ;; + start) + stat_busy "Starting acarsdec server" + [ -z "$APID" ] && $ACARSDECBIN $ACARSDECPARAMS \ + | $STDINSRV $STDINSRVPARAMS 2>&1 >> /dev/null & + if [ $? -gt 0 ]; then + stat_fail + else + add_daemon acarsdec + stat_done + fi + ;; + stop) + stat_busy "Stopping acarsdec" + [ ! -z "$APID" ] && kill $APID &>/dev/null + rm_daemon acarsdec + stat_done + ;; + restart) + $0 stop + $0 start + ;; + *) + echo "usage: $0 {start|stop|restart}" +esac +exit 0 + diff --git a/rc.d/acarsdec.conf.d.archlinux b/rc.d/acarsdec.conf.d.archlinux @@ -0,0 +1,3 @@ +ACARSDECPARAMS="-d hw:0,0 -p -R" +STDINSRVPARAMS="-p 5102" + diff --git a/serv.c b/serv.c @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2007 by Thierry Leconte (F4DWV) + * (c) 2010 by Christoph Lohmann <20h@r-36.net> + * + * $Id: serv.c,v 1.2 2007/04/22 16:14:41 f4dwv Exp $ + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <errno.h> + +#include "acarsdec.h" + +static int sa, sc; + +int init_serv(short port) +{ + struct sockaddr_in locaddr, remaddr; + socklen_t len; + char c; + int res; + + sa = socket(PF_INET, SOCK_STREAM, 0); + if (sa < 0) { + fprintf(stderr, "socket : %s\n", strerror(errno)); + return -1; + } + + res = 1; + res = setsockopt(sa, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(res)); + if (res) { + fprintf(stderr, "reuseaddr : %s\n", strerror(errno)); + return -1; + } + + memset(&locaddr, 0, sizeof(locaddr)); + locaddr.sin_family = AF_INET; + locaddr.sin_port = htons(port); + locaddr.sin_addr.s_addr = htonl(INADDR_ANY); + + len = sizeof(locaddr); + res = bind(sa, (struct sockaddr *) &locaddr, len); + if (res) { + fprintf(stderr, "bind : %s\n", strerror(errno)); + return -1; + } + + res = listen(sa, 1); + if (res) { + fprintf(stderr, "listen : %s\n", strerror(errno)); + return -1; + } + + memset(&remaddr, 0, sizeof(remaddr)); + len = sizeof(remaddr); + sc = accept(sa, (struct sockaddr *) &remaddr, &len); + if (sc < 0) { + fprintf(stderr, "accept : %s\n", strerror(errno)); + return -1; + } + + do { + res = read(sc, &c, 1); + } while (res == 1 && c != '\n'); + + + return 0; +} + + +/* convert ACARS position reports to APRS position */ +static void toaprs(int la, char lac, int ln, char lnc, int prec, char *out) +{ + int lad, lnd; + float lam, lnm; + + lad = la / 10000; + lnd = ln / 10000; + lam = (float) (la - (lad * 10000)) * 60.0 / 10000.0; + lnm = (float) (ln - (lnd * 10000)) * 60.0 / 10000.0; + + switch (prec) { + case 0: + sprintf(out, "%02d%02.0f. %c/%03d%02.0f. %c^", lad, lam, lac, lnd, lnm, lnc); + break; + case 1: + sprintf(out, "%02d%04.1f %c/%03d%04.1f %c^", lad, lam, lac, lnd, lnm, lnc); + break; + case 2: + default: + sprintf(out, "%02d%05.2f%c/%03d%05.2f%c^", lad, lam, lac, lnd, lnm, lnc); + break; + } +} + +int posconv(char *txt, unsigned char *label, char *pos) +{ + char lac, lnc; + int la, ln; + char las[7], lns[7]; + int n; + char *p; + +/*try different heuristics */ + + n = sscanf(txt, "#M1BPOS%c%05d%c%063d,", &lac, &la, &lnc, &ln); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + la *= 10; + ln *= 10; + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + n = sscanf(txt, "#M1AAEP%c%06d%c%07d", &lac, &la, &lnc, &ln); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + toaprs(la, lac, ln, lnc, 2, pos); + return 0;; + } + + if (strncmp(txt, "#M1B", 4) == 0) { + if ((p = strstr(txt, "/FPO")) != NULL) { + n = sscanf(p, "/FPO%c%05d%c%06d", &lac, &la, &lnc, &ln); + if (n == 4 && (lac == 'N' || lac == 'S') + && (lnc == 'E' || lnc == 'W')) { + la *= 10; + ln *= 10; + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + } + if ((p = strstr(txt, "/PS")) != NULL) { + n = sscanf(p, "/PS%c%05d%c%06d", &lac, &la, &lnc, &ln); + if (n == 4 && (lac == 'N' || lac == 'S') + && (lnc == 'E' || lnc == 'W')) { + la *= 10; + ln *= 10; + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + } + } + + n = sscanf(txt, "FST01%*8s%c%06d%c%07d", &lac, &la, &lnc, &ln); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + toaprs(la, lac, ln, lnc, 2, pos); + return 0;; + } + + n = sscanf(txt, "(2%c%5c%c%6c", &lac, las, &lnc, lns); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + las[5] = 0; + lns[6] = 0; + la = 10 * atoi(las); + ln = 10 * atoi(lns); + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + + n = sscanf(txt, "(:2%c%5c%c%6c", &lac, las, &lnc, lns); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + las[5] = 0; + lns[6] = 0; + la = 10 * atoi(las); + ln = 10 * atoi(lns); + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + + + n = sscanf(txt, "(2%*4s%c%5c%c%6c", &lac, las, &lnc, lns); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + las[5] = 0; + lns[6] = 0; + la = 10 * atoi(las); + ln = 10 * atoi(lns); + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + + n = sscanf(txt, "LAT %c%3c.%3c/LON %c%3c.%3c", &lac, las, &(las[3]), + &lnc, lns, &(lns[3])); + if (n == 6 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + las[6] = 0; + lns[6] = 0; + la = 10 * atoi(las); + ln = 10 * atoi(lns); + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + + + n = sscanf(txt, "#DFB(POS-%*6s-%04d%c%05d%c/", &la, &lac, &ln, &lnc); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + la *= 100; + ln *= 100; + toaprs(la, lac, ln, lnc, 0, pos); + return 0;; + } + + n = sscanf(txt, "#DFB*POS\a%*8s%c%04d%c%05d/", &lac, &la, &lnc, &ln); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + la *= 100; + ln *= 100; + toaprs(la, lac, ln, lnc, 0, pos); + return 0;; + } + + n = sscanf(txt, "POS%c%05d%c%06d,", &lac, &la, &lnc, &ln); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + la *= 10; + ln *= 10; + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + + n = sscanf(txt, "POS%*2s,%c%05d%c%06d,", &lac, &la, &lnc, &ln); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + la *= 10; + ln *= 10; + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + + n = sscanf(txt, "RCL%*2s,%c%05d%c%06d,", &lac, &la, &lnc, &ln); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + la *= 10; + ln *= 10; + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + + n = sscanf(txt, "TWX%*2s,%c%05d%c%06d,", &lac, &la, &lnc, &ln); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + la *= 10; + ln *= 10; + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + + n = sscanf(txt, "CLA%*2s,%c%05d%c%06d,", &lac, &la, &lnc, &ln); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + la *= 10; + ln *= 10; + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + + n = sscanf(txt, "%c%05d/%c%06d,", &lac, &la, &lnc, &ln); + if (n == 4 && (lac == 'N' || lac == 'S') && (lnc == 'E' || lnc == 'W')) { + la *= 10; + ln *= 10; + toaprs(la, lac, ln, lnc, 1, pos); + return 0;; + } + + return 1; +} + +int send_mesg(msg_t * msg) +{ + char apstr[512]; + char txt[512]; + char pos[64]; + unsigned char *ind; + + if(msg->label[0]=='_' && msg->label[1]==0x7f) + return 0; + + strcpy(txt,msg->txt); + for(ind = (unsigned char *)&txt; *ind != 0 ;ind++) { + if(*ind==0x0a || *ind == 0x0d) *ind=' '; + } + + ind = msg->addr; + while (*ind == '.' && *ind != 0) + ind++; + + if (posconv(msg->txt, msg->label, pos)) + sprintf(apstr, "%s>ACARS:>Fid:%s Lbl:%s %s\n", ind, msg->fid,msg->label,txt); + else + sprintf(apstr, "%s>ACARS:!%sFid:%s Lbl:%s %s\n", ind, pos,msg->fid,msg->label,txt); + + write(sc, apstr, strlen(apstr)); + + return 0; +} + + +void end_serv(void) +{ + close(sc); + close(sa); +} diff --git a/stdinsrv.py b/stdinsrv.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copy me if you can. +# by Christoph Lohmann <20h@r-36.net> +# + +import sys +from socket import socket, AF_INET, SOCK_STREAM, SO_REUSEADDR, SOL_SOCKET +from select import poll, POLLIN, POLLPRI, POLLERR, POLLHUP +from fcntl import fcntl, F_GETFL, F_SETFL +from os import O_NONBLOCK +import errno +import time +from getopt import getopt, GetoptError + +def dbg(msg): + sys.stderr.write("%s\n" % (msg)) + +def usage(app): + sys.stderr.write("usage: %s [-d] [-p port] [-h bind host]\n" % (app)) + sys.exit(1) + +def main(args): + try: + opts, args = getopt(args[1:], "dp:h:") + except GetoptError, err: + sys.stderr.write("%s\n" % (str(err))) + usage(args[0]) + + debug = False + host = "" + port = 6789 + for o, a in opts: + if o == "-p": + port = int(a) + elif o == "-d": + debug = True + elif o == "-h": + host = a + else: + assert False, "unhandled option" + + sock = socket(AF_INET, SOCK_STREAM) + sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + sock.bind((host, port)) + sock.listen(5) + + fl = fcntl(sys.stdin, F_GETFL) + fcntl(sys.stdin, F_SETFL, fl | O_NONBLOCK) + + clients = [] + def getcli(fd): + for i in clients: + if i.fileno() == fd: + return i + + def broadcastcli(data): + if debug == True: + dbg("Broadcast") + for i in clients: + try: + i.send(data) + i.flush(data) + except: + next + + def closecli(fd): + if debug == True: + dbg("Close: %d" % (fd)) + cli = getcli(i[0]) + clients.remove(cli) + po.unregister(cli) + cli.close() + + po = poll() + po.register(sys.stdin, POLLIN|POLLPRI|POLLERR|POLLHUP) + po.register(sock, POLLIN|POLLPRI) + while 1: + events = po.poll(30) + for i in events: + if i[0] == sock.fileno(): + cli = sock.accept()[0] + cli.setblocking(0) + clients.append(cli) + po.register(cli, POLLIN|POLLPRI) + if debug == True: + dbg("Accept: %d" % (cli.fileno())) + elif i[1] in (POLLERR, POLLHUP): + if debug == True: + dbg("err or hup: %d" % (i[0])) + if i[0] == sys.stdin.fileno(): + return 1 + cli = getcli(i[0]) + clients.remove(cli) + po.unregister(cli) + cli.close() + elif i[1] in (POLLIN, POLLPRI): + if i[0] == sys.stdin.fileno(): + if debug == True: + dbg("Input on stdin.") + broadcastcli(sys.stdin.read(4096)) + else: + if debug == True: + dbg("Input from client: %d" % (i[0])) + cli = getcli(i[0]) + a = cli.recv(4096) + if len(a) == 0: + closecli(i[0]) + +if __name__ == "__main__": + sys.exit(main(sys.argv)) + diff --git a/version.h b/version.h @@ -0,0 +1,3 @@ +const char version[] = "Acarsdec 1.2 (c) 2007 Thierry Leconte F4DWV\n" + " (c) 2010 Christoph Lohmann <20h@r-36.net>\n"; +