├── .gitignore ├── Makefile ├── README.md └── gascop.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | gascop 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-O2 -g -Wall -W `pkg-config --cflags librtlsdr` 2 | LIBS=`pkg-config --libs librtlsdr` -lm -lpthread 3 | CC=gcc 4 | PROGNAME=gascop 5 | 6 | all: gascop 7 | 8 | %.o: %.c 9 | $(CC) $(CFLAGS) -c $< 10 | 11 | gascop: gascop.o 12 | $(CC) -g -o gascop gascop.o $(LIBS) 13 | 14 | clean: 15 | rm -f *.o gascop 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gascop 2 | 3 | Gascop is a lightweight POCSAG decoder for use with [rtl-sdr](http://sdr.osmocom.org/trac/wiki/rtl-sdr) devices, which is heavily inspired by [dump1090](https://github.com/antirez/dump1090/) and is currently under development mainly as an exercise in processing data from rtl-sdr devices. 4 | 5 | Gascop does not use any part of the GNU Radio stack (not that there's anything wrong with it / some of my best friends / etc...), and thus is very easy to build and use. 6 | 7 | If you are interested in the theory behind the code, take a look at [the wiki](https://github.com/yuvadm/gascop/wiki/Theory). 8 | 9 | ## Build 10 | 11 | ```bash 12 | $ make 13 | ``` 14 | 15 | ## Use 16 | 17 | ```bash 18 | $ ./gascop 123450000 # for listening on 123.450 Mhz 19 | ``` 20 | 21 | ## License 22 | 23 | Copyright (c) 2013 by Yuval Adam, based on work by Salvatore Sanfilippo, all rights reserved. 24 | 25 | Gascop is free for use and distribution under the BSD license. 26 | -------------------------------------------------------------------------------- /gascop.c: -------------------------------------------------------------------------------- 1 | /* Gascop, a lightweight POCSAG decoder for rtl-sdr devices. 2 | * 3 | * Copyright (c) 2013 by 4 | * Yuval Adam 5 | * Salvatore Sanfilippo 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are 11 | * met: 12 | * 13 | * - Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * - Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include "rtl-sdr.h" 40 | 41 | /* POCSAG parameters */ 42 | #define POCSAG_ID "POCSAG" 43 | #define POCSAG_SYM_RATE 1200 44 | #define POCSAG_FM_DEV 4500 45 | #define POCSAG_SPS 8 46 | #define POCSAG_STD_SYNC 0x7cd215d8 47 | #define POCSAG_STD_IDLE 0x7a89c197 48 | #define POCSAG_WORDSIZE 32 49 | #define POCSAG_SOFTTHRESHOLD 2 50 | #define POCSAG_MAXWORD 16 51 | #define POCSAG_MSG_LEN 256 52 | 53 | #define POCSAG_DEFAULT_RATE 1200000 54 | #define POCSAG_DEFAULT_WIDTH 1000 55 | #define POCSAG_DEFAULT_HEIGHT 700 56 | #define POCSAG_ASYNC_BUF_NUMBER 12 57 | #define POCSAG_DATA_LEN (16*16384) /* 256K */ 58 | #define POCSAG_AUTO_GAIN -100 /* Use automatic gain. */ 59 | #define POCSAG_MAX_GAIN 999999 /* Use max available gain. */ 60 | 61 | /* BCH parameters */ 62 | #define POCSAG_BCH_POLY 0x769 63 | #define POCSAG_BCH_N 31 64 | #define POCSAG_BCH_K 21 65 | 66 | /* POCASG states */ 67 | #define POCSAG_SEARCH_PREAMBLE_START 0 68 | #define POCSAG_SEARCH_PREAMBLE_END 1 69 | #define POCSAG_SYNC 2 70 | #define POCSAG_SEARCH_SYNC 3 71 | #define POCSAG_SYNCHED 4 72 | 73 | #define IGNORE(V) ((void) V) 74 | 75 | struct { 76 | pthread_t reader_thread; 77 | pthread_mutex_t data_mutex; /* Mutex to synchronize buffer access. */ 78 | pthread_cond_t data_cond; /* Conditional variable associated. */ 79 | unsigned char *data; /* Raw IQ samples buffer */ 80 | uint32_t data_len; /* Buffer length. */ 81 | int data_ready; /* Data ready to be processed. */ 82 | int exit; 83 | 84 | /* rtlsdr */ 85 | int dev_index; 86 | int gain; 87 | int enable_agc; 88 | rtlsdr_dev_t *dev; 89 | int freq; 90 | } Gascop; 91 | 92 | struct pocsag_msg 93 | { 94 | uint32_t bits; 95 | int nb; 96 | int nc; 97 | char buf[POCSAG_MSG_LEN]; 98 | }; 99 | 100 | void pocsag_msg_init(struct pocsag_msg *msg) { 101 | msg->bits = 0; 102 | msg->nb = 0; 103 | msg->nc = 0; 104 | memset(msg->buf, 0x00, POCSAG_MSG_LEN); 105 | } 106 | 107 | int hammingWeight(uint32_t n) { 108 | int c; 109 | for (c = 0; n; c++) 110 | n &= n - 1; 111 | return c; 112 | } 113 | 114 | uint8_t evenParity(uint32_t n) { 115 | return hammingWeight(n) & 1; 116 | } 117 | 118 | uint32_t bchSyndrome(uint32_t data, int poly, int n, int k) { 119 | uint32_t mask = 1 << (n - 1); 120 | uint32_t coeff = poly << (k - 1); 121 | n = k; 122 | 123 | int s = data >> 1; 124 | while (n > 0) { 125 | if (s & mask) 126 | s ^= coeff; 127 | n -= 1; 128 | mask >>= 1; 129 | coeff >>= 1; 130 | } 131 | 132 | if (evenParity(data)) 133 | s |= 1 << (n - k); 134 | 135 | return s; 136 | } 137 | 138 | uint32_t bchFix(uint32_t data, int poly, int n, int k) { 139 | int i, j; 140 | for (i=0; i<32; i++) { 141 | int t = data ^ (1 << i); 142 | if (!bchSyndrome(t, poly, n, k)) 143 | return t; 144 | } 145 | for (i=0; i<32; i++) { 146 | for (j=i+1; j<32; j++) { 147 | int t = data ^ ((1 << i) | (1 << j)); 148 | if (!bchSyndrome(t, poly, n, k)) 149 | return t; 150 | } 151 | } 152 | return data; 153 | } 154 | 155 | 156 | 157 | void gascopInit(void) { 158 | pthread_mutex_init(&Gascop.data_mutex, NULL); 159 | pthread_cond_init(&Gascop.data_cond, NULL); 160 | Gascop.data_len = POCSAG_DATA_LEN; 161 | Gascop.data_ready = 0; 162 | if ((Gascop.data = malloc(Gascop.data_len)) == NULL) { 163 | fprintf(stderr, "Out of memory allocating data buffer.\n"); 164 | exit(1); 165 | } 166 | } 167 | 168 | void rtlsdrInit(void) { 169 | int device_count = rtlsdr_get_device_count(); 170 | if (!device_count) { 171 | fprintf(stderr, "No rtlsdr devices found.\n"); 172 | exit(1); 173 | } 174 | 175 | printf("Found %d device(s):\n", device_count); 176 | char vendor[256], product[256], serial[256]; 177 | int j; 178 | for (j = 0; j < device_count; j++) { 179 | rtlsdr_get_device_usb_strings(j, vendor, product, serial); 180 | fprintf(stderr, "%d: %s, %s, SN: %s %s\n", j, vendor, product, serial, 181 | (j == Gascop.dev_index) ? "(currently selected)" : ""); 182 | } 183 | 184 | if (rtlsdr_open(&Gascop.dev, Gascop.dev_index) < 0) { 185 | fprintf(stderr, "Error opening rtlsdr device: %s\n", strerror(errno)); 186 | exit(1); 187 | } 188 | 189 | rtlsdr_set_tuner_gain_mode(Gascop.dev, 0); /* auto gain */ 190 | rtlsdr_set_freq_correction(Gascop.dev, 0); 191 | rtlsdr_set_agc_mode(Gascop.dev, 1); 192 | rtlsdr_set_center_freq(Gascop.dev, Gascop.freq); 193 | rtlsdr_set_sample_rate(Gascop.dev, POCSAG_DEFAULT_RATE); 194 | rtlsdr_reset_buffer(Gascop.dev); 195 | printf("Gain reported by device: %.2f\n", 196 | rtlsdr_get_tuner_gain(Gascop.dev) / 10.0); 197 | } 198 | 199 | void rtlsdrCallback(unsigned char *buf, uint32_t len, void *ctx) { 200 | IGNORE(ctx); 201 | 202 | pthread_mutex_lock(&Gascop.data_mutex); 203 | if (len > Gascop.data_len) 204 | len = Gascop.data_len; 205 | memcpy(Gascop.data, buf, len); 206 | Gascop.data_ready = 1; 207 | pthread_cond_signal(&Gascop.data_cond); 208 | pthread_mutex_unlock(&Gascop.data_mutex); 209 | } 210 | 211 | void *readerThreadEntryPoint(void *arg) { 212 | IGNORE(arg); 213 | 214 | rtlsdr_read_async(Gascop.dev, rtlsdrCallback, NULL, 215 | POCSAG_ASYNC_BUF_NUMBER, Gascop.data_len); 216 | return NULL; 217 | } 218 | 219 | void printUsage() { 220 | printf("Usage: ./gascop [options] \n\n"); 221 | } 222 | 223 | int main(int argc, char **argv) { 224 | if (argc < 2 || !strcmp(argv[1], "--help")) { 225 | printUsage(); 226 | exit(-1); 227 | } 228 | 229 | Gascop.freq = strtoll(argv[1], NULL, 10); 230 | 231 | gascopInit(); 232 | rtlsdrInit(); 233 | pthread_create(&Gascop.reader_thread, NULL, readerThreadEntryPoint, NULL); 234 | pthread_mutex_lock(&Gascop.data_mutex); 235 | 236 | uint32_t j; 237 | int i, q; 238 | double nph, ph = 3; /* anything higher than 3 */ 239 | int ph_len = 0; 240 | 241 | 242 | while (1) { 243 | if (!Gascop.data_ready) { 244 | pthread_cond_wait(&Gascop.data_cond, &Gascop.data_mutex); 245 | continue; 246 | } 247 | Gascop.data_ready = 0; 248 | pthread_cond_signal(&Gascop.data_cond); 249 | pthread_mutex_unlock(&Gascop.data_mutex); 250 | 251 | /* Up until now we just did lots of threading stuff, now the real fun begins.. */ 252 | for (j = 0; j < Gascop.data_len; j += 2) { 253 | /* Translate data into signed IQ values */ 254 | i = Gascop.data[j] - 127; 255 | q = Gascop.data[j+1] - 127; 256 | 257 | /* Compute the instantaneous phase of the next IQ sample */ 258 | /* But take the two point moving average to smooth the signal */ 259 | nph = (atan2(q, i) / 2) + (ph / 2); 260 | 261 | /* Check if we have completed a full circle by jumping phase */ 262 | /* Also add a high pass filter, anything less than 30 is noise */ 263 | if ((ph < 0) && (nph > 0) && (ph_len > 30)) { 264 | /* printf("Phase jump from %4f to %4f after %4d samples\n", ph, nph, ph_len); */ 265 | printf("%d,", ph_len); 266 | ph_len = 0; 267 | } 268 | else { 269 | ph_len++; 270 | } 271 | 272 | /* save the phase for the next iteration */ 273 | ph = nph; 274 | 275 | /* printf("I:%5d, Q:%5d, P: %5d\n", i, q, ph); */ 276 | } 277 | 278 | pthread_mutex_lock(&Gascop.data_mutex); 279 | if (Gascop.exit) break; 280 | } 281 | 282 | rtlsdr_close(Gascop.dev); 283 | return 0; 284 | } 285 | --------------------------------------------------------------------------------