diff --git a/README.md b/README.md index ebfca92..5367c32 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Go port of the POCSAG::Encode Perl module. +Golang port of the `POCSAG::Encode` Perl module extended to support Numeric as well as AlphaNumeric messages. Example usage @@ -6,7 +6,6 @@ Example usage package main import ( - "fmt" "log" "github.com/kgolding/go-pocsagencode" @@ -14,12 +13,7 @@ import ( func main() { messages := []*pocsagencode.Message{ - &pocsagencode.Message{1300100, "Hello Pager!"}, - } - - for i := 0; i < 50; i++ { - addr := uint32(1200000 + i*100) - messages = append(messages, &pocsagencode.Message{addr, fmt.Sprintf("Hello pager number %d", addr)}) + &pocsagencode.Message{1300100, "Hello Pager!", false}, } log.Println("Sending", len(messages), "messages") diff --git a/examples/cli/main.go b/examples/cli/main.go index 92dfa3b..3b6abe7 100644 --- a/examples/cli/main.go +++ b/examples/cli/main.go @@ -1,29 +1,49 @@ package main import ( + "flag" "fmt" + "log" "os" + "path/filepath" "strconv" pc "github.com/kgolding/go-pocsagencode" ) func main() { - if len(os.Args) != 3 { - fmt.Printf("Usage: %s \ne.g. %s 13100100 'Hello world!'\n", os.Args[0], os.Args[0]) + var num bool + var debug bool + + flag.BoolVar(&num, "num", false, "send as numeric message") + flag.BoolVar(&debug, "v", false, "verbose logging") + flag.Parse() + + if len(flag.Args()) != 2 { + me := filepath.Base(os.Args[0]) + fmt.Printf(` +Usage: %s [-num] + Examples: %s 13100100 'Hello world!' + %s -num 13100100 9876\n`, me, me, me) os.Exit(1) } - addr, err := strconv.Atoi(os.Args[1]) + addr, err := strconv.Atoi(flag.Arg(0)) if err != nil { fmt.Println("Invalid pager addr - must be a number") os.Exit(2) } messages := []*pc.Message{ &pc.Message{ - Addr: uint32(addr), - Content: os.Args[2], + Addr: uint32(addr), + Content: flag.Arg(0), + IsNumeric: num, }, } + + if debug { + pc.SetLogger(log.New(os.Stdout, "", log.Lshortfile)) + } + burst, _ := pc.Generate(messages) fmt.Printf("Message: %s\n\n", burst.String()) diff --git a/examples/sendmultiple/main.go b/examples/sendmultiple/main.go index 86068af..cc990b0 100644 --- a/examples/sendmultiple/main.go +++ b/examples/sendmultiple/main.go @@ -9,12 +9,12 @@ import ( func main() { messages := []*pocsagencode.Message{ - &pocsagencode.Message{1300100, "Hello Pager!"}, + &pocsagencode.Message{1300100, "Hello Pager!", false}, } for i := 0; i < 50; i++ { addr := uint32(1200000 + i*100) - messages = append(messages, &pocsagencode.Message{addr, fmt.Sprintf("Hello pager number %d", addr)}) + messages = append(messages, &pocsagencode.Message{addr, fmt.Sprintf("Hello pager number %d", addr), false}) } log.Println("Sending", len(messages), "messages") diff --git a/pocsagencode.go b/pocsagencode.go index 2d3201e..6dc379f 100644 --- a/pocsagencode.go +++ b/pocsagencode.go @@ -6,8 +6,9 @@ import ( // Message is a single POCSAG Alphanumeric message type Message struct { - Addr uint32 - Content string + Addr uint32 + Content string + IsNumeric bool } var logger *log.Logger @@ -185,6 +186,81 @@ func appendContentText(content string) (int, Burst) { return pos, out } +// appendContentNumeric appends numeric message content to the transmission blob +func appendContentNumeric(content string) (int, Burst) { + out := make(Burst, 0) + debugf("appendContentNumeric: %s", content) + + // 084 2.6]195-3U7[ + charMap := map[byte]byte{ + '0': 0, '8': 1, '4': 2, ' ': 3, '2': 4, '.': 5, '6': 6, ']': 7, + '1': 8, '9': 9, '5': 10, '-': 11, '3': 12, 'U': 13, '7': 14, '[': 15, + } + + bitpos := 0 + word := uint32(0) + leftbits := 0 + pos := 0 + + // walk through characters in message + for i, r := range content { + var char byte + var ok bool + // set char from the charMap, and skip the character is not in the map + if char, ok = charMap[byte(r)]; !ok { + debugf("skipping invlaid char '%s'", string(r)) + continue + } + + debugf(" char %d: %d [%X]\n", i, char, char) + + // if the bits won't fit: + if bitpos+4 > 20 { + space := 20 - bitpos + // leftbits least significant bits of $char are left over in the next word + leftbits = 4 - 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) << uint(31-4-bitpos)) + + bitpos += 4 + + 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) << uint(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 << uint(30-bitpos)) + } + bitpos++ + step++ + if step == 4 { + 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, Burst) { // expand the parameters of the message @@ -217,7 +293,13 @@ func appendMessage(startpos int, msg *Message) (int, Burst) { pos++ // Next, append the message contents - contentEncLen, contentEnc := appendContentText(content) + var contentEncLen int + var contentEnc Burst + if msg.IsNumeric { + contentEncLen, contentEnc = appendContentNumeric(content) + } else { + contentEncLen, contentEnc = appendContentText(content) + } tx = append(tx, contentEnc...) pos += contentEncLen @@ -296,7 +378,7 @@ func selectMsg(pos int, msgListRef []*Message) int { // in the next Generate() call and sent in the next brrraaaap. func Generate(messages []*Message, optionFns ...OptionFn) (Burst, []*Message) { options := &Options{ - MaxLen: 3000, + MaxLen: 2000, PreambleBits: 576, } for _, opt := range optionFns { diff --git a/pocsagencode_test.go b/pocsagencode_test.go index b09548b..6369a59 100644 --- a/pocsagencode_test.go +++ b/pocsagencode_test.go @@ -1,13 +1,51 @@ package pocsagencode import ( + "log" + "os" "testing" ) -func Test_Encode(t *testing.T) { +func init() { + SetLogger(log.New(os.Stdout, "POCSAG ", log.LstdFlags)) + // Comment below to enable debug logging + SetLogger(nil) +} + +func Test_Encode_Numeric(t *testing.T) { + + enc, left := Generate([]*Message{ + &Message{1300100, "12[3]", true}, + }) + if len(left) != 0 { + t.Errorf("expect no message left, got %v", left) + } + + expect := Burst{ + // 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 data starts here + 0x7CD215D8, 0x7A89C197, 0x7A89C197, 0x7A89C197, 0x7A89C197, 0x7A89C197, 0x7A89C197, 0x7A89C197, + 0x7A89C197, 0x4F5A0109, 0xC27E3D14, 0x7A89C197, 0x7A89C197, + } + + if len(enc) != len(expect) { + t.Errorf("expected:\n%s\ngot:\n%s\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) + } + } + } +} + +func Test_Encode_Alpha(t *testing.T) { // SetLogger(log.New(os.Stdout, "POCSAG ", log.LstdFlags)) enc, left := Generate([]*Message{ - &Message{1300100, "happy christmas!"}, + &Message{1300100, "happy christmas!", false}, }) if len(left) != 0 { t.Errorf("expect no message left, got %v", left)