├── .gitignore ├── Makefile ├── README.md ├── internal ├── datatypes │ ├── bit.go │ └── bit_test.go ├── pocsag │ ├── common.go │ ├── file.go │ ├── pocsag.go │ ├── pocsag_test.go │ └── stream.go ├── utils │ ├── util.go │ └── util_test.go └── wav │ └── wav.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | gopocsag 2 | samples 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: 3 | go build -o gopocsag 4 | 5 | clean: 6 | rm gopocsag 7 | rm -rf batches 8 | 9 | test: 10 | go test ./... 11 | 12 | docgen: 13 | godoc -html ./internal/datatypes/ > doc/datatypes.html 14 | godoc -html ./internal/utils/ > doc/utils.html 15 | godoc -html ./internal/wav/ > doc/wav.html -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-pocsag 2 | 3 | A parser for POCSAG pager protocol implemented in Go 4 | 5 | ## Usage 6 | Read a recorded wav file `gopocsag -i path/to/file.wav` 7 | 8 | Listen to stream from rtl_fm: `rtl_fm -f -E deemp | gopocsag` 9 | 10 | Parse a raw datadump: `cat dump.bin | gopocsag` 11 | 12 | ## Options 13 | * `--type` force message parsing type, one of `auto` `bcd` `alpha` 14 | * `--debug` print debugging and extra information about transmission. 15 | * `--verbosity` regulate the detail of debugging information 16 | 17 | ## Resource usage 18 | Not much. About 0.2% of a i5 during normal operations. Just above 5 mb of RAM. -------------------------------------------------------------------------------- /internal/datatypes/bit.go: -------------------------------------------------------------------------------- 1 | package datatypes 2 | 3 | // A bit is high or low, 0 or 1, true or false. 4 | type Bit bool 5 | 6 | // Int returns the bit value as 0 or 1 7 | func (b Bit) Int() int { 8 | if b { 9 | return 1 10 | } else { 11 | return 0 12 | } 13 | } 14 | 15 | func (b Bit) UInt8() uint8 { 16 | return uint8(b.Int()) 17 | } 18 | -------------------------------------------------------------------------------- /internal/datatypes/bit_test.go: -------------------------------------------------------------------------------- 1 | package datatypes 2 | 3 | import ( 4 | . "gopkg.in/check.v1" 5 | "testing" 6 | ) 7 | 8 | // Hook up gocheck into the "go test" runner. 9 | func Test(t *testing.T) { TestingT(t) } 10 | 11 | var _ = Suite(&TypeSuite{}) 12 | 13 | type TypeSuite struct{} 14 | 15 | func (f *TypeSuite) Test_BitToInt(c *C) { 16 | high := Bit(true) 17 | low := Bit(false) 18 | c.Assert(high.Int(), Equals, 1) 19 | c.Assert(low.Int(), Equals, 0) 20 | } 21 | 22 | func (f *TypeSuite) Test_BitToUInt8(c *C) { 23 | high := Bit(true) 24 | low := Bit(false) 25 | c.Assert(high.UInt8(), Equals, uint8(1)) 26 | c.Assert(low.UInt8(), Equals, uint8(0)) 27 | } 28 | -------------------------------------------------------------------------------- /internal/pocsag/common.go: -------------------------------------------------------------------------------- 1 | package pocsag 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | ) 6 | 7 | var ( 8 | DEBUG bool 9 | LEVEL int 10 | ) 11 | 12 | var ( 13 | green = color.New(color.FgGreen) 14 | red = color.New(color.FgRed) 15 | blue = color.New(color.FgBlue) 16 | ) 17 | 18 | // Tell the package to print debug data 19 | func SetDebug(d bool, verbosity int) { 20 | DEBUG = d 21 | LEVEL = verbosity 22 | } 23 | -------------------------------------------------------------------------------- /internal/pocsag/file.go: -------------------------------------------------------------------------------- 1 | package pocsag 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/dhogborg/go-pocsag/internal/wav" 8 | ) 9 | 10 | // ReadWav reads a wav file from disc and puts it in memory for the 11 | // scanner to parse as a standard transmission 12 | func ReadWav(path string) *bytes.Buffer { 13 | 14 | wavdata, err := wav.NewWavData(path) 15 | 16 | if err != nil { 17 | fmt.Println(err) 18 | return nil 19 | } 20 | 21 | if DEBUG { 22 | 23 | samplecount := int(wavdata.Subchunk2Size / uint32(wavdata.BitsPerSample/8)) 24 | seconds := float32(samplecount) / float32(wavdata.SampleRate) 25 | fmt.Printf("Samplerate: %d\n", wavdata.SampleRate) 26 | fmt.Printf("Samples: %d\n", samplecount) 27 | fmt.Printf("Seconds: %0.3f\n", seconds) 28 | } 29 | 30 | buffer := bytes.NewBuffer(wavdata.Data) 31 | return buffer 32 | 33 | } 34 | -------------------------------------------------------------------------------- /internal/pocsag/pocsag.go: -------------------------------------------------------------------------------- 1 | package pocsag 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "time" 8 | 9 | "github.com/dhogborg/go-pocsag/internal/datatypes" 10 | "github.com/dhogborg/go-pocsag/internal/utils" 11 | 12 | "github.com/fatih/color" 13 | ) 14 | 15 | const ( 16 | POCSAG_PREAMBLE uint32 = 0x7CD215D8 17 | POCSAG_IDLE uint32 = 0x7A89C197 18 | POCSAG_BATCH_LEN int = 512 19 | POCSAG_CODEWORD_LEN int = 32 20 | ) 21 | 22 | type CodewordType string 23 | 24 | const ( 25 | CodewordTypeAddress CodewordType = "ADDRESS" 26 | CodewordTypeMessage CodewordType = "MESSAGE" 27 | CodewordTypeIdle CodewordType = "IDLE" 28 | ) 29 | 30 | type MessageType string 31 | 32 | const ( 33 | MessageTypeAuto MessageType = "auto" 34 | MessageTypeAlphanumeric MessageType = "alpha" 35 | MessageTypeBitcodedDecimal MessageType = "bcd" 36 | ) 37 | 38 | // ParsePOCSAG takes bits decoded from the stream and parses them for 39 | // batches of codewords then prints them using the specefied message type. 40 | func ParsePOCSAG(bits []datatypes.Bit, messagetype MessageType) []*Message { 41 | 42 | pocsag := &POCSAG{} 43 | 44 | batches, err := pocsag.ParseBatches(bits) 45 | if err != nil { 46 | println(err.Error()) 47 | return []*Message{} 48 | } 49 | 50 | if DEBUG && LEVEL > 1 { 51 | for i, batch := range batches { 52 | println("") 53 | println("Batch: ", i) 54 | batch.Print() 55 | } 56 | } 57 | 58 | return pocsag.ParseMessages(batches) 59 | } 60 | 61 | type POCSAG struct{} 62 | 63 | // ParseBatches takes bits decoded from the stream and parses them for 64 | // batches of codewords. 65 | func (p *POCSAG) ParseBatches(bits []datatypes.Bit) ([]*Batch, error) { 66 | 67 | batches := []*Batch{} 68 | 69 | var start = -1 70 | var batchno = -1 71 | // synchornize with the decoded bits 72 | for a := 0; a < len(bits)-32; a += 1 { 73 | 74 | bytes := utils.MSBBitsToBytes(bits[a:a+32], 8) 75 | 76 | if isPreamble(bytes) { 77 | 78 | batchno += 1 79 | start = a + 32 80 | 81 | // for file output as bin data 82 | batchbits := bits[a : a+POCSAG_BATCH_LEN+32] 83 | stream := utils.MSBBitsToBytes(batchbits, 8) 84 | 85 | if DEBUG && LEVEL > 2 { 86 | out, err := os.Create(fmt.Sprintf("batches/batch-%d.bin", batchno)) 87 | if err != nil { 88 | return nil, err 89 | } 90 | out.Write(stream) 91 | } 92 | 93 | batch, err := NewBatch(bits[start : start+POCSAG_BATCH_LEN]) 94 | if err != nil { 95 | println(err.Error()) 96 | } else { 97 | batches = append(batches, batch) 98 | } 99 | 100 | } 101 | 102 | } 103 | 104 | if start < 0 { 105 | return nil, fmt.Errorf("could not obtain message sync") 106 | } 107 | 108 | return batches, nil 109 | 110 | } 111 | 112 | // ParseMessages takes a bundle of codeword from a series of batches and 113 | // compiles them into messages. 114 | // A message starts with an address codeword and a bunch of message codewords follows 115 | // until either the batch ends or an idle codeword appears. 116 | func (p *POCSAG) ParseMessages(batches []*Batch) []*Message { 117 | 118 | messages := []*Message{} 119 | 120 | var message *Message 121 | for _, b := range batches { 122 | 123 | for _, codeword := range b.Codewords { 124 | 125 | switch codeword.Type { 126 | // append current and begin new message 127 | case CodewordTypeAddress: 128 | if message != nil && len(message.Payload) > 0 { 129 | messages = append(messages, message) 130 | } 131 | message = NewMessage(codeword) 132 | 133 | // append current but dont start new 134 | case CodewordTypeIdle: 135 | if message != nil && len(message.Payload) > 0 { 136 | messages = append(messages, message) 137 | } 138 | message = nil 139 | 140 | case CodewordTypeMessage: 141 | if message != nil { 142 | message.AddPayload(codeword) 143 | } else { 144 | red.Println("Message codeword without sync!") 145 | } 146 | 147 | default: 148 | red.Println("Unknown codeword encounterd") 149 | } 150 | } 151 | } 152 | 153 | if message != nil { 154 | messages = append(messages, message) 155 | } 156 | 157 | return messages 158 | } 159 | 160 | // Message construct holds refernces to codewords. 161 | // The Payload is a seies of codewords of message type. 162 | type Message struct { 163 | Timestamp time.Time 164 | Reciptient *Codeword 165 | Payload []*Codeword 166 | } 167 | 168 | // NewMessage creates a new message construct ready to accept payload codewords 169 | func NewMessage(reciptient *Codeword) *Message { 170 | return &Message{ 171 | Timestamp: time.Now(), 172 | Reciptient: reciptient, 173 | Payload: []*Codeword{}, 174 | } 175 | } 176 | 177 | func (m *Message) Print(messagetype MessageType) { 178 | green.Println("-- Message --------------") 179 | green.Println("Reciptient: ", m.ReciptientString()) 180 | 181 | if !m.IsValid() { 182 | red.Println("This message has parity check errors. Contents might be corrupted") 183 | } 184 | 185 | if DEBUG && m.biterrors() > 0 { 186 | red.Println(m.biterrors(), "bits corrected by parity check") 187 | } 188 | 189 | println("") 190 | print(m.PayloadString(messagetype)) 191 | println("") 192 | println("") 193 | 194 | } 195 | 196 | func (m *Message) Write(path string, messagetype MessageType) { 197 | if !os.IsPathSeparator(path[len(path)-1]) { 198 | path += "/" 199 | } 200 | 201 | now := time.Now() 202 | timestr := now.Format("20060102_15.04.05") 203 | file, err := os.Create(path + m.ReciptientString() + "_" + timestr + ".txt") 204 | defer file.Close() 205 | 206 | if err != nil { 207 | println("error creating file: " + err.Error()) 208 | return 209 | } 210 | 211 | file.WriteString("Time: " + now.String() + "\n") 212 | file.WriteString("Reciptient: " + m.ReciptientString() + "\n") 213 | file.WriteString("-------------------\n") 214 | file.WriteString(m.PayloadString(messagetype) + "\n") 215 | 216 | } 217 | 218 | // AddPayload codeword to a message. Must be codeword of CodewordTypeMessage type 219 | // to make sense. 220 | func (m *Message) AddPayload(codeword *Codeword) { 221 | m.Payload = append(m.Payload, codeword) 222 | } 223 | 224 | // ReciptientString returns the reciptient address as a hexadecimal representation, 225 | // with the function bits as 0 or 1. 226 | func (m *Message) ReciptientString() string { 227 | bytes := utils.MSBBitsToBytes(m.Reciptient.Payload[0:17], 8) 228 | addr := uint(bytes[1]) 229 | addr += uint(bytes[0]) << 8 230 | 231 | return fmt.Sprintf("%X%d%d", addr, 232 | m.Reciptient.Payload[18].Int(), 233 | m.Reciptient.Payload[19].Int()) 234 | } 235 | 236 | // IsValid returns true if no parity bit check errors occurs in the message payload 237 | // or the reciptient address. 238 | func (m *Message) IsValid() bool { 239 | 240 | if !m.Reciptient.ValidParity { 241 | return false 242 | } 243 | 244 | for _, c := range m.Payload { 245 | if !c.ValidParity { 246 | return false 247 | } 248 | } 249 | return true 250 | } 251 | 252 | func (m *Message) biterrors() (errors int) { 253 | errors = m.Reciptient.BitCorrections 254 | for _, c := range m.Payload { 255 | errors += c.BitCorrections 256 | } 257 | return 258 | } 259 | 260 | // PayloadString can try to decide to print the message as bitcoded decimal ("bcd") or 261 | // as an alphanumeric string. There is not always a clear indication which is correct, 262 | // so we can force either type by setting messagetype to something other than Auto. 263 | func (m *Message) PayloadString(messagetype MessageType) string { 264 | 265 | bits := m.concactenateBits() 266 | 267 | alphanum := m.AlphaPayloadString(bits) 268 | bcd := utils.BitcodedDecimals(bits) 269 | 270 | var decided = MessageTypeAuto 271 | if messagetype == MessageTypeAuto { 272 | decided = m.estimateMessageType(alphanum, bcd) 273 | } else { 274 | decided = messagetype 275 | } 276 | 277 | switch decided { 278 | case MessageTypeAlphanumeric: 279 | return alphanum 280 | case MessageTypeBitcodedDecimal: 281 | return bcd 282 | default: 283 | return alphanum 284 | } 285 | 286 | } 287 | 288 | // AlphaPayloadString takes bits in LSB to MSB order and decodes them as 289 | // 7 bit bytes that will become ASCII text. 290 | // Characters outside of ASCII can occur, so we substitude the most common. 291 | func (m *Message) AlphaPayloadString(bits []datatypes.Bit) string { 292 | 293 | str := string(utils.LSBBitsToBytes(bits, 7)) 294 | 295 | // translate to utf8 296 | charmap := map[string]string{ 297 | "[": "Ä", 298 | "\\": "Ö", 299 | "]": "Å", 300 | "{": "ä", 301 | "|": "ö", 302 | "}": "å", 303 | "~": "ß", 304 | } 305 | for b, s := range charmap { 306 | str = strings.Replace(str, b, s, -1) 307 | } 308 | 309 | return str 310 | } 311 | 312 | // concactenateBits to a single bitstream 313 | func (m *Message) concactenateBits() []datatypes.Bit { 314 | msgsbits := []datatypes.Bit{} 315 | 316 | for _, cw := range m.Payload { 317 | if cw.Type == CodewordTypeMessage { 318 | msgsbits = append(msgsbits, cw.Payload...) 319 | } 320 | } 321 | return msgsbits 322 | } 323 | 324 | // estimateMessageType tries to figure out if a message is in alpha-numeric format 325 | // or Bitcoded decimal format. There is not always a clear indication which is correct, 326 | // so we try to guess based on some assumptions: 327 | // 1) Alpha numeric messages contains mostly printable charaters. 328 | // 2) BCD messages are usually shorter. 329 | func (m *Message) estimateMessageType(persumed_alpha, persumed_bcd string) MessageType { 330 | 331 | // MessageTypeAuto... 332 | // Start guessing 333 | 334 | odds_a := 0 335 | odds_b := 0 336 | 337 | specials_a := 0 338 | specials_b := 0 339 | const alphanum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 *.,-()<>\n\r" 340 | const bcdnum = "0123456789" 341 | 342 | for _, char := range persumed_alpha { 343 | r := rune(char) 344 | if strings.IndexRune(alphanum, r) < 0 { 345 | specials_a++ 346 | } 347 | } 348 | 349 | for _, char := range persumed_bcd { 350 | r := rune(char) 351 | if strings.IndexRune(bcdnum, r) < 0 { 352 | specials_b++ 353 | } 354 | } 355 | 356 | partspecial_a := float32(specials_a) / float32(len(persumed_alpha)) 357 | partspecial_b := float32(specials_b) / float32(len(persumed_bcd)) 358 | 359 | if partspecial_a < 0.2 { 360 | odds_a += 2 361 | } 362 | 363 | if partspecial_a >= 0.2 { 364 | odds_a += 1 365 | } 366 | 367 | if partspecial_a >= 0.50 { 368 | odds_b += 2 369 | } 370 | 371 | // special charaters are uncommon in bcd messages 372 | if partspecial_b == 0 { 373 | odds_b += 2 374 | } 375 | 376 | if partspecial_b >= 0.1 { 377 | odds_a += 1 378 | } 379 | 380 | if len(persumed_alpha) > 25 { 381 | odds_a += 2 382 | } 383 | 384 | if len(persumed_alpha) > 40 { 385 | odds_a += 3 386 | } 387 | 388 | if DEBUG { 389 | red.Printf("odds: %d/%d\nspecial: %d/%d (%0.0f%%)\n\n", odds_a, odds_b, specials_a, specials_b, (partspecial_a * 100)) 390 | } 391 | 392 | if odds_a > odds_b { 393 | return MessageTypeAlphanumeric 394 | } else { 395 | return MessageTypeBitcodedDecimal 396 | } 397 | 398 | } 399 | 400 | //----------------------------- 401 | // Batch 402 | // Contains codewords. We dont care about frames, we keep the 16 codewords in a single list. 403 | type Batch struct { 404 | Codewords []*Codeword 405 | } 406 | 407 | func NewBatch(bits []datatypes.Bit) (*Batch, error) { 408 | if len(bits) != POCSAG_BATCH_LEN { 409 | return nil, fmt.Errorf("invalid number of bits in batch: ", len(bits)) 410 | } 411 | 412 | words := []*Codeword{} 413 | for a := 0; a < len(bits); a = a + POCSAG_CODEWORD_LEN { 414 | word, err := NewCodeword(bits[a : a+POCSAG_CODEWORD_LEN]) 415 | if err != nil { 416 | println(err.Error()) 417 | } else { 418 | words = append(words, word) 419 | } 420 | } 421 | 422 | b := &Batch{ 423 | Codewords: words, 424 | } 425 | 426 | return b, nil 427 | } 428 | 429 | // Print will print a list with the codewords of this bach. FOr debugging. 430 | func (b *Batch) Print() { 431 | for _, w := range b.Codewords { 432 | w.Print() 433 | } 434 | } 435 | 436 | //----------------------------- 437 | // Codeword contains the actual data. There are two codewords per frame, 438 | // and there are 8 frames per batch. 439 | // Type can be either Address or Message, and a special Idle codeword will occur 440 | // from time to time. 441 | // Payload is a stream of bits, and ValidParity bit is set on creation for later 442 | // reference. 443 | type Codeword struct { 444 | Type CodewordType 445 | Payload []datatypes.Bit 446 | ParityBits []datatypes.Bit 447 | EvenParity datatypes.Bit 448 | ValidParity bool 449 | 450 | BitCorrections int 451 | } 452 | 453 | // NewCodeword takes 32 bits, creates a new codeword construct, sets the type and checks for parity errors. 454 | func NewCodeword(bits []datatypes.Bit) (*Codeword, error) { 455 | if len(bits) != 32 { 456 | return nil, fmt.Errorf("invalid number of bits for codeword: ", len(bits)) 457 | } 458 | 459 | bits, corrected := BitCorrection(bits) 460 | 461 | mtype := CodewordTypeAddress 462 | if bits[0] == true { 463 | mtype = CodewordTypeMessage 464 | } 465 | 466 | bytes := utils.MSBBitsToBytes(bits, 8) 467 | if isIdle(bytes) { 468 | mtype = CodewordTypeIdle 469 | } 470 | 471 | c := &Codeword{ 472 | Type: mtype, 473 | Payload: bits[1:21], 474 | ParityBits: bits[21:31], 475 | EvenParity: bits[31], 476 | ValidParity: (syndrome(bits) == 0) && utils.ParityCheck(bits[:31], bits[31]), 477 | BitCorrections: corrected, 478 | } 479 | 480 | return c, nil 481 | } 482 | 483 | // BitCorrection will attempt to brute-force the codeword to make it validate 484 | // with the parity bits. This can correct up to two errounous bits from transmission. 485 | func BitCorrection(inbits []datatypes.Bit) (bits []datatypes.Bit, corrections int) { 486 | 487 | bits = make([]datatypes.Bit, 32) 488 | corrections = 0 489 | 490 | copy(bits, inbits) 491 | 492 | if syndrome(bits) == 0 { 493 | // valid message 494 | return 495 | } 496 | 497 | // test for single bit errors 498 | for a := 0; a < 31; a += 1 { 499 | 500 | bits_x := make([]datatypes.Bit, 32) 501 | copy(bits_x, bits) 502 | 503 | bits_x[a] = !bits_x[a] 504 | 505 | if syndrome(bits_x) == 0 { 506 | bits = bits_x 507 | corrections = 1 508 | return 509 | } 510 | 511 | // test double bit errors 512 | for b := 0; b < 31; b += 1 { 513 | 514 | // dont flip-flip (negate) the previous change 515 | if b != a { 516 | 517 | bits_xx := make([]datatypes.Bit, 32) 518 | copy(bits_xx, bits_x) 519 | 520 | bits_xx[b] = !bits_xx[b] 521 | 522 | if syndrome(bits_xx) == 0 { 523 | bits = bits_xx 524 | corrections = 2 525 | return 526 | } 527 | } 528 | } 529 | 530 | } 531 | 532 | return 533 | } 534 | 535 | // Print the codeword contents and type to terminal. For debugging. 536 | func (c *Codeword) Print() { 537 | 538 | payload := "" 539 | var color *color.Color = blue 540 | 541 | switch c.Type { 542 | 543 | case CodewordTypeAddress: 544 | payload = c.Adress() 545 | color = red 546 | 547 | case CodewordTypeMessage: 548 | payload = "" 549 | color = green 550 | 551 | default: 552 | color = blue 553 | 554 | } 555 | 556 | parity := utils.TernaryStr(c.ValidParity, "", "*") 557 | color.Printf("%s %s %s ", c.Type, payload, parity) 558 | corr := c.BitCorrections 559 | if corr > 0 { 560 | color.Printf("%d bits corrected", corr) 561 | } 562 | 563 | println("") 564 | } 565 | 566 | // Print the address for debugging 567 | func (c *Codeword) Adress() string { 568 | bytes := utils.MSBBitsToBytes(c.Payload[0:17], 8) 569 | addr := uint(bytes[1]) 570 | addr += uint(bytes[0]) << 8 571 | 572 | return fmt.Sprintf("%X:%s%s", addr, 573 | utils.TernaryStr(bool(c.Payload[18]), "1", "0"), 574 | utils.TernaryStr(bool(c.Payload[19]), "1", "0")) 575 | 576 | } 577 | 578 | // Utilities 579 | 580 | // isPreamble matches 4 bytes to the POCSAG preamble 0x7CD215D8 581 | func isPreamble(bytes []byte) bool { 582 | return utils.Btouint32(bytes) == POCSAG_PREAMBLE 583 | } 584 | 585 | // isIdle matches 4 bytes to the POCSAG idle codeword 0x7A89C197 586 | func isIdle(bytes []byte) bool { 587 | return utils.Btouint32(bytes) == POCSAG_IDLE 588 | } 589 | 590 | const ( 591 | BHC_COEFF = 0xED200000 592 | BCH_N = 31 593 | BCH_K = 21 594 | ) 595 | 596 | // syndrome takes a bitstream and uses the parity bits from the BCH polynomial 597 | // generator to calculate if the bits are received correctly. 598 | // A 0 return means the bits are correct. 599 | // Thanks to multimon-ng (https://github.com/EliasOenal/multimon-ng) for 600 | // detailing implmentation of this. 601 | func syndrome(bits []datatypes.Bit) uint32 { 602 | bytes := utils.MSBBitsToBytes(bits, 8) 603 | 604 | // take the parity-bit out from our codeword 605 | codeword := utils.Btouint32(bytes) >> 1 606 | 607 | // put the mask bit to the far left in the bitstream 608 | mask := uint32(1 << (BCH_N)) 609 | coeff := uint32(BHC_COEFF) 610 | 611 | // step over each data-bit (the first 21) 612 | for a := 0; a < BCH_K; a += 1 { 613 | // step the coefficient and mask right in the bitstream 614 | mask >>= 1 615 | coeff >>= 1 616 | 617 | // if the current bit in the codeword is 1 then XOR the codeword with the coefficient 618 | if (codeword & mask) > 0 { 619 | codeword = codeword ^ coeff 620 | } 621 | } 622 | 623 | // in the end, if the coefficient matches the codeword they 624 | // are canceled out by the XOR, returning 0 625 | 626 | return codeword 627 | } 628 | -------------------------------------------------------------------------------- /internal/pocsag/pocsag_test.go: -------------------------------------------------------------------------------- 1 | package pocsag 2 | 3 | import ( 4 | . "gopkg.in/check.v1" 5 | "testing" 6 | 7 | "github.com/dhogborg/go-pocsag/internal/datatypes" 8 | ) 9 | 10 | // Hook up gocheck into the "go test" runner. 11 | func Test(t *testing.T) { TestingT(t) } 12 | 13 | var _ = Suite(&PocsagSuite{}) 14 | 15 | type PocsagSuite struct{} 16 | 17 | func (f *PocsagSuite) Test_Syndrome_Calculation_Normal_Address(c *C) { 18 | // valid bitstream 19 | bits := bitstream("01010001111011110011110111000010") 20 | syndr := syndrome(bits) 21 | 22 | c.Assert(syndr > 0, Equals, false) 23 | } 24 | 25 | func (f *PocsagSuite) Test_Syndrome_Calculation_Normal_Idle(c *C) { 26 | // valid bitstream 27 | bits := bitstream("01111010100010011100000110010111") 28 | syndr := syndrome(bits) 29 | 30 | c.Assert(syndr > 0, Equals, false) 31 | } 32 | 33 | func (f *PocsagSuite) Test_Syndrome_Calculation_Normal_Message(c *C) { 34 | // valid bitstream 35 | bits := bitstream("11001101100000000000011110001100") 36 | syndr := syndrome(bits) 37 | 38 | c.Assert(syndr > 0, Equals, false) 39 | } 40 | 41 | func (f *PocsagSuite) Test_Syndrome_Calculation_Error_1(c *C) { 42 | // v error 43 | bits := bitstream("01010101111011110011110111000010") 44 | syndr := syndrome(bits) 45 | 46 | c.Assert(syndr > 0, Equals, true) 47 | } 48 | 49 | func (f *PocsagSuite) Test_Syndrome_Calculation_Error_2(c *C) { 50 | // v v errors 51 | bits := bitstream("01110101111011110011110111000010") 52 | syndr := syndrome(bits) 53 | 54 | c.Assert(syndr > 0, Equals, true) 55 | 56 | } 57 | 58 | func (f *PocsagSuite) Test_BitCorrection_No_Rrror(c *C) { 59 | 60 | bits := bitstream("01010001111011110011110111000010") 61 | cbits, corr := BitCorrection(bits) 62 | stream := streambits(cbits) 63 | 64 | c.Assert(corr, Equals, 0) 65 | c.Assert(stream, Equals, "01010001111011110011110111000010") 66 | } 67 | 68 | func (f *PocsagSuite) Test_BitCorrection_PayloadError(c *C) { 69 | // v error 70 | bits := bitstream("01010101111011110011110111000010") 71 | cbits, corr := BitCorrection(bits) 72 | stream := streambits(cbits) 73 | 74 | c.Assert(corr, Equals, 1) 75 | c.Assert(stream, Equals, "01010001111011110011110111000010") 76 | } 77 | 78 | func (f *PocsagSuite) Test_BitCorrection_PayloadErrors(c *C) { 79 | 80 | // v errors v 81 | bits := bitstream("01010101111011110011010111000010") 82 | cbits, corr := BitCorrection(bits) 83 | stream := streambits(cbits) 84 | 85 | c.Assert(corr, Equals, 2) 86 | c.Assert(stream, Equals, "01010001111011110011110111000010") 87 | } 88 | 89 | func (f *PocsagSuite) Test_BitCorrection_ParityError(c *C) { 90 | // v 91 | bits := bitstream("01010001111011110011110111010010") 92 | cbits, corr := BitCorrection(bits) 93 | stream := streambits(cbits) 94 | 95 | c.Assert(corr, Equals, 1) 96 | c.Assert(stream, Equals, "01010001111011110011110111000010") 97 | } 98 | 99 | func bitstream(stream string) []datatypes.Bit { 100 | bits := make([]datatypes.Bit, 32) 101 | for i, c := range stream { 102 | if string(c) == "1" { 103 | bits[i] = datatypes.Bit(true) 104 | } else { 105 | bits[i] = datatypes.Bit(false) 106 | } 107 | } 108 | return bits 109 | } 110 | 111 | func streambits(bits []datatypes.Bit) string { 112 | stream := "" 113 | for _, c := range bits { 114 | if c { 115 | stream += "1" 116 | } else { 117 | stream += "0" 118 | } 119 | } 120 | return stream 121 | } 122 | -------------------------------------------------------------------------------- /internal/pocsag/stream.go: -------------------------------------------------------------------------------- 1 | package pocsag 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "time" 9 | 10 | "github.com/dhogborg/go-pocsag/internal/datatypes" 11 | "github.com/dhogborg/go-pocsag/internal/utils" 12 | ) 13 | 14 | type StreamReader struct { 15 | Stream *bufio.Reader 16 | // 0 for auto 17 | baud int 18 | } 19 | 20 | // NewStreamReader returns a new stream reader for the source provided. 21 | // Set bauds 0 for automatic detection. 22 | func NewStreamReader(source io.Reader, bauds int) *StreamReader { 23 | 24 | return &StreamReader{ 25 | Stream: bufio.NewReader(source), 26 | baud: bauds, 27 | } 28 | 29 | } 30 | 31 | // StartScan takes a channel on which bitstreams will be written when found and parsed. 32 | // The scanner will continue indefently or to EOF is reached 33 | func (s *StreamReader) StartScan(bitstream chan []datatypes.Bit) { 34 | 35 | fmt.Println("Starting transmission scanner") 36 | 37 | for { 38 | 39 | bytes := make([]byte, 8192) 40 | c, err := s.Stream.Read(bytes) 41 | 42 | if err != nil { 43 | println(err.Error()) 44 | os.Exit(0) 45 | } 46 | 47 | stream := s.bToInt16(bytes[:c]) 48 | 49 | start, bitlength := s.ScanTransmissionStart(stream) 50 | 51 | if start > 0 { 52 | 53 | blue.Println("-- Transmission received at", time.Now(), "--------------") 54 | 55 | transmission := s.ReadTransmission(stream[start:]) 56 | 57 | bits := utils.StreamToBits(transmission, bitlength) 58 | 59 | if DEBUG && LEVEL > 2 { 60 | utils.PrintBitstream(bits) 61 | } 62 | 63 | bitstream <- bits 64 | } 65 | 66 | } 67 | } 68 | 69 | // ReadTransmission reads the beginning and subsequent datapackages into 70 | // a new buffer until encounters noise instead of signal. 71 | func (s *StreamReader) ReadTransmission(beginning []int16) []int16 { 72 | 73 | stream := make([]int16, 0) 74 | stream = append(stream, beginning...) 75 | 76 | for { 77 | 78 | bytes := make([]byte, 8192) 79 | c, _ := s.Stream.Read(bytes) 80 | 81 | if c > 0 { 82 | 83 | bstr := s.bToInt16(bytes[:c]) 84 | stream = append(stream, bstr...) 85 | 86 | if s.isNoise(bstr) { 87 | if DEBUG && LEVEL > 1 { 88 | print("\n") 89 | println("Transmission end (high noise level)") 90 | } 91 | break 92 | } 93 | } 94 | 95 | } 96 | 97 | return stream 98 | } 99 | 100 | // ScanTransmissionStart scans for repeated 1010101010101 pattern of bits in the 101 | // stream. A minimum of 400 samples is required to sucessfully sync the receiver with 102 | // the stream. ScanTransmissionStart looks for when the signal wave changes from high to low 103 | // and reversed, and measures the distance between those changes. They should correspond to 104 | // the bitlength determined by the current baud-rate. An attempt at guessing the baudrate is 105 | // also made when a repeated pattern is found. 106 | // retuned is the index of the stream on which the caller should begin reading bits, and 107 | // the estimated bitlength, the number of samples between each bit center in transmission stream. 108 | func (s *StreamReader) ScanTransmissionStart(stream []int16) (int, int) { 109 | 110 | if len(stream) == 0 { 111 | return -1, 0 112 | } 113 | 114 | switches := []int{} 115 | prevsamp := stream[0] 116 | first_switch := -1 117 | 118 | // find the indexes where we cross the 0-boundary 119 | // if we switch sides we store the index in an array for further analasys 120 | for a, sample := range stream { 121 | 122 | if (prevsamp > 0 && sample < 0) || (prevsamp < 0 && sample > 0) { 123 | switches = append(switches, a) 124 | if first_switch < 0 { 125 | first_switch = a 126 | } 127 | } 128 | 129 | prevsamp = sample 130 | } 131 | 132 | // find the mean distance between boundary corsses 133 | sum := 0.0 134 | for a := 0; a < len(switches)-1; a += 1 { 135 | sum += float64(switches[a+1] - switches[a]) 136 | } 137 | 138 | mean_bitlength := sum / float64(len(switches)-1) 139 | 140 | bitlength := float64(s.bitlength(int(mean_bitlength))) 141 | 142 | // if bitlength is not on a scale of known baudrates then 143 | // we probably don't have a pocsag sync-transmission 144 | if bitlength < 0 { 145 | return -1, 0 146 | } 147 | 148 | if DEBUG { 149 | blue.Println("Mean bitlength:", mean_bitlength) 150 | blue.Println("Determined bitlength:", bitlength) 151 | } 152 | 153 | // look at every other sample to see if we have a repeating pattern with matching size 154 | confidence := 0 155 | for a := 0; a < len(switches)-3; a += 1 { 156 | 157 | // length from switch a to a+1 158 | w1 := float64(switches[a+1] - switches[a]) 159 | w2 := float64(switches[a+3] - switches[a+2]) 160 | 161 | // how much the persumed bits vary from eachother 162 | intravariance := (w1 / w2) - 1 163 | if intravariance < 0 { 164 | intravariance = intravariance * -1 165 | } 166 | 167 | // how much the persumed bits vary from the determined bitlength 168 | baudvariance := (w1 / bitlength) - 1 169 | if baudvariance < 0 { 170 | baudvariance = baudvariance * -1 171 | } 172 | 173 | // don't stray more than 20% 174 | if intravariance < 0.2 && baudvariance < 0.2 { 175 | confidence += 1 176 | } else { 177 | confidence = 0 178 | } 179 | 180 | if confidence > 10 { 181 | 182 | if DEBUG { 183 | blue.Println("Found bitsync") 184 | } 185 | 186 | return switches[a] + int(bitlength/2), int(bitlength) 187 | } 188 | 189 | } 190 | 191 | return -1, 0 192 | } 193 | 194 | // bitlength returns the proper bitlength from a calcualated mean distance between 195 | // wave transitions. If the baudrate is set by configuration then that is used instead. 196 | func (s *StreamReader) bitlength(mean int) int { 197 | 198 | if mean > 150 && mean < 170 { 199 | return 160 200 | } else if mean > 75 && mean < 85 || s.baud == 600 { 201 | return 80 202 | } else if mean > 35 && mean < 45 || s.baud == 1200 { 203 | return 40 204 | } else if mean > 15 && mean < 25 || s.baud == 2400 { 205 | return 20 206 | } else { 207 | return -1 208 | } 209 | 210 | } 211 | 212 | // isNoise detects noise by calculating the number of times the signal goes over the 0-line 213 | // during a signal this value is between 25 and 50, but noise is above 100, usually around 300-400. 214 | func (s *StreamReader) isNoise(stream []int16) bool { 215 | 216 | if len(stream) == 0 { 217 | return false 218 | } 219 | 220 | prevsamp := stream[0] 221 | switches := 0 222 | 223 | // find the indexes where we cross the 0-boundary 224 | for _, sample := range stream { 225 | 226 | if (prevsamp > 0 && sample < 0) || (prevsamp < 0 && sample > 0) { 227 | switches += 1 228 | } 229 | 230 | prevsamp = sample 231 | } 232 | 233 | switchrate := float32(switches) / float32(len(stream)) 234 | 235 | if DEBUG && LEVEL > 1 { 236 | fmt.Printf("%0.0f ", switchrate*100) 237 | } 238 | 239 | return switchrate > 0.15 240 | } 241 | 242 | // bToInt16 converts bytes to int16 243 | func (s *StreamReader) bToInt16(b []byte) (u []int16) { 244 | u = make([]int16, len(b)/2) 245 | for i, _ := range u { 246 | val := int16(b[i*2]) 247 | val += int16(b[i*2+1]) << 8 248 | u[i] = val 249 | } 250 | return 251 | } 252 | -------------------------------------------------------------------------------- /internal/utils/util.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fatih/color" 7 | 8 | "github.com/dhogborg/go-pocsag/internal/datatypes" 9 | ) 10 | 11 | var ( 12 | DEBUG bool 13 | LEVEL int 14 | ) 15 | 16 | var ( 17 | green = color.New(color.FgGreen) 18 | red = color.New(color.FgRed) 19 | blue = color.New(color.FgBlue) 20 | ) 21 | 22 | // Tell the package to print debug data 23 | func SetDebug(d bool, verbosity int) { 24 | DEBUG = d 25 | LEVEL = verbosity 26 | } 27 | 28 | // StreamToBits converts samples to bits using the bitlength specified. 29 | // Observe that POCSAG signifies a high bit with a low frequency. 30 | func StreamToBits(stream []int16, bitlength int) []datatypes.Bit { 31 | 32 | bits := make([]datatypes.Bit, (len(stream)/bitlength)+1) 33 | b := 0 34 | 35 | for a := 0; a < len(stream); a += bitlength { 36 | 37 | sample := stream[a] 38 | if a > 2 && a < len(stream)-2 { 39 | // let the samples before and after influence our sample, to prevent spike errors 40 | sample = (stream[a-1] / 2) + stream[a] + (stream[a+1] / 2) 41 | } 42 | 43 | bits[b] = datatypes.Bit((sample < 0)) 44 | b += 1 45 | 46 | } 47 | 48 | return bits 49 | } 50 | 51 | // MSBBitsToBytes converts bitsream to bytes using MSB to LSB order. 52 | func MSBBitsToBytes(bits []datatypes.Bit, bitsPerByte int) []byte { 53 | 54 | var b uint8 55 | bytes := []byte{} 56 | power := bitsPerByte - 1 57 | 58 | for a := 0; a < len(bits); a += 1 { 59 | 60 | bit := bits[a].UInt8() 61 | mod := a % bitsPerByte 62 | 63 | if mod == 0 && a > 0 { 64 | bytes = append(bytes, b) 65 | b = 0 66 | } 67 | 68 | pow := uint(power - mod) 69 | b += (bit * (1 << pow)) 70 | 71 | } 72 | 73 | if len(bits)%bitsPerByte == 0 { 74 | bytes = append(bytes, b) 75 | } 76 | 77 | return bytes 78 | } 79 | 80 | // LSBBitsToBytes converts bitsream to bytes using LSB to MSB order. 81 | func LSBBitsToBytes(bits []datatypes.Bit, bitsPerByte int) []byte { 82 | 83 | var b uint8 84 | bytes := []byte{} 85 | 86 | for a := 0; a < len(bits); a += 1 { 87 | 88 | bit := bits[a].UInt8() 89 | mod := a % bitsPerByte 90 | 91 | if mod == 0 && a > 0 { 92 | bytes = append(bytes, b) 93 | b = 0 94 | } 95 | 96 | pow := uint(mod) 97 | b += (bit * (1 << pow)) 98 | 99 | } 100 | 101 | if len(bits)%bitsPerByte == 0 { 102 | bytes = append(bytes, b) 103 | } 104 | 105 | return bytes 106 | } 107 | 108 | // simple parity check 109 | func ParityCheck(bits []datatypes.Bit, even_bit datatypes.Bit) bool { 110 | 111 | sum := even_bit.Int() 112 | for _, b := range bits { 113 | if b { 114 | sum += 1 115 | } 116 | } 117 | return (sum % 2) == 0 118 | } 119 | 120 | // BitcodedDecimals takes 4 bits per decimal to create values between 0 and 15. 121 | // *) values 0-9 are used as is 122 | // *) values 10-14 are special characters translated by bcdSpecial() 123 | // *) value = 15 is not used. 124 | func BitcodedDecimals(bits []datatypes.Bit) string { 125 | 126 | msg := "" 127 | var foo uint8 = 0 128 | 129 | bitsPerByte := 4 130 | 131 | for a := 0; a < len(bits); a += 1 { 132 | 133 | bit := bits[a].UInt8() 134 | mod := a % bitsPerByte 135 | 136 | if mod == 0 && a > 0 { 137 | msg += bcdChar(foo) 138 | foo = 0 139 | } 140 | 141 | pow := uint(mod) 142 | foo += (bit * (1 << pow)) 143 | 144 | } 145 | 146 | if len(bits)%bitsPerByte == 0 { 147 | msg += bcdChar(foo) 148 | } 149 | 150 | return msg 151 | } 152 | 153 | // bcdChar translates digits and non-digit bitcoded entitis to charaters as per POCSAG protocol 154 | func bcdChar(foo uint8) string { 155 | 156 | if foo < 10 { 157 | return fmt.Sprintf("%d", foo) 158 | } 159 | 160 | if foo == 10 { 161 | return "" 162 | } 163 | 164 | chars := []string{ 165 | "", 166 | "U", 167 | " ", 168 | "-", 169 | ")", 170 | "(", 171 | } 172 | return chars[foo-10] 173 | } 174 | 175 | func Btouint32(bytes []byte) uint32 { 176 | 177 | var a uint32 = 0 178 | a += uint32(bytes[0]) << 24 179 | a += uint32(bytes[1]) << 16 180 | a += uint32(bytes[2]) << 8 181 | a += uint32(bytes[3]) 182 | 183 | return a 184 | } 185 | 186 | func TernaryStr(condition bool, a, b string) string { 187 | if condition { 188 | return a 189 | } else { 190 | return b 191 | } 192 | } 193 | 194 | // PrintStream, used for debugging of streams 195 | func PrintStream(samples []int16) { 196 | for _, sample := range samples { 197 | PrintSample(sample) 198 | } 199 | println("") 200 | } 201 | 202 | // PrintBitstream, used for debugging of streams 203 | func PrintBitstream(bits []datatypes.Bit) { 204 | for _, b := range bits { 205 | PrintSample(int16(b.Int())) 206 | } 207 | println("") 208 | } 209 | 210 | // PrintSample, used for debugging of streams 211 | func PrintSample(sample int16) { 212 | if sample > 0 { 213 | green.Printf("%d ", sample) 214 | } else { 215 | red.Printf("%d ", sample) 216 | } 217 | } 218 | 219 | func PrintUint32(i uint32) { 220 | var x uint32 = 1 << 31 221 | for a := 0; a < 32; a += 1 { 222 | if (i & x) > 0 { 223 | green.Print("1 ") 224 | } else { 225 | red.Print("0 ") 226 | } 227 | x >>= 1 228 | } 229 | println("") 230 | } 231 | -------------------------------------------------------------------------------- /internal/utils/util_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | . "gopkg.in/check.v1" 5 | "testing" 6 | 7 | "github.com/dhogborg/go-pocsag/internal/datatypes" 8 | ) 9 | 10 | // Hook up gocheck into the "go test" runner. 11 | func Test(t *testing.T) { TestingT(t) } 12 | 13 | var _ = Suite(&UtilitiesSuite{}) 14 | 15 | type UtilitiesSuite struct{} 16 | 17 | func (f *UtilitiesSuite) Test_MSB_BitsToBytes_8_FF(c *C) { 18 | bits := []datatypes.Bit{ 19 | true, true, true, true, true, true, true, true, 20 | } 21 | c.Assert(MSBBitsToBytes(bits, 8), DeepEquals, []byte{0xFF}) 22 | } 23 | 24 | func (f *UtilitiesSuite) Test_MSB_BitsToBytes_16_FFFF(c *C) { 25 | bits := []datatypes.Bit{ 26 | true, true, true, true, true, true, true, true, 27 | true, true, true, true, true, true, true, true, 28 | } 29 | c.Assert(MSBBitsToBytes(bits, 8), DeepEquals, []byte{0xFF, 0xFF}) 30 | } 31 | 32 | func (f *UtilitiesSuite) Test_MSB_BitsToBytes_16_FF00(c *C) { 33 | bits := []datatypes.Bit{ 34 | true, true, true, true, true, true, true, true, 35 | false, false, false, false, false, false, false, false, 36 | } 37 | c.Assert(MSBBitsToBytes(bits, 8), DeepEquals, []byte{0xFF, 0x00}) 38 | } 39 | 40 | func (f *UtilitiesSuite) Test_LSB_BitsToBytes_8_FF(c *C) { 41 | bits := []datatypes.Bit{true, true, true, true, true, true, true, true} 42 | c.Assert(LSBBitsToBytes(bits, 8), DeepEquals, []byte{0xFF}) 43 | } 44 | 45 | func (f *UtilitiesSuite) Test_LSB_BitsToBytes_16_FFFF(c *C) { 46 | bits := []datatypes.Bit{ 47 | true, true, true, true, true, true, true, true, 48 | true, true, true, true, true, true, true, true, 49 | } 50 | c.Assert(LSBBitsToBytes(bits, 8), DeepEquals, []byte{0xFF, 0xFF}) 51 | } 52 | 53 | func (f *UtilitiesSuite) Test_LSB_BitsToBytes_16_FF00(c *C) { 54 | bits := []datatypes.Bit{ 55 | false, false, false, false, false, false, false, false, 56 | true, true, true, true, true, true, true, true, 57 | } 58 | c.Assert(LSBBitsToBytes(bits, 8), DeepEquals, []byte{0x00, 0xFF}) 59 | } 60 | 61 | func (f *UtilitiesSuite) Test_LSB_BitsToBytes_16_00FF(c *C) { 62 | bits := []datatypes.Bit{ 63 | true, true, true, true, true, true, true, true, 64 | false, false, false, false, false, false, false, false, 65 | } 66 | c.Assert(LSBBitsToBytes(bits, 8), DeepEquals, []byte{0xFF, 0x00}) 67 | } 68 | 69 | // POCSAG speceific 70 | 71 | func (f *UtilitiesSuite) Test_MSBBitsToBytes_Keyword_PREAMBLE(c *C) { 72 | bits := []datatypes.Bit{ 73 | false, true, true, true, 74 | true, true, false, false, 75 | 76 | true, true, false, true, 77 | false, false, true, false, 78 | 79 | false, false, false, true, 80 | false, true, false, true, 81 | 82 | true, true, false, true, 83 | true, false, false, false, 84 | } 85 | c.Assert(MSBBitsToBytes(bits, 8), DeepEquals, []byte{0x7C, 0xD2, 0x15, 0xD8}) 86 | } 87 | 88 | func (f *UtilitiesSuite) Test_MSBBitsToBytes_Keyword_IDLE(c *C) { 89 | bits := []datatypes.Bit{ 90 | false, true, true, true, 91 | true, false, true, false, 92 | 93 | true, false, false, false, 94 | true, false, false, true, 95 | 96 | true, true, false, false, 97 | false, false, false, true, 98 | 99 | true, false, false, true, 100 | false, true, true, true, 101 | } 102 | c.Assert(MSBBitsToBytes(bits, 8), DeepEquals, []byte{0x7A, 0x89, 0xC1, 0x97}) 103 | } 104 | 105 | func (f *UtilitiesSuite) Test_BCD_Min(c *C) { 106 | bits := []datatypes.Bit{ 107 | false, false, false, false, 108 | } 109 | c.Assert(BitcodedDecimals(bits), Equals, "0") 110 | } 111 | 112 | func (f *UtilitiesSuite) Test_BCD_Max(c *C) { 113 | bits := []datatypes.Bit{ 114 | true, true, true, true, 115 | } 116 | c.Assert(BitcodedDecimals(bits), Equals, "(") 117 | } 118 | 119 | func (f *UtilitiesSuite) Test_BCD_10chars(c *C) { 120 | 121 | bits := []datatypes.Bit{ 122 | false, false, false, false, 123 | true, true, true, false, 124 | false, false, false, false, 125 | true, true, true, false, 126 | 127 | true, false, false, false, 128 | true, false, false, true, 129 | 130 | true, true, false, false, 131 | true, true, false, false, 132 | 133 | false, false, false, true, 134 | true, false, true, false, 135 | } 136 | 137 | c.Assert(BitcodedDecimals(bits), Equals, "0707193385") 138 | } 139 | -------------------------------------------------------------------------------- /internal/wav/wav.go: -------------------------------------------------------------------------------- 1 | package wav 2 | 3 | import ( 4 | "bufio" 5 | bin "encoding/binary" 6 | "os" 7 | ) 8 | 9 | type WavData struct { 10 | bChunkID [4]byte // B 11 | ChunkSize uint32 // L 12 | bFormat [4]byte // B 13 | 14 | bSubchunk1ID [4]byte // B 15 | Subchunk1Size uint32 // L 16 | AudioFormat uint16 // L 17 | NumChannels uint16 // L 18 | SampleRate uint32 // L 19 | ByteRate uint32 // L 20 | BlockAlign uint16 // L 21 | BitsPerSample uint16 // L 22 | 23 | bSubchunk2ID [4]byte // B 24 | Subchunk2Size uint32 // L 25 | Data []byte // L 26 | } 27 | 28 | func NewWavData(fn string) (*WavData, error) { 29 | res, err := os.OpenFile(fn, os.O_RDONLY, 0) 30 | if err != nil { 31 | return nil, err 32 | } 33 | file := bufio.NewReader(res) 34 | 35 | wav := &WavData{} 36 | bin.Read(file, bin.BigEndian, &wav.bChunkID) 37 | bin.Read(file, bin.LittleEndian, &wav.ChunkSize) 38 | bin.Read(file, bin.BigEndian, &wav.bFormat) 39 | 40 | bin.Read(file, bin.BigEndian, &wav.bSubchunk1ID) 41 | bin.Read(file, bin.LittleEndian, &wav.Subchunk1Size) 42 | bin.Read(file, bin.LittleEndian, &wav.AudioFormat) 43 | bin.Read(file, bin.LittleEndian, &wav.NumChannels) 44 | bin.Read(file, bin.LittleEndian, &wav.SampleRate) 45 | bin.Read(file, bin.LittleEndian, &wav.ByteRate) 46 | bin.Read(file, bin.LittleEndian, &wav.BlockAlign) 47 | bin.Read(file, bin.LittleEndian, &wav.BitsPerSample) 48 | 49 | bin.Read(file, bin.BigEndian, &wav.bSubchunk2ID) 50 | bin.Read(file, bin.LittleEndian, &wav.Subchunk2Size) 51 | 52 | wav.Data = make([]byte, wav.Subchunk2Size) 53 | bin.Read(file, bin.LittleEndian, &wav.Data) 54 | 55 | return wav, nil 56 | } 57 | 58 | func (w *WavData) SampleCount() int { 59 | return int(len(w.Data) / 2) 60 | } 61 | 62 | func (w *WavData) Sample(index int) int16 { 63 | in := index * 2 64 | return btoi16(w.Data[in : in+2]) 65 | } 66 | 67 | func btoi16(b []byte) int16 { 68 | value := int16(b[0]) 69 | value += int16(b[1]) << 8 70 | return value 71 | } 72 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/codegangsta/cli" 8 | "github.com/fatih/color" 9 | 10 | "github.com/dhogborg/go-pocsag/internal/datatypes" 11 | "github.com/dhogborg/go-pocsag/internal/pocsag" 12 | "github.com/dhogborg/go-pocsag/internal/utils" 13 | ) 14 | 15 | var ( 16 | config *Config 17 | ) 18 | 19 | var ( 20 | green = color.New(color.FgGreen) 21 | red = color.New(color.FgRed) 22 | blue = color.New(color.FgBlue) 23 | ) 24 | 25 | type Config struct { 26 | input string 27 | output string 28 | baud int 29 | debug bool 30 | messagetype pocsag.MessageType 31 | verbosity int 32 | } 33 | 34 | func main() { 35 | 36 | app := cli.NewApp() 37 | app.Name = "go-pocsag" 38 | app.Usage = "Parse audiostream for POCSAG messages" 39 | 40 | app.Flags = []cli.Flag{ 41 | cli.StringFlag{ 42 | Name: "input,i", 43 | Value: "", 44 | Usage: "wav file with signed 16 bit ints, - for sttdin", 45 | }, 46 | cli.StringFlag{ 47 | Name: "output,o", 48 | Value: "", 49 | Usage: "Output decoded messages to a folder", 50 | }, 51 | cli.IntFlag{ 52 | Name: "verbosity", 53 | Value: 0, 54 | Usage: "Verbosity level, 0 lowest level", 55 | }, 56 | cli.IntFlag{ 57 | Name: "baud,b", 58 | Value: 0, 59 | Usage: "Baud 600/1200/2400. Default auto", 60 | }, 61 | cli.BoolFlag{ 62 | Name: "debug", 63 | Usage: "Output debug information", 64 | }, 65 | cli.StringFlag{ 66 | Name: "type,t", 67 | Value: "auto", 68 | Usage: "Force message type: alpha, bcd, auto", 69 | }, 70 | } 71 | 72 | app.Action = func(c *cli.Context) { 73 | config = &Config{ 74 | input: c.String("input"), 75 | output: c.String("output"), 76 | baud: c.Int("baud"), 77 | debug: c.Bool("debug"), 78 | verbosity: c.Int("verbosity"), 79 | messagetype: pocsag.MessageType(c.String("type")), 80 | } 81 | 82 | utils.SetDebug(config.debug, config.verbosity) 83 | pocsag.SetDebug(config.debug, config.verbosity) 84 | 85 | Run() 86 | 87 | } 88 | 89 | app.Run(os.Args) 90 | } 91 | 92 | func Run() { 93 | 94 | var source io.Reader 95 | 96 | if config.input == "-" || config.input == "" { 97 | source = os.Stdin 98 | } else { // file reading 99 | source = pocsag.ReadWav(config.input) 100 | } 101 | 102 | if source == nil { 103 | println("invalid input") 104 | os.Exit(0) 105 | } 106 | 107 | reader := pocsag.NewStreamReader(source, config.baud) 108 | 109 | bitstream := make(chan []datatypes.Bit, 1) 110 | go reader.StartScan(bitstream) 111 | 112 | for { 113 | bits := <-bitstream 114 | messages := pocsag.ParsePOCSAG(bits, config.messagetype) 115 | 116 | for _, m := range messages { 117 | m.Print(config.messagetype) 118 | 119 | if config.output != "" { 120 | m.Write(config.output, config.messagetype) 121 | } 122 | } 123 | 124 | } 125 | } 126 | --------------------------------------------------------------------------------