multimon-ng是一个可以解码pocsag的程序,我想修改一下,让他可以解码一段pocasg数据中有两个地址码的,比如2025-11-23 11:08:12: POCSAG1200: Address: 1234001 Function: 1 Numeric: 123456
2025-11-23 11:08:12: POCSAG1200: Address: 1234003 Function: 1 Numeric: 20201411111112121212121542124121212这是收到的消息,这两条应该是一条信息,因为第一条放不下,所以变成了两条,但是解码会把他分开解码,这是multimon-ng的代码 /*
* pocsag.c -- POCSAG protocol decoder
*
* Copyright (C) 1996
* Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu)
*
* Copyright (C) 2012-2014
* Elias Oenal (multimon-ng@eliasoenal.com)
*
* Copyright (C) 2022
* Tobias Girstmair (https://gir.st/)
*
* Copyright (C) 2024
* Jason Lingohr (jason@lucid.net.au)
*
* POCSAG (Post Office Code Standard Advisory Group)
* Radio Paging Decoder
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* ---------------------------------------------------------------------- */
#include "multimon.h"
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "cJSON.h"
/* ---------------------------------------------------------------------- */
//#define CHARSET_LATIN1
//#define CHARSET_UTF8 //ÄÖÜäöüß
/* ---------------------------------------------------------------------- */
/*
* some codewords with special POCSAG meaning
*/
#define POCSAG_SYNC 0x7cd215d8
#define POCSAG_IDLE 0x7a89c197
#define POCSAG_SYNCINFO 0x7cf21436 // what is this value?
#define POCSAG_SYNC_WORDS ((2000000 >> 3) << 13)
#define POCSAG_MESSAGE_DETECTION 0x80000000 // Most significant bit is a one
#define CAESAR_ALPHA 0
#define CAESAR_SKYPER 1 // skyper messages are ROT-1 enciphered
#define POSCAG
/* ---------------------------------------------------------------------- */
int pocsag_mode = POCSAG_MODE_STANDARD;
int pocsag_invert_input = 0;
int pocsag_error_correction = 2;
int pocsag_show_partial_decodes = 0;
int pocsag_heuristic_pruning = 0;
int pocsag_prune_empty = 0;
extern int json_mode;
/* ---------------------------------------------------------------------- */
enum states{
NO_SYNC = 0, //0b00000000
SYNC = 64, //0b10000000
LOSING_SYNC = 65, //0b10000001
LOST_SYNC = 66, //0b10000010
ADDRESS = 67, //0b10000011
MESSAGE = 68, //0b10000100
END_OF_MESSAGE = 69, //0b10000101
};
static inline unsigned char even_parity(uint32_t data)
{
unsigned int temp = data ^ (data >> 16);
temp = temp ^ (temp >> 8);
temp = temp ^ (temp >> 4);
temp = temp ^ (temp >> 2);
temp = temp ^ (temp >> 1);
return temp & 1;
}
/* ---------------------------------------------------------------------- */
/*
* the code used by POCSAG is a (n=31,k=21) BCH Code with dmin=5,
* thus it could correct two bit errors in a 31-Bit codeword.
* It is a systematic code.
* The generator polynomial is:
* g(x) = x^10+x^9+x^8+x^6+x^5+x^3+1
* The parity check polynomial is:
* h(x) = x^21+x^20+x^18+x^16+x^14+x^13+x^12+x^11+x^8+x^5+x^3+1
* g(x) * h(x) = x^n+1
*/
#define BCH_POLY 03551 /* octal */
#define BCH_N 31
#define BCH_K 21
/* ---------------------------------------------------------------------- */
static unsigned int pocsag_syndrome(uint32_t data)
{
uint32_t shreg = data >> 1; /* throw away parity bit */
uint32_t mask = 1L << (BCH_N-1), coeff = BCH_POLY << (BCH_K-1);
int n = BCH_K;
for(; n > 0; mask >>= 1, coeff >>= 1, n--)
if (shreg & mask)
shreg ^= coeff;
if (even_parity(data))
shreg |= (1 << (BCH_N - BCH_K));
verbprintf(9, "BCH syndrome: data: %08lx syn: %08lx\n", data, shreg);
return shreg;
}
/* ---------------------------------------------------------------------- */
// ISO 646 national variant: US / IRV (1991)
char *trtab[128] = {
"<NUL>", // 0x0
"<SOH>", // 0x1
"<STX>", // 0x2
"<ETX>", // 0x3
"<EOT>", // 0x4
"<ENQ>", // 0x5
"<ACK>", // 0x6
"<BEL>", // 0x7
"<BS>", // 0x8
"<HT>", // 0x9
"<LF>", // 0xa
"<VT>", // 0xb
"<FF>", // 0xc
"<CR>", // 0xd
"<SO>", // 0xe
"<SI>", // 0xf
"<DLE>", // 0x10
"<DC1>", // 0x11
"<DC2>", // 0x12
"<DC3>", // 0x13
"<DC4>", // 0x14
"<NAK>", // 0x15
"<SYN>", // 0x16
"<ETB>", // 0x17
"<CAN>", // 0x18
"<EM>", // 0x19
"<SUB>", // 0x1a
"<ESC>", // 0x1b
"<FS>", // 0x1c
"<GS>", // 0x1d
"<RS>", // 0x1e
"<US>", // 0x1f
" ", // 0x20
"!", // 0x21
"\"", // 0x22
// national variant
"#", // 0x23
"$", // 0x24
"%", // 0x25
"&", // 0x26
"'", // 0x27
"(", // 0x28
")", // 0x29
"*", // 0x2a
"+", // 0x2b
",", // 0x2c
"-", // 0x2d
".", // 0x2e
"/", // 0x2f
"0", // 0x30
"1", // 0x31
"2", // 0x32
"3", // 0x33
"4", // 0x34
"5", // 0x35
"6", // 0x36
"7", // 0x37
"8", // 0x38
"9", // 0x39
":", // 0x3a
";", // 0x3b
"<", // 0x3c
"=", // 0x3d
">", // 0x3e
"?", // 0x3f
"@", // 0x40
"A", // 0x41
"B", // 0x42
"C", // 0x43
"D", // 0x44
"E", // 0x45
"F", // 0x46
"G", // 0x47
"H", // 0x48
"I", // 0x49
"J", // 0x4a
"K", // 0x4b
"L", // 0x4c
"M", // 0x4d
"N", // 0x4e
"O", // 0x4f
"P", // 0x50
"Q", // 0x51
"R", // 0x52
"S", // 0x53
"T", // 0x54
"U", // 0x55
"V", // 0x56
"W", // 0x57
"X", // 0x58
"Y", // 0x59
"Z", // 0x5a
// national variant
"[", // 0x5b
"\\", // 0x5c
"]", // 0x5d
"^", // 0x5e
"_", // 0x5f
// national variant
"`", // 0x60
"a", // 0x61
"b", // 0x62
"c", // 0x63
"d", // 0x64
"e", // 0x65
"f", // 0x66
"g", // 0x67
"h", // 0x68
"i", // 0x69
"j", // 0x6a
"k", // 0x6b
"l", // 0x6c
"m", // 0x6d
"n", // 0x6e
"o", // 0x6f
"p", // 0x70
"q", // 0x71
"r", // 0x72
"s", // 0x73
"t", // 0x74
"u", // 0x75
"v", // 0x76
"w", // 0x77
"x", // 0x78
"y", // 0x79
"z", // 0x7a
// national variant
"{", // 0x7b
"|", // 0x7c
"}", // 0x7d
"~", // 0x7e
"<DEL>" // 0x7f
};
/*
// national variant
"#", // 0x23
"$", // 0x24
"[", // 0x5b
"\\", // 0x5c
"]", // 0x5d
"^", // 0x5e
"`", // 0x60
"{", // 0x7b
"|", // 0x7c
"}", // 0x7d
"~", // 0x7e
*/
bool pocsag_init_charset(char *charset)
{
if(strcmp(charset,"DE")==0) // German charset
{
#ifdef CHARSET_UTF8
trtab[0x5b] = "Ä";
trtab[0x5c] = "Ö";
trtab[0x5d] = "Ü";
trtab[0x7b] = "ä";
trtab[0x7c] = "ö";
trtab[0x7d] = "ü";
trtab[0x7e] = "ß";
#elif defined CHARSET_LATIN1
trtab[0x5b] = "\304";
trtab[0x5c] = "\326";
trtab[0x5d] = "\334";
trtab[0x7b] = "\344";
trtab[0x7c] = "\366";
trtab[0x7d] = "\374";
trtab[0x7e] = "\337";
#else
trtab[0x5b] = "AE";
trtab[0x5c] = "OE";
trtab[0x5d] = "UE";
trtab[0x7b] = "ae";
trtab[0x7c] = "oe";
trtab[0x7d] = "ue";
trtab[0x7e] = "ss";
#endif
}
else if (strcmp(charset,"DK")==0) // Danish charset /JT Ref. https://www.ascii-code.com
{
#ifdef CHARSET_UTF8
trtab[0x5b] = "Æ";
trtab[0x5c] = "Ø";
trtab[0x5d] = "Å";
trtab[0x7b] = "æ";
trtab[0x7c] = "ø";
trtab[0x7d] = "å";
#elif defined CHARSET_LATIN1
trtab[0x5b] = "\306";
trtab[0x5c] = "\330";
trtab[0x5d] = "\305";
trtab[0x7b] = "\346";
trtab[0x7c] = "\370";
trtab[0x7d] = "\345";
#else
trtab[0x5b] = "AE";
trtab[0x5c] = "OE";
trtab[0x5d] = "Aa";
trtab[0x7b] = "ae";
trtab[0x7c] = "oe";
trtab[0x7d] = "aa";
#endif
}
else if (strcmp(charset,"SE")==0) // Swedish charset
{
#ifdef CHARSET_UTF8
trtab[0x5b] = "Ä";
trtab[0x5c] = "Ö";
trtab[0x5d] = "Å";
trtab[0x7b] = "ä";
trtab[0x7c] = "ö";
trtab[0x7d] = "å";
#elif defined CHARSET_LATIN1
trtab[0x5b] = "\304";
trtab[0x5c] = "\326";
trtab[0x5d] = "\305";
trtab[0x7b] = "\344";
trtab[0x7c] = "\366";
trtab[0x7d] = "\345";
#else
trtab[0x5b] = "AE";
trtab[0x5c] = "OE";
trtab[0x5d] = "AO";
trtab[0x7b] = "ae";
trtab[0x7c] = "oe";
trtab[0x7d] = "ao";
#endif
}
else if (strcmp(charset,"FR")==0) // French charset
{
trtab[0x24] = "£";
trtab[0x40] = "à";
trtab[0x5b] = "°";
trtab[0x5c] = "ç";
trtab[0x5d] = "§";
trtab[0x5e] = "^";
trtab[0x5f] = "_";
trtab[0x60] = "µ";
trtab[0x7b] = "é";
trtab[0x7c] = "ù";
trtab[0x7d] = "è";
trtab[0x7e] = "¨";
}
else if (strcmp(charset,"SI")==0) // Slovenian charset
{
trtab[0x40] = "Ž";
trtab[0x5b] = "Š";
trtab[0x5d] = "Ć";
trtab[0x5e] = "Č";
trtab[0x60] = "ž";
trtab[0x7b] = "š";
trtab[0x7d] = "ć";
trtab[0x7e] = "č";
}
else if (strcmp(charset,"US")==0) // US charset
{
// default
}
else
{
fprintf(stderr, "Error: invalid POCSAG charset %s\n", charset);
fprintf(stderr, "Use: US,FR,DE,DK,SE,SI\n");
charset = "US";
return false;
}
return true;
}
static char *translate_alpha(unsigned char chr)
{
return trtab[chr & 0x7f];
}
/* ---------------------------------------------------------------------- */
static int guesstimate_alpha(const unsigned char cp)
{
if((cp > 0 && cp < 32) || cp == 127)
return -5; // Non printable characters are uncommon
else if((cp > 32 && cp < 48)
|| (cp > 57 && cp < 65)
|| (cp > 90 && cp < 97)
|| (cp > 122 && cp < 127))
return -2; // Penalize special characters
else
return 1;
}
static int guesstimate_numeric(const unsigned char cp, int pos)
{
if(cp == 'U')
return -10;
else if(cp == '[' || cp == ']')
return -5;
else if(cp == ' ' || cp == '.' || cp == '-')
return -2;
else if(pos < 10) // Penalize long messages
return 5;
else
return 0;
}
static int print_msg_numeric(struct l2_state_pocsag *rx, char* buff, unsigned int size)
{
static const char *conv_table = "084 2.6]195-3U7[";
unsigned char *bp = rx->buffer;
int len = rx->numnibbles;
char* cp = buff;
int guesstimate = 0;
if ( (unsigned int) len >= size)
len = size-1;
for (; len > 0; bp++, len -= 2) {
*cp++ = conv_table[(*bp >> 4) & 0xf];
if (len > 1)
*cp++ = conv_table[*bp & 0xf];
}
*cp = '\0';
cp = buff;
for(int i = 0; *(cp+i); i++)
guesstimate += guesstimate_numeric(*(cp+i), i);
return guesstimate;
}
static unsigned char get7(const unsigned char *buf, int n)
{
/* returns the n-th seven bit word */
return ( buf[(n*7)/8]<<8 | buf[(n*7+6)/8] ) >> (n+1)%8;
}
static unsigned char rev7(unsigned char b)
{
/* reverses the bit order of a seven bit word */
return ((b << 6) & 64) | ((b >> 6) & 1) |
((b << 4) & 32) | ((b >> 4) & 2) |
((b << 2) & 16) | ((b >> 2) & 4) |
((b << 0) & 8);
}
static int print_msg_alpha(struct l2_state_pocsag *rx, char* buff, unsigned int size, int caesar)
{
int len = rx->numnibbles * 4 / 7;
char* cp = buff;
int buffree = size-1;
unsigned char curchr;
char *tstr;
int guesstimate = 0;
for (int i = 0; i < len; i++)
{
curchr = rev7(get7(rx->buffer, i)) - caesar;
guesstimate += guesstimate_alpha(curchr);
tstr = translate_alpha(curchr);
if (tstr)
{
int tlen = strlen(tstr);
if (buffree >= tlen)
{
memcpy(cp, tstr, tlen);
cp += tlen;
buffree -= tlen;
}
} else if (buffree > 0) {
*cp++ = curchr;
buffree--;
}
}
*cp = '\0';
return guesstimate;
}
/* ---------------------------------------------------------------------- */
static void pocsag_printmessage(struct demod_state *s, bool sync)
{
if(!pocsag_show_partial_decodes && ((s->l2.pocsag.address == -2) || (s->l2.pocsag.function == -2) || !sync))
return; // Hide partial decodes
if(pocsag_prune_empty && (s->l2.pocsag.numnibbles == 0))
return;
cJSON *json_output = cJSON_CreateObject();
if((s->l2.pocsag.address != -1) || (s->l2.pocsag.function != -1))
{
if(s->l2.pocsag.numnibbles == 0)
{
if (!json_mode) {
verbprintf(0, "%s: Address: %7lu Function: %1hhi ",s->dem_par->name,
s->l2.pocsag.address, s->l2.pocsag.function);
if(!sync) verbprintf(2,"<LOST SYNC>");
verbprintf(0,"\n");
}
else {
cJSON_AddStringToObject(json_output, "demod_name", s->dem_par->name);
cJSON_AddNumberToObject(json_output, "address", s->l2.pocsag.address);
cJSON_AddNumberToObject(json_output, "function", s->l2.pocsag.function);
fprintf(stdout, "%s\n", cJSON_PrintUnformatted(json_output));
cJSON_Delete(json_output);
}
}
else
{
char num_string[1024];
char alpha_string[1024];
char skyper_string[1024];
int guess_num = 0;
int guess_alpha = 0;
int guess_skyper = 0;
int unsure = 0;
int func = 0;
guess_num = print_msg_numeric(&s->l2.pocsag, num_string, sizeof(num_string));
guess_alpha = print_msg_alpha(&s->l2.pocsag, alpha_string, sizeof(alpha_string), CAESAR_ALPHA);
guess_skyper = print_msg_alpha(&s->l2.pocsag, skyper_string, sizeof(skyper_string), CAESAR_SKYPER);
func = s->l2.pocsag.function;
if(guess_num < 20 && guess_alpha < 20 && guess_skyper < 20)
{
if(pocsag_heuristic_pruning)
return;
unsure = 1;
}
if((pocsag_mode == POCSAG_MODE_NUMERIC) || ((pocsag_mode == POCSAG_MODE_STANDARD) && (func == 0)) || ((pocsag_mode == POCSAG_MODE_AUTO) && (guess_num >= 20 || unsure)))
{
if((s->l2.pocsag.address != -2) || (s->l2.pocsag.function != -2))
{
if (!json_mode)
verbprintf(0, "%s: Address: %7lu Function: %1hhi ",s->dem_par->name,
s->l2.pocsag.address, s->l2.pocsag.function);
else {
cJSON_AddStringToObject(json_output, "demod_name", s->dem_par->name);
cJSON_AddNumberToObject(json_output, "address", s->l2.pocsag.address);
cJSON_AddNumberToObject(json_output, "function", s->l2.pocsag.function);
}
}
else
{
if (!json_mode)
verbprintf(0, "%s: Address: - Function: - ",s->dem_par->name);
else {
cJSON_AddStringToObject(json_output, "demod_name", s->dem_par->name);
cJSON_AddNullToObject(json_output, "address");
cJSON_AddNullToObject(json_output, "function");
}
}
if(pocsag_mode == POCSAG_MODE_AUTO)
verbprintf(3, "Certainty: %5i ", guess_num);
if (!json_mode) {
verbprintf(0, "Numeric: %s", num_string);
if(!sync) verbprintf(2,"<LOST SYNC>");
verbprintf(0,"\n");
}
else {
cJSON_AddStringToObject(json_output, "numeric", num_string);
fprintf(stdout, "%s\n", cJSON_PrintUnformatted(json_output));
fflush(stdout);
cJSON_Delete(json_output);
}
}
if((pocsag_mode == POCSAG_MODE_ALPHA) || ((pocsag_mode == POCSAG_MODE_STANDARD) && (func != 0)) || ((pocsag_mode == POCSAG_MODE_AUTO) && (guess_alpha >= guess_skyper || unsure)))
{
if((s->l2.pocsag.address != -2) || (s->l2.pocsag.function != -2))
{
if (!json_mode)
verbprintf(0, "%s: Address: %7lu Function: %1hhi ",s->dem_par->name,
s->l2.pocsag.address, s->l2.pocsag.function);
else {
cJSON_AddStringToObject(json_output, "demod_name", s->dem_par->name);
cJSON_AddNumberToObject(json_output, "address", s->l2.pocsag.address);
cJSON_AddNumberToObject(json_output, "function", s->l2.pocsag.function);
}
}
else
{
if (!json_mode)
verbprintf(0, "%s: Address: - Function: - ",s->dem_par->name);
else {
cJSON_AddStringToObject(json_output, "demod_name", s->dem_par->name);
cJSON_AddNullToObject(json_output, "address");
cJSON_AddNullToObject(json_output, "function");
}
}
if(pocsag_mode == POCSAG_MODE_AUTO)
verbprintf(3, "Certainty: %5i ", guess_alpha);
if (!json_mode) {
verbprintf(0, "Alpha: %s", alpha_string);
if(!sync) verbprintf(2,"<LOST SYNC>");
verbprintf(0,"\n");
}
else {
cJSON_AddStringToObject(json_output, "alpha", alpha_string);
fprintf(stdout, "%s\n", cJSON_PrintUnformatted(json_output));
fflush(stdout);
cJSON_Delete(json_output);
}
}
if((pocsag_mode == POCSAG_MODE_SKYPER) || ((pocsag_mode == POCSAG_MODE_AUTO) && (guess_skyper >= guess_alpha || unsure))) // Only output SKYPER if we're explicitly asking for it or we're auto guessing! (because it's not part of one of the standards, right?!)
{
if((s->l2.pocsag.address != -2) || (s->l2.pocsag.function != -2))
if (!json_mode)
verbprintf(0, "%s: Address: %7lu Function: %1hhi ",s->dem_par->name,
s->l2.pocsag.address, s->l2.pocsag.function);
else {
cJSON_AddStringToObject(json_output, "demod_name", s->dem_par->name);
cJSON_AddNumberToObject(json_output, "address", s->l2.pocsag.address);
cJSON_AddNumberToObject(json_output, "function", s->l2.pocsag.function);
}
else
if (!json_mode)
verbprintf(0, "%s: Address: - Function: - ",s->dem_par->name);
else {
cJSON_AddStringToObject(json_output, "demod_name", s->dem_par->name);
cJSON_AddNullToObject(json_output, "address");
cJSON_AddNullToObject(json_output, "function");
}
if(pocsag_mode == POCSAG_MODE_AUTO)
verbprintf(3, "Certainty: %5i ", guess_skyper);
if (!json_mode) {
verbprintf(0, "Skyper: %s", skyper_string);
if(!sync) verbprintf(2,"<LOST SYNC>");
verbprintf(0,"\n");
}
else {
cJSON_AddStringToObject(json_output, "skyper", skyper_string);
fprintf(stdout, "%s\n", cJSON_PrintUnformatted(json_output));
fflush(stdout);
cJSON_Delete(json_output);
}
}
}
}
}
/* ---------------------------------------------------------------------- */
void pocsag_init(struct demod_state *s)
{
memset(&s->l2.pocsag, 0, sizeof(s->l2.pocsag));
s->l2.pocsag.address = -1;
s->l2.pocsag.function = -1;
}
void pocsag_deinit(struct demod_state *s)
{
if(s->l2.pocsag.pocsag_total_error_count)
verbprintf(1, "\n===%s stats===\n"
"Words BCH checked: %u\n"
"Corrected errors: %u\n"
"Corrected 1bit errors: %u\n"
"Corrected 2bit errors: %u\n"
"Invalid word or >2 bits errors: %u\n\n"
"Total bits processed: %u\n"
"Bits processed while in sync: %u\n"
"Bits processed while out of sync: %u\n"
"Successfully decoded: %f%%\n",
s->dem_par->name,
s->l2.pocsag.pocsag_total_error_count,
s->l2.pocsag.pocsag_corrected_error_count,
s->l2.pocsag.pocsag_corrected_1bit_error_count,
s->l2.pocsag.pocsag_corrected_2bit_error_count,
s->l2.pocsag.pocsag_uncorrected_error_count,
s->l2.pocsag.pocsag_total_bits_received,
s->l2.pocsag.pocsag_bits_processed_while_synced,
s->l2.pocsag.pocsag_bits_processed_while_not_synced,
(100./s->l2.pocsag.pocsag_total_bits_received)*s->l2.pocsag.pocsag_bits_processed_while_synced);
fflush(stdout);
}
static uint32_t
transpose_n(int n, uint32_t *matrix)
{
uint32_t out = 0;
int j;
for (j = 0; j < 32; ++j) {
if (matrix[j] & (1<<n))
out |= (1<<j);
}
return out;
}
#define ONE 0xffffffff
static uint32_t *
transpose_clone(uint32_t src, uint32_t *out)
{
int i;
if (!out)
out = malloc(sizeof(uint32_t)*32);
for (i = 0; i < 32; ++i) {
if (src & (1<<i))
out[i] = ONE;
else
out[i] = 0;
}
return out;
}
static void
bitslice_syndrome(uint32_t *slices)
{
const int firstBit = BCH_N - 1;
int i, n;
uint32_t paritymask = slices[0];
// do the parity and shift together
for (i = 1; i < 32; ++i) {
paritymask ^= slices[i];
slices[i-1] = slices[i];
}
slices[31] = 0;
// BCH_POLY << (BCH_K - 1) is
// 20 21 22 23
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ONE, 0, 0, ONE,
// 24 25 26 27 28 29 30 31
// 0, ONE, ONE, 0, ONE, ONE, ONE, 0
for (n = 0; n < BCH_K; ++n) {
// one line here for every '1' bit in coeff (above)
const int bit = firstBit - n;
slices[20 - n] ^= slices[bit];
slices[23 - n] ^= slices[bit];
slices[25 - n] ^= slices[bit];
slices[26 - n] ^= slices[bit];
slices[28 - n] ^= slices[bit];
slices[29 - n] ^= slices[bit];
slices[30 - n] ^= slices[bit];
slices[31 - n] ^= slices[bit];
}
// apply the parity mask we built up
slices[BCH_N - BCH_K] |= paritymask;
}
/* ---------------------------------------------------------------------- */
// This might not be elegant, yet effective!
// Error correction via bruteforce ;)
//
// It's a pragmatic solution since this was much faster to implement
// than understanding the math to solve it while being as effective.
// Besides that the overhead is neglectable.
int pocsag_brute_repair(struct l2_state_pocsag *rx, uint32_t* data)
{
if (pocsag_syndrome(*data)) {
rx->pocsag_total_error_count++;
verbprintf(6, "Error in syndrome detected!\n");
} else {
return 0;
}
if(pocsag_error_correction == 0)
{
rx->pocsag_uncorrected_error_count++;
verbprintf(6, "Couldn't correct error!\n");
return 1;
}
// check for single bit errors
{
int i, n, b1, b2;
uint32_t res;
uint32_t *xpose = 0, *in = 0;
xpose = malloc(sizeof(uint32_t)*32);
in = malloc(sizeof(uint32_t)*32);
transpose_clone(*data, xpose);
for (i = 0; i < 32; ++i)
xpose[i] ^= (1<<i);
bitslice_syndrome(xpose);
res = 0;
for (i = 0; i < 32; ++i)
res |= xpose[i];
res = ~res;
if (res) {
int n = 0;
while (res) {
++n;
res >>= 1;
}
--n;
*data ^= (1<<n);
rx->pocsag_corrected_error_count++;
rx->pocsag_corrected_1bit_error_count++;
goto returnfree;
}
if(pocsag_error_correction == 1)
{
rx->pocsag_uncorrected_error_count++;
verbprintf(6, "Couldn't correct error!\n");
if (xpose)
free(xpose);
if (in)
free(in);
return 1;
}
//check for two bit errors
n = 0;
transpose_clone(*data, xpose);
for (b1 = 0; b1 < 32; ++b1) {
for (b2 = b1; b2 < 32; ++b2) {
xpose[b1] ^= (1<<n);
xpose[b2] ^= (1<<n);
if (++n == 32) {
memcpy(in, xpose, sizeof(uint32_t)*32);
bitslice_syndrome(xpose);
res = 0;
for (i = 0; i < 32; ++i)
res |= xpose[i];
res = ~res;
if (res) {
int n = 0;
while (res) {
++n;
res >>= 1;
}
--n;
*data = transpose_n(n, in);
rx->pocsag_corrected_error_count++;
rx->pocsag_corrected_2bit_error_count++;
goto returnfree;
}
transpose_clone(*data, xpose);
n = 0;
}
}
}
if (n > 0) {
memcpy(in, xpose, sizeof(uint32_t)*32);
bitslice_syndrome(xpose);
res = 0;
for (i = 0; i < 32; ++i)
res |= xpose[i];
res = ~res;
if (res) {
int n = 0;
while (res) {
++n;
res >>= 1;
}
--n;
*data = transpose_n(n, in);
rx->pocsag_corrected_error_count++;
rx->pocsag_corrected_2bit_error_count++;
goto returnfree;
}
}
rx->pocsag_uncorrected_error_count++;
verbprintf(6, "Couldn't correct error!\n");
if (xpose)
free(xpose);
if (in)
free(in);
return 1;
returnfree:
if (xpose)
free(xpose);
if (in)
free(in);
return 0;
}
}
static inline bool word_complete(struct demod_state *s)
{
// Do nothing for 31 bits
// When the word is complete let the program counter pass
s->l2.pocsag.rx_bit = (s->l2.pocsag.rx_bit + 1) % 32;
return s->l2.pocsag.rx_bit == 0;
}
static inline bool is_sync(const uint32_t * const rx_data)
{
if(*rx_data == POCSAG_SYNC)
return true; // Sync found!
return false;
}
static inline bool is_idle(const uint32_t * const rx_data)
{
if(*rx_data == POCSAG_IDLE)
return true; // Idle found!
return false;
}
static void do_one_bit(struct demod_state *s, uint32_t rx_data)
{
s->l2.pocsag.pocsag_total_bits_received++;
switch(s->l2.pocsag.state & SYNC)
{
case NO_SYNC:
{
s->l2.pocsag.pocsag_bits_processed_while_not_synced++;
pocsag_brute_repair(&s->l2.pocsag, &rx_data);
if(is_sync(&rx_data))
{
verbprintf(4, "Aquired sync!\n");
s->l2.pocsag.state = SYNC;
}
return;
}
case SYNC:
{
s->l2.pocsag.pocsag_bits_processed_while_synced++;
if(!word_complete(s))
return; // Wait for more bits to arrive.
// it is always 17 words
unsigned char rxword = s->l2.pocsag.rx_word; // for address calculation
s->l2.pocsag.rx_word = (s->l2.pocsag.rx_word + 1) % 17;
if(s->l2.pocsag.state == SYNC)
s->l2.pocsag.state = ADDRESS; // We're in sync, move on.
if(pocsag_brute_repair(&s->l2.pocsag, &rx_data))
{
// Arbitration lost
if(s->l2.pocsag.state != LOST_SYNC)
s->l2.pocsag.state = LOSING_SYNC;
}
else
{
if(s->l2.pocsag.state == LOST_SYNC)
{
verbprintf(4, "Recovered sync!\n");
s->l2.pocsag.state = ADDRESS;
}
}
if(is_sync(&rx_data))
return; // Already sync'ed.
while(true)
switch(s->l2.pocsag.state)
{
case LOSING_SYNC:
{
verbprintf(4, "Losing sync!\n");
// Output what we've received so far.
pocsag_printmessage(s, false);
s->l2.pocsag.numnibbles = 0;
s->l2.pocsag.address = -1;
s->l2.pocsag.function = -1;
s->l2.pocsag.state = LOST_SYNC;
return;
}
case LOST_SYNC:
{
verbprintf(4, "Lost sync!\n");
s->l2.pocsag.state = NO_SYNC;
s->l2.pocsag.rx_word = 0;
return;
}
case ADDRESS:
{
if(is_idle(&rx_data)) // Idle codewords have a magic address
return;
if(rx_data & POCSAG_MESSAGE_DETECTION)
{
verbprintf(4, "Got a message: %u\n", rx_data);
s->l2.pocsag.function = -2;
s->l2.pocsag.address = -2;
s->l2.pocsag.state = MESSAGE;
break; // Performing partial decode
}
verbprintf(4, "Got an address: %u\n", rx_data);
s->l2.pocsag.function = (rx_data >> 11) & 3;
s->l2.pocsag.address = ((rx_data >> 10) & 0x1ffff8) | ((rxword >> 1) & 7);
s->l2.pocsag.state = MESSAGE;
return;
}
case MESSAGE:
{
if(rx_data & POCSAG_MESSAGE_DETECTION)
verbprintf(4, "Got a message: %u\n", rx_data);
else
{
// Address/idle signals end of message
verbprintf(4, "Got an address: %u\n", rx_data);
s->l2.pocsag.state = END_OF_MESSAGE;
break;
}
if (s->l2.pocsag.numnibbles > sizeof(s->l2.pocsag.buffer)*2 - 5) {
if (!json_mode) {
verbprintf(0, "%s: Warning: Message too long\n",
s->dem_par->name);
} else {
fprintf(stdout, "{\"error\": \"%s: Warning: Message too long\n\"}", s->dem_par->name);
fflush(stdout);
}
s->l2.pocsag.state = END_OF_MESSAGE;
break;
}
uint32_t data;
unsigned char *bp;
bp = s->l2.pocsag.buffer + (s->l2.pocsag.numnibbles >> 1);
data = (rx_data >> 11);
if (s->l2.pocsag.numnibbles & 1) {
bp[0] = (bp[0] & 0xf0) | ((data >> 16) & 0xf);
bp[1] = data >> 8;
bp[2] = data;
} else {
bp[0] = data >> 12;
bp[1] = data >> 4;
bp[2] = data << 4;
}
s->l2.pocsag.numnibbles += 5;
verbprintf(5, "We received something!\n");
return;
}
case END_OF_MESSAGE:
{
verbprintf(4, "End of message!\n");
pocsag_printmessage(s, true);
s->l2.pocsag.numnibbles = 0;
s->l2.pocsag.address = -1;
s->l2.pocsag.function = -1;
s->l2.pocsag.state = ADDRESS;
break;
}
default:
break;
}
}
default:
break;
}
}
/* ---------------------------------------------------------------------- */
void pocsag_rxbit(struct demod_state *s, int32_t bit)
{
s->l2.pocsag.rx_data <<= 1;
s->l2.pocsag.rx_data |= !bit;
verbprintf(9, " %c ", '1'-(s->l2.pocsag.rx_data & 1));
if(pocsag_invert_input)
do_one_bit(s, ~(s->l2.pocsag.rx_data)); // this tries the inverted signal
else
do_one_bit(s, s->l2.pocsag.rx_data);
}
/* ---------------------------------------------------------------------- */
/*
* demod_poc12.c -- 1200 baud POCSAG demodulator
*
* Copyright (C) 1996
* Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu)
* Copyright (C) 2024
* Marat Fayzullin (luarvique@gmail.com)
*
* POCSAG (Post Office Code Standard Advisory Group)
* Radio Paging Decoder
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* ---------------------------------------------------------------------- */
#include "multimon.h"
#include "filter.h"
#include <math.h>
#include <string.h>
#include <stdio.h>
/* ---------------------------------------------------------------------- */
#define FREQ_SAMP 22050
#define BAUD 1200
#define SUBSAMP 2
#define FILTLEN 1
/* ---------------------------------------------------------------------- */
#define SPHASEINC (0x10000u*BAUD*SUBSAMP/FREQ_SAMP)
/* ---------------------------------------------------------------------- */
static void poc12_init(struct demod_state *s)
{
pocsag_init(s);
memset(&s->l1.poc12, 0, sizeof(s->l1.poc12));
}
/* ---------------------------------------------------------------------- */
static void poc12_demod(struct demod_state *s, buffer_t buffer, int length)
{
if (s->l1.poc12.subsamp) {
if (length <= (int)s->l1.poc12.subsamp) {
s->l1.poc12.subsamp -= length;
return;
}
buffer.fbuffer += s->l1.poc12.subsamp;
length -= s->l1.poc12.subsamp;
s->l1.poc12.subsamp = 0;
}
for (; length > 0; length -= SUBSAMP, buffer.fbuffer += SUBSAMP) {
s->l1.poc12.dcd_shreg <<= 1;
s->l1.poc12.dcd_shreg |= ((*buffer.fbuffer) > 0);
verbprintf(10, "%c", '0'+(s->l1.poc12.dcd_shreg & 1));
/*
* check if transition
*/
if ((s->l1.poc12.dcd_shreg ^ (s->l1.poc12.dcd_shreg >> 1)) & 1) {
if (s->l1.poc12.sphase < (0x8000u-(SPHASEINC/2)))
s->l1.poc12.sphase += SPHASEINC/8;
else
s->l1.poc12.sphase -= SPHASEINC/8;
}
s->l1.poc12.sphase += SPHASEINC;
if (s->l1.poc12.sphase >= 0x10000u) {
s->l1.poc12.sphase &= 0xffffu;
pocsag_rxbit(s, s->l1.poc12.dcd_shreg & 1);
}
}
s->l1.poc12.subsamp = -length;
}
static void poc12_deinit(struct demod_state *s)
{
pocsag_deinit(s);
}
/* ---------------------------------------------------------------------- */
const struct demod_param demod_poc12 = {
"POCSAG1200", true, FREQ_SAMP, FILTLEN, poc12_init, poc12_demod, poc12_deinit
};
/* ---------------------------------------------------------------------- */
/*
* multimon.h -- Monitor for many different modulation formats
*
* Copyright (C) 1996
* Thomas Sailer (sailer@ife.ee.ethz.ch, hb9jnx@hb9w.che.eu)
*
* Added eas parts - A. Maitland Bottoms 27 June 2000
*
* Copyright (C) 2012-2014
* Elias Oenal (multimon-ng@eliasoenal.com)
*
* Copyright (C) 2024
* Jason Lingohr (jason@lucid.net.au)
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* ---------------------------------------------------------------------- */
#ifndef _MULTIMON_H
#define _MULTIMON_H
#include <stdint.h>
#include <stdbool.h>
#ifdef _MSC_VER
#include "msvc_support.h"
#endif
/* ---------------------------------------------------------------------- */
extern const float costabf[0x400];
#define COS(x) costabf[(((x)>>6)&0x3ffu)]
#define SIN(x) COS((x)+0xc000)
/* ---------------------------------------------------------------------- */
enum
{
POCSAG_MODE_STANDARD = 0,
POCSAG_MODE_NUMERIC = 1,
POCSAG_MODE_ALPHA = 2,
POCSAG_MODE_SKYPER = 3,
POCSAG_MODE_AUTO = 4,
};
enum EAS_L2_State
{
EAS_L2_IDLE = 0,
EAS_L2_HEADER_SEARCH = 1,
EAS_L2_READING_MESSAGE = 2,
EAS_L2_READING_EOM = 3,
};
enum EAS_L1_State
{
EAS_L1_IDLE = 0,
EAS_L1_SYNC = 1,
};
struct l2_state_clipfsk {
unsigned char rxbuf[512];
unsigned char *rxptr;
uint32_t rxstate;
uint32_t rxbitstream;
uint32_t rxbitbuf;
};
struct l2_state_fmsfsk {
unsigned char rxbuf[512];
unsigned char *rxptr;
uint32_t rxstate; // used to track the SYNC pattern
uint64_t rxbitstream; // holds RXed bits
uint32_t rxbitcount; // counts RXed bits
};
struct demod_state {
const struct demod_param *dem_par;
union {
struct l2_state_fmsfsk fmsfsk;
struct l2_state_clipfsk clipfsk;
struct l2_state_uart {
unsigned char rxbuf[8192];
unsigned char *rxptr;
uint32_t rxstate;
uint32_t rxbitstream;
uint32_t rxbitbuf;
} uart;
struct l2_state_hdlc {
unsigned char rxbuf[512];
unsigned char *rxptr;
uint32_t rxstate;
uint32_t rxbitstream;
uint32_t rxbitbuf;
} hdlc;
struct l2_state_eas {
char last_message[269];
char msg_buf[4][269];
char head_buf[4];
uint32_t headlen;
uint32_t msglen;
uint32_t msgno;
uint32_t state;
} eas;
struct l2_state_pocsag {
uint32_t rx_data;
unsigned char state; // state machine
unsigned char rx_bit; // bit counter, counts 32bits
unsigned char rx_word;
int32_t function; // POCSAG function
int32_t address; // POCSAG address
unsigned char buffer[512];
uint32_t numnibbles;
uint32_t pocsag_total_error_count;
uint32_t pocsag_corrected_error_count;
uint32_t pocsag_corrected_1bit_error_count;
uint32_t pocsag_corrected_2bit_error_count;
uint32_t pocsag_uncorrected_error_count;
uint32_t pocsag_total_bits_received;
uint32_t pocsag_bits_processed_while_synced;
uint32_t pocsag_bits_processed_while_not_synced;
} pocsag;
} l2;
union {
struct l1_state_poc5 {
uint32_t dcd_shreg;
uint32_t sphase;
uint32_t subsamp;
} poc5;
struct l1_state_poc12 {
uint32_t dcd_shreg;
uint32_t sphase;
uint32_t subsamp;
} poc12;
struct l1_state_poc24 {
uint32_t dcd_shreg;
uint32_t sphase;
} poc24;
struct l1_state_eas {
unsigned int dcd_shreg;
unsigned int sphase;
unsigned char lasts;
unsigned int subsamp;
unsigned char byte_counter;
int dcd_integrator;
uint32_t state;
} eas;
struct l1_state_ufsk12 {
unsigned int dcd_shreg;
unsigned int sphase;
unsigned int subsamp;
} ufsk12;
struct l1_state_clipfsk {
unsigned int dcd_shreg;
unsigned int sphase;
uint32_t subsamp;
} clipfsk;
struct l1_state_fmsfsk {
unsigned int dcd_shreg;
unsigned int sphase;
uint32_t subsamp;
} fmsfsk;
struct l1_state_afsk12 {
uint32_t dcd_shreg;
uint32_t sphase;
uint32_t lasts;
uint32_t subsamp;
} afsk12;
struct l1_state_afsk24 {
unsigned int dcd_shreg;
unsigned int sphase;
unsigned int lasts;
} afsk24;
struct l1_state_hapn48 {
unsigned int shreg;
unsigned int sphase;
float lvllo, lvlhi;
} hapn48;
struct l1_state_fsk96 {
unsigned int dcd_shreg;
unsigned int sphase;
unsigned int descram;
} fsk96;
struct l1_state_dtmf {
unsigned int ph[8];
float energy[4];
float tenergy[4][16];
int blkcount;
int lastch;
} dtmf;
struct l1_state_selcall {
unsigned int ph[16];
float energy[4];
float tenergy[4][32];
int blkcount;
int lastch;
int timeout;
} selcall;
struct l1_state_morse {
uint64_t current_sequence;
int_fast16_t threshold_ctr;
int_fast32_t detection_threshold;
int_fast32_t filtered;
int_fast32_t samples_since_change;
int_fast32_t signal_max;
int_fast32_t glitches;
int_fast32_t erroneous_chars;
int_fast32_t decoded_chars;
int_fast16_t time_unit_dit_dah_samples;
int_fast16_t time_unit_gaps_samples;
int_fast16_t lowpass_strength;
int_fast16_t holdoff_samples;
int_fast8_t current_state; // High = 1, Low = 0
} morse;
struct l1_state_dumpcsv {
uint32_t current_sequence;
} dumpcsv;
struct Flex * flex;
struct Flex_Next * flex_next;
struct l1_state_x10 {
uint32_t current_sequence;
uint32_t last_rise;
short current_state;
short current_stage;
char b[4];
char bi;
char bstring[42];
} x10;
#ifndef NO_X11
struct l1_state_scope {
int datalen;
int dispnum;
float data[512];
} scope;
#endif
} l1;
};
typedef struct buffer
{
const short* sbuffer;
const float* fbuffer;
} buffer_t;
struct demod_param {
const char *name;
bool float_samples; // if false samples are short instead
unsigned int samplerate;
unsigned int overlap;
void (*init)(struct demod_state *s);
void (*demod)(struct demod_state *s, buffer_t buffer, int length);
void (*deinit)(struct demod_state *s);
};
/* ---------------------------------------------------------------------- */
extern const struct demod_param demod_poc5;
extern const struct demod_param demod_poc12;
extern const struct demod_param demod_poc24;
extern const struct demod_param demod_flex;
extern const struct demod_param demod_flex_next;
extern const struct demod_param demod_eas;
extern const struct demod_param demod_ufsk1200;
extern const struct demod_param demod_clipfsk;
extern const struct demod_param demod_fmsfsk;
extern const struct demod_param demod_afsk1200;
extern const struct demod_param demod_afsk2400;
extern const struct demod_param demod_afsk2400_2;
extern const struct demod_param demod_afsk2400_3;
extern const struct demod_param demod_hapn4800;
extern const struct demod_param demod_fsk9600;
extern const struct demod_param demod_dtmf;
extern const struct demod_param demod_zvei1;
extern const struct demod_param demod_zvei2;
extern const struct demod_param demod_zvei3;
extern const struct demod_param demod_dzvei;
extern const struct demod_param demod_pzvei;
extern const struct demod_param demod_eea;
extern const struct demod_param demod_eia;
extern const struct demod_param demod_ccir;
extern const struct demod_param demod_morse;
extern const struct demod_param demod_x10;
extern const struct demod_param demod_dumpcsv;
#ifndef NO_X11
extern const struct demod_param demod_scope;
#endif
#ifndef NO_X11
#define SCOPE_DEMOD , &demod_scope
#else
#define SCOPE_DEMOD
#endif
#define ALL_DEMOD &demod_poc5, &demod_poc12, &demod_poc24, &demod_flex, &demod_flex_next, &demod_eas, &demod_ufsk1200, &demod_clipfsk, &demod_fmsfsk, \
&demod_afsk1200, &demod_afsk2400, &demod_afsk2400_2, &demod_afsk2400_3, &demod_hapn4800, \
&demod_fsk9600, &demod_dtmf, &demod_zvei1, &demod_zvei2, &demod_zvei3, &demod_dzvei, \
&demod_pzvei, &demod_eea, &demod_eia, &demod_ccir, &demod_morse, &demod_dumpcsv, &demod_x10 SCOPE_DEMOD
/* ---------------------------------------------------------------------- */
void _verbprintf(int verb_level, const char *fmt, ...);
#if !defined(MAX_VERBOSE_LEVEL)
# define MAX_VERBOSE_LEVEL 0
#endif
#define verbprintf(level, ...) \
do { if (level <= MAX_VERBOSE_LEVEL) _verbprintf(level, __VA_ARGS__); } while (0)
void hdlc_init(struct demod_state *s);
void hdlc_rxbit(struct demod_state *s, int bit);
void uart_init(struct demod_state *s);
void uart_rxbit(struct demod_state *s, int bit);
void clip_init(struct demod_state *s);
void clip_rxbit(struct demod_state *s, int bit);
void fms_init(struct demod_state *s);
void fms_rxbit(struct demod_state *s, int bit);
void pocsag_init(struct demod_state *s);
void pocsag_rxbit(struct demod_state *s, int32_t bit);
void pocsag_deinit(struct demod_state *s);
void selcall_init(struct demod_state *s);
void selcall_demod(struct demod_state *s, const float *buffer, int length,
const unsigned int *selcall_freq, const char *const name);
void selcall_deinit(struct demod_state *s);
void xdisp_terminate(int cnum);
int xdisp_start(void);
int xdisp_update(int cnum, float *f);
void print_json(int argc, char **argv);
/* ---------------------------------------------------------------------- */
#endif /* _MULTIMON_H */