From dfb7af9459908aed1cf22bb6bebeb37a61351f61 Mon Sep 17 00:00:00 2001 From: cheetah Date: Tue, 24 Sep 2019 13:08:38 +0200 Subject: [PATCH] First commit --- .gitignore | 5 + .travis.yml | 67 +++++++ .vscode/extensions.json | 7 + .vscode/settings.json | 51 +++++ include/README | 39 ++++ lib/README | 46 +++++ platformio.ini | 16 ++ src/cuddlycheetah.pocsag.cpp | 378 +++++++++++++++++++++++++++++++++++ src/cuddlycheetah.pocsag.h | 39 ++++ src/main.cpp | 162 +++++++++++++++ test/README | 11 + 11 files changed, 821 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 include/README create mode 100644 lib/README create mode 100644 platformio.ini create mode 100644 src/cuddlycheetah.pocsag.cpp create mode 100644 src/cuddlycheetah.pocsag.h create mode 100644 src/main.cpp create mode 100644 test/README diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7c486f1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,67 @@ +# Continuous Integration (CI) is the practice, in software +# engineering, of merging all developer working copies with a shared mainline +# several times a day < https://docs.platformio.org/page/ci/index.html > +# +# Documentation: +# +# * Travis CI Embedded Builds with PlatformIO +# < https://docs.travis-ci.com/user/integration/platformio/ > +# +# * PlatformIO integration with Travis CI +# < https://docs.platformio.org/page/ci/travis.html > +# +# * User Guide for `platformio ci` command +# < https://docs.platformio.org/page/userguide/cmd_ci.html > +# +# +# Please choose one of the following templates (proposed below) and uncomment +# it (remove "# " before each line) or use own configuration according to the +# Travis CI documentation (see above). +# + + +# +# Template #1: General project. Test it using existing `platformio.ini`. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio run + + +# +# Template #2: The project is intended to be used as a library with examples. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# env: +# - PLATFORMIO_CI_SRC=path/to/test/file.c +# - PLATFORMIO_CI_SRC=examples/file.ino +# - PLATFORMIO_CI_SRC=path/to/test/directory +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..272828b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..867a678 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,51 @@ +{ + "terminal.integrated.env.windows": { + "PATH": "C:\\Users\\BadWoofsky\\.platformio\\penv\\Scripts;C:\\Users\\BadWoofsky\\.platformio\\penv;C:\\Program Files\\ImageMagick-7.0.8-Q16;C:\\Program Files (x86)\\Common Files\\Intel\\Shared Libraries\\redist\\intel64_win\\compiler;M:\\TBuild\\ThirdParty\\Ninja;M:\\TBuild\\ThirdParty\\gyp;M:\\TBuild\\ThirdParty\\Perl\\site\\bin;M:\\TBuild\\ThirdParty\\Perl\\bin;C:\\Program Files\\Java\\jdk1.8.0_211\\bin;C:\\Program Files (x86)\\Common Files\\Oracle\\Java\\javapath;C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v9.0\\bin;C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v9.0\\libnvvp;C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v9.2\\bin;C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v9.2\\libnvvp;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files\\PuTTY\\;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\WINDOWS\\System32\\OpenSSH\\;C:\\Program Files\\Git\\cmd;C:\\Program Files\\Microsoft VS Code\\bin;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\WINDOWS\\System32\\OpenSSH\\;C:\\Program Files (x86)\\Common Files\\Adobe\\AG;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;M:\\AndroidSDKs\\tools;M:\\AndroidSDKs\\platform-tools;C:\\Program Files\\nodejs\\;C:\\Program Files\\NVIDIA Corporation\\NVIDIA NvDLISR;C:\\Python27\\;C:\\Users\\BadWoofsky\\AppData\\Local\\Programs\\Python\\Python36\\Scripts\\;C:\\Users\\BadWoofsky\\AppData\\Local\\Programs\\Python\\Python36\\;C:\\Users\\BadWoofsky\\AppData\\Local\\Microsoft\\WindowsApps;C:\\Program Files\\Microsoft VS Code\\bin;C:\\Users\\BadWoofsky\\AppData\\Local\\Programs\\Fiddler;C:\\Program Files\\nodejs\\;C:\\Users\\BadWoofsky\\Documents\\Tensorflow Projekte\\cuda\\bin;C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v9.0\\bin;C:\\Users\\BadWoofsky\\Downloads\\Documents\\Tensorflow Projekte\\cuda\\bin;C:\\Users\\BadWoofsky\\AppData\\Local\\Microsoft\\WindowsApps;G:\\ALT\\PATH;M:\\PremFoto\\buildenv\\apache-ant-1.10.6\\bin;C:\\Users\\BadWoofsky\\AppData\\Roaming\\npm;C:\\Program Files\\ImageMagick-7.0.8-Q16;C:\\Program Files (x86)\\Common Files\\Intel\\Shared Libraries\\redist\\intel64_win\\compiler;M:\\TBuild\\ThirdParty\\Ninja;M:\\TBuild\\ThirdParty\\gyp;M:\\TBuild\\ThirdParty\\Perl\\site\\bin;M:\\TBuild\\ThirdParty\\Perl\\bin;C:\\Program Files\\Java\\jdk1.8.0_211\\bin;C:\\Program Files (x86)\\Common Files\\Oracle\\Java\\javapath;C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v9.0\\bin;C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v9.0\\libnvvp;C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v9.2\\bin;C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v9.2\\libnvvp;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files\\PuTTY\\;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\WINDOWS\\System32\\OpenSSH\\;C:\\Program Files\\Git\\cmd;C:\\Program Files\\Microsoft VS Code\\bin;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\WINDOWS\\System32\\OpenSSH\\;C:\\Program Files (x86)\\Common Files\\Adobe\\AG;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;M:\\AndroidSDKs\\tools;M:\\AndroidSDKs\\platform-tools;C:\\Program Files\\nodejs\\;C:\\Program Files\\NVIDIA Corporation\\NVIDIA NvDLISR;C:\\Python27\\;C:\\Users\\BadWoofsky\\AppData\\Local\\Programs\\Python\\Python36\\Scripts\\;C:\\Users\\BadWoofsky\\AppData\\Local\\Programs\\Python\\Python36\\;C:\\Users\\BadWoofsky\\AppData\\Local\\Microsoft\\WindowsApps;C:\\Program Files\\Microsoft VS Code\\bin;C:\\Users\\BadWoofsky\\AppData\\Local\\Programs\\Fiddler;C:\\Program Files\\nodejs\\;C:\\Users\\BadWoofsky\\Documents\\Tensorflow Projekte\\cuda\\bin;C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v9.0\\bin;C:\\Users\\BadWoofsky\\Downloads\\Documents\\Tensorflow Projekte\\cuda\\bin;C:\\Users\\BadWoofsky\\AppData\\Local\\Microsoft\\WindowsApps;G:\\ALT\\PATH;M:\\PremFoto\\buildenv\\apache-ant-1.10.6\\bin;C:\\Users\\BadWoofsky\\AppData\\Roaming\\npm", + "PLATFORMIO_CALLER": "vscode" + }, + "C_Cpp.errorSquiggles": "Disabled", + "files.associations": { + "system_error": "cpp", + "string": "cpp", + "array": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "memory": "cpp", + "new": "cpp", + "ostream": "cpp", + "numeric": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "cinttypes": "cpp", + "utility": "cpp", + "typeinfo": "cpp" + } +} \ No newline at end of file diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..732dbb6 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,16 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:nodemcu-32s] +platform = espressif32 +board = nodemcu-32s +framework = arduino +upload_speed = 921600 +monitor_speed = 57600 ; 115200 ;57600 diff --git a/src/cuddlycheetah.pocsag.cpp b/src/cuddlycheetah.pocsag.cpp new file mode 100644 index 0000000..e8df441 --- /dev/null +++ b/src/cuddlycheetah.pocsag.cpp @@ -0,0 +1,378 @@ +#include +/** + * Calculate the CRC error checking code for the given word. + * Messages use a 10 bit CRC computed from the 21 data bits. + * This is calculated through a binary polynomial long division, returning + * the remainder. + * See https://en.wikipedia.org/wiki/Cyclic_redundancy_check#Computation + * for more information. + */ +uint32_t crc(uint32_t inputMsg) { + //Align MSB of denominatorerator with MSB of message + uint32_t denominator = CRC_GENERATOR << 20; + + //Message is right-padded with zeroes to the message length + crc length + uint32_t msg = inputMsg << CRC_BITS; + + //We iterate until denominator has been right-shifted back to it's original value. + for (int column = 0; column <= 20; column++) { + //Bit for the column we're aligned to + int msgBit = (msg >> (30 - column)) & 1; + + //If the current bit is zero, we don't modify the message this iteration + if (msgBit != 0) { + //While we would normally subtract in long division, we XOR here. + msg ^= denominator; + } + + //Shift the denominator over to align with the next column + denominator >>= 1; + } + + //At this point 'msg' contains the CRC value we've calculated + return msg & 0x3FF; +} + +/** + * Calculates the even parity bit for a message. + * If the number of bits in the message is even, return 0, else return 1. + */ +uint32_t parity(uint32_t x) { + //Our parity bit + uint32_t p = 0; + + //We xor p with each bit of the input value. This works because + //xoring two one-bits will cancel out and leave a zero bit. Thus + //xoring any even number of one bits will result in zero, and xoring + //any odd number of one bits will result in one. + for (int i = 0; i < 32; i++) { + p ^= (x & 1); + x >>= 1; + } + return p; +} + +/** + * Encodes a 21-bit message by calculating and adding a CRC code and parity bit. + */ +uint32_t encodeCodeword(uint32_t msg) { + uint32_t fullCRC = (msg << CRC_BITS) | crc(msg); + uint32_t p = parity(fullCRC); + return (fullCRC << 1) | p; +} + +/** + * ASCII encode a null-terminated string as a series of codewords, written + * to (*out). Returns the number of codewords written. Caller should ensure + * that enough memory is allocated in (*out) to contain the message + * + * initial_offset indicates which word in the current batch the function is + * beginning at, so that it can insert SYNC words at appropriate locations. + */ +uint32_t encodeASCII(uint32_t initial_offset, char* str, uint32_t strLen, uint32_t* out) { + //Number of words written to *out + uint32_t numWordsWritten = 0; + + //Data for the current word we're writing + uint32_t currentWord = 0; + + //Nnumber of bits we've written so far to the current word + uint32_t currentNumBits = 0; + + //Position of current word in the current batch + uint32_t wordPosition = initial_offset; + + // while (*str != 0) { + for (int x = 0; x < strLen; x++) { + unsigned char c = *str; + str++; + //Encode the character bits backwards + for (int i = 0; i < TEXT_BITS_PER_CHAR; i++) { + currentWord <<= 1; + currentWord |= (c >> i) & 1; + currentNumBits++; + if (currentNumBits == TEXT_BITS_PER_WORD) { + //Add the MESSAGE flag to our current word and encode it. + *out = encodeCodeword(currentWord | FLAG_MESSAGE); + out++; + currentWord = 0; + currentNumBits = 0; + numWordsWritten++; + + wordPosition++; + if (wordPosition == BATCH_SIZE) { + //We've filled a full batch, time to insert a SYNC word + //and start a new one. + *out = SYNC; + out++; + numWordsWritten++; + wordPosition = 0; + } + } + } + } + + //Write remainder of message + if (currentNumBits > 0) { + //Pad out the word to 20 bits with zeroes + currentWord <<= 20 - currentNumBits; + *out = encodeCodeword(currentWord | FLAG_MESSAGE); + out++; + numWordsWritten++; + + wordPosition++; + if (wordPosition == BATCH_SIZE) { + //We've filled a full batch, time to insert a SYNC word + //and start a new one. + *out = SYNC; + out++; + numWordsWritten++; + wordPosition = 0; + } + } + + return numWordsWritten; +} + +// Char Translationtable +char* mirrorTab = new char[10]{ 0x00, 0x08, 0x04, 0x0c, 0x02, 0x0a, 0x06, 0x0e, 0x01, 0x09 }; +char encodeDigit(char ch) { + if (ch >= '0' && ch <= '9') + return mirrorTab[ch - '0']; + + switch (ch) { + case ' ': + return 0x03; + + case 'u': + case 'U': + return 0x0d; + + case '-': + case '_': + return 0x0b; + + case '(': + case '[': + return 0x0f; + + case ')': + case ']': + return 0x07; + } + + return 0x05; +} + +uint32_t encodeNumeric(uint32_t initial_offset, char* str, uint32_t strLen, uint32_t* out) { + //Number of words written to *out + uint32_t numWordsWritten = 0; + + //Data for the current word we're writing + uint32_t currentWord = 0; + + //Nnumber of bits we've written so far to the current word + uint32_t currentNumBits = 0; + + //Position of current word in the current batch + uint32_t wordPosition = initial_offset; + + Serial.print("ENCODE NUMERIC="); Serial.println(strLen); + // while (*str != 0) { + for (int x = 0; x < strLen; x++) { + unsigned char c = *str; + str++; + //Encode the digit bits backwards + for (int i = 0; i < NUMERIC_BITS_PER_DIGIT; i++) { + currentWord <<= 1; + char digit = encodeDigit(c); + digit = ((digit & 1) <<3) | + ((digit & 2) <<1) | + ((digit & 4) >>1) | + ((digit & 8)>>3); + + currentWord |= (digit >> i) & 1; + currentNumBits++; + if (currentNumBits == NUMERIC_BITS_PER_WORD) { + //Add the MESSAGE flag to our current word and encode it. + *out = encodeCodeword(currentWord | FLAG_MESSAGE); + out++; + currentWord = 0; + currentNumBits = 0; + numWordsWritten++; + + wordPosition++; + if (wordPosition == BATCH_SIZE) { + //We've filled a full batch, time to insert a SYNC word + //and start a new one. + *out = SYNC; + out++; + numWordsWritten++; + wordPosition = 0; + } + } + } + } + + //Write remainder of message + if (currentNumBits > 0) { + //Pad out the word to 20 bits with zeroes + currentWord <<= 20 - currentNumBits; + *out = encodeCodeword(currentWord | FLAG_MESSAGE); + out++; + numWordsWritten++; + + wordPosition++; + if (wordPosition == BATCH_SIZE) { + //We've filled a full batch, time to insert a SYNC word + //and start a new one. + *out = SYNC; + out++; + numWordsWritten++; + wordPosition = 0; + } + } + + return numWordsWritten; +} + + +/** + * An address of 21 bits, but only 18 of those bits are encoded in the address + * word itself. The remaining 3 bits are derived from which frame in the batch + * is the address word. This calculates the number of words (not frames!) + * which must precede the address word so that it is in the right spot. These + * words will be filled with the idle value. + */ +int addressOffset(int address) { + return (address & 0x7) * FRAME_SIZE; +} + +/** + * Encode a full text POCSAG transmission addressed to (address). + * (*message) is a null terminated C string. + * (*out) is the destination to which the transmission will be written. + */ +void encodeTransmission(bool numeric, int address, int fb, char* message, uint32_t strLen, uint32_t* out) { + + + //Encode preamble + //Alternating 1,0,1,0 bits for 576 bits, used for receiver to synchronize + //with transmitter + for (int i = 0; i < PREAMBLE_LENGTH / 32; i++) { + *out = 0xAAAAAAAA; + out++; + } + + uint32_t* start = out; + + //Sync + *out = SYNC; + out++; + + //Write out padding before adderss word + int prefixLength = addressOffset(address); + for (int i = 0; i < prefixLength; i++) { + *out = IDLE; + out++; + } + + //Write address word. + //The last two bits of word's data contain the message type (function bits) + //The 3 least significant bits are dropped, as those are encoded by the + //word's location. + *out = encodeCodeword( ((address >> 3) << 2) | fb); + out++; + + //Encode the message itself + Serial.print("ENCODE TRANSMISSION="); Serial.println(strLen); + if (numeric == true) { + out += encodeNumeric(addressOffset(address) + 1, message, strLen, out); + } else { + out += encodeASCII(addressOffset(address) + 1, message, strLen, out); + } + + + //Finally, write an IDLE word indicating the end of the message + *out = IDLE; + out++; + + //Pad out the last batch with IDLE to write multiple of BATCH_SIZE + 1 + //words (+ 1 is there because of the SYNC words) + size_t written = out - start; + size_t padding = (BATCH_SIZE + 1) - written % (BATCH_SIZE + 1); + for (size_t i = 0; i < padding; i++) { + *out = IDLE; + out++; + } +} + +/** + * Calculates the length in words of a text POCSAG message, given the address + * and the number of characters to be transmitted. + */ +size_t textMessageLength(int address, int numChars) { + size_t numWords = 0; + + //Padding before address word. + numWords += addressOffset(address); + + //Address word itself + numWords++; + + //numChars * 7 bits per character / 20 bits per word, rounding up + numWords += (numChars * TEXT_BITS_PER_CHAR + (TEXT_BITS_PER_WORD - 1)) + / TEXT_BITS_PER_WORD; + + //Idle word representing end of message + numWords++; + + //Pad out last batch with idles + numWords += BATCH_SIZE - (numWords % BATCH_SIZE); + + //Batches consist of 16 words each and are preceded by a sync word. + //So we add one word for every 16 message words + numWords += numWords / BATCH_SIZE; + + //Preamble of 576 alternating 1,0,1,0 bits before the message + //Even though this comes first, we add it to the length last so it + //doesn't affect the other word-based calculations + numWords += PREAMBLE_LENGTH / 32; + + return numWords; +} + +/** + * Calculates the length in words of a numeric POCSAG message, given the address + * and the number of characters to be transmitted. + */ +size_t numericMessageLength(int address, int numChars) { + size_t numWords = 0; + + //Padding before address word. + numWords += addressOffset(address); + + //Address word itself + numWords++; + + //numChars * 7 bits per character / 20 bits per word, rounding up + numWords += (numChars * NUMERIC_BITS_PER_DIGIT + (NUMERIC_BITS_PER_WORD - 1)) + / NUMERIC_BITS_PER_WORD; + + //Idle word representing end of message + numWords++; + + //Pad out last batch with idles + numWords += BATCH_SIZE - (numWords % BATCH_SIZE); + + //Batches consist of 16 words each and are preceded by a sync word. + //So we add one word for every 16 message words + numWords += numWords / BATCH_SIZE; + + //Preamble of 576 alternating 1,0,1,0 bits before the message + //Even though this comes first, we add it to the length last so it + //doesn't affect the other word-based calculations + numWords += PREAMBLE_LENGTH / 32; + + return numWords; +} \ No newline at end of file diff --git a/src/cuddlycheetah.pocsag.h b/src/cuddlycheetah.pocsag.h new file mode 100644 index 0000000..b303ee1 --- /dev/null +++ b/src/cuddlycheetah.pocsag.h @@ -0,0 +1,39 @@ +#include + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; + +#define SYNC 0x7CD215D8 +#define IDLE 0x7A89C197 +#define FRAME_SIZE 2 +#define BATCH_SIZE 16 +#define PREAMBLE_LENGTH 576 + +#define FLAG_ADDRESS 0x000000 +#define FLAG_MESSAGE 0x100000 + +#define FLAG_TEXT_DATA 0x3 +#define FLAG_NUMERIC_DATA 0x0 + +#define TEXT_BITS_PER_WORD 20 +#define TEXT_BITS_PER_CHAR 7 + +#define NUMERIC_BITS_PER_WORD 20 +#define NUMERIC_BITS_PER_DIGIT 4 + +#define CRC_BITS 10 +#define CRC_GENERATOR 0b11101101001 + +uint32_t crc(uint32_t inputMsg); +uint32_t parity(uint32_t x); +uint32_t encodeCodeword(uint32_t msg); + +uint32_t encodeASCII(uint32_t initial_offset, char* str, uint32_t strLen, uint32_t* out); +// Char Translationtable +char encodeDigit(char ch); +uint32_t encodeNumeric(uint32_t initial_offset, char* str, uint32_t strLen, uint32_t* out); +int addressOffset(int address); +void encodeTransmission(bool numeric, int address, int fb, char* message, uint32_t strLen, uint32_t* out); +size_t textMessageLength(int address, int numChars); +size_t numericMessageLength(int address, int numChars); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..25613b5 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,162 @@ +#include + +#pragma region "Bluetooth" +#include +#include +#include +#include + + + +// BLE UUIDs +#define GENERIC_DISPLAY 0x1811 +#define SERVICE_180A_UUID "0000180a-0000-1000-8000-00805f9b34fb" +#define SERVICE_CCCC_UUID "0000CCCC-CCCC-CCCC-1337-00805f9b34fb" +static const BLEUUID ALERT_DISPLAY_SERVICE_UUID = BLEUUID("3db02924-b2a6-4d47-be1f-0f90ad62a048"); +static const BLEUUID DISPLAY_MESSAGE_CHARACTERISTIC_UUID = BLEUUID("8d8218b6-97bc-4527-a8db-13094ac06b1d"); + +// #define BUFF_LEN 140 +// char* payload = new char[BUFF_LEN]; + +class MyServerCallback: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + //todo display `connected` on the screen + Serial.println("LE onConnect"); + // memset(payload, 0x00, BUFF_LEN); + /*connected = true; + hasLongText = false; + hasText = false; + hasTimeText = false; + yScrollPos = 0;*/ + } + + void onDisconnect(BLEServer* pServer) { + // todo display `disconnected` on the screen + Serial.println("LE onDisconnect"); + //connected = false; + } +}; +void emulatePOCSAG(const char* msg); +class DisplayCharacteristicCallback: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* pCharacteristic) { + // write text to OLED + std::string str = pCharacteristic->getValue(); + BLEUUID uuid = pCharacteristic->getUUID(); + std::string uuid_s = uuid.toString(); + Serial.print("onWrite() {uuid="); + Serial.print(String(uuid_s.c_str())); + Serial.print(",len="); + Serial.print(str.length()); + Serial.println("}"); + Serial.println(str.c_str()); + + emulatePOCSAG(str.c_str()); + } +}; + +void setupBT() { + BLEDevice::init("CC-Bluetooth-Pager"); + BLEServer *pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallback()); + + BLEService *pServiceDeviceInfo = pServer->createService(SERVICE_180A_UUID); + BLECharacteristic *pCharacteristicDeviceInfo_ManufacturerName = new BLECharacteristic((uint16_t)0x2A29,BLECharacteristic::PROPERTY_READ); + pCharacteristicDeviceInfo_ManufacturerName->setValue("CuddlyCheetah"); + pServiceDeviceInfo->addCharacteristic(pCharacteristicDeviceInfo_ManufacturerName); + pServiceDeviceInfo->start(); + + BLEService *pService = pServer->createService(SERVICE_CCCC_UUID); + BLECharacteristic *pCharacteristicText = new BLECharacteristic(DISPLAY_MESSAGE_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_WRITE_NR); //Request MTU=500 from client + pCharacteristicText->setCallbacks(new DisplayCharacteristicCallback()); + pService->addCharacteristic(pCharacteristicText);; + pService->start(); + + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->setAppearance(GENERIC_DISPLAY); //Generic Display + pAdvertising->setScanResponse(true); + /*pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue + pAdvertising->setMinPreferred(0x12);*/ + pAdvertising->start(); + Serial.println("Waiting a client connection to notify..."); + BLEDevice::startAdvertising(); +} +#pragma endregion + +#pragma region "POCSAG" +#include +#define BAUDPAUSE 1000000 / 1200 +#define POCSAG_PIN 19 +#define FUNCTION_BITS 3 +#define CAPCODE 924395 +#define IS_NUMERIC true +#define INVERTED false + +void setupPOCSAG() { + pinMode(POCSAG_PIN, OUTPUT); + digitalWrite(POCSAG_PIN, 1); +} +// to simulate some activity +void emulatePOCSAG(const char* msg) { + char* message = (char*)msg; + + Serial.print("emulatePOCSAG "); Serial.println(message); + Serial.print("length="); Serial.println(strlen(message)); + size_t messageLength = IS_NUMERIC + ? numericMessageLength(CAPCODE, strlen(message)) + : textMessageLength(CAPCODE, strlen(message)); + + uint32_t* transmission = (uint32_t*) malloc(sizeof(uint32_t) * messageLength+2); + int Sym=0; + // https://raw.githubusercontent.com/nkolban/ESP32_BLE_Arduino/master/examples/BLE_server_multiconnect/BLE_server_multiconnect.ino + encodeTransmission(IS_NUMERIC, CAPCODE, FUNCTION_BITS, message, strlen(message), transmission); + char *pocsagData=(char *)malloc(messageLength*32 + 1); + + //Serial.println("generating"); + for (int i = 0;i < messageLength; i++) { + if (!INVERTED) transmission[i] = ~transmission[i]; + for (int j = 31; j >= 0; j--) { + pocsagData[Sym] = (transmission[i] >> j) & 0x1 == 1 ? '1' : '0'; + Sym++; + } + } + pocsagData[Sym] = 0x00; + for (const char* p = pocsagData; *p; p++) { + bool bit = (*p == '1'); + Serial.print(bit ? '1' :'0'); + } + for (const char* p = pocsagData; *p; p++) { + bool bit = (*p == '1'); + digitalWrite(POCSAG_PIN, bit); + delayMicroseconds(1000000 / 1200); + } + /*Serial.println(""); + Serial.println("transmitting"); + for (int i = 0; i < messageLength * 32; i++) { + digitalWrite(POCSAG_PIN, TabSymbol[i]); + Serial.print(TabSymbol[i] ? '1' : '0'); + delayMicroseconds(BAUDPAUSE); + }*/ + delayMicroseconds(BAUDPAUSE); + digitalWrite(POCSAG_PIN, 1); +} +void fakePOCSAG() { + for (int i = 0; i < PREAMBLE_LENGTH / 32; i++) { + digitalWrite(POCSAG_PIN, i % 2 == 0); + delayMicroseconds(BAUDPAUSE); + } + delayMicroseconds(BAUDPAUSE); + digitalWrite(POCSAG_PIN, 1); +} +#pragma endregion + + +void setup() { + Serial.begin(57600); + Serial.println('Bluetooth Empfänger V1 - by <@cuddlycheetah>'); + setupBT(); + setupPOCSAG(); + emulatePOCSAG("42"); +} + +void loop() { +} \ No newline at end of file diff --git a/test/README b/test/README new file mode 100644 index 0000000..df5066e --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html