Skip to content

Instantly share code, notes, and snippets.

@philharlow
Created December 24, 2022 04:29
Show Gist options
  • Save philharlow/2314103834c6515cbb8b339fd2691131 to your computer and use it in GitHub Desktop.
Save philharlow/2314103834c6515cbb8b339fd2691131 to your computer and use it in GitHub Desktop.
#ifndef ESPHASDevice_h
#define ESPHASDevice_h
//#define Sprintln(a) (Serial.println(a))
#define Sprintln(a)
#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266HTTPUpdateServer.h>
#include <ESP8266httpUpdate.h>
#include <WiFiClientSecure.h>
#include <ESP8266WebServer.h>
//#include <WiFiManager.h>
#include <DNSServer.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <stdint.h>
#include <EEPROM.h>
struct TimerDefinition {
unsigned long ms;
unsigned long start;
bool repeats;
void (*callback)(uint64_t now);
TimerDefinition* next = nullptr;
TimerDefinition () {}
TimerDefinition (unsigned long milliSeconds, bool repeat, void (*inCallback)(uint64_t now64)) {
ms = milliSeconds;
callback = inCallback;
start = millis();
repeats = repeat;
}
bool loop(unsigned long now);
};
struct MQTTSubscription {
String topic;
void (*callback)(String topic, String payload);
int qos;
MQTTSubscription (String inTopic, void (*inCallback)(String topic, String payload), int q) {
topic = inTopic;
callback = inCallback;
qos = q;
}
virtual void WriteTopic(JsonArray& array) { array.add(topic); }
MQTTSubscription () {}
};
class EEPROMSetting {
public:
EEPROMSetting* Next = nullptr;
String Name;
int eepromSize;
EEPROMSetting(String name) {
Name = name;
}
virtual void EEPROMPut(int index) {}
virtual void EEPROMGet(int index) {}
virtual void WriteJson(DynamicJsonDocument& doc) {}
virtual void UpdateFromJson(DynamicJsonDocument& doc) {}
virtual void Reset() {}
virtual String GetDisplayValue() {}
};
class EEPROMInt : public EEPROMSetting {
public:
int Value;
int DefaultValue;
EEPROMInt(String name, int defaultValue) : EEPROMSetting(name) {
eepromSize = sizeof(defaultValue);
Value = defaultValue;
DefaultValue = defaultValue;
}
void EEPROMPut(int index) override { EEPROM.put(index, Value); }
void EEPROMGet(int index) override { EEPROM.get(index, Value); }
void WriteJson(DynamicJsonDocument& doc) override { doc[Name.c_str()].set(Value); }
void UpdateFromJson(DynamicJsonDocument& doc) override { if(doc.containsKey(Name)) Value = doc[Name.c_str()]; }
void Reset() override { Value = DefaultValue; }
String GetDisplayValue() { return String(Value); }
};
class EEPROMFloat : public EEPROMSetting {
public:
float Value;
float DefaultValue;
EEPROMFloat(String name, float defaultValue) : EEPROMSetting(name) {
eepromSize = sizeof(defaultValue);
Value = defaultValue;
DefaultValue = defaultValue;
}
void EEPROMPut(int index) override { EEPROM.put(index, Value); }
void EEPROMGet(int index) override { EEPROM.get(index, Value); }
void WriteJson(DynamicJsonDocument& doc) override { doc[Name.c_str()].set(Value); }
void UpdateFromJson(DynamicJsonDocument& doc) override { if(doc.containsKey(Name)) Value = doc[Name.c_str()]; }
void Reset() override { Value = DefaultValue; }
String GetDisplayValue() { return String(Value); }
};
class EEPROMString : public EEPROMSetting {
public:
char Value[60];
char DefaultValue[60];
EEPROMString(String name, char defaultValue[60]) : EEPROMSetting(name) {
eepromSize = 60;// sizeof(defaultValue);
strcpy(Value, defaultValue);
strcpy(DefaultValue, defaultValue);
}
void EEPROMPut(int index) override { EEPROM.put(index, Value); }
void EEPROMGet(int index) override { EEPROM.get(index, Value); }
void WriteJson(DynamicJsonDocument& doc) override { doc[Name.c_str()].set(Value); }
void UpdateFromJson(DynamicJsonDocument& doc) override {
if(doc.containsKey(Name)) {
char val[60] = "";
String str = doc[Name.c_str()];
str.toCharArray(val, 60);
strcpy(Value, val);
}
}
void Reset() override { strcpy(Value, DefaultValue); }
String GetDisplayValue() { return Value; }
};
class EEPROMBool : public EEPROMSetting {
public:
bool Value;
bool DefaultValue;
EEPROMBool(String name, bool defaultValue) : EEPROMSetting(name) {
eepromSize = sizeof(defaultValue);
Value = defaultValue;
DefaultValue = defaultValue;
}
void EEPROMPut(int index) override { EEPROM.put(index, Value); }
void EEPROMGet(int index) override { EEPROM.get(index, Value); }
void WriteJson(DynamicJsonDocument& doc) override { doc[Name.c_str()].set(Value); }
void UpdateFromJson(DynamicJsonDocument& doc) override { if(doc.containsKey(Name)) Value = doc[Name.c_str()]; }
void Reset() override { Value = DefaultValue; }
String GetDisplayValue() { return String(Value); }
};
#define maxSubscriptions 30
class MQTTEntity {
public:
MQTTEntity* Next = nullptr;
String Name;
String StateTopic;
String Type;
String UniqueId;
String DeviceName;
String DeviceClass;
MQTTEntity(String name) {
Name = name;
DeviceName = WiFi.macAddress();
DeviceName.replace(":", "");
DeviceClass = "power";
}
void Publish(String topic, String payload);
void Subscribe(String topic, void (*callback)(String topic2, String payload));
void SendStatus() {
String payload = GetStatusPayload();
Publish(StateTopic, payload);
}
void Register() {
DynamicJsonDocument doc(512);
ApplyConfigJson(doc);
String payload;
serializeJson(doc, payload);
String configTopic = "homeassistant/" + Type + "/" + UniqueId + "/config";
Publish(configTopic, payload);
}
virtual void SetType(String type) {
Type = type;
UniqueId = Type + "_" + DeviceName + "_" + Name;
StateTopic = "has_device/" + UniqueId + "/stat/" + Name;
}
virtual void onMQTTConnected() {
Register();
SendStatus();
}
virtual String GetStatusPayload() {
return "";
};
virtual void ApplyConfigJson(DynamicJsonDocument& doc) {
doc["name"] = Name;
doc["unique_id"] = UniqueId;
doc["state_topic"] = StateTopic;
doc["device_class"] = DeviceClass;
JsonObject device = doc.createNestedObject("device");
device["configuration_url"] = "http://" + WiFi.localIP().toString();
device["identifiers"] = WiFi.macAddress();
}
};
class MQTTBinarySensor : public MQTTEntity {
public:
bool State;
MQTTBinarySensor(String name) : MQTTEntity(name) {
SetType("binary_sensor");
State = false;
}
String GetStatusPayload() {
return State ? "ON" :"OFF";
}
virtual void SetOn(bool on) {
if (State == on) return;
State = on;
SendStatus();
}
};
class MQTTNumberSensor : public MQTTEntity {
public:
float Value;
String UnitOfMeasurement;
MQTTNumberSensor(String name) : MQTTEntity(name) {
SetType("sensor");
Value = 0;
UnitOfMeasurement = "%²";
}
String GetStatusPayload() {
return String(Value);
}
void ApplyConfigJson(DynamicJsonDocument& doc) {
MQTTEntity::ApplyConfigJson(doc);
doc["unit_of_measurement"] = UnitOfMeasurement;
}
virtual void SetValue(float value) {
if (Value == value) return;
Value = value;
SendStatus();
}
};
class MQTTSwitch : public MQTTBinarySensor {
public:
String CommandTopic;
void (*HandleOn)(bool on);
void (*HandleCommand)(String topic, String payload); // TODO do this ourselves
bool subscribed = false;
MQTTSwitch(String name) : MQTTBinarySensor(name) {
SetType("switch");
DeviceClass = "switch";
}
void SetType(String type) {
MQTTBinarySensor::SetType(type);
CommandTopic = "has_device/" + UniqueId + "/cmnd/" + Name;
}
void onMQTTConnected() {
MQTTBinarySensor::onMQTTConnected();
if (subscribed) return;
Subscribe(CommandTopic.c_str(), HandleCommand);
subscribed = true;
}
void ApplyConfigJson(DynamicJsonDocument& doc) {
MQTTBinarySensor::ApplyConfigJson(doc);
doc["command_topic"] = CommandTopic;
}
void SetOn(bool on) {
if (State == on) return;
MQTTBinarySensor::SetOn(on);
HandleOn(on);
}
};
class ESPHASDevice
{
public:
ESPHASDevice();
void init(String type, String version, String eepromVersion = "");
void loop();
void subscribe(String topic, void (*callback)(String topic2, String payload), int qos = 0);
void publish(String topic, String payload, bool retain = false);
String callURL(String server, int port, String url);
String callServer(String path);
String getURLStringParameter(String name, String defaultVal = "");
int getURLIntParameter(String name, int defaultVal = -1);
void sendResponse(String text);
void sendHtml(String html);
bool (*onHandleHttpCall)();
bool (*onHandleMQTT)(String topic, String payload);
void (*onMQTTConnecting)();
void (*onMQTTConnected)();
void (*onMQTTDisconnected)(bool wasConnected);
void (*onWifiConnected)();
void (*onWifiConnecting)();
void (*onWifiDisconnected)();
void (*onSettingsChanged)();
String (*onPageNotFound)();
ESP8266WebServer* httpServer;
ESP8266HTTPUpdateServer* httpUpdater;
PubSubClient* pubSubClient;
WiFiClient espClient;
bool inited;
void createTimer(unsigned long ms, bool repeats, void (*callback)(uint64_t now));
TimerDefinition *timers = nullptr;
void timerLoop();
////////////////////////////////////////////////////////////
// Configuration
////////////////////////////////////////////////////////////
String deviceName;
String deviceType;
String firmwareVersion;
uint8_t eepromVersion;
String pubSubClientId;
////////////////////////////////////////////////////////////
// Instance
////////////////////////////////////////////////////////////
EEPROMString* NameSetting;
/* template<typename T>
T* AddSetting(T* setting);
*/
EEPROMInt* AddSettingInt(String name, int defaultVal);
EEPROMFloat* AddSettingFloat(String name, float defaultVal);
EEPROMString* AddSettingString(String name, char defaultVal[60]);
EEPROMBool* AddSettingBool(String name, bool defaultVal);
MQTTEntity* FirstEntity = nullptr;
void AddEntity(MQTTEntity* entity);
MQTTSwitch* GetSwitch(String entityName);
MQTTBinarySensor* AddBinarySensor(String name);
MQTTNumberSensor* AddNumberSensor(String name);
MQTTSwitch* AddSwitch(String name, void (*callback)(bool on));
void RefreshMQTTEntities();
void SaveSettings();
void sendSettingsJson();
void sendTopics();
void setSettingsJson(String json);
void resetSettings();
void loadSettings();
void handleMQTT(String topic, String payload);
bool CanDoBlockingWork = true;
const char* ssid = "********";
const char* password = "********";
const char* mqtt_server_address = "****.local";
MQTTSubscription subscriptions[maxSubscriptions];
int subscriptionCount = 0;
void setup_wifi();
void connectMQTT();
void registerMQTT();
void handleHttpCall();
static uint64_t millis64() {
static uint32_t low32, high32;
uint32_t new_low32 = millis();
if (new_low32 < low32) high32++;
low32 = new_low32;
return (uint64_t) high32 << 32 | low32;
}
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment