Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SSL support for MQTT #285

Open
wants to merge 4 commits into
base: 1.8.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions dist/index.html.gz.h

Large diffs are not rendered by default.

102 changes: 65 additions & 37 deletions lib/MQTT/MqttClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <WiFiClient.h>
#include <MiLightRadioConfig.h>

using namespace std::placeholders;

MqttClient::MqttClient(Settings& settings, MiLightClient*& milightClient)
: milightClient(milightClient),
settings(settings),
Expand All @@ -16,15 +18,18 @@ MqttClient::MqttClient(Settings& settings, MiLightClient*& milightClient)
this->domain = new char[strDomain.length() + 1];
strcpy(this->domain, strDomain.c_str());

this->mqttClient = new PubSubClient(tcpClient);
}

MqttClient::~MqttClient() {
mqttClient->disconnect();
mqttClient.disconnect();
delete this->domain;
}

void MqttClient::begin() {
#if ASYNC_TCP_SSL_ENABLED
char hexVal[2];
uint8_t fingerprint[SHA1_SIZE];
#endif
#ifdef MQTT_DEBUG
printf_P(
PSTR("MqttClient - Connecting to: %s\nparsed:%s:%u\n"),
Expand All @@ -33,58 +38,65 @@ void MqttClient::begin() {
settings.mqttPort()
);
#endif

mqttClient->setServer(this->domain, settings.mqttPort());
mqttClient->setCallback(
[this](char* topic, byte* payload, int length) {
this->publishCallback(topic, payload, length);
mqttmsg = false;
mqttClient.setServer(this->domain, settings.mqttPort());
mqttClient.onMessage(std::bind(&MqttClient::publishCallback, this, _1, _2, _3, _4, _5, _6));
mqttClient.onConnect(std::bind(&MqttClient::connectCallback, this, _1));
#if ASYNC_TCP_SSL_ENABLED
mqttClient.setSecure(settings.mqttSecure);
if(settings.mqttSecure && settings.mqttServerFingerprint.length() == (SHA1_SIZE * 2))
{
// Convert string to uint8_t[]
for(size_t i=0;i<settings.mqttServerFingerprint.length();i+=2)
{
hexVal[0] = settings.mqttServerFingerprint[i];
hexVal[1] = settings.mqttServerFingerprint[i+1];
fingerprint[i/2]=strtol(hexVal,NULL,16);
}
);
mqttClient.addServerFingerprint(fingerprint);
}
#endif
if (settings.mqttUsername.length() > 0) {
mqttClient.setCredentials(settings.mqttUsername.c_str(), settings.mqttPassword.c_str());
}

reconnect();
}

bool MqttClient::connect() {
void MqttClient::connect() {
char nameBuffer[30];
sprintf_P(nameBuffer, PSTR("milight-hub-%u"), ESP.getChipId());

#ifdef MQTT_DEBUG
Serial.println(F("MqttClient - connecting"));
#endif

if (settings.mqttUsername.length() > 0) {
return mqttClient->connect(
nameBuffer,
settings.mqttUsername.c_str(),
settings.mqttPassword.c_str()
);
} else {
return mqttClient->connect(nameBuffer);
}
mqttClient.connect();

}

void MqttClient::reconnect() {
if (lastConnectAttempt > 0 && (millis() - lastConnectAttempt) < MQTT_CONNECTION_ATTEMPT_FREQUENCY) {
return;
}

if (! mqttClient->connected()) {
if (connect()) {
subscribe();

#ifdef MQTT_DEBUG
Serial.println(F("MqttClient - Successfully connected to MQTT server"));
#endif
} else {
Serial.println(F("ERROR: Failed to connect to MQTT server"));
}
if (! mqttClient.connected()) {
connect();
}

lastConnectAttempt = millis();
}
void MqttClient::connectCallback(bool sessionPresent)
{
subscribe();

#ifdef MQTT_DEBUG
Serial.println(F("MqttClient - Successfully connected to MQTT server"));
#endif
}
void MqttClient::handleClient() {
reconnect();
mqttClient->loop();
if(mqttmsg) publishLoop();
}

void MqttClient::sendUpdate(const MiLightRemoteConfig& remoteConfig, uint16_t deviceId, uint16_t groupId, const char* update) {
Expand All @@ -106,7 +118,7 @@ void MqttClient::subscribe() {
printf_P(PSTR("MqttClient - subscribing to topic: %s\n"), topic.c_str());
#endif

mqttClient->subscribe(topic.c_str());
mqttClient.subscribe(topic.c_str(), 0);
}

void MqttClient::publish(
Expand All @@ -128,26 +140,40 @@ void MqttClient::publish(
printf("MqttClient - publishing update to %s\n", topic.c_str());
#endif

mqttClient->publish(topic.c_str(), message, retain);
mqttClient.publish(topic.c_str(), 0, retain, message);
}

void MqttClient::publishCallback(char* topic, byte* payload, int length) {
void MqttClient::publishCallback(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t length, size_t index, size_t total) {
if(!mqttmsg) // For now irgnore new message while process last message.
{
strncpy(mqtttopic,topic,strlen(topic));
mqtttopic[strlen(topic)] = '\0';
strncpy(mqttpayload,payload,length);
mqttpayload[length] = '\0';

mqttmsg = true;
}
}

void MqttClient::publishLoop(){
uint16_t deviceId = 0;
uint8_t groupId = 0;
const MiLightRemoteConfig* config = &FUT092Config;
size_t length = strlen(mqttpayload);
char cstrPayload[length + 1];
cstrPayload[length] = 0;
memcpy(cstrPayload, payload, sizeof(byte)*length);

strncpy(cstrPayload, mqttpayload, sizeof cstrPayload - 1);
cstrPayload[length] = '\0';

#ifdef MQTT_DEBUG
printf("MqttClient - Got message on topic: %s\n%s\n", topic, cstrPayload);
printf("MqttClient - Got message on topic: %s, %s\n", mqtttopic, mqttpayload);
#endif

char topicPattern[settings.mqttTopicPattern.length()];
strcpy(topicPattern, settings.mqttTopicPattern.c_str());

TokenIterator patternIterator(topicPattern, settings.mqttTopicPattern.length(), '/');
TokenIterator topicIterator(topic, strlen(topic), '/');
TokenIterator topicIterator(mqtttopic, strlen(mqtttopic), '/');
UrlTokenBindings tokenBindings(patternIterator, topicIterator);

if (tokenBindings.hasBinding("device_id")) {
Expand All @@ -169,7 +195,7 @@ void MqttClient::publishCallback(char* topic, byte* payload, int length) {
Serial.println(F("MqttClient - WARNING: could not find device_type token. Defaulting to FUT092.\n"));
}

StaticJsonBuffer<400> buffer;
StaticJsonBuffer<MQTTBUFFER> buffer;
JsonObject& obj = buffer.parseObject(cstrPayload);

#ifdef MQTT_DEBUG
Expand All @@ -178,6 +204,8 @@ void MqttClient::publishCallback(char* topic, byte* payload, int length) {

milightClient->prepare(config, deviceId, groupId);
milightClient->update(obj);

mqttmsg = false;
}

inline void MqttClient::bindTopicString(
Expand Down
18 changes: 14 additions & 4 deletions lib/MQTT/MqttClient.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#include <MiLightClient.h>
#include <Settings.h>
#include <PubSubClient.h>
#include <AsyncMqttClient.h>
#include <WiFiClient.h>
#include <MiLightRadioConfig.h>
#include <atomic>

#ifndef MQTT_CONNECTION_ATTEMPT_FREQUENCY
#define MQTT_CONNECTION_ATTEMPT_FREQUENCY 5000
Expand All @@ -11,6 +12,10 @@
#ifndef _MQTT_CLIENT_H
#define _MQTT_CLIENT_H

#ifndef MQTTBUFFER
#define MQTTBUFFER 400
#endif

class MqttClient {
public:
MqttClient(Settings& settings, MiLightClient*& milightClient);
Expand All @@ -24,15 +29,20 @@ class MqttClient {

private:
WiFiClient tcpClient;
PubSubClient* mqttClient;
AsyncMqttClient mqttClient;
MiLightClient*& milightClient;
Settings& settings;
char* domain;
unsigned long lastConnectAttempt;
std::atomic<bool> mqttmsg;
char mqtttopic[MQTTBUFFER];
char mqttpayload[MQTTBUFFER];

bool connect();
void connect();
void subscribe();
void publishCallback(char* topic, byte* payload, int length);
void publishCallback(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t length, size_t index, size_t total);
void publishLoop(void);
void connectCallback(bool sessionPresent);
void publish(
const String& topic,
const MiLightRemoteConfig& remoteConfig,
Expand Down
4 changes: 4 additions & 0 deletions lib/Settings/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ void Settings::patch(JsonObject& parsedSettings) {
this->setIfPresent(parsedSettings, "mqtt_topic_pattern", mqttTopicPattern);
this->setIfPresent(parsedSettings, "mqtt_update_topic_pattern", mqttUpdateTopicPattern);
this->setIfPresent(parsedSettings, "mqtt_state_topic_pattern", mqttStateTopicPattern);
this->setIfPresent(parsedSettings, "mqtt_secure", mqttSecure);
this->setIfPresent(parsedSettings, "mqtt_server_fingerprint", mqttServerFingerprint);
this->setIfPresent(parsedSettings, "discovery_port", discoveryPort);
this->setIfPresent(parsedSettings, "listen_repeats", listenRepeats);
this->setIfPresent(parsedSettings, "state_flush_interval", stateFlushInterval);
Expand Down Expand Up @@ -192,6 +194,8 @@ void Settings::serialize(Stream& stream, const bool prettyPrint) {
root["mqtt_topic_pattern"] = this->mqttTopicPattern;
root["mqtt_update_topic_pattern"] = this->mqttUpdateTopicPattern;
root["mqtt_state_topic_pattern"] = this->mqttStateTopicPattern;
root["mqtt_secure"] = this->mqttSecure;
root["mqtt_server_fingerprint"] = this->mqttServerFingerprint;
root["discovery_port"] = this->discoveryPort;
root["listen_repeats"] = this->listenRepeats;
root["state_flush_interval"] = this->stateFlushInterval;
Expand Down
2 changes: 2 additions & 0 deletions lib/Settings/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ class Settings {
String mqttTopicPattern;
String mqttUpdateTopicPattern;
String mqttStateTopicPattern;
bool mqttSecure;
String mqttServerFingerprint;
GroupStateField *groupStateFields;
size_t numGroupStateFields;
uint16_t discoveryPort;
Expand Down
4 changes: 2 additions & 2 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ lib_deps_external =
; WiFiManager
https://github.com/cmidgley/WiFiManager
ArduinoJson
PubSubClient
AsyncMqttClient
https://github.com/ratkins/RGBConverter
Hash
WebSockets
CircularBuffer
ESP8266WebServer
extra_scripts =
pre:.build_web.py
build_flags = !python .get_version.py -DMQTT_MAX_PACKET_SIZE=200 -DHTTP_UPLOAD_BUFLEN=128 -Idist -Ilib/DataStructures
build_flags = !python .get_version.py -DMQTT_MAX_PACKET_SIZE=200 -DHTTP_UPLOAD_BUFLEN=128 -Idist -Ilib/DataStructures -DASYNC_TCP_SSL_ENABLED
# -D DEBUG_PRINTF
# -D MQTT_DEBUG
# -D MILIGHT_UDP_DEBUG
Expand Down
21 changes: 21 additions & 0 deletions web/src/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ var UI_FIELDS = [ {
help: "Password to log into MQTT server",
type: "string",
tab: "tab-mqtt"
}, {
tag: "mqtt_secure",
friendly: "MQTT secure",
help: "Whether or not to use SSL.",
type: "mqtt_secure",
tab: "tab-mqtt"
}, {
tag: "mqtt_server_fingerprint",
friendly: "MQTT server fingerprint",
help: "Adds an acceptable server fingerprint (SHA1). Example 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12",
type: "string",
tab: "tab-mqtt"
}, {
tag: "radio_interface_type",
friendly: "Radio interface type",
Expand Down Expand Up @@ -833,6 +845,15 @@ $(function() {
'<input type="radio" id="disable_mode_switching" name="enable_automatic_mode_switching" autocomplete="off" value="false" /> Disable' +
'</label>' +
'</div>';
} else if (k.type == 'mqtt_secure') {
elmt += '<div class="btn-group" id="mqtt_secure" data-toggle="buttons">' +
'<label class="btn btn-secondary active">' +
'<input type="radio" id="enable_mqtt_secure" name="mqtt_secure" autocomplete="off" value="true" /> Enable' +
'</label>'+
'<label class="btn btn-secondary">' +
'<input type="radio" id="disable_mqtt_secure" name="mqtt_secure" autocomplete="off" value="false" /> Disable' +
'</label>' +
'</div>';
} else {
elmt += '<input type="text" class="form-control" name="' + k.tag + '"/>';
}
Expand Down