diff --git a/.gitignore b/.gitignore index c71b685..14e0170 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ src/imts/imts-dialer src/mpt1327/mpt1327 src/jolly/jollycom src/eurosignal/eurosignal +src/pocsag/pocsag src/tv/osmotv src/radio/osmoradio src/datenklo/datenklo diff --git a/README b/README index d92f58c..7c5a379 100644 --- a/README +++ b/README @@ -25,6 +25,7 @@ Additionally the following communication services are implemented: * Radio transmitter / receiver * Analog Modem Emulation (AM7911) * German classic 'Zeitansage' (talking clock) + * POCSAG transmitter / receiver USE AT YOUR OWN RISK! diff --git a/configure.ac b/configure.ac index 7b3b985..ddc29cc 100644 --- a/configure.ac +++ b/configure.ac @@ -105,6 +105,7 @@ AC_OUTPUT( src/mpt1327/Makefile src/jolly/Makefile src/eurosignal/Makefile + src/pocsag/Makefile src/tv/Makefile src/radio/Makefile src/datenklo/Makefile diff --git a/docs/index.html b/docs/index.html index 838aaec..a89074f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -125,6 +125,7 @@ Additional features:
  • C-Netz Magnetic Card
  • Zeitansage (German talking clock)
  • C-Netz FuVSt (MSC to control a real base station)
  • +
  • POCSAG
  • diff --git a/src/Makefile.am b/src/Makefile.am index 57fb1fb..200323e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -55,6 +55,7 @@ SUBDIRS += \ mpt1327 \ jolly \ eurosignal \ + pocsag \ tv \ radio \ zeitansage \ diff --git a/src/eurosignal/eurosignal.c b/src/eurosignal/eurosignal.c index 7769629..a38c228 100644 --- a/src/eurosignal/eurosignal.c +++ b/src/eurosignal/eurosignal.c @@ -656,7 +656,6 @@ int call_down_setup(int callref, const char __attribute__((unused)) *caller_id, euro_t *euro; euro_call_t *call; - /* find transmitter */ for (sender = sender_head; sender; sender = sender->next) { /* skip channels that are different than requested */ diff --git a/src/libdebug/debug.c b/src/libdebug/debug.c index 4593e8a..67ba00c 100755 --- a/src/libdebug/debug.c +++ b/src/libdebug/debug.c @@ -56,6 +56,7 @@ struct debug_cat { { "mpt1327", "\033[1;34m" }, { "jollycom", "\033[1;34m" }, { "eurosignal", "\033[1;34m" }, + { "pocsag", "\033[1;34m" }, { "frame", "\033[0;36m" }, { "call", "\033[0;37m" }, { "cc", "\033[1;32m" }, diff --git a/src/libdebug/debug.h b/src/libdebug/debug.h index bdcfd15..ae16857 100644 --- a/src/libdebug/debug.h +++ b/src/libdebug/debug.h @@ -18,38 +18,39 @@ #define DMPT1327 11 #define DJOLLY 12 #define DEURO 13 -#define DFRAME 14 -#define DCALL 15 -#define DCC 16 -#define DDB 17 -#define DTRANS 18 -#define DDMS 19 -#define DSMS 20 -#define DSDR 21 -#define DUHD 22 -#define DSOAPY 23 -#define DWAVE 24 -#define DRADIO 25 -#define DAM791X 26 -#define DUART 27 -#define DDEVICE 28 -#define DDATENKLO 29 -#define DZEIT 30 -#define DSIM1 31 -#define DSIM2 32 -#define DSIMI 33 -#define DSIM7 34 -#define DMTP2 35 -#define DMTP3 36 -#define DMUP 37 -#define DROUTER 38 -#define DSTDERR 39 -#define DSS5 40 -#define DISDN 41 -#define DMISDN 42 -#define DDSS1 43 -#define DSIP 44 -#define DTEL 45 +#define DPOCSAG 14 +#define DFRAME 15 +#define DCALL 16 +#define DCC 17 +#define DDB 18 +#define DTRANS 19 +#define DDMS 20 +#define DSMS 21 +#define DSDR 22 +#define DUHD 23 +#define DSOAPY 24 +#define DWAVE 25 +#define DRADIO 26 +#define DAM791X 27 +#define DUART 28 +#define DDEVICE 29 +#define DDATENKLO 30 +#define DZEIT 31 +#define DSIM1 32 +#define DSIM2 33 +#define DSIMI 34 +#define DSIM7 35 +#define DMTP2 36 +#define DMTP3 37 +#define DMUP 38 +#define DROUTER 39 +#define DSTDERR 40 +#define DSS5 41 +#define DISDN 42 +#define DMISDN 43 +#define DDSS1 44 +#define DSIP 45 +#define DTEL 46 void get_win_size(int *w, int *h); diff --git a/src/pocsag/Makefile.am b/src/pocsag/Makefile.am new file mode 100644 index 0000000..817a95b --- /dev/null +++ b/src/pocsag/Makefile.am @@ -0,0 +1,51 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +bin_PROGRAMS = \ + pocsag + +pocsag_SOURCES = \ + pocsag.c \ + frame.c \ + dsp.c \ + image.c \ + main.c +pocsag_LDADD = \ + $(COMMON_LA) \ + ../anetz/libgermanton.a \ + $(top_builddir)/src/liboptions/liboptions.a \ + $(top_builddir)/src/libdebug/libdebug.a \ + $(top_builddir)/src/libmobile/libmobile.a \ + $(top_builddir)/src/libosmocc/libosmocc.a \ + $(top_builddir)/src/libdisplay/libdisplay.a \ + $(top_builddir)/src/libjitter/libjitter.a \ + $(top_builddir)/src/libtimer/libtimer.a \ + $(top_builddir)/src/libsamplerate/libsamplerate.a \ + $(top_builddir)/src/libemphasis/libemphasis.a \ + $(top_builddir)/src/libfm/libfm.a \ + $(top_builddir)/src/libfilter/libfilter.a \ + $(top_builddir)/src/libwave/libwave.a \ + $(top_builddir)/src/libsample/libsample.a \ + $(top_builddir)/src/libg711/libg711.a \ + $(top_builddir)/src/libaaimage/libaaimage.a \ + -lm + +if HAVE_ALSA +pocsag_LDADD += \ + $(top_builddir)/src/libsound/libsound.a \ + $(ALSA_LIBS) + +endif + +if HAVE_SDR +pocsag_LDADD += \ + $(top_builddir)/src/libsdr/libsdr.a \ + $(top_builddir)/src/libam/libam.a \ + $(top_builddir)/src/libfft/libfft.a \ + $(UHD_LIBS) \ + $(SOAPY_LIBS) +endif + +if HAVE_ALSA +AM_CPPFLAGS += -DHAVE_ALSA +endif + diff --git a/src/pocsag/dsp.c b/src/pocsag/dsp.c new file mode 100644 index 0000000..af1a80b --- /dev/null +++ b/src/pocsag/dsp.c @@ -0,0 +1,288 @@ +/* POCSAG signal processing + * + * (C) 2019 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define CHAN pocsag->sender.kanal + +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "pocsag.h" +#include "frame.h" +#include "dsp.h" + +#define CODEWORD_SYNC 0x7cd215d8 + +#define MAX_DISPLAY 1.4 /* something above speech level, no emphasis */ + +static void dsp_init_ramp(pocsag_t *pocsag) +{ + double c; + int i; + + PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Generating cosine shaped ramp table.\n"); + for (i = 0; i < 256; i++) { + /* This is mathematically incorrect... */ + if (i < 64) + c = 1.0; + else if (i >= 192) + c = -1.0; + else + c = cos((double)(i - 64) / 128.0 * M_PI); + pocsag->fsk_ramp_down[i] = c * pocsag->fsk_deviation * pocsag->fsk_polarity; + pocsag->fsk_ramp_up[i] = -pocsag->fsk_ramp_down[i]; + } +} + +/* Init transceiver instance. */ +int dsp_init_sender(pocsag_t *pocsag, int samplerate, int baudrate, double deviation, double polarity) +{ + int rc; + + PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Init DSP for transceiver.\n"); + + /* set modulation parameters */ + // NOTE: baudrate equals modulation, because we have a raised cosine ramp of beta = 0.5 + sender_set_fm(&pocsag->sender, deviation, baudrate, deviation, MAX_DISPLAY); + + pocsag->fsk_bitduration = (double)samplerate / (double)baudrate; + pocsag->fsk_bitstep = 1.0 / pocsag->fsk_bitduration; + PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Use %.4f samples for one bit duration @ %d.\n", pocsag->fsk_bitduration, pocsag->sender.samplerate); + + pocsag->fsk_tx_buffer_size = pocsag->fsk_bitduration * 32.0 + 10; /* 32 bit, add some extra to prevent short buffer due to rounding */ + pocsag->fsk_tx_buffer = calloc(sizeof(sample_t), pocsag->fsk_tx_buffer_size); + if (!pocsag->fsk_tx_buffer) { + PDEBUG_CHAN(DDSP, DEBUG_ERROR, "No memory!\n"); + rc = -ENOMEM; + goto error; + } + + /* create deviation and ramp */ + pocsag->fsk_deviation = 1.0; // equals what we st at sender_set_fm() + pocsag->fsk_polarity = polarity; + dsp_init_ramp(pocsag); + + return 0; + +error: + dsp_cleanup_sender(pocsag); + + return -rc; + +} + +/* Cleanup transceiver instance. */ +void dsp_cleanup_sender(pocsag_t *pocsag) +{ + PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Cleanup DSP for transceiver.\n"); + + if (pocsag->fsk_tx_buffer) { + free(pocsag->fsk_tx_buffer); + pocsag->fsk_tx_buffer = NULL; + } +} + + +/* encode one codeward into samples + * input: 32 data bits + * output: samples + * return number of samples */ +static int fsk_block_encode(pocsag_t *pocsag, uint32_t word) +{ + /* alloc samples, add 1 in case there is a rest */ + sample_t *spl; + double phase, bitstep, devpol; + int i, count; + uint8_t lastbit; + + devpol = pocsag->fsk_deviation * pocsag->fsk_polarity; + spl = pocsag->fsk_tx_buffer; + phase = pocsag->fsk_tx_phase; + lastbit = pocsag->fsk_tx_lastbit; + bitstep = pocsag->fsk_bitstep * 256.0; + + /* add 32 bits */ + for (i = 0; i < 32; i++) { + if (lastbit) { + if ((word & 0x80000000)) { + /* stay up */ + do { + *spl++ = devpol; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } else { + /* ramp down */ + do { + *spl++ = pocsag->fsk_ramp_down[(uint8_t)phase]; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + lastbit = 0; + } + } else { + if ((word & 0x80000000)) { + /* ramp up */ + do { + *spl++ = pocsag->fsk_ramp_up[(uint8_t)phase]; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + lastbit = 1; + } else { + /* stay down */ + do { + *spl++ = -devpol; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + } + word <<= 1; + } + + /* depending on the number of samples, return the number */ + count = ((uintptr_t)spl - (uintptr_t)pocsag->fsk_tx_buffer) / sizeof(*spl); + + pocsag->fsk_tx_phase = phase; + pocsag->fsk_tx_lastbit = lastbit; + + return count; +} + +static void fsk_block_decode(pocsag_t *pocsag, uint8_t bit) +{ + if (!pocsag->fsk_rx_sync) { + pocsag->fsk_rx_word = (pocsag->fsk_rx_word << 1) | bit; + if (pocsag->fsk_rx_word == CODEWORD_SYNC) { + put_codeword(pocsag, pocsag->fsk_rx_word, -1, -1); + pocsag->fsk_rx_sync = 16; + pocsag->fsk_rx_index = 0; + } else + if (pocsag->fsk_rx_word == (uint32_t)(~CODEWORD_SYNC)) + PDEBUG_CHAN(DDSP, DEBUG_NOTICE, "Received inverted sync, caused by wrong polarity or by radio noise. Verify correct polarity!\n"); + } else { + pocsag->fsk_rx_word = (pocsag->fsk_rx_word << 1) | bit; + if (++pocsag->fsk_rx_index == 32) { + pocsag->fsk_rx_index = 0; + put_codeword(pocsag, pocsag->fsk_rx_word, (16 - pocsag->fsk_rx_sync) >> 1, pocsag->fsk_rx_sync & 1); + --pocsag->fsk_rx_sync; + } + } +} + +static void fsk_decode(pocsag_t *pocsag, sample_t *spl, int length) +{ + double phase, bitstep, polarity; + int i; + uint8_t lastbit; + + polarity = pocsag->fsk_polarity; + phase = pocsag->fsk_rx_phase; + lastbit = pocsag->fsk_rx_lastbit; + bitstep = pocsag->fsk_bitstep; + + for (i = 0; i < length; i++) { + if (*spl++ * polarity > 0.0) { + if (lastbit) { + /* stay up */ + phase += bitstep; + if (phase >= 1.0) { + phase -= 1.0; + fsk_block_decode(pocsag, 1); + } + } else { + /* ramp up */ + phase = -0.5; + fsk_block_decode(pocsag, 1); + lastbit = 1; + } + } else { + if (lastbit) { + /* ramp down */ + phase = -0.5; + fsk_block_decode(pocsag, 0); + lastbit = 0; + } else { + /* stay down */ + phase += bitstep; + if (phase >= 1.0) { + phase -= 1.0; + fsk_block_decode(pocsag, 0); + } + } + } + } + + pocsag->fsk_rx_phase = phase; + pocsag->fsk_rx_lastbit = lastbit; +} + +/* Process received audio stream from radio unit. */ +void sender_receive(sender_t *sender, sample_t *samples, int length, double __attribute__((unused)) rf_level_db) +{ + pocsag_t *pocsag = (pocsag_t *) sender; + + if (pocsag->rx) + fsk_decode(pocsag, samples, length); +} + +/* Provide stream of audio toward radio unit */ +void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length) +{ + pocsag_t *pocsag = (pocsag_t *) sender; + +again: + /* get word */ + if (!pocsag->fsk_tx_buffer_length) { + int64_t word = get_codeword(pocsag); + + /* no message, power is off */ + if (word < 0) { + memset(samples, 0, sizeof(samples) * length); + memset(power, 0, length); + return; + } + + /* encode */ + pocsag->fsk_tx_buffer_length = fsk_block_encode(pocsag, word); + pocsag->fsk_tx_buffer_pos = 0; + } + + /* send encoded word until end of source or destination buffer is reaced */ + while (length) { + *power++ = 1; + *samples++ = pocsag->fsk_tx_buffer[pocsag->fsk_tx_buffer_pos++]; + length--; + if (pocsag->fsk_tx_buffer_pos == pocsag->fsk_tx_buffer_length) { + pocsag->fsk_tx_buffer_length = 0; + break; + } + } + + /* do again, if destination buffer is not yet full */ + if (length) + goto again; +} + + diff --git a/src/pocsag/dsp.h b/src/pocsag/dsp.h new file mode 100644 index 0000000..054b22d --- /dev/null +++ b/src/pocsag/dsp.h @@ -0,0 +1,4 @@ + +int dsp_init_sender(pocsag_t *pocsag, int samplerate, int baudrate, double deviation, double polarity); +void dsp_cleanup_sender(pocsag_t *pocsag); + diff --git a/src/pocsag/frame.c b/src/pocsag/frame.c new file mode 100644 index 0000000..edaf2f5 --- /dev/null +++ b/src/pocsag/frame.c @@ -0,0 +1,494 @@ +/* POCSAG framing + * + * (C) 2021 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "pocsag.h" +#include "frame.h" + +#define CHAN pocsag->sender.kanal + +#define PREAMBLE_COUNT 18 +#define CODEWORD_PREAMBLE 0xaaaaaaaa +#define CODEWORD_SYNC 0x7cd215d8 +#define CODEWORD_IDLE 0x7a89c197 +#define IDLE_BATCHES 2 + +static const char numeric[16] = "0123456789RU -]["; +static const char hex[16] = "0123456789abcdef"; + +static const char *ctrlchar[32] = { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +}; + +static uint32_t pocsag_crc(uint32_t word) +{ + uint32_t denominator = 0x76900000; + int i; + + word <<= 10; + + for (i = 0; i < 21; i++) { + if ((word >> (30 - i)) & 1) + word ^= denominator; + denominator >>= 1; + } + + return word & 0x3ff; +} + +static uint32_t pocsag_parity(uint32_t word) +{ + word ^= word >> 16; + word ^= word >> 8; + word ^= word >> 4; + word ^= word >> 2; + word ^= word >> 1; + + return word & 1; +} + +static int debug_word(uint32_t word, int slot) +{ + if (pocsag_crc(word >> 11) != ((word >> 1) & 0x3ff)) { + PDEBUG(DPOCSAG, DEBUG_NOTICE, "CRC error in codeword 0x%08x.\n", word); + return -EINVAL; + } + + if (pocsag_parity(word)) { + PDEBUG(DPOCSAG, DEBUG_NOTICE, "Parity error in codeword 0x%08x.\n", word); + return -EINVAL; + } + + if (word == CODEWORD_SYNC) { + PDEBUG(DPOCSAG, DEBUG_DEBUG, "-> valid sync word\n"); + return 0; + } + + if (word == CODEWORD_IDLE) { + PDEBUG(DPOCSAG, DEBUG_DEBUG, "-> valid idle word\n"); + return 0; + } + + if (!(word & 0x80000000)) { + PDEBUG(DPOCSAG, DEBUG_DEBUG, "-> valid address word: RIC = '%d', function = '%d' (%s)\n", ((word >> 10) & 0x1ffff8) + slot, (word >> 11) & 0x3, pocsag_function_name[(word >> 11) & 0x3]); + } else { + PDEBUG(DPOCSAG, DEBUG_DEBUG, "-> valid message word: message = '0x%05x'\n", (word >> 11) & 0xfffff); + } + + return 0; +} + +static uint32_t encode_address(pocsag_msg_t *msg) +{ + uint32_t word; + + /* compose message */ + word = 0x0; + + /* RIC */ + word = (word << 18) | (msg->ric >> 3); + word = (word << 2) | msg->function; + + word = (word << 10) | pocsag_crc(word); + word = (word << 1) | pocsag_parity(word); + + return word; +} + +static void decode_address(uint32_t word, uint8_t slot, uint32_t *ric, enum pocsag_function *function) +{ + *ric = ((word >> 10) & 0x1ffff8) + slot; + *function = (word >> 11) & 0x3; +} + +static uint32_t encode_numeric(pocsag_msg_t *msg) +{ + uint8_t digit[5] = { 0xc, 0xc, 0xc, 0xc, 0xc }; + int index, i; + uint32_t word; + + /* get characters from string */ + index = 0; + while (msg->data_index < msg->data_length) { + for (i = 0; i < 16; i++) { + if (numeric[i] == msg->data[msg->data_index]) + break; + } + msg->data_index++; + if (i < 16) + digit[index++] = i; + if (index == 5) + break; + } + + /* compose message */ + word = 0x1; + for (i = 0; i < 5; i++) { + word = (word << 1) | (digit[i] & 0x1); + word = (word << 1) | ((digit[i] >> 1) & 0x1); + word = (word << 1) | ((digit[i] >> 2) & 0x1); + word = (word << 1) | ((digit[i] >> 3) & 0x1); + } + + word = (word << 10) | pocsag_crc(word); + word = (word << 1) | pocsag_parity(word); + + return word; +} + +static void decode_numeric(pocsag_t *pocsag, uint32_t word) +{ + uint8_t digit; + int i; + + for (i = 0; i < 5; i++) { + if (pocsag->rx_msg_data_length == sizeof(pocsag->rx_msg_data)) + return; + digit = (word >> (27 - i * 4)) & 0x1; + digit = (digit << 1) | ((word >> (28 - i * 4)) & 0x1); + digit = (digit << 1) | ((word >> (29 - i * 4)) & 0x1); + digit = (digit << 1) | ((word >> (30 - i * 4)) & 0x1); + pocsag->rx_msg_data[pocsag->rx_msg_data_length++] = numeric[digit]; + } +} + +static uint32_t encode_alpha(pocsag_msg_t *msg) +{ + int bits; + uint32_t word; + + /* compose message */ + word = 0x1; + bits = 0; + + /* get character from string */ + while (msg->data_index < msg->data_length) { + if ((msg->data[msg->data_index] & 0x80)) { + msg->data_index++; + continue; + } + while (42) { + word = (word << 1) | ((msg->data[msg->data_index] >> msg->bit_index) & 1); + bits++; + if (++msg->bit_index == 7) { + msg->bit_index = 0; + msg->data_index++; + break; + } + if (bits == 20) + break; + } + if (bits == 20) + break; + } + + /* fill remaining digit space with 0x04 (EOT) */ + while (bits <= 13) { + word = (word << 7) | 0x10; + bits += 7; + } + + /* fill remaining bits with '0's */ + if (bits < 20) + word <<= 20 - bits; + + word = (word << 10) | pocsag_crc(word); + word = (word << 1) | pocsag_parity(word); + + return word; +} + +static void decode_alpha(pocsag_t *pocsag, uint32_t word) +{ + int i; + + for (i = 0; i < 20; i++) { + if (pocsag->rx_msg_data_length == sizeof(pocsag->rx_msg_data)) + return; + if (!pocsag->rx_msg_bit_index) + pocsag->rx_msg_data[pocsag->rx_msg_data_length] = 0x00; + pocsag->rx_msg_data[pocsag->rx_msg_data_length] >>= 1; + pocsag->rx_msg_data[pocsag->rx_msg_data_length] |= ((word >> (30 - i)) & 0x1) << 6; + if (++pocsag->rx_msg_bit_index == 7) { + pocsag->rx_msg_bit_index = 0; + pocsag->rx_msg_data_length++; + } + } +} + +static void decode_hex(pocsag_t *pocsag, uint32_t word) +{ + uint8_t digit; + int i; + + for (i = 0; i < 5; i++) { + if (pocsag->rx_msg_data_length == sizeof(pocsag->rx_msg_data)) + return; + digit = (word >> (27 - i * 4)) & 0x1; + digit = (digit << 1) | ((word >> (28 - i * 4)) & 0x1); + digit = (digit << 1) | ((word >> (29 - i * 4)) & 0x1); + digit = (digit << 1) | ((word >> (30 - i * 4)) & 0x1); + pocsag->rx_msg_data[pocsag->rx_msg_data_length++] = hex[digit]; + } +} + +/* get codeword from scheduler */ +int64_t get_codeword(pocsag_t *pocsag) +{ + pocsag_msg_t *msg; + uint32_t word = 0; // make GCC happy + uint8_t slot = (pocsag->word_count - 1) >> 1; + uint8_t subslot = (pocsag->word_count - 1) & 1; + + /* no codeword, if not transmitting */ + if (!pocsag->tx) + return -1; + + /* transmitter state */ + switch (pocsag->state) { + case POCSAG_IDLE: + return -1; + case POCSAG_PREAMBLE: + if (!pocsag->word_count) + PDEBUG_CHAN(DPOCSAG, DEBUG_INFO, "Sending preamble.\n"); + /* transmit preamble */ + PDEBUG_CHAN(DPOCSAG, DEBUG_DEBUG, "Sending 32 bits of preamble pattern 0x%08x.\n", CODEWORD_PREAMBLE); + if (++pocsag->word_count == PREAMBLE_COUNT) { + pocsag_new_state(pocsag, POCSAG_MESSAGE); + pocsag->word_count = 0; + pocsag->idle_count = 0; + } + word = CODEWORD_PREAMBLE; + break; + case POCSAG_MESSAGE: + if (!pocsag->word_count) + PDEBUG_CHAN(DPOCSAG, DEBUG_INFO, "Sending batch.\n"); + /* send sync */ + if (pocsag->word_count == 0) { + PDEBUG_CHAN(DPOCSAG, DEBUG_DEBUG, "Sending 32 bits of sync pattern 0x%08x.\n", CODEWORD_SYNC); + /* count codewords */ + ++pocsag->word_count; + word = CODEWORD_SYNC; + break; + } + /* send message data, if there is an ongoing message */ + if ((msg = pocsag->current_msg)) { + /* reset idle counter */ + pocsag->idle_count = 0; + /* encode data */ + switch (msg->function) { + case POCSAG_FUNCTION_NUMERIC: + word = encode_numeric(msg); + break; + case POCSAG_FUNCTION_ALPHA: + word = encode_alpha(msg); + break; + default: + word = CODEWORD_IDLE; /* should never happen */ + } + /* if message is complete, reset index. if message is not to be repeated, remove message */ + if (msg->data_index == msg->data_length) { + pocsag->current_msg = NULL; + msg->data_index = 0; + if (msg->repeat || pocsag->sender.loopback) + msg->repeat--; + else + pocsag_msg_destroy(msg); + } + /* prevent 'use-after-free' from this point on */ + msg = NULL; + PDEBUG_CHAN(DPOCSAG, DEBUG_DEBUG, "Sending 32 bits of message codeword 0x%08x (frame %d.%d).\n", word, slot, subslot); + /* count codewords */ + if (++pocsag->word_count == 17) + pocsag->word_count = 0; + break; + } + /* if we are about to send an address codeword, we search for a pending message */ + for (msg = pocsag->msg_list; msg; msg = msg->next) { + /* if a message matches the right time slot */ + if ((msg->ric & 7) == slot) + break; + } + if (msg) { + PDEBUG_CHAN(DPOCSAG, DEBUG_INFO, "Sending message to RIC '%d' / function '%d' (%s)\n", msg->ric, msg->function, pocsag_function_name[msg->function]); + /* reset idle counter */ + pocsag->idle_count = 0; + /* encode address */ + word = encode_address(msg); + /* link message, if there is data to be sent */ + if ((msg->function == POCSAG_FUNCTION_NUMERIC || msg->function == POCSAG_FUNCTION_ALPHA) && msg->data_length) { + char text[msg->data_length + 1]; + memcpy(text, msg->data, msg->data_length); + text[msg->data_length] = '\0'; + PDEBUG_CHAN(DPOCSAG, DEBUG_INFO, " -> Message text is \"%s\".\n", text); + pocsag->current_msg = msg; + msg->data_index = 0; + msg->bit_index = 0; + } else { + /* if message is not to be repeated, remove message */ + if (msg->repeat || pocsag->sender.loopback) + msg->repeat--; + else + pocsag_msg_destroy(msg); + /* prevent 'use-after-free' from this point on */ + msg = NULL; + } + PDEBUG_CHAN(DPOCSAG, DEBUG_DEBUG, "Sending 32 bits of address codeword 0x%08x (frame %d.%d).\n", word, slot, subslot); + /* count codewords */ + if (++pocsag->word_count == 17) + pocsag->word_count = 0; + break; + } + /* no message, so we send idle pattern */ + PDEBUG_CHAN(DPOCSAG, DEBUG_DEBUG, "Sending 32 bits of idle pattern 0x%08x (frame %d.%d).\n", CODEWORD_IDLE, slot, subslot); + /* count codewords */ + if (++pocsag->word_count == 17) { + pocsag->word_count = 0; + /* if no message has been scheduled during transmission and idle counter is reached, stop transmitter */ + if (!pocsag->msg_list && pocsag->idle_count++ == IDLE_BATCHES) { + PDEBUG_CHAN(DPOCSAG, DEBUG_INFO, "Transmission done.\n"); + PDEBUG_CHAN(DPOCSAG, DEBUG_DEBUG, "Reached %d of idle batches, turning transmitter off.\n", IDLE_BATCHES); + pocsag_new_state(pocsag, POCSAG_IDLE); + } + } + word = CODEWORD_IDLE; + break; + } + + if (word != CODEWORD_PREAMBLE) + debug_word(word, slot); + + return word; +} + +static void done_rx_msg(pocsag_t *pocsag) +{ + if (!pocsag->rx_msg_valid) + return; + + pocsag->rx_msg_valid = 0; + + PDEBUG_CHAN(DPOCSAG, DEBUG_INFO, "Received message from RIC '%d' / function '%d' (%s)\n", pocsag->rx_msg_ric, pocsag->rx_msg_function, pocsag_function_name[pocsag->rx_msg_function]); + { + char text[pocsag->rx_msg_data_length * 5 + 1]; + int i, j; + for (i = 0, j = 0; i < pocsag->rx_msg_data_length; i++) { + if (pocsag->rx_msg_data[i] == 127) { + strcpy(text + j, ""); + j += strlen(text + j); + } else + if (pocsag->rx_msg_data[i] < 32) { + strcpy(text + j, ctrlchar[(int)pocsag->rx_msg_data[i]]); + j += strlen(text + j); + } else + text[j++] = pocsag->rx_msg_data[i]; + } + text[j] = '\0'; + PDEBUG_CHAN(DPOCSAG, DEBUG_INFO, " -> Message text is \"%s\".\n", text); + pocsag_msg_receive(pocsag->language, pocsag->sender.kanal, pocsag->rx_msg_ric, pocsag->rx_msg_function, text); + } +} + +void put_codeword(pocsag_t *pocsag, uint32_t word, int8_t slot, int8_t subslot) +{ + int rc; + + if (slot < 0 && word == CODEWORD_SYNC) { + PDEBUG_CHAN(DPOCSAG, DEBUG_DEBUG, "Received 32 bits of sync pattern 0x%08x.\n", CODEWORD_SYNC); + return; + } + + if (word == CODEWORD_IDLE) { + PDEBUG_CHAN(DPOCSAG, DEBUG_DEBUG, "Received 32 bits of idle pattern 0x%08x.\n", CODEWORD_IDLE); + } else + if (!(word & 0x80000000)) + PDEBUG_CHAN(DPOCSAG, DEBUG_DEBUG, "Received 32 bits of address codeword 0x%08x (frame %d.%d).\n", word, slot, subslot); + else + PDEBUG_CHAN(DPOCSAG, DEBUG_DEBUG, "Received 32 bits of message codeword 0x%08x (frame %d.%d).\n", word, slot, subslot); + rc = debug_word(word, slot); + if (rc < 0) { + done_rx_msg(pocsag); + return; + } + + if (word == CODEWORD_IDLE) { + done_rx_msg(pocsag); + return; + } + + if (!(word & 0x80000000)) { + done_rx_msg(pocsag); + pocsag->rx_msg_valid = 1; + decode_address(word, slot, &pocsag->rx_msg_ric, &pocsag->rx_msg_function); + pocsag->rx_msg_data_length = 0; + pocsag->rx_msg_bit_index = 0; + } else { + if (!pocsag->rx_msg_valid) + return; + switch (pocsag->rx_msg_function) { + case POCSAG_FUNCTION_NUMERIC: + decode_numeric(pocsag, word); + break; + case POCSAG_FUNCTION_ALPHA: + decode_alpha(pocsag, word); + break; + default: + decode_hex(pocsag, word); + ; + } + } +} + diff --git a/src/pocsag/frame.h b/src/pocsag/frame.h new file mode 100644 index 0000000..a22bfa6 --- /dev/null +++ b/src/pocsag/frame.h @@ -0,0 +1,4 @@ + +int64_t get_codeword(pocsag_t *pocsag); +void put_codeword(pocsag_t *pocsag, uint32_t word, int8_t slot, int8_t subslot); + diff --git a/src/pocsag/image.c b/src/pocsag/image.c new file mode 100644 index 0000000..9fff620 --- /dev/null +++ b/src/pocsag/image.c @@ -0,0 +1,6 @@ +#include + +const char *aaimage[] = { + NULL +}; + diff --git a/src/pocsag/main.c b/src/pocsag/main.c new file mode 100644 index 0000000..da5e55f --- /dev/null +++ b/src/pocsag/main.c @@ -0,0 +1,355 @@ +/* POCSAG (Radio-Paging Code #1) main + * + * (C) 2021 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "../libmobile/call.h" +#include "../libmobile/main_mobile.h" +#include "../liboptions/options.h" +#include "../libfm/fm.h" +#include "../anetz/besetztton.h" +#include "pocsag.h" +#include "dsp.h" + +#define MSG_SEND "/tmp/pocsag_msg_send" +#define MSG_RECEIVED "/tmp/pocsag_msg_received" +static int msg_send_fd = -1; + +static int tx = 0; /* we transmit */ +static int rx = 0; /* we receive */ +static int baudrate = 1200; +static int baudrate_given = 0; +static double deviation = 4500; +static int deviation_given = 0; +static double polarity = -1; +static int polarity_given = 0; +static enum pocsag_function function = POCSAG_FUNCTION_NUMERIC; +static const char *message = "1234"; +static enum pocsag_language language = LANGUAGE_DEFAULT; +static uint32_t scan_from = 0; +static uint32_t scan_to = 0; + +void print_help(const char *arg0) +{ + main_mobile_print_help(arg0, "-k 466.230 | -k list"); + /* - - */ + printf(" -T --tx\n"); + printf(" Transmit Eurosignal on given channel, to page a receiver. (default)\n"); + printf(" -R --rx\n"); + printf(" Receive Eurosignal on given channel, so we are the receiver.\n"); + printf(" If none of the options -T nor -R is given, only transmitter is enabled.\n"); + printf(" -B --baud-rate 512 | 1200 | 2400\n"); + printf(" Choose baud rate of transmitter.\n"); + printf(" -D --deviation wide | 4.5 | narrow | 2.5 | \n"); + printf(" Choose deviation of FFSK signal (default %.0f KHz).\n", deviation / 1000.0); + printf(" -P --polarity -1 | nagative | 1 | positive\n"); + printf(" Choose polarity of FFSK signal. 'negative' means that a binary 0 uses\n"); + printf(" positive and a binary 1 negative deviation. (default %s KHz).\n", (polarity < 0) ? "negative" : "positive"); + printf(" -F --function 0..3 | A..D | numeric | beep1 | beep2 | alphanumeric\n"); + printf(" Choose default function when 7 digit only number is dialed.\n"); + printf(" (default %d = %s)\n", function, pocsag_function_name[function]); + printf(" -M --message \"...\"\n"); + printf(" Send this message, if no caller ID was given or of built-in console\n"); + printf(" is used. (default \"%s\").\n", message); + printf(" -L --language\n"); + printf(" Translate German spcial characters from/to UTF-8.\n"); + printf(" -S --scan \n"); + printf(" Scan through given IDs once (no repetition). This can be useful to find\n"); + printf(" the RIC of a vintage receiver. Note that scanning all RICs from 0\n"); + printf(" through 2097151 would take about 16.5 Hours at 1200 Baud and known sub\n"); + printf(" RIC.\n"); + printf("\n"); + printf("File: %s\n", MSG_SEND); + printf(" Write \",0,message\" to it to send a numerical message.\n"); + printf(" Write \",3,message\" to it to send an alphanumerical message.\n"); + printf("File: %s\n", MSG_RECEIVED); + printf(" Read from it to see received messages.\n"); + main_mobile_print_station_id(); + main_mobile_print_hotkeys(); +} + +static void add_options(void) +{ + main_mobile_add_options(); + option_add('T', "tx", 0); + option_add('R', "rx", 0); + option_add('B', "baud-rate", 1); + option_add('D', "deviation", 1); + option_add('F', "function", 1); + option_add('P', "polarity", 1); + option_add('M', "message", 1); + option_add('L', "language", 0); + option_add('S', "scan", 2); +} + +static int handle_options(int short_option, int argi, char **argv) +{ + int rc; + + switch (short_option) { + case 'T': + tx = 1; + break; + case 'R': + rx = 1; + break; + case 'B': + baudrate = atoi(argv[argi]); + if (baudrate != 512 && baudrate != 1200 && baudrate != 2400) { + fprintf(stderr, "Given baud-rate is not 512, 1200 nor 2400, use '-h' for help.\n"); + return -EINVAL; + } + baudrate_given = 1; + break; + case 'D': + if (argv[argi][0] == 'n' || argv[argi][0] == 'N') + deviation = 2500.0; + else if (argv[argi][0] == 'w' || argv[argi][0] == 'W') + deviation = 4500.0; + else + deviation = atof(argv[argi]) * 1000.0; + if (deviation < 1000.0) { + fprintf(stderr, "Given deviation is too low, use higher deviation.\n"); + return -EINVAL; + } + if (deviation > 10000.0) { + fprintf(stderr, "Given deviation is too high, use lower deviation.\n"); + return -EINVAL; + } + deviation_given = 1; + break; + case 'P': + if (argv[argi][0] == 'n' || argv[argi][0] == 'N') + polarity = -1.0; + else if (argv[argi][0] == 'p' || argv[argi][0] == 'P') + polarity = 1.0; + else if (atoi(argv[argi]) == -1) + polarity = -1.0; + else if (atoi(argv[argi]) == 1) + polarity = 1.0; + else { + fprintf(stderr, "Given polarity is not positive nor negative, use '-h' for help.\n"); + return -EINVAL; + } + polarity_given = 1; + break; + case 'F': + rc = pocsag_function_name2value(argv[argi]); + if (rc < 0) { + fprintf(stderr, "Given function is invalid, use '-h' for help.\n"); + return rc; + } + function = rc; + break; + case 'M': + message = options_strdup(argv[argi++]); + break; + case 'L': + language = LANGUAGE_GERMAN; + break; + case 'S': + scan_from = atoi(argv[argi++]); + if (scan_from > 2097151) { + fprintf(stderr, "Given RIC to scan from is out of range!\n"); + return -EINVAL; + } + scan_to = atoi(argv[argi++]) + 1; + if (scan_to > 2097151) { + fprintf(stderr, "Given RIC to scan to is out of range!\n"); + return -EINVAL; + } + break; + default: + return main_mobile_handle_options(short_option, argi, argv); + } + + return 1; +} + +static void myhandler(void) +{ + static char buffer[256]; + static int pos = 0, rc, i; + int space = sizeof(buffer) - pos; + + rc = read(msg_send_fd, buffer + pos, space); + if (rc > 0) { + pos += rc; + if (pos == space) { + fprintf(stderr, "Message buffer overflow!\n"); + pos = 0; + } + /* check for end of line */ + for (i = 0; i < pos; i++) { + if (buffer[i] == '\r' || buffer[i] == '\n') + break; + } + /* send msg */ + if (i < pos) { + buffer[i] = '\0'; + pos = 0; + if (tx) + pocsag_msg_send(language, buffer); + else + PDEBUG(DPOCSAG, DEBUG_ERROR, "Failed to send message, transmitter is not enabled!\n"); + } + } +} + +int msg_receive(const char *text) +{ + FILE *fp; + + fp = fopen(MSG_RECEIVED, "a"); + if (!fp) { + fprintf(stderr, "Failed to open MSG receive file '%s'!\n", MSG_RECEIVED); + return -1; + } + + fprintf(fp, "%s\n", text); + + fclose(fp); + + return 0; +} + +static const struct number_lengths number_lengths[] = { + { 7, "RIC with default function" }, + { 8, "RIC with function (append 0..3)" }, + { 0, NULL } +}; + +int main(int argc, char *argv[]) +{ + int rc, argi; + const char *station_id = ""; + int i; + double frequency; + + /* init common tones */ + init_besetzton(); + + /* init mobile interface */ + main_mobile_init("0123456789", number_lengths, NULL, pocsag_number_valid); + + /* handle options / config file */ + add_options(); + rc = options_config_file(argc, argv, "~/.osmocom/analog/pocsag.conf", handle_options); + if (rc < 0) + return 0; + argi = options_command_line(argc, argv, handle_options); + if (argi <= 0) + return argi; + + if (argi < argc) { + station_id = argv[argi]; + rc = main_mobile_number_ask(station_id, "station ID (RIC)"); + if (rc) + return rc; + } + + if (!num_kanal) { + printf("No channel is specified, Use '-k list' to get a list of all channels.\n\n"); + print_help(argv[0]); + return 0; + } + if (!strcasecmp(kanal[0], "list")) { + pocsag_list_channels(); + goto fail; + } + if (use_sdr) { + /* set device */ + for (i = 0; i < num_kanal; i++) + dsp_device[i] = "sdr"; + num_device = num_kanal; + } + if (num_kanal == 1 && num_device == 0) + num_device = 1; /* use default */ + if (num_kanal != num_device) { + fprintf(stderr, "You need to specify as many sound devices as you have channels.\n"); + exit(0); + } + + /* TX is default */ + if (!tx && !rx) + tx = 1; + + /* create pipe for message sendy */ + unlink(MSG_SEND); + rc = mkfifo(MSG_SEND, 0666); + if (rc < 0) { + fprintf(stderr, "Failed to create mwaaage send FIFO '%s'!\n", MSG_SEND); + goto fail; + } else { + msg_send_fd = open(MSG_SEND, O_RDONLY | O_NONBLOCK); + if (msg_send_fd < 0) { + fprintf(stderr, "Failed to open mwaaage send FIFO! '%s'\n", MSG_SEND); + goto fail; + } + } + + /* inits */ + fm_init(fast_math); + pocsag_init(); + + /* create transceiver instance */ + for (i = 0; i < num_kanal; i++) { + frequency = pocsag_channel2freq(kanal[i], (deviation_given) ? NULL : &deviation, (polarity_given) ? NULL : &polarity, (baudrate_given) ? NULL : &baudrate); + if (frequency == 0.0) { + printf("Invalid channel '%s', Use '-k list' to get a list of all channels.\n\n", kanal[i]); + goto fail; + } + rc = pocsag_create(kanal[i], frequency, dsp_device[i], use_sdr, dsp_samplerate, rx_gain, tx_gain, tx, rx, language, baudrate, deviation, polarity, function, message, scan_from, scan_to, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback); + if (rc < 0) { + fprintf(stderr, "Failed to create \"Sender\" instance. Quitting!\n"); + goto fail; + } + printf("Base station ready, please tune transmitter (or receiver) to %.4f MHz\n", frequency / 1e6); + } + + main_mobile_loop("pocsag", &quit, myhandler, station_id); + +fail: + /* pipe */ + if (msg_send_fd > 0) + close(msg_send_fd); + unlink(MSG_SEND); + + /* destroy transceiver instance */ + while(sender_head) + pocsag_destroy(sender_head); + + /* exits */ + fm_exit(); + pocsag_exit(); + + options_free(); + + return 0; +} + diff --git a/src/pocsag/pocsag.c b/src/pocsag/pocsag.c new file mode 100644 index 0000000..bb0c1eb --- /dev/null +++ b/src/pocsag/pocsag.c @@ -0,0 +1,537 @@ +/* POCSAG (Radio-Paging Code #1) processing + * + * (C) 2021 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define CHAN procsag->sender.kanal + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "../libmobile/call.h" +#include "../libmobile/cause.h" +#include "../libosmocc/message.h" +#include "pocsag.h" +#include "frame.h" +#include "dsp.h" + +static struct channel_info { + double freq_mhz; /* frequency in megahertz */ + double deviation_khz; /* deviation in kilohertz */ + int baudrate; /* default baudrate */ + char *name; /* name of channel */ +} channel_info[] = { + { 466.230, -4.5, 1200, "Scall" }, + { 448.475, -4.5, 1200, "Quix" }, + { 448.425, -4.5, 1200, "TeLMI" }, + { 465.970, -4.5, 1200, "Skyper" }, + { 466.075, -4.5, 1200, "Cityruf" }, + { 466.075, -4.5, 1200, "Euromessage" }, + { 439.9875, -4.5, 1200, "DAPNET" }, + { 0.0, 0.0, 0, NULL} +}; + +static const char pocsag_lang[9][4] = { + { '@', 0xc2, 0xa7, '\0' }, + { '[', 0xc3, 0x84, '\0' }, + { '\\', 0xc3, 0x96, '\0' }, + { ']', 0xc3, 0x9c, '\0' }, + { '{', 0xc3, 0xa4, '\0' }, + { '|', 0xc3, 0xb6, '\0' }, + { '}', 0xc3, 0xbc, '\0' }, + { '~', 0xc3, 0x9f, '\0' }, + { '\0' }, +}; + + +void pocsag_list_channels(void) +{ + int i; + char text[16]; + + for (i = 0; channel_info[i].name; i++) { + if (i == 0) { + printf("\nFrequency\tDeviation\tPolarity\tBaudrate\tChannel Name\n"); + printf("--------------------------------------------------------------------------------\n"); + } + if (channel_info[i].freq_mhz * 1e3 == floor(channel_info[i].freq_mhz * 1e3)) + sprintf(text, "%.3f MHz", channel_info[i].freq_mhz); + else + sprintf(text, "%.4f MHz", channel_info[i].freq_mhz); + printf("%s\t%.3f KHz\t%s\t%d\t\t%s\n", text, fabs(channel_info[i].deviation_khz), (channel_info[i].deviation_khz < 0) ? "negative" : "positive", channel_info[i].baudrate, channel_info[i].name); + } + printf("-> Give channel name or any frequency in MHz.\n"); + printf("\n"); +} + +/* Convert channel name to frequency number of base station. */ +double pocsag_channel2freq(const char *kanal, double *deviation, double *polarity, int *baudrate) +{ + int i; + + for (i = 0; channel_info[i].name; i++) { + if (!strcasecmp(channel_info[i].name, kanal)) { + if (deviation) + *deviation = fabs(channel_info[i].deviation_khz) * 1e3; + if (polarity) + *polarity = (channel_info[i].deviation_khz > 0) ? 1.0 : -1.0; + if (baudrate) + *baudrate = channel_info[i].baudrate; + return channel_info[i].freq_mhz * 1e6; + } + } + + return atof(kanal) * 1e6; +} + +const char *pocsag_state_name[] = { + "IDLE", + "PREAMBLE", + "MESSAGE", +}; + +const char *pocsag_function_name[4] = { + "numeric", + "beep1", + "beep2", + "alphanumeric", +}; + +int pocsag_function_name2value(const char *text) +{ + int i; + + for (i = 0; i < 4; i++) { + if (!strcasecmp(pocsag_function_name[i], text)) + return i; + if (text[0] == '0' + i && text[1] == '\0') + return i; + if (text[0] == 'A' + i && text[1] == '\0') + return i; + if (text[0] == 'a' + i && text[1] == '\0') + return i; + } + + return -EINVAL; +} + +/* check if number is a valid station ID */ +const char *pocsag_number_valid(const char *number) +{ + int i; + int ric = 0; + + /* assume that the number has valid length(s) and digits */ + + for (i = 0; i < 7; i++) + ric = ric * 10 + number[i] - '0'; + if (ric > 2097151) + return "Maximum allowed RIC is (2^21)-1. (2097151)"; + + if ((ric & 0xfffffff8) == 2007664) + return "Illegal RIC. (Used for idle codeword)"; + + if (number[7] && (number[7] < '0' || number[7] > '3')) + return "Illegal function digit #8 (Use 0..3 only)"; + return NULL; +} + +int pocsag_init(void) +{ + return 0; +} + +void pocsag_exit(void) +{ +} + +const char *print_ric(pocsag_msg_t *msg) +{ + static char text[16]; + + sprintf(text, "%07d/%c", msg->ric, msg->function + '0'); + + return text; +} + +static void pocsag_display_status(void) +{ + sender_t *sender; + pocsag_t *pocsag; + pocsag_msg_t *msg; + + display_status_start(); + for (sender = sender_head; sender; sender = sender->next) { + pocsag = (pocsag_t *) sender; + display_status_channel(pocsag->sender.kanal, NULL, pocsag_state_name[pocsag->state]); + for (msg = pocsag->msg_list; msg; msg = msg->next) + display_status_subscriber(print_ric(msg), NULL); + } + display_status_end(); +} + +void pocsag_new_state(pocsag_t *pocsag, enum pocsag_state new_state) +{ + if (pocsag->state == new_state) + return; + PDEBUG(DPOCSAG, DEBUG_DEBUG, "State change: %s -> %s\n", pocsag_state_name[pocsag->state], pocsag_state_name[new_state]); + pocsag->state = new_state; + pocsag_display_status(); +} + +/* Create msg instance */ +static pocsag_msg_t *pocsag_msg_create(pocsag_t *pocsag, uint32_t callref, uint32_t ric, enum pocsag_function function, const char *text) +{ + pocsag_msg_t *msg, **msgp; + + PDEBUG(DPOCSAG, DEBUG_INFO, "Creating msg instance to page RIC '%d' / function '%d' (%s).\n", ric, function, pocsag_function_name[function]); + + /* create */ + msg = calloc(1, sizeof(*msg)); + if (!msg) { + PDEBUG(DPOCSAG, DEBUG_ERROR, "No mem!\n"); + abort(); + } + if (strlen(text) > sizeof(msg->data)) { + PDEBUG(DPOCSAG, DEBUG_ERROR, "Text too long!\n"); + return NULL; + } + + /* init */ + msg->callref = callref; + msg->ric = ric; + msg->function = function; + msg->repeat = 0; + strncpy(msg->data, text, sizeof(msg->data)); + msg->data_length = (strlen(text) < sizeof(msg->data)) ? strlen(text) : sizeof(msg->data); + + /* link */ + msg->pocsag = pocsag; + msgp = &pocsag->msg_list; + while ((*msgp)) + msgp = &(*msgp)->next; + (*msgp) = msg; + + /* kick transmitter */ + if (pocsag->state == POCSAG_IDLE) { + pocsag_new_state(pocsag, POCSAG_PREAMBLE); + pocsag->word_count = 0; + } else + pocsag_display_status(); + + return msg; +} + +/* Destroy msg instance */ +void pocsag_msg_destroy(pocsag_msg_t *msg) +{ + pocsag_msg_t **msgp; + + /* unlink */ + msgp = &msg->pocsag->msg_list; + while ((*msgp) != msg) + msgp = &(*msgp)->next; + (*msgp) = msg->next; + + /* remove from current transmitting message */ + if (msg == msg->pocsag->current_msg) + msg->pocsag->current_msg = NULL; + + /* destroy */ + free(msg); + + /* update display */ + pocsag_display_status(); +} + +void pocsag_msg_receive(enum pocsag_language language, const char *channel, uint32_t ric, enum pocsag_function function, const char *message) +{ + char text[256 + strlen(message) * 4], *p; + struct timeval tv; + struct tm *tm; + int i, j; + + gettimeofday(&tv, NULL); + tm = localtime(&tv.tv_sec); + + sprintf(text, "%04d-%02d-%02d %02d:%02d:%02d.%03d @%s %d,%s", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)(tv.tv_usec / 10000.0), channel, ric, pocsag_function_name[function]); + p = strchr(text, '\0'); + + if (message[0]) { + *p++ = ','; + + if (language == LANGUAGE_DEFAULT) { + strcpy(p, message); + p += strlen(p); + } else { + for (i = 0; message[i]; i++) { + /* decode special chracter */ + for (j = 0; pocsag_lang[j][0]; j++) { + if (pocsag_lang[j][0] == message[i]) + break; + } + /* if character matches */ + if (pocsag_lang[j][0]) { + strcpy(p, pocsag_lang[j] + 1); + p += strlen(p); + } else + *p++ = message[i]; + } + } + } + + *p++ = '\0'; + + msg_receive(text); +} + +/* Create transceiver instance and link to a list. */ +int pocsag_create(const char *kanal, double frequency, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, int tx, int rx, enum pocsag_language language, int baudrate, double deviation, double polarity, enum pocsag_function function, const char *message, uint32_t scan_from, uint32_t scan_to, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback) +{ + pocsag_t *pocsag; + int rc; + + pocsag = calloc(1, sizeof(*pocsag)); + if (!pocsag) { + PDEBUG(DPOCSAG, DEBUG_ERROR, "No memory!\n"); + return -ENOMEM; + } + + PDEBUG(DPOCSAG, DEBUG_DEBUG, "Creating 'POCSAG' instance for 'Kanal' = %s (sample rate %d).\n", kanal, samplerate); + + /* init general part of transceiver */ + rc = sender_create(&pocsag->sender, kanal, frequency, frequency, device, use_sdr, samplerate, rx_gain, tx_gain, 0, 0, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, PAGING_SIGNAL_NONE); + if (rc < 0) { + PDEBUG(DPOCSAG, DEBUG_ERROR, "Failed to init transceiver process!\n"); + goto error; + } + + /* init audio processing */ + rc = dsp_init_sender(pocsag, samplerate, (double)baudrate, deviation, polarity); + if (rc < 0) { + PDEBUG(DPOCSAG, DEBUG_ERROR, "Failed to init audio processing!\n"); + goto error; + } + + pocsag->tx = tx; + pocsag->rx = rx; + pocsag->language = language; + pocsag->default_function = function; + pocsag->default_message = message; + pocsag->scan_from = scan_from; + pocsag->scan_to = scan_to; + + pocsag_display_status(); + + PDEBUG(DPOCSAG, DEBUG_NOTICE, "Created 'Kanal' %s\n", kanal); + + if (pocsag->sender.loopback) + pocsag_msg_create(pocsag, 0, 1234567, POCSAG_FUNCTION_NUMERIC, "1234"); + + return 0; + +error: + pocsag_destroy(&pocsag->sender); + + return rc; +} + +/* Destroy transceiver instance and unlink from list. */ +void pocsag_destroy(sender_t *sender) +{ + pocsag_t *pocsag = (pocsag_t *) sender; + + PDEBUG(DPOCSAG, DEBUG_DEBUG, "Destroying 'POCSAG' instance for 'Kanal' = %s.\n", sender->kanal); + + while (pocsag->msg_list) + pocsag_msg_destroy(pocsag->msg_list); + dsp_cleanup_sender(pocsag); + sender_destroy(&pocsag->sender); + free(pocsag); +} + +/* application sends ud a message, we need to deliver */ +void pocsag_msg_send(enum pocsag_language language, const char *text) +{ + char buffer[strlen(text) + 1], *p = buffer, *ric_string, *function_string, *message; + uint32_t ric; + uint8_t function; + pocsag_t *pocsag; + int i, j, k; + int rc; + + strcpy(buffer, text); + ric_string = strsep(&p, ","); + function_string = strsep(&p, ","); + message = p; + + if (!ric_string || !function_string) { +inval: + PDEBUG(DNMT, DEBUG_NOTICE, "Given message MUST be in the following format: RIC,function[,] (function must be A = 0 = numeric, B = 1 or C = 2 = beep, D = 3 = alphanumeric)\n"); + return; + } + ric = atoi(ric_string); + if (ric > 2097151) { + PDEBUG(DNMT, DEBUG_NOTICE, "Illegal RIC %d. Maximum allowed RIC is (2^21)-1. (2097151)\n", ric); + goto inval; + } + + if (ric == 1003832) { + PDEBUG(DNMT, DEBUG_NOTICE, "Illegal RIC 1003832. (Used as idle codeword)\n"); + goto inval; + } + + rc = pocsag_function_name2value(function_string); + if (rc < 0) { + PDEBUG(DNMT, DEBUG_NOTICE, "Illegal function '%s'.\n", function_string); + goto inval; + } + function = rc; + + if (message && (function == 1 || function == 2)) { + PDEBUG(DNMT, DEBUG_NOTICE, "Message text is not allowed with function %d.\n", function); + goto inval; + } + + if (message && language != LANGUAGE_DEFAULT) { + i = 0; + p = message; + while (*p) { + /* encode special chracter */ + for (j = 0; pocsag_lang[j][0]; j++) { + for (k = 0; pocsag_lang[j][k + 1]; k++) { + /* implies that (p[k] == '\0') causes to break */ + if (p[k] != pocsag_lang[j][k + 1]) + break; + } + if (!pocsag_lang[j][k + 1]) + break; + } + /* if character matches */ + if (pocsag_lang[j][0]) { + message[i++] = pocsag_lang[j][0]; + p += k; + } else + message[i++] = *p++; + } + message[i] = '\0'; + } + + if (!message) + message=""; + + PDEBUG(DNMT, DEBUG_INFO, "Message for ID '%d/%d' with text '%s'\n", ric, function, message); + + pocsag = (pocsag_t *) sender_head; + pocsag_msg_create(pocsag, 0, ric, function, message); +} + +void call_down_clock(void) +{ +} + +/* Call control starts call towards paging network. */ +int call_down_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing) +{ + char channel = '\0'; + sender_t *sender; + pocsag_t *pocsag; + uint32_t ric; + enum pocsag_function function; + const char *message; + int i; + pocsag_msg_t *msg; + + /* find transmitter */ + for (sender = sender_head; sender; sender = sender->next) { + /* skip channels that are different than requested */ + if (channel && sender->kanal[0] != channel) + continue; + pocsag = (pocsag_t *) sender; + /* check if base station cannot transmit */ + if (!pocsag->tx) + continue; + break; + } + if (!sender) { + if (channel) + PDEBUG(DPOCSAG, DEBUG_NOTICE, "Cannot page, because given station not available, rejecting!\n"); + else + PDEBUG(DPOCSAG, DEBUG_NOTICE, "Cannot page, no trasmitting station available, rejecting!\n"); + return -CAUSE_NOCHANNEL; + } + + /* get RIC and function */ + for (ric = 0, i = 0; i < 7; i++) + ric = ric * 10 + dialing[i] - '0'; + if (dialing[7] >= '0' && dialing[7] <= '3') + function = dialing[7]- '0'; + else + function = pocsag->default_function; + + /* get message */ + if (caller_id[0]) + message = caller_id; + else + message = pocsag->default_message; + + /* create call process to page station */ + msg = pocsag_msg_create(pocsag, callref, ric, function, message); + if (!msg) + return -CAUSE_INVALNUMBER; + return -CAUSE_NORMAL; + + return 0; +} + +void call_down_answer(int __attribute__((unused)) callref) +{ +} + + +static void _release(int __attribute__((unused)) callref, int __attribute__((unused)) cause) +{ + PDEBUG(DPOCSAG, DEBUG_INFO, "Call has been disconnected by network.\n"); +} + +void call_down_disconnect(int callref, int cause) +{ + _release(callref, cause); + + call_up_release(callref, cause); +} + +/* Call control releases call toward mobile station. */ +void call_down_release(int callref, int cause) +{ + _release(callref, cause); +} + +/* Receive audio from call instance. */ +void call_down_audio(int __attribute__((unused)) callref, sample_t __attribute__((unused)) *samples, int __attribute__((unused)) count) +{ +} + +void dump_info(void) {} + diff --git a/src/pocsag/pocsag.h b/src/pocsag/pocsag.h new file mode 100644 index 0000000..311e81a --- /dev/null +++ b/src/pocsag/pocsag.h @@ -0,0 +1,111 @@ +#include "../libmobile/sender.h" + +enum pocsag_function { + POCSAG_FUNCTION_NUMERIC = 0, + POCSAG_FUNCTION_BEEP1, + POCSAG_FUNCTION_BEEP2, + POCSAG_FUNCTION_ALPHA, +}; + +extern const char *pocsag_function_name[4]; + +enum pocsag_state { + POCSAG_IDLE = 0, + POCSAG_PREAMBLE, + POCSAG_MESSAGE, +}; + +enum pocsag_language { + LANGUAGE_DEFAULT = 0, + LANGUAGE_GERMAN, +}; + +struct pocsag; + +/* instance of outgoing message */ +typedef struct pocsag_msg { + struct pocsag_msg *next; + struct pocsag *pocsag; + int callref; /* call reference */ + uint32_t ric; /* current pager ID */ + enum pocsag_function function; /* current function */ + char data[256]; /* message to be transmitted */ + int data_length; /* length of message that is not 0-terminated */ + int data_index; /* current character transmitting */ + int bit_index; /* current bit transmitting */ + int repeat; /* how often the message is sent */ +} pocsag_msg_t; + +/* instance of pocsag transmitter/receiver */ +typedef struct pocsag { + sender_t sender; + + /* system info */ + int tx; /* can transmit */ + int rx; /* can receive */ + enum pocsag_language language; /* special characters */ + enum pocsag_function default_function; /* default function, if not given by caller */ + const char *default_message; /* default message, if caller has no caller ID */ + + /* tx states */ + enum pocsag_state state; /* state (idle, preamble, message) */ + pocsag_msg_t *current_msg; /* msg, if message codewords are transmitted */ + int word_count; /* counter for codewords */ + int idle_count; /* counts when to go idle */ + uint32_t scan_from, scan_to; /* if not equal: scnning mode */ + + /* rx states */ + int rx_msg_valid; /* currently in receiving message state */ + uint32_t rx_msg_ric; /* ric of message */ + enum pocsag_function rx_msg_function; /* function of message */ + char rx_msg_data[256]; /* data buffer */ + int rx_msg_data_length; /* complete characters received */ + int rx_msg_bit_index; /* current bit received for alphanumeric */ + + /* calls */ + pocsag_msg_t *msg_list; /* linked list of all calls */ + + /* display measurements */ + dispmeasparam_t *dmp_tone_level; + dispmeasparam_t *dmp_tone_quality; + + /* dsp states */ + double fsk_deviation; /* deviation of FSK signal on sound card */ + double fsk_polarity; /* polarity of FSK signal (-1.0 = bit '1' is down) */ + sample_t fsk_ramp_up[256]; /* samples of upward ramp shape */ + sample_t fsk_ramp_down[256]; /* samples of downward ramp shape */ + double fsk_bitduration; /* duration of a bit in samples */ + double fsk_bitstep; /* fraction of a bit each sample */ + sample_t *fsk_tx_buffer; /* tx buffer for one data block */ + int fsk_tx_buffer_size; /* size of tx buffer (in samples) */ + int fsk_tx_buffer_length; /* usage of buffer (in samples) */ + int fsk_tx_buffer_pos; /* current position sending buffer */ + double fsk_tx_phase; /* current bit position */ + uint8_t fsk_tx_lastbit; /* last bit of last message, to correctly ramp */ + double fsk_rx_phase; /* current sample position */ + uint8_t fsk_rx_lastbit; /* last bit of last message, to detect level */ + uint32_t fsk_rx_word; /* shift register to receive codeword */ + int fsk_rx_sync; /* counts down to next sync */ + int fsk_rx_index; /* counts bits of received codeword */ +} pocsag_t; + +int msg_receive(const char *text); + +int pocsag_function_name2value(const char *text); +void pocsag_list_channels(void); +double pocsag_channel2freq(const char *kanal, double *deviation, double *polarity, int *baudrate); +const char *pocsag_number_valid(const char *number); +void pocsag_add_id(const char *id); +int pocsag_init(void); +void pocsag_exit(void); +int pocsag_init(void); +void pocsag_exit(void); +void pocsag_new_state(pocsag_t *pocsag, enum pocsag_state new_state); +void pocsag_msg_receive(enum pocsag_language language, const char *channel, uint32_t ric, enum pocsag_function function, const char *message); +int pocsag_create(const char *kanal, double frequency, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, int tx, int rx, enum pocsag_language language, int baudrate, double deviation, double polarity, enum pocsag_function function, const char *message, uint32_t scan_from, uint32_t scan_to, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback); +void pocsag_destroy(sender_t *sender); +void pocsag_msg_send(enum pocsag_language language, const char *text); +void pocsag_msg_destroy(pocsag_msg_t *msg); +void pocsag_get_id(pocsag_t *euro, char *id); +void pocsag_receive_id(pocsag_t *euro, char *id); +