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

Implement MQTT discovery support #38

Open
wants to merge 6 commits into
base: master
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
.gcc-flags.json
src/secrets.h
.vscode
.pio
15 changes: 12 additions & 3 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@
#define ADC_VOLTAGE_DIVIDER 44.551316985
//#define ENABLE_ADC_SLEEP

// to be adjusted by user
#define MQTT_SERVER "10.0.0.2"
#define MQTT_USER "homeassistant"
#define MQTT_COMMAND_TOPIC "vacuum/command"
#define MQTT_STATE_TOPIC "vacuum/state"
#define MQTT_USER "roomba"

// Only change if you know what you're doing!
#define MQTT_DISCOVERY "homeassistant"
#define MQTT_DEVICE_CLASS "vacuum"
#define MQTT_DIVIDER "/"
#define MQTT_TOPIC_BASE MQTT_DISCOVERY MQTT_DIVIDER MQTT_DEVICE_CLASS MQTT_DIVIDER
#define MQTT_IDPREFIX "roomba_"
#define MQTT_COMMAND_TOPIC "command"
#define MQTT_STATE_TOPIC "state"
#define MQTT_CONFIG_TOPIC "config"
100 changes: 82 additions & 18 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ bool OTAStarted;

// MQTT setup
PubSubClient mqttClient(wifiClient);
const PROGMEM char *commandTopic = MQTT_COMMAND_TOPIC;
const PROGMEM char *statusTopic = MQTT_STATE_TOPIC;

void wakeup() {
DLOG("Wakeup Roomba\n");
Expand Down Expand Up @@ -111,7 +109,7 @@ bool performCommand(const char *cmdchar) {
DLOG("Turning off\n");
roomba.power();
roombaState.cleaning = false;
} else if (cmd == "toggle" || cmd == "start_pause") {
} else if (cmd == "start" || cmd == "pause") {
DLOG("Toggling\n");
roomba.cover();
} else if (cmd == "stop") {
Expand All @@ -128,6 +126,7 @@ bool performCommand(const char *cmdchar) {
} else if (cmd == "locate") {
DLOG("Locating\n");
// TODO
//roomba.playSong(0);
} else if (cmd == "return_to_base") {
DLOG("Returning to Base\n");
roombaState.cleaning = true;
Expand All @@ -138,9 +137,28 @@ bool performCommand(const char *cmdchar) {
return true;
}

char* getEntityID() {
// build entity_id
byte MAC[6];
WiFi.macAddress(MAC);
char MACc[30];
sprintf(MACc, "%02X%02X%02X%02X%02X%02X", MAC[0], MAC[1], MAC[2], MAC[3], MAC[4], MAC[5]);
char entityID[50];
sprintf(entityID, "%s%s", MQTT_IDPREFIX, MACc);
// avoid confusions with lower/upper case differences in IDs
return strlwr(entityID);
}

char* getMQTTTopic(char* topic) {
// build mqtt target topic
char mqttTopic[200];
sprintf(mqttTopic, "%s%s%s%s", MQTT_TOPIC_BASE, getEntityID(), MQTT_DIVIDER, topic);
return mqttTopic;
}

void mqttCallback(char *topic, byte *payload, unsigned int length) {
DLOG("Received mqtt callback for topic %s\n", topic);
if (strcmp(commandTopic, topic) == 0) {
if (strcmp(getMQTTTopic(MQTT_COMMAND_TOPIC), topic) == 0) {
// turn payload into a null terminated string
char *cmd = (char *)malloc(length + 1);
memcpy(cmd, payload, length);
Expand Down Expand Up @@ -236,17 +254,16 @@ void sleepIfNecessary() {
// Fire off a quick message with our most recent state, if MQTT is connected
DLOG("Battery voltage is low (%.1fV). Sleeping for 10 minutes\n", mV / 1000);
if (mqttClient.connected()) {
StaticJsonBuffer<200> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
StaticJsonDocument<200> root;
root["battery_level"] = 0;
root["cleaning"] = false;
root["docked"] = false;
root["charging"] = false;
root["voltage"] = mV / 1000;
root["charge"] = 0;
String jsonStr;
root.printTo(jsonStr);
mqttClient.publish(statusTopic, jsonStr.c_str(), true);
serializeJson(root, jsonStr);
mqttClient.publish(getMQTTTopic(MQTT_STATE_TOPIC), jsonStr.c_str(), true);
}
delay(200);

Expand Down Expand Up @@ -358,7 +375,6 @@ void setup() {
String hostname(HOSTNAME);
WiFi.hostname(hostname);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Expand Down Expand Up @@ -393,20 +409,46 @@ void reconnect() {
// Attempt to connect
if (mqttClient.connect(HOSTNAME, MQTT_USER, MQTT_PASSWORD)) {
DLOG("MQTT connected\n");
mqttClient.subscribe(commandTopic);
mqttClient.subscribe(getMQTTTopic(MQTT_COMMAND_TOPIC));
} else {
DLOG("MQTT failed rc=%d try again in 5 seconds\n", mqttClient.state());
}
}

void sendConfig() {
if (!mqttClient.connected()) {
DLOG("MQTT Disconnected, not sending config\n");
return;
}
StaticJsonDocument<500> root;
root["name"] = getEntityID();
root["unique_id"] = getEntityID();
root["schema"] = "state";
char baseTopic[200];
sprintf(baseTopic, "%s%s", MQTT_TOPIC_BASE, getEntityID());
root["~"] = baseTopic;
root["stat_t"] = String("~/") + MQTT_STATE_TOPIC;
root["cmd_t"] = String("~/") + MQTT_COMMAND_TOPIC;
root["send_cmd_t"] = String("~/") + MQTT_COMMAND_TOPIC;
root["json_attr_t"] = String("~/") + MQTT_STATE_TOPIC;
root["sup_feat"][0] = "start";
root["sup_feat"][1] = "stop";
root["sup_feat"][2] = "pause";
root["sup_feat"][3] = "return_home";
root["sup_feat"][4] = "locate";
root["sup_feat"][5] = "clean_spot";
String jsonStr;
serializeJson(root, jsonStr);
DLOG("Reporting config: %s\n", jsonStr.c_str());
mqttClient.publish(getMQTTTopic(MQTT_CONFIG_TOPIC), jsonStr.c_str());
}

void sendStatus() {
if (!mqttClient.connected()) {
DLOG("MQTT Disconnected, not sending status\n");
return;
}
DLOG("Reporting packet Distance:%dmm ChargingState:%d Voltage:%dmV Current:%dmA Charge:%dmAh Capacity:%dmAh\n", roombaState.distance, roombaState.chargingState, roombaState.voltage, roombaState.current, roombaState.charge, roombaState.capacity);
StaticJsonBuffer<200> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
StaticJsonDocument<200> root;
root["battery_level"] = (roombaState.charge * 100)/roombaState.capacity;
root["cleaning"] = roombaState.cleaning;
root["docked"] = roombaState.docked;
Expand All @@ -416,14 +458,25 @@ void sendStatus() {
root["voltage"] = roombaState.voltage;
root["current"] = roombaState.current;
root["charge"] = roombaState.charge;
String curState = "idle";
if (roombaState.docked) {
curState = "docked";
} else {
if (roombaState.cleaning) {
curState = "cleaning";
}
}
root["state"] = curState;
String jsonStr;
root.printTo(jsonStr);
mqttClient.publish(statusTopic, jsonStr.c_str());
serializeJson(root, jsonStr);
DLOG("Reporting status: %s\n", jsonStr.c_str());
mqttClient.publish(getMQTTTopic(MQTT_STATE_TOPIC), jsonStr.c_str());
}

int lastStateMsgTime = 0;
int lastWakeupTime = 0;
int lastConnectTime = 0;
int configLoop = 0;

void loop() {
// Important callbacks that _must_ happen every cycle
Expand All @@ -438,10 +491,21 @@ void loop() {

long now = millis();
// If MQTT client can't connect to broker, then reconnect
if (!mqttClient.connected() && (now - lastConnectTime) > 5000) {
DLOG("Reconnecting MQTT\n");
if ((now - lastConnectTime) > 5000) {
lastConnectTime = now;
reconnect();
if (!mqttClient.connected()) {
DLOG("Reconnecting MQTT\n");
reconnect();
sendConfig();
} else {
// resend config every now and then to reconfigure entity e.g. in case homeassistant has been restarted
if (configLoop == 19) {
sendConfig();
configLoop = 0;
} else {
configLoop++;
}
}
}
// Wakeup the roomba at fixed intervals
if (now - lastWakeupTime > 50000) {
Expand Down