├── .clang-format ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── documentation_update.yml │ ├── feature_request.yml │ ├── support_request.yml │ └── to_do.yml └── workflows │ ├── arduino-lint.yml │ ├── docs.yml │ ├── new-items.yml │ └── tests.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── FindArduinoCore-API.cmake └── sanitize.cmake ├── docs ├── CMakeLists.txt ├── Doxyfile.in ├── Makefile ├── _static │ ├── css │ │ ├── dccex_theme.css │ │ └── sphinx_design_overrides.css │ └── images │ │ ├── favicon.ico │ │ ├── logo.png │ │ └── product-logo-native-protocol-library.png ├── bugs-requests.rst ├── conf.py ├── contribute.rst ├── examples.rst ├── include │ └── include.rst ├── index.rst ├── library.rst ├── make.bat ├── make2 github.bat ├── make2.bat ├── overview.rst ├── robots.txt ├── site-index.rst ├── tests.rst └── usage.rst ├── examples ├── DCCEXProtocol_Basic │ ├── DCCEXProtocol_Basic.ino │ └── config.example.h ├── DCCEXProtocol_Consist_Control │ ├── DCCEXProtocol_Consist_Control.ino │ └── config.example.h ├── DCCEXProtocol_Delegate │ ├── DCCEXProtocol_Delegate.ino │ └── config.example.h ├── DCCEXProtocol_Loco_Control │ ├── DCCEXProtocol_Loco_Control.ino │ └── config.example.h ├── DCCEXProtocol_Multi_Throttle_Control │ ├── DCCEXProtocol_Multi_Throttle_Control.ino │ └── config.example.h ├── DCCEXProtocol_Roster_etc │ ├── DCCEXProtocol_Roster_etc.ino │ └── config.example.h ├── DCCEXProtocol_SSID │ └── DCCEXProtocol_SSID.ino ├── DCCEXProtocol_Serial │ ├── DCCEXProtocol_Serial.ino │ └── config.example.h ├── DCCEXProtocol_Track_type │ ├── DCCEXProtocol_Track_type.ino │ └── config.example.h ├── DCCEXProtocol_Turnout_Control │ ├── DCCEXProtocol_Turnout_Control.ino │ └── config.example.h └── DCCEXProtocol_mDNS │ ├── DCCEXProtocol_mDNS.ino │ └── config.example.h ├── image-artifacts └── DCC-EX_product-native-protocol-library.svg ├── library.properties ├── requirements.txt ├── src ├── DCCEXInbound.cpp ├── DCCEXInbound.h ├── DCCEXLoco.cpp ├── DCCEXLoco.h ├── DCCEXProtocol.cpp ├── DCCEXProtocol.h ├── DCCEXRoutes.cpp ├── DCCEXRoutes.h ├── DCCEXTurnouts.cpp ├── DCCEXTurnouts.h ├── DCCEXTurntables.cpp └── DCCEXTurntables.h └── tests ├── CMakeLists.txt ├── general ├── test_DCCEXProtocol.cpp ├── test_Message.cpp ├── test_TrackPowerControl.cpp ├── test_TrackPowerParsing.cpp └── test_Version.cpp ├── loco ├── test_Consist.cpp ├── test_Loco.cpp ├── test_LocoUpdate.cpp └── test_RosterParsing.cpp ├── readWriteCVs ├── test_readCVs.cpp └── test_writeCVs.cpp ├── route ├── test_RouteParsing.cpp └── test_Routes.cpp ├── setup ├── CVTests.h ├── DCCEXProtocolDelegateMock.hpp ├── DCCEXProtocolTests.h ├── LocoTests.h ├── MockSetup.cpp ├── MockSetup.h ├── RouteTests.h ├── TestHarnessBase.cpp ├── TestHarnessBase.hpp ├── TurnoutTests.h └── TurntableTests.h ├── turnout ├── test_TurnoutParsing.cpp └── test_Turnouts.cpp └── turntable ├── test_TurntableParsing.cpp └── test_Turntables.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Right 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: Never 16 | AllowShortCaseLabelsOnASingleLine: false 17 | AllowShortFunctionsOnASingleLine: All 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: Never 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: MultiLine 25 | BinPackArguments: true 26 | BinPackParameters: true 27 | BraceWrapping: 28 | AfterCaseLabel: false 29 | AfterClass: false 30 | AfterControlStatement: false 31 | AfterEnum: false 32 | AfterFunction: false 33 | AfterNamespace: false 34 | AfterObjCDeclaration: false 35 | AfterStruct: false 36 | AfterUnion: false 37 | AfterExternBlock: false 38 | BeforeCatch: false 39 | BeforeElse: false 40 | IndentBraces: false 41 | SplitEmptyFunction: true 42 | SplitEmptyRecord: true 43 | SplitEmptyNamespace: true 44 | BreakBeforeBinaryOperators: None 45 | BreakBeforeBraces: Attach 46 | BreakBeforeInheritanceComma: false 47 | BreakInheritanceList: BeforeColon 48 | BreakBeforeTernaryOperators: true 49 | BreakConstructorInitializersBeforeComma: false 50 | BreakConstructorInitializers: BeforeColon 51 | BreakAfterJavaFieldAnnotations: false 52 | BreakStringLiterals: true 53 | ColumnLimit: 120 54 | CommentPragmas: '^ IWYU pragma:' 55 | CompactNamespaces: false 56 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 57 | ConstructorInitializerIndentWidth: 4 58 | ContinuationIndentWidth: 4 59 | Cpp11BracedListStyle: true 60 | DeriveLineEnding: true 61 | DerivePointerAlignment: false 62 | DisableFormat: false 63 | ExperimentalAutoDetectBinPacking: false 64 | FixNamespaceComments: true 65 | ForEachMacros: 66 | - foreach 67 | - Q_FOREACH 68 | - BOOST_FOREACH 69 | IncludeBlocks: Preserve 70 | IncludeCategories: 71 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 72 | Priority: 2 73 | SortPriority: 0 74 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 75 | Priority: 3 76 | SortPriority: 0 77 | - Regex: '.*' 78 | Priority: 1 79 | SortPriority: 0 80 | IncludeIsMainRegex: '(Test)?$' 81 | IncludeIsMainSourceRegex: '' 82 | IndentCaseLabels: false 83 | IndentGotoLabels: true 84 | IndentPPDirectives: None 85 | IndentWidth: 2 86 | IndentWrappedFunctionNames: false 87 | JavaScriptQuotes: Leave 88 | JavaScriptWrapImports: true 89 | KeepEmptyLinesAtTheStartOfBlocks: true 90 | MacroBlockBegin: '' 91 | MacroBlockEnd: '' 92 | MaxEmptyLinesToKeep: 1 93 | NamespaceIndentation: None 94 | ObjCBinPackProtocolList: Auto 95 | ObjCBlockIndentWidth: 2 96 | ObjCSpaceAfterProperty: false 97 | ObjCSpaceBeforeProtocolList: true 98 | PenaltyBreakAssignment: 2 99 | PenaltyBreakBeforeFirstCallParameter: 19 100 | PenaltyBreakComment: 300 101 | PenaltyBreakFirstLessLess: 120 102 | PenaltyBreakString: 1000 103 | PenaltyBreakTemplateDeclaration: 10 104 | PenaltyExcessCharacter: 1000000 105 | PenaltyReturnTypeOnItsOwnLine: 60 106 | PointerAlignment: Right 107 | ReflowComments: true 108 | SortIncludes: true 109 | SortUsingDeclarations: true 110 | SpaceAfterCStyleCast: false 111 | SpaceAfterLogicalNot: false 112 | SpaceAfterTemplateKeyword: true 113 | SpaceBeforeAssignmentOperators: true 114 | SpaceBeforeCpp11BracedList: false 115 | SpaceBeforeCtorInitializerColon: true 116 | SpaceBeforeInheritanceColon: true 117 | SpaceBeforeParens: ControlStatements 118 | SpaceBeforeRangeBasedForLoopColon: true 119 | SpaceInEmptyBlock: false 120 | SpaceInEmptyParentheses: false 121 | SpacesBeforeTrailingComments: 1 122 | SpacesInAngles: false 123 | SpacesInConditionalStatement: false 124 | SpacesInContainerLiterals: true 125 | SpacesInCStyleCastParentheses: false 126 | SpacesInParentheses: false 127 | SpacesInSquareBrackets: false 128 | SpaceBeforeSquareBrackets: false 129 | Standard: Latest 130 | StatementMacros: 131 | - Q_UNUSED 132 | - QT_REQUIRE_VERSION 133 | TabWidth: 8 134 | UseCRLF: false 135 | UseTab: Never 136 | ... 137 | 138 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | # Bug report GitHub issue form 2 | # 3 | # This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | name: Bug Report 6 | description: Submit a bug report 7 | labels: 8 | - Bug 9 | title: "Bug Report: " 10 | body: 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Thanks for taking the time to submit a bug report to the DCC-EX team! 15 | 16 | In order to help us to validate the bug and ascertain what's causing it, please provide as much information as possible in this form. 17 | 18 | - type: input 19 | id: version 20 | attributes: 21 | label: Version 22 | description: Please provide the version of the DCCEXProtocol Arduino library in use. 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | id: description 28 | attributes: 29 | label: Bug description 30 | description: Please provide a clear and concise description of what the symptoms of the bug are. 31 | validations: 32 | required: true 33 | 34 | - type: textarea 35 | id: reproduction 36 | attributes: 37 | label: Steps to reproduce the bug 38 | description: Please provide the steps to reproduce the behaviour. 39 | validations: 40 | required: true 41 | 42 | - type: textarea 43 | id: expectation 44 | attributes: 45 | label: Expected behaviour 46 | description: Please provide a clear and concise description of what you expected to happen. 47 | validations: 48 | required: true 49 | 50 | - type: textarea 51 | id: screenshots 52 | attributes: 53 | label: Screenshots 54 | description: If applicable, upload any screenshots here. 55 | 56 | - type: textarea 57 | id: extra-context 58 | attributes: 59 | label: Additional context 60 | description: Please provide any other relevant information that could help us resolve this issue, including the chosen configuration options. 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Configuration file for the template chooser 2 | # 3 | # This file needs to exist in the https://github.com/DCC-EX/.github repository in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | blank_issues_enabled: false 6 | contact_links: 7 | - name: DCC-EX Discord server 8 | url: https://discord.gg/9DDjjpxzHB 9 | about: For the best support experience, join our Discord server 10 | - name: DCC-EX Contact and Support page 11 | url: https://dcc-ex.com/support/index.html 12 | about: For other support options, refer to our Contact & Support page -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_update.yml: -------------------------------------------------------------------------------- 1 | # Documentation update GitHub issue form 2 | # 3 | # This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | name: Documentation Update 6 | description: Submit a request for documentation updates, or to report broken links or inaccuracies 7 | title: "[Documentation Update]: " 8 | labels: 9 | - Needs Documentation 10 | body: 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Use this template to submit a request for updates to our documentation. 15 | 16 | This can be used for general documentation requests if information is missing or lacking, or to correct issues with our existing documentation such as broken links, or inaccurate information. 17 | 18 | - type: textarea 19 | id: details 20 | attributes: 21 | label: Documentation details 22 | description: Provide the details of what needs to be documented or corrected. 23 | validations: 24 | required: true 25 | 26 | - type: input 27 | id: page 28 | attributes: 29 | label: Page with issues 30 | description: If reporting broken links or inaccuracies, please provide the link to the page here. 31 | placeholder: https://dcc-ex.com/DCCEXProtocol/index.html -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | # Feature Request GitHub issue form 2 | # 3 | # This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | name: Feature Request 6 | description: Suggest a new feature 7 | title: "[Feature Request]: " 8 | labels: 9 | - Enhancement 10 | body: 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Use this template to suggest a new feature for the DCCEXProtocol Arduino library. 15 | 16 | - type: textarea 17 | id: description 18 | attributes: 19 | label: Problem/idea statement 20 | description: Please provide the problem you're trying to solve, or share the idea you have. 21 | placeholder: A clear and concise description of the problem you're trying to solve, or the idea you have. 22 | validations: 23 | required: true 24 | 25 | - type: textarea 26 | id: alternatives 27 | attributes: 28 | label: Alternatives or workarounds 29 | description: Please provide any alternatives or workarounds you currently use. 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | id: context 35 | attributes: 36 | label: Additional context 37 | description: Add any other context, screenshots, or files related to the feature request here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support_request.yml: -------------------------------------------------------------------------------- 1 | # Support Request GitHub issue form 2 | # 3 | # This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | name: Support Request 6 | description: Request support or assistance 7 | title: "[Support Request]: " 8 | labels: 9 | - Support Request 10 | body: 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Use this template to request support or assistance with the DCCEXProtocol Arduino library. 15 | 16 | - type: input 17 | id: version 18 | attributes: 19 | label: Version 20 | description: Please provide the version of the library in use. 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | id: description 26 | attributes: 27 | label: Issue description 28 | description: Please describe the issue being encountered as accurately and detailed as possible. 29 | validations: 30 | required: true 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/to_do.yml: -------------------------------------------------------------------------------- 1 | # General To Do item GitHub issue form 2 | # 3 | # This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. 4 | 5 | name: To Do 6 | description: Create a general To Do item 7 | title: "[To Do]: " 8 | labels: 9 | - To Do 10 | body: 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Use this template to create an issue for a general task that needs to be done. 15 | 16 | This is handy for capturing ad-hoc items that don't necessarily require code to be written or updated. 17 | 18 | - type: textarea 19 | id: description 20 | attributes: 21 | label: Task description 22 | description: Provide the details of what needs to be done. 23 | validations: 24 | required: true -------------------------------------------------------------------------------- /.github/workflows/arduino-lint.yml: -------------------------------------------------------------------------------- 1 | name: Arduino-lint 2 | 3 | on: [push, pull_request] 4 | jobs: 5 | lint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4.1.1 9 | with: 10 | token: ${{ secrets.GITHUB_TOKEN }} 11 | - uses: arduino/arduino-lint-action@v1 12 | with: 13 | token: ${{ secrets.GITHUB_TOKEN }} 14 | library-manager: update 15 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repo 12 | uses: actions/checkout@v4.1.1 13 | - name: Requirements 14 | run: | 15 | pip3 install -r requirements.txt 16 | sudo apt-get install doxygen 17 | - name: Build docs 18 | run: | 19 | cd docs 20 | make html 21 | touch _build/html/.nojekyll 22 | - name: Deploy 23 | uses: JamesIves/github-pages-deploy-action@ba1486788b0490a235422264426c45848eac35c6 24 | with: 25 | token: ${{ secrets.GITHUB_TOKEN }} 26 | branch: gh-pages # The branch the action should deploy to. 27 | folder: docs/_build/html # The folder the action should deploy. 28 | -------------------------------------------------------------------------------- /.github/workflows/new-items.yml: -------------------------------------------------------------------------------- 1 | # This workflow is to be used for all repositories to integrate with the DCC++ EX Beta Project. 2 | # It will add all issues and pull requests for a repository to the project, and put in the correct status. 3 | # 4 | # Ensure "REPO_LABEL" is updated with the correct label for the repo stream of work. 5 | name: Add Issue or Pull Request to Project 6 | 7 | env: 8 | REPO_LABEL: ${{ secrets.PROJECT_STREAM_LABEL }} 9 | PROJECT_NUMBER: 7 10 | ORG: DCC-EX 11 | 12 | on: 13 | issues: 14 | types: 15 | - opened 16 | pull_request_target: 17 | types: 18 | - ready_for_review 19 | - opened 20 | - review_requested 21 | 22 | jobs: 23 | add_to_project: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Add labels 27 | uses: andymckay/labeler@master 28 | with: 29 | add-labels: ${{ env.REPO_LABEL }} 30 | 31 | - name: Generate token 32 | id: generate_token 33 | uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0 34 | with: 35 | app_id: ${{ secrets.PROJECT_APP_ID }} 36 | private_key: ${{ secrets. PROJECT_APP_KEY }} 37 | 38 | - name: Get project data 39 | env: 40 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 41 | run: | 42 | gh api graphql -f query=' 43 | query($org: String!, $number: Int!) { 44 | organization(login: $org){ 45 | projectV2(number: $number) { 46 | id 47 | fields(first:20) { 48 | nodes { 49 | ... on ProjectV2Field { 50 | id 51 | name 52 | } 53 | ... on ProjectV2SingleSelectField { 54 | id 55 | name 56 | options { 57 | id 58 | name 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } 65 | }' -f org=$ORG -F number=$PROJECT_NUMBER > project_data.json 66 | 67 | echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV 68 | echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV 69 | echo 'BACKLOG_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="Backlog") |.id' project_data.json) >> $GITHUB_ENV 70 | echo 'TO_DO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="To Do") |.id' project_data.json) >> $GITHUB_ENV 71 | echo 'NEEDS_REVIEW_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="Needs Review") |.id' project_data.json) >> $GITHUB_ENV 72 | echo 'IN_PROGRESS_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="In Progress") |.id' project_data.json) >> $GITHUB_ENV 73 | 74 | - name: Add issue to project 75 | env: 76 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 77 | ITEM_ID: ${{ github.event.issue.node_id }} 78 | if: github.event_name == 'issues' 79 | run: | 80 | project_item_id="$( gh api graphql -f query=' 81 | mutation($project:ID!, $item:ID!) { 82 | addProjectV2ItemById(input: {projectId: $project, contentId: $item}) { 83 | item { 84 | id 85 | } 86 | } 87 | }' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectV2ItemById.item.id')" 88 | echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV 89 | 90 | - name: Add PR to project 91 | env: 92 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 93 | ITEM_ID: ${{ github.event.pull_request.node_id }} 94 | if: github.event_name == 'pull_request' 95 | run: | 96 | project_item_id="$( gh api graphql -f query=' 97 | mutation($project:ID!, $item:ID!) { 98 | addProjectV2ItemById(input: {projectId: $project, contentId: $item}) { 99 | item { 100 | id 101 | } 102 | } 103 | }' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectV2ItemById.item.id')" 104 | echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV 105 | 106 | - name: Set status - To Do 107 | env: 108 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 109 | if: github.event_name == 'issues' && (contains(github.event.*.labels.*.name, 'Bug') || contains(github.event.*.labels.*.name, 'Support Request')) 110 | run: | 111 | gh api graphql -f query=' 112 | mutation( 113 | $project: ID! 114 | $item: ID! 115 | $status_field: ID! 116 | $status_value: String! 117 | ){ 118 | set_status: updateProjectV2ItemFieldValue(input: { 119 | projectId: $project 120 | itemId: $item 121 | fieldId: $status_field 122 | value: { 123 | singleSelectOptionId: $status_value 124 | } 125 | }) { 126 | projectV2Item { 127 | id 128 | } 129 | } 130 | }' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.TO_DO_OPTION_ID }} --silent 131 | 132 | - name: Set status - Review 133 | env: 134 | GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 135 | if: github.event_name == 'issues' && (contains(github.event.*.labels.*.name, 'Unit Tested') || contains(github.event.*.labels.*.name, 'Regression Tested') || contains(github.event.*.labels.*.name, 'Needs Review')) || github.event_name == 'pull_request' 136 | run: | 137 | gh api graphql -f query=' 138 | mutation( 139 | $project: ID! 140 | $item: ID! 141 | $status_field: ID! 142 | $status_value: String! 143 | ){ 144 | set_status: updateProjectV2ItemFieldValue(input: { 145 | projectId: $project 146 | itemId: $item 147 | fieldId: $status_field 148 | value: { 149 | singleSelectOptionId: $status_value 150 | } 151 | }) { 152 | projectV2Item { 153 | id 154 | } 155 | } 156 | }' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.NEEDS_REVIEW_OPTION_ID }} --silent 157 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Run GoogleTest 2 | 3 | on: 4 | push: 5 | branches: [main, devel] 6 | pull_request: 7 | branches: [main, devel] 8 | 9 | jobs: 10 | run-tests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4.1.1 14 | with: 15 | fetch-depth: 0 16 | - name: Prepare build directory 17 | run: cmake -Bbuild 18 | - name: Compile tests 19 | run: cmake --build build --parallel --target DCCEXProtocolTests 20 | - name: Run tests 21 | run: ./build/tests/DCCEXProtocolTests --gtest_shuffle --gtest_repeat=5 --gtest_recreate_environments_when_repeating 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | 15 | # Emacs backup files 16 | *~ 17 | .vscode 18 | 19 | # example VCS files 20 | examples/*/.pio 21 | examples/*/.pio/*.* 22 | examples/*/.vscode 23 | examples/*/.vscode/*.* 24 | examples/*/.gitignore 25 | examples/*/platform.ini 26 | 27 | examples/*/config.h 28 | 29 | # Documentation build folders 30 | build 31 | venv 32 | docs/html 33 | docs/latex 34 | _build 35 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11 FATAL_ERROR) 2 | include(FetchContent) 3 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) 4 | 5 | project(DCCEXProtocol LANGUAGES CXX) 6 | 7 | file(GLOB_RECURSE SRC src/*.cpp) 8 | add_library(DCCEXProtocol STATIC ${SRC}) 9 | add_library(DCCEX::Protocol ALIAS DCCEXProtocol) 10 | 11 | # Stuck at C++11 12 | target_compile_options(DCCEXProtocol PRIVATE -std=c++11) 13 | 14 | # Don't bother users with warnings by setting 'SYSTEM' 15 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) 16 | target_include_directories(DCCEXProtocol PUBLIC src) 17 | else() 18 | target_include_directories(DCCEXProtocol SYSTEM PUBLIC src) 19 | endif() 20 | 21 | # Fetch ArduinoCore-API 22 | if(NOT TARGET ArduinoCore-API) 23 | FetchContent_Declare( 24 | ArduinoCore-API 25 | GIT_REPOSITORY https://github.com/arduino/ArduinoCore-API 26 | GIT_TAG 1.4.0) 27 | FetchContent_Populate(ArduinoCore-API) 28 | find_package(ArduinoCore-API) 29 | endif() 30 | 31 | target_link_libraries(DCCEXProtocol PUBLIC ArduinoCore-API) 32 | 33 | foreach(FILE ${SRC}) 34 | message(${FILE}) 35 | endforeach() 36 | 37 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SRC}) 38 | 39 | if(PROJECT_IS_TOP_LEVEL) 40 | add_subdirectory(docs) 41 | add_subdirectory(tests) 42 | endif() 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # DCCEXProtocol 3 | 4 | For the full documentation, please refer to the [DCC-EX website](https://dcc-ex.com/DCCEXProtocol/index.html). 5 | 6 | # Credits 7 | 8 | The delegate and connection code in this library is taken directly from the WiThrottle library by **Copyright © 2018-2019 Blue Knobby Systems Inc.** 9 | The rest of the code has been developed by Peter Akers (Flash62au), Peter Cole (peteGSX), and Chris Harlow (UKBloke). 10 | 11 | ---- 12 | 13 | # DCC-EX Native command protocol library 14 | 15 | This library implements the DCC-EX Native command protocol (as used in EX-CommandStation ONLY), allowing a device to connect to the server and act as a client (such as a hardware based throttle). 16 | 17 | The implementation of this library is tested on ESP32 based devices running the Arduino framework. There's nothing in here that's specific to the ESP32, and little of Arduino that couldn't be replaced as needed. 18 | 19 | There has also been limited testing on STM32F103C8 Bluepill. 20 | 21 | ## Basic Design Principles 22 | 23 | First of all, this library implements the DCC-EX Native protocol in a non-blocking fashion. After creating a DCCEXProtocol object, you set up various necessities such as the network connection and a debug console (see [Dependency Injection][depinj]). 24 | 25 | Then, you call the ```check()``` method as often as you can (ideally, once per invocation of the ```loop()``` method) and the library will manage the I/O stream, reading in/parsing commands and calling methods on the [delegate] as information is available. 26 | 27 | These patterns (Dependency Injection and Delegation) allow you to keep the different parts of your sketch from becoming too intertwined with each other. Nothing in the code that manages the pushbuttons or speed knobs needs to have any detailed knowledge of the DCC-EX native protocol. 28 | 29 | ### DCCEXProtocol Class 30 | 31 | Full documentation of the classes is available via the [DCC-EX website](https://dcc-ex.com/DCCEXProtocol/index.html). 32 | 33 | The DCCEXProtocol class manages all relevant objects advertised by a DCC-EX EX-CommandStation and exposes simple methods to control these objects from the client software. 34 | 35 | These objects include: 36 | 37 | - Roster entries 38 | - Route entries 39 | - Turnouts/Points 40 | - Turntables (noting that these objects are only available in development versions) 41 | 42 | This means the client software does not need to explicitly manage the state of these objects whilever the ```check()``` method mentioned above is called appropriately. 43 | 44 | ### DCCEXProtocolDelegate Class 45 | 46 | The DCCEXProtocolDelegate class enables the client software to respond to various events generated by a DCC-EX EX-CommandStation as either broadcasts or responses to commands. 47 | 48 | The events able to be managed via this class are over and above those managed by the DCCEXProtocol class and are entirely customisable by the client software to provide dynamic user experience updates such as displaying status changes to objects as they are broadcast from the DCC-EX EX-CommandStation. 49 | 50 | # Documentation 51 | 52 | Documentation of the DCCEXProtocol library is available via the [DCC-EX website](file:///C:/Code/DCCEXProtocol/docs/_build/html/index.html). 53 | 54 | For contributors wishing to build local copies of the documentation while updating the library, here is the very high level process of the requirements to make this work on Windows: 55 | 56 | - Install [MSYS2 C++](https://code.visualstudio.com/docs/cpp/config-mingw#_prerequisites) compilers 57 | - Install [CMake](https://cmake.org/download/) and ensure you select the option to add to your user path 58 | - Install [Doxygen](https://www.doxygen.nl/download.html) and once complete, add to your user path 59 | - Install the CMake Tools extension in VSCode 60 | - Setup a Python virtual environment with "virtualenv venv" and activate with "venv\scripts\activate" 61 | - Install required Python modules with "pip3 install -r requirements.txt" 62 | - Change to the docs directory and run "make html" 63 | 64 | ---- 65 | 66 | # License 67 | 68 | Creative Commons [CC-BY-SA 4.0][CCBYSA] ![CCBYSA](https://i.creativecommons.org/l/by-sa/4.0/88x31.png) 69 | 70 | **Free Software, Oh Yeah!** 71 | 72 | [//]: # (These are reference links used in the body of this note and get stripped out when the markdown processor does its job. There is no need to format nicely because it shouldn't be seen. Thanks SO - http://stackoverflow.com/questions/4823468/store-comments-in-markdown-syntax) 73 | 74 | [depinj]: 75 | [delegate]: 76 | [CCBYSA]: 77 | -------------------------------------------------------------------------------- /cmake/FindArduinoCore-API.cmake: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE SRC ${arduinocore-api_SOURCE_DIR}/api/*.cpp) 2 | add_library(ArduinoCore-API STATIC ${SRC}) 3 | 4 | # Prevent redeclaration of int atexit(void (*func)()) 5 | target_compile_definitions(ArduinoCore-API PUBLIC -DHOST) 6 | 7 | target_include_directories( 8 | ArduinoCore-API PUBLIC ${arduinocore-api_BINARY_DIR} 9 | ${arduinocore-api_SOURCE_DIR}/api) 10 | 11 | # Create Arduino.h header 12 | file( 13 | WRITE ${arduinocore-api_BINARY_DIR}/Arduino.h # 14 | "#ifndef Arduino_h\n" # 15 | "#define Arduino_h\n" # 16 | "#include \"ArduinoAPI.h\"\n" # 17 | "#endif" # 18 | ) 19 | -------------------------------------------------------------------------------- /cmake/sanitize.cmake: -------------------------------------------------------------------------------- 1 | macro(sanitize SANITIZERS) 2 | # Set in current scope 3 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=${SANITIZERS}") 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=${SANITIZERS}") 5 | set(LDFLAGS "${LDFLAGS} -fsanitize=${SANITIZERS}") 6 | 7 | # Set in PARENT_SCOPE 8 | get_directory_property(HAS_PARENT_SCOPE PARENT_DIRECTORY) 9 | if(HAS_PARENT_SCOPE) 10 | set(CMAKE_C_FLAGS 11 | ${CMAKE_C_FLAGS} 12 | PARENT_SCOPE) 13 | set(CMAKE_CXX_FLAGS 14 | ${CMAKE_CXX_FLAGS} 15 | PARENT_SCOPE) 16 | set(LDFLAGS 17 | ${LDFLAGS} 18 | PARENT_SCOPE) 19 | endif() 20 | endmacro() 21 | -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Doxygen) 2 | 3 | if(DOXYGEN_FOUND) 4 | message("Found Doxygen") 5 | set(DOXYGEN_IN ${CMAKE_CURRENT_LIST_DIR}/Doxyfile.in) 6 | set(DOXYGEN_OUT ${CMAKE_BINARY_DIR}/Doxyfile.out) 7 | 8 | # request to configure the file 9 | configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) 10 | message("Doxygen build started") 11 | 12 | # Note: do not put "ALL" - this builds docs together with application EVERY 13 | # TIME! 14 | add_custom_target( 15 | docs 16 | COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} 17 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 18 | COMMENT "Generating API documentation with Doxygen" 19 | VERBATIM) 20 | else() 21 | message("Doxygen need to be installed to generate the doxygen documentation") 22 | endif() 23 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/css/sphinx_design_overrides.css: -------------------------------------------------------------------------------- 1 | /* Override for the sphinx-design extension classes */ 2 | .sd-card-header { 3 | font-size: 110% !important; 4 | font-family: Audiowide,Helvetica,Arial,sans-serif !important; 5 | font-weight: 500 !important; 6 | color: #00a3b9ff; 7 | text-shadow: 1px 1px 0 #00353dff; 8 | margin-bottom: .5rem !important; 9 | } -------------------------------------------------------------------------------- /docs/_static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DCC-EX/DCCEXProtocol/03e54a822fa90460e15e0daeeaeffb6acc1087cc/docs/_static/images/favicon.ico -------------------------------------------------------------------------------- /docs/_static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DCC-EX/DCCEXProtocol/03e54a822fa90460e15e0daeeaeffb6acc1087cc/docs/_static/images/logo.png -------------------------------------------------------------------------------- /docs/_static/images/product-logo-native-protocol-library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DCC-EX/DCCEXProtocol/03e54a822fa90460e15e0daeeaeffb6acc1087cc/docs/_static/images/product-logo-native-protocol-library.png -------------------------------------------------------------------------------- /docs/bugs-requests.rst: -------------------------------------------------------------------------------- 1 | Bugs and Requests 2 | ================= 3 | 4 | To report any bugs or raise support/feature requests for the library, please use our handy GitHub issue templates provided in the `DCCEXProtocol GitHub repository `_. 5 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # from sphinx.builders.html import StandaloneHTMLBuilder 7 | import subprocess 8 | # import os 9 | 10 | # Doxygen 11 | subprocess.call('doxygen Doxyfile.in', shell=True) 12 | 13 | # -- Project information ----------------------------------------------------- 14 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 15 | 16 | project = 'DCCEXProtocol' 17 | copyright = '2023 - Peter Cole, Peter Akers' 18 | author = 'Peter Cole, Peter Akers' 19 | 20 | # -- General configuration --------------------------------------------------- 21 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 22 | 23 | extensions = [ 24 | 'sphinx_sitemap', 25 | 'sphinxcontrib.spelling', 26 | 'sphinx_rtd_dark_mode', 27 | 'breathe' 28 | ] 29 | 30 | autosectionlabel_prefix_document = True 31 | 32 | # Don't make dark mode the user default 33 | default_dark_mode = False 34 | 35 | spelling_lang = 'en_UK' 36 | tokenizer_lang = 'en_UK' 37 | spelling_word_list_filename = ['spelling_wordlist.txt'] 38 | 39 | templates_path = ['_templates'] 40 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 41 | 42 | highlight_language = 'c++' 43 | 44 | numfig = True 45 | 46 | numfig_format = {'figure': 'Figure %s'} 47 | 48 | # -- Options for HTML output ------------------------------------------------- 49 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 50 | 51 | html_theme = 'sphinx_rtd_theme' 52 | html_static_path = ['_static'] 53 | 54 | html_logo = "./_static/images/product-logo-native-protocol-library.png" 55 | 56 | html_favicon = "./_static/images/favicon.ico" 57 | 58 | html_theme_options = { 59 | 'style_nav_header_background': 'white', 60 | 'logo_only': True, 61 | # Toc options 62 | 'includehidden': True, 63 | 'titles_only': False, 64 | # 'titles_only': True, 65 | 'collapse_navigation': False, 66 | # 'navigation_depth': 3, 67 | 'navigation_depth': -1, 68 | 'analytics_id': 'G-L5X0KNBF0W', 69 | } 70 | 71 | html_context = { 72 | 'display_github': True, 73 | 'github_user': 'DCC-EX', 74 | 'github_repo': 'dccexprotocol', 75 | 'github_version': 'sphinx/docs/', 76 | } 77 | 78 | html_css_files = [ 79 | 'css/dccex_theme.css', 80 | 'css/sphinx_design_overrides.css', 81 | ] 82 | 83 | # Sphinx sitemap 84 | html_baseurl = 'https://dcc-ex.com/DCCEXProtocol/' 85 | html_extra_path = [ 86 | 'robots.txt', 87 | ] 88 | 89 | # -- Breathe configuration ------------------------------------------------- 90 | 91 | breathe_projects = { 92 | "DCCEXProtocol": "_build/xml/" 93 | } 94 | breathe_default_project = "DCCEXProtocol" 95 | breathe_default_members = () 96 | -------------------------------------------------------------------------------- /docs/contribute.rst: -------------------------------------------------------------------------------- 1 | .. include:: /include/include.rst 2 | 3 | Contributions 4 | ============= 5 | 6 | The |DCC-EX| team welcomes contributions to the DCCEXProtocol library. 7 | 8 | The best way to get involved is to reach out to the |DCC-EX| team via our `Discord server `_. 9 | 10 | You can also try the other methods outlined on our `Contact Us page `_. 11 | 12 | Library Maintenance 13 | ------------------- 14 | 15 | As this library is designed to be available via the Arduino Library Manager, there are certain requirements that must be adhered to when maintaining and updating the library. 16 | 17 | You can see DCCEXProtocol in the `Arduino Library Reference `_. 18 | 19 | For details of the specific requirements of maintaining Arduino libraries, you will need to familiarise yourself with these by reviewing the `Arduino Library Manager FAQ `_. 20 | 21 | Specifically, these are some items of note when making changes to the library: 22 | 23 | - Ensure all public classes, methods, and attributes are documented in the code 24 | - When adding new methods or attributes, use human-friendly names that indicate the desired purpose 25 | - When adding a new version, ensure version notes are added to DCCEXProtocol.h 26 | 27 | To ensure the Arduino Library Manager flags that a version update has been made, these activities must be performed: 28 | 29 | - Update the "library.properties" file with the new version number 30 | - When pushing updates to the "main" branch, ensure that the GitHub workflow "arduino-lint.yml" completes without error 31 | - Add a GitHub tag to the repo with the version in the format "v0.0.1-Devel" or "v0.0.1-Prod" 32 | 33 | Once an update has been made that changes the version, this will trigger the Arduino Library Manager to scan the DCCEXProtocol for changes and, providing no issues are encountered, will publish the new version for users to download or update. 34 | 35 | The Arduino Library Manager will output logs for each library maintained which we can review for errors or issues. The `DCCEXProtocol library logs `_ are available here. 36 | 37 | Documentation 38 | ------------- 39 | 40 | Library documentation is created automatically when pushes and pull requests are merged to the "main" branch via the GitHub "docs.yml" workflow. 41 | 42 | The DCCEXProtocol class documentation is generated automatically via Doxygen and the Sphinx Breathe extension to convert code documentation to ReStructuredText, which Sphinx then generates the HTML content from. 43 | 44 | For contributors wishing to build local copies of the documentation while updating the library, here is the very high level process of the requirements to make this work on Windows: 45 | 46 | - Install `MSYS2 C++ `_ compilers 47 | - Install `CMake `_ and ensure you select the option to add to your user path 48 | - Install `Doxygen `_ and once complete, add to your user path 49 | - Install the CMake Tools extension in VSCode 50 | - Setup a Python virtual environment with "virtualenv venv" and activate with "venv\scripts\activate" 51 | - Install required Python modules with "pip3 install -r requirements.txt" 52 | - Change to the docs directory and run "make html" 53 | 54 | Credit for how to do this to the following: 55 | 56 | - Oliver K Ernst on `Medium `_ 57 | - Sy Brand in her `Microsoft Blog `_ 58 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | .. include:: /include/include.rst 2 | 3 | Examples 4 | ======== 5 | 6 | .. sidebar:: 7 | 8 | .. contents:: On this page 9 | :depth: 2 10 | :local: 11 | 12 | Several examples have been included to demonstrate functionality of the library. 13 | 14 | Note that all included examples use a WiFi connection, but the protocol is equally suited to other connections types utilising the Arduino Stream base class including Ethernet and Serial. 15 | 16 | To configure WiFi for your settings in all examples, you will need to copy the provided "config.example.h" to "config.h" and update the parameters to suit your environment: 17 | 18 | .. code-block:: cpp 19 | 20 | const char* ssid = "YOUR_SSID_HERE"; // WiFi SSID name here 21 | const char* password = "YOUR_PASSWORD_HERE"; // WiFi password here 22 | IPAddress serverAddress(192,168,4,1); // IP address of your EX-CommandStation 23 | int serverPort = 2560; // Network port of your EX-CommandStation 24 | 25 | DCCEXProtocol_Basic 26 | ------------------- 27 | 28 | This example demonstrates the basics of creating a WiFi connection to your |EX-CS| using the library, and monitoring for broadcasts and command responses. 29 | 30 | DCCEXProtocol_Delegate 31 | ---------------------- 32 | 33 | This example builds on the basic example and, in addition, demonstrates how to implement a custom `DCCEXProtocolDelegate`` class to respond to broadcasts and command responses received from |EX-CS|. 34 | 35 | DCCEXProtocol_Roster_etc 36 | ------------------------ 37 | 38 | This example demonstrates how to retrieve the object types from |EX-CS|, and further demonstrates how to use the delegate to display these object lists when received. 39 | 40 | DCCEXProtocol_Loco_Control 41 | -------------------------- 42 | 43 | This example demonstrates basic locomotive speed and function control using dummy DCC addresses, in addition to controlling track power and further use of the delegate to notify when updates to the locomotive have been received. 44 | 45 | DCCEXProtocol_Consist_Control 46 | ----------------------------- 47 | 48 | This example demonstrates how to setup a software based consist (similar to how this is accomplished in Engine Driver), with basic speed and function control of the configured dummy locomotives. The delegate is also used to notify when updates to the configured locomotives have been received. 49 | 50 | DCCEXProtocol_Turnout_control 51 | ----------------------------- 52 | 53 | This example demonstrates the basics of controlling turnouts (or points) with the library, including being notified via the delegate when turnout/point objects have been closed/thrown. 54 | 55 | DCCEXProtocol_Multi_Throttle_Control 56 | ------------------------------------ 57 | 58 | This example demonstrates how client throttle software may be written to control multiple locomotives (or consists for that matter) concurrently. 59 | 60 | What can't be demonstrated in this example is the control of speed and direction, which would typically be accomplished with the use of rotary encoders or similar. 61 | 62 | Note when setting speed and direction, these should be sent to the |EX-CS| via the |EX-PL|, and any local references to these should be set based on the response received, not directly by the input method in use. 63 | 64 | For example, when setting the speed based on the position of a rotary encoder, send that value via the protocol's `setThrottle()` method, but do not display that speed directly. Instead, utlise the delegate's `receivedLocoUpdate()` method to update the displayed speed. 65 | 66 | This ensures that the user of the throttle sees the accurate results of what the throttle is doing, and provides validation that the EX-CommandStation is responding to the user input. 67 | 68 | DCCEXProtocol_Track_type 69 | ------------------------ 70 | 71 | This example demonstrates how client throttle software can change the Track type of any track/channel. (MAIN\|PROG\|DC\|DCX\|NONE) 72 | 73 | DCCEXProtocol_Serial 74 | -------------------- 75 | 76 | This example demonstrates how to connect to an |EX-CS| using a serial based throttle, using a dedicated serial port for the connection in addition to the standard USB serial port. 77 | 78 | This has been tested using an Arduino Mega2560, with the default USB port as the console/output serial port, and the second serial port "Serial1" as the |EX-CS| connection. 79 | 80 | ---- 81 | 82 | Additional Examples 83 | ------------------- 84 | 85 | The following examples are not strictly related to the |EX-PL|, but hopefully will be useful for anyone developing a throttle to use with any |EX-CS|. 86 | 87 | DCCEXProtocol_SSID 88 | ~~~~~~~~~~~~~~~~~~ 89 | 90 | This example demonstrates how client throttle software may be written to find all the SSIDs (networks) that are available. 91 | 92 | 93 | DCCEXProtocol_mDNS 94 | ~~~~~~~~~~~~~~~~~~ 95 | 96 | This example demonstrates how client throttle software may be written to find all the |EX-CS| and WiThrottle servers that are advertising via mDNS. 97 | 98 | Note that |DCC-EX| |EX-CS| only advertise as ``wiThrottle`` servers, but will use and respond with either the **WiThrottle protocol** or the |EX-NCP| depending the type of command it first receives from the client (throttle). 99 | 100 | -------------------------------------------------------------------------------- /docs/include/include.rst: -------------------------------------------------------------------------------- 1 | .. meta:: 2 | :keywords: DCC-EX DCC DCC++ EX DCC++EX 3 | .. 4 | .. |DCC-EX| raw:: html 5 | 6 | DCC-EX 7 | .. 8 | .. |EX-CS| raw:: html 9 | 10 | EX‑CommandStation 11 | .. 12 | .. |EX-NCL| raw:: html 13 | 14 | DCC-EX 15 | Native command protocol library 16 | .. 17 | .. |EX-P| raw:: html 18 | 19 | DCCEXProtocol 20 | .. 21 | .. |EX-PL| raw:: html 22 | 23 | DCCEXProtocol library 24 | .. 25 | .. |EX-NCP| raw:: html 26 | 27 | DCC-EX 28 | native command protocol 29 | .. 30 | .. |br| raw:: html 31 | 32 |
33 | .. 34 | .. role:: dcc-ex-red 35 | .. role:: dcc-ex-red-bold 36 | .. role:: dcc-ex-red-bold-italic 37 | .. role:: dcc-ex-code 38 | .. 39 | .. role:: dcc-ex-text-size-80pct 40 | .. role:: dcc-ex-text-size-60pct 41 | .. role:: dcc-ex-text-size-200pct 42 | .. 43 | .. |_| unicode:: 0xA0 44 | :trim: 45 | .. 46 | .. |force-break| raw:: html 47 | 48 |
49 | .. 50 | .. |image-note| raw:: html 51 | 52 | Note that you can click on any of the images to make them larger. 53 | .. -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /include/include.rst 2 | 3 | Documentation for the DCC-EX Native command protocol library - DCCEXProtocol 4 | ============================================================================ 5 | 6 | .. toctree:: 7 | :maxdepth: 4 8 | :hidden: 9 | 10 | DCC-EX Native command protocol library 11 | overview 12 | usage 13 | examples 14 | library 15 | bugs-requests 16 | tests 17 | contribute 18 | site-index 19 | 20 | DCC-EX Native command protocol library 21 | -------------------------------------- 22 | 23 | This library implements the |EX-NCP| (as used in the |DCC-EX| |EX-CS| ONLY), allowing a device to connect to the server and act as a client (such as a hardware based throttle). 24 | 25 | The implementation of this library is tested on ESP32 based devices running the Arduino framework. There's nothing in here that's specific to the ESP32, and little of Arduino that couldn't be replaced as needed. 26 | 27 | There has also been limited testing on STM32F103C8 Bluepill with a serial connection. 28 | 29 | Credits 30 | ------- 31 | 32 | The delegate and connection code in this library is taken directly from the WiThrottle library by **Copyright © 2018-2019 Blue Knobby Systems Inc.** 33 | The rest of the code has been developed by Peter Cole (peteGSX), Peter Akers (Flash62au) and Chris Harlow (UKBloke). 34 | -------------------------------------------------------------------------------- /docs/library.rst: -------------------------------------------------------------------------------- 1 | Library 2 | ======= 3 | 4 | .. doxygenindex:: 5 | :project: DCCEXProtocol 6 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/make2 github.bat: -------------------------------------------------------------------------------- 1 | "C:\Program Files\doxygen\bin\doxygen" Doxyfile.in 2 | 3 | call make2 github 4 | pause 5 | -------------------------------------------------------------------------------- /docs/make2.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | @REM set SPHINXOPTS=-E -a 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "github" ( 17 | %SPHINXBUILD% -M html %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 18 | echo "dcc-ex.com" /y > docs\_build\html\CNAME 19 | echo "" /y > docs\_build\html\.nojekyll 20 | goto end 21 | ) 22 | 23 | @REM if "%1" == "clean" ( 24 | @REM rmdir /S /Q %BUILDDIR% 25 | @REM mkdir %BUILDDIR% 26 | @REM @REM %SPHINXBUILD% -M html %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 27 | @REM @REM echo "dcc-ex.com" /y > docs\_build\html\CNAME 28 | @REM @REM echo "" /y > docs\_build\html\.nojekyll 29 | @REM goto end 30 | @REM ) 31 | 32 | %SPHINXBUILD% >NUL 2>NUL 33 | if errorlevel 9009 ( 34 | echo. 35 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 36 | echo.installed, then set the SPHINXBUILD environment variable to point 37 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 38 | echo.may add the Sphinx directory to PATH. 39 | echo. 40 | echo.If you don't have Sphinx installed, grab it from 41 | echo.http://sphinx-doc.org/ 42 | exit /b 1 43 | ) 44 | 45 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 46 | goto end 47 | 48 | :help 49 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 50 | 51 | :end 52 | popd 53 | -------------------------------------------------------------------------------- /docs/overview.rst: -------------------------------------------------------------------------------- 1 | .. include:: /include/include.rst 2 | 3 | Library Design Principles 4 | ========================= 5 | 6 | .. sidebar:: 7 | 8 | .. contents:: On this page 9 | :depth: 2 10 | :local: 11 | 12 | First of all, this library implements the DCC-EX Native protocol in a non-blocking fashion. After creating a DCCEXProtocol object, you set up various necessities such as the network connection and a debug console. 13 | 14 | Then, you call the ```check()``` method as often as you can (ideally, once per invocation of the ```loop()``` method) and the library will manage the I/O stream, reading in/parsing commands and calling methods on the delegate as information is available. 15 | 16 | These patterns (Dependency Injection and Delegation) allow you to keep the different parts of your sketch from becoming too intertwined with each other. Nothing in the code that manages the pushbuttons or speed knobs needs to have any detailed knowledge of the |EX-NCP|. 17 | 18 | DCCEXProtocol Class 19 | ------------------- 20 | 21 | The `DCCEXProtocol` class manages all relevant objects advertised by a |DCC-EX| |EX-CS| and exposes simple methods to control these objects from the client software. 22 | 23 | These objects include: 24 | 25 | - Roster entries 26 | - Route entries 27 | - Turnouts 28 | - Turntables (noting that these objects are only available in development versions) 29 | 30 | This means the client software does not need to explicitly manage the state of these objects whilever the ```check()``` method mentioned above is called appropriately. 31 | 32 | DCCEXProtocolDelegate Class 33 | --------------------------- 34 | 35 | The `DCCEXProtocolDelegate` class enables the client software to respond to various events generated by a |DCC-EX| |EX-CS| as either broadcasts or responses to commands. 36 | 37 | The events able to be managed via this class are over and above those managed by the `DCCEXProtocol` class and are entirely customisable by the client software to provide dynamic user experience updates such as displaying status changes to objects as they are broadcast from the |DCC-EX| |EX-CS|. 38 | 39 | -------------------------------------------------------------------------------- /docs/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | 3 | Sitemap: https://dcc-ex.com/DCCEXProtocol/sitemap.xml 4 | -------------------------------------------------------------------------------- /docs/site-index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /include/include.rst 2 | 3 | Indices and tables 4 | ================== 5 | 6 | * :ref:`genindex` 7 | 8 | .. * :ref:`search` 9 | -------------------------------------------------------------------------------- /docs/tests.rst: -------------------------------------------------------------------------------- 1 | .. include:: /include/include.rst 2 | 3 | Library Tests 4 | ============= 5 | 6 | Courtesy of Vincent Hamp, the |EX-NCL| has unit tests that can be run locally as well automatically being run as a GitHub action when pushing to the main branch (see tests.yml in the repository). 7 | 8 | These tests are written using GoogleTest which allows for mocking as well as testing. 9 | 10 | To run these locally, you will need cmake, doxygen, and C++ build tools. If generating graphs with Doxygen, graphviz is also required. Including clang-format is recommended to help code formatting according to the included formatting also. 11 | 12 | It is recommended these be run on Linux or macOS, as you will need to use MSYS2/MinGW on Windows, and various functions are unsupported, in addition to the case insensitivity of Windows causing issues with some Arduino/C++ headers (eg. string.h in Windows is the same as String.h). To be clear, running these tests is **not recommended on Windows**. 13 | 14 | If you are using a Windows PC for the tests, it is best to utilise Windows Subsystem for Linux with Ubuntu, or your preference of distribution. Note also, that while your existing cloned repository is likely available to WSL in the `/mnt/c` directory, this will likely still cause issues as it is a case insensitive file system that has been mounted, and you should clone the repository within your WSL instance instead. 15 | 16 | To use your existing installation of VSCode with WSL, you can simply change to the repository containing the cloned repository and run ``code .``, which will launch VSCode connected to your WSL instance. 17 | 18 | For Ubuntu, install the required packages with: 19 | 20 | .. code-block:: 21 | 22 | sudo apt install build-essential doxygen cmake graphviz clang-format 23 | 24 | Once these packages are installed, running the tests is pretty simple: 25 | 26 | .. code-block:: 27 | 28 | cd /DCCEXProtocol 29 | cmake -Bbuild 30 | cmake --build build 31 | ./build/tests/DCCEXProtocolTests --gtest_shuffle 32 | 33 | If you have issues compiling the tests, you may need to delete the build directory if it already existed before you started. It is not included in the repository and is used locally only. 34 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | .. include:: /include/include.rst 2 | 3 | Usage 4 | ===== 5 | 6 | .. sidebar:: 7 | 8 | .. contents:: On this page 9 | :depth: 2 10 | :local: 11 | 12 | 13 | Whilst this library extrapolates the need for understanding the specific |DCC-EX| native commands from a throttle developer, it is highly recommended to familiarise yourself with the concepts outlined in the ``_. 14 | 15 | Setup 16 | ----- 17 | 18 | Once the `DCCEXProtocol` object is instantiated, a connection must be made to the |EX-CS| using the `connect(&stream)` method and providing a suitable Arduino Stream, such as a WiFi client or serial connection. 19 | 20 | It is also recommended to enable logging to an Arduino Stream using the `setLogStream(&stream)` method. 21 | 22 | For WiFi clients, long periods of no interactive commands being sent may cause the WiFi client to be disconnected, so it is recommended to enable heartbeats for these, which defaults to sending a heartbeat every 60 seconds. If commands are sent regularly, no heartbeats are sent. 23 | 24 | An example using an ESP32 with WiFi to connect to EX-CommandStation, with logging to the serial console: 25 | 26 | .. code-block:: cpp 27 | 28 | WiFiClient client; 29 | DCCEXProtocol dccexProtocol; 30 | 31 | void setup() { 32 | Serial.begin(115200); 33 | WiFi.begin(ssid, password); 34 | while(WiFi.status() != WL_CONNECTED) delay(1000); 35 | if (!client.connect(serverAddress, serverPort)) { 36 | while(1) delay(1000); 37 | } 38 | dccexProtocol.setLogStream(&Serial); 39 | dccexProtocol.enableHeartbeat(); 40 | dccexProtocol.connect(&client); 41 | } 42 | 43 | void loop() { 44 | dccexProtocol.check(); 45 | // other code here 46 | } 47 | 48 | An example using STM32F103C8 Bluepill with hardware serial port 1 connecting to EX-CommandStation, and logging to the USB serial console: 49 | 50 | .. code-block:: cpp 51 | 52 | DCCEXProtocol dccexProtocol; 53 | 54 | void setup() { 55 | Serial.begin(115200); 56 | Serial1.begin(115200); 57 | dccexProtocol.setLogStream(&Serial); 58 | dccexProtocol.connect(&Serial1); 59 | } 60 | 61 | void loop() { 62 | dccexProtocol.check(); 63 | // other code here 64 | } 65 | 66 | As covered in the design principles above, you must include the `check()` method as often as possible to receive command responses and broadcasts and have these processed by the library and any event handlers defined in your custom `DCCEXProtocolDelegate` class. 67 | 68 | Refer to the :doc:`examples` to see how this may be implemented. 69 | 70 | Control and Inputs 71 | ------------------ 72 | 73 | It is up to the client software utilising this library to manage control and input methods to provide input into the protocol functions such as setting locomotive speed and direction, turning functions on and off, and controlling the various other objects and methods available. 74 | 75 | For example, multiple rotary encoders may be used to simultaneously control multiple locomotives or consists. 76 | 77 | There is, however, no need to instantiate more than one `DCCEXProtocol` or `DCCEXProtocolDelegate` object providing the client software is written appropriately, and we recommend creating a custom class that can take the `DCCEXProtocol` object as a parameter to enable this. 78 | 79 | See the `DCCEXProtocol_Multi_Throttle_Control` example for an idea of how this may be implemented. 80 | 81 | A further note is that controls and inputs should be passed to the protocol only, and should not update local references to object attributes (such as speed and direction), but rather that the responses to these inputs as received by the protocol and delegate events should be used to update local references. 82 | 83 | In this manner, the user of the throttle/client software will see the true results of their inputs which will reflect what EX-CommandStation is doing in response to those inputs. 84 | 85 | Retrieving and referring to object lists 86 | ---------------------------------------- 87 | 88 | To retrieve the various objects lists from |EX-CS|, use the `getLists(bool rosterRequired, bool turnoutListRequired, bool routeListRequired, bool turntableListRequired)` method within your `loop()` function to ensure these are retrieved successfully. 89 | 90 | If you have a lot of defined objects in your |EX-CS| (eg. 50+ turnouts or 50+ roster entries), you will likely need to increase the maximum number of parameters allowed when defining the DCCEXProtocol instance which is now a configurable parameter as of version 1.0.0 of the library. 91 | 92 | You can set the command buffer size and parameter count: 93 | 94 | .. code-block:: cpp 95 | 96 | // dccexProtocol(maxCmdBuffer, maxCommandParams); 97 | DCCEXProtocol dccexProtocol; // Use default 500 byte buffer, 50 parameters 98 | DCCEXProtocol dccexProtocol(500, 100); // Use default 500 byte buffer, 100 parameters 99 | 100 | All objects are contained within linked lists and can be access via for loops: 101 | 102 | .. code-block:: cpp 103 | 104 | for (Loco* loco=dccexProtocol.roster->getFirst(); loco; loco=loco->getNext()) { 105 | // loco methods are available here 106 | } 107 | 108 | for (Turnout* turnout=dccexProtocol.turnouts->getFirst(); turnout; turnout=turnout->getNext()) { 109 | // turnout methods are available here 110 | } 111 | 112 | for (Route* route=dccexProtocol.route->getFirst(); route; route=route->getNext()) { 113 | // route methods are available here 114 | } 115 | 116 | for (Turntable* turntable=dccexProtocol.turntables->getFirst(); turntable; turntable=turntable->getNext()) { 117 | // turntable methods are available here 118 | for (TurntableIndex* ttIndex=turntable->getFirstIndex(); ttIndex; ttIndex=ttIndex->getNextIndex()) { 119 | // turntable index methods are available here 120 | } 121 | } 122 | 123 | Refer to the `DCCEXProtocol_Roster_etc` example for an idea of how this may be implemented. 124 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino: -------------------------------------------------------------------------------- 1 | // WiThrottleProtocol library: Basic example 2 | // 3 | // Shows how to create an instance of DCCEXProtocol 4 | // and how to connect to a DCC-EX Native protocol server using static IP 5 | // Tested with ESP32-WROOM board 6 | // 7 | // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 8 | // Luca Dentella, 2020 9 | 10 | #include 11 | #include 12 | 13 | 14 | // If we haven't got a custom config.h, use the example 15 | #if __has_include("config.h") 16 | #include "config.h" 17 | #else 18 | #warning config.h not found. Using defaults from config.example.h 19 | #include "config.example.h" 20 | #endif 21 | 22 | // Global objects 23 | WiFiClient client; 24 | DCCEXProtocol dccexProtocol; 25 | 26 | void setup() { 27 | 28 | Serial.begin(115200); 29 | Serial.println("DCCEXProtocol Basic Demo"); 30 | Serial.println(); 31 | 32 | // Connect to WiFi network 33 | Serial.println("Connecting to WiFi.."); 34 | WiFi.begin(ssid, password); 35 | while (WiFi.status() != WL_CONNECTED) 36 | delay(1000); 37 | Serial.print("Connected with IP: "); 38 | Serial.println(WiFi.localIP()); 39 | 40 | // Connect to the server 41 | Serial.println("Connecting to the server..."); 42 | if (!client.connect(serverAddress, serverPort)) { 43 | Serial.println("connection failed"); 44 | while (1) 45 | delay(1000); 46 | } 47 | Serial.println("Connected to the server"); 48 | 49 | dccexProtocol.setLogStream(&Serial); 50 | 51 | dccexProtocol.enableHeartbeat(); 52 | 53 | // Pass the communication to wiThrottleProtocol 54 | dccexProtocol.connect(&client); 55 | Serial.println("DCC-EX connected"); 56 | } 57 | 58 | void loop() { 59 | 60 | // parse incoming messages 61 | dccexProtocol.check(); 62 | } 63 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Basic/config.example.h: -------------------------------------------------------------------------------- 1 | // Example user configuration to use with the DCCEXProtocol examples 2 | // Copy this file and rename to config.h 3 | // Replace the variables below with the appropriate values for your environment 4 | const char *ssid = "YOUR_SSID_HERE"; // WiFi SSID name here 5 | const char *password = "YOUR_PASSWORD_HERE"; // WiFi password here 6 | IPAddress serverAddress(192, 168, 4, 1); // IP address of your EX-CommandStation 7 | int serverPort = 2560; // Network port of your EX-CommandStation 8 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Consist_Control/DCCEXProtocol_Consist_Control.ino: -------------------------------------------------------------------------------- 1 | // WiThrottleProtocol library: Consist control example 2 | // 3 | // Shows how to create and control a consist 4 | // Tested with ESP32-WROOM board 5 | // 6 | // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 7 | // Luca Dentella, 2020 8 | 9 | #include 10 | #include 11 | 12 | 13 | // If we haven't got a custom config.h, use the example 14 | #if __has_include("config.h") 15 | #include "config.h" 16 | #else 17 | #warning config.h not found. Using defaults from config.example.h 18 | #include "config.example.h" 19 | #endif 20 | 21 | // Delegate class 22 | class MyDelegate : public DCCEXProtocolDelegate { 23 | 24 | public: 25 | void receivedServerVersion(int major, int minor, int patch) override { 26 | Serial.print("\n\nReceived version: "); 27 | Serial.print(major); 28 | Serial.print("."); 29 | Serial.print(minor); 30 | Serial.print("."); 31 | Serial.println(patch); 32 | } 33 | 34 | void receivedTrackPower(TrackPower state) override { 35 | Serial.print("\n\nReceived Track Power: "); 36 | Serial.println(state); 37 | Serial.println("\n\n"); 38 | } 39 | 40 | // Use for roster Locos (LocoSource::LocoSourceRoster) 41 | void receivedLocoUpdate(Loco *loco) override { 42 | Serial.print("Received Loco update for DCC address: "); 43 | Serial.println(loco->getAddress()); 44 | } 45 | 46 | // Use for locally created Locos (LocoSource::LocoSourceEntry) 47 | void receivedLocoBroadcast(int address, int speed, Direction direction, int functionMap) override { 48 | Serial.print("\n\nReceived Loco broadcast: address|speed|direction|functionMap: "); 49 | Serial.print(address); 50 | Serial.print("|"); 51 | Serial.print(speed); 52 | Serial.print("|"); 53 | if (direction == Direction::Forward) { 54 | Serial.print("Fwd"); 55 | } else { 56 | Serial.print("Rev"); 57 | } 58 | Serial.print("|"); 59 | Serial.println(functionMap); 60 | } 61 | }; 62 | 63 | // for random speed changes 64 | int speed = 0; 65 | int up = 1; 66 | unsigned long lastTime = 0; 67 | 68 | // define our consist object 69 | Consist *consist = nullptr; 70 | 71 | // Global objects 72 | WiFiClient client; 73 | DCCEXProtocol dccexProtocol; 74 | MyDelegate myDelegate; 75 | 76 | void setup() { 77 | 78 | Serial.begin(115200); 79 | Serial.println("DCCEXProtocol Loco Control Demo"); 80 | Serial.println(); 81 | 82 | // Connect to WiFi network 83 | Serial.println("Connecting to WiFi.."); 84 | WiFi.begin(ssid, password); 85 | while (WiFi.status() != WL_CONNECTED) 86 | delay(1000); 87 | Serial.print("Connected with IP: "); 88 | Serial.println(WiFi.localIP()); 89 | 90 | // Connect to the server 91 | Serial.println("Connecting to the server..."); 92 | if (!client.connect(serverAddress, serverPort)) { 93 | Serial.println("connection failed"); 94 | while (1) 95 | delay(1000); 96 | } 97 | Serial.println("Connected to the server"); 98 | 99 | // Logging on Serial 100 | dccexProtocol.setLogStream(&Serial); 101 | 102 | // Pass the delegate instance to wiThrottleProtocol 103 | dccexProtocol.setDelegate(&myDelegate); 104 | 105 | dccexProtocol.enableHeartbeat(); 106 | 107 | // Pass the communication to wiThrottleProtocol 108 | dccexProtocol.connect(&client); 109 | Serial.println("DCC-EX connected"); 110 | 111 | dccexProtocol.requestServerVersion(); 112 | 113 | // Turn track power on for locos to move 114 | dccexProtocol.powerOn(); 115 | 116 | lastTime = millis(); 117 | } 118 | 119 | void loop() { 120 | // parse incoming messages 121 | dccexProtocol.check(); 122 | 123 | if (!consist) { 124 | consist = new Consist(); 125 | 126 | // create a loco with DCC address 11 - LocoSourceEntry means it's not from the roster 127 | Loco *loco1 = new Loco(11, LocoSource::LocoSourceEntry); 128 | Serial.print("Created loco: "); 129 | Serial.println(loco1->getAddress()); 130 | 131 | // add this loco to the consist 132 | consist->addLoco(loco1, Facing::FacingForward); 133 | 134 | // create a second loco with DCC address 12 - LocoSourceEntry means it's not from the roster 135 | Loco *loco2 = new Loco(12, LocoSource::LocoSourceEntry); 136 | Serial.print("Created loco: "); 137 | Serial.println(loco2->getAddress()); 138 | 139 | // add this loco to the consist, and it will be running in reverse direction 140 | consist->addLoco(loco2, Facing::FacingReversed); 141 | 142 | // turn track power on or the loco won't move 143 | dccexProtocol.powerOn(); 144 | } 145 | 146 | if (consist) { 147 | // every 10 seconds change speed and set a random function on or off 148 | if ((millis() - lastTime) >= 10000) { 149 | if (speed >= 100) 150 | up = -1; 151 | if (speed <= 0) 152 | up = 1; 153 | speed = speed + up; 154 | dccexProtocol.setThrottle(consist, speed, Direction::Forward); 155 | 156 | int fn = random(0, 27); 157 | int fns = random(0, 100); 158 | bool fnState = (fns < 50) ? false : true; 159 | 160 | if (fnState) { 161 | dccexProtocol.functionOn(consist, fn); 162 | } else { 163 | dccexProtocol.functionOff(consist, fn); 164 | } 165 | 166 | lastTime = millis(); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Consist_Control/config.example.h: -------------------------------------------------------------------------------- 1 | // Example user configuration to use with the DCCEXProtocol examples 2 | // Copy this file and rename to config.h 3 | // Replace the variables below with the appropriate values for your environment 4 | const char *ssid = "YOUR_SSID_HERE"; // WiFi SSID name here 5 | const char *password = "YOUR_PASSWORD_HERE"; // WiFi password here 6 | IPAddress serverAddress(192, 168, 4, 1); // IP address of your EX-CommandStation 7 | int serverPort = 2560; // Network port of your EX-CommandStation 8 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Delegate/DCCEXProtocol_Delegate.ino: -------------------------------------------------------------------------------- 1 | // WiThrottleProtocol library: Delegate example 2 | // 3 | // Shows how to create a delegate class to handle callbacks 4 | // Tested with ESP32-WROOM board 5 | // 6 | // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 7 | // Luca Dentella, 2020 8 | 9 | #include 10 | #include 11 | 12 | 13 | // If we haven't got a custom config.h, use the example 14 | #if __has_include("config.h") 15 | #include "config.h" 16 | #else 17 | #warning config.h not found. Using defaults from config.example.h 18 | #include "config.example.h" 19 | #endif 20 | 21 | // Delegate class 22 | class MyDelegate : public DCCEXProtocolDelegate { 23 | 24 | public: 25 | void receivedServerVersion(int major, int minor, int patch) override { 26 | Serial.print("\n\nReceived version: "); 27 | Serial.print(major); 28 | Serial.print("."); 29 | Serial.print(minor); 30 | Serial.print("."); 31 | Serial.println(patch); 32 | } 33 | }; 34 | 35 | // Global objects 36 | WiFiClient client; 37 | DCCEXProtocol dccexProtocol; 38 | MyDelegate myDelegate; 39 | 40 | void setup() { 41 | 42 | Serial.begin(115200); 43 | Serial.println("DCCEXProtocol Delegate Demo"); 44 | Serial.println(); 45 | 46 | // Connect to WiFi network 47 | Serial.println("Connecting to WiFi.."); 48 | WiFi.begin(ssid, password); 49 | while (WiFi.status() != WL_CONNECTED) 50 | delay(1000); 51 | Serial.print("Connected with IP: "); 52 | Serial.println(WiFi.localIP()); 53 | 54 | // Connect to the server 55 | Serial.println("Connecting to the server..."); 56 | if (!client.connect(serverAddress, serverPort)) { 57 | Serial.println("connection failed"); 58 | while (1) 59 | delay(1000); 60 | } 61 | Serial.println("Connected to the server"); 62 | 63 | // Setup logging to serial console 64 | dccexProtocol.setLogStream(&Serial); 65 | 66 | // Pass the delegate instance to wiThrottleProtocol 67 | dccexProtocol.setDelegate(&myDelegate); 68 | 69 | // Pass the communication to wiThrottleProtocol 70 | dccexProtocol.connect(&client); 71 | Serial.println("DCC-EX connected"); 72 | 73 | dccexProtocol.requestServerVersion(); 74 | } 75 | 76 | void loop() { 77 | // parse incoming messages 78 | dccexProtocol.check(); 79 | } 80 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Delegate/config.example.h: -------------------------------------------------------------------------------- 1 | // Example user configuration to use with the DCCEXProtocol examples 2 | // Copy this file and rename to config.h 3 | // Replace the variables below with the appropriate values for your environment 4 | const char *ssid = "YOUR_SSID_HERE"; // WiFi SSID name here 5 | const char *password = "YOUR_PASSWORD_HERE"; // WiFi password here 6 | IPAddress serverAddress(192, 168, 4, 1); // IP address of your EX-CommandStation 7 | int serverPort = 2560; // Network port of your EX-CommandStation 8 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Loco_Control/DCCEXProtocol_Loco_Control.ino: -------------------------------------------------------------------------------- 1 | // WiThrottleProtocol library: Loco control example 2 | // 3 | // Shows how to control a single loco 4 | // Tested with ESP32-WROOM board 5 | // 6 | // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 7 | // Luca Dentella, 2020 8 | 9 | #include 10 | #include 11 | 12 | // If we haven't got a custom config.h, use the example 13 | #if __has_include("config.h") 14 | #include "config.h" 15 | #else 16 | #warning config.h not found. Using defaults from config.example.h 17 | #include "config.example.h" 18 | #endif 19 | 20 | void printRoster(); 21 | 22 | // Delegate class 23 | class MyDelegate : public DCCEXProtocolDelegate { 24 | public: 25 | void receivedServerVersion(int major, int minor, int patch) override { 26 | Serial.print("\n\nReceived version: "); 27 | Serial.print(major); 28 | Serial.print("."); 29 | Serial.print(minor); 30 | Serial.print("."); 31 | Serial.println(patch); 32 | } 33 | 34 | void receivedTrackPower(TrackPower state) override { 35 | Serial.print("\n\nReceived Track Power: "); 36 | Serial.println(state); 37 | Serial.println("\n\n"); 38 | } 39 | 40 | // Use for roster Locos (LocoSource::LocoSourceRoster) 41 | void receivedLocoUpdate(Loco *loco) override { 42 | Serial.print("Received Loco update for DCC address: "); 43 | Serial.println(loco->getAddress()); 44 | } 45 | 46 | // Use for locally created Locos (LocoSource::LocoSourceEntry) 47 | void receivedLocoBroadcast(int address, int speed, Direction direction, int functionMap) override { 48 | Serial.print("\n\nReceived Loco broadcast: address|speed|direction|functionMap: "); 49 | Serial.print(address); 50 | Serial.print("|"); 51 | Serial.print(speed); 52 | Serial.print("|"); 53 | if (direction == Direction::Forward) { 54 | Serial.print("Fwd"); 55 | } else { 56 | Serial.print("Rev"); 57 | } 58 | Serial.print("|"); 59 | Serial.println(functionMap); 60 | } 61 | }; 62 | 63 | // for random speed changes 64 | int speed = 0; 65 | int up = 1; 66 | unsigned long lastTime = 0; 67 | 68 | // define our loco object 69 | Loco *loco = nullptr; 70 | 71 | // Global objects 72 | WiFiClient client; 73 | DCCEXProtocol dccexProtocol; 74 | MyDelegate myDelegate; 75 | 76 | void setup() { 77 | 78 | Serial.begin(115200); 79 | Serial.println("DCCEXProtocol Loco Control Demo"); 80 | Serial.println(); 81 | 82 | // Connect to WiFi network 83 | Serial.println("Connecting to WiFi.."); 84 | WiFi.begin(ssid, password); 85 | while (WiFi.status() != WL_CONNECTED) 86 | delay(1000); 87 | Serial.print("Connected with IP: "); 88 | Serial.println(WiFi.localIP()); 89 | 90 | // Connect to the server 91 | Serial.println("Connecting to the server..."); 92 | if (!client.connect(serverAddress, serverPort)) { 93 | Serial.println("connection failed"); 94 | while (1) 95 | delay(1000); 96 | } 97 | Serial.println("Connected to the server"); 98 | 99 | // Logging on Serial 100 | dccexProtocol.setLogStream(&Serial); 101 | 102 | // Pass the delegate instance to wiThrottleProtocol 103 | dccexProtocol.setDelegate(&myDelegate); 104 | 105 | // Pass the communication to wiThrottleProtocol 106 | dccexProtocol.connect(&client); 107 | Serial.println("DCC-EX connected"); 108 | 109 | dccexProtocol.requestServerVersion(); 110 | 111 | lastTime = millis(); 112 | } 113 | 114 | void loop() { 115 | // parse incoming messages 116 | dccexProtocol.check(); 117 | 118 | if (!loco) { 119 | // add a loco with DCC address 11 - LocoSourceEntry means it's not from the roster 120 | loco = new Loco(11, LocoSource::LocoSourceEntry); 121 | Serial.print("Added loco: "); 122 | Serial.println(loco->getAddress()); 123 | 124 | // turn track power on or the loco won't move 125 | dccexProtocol.powerOn(); 126 | } 127 | 128 | if (loco) { 129 | // every 10 seconds change speed and set a random function on or off 130 | if ((millis() - lastTime) >= 10000) { 131 | if (speed >= 100) 132 | up = -1; 133 | if (speed <= 0) 134 | up = 1; 135 | speed = speed + up; 136 | dccexProtocol.setThrottle(loco, speed, Direction::Forward); 137 | 138 | int fn = random(0, 27); 139 | int fns = random(0, 100); 140 | bool fnState = (fns < 50) ? false : true; 141 | 142 | if (fnState) { 143 | dccexProtocol.functionOn(loco, fn); 144 | } else { 145 | dccexProtocol.functionOff(loco, fn); 146 | } 147 | 148 | lastTime = millis(); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Loco_Control/config.example.h: -------------------------------------------------------------------------------- 1 | // Example user configuration to use with the DCCEXProtocol examples 2 | // Copy this file and rename to config.h 3 | // Replace the variables below with the appropriate values for your environment 4 | const char *ssid = "YOUR_SSID_HERE"; // WiFi SSID name here 5 | const char *password = "YOUR_PASSWORD_HERE"; // WiFi password here 6 | IPAddress serverAddress(192, 168, 4, 1); // IP address of your EX-CommandStation 7 | int serverPort = 2560; // Network port of your EX-CommandStation 8 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Multi_Throttle_Control/DCCEXProtocol_Multi_Throttle_Control.ino: -------------------------------------------------------------------------------- 1 | // WiThrottleProtocol library: Multi throttle control example 2 | // 3 | // Shows how to control locos with multiple client throttles 4 | // Tested with ESP32-WROOM board 5 | // 6 | // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 7 | // Luca Dentella, 2020 8 | 9 | #include 10 | #include 11 | 12 | 13 | // If we haven't got a custom config.h, use the example 14 | #if __has_include("config.h") 15 | #include "config.h" 16 | #else 17 | #warning config.h not found. Using defaults from config.example.h 18 | #include "config.example.h" 19 | #endif 20 | 21 | // Delegate class 22 | class MyDelegate : public DCCEXProtocolDelegate { 23 | 24 | public: 25 | void receivedServerVersion(int major, int minor, int patch) override { 26 | Serial.print("\n\nReceived version: "); 27 | Serial.print(major); 28 | Serial.print("."); 29 | Serial.print(minor); 30 | Serial.print("."); 31 | Serial.println(patch); 32 | } 33 | 34 | void receivedTrackPower(TrackPower state) override { 35 | Serial.print("\n\nReceived Track Power: "); 36 | Serial.println(state); 37 | Serial.println("\n\n"); 38 | } 39 | 40 | // Use for roster Locos (LocoSource::LocoSourceRoster) 41 | void receivedLocoUpdate(Loco *loco) override { 42 | Serial.print("Received Loco update for DCC address: "); 43 | Serial.println(loco->getAddress()); 44 | } 45 | 46 | // Use for locally created Locos (LocoSource::LocoSourceEntry) 47 | void receivedLocoBroadcast(int address, int speed, Direction direction, int functionMap) override { 48 | Serial.print("\n\nReceived Loco broadcast: address|speed|direction|functionMap: "); 49 | Serial.print(address); 50 | Serial.print("|"); 51 | Serial.print(speed); 52 | Serial.print("|"); 53 | if (direction == Direction::Forward) { 54 | Serial.print("Fwd"); 55 | } else { 56 | Serial.print("Rev"); 57 | } 58 | Serial.print("|"); 59 | Serial.println(functionMap); 60 | } 61 | }; 62 | 63 | // Example class for a throttle 64 | // You would associate a rotary encoder or similar with this class 65 | // for input and control of speed/direction 66 | class Throttle { 67 | public: 68 | Throttle(DCCEXProtocol *dccexProtocol) { _dccexProtocol = dccexProtocol; } 69 | 70 | void setLoco(Loco *loco) { _loco = loco; } 71 | 72 | Loco *getLoco() { return _loco; } 73 | 74 | void setSpeed(int speed) { 75 | if (!_loco) 76 | return; 77 | _dccexProtocol->setThrottle(_loco, speed, _loco->getDirection()); 78 | } 79 | 80 | void setDirection(Direction direction) { 81 | if (!_loco) 82 | return; 83 | _dccexProtocol->setThrottle(_loco, _loco->getSpeed(), direction); 84 | } 85 | 86 | void process() { 87 | // Routine calls here included in the loop to read encoder or other inputs 88 | } 89 | 90 | private: 91 | DCCEXProtocol *_dccexProtocol; 92 | Loco *_loco; 93 | }; 94 | 95 | // for random speed changes 96 | int speed = 0; 97 | int up = 1; 98 | unsigned long lastTime = 0; 99 | 100 | // Global objects 101 | WiFiClient client; 102 | DCCEXProtocol dccexProtocol; 103 | MyDelegate myDelegate; 104 | 105 | // Define an array for two throttles 106 | const int numThrottles = 2; 107 | Throttle *throttles[numThrottles]; 108 | 109 | void setup() { 110 | 111 | Serial.begin(115200); 112 | Serial.println("DCCEXProtocol Loco Control Demo"); 113 | Serial.println(); 114 | 115 | // Connect to WiFi network 116 | Serial.println("Connecting to WiFi.."); 117 | WiFi.begin(ssid, password); 118 | while (WiFi.status() != WL_CONNECTED) 119 | delay(1000); 120 | Serial.print("Connected with IP: "); 121 | Serial.println(WiFi.localIP()); 122 | 123 | // Connect to the server 124 | Serial.println("Connecting to the server..."); 125 | if (!client.connect(serverAddress, serverPort)) { 126 | Serial.println("connection failed"); 127 | while (1) 128 | delay(1000); 129 | } 130 | Serial.println("Connected to the server"); 131 | 132 | // Logging on Serial 133 | dccexProtocol.setLogStream(&Serial); 134 | 135 | // Pass the delegate instance to wiThrottleProtocol 136 | dccexProtocol.setDelegate(&myDelegate); 137 | 138 | // Pass the communication to wiThrottleProtocol 139 | dccexProtocol.connect(&client); 140 | Serial.println("DCC-EX connected"); 141 | 142 | dccexProtocol.requestServerVersion(); 143 | 144 | lastTime = millis(); 145 | 146 | // Dummy loco starting address 147 | int address = 12; 148 | 149 | // Create the throttles and add a loco to each 150 | for (int i = 0; i < numThrottles; i++) { 151 | Serial.print("Create throttle|loco address: "); 152 | Serial.print(i); 153 | Serial.print("|"); 154 | Serial.println(address + i); 155 | throttles[i] = new Throttle(&dccexProtocol); 156 | throttles[i]->setLoco(new Loco(address + i, LocoSource::LocoSourceEntry)); 157 | } 158 | 159 | // Turn track power on so locos can move 160 | dccexProtocol.powerOn(); 161 | } 162 | 163 | void loop() { 164 | // parse incoming messages 165 | dccexProtocol.check(); 166 | 167 | // throttle processing example 168 | for (int i = 0; i < numThrottles; i++) { 169 | throttles[i]->process(); 170 | } 171 | 172 | // every 10 seconds change speed and set a random function on or off 173 | if ((millis() - lastTime) >= 10000) { 174 | lastTime = millis(); 175 | for (int i = 0; i < numThrottles; i++) { 176 | auto th = throttles[i]; 177 | Loco *loco = th->getLoco(); 178 | if (loco) { 179 | if (speed >= 100) 180 | up = -1; 181 | if (speed <= 0) 182 | up = 1; 183 | speed = speed + up; 184 | dccexProtocol.setThrottle(loco, speed, Direction::Forward); 185 | 186 | int fn = random(0, 27); 187 | int fns = random(0, 100); 188 | bool fnState = (fns < 50) ? false : true; 189 | 190 | if (fnState) { 191 | dccexProtocol.functionOn(loco, fn); 192 | } else { 193 | dccexProtocol.functionOff(loco, fn); 194 | } 195 | } 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Multi_Throttle_Control/config.example.h: -------------------------------------------------------------------------------- 1 | // Example user configuration to use with the DCCEXProtocol examples 2 | // Copy this file and rename to config.h 3 | // Replace the variables below with the appropriate values for your environment 4 | const char *ssid = "YOUR_SSID_HERE"; // WiFi SSID name here 5 | const char *password = "YOUR_PASSWORD_HERE"; // WiFi password here 6 | IPAddress serverAddress(192, 168, 4, 1); // IP address of your EX-CommandStation 7 | int serverPort = 2560; // Network port of your EX-CommandStation 8 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Roster_etc/DCCEXProtocol_Roster_etc.ino: -------------------------------------------------------------------------------- 1 | // WiThrottleProtocol library: Roster and other objects example 2 | // 3 | // Shows how to retrieve the Roster, Turnouts/Points, Routes, Turntables 4 | // Tested with ESP32-WROOM board 5 | // 6 | // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 7 | // Luca Dentella, 2020 8 | 9 | #include 10 | #include 11 | 12 | 13 | // If we haven't got a custom config.h, use the example 14 | #if __has_include("config.h") 15 | #include "config.h" 16 | #else 17 | #warning config.h not found. Using defaults from config.example.h 18 | #include "config.example.h" 19 | #endif 20 | 21 | void printRoster(); 22 | void printTurnouts(); 23 | void printRoutes(); 24 | void printTurntables(); 25 | 26 | // Delegate class 27 | class MyDelegate : public DCCEXProtocolDelegate { 28 | 29 | public: 30 | void receivedServerVersion(int major, int minor, int patch) override { 31 | Serial.print("\n\nReceived version: "); 32 | Serial.print(major); 33 | Serial.print("."); 34 | Serial.print(minor); 35 | Serial.print("."); 36 | Serial.println(patch); 37 | } 38 | 39 | void receivedTrackPower(TrackPower state) override { 40 | Serial.print("\n\nReceived Track Power: "); 41 | Serial.println(state); 42 | Serial.println("\n\n"); 43 | } 44 | 45 | void receivedRosterList() override { 46 | Serial.println("\n\nReceived Roster"); 47 | printRoster(); 48 | } 49 | void receivedTurnoutList() override { 50 | Serial.print("\n\nReceived Turnouts/Points list"); 51 | printTurnouts(); 52 | Serial.println("\n\n"); 53 | } 54 | void receivedRouteList() override { 55 | Serial.print("\n\nReceived Routes List"); 56 | printRoutes(); 57 | Serial.println("\n\n"); 58 | } 59 | void receivedTurntableList() override { 60 | Serial.print("\n\nReceived Turntables list"); 61 | printTurntables(); 62 | Serial.println("\n\n"); 63 | } 64 | }; 65 | 66 | unsigned long lastTime = 0; 67 | 68 | bool done = false; 69 | 70 | // Global objects 71 | WiFiClient client; 72 | DCCEXProtocol dccexProtocol; 73 | MyDelegate myDelegate; 74 | 75 | void printRoster() { 76 | for (Loco *loco = dccexProtocol.roster->getFirst(); loco; loco = loco->getNext()) { 77 | int id = loco->getAddress(); 78 | const char *name = loco->getName(); 79 | Serial.print(id); 80 | Serial.print(" ~"); 81 | Serial.print(name); 82 | Serial.println("~"); 83 | for (int i = 0; i < 32; i++) { 84 | const char *fName = loco->getFunctionName(i); 85 | if (fName != nullptr) { 86 | Serial.print("loadFunctionLabels() "); 87 | Serial.print(fName); 88 | if (loco->isFunctionMomentary(i)) { 89 | Serial.print(" - Momentary"); 90 | } 91 | Serial.println(); 92 | } 93 | } 94 | } 95 | Serial.println("\n"); 96 | } 97 | 98 | void printTurnouts() { 99 | for (Turnout *turnout = dccexProtocol.turnouts->getFirst(); turnout; turnout = turnout->getNext()) { 100 | int id = turnout->getId(); 101 | const char *name = turnout->getName(); 102 | Serial.print(id); 103 | Serial.print(" ~"); 104 | Serial.print(name); 105 | Serial.println("~"); 106 | } 107 | Serial.println("\n"); 108 | } 109 | 110 | void printRoutes() { 111 | for (Route *route = dccexProtocol.routes->getFirst(); route; route = route->getNext()) { 112 | int id = route->getId(); 113 | const char *name = route->getName(); 114 | Serial.print(id); 115 | Serial.print(" ~"); 116 | Serial.print(name); 117 | Serial.println("~"); 118 | } 119 | Serial.println("\n"); 120 | } 121 | 122 | void printTurntables() { 123 | for (Turntable *turntable = dccexProtocol.turntables->getFirst(); turntable; turntable = turntable->getNext()) { 124 | int id = turntable->getId(); 125 | const char *name = turntable->getName(); 126 | Serial.print(id); 127 | Serial.print(" ~"); 128 | Serial.print(name); 129 | Serial.println("~"); 130 | 131 | int j = 0; 132 | for (TurntableIndex *turntableIndex = turntable->getFirstIndex(); turntableIndex; 133 | turntableIndex = turntableIndex->getNextIndex()) { 134 | const char *indexName = turntableIndex->getName(); 135 | Serial.print(" index"); 136 | Serial.print(j); 137 | Serial.print(" ~"); 138 | Serial.print(indexName); 139 | Serial.println("~"); 140 | j++; 141 | } 142 | } 143 | Serial.println("\n"); 144 | } 145 | 146 | void setup() { 147 | 148 | Serial.begin(115200); 149 | Serial.println("DCCEXProtocol Roster and Objects Demo"); 150 | Serial.println(); 151 | 152 | // Connect to WiFi network 153 | Serial.println("Connecting to WiFi.."); 154 | WiFi.begin(ssid, password); 155 | while (WiFi.status() != WL_CONNECTED) 156 | delay(1000); 157 | Serial.print("Connected with IP: "); 158 | Serial.println(WiFi.localIP()); 159 | 160 | // Connect to the server 161 | Serial.println("Connecting to the server..."); 162 | if (!client.connect(serverAddress, serverPort)) { 163 | Serial.println("connection failed"); 164 | while (1) 165 | delay(1000); 166 | } 167 | Serial.println("Connected to the server"); 168 | 169 | // Enable logging on Serial 170 | dccexProtocol.setLogStream(&Serial); 171 | 172 | // Pass the delegate instance to wiThrottleProtocol 173 | dccexProtocol.setDelegate(&myDelegate); 174 | 175 | // Pass the communication to wiThrottleProtocol 176 | dccexProtocol.connect(&client); 177 | Serial.println("DCC-EX connected"); 178 | 179 | dccexProtocol.requestServerVersion(); 180 | dccexProtocol.powerOn(); 181 | } 182 | 183 | void loop() { 184 | // parse incoming messages 185 | dccexProtocol.check(); 186 | 187 | // sequentially request and get the required lists. To avoid overloading the buffer 188 | // getLists(bool rosterRequired, bool turnoutListRequired, bool routeListRequired, bool turntableListRequired) 189 | dccexProtocol.getLists(true, true, true, true); 190 | } 191 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Roster_etc/config.example.h: -------------------------------------------------------------------------------- 1 | // Example user configuration to use with the DCCEXProtocol examples 2 | // Copy this file and rename to config.h 3 | // Replace the variables below with the appropriate values for your environment 4 | const char *ssid = "YOUR_SSID_HERE"; // WiFi SSID name here 5 | const char *password = "YOUR_PASSWORD_HERE"; // WiFi password here 6 | IPAddress serverAddress(192, 168, 4, 1); // IP address of your EX-CommandStation 7 | int serverPort = 2560; // Network port of your EX-CommandStation 8 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_SSID/DCCEXProtocol_SSID.ino: -------------------------------------------------------------------------------- 1 | // WiThrottleProtocol library: SSID example 2 | // 3 | // Shows how to retrieve the list of discovered SSID (Networks) 4 | // Tested with ESP32-WROOM board 5 | // 6 | // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 7 | 8 | #include 9 | #include 10 | 11 | void printSsids(); 12 | 13 | #define MAX_SSIDS 20 14 | 15 | String foundSsids[MAX_SSIDS]; 16 | long foundSsidRssis[MAX_SSIDS]; 17 | boolean foundSsidsOpen[MAX_SSIDS]; 18 | int foundSsidsCount; 19 | 20 | unsigned long lastTime = 0; 21 | 22 | bool mdnsListenerStarted = false; 23 | 24 | // Global objects 25 | WiFiClient client; 26 | 27 | void printSsids() { 28 | Serial.println(""); 29 | Serial.println("Browsing for SSIDs "); 30 | 31 | double startTime = millis(); 32 | double nowTime = startTime; 33 | 34 | int numSsids = WiFi.scanNetworks(); 35 | while ((numSsids == -1) && ((nowTime - startTime) <= 10000)) { // try for 10 seconds 36 | delay(250); 37 | Serial.print("."); 38 | nowTime = millis(); 39 | } 40 | 41 | int foundSsidsCount = 0; 42 | if (numSsids == -1) { 43 | Serial.println("Couldn't get a wifi connection"); 44 | 45 | } else { 46 | for (int thisSsid = 0; thisSsid < numSsids; thisSsid++) { 47 | /// remove duplicates (repeaters and mesh networks) 48 | boolean found = false; 49 | for (int i = 0; i < foundSsidsCount && i < MAX_SSIDS; i++) { 50 | if (WiFi.SSID(thisSsid) == foundSsids[i]) { 51 | found = true; 52 | break; 53 | } 54 | } 55 | if (!found) { 56 | foundSsids[foundSsidsCount] = WiFi.SSID(thisSsid); 57 | foundSsidRssis[foundSsidsCount] = WiFi.RSSI(thisSsid); 58 | foundSsidsOpen[foundSsidsCount] = (WiFi.encryptionType(thisSsid) == 7) ? true : false; 59 | foundSsidsCount++; 60 | } 61 | } 62 | for (int i = 0; i < foundSsidsCount; i++) { 63 | Serial.println(foundSsids[i]); 64 | } 65 | } 66 | } 67 | 68 | void setup() { 69 | 70 | Serial.begin(115200); 71 | Serial.println("DCCEXProtocol SSID example"); 72 | Serial.println(); 73 | 74 | // browse for SSIDs 75 | printSsids(); 76 | } 77 | 78 | void loop() { 79 | 80 | delay(20000); 81 | // Redo every 20 seconds - For demonstration purposes only! 82 | // Normally this will only be required once. 83 | printSsids(); 84 | } 85 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Serial/DCCEXProtocol_Serial.ino: -------------------------------------------------------------------------------- 1 | // DCCEXProtocol_Serial 2 | // 3 | // Shows how to use DCCEXProtocol over serial using a Mega2560 4 | // This uses a Mega2560 (or other AVR device) that has a second hardware serial port (Serial1) 5 | // Tested with Arduino Mega2560 6 | // 7 | // Peter Cole (PeteGSX) 2024 8 | 9 | #include 10 | 11 | // If we haven't got a custom config.h, use the example 12 | #if __has_include("config.h") 13 | #include "config.h" 14 | #else 15 | #warning config.h not found. Using defaults from config.example.h 16 | #include "config.example.h" 17 | #endif 18 | 19 | // Setup serial macros if not already defined elsewhere 20 | #ifndef CONSOLE 21 | #define CONSOLE Serial // All output should go to this 22 | #endif 23 | #ifndef CLIENT 24 | #define CLIENT Serial1 // All DCCEXProtocol commands/responses/broadcasts use this 25 | #endif 26 | 27 | // Declare functions to call from our delegate 28 | void printRoster(); 29 | void printTurnouts(); 30 | void printRoutes(); 31 | void printTurntables(); 32 | 33 | // Setup the delegate class to process broadcasts/responses 34 | class MyDelegate : public DCCEXProtocolDelegate { 35 | public: 36 | void receivedServerVersion(int major, int minor, int patch) override { 37 | CONSOLE.print("\n\nReceived version: "); 38 | CONSOLE.print(major); 39 | CONSOLE.print("."); 40 | CONSOLE.print(minor); 41 | CONSOLE.print("."); 42 | CONSOLE.println(patch); 43 | } 44 | 45 | void receivedTrackPower(TrackPower state) override { 46 | CONSOLE.print("\n\nReceived Track Power: "); 47 | CONSOLE.println(state); 48 | CONSOLE.println("\n\n"); 49 | } 50 | 51 | void receivedRosterList() override { 52 | CONSOLE.println("\n\nReceived Roster"); 53 | printRoster(); 54 | } 55 | void receivedTurnoutList() override { 56 | CONSOLE.print("\n\nReceived Turnouts/Points list"); 57 | printTurnouts(); 58 | CONSOLE.println("\n\n"); 59 | } 60 | void receivedRouteList() override { 61 | CONSOLE.print("\n\nReceived Routes List"); 62 | printRoutes(); 63 | CONSOLE.println("\n\n"); 64 | } 65 | void receivedTurntableList() override { 66 | CONSOLE.print("\n\nReceived Turntables list"); 67 | printTurntables(); 68 | CONSOLE.println("\n\n"); 69 | } 70 | 71 | void receivedScreenUpdate(int screen, int row, char *message) override { 72 | CONSOLE.println("\n\nReceived screen|row|message"); 73 | CONSOLE.print(screen); 74 | CONSOLE.print("|"); 75 | CONSOLE.print(row); 76 | CONSOLE.print("|"); 77 | CONSOLE.println(message); 78 | } 79 | 80 | }; 81 | 82 | // Global objects 83 | DCCEXProtocol dccexProtocol; 84 | MyDelegate myDelegate; 85 | 86 | void printRoster() { 87 | for (Loco *loco = dccexProtocol.roster->getFirst(); loco; loco = loco->getNext()) { 88 | int id = loco->getAddress(); 89 | const char *name = loco->getName(); 90 | CONSOLE.print(id); 91 | CONSOLE.print(" ~"); 92 | CONSOLE.print(name); 93 | CONSOLE.println("~"); 94 | for (int i = 0; i < 32; i++) { 95 | const char *fName = loco->getFunctionName(i); 96 | if (fName != nullptr) { 97 | CONSOLE.print("loadFunctionLabels() "); 98 | CONSOLE.print(fName); 99 | if (loco->isFunctionMomentary(i)) { 100 | CONSOLE.print(" - Momentary"); 101 | } 102 | CONSOLE.println(); 103 | } 104 | } 105 | } 106 | CONSOLE.println("\n"); 107 | } 108 | 109 | void printTurnouts() { 110 | for (Turnout *turnout = dccexProtocol.turnouts->getFirst(); turnout; turnout = turnout->getNext()) { 111 | int id = turnout->getId(); 112 | const char *name = turnout->getName(); 113 | CONSOLE.print(id); 114 | CONSOLE.print(" ~"); 115 | CONSOLE.print(name); 116 | CONSOLE.println("~"); 117 | } 118 | CONSOLE.println("\n"); 119 | } 120 | 121 | void printRoutes() { 122 | for (Route *route = dccexProtocol.routes->getFirst(); route; route = route->getNext()) { 123 | int id = route->getId(); 124 | const char *name = route->getName(); 125 | CONSOLE.print(id); 126 | CONSOLE.print(" ~"); 127 | CONSOLE.print(name); 128 | CONSOLE.println("~"); 129 | } 130 | CONSOLE.println("\n"); 131 | } 132 | 133 | void printTurntables() { 134 | for (Turntable *turntable = dccexProtocol.turntables->getFirst(); turntable; turntable = turntable->getNext()) { 135 | int id = turntable->getId(); 136 | const char *name = turntable->getName(); 137 | CONSOLE.print(id); 138 | CONSOLE.print(" ~"); 139 | CONSOLE.print(name); 140 | CONSOLE.println("~"); 141 | 142 | int j = 0; 143 | for (TurntableIndex *turntableIndex = turntable->getFirstIndex(); turntableIndex; 144 | turntableIndex = turntableIndex->getNextIndex()) { 145 | const char *indexName = turntableIndex->getName(); 146 | CONSOLE.print(" index"); 147 | CONSOLE.print(j); 148 | CONSOLE.print(" ~"); 149 | CONSOLE.print(indexName); 150 | CONSOLE.println("~"); 151 | j++; 152 | } 153 | } 154 | CONSOLE.println("\n"); 155 | } 156 | 157 | void setup() { 158 | CONSOLE.begin(115200); 159 | CLIENT.begin(115200); 160 | CONSOLE.println(F("DCCEXProtocol Serial Connection Demo")); 161 | CONSOLE.println(F("")); 162 | 163 | // Direct logs to CONSOLE 164 | dccexProtocol.setLogStream(&CONSOLE); 165 | 166 | // Set the delegate for broadcasts/responses 167 | dccexProtocol.setDelegate(&myDelegate); 168 | 169 | // Connect to the CS via CLIENT 170 | dccexProtocol.connect(&CLIENT); 171 | CONSOLE.println(F("DCC-EX connected")); 172 | 173 | dccexProtocol.requestServerVersion(); 174 | } 175 | 176 | void loop() { 177 | // Parse incoming messages 178 | dccexProtocol.check(); 179 | 180 | dccexProtocol.getLists(true, true, true, true); 181 | } 182 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Serial/config.example.h: -------------------------------------------------------------------------------- 1 | // Example config file for the DCCEXProtocol_Serial example. 2 | // Copy to config.h if you need to customise these for your setup. 3 | 4 | #define CONSOLE Serial 5 | #define CLIENT Serial1 6 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Track_type/DCCEXProtocol_Track_type.ino: -------------------------------------------------------------------------------- 1 | // WiThrottleProtocol library: Track Type example 2 | // 3 | // Shows how to change the track types 4 | // Tested with ESP32-WROOM board 5 | // 6 | // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 7 | // Luca Dentella, 2020 8 | 9 | #include 10 | #include 11 | 12 | 13 | // If we haven't got a custom config.h, use the example 14 | #if __has_include("config.h") 15 | #include "config.h" 16 | #else 17 | #warning config.h not found. Using defaults from config.example.h 18 | #include "config.example.h" 19 | #endif 20 | 21 | // Delegate class 22 | class MyDelegate : public DCCEXProtocolDelegate { 23 | 24 | public: 25 | void receivedTrackType(char track, TrackManagerMode type, int address) override { 26 | Serial.print("\n\nReceived TrackType: "); 27 | Serial.print(track); 28 | Serial.print(" : "); 29 | Serial.print(type); 30 | Serial.print(" : "); 31 | Serial.println(address); 32 | } 33 | }; 34 | 35 | // Global objects 36 | WiFiClient client; 37 | DCCEXProtocol dccexProtocol; 38 | MyDelegate myDelegate; 39 | 40 | // for changes 41 | unsigned long lastTime = 0; 42 | int lastTrackType = 0; 43 | 44 | void setup() { 45 | 46 | Serial.begin(115200); 47 | Serial.println("DCCEXProtocol Track Type Demo"); 48 | Serial.println(); 49 | 50 | // Connect to WiFi network 51 | Serial.println("Connecting to WiFi.."); 52 | WiFi.begin(ssid, password); 53 | while (WiFi.status() != WL_CONNECTED) 54 | delay(1000); 55 | Serial.print("Connected with IP: "); 56 | Serial.println(WiFi.localIP()); 57 | 58 | // Connect to the server 59 | Serial.println("Connecting to the server..."); 60 | if (!client.connect(serverAddress, serverPort)) { 61 | Serial.println("connection failed"); 62 | while (1) 63 | delay(1000); 64 | } 65 | Serial.println("Connected to the server"); 66 | 67 | dccexProtocol.setLogStream(&Serial); 68 | 69 | // Pass the delegate instance to wiThrottleProtocol 70 | dccexProtocol.setDelegate(&myDelegate); 71 | 72 | // Pass the communication to wiThrottleProtocol 73 | dccexProtocol.connect(&client); 74 | Serial.println("DCC-EX connected"); 75 | } 76 | 77 | void loop() { 78 | 79 | // parse incoming messages 80 | dccexProtocol.check(); 81 | 82 | // every 10 seconds change speed and set a random function on or off 83 | if ((millis() - lastTime) >= 10000) { 84 | 85 | lastTrackType++; 86 | if (lastTrackType > 4) 87 | lastTrackType = 0; 88 | 89 | if (lastTrackType == 0) { 90 | Serial.println("Set A:MAIN B:PROG"); 91 | dccexProtocol.setTrackType('A', MAIN, 0); 92 | // dccexProtocol.setTrackType('B',PROG,0); 93 | } else if (lastTrackType == 1) { 94 | Serial.println("Set A:PROG B:DC 10"); 95 | dccexProtocol.setTrackType('A', PROG, 0); 96 | dccexProtocol.setTrackType('B', DC, 10); 97 | } else if (lastTrackType == 2) { 98 | Serial.println("Set A:DC 10 B:DCX 11"); 99 | dccexProtocol.setTrackType('A', DC, 10); 100 | dccexProtocol.setTrackType('B', DCX, 11); 101 | } else if (lastTrackType == 3) { 102 | Serial.println("Set A:DCX 11 B:NONE"); 103 | dccexProtocol.setTrackType('B', NONE, 0); 104 | dccexProtocol.setTrackType('A', DCX, 11); 105 | } else if (lastTrackType == 4) { 106 | Serial.println("Set A:NONE B:MAIN"); 107 | dccexProtocol.setTrackType('A', NONE, 0); 108 | dccexProtocol.setTrackType('B', MAIN, 0); 109 | } 110 | 111 | lastTime = millis(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Track_type/config.example.h: -------------------------------------------------------------------------------- 1 | // Example user configuration to use with the DCCEXProtocol examples 2 | // Copy this file and rename to config.h 3 | // Replace the variables below with the appropriate values for your environment 4 | const char *ssid = "YOUR_SSID_HERE"; // WiFi SSID name here 5 | const char *password = "YOUR_PASSWORD_HERE"; // WiFi password here 6 | IPAddress serverAddress(192, 168, 4, 1); // IP address of your EX-CommandStation 7 | int serverPort = 2560; // Network port of your EX-CommandStation 8 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Turnout_Control/DCCEXProtocol_Turnout_Control.ino: -------------------------------------------------------------------------------- 1 | // WiThrottleProtocol library: Turnout/Point control example 2 | // 3 | // Shows how to retrieve the Turnouts/Points list and control them 4 | // Tested with ESP32-WROOM board 5 | // 6 | // NOTE: You must have at least two turnouts defined in your EX-CommandStation 7 | // 8 | // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 9 | // Luca Dentella, 2020 10 | 11 | #include 12 | #include 13 | 14 | 15 | // If we haven't got a custom config.h, use the example 16 | #if __has_include("config.h") 17 | #include "config.h" 18 | #else 19 | #warning config.h not found. Using defaults from config.example.h 20 | #include "config.example.h" 21 | #endif 22 | 23 | void printTurnouts(); 24 | 25 | // Delegate class 26 | class MyDelegate : public DCCEXProtocolDelegate { 27 | 28 | public: 29 | void receivedServerVersion(int major, int minor, int patch) override { 30 | Serial.print("Received version: "); 31 | Serial.print(major); 32 | Serial.print("."); 33 | Serial.print(minor); 34 | Serial.print("."); 35 | Serial.println(patch); 36 | } 37 | 38 | void receivedTurnoutList() override { 39 | Serial.print("Received turnout list:"); 40 | printTurnouts(); 41 | } 42 | 43 | void receivedTurnoutAction(int turnoutId, bool thrown) override { 44 | Serial.print("Received turnout action ID|thrown: "); 45 | Serial.print(turnoutId); 46 | Serial.print("|"); 47 | Serial.println(thrown); 48 | } 49 | }; 50 | 51 | unsigned long lastTime = 0; 52 | 53 | bool doneTurnouts = false; 54 | bool doneRoutes = false; 55 | Turnout *turnout1 = nullptr; 56 | Turnout *turnout2 = nullptr; 57 | 58 | // Global objects 59 | WiFiClient client; 60 | DCCEXProtocol dccexProtocol; 61 | MyDelegate myDelegate; 62 | 63 | void printTurnouts() { 64 | for (Turnout *turnout = dccexProtocol.turnouts->getFirst(); turnout; turnout = turnout->getNext()) { 65 | int id = turnout->getId(); 66 | const char *name = turnout->getName(); 67 | Serial.print(id); 68 | Serial.print(" ~"); 69 | Serial.print(name); 70 | Serial.println("~"); 71 | } 72 | Serial.println("\n"); 73 | } 74 | 75 | void setup() { 76 | 77 | Serial.begin(115200); 78 | Serial.println("DCCEXProtocol Turnout/Point Demo"); 79 | Serial.println(); 80 | 81 | // Connect to WiFi network 82 | Serial.println("Connecting to WiFi.."); 83 | WiFi.begin(ssid, password); 84 | while (WiFi.status() != WL_CONNECTED) 85 | delay(1000); 86 | Serial.print("Connected with IP: "); 87 | Serial.println(WiFi.localIP()); 88 | 89 | // Connect to the server 90 | Serial.println("Connecting to the server..."); 91 | if (!client.connect(serverAddress, serverPort)) { 92 | Serial.println("connection failed"); 93 | while (1) 94 | delay(1000); 95 | } 96 | Serial.println("Connected to the server"); 97 | 98 | // Logging on Serial 99 | dccexProtocol.setLogStream(&Serial); 100 | 101 | // Pass the delegate instance to wiThrottleProtocol 102 | dccexProtocol.setDelegate(&myDelegate); 103 | 104 | // Pass the communication to wiThrottleProtocol 105 | dccexProtocol.connect(&client); 106 | Serial.println("DCC-EX connected"); 107 | 108 | dccexProtocol.requestServerVersion(); 109 | 110 | lastTime = millis(); 111 | } 112 | 113 | void loop() { 114 | // parse incoming messages 115 | dccexProtocol.check(); 116 | 117 | // sequentially request and get the required lists. To avoid overloading the buffer 118 | dccexProtocol.getLists(false, true, false, false); 119 | 120 | if (dccexProtocol.receivedLists() && !doneTurnouts) { 121 | if (dccexProtocol.getTurnoutCount() >= 2) { 122 | turnout1 = dccexProtocol.turnouts->getFirst(); 123 | Serial.print("Turnout 1 id: "); 124 | Serial.println(turnout1->getId()); 125 | turnout2 = turnout1->getNext(); 126 | Serial.print("Turnout 2 id: "); 127 | Serial.println(turnout2->getId()); 128 | } 129 | doneTurnouts = true; 130 | } 131 | 132 | if ((millis() - lastTime) >= 10000) { 133 | if (doneTurnouts) { 134 | int action = random(0, 100); 135 | bool throwTurnout = (action > 50) ? 1 : 0; 136 | if (throwTurnout) { 137 | dccexProtocol.throwTurnout(turnout1->getId()); 138 | } else { 139 | dccexProtocol.closeTurnout(turnout1->getId()); 140 | } 141 | action = random(0, 100); 142 | throwTurnout = (action > 50) ? 1 : 0; 143 | if (throwTurnout) { 144 | dccexProtocol.throwTurnout(turnout2->getId()); 145 | } else { 146 | dccexProtocol.closeTurnout(turnout2->getId()); 147 | } 148 | } 149 | 150 | lastTime = millis(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_Turnout_Control/config.example.h: -------------------------------------------------------------------------------- 1 | // Example user configuration to use with the DCCEXProtocol examples 2 | // Copy this file and rename to config.h 3 | // Replace the variables below with the appropriate values for your environment 4 | const char *ssid = "YOUR_SSID_HERE"; // WiFi SSID name here 5 | const char *password = "YOUR_PASSWORD_HERE"; // WiFi password here 6 | IPAddress serverAddress(192, 168, 4, 1); // IP address of your EX-CommandStation 7 | int serverPort = 2560; // Network port of your EX-CommandStation 8 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_mDNS/DCCEXProtocol_mDNS.ino: -------------------------------------------------------------------------------- 1 | // WiThrottleProtocol library: mDNS example 2 | // 3 | // Shows how to retrieve the list of discovered servers 4 | // Tested with ESP32-WROOM board 5 | // 6 | // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 7 | 8 | #include 9 | #include 10 | 11 | // If we haven't got a custom config.h, use the example 12 | #if __has_include("config.h") 13 | #include "config.h" 14 | #else 15 | #warning config.h not found. Using defaults from config.example.h 16 | #include "config.example.h" 17 | #endif 18 | 19 | void printMdnsServers(); 20 | 21 | #define MAX_SERVERS 20 22 | 23 | IPAddress foundWitServersIPs[MAX_SERVERS]; 24 | int foundWitServersPorts[MAX_SERVERS]; 25 | String foundWitServersNames[MAX_SERVERS]; 26 | int foundWitServersCount; 27 | 28 | unsigned long lastTime = 0; 29 | 30 | bool mdnsListenerStarted = false; 31 | 32 | // Global objects 33 | WiFiClient client; 34 | 35 | bool setupMdnsListner() { 36 | // setup the bonjour listener 37 | 38 | if (!MDNS.begin("mDNSTest")) { 39 | Serial.println("Error setting up MDNS responder!"); 40 | return false; 41 | } else { 42 | Serial.println("MDNS responder started"); 43 | return true; 44 | } 45 | } 46 | 47 | void printMdnsServers() { 48 | Serial.println(""); 49 | 50 | double startTime = millis(); 51 | double nowTime = startTime; 52 | 53 | const char *service = "withrottle"; 54 | const char *proto = "tcp"; 55 | 56 | Serial.print("Browsing for service "); 57 | Serial.print(service); 58 | Serial.print("."); 59 | Serial.print(proto); 60 | Serial.print(".local. on "); 61 | Serial.print(ssid); 62 | Serial.println(" ... "); 63 | 64 | int noOfWitServices = 0; 65 | while ((noOfWitServices == 0) && ((nowTime - startTime) <= 5000)) { // try for 5 seconds 66 | noOfWitServices = MDNS.queryService(service, proto); 67 | if (noOfWitServices == 0) { 68 | delay(500); 69 | Serial.print("."); 70 | } 71 | nowTime = millis(); 72 | } 73 | Serial.println(""); 74 | 75 | if (noOfWitServices > 0) { 76 | for (int i = 0; ((i < noOfWitServices) && (i < MAX_SERVERS)); ++i) { 77 | foundWitServersNames[i] = MDNS.hostname(i); 78 | foundWitServersIPs[i] = MDNS.IP(i); 79 | foundWitServersPorts[i] = MDNS.port(i); 80 | if (MDNS.hasTxt(i, "jmri")) { 81 | String node = MDNS.txt(i, "node"); 82 | node.toLowerCase(); 83 | if (foundWitServersNames[i].equals(node)) { 84 | foundWitServersNames[i] = "JMRI (v" + MDNS.txt(i, "jmri") + ")"; 85 | } 86 | } 87 | } 88 | } 89 | foundWitServersCount = noOfWitServices; 90 | 91 | // EX-CommnadStations in Access Point mode cannot advertise via mDNS, 92 | // so we have to guess it based on the SSID name 93 | String ssidString = String(ssid); 94 | if (ssidString == "DCCEX_") { 95 | foundWitServersIPs[foundWitServersCount].fromString("192.168.4.1"); 96 | foundWitServersPorts[foundWitServersCount] = 2560; 97 | foundWitServersNames[foundWitServersCount] = "'Guessed' EX-CS WiT server"; 98 | foundWitServersCount++; 99 | } 100 | 101 | for (int i = 0; ((i < foundWitServersCount) && (i < MAX_SERVERS)); ++i) { 102 | Serial.print("Name: "); 103 | Serial.print(foundWitServersNames[i]); 104 | Serial.print(" IP: "); 105 | Serial.print(foundWitServersIPs[i]); 106 | Serial.print(" : "); 107 | Serial.print(foundWitServersPorts[i]); 108 | Serial.println(); 109 | } 110 | } 111 | 112 | void setup() { 113 | 114 | Serial.begin(115200); 115 | Serial.println("DCCEXProtocol mDNS example"); 116 | Serial.println(); 117 | 118 | // Connect to WiFi network 119 | Serial.println("Connecting to WiFi.."); 120 | WiFi.begin(ssid, password); 121 | while (WiFi.status() != WL_CONNECTED) 122 | delay(1000); 123 | Serial.print("Connected with IP: "); 124 | Serial.println(WiFi.localIP()); 125 | Serial.println(); 126 | 127 | // setup the mDNS listner 128 | mdnsListenerStarted = setupMdnsListner(); 129 | 130 | // browse for services 131 | if (mdnsListenerStarted) { 132 | printMdnsServers(); 133 | } 134 | } 135 | 136 | void loop() { 137 | 138 | delay(20000); 139 | // Redo every 20 seconds - For demonstration purposes only! 140 | // Normally this will only be required once, immediately after you connect to the ssid. 141 | if (mdnsListenerStarted) { 142 | printMdnsServers(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /examples/DCCEXProtocol_mDNS/config.example.h: -------------------------------------------------------------------------------- 1 | // Example user configuration to use with the DCCEXProtocol examples 2 | // Copy this file and rename to config.h 3 | // Replace the variables below with the appropriate values for your environment 4 | const char *ssid = "YOUR_SSID_HERE"; // WiFi SSID name here 5 | const char *password = "YOUR_PASSWORD_HERE"; // WiFi password here 6 | IPAddress serverAddress(192, 168, 4, 1); // IP address of your EX-CommandStation 7 | int serverPort = 2560; // Network port of your EX-CommandStation 8 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=DCCEXProtocol 2 | version=1.2.0 3 | author=Peter Cole, Peter Akers 4 | maintainer=Peter Cole, Peter Akers 5 | sentence=DCC-EX Native Protocol implementation 6 | paragraph=This library implements the DCC-EX Native protocol, allowing a device to connect to the server and act as a client (such as a dedicated fast clock device or a hardware based throttle). 7 | category=Other 8 | url=https://dcc-ex.com/DCCEXProtocol/index.html 9 | architectures=* -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.13 2 | attrs==23.1.0 3 | Babel==2.13.1 4 | breathe==4.35.0 5 | cattrs==23.2.3 6 | certifi==2023.11.17 7 | charset-normalizer==3.3.2 8 | colorama==0.4.6 9 | docutils==0.20.1 10 | esbonio==0.16.3 11 | exceptiongroup==1.2.0 12 | idna==3.6 13 | imagesize==1.4.1 14 | Jinja2==3.1.2 15 | lsprotocol==2023.0.0 16 | MarkupSafe==2.1.3 17 | packaging==23.2 18 | platformdirs==4.0.0 19 | pyenchant==3.2.2 20 | pygls==1.2.1 21 | Pygments==2.17.2 22 | pyspellchecker==0.7.2 23 | requests==2.31.0 24 | snowballstemmer==2.2.0 25 | Sphinx==7.2.6 26 | sphinx-rtd-dark-mode==1.3.0 27 | sphinx-rtd-theme==2.0.0 28 | sphinx-sitemap==2.5.1 29 | sphinxcontrib-applehelp==1.0.7 30 | sphinxcontrib-devhelp==1.0.5 31 | sphinxcontrib-htmlhelp==2.0.4 32 | sphinxcontrib-jquery==4.1 33 | sphinxcontrib-jsmath==1.0.1 34 | sphinxcontrib-qthelp==1.0.6 35 | sphinxcontrib-serializinghtml==1.1.9 36 | sphinxcontrib-spelling==8.0.0 37 | typing_extensions==4.8.0 38 | urllib3==2.1.0 39 | -------------------------------------------------------------------------------- /src/DCCEXInbound.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2023 Chris Harlow 9 | * Copyright © 2023 Peter Akers 10 | * Copyright © 2023 Peter Cole 11 | * 12 | * This work is licensed under the Creative Commons Attribution-ShareAlike 13 | * 4.0 International License. To view a copy of this license, visit 14 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 15 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 16 | * 17 | * Attribution — You must give appropriate credit, provide a link to the 18 | * license, and indicate if changes were made. You may do so in any 19 | * reasonable manner, but not in any way that suggests the licensor 20 | * endorses you or your use. 21 | * 22 | * ShareAlike — If you remix, transform, or build upon the material, you 23 | * must distribute your contributions under the same license as the 24 | * original. 25 | * 26 | * All other rights reserved. 27 | * 28 | */ 29 | 30 | // This is a demonstration script for a simplified <> command parser 31 | // It accepts <> command inputs and breaks it down to values 32 | 33 | // The dump() function is used to list the parameters obtained. 34 | // so this is the best place to look for how to access the results. 35 | #include "DCCEXInbound.h" 36 | #include 37 | 38 | // Internal stuff for the parser and getters. 39 | const int32_t QUOTE_FLAG = 0x77777000; 40 | const int32_t QUOTE_FLAG_AREA = 0xFFFFF000; 41 | enum splitState : byte { 42 | FIND_START, 43 | SET_OPCODE, 44 | SKIP_SPACES, 45 | CHECK_SIGN, 46 | BUILD_PARAM, 47 | SKIPOVER_TEXT, 48 | COMPLETE_i_COMMAND 49 | }; 50 | 51 | int16_t DCCEXInbound::_maxParams = 0; 52 | int16_t DCCEXInbound::_parameterCount = 0; 53 | byte DCCEXInbound::_opcode = 0; 54 | int32_t *DCCEXInbound::_parameterValues = nullptr; 55 | char *DCCEXInbound::_cmdBuffer = nullptr; 56 | 57 | // Public methods 58 | 59 | void DCCEXInbound::setup(int16_t maxParameterValues) { 60 | _parameterValues = (int32_t *)realloc(_parameterValues, maxParameterValues * sizeof(int32_t)); 61 | _maxParams = maxParameterValues; 62 | _parameterCount = 0; 63 | _opcode = 0; 64 | } 65 | 66 | void DCCEXInbound::cleanup() { 67 | if (_parameterValues) { 68 | free(_parameterValues); 69 | _parameterValues = nullptr; 70 | } 71 | } 72 | 73 | byte DCCEXInbound::getOpcode() { return _opcode; } 74 | 75 | int16_t DCCEXInbound::getParameterCount() { return _parameterCount; } 76 | 77 | int32_t DCCEXInbound::getNumber(int16_t parameterNumber) { 78 | if (parameterNumber < 0 || parameterNumber >= _parameterCount) 79 | return 0; 80 | if (_isTextInternal(parameterNumber)) 81 | return 0; 82 | return _parameterValues[parameterNumber]; 83 | } 84 | 85 | bool DCCEXInbound::isTextParameter(int16_t parameterNumber) { 86 | if (parameterNumber < 0 || parameterNumber >= _parameterCount) 87 | return false; 88 | return _isTextInternal(parameterNumber); 89 | } 90 | 91 | char *DCCEXInbound::getTextParameter(int16_t parameterNumber) { 92 | if (parameterNumber < 0 || parameterNumber >= _parameterCount) 93 | return 0; 94 | if (!_isTextInternal(parameterNumber)) 95 | return 0; 96 | return _cmdBuffer + (_parameterValues[parameterNumber] & ~QUOTE_FLAG_AREA); 97 | } 98 | 99 | char *DCCEXInbound::copyTextParameter(int16_t parameterNumber) { 100 | char *unsafe = getTextParameter(parameterNumber); 101 | if (!unsafe) 102 | return unsafe; // bad parameter number probably 103 | char *safe = (char *)malloc(strlen(unsafe) + 1); 104 | strcpy(safe, unsafe); 105 | return safe; 106 | } 107 | 108 | bool DCCEXInbound::parse(char *command) { 109 | _parameterCount = 0; 110 | _opcode = 0; 111 | _cmdBuffer = command; 112 | 113 | int32_t runningValue = 0; 114 | char *remainingCmd = command; 115 | bool signNegative = false; 116 | splitState state = FIND_START; 117 | 118 | while (_parameterCount < _maxParams) { 119 | byte hot = *remainingCmd; 120 | if (hot == 0) 121 | return false; // no > on end of command. 122 | 123 | // In this switch, break will go on to next char but continue will 124 | // rescan the current char. 125 | switch (state) { 126 | case FIND_START: // looking for < 127 | if (hot == '<') 128 | state = SET_OPCODE; 129 | break; 130 | case SET_OPCODE: 131 | _opcode = hot; 132 | if (_opcode == 'i') { 133 | // special case breaks all normal rules 134 | _parameterValues[_parameterCount] = QUOTE_FLAG | (remainingCmd - _cmdBuffer + 1); 135 | _parameterCount++; 136 | state = COMPLETE_i_COMMAND; 137 | break; 138 | } 139 | state = SKIP_SPACES; 140 | break; 141 | 142 | case SKIP_SPACES: // skipping spaces before a param 143 | if (hot == ' ') 144 | break; // ignore 145 | if (hot == '>') 146 | return true; 147 | state = CHECK_SIGN; 148 | continue; 149 | 150 | case CHECK_SIGN: // checking sign or quotes start param. 151 | if (hot == '"') { 152 | // for a string parameter, the value is the offset of the first char in the cmd. 153 | _parameterValues[_parameterCount] = QUOTE_FLAG | (remainingCmd - _cmdBuffer + 1); 154 | _parameterCount++; 155 | state = SKIPOVER_TEXT; 156 | break; 157 | } 158 | runningValue = 0; 159 | state = BUILD_PARAM; 160 | signNegative = hot == '-'; 161 | if (signNegative) 162 | break; 163 | continue; 164 | 165 | case BUILD_PARAM: // building a parameter 166 | if (hot >= '0' && hot <= '9') { 167 | runningValue = 10 * runningValue + (hot - '0'); 168 | break; 169 | } 170 | if (hot >= 'a' && hot <= 'z') 171 | hot = hot - 'a' + 'A'; // uppercase a..z 172 | 173 | if (hot == '_' || (hot >= 'A' && hot <= 'Z')) { 174 | // Super Kluge to turn keywords into a hash value that can be recognised later 175 | runningValue = ((runningValue << 5) + runningValue) ^ hot; 176 | break; 177 | } 178 | // did not detect 0-9 or keyword so end of parameter detected 179 | _parameterValues[_parameterCount] = runningValue * (signNegative ? -1 : 1); 180 | _parameterCount++; 181 | state = SKIP_SPACES; 182 | continue; 183 | 184 | case SKIPOVER_TEXT: 185 | if (hot == '"') { 186 | *remainingCmd = '\0'; // overwrite " in command buffer with the end-of-string 187 | state = SKIP_SPACES; 188 | } 189 | break; 190 | case COMPLETE_i_COMMAND: 191 | if (hot == '>') { 192 | *remainingCmd = '\0'; // overwrite > in command buffer with the end-of-string 193 | return true; 194 | } 195 | break; 196 | } 197 | remainingCmd++; 198 | } 199 | return false; // we ran out of max parameters 200 | } 201 | 202 | void DCCEXInbound::dump(Print *out) { 203 | out->print(F("\nDCCEXInbound Opcode='")); 204 | if (_opcode) 205 | out->write(_opcode); 206 | else 207 | out->print(F("\\0")); 208 | out->println('\''); 209 | 210 | for (int i = 0; i < getParameterCount(); i++) { 211 | if (isTextParameter(i)) { 212 | out->print(F("getTextParameter(")); 213 | out->print(i); 214 | out->print(F(")=\"")); 215 | out->print(getTextParameter(i)); 216 | out->println('"'); 217 | } else { 218 | out->print(F("getNumber(")); 219 | out->print(i); 220 | out->print(F(")=")); 221 | out->println(getNumber(i)); 222 | } 223 | } 224 | } 225 | 226 | // Private methods 227 | 228 | bool DCCEXInbound::_isTextInternal(int16_t n) { return ((_parameterValues[n] & QUOTE_FLAG_AREA) == QUOTE_FLAG); } 229 | -------------------------------------------------------------------------------- /src/DCCEXInbound.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2023 Chris Harlow 9 | * Copyright © 2023 Peter Akers 10 | * Copyright © 2023 Peter Cole 11 | * 12 | * This work is licensed under the Creative Commons Attribution-ShareAlike 13 | * 4.0 International License. To view a copy of this license, visit 14 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 15 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 16 | * 17 | * Attribution — You must give appropriate credit, provide a link to the 18 | * license, and indicate if changes were made. You may do so in any 19 | * reasonable manner, but not in any way that suggests the licensor 20 | * endorses you or your use. 21 | * 22 | * ShareAlike — If you remix, transform, or build upon the material, you 23 | * must distribute your contributions under the same license as the 24 | * original. 25 | * 26 | * All other rights reserved. 27 | * 28 | */ 29 | 30 | #ifndef DCCEXINBOUND_H 31 | #define DCCEXINBOUND_H 32 | 33 | #include 34 | 35 | /* How to use this: 36 | 1) setup is done once with your expected max parameter count. 37 | 2) Call parse with your command input buffer. 38 | If it returns true... you have results. 39 | 40 | 3) Use the get... functions to access the parameters. 41 | These parameters are ONLY VALID until you next call parse. 42 | */ 43 | 44 | /// @brief Inbound DCC-EX command parser class to parse commands and provide interpreted parameters 45 | class DCCEXInbound { 46 | public: 47 | /// @brief Setup parser once with enough space to handle the maximum number of 48 | /// parameters expected from the command station. 49 | /// @param maxParameterValues Maximum parameter values to accommodate 50 | static void setup(int16_t maxParameterValues); 51 | 52 | /// @brief Cleanup parser 53 | static void cleanup(); 54 | 55 | /// @brief Pass in a command string to parse 56 | /// @param command Char array of command to parse 57 | /// @return True if parsed ok, false if badly terminated command or too many parameters 58 | static bool parse(char *command); 59 | 60 | /// @brief Gets the DCC-EX OPCODE of the parsed command (the first char after the <) 61 | static byte getOpcode(); 62 | 63 | /// @brief Gets number of parameters detected after OPCODE is 4 parameters! 64 | /// @return Number of parameters 65 | static int16_t getParameterCount(); 66 | 67 | /// @brief Gets a numeric parameter (or hashed keyword) from parsed command 68 | /// @return The numeric parameter 69 | static int32_t getNumber(int16_t parameterNumber); 70 | 71 | /// @brief Checks if a parameter is actually text rather than numeric 72 | /// @param parameterNumber The number of the parameter to check 73 | /// @return true|false 74 | static bool isTextParameter(int16_t parameterNumber); 75 | 76 | /// @brief Gets address of text type parameter. 77 | /// does not create permanent copy 78 | /// @param parameterNumber The number of the parameter to retrieve 79 | /// @return Char array of text (use once and discard) 80 | static char *getTextParameter(int16_t parameterNumber); 81 | 82 | /// @brief gets address of a heap copy of text type parameter. 83 | /// @param parameterNumber 84 | /// @return 85 | static char *copyTextParameter(int16_t parameterNumber); 86 | 87 | /// @brief dump list of parameters obtained 88 | /// @param out Address of output e.g. &Serial 89 | static void dump(Print *); 90 | 91 | private: 92 | static int16_t _maxParams; 93 | static int16_t _parameterCount; 94 | static byte _opcode; 95 | static int32_t *_parameterValues; 96 | static char *_cmdBuffer; 97 | static bool _isTextInternal(int16_t n); 98 | }; 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /src/DCCEXRoutes.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2023 Peter Akers 9 | * Copyright © 2023 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #include "DCCEXRoutes.h" 30 | #include 31 | 32 | // Public methods 33 | 34 | Route *Route::_first = nullptr; 35 | 36 | Route::Route(int id) { 37 | _id = id; 38 | _name = nullptr; 39 | _next = nullptr; 40 | if (!_first) { 41 | _first = this; 42 | } else { 43 | Route *current = _first; 44 | while (current->_next != nullptr) { 45 | current = current->_next; 46 | } 47 | current->_next = this; 48 | } 49 | } 50 | 51 | int Route::getId() { return _id; } 52 | 53 | void Route::setName(const char *name) { 54 | if (_name) { 55 | delete[] _name; 56 | _name = nullptr; 57 | } 58 | int nameLength = strlen(name); 59 | _name = new char[nameLength + 1]; 60 | strcpy(_name, name); 61 | } 62 | 63 | const char *Route::getName() { return _name; } 64 | 65 | void Route::setType(RouteType type) { _type = type; } 66 | 67 | RouteType Route::getType() { return (RouteType)_type; } 68 | 69 | Route *Route::getFirst() { return _first; } 70 | 71 | void Route::setNext(Route *route) { _next = route; } 72 | 73 | Route *Route::getNext() { return _next; } 74 | 75 | Route *Route::getById(int id) { 76 | for (Route *r = Route::getFirst(); r; r = r->getNext()) { 77 | if (r->getId() == id) { 78 | return r; 79 | } 80 | } 81 | return nullptr; 82 | } 83 | 84 | void Route::clearRouteList() { 85 | // Count Routes in list 86 | int routeCount = 0; 87 | Route *currentRoute = Route::getFirst(); 88 | while (currentRoute != nullptr) { 89 | routeCount++; 90 | currentRoute = currentRoute->getNext(); 91 | } 92 | 93 | // Store Route pointers in an array for clean up 94 | Route **deleteRoutes = new Route *[routeCount]; 95 | currentRoute = Route::getFirst(); 96 | for (int i = 0; i < routeCount; i++) { 97 | deleteRoutes[i] = currentRoute; 98 | currentRoute = currentRoute->getNext(); 99 | } 100 | 101 | // Delete each Route 102 | for (int i = 0; i < routeCount; i++) { 103 | delete deleteRoutes[i]; 104 | } 105 | 106 | // Clean up the array of pointers 107 | delete[] deleteRoutes; 108 | 109 | // Reset first pointer 110 | Route::_first = nullptr; 111 | } 112 | 113 | Route::~Route() { 114 | _removeFromList(this); 115 | 116 | if (_name) { 117 | delete[] _name; 118 | _name = nullptr; 119 | } 120 | 121 | _next = nullptr; 122 | } 123 | 124 | // Private methods 125 | 126 | void Route::_removeFromList(Route *route) { 127 | if (!route) { 128 | return; 129 | } 130 | 131 | if (getFirst() == route) { 132 | _first = route->getNext(); 133 | } else { 134 | Route *currentRoute = _first; 135 | while (currentRoute && currentRoute->getNext() != route) { 136 | currentRoute = currentRoute->getNext(); 137 | } 138 | if (currentRoute) { 139 | currentRoute->setNext(route->getNext()); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/DCCEXRoutes.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2023 Peter Akers 9 | * Copyright © 2023 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #ifndef DCCEXROUTES_H 30 | #define DCCEXROUTES_H 31 | 32 | #include 33 | 34 | enum RouteType { 35 | RouteTypeRoute = 'R', 36 | RouteTypeAutomation = 'A', 37 | }; 38 | 39 | /// @brief Class to contain and maintain the various Route attributes and methods 40 | class Route { 41 | public: 42 | /// @brief Constructor 43 | /// @param id Route ID 44 | Route(int id); 45 | 46 | /// @brief Get route ID 47 | /// @return ID of the route 48 | int getId(); 49 | 50 | /// @brief Set route name 51 | /// @param name Name to set for the route 52 | void setName(const char *name); 53 | 54 | /// @brief Get route name 55 | /// @return Current name of the route 56 | const char *getName(); 57 | 58 | /// @brief Set route type (A automation, R route) 59 | /// @param type RouteType - RouteTypeAutomation|RouteTypeRoute 60 | void setType(RouteType type); 61 | 62 | /// @brief Get route type (A automation, R route) 63 | /// @return RouteTypeAutomation|RouteTypeRoute 64 | RouteType getType(); 65 | 66 | /// @brief Get first Route object 67 | /// @return Pointer to the first Route object 68 | static Route *getFirst(); 69 | 70 | /// @brief Set the next route in the list 71 | /// @param route Pointer to the next route 72 | void setNext(Route *route); 73 | 74 | /// @brief Get next Route object 75 | /// @return Pointer to the next Route object 76 | Route *getNext(); 77 | 78 | /// @brief Get route object by its ID 79 | /// @return Pointer to the Route, or nullptr if not found 80 | static Route *getById(int id); 81 | 82 | /// @brief Clear the list of routes 83 | static void clearRouteList(); 84 | 85 | /// @brief Destructor for a route 86 | ~Route(); 87 | 88 | private: 89 | int _id; 90 | char *_name; 91 | char _type; 92 | static Route *_first; 93 | Route *_next; 94 | 95 | /// @brief Remove the route from the list 96 | /// @param route Pointer to the route to remove 97 | void _removeFromList(Route *route); 98 | }; 99 | 100 | #endif -------------------------------------------------------------------------------- /src/DCCEXTurnouts.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2023 Peter Akers 9 | * Copyright © 2023 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #include "DCCEXTurnouts.h" 30 | #include 31 | 32 | Turnout *Turnout::_first = nullptr; 33 | 34 | Turnout::Turnout(int id, bool thrown) { 35 | _id = id; 36 | _thrown = thrown; 37 | _name = nullptr; 38 | _next = nullptr; 39 | if (!_first) { 40 | _first = this; 41 | } else { 42 | Turnout *current = _first; 43 | while (current->_next != nullptr) { 44 | current = current->_next; 45 | } 46 | current->_next = this; 47 | } 48 | } 49 | 50 | void Turnout::setThrown(bool thrown) { _thrown = thrown; } 51 | 52 | void Turnout::setName(const char *name) { 53 | if (_name) { 54 | delete[] _name; 55 | _name = nullptr; 56 | } 57 | int nameLength = strlen(name); 58 | _name = new char[nameLength + 1]; 59 | strcpy(_name, name); 60 | } 61 | 62 | int Turnout::getId() { return _id; } 63 | 64 | const char *Turnout::getName() { return _name; } 65 | 66 | bool Turnout::getThrown() { return _thrown; } 67 | 68 | Turnout *Turnout::getFirst() { return _first; } 69 | 70 | void Turnout::setNext(Turnout *turnout) { _next = turnout; } 71 | 72 | Turnout *Turnout::getNext() { return _next; } 73 | 74 | Turnout *Turnout::getById(int id) { 75 | for (Turnout *t = Turnout::getFirst(); t; t = t->getNext()) { 76 | if (t->getId() == id) { 77 | return t; 78 | } 79 | } 80 | return nullptr; 81 | } 82 | 83 | void Turnout::clearTurnoutList() { 84 | // Count Turnouts in list 85 | int turnoutCount = 0; 86 | Turnout *currentTurnout = Turnout::getFirst(); 87 | while (currentTurnout != nullptr) { 88 | turnoutCount++; 89 | currentTurnout = currentTurnout->getNext(); 90 | } 91 | 92 | // Store Turnout pointers in an array for clean up 93 | Turnout **deleteTurnouts = new Turnout *[turnoutCount]; 94 | currentTurnout = Turnout::getFirst(); 95 | for (int i = 0; i < turnoutCount; i++) { 96 | deleteTurnouts[i] = currentTurnout; 97 | currentTurnout = currentTurnout->getNext(); 98 | } 99 | 100 | // Delete each Turnout 101 | for (int i = 0; i < turnoutCount; i++) { 102 | delete deleteTurnouts[i]; 103 | } 104 | 105 | // Clean up the array of pointers 106 | delete[] deleteTurnouts; 107 | 108 | // Reset first pointer 109 | Turnout::_first = nullptr; 110 | } 111 | 112 | Turnout::~Turnout() { 113 | _removeFromList(this); 114 | 115 | if (_name) { 116 | delete[] _name; 117 | _name = nullptr; 118 | } 119 | 120 | _next = nullptr; 121 | } 122 | 123 | // Private methods 124 | 125 | void Turnout::_removeFromList(Turnout *turnout) { 126 | if (!turnout) { 127 | return; 128 | } 129 | 130 | if (getFirst() == turnout) { 131 | _first = turnout->getNext(); 132 | } else { 133 | Turnout *currentTurnout = _first; 134 | while (currentTurnout && currentTurnout->getNext() != turnout) { 135 | currentTurnout = currentTurnout->getNext(); 136 | } 137 | if (currentTurnout) { 138 | currentTurnout->setNext(turnout->getNext()); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/DCCEXTurnouts.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2023 Peter Akers 9 | * Copyright © 2023 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #ifndef DCCEXTURNOUTS_H 30 | #define DCCEXTURNOUTS_H 31 | 32 | #include 33 | 34 | /// @brief Class to contain and maintain the various Turnout/Point attributes and methods 35 | class Turnout { 36 | public: 37 | /// @brief Constructor for a Turnout object 38 | /// @param id Turnout ID 39 | /// @param thrown true (thrown)|false (closed) 40 | Turnout(int id, bool thrown); 41 | 42 | /// @brief Set thrown state (true thrown, false closed) 43 | /// @param thrown true|false 44 | void setThrown(bool thrown); 45 | 46 | /// @brief Set turnout name 47 | /// @param _name Name to set the turnout 48 | void setName(const char *_name); 49 | 50 | /// @brief Get turnout Id 51 | /// @return ID of the turnout 52 | int getId(); 53 | 54 | /// @brief Get turnout name 55 | /// @return Current name of the turnout 56 | const char *getName(); 57 | 58 | /// @brief Get thrown state (true thrown, false closed) 59 | /// @return true|false 60 | bool getThrown(); 61 | 62 | /// @brief Get first turnout object 63 | /// @return Pointer to the first Turnout object 64 | static Turnout *getFirst(); 65 | 66 | /// @brief Set the next turnout in the list 67 | /// @param turnout Pointer to the next Turnout 68 | void setNext(Turnout *turnout); 69 | 70 | /// @brief Get next turnout object 71 | /// @return Pointer to the next Turnout object 72 | Turnout *getNext(); 73 | 74 | /// @brief Get turnout object by turnout ID 75 | /// @param id ID of the turnout to retrieve 76 | /// @return Pointer to the turnout object or nullptr if not found 77 | static Turnout *getById(int id); 78 | 79 | /// @brief Clear the list of turnouts 80 | static void clearTurnoutList(); 81 | 82 | /// @brief Destructor for a Turnout 83 | ~Turnout(); 84 | 85 | private: 86 | static Turnout *_first; 87 | Turnout *_next; 88 | int _id; 89 | char *_name; 90 | bool _thrown; 91 | 92 | /// @brief Remove the turnout from the list 93 | /// @param turnout Pointer to the turnout to remove 94 | void _removeFromList(Turnout *turnout); 95 | }; 96 | 97 | #endif -------------------------------------------------------------------------------- /src/DCCEXTurntables.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2023 Peter Akers 9 | * Copyright © 2023 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #include "DCCEXTurntables.h" 30 | #include 31 | 32 | // class TurntableIndex 33 | 34 | TurntableIndex::TurntableIndex(int ttId, int id, int angle, const char *name) { 35 | _ttId = ttId; 36 | _id = id; 37 | _angle = angle; 38 | if (name) { 39 | int nameLength = strlen(name); 40 | _name = new char[nameLength + 1]; 41 | strcpy(_name, name); 42 | } else { 43 | _name = nullptr; 44 | } 45 | _nextIndex = nullptr; 46 | } 47 | 48 | int TurntableIndex::getTTId() { return _ttId; } 49 | 50 | int TurntableIndex::getId() { return _id; } 51 | 52 | int TurntableIndex::getAngle() { return _angle; } 53 | 54 | const char *TurntableIndex::getName() { return _name; } 55 | 56 | TurntableIndex *TurntableIndex::getNextIndex() { return _nextIndex; } 57 | 58 | TurntableIndex::~TurntableIndex() { 59 | if (_name) { 60 | delete[] _name; 61 | _name = nullptr; 62 | } 63 | _nextIndex = nullptr; 64 | } 65 | 66 | // class Turntable 67 | 68 | Turntable *Turntable::_first = nullptr; 69 | 70 | Turntable::Turntable(int id) { 71 | _id = id; 72 | _type = TurntableTypeUnknown; 73 | _index = 0; 74 | _numberOfIndexes = 0; 75 | _name = nullptr; 76 | _isMoving = false; 77 | _firstIndex = nullptr; 78 | _indexCount = 0; 79 | _next = nullptr; 80 | if (!_first) { 81 | _first = this; 82 | } else { 83 | Turntable *current = _first; 84 | while (current->_next != nullptr) { 85 | current = current->_next; 86 | } 87 | current->_next = this; 88 | } 89 | } 90 | 91 | int Turntable::getId() { return _id; } 92 | 93 | void Turntable::setType(TurntableType type) { _type = type; } 94 | 95 | TurntableType Turntable::getType() { return (TurntableType)_type; } 96 | 97 | void Turntable::setIndex(int index) { _index = index; } 98 | 99 | int Turntable::getIndex() { return _index; } 100 | 101 | void Turntable::setNumberOfIndexes(int numberOfIndexes) { _numberOfIndexes = numberOfIndexes; } 102 | 103 | int Turntable::getNumberOfIndexes() { return _numberOfIndexes; } 104 | 105 | void Turntable::setName(const char *name) { 106 | if (_name) { 107 | delete[] _name; 108 | _name = nullptr; 109 | } 110 | int nameLength = strlen(name); 111 | _name = new char[nameLength + 1]; 112 | strcpy(_name, name); 113 | } 114 | 115 | const char *Turntable::getName() { return _name; } 116 | 117 | void Turntable::setMoving(bool moving) { _isMoving = moving; } 118 | 119 | bool Turntable::isMoving() { return _isMoving; } 120 | 121 | int Turntable::getIndexCount() { return _indexCount; } 122 | 123 | Turntable *Turntable::getFirst() { return _first; } 124 | 125 | void Turntable::setNext(Turntable *turntable) { _next = turntable; } 126 | 127 | Turntable *Turntable::getNext() { return _next; } 128 | 129 | void Turntable::addIndex(TurntableIndex *index) { 130 | if (this->_firstIndex == nullptr) { 131 | this->_firstIndex = index; 132 | } else { 133 | TurntableIndex *current = this->_firstIndex; 134 | while (current->_nextIndex != nullptr) { 135 | current = current->_nextIndex; 136 | } 137 | current->_nextIndex = index; 138 | } 139 | _indexCount++; 140 | } 141 | 142 | TurntableIndex *Turntable::getFirstIndex() { return _firstIndex; } 143 | 144 | Turntable *Turntable::getById(int id) { 145 | for (Turntable *tt = Turntable::getFirst(); tt; tt = tt->getNext()) { 146 | if (tt->getId() == id) { 147 | return tt; 148 | } 149 | } 150 | return nullptr; 151 | } 152 | 153 | TurntableIndex *Turntable::getIndexById(int id) { 154 | for (TurntableIndex *tti = getFirstIndex(); tti; tti = tti->getNextIndex()) { 155 | if (tti->getId() == id) { 156 | return tti; 157 | } 158 | } 159 | return nullptr; 160 | } 161 | 162 | void Turntable::clearTurntableList() { 163 | // Count Turntables in list 164 | int turntableCount = 0; 165 | Turntable *currentTurntable = Turntable::getFirst(); 166 | while (currentTurntable != nullptr) { 167 | turntableCount++; 168 | currentTurntable = currentTurntable->getNext(); 169 | } 170 | 171 | // Store Turntable pointers in an array for clean up 172 | Turntable **deleteTurntables = new Turntable *[turntableCount]; 173 | currentTurntable = Turntable::getFirst(); 174 | for (int i = 0; i < turntableCount; i++) { 175 | deleteTurntables[i] = currentTurntable; 176 | currentTurntable = currentTurntable->getNext(); 177 | } 178 | 179 | // Delete each Turntable 180 | for (int i = 0; i < turntableCount; i++) { 181 | delete deleteTurntables[i]; 182 | } 183 | 184 | // Clean up the array of pointers 185 | delete[] deleteTurntables; 186 | 187 | // Reset first pointer 188 | Turntable::_first = nullptr; 189 | } 190 | 191 | Turntable::~Turntable() { 192 | _removeFromList(this); 193 | 194 | if (_name) { 195 | delete[] _name; 196 | _name = nullptr; 197 | } 198 | 199 | if (_firstIndex) { 200 | // Clean up index list 201 | TurntableIndex *currentIndex = _firstIndex; 202 | while (currentIndex != nullptr) { 203 | // Capture next index 204 | TurntableIndex *nextIndex = currentIndex->getNextIndex(); 205 | // Delete current index 206 | delete currentIndex; 207 | // Move to the next 208 | currentIndex = nextIndex; 209 | } 210 | // Set first to nullptr 211 | _firstIndex = nullptr; 212 | } 213 | 214 | _next = nullptr; 215 | } 216 | 217 | void Turntable::_removeFromList(Turntable *turntable) { 218 | if (!turntable) { 219 | return; 220 | } 221 | 222 | if (getFirst() == turntable) { 223 | _first = turntable->getNext(); 224 | } else { 225 | Turntable *currentTurntable = _first; 226 | while (currentTurntable && currentTurntable->getNext() != turntable) { 227 | currentTurntable = currentTurntable->getNext(); 228 | } 229 | if (currentTurntable) { 230 | currentTurntable->setNext(turntable->getNext()); 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/DCCEXTurntables.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2023 Peter Akers 9 | * Copyright © 2023 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #ifndef DCCEXTURNTABLES_H 30 | #define DCCEXTURNTABLES_H 31 | 32 | #include 33 | 34 | enum TurntableType { 35 | TurntableTypeDCC = 0, 36 | TurntableTypeEXTT = 1, 37 | TurntableTypeUnknown = 9, 38 | }; 39 | 40 | /// @brief Class to contain and maintain the various Turntable Index attributes and methods associated with a Turntable 41 | class TurntableIndex { 42 | public: 43 | /// @brief Constructor 44 | /// @param ttId ID of the turntable the index is associated with 45 | /// @param id ID of the index 46 | /// @param angle Angle from home for this index (0 - 3600) 47 | /// @param name Name of the index 48 | TurntableIndex(int ttId, int id, int angle, const char *name); 49 | 50 | /// @brief Get the turntable ID 51 | /// @return ID of the turntable this index is associated with 52 | int getTTId(); 53 | 54 | /// @brief Get index ID (0 is always home) 55 | /// @return ID of this index 56 | int getId(); 57 | 58 | /// @brief Get angle of the index from home 59 | /// @return Angle of this index from home (0 - 3600) 60 | int getAngle(); 61 | 62 | /// @brief Get index name 63 | /// @return Current name of the index 64 | const char *getName(); 65 | 66 | /// @brief Get next TurntableIndex object 67 | /// @return Pointer to the next TurntableIndex object 68 | TurntableIndex *getNextIndex(); 69 | 70 | /// @brief Destructor for an index 71 | ~TurntableIndex(); 72 | 73 | private: 74 | int _ttId; 75 | int _id; 76 | int _angle; 77 | char *_name; 78 | TurntableIndex *_nextIndex; 79 | 80 | friend class Turntable; 81 | }; 82 | 83 | /// @brief Class to contain and maintain the various Turntable attributes and methods 84 | class Turntable { 85 | public: 86 | /// @brief Constructor 87 | /// @param id ID of the turntable 88 | Turntable(int id); 89 | 90 | /// @brief Get turntable ID 91 | /// @return ID of the turntable 92 | int getId(); 93 | 94 | /// @brief Set turntable type 95 | /// @param type TurntableTypeDCC|TurntableTypeEXTT|TurntableTypeUnknown 96 | void setType(TurntableType type); 97 | 98 | /// @brief Get turntable type 99 | /// @return TurntableTypeDCC|TurntableTypeEXTT|TurntableTypeUnknown 100 | TurntableType getType(); 101 | 102 | /// @brief Set the current index for the turntable 103 | /// @param index ID of the index to set 104 | void setIndex(int index); 105 | 106 | /// @brief Get the current index for the turntable 107 | /// @return ID of the current index 108 | int getIndex(); 109 | 110 | /// @brief Set the number of indexes the turntable has defined (from the \ command response) 111 | /// @param numberOfIndexes Count of the indexes defined for the turntable including home 112 | void setNumberOfIndexes(int numberOfIndexes); 113 | 114 | /// @brief Get the number of indexes defined for the turntable 115 | /// @return Count of the indexes defined 116 | int getNumberOfIndexes(); 117 | 118 | /// @brief Set the turntable name 119 | /// @param name Name to set for the turntable 120 | void setName(const char *name); 121 | 122 | /// @brief Get the turntable name 123 | /// @return Current name of the turntable 124 | const char *getName(); 125 | 126 | /// @brief Set the movement state (false stationary, true moving) 127 | /// @param moving true|false 128 | void setMoving(bool moving); 129 | 130 | /// @brief Get movement state (false stationary, true moving) 131 | /// @return true|false 132 | bool isMoving(); 133 | 134 | /// @brief Get the count of indexes added to the index list (counted from the \ command response) 135 | /// @return Count of indexes received for this turntable including home 136 | int getIndexCount(); 137 | 138 | /// @brief Get the first turntable object 139 | /// @return Pointer to the first Turntable object 140 | static Turntable *getFirst(); 141 | 142 | /// @brief Set the next turntable in the list 143 | /// @param turntable Pointer to the next turntable 144 | void setNext(Turntable *turntable); 145 | 146 | /// @brief Get the next turntable object 147 | /// @return Pointer to the next Turntable object 148 | Turntable *getNext(); 149 | 150 | /// @brief Add a turntable index object to the index list for this turntable 151 | /// @param index TurntableIndex object to add 152 | void addIndex(TurntableIndex *index); 153 | 154 | /// @brief Get the first associated turntable index 155 | /// @return Pointer to the first associated TurntableIndex object 156 | TurntableIndex *getFirstIndex(); 157 | 158 | /// @brief Get a turntable object by its ID 159 | /// @param id ID of the turntable to retrieve 160 | /// @return Pointer to the Turntable object or nullptr if not found 161 | static Turntable *getById(int id); 162 | 163 | /// @brief Get TurntableIndex object by its ID 164 | /// @param id ID of the index to retrieve 165 | /// @return Pointer to the TurntableIndex object or nullptr if not found 166 | TurntableIndex *getIndexById(int id); 167 | 168 | /// @brief Clear the list of turntables 169 | static void clearTurntableList(); 170 | 171 | /// @brief Destructor for a turntable 172 | ~Turntable(); 173 | 174 | private: 175 | int _id; 176 | TurntableType _type; 177 | int _index; 178 | int _numberOfIndexes; 179 | char *_name; 180 | bool _isMoving; 181 | int _indexCount; 182 | static Turntable *_first; 183 | Turntable *_next; 184 | TurntableIndex *_firstIndex; 185 | 186 | /// @brief Remove the turntable from the list 187 | /// @param turntable Pointer to the turntable to remove 188 | void _removeFromList(Turntable *turntable); 189 | }; 190 | 191 | #endif -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(${CMAKE_SOURCE_DIR}/cmake/sanitize.cmake) 2 | 3 | file(GLOB_RECURSE SRC *.cpp) 4 | add_executable(DCCEXProtocolTests ${SRC}) 5 | 6 | # Reuse ArduinoCore-API fakes and mocks 7 | target_sources( 8 | DCCEXProtocolTests 9 | PRIVATE ${arduinocore-api_SOURCE_DIR}/test/src/MillisFake.cpp 10 | ${arduinocore-api_SOURCE_DIR}/test/src/PrintMock.cpp 11 | ${arduinocore-api_SOURCE_DIR}/test/src/StreamMock.cpp) 12 | target_include_directories(DCCEXProtocolTests 13 | PUBLIC ${arduinocore-api_SOURCE_DIR}/test/include) 14 | 15 | sanitize(address,undefined) 16 | 17 | target_compile_options(DCCEXProtocolTests PUBLIC -std=c++2a -Wall -Wextra 18 | -Wconversion -Wsign-conversion) 19 | 20 | FetchContent_Declare( 21 | googletest 22 | GIT_REPOSITORY https://github.com/google/googletest.git 23 | GIT_TAG main) 24 | FetchContent_MakeAvailable(googletest) 25 | 26 | target_link_libraries(DCCEXProtocolTests PRIVATE DCCEXProtocol 27 | GTest::gtest_main GTest::gmock) 28 | -------------------------------------------------------------------------------- /tests/general/test_DCCEXProtocol.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Vincent Hamp 9 | * Copyright © 2024 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #include "../setup/DCCEXProtocolTests.h" 30 | 31 | TEST_F(DCCEXProtocolTests, clearBufferWhenFull) { 32 | // Fill buffer with garbage 33 | for (auto i{0uz}; i < 500uz; ++i) 34 | _stream.write(static_cast('A' + (random() % 26))); 35 | 36 | // Proceed with normal message 37 | _stream << R"()"; 38 | EXPECT_CALL(_delegate, receivedMessage(StrEq("Hello World"))).Times(Exactly(1)); 39 | _dccexProtocol.check(); 40 | } 41 | -------------------------------------------------------------------------------- /tests/general/test_Message.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Vincent Hamp 9 | * Copyright © 2024 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #include "../setup/DCCEXProtocolTests.h" 30 | 31 | TEST_F(DCCEXProtocolTests, broadcastHelloWorld) { 32 | _stream << R"()"; 33 | EXPECT_CALL(_delegate, receivedMessage(StrEq("Hello World"))).Times(Exactly(1)); 34 | _dccexProtocol.check(); 35 | } 36 | -------------------------------------------------------------------------------- /tests/general/test_TrackPowerControl.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2025 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #include "../setup/DCCEXProtocolTests.h" 29 | 30 | /// @brief Validate that sending powerOn() sends <1> 31 | TEST_F(DCCEXProtocolTests, powerAllOn) { 32 | const char *expected = "<1>\r\n"; 33 | 34 | // Call power on 35 | _dccexProtocol.powerOn(); 36 | 37 | // Ensure the buffer has what's expected 38 | ASSERT_EQ(_stream.getBuffer(), expected); 39 | } 40 | 41 | /// @brief Validate that sending powerOff() sends <0> 42 | TEST_F(DCCEXProtocolTests, powerAllOff) { 43 | const char *expected = "<0>\r\n"; 44 | 45 | // Call power off 46 | _dccexProtocol.powerOff(); 47 | 48 | // Ensure the buffer has what's expected 49 | ASSERT_EQ(_stream.getBuffer(), expected); 50 | } 51 | 52 | /// @brief Validate that sending powerMainOn() sends <1 MAIN> 53 | TEST_F(DCCEXProtocolTests, powerMainOn) { 54 | const char *expected = "<1 MAIN>\r\n"; 55 | 56 | // Call power on 57 | _dccexProtocol.powerMainOn(); 58 | 59 | // Ensure the buffer has what's expected 60 | ASSERT_EQ(_stream.getBuffer(), expected); 61 | } 62 | 63 | /// @brief Validate that sending powerMainOff() sends <0 MAIN> 64 | TEST_F(DCCEXProtocolTests, powerMainOff) { 65 | const char *expected = "<0 MAIN>\r\n"; 66 | 67 | // Call power off 68 | _dccexProtocol.powerMainOff(); 69 | 70 | // Ensure the buffer has what's expected 71 | ASSERT_EQ(_stream.getBuffer(), expected); 72 | } 73 | 74 | /// @brief Validate that sending powerProgOn() sends <1 PROG> 75 | TEST_F(DCCEXProtocolTests, powerProgOn) { 76 | const char *expected = "<1 PROG>\r\n"; 77 | 78 | // Call power on 79 | _dccexProtocol.powerProgOn(); 80 | 81 | // Ensure the buffer has what's expected 82 | ASSERT_EQ(_stream.getBuffer(), expected); 83 | } 84 | 85 | /// @brief Validate that sending powerProgOff() sends <0 PROG> 86 | TEST_F(DCCEXProtocolTests, powerProgOff) { 87 | const char *expected = "<0 PROG>\r\n"; 88 | 89 | // Call power off 90 | _dccexProtocol.powerProgOff(); 91 | 92 | // Ensure the buffer has what's expected 93 | ASSERT_EQ(_stream.getBuffer(), expected); 94 | } 95 | 96 | /// @brief Validate that sending joinProg() sends <1 JOIN> 97 | TEST_F(DCCEXProtocolTests, joinProg) { 98 | const char *expected = "<1 JOIN>\r\n"; 99 | 100 | // Call join 101 | _dccexProtocol.joinProg(); 102 | 103 | // Ensure the buffer has what's expected 104 | ASSERT_EQ(_stream.getBuffer(), expected); 105 | } 106 | -------------------------------------------------------------------------------- /tests/general/test_TrackPowerParsing.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2025 Peter Cole 9 | * Copyright © 2024 Vincent Hamp 10 | * Copyright © 2024 Peter Cole 11 | * 12 | * This work is licensed under the Creative Commons Attribution-ShareAlike 13 | * 4.0 International License. To view a copy of this license, visit 14 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 15 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 16 | * 17 | * Attribution — You must give appropriate credit, provide a link to the 18 | * license, and indicate if changes were made. You may do so in any 19 | * reasonable manner, but not in any way that suggests the licensor 20 | * endorses you or your use. 21 | * 22 | * ShareAlike — If you remix, transform, or build upon the material, you 23 | * must distribute your contributions under the same license as the 24 | * original. 25 | * 26 | * All other rights reserved. 27 | * 28 | */ 29 | 30 | #include "../setup/DCCEXProtocolTests.h" 31 | 32 | TEST_F(DCCEXProtocolTests, allTracksOff) { 33 | _stream << ""; 34 | EXPECT_CALL(_delegate, receivedTrackPower(TrackPower::PowerOff)).Times(Exactly(1)); 35 | _dccexProtocol.check(); 36 | } 37 | 38 | TEST_F(DCCEXProtocolTests, allTracksOn) { 39 | _stream << ""; 40 | EXPECT_CALL(_delegate, receivedTrackPower(TrackPower::PowerOn)).Times(Exactly(1)); 41 | _dccexProtocol.check(); 42 | } 43 | 44 | TEST_F(DCCEXProtocolTests, mainTrackOn) { 45 | _stream << ""; 46 | EXPECT_CALL(_delegate, receivedTrackPower(TrackPower::PowerOn)).Times(Exactly(1)); 47 | _dccexProtocol.check(); 48 | } 49 | 50 | TEST_F(DCCEXProtocolTests, mainTrackOff) { 51 | _stream << ""; 52 | EXPECT_CALL(_delegate, receivedTrackPower(TrackPower::PowerOff)).Times(Exactly(1)); 53 | _dccexProtocol.check(); 54 | } 55 | -------------------------------------------------------------------------------- /tests/general/test_Version.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Vincent Hamp 9 | * Copyright © 2024 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #include "../setup/DCCEXProtocolTests.h" 30 | 31 | TEST_F(DCCEXProtocolTests, request) { 32 | _dccexProtocol.requestServerVersion(); 33 | EXPECT_EQ(_stream.getBuffer(), "\r\n"); 34 | _stream.clearBuffer(); 35 | } 36 | 37 | TEST_F(DCCEXProtocolTests, versionJustZeros) { 38 | EXPECT_FALSE(_dccexProtocol.receivedVersion()); 39 | _stream << ""; 40 | EXPECT_CALL(_delegate, receivedServerVersion(0, 0, 0)).Times(Exactly(1)); 41 | _dccexProtocol.check(); 42 | EXPECT_TRUE(_dccexProtocol.receivedVersion()); 43 | EXPECT_EQ(_dccexProtocol.getMajorVersion(), 0); 44 | EXPECT_EQ(_dccexProtocol.getMinorVersion(), 0); 45 | EXPECT_EQ(_dccexProtocol.getPatchVersion(), 0); 46 | } 47 | 48 | TEST_F(DCCEXProtocolTests, versionSingleDigits) { 49 | EXPECT_FALSE(_dccexProtocol.receivedVersion()); 50 | _stream << ""; 51 | EXPECT_CALL(_delegate, receivedServerVersion(1, 2, 3)).Times(Exactly(1)); 52 | _dccexProtocol.check(); 53 | EXPECT_TRUE(_dccexProtocol.receivedVersion()); 54 | EXPECT_EQ(_dccexProtocol.getMajorVersion(), 1); 55 | EXPECT_EQ(_dccexProtocol.getMinorVersion(), 2); 56 | EXPECT_EQ(_dccexProtocol.getPatchVersion(), 3); 57 | } 58 | 59 | TEST_F(DCCEXProtocolTests, versionMultipleDigits) { 60 | EXPECT_FALSE(_dccexProtocol.receivedVersion()); 61 | _stream << ""; 62 | EXPECT_CALL(_delegate, receivedServerVersion(92, 210, 10)).Times(Exactly(1)); 63 | _dccexProtocol.check(); 64 | EXPECT_TRUE(_dccexProtocol.receivedVersion()); 65 | EXPECT_EQ(_dccexProtocol.getMajorVersion(), 92); 66 | EXPECT_EQ(_dccexProtocol.getMinorVersion(), 210); 67 | EXPECT_EQ(_dccexProtocol.getPatchVersion(), 10); 68 | } 69 | 70 | TEST_F(DCCEXProtocolTests, versionIgnoreLabels) { 71 | EXPECT_FALSE(_dccexProtocol.receivedVersion()); 72 | _stream << ""; 73 | EXPECT_CALL(_delegate, receivedServerVersion(1, 2, 3)).Times(Exactly(1)); 74 | _dccexProtocol.check(); 75 | EXPECT_TRUE(_dccexProtocol.receivedVersion()); 76 | EXPECT_EQ(_dccexProtocol.getMajorVersion(), 1); 77 | EXPECT_EQ(_dccexProtocol.getMinorVersion(), 2); 78 | EXPECT_EQ(_dccexProtocol.getPatchVersion(), 3); 79 | } -------------------------------------------------------------------------------- /tests/loco/test_Consist.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #include "../setup/LocoTests.h" 29 | 30 | /// @brief Create a consist with three Loco objects 31 | TEST_F(LocoTests, createConsistByLoco) { 32 | // Create three locos for the consist 33 | char *functionList = "Lights/*Horn"; 34 | Loco *loco10 = new Loco(10, LocoSourceRoster); 35 | loco10->setName("Loco 10"); 36 | loco10->setupFunctions(functionList); 37 | Loco *loco2 = new Loco(2, LocoSourceRoster); 38 | loco2->setName("Loco 2"); 39 | loco2->setupFunctions(functionList); 40 | Loco *loco10000 = new Loco(10000, LocoSourceRoster); 41 | loco10000->setName("Loco 10000"); 42 | loco10000->setupFunctions(functionList); 43 | 44 | // Add locos to the consist, with 2 reversed 45 | Consist *consist = new Consist(); 46 | consist->setName("Test Legacy Consist"); 47 | consist->addLoco(loco10, Facing::FacingForward); 48 | consist->addLoco(loco2, Facing::FacingReversed); 49 | consist->addLoco(loco10000, Facing::FacingForward); 50 | 51 | // Validate consist makeup by object and address 52 | EXPECT_STREQ(consist->getName(), "Test Legacy Consist"); 53 | EXPECT_EQ(consist->getLocoCount(), 3); 54 | EXPECT_TRUE(consist->inConsist(loco10)); 55 | EXPECT_TRUE(consist->inConsist(loco2)); 56 | EXPECT_TRUE(consist->inConsist(loco10000)); 57 | EXPECT_TRUE(consist->inConsist(10)); 58 | EXPECT_TRUE(consist->inConsist(2)); 59 | EXPECT_TRUE(consist->inConsist(10000)); 60 | 61 | // Validate the first loco is 10 62 | EXPECT_EQ(consist->getFirst()->getLoco(), loco10); 63 | 64 | // Validate the consist speed and direction comes from the first loco 65 | EXPECT_EQ(consist->getSpeed(), 0); 66 | EXPECT_EQ(consist->getDirection(), Direction::Forward); 67 | loco2->setSpeed(35); 68 | loco10000->setDirection(Direction::Reverse); 69 | EXPECT_EQ(consist->getSpeed(), 0); 70 | EXPECT_EQ(consist->getDirection(), Direction::Forward); 71 | loco10->setSpeed(21); 72 | loco10->setDirection(Direction::Reverse); 73 | EXPECT_EQ(consist->getSpeed(), 21); 74 | EXPECT_EQ(consist->getDirection(), Direction::Reverse); 75 | 76 | // Validate removal of middle loco is as expected 77 | consist->removeLoco(loco2); 78 | EXPECT_EQ(consist->getLocoCount(), 2); 79 | EXPECT_EQ(consist->getFirst()->getLoco(), loco10); 80 | EXPECT_EQ(consist->getSpeed(), 21); 81 | EXPECT_EQ(consist->getDirection(), Direction::Reverse); 82 | 83 | // Validate removal of first loco is as expected 84 | consist->removeLoco(loco10); 85 | EXPECT_EQ(consist->getLocoCount(), 1); 86 | EXPECT_EQ(consist->getFirst()->getLoco(), loco10000); 87 | EXPECT_EQ(consist->getSpeed(), 0); 88 | EXPECT_EQ(consist->getDirection(), Direction::Reverse); 89 | 90 | // Validate removal of all locos 91 | consist->removeAllLocos(); 92 | EXPECT_EQ(consist->getLocoCount(), 0); 93 | EXPECT_EQ(consist->getFirst(), nullptr); 94 | EXPECT_EQ(consist->getSpeed(), 0); 95 | EXPECT_EQ(consist->getDirection(), Direction::Forward); 96 | 97 | // Clean up 98 | delete consist; 99 | } 100 | 101 | /// @brief Create a consist with three Locos by address 102 | TEST_F(LocoTests, createConsistByAddress) { 103 | // Add locos to the consist, with 2 reversed 104 | Consist *consist = new Consist(); 105 | consist->addLoco(10, Facing::FacingForward); 106 | consist->addLoco(2, Facing::FacingReversed); 107 | consist->addLoco(10000, Facing::FacingForward); 108 | 109 | // Validate consist makeup by object and address 110 | EXPECT_STREQ(consist->getName(), "10"); // name should be address of first loco 111 | EXPECT_EQ(consist->getLocoCount(), 3); 112 | EXPECT_TRUE(consist->inConsist(10)); 113 | EXPECT_TRUE(consist->inConsist(2)); 114 | EXPECT_TRUE(consist->inConsist(10000)); 115 | 116 | // Get loco objects for the next tests 117 | Loco *loco10 = consist->getByAddress(10)->getLoco(); 118 | ASSERT_NE(loco10, nullptr); 119 | ASSERT_EQ(loco10->getAddress(), 10); 120 | Loco *loco2 = consist->getByAddress(2)->getLoco(); 121 | ASSERT_NE(loco2, nullptr); 122 | ASSERT_EQ(loco2->getAddress(), 2); 123 | Loco *loco10000 = consist->getByAddress(10000)->getLoco(); 124 | ASSERT_NE(loco10000, nullptr); 125 | ASSERT_EQ(loco10000->getAddress(), 10000); 126 | 127 | // Validate the first loco address is 10 128 | EXPECT_EQ(consist->getFirst()->getLoco()->getAddress(), 10); 129 | 130 | // Validate the consist speed and direction comes from the first loco 131 | EXPECT_EQ(consist->getSpeed(), 0); 132 | EXPECT_EQ(consist->getDirection(), Direction::Forward); 133 | loco2->setSpeed(35); 134 | loco10000->setDirection(Direction::Reverse); 135 | EXPECT_EQ(consist->getSpeed(), 0); 136 | EXPECT_EQ(consist->getDirection(), Direction::Forward); 137 | loco10->setSpeed(21); 138 | loco10->setDirection(Direction::Reverse); 139 | EXPECT_EQ(consist->getSpeed(), 21); 140 | EXPECT_EQ(consist->getDirection(), Direction::Reverse); 141 | 142 | // Validate removal of middle loco is as expected 143 | consist->removeLoco(loco2); 144 | EXPECT_EQ(consist->getLocoCount(), 2); 145 | EXPECT_EQ(consist->getFirst()->getLoco(), loco10); 146 | EXPECT_EQ(consist->getSpeed(), 21); 147 | EXPECT_EQ(consist->getDirection(), Direction::Reverse); 148 | 149 | // Validate removal of first loco is as expected 150 | consist->removeLoco(loco10); 151 | EXPECT_EQ(consist->getLocoCount(), 1); 152 | EXPECT_EQ(consist->getFirst()->getLoco(), loco10000); 153 | EXPECT_EQ(consist->getSpeed(), 0); 154 | EXPECT_EQ(consist->getDirection(), Direction::Reverse); 155 | 156 | // Validate removal of all locos 157 | consist->removeAllLocos(); 158 | EXPECT_EQ(consist->getLocoCount(), 0); 159 | EXPECT_EQ(consist->getFirst(), nullptr); 160 | EXPECT_EQ(consist->getSpeed(), 0); 161 | EXPECT_EQ(consist->getDirection(), Direction::Forward); 162 | 163 | // Clean up the consist 164 | delete consist; 165 | } 166 | -------------------------------------------------------------------------------- /tests/loco/test_Loco.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Vincent Hamp 9 | * Copyright © 2024 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #include "../setup/LocoTests.h" 30 | 31 | /// @brief Create a single Loco 32 | TEST_F(LocoTests, createSingleLoco) { 33 | // Create an individual loco 34 | Loco *loco1 = new Loco(1, LocoSource::LocoSourceEntry); 35 | loco1->setName("Loco 1"); 36 | loco1->setupFunctions("Lights/*Horn/Bell///Function 5"); 37 | 38 | // Check address is 1, name is correct, and LocoSource correct 39 | EXPECT_EQ(loco1->getAddress(), 1); 40 | EXPECT_STREQ(loco1->getName(), "Loco 1"); 41 | EXPECT_EQ(loco1->getSource(), LocoSource::LocoSourceEntry); 42 | 43 | // Check our functions 44 | EXPECT_FALSE(loco1->isFunctionMomentary(0)); 45 | EXPECT_TRUE(loco1->isFunctionMomentary(1)); 46 | EXPECT_STREQ(loco1->getFunctionName(2), "Bell"); 47 | EXPECT_STREQ(loco1->getFunctionName(5), "Function 5"); 48 | 49 | // Check speed/direction changes 50 | EXPECT_EQ(loco1->getSpeed(), 0); 51 | EXPECT_EQ(loco1->getDirection(), Direction::Forward); 52 | loco1->setSpeed(13); 53 | loco1->setDirection(Direction::Reverse); 54 | EXPECT_EQ(loco1->getSpeed(), 13); 55 | EXPECT_EQ(loco1->getDirection(), Direction::Reverse); 56 | 57 | // Make sure this is not in the roster 58 | EXPECT_EQ(Loco::getFirst(), nullptr); 59 | 60 | // Make sure we can't find it by address either 61 | EXPECT_EQ(Loco::getByAddress(1), nullptr); 62 | 63 | // Ensure next is nullptr as this is the only loco 64 | EXPECT_EQ(loco1->getNext(), nullptr); 65 | 66 | // Clean up 67 | delete loco1; 68 | } 69 | 70 | /// @brief Create a roster of Locos 71 | TEST_F(LocoTests, createRoster) { 72 | // Roster should start empty, don't continue if it isn't 73 | ASSERT_EQ(_dccexProtocol.roster->getFirst(), nullptr); 74 | 75 | // Add three locos 76 | Loco *loco42 = new Loco(42, LocoSource::LocoSourceRoster); 77 | loco42->setName("Loco42"); 78 | Loco *loco9 = new Loco(9, LocoSource::LocoSourceRoster); 79 | loco9->setName("Loco9"); 80 | Loco *loco120 = new Loco(120, LocoSource::LocoSourceRoster); 81 | loco120->setName("Loco120"); 82 | 83 | // Now verify the roster, fatal error if first is nullptr 84 | Loco *firstLoco = _dccexProtocol.roster->getFirst(); 85 | ASSERT_NE(firstLoco, nullptr); 86 | 87 | // Check first loco details 88 | EXPECT_EQ(firstLoco->getAddress(), 42); 89 | EXPECT_STREQ(firstLoco->getName(), "Loco42"); 90 | EXPECT_EQ(firstLoco->getSource(), LocoSource::LocoSourceRoster); 91 | 92 | // Verify second loco details and fail fatally if next is nullptr 93 | Loco *secondLoco = firstLoco->getNext(); 94 | ASSERT_NE(secondLoco, nullptr); 95 | EXPECT_EQ(secondLoco->getAddress(), 9); 96 | EXPECT_STREQ(secondLoco->getName(), "Loco9"); 97 | EXPECT_EQ(secondLoco->getSource(), LocoSource::LocoSourceRoster); 98 | 99 | // Verify third loco details and fail fatally if next is nullptr 100 | Loco *thirdLoco = secondLoco->getNext(); 101 | ASSERT_NE(thirdLoco, nullptr); 102 | EXPECT_EQ(thirdLoco->getAddress(), 120); 103 | EXPECT_STREQ(thirdLoco->getName(), "Loco120"); 104 | EXPECT_EQ(thirdLoco->getSource(), LocoSource::LocoSourceRoster); 105 | 106 | // Verify end of linked list and fail fatally if next is not nullptr 107 | EXPECT_EQ(thirdLoco->getNext(), nullptr) 108 | << "Unexpected fourth Loco at address: " << thirdLoco->getNext()->getAddress(); 109 | } 110 | -------------------------------------------------------------------------------- /tests/loco/test_LocoUpdate.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Vincent Hamp 9 | * Copyright © 2024 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #include "../setup/LocoTests.h" 30 | 31 | /// @brief Create a small roster and check updates are received 32 | TEST_F(LocoTests, receiveRosterLocoUpdate) { 33 | // Setup a small roster, make sure it's created correctly 34 | ASSERT_EQ(_dccexProtocol.roster->getFirst(), nullptr); 35 | 36 | // Add two locos 37 | Loco *loco42 = new Loco(42, LocoSource::LocoSourceRoster); 38 | loco42->setName("Loco42"); 39 | Loco *loco120 = new Loco(120, LocoSource::LocoSourceRoster); 40 | loco120->setName("Loco120"); 41 | 42 | // Now verify the roster, fatal error if first is nullptr 43 | Loco *firstLoco = _dccexProtocol.roster->getFirst(); 44 | ASSERT_NE(firstLoco, nullptr); 45 | 46 | // Set a loco update for 42 in the stream: 47 | // - Speed byte = forward, speed 21 48 | // - Function 0 on 49 | _stream << ""; 50 | 51 | // Expect to receive the delegate call at the next check() 52 | EXPECT_CALL(_delegate, receivedLocoUpdate(loco42)).Times(Exactly(1)); 53 | // We should also expect an update with the details 54 | EXPECT_CALL(_delegate, receivedLocoBroadcast(42, 21, Direction::Forward, 1)).Times(Exactly(1)); 55 | _dccexProtocol.check(); 56 | 57 | // Validate expected result 58 | EXPECT_EQ(loco42->getSpeed(), 21); 59 | EXPECT_EQ(loco42->getDirection(), Direction::Forward); 60 | EXPECT_EQ(loco42->getFunctionStates(), 1); 61 | 62 | // Set a loco update for 120 in the stream: 63 | // - Speed byte = reverse, speed 11 64 | // - Functions 0 and 1 on 65 | _stream << ""; 66 | 67 | // Expect to receive the delegate call at the next check() 68 | EXPECT_CALL(_delegate, receivedLocoUpdate(loco120)).Times(Exactly(1)); 69 | // We should also expect an update with the details 70 | EXPECT_CALL(_delegate, receivedLocoBroadcast(120, 11, Direction::Reverse, 2)).Times(Exactly(1)); 71 | _dccexProtocol.check(); 72 | 73 | // Validate expected result 74 | EXPECT_EQ(loco120->getSpeed(), 11); 75 | EXPECT_EQ(loco120->getDirection(), Direction::Reverse); 76 | EXPECT_EQ(loco120->getFunctionStates(), 2); 77 | } 78 | 79 | /// @brief Check updates are received for non-roster locos 80 | TEST_F(LocoTests, receiveNonRosterLocoUpdate) { 81 | // Set a loco update for an unknown loco in the stream: 82 | // - Address 355 83 | // - Speed byte = forward, speed 31 84 | // - Functions off 85 | _stream << ""; 86 | 87 | // Expect to receive the delegate call at the next check() 88 | EXPECT_CALL(_delegate, receivedLocoBroadcast(355, 31, Direction::Forward, 0)).Times(Exactly(1)); 89 | // We should not receive a Loco object delegate call 90 | EXPECT_CALL(_delegate, receivedLocoUpdate(::testing::_)).Times(0); 91 | _dccexProtocol.check(); 92 | 93 | // Set a loco update for an unknown loco in the stream: 94 | // - Address 42 95 | // - Speed byte = reverse, speed 11 96 | // - Functions 0 and 1 on 97 | _stream << ""; 98 | 99 | // Expect to receive the delegate call at the next check() 100 | EXPECT_CALL(_delegate, receivedLocoBroadcast(42, 11, Direction::Reverse, 2)).Times(Exactly(1)); 101 | // We should not receive a Loco object delegate call 102 | EXPECT_CALL(_delegate, receivedLocoUpdate(::testing::_)).Times(0); 103 | _dccexProtocol.check(); 104 | } 105 | -------------------------------------------------------------------------------- /tests/loco/test_RosterParsing.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Vincent Hamp 9 | * Copyright © 2024 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #include "../setup/LocoTests.h" 30 | 31 | TEST_F(LocoTests, parseEmptyRoster) { 32 | EXPECT_FALSE(_dccexProtocol.receivedRoster()); 33 | _dccexProtocol.getLists(true, false, false, false); 34 | EXPECT_EQ(_stream.getBuffer(), "\r\n"); 35 | _stream.clearBuffer(); 36 | 37 | // Response 38 | _stream << ""; 39 | _dccexProtocol.check(); 40 | 41 | // Returns true since roster is empty 42 | EXPECT_TRUE(_dccexProtocol.receivedRoster()); 43 | } 44 | 45 | TEST_F(LocoTests, parseRosterWithThreeIDs) { 46 | EXPECT_FALSE(_dccexProtocol.receivedRoster()); 47 | _dccexProtocol.getLists(true, false, false, false); 48 | 49 | EXPECT_EQ(_stream.getBuffer(), "\r\n"); 50 | _stream.clearBuffer(); 51 | 52 | // Response 53 | _stream << ""; 54 | _dccexProtocol.check(); 55 | 56 | // Still false, wait for details 57 | EXPECT_FALSE(_dccexProtocol.receivedRoster()); 58 | 59 | // Detailed response for 42 60 | _stream << R"()"; 61 | _dccexProtocol.check(); 62 | 63 | // Detailed response for 9 64 | _stream << R"()"; 65 | _dccexProtocol.check(); 66 | 67 | // Detailed response for 120 68 | _stream << R"()"; 69 | EXPECT_CALL(_delegate, receivedRosterList()).Times(Exactly(1)); 70 | _dccexProtocol.check(); 71 | 72 | // Returns true since roster ist complete 73 | EXPECT_TRUE(_dccexProtocol.receivedRoster()); 74 | } 75 | -------------------------------------------------------------------------------- /tests/readWriteCVs/test_readCVs.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2025 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #include "../setup/CVTests.h" 29 | 30 | /// @brief Validate calling readLoco() generates the correct command 31 | TEST_F(CVTests, readLocoAddress) { 32 | // Reading should generate 33 | const char *expected = "\r\n"; 34 | 35 | // Call readLoco() 36 | _dccexProtocol.readLoco(); 37 | 38 | // Buffer should contain what we expect 39 | ASSERT_EQ(_stream.getBuffer(), expected); 40 | } 41 | 42 | /// @brief Validate calling readCV(cv) generates the correct command 43 | TEST_F(CVTests, readCV) { 44 | // Reading CV 19 should generate 45 | const char *expected = "\r\n"; 46 | 47 | // Call readLoco() 48 | _dccexProtocol.readCV(19); 49 | 50 | // Buffer should contain what we expect 51 | ASSERT_EQ(_stream.getBuffer(), expected); 52 | } 53 | 54 | /// @brief Validate calling validateCV(cv, value) generates the correct command 55 | TEST_F(CVTests, validateCV) { 56 | // Validating CV 1 with value 3 should generate 57 | const char *expected = "\r\n"; 58 | 59 | // Call validateCV() 60 | _dccexProtocol.validateCV(1, 3); 61 | 62 | // Buffer should contain what we expect 63 | ASSERT_EQ(_stream.getBuffer(), expected); 64 | } 65 | 66 | /// @brief Validate calling validateCVBit(cv, bit, value) generates the correct command 67 | TEST_F(CVTests, validateCVBit) { 68 | // Validating CV 1 bit 3 with value 1 should generate 69 | const char *expected = "\r\n"; 70 | 71 | // Call validateCVBit() 72 | _dccexProtocol.validateCVBit(1, 3, 1); 73 | 74 | // Buffer should contain what we expect 75 | ASSERT_EQ(_stream.getBuffer(), expected); 76 | } 77 | 78 | /// @brief Validate receiving calls receivedReadLoco() 79 | TEST_F(CVTests, readAddressCVResponse) { 80 | _stream << ""; 81 | EXPECT_CALL(_delegate, receivedReadLoco(1234)).Times(Exactly(1)); 82 | _dccexProtocol.check(); 83 | } 84 | 85 | /// @brief Validate receiving calls receivedValidateCV() 86 | TEST_F(CVTests, validateCVResponse) { 87 | _stream << ""; 88 | EXPECT_CALL(_delegate, receivedValidateCV(1, 3)).Times(Exactly(1)); 89 | _dccexProtocol.check(); 90 | } 91 | 92 | /// @brief Validate receiving calls receivedValidateCVBit() 93 | TEST_F(CVTests, validateCVBitResponse) { 94 | _stream << ""; 95 | EXPECT_CALL(_delegate, receivedValidateCVBit(1, 3, 1)).Times(Exactly(1)); 96 | _dccexProtocol.check(); 97 | } 98 | -------------------------------------------------------------------------------- /tests/readWriteCVs/test_writeCVs.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2025 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #include "../setup/CVTests.h" 29 | 30 | /// @brief Validate writeLocoAddress(address) generates the correct command 31 | TEST_F(CVTests, writeLocoAddress) { 32 | // Expect write address to generate 33 | const char *expected = "\r\n"; 34 | 35 | // Call writeLocoAddress() 36 | _dccexProtocol.writeLocoAddress(1234); 37 | 38 | // Check the buffer 39 | ASSERT_EQ(_stream.getBuffer(), expected); 40 | } 41 | 42 | /// @brief Validate writeCV(cv, value) generates the correct command 43 | TEST_F(CVTests, writeCV) { 44 | // Expect write address to generate 45 | const char *expected = "\r\n"; 46 | 47 | // Call writeCV() 48 | _dccexProtocol.writeCV(1, 3); 49 | 50 | // Check the buffer 51 | ASSERT_EQ(_stream.getBuffer(), expected); 52 | } 53 | 54 | /// @brief Validate writeCVBit(cv, bit, value) generates the correct command 55 | TEST_F(CVTests, writeCVBit) { 56 | // Expect write address to generate 57 | const char *expected = "\r\n"; 58 | 59 | // Call writeCV() 60 | _dccexProtocol.writeCVBit(19, 4, 1); 61 | 62 | // Check the buffer 63 | ASSERT_EQ(_stream.getBuffer(), expected); 64 | } 65 | 66 | /// @brief Validate writeCVOnMain(address, cv, value) generates the correct command 67 | TEST_F(CVTests, writeCVOnMain) { 68 | // Expect write CV on main to generate 69 | const char *expected = "\r\n"; 70 | 71 | // Call writeCVOnMain() 72 | _dccexProtocol.writeCVOnMain(3, 8, 4); 73 | 74 | // Check the buffer 75 | ASSERT_EQ(_stream.getBuffer(), expected); 76 | } 77 | 78 | /// @brief Validate writeCVBitOnMain(address, cv, bit, value) generates the correct command 79 | TEST_F(CVTests, writeCVBitOnMain) { 80 | // Expect write CV bit on main to generate 81 | const char *expected = "\r\n"; 82 | 83 | // Call writeCVBitOnMain() 84 | _dccexProtocol.writeCVBitOnMain(3, 19, 4, 1); 85 | 86 | // Check the buffer 87 | ASSERT_EQ(_stream.getBuffer(), expected); 88 | } 89 | 90 | /// @brief Validate a resonse to writing a loco address calls the correct delegate method 91 | TEST_F(CVTests, writeLocoAddressResponse) { 92 | _stream << ""; 93 | EXPECT_CALL(_delegate, receivedWriteLoco(1234)).Times(Exactly(1)); 94 | _dccexProtocol.check(); 95 | } 96 | 97 | /// @brief Validate a resonse to writing a CV calls the correct delegate method 98 | TEST_F(CVTests, writeCVResponse) { 99 | _stream << ""; 100 | EXPECT_CALL(_delegate, receivedWriteCV(1, 3)).Times(Exactly(1)); 101 | _dccexProtocol.check(); 102 | } 103 | -------------------------------------------------------------------------------- /tests/route/test_RouteParsing.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #include "../setup/RouteTests.h" 29 | 30 | TEST_F(RouteTests, parseEmptyRouteList) { 31 | // Received flag should be false to start 32 | EXPECT_FALSE(_dccexProtocol.receivedRouteList()); 33 | _dccexProtocol.getLists(false, false, true, false); 34 | EXPECT_EQ(_stream.getBuffer(), "\r\n"); 35 | _stream.clearBuffer(); 36 | 37 | // Empty route list response 38 | _stream << ""; 39 | _dccexProtocol.check(); 40 | 41 | // Should be true given route list is empty 42 | EXPECT_TRUE(_dccexProtocol.receivedRouteList()); 43 | } 44 | 45 | TEST_F(RouteTests, parseThreeRoutes) { 46 | // Received flag should be false to start 47 | EXPECT_FALSE(_dccexProtocol.receivedRouteList()); 48 | _dccexProtocol.getLists(false, false, true, false); 49 | EXPECT_EQ(_stream.getBuffer(), "\r\n"); 50 | _stream.clearBuffer(); 51 | 52 | // Three route response 53 | _stream << ""; 54 | _dccexProtocol.check(); 55 | 56 | // Flag should still be false while awaiting details 57 | EXPECT_FALSE(_dccexProtocol.receivedRouteList()); 58 | 59 | // First route response - Route with description 60 | _stream << R"()"; 61 | _dccexProtocol.check(); 62 | 63 | // Second route response - Automation with description 64 | _stream << R"()"; 65 | _dccexProtocol.check(); 66 | 67 | // Third route response - Route with no description 68 | _stream << R"()"; 69 | // Delegate should call one here 70 | EXPECT_CALL(_delegate, receivedRouteList()).Times(Exactly(1)); 71 | _dccexProtocol.check(); 72 | 73 | // Flag should now be true when all routes received 74 | EXPECT_TRUE(_dccexProtocol.receivedRouteList()); 75 | } 76 | -------------------------------------------------------------------------------- /tests/route/test_Routes.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #include "../setup/RouteTests.h" 29 | 30 | TEST_F(RouteTests, createSingleRoute) { 31 | // Create a route 200 as a route type 32 | Route *route200 = new Route(200); 33 | route200->setName("Route 200"); 34 | route200->setType(RouteType::RouteTypeRoute); 35 | 36 | // Validate details are correct 37 | EXPECT_EQ(route200->getId(), 200); 38 | EXPECT_STREQ(route200->getName(), "Route 200"); 39 | EXPECT_EQ(route200->getType(), RouteType::RouteTypeRoute); 40 | 41 | // Validate it is the first in the list with no next 42 | EXPECT_EQ(Route::getFirst(), route200); 43 | EXPECT_EQ(route200->getNext(), nullptr); 44 | } 45 | 46 | TEST_F(RouteTests, createThreeRoutes) { 47 | // Create three routes, route, automation, and route with no name 48 | Route *route200 = new Route(200); 49 | route200->setName("Route 200"); 50 | route200->setType(RouteType::RouteTypeRoute); 51 | Route *route300 = new Route(300); 52 | route300->setName("Automation 300"); 53 | route300->setType(RouteType::RouteTypeAutomation); 54 | Route *route400 = new Route(400); 55 | route400->setName(""); 56 | route400->setType(RouteType::RouteTypeRoute); 57 | 58 | // Validate routes are in the route list 59 | EXPECT_EQ(_dccexProtocol.routes->getById(200), route200); 60 | EXPECT_EQ(_dccexProtocol.routes->getById(300), route300); 61 | EXPECT_EQ(_dccexProtocol.routes->getById(400), route400); 62 | 63 | // Validate route details 64 | EXPECT_EQ(route200->getId(), 200); 65 | EXPECT_STREQ(route200->getName(), "Route 200"); 66 | EXPECT_EQ(route200->getType(), RouteType::RouteTypeRoute); 67 | 68 | // Validate route details 69 | EXPECT_EQ(route300->getId(), 300); 70 | EXPECT_STREQ(route300->getName(), "Automation 300"); 71 | EXPECT_EQ(route300->getType(), RouteType::RouteTypeAutomation); 72 | 73 | // Validate route details 74 | EXPECT_EQ(route400->getId(), 400); 75 | EXPECT_STREQ(route400->getName(), ""); 76 | EXPECT_EQ(route400->getType(), RouteType::RouteTypeRoute); 77 | } 78 | 79 | /// @brief Validate that sending handOffLoco(int locoAddress, int automationId) sends 80 | TEST_F(RouteTests, automationHandOff) { 81 | // Start automation ID 100 using loco address 1234 82 | const char *expected = "\r\n"; 83 | 84 | // An automation 100 must exist 85 | Route *automation100 = new Route(100); 86 | automation100->setType(RouteType::RouteTypeAutomation); 87 | 88 | // Call power on 89 | _dccexProtocol.handOffLoco(1234, 100); 90 | 91 | // Ensure the buffer has what's expected 92 | ASSERT_EQ(_stream.getBuffer(), expected); 93 | } 94 | -------------------------------------------------------------------------------- /tests/setup/CVTests.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2025 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #ifndef CVTESTS_H 29 | #define CVTESTS_H 30 | 31 | #include "TestHarnessBase.hpp" 32 | 33 | /// @brief Test harness for reading/writing CVs 34 | class CVTests : public TestHarnessBase {}; 35 | 36 | #endif // CVTESTS_H -------------------------------------------------------------------------------- /tests/setup/DCCEXProtocolDelegateMock.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class DCCEXProtocolDelegateMock : public DCCEXProtocolDelegate { 5 | public: 6 | // Notify when the server version has been received 7 | MOCK_METHOD(void, receivedServerVersion, (int, int, int), (override)); 8 | 9 | // Notify when a broadcast message has been received 10 | MOCK_METHOD(void, receivedMessage, (char *), (override)); 11 | 12 | // Notify when the roster list is received 13 | MOCK_METHOD(void, receivedRosterList, (), (override)); 14 | 15 | // Notify when the turnout list is received 16 | MOCK_METHOD(void, receivedTurnoutList, (), (override)); 17 | 18 | // Notify when the route list is received 19 | MOCK_METHOD(void, receivedRouteList, (), (override)); 20 | 21 | // Notify when the turntable list is received 22 | MOCK_METHOD(void, receivedTurntableList, (), (override)); 23 | 24 | // Notify when an update to a Loco object is received 25 | MOCK_METHOD(void, receivedLocoUpdate, (Loco *), (override)); 26 | 27 | // Notify when a Loco broadcast is received 28 | MOCK_METHOD(void, receivedLocoBroadcast, (int address, int speed, Direction direction, int functionMap), (override)); 29 | 30 | // Notify when a track power state change is received 31 | MOCK_METHOD(void, receivedTrackPower, (TrackPower), (override)); 32 | 33 | // Notify when a track type change is received 34 | MOCK_METHOD(void, receivedTrackType, (char, TrackManagerMode, int), (override)); 35 | 36 | // Notify when a turnout state change is received 37 | MOCK_METHOD(void, receivedTurnoutAction, (int, bool), (override)); 38 | 39 | // Notify when a turntable index change is received 40 | MOCK_METHOD(void, receivedTurntableAction, (int, int, bool), (override)); 41 | 42 | // Notify when a loco address is read from the programming track 43 | MOCK_METHOD(void, receivedReadLoco, (int), (override)); 44 | 45 | // Notify when a CV is read from the programming track 46 | MOCK_METHOD(void, receivedValidateCV, (int, int), (override)); 47 | 48 | // Notify when a CV bit is validated on the programming track 49 | MOCK_METHOD(void, receivedValidateCVBit, (int, int, int), (override)); 50 | 51 | // Notify when a CV is written on the programming track 52 | MOCK_METHOD(void, receivedWriteCV, (int, int), (override)); 53 | 54 | // Notify when a loco address is written on the programming track 55 | MOCK_METHOD(void, receivedWriteLoco, (int), (override)); 56 | }; -------------------------------------------------------------------------------- /tests/setup/DCCEXProtocolTests.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #ifndef DCCEXPROTOCOLTESTS_H 29 | #define DCCEXPROTOCOLTESTS_H 30 | 31 | #include "TestHarnessBase.hpp" 32 | 33 | /// @brief Test harness for DCCEX protocol parsing tests 34 | class DCCEXProtocolTests : public TestHarnessBase {}; 35 | 36 | #endif // DCCEXPROTOCOLTESTS_H 37 | -------------------------------------------------------------------------------- /tests/setup/LocoTests.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #ifndef LOCOTESTS_H 29 | #define LOCOTESTS_H 30 | 31 | #include "TestHarnessBase.hpp" 32 | 33 | /// @brief Test harness for Loco and associated classes 34 | class LocoTests : public TestHarnessBase {}; 35 | 36 | #endif // LOCOTESTS_H -------------------------------------------------------------------------------- /tests/setup/MockSetup.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Vincent Hamp 9 | * Copyright © 2024 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #include "MockSetup.h" 30 | 31 | namespace { 32 | 33 | std::string stream2string(StreamMock stream) { 34 | std::string retval; 35 | while (stream.available()) 36 | retval.push_back(static_cast(stream.read())); 37 | return retval; 38 | } 39 | 40 | } // namespace 41 | 42 | bool operator==(StreamMock lhs, StreamMock rhs) { return stream2string(lhs) == stream2string(rhs); } 43 | 44 | bool operator==(StreamMock lhs, std::string rhs) { return stream2string(lhs) == rhs; } 45 | 46 | size_t ExtendedStreamMock::write(uint8_t ch) { 47 | _buffer.push_back(static_cast(ch)); 48 | return 1; 49 | } 50 | 51 | std::string const &ExtendedStreamMock::getBuffer() const { return _buffer; } 52 | 53 | void ExtendedStreamMock::clearBuffer() { _buffer.clear(); } 54 | -------------------------------------------------------------------------------- /tests/setup/MockSetup.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Vincent Hamp 9 | * Copyright © 2024 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #ifndef MOCKSETUP_H 30 | #define MOCKSETUP_H 31 | 32 | #include 33 | #include 34 | 35 | // Make StreamMock comparable 36 | bool operator==(StreamMock lhs, StreamMock rhs); 37 | bool operator==(StreamMock lhs, std::string rhs); 38 | 39 | class ExtendedStreamMock : public StreamMock { 40 | public: 41 | /// @brief Override write method for mocking and testing buffer contents 42 | /// @param ch Char to write 43 | size_t write(uint8_t ch) override; 44 | 45 | /// @brief Get the buffer 46 | /// @return Buffer contents 47 | const std::string &getBuffer() const; 48 | 49 | /// @brief Clear the buffer 50 | void clearBuffer(); 51 | 52 | private: 53 | std::string _buffer; 54 | }; 55 | 56 | #endif // MOCKSETUP_H 57 | -------------------------------------------------------------------------------- /tests/setup/RouteTests.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #ifndef ROUTETESTS_H 29 | #define ROUTETESTS_H 30 | 31 | #include "TestHarnessBase.hpp" 32 | 33 | /// @brief Test harness for the Route class 34 | class RouteTests : public TestHarnessBase {}; 35 | 36 | #endif // ROUTETESTS_H -------------------------------------------------------------------------------- /tests/setup/TestHarnessBase.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Vincent Hamp 9 | * Copyright © 2024 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #include "TestHarnessBase.hpp" 30 | 31 | TestHarnessBase::TestHarnessBase() {} 32 | 33 | TestHarnessBase::~TestHarnessBase() {} 34 | 35 | void TestHarnessBase::SetUp() { 36 | _dccexProtocol.setDelegate(&_delegate); 37 | _dccexProtocol.setLogStream(&_console); 38 | _dccexProtocol.connect(&_stream); 39 | _dccexProtocol.clearRoster(); 40 | } 41 | 42 | void TestHarnessBase::TearDown() { 43 | _dccexProtocol.clearAllLists(); 44 | } 45 | -------------------------------------------------------------------------------- /tests/setup/TestHarnessBase.hpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Vincent Hamp 9 | * Copyright © 2024 Peter Cole 10 | * 11 | * This work is licensed under the Creative Commons Attribution-ShareAlike 12 | * 4.0 International License. To view a copy of this license, visit 13 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 14 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 15 | * 16 | * Attribution — You must give appropriate credit, provide a link to the 17 | * license, and indicate if changes were made. You may do so in any 18 | * reasonable manner, but not in any way that suggests the licensor 19 | * endorses you or your use. 20 | * 21 | * ShareAlike — If you remix, transform, or build upon the material, you 22 | * must distribute your contributions under the same license as the 23 | * original. 24 | * 25 | * All other rights reserved. 26 | * 27 | */ 28 | 29 | #ifndef TESTHARNESSBASE_HPP 30 | #define TESTHARNESSBASE_HPP 31 | 32 | #include "DCCEXProtocolDelegateMock.hpp" 33 | #include "MockSetup.h" 34 | #include 35 | 36 | using namespace testing; 37 | 38 | /// @brief Test fixture to setup and tear down tests 39 | class TestHarnessBase : public Test { 40 | public: 41 | TestHarnessBase(); 42 | virtual ~TestHarnessBase(); 43 | 44 | protected: 45 | void SetUp() override; 46 | void TearDown() override; 47 | 48 | DCCEXProtocol _dccexProtocol; 49 | DCCEXProtocolDelegateMock _delegate; 50 | StreamMock _console; 51 | ExtendedStreamMock _stream; 52 | // StreamMock _stream; 53 | }; 54 | 55 | #endif // TESTHARNESSBASE_HPP 56 | -------------------------------------------------------------------------------- /tests/setup/TurnoutTests.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #ifndef TURNOUTTESTS_H 29 | #define TUNROUTTESTS_H 30 | 31 | #include "TestHarnessBase.hpp" 32 | 33 | /// @brief Test harness for the Turnout class 34 | class TurnoutTests : public TestHarnessBase {}; 35 | 36 | #endif // TURNOUTTESTS_H -------------------------------------------------------------------------------- /tests/setup/TurntableTests.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #ifndef TURNTABLETESTS_H 29 | #define TURNTABLETESTS_H 30 | 31 | #include "TestHarnessBase.hpp" 32 | 33 | /// @brief Test harness for the Turntable class 34 | class TurntableTests : public TestHarnessBase {}; 35 | 36 | #endif // TURNTABLETESTS_H -------------------------------------------------------------------------------- /tests/turnout/test_TurnoutParsing.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #include "../setup/TurnoutTests.h" 29 | 30 | TEST_F(TurnoutTests, parseEmptyTurnoutList) { 31 | // Received flag should be false to start 32 | EXPECT_FALSE(_dccexProtocol.receivedTurnoutList()); 33 | _dccexProtocol.getLists(false, true, false, false); 34 | EXPECT_EQ(_stream.getBuffer(), "\r\n"); 35 | _stream.clearBuffer(); 36 | 37 | // Empty turnout list response 38 | _stream << ""; 39 | _dccexProtocol.check(); 40 | 41 | // Should be true given turnout list is empty 42 | EXPECT_TRUE(_dccexProtocol.receivedTurnoutList()); 43 | } 44 | 45 | TEST_F(TurnoutTests, parseThreeTurnouts) { 46 | // Received flag should be false to start 47 | EXPECT_FALSE(_dccexProtocol.receivedTurnoutList()); 48 | _dccexProtocol.getLists(false, true, false, false); 49 | EXPECT_EQ(_stream.getBuffer(), "\r\n"); 50 | _stream.clearBuffer(); 51 | 52 | // Empty turnout list response 53 | _stream << ""; 54 | _dccexProtocol.check(); 55 | 56 | // Received flag should still be false 57 | EXPECT_FALSE(_dccexProtocol.receivedTurnoutList()); 58 | 59 | // First turnout response - closed and description 60 | _stream << R"()"; 61 | _dccexProtocol.check(); 62 | 63 | // Second turnout response - thrown and description 64 | _stream << R"()"; 65 | _dccexProtocol.check(); 66 | 67 | // Third turnout response - closed and no description 68 | _stream << R"()"; 69 | // Delegate should call once here 70 | EXPECT_CALL(_delegate, receivedTurnoutList()).Times(Exactly(1)); 71 | _dccexProtocol.check(); 72 | 73 | // Should be true given turnout list is empty 74 | EXPECT_TRUE(_dccexProtocol.receivedTurnoutList()); 75 | } 76 | -------------------------------------------------------------------------------- /tests/turnout/test_Turnouts.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #include "../setup/TurnoutTests.h" 29 | 30 | TEST_F(TurnoutTests, createSingleTurnout) { 31 | // Create a turnout 100 32 | Turnout *turnout100 = new Turnout(100, false); 33 | turnout100->setName("Turnout 100"); 34 | 35 | // Validate turnout details 36 | EXPECT_EQ(turnout100->getId(), 100); 37 | EXPECT_STREQ(turnout100->getName(), "Turnout 100"); 38 | EXPECT_FALSE(turnout100->getThrown()); 39 | 40 | // Validate it's in the list by ID 41 | EXPECT_EQ(_dccexProtocol.turnouts->getById(100), turnout100); 42 | } 43 | 44 | TEST_F(TurnoutTests, createTurnoutList) { 45 | // Create three turnouts 46 | Turnout *turnout100 = new Turnout(100, false); 47 | turnout100->setName("Turnout 100"); 48 | Turnout *turnout101 = new Turnout(101, true); 49 | turnout101->setName("Turnout 101"); 50 | Turnout *turnout102 = new Turnout(102, false); 51 | turnout102->setName(""); 52 | 53 | // Validate turnouts are in the list 54 | EXPECT_EQ(_dccexProtocol.turnouts->getById(100), turnout100); 55 | EXPECT_EQ(_dccexProtocol.turnouts->getById(101), turnout101); 56 | EXPECT_EQ(_dccexProtocol.turnouts->getById(102), turnout102); 57 | 58 | // Validate turnout details 59 | EXPECT_EQ(turnout100->getId(), 100); 60 | EXPECT_STREQ(turnout100->getName(), "Turnout 100"); 61 | EXPECT_FALSE(turnout100->getThrown()); 62 | 63 | // Validate turnout details 64 | EXPECT_EQ(turnout101->getId(), 101); 65 | EXPECT_STREQ(turnout101->getName(), "Turnout 101"); 66 | EXPECT_TRUE(turnout101->getThrown()); 67 | 68 | // Validate turnout details 69 | EXPECT_EQ(turnout102->getId(), 102); 70 | EXPECT_STREQ(turnout102->getName(), ""); 71 | EXPECT_FALSE(turnout102->getThrown()); 72 | } 73 | 74 | TEST_F(TurnoutTests, operateTurnout) { 75 | // Create a turnout 100 76 | Turnout *turnout100 = new Turnout(100, false); 77 | turnout100->setName("Turnout 100"); 78 | 79 | // Close it and validate 80 | turnout100->setThrown(false); 81 | EXPECT_FALSE(turnout100->getThrown()); 82 | 83 | // Throw it and validate 84 | turnout100->setThrown(true); 85 | EXPECT_TRUE(turnout100->getThrown()); 86 | 87 | // Close it and validate 88 | turnout100->setThrown(false); 89 | EXPECT_FALSE(turnout100->getThrown()); 90 | } 91 | -------------------------------------------------------------------------------- /tests/turntable/test_TurntableParsing.cpp: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- 2 | * 3 | * DCCEXProtocol 4 | * 5 | * This package implements a DCCEX native protocol connection, 6 | * allow a device to communicate with a DCC-EX EX-CommandStation. 7 | * 8 | * Copyright © 2024 Peter Cole 9 | * 10 | * This work is licensed under the Creative Commons Attribution-ShareAlike 11 | * 4.0 International License. To view a copy of this license, visit 12 | * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to 13 | * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 14 | * 15 | * Attribution — You must give appropriate credit, provide a link to the 16 | * license, and indicate if changes were made. You may do so in any 17 | * reasonable manner, but not in any way that suggests the licensor 18 | * endorses you or your use. 19 | * 20 | * ShareAlike — If you remix, transform, or build upon the material, you 21 | * must distribute your contributions under the same license as the 22 | * original. 23 | * 24 | * All other rights reserved. 25 | * 26 | */ 27 | 28 | #include "../setup/TurntableTests.h" 29 | 30 | TEST_F(TurntableTests, parseEmptyTurntableList) { 31 | // Received flag should be false to start 32 | EXPECT_FALSE(_dccexProtocol.receivedTurntableList()); 33 | _dccexProtocol.getLists(false, false, false, true); 34 | EXPECT_EQ(_stream.getBuffer(), "\r\n"); 35 | _stream.clearBuffer(); 36 | 37 | // Empty turntable list response 38 | _stream << ""; 39 | _dccexProtocol.check(); 40 | 41 | // Should be true given turntable list is empty 42 | EXPECT_TRUE(_dccexProtocol.receivedTurntableList()); 43 | } 44 | 45 | TEST_F(TurntableTests, parseTwoTurntables) { 46 | // Received flag should be false to start 47 | EXPECT_FALSE(_dccexProtocol.receivedTurntableList()); 48 | _dccexProtocol.getLists(false, false, false, true); 49 | EXPECT_EQ(_stream.getBuffer(), "\r\n"); 50 | _stream.clearBuffer(); 51 | 52 | // Two turntables in response 53 | _stream << ""; 54 | _dccexProtocol.check(); 55 | 56 | // Received should still be false while waiting for details 57 | EXPECT_FALSE(_dccexProtocol.receivedTurntableList()); 58 | 59 | // First turntable response - EX-Turntable at ID 1 with 5 indexes, currently at home position 60 | _stream << R"()"; 61 | 62 | // Second turntable response - DCC Turntable at ID 1 with 6 indexes, currently at position 3 63 | _stream << R"()"; 64 | 65 | // ID 1 Position responses 66 | _stream << R"()"; 67 | _dccexProtocol.check(); 68 | _stream << R"()"; 69 | _dccexProtocol.check(); 70 | _stream << R"()"; 71 | _dccexProtocol.check(); 72 | _stream << R"()"; 73 | _dccexProtocol.check(); 74 | _stream << R"()"; 75 | _dccexProtocol.check(); 76 | 77 | // ID 2 Position responses 78 | _stream << R"()"; 79 | _dccexProtocol.check(); 80 | _stream << R"()"; 81 | _dccexProtocol.check(); 82 | _stream << R"()"; 83 | _dccexProtocol.check(); 84 | _stream << R"()"; 85 | _dccexProtocol.check(); 86 | _stream << R"()"; 87 | _dccexProtocol.check(); 88 | _stream << R"()"; 89 | 90 | // Delegate should call back once here 91 | EXPECT_CALL(_delegate, receivedTurntableList()).Times(Exactly(1)); 92 | _dccexProtocol.check(); 93 | 94 | // Now the flag should be true 95 | EXPECT_TRUE(_dccexProtocol.receivedTurntableList()); 96 | } 97 | --------------------------------------------------------------------------------