This commit is contained in:
Andreas Eversberg 2021-10-21 21:02:20 +02:00
parent 869625cfcd
commit 7fa5f85731
17 changed files with 1889 additions and 33 deletions

1
.gitignore vendored
View File

@ -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

1
README
View File

@ -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!

View File

@ -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

View File

@ -125,6 +125,7 @@ Additional features:
<li><a href="magnetic.html">C-Netz Magnetic Card</a></li>
<li>Zeitansage (German talking clock)</li>
<li>C-Netz FuVSt (MSC to control a real base station)</li>
<li>POCSAG</li>
</ul>
</td></tr></table></center>

View File

@ -55,6 +55,7 @@ SUBDIRS += \
mpt1327 \
jolly \
eurosignal \
pocsag \
tv \
radio \
zeitansage \

View File

@ -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 */

View File

@ -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" },

View File

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

51
src/pocsag/Makefile.am Normal file
View File

@ -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

288
src/pocsag/dsp.c Normal file
View File

@ -0,0 +1,288 @@
/* POCSAG signal processing
*
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu>
* 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 <http://www.gnu.org/licenses/>.
*/
#define CHAN pocsag->sender.kanal
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#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;
}

4
src/pocsag/dsp.h Normal file
View File

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

494
src/pocsag/frame.c Normal file
View File

@ -0,0 +1,494 @@
/* POCSAG framing
*
* (C) 2021 by Andreas Eversberg <jolly@eversberg.eu>
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#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] = {
"<NUL>",
"<SOH>",
"<STX>",
"<ETX>",
"<EOT>",
"<ENQ>",
"<ACK>",
"<BEL>",
"<BS>",
"<HT>",
"<LF>",
"<VT>",
"<FF>",
"<CR>",
"<SO>",
"<SI>",
"<DLE>",
"<DC1>",
"<DC2",
"<DC3>",
"<DC4>",
"<NAK>",
"<SYN>",
"<ETB>",
"<CAN>",
"<EM>",
"<SUB>",
"<ESC>",
"<FS>",
"<GS>",
"<RS>",
"<US>",
};
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, "<DEL>");
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);
;
}
}
}

4
src/pocsag/frame.h Normal file
View File

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

6
src/pocsag/image.c Normal file
View File

@ -0,0 +1,6 @@
#include <stdio.h>
const char *aaimage[] = {
NULL
};

355
src/pocsag/main.c Normal file
View File

@ -0,0 +1,355 @@
/* POCSAG (Radio-Paging Code #1) main
*
* (C) 2021 by Andreas Eversberg <jolly@eversberg.eu>
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#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 | <other KHz>\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 <from> <to>\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 \"<ric>,0,message\" to it to send a numerical message.\n");
printf(" Write \"<ric>,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;
}

537
src/pocsag/pocsag.c Normal file
View File

@ -0,0 +1,537 @@
/* POCSAG (Radio-Paging Code #1) processing
*
* (C) 2021 by Andreas Eversberg <jolly@eversberg.eu>
* 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 <http://www.gnu.org/licenses/>.
*/
#define CHAN procsag->sender.kanal
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <sys/time.h>
#include <time.h>
#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[,<message with comma and spaces>] (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) {}

111
src/pocsag/pocsag.h Normal file
View File

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