Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
d3450b6
Inital implementation of ROProperty & RWProperty
wayne-love Feb 3, 2026
59d79a0
Extend Propery classes to be able to set and return via human readabl…
wayne-love Feb 6, 2026
3ca7e45
Fix missed call in SpaUtils JSON response constructor
wayne-love Feb 6, 2026
2e1220d
Refactor sleep timer properties and update related functions for cons…
wayne-love Feb 18, 2026
65a38a3
Migrate snooze start and end times to RWProperty
wayne-love Feb 19, 2026
d9d72a4
Refactor sleep timer state handling to use setLabel method for improv…
wayne-love Feb 19, 2026
ad36542
Debug github build
wayne-love Feb 20, 2026
a12533c
testing 2020 standards compliance
wayne-love Feb 20, 2026
b63ffee
Pin platform version
wayne-love Feb 21, 2026
b275c59
Explicitly define pioarduino
wayne-love Feb 21, 2026
87d5ae1
Migrate HPMP and ColorMode
wayne-love Feb 21, 2026
42c1f47
Remove explicit validators
wayne-love Feb 22, 2026
884fbdc
Migrate LBRTValue
wayne-love Feb 22, 2026
0d0b2b2
Migreate LSPD + fix dumb error with assignement of LBRT
wayne-love Feb 22, 2026
7c8921d
Migrate CurrClr
wayne-love Feb 23, 2026
d028ed9
Migrate PumpStatuses + update gitignore
wayne-love Mar 7, 2026
1d34cc9
Migrate HELE
wayne-love Mar 7, 2026
bdd994d
Migrate SpaDayOfWeek
wayne-love Mar 8, 2026
c687dd8
migrate Blower
wayne-love Mar 8, 2026
6527e71
Migrate Mode
wayne-love Mar 8, 2026
eb96f77
Migrate VARIValue
wayne-love Mar 8, 2026
ddad3d6
Migrate Filter Block Hours
wayne-love Mar 8, 2026
8980b48
Migrate FiltHrs
wayne-love Mar 8, 2026
68b70e1
Migrate LockMode and fix visabilit of map decs.
wayne-love Mar 8, 2026
4c90195
Remove unneeded getSTMP
wayne-love Mar 8, 2026
f266d18
Migrate SpaTIme
wayne-love Mar 8, 2026
e3f9cdf
Tidy up of migrations
wayne-love Mar 8, 2026
7eecf1b
Tidy up SpaTime, Migrate RB_TP_Light
wayne-love Mar 8, 2026
a03cb69
Final depreciation of SpaProperites
wayne-love Mar 8, 2026
957e544
update comments
wayne-love Mar 12, 2026
baf1d20
Comment tidy up
wayne-love Mar 12, 2026
1929244
Add ss (send serial) command to debug interface.
wayne-love Mar 13, 2026
017965a
Change debug cmd from spa to ss
wayne-love Mar 13, 2026
ed7da15
Fix bad option list for blower, extend ROProperty for runtime modific…
wayne-love Mar 21, 2026
9b78f31
Add Heat Element Current measure
wayne-love Apr 4, 2026
85b078b
Extend timeouts on SS command to catch long running commands
wayne-love Apr 5, 2026
fff3095
Add W01 to W07 commands to Register Map
wayne-love Apr 5, 2026
e20888a
Update register map with discovered functions
wayne-love Apr 8, 2026
c321a0e
Add "Keyboard Button Press" button to allow spofing of button presses…
wayne-love Apr 8, 2026
2f0992b
Fix missing updates
wayne-love Apr 9, 2026
5575f0b
Update VMAX description from voltage to current
wayne-love Jun 20, 2026
b9317a0
Add VMAX as RWProperty
wayne-love Jun 20, 2026
c66156d
Merge branch 'SpaInterface-TNG' of https://github.com/wayne-love/ESPy…
wayne-love Jun 20, 2026
5bfebf0
Change VMax to number not text
wayne-love Jul 4, 2026
bdd8f5a
Add CLMT as settable & Sanatize cycle start time
wayne-love Jul 4, 2026
df474de
Merge branch 'master' into SpaInterface-TNG
wayne-love Jul 15, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/build_firmware.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
- name: Build file system image for ${{ inputs.platform }}
run: pio run --target buildfs -e ${{ inputs.platform }}
- name: Build for ${{ inputs.platform }}
run: pio run -e ${{ inputs.platform }} -t mergebin
run: pio run -e ${{ inputs.platform }} -v -t mergebin
#- name: Display firmware files
# run: ls -la .pio/build/${{ inputs.platform }}/firmware*.bin
- name: Rename and move firmware files
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
.vscode/ipch
.vscode/extensions.json
.DS_Store
.vscode/extensions.json
.vscode/extensions.json
52 changes: 19 additions & 33 deletions lib/HAAutoDiscovery/HAAutoDiscovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,50 +70,36 @@ void generateTextAdJSON(String& output, const AutoDiscoveryInformationTemplate&
serializeJson(json, output);
}

void generateSwitchAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic) {
void generateNumberAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic, String unitOfMeasure, int min, int max, int step) {
JsonDocument json;
generateCommonAdJSON(json, config, spa, discoveryTopic, "switch");
generateCommonAdJSON(json, config, spa, discoveryTopic, "number");

json["command_topic"] = spa.commandTopic + "/" + config.propertyId;
if (!unitOfMeasure.isEmpty()) json["unit_of_measurement"] = unitOfMeasure;
json["mode"] = "box";
json["min"] = min;
json["max"] = max;
json["step"] = step;

serializeJson(json, output);
}

void generateFanAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic, int min, int max, const String* modes, const size_t modesSize) {
void generateSwitchAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic) {
JsonDocument json;
generateCommonAdJSON(json, config, spa, discoveryTopic, "fan");
generateCommonAdJSON(json, config, spa, discoveryTopic, "switch");

// Find the last character that is not a space or curly brace
int lastIndex = config.valueTemplate.length() - 1;
while (lastIndex >= 0 && (config.valueTemplate[lastIndex] == ' ' || config.valueTemplate[lastIndex] == '}')) {
lastIndex--;
}
json["state_value_template"] = config.valueTemplate.substring(0, lastIndex + 1) + ".state" + config.valueTemplate.substring(lastIndex + 1);
json["command_topic"] = spa.commandTopic + "/" + config.propertyId + "_state";

if (max > min) {
json["percentage_state_topic"] = spa.stateTopic;
json["percentage_command_topic"] = spa.commandTopic + "/" + config.propertyId + "_speed";
json["percentage_value_template"] = config.valueTemplate.substring(0, lastIndex + 1) + ".speed" + config.valueTemplate.substring(lastIndex + 1);

json["speed_range_min"]=min;
json["speed_range_max"]=max;
}

if (modes != nullptr && modesSize > 0) {
json["preset_mode_state_topic"] = spa.stateTopic;
json["preset_mode_command_topic"] = spa.commandTopic + "/" + config.propertyId + "_mode";
json["preset_mode_value_template"] = config.valueTemplate.substring(0, lastIndex + 1) + ".mode" + config.valueTemplate.substring(lastIndex + 1);

JsonArray jsonModes = json["preset_modes"].to<JsonArray>();
//for (const auto& mode : *modes) jsonModes.add(mode);
for (size_t i = 0; i < modesSize; ++i) jsonModes.add(modes[i]);
}
json["command_topic"] = spa.commandTopic + "/" + config.propertyId;

if (config.propertyId.startsWith("pump")) {
json["icon"] = "mdi:pump";
}
serializeJson(json, output);
}

void generateButtonAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic) {
JsonDocument json;
generateCommonAdJSON(json, config, spa, discoveryTopic, "button");
json.remove("state_topic");
json.remove("value_template");
json["command_topic"] = spa.commandTopic + "/" + config.propertyId;
json["payload_press"] = "PRESS";
serializeJson(json, output);
}

Expand Down
102 changes: 101 additions & 1 deletion lib/HAAutoDiscovery/HAAutoDiscovery.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <Arduino.h>
#include <ArduinoJson.h>
#include "SpaInterface.h"


/// @brief Configuration structure for the data elements for the Spa.
Expand Down Expand Up @@ -38,7 +39,9 @@ void generateCommonAdJSON(JsonDocument& json, const AutoDiscoveryInformationTemp
void generateSensorAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic, String stateClass="", String unitOfMeasure="");
void generateBinarySensorAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic);
void generateTextAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic, String regex="");
void generateNumberAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic, String unitOfMeasure="", int min=0, int max=100, int step=1);
void generateSwitchAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic);
void generateButtonAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic);

template <typename T, size_t N>
void generateSelectAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic, const std::array<T, N>& options) {
Expand All @@ -52,7 +55,63 @@ void generateSelectAdJSON(String& output, const AutoDiscoveryInformationTemplate
serializeJson(json, output);
}

void generateFanAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic, int min, int max, const String* modes, const size_t modesSize=0);
template <typename T>
void generateSelectAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic, const SpaInterface::ROProperty<T>& prop) {
JsonDocument json;
generateCommonAdJSON(json, config, spa, discoveryTopic, "select");

json["command_topic"] = spa.commandTopic + "/" + config.propertyId;
JsonArray opts = json["options"].to<JsonArray>();
const size_t count = prop.getLabelCount();
for (size_t i = 0; i < count; i++) {
opts.add(prop.getLabelAt(i));
}

serializeJson(json, output);
}

template <typename T>
void generateFanAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic, int min, int max, const SpaInterface::ROProperty<T>& prop) {
JsonDocument json;
generateCommonAdJSON(json, config, spa, discoveryTopic, "fan");

// Find the last character that is not a space or curly brace
int lastIndex = config.valueTemplate.length() - 1;
while (lastIndex >= 0 && (config.valueTemplate[lastIndex] == ' ' || config.valueTemplate[lastIndex] == '}')) {
lastIndex--;
}
json["state_value_template"] = config.valueTemplate.substring(0, lastIndex + 1) + ".state" + config.valueTemplate.substring(lastIndex + 1);
json["command_topic"] = spa.commandTopic + "/" + config.propertyId + "_state";

if (max > min) {
json["percentage_state_topic"] = spa.stateTopic;
json["percentage_command_topic"] = spa.commandTopic + "/" + config.propertyId + "_speed";
json["percentage_value_template"] = config.valueTemplate.substring(0, lastIndex + 1) + ".speed" + config.valueTemplate.substring(lastIndex + 1);

json["speed_range_min"]=min;
json["speed_range_max"]=max;
}

const size_t count = prop.getLabelCount();
if (count > 0) {
json["preset_mode_state_topic"] = spa.stateTopic;
json["preset_mode_command_topic"] = spa.commandTopic + "/" + config.propertyId + "_mode";
json["preset_mode_value_template"] = config.valueTemplate.substring(0, lastIndex + 1) + ".mode" + config.valueTemplate.substring(lastIndex + 1);

JsonArray jsonModes = json["preset_modes"].to<JsonArray>();
for (size_t i = 0; i < count; i++) jsonModes.add(prop.getLabelAt(i));

}

if (config.propertyId.startsWith("pump")) {
json["icon"] = "mdi:pump";
}

serializeJson(json, output);

}



template <typename T, size_t N>
void generateLightAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic, const std::array<T, N>& colorModes) {
Expand Down Expand Up @@ -94,6 +153,47 @@ void generateLightAdJSON(String& output, const AutoDiscoveryInformationTemplate&
serializeJson(json, output);
}

template <typename T>
void generateLightAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic, const SpaInterface::ROProperty<T>& prop) {
JsonDocument json;
generateCommonAdJSON(json, config, spa, discoveryTopic, "light");

json["brightness_state_topic"] = spa.stateTopic;
json["color_mode_state_topic"] = spa.stateTopic;
json["effect_state_topic"] = spa.stateTopic;
json["hs_state_topic"] = spa.stateTopic;

json["command_topic"] = spa.commandTopic + "/" + config.propertyId + "_state";
json["brightness_command_topic"] = spa.commandTopic + "/" + config.propertyId + "_brightness";
json["effect_command_topic"] = spa.commandTopic + "/" + config.propertyId + "_effect";
json["hs_command_topic"] = spa.commandTopic + "/" + config.propertyId + "_color";

int lastIndex = config.valueTemplate.length() - 1;
while (lastIndex >= 0 && (config.valueTemplate[lastIndex] == ' ' || config.valueTemplate[lastIndex] == '}')) {
lastIndex--;
}

json["state_value_template"] = config.valueTemplate.substring(0, lastIndex + 1) + ".state" + config.valueTemplate.substring(lastIndex + 1);
json["brightness_value_template"] = config.valueTemplate.substring(0, lastIndex + 1) + ".brightness" + config.valueTemplate.substring(lastIndex + 1);
json["effect_value_template"] = config.valueTemplate.substring(0, lastIndex + 1) + ".effect" + config.valueTemplate.substring(lastIndex + 1);
json["hs_value_template"] = config.valueTemplate.substring(0, lastIndex + 1) + ".color.h" + config.valueTemplate.substring(lastIndex + 1) + ","
+ config.valueTemplate.substring(0, lastIndex + 1) + ".color.s" + config.valueTemplate.substring(lastIndex + 1);
json["color_mode_value_template"] = config.valueTemplate.substring(0, lastIndex + 1) + ".color_mode" + config.valueTemplate.substring(lastIndex + 1);

json["brightness"] = true;
json["brightness_scale"]=5;
json["effect"] = true;
JsonArray effect_list = json["effect_list"].to<JsonArray>();
const size_t count = prop.getLabelCount();
for (size_t i = 0; i < count; i++) {
effect_list.add(prop.getLabelAt(i));
}
JsonArray color_modes = json["supported_color_modes"].to<JsonArray>();
color_modes.add("hs");

serializeJson(json, output);
}

void generateClimateAdJSON(String& output, const AutoDiscoveryInformationTemplate& config, const SpaADInformationTemplate& spa, String &discoveryTopic);

/*
Expand Down
Loading
Loading