Skip to content

Commit de3e4bc

Browse files
author
Scott Powell
committed
* added REQ_TYPE_GET_AVG_MIN_MAX
* TimeSeriesData * very basic SensorMesh::sendAlert()
1 parent 810b1f8 commit de3e4bc

File tree

3 files changed

+135
-10
lines changed

3 files changed

+135
-10
lines changed

examples/simple_sensor/SensorMesh.cpp

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#define REQ_TYPE_GET_STATUS 0x01
5151
#define REQ_TYPE_KEEP_ALIVE 0x02
5252
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
53+
#define REQ_TYPE_GET_AVG_MIN_MAX 0x04
5354

5455
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
5556

@@ -154,6 +155,22 @@ uint8_t SensorMesh::handleRequest(bool is_admin, uint32_t sender_timestamp, uint
154155
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
155156
return 4 + tlen; // reply_len
156157
}
158+
case REQ_TYPE_GET_AVG_MIN_MAX: {
159+
uint32_t start_secs_ago, end_secs_ago;
160+
memcpy(&start_secs_ago, &payload[0], 4);
161+
memcpy(&end_secs_ago, &payload[4], 4);
162+
uint8_t res1 = payload[8]; // reserved for future (extra query params)
163+
uint8_t res2 = payload[8];
164+
165+
MinMaxAvg data[8];
166+
int n;
167+
if (res1 == 0 && res2 == 0) {
168+
n = querySeriesData(start_secs_ago, end_secs_ago, data, 8);
169+
} else {
170+
n = 0;
171+
}
172+
return 0; // TODO: encode data[0..n)
173+
}
157174
}
158175
return 0; // unknown command
159176
}
@@ -192,6 +209,34 @@ ContactInfo* SensorMesh::putContact(const mesh::Identity& id) {
192209
return c;
193210
}
194211

212+
void SensorMesh::sendAlert(const char* text) {
213+
int text_len = strlen(text);
214+
215+
// send text message to all admins
216+
for (int i = 0; i < num_contacts; i++) {
217+
auto c = &contacts[i];
218+
if (!c->isAdmin()) continue;
219+
220+
uint8_t data[MAX_PACKET_PAYLOAD];
221+
uint32_t now = getRTCClock()->getCurrentTimeUnique(); // need different timestamp per packet
222+
memcpy(data, &now, 4);
223+
data[4] = (TXT_TYPE_PLAIN << 2); // attempt and flags
224+
memcpy(&data[5], text, text_len);
225+
// calc expected ACK reply
226+
// uint32_t expected_ack;
227+
// mesh::Utils::sha256((uint8_t *)&expected_ack, 4, data, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE);
228+
229+
auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, c->id, c->shared_secret, data, 5 + text_len);
230+
if (pkt) {
231+
if (c->out_path_len >= 0) { // we have an out_path, so send DIRECT
232+
sendDirect(pkt, c->out_path, c->out_path_len);
233+
} else {
234+
sendFlood(pkt);
235+
}
236+
}
237+
}
238+
}
239+
195240
void SensorMesh::alertIfLow(Trigger& t, float value, float threshold, const char* text) {
196241
if (value < threshold) {
197242
if (!t.triggered) {
@@ -222,6 +267,50 @@ void SensorMesh::alertIfHigh(Trigger& t, float value, float threshold, const cha
222267
}
223268
}
224269

270+
void SensorMesh::recordData(TimeSeriesData& data, float value) {
271+
uint32_t now = getRTCClock()->getCurrentTime();
272+
if (now >= data.last_timestamp + data.interval_secs) {
273+
data.last_timestamp = now;
274+
275+
data.data[data.next] = value; // append to cycle table
276+
data.next = (data.next + 1) % data.num_slots;
277+
}
278+
}
279+
280+
void SensorMesh::calcDataMinMaxAvg(const TimeSeriesData& data, uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg* dest, uint8_t channel, uint8_t lpp_type) {
281+
int i = data.next, n = data.num_slots;
282+
uint32_t ago = data.interval_secs * data.num_slots;
283+
int num_values = 0;
284+
float total = 0.0f;
285+
286+
dest->_channel = channel;
287+
dest->_lpp_type = lpp_type;
288+
289+
// start at earliest recording, through to most recent
290+
while (n > 0) {
291+
n--;
292+
i = (i + 1) % data.num_slots;
293+
if (ago >= end_secs_ago && ago < start_secs_ago) {
294+
float v = data.data[i];
295+
num_values++;
296+
total += v;
297+
if (num_values == 1) {
298+
dest->_max = dest->_min = v;
299+
} else {
300+
if (v < dest->_min) dest->_min = v;
301+
if (v > dest->_max) dest->_max = v;
302+
}
303+
}
304+
ago -= data.interval_secs;
305+
}
306+
// calc average
307+
if (num_values > 0) {
308+
dest->_avg = total / num_values;
309+
} else {
310+
dest->_avg = NAN;
311+
}
312+
}
313+
225314
float SensorMesh::getAirtimeBudgetFactor() const {
226315
return _prefs.airtime_factor;
227316
}
@@ -577,7 +666,7 @@ void SensorMesh::loop() {
577666
// query other sensors -- target specific
578667
sensors.querySensors(0xFF, telemetry); // allow all telemetry permissions
579668

580-
checkForAlerts();
669+
onSensorDataRead();
581670

582671
// save telemetry to time-series datastore
583672
File file = openAppend(_fs, "/s_data");

examples/simple_sensor/SensorMesh.h

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,33 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks {
9393
void alertIfLow(Trigger& t, float value, float threshold, const char* text);
9494
void alertIfHigh(Trigger& t, float value, float threshold, const char* text);
9595

96-
virtual void checkForAlerts() = 0; // for app to implement
96+
class TimeSeriesData {
97+
public:
98+
float* data;
99+
int num_slots, next;
100+
uint32_t last_timestamp;
101+
uint32_t interval_secs;
102+
103+
TimeSeriesData(float* array, int num, uint32_t secs) : num_slots(num), data(array), last_timestamp(0), next(0), interval_secs(secs) {
104+
memset(data, 0, sizeof(float)*num);
105+
}
106+
TimeSeriesData(int num, uint32_t secs) : num_slots(num), last_timestamp(0), next(0), interval_secs(secs) {
107+
data = new float[num];
108+
memset(data, 0, sizeof(float)*num);
109+
}
110+
};
111+
112+
void recordData(TimeSeriesData& data, float value);
113+
114+
struct MinMaxAvg {
115+
float _min, _max, _avg;
116+
uint8_t _lpp_type, _channel;
117+
};
118+
119+
void calcDataMinMaxAvg(const TimeSeriesData& data, uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg* dest, uint8_t channel, uint8_t lpp_type);
120+
121+
virtual void onSensorDataRead() = 0; // for app to implement
122+
virtual int querySeriesData(uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg dest[], int max_num) = 0; // for app to implement
97123

98124
// Mesh overrides
99125
float getAirtimeBudgetFactor() const override;
@@ -130,6 +156,6 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks {
130156
mesh::Packet* createSelfAdvert();
131157
ContactInfo* putContact(const mesh::Identity& id);
132158

133-
void sendAlert(const char* text) { } // TODO
159+
void sendAlert(const char* text);
134160

135161
};

examples/simple_sensor/main.cpp

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,28 @@
88
class MyMesh : public SensorMesh {
99
public:
1010
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
11-
: SensorMesh(board, radio, ms, rng, rtc, tables) { }
11+
: SensorMesh(board, radio, ms, rng, rtc, tables),
12+
battery_data(12*24, 5*60) // 24 hours worth of battery data, every 5 minutes
13+
{
14+
}
1215

1316
protected:
14-
/* ========================== custom alert logic here ========================== */
17+
/* ========================== custom logic here ========================== */
1518
Trigger low_batt;
19+
TimeSeriesData battery_data;
20+
21+
void onSensorDataRead() override {
22+
float batt_voltage = getVoltage(TELEM_CHANNEL_SELF);
23+
24+
recordData(battery_data, batt_voltage); // record battery
25+
alertIfLow(low_batt, batt_voltage, 3.4f, "Battery low!");
26+
}
1627

17-
void checkForAlerts() override {
18-
alertIfLow(low_batt, getVoltage(TELEM_CHANNEL_SELF), 3.4f, "Battery low!");
19-
// alertIf ...
20-
// alertIf ...
28+
int querySeriesData(uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg dest[], int max_num) override {
29+
calcDataMinMaxAvg(battery_data, start_secs_ago, end_secs_ago, &dest[0], TELEM_CHANNEL_SELF, LPP_VOLTAGE);
30+
return 1;
2131
}
22-
/* ============================================================================= */
32+
/* ======================================================================= */
2333
};
2434

2535
StdRNG fast_rng;

0 commit comments

Comments
 (0)