Payload correction using parity check bits
This commit is contained in:
parent
103940924e
commit
b07839f7ef
5 changed files with 278 additions and 32 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
gopocsag
|
gopocsag
|
||||||
|
samples
|
||||||
|
|
|
@ -125,14 +125,14 @@ func (p *POCSAG) ParseMessages(batches []*Batch) []*Message {
|
||||||
switch codeword.Type {
|
switch codeword.Type {
|
||||||
// append current and begin new message
|
// append current and begin new message
|
||||||
case CodewordTypeAddress:
|
case CodewordTypeAddress:
|
||||||
if message != nil {
|
if message != nil && len(message.Payload) > 0 {
|
||||||
messages = append(messages, message)
|
messages = append(messages, message)
|
||||||
}
|
}
|
||||||
message = NewMessage(codeword)
|
message = NewMessage(codeword)
|
||||||
|
|
||||||
// append current but dont start new
|
// append current but dont start new
|
||||||
case CodewordTypeIdle:
|
case CodewordTypeIdle:
|
||||||
if message != nil {
|
if message != nil && len(message.Payload) > 0 {
|
||||||
messages = append(messages, message)
|
messages = append(messages, message)
|
||||||
}
|
}
|
||||||
message = nil
|
message = nil
|
||||||
|
@ -182,6 +182,10 @@ func (m *Message) Print(messagetype MessageType) {
|
||||||
red.Println("This message has parity check errors. Contents might be corrupted")
|
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("")
|
println("")
|
||||||
print(m.PayloadString(messagetype))
|
print(m.PayloadString(messagetype))
|
||||||
println("")
|
println("")
|
||||||
|
@ -245,6 +249,14 @@ func (m *Message) IsValid() bool {
|
||||||
return true
|
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
|
// 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,
|
// 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.
|
// so we can force either type by setting messagetype to something other than Auto.
|
||||||
|
@ -434,6 +446,8 @@ type Codeword struct {
|
||||||
ParityBits []datatypes.Bit
|
ParityBits []datatypes.Bit
|
||||||
EvenParity datatypes.Bit
|
EvenParity datatypes.Bit
|
||||||
ValidParity bool
|
ValidParity bool
|
||||||
|
|
||||||
|
BitCorrections int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCodeword takes 32 bits, creates a new codeword construct, sets the type and checks for parity errors.
|
// 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))
|
return nil, fmt.Errorf("invalid number of bits for codeword: ", len(bits))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bits, corrected := BitCorrection(bits)
|
||||||
|
|
||||||
mtype := CodewordTypeAddress
|
mtype := CodewordTypeAddress
|
||||||
if bits[0] == true {
|
if bits[0] == true {
|
||||||
mtype = CodewordTypeMessage
|
mtype = CodewordTypeMessage
|
||||||
|
@ -453,16 +469,69 @@ func NewCodeword(bits []datatypes.Bit) (*Codeword, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &Codeword{
|
c := &Codeword{
|
||||||
Type: mtype,
|
Type: mtype,
|
||||||
Payload: bits[1:21],
|
Payload: bits[1:21],
|
||||||
ParityBits: bits[21:31],
|
ParityBits: bits[21:31],
|
||||||
EvenParity: bits[31],
|
EvenParity: bits[31],
|
||||||
ValidParity: utils.ParityCheck(bits[0:31], bits[31]),
|
ValidParity: (syndrome(bits) == 0) && utils.ParityCheck(bits[:31], bits[31]),
|
||||||
|
BitCorrections: corrected,
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
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.
|
// Print the codeword contents and type to terminal. For debugging.
|
||||||
func (c *Codeword) Print() {
|
func (c *Codeword) Print() {
|
||||||
|
|
||||||
|
@ -485,10 +554,16 @@ func (c *Codeword) Print() {
|
||||||
}
|
}
|
||||||
|
|
||||||
parity := utils.TernaryStr(c.ValidParity, "", "*")
|
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 {
|
func (c *Codeword) Adress() string {
|
||||||
bytes := utils.MSBBitsToBytes(c.Payload[0:17], 8)
|
bytes := utils.MSBBitsToBytes(c.Payload[0:17], 8)
|
||||||
addr := uint(bytes[1])
|
addr := uint(bytes[1])
|
||||||
|
@ -501,27 +576,52 @@ func (c *Codeword) Adress() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
|
|
||||||
// isPreamble matches 4 bytes to the POCSAG preamble 0x7CD215D8
|
// isPreamble matches 4 bytes to the POCSAG preamble 0x7CD215D8
|
||||||
func isPreamble(bytes []byte) bool {
|
func isPreamble(bytes []byte) bool {
|
||||||
|
return utils.Btouint32(bytes) == POCSAG_PREAMBLE
|
||||||
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
|
// isIdle matches 4 bytes to the POCSAG idle codeword 0x7A89C197
|
||||||
func isIdle(bytes []byte) bool {
|
func isIdle(bytes []byte) bool {
|
||||||
|
return utils.Btouint32(bytes) == POCSAG_IDLE
|
||||||
var a uint32 = 0
|
}
|
||||||
a += uint32(bytes[0]) << 24
|
|
||||||
a += uint32(bytes[1]) << 16
|
const (
|
||||||
a += uint32(bytes[2]) << 8
|
BHC_COEFF = 0xED200000
|
||||||
a += uint32(bytes[3])
|
BCH_N = 31
|
||||||
|
BCH_K = 21
|
||||||
return a == POCSAG_IDLE
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in the end, if the coefficient matches the codeword they
|
||||||
|
// are canceled out by the XOR, returning 0
|
||||||
|
|
||||||
|
return codeword
|
||||||
}
|
}
|
||||||
|
|
121
internal/pocsag/pocsag_test.go
Normal file
121
internal/pocsag/pocsag_test.go
Normal file
|
@ -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.
|
// 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) {
|
func (s *StreamReader) StartScan(bitstream chan []datatypes.Bit) {
|
||||||
|
|
||||||
fmt.Println("Starting transmission scanner")
|
fmt.Println("Starting transmission scanner")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
bytes := make([]byte, 4096)
|
bytes := make([]byte, 8192)
|
||||||
c, err := s.Stream.Read(bytes)
|
c, err := s.Stream.Read(bytes)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -75,7 +75,7 @@ func (s *StreamReader) ReadTransmission(beginning []int16) []int16 {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
bytes := make([]byte, 4096)
|
bytes := make([]byte, 8192)
|
||||||
c, _ := s.Stream.Read(bytes)
|
c, _ := s.Stream.Read(bytes)
|
||||||
|
|
||||||
if c > 0 {
|
if c > 0 {
|
||||||
|
@ -146,8 +146,8 @@ func (s *StreamReader) ScanTransmissionStart(stream []int16) (int, int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if DEBUG {
|
if DEBUG {
|
||||||
fmt.Println("Mean bitlength:", mean_bitlength)
|
blue.Println("Mean bitlength:", mean_bitlength)
|
||||||
fmt.Println("Determined bitlength:", bitlength)
|
blue.Println("Determined bitlength:", bitlength)
|
||||||
}
|
}
|
||||||
|
|
||||||
// look at every other sample to see if we have a repeating pattern with matching size
|
// 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 confidence > 10 {
|
||||||
|
|
||||||
if DEBUG {
|
if DEBUG {
|
||||||
fmt.Println("Found bitsync")
|
blue.Println("Found bitsync")
|
||||||
}
|
}
|
||||||
|
|
||||||
return switches[a] + int(bitlength/2), int(bitlength)
|
return switches[a] + int(bitlength/2), int(bitlength)
|
||||||
|
|
|
@ -172,6 +172,17 @@ func bcdChar(foo uint8) string {
|
||||||
return chars[foo-10]
|
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 {
|
func TernaryStr(condition bool, a, b string) string {
|
||||||
if condition {
|
if condition {
|
||||||
return a
|
return a
|
||||||
|
@ -204,3 +215,16 @@ func PrintSample(sample int16) {
|
||||||
red.Printf("%d ", sample)
|
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…
Add table
Reference in a new issue