├── .clang-format ├── .clang-tidy ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── build-and-test.yml │ ├── codeql-buildscript.sh │ ├── codeql.yml │ ├── fail_on_error.py │ └── release.yml ├── .gitignore ├── .lvimrc ├── .readthedocs.yaml ├── .vscode ├── c_cpp_properties.json ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── AUTHORS ├── CHANGELOG.md ├── CITATION.cff ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE ├── README.md ├── cmake ├── i686-w64-mingw32-gcc.cmake └── x86_64-w64-mingw32-gcc.cmake ├── dev └── main.cpp ├── docs ├── Makefile ├── api-reference │ ├── gps.rst │ ├── gps_opt.rst │ └── index.rst ├── authors │ └── index.rst ├── changelog │ └── index.rst ├── conf.py ├── doxyfile.doxy ├── examples │ └── index.rst ├── get-started │ └── index.rst ├── index.rst ├── make.bat ├── requirements.txt ├── static │ ├── css │ │ ├── common.css │ │ └── custom.css │ ├── dark-light │ │ ├── checked.svg │ │ ├── common-dark-light.css │ │ ├── dark-mode-toggle.mjs │ │ ├── dark.css │ │ ├── light.css │ │ ├── moon.png │ │ ├── moon.svg │ │ ├── sun.png │ │ ├── sun.svg │ │ └── unchecked.svg │ └── images │ │ ├── logo.drawio │ │ ├── logo.svg │ │ ├── logo_tm.png │ │ └── logo_tm_full.png └── user-manual │ ├── float-double.rst │ ├── how-it-works.rst │ ├── index.rst │ ├── nmea-update-packet.rst │ ├── tests.rst │ └── thread-safety.rst ├── examples ├── CMakeLists.txt ├── Makefile ├── example.c ├── example_buff.c ├── example_dist_bear.c └── example_stat.c ├── library.json ├── lwgps ├── CMakeLists.txt ├── library.cmake └── src │ ├── include │ └── lwgps │ │ ├── lwgps.h │ │ ├── lwgps.hpp │ │ ├── lwgps_opt.h │ │ └── lwgps_opts_template.h │ └── lwgps │ └── lwgps.c └── tests ├── CMakeLists.txt ├── test.h ├── test.py ├── test_main.cpp ├── test_parse_ext_time ├── cmake.cmake ├── lwgps_opts.h └── test_parse_ext_time.c └── test_parse_standard ├── cmake.cmake ├── lwgps_opts.h └── test_parse_standard.c /.clang-format: -------------------------------------------------------------------------------- 1 | # Language part removed. With clang-format >=20.1, the C and Cpp are separately handled, 2 | # so either there is no language at all, or we need to create 2 formats for C and Cpp, separately 3 | 4 | --- 5 | # Language: Cpp 6 | # BasedOnStyle: LLVM 7 | AccessModifierOffset: -2 8 | AlignAfterOpenBracket: Align 9 | AlignArrayOfStructures: None 10 | AlignConsecutiveMacros: 11 | Enabled: true 12 | AcrossEmptyLines: true 13 | AcrossComments: true 14 | AlignConsecutiveAssignments: None 15 | AlignConsecutiveBitFields: 16 | Enabled: true 17 | AcrossEmptyLines: true 18 | AcrossComments: true 19 | AlignConsecutiveDeclarations: None 20 | AlignEscapedNewlines: Right 21 | AlignOperands: Align 22 | SortIncludes: true 23 | InsertBraces: true # Control statements must have curly brackets 24 | AlignTrailingComments: true 25 | AllowAllArgumentsOnNextLine: true 26 | AllowAllParametersOfDeclarationOnNextLine: true 27 | AllowShortEnumsOnASingleLine: true 28 | AllowShortBlocksOnASingleLine: Empty 29 | AllowShortCaseLabelsOnASingleLine: true 30 | AllowShortFunctionsOnASingleLine: All 31 | AllowShortLambdasOnASingleLine: All 32 | AllowShortIfStatementsOnASingleLine: Never 33 | AllowShortLoopsOnASingleLine: false 34 | AlwaysBreakAfterDefinitionReturnType: None 35 | AlwaysBreakAfterReturnType: AllDefinitions 36 | AlwaysBreakBeforeMultilineStrings: false 37 | AlwaysBreakTemplateDeclarations: Yes 38 | AttributeMacros: 39 | - __capability 40 | BinPackArguments: true 41 | BinPackParameters: true 42 | BraceWrapping: 43 | AfterCaseLabel: false 44 | AfterClass: false 45 | AfterControlStatement: Never 46 | AfterEnum: false 47 | AfterFunction: false 48 | AfterNamespace: false 49 | AfterObjCDeclaration: false 50 | AfterStruct: false 51 | AfterUnion: false 52 | AfterExternBlock: false 53 | BeforeCatch: false 54 | BeforeElse: false 55 | BeforeLambdaBody: false 56 | BeforeWhile: false 57 | IndentBraces: false 58 | SplitEmptyFunction: true 59 | SplitEmptyRecord: true 60 | SplitEmptyNamespace: true 61 | BreakBeforeBinaryOperators: NonAssignment 62 | BreakBeforeConceptDeclarations: true 63 | BreakBeforeBraces: Attach 64 | BreakBeforeInheritanceComma: false 65 | BreakInheritanceList: BeforeColon 66 | BreakBeforeTernaryOperators: true 67 | BreakConstructorInitializersBeforeComma: false 68 | BreakConstructorInitializers: BeforeColon 69 | BreakAfterJavaFieldAnnotations: false 70 | BreakStringLiterals: true 71 | ColumnLimit: 120 72 | CommentPragmas: "^ IWYU pragma:" 73 | QualifierAlignment: Leave 74 | CompactNamespaces: false 75 | ConstructorInitializerIndentWidth: 4 76 | ContinuationIndentWidth: 4 77 | Cpp11BracedListStyle: true 78 | DeriveLineEnding: true 79 | DerivePointerAlignment: false 80 | DisableFormat: false 81 | EmptyLineAfterAccessModifier: Never 82 | EmptyLineBeforeAccessModifier: LogicalBlock 83 | ExperimentalAutoDetectBinPacking: false 84 | PackConstructorInitializers: BinPack 85 | BasedOnStyle: "" 86 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 87 | AllowAllConstructorInitializersOnNextLine: true 88 | FixNamespaceComments: true 89 | ForEachMacros: 90 | - foreach 91 | - Q_FOREACH 92 | - BOOST_FOREACH 93 | IfMacros: 94 | - KJ_IF_MAYBE 95 | IncludeBlocks: Preserve 96 | IncludeCategories: 97 | - Regex: "^<(.*)>" 98 | Priority: 0 99 | - Regex: '^"(.*)"' 100 | Priority: 1 101 | - Regex: "(.*)" 102 | Priority: 2 103 | IncludeIsMainRegex: "(Test)?$" 104 | IncludeIsMainSourceRegex: "" 105 | IndentAccessModifiers: false 106 | IndentCaseLabels: true 107 | IndentCaseBlocks: false 108 | IndentGotoLabels: true 109 | IndentPPDirectives: None 110 | IndentExternBlock: AfterExternBlock 111 | IndentRequires: true 112 | IndentWidth: 4 113 | IndentWrappedFunctionNames: false 114 | InsertTrailingCommas: None 115 | JavaScriptQuotes: Leave 116 | JavaScriptWrapImports: true 117 | KeepEmptyLinesAtTheStartOfBlocks: true 118 | LambdaBodyIndentation: Signature 119 | MacroBlockBegin: "" 120 | MacroBlockEnd: "" 121 | MaxEmptyLinesToKeep: 1 122 | NamespaceIndentation: None 123 | ObjCBinPackProtocolList: Auto 124 | ObjCBlockIndentWidth: 2 125 | ObjCBreakBeforeNestedBlockParam: true 126 | ObjCSpaceAfterProperty: false 127 | ObjCSpaceBeforeProtocolList: true 128 | PenaltyBreakAssignment: 2 129 | PenaltyBreakBeforeFirstCallParameter: 19 130 | PenaltyBreakComment: 300 131 | PenaltyBreakFirstLessLess: 120 132 | PenaltyBreakOpenParenthesis: 0 133 | PenaltyBreakString: 1000 134 | PenaltyBreakTemplateDeclaration: 10 135 | PenaltyExcessCharacter: 1000000 136 | PenaltyReturnTypeOnItsOwnLine: 60 137 | PenaltyIndentedWhitespace: 0 138 | PointerAlignment: Left 139 | PPIndentWidth: -1 140 | ReferenceAlignment: Pointer 141 | ReflowComments: false 142 | RemoveBracesLLVM: false 143 | SeparateDefinitionBlocks: Always 144 | ShortNamespaceLines: 1 145 | SortJavaStaticImport: Before 146 | SortUsingDeclarations: true 147 | SpaceAfterCStyleCast: false 148 | SpaceAfterLogicalNot: false 149 | SpaceAfterTemplateKeyword: true 150 | SpaceBeforeAssignmentOperators: true 151 | SpaceBeforeCaseColon: false 152 | SpaceBeforeParens: ControlStatements 153 | SpaceBeforeParensOptions: 154 | AfterControlStatements: true 155 | AfterForeachMacros: true 156 | AfterFunctionDefinitionName: false 157 | AfterFunctionDeclarationName: false 158 | AfterIfMacros: true 159 | AfterOverloadedOperator: false 160 | BeforeNonEmptyParentheses: false 161 | SpaceAroundPointerQualifiers: Default 162 | SpaceBeforeRangeBasedForLoopColon: true 163 | SpaceInEmptyBlock: false 164 | SpaceInEmptyParentheses: false 165 | SpacesBeforeTrailingComments: 1 166 | SpacesInAngles: Never 167 | SpacesInConditionalStatement: false 168 | SpacesInContainerLiterals: true 169 | SpacesInCStyleCastParentheses: false 170 | SpacesInLineCommentPrefix: 171 | Minimum: 1 172 | Maximum: -1 173 | SpacesInParentheses: false 174 | SpacesInSquareBrackets: false 175 | SpaceBeforeSquareBrackets: false 176 | BitFieldColonSpacing: Both 177 | Standard: Latest 178 | StatementAttributeLikeMacros: 179 | - Q_EMIT 180 | StatementMacros: 181 | - Q_UNUSED 182 | - QT_REQUIRE_VERSION 183 | TabWidth: 8 184 | UseCRLF: false 185 | UseTab: Never 186 | WhitespaceSensitiveMacros: 187 | - STRINGIZE 188 | - PP_STRINGIZE 189 | - BOOST_PP_STRINGIZE 190 | - NS_SWIFT_NAME 191 | - CF_SWIFT_NAME 192 | SpaceBeforeCpp11BracedList: false 193 | SpaceBeforeCtorInitializerColon: true 194 | SpaceBeforeInheritanceColon: true 195 | --- 196 | 197 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: "*, 3 | -abseil-*, 4 | -altera-*, 5 | -android-*, 6 | -fuchsia-*, 7 | -google-*, 8 | -llvm*, 9 | -modernize-use-trailing-return-type, 10 | -zircon-*, 11 | -readability-else-after-return, 12 | -readability-static-accessed-through-instance, 13 | -readability-avoid-const-params-in-decls, 14 | -cppcoreguidelines-non-private-member-variables-in-classes, 15 | -misc-non-private-member-variables-in-classes, 16 | " 17 | WarningsAsErrors: '' 18 | HeaderFilterRegex: '' 19 | FormatStyle: none -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: ['paypal.me/tilz0R'] 4 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Windows CMake Build & Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | - test 8 | pull_request: 9 | branches: 10 | - develop 11 | 12 | jobs: 13 | build: 14 | runs-on: windows-latest 15 | 16 | steps: 17 | - name: Checkout Repository 18 | uses: actions/checkout@v4 19 | 20 | - name: Install MinGW 21 | run: | 22 | choco install mingw --version=12.2.0 -y 23 | echo "C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64\bin" >> $GITHUB_PATH 24 | gcc --version 25 | 26 | - name: Run Tests 27 | working-directory: tests 28 | run: | 29 | python test.py 30 | -------------------------------------------------------------------------------- /.github/workflows/codeql-buildscript.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd examples 4 | mkdir build && cd build && cmake .. 5 | make 6 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main", "master" ] 17 | schedule: 18 | - cron: '0 0 * * *' 19 | pull_request: 20 | branches: '*' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze 25 | # Runner size impacts CodeQL analysis time. To learn more, please see: 26 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 27 | # - https://gh.io/supported-runners-and-hardware-resources 28 | # - https://gh.io/using-larger-runners 29 | # Consider using larger runners for possible analysis time improvements. 30 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-20.04' }} 31 | timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} 32 | permissions: 33 | actions: read 34 | contents: read 35 | security-events: write 36 | 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | language: [ 'cpp' ] 41 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ] 42 | # Use only 'java' to analyze code written in Java, Kotlin or both 43 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both 44 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 45 | 46 | steps: 47 | - name: Checkout repository 48 | uses: actions/checkout@v3 49 | with: 50 | submodules: recursive 51 | 52 | # Initializes the CodeQL tools for scanning. 53 | - name: Initialize CodeQL 54 | uses: github/codeql-action/init@v2 55 | with: 56 | languages: ${{ matrix.language }} 57 | # If you wish to specify custom queries, you can do so here or in a config file. 58 | # By default, queries listed here will override any specified in a config file. 59 | # Prefix the list here with "+" to use these queries and those in the config file. 60 | 61 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 62 | # queries: security-extended,security-and-quality 63 | queries: security-and-quality 64 | 65 | 66 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). 67 | # If this step fails, then you should remove it and run the build manually (see below) 68 | #- name: Autobuild 69 | # uses: github/codeql-action/autobuild@v2 70 | 71 | # ℹ️ Command-line programs to run using the OS shell. 72 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 73 | 74 | # If the Autobuild fails above, remove it and uncomment the following three lines. 75 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 76 | 77 | - run: | 78 | ./.github/workflows/codeql-buildscript.sh 79 | 80 | - name: Perform CodeQL Analysis 81 | uses: github/codeql-action/analyze@v2 82 | with: 83 | category: "/language:${{matrix.language}}" 84 | upload: false 85 | id: step1 86 | 87 | # Filter out rules with low severity or high false positve rate 88 | # Also filter out warnings in third-party code 89 | - name: Filter out unwanted errors and warnings 90 | uses: advanced-security/filter-sarif@v1 91 | with: 92 | patterns: | 93 | -**:cpp/path-injection 94 | -**:cpp/world-writable-file-creation 95 | -**:cpp/poorly-documented-function 96 | -**:cpp/potentially-dangerous-function 97 | -**:cpp/use-of-goto 98 | -**:cpp/integer-multiplication-cast-to-long 99 | -**:cpp/comparison-with-wider-type 100 | -**:cpp/leap-year/* 101 | -**:cpp/ambiguously-signed-bit-field 102 | -**:cpp/suspicious-pointer-scaling 103 | -**:cpp/suspicious-pointer-scaling-void 104 | -**:cpp/unsigned-comparison-zero 105 | -**/cmake*/Modules/** 106 | input: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif 107 | output: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif 108 | 109 | - name: Upload CodeQL results to code scanning 110 | uses: github/codeql-action/upload-sarif@v2 111 | with: 112 | sarif_file: ${{ steps.step1.outputs.sarif-output }} 113 | category: "/language:${{matrix.language}}" 114 | 115 | - name: Upload CodeQL results as an artifact 116 | if: success() || failure() 117 | uses: actions/upload-artifact@v3 118 | with: 119 | name: codeql-results 120 | path: ${{ steps.step1.outputs.sarif-output }} 121 | retention-days: 5 122 | 123 | - name: Fail if an error is found 124 | run: | 125 | ./.github/workflows/fail_on_error.py \ 126 | ${{ steps.step1.outputs.sarif-output }}/cpp.sarif 127 | -------------------------------------------------------------------------------- /.github/workflows/fail_on_error.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json 4 | import sys 5 | 6 | # Return whether SARIF file contains error-level results 7 | def codeql_sarif_contain_error(filename): 8 | with open(filename, 'r') as f: 9 | s = json.load(f) 10 | 11 | for run in s.get('runs', []): 12 | rules_metadata = run['tool']['driver']['rules'] 13 | if not rules_metadata: 14 | rules_metadata = run['tool']['extensions'][0]['rules'] 15 | 16 | for res in run.get('results', []): 17 | if 'ruleIndex' in res: 18 | rule_index = res['ruleIndex'] 19 | elif 'rule' in res and 'index' in res['rule']: 20 | rule_index = res['rule']['index'] 21 | else: 22 | continue 23 | try: 24 | rule_level = rules_metadata[rule_index]['defaultConfiguration']['level'] 25 | except IndexError as e: 26 | print(e, rule_index, len(rules_metadata)) 27 | else: 28 | if rule_level == 'error': 29 | return True 30 | return False 31 | 32 | if __name__ == "__main__": 33 | if codeql_sarif_contain_error(sys.argv[1]): 34 | sys.exit(1) 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | name: Create Release 8 | 9 | jobs: 10 | build: 11 | name: Create Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | - name: Create Release 17 | id: create_release 18 | uses: actions/create-release@v1 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 21 | with: 22 | tag_name: ${{ github.ref }} 23 | release_name: Release ${{ github.ref }} 24 | body: | 25 | See the [CHANGELOG](CHANGELOG.md) 26 | draft: false 27 | prerelease: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Build Keil files 2 | *.rar 3 | *.o 4 | *.d 5 | *.crf 6 | *.htm 7 | *.dep 8 | *.map 9 | *.bak 10 | *.axf 11 | *.lnp 12 | *.lst 13 | *.ini 14 | *.scvd 15 | *.iex 16 | *.sct 17 | *.MajerleT 18 | *.tjuln 19 | *.tilen 20 | *.dbgconf 21 | *.uvguix 22 | *.uvoptx 23 | *.__i 24 | *.i 25 | *.txt 26 | !docs/*.txt 27 | !CMakeLists.txt 28 | RTE/ 29 | 30 | *debug 31 | 32 | # IAR Settings 33 | **/settings/*.crun 34 | **/settings/*.dbgdt 35 | **/settings/*.cspy 36 | **/settings/*.cspy.* 37 | **/settings/*.xcl 38 | **/settings/*.dni 39 | **/settings/*.wsdt 40 | **/settings/*.wspos 41 | 42 | # IAR Debug Exe 43 | **/Exe/*.sim 44 | 45 | # IAR Debug Obj 46 | **/Obj/*.pbd 47 | **/Obj/*.pbd.* 48 | **/Obj/*.pbi 49 | **/Obj/*.pbi.* 50 | 51 | *.TMP 52 | /docs_src/x_Doxyfile.doxy 53 | 54 | .DS_Store 55 | 56 | ## Ignore Visual Studio temporary files, build results, and 57 | ## files generated by popular Visual Studio add-ons. 58 | ## 59 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 60 | 61 | # User-specific files 62 | *.suo 63 | *.user 64 | *.userosscache 65 | *.sln.docstates 66 | 67 | # User-specific files (MonoDevelop/Xamarin Studio) 68 | *.userprefs 69 | 70 | # Build results 71 | [Dd]ebug/ 72 | [Dd]ebugPublic/ 73 | [Rr]elease/ 74 | [Rr]eleases/ 75 | [Dd]ebug*/ 76 | x64/ 77 | x86/ 78 | bld/ 79 | [Bb]in/ 80 | [Oo]bj/ 81 | [Ll]og/ 82 | _build/ 83 | build/ 84 | __build__/ 85 | 86 | # Visual Studio 2015/2017 cache/options directory 87 | .vs/ 88 | # Uncomment if you have tasks that create the project's static files in wwwroot 89 | #wwwroot/ 90 | 91 | # Visual Studio 2017 auto generated files 92 | Generated\ Files/ 93 | 94 | # MSTest test Results 95 | [Tt]est[Rr]esult*/ 96 | [Bb]uild[Ll]og.* 97 | 98 | # NUNIT 99 | *.VisualState.xml 100 | TestResult.xml 101 | 102 | # Build Results of an ATL Project 103 | [Dd]ebugPS/ 104 | [Rr]eleasePS/ 105 | dlldata.c 106 | 107 | # Benchmark Results 108 | BenchmarkDotNet.Artifacts/ 109 | 110 | # .NET Core 111 | project.lock.json 112 | project.fragment.lock.json 113 | artifacts/ 114 | **/Properties/launchSettings.json 115 | 116 | # StyleCop 117 | StyleCopReport.xml 118 | 119 | # Files built by Visual Studio 120 | *_i.c 121 | *_p.c 122 | *_i.h 123 | *.ilk 124 | *.meta 125 | *.obj 126 | *.pch 127 | *.pdb 128 | *.pgc 129 | *.pgd 130 | *.rsp 131 | *.sbr 132 | *.tlb 133 | *.tli 134 | *.tlh 135 | *.tmp 136 | *.tmp_proj 137 | *.log 138 | *.vspscc 139 | *.vssscc 140 | .builds 141 | *.pidb 142 | *.svclog 143 | *.scc 144 | *.out 145 | *.sim 146 | 147 | # Chutzpah Test files 148 | _Chutzpah* 149 | 150 | # Visual C++ cache files 151 | ipch/ 152 | *.aps 153 | *.ncb 154 | *.opendb 155 | *.opensdf 156 | *.sdf 157 | *.cachefile 158 | *.VC.db 159 | *.VC.VC.opendb 160 | 161 | # Visual Studio profiler 162 | *.psess 163 | *.vsp 164 | *.vspx 165 | *.sap 166 | 167 | # Visual Studio Trace Files 168 | *.e2e 169 | 170 | # TFS 2012 Local Workspace 171 | $tf/ 172 | 173 | # Guidance Automation Toolkit 174 | *.gpState 175 | 176 | # ReSharper is a .NET coding add-in 177 | _ReSharper*/ 178 | *.[Rr]e[Ss]harper 179 | *.DotSettings.user 180 | 181 | # JustCode is a .NET coding add-in 182 | .JustCode 183 | 184 | # TeamCity is a build add-in 185 | _TeamCity* 186 | 187 | # DotCover is a Code Coverage Tool 188 | *.dotCover 189 | 190 | # AxoCover is a Code Coverage Tool 191 | .axoCover/* 192 | !.axoCover/settings.json 193 | 194 | # Visual Studio code coverage results 195 | *.coverage 196 | *.coveragexml 197 | 198 | # NCrunch 199 | _NCrunch_* 200 | .*crunch*.local.xml 201 | nCrunchTemp_* 202 | 203 | # MightyMoose 204 | *.mm.* 205 | AutoTest.Net/ 206 | 207 | # Web workbench (sass) 208 | .sass-cache/ 209 | 210 | # Installshield output folder 211 | [Ee]xpress/ 212 | 213 | # DocProject is a documentation generator add-in 214 | DocProject/buildhelp/ 215 | DocProject/Help/*.HxT 216 | DocProject/Help/*.HxC 217 | DocProject/Help/*.hhc 218 | DocProject/Help/*.hhk 219 | DocProject/Help/*.hhp 220 | DocProject/Help/Html2 221 | DocProject/Help/html 222 | 223 | # Click-Once directory 224 | publish/ 225 | 226 | # Publish Web Output 227 | *.[Pp]ublish.xml 228 | *.azurePubxml 229 | # Note: Comment the next line if you want to checkin your web deploy settings, 230 | # but database connection strings (with potential passwords) will be unencrypted 231 | *.pubxml 232 | *.publishproj 233 | 234 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 235 | # checkin your Azure Web App publish settings, but sensitive information contained 236 | # in these scripts will be unencrypted 237 | PublishScripts/ 238 | 239 | # NuGet Packages 240 | *.nupkg 241 | # The packages folder can be ignored because of Package Restore 242 | **/[Pp]ackages/* 243 | # except build/, which is used as an MSBuild target. 244 | !**/[Pp]ackages/build/ 245 | # Uncomment if necessary however generally it will be regenerated when needed 246 | #!**/[Pp]ackages/repositories.config 247 | # NuGet v3's project.json files produces more ignorable files 248 | *.nuget.props 249 | *.nuget.targets 250 | 251 | # Microsoft Azure Build Output 252 | csx/ 253 | *.build.csdef 254 | 255 | # Microsoft Azure Emulator 256 | ecf/ 257 | rcf/ 258 | 259 | # Windows Store app package directories and files 260 | AppPackages/ 261 | BundleArtifacts/ 262 | Package.StoreAssociation.xml 263 | _pkginfo.txt 264 | *.appx 265 | 266 | # Visual Studio cache files 267 | # files ending in .cache can be ignored 268 | *.[Cc]ache 269 | # but keep track of directories ending in .cache 270 | !*.[Cc]ache/ 271 | 272 | # Others 273 | ClientBin/ 274 | ~$* 275 | *~ 276 | *.dbmdl 277 | *.dbproj.schemaview 278 | *.jfm 279 | *.pfx 280 | *.publishsettings 281 | orleans.codegen.cs 282 | 283 | # Including strong name files can present a security risk 284 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 285 | #*.snk 286 | 287 | # Since there are multiple workflows, uncomment next line to ignore bower_components 288 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 289 | #bower_components/ 290 | 291 | # RIA/Silverlight projects 292 | Generated_Code/ 293 | 294 | # Backup & report files from converting an old project file 295 | # to a newer Visual Studio version. Backup files are not needed, 296 | # because we have git ;-) 297 | _UpgradeReport_Files/ 298 | Backup*/ 299 | UpgradeLog*.XML 300 | UpgradeLog*.htm 301 | 302 | # SQL Server files 303 | *.mdf 304 | *.ldf 305 | *.ndf 306 | 307 | # Business Intelligence projects 308 | *.rdl.data 309 | *.bim.layout 310 | *.bim_*.settings 311 | 312 | # Microsoft Fakes 313 | FakesAssemblies/ 314 | 315 | # GhostDoc plugin setting file 316 | *.GhostDoc.xml 317 | 318 | # Node.js Tools for Visual Studio 319 | .ntvs_analysis.dat 320 | node_modules/ 321 | 322 | # TypeScript v1 declaration files 323 | typings/ 324 | 325 | # Visual Studio 6 build log 326 | *.plg 327 | 328 | # Visual Studio 6 workspace options file 329 | *.opt 330 | 331 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 332 | *.vbw 333 | 334 | # Visual Studio LightSwitch build output 335 | **/*.HTMLClient/GeneratedArtifacts 336 | **/*.DesktopClient/GeneratedArtifacts 337 | **/*.DesktopClient/ModelManifest.xml 338 | **/*.Server/GeneratedArtifacts 339 | **/*.Server/ModelManifest.xml 340 | _Pvt_Extensions 341 | 342 | # Paket dependency manager 343 | .paket/paket.exe 344 | paket-files/ 345 | 346 | # FAKE - F# Make 347 | .fake/ 348 | 349 | # JetBrains Rider 350 | .idea/ 351 | *.sln.iml 352 | 353 | # CodeRush 354 | .cr/ 355 | 356 | # Python Tools for Visual Studio (PTVS) 357 | __pycache__/ 358 | *.pyc 359 | 360 | # Cake - Uncomment if you are using it 361 | # tools/** 362 | # !tools/packages.config 363 | 364 | # Tabs Studio 365 | *.tss 366 | 367 | # Telerik's JustMock configuration file 368 | *.jmconfig 369 | 370 | # BizTalk build output 371 | *.btp.cs 372 | *.btm.cs 373 | *.odx.cs 374 | *.xsd.cs 375 | 376 | # OpenCover UI analysis results 377 | OpenCover/ 378 | 379 | # Azure Stream Analytics local run output 380 | ASALocalRun/ 381 | 382 | # MSBuild Binary and Structured Log 383 | *.binlog 384 | 385 | log_file.txt 386 | .metadata/ 387 | .mxproject 388 | .settings/ 389 | project.ioc 390 | mx.scratch 391 | *.tilen majerle 392 | 393 | 394 | # Altium 395 | Project outputs* 396 | History/ 397 | *.SchDocPreview 398 | *.$$$Preview 399 | 400 | # VSCode projects 401 | project_vscode_compiled.exe -------------------------------------------------------------------------------- /.lvimrc: -------------------------------------------------------------------------------- 1 | " tree-specific vimrc to comply with project coding style 2 | " see https://github.com/MaJerle/c-code-style 3 | if &ft == "c" || &ft == "cpp" 4 | setlocal shiftwidth=4 tabstop=4 softtabstop=4 expandtab autoindent cc=80 foldmethod=indent 5 | endif 6 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: ubuntu-22.04 4 | tools: 5 | python: "3.11" 6 | 7 | # Build documentation in the docs/ directory with Sphinx 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | # Python configuration 12 | python: 13 | install: 14 | - requirements: docs/requirements.txt 15 | 16 | formats: 17 | - pdf 18 | - epub 19 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "configurations": [ 4 | { 5 | /* 6 | * Full configuration is provided by CMake plugin for vscode, 7 | * that shall be installed by user 8 | */ 9 | "name": "Win32", 10 | "intelliSenseMode": "${default}", 11 | "configurationProvider": "ms-vscode.cmake-tools" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-vscode.cpptools", 4 | "ms-vscode.cmake-tools", 5 | "twxs.cmake", 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | /* GDB must in be in the PATH environment */ 6 | "name": "(Windows) Launch", 7 | "type": "cppdbg", 8 | "request": "launch", 9 | "program": "${command:cmake.launchTargetPath}", 10 | "args": [], 11 | "stopAtEntry": false, 12 | "cwd": "${fileDirname}", 13 | "environment": [] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "lwevt_types.h": "c", 4 | "lwevt_type.h": "c", 5 | "lwevt.h": "c", 6 | "string.h": "c", 7 | "lwevt_opt.h": "c", 8 | "stdlib.h": "c", 9 | "lwgps.h": "c", 10 | "lwgps_opt.h": "c" 11 | }, 12 | "esbonio.sphinx.confDir": "", 13 | "C_Cpp.codeAnalysis.clangTidy.useBuildPath": true 14 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cppbuild", 6 | "label": "Build project", 7 | "command": "cmake", 8 | "args": ["--build", "${command:cmake.buildDirectory}", "-j", "8"], 9 | "options": { 10 | "cwd": "${workspaceFolder}" 11 | }, 12 | "problemMatcher": ["$gcc"], 13 | "group": { 14 | "kind": "build", 15 | "isDefault": true 16 | } 17 | }, 18 | { 19 | "type": "shell", 20 | "label": "Re-build project", 21 | "command": "cmake", 22 | "args": ["--build", "${command:cmake.buildDirectory}", "--clean-first", "-v", "-j", "8"], 23 | "options": { 24 | "cwd": "${workspaceFolder}" 25 | }, 26 | "problemMatcher": ["$gcc"], 27 | }, 28 | { 29 | "type": "shell", 30 | "label": "Clean project", 31 | "command": "cmake", 32 | "args": ["--build", "${command:cmake.buildDirectory}", "--target", "clean"], 33 | "options": { 34 | "cwd": "${workspaceFolder}" 35 | }, 36 | "problemMatcher": [] 37 | }, 38 | { 39 | "type": "shell", 40 | "label": "Run application", 41 | "command": "${command:cmake.launchTargetPath}", 42 | "args": [], 43 | "problemMatcher": [], 44 | }, 45 | { 46 | "label": "Docs: Install python plugins from requirements.txt file", 47 | "type": "shell", 48 | "command": "python -m pip install -r requirements.txt", 49 | "options": { 50 | "cwd": "${workspaceFolder}/docs" 51 | }, 52 | "problemMatcher": [] 53 | }, 54 | { 55 | "label": "Docs: Generate html", 56 | "type": "shell", 57 | "command": ".\\make html", 58 | "options": { 59 | "cwd": "${workspaceFolder}/docs" 60 | }, 61 | "problemMatcher": [] 62 | }, 63 | { 64 | "label": "Docs: Clean build directory", 65 | "type": "shell", 66 | "command": ".\\make clean", 67 | "options": { 68 | "cwd": "${workspaceFolder}/docs" 69 | }, 70 | "problemMatcher": [] 71 | }, 72 | ] 73 | } -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Tilen Majerle 2 | Sirio Balmelli 3 | Robin Mueller 4 | Sebastian Krebs 5 | Tilen Majerle 6 | Brian 7 | Gonçalo Salazar 8 | Jesse Hoogervorst -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Develop 4 | 5 | - Add support for differential GPS last time 6 | - Rework library CMake with removed INTERFACE type 7 | - Add `time_valid` and `date_valid` to give information if information from modem is valid 8 | 9 | ## v2.2.0 10 | 11 | - Split `CMakeLists.txt` files between library and executable 12 | - Change license year to `2023` 13 | - Add `.clang-format` draft 14 | - Deprecate lowercase `lwgps_speed_xxx` enumeration. Temporary implement macro to keep backward compatibility. Will be removed in next major release 15 | - Improve `C++` port 16 | 17 | ## v2.1.0 18 | 19 | - Add configuration settings to be consistend with other LwXX libraries 20 | - Apply code style settings with Artistic style options 21 | 22 | ## v2.0.0 23 | 24 | - Break compatibility with v1.x 25 | - Function prefix set to `lwgps_` 26 | - Macros prefix set to `LWGPS_` 27 | - Added support for PUBX Ublox statement 28 | 29 | ## v1.1.0 30 | 31 | - Use pre-increment instead of post-increment 32 | - Remove buffer library and propose ringbuff instead 33 | - Other code style enhancements 34 | 35 | ## v1.0.0 36 | 37 | - Initial release 38 | 39 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | # This CITATION.cff file was generated with cffinit. 2 | # Visit https://bit.ly/cffinit to generate yours today! 3 | 4 | cff-version: 1.2.0 5 | title: lwgps 6 | message: >- 7 | If you use this software, please cite it using the 8 | metadata from this file. 9 | type: software 10 | authors: 11 | - family-names: Majerle 12 | given-names: Tilen 13 | email: tilen.majerle@gmail.com 14 | - given-names: Sirio 15 | family-names: Balmelli 16 | - given-names: Robin 17 | family-names: Mueller 18 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | # Setup project 4 | project(LwLibPROJECT) 5 | 6 | if(NOT PROJECT_IS_TOP_LEVEL) 7 | add_subdirectory(lwgps) 8 | else() 9 | set(TEST_CMAKE_FILE_NAME "test_parse_standard/cmake.cmake") 10 | add_subdirectory(tests) 11 | endif() 12 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "configurePresets": [ 4 | { 5 | "name": "default", 6 | "hidden": true, 7 | "generator": "Ninja", 8 | "binaryDir": "${sourceDir}/build/${presetName}", 9 | "cacheVariables": { 10 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" 11 | } 12 | }, 13 | { 14 | "name": "Win32-Debug", 15 | "inherits": "default", 16 | "toolchainFile": "${sourceDir}/cmake/i686-w64-mingw32-gcc.cmake", 17 | "cacheVariables": { 18 | "CMAKE_BUILD_TYPE": "Debug" 19 | } 20 | }, 21 | { 22 | "name": "Win64-Debug", 23 | "inherits": "default", 24 | "toolchainFile": "${sourceDir}/cmake/x86_64-w64-mingw32-gcc.cmake", 25 | "cacheVariables": { 26 | "CMAKE_BUILD_TYPE": "Debug" 27 | } 28 | } 29 | ], 30 | "buildPresets": [ 31 | { 32 | "name": "Win32-Debug", 33 | "configurePreset": "Win32-Debug" 34 | }, 35 | { 36 | "name": "Win64-Debug", 37 | "configurePreset": "Win64-Debug" 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Tilen MAJERLE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lightweight GPS NMEA parser 2 | 3 | Platform independent GPS NMEA parser for embedded systems. 4 | 5 |

Read first: Documentation

6 | 7 | ## Features 8 | 9 | * Written in C (C11) 10 | * Platform independent, easy to use 11 | * Built-in support for 4 GPS statements 12 | * ``GPGGA`` or ``GNGGA``: GPS fix data 13 | * ``GPGSA`` or ``GNGSA``: GPS active satellites and dillusion of position 14 | * ``GPGSV`` or ``GNGSV``: List of satellites in view zone 15 | * ``GPRMC`` or ``GNRMC``: Recommended minimum specific GPS/Transit data 16 | * Optional ``float`` or ``double`` floating point units 17 | * Low-level layer is separated from application layer, thus allows you to add custom communication with GPS device 18 | * Works with operating systems 19 | * Works with different communication interfaces 20 | * User friendly MIT license 21 | 22 | ## Contribute 23 | 24 | Fresh contributions are always welcome. Simple instructions to proceed: 25 | 26 | 1. Fork Github repository 27 | 2. Follow [C style & coding rules](https://github.com/MaJerle/c-code-style) already used in the project 28 | 3. Create a pull request to develop branch with new features or bug fixes 29 | 30 | Alternatively you may: 31 | 32 | 1. Report a bug 33 | 2. Ask for a feature request 34 | 35 | ## Test 36 | 37 | To build the code and run basic tests on your host:: 38 | 39 | cd examples 40 | make test 41 | -------------------------------------------------------------------------------- /cmake/i686-w64-mingw32-gcc.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Windows) 2 | 3 | # Some default GCC settings 4 | set(CMAKE_C_COMPILER i686-w64-mingw32-gcc) 5 | set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) 6 | 7 | set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) 8 | -------------------------------------------------------------------------------- /cmake/x86_64-w64-mingw32-gcc.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Windows) 2 | 3 | # Some default GCC settings 4 | set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) 5 | set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) 6 | 7 | set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) 8 | -------------------------------------------------------------------------------- /dev/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This example uses direct processing function, 3 | * to process dummy NMEA data from GPS receiver 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include "lwgps/lwgps.h" 9 | #include "lwgps/lwgps.hpp" 10 | 11 | /* External function */ 12 | extern "C" int run_tests_time(void); 13 | extern "C" int run_tests_main(void); 14 | 15 | Lwgps::Lwgps gps; 16 | 17 | int 18 | main() { 19 | int ret = 0; 20 | ret |= run_tests_main(); 21 | ret |= run_tests_time(); 22 | 23 | printf("Done\r\n"); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /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/api-reference/gps.rst: -------------------------------------------------------------------------------- 1 | .. _api_lwgps: 2 | 3 | LwGPS 4 | ===== 5 | 6 | .. doxygengroup:: LWGPS 7 | -------------------------------------------------------------------------------- /docs/api-reference/gps_opt.rst: -------------------------------------------------------------------------------- 1 | .. _api_lwgps_opt: 2 | 3 | Configuration 4 | ============= 5 | 6 | This is the default configuration of the middleware. 7 | When any of the settings shall be modified, it shall be done in dedicated application config ``lwgps_opts.h`` file. 8 | 9 | .. note:: 10 | Check :ref:`getting_started` to create configuration file. 11 | 12 | .. doxygengroup:: LWGPS_OPT 13 | :inner: -------------------------------------------------------------------------------- /docs/api-reference/index.rst: -------------------------------------------------------------------------------- 1 | .. _api_reference: 2 | 3 | API reference 4 | ============= 5 | 6 | List of all the modules: 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | :glob: 11 | 12 | * 13 | -------------------------------------------------------------------------------- /docs/authors/index.rst: -------------------------------------------------------------------------------- 1 | .. _authors: 2 | 3 | Authors 4 | ======= 5 | 6 | List of authors and contributors to the library 7 | 8 | .. literalinclude:: ../../AUTHORS -------------------------------------------------------------------------------- /docs/changelog/index.rst: -------------------------------------------------------------------------------- 1 | .. _changelof: 2 | 3 | Changelog 4 | ========= 5 | 6 | .. literalinclude:: ../../CHANGELOG.md 7 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | from sphinx.builders.html import StandaloneHTMLBuilder 17 | import subprocess, os 18 | 19 | # Run doxygen first 20 | # read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True' 21 | # if read_the_docs_build: 22 | subprocess.call('doxygen doxyfile.doxy', shell=True) 23 | # -- Project information ----------------------------------------------------- 24 | 25 | project = 'LwGPS' 26 | copyright = '2023, Tilen MAJERLE' 27 | author = 'Tilen MAJERLE' 28 | 29 | # Try to get branch at which this is running 30 | # and try to determine which version to display in sphinx 31 | # Version is using git tag if on master/main or "latest-develop" if on develop branch 32 | version = '' 33 | git_branch = '' 34 | 35 | def cmd_exec_print(t): 36 | print("cmd > ", t, "\n", os.popen(t).read().strip(), "\n") 37 | 38 | # Print demo data here 39 | cmd_exec_print('git branch') 40 | cmd_exec_print('git describe') 41 | cmd_exec_print('git describe --tags') 42 | cmd_exec_print('git describe --tags --abbrev=0') 43 | cmd_exec_print('git describe --tags --abbrev=1') 44 | 45 | # Get current branch 46 | res = os.popen('git branch').read().strip() 47 | for line in res.split("\n"): 48 | if line[0] == '*': 49 | git_branch = line[1:].strip() 50 | 51 | # Decision for display version 52 | git_branch = git_branch.replace('(HEAD detached at ', '').replace(')', '') 53 | if git_branch.find('master') >= 0 or git_branch.find('main') >= 0: 54 | #version = os.popen('git describe --tags --abbrev=0').read().strip() 55 | version = 'latest-stable' 56 | elif git_branch.find('develop-') >= 0 or git_branch.find('develop/') >= 0: 57 | version = 'branch-' + git_branch 58 | elif git_branch == 'develop' or git_branch == 'origin/develop': 59 | version = 'latest-develop' 60 | else: 61 | version = os.popen('git describe --tags --abbrev=0').read().strip() 62 | 63 | # For debugging purpose only 64 | print("GIT BRANCH: " + git_branch) 65 | print("PROJ VERSION: " + version) 66 | 67 | # -- General configuration --------------------------------------------------- 68 | 69 | # Add any Sphinx extension module names here, as strings. They can be 70 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 71 | # ones. 72 | extensions = [ 73 | 'sphinx.ext.autodoc', 74 | 'sphinx.ext.intersphinx', 75 | 'sphinx.ext.autosectionlabel', 76 | 'sphinx.ext.todo', 77 | 'sphinx.ext.coverage', 78 | 'sphinx.ext.mathjax', 79 | 'sphinx.ext.ifconfig', 80 | 'sphinx.ext.viewcode', 81 | 'sphinx_sitemap', 82 | 83 | 'breathe', 84 | ] 85 | 86 | # Add any paths that contain templates here, relative to this directory. 87 | templates_path = ['templates'] 88 | 89 | # List of patterns, relative to source directory, that match files and 90 | # directories to ignore when looking for source files. 91 | # This pattern also affects html_static_path and html_extra_path. 92 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 93 | 94 | highlight_language = 'c' 95 | 96 | # -- Options for HTML output ------------------------------------------------- 97 | 98 | # The theme to use for HTML and HTML Help pages. See the documentation for 99 | # a list of builtin themes. 100 | # 101 | html_theme = 'sphinx_rtd_theme' 102 | html_theme_options = { 103 | 'canonical_url': '', 104 | 'analytics_id': '', # Provided by Google in your dashboard 105 | 'display_version': True, 106 | 'prev_next_buttons_location': 'bottom', 107 | 'style_external_links': False, 108 | 109 | 'logo_only': False, 110 | 111 | # Toc options 112 | 'collapse_navigation': True, 113 | 'sticky_navigation': True, 114 | 'navigation_depth': 4, 115 | 'includehidden': True, 116 | 'titles_only': False 117 | } 118 | html_logo = 'static/images/logo.svg' 119 | github_url = 'https://github.com/MaJerle/lwgps' 120 | html_baseurl = 'https://docs.majerle.eu/projects/lwgps/' 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ['static'] 126 | html_css_files = [ 127 | 'css/common.css', 128 | 'css/custom.css', 129 | 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css', 130 | ] 131 | html_js_files = [ 132 | '' 133 | ] 134 | 135 | # Master index file 136 | master_doc = 'index' 137 | 138 | # --- Breathe configuration ----------------------------------------------------- 139 | breathe_projects = { 140 | "lwgps": "_build/xml/" 141 | } 142 | breathe_default_project = "lwgps" 143 | breathe_default_members = ('members', 'undoc-members') 144 | breathe_show_enumvalue_initializer = True -------------------------------------------------------------------------------- /docs/examples/index.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | Examples and demos 4 | ================== 5 | 6 | There are several basic examples provided with the library. 7 | 8 | Parse block of data 9 | ^^^^^^^^^^^^^^^^^^^ 10 | 11 | In this example, block of data is prepared as big string array and sent to processing function in single shot. 12 | Application can then check if GPS signal has been detected as valid and use other data accordingly. 13 | 14 | .. literalinclude:: ../../examples/example.c 15 | :language: c 16 | :linenos: 17 | :caption: Minimum example code 18 | 19 | Parse received data from interrupt/DMA 20 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 21 | 22 | Second example is a typical use case with interrupts on embedded systems. 23 | For each received character, application uses ``ringbuff`` as intermediate buffer. 24 | Data are later processed outside interrupt context. 25 | 26 | .. note:: 27 | For the sake of this example, application *implements* interrupts as function call in *while loop*. 28 | 29 | .. literalinclude:: ../../examples/example_buff.c 30 | :language: c 31 | :linenos: 32 | :caption: Example of buffer 33 | 34 | Distance and bearing 35 | ^^^^^^^^^^^^^^^^^^^^ 36 | 37 | Library provides calculation of distance and bearing between ``2`` coordinates on earth. 38 | This is useful if used with autonomnous devices to understand in which direction 39 | device has to move to reach end point while knowing start coordinate. 40 | 41 | .. literalinclude:: ../../examples/example_dist_bear.c 42 | :language: c 43 | :linenos: 44 | :caption: Distance and bearing calculation 45 | 46 | .. toctree:: 47 | :maxdepth: 2 48 | -------------------------------------------------------------------------------- /docs/get-started/index.rst: -------------------------------------------------------------------------------- 1 | .. _getting_started: 2 | 3 | Getting started 4 | =============== 5 | 6 | Getting started may be the most challenging part of every new library. 7 | This guide is describing how to start with the library quickly and effectively 8 | 9 | .. _download_library: 10 | 11 | Download library 12 | ^^^^^^^^^^^^^^^^ 13 | 14 | Library is primarly hosted on `Github `_. 15 | 16 | You can get it by: 17 | 18 | * Downloading latest release from `releases area `_ on Github 19 | * Cloning ``main`` branch for latest stable version 20 | * Cloning ``develop`` branch for latest development 21 | 22 | Download from releases 23 | ********************** 24 | 25 | All releases are available on Github `releases area `_. 26 | 27 | Clone from Github 28 | ***************** 29 | 30 | First-time clone 31 | """""""""""""""" 32 | 33 | This is used when you do not have yet local copy on your machine. 34 | 35 | * Make sure ``git`` is installed. 36 | * Open console and navigate to path in the system to clone repository to. Use command ``cd your_path`` 37 | * Clone repository with one of available options below 38 | 39 | * Run ``git clone --recurse-submodules https://github.com/MaJerle/lwgps`` command to clone entire repository, including submodules 40 | * Run ``git clone --recurse-submodules --branch develop https://github.com/MaJerle/lwgps`` to clone `development` branch, including submodules 41 | * Run ``git clone --recurse-submodules --branch main https://github.com/MaJerle/lwgps`` to clone `latest stable` branch, including submodules 42 | 43 | * Navigate to ``examples`` directory and run favourite example 44 | 45 | Update cloned to latest version 46 | """"""""""""""""""""""""""""""" 47 | 48 | * Open console and navigate to path in the system where your repository is located. Use command ``cd your_path`` 49 | * Run ``git pull origin main`` command to get latest changes on ``main`` branch 50 | * Run ``git pull origin develop`` command to get latest changes on ``develop`` branch 51 | * Run ``git submodule update --init --remote`` to update submodules to latest version 52 | 53 | .. note:: 54 | This is preferred option to use when you want to evaluate library and run prepared examples. 55 | Repository consists of multiple submodules which can be automatically downloaded when cloning and pulling changes from root repository. 56 | 57 | Add library to project 58 | ^^^^^^^^^^^^^^^^^^^^^^ 59 | 60 | At this point it is assumed that you have successfully download library, either cloned it or from releases page. 61 | Next step is to add the library to the project, by means of source files to compiler inputs and header files in search path. 62 | 63 | *CMake* is the main supported build system. Package comes with the ``CMakeLists.txt`` and ``library.cmake`` files, both located in the ``lwgps`` directory: 64 | 65 | * ``library.cmake``: It is a fully configured set of variables and with library definition. User can include this file to the project file with ``include(path/to/library.cmake)`` and then manually use the variables provided by the file, such as list of source files, include paths or necessary compiler definitions. It is up to the user to properly use the this file on its own. 66 | * ``CMakeLists.txt``: It is a wrapper-only file and includes ``library.cmake`` file. It is used for when user wants to include the library to the main project by simply calling *CMake* ``add_subdirectory`` command, followed by ``target_link_libraries`` to link external library to the final project. 67 | 68 | .. tip:: 69 | Open ``library.cmake`` and analyze the provided information. Among variables, you can also find list of all possible exposed libraries for the user. 70 | 71 | If you do not use the *CMake*, you can do the following: 72 | 73 | * Copy ``lwgps`` folder to your project, it contains library files 74 | * Add ``lwgps/src/include`` folder to `include path` of your toolchain. This is where `C/C++` compiler can find the files during compilation process. Usually using ``-I`` flag 75 | * Add source files from ``lwgps/src/`` folder to toolchain build. These files are built by `C/C++` compiler 76 | * Copy ``lwgps/src/include/lwgps/lwgps_opts_template.h`` to project folder and rename it to ``lwgps_opts.h`` 77 | * Build the project 78 | 79 | Configuration file 80 | ^^^^^^^^^^^^^^^^^^ 81 | 82 | Configuration file is used to overwrite default settings defined for the essential use case. 83 | Library comes with template config file, which can be modified according to the application needs. 84 | and it should be copied (or simply renamed in-place) and named ``lwgps_opts.h`` 85 | 86 | .. note:: 87 | Default configuration template file location: ``lwgps/src/include/lwgps/lwgps_opts_template.h``. 88 | File must be renamed to ``lwgps_opts.h`` first and then copied to the project directory where compiler 89 | include paths have access to it by using ``#include "lwgps_opts.h"``. 90 | 91 | .. tip:: 92 | If you are using *CMake* build system, define the variable ``LWGPS_OPTS_FILE`` before adding library's directory to the *CMake* project. 93 | Variable must contain the path to the user options file. If not provided and to avoid build error, one will be generated in the build directory. 94 | 95 | Configuration options list is available available in the :ref:`api_lwgps_opt` section. 96 | If any option is about to be modified, it should be done in configuration file 97 | 98 | .. literalinclude:: ../../lwgps/src/include/lwgps/lwgps_opts_template.h 99 | :language: c 100 | :linenos: 101 | :caption: Template configuration file 102 | 103 | .. note:: 104 | If you prefer to avoid using configuration file, application must define 105 | a global symbol ``LWGPS_IGNORE_USER_OPTS``, visible across entire application. 106 | This can be achieved with ``-D`` compiler option. 107 | 108 | Minimal example code 109 | ^^^^^^^^^^^^^^^^^^^^ 110 | 111 | To verify proper library setup, minimal example has been prepared. 112 | Run it in your main application file to verify its proper execution 113 | 114 | .. literalinclude:: ../../examples/example.c 115 | :language: c 116 | :linenos: 117 | :caption: Absolute minimum example -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | LwGPS |version| documentation 2 | ============================= 3 | 4 | Welcome to the documentation for version |version|. 5 | 6 | LwGPS is lightweight, platform independent library to parse NMEA statements from GPS receivers. It is highly optimized for embedded systems. 7 | 8 | .. image:: static/images/logo.svg 9 | :align: center 10 | 11 | .. rst-class:: center 12 | .. rst-class:: index_links 13 | 14 | :ref:`download_library` :ref:`getting_started` `Open Github `_ `Donate `_ 15 | 16 | Features 17 | ^^^^^^^^ 18 | 19 | * Written in C (C11) 20 | * Platform independent, easy to use 21 | * Built-in support for 4 GPS statements 22 | 23 | * ``GPGGA`` or ``GNGGA``: GPS fix data 24 | * ``GPGSA`` or ``GNGSA``: GPS active satellites and dillusion of position 25 | * ``GPGSV`` or ``GNGSV``: List of satellites in view zone 26 | * ``GPRMC`` or ``GNRMC``: Recommended minimum specific GPS/Transit data 27 | 28 | * Optional ``float`` or ``double`` floating point units 29 | * Low-level layer is separated from application layer, thus allows you to add custom communication with GPS device 30 | * Works with operating systems 31 | * Works with different communication interfaces 32 | * User friendly MIT license 33 | 34 | Requirements 35 | ^^^^^^^^^^^^ 36 | 37 | * C compiler 38 | * Driver for receiving data from GPS receiver 39 | * Few *kB* of non-volatile memory 40 | 41 | Contribute 42 | ^^^^^^^^^^ 43 | 44 | Fresh contributions are always welcome. Simple instructions to proceed: 45 | 46 | #. Fork Github repository 47 | #. Respect `C style & coding rules `_ used by the library 48 | #. Create a pull request to ``develop`` branch with new features or bug fixes 49 | 50 | Alternatively you may: 51 | 52 | #. Report a bug 53 | #. Ask for a feature request 54 | 55 | License 56 | ^^^^^^^ 57 | 58 | .. literalinclude:: ../LICENSE 59 | 60 | Table of contents 61 | ^^^^^^^^^^^^^^^^^ 62 | 63 | .. toctree:: 64 | :maxdepth: 2 65 | :caption: Contents 66 | 67 | self 68 | get-started/index 69 | user-manual/index 70 | api-reference/index 71 | examples/index 72 | changelog/index 73 | authors/index 74 | 75 | .. toctree:: 76 | :maxdepth: 2 77 | :caption: Other projects 78 | :hidden: 79 | 80 | LwBTN - Button manager 81 | LwDTC - DateTimeCron 82 | LwESP - ESP-AT library 83 | LwEVT - Event manager 84 | LwGPS - GPS NMEA parser 85 | LwCELL - Cellular modem host AT library 86 | LwJSON - JSON parser 87 | LwMEM - Memory manager 88 | LwOW - OneWire with UART 89 | LwPKT - Packet protocol 90 | LwPRINTF - Printf 91 | LwRB - Ring buffer 92 | LwSHELL - Shell 93 | LwUTIL - Utility functions 94 | LwWDG - RTOS task watchdog 95 | 96 | -------------------------------------------------------------------------------- /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 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 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/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=3.5.1 2 | breathe>=4.9.1 3 | urllib3==1.26.18 4 | docutils==0.16 5 | colorama 6 | sphinx_rtd_theme>=1.0.0 7 | sphinx-tabs 8 | sphinxcontrib-svg2pdfconverter 9 | sphinx-sitemap 10 | -------------------------------------------------------------------------------- /docs/static/css/common.css: -------------------------------------------------------------------------------- 1 | /* Center aligned text */ 2 | .center { 3 | text-align: center; 4 | } 5 | 6 | /* Paragraph with main links on index page */ 7 | .index-links { 8 | text-align: center; 9 | margin-top: 10px; 10 | } 11 | .index-links a { 12 | display: inline-block; 13 | border: 1px solid #0E4263; 14 | padding: 5px 20px; 15 | margin: 2px 5px; 16 | background: #2980B9; 17 | border-radius: 4px; 18 | color: #FFFFFF; 19 | } 20 | .index-links a:hover, .index-links a:active { 21 | background: #0E4263; 22 | } 23 | 24 | /* Table header p w/0 margin */ 25 | .index-links a table thead th { 26 | vertical-align: middle; 27 | } 28 | 29 | table thead th p { 30 | margin: 0; 31 | } 32 | 33 | .table-nowrap td { 34 | white-space: normal !important; 35 | } 36 | 37 | /* Breathe output changes */ 38 | .breathe-sectiondef.container { 39 | background: #f9f9f9; 40 | padding: 10px; 41 | margin-bottom: 10px; 42 | border: 1px solid #efefef; 43 | } 44 | .breathe-sectiondef.container .breathe-sectiondef-title { 45 | background: #2980b9; 46 | color: #FFFFFF; 47 | padding: 4px; 48 | margin: -10px -10px 0 -10px; 49 | } 50 | .breathe-sectiondef.container .function, 51 | .breathe-sectiondef.container .member, 52 | .breathe-sectiondef.container .class, 53 | .breathe-sectiondef.container .type { 54 | border-bottom: 1px solid #efefef; 55 | } 56 | .breathe-sectiondef.container .function:last-child, 57 | .breathe-sectiondef.container .member:last-child, 58 | .breathe-sectiondef.container .class:last-child, 59 | .breathe-sectiondef.container .type:last-child { 60 | border-bottom: none; 61 | margin-bottom: 0; 62 | } 63 | 64 | /*# sourceMappingURL=common.css.map */ 65 | -------------------------------------------------------------------------------- /docs/static/css/custom.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwgps/1e5f9a81a58b594652b867703c4ea0bc09e179a4/docs/static/css/custom.css -------------------------------------------------------------------------------- /docs/static/dark-light/checked.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/dark-light/common-dark-light.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2019 Google LLC 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | :root { 18 | --heading-color: red; 19 | --duration: 0.5s; 20 | --timing: ease; 21 | } 22 | 23 | *, 24 | ::before, 25 | ::after { 26 | box-sizing: border-box; 27 | } 28 | 29 | body { 30 | margin: 0; 31 | transition: 32 | color var(--duration) var(--timing), 33 | background-color var(--duration) var(--timing); 34 | font-family: sans-serif; 35 | font-size: 12pt; 36 | background-color: var(--background-color); 37 | color: var(--text-color); 38 | display: flex; 39 | justify-content: center; 40 | } 41 | 42 | main { 43 | margin: 1rem; 44 | max-width: 30rem; 45 | position: relative; 46 | } 47 | 48 | h1 { 49 | color: var(--heading-color); 50 | text-shadow: 0.1rem 0.1rem 0.1rem var(--shadow-color); 51 | transition: text-shadow var(--duration) var(--timing); 52 | } 53 | 54 | img { 55 | max-width: 100%; 56 | height: auto; 57 | transition: filter var(--duration) var(--timing); 58 | } 59 | 60 | p { 61 | line-height: 1.5; 62 | word-wrap: break-word; 63 | overflow-wrap: break-word; 64 | hyphens: auto; 65 | } 66 | 67 | fieldset { 68 | border: solid 0.1rem; 69 | box-shadow: 0.1rem 0.1rem 0.1rem var(--shadow-color); 70 | transition: box-shadow var(--duration) var(--timing); 71 | } 72 | 73 | div { 74 | padding: 0.5rem; 75 | } 76 | 77 | aside { 78 | position: absolute; 79 | right: 0; 80 | padding: 0.5rem; 81 | } 82 | 83 | aside:nth-of-type(1) { 84 | top: 0; 85 | } 86 | 87 | aside:nth-of-type(2) { 88 | top: 3rem; 89 | } 90 | 91 | aside:nth-of-type(3) { 92 | top: 7rem; 93 | } 94 | 95 | aside:nth-of-type(4) { 96 | top: 12rem; 97 | } 98 | 99 | #content select, 100 | #content button, 101 | #content input[type="text"], 102 | #content input[type="search"] { 103 | width: 15rem; 104 | } 105 | 106 | dark-mode-toggle { 107 | --dark-mode-toggle-remember-icon-checked: url("checked.svg"); 108 | --dark-mode-toggle-remember-icon-unchecked: url("unchecked.svg"); 109 | --dark-mode-toggle-remember-font: 0.75rem "Helvetica"; 110 | --dark-mode-toggle-legend-font: bold 0.85rem "Helvetica"; 111 | --dark-mode-toggle-label-font: 0.85rem "Helvetica"; 112 | --dark-mode-toggle-color: var(--text-color); 113 | --dark-mode-toggle-background-color: none; 114 | 115 | margin-bottom: 1.5rem; 116 | } 117 | 118 | #dark-mode-toggle-1 { 119 | --dark-mode-toggle-dark-icon: url("sun.png"); 120 | --dark-mode-toggle-light-icon: url("moon.png"); 121 | } 122 | 123 | #dark-mode-toggle-2 { 124 | --dark-mode-toggle-dark-icon: url("sun.svg"); 125 | --dark-mode-toggle-light-icon: url("moon.svg"); 126 | --dark-mode-toggle-icon-size: 2rem; 127 | --dark-mode-toggle-icon-filter: invert(100%); 128 | } 129 | 130 | #dark-mode-toggle-3, 131 | #dark-mode-toggle-4 { 132 | --dark-mode-toggle-dark-icon: url("moon.png"); 133 | --dark-mode-toggle-light-icon: url("sun.png"); 134 | } 135 | 136 | #dark-mode-toggle-3 { 137 | --dark-mode-toggle-remember-filter: invert(100%); 138 | } 139 | 140 | #dark-mode-toggle-4 { 141 | --dark-mode-toggle-active-mode-background-color: var(--accent-color); 142 | --dark-mode-toggle-remember-filter: invert(100%); 143 | } 144 | -------------------------------------------------------------------------------- /docs/static/dark-light/dark-mode-toggle.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // @license © 2019 Google LLC. Licensed under the Apache License, Version 2.0. 18 | const doc = document; 19 | const store = localStorage; 20 | const PREFERS_COLOR_SCHEME = 'prefers-color-scheme'; 21 | const MEDIA = 'media'; 22 | const LIGHT = 'light'; 23 | const DARK = 'dark'; 24 | const MQ_DARK = `(${PREFERS_COLOR_SCHEME}:${DARK})`; 25 | const MQ_LIGHT = `(${PREFERS_COLOR_SCHEME}:${LIGHT})`; 26 | const LINK_REL_STYLESHEET = 'link[rel=stylesheet]'; 27 | const REMEMBER = 'remember'; 28 | const LEGEND = 'legend'; 29 | const TOGGLE = 'toggle'; 30 | const SWITCH = 'switch'; 31 | const APPEARANCE = 'appearance'; 32 | const PERMANENT = 'permanent'; 33 | const MODE = 'mode'; 34 | const COLOR_SCHEME_CHANGE = 'colorschemechange'; 35 | const PERMANENT_COLOR_SCHEME = 'permanentcolorscheme'; 36 | const ALL = 'all'; 37 | const NOT_ALL = 'not all'; 38 | const NAME = 'dark-mode-toggle'; 39 | const DEFAULT_URL = 'https://googlechromelabs.github.io/dark-mode-toggle/demo/'; 40 | 41 | // See https://html.spec.whatwg.org/multipage/common-dom-interfaces.html ↵ 42 | // #reflecting-content-attributes-in-idl-attributes. 43 | const installStringReflection = (obj, attrName, propName = attrName) => { 44 | Object.defineProperty(obj, propName, { 45 | enumerable: true, 46 | get() { 47 | const value = this.getAttribute(attrName); 48 | return value === null ? '' : value; 49 | }, 50 | set(v) { 51 | this.setAttribute(attrName, v); 52 | }, 53 | }); 54 | }; 55 | 56 | const installBoolReflection = (obj, attrName, propName = attrName) => { 57 | Object.defineProperty(obj, propName, { 58 | enumerable: true, 59 | get() { 60 | return this.hasAttribute(attrName); 61 | }, 62 | set(v) { 63 | if (v) { 64 | this.setAttribute(attrName, ''); 65 | } else { 66 | this.removeAttribute(attrName); 67 | } 68 | }, 69 | }); 70 | }; 71 | 72 | const template = doc.createElement('template'); 73 | // ⚠️ Note: this is a minified version of `src/template-contents.tpl`. 74 | // Compress the CSS with https://cssminifier.com/, then paste it here. 75 | // eslint-disable-next-line max-len 76 | template.innerHTML = `
`; 77 | 78 | export class DarkModeToggle extends HTMLElement { 79 | static get observedAttributes() { 80 | return [MODE, APPEARANCE, PERMANENT, LEGEND, LIGHT, DARK, REMEMBER]; 81 | } 82 | 83 | constructor() { 84 | super(); 85 | 86 | installStringReflection(this, MODE); 87 | installStringReflection(this, APPEARANCE); 88 | installStringReflection(this, LEGEND); 89 | installStringReflection(this, LIGHT); 90 | installStringReflection(this, DARK); 91 | installStringReflection(this, REMEMBER); 92 | 93 | installBoolReflection(this, PERMANENT); 94 | 95 | this._darkCSS = null; 96 | this._lightCSS = null; 97 | 98 | doc.addEventListener(COLOR_SCHEME_CHANGE, (event) => { 99 | this.mode = event.detail.colorScheme; 100 | this._updateRadios(); 101 | this._updateCheckbox(); 102 | }); 103 | 104 | doc.addEventListener(PERMANENT_COLOR_SCHEME, (event) => { 105 | this.permanent = event.detail.permanent; 106 | this._permanentCheckbox.checked = this.permanent; 107 | }); 108 | 109 | this._initializeDOM(); 110 | } 111 | 112 | _initializeDOM() { 113 | const shadowRoot = this.attachShadow({mode: 'open'}); 114 | shadowRoot.appendChild(template.content.cloneNode(true)); 115 | 116 | // We need to support `media="(prefers-color-scheme: dark)"` (with space) 117 | // and `media="(prefers-color-scheme:dark)"` (without space) 118 | this._darkCSS = doc.querySelectorAll(`${LINK_REL_STYLESHEET}[${MEDIA}*=${PREFERS_COLOR_SCHEME}][${MEDIA}*="${DARK}"]`); 119 | this._lightCSS = doc.querySelectorAll(`${LINK_REL_STYLESHEET}[${MEDIA}*=${PREFERS_COLOR_SCHEME}][${MEDIA}*="${LIGHT}"]`); 120 | 121 | // Get DOM references. 122 | this._lightRadio = shadowRoot.querySelector('[part=lightRadio]'); 123 | this._lightLabel = shadowRoot.querySelector('[part=lightLabel]'); 124 | this._darkRadio = shadowRoot.querySelector('[part=darkRadio]'); 125 | this._darkLabel = shadowRoot.querySelector('[part=darkLabel]'); 126 | this._darkCheckbox = shadowRoot.querySelector('[part=toggleCheckbox]'); 127 | this._checkboxLabel = shadowRoot.querySelector('[part=toggleLabel]'); 128 | this._legendLabel = shadowRoot.querySelector('legend'); 129 | this._permanentAside = shadowRoot.querySelector('aside'); 130 | this._permanentCheckbox = 131 | shadowRoot.querySelector('[part=permanentCheckbox]'); 132 | this._permanentLabel = shadowRoot.querySelector('[part=permanentLabel]'); 133 | 134 | // Does the browser support native `prefers-color-scheme`? 135 | const hasNativePrefersColorScheme = 136 | matchMedia(MQ_DARK).media !== NOT_ALL; 137 | // Listen to `prefers-color-scheme` changes. 138 | if (hasNativePrefersColorScheme) { 139 | matchMedia(MQ_DARK).addListener(({matches}) => { 140 | this.mode = matches ? DARK : LIGHT; 141 | this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); 142 | }); 143 | } 144 | // Set initial state, giving preference to a remembered value, then the 145 | // native value (if supported), and eventually defaulting to a light 146 | // experience. 147 | const rememberedValue = store.getItem(NAME); 148 | if (rememberedValue && [DARK, LIGHT].includes(rememberedValue)) { 149 | this.mode = rememberedValue; 150 | this._permanentCheckbox.checked = true; 151 | this.permanent = true; 152 | } else if (hasNativePrefersColorScheme) { 153 | this.mode = matchMedia(MQ_LIGHT).matches ? LIGHT : DARK; 154 | } 155 | if (!this.mode) { 156 | this.mode = LIGHT; 157 | } 158 | if (this.permanent && !rememberedValue) { 159 | store.setItem(NAME, this.mode); 160 | } 161 | 162 | // Default to toggle appearance. 163 | if (!this.appearance) { 164 | this.appearance = TOGGLE; 165 | } 166 | 167 | // Update the appearance to either of toggle or switch. 168 | this._updateAppearance(); 169 | 170 | // Update the radios 171 | this._updateRadios(); 172 | 173 | // Make the checkbox reflect the state of the radios 174 | this._updateCheckbox(); 175 | 176 | // Synchronize the behavior of the radio and the checkbox. 177 | [this._lightRadio, this._darkRadio].forEach((input) => { 178 | input.addEventListener('change', () => { 179 | this.mode = this._lightRadio.checked ? LIGHT : DARK; 180 | this._updateCheckbox(); 181 | this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); 182 | }); 183 | }); 184 | this._darkCheckbox.addEventListener('change', () => { 185 | this.mode = this._darkCheckbox.checked ? DARK : LIGHT; 186 | this._updateRadios(); 187 | this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); 188 | }); 189 | 190 | // Make remembering the last mode optional 191 | this._permanentCheckbox.addEventListener('change', () => { 192 | this.permanent = this._permanentCheckbox.checked; 193 | this._dispatchEvent(PERMANENT_COLOR_SCHEME, { 194 | permanent: this.permanent, 195 | }); 196 | }); 197 | 198 | // Finally update the mode and let the world know what's going on 199 | this._updateMode(); 200 | this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); 201 | this._dispatchEvent(PERMANENT_COLOR_SCHEME, { 202 | permanent: this.permanent, 203 | }); 204 | } 205 | 206 | attributeChangedCallback(name, oldValue, newValue) { 207 | if (name === MODE) { 208 | if (![LIGHT, DARK].includes(newValue)) { 209 | throw new RangeError(`Allowed values: "${LIGHT}" and "${DARK}".`); 210 | } 211 | // Only show the dialog programmatically on devices not capable of hover 212 | // and only if there is a label 213 | if (matchMedia('(hover:none)').matches && this.remember) { 214 | this._showPermanentAside(); 215 | } 216 | if (this.permanent) { 217 | store.setItem(NAME, this.mode); 218 | } 219 | this._updateRadios(); 220 | this._updateCheckbox(); 221 | this._updateMode(); 222 | } else if (name === APPEARANCE) { 223 | if (![TOGGLE, SWITCH].includes(newValue)) { 224 | throw new RangeError(`Allowed values: "${TOGGLE}" and "${SWITCH}".`); 225 | } 226 | this._updateAppearance(); 227 | } else if (name === PERMANENT) { 228 | if (this.permanent) { 229 | store.setItem(NAME, this.mode); 230 | } else { 231 | store.removeItem(NAME); 232 | } 233 | this._permanentCheckbox.checked = this.permanent; 234 | } else if (name === LEGEND) { 235 | this._legendLabel.textContent = newValue; 236 | } else if (name === REMEMBER) { 237 | this._permanentLabel.textContent = newValue; 238 | } else if (name === LIGHT) { 239 | this._lightLabel.textContent = newValue; 240 | if (this.mode === LIGHT) { 241 | this._checkboxLabel.textContent = newValue; 242 | } 243 | } else if (name === DARK) { 244 | this._darkLabel.textContent = newValue; 245 | if (this.mode === DARK) { 246 | this._checkboxLabel.textContent = newValue; 247 | } 248 | } 249 | } 250 | 251 | _dispatchEvent(type, value) { 252 | this.dispatchEvent(new CustomEvent(type, { 253 | bubbles: true, 254 | composed: true, 255 | detail: value, 256 | })); 257 | } 258 | 259 | _updateAppearance() { 260 | // Hide or show the light-related affordances dependent on the appearance, 261 | // which can be "switch" or "toggle". 262 | const appearAsToggle = this.appearance === TOGGLE; 263 | this._lightRadio.hidden = appearAsToggle; 264 | this._lightLabel.hidden = appearAsToggle; 265 | this._darkRadio.hidden = appearAsToggle; 266 | this._darkLabel.hidden = appearAsToggle; 267 | this._darkCheckbox.hidden = !appearAsToggle; 268 | this._checkboxLabel.hidden = !appearAsToggle; 269 | } 270 | 271 | _updateRadios() { 272 | if (this.mode === LIGHT) { 273 | this._lightRadio.checked = true; 274 | } else { 275 | this._darkRadio.checked = true; 276 | } 277 | } 278 | 279 | _updateCheckbox() { 280 | if (this.mode === LIGHT) { 281 | this._checkboxLabel.style.setProperty(`--${NAME}-checkbox-icon`, 282 | `var(--${NAME}-light-icon,url("${DEFAULT_URL}moon.png"))`); 283 | this._checkboxLabel.textContent = this.light; 284 | if (!this.light) { 285 | this._checkboxLabel.ariaLabel = DARK; 286 | } 287 | this._darkCheckbox.checked = false; 288 | } else { 289 | this._checkboxLabel.style.setProperty(`--${NAME}-checkbox-icon`, 290 | `var(--${NAME}-dark-icon,url("${DEFAULT_URL}sun.png"))`); 291 | this._checkboxLabel.textContent = this.dark; 292 | if (!this.dark) { 293 | this._checkboxLabel.ariaLabel = LIGHT; 294 | } 295 | this._darkCheckbox.checked = true; 296 | } 297 | } 298 | 299 | _updateMode() { 300 | if (this.mode === LIGHT) { 301 | this._lightCSS.forEach((link) => { 302 | link.media = ALL; 303 | link.disabled = false; 304 | }); 305 | this._darkCSS.forEach((link) => { 306 | link.media = NOT_ALL; 307 | link.disabled = true; 308 | }); 309 | } else { 310 | this._darkCSS.forEach((link) => { 311 | link.media = ALL; 312 | link.disabled = false; 313 | }); 314 | this._lightCSS.forEach((link) => { 315 | link.media = NOT_ALL; 316 | link.disabled = true; 317 | }); 318 | } 319 | } 320 | 321 | _showPermanentAside() { 322 | this._permanentAside.style.visibility = 'visible'; 323 | setTimeout(() => { 324 | this._permanentAside.style.visibility = 'hidden'; 325 | }, 3000); 326 | } 327 | } 328 | 329 | customElements.define(NAME, DarkModeToggle); -------------------------------------------------------------------------------- /docs/static/dark-light/dark.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2019 Google LLC 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | :root { 18 | color-scheme: dark; /* stylelint-disable-line property-no-unknown */ 19 | 20 | --background-color: rgb(15 15 15); 21 | --text-color: rgb(240 240 240); 22 | --shadow-color: rgb(240 240 240 / 50%); 23 | --accent-color: rgb(0 0 240 / 50%); 24 | } 25 | 26 | img { 27 | filter: grayscale(50%); 28 | } 29 | 30 | .icon { 31 | filter: invert(100%); 32 | } 33 | 34 | a { 35 | color: yellow; 36 | } 37 | -------------------------------------------------------------------------------- /docs/static/dark-light/light.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2019 Google LLC 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | :root { 18 | color-scheme: light; /* stylelint-disable-line property-no-unknown */ 19 | 20 | --background-color: rgb(240 240 240); 21 | --text-color: rgb(15 15 15); 22 | --shadow-color: rgb(15 15 15 / 50%); 23 | --accent-color: rgb(240 0 0 / 50%); 24 | } 25 | -------------------------------------------------------------------------------- /docs/static/dark-light/moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwgps/1e5f9a81a58b594652b867703c4ea0bc09e179a4/docs/static/dark-light/moon.png -------------------------------------------------------------------------------- /docs/static/dark-light/moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | moon 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/static/dark-light/sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwgps/1e5f9a81a58b594652b867703c4ea0bc09e179a4/docs/static/dark-light/sun.png -------------------------------------------------------------------------------- /docs/static/dark-light/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/dark-light/unchecked.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/images/logo.drawio: -------------------------------------------------------------------------------- 1 | jZJNb8MgDIZ/TY6VkpB067Vd10nrqkk59IyCG1AhRJSMdL9+ZDH5UDVpJ/BjY+B9HZGd6g6GNvxDM5BRGrMuIi9RmibxU+aXntwHssnzAVRGMCyaQCG+IZxE2goGt0Wh1Vpa0SxhqesaSrtg1BjtlmUXLZe3NrSCB1CUVD7Ss2CWD/Q5jyf+BqLi4eYkxoyioRjBjVOm3QyRfUR2Rms77FS3A9mLF3QZzr3+kR0fZqC2/zlwWrEMNsodT6rdZpv3izwnK+zyRWWLHz66w2eBL7b3IIPRbc2g75REZOu4sFA0tOyzzhvvGbdKYvpmjb6Ocq09uejaorekj0ctYh88fiS8CoyFbobwYwfQCqy5+xLMkgynCqcsDbGbPEvWaASf+RV8pDgm1dh6UtJvUMwQTqb95majT/Y/ -------------------------------------------------------------------------------- /docs/static/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
LwGPS
LwGPS
-------------------------------------------------------------------------------- /docs/static/images/logo_tm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwgps/1e5f9a81a58b594652b867703c4ea0bc09e179a4/docs/static/images/logo_tm.png -------------------------------------------------------------------------------- /docs/static/images/logo_tm_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwgps/1e5f9a81a58b594652b867703c4ea0bc09e179a4/docs/static/images/logo_tm_full.png -------------------------------------------------------------------------------- /docs/user-manual/float-double.rst: -------------------------------------------------------------------------------- 1 | .. _float_double: 2 | 3 | Float/double precision 4 | ====================== 5 | 6 | With configuration of ``GSM_CFG_DOUBLE``, it is possible to enable ``double`` floating point precision. 7 | All floating point variables are then configured in *double precision*. 8 | 9 | When configuration is set to ``0``, floating point variables are configured in *single precision* format. 10 | 11 | .. note:: 12 | Single precision uses less memory in application. As a drawback, application may be a subject of data loss at latter digits. 13 | 14 | .. toctree:: 15 | :maxdepth: 2 -------------------------------------------------------------------------------- /docs/user-manual/how-it-works.rst: -------------------------------------------------------------------------------- 1 | .. _how_it_works: 2 | 3 | How it works 4 | ============ 5 | 6 | LwGPS parses raw data formatted as NMEA 0183 statements from GPS receivers. It supports up to ``4`` different statements: 7 | 8 | * ``GPGGA`` or ``GNGGA``: GPS fix data 9 | * ``GPGSA`` or ``GNGSA``: GPS active satellites and dillusion of position 10 | * ``GPGSV`` or ``GNGSV``: List of satellites in view zone 11 | * ``GPRMC`` or ``GNRMC``: Recommended minimum specific GPS/Transit data 12 | 13 | .. tip:: 14 | By changing different configuration options, it is possible to disable some statements. 15 | Check :ref:`api_lwgps_opt` for more information. 16 | 17 | Application must assure to properly receive data from GPS receiver. 18 | Usually GPS receivers communicate with host embedded system with UART protocol and output directly formatted NMEA 0183 statements. 19 | 20 | .. note:: 21 | Application must take care of properly receive data from GPS. 22 | 23 | Application must use :cpp:func:`lwgps_process` function for data processing. Function will: 24 | 25 | * Detect statement type, such as *GPGGA* or *GPGSV* 26 | * Parse all the terms of specific statement 27 | * Check valid CRC after each statement 28 | 29 | Programmer's model is as following: 30 | 31 | * Application receives data from GPS receiver 32 | * Application sends data to :cpp:func:`lwgps_process` function 33 | * Application uses processed data to display altitude, latitude, longitude, and other parameters 34 | 35 | Check :ref:`examples` for typical example 36 | 37 | .. toctree:: 38 | :maxdepth: 2 39 | -------------------------------------------------------------------------------- /docs/user-manual/index.rst: -------------------------------------------------------------------------------- 1 | .. _um: 2 | 3 | User manual 4 | =========== 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | how-it-works 10 | float-double 11 | thread-safety 12 | nmea-update-packet 13 | tests -------------------------------------------------------------------------------- /docs/user-manual/nmea-update-packet.rst: -------------------------------------------------------------------------------- 1 | .. _nmea_packet_update: 2 | 3 | NMEA data refresh 4 | ================= 5 | 6 | LwGPS is designed to parse standard NMEA output from GPS module. 7 | 8 | .. tip:: 9 | You can read more about `NMEA 0183 here `_. 10 | 11 | GPS module outputs several NMEA statements periodically, for instance once a second. In rare cases, outputs can be even every `100ms`. 12 | The common *problem* we try to solve is what happens if application tries to access GPS parsed data, while library processed only part of 13 | new NMEA statement. 14 | 15 | Depending on the application requirements, it is necessary to make sure data used by the application are all from the single NMEA output packet, 16 | and not split between different ones. Below are ``2`` examples of several statements GPS module will output every second. 17 | 18 | First statement at any given time: 19 | ``$GPRMC,183729,A,3907.356,N,12102.482,W,000.0,360.0,080301,015.5,E*6F`` 20 | ``$GPGGA,183730,3907.356,N,12102.482,W,1,05,1.6,646.4,M,-24.1,M,,*75`` 21 | ``$GPGSA,A,3,02,,,07,,09,24,26,,,,,1.6,1.6,1.0*3D`` 22 | ``$GPGSV,2,1,08,02,43,088,38,04,42,145,00,05,11,291,00,07,60,043,35*71`` 23 | ``$GPGSV,2,2,08,08,02,145,00,09,46,303,47,24,16,178,32,26,18,231,43*77`` 24 | 25 | New statement after one second: 26 | ``$GPRMC,183729,A,3907.356,N,12102.482,W,000.0,360.0,080301,015.5,E*6F`` 27 | ``$GPGGA,183730,3907.356,N,12102.482,W,1,05,1.6,646.4,M,-24.1,M,,*75`` 28 | ``$GPGSA,A,3,02,,,07,,09,24,26,,,,,1.6,1.6,1.0*3D`` 29 | ``$GPGSV,2,1,08,02,43,088,38,04,42,145,00,05,11,291,00,07,60,043,35*71`` 30 | ``$GPGSV,2,2,08,08,02,145,00,09,46,303,47,24,16,178,32,26,18,231,43*77`` 31 | 32 | If application manages to check GPS parsed data after first packet has been processed and second didn't arrive yet, there is no issue. 33 | Application parsed data are all belonging to single packet, at specific time. 34 | 35 | But what would happen if application starts using GPS data while ``GPGGA`` packet is being received for second time? 36 | 37 | * Application has new ``GPRMC`` information, from new packet 38 | * Application still keeps ``GPGGA``, ``GPGSA`` and ``GPGSV`` data from old packets 39 | 40 | This could be a major issue for some applications. Time, speed and position do not match anymore. 41 | 42 | Common approach 43 | *************** 44 | 45 | A common approach to this is to have a source of time in the application. 46 | A set of timeouts could determine if packet has just started, or has just been completed and is now fully filled with new data. 47 | 48 | An algorithm would be, assuming GPS sends packet data every ``1`` second: 49 | 50 | * When character comes, if time of previous character is greater than maximum time between ``2`` characters (let's say ``10ms``, even if this is a lot), this is probably start of new packet. 51 | * If new time is ``>10ms`` since last received character, it was probably the last character. 52 | * Application can now use new data 53 | * Application goes to *wait new packet mode* 54 | * Go back to step nr.1 55 | 56 | .. toctree:: 57 | :maxdepth: 2 58 | -------------------------------------------------------------------------------- /docs/user-manual/tests.rst: -------------------------------------------------------------------------------- 1 | .. _tests: 2 | 3 | Tests during development 4 | ======================== 5 | 6 | During the development, test check is performed to validate raw NMEA input data vs expected result. 7 | 8 | .. literalinclude:: ../../examples/test_code.c 9 | :language: c 10 | :linenos: 11 | :caption: Test code for development 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | -------------------------------------------------------------------------------- /docs/user-manual/thread-safety.rst: -------------------------------------------------------------------------------- 1 | .. _thread_safety: 2 | 3 | Thread safety 4 | ============= 5 | 6 | Library tends to be as simple as possible. 7 | No specific features have been implemented for thread safety. 8 | 9 | When library is using multi-thread environment and if multi threads tend to access to shared resources, 10 | user must resolve it with care, using mutual exclusion. 11 | 12 | .. tip:: 13 | When single thread is dedicated for GPS processing, no special mutual exclusion is necessary. 14 | 15 | .. toctree:: 16 | :maxdepth: 2 -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwgps/1e5f9a81a58b594652b867703c4ea0bc09e179a4/examples/CMakeLists.txt -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | # gps-nmea-parser examples and tests Makefile 2 | # (c) 2020 Sirio, Balmelli Analog & Digital 3 | 4 | TARGETS := \ 5 | example.exe \ 6 | example_stat.exe \ 7 | test_code.exe \ 8 | test_time.exe 9 | 10 | .PHONY: all clean test 11 | all: $(TARGETS) 12 | 13 | clean: 14 | @rm -fv $(TARGETS) 15 | 16 | test: $(TARGETS) 17 | @for tgt in $(TARGETS); do \ 18 | echo "\n--- $$tgt ---"; \ 19 | ./$$tgt; \ 20 | done 21 | 22 | CFLAGS += -Wall \ 23 | -DDEBUG=1 \ 24 | -I../lwgps/src/include \ 25 | -I./ 26 | 27 | example.exe: example.c 28 | 29 | example_stat.exe: CFLAGS += -DLWGPS_CFG_STATUS=1 30 | example_stat.exe: example_stat.c 31 | 32 | test_code.exe: ../lwgps/src/lwgps/lwgps.c test_code.c ../dev/VisualStudio/main.c 33 | 34 | test_time.exe: CFLAGS += \ 35 | -DLWGPS_CFG_STATEMENT_PUBX=1 \ 36 | -DLWGPS_CFG_STATEMENT_PUBX_TIME=1 37 | test_time.exe: ../lwgps/src/lwgps/lwgps.c test_time.c ../dev/VisualStudio/main.c 38 | 39 | $(TARGETS) : ../lwgps/src/lwgps/lwgps.c 40 | $(CC) -o $@ $(CFLAGS) $^ 41 | -------------------------------------------------------------------------------- /examples/example.c: -------------------------------------------------------------------------------- 1 | /** 2 | * This example uses direct processing function 3 | * to process dummy NMEA data from GPS receiver 4 | */ 5 | #include 6 | #include 7 | #include "lwgps/lwgps.h" 8 | 9 | /* GPS handle */ 10 | lwgps_t hgps; 11 | 12 | /** 13 | * \brief Dummy data from GPS receiver 14 | */ 15 | const char gps_rx_data[] = "" 16 | "$GPRMC,183729,A,3907.356,N,12102.482,W,000.0,360.0,080301,015.5,E*6F\r\n" 17 | "$GPRMB,A,,,,,,,,,,,,V*71\r\n" 18 | "$GPGGA,183730,3907.356,N,12102.482,W,1,05,1.6,646.4,M,-24.1,M,,*75\r\n" 19 | "$GPGSA,A,3,02,,,07,,09,24,26,,,,,1.6,1.6,1.0*3D\r\n" 20 | "$GPGSV,2,1,08,02,43,088,38,04,42,145,00,05,11,291,00,07,60,043,35*71\r\n" 21 | "$GPGSV,2,2,08,08,02,145,00,09,46,303,47,24,16,178,32,26,18,231,43*77\r\n" 22 | "$PGRME,22.0,M,52.9,M,51.0,M*14\r\n" 23 | "$GPGLL,3907.360,N,12102.481,W,183730,A*33\r\n" 24 | "$PGRMZ,2062,f,3*2D\r\n" 25 | "$PGRMM,WGS84*06\r\n" 26 | "$GPBOD,,T,,M,,*47\r\n" 27 | "$GPRTE,1,1,c,0*07\r\n" 28 | "$GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67\r\n" 29 | "$GPRMB,A,,,,,,,,,,,,V*71\r\n"; 30 | 31 | int 32 | main() { 33 | /* Init GPS */ 34 | lwgps_init(&hgps); 35 | 36 | /* Process all input data */ 37 | lwgps_process(&hgps, gps_rx_data, strlen(gps_rx_data)); 38 | 39 | /* Print messages */ 40 | printf("Valid status: %d\r\n", hgps.is_valid); 41 | printf("Latitude: %f degrees\r\n", hgps.latitude); 42 | printf("Longitude: %f degrees\r\n", hgps.longitude); 43 | printf("Altitude: %f meters\r\n", hgps.altitude); 44 | 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /examples/example_buff.c: -------------------------------------------------------------------------------- 1 | #include "lwgps/lwgps.h" 2 | #include "lwrb/lwrb.h" 3 | #include 4 | 5 | /* GPS handle */ 6 | lwgps_t hgps; 7 | 8 | /* GPS buffer */ 9 | lwrb_t hgps_buff; 10 | uint8_t hgps_buff_data[12]; 11 | 12 | /** 13 | * \brief Dummy data from GPS receiver 14 | * \note This data are used to fake UART receive event on microcontroller 15 | */ 16 | const char 17 | gps_rx_data[] = "" 18 | "$GPRMC,183729,A,3907.356,N,12102.482,W,000.0,360.0,080301,015.5,E*6F\r\n" 19 | "$GPRMB,A,,,,,,,,,,,,V*71\r\n" 20 | "$GPGGA,183730,3907.356,N,12102.482,W,1,05,1.6,646.4,M,-24.1,M,,*75\r\n" 21 | "$GPGSA,A,3,02,,,07,,09,24,26,,,,,1.6,1.6,1.0*3D\r\n" 22 | "$GPGSV,2,1,08,02,43,088,38,04,42,145,00,05,11,291,00,07,60,043,35*71\r\n" 23 | "$GPGSV,2,2,08,08,02,145,00,09,46,303,47,24,16,178,32,26,18,231,43*77\r\n" 24 | "$PGRME,22.0,M,52.9,M,51.0,M*14\r\n" 25 | "$GPGLL,3907.360,N,12102.481,W,183730,A*33\r\n" 26 | "$PGRMZ,2062,f,3*2D\r\n" 27 | "$PGRMM,WGS84*06\r\n" 28 | "$GPBOD,,T,,M,,*47\r\n" 29 | "$GPRTE,1,1,c,0*07\r\n" 30 | "$GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67\r\n" 31 | "$GPRMB,A,,,,,,,,,,,,V*71\r\n"; 32 | static size_t write_ptr; 33 | static void uart_irqhandler(void); 34 | 35 | int 36 | main() { 37 | uint8_t rx; 38 | 39 | /* Init GPS */ 40 | lwgps_init(&hgps); 41 | 42 | /* Create buffer for received data */ 43 | lwrb_init(&hgps_buff, hgps_buff_data, sizeof(hgps_buff_data)); 44 | 45 | while (1) { 46 | /* Add new character to buffer */ 47 | /* Fake UART interrupt handler on host microcontroller */ 48 | uart_irqhandler(); 49 | 50 | /* Process all input data */ 51 | /* Read from buffer byte-by-byte and call processing function */ 52 | if (lwrb_get_full(&hgps_buff)) { /* Check if anything in buffer now */ 53 | while (lwrb_read(&hgps_buff, &rx, 1) == 1) { 54 | lwgps_process(&hgps, &rx, 1); /* Process byte-by-byte */ 55 | } 56 | } else { 57 | /* Print all data after successful processing */ 58 | printf("Latitude: %f degrees\r\n", hgps.latitude); 59 | printf("Longitude: %f degrees\r\n", hgps.longitude); 60 | printf("Altitude: %f meters\r\n", hgps.altitude); 61 | break; 62 | } 63 | } 64 | 65 | return 0; 66 | } 67 | 68 | /** 69 | * \brief Interrupt handler routing for UART received character 70 | * \note This is not real MCU, it is software method, called from main 71 | */ 72 | static void 73 | uart_irqhandler(void) { 74 | /* Make interrupt handler as fast as possible */ 75 | /* Only write to received buffer and process later */ 76 | if (write_ptr < strlen(gps_rx_data)) { 77 | /* Write to buffer only */ 78 | lwrb_write(&hgps_buff, &gps_rx_data[write_ptr], 1); 79 | ++write_ptr; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /examples/example_dist_bear.c: -------------------------------------------------------------------------------- 1 | #include "lwgps/lwgps.h" 2 | 3 | /* Distance and bearing results */ 4 | lwgps_float_t dist, bear; 5 | 6 | /* New York coordinates */ 7 | lwgps_float_t lat1 = 40.685721; 8 | lwgps_float_t lon1 = -73.820465; 9 | 10 | /* Munich coordinates */ 11 | lwgps_float_t lat2 = 48.150906; 12 | lwgps_float_t lon2 = 11.554176; 13 | 14 | /* Go from New York to Munich */ 15 | /* Calculate distance and bearing related to north */ 16 | lwgps_distance_bearing(lat1, lon1, lat2, lon2, &dist, &bear); 17 | printf("Distance: %f meters\r\n", (float)dist); 18 | printf("Initial bearing: %f degrees\r\n", (float)bear); 19 | 20 | /* Go from Munich to New York */ 21 | /* Calculate distance and bearing related to north */ 22 | lwgps_distance_bearing(lat2, lon2, lat1, lon1, &dist, &bear); 23 | printf("Distance: %f meters\r\n", (float)dist); 24 | printf("Initial bearing: %f degrees\r\n", (float)bear); -------------------------------------------------------------------------------- /examples/example_stat.c: -------------------------------------------------------------------------------- 1 | /** 2 | * This example tests the callback functionality of lwgps_process() 3 | * when the LWGPS_CFG_STATUS flag is set. 4 | */ 5 | #include 6 | #include 7 | #include "lwgps/lwgps.h" 8 | 9 | #if !LWGPS_CFG_STATUS 10 | #error "this test must be compiled with -DLWGPS_CFG_STATUS=1" 11 | #endif /* !LWGPS_CFG_STATUS */ 12 | 13 | /* GPS handle */ 14 | lwgps_t hgps; 15 | 16 | /** 17 | * \brief Dummy data from GPS receiver 18 | */ 19 | const char 20 | gps_rx_data[] = "" 21 | "$GPRMC,183729,A,3907.356,N,12102.482,W,000.0,360.0,080301,015.5,E*6F\r\n" 22 | "$GPRMB,A,,,,,,,,,,,,V*71\r\n" 23 | "$GPGGA,183730,3907.356,N,12102.482,W,1,05,1.6,646.4,M,-24.1,M,,*75\r\n" 24 | "$GPGSA,A,3,02,,,07,,09,24,26,,,,,1.6,1.6,1.0*3D\r\n" 25 | "$GPGSV,2,1,08,02,43,088,38,04,42,145,00,05,11,291,00,07,60,043,35*71\r\n" 26 | "$GPGSV,2,2,08,08,02,145,00,09,46,303,47,24,16,178,32,26,18,231,43*77\r\n" 27 | "$PGRME,22.0,M,52.9,M,51.0,M*14\r\n" 28 | "$GPGLL,3907.360,N,12102.481,W,183730,A*33\r\n" 29 | "$PGRMZ,2062,f,3*2D\r\n" 30 | "$PGRMM,WGS84*06\r\n" 31 | "$GPBOD,,T,,M,,*47\r\n" 32 | "$GPRTE,1,1,c,0*07\r\n" 33 | "$GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67\r\n" 34 | "$GPRMB,A,,,,,,,,,,,,V*71\r\n"; 35 | 36 | const lwgps_statement_t expected[] = { 37 | STAT_RMC, 38 | STAT_UNKNOWN, 39 | STAT_GGA, 40 | STAT_GSA, 41 | STAT_GSV, 42 | STAT_GSV, 43 | STAT_UNKNOWN, 44 | STAT_UNKNOWN, 45 | STAT_UNKNOWN, 46 | STAT_CHECKSUM_FAIL, 47 | STAT_UNKNOWN, 48 | STAT_UNKNOWN, 49 | STAT_RMC, 50 | STAT_UNKNOWN 51 | }; 52 | 53 | static int err_cnt; 54 | 55 | void 56 | callback(lwgps_statement_t res) { 57 | static int i; 58 | 59 | if (res != expected[i]) { 60 | printf("failed i %d, expected res %d but received %d\n", 61 | i, expected[i], res); 62 | ++err_cnt; 63 | } 64 | 65 | ++i; 66 | } 67 | 68 | int 69 | main() { 70 | /* Init GPS */ 71 | lwgps_init(&hgps); 72 | 73 | /* Process all input data */ 74 | lwgps_process(&hgps, gps_rx_data, strlen(gps_rx_data), callback); 75 | 76 | return err_cnt; 77 | } 78 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LwGPS", 3 | "version": "2.2.0", 4 | "description": "Lightweight GPS NMEA statement parser for embedded systems", 5 | "keywords": "lwgps, global, position, satellite, glonass, gps, altitude, embedded, platform, independent", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/MaJerle/lwgps.git" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Tilen Majerle", 13 | "email": "tilen@majerle.eu", 14 | "url": "https://majerle.eu" 15 | } 16 | ], 17 | "license": "MIT", 18 | "homepage": "https://github.com/MaJerle/lwgps", 19 | "dependencies": {}, 20 | "frameworks": "*", 21 | "platforms": "*", 22 | "export": { 23 | "exclude": [ 24 | ".github", 25 | "dev", 26 | "docs", 27 | "**/.vs", 28 | "**/Debug", 29 | "build", 30 | "**/build" 31 | ] 32 | }, 33 | "build": { 34 | "includeDir": "lwgps/src/include" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lwgps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | include(${CMAKE_CURRENT_LIST_DIR}/library.cmake) -------------------------------------------------------------------------------- /lwgps/library.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # LIB_PREFIX: LWGPS 3 | # 4 | # This file provides set of variables for end user 5 | # and also generates one (or more) libraries, that can be added to the project using target_link_libraries(...) 6 | # 7 | # Before this file is included to the root CMakeLists file (using include() function), user can set some variables: 8 | # 9 | # LWGPS_OPTS_FILE: If defined, it is the path to the user options file. If not defined, one will be generated for you automatically 10 | # LWGPS_COMPILE_OPTIONS: If defined, it provide compiler options for generated library. 11 | # LWGPS_COMPILE_DEFINITIONS: If defined, it provides "-D" definitions to the library build 12 | # 13 | 14 | # Custom include directory 15 | set(LWGPS_CUSTOM_INC_DIR ${CMAKE_CURRENT_BINARY_DIR}/lib_inc) 16 | 17 | # Library core sources 18 | set(lwgps_core_SRCS 19 | ${CMAKE_CURRENT_LIST_DIR}/src/lwgps/lwgps.c 20 | ) 21 | 22 | # Setup include directories 23 | set(lwgps_include_DIRS 24 | ${CMAKE_CURRENT_LIST_DIR}/src/include 25 | ${LWGPS_CUSTOM_INC_DIR} 26 | ) 27 | 28 | # Register library to the system 29 | add_library(lwgps) 30 | target_sources(lwgps PRIVATE ${lwgps_core_SRCS}) 31 | target_include_directories(lwgps PUBLIC ${lwgps_include_DIRS}) 32 | target_compile_options(lwgps PRIVATE ${LWGPS_COMPILE_OPTIONS}) 33 | target_compile_definitions(lwgps PRIVATE ${LWGPS_COMPILE_DEFINITIONS}) 34 | 35 | # Create config file if user didn't provide one info himself 36 | if(NOT LWGPS_OPTS_FILE) 37 | message(STATUS "Using default lwgps_opts.h file") 38 | set(LWGPS_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/src/include/lwgps/lwgps_opts_template.h) 39 | else() 40 | message(STATUS "Using custom lwgps_opts.h file from ${LWGPS_OPTS_FILE}") 41 | endif() 42 | configure_file(${LWGPS_OPTS_FILE} ${LWGPS_CUSTOM_INC_DIR}/lwgps_opts.h COPYONLY) 43 | 44 | -------------------------------------------------------------------------------- /lwgps/src/include/lwgps/lwgps.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwgps.h 3 | * \brief GPS main file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwGPS - Lightweight GPS NMEA parser library. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v2.2.0 33 | */ 34 | #ifndef LWGPS_HDR_H 35 | #define LWGPS_HDR_H 36 | 37 | #include 38 | #include 39 | #include 40 | #include "lwgps/lwgps_opt.h" 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif /* __cplusplus */ 45 | 46 | /** 47 | * \defgroup LWGPS Lightweight GPS NMEA parser 48 | * \brief Lightweight GPS NMEA parser 49 | * \{ 50 | */ 51 | 52 | /** 53 | * \brief GPS float definition, can be either `float` or `double` 54 | * \note Check for \ref LWGPS_CFG_DOUBLE configuration 55 | */ 56 | #if LWGPS_CFG_DOUBLE || __DOXYGEN__ 57 | typedef double lwgps_float_t; 58 | #else 59 | typedef float lwgps_float_t; 60 | #endif 61 | 62 | /** 63 | * \brief Satellite descriptor 64 | */ 65 | typedef struct { 66 | uint8_t num; /*!< Satellite number */ 67 | uint8_t elevation; /*!< Elevation value */ 68 | uint16_t azimuth; /*!< Azimuth in degrees */ 69 | uint8_t snr; /*!< Signal-to-noise ratio */ 70 | } lwgps_sat_t; 71 | 72 | /** 73 | * \brief ENUM of possible GPS statements parsed 74 | */ 75 | typedef enum { 76 | STAT_UNKNOWN = 0, /*!< Unknown NMEA statement */ 77 | STAT_GGA = 1, /*!< GPGGA statement */ 78 | STAT_GSA = 2, /*!< GPGSA statement */ 79 | STAT_GSV = 3, /*!< GPGSV statement */ 80 | STAT_RMC = 4, /*!< GPRMC statement */ 81 | STAT_UBX = 5, /*!< UBX statement (uBlox specific) */ 82 | STAT_UBX_TIME = 6, /*!< UBX TIME statement (uBlox specific) */ 83 | STAT_CHECKSUM_FAIL = UINT8_MAX /*!< Special case, used when checksum fails */ 84 | } lwgps_statement_t; 85 | 86 | /** 87 | * \brief GPS main structure 88 | */ 89 | typedef struct { 90 | #if LWGPS_CFG_STATEMENT_GPGGA || __DOXYGEN__ 91 | /* Information related to GPGGA statement */ 92 | lwgps_float_t latitude; /*!< Latitude in units of degrees */ 93 | lwgps_float_t longitude; /*!< Longitude in units of degrees */ 94 | lwgps_float_t altitude; /*!< Altitude in units of meters */ 95 | lwgps_float_t geo_sep; /*!< Geoid separation in units of meters */ 96 | uint8_t sats_in_use; /*!< Number of satellites in use */ 97 | uint8_t fix; /*!< Fix status. `0` = invalid, `1` = GPS fix, `2` = DGPS fix, `3` = PPS fix */ 98 | uint8_t hours; /*!< Hours in UTC */ 99 | uint8_t minutes; /*!< Minutes in UTC */ 100 | uint8_t seconds; /*!< Seconds in UTC */ 101 | uint8_t time_valid; /*!< Flag whether time data has been provided by GPS modem in a valid `6` digit format */ 102 | lwgps_float_t dgps_age; /*!< Age of DGPS correction data (in seconds) */ 103 | #endif /* LWGPS_CFG_STATEMENT_GPGGA || __DOXYGEN__ */ 104 | 105 | #if LWGPS_CFG_STATEMENT_GPGSA || __DOXYGEN__ 106 | /* Information related to GPGSA statement */ 107 | lwgps_float_t dop_h; /*!< Dolution of precision, horizontal */ 108 | lwgps_float_t dop_v; /*!< Dolution of precision, vertical */ 109 | lwgps_float_t dop_p; /*!< Dolution of precision, position */ 110 | uint8_t fix_mode; /*!< Fix mode. `1` = NO fix, `2` = 2D fix, `3` = 3D fix */ 111 | uint8_t satellites_ids[12]; /*!< List of satellite IDs in use. Valid range is `0` to `sats_in_use` */ 112 | #endif /* LWGPS_CFG_STATEMENT_GPGSA || __DOXYGEN__ */ 113 | 114 | #if LWGPS_CFG_STATEMENT_GPGSV || __DOXYGEN__ 115 | /* Information related to GPGSV statement */ 116 | uint8_t sats_in_view; /*!< Number of satellites in view */ 117 | #if LWGPS_CFG_STATEMENT_GPGSV_SAT_DET || __DOXYGEN__ 118 | lwgps_sat_t sats_in_view_desc[12]; 119 | #endif /* LWGPS_CFG_STATEMENT_GPGSV_SAT_DET || __DOXYGEN__ */ 120 | #endif /* LWGPS_CFG_STATEMENT_GPGSV || __DOXYGEN__ */ 121 | 122 | #if LWGPS_CFG_STATEMENT_GPRMC || __DOXYGEN__ 123 | /* Information related to GPRMC statement */ 124 | uint8_t is_valid; /*!< GPS valid status */ 125 | lwgps_float_t speed; /*!< Ground speed in knots */ 126 | lwgps_float_t course; /*!< Ground coarse */ 127 | lwgps_float_t variation; /*!< Magnetic variation */ 128 | uint8_t date; /*!< Fix date */ 129 | uint8_t month; /*!< Fix month */ 130 | uint8_t year; /*!< Fix year */ 131 | uint8_t date_valid; /*!< Flag whether date data has been provided by GPS modem in a valid `6` digit format */ 132 | #endif /* LWGPS_CFG_STATEMENT_GPRMC || __DOXYGEN__ */ 133 | 134 | #if LWGPS_CFG_STATEMENT_PUBX_TIME || __DOXYGEN__ 135 | #if !LWGPS_CFG_STATEMENT_GPGGA && !__DOXYGEN__ 136 | /* rely on time fields from GPGGA if possible */ 137 | uint8_t hours; 138 | uint8_t minutes; 139 | uint8_t seconds; 140 | #endif /* !LWGPS_CFG_STATEMENT_GPGGA && !__DOXYGEN__ */ 141 | #if !LWGPS_CFG_STATEMENT_GPRMC && !__DOXYGEN__ 142 | /* rely on date fields from GPRMC if possible */ 143 | uint8_t date; 144 | uint8_t month; 145 | uint8_t year; 146 | #endif /* !LWGPS_CFG_STATEMENT_GPRMC && !__DOXYGEN__ */ 147 | /* fields only available in PUBX_TIME */ 148 | lwgps_float_t utc_tow; /*!< UTC TimeOfWeek, eg 113851.00 */ 149 | uint16_t utc_wk; /*!< UTC week number, continues beyond 1023 */ 150 | uint8_t leap_sec; /*!< UTC leap seconds; UTC + leap_sec = TAI */ 151 | uint32_t clk_bias; /*!< Receiver clock bias, eg 1930035 */ 152 | lwgps_float_t clk_drift; /*!< Receiver clock drift, eg -2660.664 */ 153 | uint32_t tp_gran; /*!< Time pulse granularity, eg 43 */ 154 | #endif /* LWGPS_CFG_STATEMENT_PUBX_TIME || __DOXYGEN__ */ 155 | 156 | #if !__DOXYGEN__ 157 | struct { 158 | lwgps_statement_t stat; /*!< Statement index */ 159 | char term_str[13]; /*!< Current term in string format */ 160 | uint8_t term_pos; /*!< Current index position in term */ 161 | uint8_t term_num; /*!< Current term number */ 162 | 163 | uint8_t star; /*!< Star detected flag */ 164 | 165 | #if LWGPS_CFG_CRC 166 | uint8_t crc_calc; /*!< Calculated CRC string */ 167 | #endif /* LWGPS_CFG_CRC */ 168 | 169 | union { 170 | uint8_t dummy; /*!< Dummy byte */ 171 | #if LWGPS_CFG_STATEMENT_GPGGA 172 | struct { 173 | lwgps_float_t latitude; /*!< GPS latitude position in degrees */ 174 | lwgps_float_t longitude; /*!< GPS longitude position in degrees */ 175 | lwgps_float_t altitude; /*!< GPS altitude in meters */ 176 | lwgps_float_t geo_sep; /*!< Geoid separation in units of meters */ 177 | uint8_t sats_in_use; /*!< Number of satellites currently in use */ 178 | uint8_t fix; /*!< Type of current fix, `0` = Invalid, `1` = GPS fix, `2` = Differential GPS fix */ 179 | uint8_t hours; /*!< Current UTC hours */ 180 | uint8_t minutes; /*!< Current UTC minutes */ 181 | uint8_t seconds; /*!< Current UTC seconds */ 182 | uint8_t 183 | time_valid; /*!< Flag whether time data has been provided by GPS modem in a valid `6` digit format */ 184 | lwgps_float_t dgps_age; /*!< Age of DGPS correction data (in seconds) */ 185 | } gga; /*!< GPGGA message */ 186 | #endif /* LWGPS_CFG_STATEMENT_GPGGA */ 187 | #if LWGPS_CFG_STATEMENT_GPGSA 188 | struct { 189 | lwgps_float_t dop_h; /*!< Horizontal dilution of precision */ 190 | lwgps_float_t dop_v; /*!< Vertical dilution of precision */ 191 | lwgps_float_t dop_p; /*!< Position dilution of precision */ 192 | uint8_t fix_mode; /*!< Fix mode, `1` = No fix, `2` = 2D fix, `3` = 3D fix */ 193 | uint8_t satellites_ids[12]; /*!< IDs of satellites currently in use */ 194 | } gsa; /*!< GPGSA message */ 195 | #endif /* LWGPS_CFG_STATEMENT_GPGSA */ 196 | #if LWGPS_CFG_STATEMENT_GPGSV 197 | struct { 198 | uint8_t sats_in_view; /*!< Number of stallites in view */ 199 | uint8_t stat_num; /*!< Satellite line number during parsing GPGSV data */ 200 | } gsv; /*!< GPGSV message */ 201 | #endif /* LWGPS_CFG_STATEMENT_GPGSV */ 202 | #if LWGPS_CFG_STATEMENT_GPRMC 203 | struct { 204 | uint8_t is_valid; /*!< Status whether GPS status is valid or not */ 205 | uint8_t date; /*!< Current UTC date */ 206 | uint8_t month; /*!< Current UTC month */ 207 | uint8_t year; /*!< Current UTC year */ 208 | uint8_t 209 | date_valid; /*!< Flag whether time data has been provided by GPS modem in a valid `6` digit format */ 210 | lwgps_float_t speed; /*!< Current spead over the ground in knots */ 211 | lwgps_float_t course; /*!< Current course over ground */ 212 | lwgps_float_t variation; /*!< Current magnetic variation in degrees */ 213 | } rmc; /*!< GPRMC message */ 214 | #endif /* LWGPS_CFG_STATEMENT_GPRMC */ 215 | #if LWGPS_CFG_STATEMENT_PUBX_TIME 216 | struct { 217 | uint8_t hours; /*!< Current UTC hours */ 218 | uint8_t minutes; /*!< Current UTC minutes */ 219 | uint8_t seconds; /*!< Current UTC seconds */ 220 | uint8_t date; /*!< Current UTC date */ 221 | uint8_t month; /*!< Current UTC month */ 222 | uint8_t year; /*!< Current UTC year */ 223 | lwgps_float_t utc_tow; /*!< UTC TimeOfWeek, eg 113851.00 */ 224 | uint16_t utc_wk; /*!< UTC week number, continues beyond 1023 */ 225 | uint8_t leap_sec; /*!< UTC leap seconds; UTC + leap_sec = TAI */ 226 | uint32_t clk_bias; /*!< Receiver clock bias, eg 1930035 */ 227 | lwgps_float_t clk_drift; /*!< Receiver clock drift, eg -2660.664 */ 228 | uint32_t tp_gran; /*!< Time pulse granularity, eg 43 */ 229 | } time; /*!< PUBX TIME message */ 230 | #endif /* LWGPS_CFG_STATEMENT_PUBX_TIME */ 231 | } data; /*!< Union with data for each information */ 232 | } p; /*!< Structure with private data */ 233 | #endif /* !__DOXYGEN__ */ 234 | } lwgps_t; 235 | 236 | /** 237 | * \brief List of optional speed transformation from GPS values (in knots) 238 | */ 239 | typedef enum { 240 | /* Metric values */ 241 | LWGPS_SPEED_KPS, /*!< Kilometers per second */ 242 | LWGPS_SPEED_KPH, /*!< Kilometers per hour */ 243 | LWGPS_SPEED_MPS, /*!< Meters per second */ 244 | LWGPS_SPEED_MPM, /*!< Meters per minute */ 245 | 246 | /* Imperial values */ 247 | LWGPS_SPEED_MIPS, /*!< Miles per second */ 248 | LWGPS_SPEED_MPH, /*!< Miles per hour */ 249 | LWGPS_SPEED_FPS, /*!< Foots per second */ 250 | LWGPS_SPEED_FPM, /*!< Foots per minute */ 251 | 252 | /* Optimized for runners/joggers */ 253 | LWGPS_SPEED_MPK, /*!< Minutes per kilometer */ 254 | LWGPS_SPEED_SPK, /*!< Seconds per kilometer */ 255 | LWGPS_SPEED_SP100M, /*!< Seconds per 100 meters */ 256 | LWGPS_SPEED_MIPM, /*!< Minutes per mile */ 257 | LWGPS_SPEED_SPM, /*!< Seconds per mile */ 258 | LWGPS_SPEED_SP100Y, /*!< Seconds per 100 yards */ 259 | 260 | /* Nautical values */ 261 | LWGPS_SPEED_SMPH, /*!< Sea miles per hour */ 262 | } lwgps_speed_t; 263 | 264 | #define lwgps_speed_kps LWGPS_SPEED_KPS /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 265 | #define lwgps_speed_kph LWGPS_SPEED_KPH /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 266 | #define lwgps_speed_mps LWGPS_SPEED_MPS /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 267 | #define lwgps_speed_mpm LWGPS_SPEED_MPM /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 268 | #define lwgps_speed_mips LWGPS_SPEED_MIPS /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 269 | #define lwgps_speed_mph LWGPS_SPEED_MPH /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 270 | #define lwgps_speed_fps LWGPS_SPEED_FPS /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 271 | #define lwgps_speed_fpm LWGPS_SPEED_FPM /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 272 | #define lwgps_speed_mpk LWGPS_SPEED_MPK /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 273 | #define lwgps_speed_spk LWGPS_SPEED_SPK /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 274 | #define lwgps_speed_sp100m LWGPS_SPEED_SP100M /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 275 | #define lwgps_speed_mipm LWGPS_SPEED_MIPM /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 276 | #define lwgps_speed_spm LWGPS_SPEED_SPM /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 277 | #define lwgps_speed_sp100y LWGPS_SPEED_SP100Y /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 278 | #define lwgps_speed_smph LWGPS_SPEED_SMPH /*!< Backward compatibility. \deprecated Use \ref lwgps_speed_t instead */ 279 | 280 | /** 281 | * \brief Signature for caller-suplied callback function from gps_process 282 | * \param[in] res: statement type of recently parsed statement 283 | */ 284 | typedef void (*lwgps_process_fn)(lwgps_statement_t res); 285 | 286 | /** 287 | * \brief Check if current GPS data contain valid signal 288 | * \note \ref LWGPS_CFG_STATEMENT_GPRMC must be enabled and `GPRMC` statement must be sent from GPS receiver 289 | * \param[in] _gh: GPS handle 290 | * \return `1` on success, `0` otherwise 291 | */ 292 | #if LWGPS_CFG_STATEMENT_GPRMC || __DOXYGEN__ 293 | #define lwgps_is_valid(_gh) ((_gh)->is_valid) 294 | #else 295 | #define lwgps_is_valid(_gh) (0) 296 | #endif /* LWGPS_CFG_STATEMENT_GPRMC || __DOXYGEN__ */ 297 | 298 | uint8_t lwgps_init(lwgps_t* gh); 299 | #if LWGPS_CFG_STATUS || __DOXYGEN__ 300 | uint8_t lwgps_process(lwgps_t* gh, const void* data, size_t len, lwgps_process_fn evt_fn); 301 | #else /* LWGPS_CFG_STATUS */ 302 | uint8_t lwgps_process(lwgps_t* gh, const void* data, size_t len); 303 | #endif /* !LWGPS_CFG_STATUS */ 304 | uint8_t lwgps_distance_bearing(lwgps_float_t las, lwgps_float_t los, lwgps_float_t lae, lwgps_float_t loe, 305 | lwgps_float_t* d, lwgps_float_t* b); 306 | lwgps_float_t lwgps_to_speed(lwgps_float_t sik, lwgps_speed_t ts); 307 | 308 | /** 309 | * \} 310 | */ 311 | 312 | #ifdef __cplusplus 313 | } 314 | #endif /* __cplusplus */ 315 | 316 | #endif /* LWGPS_HDR_H */ 317 | -------------------------------------------------------------------------------- /lwgps/src/include/lwgps/lwgps.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwgps.hpp 3 | * \brief C++ wrapper for LwGPS 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2023 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwGPS - Lightweight GPS NMEA parser library. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v2.2.0 33 | */ 34 | #ifndef LWGPS_HDR_HPP 35 | #define LWGPS_HDR_HPP 36 | 37 | #include "lwgps/lwgps.h" 38 | 39 | namespace Lwgps { 40 | class Lwgps { 41 | private: 42 | lwgps_t m_hgps; 43 | 44 | #if LWGPS_CFG_STATUS || __DOXYGEN__ 45 | lwgps_process_fn m_procfn; 46 | #endif /* LWGPS_CFG_STATUS || __DOXYGEN__ */ 47 | 48 | public: 49 | Lwgps(const Lwgps& other) = delete; /* No copy constructor */ 50 | Lwgps& operator=(const Lwgps& other) = delete; /* No copy assignment */ 51 | Lwgps(const Lwgps&& other) = delete; /* No move constructor */ 52 | Lwgps& operator=(Lwgps&& other) = delete; /* No move assignment */ 53 | 54 | Lwgps() { /* Constructor */ 55 | lwgps_init(&m_hgps); 56 | #if LWGPS_CFG_STATUS 57 | m_procfn = nullptr; 58 | #endif /* LWGPS_CFG_STATUS */ 59 | } 60 | 61 | #if LWGPS_CFG_STATUS || __DOXYGEN__ 62 | /** 63 | * \brief Set processing callback function 64 | * 65 | * \param procfn 66 | */ 67 | void 68 | set_process_fn(lwgps_process_fn procfn) { 69 | this->m_procfn = procfn; 70 | } 71 | #endif /* LWGPS_CFG_STATUS || __DOXYGEN__ */ 72 | 73 | /** 74 | * \brief Process NMEA data from GPS receiver 75 | * \param[in] data: Received data 76 | * \param[in] len: Number of bytes to process 77 | * \return `1` on success, `0` otherwise 78 | */ 79 | uint8_t 80 | process(const void* data, size_t len) { 81 | return lwgps_process(&m_hgps, data, len 82 | #if LWGPS_CFG_STATUS 83 | , 84 | m_procfn 85 | #endif /* LWGPS_CFG_STATUS */ 86 | ); 87 | } 88 | 89 | #if LWESP_CFG_DISTANCE_BEARING || __DOXYGEN__ 90 | 91 | /** 92 | * \brief Calculate distance and bearing between `2` latitude and longitude coordinates 93 | * \param[in] las: Latitude start coordinate, in units of degrees 94 | * \param[in] los: Longitude start coordinate, in units of degrees 95 | * \param[in] lae: Latitude end coordinate, in units of degrees 96 | * \param[in] loe: Longitude end coordinate, in units of degrees 97 | * \param[out] d: Pointer to output distance in units of meters 98 | * \param[out] b: Pointer to output bearing between start and end coordinate in relation to north in units of degrees 99 | * \return `1` on success, `0` otherwise 100 | */ 101 | static uint8_t 102 | distance_bearing(lwgps_float_t las, lwgps_float_t los, lwgps_float_t lae, lwgps_float_t loe, lwgps_float_t* d, 103 | lwgps_float_t* b) { 104 | return lwgps_distance_bearing(las, los, lae, loe, d, b); 105 | } 106 | #endif /* LWESP_CFG_DISTANCE_BEARING || __DOXYGEN__ */ 107 | 108 | /** 109 | * \brief Convert NMEA GPS speed (in knots = nautical mile per hour) to different speed format 110 | * \param[in] sik: Speed in knots, received from GPS NMEA statement 111 | * \param[in] ts: Target speed to convert to from knots 112 | * \return Speed calculated from knots 113 | */ 114 | static lwgps_float_t 115 | to_speed(lwgps_float_t sik, lwgps_speed_t ts) { 116 | return lwgps_to_speed(sik, ts); 117 | } 118 | 119 | ~Lwgps() { /* Destructor */ 120 | } 121 | }; 122 | }; // namespace Lwgps 123 | 124 | #endif /* LWGPS_HDR_HPP */ 125 | -------------------------------------------------------------------------------- /lwgps/src/include/lwgps/lwgps_opt.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwgps_opt.h 3 | * \brief LwGPS options 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwGPS - Lightweight GPS NMEA parser library. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: $2.2.0$ 33 | */ 34 | #ifndef LWGPS_OPT_HDR_H 35 | #define LWGPS_OPT_HDR_H 36 | 37 | /* Uncomment to ignore user options (or set macro in compiler flags) */ 38 | /* #define LWGPS_IGNORE_USER_OPTS */ 39 | 40 | /* Include application options */ 41 | #ifndef LWGPS_IGNORE_USER_OPTS 42 | #include "lwgps_opts.h" 43 | #endif /* LWGPS_IGNORE_USER_OPTS */ 44 | 45 | #ifdef __cplusplus 46 | extern "C" { 47 | #endif /* __cplusplus */ 48 | 49 | /** 50 | * \defgroup LWGPS_OPT Configuration 51 | * \brief Default configuration setup 52 | * \{ 53 | */ 54 | 55 | /** 56 | * \brief Enables `1` or disables `0` `double precision` for floating point 57 | * values such as latitude, longitude, altitude. 58 | * 59 | * `double` is used as variable type when enabled, `float` when disabled. 60 | */ 61 | #ifndef LWGPS_CFG_DOUBLE 62 | #define LWGPS_CFG_DOUBLE 1 63 | #endif 64 | 65 | /** 66 | * \brief Enables `1` or disables `0` status reporting callback 67 | * by \ref lwgps_process 68 | * 69 | * \note This is an extension, so not enabled by default. 70 | */ 71 | #ifndef LWGPS_CFG_STATUS 72 | #define LWGPS_CFG_STATUS 0 73 | #endif 74 | 75 | /** 76 | * \brief Enables `1` or disables `0` `GGA` statement parsing. 77 | * 78 | * \note This statement must be enabled to parse: 79 | * - Latitude, Longitude, Altitude 80 | * - Number of satellites in use, fix (no fix, GPS, DGPS), UTC time 81 | */ 82 | #ifndef LWGPS_CFG_STATEMENT_GPGGA 83 | #define LWGPS_CFG_STATEMENT_GPGGA 1 84 | #endif 85 | 86 | /** 87 | * \brief Enables `1` or disables `0` `GSA` statement parsing. 88 | * 89 | * \note This statement must be enabled to parse: 90 | * - Position/Vertical/Horizontal dilution of precision 91 | * - Fix mode (no fix, 2D, 3D fix) 92 | * - IDs of satellites in use 93 | */ 94 | #ifndef LWGPS_CFG_STATEMENT_GPGSA 95 | #define LWGPS_CFG_STATEMENT_GPGSA 1 96 | #endif 97 | 98 | /** 99 | * \brief Enables `1` or disables `0` `RMC` statement parsing. 100 | * 101 | * \note This statement must be enabled to parse: 102 | * - Validity of GPS signal 103 | * - Ground speed in knots and coarse in degrees 104 | * - Magnetic variation 105 | * - UTC date 106 | */ 107 | #ifndef LWGPS_CFG_STATEMENT_GPRMC 108 | #define LWGPS_CFG_STATEMENT_GPRMC 1 109 | #endif 110 | 111 | /** 112 | * \brief Enables `1` or disables `0` `GSV` statement parsing. 113 | * 114 | * \note This statement must be enabled to parse: 115 | * - Number of satellites in view 116 | * - Optional details of each satellite in view. See \ref LWGPS_CFG_STATEMENT_GPGSV_SAT_DET 117 | */ 118 | #ifndef LWGPS_CFG_STATEMENT_GPGSV 119 | #define LWGPS_CFG_STATEMENT_GPGSV 1 120 | #endif 121 | 122 | /** 123 | * \brief Enables `1` or disables `0` detailed parsing of each 124 | * satellite in view for `GSV` statement. 125 | * 126 | * \note When this feature is disabled, only number of "satellites in view" is parsed 127 | */ 128 | #ifndef LWGPS_CFG_STATEMENT_GPGSV_SAT_DET 129 | #define LWGPS_CFG_STATEMENT_GPGSV_SAT_DET 0 130 | #endif 131 | 132 | /** 133 | * \brief Enables `1` or disables `0` parsing and generation 134 | * of PUBX (uBlox) messages 135 | * 136 | * PUBX are a nonstandard ublox-specific extensions, 137 | * so disabled by default. 138 | */ 139 | #ifndef LWGPS_CFG_STATEMENT_PUBX 140 | #define LWGPS_CFG_STATEMENT_PUBX 0 141 | #endif 142 | 143 | /** 144 | * \brief Enables `1` or disables `0` parsing and generation 145 | * of PUBX (uBlox) TIME messages. 146 | * 147 | * \note TIME messages can be used to obtain: 148 | * - UTC time of week 149 | * - UTC week number 150 | * - Leap seconds (allows conversion to eg. TAI) 151 | * 152 | * This is a nonstandard ublox-specific extension, 153 | * so disabled by default. 154 | * 155 | * This configure option requires LWGPS_CFG_STATEMENT_PUBX 156 | */ 157 | #ifndef LWGPS_CFG_STATEMENT_PUBX_TIME 158 | #define LWGPS_CFG_STATEMENT_PUBX_TIME 0 159 | #endif 160 | 161 | /** 162 | * \brief Enables `1` or disables `0` CRC calculation and check 163 | * 164 | * \note When not enabled, CRC check is ignored 165 | */ 166 | #ifndef LWGPS_CFG_CRC 167 | #define LWGPS_CFG_CRC 1 168 | #endif 169 | 170 | /** 171 | * \brief Enables `1` or disables `0` distance and bearing calculation 172 | * 173 | * \note When not enabled, corresponding function is disabled 174 | */ 175 | #ifndef LWESP_CFG_DISTANCE_BEARING 176 | #define LWESP_CFG_DISTANCE_BEARING 1 177 | #endif 178 | 179 | /** 180 | * \brief Memory set function 181 | * 182 | * \note Function footprint is the same as \ref memset 183 | */ 184 | #ifndef LWGPS_MEMSET 185 | #define LWGPS_MEMSET(dst, val, len) memset((dst), (val), (len)) 186 | #endif 187 | 188 | /** 189 | * \brief Memory copy function 190 | * 191 | * \note Function footprint is the same as \ref memcpy 192 | */ 193 | #ifndef LWGPS_MEMCPY 194 | #define LWGPS_MEMCPY(dst, src, len) memcpy((dst), (src), (len)) 195 | #endif 196 | 197 | /* Guard against accidental parser breakage */ 198 | #if LWGPS_CFG_STATEMENT_PUBX_TIME && !LWGPS_CFG_STATEMENT_PUBX 199 | #error LWGPS_CFG_STATEMENT_PUBX must be enabled when enabling LWGPS_CFG_STATEMENT_PUBX_TIME 200 | #endif /* LWGPS_CFG_STATEMENT_PUBX_TIME && !LWGPS_CFG_STATEMENT_PUBX */ 201 | 202 | /** 203 | * \} 204 | */ 205 | 206 | #ifdef __cplusplus 207 | } 208 | #endif /* __cplusplus */ 209 | 210 | #endif /* LWGPS_OPT_HDR_H */ 211 | -------------------------------------------------------------------------------- /lwgps/src/include/lwgps/lwgps_opts_template.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwgps_opts_template.h 3 | * \brief LwGPS configuration file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwGPS - Lightweight GPS NMEA parser library. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v2.2.0 33 | */ 34 | #ifndef LWGPS_OPTS_HDR_H 35 | #define LWGPS_OPTS_HDR_H 36 | 37 | /* Rename this file to "lwgps_opts.h" for your application */ 38 | 39 | /* 40 | * Open "include/lwgps/lwgps_opt.h" and 41 | * copy & replace here settings you want to change values 42 | */ 43 | 44 | #endif /* LWGPS_OPTS_HDR_H */ 45 | -------------------------------------------------------------------------------- /lwgps/src/lwgps/lwgps.c: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwgps.c 3 | * \brief GPS main file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwGPS - Lightweight GPS NMEA parser library. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v2.2.0 33 | */ 34 | #include 35 | #include 36 | #include "lwgps/lwgps.h" 37 | #if LWESP_CFG_DISTANCE_BEARING 38 | #include 39 | #endif 40 | 41 | #define FLT(x) ((lwgps_float_t)(x)) 42 | #define D2R(x) FLT(FLT(x) * FLT(0.01745329251994)) /*!< Degrees to radians */ 43 | #define R2D(x) FLT(FLT(x) * FLT(57.29577951308232)) /*!< Radians to degrees */ 44 | #define EARTH_RADIUS FLT(6371.0) /*!< Earth radius in units of kilometers */ 45 | 46 | #if LWGPS_CFG_CRC 47 | #define CRC_ADD(_gh, ch) (_gh)->p.crc_calc ^= (uint8_t)(ch) 48 | #else 49 | #define CRC_ADD(_gh, ch) 50 | #endif /* LWGPS_CFG_CRC */ 51 | #define TERM_ADD(_gh, ch) \ 52 | do { \ 53 | if ((_gh)->p.term_pos < (sizeof((_gh)->p.term_str) - 1)) { \ 54 | (_gh)->p.term_str[(_gh)->p.term_pos] = (ch); \ 55 | (_gh)->p.term_str[++(_gh)->p.term_pos] = 0; \ 56 | } \ 57 | } while (0) 58 | #define TERM_NEXT(_gh) \ 59 | do { \ 60 | (_gh)->p.term_str[((_gh)->p.term_pos = 0)] = 0; \ 61 | ++(_gh)->p.term_num; \ 62 | } while (0) 63 | 64 | #define CIN(x) ((x) >= '0' && (x) <= '9') 65 | #define CIHN(x) (((x) >= '0' && (x) <= '9') || ((x) >= 'a' && (x) <= 'f') || ((x) >= 'A' && (x) <= 'F')) 66 | #define CTN(x) ((x) - '0') 67 | #define CHTN(x) \ 68 | (((x) >= '0' && (x) <= '9') \ 69 | ? ((x) - '0') \ 70 | : (((x) >= 'a' && (x) <= 'z') ? ((x) - 'a' + 10) : (((x) >= 'A' && (x) <= 'Z') ? ((x) - 'A' + 10) : 0))) 71 | 72 | /** 73 | * \brief Parse number as integer 74 | * \param[in] ghandle: GPS handle 75 | * \param[in] text: Text to parse. Set to `NULL` to parse current GPS term 76 | * \return Parsed integer 77 | * 78 | * \note \ref result from this function is never used in the library 79 | * to perform array access or even allocate any memory. 80 | * Some reports have been triggered about *CWE-190*, 81 | * which does not have negative effects in this particular use case. 82 | * 83 | * Security attacks with invalid input data may trigger 84 | * overflow in the number, which will later be used wrongly in the application, 85 | * but never inside the library itself. 86 | */ 87 | static int32_t 88 | prv_parse_number(lwgps_t* ghandle, const char* text) { 89 | int32_t res = 0; 90 | uint8_t minus = 0; 91 | 92 | if (text == NULL) { 93 | text = ghandle->p.term_str; 94 | } 95 | for (; text != NULL && *text == ' '; ++text) {} /* Strip leading spaces */ 96 | 97 | minus = (*text == '-' ? (++text, 1) : 0); 98 | for (; text != NULL && CIN(*text); ++text) { 99 | res = 10L * res + CTN(*text); 100 | } 101 | return minus ? -res : res; 102 | } 103 | 104 | /** 105 | * \brief Parse number as double and convert it to \ref lwgps_float_t 106 | * \param[in] ghandle: GPS handle 107 | * \param[in] text: Text to parse. Set to `NULL` to parse current GPS term 108 | * \return Parsed double in \ref lwgps_float_t format 109 | * 110 | * \note \ref result from this function is never used in the library 111 | * to perform array access or even allocate any memory. 112 | * Some reports have been triggered about *CWE-190*, 113 | * which does not have negative effects in this particular use case. 114 | * 115 | * Security attacks with invalid input data may trigger 116 | * overflow in the number, which will later be used wrongly in the application, 117 | * but never inside the library itself. 118 | */ 119 | static lwgps_float_t 120 | prv_parse_float_number(lwgps_t* ghandle, const char* text) { 121 | lwgps_float_t value = (lwgps_float_t)0, power = (lwgps_float_t)1, sign = (lwgps_float_t)1; 122 | 123 | if (text == NULL) { 124 | text = ghandle->p.term_str; 125 | } 126 | for (; text != NULL && *text == ' '; ++text) {} /* Strip leading spaces */ 127 | 128 | if (*text == '-') { /* Check sign */ 129 | sign = -1; 130 | ++text; 131 | } 132 | while (CIN(*text)) { /* Convert main part */ 133 | value = value * (lwgps_float_t)10 + CTN(*text); 134 | ++text; 135 | } 136 | if (*text == '.') { /* Skip the dot character */ 137 | ++text; 138 | } 139 | while (CIN(*text)) { /* Get the power */ 140 | value = value * (lwgps_float_t)10 + CTN(*text); 141 | power *= (lwgps_float_t)10; 142 | ++text; 143 | } 144 | return sign * value / power; 145 | } 146 | 147 | /** 148 | * \brief Parse latitude/longitude NMEA format to double 149 | * 150 | * NMEA output for latitude is ddmm.sss and longitude is dddmm.sss 151 | * \param[in] ghandle: GPS handle 152 | * \return Latitude/Longitude value in degrees 153 | */ 154 | static lwgps_float_t 155 | prv_parse_lat_long(lwgps_t* ghandle) { 156 | lwgps_float_t llong, deg, min; 157 | 158 | llong = prv_parse_float_number(ghandle, NULL); /* Parse value as double */ 159 | deg = FLT((int)((int)llong / 100)); /* Get absolute degrees value, interested in integer part only */ 160 | min = llong - (deg * FLT(100)); /* Get remaining part from full number, minutes */ 161 | llong = deg + (min / FLT(60)); /* Calculate latitude/longitude */ 162 | 163 | return llong; 164 | } 165 | 166 | /** 167 | * \brief Parse received term 168 | * \param[in] ghandle: GPS handle 169 | * \return `1` on success, `0` otherwise 170 | */ 171 | static uint8_t 172 | prv_parse_term(lwgps_t* ghandle) { 173 | if (ghandle->p.term_num == 0) { /* Check string type */ 174 | if (0) { 175 | #if LWGPS_CFG_STATEMENT_GPGGA 176 | } else if (!strncmp(ghandle->p.term_str, "$GPGGA", 6) || !strncmp(ghandle->p.term_str, "$GNGGA", 6)) { 177 | ghandle->p.stat = STAT_GGA; 178 | #endif /* LWGPS_CFG_STATEMENT_GPGGA */ 179 | #if LWGPS_CFG_STATEMENT_GPGSA 180 | } else if (!strncmp(ghandle->p.term_str, "$GPGSA", 6) || !strncmp(ghandle->p.term_str, "$GNGSA", 6)) { 181 | ghandle->p.stat = STAT_GSA; 182 | #endif /* LWGPS_CFG_STATEMENT_GPGSA */ 183 | #if LWGPS_CFG_STATEMENT_GPGSV 184 | } else if (!strncmp(ghandle->p.term_str, "$GPGSV", 6) || !strncmp(ghandle->p.term_str, "$GNGSV", 6)) { 185 | ghandle->p.stat = STAT_GSV; 186 | #endif /* LWGPS_CFG_STATEMENT_GPGSV */ 187 | #if LWGPS_CFG_STATEMENT_GPRMC 188 | } else if (!strncmp(ghandle->p.term_str, "$GPRMC", 6) || !strncmp(ghandle->p.term_str, "$GNRMC", 6)) { 189 | ghandle->p.stat = STAT_RMC; 190 | #endif /* LWGPS_CFG_STATEMENT_GPRMC */ 191 | #if LWGPS_CFG_STATEMENT_PUBX 192 | } else if (!strncmp(ghandle->p.term_str, "$PUBX", 5)) { 193 | ghandle->p.stat = STAT_UBX; 194 | #endif /* LWGPS_CFG_STATEMENT_PUBX */ 195 | } else { 196 | ghandle->p.stat = STAT_UNKNOWN; /* Invalid statement for library */ 197 | } 198 | return 1; 199 | } 200 | 201 | /* Start parsing terms */ 202 | if (ghandle->p.stat == STAT_UNKNOWN) { 203 | #if LWGPS_CFG_STATEMENT_GPGGA 204 | } else if (ghandle->p.stat == STAT_GGA) { /* Process GPGGA statement */ 205 | switch (ghandle->p.term_num) { 206 | case 1: /* Process UTC time */ 207 | if (ghandle->p.term_pos >= 6) { 208 | ghandle->p.data.gga.hours = 10 * CTN(ghandle->p.term_str[0]) + CTN(ghandle->p.term_str[1]); 209 | ghandle->p.data.gga.minutes = 10 * CTN(ghandle->p.term_str[2]) + CTN(ghandle->p.term_str[3]); 210 | ghandle->p.data.gga.seconds = 10 * CTN(ghandle->p.term_str[4]) + CTN(ghandle->p.term_str[5]); 211 | ghandle->p.data.gga.time_valid = 1; 212 | } else { 213 | ghandle->p.data.gga.time_valid = 0; 214 | } 215 | break; 216 | case 2: /* Latitude */ 217 | ghandle->p.data.gga.latitude = prv_parse_lat_long(ghandle); /* Parse latitude */ 218 | break; 219 | case 3: /* Latitude north/south information */ 220 | if (ghandle->p.term_str[0] == 'S' || ghandle->p.term_str[0] == 's') { 221 | ghandle->p.data.gga.latitude = -ghandle->p.data.gga.latitude; 222 | } 223 | break; 224 | case 4: /* Longitude */ 225 | ghandle->p.data.gga.longitude = prv_parse_lat_long(ghandle); /* Parse longitude */ 226 | break; 227 | case 5: /* Longitude east/west information */ 228 | if (ghandle->p.term_str[0] == 'W' || ghandle->p.term_str[0] == 'w') { 229 | ghandle->p.data.gga.longitude = -ghandle->p.data.gga.longitude; 230 | } 231 | break; 232 | case 6: /* Fix status */ ghandle->p.data.gga.fix = (uint8_t)prv_parse_number(ghandle, NULL); break; 233 | case 7: /* Satellites in use */ 234 | ghandle->p.data.gga.sats_in_use = (uint8_t)prv_parse_number(ghandle, NULL); 235 | break; 236 | case 9: /* Altitude */ ghandle->p.data.gga.altitude = prv_parse_float_number(ghandle, NULL); break; 237 | case 11: /* Altitude above ellipsoid */ 238 | ghandle->p.data.gga.geo_sep = prv_parse_float_number(ghandle, NULL); 239 | break; 240 | case 13: /* Age of differential GPS correction data */ 241 | ghandle->p.data.gga.dgps_age = prv_parse_float_number(ghandle, NULL); 242 | break; 243 | default: break; 244 | } 245 | #endif /* LWGPS_CFG_STATEMENT_GPGGA */ 246 | #if LWGPS_CFG_STATEMENT_GPGSA 247 | } else if (ghandle->p.stat == STAT_GSA) { /* Process GPGSA statement */ 248 | switch (ghandle->p.term_num) { 249 | case 2: ghandle->p.data.gsa.fix_mode = (uint8_t)prv_parse_number(ghandle, NULL); break; 250 | case 15: ghandle->p.data.gsa.dop_p = prv_parse_float_number(ghandle, NULL); break; 251 | case 16: ghandle->p.data.gsa.dop_h = prv_parse_float_number(ghandle, NULL); break; 252 | case 17: ghandle->p.data.gsa.dop_v = prv_parse_float_number(ghandle, NULL); break; 253 | default: 254 | /* Parse satellite IDs */ 255 | if (ghandle->p.term_num >= 3 && ghandle->p.term_num <= 14) { 256 | ghandle->p.data.gsa.satellites_ids[ghandle->p.term_num - 3] = 257 | (uint8_t)prv_parse_number(ghandle, NULL); 258 | } 259 | break; 260 | } 261 | #endif /* LWGPS_CFG_STATEMENT_GPGSA */ 262 | #if LWGPS_CFG_STATEMENT_GPGSV 263 | } else if (ghandle->p.stat == STAT_GSV) { /* Process GPGSV statement */ 264 | switch (ghandle->p.term_num) { 265 | case 2: /* Current GPGSV statement number */ 266 | ghandle->p.data.gsv.stat_num = (uint8_t)prv_parse_number(ghandle, NULL); 267 | break; 268 | case 3: /* Process satellites in view */ 269 | ghandle->p.data.gsv.sats_in_view = (uint8_t)prv_parse_number(ghandle, NULL); 270 | break; 271 | default: 272 | #if LWGPS_CFG_STATEMENT_GPGSV_SAT_DET 273 | if (ghandle->p.term_num >= 4 && ghandle->p.term_num <= 19) { /* Check current term number */ 274 | uint8_t index, term_num = ghandle->p.term_num - 4; /* Normalize term number from 4-19 to 0-15 */ 275 | uint16_t value; 276 | 277 | index = ((ghandle->p.data.gsv.stat_num - 1) << 0x02) + (term_num >> 2); /* Get array index */ 278 | if (index < sizeof(ghandle->sats_in_view_desc) / sizeof(ghandle->sats_in_view_desc[0])) { 279 | value = (uint16_t)prv_parse_number(ghandle, NULL); /* Parse number as integer */ 280 | switch (term_num & 0x03) { 281 | case 0: ghandle->sats_in_view_desc[index].num = value; break; 282 | case 1: ghandle->sats_in_view_desc[index].elevation = value; break; 283 | case 2: ghandle->sats_in_view_desc[index].azimuth = value; break; 284 | case 3: ghandle->sats_in_view_desc[index].snr = value; break; 285 | default: break; 286 | } 287 | } 288 | } 289 | #endif /* LWGPS_CFG_STATEMENT_GPGSV_SAT_DET */ 290 | break; 291 | } 292 | #endif /* LWGPS_CFG_STATEMENT_GPGSV */ 293 | #if LWGPS_CFG_STATEMENT_GPRMC 294 | } else if (ghandle->p.stat == STAT_RMC) { /* Process GPRMC statement */ 295 | switch (ghandle->p.term_num) { 296 | case 2: /* Process valid status */ ghandle->p.data.rmc.is_valid = (ghandle->p.term_str[0] == 'A'); break; 297 | case 7: /* Process ground speed in knots */ 298 | ghandle->p.data.rmc.speed = prv_parse_float_number(ghandle, NULL); 299 | break; 300 | case 8: /* Process true ground coarse */ 301 | ghandle->p.data.rmc.course = prv_parse_float_number(ghandle, NULL); 302 | break; 303 | case 9: /* Process date */ 304 | if (ghandle->p.term_pos >= 6) { 305 | ghandle->p.data.rmc.date = 306 | (uint8_t)(10U * CTN(ghandle->p.term_str[0]) + CTN(ghandle->p.term_str[1])); 307 | ghandle->p.data.rmc.month = 308 | (uint8_t)(10U * CTN(ghandle->p.term_str[2]) + CTN(ghandle->p.term_str[3])); 309 | ghandle->p.data.rmc.year = 310 | (uint8_t)(10U * CTN(ghandle->p.term_str[4]) + CTN(ghandle->p.term_str[5])); 311 | ghandle->p.data.rmc.date_valid = 1; 312 | } else { 313 | ghandle->p.data.rmc.date_valid = 0; 314 | } 315 | break; 316 | case 10: /* Process magnetic variation */ 317 | ghandle->p.data.rmc.variation = prv_parse_float_number(ghandle, NULL); 318 | break; 319 | case 11: /* Process magnetic variation east/west */ 320 | if (ghandle->p.term_str[0] == 'W' || ghandle->p.term_str[0] == 'w') { 321 | ghandle->p.data.rmc.variation = -ghandle->p.data.rmc.variation; 322 | } 323 | break; 324 | default: break; 325 | } 326 | #endif /* LWGPS_CFG_STATEMENT_GPRMC */ 327 | #if LWGPS_CFG_STATEMENT_PUBX 328 | } else if (ghandle->p.stat == STAT_UBX) { /* Disambiguate generic PUBX statement */ 329 | if (ghandle->p.term_str[0] == '0' && ghandle->p.term_str[1] == '4') { 330 | ghandle->p.stat = STAT_UBX_TIME; 331 | } 332 | #if LWGPS_CFG_STATEMENT_PUBX_TIME 333 | } else if (ghandle->p.stat == STAT_UBX_TIME) { /* Process PUBX (uBlox) TIME statement */ 334 | switch (ghandle->p.term_num) { 335 | case 2: /* Process UTC time; ignore fractions of seconds */ 336 | ghandle->p.data.time.hours = 10U * CTN(ghandle->p.term_str[0]) + CTN(ghandle->p.term_str[1]); 337 | ghandle->p.data.time.minutes = 10U * CTN(ghandle->p.term_str[2]) + CTN(ghandle->p.term_str[3]); 338 | ghandle->p.data.time.seconds = 10U * CTN(ghandle->p.term_str[4]) + CTN(ghandle->p.term_str[5]); 339 | break; 340 | case 3: /* Process UTC date */ 341 | ghandle->p.data.time.date = 10U * CTN(ghandle->p.term_str[0]) + CTN(ghandle->p.term_str[1]); 342 | ghandle->p.data.time.month = 10U * CTN(ghandle->p.term_str[2]) + CTN(ghandle->p.term_str[3]); 343 | ghandle->p.data.time.year = 10U * CTN(ghandle->p.term_str[4]) + CTN(ghandle->p.term_str[5]); 344 | break; 345 | case 4: /* Process UTC TimeOfWeek */ 346 | ghandle->p.data.time.utc_tow = prv_parse_float_number(ghandle, NULL); 347 | break; 348 | case 5: /* Process UTC WeekNumber */ ghandle->p.data.time.utc_wk = prv_parse_number(ghandle, NULL); break; 349 | case 6: /* Process UTC leap seconds */ 350 | /* 351 | * Accomodate a 2- or 3-digit leap second count 352 | * a trailing 'D' means this is the firmware's default value. 353 | */ 354 | if (ghandle->p.term_str[2] == 'D' || ghandle->p.term_str[2] == '\0') { 355 | ghandle->p.data.time.leap_sec = 10U * CTN(ghandle->p.term_str[0]) + CTN(ghandle->p.term_str[1]); 356 | } else { 357 | ghandle->p.data.time.leap_sec = 100U * CTN(ghandle->p.term_str[0]) 358 | + 10U * CTN(ghandle->p.term_str[1]) + CTN(ghandle->p.term_str[2]); 359 | } 360 | break; 361 | case 7: /* Process clock bias */ ghandle->p.data.time.clk_bias = prv_parse_number(ghandle, NULL); break; 362 | case 8: /* Process clock drift */ 363 | ghandle->p.data.time.clk_drift = prv_parse_float_number(ghandle, NULL); 364 | break; 365 | case 9: /* Process time pulse granularity */ 366 | ghandle->p.data.time.tp_gran = prv_parse_number(ghandle, NULL); 367 | break; 368 | default: break; 369 | } 370 | #endif /* LWGPS_CFG_STATEMENT_PUBX_TIME */ 371 | #endif /* LWGPS_CFG_STATEMENT_PUBX */ 372 | } 373 | return 1; 374 | } 375 | 376 | #if LWGPS_CFG_CRC 377 | /** 378 | * \brief Compare calculated CRC with received CRC 379 | * \param[in] ghandle: GPS handle 380 | * \return `1` on success, `0` otherwise 381 | */ 382 | static uint8_t 383 | prv_check_crc(lwgps_t* ghandle) { 384 | uint8_t crc; 385 | crc = (uint8_t)((CHTN(ghandle->p.term_str[0]) & 0x0FU) << 0x04U) 386 | | (CHTN(ghandle->p.term_str[1]) & 0x0FU); /* Convert received CRC from string (hex) to number */ 387 | return ghandle->p.crc_calc == crc; /* They must match! */ 388 | } 389 | #else 390 | #define prv_check_crc(ghandle) (1) 391 | #endif /* LWGPS_CFG_CRC */ 392 | 393 | /** 394 | * \brief Copy temporary memory to user memory 395 | * \param[in] ghandle: GPS handle 396 | * \return `1` on success, `0` otherwise 397 | */ 398 | static uint8_t 399 | prv_copy_from_tmp_memory(lwgps_t* ghandle) { 400 | if (0) { 401 | #if LWGPS_CFG_STATEMENT_GPGGA 402 | } else if (ghandle->p.stat == STAT_GGA) { 403 | ghandle->latitude = ghandle->p.data.gga.latitude; 404 | ghandle->longitude = ghandle->p.data.gga.longitude; 405 | ghandle->altitude = ghandle->p.data.gga.altitude; 406 | ghandle->geo_sep = ghandle->p.data.gga.geo_sep; 407 | ghandle->sats_in_use = ghandle->p.data.gga.sats_in_use; 408 | ghandle->fix = ghandle->p.data.gga.fix; 409 | ghandle->hours = ghandle->p.data.gga.hours; 410 | ghandle->minutes = ghandle->p.data.gga.minutes; 411 | ghandle->seconds = ghandle->p.data.gga.seconds; 412 | ghandle->time_valid = ghandle->p.data.gga.time_valid; 413 | ghandle->dgps_age = ghandle->p.data.gga.dgps_age; 414 | #endif /* LWGPS_CFG_STATEMENT_GPGGA */ 415 | #if LWGPS_CFG_STATEMENT_GPGSA 416 | } else if (ghandle->p.stat == STAT_GSA) { 417 | ghandle->dop_h = ghandle->p.data.gsa.dop_h; 418 | ghandle->dop_p = ghandle->p.data.gsa.dop_p; 419 | ghandle->dop_v = ghandle->p.data.gsa.dop_v; 420 | ghandle->fix_mode = ghandle->p.data.gsa.fix_mode; 421 | LWGPS_MEMCPY(ghandle->satellites_ids, ghandle->p.data.gsa.satellites_ids, sizeof(ghandle->satellites_ids)); 422 | #endif /* LWGPS_CFG_STATEMENT_GPGSA */ 423 | #if LWGPS_CFG_STATEMENT_GPGSV 424 | } else if (ghandle->p.stat == STAT_GSV) { 425 | ghandle->sats_in_view = ghandle->p.data.gsv.sats_in_view; 426 | #endif /* LWGPS_CFG_STATEMENT_GPGSV */ 427 | #if LWGPS_CFG_STATEMENT_GPRMC 428 | } else if (ghandle->p.stat == STAT_RMC) { 429 | ghandle->course = ghandle->p.data.rmc.course; 430 | ghandle->is_valid = ghandle->p.data.rmc.is_valid; 431 | ghandle->speed = ghandle->p.data.rmc.speed; 432 | ghandle->variation = ghandle->p.data.rmc.variation; 433 | ghandle->date = ghandle->p.data.rmc.date; 434 | ghandle->month = ghandle->p.data.rmc.month; 435 | ghandle->year = ghandle->p.data.rmc.year; 436 | ghandle->date_valid = ghandle->p.data.rmc.date_valid; 437 | #endif /* LWGPS_CFG_STATEMENT_GPRMC */ 438 | #if LWGPS_CFG_STATEMENT_PUBX_TIME 439 | } else if (ghandle->p.stat == STAT_UBX_TIME) { 440 | ghandle->hours = ghandle->p.data.time.hours; 441 | ghandle->minutes = ghandle->p.data.time.minutes; 442 | ghandle->seconds = ghandle->p.data.time.seconds; 443 | ghandle->date = ghandle->p.data.time.date; 444 | ghandle->month = ghandle->p.data.time.month; 445 | ghandle->year = ghandle->p.data.time.year; 446 | ghandle->utc_tow = ghandle->p.data.time.utc_tow; 447 | ghandle->utc_wk = ghandle->p.data.time.utc_wk; 448 | ghandle->leap_sec = ghandle->p.data.time.leap_sec; 449 | ghandle->clk_bias = ghandle->p.data.time.clk_bias; 450 | ghandle->clk_drift = ghandle->p.data.time.clk_drift; 451 | ghandle->tp_gran = ghandle->p.data.time.tp_gran; 452 | #endif /* LWGPS_CFG_STATEMENT_PUBX_TIME */ 453 | } 454 | return 1; 455 | } 456 | 457 | /** 458 | * \brief Init GPS handle 459 | * \param[in] ghandle: GPS handle structure 460 | * \return `1` on success, `0` otherwise 461 | */ 462 | uint8_t 463 | lwgps_init(lwgps_t* ghandle) { 464 | LWGPS_MEMSET(ghandle, 0x00, sizeof(*ghandle)); /* Reset structure */ 465 | return 1; 466 | } 467 | 468 | /** 469 | * \brief Process NMEA data from GPS receiver 470 | * \param[in] ghandle: GPS handle structure 471 | * \param[in] data: Received data 472 | * \param[in] len: Number of bytes to process 473 | * \param[in] evt_fn: Event function to notify application layer. 474 | * This parameter is available only if \ref LWGPS_CFG_STATUS is enabled 475 | * \return `1` on success, `0` otherwise 476 | */ 477 | uint8_t 478 | #if LWGPS_CFG_STATUS || __DOXYGEN__ 479 | lwgps_process(lwgps_t* ghandle, const void* data, size_t len, lwgps_process_fn evt_fn) { 480 | #else /* LWGPS_CFG_STATUS */ 481 | lwgps_process(lwgps_t* ghandle, const void* data, size_t len) { 482 | #endif /* !LWGPS_CFG_STATUS */ 483 | const uint8_t* d = data; 484 | 485 | for (; len > 0; ++d, --len) { /* Process all bytes */ 486 | if (*d == '$') { /* Check for beginning of NMEA line */ 487 | LWGPS_MEMSET(&ghandle->p, 0x00, sizeof(ghandle->p)); /* Reset private memory */ 488 | TERM_ADD(ghandle, *d); /* Add character to term */ 489 | } else if (*d == ',') { /* Term separator character */ 490 | prv_parse_term(ghandle); /* Parse term we have currently in memory */ 491 | CRC_ADD(ghandle, *d); /* Add character to CRC computation */ 492 | TERM_NEXT(ghandle); /* Start with next term */ 493 | } else if (*d == '*') { /* Start indicates end of data for CRC computation */ 494 | prv_parse_term(ghandle); /* Parse term we have currently in memory */ 495 | ghandle->p.star = 1; /* STAR detected */ 496 | TERM_NEXT(ghandle); /* Start with next term */ 497 | } else if (*d == '\r') { 498 | if (prv_check_crc(ghandle)) { /* Check for CRC result */ 499 | /* CRC is OK, in theory we can copy data from statements to user data */ 500 | prv_copy_from_tmp_memory(ghandle); /* Copy memory from temporary to user memory */ 501 | #if LWGPS_CFG_STATUS 502 | if (evt_fn != NULL) { 503 | evt_fn(ghandle->p.stat); 504 | } 505 | } else if (evt_fn != NULL) { 506 | evt_fn(STAT_CHECKSUM_FAIL); 507 | #endif /* LWGPS_CFG_STATUS */ 508 | } 509 | } else { 510 | if (!ghandle->p.star) { /* Add to CRC only if star not yet detected */ 511 | CRC_ADD(ghandle, *d); /* Add to CRC */ 512 | } 513 | TERM_ADD(ghandle, *d); /* Add character to term */ 514 | } 515 | } 516 | return 1; 517 | } 518 | 519 | #if LWESP_CFG_DISTANCE_BEARING || __DOXYGEN__ 520 | 521 | /** 522 | * \brief Calculate distance and bearing between `2` latitude and longitude coordinates 523 | * \param[in] las: Latitude start coordinate, in units of degrees 524 | * \param[in] los: Longitude start coordinate, in units of degrees 525 | * \param[in] lae: Latitude end coordinate, in units of degrees 526 | * \param[in] loe: Longitude end coordinate, in units of degrees 527 | * \param[out] d: Pointer to output distance in units of meters 528 | * \param[out] b: Pointer to output bearing between start and end coordinate in relation to north in units of degrees 529 | * \return `1` on success, `0` otherwise 530 | */ 531 | uint8_t 532 | lwgps_distance_bearing(lwgps_float_t las, lwgps_float_t los, lwgps_float_t lae, lwgps_float_t loe, lwgps_float_t* d, 533 | lwgps_float_t* b) { 534 | lwgps_float_t df, dfi, a; 535 | 536 | if (d == NULL && b == NULL) { 537 | return 0; 538 | } 539 | 540 | /* Convert degrees to radians */ 541 | df = D2R(lae - las); 542 | dfi = D2R(loe - los); 543 | las = D2R(las); 544 | los = D2R(los); 545 | lae = D2R(lae); 546 | loe = D2R(loe); 547 | 548 | /* 549 | * Calculate distance 550 | * 551 | * Calculated distance is absolute value in meters between 2 points on earth. 552 | */ 553 | if (d != NULL) { 554 | /* 555 | * a = sin(df / 2)^2 + cos(las) * cos(lae) * sin(dfi / 2)^2 556 | * *d = RADIUS * 2 * atan(sqrt(a) / sqrt(1 - a)) * 1000 (for meters) 557 | */ 558 | #if LWGPS_CFG_DOUBLE 559 | a = FLT(sin(df * 0.5) * sin(df * 0.5) + sin(dfi * 0.5) * sin(dfi * 0.5) * cos(las) * cos(lae)); 560 | *d = FLT(EARTH_RADIUS * 2.0 * atan2(sqrt(a), sqrt(1.0 - a)) * 1000.0); 561 | #else /* LWGPS_CFG_DOUBLE */ 562 | a = FLT(sinf(df * 0.5f) * sinf(df * 0.5f) + sinf(dfi * 0.5f) * sinf(dfi * 0.5f) * cosf(las) * cosf(lae)); 563 | *d = FLT(EARTH_RADIUS * 2.0f * atan2f(sqrtf(a), sqrtf(1.0f - a)) * 1000.0f); 564 | #endif /* !LWGPS_CFG_DOUBLE */ 565 | } 566 | 567 | /* 568 | * Calculate bearing 569 | * 570 | * Bearing is calculated from point 1 to point 2. 571 | * Result will tell us in which direction (according to north) we should move, 572 | * to reach point 2. 573 | * 574 | * Example: 575 | * Bearing is 0 => move to north 576 | * Bearing is 90 => move to east 577 | * Bearing is 180 => move to south 578 | * Bearing is 270 => move to west 579 | */ 580 | if (b != NULL) { 581 | #if LWGPS_CFG_DOUBLE 582 | df = FLT(sin(loe - los) * cos(lae)); 583 | dfi = FLT(cos(las) * sin(lae) - sin(las) * cos(lae) * cos(loe - los)); 584 | 585 | *b = R2D(atan2(df, dfi)); /* Calculate bearing and convert to degrees */ 586 | #else /* LWGPS_CFG_DOUBLE */ 587 | df = FLT(sinf(loe - los) * cosf(lae)); 588 | dfi = FLT(cosf(las) * sinf(lae) - sinf(las) * cosf(lae) * cosf(loe - los)); 589 | 590 | *b = R2D(atan2f(df, dfi)); /* Calculate bearing and convert to degrees */ 591 | #endif /* !LWGPS_CFG_DOUBLE */ 592 | if (*b < 0) { /* Check for negative angle */ 593 | *b += FLT(360); /* Make bearing always positive */ 594 | } 595 | } 596 | return 1; 597 | } 598 | 599 | #endif /* LWESP_CFG_DISTANCE_BEARING || __DOXYGEN__ */ 600 | 601 | /** 602 | * \brief Convert NMEA GPS speed (in knots = nautical mile per hour) to different speed format 603 | * \param[in] sik: Speed in knots, received from GPS NMEA statement 604 | * \param[in] ts: Target speed to convert to from knots 605 | * \return Speed calculated from knots 606 | */ 607 | lwgps_float_t 608 | lwgps_to_speed(lwgps_float_t sik, lwgps_speed_t ts) { 609 | switch (ts) { 610 | case LWGPS_SPEED_KPS: return FLT(sik * FLT(0.000514)); 611 | case LWGPS_SPEED_KPH: return FLT(sik * FLT(1.852)); 612 | case LWGPS_SPEED_MPS: return FLT(sik * FLT(0.5144)); 613 | case LWGPS_SPEED_MPM: return FLT(sik * FLT(30.87)); 614 | case LWGPS_SPEED_MIPS: return FLT(sik * FLT(0.0003197)); 615 | case LWGPS_SPEED_MPH: return FLT(sik * FLT(1.151)); 616 | case LWGPS_SPEED_FPS: return FLT(sik * FLT(1.688)); 617 | case LWGPS_SPEED_FPM: return FLT(sik * FLT(101.3)); 618 | case LWGPS_SPEED_MPK: return FLT(sik * FLT(32.4)); 619 | case LWGPS_SPEED_SPK: return FLT(sik * FLT(1944.0)); 620 | case LWGPS_SPEED_SP100M: return FLT(sik * FLT(194.4)); 621 | case LWGPS_SPEED_MIPM: return FLT(sik * FLT(52.14)); 622 | case LWGPS_SPEED_SPM: return FLT(sik * FLT(3128.0)); 623 | case LWGPS_SPEED_SP100Y: return FLT(sik * FLT(177.7)); 624 | case LWGPS_SPEED_SMPH: return FLT(sik * FLT(1.0)); 625 | default: return 0; 626 | } 627 | } 628 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | # Setup project 4 | project(LwLibPROJECT) 5 | 6 | # Set default compile flags for GCC 7 | if(CMAKE_COMPILER_IS_GNUCXX) 8 | message(STATUS "GCC detected, adding compile flags") 9 | set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall -Wextra") 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall -Wextra") 11 | endif(CMAKE_COMPILER_IS_GNUCXX) 12 | 13 | enable_testing() 14 | add_executable(${CMAKE_PROJECT_NAME}) 15 | target_sources(${CMAKE_PROJECT_NAME} PRIVATE 16 | ${CMAKE_CURRENT_LIST_DIR}/test_main.cpp 17 | ) 18 | target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE 19 | ${CMAKE_CURRENT_LIST_DIR}/ 20 | ) 21 | 22 | # Include file that can add more sources and prepare lib parameters 23 | include(${TEST_CMAKE_FILE_NAME}) 24 | 25 | # Add subdir with lwgps and link to project 26 | add_subdirectory("../lwgps" lwgps) 27 | target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC lwgps) 28 | target_compile_definitions(lwgps PUBLIC LWGPS_DEV) 29 | 30 | # Add test 31 | add_test(NAME Test COMMAND $) 32 | -------------------------------------------------------------------------------- /tests/test.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_COMMON_HDR_H 2 | #define TEST_COMMON_HDR_H 3 | 4 | #include 5 | #include 6 | 7 | #define RUN_TEST(x) \ 8 | do { \ 9 | if (!(x)) { \ 10 | printf("Test FAILED on line %u with condition " #x "\r\n", (unsigned)__LINE__); \ 11 | return -1; \ 12 | } \ 13 | } while (0) 14 | #define FLT_IS_EQUAL(x, y) (fabs((double)(x) - (double)(y)) < 0.00001) 15 | #define INT_IS_EQUAL(x, y) ((int)((x) == (y))) 16 | 17 | #endif /* TEST_COMMON_HDR_H */ 18 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | import glob, os, shutil, sys, argparse 2 | 3 | def main(args): 4 | retval:int = 0 5 | 6 | rootpath = os.getcwd() 7 | files = [f for f in glob.glob(os.path.join(os.getcwd(), '**', '*.cmake'), recursive=True) if '__build__' not in f] 8 | print(files, flush=True) 9 | 10 | for file in files: 11 | basep = os.path.dirname(file) 12 | print('Test path:', basep, flush=True) 13 | 14 | # Reset directory, delete previous runs 15 | os.chdir(basep) 16 | try: shutil.rmtree('__build__/', ignore_errors=True) 17 | except: pass 18 | 19 | # Run and test 20 | os.mkdir('__build__') 21 | os.chdir('__build__') 22 | print('Configure the CMake', flush=True) 23 | retval |= os.system('cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -S../.. -G Ninja -DTEST_CMAKE_FILE_NAME={}'.format(file)) 24 | 25 | print('Compile', flush=True) 26 | retval |= os.system('cmake --build .') 27 | print('Run test', flush=True) 28 | retval |= os.system('ctest . --output-on-failure -C Debug') 29 | 30 | return retval 31 | 32 | # Get parser 33 | def get_parser(): 34 | parser = argparse.ArgumentParser() 35 | parser.add_argument("--github", required=False, action='store_true', help="Flag if test runs on Github workflow") 36 | return parser 37 | 38 | # Run the script 39 | if __name__ == '__main__': 40 | print('Main script running', flush=True) 41 | 42 | parser = get_parser() 43 | sys.exit(1 if main(parser.parse_args()) != 0 else 0) -------------------------------------------------------------------------------- /tests/test_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwgps/lwgps.h" 3 | 4 | extern "C" int test_run(void); 5 | 6 | int 7 | main(void) { 8 | int ret = 0; 9 | printf("Application running\r\n"); 10 | ret = test_run(); 11 | printf("Done\r\n"); 12 | return ret; 13 | } -------------------------------------------------------------------------------- /tests/test_parse_ext_time/cmake.cmake: -------------------------------------------------------------------------------- 1 | # CMake include file 2 | 3 | # Add more sources 4 | target_sources(${CMAKE_PROJECT_NAME} PRIVATE 5 | ${CMAKE_CURRENT_LIST_DIR}/test_parse_ext_time.c 6 | ) 7 | 8 | # Options file 9 | set(LWGPS_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/lwgps_opts.h) -------------------------------------------------------------------------------- /tests/test_parse_ext_time/lwgps_opts.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwgps_opts_template.h 3 | * \brief LwGPS configuration file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwGPS - Lightweight GPS NMEA parser library. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: $2.2.0$ 33 | */ 34 | #ifndef LWGPS_HDR_OPTS_H 35 | #define LWGPS_HDR_OPTS_H 36 | 37 | #define LWGPS_CFG_STATEMENT_PUBX 1 38 | #define LWGPS_CFG_STATEMENT_PUBX_TIME 1 39 | 40 | #endif /* LWGPS_HDR_OPTS_H */ 41 | -------------------------------------------------------------------------------- /tests/test_parse_ext_time/test_parse_ext_time.c: -------------------------------------------------------------------------------- 1 | /** 2 | * \file test_parse_ext_time.c 3 | * \author Tilen MAJERLE 4 | * \brief 5 | * \version 0.1 6 | * \date 2025-03-30 7 | * 8 | * @copyright Copyright (c) 2025 9 | * 10 | */ 11 | #include 12 | #include 13 | #include "lwgps/lwgps.h" 14 | #include "test.h" 15 | 16 | #if !LWGPS_CFG_STATEMENT_PUBX_TIME 17 | #error "this test must be compiled with -DLWGPS_CFG_STATEMENT_PUBX_TIME=1" 18 | #endif /* !LWGPS_CFG_STATEMENT_PUBX_TIME */ 19 | 20 | /* GPS handle */ 21 | static lwgps_t hgps; 22 | 23 | /** 24 | * \brief Dummy data from GPS receiver 25 | */ 26 | const char gps_rx_data_A[] = "" 27 | "$PUBX,04*37\r\n" 28 | "$PUBX,04,073731.00,091202,113851.00,1196,15D,1930035,-2660.664,43*71\r\n" 29 | ""; 30 | const char gps_rx_data_B[] = "" 31 | "$PUBX,04,200714.00,230320,158834.00,2098,18,536057,257.043,16*12\r\n" 32 | ""; 33 | 34 | /** 35 | * \brief Run the test of raw input data 36 | */ 37 | int 38 | test_run(void) { 39 | lwgps_init(&hgps); 40 | 41 | /* Process and test block A */ 42 | lwgps_process(&hgps, gps_rx_data_A, strlen(gps_rx_data_A)); 43 | 44 | RUN_TEST(INT_IS_EQUAL(hgps.hours, 7)); 45 | RUN_TEST(INT_IS_EQUAL(hgps.minutes, 37)); 46 | RUN_TEST(INT_IS_EQUAL(hgps.seconds, 31)); 47 | RUN_TEST(INT_IS_EQUAL(hgps.date, 9)); 48 | RUN_TEST(INT_IS_EQUAL(hgps.month, 12)); 49 | RUN_TEST(INT_IS_EQUAL(hgps.year, 2)); 50 | RUN_TEST(FLT_IS_EQUAL(hgps.utc_tow, 113851.00)); 51 | RUN_TEST(INT_IS_EQUAL(hgps.utc_wk, 1196)); 52 | RUN_TEST(INT_IS_EQUAL(hgps.leap_sec, 15)); 53 | RUN_TEST(INT_IS_EQUAL(hgps.clk_bias, 1930035)); 54 | RUN_TEST(FLT_IS_EQUAL(hgps.clk_drift, -2660.664)); 55 | RUN_TEST(INT_IS_EQUAL(hgps.tp_gran, 43)); 56 | 57 | /* Process and test block B */ 58 | lwgps_process(&hgps, gps_rx_data_B, strlen(gps_rx_data_B)); 59 | 60 | RUN_TEST(INT_IS_EQUAL(hgps.hours, 20)); 61 | RUN_TEST(INT_IS_EQUAL(hgps.minutes, 7)); 62 | RUN_TEST(INT_IS_EQUAL(hgps.seconds, 14)); 63 | RUN_TEST(INT_IS_EQUAL(hgps.date, 23)); 64 | RUN_TEST(INT_IS_EQUAL(hgps.month, 3)); 65 | RUN_TEST(INT_IS_EQUAL(hgps.year, 20)); 66 | RUN_TEST(FLT_IS_EQUAL(hgps.utc_tow, 158834.00)); 67 | RUN_TEST(INT_IS_EQUAL(hgps.utc_wk, 2098)); 68 | RUN_TEST(INT_IS_EQUAL(hgps.leap_sec, 18)); 69 | RUN_TEST(INT_IS_EQUAL(hgps.clk_bias, 536057)); 70 | RUN_TEST(FLT_IS_EQUAL(hgps.clk_drift, 257.043)); 71 | RUN_TEST(INT_IS_EQUAL(hgps.tp_gran, 16)); 72 | 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /tests/test_parse_standard/cmake.cmake: -------------------------------------------------------------------------------- 1 | # CMake include file 2 | 3 | # Add more sources 4 | target_sources(${CMAKE_PROJECT_NAME} PRIVATE 5 | ${CMAKE_CURRENT_LIST_DIR}/test_parse_standard.c 6 | ) 7 | 8 | # Options file 9 | set(LWGPS_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/lwgps_opts.h) -------------------------------------------------------------------------------- /tests/test_parse_standard/lwgps_opts.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwgps_opts_template.h 3 | * \brief LwGPS configuration file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwGPS - Lightweight GPS NMEA parser library. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: $2.2.0$ 33 | */ 34 | #ifndef LWGPS_HDR_OPTS_H 35 | #define LWGPS_HDR_OPTS_H 36 | 37 | #endif /* LWGPS_HDR_OPTS_H */ 38 | -------------------------------------------------------------------------------- /tests/test_parse_standard/test_parse_standard.c: -------------------------------------------------------------------------------- 1 | /** 2 | * \file test_parse_standard.c 3 | * \author Tilen MAJERLE 4 | * \brief 5 | * \version 0.1 6 | * \date 2025-03-30 7 | * 8 | * @copyright Copyright (c) 2025 9 | * 10 | */ 11 | #include 12 | #include 13 | #include "lwgps/lwgps.h" 14 | #include "test.h" 15 | 16 | /** 17 | * \brief Dummy data from GPS receiver 18 | */ 19 | const char gps_rx_data[] = "" 20 | "$GPRMC,183729,A,3907.356,N,12102.482,W,000.0,360.0,080301,015.5,E*6F\r\n" 21 | "$GPGGA,183730,3907.356,N,12102.482,W,1,05,1.6,646.4,M,-24.1,M,,*75\r\n" 22 | "$GPGSA,A,3,02,,,07,,09,24,26,,,,,1.6,1.6,1.0*3D\r\n" 23 | "$GPGSV,2,1,08,02,43,088,38,04,42,145,00,05,11,291,00,07,60,043,35*71\r\n" 24 | "$GPGSV,2,2,08,08,02,145,00,09,46,303,47,24,16,178,32,26,18,231,43*77\r\n" 25 | ""; 26 | 27 | /** 28 | * \brief Run the test of raw input data 29 | */ 30 | int 31 | test_run(void) { 32 | lwgps_t hgps; 33 | lwgps_init(&hgps); 34 | 35 | /* Process all input data */ 36 | lwgps_process(&hgps, gps_rx_data, strlen(gps_rx_data)); 37 | 38 | /* Run the test */ 39 | RUN_TEST(!INT_IS_EQUAL(hgps.is_valid, 0)); 40 | RUN_TEST(INT_IS_EQUAL(hgps.fix, 1)); 41 | RUN_TEST(INT_IS_EQUAL(hgps.fix_mode, 3)); 42 | RUN_TEST(FLT_IS_EQUAL(hgps.latitude, 39.1226000000)); 43 | RUN_TEST(FLT_IS_EQUAL(hgps.longitude, -121.0413666666)); 44 | RUN_TEST(FLT_IS_EQUAL(hgps.altitude, 646.4000000000)); 45 | RUN_TEST(FLT_IS_EQUAL(hgps.course, 360.0000000000)); 46 | RUN_TEST(INT_IS_EQUAL(hgps.dop_p, 1.6000000000)); 47 | RUN_TEST(INT_IS_EQUAL(hgps.dop_h, 1.6000000000)); 48 | RUN_TEST(INT_IS_EQUAL(hgps.dop_v, 1.0000000000)); 49 | RUN_TEST(FLT_IS_EQUAL(hgps.speed, 0.0000000000)); 50 | RUN_TEST(FLT_IS_EQUAL(hgps.geo_sep, -24.100000000)); 51 | RUN_TEST(FLT_IS_EQUAL(hgps.variation, 15.500000000)); 52 | RUN_TEST(INT_IS_EQUAL(hgps.sats_in_view, 8)); 53 | 54 | RUN_TEST(INT_IS_EQUAL(hgps.sats_in_use, 5)); 55 | RUN_TEST(INT_IS_EQUAL(hgps.satellites_ids[0], 2)); 56 | RUN_TEST(INT_IS_EQUAL(hgps.satellites_ids[1], 0)); 57 | RUN_TEST(INT_IS_EQUAL(hgps.satellites_ids[2], 0)); 58 | RUN_TEST(INT_IS_EQUAL(hgps.satellites_ids[3], 7)); 59 | RUN_TEST(INT_IS_EQUAL(hgps.satellites_ids[4], 0)); 60 | RUN_TEST(INT_IS_EQUAL(hgps.satellites_ids[5], 9)); 61 | RUN_TEST(INT_IS_EQUAL(hgps.satellites_ids[6], 24)); 62 | RUN_TEST(INT_IS_EQUAL(hgps.satellites_ids[7], 26)); 63 | RUN_TEST(INT_IS_EQUAL(hgps.satellites_ids[8], 0)); 64 | RUN_TEST(INT_IS_EQUAL(hgps.satellites_ids[9], 0)); 65 | RUN_TEST(INT_IS_EQUAL(hgps.satellites_ids[10], 0)); 66 | RUN_TEST(INT_IS_EQUAL(hgps.satellites_ids[11], 0)); 67 | 68 | RUN_TEST(INT_IS_EQUAL(hgps.time_valid, 1)); 69 | RUN_TEST(INT_IS_EQUAL(hgps.date_valid, 1)); 70 | RUN_TEST(INT_IS_EQUAL(hgps.date, 8)); 71 | RUN_TEST(INT_IS_EQUAL(hgps.month, 3)); 72 | RUN_TEST(INT_IS_EQUAL(hgps.year, 1)); 73 | RUN_TEST(INT_IS_EQUAL(hgps.hours, 18)); 74 | RUN_TEST(INT_IS_EQUAL(hgps.minutes, 37)); 75 | RUN_TEST(INT_IS_EQUAL(hgps.seconds, 30)); 76 | 77 | return 0; 78 | } 79 | --------------------------------------------------------------------------------