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 Home Assistant MQTT discovery. #467

Merged
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