Initial public commit

master
David Högborg 9 years ago
commit a958855568

1
.gitignore vendored

@ -0,0 +1 @@
gopocsag

@ -0,0 +1,15 @@
build:
go build -o gopocsag
clean:
rm gopocsag
rm -rf batches
test:
go test ./...
docgen:
godoc -html ./internal/datatypes/ > doc/datatypes.html
godoc -html ./internal/utils/ > doc/utils.html
godoc -html ./internal/wav/ > doc/wav.html

@ -0,0 +1,3 @@
# go-pocsag
A parser for POCSAG pager protocol implemented in Go

@ -0,0 +1,17 @@
package datatypes
// A bit is high or low, 0 or 1, true or false.
type Bit bool
// Int returns the bit value as 0 or 1
func (b Bit) Int() int {
if b {
return 1
} else {
return 0
}
}
func (b Bit) UInt8() uint8 {
return uint8(b.Int())
}

@ -0,0 +1,27 @@
package datatypes
import (
. "gopkg.in/check.v1"
"testing"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }
var _ = Suite(&TypeSuite{})
type TypeSuite struct{}
func (f *TypeSuite) Test_BitToInt(c *C) {
high := Bit(true)
low := Bit(false)
c.Assert(high.Int(), Equals, 1)
c.Assert(low.Int(), Equals, 0)
}
func (f *TypeSuite) Test_BitToUInt8(c *C) {
high := Bit(true)
low := Bit(false)
c.Assert(high.UInt8(), Equals, uint8(1))
c.Assert(low.UInt8(), Equals, uint8(0))
}

@ -0,0 +1,20 @@
package pocsag
import (
"github.com/fatih/color"
)
var (
DEBUG bool
)
var (
green = color.New(color.FgGreen)
red = color.New(color.FgRed)
blue = color.New(color.FgBlue)
)
// Tell the package to print debug data
func SetDebug(d bool) {
DEBUG = d
}

@ -0,0 +1,32 @@
package pocsag
import (
"bytes"
"fmt"
"bitbucket.org/dhogborg/go-pocsag/internal/wav"
)
// ReadWav reads a wav file from disc and puts it in memory for the
// scanner to parse as a standard transmission
func ReadWav(path string) *bytes.Buffer {
wavdata, err := wav.NewWavData(path)
if err != nil {
fmt.Println(err)
return nil
}
samplecount := int(wavdata.Subchunk2Size / uint32(wavdata.BitsPerSample/8))
seconds := float32(samplecount) / float32(wavdata.SampleRate)
if DEBUG {
fmt.Printf("Samples: %d\n", samplecount)
fmt.Printf("Seconds: %0.3f\n", seconds)
}
buffer := bytes.NewBuffer(wavdata.Data)
return buffer
}

@ -0,0 +1,475 @@
package pocsag
import (
"fmt"
"os"
"strings"
"bitbucket.org/dhogborg/go-pocsag/internal/datatypes"
"bitbucket.org/dhogborg/go-pocsag/internal/utils"
"github.com/fatih/color"
)
const (
POCSAG_PREAMBLE uint32 = 0x7CD215D8
POCSAG_IDLE uint32 = 0x7A89C197
POCSAG_BATCH_LEN int = 512
POCSAG_CODEWORD_LEN int = 32
)
type CodewordType string
const (
CodewordTypeAddress CodewordType = "ADDRESS"
CodewordTypeMessage CodewordType = "MESSAGE"
CodewordTypeIdle CodewordType = "IDLE"
)
type MessageType string
const (
MessageTypeAuto MessageType = "auto"
MessageTypeAlphanumeric MessageType = "alpha"
MessageTypeBitcodedDecimal MessageType = "bcd"
)
// ParsePOCSAG takes bits decoded from the stream and parses them for
// batches of codewords then prints them using the specefied message type.
func ParsePOCSAG(bits []datatypes.Bit, messagetype MessageType) {
pocsag := &POCSAG{}
batches, err := pocsag.ParseBatches(bits)
if err != nil {
println(err.Error())
return
}
if DEBUG {
for i, batch := range batches {
println("")
println("Batch: ", i)
batch.Print()
}
}
messages := pocsag.ParseMessages(batches)
for _, m := range messages {
green.Println("-- Message --------------")
green.Println("Reciptient: ", m.ReciptientString())
if !m.IsValid() {
red.Println("This message has parity check errors. Contents might be corrupted")
}
println("")
print(m.PayloadString(messagetype))
println("")
println("")
}
}
type POCSAG struct{}
// ParseBatches takes bits decoded from the stream and parses them for
// batches of codewords.
func (p *POCSAG) ParseBatches(bits []datatypes.Bit) ([]*Batch, error) {
batches := []*Batch{}
var start = -1
var batchno = -1
// synchornize with the decoded bits
for a := 0; a < len(bits)-32; a += 1 {
bytes := utils.MSBBitsToBytes(bits[a:a+32], 8)
if isPreamble(bytes) {
batchno += 1
start = a + 32
// for file output as bin data
batchbits := bits[a : a+POCSAG_BATCH_LEN+32]
stream := utils.MSBBitsToBytes(batchbits, 8)
if DEBUG {
out, err := os.Create(fmt.Sprintf("batches/batch-%d.bin", batchno))
if err != nil {
return nil, err
}
out.Write(stream)
}
batch, err := NewBatch(bits[start : start+POCSAG_BATCH_LEN])
if err != nil {
println(err.Error())
} else {
batches = append(batches, batch)
}
}
}
if start < 0 {
return nil, fmt.Errorf("could not obtain message sync")
}
return batches, nil
}
// ParseMessages takes a bundle of codeword from a series of batches and
// compiles them into messages.
// A message starts with an address codeword and a bunch of message codewords follows
// until either the batch ends or an idle codeword appears.
func (p *POCSAG) ParseMessages(batches []*Batch) []*Message {
messages := []*Message{}
var message *Message
for _, b := range batches {
for _, codeword := range b.Codewords {
switch codeword.Type {
// append current and begin new message
case CodewordTypeAddress:
if message != nil {
messages = append(messages, message)
}
message = NewMessage(codeword)
// append current but dont start new
case CodewordTypeIdle:
if message != nil {
messages = append(messages, message)
}
message = nil
case CodewordTypeMessage:
if message != nil {
message.AddPayload(codeword)
} else {
red.Println("Message codeword without sync!")
}
default:
red.Println("Unknown codeword encounterd")
}
}
}
if message != nil {
messages = append(messages, message)
}
return messages
}
// Message construct holds refernces to codewords.
// The Payload is a seies of codewords of message type.
type Message struct {
Reciptient *Codeword
Payload []*Codeword
}
// NewMessage creates a new message construct ready to accept payload codewords
func NewMessage(reciptient *Codeword) *Message {
return &Message{
Reciptient: reciptient,
Payload: []*Codeword{},
}
}
// AddPayload codeword to a message. Must be codeword of CodewordTypeMessage type
// to make sense.
func (m *Message) AddPayload(codeword *Codeword) {
m.Payload = append(m.Payload, codeword)
}
// ReciptientString returns the reciptient address as a hexadecimal representation,
// with the function bits as 0 or 1.
func (m *Message) ReciptientString() string {
bytes := utils.MSBBitsToBytes(m.Reciptient.Payload[0:17], 8)
addr := uint(bytes[1])
addr += uint(bytes[0]) << 8
return fmt.Sprintf("%X:%s%s", addr,
utils.TernaryStr(bool(m.Reciptient.Payload[18]), "1", "0"),
utils.TernaryStr(bool(m.Reciptient.Payload[19]), "1", "0"))
}
// IsValid returns true if no parity bit check errors occurs in the message payload
// or the reciptient address.
func (m *Message) IsValid() bool {
if !m.Reciptient.ValidParity {
return false
}
for _, c := range m.Payload {
if !c.ValidParity {
return false
}
}
return true
}
// PayloadString can try to decide to print the message as bitcoded decimal ("bcd") or
// as an alphanumeric string. There is not always a clear indication which is correct,
// so we can force either type by setting messagetype to something other than Auto.
func (m *Message) PayloadString(messagetype MessageType) string {
bits := m.concactenateBits()
alphanum := m.AlphaPayloadString(bits)
if messagetype == MessageTypeAuto {
if m.isAlphaNumericMessage(alphanum) {
messagetype = MessageTypeAlphanumeric
} else {
messagetype = MessageTypeBitcodedDecimal
}
}
switch messagetype {
case MessageTypeAlphanumeric:
return alphanum
case MessageTypeBitcodedDecimal:
return utils.BitcodedDecimals(bits)
default:
return alphanum
}
}
// AlphaPayloadString takes bits in LSB to MSB order and decodes them as
// 7 bit bytes that will become ASCII text.
// Characters outside of ASCII can occur, so we substitude the most common.
func (m *Message) AlphaPayloadString(bits []datatypes.Bit) string {
str := string(utils.LSBBitsToBytes(bits, 7))
// translate to utf8
charmap := map[string]string{
"[": "Ä",
"\\": "Ö",
"]": "Ü",
"{": "ä",
"|": "ö",
"}": "ü",
"~": "ß",
}
for b, s := range charmap {
str = strings.Replace(str, b, s, -1)
}
return str
}
// concactenateBits to a single bitstream
func (m *Message) concactenateBits() []datatypes.Bit {
msgsbits := []datatypes.Bit{}
for _, cw := range m.Payload {
if cw.Type == CodewordTypeMessage {
msgsbits = append(msgsbits, cw.Payload...)
}
}
return msgsbits
}
// isAlphaNumericMessage tries to figure out if a message is in alpha-numeric format
// or Bitcoded decimal format. There is not always a clear indication which is correct,
// so we try to guess based on some assumptions:
// 1) Alpha numeric messages contains mostly printable charaters.
// 2) BCD messages are usually shorter.
func (m *Message) isAlphaNumericMessage(persumed string) bool {
// MessageTypeAuto...
// Start guessing
odds := 0
specials := 0
const alphanum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 *.,-()<>\n\r"
for _, char := range persumed {
r := rune(char)
if strings.IndexRune(alphanum, r) < 0 {
specials++
}
}
partspecial := float32(specials) / float32(len(persumed))
if partspecial < 0.2 {
odds += 2
}
if partspecial >= 0.2 {
odds += -1
}
if partspecial >= 0.50 {
odds += -2
}
if len(persumed) > 25 {
odds += 2
}
if len(persumed) > 40 {
odds += 3
}
if DEBUG {
fmt.Printf("odds: %d\nspecial: %d (%0.0f%%)\n\n", odds, specials, (partspecial * 100))
}
return odds > 0
}
//-----------------------------
// Batch
// Contains codewords. We dont care about frames, we keep the 16 codewords in a single list.
type Batch struct {
Codewords []*Codeword
}
func NewBatch(bits []datatypes.Bit) (*Batch, error) {
if len(bits) != POCSAG_BATCH_LEN {
return nil, fmt.Errorf("invalid number of bits in batch: ", len(bits))
}
words := []*Codeword{}
for a := 0; a < len(bits); a = a + POCSAG_CODEWORD_LEN {
word, err := NewCodeword(bits[a : a+POCSAG_CODEWORD_LEN])
if err != nil {
println(err.Error())
} else {
words = append(words, word)
}
}
b := &Batch{
Codewords: words,
}
return b, nil
}
// Print will print a list with the codewords of this bach. FOr debugging.
func (b *Batch) Print() {
for _, w := range b.Codewords {
w.Print()
}
}
//-----------------------------
// Codeword contains the actual data. There are two codewords per frame,
// and there are 8 frames per batch.
// Type can be either Address or Message, and a special Idle codeword will occur
// from time to time.
// Payload is a stream of bits, and ValidParity bit is set on creation for later
// reference.
type Codeword struct {
Type CodewordType
Payload []datatypes.Bit
ParityBits []datatypes.Bit
EvenParity datatypes.Bit
ValidParity bool
}
// NewCodeword takes 32 bits, creates a new codeword construct, sets the type and checks for parity errors.
func NewCodeword(bits []datatypes.Bit) (*Codeword, error) {
if len(bits) != 32 {
return nil, fmt.Errorf("invalid number of bits for codeword: ", len(bits))
}
mtype := CodewordTypeAddress
if bits[0] == true {
mtype = CodewordTypeMessage
}
bytes := utils.MSBBitsToBytes(bits, 8)
if isIdle(bytes) {
mtype = CodewordTypeIdle
}
c := &Codeword{
Type: mtype,
Payload: bits[1:21],
ParityBits: bits[21:31],
EvenParity: bits[31],
ValidParity: utils.ParityCheck(bits[0:31], bits[31]),
}
return c, nil
}
// Print the codeword contents and type to terminal. For debugging.
func (c *Codeword) Print() {
payload := ""
var color *color.Color = blue
switch c.Type {
case CodewordTypeAddress:
payload = c.Adress()
color = red
case CodewordTypeMessage:
payload = ""
color = green
default:
color = blue
}
parity := utils.TernaryStr(c.ValidParity, "", "*")
color.Printf("%s %s %s\n", c.Type, payload, parity)
}
func (c *Codeword) Adress() string {
bytes := utils.MSBBitsToBytes(c.Payload[0:17], 8)
addr := uint(bytes[1])
addr += uint(bytes[0]) << 8
return fmt.Sprintf("%X:%s%s", addr,
utils.TernaryStr(bool(c.Payload[18]), "1", "0"),
utils.TernaryStr(bool(c.Payload[19]), "1", "0"))
}
// Utilities
// isPreamble matches 4 bytes to the POCSAG preamble 0x7CD215D8
func isPreamble(bytes []byte) bool {
var a uint32 = 0
a += uint32(bytes[0]) << 24
a += uint32(bytes[1]) << 16
a += uint32(bytes[2]) << 8
a += uint32(bytes[3])
return a == POCSAG_PREAMBLE
}
// isIdle matches 4 bytes to the POCSAG idle codeword 0x7A89C197
func isIdle(bytes []byte) bool {
var a uint32 = 0
a += uint32(bytes[0]) << 24
a += uint32(bytes[1]) << 16
a += uint32(bytes[2]) << 8
a += uint32(bytes[3])
return a == POCSAG_IDLE
}

@ -0,0 +1,238 @@
package pocsag
import (
"bufio"
"fmt"
"io"
"os"
"time"
"bitbucket.org/dhogborg/go-pocsag/internal/datatypes"
"bitbucket.org/dhogborg/go-pocsag/internal/utils"
)
type StreamReader struct {
Stream *bufio.Reader
// 0 for auto
baud int
}
// NewStreamReader returns a new stream reader for the source provided.
// Set bauds 0 for automatic detection.
func NewStreamReader(source io.Reader, bauds int) *StreamReader {
return &StreamReader{
Stream: bufio.NewReader(source),
baud: bauds,
}
}
// StartScan takes a channel on which bitstreams will be written when found and parsed.
// The scanner will continue indefently and sleep for 3 ms per cycle to go easy on the system load.
func (s *StreamReader) StartScan(bitstream chan []datatypes.Bit) {
fmt.Println("Starting transmission scanner")
for {
bytes := make([]byte, 4096)
c, err := s.Stream.Read(bytes)
if err != nil {
println(err.Error())
os.Exit(0)
}
stream := s.bToInt16(bytes[:c])
start, bitlength := s.ScanTransmissionStart(stream)
if start > 0 {
blue.Println("-- Transmission received at", time.Now(), "--------------")
transmission := s.ReadTransmission(stream[start:])
bits := utils.StreamToBits(transmission, bitlength)
bitstream <- bits
}
time.Sleep(3 * time.Millisecond)
}
}
// ReadTransmission reads the beginning and subsequent datapackages into
// a new buffer until encounters noise instead of signal.
func (s *StreamReader) ReadTransmission(beginning []int16) []int16 {
stream := make([]int16, 0)
stream = append(stream, beginning...)
for {
bytes := make([]byte, 4096)
c, _ := s.Stream.Read(bytes)
if c > 0 {
bstr := s.bToInt16(bytes[:c])
stream = append(stream, bstr...)
if s.isNoise(bstr) {
break
}
}
}
return stream
}
// ScanTransmissionStart scans for repeated 1010101010101 pattern of bits in the
// stream. A minimum of 400 samples is required to sucessfully sync the receiver with
// the stream. ScanTransmissionStart looks for when the signal wave changes from high to low
// and reversed, and measures the distance between those changes. They should correspond to
// the bitlength determined by the current baud-rate. An attempt at guessing the baudrate is
// also made when a repeated pattern is found.
// retuned is the index of the stream on which the caller should begin reading bits, and
// the estimated bitlength, the number of samples between each bit center in transmission stream.
func (s *StreamReader) ScanTransmissionStart(stream []int16) (int, int) {
if len(stream) == 0 {
return -1, 0
}
switches := []int{}
prevsamp := stream[0]
first_switch := -1
// find the indexes where we cross the 0-boundary
// if we switch sides we store the index in an array for further analasys
for a, sample := range stream {
if (prevsamp > 0 && sample < 0) || (prevsamp < 0 && sample > 0) {
switches = append(switches, a)
if first_switch < 0 {
first_switch = a
}
}
prevsamp = sample
}
// find the mean distance between boundary corsses
sum := 0.0
for a := 0; a < len(switches)-1; a += 1 {
sum += float64(switches[a+1] - switches[a])
}
mean_bitlength := sum / float64(len(switches)-1)
bitlength := float64(s.bitlength(int(mean_bitlength)))
// if bitlength is not on a scale of known baudrates then
// we probably don't have a pocsag sync-transmission
if bitlength < 0 {
return -1, 0
}
if DEBUG {
fmt.Println("Mean bitlength:", mean_bitlength)
fmt.Println("Determined bitlength:", bitlength)
}
// look at every other sample to see if we have a repeating pattern with matching size
confidence := 0
for a := 0; a < len(switches)-3; a += 1 {
// length from switch a to a+1
w1 := float64(switches[a+1] - switches[a])
w2 := float64(switches[a+3] - switches[a+2])
// how much the persumed bits vary from eachother
intravariance := (w1 / w2) - 1
if intravariance < 0 {
intravariance = intravariance * -1
}
// how much the persumed bits vary from the determined bitlength
baudvariance := (w1 / bitlength) - 1
if baudvariance < 0 {
baudvariance = baudvariance * -1
}
// don't stray more than 20%
if intravariance < 0.2 && baudvariance < 0.2 {
confidence += 1
} else {
confidence = 0
}
if confidence > 10 {
if DEBUG {
fmt.Println("Found bitsync")
}
return switches[a] + int(bitlength/2), int(bitlength)
}
}
return -1, 0
}
// bitlength returns the proper bitlength from a calcualated mean distance between
// wave transitions. If the baudrate is set by configuration then that is used instead.
func (s *StreamReader) bitlength(mean int) int {
if mean > 150 && mean < 170 {
return 160
} else if mean > 75 && mean < 85 || s.baud == 600 {
return 80
} else if mean > 35 && mean < 45 || s.baud == 1200 {
return 40
} else if mean > 15 && mean < 25 || s.baud == 2400 {
return 20
} else {
return -1
}
}
// isNoise detects noise by calculating the number of times the signal goes over the 0-line
// during a signal this value is between 25 and 50, but noise is above 100, usually around 300-400.
func (s *StreamReader) isNoise(stream []int16) bool {
if len(stream) == 0 {
return false
}
prevsamp := stream[0]
switches := 0
// find the indexes where we cross the 0-boundary
for _, sample := range stream {
if (prevsamp > 0 && sample < 0) || (prevsamp < 0 && sample > 0) {
switches += 1
}
prevsamp = sample
}
return switches > 100
}
// bToInt16 converts bytes to int16
func (s *StreamReader) bToInt16(b []byte) (u []int16) {
u = make([]int16, len(b)/2)
for i, _ := range u {
val := int16(b[i*2])
val += int16(b[i*2+1]) << 8
u[i] = val
}
return
}

@ -0,0 +1,201 @@
package utils
import (
"fmt"
"github.com/fatih/color"
"bitbucket.org/dhogborg/go-pocsag/internal/datatypes"
)
var (
DEBUG bool
)
var (
green = color.New(color.FgGreen)
red = color.New(color.FgRed)
blue = color.New(color.FgBlue)
)
func SetDebug(d bool) {
DEBUG = d
}
// StreamToBits converts samples to bits using the bitlength specified.
// Observe that POCSAG signifies a high bit with a low frequency.
func StreamToBits(stream []int16, bitlength int) []datatypes.Bit {
bits := make([]datatypes.Bit, (len(stream)/bitlength)+1)
b := 0
for a := 0; a < len(stream); a += bitlength {
sample := stream[a]
if a > 2 && a < len(stream)-2 {
// let the samples before and after influence our sample, to prevent spike errors
sample = (stream[a-1] / 2) + stream[a] + (stream[a+1] / 2)
}
bits[b] = datatypes.Bit((sample < 0))
b += 1
}
return bits
}
// MSBBitsToBytes converts bitsream to bytes using MSB to LSB order.
func MSBBitsToBytes(bits []datatypes.Bit, bitsPerByte int) []byte {
var b uint8
bytes := []byte{}
power := bitsPerByte - 1
for a := 0; a < len(bits); a += 1 {
bit := bits[a].UInt8()
mod := a % bitsPerByte
if mod == 0 && a > 0 {
bytes = append(bytes, b)
b = 0
}
pow := uint(power - mod)
b += (bit * (1 << pow))
}
if len(bits)%bitsPerByte == 0 {
bytes = append(bytes, b)
}
return bytes
}
// LSBBitsToBytes converts bitsream to bytes using LSB to MSB order.
func LSBBitsToBytes(bits []datatypes.Bit, bitsPerByte int) []byte {
var b uint8
bytes := []byte{}
for a := 0; a < len(bits); a += 1 {
bit := bits[a].UInt8()
mod := a % bitsPerByte
if mod == 0 && a > 0 {
bytes = append(bytes, b)
b = 0
}
pow := uint(mod)
b += (bit * (1 << pow))
}
if len(bits)%bitsPerByte == 0 {
bytes = append(bytes, b)
}
return bytes
}
// simple parity check
func ParityCheck(bits []datatypes.Bit, even_bit datatypes.Bit) bool {
sum := even_bit.Int()
for _, b := range bits {
if b {
sum += 1
}
}
return (sum % 2) == 0
}
// BitcodedDecimals takes 4 bits per decimal to create values between 0 and 15.
// *) values 0-9 are used as is
// *) values 10-14 are special characters translated by bcdSpecial()
// *) value = 15 is not used.
func BitcodedDecimals(bits []datatypes.Bit) string {
msg := ""
var foo uint8 = 0
bitsPerByte := 4
for a := 0; a < len(bits); a += 1 {
bit := bits[a].UInt8()
mod := a % bitsPerByte
if mod == 0 && a > 0 {
msg += bcdChar(foo)
foo = 0
}
pow := uint(mod)
foo += (bit * (1 << pow))
}
if len(bits)%bitsPerByte == 0 {
msg += bcdChar(foo)
}
return msg
}
// bcdChar translates digits and non-digit bitcoded entitis to charaters as per POCSAG protocol
func bcdChar(foo uint8) string {
if foo < 10 {
return fmt.Sprintf("%d", foo)
}
if foo == 10 {
return ""
}
chars := []string{
"",
"U",
" ",
"-",
")",
"(",
}
return chars[foo-10]
}
func TernaryStr(condition bool, a, b string) string {
if condition {
return a
} else {
return b
}
}
// PrintStream, used for debugging of streams
func PrintStream(samples []int16) {
for _, sample := range samples {
PrintSample(sample)
}
}
// PrintBitstream, used for debugging of streams
func PrintBitstream(bits []datatypes.Bit) {
for _, b := range bits {
PrintSample(int16(b.Int()))
}
}
// PrintSample, used for debugging of streams
func PrintSample(sample int16) {
if sample > 0 {
green.Printf("%d ", sample)
} else {
red.Printf("%d ", sample)
}
}

@ -0,0 +1,138 @@
package utils
import (
. "gopkg.in/check.v1"
"testing"
"bitbucket.org/dhogborg/go-pocsag/internal/datatypes"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }
var _ = Suite(&UtilitiesSuite{})
type UtilitiesSuite struct{}
func (f *UtilitiesSuite) Test_MSB_BitsToBytes_8_FF(c *C) {
bits := []datatypes.Bit{
true, true, true, true, true, true, true, true,
}
c.Assert(MSBBitsToBytes(bits, 8), DeepEquals, []byte{0xFF})
}
func (f *UtilitiesSuite) Test_MSB_BitsToBytes_16_FFFF(c *C) {
bits := []datatypes.Bit{
true, true, true, true, true, true, true, true,
true, true, true, true, true, true, true, true,
}
c.Assert(MSBBitsToBytes(bits, 8), DeepEquals, []byte{0xFF, 0xFF})
}
func (f *UtilitiesSuite) Test_MSB_BitsToBytes_16_FF00(c *C) {
bits := []datatypes.Bit{
true, true, true, true, true, true, true, true,
false, false, false, false, false, false, false, false,
}
c.Assert(MSBBitsToBytes(bits, 8), DeepEquals, []byte{0xFF, 0x00})
}
func (f *UtilitiesSuite) Test_LSB_BitsToBytes_8_FF(c *C) {
bits := []datatypes.Bit{true, true, true, true, true, true, true, true}
c.Assert(LSBBitsToBytes(bits, 8), DeepEquals, []byte{0xFF})
}
func (f *UtilitiesSuite) Test_LSB_BitsToBytes_16_FFFF(c *C) {
bits := []datatypes.Bit{
true, true, true, true, true, true, true, true,
true, true, true, true, true, true, true, true,
}
c.Assert(LSBBitsToBytes(bits, 8), DeepEquals, []byte{0xFF, 0xFF})
}
func (f *UtilitiesSuite) Test_LSB_BitsToBytes_16_FF00(c *C) {
bits := []datatypes.Bit{
false, false, false, false, false, false, false, false,
true, true, true, true, true, true, true, true,
}
c.Assert(LSBBitsToBytes(bits, 8), DeepEquals, []byte{0x00, 0xFF})
}
func (f *UtilitiesSuite) Test_LSB_BitsToBytes_16_00FF(c *C) {
bits := []datatypes.Bit{
true, true, true, true, true, true, true, true,
false, false, false, false, false, false, false, false,
}
c.Assert(LSBBitsToBytes(bits, 8), DeepEquals, []byte{0xFF, 0x00})
}
// POCSAG speceific
func (f *UtilitiesSuite) Test_MSBBitsToBytes_Keyword_PREAMBLE(c *C) {
bits := []datatypes.Bit{
false, true, true, true,
true, true, false, false,
true, true, false, true,
false, false, true, false,
false, false, false, true,
false, true, false, true,
true, true, false, true,
true, false, false, false,
}
c.Assert(MSBBitsToBytes(bits, 8), DeepEquals, []byte{0x7C, 0xD2, 0x15, 0xD8})
}
func (f *UtilitiesSuite) Test_MSBBitsToBytes_Keyword_IDLE(c *C) {
bits := []datatypes.Bit{
false, true, true, true,
true, false, true, false,
true, false, false, false,
true, false, false, true,
true, true, false, false,
false, false, false, true,
true, false, false, true,
false, true, true, true,
}
c.Assert(MSBBitsToBytes(bits, 8), DeepEquals, []byte{0x7A, 0x89, 0xC1, 0x97})
}
func (f *UtilitiesSuite) Test_BCD_Min(c *C) {
bits := []datatypes.Bit{
false, false, false, false,
}
c.Assert(BitcodedDecimals(bits), Equals, "0")
}
func (f *UtilitiesSuite) Test_BCD_Max(c *C) {
bits := []datatypes.Bit{
true, true, true, true,
}
c.Assert(BitcodedDecimals(bits), Equals, "(")
}
func (f *UtilitiesSuite) Test_BCD_10chars(c *C) {
bits := []datatypes.Bit{
false, false, false, false,
true, true, true, false,
false, false, false, false,
true, true, true, false,
true, false, false, false,
true, false, false, true,
true, true, false, false,
true, true, false, false,
false, false, false, true,
true, false, true, false,
}
c.Assert(BitcodedDecimals(bits), Equals, "0707193385")
}

@ -0,0 +1,71 @@
package wav
import (
"bufio"
bin "encoding/binary"
"os"
)
type WavData struct {
bChunkID [4]byte // B
ChunkSize uint32 // L
bFormat [4]byte // B
bSubchunk1ID [4]byte // B
Subchunk1Size uint32 // L
AudioFormat uint16 // L
NumChannels uint16 // L
SampleRate uint32 // L
ByteRate uint32 // L
BlockAlign uint16 // L
BitsPerSample uint16 // L
bSubchunk2ID [4]byte // B
Subchunk2Size uint32 // L
Data []byte // L
}
func NewWavData(fn string) (*WavData, error) {
res, err := os.OpenFile(fn, os.O_RDONLY, 0)
if err != nil {
return nil, err
}
file := bufio.NewReader(res)
wav := &WavData{}
bin.Read(file, bin.BigEndian, &wav.bChunkID)
bin.Read(file, bin.LittleEndian, &wav.ChunkSize)
bin.Read(file, bin.BigEndian, &wav.bFormat)
bin.Read(file, bin.BigEndian, &wav.bSubchunk1ID)
bin.Read(file, bin.LittleEndian, &wav.Subchunk1Size)
bin.Read(file, bin.LittleEndian, &wav.AudioFormat)
bin.Read(file, bin.LittleEndian, &wav.NumChannels)
bin.Read(file, bin.LittleEndian, &wav.SampleRate)
bin.Read(file, bin.LittleEndian, &wav.ByteRate)
bin.Read(file, bin.LittleEndian, &wav.BlockAlign)
bin.Read(file, bin.LittleEndian, &wav.BitsPerSample)
bin.Read(file, bin.BigEndian, &wav.bSubchunk2ID)
bin.Read(file, bin.LittleEndian, &wav.Subchunk2Size)
wav.Data = make([]byte, wav.Subchunk2Size)
bin.Read(file, bin.LittleEndian, &wav.Data)
return wav, nil
}
func (w *WavData) SampleCount() int {
return int(len(w.Data) / 2)
}
func (w *WavData) Sample(index int) int16 {
in := index * 2
return btoi16(w.Data[in : in+2])
}
func btoi16(b []byte) int16 {
value := int16(b[0])
value += int16(b[1]) << 8
return value
}

@ -0,0 +1,102 @@
package main
import (
"io"
"os"
"github.com/codegangsta/cli"
"github.com/fatih/color"
"bitbucket.org/dhogborg/go-pocsag/internal/datatypes"
"bitbucket.org/dhogborg/go-pocsag/internal/pocsag"
"bitbucket.org/dhogborg/go-pocsag/internal/utils"
)
var (
config *Config
)
var (
green = color.New(color.FgGreen)
red = color.New(color.FgRed)
blue = color.New(color.FgBlue)
)
type Config struct {
input string
baud int
debug bool
messagetype pocsag.MessageType
}
func main() {
app := cli.NewApp()
app.Name = "go-pocsag"
app.Usage = "Parse audiostream for POCSAG messages"
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "input,i",
Value: "",
Usage: "wav file or data dump with signed 16 bit ints",
},
cli.IntFlag{
Name: "baud,b",
Value: 0,
Usage: "Baud 600/1200/2400. Default auto",
},
cli.BoolFlag{
Name: "debug",
Usage: "Debug mode",
},
cli.StringFlag{
Name: "type,t",
Value: "auto",
Usage: "Force message type: alpha, bcd, auto",
},
}
app.Action = func(c *cli.Context) {
config = &Config{
input: c.String("input"),
baud: c.Int("baud"),
debug: c.Bool("debug"),
messagetype: pocsag.MessageType(c.String("type")),
}
utils.SetDebug(config.debug)
pocsag.SetDebug(config.debug)
Run()
}
app.Run(os.Args)
}
func Run() {
var source io.Reader
if config.input == "-" {
source = os.Stdin
} else { // file reading
source = pocsag.ReadWav(config.input)
}
if source == nil {
println("invalid input")
os.Exit(0)
}
reader := pocsag.NewStreamReader(source, config.baud)
bitstream := make(chan []datatypes.Bit, 1)
go reader.StartScan(bitstream)
for {
bits := <-bitstream
pocsag.ParsePOCSAG(bits, config.messagetype)
}
}
Loading…
Cancel
Save