5#include "mupplet_core.h"
8#define USTD_FEATURE_HOMEASSISTANT
88 static const char *version;
108 const char *manufacturer;
132 int tID = SCHEDULER_MAIN;
134#ifdef USTD_FEATURE_FILESYSTEM
141 String deviceManufacturer;
143 String deviceVersion;
145 String haTopicAttrib =
"ha/attribs/";
146 String haTopicConfig =
"!!homeassistant/";
149 bool autodiscovery =
false;
150 bool connected =
false;
156 String lastWillTopic;
157 String lastWillMessage;
160 ustd::array<Attributes> attribGroups;
161 ustd::array<Entity> entityConfigs;
173 HomeAssistant(String name, String manufacturer, String model, String version)
174 : deviceName(name), deviceManufacturer(manufacturer), deviceModel(model),
175 deviceVersion(version) {
179 for (
unsigned int i = 0; i < attribGroups.length(); i++) {
180 free((
void *)attribGroups[i].name);
182 for (
unsigned int i = 0; i < entityConfigs.length(); i++) {
183 free((
void *)entityConfigs[i].name);
193 void begin(Scheduler *_pSched,
bool initialAutodiscovery =
false) {
197#ifdef USTD_FEATURE_FILESYSTEM
198 autodiscovery = config.readBool(
"ha/autodiscovery", initialAutodiscovery);
199 if ((deviceId = config.readString(
"net/deviceid")) ==
"") {
201 deviceId = WiFi.macAddress();
202 deviceId.replace(
":",
"");
205 autodiscovery = initialAutodiscovery;
206 deviceId = WiFi.macAddress();
207 deviceId.replace(
":",
"");
214 pSched->subscribe(tID,
"mqtt/config", [
this](String topic, String msg, String orig) {
215 this->onMqttConfig(topic, msg, orig);
217 pSched->subscribe(tID,
"mqtt/state", [
this](String topic, String msg, String orig) {
218 this->onMqttState(topic, msg, orig);
220 pSched->subscribe(tID,
"net/network", [
this](String topic, String msg, String orig) {
221 this->onNetNetwork(topic, msg, orig);
223 pSched->subscribe(tID,
"net/rssi", [
this](String topic, String msg, String orig) {
224 this->onNetRssi(topic, msg, orig);
228 pSched->subscribe(tID,
"ha/state/#", [
this](String topic, String msg, String originator) {
229 this->onCommand(topic.substring(11), msg);
233 pSched->publish(
"net/network/get");
234 pSched->publish(
"mqtt/state/get");
244 if (autodiscovery != enabled) {
245 autodiscovery = enabled;
249#ifdef USTD_FEATURE_FILESYSTEM
250 config.writeBool(
"ha/autodiscovery", autodiscovery);
271 void addAttributes(String attribGroup, String manufacturer =
"", String model =
"",
272 String version =
"") {
274 for (
unsigned int i = 0; i < attribGroups.length(); i++) {
275 if (attribGroup == attribGroups[i].name) {
282 String *man = manufacturer.length() ? &manufacturer : &deviceManufacturer;
283 String *mod = model.length() ? &model : &deviceModel;
284 String *ver = version.length() ? &version : &deviceVersion;
286 att.name = (
char *)malloc(attribGroup.length() + man->length() + mod->length() +
289 att.manufacturer = copyfieldgetnext(att.name, attribGroup);
290 att.model = copyfieldgetnext(att.manufacturer, *man);
291 att.version = copyfieldgetnext(att.model, *mod);
292 strcpy((
char *)att.version, ver->c_str());
293 if (attribGroups.add(att) == -1) {
294 free((
void *)att.name);
313 void addSwitch(String name, String human =
"", String dev_cla =
"", String icon =
"",
314 String attribs =
"") {
315 addGenericActor(
Switch, name, -1, human, dev_cla, icon, attribs);
333 void addSwitch(String name,
int channel, String human =
"", String dev_cla =
"",
334 String icon =
"", String attribs =
"") {
336 addGenericActor(
Switch, name, channel, human, dev_cla, icon, attribs);
355 void addMultiSwitch(String name,
int count, String human =
"", String dev_cla =
"",
356 String icon =
"", String attribs =
"") {
358 addGenericActor(
Switch, name, -count, human, dev_cla, icon, attribs);
378 String attribs =
"", String effects =
"") {
380 addGenericActor(type, name, -1, human,
"", icon, attribs, effects);
401 String icon =
"", String attribs =
"", String effects =
"") {
403 addGenericActor(type, name, channel, human,
"", icon, attribs, effects);
423 String icon =
"", String attribs =
"") {
425 addGenericActor(type, name, -count, human,
"", icon, attribs);
448 void addSensor(String name, String value, String human =
"", String dev_cla =
"",
449 String unit =
"", String icon =
"", String val_tpl =
"",
int exp_aft = -1,
450 bool frc_upd =
false, String attribs =
"") {
451 addGenericSensor(Sensor, name, value, -1, human, dev_cla, unit, icon, val_tpl, exp_aft,
452 frc_upd, -1, attribs);
475 void addSensor(String name, String value,
int channel, String human =
"", String dev_cla =
"",
476 String unit =
"", String icon =
"", String val_tpl =
"",
int exp_aft = -1,
477 bool frc_upd =
false, String attribs =
"") {
479 addGenericSensor(Sensor, name, value, channel, human, dev_cla, unit, icon, val_tpl,
480 exp_aft, frc_upd, -1, attribs);
505 String dev_cla =
"", String unit =
"", String icon =
"",
506 String val_tpl =
"",
int exp_aft = -1,
bool frc_upd =
false,
507 String attribs =
"") {
509 addGenericSensor(Sensor, name, value, -count, human, dev_cla, unit, icon, val_tpl,
510 exp_aft, frc_upd, -1, attribs);
535 void addBinarySensor(String name, String value, String human =
"", String dev_cla =
"",
536 String unit =
"", String icon =
"", String val_tpl =
"",
int exp_aft = -1,
537 bool frc_upd =
false,
int off_dly = -1, String attribs =
"") {
538 addGenericSensor(
BinarySensor, name, value, -1, human, dev_cla, unit, icon, val_tpl,
539 exp_aft, frc_upd, off_dly, attribs);
565 String dev_cla =
"", String unit =
"", String icon =
"",
566 String val_tpl =
"",
int exp_aft = -1,
bool frc_upd =
false,
567 int off_dly = -1, String attribs =
"") {
569 addGenericSensor(
BinarySensor, name, value, channel, human, dev_cla, unit, icon,
570 val_tpl, exp_aft, frc_upd, off_dly, attribs);
597 String dev_cla =
"", String unit =
"", String icon =
"",
598 String val_tpl =
"",
int exp_aft = -1,
bool frc_upd =
false,
599 int off_dly = -1, String attribs =
"") {
601 addGenericSensor(
BinarySensor, name, value, -count, human, dev_cla, unit, icon, val_tpl,
602 exp_aft, frc_upd, off_dly, attribs);
607 void onMqttConfig(String topic, String msg, String originator) {
608 if (originator ==
"mqtt") {
611 pathPrefix = shift(msg,
'+');
612 lastWillTopic = shift(msg,
'+');
613 lastWillMessage = shift(msg,
'+');
616 void onMqttState(String topic, String msg, String originator) {
617 if (originator ==
"mqtt") {
620 bool previous = connected;
621 connected = msg ==
"connected";
622 if (connected != previous) {
627 void onNetNetwork(String topic, String msg, String originator) {
628 if (originator ==
"mqtt") {
631 JSONVar mqttMsg = JSON.parse(msg);
632 if (JSON.typeof(mqttMsg) ==
"undefined") {
635 if (!strcmp((
const char *)mqttMsg[
"state"],
"connected")) {
636 ipAddress = (
const char *)mqttMsg[
"ip"];
637 macAddress = (
const char *)mqttMsg[
"mac"];
638 hostName = (
const char *)mqttMsg[
"hostname"];
642 void onNetRssi(String topic, String msg, String originator) {
643 if (originator ==
"mqtt") {
652 void onCommand(String topic, String msg) {
653 if (topic ==
"get") {
655 }
else if (topic ==
"set") {
658 if (msg ==
"on" || msg ==
"true") {
660 }
else if (msg ==
"off" || msg ==
"false") {
666 void addGenericActor(
DeviceType type, String name,
int channel, String human =
"",
667 String dev_cla =
"", String icon =
"", String attribs =
"", String effects =
"") {
668 size_t len = name.length() + human.length() + dev_cla.length() + icon.length() +
669 attribs.length() + effects.length() + 6;
671 entity.name = (
const char *)malloc(len);
674 entity.channel = channel;
675 entity.human = copyfieldgetnext(entity.name, name);
676 entity.dev_cla = copyfieldgetnext(entity.human, human);
677 entity.icon = copyfieldgetnext(entity.dev_cla, dev_cla);
678 entity.attribs = copyfieldgetnext(entity.icon, icon);
679 entity.effects = copyfieldgetnext(entity.attribs, attribs);
680 copyfieldgetnext(entity.effects, effects);
681 if (entityConfigs.add(entity) == -1) {
682 free((
void *)entity.name);
687 void addGenericSensor(
DeviceType type, String name, String value,
int channel,
688 String human =
"", String dev_cla =
"", String unit =
"",
689 String icon =
"", String val_tpl =
"",
int exp_aft = -1,
690 bool frc_upd =
false,
int off_dly = -1, String attribs =
"") {
691 size_t len = name.length() + value.length() + human.length() + dev_cla.length() +
692 unit.length() + icon.length() + val_tpl.length() + attribs.length() + 8;
695 entity.name = (
const char *)malloc(len);
698 entity.channel = channel;
699 entity.value = copyfieldgetnext(entity.name, name);
700 entity.human = copyfieldgetnext(entity.value, value);
701 entity.dev_cla = copyfieldgetnext(entity.human, human);
702 entity.unit = copyfieldgetnext(entity.dev_cla, dev_cla);
703 entity.icon = copyfieldgetnext(entity.unit, unit);
704 entity.val_tpl = copyfieldgetnext(entity.icon, icon);
705 entity.attribs = copyfieldgetnext(entity.val_tpl, val_tpl);
706 copyfieldgetnext(entity.attribs, attribs);
707 entity.off_dly = off_dly;
708 entity.exp_aft = exp_aft;
709 entity.frc_upd = frc_upd;
710 if (entityConfigs.add(entity) == -1) {
711 free((
void *)entity.name);
716 static const char *getDeviceClass(
DeviceType type) {
721 return "binary_sensor";
734 String getConfigTopic(
DeviceType type,
const char *uniq_id) {
735 return haTopicConfig + getDeviceClass(type) +
"/" + uniq_id +
"/config";
738 void flushDeviceConfig(
DeviceType type, JSONVar &msg) {
739 msg[
"dev"][
"ids"][0] = deviceId;
740 msg[
"dev"][
"name"] = deviceName;
741 msg[
"dev"][
"mf"] = deviceManufacturer;
742 msg[
"dev"][
"mdl"] = deviceModel;
743 msg[
"dev"][
"sw"] = deviceVersion;
749 pSched->publish(getConfigTopic(type, msg[
"uniq_id"]), JSON.stringify(msg));
754 void publishDeviceConfig() {
756 msg[
"~"] = pathPrefix +
"/";
757 msg[
"name"] = hostName +
" Status";
758 msg[
"stat_t"] =
"~ha/attribs/device";
759 msg[
"avty_t"] =
"~mqtt/state";
760 msg[
"pl_avail"] =
"connected";
761 msg[
"pl_not_avail"] = lastWillMessage;
762 msg[
"json_attr_t"] =
"~ha/attribs/device";
763 msg[
"unit_of_meas"] =
"%";
764 msg[
"val_tpl"] =
"{{value_json['RSSI']}}";
765 msg[
"ic"] =
"mdi:information-outline";
766 msg[
"uniq_id"] = deviceId +
"_status";
767 flushDeviceConfig(DeviceType::Sensor, msg);
770 static String getEntityName(Entity &entity) {
771 if (entity.human && *entity.human) {
773 }
else if (entity.value && *entity.value) {
774 return String(entity.name) +
" " + entity.value;
780 String getEntityKey(Entity &entity) {
782 if (entity.value && *entity.value) {
783 retVal = deviceId +
"_" + entity.name +
"_" + entity.value;
785 retVal = deviceId +
"_" + entity.name;
787 retVal.replace(
" ",
"_");
791 static String getEntityTopic(Entity &entity) {
792 String entityTopic = entity.name;
793 entityTopic.concat(
"/");
794 entityTopic.concat(getDeviceClass(entity.type));
795 entityTopic.replace(
" ",
"_");
799 void publishConfigs() {
801 publishDeviceConfig();
804 for (
unsigned int i = 0; i < entityConfigs.length(); i++) {
805 publishConfig(entityConfigs[i]);
809 void publishConfig(Entity &entity) {
810 String name = getEntityName(entity);
811 String key = getEntityKey(entity);
812 String topic = getEntityTopic(entity);
814 if (entity.channel == -1) {
815 publishConfig(entity, name, key, topic);
816 }
else if (entity.channel < -1) {
817 for (
int i = 0; i < abs(entity.channel); i++) {
818 publishConfig(entity, name +
"." + i, key +
"_" + i, topic +
"/" + i);
821 int i = entity.channel;
822 publishConfig(entity, name +
"." + i, key +
"_" + i, topic +
"/" + i);
826 void publishConfig(Entity &entity, String name, String key, String topic) {
828 msg[
"~"] = pathPrefix +
"/";
829 msg[
"name"] = hostName +
" " + name;
830 msg[
"uniq_id"] = key;
831 msg[
"avty_t"] =
"~mqtt/state";
832 msg[
"pl_avail"] =
"connected";
833 msg[
"pl_not_avail"] = lastWillMessage;
834 msg[
"json_attr_t"] =
"~" + haTopicAttrib + (*entity.attribs ? entity.attribs :
"device");
835 if (*entity.dev_cla) {
836 msg[
"dev_cla"] = entity.dev_cla;
839 msg[
"ic"] = entity.icon;
841 switch (entity.type) {
851 publishLightConfig(msg, entity, topic);
853 case DeviceType::Sensor:
855 publishSensorConfig(msg, entity, topic);
858 publishSwitchConfig(msg, entity, topic);
865 void publishLightConfig(JSONVar &msg, Entity &entity, String &topic) {
866 msg[
"stat_t"] =
"~" + topic +
"/state";
867 msg[
"cmd_t"] = hostName +
"/" + topic +
"/set";
868 msg[
"payload_on"] =
"on";
869 msg[
"payload_off"] =
"off";
873 msg[
"bri_cmd_t"] = hostName +
"/" + topic +
"/set";
874 msg[
"bri_scl"] =
"100";
875 msg[
"bri_stat_t"] =
"~" + topic +
"/unitbrightness";
876 msg[
"bri_val_tpl"] =
"{{ value | float * 100 | round(0) }}";
877 msg[
"on_cmd_type"] =
"brightness";
881 msg[
"bri_cmd_t"] = hostName +
"/" + topic +
"/set";
882 msg[
"bri_scl"] =
"100";
883 msg[
"bri_stat_t"] =
"~" + topic +
"/unitbrightness";
884 msg[
"bri_val_tpl"] =
"{{ value | float * 100 | round(0) }}";
885 msg[
"on_cmd_type"] =
"first";
887 msg[
"color_mode"] =
true;
888 switch (entity.type) {
890 msg[
"supported_color_modes"] = {
"rgb"};
893 msg[
"supported_color_modes"] = {
"rgbw"};
896 msg[
"supported_color_modes"] = {
"rgbww"};
901 msg[
"rgb_cmd_t"] = hostName +
"/" + topic +
"/color/set";
902 msg[
"rgb_stat_t"] =
"~" + topic +
"/color";
903 if (strcmp(entity.effects,
"")) {
904 msg[
"effect_command_topic"] = hostName +
"/" + topic +
"/effect/set";
905 msg[
"effect_state_topic"] =
"~" + topic +
"/effect";
910 String effs = entity.effects;
911 ind = effs.indexOf(
',');
913 String tmp = effs.substring(0, ind);
917 effs = effs.substring(ind + 1);
918 ind = effs.indexOf(
',');
923 msg[
"effect_list"] = elst;
926 flushDeviceConfig(entity.type, msg);
929 void publishSensorConfig(JSONVar &msg, Entity &entity, String &topic) {
930 msg[
"stat_t"] =
"~" + topic +
"/" + entity.value;
931 if (*entity.val_tpl) {
932 msg[
"val_tpl"] = entity.val_tpl;
935 msg[
"unit_of_meas"] = entity.unit;
937 if (entity.exp_aft != -1) {
938 msg[
"exp_aft"] = entity.exp_aft;
940 if (entity.frc_upd) {
941 msg[
"frc_upd"] =
"true";
943 flushDeviceConfig(entity.type, msg);
946 void publishSwitchConfig(JSONVar &msg, Entity &entity, String &topic) {
947 msg[
"stat_t"] =
"~" + topic +
"/state";
948 msg[
"cmd_t"] = hostName +
"/" + topic +
"/set";
949 msg[
"payload_on"] =
"on";
950 msg[
"payload_off"] =
"off";
951 if (*entity.dev_cla) {
952 msg[
"dev_cla"] = entity.dev_cla;
954 flushDeviceConfig(entity.type, msg);
957 void unpublishConfigs() {
959 pSched->publish(
"!homeassistant/sensor/" + deviceId +
"_status/config");
962 for (
unsigned int i = 0; i < entityConfigs.length(); i++) {
963 unpublishConfig(entityConfigs[i]);
967 void unpublishConfig(Entity &entity) {
968 String entityKey = getEntityKey(entity);
969 String configTopic = haTopicConfig + getDeviceClass(entity.type) +
"/";
970 if (entity.channel == -1) {
971 pSched->publish(configTopic + entityKey +
"/config");
972 }
else if (entity.channel < -1) {
973 for (
int channel = 0; channel < abs(entity.channel); channel++) {
974 pSched->publish(configTopic + entityKey +
"_" + channel +
"/config");
977 pSched->publish(configTopic + entityKey +
"_" + entity.channel +
"/config");
981 void publishAttribs() {
982 JSONVar msg = JSON.parse(
"{}");
983 msg[
"RSSI"] = String(WifiGetRssiAsQuality(rssiVal));
984 msg[
"Signal (dBm)"] = String(rssiVal);
985 msg[
"Mac"] = macAddress;
986 msg[
"IP"] = ipAddress;
987 msg[
"Host"] = hostName;
988 for (
unsigned int i = 0; i < attribGroups.length(); i++) {
989 msg[
"Manufacturer"] = attribGroups[i].manufacturer;
990 msg[
"Model"] = attribGroups[i].model;
991 msg[
"Version"] = attribGroups[i].version;
992 pSched->publish(haTopicAttrib + attribGroups[i].name, JSON.stringify(msg));
996 void unpublishAttribs() {
997 for (
unsigned int i = 0; i < attribGroups.length(); i++) {
998 pSched->publish(haTopicAttrib + attribGroups[i].name);
1002 void publishState() {
1003 pSched->publish(
"ha/state", autodiscovery ?
"on" :
"off");
1008 if (autodiscovery) {
1018 static const char *copyfieldgetnext(
const char *field, String &value) {
1019 strcpy((
char *)field, value.c_str());
1020 return field + value.length() + 1;
1023 static int WifiGetRssiAsQuality(
int rssi) {
1028 }
else if (rssi >= -50) {
1031 quality = 2 * (rssi + 100);
1037const char *HomeAssistant::version =
"0.1.0";
Definition: home_assistant.h:86
void addLight(String name, String human="", DeviceType type=LightDim, String icon="", String attribs="", String effects="")
Definition: home_assistant.h:377
DeviceType
HomeAssistant Device Type.
Definition: home_assistant.h:91
@ LightRGBWW
A color light with additional white component.
Definition: home_assistant.h:101
@ Switch
Sensor reporting binary states.
Definition: home_assistant.h:95
@ BinarySensor
Sensor reporting numerical or state values.
Definition: home_assistant.h:94
@ LightRGBW
A color light.
Definition: home_assistant.h:100
@ LightRGB
A light with light temperature that can be dimmed.
Definition: home_assistant.h:99
@ LightWW
A light that can be dimmed.
Definition: home_assistant.h:98
@ LightDim
A simple light that can only be switched on and off.
Definition: home_assistant.h:97
@ Light
A simple device that can only be switched on and off.
Definition: home_assistant.h:96
void addMultiSwitch(String name, int count, String human="", String dev_cla="", String icon="", String attribs="")
Definition: home_assistant.h:355
void begin(Scheduler *_pSched, bool initialAutodiscovery=false)
Definition: home_assistant.h:193
void setAutoDiscovery(bool enabled)
Definition: home_assistant.h:243
HomeAssistant(String name, String manufacturer, String model, String version)
Definition: home_assistant.h:173
void addAttributes(String attribGroup, String manufacturer="", String model="", String version="")
Definition: home_assistant.h:271
void addBinarySensor(String name, String value, String human="", String dev_cla="", String unit="", String icon="", String val_tpl="", int exp_aft=-1, bool frc_upd=false, int off_dly=-1, String attribs="")
Definition: home_assistant.h:535
void addSensor(String name, String value, String human="", String dev_cla="", String unit="", String icon="", String val_tpl="", int exp_aft=-1, bool frc_upd=false, String attribs="")
Definition: home_assistant.h:448
void addMultiBinarySensor(String name, String value, int count, String human="", String dev_cla="", String unit="", String icon="", String val_tpl="", int exp_aft=-1, bool frc_upd=false, int off_dly=-1, String attribs="")
Definition: home_assistant.h:596
void addMultiLight(String name, int count, String human="", DeviceType type=LightDim, String icon="", String attribs="")
Definition: home_assistant.h:422
void addSwitch(String name, int channel, String human="", String dev_cla="", String icon="", String attribs="")
Definition: home_assistant.h:333
void addSensor(String name, String value, int channel, String human="", String dev_cla="", String unit="", String icon="", String val_tpl="", int exp_aft=-1, bool frc_upd=false, String attribs="")
Definition: home_assistant.h:475
void addMultiSensor(String name, String value, int count, String human="", String dev_cla="", String unit="", String icon="", String val_tpl="", int exp_aft=-1, bool frc_upd=false, String attribs="")
Definition: home_assistant.h:504
void addLight(String name, int channel, String human="", DeviceType type=LightDim, String icon="", String attribs="", String effects="")
Definition: home_assistant.h:400
void addBinarySensor(String name, String value, int channel, String human="", String dev_cla="", String unit="", String icon="", String val_tpl="", int exp_aft=-1, bool frc_upd=false, int off_dly=-1, String attribs="")
Definition: home_assistant.h:564
void addSwitch(String name, String human="", String dev_cla="", String icon="", String attribs="")
Definition: home_assistant.h:313
mupplet-core GPIO Light class
Definition: mup_light.h:57
mupplet-core GPIO Switch class
Definition: mup_switch.h:136
The muwerk namespace.
Definition: home_assistant.h:10
long parseLong(String arg, long defaultVal)
Definition: mupplet_core.h:101