This commit is contained in:
2021-02-17 22:54:53 -05:00
parent bc316375e0
commit 68f57a0807
20 changed files with 3554 additions and 23 deletions

View File

@@ -0,0 +1,114 @@
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include "config.h"
#include "mqtt.h"
#include "util.h"
#define BTN_PIN D0
#define RED_LED_PIN 4
#define GREEN_LED_PIN 5
#define BLUE_LED_PIN 12
#define LONG_PRESS_MILLIS 1000
static WiFiClient wifiClient;
static PubSubClient mqttClient;
void blink(int pin, int times, int onMillis, int offMillis) {
// for (int i = 0; i < times; i++) {
// digitalWrite(pin, HIGH);
// delay(onMillis);
// digitalWrite(pin, LOW);
// delay(offMillis);
// }
}
void fail(const char *message) {
PRINT(message);
blink(RED_LED_PIN, 3, 50, 50);
// TODO: go back to sleep
}
void success() {
blink(GREEN_LED_PIN, 1, 50, 0);
}
static void initConnection() {
initWifi(wifiClient);
initMqtt(mqttClient, wifiClient);
}
void handleShortButtonWakeup() {
initConnection();
if (!publishTriggerMessage(mqttClient, String("short_press"))) {
fail("failed to publish trigger message\n");
}
if (!publishBatteryMessage(mqttClient, 100)) {
fail("Failed to publish battery message\n");
}
}
void handleLongButtonWakeup() {
initConnection();
if (!publishHABatteryDiscoveryConfig(mqttClient)) {
fail("Failed to publish battery config\n");
}
if (!publishHATriggerDiscoveryConfig(mqttClient)) {
fail("Failed to publish trigger config\n");
}
if (!publishBatteryMessage(mqttClient, 100)) {
fail("Failed to publish trigger message\n");
}
success();
}
void setup() {
// Sample the input button ASAP.
// pinMode(BTN_PIN, INPUT);
// bool btn_value = digitalRead(BTN_PIN);
unsigned long startTimingMillis = millis();
pinMode(RED_LED_PIN, OUTPUT);
pinMode(BLUE_LED_PIN, OUTPUT);
pinMode(GREEN_LED_PIN, OUTPUT);
#if defined(DEBUG_SKETCH) || defined(DEBUG_SKETCH_TIMING)
Serial.begin(9600);
#endif
PRINTLN("\nBeginning setup\n");
PRINT("Device name:");
PRINTLN(getDeviceHumanName());
handleShortButtonWakeup();
digitalWrite(RED_LED_PIN, LOW);
digitalWrite(GREEN_LED_PIN, LOW);
digitalWrite(BLUE_LED_PIN, LOW);
// We've done our work- back to sleep.
PRINT("Going to sleep...\n");
#if defined(DEBUG_SKETCH_TIMING)
unsigned long endTimeMillis = millis();
Serial.print("Ran for " + String(endTimeMillis - startTimingMillis) + " milliseconds\n");
#endif
// Drain all of our pending requests
mqttClient.disconnect();
wifiClient.flush();
while (mqttClient.state() != -1) {
yield();
}
ESP.deepSleep(0, RFMode::RF_NO_CAL);
}
void loop() {
}

View File

@@ -0,0 +1,146 @@
#include "mqtt.h"
#include <ArduinoJson.h>
#include "config.h"
#include "util.h"
#define MAX_PACKET_SIZE 1024
#define TIMEOUT_MILLIS 1000
#define TRIGGER_KEY "event"
#define VALUE_TEMPLATE(key) ("{{ value_json." key " }}")
// TODO: This should really be a class, this is kinda busted
static bool isDone = true;
void initMqtt(PubSubClient &mqttClient, WiFiClient &wifiClient) {
mqttClient.setServer(CONFIG_MQTT_SERVER, CONFIG_MQTT_SERVER_PORT);
mqttClient.setClient(wifiClient);
mqttClient.setBufferSize(MAX_PACKET_SIZE);
mqttClient.setCallback([](char *, unsigned char*, unsigned int){
PRINTLN("Message sent!");
isDone = true;
});
PRINT("Connecting to MQTT broker...");
while (!mqttClient.connected()) {
PRINT(".");
mqttClient.connect(getDeviceMachineName().c_str());
}
PRINTLN("\nConnected!");
}
static bool waitTilDone(PubSubClient &mqttClient) {
mqttClient.loop();
return true;
// unsigned long startTime = millis();
// do {
// mqttClient.loop();
// } while (!isDone && startTime + TIMEOUT_MILLIS > millis());
// return isDone;
}
static String getHATriggerConfigTopic() {
return String(CONFIG_HOMEASSISTANT_MQTT_PREFIX "device_automation/") + getDeviceMachineName() + "/config";
}
static String getHASensorConfigTopic() {
return String(CONFIG_HOMEASSISTANT_MQTT_PREFIX "sensor/") + getDeviceMachineName() + "/config";
}
static String getButtonTopicBase(bool abbrev) {
if (abbrev) {
return String("~");
} else {
return String(CONFIG_BUTTON_EVENT_TOPIC_PREFIX) + getDeviceMachineName();
}
}
static String getButtonStateTopic(bool abbrev) {
return getButtonTopicBase(abbrev) + "/" CONFIG_BUTTON_STATE_TOPIC_NAME;
}
static String getButtonTriggerTopic(bool abbrev) {
return getButtonTopicBase(abbrev) + "/" CONFIG_BUTTON_TRIGGER_TOPIC_NAME;
}
static bool publishJsonDocument(PubSubClient &mqttClient, String topic, JsonDocument &message, bool retain) {
char str[MAX_PACKET_SIZE];
size_t len = serializeJson(message, str, MAX_PACKET_SIZE);
PRINT(topic);
PRINT(":");
PRINTLN(str);
isDone = false;
if (!mqttClient.publish(topic.c_str(), (uint8_t *) str, len, retain)) {
return false;
}
return waitTilDone(mqttClient);
}
static void addDeviceJson(JsonDocument &doc) {
JsonObject device = doc.createNestedObject("device");
device["name"] = getDeviceHumanName();
device["mf"] = "DIY"; // manufacturer
device["mdl"] = "IoT Button - ESP8266"; // model
device["sw"] = "1.0"; // sw_version
JsonArray ids = device.createNestedArray("ids");
ids.add(getDeviceId());
}
bool publishBatteryMessage(PubSubClient &mqttClient, int percentage) {
StaticJsonDocument<MAX_PACKET_SIZE> json;
json["battery"] = percentage;
isDone = false;
PRINTLN("Publishing battery message");
return publishJsonDocument(mqttClient, getButtonStateTopic(false), json, true);
}
bool publishTriggerMessage(PubSubClient &mqttClient, String message) {
isDone = false;
PRINTLN("Publishing trigger message");
if (!mqttClient.publish(getButtonTriggerTopic(false).c_str(), message.c_str(), false)) {
return false;
}
return waitTilDone(mqttClient);
}
bool publishHABatteryDiscoveryConfig(PubSubClient &mqttClient) {
StaticJsonDocument<MAX_PACKET_SIZE> json;
bool abbrev = true;
json["~"] = getButtonTopicBase(false);
json["stat_t"] = getButtonStateTopic(abbrev); // state_topic
json["val_tpl"] = VALUE_TEMPLATE("battery"); // state_value_template
json["uniq_id"] = getDeviceMachineName() + "-battery"; // unique_id
json["dev_cla"] = "battery"; // device_class
json["name"] = getDeviceHumanName() + ": Battery";
addDeviceJson(json);
PRINTLN("Publishing device sensor config");
return publishJsonDocument(mqttClient, getHASensorConfigTopic(), json, true);
}
bool publishHATriggerDiscoveryConfig(PubSubClient &mqttClient) {
StaticJsonDocument<MAX_PACKET_SIZE> json;
bool abbrev = true;
// json["name"] = getDeviceHumanName();
json["atype"] = "trigger"; // automation_type
json["type"] = "button_short_press";
json["stype"] = "button_1"; // subtype
json["~"] = getButtonTopicBase(false);
json["t"] = getButtonTriggerTopic(abbrev); // topic
addDeviceJson(json);
PRINTLN("Publishing device trigger discovery");
return publishJsonDocument(mqttClient, getHATriggerConfigTopic(), json, true);
}

View File

@@ -0,0 +1,16 @@
#ifndef __MQTT_H_
#define __MQTT_H_
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
void initMqtt(PubSubClient& client, WiFiClient &wifiClient);
bool publishHABatteryDiscoveryConfig(PubSubClient &client);
bool publishHATriggerDiscoveryConfig(PubSubClient &client);
bool publishTriggerMessage(PubSubClient &client, String message);
bool publishBatteryMessage(PubSubClient &client, int percent);
#endif // __MQTT_H_

View File

@@ -0,0 +1,41 @@
#include "util.h"
#include "config.h"
#define DEVICE_NAME_PREFIX "button-"
#define PRINT_DELAY_MILLIS 500
String getDeviceId() {
return String(ESP.getChipId(), HEX);
}
String getDeviceMachineName() {
return String(DEVICE_NAME_PREFIX) + getDeviceId();
}
String getDeviceHumanName() {
return "IoT Button (" + getDeviceId() + ")";
}
void initWifi(WiFiClient &wifi) {
PRINT("Connecting to WiFi using MAC: ");
PRINTLN(WiFi.macAddress());
WiFi.hostname(getDeviceMachineName().c_str());
WiFi.begin(CONFIG_SSID, CONFIG_PSK);
long last_time = millis();
while (WiFi.status() != WL_CONNECTED) {
yield();
long time = millis();
if (time >= last_time + PRINT_DELAY_MILLIS) {
PRINT(".");
last_time = time;
}
}
PRINTLN("\nConnected!");
PRINT("IP Address: ");
PRINTLN(WiFi.localIP());
}

View File

@@ -0,0 +1,21 @@
#ifndef __UTIL_H_
#define __UTIL_H_
#include <Arduino.h>
#include <ESP8266WiFi.h>
#ifdef DEBUG_SKETCH
#define PRINT(x) { Serial.print(x); } while (0)
#define PRINTLN(x) { Serial.println(x); } while (0)
#else
#define PRINT(x)
#define PRINTLN(x)
#endif
String getDeviceHumanName();
String getDeviceMachineName();
String getDeviceId();
void initWifi(WiFiClient &wifi);
#endif // __UTIL_H_