Payload correction using parity check bits

master
David Högborg 9 years ago
parent 103940924e
commit b07839f7ef

3
.gitignore vendored

@ -1 +1,2 @@
gopocsag
gopocsag
samples

@ -125,14 +125,14 @@ func (p *POCSAG) ParseMessages(batches []*Batch) []*Message {
switch codeword.Type {
// append current and begin new message
case CodewordTypeAddress:
if message != nil {
if message != nil && len(message.Payload) > 0 {
messages = append(messages, message)
}
message = NewMessage(codeword)
// append current but dont start new
case CodewordTypeIdle:
if message != nil {
if message != nil && len(message.Payload) > 0 {
messages = append(messages, message)
}
message = nil
@ -182,6 +182,10 @@ func (m *Message) Print(messagetype MessageType) {
red.Println("This message has parity check errors. Contents might be corrupted")
}
if DEBUG && m.biterrors() > 0 {
red.Println(m.biterrors(), "bits corrected by parity check")
}
println("")
print(m.PayloadString(messagetype))
println("")
@ -245,6 +249,14 @@ func (m *Message) IsValid() bool {
return true
}
func (m *Message) biterrors() (errors int) {
errors = m.Reciptient.BitCorrections
for _, c := range m.Payload {
errors += c.BitCorrections
}
return
}
// 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.
@ -434,6 +446,8 @@ type Codeword struct {
ParityBits []datatypes.Bit
EvenParity datatypes.Bit
ValidParity bool
BitCorrections int
}
// NewCodeword takes 32 bits, creates a new codeword construct, sets the type and checks for parity errors.
@ -442,6 +456,8 @@ func NewCodeword(bits []datatypes.Bit) (*Codeword, error) {
return nil, fmt.Errorf("invalid number of bits for codeword: ", len(bits))
}
bits, corrected := BitCorrection(bits)
mtype := CodewordTypeAddress
if bits[0] == true {
mtype = CodewordTypeMessage
@ -453,16 +469,69 @@ func NewCodeword(bits []datatypes.Bit) (*Codeword, error) {
}
c := &Codeword{
Type: mtype,
Payload: bits[1:21],
ParityBits: bits[21:31],
EvenParity: bits[31],
ValidParity: utils.ParityCheck(bits[0:31], bits[31]),
Type: mtype,
Payload: bits[1:21],
ParityBits: bits[21:31],
EvenParity: bits[31],
ValidParity: (syndrome(bits) == 0) && utils.ParityCheck(bits[:31], bits[31]),
BitCorrections: corrected,
}
return c, nil
}
// BitCorrection will attempt to brute-force the codeword to make it validate
// with the parity bits. This can correct up to two errounous bits from transmission.
func BitCorrection(inbits []datatypes.Bit) (bits []datatypes.Bit, corrections int) {
bits = make([]datatypes.Bit, 32)
corrections = 0
copy(bits, inbits)
if syndrome(bits) == 0 {
// valid message
return
}
// test for single bit errors
for a := 0; a < 31; a += 1 {
bits_x := make([]datatypes.Bit, 32)
copy(bits_x, bits)
bits_x[a] = !bits_x[a]
if syndrome(bits_x) == 0 {
bits = bits_x
corrections = 1
return
}
// test double bit errors
for b := 0; b < 31; b += 1 {
// dont flip-flip (negate) the previous change
if b != a {
bits_xx := make([]datatypes.Bit, 32)
copy(bits_xx, bits_x)
bits_xx[b] = !bits_xx[b]
if syndrome(bits_xx) == 0 {
bits = bits_xx
corrections = 2
return
}
}
}
}
return
}
// Print the codeword contents and type to terminal. For debugging.
func (c *Codeword) Print() {
@ -485,10 +554,16 @@ func (c *Codeword) Print() {
}
parity := utils.TernaryStr(c.ValidParity, "", "*")
color.Printf("%s %s %s ", c.Type, payload, parity)
corr := c.BitCorrections
if corr > 0 {
color.Printf("%d bits corrected", corr)
}
color.Printf("%s %s %s\n", c.Type, payload, parity)
println("")
}
// Print the address for debugging
func (c *Codeword) Adress() string {
bytes := utils.MSBBitsToBytes(c.Payload[0:17], 8)
addr := uint(bytes[1])
@ -501,27 +576,52 @@ func (c *Codeword) Adress() string {
}
// 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
return utils.Btouint32(bytes) == POCSAG_PREAMBLE
}
// isIdle matches 4 bytes to the POCSAG idle codeword 0x7A89C197
func isIdle(bytes []byte) bool {
return utils.Btouint32(bytes) == POCSAG_IDLE
}
const (
BHC_COEFF = 0xED200000
BCH_N = 31
BCH_K = 21
)
// syndrome takes a bitstream and uses the parity bits from the BCH polynomial
// generator to calculate if the bits are received correctly.
// A 0 return means the bits are correct.
// Thanks to multimon-ng (https://github.com/EliasOenal/multimon-ng) for
// detailing implmentation of this.
func syndrome(bits []datatypes.Bit) uint32 {
bytes := utils.MSBBitsToBytes(bits, 8)
// take the parity-bit out from our codeword
codeword := utils.Btouint32(bytes) >> 1
// put the mask bit to the far left in the bitstream
mask := uint32(1 << (BCH_N))
coeff := uint32(BHC_COEFF)
var a uint32 = 0
a += uint32(bytes[0]) << 24
a += uint32(bytes[1]) << 16
a += uint32(bytes[2]) << 8
a += uint32(bytes[3])
// step over each data-bit (the first 21)
for a := 0; a < BCH_K; a += 1 {
// step the coefficient and mask right in the bitstream
mask >>= 1
coeff >>= 1
// if the current bit in the codeword is 1 then XOR the codeword with the coefficient
if (codeword & mask) > 0 {
codeword = codeword ^ coeff
}
}
return a == POCSAG_IDLE
// in the end, if the coefficient matches the codeword they
// are canceled out by the XOR, returning 0
return codeword
}

@ -0,0 +1,121 @@
package pocsag
import (
. "gopkg.in/check.v1"
"testing"
"github.com/dhogborg/go-pocsag/internal/datatypes"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }
var _ = Suite(&PocsagSuite{})
type PocsagSuite struct{}
func (f *PocsagSuite) Test_Syndrome_Calculation_Normal_Address(c *C) {
// valid bitstream
bits := bitstream("01010001111011110011110111000010")
syndr := syndrome(bits)
c.Assert(syndr > 0, Equals, false)
}
func (f *PocsagSuite) Test_Syndrome_Calculation_Normal_Idle(c *C) {
// valid bitstream
bits := bitstream("01111010100010011100000110010111")
syndr := syndrome(bits)
c.Assert(syndr > 0, Equals, false)
}
func (f *PocsagSuite) Test_Syndrome_Calculation_Normal_Message(c *C) {
// valid bitstream
bits := bitstream("11001101100000000000011110001100")
syndr := syndrome(bits)
c.Assert(syndr > 0, Equals, false)
}
func (f *PocsagSuite) Test_Syndrome_Calculation_Error_1(c *C) {
// v error
bits := bitstream("01010101111011110011110111000010")
syndr := syndrome(bits)
c.Assert(syndr > 0, Equals, true)
}
func (f *PocsagSuite) Test_Syndrome_Calculation_Error_2(c *C) {
// v v errors
bits := bitstream("01110101111011110011110111000010")
syndr := syndrome(bits)
c.Assert(syndr > 0, Equals, true)
}
func (f *PocsagSuite) Test_BitCorrection_No_Rrror(c *C) {
bits := bitstream("01010001111011110011110111000010")
cbits, corr := BitCorrection(bits)
stream := streambits(cbits)
c.Assert(corr, Equals, 0)
c.Assert(stream, Equals, "01010001111011110011110111000010")
}
func (f *PocsagSuite) Test_BitCorrection_PayloadError(c *C) {
// v error
bits := bitstream("01010101111011110011110111000010")
cbits, corr := BitCorrection(bits)
stream := streambits(cbits)
c.Assert(corr, Equals, 1)
c.Assert(stream, Equals, "01010001111011110011110111000010")
}
func (f *PocsagSuite) Test_BitCorrection_PayloadErrors(c *C) {
// v errors v
bits := bitstream("01010101111011110011010111000010")
cbits, corr := BitCorrection(bits)
stream := streambits(cbits)
c.Assert(corr, Equals, 2)
c.Assert(stream, Equals, "01010001111011110011110111000010")
}
func (f *PocsagSuite) Test_BitCorrection_ParityError(c *C) {
// v
bits := bitstream("01010001111011110011110111010010")
cbits, corr := BitCorrection(bits)
stream := streambits(cbits)
c.Assert(corr, Equals, 1)
c.Assert(stream, Equals, "01010001111011110011110111000010")
}
func bitstream(stream string) []datatypes.Bit {
bits := make([]datatypes.Bit, 32)
for i, c := range stream {
if string(c) == "1" {
bits[i] = datatypes.Bit(true)
} else {
bits[i] = datatypes.Bit(false)
}
}
return bits
}
func streambits(bits []datatypes.Bit) string {
stream := ""
for _, c := range bits {
if c {
stream += "1"
} else {
stream += "0"
}
}
return stream
}

@ -29,14 +29,14 @@ func NewStreamReader(source io.Reader, bauds int) *StreamReader {
}
// 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.
// The scanner will continue indefently or to EOF is reached
func (s *StreamReader) StartScan(bitstream chan []datatypes.Bit) {
fmt.Println("Starting transmission scanner")
for {
bytes := make([]byte, 4096)
bytes := make([]byte, 8192)
c, err := s.Stream.Read(bytes)
if err != nil {
@ -75,7 +75,7 @@ func (s *StreamReader) ReadTransmission(beginning []int16) []int16 {
for {
bytes := make([]byte, 4096)
bytes := make([]byte, 8192)
c, _ := s.Stream.Read(bytes)
if c > 0 {
@ -146,8 +146,8 @@ func (s *StreamReader) ScanTransmissionStart(stream []int16) (int, int) {
}
if DEBUG {
fmt.Println("Mean bitlength:", mean_bitlength)
fmt.Println("Determined bitlength:", bitlength)
blue.Println("Mean bitlength:", mean_bitlength)
blue.Println("Determined bitlength:", bitlength)
}
// look at every other sample to see if we have a repeating pattern with matching size
@ -180,7 +180,7 @@ func (s *StreamReader) ScanTransmissionStart(stream []int16) (int, int) {
if confidence > 10 {
if DEBUG {
fmt.Println("Found bitsync")
blue.Println("Found bitsync")
}
return switches[a] + int(bitlength/2), int(bitlength)

@ -172,6 +172,17 @@ func bcdChar(foo uint8) string {
return chars[foo-10]
}
func Btouint32(bytes []byte) uint32 {
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
}
func TernaryStr(condition bool, a, b string) string {
if condition {
return a
@ -204,3 +215,16 @@ func PrintSample(sample int16) {
red.Printf("%d ", sample)
}
}
func PrintUint32(i uint32) {
var x uint32 = 1 << 31
for a := 0; a < 32; a += 1 {
if (i & x) > 0 {
green.Print("1 ")
} else {
red.Print("0 ")
}
x >>= 1
}
println("")
}

Loading…
Cancel
Save