diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 6e371274..d07050fe 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -585,7 +585,7 @@ void ModalMessageView::paint(Painter& painter) { painter.draw_string( { 1 * 8, (Coord)(120 + (i * 16)) }, style(), - message_.substr(start, pos) + message_.substr(start, pos - start) ); i++; start = pos + 1; diff --git a/firmware/application/ui_navigation.hpp b/firmware/application/ui_navigation.hpp index 94b8e1a4..8bb2daf9 100644 --- a/firmware/application/ui_navigation.hpp +++ b/firmware/application/ui_navigation.hpp @@ -296,17 +296,17 @@ private: const std::function on_choice_; Button button_ok { - { 10 * 8, 13 * 16, 10 * 8, 24 }, + { 10 * 8, 14 * 16, 10 * 8, 48 }, "OK", }; Button button_yes { - { 5 * 8, 13 * 16, 8 * 8, 48 }, + { 5 * 8, 14 * 16, 8 * 8, 48 }, "YES", }; Button button_no { - { 17 * 8, 13 * 16, 8 * 8, 48 }, + { 17 * 8, 14 * 16, 8 * 8, 48 }, "NO", }; }; diff --git a/firmware/application/ui_pocsag_tx.cpp b/firmware/application/ui_pocsag_tx.cpp index 635a4ff8..f62186cd 100644 --- a/firmware/application/ui_pocsag_tx.cpp +++ b/firmware/application/ui_pocsag_tx.cpp @@ -22,7 +22,6 @@ #include "ui_pocsag_tx.hpp" -#include "pocsag.hpp" #include "baseband_api.hpp" #include "string_format.hpp" @@ -55,16 +54,34 @@ void POCSAGTXView::on_tx_progress(const int progress, const bool done) { progressbar.set_value(progress); } -void POCSAGTXView::start_tx() { +bool POCSAGTXView::start_tx() { uint32_t total_frames, i, codeword, bi, address; pocsag::BitRate bitrate; std::vector codewords; + MessageType type; - address = address_field.value_dec_u32(); - if (address > 0x1FFFFFU) - address = 0; // Todo: Error screen + type = (MessageType)options_type.selected_index_value(); - pocsag_encode(BCH_code, message, address, codewords); + address = field_address.value_dec_u32(); + if (address > 0x1FFFFFU) { + nav_.display_modal("Bad address", "Address must be less\nthan 2097152.", INFO, nullptr); + return false; + } + + if (type == MessageType::NUMERIC_ONLY) { + // Check for invalid characters + if (message.find_first_not_of("0123456789SU -][") != std::string::npos) { + nav_.display_modal( + "Bad message", + "A numeric only message must\nonly contain:\n0123456789SU][- or space.", + INFO, + nullptr + ); + return false; + } + } + + pocsag_encode(type, BCH_code, message, address, codewords); total_frames = codewords.size() / 2; @@ -88,7 +105,7 @@ void POCSAGTXView::start_tx() { data_ptr[bi++] = codeword & 0xFF; } - text_debug_a.set("Codewords: " + to_string_dec_uint(codewords.size())); + //text_debug_a.set("Codewords: " + to_string_dec_uint(codewords.size())); bitrate = pocsag_bitrates[options_bitrate.selected_index()]; @@ -98,6 +115,8 @@ void POCSAGTXView::start_tx() { 4500, 64 ); + + return true; } void POCSAGTXView::paint(Painter&) { @@ -109,15 +128,20 @@ void POCSAGTXView::on_set_text(NavigationView& nav) { textentry(nav, buffer, 16); } -POCSAGTXView::POCSAGTXView(NavigationView& nav) { +POCSAGTXView::POCSAGTXView( + NavigationView& nav +) : nav_ (nav) +{ baseband::run_image(portapack::spi_flash::image_tag_fsktx); add_children({ - &text_debug_a, - &text_address, - &address_field, + &text_bitrate, &options_bitrate, + &text_address, + &field_address, + &text_type, + &options_type, &text_message, &button_message, &progressbar, @@ -125,6 +149,7 @@ POCSAGTXView::POCSAGTXView(NavigationView& nav) { }); options_bitrate.set_selected_index(1); // 1200bps + options_type.set_selected_index(2); // Alphanumeric button_message.on_select = [this, &nav](Button&) { this->on_set_text(nav); @@ -138,12 +163,13 @@ POCSAGTXView::POCSAGTXView(NavigationView& nav) { }; tx_view.on_start = [this]() { - tx_view.set_transmitting(true); - start_tx(); + if (start_tx()) + tx_view.set_transmitting(true); }; tx_view.on_stop = [this]() { tx_view.set_transmitting(false); + transmitter_model.disable(); }; } diff --git a/firmware/application/ui_pocsag_tx.hpp b/firmware/application/ui_pocsag_tx.hpp index c610284b..1cbb7137 100644 --- a/firmware/application/ui_pocsag_tx.hpp +++ b/firmware/application/ui_pocsag_tx.hpp @@ -33,6 +33,9 @@ #include "bch_code.hpp" #include "message.hpp" #include "transmitter_model.hpp" +#include "pocsag.hpp" + +using namespace pocsag; namespace ui { @@ -54,6 +57,7 @@ public: private: char buffer[17] = "PORTAPACK"; std::string message { }; + NavigationView& nav_; BCHCode BCH_code { { 1, 0, 1, 0, 0, 1 }, @@ -62,25 +66,14 @@ private: void on_set_text(NavigationView& nav); void on_tx_progress(const int progress, const bool done); - void start_tx(); + bool start_tx(); - Text text_debug_a { - { 1 * 8, 4 * 8, 20 * 8, 16 }, - "-" + Text text_bitrate { + { 3 * 8, 4 * 8, 8 * 8, 16 }, + "Bitrate:" }; - - Text text_address { - { 3 * 8, 10 * 8, 20 * 8, 16 }, - "Address:" - }; - SymField address_field { - { 11 * 8, 10 * 8 }, - 7, - SymField::SYMFIELD_DEC - }; - OptionsField options_bitrate { - { 11 * 8, 12 * 8 }, + { 11 * 8, 4 * 8 }, 8, { { "512 bps ", 0 }, @@ -89,12 +82,36 @@ private: } }; + Text text_address { + { 3 * 8, 6 * 8, 8 * 8, 16 }, + "Address:" + }; + SymField field_address { + { 11 * 8, 6 * 8 }, + 7, + SymField::SYMFIELD_DEC + }; + + Text text_type { + { 6 * 8, 8 * 8, 5 * 8, 16 }, + "Type:" + }; + OptionsField options_type { + { 11 * 8, 8 * 8 }, + 12, + { + { "Address only", MessageType::ADDRESS_ONLY }, + { "Numeric only", MessageType::NUMERIC_ONLY }, + { "Alphanumeric", MessageType::ALPHANUMERIC } + } + }; + Text text_message { - { 3 * 8, 14 * 8, 16 * 8, 16 }, + { 3 * 8, 12 * 8, 16 * 8, 16 }, "" }; Button button_message { - { 3 * 8, 16 * 8, 8 * 8, 28 }, + { 3 * 8, 14 * 8, 8 * 8, 28 }, "Set" }; diff --git a/firmware/common/pocsag.cpp b/firmware/common/pocsag.cpp index 460e466d..a064941b 100644 --- a/firmware/common/pocsag.cpp +++ b/firmware/common/pocsag.cpp @@ -76,17 +76,43 @@ void insert_BCH(BCHCode& BCH_code, uint32_t * codeword) { // Even parity (*codeword) |= (parity & 1); } + +uint32_t get_digit_code(char code) { + if ((code >= '0') && (code <= '9')) { + code -= '0'; + } else { + if (code == 'S') + code = 10; + else if (code == 'U') + code = 11; + else if (code == ' ') + code = 12; + else if (code == '-') + code = 13; + else if (code == ']') + code = 14; + else if (code == '[') + code = 15; + else + code = 12; + } + + code = ((code & 0x0C) >> 2) | ((code & 0x03) << 2); // ----3210 -> ----1032 + code = ((code & 0x0A) >> 1) | ((code & 0x05) << 1); // ----1032 -> ----0123 + + return code; +} void pocsag_encode( - BCHCode& BCH_code, const std::string text, const uint32_t address, + const MessageType type, BCHCode& BCH_code, const std::string message, const uint32_t address, std::vector& codewords) { size_t b, c, address_slot; size_t bit_idx, char_idx = 0; - uint32_t codeword; + uint32_t codeword, digit_code; char ascii_char; - size_t text_size = text.size(); + size_t message_size = message.size(); // Preamble for (b = 0; b < (POCSAG_PREAMBLE_LENGTH / 32); b++) { @@ -97,7 +123,8 @@ void pocsag_encode( codeword = (address & 0x1FFFF8U) << 10; address_slot = (address & 7) * 2; // Function - codeword |= (3 << 11); + if (type == MessageType::ALPHANUMERIC) + codeword |= (3 << 11); insert_BCH(BCH_code, &codeword); @@ -106,11 +133,13 @@ void pocsag_encode( for (c = 0; c < 16; c++) { if (c == address_slot) { codewords.push_back(codeword); - break; + if (type != MessageType::ADDRESS_ONLY) break; } else codewords.push_back(POCSAG_IDLEWORD); } + if (type == MessageType::ADDRESS_ONLY) return; // Done. + codeword = 0; bit_idx = 20 + 11; @@ -120,49 +149,76 @@ void pocsag_encode( for ( ; c < 16; c++) { - if (char_idx < text_size) { - - // Fill up 20 bits - do { - bit_idx -= 7; + // Fill up 20 bits + if (type == MessageType::ALPHANUMERIC) { + if ((char_idx < message_size) || (ascii_char)) { + do { + bit_idx -= 7; + + if (char_idx < message_size) + ascii_char = message[char_idx] & 0x7F; + else + ascii_char = 0; // Codeword padding + + // Bottom's up + ascii_char = (ascii_char & 0xF0) >> 4 | (ascii_char & 0x0F) << 4; // *6543210 -> 3210*654 + ascii_char = (ascii_char & 0xCC) >> 2 | (ascii_char & 0x33) << 2; // 3210*654 -> 103254*6 + ascii_char = (ascii_char & 0xAA) >> 2 | (ascii_char & 0x55); // 103254*6 -> *0123456 + + codeword |= (ascii_char << bit_idx); + + char_idx++; + + } while (bit_idx > 11); - if (char_idx < text_size) - ascii_char = text[char_idx] & 0x7F; - else - ascii_char = 0; // Codeword padding + codeword &= 0x7FFFF800; // Trim data + codeword |= 0x80000000; // Message type + insert_BCH(BCH_code, &codeword); - // Bottom's up - ascii_char = (ascii_char & 0xF0) >> 4 | (ascii_char & 0x0F) << 4; // *6543210 -> 3210*654 - ascii_char = (ascii_char & 0xCC) >> 2 | (ascii_char & 0x33) << 2; // 3210*654 -> 103254*6 - ascii_char = (ascii_char & 0xAA) >> 2 | (ascii_char & 0x55); // 103254*6 -> *0123456 + codewords.push_back(codeword); - codeword |= (ascii_char << bit_idx); - - char_idx++; - - } while (bit_idx > 11); - - codeword &= 0x7FFFF800; // Trim data - codeword |= 0x80000000; // Message type - insert_BCH(BCH_code, &codeword); - - codewords.push_back(codeword); - - if (bit_idx != 11) { - bit_idx = 20 + bit_idx; - codeword = ascii_char << bit_idx; + if (bit_idx != 11) { + bit_idx = 20 + bit_idx; + codeword = ascii_char << bit_idx; + } else { + bit_idx = 20 + 11; + codeword = 0; + } } else { + codewords.push_back(POCSAG_IDLEWORD); // Batch padding + } + } else if (type == MessageType::NUMERIC_ONLY) { + if (char_idx < message_size) { + do { + bit_idx -= 4; + + if (char_idx < message_size) + digit_code = get_digit_code(message[char_idx]); + else + digit_code = 3; // Space (codeword padding) + + codeword |= (digit_code << bit_idx); + + char_idx++; + + } while (bit_idx > 11); + + codeword |= 0x80000000; // Message type + insert_BCH(BCH_code, &codeword); + + codewords.push_back(codeword); + bit_idx = 20 + 11; codeword = 0; + } else { + codewords.push_back(POCSAG_IDLEWORD); // Batch padding } - } else { - codewords.push_back(POCSAG_IDLEWORD); // Batch padding } } c = 0; - } while (char_idx < text_size); + } while (char_idx < message_size); } bool pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState * const state) { diff --git a/firmware/common/pocsag.hpp b/firmware/common/pocsag.hpp index 6021ecc5..93671512 100644 --- a/firmware/common/pocsag.hpp +++ b/firmware/common/pocsag.hpp @@ -49,6 +49,12 @@ enum OutputType : uint32_t { MESSAGE }; +enum MessageType : uint32_t { + ADDRESS_ONLY, + NUMERIC_ONLY, + ALPHANUMERIC +}; + struct POCSAGState { uint32_t function; uint32_t address; @@ -69,7 +75,9 @@ std::string bitrate_str(BitRate bitrate); std::string flag_str(PacketFlag packetflag); void insert_BCH(BCHCode& BCH_code, uint32_t * codeword); -void pocsag_encode(BCHCode& BCH_code, const std::string text, const uint32_t address, std::vector& codewords); +uint32_t get_digit_code(char code); +void pocsag_encode(const MessageType type, BCHCode& BCH_code, const std::string message, + const uint32_t address, std::vector& codewords); bool pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState * const state); } /* namespace pocsag */ diff --git a/firmware/portapack-h1-havoc.bin b/firmware/portapack-h1-havoc.bin index 7561258b..b78cc76a 100644 Binary files a/firmware/portapack-h1-havoc.bin and b/firmware/portapack-h1-havoc.bin differ