Merge pull request #57 from xJARiD/develop

fix: mqtt offline status and cli commands
This commit is contained in:
xJARiD
2026-05-01 08:45:12 +10:00
committed by GitHub
7 changed files with 91 additions and 20 deletions
+21 -2
View File
@@ -146,6 +146,21 @@ disable_debug_flags() {
fi
}
get_firmware_build_date() {
local env_name="$1"
local header="examples/simple_repeater/MyMesh.h"
if [[ "$env_name" == *companion_radio* ]]; then
header="examples/companion_radio/MyMesh.h"
elif [[ "$env_name" == *room_server* ]]; then
header="examples/simple_room_server/MyMesh.h"
elif [[ "$env_name" == *sensor* ]]; then
header="examples/simple_sensor/SensorMesh.h"
fi
sed -n 's/^[[:space:]]*#define[[:space:]][[:space:]]*FIRMWARE_BUILD_DATE[[:space:]][[:space:]]*"\([^"]*\)".*/\1/p' "$header" | head -n 1
}
# build firmware for the provided pio env in $1
build_firmware() {
# get env platform for post build actions
@@ -154,8 +169,12 @@ build_firmware() {
# get git commit sha
COMMIT_HASH=$(git rev-parse --short HEAD)
# set firmware build date
FIRMWARE_BUILD_DATE=$(date '+%d-%b-%Y')
# use the same fallback build date as direct PlatformIO builds
FIRMWARE_BUILD_DATE=$(get_firmware_build_date "$1")
if [ -z "$FIRMWARE_BUILD_DATE" ]; then
echo "FIRMWARE_BUILD_DATE could not be read for $1"
exit 1
fi
# get FIRMWARE_VERSION, which should be provided by the environment
if [ -z "$FIRMWARE_VERSION" ]; then
+4
View File
@@ -8,6 +8,8 @@ It does not try to repeat the full upstream MeshCore CLI surface.
These commands are available on `*_repeater_mqtt` firmware targets.
No-argument `get` commands must be entered exactly as shown.
### MQTT Status And Routing
- `get mqtt.status`: shows WiFi, NTP, IATA, endpoint status, status publishing state, and TX state.
@@ -50,6 +52,8 @@ Notes:
- new repeater MQTT installs default `mqtt.iata` to `UNSET`
- `letsmesh-eu` and `letsmesh-us` remain off by default unless already configured in saved prefs
- if `mqtt.iata` is `UNSET`, `eastmesh-au`, `letsmesh-eu`, and `letsmesh-us` will not connect even if they are toggled on
- turning off a connected broker publishes a retained MQTT status update with `"status":"offline"` before the client disconnects
- changing `mqtt.iata` away from a configured value also publishes retained offline status to the old status topic, restarts connected broker clients, and reconnects under the new topic path
Legacy dotted aliases are also accepted:
+2
View File
@@ -221,6 +221,8 @@ Notes:
- while `mqtt.iata` is `UNSET`, enabled MQTT brokers do not attempt to connect
- the current MQTT server states are loaded when the page opens
- you can toggle each MQTT server on or off from this panel
- turning off a connected MQTT server publishes retained offline status before the client disconnects
- changing `mqtt.iata` away from a configured value publishes retained offline status to the old status topic, restarts connected broker clients, and reconnects under the new topic path
- if all three servers are enabled at once, the panel shows a warning recommending two at most
## `/stats` Overview
+12 -12
View File
@@ -2233,11 +2233,11 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
}
#endif
#if defined(ESP_PLATFORM)
} else if (memcmp(command, "get wifi.status", 15) == 0) {
} else if (strcmp(command, "get wifi.status") == 0) {
network.formatWifiStatusReply(reply, 160);
} else if (memcmp(command, "get wifi.ssid", 13) == 0) {
} else if (strcmp(command, "get wifi.ssid") == 0) {
sprintf(reply, "> %s", network.getWifiSSID()[0] ? network.getWifiSSID() : "-");
} else if (memcmp(command, "get wifi.powersaving", 20) == 0) {
} else if (strcmp(command, "get wifi.powersaving") == 0) {
sprintf(reply, "> %s", network.getWifiPowerSave());
#endif
#if defined(ESP_PLATFORM) && WITH_WEB_PANEL
@@ -2308,23 +2308,23 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
mqtt.formatStatusReply(reply, 160);
} else if (strcmp(command, "get mqtt.client_version") == 0) {
sprintf(reply, "> %s", mqtt.getClientVersion());
} else if (memcmp(command, "get mqtt.iata", 13) == 0) {
} else if (strcmp(command, "get mqtt.iata") == 0) {
sprintf(reply, "> %s", mqtt.getIata());
} else if (memcmp(command, "get mqtt.owner", 14) == 0) {
} else if (strcmp(command, "get mqtt.owner") == 0) {
sprintf(reply, "> %s", mqtt.getOwnerPublicKey()[0] ? mqtt.getOwnerPublicKey() : "-");
} else if (memcmp(command, "get mqtt.email", 14) == 0) {
} else if (strcmp(command, "get mqtt.email") == 0) {
sprintf(reply, "> %s", mqtt.getOwnerEmail()[0] ? mqtt.getOwnerEmail() : "-");
} else if (memcmp(command, "get mqtt.packets", 16) == 0) {
} else if (strcmp(command, "get mqtt.packets") == 0) {
sprintf(reply, "> %s", mqtt.isPacketsEnabled() ? "on" : "off");
} else if (memcmp(command, "get mqtt.raw", 12) == 0) {
} else if (strcmp(command, "get mqtt.raw") == 0) {
sprintf(reply, "> %s", mqtt.isRawEnabled() ? "on" : "off");
} else if (memcmp(command, "get mqtt.tx", 11) == 0) {
} else if (strcmp(command, "get mqtt.tx") == 0) {
sprintf(reply, "> %s", mqtt.isTxEnabled() ? "on" : "off");
} else if (memcmp(command, "get mqtt.eastmesh-au", 20) == 0 || memcmp(command, "get mqtt.eastmesh.au", 20) == 0) {
} else if (strcmp(command, "get mqtt.eastmesh-au") == 0 || strcmp(command, "get mqtt.eastmesh.au") == 0) {
sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x01) ? "on" : "off");
} else if (memcmp(command, "get mqtt.letsmesh-eu", 21) == 0 || memcmp(command, "get mqtt.letsmesh.eu", 21) == 0) {
} else if (strcmp(command, "get mqtt.letsmesh-eu") == 0 || strcmp(command, "get mqtt.letsmesh.eu") == 0) {
sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x02) ? "on" : "off");
} else if (memcmp(command, "get mqtt.letsmesh-us", 21) == 0 || memcmp(command, "get mqtt.letsmesh.us", 21) == 0) {
} else if (strcmp(command, "get mqtt.letsmesh-us") == 0 || strcmp(command, "get mqtt.letsmesh.us") == 0) {
sprintf(reply, "> %s", mqtt.isEndpointEnabled(0x04) ? "on" : "off");
} else if (memcmp(command, "set mqtt.tx ", 12) == 0) {
mqtt.setTxEnabled(memcmp(&command[12], "on", 2) == 0);
+24
View File
@@ -557,3 +557,27 @@ releases:
area: web-panel
text: "Documented no-PSRAM `/stats` history behavior, stale browser-token recovery, and the lightweight `/api/session` endpoint used by the web panel."
breaking_changes: []
- track: repeater-mqtt
version: "1.3.15"
tag: "repeater-mqtt-eastmesh-v1.3.15"
date: "2026-04-30"
previous_version: "1.3.14"
summary: "Publishes retained offline status for clean MQTT broker disables and IATA topic changes so operator-driven disconnects do not leave stale online status behind."
changes:
- type: fixed
area: mqtt
text: "Published explicit retained offline status before a connected broker is toggled off, matching operator expectations without relying on last-will behavior that only fires on unclean disconnects."
- type: fixed
area: mqtt
text: "Published retained offline status to the old status topic before `mqtt.iata` changes, then restarted connected broker clients so they reconnect and publish online status under the new IATA path."
- type: docs
area: mqtt
text: "Updated the CLI and web-panel docs to describe retained offline status publishing for broker toggles and IATA changes."
- type: fixed
area: cli
text: "Matched no-argument MQTT and Wi-Fi `get` commands exactly so malformed commands with trailing text no longer execute as valid getters."
- type: changed
area: build
text: "Aligned release-style builds with direct PlatformIO builds by using the firmware source build date instead of stamping the current build day into `ver` output."
breaking_changes: []
+27 -5
View File
@@ -695,12 +695,12 @@ int MQTTUplink::buildRawJson(char* buffer, size_t buffer_size, const mesh::Packe
return len;
}
void MQTTUplink::publishOnlineStatus(BrokerState& broker) {
void MQTTUplink::publishBrokerStatus(BrokerState& broker, bool online) {
char* payload = allocScratchBuffer(768);
if (payload == nullptr) {
return;
}
int len = buildStatusJson(payload, 768, true);
int len = buildStatusJson(payload, 768, online);
if (len > 0 && static_cast<size_t>(len) < 768) {
queuePublish(broker, broker.status_topic, payload, true);
}
@@ -999,7 +999,7 @@ void MQTTUplink::loop(const MQTTStatusSnapshot& snapshot) {
connect_started = true;
}
if (broker.connected && !broker.connect_announced) {
publishOnlineStatus(broker);
publishBrokerStatus(broker, true);
broker.connect_announced = true;
_last_status_publish = millis();
} else if (!broker.connected) {
@@ -1116,6 +1116,12 @@ bool MQTTUplink::setEndpointEnabled(uint8_t bit, bool enabled) {
return false;
}
} else {
for (BrokerState& broker : _brokers) {
if (broker.spec != nullptr && broker.spec->bit == bit && broker.connected && broker.client != nullptr) {
publishBrokerStatus(broker, false);
break;
}
}
next_mask &= ~bit;
}
_prefs.enabled_mask = next_mask;
@@ -1158,14 +1164,30 @@ bool MQTTUplink::setIata(const char* iata) {
for (size_t i = 0; cleaned[i] != 0; ++i) {
cleaned[i] = toupper(static_cast<unsigned char>(cleaned[i]));
}
bool changed = strcmp(cleaned, _prefs.iata) != 0;
if (changed && !isUnsetIataValue(_prefs.iata)) {
publishStatus(false);
}
if (strcmp(cleaned, MQTT_UNSET_IATA) == 0) {
StrHelper::strncpy(_prefs.iata, MQTT_UNSET_IATA, sizeof(_prefs.iata));
refreshIdentityStrings();
return savePrefs();
bool saved = savePrefs();
if (changed) {
for (BrokerState& broker : _brokers) {
destroyBroker(broker);
}
}
return saved;
}
StrHelper::strncpy(_prefs.iata, cleaned, sizeof(_prefs.iata));
refreshIdentityStrings();
return savePrefs();
bool saved = savePrefs();
if (changed) {
for (BrokerState& broker : _brokers) {
destroyBroker(broker);
}
}
return saved;
}
bool MQTTUplink::setOwnerPublicKey(const char* owner_public_key) {
+1 -1
View File
@@ -138,7 +138,7 @@ private:
void destroyBroker(BrokerState& broker, bool reset_retry_state = true);
bool refreshToken(BrokerState& broker);
void publishStatus(bool online);
void publishOnlineStatus(BrokerState& broker);
void publishBrokerStatus(BrokerState& broker, bool online);
void queuePublish(BrokerState& broker, const char* topic, const char* payload, bool retain);
int buildStatusJson(char* buffer, size_t buffer_size, bool online) const;
int buildPacketJson(char* buffer, size_t buffer_size, const mesh::Packet& packet, bool is_tx, int rssi, float snr,