Dawn Framework 1.0
Universal data acquisition framework for embedded systems
gnss.cxx
1// dawn/src/system/gnss.cxx
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5
6#include "dawn/system/gnss.hxx"
7
8#include <cerrno>
9#include <cstdio>
10#include <unistd.h>
11
12#include <nuttx/uorb.h>
13
14#include "dawn/debug.hxx"
15#include "dawn/porting/lte.hxx"
16#include "dawn/porting/sensors.hxx"
17
18using namespace dawn;
19
20// Fix-monitor poll period. The GNSS device is non-blocking, so the loop polls
21// for a fix and stays responsive to stop requests.
22
23#define GNSS_MONITOR_POLL_US (1000000)
24
25// Coexistence timing (seconds). LTE is the priority: the manager only ever
26// borrows the radio for a bounded window and always hands it back, so the
27// device stays usable on LTE even when GNSS never fixes (e.g. indoors).
28
29#define GNSS_SETTLE_S CONFIG_DAWN_SYSTEM_GNSS_SETTLE
30#define GNSS_ACQUIRE_S CONFIG_DAWN_SYSTEM_GNSS_ACQUIRE
31#define GNSS_RETRY_S CONFIG_DAWN_SYSTEM_GNSS_RETRY
32#define GNSS_MAXATTEMPTS CONFIG_DAWN_SYSTEM_GNSS_MAXATTEMPTS
33#define GNSS_REARM_S CONFIG_DAWN_SYSTEM_GNSS_REARM
34
35// Default runtime on/off state. GNSS starts disabled (LTE only) unless either
36// Kconfig or the descriptor turns it on; a server can flip it at runtime over
37// LwM2M (IPSO Actuation 3306/5850 -> the 'enabled' config item).
38
39#ifdef CONFIG_DAWN_SYSTEM_GNSS_ENABLED
40# define GNSS_ENABLED_DEFAULT (true)
41#else
42# define GNSS_ENABLED_DEFAULT (false)
43#endif
44
45CSystemGnss::CSystemGnss(CDescObject &desc)
46 : CSystemCommon(desc)
47 , mode(0)
48 , enabled(false)
49 , settle(GNSS_SETTLE_S)
50 , acquire(GNSS_ACQUIRE_S)
51 , retry(GNSS_RETRY_S)
52 , maxAttempts(GNSS_MAXATTEMPTS)
53 , rearm(GNSS_REARM_S)
54 , fd(-1)
55{
56}
57
58void CSystemGnss::loadParams()
59{
60 CDescObject &desc = getDesc();
61 size_t count = desc.getSize();
62 size_t offset = 0;
63 size_t i;
65
66 // Kconfig defaults
67
68 mode = CONFIG_DAWN_SYSTEM_GNSS_MODE;
69 enabled = GNSS_ENABLED_DEFAULT;
70 settle = GNSS_SETTLE_S;
71 acquire = GNSS_ACQUIRE_S;
72 retry = GNSS_RETRY_S;
73 maxAttempts = GNSS_MAXATTEMPTS;
74 rearm = GNSS_REARM_S;
75
76 // Override from descriptor config items
77
78 for (i = 0; i < count; i++)
79 {
80 item = desc.objectCfgItemNext(offset);
81 if (item == nullptr)
82 {
83 break;
84 }
85
86 if (item->cfgid.s.cls != SYSTEM_CLASS_GNSS)
87 {
88 continue;
89 }
90
91 switch (item->cfgid.s.id)
92 {
93 case GNSS_CFG_MODE:
94 mode = static_cast<uint8_t>(item->data[0] & 0xff);
95 break;
96
97 case GNSS_CFG_SETTLE:
98 settle = item->data[0];
99 break;
100
101 case GNSS_CFG_ACQUIRE:
102 acquire = item->data[0];
103 break;
104
105 case GNSS_CFG_RETRY:
106 retry = item->data[0];
107 break;
108
110 maxAttempts = item->data[0];
111 break;
112
113 case GNSS_CFG_REARM:
114 rearm = item->data[0];
115 break;
116
117 case GNSS_CFG_ENABLED:
118 enabled = (item->data[0] != 0);
119 break;
120
121 default:
122 break;
123 }
124 }
125}
126
128{
129 loadParams();
130 return OK;
131}
132
133void CSystemGnss::monitor()
134{
135 struct sensor_gnss gnss;
136 int n;
137
138 // Activate this subscription so the GNSS fix events are delivered to our
139 // read() (the device only pushes a sample on a valid fix).
140
141 sensor_set_interval(fd, GNSS_MONITOR_POLL_US);
142
143 // Wait up to 'timeout' seconds for LTE to be CONNECTED (true) or give up
144 // (false). Used both for the initial LTE-first deferral and to let LTE
145 // recover after each GNSS window before counting any cooldown.
146
147 auto waitLte = [this](uint32_t timeout)
148 {
149 uint32_t status;
150 for (uint32_t t = 0; t < timeout && !shouldQuit(); t++)
151 {
152 if (lte_port_status(&status) == OK && status == DAWN_LTE_STATUS_CONNECTED)
153 {
154 return true;
155 }
156
157 sleep(1);
158 }
159
160 return false;
161 };
162
163 // Interruptible LTE-reachable wait (no PSM, no GNSS priority).
164
165 auto idle = [this](uint32_t secs)
166 {
167 for (uint32_t t = 0; t < secs && !shouldQuit(); t++)
168 {
169 sleep(1);
170 }
171 };
172
173 // LTE first. Do not disturb the radio until LTE is connected and reachable;
174 // GNSS is deferred behind it. Indoors this is the whole point - the device
175 // stays usable on LTE even if a fix never comes.
176
177 waitLte(settle);
178
179 // Arming loop. Each arming makes up to 'maxAttempts' bounded acquisition
180 // windows (maxAttempts == 0 => unlimited, i.e. retry forever - the
181 // registration-safe default, which relies on the LwM2M lifetime exceeding
182 // acquire+retry). A cold fix needs the radio (LTE asleep in PSM + GNSS
183 // priority), so each window is time-boxed and ALWAYS followed by handing
184 // the radio back to LTE and waiting for it to recover - LTE availability is
185 // never sacrificed to GNSS. If a round gives up without a fix, the manager
186 // parks fully on LTE for 'rearm' seconds and then arms again. A fix ends the
187 // policy entirely (the modem keeps tracking warm).
188
189 bool fixed = false;
190
191 while (!shouldQuit() && !fixed)
192 {
193 uint32_t attempt = 0;
194
195 while (!shouldQuit() && (maxAttempts == 0 || attempt < maxAttempts))
196 {
197 attempt++;
198
199 // Bounded acquisition window: LTE sleeps, GNSS owns the radio.
200
201 lte_port_set_psave(DAWN_LTE_PSAVE_PSM);
202 sensor_gnss_set_priority(fd, true);
203 DAWNINFO("GNSS manager: acquiring (bounded %lus, attempt %lu)\n",
204 (unsigned long)acquire,
205 (unsigned long)attempt);
206
207 for (uint32_t t = 0; t < acquire && !shouldQuit(); t++)
208 {
209 n = sensor_read(fd, &gnss, sizeof(gnss));
210 if (n > 0)
211 {
212 fixed = true;
213 break;
214 }
215
216 sleep(1);
217 }
218
219 // Always yield the radio back to LTE, then wait for LTE to recover
220 // before counting any cooldown - this keeps the registration alive.
221
222 sensor_gnss_set_priority(fd, false);
223 lte_port_set_psave(DAWN_LTE_PSAVE_NONE);
224 waitLte(settle);
225
226 if (fixed)
227 {
228 DAWNINFO("GNSS manager: fix acquired (lat=%f); LTE restored\n",
229 static_cast<double>(gnss.latitude));
230 break;
231 }
232
233 // Cool down (LTE reachable) before the next window, unless this was
234 // the last allowed attempt (then 'rearm' below takes over).
235
236 if (maxAttempts == 0 || attempt < maxAttempts)
237 {
238 DAWNINFO("GNSS manager: no fix in %lus; LTE reachable, "
239 "retry in %lus\n",
240 (unsigned long)acquire,
241 (unsigned long)retry);
242 idle(retry);
243 }
244 }
245
246 if (fixed || shouldQuit())
247 {
248 break;
249 }
250
251 // Gave up after maxAttempts windows: park on LTE and re-arm later.
252
253 DAWNINFO("GNSS manager: gave up after %lu attempts; LTE only, "
254 "re-arm in %lus\n",
255 (unsigned long)attempt,
256 (unsigned long)rearm);
257 idle(rearm);
258 }
259
260 // Leave the radio reachable on stop.
261
262 sensor_gnss_set_priority(fd, false);
263 lte_port_set_psave(DAWN_LTE_PSAVE_NONE);
264}
265
266int CSystemGnss::startMonitor()
267{
268 char path[32];
269
270 if (isRunning())
271 {
272 return OK;
273 }
274
275 std::snprintf(path, sizeof(path), "/dev/uorb/sensor_gnss0");
276
277 fd = sensor_open(path);
278 if (fd < 0)
279 {
280 DAWNERR("GNSS manager: open %s failed %d\n", path, fd);
281 return fd;
282 }
283
284 DAWNINFO("GNSS manager: enabled, starting acquisition\n");
285 return startWorkerThread([this]() { monitor(); });
286}
287
288void CSystemGnss::stopMonitor()
289{
290 if (isRunning())
291 {
293 }
294
295 if (fd >= 0)
296 {
297 sensor_close(fd);
298 fd = -1;
299 }
300}
301
303{
304 loadParams();
305
306 // mode OFF disables the policy entirely (the manager is inert and cannot be
307 // turned on at runtime). Otherwise honour the runtime on/off switch: GNSS is
308 // deferred until 'enabled' (default off => LTE only), which a server can flip
309 // live over LwM2M.
310
311 if (mode == DAWN_GNSS_MODE_OFF)
312 {
313 DAWNINFO("GNSS manager: off (mode)\n");
314 return OK;
315 }
316
317 if (!enabled)
318 {
319 DAWNINFO("GNSS manager: idle (disabled); enable over LwM2M to start\n");
320 return OK;
321 }
322
323 return startMonitor();
324}
325
327{
328 stopMonitor();
329 return OK;
330}
331
332int CSystemGnss::onSetObjConfig(SObjectCfg::ObjectCfgId objcfg, uint32_t *data, size_t len)
333{
334 // Runtime control hook: invoked on every config write before the value is
335 // stored. The 'enabled' switch (IPSO Actuation 3306/5850 over LwM2M) starts
336 // or stops the monitor live; mode OFF still wins. Other params take effect on
337 // the next start.
338
340 SObjectCfg::objectCfgGetId(objcfg) != GNSS_CFG_ENABLED || len < 1)
341 {
342 return OK;
343 }
344
345 enabled = (data[0] != 0);
346
347 if (enabled && mode != DAWN_GNSS_MODE_OFF)
348 {
349 return startMonitor();
350 }
351
352 DAWNINFO("GNSS manager: disabled over LwM2M; LTE only\n");
353 stopMonitor();
354 return OK;
355}
Descriptor wrapper for individual object configuration.
size_t getSize() const
Get number of configuration items for this object.
SObjectCfg::SObjectCfgItem * objectCfgItemNext(size_t &offset) const
Get config item at current offset and advance past it.
CDescObject & getDesc()
Get descriptor object for this object.
Definition object.cxx:190
Base class for OBJTYPE_ANY configuration objects.
Definition common.hxx:25
@ SYSTEM_CLASS_GNSS
GNSS coexistence manager (policy)
Definition common.hxx:31
int doStart()
Start implementation hook.
Definition gnss.cxx:302
@ GNSS_CFG_MODE
DAWN_GNSS_MODE_* (DTYPE_UINT8)
Definition gnss.hxx:46
@ GNSS_CFG_ENABLED
Runtime on/off switch (DTYPE_BOOL)
Definition gnss.hxx:52
@ GNSS_CFG_ACQUIRE
Bounded fix window, seconds (DTYPE_UINT32)
Definition gnss.hxx:48
@ GNSS_CFG_REARM
Re-arm delay after give-up, s (DTYPE_UINT32)
Definition gnss.hxx:51
@ GNSS_CFG_MAXATTEMPTS
No-fix windows before give-up (DTYPE_UINT32)
Definition gnss.hxx:50
@ GNSS_CFG_SETTLE
LTE-first grace, seconds (DTYPE_UINT32)
Definition gnss.hxx:47
@ GNSS_CFG_RETRY
Cooldown between attempts, s (DTYPE_UINT32)
Definition gnss.hxx:49
int configure()
Configure object from descriptor data.
Definition gnss.cxx:127
int onSetObjConfig(SObjectCfg::ObjectCfgId objcfg, uint32_t *data, size_t len)
React to runtime config writes (e.g.
Definition gnss.cxx:332
int doStop()
Stop implementation hook.
Definition gnss.cxx:326
bool isRunning() const
Check if the worker thread is running.
Definition thread.cxx:262
int stopWorkerThread()
Stop the worker thread.
Definition thread.hxx:258
int startWorkerThread(Func &&func)
Start the worker thread with a given function.
Definition thread.hxx:246
uint32_t ObjectCfgId
ConfigID type - single 32-bit value.
Definition objectcfg.hxx:60
static uint16_t objectCfgGetCls(const ObjectCfgId objcfg)
Extract object class from ConfigID.
static uint8_t objectCfgGetId(const ObjectCfgId objcfg)
Extract configuration identifier from ConfigID.
Out-of-tree user-extension hooks for Dawn.
Definition bindable.hxx:13
@ DAWN_GNSS_MODE_OFF
Manager idle; do not touch the radio knobs.
Definition gnss.hxx:19
@ DAWN_LTE_PSAVE_NONE
PSM and eDRX disabled (always reachable)
Definition lte.hxx:40
@ DAWN_LTE_PSAVE_PSM
Power Saving Mode.
Definition lte.hxx:41
@ DAWN_LTE_STATUS_CONNECTED
Connected (PDN active, IP assigned)
Definition lte.hxx:52
Single configuration item within object.
ObjectCfgData_t data[]
Configuration data array (flexible, size from cfgid.s.size).
UObjectCfgId cfgid
Configuration ID header (type, class, id, size, rw, dtype).
uint32_t cls
Object class (bits 21-29, max 511).
uint32_t id
Configuration identifier (bits 0-4, max 31).
Definition objectcfg.hxx:94
struct dawn::SObjectCfg::UObjectCfgId::@10 s
Bit-field structure for named member access.