Added telemetry print to repeater admin including battery and temperature status

This commit is contained in:
pelgraine
2026-02-27 23:09:51 +11:00
parent 01a7ab80eb
commit a536196fd7
6 changed files with 137 additions and 2 deletions

View File

@@ -51,4 +51,5 @@ public:
// Repeater admin callbacks (from MyMesh)
virtual void onAdminLoginResult(bool success, uint8_t permissions, uint32_t server_time) {}
virtual void onAdminCliResponse(const char* from_name, const char* text) {}
virtual void onAdminTelemetryResult(const uint8_t* data, uint8_t len) {}
};

View File

@@ -706,6 +706,29 @@ bool MyMesh::uiSendCliCommand(uint32_t contact_idx, const char* command) {
return true;
}
bool MyMesh::uiSendTelemetryRequest(uint32_t contact_idx) {
ContactInfo contact;
if (!getContactByIdx(contact_idx, contact)) return false;
ContactInfo* recipient = lookupContactByPubKey(contact.id.pub_key, PUB_KEY_SIZE);
if (!recipient) return false;
uint32_t tag, est_timeout;
int result = sendRequest(*recipient, REQ_TYPE_GET_TELEMETRY_DATA, tag, est_timeout);
if (result == MSG_SEND_FAILED) {
MESH_DEBUG_PRINTLN("UI: Telemetry request send failed to %s", recipient->name);
return false;
}
clearPendingReqs();
pending_telemetry = tag;
MESH_DEBUG_PRINTLN("UI: Telemetry request sent to %s (%s), timeout=%dms",
recipient->name, result == MSG_SEND_SENT_FLOOD ? "flood" : "direct",
est_timeout);
return true;
}
uint8_t MyMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data,
uint8_t len, uint8_t *reply) {
if (data[0] == REQ_TYPE_GET_TELEMETRY_DATA) {
@@ -816,6 +839,7 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
_serial->writeFrame(out_frame, i);
} else if (len > 4 && tag == pending_telemetry) { // check for matching response tag
pending_telemetry = 0;
MESH_DEBUG_PRINTLN("Telemetry response received from %s, len=%d", contact.name, len);
int i = 0;
out_frame[i++] = PUSH_CODE_TELEMETRY_RESPONSE;
@@ -825,6 +849,11 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
memcpy(&out_frame[i], &data[4], len - 4);
i += (len - 4);
_serial->writeFrame(out_frame, i);
#ifdef DISPLAY_CLASS
// Route telemetry data to UI (LPP buffer after the 4-byte tag)
if (_ui) _ui->onAdminTelemetryResult(&data[4], len - 4);
#endif
} else if (len > 4 && tag == pending_req) { // check for matching response tag
pending_req = 0;

View File

@@ -111,6 +111,7 @@ public:
// Repeater admin - UI-initiated operations
bool uiLoginToRepeater(uint32_t contact_idx, const char* password);
bool uiSendCliCommand(uint32_t contact_idx, const char* command);
bool uiSendTelemetryRequest(uint32_t contact_idx);
int getAdminContactIdx() const { return _admin_contact_idx; }

View File

@@ -208,6 +208,13 @@ private:
PwdCacheEntry _pwdCache[PWD_CACHE_SIZE];
int _pwdCacheCount;
// Remote telemetry (from LPP response)
float _telemVoltage; // Battery voltage in volts
float _telemTempC; // Temperature in celsius
bool _telemHasVoltage;
bool _telemHasTemp;
bool _telemRequested; // Telemetry request already sent
const char* getCachedPassword(int contactIdx) {
for (int i = 0; i < _pwdCacheCount; i++) {
if (_pwdCache[i].contactIdx == contactIdx) return _pwdCache[i].password;
@@ -236,6 +243,49 @@ private:
}
}
// --- LPP telemetry parser ---
// Walks a CayenneLPP buffer extracting voltage and temperature.
// Format per entry: [channel 1B][type 1B][data varies]
void parseLPPTelemetry(const uint8_t* data, uint8_t len) {
const uint8_t TELEM_TYPE_VOLTAGE = 0x74; // 2 bytes, uint16 / 100 = V
const uint8_t TELEM_TYPE_TEMPERATURE = 0x67; // 2 bytes, int16 / 10 = C
int pos = 0;
while (pos + 2 < len) {
// uint8_t channel = data[pos]; // not needed
uint8_t type = data[pos + 1];
pos += 2; // skip channel + type
// Determine data size for this type
int dataSize = 0;
switch (type) {
case TELEM_TYPE_VOLTAGE: dataSize = 2; break;
case TELEM_TYPE_TEMPERATURE: dataSize = 2; break;
case 0x88: dataSize = 9; break; // GPS
case 0x75: dataSize = 2; break; // Current
case 0x68: dataSize = 1; break; // Humidity
case 0x73: dataSize = 2; break; // Pressure
case 0x76: dataSize = 2; break; // Altitude
case 0x80: dataSize = 2; break; // Power
default: return; // Unknown type, stop parsing
}
if (pos + dataSize > len) break;
if (type == TELEM_TYPE_VOLTAGE) {
uint16_t raw = ((uint16_t)data[pos] << 8) | data[pos + 1];
_telemVoltage = raw / 100.0f;
_telemHasVoltage = true;
} else if (type == TELEM_TYPE_TEMPERATURE) {
int16_t raw = ((int16_t)data[pos] << 8) | data[pos + 1];
_telemTempC = raw / 10.0f;
_telemHasTemp = true;
}
pos += dataSize;
}
}
// --- Helpers ---
static void formatTime(char* buf, size_t bufLen, uint32_t epoch) {
@@ -378,7 +428,9 @@ public:
_catSel(0), _cmdSel(0), _scrollOffset(0),
_paramLen(0), _pendingCmd(nullptr),
_responseLen(0), _responseScroll(0), _responseTotalLines(0),
_cmdSentAt(0), _waitingForLogin(false), _pwdCacheCount(0) {
_cmdSentAt(0), _waitingForLogin(false), _pwdCacheCount(0),
_telemVoltage(0), _telemTempC(0),
_telemHasVoltage(false), _telemHasTemp(false), _telemRequested(false) {
_password[0] = '\0';
_repeaterName[0] = '\0';
_response[0] = '\0';
@@ -405,6 +457,9 @@ public:
_pendingCmd = nullptr;
_paramLen = 0;
_paramBuf[0] = '\0';
_telemHasVoltage = false;
_telemHasTemp = false;
_telemRequested = false;
const char* cached = getCachedPassword(contactIdx);
if (cached) {
@@ -427,6 +482,14 @@ public:
_serverTime = server_time;
_state = STATE_CATEGORY_MENU;
cachePassword(_contactIdx, _password);
// Auto-request telemetry (battery & temperature) after login
if (!_telemRequested) {
_telemRequested = true;
bool sent = the_mesh.uiSendTelemetryRequest(_contactIdx);
Serial.printf("[Admin] Telemetry request %s for contact idx %d\n",
sent ? "sent" : "FAILED", _contactIdx);
}
} else {
snprintf(_response, sizeof(_response), "Login failed.\nCheck password.");
_responseLen = strlen(_response);
@@ -456,6 +519,15 @@ public:
_state = STATE_RESPONSE_VIEW;
}
void onTelemetryResult(const uint8_t* data, uint8_t len) {
Serial.printf("[Admin] Telemetry response received, %d bytes:", len);
for (int i = 0; i < len && i < 32; i++) Serial.printf(" %02X", data[i]);
Serial.println();
parseLPPTelemetry(data, len);
Serial.printf("[Admin] Parsed: hasVoltage=%d (%.2fV) hasTemp=%d (%.1fC)\n",
_telemHasVoltage, _telemVoltage, _telemHasTemp, _telemTempC);
}
void poll() override {
if ((_state == STATE_LOGGING_IN || _state == STATE_COMMAND_PENDING) &&
_cmdSentAt > 0 && (millis() - _cmdSentAt) > ADMIN_TIMEOUT_MS) {
@@ -469,6 +541,7 @@ public:
_responseLen = strlen(_response);
_state = STATE_ERROR;
}
_task->forceRefresh(); // Immediate redraw on state change
}
}
@@ -558,7 +631,7 @@ public:
break;
}
if (_state == STATE_LOGGING_IN || _state == STATE_COMMAND_PENDING) return 1000;
if (_state == STATE_LOGGING_IN || _state == STATE_COMMAND_PENDING) return 30000; // static text; poll()/callbacks force refresh on state change
if (_state == STATE_PASSWORD_ENTRY && _lastCharAt > 0 && (millis() - _lastCharAt) < 800) {
return _lastCharAt + 800 - millis() + 50;
}
@@ -682,6 +755,28 @@ private:
y += lineHeight + 2;
}
// Remote telemetry info line (battery & temperature)
if (_telemHasVoltage || _telemHasTemp) {
display.setColor(DisplayDriver::LIGHT);
display.setCursor(0, y);
char telem[48];
int tpos = 0;
if (_telemHasVoltage) {
tpos += snprintf(telem + tpos, sizeof(telem) - tpos, "Batt:%.2fV", _telemVoltage);
}
if (_telemHasTemp) {
if (tpos > 0) tpos += snprintf(telem + tpos, sizeof(telem) - tpos, " ");
tpos += snprintf(telem + tpos, sizeof(telem) - tpos, "Temp:%.1fC", _telemTempC);
}
display.print(telem);
y += lineHeight + 2;
} else if (_telemRequested) {
display.setColor(DisplayDriver::LIGHT);
display.setCursor(0, y);
display.print("Telemetry: requesting...");
y += lineHeight + 2;
}
// Render categories
for (int i = 0; i < CAT_COUNT && y + lineHeight <= display.height() - 16; i++) {
bool isSystem = (i == CAT_REBOOT_OTA);

View File

@@ -1550,6 +1550,14 @@ void UITask::onAdminCliResponse(const char* from_name, const char* text) {
}
}
void UITask::onAdminTelemetryResult(const uint8_t* data, uint8_t len) {
Serial.printf("[UITask] onAdminTelemetryResult: %d bytes, onAdmin=%d\n", len, isOnRepeaterAdmin());
if (repeater_admin && isOnRepeaterAdmin()) {
((RepeaterAdminScreen*)repeater_admin)->onTelemetryResult(data, len);
_next_refresh = 100; // trigger re-render
}
}
#ifdef MECK_AUDIO_VARIANT
bool UITask::isAudioPlayingInBackground() const {
if (!audiobook_screen) return false;

View File

@@ -158,6 +158,7 @@ public:
// Repeater admin callbacks
void onAdminLoginResult(bool success, uint8_t permissions, uint32_t server_time) override;
void onAdminCliResponse(const char* from_name, const char* text) override;
void onAdminTelemetryResult(const uint8_t* data, uint8_t len) override;
// Get current screen for checking state
UIScreen* getCurrentScreen() const { return curr; }