aboutsummaryrefslogtreecommitdiffstats
path: root/src/pocsag/pocsag.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pocsag/pocsag.c')
-rw-r--r--src/pocsag/pocsag.c602
1 files changed, 602 insertions, 0 deletions
diff --git a/src/pocsag/pocsag.c b/src/pocsag/pocsag.c
new file mode 100644
index 0000000..099fb04
--- /dev/null
+++ b/src/pocsag/pocsag.c
@@ -0,0 +1,602 @@
+/* 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 pocsag->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 "../liblogging/logging.h"
+#include "../libmobile/call.h"
+#include "../libmobile/cause.h"
+#include <osmocom/cc/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++) {
+ if (number[i] < '0' || number[i] > '9')
+ return "Illegal RIC digit (Use 0..9 only)";
+ 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') && !(number[7] >= 'A' && number[7] <= 'D'))
+ return "Illegal function digit #8 (Use 0..3 only)";
+ return NULL;
+}
+
+int pocsag_init(void)
+{
+ return 0;
+}
+
+void pocsag_exit(void)
+{
+}
+
+static 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;
+ LOGP(DPOCSAG, LOGL_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 *message, size_t message_length)
+{
+ pocsag_msg_t *msg, **msgp;
+
+ LOGP(DPOCSAG, LOGL_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) {
+ LOGP(DPOCSAG, LOGL_ERROR, "No mem!\n");
+ abort();
+ }
+ if (message_length > sizeof(msg->data)) {
+ LOGP(DPOCSAG, LOGL_ERROR, "Text too long!\n");
+ message_length = sizeof(msg->data);
+ }
+
+ /* init */
+ msg->callref = callref;
+ msg->ric = ric;
+ msg->function = function;
+ memcpy(msg->data, message, message_length);
+ msg->data_length = message_length;
+ msg->padding = pocsag->padding;
+
+ /* 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();
+}
+
+static int pocsag_scan_or_loopback(pocsag_t *pocsag)
+{
+ if (pocsag->scan_from < pocsag->scan_to) {
+ char message[16];
+
+ switch (pocsag->default_function) {
+ case POCSAG_FUNCTION_NUMERIC:
+ sprintf(message, "%05d", pocsag->scan_from / 100);
+ break;
+ case POCSAG_FUNCTION_ALPHA:
+ sprintf(message, "%02x", pocsag->scan_from / 10000);
+ break;
+ default:
+ message[0] = '\0';
+ }
+ LOGP_CHAN(DPOCSAG, LOGL_NOTICE, "Transmitting %s message '%s' with RIC '%d'.\n", pocsag_function_name[pocsag->default_function], message, pocsag->scan_from);
+ pocsag_msg_create(pocsag, 0, pocsag->scan_from, pocsag->default_function, message, strlen(message));
+ pocsag->scan_from++;
+ return 1;
+ }
+
+ if (pocsag->sender.loopback) {
+ LOGP(DPOCSAG, LOGL_INFO, "Sending message for loopback test.\n");
+ pocsag_msg_create(pocsag, 0, 1234567, POCSAG_FUNCTION_NUMERIC, "1234", 4);
+ return 1;
+ }
+
+ return 0;
+}
+
+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, char padding, 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) {
+ LOGP(DPOCSAG, LOGL_ERROR, "No memory!\n");
+ return -ENOMEM;
+ }
+
+ LOGP(DPOCSAG, LOGL_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) {
+ LOGP(DPOCSAG, LOGL_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) {
+ LOGP(DPOCSAG, LOGL_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->padding = padding;
+
+ pocsag_display_status();
+
+ LOGP(DPOCSAG, LOGL_NOTICE, "Created 'Kanal' %s\n", kanal);
+
+ pocsag_scan_or_loopback(pocsag);
+
+ 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;
+
+ LOGP(DPOCSAG, LOGL_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 us a message, we need to deliver */
+void pocsag_msg_send(enum pocsag_language language, const char *text, size_t text_length)
+{
+ char ric_string[text_length + 1];
+ char function_string[text_length + 1];
+ char message[text_length];
+ uint32_t ric;
+ uint8_t function;
+ pocsag_t *pocsag;
+ int message_length = 0;
+ int i, ii, j, k;
+ int rc;
+
+ for (i = 0; text_length; i++) {
+ if (*text == ',')
+ break;
+ ric_string[i] = *text++;
+ text_length--;
+ }
+ ric_string[i] = '\0';
+ if (!text_length) {
+inval:
+ LOGP(DNMT, LOGL_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;
+ }
+ text++;
+ text_length--;
+ for (i = 0; text_length; i++) {
+ if (*text == ',')
+ break;
+ function_string[i] = *text++;
+ text_length--;
+ }
+ function_string[i] = '\0';
+ if (text_length) {
+ text++;
+ text_length--;
+ message_length = scan_message(text, text_length, message, sizeof(message));
+ }
+
+ ric = atoi(ric_string);
+ if (ric > 2097151) {
+ LOGP(DNMT, LOGL_NOTICE, "Illegal RIC %d. Maximum allowed RIC is (2^21)-1. (2097151)\n", ric);
+ goto inval;
+ }
+
+ if (ric == 1003832) {
+ LOGP(DNMT, LOGL_NOTICE, "Illegal RIC 1003832. (Used as idle codeword)\n");
+ goto inval;
+ }
+
+ rc = pocsag_function_name2value(function_string);
+ if (rc < 0) {
+ LOGP(DNMT, LOGL_NOTICE, "Illegal function '%s'.\n", function_string);
+ goto inval;
+ }
+ function = rc;
+
+ if (message_length && (function == 1 || function == 2)) {
+ LOGP(DNMT, LOGL_NOTICE, "Message text is not allowed with function %d.\n", function);
+ goto inval;
+ }
+
+ if (message_length && language != LANGUAGE_DEFAULT) {
+ i = 0;
+ /* input counter is ii, output counter is i */
+ for (ii = 0; ii < message_length; i++) {
+ /* encode special chracter */
+ for (j = 0; pocsag_lang[j][0]; j++) {
+ for (k = 0; pocsag_lang[j][k + 1]; k++) {
+ /* break if input buffer ends */
+ if (ii + k == message_length)
+ break;
+ /* break if string does not match */
+ if (message[ii + k] != pocsag_lang[j][k + 1])
+ break;
+ }
+ /* break, if k-loop was completed */
+ if (!pocsag_lang[j][k + 1])
+ break;
+ }
+ /* if character matches (k-loop was completed, j-loop not) */
+ if (pocsag_lang[j][0]) {
+ message[i] = pocsag_lang[j][0];
+ ii += k;
+ } else
+ message[i] = message[ii++];
+ }
+ message_length = i;
+ }
+
+ LOGP(DNMT, LOGL_INFO, "Message for ID '%d/%d' with text '%s'\n", ric, function, print_message(message, message_length));
+
+ pocsag = (pocsag_t *) sender_head;
+ pocsag_msg_create(pocsag, 0, ric, function, message, message_length);
+}
+
+void call_down_clock(void)
+{
+}
+
+/* Call control starts call towards paging network. */
+int call_down_setup(int callref, const char *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)
+ LOGP(DPOCSAG, LOGL_NOTICE, "Cannot page, because given station not available, rejecting!\n");
+ else
+ LOGP(DPOCSAG, LOGL_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 if (dialing[7] >= 'a' && dialing[7] <= 'd')
+ function = dialing[7]- 'A';
+ else if (dialing[7] >= 'A' && dialing[7] <= 'D')
+ function = dialing[7]- 'A';
+ 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, strlen(message));
+ if (!msg)
+ return -CAUSE_INVALNUMBER;
+ return -CAUSE_NORMAL;
+
+ return 0;
+}
+
+/* message was transmitted */
+void pocsag_msg_done(pocsag_t *pocsag)
+{
+ /* start scanning, if enabled, otherwise send loopback sequence, if enabled */
+ pocsag_scan_or_loopback(pocsag);
+}
+
+void call_down_answer(int __attribute__((unused)) callref, struct timeval __attribute__((unused)) *tv_meter)
+{
+}
+
+
+static void _release(int __attribute__((unused)) callref, int __attribute__((unused)) cause)
+{
+ LOGP(DPOCSAG, LOGL_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(void __attribute__((unused)) *decoder, void __attribute__((unused)) *decoder_priv, int __attribute__((unused)) callref, uint16_t __attribute__((unused)) sequence, uint8_t __attribute__((unused)) marker, uint32_t __attribute__((unused)) timestamp, uint32_t __attribute__((unused)) ssrc, uint8_t __attribute__((unused)) *payload, int __attribute__((unused)) payload_len)
+{
+}
+
+void dump_info(void) {}
+