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);
+