|
50 | 50 | #define REQ_TYPE_GET_STATUS 0x01 |
51 | 51 | #define REQ_TYPE_KEEP_ALIVE 0x02 |
52 | 52 | #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 |
| 53 | +#define REQ_TYPE_GET_AVG_MIN_MAX 0x04 |
53 | 54 |
|
54 | 55 | #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ |
55 | 56 |
|
@@ -154,6 +155,22 @@ uint8_t SensorMesh::handleRequest(bool is_admin, uint32_t sender_timestamp, uint |
154 | 155 | memcpy(&reply_data[4], telemetry.getBuffer(), tlen); |
155 | 156 | return 4 + tlen; // reply_len |
156 | 157 | } |
| 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 | + } |
157 | 174 | } |
158 | 175 | return 0; // unknown command |
159 | 176 | } |
@@ -192,6 +209,34 @@ ContactInfo* SensorMesh::putContact(const mesh::Identity& id) { |
192 | 209 | return c; |
193 | 210 | } |
194 | 211 |
|
| 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 | + |
195 | 240 | void SensorMesh::alertIfLow(Trigger& t, float value, float threshold, const char* text) { |
196 | 241 | if (value < threshold) { |
197 | 242 | if (!t.triggered) { |
@@ -222,6 +267,50 @@ void SensorMesh::alertIfHigh(Trigger& t, float value, float threshold, const cha |
222 | 267 | } |
223 | 268 | } |
224 | 269 |
|
| 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 | + |
225 | 314 | float SensorMesh::getAirtimeBudgetFactor() const { |
226 | 315 | return _prefs.airtime_factor; |
227 | 316 | } |
@@ -577,7 +666,7 @@ void SensorMesh::loop() { |
577 | 666 | // query other sensors -- target specific |
578 | 667 | sensors.querySensors(0xFF, telemetry); // allow all telemetry permissions |
579 | 668 |
|
580 | | - checkForAlerts(); |
| 669 | + onSensorDataRead(); |
581 | 670 |
|
582 | 671 | // save telemetry to time-series datastore |
583 | 672 | File file = openAppend(_fs, "/s_data"); |
|
0 commit comments