log GPS, PMU, Spectrum peak, and Scanner via MQTT
The aim is to be very similar to what's on the display, but for remote deployments. Messages can be controlled by OR-ing values together in the mqtt_active config element. * sondes = 0x01 * uptime = 0x02 * pmu = 0x04 * gps = 0x08 * RF info (scanner and spectrum) = 0x10 * generic debug = 0x80 Using a value of 1 will send only sonde measurements over MQTT since they are most interesting; a value of 3 replicates the current behavior sending both uptime and sondes. There is also a control adjust the reporting rate of uptime, PMU, and gps messages. PMU messages use positive current to indicate battery charging and negative current to indicate discharging. GPS messages include both host and GPS time, and fix state, without regard to fixed position. `cfg.js` has been updated to add the new controls, and version.h updates the filesystem minor version to reflect that change Future improvements may include: * hoisting the time formatter to make it useful for all other time printers * TLS support * remote control over MQTT * reporting position source in the uptime message (fixed or gps) * replacing Serial.print and .println with .printf or LOG_x
This commit is contained in:
parent
542e496d37
commit
39e4585021
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ mqtt.port=1883
|
|||
mqtt.username=
|
||||
mqtt.password=
|
||||
mqtt.prefix=rdz_sonde_server/
|
||||
mqtt.report_interval=60000
|
||||
#-------------------------------#
|
||||
# Sondehub v2 settings
|
||||
#-------------------------------#
|
||||
|
|
|
|||
|
|
@ -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; i<scanconfig.PLOT_W; i+=1) {
|
||||
int r=scanresult[i*scanconfig.SMPL_PIX];
|
||||
if(r>peakres+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<scanconfig.SMPL_PIX; j++) {
|
||||
r = scanresult[i*scanconfig.SMPL_PIX+j];
|
||||
|
|
@ -200,8 +201,10 @@ void Scanner::scan()
|
|||
Serial.print(scandisp[i]); Serial.print(", ");
|
||||
}
|
||||
Serial.println("\n");
|
||||
Serial.print("Peak: ");
|
||||
Serial.print(peakf);
|
||||
Serial.printf("Peak: %f rssi %d\n", peakf, peakres);
|
||||
#if FEATURE_MQTT
|
||||
connMQTT.publishPeak(peakf, peakres);
|
||||
#endif
|
||||
}
|
||||
|
||||
Scanner scanner = Scanner();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include "SX1278FSK.h"
|
||||
#include "Display.h"
|
||||
#include <Wire.h>
|
||||
#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;
|
||||
|
|
|
|||
|
|
@ -220,6 +220,7 @@ struct st_mqtt {
|
|||
char username[64];
|
||||
char password[64];
|
||||
char prefix[64];
|
||||
int report_interval;
|
||||
};
|
||||
|
||||
struct st_cm {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,13 @@
|
|||
#include <WiFi.h>
|
||||
#include <AsyncMqttClient.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <time.h>
|
||||
#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;i<length;i++) {
|
||||
Serial.print((char)payload[i]);
|
||||
}
|
||||
|
|
@ -46,22 +50,24 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) {
|
|||
|
||||
/* Network initialization (as soon as network becomes available) */
|
||||
void MQTT::netsetup() {
|
||||
if (!sonde.config.mqtt.active)
|
||||
if (0 == sonde.config.mqtt.active)
|
||||
return;
|
||||
if (strlen(sonde.config.mqtt.host)==0)
|
||||
return;
|
||||
|
||||
if (sonde.config.mqtt.report_interval <1000)
|
||||
sonde.config.mqtt.report_interval = 60000;
|
||||
WiFi.hostByName(sonde.config.mqtt.host, this->ip);
|
||||
|
||||
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];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ struct StationPos {
|
|||
int8_t sat;
|
||||
int8_t valid;
|
||||
int8_t chase;
|
||||
char time[20];
|
||||
};
|
||||
|
||||
extern struct StationPos gpsPos, posInfo;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
Loading…
Reference in New Issue