Skip to content

Commit

Permalink
Add Home Assistant MQTT discovery. (#467)
Browse files Browse the repository at this point in the history
* Add Home Assistant MQTT discovery.

* Add Home Assistant MQTT discovery.

* Add Home Assistant MQTT discovery.

* Add Home Assistant MQTT discovery - better performance.

* Add Home Assistant MQTT discovery - better topic name.

* Add Home Assistant MQTT discovery - add disable HA MQTT discovery.

* Add Home Assistant MQTT discovery - lint format.

Co-authored-by: Krzysztof Malisiewicz <[email protected]>
Co-authored-by: Samuel Lang <[email protected]>
  • Loading branch information
3 people authored Jun 5, 2021
1 parent c8bbbfe commit 394a2b1
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 26 deletions.
1 change: 1 addition & 0 deletions pio/lib/Globals/Globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ extern Ticker flasher;
#define API_THINGSPEAK true
#define API_BLYNK true
#define API_BREWBLOX true
#define API_MQTT_HASSIO true
#define API_AWSIOTMQTT true //AWS

//#define BLYNK_DEBUG
Expand Down
61 changes: 53 additions & 8 deletions pio/lib/Sender/Sender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,51 @@ bool SenderClass::mqttConnect(const String &server, uint16_t port, const String
return false;
}

#ifdef API_MQTT_HASSIO
bool SenderClass::enableHassioDiscovery(String server, uint16_t port, String username, String password, String name, String unit)
{
bool response = mqttConnect(server, port, name, username, password);
if (response)
{
_mqttClient.setBufferSize(512);
auto chipid = String(ESP.getChipId(), HEX);
String device = "\"dev\": { \"name\": \"" + name + "\",\"mdl\": \"ispindel\",\"sw\": \"" + FIRMWAREVERSION + "\",\"mf\": \"iSpindel\",\"ids\": [\"" + chipid + "\"]}";
String topic = "homeassistant/sensor/iSpindel_" + chipid + "/";
_mqttClient.publish((topic + "temperature/config").c_str(), ("{ \"uniq_id\": \"" + chipid + "_temp\", \"dev_cla\": \"temperature\", \"name\": \"Temperature\", \"unit_of_meas\": \"°" + unit + "\", \"val_tpl\": \"{{ value_json }}\", \"stat_t\": \"ispindel/" + name + "/temperature\"," + device + "}").c_str(), true);
_mqttClient.publish((topic + "tilt/config").c_str(), ("{ \"uniq_id\": \"" + chipid + "_tilt\", \"name\": \"Tilt\", \"val_tpl\": \"{{ value_json }}\", \"stat_t\": \"ispindel/" + name + "/tilt\"," + device + "}").c_str(), true);
_mqttClient.publish((topic + "battery/config").c_str(), ("{ \"uniq_id\": \"" + chipid + "_battery\", \"dev_cla\": \"voltage\", \"name\": \"Battery voltage\", \"unit_of_meas\": \"V\", \"val_tpl\": \"{{ value_json }}\", \"stat_t\": \"ispindel/" + name + "/battery\"," + device + "}").c_str(), true);
_mqttClient.publish((topic + "rssi/config").c_str(), ("{ \"uniq_id\": \"" + chipid + "_rssi\", \"dev_cla\": \"signal_strength\", \"name\": \"Signal Strength\", \"unit_of_meas\": \"dB\", \"val_tpl\": \"{{ value_json }}\", \"stat_t\": \"ispindel/" + name + "/RSSI\"," + device + "}").c_str(), true);
_mqttClient.publish((topic + "gravity/config").c_str(), ("{ \"uniq_id\": \"" + chipid + "_gravity\", \"name\": \"Gravity\", \"unit_of_meas\": \"°P\", \"val_tpl\": \"{{ value_json }}\", \"stat_t\": \"ispindel/" + name + "/gravity\"," + device + "}").c_str(), true);
_mqttClient.loop();
}

CONSOLELN(F("Closing MQTT connection"));
_mqttClient.disconnect();
stopclient();
return response;
}

bool SenderClass::disableHassioDiscovery(String server, uint16_t port, String username, String password, String name)
{
bool response = mqttConnect(server, port, name, username, password);
if (response)
{
auto chipid = String(ESP.getChipId(), HEX);
String topic = "homeassistant/sensor/iSpindel_" + chipid + "/";
_mqttClient.publish((topic + "temperature/config").c_str(), "");
_mqttClient.publish((topic + "tilt/config").c_str(), "");
_mqttClient.publish((topic + "battery/config").c_str(), "");
_mqttClient.publish((topic + "rssi/config").c_str(), "");
_mqttClient.publish((topic + "gravity/config").c_str(), "");
_mqttClient.loop();
}
CONSOLELN(F("Closing MQTT connection"));
_mqttClient.disconnect();
stopclient();
return response;
}
#endif

bool SenderClass::sendMQTT(String server, uint16_t port, String username, String password, String name)
{
bool response = mqttConnect(server, port, name, username, password);
Expand Down Expand Up @@ -248,20 +293,20 @@ String SenderClass::sendTCP(String server, uint16_t port)
bool SenderClass::sendThingSpeak(String token, long Channel)
{
int field = 0;
unsigned long channelNumber = Channel;
unsigned long channelNumber = Channel;
const char * writeAPIKey = token.c_str();

serializeJson(_doc, Serial);
ThingSpeak.begin(_client);

CONSOLELN(F("\nSender: ThingSpeak posting"));

for (const auto &kv : _doc.as<JsonObject>())
{
field++;
{
field++;
ThingSpeak.setField(field, kv.value().as<String>());
}
// write to the ThingSpeak channel
// write to the ThingSpeak channel
int x = ThingSpeak.writeFields(channelNumber, writeAPIKey);

if(x == 200){
Expand Down Expand Up @@ -639,7 +684,7 @@ bool SenderClass::sendBlynk(char* token)
i++;
delay(50);
}

if (Blynk.connected())
{
CONSOLELN(F("\nConnected to the Blynk server, sending data"));
Expand All @@ -656,7 +701,7 @@ bool SenderClass::sendBlynk(char* token)
CONSOLELN(F("\nFailed to connect to Blynk, going to sleep"));
return false;
}

delay(150); //delay to allow last value to be sent;
return true;
}
Expand Down
6 changes: 6 additions & 0 deletions pio/lib/Sender/Sender.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#ifndef _SENDER_H_
#define _SENDER_H_

#include "Globals.h"

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>
Expand All @@ -30,6 +32,10 @@ class SenderClass
bool sendTCONTROL(String server, uint16_t port);
bool sendBlynk(char* token);
bool sendBrewblox(String server, uint16_t port, String topic, String username, String password, String name);
#ifdef API_MQTT_HASSIO
bool enableHassioDiscovery(String server, uint16_t port, String username, String password, String name, String unit);
bool disableHassioDiscovery(String server, uint16_t port, String username, String password, String name);
#endif
bool sendSecureMQTT(char CACert[], char deviceCert[], char deviceKey[], String server, uint16_t port, String name, String topic); //AWS
void add(String id, float value);
void add(String id, String value);
Expand Down
38 changes: 21 additions & 17 deletions pio/lib/WiFiManagerKT/WiFiManagerKT.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,29 @@ const char HTTP_ISPINDEL_IMG[] PROGMEM = "<img src=\"data:image/jpeg;base64,/9j/
const char HTTP_200[] PROGMEM = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
const char HTTP_HEADER[] PROGMEM = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" charset=\"utf-8\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/><title>{v}</title>";

const char HTTP_STYLE[] PROGMEM = "<style>body,textarea,input,select{background: 0;border-radius: 0;font: 16px sans-serif;margin: 0}textarea,input,select{outline: 0;font-size: 14px;border: 1px solid #ccc;padding: 8px;width: 90%}.btn a{text-decoration: none}.container{margin: auto;width: 90%}@media(min-width:1200px){.container{margin: auto;width: 30%}}@media(min-width:768px) and (max-width:1200px){.container{margin: auto;width: 50%}}.btn,h2{font-size: 2em}h1{font-size: 3em}.btn{background: #0ae;border-radius: 4px;border: 0;color: #fff;cursor: pointer;display: inline-block;margin: 2px 0;padding: 10px 14px 11px;width: 100%}.btn:hover{background: #09d}.btn:active,.btn:focus{background: #08b}label>*{display: inline}form>*{display: block;margin-bottom: 10px}textarea:focus,input:focus,select:focus{border-color: #5ab}.msg{background: #def;border-left: 5px solid #59d;padding: 1.5em}.q{float: right;width: 64px;text-align: right}.l{background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==') no-repeat left center;background-size: 1em}input[type='checkbox']{float: left;width: 20px}.table td{padding:.5em;text-align:left}.table tbody>:nth-child(2n-1){background:#ddd}</style>";
const char HTTP_STYLE[] PROGMEM = "<style>body,textarea,input,select{background: 0;border-radius: 0;font: 16px sans-serif;margin: 0}textarea,input,select{outline: 0;font-size: 14px;border: 1px solid #ccc;padding: 8px;width: 90%}.btn a{text-decoration: none}.container{margin: auto;width: 90%}@media(min-width:1200px){.container{margin: auto;width: 30%}}@media(min-width:768px) and (max-width:1200px){.container{margin: auto;width: 50%}}.btn,h2{font-size: 2em}h1{font-size: 3em}.btn{background: #0ae;border-radius: 4px;border: 0;color: #fff;cursor: pointer;display: inline-block;margin: 2px 0;padding: 10px 14px 11px;width: 100%}.btn:hover{background: #09d}.btn:active,.btn:focus{background: #08b}label>*{display: inline}form>*{display: block;margin-bottom: 10px}textarea:focus,input:focus,select:focus{border-color: #5ab}.msg{background: #def;border-left: 5px solid #59d;padding: 1.5em}.q{float: right;width: 64px;text-align: right}.l{background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==') no-repeat left center;background-size: 1em}input[type='checkbox']{width: 20px}.table td{padding:.5em;text-align:left}.table tbody>:nth-child(2n-1){background:#ddd}</style>";

const char HTTP_SCRIPT[] PROGMEM = R"V0G0N(
<script>
var lAPI = [
{"name":"Ubidots", "token":1,"server":0,"uri":0,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0},
{"name":"empty", "token":1,"server":0,"uri":0,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0},
{"name":"CraftBeerPi","token":0,"server":1,"uri":0,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0},
{"name":"HTTP", "token":1,"server":1,"uri":1,"port":1,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0},
{"name":"TControl", "token":0,"server":1,"uri":0,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0},
{"name":"FHEM", "token":0,"server":1,"uri":0,"port":1,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0},
{"name":"TCP", "token":1,"server":1,"uri":0,"port":1,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0},
{"name":"iSpindel.de","token":1,"server":0,"uri":0,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0},
{"name":"InfluxDB", "token":0,"server":1,"uri":0,"port":1,"channel":0,"db":1,"username":1,"password":1,"job":0,"instance":0,"warning1":0},
{"name":"Prometheus", "token":0,"server":1,"uri":0,"port":1,"channel":0,"db":0,"username":0,"password":0,"job":1,"instance":1,"warning1":0},
{"name":"MQTT", "token":0,"server":1,"uri":0,"port":1,"channel":0,"db":0,"username":1,"password":1,"job":0,"instance":0,"warning1":0},
{"name":"ThingSpeak", "token":1,"server":0,"uri":0,"port":0,"channel":1,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0},
{"name":"Blynk", "token":1,"server":0,"uri":0,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0},
{"name":"Brewblox", "token":0,"server":1,"uri":1,"port":1,"channel":0,"db":0,"username":1,"password":1,"job":0,"instance":0,"warning1":0},
{"name":"AWSIOTMQTT", "token":0,"server":1,"uri":1,"port":1,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":1},
{"name":"HTTPS Post", "token":1,"server":1,"uri":1,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0}];
{"name":"Ubidots", "token":1,"server":0,"uri":0,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0,"hassio":0},
{"name":"empty", "token":1,"server":0,"uri":0,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0,"hassio":0},
{"name":"CraftBeerPi","token":0,"server":1,"uri":0,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0,"hassio":0},
{"name":"HTTP", "token":1,"server":1,"uri":1,"port":1,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0,"hassio":0},
{"name":"TControl", "token":0,"server":1,"uri":0,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0,"hassio":0},
{"name":"FHEM", "token":0,"server":1,"uri":0,"port":1,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0,"hassio":0},
{"name":"TCP", "token":1,"server":1,"uri":0,"port":1,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0,"hassio":0},
{"name":"iSpindel.de","token":1,"server":0,"uri":0,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0,"hassio":0},
{"name":"InfluxDB", "token":0,"server":1,"uri":0,"port":1,"channel":0,"db":1,"username":1,"password":1,"job":0,"instance":0,"warning1":0,"hassio":0},
{"name":"Prometheus", "token":0,"server":1,"uri":0,"port":1,"channel":0,"db":0,"username":0,"password":0,"job":1,"instance":1,"warning1":0,"hassio":0},
{"name":"MQTT", "token":0,"server":1,"uri":0,"port":1,"channel":0,"db":0,"username":1,"password":1,"job":0,"instance":0,"warning1":0,"hassio":1},
{"name":"ThingSpeak", "token":1,"server":0,"uri":0,"port":0,"channel":1,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0,"hassio":0},
{"name":"Blynk", "token":1,"server":0,"uri":0,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0,"hassio":0},
{"name":"Brewblox", "token":0,"server":1,"uri":1,"port":1,"channel":0,"db":0,"username":1,"password":1,"job":0,"instance":0,"warning1":0,"hassio":0},
{"name":"AWSIOTMQTT", "token":0,"server":1,"uri":1,"port":1,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":1,"hassio":0},
{"name":"HTTPS Post", "token":1,"server":1,"uri":1,"port":0,"channel":0,"db":0,"username":0,"password":0,"job":0,"instance":0,"warning1":0,"hassio":0}
];
var $ = function (id) { return document.getElementById(id); };
var labels = document.getElementsByTagName('LABEL');
function set(id, show) {
Expand Down Expand Up @@ -112,6 +114,8 @@ const char HTTP_TEMPSCALE_LIST[] PROGMEM = R"V0G0N(

const char TYPE_HIDDEN[] = "type=\"hidden\"";
const char TYPE_NUMBER[] = "type=\"number\" step=\"any\"";
const char TYPE_CHECKBOX[] = "type=\"checkbox\"";
const char TYPE_CHECKBOX_CHECKED[] = "type=\"checkbox\" checked";

const char HTTP_HEADER_END[] PROGMEM = "</head><body><div class=\"container\">";
const char HTTP_PORTAL_OPTIONS[] PROGMEM = "<form action=\"/iSpindel\" method=\"get\"><button class=\"btn\">iSpindel Info</button></form><br/><form action=\"/wifi\" method=\"get\"><button class=\"btn\">Configuration</button></form><br/><form action=\"/mnt\" method=\"get\"><button class=\"btn\">Maintenance</button></form><br/><form action=\"/i\" method=\"get\"><button class=\"btn\">Information</button></form><br/><form action=\"/close\" method=\"get\"><button class=\"btn\">Exit Portal</button></form><br/>";
Expand Down
43 changes: 42 additions & 1 deletion pio/src/iSpindel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ char my_password[TKIDSIZE];
char my_job[TKIDSIZE] = "ispindel";
char my_instance[TKIDSIZE] = "000";
char my_polynominal[250] = "-0.00031*tilt^2+0.557*tilt-14.054";
#ifdef API_MQTT_HASSIO
bool my_hassio = false;
bool my_hassio_changed = false;
#endif

String my_ssid;
String my_psk;
Expand Down Expand Up @@ -220,7 +224,10 @@ bool readConfig()
my_psk = (const char *)doc["PSK"];
if (doc.containsKey("POLY"))
strcpy(my_polynominal, doc["POLY"]);

#ifdef API_MQTT_HASSIO
if (doc.containsKey("Hassio"))
my_hassio = doc["Hassio"];
#endif
if (doc.containsKey("Offset"))
{
for (size_t i = 0; i < (sizeof(my_Offset) / sizeof(*my_Offset)); i++)
Expand Down Expand Up @@ -324,6 +331,21 @@ String htmlencode(String str)
return encodedstr;
}

void postConfig()
{
#ifdef API_MQTT_HASSIO
SenderClass sender;
if (my_hassio)
{
sender.enableHassioDiscovery(my_server, my_port, my_username, my_password, my_name, tempScaleLabel());
}
if (my_hassio_changed && !my_hassio)
{
sender.disableHassioDiscovery(my_server, my_port, my_username, my_password, my_name);
}
#endif
}

bool startConfiguration()
{

Expand All @@ -348,6 +370,10 @@ bool startConfiguration()
WiFiManagerParameter custom_password("password", "Password", my_password, TKIDSIZE);
WiFiManagerParameter custom_job("job", "Prometheus job", my_job, TKIDSIZE);
WiFiManagerParameter custom_instance("instance", "Prometheus instance", my_instance, TKIDSIZE);
#ifdef API_MQTT_HASSIO
WiFiManagerParameter custom_hassio("hassio", "Home Assistant integration via MQTT", "checked", TKIDSIZE,
my_hassio ? TYPE_CHECKBOX_CHECKED : TYPE_CHECKBOX);
#endif
WiFiManagerParameter custom_vfact("vfact", "Battery conversion factor", String(my_vfact).c_str(), 7, TYPE_NUMBER);
WiFiManagerParameter tempscale_list(HTTP_TEMPSCALE_LIST);
WiFiManagerParameter custom_tempscale("tempscale", "tempscale", String(my_tempscale).c_str(), 5, TYPE_HIDDEN,
Expand Down Expand Up @@ -383,6 +409,9 @@ bool startConfiguration()
wifiManager.addParameter(&custom_password);
wifiManager.addParameter(&custom_job);
wifiManager.addParameter(&custom_instance);
#ifdef API_MQTT_HASSIO
wifiManager.addParameter(&custom_hassio);
#endif
WiFiManagerParameter custom_polynom_lbl(
"<hr><label for=\"POLYN\">Gravity conversion<br/>ex. \"-0.00031*tilt^2+0.557*tilt-14.054\"</label>");
wifiManager.addParameter(&custom_polynom_lbl);
Expand Down Expand Up @@ -417,6 +446,13 @@ bool startConfiguration()
my_port = String(custom_port.getValue()).toInt();
my_channel = String(custom_channel.getValue()).toInt();
my_tempscale = String(custom_tempscale.getValue()).toInt();
#ifdef API_MQTT_HASSIO
{
auto hassio = my_api == DTMQTT && String(custom_hassio.getValue()) == "checked";
my_hassio_changed = my_hassio != hassio;
my_hassio = hassio;
}
#endif
validateInput(custom_uri.getValue(), my_uri);

String tmp = custom_vfact.getValue();
Expand All @@ -433,6 +469,8 @@ bool startConfiguration()
WiFi.setAutoConnect(true);
WiFi.setAutoReconnect(true);

postConfig();

return saveConfig();
}
return false;
Expand Down Expand Up @@ -489,6 +527,9 @@ bool saveConfig()
doc["Password"] = my_password;
doc["Job"] = my_job;
doc["Instance"] = my_instance;
#ifdef API_MQTT_HASSIO
doc["Hassio"] = my_hassio;
#endif
doc["Vfact"] = my_vfact;
doc["TS"] = my_tempscale;
doc["OWpin"] = my_OWpin;
Expand Down

0 comments on commit 394a2b1

Please sign in to comment.