1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-16 23:05:46 +00:00

Compare commits

...

52 Commits

Author SHA1 Message Date
Jesse Hills
a91e6a6bdf Merge pull request #1959 from esphome/bump-1.19.4
1.19.4
2021-06-24 13:31:01 +12:00
Jesse Hills
8600620305 Bump version to v1.19.4 2021-06-24 12:49:45 +12:00
Jesse Hills
96721f305f Bump dashboard to 20210623.0 (#1958) 2021-06-24 12:49:45 +12:00
Otto Winter
2bf70d7d00 Compat argv parsing improvements (#1952) 2021-06-24 12:49:45 +12:00
Otto Winter
1d8c170f48 Add climate preset NONE again (#1951) 2021-06-24 12:49:45 +12:00
Otto Winter
6009c7edb4 Disallow power_save_mode NONE if used together with BLE (#1950) 2021-06-24 12:49:44 +12:00
Otto Winter
e3f36c033e API raise minor version for climate changes (#1947) 2021-06-24 12:49:44 +12:00
Otto Winter
d4eb0f1655 Rework climate traits (#1941)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-24 12:49:36 +12:00
Jesse Hills
e20ec00071 Merge pull request #1956 from esphome/bump-1.19.3
1.19.3
2021-06-23 20:16:42 +12:00
Jesse Hills
150114d774 Bump version to v1.19.3 2021-06-23 19:39:37 +12:00
Jesse Hills
89dfa5ea82 Bump esphome-dashboard to 20210622.0 (#1955) 2021-06-23 19:39:31 +12:00
Jesse Hills
97aa930ad2 Merge pull request #1943 from esphome/bump-1.19.2
1.19.2
2021-06-21 14:59:11 +12:00
Jesse Hills
2a5def10e7 Bump version to v1.19.2 2021-06-21 14:40:05 +12:00
Jesse Hills
969834e037 Fix bad climate control enum (#1942) 2021-06-21 14:40:05 +12:00
Jesse Hills
d73a44c504 Allow wifi setup to proceed when there is no sta or ap (#1931) 2021-06-21 14:40:05 +12:00
Sergey V. DUDANOV
8aec092ab6 Fix midea_ac query frame (#1940) 2021-06-21 14:40:05 +12:00
Chris Nussbaum
4fa959ba45 Don't send Tuya commands while currently receiving a message (#1886)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
2021-06-21 14:40:05 +12:00
Jesse Hills
b43712d78d Merge pull request #1936 from esphome/bump-1.19.1
1.19.1
2021-06-18 12:10:30 +12:00
Jesse Hills
01904a0f10 Bump version to v1.19.1 2021-06-18 11:52:02 +12:00
Jesse Hills
dd875e7529 Replace CLIMATE_MODE_AUTO with CLIMATE_MODE_HEAT_COOL in most cases (#1933) 2021-06-18 11:52:02 +12:00
Otto Winter
f1dcf0f0b8 Improve config final validation (#1917) 2021-06-18 11:52:02 +12:00
Sergey V. DUDANOV
a045d001bf Fix: midea_ac: fixed query status frame (#1922) 2021-06-18 11:52:01 +12:00
Paulus Schoutsen
066c1022d0 Update dashboard to 20210617.0 (#1930) 2021-06-18 11:52:01 +12:00
Jesse Hills
59c192becc Merge pull request #1923 from esphome/bump-1.19.0
1.19.0
2021-06-17 06:09:09 +12:00
Jesse Hills
a800816750 Merge branch 'beta' into bump-1.19.0 2021-06-17 05:59:02 +12:00
Jesse Hills
99d9ab4e40 Merge pull request #1926 from esphome/bump-1.19.0b7
1.19.0b7
2021-06-17 05:55:09 +12:00
Jesse Hills
f310ca1b74 Bump version to v1.19.0b7 2021-06-17 05:40:55 +12:00
Franck Nijhof
f763daa577 Fix update-all from dashboard (#1924) 2021-06-17 05:40:55 +12:00
Jesse Hills
970563e07b Bump version to v1.19.0 2021-06-16 21:00:51 +12:00
Jesse Hills
e006045f59 Merge pull request #1921 from esphome/bump-1.19.0b6
1.19.0b6
2021-06-16 15:42:43 +12:00
Jesse Hills
fff3645901 Bump version to v1.19.0b6 2021-06-16 13:51:31 +12:00
Jesse Hills
a5383fd208 Shorten the ble name to prevent crash with long device names (#1920) 2021-06-16 13:51:31 +12:00
Jesse Hills
9ce3a2059f Merge pull request #1919 from esphome/bump-1.19.0b5
1.19.0b5
2021-06-16 09:45:10 +12:00
Jesse Hills
69e6cf2c0c Bump version to v1.19.0b5 2021-06-16 09:06:50 +12:00
Paulus Schoutsen
28635124f9 Bump dashboard to 20210615.0 (#1918) 2021-06-16 09:06:50 +12:00
Jesse Hills
035be87a83 Merge pull request #1914 from esphome/bump-1.19.0b4
1.19.0b4
2021-06-15 20:47:38 +12:00
Jesse Hills
e8bdbc45a9 Bump version to v1.19.0b4 2021-06-15 20:26:53 +12:00
Guillermo Ruffino
429caccefa fixes compatibility with esphome cfg vscode (#1911) 2021-06-15 20:26:53 +12:00
Paulus Schoutsen
744ca1af7c Bump frontend to 20210614.0 (#1912) 2021-06-15 20:26:52 +12:00
Jesse Hills
106f0d611f Validate that either networks, ap, or improv is set up (#1910) 2021-06-15 20:26:52 +12:00
Jesse Hills
d826416684 Allow no networks or AP to be set. (#1908)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-06-15 20:26:52 +12:00
Jesse Hills
81959804df Merge pull request #1900 from esphome/bump-1.19.0b3
1.19.0b3
2021-06-12 11:15:03 +12:00
Jesse Hills
75b524ddc4 Fix formatting from cherry-pick conflict 2021-06-12 11:07:58 +12:00
Jesse Hills
f599c36272 Bump version to v1.19.0b3 2021-06-12 11:03:09 +12:00
Paulus Schoutsen
9bb64315f3 Add new wizard + allow installing firmware over webserial (#1887) 2021-06-12 11:03:09 +12:00
Jesse Hills
575badc690 Move esp32_ble_server to its own component (#1898) 2021-06-12 11:02:59 +12:00
Jesse Hills
4b91cfb7f9 Ensure wifi is in at least station mode before starting improv (#1899) 2021-06-12 11:01:54 +12:00
Jesse Hills
a57a842f7b Merge pull request #1888 from esphome/bump-1.19.0b2
1.19.0b2
2021-06-10 17:50:38 +12:00
Jesse Hills
a8c253a2a5 Bump version to v1.19.0b2 2021-06-10 17:17:52 +12:00
Geoff Davis
8b737aabd9 Add support for waveshare_epaper 1.54v2 (#1843)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-10 17:17:52 +12:00
Jesse Hills
0db4815f3d BLE loop use (#1882) 2021-06-10 17:17:52 +12:00
Oxan van Leeuwen
139db58a66 Simplify LightCall validation (#1874) 2021-06-10 17:17:52 +12:00
114 changed files with 1456 additions and 3677 deletions

View File

@@ -36,6 +36,7 @@ esphome/components/dfplayer/* @glmnet
esphome/components/dht/* @OttoWinter
esphome/components/ds1307/* @badbadc0ffee
esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz
esphome/components/esp32_improv/* @jesserockz
esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb

View File

@@ -394,7 +394,7 @@ def command_update_all(args):
import click
success = {}
files = list_yaml_files(args.configuration)
files = list_yaml_files(args.configuration[0])
twidth = 60
def print_bar(middle_text):
@@ -408,7 +408,7 @@ def command_update_all(args):
print("-" * twidth)
print()
rc = run_external_process(
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
"esphome", "--dashboard", "run", "--no-logs", "--device", "OTA", f
)
if rc == 0:
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
@@ -504,6 +504,7 @@ def parse_args(argv):
"version",
"clean",
"dashboard",
"vscode",
],
)
@@ -513,14 +514,26 @@ def parse_args(argv):
compat_parser.error = _raise
try:
result, unparsed = compat_parser.parse_known_args(argv[1:])
last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
argv = argv[0:last_option] + [result.command] + result.configuration + unparsed
deprecated_argv_suggestion = argv
except argparse.ArgumentError:
# This is not an old-style command line, so we don't have to do anything.
deprecated_argv_suggestion = None
deprecated_argv_suggestion = None
if ["dashboard", "config"] == argv[1:3]:
# this is most likely meant in new-style arg format. do not try compat parsing
pass
else:
try:
result, unparsed = compat_parser.parse_known_args(argv[1:])
last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
unparsed = [
"--device" if arg in ("--upload-port", "--serial-port") else arg
for arg in unparsed
]
argv = (
argv[0:last_option] + [result.command] + result.configuration + unparsed
)
deprecated_argv_suggestion = argv
except argparse.ArgumentError:
# This is not an old-style command line, so we don't have to do anything.
pass
# And continue on with regular parsing
parser = argparse.ArgumentParser(
@@ -686,7 +699,7 @@ def run_esphome(argv):
CORE.dashboard = args.dashboard
setup_log(args.verbose, args.quiet)
if args.deprecated_argv_suggestion is not None:
if args.deprecated_argv_suggestion is not None and args.command != "vscode":
_LOGGER.warning(
"Calling ESPHome with the configuration before the command is deprecated "
"and will be removed in the future. "

View File

@@ -710,13 +710,14 @@ enum ClimateAction {
CLIMATE_ACTION_FAN = 6;
}
enum ClimatePreset {
CLIMATE_PRESET_ECO = 0;
CLIMATE_PRESET_AWAY = 1;
CLIMATE_PRESET_BOOST = 2;
CLIMATE_PRESET_COMFORT = 3;
CLIMATE_PRESET_HOME = 4;
CLIMATE_PRESET_SLEEP = 5;
CLIMATE_PRESET_ACTIVITY = 6;
CLIMATE_PRESET_NONE = 0;
CLIMATE_PRESET_HOME = 1;
CLIMATE_PRESET_AWAY = 2;
CLIMATE_PRESET_BOOST = 3;
CLIMATE_PRESET_COMFORT = 4;
CLIMATE_PRESET_ECO = 5;
CLIMATE_PRESET_SLEEP = 6;
CLIMATE_PRESET_ACTIVITY = 7;
}
message ListEntitiesClimateResponse {
option (id) = 46;
@@ -734,7 +735,9 @@ message ListEntitiesClimateResponse {
float visual_min_temperature = 8;
float visual_max_temperature = 9;
float visual_temperature_step = 10;
bool supports_away = 11;
// for older peer versions - in new system this
// is if CLIMATE_PRESET_AWAY exists is supported_presets
bool legacy_supports_away = 11;
bool supports_action = 12;
repeated ClimateFanMode supported_fan_modes = 13;
repeated ClimateSwingMode supported_swing_modes = 14;
@@ -754,7 +757,8 @@ message ClimateStateResponse {
float target_temperature = 4;
float target_temperature_low = 5;
float target_temperature_high = 6;
bool away = 7;
// For older peers, equal to preset == CLIMATE_PRESET_AWAY
bool legacy_away = 7;
ClimateAction action = 8;
ClimateFanMode fan_mode = 9;
ClimateSwingMode swing_mode = 10;
@@ -777,8 +781,9 @@ message ClimateCommandRequest {
float target_temperature_low = 7;
bool has_target_temperature_high = 8;
float target_temperature_high = 9;
bool has_away = 10;
bool away = 11;
// legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
bool has_legacy_away = 10;
bool legacy_away = 11;
bool has_fan_mode = 12;
ClimateFanMode fan_mode = 13;
bool has_swing_mode = 14;

View File

@@ -475,14 +475,14 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
} else {
resp.target_temperature = climate->target_temperature;
}
if (traits.get_supports_away())
resp.away = climate->away;
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value())
resp.custom_fan_mode = climate->custom_fan_mode.value();
if (traits.get_supports_presets() && climate->preset.has_value())
if (traits.get_supports_presets() && climate->preset.has_value()) {
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY;
}
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value())
resp.custom_preset = climate->custom_preset.value();
if (traits.get_supports_swing_modes())
@@ -498,40 +498,26 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
msg.unique_id = get_default_unique_id("climate", climate);
msg.supports_current_temperature = traits.get_supports_current_temperature();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
for (auto mode :
{climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT,
climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_HEAT_COOL}) {
if (traits.supports_mode(mode))
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
}
for (auto mode : traits.get_supported_modes())
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
msg.visual_min_temperature = traits.get_visual_min_temperature();
msg.visual_max_temperature = traits.get_visual_max_temperature();
msg.visual_temperature_step = traits.get_visual_temperature_step();
msg.supports_away = traits.get_supports_away();
msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
msg.supports_action = traits.get_supports_action();
for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO,
climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH,
climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) {
if (traits.supports_fan_mode(fan_mode))
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
}
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) {
for (auto fan_mode : traits.get_supported_fan_modes())
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
msg.supported_custom_fan_modes.push_back(custom_fan_mode);
}
for (auto preset : {climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_AWAY, climate::CLIMATE_PRESET_BOOST,
climate::CLIMATE_PRESET_COMFORT, climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_SLEEP,
climate::CLIMATE_PRESET_ACTIVITY}) {
if (traits.supports_preset(preset))
msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
}
for (auto const &custom_preset : traits.get_supported_custom_presets()) {
for (auto preset : traits.get_supported_presets())
msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
for (auto const &custom_preset : traits.get_supported_custom_presets())
msg.supported_custom_presets.push_back(custom_preset);
}
for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
climate::CLIMATE_SWING_HORIZONTAL}) {
if (traits.supports_swing_mode(swing_mode))
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
}
for (auto swing_mode : traits.get_supported_swing_modes())
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
return this->send_list_entities_climate_response(msg);
}
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
@@ -548,8 +534,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
call.set_target_temperature_low(msg.target_temperature_low);
if (msg.has_target_temperature_high)
call.set_target_temperature_high(msg.target_temperature_high);
if (msg.has_away)
call.set_away(msg.away);
if (msg.has_legacy_away)
call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME);
if (msg.has_fan_mode)
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
if (msg.has_custom_fan_mode)
@@ -629,7 +615,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 4;
resp.api_version_minor = 5;
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
this->connection_state_ = ConnectionState::CONNECTED;
return resp;

View File

@@ -192,16 +192,18 @@ template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::Climate
}
template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::ClimatePreset value) {
switch (value) {
case enums::CLIMATE_PRESET_ECO:
return "CLIMATE_PRESET_ECO";
case enums::CLIMATE_PRESET_NONE:
return "CLIMATE_PRESET_NONE";
case enums::CLIMATE_PRESET_HOME:
return "CLIMATE_PRESET_HOME";
case enums::CLIMATE_PRESET_AWAY:
return "CLIMATE_PRESET_AWAY";
case enums::CLIMATE_PRESET_BOOST:
return "CLIMATE_PRESET_BOOST";
case enums::CLIMATE_PRESET_COMFORT:
return "CLIMATE_PRESET_COMFORT";
case enums::CLIMATE_PRESET_HOME:
return "CLIMATE_PRESET_HOME";
case enums::CLIMATE_PRESET_ECO:
return "CLIMATE_PRESET_ECO";
case enums::CLIMATE_PRESET_SLEEP:
return "CLIMATE_PRESET_SLEEP";
case enums::CLIMATE_PRESET_ACTIVITY:
@@ -2672,7 +2674,7 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
return true;
}
case 11: {
this->supports_away = value.as_bool();
this->legacy_supports_away = value.as_bool();
return true;
}
case 12: {
@@ -2756,7 +2758,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(8, this->visual_min_temperature);
buffer.encode_float(9, this->visual_max_temperature);
buffer.encode_float(10, this->visual_temperature_step);
buffer.encode_bool(11, this->supports_away);
buffer.encode_bool(11, this->legacy_supports_away);
buffer.encode_bool(12, this->supports_action);
for (auto &it : this->supported_fan_modes) {
buffer.encode_enum<enums::ClimateFanMode>(13, it, true);
@@ -2823,8 +2825,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" supports_away: ");
out.append(YESNO(this->supports_away));
out.append(" legacy_supports_away: ");
out.append(YESNO(this->legacy_supports_away));
out.append("\n");
out.append(" supports_action: ");
@@ -2869,7 +2871,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
return true;
}
case 7: {
this->away = value.as_bool();
this->legacy_away = value.as_bool();
return true;
}
case 8: {
@@ -2939,7 +2941,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(4, this->target_temperature);
buffer.encode_float(5, this->target_temperature_low);
buffer.encode_float(6, this->target_temperature_high);
buffer.encode_bool(7, this->away);
buffer.encode_bool(7, this->legacy_away);
buffer.encode_enum<enums::ClimateAction>(8, this->action);
buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode);
@@ -2979,8 +2981,8 @@ void ClimateStateResponse::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" away: ");
out.append(YESNO(this->away));
out.append(" legacy_away: ");
out.append(YESNO(this->legacy_away));
out.append("\n");
out.append(" action: ");
@@ -3031,11 +3033,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
return true;
}
case 10: {
this->has_away = value.as_bool();
this->has_legacy_away = value.as_bool();
return true;
}
case 11: {
this->away = value.as_bool();
this->legacy_away = value.as_bool();
return true;
}
case 12: {
@@ -3120,8 +3122,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(7, this->target_temperature_low);
buffer.encode_bool(8, this->has_target_temperature_high);
buffer.encode_float(9, this->target_temperature_high);
buffer.encode_bool(10, this->has_away);
buffer.encode_bool(11, this->away);
buffer.encode_bool(10, this->has_legacy_away);
buffer.encode_bool(11, this->legacy_away);
buffer.encode_bool(12, this->has_fan_mode);
buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
buffer.encode_bool(14, this->has_swing_mode);
@@ -3176,12 +3178,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" has_away: ");
out.append(YESNO(this->has_away));
out.append(" has_legacy_away: ");
out.append(YESNO(this->has_legacy_away));
out.append("\n");
out.append(" away: ");
out.append(YESNO(this->away));
out.append(" legacy_away: ");
out.append(YESNO(this->legacy_away));
out.append("\n");
out.append(" has_fan_mode: ");

View File

@@ -90,13 +90,14 @@ enum ClimateAction : uint32_t {
CLIMATE_ACTION_FAN = 6,
};
enum ClimatePreset : uint32_t {
CLIMATE_PRESET_ECO = 0,
CLIMATE_PRESET_AWAY = 1,
CLIMATE_PRESET_BOOST = 2,
CLIMATE_PRESET_COMFORT = 3,
CLIMATE_PRESET_HOME = 4,
CLIMATE_PRESET_SLEEP = 5,
CLIMATE_PRESET_ACTIVITY = 6,
CLIMATE_PRESET_NONE = 0,
CLIMATE_PRESET_HOME = 1,
CLIMATE_PRESET_AWAY = 2,
CLIMATE_PRESET_BOOST = 3,
CLIMATE_PRESET_COMFORT = 4,
CLIMATE_PRESET_ECO = 5,
CLIMATE_PRESET_SLEEP = 6,
CLIMATE_PRESET_ACTIVITY = 7,
};
} // namespace enums
@@ -709,7 +710,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
float visual_min_temperature{0.0f};
float visual_max_temperature{0.0f};
float visual_temperature_step{0.0f};
bool supports_away{false};
bool legacy_supports_away{false};
bool supports_action{false};
std::vector<enums::ClimateFanMode> supported_fan_modes{};
std::vector<enums::ClimateSwingMode> supported_swing_modes{};
@@ -732,7 +733,7 @@ class ClimateStateResponse : public ProtoMessage {
float target_temperature{0.0f};
float target_temperature_low{0.0f};
float target_temperature_high{0.0f};
bool away{false};
bool legacy_away{false};
enums::ClimateAction action{};
enums::ClimateFanMode fan_mode{};
enums::ClimateSwingMode swing_mode{};
@@ -758,8 +759,8 @@ class ClimateCommandRequest : public ProtoMessage {
float target_temperature_low{0.0f};
bool has_target_temperature_high{false};
float target_temperature_high{0.0f};
bool has_away{false};
bool away{false};
bool has_legacy_away{false};
bool legacy_away{false};
bool has_fan_mode{false};
enums::ClimateFanMode fan_mode{};
bool has_swing_mode{false};

View File

@@ -21,7 +21,7 @@ void BangBangClimate::setup() {
restore->to_call(this).perform();
} else {
// restore from defaults, change_away handles those for us
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
this->change_away_(false);
}
}
@@ -32,8 +32,8 @@ void BangBangClimate::control(const climate::ClimateCall &call) {
this->target_temperature_low = *call.get_target_temperature_low();
if (call.get_target_temperature_high().has_value())
this->target_temperature_high = *call.get_target_temperature_high();
if (call.get_away().has_value())
this->change_away_(*call.get_away());
if (call.get_preset().has_value())
this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY);
this->compute_state_();
this->publish_state();
@@ -41,16 +41,25 @@ void BangBangClimate::control(const climate::ClimateCall &call) {
climate::ClimateTraits BangBangClimate::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);
traits.set_supports_auto_mode(true);
traits.set_supports_cool_mode(this->supports_cool_);
traits.set_supports_heat_mode(this->supports_heat_);
traits.set_supported_modes({
climate::CLIMATE_MODE_OFF,
climate::CLIMATE_MODE_HEAT_COOL,
});
if (supports_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
if (supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
traits.set_supports_two_point_target_temperature(true);
traits.set_supports_away(this->supports_away_);
if (supports_away_)
traits.set_supported_presets({
climate::CLIMATE_PRESET_HOME,
climate::CLIMATE_PRESET_AWAY,
});
traits.set_supports_action(true);
return traits;
}
void BangBangClimate::compute_state_() {
if (this->mode != climate::CLIMATE_MODE_AUTO) {
if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) {
// in non-auto mode, switch directly to appropriate action
// - HEAT mode -> HEATING action
// - COOL mode -> COOLING action
@@ -140,7 +149,7 @@ void BangBangClimate::change_away_(bool away) {
this->target_temperature_low = this->away_config_.default_temperature_low;
this->target_temperature_high = this->away_config_.default_temperature_high;
}
this->away = away;
this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME;
}
void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) {
this->normal_config_ = normal_config;

View File

@@ -36,7 +36,7 @@ ClimateTraits = climate_ns.class_("ClimateTraits")
ClimateMode = climate_ns.enum("ClimateMode")
CLIMATE_MODES = {
"OFF": ClimateMode.CLIMATE_MODE_OFF,
"HEAT_COOL": ClimateMode.CLIMATE_HEAT_COOL,
"HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL,
"COOL": ClimateMode.CLIMATE_MODE_COOL,
"HEAT": ClimateMode.CLIMATE_MODE_HEAT,
"DRY": ClimateMode.CLIMATE_MODE_DRY,

View File

@@ -27,7 +27,9 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
call.set_target_temperature(this->target_temperature_.optional_value(x...));
call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...));
call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
call.set_away(this->away_.optional_value(x...));
if (away_.has_value()) {
call.set_preset(away_.value(x...) ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME);
}
call.set_fan_mode(this->fan_mode_.optional_value(x...));
call.set_fan_mode(this->custom_fan_mode_.optional_value(x...));
call.set_preset(this->preset_.optional_value(x...));

View File

@@ -43,9 +43,6 @@ void ClimateCall::perform() {
if (this->target_temperature_high_.has_value()) {
ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_);
}
if (this->away_.has_value()) {
ESP_LOGD(TAG, " Away Mode: %s", ONOFF(*this->away_));
}
this->parent_->control(*this);
}
void ClimateCall::validate_() {
@@ -125,12 +122,6 @@ void ClimateCall::validate_() {
this->target_temperature_high_.reset();
}
}
if (this->away_.has_value()) {
if (!traits.get_supports_away()) {
ESP_LOGW(TAG, " Cannot set away mode for this device!");
this->away_.reset();
}
}
}
ClimateCall &ClimateCall::set_mode(ClimateMode mode) {
this->mode_ = mode;
@@ -181,8 +172,7 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
} else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) {
this->set_fan_mode(CLIMATE_FAN_DIFFUSE);
} else {
auto custom_fan_modes = this->parent_->get_traits().get_supported_custom_fan_modes();
if (std::find(custom_fan_modes.begin(), custom_fan_modes.end(), fan_mode) != custom_fan_modes.end()) {
if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) {
this->custom_fan_mode_ = fan_mode;
this->fan_mode_.reset();
} else {
@@ -218,8 +208,7 @@ ClimateCall &ClimateCall::set_preset(const std::string &preset) {
} else if (str_equals_case_insensitive(preset, "ACTIVITY")) {
this->set_preset(CLIMATE_PRESET_ACTIVITY);
} else {
auto custom_presets = this->parent_->get_traits().get_supported_custom_presets();
if (std::find(custom_presets.begin(), custom_presets.end(), preset) != custom_presets.end()) {
if (this->parent_->get_traits().supports_custom_preset(preset)) {
this->custom_preset_ = preset;
this->preset_.reset();
} else {
@@ -269,18 +258,23 @@ const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_;
const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
const optional<bool> &ClimateCall::get_away() const { return this->away_; }
optional<bool> ClimateCall::get_away() const {
if (!this->preset_.has_value())
return {};
return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY;
}
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; }
const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; }
const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; }
ClimateCall &ClimateCall::set_away(bool away) {
this->away_ = away;
this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
return *this;
}
ClimateCall &ClimateCall::set_away(optional<bool> away) {
this->away_ = away;
if (away.has_value())
this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
return *this;
}
ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) {
@@ -338,20 +332,17 @@ void Climate::save_state_() {
} else {
state.target_temperature = this->target_temperature;
}
if (traits.get_supports_away()) {
state.away = this->away;
}
if (traits.get_supports_fan_modes() && fan_mode.has_value()) {
state.uses_custom_fan_mode = false;
state.fan_mode = this->fan_mode.value();
}
if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) {
state.uses_custom_fan_mode = true;
auto &custom_fan_modes = traits.get_supported_custom_fan_modes();
auto it = std::find(custom_fan_modes.begin(), custom_fan_modes.end(), this->custom_fan_mode.value());
// only set custom fan mode if value exists, otherwise leave it as is
if (it != custom_fan_modes.cend()) {
state.custom_fan_mode = std::distance(custom_fan_modes.begin(), it);
const auto &supported = traits.get_supported_custom_fan_modes();
std::vector<std::string> vec{supported.begin(), supported.end()};
auto it = std::find(vec.begin(), vec.end(), custom_fan_mode);
if (it != vec.end()) {
state.custom_fan_mode = std::distance(vec.begin(), it);
}
}
if (traits.get_supports_presets() && preset.has_value()) {
@@ -360,11 +351,12 @@ void Climate::save_state_() {
}
if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) {
state.uses_custom_preset = true;
auto custom_presets = traits.get_supported_custom_presets();
auto it = std::find(custom_presets.begin(), custom_presets.end(), this->custom_preset.value());
const auto &supported = traits.get_supported_custom_presets();
std::vector<std::string> vec{supported.begin(), supported.end()};
auto it = std::find(vec.begin(), vec.end(), custom_preset);
// only set custom preset if value exists, otherwise leave it as is
if (it != custom_presets.cend()) {
state.custom_preset = std::distance(custom_presets.begin(), it);
if (it != vec.cend()) {
state.custom_preset = std::distance(vec.begin(), it);
}
}
if (traits.get_supports_swing_modes()) {
@@ -405,9 +397,6 @@ void Climate::publish_state() {
} else {
ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature);
}
if (traits.get_supports_away()) {
ESP_LOGD(TAG, " Away: %s", ONOFF(this->away));
}
// Send state to frontend
this->state_callback_.call();
@@ -453,9 +442,6 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
} else {
call.set_target_temperature(this->target_temperature);
}
if (traits.get_supports_away()) {
call.set_away(this->away);
}
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
call.set_fan_mode(this->fan_mode);
}
@@ -476,23 +462,27 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
} else {
climate->target_temperature = this->target_temperature;
}
if (traits.get_supports_away()) {
climate->away = this->away;
}
if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) {
climate->fan_mode = this->fan_mode;
}
if (!traits.get_supported_custom_fan_modes().empty() && this->uses_custom_fan_mode) {
climate->custom_fan_mode = traits.get_supported_custom_fan_modes()[this->custom_fan_mode];
// std::set has consistent order (lexicographic for strings), so this is ok
const auto &modes = traits.get_supported_custom_fan_modes();
std::vector<std::string> modes_vec{modes.begin(), modes.end()};
if (custom_fan_mode < modes_vec.size()) {
climate->custom_fan_mode = modes_vec[this->custom_fan_mode];
}
}
if (traits.get_supports_presets() && !this->uses_custom_preset) {
climate->preset = this->preset;
}
if (!traits.get_supported_custom_presets().empty() && this->uses_custom_preset) {
climate->custom_preset = traits.get_supported_custom_presets()[this->custom_preset];
}
if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) {
climate->custom_preset = traits.get_supported_custom_presets()[this->preset];
// std::set has consistent order (lexicographic for strings), so this is ok
const auto &presets = traits.get_supported_custom_presets();
std::vector<std::string> presets_vec{presets.begin(), presets.end()};
if (custom_preset < presets_vec.size()) {
climate->custom_preset = presets_vec[this->custom_preset];
}
}
if (traits.get_supports_swing_modes()) {
climate->swing_mode = this->swing_mode;

View File

@@ -63,7 +63,9 @@ class ClimateCall {
* For climate devices with two point target temperature control
*/
ClimateCall &set_target_temperature_high(optional<float> target_temperature_high);
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead")
ClimateCall &set_away(bool away);
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead")
ClimateCall &set_away(optional<bool> away);
/// Set the fan mode of the climate device.
ClimateCall &set_fan_mode(ClimateFanMode fan_mode);
@@ -94,7 +96,8 @@ class ClimateCall {
const optional<float> &get_target_temperature() const;
const optional<float> &get_target_temperature_low() const;
const optional<float> &get_target_temperature_high() const;
const optional<bool> &get_away() const;
ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead")
optional<bool> get_away() const;
const optional<ClimateFanMode> &get_fan_mode() const;
const optional<ClimateSwingMode> &get_swing_mode() const;
const optional<std::string> &get_custom_fan_mode() const;
@@ -109,7 +112,6 @@ class ClimateCall {
optional<float> target_temperature_;
optional<float> target_temperature_low_;
optional<float> target_temperature_high_;
optional<bool> away_;
optional<ClimateFanMode> fan_mode_;
optional<ClimateSwingMode> swing_mode_;
optional<std::string> custom_fan_mode_;
@@ -120,7 +122,6 @@ class ClimateCall {
/// Struct used to save the state of the climate device in restore memory.
struct ClimateDeviceRestoreState {
ClimateMode mode;
bool away;
bool uses_custom_fan_mode{false};
union {
ClimateFanMode fan_mode;
@@ -159,7 +160,7 @@ struct ClimateDeviceRestoreState {
*
* The entire state of the climate device is encoded in public properties of the base class (current_temperature,
* mode etc). These are read-only for the user and rw for integrations. The reason these are public
* is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_AUTO) ...`
* is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...`
*/
class Climate : public Nameable {
public:
@@ -191,6 +192,7 @@ class Climate : public Nameable {
* Away allows climate devices to have two different target temperature configs:
* one for normal mode and one for away mode.
*/
ESPDEPRECATED("away is deprecated, use preset instead")
bool away{false};
/// The active fan mode of the climate device.

View File

@@ -84,6 +84,10 @@ const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) {
const char *climate_preset_to_string(ClimatePreset preset) {
switch (preset) {
case climate::CLIMATE_PRESET_NONE:
return "NONE";
case climate::CLIMATE_PRESET_HOME:
return "HOME";
case climate::CLIMATE_PRESET_ECO:
return "ECO";
case climate::CLIMATE_PRESET_AWAY:
@@ -92,8 +96,6 @@ const char *climate_preset_to_string(ClimatePreset preset) {
return "BOOST";
case climate::CLIMATE_PRESET_COMFORT:
return "COMFORT";
case climate::CLIMATE_PRESET_HOME:
return "HOME";
case climate::CLIMATE_PRESET_SLEEP:
return "SLEEP";
case climate::CLIMATE_PRESET_ACTIVITY:

View File

@@ -39,7 +39,6 @@ enum ClimateAction : uint8_t {
CLIMATE_ACTION_FAN = 6,
};
/// Enum for all modes a climate fan can be in
enum ClimateFanMode : uint8_t {
/// The fan mode is set to On
CLIMATE_FAN_ON = 0,
@@ -75,20 +74,22 @@ enum ClimateSwingMode : uint8_t {
/// Enum for all modes a climate swing can be in
enum ClimatePreset : uint8_t {
/// Preset is set to ECO
CLIMATE_PRESET_ECO = 0,
/// Preset is set to AWAY
CLIMATE_PRESET_AWAY = 1,
/// Preset is set to BOOST
CLIMATE_PRESET_BOOST = 2,
/// Preset is set to COMFORT
CLIMATE_PRESET_COMFORT = 3,
/// Preset is set to HOME
CLIMATE_PRESET_HOME = 4,
/// Preset is set to SLEEP
CLIMATE_PRESET_SLEEP = 5,
/// Preset is set to ACTIVITY
CLIMATE_PRESET_ACTIVITY = 6,
/// No preset is active
CLIMATE_PRESET_NONE = 0,
/// Device is in home preset
CLIMATE_PRESET_HOME = 1,
/// Device is in away preset
CLIMATE_PRESET_AWAY = 2,
/// Device is in boost preset
CLIMATE_PRESET_BOOST = 3,
/// Device is in comfort preset
CLIMATE_PRESET_COMFORT = 4,
/// Device is running an energy-saving preset
CLIMATE_PRESET_ECO = 5,
/// Device is prepared for sleep
CLIMATE_PRESET_SLEEP = 6,
/// Device is reacting to activity (e.g., movement sensors)
CLIMATE_PRESET_ACTIVITY = 7,
};
/// Convert the given ClimateMode to a human-readable string.

View File

@@ -4,50 +4,6 @@
namespace esphome {
namespace climate {
bool ClimateTraits::supports_mode(ClimateMode mode) const {
switch (mode) {
case CLIMATE_MODE_OFF:
return true;
case CLIMATE_MODE_AUTO:
return this->supports_auto_mode_;
case CLIMATE_MODE_COOL:
return this->supports_cool_mode_;
case CLIMATE_MODE_HEAT:
return this->supports_heat_mode_;
case CLIMATE_MODE_FAN_ONLY:
return this->supports_fan_only_mode_;
case CLIMATE_MODE_DRY:
return this->supports_dry_mode_;
default:
return false;
}
}
bool ClimateTraits::get_supports_current_temperature() const { return supports_current_temperature_; }
void ClimateTraits::set_supports_current_temperature(bool supports_current_temperature) {
supports_current_temperature_ = supports_current_temperature;
}
bool ClimateTraits::get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; }
void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
supports_two_point_target_temperature_ = supports_two_point_target_temperature;
}
void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; }
void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; }
void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; }
void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) {
supports_fan_only_mode_ = supports_fan_only_mode;
}
void ClimateTraits::set_supports_dry_mode(bool supports_dry_mode) { supports_dry_mode_ = supports_dry_mode; }
void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; }
void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; }
float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; }
void ClimateTraits::set_visual_min_temperature(float visual_min_temperature) {
visual_min_temperature_ = visual_min_temperature;
}
float ClimateTraits::get_visual_max_temperature() const { return visual_max_temperature_; }
void ClimateTraits::set_visual_max_temperature(float visual_max_temperature) {
visual_max_temperature_ = visual_max_temperature;
}
float ClimateTraits::get_visual_temperature_step() const { return visual_temperature_step_; }
int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
// use printf %g to find number of digits based on temperature step
char buf[32];
@@ -59,160 +15,6 @@ int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
return str.length() - dot_pos - 1;
}
void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
bool ClimateTraits::get_supports_away() const { return supports_away_; }
bool ClimateTraits::get_supports_action() const { return supports_action_; }
void ClimateTraits::set_supports_fan_mode_on(bool supports_fan_mode_on) {
this->supports_fan_mode_on_ = supports_fan_mode_on;
}
void ClimateTraits::set_supports_fan_mode_off(bool supports_fan_mode_off) {
this->supports_fan_mode_off_ = supports_fan_mode_off;
}
void ClimateTraits::set_supports_fan_mode_auto(bool supports_fan_mode_auto) {
this->supports_fan_mode_auto_ = supports_fan_mode_auto;
}
void ClimateTraits::set_supports_fan_mode_low(bool supports_fan_mode_low) {
this->supports_fan_mode_low_ = supports_fan_mode_low;
}
void ClimateTraits::set_supports_fan_mode_medium(bool supports_fan_mode_medium) {
this->supports_fan_mode_medium_ = supports_fan_mode_medium;
}
void ClimateTraits::set_supports_fan_mode_high(bool supports_fan_mode_high) {
this->supports_fan_mode_high_ = supports_fan_mode_high;
}
void ClimateTraits::set_supports_fan_mode_middle(bool supports_fan_mode_middle) {
this->supports_fan_mode_middle_ = supports_fan_mode_middle;
}
void ClimateTraits::set_supports_fan_mode_focus(bool supports_fan_mode_focus) {
this->supports_fan_mode_focus_ = supports_fan_mode_focus;
}
void ClimateTraits::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) {
this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse;
}
bool ClimateTraits::supports_fan_mode(ClimateFanMode fan_mode) const {
switch (fan_mode) {
case climate::CLIMATE_FAN_ON:
return this->supports_fan_mode_on_;
case climate::CLIMATE_FAN_OFF:
return this->supports_fan_mode_off_;
case climate::CLIMATE_FAN_AUTO:
return this->supports_fan_mode_auto_;
case climate::CLIMATE_FAN_LOW:
return this->supports_fan_mode_low_;
case climate::CLIMATE_FAN_MEDIUM:
return this->supports_fan_mode_medium_;
case climate::CLIMATE_FAN_HIGH:
return this->supports_fan_mode_high_;
case climate::CLIMATE_FAN_MIDDLE:
return this->supports_fan_mode_middle_;
case climate::CLIMATE_FAN_FOCUS:
return this->supports_fan_mode_focus_;
case climate::CLIMATE_FAN_DIFFUSE:
return this->supports_fan_mode_diffuse_;
default:
return false;
}
}
bool ClimateTraits::get_supports_fan_modes() const {
return this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ ||
this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ ||
this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_;
}
void ClimateTraits::set_supported_custom_fan_modes(std::vector<std::string> &supported_custom_fan_modes) {
this->supported_custom_fan_modes_ = supported_custom_fan_modes;
}
const std::vector<std::string> ClimateTraits::get_supported_custom_fan_modes() const {
return this->supported_custom_fan_modes_;
}
bool ClimateTraits::supports_custom_fan_mode(std::string &custom_fan_mode) const {
return std::count(this->supported_custom_fan_modes_.begin(), this->supported_custom_fan_modes_.end(),
custom_fan_mode);
}
bool ClimateTraits::supports_preset(ClimatePreset preset) const {
switch (preset) {
case climate::CLIMATE_PRESET_ECO:
return this->supports_preset_eco_;
case climate::CLIMATE_PRESET_AWAY:
return this->supports_preset_away_;
case climate::CLIMATE_PRESET_BOOST:
return this->supports_preset_boost_;
case climate::CLIMATE_PRESET_COMFORT:
return this->supports_preset_comfort_;
case climate::CLIMATE_PRESET_HOME:
return this->supports_preset_home_;
case climate::CLIMATE_PRESET_SLEEP:
return this->supports_preset_sleep_;
case climate::CLIMATE_PRESET_ACTIVITY:
return this->supports_preset_activity_;
default:
return false;
}
}
void ClimateTraits::set_supports_preset_eco(bool supports_preset_eco) {
this->supports_preset_eco_ = supports_preset_eco;
}
void ClimateTraits::set_supports_preset_away(bool supports_preset_away) {
this->supports_preset_away_ = supports_preset_away;
}
void ClimateTraits::set_supports_preset_boost(bool supports_preset_boost) {
this->supports_preset_boost_ = supports_preset_boost;
}
void ClimateTraits::set_supports_preset_comfort(bool supports_preset_comfort) {
this->supports_preset_comfort_ = supports_preset_comfort;
}
void ClimateTraits::set_supports_preset_home(bool supports_preset_home) {
this->supports_preset_home_ = supports_preset_home;
}
void ClimateTraits::set_supports_preset_sleep(bool supports_preset_sleep) {
this->supports_preset_sleep_ = supports_preset_sleep;
}
void ClimateTraits::set_supports_preset_activity(bool supports_preset_activity) {
this->supports_preset_activity_ = supports_preset_activity;
}
bool ClimateTraits::get_supports_presets() const {
return this->supports_preset_eco_ || this->supports_preset_away_ || this->supports_preset_boost_ ||
this->supports_preset_comfort_ || this->supports_preset_home_ || this->supports_preset_sleep_ ||
this->supports_preset_activity_;
}
void ClimateTraits::set_supported_custom_presets(std::vector<std::string> &supported_custom_presets) {
this->supported_custom_presets_ = supported_custom_presets;
}
const std::vector<std::string> ClimateTraits::get_supported_custom_presets() const {
return this->supported_custom_presets_;
}
bool ClimateTraits::supports_custom_preset(std::string &custom_preset) const {
return std::count(this->supported_custom_presets_.begin(), this->supported_custom_presets_.end(), custom_preset);
}
void ClimateTraits::set_supports_swing_mode_off(bool supports_swing_mode_off) {
this->supports_swing_mode_off_ = supports_swing_mode_off;
}
void ClimateTraits::set_supports_swing_mode_both(bool supports_swing_mode_both) {
this->supports_swing_mode_both_ = supports_swing_mode_both;
}
void ClimateTraits::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) {
this->supports_swing_mode_vertical_ = supports_swing_mode_vertical;
}
void ClimateTraits::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) {
this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal;
}
bool ClimateTraits::supports_swing_mode(ClimateSwingMode swing_mode) const {
switch (swing_mode) {
case climate::CLIMATE_SWING_OFF:
return this->supports_swing_mode_off_;
case climate::CLIMATE_SWING_BOTH:
return this->supports_swing_mode_both_;
case climate::CLIMATE_SWING_VERTICAL:
return this->supports_swing_mode_vertical_;
case climate::CLIMATE_SWING_HORIZONTAL:
return this->supports_swing_mode_horizontal_;
default:
return false;
}
}
bool ClimateTraits::get_supports_swing_modes() const {
return this->supports_swing_mode_off_ || this->supports_swing_mode_both_ || supports_swing_mode_vertical_ ||
supports_swing_mode_horizontal_;
}
} // namespace climate
} // namespace esphome

View File

@@ -2,6 +2,7 @@
#include "esphome/core/helpers.h"
#include "climate_mode.h"
#include <set>
namespace esphome {
namespace climate {
@@ -24,8 +25,6 @@ namespace climate {
* - heat mode (increases current temperature)
* - dry mode (removes humidity from air)
* - fan mode (only turns on fan)
* - supports away - away mode means that the climate device supports two different
* target temperature settings: one target temp setting for "away" mode and one for non-away mode.
* - supports action - if the climate device supports reporting the active
* current action of the device with the action property.
* - supports fan modes - optionally, if it has a fan which can be configured in different ways:
@@ -41,93 +40,147 @@ namespace climate {
*/
class ClimateTraits {
public:
bool get_supports_current_temperature() const;
void set_supports_current_temperature(bool supports_current_temperature);
bool get_supports_two_point_target_temperature() const;
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature);
void set_supports_auto_mode(bool supports_auto_mode);
void set_supports_cool_mode(bool supports_cool_mode);
void set_supports_heat_mode(bool supports_heat_mode);
void set_supports_fan_only_mode(bool supports_fan_only_mode);
void set_supports_dry_mode(bool supports_dry_mode);
void set_supports_away(bool supports_away);
bool get_supports_away() const;
void set_supports_action(bool supports_action);
bool get_supports_action() const;
bool supports_mode(ClimateMode mode) const;
void set_supports_fan_mode_on(bool supports_fan_mode_on);
void set_supports_fan_mode_off(bool supports_fan_mode_off);
void set_supports_fan_mode_auto(bool supports_fan_mode_auto);
void set_supports_fan_mode_low(bool supports_fan_mode_low);
void set_supports_fan_mode_medium(bool supports_fan_mode_medium);
void set_supports_fan_mode_high(bool supports_fan_mode_high);
void set_supports_fan_mode_middle(bool supports_fan_mode_middle);
void set_supports_fan_mode_focus(bool supports_fan_mode_focus);
void set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse);
bool supports_fan_mode(ClimateFanMode fan_mode) const;
bool get_supports_fan_modes() const;
void set_supported_custom_fan_modes(std::vector<std::string> &supported_custom_fan_modes);
const std::vector<std::string> get_supported_custom_fan_modes() const;
bool supports_custom_fan_mode(std::string &custom_fan_mode) const;
bool supports_preset(ClimatePreset preset) const;
void set_supports_preset_eco(bool supports_preset_eco);
void set_supports_preset_away(bool supports_preset_away);
void set_supports_preset_boost(bool supports_preset_boost);
void set_supports_preset_comfort(bool supports_preset_comfort);
void set_supports_preset_home(bool supports_preset_home);
void set_supports_preset_sleep(bool supports_preset_sleep);
void set_supports_preset_activity(bool supports_preset_activity);
bool get_supports_presets() const;
void set_supported_custom_presets(std::vector<std::string> &supported_custom_presets);
const std::vector<std::string> get_supported_custom_presets() const;
bool supports_custom_preset(std::string &custom_preset) const;
void set_supports_swing_mode_off(bool supports_swing_mode_off);
void set_supports_swing_mode_both(bool supports_swing_mode_both);
void set_supports_swing_mode_vertical(bool supports_swing_mode_vertical);
void set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal);
bool supports_swing_mode(ClimateSwingMode swing_mode) const;
bool get_supports_swing_modes() const;
bool get_supports_current_temperature() const { return supports_current_temperature_; }
void set_supports_current_temperature(bool supports_current_temperature) {
supports_current_temperature_ = supports_current_temperature;
}
bool get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; }
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
supports_two_point_target_temperature_ = supports_two_point_target_temperature;
}
void set_supported_modes(std::set<ClimateMode> modes) { supported_modes_ = std::move(modes); }
void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
void set_supports_cool_mode(bool supports_cool_mode) { set_mode_support_(CLIMATE_MODE_COOL, supports_cool_mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
void set_supports_heat_mode(bool supports_heat_mode) { set_mode_support_(CLIMATE_MODE_HEAT, supports_heat_mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
void set_supports_heat_cool_mode(bool supported) { set_mode_support_(CLIMATE_MODE_HEAT_COOL, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
void set_supports_fan_only_mode(bool supports_fan_only_mode) {
set_mode_support_(CLIMATE_MODE_FAN_ONLY, supports_fan_only_mode);
}
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); }
bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); }
const std::set<ClimateMode> get_supported_modes() const { return supported_modes_; }
float get_visual_min_temperature() const;
void set_visual_min_temperature(float visual_min_temperature);
float get_visual_max_temperature() const;
void set_visual_max_temperature(float visual_max_temperature);
float get_visual_temperature_step() const;
void set_supports_action(bool supports_action) { supports_action_ = supports_action; }
bool get_supports_action() const { return supports_action_; }
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { supported_fan_modes_ = std::move(modes); }
void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_fan_mode_off(bool supported) { set_fan_mode_support_(CLIMATE_FAN_OFF, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_fan_mode_auto(bool supported) { set_fan_mode_support_(CLIMATE_FAN_AUTO, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_fan_mode_low(bool supported) { set_fan_mode_support_(CLIMATE_FAN_LOW, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_fan_mode_medium(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MEDIUM, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_fan_mode_high(bool supported) { set_fan_mode_support_(CLIMATE_FAN_HIGH, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_fan_mode_middle(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MIDDLE, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); }
bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); }
bool get_supports_fan_modes() const { return !supported_fan_modes_.empty(); }
const std::set<ClimateFanMode> get_supported_fan_modes() const { return supported_fan_modes_; }
void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
}
const std::set<std::string> &get_supported_custom_fan_modes() const { return supported_custom_fan_modes_; }
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
return supported_custom_fan_modes_.count(custom_fan_mode);
}
void set_supported_presets(std::set<ClimatePreset> presets) { supported_presets_ = std::move(presets); }
void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); }
bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); }
bool get_supports_presets() const { return !supported_presets_.empty(); }
const std::set<climate::ClimatePreset> &get_supported_presets() const { return supported_presets_; }
void set_supported_custom_presets(std::set<std::string> supported_custom_presets) {
supported_custom_presets_ = std::move(supported_custom_presets);
}
const std::set<std::string> &get_supported_custom_presets() const { return supported_custom_presets_; }
bool supports_custom_preset(const std::string &custom_preset) const {
return supported_custom_presets_.count(custom_preset);
}
ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead")
void set_supports_away(bool supports) {
if (supports) {
supported_presets_.insert(CLIMATE_PRESET_AWAY);
supported_presets_.insert(CLIMATE_PRESET_HOME);
}
}
ESPDEPRECATED("This method is deprecated, use supports_preset() instead")
bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); }
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); }
void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_swing_mode_both(bool supported) { set_swing_mode_support_(CLIMATE_SWING_BOTH, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_swing_mode_vertical(bool supported) { set_swing_mode_support_(CLIMATE_SWING_VERTICAL, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
void set_supports_swing_mode_horizontal(bool supported) {
set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported);
}
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); }
bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); }
const std::set<ClimateSwingMode> get_supported_swing_modes() { return supported_swing_modes_; }
float get_visual_min_temperature() const { return visual_min_temperature_; }
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
float get_visual_max_temperature() const { return visual_max_temperature_; }
void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; }
float get_visual_temperature_step() const { return visual_temperature_step_; }
int8_t get_temperature_accuracy_decimals() const;
void set_visual_temperature_step(float temperature_step);
void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
protected:
void set_mode_support_(climate::ClimateMode mode, bool supported) {
if (supported) {
supported_modes_.insert(mode);
} else {
supported_modes_.erase(mode);
}
}
void set_fan_mode_support_(climate::ClimateFanMode mode, bool supported) {
if (supported) {
supported_fan_modes_.insert(mode);
} else {
supported_fan_modes_.erase(mode);
}
}
void set_swing_mode_support_(climate::ClimateSwingMode mode, bool supported) {
if (supported) {
supported_swing_modes_.insert(mode);
} else {
supported_swing_modes_.erase(mode);
}
}
bool supports_current_temperature_{false};
bool supports_two_point_target_temperature_{false};
bool supports_auto_mode_{false};
bool supports_cool_mode_{false};
bool supports_heat_mode_{false};
bool supports_fan_only_mode_{false};
bool supports_dry_mode_{false};
bool supports_away_{false};
std::set<climate::ClimateMode> supported_modes_ = {climate::CLIMATE_MODE_OFF};
bool supports_action_{false};
bool supports_fan_mode_on_{false};
bool supports_fan_mode_off_{false};
bool supports_fan_mode_auto_{false};
bool supports_fan_mode_low_{false};
bool supports_fan_mode_medium_{false};
bool supports_fan_mode_high_{false};
bool supports_fan_mode_middle_{false};
bool supports_fan_mode_focus_{false};
bool supports_fan_mode_diffuse_{false};
bool supports_swing_mode_off_{false};
bool supports_swing_mode_both_{false};
bool supports_swing_mode_vertical_{false};
bool supports_swing_mode_horizontal_{false};
bool supports_preset_eco_{false};
bool supports_preset_away_{false};
bool supports_preset_boost_{false};
bool supports_preset_comfort_{false};
bool supports_preset_home_{false};
bool supports_preset_sleep_{false};
bool supports_preset_activity_{false};
std::vector<std::string> supported_custom_fan_modes_;
std::vector<std::string> supported_custom_presets_;
std::set<climate::ClimateFanMode> supported_fan_modes_;
std::set<climate::ClimateSwingMode> supported_swing_modes_;
std::set<climate::ClimatePreset> supported_presets_;
std::set<std::string> supported_custom_fan_modes_;
std::set<std::string> supported_custom_presets_;
float visual_min_temperature_{10};
float visual_max_temperature_{30};

View File

@@ -9,63 +9,22 @@ static const char *TAG = "climate_ir";
climate::ClimateTraits ClimateIR::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(this->sensor_ != nullptr);
traits.set_supports_auto_mode(true);
traits.set_supports_cool_mode(this->supports_cool_);
traits.set_supports_heat_mode(this->supports_heat_);
traits.set_supports_dry_mode(this->supports_dry_);
traits.set_supports_fan_only_mode(this->supports_fan_only_);
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
if (supports_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
if (supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
if (supports_dry_)
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
if (supports_fan_only_)
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
traits.set_supports_two_point_target_temperature(false);
traits.set_supports_away(false);
traits.set_visual_min_temperature(this->minimum_temperature_);
traits.set_visual_max_temperature(this->maximum_temperature_);
traits.set_visual_temperature_step(this->temperature_step_);
for (auto fan_mode : this->fan_modes_) {
switch (fan_mode) {
case climate::CLIMATE_FAN_AUTO:
traits.set_supports_fan_mode_auto(true);
break;
case climate::CLIMATE_FAN_DIFFUSE:
traits.set_supports_fan_mode_diffuse(true);
break;
case climate::CLIMATE_FAN_FOCUS:
traits.set_supports_fan_mode_focus(true);
break;
case climate::CLIMATE_FAN_HIGH:
traits.set_supports_fan_mode_high(true);
break;
case climate::CLIMATE_FAN_LOW:
traits.set_supports_fan_mode_low(true);
break;
case climate::CLIMATE_FAN_MEDIUM:
traits.set_supports_fan_mode_medium(true);
break;
case climate::CLIMATE_FAN_MIDDLE:
traits.set_supports_fan_mode_middle(true);
break;
case climate::CLIMATE_FAN_OFF:
traits.set_supports_fan_mode_off(true);
break;
case climate::CLIMATE_FAN_ON:
traits.set_supports_fan_mode_on(true);
break;
}
}
for (auto swing_mode : this->swing_modes_) {
switch (swing_mode) {
case climate::CLIMATE_SWING_OFF:
traits.set_supports_swing_mode_off(true);
break;
case climate::CLIMATE_SWING_BOTH:
traits.set_supports_swing_mode_both(true);
break;
case climate::CLIMATE_SWING_VERTICAL:
traits.set_supports_swing_mode_vertical(true);
break;
case climate::CLIMATE_SWING_HORIZONTAL:
traits.set_supports_swing_mode_horizontal(true);
break;
}
}
traits.set_supported_fan_modes(fan_modes_);
traits.set_supported_swing_modes(swing_modes_);
return traits;
}

View File

@@ -19,9 +19,8 @@ namespace climate_ir {
class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener {
public:
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
bool supports_dry = false, bool supports_fan_only = false,
std::vector<climate::ClimateFanMode> fan_modes = {},
std::vector<climate::ClimateSwingMode> swing_modes = {}) {
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
std::set<climate::ClimateSwingMode> swing_modes = {}) {
this->minimum_temperature_ = minimum_temperature;
this->maximum_temperature_ = maximum_temperature;
this->temperature_step_ = temperature_step;
@@ -58,8 +57,8 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
bool supports_heat_{true};
bool supports_dry_{false};
bool supports_fan_only_{false};
std::vector<climate::ClimateFanMode> fan_modes_ = {};
std::vector<climate::ClimateSwingMode> swing_modes_ = {};
std::set<climate::ClimateFanMode> fan_modes_ = {};
std::set<climate::ClimateSwingMode> swing_modes_ = {};
remote_transmitter::RemoteTransmitterComponent *transmitter_;
sensor::Sensor *sensor_{nullptr};

View File

@@ -39,7 +39,7 @@ void LgIrClimate::transmit_state() {
send_swing_cmd_ = false;
remote_state |= COMMAND_SWING;
} else {
if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_AUTO) {
if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
remote_state |= COMMAND_ON_AI;
} else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) {
remote_state |= COMMAND_ON;
@@ -52,7 +52,7 @@ void LgIrClimate::transmit_state() {
case climate::CLIMATE_MODE_HEAT:
remote_state |= COMMAND_HEAT;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state |= COMMAND_AUTO;
break;
case climate::CLIMATE_MODE_DRY:
@@ -89,7 +89,7 @@ void LgIrClimate::transmit_state() {
}
}
if (this->mode == climate::CLIMATE_MODE_AUTO) {
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
// remote_state |= FAN_MODE_AUTO_DRY;
}
@@ -128,7 +128,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
if ((remote_state & COMMAND_MASK) == COMMAND_ON) {
this->mode = climate::CLIMATE_MODE_COOL;
} else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) {
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
}
if ((remote_state & COMMAND_MASK) == COMMAND_OFF) {
@@ -138,7 +138,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
} else {
if ((remote_state & COMMAND_MASK) == COMMAND_AUTO)
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN)
this->mode = climate::CLIMATE_MODE_DRY;
else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
@@ -152,7 +152,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
// Fan Speed
if (this->mode == climate::CLIMATE_MODE_AUTO) {
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT ||
this->mode == climate::CLIMATE_MODE_DRY) {

View File

@@ -70,7 +70,7 @@ void CoolixClimate::transmit_state() {
case climate::CLIMATE_MODE_HEAT:
remote_state |= COOLIX_HEAT;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state |= COOLIX_AUTO;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
@@ -89,7 +89,7 @@ void CoolixClimate::transmit_state() {
} else {
remote_state |= COOLIX_FAN_TEMP_CODE;
}
if (this->mode == climate::CLIMATE_MODE_AUTO || this->mode == climate::CLIMATE_MODE_DRY) {
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_DRY) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
remote_state |= COOLIX_FAN_MODE_AUTO_DRY;
} else {
@@ -197,7 +197,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT)
this->mode = climate::CLIMATE_MODE_HEAT;
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO)
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY)
this->mode = climate::CLIMATE_MODE_DRY;
@@ -207,7 +207,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
this->mode = climate::CLIMATE_MODE_COOL;
// Fan Speed
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_AUTO ||
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL ||
this->mode == climate::CLIMATE_MODE_DRY)
this->fan_mode = climate::CLIMATE_FAN_AUTO;
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)

View File

@@ -45,6 +45,9 @@ CONFIG_SCHEMA = (
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"cse7766", baud_rate=4800, require_rx=True
)
async def to_code(config):
@@ -64,9 +67,3 @@ async def to_code(config):
conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens))
def validate(config, item_config):
uart.validate_device(
"cse7766", config, item_config, baud_rate=4800, require_tx=False
)

View File

@@ -77,7 +77,7 @@ uint8_t DaikinClimate::operation_mode_() {
case climate::CLIMATE_MODE_HEAT:
operating_mode |= DAIKIN_MODE_HEAT;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
operating_mode |= DAIKIN_MODE_AUTO;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
@@ -131,7 +131,7 @@ uint8_t DaikinClimate::temperature_() {
switch (this->mode) {
case climate::CLIMATE_MODE_FAN_ONLY:
return 0x32;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
case climate::CLIMATE_MODE_DRY:
return 0xc0;
default:
@@ -160,7 +160,7 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
this->mode = climate::CLIMATE_MODE_HEAT;
break;
case DAIKIN_MODE_AUTO:
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
case DAIKIN_MODE_FAN:
this->mode = climate::CLIMATE_MODE_FAN_ONLY;

View File

@@ -43,12 +43,11 @@ const uint8_t DAIKIN_STATE_FRAME_SIZE = 19;
class DaikinClimate : public climate_ir::ClimateIR {
public:
DaikinClimate()
: climate_ir::ClimateIR(
DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true,
std::vector<climate::ClimateFanMode>{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW,
climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
: climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true,
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
protected:
// Transmit via IR the state of this climate controller.

View File

@@ -68,6 +68,9 @@ CONFIG_SCHEMA = cv.All(
}
).extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"dfplayer", baud_rate=9600, require_tx=True
)
async def to_code(config):
@@ -80,12 +83,6 @@ async def to_code(config):
await automation.build_automation(trigger, [], conf)
def validate(config, item_config):
uart.validate_device(
"dfplayer", config, item_config, baud_rate=9600, require_rx=False
)
@automation.register_action(
"dfplayer.play_next",
NextAction,

View File

@@ -1,30 +1,18 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODEL, ESP_PLATFORM_ESP32
from esphome.const import CONF_ID, ESP_PLATFORM_ESP32
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
CODEOWNERS = ["@jesserockz"]
CONF_MANUFACTURER = "manufacturer"
CONF_SERVER = "server"
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
BLEServer = esp32_ble_ns.class_("BLEServer", cg.Component)
BLEServiceComponent = esp32_ble_ns.class_("BLEServiceComponent")
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32BLE),
cv.Optional(CONF_SERVER): cv.Schema(
{
cv.GenerateID(): cv.declare_id(BLEServer),
cv.Optional(CONF_MANUFACTURER, default="ESPHome"): cv.string,
cv.Optional(CONF_MODEL): cv.string,
}
),
}
).extend(cv.COMPONENT_SCHEMA)
@@ -32,13 +20,3 @@ CONFIG_SCHEMA = cv.Schema(
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if CONF_SERVER in config:
conf = config[CONF_SERVER]
server = cg.new_Pvariable(conf[CONF_ID])
await cg.register_component(server, conf)
cg.add(server.set_manufacturer(conf[CONF_MANUFACTURER]))
if CONF_MODEL in conf:
cg.add(server.set_model(conf[CONF_MODEL]))
cg.add_define("USE_ESP32_BLE_SERVER")
cg.add(var.set_server(server))

View File

@@ -1,4 +1,5 @@
#include "ble.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
@@ -20,38 +21,28 @@ void ESP32BLE::setup() {
global_ble = this;
ESP_LOGCONFIG(TAG, "Setting up BLE...");
xTaskCreatePinnedToCore(ESP32BLE::ble_core_task_,
"ble_task", // name
10000, // stack size
nullptr, // input params
1, // priority
nullptr, // handle, not needed
0 // core
);
if (!ble_setup_()) {
ESP_LOGE(TAG, "BLE could not be set up");
this->mark_failed();
return;
}
this->advertising_ = new BLEAdvertising();
this->advertising_->set_scan_response(true);
this->advertising_->set_min_preferred_interval(0x06);
this->advertising_->start();
ESP_LOGD(TAG, "BLE setup complete");
}
void ESP32BLE::mark_failed() {
Component::mark_failed();
#ifdef USE_ESP32_BLE_SERVER
if (this->server_ != nullptr) {
this->server_->mark_failed();
}
}
bool ESP32BLE::can_proceed() { return this->ready_; }
void ESP32BLE::ble_core_task_(void *params) {
if (!ble_setup_()) {
ESP_LOGE(TAG, "BLE could not be set up");
global_ble->mark_failed();
return;
}
global_ble->ready_ = true;
ESP_LOGD(TAG, "BLE Setup complete");
while (true) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
#endif
}
bool ESP32BLE::ble_setup_() {
@@ -84,7 +75,7 @@ bool ESP32BLE::ble_setup_() {
return false;
}
if (global_ble->has_server()) {
if (this->has_server()) {
err = esp_ble_gatts_register_callback(ESP32BLE::gatts_event_handler);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_register_callback failed: %d", err);
@@ -92,7 +83,7 @@ bool ESP32BLE::ble_setup_() {
}
}
if (global_ble->has_client()) {
if (this->has_client()) {
err = esp_ble_gattc_register_callback(ESP32BLE::gattc_event_handler);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err);
@@ -100,7 +91,16 @@ bool ESP32BLE::ble_setup_() {
}
}
err = esp_ble_gap_set_device_name(App.get_name().c_str());
std::string name = App.get_name();
if (name.length() > 20) {
if (App.is_name_add_mac_suffix_enabled()) {
name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address
} else {
name = name.substr(0, 20);
}
}
err = esp_ble_gap_set_device_name(name.c_str());
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_set_device_name failed: %d", err);
return false;
@@ -119,27 +119,28 @@ bool ESP32BLE::ble_setup_() {
return true;
}
// void ESP32BLE::loop() {
// BLEEvent *ble_event = this->ble_events_.pop();
// while (ble_event != nullptr) {
// switch (ble_event->type_) {
// case ble_event->GATTS:
// this->real_gatts_event_handler_(ble_event->event_.gatts.gatts_event, ble_event->event_.gatts.gatts_if,
// &ble_event->event_.gatts.gatts_param);
// break;
// case ble_event->GAP:
// this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
// break;
// default:
// break;
// }
// delete ble_event;
// ble_event = this->ble_events_.pop();
// }
// }
void ESP32BLE::loop() {
BLEEvent *ble_event = this->ble_events_.pop();
while (ble_event != nullptr) {
switch (ble_event->type_) {
case ble_event->GATTS:
this->real_gatts_event_handler_(ble_event->event_.gatts.gatts_event, ble_event->event_.gatts.gatts_if,
&ble_event->event_.gatts.gatts_param);
break;
case ble_event->GAP:
this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
break;
default:
break;
}
delete ble_event;
ble_event = this->ble_events_.pop();
}
}
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
global_ble->real_gap_event_handler_(event, param);
BLEEvent *new_event = new BLEEvent(event, param);
global_ble->ble_events_.push(new_event);
}
void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
@@ -152,13 +153,16 @@ void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap
void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) {
global_ble->real_gatts_event_handler_(event, gatts_if, param);
BLEEvent *new_event = new BLEEvent(event, gatts_if, param);
global_ble->ble_events_.push(new_event);
}
void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) {
ESP_LOGV(TAG, "(BLE) gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
#ifdef USE_ESP32_BLE_SERVER
this->server_->gatts_event_handler(event, gatts_if, param);
#endif
}
void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,

View File

@@ -1,16 +1,21 @@
#pragma once
#include "ble_advertising.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "ble_server.h"
#include "queue.h"
#ifdef USE_ESP32_BLE_SERVER
#include "esphome/components/esp32_ble_server/ble_server.h"
#endif
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gap_ble_api.h>
#include <esp_gatts_api.h>
#include <esp_gattc_api.h>
namespace esphome {
namespace esp32_ble {
@@ -23,19 +28,25 @@ typedef struct {
class ESP32BLE : public Component {
public:
void setup() override;
// void loop() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
void mark_failed() override;
bool can_proceed() override;
bool has_server() { return this->server_ != nullptr; }
bool has_server() {
#ifdef USE_ESP32_BLE_SERVER
return this->server_ != nullptr;
#else
return false;
#endif
}
bool has_client() { return false; }
bool is_ready() { return this->ready_; }
void set_server(BLEServer *server) { this->server_ = server; }
BLEAdvertising *get_advertising() { return this->advertising_; }
#ifdef USE_ESP32_BLE_SERVER
void set_server(esp32_ble_server::BLEServer *server) { this->server_ = server; }
#endif
protected:
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
@@ -45,13 +56,13 @@ class ESP32BLE : public Component {
void real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
static void ble_core_task_(void *params);
static bool ble_setup_();
bool ble_setup_();
bool ready_{false};
BLEServer *server_{nullptr};
#ifdef USE_ESP32_BLE_SERVER
esp32_ble_server::BLEServer *server_{nullptr};
#endif
Queue<BLEEvent> ble_events_;
BLEAdvertising *advertising_;
};
extern ESP32BLE *global_ble;

View File

@@ -32,6 +32,10 @@ BLEAdvertising::BLEAdvertising() {
}
void BLEAdvertising::add_service_uuid(ESPBTUUID uuid) { this->advertising_uuids_.push_back(uuid); }
void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) {
this->advertising_uuids_.erase(std::remove(this->advertising_uuids_.begin(), this->advertising_uuids_.end(), uuid),
this->advertising_uuids_.end());
}
void BLEAdvertising::start() {
int num_services = this->advertising_uuids_.size();

View File

@@ -17,6 +17,7 @@ class BLEAdvertising {
BLEAdvertising();
void add_service_uuid(ESPBTUUID uuid);
void remove_service_uuid(ESPBTUUID uuid);
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }

View File

@@ -53,7 +53,7 @@ template<class T> class Queue {
SemaphoreHandle_t m;
};
// Received GAP and GATTC events are only queued, and get processed in the main loop().
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
// This class stores each event in a single type.
class BLEEvent {
public:
@@ -68,9 +68,18 @@ class BLEEvent {
this->event_.gattc.gattc_if = i;
memcpy(&this->event_.gattc.gattc_param, p, sizeof(esp_ble_gattc_cb_param_t));
// Need to also make a copy of notify event data.
if (e == ESP_GATTC_NOTIFY_EVT) {
memcpy(this->event_.gattc.notify_data, p->notify.value, p->notify.value_len);
this->event_.gattc.gattc_param.notify.value = this->event_.gattc.notify_data;
switch (e) {
case ESP_GATTC_NOTIFY_EVT:
memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len);
this->event_.gattc.gattc_param.notify.value = this->event_.gattc.data;
break;
case ESP_GATTC_READ_CHAR_EVT:
case ESP_GATTC_READ_DESCR_EVT:
memcpy(this->event_.gattc.data, p->read.value, p->read.value_len);
this->event_.gattc.gattc_param.read.value = this->event_.gattc.data;
break;
default:
break;
}
this->type_ = GATTC;
};
@@ -79,6 +88,15 @@ class BLEEvent {
this->event_.gatts.gatts_event = e;
this->event_.gatts.gatts_if = i;
memcpy(&this->event_.gatts.gatts_param, p, sizeof(esp_ble_gatts_cb_param_t));
// Need to also make a copy of write data.
switch (e) {
case ESP_GATTS_WRITE_EVT:
memcpy(this->event_.gatts.data, p->write.value, p->write.len);
this->event_.gatts.gatts_param.write.value = this->event_.gatts.data;
break;
default:
break;
}
this->type_ = GATTS;
};
@@ -92,13 +110,14 @@ class BLEEvent {
esp_gattc_cb_event_t gattc_event;
esp_gatt_if_t gattc_if;
esp_ble_gattc_cb_param_t gattc_param;
uint8_t notify_data[64];
uint8_t data[64];
} gattc;
struct gatts_event {
esp_gatts_cb_event_t gatts_event;
esp_gatt_if_t gatts_if;
esp_ble_gatts_cb_param_t gatts_param;
uint8_t data[64];
} gatts;
} event_;
enum ble_event_t : uint8_t {

View File

@@ -0,0 +1,39 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODEL, ESP_PLATFORM_ESP32
from esphome.components import esp32_ble
AUTO_LOAD = ["esp32_ble"]
CODEOWNERS = ["@jesserockz"]
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
CONF_MANUFACTURER = "manufacturer"
CONF_BLE_ID = "ble_id"
esp32_ble_server_ns = cg.esphome_ns.namespace("esp32_ble_server")
BLEServer = esp32_ble_server_ns.class_("BLEServer", cg.Component)
BLEServiceComponent = esp32_ble_server_ns.class_("BLEServiceComponent")
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(BLEServer),
cv.GenerateID(CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
cv.Optional(CONF_MANUFACTURER, default="ESPHome"): cv.string,
cv.Optional(CONF_MODEL): cv.string,
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
parent = await cg.get_variable(config[CONF_BLE_ID])
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add(var.set_manufacturer(config[CONF_MANUFACTURER]))
if CONF_MODEL in config:
cg.add(var.set_model(config[CONF_MODEL]))
cg.add_define("USE_ESP32_BLE_SERVER")
cg.add(parent.set_server(var))

View File

@@ -1,18 +1,18 @@
#include "ble_2901.h"
#include "ble_uuid.h"
#include "esphome/components/esp32_ble/ble_uuid.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace esp32_ble {
namespace esp32_ble_server {
BLE2901::BLE2901(const std::string &value) : BLE2901((uint8_t *) value.data(), value.length()) {}
BLE2901::BLE2901(const uint8_t *data, size_t length) : BLEDescriptor(ESPBTUUID::from_uint16(0x2901)) {
BLE2901::BLE2901(const uint8_t *data, size_t length) : BLEDescriptor(esp32_ble::ESPBTUUID::from_uint16(0x2901)) {
this->set_value(data, length);
this->permissions_ = ESP_GATT_PERM_READ;
}
} // namespace esp32_ble
} // namespace esp32_ble_server
} // namespace esphome
#endif

View File

@@ -5,7 +5,7 @@
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace esp32_ble {
namespace esp32_ble_server {
class BLE2901 : public BLEDescriptor {
public:
@@ -13,7 +13,7 @@ class BLE2901 : public BLEDescriptor {
BLE2901(const uint8_t *data, size_t length);
};
} // namespace esp32_ble
} // namespace esp32_ble_server
} // namespace esphome
#endif

View File

@@ -1,18 +1,18 @@
#include "ble_2902.h"
#include "ble_uuid.h"
#include "esphome/components/esp32_ble/ble_uuid.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace esp32_ble {
namespace esp32_ble_server {
BLE2902::BLE2902() : BLEDescriptor(ESPBTUUID::from_uint16(0x2902)) {
BLE2902::BLE2902() : BLEDescriptor(esp32_ble::ESPBTUUID::from_uint16(0x2902)) {
this->value_.attr_len = 2;
uint8_t data[2] = {0, 0};
memcpy(this->value_.attr_value, data, 2);
}
} // namespace esp32_ble
} // namespace esp32_ble_server
} // namespace esphome
#endif

View File

@@ -5,14 +5,14 @@
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace esp32_ble {
namespace esp32_ble_server {
class BLE2902 : public BLEDescriptor {
public:
BLE2902();
};
} // namespace esp32_ble
} // namespace esp32_ble_server
} // namespace esphome
#endif

View File

@@ -7,16 +7,14 @@
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace esp32_ble {
namespace esp32_ble_server {
static const char *TAG = "esp32_ble.characteristic";
static const char *const TAG = "esp32_ble_server.characteristic";
BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) : uuid_(uuid) {
this->set_value_lock_ = xSemaphoreCreateBinary();
this->create_lock_ = xSemaphoreCreateBinary();
xSemaphoreGive(this->set_value_lock_);
xSemaphoreGive(this->create_lock_);
this->properties_ = (esp_gatt_char_prop_t) 0;
this->set_broadcast_property((properties & PROPERTY_BROADCAST) != 0);
@@ -100,12 +98,11 @@ void BLECharacteristic::notify(bool notification) {
void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) { this->descriptors_.push_back(descriptor); }
bool BLECharacteristic::do_create(BLEService *service) {
void BLECharacteristic::do_create(BLEService *service) {
this->service_ = service;
esp_attr_control_t control;
control.auto_rsp = ESP_GATT_RSP_BY_APP;
xSemaphoreTake(this->create_lock_, portMAX_DELAY);
ESP_LOGV(TAG, "Creating characteristic - %s", this->uuid_.to_string().c_str());
esp_bt_uuid_t uuid = this->uuid_.get_uuid();
@@ -114,15 +111,39 @@ bool BLECharacteristic::do_create(BLEService *service) {
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_add_char failed: %d", err);
return;
}
this->state_ = CREATING;
}
bool BLECharacteristic::is_created() {
if (this->state_ == CREATED)
return true;
if (this->state_ != CREATING_DEPENDENTS)
return false;
}
xSemaphoreWait(this->create_lock_, portMAX_DELAY);
bool created = true;
for (auto *descriptor : this->descriptors_) {
descriptor->do_create(this);
created &= descriptor->is_created();
}
return true;
if (created)
this->state_ = CREATED;
return this->state_ == CREATED;
}
bool BLECharacteristic::is_failed() {
if (this->state_ == FAILED)
return true;
bool failed = false;
for (auto *descriptor : this->descriptors_) {
failed |= descriptor->is_failed();
}
if (failed)
this->state_ = FAILED;
return this->state_ == FAILED;
}
void BLECharacteristic::set_broadcast_property(bool value) {
@@ -168,7 +189,12 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
case ESP_GATTS_ADD_CHAR_EVT: {
if (this->uuid_ == ESPBTUUID::from_uuid(param->add_char.char_uuid)) {
this->handle_ = param->add_char.attr_handle;
xSemaphoreGive(this->create_lock_);
for (auto *descriptor : this->descriptors_) {
descriptor->do_create(this);
}
this->state_ = CREATING_DEPENDENTS;
}
break;
}
@@ -274,7 +300,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
}
}
} // namespace esp32_ble
} // namespace esp32_ble_server
} // namespace esphome
#endif

View File

@@ -1,9 +1,9 @@
#pragma once
#include <vector>
#include "ble_uuid.h"
#include "ble_descriptor.h"
#include "esphome/components/esp32_ble/ble_uuid.h"
#include <vector>
#ifdef ARDUINO_ARCH_ESP32
@@ -14,7 +14,9 @@
#include <esp_bt_defs.h>
namespace esphome {
namespace esp32_ble {
namespace esp32_ble_server {
using namespace esp32_ble;
class BLEService;
@@ -42,10 +44,10 @@ class BLECharacteristic {
void notify(bool notification = true);
bool do_create(BLEService *service);
void do_create(BLEService *service);
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
void on_write(const std::function<void(const std::vector<uint8_t> &)> &func) { this->on_write_ = func; }
void on_write(const std::function<void(const std::vector<uint8_t> &)> &&func) { this->on_write_ = std::move(func); }
void add_descriptor(BLEDescriptor *descriptor);
@@ -60,6 +62,9 @@ class BLECharacteristic {
static const uint32_t PROPERTY_INDICATE = 1 << 4;
static const uint32_t PROPERTY_WRITE_NR = 1 << 5;
bool is_created();
bool is_failed();
protected:
bool write_event_{false};
BLEService *service_;
@@ -70,16 +75,23 @@ class BLECharacteristic {
uint16_t value_read_offset_{0};
std::vector<uint8_t> value_;
SemaphoreHandle_t set_value_lock_;
SemaphoreHandle_t create_lock_;
std::vector<BLEDescriptor *> descriptors_;
std::function<void(const std::vector<uint8_t> &)> on_write_;
esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE;
enum State : uint8_t {
FAILED = 0x00,
INIT,
CREATING,
CREATING_DEPENDENTS,
CREATED,
} state_{INIT};
};
} // namespace esp32_ble
} // namespace esp32_ble_server
} // namespace esphome
#endif

View File

@@ -7,39 +7,34 @@
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace esp32_ble {
namespace esp32_ble_server {
static const char *TAG = "esp32_ble.descriptor";
static const char *const TAG = "esp32_ble_server.descriptor";
BLEDescriptor::BLEDescriptor(ESPBTUUID uuid, uint16_t max_len) {
this->uuid_ = uuid;
this->value_.attr_len = 0;
this->value_.attr_max_len = max_len;
this->value_.attr_value = (uint8_t *) malloc(max_len);
this->create_lock_ = xSemaphoreCreateBinary();
xSemaphoreGive(this->create_lock_);
}
BLEDescriptor::~BLEDescriptor() { free(this->value_.attr_value); }
bool BLEDescriptor::do_create(BLECharacteristic *characteristic) {
void BLEDescriptor::do_create(BLECharacteristic *characteristic) {
this->characteristic_ = characteristic;
esp_attr_control_t control;
control.auto_rsp = ESP_GATT_AUTO_RSP;
xSemaphoreTake(this->create_lock_, portMAX_DELAY);
ESP_LOGV(TAG, "Creating descriptor - %s", this->uuid_.to_string().c_str());
esp_bt_uuid_t uuid = this->uuid_.get_uuid();
esp_err_t err = esp_ble_gatts_add_char_descr(this->characteristic_->get_service()->get_handle(), &uuid,
this->permissions_, &this->value_, &control);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_add_char_descr failed: %d", err);
return false;
this->state_ = FAILED;
return;
}
xSemaphoreWait(this->create_lock_, portMAX_DELAY);
return true;
this->state_ = CREATING;
}
void BLEDescriptor::set_value(const std::string &value) { this->set_value((uint8_t *) value.data(), value.length()); }
@@ -60,7 +55,7 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
this->characteristic_->get_service()->get_handle() == param->add_char_descr.service_handle &&
this->characteristic_ == this->characteristic_->get_service()->get_last_created_characteristic()) {
this->handle_ = param->add_char_descr.attr_handle;
xSemaphoreGive(this->create_lock_);
this->state_ = CREATED;
}
break;
}
@@ -76,7 +71,7 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
}
}
} // namespace esp32_ble
} // namespace esp32_ble_server
} // namespace esphome
#endif

View File

@@ -1,6 +1,6 @@
#pragma once
#include "ble_uuid.h"
#include "esphome/components/esp32_ble/ble_uuid.h"
#ifdef ARDUINO_ARCH_ESP32
@@ -8,7 +8,9 @@
#include <esp_gatts_api.h>
namespace esphome {
namespace esp32_ble {
namespace esp32_ble_server {
using namespace esp32_ble;
class BLECharacteristic;
@@ -16,25 +18,34 @@ class BLEDescriptor {
public:
BLEDescriptor(ESPBTUUID uuid, uint16_t max_len = 100);
virtual ~BLEDescriptor();
bool do_create(BLECharacteristic *characteristic);
void do_create(BLECharacteristic *characteristic);
void set_value(const std::string &value);
void set_value(const uint8_t *data, size_t length);
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
bool is_created() { return this->state_ == CREATED; }
bool is_failed() { return this->state_ == FAILED; }
protected:
BLECharacteristic *characteristic_{nullptr};
ESPBTUUID uuid_;
uint16_t handle_{0xFFFF};
SemaphoreHandle_t create_lock_;
esp_attr_value_t value_;
esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE;
enum State : uint8_t {
FAILED = 0x00,
INIT,
CREATING,
CREATED,
} state_{INIT};
};
} // namespace esp32_ble
} // namespace esp32_ble_server
} // namespace esphome
#endif

View File

@@ -1,5 +1,6 @@
#include "ble_server.h"
#include "ble.h"
#include "esphome/components/esp32_ble/ble.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/version.h"
@@ -14,9 +15,9 @@
#include <esp_gap_ble_api.h>
namespace esphome {
namespace esp32_ble {
namespace esp32_ble_server {
static const char *TAG = "esp32_ble.server";
static const char *const TAG = "esp32_ble_server";
static const uint16_t DEVICE_INFORMATION_SERVICE_UUID = 0x180A;
static const uint16_t MODEL_UUID = 0x2A24;
@@ -32,59 +33,68 @@ void BLEServer::setup() {
ESP_LOGD(TAG, "Setting up BLE Server...");
global_ble_server = this;
this->register_lock_ = xSemaphoreCreateBinary();
xSemaphoreGive(this->register_lock_);
this->advertising_ = new BLEAdvertising();
this->setup_server_();
for (auto *component : this->service_components_) {
component->setup_service();
}
ESP_LOGD(TAG, "BLE Server set up complete...");
}
void BLEServer::setup_server_() {
xSemaphoreTake(this->register_lock_, portMAX_DELAY);
esp_err_t err = esp_ble_gatts_app_register(0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_app_register failed: %d", err);
this->mark_failed();
return;
void BLEServer::loop() {
switch (this->state_) {
case RUNNING:
return;
case INIT: {
esp_err_t err = esp_ble_gatts_app_register(0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_app_register failed: %d", err);
this->mark_failed();
return;
}
this->state_ = REGISTERING;
break;
}
case REGISTERING: {
if (this->registered_) {
this->device_information_service_ = this->create_service(DEVICE_INFORMATION_SERVICE_UUID);
this->create_device_characteristics_();
this->state_ = STARTING_SERVICE;
}
break;
}
case STARTING_SERVICE: {
if (!this->device_information_service_->is_created()) {
break;
}
if (this->device_information_service_->is_running()) {
this->state_ = RUNNING;
this->can_proceed_ = true;
ESP_LOGD(TAG, "BLE server setup successfully");
} else if (!this->device_information_service_->is_starting()) {
this->device_information_service_->start();
}
break;
}
}
xSemaphoreWait(this->register_lock_, portMAX_DELAY);
this->device_information_service = this->create_service(DEVICE_INFORMATION_SERVICE_UUID);
this->create_device_characteristics_();
this->advertising_->set_scan_response(true);
this->advertising_->set_min_preferred_interval(0x06);
this->advertising_->start();
this->device_information_service->start();
}
bool BLEServer::create_device_characteristics_() {
if (this->model_.has_value()) {
BLECharacteristic *model =
this->device_information_service->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ);
this->device_information_service_->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ);
model->set_value(this->model_.value());
} else {
#ifdef ARDUINO_BOARD
BLECharacteristic *model =
this->device_information_service->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ);
this->device_information_service_->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ);
model->set_value(ARDUINO_BOARD);
#endif
}
BLECharacteristic *version =
this->device_information_service->create_characteristic(VERSION_UUID, BLECharacteristic::PROPERTY_READ);
this->device_information_service_->create_characteristic(VERSION_UUID, BLECharacteristic::PROPERTY_READ);
version->set_value("ESPHome " ESPHOME_VERSION);
BLECharacteristic *manufacturer =
this->device_information_service->create_characteristic(MANUFACTURER_UUID, BLECharacteristic::PROPERTY_READ);
this->device_information_service_->create_characteristic(MANUFACTURER_UUID, BLECharacteristic::PROPERTY_READ);
manufacturer->set_value(this->manufacturer_);
return true;
@@ -104,7 +114,7 @@ BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t n
BLEService *service = new BLEService(uuid, num_handles, inst_id);
this->services_.push_back(service);
if (advertise) {
this->advertising_->add_service_uuid(uuid);
esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid);
}
service->do_create(this);
return service;
@@ -126,7 +136,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga
ESP_LOGD(TAG, "BLE Client disconnected");
if (this->remove_client_(param->disconnect.conn_id))
this->connected_clients_--;
this->advertising_->start();
esp32_ble::global_ble->get_advertising()->start();
for (auto *component : this->service_components_) {
component->on_client_disconnect();
}
@@ -134,7 +144,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga
}
case ESP_GATTS_REG_EVT: {
this->gatts_if_ = gatts_if;
xSemaphoreGive(this->register_lock_);
this->registered_ = true;
break;
}
default:
@@ -152,7 +162,7 @@ void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); }
BLEServer *global_ble_server = nullptr;
} // namespace esp32_ble
} // namespace esp32_ble_server
} // namespace esphome
#endif

View File

@@ -1,15 +1,16 @@
#pragma once
#include "ble_service.h"
#include "ble_characteristic.h"
#include "esphome/components/esp32_ble/ble_advertising.h"
#include "esphome/components/esp32_ble/ble_uuid.h"
#include "esphome/components/esp32_ble/queue.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "ble_service.h"
#include "ble_characteristic.h"
#include "ble_uuid.h"
#include "ble_advertising.h"
#include <map>
#include "queue.h"
#include <map>
#ifdef ARDUINO_ARCH_ESP32
@@ -17,20 +18,25 @@
#include <esp_gatts_api.h>
namespace esphome {
namespace esp32_ble {
namespace esp32_ble_server {
using namespace esp32_ble;
class BLEServiceComponent {
public:
virtual void setup_service();
virtual void on_client_connect(){};
virtual void on_client_disconnect(){};
virtual void start();
virtual void stop();
};
class BLEServer : public Component {
public:
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
bool can_proceed() override { return this->can_proceed_; }
void teardown();
@@ -45,7 +51,6 @@ class BLEServer : public Component {
esp_gatt_if_t get_gatts_if() { return this->gatts_if_; }
uint32_t get_connected_client_count() { return this->connected_clients_; }
const std::map<uint16_t, void *> &get_clients() { return this->clients_; }
BLEAdvertising *get_advertising() { return this->advertising_; }
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
@@ -53,32 +58,38 @@ class BLEServer : public Component {
protected:
bool create_device_characteristics_();
void setup_server_();
void add_client_(uint16_t conn_id, void *client) {
this->clients_.insert(std::pair<uint16_t, void *>(conn_id, client));
}
bool remove_client_(uint16_t conn_id) { return this->clients_.erase(conn_id) > 0; }
bool can_proceed_{false};
std::string manufacturer_;
optional<std::string> model_;
esp_gatt_if_t gatts_if_{0};
BLEAdvertising *advertising_;
bool registered_{false};
uint32_t connected_clients_{0};
std::map<uint16_t, void *> clients_;
std::vector<BLEService *> services_;
BLEService *device_information_service;
BLEService *device_information_service_;
std::vector<BLEServiceComponent *> service_components_;
SemaphoreHandle_t register_lock_;
enum State : uint8_t {
INIT = 0x00,
REGISTERING,
STARTING_SERVICE,
RUNNING,
} state_{INIT};
};
extern BLEServer *global_ble_server;
} // namespace esp32_ble
} // namespace esp32_ble_server
} // namespace esphome
#endif

View File

@@ -5,20 +5,12 @@
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace esp32_ble {
namespace esp32_ble_server {
static const char *TAG = "esp32_ble.service";
static const char *const TAG = "esp32_ble_server.service";
BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id)
: uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id) {
this->create_lock_ = xSemaphoreCreateBinary();
this->start_lock_ = xSemaphoreCreateBinary();
this->stop_lock_ = xSemaphoreCreateBinary();
xSemaphoreGive(this->create_lock_);
xSemaphoreGive(this->start_lock_);
xSemaphoreGive(this->stop_lock_);
}
: uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id) {}
BLEService::~BLEService() {
for (auto &chr : this->characteristics_)
@@ -47,10 +39,9 @@ BLECharacteristic *BLEService::create_characteristic(ESPBTUUID uuid, esp_gatt_ch
return characteristic;
}
bool BLEService::do_create(BLEServer *server) {
void BLEService::do_create(BLEServer *server) {
this->server_ = server;
xSemaphoreTake(this->create_lock_, portMAX_DELAY);
esp_gatt_srvc_id_t srvc_id;
srvc_id.is_primary = true;
srvc_id.id.inst_id = this->inst_id_;
@@ -59,36 +50,58 @@ bool BLEService::do_create(BLEServer *server) {
esp_err_t err = esp_ble_gatts_create_service(server->get_gatts_if(), &srvc_id, this->num_handles_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_create_service failed: %d", err);
return false;
this->init_state_ = FAILED;
return;
}
xSemaphoreWait(this->create_lock_, portMAX_DELAY);
this->init_state_ = CREATING;
}
bool BLEService::do_create_characteristics_() {
if (this->created_characteristic_count_ >= this->characteristics_.size() &&
(this->last_created_characteristic_ == nullptr || this->last_created_characteristic_->is_created()))
return false; // Signifies there are no characteristics, or they are all finished being created.
if (this->last_created_characteristic_ != nullptr && !this->last_created_characteristic_->is_created())
return true; // Signifies that the previous characteristic is still being created.
auto *characteristic = this->characteristics_[this->created_characteristic_count_++];
this->last_created_characteristic_ = characteristic;
characteristic->do_create(this);
return true;
}
void BLEService::start() {
for (auto *characteristic : this->characteristics_) {
this->last_created_characteristic_ = characteristic;
characteristic->do_create(this);
}
if (this->do_create_characteristics_())
return;
xSemaphoreTake(this->start_lock_, portMAX_DELAY);
esp_err_t err = esp_ble_gatts_start_service(this->handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_start_service failed: %d", err);
return;
}
xSemaphoreWait(this->start_lock_, portMAX_DELAY);
this->running_state_ = STARTING;
}
void BLEService::stop() {
xSemaphoreTake(this->stop_lock_, portMAX_DELAY);
esp_err_t err = esp_ble_gatts_stop_service(this->handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_stop_service failed: %d", err);
return;
}
xSemaphoreWait(this->stop_lock_, portMAX_DELAY);
this->running_state_ = STOPPING;
}
bool BLEService::is_created() { return this->init_state_ == CREATED; }
bool BLEService::is_failed() {
if (this->init_state_ == FAILED)
return true;
bool failed = false;
for (auto *characteristic : this->characteristics_)
failed |= characteristic->is_failed();
if (failed)
this->init_state_ = FAILED;
return this->init_state_ == FAILED;
}
void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
@@ -98,19 +111,19 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g
if (this->uuid_ == ESPBTUUID::from_uuid(param->create.service_id.id.uuid) &&
this->inst_id_ == param->create.service_id.id.inst_id) {
this->handle_ = param->create.service_handle;
xSemaphoreGive(this->create_lock_);
this->init_state_ = CREATED;
}
break;
}
case ESP_GATTS_START_EVT: {
if (param->start.service_handle == this->handle_) {
xSemaphoreGive(this->start_lock_);
this->running_state_ = RUNNING;
}
break;
}
case ESP_GATTS_STOP_EVT: {
if (param->start.service_handle == this->handle_) {
xSemaphoreGive(this->stop_lock_);
this->running_state_ = STOPPED;
}
break;
}
@@ -123,7 +136,7 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g
}
}
} // namespace esp32_ble
} // namespace esp32_ble_server
} // namespace esphome
#endif

View File

@@ -1,7 +1,7 @@
#pragma once
#include "ble_uuid.h"
#include "ble_characteristic.h"
#include "esphome/components/esp32_ble/ble_uuid.h"
#ifdef ARDUINO_ARCH_ESP32
@@ -12,10 +12,12 @@
#include <esp_bt_defs.h>
namespace esphome {
namespace esp32_ble {
namespace esp32_ble_server {
class BLEServer;
using namespace esp32_ble;
class BLEService {
public:
BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id);
@@ -33,29 +35,47 @@ class BLEService {
BLEServer *get_server() { return this->server_; }
bool do_create(BLEServer *server);
void do_create(BLEServer *server);
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
void start();
void stop();
protected:
bool errored_{false};
bool is_created();
bool is_failed();
bool is_running() { return this->running_state_ == RUNNING; }
bool is_starting() { return this->running_state_ == STARTING; }
protected:
std::vector<BLECharacteristic *> characteristics_;
BLECharacteristic *last_created_characteristic_{nullptr};
uint32_t created_characteristic_count_{0};
BLEServer *server_;
ESPBTUUID uuid_;
uint16_t num_handles_;
uint16_t handle_{0xFFFF};
uint8_t inst_id_;
SemaphoreHandle_t create_lock_;
SemaphoreHandle_t start_lock_;
SemaphoreHandle_t stop_lock_;
bool do_create_characteristics_();
enum InitState : uint8_t {
FAILED = 0x00,
INIT,
CREATING,
CREATING_DEPENDENTS,
CREATED,
} init_state_{INIT};
enum RunningState : uint8_t {
STARTING,
RUNNING,
STOPPING,
STOPPED,
} running_state_{STOPPED};
};
} // namespace esp32_ble
} // namespace esp32_ble_server
} // namespace esphome
#endif

View File

@@ -1,12 +1,13 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor, output, esp32_ble
from esphome.components import binary_sensor, output, esp32_ble_server
from esphome.const import CONF_ID, ESP_PLATFORM_ESP32
AUTO_LOAD = ["binary_sensor", "output", "improv"]
AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"]
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["esp32_ble", "wifi"]
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
DEPENDENCIES = ["wifi"]
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
CONF_AUTHORIZED_DURATION = "authorized_duration"
@@ -18,7 +19,7 @@ CONF_WIFI_TIMEOUT = "wifi_timeout"
esp32_improv_ns = cg.esphome_ns.namespace("esp32_improv")
ESP32ImprovComponent = esp32_improv_ns.class_(
"ESP32ImprovComponent", cg.Component, esp32_ble.BLEServiceComponent
"ESP32ImprovComponent", cg.Component, esp32_ble_server.BLEServiceComponent
)
@@ -33,7 +34,7 @@ def validate_none_(value):
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32ImprovComponent),
cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble.BLEServer),
cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble_server.BLEServer),
cv.Required(CONF_AUTHORIZER): cv.Any(
validate_none_, cv.use_id(binary_sensor.BinarySensor)
),

View File

@@ -1,7 +1,9 @@
#include "esp32_improv_component.h"
#include "esphome/core/log.h"
#include "esphome/components/esp32_ble/ble.h"
#include "esphome/components/esp32_ble_server/ble_2902.h"
#include "esphome/core/application.h"
#include "esphome/components/esp32_ble/ble_2902.h"
#include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32
@@ -12,38 +14,39 @@ static const char *TAG = "esp32_improv.component";
ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
void ESP32ImprovComponent::setup_service() {
this->service_ = esp32_ble::global_ble_server->create_service(improv::SERVICE_UUID, true);
void ESP32ImprovComponent::setup() {
this->service_ = global_ble_server->create_service(improv::SERVICE_UUID, true);
this->setup_characteristics();
}
void ESP32ImprovComponent::setup_characteristics() {
this->status_ = this->service_->create_characteristic(
improv::STATUS_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ | esp32_ble::BLECharacteristic::PROPERTY_NOTIFY);
esp32_ble::BLEDescriptor *status_descriptor = new esp32_ble::BLE2902();
improv::STATUS_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor *status_descriptor = new BLE2902();
this->status_->add_descriptor(status_descriptor);
this->error_ = this->service_->create_characteristic(
improv::ERROR_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ | esp32_ble::BLECharacteristic::PROPERTY_NOTIFY);
esp32_ble::BLEDescriptor *error_descriptor = new esp32_ble::BLE2902();
improv::ERROR_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor *error_descriptor = new BLE2902();
this->error_->add_descriptor(error_descriptor);
this->rpc_ =
this->service_->create_characteristic(improv::RPC_COMMAND_UUID, esp32_ble::BLECharacteristic::PROPERTY_WRITE);
this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE);
this->rpc_->on_write([this](const std::vector<uint8_t> &data) {
if (data.size() > 0) {
this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end());
}
});
esp32_ble::BLEDescriptor *rpc_descriptor = new esp32_ble::BLE2902();
BLEDescriptor *rpc_descriptor = new BLE2902();
this->rpc_->add_descriptor(rpc_descriptor);
this->rpc_response_ =
this->service_->create_characteristic(improv::RPC_RESULT_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ |
esp32_ble::BLECharacteristic::PROPERTY_NOTIFY);
esp32_ble::BLEDescriptor *rpc_response_descriptor = new esp32_ble::BLE2902();
this->rpc_response_ = this->service_->create_characteristic(
improv::RPC_RESULT_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor *rpc_response_descriptor = new BLE2902();
this->rpc_response_->add_descriptor(rpc_response_descriptor);
this->capabilities_ =
this->service_->create_characteristic(improv::CAPABILITIES_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ);
esp32_ble::BLEDescriptor *capabilities_descriptor = new esp32_ble::BLE2902();
this->service_->create_characteristic(improv::CAPABILITIES_UUID, BLECharacteristic::PROPERTY_READ);
BLEDescriptor *capabilities_descriptor = new BLE2902();
this->capabilities_->add_descriptor(capabilities_descriptor);
uint8_t capabilities = 0x00;
if (this->status_indicator_ != nullptr)
@@ -62,16 +65,17 @@ void ESP32ImprovComponent::loop() {
if (this->status_indicator_ != nullptr)
this->status_indicator_->turn_off();
if (this->should_start_ && this->setup_complete_) {
ESP_LOGD(TAG, "Starting Improv service...");
if (this->service_->is_created() && this->should_start_ && this->setup_complete_) {
if (this->service_->is_running()) {
esp32_ble::global_ble->get_advertising()->start();
this->service_->start();
this->service_->get_server()->get_advertising()->start();
this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
this->set_error_(improv::ERROR_NONE);
this->should_start_ = false;
ESP_LOGD(TAG, "Service started!");
this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
this->set_error_(improv::ERROR_NONE);
this->should_start_ = false;
ESP_LOGD(TAG, "Service started!");
} else {
this->service_->start();
}
}
break;
case improv::STATE_AWAITING_AUTHORIZATION: {
@@ -191,17 +195,14 @@ void ESP32ImprovComponent::start() {
this->should_start_ = true;
}
void ESP32ImprovComponent::end() {
void ESP32ImprovComponent::stop() {
this->set_timeout("end-service", 1000, [this] {
this->service_->stop();
this->set_state_(improv::STATE_STOPPED);
});
}
float ESP32ImprovComponent::get_setup_priority() const {
// Before WiFi
return setup_priority::AFTER_BLUETOOTH;
}
float ESP32ImprovComponent::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
void ESP32ImprovComponent::dump_config() {
ESP_LOGCONFIG(TAG, "ESP32 Improv:");

View File

@@ -1,31 +1,34 @@
#pragma once
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/esp32_ble_server/ble_server.h"
#include "esphome/components/esp32_ble_server/ble_characteristic.h"
#include "esphome/components/improv/improv.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/wifi/wifi_component.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/esp32_ble/ble_server.h"
#include "esphome/components/esp32_ble/ble_characteristic.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/wifi/wifi_component.h"
#include "esphome/components/improv/improv.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace esp32_improv {
class ESP32ImprovComponent : public Component, public esp32_ble::BLEServiceComponent {
using namespace esp32_ble_server;
class ESP32ImprovComponent : public Component, public BLEServiceComponent {
public:
ESP32ImprovComponent();
void dump_config() override;
void loop() override;
void setup_service() override;
void setup() override;
void setup_characteristics();
void on_client_disconnect() override;
float get_setup_priority() const override;
void start();
void end();
void start() override;
void stop() override;
bool is_active() const { return this->state_ != improv::STATE_STOPPED; }
void set_authorizer(binary_sensor::BinarySensor *authorizer) { this->authorizer_ = authorizer; }
@@ -45,12 +48,12 @@ class ESP32ImprovComponent : public Component, public esp32_ble::BLEServiceCompo
std::vector<uint8_t> incoming_data_;
wifi::WiFiAP connecting_sta_;
esp32_ble::BLEService *service_;
esp32_ble::BLECharacteristic *status_;
esp32_ble::BLECharacteristic *error_;
esp32_ble::BLECharacteristic *rpc_;
esp32_ble::BLECharacteristic *rpc_response_;
esp32_ble::BLECharacteristic *capabilities_;
BLEService *service_;
BLECharacteristic *status_;
BLECharacteristic *error_;
BLECharacteristic *rpc_;
BLECharacteristic *rpc_response_;
BLECharacteristic *capabilities_;
binary_sensor::BinarySensor *authorizer_{nullptr};
output::BinaryOutput *status_indicator_{nullptr};

View File

@@ -132,7 +132,7 @@ void FujitsuGeneralClimate::transmit_state() {
case climate::CLIMATE_MODE_FAN_ONLY:
SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_FAN);
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
default:
SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_AUTO);
break;
@@ -343,7 +343,7 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) {
case FUJITSU_GENERAL_MODE_AUTO:
default:
// TODO: CLIMATE_MODE_10C is missing from esphome
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
}

View File

@@ -62,6 +62,7 @@ CONFIG_SCHEMA = (
.extend(cv.polling_component_schema("20s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("gps", require_rx=True)
async def to_code(config):
@@ -95,7 +96,3 @@ async def to_code(config):
# https://platformio.org/lib/show/1655/TinyGPSPlus
cg.add_library("1655", "1.0.2") # TinyGPSPlus, has name conflict
def validate(config, item_config):
uart.validate_device("gps", config, item_config, require_tx=False)

View File

@@ -155,7 +155,7 @@ void HitachiClimate::transmit_state() {
case climate::CLIMATE_MODE_HEAT:
set_mode_(HITACHI_AC344_MODE_HEAT);
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
set_mode_(HITACHI_AC344_MODE_AUTO);
break;
case climate::CLIMATE_MODE_FAN_ONLY:
@@ -251,7 +251,7 @@ bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) {
this->mode = climate::CLIMATE_MODE_HEAT;
break;
case HITACHI_AC344_MODE_AUTO:
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
case HITACHI_AC344_MODE_FAN:
this->mode = climate::CLIMATE_MODE_FAN_ONLY;

View File

@@ -79,11 +79,10 @@ const uint16_t HITACHI_AC344_BITS = HITACHI_AC344_STATE_LENGTH * 8;
class HitachiClimate : public climate_ir::ClimateIR {
public:
HitachiClimate()
: climate_ir::ClimateIR(
HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true,
std::vector<climate::ClimateFanMode>{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW,
climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {}
: climate_ir::ClimateIR(HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true,
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {}
protected:
uint8_t remote_state_[HITACHI_AC344_STATE_LENGTH]{0x01, 0x10, 0x00, 0x40, 0x00, 0xFF, 0x00, 0xCC, 0x00, 0x00, 0x00,

View File

@@ -2,19 +2,16 @@ import urllib.parse as urlparse
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome import automation
from esphome.const import (
CONF_ID,
CONF_TIMEOUT,
CONF_ESPHOME,
CONF_METHOD,
CONF_ARDUINO_VERSION,
ARDUINO_VERSION_ESP8266,
CONF_TRIGGER_ID,
CONF_URL,
)
from esphome.core import CORE, Lambda
from esphome.core.config import PLATFORMIO_ESP8266_LUT
DEPENDENCIES = ["network"]
AUTO_LOAD = ["json"]
@@ -36,29 +33,6 @@ CONF_VERIFY_SSL = "verify_ssl"
CONF_ON_RESPONSE = "on_response"
def validate_framework(config):
if CORE.is_esp32:
return config
version = "RECOMMENDED"
if CONF_ARDUINO_VERSION in CORE.raw_config[CONF_ESPHOME]:
version = CORE.raw_config[CONF_ESPHOME][CONF_ARDUINO_VERSION]
if version in ["LATEST", "DEV"]:
return config
framework = (
PLATFORMIO_ESP8266_LUT[version]
if version in PLATFORMIO_ESP8266_LUT
else version
)
if framework < ARDUINO_VERSION_ESP8266["2.5.1"]:
raise cv.Invalid(
"This component is not supported on arduino framework version below 2.5.1"
)
return config
def validate_url(value):
value = cv.string(value)
try:
@@ -92,19 +66,32 @@ def validate_secure_url(config):
return config
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(HttpRequestComponent),
cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string,
cv.Optional(
CONF_TIMEOUT, default="5s"
): cv.positive_time_period_milliseconds,
}
)
.add_extra(validate_framework)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(HttpRequestComponent),
cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string,
cv.Optional(CONF_TIMEOUT, default="5s"): cv.positive_time_period_milliseconds,
}
).extend(cv.COMPONENT_SCHEMA)
def validate_framework(value):
if not CORE.is_esp8266:
# only for ESP8266
return
framework_version = fv.get_arduino_framework_version()
if framework_version is None or framework_version == "dev":
return
if framework_version < "2.5.1":
raise cv.Invalid(
"This component is not supported on arduino framework version below 2.5.1, ",
"please check esphome->arduino_version",
)
FINAL_VALIDATE_SCHEMA = cv.Schema(validate_framework)
async def to_code(config):

View File

@@ -407,7 +407,7 @@ LightColorValues LightCall::validate_() {
this->color_temperature_.reset();
}
// sets RGB to 100% if only White specified
// If white channel is specified, set RGB to white color (when interlock is enabled)
if (this->white_.has_value()) {
if (traits.get_supports_color_interlock()) {
if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
@@ -415,7 +415,7 @@ LightColorValues LightCall::validate_() {
this->green_ = optional<float>(1.0f);
this->blue_ = optional<float>(1.0f);
}
// make white values binary aka 0.0f or 1.0f...this allows brightness to do its job
// make white values binary aka 0.0f or 1.0f... this allows brightness to do its job
if (*this->white_ > 0.0f) {
this->white_ = optional<float>(1.0f);
} else {
@@ -423,44 +423,27 @@ LightColorValues LightCall::validate_() {
}
}
}
// White to 0% if (exclusively) setting any RGB value that isn't 255,255,255
// If only a color channel is specified, set white channel to 100% for white, otherwise 0% (when interlock is enabled)
else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
if (traits.get_supports_color_interlock()) {
if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f &&
traits.get_supports_rgb_white_value() && traits.get_supports_color_interlock()) {
if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) {
this->white_ = optional<float>(1.0f);
} else if (!this->white_.has_value() || !traits.get_supports_rgb_white_value()) {
} else {
this->white_ = optional<float>(0.0f);
}
}
}
// if changing Kelvin alone, change to white light
// If only a color temperature is specified, change to white light
else if (this->color_temperature_.has_value()) {
if (!traits.get_supports_color_interlock()) {
if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
this->red_ = optional<float>(1.0f);
this->green_ = optional<float>(1.0f);
this->blue_ = optional<float>(1.0f);
}
}
// if setting Kelvin from color (i.e. switching to white light), set White to 100%
this->red_ = optional<float>(1.0f);
this->green_ = optional<float>(1.0f);
this->blue_ = optional<float>(1.0f);
// if setting color temperature from color (i.e. switching to white light), set White to 100%
auto cv = this->parent_->remote_values;
bool was_color = cv.get_red() != 1.0f || cv.get_blue() != 1.0f || cv.get_green() != 1.0f;
bool now_white = *this->red_ == 1.0f && *this->blue_ == 1.0f && *this->green_ == 1.0f;
if (traits.get_supports_color_interlock()) {
if (cv.get_white() < 1.0f) {
this->white_ = optional<float>(1.0f);
}
if (was_color && !this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
this->red_ = optional<float>(1.0f);
this->green_ = optional<float>(1.0f);
this->blue_ = optional<float>(1.0f);
}
} else {
if (!this->white_.has_value() && was_color && now_white) {
this->white_ = optional<float>(1.0f);
}
if (traits.get_supports_color_interlock() || was_color) {
this->white_ = optional<float>(1.0f);
}
}

View File

@@ -167,24 +167,38 @@ climate::ClimateTraits MideaAC::traits() {
traits.set_visual_min_temperature(17);
traits.set_visual_max_temperature(30);
traits.set_visual_temperature_step(0.5);
traits.set_supports_auto_mode(true);
traits.set_supports_cool_mode(true);
traits.set_supports_dry_mode(true);
traits.set_supports_heat_mode(true);
traits.set_supports_fan_only_mode(true);
traits.set_supports_fan_mode_auto(true);
traits.set_supports_fan_mode_low(true);
traits.set_supports_fan_mode_medium(true);
traits.set_supports_fan_mode_high(true);
traits.set_supported_modes({
climate::CLIMATE_MODE_OFF,
climate::CLIMATE_MODE_HEAT_COOL,
climate::CLIMATE_MODE_COOL,
climate::CLIMATE_MODE_DRY,
climate::CLIMATE_MODE_HEAT,
climate::CLIMATE_MODE_FAN_ONLY,
});
traits.set_supported_fan_modes({
climate::CLIMATE_FAN_AUTO,
climate::CLIMATE_FAN_LOW,
climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH,
});
traits.set_supported_custom_fan_modes(this->traits_custom_fan_modes_);
traits.set_supports_swing_mode_off(true);
traits.set_supports_swing_mode_vertical(true);
traits.set_supports_swing_mode_horizontal(this->traits_swing_horizontal_);
traits.set_supports_swing_mode_both(this->traits_swing_both_);
traits.set_supports_preset_home(true);
traits.set_supports_preset_eco(this->traits_preset_eco_);
traits.set_supports_preset_sleep(this->traits_preset_sleep_);
traits.set_supports_preset_boost(this->traits_preset_boost_);
traits.set_supported_swing_modes({
climate::CLIMATE_SWING_OFF,
climate::CLIMATE_SWING_VERTICAL,
});
if (traits_swing_horizontal_)
traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL);
if (traits_swing_both_)
traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH);
traits.set_supported_presets({
climate::CLIMATE_PRESET_HOME,
});
if (traits_preset_eco_)
traits.add_supported_preset(climate::CLIMATE_PRESET_ECO);
if (traits_preset_sleep_)
traits.add_supported_preset(climate::CLIMATE_PRESET_SLEEP);
if (traits_preset_boost_)
traits.add_supported_preset(climate::CLIMATE_PRESET_BOOST);
traits.set_supported_custom_presets(this->traits_custom_presets_);
traits.set_supports_current_temperature(true);
return traits;

View File

@@ -1,9 +1,9 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/midea_dongle/midea_dongle.h"
#include "esphome/components/climate/climate.h"
#include "esphome/components/midea_dongle/midea_dongle.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "midea_frame.h"
namespace esphome {
@@ -26,10 +26,12 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu
void set_preset_sleep(bool state) { this->traits_preset_sleep_ = state; }
void set_preset_boost(bool state) { this->traits_preset_boost_ = state; }
bool allow_preset(climate::ClimatePreset preset) const;
void set_custom_fan_modes(std::vector<std::string> custom_fan_modes) {
this->traits_custom_fan_modes_ = custom_fan_modes;
void set_custom_fan_modes(std::set<std::string> custom_fan_modes) {
this->traits_custom_fan_modes_ = std::move(custom_fan_modes);
}
void set_custom_presets(std::set<std::string> custom_presets) {
this->traits_custom_presets_ = std::move(custom_presets);
}
void set_custom_presets(std::vector<std::string> custom_presets) { this->traits_custom_presets_ = custom_presets; }
bool allow_custom_preset(const std::string &custom_preset) const;
protected:
@@ -53,8 +55,8 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu
bool traits_preset_eco_{false};
bool traits_preset_sleep_{false};
bool traits_preset_boost_{false};
std::vector<std::string> traits_custom_fan_modes_{{}};
std::vector<std::string> traits_custom_presets_{{}};
std::set<std::string> traits_custom_fan_modes_{{}};
std::set<std::string> traits_custom_presets_{{}};
};
} // namespace midea_ac

View File

@@ -8,9 +8,9 @@ const std::string MIDEA_SILENT_FAN_MODE = "silent";
const std::string MIDEA_TURBO_FAN_MODE = "turbo";
const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection";
const uint8_t QueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x00,
0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x68};
const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x81,
0x00, 0xFF, 0x03, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x37, 0x31};
const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21,
0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -44,7 +44,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const {
return climate::CLIMATE_MODE_OFF;
switch (this->pbuf_[12] >> 5) {
case MIDEA_MODE_AUTO:
return climate::CLIMATE_MODE_AUTO;
return climate::CLIMATE_MODE_HEAT_COOL;
case MIDEA_MODE_COOL:
return climate::CLIMATE_MODE_COOL;
case MIDEA_MODE_DRY:
@@ -61,7 +61,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const {
void PropertiesFrame::set_mode(climate::ClimateMode mode) {
uint8_t m;
switch (mode) {
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
m = MIDEA_MODE_AUTO;
break;
case climate::CLIMATE_MODE_COOL:

View File

@@ -33,7 +33,7 @@ void MitsubishiClimate::transmit_state() {
case climate::CLIMATE_MODE_HEAT:
remote_state[6] = MITSUBISHI_HEAT;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state[6] = MITSUBISHI_AUTO;
break;
case climate::CLIMATE_MODE_OFF:

View File

@@ -61,7 +61,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
// temp_step
root["temp_step"] = traits.get_visual_temperature_step();
if (traits.get_supports_away()) {
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
// away_mode_command_topic
root["away_mode_cmd_t"] = this->get_away_command_topic();
// away_mode_state_topic
@@ -164,19 +164,19 @@ void MQTTClimateComponent::setup() {
});
}
if (traits.get_supports_away()) {
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) {
auto onoff = parse_on_off(payload.c_str());
auto call = this->device_->make_call();
switch (onoff) {
case PARSE_ON:
call.set_away(true);
call.set_preset(CLIMATE_PRESET_AWAY);
break;
case PARSE_OFF:
call.set_away(false);
call.set_preset(CLIMATE_PRESET_HOME);
break;
case PARSE_TOGGLE:
call.set_away(!this->device_->away);
call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY);
break;
case PARSE_NONE:
default:
@@ -259,8 +259,8 @@ bool MQTTClimateComponent::publish_state_() {
success = false;
}
if (traits.get_supports_away()) {
std::string payload = ONOFF(this->device_->away);
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY);
if (!this->publish(this->get_away_state_topic(), payload))
success = false;
}

View File

@@ -20,7 +20,7 @@ void PIDClimate::setup() {
restore->to_call(this).perform();
} else {
// restore from defaults, change_away handles those for us
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
this->target_temperature = this->default_target_temperature_;
}
}
@@ -31,7 +31,7 @@ void PIDClimate::control(const climate::ClimateCall &call) {
this->target_temperature = *call.get_target_temperature();
// If switching to non-auto mode, set output immediately
if (this->mode != climate::CLIMATE_MODE_AUTO)
if (this->mode != climate::CLIMATE_MODE_HEAT_COOL)
this->handle_non_auto_mode_();
this->publish_state();
@@ -39,10 +39,14 @@ void PIDClimate::control(const climate::ClimateCall &call) {
climate::ClimateTraits PIDClimate::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);
traits.set_supports_auto_mode(true);
traits.set_supports_two_point_target_temperature(false);
traits.set_supports_cool_mode(this->supports_cool_());
traits.set_supports_heat_mode(this->supports_heat_());
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
if (supports_cool_())
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
if (supports_heat_())
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
traits.set_supports_action(true);
return traits;
}
@@ -121,14 +125,14 @@ void PIDClimate::update_pid_() {
// keep autotuner instance so that subsequent dump_configs will print the long result message.
} else {
value = res.output;
if (mode != climate::CLIMATE_MODE_AUTO) {
if (mode != climate::CLIMATE_MODE_HEAT_COOL) {
ESP_LOGW(TAG, "For PID autotuner you need to set AUTO (also called heat/cool) mode!");
}
}
}
}
if (this->mode != climate::CLIMATE_MODE_AUTO) {
if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) {
this->handle_non_auto_mode_();
} else {
this->write_output_(value);

View File

@@ -19,13 +19,12 @@ CONFIG_SCHEMA = cv.All(
).extend(spi.spi_device_schema(cs_pin_required=True))
)
FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema(
"rc522_spi", require_miso=True, require_mosi=True
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await rc522.setup_rc522(var, config)
await spi.register_spi_device(var, config)
def validate(config, item_config):
# validate given SPI hub is suitable for rc522_spi, it needs both miso and mosi
spi.validate_device("rc522_spi", config, item_config, True, True)

View File

@@ -1,6 +1,7 @@
import logging
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome import automation
from esphome.components.output import FloatOutput
from esphome.const import CONF_ID, CONF_OUTPUT, CONF_PLATFORM, CONF_TRIGGER_ID
@@ -36,12 +37,8 @@ CONFIG_SCHEMA = cv.Schema(
).extend(cv.COMPONENT_SCHEMA)
def validate(config, item_config):
# Not adding this to FloatOutput as this is the only component which needs `update_frequency`
parent_config = config.get_config_by_id(item_config[CONF_OUTPUT])
platform = parent_config[CONF_PLATFORM]
def validate_parent_output_config(value):
platform = value.get(CONF_PLATFORM)
PWM_GOOD = ["esp8266_pwm", "ledc"]
PWM_BAD = [
"ac_dimmer ",
@@ -55,14 +52,25 @@ def validate(config, item_config):
]
if platform in PWM_BAD:
raise ValueError(f"Component rtttl cannot use {platform} as output component")
raise cv.Invalid(f"Component rtttl cannot use {platform} as output component")
if platform not in PWM_GOOD:
_LOGGER.warning(
"Component rtttl is not known to work with the selected output type. Make sure this output supports custom frequency output method."
"Component rtttl is not known to work with the selected output type. "
"Make sure this output supports custom frequency output method."
)
FINAL_VALIDATE_SCHEMA = cv.Schema(
{
cv.Required(CONF_OUTPUT): fv.id_declaration_match_schema(
validate_parent_output_config
)
},
extra=cv.ALLOW_EXTRA,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -40,6 +40,9 @@ CONFIG_SCHEMA = cv.All(
.extend(cv.polling_component_schema("5s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"sim800l", baud_rate=9600, require_tx=True, require_rx=True
)
async def to_code(config):
@@ -54,10 +57,6 @@ async def to_code(config):
)
def validate(config, item_config):
uart.validate_device("sim800l", config, item_config, baud_rate=9600)
SIM800L_SEND_SMS_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(Sim800LComponent),

View File

@@ -1,5 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome import pins
from esphome.const import (
CONF_CLK_PIN,
@@ -69,9 +70,24 @@ async def register_spi_device(var, config):
cg.add(var.set_cs_pin(pin))
def validate_device(name, config, item_config, require_mosi, require_miso):
spi_config = config.get_config_by_id(item_config[CONF_SPI_ID])
if require_mosi and CONF_MISO_PIN not in spi_config:
raise ValueError(f"Component {name} requires parent spi to declare miso_pin")
if require_miso and CONF_MOSI_PIN not in spi_config:
raise ValueError(f"Component {name} requires parent spi to declare mosi_pin")
def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool):
hub_schema = {}
if require_miso:
hub_schema[
cv.Required(
CONF_MISO_PIN,
msg=f"Component {name} requires this spi bus to declare a miso_pin",
)
] = cv.valid
if require_mosi:
hub_schema[
cv.Required(
CONF_MOSI_PIN,
msg=f"Component {name} requires this spi bus to declare a mosi_pin",
)
] = cv.valid
return cv.Schema(
{cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(hub_schema)},
extra=cv.ALLOW_EXTRA,
)

View File

@@ -47,7 +47,7 @@ void Tcl112Climate::transmit_state() {
// Set mode
switch (this->mode) {
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state[6] &= 0xF0;
remote_state[6] |= TCL112_AUTO;
break;
@@ -204,7 +204,7 @@ bool Tcl112Climate::on_receive(remote_base::RemoteReceiveData data) {
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
break;
case TCL112_AUTO:
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
}
}

View File

@@ -227,7 +227,7 @@ async def to_code(config):
await cg.register_component(var, config)
await climate.register_climate(var, config)
auto_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config
heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config
two_points_available = CONF_HEAT_ACTION in config and (
CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config
)
@@ -258,10 +258,10 @@ async def to_code(config):
var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION]
)
if auto_mode_available is True:
cg.add(var.set_supports_auto(True))
if heat_cool_mode_available is True:
cg.add(var.set_supports_heat_cool(True))
else:
cg.add(var.set_supports_auto(False))
cg.add(var.set_supports_heat_cool(False))
if CONF_COOL_ACTION in config:
await automation.build_automation(

View File

@@ -21,7 +21,7 @@ void ThermostatClimate::setup() {
restore->to_call(this).perform();
} else {
// restore from defaults, change_away handles temps for us
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
this->change_away_(false);
}
// refresh the climate action based on the restored settings
@@ -50,12 +50,13 @@ void ThermostatClimate::control(const climate::ClimateCall &call) {
this->target_temperature_low = *call.get_target_temperature_low();
if (call.get_target_temperature_high().has_value())
this->target_temperature_high = *call.get_target_temperature_high();
if (call.get_away().has_value()) {
if (call.get_preset().has_value()) {
// setup_complete_ blocks modifying/resetting the temps immediately after boot
if (this->setup_complete_) {
this->change_away_(*call.get_away());
this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY);
} else {
this->away = *call.get_away();
this->preset = *call.get_preset();
;
}
}
// set point validation
@@ -78,26 +79,51 @@ void ThermostatClimate::control(const climate::ClimateCall &call) {
climate::ClimateTraits ThermostatClimate::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);
traits.set_supports_auto_mode(this->supports_auto_);
traits.set_supports_cool_mode(this->supports_cool_);
traits.set_supports_dry_mode(this->supports_dry_);
traits.set_supports_fan_only_mode(this->supports_fan_only_);
traits.set_supports_heat_mode(this->supports_heat_);
traits.set_supports_fan_mode_on(this->supports_fan_mode_on_);
traits.set_supports_fan_mode_off(this->supports_fan_mode_off_);
traits.set_supports_fan_mode_auto(this->supports_fan_mode_auto_);
traits.set_supports_fan_mode_low(this->supports_fan_mode_low_);
traits.set_supports_fan_mode_medium(this->supports_fan_mode_medium_);
traits.set_supports_fan_mode_high(this->supports_fan_mode_high_);
traits.set_supports_fan_mode_middle(this->supports_fan_mode_middle_);
traits.set_supports_fan_mode_focus(this->supports_fan_mode_focus_);
traits.set_supports_fan_mode_diffuse(this->supports_fan_mode_diffuse_);
traits.set_supports_swing_mode_both(this->supports_swing_mode_both_);
traits.set_supports_swing_mode_horizontal(this->supports_swing_mode_horizontal_);
traits.set_supports_swing_mode_off(this->supports_swing_mode_off_);
traits.set_supports_swing_mode_vertical(this->supports_swing_mode_vertical_);
if (supports_auto_)
traits.add_supported_mode(climate::CLIMATE_MODE_AUTO);
if (supports_heat_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
if (supports_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
if (supports_dry_)
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
if (supports_fan_only_)
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
if (supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
if (supports_fan_mode_on_)
traits.add_supported_fan_mode(climate::CLIMATE_FAN_ON);
if (supports_fan_mode_off_)
traits.add_supported_fan_mode(climate::CLIMATE_FAN_OFF);
if (supports_fan_mode_auto_)
traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO);
if (supports_fan_mode_low_)
traits.add_supported_fan_mode(climate::CLIMATE_FAN_LOW);
if (supports_fan_mode_medium_)
traits.add_supported_fan_mode(climate::CLIMATE_FAN_MEDIUM);
if (supports_fan_mode_high_)
traits.add_supported_fan_mode(climate::CLIMATE_FAN_HIGH);
if (supports_fan_mode_middle_)
traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE);
if (supports_fan_mode_focus_)
traits.add_supported_fan_mode(climate::CLIMATE_FAN_FOCUS);
if (supports_fan_mode_diffuse_)
traits.add_supported_fan_mode(climate::CLIMATE_FAN_DIFFUSE);
if (supports_swing_mode_both_)
traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH);
if (supports_swing_mode_horizontal_)
traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL);
if (supports_swing_mode_off_)
traits.add_supported_swing_mode(climate::CLIMATE_SWING_OFF);
if (supports_swing_mode_vertical_)
traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL);
if (supports_away_)
traits.set_supported_presets({climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_AWAY});
traits.set_supports_two_point_target_temperature(this->supports_two_points_);
traits.set_supports_away(this->supports_away_);
traits.set_supports_action(true);
return traits;
}
@@ -130,7 +156,7 @@ climate::ClimateAction ThermostatClimate::compute_action_() {
case climate::CLIMATE_MODE_OFF:
target_action = climate::CLIMATE_ACTION_OFF;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
case climate::CLIMATE_MODE_COOL:
case climate::CLIMATE_MODE_HEAT:
if (this->supports_cool_) {
@@ -321,7 +347,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) {
case climate::CLIMATE_MODE_OFF:
trig = this->off_mode_trigger_;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
// trig = this->auto_mode_trigger_;
break;
case climate::CLIMATE_MODE_COOL:
@@ -339,7 +365,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) {
default:
// we cannot report an invalid mode back to HA (even if it asked for one)
// and must assume some valid value
mode = climate::CLIMATE_MODE_AUTO;
mode = climate::CLIMATE_MODE_HEAT_COOL;
// trig = this->auto_mode_trigger_;
}
assert(trig != nullptr);
@@ -398,7 +424,7 @@ void ThermostatClimate::change_away_(bool away) {
} else
this->target_temperature = this->away_config_.default_temperature;
}
this->away = away;
this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME;
}
void ThermostatClimate::set_normal_config(const ThermostatClimateTargetTempConfig &normal_config) {
this->normal_config_ = normal_config;
@@ -434,6 +460,9 @@ ThermostatClimate::ThermostatClimate()
swing_mode_vertical_trigger_(new Trigger<>()) {}
void ThermostatClimate::set_hysteresis(float hysteresis) { this->hysteresis_ = hysteresis; }
void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) {
this->supports_heat_cool_ = supports_heat_cool;
}
void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_auto_ = supports_auto; }
void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; }
@@ -521,6 +550,7 @@ void ThermostatClimate::dump_config() {
}
ESP_LOGCONFIG(TAG, " Hysteresis: %.1f°C", this->hysteresis_);
ESP_LOGCONFIG(TAG, " Supports AUTO: %s", YESNO(this->supports_auto_));
ESP_LOGCONFIG(TAG, " Supports HEAT/COOL: %s", YESNO(this->supports_heat_cool_));
ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_));
ESP_LOGCONFIG(TAG, " Supports DRY: %s", YESNO(this->supports_dry_));
ESP_LOGCONFIG(TAG, " Supports FAN_ONLY: %s", YESNO(this->supports_fan_only_));

View File

@@ -29,6 +29,7 @@ class ThermostatClimate : public climate::Climate, public Component {
void set_hysteresis(float hysteresis);
void set_sensor(sensor::Sensor *sensor);
void set_supports_auto(bool supports_auto);
void set_supports_heat_cool(bool supports_heat_cool);
void set_supports_cool(bool supports_cool);
void set_supports_dry(bool supports_dry);
void set_supports_fan_only(bool supports_fan_only);
@@ -113,6 +114,7 @@ class ThermostatClimate : public climate::Climate, public Component {
/// A false value for any given attribute means that the controller has no such action
/// (for example a thermostat, where only heating and not-heating is possible).
bool supports_auto_{false};
bool supports_heat_cool_{false};
bool supports_cool_{false};
bool supports_dry_{false};
bool supports_fan_only_{false};

View File

@@ -80,7 +80,7 @@ void ToshibaClimate::transmit_state() {
mode = TOSHIBA_MODE_COOL;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
default:
mode = TOSHIBA_MODE_AUTO;
}
@@ -190,7 +190,7 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) {
case TOSHIBA_MODE_AUTO:
default:
/* Note: Dry and Fan-only modes are reported as Auto, as they are not supported yet */
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
}
/* Get the target temperature */

View File

@@ -13,7 +13,7 @@ void TuyaClimate::setup() {
this->mode = climate::CLIMATE_MODE_OFF;
if (datapoint.value_bool) {
if (this->supports_heat_ && this->supports_cool_) {
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
} else if (this->supports_heat_) {
this->mode = climate::CLIMATE_MODE_HEAT;
} else if (this->supports_cool_) {
@@ -68,8 +68,10 @@ void TuyaClimate::control(const climate::ClimateCall &call) {
climate::ClimateTraits TuyaClimate::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(this->current_temperature_id_.has_value());
traits.set_supports_heat_mode(this->supports_heat_);
traits.set_supports_cool_mode(this->supports_cool_);
if (supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
if (supports_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
traits.set_supports_action(true);
return traits;
}

View File

@@ -333,7 +333,7 @@ void Tuya::send_raw_command_(TuyaCommand command) {
void Tuya::process_command_queue_() {
uint32_t delay = millis() - this->last_command_timestamp_;
// Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly
if (delay > COMMAND_DELAY && !command_queue_.empty()) {
if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) {
this->send_raw_command_(command_queue_.front());
this->command_queue_.erase(command_queue_.begin());
}

View File

@@ -1,5 +1,8 @@
from typing import Optional
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome import pins, automation
from esphome.const import (
CONF_BAUD_RATE,
@@ -92,42 +95,6 @@ async def to_code(config):
cg.add(var.set_parity(config[CONF_PARITY]))
def validate_device(
name, config, item_config, baud_rate=None, require_tx=True, require_rx=True
):
if not hasattr(config, "uart_devices"):
config.uart_devices = {}
devices = config.uart_devices
uart_config = config.get_config_by_id(item_config[CONF_UART_ID])
uart_id = uart_config[CONF_ID]
device = devices.setdefault(uart_id, {})
if require_tx:
if CONF_TX_PIN not in uart_config:
raise ValueError(f"Component {name} requires parent uart to declare tx_pin")
if CONF_TX_PIN in device:
raise ValueError(
f"Component {name} cannot use the same uart.{CONF_TX_PIN} as component {device[CONF_TX_PIN]} is already using it"
)
device[CONF_TX_PIN] = name
if require_rx:
if CONF_RX_PIN not in uart_config:
raise ValueError(f"Component {name} requires parent uart to declare rx_pin")
if CONF_RX_PIN in device:
raise ValueError(
f"Component {name} cannot use the same uart.{CONF_RX_PIN} as component {device[CONF_RX_PIN]} is already using it"
)
device[CONF_RX_PIN] = name
if baud_rate and uart_config[CONF_BAUD_RATE] != baud_rate:
raise ValueError(
f"Component {name} requires parent uart baud rate be {baud_rate}"
)
# A schema to use for all UART devices, all UART integrations must extend this!
UART_DEVICE_SCHEMA = cv.Schema(
{
@@ -135,6 +102,64 @@ UART_DEVICE_SCHEMA = cv.Schema(
}
)
KEY_UART_DEVICES = "uart_devices"
def final_validate_device_schema(
name: str,
*,
baud_rate: Optional[int] = None,
require_tx: bool = False,
require_rx: bool = False,
):
def validate_baud_rate(value):
if value != baud_rate:
raise cv.Invalid(
f"Component {name} required baud rate {baud_rate} for the uart bus"
)
return value
def validate_pin(opt, device):
def validator(value):
if opt in device:
raise cv.Invalid(
f"The uart {opt} is used both by {name} and {device[opt]}, "
f"but can only be used by one. Please create a new uart bus for {name}."
)
device[opt] = name
return value
return validator
def validate_hub(hub_config):
hub_schema = {}
uart_id = hub_config[CONF_ID]
devices = fv.full_config.get().data.setdefault(KEY_UART_DEVICES, {})
device = devices.setdefault(uart_id, {})
if require_tx:
hub_schema[
cv.Required(
CONF_TX_PIN,
msg=f"Component {name} requires this uart bus to declare a tx_pin",
)
] = validate_pin(CONF_TX_PIN, device)
if require_rx:
hub_schema[
cv.Required(
CONF_RX_PIN,
msg=f"Component {name} requires this uart bus to declare a rx_pin",
)
] = validate_pin(CONF_RX_PIN, device)
if baud_rate is not None:
hub_schema[cv.Required(CONF_BAUD_RATE)] = validate_baud_rate
return cv.Schema(hub_schema, extra=cv.ALLOW_EXTRA)(hub_config)
return cv.Schema(
{cv.Required(CONF_UART_ID): fv.id_declaration_match_schema(validate_hub)},
extra=cv.ALLOW_EXTRA,
)
async def register_uart_device(var, config):
"""Register a UART device, setting up all the internal values.

View File

@@ -46,6 +46,7 @@ WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel"
MODELS = {
"1.54in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN),
"1.54inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN_V2),
"2.13in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN),
"2.13in-ttgo": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN),
"2.13in-ttgo-b1": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B1),
@@ -67,7 +68,7 @@ def validate_full_update_every_only_type_a(value):
if MODELS[value[CONF_MODEL]][0] != "a":
raise cv.Invalid(
"The 'full_update_every' option is only available for models "
"'1.54in', '2.13in', '2.90in', and '2.90inV2'."
"'1.54in', '1.54inV2', '2.13in', '2.90in', and '2.90inV2'."
)
return value

View File

@@ -210,6 +210,9 @@ void WaveshareEPaperTypeA::dump_config() {
case WAVESHARE_EPAPER_1_54_IN:
ESP_LOGCONFIG(TAG, " Model: 1.54in");
break;
case WAVESHARE_EPAPER_1_54_IN_V2:
ESP_LOGCONFIG(TAG, " Model: 1.54inV2");
break;
case WAVESHARE_EPAPER_2_13_IN:
ESP_LOGCONFIG(TAG, " Model: 2.13in");
break;
@@ -334,7 +337,7 @@ void HOT WaveshareEPaperTypeA::display() {
// COMMAND DISPLAY UPDATE CONTROL 2
this->command(0x22);
if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2) {
if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2 || this->model_ == WAVESHARE_EPAPER_1_54_IN_V2) {
this->data(full_update ? 0xF7 : 0xFF);
} else if (this->model_ == TTGO_EPAPER_2_13_IN_B73) {
this->data(0xC7);
@@ -353,6 +356,8 @@ int WaveshareEPaperTypeA::get_width_internal() {
switch (this->model_) {
case WAVESHARE_EPAPER_1_54_IN:
return 200;
case WAVESHARE_EPAPER_1_54_IN_V2:
return 200;
case WAVESHARE_EPAPER_2_13_IN:
return 128;
case TTGO_EPAPER_2_13_IN:
@@ -372,6 +377,8 @@ int WaveshareEPaperTypeA::get_height_internal() {
switch (this->model_) {
case WAVESHARE_EPAPER_1_54_IN:
return 200;
case WAVESHARE_EPAPER_1_54_IN_V2:
return 200;
case WAVESHARE_EPAPER_2_13_IN:
return 250;
case TTGO_EPAPER_2_13_IN:

View File

@@ -66,6 +66,7 @@ class WaveshareEPaper : public PollingComponent,
enum WaveshareEPaperTypeAModel {
WAVESHARE_EPAPER_1_54_IN = 0,
WAVESHARE_EPAPER_1_54_IN_V2,
WAVESHARE_EPAPER_2_13_IN,
WAVESHARE_EPAPER_2_9_IN,
WAVESHARE_EPAPER_2_9_IN_V2,
@@ -85,7 +86,7 @@ class WaveshareEPaperTypeA : public WaveshareEPaper {
void display() override;
void deep_sleep() override {
if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2) {
if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2 || this->model_ == WAVESHARE_EPAPER_1_54_IN_V2) {
// COMMAND DEEP SLEEP MODE
this->command(0x10);
this->data(0x01);

View File

@@ -48,7 +48,7 @@ void WhirlpoolClimate::transmit_state() {
this->powered_on_assumed = powered_on;
}
switch (this->mode) {
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
// set fan auto
// set temp auto temp
// set sleep false
@@ -239,7 +239,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
break;
case WHIRLPOOL_AUTO:
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
}
}

View File

@@ -1,5 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome import automation
from esphome.automation import Condition
from esphome.components.network import add_mdns_library
@@ -137,6 +138,53 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend(
)
def final_validate(config):
has_sta = bool(config.get(CONF_NETWORKS, True))
has_ap = CONF_AP in config
has_improv = "esp32_improv" in fv.full_config.get()
if (not has_sta) and (not has_ap) and (not has_improv):
raise cv.Invalid(
"Please specify at least an SSID or an Access Point to create."
)
def final_validate_power_esp32_ble(value):
if not CORE.is_esp32:
return
if value != "NONE":
# WiFi should be in modem sleep (!=NONE) with BLE coexistence
# https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-guides/wifi.html#station-sleep
return
framework_version = fv.get_arduino_framework_version()
if framework_version not in (None, "dev") and framework_version < "1.0.5":
# Only frameworks 1.0.5+ impacted
return
full = fv.full_config.get()
for conflicting in [
"esp32_ble",
"esp32_ble_beacon",
"esp32_ble_server",
"esp32_ble_tracker",
]:
if conflicting in full:
raise cv.Invalid(
f"power_save_mode NONE is incompatible with {conflicting}. "
f"Please remove the power save mode. See also "
f"https://github.com/esphome/issues/issues/2141#issuecomment-865688582"
)
FINAL_VALIDATE_SCHEMA = cv.All(
cv.Schema(
{
cv.Optional(CONF_POWER_SAVE_MODE): final_validate_power_esp32_ble,
},
extra=cv.ALLOW_EXTRA,
),
final_validate,
)
def _validate(config):
if CONF_PASSWORD in config and CONF_SSID not in config:
raise cv.Invalid("Cannot have WiFi password without SSID!")
@@ -157,9 +205,7 @@ def _validate(config):
config[CONF_NETWORKS] = cv.ensure_list(WIFI_NETWORK_STA)(network)
if (CONF_NETWORKS not in config) and (CONF_AP not in config):
raise cv.Invalid(
"Please specify at least an SSID or an Access Point " "to create."
)
config[CONF_NETWORKS] = []
if config.get(CONF_FAST_CONNECT, False):
networks = config.get(CONF_NETWORKS, [])

View File

@@ -79,7 +79,8 @@ void WiFiComponent::setup() {
}
#ifdef USE_IMPROV
if (esp32_improv::global_improv_component != nullptr)
esp32_improv::global_improv_component->start();
if (this->wifi_mode_(true, {}))
esp32_improv::global_improv_component->start();
#endif
this->wifi_apply_hostname_();
#if defined(ARDUINO_ARCH_ESP32) && defined(USE_MDNS)
@@ -143,11 +144,11 @@ void WiFiComponent::loop() {
}
#ifdef USE_IMPROV
if (esp32_improv::global_improv_component != nullptr) {
if (!this->is_connected()) {
esp32_improv::global_improv_component->start();
}
}
if (esp32_improv::global_improv_component != nullptr)
if (!this->is_connected())
if (this->wifi_mode_(true, {}))
esp32_improv::global_improv_component->start();
#endif
if (!this->has_ap() && this->reboot_timeout_ != 0) {
@@ -508,7 +509,7 @@ void WiFiComponent::check_connecting_finished() {
}
#ifdef USE_IMPROV
if (this->is_esp32_improv_active_()) {
esp32_improv::global_improv_component->end();
esp32_improv::global_improv_component->stop();
}
#endif
@@ -586,7 +587,7 @@ void WiFiComponent::retry_connect() {
}
bool WiFiComponent::can_proceed() {
if (this->has_ap() && !this->has_sta()) {
if (!this->has_sta()) {
return true;
}
return this->is_connected();

View File

@@ -82,11 +82,14 @@ const uint32_t YASHIMA_CARRIER_FREQUENCY = 38000;
climate::ClimateTraits YashimaClimate::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(this->sensor_ != nullptr);
traits.set_supports_auto_mode(true);
traits.set_supports_cool_mode(this->supports_cool_);
traits.set_supports_heat_mode(this->supports_heat_);
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
if (supports_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
if (supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
traits.set_supports_two_point_target_temperature(false);
traits.set_supports_away(false);
traits.set_visual_min_temperature(YASHIMA_TEMP_MIN);
traits.set_visual_max_temperature(YASHIMA_TEMP_MAX);
traits.set_visual_temperature_step(1);
@@ -139,7 +142,7 @@ void YashimaClimate::transmit_state_() {
// Set mode
switch (this->mode) {
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state[0] |= YASHIMA_MODE_AUTO_BYTE0;
remote_state[5] |= YASHIMA_MODE_AUTO_BYTE5;
break;

View File

@@ -21,11 +21,13 @@ from esphome.helpers import indent
from esphome.util import safe_print, OrderedDict
from typing import List, Optional, Tuple, Union
from esphome.core import ConfigType
from esphome.loader import get_component, get_platform, ComponentManifest
from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue
from esphome.voluptuous_schema import ExtraKeysInvalid
from esphome.log import color, Fore
import esphome.final_validate as fv
import esphome.config_validation as cv
from esphome.types import ConfigType, ConfigPathType, ConfigFragmentType
_LOGGER = logging.getLogger(__name__)
@@ -54,7 +56,7 @@ def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool
return path[: len(other)] == other
class Config(OrderedDict):
class Config(OrderedDict, fv.FinalValidateConfig):
def __init__(self):
super().__init__()
# A list of voluptuous errors
@@ -65,6 +67,7 @@ class Config(OrderedDict):
self.output_paths = [] # type: List[Tuple[ConfigPath, str]]
# A list of components ids with the config path
self.declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]]
self._data = {}
def add_error(self, error):
# type: (vol.Invalid) -> None
@@ -72,6 +75,12 @@ class Config(OrderedDict):
for err in error.errors:
self.add_error(err)
return
if cv.ROOT_CONFIG_PATH in error.path:
# Root value means that the path before the root should be ignored
last_root = max(
i for i, v in enumerate(error.path) if v is cv.ROOT_CONFIG_PATH
)
error.path = error.path[last_root + 1 :]
self.errors.append(error)
@contextmanager
@@ -140,13 +149,16 @@ class Config(OrderedDict):
return doc_range
def get_nested_item(self, path):
# type: (ConfigPath) -> ConfigType
def get_nested_item(
self, path: ConfigPathType, raise_error: bool = False
) -> ConfigFragmentType:
data = self
for item_index in path:
try:
data = data[item_index]
except (KeyError, IndexError, TypeError):
if raise_error:
raise
return {}
return data
@@ -163,11 +175,20 @@ class Config(OrderedDict):
part.append(item_index)
return part
def get_config_by_id(self, id):
def get_path_for_id(self, id: core.ID):
"""Return the config fragment where the given ID is declared."""
for declared_id, path in self.declare_ids:
if declared_id.id == str(id):
return self.get_nested_item(path[:-1])
return None
return path
raise KeyError(f"ID {id} not found in configuration")
def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType:
return self.get_nested_item(path, raise_error=True)
@property
def data(self):
"""Return temporary data used by final validation functions."""
return self._data
def iter_ids(config, path=None):
@@ -189,23 +210,22 @@ def do_id_pass(result): # type: (Config) -> None
from esphome.cpp_generator import MockObjClass
from esphome.cpp_types import Component
declare_ids = result.declare_ids # type: List[Tuple[core.ID, ConfigPath]]
searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]]
for id, path in iter_ids(result):
if id.is_declaration:
if id.id is not None:
# Look for duplicate definitions
match = next((v for v in declare_ids if v[0].id == id.id), None)
match = next((v for v in result.declare_ids if v[0].id == id.id), None)
if match is not None:
opath = "->".join(str(v) for v in match[1])
result.add_str_error(f"ID {id.id} redefined! Check {opath}", path)
continue
declare_ids.append((id, path))
result.declare_ids.append((id, path))
else:
searching_ids.append((id, path))
# Resolve default ids after manual IDs
for id, _ in declare_ids:
id.resolve([v[0].id for v in declare_ids])
for id, _ in result.declare_ids:
id.resolve([v[0].id for v in result.declare_ids])
if isinstance(id.type, MockObjClass) and id.type.inherits_from(Component):
CORE.component_ids.add(id.id)
@@ -213,7 +233,7 @@ def do_id_pass(result): # type: (Config) -> None
for id, path in searching_ids:
if id.id is not None:
# manually declared
match = next((v[0] for v in declare_ids if v[0].id == id.id), None)
match = next((v[0] for v in result.declare_ids if v[0].id == id.id), None)
if match is None or not match.is_manual:
# No declared ID with this name
import difflib
@@ -224,7 +244,7 @@ def do_id_pass(result): # type: (Config) -> None
)
# Find candidates
matches = difflib.get_close_matches(
id.id, [v[0].id for v in declare_ids if v[0].is_manual]
id.id, [v[0].id for v in result.declare_ids if v[0].is_manual]
)
if matches:
matches_s = ", ".join(f'"{x}"' for x in matches)
@@ -245,7 +265,7 @@ def do_id_pass(result): # type: (Config) -> None
if id.id is None and id.type is not None:
matches = []
for v in declare_ids:
for v in result.declare_ids:
if v[0] is None or not isinstance(v[0].type, MockObjClass):
continue
inherits = v[0].type.inherits_from(id.type)
@@ -278,8 +298,6 @@ def do_id_pass(result): # type: (Config) -> None
def recursive_check_replaceme(value):
import esphome.config_validation as cv
if isinstance(value, list):
return cv.Schema([recursive_check_replaceme])(value)
if isinstance(value, dict):
@@ -558,14 +576,16 @@ def validate_config(config, command_line_substitutions):
# 7. Final validation
if not result.errors:
# Inter - components validation
for path, conf, comp in validate_queue:
if comp.config_schema is None:
token = fv.full_config.set(result)
for path, _, comp in validate_queue:
if comp.final_validate_schema is None:
continue
if callable(comp.validate):
try:
comp.validate(result, result.get_nested_item(path))
except ValueError as err:
result.add_str_error(err, path)
conf = result.get_nested_item(path)
with result.catch_error(path):
comp.final_validate_schema(conf)
fv.full_config.reset(token)
return result
@@ -621,8 +641,12 @@ def _format_vol_invalid(ex, config):
)
elif "extra keys not allowed" in str(ex):
message += "[{}] is an invalid option for [{}].".format(ex.path[-1], paren)
elif "required key not provided" in str(ex):
message += "'{}' is a required option for [{}].".format(ex.path[-1], paren)
elif isinstance(ex, vol.RequiredFieldInvalid):
if ex.msg == "required key not provided":
message += "'{}' is a required option for [{}].".format(ex.path[-1], paren)
else:
# Required has set a custom error message
message += ex.msg
else:
message += humanize_error(config, ex)

View File

@@ -75,6 +75,9 @@ Inclusive = vol.Inclusive
ALLOW_EXTRA = vol.ALLOW_EXTRA
UNDEFINED = vol.UNDEFINED
RequiredFieldInvalid = vol.RequiredFieldInvalid
# this sentinel object can be placed in an 'Invalid' path to say
# the rest of the error path is relative to the root config path
ROOT_CONFIG_PATH = object()
RESERVED_IDS = [
# C++ keywords http://en.cppreference.com/w/cpp/keyword
@@ -218,8 +221,8 @@ class Required(vol.Required):
- *not* the `config.get(CONF_<KEY>)` syntax.
"""
def __init__(self, key):
super().__init__(key)
def __init__(self, key, msg=None):
super().__init__(key, msg=msg)
def check_not_templatable(value):
@@ -1073,6 +1076,7 @@ def invalid(message):
def valid(value):
"""A validator that is always valid and returns the value as-is."""
return value

View File

@@ -2,7 +2,7 @@
MAJOR_VERSION = 1
MINOR_VERSION = 19
PATCH_VERSION = "0b1"
PATCH_VERSION = "4"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"

View File

@@ -2,7 +2,7 @@ import logging
import math
import os
import re
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
from esphome.const import (
CONF_ARDUINO_VERSION,
@@ -23,6 +23,7 @@ from esphome.util import OrderedDict
if TYPE_CHECKING:
from ..cpp_generator import MockObj, MockObjClass, Statement
from ..types import ConfigType
_LOGGER = logging.getLogger(__name__)
@@ -462,9 +463,9 @@ class EsphomeCore:
# The board that's used (for example nodemcuv2)
self.board: Optional[str] = None
# The full raw configuration
self.raw_config: Optional[ConfigType] = None
self.raw_config: Optional["ConfigType"] = None
# The validated configuration, this is None until the config has been validated
self.config: Optional[ConfigType] = None
self.config: Optional["ConfigType"] = None
# The pending tasks in the task queue (mostly for C++ generation)
# This is a priority queue (with heapq)
# Each item is a tuple of form: (-priority, unique number, task)
@@ -752,6 +753,3 @@ class EnumValue:
CORE = EsphomeCore()
ConfigType = Dict[str, Any]
CoreType = EsphomeCore

View File

@@ -38,6 +38,7 @@ namespace esphome {
class Application {
public:
void pre_setup(const std::string &name, const char *compilation_time, bool name_add_mac_suffix) {
this->name_add_mac_suffix_ = name_add_mac_suffix;
if (name_add_mac_suffix) {
this->name_ = name + "-" + get_mac_address().substr(6);
} else {
@@ -97,6 +98,8 @@ class Application {
/// Get the name of this Application set by set_name().
const std::string &get_name() const { return this->name_; }
bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; }
const std::string &get_compilation_time() const { return this->compilation_time_; }
/** Set the target interval with which to run the loop() calls.
@@ -245,6 +248,7 @@ class Application {
std::string name_;
std::string compilation_time_;
bool name_add_mac_suffix_;
uint32_t last_loop_{0};
uint32_t loop_interval_{16};
int dump_config_at_{-1};

View File

@@ -19,6 +19,7 @@
#define USE_JSON
#ifdef ARDUINO_ARCH_ESP32
#define USE_ESP32_CAMERA
#define USE_ESP32_BLE_SERVER
#define USE_IMPROV
#endif
#define USE_TIME

View File

@@ -8,7 +8,8 @@ from esphome.const import (
)
# pylint: disable=unused-import
from esphome.core import coroutine, ID, CORE, ConfigType
from esphome.core import coroutine, ID, CORE
from esphome.types import ConfigType
from esphome.cpp_generator import RawExpression, add, get_variable
from esphome.cpp_types import App, GPIOPin
from esphome.util import Registry, RegistryEntry

View File

@@ -9,6 +9,7 @@ import json
import logging
import multiprocessing
import os
import secrets
import shutil
import subprocess
import threading
@@ -44,6 +45,8 @@ from esphome.zeroconf import DashboardStatus, Zeroconf
_LOGGER = logging.getLogger(__name__)
ENV_DEV = "ESPHOME_DASHBOARD_DEV"
class DashboardSettings:
def __init__(self):
@@ -111,6 +114,7 @@ def template_args():
docs_link = "https://next.esphome.io/"
else:
docs_link = "https://www.esphome.io/"
return {
"version": version,
"docs_link": docs_link,
@@ -349,6 +353,7 @@ class SerialPortRequestHandler(BaseHandler):
data.append({"port": port.path, "desc": desc})
data.append({"port": "OTA", "desc": "Over-The-Air"})
data.sort(key=lambda x: x["port"], reverse=True)
self.set_header("content-type", "application/json")
self.write(json.dumps(data))
@@ -358,11 +363,15 @@ class WizardRequestHandler(BaseHandler):
from esphome import wizard
kwargs = {
k: "".join(x.decode() for x in v) for k, v in self.request.arguments.items()
k: "".join(x.decode() for x in v)
for k, v in self.request.arguments.items()
if k in ("name", "platform", "board", "ssid", "psk", "password")
}
kwargs["ota_password"] = secrets.token_hex(16)
destination = settings.rel_path(kwargs["name"] + ".yaml")
wizard.wizard_write(path=destination, **kwargs)
self.redirect("./?begin=True")
self.set_status(200)
self.finish()
class DownloadBinaryRequestHandler(BaseHandler):
@@ -473,7 +482,7 @@ class MainRequestHandler(BaseHandler):
entries = _list_dashboard_entries()
self.render(
"templates/index.html",
get_template_path("index"),
entries=entries,
begin=begin,
**template_args(),
@@ -560,6 +569,7 @@ class PingRequestHandler(BaseHandler):
@authenticated
def get(self):
PING_REQUEST.set()
self.set_header("content-type", "application/json")
self.write(json.dumps(PING_RESULT))
@@ -567,6 +577,21 @@ def is_allowed(configuration):
return os.path.sep not in configuration
class InfoRequestHandler(BaseHandler):
@authenticated
@bind_config
def get(self, configuration=None):
yaml_path = settings.rel_path(configuration)
all_yaml_files = settings.list_yaml_files()
if yaml_path not in all_yaml_files:
self.set_status(404)
return
self.set_header("content-type", "application/json")
self.write(DashboardEntry(yaml_path).storage.to_json())
class EditRequestHandler(BaseHandler):
@authenticated
@bind_config
@@ -633,7 +658,7 @@ class LoginHandler(BaseHandler):
def render_login_page(self, error=None):
self.render(
"templates/login.html",
get_template_path("login"),
error=error,
hassio=settings.using_hassio_auth,
has_username=bool(settings.username),
@@ -691,22 +716,48 @@ class LogoutHandler(BaseHandler):
self.redirect("./login")
_STATIC_FILE_HASHES = {}
def get_base_frontend_path():
if ENV_DEV not in os.environ:
import esphome_dashboard
return esphome_dashboard.where()
static_path = os.environ[ENV_DEV]
if not static_path.endswith("/"):
static_path += "/"
# This path can be relative, so resolve against the root or else templates don't work
return os.path.abspath(os.path.join(os.getcwd(), static_path, "esphome_dashboard"))
def get_template_path(template_name):
return os.path.join(get_base_frontend_path(), f"{template_name}.template.html")
def get_static_path(*args):
return os.path.join(get_base_frontend_path(), "static", *args)
@functools.lru_cache(maxsize=None)
def get_static_file_url(name):
static_path = os.path.join(os.path.dirname(__file__), "static")
if name in _STATIC_FILE_HASHES:
hash_ = _STATIC_FILE_HASHES[name]
else:
path = os.path.join(static_path, name)
with open(path, "rb") as f_handle:
hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8]
_STATIC_FILE_HASHES[name] = hash_
return f"./static/{name}?hash={hash_}"
base = f"./static/{name}"
if ENV_DEV in os.environ:
return base
# Module imports can't deduplicate if stuff added to url
if name == "js/esphome/index.js":
import esphome_dashboard
return base.replace("index.js", esphome_dashboard.entrypoint())
path = get_static_path(name)
with open(path, "rb") as f_handle:
hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8]
return f"{base}?hash={hash_}"
def make_app(debug=False):
def make_app(debug=get_bool_env(ENV_DEV)):
def log_function(handler):
if handler.get_status() < 400:
log_method = access_log.info
@@ -736,7 +787,6 @@ def make_app(debug=False):
"Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"
)
static_path = os.path.join(os.path.dirname(__file__), "static")
app_settings = {
"debug": debug,
"cookie_secret": settings.cookie_secret,
@@ -758,6 +808,7 @@ def make_app(debug=False):
(rel + "vscode", EsphomeVscodeHandler),
(rel + "ace", EsphomeAceEditorHandler),
(rel + "update-all", EsphomeUpdateAllHandler),
(rel + "info", InfoRequestHandler),
(rel + "edit", EditRequestHandler),
(rel + "download.bin", DownloadBinaryRequestHandler),
(rel + "serial-ports", SerialPortRequestHandler),
@@ -765,14 +816,11 @@ def make_app(debug=False):
(rel + "delete", DeleteRequestHandler),
(rel + "undo-delete", UndoDeleteRequestHandler),
(rel + "wizard.html", WizardRequestHandler),
(rel + r"static/(.*)", StaticFileHandler, {"path": static_path}),
(rel + r"static/(.*)", StaticFileHandler, {"path": get_static_path()}),
],
**app_settings,
)
if debug:
_STATIC_FILE_HASHES.clear()
return app

View File

@@ -1,398 +0,0 @@
/* Base */
:root {
/* Colors */
--primary-bg-color: #fafafa;
--alert-standard-color: #666666;
--alert-standard-color-bg: #e6e6e6;
--alert-info-color: #00539f;
--alert-info-color-bg: #E6EEF5;
--alert-success-color: #4CAF50;
--alert-success-color-bg: #EDF7EE;
--alert-warning-color: #FF9800;
--alert-warning-color-bg: #FFF5E6;
--alert-error-color: #D93025;
--alert-error-color-bg: #FAEFEB;
}
body {
display: flex;
min-height: 100vh;
flex-direction: column;
background-color: var(--primary-bg-color);
}
/* Layout */
.valign-wrapper {
position: absolute;
width:100vw;
height:100vh;
}
.valign {
width: 100%;
}
main {
flex: 1 0 auto;
}
/* Alerts & Errors */
.alert {
width: 100%;
margin: 10px auto;
padding: 10px;
border-radius: 2px;
border-left-width: 4px;
border-left-style: solid;
}
.alert .title {
font-weight: bold;
}
.alert .title::after {
content: "\A";
white-space: pre;
}
.alert.alert-error {
color: var(--alert-error-color);
border-left-color: var(--alert-error-color);
background-color: var(--alert-error-color-bg);
}
.card.card-error, .card.status-offline {
border-top: 4px solid var(--alert-error-color);
}
.card.status-online {
border-top: 4px solid var(--alert-success-color);
}
.card.status-not-responding {
border-top: 4px solid var(--alert-warning-color);
}
.card.status-unknown {
border-top: 4px solid var(--alert-standard-color);
}
/* Login Page */
#login-page .row.no-bottom-margin {
margin-bottom: 0 !important;
}
#login-page .logo {
display: block;
width: auto;
max-width: 300px;
margin-left: auto;
margin-right: auto;
}
#login-page .input-field input:focus + label {
color: #000;
}
#login-page .input-field input:focus {
border-bottom: 1px solid #000;
box-shadow: 0 1px 0 0 #000;
}
#login-page .input-field .prefix.active {
color: #000;
}
#login-page .version-number {
display: block;
text-align: center;
margin-bottom: 20px;;
color:#808080;
font-size: 12px;
}
#login-page footer {
color: #757575;
font-size: 12px;
}
#login-page footer a {
color: #424242;
}
#login-page footer p {
-webkit-margin-before: 0px;
margin-block-start: 0px;
-webkit-margin-after: 5px;
margin-block-end: 5px;
}
#login-page footer p:last-child {
-webkit-margin-after: 0px;
margin-block-end: 0px;
}
/* Dashboard */
.logo-wrapper {
height: 64px;
height: 100%;
width: 0;
margin-left: 24px;
}
.logo {
width: auto;
height: 48px;
margin: 8px 0;
}
@media only screen and (max-width: 601px) {
.logo {
height: 38px;
margin: 9px 0;
}
}
.nav-icons {
margin-right: 24px;
}
.nav-icons i {
color: black;
}
nav {
height: auto;
line-height: normal;
}
.select-port-container {
margin-top: 8px;
margin-right: 10px;
width: 350px;
}
.serial-port-select {
margin-top: 8px;
margin-right: 10px;
width: 350px;
}
.serial-port-select .select-dropdown {
color: black;
}
.serial-port-select .select-dropdown:focus {
border-bottom: 1px solid #607d8b !important;
}
.serial-port-select .caret {
fill: black;
}
.serial-port-select .dropdown-content li>span {
color: black;
}
#nav-dropdown li a, .node-dropdown li a {
color: black;
}
main .container {
margin-top: 20px;
margin-bottom: 20px;
width: 90%;
max-width: 1920px;
}
#nodes .card-content {
height: calc(100% - 47px);
}
#nodes .card-content, #nodes .card-action {
padding: 12px;
}
#nodes .grid-1-col {
display: grid;
grid-template-columns: 1fr;
}
#nodes .grid-2-col {
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 1.5rem;
}
#nodes .grid-3-col {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-column-gap: 1.5rem;
}
@media only screen and (max-width: 1100px) {
#nodes .grid-3-col {
grid-template-columns: 1fr 1fr;
grid-column-gap: 1.5rem;
}
}
@media only screen and (max-width: 750px) {
#nodes .grid-2-col {
grid-template-columns: 1fr;
grid-column-gap: 0;
}
#nodes .grid-3-col {
grid-template-columns: 1fr;
grid-column-gap: 0;
}
}
i.node-update-avaliable {
color:#3f51b5;
}
i.node-webserver {
color:#039be5;
}
.node-config-path {
margin-top: -8px;
margin-bottom: 8px;
font-size: 14px;
}
.node-card-comment {
color: #444;
font-style: italic;
}
.card-action a, .card-dropdown-action a {
cursor: pointer;
}
.tooltipped {
cursor: help;
}
#js-loading-indicator {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.editor {
margin-top: 0;
margin-bottom: 0;
border-radius: 3px;
height: calc(100% - 56px);
}
.inlinecode {
box-sizing: border-box;
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
background-color: rgba(27,31,35,0.05);
border-radius: 3px;
font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
}
.log {
height: 100%;
max-height: calc(100% - 56px);
background-color: #1c1c1c;
margin-top: 0;
margin-bottom: 0;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px;
padding: 16px;
overflow: auto;
line-height: 1.45;
border-radius: 3px;
white-space: pre-wrap;
overflow-wrap: break-word;
color: #DDD;
}
.log-bold { font-weight: bold; }
.log-italic { font-style: italic; }
.log-underline { text-decoration: underline; }
.log-strikethrough { text-decoration: line-through; }
.log-underline.log-strikethrough { text-decoration: underline line-through; }
.log-secret {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.log-secret-redacted {
opacity: 0;
width: 1px;
font-size: 1px;
}
.log-fg-black { color: rgb(128,128,128); }
.log-fg-red { color: rgb(255,0,0); }
.log-fg-green { color: rgb(0,255,0); }
.log-fg-yellow { color: rgb(255,255,0); }
.log-fg-blue { color: rgb(0,0,255); }
.log-fg-magenta { color: rgb(255,0,255); }
.log-fg-cyan { color: rgb(0,255,255); }
.log-fg-white { color: rgb(187,187,187); }
.log-bg-black { background-color: rgb(0,0,0); }
.log-bg-red { background-color: rgb(255,0,0); }
.log-bg-green { background-color: rgb(0,255,0); }
.log-bg-yellow { background-color: rgb(255,255,0); }
.log-bg-blue { background-color: rgb(0,0,255); }
.log-bg-magenta { background-color: rgb(255,0,255); }
.log-bg-cyan { background-color: rgb(0,255,255); }
.log-bg-white { background-color: rgb(255,255,255); }
ul.browser-default {
padding-left: 30px;
margin-top: 10px;
margin-bottom: 15px;
}
ul.browser-default li {
list-style-type: initial;
}
ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .step.done::before, ul.stepper.horizontal .step.active .step-title::before, ul.stepper.horizontal .step.done .step-title::before {
background-color: #3f51b5 !important;
}
.select-action {
width: auto !important;
height: auto !important;
white-space: nowrap;
}
.modal {
width: 95%;
max-height: 90%;
height: 85% !important;
}
.page-footer {
display: flex;
align-items: center;
min-height: 50px;
padding-top: 0;
color: grey;
}
.page-footer a {
color: #afafaf;
}
@media only screen and (max-width: 992px) {
.page-footer .left, .page-footer .right {
width: 100%;
text-align: center;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,12 +0,0 @@
The recommended way to use the Material Icons font is by linking to the web font hosted on Google Fonts:
```html
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
```
Read more in our full usage guide:
http://google.github.io/material-design-icons/#icon-font-for-the-web
Source:
https://github.com/google/material-design-icons

View File

@@ -1,34 +0,0 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(MaterialIcons-Regular.woff2) format('woff2'),
url(MaterialIcons-Regular.woff) format('woff');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +0,0 @@
define("ace/theme/dreamweaver", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { t.isDark = !1, t.cssClass = "ace-dreamweaver", t.cssText = '.ace-dreamweaver .ace_gutter {background: #e8e8e8;color: #333;}.ace-dreamweaver .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-dreamweaver {background-color: #FFFFFF;color: black;}.ace-dreamweaver .ace_fold {background-color: #757AD8;}.ace-dreamweaver .ace_cursor {color: black;}.ace-dreamweaver .ace_invisible {color: rgb(191, 191, 191);}.ace-dreamweaver .ace_storage,.ace-dreamweaver .ace_keyword {color: blue;}.ace-dreamweaver .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-dreamweaver .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-dreamweaver .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-dreamweaver .ace_invalid {background-color: rgb(153, 0, 0);color: white;}.ace-dreamweaver .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-dreamweaver .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-dreamweaver .ace_support.ace_type,.ace-dreamweaver .ace_support.ace_class {color: #009;}.ace-dreamweaver .ace_support.ace_php_tag {color: #f00;}.ace-dreamweaver .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-dreamweaver .ace_string {color: #00F;}.ace-dreamweaver .ace_comment {color: rgb(76, 136, 107);}.ace-dreamweaver .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-dreamweaver .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-dreamweaver .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-dreamweaver .ace_variable {color: #06F}.ace-dreamweaver .ace_xml-pe {color: rgb(104, 104, 91);}.ace-dreamweaver .ace_entity.ace_name.ace_function {color: #00F;}.ace-dreamweaver .ace_heading {color: rgb(12, 7, 255);}.ace-dreamweaver .ace_list {color:rgb(185, 6, 144);}.ace-dreamweaver .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-dreamweaver .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-dreamweaver .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-dreamweaver .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-dreamweaver .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-dreamweaver .ace_gutter-active-line {background-color : #DCDCDC;}.ace-dreamweaver .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-dreamweaver .ace_meta.ace_tag {color:#009;}.ace-dreamweaver .ace_meta.ace_tag.ace_anchor {color:#060;}.ace-dreamweaver .ace_meta.ace_tag.ace_form {color:#F90;}.ace-dreamweaver .ace_meta.ace_tag.ace_image {color:#909;}.ace-dreamweaver .ace_meta.ace_tag.ace_script {color:#900;}.ace-dreamweaver .ace_meta.ace_tag.ace_style {color:#909;}.ace-dreamweaver .ace_meta.ace_tag.ace_table {color:#099;}.ace-dreamweaver .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-dreamweaver .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}'; var r = e("../lib/dom"); r.importCssString(t.cssText, t.cssClass) }); (function () {
window.require(["ace/theme/dreamweaver"], function (m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More