diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..4ad321e --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,26 @@ +name: Docs + +on: + push: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4.1.1 + - name: Requirements + run: | + sudo apt-get install doxygen graphviz + - name: Build docs + run: | + cd docs + # touch _build/html/.nojekyll + doxygen Doxyfile.in + - name: Deploy + uses: JamesIves/github-pages-deploy-action@ba1486788b0490a235422264426c45848eac35c6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: gh-pages # The branch the action should deploy to. + folder: docs/_build/html # The folder the action should deploy. diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..522f0ca --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,21 @@ +name: Tests + +on: + push: + branches: [main, devel] + pull_request: + branches: [main, devel] + +jobs: + run-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.1.1 + - name: Install Python Wheel + run: pip install wheel + - name: Install PlatformIO Core + run: pip install -U platformio + - name: Install valgrind + run: sudo apt install -y valgrind + - name: Run GoogleTest + run: python -m platformio test -v -e native_test -a "--gtest_shuffle" -a "--gtest_repeat=5" diff --git a/.gitignore b/.gitignore index 7c4b078..ed1d303 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,10 @@ .vscode .DS_Store config.h +myConfig.h +myDevices.h +build +_build +venv +docs/ex-display/ +platformio.ini.test diff --git a/Arial9pt7b.h b/Arial9pt7b.h deleted file mode 100644 index d8d516b..0000000 --- a/Arial9pt7b.h +++ /dev/null @@ -1,205 +0,0 @@ -#pragma once -#include - -const uint8_t Arial9pt7bBitmaps[] PROGMEM = { - 0xFE, 0xAA, 0xA2, 0x80, 0xDE, 0xF7, 0x29, 0x00, 0x08, 0x86, 0x61, 0x18, - 0x44, 0xFF, 0xC4, 0x43, 0x30, 0x88, 0xFF, 0xC8, 0x86, 0x21, 0x98, 0x44, - 0x00, 0x10, 0x3C, 0xF6, 0xD3, 0xD0, 0xD0, 0x7C, 0x3E, 0x17, 0x13, 0x91, - 0xD3, 0xF6, 0x3C, 0x10, 0x10, 0x70, 0x23, 0x21, 0x08, 0x84, 0x23, 0x20, - 0x89, 0x83, 0x24, 0x07, 0x33, 0x80, 0x93, 0x06, 0xCC, 0x13, 0x10, 0x8C, - 0xC2, 0x12, 0x10, 0x38, 0x1C, 0x06, 0xC1, 0x8C, 0x31, 0x03, 0x60, 0x38, - 0x1F, 0x06, 0x76, 0xC6, 0x98, 0x73, 0x0E, 0x33, 0x63, 0xC4, 0xFE, 0x80, - 0x13, 0x26, 0x4C, 0xCC, 0xCC, 0xCC, 0x46, 0x22, 0x10, 0xC4, 0x62, 0x33, - 0x11, 0x11, 0x13, 0x32, 0x64, 0xC0, 0x25, 0x5C, 0xA5, 0x00, 0x08, 0x04, - 0x02, 0x1F, 0xFF, 0xF8, 0x40, 0x20, 0x10, 0xF8, 0xFF, 0xC0, 0x08, 0xC4, - 0x23, 0x10, 0x84, 0x62, 0x11, 0x88, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0x83, - 0x81, 0x81, 0x81, 0x83, 0xC3, 0xC3, 0x66, 0x3C, 0x19, 0xDF, 0xB9, 0x8C, - 0x63, 0x18, 0xC6, 0x31, 0x80, 0x3C, 0x66, 0xC3, 0xC3, 0x03, 0x03, 0x06, - 0x0C, 0x38, 0x70, 0x40, 0xFF, 0xFF, 0x3C, 0x66, 0xC2, 0xC3, 0x06, 0x1C, - 0x06, 0x03, 0x03, 0x83, 0xC3, 0x66, 0x3C, 0x02, 0x03, 0x03, 0x81, 0x41, - 0x21, 0x91, 0x88, 0x84, 0xFF, 0x81, 0x00, 0x80, 0x40, 0x20, 0x7F, 0x7F, - 0x40, 0x40, 0xDC, 0xE6, 0xC3, 0x01, 0x01, 0xC3, 0xC3, 0x66, 0x3C, 0x3C, - 0x66, 0xC3, 0xC0, 0x9C, 0xE6, 0xC3, 0xC3, 0xC1, 0xC3, 0xC3, 0x66, 0x3C, - 0xFF, 0xFF, 0x06, 0x06, 0x0C, 0x08, 0x18, 0x18, 0x30, 0x30, 0x30, 0x20, - 0x20, 0x3C, 0x66, 0xC3, 0xC3, 0x46, 0x3C, 0x66, 0xC3, 0x81, 0x81, 0xC3, - 0x66, 0x3C, 0x3C, 0x66, 0xC3, 0x83, 0x83, 0xC3, 0xE7, 0x39, 0x03, 0xC3, - 0xC2, 0x6E, 0x3C, 0xC1, 0x80, 0xC1, 0xF0, 0x01, 0x83, 0xC7, 0x8E, 0x0C, - 0x03, 0x80, 0x78, 0x0F, 0x01, 0x80, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, - 0xFC, 0x80, 0x70, 0x1E, 0x01, 0xC0, 0x18, 0x71, 0xE1, 0xC0, 0x80, 0x00, - 0x3C, 0x66, 0xC3, 0xC3, 0x03, 0x06, 0x0E, 0x08, 0x18, 0x18, 0x00, 0x18, - 0x18, 0x07, 0xF0, 0x07, 0x1E, 0x0C, 0x01, 0x84, 0x00, 0x46, 0x3D, 0x32, - 0x32, 0x8B, 0x30, 0xC5, 0x10, 0x62, 0x98, 0x21, 0x4C, 0x11, 0xA6, 0x19, - 0x99, 0x9D, 0x84, 0x73, 0x83, 0x00, 0x08, 0xC0, 0x1C, 0x3C, 0x38, 0x07, - 0xF0, 0x00, 0x06, 0x00, 0xE0, 0x0F, 0x00, 0xB0, 0x19, 0x81, 0x98, 0x30, - 0x83, 0x0C, 0x3F, 0xC6, 0x06, 0x60, 0x64, 0x06, 0xC0, 0x30, 0xFF, 0x3F, - 0xEC, 0x0B, 0x02, 0xC1, 0xBF, 0xCF, 0xFB, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, - 0xFE, 0xFF, 0x00, 0x1F, 0x07, 0x39, 0x81, 0xE0, 0x3C, 0x01, 0x80, 0x30, - 0x06, 0x00, 0xC0, 0x78, 0x0D, 0x83, 0x1C, 0xE1, 0xF0, 0xFF, 0x1F, 0xF3, - 0x03, 0x60, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x03, - 0x7F, 0xCF, 0xF0, 0xFF, 0xFF, 0xFC, 0x03, 0x00, 0xC0, 0x3F, 0xEF, 0xFB, - 0x00, 0xC0, 0x30, 0x0C, 0x03, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xF0, 0x18, - 0x0C, 0x06, 0x03, 0xFD, 0xFE, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x00, 0x0F, - 0x83, 0x9E, 0x60, 0x64, 0x03, 0xC0, 0x0C, 0x00, 0xC3, 0xFC, 0x3F, 0xC0, - 0x34, 0x03, 0x60, 0x33, 0x9E, 0x0F, 0x80, 0xC0, 0x78, 0x0F, 0x01, 0xE0, - 0x3C, 0x07, 0xFF, 0xFF, 0xFE, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, - 0x06, 0xFF, 0xF8, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x43, 0x42, 0x7E, 0x3C, 0xC0, 0xD8, 0x33, 0x0C, 0x63, 0x0C, 0xC1, 0xB8, - 0x3F, 0x07, 0x30, 0xC3, 0x18, 0x73, 0x06, 0x60, 0x6C, 0x0E, 0xC0, 0xC0, - 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF, 0xFF, 0xE0, - 0x3F, 0x01, 0xFC, 0x1F, 0xA0, 0xFD, 0x05, 0xEC, 0x6F, 0x22, 0x79, 0x13, - 0xCD, 0x9E, 0x28, 0xF1, 0x47, 0x8E, 0x3C, 0x21, 0x80, 0xC0, 0x7C, 0x0F, - 0xC1, 0xF8, 0x3D, 0x87, 0x98, 0xF3, 0x1E, 0x33, 0xC3, 0x78, 0x6F, 0x07, - 0xE0, 0x7C, 0x0E, 0x1F, 0x83, 0x9C, 0x60, 0x6C, 0x03, 0xC0, 0x3C, 0x03, - 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0x60, 0x63, 0x9C, 0x1F, 0x80, 0xFF, - 0x3F, 0xFC, 0x0F, 0x03, 0xC0, 0xF0, 0x3F, 0xFB, 0xF8, 0xC0, 0x30, 0x0C, - 0x03, 0x00, 0xC0, 0x00, 0x1F, 0x83, 0x9C, 0x60, 0x6C, 0x03, 0xC0, 0x3C, - 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x33, 0x61, 0xE3, 0x9C, 0x1F, 0xE0, - 0x01, 0xFF, 0x8C, 0x1C, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x0C, 0xFF, 0x8C, - 0x70, 0xC1, 0x8C, 0x18, 0xC0, 0xCC, 0x06, 0xC0, 0x60, 0x3E, 0x1F, 0xEC, - 0x1B, 0x03, 0xC0, 0x1E, 0x01, 0xF0, 0x07, 0x00, 0xF0, 0x3C, 0x0D, 0xFE, - 0x3F, 0x00, 0xFF, 0xFF, 0xFC, 0x10, 0x02, 0x00, 0x40, 0x08, 0x01, 0x00, - 0x20, 0x04, 0x00, 0x80, 0x10, 0x02, 0x00, 0x40, 0xC0, 0x78, 0x0F, 0x01, - 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x68, 0x09, 0x83, 0x3F, - 0xC1, 0xF0, 0xC0, 0x34, 0x02, 0x60, 0x66, 0x06, 0x30, 0xC3, 0x0C, 0x10, - 0x81, 0x98, 0x19, 0x80, 0x90, 0x0F, 0x00, 0x60, 0x06, 0x00, 0xC1, 0xC1, - 0xA0, 0xE0, 0x98, 0x50, 0xCC, 0x2C, 0x66, 0x36, 0x31, 0x11, 0x10, 0x88, - 0x88, 0x6C, 0x6C, 0x36, 0x34, 0x0A, 0x0A, 0x05, 0x05, 0x03, 0x83, 0x80, - 0xC1, 0x80, 0x60, 0x63, 0x04, 0x30, 0xC1, 0x98, 0x0F, 0x00, 0x60, 0x06, - 0x00, 0xF0, 0x19, 0x81, 0x98, 0x30, 0xC6, 0x06, 0xC0, 0x70, 0xC0, 0x26, - 0x06, 0x30, 0xC1, 0x98, 0x19, 0x80, 0xF0, 0x06, 0x00, 0x60, 0x06, 0x00, - 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x7F, 0xCF, 0xF8, 0x07, 0x00, 0xC0, - 0x30, 0x0C, 0x03, 0x00, 0xE0, 0x18, 0x06, 0x01, 0x80, 0x7F, 0xFF, 0xFE, - 0xFC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xF0, 0x86, 0x10, 0x86, - 0x10, 0x84, 0x30, 0x84, 0x30, 0x80, 0xF3, 0x33, 0x33, 0x33, 0x33, 0x33, - 0x33, 0x33, 0xF0, 0x18, 0x1C, 0x3C, 0x34, 0x26, 0x62, 0x43, 0xFF, 0xC0, - 0xCC, 0x3E, 0xE6, 0xC3, 0x03, 0x0F, 0x7F, 0xC3, 0x83, 0xCF, 0x7B, 0xC0, - 0xC0, 0xC0, 0xDC, 0xF6, 0xC3, 0xC1, 0xC1, 0xC1, 0xC3, 0xC3, 0xE6, 0xDC, - 0x3C, 0x66, 0xC3, 0x80, 0x80, 0x80, 0x83, 0xC3, 0x66, 0x3C, 0x03, 0x03, - 0x03, 0x3B, 0x6F, 0xC3, 0x83, 0x83, 0x83, 0x83, 0xC3, 0x6F, 0x3F, 0x3C, - 0x66, 0xC3, 0x81, 0xFF, 0x80, 0xC0, 0xC3, 0x67, 0x3C, 0x3C, 0xC2, 0x3E, - 0x20, 0x82, 0x08, 0x20, 0x82, 0x08, 0x20, 0x3B, 0x6F, 0xC3, 0x83, 0x83, - 0x83, 0x83, 0xC3, 0x6F, 0x3B, 0x03, 0x83, 0xEE, 0x7C, 0xC0, 0xC0, 0xC0, - 0xDC, 0xF6, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xF3, 0xFF, - 0xFF, 0xC0, 0x33, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3B, 0xE0, 0xC0, - 0xC0, 0xC0, 0xC6, 0xCC, 0xD8, 0xF0, 0xF0, 0xD8, 0xCC, 0xCC, 0xC6, 0xC3, - 0xFF, 0xFF, 0xFF, 0xC0, 0xDC, 0xF7, 0x3D, 0xF0, 0xC7, 0x84, 0x3C, 0x21, - 0xE1, 0x0F, 0x08, 0x78, 0x43, 0xC2, 0x1E, 0x10, 0xC0, 0xDC, 0xF6, 0xC3, - 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x3C, 0x66, 0xC3, 0x81, 0x81, - 0x81, 0x81, 0xC3, 0x66, 0x3C, 0xDC, 0xF6, 0xC3, 0xC1, 0xC1, 0xC1, 0xC1, - 0xC3, 0xE6, 0xDC, 0xC0, 0xC0, 0xC0, 0xC0, 0x3B, 0x6F, 0xC3, 0x83, 0x83, - 0x83, 0x83, 0xC3, 0x6F, 0x3B, 0x03, 0x03, 0x03, 0x03, 0xFF, 0x71, 0x8C, - 0x63, 0x18, 0xC6, 0x00, 0x7D, 0x9E, 0x1E, 0x07, 0x83, 0xC1, 0xC1, 0xEE, - 0xF8, 0x23, 0x19, 0xF6, 0x31, 0x8C, 0x63, 0x18, 0x43, 0x80, 0xC3, 0xC3, - 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x6F, 0x7B, 0xC1, 0xA0, 0x98, 0xCC, - 0x62, 0x21, 0xB0, 0x50, 0x28, 0x1C, 0x04, 0x00, 0xC2, 0x1E, 0x38, 0x91, - 0xC4, 0xCA, 0x66, 0x53, 0x16, 0xD0, 0xA6, 0x87, 0x1C, 0x38, 0xC0, 0xC6, - 0x00, 0x63, 0x31, 0x8D, 0x83, 0x81, 0xC0, 0xE0, 0xD8, 0x6C, 0x63, 0x60, - 0xC0, 0xC1, 0xA0, 0x98, 0xC4, 0x62, 0x21, 0xB0, 0x58, 0x28, 0x1C, 0x06, - 0x02, 0x03, 0x07, 0x03, 0x80, 0x7F, 0x01, 0x81, 0x80, 0x80, 0xC0, 0xC0, - 0xC0, 0xC0, 0xC0, 0x7F, 0xC0, 0x39, 0x18, 0xC6, 0x31, 0x88, 0x82, 0x18, - 0xC6, 0x31, 0x84, 0x38, 0xFF, 0xFF, 0x80, 0xE0, 0x83, 0x0C, 0x30, 0xC3, - 0x04, 0x0C, 0x43, 0x0C, 0x30, 0xC3, 0x08, 0xE0, 0xF8, 0xFF, 0xE3, 0xC0 }; - -const GFXglyph Arial9pt7bGlyphs[] PROGMEM = { - { 0, 0, 0, 5, 0, 1 }, // 0x20 ' ' - { 0, 2, 13, 5, 2, -12 }, // 0x21 '!' - { 4, 5, 5, 6, 1, -12 }, // 0x22 '"' - { 8, 10, 13, 10, 0, -12 }, // 0x23 '#' - { 25, 8, 16, 10, 1, -13 }, // 0x24 '$' - { 41, 14, 13, 16, 1, -12 }, // 0x25 '%' - { 64, 11, 13, 12, 1, -12 }, // 0x26 '&' - { 82, 2, 5, 3, 1, -12 }, // 0x27 ''' - { 84, 4, 17, 6, 1, -12 }, // 0x28 '(' - { 93, 4, 17, 6, 1, -12 }, // 0x29 ')' - { 102, 5, 5, 7, 1, -12 }, // 0x2A '*' - { 106, 9, 8, 11, 1, -9 }, // 0x2B '+' - { 115, 1, 5, 5, 2, -1 }, // 0x2C ',' - { 116, 4, 2, 6, 1, -4 }, // 0x2D '-' - { 117, 1, 2, 5, 2, -1 }, // 0x2E '.' - { 118, 5, 13, 5, 0, -12 }, // 0x2F '/' - { 127, 8, 13, 10, 1, -12 }, // 0x30 '0' - { 140, 5, 13, 10, 2, -12 }, // 0x31 '1' - { 149, 8, 13, 10, 1, -12 }, // 0x32 '2' - { 162, 8, 13, 10, 1, -12 }, // 0x33 '3' - { 175, 9, 13, 10, 0, -12 }, // 0x34 '4' - { 190, 8, 13, 10, 1, -12 }, // 0x35 '5' - { 203, 8, 13, 10, 1, -12 }, // 0x36 '6' - { 216, 8, 13, 10, 1, -12 }, // 0x37 '7' - { 229, 8, 13, 10, 1, -12 }, // 0x38 '8' - { 242, 8, 13, 10, 1, -12 }, // 0x39 '9' - { 255, 1, 9, 5, 2, -8 }, // 0x3A ':' - { 257, 1, 12, 5, 2, -8 }, // 0x3B ';' - { 259, 9, 9, 11, 1, -10 }, // 0x3C '<' - { 270, 9, 6, 11, 1, -8 }, // 0x3D '=' - { 277, 9, 9, 11, 1, -10 }, // 0x3E '>' - { 288, 8, 13, 10, 1, -12 }, // 0x3F '?' - { 301, 17, 17, 18, 1, -12 }, // 0x40 '@' - { 338, 12, 13, 12, 0, -12 }, // 0x41 'A' - { 358, 10, 13, 12, 1, -12 }, // 0x42 'B' - { 375, 11, 13, 13, 1, -12 }, // 0x43 'C' - { 393, 11, 13, 13, 1, -12 }, // 0x44 'D' - { 411, 10, 13, 12, 1, -12 }, // 0x45 'E' - { 428, 9, 13, 11, 1, -12 }, // 0x46 'F' - { 443, 12, 13, 14, 1, -12 }, // 0x47 'G' - { 463, 11, 13, 13, 1, -12 }, // 0x48 'H' - { 481, 1, 13, 5, 2, -12 }, // 0x49 'I' - { 483, 8, 13, 9, 0, -12 }, // 0x4A 'J' - { 496, 11, 13, 12, 1, -12 }, // 0x4B 'K' - { 514, 8, 13, 10, 1, -12 }, // 0x4C 'L' - { 527, 13, 13, 15, 1, -12 }, // 0x4D 'M' - { 549, 11, 13, 13, 1, -12 }, // 0x4E 'N' - { 567, 12, 13, 14, 1, -12 }, // 0x4F 'O' - { 587, 10, 13, 12, 1, -12 }, // 0x50 'P' - { 604, 12, 14, 14, 1, -12 }, // 0x51 'Q' - { 625, 12, 13, 13, 1, -12 }, // 0x52 'R' - { 645, 10, 13, 12, 1, -12 }, // 0x53 'S' - { 662, 11, 13, 11, 0, -12 }, // 0x54 'T' - { 680, 11, 13, 13, 1, -12 }, // 0x55 'U' - { 698, 12, 13, 12, 0, -12 }, // 0x56 'V' - { 718, 17, 13, 17, 0, -12 }, // 0x57 'W' - { 746, 12, 13, 12, 0, -12 }, // 0x58 'X' - { 766, 12, 13, 12, 0, -12 }, // 0x59 'Y' - { 786, 11, 13, 11, 0, -12 }, // 0x5A 'Z' - { 804, 4, 17, 5, 1, -12 }, // 0x5B '[' - { 813, 5, 13, 5, 0, -12 }, // 0x5C '\' - { 822, 4, 17, 5, 0, -12 }, // 0x5D ']' - { 831, 8, 7, 8, 0, -12 }, // 0x5E '^' - { 838, 10, 1, 10, 0, 3 }, // 0x5F '_' - { 840, 3, 2, 6, 1, -12 }, // 0x60 '`' - { 841, 8, 10, 10, 1, -9 }, // 0x61 'a' - { 851, 8, 13, 10, 1, -12 }, // 0x62 'b' - { 864, 8, 10, 9, 1, -9 }, // 0x63 'c' - { 874, 8, 13, 10, 1, -12 }, // 0x64 'd' - { 887, 8, 10, 10, 1, -9 }, // 0x65 'e' - { 897, 6, 13, 5, 0, -12 }, // 0x66 'f' - { 907, 8, 14, 10, 1, -9 }, // 0x67 'g' - { 921, 8, 13, 10, 1, -12 }, // 0x68 'h' - { 934, 2, 13, 4, 1, -12 }, // 0x69 'i' - { 938, 4, 17, 4, -1, -12 }, // 0x6A 'j' - { 947, 8, 13, 9, 1, -12 }, // 0x6B 'k' - { 960, 2, 13, 4, 1, -12 }, // 0x6C 'l' - { 964, 13, 10, 15, 1, -9 }, // 0x6D 'm' - { 981, 8, 10, 10, 1, -9 }, // 0x6E 'n' - { 991, 8, 10, 10, 1, -9 }, // 0x6F 'o' - { 1001, 8, 14, 10, 1, -9 }, // 0x70 'p' - { 1015, 8, 14, 10, 1, -9 }, // 0x71 'q' - { 1029, 5, 10, 6, 1, -9 }, // 0x72 'r' - { 1036, 7, 10, 9, 1, -9 }, // 0x73 's' - { 1045, 5, 13, 5, 0, -12 }, // 0x74 't' - { 1054, 8, 10, 10, 1, -9 }, // 0x75 'u' - { 1064, 9, 10, 9, 0, -9 }, // 0x76 'v' - { 1076, 13, 10, 13, 0, -9 }, // 0x77 'w' - { 1093, 9, 10, 9, 0, -9 }, // 0x78 'x' - { 1105, 9, 14, 9, 0, -9 }, // 0x79 'y' - { 1121, 9, 10, 9, 0, -9 }, // 0x7A 'z' - { 1133, 5, 17, 6, 1, -12 }, // 0x7B '{' - { 1144, 1, 17, 5, 2, -12 }, // 0x7C '|' - { 1147, 6, 17, 6, 0, -12 }, // 0x7D '}' - { 1160, 9, 3, 11, 1, -7 } }; // 0x7E '~' - -const GFXfont Arial9pt7b PROGMEM = { - (uint8_t *)Arial9pt7bBitmaps, - (GFXglyph *)Arial9pt7bGlyphs, - 0x20, 0x7E, 21 }; - -// Approx. 1836 bytes diff --git a/AtFinder.cpp b/AtFinder.cpp index b7554c1..1d6920a 100644 --- a/AtFinder.cpp +++ b/AtFinder.cpp @@ -1,7 +1,24 @@ +/* + * © 2024 Chris Harlow + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + #include "AtFinder.h" #include - /* This is a finite state automation (FSA) to recognize a dccex output message in the format <@ screenid screenrow "text">. @@ -14,25 +31,26 @@ At any given time, the state is one of the enum values. Within each state, the parsed character is either used as appropriate or ignored and the state may or may not change to a different state. */ -DISPLAY_CALLBACK AtFinder::callback = nullptr; -uint8_t AtFinder::maxTextLength = 0; -char *AtFinder::text = nullptr; +uint8_t AtFinder::_maxTextLength = 0; +char *AtFinder::_text = nullptr; +CallbackInterface *AtFinder::_callback = nullptr; +Logger *AtFinder::_logger = nullptr; // Set maximum accepted length of "text". Longer texts will be trimmed. // Set callback function that will be called when message detected. // Note: the code is safe if setup is not called before use because the -// code can not reach the text handling or callback states. +// code can not reach the text handling or callback states. // If the sketch has no reference to the setup function at all, // then compiler constant propagation is smart enough to realise // that it can never reach the text handling states, and thus // the entire function has no effect and is eliminated from the link. - -void AtFinder::setup(uint8_t _maxTextLength, DISPLAY_CALLBACK _callback) { - maxTextLength = _maxTextLength; - text = (char *)malloc(maxTextLength + 1); - callback = _callback; +void AtFinder::setup(uint8_t maxTextLength, CallbackInterface *callback) { + _maxTextLength = maxTextLength; + _text = (char *)malloc(_maxTextLength + 1); + _callback = callback; + LOG(LogLevel::LOG_DEBUG, "AtFinder::setup with _maxTextLength %d", _maxTextLength); } void AtFinder::processInputChar(char hot) { @@ -57,7 +75,7 @@ void AtFinder::processInputChar(char hot) { state = SET_OPCODE; return; case SET_OPCODE: // waiting for opcode - state = (hot == '@' && callback) ? SKIP_SPACES1 : FIND_START; + state = (hot == '@' && _callback) ? SKIP_SPACES1 : FIND_START; return; case SKIP_SPACES1: // skip spaces to screen id if (hot == ' ') @@ -82,7 +100,7 @@ void AtFinder::processInputChar(char hot) { screenRow = 0; state = BUILD_ROW; [[fallthrough]]; // character will be reinterpreted - case BUILD_ROW: // building screen row + case BUILD_ROW: // building screen row if (hot == ' ') { state = SKIP_SPACES3; return; @@ -105,14 +123,24 @@ void AtFinder::processInputChar(char hot) { case COPY_TEXT: // copying text to buffer if (hot == '"') { // end of text found - text[textLength] = 0; - if (callback) - callback(screenId, screenRow, text); // ET PHONE HOME! + _text[textLength] = 0; + if (_callback) { + _callback->updateScreen(screenId, screenRow, _text); + } state = FIND_START; return; } - if (textLength < maxTextLength) - text[textLength++] = hot; + if (textLength < _maxTextLength) + _text[textLength++] = hot; return; } } + +void AtFinder::cleanUp() { + free(AtFinder::_text); + AtFinder::_text = nullptr; + AtFinder::_callback = nullptr; + AtFinder::_logger = nullptr; +} + +void AtFinder::setLogger(Logger *logger) { _logger = logger; } diff --git a/AtFinder.h b/AtFinder.h index 5149737..e5ea1cf 100644 --- a/AtFinder.h +++ b/AtFinder.h @@ -1,16 +1,51 @@ +/* + * © 2024 Chris Harlow + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + #ifndef AtFinder_h #define AtFinder_h +#include "CallbackInterface.h" +#include "Logger.h" #include -typedef void (*DISPLAY_CALLBACK)(uint8_t screenId, uint8_t row, char *text); +/// @brief Class to monitor input stream for EXRAIL SCREEN commands +/// Format: <@ screen row "Text"> class AtFinder { public: - static void setup(uint8_t _maxTextLength, DISPLAY_CALLBACK _callback); + /// @brief Setup the AtFinder parser + /// @param maxTextLength Maximum length of text the parser will accept + /// @param callback Parser will call this function and provide display ID, row number, and a char array of text + static void setup(uint8_t maxTextLength, CallbackInterface *callback); + + /// @brief Call this as often as possible with a streaming input (eg. Serial.read()) + /// @param hot The next character to process in the stream static void processInputChar(char hot); + /// @brief Clean up memory allocations used by AtFinder + static void cleanUp(); + + /// @brief Set the logger instance to use for diagnostic logging + /// @param logger Pointer to the Logger instance to use + static void setLogger(Logger *logger); + private: - static DISPLAY_CALLBACK callback; - static uint8_t maxTextLength; - static char *text; + static uint8_t _maxTextLength; + static char *_text; + static CallbackInterface *_callback; + static Logger *_logger; }; #endif \ No newline at end of file diff --git a/CallbackInterface.h b/CallbackInterface.h new file mode 100644 index 0000000..2fb85cb --- /dev/null +++ b/CallbackInterface.h @@ -0,0 +1,50 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef CALLBACKINTERFACE_H +#define CALLBACKINTERFACE_H + +#include "InputActions.h" +#include "Logger.h" +#include + +/// @brief Interface class to use for callbacks +class CallbackInterface { +public: + /// @brief Method to implement updating a screen + /// @param screenId ID of the screen to update + /// @param row Row number to update + /// @param text Text to update + virtual void updateScreen(uint8_t screenId, uint8_t row, const char *text) = 0; + + /// @brief Method to implement to respond to an input action + /// @param action PRESS_UP, PRESS_DOWN, PRESS_LEFT, PRESS_RIGHT, PRESS_CENTRE + virtual void onInputAction(InputAction action) = 0; + + /// @brief Set the logger instance to use for diagnostic logging + /// @param logger Pointer to the Logger instance to use + void setLogger(Logger *logger) { _logger = logger; } + + /// @brief Virtual destructor for the CallBackInterface + virtual ~CallbackInterface() = default; + +protected: + /// @brief Pointer to the logger interface + Logger *_logger = nullptr; +}; + +#endif // CALLBACKINTERFACE_H diff --git a/Configurator.cpp b/Configurator.cpp new file mode 100644 index 0000000..9619763 --- /dev/null +++ b/Configurator.cpp @@ -0,0 +1,84 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#include "Configurator.h" +#include "AtFinder.h" + +#ifndef PIO_UNIT_TESTING +#include "CreateUserDevices.h" +#endif // PIO_UNIT_TESTING + +Configurator::Configurator(Stream *consoleStream, Stream *commandStationStream, LogLevel logLevel) + : _consoleStream(consoleStream), _commandStationStream(commandStationStream) { + _logger = new Logger(_consoleStream); + _logger->setLogLevel(logLevel); + _displayManager = new DisplayManager(); + _displayManager->setLogger(_logger); + _inputManager = new InputManager(); + _inputManager->setLogger(_logger); + _screenManager = new ScreenManager(); + _screenManager->setLogger(_logger); + unsigned long pauseDisplayUpdates = STARTUP_INFO_DELAY + millis(); + _controller = new Controller(_consoleStream, _commandStationStream, _displayManager, _inputManager, _screenManager, + _logger, pauseDisplayUpdates); +} + +void Configurator::initialise() { + LOG(LogLevel::LOG_DEBUG, "Configurator::initialise()"); + AtFinder::setLogger(_logger); // Set the AtFinder logger for debug etc. + AtFinder::setup(100, _controller); // Setup AtFinder to call back to Controller + _displayManager->createDisplays(); // Create user defined displays from myConfig.h + _inputManager->setCallback(_controller); // Input uses the Controller for callbacks + _inputManager->createInput(); // Create user defined input from myConfig.h + // If the user defined input requires a display instance, set it + if (_inputManager->getInput() != nullptr && _inputManager->getInput()->needsDisplay() != -1) { + uint8_t displayId = _inputManager->getInput()->needsDisplay(); + DisplayInterface *display = _displayManager->getDisplayById(displayId); + if (display != nullptr) { + _inputManager->setDisplay(display); + } else { + LOG(LogLevel::LOG_ERROR, "Could not get display ID %d required by the input, user input not available", displayId); + } + } +} + +Stream *Configurator::getConsoleStream() { return _consoleStream; } + +Stream *Configurator::getCommandStationStream() { return _commandStationStream; } + +Logger *Configurator::getLogger() { return _logger; } + +Controller *Configurator::getController() { return _controller; } + +DisplayManager *Configurator::getDisplayManager() { return _displayManager; } + +InputManager *Configurator::getInputManager() { return _inputManager; } + +ScreenManager *Configurator::getScreenManager() { return _screenManager; } + +Configurator::~Configurator() { + AtFinder::cleanUp(); + delete _controller; + delete _logger; + delete _displayManager; + delete _inputManager; + delete _screenManager; + _controller = nullptr; + _logger = nullptr; + _displayManager = nullptr; + _screenManager = nullptr; +} diff --git a/Configurator.h b/Configurator.h new file mode 100644 index 0000000..dca1c2a --- /dev/null +++ b/Configurator.h @@ -0,0 +1,80 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef CONFIGURATOR_H +#define CONFIGURATOR_H + +#include "Controller.h" +#include "Defines.h" +#include "DisplayManager.h" +#include "InputManager.h" +#include "Logger.h" +#include "ScreenManager.h" + +class Configurator { +public: + /// @brief Constructor for this Configurator + /// @param consoleStream Pointer to the Stream for console interaction + /// @param commandStationStream Pointer to the Stream for the CommandStation connection + /// @param logLevel LogLevel to set (default WARN) + Configurator(Stream *consoleStream, Stream *commandStationStream, LogLevel logLevel = LogLevel::LOG_WARN); + + /// @brief Call all one-shot initialisation or begin methods on startup + void initialise(); + + /// @brief Get the console Stream instance for this configurator + /// @return Pointer to the Stream + Stream *getConsoleStream(); + + /// @brief Get the CommandStation connection Stream instance for this configurator + /// @return Pointer to the Stream + Stream *getCommandStationStream(); + + /// @brief Get the Logger instance for this Configurator + /// @return Pointer to the Logger + Logger *getLogger(); + + /// @brief Get the DisplayManager instance for this Configurator + /// @return Pointer to the DisplayManager + DisplayManager *getDisplayManager(); + + /// @brief Get the InputManager instance for this Configurator + /// @return Pointer to the InputManager + InputManager *getInputManager(); + + /// @brief Get the ScreenManager instance for this Configurator + /// @return Pointer to the ScreenManager + ScreenManager *getScreenManager(); + + /// @brief Get the Controller instance for this Configurator + /// @return Pointer to the Controller + Controller *getController(); + + /// @brief Destructor for the Configurator + ~Configurator(); + +private: + Stream *_consoleStream; + Stream *_commandStationStream; + Logger *_logger; + DisplayManager *_displayManager; + InputManager *_inputManager; + ScreenManager *_screenManager; + Controller *_controller; +}; + +#endif // CONFIGURATOR_H diff --git a/Controller.cpp b/Controller.cpp new file mode 100644 index 0000000..d9acbf5 --- /dev/null +++ b/Controller.cpp @@ -0,0 +1,174 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#include "Controller.h" +#include "AtFinder.h" +#include "Version.h" + +Controller::Controller(Stream *consoleStream, Stream *commandStationStream, DisplayManager *displayManager, + InputManager *inputManager, ScreenManager *screenManager, Logger *logger, + unsigned long pauseDisplayUpdatesUntil) + : _consoleStream(consoleStream), _commandStationStream(commandStationStream), _displayManager(displayManager), + _inputManager(inputManager), _screenManager(screenManager), _pauseDisplayUpdatesUntil(pauseDisplayUpdatesUntil) { + _logger = logger; + if (_pauseDisplayUpdatesUntil > 0) { + _pauseDisplayUpdates = true; + } else { + _pauseDisplayUpdates = false; + } +} + +void Controller::begin() { + _displayManager->startDisplays(); // Start displays + _inputManager->startInput(); // Start input + LOG(LogLevel::LOG_MESSAGE, "EX-Display version %s", VERSION); + _displayManager->displayStartupInfo(VERSION); // Display version info +} + +void Controller::update() { + // If the stream from the CommandStation is valid and has data, send it through AtFinder + if (_commandStationStream != nullptr && _commandStationStream->available()) { + char csChar = _commandStationStream->read(); + AtFinder::processInputChar(csChar); + } + // If the stream from the console is valid and has data, send it through AtFinder + if (_consoleStream != nullptr && _consoleStream->available()) { + char consoleChar = _consoleStream->read(); + AtFinder::processInputChar(consoleChar); + } + // On startup, EX-Display version is displayed, this ensures it remains on screen until the elapsed time set by the + // Configurator + // When the time has passed, turn the pause of and clear each display to enable normal display operation + if (_pauseDisplayUpdates && millis() > _pauseDisplayUpdatesUntil) { + LOG(LogLevel::LOG_DEBUG, "_pauseDisplayUpdatesUntil time exceeded, resume display updates"); + _pauseDisplayUpdatesUntil = 0; + _pauseDisplayUpdates = false; + _displayManager->clearAllDisplays(); + } + + // Process user input provided there is a valid InputManager and InputInterface + bool isCalibrating = false; // Set up the calibrating flag to avoid display updates if in progress + if (_inputManager != nullptr) { + auto *input = _inputManager->getInput(); + if (input) { + // Poll the InputInterface's check method for user input actions + isCalibrating = input->isCalibrating(); + input->check(); + } + } + + // Only process displays if there is a valid DisplayManager and ScreenManager, and any input is not calibrating + if (_displayManager != nullptr && _screenManager != nullptr && !_pauseDisplayUpdates && !isCalibrating) { + // Get DisplayManager to update displays using ScreenManager + _displayManager->update(_screenManager); + } +} + +void Controller::updateScreen(uint8_t screenId, uint8_t row, const char *text) { + LOG(LogLevel::LOG_DEBUG, "Controller::updateScreen(%d, %d, %s)", screenId, row, text); + if (_screenManager == nullptr) { + LOG(LogLevel::LOG_DEBUG, "Controller::_screenManager == nullptr"); + return; + } + Screen *screen = _screenManager->updateScreen(screenId); + if (screen != nullptr) { + screen->updateScreenRow(row, text); + } +} + +void Controller::onInputAction(InputAction action) { + LOG(LogLevel::LOG_DEBUG, "Controller::onInputAction(%d)", action); + // No inputs are valid if there are no displays or screens, so just bail out + if (_displayManager == nullptr || _screenManager == nullptr || _displayManager->getFirstDisplay() == nullptr || + _screenManager->getFirstScreen() == nullptr) { + return; + } + auto *display = _displayManager->getFirstDisplay(); + switch (action) { + // Left press selects the previous screen for display + case InputAction::PRESS_LEFT: { + LOG(LogLevel::LOG_DEBUG, "Controller::onInputAction(InputAction::PRESS_LEFT)"); + _selectPreviousScreen(display); + break; + } + // Right press selects the next screen for display + case InputAction::PRESS_RIGHT: { + LOG(LogLevel::LOG_DEBUG, "Controller::onInputAction(InputAction::PRESS_RIGHT)"); + _selectNextScreen(display); + break; + } + // Up scrolls the screen up one row + case InputAction::PRESS_UP: { + LOG(LogLevel::LOG_DEBUG, "Controller::onInputAction(InputAction::PRESS_UP)"); + break; + } + // Down scrolls the screen down one row + case InputAction::PRESS_DOWN: { + LOG(LogLevel::LOG_DEBUG, "Controller::onInputAction(InputAction::PRESS_DOWN)"); + break; + } + // Centre moves input control to the next display - does nothing until multiple displays are supported + case InputAction::PRESS_CENTRE: { + LOG(LogLevel::LOG_DEBUG, "Controller::onInputAction(InputAction::PRESS_CENTRE)"); + break; + } + default: { + LOG(LogLevel::LOG_DEBUG, "Controller::onInputAction(Unknown)"); + break; + } + } +} + +Controller::~Controller() { + _displayManager = nullptr; + _screenManager = nullptr; + _consoleStream = nullptr; + _commandStationStream = nullptr; + _logger = nullptr; +} + +void Controller::_selectPreviousScreen(DisplayInterface *display) { + // If screen ID is invalid, select the first screen + int screenId = display->getScreenId(); + int previousId = -1; + if (screenId == -1) { + previousId = _screenManager->getFirstScreen()->getId(); + } else { + // Otherwise get the ID of the previous screen and update it + Screen *screen = _screenManager->getScreenById(screenId); + previousId = _screenManager->getPreviousScreen(screen)->getId(); + } + display->setScreenId(previousId); + LOG(LogLevel::LOG_DEBUG, "Controller::_selectPreviousScreen: _displayId=%d|screenId=%d|previousId=%d", display->getId(), + screenId, previousId); +} + +void Controller::_selectNextScreen(DisplayInterface *display) { + // If screen ID is invalid, select the first screen + int screenId = display->getScreenId(); + int nextId = -1; + if (screenId == -1) { + nextId = _screenManager->getFirstScreen()->getId(); + } else { + // Otherwise get the ID of the next screen and update it + Screen *screen = _screenManager->getScreenById(screenId); + nextId = _screenManager->getNextScreen(screen)->getId(); + } + display->setScreenId(nextId); + LOG(LogLevel::LOG_DEBUG, "Controller::_selectNextScreen: _displayId=%d|screenId=%d|nextId=%d", display->getId(), + screenId, nextId); +} diff --git a/Controller.h b/Controller.h new file mode 100644 index 0000000..2938146 --- /dev/null +++ b/Controller.h @@ -0,0 +1,83 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef CONTROLLER_H +#define CONTROLLER_H + +#include "CallbackInterface.h" +#include "DisplayManager.h" +#include "InputManager.h" +#include "Logger.h" +#include "ScreenManager.h" +#include + +/// @brief Class for the central controller for EX-Display. +/// All application activities are controlled through this class to manage screens, displays, and user input. All +/// devices have their begin or init type methods called from Controller::begin(). +class Controller : public CallbackInterface { +public: + /// @brief Constructor for this Controller + /// @param consoleStream Pointer to the Stream for console interactions and log output + /// @param commandStationStream Pointer to the incoming Stream connected to EX-CommandStation + /// @param displayManager Pointer to the DisplayManager instance + /// @param inputManager Pointer to the InputManager instance + /// @param screenManager Pointer to the ScreenManager instance + /// @param logger Pointer to the Logger instance + /// @param pauseDisplayUpdatesUntil Time in milliseconds the EX-Display version will be displayed until + Controller(Stream *consoleStream, Stream *commandStationStream, DisplayManager *displayManager, + InputManager *inputManager, ScreenManager *screenManager, Logger *logger, + unsigned long pauseDisplayUpdatesUntil = 0); + + /// @brief Call once to start displays and input + void begin(); + + /// @brief Processes all ongoing activities, monitoring streams, receiving user input, updates displays, etc. + /// Call at least once per main loop iteration + void update(); + + /// @brief Method to implement updating a screen + /// @param screenId ID of the screen to update + /// @param row Row number to update + /// @param text Text to update + void updateScreen(uint8_t screenId, uint8_t row, const char *text) override; + + /// @brief Method to implement to respond to an input action + /// @param action PRESS_UP, PRESS_DOWN, PRESS_LEFT, PRESS_RIGHT, PRESS_CENTRE + void onInputAction(InputAction action) override; + + /// @brief Destructor for the Controller + ~Controller() override; + +private: + Stream *_consoleStream; + Stream *_commandStationStream; + DisplayManager *_displayManager; + InputManager *_inputManager; + ScreenManager *_screenManager; + unsigned long _pauseDisplayUpdatesUntil; + bool _pauseDisplayUpdates; + + /// @brief Selects the previous screen for the selected display + /// @param display Pointer to the DisplayInterface to set + void _selectPreviousScreen(DisplayInterface *display); + + /// @brief Selects the next screen for the selected display + /// @param display Pointer to the DisplayInterface to set + void _selectNextScreen(DisplayInterface *display); +}; + +#endif // CONTROLLER_H diff --git a/CreateDeviceMacroReset.h b/CreateDeviceMacroReset.h new file mode 100644 index 0000000..e74c115 --- /dev/null +++ b/CreateDeviceMacroReset.h @@ -0,0 +1,26 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +/** + * @brief Cleans and resets CreateDevice macros + */ + +#undef USER_DISPLAY +#undef USER_INPUT + +#define USER_DISPLAY(type, params...) +#define USER_INPUT(type, params...) diff --git a/CreateDeviceMacros.h b/CreateDeviceMacros.h new file mode 100644 index 0000000..6382588 --- /dev/null +++ b/CreateDeviceMacros.h @@ -0,0 +1,57 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +/** + * @brief Preprocessor macros to create user specified devices from myDevices.h + */ + +#ifndef CREATEDEVICEMACROS_H +#define CREATEDEVICEMACROS_H + +// Process USER_DISPLAY entries +#include "CreateDeviceMacroReset.h" +#undef USER_DISPLAY +#define USER_DISPLAY(type, params...) this->addDisplay(type::create(params)); +void DisplayManager::createDisplays() { +#ifndef PIO_UNIT_TESTING +#if __has_include("myDevices.h") +#include "myDevices.h" +#else +#error No myDevices.h created, no displays or input devices available +#endif // myDevices.h +#else +#include "test/mocks/MockMyDevices.h" +#endif // PIO_UNIT_TESTING +} + +// Process USER_INPUT entry +#include "CreateDeviceMacroReset.h" +#undef USER_INPUT +#define USER_INPUT(type, params...) this->addInput(type::create(params)); +void InputManager::createInput() { +#ifndef PIO_UNIT_TESTING +#if __has_include("myDevices.h") +#include "myDevices.h" +#else +#error No myDevices.h created, no displays or input devices available +#endif // myDevices.h +#else +#include "test/mocks/MockMyDevices.h" +#endif // PIO_UNIT_TESTING +} + +#endif // CREATEDEVICEMACROS_H diff --git a/CreateUserDevices.h b/CreateUserDevices.h new file mode 100644 index 0000000..c8fa96a --- /dev/null +++ b/CreateUserDevices.h @@ -0,0 +1,42 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +/** + * @brief Include this file in Configurator.h to ensure all user devices in myConfig.h are created + * + */ + +#ifndef CREATEUSERDEVICES_H +#define CREATEUSERDEVICES_H + +// Always include DisplayManager and InputManager for both testing and running +#include "DisplayManager.h" +#include "InputManager.h" + +#ifndef PIO_UNIT_TESTING // Do not include actual device headers for testing as they are incompatible +#include "TFT_eSPIDisplay.h" +#include "TFT_eSPITouch.h" +#else // Include mock headers for testing +#include "test/mocks/MockDisplay.h" +#include "test/mocks/MockInput.h" +#include "test/mocks/MockSPIDisplay.h" +#endif // PIO_UNIT_TESTING + +// Include CreateDeviceMacros last to build the DisplayManager and InputManager implementation methods +#include "CreateDeviceMacros.h" + +#endif // CREATEUSERDEVICES_H diff --git a/Defines.h b/Defines.h index 511c7fc..f848993 100644 --- a/Defines.h +++ b/Defines.h @@ -1,70 +1,60 @@ -#ifndef EX_DISPLAY_H -#define EX_DISPLAY_H +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ -#define MCU 1 -#define TFT 2 +#ifndef DEFINES_H +#define DEFINES_H -#if __has_include("config.h") -#include "config.h" -#else -#warning Configuration file "config.h" not found, using example file "config.example.h" -#include "config.example.h" -#endif +// Give log defines their values before user config +#include "Logger.h" +#define LOG_NONE LOG_NONE +#define LOG_ERROR LOG_ERROR +#define LOG_WARN LOG_WARN +#define LOG_INFO LOG_INFO +#define LOG_DEBUG LOG_DEBUG -// Set default parameters if not set in config.h for some reason -#ifndef TEXT_FONT -#define TEXT_FONT ARIAL9PT7B -#endif -#ifndef TEXT_COLOUR -#define TEXT_COLOUR WHITE -#endif -#ifndef TEXT_SIZE -#define TEXT_SIZE 1 -#endif -#ifndef BACKGROUND_COLOUR -#define BACKGROUND_COLOUR BLACK -#endif -#ifndef SCREEN_ROTATION -#define SCREEN_ROTATION 1 -#endif +#ifndef PIO_UNIT_TESTING // Don't warn or use user config when testing +// Incude user's myConfig.h if it exists. +#if __has_include("myConfig.h") +#include "myConfig.h" +#else +#warning myConfig.h not found, using defaults +#endif // myConfig -#if SCREEN_0_TYPE == MCU -#define SCREEN_0 new EXDisplay(0, new MCUFriendScreen(tft, 8, 20), 30); -#elif SCREEN_0_TYPE == TFT -#define SCREEN_0 new EXDisplay(0, new TFT_eSPIScreen(tft, 8, 20), 30); +// Default settings for streams +#ifndef CONSOLE_STREAM +#define CONSOLE_STREAM Serial +#endif // CONSOLE_STREAM +#ifndef COMMANDSTATION_STREAM +#if defined(ARDUINO_ARCH_ESP32) +#define COMMANDSTATION_STREAM Serial2 #else -#error A screen type for the first screen has not been set, you must define either MCU or TFT -#endif +#define COMMANDSTATION_STREAM Serial +#endif // PLATFORM TYPE +#endif // COMMANDSTATION_STREAM +#endif // PIO_UNIT_TESTING -// Set up console and CS listener for Mega -#if defined(ARDUINO_AVR_MEGA2560) -#define RX_PIN 0 // Define the RX pin for Serial1 -#define CONSOLE Serial -#define CS_LISTEN Serial1 -//#define RX_PIN 19 // Define the RX pin for Serial1 -// Set up console and CS listener for Uno -#elif defined(ARDUINO_AVR_UNO) -#define RX_PIN 0 -#define CONSOLE Serial -#define CS_LISTEN Serial -// Set up console and CS listener for ESP32 -#elif defined(ESP32) -#define RX_PIN 0 -#define CONSOLE Serial -#define CS_LISTEN Serial -// Set up console and CS listener for F411RE -#elif defined(ARDUINO_NUCLEO_F411RE) -#define CONSOLE Serial -HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE -#define CS_LISTEN Serial1 -#define RX_PIN PB7; -// Set up console and CS listener for F446RE -#elif defined(ARDUINO_NUCLEO_F446RE) -#define CONSOLE Serial -HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5 - F446RE -#define CS_LISTEN Serial5 -#define RX_PIN PD2; +// Default log level WARN unless overridden +#ifndef LOG_LEVEL +#define LOG_LEVEL LOG_WARN +#endif // LOG_LEVEL -#endif +// Default to showing the EX-Display version for 2 seconds unless overridden +#ifndef STARTUP_INFO_DELAY +#define STARTUP_INFO_DELAY 3000 +#endif // STARTUP_INFO_DELAY -#endif \ No newline at end of file +#endif // DEFINES_H diff --git a/DisplayFunctions.cpp b/DisplayFunctions.cpp deleted file mode 100644 index 1304b98..0000000 --- a/DisplayFunctions.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "Defines.h" -#include "DisplayFunctions.h" -#include "MCUFriendScreen.h" - -#ifdef DEBUG -bool debug = true; -#else -bool debug = false; -#endif - -// EXDisplay *display0 = new EXDisplay(0, new MCUFriendScreen(8, 20), 30); - -// This function is called from AtFinder when a -// <@ screenid row "text"> message is discovered. - -void updateEXDisplayRow(uint8_t screenId, uint8_t screenRow, char *text) { - - // special diagnostic to dump buffers on request - if (screenId == 255) { - displayAllRows(); - return; - } - - EXDisplay *display = EXDisplay::getDisplayByNumber(screenId); - if (display) { - display->updateRow(screenRow, text); - CONSOLE.print(F("\nCallback activated for screen|row|text: ")); - CONSOLE.print(screenId); - CONSOLE.print(F("|")); - CONSOLE.print(screenRow); - CONSOLE.print(F("|")); - CONSOLE.println(text); - // Set a flag so the screen driver knows something has changed. - // ScreenChanged[screenId]=true; - // If this is the current screen we could call a row update line directly from here - // but do we know which screen row to use? - // PrintThisLine(screenId, screenRow, text) - } else { - CONSOLE.print("\nCallback ignored for screen "); - CONSOLE.println(screenId); - } -} - -void updateScreens() { - for (EXDisplay *display = EXDisplay::getFirst(); display; display = display->getNext()) { - auto *screen = display->getEXScreen(); -#ifdef SCROLLTIME - display->autoScroll(SCROLLTIME); -#endif - for (EXDisplayRow *row = display->getFirstRow(); row; row = row->getNext()) { - if (row->needsRender() && row->isChanged()) { - screen->writeRow(row->getDisplayRow(), 0, TEXT_FONT, TEXT_COLOUR, TEXT_SIZE, row->getRowText()); - } - } - } -} - -void displayAllRows() { - for (EXDisplay *display = EXDisplay::getFirst(); display; display = display->getNext()) { - CONSOLE.print(F("\n\nRows for display ")); - CONSOLE.println(display->getDisplayNumber()); - CONSOLE.println(F("Row|Display Row|Message|isChanged|needsRender")); - for (EXDisplayRow *row = display->getFirstRow(); row; row = row->getNext()) { - CONSOLE.print(row->getRowNumber()); - CONSOLE.print(F("|")); - CONSOLE.print(row->getDisplayRow()); - CONSOLE.print(F("|")); - CONSOLE.print(row->getRowText()); - CONSOLE.print(F("|")); - CONSOLE.print(row->isChanged()); - CONSOLE.print(F("|")); - CONSOLE.println(row->needsRender()); - } - } -} diff --git a/DisplayInterface.h b/DisplayInterface.h new file mode 100644 index 0000000..df76819 --- /dev/null +++ b/DisplayInterface.h @@ -0,0 +1,119 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef DISPLAYINTERFACE_H +#define DISPLAYINTERFACE_H + +#include "Logger.h" +#include "Screen.h" + +/// @brief Class to abstract away all physical display implementation to enable multiple display types +class DisplayInterface { +public: + /// @brief Perform any initial once off setup or configuration here and call only once + virtual void begin() = 0; + + /// @brief Clear the entire screen + virtual void clearScreen() = 0; + + /// @brief Display the specified Screen on this display + /// @param screen Pointer to the Screen to display + virtual void displayScreen(Screen *screen) = 0; + + /// @brief Display the startup screen with software version + /// @param version EX-Display version + virtual void displayStartupInfo(const char *version) = 0; + + /// @brief Set the next DisplayInterface derived instance in the list + /// @param display Pointer to the next instance + void setNext(DisplayInterface *display) { _next = display; } + + /// @brief Get the next DisplayInterface derived instance in the list + /// @return Pointer to the next instance + DisplayInterface *getNext() { return _next; } + + /// @brief Set the logger instance to use for diagnostic logging + /// @param logger Pointer to the Logger instance to use + void setLogger(Logger *logger) { _logger = logger; } + + /// @brief Set the ID for this display instance + /// @param displayId ID of this display + void setId(uint8_t displayId) { _displayId = displayId; } + + /// @brief Get the ID of this display instance + /// @return ID of this display + uint8_t getId() { return _displayId; } + + /// @brief Set the Screen ID this display is currently displaying + /// @param screenId Screen ID + void setScreenId(int screenId) { + _needsRedraw = true; + _screenId = screenId; + } + + /// @brief Get the Screen ID this display is currently displaing + /// @return Screen ID + int getScreenId() { return _screenId; } + + /// @brief Get the defined CS pin for this display to see if it needs manual SPI switching + /// @return Pin number of the SPI CS pin for this display (default -1 for no switching) + int getCSPin() { return _csPin; } + + /// @brief Set the flag for whether this display needs redrawing or not - individual row updates are not affected + /// @param redraw true if entire redraw, otherwise false + void setNeedsRedraw(bool redraw) { _needsRedraw = redraw; } + + /// @brief Test if this display needs an entire redraw + /// @return true|false + bool needsRedraw() { return _needsRedraw; } + + /// @brief Destructor for a DisplayInterface + virtual ~DisplayInterface() = default; + +protected: + /// @brief Pointer to the next DisplayInterface derived instance in the list + DisplayInterface *_next = nullptr; + /// @brief Default text colour for the display + uint16_t _textColour; + /// @brief Default background colour for the display + uint16_t _backgroundColour; + /// @brief Pointer to the Logger instance for the DisplayInterface derived classes + Logger *_logger = nullptr; + /// @brief ID for this display instance + uint8_t _displayId = 0; + /// @brief ID of the screen this display is currently displaying, defaults to -1 to flag it is not a valid ID + int _screenId = -1; + /// @brief Orientation of this display, most displays require this setting otherwise ignore it + uint8_t _rotation = 0; + /// @brief Multiplier for text size, most displays require this setting otherwise ignore it + uint8_t _textSize = 1; + /// @brief Maximum row number (not count) that will fit on this display (based on font height, not pixels) + uint8_t _maxRow = 0; + /// @brief Maximum column number (not count) that will fit on this display (based on font width, not pixels) + uint8_t _maxColumn = 0; + /// @brief Calculated font height to determine row positioning + uint8_t _fontHeight = 0; + /// @brief Calculated font width to determine column positioning + uint8_t _fontWidth = 0; + /// @brief If there are more than one SPI displays that libraries don't officially support, the CS pin can be provided + /// to switch between them (default -1 disables this) + int _csPin = -1; + /// @brief Flag that this display needs redrawing - needed for switching between screens + bool _needsRedraw = true; +}; + +#endif // DISPLAYINTERFACE_H diff --git a/DisplayManager.cpp b/DisplayManager.cpp new file mode 100644 index 0000000..a53ec1e --- /dev/null +++ b/DisplayManager.cpp @@ -0,0 +1,162 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#include "DisplayManager.h" +#ifndef PIO_UNIT_TESTING // Cannot create physical displays with Platform IO testing +#include "TFT_eSPIDisplay.h" +#endif // PIO_UNIT_TESTING + +DisplayManager::DisplayManager() : _firstDisplay(nullptr), _logger(nullptr), _nextDisplayId(0) {} + +void DisplayManager::addDisplay(DisplayInterface *display) { + LOG(LogLevel::LOG_DEBUG, "DisplayManager::addDisplay()"); + if (display == nullptr) { + return; + } + display->setId(_nextDisplayId++); + if (_logger != nullptr) { + display->setLogger(_logger); + } + if (_firstDisplay == nullptr) { + _firstDisplay = display; + return; + } + DisplayInterface *current = _firstDisplay; + while (current->getNext() != nullptr) { + current = current->getNext(); + } + current->setNext(display); +} + +void DisplayManager::startDisplays() { + LOG(LogLevel::LOG_DEBUG, "DisplayManager::startDisplays()"); + if (_firstDisplay == nullptr) { + return; + } + _setupAllSPICSPins(); + for (auto *display = _firstDisplay; display; display = display->getNext()) { + display->begin(); + } +} + +void DisplayManager::clearAllDisplays() { + LOG(LogLevel::LOG_DEBUG, "DisplayManager::clearAllDisplays()"); + if (_firstDisplay == nullptr) { + return; + } + for (auto *display = _firstDisplay; display; display = display->getNext()) { + // Select this SPI display if necessary + _selectSPIDisplay(display); + display->clearScreen(); + } +} + +void DisplayManager::displayStartupInfo(const char *version) { + LOG(LogLevel::LOG_DEBUG, "DisplayManager::displayStartupInfo(%s)", version); + // Do nothing if we don't have a DisplayManager or any displays + if (_firstDisplay == nullptr) { + return; + } + for (auto *display = _firstDisplay; display; display = display->getNext()) { + // Select this SPI display if necessary + _selectSPIDisplay(display); + display->displayStartupInfo(version); + } +} + +void DisplayManager::update(ScreenManager *screenManager) { + // Do nothing if there is no ScreenManager with Screen info + if (screenManager == nullptr) { + return; + } + // Iterate through each display + for (auto *display = _firstDisplay; display; display = display->getNext()) { + // Select this SPI display if necessary + _selectSPIDisplay(display); + // If the screen ID for this display is invalid, default to the same ID + int screenId = display->getScreenId(); + if (screenId == -1) { + screenId = display->getId(); + display->setScreenId(screenId); + } + // Get the current screen for this display + Screen *screen = screenManager->getScreenById(screenId); + // If there is one, display it + if (screen) { + display->displayScreen(screen); + } + } +} + +DisplayInterface *DisplayManager::getFirstDisplay() { return _firstDisplay; } + +DisplayInterface *DisplayManager::getDisplayById(uint8_t displayId) { + for (DisplayInterface *display = _firstDisplay; display; display = display->getNext()) { + if (display->getId() == displayId) { + return display; + } + } + return nullptr; +} + +void DisplayManager::setLogger(Logger *logger) { _logger = logger; } + +DisplayManager::~DisplayManager() { + if (_firstDisplay != nullptr) { + DisplayInterface *display = _firstDisplay; + DisplayInterface *next = nullptr; + while (display) { + next = display->getNext(); + delete display; + display = next; + } + _firstDisplay = nullptr; + } + _logger = nullptr; +} + +void DisplayManager::_setupAllSPICSPins() { + for (DisplayInterface *display = _firstDisplay; display; display = display->getNext()) { + int csPin = display->getCSPin(); + // If not set, don't do anything and move to the next display + if (csPin == -1) { + continue; + } + // Set the pin to output mode + pinMode(csPin, OUTPUT); + // Then enable the display ready for _tft->init() + digitalWrite(csPin, LOW); + } +} + +void DisplayManager::_selectSPIDisplay(DisplayInterface *display) { + // If not set, don't do anything + int csPin = display->getCSPin(); + if (csPin == -1) { + return; + } + // First every other display needs to be disabled + for (DisplayInterface *other = _firstDisplay; other; other = other->getNext()) { + if (other != display) { + if (other->getCSPin() != -1) { + digitalWrite(other->getCSPin(), HIGH); + } + } + } + // Otherwise set CS pin low to select this screen + digitalWrite(csPin, LOW); +} diff --git a/DisplayManager.h b/DisplayManager.h new file mode 100644 index 0000000..43c2d24 --- /dev/null +++ b/DisplayManager.h @@ -0,0 +1,81 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef DISPLAYMANAGER_H +#define DISPLAYMANAGER_H + +#include "DisplayInterface.h" +#include "Logger.h" +#include "ScreenManager.h" + +/// @brief Manages all physical displays +class DisplayManager { +public: + /// @brief Constructor for the DisplayManager + DisplayManager(); + + /// @brief Add a DisplayInterface derived instance to the list of displays + /// @param display Pointer to the instance to add + void addDisplay(DisplayInterface *display); + + /// @brief Parses the displays configured in myConfig.h and creates the list of displays (this is implemented in + /// CreateDeviceMacros.h, not DisplayManager.cpp) + void createDisplays(); + + /// @brief Call the begin() method for all associated Display instances + void startDisplays(); + + /// @brief Clear all displays + void clearAllDisplays(); + + /// @brief Calls the displayStartupInfo of all Display instances to display the version + /// @param version EX-Display version + void displayStartupInfo(const char *version); + + /// @brief Call this routinely to ensure all displays are updated with screens + /// @param screenManager Pointer to the ScreenManager instance + void update(ScreenManager *screenManager); + + /// @brief Get the first DisplayInterface derived instance in the list of displays + /// @return Pointer to the first instance + DisplayInterface *getFirstDisplay(); + + /// @brief Get a DisplayInterface derived instance by its ID + /// @return Pointer to the specified instance + DisplayInterface *getDisplayById(uint8_t displayId); + + /// @brief Set the Logger instance + /// @param logger Pointer to the Logger + void setLogger(Logger *logger); + + /// @brief Destructor for the DisplayManager + ~DisplayManager(); + +private: + DisplayInterface *_firstDisplay; + Logger *_logger; + uint8_t _nextDisplayId; + + /// @brief Set the chip select pin state for all displays that have it set and select them + void _setupAllSPICSPins(); + + /// @brief Manually selects SPI displays that require manual selecting via the CS pin - sets pin low + /// @param display Pointer to the DisplayInterface instance + void _selectSPIDisplay(DisplayInterface *display); +}; + +#endif // DISPLAYMANAGER_H diff --git a/EX-Display.ino b/EX-Display.ino index b7270ab..0fddd4a 100644 --- a/EX-Display.ino +++ b/EX-Display.ino @@ -1,139 +1,196 @@ - -#include "AtFinder.h" -#include "Defines.h" -#include "DisplayFunctions.h" -#include "MCUFriendScreen.h" -#include "TFT_eSPIScreen.h" -#include "version.h" -#include - -bool StartupPhase = true; -unsigned long timestamp = 0; -long screencount = 0; - -// Chris just doing this for manual testing on my mega... so I can debug down the serial monitor -#if defined(ARDUINO_AVR_MEGA2560) -#undef CS_LISTEN -#define CS_LISTEN Serial -#endif - -#if SCREEN_0_TYPE == MCU -MCUFRIEND_kbv tft; -#elif SCREEN_0_TYPE == TFT -TFT_eSPI tft = TFT_eSPI(); -#endif - +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +/** + * @mainpage EX-Display - a project for extending EX-CommandStation displays and screens + * + * @section intro_sec Introduction + * EX-Display enables one or more physical displays to be connected to EX-CommandStation via a serial connection to take + * advantage of the EXRAIL SCREEN() command to display various user-generated information. + * + * This can also be used to remotely display the EX-CommandStation serial logs. + * + * @section details Details + * This software has been written to be extensible in order to work with various different types of microcontrollers, + * display libraries, and input devices. + * + * One important note is that EXRAIL SCREEN() commands are only published to the EX-CommandStation serial console, and + * therefore you will need to connect the appropriate EX-Display device UART's Rx pin to the first EX-CommandStation + * UART's Tx pin. These commands cannot be received on additional serial ports, nor over WiFi. + * + * Sensible defaults have been chosen for different platforms, for example, by default Serial2 on Espressif ESP32 + * platforms will be used for the connection to EX-CommandStation, whereas on a Nucleo F411RE, this will be Serial6. + * + * EX-Display looks for serial input in this format: + * @code {.cpp} + * <@ screen row "text"> + * @endcode + * + * For general user configuration options, refer to @ref myConfig.example.h, and for configuring devices, refer to @ref + * myDevices.example.h. + * + * The sensible defaults for the different platforms and so forth are in @ref Defines.h, with sensible defaults for each + * device being located in the appropriate header file, for example @ref TFT_eSPIDisplay.h. + * + * @section application_architecture Application Architecture + * The application has been written in a way that all configuration of user devices and preferences, and all instance + * instantiation is performed by what is called the Configurator, see @ref Configurator.h. + * + * Once all user configuration, devices, and the various instances are instantiated, the Controller takes care of + * initialising those. It is then responsible for reading input from EX-CommandStation and the user input device, and + * directing these to the ScreenManager or DisplayManager as appropriate, see @ref Controller.h. + * + * All Screen input is directed to the ScreenManager to manage the content including interpretation of the various + * attributes affecting what the Screen is expected to look like, see @ref ScreenManager.h. Each Screen is + * representative of the output from EX-CommandStation only, and has no involvement on what is physically displayed. + * + * The DisplayManager (see @ref DisplayManager.h) is responsible for creating user display instances and directing which + * Screen a Display should be displaying, with physical displays implemented by a class deriving from the + * DisplayInterface class (see @ref DisplayInterface.h). This enables support for various different display libraries + * and associated devices and keeps those abstracted from the DisplayManager to prevent conflicts. + * + * Likewise, the InputManager (see @ref InputManager.h) is responsible for creating the user input instance, with this + * instance using a callback to the Controller to action user input. The user input classes are derived from the + * InputInterface class (see @ref InputInterface.h) to abstract to support various different input types without + * conflicts. + * + * @subsection testing Testing + * + * Where possible, unit and integration tests have been written using PlatformIO and GoogleTest to validate that the + * various methods and classes continue to do what they're supposed to do, and also to prevent causing memory leaks and + * calls to undefined objects and so forth. + * + * The various display and input device libraries typically are not compatible with the native environment the + * PlatformIO tests run in, so there are no tests written for the implemented display and input classes, but these are + * mocked instead. See @ref MockDisplay, @ref MockSPIDisplay, @ref MockInput, and @ref MockButtonInput for examples of + * these. Also note the mention of needing include guards for this as per @ref adding_types. + * + * All tests are run when pushing or merging to both the main and devel branches, and can be run locally with PlatformIO + * also: + * + * @code + * pio test -e native_test + * pio test -e native_test_windows + * @endcode + * + * Note that due to issues with the sanitizers that check from memory leaks and undefined references not being available + * on Windows, you must use the second option for local testing, and there is no way to validate that memory leaks have + * been introduce, or undefined references are being called. It is best to run these tests on Linux, and on Windows this + * can be accomplished by using Windows Subsystem for Linux. + * + * The best approach when adding new functionality is to write the test first, and then ensure the code to support the + * feature makes the test succeed. + * + * @section adding_types Adding Display and Input Types + * As mentioned in the @ref application_architecture, both displays and inputs are derived from the appropriate + * interface class to enable multiple different types to be made available for users to configure and use. + * + * When adding either display or input classes, be sure to add include guards around the derived class to prevent + * causing errors when running PlatformIO tests, as these are typically not compatible with the native environment that + * the tests run in. + * + * @code {.cpp} + * #ifndef NEWDISPLAYTYPE_H + * #define NEWDISPLAYTYPE_H + * + * #ifndef PIO_UNIT_TESTING // Do not compile when testing, likely to cause test failures + * + * class MyNewDisplay : public DisplayInterface { + * ... + * }; + * + * #endif // PIO_UNIT_TESTING + * + * #endif // NEWDISPLAYTYPE_H + * @endcode + * + * Further to this, all display and input classes must define one or more public static methods to call the constructor + * to enable the device creation to be added to either the DisplayManager or InputManager creation methods during + * compilation. If there are overloaded constructors, each must be matched by an equivalent static create method. Refer + * to @ref TFT_eSPIDisplay.h for an example of this. This create method must return a pointer to the new instance to + * support this. + * + * @code {.cpp} + * class MyNewDisplay : public DisplayInterface { + * public: + * MyNewDisplay(uint16_t textColour, uint16_t backgroundColour) {...} + * + * MyNewDisplay(uint16_t textColour, uint16_t backgroundColour, int param) {...} + * + * static MyNewDisplay *create(uint16_t textColour, uint16_t backgroundColour) {...} + * + * static MyNewDisplay *create(uint16_t textColour, uint16_t backgroundColour, int param) {...} + * }; + * @endcode + * + * @subsection adding_displays Adding Displays + * + * The purpose of the display classes is to implement the DisplayInterface using display libraries to work with user + * display devices, with the first of these being TFT_eSPIDisplay to implement using the TFT_eSPI library. + * + * Each additional display class must implement all virtual methods of the DisplayInterface class, and is responsible + * for all visual display of the Screen instance that is to be displayed. + * + * As mentioned in the above section, be sure to add static create methods to allow these to be defined in the user's + * myDevices.h file. + * + * Further to this, any font or colour definitions related to the specific display class or library must be defined in + * that device's header file only, do not add these to Defines.h to ensure there are no possible conflicts with other + * display classes or libraries. + * + * @subsection adding_inputs Adding Inputs + * + * Similar to the display classes, the input classes are also to implement the InputInterface class to work with various + * user input methods. The first of these is the TFT_eSPITouch class to implement touch screen support with the TFT_eSPI + * library. + * + * In this instance, this input method must share the same TFT_eSPI instance as the display, and the InputInterface has + * the _needsDisplay parameter available to allow specifying the display instance to retrieve the TFT_eSPI + * instance from. The method public getTFT_eSPInstance() has been added to allow the TFT_eSPIInput class to retrieve + * this instance within the begin() method. See @ref TFT_eSPITouch to see how this is implemented. + * + * Each input class must implement the virtual methods of the InputInterface class, and the check() method will callback + * to the Controller class to provide the appropriate InputAction type that the user has selected. + */ + +#ifndef PIO_UNIT_TESTING + +#include "Configurator.h" + +/// @brief Create the Configurator instance to configure EX-Display +/// @param console Pointer to the console stream - defaults to Serial, customise in myConfig.h +/// @param commandStation Pointer to the CommandStation connection stream - default depends on platform, customise in +/// myConfig.h +/// @param logLevel Sets the log level for the application - defaults to WARN, customise in myConfig.h +Configurator *configurator = new Configurator(&CONSOLE_STREAM, &COMMANDSTATION_STREAM, LOG_LEVEL); + +/// @brief Main setup method, initialise Configurator here void setup() { - CONSOLE.begin(115200); - CS_LISTEN.begin(115200); // Start Serial1 for listening to messages - - CONSOLE.print(F("EX-Display v")); - CONSOLE.println(VERSION); - - // Tell AtFinder our maximum supported text length, - // and how to call back when found. - AtFinder::setup(100, updateEXDisplayRow); - - // SCREEN::TFT_Startup(); - // tft.invertDisplay(1); - // tft.invertDisplay(0); - - // HARDWARE SETUP TODO..... Create an EXDisplay instance for each screen this ino wants to display. - // The updateEXDisplayRow will ignore messages destined for screens we dont have. - // For testing lets create some - SCREEN_0 -#ifdef SCREEN_1_TYPE - SCREEN_1 -#endif -#ifdef SCREEN_2_TYPE - SCREEN_2 -#endif - // new EXDisplay(0, new MCUFriendScreen(8, 20), 30); - - for (EXDisplay *display = EXDisplay::getFirst(); display; display = display->getNext()) { - display->getEXScreen()->setupScreen(SCREEN_ROTATION, TEXT_COLOUR, BACKGROUND_COLOUR); - CONSOLE.print(F("Display ID|Max Rows|Max Columns: ")); - CONSOLE.print(display->getDisplayNumber()); - CONSOLE.print(F("|")); - CONSOLE.print(display->getScreenMaxRows()); - CONSOLE.print(F("|")); - CONSOLE.println(display->getScreenMaxColumns()); - } - - // Setup the start screen. - // if (MAX_SCREENS > 1) { - // currentScreenID = INITIAL_SCREEN; - // } - // else { - // currentScreenID = 0; - // } - - timestamp = millis(); - CONSOLE.println(F("End of Setup")); + CONSOLE_STREAM.begin(115200); + COMMANDSTATION_STREAM.begin(115200); + configurator->initialise(); + Controller *controller = configurator->getController(); + controller->begin(); } +/// @brief Main loop, simply calls controller->update() to manage all interactions void loop() { - - // Check if we are in the startup phase - // This phase inhibits screen drawing until the startup messages are - // issued by the CS - if (StartupPhase) { - if ((millis() - timestamp) >= STARTUP_TIME) { - StartupPhase = false; - screencount = millis(); - } - } - - // each byte received form serial is passed to the parse - if (CS_LISTEN.available()) { - AtFinder::processInputChar(CS_LISTEN.read()); - } - // you can display all rows by sending <@ 255 0 ""> - // No data incoming so see if we need to display anything - // DISABLE IN STARTUPPHASE - else { - updateScreens(); - /* DISABLE SO IT WILL COMPILE - if (StartupPhase==false){ - // add thie following in once display is working - // for (byte x= 0; x SCROLLTIME) { - - if (currentScreenID >= MAX_SCREENS-1) { - currentScreenID=0; - } - else { - currentScreenID++; - - } - screencount=millis(); - ScreenChanged[currentScreenID] = true; - - } - #else - if (SCREEN::check_touch) { - if (currentScreenID >= MAX_SCREENS-1) { - currentScreenID=0; - } - else { - currentScreenID++; - - } - - ScreenChanged[currentScreenID] = true; - - } - - #endif - } - */ - } + Controller *controller = configurator->getController(); + controller->update(); } + +#endif // PIO_UNIT_TEST diff --git a/EXDisplayClass.cpp b/EXDisplayClass.cpp deleted file mode 100644 index 321bbbb..0000000 --- a/EXDisplayClass.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include "EXDisplayClass.h" - -/// @brief Define the first EXDisplay object as a nullptr -EXDisplay *EXDisplay::_first = nullptr; - -/* - * EXDisplay class implementation - */ -EXDisplay::EXDisplay(uint8_t displayNumber, EXScreen *exScreen, uint8_t maxScreenWidth) - : _displayNumber(displayNumber), _exScreen(exScreen), _maxScreenWidth(maxScreenWidth) { - _firstRow = nullptr; - _next = _first; - _first = this; - _numberOfRows = 0; - _scrollPosition = 0; - _lastScrollTime = 0; -} - -EXDisplay *EXDisplay::getFirst() { return _first; } - -EXDisplay *EXDisplay::getNext() { return _next; } - -EXDisplayRow *EXDisplay::getFirstRow() { return _firstRow; } - -uint8_t EXDisplay::getDisplayNumber() { return _displayNumber; } - -EXDisplayRow *EXDisplay::getRowByNumber(uint8_t rowNumber) { - for (auto *row = _firstRow; row; row = row->getNext()) { - if (row->getRowNumber() == rowNumber) { - return row; - } - } - return nullptr; -} - -void EXDisplay::updateRow(uint8_t rowNumber, char *rowText) { - auto *row = getRowByNumber(rowNumber); - if (!row) { - // create a new row and chain it in - row = new EXDisplayRow(rowNumber); - _numberOfRows++; - - // find the row prior to the one we want to add - EXDisplayRow *previous = nullptr; - for (auto peek = _firstRow; peek; peek = peek->getNext()) { - if (peek->getRowNumber() > rowNumber) - break; - previous = peek; - } - if (previous) { - // chain after previous - row->setNext(previous->getNext()); - previous->setNext(row); - } else { - // chain at start of list - row->setNext(_firstRow); - _firstRow = row; - } - } - row->setRowText(rowText); - row->setDisplayRow(rowNumber, _exScreen->getMaxRows()); -} - -void EXDisplay::scroll() { - uint8_t screenRows = _exScreen->getMaxRows(); - uint8_t newPosition = 0; - if (_numberOfRows <= screenRows) { - _scrollPosition = newPosition; - } else { - newPosition = _scrollPosition++; - if (newPosition >= _numberOfRows) { - newPosition = 0; - } - } - _scrollPosition = newPosition; -} - -void EXDisplay::autoScroll(unsigned long scrollDelay) { - if (millis() - _lastScrollTime > scrollDelay) { - _lastScrollTime = millis(); - CONSOLE.println(F("Time to scroll")); - scroll(); - } -} - -EXScreen *EXDisplay::getEXScreen() { return _exScreen; } - -uint8_t EXDisplay::getScreenMaxRows() { return _exScreen->getMaxRows(); } - -uint8_t EXDisplay::getScreenMaxColumns() { return _exScreen->getMaxColumns(); } - -/*** probably not needed - void EXDisplay::deleteRowNumber(uint8_t rowNumber) { - EXDisplayRow *currentRow = _firstRow; - while (currentRow != nullptr && currentRow->getNext() != nullptr) { - if (currentRow->getNext()->getRowNumber() == rowNumber) { - EXDisplayRow *temp = currentRow->getNext(); - currentRow->setNext(temp->getNext()); - delete temp; // CAUTION DESTRUCTOR MUST DELETE TEXT TOO - return; - } - currentRow = currentRow->getNext(); - } -} -***/ - -bool EXDisplay::displayNumberExists(uint8_t displayNumber) { return getDisplayByNumber(displayNumber) != nullptr; } - -EXDisplay *EXDisplay::getDisplayByNumber(uint8_t displayNumber) { - for (auto *display = _first; display; display = display->_next) { - if (display->_displayNumber == displayNumber) { - return display; - } - } - return nullptr; -} diff --git a/EXDisplayClass.h b/EXDisplayClass.h deleted file mode 100644 index 6dca047..0000000 --- a/EXDisplayClass.h +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef EXDISPLAYCLASS_H -#define EXDISPLAYCLASS_H - -#include - -#include "Defines.h" -#include "EXDisplayRow.h" -#include "EXScreen.h" - -/** - * @brief Class for each display. - * Each display is in a linked list, with associated rows in a linked list as an attribute of the EXDisplay object. - * - */ -class EXDisplay { -public: - EXDisplay(uint8_t displayNumber, EXScreen *exScreen, uint8_t maxScreenWidth); - - /// @brief Get the first EXDisplay object in the linked list - /// @return Pointer to the first EXDisplay object - static EXDisplay *getFirst(); - - /// @brief Get the next EXDisplay object in the linked list - /// @return Pointer to the next EXDisplay object - EXDisplay *getNext(); - - /// @brief Get the first EXDisplayRow object associated with this display - /// @return Pointer to the first EXDisplayRow object for this display - EXDisplayRow *getFirstRow(); - - /// @brief Get the display's number - /// @return 0 - 255 - uint8_t getDisplayNumber(); - - /// @brief Get an EXDisplayRow object for this display by the specified row number - /// @param rowNumber Row number to retrieve, 0 - 255 - /// @return Pointer to an EXDisplayRow object, or nullptr if not exists - EXDisplayRow *getRowByNumber(uint8_t rowNumber); - - /// @brief Update text and ticker for the specified row number, will add if it doesn't exist - /// @param rowNumber Row number to display text on, 0 - 255 - /// @param rowText Char array of text for the row - void updateRow(uint8_t rowNumber, char *rowText); - - /// @brief Scroll one row vertically - void scroll(); - - /// @brief Method to automatically update row positions for automatic vertical scrolling - /// @param scrollDelay Time in milliseconds between vertical scrolling updates - void autoScroll(unsigned long scrollDelay); - - /// @brief Get the EXScreen object associated with this display - /// @return Pointer to the associated EXScreen object - EXScreen *getEXScreen(); - - /// @brief Get the maximum number of rows displayable on the associated screen - /// @return 0 - 255 - uint8_t getScreenMaxRows(); - - /// @brief Get the maximum number of columns displayable on the associated screen - /// @return 0 - 255 - uint8_t getScreenMaxColumns(); - - /// @brief Check if there is already a display created at the specified number - /// @param displayNumber True|False - /// @return - static bool displayNumberExists(uint8_t displayNumber); - - /// @brief Get the display and associated row linked list for the specified display number - /// @param displayNumber Display number to get, 0 - 255 - /// @return EXDisplay object, or nullptr if not exists - static EXDisplay *getDisplayByNumber(uint8_t displayNumber); - -private: - // chaining displays - static EXDisplay *_first; - EXDisplay *_next; - // display data and rows - uint8_t _displayNumber; - EXDisplayRow *_firstRow; - EXScreen *_exScreen; - // Screen management variables added here - uint8_t _maxScreenWidth; // Maximum number of chars that can fit on the physical screen - uint8_t _numberOfRows; // Calculated number of rows for this screen - uint8_t _scrollPosition; // Row number that is top of screen for scrolling support - unsigned long _lastScrollTime; // Last time in milliseconds an auto scroll was done -}; - -#endif diff --git a/EXDisplayRow.cpp b/EXDisplayRow.cpp deleted file mode 100644 index bbf1455..0000000 --- a/EXDisplayRow.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "EXDisplayRow.h" -/* - * EXDisplayRow class implementation - */ -EXDisplayRow::EXDisplayRow(uint8_t rowNumber) { - _rowNumber = rowNumber; - _maxMalloc = 0; - _rowText = nullptr; - _changed = true; - _needsRender = false; -} - -uint8_t EXDisplayRow::getRowNumber() { return _rowNumber; } - -void EXDisplayRow::setRowText(char *rowText) { - // Note size limit is 254 chars but that is beyond - // the capability of the caller anyway. - // Ignore change if text the same... - if (_rowText && strcmp(_rowText, rowText) == 0) - return; - - uint8_t bytesNeeded = strlen(rowText) + 1; - if (bytesNeeded > _maxMalloc || !_rowText) { - // _rowText is not big enough so realloc() it. - // Deliberately dont realloc() if its big enough to - // avolid fragmentation caused by realloc freeing - // the tail which we will probabaly need later anyway. - _rowText = (char *)realloc(_rowText, bytesNeeded); - _maxMalloc = bytesNeeded; - } - strcpy(_rowText, rowText); - _changed = true; -} - -char *EXDisplayRow::getRowText() { - _changed = false; // are you sure... every time? - return _rowText; -} - -bool EXDisplayRow::isChanged() { return _changed; } - -void EXDisplayRow::setDisplayRow(uint8_t displayRow, uint8_t maxScreenRows) { - _displayRow = displayRow; - if (_displayRow >= maxScreenRows) { - _needsRender = false; - } else { - _needsRender = true; - } -} - -uint8_t EXDisplayRow::getDisplayRow() { return _displayRow; } - -bool EXDisplayRow::needsRender() { return _needsRender; } - -EXDisplayRow *EXDisplayRow::getNext() { return _next; } - -void EXDisplayRow::setNext(EXDisplayRow *next) { _next = next; } diff --git a/EXDisplayRow.h b/EXDisplayRow.h deleted file mode 100644 index ee170ac..0000000 --- a/EXDisplayRow.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef EXDisplayRow_H -#define EXDisplayRow_H - -#include - -/** - * @brief Class for each row associated with a display. - * Each row is a member of a linked list which is an attribute of an EXDisplay object. - * - */ -class EXDisplayRow { -public: - /// @brief Constructor for the EXDisplayRow object - /// @param rowNumber Row number on the display, 0 - 255 - EXDisplayRow(uint8_t rowNumber); - - /// @brief Get the row number this should be displayed on - /// @return 0 - 255 - uint8_t getRowNumber(); - - /// @brief Update the text for the specified row - /// @param rowText Char array containing the text - void setRowText(char *rowText); - - /// @brief Get the text associated with this row - /// @return Char array - char *getRowText(); - - /// @brief Check if this row has been changed, set when setRowText called, reset when getRowText called - /// @return True|False - bool isChanged(); - - /// @brief Set the physical screen row this row should be rendered/drawn on and provide the max rows for the screen - /// @param displayRow 0 - 255 - /// @param maxScreenRows Maximum number of rows the screen can display - sets _needsRender - void setDisplayRow(uint8_t displayRow, uint8_t maxScreenRows); - - /// @brief calculated to determine which screen row is used - /// @return 0 - 255 - uint8_t getDisplayRow(); - - /// @brief Check if this row fits on a physical display and needs to be rendered/drawn - /// @return True|False - bool needsRender(); - - /// @brief Set the pointer to the next EXDisplayRow object in the linked list - /// @param next Pointer to the next EXDisplayRow object - void setNext(EXDisplayRow *next); - - /// @brief Get the next row - /// @return Pointer to the next EXDisplayRow object in the linked list - EXDisplayRow *getNext(); - -private: - uint8_t _rowNumber; // This is the row number received from the parser - uint8_t _maxMalloc; // This is the calculated maximum length of the text received from the parser - char *_rowText; // This is the text received from the parser - bool _changed; // Flag set when text received from the parser is different to rowText - uint8_t _displayRow; // This is the calculated physical row on a display that this line belongs on - bool _needsRender; // Flag that is set when row belongs on a physical display, false when off-screen - EXDisplayRow *_next; -}; -#endif \ No newline at end of file diff --git a/EXScreen.h b/EXScreen.h deleted file mode 100644 index 0ee2109..0000000 --- a/EXScreen.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef EXSCREEN_H -#define EXSCREEN_H - -#include -#include - -/// @brief This EXScreen class is designed to be extended by other screen classes that define the methods to perform the -/// requesite activities according to the library in use to drive the physical display. -/// This allows all physical screen methods to be consistent regardless of the library or driver in use, allowing this -/// code to support multiple different display types without needing to have the main functions call different -/// libraries/methods. As this is a virtual only class, every virtual method must be declared and defined in the class -/// that extends this. -class EXScreen { -public: - /// @brief Constructor for a new EXScreen object - EXScreen(uint8_t maxRows, uint8_t maxColumns) : _maxRows(maxRows), _maxColumns(maxColumns) {} - - /// @brief Virtual function to implement to setup the required parameters for the physical screen - virtual void setupScreen(uint8_t rotation, uint16_t textColour, uint16_t backgroundColour); - - /// @brief Virtual function to implement to write a row of text to the physical screen - virtual void writeRow(uint8_t row, uint8_t column, const GFXfont *fontName, uint16_t fontColour, uint8_t textSize, - char *message); - - /// @brief Get the maximum number of rows this screen can physically display - /// @return 0 - 255 - uint8_t getMaxRows() { return _maxRows; } - - /// @brief Get the maximum number of columns this screen can physically display - /// @return 0 - 255 - uint8_t getMaxColumns() { return _maxColumns; } - -private: - uint8_t _maxRows; // Maximum number of rows this screen can display - uint8_t _maxColumns; // Maximum number of columns this screen can display -}; - -#endif diff --git a/FontOptions.h b/FontOptions.h deleted file mode 100644 index cf8f06d..0000000 --- a/FontOptions.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef FONTOPTIONS_H -#define FONTOPTIONS_H - -#include "Adafruit_GFX.h" -#include "Arial9pt7b.h" -#include "FreeSans12pt7b.h" - -/// @brief Macros for available colours -#define BLACK 0x0000 -#define RED 0xF800 -#define GREEN 0x07E0 -#define CYAN 0x07FF -#define MAGENTA 0xF81F -#define YELLOW 0xFFE0 -#define WHITE 0xFFFF - -/// @brief Macros for avaialble fonts -#define ARIAL9PT7B &Arial9pt7b -#define FREESANS12PT7B &FreeSans12pt7b - -#endif diff --git a/FreeSans12pt7b.h b/FreeSans12pt7b.h deleted file mode 100644 index 6e77392..0000000 --- a/FreeSans12pt7b.h +++ /dev/null @@ -1,272 +0,0 @@ -#pragma once -#include - -const uint8_t FreeSans12pt7bBitmaps[] PROGMEM = { - 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xCF, 0x3C, 0xF3, 0x8A, 0x20, 0x06, 0x30, - 0x31, 0x03, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x03, 0x18, 0x18, - 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC0, 0xC6, 0x06, 0x30, - 0x04, 0x03, 0xE1, 0xFF, 0x72, 0x6C, 0x47, 0x88, 0xF1, 0x07, 0x20, 0x7E, - 0x03, 0xF0, 0x17, 0x02, 0x3C, 0x47, 0x88, 0xF1, 0x1B, 0x26, 0x7F, 0xC3, - 0xE0, 0x10, 0x02, 0x00, 0x00, 0x06, 0x03, 0xC0, 0x40, 0x7E, 0x0C, 0x0E, - 0x70, 0x80, 0xC3, 0x18, 0x0C, 0x31, 0x00, 0xE7, 0x30, 0x07, 0xE6, 0x00, - 0x3C, 0x40, 0x00, 0x0C, 0x7C, 0x00, 0x8F, 0xE0, 0x19, 0xC7, 0x01, 0x18, - 0x30, 0x31, 0x83, 0x02, 0x1C, 0x70, 0x40, 0xFE, 0x04, 0x07, 0xC0, 0x0F, - 0x00, 0x7E, 0x03, 0x9C, 0x0C, 0x30, 0x30, 0xC0, 0xE7, 0x01, 0xF8, 0x03, - 0x80, 0x3E, 0x01, 0xCC, 0x6E, 0x19, 0xB0, 0x7C, 0xC0, 0xF3, 0x03, 0xCE, - 0x1F, 0x9F, 0xE6, 0x1E, 0x1C, 0xFF, 0xA0, 0x08, 0x8C, 0x66, 0x31, 0x98, - 0xC6, 0x31, 0x8C, 0x63, 0x08, 0x63, 0x08, 0x61, 0x0C, 0x20, 0x82, 0x18, - 0xC3, 0x18, 0xC3, 0x18, 0xC6, 0x31, 0x8C, 0x62, 0x31, 0x88, 0xC4, 0x62, - 0x00, 0x10, 0x23, 0x5B, 0xE3, 0x8D, 0x91, 0x00, 0x0C, 0x03, 0x00, 0xC0, - 0x30, 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0xF5, 0x60, - 0xFF, 0xF0, 0xF0, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, - 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x00, 0x1F, 0x07, 0xF1, 0xC7, 0x30, - 0x6E, 0x0F, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, - 0x0E, 0xC1, 0x9C, 0x71, 0xFC, 0x1F, 0x00, 0x08, 0xCF, 0xFF, 0x8C, 0x63, - 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0x1F, 0x0F, 0xF9, 0x87, 0x60, 0x7C, - 0x06, 0x00, 0xC0, 0x18, 0x07, 0x01, 0xC0, 0xF0, 0x78, 0x1C, 0x06, 0x00, - 0x80, 0x30, 0x07, 0xFF, 0xFF, 0xE0, 0x3F, 0x0F, 0xF3, 0x87, 0x60, 0x6C, - 0x0C, 0x01, 0x80, 0x70, 0x7C, 0x0F, 0x80, 0x18, 0x01, 0x80, 0x3C, 0x07, - 0x80, 0xD8, 0x73, 0xFC, 0x1F, 0x00, 0x01, 0x80, 0x70, 0x0E, 0x03, 0xC0, - 0xD8, 0x1B, 0x06, 0x61, 0x8C, 0x21, 0x8C, 0x33, 0x06, 0x7F, 0xFF, 0xFE, - 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x3F, 0xCF, 0xF9, 0x80, 0x30, 0x06, - 0x00, 0xDE, 0x1F, 0xE7, 0x0E, 0x00, 0xE0, 0x0C, 0x01, 0x80, 0x30, 0x07, - 0x81, 0xF8, 0x73, 0xFC, 0x1F, 0x00, 0x0F, 0x07, 0xF9, 0xC3, 0x30, 0x74, - 0x01, 0x80, 0x33, 0xC7, 0xFE, 0xF0, 0xDC, 0x1F, 0x01, 0xE0, 0x3C, 0x06, - 0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00, 0xFF, 0xFF, 0xFC, 0x01, 0x00, 0x60, - 0x18, 0x02, 0x00, 0xC0, 0x30, 0x06, 0x01, 0x80, 0x30, 0x04, 0x01, 0x80, - 0x30, 0x06, 0x01, 0x80, 0x30, 0x00, 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x66, - 0x0C, 0xC1, 0x8C, 0x61, 0xFC, 0x3F, 0x8E, 0x3B, 0x01, 0xE0, 0x3C, 0x07, - 0x80, 0xD8, 0x31, 0xFC, 0x1F, 0x00, 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x6C, - 0x07, 0x80, 0xF0, 0x1E, 0x07, 0x61, 0xEF, 0xFC, 0x79, 0x80, 0x30, 0x05, - 0x81, 0x98, 0x73, 0xFC, 0x1E, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0xF0, 0x00, - 0x0F, 0x56, 0x00, 0x00, 0x07, 0x01, 0xE0, 0xF8, 0x3C, 0x0F, 0x00, 0xE0, - 0x07, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x01, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x0E, 0x00, 0x78, 0x01, 0xF0, 0x07, - 0xC0, 0x0F, 0x00, 0x70, 0x1E, 0x0F, 0x03, 0xC0, 0xF0, 0x08, 0x00, 0x1F, - 0x1F, 0xEE, 0x1B, 0x03, 0xC0, 0xC0, 0x30, 0x0C, 0x06, 0x03, 0x81, 0xC0, - 0xE0, 0x30, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0xFE, - 0x00, 0x0F, 0xFE, 0x00, 0xF0, 0x3E, 0x07, 0x00, 0x3C, 0x38, 0x00, 0x30, - 0xC1, 0xE0, 0x66, 0x0F, 0xD9, 0xD8, 0x61, 0xC3, 0xC3, 0x07, 0x0F, 0x1C, - 0x1C, 0x3C, 0x60, 0x60, 0xF1, 0x81, 0x83, 0xC6, 0x06, 0x1B, 0x18, 0x38, - 0xEE, 0x71, 0xE7, 0x18, 0xFD, 0xF8, 0x71, 0xE7, 0xC0, 0xE0, 0x00, 0x01, - 0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x01, 0xFC, 0x00, 0x03, 0xC0, 0x03, 0xC0, - 0x03, 0xC0, 0x07, 0xE0, 0x06, 0x60, 0x06, 0x60, 0x0E, 0x70, 0x0C, 0x30, - 0x0C, 0x30, 0x1C, 0x38, 0x18, 0x18, 0x1F, 0xF8, 0x3F, 0xFC, 0x30, 0x1C, - 0x30, 0x0C, 0x70, 0x0E, 0x60, 0x06, 0x60, 0x06, 0xFF, 0xC7, 0xFF, 0x30, - 0x19, 0x80, 0x6C, 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x0C, 0xFF, 0xC7, 0xFF, - 0x30, 0x0D, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7, - 0xFE, 0x00, 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x66, 0x00, 0x6C, - 0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, - 0x06, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1E, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0, - 0xFF, 0x83, 0xFF, 0x8C, 0x07, 0x30, 0x0E, 0xC0, 0x1B, 0x00, 0x7C, 0x00, - 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x1F, 0x00, - 0x6C, 0x03, 0xB0, 0x1C, 0xFF, 0xE3, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, - 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0, - 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xFF, 0xDF, - 0xFB, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x00, - 0x07, 0xF0, 0x1F, 0xFC, 0x3C, 0x1E, 0x70, 0x06, 0x60, 0x03, 0xE0, 0x00, - 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x7F, 0xC0, 0x7F, 0xC0, 0x03, 0xC0, 0x03, - 0x60, 0x03, 0x60, 0x07, 0x30, 0x0F, 0x3C, 0x1F, 0x1F, 0xFB, 0x07, 0xE1, - 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, - 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, - 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x01, - 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, - 0x3C, 0x1E, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00, 0xC0, 0x3B, 0x01, 0xCC, - 0x0E, 0x30, 0x70, 0xC3, 0x83, 0x1C, 0x0C, 0xE0, 0x33, 0x80, 0xDE, 0x03, - 0xDC, 0x0E, 0x38, 0x30, 0x60, 0xC1, 0xC3, 0x03, 0x8C, 0x06, 0x30, 0x1C, - 0xC0, 0x3B, 0x00, 0x60, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, - 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, - 0xFF, 0xFF, 0xF0, 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xD0, - 0x0F, 0xD8, 0x1B, 0xD8, 0x1B, 0xD8, 0x1B, 0xCC, 0x33, 0xCC, 0x33, 0xCC, - 0x33, 0xC6, 0x63, 0xC6, 0x63, 0xC6, 0x63, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, - 0xC3, 0xC1, 0x83, 0xE0, 0x1F, 0x00, 0xFC, 0x07, 0xE0, 0x3D, 0x81, 0xEE, - 0x0F, 0x30, 0x79, 0xC3, 0xC6, 0x1E, 0x18, 0xF0, 0xE7, 0x83, 0x3C, 0x1D, - 0xE0, 0x6F, 0x01, 0xF8, 0x0F, 0xC0, 0x3E, 0x01, 0xC0, 0x03, 0xE0, 0x0F, - 0xFC, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, - 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, - 0x33, 0x00, 0x18, 0xC0, 0x18, 0x78, 0x3C, 0x1F, 0xFC, 0x03, 0xF8, 0x00, - 0xFF, 0x8F, 0xFE, 0xC0, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x07, - 0xFF, 0xEF, 0xFC, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, - 0xC0, 0x0C, 0x00, 0x03, 0xE0, 0x0F, 0xFC, 0x0F, 0x07, 0x86, 0x00, 0xC6, - 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, - 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x01, 0x98, 0xC0, 0xFC, 0x78, - 0x3C, 0x1F, 0xFF, 0x03, 0xF9, 0x80, 0x00, 0x40, 0xFF, 0xC3, 0xFF, 0xCC, - 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x0C, 0xFF, 0xE3, - 0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06, - 0xC0, 0x1B, 0x00, 0x70, 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x9C, 0x07, 0x60, - 0x0D, 0x80, 0x06, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80, - 0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80, - 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, - 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, - 0x06, 0x00, 0x60, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, - 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, - 0xE0, 0x0F, 0x80, 0xEE, 0x0E, 0x3F, 0xE0, 0x7C, 0x00, 0x60, 0x06, 0xC0, - 0x1D, 0xC0, 0x31, 0x80, 0x63, 0x01, 0xC7, 0x03, 0x06, 0x06, 0x0C, 0x1C, - 0x1C, 0x30, 0x18, 0x60, 0x31, 0xC0, 0x73, 0x00, 0x66, 0x00, 0xDC, 0x01, - 0xF0, 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x00, 0xE0, 0x30, 0x1D, 0x80, 0xE0, - 0x76, 0x07, 0x81, 0xD8, 0x1E, 0x06, 0x70, 0x7C, 0x18, 0xC1, 0xB0, 0xE3, - 0x0C, 0xC3, 0x8C, 0x33, 0x0C, 0x38, 0xC6, 0x30, 0x67, 0x18, 0xC1, 0x98, - 0x67, 0x06, 0x61, 0xD8, 0x1D, 0x83, 0x60, 0x3C, 0x0D, 0x80, 0xF0, 0x3E, - 0x03, 0xC0, 0x70, 0x0F, 0x01, 0xC0, 0x18, 0x07, 0x00, 0x70, 0x0E, 0x60, - 0x38, 0xE0, 0x60, 0xE1, 0xC0, 0xC3, 0x01, 0xCC, 0x01, 0xF8, 0x01, 0xE0, - 0x03, 0x80, 0x07, 0x80, 0x1F, 0x00, 0x33, 0x00, 0xE7, 0x03, 0x86, 0x06, - 0x0E, 0x1C, 0x0E, 0x70, 0x0C, 0xC0, 0x1C, 0x60, 0x06, 0x70, 0x0E, 0x30, - 0x1C, 0x38, 0x18, 0x1C, 0x38, 0x0C, 0x30, 0x0E, 0x70, 0x06, 0x60, 0x03, - 0xC0, 0x03, 0xC0, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, - 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0xFF, 0xFF, 0xFF, 0xC0, 0x0E, - 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03, - 0x80, 0x38, 0x03, 0x80, 0x18, 0x01, 0xC0, 0x1C, 0x00, 0xFF, 0xFF, 0xFF, - 0xC0, 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, - 0xF0, 0x81, 0x81, 0x02, 0x06, 0x04, 0x08, 0x18, 0x10, 0x20, 0x60, 0x40, - 0x81, 0x81, 0x02, 0x06, 0x04, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, - 0x33, 0x33, 0x33, 0x3F, 0xF0, 0x0C, 0x0E, 0x05, 0x86, 0xC3, 0x21, 0x19, - 0x8C, 0x83, 0xC1, 0x80, 0xFF, 0xFE, 0xE3, 0x8C, 0x30, 0x3F, 0x07, 0xF8, - 0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C, - 0xE3, 0xC7, 0xEF, 0x3C, 0x70, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, - 0x0C, 0xF8, 0xDF, 0xCF, 0x0E, 0xE0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, - 0x3C, 0x03, 0xE0, 0x6F, 0x0E, 0xDF, 0xCC, 0xF8, 0x1F, 0x0F, 0xE7, 0x1B, - 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F, - 0x00, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x3C, 0xCF, 0xFB, 0x8F, - 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8F, 0x3F, - 0x63, 0xCC, 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x3C, 0x07, 0xFF, 0xFF, 0xFE, - 0x00, 0xC0, 0x1C, 0x0D, 0xC3, 0x1F, 0xE1, 0xF0, 0x3B, 0xD8, 0xC6, 0x7F, - 0xEC, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x00, 0x1E, 0x67, 0xFD, 0xC7, - 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F, - 0xB1, 0xE6, 0x00, 0xC0, 0x3E, 0x0E, 0x7F, 0xC7, 0xE0, 0xC0, 0x30, 0x0C, - 0x03, 0x00, 0xC0, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03, - 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30, 0xF0, 0x3F, 0xFF, 0xFF, - 0xF0, 0x33, 0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, - 0xE0, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x83, 0x30, 0xC6, 0x30, - 0xCC, 0x1B, 0x83, 0xF0, 0x77, 0x0C, 0x61, 0x8E, 0x30, 0xE6, 0x0C, 0xC1, - 0xD8, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xCF, 0x1F, 0x6F, 0xDF, 0xFC, - 0x78, 0xFC, 0x18, 0x3C, 0x0C, 0x1E, 0x06, 0x0F, 0x03, 0x07, 0x81, 0x83, - 0xC0, 0xC1, 0xE0, 0x60, 0xF0, 0x30, 0x78, 0x18, 0x3C, 0x0C, 0x18, 0xCF, - 0x37, 0xEF, 0x1F, 0x83, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, - 0x0F, 0x03, 0xC0, 0xC0, 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80, - 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x1F, 0xC1, 0xF0, 0xCF, 0x8D, - 0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, - 0x07, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00, - 0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, - 0x7C, 0x1D, 0xC7, 0x9F, 0xF1, 0xE6, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, - 0xCF, 0x7F, 0x38, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0, 0x3E, 0x1F, - 0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3E, 0x01, 0xF0, 0x3E, 0x1D, - 0xFE, 0x3E, 0x00, 0x63, 0x19, 0xFF, 0xB1, 0x8C, 0x63, 0x18, 0xC6, 0x31, - 0xE7, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, - 0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0, 0xE0, 0x66, 0x06, 0x60, 0x67, 0x0C, - 0x30, 0xC3, 0x0C, 0x39, 0x81, 0x98, 0x19, 0x81, 0xF0, 0x0F, 0x00, 0xE0, - 0x0E, 0x00, 0xC1, 0xC1, 0xB0, 0xE1, 0xD8, 0x70, 0xCC, 0x2C, 0x66, 0x36, - 0x31, 0x9B, 0x18, 0xCD, 0x98, 0x64, 0x6C, 0x16, 0x36, 0x0F, 0x1A, 0x07, - 0x8F, 0x03, 0x83, 0x80, 0xC1, 0xC0, 0x60, 0xEE, 0x18, 0xC6, 0x0C, 0xC1, - 0xF0, 0x1C, 0x01, 0x80, 0x78, 0x1B, 0x03, 0x30, 0xC7, 0x30, 0x66, 0x06, - 0xE0, 0x6C, 0x0D, 0x83, 0x38, 0x63, 0x0C, 0x63, 0x0E, 0x60, 0xCC, 0x1B, - 0x03, 0x60, 0x3C, 0x07, 0x00, 0xE0, 0x18, 0x03, 0x00, 0xE0, 0x78, 0x0E, - 0x00, 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0x03, 0x81, 0xC0, 0x60, 0x30, - 0x18, 0x0E, 0x03, 0xFF, 0xFF, 0xC0, 0x19, 0xCC, 0x63, 0x18, 0xC6, 0x31, - 0x99, 0x86, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x1C, 0x60, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0xC7, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x0C, 0x33, 0x31, - 0x8C, 0x63, 0x18, 0xC6, 0x73, 0x00, 0x70, 0x3E, 0x09, 0xE4, 0x1F, 0x03, - 0x80}; - -const GFXglyph FreeSans12pt7bGlyphs[] PROGMEM = { - {0, 0, 0, 6, 0, 1}, // 0x20 ' ' - {0, 2, 18, 8, 3, -17}, // 0x21 '!' - {5, 6, 6, 8, 1, -16}, // 0x22 '"' - {10, 13, 16, 13, 0, -15}, // 0x23 '#' - {36, 11, 20, 13, 1, -17}, // 0x24 '$' - {64, 20, 17, 21, 1, -16}, // 0x25 '%' - {107, 14, 17, 16, 1, -16}, // 0x26 '&' - {137, 2, 6, 5, 1, -16}, // 0x27 ''' - {139, 5, 23, 8, 2, -17}, // 0x28 '(' - {154, 5, 23, 8, 1, -17}, // 0x29 ')' - {169, 7, 7, 9, 1, -17}, // 0x2A '*' - {176, 10, 11, 14, 2, -10}, // 0x2B '+' - {190, 2, 6, 7, 2, -1}, // 0x2C ',' - {192, 6, 2, 8, 1, -7}, // 0x2D '-' - {194, 2, 2, 6, 2, -1}, // 0x2E '.' - {195, 7, 18, 7, 0, -17}, // 0x2F '/' - {211, 11, 17, 13, 1, -16}, // 0x30 '0' - {235, 5, 17, 13, 3, -16}, // 0x31 '1' - {246, 11, 17, 13, 1, -16}, // 0x32 '2' - {270, 11, 17, 13, 1, -16}, // 0x33 '3' - {294, 11, 17, 13, 1, -16}, // 0x34 '4' - {318, 11, 17, 13, 1, -16}, // 0x35 '5' - {342, 11, 17, 13, 1, -16}, // 0x36 '6' - {366, 11, 17, 13, 1, -16}, // 0x37 '7' - {390, 11, 17, 13, 1, -16}, // 0x38 '8' - {414, 11, 17, 13, 1, -16}, // 0x39 '9' - {438, 2, 13, 6, 2, -12}, // 0x3A ':' - {442, 2, 16, 6, 2, -11}, // 0x3B ';' - {446, 12, 12, 14, 1, -11}, // 0x3C '<' - {464, 12, 6, 14, 1, -8}, // 0x3D '=' - {473, 12, 12, 14, 1, -11}, // 0x3E '>' - {491, 10, 18, 13, 2, -17}, // 0x3F '?' - {514, 22, 21, 24, 1, -17}, // 0x40 '@' - {572, 16, 18, 16, 0, -17}, // 0x41 'A' - {608, 13, 18, 16, 2, -17}, // 0x42 'B' - {638, 15, 18, 17, 1, -17}, // 0x43 'C' - {672, 14, 18, 17, 2, -17}, // 0x44 'D' - {704, 12, 18, 15, 2, -17}, // 0x45 'E' - {731, 11, 18, 14, 2, -17}, // 0x46 'F' - {756, 16, 18, 18, 1, -17}, // 0x47 'G' - {792, 13, 18, 17, 2, -17}, // 0x48 'H' - {822, 2, 18, 7, 2, -17}, // 0x49 'I' - {827, 9, 18, 13, 1, -17}, // 0x4A 'J' - {848, 14, 18, 16, 2, -17}, // 0x4B 'K' - {880, 10, 18, 14, 2, -17}, // 0x4C 'L' - {903, 16, 18, 20, 2, -17}, // 0x4D 'M' - {939, 13, 18, 18, 2, -17}, // 0x4E 'N' - {969, 17, 18, 19, 1, -17}, // 0x4F 'O' - {1008, 12, 18, 16, 2, -17}, // 0x50 'P' - {1035, 17, 19, 19, 1, -17}, // 0x51 'Q' - {1076, 14, 18, 17, 2, -17}, // 0x52 'R' - {1108, 14, 18, 16, 1, -17}, // 0x53 'S' - {1140, 12, 18, 15, 1, -17}, // 0x54 'T' - {1167, 13, 18, 17, 2, -17}, // 0x55 'U' - {1197, 15, 18, 15, 0, -17}, // 0x56 'V' - {1231, 22, 18, 22, 0, -17}, // 0x57 'W' - {1281, 15, 18, 16, 0, -17}, // 0x58 'X' - {1315, 16, 18, 16, 0, -17}, // 0x59 'Y' - {1351, 13, 18, 15, 1, -17}, // 0x5A 'Z' - {1381, 4, 23, 7, 2, -17}, // 0x5B '[' - {1393, 7, 18, 7, 0, -17}, // 0x5C '\' - {1409, 4, 23, 7, 1, -17}, // 0x5D ']' - {1421, 9, 9, 11, 1, -16}, // 0x5E '^' - {1432, 15, 1, 13, -1, 4}, // 0x5F '_' - {1434, 5, 4, 6, 1, -17}, // 0x60 '`' - {1437, 12, 13, 13, 1, -12}, // 0x61 'a' - {1457, 12, 18, 13, 1, -17}, // 0x62 'b' - {1484, 10, 13, 12, 1, -12}, // 0x63 'c' - {1501, 11, 18, 13, 1, -17}, // 0x64 'd' - {1526, 11, 13, 13, 1, -12}, // 0x65 'e' - {1544, 5, 18, 7, 1, -17}, // 0x66 'f' - {1556, 11, 18, 13, 1, -12}, // 0x67 'g' - {1581, 10, 18, 13, 1, -17}, // 0x68 'h' - {1604, 2, 18, 5, 2, -17}, // 0x69 'i' - {1609, 4, 23, 6, 0, -17}, // 0x6A 'j' - {1621, 11, 18, 12, 1, -17}, // 0x6B 'k' - {1646, 2, 18, 5, 1, -17}, // 0x6C 'l' - {1651, 17, 13, 19, 1, -12}, // 0x6D 'm' - {1679, 10, 13, 13, 1, -12}, // 0x6E 'n' - {1696, 11, 13, 13, 1, -12}, // 0x6F 'o' - {1714, 12, 17, 13, 1, -12}, // 0x70 'p' - {1740, 11, 17, 13, 1, -12}, // 0x71 'q' - {1764, 6, 13, 8, 1, -12}, // 0x72 'r' - {1774, 10, 13, 12, 1, -12}, // 0x73 's' - {1791, 5, 16, 7, 1, -15}, // 0x74 't' - {1801, 10, 13, 13, 1, -12}, // 0x75 'u' - {1818, 12, 13, 12, 0, -12}, // 0x76 'v' - {1838, 17, 13, 17, 0, -12}, // 0x77 'w' - {1866, 11, 13, 11, 0, -12}, // 0x78 'x' - {1884, 11, 18, 11, 0, -12}, // 0x79 'y' - {1909, 10, 13, 12, 1, -12}, // 0x7A 'z' - {1926, 5, 23, 8, 1, -17}, // 0x7B '{' - {1941, 2, 23, 6, 2, -17}, // 0x7C '|' - {1947, 5, 23, 8, 2, -17}, // 0x7D '}' - {1962, 10, 5, 12, 1, -10}}; // 0x7E '~' - -const GFXfont FreeSans12pt7b PROGMEM = {(uint8_t *)FreeSans12pt7bBitmaps, - (GFXglyph *)FreeSans12pt7bGlyphs, 0x20, - 0x7E, 29}; - -// Approx. 2641 bytes diff --git a/InputActions.h b/InputActions.h new file mode 100644 index 0000000..06d54d5 --- /dev/null +++ b/InputActions.h @@ -0,0 +1,36 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef INPUTACTION_H +#define INPUTACTION_H + +/// @brief Input action to be returned from the user interface to control screens and displays +enum InputAction { + PRESS_NONE, + PRESS_UP, + PRESS_DOWN, + PRESS_LEFT, + PRESS_RIGHT, + PRESS_CENTRE, + HOLD_UP, + HOLD_DOWN, + HOLD_LEFT, + HOLD_RIGHT, + HOLD_CENTRE +}; + +#endif // INPUTACTION_H diff --git a/InputInterface.cpp b/InputInterface.cpp new file mode 100644 index 0000000..d955e3c --- /dev/null +++ b/InputInterface.cpp @@ -0,0 +1,112 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#include "InputInterface.h" + +void InputInterface::setCallback(CallbackInterface *callback) { _callback = callback; } + +void InputInterface::setLogger(Logger *logger) { _logger = logger; } + +bool InputInterface::isCalibrating() { return _isCalibrating; } + +int InputInterface::needsDisplay() { return _needsDisplay; } + +void InputInterface::setDisplay(DisplayInterface *display) { + if (display == nullptr) { + return; + } + _display = display; + LOG(LogLevel::LOG_DEBUG, "DisplayInterface::setDisplay() - display ID %d", _display->getId()); +} + +void InputInterface::setDebounceDelay(unsigned long delay) { _debounceDelay = delay; } + +void InputInterface::setHoldThreshold(unsigned long threshold) { _holdThreshold = threshold; } + +void InputInterface::forceCalibration() { _forceCalibration = true; } + +InputAction InputInterface::_debounceOrHeld(InputAction currentAction) { + // Record the current time for comparisons + unsigned long currentTime = millis(); + InputAction returnAction = InputAction::PRESS_NONE; + // If current and last actions differ, we're not pressing or holding, reset accordingly + if (currentAction != _lastAction) { + // If we're going from some action to none and not holding, then it must've been a press + if ((currentTime - _lastDebounceTime) > _debounceDelay && currentAction == InputAction::PRESS_NONE && !_isHolding) { + returnAction = _lastAction; + LOG(LogLevel::LOG_DEBUG, "InputInterface::_debounceOrHeld() - press detected: %d", returnAction); + } + _lastDebounceTime = currentTime; + _lastAction = currentAction; + _isHolding = false; + LOG(LogLevel::LOG_DEBUG, "InputInterface::_debounceOrHeld() - action changed, resetting"); + return returnAction; + } + // Check if the debounce time has been exceeded + if (currentAction != InputAction::PRESS_NONE && (currentTime - _lastDebounceTime) > _debounceDelay) { + // Check if the hold time has been exceeded + if ((currentTime - _lastDebounceTime) > _holdThreshold) { + if (!_isHolding) { + // Flag that we're holding, and change from PRESS to HOLD + _isHolding = true; + LOG(LogLevel::LOG_DEBUG, "InputInterface::_debounceOrHeld() - hold detected: %d", currentAction); + switch (currentAction) { + case InputAction::PRESS_UP: + returnAction = InputAction::HOLD_UP; + break; + case InputAction::PRESS_DOWN: + returnAction = InputAction::HOLD_DOWN; + break; + case InputAction::PRESS_LEFT: + returnAction = InputAction::HOLD_LEFT; + break; + case InputAction::PRESS_RIGHT: + returnAction = InputAction::HOLD_RIGHT; + break; + case InputAction::PRESS_CENTRE: + returnAction = InputAction::HOLD_CENTRE; + break; + default: + returnAction = InputAction::PRESS_NONE; + break; + } + } + } + } + return returnAction; +} + +InputAction InputInterface::_calculateInputAction(int touchX, int touchY, int displayWidth, int displayHeight) { + LOG(LogLevel::LOG_DEBUG, "InputInterface::_calculateInputAction(%d, %d, %d, %d)", touchX, touchY, displayWidth, + displayHeight); + InputAction action = InputAction::PRESS_NONE; + int thirdWidth = displayWidth / 3; + int thirdHeight = displayHeight / 3; + + if (touchX <= thirdWidth) { + action = InputAction::PRESS_LEFT; + } else if (touchX >= displayWidth - thirdWidth) { + action = InputAction::PRESS_RIGHT; + } else if (touchY <= thirdHeight) { + action = InputAction::PRESS_UP; + } else if (touchY >= displayHeight - thirdHeight) { + action = InputAction::PRESS_DOWN; + } else { + action = InputAction::PRESS_CENTRE; + } + return action; +} diff --git a/InputInterface.h b/InputInterface.h new file mode 100644 index 0000000..45e6772 --- /dev/null +++ b/InputInterface.h @@ -0,0 +1,119 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef INPUTINTERFACE_H +#define INPUTINTERFACE_H + +#include "CallbackInterface.h" +#include "DisplayInterface.h" +#include "InputActions.h" +#include "Logger.h" + +/// @brief Class to abstract away all physical input implementatio to enable multiple input types +/// Default return should be PRESS_NONE +class InputInterface { +public: + /// @brief Perform any initial once off setup or configuration here and call only once + virtual void begin() = 0; + + /// @brief Call this method at least once per main loop to monitor for input actions + /// Any actions should call the callback set in the _callback attribute + virtual void check() = 0; + + /// @brief Set the instance for callbacks when users trigger an input action + /// @param callback Class instance derived from the CallbackInterface class + /// Must call the method onInputAction(InputAction); + void setCallback(CallbackInterface *callback); + + /// @brief Set the logger instance to use for diagnostic logging + /// @param logger Pointer to the Logger instance to use + void setLogger(Logger *logger); + + /// @brief Test if this InputInterface is undergoing calibration as required by touch screens + /// @return true|false + bool isCalibrating(); + + /// @brief Test if this InputInterface requires a display instance - needed for TFT_eSPI as it shares the instance + /// @return Display ID of the required display - -1 if not required + int needsDisplay(); + + /// @brief Set the DisplayInterface for this input if required + /// @param display Pointer to the DisplayInterface + void setDisplay(DisplayInterface *display); + + /// @brief Set the debounce delay + /// @param delay Debounce delay in milliseconds (default 50ms) + void setDebounceDelay(unsigned long delay); + + /// @brief Set the threshold for detecting an input is held + /// @param threshold Threshold in milliseconds (default 500ms) + void setHoldThreshold(unsigned long threshold); + + /// @brief Force a touch screen into calibration mode, even if existing calibration is valid + void forceCalibration(); + + /// @brief Destructor for an InputInterface + virtual ~InputInterface() = default; + +protected: + /// @brief Pointer to the instance for callbacks + /// Must implement updateScreen() and onInputAction() methods + CallbackInterface *_callback = nullptr; + /// @brief Pointer to the Logger instance for derived classes to use + Logger *_logger = nullptr; + /// @brief Flag if the input interface is undergoing calibration - needed for touch screens + bool _isCalibrating = false; + /// @brief Display ID if this input interface requires a display instance - needed for TFT_eSPI as it shares the + /// instance + int _needsDisplay = -1; + /// @brief Pointer to the DisplayInterface if this input requires it + DisplayInterface *_display = nullptr; + /// @brief Time of the last debounce + unsigned long _lastDebounceTime = 0; + /// @brief Inputs must remain constant for this amount of time to be valid + unsigned long _debounceDelay = 50; + /// @brief Inputs constant for longer than this threshold change from PRESS to HOLD + unsigned long _holdThreshold = 500; + /// @brief Set initial InputAction for comparisons in determining debounce or hold + InputAction _lastAction = InputAction::PRESS_NONE; + /// @brief Flag to help determining if input is held + bool _isHolding = false; + /// @brief Flag to force calibration for touch screens if it's required + bool _forceCalibration = false; + + /// @brief Call this from the derived class' check() method to debounce and detect if the input is a hold vs. press + /// @param currentAction The InputAction needing to be interpreted + /// @return Determined InputAction - either debounced press, held, or none + InputAction _debounceOrHeld(InputAction currentAction); + + /// @brief This method is designed to take an input from a touch screen display and determine which "button" has been + /// touched according to the coordinates of the touch. This assumes the touch screen has been calibrated correctly. + /// @details The display is divided as such: + /// - The entire left third of the display is the left button + /// - The entire right third of the display is the right button + /// - The top third of the resulting centre of the display is the up button + /// - The bottom third of the resulting centre of the display is the down button + /// - The centre third of both width and height is the centre button + /// @param touchX The X coordinate of the touch + /// @param touchY The Y coordinate of the touch + /// @param displayWidth Display width in pixels + /// @param displayHeight Display height in pixels + /// @return A valid InputAction PRESS type + InputAction _calculateInputAction(int touchX, int touchY, int displayWidth, int displayHeight); +}; + +#endif // INPUTINTERFACE_H diff --git a/InputManager.cpp b/InputManager.cpp new file mode 100644 index 0000000..dc289c7 --- /dev/null +++ b/InputManager.cpp @@ -0,0 +1,79 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#include "InputManager.h" + +// Do not load when testing, TFT_eSPI library is incompatible and will cause failures. +#ifndef PIO_UNIT_TESTING +#include "TFT_eSPITouch.h" +#endif // PIO_UNIT_TESTING + +InputManager::InputManager() : _display(nullptr), _input(nullptr), _logger(nullptr), _callback(nullptr) {} + +void InputManager::addInput(InputInterface *input) { + LOG(LogLevel::LOG_DEBUG, "InputManager::addInput()"); + if (input == nullptr) { + LOG(LogLevel::LOG_ERROR, "InputInterface doesn't exist, user input will not be available"); + return; + } + _input = input; + if (_logger) { + _input->setLogger(_logger); + } + if (_callback == nullptr) { + LOG(LogLevel::LOG_ERROR, "InputInterface callback not set, user input will not be available"); + return; + } + _input->setCallback(_callback); +#ifdef DEBOUNCE_DELAY + _input->setDebounceDelay(DEBOUNCE_DELAY); +#endif // DEBOUNCE DELAY +#ifdef HOLD_THRESHOLD + _input->setHoldThreshold(HOLD_THRESHOLD); +#endif // HOLD_THRESHOLD +#ifdef FORCE_CALIBRATION + _input->forceCalibration(); +#endif // FORCE_CALIBRATION +} + +InputInterface *InputManager::getInput() { return _input; } + +void InputManager::setDisplay(DisplayInterface *display) { + if (display == nullptr || _input == nullptr) { + return; + } + LOG(LogLevel::LOG_DEBUG, "InputManager::setDisplay() - display ID %d", display->getId()); + _input->setDisplay(display); +} + +void InputManager::startInput() { + LOG(LogLevel::LOG_DEBUG, "InputManager::startInput()"); + if (_input != nullptr) { + _input->begin(); + } +} + +void InputManager::setLogger(Logger *logger) { _logger = logger; } + +void InputManager::setCallback(CallbackInterface *callback) { _callback = callback; } + +InputManager::~InputManager() { + if (_input != nullptr) { + delete _input; + _input = nullptr; + } +} diff --git a/InputManager.h b/InputManager.h new file mode 100644 index 0000000..44ef25f --- /dev/null +++ b/InputManager.h @@ -0,0 +1,69 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef INPUTMANAGER_H +#define INPUTMANAGER_H + +#include "CallbackInterface.h" +#include "Defines.h" +#include "DisplayInterface.h" +#include "InputInterface.h" +#include "Logger.h" + +class InputManager { +public: + /// @brief Constructor for the InputManager + InputManager(); + + /// @brief Call once to create the InputInterface derived input + void createInput(); + + /// @brief Set the InputInterface + /// @param input Pointer to the InputInterface + void addInput(InputInterface *input); + + /// @brief Get the configured InputInterface + /// @return Pointer to the input + InputInterface *getInput(); + + /// @brief Set the display interface to retrieve required attributes - note this should only be required for TFT_eSPI + /// touch screens that share the same instance between display and touch + /// @param display Pointer to the DisplayInterface + void setDisplay(DisplayInterface *display); + + /// @brief Start the configured input - calls the InputInterface::begin() method + void startInput(); + + /// @brief Set the Logger instance + /// @param logger Pointer to the Logger + void setLogger(Logger *logger); + + /// @brief Set the CallbackInterface derived instance for user input to use for callbacks + /// @param callback Pointer to the instance + void setCallback(CallbackInterface *callback); + + /// @brief Destructor for the InputManager + ~InputManager(); + +private: + DisplayInterface *_display; + InputInterface *_input; + Logger *_logger; + CallbackInterface *_callback; +}; + +#endif // INPUTMANAGER_H diff --git a/Logger.cpp b/Logger.cpp new file mode 100644 index 0000000..77c4110 --- /dev/null +++ b/Logger.cpp @@ -0,0 +1,84 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#include "Logger.h" + +Logger::Logger(Stream *outputStream) : _outputStream(outputStream), _currentLevel(LogLevel::LOG_WARN) {} + +void Logger::setLogLevel(LogLevel logLevel) { _currentLevel = logLevel; } + +LogLevel Logger::getLogLevel() { return _currentLevel; } + +void Logger::log(LogLevel logLevel, const char *format, ...) { + if (logLevel <= _currentLevel) { + // Setup the prefix + const char *prefix; + switch (logLevel) { + case LogLevel::LOG_MESSAGE: + prefix = "[MESSAGE] "; + break; + case LogLevel::LOG_ERROR: + prefix = "[ERROR] "; + break; + case LogLevel::LOG_WARN: + prefix = "[WARN] "; + break; + case LogLevel::LOG_INFO: + prefix = "[INFO] "; + break; + case LogLevel::LOG_DEBUG: + prefix = "[DEBUG] "; + break; + default: + prefix = ""; + break; + } + + // Calculate buffer size from args + prefix + va_list args; + va_start(args, format); + // Get size of string + null terminator + int size = vsnprintf(nullptr, 0, format, args) + 1; + // End args processing + va_end(args); + + // Allocate buffer including prefix size + size_t totalSize = size + strlen(prefix); + char *buffer = new char[totalSize]; + + if (buffer == nullptr) { + _outputStream->println("[ERROR] Logger::log memory allocation failed"); + return; + } + + // Copy prefix to buffer + strcpy(buffer, prefix); + + // Start args processing + va_start(args, format); + // Get the string into the buffer after the prefix + vsnprintf(buffer + strlen(prefix), size, format, args); + // End args processing + va_end(args); + + // Output formatted message + _outputStream->println(buffer); + + // Clean up + delete[] buffer; + } +} diff --git a/Logger.h b/Logger.h new file mode 100644 index 0000000..11cba03 --- /dev/null +++ b/Logger.h @@ -0,0 +1,59 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef LOGGER_H +#define LOGGER_H + +#include + +/// @brief Macro for shorter logging calls, assumes a pointer to a Logger instance called _logger +/// LOG(LogLevel, "Message"); +#define LOG(level, ...) \ + if (_logger) \ + _logger->log(level, __VA_ARGS__) + +/// @brief Define valid log levels in ascending order +enum LogLevel { LOG_MESSAGE, LOG_NONE, LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG }; + +/// @brief Class to enable simple logging to a Stream object with different log levels +/// This enables embedding permanent error, warn, info, and debug messages in the software, with the user defining the +/// log level at compile time if more diagnostics are required +class Logger { +public: + /// @brief Constructor for the logger + /// @param outputStream Pointer to a Stream instance to output log messages to (eg. Logger(&Serial);) + Logger(Stream *outputStream); + + /// @brief Set the log level + /// @param logLevel Valid LogLevel + void setLogLevel(LogLevel logLevel); + + /// @brief Get the current log level + /// @return LogLevel + LogLevel getLogLevel(); + + /// @brief Log a message for the specified log level + /// @param logLevel Valid LogLevel + /// @param format Format to apply to the message + void log(LogLevel logLevel, const char *format, ...); + +private: + Stream *_outputStream; + LogLevel _currentLevel; +}; + +#endif // LOGGER_H diff --git a/MCUFriendScreen.cpp b/MCUFriendScreen.cpp deleted file mode 100644 index 428c678..0000000 --- a/MCUFriendScreen.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "Defines.h" -#include "MCUFriendScreen.h" - -MCUFriendScreen::MCUFriendScreen(MCUFRIEND_kbv &tft, uint8_t maxRows, uint8_t maxColumns) - : EXScreen(maxRows, maxColumns), _tft(tft) {} - -void MCUFriendScreen::setupScreen(uint8_t rotation, uint16_t textColour, uint16_t backgroundColour) { - uint16_t screenId = _tft.readID(); - CONSOLE.print("TFT ID: 0x"); - CONSOLE.println(screenId, HEX); - if (screenId == 0xD3D3) { - screenId = 0x9486; - } - _tft.begin(screenId); - _tft.setRotation(rotation); - _tft.setTextColor(textColour); - _tft.fillScreen(backgroundColour); -} - -void MCUFriendScreen::writeRow(uint8_t row, uint8_t column, const GFXfont *fontName, uint16_t fontColour, - uint8_t textSize, char *message) { - CONSOLE.print(F("Write to screen DisplayRow|Column|Message: ")); - CONSOLE.print(row); - CONSOLE.print(F("|")); - CONSOLE.print(column); - CONSOLE.print(F("|")); - CONSOLE.println(message); - _tft.setFont(fontName); - _tft.setTextColor(fontColour); - _tft.setCursor(column, row); - _tft.setTextSize(textSize); - _tft.print(message); -} - -/* -void EXScreen::newPage(uint8_t screenId) { - // Method here to write new page to the display - CONSOLE.println("New Page"); - tft.fillScreen(BLACK); - char header[25] = {""}; - sprintf(header, "DCC-EX SCREEN %d\n", screenId); - tft.setTextColor(YELLOW); - showmsgXY(1, 20, 1, header); - tft.drawFastHLine(0, 25, tft.width(), WHITE); - tft.setTextColor(WHITE); // set this for all screen lines -*/ diff --git a/MCUFriendScreen.h b/MCUFriendScreen.h deleted file mode 100644 index 5feb9ad..0000000 --- a/MCUFriendScreen.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef MCUFRIENDSCREEN_H -#define MCUFRIENDSCREEN_H - -#include "EXScreen.h" -#include "MCUFRIEND_kbv.h" -#include - -class MCUFriendScreen : public EXScreen { -public: - MCUFriendScreen(MCUFRIEND_kbv &tft, uint8_t maxRows, uint8_t maxColumns); - - virtual void setupScreen(uint8_t rotation, uint16_t textColour, uint16_t backgroundColour) override; - - virtual void writeRow(uint8_t row, uint8_t column, const GFXfont *fontName, uint16_t fontColour, uint8_t textSize, - char *message) override; - -private: - MCUFRIEND_kbv _tft; -}; - -#endif diff --git a/OLDEXScreen.cpp b/OLDEXScreen.cpp deleted file mode 100644 index d569f36..0000000 --- a/OLDEXScreen.cpp +++ /dev/null @@ -1,180 +0,0 @@ -/* - * © 2024, Matt Bidwell, Paul Antoine, Colin Murdoch, Chris Harlow - * All rights reserved. - * - * - * This is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * It is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with CommandStation. If not, see . - */ - -#include "Defines.h" -#include "DisplayFunctions.h" -#include "EXDisplayClass.h" -#include "EXDisplayRow.h" -#include "OLDEXScreen.h" -#include "config.h" -#include -#include -#include - - -// MCUFRIEND_kbv tft; - -#include "Arial9pt7b.h" -//#include "FreeSans12pt7b.h" - -// int ScreenLines; - -/* DISABLE -void TFT_Startup() -{ - - uint16_t ID = tft.readID(); - CONSOLE.print("TFT ID = 0x"); - CONSOLE.println(ID, HEX); - // #ifdef DEBUG - // CONSOLE.println("Calibrate for your Touch Panel"); - // #endif - //if (ID == 0xD3D3) ID = 0x9486; // write-only shield - - tft.begin(ID); - - tft.setRotation(1); - tft.setTextColor(0xFFFF); - tft.fillScreen(BLACK); - - // create a string of blanks for the display. - // This does not seem necessary as we draw the whole screen. - // for (byte x=0; x<=MAX_LINE_LENGTH; x++){ - // blankmsg[x]=' '; - // } - // blankmsg[MAX_LINE_LENGTH+1]='\0'; - - SCREEN::StartScreenPrint(); - -} -*/ - -/* DISABLE -void showmsgXY(int x, int y, int sz, const char *msg) -{ - tft.setFont(); - tft.setFont(&Arial9pt7b); - tft.setCursor(x, y); - tft.setTextSize(sz); - tft.print(msg); - -} -*/ - -/* DISABLE -void setScreenRows(uint8_t rowId) { - -} -*/ - -/* DISABLE -void CheckScreens(){ - - if (ScreenChanged[currentScreenID]==true) { - CONSOLE.print("Time to draw a screen"); - SCREEN::StartScreenPrint(); - PrintInProgress=false; - ScreenChanged[currentScreenID] = false; - - EXDisplay *display = EXDisplay::getDisplayByNumber(currentScreenID); - if (display) { - EXDisplayRow *row = display->getFirstRow(); - CONSOLE.println("Pinting a line"); - SCREEN::PrintALine(row->getRowNumber(), row->getRowText()); - PrintInProgress=true; - } - else { - SCREEN::PrintNoData(); - } - } - else { - if (PrintInProgress) { - EXDisplayRow *row = row->getNext(); - SCREEN::PrintALine(row->getRowNumber(), row->getRowText()); - } - - } -} -*/ - -/* DISABLE -void TFT_DrawHeader() { - char header[25] = {""}; - sprintf(header, "DCC-EX SCREEN %d\n", currentScreenID); - tft.setTextColor(YELLOW); - showmsgXY(1, 20, 1, header); - tft.drawFastHLine(0, 25, tft.width(), WHITE); - tft.setTextColor(WHITE); // set this for all screen lines -} -*/ - -/* DISABLE -void StartScreenPrint() { - CONSOLE.println("New Page"); - tft.fillScreen(BLACK); - - TFT_DrawHeader(); - - CONSOLE.println("Drawn Header\n"); - NextRowToPrint = 0; - NextScreenLine = 0; -} -*/ - -// void PrintNoData() { showmsgXY(100, 100, 1, "No Data"); } - -/* DISABLE -void PrintALine(int Row, char *text) { - - int vpos = (NextScreenLine * 21) + 44; - - // showmsgXY(1, vpos, 1, blankmsg); - - showmsgXY(1, vpos, 1, text); - // increment the screen & line count - NextRowToPrint++; - NextScreenLine++; - - if (NextRowToPrint >= MAX_ROWS) { - // We've reached the end of this data for this page - PrintInProgress = false; - - // Any blank lines needed? - while (NextScreenLine < MAX_ROWS) { - vpos = (NextScreenLine * 21) + 44; - // showmsgXY(1, vpos, 1, blankmsg); - NextScreenLine++; - } - NextRowToPrint = 0; - NextScreenLine = 0; - } -} -*/ - -/* DISABLE -void DisplayScreen() { - - for (byte x = 0; x < 10; x++) { - // printf("Line %d - Use - %d - %s\n", x, DisplayLines[currentScreenID][x].inuse, - // DisplayLines[currentScreenID][x].text); - } -} -*/ - -// bool check_touch() { return false; } diff --git a/OLDEXScreen.h b/OLDEXScreen.h deleted file mode 100644 index 7e79ce4..0000000 --- a/OLDEXScreen.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef OLDEX_SCREEN_H -#define OLDEX_SCREEN_H - -// EX-Screen.h - -// #define DEBUG - -// uint8_t currentScreenID = 0; - -// ALL Touch panels and wiring are DIFFERENT. The touch screen needs to be calibrated -// See the README files for how to run the calibration routine and -// copy-paste results from TouchScreen_Calibr_native.ino into the lines below. -// These settings are for the McuFriend 2.8" shield in Portrait tft.screenRotaion(0) - -//const byte XP = 7, XM = A1, YP = A2, YM = 6; -//const int TS_LEFT=899,TS_RT=122,TS_TOP=100,TS_BOT=898; -//const int TS_LEFT=122,TS_RT=899,TS_TOP=100,TS_BOT=898; - -// Define some colours for the display - -/* DISABLE -#define BLACK 0x0000U -#define RED 0xF800U -#define GREEN 0x07E0U -#define CYAN 0x07FFU -#define MAGENTA 0xF81FU -#define YELLOW 0xFFE0U -#define WHITE 0xFFFFU -*/ -//#define YELLOW 0xE0U -//#define WHITE 0xFFU -//const uint16_t YELLOW = 0xFFE0U; -//const uint16_t WHITE = 0xFFFFU; - - -/*#if defined(ESP32) - #define YELLOW ~YELLOW - #define WHITE ~WHITE -#endif*/ - -// variables to indicate what needs doing to display the screen -/* DISABLE -bool ScreenChanged[MAX_SCREENS]; -bool PrintInProgress=false; -byte NextRowToPrint=0; -byte NextScreenLine=0; -*/ - -/* DISABLE -namespace SCREEN -{ - - void TFT_Startup(); - void showmsgXY(int x, int y, int sz, const char *msg); - void TFT_DrawHeader(); - void testprint(byte lines); - void setScreenRows(uint8_t rowId); - void CheckScreens(); - void StartScreenPrint(); - void PrintNoData(); - //void PrintSingleLine(byte screenNo, byte screenRow); - void PrintALine(int Row, char * text); - void DisplayScreen(); - void check_touch(); - -} -*/ - -#endif \ No newline at end of file diff --git a/Old/AdafruitTouch.cpp.old b/Old/AdafruitTouch.cpp.old new file mode 100644 index 0000000..9acefb4 --- /dev/null +++ b/Old/AdafruitTouch.cpp.old @@ -0,0 +1,29 @@ +#include "Defines.h" + +#if defined(NEEDS_MCU) && defined(USE_TOUCH) + +#include "AdafruitTouch.h" + +AdafruitTouch::AdafruitTouch(TouchScreen &touchScreen) : InputMethod(), _touchScreen(touchScreen) {} + +void AdafruitTouch::begin() { + if (!_screen) { + return; + } + _calculateButtons(); +} + +bool AdafruitTouch::_readRawInput(ButtonName button) { + TSPoint tsPoint = _touchScreen.getPoint(); + if (tsPoint.z > _touchScreen.pressureThreshhold) { + uint16_t x = map(tsPoint.x, TS_MINX, TS_MAXX, 0, _screen->getWidth()); + uint16_t y = map(tsPoint.y, TS_MINY, TS_MAXY, 0, _screen->getHeight()); + if (x >= _buttons[button].xStart && x <= _buttons[button].xEnd && y >= _buttons[button].yStart && + y <= _buttons[button].yEnd) { + return true; + } + } + return false; +} + +#endif diff --git a/Old/AdafruitTouch.h.old b/Old/AdafruitTouch.h.old new file mode 100644 index 0000000..ffc8e05 --- /dev/null +++ b/Old/AdafruitTouch.h.old @@ -0,0 +1,30 @@ +#ifndef ADAFRUITTOUCH_H +#define ADAFRUITTOUCH_H + +#include "Defines.h" + +#if defined(NEEDS_MCU) && defined(USE_TOUCH) +#include "InputMethod.h" +#include +#include +#include +#include + + +class AdafruitTouch : public InputMethod { +public: + /// @brief Constructor for an AdafruitTouch instance + /// @param touchScreen Reference to an Adafruit TouchScreen instance + AdafruitTouch(TouchScreen &touchScreen); + + void begin() override; + +private: + TouchScreen &_touchScreen; + + bool _readRawInput(ButtonName button) override; +}; + +#endif // NEEDS_MCU and USE_TOUCH + +#endif diff --git a/Old/AutomaticScreenSetup.h.old b/Old/AutomaticScreenSetup.h.old new file mode 100644 index 0000000..efb70fd --- /dev/null +++ b/Old/AutomaticScreenSetup.h.old @@ -0,0 +1,9 @@ +#ifndef AUTOMATICSCREENSETUP_H +#define AUTOMATICSCREENSETUP_H + +#include "Defines.h" +#include + + + +#endif diff --git a/Old/ConfigMacros.h.old b/Old/ConfigMacros.h.old new file mode 100644 index 0000000..d98914e --- /dev/null +++ b/Old/ConfigMacros.h.old @@ -0,0 +1,12 @@ +#ifndef CONFIGMACROS_H +#define CONFIGMACROS_H + +#define EXDISPLAY(id, foreground, background) ->addDisplay(id, foreground, background) + +#define TFT_ESPISCREEN(config) new TFT_eSPIScreen() config; + +#define BUTTONS(b1, b2, b3, b4, b5) ->setInput(new InputMethod(b1, b2, b3, b4, b5)) + +#define EXTOUCH ->setInput(new InputMethod()) + +#endif diff --git a/Old/Defines.h.old b/Old/Defines.h.old new file mode 100644 index 0000000..11a3b22 --- /dev/null +++ b/Old/Defines.h.old @@ -0,0 +1,85 @@ +#ifndef EX_DISPLAY_H +#define EX_DISPLAY_H + +#define MCU 1 +#define TFT 2 +#define OLED 3 + +#include "ConfigMacros.h" + +#if __has_include("config.h") +#include "config.h" +#else +#warning Configuration file "config.h" not found, using example file "config.example.h" +#include "config.example.h" +#endif + +// Set default parameters if not set in config.h for some reason +#ifndef TEXT_FONT +#define TEXT_FONT MEDIUM +#endif +#ifndef TEXT_COLOUR +#define TEXT_COLOUR WHITE +#endif +#ifndef TEXT_SIZE +#define TEXT_SIZE 1 +#endif +#ifndef BACKGROUND_COLOUR +#define BACKGROUND_COLOUR BLACK +#endif +#ifndef SCREEN_ROTATION +#define SCREEN_ROTATION 1 +#endif + +#if SCREEN_TYPE == MCU +#define NEEDS_MCU +#elif SCREEN_TYPE == TFT +#define NEEDS_TFT +#elif SCREEN_TYPE == OLED +#define NEEDS_OLED +#else +#error A screen type for the first screen has not been set, you must define either MCU, TFT, or OLED +#endif + +// Set up console and CS listener for Mega +#if defined(ARDUINO_AVR_MEGA2560) +#define RX_PIN 0 // Define the RX pin for Serial1 +#define CONSOLE Serial +#define CS_LISTEN Serial1 +//#define RX_PIN 19 // Define the RX pin for Serial1 +#define USE_EEPROM +// Set up console and CS listener for Uno +#elif defined(ARDUINO_AVR_UNO) +#define RX_PIN 0 +#define CONSOLE Serial +#define CS_LISTEN Serial +#define USE_EEPROM +// Set up console and CS listener for ESP32 +#elif defined(ARDUINO_ARCH_ESP32) +#define RX_PIN 0 +#define CONSOLE Serial +#define CS_LISTEN Serial +#define USE_SPIFFS +// Set up console and CS listener for F411RE +#elif defined(ARDUINO_NUCLEO_F411RE) +#define CONSOLE Serial +HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE +#define CS_LISTEN Serial1 +#define RX_PIN PB7; +#define USE_FLASH +// Set up console and CS listener for F446RE +#elif defined(ARDUINO_NUCLEO_F446RE) +#define CONSOLE Serial +HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5 - F446RE +#define CS_LISTEN Serial5 +#define RX_PIN PD2; +#define USE_FLASH +#endif + +// If user has defined SERIAL_ONLY, override CS_LISTEN +#ifdef SERIAL_ONLY +#undef CS_LISTEN +#define CS_LISTEN Serial +#endif + +#endif \ No newline at end of file diff --git a/Old/DisplayFunctions.cpp.old b/Old/DisplayFunctions.cpp.old new file mode 100644 index 0000000..fc09179 --- /dev/null +++ b/Old/DisplayFunctions.cpp.old @@ -0,0 +1,68 @@ +#include "Defines.h" +#include "DisplayFunctions.h" + +#ifdef DEBUG +bool debug = true; +#else +bool debug = false; +#endif + +// This function is called from AtFinder when a +// <@ screenid row "text"> message is discovered. + +void updateDisplayRow(uint8_t screenId, uint8_t screenRow, char *text) { + + // special diagnostic to dump buffers on request + if (screenId == 255) { + displayAllRows(); + return; + } + + for (PhysicalScreen *screen = PhysicalScreen::getFirst(); screen; screen = screen->getNext()) { + for (LogicalDisplay *display = screen->getFirstDisplay(); display; display = display->getNext()) { + if (display->getDisplayNumber() == screenId) { + display->updateRow(screenRow, text); + return; + } + } + } +} + +void updateScreen() { + for (PhysicalScreen *screen = PhysicalScreen::getFirst(); screen; screen = screen->getNext()) { +#ifdef SCROLLTIME + for (LogicalDisplay *display = screen->getFirstDisplay(); display; display = display->getNext()) { + display->autoScroll(SCROLLTIME); + } +#endif +#ifdef DISPLAY_SWITCH_TIME + screen->autoSwitch(DISPLAY_SWITCH_TIME); +#endif + screen->processActiveDisplay(); + } +} + +void displayAllRows() { + for (PhysicalScreen *screen = PhysicalScreen::getFirst(); screen; screen = screen->getNext()) { + CONSOLE.print(F("\n~~ All displays and rows for screen ")); + CONSOLE.print(screen->getScreenNumber()); + CONSOLE.println(F(" ~~")); + + for (LogicalDisplay *display = screen->getFirstDisplay(); display; display = display->getNext()) { + CONSOLE.print(F("~~ Display ")); + CONSOLE.print(display->getDisplayNumber()); + CONSOLE.println(F(" ~~\nRow|Display Row|Message|isChanged|needsRender")); + for (LogicalDisplayRow *row = display->getFirstRow(); row; row = row->getNext()) { + CONSOLE.print(row->getRowNumber()); + CONSOLE.print(F("|")); + CONSOLE.print(row->getDisplayRow()); + CONSOLE.print(F("|")); + CONSOLE.print(row->getRowText()); + CONSOLE.print(F("|")); + CONSOLE.print(row->isChanged()); + CONSOLE.print(F("|")); + CONSOLE.println(row->needsRender()); + } + } + } +} diff --git a/DisplayFunctions.h b/Old/DisplayFunctions.h.old similarity index 58% rename from DisplayFunctions.h rename to Old/DisplayFunctions.h.old index 9495ab4..e9f8278 100644 --- a/DisplayFunctions.h +++ b/Old/DisplayFunctions.h.old @@ -1,22 +1,19 @@ #ifndef DISPLAYFUNCTIONS_H #define DISPLAYFUNCTIONS_H -#include "EXDisplayClass.h" +#include "PhysicalScreen.h" #include - extern bool debug; -// extern EXDisplay *display0; - /// @brief Callback function for AtFinder /// @param screenId ID of the screen to update /// @param screenRow Row of the screen to update /// @param text Char array of text -void updateEXDisplayRow(uint8_t screenId, uint8_t screenRow, char *text); +void updateDisplayRow(uint8_t screenId, uint8_t screenRow, char *text); -/// @brief Iterate through each display object and update the display of their associated screen -void updateScreens(); +/// @brief Update the active display on its associated screen +void updateScreen(); /// @brief Display all rows on all screens - handy for debug void displayAllRows(); diff --git a/Old/EX-Display.ino.old b/Old/EX-Display.ino.old new file mode 100644 index 0000000..8c79764 --- /dev/null +++ b/Old/EX-Display.ino.old @@ -0,0 +1,162 @@ +#include "AtFinder.h" +#include "Defines.h" +#include "DisplayFunctions.h" +#include "version.h" +#include + +bool StartupPhase = true; +unsigned long timestamp = 0; +long screencount = 0; +char startupMessage[13] = {}; + +#if SCREEN_TYPE == MCU +#include "MCUFriendScreen.h" +#ifdef USE_TOUCH +#include "AdafruitTouch.h" +TouchScreen touchScreen = TouchScreen(XP, YP, XM, YM, 300); +auto *input = new AdafruitTouch(touchScreen); +#endif +#elif SCREEN_TYPE == TFT +#include "TFT_eSPIScreen.h" +#if defined(USE_TOUCH) +#include "TFT_eSPITouch.h" +auto *input = new TFT_eSPITouch(tft); +#endif +#elif SCREEN_TYPE == OLED +#include "OLEDScreen.h" +// Adafruit_SSD1306 oled = Adafruit_SSD1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire); +#endif + +#if defined(USE_BUTTONS) +#include "PushButton.h" +auto *input = new PushButton(LEFT_BUTTON, RIGHT_BUTTON, CENTRE_BUTTON, UP_BUTTON, DOWN_BUTTON); +#endif + +void setup() { +#if defined(DISABLE_SECOND_TFT) // Workaround for second TFT not controlled yet + pinMode(22, OUTPUT); + pinMode(16, OUTPUT); + digitalWrite(22, HIGH); + digitalWrite(16, HIGH); +#endif + CONSOLE.begin(115200); + CS_LISTEN.begin(115200); // Start Serial1 for listening to messages + + CONSOLE.print(F("EX-Display v")); + CONSOLE.println(VERSION); + +#ifdef NEEDS_OLED + Wire.begin(); +#endif + + // Setup our physical screens first - required before adding displays +#if defined(NEEDS_TFT) + auto *screen = new TFT_eSPIScreen(); +#elif defined(NEEDS_MCU) + auto *screen = new MCUFriendScreen(); +#elif defined(NEEDS_OLED) + auto *screen = new OLEDScreen(SCREEN_WIDTH, SCREEN_HEIGHT); +#endif + screen->setupScreen(SCREEN_ROTATION, TEXT_SIZE, BACKGROUND_COLOUR) + ->addDisplay(DISPLAY_1_ID, TEXT_COLOUR, BACKGROUND_COLOUR) + ->addDisplay(DISPLAY_2_ID, TEXT_COLOUR, BACKGROUND_COLOUR) + ->addDisplay(DISPLAY_3_ID, TEXT_COLOUR, BACKGROUND_COLOUR); + + // Tell AtFinder our maximum supported text length, + // and how to call back when found. + AtFinder::setup(100, updateDisplayRow); + + // Create display instances + for (LogicalDisplay *display = screen->getFirstDisplay(); display; display = display->getNext()) { + CONSOLE.print(F("Display ID|Max Rows|Max Columns: ")); + CONSOLE.print(display->getDisplayNumber()); + CONSOLE.print(F("|")); + CONSOLE.print(screen->getMaxRows()); + CONSOLE.print(F("|")); + CONSOLE.println(display->getMaxRowLength()); + } + + sprintf(startupMessage, "EX-Display"); + screen->writeRow(0, 0, TEXT_COLOUR, BACKGROUND_COLOUR, 0, startupMessage, false); + sprintf(startupMessage, VERSION); + screen->writeRow(1, 0, TEXT_COLOUR, BACKGROUND_COLOUR, 0, startupMessage, false); + + delay(2000); + + screen->clearScreen(BACKGROUND_COLOUR); + + timestamp = millis(); + +#if defined(USE_TOUCH) || defined(USE_BUTTONS) + input->setScreen(screen); + input->begin(); +#endif + + CONSOLE.println(F("End of Setup")); +} + +void loop() { + + // Check if we are in the startup phase + // This phase inhibits screen drawing until the startup messages are + // issued by the CS + if (StartupPhase) { + if ((millis() - timestamp) >= STARTUP_TIME) { + StartupPhase = false; + screencount = millis(); + } + } + + // each byte received form serial is passed to the parse + if (CS_LISTEN.available()) { + AtFinder::processInputChar(CS_LISTEN.read()); + } + // you can display all rows by sending <@ 255 0 ""> + // No data incoming so see if we need to display anything + // DISABLE IN STARTUPPHASE + else { + if (!StartupPhase) { + updateScreen(); +#if defined(USE_TOUCH) || defined(USE_BUTTONS) + ButtonResult inputButton = input->processInput(); + switch (inputButton.button) { + case LeftButton: + CONSOLE.print(F("Left button state: ")); + CONSOLE.println(inputButton.state); + if (inputButton.state == Pressed) + screen->switchToPreviousDisplay(); + break; + + case RightButton: + CONSOLE.print(F("Right button state: ")); + CONSOLE.println(inputButton.state); + if (inputButton.state == Pressed) + screen->switchToNextDisplay(); + break; + + case CentreButton: + CONSOLE.print(F("Centre button state: ")); + CONSOLE.println(inputButton.state); + break; + + case UpButton: + CONSOLE.print(F("Up button state: ")); + CONSOLE.println(inputButton.state); + if (inputButton.state == Pressed) + screen->getActiveDisplay()->scrollUp(); + break; + + case DownButton: + CONSOLE.print(F("Down button state: ")); + CONSOLE.println(inputButton.state); + if (inputButton.state == Pressed) + screen->getActiveDisplay()->scrollDown(); + break; + + default: + break; + } +#endif + } + } +} diff --git a/Old/FontOptions.h.old b/Old/FontOptions.h.old new file mode 100644 index 0000000..241cbce --- /dev/null +++ b/Old/FontOptions.h.old @@ -0,0 +1,41 @@ +#ifndef FONTOPTIONS_H +#define FONTOPTIONS_H + +#if SCREEN_TYPE == MCU +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +/// @brief Macros for available colours +#define BLACK 0x0000 +#define RED 0xF800 +#define GREEN 0x07E0 +#define CYAN 0x07FF +#define MAGENTA 0xF81F +#define YELLOW 0xFFE0 +#define WHITE 0xFFFF +#define OLED_BLACK 0 +#define OLED_WHITE 1 +#define OLED_INVERT 2 + +/// @brief Macros for available fonts +#if SCREEN_TYPE == MCU || SCREEN_TYPE == TFT +#define SMALL &FreeMono9pt7b +#define MEDIUM &FreeMono12pt7b +#define LARGE &FreeMono18pt7b +#define XLARGE &FreeMono24pt7b +#define SMALL_SANS &FreeSans9pt7b +#define MEDIUM_SANS &FreeSans12pt7b +#define LARGE_SANS &FreeSans18pt7b +#define XLARGE_SANS &FreeSans24pt7b +#elif SCREEN_TYPE == OLED +#define MEDIUM System5x7 +#endif + +#endif diff --git a/Old/InputMethod.cpp.old b/Old/InputMethod.cpp.old new file mode 100644 index 0000000..ab03561 --- /dev/null +++ b/Old/InputMethod.cpp.old @@ -0,0 +1,85 @@ +#include "Defines.h" +#include "InputMethod.h" + +InputMethod::InputMethod() { + _screen = nullptr; + for (ButtonName b = LeftButton; b < NoButton; b = static_cast(static_cast(b) + 1)) { + _buttons[b]._lastDebounceTime = 0; + _buttons[b]._buttonHoldStartTime = 0; + _buttons[b]._buttonState = false; + _buttons[b]._lastButtonState = false; + } +} + +ButtonResult InputMethod::processInput() { + unsigned long currentTime = millis(); + ButtonResult result = {NoButton, None}; + + for (ButtonName b = LeftButton; b < NoButton; b = static_cast(static_cast(b) + 1)) { + bool rawState = _readRawInput(b); + Button &btn = _buttons[b]; + if (rawState != btn._lastButtonState) { + btn._lastDebounceTime = currentTime; + } + if ((currentTime - btn._lastDebounceTime) > _debounceDelay) { + if (rawState != btn._buttonState) { + btn._buttonState = rawState; + if (btn._buttonState) { + btn._buttonHoldStartTime = currentTime; + result = {b, Pressed}; + } else { + result = {b, Released};us + } + } else if (btn._buttonState && (currentTime - btn._buttonHoldStartTime) > _holdThreshold) { + result = {b, Held}; + } + } + btn._lastButtonState = rawState; + if (result.state != None) { + break; + } + } + return result; +} + +void InputMethod::setScreen(PhysicalScreen *screen) { _screen = screen; } + +#ifdef USE_TOUCH +void InputMethod::_calculateButtons() { + // Left third of full screen height is left button, right third right button + // Upper third of centre section is up button, lower third down button + // Centre third of centre section is centre button + _buttons[LeftButton].xStart = 0; + _buttons[LeftButton].xEnd = _screen->getWidth() / 3; + _buttons[LeftButton].yStart = 0; + _buttons[LeftButton].yEnd = _screen->getHeight(); + _buttons[UpButton].xStart = _buttons[LeftButton].xEnd + 1; + _buttons[UpButton].xEnd = (_screen->getWidth() / 3) + _buttons[LeftButton].xEnd; + _buttons[UpButton].yStart = 0; + _buttons[UpButton].yEnd = (_screen->getHeight() / 3); + _buttons[CentreButton].xStart = _buttons[UpButton].xStart; + _buttons[CentreButton].xEnd = _buttons[UpButton].xEnd; + _buttons[CentreButton].yStart = _buttons[UpButton].yEnd + 1; + _buttons[CentreButton].yEnd = (_screen->getHeight() / 3) + _buttons[UpButton].yEnd; + _buttons[DownButton].xStart = _buttons[UpButton].xStart; + _buttons[DownButton].xEnd = _buttons[UpButton].xEnd; + _buttons[DownButton].yStart = _buttons[CentreButton].yEnd + 1; + _buttons[DownButton].yEnd = _screen->getHeight(); + _buttons[RightButton].xStart = _buttons[CentreButton].xEnd + 1; + _buttons[RightButton].xEnd = _screen->getWidth(); + _buttons[RightButton].yStart = 0; + _buttons[RightButton].yEnd = _screen->getHeight(); + for (ButtonName b = LeftButton; b < NoButton; b = static_cast(static_cast(b) + 1)) { + CONSOLE.print(F("Button|xStart|xEnd|yStart|yEnd: ")); + CONSOLE.print(b); + CONSOLE.print(F("|")); + CONSOLE.print(_buttons[b].xStart); + CONSOLE.print(F("|")); + CONSOLE.print(_buttons[b].xEnd); + CONSOLE.print(F("|")); + CONSOLE.print(_buttons[b].yStart); + CONSOLE.print(F("|")); + CONSOLE.println(_buttons[b].yEnd); + } +} +#endif diff --git a/Old/InputMethod.h.old b/Old/InputMethod.h.old new file mode 100644 index 0000000..577b7d0 --- /dev/null +++ b/Old/InputMethod.h.old @@ -0,0 +1,82 @@ +#ifndef INPUTMETHOD_H +#define INPUTMETHOD_H + +#include "Defines.h" +#include "PhysicalScreen.h" +#include + +/* +This class is to abstract input methods from controlling the physical screen. + +This class should be extended by hardware specific classes that implement the virtual methods in this class to interact +with a physical screen. + +The aim is to have five buttons (or equivalent sections on a touch screen) that represent left, right, up, down, and +centre. These may have both push and hold methods to perform different actions. + +InputMethod instances will be in a linked list to cycle through to process inputs, and each InputMethod instance should +have a pointer to a screen to control. +*/ + +struct Button { +#if defined(USE_TOUCH) + uint16_t xStart = 0; + uint16_t xEnd = 0; + uint16_t yStart = 0; + uint16_t yEnd = 0; +#elif defined(USE_BUTTONS) + uint8_t pin; +#endif + unsigned long _lastDebounceTime; + unsigned long _buttonHoldStartTime; + bool _buttonState; + bool _lastButtonState; +}; + +enum ButtonName { + LeftButton = 0, + RightButton = 1, + CentreButton = 2, + UpButton = 3, + DownButton = 4, + NoButton = 5, +}; + +enum ButtonState { + Pressed = 0, + Held = 1, + Released = 2, + None = 3, +}; + +struct ButtonResult { + ButtonName button; + ButtonState state; +}; + +class InputMethod { +public: + /// @brief Constructor for the InputMethod class + InputMethod(); + + virtual void begin() = 0; + + ButtonResult processInput(); + + void setScreen(PhysicalScreen *screen); + +protected: + PhysicalScreen *_screen; // Physical screen instance associated with this input + uint8_t _inputNumber; // Auto incrementing number, enables multiple touch screens + + Button _buttons[5]; +#if defined(USE_TOUCH) + void _calculateButtons(); +#endif + virtual bool _readRawInput(ButtonName button) = 0; + + static const unsigned long _debounceDelay = 50; + static const unsigned long _holdThreshold = 500; +}; + +#endif diff --git a/Old/LogicalDisplay.cpp.old b/Old/LogicalDisplay.cpp.old new file mode 100644 index 0000000..58bb9b9 --- /dev/null +++ b/Old/LogicalDisplay.cpp.old @@ -0,0 +1,188 @@ +#include "Defines.h" +#include "LogicalDisplay.h" + +/* + * LogicalDisplay class implementation + */ +LogicalDisplay::LogicalDisplay(uint8_t displayNumber, uint8_t maxRowLength, uint8_t maxRowNumber, uint16_t textColour, + uint16_t backgroundColour) + : _displayNumber(displayNumber), _maxRowLength(maxRowLength), _maxRowNumber(maxRowNumber), + _defaultTextColour(textColour), _defaultBackgroundColour(backgroundColour) { + _firstRow = nullptr; + _next = nullptr; + _highestRowNumber = 0; + _numberOfRows = 0; + _scrollPosition = 0; + _lastScrollTime = 0; + _needsRedraw = false; +} + +void LogicalDisplay::setNext(LogicalDisplay *display) { _next = display; } + +LogicalDisplay *LogicalDisplay::getNext() { return _next; } + +LogicalDisplayRow *LogicalDisplay::getFirstRow() { return _firstRow; } + +uint8_t LogicalDisplay::getDisplayNumber() { return _displayNumber; } + +LogicalDisplayRow *LogicalDisplay::getRowByNumber(uint8_t rowNumber) { + for (auto *row = _firstRow; row; row = row->getNext()) { + if (row->getRowNumber() == rowNumber) { + return row; + } + } + return nullptr; +} + +void LogicalDisplay::updateRow(uint8_t rowNumber, char *rowText) { + auto *row = getRowByNumber(rowNumber); + if (row && strlen(rowText) == 0) { + _deleteRow(row); + } else if (!row && strlen(rowText) == 0) { + return; + } else if (!row) { + row = _addRow(rowNumber, rowText); + } else { + row->setRowText(rowText); + } +} + +void LogicalDisplay::scrollUp() { + // Scroll up logic: + // _scrollPosition needs to decrement to bring lower rows up the screen + // If _scrollPosition ends at 0, it should become the highest possible row + uint8_t lastRow = _maxRowNumber - 1; // Highest possible row number on screen + if (_highestRowNumber <= lastRow) { // If our max row number is on screen, no scroll required + return; + } + if (_scrollPosition == 0) { // If row 0 is top of screen, need to move to the highest row number + _scrollPosition = _highestRowNumber; + } else { // + _scrollPosition--; + } + for (LogicalDisplayRow *row = _firstRow; row; row = row->getNext()) { + uint8_t newRow = row->getDisplayRow(); + if (newRow == _highestRowNumber) { // If row at bottom, it becomes first row + newRow = 0; + } else { // Otherwise move down one row + newRow++; + } + row->setDisplayRow(_scrollPosition, _maxRowNumber, _highestRowNumber); + } + _needsRedraw = true; // Need to redraw after each scroll +} + +void LogicalDisplay::scrollDown() { + uint8_t lastRow = _maxRowNumber - 1; // Highest possible row number on screen + if (_highestRowNumber <= lastRow) { // If our max row number is on screen, no scroll required + return; + } + if (_scrollPosition >= lastRow) { // If last row is top of screen, goes to bottom + _scrollPosition = 0; + } else { // Otherwise next row is top of screen + _scrollPosition++; + } + for (LogicalDisplayRow *row = _firstRow; row; row = row->getNext()) { + uint8_t newRow = row->getDisplayRow(); + if (newRow == 0) { // If row at top of screen, it becomes last row + newRow = _highestRowNumber; + } else { // Otherwise move up one row + newRow--; + } + row->setDisplayRow(_scrollPosition, _maxRowNumber, _highestRowNumber); + } + _needsRedraw = true; // Need to redraw after each scroll +} + +void LogicalDisplay::autoScroll(unsigned long scrollDelay) { + if (millis() - _lastScrollTime > scrollDelay) { + _lastScrollTime = millis(); + scrollDown(); + } +} + +bool LogicalDisplay::needsRedraw() { return _needsRedraw; } + +void LogicalDisplay::setRedraw(bool redraw) { _needsRedraw = redraw; } + +uint16_t LogicalDisplay::getDefaultTextColour() { return _defaultTextColour; } + +uint16_t LogicalDisplay::getDefaultBackgroundColour() { return _defaultBackgroundColour; } + +uint8_t LogicalDisplay::getMaxRowLength() { return _maxRowLength; } + +LogicalDisplayRow *LogicalDisplay::_addRow(uint8_t rowNumber, char *rowText) { + // create a new row and chain it in + LogicalDisplayRow *row = new LogicalDisplayRow(rowNumber); + _numberOfRows++; + + // find the row prior to the one we want to add + LogicalDisplayRow *previous = nullptr; + for (auto peek = _firstRow; peek; peek = peek->getNext()) { + if (peek->getRowNumber() > rowNumber) + break; + previous = peek; + } + if (previous) { + // chain after previous + row->setNext(previous->getNext()); + previous->setNext(row); + } else { + // chain at start of list + row->setNext(_firstRow); + _firstRow = row; + } + if (rowNumber > _highestRowNumber) { + _highestRowNumber = rowNumber; + } + row->setColours(TEXT_COLOUR, BACKGROUND_COLOUR); + row->setRowText(rowText); + row->setDisplayRow(_scrollPosition, _maxRowNumber, _highestRowNumber); + return row; +} + +void LogicalDisplay::_deleteRow(LogicalDisplayRow *row) { + if (!row) { + return; // Return if the row is nullptr + } + + // Find the previous node in the linked list + LogicalDisplayRow *previous = nullptr; + LogicalDisplayRow *current = _firstRow; + while (current != row) { + previous = current; + current = current->getNext(); + } + + // Remove the node from the linked list + if (previous) { + previous->setNext(row->getNext()); + } else { + _firstRow = row->getNext(); + } + + // Decrement the number of rows + _numberOfRows--; + + // Update the maximum row number if necessary + if (row->getRowNumber() == _highestRowNumber) { + _highestRowNumber = 0; + for (LogicalDisplayRow *temp = _firstRow; temp; temp = temp->getNext()) { + if (temp->getRowNumber() > _highestRowNumber) { + _highestRowNumber = temp->getRowNumber(); + } + } + } + + // If we're now within the confines of the screen, need to reset display rows and redraw + if (_highestRowNumber <= _maxRowNumber) { + _scrollPosition = 0; + for (LogicalDisplayRow *temp = _firstRow; temp; temp = temp->getNext()) { + temp->setDisplayRow(_scrollPosition, _maxRowNumber, _highestRowNumber); + } + _needsRedraw = true; + } + + // Delete the row object + delete row; +} diff --git a/Old/LogicalDisplay.h.old b/Old/LogicalDisplay.h.old new file mode 100644 index 0000000..943f079 --- /dev/null +++ b/Old/LogicalDisplay.h.old @@ -0,0 +1,100 @@ +#ifndef LOGICALDISPLAY_H +#define LOGICALDISPLAY_H + +#include "Defines.h" +#include "LogicalDisplayRow.h" +#include + +class LogicalDisplay { +public: + /// @brief Constructor for the LogicalDisplay class + /// @param displayNumber The ID of this display, 0 - 254 + /// @param textColour Valid colour code for the default text colour + /// @param backgroundColour Valid colour code for the default background colour + LogicalDisplay(uint8_t displayNumber, uint8_t maxRowLength, uint8_t maxRowNumber, uint16_t textColour, + uint16_t backgroundColour); + + /// @brief Set the next display in the linked list + /// @param display Pointer to the next LogicalDisplay instance + void setNext(LogicalDisplay *display); + + /// @brief Get the next LogicalDisplay object in the linked list + /// @return Pointer to the next LogicalDisplay object + LogicalDisplay *getNext(); + + /// @brief Get the first LogicalDisplayRow object associated with this display + /// @return Pointer to the first LogicalDisplayRow object for this display + LogicalDisplayRow *getFirstRow(); + + /// @brief Get the display's number + /// @return 0 - 255 + uint8_t getDisplayNumber(); + + /// @brief Get an LogicalDisplayRow object for this display by the specified row number + /// @param rowNumber Row number to retrieve, 0 - 255 + /// @return Pointer to an LogicalDisplayRow object, or nullptr if not exists + LogicalDisplayRow *getRowByNumber(uint8_t rowNumber); + + /// @brief Update text and ticker for the specified row number, will add if it doesn't exist + /// @param rowNumber Row number to display text on, 0 - 255 + /// @param rowText Char array of text for the row + void updateRow(uint8_t rowNumber, char *rowText); + + /// @brief Scroll up one row vertically + void scrollUp(); + + /// @brief Scroll down one row vertically + void scrollDown(); + + /// @brief Call this method as often as possible to support timed vertical scrolling + /// @param scrollDelay Time in milliseconds between vertical scrolling updates + void autoScroll(unsigned long scrollDelay); + + /// @brief Check if this display needs to be redrawn + /// @return True|False + bool needsRedraw(); + + /// @brief Set the needsRedraw flag + /// @param redraw true|false + void setRedraw(bool redraw); + + /// @brief Get the default text colour for this display + /// @return 16 bit colour code + uint16_t getDefaultTextColour(); + + /// @brief Get the default background colour for this display + /// @return 16 bit colour code + uint16_t getDefaultBackgroundColour(); + + /// @brief Get the maximum number of characters that will fit on a row + /// @return 0 - 255 + uint8_t getMaxRowLength(); + +private: + LogicalDisplay *_next; // Pointer to the next display in the list + uint8_t _displayNumber; // ID of this display + LogicalDisplayRow *_firstRow; // Pointer to the first LogicalDisplayRow instances in the linked list + uint8_t _highestRowNumber; // Highest row number from the associated linked list instances + uint8_t _maxRowLength; // Number of characters that will fit on the physical screen + uint8_t _maxRowNumber; // Max number of rows that will fit on the physical screen + uint8_t _numberOfRows; // Calculated number of rows in the linked list + uint8_t _scrollPosition; // Row number that is at the top of the display to support scrolling + unsigned long _lastScrollTime; // Last time in ms a scroll function was performed + bool _needsRedraw; // Flag if this display needs to be redrawn + uint8_t _fontHeight; // Height of the font for this display + uint8_t _fontWidth; // Width of the font for this display + uint8_t _defaultTextColour; // Default colour for text for this display + uint8_t _defaultBackgroundColour; // Default background colour for this display + + /// @brief Private method to add a row + /// @param rowNumber 0 - 255 + /// @param rowText Pointer to the char array containing the text for this row + /// @return Pointer to the created LogicalDisplayRow instance + LogicalDisplayRow *_addRow(uint8_t rowNumber, char *rowText); + + /// @brief Delete the specified row number + /// @param row Pointer to the LogicalDisplayRow instance to delete + void _deleteRow(LogicalDisplayRow *row); +}; + +#endif diff --git a/Old/LogicalDisplayRow.cpp.old b/Old/LogicalDisplayRow.cpp.old new file mode 100644 index 0000000..11994d0 --- /dev/null +++ b/Old/LogicalDisplayRow.cpp.old @@ -0,0 +1,210 @@ +#include "Defines.h" +#include "LogicalDisplayRow.h" + +/* + * LogicalDisplayRow class implementation + */ +LogicalDisplayRow::LogicalDisplayRow(uint8_t rowNumber) { + _rowNumber = rowNumber; + _maxMalloc = 0; + _rowText = nullptr; + _changed = true; + _needsRender = false; + _rowAttributes = {false, false, false, false}; +} + +uint8_t LogicalDisplayRow::getRowNumber() { return _rowNumber; } + +uint8_t LogicalDisplayRow::getMaxRowLength() { return _maxMalloc; } + +void LogicalDisplayRow::setRowText(char *rowText) { + // Note size limit is 254 chars but that is beyond + // the capability of the caller anyway. + + // Ignore change if text the same... + if (_rowText && strcmp(_rowText, rowText) == 0) + return; + + uint8_t bytesNeeded = strlen(rowText) + 1; + if (bytesNeeded > _maxMalloc || !_rowText) { + // _rowText is not big enough so realloc() it. + // Deliberately dont realloc() if its big enough to + // avolid fragmentation caused by realloc freeing + // the tail which we will probabaly need later anyway. + _rowText = (char *)realloc(_rowText, bytesNeeded); + _maxMalloc = bytesNeeded; + } + strcpy(_rowText, rowText); + + // Now we have our new text, check for formatters + _rowFormatter(); + + _changed = true; +} + +char *LogicalDisplayRow::getRowText() { + _changed = false; + return _rowText; +} + +bool LogicalDisplayRow::isChanged() { return _changed; } + +void LogicalDisplayRow::setDisplayRow(uint8_t scrollPosition, uint8_t maxScreenRows, uint8_t maxRowNumber) { + if (_rowNumber >= scrollPosition) { + _displayRow = _rowNumber - scrollPosition; + } else { + _displayRow = maxRowNumber - (scrollPosition - _rowNumber - 1); + } + if (_displayRow >= maxScreenRows) { + _needsRender = false; + } else { + _needsRender = true; + } +} + +uint8_t LogicalDisplayRow::getDisplayRow() { return _displayRow; } + +bool LogicalDisplayRow::needsRender() { return _needsRender; } + +LogicalDisplayRow *LogicalDisplayRow::getNext() { return _next; } + +void LogicalDisplayRow::setNext(LogicalDisplayRow *next) { _next = next; } + +void LogicalDisplayRow::setColours(uint16_t textColour, uint16_t backgroundColour) { + _textColour = textColour; + _backgroundColour = backgroundColour; + _changed = true; +} + +uint16_t LogicalDisplayRow::getTextColour() { return _textColour; } + +uint16_t LogicalDisplayRow::getBackgroundColour() { return _backgroundColour; } + +bool LogicalDisplayRow::isLine() { + _changed = false; + return _rowAttributes.line; +} + +bool LogicalDisplayRow::isUnderlined() { return _rowAttributes.underline; } + +LogicalDisplayRow::~LogicalDisplayRow() { free(_rowText); } + +void LogicalDisplayRow::_rowFormatter() { + /* + Need to check for all possible formatting codes here + When formatting codes discovered: + - Apply attributes to _rowAttributes as required + - Embedded colours: "#0xdddd#0xdddd#Coloured text" + - Row underlined: "_This text is underlined_" + - Row is a horizontal line: "--" + - Row always tickers: "~~Text to ticker" + - Row never tickers: "!~Never ticker text" + */ + _setColours(); + _setLine(); + _setUnderline(); + _alwaysTicker(); + _neverTicker(); +} + +void LogicalDisplayRow::_setColours() { + // Check for format #0xdddd#0xdddd# + if (_rowText[0] != '#' || _rowText[7] != '#' || _rowText[14] != '#') { + return; + } + // Find the positions of the three '#' characters + char *start = strchr(_rowText, '#'); + if (start == NULL) { + return; + } + char *middle = strchr(start + 1, '#'); + if (middle == NULL) { + return; + } + char *end = strchr(middle + 1, '#'); + if (end == NULL) { + return; + } + // Check if the hexadecimal values are of the correct length (4 characters) + if (end - middle - 1 != 6 || middle - start - 1 != 6) { + return; + } + // Check if the characters between the '#' are valid hexadecimal digits + if (_rowText[1] != '0' || _rowText[2] != 'x' || middle[1] != '0' || middle[2] != 'x') { + return; + } + for (const char *p = _rowText + 3; p < middle; p++) { + if (!((p[0] >= '0' && p[0] <= '9') || (p[0] >= 'A' && p[0] <= 'F'))) { + return; + } + } + for (const char *p = middle + 3; p < end; p++) { + if (!((p[0] >= '0' && p[0] <= '9') || (p[0] >= 'A' && p[0] <= 'F'))) { + return; + } + } + // Find first # + start = strchr(_rowText, '#'); + // Convert text colour + char *endPointer; + _textColour = (uint16_t)strtol(start + 1, &endPointer, 16); + // Find next # + start = strchr(start + 1, '#'); + // Convert background colour + _backgroundColour = (uint16_t)strtol(start + 1, &endPointer, 16); + _removeFormatters(15); +} + +void LogicalDisplayRow::_setLine() { + if (strlen(_rowText) != 2 || _rowText[0] != '-' || _rowText[strlen(_rowText) - 1] != '-') { + _rowAttributes.line = false; + return; + } + _rowAttributes.line = true; + _removeFormatters(2); +} + +void LogicalDisplayRow::_setUnderline() { + if (_rowText[0] != '_' || _rowText[1] != '_') { + if (_rowAttributes.underline) { + _rowAttributes.underline = false; + } + return; + } + _rowAttributes.underline = true; + _removeFormatters(2); +} + +void LogicalDisplayRow::_alwaysTicker() { + // Check for leading "~~" + if (_rowText[0] != '~' || _rowText[1] != '~') { + _rowAttributes.alwaysTicker = false; + return; + } + _rowAttributes.alwaysTicker = true; + return; +} + +void LogicalDisplayRow::_neverTicker() { + // Check for leading "!~" + if (_rowText[0] != '!' || _rowText[1] != '~') { + _rowAttributes.neverTicker = false; + return; + } + _rowAttributes.neverTicker = true; + return; +} + +void LogicalDisplayRow::_removeFormatters(uint8_t size) { + // New length for "text" needs to be length - formatters (size) + null pointer (1) + uint8_t textLength = strlen(_rowText) - size + 1; + // Set our temp char array to what we already have defined as our max size + char *temp = (char *)malloc(_maxMalloc); + // Copy the text minus formatters to temp and null terminate + strncpy(temp, _rowText + size, textLength); + temp[textLength] = '\0'; + // Free the no longer required _rowText memory + free(_rowText); + // Temp is now _rowText + _rowText = temp; +} diff --git a/Old/LogicalDisplayRow.h.old b/Old/LogicalDisplayRow.h.old new file mode 100644 index 0000000..ebdb0c3 --- /dev/null +++ b/Old/LogicalDisplayRow.h.old @@ -0,0 +1,130 @@ +#ifndef LOGICALDISPLAYROW_H +#define LOGICALDISPLAYROW_H + +#include + +/** + * @brief Class for each row associated with a display. + * Each row is a member of a linked list which is an attribute of an EXDisplay object. + * + */ +class LogicalDisplayRow { + /// @brief Structure for the _rowAttributes attribute for row formatting + struct RowAttributes { + bool line : 1; + bool underline : 1; + bool alwaysTicker : 1; + bool neverTicker : 1; + }; + +public: + /// @brief Constructor for the LogicalDisplayRow object + /// @param rowNumber Row number on the display, 0 - 255 + LogicalDisplayRow(uint8_t rowNumber); + + /// @brief Get the row number this should be displayed on + /// @return 0 - 255 + uint8_t getRowNumber(); + + /// @brief Get the maximum length this row has been set to + /// @return 0 - 255 + uint8_t getMaxRowLength(); + + /// @brief Update the text for the specified row + /// @param rowText Char array containing the text + void setRowText(char *rowText); + + /// @brief Get the text associated with this row + /// @return Char array + char *getRowText(); + + /// @brief Check if this row has been changed, set when setRowText called, reset when getRowText called + /// @return True|False + bool isChanged(); + + /// @brief Set the physical screen row this row should be rendered/drawn on + /// @param scrollPosition The current row that is at the top of the screen + /// @param maxScreenRows Maximum number of rows the screen can display - sets _needsRender + /// @param maxRowNumber The highest row number in the list of rows + void setDisplayRow(uint8_t scrollPosition, uint8_t maxScreenRows, uint8_t maxRowNumber); + + /// @brief calculated to determine which screen row is used + /// @return 0 - 255 + uint8_t getDisplayRow(); + + /// @brief Check if this row fits on a physical display and needs to be rendered/drawn + /// @return True|False + bool needsRender(); + + /// @brief Set the pointer to the next LogicalDisplayRow object in the linked list + /// @param next Pointer to the next LogicalDisplayRow object + void setNext(LogicalDisplayRow *next); + + /// @brief Get the next row + /// @return Pointer to the next LogicalDisplayRow object in the linked list + LogicalDisplayRow *getNext(); + + /// @brief Set the text/foreground and background colour for this row + /// @param textColour Hex value of the text/foreground colour + /// @param backgroundColour Hex value of the background colour + void setColours(uint16_t textColour, uint16_t backgroundColour); + + /// @brief Get the text/foreground colour for this row + /// @return Colour code + uint16_t getTextColour(); + + /// @brief Get the background colour for this row + /// @return Colour code + uint16_t getBackgroundColour(); + + /// @brief Check if this row is a horizontal line + /// @return true|false + bool isLine(); + + /// @brief Check if this row should be underlined + /// @return true|false + bool isUnderlined(); + + /// @brief Destructor for the LogicalDisplayRow object + ~LogicalDisplayRow(); + +private: + uint8_t _rowNumber; // This is the row number received from the parser + uint8_t _maxMalloc; // This is the calculated maximum length of the text received from the parser + char *_rowText; // This is the text received from the parser + bool _changed; // Flag set when text received from the parser is different to rowText + uint8_t _displayRow; // This is the calculated physical row on a display that this line belongs on + bool _needsRender; // Flag that is set when row belongs on a physical display, false when off-screen + LogicalDisplayRow *_next; + uint16_t _textColour; // Text/foreground colour for this row + uint16_t _backgroundColour; // Background colour for this row + RowAttributes _rowAttributes; // One bit per attribute to allow 8 total + + /// @brief Private method to format the row attributes for the specified row + /// @param row Pointer to an LogicalDisplayRow object + void _rowFormatter(); + + /// @brief Extract colour codes from text + void _setColours(); + + /// @brief Set the line attribute for this row + void _setLine(); + + /// @brief Set the underline attribute for this row + /// @param underline true|false + void _setUnderline(); + + /// @brief Check for "~~Always ticker this text" + /// @return true|false + void _alwaysTicker(); + + /// @brief Check for "!~Never ticker this text" + /// @return true|false + void _neverTicker(); + + /// @brief Remove leading formatting characters from the provided char array + /// @param text Pointer to the char array + /// @param size Number of leading formatting characters to remove + void _removeFormatters(uint8_t size); +}; +#endif \ No newline at end of file diff --git a/Old/MCUFriendScreen.cpp.old b/Old/MCUFriendScreen.cpp.old new file mode 100644 index 0000000..8a08fa1 --- /dev/null +++ b/Old/MCUFriendScreen.cpp.old @@ -0,0 +1,109 @@ +#include "Defines.h" +#ifdef NEEDS_MCU + +#include "MCUFriendScreen.h" + +MCUFriendScreen::MCUFriendScreen() : PhysicalScreen() { + _tft = new MCUFRIEND_kbv(); +} + +PhysicalScreen *MCUFriendScreen::setupScreen(uint8_t rotation, uint8_t textSize, uint16_t backgroundColour) { + + const GFXfont *gfxFont = TEXT_FONT; + + uint16_t screenId = _tft->readID(); + CONSOLE.print("TFT ID: 0x"); + CONSOLE.println(screenId, HEX); + if (screenId == 0xD3D3) { + screenId = 0x9486; + } + if (screenId == 0x0) { + CONSOLE.println(F("NO SCREEN DODGY FORCE TO 0x9486")); + screenId = 0x9341; + } + _tft->begin(screenId); + _tft->setRotation(rotation); + _tft->setFont(gfxFont); + _tft->setTextSize(textSize); + _tft->setTextWrap(false); +#ifdef INVERT_SCREEN + _tft->invertDisplay(screenId); +#endif + _tft->fillScreen(backgroundColour); + _fontHeight = gfxFont->yAdvance; + _fontWidth = getCharacterWidth("A"); + _maxRows = _tft->height() / _fontHeight; + _maxRowLength = _tft->width() / _fontWidth; + CONSOLE.println(F("\n~~ SETUP PARAMETERS ~~")); + CONSOLE.print(F("MCUFRIEND_kbv parameters: _tft->height()|_tft->width(): ")); + CONSOLE.print(_tft->height()); + CONSOLE.print(F("|")); + CONSOLE.println(_tft->width()); + CONSOLE.print(F("_maxRows|_maxRowLength|_fontHeight|_fontWidth: ")); + CONSOLE.print(_maxRows); + CONSOLE.print(F("|")); + CONSOLE.print(_maxRowLength); + CONSOLE.print(F("|")); + CONSOLE.print(_fontHeight); + CONSOLE.print(F("|")); + CONSOLE.println(_fontWidth); + CONSOLE.println(F("~~ END SETUP PARAMETERS ~~\n")); + return this; +} + +uint8_t MCUFriendScreen::getCharacterWidth(const char *character) { + int16_t x1, y1; + uint16_t w, h; + _tft->getTextBounds(character, 0, 0, &x1, &y1, &w, &h); + return w; +} + +void MCUFriendScreen::clearScreen(uint16_t backgroundColour) { _tft->fillScreen(backgroundColour); } + +void MCUFriendScreen::clearRow(uint8_t row, uint16_t backgroundColour) { + uint16_t textRow = ((row + 1) * _fontHeight); + int32_t x = 0; + int32_t y = ((textRow - _fontHeight) + 10); + int32_t w = _fontWidth * _maxRowLength; + int32_t h = _fontHeight; + _tft->fillRect(x, y, w, h, backgroundColour); +} + +void MCUFriendScreen::writeRow(uint8_t row, uint8_t column, uint16_t fontColour, uint16_t backgroundColour, + uint8_t maxLength, char *message, bool underlined) { + uint16_t textRow = ((row + 1) * _fontHeight); + uint16_t width = _fontWidth * maxLength; + _tft->fillRect(1, ((textRow - _fontHeight) + 10), _tft->width(), (_fontHeight), backgroundColour); + _tft->setTextColor(fontColour, backgroundColour); + _tft->setCursor(1, textRow); + _tft->print(message); + if (underlined) { + _tft->drawLine(column, textRow + _fontHeight, width, textRow + _fontHeight, fontColour); + } + // CONSOLE.println(F("\n~~ WRITE ROW PARAMETERS ~~")); + // CONSOLE.print(F("textRow|width: ")); + // CONSOLE.print(textRow); + // CONSOLE.print(F("|")); + // CONSOLE.println(width); + // CONSOLE.println(F("~~ END WRITE ROW PARAMETERS ~~\n")); + CONSOLE.print(F("Printing row: ")); + CONSOLE.println(message); +} + +void MCUFriendScreen::writeLine(uint8_t row, uint8_t column, uint8_t lineLength, uint16_t lineColour, + uint16_t backgroundColour) { + // Horizontal start/end + int32_t x1 = column; + int32_t x2 = _fontWidth * lineLength; + // Vertical start - half way up the font height + int32_t y1 = (row * _fontHeight) + row + (_fontHeight / 2); + int32_t y2 = y1; + clearRow(row, backgroundColour); + _tft->drawLine(x1, y1, x2, y2, lineColour); +} + +uint16_t MCUFriendScreen::getHeight() { return _tft->height(); } + +uint16_t MCUFriendScreen::getWidth() { return _tft->width(); } + +#endif diff --git a/Old/MCUFriendScreen.h.old b/Old/MCUFriendScreen.h.old new file mode 100644 index 0000000..a2a6565 --- /dev/null +++ b/Old/MCUFriendScreen.h.old @@ -0,0 +1,39 @@ +#include "Defines.h" +#ifdef NEEDS_MCU + +#ifndef MCUFRIENDSCREEN_H +#define MCUFRIENDSCREEN_H + +#include "PhysicalScreen.h" +#include +#include +#include + +class MCUFriendScreen : public PhysicalScreen { +public: + MCUFriendScreen(); + + virtual PhysicalScreen *setupScreen(uint8_t rotation, uint8_t textSize, uint16_t backgroundColour) override; + + virtual void clearScreen(uint16_t backgroundColour) override; + + virtual void clearRow(uint8_t row, uint16_t backgroundColour) override; + + virtual void writeRow(uint8_t row, uint8_t column, uint16_t fontColour, uint16_t backgroundColour, uint8_t maxLength, + char *message, bool underlined) override; + + virtual void writeLine(uint8_t row, uint8_t column, uint8_t lineLength, uint16_t lineColour, + uint16_t backgroundColour) override; + + virtual uint16_t getHeight() override; + + virtual uint16_t getWidth() override; + + uint8_t getCharacterWidth(const char *character); + +private: + MCUFRIEND_kbv *_tft; +}; + +#endif +#endif diff --git a/Old/OLEDScreen.cpp.old b/Old/OLEDScreen.cpp.old new file mode 100644 index 0000000..1d41db4 --- /dev/null +++ b/Old/OLEDScreen.cpp.old @@ -0,0 +1,92 @@ +#include "Defines.h" +#ifdef NEEDS_OLED +#include "OLEDScreen.h" + +OLEDScreen::OLEDScreen(uint8_t screenWidth, uint8_t screenHeight, uint8_t deviceAddress, uint8_t muxAddress, + uint8_t subBus) + : PhysicalScreen(), _screenWidth(screenWidth), _screenHeight(screenHeight), _deviceAddress(deviceAddress), + _muxAddress(muxAddress), _subBus(subBus) { + if ((_screenHeight != 32 && _screenHeight != 64) || (_screenWidth != 128 && _screenHeight != 132)) { + CONSOLE.println(F("Invalid screen width/height specified")); + return; + } + _oled = new SSD1306AsciiWire; + _textSize = 1; +} + +PhysicalScreen *OLEDScreen::setupScreen(uint8_t rotation, uint8_t textSize, uint16_t backgroundColour) { + _switchMUX(); + if (_screenWidth == 132 && _screenHeight == 64) { + _oled->begin(&SH1106_128x64, _deviceAddress); + _screenWidth = 128; + } else if (_screenWidth == 128 && _screenHeight == 64) { + _oled->begin(&Adafruit128x64, _deviceAddress); + } else if (_screenWidth == 128 && _screenHeight == 32) { + _oled->begin(&Adafruit128x32, _deviceAddress); + } + if (rotation == 1) { + _oled->displayRemap(true); + } + _oled->setFont(TEXT_FONT); + if (textSize == 2) { + _textSize = 2; + _oled->set2X(); + } else { + _oled->set1X(); + } + _oled->clear(); + // _fontHeight = _oled->fontHeight(); + _fontHeight = 8 * _textSize; // This is pretty much hard coded by the library for row calculations + _fontWidth = _oled->fontWidth(); + _maxRows = _oled->displayHeight() / _fontHeight; + _maxRowLength = _oled->displayWidth() / _fontWidth; + CONSOLE.print(F("Setup done: _fontHeight|_fontWidth|_oled->Height|_oled->Width: ")); + CONSOLE.print(_fontHeight); + CONSOLE.print(F("|")); + CONSOLE.print(_fontWidth); + CONSOLE.print(F("|")); + CONSOLE.print(_oled->displayHeight()); + CONSOLE.print(F("|")); + CONSOLE.println(_oled->displayWidth()); + return this; +} + +void OLEDScreen::clearScreen(uint16_t backgroundColour) { + _switchMUX(); + _oled->clear(); +} + +void OLEDScreen::clearRow(uint8_t row, uint16_t backgroundColour) { + _switchMUX(); + _oled->setCursor(0, row * _textSize); + _oled->clearToEOL(); +} + +void OLEDScreen::writeRow(uint8_t row, uint8_t column, uint16_t fontColour, uint16_t backgroundColour, + uint8_t maxLength, char *message, bool underlined) { + _switchMUX(); + _oled->setCursor(column, row * _textSize); + _oled->print(message); + CONSOLE.print(F("\nwriteRow textRow|message: ")); + CONSOLE.print(row); + CONSOLE.print(F("|")); + CONSOLE.println(message); +} + +void OLEDScreen::writeLine(uint8_t row, uint8_t column, uint8_t lineLength, uint16_t lineColour, + uint16_t backgroundColour) {} + +uint16_t OLEDScreen::getHeight() { return _oled->displayHeight(); } + +uint16_t OLEDScreen::getWidth() { return _oled->displayWidth(); } + +void OLEDScreen::_switchMUX() { + if (_muxAddress == 0 || _subBus == 255) { + return; + } + Wire.beginTransmission(_muxAddress); + Wire.write(_subBus); + Wire.endTransmission(); +} + +#endif diff --git a/Old/OLEDScreen.h.old b/Old/OLEDScreen.h.old new file mode 100644 index 0000000..ce17bce --- /dev/null +++ b/Old/OLEDScreen.h.old @@ -0,0 +1,58 @@ +#include "Defines.h" +#ifdef NEEDS_OLED + +#ifndef OLEDSCREEN_H +#define OLEDSCREEN_H + +#include "PhysicalScreen.h" +#include +#include +#include +#include + +/// @brief Class to drive monochrome OLEDs with EX-Display, noting all colours are ignored +class OLEDScreen : public PhysicalScreen { +public: + /// @brief Constructor for an OLEDScreen instance + /// @param screenWidth Width of the OLED in pixels, must be either 128 (SSD1306) or 132 (SH1106) + /// @param screenHeight Height of the OLED in pixels, must be either 32 or 64 + /// @param deviceAddress (Optional - default 0x3C) I2C address of the OLED + /// @param muxAddress (Optional) Address of a multiplexor the OLED is behind if appropriate + /// @param subBus (Optional) Sub bus ID of a multiplexor the OLED is on if appropriate + OLEDScreen(uint8_t screenWidth, uint8_t screenHeight, uint8_t deviceAddress = 0x3C, uint8_t muxAddress = 0, + uint8_t subBus = 255); + + virtual PhysicalScreen *setupScreen(uint8_t rotation, uint8_t textSize, uint16_t backgroundColour) override; + + virtual void clearScreen(uint16_t backgroundColour) override; + + virtual void clearRow(uint8_t row, uint16_t backgroundColour) override; + + virtual void writeRow(uint8_t row, uint8_t column, uint16_t fontColour, uint16_t backgroundColour, uint8_t maxLength, + char *message, bool underlined) override; + + virtual void writeLine(uint8_t row, uint8_t column, uint8_t lineLength, uint16_t lineColour, + uint16_t backgroundColour) override; + + virtual uint16_t getHeight() override; + + virtual uint16_t getWidth() override; + +private: + SSD1306AsciiWire *_oled; + uint8_t _screenWidth; + uint8_t _screenHeight; + uint8_t _deviceAddress; + uint8_t _muxAddress; + uint8_t _subBus; + uint8_t _textSize; + + /// @brief Private method to select the correct MUX and sub bus if required for this screen + /// @param muxAddress Valid MUX address, 0x70 - 0x77 + /// @param subBus Valid MUX sub bus, 0 - 7 + void _switchMUX(); +}; + +#endif + +#endif diff --git a/Old/PhysicalScreen.cpp.old b/Old/PhysicalScreen.cpp.old new file mode 100644 index 0000000..88efb6c --- /dev/null +++ b/Old/PhysicalScreen.cpp.old @@ -0,0 +1,158 @@ +#include "Defines.h" +#include "PhysicalScreen.h" +#include + +PhysicalScreen *PhysicalScreen::_first = nullptr; +uint8_t PhysicalScreen::_screenCount = 0; + +PhysicalScreen::PhysicalScreen() { + _firstDisplay = nullptr; + _activeDisplay = nullptr; + _displayCount = 0; + _lastSwitchTime = 0; + _maxRows = 0; + _maxRowLength = 0; + _fontHeight = 0; + _fontWidth = 0; + _screenNumber = _screenCount++; + if (!_first) { + _first = this; + _next = nullptr; + } else { + for (PhysicalScreen *screen = PhysicalScreen::getFirst(); screen; screen = screen->_next) { + if (!screen->_next) { + screen->_next = this; + } + } + } +} + +PhysicalScreen *PhysicalScreen::getFirst() { return _first; } + +PhysicalScreen *PhysicalScreen::getNext() { return _next; } + +PhysicalScreen *PhysicalScreen::addDisplay(uint8_t displayNumber, uint16_t defaultTextColour, uint16_t defaultBackgroundColour) { + // Create a new display + LogicalDisplay *display = + new LogicalDisplay(displayNumber, _maxRowLength, _maxRows, defaultTextColour, defaultBackgroundColour); + + // Find previous display + LogicalDisplay *previous = nullptr; + for (auto peek = _firstDisplay; peek; peek = peek->getNext()) { + if (peek->getDisplayNumber() > displayNumber) { + break; + } + previous = peek; + } + if (previous) { + // If there's a previous display, chain after it + display->setNext(previous->getNext()); + previous->setNext(display); + } else { + // Put at start of list + display->setNext(_firstDisplay); + _firstDisplay = display; + } + if (!_activeDisplay) { + _activeDisplay = display; + } + _displayCount++; + return this; +} + +LogicalDisplay *PhysicalScreen::getFirstDisplay() { return _firstDisplay; } + +void PhysicalScreen::setActiveDisplay(LogicalDisplay *activeDisplay) { _activeDisplay = activeDisplay; } + +LogicalDisplay *PhysicalScreen::getActiveDisplay() { return _activeDisplay; } + +bool PhysicalScreen::displayNumberExists(uint8_t displayNumber) { return getDisplayByNumber(displayNumber) != nullptr; } + +LogicalDisplay *PhysicalScreen::getDisplayByNumber(uint8_t displayNumber) { + for (auto *display = _firstDisplay; display; display = display->getNext()) { + if (display->getDisplayNumber() == displayNumber) { + return display; + } + } + return nullptr; +} + +void PhysicalScreen::switchToNextDisplay() { + if (!_activeDisplay) { + _activeDisplay = _firstDisplay; + _activeDisplay->setRedraw(true); + return; + } + if (_displayCount == 1) { + return; + } + if (_activeDisplay->getNext()) { + _activeDisplay = _activeDisplay->getNext(); + } else { + _activeDisplay = _firstDisplay; + } + _activeDisplay->setRedraw(true); +} + +void PhysicalScreen::switchToPreviousDisplay() { + if (!_activeDisplay) { + _activeDisplay = _firstDisplay; + _activeDisplay->setRedraw(true); + return; + } + if (_displayCount == 1) { + return; + } + LogicalDisplay *last = nullptr; + for (LogicalDisplay *display = _firstDisplay; display; display = display->getNext()) { + if (!display->getNext()) { + last = display; + break; + } + } + if (_activeDisplay == _firstDisplay) { + _activeDisplay = last; + _activeDisplay->setRedraw(true); + return; + } + for (LogicalDisplay *display = _firstDisplay; display; display = display->getNext()) { + if (display->getNext() == _activeDisplay) { + _activeDisplay = display; + _activeDisplay->setRedraw(true); + return; + } + } +} + +void PhysicalScreen::autoSwitch(unsigned long switchDelay) { + if (millis() - _lastSwitchTime > switchDelay) { + _lastSwitchTime = millis(); + switchToNextDisplay(); + } +} + +void PhysicalScreen::processActiveDisplay() { + if (!_activeDisplay) + return; + if (_activeDisplay->needsRedraw()) { + clearScreen(_activeDisplay->getDefaultBackgroundColour()); + } + for (LogicalDisplayRow *row = _activeDisplay->getFirstRow(); row; row = row->getNext()) { + if (row->needsRender() && (row->isChanged() || _activeDisplay->needsRedraw())) { + if (row->isLine()) { + writeLine(row->getDisplayRow(), 0, _activeDisplay->getMaxRowLength(), row->getTextColour(), + row->getBackgroundColour()); + } else { + writeRow(row->getDisplayRow(), 0, row->getTextColour(), row->getBackgroundColour(), + _activeDisplay->getMaxRowLength(), row->getRowText(), row->isUnderlined()); + } + } + } + _activeDisplay->setRedraw(false); +} + +uint8_t PhysicalScreen::getScreenNumber() { return _screenNumber; } + +uint8_t PhysicalScreen::getMaxRows() { return _maxRows; } + +uint8_t PhysicalScreen::getMaxRowLength() { return _maxRowLength; } diff --git a/Old/PhysicalScreen.h.old b/Old/PhysicalScreen.h.old new file mode 100644 index 0000000..937149a --- /dev/null +++ b/Old/PhysicalScreen.h.old @@ -0,0 +1,138 @@ +#ifndef PHYSICALSCREEN_H +#define PHYSICALSCREEN_H + +#include "Defines.h" +#include "LogicalDisplay.h" +#include + +/// @brief This PhysicalScreen class is designed to be extended by other screen classes that define the methods to +/// perform the requesite activities according to the library in use to drive the physical screen. This allows all +/// physical screen methods to be consistent regardless of the library or driver in use, allowing this code to support +/// multiple different screen types without needing to have the main functions call different libraries/methods. Every +/// virtual method must be declared and defined in the class that extends this. +class PhysicalScreen { +public: + /// @brief Constructor for a new PhysicalScreen instance + PhysicalScreen(); + + /// @brief Virtual function to implement to setup the physical screen parameters + /// @param rotation Rotate the screen if necessary + /// @param textSize Pixel multiplier to increase text size if desired + /// @param backgroundColour Fill the screen with this colour during setup + /// @return Pointer to the current PhysicalScreen object to enable pipelining + virtual PhysicalScreen *setupScreen(uint8_t rotation, uint8_t textSize, uint16_t backgroundColour) = 0; + + /// @brief Virtual function to implement to clear the entire screen + /// @param backgroundColour Valid colour to set the entire screen to + virtual void clearScreen(uint16_t backgroundColour) = 0; + + /// @brief Virtual function to implement to clear the specified row + /// @param row Row number to clear, 0 - 255 + /// @param backgroundColour Valid colour to set the row to + virtual void clearRow(uint8_t row, uint16_t backgroundColour) = 0; + + /// @brief Virtual function to implement to write a row of text to the physical screen + /// @param row Row on screen, 0 - 255 (not pixels) + /// @param column Column on screen, 0 - 255 (not pixels) + /// @param fontColour Valid colour for the text + /// @param backgroundColour Valid colour for the background + /// @param maxLength Maximum number of columns (not pixels) that can fit on the screen + /// @param message Char array containing the text to display + virtual void writeRow(uint8_t row, uint8_t column, uint16_t fontColour, uint16_t backgroundColour, uint8_t maxLength, + char *message, bool underlined) = 0; + + /// @brief Virtual function to implement to write a horizontal line on the specified row + /// @param row Row on screen, 0 - 255 (not pixels) + /// @param column Column to start the line, 0 - 255 (not pixels) + /// @param lineWidth Pixel width to make the line + /// @param lineLength Number of columns for the line (not pixels), 0 - 255 + /// @param lineColour Valid colour for the line + /// @param backgroundColour Valid colour for the background + virtual void writeLine(uint8_t row, uint8_t column, uint8_t lineLength, uint16_t lineColour, + uint16_t backgroundColour) = 0; + + /// @brief Get the physical screen height + /// @return Screen height in pixels + virtual uint16_t getHeight() = 0; + + /// @brief Get they physical screen width + /// @return Screen width in pixels + virtual uint16_t getWidth() = 0; + + /// @brief Get the first screen + /// @return Pointer to the first PhysicalScreen instance + static PhysicalScreen *getFirst(); + + /// @brief Get the next screen in the list + /// @return Pointer to the next PhysicalScreen instance + PhysicalScreen *getNext(); + + /// @brief Add a new LogicalDisplay instance to be displayed on this screen, ordered by display number + /// @param displayNumber Display ID for the instance + /// @return Pointer to the current PhysicalScreen instance to enable pipelining of multiple display creations + PhysicalScreen *addDisplay(uint8_t displayNumber, uint16_t defaultTextColour, uint16_t defaultBackgroundColour); + + /// @brief Get the first LogicalDisplay instance in the linked list + /// @return Pointer to the first LogicalDisplay instance + LogicalDisplay *getFirstDisplay(); + + /// @brief Set the active LogicalDisplay instance to display on this screen + /// @param activeDisplay Pointer to the LogicalDisplay instance to mark as active + void setActiveDisplay(LogicalDisplay *activeDisplay); + + /// @brief Get the active LogicalDisplay instance that should display on this screen + /// @return Pointer to the active LogicalDisplay instance + LogicalDisplay *getActiveDisplay(); + + /// @brief Check if there is already a display created at the specified number + /// @param displayNumber True|False + /// @return + bool displayNumberExists(uint8_t displayNumber); + + /// @brief Get the display and associated row linked list for the specified display number + /// @param displayNumber Display number to get, 0 - 255 + /// @return Pointer to a LogicalDisplay instance, or nullptr if not exists + LogicalDisplay *getDisplayByNumber(uint8_t displayNumber); + + /// @brief Switch active display to the next display in the linked list + void switchToNextDisplay(); + + /// @brief Switch active display to the previous display in the linked list + void switchToPreviousDisplay(); + + /// @brief Call this method as often as possible to support timed display switching + /// @param switchDelay Time in milliseconds before switching displays + void autoSwitch(unsigned long switchDelay); + + /// @brief Call this method as often as possible to ensure this physical screen is updated correctly + void processActiveDisplay(); + + /// @brief Get this screen instance's number + /// @return 0 - 255 + uint8_t getScreenNumber(); + + /// @brief Get the maximum number of rows that will fit on this screen + /// @return 0 - 255 + uint8_t getMaxRows(); + + /// @brief Get the maximum number of characters that will fit on this screen + /// @return 0 - 255; + uint8_t getMaxRowLength(); + +protected: + PhysicalScreen *_next; // Pointer to the next screen instance + LogicalDisplay *_firstDisplay; // Pointer to the first associated logical display + LogicalDisplay *_activeDisplay; // Current logical display this physical screen needs to show + uint8_t _displayCount; // Number of logical displays associated with this screen + unsigned long _lastSwitchTime; // Last time an auto switch to the next display instance occurred + uint8_t _maxRows; // Calculated maximum number of rows that will fit on screen based on font size + uint8_t _maxRowLength; // Calculated maximum number of characters that will fit on screen based on font size + uint8_t _fontHeight; // Calculated height of the font to determine row count + uint8_t _fontWidth; // Calculated width of the font to determine row length + uint8_t _screenNumber; // Number of this screen + + static PhysicalScreen *_first; // Pointer to the first screen instance + static uint8_t _screenCount; // Count of screen instances +}; + +#endif diff --git a/Old/PushButton.cpp.old b/Old/PushButton.cpp.old new file mode 100644 index 0000000..632f351 --- /dev/null +++ b/Old/PushButton.cpp.old @@ -0,0 +1,28 @@ +#include "Defines.h" + +#if defined(USE_BUTTONS) + +#include "PushButton.h" + +PushButton::PushButton(uint8_t leftPin, uint8_t rightPin, uint8_t centrePin, uint8_t upPin, uint8_t downPin) + : InputMethod() { + _buttons[LeftButton].pin = leftPin; + _buttons[RightButton].pin = rightPin; + _buttons[CentreButton].pin = centrePin; + _buttons[UpButton].pin = upPin; + _buttons[DownButton].pin = downPin; +} + +void PushButton::begin() { + pinMode(_buttons[LeftButton].pin, INPUT_PULLUP); + pinMode(_buttons[RightButton].pin, INPUT_PULLUP); + pinMode(_buttons[CentreButton].pin, INPUT_PULLUP); + pinMode(_buttons[UpButton].pin, INPUT_PULLUP); + pinMode(_buttons[DownButton].pin, INPUT_PULLUP); +} + +bool PushButton::_readRawInput(ButtonName button) { + return digitalRead(_buttons[button].pin) == LOW; +} + +#endif diff --git a/Old/PushButton.h.old b/Old/PushButton.h.old new file mode 100644 index 0000000..a244215 --- /dev/null +++ b/Old/PushButton.h.old @@ -0,0 +1,23 @@ +#ifndef PUSHBUTTON_H +#define PUSHBUTTON_H + +#include "Defines.h" + +#if defined(USE_BUTTONS) +#include "InputMethod.h" +#include + +class PushButton : public InputMethod { +public: + PushButton(uint8_t leftPin, uint8_t rightPin, uint8_t centrePin, uint8_t upPin, uint8_t downPin); + + void begin() override; + +private: + bool _readRawInput(ButtonName button) override; + +}; + +#endif // USE_BUTTONS + +#endif diff --git a/Old/TFT_eSPIScreen.cpp.old b/Old/TFT_eSPIScreen.cpp.old new file mode 100644 index 0000000..498bc89 --- /dev/null +++ b/Old/TFT_eSPIScreen.cpp.old @@ -0,0 +1,71 @@ +#include "Defines.h" +#ifdef NEEDS_TFT + +#include "TFT_eSPIScreen.h" + +// TFT_eSPIScreen::TFT_eSPIScreen(TFT_eSPI &tft) : PhysicalScreen(), _tft(tft) {} +TFT_eSPIScreen::TFT_eSPIScreen() : PhysicalScreen() { _tft = new TFT_eSPI(); } + +PhysicalScreen *TFT_eSPIScreen::setupScreen(uint8_t rotation, uint8_t textSize, uint16_t backgroundColour) { + const GFXfont *gfxFont = TEXT_FONT; + _tft->init(); + _tft->setRotation(rotation); + _tft->fillScreen(backgroundColour); + _tft->setFreeFont(gfxFont); + _tft->setTextSize(textSize); + _fontHeight = gfxFont->yAdvance; + _fontWidth = _tft->textWidth("A"); + _maxRows = _tft->height() / _fontHeight; + _maxRowLength = _tft->width() / _fontWidth; + CONSOLE.print(F("Setup done: _fontHeight|_fontWidth|_tft->Height()|_tft->Width(): ")); + CONSOLE.print(_fontHeight); + CONSOLE.print(F("|")); + CONSOLE.print(_fontWidth); + CONSOLE.print(F("|")); + CONSOLE.print(_tft->height()); + CONSOLE.print(F("|")); + CONSOLE.println(_tft->width()); + return this; +} + +void TFT_eSPIScreen::clearScreen(uint16_t backgroundColour) { _tft->fillScreen(backgroundColour); } + +void TFT_eSPIScreen::clearRow(uint8_t row, uint16_t backgroundColour) { + int32_t x = 0; + int32_t y = (row * _fontHeight) + row; + int32_t w = _fontWidth * _maxRowLength; + int32_t h = _fontHeight; + _tft->fillRect(x, y, w, h, backgroundColour); +} + +void TFT_eSPIScreen::writeRow(uint8_t row, uint8_t column, uint16_t fontColour, uint16_t backgroundColour, + uint8_t maxLength, char *message, bool underlined) { + uint16_t y = (row * _fontHeight) + row; + uint16_t width = _fontWidth * maxLength; + _tft->setTextPadding(width); + _tft->setTextColor(fontColour, backgroundColour); + if (underlined) { + _tft->drawLine(column, y + _fontHeight, width, y + _fontHeight, fontColour); + } else { + _tft->drawLine(column, y + _fontHeight, width, y + _fontHeight, backgroundColour); + } + _tft->drawString(message, column, y); +} + +void TFT_eSPIScreen::writeLine(uint8_t row, uint8_t column, uint8_t lineLength, uint16_t lineColour, + uint16_t backgroundColour) { + // Horizontal start/end + int32_t x1 = column; + int32_t x2 = _fontWidth * lineLength; + // Vertical start - half way up the font height + int32_t y1 = (row * _fontHeight) + row + (_fontHeight / 2); + int32_t y2 = y1; + clearRow(row, backgroundColour); + _tft->drawLine(x1, y1, x2, y2, lineColour); +} + +uint16_t TFT_eSPIScreen::getHeight() { return _tft->height(); } + +uint16_t TFT_eSPIScreen::getWidth() { return _tft->width(); } + +#endif diff --git a/Old/TFT_eSPIScreen.h.old b/Old/TFT_eSPIScreen.h.old new file mode 100644 index 0000000..48fc144 --- /dev/null +++ b/Old/TFT_eSPIScreen.h.old @@ -0,0 +1,40 @@ +#include "Defines.h" +#ifdef NEEDS_TFT + +#ifndef TFT_ESPISCREEN_H +#define TFT_ESPISCREEN_H + +#include "PhysicalScreen.h" +#include +#include +#include + +class TFT_eSPIScreen : public PhysicalScreen { +public: + // TFT_eSPIScreen(TFT_eSPI &tft); + TFT_eSPIScreen(); + + virtual PhysicalScreen *setupScreen(uint8_t rotation, uint8_t textSize, uint16_t backgroundColour) override; + + virtual void clearScreen(uint16_t backgroundColour) override; + + virtual void clearRow(uint8_t row, uint16_t backgroundColour) override; + + virtual void writeRow(uint8_t row, uint8_t column, uint16_t fontColour, uint16_t backgroundColour, uint8_t maxLength, + char *message, bool underlined) override; + + virtual void writeLine(uint8_t row, uint8_t column, uint8_t lineLength, uint16_t lineColour, + uint16_t backgroundColour) override; + + virtual uint16_t getHeight() override; + + virtual uint16_t getWidth() override; + +private: + // TFT_eSPI &_tft; + TFT_eSPI *_tft; +}; + +#endif + +#endif diff --git a/Old/TFT_eSPITouch.cpp.old b/Old/TFT_eSPITouch.cpp.old new file mode 100644 index 0000000..36fbf2a --- /dev/null +++ b/Old/TFT_eSPITouch.cpp.old @@ -0,0 +1,96 @@ +#include "Defines.h" + +#if defined(NEEDS_TFT) && defined(USE_TOUCH) + +#include "TFT_eSPITouch.h" + +#ifdef USE_SPIFFS +const char _calibrationFileBase[] = "/calibrationData"; +#endif + +TFT_eSPITouch::TFT_eSPITouch(TFT_eSPI &tft) : InputMethod(), _tft(tft) { +#ifdef USE_SPIFFS + sprintf(_calibrationFile, "%s%d", _calibrationFileBase, _inputNumber); +#endif +} + +void TFT_eSPITouch::begin() { + if (!_screen) { + return; + } +#ifdef USE_SPIFFS + if (!SPIFFS.begin()) { + CONSOLE.println(F("Formatting file system")); + SPIFFS.format(); + SPIFFS.begin(); + } +#endif + if (!_setCalibration()) { + CONSOLE.println(F("Calibration data not valid")); + if (!_doCalibration()) { + _screen->clearScreen(TFT_BLACK); + _screen->writeRow(0, 0, TFT_WHITE, TFT_RED, _screen->getMaxRowLength(), "ERROR", true); + _screen->writeRow(1, 0, TFT_WHITE, TFT_BLACK, _screen->getMaxRowLength(), "Could not save calibration data", + false); + CONSOLE.println(F("ERROR: Could not save calibration data")); + delay(3000); + _screen->clearScreen(TFT_BLACK); + } else { + _screen->clearScreen(TFT_BLACK); + } + } else { + CONSOLE.println(F("Calibration data valid")); + _screen->clearScreen(TFT_BLACK); + } + _calculateButtons(); +} + +bool TFT_eSPITouch::_setCalibration() { +#ifdef FORCE_CALIBRATION + return false; +#endif + uint16_t calibrationData[5]; +#ifdef USE_SPIFFS + File f = SPIFFS.open(_calibrationFile, "r"); + if (!f) + return false; + bool dataValid = false; + if ((f.readBytes((char *)calibrationData, 14)) == 14) { + dataValid = true; + f.close(); + } + if (!dataValid) + return false; +#elif USE_EEPROM + return false; +#elif USE_FLASH + return false; +#endif + _tft.setTouch(calibrationData); + return true; +} + +bool TFT_eSPITouch::_doCalibration() { + uint16_t calibrationData[5]; + _tft.calibrateTouch(calibrationData, TFT_WHITE, TFT_RED, 15); + File f = SPIFFS.open(_calibrationFile, "w"); + if (f) { + f.write((const unsigned char *)calibrationData, 14); + f.close(); + return true; + } + return false; +} + +bool TFT_eSPITouch::_readRawInput(ButtonName button) { + uint16_t touchX, touchY; + if (_tft.getTouch(&touchX, &touchY)) { + if (touchX >= _buttons[button].xStart && touchX <= _buttons[button].xEnd && touchY >= _buttons[button].yStart && + touchY <= _buttons[button].yEnd) { + return true; + } + } + return false; +} + +#endif diff --git a/Old/TFT_eSPITouch.h.old b/Old/TFT_eSPITouch.h.old new file mode 100644 index 0000000..56898b7 --- /dev/null +++ b/Old/TFT_eSPITouch.h.old @@ -0,0 +1,35 @@ +#ifndef TFT_ESPITOUCH_H +#define TFT_ESPITOUCH_H + +#include "Defines.h" + +#if defined(NEEDS_TFT) && defined(USE_TOUCH) +#ifdef USE_SPIFFS +#include +#endif +#include "InputMethod.h" +#include +#include + +class TFT_eSPITouch : public InputMethod { +public: + /// @brief Constructor for the TFT_eSPITouch instance + /// @param tftTouch Reference to an existing TFT_eSPI instance + TFT_eSPITouch(TFT_eSPI &tft); + + void begin() override; + +private: + TFT_eSPI &_tft; + +#ifdef USE_SPIFFS + char _calibrationFile[20]; +#endif + bool _setCalibration(); + bool _doCalibration(); + bool _readRawInput(ButtonName button) override; +}; + +#endif // NEEDS_TFT and USE_TOUCH + +#endif diff --git a/Old/config.example.h.old b/Old/config.example.h.old new file mode 100644 index 0000000..4f1df50 --- /dev/null +++ b/Old/config.example.h.old @@ -0,0 +1,112 @@ +#ifndef CONFIG_H +#define CONFIG_H + +// This is the configuration file for EX-Display +// Entries here will control how the screen display will operate + +// Define the pphysical screen types in use here - this needs to be ultra simplified at some point +// Should we eventually support up to 3 displays? +#define SCREEN_TYPE MCU +// #define SCREEN_TYPE TFT +// #define SCREEN_TYPE OLED + +// #define OLED_ADDRESS 0x3C + +// Define the number of logical displays here +// This is not a good way to do this +#define NUMBER_OF_DISPLAYS 1 +#define DISPLAY_1_ID 0 +// #define DISPLAY_2_ID 3 +// #define DISPLAY_3_ID 8 + +// DO NOT REMOVE OR MOVE THIS LINE - MUST BE BEFORE FONT DEFINITION +// Include the various fonts and font colours available for use with EX-Display +// Note for monochrome OLEDs you will need to set the OLED colour option here +#include "FontOptions.h" +// Font options +#define TEXT_FONT MEDIUM +#define TEXT_COLOUR WHITE +// #define TEXT_COLOUR OLED_WHITE +#define BACKGROUND_COLOUR BLACK +// #define BACKGROUND_COLOUR OLED_BLACK + +// Set the pixel multiplier for text - this will multiple a single pixel by this number +// For OLED, this can only be 1 or 2 +#define TEXT_SIZE 1 + +// If rotation of the screen is required, set it here +// TFT type screens typically require 1 or 3 to rotate from portrait to landscape +// Set to 0 for an OLED screen or 1 to rotate 180 degrees +// #define SCREEN_ROTATION 0 +#define SCREEN_ROTATION 1 +// #define SCREEN_ROTATION 2 +// #define SCREEN_ROTATION 3 + +// Some screens need inverting. If your screen displays inverted colours uncomment the following line. +//#define INVERT_SCREEN + +// First entries about the display you are using +// as we display across the wider part of the screen iwdth is the larger dimension +// These should only be required if using OLED screen type +// #define SCREEN_WIDTH 128 +// #define SCREEN_HEIGHT 64 + +// If a touch screen is in use a touch will swap from screen to screen +// if in use uncomment the following +// #define USE_TOUCH + +// If you have a need force recalibrating the touch screen, uncomment this line +// #define FORCE_CALIBRATION + +// If using an MCUFRIEND_kbv type screen it requires the Adafruit TouchScreen driver +// You must define the below parameters for this scenario +// If using an MCUFRIEND_kbv type screen it requires the Adafruit TouchScreen driver +// You must define the below parameters for this scenario +// Example pins for Uno/Mega2560 +const int XP = 9, XM = A3, YP = A2, YM = 8; // 320x480 ID=0x9488 + +// Example pins for ESP32 WROOM +// const int XP=32,XM=2,YP=4,YM=35; //320x480 ID=0x9488 + +// Also if using MCUFRIEND_kbv with the Adafruit TouchScreen driver you must define the below +// parameters for calibration +const int TS_LEFT = 114, TS_RT = 917, TS_TOP = 74, TS_BOT = 931; + +// If using push buttons rather than a touch screen, comment out USE_TOUCH above and uncomment the below +// Also efine the GPIO pin used by each push button +// #define USE_BUTTONS + +// Example pins for Mega2560 +// #define LEFT_BUTTON 22 +// #define RIGHT_BUTTON 23 +// #define CENTRE_BUTTON 24 +// #define UP_BUTTON 25 +// #define DOWN_BUTTON 26 + +// Example pins for ESP32 WROOM +// #define LEFT_BUTTON 32 +// #define RIGHT_BUTTON 33 +// #define CENTRE_BUTTON 25 +// #define UP_BUTTON 26 +// #define DOWN_BUTTON 27 + +// if the touch screen not in use, Now define the scroll requirements +#define SCROLLTIME 1000 // 5 seconds + +// If hosting multiple displays on the same screen, set switching delay +#define DISPLAY_SWITCH_TIME 5000 + +// And now define the maximum text length. +#define MAX_LINE_LENGTH 50 + +// The startup Timelapse - allows time to handle all the CS output +#define STARTUP_TIME 2000 + +// Uncomment this line to display debug output +#define DEBUG + +// Uncomment this line when wanting to test with serial in put only, and no CS connection +// This overrides the input and only accepts input from the serial console +// #define SERIAL_ONLY + +#endif \ No newline at end of file diff --git a/Old/platformio.ini.old b/Old/platformio.ini.old new file mode 100644 index 0000000..f0cd830 --- /dev/null +++ b/Old/platformio.ini.old @@ -0,0 +1,173 @@ +; 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 + +[platformio] +default_envs = + mega2560 + uno + ESP32 + Nucleo-F411RE + Nucleo-F446RE +src_dir = . +include_dir = . + +[env] +build_flags = + -Wall + -std=c++17 +lib_deps = + prenticedavid/MCUFRIEND_kbv@^3.1.0-Beta + adafruit/Adafruit GFX Library@^1.11.9 + bodmer/TFT_eSPI + adafruit/Adafruit TouchScreen@^1.1.5 + greiman/SSD1306Ascii@^1.3.5 + +[env:mega2560] +platform = atmelavr +board = megaatmega2560 +framework = arduino +lib_deps = + ${env.lib_deps} +build_flags = + ${env.build_flags} +monitor_speed = 115200 +monitor_echo = yes + +[env:uno] +platform = atmelavr +board = uno +framework = arduino +lib_deps = + ${env.lib_deps} +build_flags = + ${env.build_flags} + -D SERIAL_RX_BUFFER_SIZE=256 +monitor_speed = 115200 +monitor_echo = yes + +[env:ESP32] +platform = espressif32 +board = esp32dev +framework = arduino +lib_deps = + ${env.lib_deps} +build_flags = + ${env.build_flags} + -DUSER_SETUP_LOADED=1 + -DST7796_DRIVER=1 + -DTFT_SDA_READ=1 + -DTFT_BL=5 + -DTFT_MISO=19 + -DTFT_MOSI=23 + -DTFT_SCLK=18 + -DTFT_CS=15 + -DTFT_DC=2 + -DTFT_RST=4 + -DTOUCH_CS=22 + -DLOAD_GLCD=1 + -DLOAD_FONT2=1 + -DLOAD_FONT4=1 + -DLOAD_FONT6=1 + -DLOAD_FONT7=1 + -DLOAD_FONT8=1 + -DLOAD_GFXFF=1 + -DSMOOTH_FONT=1 + -DSPI_FREQUENCY=28000000 + -DSPI_TOUCH_FREQUENCY=2500000 +monitor_speed = 115200 +monitor_echo = yes + +[env:ESP32-RPi] +platform = espressif32 +board = esp32dev +framework = arduino +lib_deps = + ${env.lib_deps} +build_flags = + ${env.build_flags} + -DUSER_SETUP_LOADED=1 + -DRPI_DISPLAY_TYPE=1 + -DILI9486_DRIVER=1 + -DTFT_MISO=23 + -DTFT_MOSI=19 + -DTFT_SCLK=18 + -DTFT_CS=15 + -DTFT_DC=2 + -DTFT_RST=4 + -DTOUCH_CS=21 + -DLOAD_GLCD=1 + -DLOAD_FONT2=1 + -DLOAD_FONT4=1 + -DLOAD_FONT6=1 + -DLOAD_FONT7=1 + -DLOAD_FONT8=1 + -DLOAD_GFXFF=1 + -DSMOOTH_FONT=1 + -DSPI_FREQUENCY=26000000 + -DSPI_TOUCH_FREQUENCY=2500000 +monitor_speed = 115200 +monitor_echo = yes + +[env:Wemos_D1_Mini32] +platform = espressif32 +board = wemos_d1_mini32 +framework = arduino +lib_deps = + ${env.lib_deps} +build_flags = + ${env.build_flags} + -DUSER_SETUP_LOADED=1 + -DRPI_DISPLAY_TYPE=1 + -DILI9486_DRIVER=1 + -DTFT_MISO=23 + -DTFT_MOSI=19 + -DTFT_SCLK=18 + -DTFT_CS=15 + -DTFT_DC=2 + -DTFT_RST=4 + -DTOUCH_CS=22 + -DLOAD_GLCD=1 + -DLOAD_FONT2=1 + -DLOAD_FONT4=1 + -DLOAD_FONT6=1 + -DLOAD_FONT7=1 + -DLOAD_FONT8=1 + -DLOAD_GFXFF=1 + -DSMOOTH_FONT=1 + -DSPI_FREQUENCY=26000000 + -DSPI_TOUCH_FREQUENCY=2500000 +monitor_speed = 115200 +monitor_echo = yes + +[env:Nucleo-F446RE] +platform = ststm32 +board = nucleo_f446re +framework = arduino +lib_deps = + ${env.lib_deps} +upload_protocol = stlink +build_flags = + ${env.build_flags} + -std=c++17 -Os -g2 +monitor_speed = 115200 +monitor_echo = yes + +[env:Nucleo-F411RE] +platform = ststm32 +board = nucleo_f411re +framework = arduino +lib_deps = + ${env.lib_deps} +upload_protocol = stlink +build_flags = + ${env.build_flags} + -std=c++17 -Os -g2 +monitor_speed = 115200 +monitor_echo = yes diff --git a/README.md b/README.md index 532587b..35f422b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,101 @@ # EX-Display -A project for multiple displays connected to EX-CommandStation via I2C +A project for multiple displays connected to EX-CommandStation. -Raw proof of concept - developed on Mega2560 + mcufriend display shield, and using mcufriend_kbv library. Tests OK on Uno, although first half-char of each line is offscreen, TBD. +The aim is to extend the current JMRI type virtual screen functionality to be displayed on additional microcontrollers with attached screens by using the `SCREEN(screen, row, "Message")` EXRAIL command. -Connection: -- MEGA: GND-GND and RX pin 19 on mega to serial tx pin on EX-CS -- UNO: GND-GND and RX pin 0 (used testprobes to access both pins under display shield, needs a better physical solution) +There will be support for displaying output for multiple logical screens on one single physical display in addition to supporting multiple physical displays. -Zero apologies for raw first effort here +## Terminology - Display vs. Screen + +In the context of this project, the term "display" is used when referring to a physical display connected to a microcontroller to display information. + +The term "screen" refers to a logical representation of what EX-CommandStation is expecting to display via the EXRAIL ``SCREEN(...)`` command. + +A logical "screen" is associated with a physical "display", with EX-Display controlling that association and ensuring EXRAIL ``SCREEN(...)`` commands are displayed on the correct physical display. + +## Supported Display Types + +EX-Display is written in a way to allow implementation of any physical display and its associated display library by using interface classes to abstract the logic away from the required library details. + + + +## Configuration + +Copy "config.example.h" to "config.h" and set the desired parameters. + +The key parameters to set depend on the physical screen type in use and are commented in the example config file. + +## Features and Usage + +As features are added they will be outlined here, with examples on how to use them. + +## Basic EXRAIL commands + +The basic EXRAIL command format is `SCREEN(screen, row, "Message")`. + +This example displays the text "This is row 0" on the first row of the first screen: + +`SCREEN(0, 0, "This is row 0")` + +## Formatting rows + +It is possible to perform limited formatting of rows using the below methods. + +Formatting is done once per row, and the formatting codes must be at the beginning of the text. The formatting codes are removed from the text and will not be displayed. + +### Setting Colours + +It is possible to set the text/foreground and background colours to any valid hex colour code when setting the colours using the "Message" field of the command. + +This is done by using the format `<@ screen row "#0x#0x#Message">`. Note that the hex values must be in the format "0xdddd", where "d" represents a four digit colour code. + +If you wish to set colours for a row as well as setting another formatting option, then set the row colour first, followed by a second row that sets the text and required formatting. + +This example displays the text "Yellow" on the first row of the first screen with yellow coloured text on a black background: + +`SCREEN(0, 0, "#0xFFE0#0x0000#Yellow")` + +If you wish to have a coloured line that is also underlined, do it like this: + +``` cpp +SCREEN(0, 0, "#0xFFE0#0x0000#") +SCREEN(0, 0, "__This is now yellow and underlined") +``` + +### Horizontal Line + +Note that setting a horizontal line will remove any text previously set for that particular row. To replace a line, simply update it without the line formatting with text, or delete it. + +`SCREEN(0, 0, "--")` + +### Underlined Text + +`SCREEN(0, 0, "__This is underlined")` + +### Always Ticker This Row + +*Not implemented yet* + +`SCREEN(0, 0, "~~This will always ticker")` + +### Never Ticker This Row + +*Not implemented yet* + +`SCREEN(0, 0, "!~This will never ticker")` + +### Delete Row + +Simply set the row message to zero length to delete it, noting that this doesn't just blank the text out, but deletes the row in its entirety, including all formatting attributes. + +`SCREEN(0, 1, "")` + +## Serial Connections + +- MEGA: GND-GND and RX pin 19 on mega to serial tx pin on EX-CS +- UNO: GND-GND and RX pin 0 (used testprobes to access both pins under display shield, needs a better physical solution) diff --git a/Screen.cpp b/Screen.cpp new file mode 100644 index 0000000..8cdc612 --- /dev/null +++ b/Screen.cpp @@ -0,0 +1,126 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#include "Screen.h" + +Screen::Screen(uint8_t screenId) : _screenId(screenId), _next(nullptr), _firstScreenRow(nullptr), _logger(nullptr) {} + +uint8_t Screen::getId() { return _screenId; } + +void Screen::setNext(Screen *screen) { _next = screen; } + +Screen *Screen::getNext() { return _next; } + +void Screen::updateScreenRow(uint8_t screenRowId, const char *text) { + if (text == nullptr) { + return; + } + LOG(LogLevel::LOG_DEBUG, "Screen::updateScreenRow[%d](%d, %s)", _screenId, screenRowId, text); + // Check if it exists already + ScreenRow *updateRow = getScreenRowById(screenRowId); + // If not, create and add to the list unless this is supposed to delete it with "" + if (updateRow == nullptr && strcmp(text, "") != 0) { + _addScreenRow(screenRowId, text); + } else { + // If "" this row is to be deleted + if (strcmp(text, "") == 0) { + _deleteScreenRow(updateRow); + } else { + // Otherwise update the text + updateRow->setText(text); + } + } +} + +ScreenRow *Screen::getFirstScreenRow() { return _firstScreenRow; } + +ScreenRow *Screen::getScreenRowById(uint8_t screenRowId) { + if (_firstScreenRow == nullptr) { + return nullptr; + } + for (ScreenRow *row = _firstScreenRow; row; row = row->getNext()) { + if (row->getId() == screenRowId) { + return row; + } + } + return nullptr; +} + +void Screen::setLogger(Logger *logger) { _logger = logger; } + +Screen::~Screen() { + if (_firstScreenRow != nullptr) { + ScreenRow *row = _firstScreenRow; + ScreenRow *next = nullptr; + while (row) { + next = row->getNext(); + delete row; + row = next; + } + _firstScreenRow = nullptr; + } + + _next = nullptr; +} + +void Screen::_addScreenRow(uint8_t screenRowId, const char *text) { + LOG(LogLevel::LOG_DEBUG, "Screen::_addScreenRow(%d, %s)", screenRowId, text); + ScreenRow *newRow = new ScreenRow(screenRowId); + newRow->setText(text); + if (_logger != nullptr) { + newRow->setLogger(_logger); + } + newRow->setText(text); + // If we don't have a first, this is it + if (_firstScreenRow == nullptr) { + _firstScreenRow = newRow; + } else { + // Otherwise, iterate through the list and add to the end + ScreenRow *row = _firstScreenRow; + while (row->getNext() != nullptr) { + row = row->getNext(); + } + row->setNext(newRow); + } +} + +void Screen::_deleteScreenRow(ScreenRow *screenRow) { + if (screenRow == nullptr) { + return; + } + LOG(LogLevel::LOG_DEBUG, "Screen::_deleteScreenRow() - ID: %d", screenRow->getId()); + // If it's the first in the list, shuffle to next, delete, and done + if (_firstScreenRow == screenRow) { + ScreenRow *deleteRow = _firstScreenRow; + _firstScreenRow = deleteRow->getNext(); + delete deleteRow; + return; + } + // Otherwise iterate through, shuffle, delete, and done + ScreenRow *currentRow = _firstScreenRow; + ScreenRow *previousRow = nullptr; + + while (currentRow != nullptr) { + if (currentRow == screenRow) { + previousRow->setNext(currentRow->getNext()); + delete currentRow; + return; + } + previousRow = currentRow; + currentRow = currentRow->getNext(); + } +} diff --git a/Screen.h b/Screen.h new file mode 100644 index 0000000..c77a22e --- /dev/null +++ b/Screen.h @@ -0,0 +1,85 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef SCREEN_H +#define SCREEN_H + +#include "ScreenRow.h" +#include + +/// @brief Class for each screen as received from the EXRAIL SCREEN() command +/// Each Screen instance contains a linked list of ScreenRow instances +/// Screen instances are contained in a linked list managed separately by the ScreenManager +class Screen { +public: + /// @brief Constructor for each Screen instance + /// @param screenId ID of this screen as set in EXRAIL SCREEN() + Screen(uint8_t screenId); + + /// @brief Get the ID for this screen instance + /// @return Screen ID + uint8_t getId(); + + /// @brief Set the next Screen instance in the list + /// @param screen Pointer to the next Screen instance + void setNext(Screen *screen); + + /// @brief Get the next Screen instance in the list + /// @return Pointer to the next Screen instance + Screen *getNext(); + + /// @brief Update the specified ScreenRow, or create it if it doesn't exist + /// This will automatically maintain the linked list of ScreenRow instances + /// Setting text to an empty string "" will delete the associated row from the list + /// @param screenRowId ScreenRow ID + /// @param text Text to associated with this ScreenRow + void updateScreenRow(uint8_t screenRowId, const char *text); + + /// @brief Get the first ScreenRow instance in the list + /// @return Pointer to the first ScreenRow instance + ScreenRow *getFirstScreenRow(); + + /// @brief Get the ScreenRow instance at the specified ID + /// @param screenRowId ScreenRow ID to retrieve + /// @return Pointer to the ScreenRow instance at the ID + ScreenRow *getScreenRowById(uint8_t screenRowId); + + /// @brief Set the Logger instance + /// @param logger Pointer to the Logger + void setLogger(Logger *logger); + + /// @brief Destructor for the Screen instance + /// Will automatically clean up the associated ScreenRow list + ~Screen(); + +private: + uint8_t _screenId; + Screen *_next; + ScreenRow *_firstScreenRow; + Logger *_logger; + + /// @brief Add a new ScreenRow to the list + /// @param screenRowId ID of the ScreenRow to add + /// @param text Text for this ScreenRow + void _addScreenRow(uint8_t screenRowId, const char *text); + + /// @brief Delete the specified ScreenRow and update the list accordingly + /// @param screenRow Pointer to the ScreenRow instance to delete + void _deleteScreenRow(ScreenRow *screenRow); +}; + +#endif // SCREEN_H diff --git a/ScreenManager.cpp b/ScreenManager.cpp new file mode 100644 index 0000000..bb00326 --- /dev/null +++ b/ScreenManager.cpp @@ -0,0 +1,154 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#include "ScreenManager.h" + +ScreenManager::ScreenManager() : _firstScreen(nullptr), _logger(nullptr) {} + +Screen *ScreenManager::updateScreen(uint8_t screenId) { + // If the screen doesn't exist, create and add to list + LOG(LogLevel::LOG_DEBUG, "ScreenManager::addScreen(%d)", screenId); + if (getScreenById(screenId) == nullptr) { + Screen *newScreen = new Screen(screenId); + if (_logger != nullptr) { + newScreen->setLogger(_logger); + } + // Make it the first one if we don't already have a first + if (_firstScreen == nullptr) { + _firstScreen = newScreen; + return newScreen; + } + Screen *screen = _firstScreen; + while (screen->getNext() != nullptr) { + screen = screen->getNext(); + } + screen->setNext(newScreen); + return newScreen; + } else { + return getScreenById(screenId); + } +} + +Screen *ScreenManager::getFirstScreen() { return _firstScreen; } + +Screen *ScreenManager::getScreenById(uint8_t screenId) { + if (_firstScreen == nullptr) { + return nullptr; + } + for (Screen *screen = _firstScreen; screen; screen = screen->getNext()) { + if (screen->getId() == screenId) { + return screen; + } + } + return nullptr; +} + +Screen *ScreenManager::getPreviousScreen(Screen *screen) { + // If there is no screen, nullptr + if (_firstScreen == nullptr) { + return nullptr; + } + // If there's only one screen, that's the one + if (_firstScreen->getNext() == nullptr) { + return _firstScreen; + } + // If this is the lowest numbered screen, wrap to highest as previous + if (screen->getId() == getMinScreenId()) { + return getScreenById(getMaxScreenId()); + } + // Otherwise we need to find it + // Iterate descending through all screens by ID, starting from provided screen -1 + uint8_t minId = getMinScreenId(); + uint8_t maxId = screen->getId() - 1; + for (uint8_t i = maxId; i >= minId; i--) { + Screen *testScreen = getScreenById(i); + // If there's a screen at this ID, it must be the previous, return it + if (testScreen != nullptr) { + return testScreen; + } + } + return nullptr; +} + +Screen *ScreenManager::getNextScreen(Screen *screen) { + // If there is no screen, nullptr + if (_firstScreen == nullptr) { + return nullptr; + } + // If there's only one screen, that's the one + if (_firstScreen->getNext() == nullptr) { + return _firstScreen; + } + // If this is the highest numbered screen, wrap to lowest as next + if (screen->getId() == getMaxScreenId()) { + return getScreenById(getMinScreenId()); + } + // Otherwise we need to find it + // Iterate through all screens by ID, starting from provided screen +1 + uint8_t minId = screen->getId() + 1; + uint8_t maxId = getMaxScreenId(); + for (uint8_t i = minId; i <= maxId; i++) { + Screen *testScreen = getScreenById(i); + // If there's a screen at this ID, it must be the next, return it + if (testScreen != nullptr) { + return testScreen; + } + } + return nullptr; +} + +uint8_t ScreenManager::getMinScreenId() { + if (_firstScreen == nullptr) { + return 0; + } + uint8_t screenId = _firstScreen->getId(); + for (Screen *screen = _firstScreen; screen; screen = screen->getNext()) { + if (screen->getId() < screenId) { + screenId = screen->getId(); + } + } + return screenId; +} + +uint8_t ScreenManager::getMaxScreenId() { + if (_firstScreen == nullptr) { + return 0; + } + uint8_t screenId = _firstScreen->getId(); + for (Screen *screen = _firstScreen; screen; screen = screen->getNext()) { + if (screen->getId() > screenId) { + screenId = screen->getId(); + } + } + return screenId; +} + +void ScreenManager::setLogger(Logger *logger) { _logger = logger; } + +ScreenManager::~ScreenManager() { + if (_firstScreen != nullptr) { + Screen *screen = _firstScreen; + Screen *next = nullptr; + while (screen) { + next = screen->getNext(); + delete screen; + screen = next; + } + _firstScreen = nullptr; + } + _logger = nullptr; +} diff --git a/ScreenManager.h b/ScreenManager.h new file mode 100644 index 0000000..38f515b --- /dev/null +++ b/ScreenManager.h @@ -0,0 +1,73 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef SCREENMANAGER_H +#define SCREENMANAGER_H + +#include "Screen.h" + +/// @brief Class to manage all EX-Display screens +class ScreenManager { +public: + /// @brief Constructor for the ScreenManager + ScreenManager(); + + /// @brief Update a Screen, adds a new one if it doesn't exist + /// @param screenId ID of the Screen + /// @return Pointer to the new Screen + Screen *updateScreen(uint8_t screenId); + + /// @brief Get the first Screen instance in the list + /// @return Pointer to the first Screen instance + Screen *getFirstScreen(); + + /// @brief Get the Screen with the specified ID + /// @param screenId ID of the Screen instance to retrieve + /// @return Pointer to the Screen instance + Screen *getScreenById(uint8_t screenId); + + /// @brief Get the previous screen in the list (sorted by ID) + /// @param screen Pointer to the current screen + /// @return Pointer to the previous Screen + Screen *getPreviousScreen(Screen *screen); + + /// @brief Get the next screen in the list (sorted by ID) + /// @param screen Pointer to the current screen + /// @return Pointer to the next Screen + Screen *getNextScreen(Screen *screen); + + /// @brief Get the lowest Screen ID in the list + /// @return Lowest Screen ID + uint8_t getMinScreenId(); + + /// @brief Get the highest Screen ID in the list + /// @return Highest Screen ID + uint8_t getMaxScreenId(); + + /// @brief Set the Logger instance + /// @param logger Pointer to the Logger + void setLogger(Logger *logger); + + /// @brief Destructor for the ScreenManager + ~ScreenManager(); + +private: + Screen *_firstScreen; + Logger *_logger; +}; + +#endif // SCREENMANAGER_H diff --git a/ScreenRow.cpp b/ScreenRow.cpp new file mode 100644 index 0000000..d61f4b4 --- /dev/null +++ b/ScreenRow.cpp @@ -0,0 +1,59 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#include "ScreenRow.h" + +ScreenRow::ScreenRow(uint8_t screenRowId) + : _screenRowId(screenRowId), _text(nullptr), _next(nullptr), _logger(nullptr), _needsRedraw(true) {} + +uint8_t ScreenRow::getId() { return _screenRowId; } + +void ScreenRow::setNext(ScreenRow *screenRow) { _next = screenRow; } + +ScreenRow *ScreenRow::getNext() { return _next; } + +void ScreenRow::setText(const char *text) { + if (text == nullptr) { + return; + } + if (_text) { + delete[] _text; + _text = nullptr; + } + _text = new char[strlen(text) + 1]; + strcpy(_text, text); + _needsRedraw = true; + LOG(LogLevel::LOG_DEBUG, "ScreenRow::setText(%s)", _text); +} + +const char *ScreenRow::getText() { + _needsRedraw = false; + return _text; +} + +void ScreenRow::setLogger(Logger *logger) { _logger = logger; } + +bool ScreenRow::needsRedraw() { return _needsRedraw; } + +ScreenRow::~ScreenRow() { + if (_text) { + delete[] _text; + } + _text = nullptr; + _next = nullptr; + _logger = nullptr; +} diff --git a/ScreenRow.h b/ScreenRow.h new file mode 100644 index 0000000..5d5f9fc --- /dev/null +++ b/ScreenRow.h @@ -0,0 +1,69 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef SCREENROW_H +#define SCREENROW_H + +#include "Logger.h" +#include + +class ScreenRow { +public: + /// @brief Constructor for each ScreenRow instance + /// @param screenRowId ID of this ScreenRow + ScreenRow(uint8_t screenRowId); + + /// @brief Get the ID of this ScreenRow + /// @return ID of this ScreenRow + uint8_t getId(); + + /// @brief Set the next ScreenRow instance in the list + /// @param screenRow Pointer to the next ScreenRow instance + void setNext(ScreenRow *screenRow); + + /// @brief Get the next ScreenRow instance in the list + /// @return Pointer to the next ScreenRow instance + ScreenRow *getNext(); + + /// @brief Set the text associated with this ScreenRow + /// @param text Text for this ScreenRow + void setText(const char *text); + + /// @brief Get the text associated with this ScreenRow + /// @return Text for this ScreenRow + const char *getText(); + + /// @brief Set the Logger instance + /// @param logger Pointer to the Logger + void setLogger(Logger *logger); + + /// @brief Test if this row needs to be redrawn + /// @return true|false + bool needsRedraw(); + + /// @brief Destructor for each ScreenRow instance + ~ScreenRow(); + +private: + uint8_t _screenRowId; + char *_text; + ScreenRow *_next; + Logger *_logger; + bool _needsRedraw; +}; + +#endif // SCREENROW_H diff --git a/TFT_eSPIDisplay.cpp b/TFT_eSPIDisplay.cpp new file mode 100644 index 0000000..e5a755c --- /dev/null +++ b/TFT_eSPIDisplay.cpp @@ -0,0 +1,172 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#include "TFT_eSPIDisplay.h" + +// Do not load when testing, TFT_eSPI library is incompatible and will cause failures. +#ifndef PIO_UNIT_TESTING + +TFT_eSPI *TFT_eSPIDisplay::_tft = nullptr; +bool TFT_eSPIDisplay::_tftInitialised = false; + +TFT_eSPIDisplay::TFT_eSPIDisplay(uint8_t rotation, uint8_t textSize, uint16_t textColour, uint16_t backgroundColour) { + _rotation = rotation; + _textSize = textSize; + _textColour = textColour; + _backgroundColour = backgroundColour; + if (_tft == nullptr) { + _tft = new TFT_eSPI(); + } + _gfxFont = TEXT_FONT; +} + +TFT_eSPIDisplay::TFT_eSPIDisplay(uint8_t rotation, uint8_t textSize, uint16_t textColour, uint16_t backgroundColour, + int csPin) { + _rotation = rotation; + _textSize = textSize; + _textColour = textColour; + _backgroundColour = backgroundColour; + _csPin = csPin; + if (_tft == nullptr) { + _tft = new TFT_eSPI(); + } + _gfxFont = TEXT_FONT; +} + +void TFT_eSPIDisplay::begin() { + LOG(LogLevel::LOG_DEBUG, "TFT_eSPIDisplay::begin[%d]()", _displayId); + if (!_tftInitialised) { + _tft->init(); + _tftInitialised = true; + } + _tft->setTextSize(_textSize); + _tft->setRotation(_rotation); + _tft->setTextColor(_textColour); + _tft->setFreeFont(_gfxFont); + _fontHeight = _gfxFont->yAdvance; + _fontWidth = _tft->textWidth("A"); + _maxRow = _tft->height() / _fontHeight; + _maxColumn = _tft->width() / _fontWidth; + LOG(LogLevel::LOG_DEBUG, + "TFT_eSPIDisplay[%d] settings: " + "_textSize=%d|_rotation=%d|_textColour=0x%04X|_backgroundColour=0x%04X|_csPin=%d|_fontHeight=%d|_fontWidth=%d|_" + "maxRow=%d|_" + "maxColumn=%d", + _displayId, _textSize, _rotation, _textColour, _backgroundColour, _csPin, _fontHeight, _fontWidth, _maxRow, + _maxColumn); + _tft->fillScreen(_backgroundColour); +} + +void TFT_eSPIDisplay::clearScreen() { + LOG(LogLevel::LOG_DEBUG, "TFT_eSPIDisplay::clearScreen[%d]() - colour 0x%04X", _displayId, _backgroundColour); + _tft->fillScreen(_backgroundColour); +} + +void TFT_eSPIDisplay::displayScreen(Screen *screen) { + // If this display needs redrawing, clear first then process rows + // Must set a local redraw flag here so we can clear the instance for next time + if (_needsRedraw) { + clearScreen(); + } + for (ScreenRow *row = screen->getFirstScreenRow(); row; row = row->getNext()) { + if (row->needsRedraw() || _needsRedraw) { + displayRow(row->getId(), row->getText(), false, 0); + } + } + // Now we've redrawn, clear the flag + _needsRedraw = false; +} + +void TFT_eSPIDisplay::displayRow(uint8_t row, const char *text, bool underlined, uint8_t column) { + if (text == nullptr) { + return; + } + int32_t x = 0; + int32_t y = 0; + _getRowPosition(column, row, x, y); + LOG(LogLevel::LOG_DEBUG, "TFT_eSPIDisplay::displayRow[%d](%d, %s, %d, %d) at X=%d|Y=%d", _displayId, row, text, + underlined, column, x, y); + _tft->setTextColor(_textColour); + LOG(LogLevel::LOG_DEBUG, "setTextColour(0x%04X)", _textColour); + if (column == 0) { + clearRow(row); + } + _tft->drawString(text, x, y); +} + +void TFT_eSPIDisplay::clearRow(uint8_t row) { + LOG(LogLevel::LOG_DEBUG, "TFT_eSPIDisplay::clearRow[%d](%d)", _displayId, row); + int32_t x = 0; + int32_t y = 0; + _getRowPosition(0, row, x, y); + _tft->fillRect(x, y, _tft->width(), _fontHeight, _backgroundColour); +} + +void TFT_eSPIDisplay::displayStartupInfo(const char *version) { + LOG(LogLevel::LOG_DEBUG, "TFT_eSPIDisplay::displayStartupInfo[%d](%s)", _displayId, version); + _tft->fillScreen(0xFFFF); + int32_t x = 0; + int32_t y = 0; + _tft->setTextColor(0x01C8); + _getRowPosition(0, 0, x, y); + _tft->drawString("DCC-", x, y); + _tft->setTextColor(0x03B6); + _getRowPosition(4, 0, x, y); + _tft->drawString("EX", x, y); + _tft->setTextColor(0x01C8); + _getRowPosition(0, 1, x, y); + _tft->drawString("EX-Display", x, y); + _getRowPosition(0, 2, x, y); + _tft->drawString("Version: ", x, y); + _tft->setTextColor(0x03B6); + _getRowPosition(9, 2, x, y); + _tft->drawString(version, x, y); +} + +TFT_eSPI *TFT_eSPIDisplay::getTFT_eSPIInstance() { + LOG(LogLevel::LOG_DEBUG, "TFT_eSPIDisplay::getTFT_eSPIInstance[%d]", _displayId); + return _tft; +} + +bool TFT_eSPIDisplay::tftInitialised() { return _tftInitialised; } + +TFT_eSPIDisplay *TFT_eSPIDisplay::create(uint8_t rotation, uint8_t textSize, uint16_t textColour, + uint16_t backgroundColour) { + TFT_eSPIDisplay *newDisplay = new TFT_eSPIDisplay(rotation, textSize, textColour, backgroundColour); + return newDisplay; +} + +TFT_eSPIDisplay *TFT_eSPIDisplay::create(uint8_t rotation, uint8_t textSize, uint16_t textColour, + uint16_t backgroundColour, int csPin) { + TFT_eSPIDisplay *newDisplay = new TFT_eSPIDisplay(rotation, textSize, textColour, backgroundColour, csPin); + return newDisplay; +} + +TFT_eSPIDisplay::~TFT_eSPIDisplay() { + delete _tft; + _tft = nullptr; +} + +void TFT_eSPIDisplay::_getRowPosition(uint8_t column, uint8_t row, int32_t &x, int32_t &y) { + LOG(LogLevel::LOG_DEBUG, "TFT_eSPIDisplay::_getRowPosition[%d](%d, %d, %d, %d)", _displayId, column, row, x, y); + x = column * _fontWidth; + y = (row * _fontHeight); + LOG(LogLevel::LOG_DEBUG, "TFT_eSPIDisplay::_getRowPosition[%d] x=%d|_fontWidth=%d|y=%d|_fontHeight=%d", _displayId, x, + _fontWidth, y, _fontHeight); +} + +#endif // PIO_UNIT_TESTING diff --git a/TFT_eSPIDisplay.h b/TFT_eSPIDisplay.h new file mode 100644 index 0000000..e1c223c --- /dev/null +++ b/TFT_eSPIDisplay.h @@ -0,0 +1,131 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef TFT_ESPIDISPLAY_H +#define TFT_ESPIDISPLAY_H + +// Do not load when testing, TFT_eSPI library is incompatible and will cause failures. +#ifndef PIO_UNIT_TESTING + +#include "Defines.h" +#include "DisplayInterface.h" +#include +#include + +// Define the standard fonts available for this display type +#define FONT_TFT_ESPI_SMALL &FreeMono9pt7b +#define FONT_TFT_ESPI_MEDIUM &FreeMono12pt7b +#define FONT_TFT_ESPI_LARGE &FreeMono18pt7b +#define FONT_TFT_ESPI_XLARGE &FreeMono24pt7b +#define FONT_TFT_ESPI_SMALL_SANS &FreeSans9pt7b +#define FONT_TFT_ESPI_MEDIUM_SANS &FreeSans12pt7b +#define FONT_TFT_ESPI_LARGE_SANS &FreeSans18pt7b +#define FONT_TFT_ESPI_XLARGE_SANS &FreeSans24pt7b + +// If not overridden by myConfig.h, set the font +#ifndef TEXT_FONT +#define TEXT_FONT FONT_TFT_ESPI_MEDIUM +#endif // TEXT_FONT + +/// @brief Display class for TFT_eSPI based displays +class TFT_eSPIDisplay : public DisplayInterface { +public: + /// @brief Constructor for a TFT_eSPIDisplay instance + /// @param rotation Rotation of the display, 0 - 3, refer to TFT_eSPI documentation for details + /// @param textSize Multiplier for the text size, refer to TFT_eSPI documentation for details + /// @param textColour Default 16bit text colour, refer to TFT_eSPI documentation for details + /// @param backgroundColour Default 16bit background colour, refer to TFT_eSPI documentation for details + TFT_eSPIDisplay(uint8_t rotation, uint8_t textSize, uint16_t textColour, uint16_t backgroundColour); + + /// @brief Alternate constructor for a TFT_eSPIDisplay instance to specify the CS pin to allow for two displays + /// @param rotation Rotation of the display, 0 - 3, refer to TFT_eSPI documentation for details + /// @param textSize Multiplier for the text size, refer to TFT_eSPI documentation for details + /// @param textColour Default 16bit text colour, refer to TFT_eSPI documentation for details + /// @param backgroundColour Default 16bit background colour, refer to TFT_eSPI documentation for details + /// @param csPin Pin this display's chip select (CS) pin is connected to to enable manual display switching + TFT_eSPIDisplay(uint8_t rotation, uint8_t textSize, uint16_t textColour, uint16_t backgroundColour, int csPin); + + /// @brief Perform any initial once off setup or configuration here and call only once + void begin() override; + + /// @brief Clear the entire screen + void clearScreen() override; + + /// @brief Display the specified Screen on this display + /// @param screen Pointer to the Screen to display + void displayScreen(Screen *screen) override; + + /// @brief Display a row of text on the display + /// @param row Row number as specified in the SCREEN() command (not pixels) + /// @param text Text to be displayed on this row + /// @param underlined (Optional) Flag to underline this row - default false + /// @param column (Optional) Column to start displaying the text, column being width of a character (not pixels) + void displayRow(uint8_t row, const char *text, bool underlined = false, uint8_t column = 0); + + /// @brief Clear the specified row + /// @param row Row number as specified in the SCREEN() command (not pixels) + void clearRow(uint8_t row); + + /// @brief Display the startup screen with software version + /// @param version EX-Display version + void displayStartupInfo(const char *version) override; + + /// @brief Get the TFT_eSPI instance created by this instance - needed for the touch interface + /// @return Pointer to the TFT_eSPI instance + TFT_eSPI *getTFT_eSPIInstance(); + + /// @brief Test if the TFT_eSPI instance has been initialised - needed for the touch interface + /// @return true|false + bool tftInitialised(); + + /// @brief Static method to enable the compiler to generate create commands from myDevices.h entries + /// @param rotation rotation Rotation of the display, 0 - 3, refer to TFT_eSPI documentation for details + /// @param textSize Multiplier for the text size, refer to TFT_eSPI documentation for details + /// @param textColour Default 16bit text colour, refer to TFT_eSPI documentation for details + /// @param backgroundColour Default 16bit background colour, refer to TFT_eSPI documentation for details + /// @return Pointer to a new TFT_eSPIDisplay instance + static TFT_eSPIDisplay *create(uint8_t rotation, uint8_t textSize, uint16_t textColour, uint16_t backgroundColour); + + /// @brief Alternate static method to enable the compiler to generate create commands from myDevices.h entries + /// @param rotation rotation Rotation of the display, 0 - 3, refer to TFT_eSPI documentation for details + /// @param textSize Multiplier for the text size, refer to TFT_eSPI documentation for details + /// @param textColour Default 16bit text colour, refer to TFT_eSPI documentation for details + /// @param backgroundColour Default 16bit background colour, refer to TFT_eSPI documentation for details + /// @param csPin Pin this display's chip select (CS) pin is connected to to enable manual display switching + /// @return Pointer to a new TFT_eSPIDisplay instance + static TFT_eSPIDisplay *create(uint8_t rotation, uint8_t textSize, uint16_t textColour, uint16_t backgroundColour, + int csPin); + + /// @brief Destructor for the TFT_eSPIDisplay + ~TFT_eSPIDisplay() override; + +private: + static TFT_eSPI *_tft; + const GFXfont *_gfxFont; + static bool _tftInitialised; + + /// @brief Get the X/Y coordinates to draw the specified row, starting at the specified column + /// @param row Row number + /// @param column Column to start drawing at + /// @param x Variable to update with the x position + /// @param y Variable to update with the y position + void _getRowPosition(uint8_t row, uint8_t column, int32_t &x, int32_t &y); +}; + +#endif // PIO_UNIT_TESTING + +#endif // TFT_ESPIDISPLAY_H diff --git a/TFT_eSPIScreen.cpp b/TFT_eSPIScreen.cpp deleted file mode 100644 index 9f5ad20..0000000 --- a/TFT_eSPIScreen.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "Defines.h" -#include "TFT_eSPIScreen.h" - -TFT_eSPIScreen::TFT_eSPIScreen(TFT_eSPI &tft, uint8_t maxRows, uint8_t maxColumns) - : EXScreen(maxRows, maxColumns), _tft(tft) {} - -void TFT_eSPIScreen::setupScreen(uint8_t rotation, uint16_t textColour, uint16_t backgroundColour) { - _tft.init(); - _tft.setRotation(rotation); - _tft.fillScreen(backgroundColour); - _tft.setCursor(0, 0); - _tft.print(F("EX-Display")); -} - -void TFT_eSPIScreen::writeRow(uint8_t row, uint8_t column, const GFXfont *fontName, uint16_t fontColour, - uint8_t textSize, char *message) { - _tft.setCursor(column, row); - _tft.setTextColor(fontColour); - _tft.print(message); -} diff --git a/TFT_eSPIScreen.h b/TFT_eSPIScreen.h deleted file mode 100644 index 9eaea03..0000000 --- a/TFT_eSPIScreen.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef TFT_ESPISCREEN_H -#define TFT_ESPISCREEN_H - -#include "EXScreen.h" -#include -#include -#include - -class TFT_eSPIScreen : public EXScreen { -public: - TFT_eSPIScreen(TFT_eSPI &tft, uint8_t maxRows, uint8_t maxColumns); - - virtual void setupScreen(uint8_t rotation, uint16_t textColour, uint16_t backgroundColour) override; - - virtual void writeRow(uint8_t row, uint8_t column, const GFXfont *fontName, uint16_t fontColour, uint8_t textSize, - char *message) override; - -private: - TFT_eSPI _tft; -}; - -#endif diff --git a/TFT_eSPITouch.cpp b/TFT_eSPITouch.cpp new file mode 100644 index 0000000..c6bb982 --- /dev/null +++ b/TFT_eSPITouch.cpp @@ -0,0 +1,165 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +// Do not load when testing, TFT_eSPI library is incompatible and will cause failures. +#ifndef PIO_UNIT_TESTING + +// Must include SPIFFS before any other includes - Espressif limitation +#include "SPIFFS.h" + +// Others here +#include "CallbackInterface.h" +#include "TFT_eSPITouch.h" + +TFT_eSPITouch::TFT_eSPITouch(int displayId) : _tft(nullptr), _calibrationFile("/calibrationData") { + _needsDisplay = displayId; + _isCalibrating = false; +} + +void TFT_eSPITouch::begin() { + LOG(LogLevel::LOG_DEBUG, "TFT_eSPITouch::begin()"); + // Can't do anything without a TFT_eSPIDisplay or TFT_eSPI instance + if (_display == nullptr) { + LOG(LogLevel::LOG_ERROR, "An existing DisplayInterface instance is not set, cannot use touch"); + return; + } + // The display must be able to be cast to a TFT_eSPIDisplay instance to be valid + TFT_eSPIDisplay *display = static_cast(_display); + if (!display) { + LOG(LogLevel::LOG_ERROR, "Provided DisplayInterface is not a TFT_eSPIDisplay type, cannot use touch"); + return; + } + _tft = display->getTFT_eSPIInstance(); + if (!_tft) { + LOG(LogLevel::LOG_ERROR, "An existing TFT_eSPI instance is not set, cannot use touch"); + return; + } + // If the display instance didn't initialised the tft instance, it must be done first, call begin() + if (!display->tftInitialised()) { + LOG(LogLevel::LOG_WARN, "The associated TFT_eSPIDislay instance %d has not been initialised, initialising now", + display->getId()); + display->begin(); + } + // If SPIFFS isn't setup or can't be formatted etc., we can't save calibration, don't do it + if (!_setupSPIFFS()) { + LOG(LogLevel::LOG_ERROR, "SPIFFS filesystem unable to begin, calibration not possible"); + return; + } + // If the touch input isn't calibrated, do it first + if (!_calibrated()) { + LOG(LogLevel::LOG_DEBUG, "TFT_eSPI touch input not calibrated, starting"); + _isCalibrating = true; + // If calibration fails, or data can't be saved, show the user an error and halt for 5 secs + // This will allow normal display operationg to resume, but input won't be reliable + if (!_doCalibration()) { + LOG(LogLevel::LOG_ERROR, "TFT_eSPI touch input calibration failed"); + _displayCalibrationError(display); + _isCalibrating = false; + } else { + // Otherwise clear the screen and continue + LOG(LogLevel::LOG_DEBUG, "TFT_eSPI touch input calibrated successfully"); + display->clearScreen(); + _isCalibrating = false; + } + } +} + +void TFT_eSPITouch::check() { + if (_tft == nullptr) { + return; + } + InputAction action = InputAction::PRESS_NONE; + uint16_t touchX; + uint16_t touchY; + if (_tft->getTouch(&touchX, &touchY)) { + LOG(LogLevel::LOG_DEBUG, "TFT_eSPITouch::check() - touchX=%d|touchY=%d", touchX, touchY); + action = _calculateInputAction(touchX, touchY, _tft->width(), _tft->height()); + } + action = _debounceOrHeld(action); + if (_callback != nullptr && action != InputAction::PRESS_NONE) { + _callback->onInputAction(action); + } +} + +TFT_eSPITouch *TFT_eSPITouch::create(int displayId) { + TFT_eSPITouch *newTouch = new TFT_eSPITouch(displayId); + return newTouch; +} + +bool TFT_eSPITouch::_calibrated() { + if (_forceCalibration) { + return false; + } + uint16_t calibrationData[5]; + File file = SPIFFS.open(_calibrationFile, "r"); + if (!file) { + LOG(LogLevel::LOG_WARN, "Calibration file not available, calibration required"); + return false; + } + bool dataValid = false; + if (file.readBytes((char *)calibrationData, 14) == 14) { + dataValid = true; + file.close(); + } + if (!dataValid) { + LOG(LogLevel::LOG_WARN, "Saved calibration data not valid, calibration required"); + return false; + } + _tft->setTouch(calibrationData); + return true; +} + +bool TFT_eSPITouch::_doCalibration() { + LOG(LogLevel::LOG_WARN, "Calibration process commenced"); + uint16_t calibrationData[5]; + _tft->calibrateTouch(calibrationData, TFT_WHITE, TFT_RED, 15); + File file = SPIFFS.open(_calibrationFile, "w"); + if (file) { + file.write((const unsigned char *)calibrationData, 14); + file.close(); + LOG(LogLevel::LOG_MESSAGE, "Calibration succeeded"); + return true; + } + LOG(LogLevel::LOG_ERROR, "Calibration process failed"); + return false; +} + +bool TFT_eSPITouch::_setupSPIFFS() { + bool setup = false; + if (!SPIFFS.begin()) { + LOG(LogLevel::LOG_WARN, "SPIFFS filesystem has not been initialised, formatting now, calibration required"); + SPIFFS.format(); + if (SPIFFS.begin()) { + setup = true; + } + } else { + setup = true; + } + return setup; +} + +void TFT_eSPITouch::_displayCalibrationError(TFT_eSPIDisplay *display) { + display->clearScreen(); + display->displayRow(0, "ERROR!"); + display->displayRow(1, "TFT_eSPI touch input calibration failed"); + display->displayRow(2, "Touch input will be unreliable"); + display->displayRow(3, "Operation resumes in 5 seconds"); + delay(5000); + display->clearScreen(); +} + +#endif // PIO_UNIT_TESTING diff --git a/TFT_eSPITouch.h b/TFT_eSPITouch.h new file mode 100644 index 0000000..cca4ecd --- /dev/null +++ b/TFT_eSPITouch.h @@ -0,0 +1,72 @@ +/* + * © 2024 Peter Cole + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this code. If not, see . + */ + +#ifndef TFT_ESPITOUCH_H +#define TFT_ESPITOUCH_H + +// Do not load when testing, TFT_eSPI library is incompatible and will cause failures. +#ifndef PIO_UNIT_TESTING + +#include "Defines.h" +#include "InputInterface.h" +#include "TFT_eSPIDisplay.h" +#include +#include + +/// @brief Display class for TFT_eSPI touch screens +class TFT_eSPITouch : public InputInterface { +public: + /// @brief Constructor for the TFT_eSPITouch instance + /// @param displayId ID of the display to retrieve the TFT_eSPI instance from + TFT_eSPITouch(int displayId); + + /// @brief Perform any initial once off setup or configuration here and call only once + void begin() override; + + /// @brief Call this method at least once per main loop to monitor for input actions + /// Any actions should call the callback set in the _callback attribute + void check() override; + + /// @brief Static method to enable the compiler to generate create commands from myDevices.h entries + /// @param displayId ID of the display to retrieve the TFT_eSPI instance from + /// @return Pointer to the new TFT_eSPITouch instance + static TFT_eSPITouch *create(int displayId); + +private: + TFT_eSPI *_tft; + const char _calibrationFile[20]; + + /// @brief Test if valid calibration data is available for this touch interface + /// @return true|false + bool _calibrated(); + + /// @brief Run the TFT_eSPI calibration sequence + /// @return true if successful and saved, otherwise false + bool _doCalibration(); + + /// @brief Ensure SPIFFS filesystem is up and running + /// @return true|false + bool _setupSPIFFS(); + + /// @brief Display calibration error on screen and pause for 5 seconds + /// @param display Pointer to the TFT_eSPIDisplay instance to use + void _displayCalibrationError(TFT_eSPIDisplay *display); +}; + +#endif // PIO_UNIT_TESTING + +#endif // TFT_ESPITOUCH_H diff --git a/Version.h b/Version.h new file mode 100644 index 0000000..1caeb18 --- /dev/null +++ b/Version.h @@ -0,0 +1,71 @@ +#ifndef VERSION_H +#define VERSION_H + +// Numeric version here: major.minor.patch +#define VERSION "0.0.21" + +// 0.0.21 includes: +// - Refactor DisplayInterface to accept an entire Screen to display to let the derived classes control the entire +// physical display process +// 0.0.20 includes: +// - Update log levels with LOG_ preface to avoid STM32 framework conflicts for ERROR +// 0.0.19 includes: +// - Resolved multiple TFT_eSPI screens not operating independently by using static TFT_eSPI instance +// 0.0.18 includes: +// - Added ability to force touchscreen calibration if required +// 0.0.17 includes: +// - Improved experimental support for multiple TFT_eSPI instances +// 0.0.16 includes: +// - Add configuration of input device via macro USER_INPUT() +// 0.0.15 includes: +// - Fix missing redraw functionality when switching screens +// - Implemented myDevices.h to configure display devices via macro USER_DISPLAY() +// 0.0.14 includes: +// - Major rewrite using OOP +// - Swap display/screen terminology - screen is logical from EX-CommandStation, display is physical +// - Basic function of a single screen 0 to a single display 0 +// - Add tests +// 0.0.13 includes: +// - Improved user configuration +// - Swap OLED support to use SSD1306_Ascii +// 0.0.12 includes: +// - Fixed left button selecting previous logical display +// 0.0.11 includes: +// - Add InputMethod class with support for TFT_eSPI and MCUFRIEND_kbv touch screens +// - Also add support for physical push buttons +// 0.0.10 includes: +// - Refactor so that logical displays are attributes of physical screens +// - This should simplify addition of touch/input control +// - Ensure order of logical displays in linked list are in ascending order of number +// 0.0.9 includes: +// - Add support for SSD1306 and SH1106 I2C connected OLEDs +// - Disable text wrap +// 0.0.8 includes: +// - Fix a bug where adding new rows while the screen is scrolling puts them on the wrong row +// 0.0.7 includes: +// - Move row formatting to the EXDisplayRow class so only one call is needed to set a row up +// - Change colour attributes to match other formatting options so they can be sent with text +// - Implement support for deleting a row when sending "" as the message +// 0.0.6 includes: +// - Add ability to set individual row colours via the parser +// - Add ability to draw a horizontal line on a row rather than text +// - Add ability to underline a row +// 0.0.5 includes: +// - Refactor screen and logical display to enable multiple displays to use the same screen +// - Enable timed switching between logical displays +// - Enable timed vertical scrolling when there are more rows than can be on a single screen +// - Moved display redraw function into the EXDisplay class to simplify screen updates +// 0.0.4 includes: +// - Adjusted to use Adafruit fonts for MCUFRIEND_kbv +// 0.0.3 includes: +// - Fixed TFT_eSPI row height calculations and overwrite of previous text +// - Moved to using TFT_eSPI fonts only +// - Refactored EXScreen and associated classes +// - Calculate max rows and columns based on resolution and specified font size +// 0.0.2 includes: +// - Adding TFT_eSPI library +// - Implement vertical scrolling +// 0.0.1 includes: +// - First version number for initial development + +#endif diff --git a/config.example.h b/config.example.h deleted file mode 100644 index 21d35a6..0000000 --- a/config.example.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef CONFIG_H -#define CONFIG_H - -// DO NOT REMOVE THIS LINE -// Include the various fonts and font colours available for use with EX-Display -#include "FontOptions.h" - -// This is the configuration file for EX-Display -// Entries here will control how the screen display will operate - -// Define the screen types in use here - this needs to be ultra simplified at some point -// Should we eventually support up to 3 displays? -#define SCREEN_0_TYPE MCU -// #define SCREEN_0_TYPE TFT -// #define SCREEN_1_TYPE MCU -// #define SCREEN_2_TYPE MCU - -// Font options -#define TEXT_FONT ARIAL9PT7B -#define TEXT_COLOUR WHITE -#define TEXT_SIZE 1 -#define BACKGROUND_COLOUR BLACK - -// If rotation of the screen is required, set it here -#define SCREEN_ROTATION 1 - -// First entries about the display you are using -// as we display across the wider part of the screen iwdth is the larger dimension -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 240 - -// Now we have to define how many screens you wish to display -// The system output screen is 0. -// An arduino Uno is capable of storing and displaying about 60 lines -// before running out of RAM e.g. (using 30 char lines) -// 4 screens of 16 lines -// 6 screens of 10 lines -// The first paramater tells EX-Display which initial screen to display -#define INITIAL_SCREEN 0 - -// Now define how many screens you wish to be used in turn -#define MAX_SCREENS 2 -#define MAX_ROWS 10 - -// If a touch screen is in use a touch will swap from screen to screen -// if in use uncomment the following -// #define USE_TOUCH - -// if the touch screen not in use, Now define the scroll requirements -#define SCROLLTIME 5000 // 5 seconds - -// And now define the maximum text length. -#define MAX_LINE_LENGTH 30 - -// The startup Timelapse - allows time to handle all the CS output -#define STARTUP_TIME 2000 - -// Uncomment this line to display debug output -#define DEBUG - -#endif \ No newline at end of file diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in new file mode 100644 index 0000000..579e997 --- /dev/null +++ b/docs/Doxyfile.in @@ -0,0 +1,2865 @@ +# Doxyfile 1.9.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "EX-Display" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "EX-Display" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = "_build" + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = ino=C++ + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ../ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, +# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cxxm \ + *.cpp \ + *.cppm \ + *.c++ \ + *.c++m \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.ixx \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice \ + *.ino + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = _build + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /