diff --git a/RX_FSK/RX_FSK.ino b/RX_FSK/RX_FSK.ino index e6c4303..159a15b 100644 --- a/RX_FSK/RX_FSK.ino +++ b/RX_FSK/RX_FSK.ino @@ -723,6 +723,7 @@ struct st_configitems config_list[] = { {"mqtt.username", 63, &sonde.config.mqtt.username}, {"mqtt.password", 63, &sonde.config.mqtt.password}, {"mqtt.prefix", 63, &sonde.config.mqtt.prefix}, + {"mqtt.report_interval", 0, &sonde.config.mqtt.report_interval}, #endif #if FEATURE_SDCARD /* SD-Card settings */ diff --git a/RX_FSK/data/cfg.js b/RX_FSK/data/cfg.js index 4dc261c..2738037 100644 --- a/RX_FSK/data/cfg.js +++ b/RX_FSK/data/cfg.js @@ -51,13 +51,14 @@ var cfgs = [ [ "tcp.chase", "APRS location reporting (0=off, 1=fixed, 2=chase/GPS, 3=auto)"], [ "tcp.comment", "APRS location comment"], [ "", "MQTT data feed configuration", "https://github.com/dl9rdz/rdz_ttgo_sonde/wiki/MQTT-configuration"], -[ "mqtt.active", "MQTT active (needs reboot)"], +[ "mqtt.active", "MQTT message selection bitfield: 1=Sondes, 2=Uptime, 4=PMU, 8=GPS, 16=Scanner/Spectrum Peak, 128=Debug. 0 to disable MQTT (needs reboot)"], [ "mqtt.id", "MQTT client ID"], [ "mqtt.host", "MQTT server hostname"], [ "mqtt.port", "MQTT port"], [ "mqtt.username", "MQTT username"], [ "mqtt.password", "MQTT password"], [ "mqtt.prefix", "MQTT prefix"], +[ "mqtt.report_interval", "MQTT reporting interval (ms)"], [ "", "Chasemapper settings", "https://github.com/dl9rdz/rdz_ttgo_sonde/wiki/Chasemapper-configuration"], [ "cm.active", "Chasemapper active (0=disabled, 1=active)"], [ "cm.host", "Chasemapper UDP host"], diff --git a/RX_FSK/data/config.txt b/RX_FSK/data/config.txt index 0c24719..044893e 100644 --- a/RX_FSK/data/config.txt +++ b/RX_FSK/data/config.txt @@ -128,6 +128,7 @@ mqtt.port=1883 mqtt.username= mqtt.password= mqtt.prefix=rdz_sonde_server/ +mqtt.report_interval=60000 #-------------------------------# # Sondehub v2 settings #-------------------------------# diff --git a/RX_FSK/src/Scanner.cpp b/RX_FSK/src/Scanner.cpp index 64039f9..c246cb4 100644 --- a/RX_FSK/src/Scanner.cpp +++ b/RX_FSK/src/Scanner.cpp @@ -5,6 +5,7 @@ #include "SX1278FSK.h" #include "Sonde.h" #include "Display.h" +#include "src/conn-mqtt.h" double STARTF; @@ -179,7 +180,7 @@ void Scanner::scan() int peakres=-9999; for(int i=0; ipeakres+1) { peakres=r; peakidx=i*scanconfig.SMPL_PIX; } + if(r>peakres+1) { peakres=r; peakidx=i*scanconfig.SMPL_PIX; } scandisp[i] = r; for(int j=1; j +#include "conn-mqtt.h" RXTask rxtask = { -1, -1, -1, 0xFFFF, 0 }; @@ -340,6 +341,7 @@ void Sonde::defaultConfig() { strcpy(config.mqtt.username, "/0"); strcpy(config.mqtt.password, "/0"); strcpy(config.mqtt.prefix, "rdz_sonde_server/"); + config.mqtt.report_interval = 60000; } extern struct st_configitems config_list[]; @@ -535,6 +537,12 @@ void Sonde::setup() { int afcbw = (int)sx1278.getAFCBandwidth(); int rxbw = (int)sx1278.getRxBandwidth(); LOG_I(TAG, "Sonde::setup() done: Type %s Freq %f, AFC BW: %d, RX BW: %d\n", sondeTypeStr[sondeList[rxtask.currentSonde].type], 0.000001*freq, afcbw, rxbw); +#if FEATURE_MQTT + connMQTT.publishQRG( + rxtask.currentSonde+1, + sondeTypeStr[sondeList[rxtask.currentSonde].type], + sondeList[rxtask.currentSonde].launchsite, freq/1e6); +#endif // reset rxtimer / norxtimer state sonde.sondeList[sonde.currentSonde].lastState = -1; diff --git a/RX_FSK/src/Sonde.h b/RX_FSK/src/Sonde.h index 8d46a3a..a38c7c1 100644 --- a/RX_FSK/src/Sonde.h +++ b/RX_FSK/src/Sonde.h @@ -220,6 +220,7 @@ struct st_mqtt { char username[64]; char password[64]; char prefix[64]; + int report_interval; }; struct st_cm { diff --git a/RX_FSK/src/conn-mqtt.cpp b/RX_FSK/src/conn-mqtt.cpp index 2059c69..bcc9b82 100644 --- a/RX_FSK/src/conn-mqtt.cpp +++ b/RX_FSK/src/conn-mqtt.cpp @@ -8,8 +8,13 @@ #include #include #include +#include #include "json.h" +#include "pmu.h" +#include "posinfo.h" +extern PMU *pmu; +extern struct StationPos gpsPos; extern const char *version_name; extern const char *version_id; @@ -21,23 +26,22 @@ extern const char *version_id; {"mqtt.username", 63, &sonde.config.mqtt.username}, {"mqtt.password", 63, &sonde.config.mqtt.password}, {"mqtt.prefix", 63, &sonde.config.mqtt.prefix}, + {"mqtt.report_interval", 0, &sonde.config.mqtt.interval}, */ TimerHandle_t mqttReconnectTimer; extern t_wifi_state wifi_state; +char time_str[32]; /* Global initalization (on TTGO startup) */ void MQTT::init() { } - // Internal helper function for netsetup void mqttCallback(char* topic, byte* payload, unsigned int length) { - Serial.print("Message arrived ["); - Serial.print(topic); - Serial.print("] "); + Serial.printf("Message arrived [%s]", topic); for (int i=0;iip); - Serial.println("[MQTT] pubsub client"); mqttClient.setServer(ip, sonde.config.mqtt.port); snprintf(clientID, 20, "%s%04d", sonde.config.mqtt.id, (int)random(0, 1000)); clientID[20] = 0; - Serial.print(clientID); + Serial.printf("[MQTT] pubsub client %s connecting to %s\n", clientID, sonde.config.mqtt.host); mqttClient.setClientId(clientID); if (strlen(sonde.config.mqtt.password) > 0) { mqttClient.setCredentials(sonde.config.mqtt.username, sonde.config.mqtt.password); } + MQTT::connectToMqtt(); } void MQTT::netshutdown() { @@ -70,12 +76,8 @@ void MQTT::netshutdown() { mqttClient.disconnect(true); // force } - void MQTT::updateSonde( SondeInfo *si ) { - if(!sonde.config.mqtt.active) - return; - - if(1 /*connected*/) { + if(mqttGate(MQTT_SEND_UPTIME)){ Serial.println("Sending sonde info via MQTT"); // TODO: Check if si is good / fresh publishPacket(si); @@ -83,53 +85,178 @@ void MQTT::updateSonde( SondeInfo *si ) { } void MQTT::updateStation( PosInfo *pi ) { - if(!sonde.config.mqtt.active) - return; - unsigned long now = millis(); - if ( (lastMqttUptime == 0) || (now - lastMqttUptime > 60000) ) { - publishUptime(); - lastMqttUptime = now; + if ( (lastMqttUptime == 0) || (now - lastMqttUptime >= sonde.config.mqtt.report_interval) ) { + MQTT::connectToMqtt(); + publishUptime(); + publishPmuInfo(); + publishGps(); + lastMqttUptime = now; } } // Internal (private) functions +int MQTT::mqttGate(uint flag){ + // Decide whether or not to send (gate) the message based on selected MQTT + // message types and MQTT connection. If the conditions are not met, then + // the publisher function will simply return without sending anything. + return ((sonde.config.mqtt.active & flag) && mqttClient.connected()); +} + int MQTT::connectToMqtt() { if(mqttClient.connected()) return 1; if(wifi_state != WIFI_CONNECTED) return 0; + if(0 == sonde.config.mqtt.active) + return 0; Serial.println("MQTT not connected, connecting...."); mqttClient.connect(); return 1; } +// Get current time and format it into a string. +// FIXME - make this globally accessible so that any function that wants to +// use the current time as a string can do this +void MQTT::timeFormat() +{ + static unsigned long last_updated = 0; + if ((millis() - last_updated) < 500) + return; + + last_updated = millis(); + time_t now; + time(&now); + strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", gmtime(&now)); +} + void MQTT::publishUptime() { // ensure we've got connection - if(!connectToMqtt()) + if(!mqttGate(MQTT_SEND_UPTIME)) return; Serial.println("[MQTT] writing"); char payload[256]; + + timeFormat(); + // maybe TODO: Use dynamic position if GPS is available? // rxlat, rxlon only if not empty - snprintf(payload, 256, "{\"uptime\": %lu, \"user\": \"%s\", ", millis(), sonde.config.mqtt.username); - if( !isnan(sonde.config.rxlat) && !isnan(sonde.config.rxlon) ) { - snprintf(payload, 256, "%s\"rxlat\": %.5f, \"rxlon\": %.5f, ", payload, sonde.config.rxlat, sonde.config.rxlon); + snprintf(payload, 256, + "{\"uptime\": %.1f, \"user\": \"%s\", \"time\": %s,", + millis() / 1000.0, sonde.config.mqtt.username, time_str ); + + if (!isnan(sonde.config.rxlat) && !isnan(sonde.config.rxlon)) { + snprintf(payload, 256, + "%s \"rxlat\": %.5f, \"rxlon\": %.5f,", + payload, sonde.config.rxlat, sonde.config.rxlon); } - snprintf(payload, 256, "%s\"SW\": \"%s\", \"VER\": \"%s\"}", payload, version_name, version_id); + snprintf(payload, 256, + "%s \"SW\": \"%s\", \"VER\": \"%s\"}", + payload, version_name, version_id); Serial.println(payload); char topic[128]; snprintf(topic, 128, "%s%s", sonde.config.mqtt.prefix, "uptime"); mqttClient.publish(topic, 1, 1, payload); } +void MQTT::publishPmuInfo() +{ + if(!mqttGate(MQTT_SEND_PMU)) + return; + + char payload[256]; + float i_d = pmu->getBattDischargeCurrent(); + float i_c = pmu->getBattChargeCurrent(); + float i_batt = 0; + + if (i_c) + i_batt = i_c; + else if (i_d) + i_batt = -i_d; + + snprintf(payload, sizeof(payload), + "{\"I_Batt\": %.1f, \"V_Batt\": %.3f, \"I_Vbus\": %.1f, \"V_Vbus\": %.3f, \"T_sys\": %.1f}", + i_batt, pmu->getBattVoltage() / 1000., + pmu->getVbusCurrent(), pmu->getVbusVoltage() / 1000., + pmu->getTemperature()); + + char topic[128]; + snprintf(topic, sizeof(topic), "%s%s", sonde.config.mqtt.prefix, "pmu"); + mqttClient.publish(topic, 1, 1, payload); +} + + +void MQTT::publishGps() +{ + if((sonde.config.gps_rxd==-1) || !mqttGate(MQTT_SEND_GPS)) + return; + + char payload[256]; + snprintf(payload, sizeof(payload), + "{\"valid\": %1d, \"systime\": \"%s\", \"gpstime\": \"%s\", " + "\"lat\": %f, \"lon\": %f, \"alt\": %d, " + "\"course\":%d, \"speed\":%.1f, \"sat\": %d}", + gpsPos.valid, time_str, gpsPos.time, + gpsPos.lat, gpsPos.lon, gpsPos.alt, + gpsPos.course, gpsPos.speed, gpsPos.sat + ); + + char topic[128]; + snprintf(topic, sizeof(topic), "%s%s", sonde.config.mqtt.prefix, "gps"); + mqttClient.publish(topic, 1, 1, payload); +} + + +void MQTT::publishPeak(double pf, int rssi) +{ + if(!mqttGate(MQTT_SEND_RFINFO)) + return; + + timeFormat(); + char payload[256]; + snprintf(payload, 256, "{\"time\": \"%s\". \"peak\": %.3f, \"rssi\": %.1f}",time_str, pf*1e-6, rssi/2.0); + char topic[128]; + snprintf(topic, sizeof(topic), "%s%s", sonde.config.mqtt.prefix, "spectrum"); + mqttClient.publish(topic, 1, /* retain */ false, payload); +} + +// What's the scanner looking at? +void MQTT::publishQRG(int num, const char* type, char* launchsite, float mhz) +{ + if(!mqttGate(MQTT_SEND_RFINFO)) + return; + + char payload[256]; + snprintf( + payload, 256, + "{\"num\": %d, \"type\": \"%s\", \"site\": \"%s\", \"freq\": %.3f}", + num, type, launchsite, mhz); + char topic[128]; + snprintf(topic, sizeof(topic), "%s%s", sonde.config.mqtt.prefix, "qrg"); + mqttClient.publish(topic, 1, /*retain*/ false, payload); +} + + +// think "syslog over mqtt" +void MQTT::publishDebug(char *debugmsg) +{ + if(!mqttGate(MQTT_SEND_DEBUG)) + return; + + char payload[256]; + snprintf(payload, 256, "{\"msg\": %s}", debugmsg); + char topic[128]; + snprintf(topic, sizeof(topic), "%s%s", sonde.config.mqtt.prefix, "debug"); + mqttClient.publish(topic, 1, /*retain*/ false, payload); +} + void MQTT::publishPacket(SondeInfo *si) { SondeData *s = &(si->d); // ensure we've got connection - if(!connectToMqtt()) + if(!mqttGate(MQTT_SEND_UPTIME)) return; char payload[1024]; diff --git a/RX_FSK/src/conn-mqtt.h b/RX_FSK/src/conn-mqtt.h index 6fad786..b1f5f69 100644 --- a/RX_FSK/src/conn-mqtt.h +++ b/RX_FSK/src/conn-mqtt.h @@ -10,6 +10,14 @@ //#include "RS41.h" #include "conn.h" +#define MQTT_SEND_SONDE 0x01 +#define MQTT_SEND_UPTIME 0x02 +#define MQTT_SEND_PMU 0x04 +#define MQTT_SEND_GPS 0x08 +#define MQTT_SEND_RFINFO 0x10 +#define MQTT_SEND_DEBUG 0x80 +#define MQTT_SEND_ANY (MQTT_SEND_UPTIME|MQTT_SEND_SONDE|MQTT_SEND_PMU|MQTT_SEND_GPS|MQTT_SEND_RFINFO|MQTT_SEND_DEBUG) + class MQTT : public Conn { public: @@ -28,28 +36,39 @@ public: /* Called approx 1x / second* */ void updateStation( PosInfo *pi ); + /* Say whether MQTT is connected, disconnected, or even enabled */ String getStatus(); String getName(); -private: - WiFiClient mqttWifiClient; - AsyncMqttClient mqttClient; - TimerHandle_t mqttReconnectTimer; - IPAddress ip; - //uint16_t port; - //const char *username; - //const char *password; - //const char *prefix; - char clientID[21]; + /* Radio debug - spectrum and scanner*/ + void publishPeak(double pf, int rssi); + void publishQRG(int num, const char* type, char* launchsite, float mhz); + void publishDebug(char* debugmsg); - //void init(const char *host, uint16_t port, const char *id, const char *username, const char *password, const char *prefix); - void publishPacket(SondeInfo *s); - void publishUptime(); - int connectToMqtt(); + private: + WiFiClient mqttWifiClient; + AsyncMqttClient mqttClient; + TimerHandle_t mqttReconnectTimer; + IPAddress ip; + // uint16_t port; + // const char *username; + // const char *password; + // const char *prefix; + char clientID[21]; - unsigned long lastMqttUptime = 0; - boolean mqttEnabled; + // void init(const char *host, uint16_t port, const char *id, const char + // *username, const char *password, const char *prefix); + void publishPacket(SondeInfo* s); + void publishUptime(); + void publishPmuInfo(); + void publishGps(); + void timeFormat(); + int mqttGate(uint flag); + int connectToMqtt(); + + unsigned long lastMqttUptime = 0; + boolean mqttEnabled; }; extern MQTT connMQTT; diff --git a/RX_FSK/src/posinfo.cpp b/RX_FSK/src/posinfo.cpp index 7486fac..963d929 100644 --- a/RX_FSK/src/posinfo.cpp +++ b/RX_FSK/src/posinfo.cpp @@ -171,6 +171,10 @@ void gpsTask(void *parameter) { gpsPos.hdop = nmea.getHDOP(); gpsPos.sat = nmea.getNumSatellites(); gpsPos.speed = nmea.getSpeed() / 1000.0 * 0.514444; // speed is in m/s nmea.getSpeed is in 0.001 knots + snprintf(gpsPos.time, sizeof(gpsPos.time), + "%04d-%02d-%02d %02d:%02d:%02d", nmea.getYear(), + nmea.getMonth(), nmea.getDay(), nmea.getHour(), + nmea.getMinute(), nmea.getSecond()); #ifdef DEBUG_GPS uint8_t hdop = nmea.getHDOP(); Serial.printf(" =>: valid: %d N %f E %f alt %d course:%d dop:%d\n", gpsPos.valid ? 1 : 0, gpsPos.lat, gpsPos.lon, gpsPos.alt, gpsPos.course, hdop); diff --git a/RX_FSK/src/posinfo.h b/RX_FSK/src/posinfo.h index 81172a9..d8b8832 100644 --- a/RX_FSK/src/posinfo.h +++ b/RX_FSK/src/posinfo.h @@ -23,6 +23,7 @@ struct StationPos { int8_t sat; int8_t valid; int8_t chase; + char time[20]; }; extern struct StationPos gpsPos, posInfo; diff --git a/RX_FSK/version.h b/RX_FSK/version.h index 3802bef..4b57d75 100644 --- a/RX_FSK/version.h +++ b/RX_FSK/version.h @@ -1,4 +1,4 @@ const char *version_name = "rdzTTGOsonde"; const char *version_id = "dev20241222"; const int FS_MAJOR=3; -const int FS_MINOR=3; +const int FS_MINOR=4; \ No newline at end of file