├── .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 |
5 |
--------------------------------------------------------------------------------
/assets/tracker-edge.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------