Created
December 24, 2022 04:29
-
-
Save philharlow/2314103834c6515cbb8b339fd2691131 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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