├── .github └── workflows │ └── compile.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── particle-logo.svg └── tracker-edge.svg ├── blueprint.yaml ├── config-schema.json ├── lib ├── Thermistor │ ├── library.properties │ ├── src │ │ └── thermistor.h │ └── test │ │ └── main.cpp └── bmi160 │ ├── library.properties │ ├── src │ ├── bmi160.cpp │ ├── bmi160.h │ └── bmi160regs.h │ └── test │ └── main.cpp ├── particle.include ├── project.properties └── src ├── EdgePlatform.cpp ├── EdgePlatform.h ├── IEdgePlatformConfiguration.h ├── IGnssLed.h ├── IoGnssLed.h ├── LocationPublish.cpp ├── LocationPublish.h ├── TrackerEvalConfiguration.h ├── TrackerOneConfiguration.h ├── build.mk ├── gnss_led.cpp ├── gnss_led.h ├── location_service.cpp ├── location_service.h ├── main.cpp ├── memfault_metrics_heartbeat_config.def ├── memfault_particle_user_config.h ├── motion_service.cpp ├── motion_service.h ├── temperature.cpp ├── temperature.h ├── tracker.cpp ├── tracker.h ├── tracker_cellular.cpp ├── tracker_cellular.h ├── tracker_config.h ├── tracker_fuelgauge.cpp ├── tracker_fuelgauge.h ├── tracker_imu.cpp ├── tracker_imu.h ├── tracker_location.cpp ├── tracker_location.h ├── tracker_motion.cpp ├── tracker_motion.h ├── tracker_rgb.cpp ├── tracker_rgb.h ├── tracker_shipping.cpp ├── tracker_shipping.h ├── tracker_sleep.cpp └── tracker_sleep.h /.github/workflows/compile.yml: -------------------------------------------------------------------------------- 1 | name: Compile 2 | 3 | on: [push] 4 | 5 | jobs: 6 | compile: 7 | strategy: 8 | matrix: 9 | device-os-version: [ '3.x', '4.x', '5.x', '6.x' ] 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v4 14 | with: 15 | submodules: true 16 | 17 | - name: Compile application 18 | id: compile 19 | uses: particle-iot/compile-action@v1 20 | with: 21 | particle-platform-name: 'tracker' 22 | device-os-version: '${{ matrix.device-os-version }}' 23 | 24 | - name: Upload artifact 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: tracker-artifacts-${{ matrix.device-os-version }} 28 | path: | 29 | ${{ steps.compile.outputs.firmware-path }} 30 | ${{ steps.compile.outputs.target-path }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | 4 | # Libraries 5 | *.lib 6 | 7 | # Shared objects (inc. Windows DLLs) 8 | *.dll 9 | *.so 10 | *.so.* 11 | *.dylib 12 | 13 | # Executables 14 | *.exe 15 | *.out 16 | *.app 17 | 18 | # build folder 19 | *.map 20 | *.lst 21 | *.d 22 | *.o 23 | 24 | # Platform-specific settings 25 | .settings/* 26 | .cproject 27 | .project 28 | .DS_Store 29 | 30 | .vscode 31 | target 32 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/gps-nmea-parser"] 2 | path = lib/gps-nmea-parser 3 | url = https://github.com/particle-iot/gps-nmea-parser.git 4 | [submodule "lib/gps-ublox"] 5 | path = lib/gps-ublox 6 | url = https://github.com/particle-iot/gps-ublox.git 7 | [submodule "lib/can-mcp25x"] 8 | path = lib/can-mcp25x 9 | url = https://github.com/particle-iot/can-mcp25x.git 10 | [submodule "lib/fw-config-service"] 11 | path = lib/fw-config-service 12 | url = https://github.com/particle-iot/fw-config-service.git 13 | [submodule "lib/geofence"] 14 | path = lib/geofence 15 | url = https://github.com/particle-iot/geofence.git 16 | [submodule "lib/memfault"] 17 | path = lib/memfault 18 | url = https://github.com/memfault/particle-firmware-library.git 19 | [submodule "lib/background-publish"] 20 | path = lib/background-publish 21 | url = https://github.com/particle-iot/background-publish.git 22 | [submodule "lib/disk-queue"] 23 | path = lib/disk-queue 24 | url = https://github.com/particle-iot/disk-queue.git 25 | [submodule "lib/model_gauge"] 26 | path = lib/model_gauge 27 | url = https://github.com/particle-iot/model_gauge.git 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v19 2 | 3 | ### COMPATIBILTY 4 | 5 | Must be built using device OS v3.2.0 or greater. 6 | 7 | ### FEATURES 8 | 9 | - Added satellite diagnostics configuration to publish satellite count and mean CN0. 10 | - Updated battery model for more accurate state-of-charge 11 | 12 | ### ENHANCEMENTS 13 | 14 | - Source can be built for device OS 3.2.0 to 6.x. 15 | 16 | ### BUGFIXES 17 | 18 | - Fixed thermistor characteristics for more accuracy. 19 | - Fixed excessive shutdown current for GNSS ephemeris battery during shipping mode. 20 | - Fixed Ublox M8U fix stability stability check to exclude bad acquistions. 21 | 22 | ### OTHER CHANGES 23 | 24 | - Some function and file cleanup to allow for abstraction and organization of source code. 25 | - Use of C++ templates to replace selected function pointer callbacks. 26 | 27 | 28 | ## v18 29 | 30 | ### COMPATIBILTY 31 | 32 | Must be built using device OS v3.2.0 or greater. 33 | 34 | ### FEATURES 35 | 36 | - Store and Forward feature for saving publish data while offline. 37 | - Added support for Memfault device monitoring. 38 | - Added battery charging configuration overrides. 39 | 40 | ### ENHANCEMENTS 41 | 42 | - Added a new log category for TrackerSleep class. 43 | - Added more control for GNSS module dead reckoning. 44 | - Added GNSS enhancements for time-to-first-fix (TTFF). 45 | 46 | ### BUGFIXES 47 | 48 | - Fix setting of HDOP to geofence zones. 49 | 50 | ### OTHER CHANGES 51 | 52 | - Add configuration schema file to the project. 53 | - Cleared several compile warnings. 54 | - Changes for building under device OS versions above 3.x. 55 | 56 | 57 | ## v17 58 | 59 | ### COMPATIBILTY 60 | 61 | Must be built using device OS v3.2.0 or greater. 62 | 63 | ### FEATURES 64 | 65 | - Geofence feature with settings for 4 circular zones. 66 | 67 | ### ENHANCEMENTS 68 | 69 | - Replaced application AM1805 RTC driver with new device OS watchdog calls. 70 | 71 | ### BUGFIXES 72 | 73 | - Disable the hardware watchdog whenever performing firmware updates over OTA. 74 | 75 | ### OTHER CHANGES 76 | 77 | - [TRACKER ONE] Changed the low battery evaluation interval to catch low state-of-charge sooner. 78 | 79 | 80 | ## v16 81 | 82 | ### COMPATIBILTY 83 | 84 | Must be built using device OS v3.0.0 or greater. 85 | 86 | ### FEATURES 87 | 88 | - Additional setting and implementation for GNSS initialization retries if failures are detected. 89 | 90 | ### ENHANCEMENTS 91 | 92 | - The ublox GPS library has been updated to provide performance counters. 93 | 94 | ### BUGFIXES 95 | 96 | - Fix to calculating sleep and execution times when the device is in areas of low connectivity. 97 | 98 | ### OTHER CHANGES 99 | 100 | - Switched several logging calls from the LOG() macro to the functional Log() method. 101 | 102 | 103 | ## v15 104 | 105 | ### COMPATIBILTY 106 | 107 | Must be built using device OS v3.0.0 or greater. 108 | 109 | ### FEATURES 110 | 111 | - Additional setting for GNSS lock stability criteria based on HDOP rather than using existing horizonatal accuracy. 112 | - Both HDOP and VDOP figures were added to outgoing location publishes. 113 | 114 | ### ENHANCEMENTS 115 | 116 | - The CAN bus library can be initialized in listen-only mode. 117 | - GPS ublox driver changes to detect errors during initialization and return immediately upon such errors. 118 | - An additional trigger named "err" will be sent in location publishes if GNSS module errors are encountered. 119 | - [TRACKER ONE] The GPS LED will flash rapidly if GNSS module errors are encountered. 120 | - WiFi scanning time has been shortened in order to reduce power usage. 121 | 122 | ### BUGFIXES 123 | 124 | - GNSS module power on and off sequence changes for modules that may power up in the wrong interface configuration. 125 | 126 | 127 | ## v14 128 | 129 | ### COMPATIBILTY 130 | 131 | Must be built using device OS v3.0.0-rc.2 or greater. 132 | 133 | ### FEATURES 134 | 135 | No new features. 136 | 137 | ### ENHANCEMENTS 138 | 139 | No new enhancements. 140 | 141 | ### BUGFIXES 142 | 143 | - Fuel gauge reports incorrect battery charge during sleep. 144 | 145 | 146 | ## v13 147 | 148 | ### COMPATIBILTY 149 | 150 | Must be built using device OS v3.0.0-rc.2 or greater. 151 | 152 | ### FEATURES 153 | 154 | - Added reset command from cloud. 155 | 156 | ### ENHANCEMENTS 157 | 158 | - Updated ublox GNSS drivers to allow UDR model changes. 159 | 160 | ### BUGFIXES 161 | 162 | - Increased I2C timeouts for the AM1805 RTC driver as a precaution against crashes. 163 | - [TRACKER ONE] Fixed GNSS LED state to make sure it is off when going into shipping mode. 164 | 165 | 166 | ## v12 167 | 168 | ### COMPATIBILTY 169 | 170 | Must be built using device OS v3.0.0-rc.2 or greater. 171 | 172 | ### FEATURES 173 | 174 | - Added enhanced location services (Location Fusion). 175 | - Added GNSS speed to location publishes. 176 | - Added dynamic charge current control over temperature. 177 | - [TRACKER ONE] Added IO/CAN power controls for configuration in application. 178 | 179 | ### ENHANCEMENTS 180 | 181 | - Changed priorities of first and immediate publishes. 182 | - Reduced application footprint by migrating to wiring library mutexes. 183 | - Allow device OS to control battery charge enablement. 184 | 185 | ### BUGFIXES 186 | 187 | - Fixed issue of default chip select SPI initialization effect on D5. 188 | - Fixed power management issue with incorrectly written DCT values. 189 | - Fixed issue with multiple publishes at boot. 190 | 191 | 192 | ## v11 193 | 194 | ### COMPATIBILTY 195 | 196 | Must be built using device OS v2.0.0-rc.3 or greater. 197 | 198 | ### FEATURES 199 | 200 | - Added configurable setting to enable or disable parsing of location publish acknowledgements from the cloud. 201 | 202 | ### ENHANCEMENTS 203 | 204 | - Changed CAN library enumerations for setting power modes so they do not collide with symbols in other device driver libraries. 205 | - Changed CAN library so that cloud compiles are possible. 206 | 207 | ### BUGFIXES 208 | 209 | - Fixed issue of no location publishes when GNSS signal quality is low and out-of-lock. 210 | - [TRACKER ONE] Changed BLE antenna configuration from internal to external. 211 | 212 | 213 | ## v10 214 | 215 | ### COMPATIBILTY 216 | 217 | Must be built using device OS v2.0.0-rc.3 or greater. 218 | 219 | ### FEATURES 220 | 221 | - Added sleep feature to place device into ultra low power mode sleep and wake periodically for timed and triggered events. 222 | 223 | ### ENHANCEMENTS 224 | 225 | - Improved GPS lock status with a stability check of horizontal accuracy. Location publishes will be held off until the accuracy is stable. 226 | - Placed CAN and ESP32 devices into low power mode to reduce overall power consumption 227 | - Enabled the RTC watchdog by default 228 | - [TRACKER ONE] Lowered low battery detect shutoff from 8% state-of-charge to 2% to gain more operational time. Lowered battery warning from 15% to 8%. 229 | 230 | ### BUGFIXES 231 | 232 | - [TRACKER ONE] Fixed intermittent issues with battery charge enablement/disablement when lower than 0 degrees C and above 50 degrees C 233 | 234 | 235 | ## v9 236 | 237 | ### FEATURES 238 | 239 | - Added lock trigger that signifies changes in GNSS lock status 240 | - Added CAN bus library as submodule 241 | - [TRACKER ONE] Disable battery charging when temperatures fall below 0 degrees C or rise above 50 degrees C in order to protect battery outside of operational temperatures 242 | - [TRACKER ONE] Force device to enter shipping mode when battery charge falls below 8% and publish location message with battery low trigger. Publish location message with battery warning when charge falls below 15%. 243 | 244 | ### ENHANCEMENTS 245 | 246 | - Improved ease-of-use in git clones of repository submodules by shifting from SSH to HTTPS URL links 247 | - Changed default minimum and maximum publish intervals to 15 minutes and 1 hour respectively in order to conserve user data limits 248 | 249 | ### BUGFIXES 250 | 251 | - Fixed BMI160 (IMU) power consumption when placing devce into suspend mode (sleep) 252 | - Fixed BMI160 (IMU) SPI bus access timing differences when operating in normal versus low power and suspend modes 253 | - Fixed ublox GNSS module library warnings for packed vs unpacked structure casting 254 | - Fixed issue with parsing input of malformed JSON configuration commands 255 | - Fixed BMI160 (IMU) configuration settings to return to default values upon factory reset 256 | 257 | 258 | ## v8 259 | 260 | ### FEATURES 261 | 262 | - Detect and differentiate Tracker One and evaluation board products 263 | - Receive USB bus messages to configure parameters or command firmware 264 | 265 | ### ENHANCEMENTS 266 | 267 | - Changed several tracker classes to singleton for ease-of-use 268 | 269 | ### BUGFIXES 270 | 271 | - Fixed memory leak with cloud service messages to be published 272 | - Fixed memory leak related to GNSS power cycling 273 | 274 | 275 | ## v7 276 | 277 | ### FEATURES 278 | 279 | - Initial repository for Tracker application reference source 280 | - Publish location on minimum/maximum publish interval times 281 | - Publish location on motion events detected on the Inertial Measurement Unit (IMU) 282 | - Publish location on high g events detected on the IMU 283 | - Publish location on radius boundary since the last publish 284 | 285 | ### ENHANCEMENTS 286 | 287 | 288 | ### BUGFIXES 289 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributions 2 | 3 | All contributors must first sign the [Particle Individual Contributor License Agreement (CLA)](https://docs.google.com/a/spark.io/forms/d/1_2P-vRKGUFg5bmpcKLHO_qNZWGi5HKYnfrrkd-sbZoA/viewform), which is based on the Google CLA, and provides the Particle team a license to re-distribute your contributions. 4 | 5 | Whenever possible, please follow these guidelines for contributions: 6 | 7 | - Keep each pull request small and focused on a single feature or bug fix. 8 | - Familiarize yourself with the code base, and follow the formatting principles adhered to in the surrounding code. 9 | - Wherever possible, provide unit tests for your contributions. 10 | - If the changes have an impact application developers, then those changes should be described in the documentation. 11 | 12 | 13 | # Tests and Documentation 14 | 15 | Any changes that affect firmware application developers, such as a new class or API, or change in existing behavior should have an accompanying PR to the `particle/docs` repo describing the changes. This ensures the documentation is kept up to date with changes to the firmware. 16 | 17 | 18 | # Subtrees 19 | 20 | The repository imports content from other repos via git subtrees. These are the current 21 | subtrees: 22 | 23 | - lib/AM1805 24 | 25 | (you can find an up-to-date list by running git log | grep git-subtree-dir | awk '{ print $2 }') 26 | 27 | When making commits, do not mix commits that span subtrees. E.g. a commit that posts 28 | changes to files in lib/AM1805 and src should be avoided. 29 | No real harm is done, but the commit messages can be confusing since those files outside the subtree 30 | will be removed when the subtree is pushed to the upstream repo. 31 | 32 | To avoid any issues with subtres, it's simplest to make all changes to the upstream repo via 33 | PRs from a separate working copy, just as you would with any regular repo. Then pull these changes 34 | into the subtree in this repo. 35 | 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Particle Tracker Edge Firmware 3 | 4 | Application reference firmware for the Particle Tracker. 5 | 6 | # Resources 7 | 8 | - [Latest Release](https://github.com/particle-iot/tracker-edge/releases) 9 | - [Changelog](CHANGELOG.md) 10 | 11 | ### CREDITS AND ATTRIBUTIONS 12 | 13 | The firmware uses the GNU GCC toolchain for ARM Cortex-M processors, standard peripheral libraries and Arduino's implementation of Wiring. 14 | 15 | ### LICENSE 16 | 17 | Unless stated elsewhere, file headers or otherwise, all files herein are licensed under an Apache License, Version 2.0. For more information, please read the LICENSE file. 18 | 19 | If you have questions about software licensing, please contact Particle [support](https://support.particle.io/). 20 | 21 | 22 | ### LICENSE FAQ 23 | 24 | **This firmware is released under Apache License, Version 2.0, what does that mean for you?** 25 | 26 | * You may use this commercially to build applications for your devices! You **DO NOT** need to distribute your object files or the source code of your application under Apache License. Your source can be released under a non-Apache license. Your source code belongs to you when you build an application using this reference firmware. 27 | 28 | **When am I required to share my code?** 29 | 30 | * You are **NOT required** to share your application firmware binaries, source, or object files when linking against libraries or System Firmware licensed under LGPL. 31 | 32 | **Why?** 33 | 34 | * This license allows businesses to confidently build firmware and make devices without risk to their intellectual property, while at the same time helping the community benefit from non-proprietary contributions to the shared reference firmware. 35 | 36 | **Questions / Concerns?** 37 | 38 | * Particle intends for this firmware to be commercially useful and safe for our community of makers and enterprises. Please [Contact Us](https://support.particle.io/) if you have any questions or concerns, or if you require special licensing. 39 | 40 | _(Note! This FAQ isn't meant to be legal advice, if you're unsure, please consult an attorney)_ 41 | 42 | 43 | ### COMPILE & FLASH WITH WORKBENCH 44 | 45 | This application must be built with device OS version 2.0.0-rc.3 and above. 46 | 47 | 1. Clone this repository `$ git clone git@github.com:particle-iot/tracker-edge.git && cd ./tracker-edge` 48 | 2. Init & Update Submodules `$ git submodule update --init --recursive` 49 | 3. Open Particle Workbench 50 | 4. Run the `Particle: Import Project` command, follow the prompts, and wait for the project to load 51 | 5. Run the `Particle: Configure Workspace for Device` command and select a compatible Device OS version and the `tracker` platform when prompted ([docs](https://docs.particle.io/tutorials/developer-tools/workbench/#cloud-build-and-flash)) 52 | 6. Connect your device 53 | 7. Compile & Flash! 54 | 55 | ### CONTRIBUTE 56 | 57 | Want to contribute to the Particle tracker edge firmware project? Follow [this link](CONTRIBUTING.md) to find out how. 58 | 59 | ### CONNECT 60 | 61 | Having problems or have awesome suggestions? Connect with us [here.](https://community.particle.io/c/tracking-system). 62 | 63 | Enterprise customers can contact [support](https://support.particle.io/). 64 | -------------------------------------------------------------------------------- /assets/particle-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/tracker-edge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /blueprint.yaml: -------------------------------------------------------------------------------- 1 | slug: particle-tracker-edge 2 | type: Solution 3 | category: "Reference applications" 4 | expertiseLevel: "Intermediate" 5 | tags: ["Tracker One", "Sensors", "Industrial"] 6 | icon: assets/tracker-edge.svg 7 | gitrepo: https://github.com/particle-iot/tracker-edge 8 | name: "Tracker Edge" 9 | shortDescription: "A complete reference asset tracking solution." 10 | version: 1.19.0 11 | models: [] 12 | language: [ "C++" ] 13 | cloudServices: 14 | - name: Integrations 15 | - name: Logic 16 | - name: Ledger 17 | - name: Lake 18 | integrations: [ ] 19 | supportedDevices: 20 | - name: Tracker One 21 | link: https://www.particle.io/tracker-one/ 22 | - name: T-SoM 23 | link: https://www.particle.io/tracker-som/ 24 | hardwareDependencies: 25 | - name: Supported device 26 | introduction: | 27 | Tracker Edge is a flexible firmware solution that can be easily reconfigured for various use cases. 28 | 29 | It integrates with Particle's Tracking System for automatic location updates and is open-source, allowing for customization to meet specific needs. It can be used off-the-shelf, semi-customized with added sensors and behavior modifications, or fully customized modifying the code. 30 | 31 | description: | 32 | A comprehensive asset tracking solution that includes location services, various power modes, and extensive configuration capabilities. 33 | 34 | Key Features: 35 | - Real-Time Event Publishing: Publishes location on movement events, high-G events, and boundary crossings. 36 | - Location and Diagnostics: Publishes satellite count and signal metrics for GNSS health. Enhanced location tracking with features like geofencing, circular zones, and high-accuracy GNSS data for both speed and lock stability. 37 | - Battery and Power Management: Optimized battery state-of-charge accuracy, shutdown current, and configurable power modes. Includes control over battery charge limits based on operating temperatures to extend device life. 38 | - Storage and Data Persistence: Store-and-forward feature enables data retention while offline, with reliable data uploads once connectivity is restored. 39 | - Cloud Configuration: Includes cloud-configurable settings for various aspects like GNSS dead reckoning, power settings, and publish rates. 40 | - Tracking in Extreme Conditions: Accurate tracking in poor conditions, sleep modes to conserve power, and configurable publish rates based on movement or location triggers. 41 | - Device Health and Diagnostics: Built-in support for the Memfault platform for advanced device monitoring and diagnostics, as well as detailed event logging. 42 | 43 | additionalResources: 44 | -------------------------------------------------------------------------------- /lib/Thermistor/library.properties: -------------------------------------------------------------------------------- 1 | name=Thermistor 2 | version=0.0.2 3 | author=Eric Berseth 4 | license=Apache License, Version 2.0 5 | sentence=Library to implement a thermistor temperature reader 6 | architectures=* 7 | -------------------------------------------------------------------------------- /lib/Thermistor/src/thermistor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include 20 | #include "Particle.h" 21 | 22 | namespace particle { 23 | 24 | #if ( SYSTEM_VERSION >= SYSTEM_VERSION_DEFAULT(6, 2, 0) ) 25 | #define PIN_VALIDATE hal_pin_validate_function 26 | #else 27 | #define PIN_VALIDATE HAL_Validate_Pin_Function 28 | #endif // SYSTEM_VERSION 29 | 30 | /** 31 | * @brief Resistor divider circuit used for thermistor. 32 | * 33 | */ 34 | enum class ThermistorCircuit { 35 | NONE, /**< No configuration */ 36 | LOW_SIDE_DIVIDER, /**< Thermistor is on the low side of the divider. V = Vcc * Rt / (Rt + Rfixed) */ 37 | HIGH_SIDE_DIVIDER, /**< Thermistor is on the high side of the divider. V = Vcc * Rfixed / (Rt + Rfixed) */ 38 | }; 39 | 40 | /** 41 | * @brief Type of thermistor. 42 | * 43 | */ 44 | enum class ThermistorType { 45 | NONE, /**< No configuration */ 46 | POSITIVE_COEFF, /**< Positive temperature coefficient thermistor */ 47 | NEGATIVE_COEFF, /**< Negative temperature coefficient thermistor */ 48 | }; 49 | 50 | /** 51 | * @brief Structure to configure thermistor reading. 52 | * 53 | */ 54 | struct ThermistorConfig { 55 | ThermistorCircuit circuit; /**< Circuit details for resistor divider. */ 56 | ThermistorType type; /**< Type of thermistor. */ 57 | float beta; /**< Beta parameter for thermistor. */ 58 | float t0; /**< Temperature for reference resistance. */ 59 | float r0; /**< Reference resitance at reference temperature. */ 60 | float fixedR; /**< Fixed resistor used in resistor divider with thermistor. */ 61 | float adcResolution; /**< Maximum ADC value. */ 62 | float minTemperature; /**< Minumum temperature that can be measured with sensor. */ 63 | float maxTemperature; /**< Maximum temperature that can be measured with sensor. */ 64 | }; 65 | 66 | /** 67 | * @brief Thermistor class to read from various resistor topologies and types. 68 | * 69 | */ 70 | class Thermistor { 71 | public: 72 | const float ThermistorError = -300.0; /**< Error value */ 73 | 74 | /** 75 | * @brief Construct a new Thermistor object 76 | * 77 | */ 78 | Thermistor() : 79 | inputPin_(PIN_INVALID), 80 | config_((ThermistorConfig){ 81 | .circuit = ThermistorCircuit::NONE, 82 | .type = ThermistorType::NONE, 83 | .beta = 1.0, 84 | .t0 = 1.0, 85 | .r0 = 1.0, 86 | .fixedR = 1.0, 87 | .adcResolution = 4096.0, 88 | .minTemperature = 1.0, 89 | .maxTemperature = 1.0}), 90 | ratioNormalize_(1.0f) { 91 | 92 | } 93 | 94 | /** 95 | * @brief Destroy the Thermistor object 96 | * 97 | */ 98 | virtual ~Thermistor() { 99 | 100 | } 101 | 102 | /** 103 | * @brief Initialize thermistor settings for particular circuit and thermistor type. 104 | * 105 | * @param inputPin Analog input pin to read voltage from voltage divider. 106 | * @param config Configuration settings. 107 | * @retval SYSTEM_ERROR_NONE 108 | * @retval SYSTEM_ERROR_NOT_SUPPORTED 109 | * @retval SYSTEM_ERROR_INVALID_ARGUMENT 110 | * @retval SYSTEM_ERROR_ALREADY_EXISTS 111 | * @retval SYSTEM_ERROR_IO 112 | */ 113 | int begin(pin_t inputPin, ThermistorConfig& config) { 114 | // Only negative coefficient thermistors are supported 115 | CHECK_TRUE((config.type == ThermistorType::NEGATIVE_COEFF), SYSTEM_ERROR_NOT_SUPPORTED); 116 | 117 | // Check the pin type 118 | pin_t checkPin = inputPin; 119 | CHECK_TRUE((checkPin >= FIRST_ANALOG_PIN), SYSTEM_ERROR_INVALID_ARGUMENT); 120 | CHECK_TRUE((checkPin < FIRST_ANALOG_PIN + TOTAL_ANALOG_PINS), SYSTEM_ERROR_INVALID_ARGUMENT); 121 | checkPin += FIRST_ANALOG_PIN; 122 | CHECK_TRUE(pinAvailable(checkPin), SYSTEM_ERROR_ALREADY_EXISTS); 123 | CHECK_TRUE((PIN_VALIDATE(checkPin, PF_ADC) == PF_ADC), SYSTEM_ERROR_IO); 124 | 125 | // Various configuration checks 126 | CHECK_TRUE((config.adcResolution > 1.0), SYSTEM_ERROR_INVALID_ARGUMENT); 127 | CHECK_TRUE((config.r0 > 0.0), SYSTEM_ERROR_INVALID_ARGUMENT); 128 | CHECK_TRUE((config.fixedR > 0.0), SYSTEM_ERROR_INVALID_ARGUMENT); 129 | CHECK_TRUE((config.t0 > -kelvinCelcius_), SYSTEM_ERROR_INVALID_ARGUMENT); 130 | 131 | inputPin_ = inputPin; 132 | config_ = config; 133 | 134 | // Compute constants so that they do not need to be calculated on every read 135 | ratioNormalize_ = config_.fixedR / config_.r0 * expf(config_.beta / (config_.t0 + kelvinCelcius_)); 136 | 137 | return SYSTEM_ERROR_NONE; 138 | } 139 | 140 | /** 141 | * @brief Get the current temperature. 142 | * 143 | * @return float Temperature in degrees Celcius. 144 | */ 145 | float getTemperature() { 146 | int32_t val = analogRead(inputPin_); 147 | float vRatio = val / config_.adcResolution; 148 | float rRatio = 1.0; 149 | 150 | switch (config_.circuit) { 151 | case ThermistorCircuit::LOW_SIDE_DIVIDER: { 152 | rRatio = vRatio / (1.0 - vRatio); 153 | break; 154 | } 155 | 156 | case ThermistorCircuit::HIGH_SIDE_DIVIDER: { 157 | rRatio = (1.0 - vRatio) / vRatio; 158 | break; 159 | } 160 | 161 | default: { 162 | return SYSTEM_ERROR_INVALID_STATE; 163 | } 164 | 165 | } 166 | 167 | // T = B / ln(rratio) 168 | rRatio *= ratioNormalize_; 169 | float temperature = config_.beta / logf(rRatio) - kelvinCelcius_; 170 | if ((temperature < config_.minTemperature) || (temperature > config_.maxTemperature)) { 171 | temperature = ThermistorError; 172 | } 173 | 174 | return temperature; 175 | } 176 | 177 | private: 178 | const float kelvinCelcius_ = 273.15f; // 0 degree Kelvin = -273.15 degrees C 179 | 180 | pin_t inputPin_; 181 | ThermistorConfig config_; 182 | float ratioNormalize_; 183 | }; // class Thermistor 184 | 185 | } // namespace particle 186 | -------------------------------------------------------------------------------- /lib/Thermistor/test/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #include "Particle.h" 18 | #include "thermistor.h" 19 | 20 | /* 21 | LOG_LEVEL_ALL : special value that can be used to enable logging of all messages 22 | LOG_LEVEL_TRACE : verbose output for debugging purposes 23 | LOG_LEVEL_INFO : regular information messages 24 | LOG_LEVEL_WARN : warnings and non-critical errors 25 | LOG_LEVEL_ERROR : error messages 26 | LOG_LEVEL_NONE : special value that can be used to disable logging of any messages 27 | */ 28 | // SerialLogHandler logHandler(115200, LOG_LEVEL_ALL, 29 | // { 30 | // {"app", LOG_LEVEL_ALL}, 31 | // }); 32 | 33 | SYSTEM_THREAD(ENABLED); 34 | SYSTEM_MODE(MANUAL); 35 | 36 | #define TEST_THERMISTOR_PIN (A0) 37 | static unsigned long UPDATE_DELAY = 1000; // milliseconds 38 | 39 | // Configuration based on Panasonic ERTJ1VR104FM NTC thermistor 40 | ThermistorConfig config = { 41 | .circuit = ThermistorCircuit::HIGH_SIDE_DIVIDER, // Thermistor is between VCC and the ADC input 42 | .type = ThermistorType::NEGATIVE_COEFF, // NTC type 43 | .beta = 4200.0, // B(25/50) figure 44 | .t0 = 25.0, // degrees C 45 | .r0 = 100000.0, // ohms 46 | .fixedR = 100000.0, // ohms 47 | .adcResolution = 4096.0, // values full-range 48 | .minTemperature = -40.0, // minimum temperature to represent 49 | .maxTemperature = 150.0 // maximum temperature to represent 50 | }; 51 | 52 | Thermistor thermistor; 53 | 54 | // !!! Note: your experience will be much more rewarding if you use Serial1 versus Serial 55 | 56 | void setup() { 57 | Serial1.begin(115200); 58 | //while(!Serial.isConnected()); 59 | 60 | volatile int ret = -1; 61 | ret = thermistor.begin(TEST_THERMISTOR_PIN, config); 62 | 63 | if (ret != SYSTEM_ERROR_NONE) { 64 | Serial1.printf("Thermistor.begin() failed.\r\n"); 65 | } 66 | } 67 | 68 | volatile bool once = false; 69 | 70 | void loop() { 71 | float temperature = thermistor.getTemperature(); 72 | Serial1.printlnf("T(degC) = %.1f", temperature); 73 | 74 | delay(UPDATE_DELAY); 75 | } 76 | -------------------------------------------------------------------------------- /lib/bmi160/library.properties: -------------------------------------------------------------------------------- 1 | # Fill in information about your library then remove # from the start of lines 2 | # https://docs.particle.io/guide/tools-and-features/libraries/#library-properties-fields 3 | name=bmi160 4 | version=1.0.0 5 | author=Eric Berseth 6 | # license=insert your choice of license here 7 | # sentence=one sentence description of this library 8 | # paragraph=a longer description of this library, always prepended with sentence when shown 9 | # url=the URL of the project, like https://github.com/mygithub_user/my_repo 10 | # repository=git repository for the project, like https://github.com/mygithub_user/my_repo.git 11 | # architectures=a list of supported boards if this library is hardware dependent, like particle-photon,particle-electron 12 | -------------------------------------------------------------------------------- /lib/bmi160/src/bmi160.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "Particle.h" 20 | 21 | namespace particle { 22 | 23 | struct Bmi160Accelerometer { 24 | float x; 25 | float y; 26 | float z; 27 | }; 28 | 29 | enum class Bmi160AccelSignificantMotionSkip { 30 | SIG_MOTION_SKIP_1_5_S = 0, 31 | SIG_MOTION_SKIP_3_0_S = 1, 32 | SIG_MOTION_SKIP_6_0_S = 2, 33 | SIG_MOTION_SKIP_12_S = 3, 34 | }; 35 | 36 | enum class Bmi160AccelSignificantMotionProof { 37 | SIG_MOTION_PROOF_0_25_S = 0, 38 | SIG_MOTION_PROOF_0_5_S = 1, 39 | SIG_MOTION_PROOF_1_S = 2, 40 | SIG_MOTION_PROOF_2_S = 3, 41 | }; 42 | 43 | enum class Bmi160AccelMotionMode { 44 | ACCEL_MOTION_MODE_ANY, 45 | ACCEL_MOTION_MODE_SIGNIFICANT, 46 | }; 47 | 48 | struct Bmi160AccelMotionConfig { 49 | Bmi160AccelMotionMode mode; 50 | float motionThreshold; 51 | unsigned motionDuration; 52 | Bmi160AccelSignificantMotionSkip motionSkip; 53 | Bmi160AccelSignificantMotionProof motionProof; 54 | }; 55 | 56 | struct Bmi160AccelHighGConfig { 57 | float threshold; 58 | float duration; 59 | float hysteresis; 60 | }; 61 | 62 | struct Bmi160AccelerometerConfig { 63 | float rate; 64 | float range; 65 | }; 66 | 67 | enum class Bmi160InterruptSource { 68 | INTR_NONE, 69 | INTR_STEP, 70 | INTR_SIGNIFICANT_MOTION, 71 | INTR_ANY_MOTION, 72 | INTR_PMU_TRIGGER, 73 | INTR_DOUBLE_TAP, 74 | INTR_SINGLE_TAP, 75 | INTR_ORIENTATION, 76 | INTR_FLAT, 77 | INTR_HIGH_G, 78 | INTR_LOW_G, 79 | INTR_DATA_READY, 80 | INTR_FIFO_FULL, 81 | INTR_FIFO_WATERMARK, 82 | INTR_NO_MOTION, 83 | }; 84 | 85 | enum Bmi160PmuAccel: uint8_t { 86 | PMU_STATUS_ACC_SUSPEND = 0x0, 87 | PMU_STATUS_ACC_NORMAL = 0x1, 88 | PMU_STATUS_ACC_LOW = 0x2, 89 | }; 90 | 91 | enum Bmi160PmuGyro: uint8_t { 92 | PMU_STATUS_GYRO_SUSPEND = 0x0, 93 | PMU_STATUS_GYRO_NORMAL = 0x1, 94 | PMU_STATUS_GYRO_FAST_START_UP = 0x3, 95 | }; 96 | 97 | 98 | class Bmi160 { 99 | public: 100 | enum class Bmi160EventType { 101 | NONE, 102 | BREAK, 103 | SYNC, 104 | }; 105 | 106 | enum class Bmi160PowerState { 107 | PMU_SUSPEND, 108 | PMU_NORMAL, 109 | PMU_LOW_POWER, 110 | PMU_FAST_STARTUP, 111 | }; 112 | 113 | // TODO - remove Bmi160InterruptSourceRaw 114 | enum Bmi160InterruptSourceRaw : uint32_t { 115 | BMI_INTR_BIT_STEP = (1UL << 0), 116 | BMI_INTR_BIT_SIGNIFICANT_MOTION = (1UL << 1), 117 | BMI_INTR_BIT_ANY_MOTION = (1UL << 2), 118 | BMI_INTR_BIT_PMU_TRIGGER = (1UL << 3), 119 | BMI_INTR_BIT_DOUBLE_TAP = (1UL << 4), 120 | BMI_INTR_BIT_SINGLE_TAP = (1UL << 5), 121 | BMI_INTR_BIT_ORIENTATION = (1UL << 6), 122 | BMI_INTR_BIT_FLAT = (1UL << 7), 123 | // 8 + 0 124 | // 8 + 1 125 | BMI_INTR_BIT_HIGH_G = (1UL << (8 + 2)), 126 | BMI_INTR_BIT_LOW_G = (1UL << (8 + 3)), 127 | BMI_INTR_BIT_DATA_READY = (1UL << (8 + 4)), 128 | BMI_INTR_BIT_FIFO_FULL = (1UL << (8 + 5)), 129 | BMI_INTR_BIT_FIFO_WATERMARK = (1UL << (8 + 6)), 130 | BMI_INTR_BIT_NO_MOTION = (1UL << (8 + 7)), 131 | 132 | BMI_INTR_BIT_ANY_FIRST_X = (1UL << (16 + 0)), 133 | BMI_INTR_BIT_ANY_FIRST_Y = (1UL << (16 + 1)), 134 | BMI_INTR_BIT_ANY_FIRST_Z = (1UL << (16 + 2)), 135 | BMI_INTR_BIT_ANY_SIGN = (1UL << (16 + 3)), 136 | BMI_INTR_BIT_TAP_FIRST_X = (1UL << (16 + 4)), 137 | BMI_INTR_BIT_TAP_FIRST_Y = (1UL << (16 + 5)), 138 | BMI_INTR_BIT_TAP_FIRST_Z = (1UL << (16 + 6)), 139 | BMI_INTR_BIT_TAP_SIGN = (1UL << (16 + 7)), 140 | 141 | BMI_INTR_BIT_HIGH_FIRST_X = (1UL << (24 + 0)), 142 | BMI_INTR_BIT_HIGH_FIRST_Y = (1UL << (24 + 1)), 143 | BMI_INTR_BIT_HIGH_FIRST_Z = (1UL << (24 + 2)), 144 | BMI_INTR_BIT_HIGH_SIGN = (1UL << (24 + 3)), 145 | BMI_INTR_BIT_ORIENT_0 = (1UL << (24 + 4)), 146 | BMI_INTR_BIT_ORIENT_1 = (1UL << (24 + 5)), 147 | BMI_INTR_BIT_ORIENT_2 = (1UL << (24 + 6)), 148 | BMI_INTR_BIT_ORIENT_FLAT = (1UL << (24 + 7)), 149 | }; 150 | 151 | int begin(const TwoWire* interface, uint8_t address, pin_t interruptPin, size_t eventDepth = 8); 152 | int begin(const SPIClass& interface, pin_t selectPin, pin_t interruptPin, size_t eventDepth = 8); 153 | int end(); 154 | int reset(); 155 | int sleep(); 156 | int wakeup(); 157 | int syncEvent(Bmi160EventType event); 158 | int waitOnEvent(Bmi160EventType& event, system_tick_t timeout); 159 | 160 | int getChipId(uint8_t& val); 161 | 162 | int initAccelerometer(Bmi160AccelerometerConfig& config, bool feedback = false); 163 | int getAccelerometer(Bmi160Accelerometer& data); 164 | int getAccelerometerPmu(Bmi160PowerState& pmu); 165 | int initMotion(Bmi160AccelMotionConfig& config, bool feedback = false); 166 | int initHighG(Bmi160AccelHighGConfig& config, bool feedback = false); 167 | int startMotionDetect(); 168 | int stopMotionDetect(); 169 | int startHighGDetect(); 170 | int stopHighGDetect(); 171 | 172 | int getStatus(uint32_t& val, bool clear = false); 173 | bool isMotionDetect(uint32_t val); 174 | bool isHighGDetect(uint32_t val); 175 | 176 | static Bmi160& getInstance(); 177 | 178 | private: 179 | enum class InterfaceType { 180 | BMI_I2C, 181 | BMI_SPI 182 | }; 183 | 184 | Bmi160(); 185 | ~Bmi160(); 186 | 187 | int setSpiMode(); 188 | int initialize(); 189 | int cleanup(); 190 | void bmi160Handler(); 191 | 192 | float convertValue(float val, float toRange, float fromRange); 193 | uint8_t convertRateToOdr(float rate); 194 | float convertOdrToRate(uint8_t odr); 195 | int setAccelRange(float& range, bool feedback = false); 196 | int setAccelRate(float& rate, bool feedback = false); 197 | int setAccelMotionThreshold(float& threshold, bool feedback = false); 198 | int setAccelMotionDuration(unsigned& duration, bool feedback = false); 199 | int setAccelMotionSkip(Bmi160AccelSignificantMotionSkip skip); 200 | int setAccelMotionProof(Bmi160AccelSignificantMotionProof proof); 201 | int setAccelHighGThreshold(float& threshold, bool feedback = false); 202 | int setAccelHighGDuration(float& duration, bool feedback = false); 203 | int setAccelHighGHysteresis(float& hysteresis, bool feedback = false); 204 | 205 | int writeRegister(uint8_t reg, uint8_t val); 206 | int readRegister(uint8_t reg, uint8_t* val, int length = 1); 207 | 208 | InterfaceType type_; 209 | TwoWire* wire_; 210 | uint8_t address_; 211 | SPIClass* spi_; 212 | pin_t csPin_; 213 | const __SPISettings spiSettings_; 214 | pin_t intPin_; 215 | bool initialized_; 216 | Bmi160PmuAccel accelPmu_; 217 | Bmi160PmuGyro gyroPmu_; 218 | int rangeAccel_; 219 | float rateAccel_; 220 | uint8_t latchShadow_; 221 | os_queue_t motionSyncQueue_; 222 | static RecursiveMutex mutex_; 223 | }; // class Bmi160 224 | 225 | #define BMI160 Bmi160::getInstance() 226 | 227 | } // namespace particle 228 | -------------------------------------------------------------------------------- /lib/bmi160/test/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #include "Particle.h" 18 | #include "bmi160.h" 19 | 20 | #define USE_BMI160_SPI (1) 21 | #define USE_BMI160_I2C (0) 22 | 23 | #if (USE_BMI160_SPI == USE_BMI160_I2C) 24 | #error "SPI or I2C interface must be selected" 25 | #endif 26 | 27 | #define SPI_XFACE (SPI1) 28 | #define SPI_CS_PIN (SEN_CS) 29 | 30 | #define I2C_INTERFACE (&Wire) 31 | #define I2C_ADDRESS (0x68) 32 | 33 | #define INT_PIN (SEN_INT) 34 | 35 | /* 36 | LOG_LEVEL_ALL : special value that can be used to enable logging of all messages 37 | LOG_LEVEL_TRACE : verbose output for debugging purposes 38 | LOG_LEVEL_INFO : regular information messages 39 | LOG_LEVEL_WARN : warnings and non-critical errors 40 | LOG_LEVEL_ERROR : error messages 41 | LOG_LEVEL_NONE : special value that can be used to disable logging of any messages 42 | */ 43 | // SerialLogHandler logHandler(115200, LOG_LEVEL_ALL, 44 | // { 45 | // {"app", LOG_LEVEL_ALL}, 46 | // }); 47 | 48 | SYSTEM_THREAD(ENABLED); 49 | SYSTEM_MODE(MANUAL); 50 | 51 | Bmi160AccelerometerConfig config = { 52 | .rate = 100.0, // Hz [0.78Hz -> 1600Hz] 53 | .range = 16.0, // g [2g, 4g, 8g, 16g] 54 | }; 55 | 56 | Bmi160AccelMotionConfig configMotion = { 57 | .mode = Bmi160AccelMotionMode::ACCEL_MOTION_MODE_ANY, 58 | .motionThreshold = 0.25, // g [up to range] 59 | .motionDuration = 4, // counts of rate period [1 -> 4] 60 | .motionSkip = Bmi160AccelSignificantMotionSkip::SIG_MOTION_SKIP_1_5_S, // seconds [1.5s, 3s, 6s, 12s] 61 | .motionProof = Bmi160AccelSignificantMotionProof::SIG_MOTION_PROOF_0_25_S, // seconds [0.25s, 0.5s, 1s, 2s] 62 | }; 63 | 64 | Bmi160AccelHighGConfig configHighG = { 65 | .threshold = 4.0, // g [up to range] 66 | .duration = 0.0025, // seconds 67 | .hysteresis = 16.0 / 16.0, // g [up to range] 68 | }; 69 | 70 | static int WAKEUP_TIME = 60; // seconds 71 | static int MOTION_TIMOUT = 20 * 1000; // milliseconds 72 | static bool SLEEP_ENABLED = false; 73 | static unsigned long UPDATE_DELAY = 10; // milliseconds 74 | 75 | 76 | // !!! Note: your experience will be much more rewarding if you use Serial1 versus Serial 77 | 78 | void setup() { 79 | Serial1.begin(115200); 80 | //while(!Serial.isConnected()); 81 | 82 | int ret = 0; 83 | 84 | #if USE_BMI160_I2C 85 | I2C_INTERFACE->setSpeed(CLOCK_SPEED_100KHZ); 86 | 87 | if (BMI160.begin(I2C_INTERFACE, I2C_ADDRESS, INT_PIN) != SYSTEM_ERROR_NONE) { 88 | Serial1.printf("I2C BMI160.begin() failed.\r\n"); 89 | } 90 | 91 | #elif USE_BMI160_SPI 92 | SPI_XFACE.begin(); 93 | 94 | ret = BMI160.begin(SPI_XFACE, SPI_CS_PIN, INT_PIN); 95 | if (ret != SYSTEM_ERROR_NONE) { 96 | Serial1.printlnf("SPI BMI160.begin() failed. %d", ret); 97 | } 98 | 99 | #else 100 | #error "Interface not recognized" 101 | #endif // USE_BMI160_XFACE 102 | 103 | uint8_t chipId; 104 | if (BMI160.getChipId(chipId) != SYSTEM_ERROR_NONE) { 105 | Serial1.printf("BMI160.getChipId() failed.\r\n"); 106 | } 107 | 108 | BMI160.reset(); 109 | BMI160.initAccelerometer(config); 110 | BMI160.initMotion(configMotion); 111 | BMI160.initHighG(configHighG); 112 | BMI160.wakeup(); 113 | } 114 | 115 | void loop() { 116 | static system_tick_t sleepTime = 0; 117 | int ret = 0; 118 | 119 | if (Serial1.available()) { 120 | int c = Serial1.read(); 121 | Serial1.printlnf("%c (%d)", (char)c, c); 122 | switch ((char)c) { 123 | case '0': { 124 | BMI160.reset(); 125 | BMI160.initAccelerometer(config); 126 | BMI160.initMotion(configMotion); 127 | ret = BMI160.initHighG(configHighG); 128 | break; 129 | } 130 | 131 | case '1': ret = BMI160.wakeup(); break; 132 | case '2': ret = BMI160.sleep(); break; 133 | case '3': ret = BMI160.startMotionDetect(); break; 134 | case '4': ret = BMI160.stopMotionDetect(); break; 135 | case '5': ret = BMI160.startHighGDetect(); break; 136 | case '6': ret = BMI160.stopHighGDetect(); break; 137 | 138 | } 139 | if (ret) { 140 | Serial1.printlnf("'%c' returned %d", (char)c, ret); 141 | } 142 | } 143 | 144 | Bmi160Accelerometer accel = {0}; 145 | BMI160.getAccelerometer(accel); 146 | Bmi160::Bmi160PowerState pmu; 147 | BMI160.getAccelerometerPmu(pmu); 148 | 149 | uint32_t val = 0; 150 | bool intr = false; 151 | Bmi160::Bmi160EventType event; 152 | BMI160.waitOnEvent(event, 0); 153 | if (event == Bmi160::Bmi160EventType::SYNC) { 154 | intr = true; 155 | BMI160.getStatus(val, true); 156 | } 157 | else { 158 | BMI160.getStatus(val, false); 159 | } 160 | // Serial1.printf("%2.3f,%2.3f,%2.3f,%u,%u,%u,%08lx\r\n", 161 | // accel.x, accel.y, accel.z, (intr) ? 1 : 0, BMI160.isMotionDetect(val), BMI160.isHighGDetect(val), val); 162 | Serial1.printf("%2.3f,%2.3f,%2.3f,%u,%u,%u\r\n", 163 | accel.x, accel.y, accel.z, (intr) ? 1 : 0, BMI160.isMotionDetect(val), BMI160.isHighGDetect(val)); 164 | 165 | // Keep from going to sleep by moving timeout ahead 166 | if (intr) { 167 | sleepTime = millis(); 168 | } 169 | 170 | delay(UPDATE_DELAY); 171 | 172 | if (SLEEP_ENABLED && (millis() - sleepTime >= MOTION_TIMOUT)) { 173 | SystemSleepConfiguration config; 174 | 175 | config.mode(SystemSleepMode::ULTRA_LOW_POWER) 176 | .gpio(INT_PIN, FALLING) 177 | .duration(WAKEUP_TIME); 178 | 179 | System.sleep(config); 180 | sleepTime = millis(); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /particle.include: -------------------------------------------------------------------------------- 1 | **/*.def 2 | 3 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | name=tracker-edge 2 | version=5.0.0 3 | license=Apache License, Version 2.0 4 | sentence=Particle Tracker reference application 5 | url=https://www.particle.io/particle-tracking-system/ 6 | repository=https://github.com/particle-iot/tracker-edge.git 7 | architectures=particle-tracker 8 | -------------------------------------------------------------------------------- /src/EdgePlatform.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Particle Industries, Inc. 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 | * http://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 | //***************** INCLUDES ********************* 18 | #include "EdgePlatform.h" 19 | #include "tracker_config.h" 20 | 21 | 22 | //***************** DEFINES ********************** 23 | 24 | 25 | //***************** CONSTANTS ******************** 26 | constexpr uint8_t GNSS_MASK = 0x07; 27 | constexpr uint8_t GNSS_SHIFT = 0; 28 | constexpr uint8_t GNSS_NEO_M8U = 0b111 << GNSS_SHIFT; 29 | constexpr uint8_t GNSS_LC29HBA = 0b110 << GNSS_SHIFT; 30 | constexpr uint8_t GNSS_NEO_M9V = 0b101 << GNSS_SHIFT; 31 | 32 | constexpr uint8_t IMU_MASK = 0x18; 33 | constexpr uint8_t IMU_SHIFT = 3; 34 | constexpr uint8_t IMU_BMI160 = 0b11 << IMU_SHIFT; 35 | constexpr uint8_t IMU_BMI270 = 0b10 << IMU_SHIFT; 36 | 37 | constexpr uint8_t GPIO_MASK = 0x60; 38 | constexpr uint8_t GPIO_SHIFT = 5; 39 | constexpr uint8_t GPIO_MCP23S17 = 0b11 << GPIO_SHIFT; 40 | 41 | constexpr uint8_t CAN_MASK = 0x80; 42 | constexpr uint8_t CAN_MCP25625 = 0b111; 43 | 44 | constexpr uint8_t FG_MAX17043 = 0b1; 45 | constexpr uint8_t FG_SHIFT = 6; 46 | 47 | constexpr uint8_t WIFI_MASK = 0x60; 48 | constexpr uint8_t WIFI_SHIFT = 4; 49 | constexpr uint8_t WIFI_ESP32_D2WD = 0b11 << WIFI_SHIFT; 50 | constexpr uint8_t WIFI_ESP32_U4WDH = 0b10 << WIFI_SHIFT; 51 | 52 | constexpr uint8_t THERMISTOR_MASK = 0x08; 53 | constexpr uint8_t THERMISTOR_SHIFT = 3; 54 | constexpr uint8_t THERMISTOR_SOFT = 0b1 << THERMISTOR_SHIFT; 55 | constexpr uint8_t THERMISTOR_PMIC = 0b0 << THERMISTOR_SHIFT; 56 | 57 | constexpr uint8_t CURRENT_MASK = 0x04; 58 | constexpr uint8_t CURRENT_SHIFT = 2; 59 | constexpr uint8_t CURRENT_1_5_A = 0b1 << CURRENT_SHIFT; 60 | constexpr uint8_t CURRENT_3_A = 0b0 << CURRENT_SHIFT; 61 | 62 | //***************** GLOBALS ********************** 63 | EdgePlatform *EdgePlatform::_instance = nullptr; 64 | 65 | Logger EdgePlatformLogger("otp"); 66 | 67 | void EdgePlatform::init(uint32_t model, uint32_t variant) 68 | { 69 | #ifdef TRACKER_HAS_HW_INFO 70 | hal_device_hw_info info; 71 | uint32_t res; 72 | 73 | // Read the OTP region using the DeviceOS API 74 | hal_get_device_hw_info(&info, &res); 75 | EdgePlatformLogger.info("BYTE 3: 0x%lx", info.features >> 8); 76 | EdgePlatformLogger.info("BYTE 2: 0x%lx", info.features & 0xFF); 77 | 78 | uint8_t byte2 = info.features & 0xFF; 79 | uint8_t byte3 = info.features >> 8; 80 | 81 | #else // TRACKER_HAS_HW_INFO 82 | uint8_t byte2 = 0xFF; 83 | uint8_t byte3 = 0xFF; 84 | #endif // TRACKER_HAS_HW_INFO 85 | 86 | // Parse OTP area to determine module type 87 | switch (model) { 88 | case TRACKER_MODEL_BARE_SOM: 89 | model_ = TrackerModel::eBARE_SOM; 90 | break; 91 | case TRACKER_MODEL_EVAL: 92 | model_ = TrackerModel::eEVAL; 93 | break; 94 | case TRACKER_MODEL_TRACKERONE: 95 | model_ = TrackerModel::eTRACKER_ONE; 96 | break; 97 | case TRACKER_MODEL_BARE_SOM_DEFAULT: 98 | default: 99 | model_ = TrackerModel::eBARE_SOM_DEFAULT; 100 | } 101 | 102 | // Parse out info.features:BYTE2 for GNSS, IMU and GPIO Expander 103 | // BYTE2: 104 | // +----+----+----+----+----+----+----+----+ 105 | // | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 106 | // +----+----+----+----+----+----+----+----+ 107 | // | CAN| GPIO | IMU | GNSS | 108 | // +----+----+----+----+----+----+----+----+ 109 | 110 | // Only one type of CAN interface has been defined 111 | canIface_ = EdgePlatform::CanXcvr::eMCP25625; 112 | 113 | if ((byte2 & GPIO_MASK) == GPIO_MCP23S17) { 114 | gpioExpander_ = EdgePlatform::GpioExpander::eMCP23S17; 115 | } 116 | 117 | switch (byte2 & IMU_MASK) { 118 | case IMU_BMI160: 119 | imu_ = ImuVariant::eBMI160; 120 | break; 121 | } 122 | 123 | switch (byte2 & GNSS_MASK) { 124 | case GNSS_NEO_M8U: 125 | gnss_ = GnssVariant::eNEO_M8U; 126 | break; 127 | } 128 | 129 | // Parse out info.features:BYTE3 for WIFI, Thermistor, Fuel Gauge, Current Limit 130 | // BYTE3: 131 | // +----+----+----+----+----+----+----+----+ 132 | // | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 133 | // +----+----+----+----+----+----+----+----+ 134 | // | xxx| FG | WIFI | TR |ILIM| CAN | 135 | // +----+----+----+----+----+----+----+----+ 136 | 137 | // Only one type of fuel gauge has been defined 138 | fg_ = EdgePlatform::FuelGaugeType::eMAX17043; 139 | 140 | switch (byte3 & WIFI_MASK) { 141 | case WIFI_ESP32_D2WD: 142 | wifi_ = WiFiVariant::eESP32_D2WD; 143 | break; 144 | case WIFI_ESP32_U4WDH: 145 | wifi_ = WiFiVariant::eESP32_U4WDH; 146 | break; 147 | } 148 | 149 | if ((byte3 & THERMISTOR_MASK) == THERMISTOR_SOFT) { 150 | tr_ = ThermistorType::eSOFTWARE; 151 | } else { 152 | tr_ = ThermistorType::ePMIC; 153 | } 154 | 155 | if ((byte3 & CURRENT_MASK) == CURRENT_1_5_A) { 156 | currentLimit_ = Ilim::eILIM_1_5; 157 | } else { 158 | currentLimit_ = Ilim::eILIM_3; 159 | } 160 | 161 | // Flag the initialization as complete 162 | isInitialized_ = true; 163 | } 164 | 165 | EdgePlatform::TrackerModel EdgePlatform::getModel() const 166 | { 167 | CHECK_TRUE(isInitialized_, TrackerModel::eMODEL_INVALID); 168 | 169 | return model_; 170 | } 171 | 172 | EdgePlatform::GnssVariant EdgePlatform::getGnss() const 173 | { 174 | CHECK_TRUE(isInitialized_, GnssVariant::eGNSS_INVALID); 175 | 176 | return gnss_; 177 | } 178 | 179 | EdgePlatform::ImuVariant EdgePlatform::getImu() const 180 | { 181 | CHECK_TRUE(isInitialized_, ImuVariant::eIMU_INVALID); 182 | 183 | return imu_; 184 | } 185 | 186 | EdgePlatform::GpioExpander EdgePlatform::getGpioExpander() const 187 | { 188 | CHECK_TRUE(isInitialized_, GpioExpander::eEXPANDER_INVALID); 189 | 190 | return gpioExpander_; 191 | } 192 | 193 | EdgePlatform::CanXcvr EdgePlatform::getCanInterface() const 194 | { 195 | CHECK_TRUE(isInitialized_, CanXcvr::eCAN_INVALID); 196 | 197 | return canIface_; 198 | } 199 | 200 | EdgePlatform::Ilim EdgePlatform::getCurrentLimit() const 201 | { 202 | CHECK_TRUE(isInitialized_, Ilim::eILIM_INVALID); 203 | 204 | return currentLimit_; 205 | } 206 | 207 | EdgePlatform::ThermistorType EdgePlatform::getThermistor() const 208 | { 209 | CHECK_TRUE(isInitialized_, ThermistorType::eTR_INVALID); 210 | 211 | return tr_; 212 | } 213 | 214 | EdgePlatform::WiFiVariant EdgePlatform::getWifiType() const 215 | { 216 | CHECK_TRUE(isInitialized_, WiFiVariant::eWIFI_INVALID); 217 | 218 | return wifi_; 219 | } 220 | 221 | EdgePlatform::FuelGaugeType EdgePlatform::getFuelGaugeType() const 222 | { 223 | CHECK_TRUE(isInitialized_, FuelGaugeType::eFG_INVALID); 224 | 225 | return fg_; 226 | } 227 | -------------------------------------------------------------------------------- /src/EdgePlatform.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "Particle.h" 20 | 21 | /** 22 | * @brief Class used to read and parse OTP fields. 23 | */ 24 | class EdgePlatform 25 | { 26 | public: 27 | //***************** ENUMERATIONS ********************** 28 | enum class TrackerModel 29 | { 30 | eBARE_SOM_DEFAULT, 31 | eBARE_SOM, 32 | eEVAL, 33 | eTRACKER_ONE, 34 | eMODEL_INVALID 35 | }; 36 | 37 | enum class GnssVariant 38 | { 39 | eNEO_M8U, 40 | eGNSS_INVALID 41 | }; 42 | 43 | enum class ImuVariant 44 | { 45 | eBMI160, 46 | eIMU_INVALID 47 | }; 48 | 49 | enum class GpioExpander 50 | { 51 | eMCP23S17, 52 | eEXPANDER_INVALID 53 | }; 54 | 55 | enum class CanXcvr 56 | { 57 | eMCP25625, 58 | eCAN_INVALID 59 | }; 60 | 61 | enum class Ilim 62 | { 63 | eILIM_1_5, 64 | eILIM_3, 65 | eILIM_INVALID 66 | }; 67 | 68 | enum class ThermistorType 69 | { 70 | eSOFTWARE, 71 | ePMIC, 72 | eTR_INVALID 73 | }; 74 | 75 | enum class WiFiVariant 76 | { 77 | eESP32_D2WD, 78 | eESP32_U4WDH, 79 | eWIFI_INVALID 80 | }; 81 | 82 | enum class FuelGaugeType 83 | { 84 | eMAX17043, 85 | eFG_INVALID 86 | }; 87 | 88 | /** 89 | * @brief Return instance of the LocationService 90 | * 91 | * @return LocationService& 92 | */ 93 | static EdgePlatform &instance() 94 | { 95 | if(!_instance) 96 | { 97 | _instance = new EdgePlatform(); 98 | } 99 | return *_instance; 100 | } 101 | 102 | /** 103 | * @brief Read OTP region for hardware information 104 | * 105 | * @param model Model info from non-volatile memory 106 | * @param variant Variant info from non-volatile memory 107 | */ 108 | void init(uint32_t model, uint32_t variant); 109 | 110 | /** 111 | * @brief Return Tracker model type 112 | * 113 | * @return TrackerModel 114 | */ 115 | TrackerModel getModel() const; 116 | 117 | /** 118 | * @brief Return GNSS model type 119 | * 120 | * @return GnssVariant 121 | */ 122 | GnssVariant getGnss() const; 123 | 124 | /** 125 | * @brief Return IMU model type 126 | * 127 | * @return ImuVariant 128 | */ 129 | ImuVariant getImu() const; 130 | 131 | /** 132 | * @brief Return GPIO expander model type 133 | * 134 | * @return GpioExpander 135 | */ 136 | GpioExpander getGpioExpander() const; 137 | 138 | /** 139 | * @brief Return CAN transceiver type 140 | * 141 | * @return CanXcvr 142 | */ 143 | CanXcvr getCanInterface() const; 144 | 145 | /** 146 | * @brief Return current limit 147 | * 148 | * @return Ilim 149 | */ 150 | Ilim getCurrentLimit() const; 151 | 152 | /** 153 | * @brief Return thermistor type 154 | * 155 | * @return ThermistorType 156 | */ 157 | ThermistorType getThermistor() const; 158 | 159 | /** 160 | * @brief Return wifi type 161 | * 162 | * @return WiFiVariant 163 | */ 164 | WiFiVariant getWifiType() const; 165 | 166 | /** 167 | * @brief Return fuel gauge type 168 | * 169 | * @return FuelGaugeType 170 | */ 171 | FuelGaugeType getFuelGaugeType() const; 172 | 173 | private: 174 | /** 175 | * Create a new Tracker Platform object 176 | * @brief Default constructor. 177 | */ 178 | EdgePlatform() { }; 179 | static EdgePlatform *_instance; 180 | bool isInitialized_ {false}; 181 | 182 | TrackerModel model_ {TrackerModel::eMODEL_INVALID}; 183 | GnssVariant gnss_ {GnssVariant::eGNSS_INVALID}; 184 | ImuVariant imu_ {ImuVariant::eIMU_INVALID}; 185 | GpioExpander gpioExpander_ {GpioExpander::eEXPANDER_INVALID}; 186 | CanXcvr canIface_ {CanXcvr::eCAN_INVALID}; 187 | Ilim currentLimit_ {Ilim::eILIM_INVALID}; 188 | ThermistorType tr_ {ThermistorType::eTR_INVALID}; 189 | WiFiVariant wifi_ {WiFiVariant::eWIFI_INVALID}; 190 | FuelGaugeType fg_ {FuelGaugeType::eFG_INVALID}; 191 | }; 192 | -------------------------------------------------------------------------------- /src/IEdgePlatformConfiguration.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "Particle.h" 20 | #include "IGnssLed.h" 21 | 22 | typedef struct 23 | { 24 | int canSleepRetries; 25 | int lowBatteryCutoff; 26 | int lowBatteryCutoffCorrection; 27 | int lowBatteryWarning; 28 | int lowBatteryWarningHyst; 29 | unsigned int lowBatteryAwakeEvalInterval; 30 | unsigned int lowBatterySleepEvalInterval; 31 | unsigned int lowBatterySleepWakeInterval; 32 | system_tick_t postChargeSettleTime; 33 | unsigned int lowBatteryStartTime; 34 | unsigned int lowBatteryDebounceTime; 35 | unsigned int chargingAwakeEvalTime; 36 | unsigned int chargingSleepEvalTime; 37 | uint16_t chargeCurrentHigh; 38 | uint16_t chargeCurrentLow; 39 | uint16_t inputCurrent; 40 | unsigned int failedOtaKeepAwake; 41 | system_tick_t watchdogExpireTime; 42 | float memfaultBatteryScaling; 43 | float memfaultTemperatureScaling; 44 | float memfaultTemperatureInvalid; 45 | IGnssLed *pGnssLed; 46 | }EdgePlatformCommonConfiguration; 47 | 48 | 49 | class IEdgePlatformConfiguration 50 | { 51 | public: 52 | IEdgePlatformConfiguration() 53 | { 54 | commonCfg.lowBatteryCutoff = 2; // percent of battery charge 55 | commonCfg.lowBatteryCutoffCorrection = 1; // percent of battery charge 56 | commonCfg.lowBatteryWarning = 8; // percent of battery charge 57 | commonCfg.lowBatteryWarningHyst = 1; // percent of battery charge 58 | commonCfg.lowBatteryAwakeEvalInterval = 2 * 60; // seconds to sample for low battery condition 59 | commonCfg.lowBatterySleepEvalInterval = 1; // seconds to sample for low battery condition 60 | commonCfg.lowBatterySleepWakeInterval = 15 * 60; // seconds to sample for low battery condition 61 | commonCfg.postChargeSettleTime = 500; // milliseconds 62 | commonCfg.lowBatteryStartTime = 20; // seconds to debounce low battery condition 63 | commonCfg.lowBatteryDebounceTime = 5; // seconds to debounce low battery condition 64 | commonCfg.chargingAwakeEvalTime = 10; // seconds to sample the PMIC charging state 65 | commonCfg.chargingSleepEvalTime = 1; // seconds to sample the PMIC charging state 66 | commonCfg.chargeCurrentHigh = 1536; // milliamps 67 | commonCfg.chargeCurrentLow = 512; // milliamps 68 | commonCfg.inputCurrent = 2048; // milliamps 69 | commonCfg.failedOtaKeepAwake = 60; // seconds to stay awake after failed OTA 70 | commonCfg.watchdogExpireTime = 60 * 1000; // milliseconds to expire the WDT 71 | commonCfg.memfaultBatteryScaling = 10.0f; // scaling for battery SOC reporting 72 | commonCfg.memfaultTemperatureScaling = 10.0f; // scaling for temperature reporting 73 | commonCfg.memfaultTemperatureInvalid = -300.0f; // invalid temperature 74 | commonCfg.pGnssLed = nullptr; 75 | } 76 | 77 | EdgePlatformCommonConfiguration get_common_config_data() 78 | { 79 | return commonCfg; 80 | } 81 | 82 | virtual void load_specific_platform_config() = 0; 83 | protected: 84 | EdgePlatformCommonConfiguration commonCfg; 85 | }; 86 | -------------------------------------------------------------------------------- /src/IGnssLed.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Particle.h" 4 | 5 | namespace particle 6 | { 7 | class IGnssLed 8 | { 9 | public: 10 | IGnssLed(){}; 11 | 12 | /** 13 | * @brief Initialize some hardware resource 14 | */ 15 | virtual void init() = 0; 16 | 17 | /** 18 | * @brief set LED on 19 | */ 20 | virtual void on() = 0; 21 | 22 | /** 23 | * @brief set LED off 24 | */ 25 | virtual void off() = 0; 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/IoGnssLed.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Particle.h" 4 | #include "IGnssLed.h" 5 | 6 | namespace particle 7 | { 8 | class IoGnssLed : public IGnssLed 9 | { 10 | public: 11 | IoGnssLed(uint32_t pin) : _pin(pin) 12 | { 13 | } 14 | 15 | /** 16 | * @brief Initialize LED pin 17 | */ 18 | void init() 19 | { 20 | pinMode(_pin, OUTPUT); 21 | off(); 22 | } 23 | 24 | /** 25 | * @brief set LED on 26 | */ 27 | void on() 28 | { 29 | digitalWrite(_pin, LOW); 30 | } 31 | 32 | /** 33 | * @brief set LED off 34 | */ 35 | void off() 36 | { 37 | digitalWrite(_pin, HIGH); 38 | } 39 | private: 40 | uint32_t _pin; 41 | }; 42 | } -------------------------------------------------------------------------------- /src/LocationPublish.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Particle Industries, Inc. 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 | * http://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 | #include "LocationPublish.h" 18 | #include "tracker_location.h" 19 | #include "tracker.h" 20 | 21 | constexpr int HIGH_PRIORITY = 0; 22 | constexpr int LOW_PRIORITY = 1; 23 | const int DEFAULT_DISK_LIMIT = 64;//In KB 24 | const size_t KILOBYTE_CONSTANT = 1024; 25 | const char STORE_QUEUE_FILE_PATH[] = "/usr/store_queue"; 26 | 27 | uint8_t store_msg_buffer[particle::protocol::MAX_EVENT_DATA_LENGTH + 1] = {0}; 28 | 29 | void locationGenerationCallback(JSONWriter &writer, 30 | LocationPoint &point, const void *context); 31 | 32 | void LocationPublish::init() { 33 | static ConfigObject store_forward("store", { 34 | ConfigBool("enable", &store_config.enable), 35 | ConfigInt("quota", &store_config.quota), 36 | ConfigStringEnum("policy", { 37 | {"drop_old", (int32_t) DiskQueuePolicy::FifoDeleteOld}, 38 | {"drop_new", (int32_t) DiskQueuePolicy::FifoDeleteNew} 39 | }, &store_config.policy) 40 | }); 41 | 42 | ConfigService::instance().registerModule(store_forward); 43 | 44 | if(store_config.enable) { 45 | start(); 46 | } 47 | 48 | Tracker::instance().location.regLocGenCallback(locationGenerationCallback); 49 | } 50 | 51 | void LocationPublish::start() { 52 | if(store_msg_queue.start(STORE_QUEUE_FILE_PATH, 53 | store_config.quota*KILOBYTE_CONSTANT, 54 | store_config.policy) != SYSTEM_ERROR_NONE) { 55 | Log.error("Failed to start location publish disk queue"); 56 | } 57 | } 58 | 59 | void LocationPublish::tick() { 60 | static StoreConfig current_config = store_config; 61 | 62 | //check if settings changed, if still enabled re-run start, 63 | //if disabled stop the disk queue 64 | if(current_config != store_config) { 65 | if(store_config.enable) { 66 | start(); 67 | } 68 | else { 69 | factoryReset(); 70 | } 71 | current_config = store_config; 72 | } 73 | 74 | //check if DiskQueue has messages to retry 75 | if(!store_msg_queue.isEmpty() && isStoreEnabled() && Particle.connected()) { 76 | CloudServicePublishFlags cloud_flags = 77 | (TrackerLocation::instance().isProcessAckEnabled()) ? 78 | CloudServicePublishFlags::FULL_ACK : CloudServicePublishFlags::NONE; 79 | 80 | auto size = store_msg_queue.peekFrontSize(); 81 | if (size > particle::protocol::MAX_EVENT_DATA_LENGTH) { 82 | Log.warn("Disk queue file size exceeds maximum message length; truncating"); 83 | size = particle::protocol::MAX_EVENT_DATA_LENGTH; 84 | } 85 | store_msg_queue.peekFront(store_msg_buffer, size); 86 | store_msg_queue.popFront(); //ok to pop, disk_queue_cb will throw it back 87 | //on if it fails again and there's space in the disk queue 88 | 89 | store_msg_buffer[size] = '\0'; // file data are not null terminated, but CloudService::send expects it 90 | 91 | //Priority level set to normal. don't want these to be high priority 92 | regPendingLocPubCallback(); //use the pending callback for this since 93 | //the exchange between pending vs the current hasn't occured yet 94 | CloudService::instance().send((const char*)store_msg_buffer, 95 | WITH_ACK, 96 | cloud_flags, 97 | std::bind(&TrackerLocation::location_publish_cb, &TrackerLocation::instance(), 98 | std::placeholders::_1, std::placeholders::_2, System.uptime()), 99 | CLOUD_DEFAULT_TIMEOUT_MS, "loc", 0, 1); 100 | } 101 | } 102 | 103 | void LocationPublish::regLocPubCallback() { 104 | Tracker::instance().location.regLocPubCallback(&LocationPublish::disk_queue_cb, 105 | this); 106 | } 107 | 108 | void LocationPublish::regPendingLocPubCallback() { 109 | Tracker::instance().location.regPendLocPubCallback(&LocationPublish::disk_queue_cb, 110 | this); 111 | } 112 | 113 | int LocationPublish::disk_queue_cb(CloudServiceStatus status, 114 | const String &req_event) { 115 | if((SUCCESS != status) && store_config.enable) { 116 | if(!store_msg_queue.pushBack(req_event)) { 117 | Log.warn("Unable to write location message to DiskQueue, discarding"); 118 | } 119 | } 120 | return 0; 121 | } 122 | 123 | void locationGenerationCallback(JSONWriter &writer, LocationPoint &point, const void *context) 124 | { 125 | LocationPublish::instance().regLocPubCallback(); 126 | } 127 | -------------------------------------------------------------------------------- /src/LocationPublish.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "DiskQueue.h" 20 | #include "cloud_service.h" 21 | 22 | extern const int DEFAULT_DISK_LIMIT; //in KB 23 | extern const size_t KILOBYTE_CONSTANT; 24 | 25 | struct StoreConfig { 26 | int quota{DEFAULT_DISK_LIMIT}; 27 | DiskQueuePolicy policy {DiskQueuePolicy::FifoDeleteOld}; 28 | bool enable{false}; 29 | 30 | bool operator!=(const StoreConfig& other) const { 31 | if((quota != other.quota) || (policy != other.policy) || 32 | (enable != other.enable)) {return true;} 33 | else {return false;} 34 | } 35 | }; 36 | class LocationPublish { 37 | public: 38 | static LocationPublish& instance() { 39 | static LocationPublish instance; 40 | return instance; 41 | } 42 | 43 | /** 44 | * @brief Initialize the LocationPublish object 45 | * 46 | * @details creates the ConfigObject for the store forward feature, and 47 | * registers the object. Will also call start() if the store forward feature 48 | * is enabled 49 | */ 50 | void init(); 51 | 52 | /** 53 | * @brief Start the DiskQueue 54 | * 55 | * @details Calls DiskQueue::start() for store_msg_queue. This will get 56 | * the DiskQueue started with the quota and policy desired. Can be called 57 | * again if there is a change to the policy or size. 58 | */ 59 | void start(); 60 | 61 | /** 62 | * @brief Called once a second to consume the store_msg_queue if there 63 | * is data to send 64 | * 65 | * @details Call this function once a second to consume messages saved in the 66 | * store_msg_queue. This setsup the the entire callback system to run again 67 | */ 68 | void tick(); 69 | 70 | /** 71 | * @brief Register the callback to be called from every generated location 72 | * publish 73 | * 74 | * @details This function is a wraper that is called on every generated 75 | * location publish to add the disk_queue_cb to be issued on location publish 76 | */ 77 | void regLocPubCallback(); 78 | 79 | /** 80 | * @brief Register the pending callbacks 81 | * 82 | * @details This is necessary since the copying of the location publish 83 | * callbacks hasn't occured, this method puts the disk_queue_cb as the next 84 | * pending callback that can be issued if the message fails or succeeds 85 | */ 86 | void regPendingLocPubCallback(); 87 | 88 | /** 89 | * @brief Makes the decision to store and forward a message after any 90 | * location publish 91 | * 92 | * @details Checks to see if the status of the message publish is not 93 | * SUCCESS. If the store forward feature is enabled, and there is data, 94 | * push the message on to the store_msg_queue 95 | * 96 | * @param[in] status of the message that was published 97 | * @param[in] rsp_root JSON root of the message 98 | * @param[in] req_event data containing the message in JSON format 99 | * 100 | * @return 0 for success 101 | */ 102 | int disk_queue_cb(CloudServiceStatus status, 103 | const String &req_event); 104 | 105 | /** 106 | * @brief Return the state of the store_config.enable variable 107 | * 108 | * @details Is the store and forward feature enabled. This returns 109 | * its state 110 | * 111 | * @return TRUE if enabled, FALSE if not 112 | */ 113 | bool isStoreEnabled() const { 114 | return store_config.enable; 115 | } 116 | 117 | /** 118 | * @brief Called to cleanup the store_msg_queue. Is called if you disable the 119 | * store forward feature, or reset the device to factory 120 | * 121 | * @details Flushes, Closes out, and deletes the store_msg_queue files 122 | */ 123 | void factoryReset() { 124 | store_msg_queue.unlinkFiles(); //unlink the files first 125 | store_msg_queue.stop(); //then clear the files from _fileList 126 | } 127 | 128 | //remove copy and assignment operators 129 | LocationPublish(LocationPublish const&) = delete; 130 | void operator=(LocationPublish const&) = delete; 131 | 132 | private: 133 | LocationPublish() : store_msg_queue () {} 134 | 135 | DiskQueue store_msg_queue; 136 | StoreConfig store_config; 137 | }; 138 | -------------------------------------------------------------------------------- /src/TrackerEvalConfiguration.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "IEdgePlatformConfiguration.h" 20 | #include "tracker_config.h" 21 | 22 | 23 | class TrackerEvalConfiguration: public IEdgePlatformConfiguration 24 | { 25 | public: 26 | TrackerEvalConfiguration() 27 | { 28 | commonCfg.chargeCurrentHigh = 1024; // milliamps 29 | commonCfg.inputCurrent = 1500; // milliamps 30 | } 31 | 32 | void load_specific_platform_config() 33 | { 34 | } 35 | protected: 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /src/TrackerOneConfiguration.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "IEdgePlatformConfiguration.h" 20 | #include "tracker_config.h" 21 | #include "IoGnssLed.h" 22 | 23 | class TrackerOneConfiguration: public IEdgePlatformConfiguration 24 | { 25 | public: 26 | TrackerOneConfiguration() 27 | { 28 | commonCfg.chargeCurrentHigh = 1024; // milliamps 29 | commonCfg.inputCurrent = 1500; // milliamps 30 | commonCfg.pGnssLed = new IoGnssLed(TRACKER_GNSS_LOCK_LED); 31 | } 32 | 33 | void load_specific_platform_config() 34 | { 35 | } 36 | protected: 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /src/build.mk: -------------------------------------------------------------------------------- 1 | # Place this custom makefile here: /src/build.mk 2 | 3 | INCLUDE_DIRS += $(SOURCE_PATH)/$(USRSRC) # add user sources to include path 4 | # add C and CPP files - if USRSRC is not empty, then add a slash 5 | CPPSRC += $(call target_files,$(USRSRC_SLASH),*.cpp) 6 | CSRC += $(call target_files,$(USRSRC_SLASH),*.c) 7 | 8 | # Remove certain libraries based on device OS version 9 | # --------------------------------------------- 10 | ifeq ($(shell test $(VERSION) -ge 5000; echo $$?),0) 11 | $(info $$MODULE_LIBSV2 is [${MODULE_LIBSV2}]) 12 | MODULE_LIBSV2_FILTERED:=$(filter-out $(SOURCE_PATH)lib/memfault,$(MODULE_LIBSV2)) 13 | MODULE_LIBSV2=$(MODULE_LIBSV2_FILTERED) 14 | $(info $$MODULE_LIBSV2 is [${MODULE_LIBSV2}]) 15 | endif 16 | -------------------------------------------------------------------------------- /src/gnss_led.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #include "Particle.h" 18 | #include "gnss_led.h" 19 | #include "tracker_config.h" 20 | 21 | static void GnssLedTimer(); 22 | 23 | static Timer* timer_ = nullptr; 24 | static bool enabled = true; 25 | static LocationStatus lastStatus_ = { .powered = -1, .locked = -1, .error = -1 }; 26 | static int blinkCount_ = GNSS_LED_CONTROL_BLINK_PERIOD_MS / GNSS_LED_CONTROL_TIMER_PERIOD_MS; 27 | static bool blinkState_ = false; 28 | static IGnssLed *pInterface = nullptr; 29 | 30 | static void GnssLedTimer() { 31 | 32 | if (!enabled) { 33 | if (pInterface) 34 | { 35 | pInterface->off(); 36 | } 37 | lastStatus_ = { .powered = -1, .locked = -1 }; 38 | return; 39 | } 40 | 41 | LocationStatus status = {0}; 42 | (void)LocationService::instance().getStatus(status); 43 | 44 | SCOPE_GUARD({ 45 | lastStatus_ = status; 46 | }); 47 | 48 | if (lastStatus_.powered == -1) { 49 | return; 50 | } 51 | 52 | if (status.error) { 53 | 54 | if (pInterface) 55 | { 56 | if(blinkState_) 57 | { 58 | pInterface->on(); 59 | } 60 | else 61 | { 62 | pInterface->off(); 63 | } 64 | } 65 | 66 | blinkState_ = !blinkState_; 67 | return; 68 | } 69 | 70 | if ((lastStatus_.powered != status.powered) || 71 | (lastStatus_.locked != status.locked)) { 72 | blinkCount_ = GNSS_LED_CONTROL_BLINK_PERIOD_MS / GNSS_LED_CONTROL_TIMER_PERIOD_MS; 73 | blinkState_ = false; 74 | } 75 | 76 | if (status.powered == 0) { 77 | if (pInterface) 78 | { 79 | pInterface->off(); 80 | } 81 | } 82 | else if (status.locked) { 83 | if (pInterface) 84 | { 85 | pInterface->on(); 86 | } 87 | } 88 | else { 89 | if (blinkCount_ == 0) { 90 | blinkCount_ = GNSS_LED_CONTROL_BLINK_PERIOD_MS / GNSS_LED_CONTROL_TIMER_PERIOD_MS; 91 | 92 | if (pInterface) 93 | { 94 | if(blinkState_) 95 | { 96 | pInterface->on(); 97 | } 98 | else 99 | { 100 | pInterface->off(); 101 | } 102 | } 103 | blinkState_ = !blinkState_; 104 | } 105 | else { 106 | blinkCount_--; 107 | } 108 | } 109 | } 110 | 111 | 112 | int GnssLedInit(IGnssLed *pInstance) { 113 | pInterface = pInstance; 114 | if (pInterface) 115 | { 116 | pInterface->init(); 117 | } 118 | enabled = false; 119 | 120 | timer_ = new Timer(GNSS_LED_CONTROL_TIMER_PERIOD_MS, GnssLedTimer); 121 | if (timer_ == nullptr) { 122 | return SYSTEM_ERROR_NO_MEMORY; 123 | } 124 | 125 | timer_->start(); 126 | 127 | return SYSTEM_ERROR_NONE; 128 | } 129 | 130 | void GnssLedEnable(bool enable) { 131 | enabled = enable; 132 | if (!enabled) { 133 | if (pInterface) 134 | { 135 | pInterface->off(); 136 | } 137 | } 138 | } 139 | 140 | void GnssLedError() { 141 | enabled = false; 142 | } 143 | -------------------------------------------------------------------------------- /src/gnss_led.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "location_service.h" 20 | #include "IGnssLed.h" 21 | 22 | using namespace particle; 23 | 24 | constexpr system_tick_t GNSS_LED_CONTROL_TIMER_PERIOD_MS = 50; 25 | constexpr system_tick_t GNSS_LED_CONTROL_BLINK_PERIOD_MS = 250; 26 | 27 | int GnssLedInit(IGnssLed *pInstance); 28 | void GnssLedEnable(bool enable); 29 | void GnssLedError(); 30 | -------------------------------------------------------------------------------- /src/location_service.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #include 18 | #include 19 | #include 20 | #include "Particle.h" 21 | #include "tracker_config.h" 22 | #include "location_service.h" 23 | #include "EdgePlatform.h" 24 | 25 | using namespace spark; 26 | using namespace particle; 27 | using namespace std::placeholders; 28 | 29 | namespace { 30 | 31 | } // anonymous namespace 32 | 33 | LocationService *LocationService::_instance = nullptr; 34 | 35 | LocationService::LocationService() 36 | : ubloxGps_(nullptr), 37 | pointThreshold_({0}), 38 | pointThresholdConfigured_(false), 39 | fastGnssLock_(false), 40 | gnssType_(GnssModuleType::GNSS_NONE) { 41 | 42 | } 43 | 44 | void LocationService::setModuleType(void) 45 | { 46 | gnssType_ = GnssModuleType::GNSS_UBLOX; 47 | } 48 | 49 | int LocationService::begin(const LocationServiceConfiguration& config) { 50 | 51 | int ret = SYSTEM_ERROR_NONE; 52 | 53 | // Assign the GNSS hardware variant 54 | setModuleType(); 55 | 56 | _deviceConfig = config; 57 | 58 | CHECK_FALSE(ubloxGps_, SYSTEM_ERROR_INVALID_STATE); 59 | 60 | pinMode(UBLOX_CS_PIN, OUTPUT); 61 | pinMode(UBLOX_PWR_EN_PIN, OUTPUT); 62 | pinMode(UBLOX_RESETN_PIN, OUTPUT); 63 | digitalWrite(UBLOX_RESETN_PIN, LOW); 64 | 65 | CHECK_TRUE(assertEnable(false), SYSTEM_ERROR_IO); 66 | CHECK_TRUE(assertSelect(false), SYSTEM_ERROR_IO); 67 | 68 | selectPin_ = UBLOX_CS_PIN; 69 | enablePin_ = UBLOX_PWR_EN_PIN; 70 | 71 | do { 72 | ubloxGps_ = new ubloxGPS(UBLOX_SPI_INTERFACE, 73 | std::bind(&LocationService::assertSelect, this, _1), 74 | std::bind(&LocationService::assertEnable, this, _1), 75 | UBLOX_TX_READY_MCU_PIN, 76 | UBLOX_TX_READY_GPS_PIN); 77 | if (!ubloxGps_) { 78 | Log.error("ubloxGPS instantiation failed"); 79 | ret = SYSTEM_ERROR_INTERNAL; 80 | break; 81 | } 82 | 83 | return SYSTEM_ERROR_NONE; 84 | } while (false); 85 | 86 | // Cleanup 87 | cleanup(); 88 | 89 | return ret; 90 | } 91 | 92 | void LocationService::cleanup() { 93 | if (ubloxGps_) { 94 | delete ubloxGps_; 95 | } 96 | } 97 | 98 | void LocationService::setFastLock(bool enable) { 99 | if (ubloxGps_) { 100 | if (enable) { 101 | ubloxGps_->setLockMethod(ubloxGpsLockMethod::HorizontalDop); 102 | ubloxGps_->setLockHdopThreshold(LOCATION_LOCK_HDOP_MAX_DEFAULT); 103 | } else { 104 | ubloxGps_->setLockMethod(ubloxGpsLockMethod::HorizontalAccuracy); 105 | } 106 | } 107 | } 108 | 109 | bool LocationService::getFastLock() { 110 | if (ubloxGps_) { 111 | return (ubloxGpsLockMethod::HorizontalDop == ubloxGps_->getLockMethod()); 112 | } 113 | 114 | return false; 115 | } 116 | 117 | bool LocationService::configureGPS(LocationServiceConfiguration& config) { 118 | enableHotStartOnWake_ = config.enableHotStartOnWake(); 119 | 120 | bool ret = true; 121 | 122 | WITH_LOCK(*ubloxGps_) { 123 | setFastLock(_deviceConfig.enableFastLock()); 124 | ret &= ubloxGps_->setMode(_deviceConfig.udrModel()); 125 | ret &= ubloxGps_->setIMUAlignmentAngles( 126 | _deviceConfig.imuYaw(), 127 | _deviceConfig.imuPitch(), 128 | _deviceConfig.imuRoll() 129 | ); 130 | ret &= ubloxGps_->setIMUAutoAlignment(_deviceConfig.enableIMUAutoAlignment()); 131 | ret &= ubloxGps_->setUDREnable(_deviceConfig.enableUDR()); 132 | ret &= ubloxGps_->setIMUtoVRP( 133 | _deviceConfig.imuToVRPX(), 134 | _deviceConfig.imuToVRPY(), 135 | _deviceConfig.imuToVRPZ() 136 | ); 137 | ret &= ubloxGps_->setAOPSettings(_deviceConfig.enableAssistNowAutonomous()); 138 | } 139 | return ret; 140 | } 141 | 142 | int LocationService::start(bool restart) { 143 | CHECK_TRUE(ubloxGps_, SYSTEM_ERROR_INVALID_STATE); 144 | 145 | if (restart && ubloxGps_->isOn()) { 146 | if (enableHotStartOnWake_) { 147 | CHECK_TRUE(ubloxGps_->saveOnShutdown(), SYSTEM_ERROR_INVALID_STATE); 148 | } 149 | ubloxGps_->off(); 150 | } 151 | 152 | if (!ubloxGps_->isOn()) { 153 | auto ret = ubloxGps_->on(); 154 | if (ret) { 155 | Log.error("Error %d when turning GNSS on", ret); 156 | return ret; 157 | } 158 | Log.info("GNSS Start"); 159 | CHECK_TRUE(configureGPS(_deviceConfig), SYSTEM_ERROR_INVALID_STATE); 160 | } 161 | 162 | return SYSTEM_ERROR_NONE; 163 | } 164 | 165 | int LocationService::stop() { 166 | int ret = SYSTEM_ERROR_NONE; 167 | 168 | CHECK_TRUE(ubloxGps_, SYSTEM_ERROR_INVALID_STATE); 169 | 170 | if (ubloxGps_->isOn()) { 171 | Log.info("Turning GNSS off"); 172 | if (enableHotStartOnWake_) { 173 | CHECK_TRUE(ubloxGps_->saveOnShutdown(), SYSTEM_ERROR_INVALID_STATE); 174 | } 175 | ret = ubloxGps_->off(); 176 | } 177 | 178 | return ret; 179 | } 180 | 181 | int LocationService::getLocation(LocationPoint& point) { 182 | point.type = LocationType::DEVICE; 183 | point.sources.append(LocationSource::GNSS); 184 | 185 | WITH_LOCK(*ubloxGps_) { 186 | point.locked = (ubloxGps_->getLock()) ? 1 : 0; 187 | point.stable = ubloxGps_->isLockStable(); 188 | point.lockedDuration = ubloxGps_->getLockDuration(); 189 | point.epochTime = (time_t)ubloxGps_->getUTCTime(); 190 | point.timeScale = LocationTimescale::TIMESCALE_UTC; 191 | point.satsInUse = ubloxGps_->getSatellites(); 192 | point.satsInView = ubloxGps_->getSatellitesDesc(point.sats_in_view_desc); 193 | if (point.locked) { 194 | point.latitude = ubloxGps_->getLatitude(); 195 | point.longitude = ubloxGps_->getLongitude(); 196 | point.altitude = ubloxGps_->getAltitude(); 197 | point.speed = ubloxGps_->getSpeed(GPS_SPEED_UNIT_MPS); 198 | point.heading = ubloxGps_->getHeading(); 199 | point.horizontalAccuracy = ubloxGps_->getHorizontalAccuracy(); 200 | point.horizontalDop = ubloxGps_->getHDOP(); 201 | point.verticalAccuracy = ubloxGps_->getVerticalAccuracy(); 202 | point.verticalDop = ubloxGps_->getVDOP(); 203 | } 204 | } 205 | 206 | return SYSTEM_ERROR_NONE; 207 | } 208 | 209 | int LocationService::getRadiusThreshold(float& radius) { 210 | const std::lock_guard lock(pointMutex_); 211 | radius = pointThreshold_.radius; 212 | return SYSTEM_ERROR_NONE; 213 | } 214 | 215 | int LocationService::setRadiusThreshold(float radius) { 216 | const std::lock_guard lock(pointMutex_); 217 | pointThreshold_.radius = std::fabs(radius); 218 | return SYSTEM_ERROR_NONE; 219 | } 220 | 221 | int LocationService::getWayPoint(float& latitude, float& longitude) { 222 | const std::lock_guard lock(pointMutex_); 223 | CHECK_TRUE(pointThresholdConfigured_, SYSTEM_ERROR_INVALID_STATE); 224 | latitude = pointThreshold_.latitude; 225 | longitude = pointThreshold_.longitude; 226 | return SYSTEM_ERROR_NONE; 227 | } 228 | 229 | int LocationService::setWayPoint(float latitude, float longitude) { 230 | const std::lock_guard lock(pointMutex_); 231 | pointThreshold_.latitude = latitude; 232 | pointThreshold_.longitude = longitude; 233 | pointThresholdConfigured_ = true; 234 | return SYSTEM_ERROR_NONE; 235 | } 236 | 237 | int LocationService::getWayPoint(PointThreshold& point) { 238 | const std::lock_guard lock(pointMutex_); 239 | CHECK_TRUE(pointThresholdConfigured_, SYSTEM_ERROR_INVALID_STATE); 240 | point = pointThreshold_; 241 | return SYSTEM_ERROR_NONE; 242 | } 243 | 244 | int LocationService::getDistance(float& distance, const PointThreshold& wayPoint, const LocationPoint& point) { 245 | CHECK_TRUE(pointThresholdConfigured_, SYSTEM_ERROR_INVALID_STATE); 246 | 247 | CHECK_TRUE(ubloxGps_, SYSTEM_ERROR_INVALID_STATE); 248 | 249 | distance = fabs(ubloxGps_->getDistance( 250 | wayPoint.latitude, wayPoint.longitude, 251 | point.latitude, point.longitude)); 252 | 253 | return SYSTEM_ERROR_NONE; 254 | } 255 | 256 | int LocationService::isOutsideRadius(bool& outside, const LocationPoint& point) { 257 | CHECK_TRUE(pointThresholdConfigured_, SYSTEM_ERROR_INVALID_STATE); 258 | 259 | float distance = 0.0; 260 | PointThreshold current; 261 | getWayPoint(current); 262 | 263 | CHECK_TRUE(ubloxGps_, SYSTEM_ERROR_INVALID_STATE); 264 | 265 | distance = fabs(ubloxGps_->getDistance( 266 | current.latitude, current.longitude, 267 | point.latitude, point.longitude)); 268 | 269 | if (distance > current.radius) { 270 | outside = true; 271 | } else { 272 | outside = false; 273 | } 274 | 275 | return SYSTEM_ERROR_NONE; 276 | } 277 | 278 | int LocationService::getStatus(LocationStatus& status) { 279 | CHECK_TRUE(ubloxGps_, SYSTEM_ERROR_INVALID_STATE); 280 | 281 | auto gpsState = ubloxGps_->getGpsStatus(); 282 | 283 | switch (gpsState) { 284 | case GPS_STATUS_OFF: { 285 | status.locked = 0; 286 | status.powered = 0; 287 | status.error = 0; 288 | break; 289 | } 290 | 291 | case GPS_STATUS_FIXING: { 292 | status.locked = 0; 293 | status.powered = 1; 294 | status.error = 0; 295 | break; 296 | } 297 | 298 | case GPS_STATUS_LOCK: { 299 | status.locked = 1; 300 | status.powered = 1; 301 | status.error = 0; 302 | break; 303 | } 304 | 305 | case GPS_STATUS_ERROR: { 306 | status.locked = 0; 307 | status.powered = 0; 308 | status.error = 1; 309 | break; 310 | } 311 | 312 | default: break; 313 | } 314 | 315 | return SYSTEM_ERROR_NONE; 316 | } 317 | 318 | bool LocationService::isLockStable() { 319 | return ubloxGps_->isLockStable(); 320 | } 321 | 322 | bool LocationService::isActive() { 323 | return ubloxGps_->is_active(); 324 | }; 325 | 326 | bool LocationService::assertSelect(bool select) 327 | { 328 | digitalWrite(selectPin_, (select) ? LOW : HIGH); 329 | return true; 330 | } 331 | 332 | bool LocationService::assertEnable(bool enable) 333 | { 334 | // UP DOWN 335 | // ____ ____ ______ 336 | // VCC __/ \_/ \____ 337 | // ____ _______ ___ _____ 338 | // RST \_/ \_/ 339 | 340 | if (enable) { 341 | digitalWrite(enablePin_, HIGH); 342 | delay(1000); 343 | digitalWrite(UBLOX_RESETN_PIN, LOW); 344 | delay(100); 345 | digitalWrite(UBLOX_RESETN_PIN, HIGH); 346 | digitalWrite(enablePin_, LOW); 347 | delay(100); 348 | digitalWrite(enablePin_, HIGH); 349 | } else { 350 | digitalWrite(UBLOX_RESETN_PIN, LOW); 351 | delay(100); 352 | digitalWrite(UBLOX_RESETN_PIN, HIGH); 353 | digitalWrite(enablePin_, LOW); 354 | } 355 | 356 | return true; 357 | } 358 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #include "Particle.h" 18 | #include "tracker_config.h" 19 | #include "tracker.h" 20 | 21 | SYSTEM_THREAD(ENABLED); 22 | SYSTEM_MODE(SEMI_AUTOMATIC); 23 | 24 | #if TRACKER_PRODUCT_NEEDED 25 | PRODUCT_ID(TRACKER_PRODUCT_ID); 26 | #endif // TRACKER_PRODUCT_NEEDED 27 | PRODUCT_VERSION(TRACKER_PRODUCT_VERSION); 28 | 29 | STARTUP( 30 | Tracker::startup(); 31 | ); 32 | 33 | SerialLogHandler logHandler(115200, LOG_LEVEL_TRACE, { 34 | { "app.gps.nmea", LOG_LEVEL_INFO }, 35 | { "app.gps.ubx", LOG_LEVEL_INFO }, 36 | { "ncp.at", LOG_LEVEL_INFO }, 37 | { "net.ppp.client", LOG_LEVEL_INFO }, 38 | }); 39 | 40 | void setup() 41 | { 42 | Tracker::instance().init(); 43 | } 44 | 45 | void loop() 46 | { 47 | Tracker::instance().loop(); 48 | } 49 | -------------------------------------------------------------------------------- /src/memfault_metrics_heartbeat_config.def: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Definitions for Tracker Edge specific metrics. 3 | // ---------------------------------------------------------------------------- 4 | 5 | // Metric for battery state-of-charge 6 | // Unit: Tenths of a percent 7 | MEMFAULT_METRICS_KEY_DEFINE(Bat_Soc, kMemfaultMetricType_Unsigned) 8 | 9 | // Metric for system temperature 10 | // Unit: Tenths of a degree Celsius 11 | MEMFAULT_METRICS_KEY_DEFINE(Tracker_TempC, kMemfaultMetricType_Signed) 12 | -------------------------------------------------------------------------------- /src/memfault_particle_user_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #define MEMFAULT_PARTICLE_PORT_CPP_ONLY_SYSTEM_VERSION (0) 20 | 21 | // Reserve space for exception traces 22 | #define MEMFAULT_PLATFORM_COREDUMP_STORAGE_RAM_SIZE (2048) 23 | 24 | //! WARNING: This should only be set to "1" for development builds & testing 25 | #define MEMFAULT_PARTICLE_PORT_DEBUG_API_ENABLE (1) 26 | 27 | // The name reported into Memfault for this application 28 | #define MEMFAULT_PARTICLE_PORT_SOFTWARE_TYPE "tracker-edge" 29 | 30 | //! WARNING: Only uncomment this if the user wants to override default settings 31 | #define MEMFAULT_METRICS_HEARTBEAT_INTERVAL_SECS (60) 32 | 33 | //! WARNING: Enabling logging will increase data usage and program memory size 34 | #define MEMFAULT_PARTICLE_PORT_LOG_STORAGE_ENABLE (0) 35 | #define MEMFAULT_PARTICLE_PORT_LOGGING_ENABLE (0) 36 | 37 | //! WARNING: This overrides a default storage size in the library 38 | #define MEMFAULT_PARTICLE_PORT_LOG_STORAGE_SIZE (1024) 39 | -------------------------------------------------------------------------------- /src/motion_service.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #include "Particle.h" 18 | #include "tracker_config.h" 19 | #include "motion_service.h" 20 | #include "tracker_imu.h" 21 | 22 | using namespace spark; 23 | using namespace particle; 24 | 25 | namespace { 26 | 27 | // TODO: This should probably be a constant expression 28 | BmiAccelerometerConfig bmiAccelConfig = { 29 | .rate = 100.0, // Hz [0.78Hz -> 1600Hz] 30 | .range = 16.0, // g [2g, 4g, 8g, 16g] 31 | }; 32 | 33 | BmiAccelMotionConfig bmiMotionConfigs[] = { 34 | // Low sensitivity motion detection settings 35 | { 36 | .mode = BmiAccelMotionMode::ACCEL_MOTION_MODE_SIGNIFICANT, 37 | .motionThreshold = 1, // g [up to range] 38 | .motionDuration = 4, // counts of rate period [1 -> 4] 39 | .motionSkip = BmiAccelSignificantMotionSkip::SIG_MOTION_SKIP_1_5_S, // seconds [1.5s, 3s, 6s, 12s] 40 | .motionProof = BmiAccelSignificantMotionProof::SIG_MOTION_PROOF_0_25_S, // seconds [0.25s, 0.5s, 1s, 2s] 41 | }, 42 | 43 | // Medium sensitivity motion detection settings 44 | { 45 | .mode = BmiAccelMotionMode::ACCEL_MOTION_MODE_ANY, 46 | .motionThreshold = 0.5, // g [up to range] 47 | .motionDuration = 4, // counts of rate period [1 -> 4] 48 | .motionSkip = BmiAccelSignificantMotionSkip::SIG_MOTION_SKIP_1_5_S, // seconds [1.5s, 3s, 6s, 12s] 49 | .motionProof = BmiAccelSignificantMotionProof::SIG_MOTION_PROOF_0_25_S, // seconds [0.25s, 0.5s, 1s, 2s] 50 | }, 51 | 52 | // High sensitivity motion detection settings 53 | { 54 | .mode = BmiAccelMotionMode::ACCEL_MOTION_MODE_ANY, 55 | .motionThreshold = 0.1, // g [up to range] 56 | .motionDuration = 1, // counts of rate period [1 -> 4] 57 | .motionSkip = BmiAccelSignificantMotionSkip::SIG_MOTION_SKIP_1_5_S, // seconds [1.5s, 3s, 6s, 12s] 58 | .motionProof = BmiAccelSignificantMotionProof::SIG_MOTION_PROOF_0_25_S, // seconds [0.25s, 0.5s, 1s, 2s] 59 | }, 60 | }; 61 | 62 | BmiAccelHighGConfig bmiHighGConfig = { 63 | .threshold = 4.0, // g [up to range] 64 | .duration = 0.0025, // seconds 65 | .hysteresis = 16.0 / 16.0, // g [up to range] 66 | }; 67 | 68 | enum MotionAwakeFlags : uint32_t { 69 | MOTION_AWAKE_NONE = 0, // Nothing is awake 70 | MOTION_AWAKE_HIGH_G = (1UL << 1), // High G is awake 71 | MOTION_AWAKE_SIGANY = (1UL << 2), // Significant/any motion is awake 72 | }; 73 | 74 | } // anonymous namespace 75 | 76 | MotionService *MotionService::_instance = nullptr; 77 | 78 | MotionService::MotionService() 79 | : thread_(nullptr), 80 | counters_({0}), 81 | motionEventQueue_(nullptr), 82 | mode_(MotionDetectionMode::NONE), 83 | highGMode_(HighGDetectionMode::DISABLE), 84 | awakeFlags_(0), 85 | eventDepth_(0) { 86 | 87 | } 88 | 89 | int MotionService::start(size_t eventDepth) { 90 | CHECK_FALSE(thread_, SYSTEM_ERROR_INVALID_STATE); 91 | 92 | int ret = SYSTEM_ERROR_NONE; 93 | 94 | [[maybe_unused]] auto imu = IMU.getImuType(); 95 | 96 | // Retrieve (if first time) instance of the BMI160/BMI270 IMU device 97 | ret = IMU.begin(BMI_SPI_INTERFACE, BMI_SPI_CS_PIN, BMI_INT_PIN); 98 | if (ret != SYSTEM_ERROR_NONE) { 99 | Log.error("IMU.begin() failed"); 100 | return ret; 101 | } 102 | 103 | // Clear all configuration to defaults 104 | IMU.reset(); 105 | awakeFlags_ = MOTION_AWAKE_NONE; 106 | CHECK(IMU.initAccelerometer(bmiAccelConfig)); 107 | 108 | // Create the motion event queue if it hasn't been created yet 109 | eventDepth_ = eventDepth; 110 | if (!motionEventQueue_ && os_queue_create(&motionEventQueue_, sizeof(MotionEvent), eventDepth, nullptr)) { 111 | motionEventQueue_ = nullptr; 112 | Log.error("os_queue_create() failed"); 113 | return SYSTEM_ERROR_INTERNAL; 114 | } 115 | 116 | // Start the main MotionService thread 117 | ret = os_thread_create(&thread_, "MOTSERV", OS_THREAD_PRIORITY_DEFAULT, MotionService::thread, this, OS_THREAD_STACK_SIZE_DEFAULT); 118 | if (ret) { 119 | Log.error("os_thread_create() failed"); 120 | return ret; 121 | } 122 | 123 | return SYSTEM_ERROR_NONE; 124 | } 125 | 126 | int MotionService::stop() { 127 | CHECK_TRUE(thread_, SYSTEM_ERROR_INVALID_STATE); 128 | CHECK_FALSE(IMU.syncEvent(BmiEventType::BREAK), SYSTEM_ERROR_UNKNOWN); 129 | return SYSTEM_ERROR_NONE; 130 | } 131 | 132 | int MotionService::kill() { 133 | CHECK_TRUE(thread_, SYSTEM_ERROR_INVALID_STATE); 134 | CHECK_FALSE(os_thread_exit(thread_), SYSTEM_ERROR_INVALID_STATE); 135 | return SYSTEM_ERROR_NONE; 136 | } 137 | 138 | int MotionService::join() { 139 | CHECK_TRUE(thread_, SYSTEM_ERROR_INVALID_STATE); 140 | CHECK_FALSE(os_thread_join(thread_), SYSTEM_ERROR_UNKNOWN); 141 | thread_ = nullptr; 142 | return SYSTEM_ERROR_NONE; 143 | } 144 | 145 | int MotionService::enableMotionDetection(MotionDetectionMode mode) { 146 | // The motion service is simply configured from the outside using a handful of 147 | // abstracted configuration modes: low, medium, and high. 148 | switch (mode) { 149 | case MotionDetectionMode::NONE: { 150 | CHECK(IMU.stopMotionDetect()); 151 | clearAwakeFlag(MOTION_AWAKE_SIGANY); 152 | if (!isAnyAwake()) { 153 | CHECK(IMU.sleep()); 154 | } 155 | mode_ = mode; 156 | return SYSTEM_ERROR_NONE; 157 | } 158 | 159 | case MotionDetectionMode::LOW_SENSITIVITY: { 160 | IMU.initMotion(bmiMotionConfigs[0], false); 161 | 162 | break; 163 | } 164 | 165 | case MotionDetectionMode::MEDIUM_SENSITIVITY: { 166 | IMU.initMotion(bmiMotionConfigs[1], false); 167 | break; 168 | } 169 | 170 | case MotionDetectionMode::HIGH_SENSITIVITY: { 171 | IMU.initMotion(bmiMotionConfigs[2], false); 172 | break; 173 | } 174 | 175 | default: { 176 | return SYSTEM_ERROR_UNKNOWN; 177 | } 178 | } 179 | 180 | if (!isAnyAwake()) { 181 | CHECK(IMU.wakeup()); 182 | } 183 | setAwakeFlag(MOTION_AWAKE_SIGANY); 184 | CHECK(IMU.startMotionDetect()); 185 | 186 | mode_ = mode; 187 | 188 | return SYSTEM_ERROR_NONE; 189 | } 190 | 191 | int MotionService::disableMotionDetection() { 192 | return enableMotionDetection(MotionDetectionMode::NONE); 193 | } 194 | 195 | MotionDetectionMode MotionService::getMotionDetection() { 196 | return mode_; 197 | } 198 | 199 | int MotionService::enableHighGDetection() { 200 | if (!isAnyAwake()) { 201 | CHECK(IMU.wakeup()); 202 | } 203 | setAwakeFlag(MOTION_AWAKE_HIGH_G); 204 | IMU.initHighG(bmiHighGConfig, false); 205 | CHECK(IMU.startHighGDetect()); 206 | highGMode_ = HighGDetectionMode::ENABLE; 207 | 208 | return SYSTEM_ERROR_NONE; 209 | } 210 | 211 | int MotionService::disableHighGDetection() { 212 | CHECK(IMU.stopHighGDetect()); 213 | highGMode_ = HighGDetectionMode::DISABLE; 214 | clearAwakeFlag(MOTION_AWAKE_HIGH_G); 215 | if (!isAnyAwake()) { 216 | CHECK(IMU.sleep()); 217 | } 218 | 219 | return SYSTEM_ERROR_NONE; 220 | } 221 | 222 | HighGDetectionMode MotionService::getHighGDetection() { 223 | return highGMode_; 224 | } 225 | 226 | int MotionService::waitOnEvent(MotionEvent& event, system_tick_t timeout) { 227 | auto ret = os_queue_take(motionEventQueue_, &event, timeout, nullptr); 228 | if (ret) { 229 | event.source = MotionSource::MOTION_NONE; 230 | } 231 | 232 | return SYSTEM_ERROR_NONE; 233 | } 234 | 235 | void MotionService::getStatistics(MotionCounters& stats) { 236 | stats = counters_; 237 | } 238 | 239 | // TODO: Migrate to conditional variables when they become available from the device OS 240 | void MotionService::thread(void* context) { 241 | MotionService* self = static_cast(context); 242 | 243 | // This thread is not expected to exit but provisions were made here to allow for the thread 244 | // to die on its own using the BREAK event. 245 | bool exitLoop = false; 246 | while (!exitLoop) { 247 | BmiEventType event; 248 | IMU.waitOnEvent(event, MotionService::MOTION_TIMEOUT_DEFAULT); 249 | switch (event) { 250 | 251 | // This event may be a result of a timeout of the waitOnEvent() call if 252 | // a timeout value was given. It can also be a mechanism to poke the service 253 | // to perform some kind of housekeeping. 254 | case BmiEventType::NONE: { 255 | self->counters_.noneEvents++; 256 | break; 257 | } 258 | 259 | // This event comes directly from the IMU device driver as a result of one of the 260 | // several interrupts supported on the device. 261 | // Capture the device event, inquire as to what caused the interrupt, and wrap this 262 | // extra information to the queue consumer. 263 | case BmiEventType::SYNC: { 264 | self->counters_.syncEvents++; 265 | uint32_t status = 0; 266 | IMU.getStatus(status, true); 267 | 268 | if (IMU.isHighGDetect(status)) { 269 | self->counters_.highGEvents++; 270 | MotionEvent event = { .source = MotionSource::MOTION_HIGH_G }; 271 | os_queue_put(self->motionEventQueue_, &event, 0, nullptr); 272 | } 273 | if (IMU.isMotionDetect(status)) { 274 | self->counters_.motionEvents++; 275 | MotionEvent event = { .source = MotionSource::MOTION_MOVEMENT }; 276 | os_queue_put(self->motionEventQueue_, &event, 0, nullptr); 277 | } 278 | break; 279 | } 280 | 281 | // This is an explicit request to exit the thread 282 | case BmiEventType::BREAK: { 283 | self->counters_.breakEvents++; 284 | exitLoop = true; 285 | break; 286 | } 287 | 288 | default: { 289 | break; 290 | } 291 | } 292 | } 293 | 294 | os_thread_exit(nullptr); 295 | } 296 | 297 | size_t MotionService::getQueueDepth() { 298 | return eventDepth_; 299 | } 300 | 301 | void MotionService::setAwakeFlag(uint32_t bits) { 302 | awakeFlags_ |= bits; 303 | } 304 | 305 | void MotionService::clearAwakeFlag(uint32_t bits) { 306 | awakeFlags_ &= ~bits; 307 | } 308 | 309 | bool MotionService::isAnyAwake() { 310 | return (awakeFlags_ == MOTION_AWAKE_NONE) ? false : true; 311 | } 312 | -------------------------------------------------------------------------------- /src/motion_service.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "Particle.h" 20 | 21 | /** 22 | * @brief Type of source for the given event. 23 | * 24 | */ 25 | enum class MotionSource { 26 | MOTION_NONE, /**< No movement, periodic timeout */ 27 | MOTION_MOVEMENT, /**< Movement detected */ 28 | MOTION_HIGH_G, /**< High-G movement detected */ 29 | MOTION_ORIENTATION, /**< Orientation change detected */ 30 | }; 31 | 32 | 33 | /** 34 | * @brief Event reporting structure. 35 | * 36 | */ 37 | struct MotionEvent { 38 | MotionSource source; /**< Type of source generating this event */ 39 | system_tick_t timestamp; /**< Timestamp, in milliseconds, of the event */ 40 | }; 41 | 42 | /** 43 | * @brief Sensitivity confuration for motion detection. 44 | * 45 | */ 46 | enum class MotionDetectionMode { 47 | NONE, /**< No motion detection */ 48 | LOW_SENSITIVITY, /**< Low sensitivity motion detection */ 49 | MEDIUM_SENSITIVITY, /**< Medium sensitivity motion detection */ 50 | HIGH_SENSITIVITY, /**< High sensitivity motion detection */ 51 | }; 52 | 53 | /** 54 | * @brief Statistics reporting structure. 55 | * 56 | */ 57 | struct MotionCounters { 58 | 59 | size_t noneEvents; /**< Count of no movement or periodic timeouts taking from queue */ 60 | size_t syncEvents; /**< Count of interrupt events from inertial motion units */ 61 | size_t motionEvents; /**< Count of motion events from inertial motion units */ 62 | size_t highGEvents; /**< Count of high G events from inertial motion units */ 63 | size_t breakEvents; /**< Count of graceful thread exits */ 64 | }; 65 | 66 | /** 67 | * @brief Configuration for high G detection. 68 | * 69 | */ 70 | enum class HighGDetectionMode { 71 | DISABLE, /**< Disabled high G detection */ 72 | ENABLE, /**< Enabled high G detection */ 73 | }; 74 | 75 | 76 | /** 77 | * @brief Motion service class to configure and service intertial motion unit events. 78 | * 79 | */ 80 | class MotionService { 81 | public: 82 | static constexpr system_tick_t MOTION_TIMEOUT_DEFAULT = 5*60*1000; 83 | static constexpr system_tick_t MOTION_EVENTS_DEFAULT = 10; 84 | 85 | /** 86 | * @brief Return instance of the motion service 87 | * 88 | * @retval MotionService& 89 | */ 90 | static MotionService &instance() 91 | { 92 | if(!_instance) 93 | { 94 | _instance = new MotionService(); 95 | } 96 | return *_instance; 97 | } 98 | 99 | /** 100 | * @brief Start the motion service 101 | * 102 | * @param eventDepth Count of maximum events in queue. 103 | * @retval SYSTEM_ERROR_NONE 104 | * @retval SYSTEM_ERROR_INVALID_STATE 105 | * @retval SYSTEM_ERROR_INTERNAL 106 | * @retval SYSTEM_ERROR_INVALID_ARGUMENT 107 | * @retval SYSTEM_ERROR_IO 108 | */ 109 | int start(size_t eventDepth = MOTION_EVENTS_DEFAULT); 110 | 111 | /** 112 | * @brief Stop the motion service, gracefully 113 | * 114 | * @retval SYSTEM_ERROR_NONE 115 | * @retval SYSTEM_ERROR_INVALID_STATE 116 | * @retval SYSTEM_ERROR_UNKNOWN 117 | */ 118 | int stop(); 119 | 120 | /** 121 | * @brief Stop the motion service, forcibly 122 | * 123 | * @retval SYSTEM_ERROR_NONE 124 | */ 125 | int kill(); 126 | 127 | /** 128 | * @brief Join the motion service thread 129 | * 130 | * @retval SYSTEM_ERROR_NONE 131 | */ 132 | int join(); 133 | 134 | /** 135 | * @brief Enable (and disable) motion detection mode for given sensitivity 136 | * 137 | * @param mode One of NONE, LOW_SENSITIVITY, MEDIUM_SENSITIVITY, HIGH_SENSITIVITY 138 | * @retval SYSTEM_ERROR_NONE 139 | */ 140 | int enableMotionDetection(MotionDetectionMode mode); 141 | 142 | /** 143 | * @brief Disable motion detection 144 | * 145 | * @retval SYSTEM_ERROR_NONE 146 | */ 147 | int disableMotionDetection(); 148 | 149 | /** 150 | * @brief Get configured motion detection mode 151 | * 152 | * @retval One of NONE, LOW_SENSITIVITY, MEDIUM_SENSITIVITY, HIGH_SENSITIVITY 153 | */ 154 | MotionDetectionMode getMotionDetection(); 155 | 156 | /** 157 | * @brief Enable high G detection mode 158 | * 159 | * @retval SYSTEM_ERROR_NONE 160 | */ 161 | int enableHighGDetection(); 162 | 163 | /** 164 | * @brief Disable high G detection mode 165 | * 166 | * @retval SYSTEM_ERROR_NONE 167 | */ 168 | int disableHighGDetection(); 169 | 170 | /** 171 | * @brief Get configured high G detection mode 172 | * 173 | * @retval One of DISABLED, ENABLED 174 | */ 175 | HighGDetectionMode getHighGDetection(); 176 | 177 | /** 178 | * @brief Wait and take event items from queue 179 | * 180 | * @param event Returned event information 181 | * @param timeout Timeout in milliseconds to wait for events. Use 0 to return immediately if no event available. 182 | * @retval SYSTEM_ERROR_NONE 183 | */ 184 | int waitOnEvent(MotionEvent& event, system_tick_t timeout); 185 | 186 | /** 187 | * @brief Get MotionService statistics 188 | * 189 | * @param stats Returned structure of MotionService statistics 190 | */ 191 | void getStatistics(MotionCounters& stats); 192 | 193 | /** 194 | * @brief Get the event queue depth 195 | * 196 | * @return size_t Queue capacity 197 | */ 198 | size_t getQueueDepth(); 199 | 200 | /** 201 | * @brief Indicate if any IMU module is awake 202 | * 203 | * @return true At least one module is keeping the IMU from sleeping 204 | * @return false No modules are keeping the IMU from sleeping 205 | */ 206 | bool isAnyAwake(); 207 | 208 | private: 209 | 210 | MotionService(); 211 | static MotionService *_instance; 212 | 213 | /** 214 | * @brief MotionService main thread to receive, process, and send events 215 | * 216 | * @param context MotionService instance pointer 217 | */ 218 | static void thread(void* context); 219 | 220 | /** 221 | * @brief Set the particuler awake flag 222 | * 223 | * @param bits Bitmap of flags to set 224 | */ 225 | void setAwakeFlag(uint32_t bits); 226 | 227 | /** 228 | * @brief Clear the particuler awake flag 229 | * 230 | * @param bits Bitmap of flags to clear 231 | */ 232 | void clearAwakeFlag(uint32_t bits); 233 | 234 | os_thread_t thread_; 235 | MotionCounters counters_; 236 | os_queue_t motionEventQueue_; 237 | MotionDetectionMode mode_; 238 | HighGDetectionMode highGMode_; 239 | uint32_t awakeFlags_; 240 | size_t eventDepth_; 241 | }; 242 | -------------------------------------------------------------------------------- /src/temperature.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #include 18 | #include "EdgePlatform.h" 19 | #include "thermistor.h" 20 | #include "temperature.h" 21 | #include "tracker_sleep.h" 22 | 23 | 24 | // Configuration based on Panasonic ERTJ1VR104FM NTC thermistor 25 | ThermistorConfig _thermistorConfig = { 26 | .circuit = ThermistorCircuit::LOW_SIDE_DIVIDER, // Thermistor is between VCC and the ADC input 27 | .type = ThermistorType::NEGATIVE_COEFF, // NTC type 28 | .beta = 3435.0, // B(25/50) figure 29 | .t0 = 25.0, // degrees C 30 | .r0 = 10000.0, // ohms 31 | .fixedR = 10000.0, // ohms 32 | .adcResolution = 4096.0, // values full-range 33 | .minTemperature = -40.0, // minimum temperature to represent 34 | .maxTemperature = 150.0 // maximum temperature to represent 35 | }; 36 | 37 | // Basic structure to hold all configuration fields 38 | struct ConfigData { 39 | double highThreshold; 40 | bool highEnable; 41 | bool highLatch; 42 | double lowThreshold; 43 | bool lowEnable; 44 | bool lowLatch; 45 | double hysteresis; 46 | }; 47 | 48 | 49 | static ConfigData _temperatureConfig = { 50 | .highThreshold = TemperatureHighDefault, 51 | .highEnable = false, 52 | .highLatch = true, 53 | .lowThreshold = TemperatureLowDefault, 54 | .lowEnable = false, 55 | .lowLatch = true, 56 | .hysteresis = TemperatureHysteresisDefault 57 | }; 58 | 59 | 60 | // Configuration service node setup 61 | // { "temp_trig" : 62 | // { "high": 25.0, 63 | // "high_en": false, 64 | // "high_latch": true, 65 | // "low": 25.0, 66 | // "low_en": false 67 | // "low_latch": true, 68 | // "hyst": 5.0 69 | // } 70 | // } 71 | 72 | enum class TempState { 73 | UNKNOWN, //< Initial state 74 | NORMAL, //< Value is not outside limit and isn't pending a pass through the hysteresis limit 75 | OUTSIDE_LIMIT, //< Value is outside of the given limit 76 | INSIDE_LIMIT, //< Value is inside of the give limit and is pending a pass through the hysteresis limit 77 | }; 78 | 79 | enum class TempChargeState { 80 | UNKNOWN, //< Initial state 81 | NORMAL, //< Value is not outside limit and isn't pending a pass through the hysteresis limit 82 | OVER_TEMPERATURE, //< Value is above the upper given limit 83 | UNDER_TEMPERATURE, //< Value is below the lower given limit 84 | OVER_CHARGE_REDUCTION, //< Value is above the intermediate given limit 85 | }; 86 | 87 | 88 | static Thermistor _thermistor; 89 | static TemperatureCallback _eventCallback = nullptr; 90 | static unsigned int chargeEvalTick = 0; 91 | 92 | void onWake(TrackerSleepContext context) { 93 | // Allow evaluation immediately after wake 94 | chargeEvalTick = 0; 95 | } 96 | 97 | float get_temperature() { 98 | return _thermistor.getTemperature(); 99 | } 100 | 101 | int temperature_init(pin_t analogPin, TemperatureCallback eventCallback) { 102 | CHECK(_thermistor.begin(analogPin, _thermistorConfig)); 103 | 104 | static ConfigObject _serviceObject 105 | ( 106 | "temp_trig", 107 | { 108 | ConfigFloat("high", &_temperatureConfig.highThreshold, _thermistorConfig.minTemperature, _thermistorConfig.maxTemperature), 109 | ConfigBool("high_en", &_temperatureConfig.highEnable), 110 | ConfigBool("high_latch", &_temperatureConfig.highLatch), 111 | ConfigFloat("low", &_temperatureConfig.lowThreshold, _thermistorConfig.minTemperature, _thermistorConfig.maxTemperature), 112 | ConfigBool("low_en", &_temperatureConfig.lowEnable), 113 | ConfigBool("low_latch", &_temperatureConfig.lowLatch), 114 | ConfigFloat("hyst", &_temperatureConfig.hysteresis, 0.0, _thermistorConfig.maxTemperature - _thermistorConfig.minTemperature), 115 | } 116 | ); 117 | 118 | CHECK(ConfigService::instance().registerModule(_serviceObject)); 119 | 120 | _eventCallback = eventCallback; 121 | 122 | void onWake(TrackerSleepContext context); 123 | 124 | return SYSTEM_ERROR_NONE; 125 | } 126 | 127 | // All state and variables related to high threshold evaluation 128 | static TempState highState = TempState::NORMAL; 129 | static std::atomic highEvents(0); 130 | static size_t highEventsLast = 0; 131 | static bool highLatch = false; 132 | 133 | size_t temperature_high_events() { 134 | auto eventsCapture = highEvents.load(); 135 | auto eventsCount = eventsCapture - highEventsLast; 136 | highEventsLast = eventsCapture; 137 | return (_temperatureConfig.highLatch) ? highLatch : eventsCount; 138 | } 139 | 140 | // All state and variables related to low threshold evaluation 141 | static TempState lowState = TempState::NORMAL; 142 | static std::atomic lowEvents(0); 143 | static size_t lowEventsLast = 0; 144 | static bool lowLatch = false; 145 | 146 | size_t temperature_low_events() { 147 | auto eventsCapture = lowEvents.load(); 148 | auto eventsCount = eventsCapture - lowEventsLast; 149 | lowEventsLast = eventsCapture; 150 | return (_temperatureConfig.lowLatch) ? lowLatch : eventsCount; 151 | } 152 | 153 | void evaluate_user_temperature(float temperature) { 154 | // Evaluate temperature against high threshold 155 | if (_temperatureConfig.highEnable) { 156 | switch (highState) { 157 | case TempState::UNKNOWN: 158 | highState = TempState::NORMAL; 159 | // Fall through 160 | case TempState::NORMAL: { 161 | if (temperature >= _temperatureConfig.highThreshold) { 162 | highEvents++; 163 | highLatch = true; 164 | highState = TempState::OUTSIDE_LIMIT; 165 | } 166 | break; 167 | } 168 | 169 | case TempState::OUTSIDE_LIMIT: { 170 | if (temperature < _temperatureConfig.highThreshold) { 171 | highState = TempState::INSIDE_LIMIT; 172 | } 173 | break; 174 | } 175 | 176 | case TempState::INSIDE_LIMIT: { 177 | if (temperature <= _temperatureConfig.highThreshold - _temperatureConfig.hysteresis) { 178 | highLatch = false; 179 | highState = TempState::NORMAL; 180 | } 181 | else if (temperature >= _temperatureConfig.highThreshold) { 182 | highState = TempState::OUTSIDE_LIMIT; 183 | } 184 | break; 185 | } 186 | } 187 | } 188 | 189 | // Evaluate temperature against low threshold 190 | if (_temperatureConfig.lowEnable) { 191 | switch (lowState) { 192 | case TempState::UNKNOWN: 193 | lowState = TempState::NORMAL; 194 | // Fall through 195 | case TempState::NORMAL: { 196 | if (temperature <= _temperatureConfig.lowThreshold) { 197 | lowEvents++; 198 | lowLatch = true; 199 | lowState = TempState::OUTSIDE_LIMIT; 200 | } 201 | break; 202 | } 203 | 204 | case TempState::OUTSIDE_LIMIT: { 205 | if (temperature > _temperatureConfig.lowThreshold) { 206 | lowState = TempState::INSIDE_LIMIT; 207 | } 208 | break; 209 | } 210 | 211 | case TempState::INSIDE_LIMIT: { 212 | if (temperature >= _temperatureConfig.lowThreshold + _temperatureConfig.hysteresis) { 213 | lowLatch = false; 214 | lowState = TempState::NORMAL; 215 | } 216 | else if (temperature <= _temperatureConfig.lowThreshold) { 217 | lowState = TempState::OUTSIDE_LIMIT; 218 | } 219 | break; 220 | } 221 | } 222 | } 223 | } 224 | 225 | int alertEventListener(TemperatureChargeEvent event) { 226 | if (_eventCallback) { 227 | return _eventCallback(event); 228 | } 229 | 230 | return SYSTEM_ERROR_NOT_SUPPORTED; 231 | } 232 | 233 | TempChargeState toChargeState(TempChargeState state) { 234 | switch (state) { 235 | case TempChargeState::UNKNOWN: 236 | break; 237 | 238 | case TempChargeState::NORMAL: 239 | alertEventListener(TemperatureChargeEvent::NORMAL); 240 | break; 241 | 242 | case TempChargeState::OVER_TEMPERATURE: 243 | alertEventListener(TemperatureChargeEvent::OVER_TEMPERATURE); 244 | break; 245 | 246 | case TempChargeState::UNDER_TEMPERATURE: 247 | alertEventListener(TemperatureChargeEvent::UNDER_TEMPERATURE); 248 | break; 249 | 250 | case TempChargeState::OVER_CHARGE_REDUCTION: 251 | alertEventListener(TemperatureChargeEvent::OVER_CHARGE_REDUCTION); 252 | break; 253 | 254 | } 255 | 256 | return state; 257 | } 258 | 259 | void evaluate_charge_temperature(float temperature) { 260 | static TempChargeState chargeTempState = TempChargeState::UNKNOWN; 261 | 262 | unsigned int evalLoopInterval = TrackerSleep::instance().isSleepDisabled() ? ChargeTickAwakeEvalInterval : ChargeTickSleepEvalInterval; 263 | if (System.uptime() - chargeEvalTick < evalLoopInterval) { 264 | return; 265 | } 266 | 267 | chargeEvalTick = System.uptime(); 268 | 269 | bool isTempHigh = (temperature >= ChargeTempHighLimit); // Inclusive 270 | bool isTempBelowHighHyst = (temperature <= (ChargeTempHighLimit - ChargeTempHyst)); // Inclusive 271 | bool isTempReducedA = (temperature >= ChargeTempReducedALimit); // Inclusive 272 | bool isTempReducedAHyst = (temperature <= (ChargeTempReducedALimit - ChargeTempHyst)); // Inclusive 273 | bool isTempLow = (temperature <= ChargeTempLowLimit); // Inclusive 274 | bool isTempAboveLowHyst = (temperature >= (ChargeTempLowLimit + ChargeTempHyst)); // Inclusive 275 | 276 | // The states are defined as follows: 277 | // 278 | // OVER_TEMPERATURE 279 | // --------------------------------------------------------^ 54 degC (ChargeTempHighLimit) 280 | // OVER_CHARGE_REDUCTION ^ OVER_TEMPERATURE v 53 degC 281 | // --------------------------------------------------------v 52 degC (ChargeTempHighLimit - ChargeTempHyst) 282 | // OVER_CHARGE_REDUCTION 283 | // --------------------------------------------------------^ 40 degC (ChargeTempReducedALimit) 284 | // NORMAL ^ OVER_CHARGE_REDUCTION v 39 degC 285 | // --------------------------------------------------------v 38 degC (ChargeTempReducedALimit - ChargeTempHyst) 286 | // NORMAL 287 | // --------------------------------------------------------^ 2 degC (ChargeTempLowLimit + ChargeTempHyst) 288 | // UNDER_TEMPERATURE ^ NORMAL v 1 degC 289 | // --------------------------------------------------------v 0 degC (ChargeTempLowLimit) 290 | // UNDER_TEMPERATURE 291 | // 292 | 293 | switch (chargeTempState) { 294 | // 295 | // Initial state at boot 296 | // 297 | case TempChargeState::UNKNOWN: { 298 | if (isTempLow) { 299 | chargeTempState = toChargeState(TempChargeState::UNDER_TEMPERATURE); 300 | } 301 | else if (isTempHigh) { 302 | chargeTempState = toChargeState(TempChargeState::OVER_TEMPERATURE); 303 | } 304 | else if (isTempReducedA) { 305 | chargeTempState = toChargeState(TempChargeState::OVER_CHARGE_REDUCTION); 306 | } 307 | else { 308 | chargeTempState = toChargeState(TempChargeState::NORMAL); 309 | } 310 | 311 | break; 312 | } 313 | 314 | // 315 | // No limits were breached 316 | // 317 | case TempChargeState::NORMAL: { 318 | if (isTempLow) { 319 | chargeTempState = toChargeState(TempChargeState::UNDER_TEMPERATURE); 320 | } 321 | else if (isTempHigh) { 322 | chargeTempState = toChargeState(TempChargeState::OVER_TEMPERATURE); 323 | } 324 | else if (isTempReducedA) { 325 | chargeTempState = toChargeState(TempChargeState::OVER_CHARGE_REDUCTION); 326 | } 327 | 328 | break; 329 | } 330 | 331 | // 332 | // Under normal charging operation 333 | // 334 | case TempChargeState::UNDER_TEMPERATURE: { 335 | if (isTempHigh) { 336 | chargeTempState = toChargeState(TempChargeState::OVER_TEMPERATURE); 337 | } 338 | else if (isTempReducedA) { 339 | chargeTempState = toChargeState(TempChargeState::OVER_CHARGE_REDUCTION); 340 | } 341 | else if (isTempAboveLowHyst) { 342 | chargeTempState = toChargeState(TempChargeState::NORMAL); 343 | } 344 | 345 | break; 346 | } 347 | 348 | // 349 | // Above normal charging operation 350 | // 351 | case TempChargeState::OVER_TEMPERATURE: { 352 | if (isTempLow) { 353 | chargeTempState = toChargeState(TempChargeState::UNDER_TEMPERATURE); 354 | } 355 | else if (isTempBelowHighHyst && isTempReducedA) { 356 | chargeTempState = toChargeState(TempChargeState::OVER_CHARGE_REDUCTION); 357 | } 358 | else if (isTempReducedAHyst) { 359 | chargeTempState = toChargeState(TempChargeState::NORMAL); 360 | } 361 | 362 | break; 363 | } 364 | 365 | // 366 | // Above normal charging operation 367 | // 368 | case TempChargeState::OVER_CHARGE_REDUCTION: { 369 | if (isTempHigh) { 370 | chargeTempState = toChargeState(TempChargeState::OVER_TEMPERATURE); 371 | } 372 | else if (isTempLow) { 373 | chargeTempState = toChargeState(TempChargeState::UNDER_TEMPERATURE); 374 | } 375 | else if (isTempReducedAHyst) { 376 | chargeTempState = toChargeState(TempChargeState::NORMAL); 377 | } 378 | 379 | break; 380 | } 381 | } 382 | } 383 | 384 | int temperature_tick() { 385 | float temperature = get_temperature(); 386 | 387 | evaluate_user_temperature(temperature); 388 | evaluate_charge_temperature(temperature); 389 | 390 | return SYSTEM_ERROR_NONE; 391 | } 392 | -------------------------------------------------------------------------------- /src/temperature.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "Particle.h" 20 | #include "tracker_config.h" 21 | #include "config_service.h" 22 | 23 | enum class TemperatureChargeEvent { 24 | NORMAL, //< Normal temperature condition 25 | OVER_TEMPERATURE, //< Over temperature condition 26 | UNDER_TEMPERATURE, //< Under temperature condition 27 | OVER_CHARGE_REDUCTION, //< Over temperature for reduced charge condition 28 | }; 29 | 30 | using TemperatureCallback = std::function; 31 | 32 | 33 | // Default temperature high threshold. 34 | constexpr double TemperatureHighDefault = 25.0; // degrees celsius 35 | 36 | // Default temperature low threshold. 37 | constexpr double TemperatureLowDefault = 25.0; // degrees celsius 38 | 39 | // Default temperature hysteresis 40 | constexpr double TemperatureHysteresisDefault = 5.0; // degrees celsius 41 | 42 | // High limit to disable battery charging (inclusive) 43 | constexpr double ChargeTempHighLimit = 54.0; // degrees celsius 44 | 45 | // Low limit to disable battery charging (inclusive) 46 | constexpr double ChargeTempLowLimit = 0.0; // degrees celsius 47 | 48 | // High limit to reduce battery charging current (inclusive) 49 | constexpr double ChargeTempReducedALimit = 40.0; // degrees celsius 50 | 51 | // Hysteresis applied to high/low limits to re-enable battery charging 52 | constexpr double ChargeTempHyst = 2.0; // degrees celsius 53 | 54 | // Rate, in seconds, to sample the temperature and evaluate battery charge enablement when awake 55 | constexpr unsigned int ChargeTickAwakeEvalInterval = 30; // seconds 56 | 57 | // Rate, in seconds, to sample the temperature and evaluate battery charge enablement when woken 58 | constexpr unsigned int ChargeTickSleepEvalInterval = 1; // seconds 59 | 60 | /** 61 | * @brief Get the current temperature 62 | * 63 | * @return float Current temperature in degrees celsius. 64 | */ 65 | float get_temperature(); 66 | 67 | /** 68 | * @brief Get the number of temperature high threshold events since last call to this function. 69 | * 70 | * @return size_t Number of events that have elapsed. 71 | */ 72 | size_t temperature_high_events(); 73 | 74 | /** 75 | * @brief Get the number of temperature low threshold events since last call to this function. 76 | * 77 | * @return size_t Number of events that have elapsed. 78 | */ 79 | size_t temperature_low_events(); 80 | 81 | /** 82 | * @brief Initialize the temperature sampling feature. 83 | * 84 | * @param [in] analogPin Analog pin to use for thermistor sampling. 85 | * 86 | * @retval SYSTEM_ERROR_NONE 87 | * @retval SYSTEM_ERROR_INVALID_ARGUMENT 88 | */ 89 | int temperature_init(pin_t analogPin, TemperatureCallback eventCallback); 90 | 91 | /** 92 | * @brief Process the temperature loop tick. 93 | * 94 | * @retval SYSTEM_ERROR_NONE 95 | */ 96 | int temperature_tick(); 97 | -------------------------------------------------------------------------------- /src/tracker_cellular.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #include "tracker_cellular.h" 18 | 19 | #ifndef ARRAY_SIZE 20 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 21 | #endif 22 | 23 | TrackerCellular *TrackerCellular::_instance = nullptr; 24 | 25 | TrackerCellular::TrackerCellular() : _signal_update(0), _thread(nullptr) 26 | { 27 | os_queue_create(&_commandQueue, sizeof(TrackerCellularCommand), 1, nullptr); 28 | _thread = new Thread("tracker_cellular", [this]() {TrackerCellular::thread_f();}, OS_THREAD_PRIORITY_DEFAULT); 29 | } 30 | 31 | int TrackerCellular::startScan() { 32 | auto event = TrackerCellularCommand::Measure; 33 | CHECK_FALSE(os_queue_put(_commandQueue, &event, 0, nullptr), SYSTEM_ERROR_BUSY); 34 | 35 | return SYSTEM_ERROR_NONE; 36 | } 37 | 38 | int TrackerCellular::parseServeCell(const char* in, CellularServing& out) { 39 | CellularServing ret; 40 | char state[16] = {}; 41 | char rat[16] = {}; 42 | 43 | out = {}; 44 | auto nitems = sscanf(in, " +QENG: \"servingcell\",\"%15[^\"]\",\"%15[^\"]\",\"%*15[^\"]\"," 45 | "%u,%u,%lX," 46 | "%*15[^,],%*15[^,],%*15[^,],%*15[^,],%*15[^,],%X,%d", 47 | state, rat, 48 | &ret.mcc, &ret.mnc, &ret.cellId, &ret.tac, &ret.signalPower); 49 | 50 | if (nitems < 7) { 51 | return SYSTEM_ERROR_NOT_ENOUGH_DATA; 52 | } 53 | 54 | if (!strncmp(rat, "CAT-M", 5) || !strncmp(rat, "eMTC", 4)) { 55 | out.rat = RadioAccessTechnology::LTE_CAT_M1; 56 | } 57 | else if (!strncmp(rat, "LTE", 3)) { 58 | out.rat = RadioAccessTechnology::LTE; 59 | } 60 | else if (!strncmp(rat, "CAT-NB", 6)) { 61 | out.rat = RadioAccessTechnology::LTE_NB_IOT; 62 | } 63 | else { 64 | return SYSTEM_ERROR_NOT_SUPPORTED; 65 | } 66 | 67 | out.mcc = ret.mcc; 68 | out.mnc = ret.mnc; 69 | out.cellId = ret.cellId; 70 | out.tac = ret.tac; 71 | out.signalPower = ret.signalPower; 72 | 73 | return SYSTEM_ERROR_NONE; 74 | } 75 | 76 | int TrackerCellular::serving_cb(int type, const char* buf, int len, TrackerCellular* context) { 77 | if (type == TYPE_OK) { 78 | return RESP_OK; 79 | } 80 | 81 | (void)parseServeCell(buf, context->_servingTower); 82 | return WAIT; 83 | } 84 | 85 | int TrackerCellular::parseCell(const char* in, CellularNeighbor& out) { 86 | CellularNeighbor ret; 87 | char rat[16] = {0}; 88 | 89 | auto nitems = sscanf(in, " +QENG: \"neighbourcell %*15[^\"]\",\"%15[^\"]\",%lu,%lu,%d,%d,%d", 90 | rat, 91 | &ret.earfcn, &ret.neighborId, &ret.signalQuality, &ret.signalPower, &ret.signalStrength); 92 | 93 | if (nitems < 6) { 94 | return SYSTEM_ERROR_NOT_ENOUGH_DATA; 95 | } 96 | 97 | if (!strncmp(rat, "CAT-M", 5) || !strncmp(rat, "eMTC", 4)) { 98 | out.rat = RadioAccessTechnology::LTE_CAT_M1; 99 | } 100 | else if (!strncmp(rat, "LTE", 3)) { 101 | out.rat = RadioAccessTechnology::LTE; 102 | } 103 | else if (!strncmp(rat, "CAT-NB", 6)) { 104 | out.rat = RadioAccessTechnology::LTE_NB_IOT; 105 | } 106 | else { 107 | return SYSTEM_ERROR_NOT_SUPPORTED; 108 | } 109 | 110 | out.earfcn = ret.earfcn; 111 | out.neighborId = ret.neighborId; 112 | out.signalQuality = ret.signalQuality; 113 | out.signalPower = ret.signalPower; 114 | out.signalStrength = ret.signalStrength; 115 | 116 | return SYSTEM_ERROR_NONE; 117 | } 118 | 119 | int TrackerCellular::neighbor_cb(int type, const char* buf, int len, TrackerCellular* context) { 120 | if (type == TYPE_OK) { 121 | return RESP_OK; 122 | } 123 | 124 | CellularNeighbor neighbor {}; 125 | if (parseCell(buf, neighbor) == SYSTEM_ERROR_NONE) { 126 | context->addNeighborList(neighbor); 127 | } 128 | 129 | return WAIT; 130 | } 131 | 132 | void TrackerCellular::resetNeighborList() { 133 | _towerListSize = 0; 134 | } 135 | 136 | int TrackerCellular::addNeighborList(const CellularNeighbor& neighbor) { 137 | if (0 > _towerListSize) { 138 | resetNeighborList(); 139 | } 140 | if (ARRAY_SIZE(_towerList) > (size_t)_towerListSize) { 141 | _towerList[_towerListSize++] = neighbor; 142 | return SYSTEM_ERROR_NONE; 143 | } 144 | 145 | return SYSTEM_ERROR_NO_MEMORY; 146 | } 147 | 148 | TrackerCellularCommand TrackerCellular::waitOnEvent(system_tick_t timeout) { 149 | TrackerCellularCommand event {TrackerCellularCommand::None}; 150 | auto ret = os_queue_take(_commandQueue, &event, timeout, nullptr); 151 | if (ret) { 152 | event = TrackerCellularCommand::None; 153 | } 154 | 155 | return event; 156 | } 157 | 158 | // a thread to capture cellular signal strength in a non-blocking fashion 159 | void TrackerCellular::thread_f() 160 | { 161 | auto loop = true; 162 | while (loop) { 163 | // Look for requests and provide a loop delay 164 | auto event = waitOnEvent(TRACKER_CELLULAR_PERIOD_SUCCESS_MS); 165 | 166 | if (Cellular.ready()) { 167 | // Grab the cellular strength on every loop iteration 168 | auto rssi = Cellular.RSSI(); 169 | 170 | if (rssi.getStrengthValue() < 0) { 171 | auto uptime = System.uptime(); 172 | WITH_LOCK(mutex) { 173 | _signal = rssi; 174 | _signal_update = uptime; 175 | } 176 | } else { 177 | _signal_update = 0; 178 | } 179 | } 180 | 181 | switch (event) { 182 | case TrackerCellularCommand::None: 183 | // Do nothing 184 | break; 185 | 186 | case TrackerCellularCommand::Exit: 187 | // Get out of main loop and join 188 | loop = false; 189 | break; 190 | 191 | case TrackerCellularCommand::Measure: { 192 | // Access to this data will always be requested in advance. We just need 193 | // to take inventory of what has been collected and data from the operation. 194 | 195 | if (!Cellular.ready()) { 196 | WITH_LOCK(mutex) { 197 | _userServingTower = {}; 198 | _userTowerListSize = 0; 199 | } 200 | // The cellular modem is not even ready (maybe not powered) so leave 201 | break; 202 | } 203 | 204 | auto serveRet = Cellular.command(serving_cb, this, 10000, "AT+QENG=\"servingcell\"\r\n"); 205 | resetNeighborList(); // Clears the list 206 | auto neighborRet = Cellular.command(neighbor_cb, this, 10000, "AT+QENG=\"neighbourcell\"\r\n"); 207 | // Simple copies for thread safety and to avoid very long holds on the mutex 208 | WITH_LOCK(mutex) { 209 | if (RESP_OK == serveRet) { 210 | _userServingTower = _servingTower; 211 | } else { 212 | _userServingTower = {}; 213 | } 214 | if (RESP_OK == neighborRet) { 215 | _userTowerListSize = _towerListSize; 216 | for (int i = 0;i < _towerListSize;++i) { 217 | _userTowerList[i] = _towerList[i]; 218 | } 219 | } else { 220 | _userTowerListSize = 0; 221 | } 222 | } 223 | break; 224 | } 225 | 226 | default: 227 | break; 228 | } 229 | } 230 | 231 | // Kill the thread if we get here 232 | _thread->cancel(); 233 | } 234 | 235 | int TrackerCellular::getSignal(CellularSignal &signal, unsigned int max_age) 236 | { 237 | const std::lock_guard lg(mutex); 238 | 239 | if(!_signal_update || System.uptime() - _signal_update > max_age) 240 | { 241 | return -ENODATA; 242 | } 243 | 244 | signal = _signal; 245 | return 0; 246 | } 247 | 248 | unsigned int TrackerCellular::getSignalUpdate() 249 | { 250 | return _signal_update; 251 | } 252 | 253 | int TrackerCellular::getServingTower(CellularServing& serving) { 254 | WITH_LOCK(mutex) { 255 | serving = _userServingTower; 256 | } 257 | 258 | return SYSTEM_ERROR_NONE; 259 | } 260 | 261 | int TrackerCellular::getNeighborTowers(Vector& neigbors) { 262 | WITH_LOCK(mutex) { 263 | for (int i = 0;i < _userTowerListSize;++i) { 264 | neigbors.append(_userTowerList[i]); 265 | } 266 | } 267 | 268 | return SYSTEM_ERROR_NONE; 269 | } 270 | -------------------------------------------------------------------------------- /src/tracker_cellular.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "Particle.h" 20 | 21 | // delay between checking cell strength when no errors detected 22 | constexpr system_tick_t TRACKER_CELLULAR_PERIOD_SUCCESS_MS {1000}; 23 | 24 | // delay between checking cell strength when errors detected 25 | // longer than success to minimize thrashing on the cell interface which could 26 | // delay recovery in Device-OS 27 | constexpr system_tick_t TRACKER_CELLULAR_PERIOD_ERROR_MS {10000}; 28 | 29 | // cell updates need to be at least this often or flagged as an error 30 | constexpr unsigned int TRACKER_CELLULAR_DEFAULT_MAX_AGE_SEC {10}; 31 | 32 | // Only have enough space for so many neighbor towers 33 | constexpr size_t TRACKER_CELLULAR_MAX_NEIGHBORS {4}; 34 | 35 | // Maximum amount of time, in milliseconds, that a tower scan should take 36 | constexpr system_tick_t TRACKER_CELLULAR_SCAN_DELAY {500 + 500}; 37 | 38 | /** 39 | * @brief Commands to instruct cellular thread 40 | * 41 | */ 42 | enum class TrackerCellularCommand { 43 | None, /**< Do nothing */ 44 | Measure, /**< Perform cellular scan */ 45 | Exit, /**< Exit from thread */ 46 | }; 47 | 48 | /** 49 | * @brief Type of radio used in modem to tower communications 50 | * 51 | */ 52 | enum class RadioAccessTechnology { 53 | NONE = -1, 54 | LTE = 7, 55 | LTE_CAT_M1 = 8, 56 | LTE_NB_IOT = 9 57 | }; 58 | 59 | /** 60 | * @brief Information identifying the serving tower 61 | * 62 | */ 63 | struct CellularServing { 64 | RadioAccessTechnology rat {RadioAccessTechnology::NONE}; 65 | unsigned int mcc {0}; // 0-999 66 | unsigned int mnc {0}; // 0-999 67 | uint32_t cellId {0}; // 28-bits 68 | unsigned int tac {0}; // 16-bits 69 | int signalPower {0}; 70 | }; 71 | 72 | /** 73 | * @brief Information identifying a neighboring tower 74 | * 75 | */ 76 | struct CellularNeighbor { 77 | RadioAccessTechnology rat {RadioAccessTechnology::NONE}; 78 | uint32_t earfcn {0}; // 28-bits 79 | uint32_t neighborId {0}; // 0-503 80 | int signalQuality {0}; 81 | int signalPower {0}; 82 | int signalStrength {0}; 83 | }; 84 | 85 | /** 86 | * @brief TrackerCellular class to grab cellular modem and tower information 87 | * 88 | */ 89 | class TrackerCellular { 90 | public: 91 | /** 92 | * @brief Start scan for cellular towers 93 | * 94 | * @retval SYSTEM_ERROR_NONE Success 95 | * @retval SYSTEM_ERROR_BUSY Cannot start a new scan 96 | */ 97 | int startScan(); 98 | 99 | /** 100 | * @brief Get the cellular signal strength 101 | * 102 | * @param[out] signal Object with signal strength values 103 | * @param[in] max_age How old a measurement can be to be valid 104 | * @retval 0 Success 105 | * @retval -ENODATA Measurement is old 106 | */ 107 | int getSignal(CellularSignal &signal, unsigned int max_age=TRACKER_CELLULAR_DEFAULT_MAX_AGE_SEC); 108 | 109 | /** 110 | * @brief Get the signal strength age 111 | * 112 | * @return unsigned int Age in seconds 113 | */ 114 | unsigned int getSignalUpdate(); 115 | 116 | /** 117 | * @brief Get the serving tower information 118 | * 119 | * @param[out] serving The serving tower information 120 | * @retval SYSTEM_ERROR_NONE Success 121 | */ 122 | int getServingTower(CellularServing& serving); 123 | 124 | /** 125 | * @brief Get the neighbor towers information 126 | * 127 | * @param[out] neigbors The neighbor towers information 128 | * @retval SYSTEM_ERROR_NONE Success 129 | */ 130 | int getNeighborTowers(Vector& neigbors); 131 | 132 | /** 133 | * @brief Lock object 134 | * 135 | */ 136 | inline void lock() {mutex.lock();} 137 | 138 | /** 139 | * @brief Unlock object 140 | * 141 | */ 142 | inline void unlock() {mutex.unlock();} 143 | 144 | /** 145 | * @brief Singleton class instance access for TrackerCellular 146 | * 147 | * @return TrackerCellular& 148 | */ 149 | static TrackerCellular &instance() 150 | { 151 | if(!_instance) 152 | { 153 | _instance = new TrackerCellular(); 154 | } 155 | return *_instance; 156 | } 157 | 158 | private: 159 | TrackerCellular(); 160 | 161 | CellularSignal _signal; 162 | unsigned int _signal_update; 163 | 164 | CellularServing _servingTower; 165 | CellularServing _userServingTower; 166 | CellularNeighbor _towerList[TRACKER_CELLULAR_MAX_NEIGHBORS]; 167 | int _towerListSize {0}; 168 | CellularNeighbor _userTowerList[TRACKER_CELLULAR_MAX_NEIGHBORS]; 169 | int _userTowerListSize {0}; 170 | 171 | RecursiveMutex mutex; 172 | os_queue_t _commandQueue; 173 | Thread * _thread; 174 | 175 | static int parseServeCell(const char* in, CellularServing& out); 176 | static int serving_cb(int type, const char* buf, int len, TrackerCellular* context); 177 | static int parseCell(const char* in, CellularNeighbor& out); 178 | static int neighbor_cb(int type, const char* buf, int len, TrackerCellular* context); 179 | void resetNeighborList(); 180 | int addNeighborList(const CellularNeighbor& neighbor); 181 | TrackerCellularCommand waitOnEvent(system_tick_t timeout); 182 | void thread_f(); 183 | 184 | static TrackerCellular *_instance; 185 | }; 186 | -------------------------------------------------------------------------------- /src/tracker_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "Particle.h" 20 | 21 | //----------------------------------------------------------------------------- 22 | // Tracker platform 23 | //----------------------------------------------------------------------------- 24 | 25 | #if (PLATFORM_ID != PLATFORM_TRACKER) 26 | #error "Platform not supported" 27 | #endif 28 | 29 | // 30 | // Tracker types 31 | // 32 | #define TRACKER_MODEL_BARE_SOM_DEFAULT (0xffff) 33 | #define TRACKER_MODEL_BARE_SOM (0x0000) 34 | #define TRACKER_MODEL_EVAL (0x0001) 35 | #define TRACKER_MODEL_TRACKERONE (0x0002) 36 | 37 | 38 | // 39 | // Variables that can be passed in through compile flags (not Workbench) 40 | // 41 | // Simple macro to check if the product ID needs to be specified 42 | #define TRACKER_PRODUCT_NEEDED ((SYSTEM_VERSION >> 24) == 3) 43 | 44 | #ifndef TRACKER_PRODUCT_ID 45 | #define TRACKER_PRODUCT_ID (PLATFORM_ID) 46 | #endif 47 | 48 | #ifndef TRACKER_PRODUCT_VERSION 49 | #define TRACKER_PRODUCT_VERSION (19) 50 | #endif 51 | 52 | #if ( SYSTEM_VERSION >= SYSTEM_VERSION_DEFAULT(4, 0, 0) ) 53 | #define TRACKER_HAS_HW_INFO 54 | #endif // SYSTEM_VERSION 55 | 56 | #if ( (SYSTEM_VERSION < SYSTEM_VERSION_ALPHA(5, 0, 0, 1)) && (PLATFORM_ID == PLATFORM_TRACKER) ) 57 | #define TRACKER_USE_MEMFAULT 58 | #endif // SYSTEM_VERSION 59 | 60 | // 61 | // Pin and interface mapping 62 | // 63 | #define BMI_SPI_INTERFACE (SPI1) 64 | #define BMI_SPI_CS_PIN (SEN_CS) 65 | #define BMI_INT_PIN (SEN_INT) 66 | #define BMI_INT_MODE (FALLING) 67 | 68 | #define UBLOX_SPI_INTERFACE (SPI1) 69 | #define UBLOX_CS_PIN (GPS_CS) 70 | #define UBLOX_PWR_EN_PIN (GPS_PWR) 71 | #define UBLOX_RESETN_PIN (GPS_RST) 72 | #define UBLOX_TX_READY_MCU_PIN (GPS_INT) 73 | #define UBLOX_TX_READY_GPS_PIN (14) // PIO 14 is EXTINT on GPS Module 74 | 75 | #define ESP32_SPI_INTERFACE (SPI1) 76 | #define ESP32_CS_PIN (WIFI_CS) 77 | #define ESP32_BOOT_MODE_PIN (WIFI_BOOT) 78 | #define ESP32_PWR_EN_PIN (WIFI_EN) 79 | #define ESP32_INT_PIN (WIFI_INT) 80 | 81 | #define MCP_CAN_SPI_INTERFACE (SPI1) 82 | #define MCP_CAN_PWR_EN_PIN (CAN_PWR) 83 | #define MCP_CAN_RESETN_PIN (CAN_RST) 84 | #define MCP_CAN_CS_PIN (CAN_CS) 85 | #define MCP_CAN_INT_PIN (CAN_INT) 86 | #define MCP_CAN_STBY_PIN (CAN_STBY) 87 | 88 | 89 | // 90 | // Tracker One Specifc IO 91 | // 92 | #define TRACKER_THERMISTOR (A0) 93 | #define TRACKER_USER_BUTTON (D1) 94 | #define TRACKER_GNSS_LOCK_LED (D2) 95 | 96 | //#define RTC_WDT_DISABLE // Optional define for Tracker 97 | -------------------------------------------------------------------------------- /src/tracker_fuelgauge.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #include "Particle.h" 18 | #include "tracker_fuelgauge.h" 19 | #include "model_gauge.h" 20 | 21 | using namespace particle::power; 22 | 23 | const model_config_t model_config_lg18650_1S4P = { 24 | .EmptyAdjustment=0, 25 | .FullAdjustment=100, 26 | .RCOMP0 = 123, 27 | .TempCoUp = -0.0, 28 | .TempCoDown = -0.0, 29 | .OCVTest = 56176, 30 | .SOCCheckA = 225, 31 | .SOCCheckB = 227, 32 | .bits = 19, 33 | .model_data = { 34 | 0x99, 0x20, 0xA6, 0xA0, 0xA9, 0x50, 0xAC, 0x40, 0xB0, 0x60, 0xB3, 0x20, 0xB4, 0xF0, 0xB7, 0x60, 35 | 0xBB, 0xF0, 0xBE, 0xC0, 0xC2, 0x00, 0xC5, 0x50, 0xC8, 0xF0, 0xCB, 0x10, 0xCD, 0x10, 0xD1, 0x70, 36 | 0x01, 0x20, 0x14, 0x40, 0x0A, 0xA0, 0x0C, 0x40, 0x1A, 0x00, 0x23, 0x20, 0x1D, 0xE0, 0x0F, 0xA0, 37 | 0x0A, 0x60, 0x13, 0x80, 0x11, 0xE0, 0x0F, 0x00, 0x11, 0x40, 0x27, 0x80, 0x0A, 0xA0, 0x0A, 0xA0 38 | } 39 | }; 40 | 41 | 42 | TrackerFuelGauge *TrackerFuelGauge::_instance = nullptr; 43 | static ModelGauge model_gauge(model_config_lg18650_1S4P); 44 | 45 | void TrackerFuelGauge::init() 46 | { 47 | // load model config when power on 48 | model_gauge.load_config(); 49 | 50 | /* 51 | Notify DVOS to switch SOC bits. 52 | DVOS 3.3.0 and above 53 | */ 54 | auto cfg = System.getPowerConfiguration(); 55 | //Log.info("## soc_bits == %d ##",cfg.socBitPrecision()); 56 | if(model_config_lg18650_1S4P.bits == 19) 57 | {// 19 Bits 58 | cfg.socBitPrecision(SOC_19_BIT_PRECISION); 59 | } 60 | else 61 | {// 18 Bits 62 | cfg.socBitPrecision(DEFAULT_SOC_18_BIT_PRECISION); 63 | } 64 | System.setPowerConfiguration(cfg); 65 | 66 | verify_model(); 67 | } 68 | 69 | void TrackerFuelGauge::verify_model() 70 | { 71 | // verify model, reload model if verify failed 72 | auto ret = model_gauge.verify_model(); 73 | verifyCount++; 74 | if (ModelGaugeStatus::NONE != ret) { 75 | verifyFail++; 76 | } 77 | } 78 | 79 | void TrackerFuelGauge::loop() 80 | { 81 | // verify model every 1 hour 82 | if(System.uptime() - last_1h >= 3600) 83 | { 84 | last_1h = System.uptime(); 85 | verify_model(); 86 | } 87 | #ifdef FUEL_GAUGE_TEST 88 | test(); 89 | #endif 90 | } 91 | 92 | /** 93 | * @brief get soc percentage 94 | * @return soc percentage value 95 | */ 96 | float TrackerFuelGauge::getSoC() 97 | { 98 | return model_gauge.get_soc(); 99 | } 100 | 101 | /** 102 | * @brief get battery voltage 103 | * @return voltage value 104 | */ 105 | float TrackerFuelGauge::getVolt() 106 | { 107 | return model_gauge.get_volt(); 108 | } 109 | 110 | 111 | #ifdef FUEL_GAUGE_TEST 112 | int readPmicRegister(uint8_t address, uint8_t reg, uint8_t* val, int length) { 113 | Wire1.beginTransmission(address); 114 | Wire1.write(®, 1); 115 | CHECK_TRUE(Wire1.endTransmission(false) == 0, SYSTEM_ERROR_INTERNAL); 116 | 117 | auto remaining = std::min(length, I2C_BUFFER_LENGTH); 118 | auto readLength = (int)Wire1.requestFrom((int)address, remaining); 119 | if (readLength != remaining) { 120 | Wire1.endTransmission(); 121 | return SYSTEM_ERROR_INTERNAL; 122 | } 123 | 124 | while (Wire1.available() && remaining--) { 125 | *val++ = Wire1.read(); 126 | } 127 | 128 | return SYSTEM_ERROR_NONE; 129 | } 130 | 131 | #include "temperature.h" 132 | void TrackerFuelGauge::enable_publish_pmic_regs(bool enable) 133 | { 134 | publishPMICRegs = enable; 135 | } 136 | 137 | 138 | void TrackerFuelGauge::test() 139 | { 140 | static uint32_t publicInterval = 0; 141 | FuelGauge fuel; 142 | extern float stsTemperature; 143 | if (System.uptime() - publicInterval > 60 && Particle.connected()) 144 | { 145 | float thTemperature = get_temperature(); 146 | auto source = System.powerSource(); 147 | int state = System.batteryState(); 148 | float batterySocSystem = System.batteryCharge(); 149 | const String szPowerSources[] = { 150 | "unknown", "vin", "usb host", "usb adapter", 151 | "usb otg", "battery" 152 | }; 153 | const String szBatteryState[] = 154 | { 155 | "UNKNOWN","NOT_CHARGING","CHARGING","CHARGED","DISCHARGING","FAULT","DISCONNECTED" 156 | }; 157 | if (state > BATTERY_STATE_DISCONNECTED) 158 | { 159 | state = BATTERY_STATE_UNKNOWN; 160 | } 161 | 162 | 163 | 164 | 165 | CloudService &cloud_service = CloudService::instance(); 166 | cloud_service.beginCommand("FUEL_GUAGE_TEST"); 167 | cloud_service.writer().name("FUEL_GUAGE_TEST").beginObject(); 168 | cloud_service.writer().name("source").value(szPowerSources[source].c_str()); 169 | cloud_service.writer().name("state").value(szBatteryState[state].c_str()); 170 | cloud_service.writer().name("sys_soc").value(batterySocSystem,2); 171 | cloud_service.writer().name("fg_soc").value(fuel.getSoC(),2); 172 | cloud_service.writer().name("mg_soc").value(getSoC(),2); 173 | cloud_service.writer().name("voltage").value(getVolt(),3); 174 | cloud_service.writer().name("verify").value(verifyCount); 175 | cloud_service.writer().name("reload").value(verifyFail); 176 | cloud_service.writer().name("stemp").value((double)stsTemperature, 1); 177 | cloud_service.writer().name("ttemp").value((double)thTemperature, 1); 178 | if (publishPMICRegs) 179 | { 180 | uint8_t regs[11] {}; 181 | { 182 | PMIC pmic(true); 183 | auto ret = readPmicRegister(0x6b, 0, regs, ARRAY_SIZE(regs)); 184 | } 185 | auto powerConfig = System.getPowerConfiguration(); 186 | cloud_service.writer().name("charge_current").value(powerConfig.batteryChargeCurrent()); 187 | cloud_service.writer().name("regs").beginArray(); 188 | for (int i = 0;i < ARRAY_SIZE(regs);i++) { 189 | cloud_service.writer().value((unsigned int)regs[i]); 190 | } 191 | cloud_service.writer().endArray(); 192 | } 193 | 194 | 195 | cloud_service.writer().endObject(); 196 | 197 | cloud_service.lock(); 198 | int rval = cloud_service.send(); 199 | cloud_service.unlock(); 200 | //Log.info("%.*s", cloud_service.writer().dataSize(), cloud_service.writer().buffer()); 201 | //Log.info("### Publish FuelGauge Data %s ###",rval == -EBUSY ? "FAIL" : "SUCCESS"); 202 | 203 | publicInterval = System.uptime(); 204 | } 205 | } 206 | #endif 207 | -------------------------------------------------------------------------------- /src/tracker_fuelgauge.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "config_service.h" 20 | 21 | class TrackerFuelGauge 22 | { 23 | public: 24 | static TrackerFuelGauge &instance() 25 | { 26 | if(!_instance) 27 | { 28 | _instance = new TrackerFuelGauge(); 29 | } 30 | return *_instance; 31 | } 32 | void init(); 33 | void loop(); 34 | 35 | /** 36 | * @brief get soc percentage 37 | * @return soc percentage value 38 | */ 39 | float getSoC(); 40 | 41 | /** 42 | * @brief get battery voltage 43 | * @return voltage value 44 | */ 45 | float getVolt(); 46 | #ifdef FUEL_GAUGE_TEST 47 | void enable_publish_pmic_regs(bool enable); 48 | #endif 49 | private: 50 | TrackerFuelGauge() {} 51 | void verify_model(); 52 | #ifdef FUEL_GAUGE_TEST 53 | void test(); 54 | bool publishPMICRegs = false; 55 | #endif 56 | static TrackerFuelGauge *_instance; 57 | uint32_t last_1h = 0; 58 | uint32_t verifyCount {}; 59 | uint32_t verifyFail {}; 60 | }; 61 | -------------------------------------------------------------------------------- /src/tracker_imu.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | 18 | //***************** INCLUDES ********************* 19 | #include "Particle.h" 20 | #include "tracker_imu.h" 21 | #include "EdgePlatform.h" 22 | #include "bmi160.h" 23 | 24 | 25 | //***************** DEFINES ********************** 26 | 27 | 28 | //***************** CONSTANTS ******************** 29 | 30 | 31 | 32 | //***************** GLOBALS ********************** 33 | TrackerImu *TrackerImu::_instance = nullptr; 34 | 35 | 36 | //************ CLASS: TrackerImu ************ 37 | TrackerImu::TrackerImu() 38 | : isInitialized_(false), 39 | imu_(BmiVariant::IMU_INVALID) 40 | { 41 | 42 | } 43 | 44 | BmiVariant TrackerImu::getImuType(void) 45 | { 46 | auto imu = EdgePlatform::instance().getImu(); 47 | 48 | // Set the IMU type 49 | switch(imu) 50 | { 51 | case EdgePlatform::ImuVariant::eBMI160: 52 | imu_ = BmiVariant::IMU_BMI160; 53 | break; 54 | default: 55 | imu_ = BmiVariant::IMU_INVALID; 56 | break; 57 | } 58 | 59 | isInitialized_ = true; 60 | 61 | return imu_; 62 | } 63 | 64 | int TrackerImu::begin(const TwoWire* interface, uint8_t address, pin_t interruptPin, size_t eventDepth) 65 | { 66 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 67 | 68 | switch(imu_) 69 | { 70 | case BmiVariant::IMU_BMI160: 71 | { 72 | return BMI160.begin(interface, address, interruptPin, eventDepth); 73 | break; 74 | } 75 | } 76 | 77 | return SYSTEM_ERROR_INVALID_STATE; 78 | } 79 | 80 | 81 | int TrackerImu::begin(const SPIClass& interface, pin_t selectPin, pin_t interruptPin, size_t eventDepth) 82 | { 83 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 84 | 85 | switch(imu_) 86 | { 87 | case BmiVariant::IMU_BMI160: 88 | { 89 | return BMI160.begin(interface, selectPin, interruptPin, eventDepth); 90 | break; 91 | } 92 | } 93 | 94 | return SYSTEM_ERROR_INVALID_STATE; 95 | } 96 | 97 | 98 | int TrackerImu::end() 99 | { 100 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 101 | 102 | switch(imu_) 103 | { 104 | case BmiVariant::IMU_BMI160: 105 | { 106 | return BMI160.end(); 107 | break; 108 | } 109 | } 110 | 111 | return SYSTEM_ERROR_INVALID_STATE; 112 | } 113 | 114 | int TrackerImu::reset() 115 | { 116 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 117 | 118 | switch(imu_) 119 | { 120 | case BmiVariant::IMU_BMI160: 121 | { 122 | return BMI160.reset(); 123 | break; 124 | } 125 | } 126 | 127 | return SYSTEM_ERROR_INVALID_STATE; 128 | } 129 | 130 | int TrackerImu::sleep() 131 | { 132 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 133 | 134 | switch(imu_) 135 | { 136 | case BmiVariant::IMU_BMI160: 137 | { 138 | return BMI160.sleep(); 139 | break; 140 | } 141 | } 142 | 143 | return SYSTEM_ERROR_INVALID_STATE; 144 | } 145 | 146 | int TrackerImu::wakeup() 147 | { 148 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 149 | 150 | switch(imu_) 151 | { 152 | case BmiVariant::IMU_BMI160: 153 | { 154 | return BMI160.wakeup(); 155 | break; 156 | } 157 | } 158 | 159 | return SYSTEM_ERROR_INVALID_STATE; 160 | } 161 | 162 | int TrackerImu::syncEvent(BmiEventType event) 163 | { 164 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_BUSY); 165 | 166 | switch(imu_) 167 | { 168 | case BmiVariant::IMU_BMI160: 169 | { 170 | return BMI160.syncEvent(static_cast(event)); 171 | break; 172 | } 173 | } 174 | 175 | return SYSTEM_ERROR_BUSY; 176 | } 177 | 178 | int TrackerImu::waitOnEvent(BmiEventType& event, system_tick_t timeout) 179 | { 180 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_NONE); 181 | 182 | switch(imu_) 183 | { 184 | case BmiVariant::IMU_BMI160: 185 | { 186 | Bmi160::Bmi160EventType evt160; 187 | auto status = BMI160.waitOnEvent(evt160, timeout); 188 | event = static_cast(evt160); 189 | return status; 190 | break; 191 | } 192 | } 193 | 194 | return SYSTEM_ERROR_NONE; 195 | } 196 | 197 | int TrackerImu::initAccelerometer(BmiAccelerometerConfig& config, bool feedback) 198 | { 199 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 200 | 201 | switch(imu_) 202 | { 203 | case BmiVariant::IMU_BMI160: 204 | { 205 | Bmi160AccelerometerConfig cfg160{0}; 206 | cfg160.rate = config.rate; 207 | cfg160.range = config.range; 208 | return BMI160.initAccelerometer(cfg160, feedback); 209 | break; 210 | } 211 | } 212 | 213 | return SYSTEM_ERROR_INVALID_STATE; 214 | } 215 | 216 | int TrackerImu::getAccelerometer(BmiAccelerometer& data) 217 | { 218 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 219 | 220 | switch(imu_) 221 | { 222 | case BmiVariant::IMU_BMI160: 223 | { 224 | Bmi160Accelerometer data160{0}; 225 | auto retval = BMI160.getAccelerometer(data160); 226 | data.x = data160.x; 227 | data.y = data160.y; 228 | data.z = data160.z; 229 | return retval; 230 | break; 231 | } 232 | } 233 | 234 | return SYSTEM_ERROR_INVALID_STATE; 235 | } 236 | 237 | int TrackerImu::getAccelerometerPmu(BmiPowerState& pmu) 238 | { 239 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 240 | 241 | switch(imu_) 242 | { 243 | case BmiVariant::IMU_BMI160: 244 | { 245 | Bmi160::Bmi160PowerState data160{0}; 246 | auto retval = BMI160.getAccelerometerPmu(data160); 247 | pmu = (BmiPowerState)data160; 248 | return retval; 249 | break; 250 | } 251 | } 252 | 253 | return SYSTEM_ERROR_INVALID_STATE; 254 | } 255 | 256 | int TrackerImu::initMotion(BmiAccelMotionConfig& config, bool feedback) 257 | { 258 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 259 | 260 | switch(imu_) 261 | { 262 | case BmiVariant::IMU_BMI160: 263 | { 264 | Bmi160AccelMotionConfig cfg160{}; 265 | cfg160.mode = (Bmi160AccelMotionMode)config.mode; 266 | cfg160.motionDuration = config.motionDuration; 267 | cfg160.motionProof = (Bmi160AccelSignificantMotionProof)config.motionProof; 268 | cfg160.motionSkip = (Bmi160AccelSignificantMotionSkip)config.motionSkip; 269 | cfg160.motionThreshold = config.motionThreshold; 270 | return BMI160.initMotion(cfg160, feedback); 271 | break; 272 | } 273 | } 274 | 275 | return SYSTEM_ERROR_INVALID_STATE; 276 | } 277 | 278 | int TrackerImu::initHighG(BmiAccelHighGConfig& config, bool feedback) 279 | { 280 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 281 | 282 | switch(imu_) 283 | { 284 | case BmiVariant::IMU_BMI160: 285 | { 286 | Bmi160AccelHighGConfig cfg160{}; 287 | cfg160.duration = config.duration; 288 | cfg160.hysteresis = config.hysteresis; 289 | cfg160.threshold = config.threshold; 290 | return BMI160.initHighG(cfg160, feedback); 291 | break; 292 | } 293 | } 294 | 295 | return SYSTEM_ERROR_INVALID_STATE; 296 | } 297 | 298 | int TrackerImu::startMotionDetect() 299 | { 300 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 301 | 302 | switch(imu_) 303 | { 304 | case BmiVariant::IMU_BMI160: 305 | { 306 | return BMI160.startMotionDetect(); 307 | break; 308 | } 309 | } 310 | 311 | return SYSTEM_ERROR_INVALID_STATE; 312 | } 313 | 314 | int TrackerImu::stopMotionDetect() 315 | { 316 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 317 | 318 | switch(imu_) 319 | { 320 | case BmiVariant::IMU_BMI160: 321 | { 322 | return BMI160.stopMotionDetect(); 323 | break; 324 | } 325 | } 326 | 327 | return SYSTEM_ERROR_INVALID_STATE; 328 | } 329 | 330 | int TrackerImu::startHighGDetect() 331 | { 332 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 333 | 334 | switch(imu_) 335 | { 336 | case BmiVariant::IMU_BMI160: 337 | { 338 | return BMI160.startHighGDetect(); 339 | break; 340 | } 341 | } 342 | 343 | return SYSTEM_ERROR_INVALID_STATE; 344 | } 345 | 346 | int TrackerImu::stopHighGDetect() 347 | { 348 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 349 | 350 | switch(imu_) 351 | { 352 | case BmiVariant::IMU_BMI160: 353 | { 354 | return BMI160.stopHighGDetect(); 355 | break; 356 | } 357 | } 358 | 359 | return SYSTEM_ERROR_INVALID_STATE; 360 | } 361 | 362 | int TrackerImu::getStatus(uint32_t& val, bool clear) 363 | { 364 | CHECK_TRUE(isInitialized_, SYSTEM_ERROR_INVALID_STATE); 365 | 366 | switch(imu_) 367 | { 368 | case BmiVariant::IMU_BMI160: 369 | { 370 | return BMI160.getStatus(val, clear); 371 | break; 372 | } 373 | } 374 | 375 | return SYSTEM_ERROR_INVALID_STATE; 376 | } 377 | 378 | bool TrackerImu::isMotionDetect(uint32_t val) 379 | { 380 | CHECK_TRUE(isInitialized_, false); 381 | 382 | switch(imu_) 383 | { 384 | case BmiVariant::IMU_BMI160: 385 | { 386 | return BMI160.isMotionDetect(val); 387 | break; 388 | } 389 | } 390 | 391 | return false; 392 | } 393 | 394 | bool TrackerImu::isHighGDetect(uint32_t val) 395 | { 396 | CHECK_TRUE(isInitialized_, false); 397 | 398 | switch(imu_) 399 | { 400 | case BmiVariant::IMU_BMI160: 401 | { 402 | return BMI160.isHighGDetect(val); 403 | break; 404 | } 405 | } 406 | 407 | return false; 408 | } 409 | -------------------------------------------------------------------------------- /src/tracker_imu.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "Particle.h" 20 | 21 | namespace particle { 22 | 23 | enum class BmiVariant { 24 | IMU_BMI160, 25 | IMU_INVALID 26 | }; 27 | 28 | enum class BmiEventType { 29 | NONE, 30 | BREAK, 31 | SYNC, 32 | }; 33 | 34 | enum class BmiPowerState { 35 | PMU_SUSPEND, 36 | PMU_NORMAL, 37 | PMU_LOW_POWER, 38 | PMU_FAST_STARTUP, 39 | }; 40 | 41 | enum class BmiAccelMotionMode { 42 | ACCEL_MOTION_MODE_ANY, 43 | ACCEL_MOTION_MODE_SIGNIFICANT, 44 | }; 45 | 46 | enum class BmiAccelSignificantMotionSkip { 47 | SIG_MOTION_SKIP_1_5_S = 0, 48 | SIG_MOTION_SKIP_3_0_S = 1, 49 | SIG_MOTION_SKIP_6_0_S = 2, 50 | SIG_MOTION_SKIP_12_S = 3, 51 | }; 52 | 53 | enum class BmiAccelSignificantMotionProof { 54 | SIG_MOTION_PROOF_0_25_S = 0, 55 | SIG_MOTION_PROOF_0_5_S = 1, 56 | SIG_MOTION_PROOF_1_S = 2, 57 | SIG_MOTION_PROOF_2_S = 3, 58 | }; 59 | 60 | struct BmiAccelerometerConfig { 61 | float rate; 62 | float range; 63 | }; 64 | 65 | struct BmiAccelerometer { 66 | float x; 67 | float y; 68 | float z; 69 | }; 70 | 71 | struct BmiAccelHighGConfig { 72 | float threshold; 73 | float duration; 74 | float hysteresis; 75 | }; 76 | 77 | struct BmiAccelMotionConfig { 78 | BmiAccelMotionMode mode; 79 | float motionThreshold; 80 | unsigned motionDuration; 81 | BmiAccelSignificantMotionSkip motionSkip; 82 | BmiAccelSignificantMotionProof motionProof; 83 | }; 84 | 85 | 86 | /** 87 | * @brief Class used to abstract IMU. 88 | */ 89 | class TrackerImu { 90 | public: 91 | public: 92 | /** 93 | * @brief Return instance of the TrackerImu 94 | * 95 | * @return TrackerImu& 96 | */ 97 | static TrackerImu &instance() 98 | { 99 | if(!_instance) 100 | { 101 | _instance = new TrackerImu(); 102 | } 103 | return *_instance; 104 | } 105 | 106 | /** 107 | * @brief Get the IMU variant for this platform 108 | * 109 | * @return BmiVariant 110 | */ 111 | BmiVariant getImuType(void); 112 | 113 | // Common methods 114 | int begin(const TwoWire* interface, uint8_t address, pin_t interruptPin, size_t eventDepth = 8); 115 | int begin(const SPIClass& interface, pin_t selectPin, pin_t interruptPin, size_t eventDepth = 8); 116 | int end(); 117 | int reset(); 118 | int sleep(); 119 | int wakeup(); 120 | int syncEvent(BmiEventType event); 121 | int waitOnEvent(BmiEventType& event, system_tick_t timeout); 122 | 123 | int initAccelerometer(BmiAccelerometerConfig& config, bool feedback = false); 124 | int getAccelerometer(BmiAccelerometer& data); 125 | int getAccelerometerPmu(BmiPowerState& pmu); 126 | int initMotion(BmiAccelMotionConfig& config, bool feedback = false); 127 | int initHighG(BmiAccelHighGConfig& config, bool feedback = false); 128 | int startMotionDetect(); 129 | int stopMotionDetect(); 130 | int startHighGDetect(); 131 | int stopHighGDetect(); 132 | 133 | int getStatus(uint32_t& val, bool clear = false); 134 | bool isMotionDetect(uint32_t val); 135 | bool isHighGDetect(uint32_t val); 136 | 137 | private: 138 | TrackerImu(); 139 | static TrackerImu *_instance; 140 | bool isInitialized_ {false}; 141 | BmiVariant imu_ {BmiVariant::IMU_INVALID}; 142 | 143 | }; // class TrackerImu 144 | 145 | #define IMU TrackerImu::instance() 146 | 147 | } // namespace particle 148 | -------------------------------------------------------------------------------- /src/tracker_location.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "config_service.h" 20 | #include "cloud_service.h" 21 | #include "location_service.h" 22 | #include "motion_service.h" 23 | #include "tracker_sleep.h" 24 | #include "Geofence.h" 25 | 26 | #define TRACKER_LOCATION_INTERVAL_MIN_DEFAULT_SEC (900) 27 | #define TRACKER_LOCATION_INTERVAL_MAX_DEFAULT_SEC (3600) 28 | #define TRACKER_LOCATION_MIN_PUBLISH_DEFAULT (false) 29 | #define TRACKER_LOCATION_LOCK_TRIGGER (true) 30 | #define TRACKER_LOCATION_PROCESS_ACK (true) 31 | 32 | // wait at most this many seconds for a locked GPS location to become stable 33 | // before publishing regardless 34 | #define TRACKER_LOCATION_STABLE_WAIT_MAX (30) 35 | 36 | // wait at most this many seconds for initial lock on boot before publishing 37 | // regardless 38 | #define TRACKER_LOCATION_INITIAL_LOCK_MAX (90) 39 | 40 | constexpr int TrackerLocationMaxWpsCollect = 20; 41 | constexpr int TrackerLocationMaxWpsSend = 5; 42 | constexpr int TrackerLocationMaxTowerSend = 3; 43 | constexpr int NUM_OF_GEOFENCE_ZONES = 4; 44 | 45 | struct tracker_location_config_t { 46 | int32_t interval_min_seconds; // 0 = no min 47 | int32_t interval_max_seconds; // 0 = no max 48 | bool min_publish; 49 | bool lock_trigger; 50 | bool process_ack; 51 | bool tower; 52 | bool gnss; 53 | bool wps; 54 | bool enhance_loc; 55 | bool loc_cb; 56 | bool diag; 57 | }; 58 | 59 | enum class Trigger { 60 | NORMAL = 0, 61 | IMMEDIATE = 1, 62 | }; 63 | 64 | enum class GnssState { 65 | OFF, 66 | ERROR, 67 | ON_UNLOCKED, 68 | ON_LOCKED_UNSTABLE, 69 | ON_LOCKED_STABLE, 70 | DISABLED, 71 | }; 72 | 73 | enum class PublishReason { 74 | NONE, 75 | TIME, 76 | TRIGGERS, 77 | IMMEDIATE, 78 | }; 79 | 80 | struct EvaluationResults { 81 | PublishReason reason; 82 | bool networkNeeded; 83 | bool lockWait; 84 | }; 85 | 86 | struct TrackerGeofenceConfig { 87 | int32_t interval; // seconds 88 | }; 89 | 90 | class TrackerLocation 91 | { 92 | public: 93 | /** 94 | * @brief Return instance of the tracker location object 95 | * 96 | * @retval CloudService& 97 | */ 98 | static TrackerLocation &instance() 99 | { 100 | if(!_instance) 101 | { 102 | _instance = new TrackerLocation(); 103 | } 104 | return *_instance; 105 | } 106 | 107 | /** 108 | * @brief Initialize the TrackerLocation object 109 | * 110 | * @param gnssRetries GNSS initialization retry count 111 | */ 112 | void init(unsigned int gnssRetries); 113 | 114 | void loop(); 115 | 116 | // register for callback during generation of location publish allowing 117 | // for insertion of custom fields into the output 118 | // these callbacks are persistent and not removed on generation 119 | int regLocGenCallback( 120 | std::function, 121 | const void *context=nullptr); 122 | 123 | template 124 | int regLocGenCallback( 125 | void (T::*cb)(JSONWriter&, LocationPoint &, const void *), 126 | T *instance, 127 | const void *context=nullptr); 128 | 129 | // register for callback on location publish success/fail 130 | // these callbacks are NOT persistent and are used for the next publish 131 | int regLocPubCallback( 132 | std::function cb); 133 | 134 | template 135 | int regLocPubCallback( 136 | int (T::*cb)(CloudServiceStatus status, const String&), 137 | T *instance); 138 | 139 | int regPendLocPubCallback(std::function cb); 140 | 141 | template 142 | int regPendLocPubCallback( 143 | int (T::*cb)(CloudServiceStatus status, const String&), 144 | T *instance); 145 | 146 | // register for callback after location publish for the cloud supplied ehanced callback 147 | // these callbacks are persistent and not removed on generation 148 | int regEnhancedLocCallback( 149 | std::function, 150 | const void *context=nullptr); 151 | 152 | template 153 | int regEnhancedLocCallback( 154 | void (T::*cb)(const LocationPoint&, const void *), 155 | T *instance, 156 | const void *context=nullptr); 157 | 158 | int triggerLocPub(Trigger type = Trigger::NORMAL, const char *s = "user"); 159 | 160 | void lock() {mutex.lock();} 161 | void unlock() {mutex.unlock();} 162 | 163 | inline bool getMinPublish() { return _config_state.min_publish; } 164 | 165 | int addWap(WiFiAccessPoint* wap); 166 | 167 | Geofence& getGeoFence() { 168 | return _geofence; 169 | } 170 | bool isProcessAckEnabled() {return _config_state.process_ack;} 171 | int location_publish_cb(CloudServiceStatus status, String&& req_event, std::uint32_t last_publish_time); 172 | void issue_location_publish_callbacks(CloudServiceStatus status, const String &req_event); 173 | 174 | private: 175 | TrackerLocation() : 176 | _sleep(TrackerSleep::instance()), 177 | _geofence(NUM_OF_GEOFENCE_ZONES), 178 | _loopSampleTick(0), 179 | _pending_immediate(false), 180 | _first_publish(true), 181 | _pending_first_publish(false), 182 | _pendingShutdown(false), 183 | _earlyWake(0), 184 | _nextEarlyWake(0), 185 | _pendingGeofence(false), 186 | _lastInterval(0), 187 | _publishAttempted(0), 188 | _monotonic_publish_sec(0), 189 | _newMonotonic(true), 190 | _firstLockSec(0), 191 | _gnssStartedSec(0), 192 | _lastGnssState(GnssState::OFF), 193 | _gnssRetryDefault(0), 194 | _gnssCycleCurrent(0) { 195 | 196 | _config_state = { 197 | .interval_min_seconds = TRACKER_LOCATION_INTERVAL_MIN_DEFAULT_SEC, 198 | .interval_max_seconds = TRACKER_LOCATION_INTERVAL_MAX_DEFAULT_SEC, 199 | .min_publish = TRACKER_LOCATION_MIN_PUBLISH_DEFAULT, 200 | .lock_trigger = TRACKER_LOCATION_LOCK_TRIGGER, 201 | .process_ack = TRACKER_LOCATION_PROCESS_ACK, 202 | .tower = true, 203 | .gnss = true, 204 | .wps = true, 205 | .enhance_loc = true, 206 | .loc_cb = false, 207 | .diag = false, 208 | }; 209 | 210 | _config_state_loop_safe = _config_state; 211 | } 212 | static TrackerLocation *_instance; 213 | TrackerSleep& _sleep; 214 | Geofence _geofence; 215 | 216 | RecursiveMutex mutex; 217 | 218 | Vector _pending_triggers; 219 | system_tick_t _loopSampleTick; 220 | bool _pending_immediate; 221 | bool _first_publish; 222 | bool _pending_first_publish; 223 | bool _pendingShutdown; 224 | unsigned int _earlyWake; 225 | unsigned int _nextEarlyWake; 226 | TrackerGeofenceConfig _geofenceConfig {}; 227 | bool _pendingGeofence; 228 | 229 | int enter_location_config_cb(bool write, const void *context); 230 | int exit_location_config_cb(bool write, int status, const void *context); 231 | 232 | int get_loc_cb(JSONValue *root); 233 | 234 | void location_publish(); 235 | 236 | bool isSleepEnabled(); 237 | void enableNetwork(); 238 | int enableGnss(); 239 | int disableGnss(); 240 | void onSleepPrepare(TrackerSleepContext context); 241 | void onSleep(TrackerSleepContext context); 242 | void onSleepCancel(TrackerSleepContext context); 243 | void onWake(TrackerSleepContext context); 244 | void onSleepState(TrackerSleepContext context); 245 | void onGeofenceCallback(CallbackContext& context); 246 | EvaluationResults evaluatePublish(bool error); 247 | void buildPublish(LocationPoint& cur_loc, bool error = false); 248 | GnssState loopLocation(LocationPoint& cur_loc); 249 | size_t buildTowerInfo(JSONBufferWriter& writer, size_t size); 250 | static void wifi_cb(WiFiAccessPoint* wap, TrackerLocation* context); 251 | size_t buildWpsInfo(JSONBufferWriter& writer, size_t size); 252 | 253 | int buildEnhLocation(JSONValue& node, LocationPoint& point); 254 | int enhanced_cb(JSONValue* root); 255 | 256 | unsigned int setGnssCycle() { 257 | return _gnssCycleCurrent = _gnssRetryDefault + 1; // Initial attempt plus retries 258 | } 259 | 260 | unsigned int getGnssCycle() const { 261 | return _gnssCycleCurrent; 262 | } 263 | 264 | unsigned int decGnssCycle() { 265 | if (0 != _gnssCycleCurrent) { 266 | _gnssCycleCurrent--; 267 | } 268 | return _gnssCycleCurrent; 269 | } 270 | 271 | uint32_t _last_location_publish_sec; 272 | int32_t _lastInterval; 273 | std::atomic _publishAttempted; 274 | uint32_t _monotonic_publish_sec; 275 | bool _newMonotonic; 276 | uint32_t _firstLockSec; 277 | uint32_t _gnssStartedSec; 278 | GnssState _lastGnssState; 279 | unsigned int _gnssRetryDefault; 280 | unsigned int _gnssCycleCurrent; 281 | 282 | tracker_location_config_t _config_state, _config_state_shadow, _config_state_loop_safe; 283 | 284 | Vector> locGenCallbacks; 285 | // publish callback for the next publish (not in flight) 286 | Vector> locPubCallbacks; 287 | // publish callbacks for the current/pending publish (in flight) 288 | Vector> pendingLocPubCallbacks; 289 | // publish callbacks for the enhanced location callback 290 | Vector> enhancedLocCallbacks; 291 | os_queue_t _enhancedLocQueue; 292 | 293 | Vector wpsList; 294 | }; 295 | 296 | template 297 | int TrackerLocation::regLocGenCallback( 298 | void (T::*cb)(JSONWriter&, LocationPoint &, const void *), 299 | T *instance, 300 | const void *context) 301 | { 302 | return regLocGenCallback(std::bind(cb, instance, _1, _2), context); 303 | } 304 | 305 | template 306 | int TrackerLocation::regLocPubCallback( 307 | int (T::*cb)(CloudServiceStatus status, const String &), 308 | T *instance) 309 | { 310 | return regLocPubCallback(std::bind(cb, instance, _1, _2)); 311 | } 312 | 313 | template 314 | int TrackerLocation::regPendLocPubCallback( 315 | int (T::*cb)(CloudServiceStatus status, const String &), 316 | T *instance) 317 | { 318 | return regPendLocPubCallback(std::bind(cb, instance, _1, _2)); 319 | } 320 | 321 | template 322 | int TrackerLocation::regEnhancedLocCallback( 323 | void (T::*cb)(const LocationPoint&, const void*), 324 | T* instance, 325 | const void* context) 326 | { 327 | return regEnhancedLocCallback(std::bind(cb, instance, _1), context); 328 | } 329 | -------------------------------------------------------------------------------- /src/tracker_motion.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #include "tracker_motion.h" 18 | #include "tracker_location.h" 19 | #include "tracker_sleep.h" 20 | 21 | #include "config_service.h" 22 | #include "motion_service.h" 23 | 24 | TrackerMotion *TrackerMotion::_instance = nullptr; 25 | 26 | static int get_motion_enabled_cb(int32_t &value, const void *context) 27 | { 28 | value = (int32_t) static_cast((void *)context)->getMotionDetection(); 29 | return 0; 30 | } 31 | 32 | static int set_motion_enabled_cb(int32_t value, const void *context) 33 | { 34 | MotionService *motion_service = static_cast((void *)context); 35 | 36 | motion_service->enableMotionDetection((MotionDetectionMode) value); 37 | if ((MotionDetectionMode)value == MotionDetectionMode::NONE) { 38 | if (!motion_service->isAnyAwake()) { 39 | TrackerSleep::instance().ignore((pin_t)BMI_INT_PIN); 40 | } 41 | } 42 | else { 43 | TrackerSleep::instance().wakeFor((pin_t)BMI_INT_PIN, BMI_INT_MODE); 44 | } 45 | return 0; 46 | } 47 | 48 | static int get_high_g_enabled_cb(int32_t &value, const void *context) 49 | { 50 | value = (int32_t) static_cast((void *)context)->getHighGDetection(); 51 | return 0; 52 | } 53 | 54 | static int set_high_g_enabled_cb(int32_t value, const void *context) 55 | { 56 | MotionService *motion_service = static_cast((void *)context); 57 | 58 | if(value == (int32_t) HighGDetectionMode::DISABLE) 59 | { 60 | motion_service->disableHighGDetection(); 61 | if (!motion_service->isAnyAwake()) { 62 | TrackerSleep::instance().ignore((pin_t)BMI_INT_PIN); 63 | } 64 | } 65 | else if(value == (int32_t) HighGDetectionMode::ENABLE) 66 | { 67 | motion_service->enableHighGDetection(); 68 | TrackerSleep::instance().wakeFor((pin_t)BMI_INT_PIN, BMI_INT_MODE); 69 | } 70 | else 71 | { 72 | return -EINVAL; 73 | } 74 | 75 | return 0; 76 | } 77 | 78 | void TrackerMotion::init() 79 | { 80 | static ConfigObject imu_desc 81 | ( 82 | "imu_trig", 83 | { 84 | ConfigStringEnum( 85 | "motion", 86 | { 87 | {"disable", (int32_t) MotionDetectionMode::NONE}, 88 | {"low", (int32_t) MotionDetectionMode::LOW_SENSITIVITY}, 89 | {"medium", (int32_t) MotionDetectionMode::MEDIUM_SENSITIVITY}, 90 | {"high", (int32_t) MotionDetectionMode::HIGH_SENSITIVITY}, 91 | }, 92 | get_motion_enabled_cb, 93 | set_motion_enabled_cb, 94 | &MotionService::instance() 95 | ), 96 | ConfigStringEnum( 97 | "high_g", 98 | { 99 | {"disable", (int32_t) HighGDetectionMode::DISABLE}, 100 | {"enable", (int32_t) HighGDetectionMode::ENABLE}, 101 | }, 102 | get_high_g_enabled_cb, 103 | set_high_g_enabled_cb, 104 | &MotionService::instance() 105 | ), 106 | } 107 | ); 108 | 109 | ConfigService::instance().registerModule(imu_desc); 110 | } 111 | 112 | void TrackerMotion::loop() 113 | { 114 | MotionEvent motion_event; 115 | size_t depth = MotionService::instance().getQueueDepth(); 116 | 117 | do { 118 | MotionService::instance().waitOnEvent(motion_event, 0); 119 | switch (motion_event.source) 120 | { 121 | case MotionSource::MOTION_HIGH_G: 122 | TrackerLocation::instance().triggerLocPub(Trigger::NORMAL, "imu_g"); 123 | break; 124 | case MotionSource::MOTION_MOVEMENT: 125 | TrackerLocation::instance().triggerLocPub(Trigger::NORMAL,"imu_m"); 126 | break; 127 | } 128 | } while (--depth && (motion_event.source != MotionSource::MOTION_NONE)); 129 | } 130 | -------------------------------------------------------------------------------- /src/tracker_motion.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | class TrackerMotion 20 | { 21 | public: 22 | /** 23 | * @brief Return instance of the tracker motion object 24 | * 25 | * @retval CloudService& 26 | */ 27 | static TrackerMotion &instance() 28 | { 29 | if(!_instance) 30 | { 31 | _instance = new TrackerMotion(); 32 | } 33 | return *_instance; 34 | } 35 | 36 | void init(); 37 | void loop(); 38 | private: 39 | TrackerMotion() {} 40 | static TrackerMotion *_instance; 41 | }; -------------------------------------------------------------------------------- /src/tracker_rgb.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #include "Particle.h" 18 | 19 | #include "tracker_rgb.h" 20 | #include "tracker_cellular.h" 21 | 22 | #define RGB_CONTROL_TIMER_PERIOD_MS (250) 23 | #define RGB_CONTROL_FAST_FADE_PERIOD_MS (500) 24 | #define RGB_CONTROL_SLOW_FADE_PERIOD_MS (1000) 25 | 26 | // on a 0%-100% scale to mark transition betweeen merely OK to GOOD signal 27 | #define RGB_CONTROL_CELL_STRENGTH_GOOD (70) 28 | 29 | TrackerRGB *TrackerRGB::_instance = nullptr; 30 | static LEDStatus ledStatus(RGB_COLOR_RED, LED_PATTERN_SOLID, LED_SPEED_NORMAL, LED_PRIORITY_CRITICAL); 31 | static Timer * rgb_control_timer; 32 | 33 | static struct { 34 | RGBControlType type; 35 | struct { 36 | int32_t brightness; 37 | int32_t red; 38 | int32_t green; 39 | int32_t blue; 40 | } direct; 41 | } rgb_config = { 42 | .type = RGBControlType::APP_PARTICLE, 43 | .direct = { 44 | .brightness = 255, 45 | .red = 0, 46 | .green = 0, 47 | .blue = 255, 48 | } 49 | }; 50 | 51 | // actual led control driven by a periodic timer 52 | static void rgb_control_timer_cb() 53 | { 54 | switch(rgb_config.type) 55 | { 56 | case RGBControlType::APP_DIRECT: 57 | { 58 | RGB.brightness(rgb_config.direct.brightness); 59 | RGB.color(rgb_config.direct.red, 60 | rgb_config.direct.green, 61 | rgb_config.direct.blue); 62 | break; 63 | } 64 | case RGBControlType::APP_TRACKER: // fall-thru 65 | case RGBControlType::APP_GRADIENT: 66 | { 67 | CellularSignal signal; 68 | 69 | if(TrackerCellular::instance().getSignal(signal)) 70 | { 71 | // not connected to cell 72 | // error or not recent enough signal (which is also error) 73 | ledStatus.setPattern(LED_PATTERN_FADE); 74 | ledStatus.setPeriod(RGB_CONTROL_FAST_FADE_PERIOD_MS); 75 | ledStatus.setColor(RGB_COLOR_RED); 76 | } 77 | else 78 | { 79 | // connected to cell (recent enough signal) 80 | ledStatus.setPattern(Particle.connected() ? LED_PATTERN_SOLID : LED_PATTERN_FADE); 81 | ledStatus.setPeriod(RGB_CONTROL_SLOW_FADE_PERIOD_MS); 82 | 83 | if(signal.getStrength() < RGB_CONTROL_CELL_STRENGTH_GOOD) 84 | { 85 | ledStatus.setColor(RGB_COLOR_YELLOW); 86 | } 87 | else 88 | { 89 | ledStatus.setColor(RGB_COLOR_GREEN); 90 | } 91 | } 92 | } 93 | default: 94 | break; 95 | } 96 | } 97 | 98 | // get callback for config management 99 | static int rgb_control_get_type_cb(int32_t &value, const void *context) 100 | { 101 | value = (int32_t) TrackerRGB::getType(); 102 | return 0; 103 | } 104 | 105 | // set callback for config management 106 | static int rgb_control_set_type_cb(int32_t value, const void *context) 107 | { 108 | return TrackerRGB::setType((RGBControlType) value); 109 | } 110 | 111 | void TrackerRGB::init() 112 | { 113 | rgb_control_timer = new Timer(RGB_CONTROL_TIMER_PERIOD_MS, rgb_control_timer_cb); 114 | setType(rgb_config.type); 115 | 116 | static ConfigObject rgb_control_desc("rgb", { 117 | ConfigStringEnum( 118 | "type", 119 | { 120 | {"particle", (int32_t) RGBControlType::APP_PARTICLE}, 121 | {"off", (int32_t) RGBControlType::APP_OFF}, 122 | {"tracker", (int32_t) RGBControlType::APP_TRACKER}, 123 | {"direct", (int32_t) RGBControlType::APP_DIRECT}, 124 | {"gradient", (int32_t) RGBControlType::APP_GRADIENT}, 125 | }, 126 | rgb_control_get_type_cb, 127 | rgb_control_set_type_cb 128 | ), 129 | ConfigObject("direct", { 130 | ConfigInt("brightness", &rgb_config.direct.brightness, 0, 255), 131 | ConfigInt("red", &rgb_config.direct.red, 0, 255), 132 | ConfigInt("green", &rgb_config.direct.green, 0, 255), 133 | ConfigInt("blue", &rgb_config.direct.blue, 0, 255), 134 | }) 135 | }); 136 | ConfigService::instance().registerModule(rgb_control_desc); 137 | 138 | rgb_control_timer->start(); 139 | } 140 | 141 | int TrackerRGB::setType(RGBControlType type) 142 | { 143 | switch(type) 144 | { 145 | case RGBControlType::APP_PARTICLE: 146 | ledStatus.setActive(false); 147 | if(rgb_config.type != RGBControlType::APP_PARTICLE) 148 | { 149 | RGB.brightness(255); 150 | RGB.control(false); 151 | } 152 | break; 153 | case RGBControlType::APP_OFF: 154 | ledStatus.setActive(false); 155 | RGB.control(true); 156 | RGB.brightness(0); 157 | break; 158 | case RGBControlType::APP_TRACKER: // fall-thru 159 | case RGBControlType::APP_GRADIENT: // fall-thru 160 | case RGBControlType::APP_DIRECT: 161 | RGB.brightness(255); 162 | ledStatus.setActive(type != RGBControlType::APP_DIRECT); 163 | RGB.control(type == RGBControlType::APP_DIRECT); 164 | break; 165 | default: 166 | return -EINVAL; 167 | } 168 | rgb_config.type = type; 169 | 170 | return 0; 171 | } 172 | 173 | RGBControlType TrackerRGB::getType() 174 | { 175 | return rgb_config.type; 176 | } 177 | -------------------------------------------------------------------------------- /src/tracker_rgb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "config_service.h" 20 | 21 | enum class RGBControlType { 22 | APP_PARTICLE, // standard particle device-os control 23 | APP_OFF, // force LED off 24 | APP_TRACKER, // display cell/cloud connection and signal strength customized for tracker 25 | APP_GRADIENT, // with added gradient 26 | APP_DIRECT, // direct control of LED (rgb and brightness) 27 | }; 28 | 29 | class TrackerRGB 30 | { 31 | public: 32 | static TrackerRGB &instance() 33 | { 34 | if(!_instance) 35 | { 36 | _instance = new TrackerRGB(); 37 | } 38 | return *_instance; 39 | } 40 | 41 | void init(); 42 | static int setType(RGBControlType type); 43 | static RGBControlType getType(); 44 | private: 45 | TrackerRGB() {} 46 | static TrackerRGB *_instance; 47 | }; 48 | -------------------------------------------------------------------------------- /src/tracker_shipping.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #include "tracker_shipping.h" 18 | #include "tracker.h" 19 | 20 | #define SHIPPING_MODE_LED_CYCLE_PERIOD_MS (250) 21 | #define SHIPPING_MODE_LED_CYCLE_DURATION_MS (5000) 22 | #define SHIPPING_MODE_DEFER_DURATION_MS (5000) // 5 seconds 23 | #define SHIPPING_MODE_SAMPLE_MS (1000) // 1 second 24 | #define SHIPPING_MODE_TIMEOUT (60) // SHIPPING_MODE_SAMPLE_MS intervals 25 | 26 | TrackerShipping *TrackerShipping::_instance = nullptr; 27 | 28 | int TrackerShipping::regShutdownBeginCallback(ShippingModeCb begin) 29 | { 30 | _beginCallback = begin; 31 | 32 | return 0; 33 | } 34 | 35 | int TrackerShipping::regShutdownIoCallback(ShippingModeCb io) 36 | { 37 | _ioCallback = io; 38 | 39 | return 0; 40 | } 41 | 42 | int TrackerShipping::regShutdownFinalCallback(ShippingModeCb final) 43 | { 44 | _finalCallback = final; 45 | 46 | return 0; 47 | } 48 | 49 | void TrackerShipping::pmicHandler() 50 | { 51 | TrackerShipping::instance()._pmicFire = true; 52 | } 53 | 54 | void TrackerShipping::shutdown() 55 | { 56 | if(_ioCallback) 57 | { 58 | (void)_ioCallback(); 59 | } 60 | 61 | // blink RGB to signal entering shipping mode 62 | RGB.control(true); 63 | RGB.brightness(255); 64 | for(int i=0; 65 | i < (SHIPPING_MODE_LED_CYCLE_DURATION_MS / SHIPPING_MODE_LED_CYCLE_PERIOD_MS); 66 | i++) 67 | { 68 | // cycle between primary colors 69 | RGB.color(((uint32_t) 0xFF) << ((i % 3) * 8)); 70 | HAL_Delay_Milliseconds(SHIPPING_MODE_LED_CYCLE_PERIOD_MS); 71 | } 72 | 73 | auto shipping = &TrackerShipping::instance(); 74 | if (shipping->_checkPower) 75 | { 76 | // Attach and own the PMIC interrupt in order to provide the quickest 77 | // way to figure out changes in PMIC input power right before going into 78 | // shipping mode. 79 | attachInterrupt(PMIC_INT, &TrackerShipping::pmicHandler, FALLING); 80 | } 81 | 82 | // The PMIC will be locked from this point forward with no further changes allowed 83 | PMIC pmic(true); 84 | 85 | // Disable charging will ensure there are no asynchronous events that may interrupt 86 | // entering of shipping mode 87 | pmic.disableCharging(); 88 | 89 | // Clear all faults 90 | (void)pmic.getFault(); 91 | (void)pmic.getFault(); 92 | 93 | pmic.disableWatchdog(); 94 | if (shipping->_checkPower && shipping->_pmicFire) 95 | { 96 | // If the PMIC interrupted us then reset instead of going into shipping mode because 97 | // the power is likely to be applied between when the mode was commanded and the delayed 98 | // response of this particular handler. 99 | System.reset(); 100 | } 101 | 102 | pmic.disableBATFET(); 103 | 104 | // Wait for the PMIC to exit DPDM detection before shutting down 105 | int timeout = SHIPPING_MODE_TIMEOUT; 106 | while (pmic.isInDPDM() && (timeout-- > 0)) 107 | { 108 | delay(SHIPPING_MODE_SAMPLE_MS); 109 | } 110 | 111 | // Clear all faults 112 | (void)pmic.getFault(); 113 | (void)pmic.getFault(); 114 | 115 | RGB.brightness(0); 116 | 117 | if(_finalCallback) 118 | { 119 | (void)_finalCallback(); 120 | } 121 | 122 | // Enter sleep with lower level sleep API so power management doesn't override PMIC settings 123 | SystemSleepConfiguration config; 124 | config.mode(SystemSleepMode::HIBERNATE) 125 | .gpio(PMIC_INT, FALLING); 126 | hal_sleep_enter(config.halConfig(), nullptr, nullptr); 127 | 128 | // shouldn't hit these lines as never coming back from sleep but out of an 129 | // abundance of paranoia force a reset so we don't get stuck in some weird 130 | // pseudo-shutdown state 131 | System.reset(); 132 | } 133 | 134 | int TrackerShipping::enter(bool checkPower) 135 | { 136 | if(_beginCallback) 137 | { 138 | int rval = _beginCallback(); 139 | 140 | if(rval) 141 | { 142 | return rval; 143 | } 144 | } 145 | 146 | // This flag will allow the shipping mode code to check power state before shutting down 147 | _checkPower = checkPower; 148 | 149 | // Timer call will shutdown device so don't worry about dynamic memory 150 | auto deferredShutdown = new Timer(SHIPPING_MODE_DEFER_DURATION_MS, &TrackerShipping::shutdown, *this, true); 151 | deferredShutdown->start(); 152 | 153 | return 0; // compiler warnings about no return... 154 | } 155 | 156 | int TrackerShipping::enter_cb(JSONValue *root) 157 | { 158 | return enter(); 159 | } 160 | 161 | void TrackerShipping::init() 162 | { 163 | CloudService::instance().registerCommand("enter_shipping", std::bind(&TrackerShipping::enter_cb, this, std::placeholders::_1)); 164 | } 165 | -------------------------------------------------------------------------------- /src/tracker_shipping.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Particle Industries, Inc. 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 | * http://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 | #pragma once 18 | 19 | #include "cloud_service.h" 20 | 21 | using ShippingModeCb = std::function; 22 | 23 | class TrackerShipping 24 | { 25 | public: 26 | static TrackerShipping& instance() { 27 | if(!_instance) 28 | { 29 | _instance = new TrackerShipping(); 30 | } 31 | return *_instance; 32 | } 33 | 34 | void init(); 35 | 36 | int enter(bool checkPower = false); 37 | 38 | int regShutdownBeginCallback(ShippingModeCb begin); 39 | int regShutdownIoCallback(ShippingModeCb io); 40 | int regShutdownFinalCallback(ShippingModeCb final); 41 | private: 42 | TrackerShipping() : 43 | _beginCallback(nullptr), 44 | _ioCallback(nullptr), 45 | _finalCallback(nullptr), 46 | _checkPower(false), 47 | _pmicFire(false) {} 48 | static TrackerShipping* _instance; 49 | 50 | ShippingModeCb _beginCallback; 51 | ShippingModeCb _ioCallback; 52 | ShippingModeCb _finalCallback; 53 | bool _checkPower; 54 | bool _pmicFire; 55 | 56 | int enter_cb(JSONValue *root); 57 | void shutdown(); 58 | static void pmicHandler(); 59 | }; 60 | --------------------------------------------------------------------------------