Skip to content

Commit a6dd7d8

Browse files
committed
Low power detection sensor module for nRF52.
Using low power polling during intervaled wake ups (state_broadcast_secs set) or Nordics GPIO sense feature in shutdown mode if state_broadcast_secs unset (=0). Introduces GPREGRET2 to distinguish between wakeups by timeout or event.
1 parent 0081cec commit a6dd7d8

File tree

3 files changed

+104
-5
lines changed

3 files changed

+104
-5
lines changed

src/modules/DetectionSensorModule.cpp

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
#include "configuration.h"
77
#include "main.h"
88
#include <Throttle.h>
9+
#ifdef ARCH_NRF52
10+
#include "sleep.h"
11+
#endif
12+
913
DetectionSensorModule *detectionSensorModule;
1014

1115
#define GPIO_POLLING_INTERVAL 100
@@ -73,17 +77,65 @@ int32_t DetectionSensorModule::runOnce()
7377

7478
// This is the first time the OSThread library has called this function, so do some setup
7579
firstTime = false;
80+
7681
if (moduleConfig.detection_sensor.monitor_pin > 0) {
77-
pinMode(moduleConfig.detection_sensor.monitor_pin, moduleConfig.detection_sensor.use_pullup ? INPUT_PULLUP : INPUT);
82+
83+
#ifdef ARCH_NRF52
84+
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) {
85+
nrf_gpio_pin_sense_t toSense =
86+
(moduleConfig.detection_sensor.detection_trigger_type & 1) ? NRF_GPIO_PIN_SENSE_HIGH : NRF_GPIO_PIN_SENSE_LOW;
87+
nrf_gpio_cfg_input(moduleConfig.detection_sensor.monitor_pin,
88+
moduleConfig.detection_sensor.use_pullup ? NRF_GPIO_PIN_PULLUP : NRF_GPIO_PIN_NOPULL);
89+
nrf_gpio_cfg_sense_set(moduleConfig.detection_sensor.monitor_pin, toSense);
90+
91+
if (NRF_P0->LATCH || NRF_P1->LATCH) {
92+
LOG_INFO("Woke up from eternal sleep by GPIO. Sending message.",
93+
__builtin_ctz(NRF_P0->LATCH ? NRF_P0->LATCH : NRF_P1->LATCH), NRF_P0->LATCH ? 0 : 1);
94+
NRF_P1->LATCH = 0xFFFFFFFF;
95+
NRF_P0->LATCH = 0xFFFFFFFF;
96+
sendDetectionMessage();
97+
} else if (NRF_POWER->GPREGRET2 == 255) {
98+
LOG_INFO("Woke up from timeout. Sending state message.");
99+
sendCurrentStateMessage(hasDetectionEvent());
100+
} else if (NRF_POWER->GPREGRET2) {
101+
LOG_INFO("Woke up from sleep by GPIO. Sending detection message.");
102+
sendDetectionMessage();
103+
} else {
104+
// We booted fresh. Enforce sending on first detection event.
105+
lastSentToMesh = -Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs);
106+
}
107+
NRF_POWER->GPREGRET2 = 0;
108+
109+
LOG_INFO("Detection Sensor Module: init in power saving mode");
110+
// Choose the minimum wakeup time (config.power.min_wake_sec) depending to your needs.
111+
// The least time should be 5s to send just the detection message.
112+
// Choose 45s+ for comfortable connectivity via BLE or USB.
113+
// Device Telemetry is sent after ~62s.
114+
return Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, 90);
115+
} else
116+
#endif
117+
pinMode(moduleConfig.detection_sensor.monitor_pin,
118+
moduleConfig.detection_sensor.use_pullup ? INPUT_PULLUP : INPUT);
78119
} else {
79120
LOG_WARN("Detection Sensor Module: Set to enabled but no monitor pin is set. Disable module");
80121
return disable();
81122
}
82-
LOG_INFO("Detection Sensor Module: init");
123+
LOG_INFO("Detection Sensor Module: init in default mode");
83124

84125
return setStartDelay();
85126
}
86127

128+
#ifdef ARCH_NRF52
129+
if ((config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving)) {
130+
// if a 'State Broadcast Interval' (moduleConfig.detection_sensor.state_broadcast_secs) is specified it will be used, if
131+
// unset the sleep will be forever in the first case the module enters a low power mode, in the 2nd case it will shutdown
132+
// with least power consumption possible
133+
uint32_t nightyNightMs =
134+
Default::getConfiguredOrDefault(moduleConfig.detection_sensor.state_broadcast_secs * 1000, DELAY_FOREVER);
135+
doDeepSleep(nightyNightMs, false, true);
136+
}
137+
#endif
138+
87139
// LOG_DEBUG("Detection Sensor Module: Current pin state: %i", digitalRead(moduleConfig.detection_sensor.monitor_pin));
88140

89141
if (!Throttle::isWithinTimespanMs(lastSentToMesh,
@@ -124,6 +176,9 @@ void DetectionSensorModule::sendDetectionMessage()
124176
meshtastic_MeshPacket *p = allocDataPacket();
125177
p->want_ack = false;
126178
p->decoded.payload.size = strlen(message);
179+
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
180+
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
181+
127182
memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size);
128183
if (moduleConfig.detection_sensor.send_bell && p->decoded.payload.size < meshtastic_Constants_DATA_PAYLOAD_LEN) {
129184
p->decoded.payload.bytes[p->decoded.payload.size] = 7; // Bell character
@@ -146,6 +201,9 @@ void DetectionSensorModule::sendCurrentStateMessage(bool state)
146201
meshtastic_MeshPacket *p = allocDataPacket();
147202
p->want_ack = false;
148203
p->decoded.payload.size = strlen(message);
204+
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
205+
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
206+
149207
memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size);
150208
lastSentToMesh = millis();
151209
if (!channels.isDefaultChannel(0)) {
@@ -156,9 +214,36 @@ void DetectionSensorModule::sendCurrentStateMessage(bool state)
156214
delete[] message;
157215
}
158216

217+
boolean DetectionSensorModule::getState()
218+
{
219+
return digitalRead(moduleConfig.detection_sensor.monitor_pin);
220+
}
221+
159222
bool DetectionSensorModule::hasDetectionEvent()
160223
{
161-
bool currentState = digitalRead(moduleConfig.detection_sensor.monitor_pin);
224+
bool currentState = getState();
162225
// LOG_DEBUG("Detection Sensor Module: Current state: %i", currentState);
163226
return (moduleConfig.detection_sensor.detection_trigger_type & 1) ? currentState : !currentState;
164-
}
227+
}
228+
229+
boolean DetectionSensorModule::shouldSleep()
230+
{
231+
return moduleConfig.detection_sensor.enabled && config.power.is_power_saving && moduleConfig.detection_sensor.monitor_pin > 0;
232+
}
233+
234+
#ifdef ARCH_NRF52
235+
void DetectionSensorModule::lpLoop(uint32_t msecToWake)
236+
{
237+
for (uint32_t i = msecToWake / 100; i >= 0; i--) {
238+
delay(100);
239+
if (hasDetectionEvent() &&
240+
!Throttle::isWithinTimespanMs(
241+
lastSentToMesh, Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs))) {
242+
NRF_POWER->GPREGRET2 = 1; // use 1 for detection
243+
break;
244+
}
245+
if (i == 0)
246+
NRF_POWER->GPREGRET2 = 255; // use 255 for timeout without detection
247+
}
248+
}
249+
#endif

src/modules/DetectionSensorModule.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ class DetectionSensorModule : public SinglePortModule, private concurrency::OSTh
77
DetectionSensorModule() : SinglePortModule("detection", meshtastic_PortNum_DETECTION_SENSOR_APP), OSThread("DetectionSensor")
88
{
99
}
10+
boolean shouldSleep();
11+
void lpLoop(uint32_t msecToWake);
1012

1113
protected:
1214
virtual int32_t runOnce() override;
@@ -18,6 +20,7 @@ class DetectionSensorModule : public SinglePortModule, private concurrency::OSTh
1820
void sendDetectionMessage();
1921
void sendCurrentStateMessage(bool state);
2022
bool hasDetectionEvent();
23+
boolean getState();
2124
};
2225

2326
extern DetectionSensorModule *detectionSensorModule;

src/platform/nrf52/main-nrf52.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
#include "BQ25713.h"
3131
#endif
3232

33+
#ifndef DMESHTASTIC_EXCLUDE_DETECTIONSENSOR
34+
#include "modules/DetectionSensorModule.h"
35+
#endif
36+
3337
// Weak empty variant initialization function.
3438
// May be redefined by variant files.
3539
void variant_shutdown() __attribute__((weak));
@@ -405,8 +409,15 @@ void cpuDeepSleep(uint32_t msecToWake)
405409
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER, meshtastic_Config_DeviceConfig_Role_SENSOR) &&
406410
config.power.is_power_saving == true)) {
407411
sd_power_mode_set(NRF_POWER_MODE_LOWPWR);
408-
delay(msecToWake);
412+
#ifndef DMESHTASTIC_EXCLUDE_DETECTIONSENSOR
413+
if (detectionSensorModule != nullptr && detectionSensorModule->shouldSleep()) {
414+
detectionSensorModule->lpLoop(msecToWake);
415+
} else
416+
#endif
417+
delay(msecToWake);
418+
409419
NVIC_SystemReset();
420+
410421
} else {
411422
// Resume on user button press
412423
// https://github.com/lyusupov/SoftRF/blob/81c519ca75693b696752235d559e881f2e0511ee/software/firmware/source/SoftRF/src/platform/nRF52.cpp#L1738

0 commit comments

Comments
 (0)