inital commit

master
Kevin Golding 5 years ago
commit cfe7772b7d

@ -0,0 +1,2 @@
Go port of the POCSAG::Encode Perl module.

@ -0,0 +1,364 @@
package pocsagencode
import (
"log"
)
// Message is a single POCSAG Alphanumeric message
type Message struct {
Addr uint32
Content string
}
var logger *log.Logger
// SetLogger can be passed a *log.Logger to enable log output
// Example pocsagencoder.SetLogger(log.New(os.Stdout, "POCSAG ", log.LstdFlags))
func SetLogger(Logger *log.Logger) {
logger = Logger
}
func debugf(format string, args ...interface{}) {
if logger != nil {
logger.Printf(format, args...)
}
}
// The POCSAG transmission starts with 576 bit reversals (101010...).
// That's 576/8 == 72 bytes of 0xAA.
var pocsagPreambleWord uint32 = 0xAAAAAAAA
// The Frame Synchronisation (FS) code is 32 bits:
// 01111100 11010010 00010101 11011000
var pocsagFrameSyncWord uint32 = 0x7CD215D8
// The Idle Codeword:
// 01111010 10001001 11000001 10010111
var pocsagIdleWord uint32 = 0x7A89C197
// calcBchAndParity calculates the binary checksum and parity for a codeword
func calcBchAndParity(cw uint32) uint32 {
// make sure the 11 LSB are 0.
cw &= 0xFFFFF800
parity := 0
// calculate bch
localCw := cw
for bit := 1; bit <= 21; bit++ {
if cw&0x80000000 > 0 {
cw ^= 0xED200000
}
cw = cw << 1
}
localCw |= (cw >> 21)
// at this point $local_cw has codeword with bch
// calculate parity
cw = localCw
for bit := 1; bit <= 32; bit++ {
if cw&0x80000000 > 0 {
parity++
}
cw = cw << 1
}
// turn last bit to 1 depending on parity
cw_with_parity := localCw
if parity%2 != 0 {
cw_with_parity = localCw + 1
}
debugf(" bch_and_parity returning %X\n", cw_with_parity)
return cw_with_parity
}
//
// Given the numeric destination address and function, generate an address codeword.
//
// sub _address_codeword($$)
func addressCodeword(inAddr uint32, function byte) uint32 {
// POCSAG recommendation 1.3.2
// The three least significant bits are not transmitted but
// serve to define the frame in which the address codeword
// must be transmitted.
// So we take them away.
// shift address to right by two bits to remove the least significant bits
addr := inAddr >> 3
// truncate address to 18 bits
addr &= 0x3FFFF
// truncate function to 2 bits
function &= 0x3
// codeword without parity
codeword := addr<<13 | uint32(function)<<11
debugf(" generated address codeword for %d function %d: %X\n", inAddr, function, codeword)
return calcBchAndParity(codeword)
}
// appendMessageCodeword appends a message content codeword to the message, calculating bch+parity for it
func appendMessageCodeword(word uint32) uint32 {
return calcBchAndParity(word | 1<<31)
}
// reverseBits reverses the bits in a byte. Used to encode characters in a text message,
//since the opposite order is used when transmitting POCSAG text.
func reverseBits(in byte) byte {
out := byte(0)
for i := byte(0); i < 7; i++ {
out |= ((in >> i) & 0x01) << (6 - i)
}
return out
}
// appendContentText appends text message content to the transmission blob
func appendContentText(content string) (int, []uint32) {
out := make([]uint32, 0)
debugf("appendContentText: %s", content)
bitpos := 0
word := uint32(0)
leftbits := 0
pos := 0
// walk through characters in message
for i, r := range content {
// make sure it's 7 bits
char := byte(r & 0x7f)
debugf(" char %d: %d [%X]\n", i, char, char)
char = reverseBits(char)
// if the bits won't fit:
if bitpos+7 > 20 {
space := 20 - bitpos
// leftbits least significant bits of $char are left over in the next word
leftbits = 7 - space
debugf(" bits of char won't fit since bitpos is %d, got %d bits free, leaving %d bits in next word", bitpos, space, leftbits)
}
word |= (uint32(char) << (31 - 7 - bitpos))
bitpos += 7
if bitpos >= 20 {
debugf(" appending word: %X\n", word)
out = append(out, appendMessageCodeword(word))
pos++
word = 0
bitpos = 0
}
if leftbits > 0 {
word |= (uint32(char) << (31 - leftbits))
bitpos = leftbits
leftbits = 0
}
}
if bitpos > 0 {
debugf(" got %d bits in word at end of text, word: %X", bitpos, word)
step := 0
for bitpos < 20 {
if step == 2 {
word |= (1 << (30 - bitpos))
}
bitpos++
step++
if step == 7 {
step = 0
}
}
out = append(out, appendMessageCodeword(word))
pos++
}
return pos, out
}
// appendMessage appends a single message to the end of the transmission blob.
func appendMessage(startpos int, msg *Message) (int, []uint32) {
// expand the parameters of the message
addr := msg.Addr
function := byte(0)
type_ := 'a'
content := msg.Content
debugf("append_message: addr %d function %d type %d content %s", addr, function, type_, content)
// the starting frame is selected based on the three least significant bits
frameAddr := addr & 7
frameAddrCw := frameAddr * 2 // or << 2 ?
debugf(" frame_addr is %d, current position %d", frameAddr, startpos)
// append idle codewords, until we're in the right frame for this address
tx := make([]uint32, 0)
pos := 0
for uint32(startpos+pos)%16 != frameAddrCw {
debugf(" inserting IDLE codewords in position %d (%d)", startpos+pos, (startpos+pos)%16)
tx = append(tx, pocsagIdleWord)
pos++
}
// Then, append the address codeword, containing the function and the address
// (sans 3 least significant bits, which are indicated by the starting frame,
// which the receiver is waiting for)
tx = append(tx, addressCodeword(addr, function))
pos++
// Next, append the message contents
contentEncLen, contentEnc := appendContentText(content)
tx = append(tx, contentEnc...)
pos += contentEncLen
// Return the current frame position and the binary string to be appended
return pos, tx
}
// insertSCS inserts Synchronisation Codewords before every 8 POCSAG frames
// (frame is SC+ 64 bytes of address and message codewords)
func insertSCS(tx []uint32) []uint32 {
out := make([]uint32, 0)
// each batch is SC + 8 frames, each frame is 2 codewords,
// each codeword is 32 bits, so we must insert an SC
// every (8*2*32) bits == 64 bytes
txLen := len(tx)
for i := 0; i < txLen; i += 16 {
// put in the CW and 64 the next 64 bytes
out = append(out, pocsagFrameSyncWord)
end := i + 16
if end > txLen {
end = txLen
}
out = append(out, tx[i:end]...)
}
return out
}
// selectMsg selects the optimal next message to be appended, trying to
// minimize the amount of idle codewords transmitted
func selectMsg(pos int, msgListRef []*Message) int {
currentPick := -1
currentDist := 0
posFrame := uint32(pos/2) % 8
debugf("select_msg pos %d: %d", pos, posFrame)
for i := 0; i < len(msgListRef); i++ {
addr := msgListRef[i].Addr
frameAddr := addr & 7
distance := int(frameAddr - posFrame)
if distance < 0 {
distance += 8
}
debugf(" considering list item %d: %d - frame addr %d distance %d\n", i, addr, frameAddr, distance)
if frameAddr == posFrame {
debugf(" exact match %d: %d - frame addr %d\n", i, addr, frameAddr)
return i
}
if currentPick == -1 {
debugf(" first option %d: %d - frame addr %d distance %d\n", i, addr, frameAddr, distance)
currentPick = i
currentDist = distance
continue
}
if distance < currentDist {
debugf(" better option %d: %d - frame addr %d distance %d", i, addr, frameAddr, distance)
currentPick = i
currentDist = distance
}
}
return currentPick
}
// Generate a transmission from an array of given messages, to fit with the maximum lenght
// The function returns the an array of Uint32 to be keyed over the air in FSK, and
// any messages which did not fit in the transmission, given the maximum
// transmission length (in bytes) given in the first parameter. They can be passed
// in the next Generate() call and sent in the next brrraaaap.
func Generate(maxLen int, preambleBits int, messages []*Message) ([]uint32, []*Message) {
txWithoutScs := make([]uint32, 0)
debugf("generate_transmission, maxlen: %d", maxLen)
pos := 0
for len(messages) > 0 {
// figure out an optimal next message to minimize the amount of required idle codewords
// TODO: do a deeper search, considering the length of the message and a possible
// optimal next recipient
optimalNextMsg := selectMsg(pos, messages)
msg := messages[optimalNextMsg]
messages = append(messages[:optimalNextMsg], messages[optimalNextMsg+1:]...)
appendLen, x := appendMessage(pos, msg)
nextLen := pos + appendLen + 2
// initial sync codeword + one for every 16 codewords
nextLen += 1 + int((nextLen-1)/16)
nextLenBytes := nextLen * 4
debugf("after this message of %d codewords, burst will be %d codewords and %d bytes long\n", appendLen, nextLen, nextLenBytes)
if nextLenBytes > maxLen {
if pos == 0 {
debugf("burst would become too large (%d > %d) with first message alone - discarding!", nextLenBytes, maxLen)
} else {
debugf("burst would become too large (%d > %d) - returning msg in queue", nextLenBytes, maxLen)
messages = append([]*Message{msg}, messages...)
break
}
} else {
txWithoutScs = append(txWithoutScs, x...)
pos += appendLen
}
}
// if the burst is empty, return it as completely empty
if pos == 0 {
return []uint32{}, messages
}
// append a couple of IDLE codewords, otherwise many pagers will
// happily decode the junk in the end and show it to the recipient
txWithoutScs = append(txWithoutScs, pocsagIdleWord)
txWithoutScs = append(txWithoutScs, pocsagIdleWord)
burstLen := len(txWithoutScs)
debugf("transmission without SCs: %d bytes, %d codewords\n%X\n", burstLen*4, burstLen, txWithoutScs)
// put SC every 8 frames
burst := insertSCS(txWithoutScs)
burstLen = len(burst)
debugf("transmission with SCs: %d bytes, %d codewords\n%X\n", burstLen*4, burstLen, burst)
if preambleBits > 0 {
preambleWords := preambleBits / 32
if preambleBits%32 > 0 {
preambleWords++
}
preamble := make([]uint32, preambleWords)
for i := range preamble {
preamble[i] = pocsagPreambleWord
}
burst = append(preamble, burst...)
}
return burst, messages
}

@ -0,0 +1,38 @@
package pocsagencode
import (
// "log"
// "os"
"testing"
)
func Test_Encode(t *testing.T) {
// SetLogger(log.New(os.Stdout, "POCSAG ", log.LstdFlags))
enc, left := Generate(3000, 576, []*Message{
&Message{1300100, "happy christmas!"},
})
if len(left) != 0 {
t.Errorf("expect no message left, got %v", left)
}
expect := []uint32{
// 18 words, 576 bits of preamble
0xAAAAAAAA, 0xAAAAAAAA,
0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA,
0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA, 0xAAAAAAAA,
// The real dat starts here
0x7CD215D8, 0x7A89C197, 0x7A89C197, 0x7A89C197, 0x7A89C197, 0x7A89C197, 0x7A89C197, 0x7A89C197,
0x7A89C197, 0x4F5A0109, 0x8B861C9F, 0xC3CF04CD, 0xD8C5A3C6, 0xF979C8DE, 0xBDB878F3, 0x9E110386,
0x7A89C197, 0x7CD215D8, 0x7A89C197,
}
if len(enc) != len(expect) {
t.Errorf("expected:\n%X\ngot:\n%X\n", expect, enc)
} else {
for i, w := range expect {
if w != enc[i] {
t.Errorf("expected:%X got:%X at index %d\n", w, enc[i], i)
}
}
}
}
Loading…
Cancel
Save