mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-04-20 22:13:47 +00:00
* added REQ_TYPE_GET_AVG_MIN_MAX
* TimeSeriesData * very basic SensorMesh::sendAlert()
This commit is contained in:
parent
810b1f8fe7
commit
de3e4bc27c
3 changed files with 135 additions and 10 deletions
|
|
@ -50,6 +50,7 @@
|
|||
#define REQ_TYPE_GET_STATUS 0x01
|
||||
#define REQ_TYPE_KEEP_ALIVE 0x02
|
||||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
||||
#define REQ_TYPE_GET_AVG_MIN_MAX 0x04
|
||||
|
||||
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
||||
|
||||
|
|
@ -154,6 +155,22 @@ uint8_t SensorMesh::handleRequest(bool is_admin, uint32_t sender_timestamp, uint
|
|||
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
||||
return 4 + tlen; // reply_len
|
||||
}
|
||||
case REQ_TYPE_GET_AVG_MIN_MAX: {
|
||||
uint32_t start_secs_ago, end_secs_ago;
|
||||
memcpy(&start_secs_ago, &payload[0], 4);
|
||||
memcpy(&end_secs_ago, &payload[4], 4);
|
||||
uint8_t res1 = payload[8]; // reserved for future (extra query params)
|
||||
uint8_t res2 = payload[8];
|
||||
|
||||
MinMaxAvg data[8];
|
||||
int n;
|
||||
if (res1 == 0 && res2 == 0) {
|
||||
n = querySeriesData(start_secs_ago, end_secs_ago, data, 8);
|
||||
} else {
|
||||
n = 0;
|
||||
}
|
||||
return 0; // TODO: encode data[0..n)
|
||||
}
|
||||
}
|
||||
return 0; // unknown command
|
||||
}
|
||||
|
|
@ -192,6 +209,34 @@ ContactInfo* SensorMesh::putContact(const mesh::Identity& id) {
|
|||
return c;
|
||||
}
|
||||
|
||||
void SensorMesh::sendAlert(const char* text) {
|
||||
int text_len = strlen(text);
|
||||
|
||||
// send text message to all admins
|
||||
for (int i = 0; i < num_contacts; i++) {
|
||||
auto c = &contacts[i];
|
||||
if (!c->isAdmin()) continue;
|
||||
|
||||
uint8_t data[MAX_PACKET_PAYLOAD];
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique(); // need different timestamp per packet
|
||||
memcpy(data, &now, 4);
|
||||
data[4] = (TXT_TYPE_PLAIN << 2); // attempt and flags
|
||||
memcpy(&data[5], text, text_len);
|
||||
// calc expected ACK reply
|
||||
// uint32_t expected_ack;
|
||||
// mesh::Utils::sha256((uint8_t *)&expected_ack, 4, data, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE);
|
||||
|
||||
auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, c->id, c->shared_secret, data, 5 + text_len);
|
||||
if (pkt) {
|
||||
if (c->out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||
sendDirect(pkt, c->out_path, c->out_path_len);
|
||||
} else {
|
||||
sendFlood(pkt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SensorMesh::alertIfLow(Trigger& t, float value, float threshold, const char* text) {
|
||||
if (value < threshold) {
|
||||
if (!t.triggered) {
|
||||
|
|
@ -222,6 +267,50 @@ void SensorMesh::alertIfHigh(Trigger& t, float value, float threshold, const cha
|
|||
}
|
||||
}
|
||||
|
||||
void SensorMesh::recordData(TimeSeriesData& data, float value) {
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
if (now >= data.last_timestamp + data.interval_secs) {
|
||||
data.last_timestamp = now;
|
||||
|
||||
data.data[data.next] = value; // append to cycle table
|
||||
data.next = (data.next + 1) % data.num_slots;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
int i = data.next, n = data.num_slots;
|
||||
uint32_t ago = data.interval_secs * data.num_slots;
|
||||
int num_values = 0;
|
||||
float total = 0.0f;
|
||||
|
||||
dest->_channel = channel;
|
||||
dest->_lpp_type = lpp_type;
|
||||
|
||||
// start at earliest recording, through to most recent
|
||||
while (n > 0) {
|
||||
n--;
|
||||
i = (i + 1) % data.num_slots;
|
||||
if (ago >= end_secs_ago && ago < start_secs_ago) {
|
||||
float v = data.data[i];
|
||||
num_values++;
|
||||
total += v;
|
||||
if (num_values == 1) {
|
||||
dest->_max = dest->_min = v;
|
||||
} else {
|
||||
if (v < dest->_min) dest->_min = v;
|
||||
if (v > dest->_max) dest->_max = v;
|
||||
}
|
||||
}
|
||||
ago -= data.interval_secs;
|
||||
}
|
||||
// calc average
|
||||
if (num_values > 0) {
|
||||
dest->_avg = total / num_values;
|
||||
} else {
|
||||
dest->_avg = NAN;
|
||||
}
|
||||
}
|
||||
|
||||
float SensorMesh::getAirtimeBudgetFactor() const {
|
||||
return _prefs.airtime_factor;
|
||||
}
|
||||
|
|
@ -577,7 +666,7 @@ void SensorMesh::loop() {
|
|||
// query other sensors -- target specific
|
||||
sensors.querySensors(0xFF, telemetry); // allow all telemetry permissions
|
||||
|
||||
checkForAlerts();
|
||||
onSensorDataRead();
|
||||
|
||||
// save telemetry to time-series datastore
|
||||
File file = openAppend(_fs, "/s_data");
|
||||
|
|
|
|||
|
|
@ -93,7 +93,33 @@ protected:
|
|||
void alertIfLow(Trigger& t, float value, float threshold, const char* text);
|
||||
void alertIfHigh(Trigger& t, float value, float threshold, const char* text);
|
||||
|
||||
virtual void checkForAlerts() = 0; // for app to implement
|
||||
class TimeSeriesData {
|
||||
public:
|
||||
float* data;
|
||||
int num_slots, next;
|
||||
uint32_t last_timestamp;
|
||||
uint32_t interval_secs;
|
||||
|
||||
TimeSeriesData(float* array, int num, uint32_t secs) : num_slots(num), data(array), last_timestamp(0), next(0), interval_secs(secs) {
|
||||
memset(data, 0, sizeof(float)*num);
|
||||
}
|
||||
TimeSeriesData(int num, uint32_t secs) : num_slots(num), last_timestamp(0), next(0), interval_secs(secs) {
|
||||
data = new float[num];
|
||||
memset(data, 0, sizeof(float)*num);
|
||||
}
|
||||
};
|
||||
|
||||
void recordData(TimeSeriesData& data, float value);
|
||||
|
||||
struct MinMaxAvg {
|
||||
float _min, _max, _avg;
|
||||
uint8_t _lpp_type, _channel;
|
||||
};
|
||||
|
||||
void calcDataMinMaxAvg(const TimeSeriesData& data, uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg* dest, uint8_t channel, uint8_t lpp_type);
|
||||
|
||||
virtual void onSensorDataRead() = 0; // for app to implement
|
||||
virtual int querySeriesData(uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg dest[], int max_num) = 0; // for app to implement
|
||||
|
||||
// Mesh overrides
|
||||
float getAirtimeBudgetFactor() const override;
|
||||
|
|
@ -130,6 +156,6 @@ private:
|
|||
mesh::Packet* createSelfAdvert();
|
||||
ContactInfo* putContact(const mesh::Identity& id);
|
||||
|
||||
void sendAlert(const char* text) { } // TODO
|
||||
void sendAlert(const char* text);
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,18 +8,28 @@
|
|||
class MyMesh : public SensorMesh {
|
||||
public:
|
||||
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
|
||||
: SensorMesh(board, radio, ms, rng, rtc, tables) { }
|
||||
: SensorMesh(board, radio, ms, rng, rtc, tables),
|
||||
battery_data(12*24, 5*60) // 24 hours worth of battery data, every 5 minutes
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
/* ========================== custom alert logic here ========================== */
|
||||
/* ========================== custom logic here ========================== */
|
||||
Trigger low_batt;
|
||||
TimeSeriesData battery_data;
|
||||
|
||||
void checkForAlerts() override {
|
||||
alertIfLow(low_batt, getVoltage(TELEM_CHANNEL_SELF), 3.4f, "Battery low!");
|
||||
// alertIf ...
|
||||
// alertIf ...
|
||||
void onSensorDataRead() override {
|
||||
float batt_voltage = getVoltage(TELEM_CHANNEL_SELF);
|
||||
|
||||
recordData(battery_data, batt_voltage); // record battery
|
||||
alertIfLow(low_batt, batt_voltage, 3.4f, "Battery low!");
|
||||
}
|
||||
/* ============================================================================= */
|
||||
|
||||
int querySeriesData(uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg dest[], int max_num) override {
|
||||
calcDataMinMaxAvg(battery_data, start_secs_ago, end_secs_ago, &dest[0], TELEM_CHANNEL_SELF, LPP_VOLTAGE);
|
||||
return 1;
|
||||
}
|
||||
/* ======================================================================= */
|
||||
};
|
||||
|
||||
StdRNG fast_rng;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue