diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index aafe780b..bc0045f1 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -15,7 +15,15 @@
+
+
+
+
+
+
+
+
@@ -47,6 +55,11 @@
+
+
+
+
+
@@ -60,9 +73,9 @@
-
+
-
+
@@ -98,6 +111,7 @@
+
@@ -129,6 +143,11 @@
37
+
+ file://$USER_HOME$/.platformio/packages/framework-portduino/libraries/WiFi/src/WiFiClient.cpp
+ 49
+
+
diff --git a/bin/mqtt-listen.sh b/bin/mqtt-listen.sh
new file mode 100755
index 00000000..ed2d2272
--- /dev/null
+++ b/bin/mqtt-listen.sh
@@ -0,0 +1 @@
+mosquitto_sub -h test.mosquitto.org -v -t mstat/\# -t mesh/\#
diff --git a/bin/mqtt-send-status.sh b/bin/mqtt-send-status.sh
new file mode 100755
index 00000000..107c3b10
--- /dev/null
+++ b/bin/mqtt-send-status.sh
@@ -0,0 +1 @@
+mosquitto_pub -h test.mosquitto.org -t mstat/FakeNode -m online -d
diff --git a/docs/software/TODO.md b/docs/software/TODO.md
index 025f14b8..015f4760 100644
--- a/docs/software/TODO.md
+++ b/docs/software/TODO.md
@@ -53,11 +53,22 @@ You probably don't care about this section - skip to the next one.
## MQTT
+* DONE have sim provide a fake wifi connection status saying connected
+* DONE don't start MQTT if we don't have wifi connected
* mqtt.meshtastic.org should have VERY basic auth at launch (to prevent abuse)
-* use MQTT for simulator mesh network
+* make a GlobalChat channel as an initial test (with a well known AES128 key), figure out how globally unique IDs work
+* Give place in android app for users to select which channel they are sending on (and which channels they are watching)
+* attempt reconnect to server if internet connectivity changes
+* don't bother contacting server if we don't have any uplink/downlink channels
+* test on ESP32
+* no need for python gateway to web initially: because both the simulator and ESP32 can talk wifi directly
+* if simmesh_name is set in preferences, create the MQTTSimInterface using that as the global channel_id
+* figure out how to use MQTT for simulator mesh network, use a special simmesh_name global channel_id? (because this would allow all nodes in simnet_xxx to subscribe only to those packets)
* do initial development inside of portduino
* do as much possible on the device side (so we can eventually just have ESP32 talk directly to server)
+* add mqtt_server to radio prefs
* eventually add a MQTTPacket on the ToRadio & FromRadio links
+* LATER: an android gateway would be useful
## Multichannel support
diff --git a/docs/software/mqtt.md b/docs/software/mqtt.md
index 0c96cb71..df6a2134 100644
--- a/docs/software/mqtt.md
+++ b/docs/software/mqtt.md
@@ -12,7 +12,9 @@
- [NODEID](#nodeid)
- [USERID](#userid)
- [CHANNELID](#channelid)
+ - [PORTID](#portid)
- [Gateway nodes](#gateway-nodes)
+ - [MQTTSimInterface](#mqttsiminterface)
- [Optional web services](#optional-web-services)
- [Public MQTT broker service](#public-mqtt-broker-service)
- [Riot.im messaging bridge](#riotim-messaging-bridge)
@@ -68,17 +70,19 @@ Any gateway-device will contact the MQTT broker.
### Topics
-The "mesh/crypt/CHANNELID/NODEID/PORTID" [topic](https://www.hivemq.com/blog/mqtt-essentials-part-5-mqtt-topics-best-practices/) will be used for messages sent from/to a mesh.
+* The "/mesh/crypt/CHANNELID/NODEID/PORTID" [topic](https://www.hivemq.com/blog/mqtt-essentials-part-5-mqtt-topics-best-practices/) will be used for messages sent from/to a mesh.
Gateway nodes will foward any MeshPacket from a local mesh channel with uplink_enabled. The packet (encapsulated in a ServiceEnvelope) will remain encrypted with the key for the specified channel.
-For any channels in the local node with downlink_enabled, the gateway node will forward packets from MQTT to the local mesh. It will do this by subscribing to mesh/crypt/CHANNELID/# and forwarding relevant packets.
+For any channels in the gateway node with downlink_enabled, the gateway node will forward packets from MQTT to the local mesh. It will do this by subscribing to mesh/crypt/CHANNELID/# and forwarding relevant packets.
If the channelid 'well known'/public it could be decrypted by a web service (if the web service was provided with the associated channel key), in which case it will be decrypted by a web service and appear at "mesh/clear/CHANNELID/NODEID/PORTID". Note: This is not in the initial deliverable.
-FIXME, discuss how text message global mirroring could scale (or not)
+FIXME, consider how text message global mirroring could scale (or not)
FIXME, possibly don't global mirror text messages - instead rely on matrix/riot?
-FIXME, discuss possible attacks by griefers and how they can be prvented
+FIXME, consider possible attacks by griefers and how they can be prvented
+
+* The "/mstat/NODEID" topic contains a simple string showing connection status of nodes. We rely on the MQTT feature for automatically publishing special failrue messages to this topic when the device disconnects.
#### Service Envelope
@@ -88,7 +92,7 @@ ServiceEnvelope will include the message, and full information about arrival tim
#### NODEID
-The unique ID for a node. A hex string that starts with a ! symbol.
+The unique ID for a node. A 8 byte (16 character) hex string that starts with a ! symbol.
#### USERID
@@ -96,7 +100,15 @@ A user ID string. This string is either a user ID if known or a nodeid to simply
#### CHANNELID
-FIXME, figure out how channelids work
+For the time being we simply use the local "channel name" - which is not quite good enough.
+
+FIXME, figure out how channelids work in more detail. They should generally be globally unique, but this is not a requirement. If someone accidentially (or maliciously) sends data using a channel ID they do not 'own' they will still lacking a valid AES256 encryption, so it will be ignored by others.
+
+idea to be pondered: When the user clicks to enable uplink/downlink check the name they entered and 'claim' it on the server?
+
+#### PORTID
+
+Portid is used to descriminated between different packet types which are sent over a channel. As used here it is an integer typically (but not necessarily) chosen from portnums.proto.
### Gateway nodes
@@ -107,6 +119,17 @@ Gateway nodes (via code running in the phone) will contain two tables to whiteli
Since multiple gateway nodes might be connected to a single mesh, it is possible that duplicate messages will be published on any particular topic. Therefore subscribers to these topics should
deduplicate if needed by using the packet ID of each message.
+
+### MQTTSimInterface
+
+This is a bit orthogonal from the main MQTT feature set, but a special simulated LoRa interface called MQTTSimInterface uses the
+MQTT messaging infrastructure to send "LoRa" packets between simulated nodes running on Linux. This allows us to test radio topologies and code without having to use real hardware.
+
+This service uses the standard mesh/crypt/... topic, but it picks a special CHANNEL_ID. That CHANNEL_ID is typcially of the form "simmesh_xxx".
+
+FIXME: Figure out how to secure the creation and use of well known CHANNEL_IDs.
+
+
### Optional web services
#### Public MQTT broker service
diff --git a/platformio.ini b/platformio.ini
index 12f90112..588231b1 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -9,7 +9,7 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
-default_envs = tbeam
+;default_envs = tbeam
;default_envs = tbeam0.7
;default_envs = heltec
;default_envs = tlora-v1
@@ -18,7 +18,7 @@ default_envs = tbeam
;default_envs = lora-relay-v1 # nrf board
;default_envs = eink
;default_envs = nrf52840dk-geeksville
-;default_envs = native # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
+default_envs = native # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
[common]
; common is not currently used
@@ -76,6 +76,7 @@ lib_deps =
Wire ; explicitly needed here because the AXP202 library forgets to add it
SPI
https://github.com/geeksville/ArduinoThread.git#72921ac222eed6f526ba1682023cee290d9aa1b3
+ PubSubClient
; Common settings for conventional (non Portduino) Ardino targets
[arduino_base]
@@ -202,7 +203,7 @@ build_flags =
-Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.7
;-DCFG_DEBUG=3
src_filter =
- ${arduino_base.src_filter} - - - - -
+ ${arduino_base.src_filter} - - - - - -
lib_ignore =
BluetoothOTA
monitor_port = /dev/ttyACM1
diff --git a/proto b/proto
index 2aa14392..9c16118e 160000
--- a/proto
+++ b/proto
@@ -1 +1 @@
-Subproject commit 2aa14392145c38476bf61fea2c43629e1973af6d
+Subproject commit 9c16118e59e7318cd8cb92199511c19232939f5f
diff --git a/src/main.cpp b/src/main.cpp
index 25e5e15c..c2dd0b21 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -32,8 +32,9 @@
#include "nimble/BluetoothUtil.h"
#endif
-#ifdef PORTDUINO
+#if defined(HAS_WIFI) || defined(PORTDUINO)
#include "mesh/wifi/WiFiServerAPI.h"
+#include "mqtt/MQTT.h"
#endif
#include "RF95Interface.h"
@@ -541,6 +542,10 @@ void setup()
initApiServer();
#endif
+#if defined(PORTDUINO) || defined(HAS_WIFI)
+ mqttInit();
+#endif
+
// Start airtime logger thread.
airTime = new AirTime();
diff --git a/src/mesh/generated/admin.pb.h b/src/mesh/generated/admin.pb.h
index 1096a9ad..5ac5bcc1 100644
--- a/src/mesh/generated/admin.pb.h
+++ b/src/mesh/generated/admin.pb.h
@@ -79,7 +79,7 @@ extern const pb_msgdesc_t AdminMessage_msg;
#define AdminMessage_fields &AdminMessage_msg
/* Maximum encoded size of messages (where known) */
-#define AdminMessage_size 360
+#define AdminMessage_size 397
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/generated/apponly.pb.c b/src/mesh/generated/apponly.pb.c
index d6f17d7f..bebc8b75 100644
--- a/src/mesh/generated/apponly.pb.c
+++ b/src/mesh/generated/apponly.pb.c
@@ -6,9 +6,6 @@
#error Regenerate this file with the current version of nanopb generator.
#endif
-PB_BIND(ServiceEnvelope, ServiceEnvelope, 2)
-
-
PB_BIND(ChannelSet, ChannelSet, AUTO)
diff --git a/src/mesh/generated/apponly.pb.h b/src/mesh/generated/apponly.pb.h
index 89fe235d..f1421b5e 100644
--- a/src/mesh/generated/apponly.pb.h
+++ b/src/mesh/generated/apponly.pb.h
@@ -4,7 +4,6 @@
#ifndef PB_APPONLY_PB_H_INCLUDED
#define PB_APPONLY_PB_H_INCLUDED
#include
-#include "mesh.pb.h"
#include "channel.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
@@ -16,54 +15,31 @@ typedef struct _ChannelSet {
pb_callback_t settings;
} ChannelSet;
-typedef struct _ServiceEnvelope {
- bool has_packet;
- MeshPacket packet;
- pb_callback_t channel_id;
- pb_callback_t gateway_id;
-} ServiceEnvelope;
-
#ifdef __cplusplus
extern "C" {
#endif
/* Initializer values for message structs */
-#define ServiceEnvelope_init_default {false, MeshPacket_init_default, {{NULL}, NULL}, {{NULL}, NULL}}
#define ChannelSet_init_default {{{NULL}, NULL}}
-#define ServiceEnvelope_init_zero {false, MeshPacket_init_zero, {{NULL}, NULL}, {{NULL}, NULL}}
#define ChannelSet_init_zero {{{NULL}, NULL}}
/* Field tags (for use in manual encoding/decoding) */
#define ChannelSet_settings_tag 1
-#define ServiceEnvelope_packet_tag 1
-#define ServiceEnvelope_channel_id_tag 2
-#define ServiceEnvelope_gateway_id_tag 3
/* Struct field encoding specification for nanopb */
-#define ServiceEnvelope_FIELDLIST(X, a) \
-X(a, STATIC, OPTIONAL, MESSAGE, packet, 1) \
-X(a, CALLBACK, SINGULAR, STRING, channel_id, 2) \
-X(a, CALLBACK, SINGULAR, STRING, gateway_id, 3)
-#define ServiceEnvelope_CALLBACK pb_default_field_callback
-#define ServiceEnvelope_DEFAULT NULL
-#define ServiceEnvelope_packet_MSGTYPE MeshPacket
-
#define ChannelSet_FIELDLIST(X, a) \
X(a, CALLBACK, REPEATED, MESSAGE, settings, 1)
#define ChannelSet_CALLBACK pb_default_field_callback
#define ChannelSet_DEFAULT NULL
#define ChannelSet_settings_MSGTYPE ChannelSettings
-extern const pb_msgdesc_t ServiceEnvelope_msg;
extern const pb_msgdesc_t ChannelSet_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
-#define ServiceEnvelope_fields &ServiceEnvelope_msg
#define ChannelSet_fields &ChannelSet_msg
/* Maximum encoded size of messages (where known) */
-/* ServiceEnvelope_size depends on runtime parameters */
/* ChannelSet_size depends on runtime parameters */
#ifdef __cplusplus
diff --git a/src/mesh/generated/mqtt.pb.c b/src/mesh/generated/mqtt.pb.c
new file mode 100644
index 00000000..a93584c3
--- /dev/null
+++ b/src/mesh/generated/mqtt.pb.c
@@ -0,0 +1,12 @@
+/* Automatically generated nanopb constant definitions */
+/* Generated by nanopb-0.4.4 */
+
+#include "mqtt.pb.h"
+#if PB_PROTO_HEADER_VERSION != 40
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+PB_BIND(ServiceEnvelope, ServiceEnvelope, AUTO)
+
+
+
diff --git a/src/mesh/generated/mqtt.pb.h b/src/mesh/generated/mqtt.pb.h
new file mode 100644
index 00000000..a8f55470
--- /dev/null
+++ b/src/mesh/generated/mqtt.pb.h
@@ -0,0 +1,55 @@
+/* Automatically generated nanopb header */
+/* Generated by nanopb-0.4.4 */
+
+#ifndef PB_MQTT_PB_H_INCLUDED
+#define PB_MQTT_PB_H_INCLUDED
+#include
+#include "mesh.pb.h"
+
+#if PB_PROTO_HEADER_VERSION != 40
+#error Regenerate this file with the current version of nanopb generator.
+#endif
+
+/* Struct definitions */
+typedef struct _ServiceEnvelope {
+ struct _MeshPacket *packet;
+ char *channel_id;
+ char *gateway_id;
+} ServiceEnvelope;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Initializer values for message structs */
+#define ServiceEnvelope_init_default {NULL, NULL, NULL}
+#define ServiceEnvelope_init_zero {NULL, NULL, NULL}
+
+/* Field tags (for use in manual encoding/decoding) */
+#define ServiceEnvelope_packet_tag 1
+#define ServiceEnvelope_channel_id_tag 2
+#define ServiceEnvelope_gateway_id_tag 3
+
+/* Struct field encoding specification for nanopb */
+#define ServiceEnvelope_FIELDLIST(X, a) \
+X(a, POINTER, OPTIONAL, MESSAGE, packet, 1) \
+X(a, POINTER, SINGULAR, STRING, channel_id, 2) \
+X(a, POINTER, SINGULAR, STRING, gateway_id, 3)
+#define ServiceEnvelope_CALLBACK NULL
+#define ServiceEnvelope_DEFAULT NULL
+#define ServiceEnvelope_packet_MSGTYPE MeshPacket
+
+extern const pb_msgdesc_t ServiceEnvelope_msg;
+
+/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
+#define ServiceEnvelope_fields &ServiceEnvelope_msg
+
+/* Maximum encoded size of messages (where known) */
+/* ServiceEnvelope_size depends on runtime parameters */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/src/mesh/generated/radioconfig.pb.h b/src/mesh/generated/radioconfig.pb.h
index 51d4d71e..afccd937 100644
--- a/src/mesh/generated/radioconfig.pb.h
+++ b/src/mesh/generated/radioconfig.pb.h
@@ -87,6 +87,8 @@ typedef struct _RadioConfig_UserPreferences {
bool fixed_position;
bool serial_disabled;
float frequency_offset;
+ char mqtt_server[32];
+ bool mqtt_disabled;
bool factory_reset;
bool debug_log_enabled;
pb_size_t ignore_incoming_count;
@@ -152,9 +154,9 @@ extern "C" {
/* Initializer values for message structs */
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default}
-#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
+#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, "", 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero}
-#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
+#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, "", 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
/* Field tags (for use in manual encoding/decoding) */
#define RadioConfig_UserPreferences_position_broadcast_secs_tag 1
@@ -181,6 +183,8 @@ extern "C" {
#define RadioConfig_UserPreferences_fixed_position_tag 39
#define RadioConfig_UserPreferences_serial_disabled_tag 40
#define RadioConfig_UserPreferences_frequency_offset_tag 41
+#define RadioConfig_UserPreferences_mqtt_server_tag 42
+#define RadioConfig_UserPreferences_mqtt_disabled_tag 43
#define RadioConfig_UserPreferences_factory_reset_tag 100
#define RadioConfig_UserPreferences_debug_log_enabled_tag 101
#define RadioConfig_UserPreferences_ignore_incoming_tag 103
@@ -243,6 +247,8 @@ X(a, STATIC, SINGULAR, BOOL, is_low_power, 38) \
X(a, STATIC, SINGULAR, BOOL, fixed_position, 39) \
X(a, STATIC, SINGULAR, BOOL, serial_disabled, 40) \
X(a, STATIC, SINGULAR, FLOAT, frequency_offset, 41) \
+X(a, STATIC, SINGULAR, STRING, mqtt_server, 42) \
+X(a, STATIC, SINGULAR, BOOL, mqtt_disabled, 43) \
X(a, STATIC, SINGULAR, BOOL, factory_reset, 100) \
X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 101) \
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \
@@ -282,8 +288,8 @@ extern const pb_msgdesc_t RadioConfig_UserPreferences_msg;
#define RadioConfig_UserPreferences_fields &RadioConfig_UserPreferences_msg
/* Maximum encoded size of messages (where known) */
-#define RadioConfig_size 357
-#define RadioConfig_UserPreferences_size 354
+#define RadioConfig_size 394
+#define RadioConfig_UserPreferences_size 391
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
new file mode 100644
index 00000000..377b6078
--- /dev/null
+++ b/src/mqtt/MQTT.cpp
@@ -0,0 +1,76 @@
+#include "MQTT.h"
+#include "NodeDB.h"
+#include
+#include
+
+MQTT *mqtt;
+
+String statusTopic = "mstat/";
+String packetTopic = "mesh/";
+
+void mqttCallback(char *topic, byte *payload, unsigned int length)
+{
+ DEBUG_MSG("MQTT topic %s\n", topic);
+
+ // After parsing ServiceEnvelope
+ // FIXME - make sure to free both strings and the MeshPacket
+}
+
+void mqttInit()
+{
+ // FIXME, for now we require the user to specifically set a MQTT server (till tested)
+ if (radioConfig.preferences.mqtt_disabled || !*radioConfig.preferences.mqtt_server)
+ DEBUG_MSG("MQTT disabled...\n");
+ else if (!WiFi.isConnected())
+ DEBUG_MSG("WiFi is not connected, can not start MQTT\n");
+ else
+ new MQTT();
+}
+
+MQTT::MQTT() : pubSub(mqttClient)
+{
+ assert(!mqtt);
+ mqtt = this;
+
+ // pubSub.setServer("devsrv.ezdevice.net", 1883); or 192.168.10.188
+ const char *serverAddr = "test.mosquitto.org"; // "mqtt.meshtastic.org"; // default hostname
+
+ if (*radioConfig.preferences.mqtt_server)
+ serverAddr = radioConfig.preferences.mqtt_server; // Override the default
+
+ pubSub.setServer(serverAddr, 1883);
+ pubSub.setCallback(mqttCallback);
+
+ DEBUG_MSG("Connecting to MQTT server: %s\n", serverAddr);
+ auto myStatus = (statusTopic + nodeId);
+ // bool connected = pubSub.connect(nodeId.c_str(), "meshdev", "apes4cats", myStatus.c_str(), 1, true, "offline");
+ bool connected = pubSub.connect(nodeId.c_str(), myStatus.c_str(), 1, true, "offline");
+ if (connected) {
+ DEBUG_MSG("MQTT connected\n");
+
+ static char subsStr[64]; /* We keep this static because the mqtt lib
+ might not be copying it */
+ // snprintf(subsStr, sizeof(subsStr), "/ezd/todev/%s/#", clientId);
+ // mqtt.subscribe(subsStr, 1); // we use qos 1 because we don't want to miss messages
+
+ /// FIXME, include more information in the status text
+ bool ok = pubSub.publish(myStatus.c_str(), "online", true);
+ DEBUG_MSG("published %d\n", ok);
+ }
+}
+
+void MQTT::publish(const MeshPacket *mp, String channelId)
+{
+ // DEBUG_MSG("publish %s = %s\n", suffix.c_str(), payload.c_str());
+
+ // pubSub.publish(getTopic(suffix), payload.c_str(), retained);
+}
+
+const char *MQTT::getTopic(String suffix, const char *direction)
+{
+ static char buf[128];
+
+ // "mesh/crypt/CHANNELID/NODEID/PORTID"
+ snprintf(buf, sizeof(buf), "mesh/%s/%s/%s", direction, nodeId.c_str(), suffix.c_str());
+ return buf;
+}
\ No newline at end of file
diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h
new file mode 100644
index 00000000..97412d2b
--- /dev/null
+++ b/src/mqtt/MQTT.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "configuration.h"
+
+#include
+#include
+
+/**
+ * Our wrapper/singleton for sending/receiving MQTT "udp" packets. This object isolates the MQTT protocol implementation from
+ * the two components that use it: MQTTPlugin and MQTTSimInterface.
+ */
+class MQTT
+{
+ /// Our globally unique node ID
+ String nodeId = "fixmemode";
+
+ // supposedly the current version is busted:
+ // http://www.iotsharing.com/2017/08/how-to-use-esp32-mqtts-with-mqtts-mosquitto-broker-tls-ssl.html
+ // WiFiClientSecure wifiClient;
+ WiFiClient mqttClient;
+ PubSubClient pubSub;
+
+ public:
+ MQTT();
+
+ /**
+ * Publish a packet on the glboal MQTT server.
+ * @param channelId must be a globally unique channel ID
+ */
+ void publish(const MeshPacket *mp, String channelId);
+
+ private:
+ const char *getTopic(String suffix, const char *direction = "dev");
+};
+
+void mqttInit();
+
+extern MQTT *mqtt;