diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..52ac3648b0 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,8 @@ +# These are supported funding model platforms + +github: +patreon: ottowinter +open_collective: +ko_fi: +tidelift: +custom: https://esphome.io/guides/supporters.html diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 94116bcee1..3278827486 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,6 +3,8 @@ variables: DOCKER_DRIVER: overlay2 DOCKER_HOST: tcp://docker:2375/ + BASE_VERSION: '1.8.3' + TZ: UTC stages: - lint @@ -10,23 +12,20 @@ stages: - deploy .lint: &lint - image: esphome/esphome-base-amd64 + image: esphome/esphome-lint:latest stage: lint before_script: - - pip install -e . - - pip install flake8==3.6.0 pylint==1.9.4 pillow + - script/setup tags: - docker .test: &test - image: esphome/esphome-base-amd64 + image: esphome/esphome-lint:latest stage: test before_script: - - pip install -e . + - script/setup tags: - docker - variables: - TZ: UTC .docker-base: &docker-base image: esphome/esphome-base-builder @@ -41,11 +40,11 @@ stages: - | if [[ "${IS_HASSIO}" == "YES" ]]; then - BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.5.1 + BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:${BASE_VERSION} BUILD_TO=esphome/esphome-hassio-${BUILD_ARCH} DOCKERFILE=docker/Dockerfile.hassio else - BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.5.1 + BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:${BASE_VERSION} if [[ "${BUILD_ARCH}" == "amd64" ]]; then BUILD_TO=esphome/esphome else @@ -94,15 +93,32 @@ stages: - docker stage: deploy -flake8: +lint-custom: <<: *lint script: - - flake8 esphome + - script/ci-custom.py -pylint: +lint-python: <<: *lint script: - - pylint esphome + - script/lint-python + +lint-tidy: + <<: *lint + script: + - pio init --ide atom + - | + if ! patch -R -p0 -s -f --dry-run ")); + stream->print(F("")); + stream->print(F("

WiFi Networks

")); + + if (request->hasArg("save")) { + stream->print(F("
The ESP will now try to connect to the network...
Please give it some " + "time to connect.
Note: Copy the changed network to your YAML file - the next OTA update will " + "overwrite these settings.
")); + } + + for (auto &scan : wifi::global_wifi_component->get_scan_result()) { + if (scan.get_is_hidden()) + continue; + + stream->print(F("
")); + + if (scan.get_rssi() >= -50) { + stream->print(F("")); + } else if (scan.get_rssi() >= -65) { + stream->print(F("")); + } else if (scan.get_rssi() >= -85) { + stream->print(F("")); + } else { + stream->print(F("")); + } + + stream->print(F("")); + stream->print(scan.get_ssid().c_str()); + stream->print(F("")); + if (scan.get_with_auth()) { + stream->print(F("")); + } + stream->print(F("
")); + } + + stream->print(F("

WiFi Settings







")); + stream->print(F("

OTA Update

")); + stream->print(F("
")); + request->send(stream); +} +void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { + std::string ssid = request->arg("ssid").c_str(); + std::string psk = request->arg("psk").c_str(); + ESP_LOGI(TAG, "Captive Portal Requested WiFi Settings Change:"); + ESP_LOGI(TAG, " SSID='%s'", ssid.c_str()); + ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str()); + this->override_sta_(ssid, psk); + request->redirect("/?save=true"); +} +void CaptivePortal::override_sta_(const std::string &ssid, const std::string &password) { + CaptivePortalSettings save{}; + strcpy(save.ssid, ssid.c_str()); + strcpy(save.password, password.c_str()); + this->pref_.save(&save); + + wifi::WiFiAP sta{}; + sta.set_ssid(ssid); + sta.set_password(password); + wifi::global_wifi_component->set_sta(sta); +} + +void CaptivePortal::setup() { + // Hash with compilation time + // This ensures the AP override is not applied for OTA + uint32_t hash = fnv1_hash(App.get_compilation_time()); + this->pref_ = global_preferences.make_preference(hash, true); + + CaptivePortalSettings save{}; + if (this->pref_.load(&save)) { + this->override_sta_(save.ssid, save.password); + } +} +void CaptivePortal::start() { + this->base_->init(); + if (!this->initialized_) { + this->base_->add_handler(this); + this->base_->add_ota_handler(); + } + + this->dns_server_ = new DNSServer(); + this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); + IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); + this->dns_server_->start(53, "*", ip); + + this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { + bool not_found = false; + if (!this->active_) { + not_found = true; + } else if (req->host() == wifi::global_wifi_component->wifi_soft_ap_ip().toString()) { + not_found = true; + } + + if (not_found) { + req->send(404, "text/html", "File not found"); + return; + } + + auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().toString(); + req->redirect(url); + }); + + this->initialized_ = true; + this->active_ = true; +} + +const char STYLESHEET_CSS[] PROGMEM = + R"(*{box-sizing:inherit}div,input{padding:5px;font-size:1em}input{width:95%}body{text-align:center;font-family:sans-serif}button{border:0;border-radius:.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;padding:0}.main{text-align:left;display:inline-block;min-width:260px}.network{display:flex;justify-content:space-between;align-items:center}.network-left{display:flex;align-items:center}.network-ssid{margin-bottom:-7px;margin-left:10px}.info{border:1px solid;margin:10px 0;padding:15px 10px;color:#4f8a10;background-color:#dff2bf})"; +const char LOCK_SVG[] PROGMEM = + R"()"; + +void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { + if (req->url() == "/") { + this->handle_index(req); + return; + } else if (req->url() == "/wifisave") { + this->handle_wifisave(req); + return; + } else if (req->url() == "/stylesheet.css") { + req->send_P(200, "text/css", STYLESHEET_CSS); + return; + } else if (req->url() == "/lock.svg") { + req->send_P(200, "image/svg+xml", LOCK_SVG); + return; + } + + AsyncResponseStream *stream = req->beginResponseStream("image/svg+xml"); + stream->print(F("url() == "/wifi-strength-4.svg") { + stream->print(F("3z")); + } else { + if (req->url() == "/wifi-strength-1.svg") { + stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36a8.43 8.43 0 0 0-7.22-.01L3.27 7.4")); + } else if (req->url() == "/wifi-strength-2.svg") { + stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-3.21 3.98a11.32 11.32 0 0 0-11 0L3.27 7.4")); + } else if (req->url() == "/wifi-strength-3.svg") { + stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-1.94 2.43A13.6 13.6 0 0 0 12 8C9 8 6.68 9 5.21 9.84l-1.94-2.")); + } + stream->print(F("4A16.94 16.94 0 0 1 12 5z")); + } + stream->print(F("\"/>")); + req->send(stream); +} +CaptivePortal::CaptivePortal(web_server_base::WebServerBase *base) : base_(base) { global_captive_portal = this; } +float CaptivePortal::get_setup_priority() const { + // Before WiFi + return setup_priority::WIFI + 1.0f; +} + +CaptivePortal *global_captive_portal = nullptr; + +} // namespace captive_portal +} // namespace esphome diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h new file mode 100644 index 0000000000..4b1717d157 --- /dev/null +++ b/esphome/components/captive_portal/captive_portal.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include "esphome/core/component.h" +#include "esphome/core/preferences.h" +#include "esphome/components/web_server_base/web_server_base.h" + +namespace esphome { + +namespace captive_portal { + +struct CaptivePortalSettings { + char ssid[33]; + char password[65]; +} PACKED; // NOLINT + +class CaptivePortal : public AsyncWebHandler, public Component { + public: + CaptivePortal(web_server_base::WebServerBase *base); + void setup() override; + void loop() override { + if (this->dns_server_ != nullptr) + this->dns_server_->processNextRequest(); + } + float get_setup_priority() const override; + void start(); + bool is_active() const { return this->active_; } + void end() { + this->active_ = false; + this->base_->deinit(); + this->dns_server_->stop(); + delete this->dns_server_; + } + + bool canHandle(AsyncWebServerRequest *request) override { + if (!this->active_) + return false; + + if (request->method() == HTTP_GET) { + if (request->url() == "/") + return true; + if (request->url() == "/stylesheet.css") + return true; + if (request->url() == "/wifi-strength-1.svg") + return true; + if (request->url() == "/wifi-strength-2.svg") + return true; + if (request->url() == "/wifi-strength-3.svg") + return true; + if (request->url() == "/wifi-strength-4.svg") + return true; + if (request->url() == "/lock.svg") + return true; + if (request->url() == "/wifisave") + return true; + } + + return false; + } + + void handle_index(AsyncWebServerRequest *request); + + void handle_wifisave(AsyncWebServerRequest *request); + + void handleRequest(AsyncWebServerRequest *req) override; + + protected: + void override_sta_(const std::string &ssid, const std::string &password); + + web_server_base::WebServerBase *base_; + bool initialized_{false}; + bool active_{false}; + ESPPreferenceObject pref_; + DNSServer *dns_server_{nullptr}; +}; + +extern CaptivePortal *global_captive_portal; + +} // namespace captive_portal +} // namespace esphome diff --git a/esphome/components/captive_portal/index.html b/esphome/components/captive_portal/index.html new file mode 100644 index 0000000000..627bf81215 --- /dev/null +++ b/esphome/components/captive_portal/index.html @@ -0,0 +1,55 @@ + + + + + + + {{ App.get_name() }} + + + + +
+

WiFi Networks

+
+ The ESP will now try to connect to the network...
+ Please give it some time to connect.
+ Note: Copy the changed network to your YAML file - the next OTA update will overwrite these settings. +
+ + + +

WiFi Settings

+
+
+
+
+ +
+

+
+ +

OTA Update

+
+ + +
+
+ + diff --git a/esphome/components/captive_portal/lock.svg b/esphome/components/captive_portal/lock.svg new file mode 100644 index 0000000000..743a1cc55a --- /dev/null +++ b/esphome/components/captive_portal/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/esphome/components/captive_portal/stylesheet.css b/esphome/components/captive_portal/stylesheet.css new file mode 100644 index 0000000000..73f82f05f1 --- /dev/null +++ b/esphome/components/captive_portal/stylesheet.css @@ -0,0 +1,58 @@ +* { + box-sizing: inherit; +} + +div, input { + padding: 5px; + font-size: 1em; +} + +input { + width: 95%; +} + +body { + text-align: center; + font-family: sans-serif; +} + +button { + border: 0; + border-radius: 0.3rem; + background-color: #1fa3ec; + color: #fff; + line-height: 2.4rem; + font-size: 1.2rem; + width: 100%; + padding: 0; +} + +.main { + text-align: left; + display: inline-block; + min-width: 260px; +} + +.network { + display: flex; + justify-content: space-between; + align-items: center; +} + +.network-left { + display: flex; + align-items: center; +} + +.network-ssid { + margin-bottom: -7px; + margin-left: 10px; +} + +.info { + border: 1px solid; + margin: 10px 0px; + padding: 15px 10px; + color: #4f8a10; + background-color: #dff2bf; +} diff --git a/esphome/components/captive_portal/wifi-strength-1.svg b/esphome/components/captive_portal/wifi-strength-1.svg new file mode 100644 index 0000000000..189a38193c --- /dev/null +++ b/esphome/components/captive_portal/wifi-strength-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/esphome/components/captive_portal/wifi-strength-2.svg b/esphome/components/captive_portal/wifi-strength-2.svg new file mode 100644 index 0000000000..9b4b2d2396 --- /dev/null +++ b/esphome/components/captive_portal/wifi-strength-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/esphome/components/captive_portal/wifi-strength-3.svg b/esphome/components/captive_portal/wifi-strength-3.svg new file mode 100644 index 0000000000..44b7532bb7 --- /dev/null +++ b/esphome/components/captive_portal/wifi-strength-3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/esphome/components/captive_portal/wifi-strength-4.svg b/esphome/components/captive_portal/wifi-strength-4.svg new file mode 100644 index 0000000000..a22b0b8281 --- /dev/null +++ b/esphome/components/captive_portal/wifi-strength-4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/esphome/components/coolix/__init__.py b/esphome/components/coolix/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/coolix/climate.py b/esphome/components/coolix/climate.py new file mode 100644 index 0000000000..750a97d087 --- /dev/null +++ b/esphome/components/coolix/climate.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate, remote_transmitter, sensor +from esphome.const import CONF_ID, CONF_SENSOR + +AUTO_LOAD = ['sensor'] + +coolix_ns = cg.esphome_ns.namespace('coolix') +CoolixClimate = coolix_ns.class_('CoolixClimate', climate.Climate, cg.Component) + +CONF_TRANSMITTER_ID = 'transmitter_id' +CONF_SUPPORTS_HEAT = 'supports_heat' +CONF_SUPPORTS_COOL = 'supports_cool' + +CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(CoolixClimate), + cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent), + cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, + cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, + cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), +}).extend(cv.COMPONENT_SCHEMA)) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield climate.register_climate(var, config) + + cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL])) + cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT])) + if CONF_SENSOR in config: + sens = yield cg.get_variable(config[CONF_SENSOR]) + cg.add(var.set_sensor(sens)) + + transmitter = yield cg.get_variable(config[CONF_TRANSMITTER_ID]) + cg.add(var.set_transmitter(transmitter)) diff --git a/esphome/components/coolix/coolix.cpp b/esphome/components/coolix/coolix.cpp new file mode 100644 index 0000000000..ffc67adeb3 --- /dev/null +++ b/esphome/components/coolix/coolix.cpp @@ -0,0 +1,170 @@ +#include "coolix.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace coolix { + +static const char *TAG = "coolix.climate"; + +const uint32_t COOLIX_OFF = 0xB27BE0; +// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore. +const uint32_t COOLIX_DEFAULT_STATE = 0xB2BFC8; +const uint32_t COOLIX_DEFAULT_STATE_AUTO_24_FAN = 0xB21F48; +const uint8_t COOLIX_COOL = 0b00; +const uint8_t COOLIX_DRY = 0b01; +const uint8_t COOLIX_AUTO = 0b10; +const uint8_t COOLIX_HEAT = 0b11; +const uint8_t COOLIX_FAN = 4; // Synthetic. +const uint32_t COOLIX_MODE_MASK = 0b000000000000000000001100; // 0xC + +// Temperature +const uint8_t COOLIX_TEMP_MIN = 17; // Celsius +const uint8_t COOLIX_TEMP_MAX = 30; // Celsius +const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1; +const uint8_t COOLIX_FAN_TEMP_CODE = 0b1110; // Part of Fan Mode. +const uint32_t COOLIX_TEMP_MASK = 0b11110000; +const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = { + 0b0000, // 17C + 0b0001, // 18c + 0b0011, // 19C + 0b0010, // 20C + 0b0110, // 21C + 0b0111, // 22C + 0b0101, // 23C + 0b0100, // 24C + 0b1100, // 25C + 0b1101, // 26C + 0b1001, // 27C + 0b1000, // 28C + 0b1010, // 29C + 0b1011 // 30C +}; + +// Constants +// Pulse parms are *50-100 for the Mark and *50+100 for the space +// First MARK is the one after the long gap +// pulse parameters in usec +const uint16_t COOLIX_TICK = 560; // Approximately 21 cycles at 38kHz +const uint16_t COOLIX_BIT_MARK_TICKS = 1; +const uint16_t COOLIX_BIT_MARK = COOLIX_BIT_MARK_TICKS * COOLIX_TICK; +const uint16_t COOLIX_ONE_SPACE_TICKS = 3; +const uint16_t COOLIX_ONE_SPACE = COOLIX_ONE_SPACE_TICKS * COOLIX_TICK; +const uint16_t COOLIX_ZERO_SPACE_TICKS = 1; +const uint16_t COOLIX_ZERO_SPACE = COOLIX_ZERO_SPACE_TICKS * COOLIX_TICK; +const uint16_t COOLIX_HEADER_MARK_TICKS = 8; +const uint16_t COOLIX_HEADER_MARK = COOLIX_HEADER_MARK_TICKS * COOLIX_TICK; +const uint16_t COOLIX_HEADER_SPACE_TICKS = 8; +const uint16_t COOLIX_HEADER_SPACE = COOLIX_HEADER_SPACE_TICKS * COOLIX_TICK; +const uint16_t COOLIX_MIN_GAP_TICKS = COOLIX_HEADER_MARK_TICKS + COOLIX_ZERO_SPACE_TICKS; +const uint16_t COOLIX_MIN_GAP = COOLIX_MIN_GAP_TICKS * COOLIX_TICK; + +const uint16_t COOLIX_BITS = 24; + +climate::ClimateTraits CoolixClimate::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_two_point_target_temperature(false); + traits.set_supports_away(false); + traits.set_visual_min_temperature(17); + traits.set_visual_max_temperature(30); + traits.set_visual_temperature_step(1); + return traits; +} + +void CoolixClimate::setup() { + if (this->sensor_) { + this->sensor_->add_on_state_callback([this](float state) { + this->current_temperature = state; + // current temperature changed, publish state + this->publish_state(); + }); + this->current_temperature = this->sensor_->state; + } else + this->current_temperature = NAN; + // restore set points + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(this); + } else { + // restore from defaults + this->mode = climate::CLIMATE_MODE_AUTO; + // initialize target temperature to some value so that it's not NAN + this->target_temperature = roundf(this->current_temperature); + } +} + +void CoolixClimate::control(const climate::ClimateCall &call) { + if (call.get_mode().has_value()) + this->mode = *call.get_mode(); + if (call.get_target_temperature().has_value()) + this->target_temperature = *call.get_target_temperature(); + + this->transmit_state_(); + this->publish_state(); +} + +void CoolixClimate::transmit_state_() { + uint32_t remote_state; + + switch (this->mode) { + case climate::CLIMATE_MODE_COOL: + remote_state = (COOLIX_DEFAULT_STATE & ~COOLIX_MODE_MASK) | (COOLIX_COOL << 2); + break; + case climate::CLIMATE_MODE_HEAT: + remote_state = (COOLIX_DEFAULT_STATE & ~COOLIX_MODE_MASK) | (COOLIX_HEAT << 2); + break; + case climate::CLIMATE_MODE_AUTO: + remote_state = COOLIX_DEFAULT_STATE_AUTO_24_FAN; + break; + case climate::CLIMATE_MODE_OFF: + default: + remote_state = COOLIX_OFF; + break; + } + if (this->mode != climate::CLIMATE_MODE_OFF) { + auto temp = (uint8_t) roundf(clamp(this->target_temperature, COOLIX_TEMP_MIN, COOLIX_TEMP_MAX)); + remote_state &= ~COOLIX_TEMP_MASK; // Clear the old temp. + remote_state |= (COOLIX_TEMP_MAP[temp - COOLIX_TEMP_MIN] << 4); + } + + ESP_LOGV(TAG, "Sending coolix code: %u", remote_state); + + auto transmit = this->transmitter_->transmit(); + auto data = transmit.get_data(); + + data->set_carrier_frequency(38000); + uint16_t repeat = 1; + for (uint16_t r = 0; r <= repeat; r++) { + // Header + data->mark(COOLIX_HEADER_MARK); + data->space(COOLIX_HEADER_SPACE); + // Data + // Break data into byte segments, starting at the Most Significant + // Byte. Each byte then being sent normal, then followed inverted. + for (uint16_t i = 8; i <= COOLIX_BITS; i += 8) { + // Grab a bytes worth of data. + uint8_t segment = (remote_state >> (COOLIX_BITS - i)) & 0xFF; + // Normal + for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) { + data->mark(COOLIX_BIT_MARK); + data->space((segment & mask) ? COOLIX_ONE_SPACE : COOLIX_ZERO_SPACE); + } + // Inverted + for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) { + data->mark(COOLIX_BIT_MARK); + data->space(!(segment & mask) ? COOLIX_ONE_SPACE : COOLIX_ZERO_SPACE); + } + } + // Footer + data->mark(COOLIX_BIT_MARK); + data->space(COOLIX_MIN_GAP); // Pause before repeating + } + + transmit.perform(); +} + +} // namespace coolix +} // namespace esphome diff --git a/esphome/components/coolix/coolix.h b/esphome/components/coolix/coolix.h new file mode 100644 index 0000000000..0d52018d2a --- /dev/null +++ b/esphome/components/coolix/coolix.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/climate/climate.h" +#include "esphome/components/remote_base/remote_base.h" +#include "esphome/components/remote_transmitter/remote_transmitter.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace coolix { + +class CoolixClimate : public climate::Climate, public Component { + public: + void setup() override; + void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { + this->transmitter_ = transmitter; + } + void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } + void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } + void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } + + protected: + /// Override control to change settings of the climate device. + void control(const climate::ClimateCall &call) override; + /// Return the traits of this controller. + climate::ClimateTraits traits() override; + + /// Transmit via IR the state of this climate controller. + void transmit_state_(); + + bool supports_cool_{true}; + bool supports_heat_{true}; + + remote_transmitter::RemoteTransmitterComponent *transmitter_; + sensor::Sensor *sensor_{nullptr}; +}; + +} // namespace coolix +} // namespace esphome diff --git a/esphome/components/ct_clamp/__init__.py b/esphome/components/ct_clamp/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.cpp b/esphome/components/ct_clamp/ct_clamp_sensor.cpp new file mode 100644 index 0000000000..8819f9711e --- /dev/null +++ b/esphome/components/ct_clamp/ct_clamp_sensor.cpp @@ -0,0 +1,88 @@ +#include "ct_clamp_sensor.h" + +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace ct_clamp { + +static const char *TAG = "ct_clamp"; + +void CTClampSensor::setup() { + this->is_calibrating_offset_ = true; + this->high_freq_.start(); + this->set_timeout("calibrate_offset", this->sample_duration_, [this]() { + this->high_freq_.stop(); + this->is_calibrating_offset_ = false; + if (this->num_samples_ != 0) { + this->offset_ = this->sample_sum_ / this->num_samples_; + } + }); +} + +void CTClampSensor::dump_config() { + LOG_SENSOR("", "CT Clamp Sensor", this); + ESP_LOGCONFIG(TAG, " Sample Duration: %.2fs", this->sample_duration_ / 1e3f); + LOG_UPDATE_INTERVAL(this); +} + +void CTClampSensor::update() { + if (this->is_calibrating_offset_) + return; + + // Update only starts the sampling phase, in loop() the actual sampling is happening. + + // Request a high loop() execution interval during sampling phase. + this->high_freq_.start(); + + // Set timeout for ending sampling phase + this->set_timeout("read", this->sample_duration_, [this]() { + this->is_sampling_ = false; + this->high_freq_.stop(); + + if (this->num_samples_ == 0) { + // Shouldn't happen, but let's not crash if it does. + this->publish_state(NAN); + return; + } + + float raw = this->sample_sum_ / this->num_samples_; + float irms = std::sqrt(raw); + ESP_LOGD(TAG, "'%s' - Raw Value: %.2fA", this->name_.c_str(), irms); + this->publish_state(irms); + }); + + // Set sampling values + this->is_sampling_ = true; + this->num_samples_ = 0; + this->sample_sum_ = 0.0f; +} + +void CTClampSensor::loop() { + if (!this->is_sampling_ || !this->is_calibrating_offset_) + return; + + // Perform a single sample + float value = this->source_->sample(); + + if (this->is_calibrating_offset_) { + this->sample_sum_ += value; + this->num_samples_++; + return; + } + + // Adjust DC offset via low pass filter (exponential moving average) + const float alpha = 0.001f; + this->offset_ = this->offset_ * (1 - alpha) + value * alpha; + + // Filtered value centered around the mid-point (0V) + float filtered = value - this->offset_; + + // IRMS is sqrt(∑v_i²) + float sq = filtered * filtered; + this->sample_sum_ += sq; + this->num_samples_++; +} + +} // namespace ct_clamp +} // namespace esphome diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.h b/esphome/components/ct_clamp/ct_clamp_sensor.h new file mode 100644 index 0000000000..c709f6718b --- /dev/null +++ b/esphome/components/ct_clamp/ct_clamp_sensor.h @@ -0,0 +1,52 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/esphal.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/voltage_sampler/voltage_sampler.h" + +namespace esphome { +namespace ct_clamp { + +class CTClampSensor : public sensor::Sensor, public PollingComponent { + public: + void setup() override; + void update() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override { + // After the base sensor has been initialized + return setup_priority::DATA - 1.0f; + } + + void set_sample_duration(uint32_t sample_duration) { sample_duration_ = sample_duration; } + void set_source(voltage_sampler::VoltageSampler *source) { source_ = source; } + + protected: + /// High Frequency loop() requester used during sampling phase. + HighFrequencyLoopRequester high_freq_; + + /// Duration in ms of the sampling phase. + uint32_t sample_duration_; + /// The sampling source to read values from. + voltage_sampler::VoltageSampler *source_; + + /** The DC offset of the circuit. + * + * Diagram: https://learn.openenergymonitor.org/electricity-monitoring/ct-sensors/interface-with-arduino + * + * This is automatically calculated with an exponential moving average/digital low pass filter. + * + * 0.5 is a good initial approximation to start with for most ESP8266 setups. + */ + float offset_ = 0.5f; + + float sample_sum_ = 0.0f; + uint32_t num_samples_ = 0; + bool is_sampling_ = false; + /// Calibrate offset value once at boot + bool is_calibrating_offset_ = false; +}; + +} // namespace ct_clamp +} // namespace esphome diff --git a/esphome/components/ct_clamp/sensor.py b/esphome/components/ct_clamp/sensor.py new file mode 100644 index 0000000000..9f41f8c614 --- /dev/null +++ b/esphome/components/ct_clamp/sensor.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, voltage_sampler +from esphome.const import CONF_SENSOR, CONF_ID, ICON_FLASH, UNIT_AMPERE + +AUTO_LOAD = ['voltage_sampler'] + +CONF_SAMPLE_DURATION = 'sample_duration' + +ct_clamp_ns = cg.esphome_ns.namespace('ct_clamp') +CTClampSensor = ct_clamp_ns.class_('CTClampSensor', sensor.Sensor, cg.PollingComponent) + +CONFIG_SCHEMA = sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2).extend({ + cv.GenerateID(): cv.declare_id(CTClampSensor), + cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), + cv.Optional(CONF_SAMPLE_DURATION, default='200ms'): cv.positive_time_period_milliseconds, +}).extend(cv.polling_component_schema('60s')) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield sensor.register_sensor(var, config) + + sens = yield cg.get_variable(config[CONF_SENSOR]) + cg.add(var.set_source(sens)) + cg.add(var.set_sample_duration(config[CONF_SAMPLE_DURATION])) diff --git a/esphome/components/cwww/cwww_light_output.h b/esphome/components/cwww/cwww_light_output.h index 4497d051e4..8192039511 100644 --- a/esphome/components/cwww/cwww_light_output.h +++ b/esphome/components/cwww/cwww_light_output.h @@ -19,6 +19,8 @@ class CWWWLightOutput : public light::LightOutput { traits.set_supports_rgb(false); traits.set_supports_rgb_white_value(false); traits.set_supports_color_temperature(true); + traits.set_min_mireds(this->cold_white_temperature_); + traits.set_max_mireds(this->warm_white_temperature_); return traits; } void write_state(light::LightState *state) override { diff --git a/esphome/components/cwww/light.py b/esphome/components/cwww/light.py index 5d2b4ab2c9..f86f1eace0 100644 --- a/esphome/components/cwww/light.py +++ b/esphome/components/cwww/light.py @@ -25,4 +25,4 @@ def to_code(config): wwhite = yield cg.get_variable(config[CONF_WARM_WHITE]) cg.add(var.set_warm_white(wwhite)) - cg.add(var.set_warm_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE])) + cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE])) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 1d3e693ff9..6eeddb1b56 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -132,10 +132,14 @@ void DallasComponent::update() { enable_interrupts(); if (!res) { + ESP_LOGW(TAG, "'%s' - Reseting bus for read failed!", sensor->get_name().c_str()); + sensor->publish_state(NAN); this->status_set_warning(); return; } if (!sensor->check_scratch_pad()) { + ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", sensor->get_name().c_str()); + sensor->publish_state(NAN); this->status_set_warning(); return; } @@ -244,11 +248,7 @@ bool DallasTemperatureSensor::check_scratch_pad() { this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8], crc8(this->scratch_pad_, 8)); #endif - if (crc8(this->scratch_pad_, 8) != this->scratch_pad_[8]) { - ESP_LOGE(TAG, "Reading scratchpad from Dallas Sensor failed"); - return false; - } - return true; + return crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]; } float DallasTemperatureSensor::get_temp_c() { int16_t temp = (int16_t(this->scratch_pad_[1]) << 11) | (int16_t(this->scratch_pad_[0]) << 3); diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 9cb3ec9cef..4ffc034d50 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/helpers.h" #include "esphome/core/defines.h" +#include "esphome/core/version.h" #ifdef ARDUINO_ARCH_ESP32 #include @@ -19,7 +20,7 @@ void DebugComponent::dump_config() { return; #endif - ESP_LOGD(TAG, "ESPHome Core version %s", ESPHOME_VERSION); + ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); this->free_heap_ = ESP.getFreeHeap(); ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 217c0cbf0d..684d7e12bf 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -87,7 +87,7 @@ void DeepSleepComponent::begin_sleep(bool manual) { ESP.deepSleep(*this->sleep_duration_); #endif } -float DeepSleepComponent::get_setup_priority() const { return -100.0f; } +float DeepSleepComponent::get_setup_priority() const { return setup_priority::LATE; } void DeepSleepComponent::prevent_deep_sleep() { this->prevent_ = true; } } // namespace deep_sleep diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index 79732bb269..936f87e3fa 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -1,4 +1,4 @@ -#include "esphome/components/dht/dht.h" +#include "dht.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" @@ -153,28 +153,39 @@ bool HOT DHT::read_sensor_(float *temperature, float *humidity, bool report_erro if (checksum_a != data[4] && checksum_b != data[4]) { if (report_errors) { - ESP_LOGE(TAG, "Checksum invalid: %u!=%u", checksum_a, data[4]); + ESP_LOGW(TAG, "Checksum invalid: %u!=%u", checksum_a, data[4]); } return false; } if (this->model_ == DHT_MODEL_DHT11) { *humidity = data[0]; + if (*humidity > 100) + *humidity = NAN; *temperature = data[2]; } else { uint16_t raw_humidity = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF); uint16_t raw_temperature = (uint16_t(data[2] & 0xFF) << 8) | (data[3] & 0xFF); - *humidity = raw_humidity * 0.1f; if ((raw_temperature & 0x8000) != 0) raw_temperature = ~(raw_temperature & 0x7FFF); + if (raw_temperature == 1 && raw_humidity == 10) { + if (report_errors) { + ESP_LOGW(TAG, "Invalid temperature+humidity! Sensor reported 1°C and 1%% Hum"); + } + return false; + } + + *humidity = raw_humidity * 0.1f; + if (*humidity > 100) + *humidity = NAN; *temperature = int16_t(raw_temperature) * 0.1f; } if (*temperature == 0.0f && (*humidity == 1.0f || *humidity == 2.0f)) { if (report_errors) { - ESP_LOGE(TAG, "DHT reports invalid data. Is the update interval too high or the sensor damaged?"); + ESP_LOGW(TAG, "DHT reports invalid data. Is the update interval too high or the sensor damaged?"); } return false; } diff --git a/esphome/components/dht/sensor.py b/esphome/components/dht/sensor.py index e1e18bb7f9..8455f74fb4 100644 --- a/esphome/components/dht/sensor.py +++ b/esphome/components/dht/sensor.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_MODEL, CONF_PIN, CONF_TEMPERATURE, \ - CONF_UPDATE_INTERVAL, ICON_THERMOMETER, UNIT_CELSIUS, ICON_WATER_PERCENT, UNIT_PERCENT + ICON_THERMOMETER, UNIT_CELSIUS, ICON_WATER_PERCENT, UNIT_PERCENT from esphome.cpp_helpers import gpio_pin_expression dht_ns = cg.esphome_ns.namespace('dht') @@ -24,7 +24,6 @@ CONFIG_SCHEMA = cv.Schema({ cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), cv.Optional(CONF_MODEL, default='auto detect'): cv.enum(DHT_MODELS, upper=True, space='_'), - cv.Optional(CONF_UPDATE_INTERVAL, default='60s'): cv.update_interval, }).extend(cv.polling_component_schema('60s')) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 5ef814c2ba..9e6cb1cbce 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -1,5 +1,6 @@ #include "esp32_ble_tracker.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" #ifdef ARDUINO_ARCH_ESP32 @@ -139,7 +140,7 @@ bool ESP32BLETracker::ble_setup() { void ESP32BLETracker::start_scan(bool first) { if (!xSemaphoreTake(this->scan_end_lock_, 0L)) { - ESP_LOGW("Cannot start scan!"); + ESP_LOGW(TAG, "Cannot start scan!"); return; } @@ -162,6 +163,11 @@ void ESP32BLETracker::start_scan(bool first) { esp_ble_gap_set_scan_params(&this->scan_params_); esp_ble_gap_start_scanning(this->scan_interval_); + + this->set_timeout("scan", this->scan_interval_ * 2000, []() { + ESP_LOGW(TAG, "ESP-IDF BLE scan never terminated, rebooting to restore BLE stack..."); + App.reboot(); + }); } void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { diff --git a/esphome/components/esp32_touch/binary_sensor.py b/esphome/components/esp32_touch/binary_sensor.py index 94748e53e8..a72ca5796f 100644 --- a/esphome/components/esp32_touch/binary_sensor.py +++ b/esphome/components/esp32_touch/binary_sensor.py @@ -1,10 +1,9 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.components.esp32_touch import ESP32TouchComponent from esphome.const import CONF_NAME, CONF_PIN, CONF_THRESHOLD, ESP_PLATFORM_ESP32, CONF_ID from esphome.pins import validate_gpio_pin -from . import esp32_touch_ns +from . import esp32_touch_ns, ESP32TouchComponent ESP_PLATFORMS = [ESP_PLATFORM_ESP32] DEPENDENCIES = ['esp32_touch'] diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index e85d0ef5c2..56bc407e84 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -116,6 +116,7 @@ void ESP32TouchComponent::loop() { touch_pad_read(child->get_touch_pad(), &value); } + child->value_ = value; child->publish_state(value < child->get_threshold()); if (this->setup_mode_) { @@ -128,23 +129,7 @@ void ESP32TouchComponent::loop() { delay(250); } } -void ESP32TouchComponent::register_touch_pad(ESP32TouchBinarySensor *pad) { this->children_.push_back(pad); } -void ESP32TouchComponent::set_setup_mode(bool setup_mode) { this->setup_mode_ = setup_mode; } -bool ESP32TouchComponent::iir_filter_enabled_() const { return this->iir_filter_ > 0; } -void ESP32TouchComponent::set_iir_filter(uint32_t iir_filter) { this->iir_filter_ = iir_filter; } -float ESP32TouchComponent::get_setup_priority() const { return setup_priority::DATA; } -void ESP32TouchComponent::set_sleep_duration(uint16_t sleep_duration) { this->sleep_cycle_ = sleep_duration; } -void ESP32TouchComponent::set_measurement_duration(uint16_t meas_cycle) { this->meas_cycle_ = meas_cycle; } -void ESP32TouchComponent::set_low_voltage_reference(touch_low_volt_t low_voltage_reference) { - this->low_voltage_reference_ = low_voltage_reference; -} -void ESP32TouchComponent::set_high_voltage_reference(touch_high_volt_t high_voltage_reference) { - this->high_voltage_reference_ = high_voltage_reference; -} -void ESP32TouchComponent::set_voltage_attenuation(touch_volt_atten_t voltage_attenuation) { - this->voltage_attenuation_ = voltage_attenuation; -} void ESP32TouchComponent::on_shutdown() { if (this->iir_filter_enabled_()) { touch_pad_filter_stop(); @@ -155,8 +140,6 @@ void ESP32TouchComponent::on_shutdown() { ESP32TouchBinarySensor::ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold) : BinarySensor(name), touch_pad_(touch_pad), threshold_(threshold) {} -touch_pad_t ESP32TouchBinarySensor::get_touch_pad() const { return this->touch_pad_; } -uint16_t ESP32TouchBinarySensor::get_threshold() const { return this->threshold_; } } // namespace esp32_touch } // namespace esphome diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index b68876c33e..7adee23971 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -12,32 +12,36 @@ class ESP32TouchBinarySensor; class ESP32TouchComponent : public Component { public: - void register_touch_pad(ESP32TouchBinarySensor *pad); + void register_touch_pad(ESP32TouchBinarySensor *pad) { children_.push_back(pad); } - void set_setup_mode(bool setup_mode); + void set_setup_mode(bool setup_mode) { setup_mode_ = setup_mode; } - void set_iir_filter(uint32_t iir_filter); + void set_iir_filter(uint32_t iir_filter) { iir_filter_ = iir_filter; } - void set_sleep_duration(uint16_t sleep_duration); + void set_sleep_duration(uint16_t sleep_duration) { sleep_cycle_ = sleep_duration; } - void set_measurement_duration(uint16_t meas_cycle); + void set_measurement_duration(uint16_t meas_cycle) { meas_cycle_ = meas_cycle; } - void set_low_voltage_reference(touch_low_volt_t low_voltage_reference); + void set_low_voltage_reference(touch_low_volt_t low_voltage_reference) { + low_voltage_reference_ = low_voltage_reference; + } - void set_high_voltage_reference(touch_high_volt_t high_voltage_reference); + void set_high_voltage_reference(touch_high_volt_t high_voltage_reference) { + high_voltage_reference_ = high_voltage_reference; + } - void set_voltage_attenuation(touch_volt_atten_t voltage_attenuation); + void set_voltage_attenuation(touch_volt_atten_t voltage_attenuation) { voltage_attenuation_ = voltage_attenuation; } void setup() override; void dump_config() override; void loop() override; - float get_setup_priority() const override; + float get_setup_priority() const override { return setup_priority::DATA; } void on_shutdown() override; protected: /// Is the IIR filter enabled? - bool iir_filter_enabled_() const; + bool iir_filter_enabled_() const { return iir_filter_ > 0; } uint16_t sleep_cycle_{}; uint16_t meas_cycle_{65535}; @@ -54,14 +58,17 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { public: ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold); - touch_pad_t get_touch_pad() const; - uint16_t get_threshold() const; + touch_pad_t get_touch_pad() const { return touch_pad_; } + uint16_t get_threshold() const { return threshold_; } + void set_threshold(uint16_t threshold) { threshold_ = threshold; } + uint16_t get_value() const { return value_; } protected: friend ESP32TouchComponent; touch_pad_t touch_pad_; uint16_t threshold_; + uint16_t value_; }; } // namespace esp32_touch diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index f3bc91440e..50fdb1c2c9 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -23,7 +23,7 @@ FanSpeed = fan_ns.enum('FanSpeed') FAN_SPEEDS = { 'OFF': FanSpeed.FAN_SPEED_OFF, 'LOW': FanSpeed.FAN_SPEED_LOW, - 'MEDIuM': FanSpeed.FAN_SPEED_MEDIUM, + 'MEDIUM': FanSpeed.FAN_SPEED_MEDIUM, 'HIGH': FanSpeed.FAN_SPEED_HIGH, } diff --git a/esphome/components/fastled_base/__init__.py b/esphome/components/fastled_base/__init__.py index aad30198af..b552c917c0 100644 --- a/esphome/components/fastled_base/__init__.py +++ b/esphome/components/fastled_base/__init__.py @@ -5,8 +5,7 @@ from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_RGB_ORDER, CONF_MA from esphome.core import coroutine fastled_base_ns = cg.esphome_ns.namespace('fastled_base') -FastLEDLightOutput = fastled_base_ns.class_('FastLEDLightOutput', cg.Component, - light.AddressableLight) +FastLEDLightOutput = fastled_base_ns.class_('FastLEDLightOutput', light.AddressableLight) RGB_ORDERS = [ 'RGB', @@ -35,5 +34,6 @@ def new_fastled_light(config): cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE])) yield light.register_light(var, config) - cg.add_library('FastLED', '3.2.0') + # https://github.com/FastLED/FastLED/blob/master/library.json + cg.add_library('FastLED', '3.2.9') yield var diff --git a/esphome/components/fastled_base/fastled_light.h b/esphome/components/fastled_base/fastled_light.h index f9ae2f58d5..0729941e31 100644 --- a/esphome/components/fastled_base/fastled_light.h +++ b/esphome/components/fastled_base/fastled_light.h @@ -16,7 +16,7 @@ namespace esphome { namespace fastled_base { -class FastLEDLightOutput : public Component, public light::AddressableLight { +class FastLEDLightOutput : public light::AddressableLight { public: /// Only for custom effects: Get the internal controller. CLEDController *get_controller() const { return this->controller_; } diff --git a/esphome/components/globals/__init__.py b/esphome/components/globals/__init__.py index 4dce6e7583..e7030978ed 100644 --- a/esphome/components/globals/__init__.py +++ b/esphome/components/globals/__init__.py @@ -50,6 +50,7 @@ def globals_set_to_code(config, action_id, template_arg, args): full_id, paren = yield cg.get_variable_with_full_id(config[CONF_ID]) template_arg = cg.TemplateArguments(full_id.type, *template_arg) var = cg.new_Pvariable(action_id, template_arg, paren) - templ = yield cg.templatable(config[CONF_VALUE], args, None) + templ = yield cg.templatable(config[CONF_VALUE], args, None, + to_exp=cg.RawExpression) cg.add(var.set_value(templ)) yield var diff --git a/esphome/components/globals/globals_component.h b/esphome/components/globals/globals_component.h index c7d2a18d84..397c55f6c4 100644 --- a/esphome/components/globals/globals_component.h +++ b/esphome/components/globals/globals_component.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/helpers.h" namespace esphome { namespace globals { @@ -64,5 +65,7 @@ template class GlobalVarSetAction : public Action T &id(GlobalsComponent *value) { return value->value(); } + } // namespace globals } // namespace esphome diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp index dff3609ce2..f95778af4c 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp @@ -1,4 +1,4 @@ -#include "esphome/components/gpio/binary_sensor/gpio_binary_sensor.h" +#include "gpio_binary_sensor.h" #include "esphome/core/log.h" namespace esphome { diff --git a/esphome/components/gpio/switch/gpio_switch.cpp b/esphome/components/gpio/switch/gpio_switch.cpp index 22139d6b9c..d22a74847e 100644 --- a/esphome/components/gpio/switch/gpio_switch.cpp +++ b/esphome/components/gpio/switch/gpio_switch.cpp @@ -1,4 +1,4 @@ -#include "esphome/components/gpio/switch/gpio_switch.h" +#include "gpio_switch.h" #include "esphome/core/log.h" namespace esphome { diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index 3ecbc89f73..ec94ebdad4 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -20,4 +20,6 @@ def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) yield cg.register_component(var, config) yield uart.register_uart_device(var, config) + + # https://platformio.org/lib/show/1655/TinyGPSPlus cg.add_library('TinyGPSPlus', '1.0.2') diff --git a/esphome/components/hlw8012/hlw8012.h b/esphome/components/hlw8012/hlw8012.h index b9321b51c6..4e5dc0f67f 100644 --- a/esphome/components/hlw8012/hlw8012.h +++ b/esphome/components/hlw8012/hlw8012.h @@ -8,6 +8,8 @@ namespace esphome { namespace hlw8012 { +enum HLW8012InitialMode { HLW8012_INITIAL_MODE_CURRENT = 0, HLW8012_INITIAL_MODE_VOLTAGE }; + class HLW8012Component : public PollingComponent { public: void setup() override; @@ -15,6 +17,9 @@ class HLW8012Component : public PollingComponent { float get_setup_priority() const override; void update() override; + void set_initial_mode(HLW8012InitialMode initial_mode) { + current_mode_ = initial_mode == HLW8012_INITIAL_MODE_CURRENT; + } void set_change_mode_every(uint32_t change_mode_every) { change_mode_every_ = change_mode_every; } void set_current_resistor(float current_resistor) { current_resistor_ = current_resistor; } void set_voltage_divider(float voltage_divider) { voltage_divider_ = voltage_divider; } diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index 697c34f9d2..e1f02b8fd2 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -2,7 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.components import sensor -from esphome.const import CONF_CHANGE_MODE_EVERY, CONF_CURRENT, \ +from esphome.const import CONF_CHANGE_MODE_EVERY, CONF_INITIAL_MODE, CONF_CURRENT, \ CONF_CURRENT_RESISTOR, CONF_ID, CONF_POWER, CONF_SEL_PIN, CONF_VOLTAGE, CONF_VOLTAGE_DIVIDER, \ ICON_FLASH, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT @@ -10,6 +10,11 @@ AUTO_LOAD = ['pulse_counter'] hlw8012_ns = cg.esphome_ns.namespace('hlw8012') HLW8012Component = hlw8012_ns.class_('HLW8012Component', cg.PollingComponent) +HLW8012InitialMode = hlw8012_ns.enum('HLW8012InitialMode') +INITIAL_MODES = { + CONF_CURRENT: HLW8012InitialMode.HLW8012_INITIAL_MODE_CURRENT, + CONF_VOLTAGE: HLW8012InitialMode.HLW8012_INITIAL_MODE_VOLTAGE, +} CONF_CF1_PIN = 'cf1_pin' CONF_CF_PIN = 'cf_pin' @@ -28,6 +33,7 @@ CONFIG_SCHEMA = cv.Schema({ cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, cv.Optional(CONF_CHANGE_MODE_EVERY, default=8): cv.All(cv.uint32_t, cv.Range(min=1)), + cv.Optional(CONF_INITIAL_MODE, default=CONF_VOLTAGE): cv.one_of(*INITIAL_MODES, lower=True), }).extend(cv.polling_component_schema('60s')) @@ -54,3 +60,4 @@ def to_code(config): cg.add(var.set_current_resistor(config[CONF_CURRENT_RESISTOR])) cg.add(var.set_voltage_divider(config[CONF_VOLTAGE_DIVIDER])) cg.add(var.set_change_mode_every(config[CONF_CHANGE_MODE_EVERY])) + cg.add(var.set_initial_mode(INITIAL_MODES[config[CONF_INITIAL_MODE]])) diff --git a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp index 61c73d272b..203f6d8a24 100644 --- a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp +++ b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp @@ -16,14 +16,16 @@ void HomeassistantBinarySensor::setup() { ESP_LOGW(TAG, "Can't convert '%s' to binary state!", state.c_str()); break; case PARSE_ON: - ESP_LOGD(TAG, "'%s': Got state ON", this->entity_id_.c_str()); - this->publish_state(true); - break; case PARSE_OFF: - ESP_LOGD(TAG, "'%s': Got state OFF", this->entity_id_.c_str()); - this->publish_state(false); + bool new_state = val == PARSE_ON; + ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), ONOFF(new_state)); + if (this->initial_) + this->publish_initial_state(new_state); + else + this->publish_state(new_state); break; } + this->initial_ = false; }); } void HomeassistantBinarySensor::dump_config() { diff --git a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.h b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.h index c2c7ec4480..e468fd00eb 100644 --- a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.h +++ b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.h @@ -15,6 +15,7 @@ class HomeassistantBinarySensor : public binary_sensor::BinarySensor, public Com protected: std::string entity_id_; + bool initial_{true}; }; } // namespace homeassistant diff --git a/esphome/components/homeassistant/time/homeassistant_time.cpp b/esphome/components/homeassistant/time/homeassistant_time.cpp index b21fd4c0ce..e9d97690fb 100644 --- a/esphome/components/homeassistant/time/homeassistant_time.cpp +++ b/esphome/components/homeassistant/time/homeassistant_time.cpp @@ -22,19 +22,5 @@ void HomeassistantTime::setup() { HomeassistantTime *global_homeassistant_time = nullptr; -bool GetTimeResponse::decode_32bit(uint32_t field_id, uint32_t value) { - switch (field_id) { - case 1: - // fixed32 epoch_seconds = 1; - if (global_homeassistant_time != nullptr) { - global_homeassistant_time->set_epoch_time(value); - } - return true; - default: - return false; - } -} -api::APIMessageType GetTimeResponse::message_type() const { return api::APIMessageType::GET_TIME_RESPONSE; } - } // namespace homeassistant } // namespace esphome diff --git a/esphome/components/homeassistant/time/homeassistant_time.h b/esphome/components/homeassistant/time/homeassistant_time.h index 43937c6f13..8ab09d1185 100644 --- a/esphome/components/homeassistant/time/homeassistant_time.h +++ b/esphome/components/homeassistant/time/homeassistant_time.h @@ -17,11 +17,5 @@ class HomeassistantTime : public time::RealTimeClock { extern HomeassistantTime *global_homeassistant_time; -class GetTimeResponse : public api::APIMessage { - public: - bool decode_32bit(uint32_t field_id, uint32_t value) override; - api::APIMessageType message_type() const override; -}; - } // namespace homeassistant } // namespace esphome diff --git a/esphome/components/hx711/hx711.cpp b/esphome/components/hx711/hx711.cpp index efa9e7b264..1c808a2501 100644 --- a/esphome/components/hx711/hx711.cpp +++ b/esphome/components/hx711/hx711.cpp @@ -1,5 +1,6 @@ #include "hx711.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" namespace esphome { namespace hx711 { @@ -10,6 +11,7 @@ void HX711Sensor::setup() { ESP_LOGCONFIG(TAG, "Setting up HX711 '%s'...", this->name_.c_str()); this->sck_pin_->setup(); this->dout_pin_->setup(); + this->sck_pin_->digital_write(false); // Read sensor once without publishing to set the gain this->read_sensor_(nullptr); @@ -25,8 +27,9 @@ float HX711Sensor::get_setup_priority() const { return setup_priority::DATA; } void HX711Sensor::update() { uint32_t result; if (this->read_sensor_(&result)) { - ESP_LOGD(TAG, "'%s': Got value %u", this->name_.c_str(), result); - this->publish_state(result); + int32_t value = static_cast(result); + ESP_LOGD(TAG, "'%s': Got value %d", this->name_.c_str(), value); + this->publish_state(value); } } bool HX711Sensor::read_sensor_(uint32_t *result) { @@ -39,10 +42,11 @@ bool HX711Sensor::read_sensor_(uint32_t *result) { this->status_clear_warning(); uint32_t data = 0; + disable_interrupts(); for (uint8_t i = 0; i < 24; i++) { this->sck_pin_->digital_write(true); delayMicroseconds(1); - data |= uint32_t(this->dout_pin_->digital_read()) << (24 - i); + data |= uint32_t(this->dout_pin_->digital_read()) << (23 - i); this->sck_pin_->digital_write(false); delayMicroseconds(1); } @@ -50,10 +54,16 @@ bool HX711Sensor::read_sensor_(uint32_t *result) { // Cycle clock pin for gain setting for (uint8_t i = 0; i < this->gain_; i++) { this->sck_pin_->digital_write(true); + delayMicroseconds(1); this->sck_pin_->digital_write(false); + delayMicroseconds(1); + } + enable_interrupts(); + + if (data & 0x800000ULL) { + data |= 0xFF000000ULL; } - data ^= 0x800000; if (result != nullptr) *result = data; return true; diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index 834fb1334a..3fa9d2c37a 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -50,7 +50,7 @@ void I2CComponent::dump_config() { } } } -float I2CComponent::get_setup_priority() const { return setup_priority::HARDWARE; } +float I2CComponent::get_setup_priority() const { return setup_priority::BUS; } void I2CComponent::raw_begin_transmission(uint8_t address) { ESP_LOGVV(TAG, "Beginning Transmission to 0x%02X:", address); diff --git a/esphome/components/integration/sensor.py b/esphome/components/integration/sensor.py index 9e4dc0847f..a354ab7433 100644 --- a/esphome/components/integration/sensor.py +++ b/esphome/components/integration/sensor.py @@ -2,7 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.components import sensor -from esphome.const import CONF_ID, CONF_TIME_ID, CONF_SENSOR, CONF_RESTORE +from esphome.const import CONF_ID, CONF_SENSOR, CONF_RESTORE integration_ns = cg.esphome_ns.namespace('integration') IntegrationSensor = integration_ns.class_('IntegrationSensor', sensor.Sensor, cg.Component) @@ -26,7 +26,6 @@ INTEGRATION_METHODS = { CONF_TIME_UNIT = 'time_unit' CONF_INTEGRATION_METHOD = 'integration_method' - CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend({ cv.GenerateID(): cv.declare_id(IntegrationSensor), cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), @@ -45,7 +44,7 @@ def to_code(config): sens = yield cg.get_variable(config[CONF_SENSOR]) cg.add(var.set_sensor(sens)) - cg.add(var.set_time(config[CONF_TIME_ID])) + cg.add(var.set_time(config[CONF_TIME_UNIT])) cg.add(var.set_method(config[CONF_INTEGRATION_METHOD])) cg.add(var.set_restore(config[CONF_RESTORE])) diff --git a/esphome/components/lcd_base/__init__.py b/esphome/components/lcd_base/__init__.py index 27f65f9336..bff194578c 100644 --- a/esphome/components/lcd_base/__init__.py +++ b/esphome/components/lcd_base/__init__.py @@ -1,12 +1,11 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import display -from esphome.const import CONF_DIMENSIONS, CONF_LAMBDA +from esphome.const import CONF_DIMENSIONS from esphome.core import coroutine lcd_base_ns = cg.esphome_ns.namespace('lcd_base') LCDDisplay = lcd_base_ns.class_('LCDDisplay', cg.PollingComponent) -LCDDisplayRef = LCDDisplay.operator('ref') def validate_lcd_dimensions(value): @@ -28,8 +27,3 @@ def setup_lcd_display(var, config): yield cg.register_component(var, config) yield display.register_display(var, config) cg.add(var.set_dimensions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1])) - - if CONF_LAMBDA in config: - lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(LCDDisplayRef, 'it')], - return_type=cg.void) - cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/lcd_base/lcd_display.cpp b/esphome/components/lcd_base/lcd_display.cpp index af6b8304eb..51541049b1 100644 --- a/esphome/components/lcd_base/lcd_display.cpp +++ b/esphome/components/lcd_base/lcd_display.cpp @@ -107,7 +107,7 @@ void LCDDisplay::update() { for (uint8_t i = 0; i < this->rows_ * this->columns_; i++) this->buffer_[i] = ' '; - this->writer_(*this); + this->call_writer(); this->display(); } void LCDDisplay::command_(uint8_t value) { this->send(value, false); } diff --git a/esphome/components/lcd_base/lcd_display.h b/esphome/components/lcd_base/lcd_display.h index 200600eb9c..791f31ace3 100644 --- a/esphome/components/lcd_base/lcd_display.h +++ b/esphome/components/lcd_base/lcd_display.h @@ -12,11 +12,8 @@ namespace lcd_base { class LCDDisplay; -using lcd_writer_t = std::function; - class LCDDisplay : public PollingComponent { public: - void set_writer(lcd_writer_t &&writer) { this->writer_ = std::move(writer); } void set_dimensions(uint8_t columns, uint8_t rows) { this->columns_ = columns; this->rows_ = rows; @@ -54,11 +51,11 @@ class LCDDisplay : public PollingComponent { virtual void send(uint8_t value, bool rs) = 0; void command_(uint8_t value); + virtual void call_writer() = 0; uint8_t columns_; uint8_t rows_; uint8_t *buffer_{nullptr}; - lcd_writer_t writer_; }; } // namespace lcd_base diff --git a/esphome/components/lcd_gpio/display.py b/esphome/components/lcd_gpio/display.py index 1f98955ece..91498d59c9 100644 --- a/esphome/components/lcd_gpio/display.py +++ b/esphome/components/lcd_gpio/display.py @@ -2,7 +2,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.components import lcd_base -from esphome.const import CONF_DATA_PINS, CONF_ENABLE_PIN, CONF_RS_PIN, CONF_RW_PIN, CONF_ID +from esphome.const import CONF_DATA_PINS, CONF_ENABLE_PIN, CONF_RS_PIN, CONF_RW_PIN, CONF_ID, \ + CONF_LAMBDA AUTO_LOAD = ['lcd_base'] @@ -42,3 +43,9 @@ def to_code(config): if CONF_RW_PIN in config: rw = yield cg.gpio_pin_expression(config[CONF_RW_PIN]) cg.add(var.set_rw_pin(rw)) + + if CONF_LAMBDA in config: + lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], + [(GPIOLCDDisplay.operator('ref'), 'it')], + return_type=cg.void) + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/lcd_gpio/gpio_lcd_display.h b/esphome/components/lcd_gpio/gpio_lcd_display.h index ed3b0c1137..01f6f95d9a 100644 --- a/esphome/components/lcd_gpio/gpio_lcd_display.h +++ b/esphome/components/lcd_gpio/gpio_lcd_display.h @@ -8,6 +8,7 @@ namespace lcd_gpio { class GPIOLCDDisplay : public lcd_base::LCDDisplay { public: + void set_writer(std::function &&writer) { this->writer_ = std::move(writer); } void setup() override; void set_data_pins(GPIOPin *d0, GPIOPin *d1, GPIOPin *d2, GPIOPin *d3) { this->data_pins_[0] = d0; @@ -36,10 +37,13 @@ class GPIOLCDDisplay : public lcd_base::LCDDisplay { void write_n_bits(uint8_t value, uint8_t n) override; void send(uint8_t value, bool rs) override; + void call_writer() override { this->writer_(*this); } + GPIOPin *rs_pin_{nullptr}; GPIOPin *rw_pin_{nullptr}; GPIOPin *enable_pin_{nullptr}; GPIOPin *data_pins_[8]{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; + std::function writer_; }; } // namespace lcd_gpio diff --git a/esphome/components/lcd_pcf8574/display.py b/esphome/components/lcd_pcf8574/display.py index 2bc04a283f..2bbb3a2f7b 100644 --- a/esphome/components/lcd_pcf8574/display.py +++ b/esphome/components/lcd_pcf8574/display.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import lcd_base, i2c -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_LAMBDA DEPENDENCIES = ['i2c'] AUTO_LOAD = ['lcd_base'] @@ -18,3 +18,9 @@ def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) yield lcd_base.setup_lcd_display(var, config) yield i2c.register_i2c_device(var, config) + + if CONF_LAMBDA in config: + lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], + [(PCF8574LCDDisplay.operator('ref'), 'it')], + return_type=cg.void) + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/lcd_pcf8574/pcf8574_display.cpp b/esphome/components/lcd_pcf8574/pcf8574_display.cpp index 59491c7d5a..e3002da25d 100644 --- a/esphome/components/lcd_pcf8574/pcf8574_display.cpp +++ b/esphome/components/lcd_pcf8574/pcf8574_display.cpp @@ -6,9 +6,13 @@ namespace lcd_pcf8574 { static const char *TAG = "lcd_pcf8574"; +static const uint8_t LCD_DISPLAY_BACKLIGHT_ON = 0x08; +static const uint8_t LCD_DISPLAY_BACKLIGHT_OFF = 0x00; + void PCF8574LCDDisplay::setup() { ESP_LOGCONFIG(TAG, "Setting up PCF8574 LCD Display..."); - if (!this->write_bytes(0x08, nullptr, 0)) { + this->backlight_value_ = LCD_DISPLAY_BACKLIGHT_ON; + if (!this->write_bytes(this->backlight_value_, nullptr, 0)) { this->mark_failed(); return; } @@ -29,7 +33,7 @@ void PCF8574LCDDisplay::write_n_bits(uint8_t value, uint8_t n) { // Ugly fix: in the super setup() with n == 4 value needs to be shifted left value <<= 4; } - uint8_t data = value | 0x08; // Enable backlight + uint8_t data = value | this->backlight_value_; // Set backlight state this->write_bytes(data, nullptr, 0); // Pulse ENABLE this->write_bytes(data | 0x04, nullptr, 0); @@ -41,6 +45,14 @@ void PCF8574LCDDisplay::send(uint8_t value, bool rs) { this->write_n_bits((value & 0xF0) | rs, 0); this->write_n_bits(((value << 4) & 0xF0) | rs, 0); } +void PCF8574LCDDisplay::backlight() { + this->backlight_value_ = LCD_DISPLAY_BACKLIGHT_ON; + this->write_bytes(this->backlight_value_, nullptr, 0); +} +void PCF8574LCDDisplay::no_backlight() { + this->backlight_value_ = LCD_DISPLAY_BACKLIGHT_OFF; + this->write_bytes(this->backlight_value_, nullptr, 0); +} } // namespace lcd_pcf8574 } // namespace esphome diff --git a/esphome/components/lcd_pcf8574/pcf8574_display.h b/esphome/components/lcd_pcf8574/pcf8574_display.h index 133679c501..4db3afb9b0 100644 --- a/esphome/components/lcd_pcf8574/pcf8574_display.h +++ b/esphome/components/lcd_pcf8574/pcf8574_display.h @@ -9,13 +9,22 @@ namespace lcd_pcf8574 { class PCF8574LCDDisplay : public lcd_base::LCDDisplay, public i2c::I2CDevice { public: + void set_writer(std::function &&writer) { this->writer_ = std::move(writer); } void setup() override; void dump_config() override; + void backlight(); + void no_backlight(); protected: bool is_four_bit_mode() override { return true; } void write_n_bits(uint8_t value, uint8_t n) override; void send(uint8_t value, bool rs) override; + + void call_writer() override { this->writer_(*this); } + + // Stores the current state of the backlight. + uint8_t backlight_value_; + std::function writer_; }; } // namespace lcd_pcf8574 diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index e0ea8f246d..63aac69462 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome.components import mqtt, power_supply from esphome.const import CONF_COLOR_CORRECT, \ CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_ID, \ - CONF_INTERNAL, CONF_NAME, CONF_MQTT_ID, CONF_POWER_SUPPLY + CONF_INTERNAL, CONF_NAME, CONF_MQTT_ID, CONF_POWER_SUPPLY, CONF_RESTORE_MODE from esphome.core import coroutine, coroutine_with_priority from .automation import light_control_to_code # noqa from .effects import validate_effects, BINARY_EFFECTS, \ @@ -12,9 +12,20 @@ from .types import ( # noqa LightState, AddressableLightState, light_ns, LightOutput, AddressableLight) IS_PLATFORM_COMPONENT = True + +LightRestoreMode = light_ns.enum('LightRestoreMode') +RESTORE_MODES = { + 'RESTORE_DEFAULT_OFF': LightRestoreMode.LIGHT_RESTORE_DEFAULT_OFF, + 'RESTORE_DEFAULT_ON': LightRestoreMode.LIGHT_RESTORE_DEFAULT_ON, + 'ALWAYS_OFF': LightRestoreMode.LIGHT_ALWAYS_OFF, + 'ALWAYS_ON': LightRestoreMode.LIGHT_ALWAYS_ON, +} + LIGHT_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ cv.GenerateID(): cv.declare_id(LightState), cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTJSONLightComponent), + cv.Optional(CONF_RESTORE_MODE, default='restore_default_off'): + cv.enum(RESTORE_MODES, upper=True, space='_'), }) BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend({ @@ -41,6 +52,7 @@ ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend({ @coroutine def setup_light_core_(light_var, output_var, config): + cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) if CONF_INTERNAL in config: cg.add(light_var.set_internal(config[CONF_INTERNAL])) if CONF_DEFAULT_TRANSITION_LENGTH in config: diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 640d8112b1..68a303f23d 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -4,6 +4,8 @@ namespace esphome { namespace light { +static const char *TAG = "light.addressable"; + const ESPColor ESPColor::BLACK = ESPColor(0, 0, 0, 0); const ESPColor ESPColor::WHITE = ESPColor(255, 255, 255, 255); @@ -80,11 +82,78 @@ void ESPRangeView::set(const ESPColor &color) { } } ESPColorView ESPRangeView::operator[](int32_t index) const { - index = interpret_index(index, this->size()); + index = interpret_index(index, this->size()) + this->begin_; return (*this->parent_)[index]; } +ESPRangeIterator ESPRangeView::begin() { return {*this, this->begin_}; } +ESPRangeIterator ESPRangeView::end() { return {*this, this->end_}; } +void ESPRangeView::set_red(uint8_t red) { + for (auto c : *this) + c.set_red(red); +} +void ESPRangeView::set_green(uint8_t green) { + for (auto c : *this) + c.set_green(green); +} +void ESPRangeView::set_blue(uint8_t blue) { + for (auto c : *this) + c.set_blue(blue); +} +void ESPRangeView::set_white(uint8_t white) { + for (auto c : *this) + c.set_white(white); +} +void ESPRangeView::set_effect_data(uint8_t effect_data) { + for (auto c : *this) + c.set_effect_data(effect_data); +} +void ESPRangeView::fade_to_white(uint8_t amnt) { + for (auto c : *this) + c.fade_to_white(amnt); +} +void ESPRangeView::fade_to_black(uint8_t amnt) { + for (auto c : *this) + c.fade_to_white(amnt); +} +void ESPRangeView::lighten(uint8_t delta) { + for (auto c : *this) + c.lighten(delta); +} +void ESPRangeView::darken(uint8_t delta) { + for (auto c : *this) + c.darken(delta); +} +ESPRangeView &ESPRangeView::operator=(const ESPRangeView &rhs) { + // If size doesn't match, error (todo warning) + if (rhs.size() != this->size()) + return *this; -ESPColorView ESPRangeView::Iterator::operator*() const { return (*this->range_->parent_)[this->i_]; } + if (this->parent_ != rhs.parent_) { + for (int32_t i = 0; i < this->size(); i++) + (*this)[i].set(rhs[i].get()); + return *this; + } + + // If both equal, already done + if (rhs.begin_ == this->begin_) + return *this; + + if (rhs.begin_ > this->begin_) { + // Copy from left + for (int32_t i = 0; i < this->size(); i++) { + (*this)[i].set(rhs[i].get()); + } + } else { + // Copy from right + for (int32_t i = this->size() - 1; i >= 0; i--) { + (*this)[i].set(rhs[i].get()); + } + } + + return *this; +} + +ESPColorView ESPRangeIterator::operator*() const { return this->range_.parent_->get(this->i_); } int32_t HOT interpret_index(int32_t index, int32_t size) { if (index < 0) @@ -92,5 +161,23 @@ int32_t HOT interpret_index(int32_t index, int32_t size) { return index; } +void AddressableLight::call_setup() { + this->setup(); + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + this->set_interval(5000, [this]() { + const char *name = this->state_parent_ == nullptr ? "" : this->state_parent_->get_name().c_str(); + ESP_LOGVV(TAG, "Addressable Light '%s' (effect_active=%s next_show=%s)", name, YESNO(this->effect_active_), + YESNO(this->next_show_)); + for (int i = 0; i < this->size(); i++) { + auto color = this->get(i); + ESP_LOGVV(TAG, " [%2d] Color: R=%3u G=%3u B=%3u W=%3u", i, color.get_red_raw(), color.get_green_raw(), + color.get_blue_raw(), color.get_white_raw()); + } + ESP_LOGVV(TAG, ""); + }); +#endif +} + } // namespace light } // namespace esphome diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index b4249097db..4383b4b245 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -335,13 +335,21 @@ class ESPColorView : public ESPColorSettable { void darken(uint8_t delta) override { this->set(this->get().darken(delta)); } ESPColor get() const { return ESPColor(this->get_red(), this->get_green(), this->get_blue(), this->get_white()); } uint8_t get_red() const { return this->color_correction_->color_uncorrect_red(*this->red_); } + uint8_t get_red_raw() const { return *this->red_; } uint8_t get_green() const { return this->color_correction_->color_uncorrect_green(*this->green_); } + uint8_t get_green_raw() const { return *this->green_; } uint8_t get_blue() const { return this->color_correction_->color_uncorrect_blue(*this->blue_); } + uint8_t get_blue_raw() const { return *this->blue_; } uint8_t get_white() const { if (this->white_ == nullptr) return 0; return this->color_correction_->color_uncorrect_white(*this->white_); } + uint8_t get_white_raw() const { + if (this->white_ == nullptr) + return 0; + return *this->white_; + } uint8_t get_effect_data() const { if (this->effect_data_ == nullptr) return 0; @@ -364,23 +372,10 @@ class AddressableLight; int32_t interpret_index(int32_t index, int32_t size); +class ESPRangeIterator; + class ESPRangeView : public ESPColorSettable { public: - class Iterator { - public: - Iterator(ESPRangeView *range, int32_t i) : range_(range), i_(i) {} - Iterator operator++() { - this->i_++; - return *this; - } - bool operator!=(const Iterator &other) const { return this->i_ != other.i_; } - ESPColorView operator*() const; - - protected: - ESPRangeView *range_; - int32_t i_; - }; - ESPRangeView(AddressableLight *parent, int32_t begin, int32_t an_end) : parent_(parent), begin_(begin), end_(an_end) { if (this->end_ < this->begin_) { this->end_ = this->begin_; @@ -388,8 +383,8 @@ class ESPRangeView : public ESPColorSettable { } ESPColorView operator[](int32_t index) const; - Iterator begin() { return {this, this->begin_}; } - Iterator end() { return {this, this->end_}; } + ESPRangeIterator begin(); + ESPRangeIterator end(); void set(const ESPColor &color) override; ESPRangeView &operator=(const ESPColor &rhs) { @@ -404,78 +399,42 @@ class ESPRangeView : public ESPColorSettable { this->set_hsv(rhs); return *this; } - ESPRangeView &operator=(const ESPRangeView &rhs) { - // If size doesn't match, error (todo warning) - if (rhs.size() != this->size()) - return *this; - - if (this->parent_ != rhs.parent_) { - for (int32_t i = 0; i < this->size(); i++) - (*this)[i].set(rhs[i].get()); - return *this; - } - - // If both equal, already done - if (rhs.begin_ == this->begin_) - return *this; - - if (rhs.begin_ < this->begin_) { - // Copy into rhs - for (int32_t i = 0; i < this->size(); i++) - rhs[i].set((*this)[i].get()); - } else { - // Copy into this - for (int32_t i = 0; i < this->size(); i++) - (*this)[i].set(rhs[i].get()); - } - - return *this; - } - void set_red(uint8_t red) override { - for (auto c : *this) - c.set_red(red); - } - void set_green(uint8_t green) override { - for (auto c : *this) - c.set_green(green); - } - void set_blue(uint8_t blue) override { - for (auto c : *this) - c.set_blue(blue); - } - void set_white(uint8_t white) override { - for (auto c : *this) - c.set_white(white); - } - void set_effect_data(uint8_t effect_data) override { - for (auto c : *this) - c.set_effect_data(effect_data); - } - void fade_to_white(uint8_t amnt) override { - for (auto c : *this) - c.fade_to_white(amnt); - } - void fade_to_black(uint8_t amnt) override { - for (auto c : *this) - c.fade_to_white(amnt); - } - void lighten(uint8_t delta) override { - for (auto c : *this) - c.lighten(delta); - } - void darken(uint8_t delta) override { - for (auto c : *this) - c.darken(delta); - } + ESPRangeView &operator=(const ESPRangeView &rhs); + void set_red(uint8_t red) override; + void set_green(uint8_t green) override; + void set_blue(uint8_t blue) override; + void set_white(uint8_t white) override; + void set_effect_data(uint8_t effect_data) override; + void fade_to_white(uint8_t amnt) override; + void fade_to_black(uint8_t amnt) override; + void lighten(uint8_t delta) override; + void darken(uint8_t delta) override; int32_t size() const { return this->end_ - this->begin_; } protected: + friend ESPRangeIterator; + AddressableLight *parent_; int32_t begin_; int32_t end_; }; -class AddressableLight : public LightOutput { +class ESPRangeIterator { + public: + ESPRangeIterator(const ESPRangeView &range, int32_t i) : range_(range), i_(i) {} + ESPRangeIterator operator++() { + this->i_++; + return *this; + } + bool operator!=(const ESPRangeIterator &other) const { return this->i_ != other.i_; } + ESPColorView operator*() const; + + protected: + ESPRangeView range_; + int32_t i_; +}; + +class AddressableLight : public LightOutput, public Component { public: virtual int32_t size() const = 0; ESPColorView operator[](int32_t index) const { return this->get_view_internal(interpret_index(index, this->size())); } @@ -487,8 +446,8 @@ class AddressableLight : public LightOutput { return ESPRangeView(this, from, to); } ESPRangeView all() { return ESPRangeView(this, 0, this->size()); } - ESPRangeView::Iterator begin() { return this->all().begin(); } - ESPRangeView::Iterator end() { return this->all().end(); } + ESPRangeIterator begin() { return this->all().begin(); } + ESPRangeIterator end() { return this->all().end(); } void shift_left(int32_t amnt) { if (amnt < 0) { this->shift_right(-amnt); @@ -530,13 +489,18 @@ class AddressableLight : public LightOutput { this->correction_.set_max_brightness(ESPColor(uint8_t(roundf(red * 255.0f)), uint8_t(roundf(green * 255.0f)), uint8_t(roundf(blue * 255.0f)), uint8_t(roundf(white * 255.0f)))); } - void setup_state(LightState *state) override { this->correction_.calculate_gamma_table(state->get_gamma_correct()); } + void setup_state(LightState *state) override { + this->correction_.calculate_gamma_table(state->get_gamma_correct()); + this->state_parent_ = state; + } void schedule_show() { this->next_show_ = true; } #ifdef USE_POWER_SUPPLY void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_.set_parent(power_supply); } #endif + void call_setup() override; + protected: bool should_show_() const { return this->effect_active_ || this->next_show_; } void mark_shown_() { @@ -559,6 +523,7 @@ class AddressableLight : public LightOutput { #ifdef USE_POWER_SUPPLY power_supply::PowerSupplyRequester power_; #endif + LightState *state_parent_{nullptr}; }; } // namespace light diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 545af6a0f2..9cdc9628c6 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -50,19 +50,19 @@ class AddressableLightEffect : public LightEffect { class AddressableLambdaLightEffect : public AddressableLightEffect { public: - AddressableLambdaLightEffect(const std::string &name, const std::function &f, + AddressableLambdaLightEffect(const std::string &name, const std::function &f, uint32_t update_interval) : AddressableLightEffect(name), f_(f), update_interval_(update_interval) {} void apply(AddressableLight &it, const ESPColor ¤t_color) override { const uint32_t now = millis(); if (now - this->last_run_ >= this->update_interval_) { this->last_run_ = now; - this->f_(it); + this->f_(it, current_color); } } protected: - std::function f_; + std::function f_; uint32_t update_interval_; uint32_t last_run_{0}; }; @@ -113,7 +113,7 @@ class AddressableColorWipeEffect : public AddressableLightEffect { it.shift_right(1); const AddressableColorWipeEffectColor color = this->colors_[this->at_color_]; const ESPColor esp_color = ESPColor(color.r, color.g, color.b, color.w); - if (!this->reverse_) + if (this->reverse_) it[-1] = esp_color; else it[0] = esp_color; @@ -143,14 +143,19 @@ class AddressableScanEffect : public AddressableLightEffect { public: explicit AddressableScanEffect(const std::string &name) : AddressableLightEffect(name) {} void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; } + void set_scan_width(uint32_t scan_width) { this->scan_width_ = scan_width; } void apply(AddressableLight &it, const ESPColor ¤t_color) override { it.all() = ESPColor::BLACK; - it[this->at_led_] = current_color; + + for (auto i = 0; i < this->scan_width_; i++) { + it[this->at_led_ + i] = current_color; + } + const uint32_t now = millis(); if (now - this->last_move_ > this->move_interval_) { if (direction_) { this->at_led_++; - if (this->at_led_ == it.size() - 1) + if (this->at_led_ == it.size() - this->scan_width_) this->direction_ = false; } else { this->at_led_--; @@ -163,6 +168,7 @@ class AddressableScanEffect : public AddressableLightEffect { protected: uint32_t move_interval_{}; + uint32_t scan_width_{1}; uint32_t last_move_{0}; int at_led_{0}; bool direction_{true}; @@ -308,14 +314,15 @@ class AddressableFlickerEffect : public AddressableLightEffect { explicit AddressableFlickerEffect(const std::string &name) : AddressableLightEffect(name) {} void apply(AddressableLight &it, const ESPColor ¤t_color) override { const uint32_t now = millis(); - const uint8_t delta_intensity = 255 - this->intensity_; + const uint8_t intensity = this->intensity_; + const uint8_t inv_intensity = 255 - intensity; if (now - this->last_update_ < this->update_interval_) return; this->last_update_ = now; fast_random_set_seed(random_uint32()); for (auto var : it) { - const uint8_t flicker = fast_random_8() % this->intensity_; - var = (var.get() * delta_intensity) + (current_color * flicker); + const uint8_t flicker = fast_random_8() % intensity; + var = (var.get() * inv_intensity) + (current_color * flicker); } } void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index af295df369..70c423af0a 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -89,7 +89,7 @@ template class LightIsOnCondition : public Condition { protected: LightState *state_; }; -template class LightIsOffCondition : public Condition { +template class LightIsOffCondition : public Condition { public: explicit LightIsOffCondition(LightState *state) : state_(state) {} bool check(Ts... x) override { return !this->state_->current_values.is_on(); } diff --git a/esphome/components/light/automation.py b/esphome/components/light/automation.py index 629073707e..9e14246c0f 100644 --- a/esphome/components/light/automation.py +++ b/esphome/components/light/automation.py @@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_TRANSITION_LENGTH, CONF_STATE, CONF_FLAS CONF_EFFECT, CONF_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, \ CONF_COLOR_TEMPERATURE, CONF_RANGE_FROM, CONF_RANGE_TO from .types import DimRelativeAction, ToggleAction, LightState, LightControlAction, \ - AddressableLightState, AddressableSet + AddressableLightState, AddressableSet, LightIsOnCondition, LightIsOffCondition @automation.register_action('light.toggle', ToggleAction, automation.maybe_simple_id({ @@ -145,3 +145,16 @@ def light_addressable_set_to_code(config, action_id, template_arg, args): templ = yield cg.templatable(config[CONF_WHITE], args, cg.uint8, to_exp=rgbw_to_exp) cg.add(var.set_white(templ)) yield var + + +@automation.register_condition('light.is_on', LightIsOnCondition, + automation.maybe_simple_id({ + cv.Required(CONF_ID): cv.use_id(LightState), + })) +@automation.register_condition('light.is_off', LightIsOffCondition, + automation.maybe_simple_id({ + cv.Required(CONF_ID): cv.use_id(LightState), + })) +def light_is_on_off_to_code(config, condition_id, template_arg, args): + paren = yield cg.get_variable(config[CONF_ID]) + yield cg.new_Pvariable(condition_id, template_arg, paren) diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index 4b33b77073..55aa007f56 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -129,7 +129,7 @@ class FlickerLightEffect : public LightEffect { LightColorValues out; const float alpha = this->alpha_; const float beta = 1.0f - alpha; - out.set_state(remote.get_state()); + out.set_state(true); out.set_brightness(remote.get_brightness() * beta + current.get_brightness() * alpha + (random_cubic_float() * this->intensity_)); out.set_red(remote.get_red() * beta + current.get_red() * alpha + (random_cubic_float() * this->intensity_)); @@ -144,6 +144,7 @@ class FlickerLightEffect : public LightEffect { if (traits.get_supports_brightness()) call.set_transition_length(0); call.from_light_color_values(out); + call.set_state(true); call.perform(); } diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index 93dea2628d..c2250e7e0c 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -4,18 +4,19 @@ from esphome import automation from esphome.const import CONF_NAME, CONF_LAMBDA, CONF_UPDATE_INTERVAL, CONF_TRANSITION_LENGTH, \ CONF_COLORS, CONF_STATE, CONF_DURATION, CONF_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE, \ CONF_WHITE, CONF_ALPHA, CONF_INTENSITY, CONF_SPEED, CONF_WIDTH, CONF_NUM_LEDS, CONF_RANDOM, \ - CONF_THEN + CONF_SEQUENCE from esphome.util import Registry from .types import LambdaLightEffect, RandomLightEffect, StrobeLightEffect, \ StrobeLightEffectColor, LightColorValues, AddressableLightRef, AddressableLambdaLightEffect, \ FlickerLightEffect, AddressableRainbowLightEffect, AddressableColorWipeEffect, \ AddressableColorWipeEffectColor, AddressableScanEffect, AddressableTwinkleEffect, \ AddressableRandomTwinkleEffect, AddressableFireworksEffect, AddressableFlickerEffect, \ - AutomationLightEffect + AutomationLightEffect, ESPColor CONF_ADD_LED_INTERVAL = 'add_led_interval' CONF_REVERSE = 'reverse' CONF_MOVE_INTERVAL = 'move_interval' +CONF_SCAN_WIDTH = 'scan_width' CONF_TWINKLE_PROBABILITY = 'twinkle_probability' CONF_PROGRESS_INTERVAL = 'progress_interval' CONF_SPARK_PROBABILITY = 'spark_probability' @@ -63,11 +64,11 @@ def lambda_effect_to_code(config, effect_id): @register_effect('automation', AutomationLightEffect, "Automation", { - cv.Required(CONF_THEN): automation.validate_automation(single=True), + cv.Required(CONF_SEQUENCE): automation.validate_automation(single=True), }) def automation_effect_to_code(config, effect_id): var = yield cg.new_Pvariable(effect_id, config[CONF_NAME]) - yield automation.build_automation(var.get_trig(), [], config[CONF_THEN]) + yield automation.build_automation(var.get_trig(), [], config[CONF_SEQUENCE]) yield var @@ -128,7 +129,7 @@ def flicker_effect_to_code(config, effect_id): cv.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.positive_time_period_milliseconds, }) def addressable_lambda_effect_to_code(config, effect_id): - args = [(AddressableLightRef, 'it')] + args = [(AddressableLightRef, 'it'), (ESPColor, 'current_color')] lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], args, return_type=cg.void) var = cg.new_Pvariable(effect_id, config[CONF_NAME], lambda_, config[CONF_UPDATE_INTERVAL]) @@ -179,10 +180,12 @@ def addressable_color_wipe_effect_to_code(config, effect_id): @register_effect('addressable_scan', AddressableScanEffect, "Scan", { cv.Optional(CONF_MOVE_INTERVAL, default='0.1s'): cv.positive_time_period_milliseconds, + cv.Optional(CONF_SCAN_WIDTH, default=1): cv.int_range(min=1), }) def addressable_scan_effect_to_code(config, effect_id): var = cg.new_Pvariable(effect_id, config[CONF_NAME]) cg.add(var.set_move_interval(config[CONF_MOVE_INTERVAL])) + cg.add(var.set_scan_width(config[CONF_SCAN_WIDTH])) yield var diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 7e61ea245a..cafced27fc 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -98,12 +98,25 @@ void LightState::setup() { effect->init_internal(this); } - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); - LightStateRTCState recovered{}; - // Attempt to load from preferences, else fall back to default values from struct - this->rtc_.load(&recovered); - auto call = this->make_call(); + LightStateRTCState recovered{}; + switch (this->restore_mode_) { + case LIGHT_RESTORE_DEFAULT_OFF: + case LIGHT_RESTORE_DEFAULT_ON: + this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + // Attempt to load from preferences, else fall back to default values from struct + if (!this->rtc_.load(&recovered)) { + recovered.state = this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON; + } + break; + case LIGHT_ALWAYS_OFF: + recovered.state = false; + break; + case LIGHT_ALWAYS_ON: + recovered.state = true; + break; + } + call.set_state(recovered.state); call.set_brightness_if_supported(recovered.brightness); call.set_red_if_supported(recovered.red); diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index 24e659ec8a..d67aa2c53d 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -160,6 +160,13 @@ class LightCall { bool save_{true}; }; +enum LightRestoreMode { + LIGHT_RESTORE_DEFAULT_OFF, + LIGHT_RESTORE_DEFAULT_ON, + LIGHT_ALWAYS_OFF, + LIGHT_ALWAYS_ON, +}; + /** This class represents the communication layer between the front-end MQTT layer and the * hardware output layer. */ @@ -249,6 +256,7 @@ class LightState : public Nameable, public Component { /// Set the gamma correction factor void set_gamma_correct(float gamma_correct); float get_gamma_correct() const { return this->gamma_correct_; } + void set_restore_mode(LightRestoreMode restore_mode) { restore_mode_ = restore_mode; } const std::vector &get_effects() const; @@ -292,6 +300,8 @@ class LightState : public Nameable, public Component { /// Object used to store the persisted values of the light. ESPPreferenceObject rtc_; + /// Restore mode of the light. + LightRestoreMode restore_mode_; /// Default transition length for all transitions in ms. uint32_t default_transition_length_{}; /// Value for storing the index of the currently active effect. 0 if no effect is active diff --git a/esphome/components/light/types.py b/esphome/components/light/types.py index 33ba759df0..fb88d021f2 100644 --- a/esphome/components/light/types.py +++ b/esphome/components/light/types.py @@ -7,8 +7,10 @@ LightState = light_ns.class_('LightState', cg.Nameable, cg.Component) # Fake class for addressable lights AddressableLightState = light_ns.class_('LightState', LightState) LightOutput = light_ns.class_('LightOutput') -AddressableLight = light_ns.class_('AddressableLight') +AddressableLight = light_ns.class_('AddressableLight', cg.Component) AddressableLightRef = AddressableLight.operator('ref') + +ESPColor = light_ns.class_('ESPColor') LightColorValues = light_ns.class_('LightColorValues') # Actions @@ -16,6 +18,8 @@ ToggleAction = light_ns.class_('ToggleAction', automation.Action) LightControlAction = light_ns.class_('LightControlAction', automation.Action) DimRelativeAction = light_ns.class_('DimRelativeAction', automation.Action) AddressableSet = light_ns.class_('AddressableSet', automation.Action) +LightIsOnCondition = light_ns.class_('LightIsOnCondition', automation.Condition) +LightIsOffCondition = light_ns.class_('LightIsOffCondition', automation.Condition) # Effects LightEffect = light_ns.class_('LightEffect') diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 94b33ae18e..f15fd44612 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -29,7 +29,7 @@ LOG_LEVEL_TO_ESP_LOG = { 'VERY_VERBOSE': cg.global_ns.ESP_LOGVV, } -LOG_LEVEL_SEVERITY = ['NONE', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'VERBOSE', 'VERY_VERBOSE'] +LOG_LEVEL_SEVERITY = ['NONE', 'ERROR', 'WARN', 'INFO', 'CONFIG', 'DEBUG', 'VERBOSE', 'VERY_VERBOSE'] UART_SELECTION_ESP32 = ['UART0', 'UART1', 'UART2'] diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index cd71049f15..bc6951c9b9 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -1,4 +1,4 @@ -#include "esphome/components/logger/logger.h" +#include "logger.h" #ifdef ARDUINO_ARCH_ESP32 #include @@ -10,38 +10,74 @@ namespace logger { static const char *TAG = "logger"; -int HOT Logger::log_vprintf_(int level, const char *tag, const char *format, va_list args) { // NOLINT - if (level > this->level_for(tag)) - return 0; +static const char *LOG_LEVEL_COLORS[] = { + "", // NONE + ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), // ERROR + ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_YELLOW), // WARNING + ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GREEN), // INFO + ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_MAGENTA), // CONFIG + ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_CYAN), // DEBUG + ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GRAY), // VERBOSE + ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_WHITE), // VERY_VERBOSE +}; +static const char *LOG_LEVEL_LETTERS[] = { + "", // NONE + "E", // ERROR + "W", // WARNING + "I", // INFO + "C", // CONFIG + "D", // DEBUG + "V", // VERBOSE + "VV", // VERY_VERBOSE +}; - int ret = vsnprintf(this->tx_buffer_.data(), this->tx_buffer_.capacity(), format, args); - this->log_message_(level, tag, this->tx_buffer_.data(), ret); - return ret; +void Logger::write_header_(int level, const char *tag, int line) { + if (level < 0) + level = 0; + if (level > 7) + level = 7; + + const char *color = LOG_LEVEL_COLORS[level]; + const char *letter = LOG_LEVEL_LETTERS[level]; + this->printf_to_buffer_("%s[%s][%s:%03u]: ", color, letter, tag, line); +} + +void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT + if (level > this->level_for(tag)) + return; + + this->reset_buffer_(); + this->write_header_(level, tag, line); + this->vprintf_to_buffer_(format, args); + this->write_footer_(); + this->log_message_(level, tag); } #ifdef USE_STORE_LOG_STR_IN_FLASH -int Logger::log_vprintf_(int level, const char *tag, const __FlashStringHelper *format, va_list args) { // NOLINT +void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, + va_list args) { // NOLINT if (level > this->level_for(tag)) - return 0; + return; + this->reset_buffer_(); // copy format string const char *format_pgm_p = (PGM_P) format; size_t len = 0; - char *write = this->tx_buffer_.data(); char ch = '.'; - while (len < this->tx_buffer_.capacity() && ch != '\0') { - *write++ = ch = pgm_read_byte(format_pgm_p++); - len++; + while (!this->is_buffer_full_() && ch != '\0') { + this->tx_buffer_[this->tx_buffer_at_++] = ch = pgm_read_byte(format_pgm_p++); } - if (len == this->tx_buffer_.capacity()) - return -1; + // Buffer full form copying format + if (this->is_buffer_full_()) + return; + + // length of format string, includes null terminator + uint32_t offset = this->tx_buffer_at_; // now apply vsnprintf - size_t offset = len + 1; - size_t remaining = this->tx_buffer_.capacity() - offset; - char *msg = this->tx_buffer_.data() + offset; - int ret = vsnprintf(msg, remaining, this->tx_buffer_.data(), args); - this->log_message_(level, tag, msg, ret); - return ret; + this->write_header_(level, tag, line); + this->vprintf_to_buffer_(this->tx_buffer_, args); + this->write_footer_(); + this->log_message_(level, tag, offset); } #endif @@ -54,22 +90,26 @@ int HOT Logger::level_for(const char *tag) { return it.level; } } - return this->global_log_level_; + return ESPHOME_LOG_LEVEL; } -void HOT Logger::log_message_(int level, const char *tag, char *msg, int ret) { - if (ret <= 0) - return; +void HOT Logger::log_message_(int level, const char *tag, int offset) { // remove trailing newline - if (msg[ret - 1] == '\n') { - msg[ret - 1] = '\0'; + if (this->tx_buffer_[this->tx_buffer_at_ - 1] == '\n') { + this->tx_buffer_at_--; } + // make sure null terminator is present + this->set_null_terminator_(); + + const char *msg = this->tx_buffer_ + offset; if (this->baud_rate_ > 0) this->hw_serial_->println(msg); this->log_callback_.call(level, tag, msg); } -Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart) : baud_rate_(baud_rate), uart_(uart) { - this->set_tx_buffer_size(tx_buffer_size); +Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart) + : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size), uart_(uart) { + // add 1 to buffer size for null terminator + this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; } void Logger::pre_setup() { @@ -96,7 +136,7 @@ void Logger::pre_setup() { if (this->uart_ == UART_SELECTION_UART0_SWAP) { this->hw_serial_->swap(); } - this->hw_serial_->setDebugOutput(this->global_log_level_ >= ESPHOME_LOG_LEVEL_VERBOSE); + this->hw_serial_->setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); #endif } #ifdef ARDUINO_ARCH_ESP8266 @@ -108,7 +148,7 @@ void Logger::pre_setup() { global_logger = this; #ifdef ARDUINO_ARCH_ESP32 esp_log_set_vprintf(esp_idf_log_vprintf_); - if (this->global_log_level_ >= ESPHOME_LOG_LEVEL_VERBOSE) { + if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { esp_log_level_set("*", ESP_LOG_VERBOSE); } #endif @@ -116,17 +156,15 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } -void Logger::set_global_log_level(int log_level) { this->global_log_level_ = log_level; } void Logger::set_log_level(const std::string &tag, int log_level) { this->log_levels_.push_back(LogLevelOverride{tag, log_level}); } -void Logger::set_tx_buffer_size(size_t tx_buffer_size) { this->tx_buffer_.reserve(tx_buffer_size); } UARTSelection Logger::get_uart() const { return this->uart_; } void Logger::add_on_log_callback(std::function &&callback) { this->log_callback_.add(std::move(callback)); } float Logger::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } -const char *LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "DEBUG", "VERBOSE", "VERY_VERBOSE"}; +const char *LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"}; #ifdef ARDUINO_ARCH_ESP32 const char *UART_SELECTIONS[] = {"UART0", "UART1", "UART2"}; #endif @@ -135,13 +173,14 @@ const char *UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"}; #endif void Logger::dump_config() { ESP_LOGCONFIG(TAG, "Logger:"); - ESP_LOGCONFIG(TAG, " Level: %s", LOG_LEVELS[this->global_log_level_]); + ESP_LOGCONFIG(TAG, " Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]); ESP_LOGCONFIG(TAG, " Log Baud Rate: %u", this->baud_rate_); ESP_LOGCONFIG(TAG, " Hardware UART: %s", UART_SELECTIONS[this->uart_]); for (auto &it : this->log_levels_) { ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]); } } +void Logger::write_footer_() { this->write_to_buffer_(ESPHOME_LOG_RESET_COLOR, strlen(ESPHOME_LOG_RESET_COLOR)); } Logger *global_logger = nullptr; diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 6f06c63595..18196b68d2 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -31,16 +31,9 @@ class Logger : public Component { /// Manually set the baud rate for serial, set to 0 to disable. void set_baud_rate(uint32_t baud_rate); - /// Set the buffer size that's used for constructing log messages. Log messages longer than this will be truncated. - void set_tx_buffer_size(size_t tx_buffer_size); - /// Get the UART used by the logger. UARTSelection get_uart() const; - /// Set the global log level. Note: Use the ESPHOME_LOG_LEVEL define to also remove the logs from the build. - void set_global_log_level(int log_level); - int get_global_log_level() const { return this->global_log_level_; } - /// Set the log level of the specified tag. void set_log_level(const std::string &tag, int log_level); @@ -57,17 +50,58 @@ class Logger : public Component { float get_setup_priority() const override; - int log_vprintf_(int level, const char *tag, const char *format, va_list args); // NOLINT + void log_vprintf_(int level, const char *tag, int line, const char *format, va_list args); // NOLINT #ifdef USE_STORE_LOG_STR_IN_FLASH - int log_vprintf_(int level, const char *tag, const __FlashStringHelper *format, va_list args); // NOLINT + void log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args); // NOLINT #endif protected: - void log_message_(int level, const char *tag, char *msg, int ret); + void write_header_(int level, const char *tag, int line); + void write_footer_(); + void log_message_(int level, const char *tag, int offset = 0); + + inline bool is_buffer_full_() const { return this->tx_buffer_at_ >= this->tx_buffer_size_; } + inline int buffer_remaining_capacity_() const { return this->tx_buffer_size_ - this->tx_buffer_at_; } + inline void reset_buffer_() { this->tx_buffer_at_ = 0; } + inline void set_null_terminator_() { + // does not increment buffer_at + this->tx_buffer_[this->tx_buffer_at_] = '\0'; + } + inline void write_to_buffer_(char value) { + if (!this->is_buffer_full_()) + this->tx_buffer_[this->tx_buffer_at_++] = value; + } + inline void write_to_buffer_(const char *value, int length) { + for (int i = 0; i < length && !this->is_buffer_full_(); i++) { + this->tx_buffer_[this->tx_buffer_at_++] = value[i]; + } + } + inline void vprintf_to_buffer_(const char *format, va_list args) { + if (this->is_buffer_full_()) + return; + int remaining = this->buffer_remaining_capacity_(); + int ret = vsnprintf(this->tx_buffer_ + this->tx_buffer_at_, remaining, format, args); + if (ret < 0) { + // Encoding error, do not increment buffer_at + return; + } + if (ret >= remaining) { + // output was too long, truncated + ret = remaining; + } + this->tx_buffer_at_ += ret; + } + inline void printf_to_buffer_(const char *format, ...) { + va_list arg; + va_start(arg, format); + this->vprintf_to_buffer_(format, arg); + va_end(arg); + } uint32_t baud_rate_; - std::vector tx_buffer_; - int global_log_level_{ESPHOME_LOG_LEVEL}; + char *tx_buffer_{nullptr}; + int tx_buffer_at_{0}; + int tx_buffer_size_{0}; UARTSelection uart_{UART_SELECTION_UART0}; HardwareSerial *hw_serial_{nullptr}; struct LogLevelOverride { diff --git a/esphome/components/max31855/max31855.cpp b/esphome/components/max31855/max31855.cpp index 18a00b10d7..0462ed4342 100644 --- a/esphome/components/max31855/max31855.cpp +++ b/esphome/components/max31855/max31855.cpp @@ -82,7 +82,5 @@ void MAX31855Sensor::read_data_() { this->status_clear_warning(); } -bool MAX31855Sensor::is_device_msb_first() { return true; } - } // namespace max31855 } // namespace esphome diff --git a/esphome/components/max31855/max31855.h b/esphome/components/max31855/max31855.h index f9cdf335f1..1d0fc79ac0 100644 --- a/esphome/components/max31855/max31855.h +++ b/esphome/components/max31855/max31855.h @@ -7,7 +7,10 @@ namespace esphome { namespace max31855 { -class MAX31855Sensor : public sensor::Sensor, public PollingComponent, public spi::SPIDevice { +class MAX31855Sensor : public sensor::Sensor, + public PollingComponent, + public spi::SPIDevice { public: void setup() override; void dump_config() override; @@ -16,8 +19,6 @@ class MAX31855Sensor : public sensor::Sensor, public PollingComponent, public sp void update() override; protected: - bool is_device_msb_first() override; - void read_data_(); }; diff --git a/esphome/components/max6675/max6675.cpp b/esphome/components/max6675/max6675.cpp index 8ea7feb963..53442b9cb1 100644 --- a/esphome/components/max6675/max6675.cpp +++ b/esphome/components/max6675/max6675.cpp @@ -48,7 +48,5 @@ void MAX6675Sensor::read_data_() { this->status_clear_warning(); } -bool MAX6675Sensor::is_device_msb_first() { return true; } - } // namespace max6675 } // namespace esphome diff --git a/esphome/components/max6675/max6675.h b/esphome/components/max6675/max6675.h index 48f51fbe11..09bd9df3b8 100644 --- a/esphome/components/max6675/max6675.h +++ b/esphome/components/max6675/max6675.h @@ -7,7 +7,10 @@ namespace esphome { namespace max6675 { -class MAX6675Sensor : public sensor::Sensor, public PollingComponent, public spi::SPIDevice { +class MAX6675Sensor : public sensor::Sensor, + public PollingComponent, + public spi::SPIDevice { public: void setup() override; void dump_config() override; @@ -16,8 +19,6 @@ class MAX6675Sensor : public sensor::Sensor, public PollingComponent, public spi void update() override; protected: - bool is_device_msb_first() override; - void read_data_(); }; diff --git a/esphome/components/max7219/max7219.cpp b/esphome/components/max7219/max7219.cpp index bc3c3ae0c9..db43ff19f6 100644 --- a/esphome/components/max7219/max7219.cpp +++ b/esphome/components/max7219/max7219.cpp @@ -155,7 +155,6 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) { this->send_byte_(a_register, data); this->disable(); } -bool MAX7219Component::is_device_msb_first() { return true; } void MAX7219Component::update() { for (uint8_t i = 0; i < this->num_chips_ * 8; i++) this->buffer_[i] = 0; diff --git a/esphome/components/max7219/max7219.h b/esphome/components/max7219/max7219.h index e2379fa69b..1920268ba4 100644 --- a/esphome/components/max7219/max7219.h +++ b/esphome/components/max7219/max7219.h @@ -16,7 +16,9 @@ class MAX7219Component; using max7219_writer_t = std::function; -class MAX7219Component : public PollingComponent, public spi::SPIDevice { +class MAX7219Component : public PollingComponent, + public spi::SPIDevice { public: void set_writer(max7219_writer_t &&writer); @@ -54,7 +56,6 @@ class MAX7219Component : public PollingComponent, public spi::SPIDevice { protected: void send_byte_(uint8_t a_register, uint8_t data); void send_to_all_(uint8_t a_register, uint8_t data); - bool is_device_msb_first() override; uint8_t intensity_{15}; /// Intensity of the display from 0 to 15 (most) uint8_t num_chips_{1}; diff --git a/esphome/components/mpr121/__init__.py b/esphome/components/mpr121/__init__.py index 9e1bd3726d..b1ef9eaef5 100644 --- a/esphome/components/mpr121/__init__.py +++ b/esphome/components/mpr121/__init__.py @@ -3,6 +3,11 @@ import esphome.config_validation as cv from esphome.components import i2c from esphome.const import CONF_ID +CONF_TOUCH_THRESHOLD = "touch_threshold" +CONF_RELEASE_THRESHOLD = "release_threshold" +CONF_TOUCH_DEBOUNCE = "touch_debounce" +CONF_RELEASE_DEBOUNCE = "release_debounce" + DEPENDENCIES = ['i2c'] AUTO_LOAD = ['binary_sensor'] @@ -13,10 +18,18 @@ MPR121Component = mpr121_ns.class_('MPR121Component', cg.Component, i2c.I2CDevic MULTI_CONF = True CONFIG_SCHEMA = cv.Schema({ cv.GenerateID(): cv.declare_id(MPR121Component), + cv.Optional(CONF_RELEASE_DEBOUNCE, default=0): cv.int_range(min=0, max=7), + cv.Optional(CONF_TOUCH_DEBOUNCE, default=0): cv.int_range(min=0, max=7), + cv.Optional(CONF_TOUCH_THRESHOLD, default=0x0b): cv.int_range(min=0x05, max=0x30), + cv.Optional(CONF_RELEASE_THRESHOLD, default=0x06): cv.int_range(min=0x05, max=0x30), }).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x5A)) def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_touch_debounce(config[CONF_TOUCH_DEBOUNCE])) + cg.add(var.set_release_debounce(config[CONF_RELEASE_DEBOUNCE])) + cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD])) + cg.add(var.set_release_threshold(config[CONF_RELEASE_THRESHOLD])) yield cg.register_component(var, config) yield i2c.register_i2c_device(var, config) diff --git a/esphome/components/mpr121/binary_sensor.py b/esphome/components/mpr121/binary_sensor.py index 100dacd6dd..dddfeb40e1 100644 --- a/esphome/components/mpr121/binary_sensor.py +++ b/esphome/components/mpr121/binary_sensor.py @@ -2,7 +2,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor from esphome.const import CONF_CHANNEL, CONF_ID -from . import mpr121_ns, MPR121Component, CONF_MPR121_ID +from . import mpr121_ns, MPR121Component, CONF_MPR121_ID, CONF_TOUCH_THRESHOLD, \ + CONF_RELEASE_THRESHOLD DEPENDENCIES = ['mpr121'] MPR121Channel = mpr121_ns.class_('MPR121Channel', binary_sensor.BinarySensor) @@ -11,14 +12,20 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ cv.GenerateID(): cv.declare_id(MPR121Channel), cv.GenerateID(CONF_MPR121_ID): cv.use_id(MPR121Component), cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=11), + cv.Optional(CONF_TOUCH_THRESHOLD): cv.int_range(min=0x05, max=0x30), + cv.Optional(CONF_RELEASE_THRESHOLD): cv.int_range(min=0x05, max=0x30), }) def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) yield binary_sensor.register_binary_sensor(var, config) - + hub = yield cg.get_variable(config[CONF_MPR121_ID]) cg.add(var.set_channel(config[CONF_CHANNEL])) - hub = yield cg.get_variable(config[CONF_MPR121_ID]) + if CONF_TOUCH_THRESHOLD in config: + cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD])) + if CONF_RELEASE_THRESHOLD in config: + cg.add(var.set_release_threshold(config[CONF_RELEASE_THRESHOLD])) + cg.add(hub.register_channel(var)) diff --git a/esphome/components/mpr121/mpr121.cpp b/esphome/components/mpr121/mpr121.cpp index c43a6319f4..a24a703306 100644 --- a/esphome/components/mpr121/mpr121.cpp +++ b/esphome/components/mpr121/mpr121.cpp @@ -18,9 +18,11 @@ void MPR121Component::setup() { } // set touch sensitivity for all 12 channels - for (uint8_t i = 0; i < 12; i++) { - this->write_byte(MPR121_TOUCHTH_0 + 2 * i, 12); - this->write_byte(MPR121_RELEASETH_0 + 2 * i, 6); + for (auto *channel : this->channels_) { + this->write_byte(MPR121_TOUCHTH_0 + 2 * channel->channel_, + channel->touch_threshold_.value_or(this->touch_threshold_)); + this->write_byte(MPR121_RELEASETH_0 + 2 * channel->channel_, + channel->release_threshold_.value_or(this->release_threshold_)); } this->write_byte(MPR121_MHDR, 0x01); this->write_byte(MPR121_NHDR, 0x01); @@ -44,6 +46,19 @@ void MPR121Component::setup() { // start with first 5 bits of baseline tracking this->write_byte(MPR121_ECR, 0x8F); } + +void MPR121Component::set_touch_debounce(uint8_t debounce) { + uint8_t mask = debounce << 4; + this->debounce_ &= 0x0f; + this->debounce_ |= mask; +} + +void MPR121Component::set_release_debounce(uint8_t debounce) { + uint8_t mask = debounce & 0x0f; + this->debounce_ &= 0xf0; + this->debounce_ |= mask; +}; + void MPR121Component::dump_config() { ESP_LOGCONFIG(TAG, "MPR121:"); LOG_I2C_DEVICE(this); diff --git a/esphome/components/mpr121/mpr121.h b/esphome/components/mpr121/mpr121.h index d5a2ec3243..3388f04926 100644 --- a/esphome/components/mpr121/mpr121.h +++ b/esphome/components/mpr121/mpr121.h @@ -46,17 +46,29 @@ enum { }; class MPR121Channel : public binary_sensor::BinarySensor { + friend class MPR121Component; + public: void set_channel(uint8_t channel) { channel_ = channel; } void process(uint16_t data) { this->publish_state(static_cast(data & (1 << this->channel_))); } + void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; }; + void set_release_threshold(uint8_t release_threshold) { this->release_threshold_ = release_threshold; }; protected: uint8_t channel_{0}; + optional touch_threshold_{}; + optional release_threshold_{}; }; class MPR121Component : public Component, public i2c::I2CDevice { public: void register_channel(MPR121Channel *channel) { this->channels_.push_back(channel); } + void set_touch_debounce(uint8_t debounce); + void set_release_debounce(uint8_t debounce); + void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; }; + void set_release_threshold(uint8_t release_threshold) { this->release_threshold_ = release_threshold; }; + uint8_t get_touch_threshold() { return this->touch_threshold_; }; + uint8_t get_release_threshold() { return this->release_threshold_; }; void setup() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } @@ -64,6 +76,9 @@ class MPR121Component : public Component, public i2c::I2CDevice { protected: std::vector channels_{}; + uint8_t debounce_{0}; + uint8_t touch_threshold_{}; + uint8_t release_threshold_{}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index c363f93d06..d88187ceec 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -15,7 +15,7 @@ from esphome.const import CONF_AVAILABILITY, CONF_BIRTH_MESSAGE, CONF_BROKER, CO from esphome.core import coroutine_with_priority, coroutine, CORE DEPENDENCIES = ['network'] -AUTO_LOAD = ['json'] +AUTO_LOAD = ['json', 'async_tcp'] def validate_message_just_topic(value): @@ -41,7 +41,8 @@ MQTTClientComponent = mqtt_ns.class_('MQTTClientComponent', cg.Component) MQTTPublishAction = mqtt_ns.class_('MQTTPublishAction', automation.Action) MQTTPublishJsonAction = mqtt_ns.class_('MQTTPublishJsonAction', automation.Action) MQTTMessageTrigger = mqtt_ns.class_('MQTTMessageTrigger', - automation.Trigger.template(cg.std_string)) + automation.Trigger.template(cg.std_string), + cg.Component) MQTTJsonMessageTrigger = mqtt_ns.class_('MQTTJsonMessageTrigger', automation.Trigger.template(cg.JsonObjectConstRef)) MQTTComponent = mqtt_ns.class_('MQTTComponent', cg.Component) @@ -104,7 +105,7 @@ CONFIG_SCHEMA = cv.All(cv.Schema({ cv.Optional(CONF_PORT, default=1883): cv.port, cv.Optional(CONF_USERNAME, default=''): cv.string, cv.Optional(CONF_PASSWORD, default=''): cv.string, - cv.Optional(CONF_CLIENT_ID, default=lambda: CORE.name): cv.string, + cv.Optional(CONF_CLIENT_ID): cv.string, cv.Optional(CONF_DISCOVERY, default=True): cv.Any(cv.boolean, cv.one_of("CLEAN", upper=True)), cv.Optional(CONF_DISCOVERY_RETAIN, default=True): cv.boolean, cv.Optional(CONF_DISCOVERY_PREFIX, default="homeassistant"): cv.publish_topic, @@ -120,7 +121,7 @@ CONFIG_SCHEMA = cv.All(cv.Schema({ cv.Optional(CONF_SSL_FINGERPRINTS): cv.All(cv.only_on_esp8266, cv.ensure_list(validate_fingerprint)), cv.Optional(CONF_KEEPALIVE, default='15s'): cv.positive_time_period_seconds, - cv.Optional(CONF_REBOOT_TIMEOUT, default='5min'): cv.positive_time_period_milliseconds, + cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds, cv.Optional(CONF_ON_MESSAGE): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MQTTMessageTrigger), cv.Required(CONF_TOPIC): cv.subscribe_topic, @@ -153,6 +154,7 @@ def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) yield cg.register_component(var, config) + # https://github.com/marvinroger/async-mqtt-client/blob/master/library.json cg.add_library('AsyncMqttClient', '0.8.2') cg.add_define('USE_MQTT') cg.add_global(mqtt_ns.using) @@ -161,7 +163,8 @@ def to_code(config): cg.add(var.set_broker_port(config[CONF_PORT])) cg.add(var.set_username(config[CONF_USERNAME])) cg.add(var.set_password(config[CONF_PASSWORD])) - cg.add(var.set_client_id(config[CONF_CLIENT_ID])) + if CONF_CLIENT_ID in config: + cg.add(var.set_client_id(config[CONF_CLIENT_ID])) discovery = config[CONF_DISCOVERY] discovery_retain = config[CONF_DISCOVERY_RETAIN] @@ -216,6 +219,7 @@ def to_code(config): cg.add(trig.set_qos(conf[CONF_QOS])) if CONF_PAYLOAD in conf: cg.add(trig.set_payload(conf[CONF_PAYLOAD])) + yield cg.register_component(trig, conf) yield automation.build_automation(trig, [(cg.std_string, 'x')], conf) for conf in config.get(CONF_ON_JSON_MESSAGE, []): diff --git a/esphome/components/mqtt/custom_mqtt_device.cpp b/esphome/components/mqtt/custom_mqtt_device.cpp new file mode 100644 index 0000000000..8b17c5f17f --- /dev/null +++ b/esphome/components/mqtt/custom_mqtt_device.cpp @@ -0,0 +1,30 @@ +#include "custom_mqtt_device.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mqtt { + +static const char *TAG = "mqtt.custom"; + +bool CustomMQTTDevice::publish(const std::string &topic, const std::string &payload, uint8_t qos, bool retain) { + return global_mqtt_client->publish(topic, payload, qos, retain); +} +bool CustomMQTTDevice::publish(const std::string &topic, float value, int8_t number_decimals) { + auto str = value_accuracy_to_string(value, number_decimals); + return this->publish(topic, str); +} +bool CustomMQTTDevice::publish(const std::string &topic, int value) { + char buffer[24]; + sprintf(buffer, "%d", value); + return this->publish(topic, buffer); +} +bool CustomMQTTDevice::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, bool retain) { + return global_mqtt_client->publish_json(topic, f, qos, retain); +} +bool CustomMQTTDevice::publish_json(const std::string &topic, const json::json_build_t &f) { + return this->publish_json(topic, f, 0, false); +} +bool CustomMQTTDevice::is_connected() { return global_mqtt_client != nullptr && global_mqtt_client->is_connected(); } + +} // namespace mqtt +} // namespace esphome diff --git a/esphome/components/mqtt/custom_mqtt_device.h b/esphome/components/mqtt/custom_mqtt_device.h new file mode 100644 index 0000000000..1c8b2e916e --- /dev/null +++ b/esphome/components/mqtt/custom_mqtt_device.h @@ -0,0 +1,217 @@ +#pragma once + +#include "esphome/core/component.h" +#include "mqtt_client.h" + +namespace esphome { +namespace mqtt { + +/** This class is a helper class for custom components that communicate using + * MQTT. It has 5 helper functions that you can use (square brackets indicate optional): + * + * - `subscribe(topic, function_pointer, [qos])` + * - `subscribe_json(topic, function_pointer, [qos])` + * - `publish(topic, payload, [qos], [retain])` + * - `publish_json(topic, payload_builder, [qos], [retain])` + * - `is_connected()` + */ +class CustomMQTTDevice { + public: + /** Subscribe to an MQTT topic with the given Quality of Service. + * + * Example: + * + * ```cpp + * class MyCustomMQTTDevice : public Component, public mqtt:CustomMQTTDevice { + * public: + * void setup() override { + * subscribe("the/topic", &MyCustomMQTTDevice::on_message); + * pinMode(5, OUTPUT); + * } + * + * // topic and payload parameters can be removed if not needed + * // e.g: void on_message() { + * + * void on_message(const std::string &topic, const std::string &payload) { + * // do something with topic and payload + * if (payload == "ON") { + * digitalWrite(5, HIGH); + * } else { + * digitalWrite(5, LOW); + * } + * } + * }; + * ``` + * + * @tparam T A C++ template argument for determining the type of the callback. + * @param topic The topic to subscribe to. Re-subscription on re-connects is automatically handled. + * @param callback The callback (must be a class member) to subscribe with. + * @param qos The Quality of Service to subscribe with. Defaults to 0. + */ + template + void subscribe(const std::string &topic, void (T::*callback)(const std::string &, const std::string &), + uint8_t qos = 0); + + template + void subscribe(const std::string &topic, void (T::*callback)(const std::string &), uint8_t qos = 0); + + template void subscribe(const std::string &topic, void (T::*callback)(), uint8_t qos = 0); + + /** Subscribe to an MQTT topic and call the callback if the payload can be decoded + * as JSON with the given Quality of Service. + * + * Example: + * + * ```cpp + * class MyCustomMQTTDevice : public Component, public mqtt:CustomMQTTDevice { + * public: + * void setup() override { + * subscribe_json("the/topic", &MyCustomMQTTDevice::on_json_message); + * pinMode(5, OUTPUT); + * } + * + * // topic parameter can be remove if not needed: + * // e.g.: void on_json_message(JsonObject &payload) { + * + * void on_json_message(const std::string &topic, JsonObject &payload) { + * // do something with topic and payload + * if (payload["number"] == 1) { + * digitalWrite(5, HIGH); + * } else { + * digitalWrite(5, LOW); + * } + * } + * }; + * ``` + * + * @tparam T A C++ template argument for determining the type of the callback. + * @param topic The topic to subscribe to. Re-subscription on re-connects is automatically handled. + * @param callback The callback (must be a class member) to subscribe with. + * @param qos The Quality of Service to subscribe with. Defaults to 0. + */ + template + void subscribe_json(const std::string &topic, void (T::*callback)(const std::string &, JsonObject &), + uint8_t qos = 0); + + template + void subscribe_json(const std::string &topic, void (T::*callback)(JsonObject &), uint8_t qos = 0); + + /** Publish an MQTT message with the given payload and QoS and retain settings. + * + * Example: + * + * ```cpp + * void in_some_method() { + * publish("the/topic", "The Payload", 0, true); + * } + * ``` + * + * @param topic The topic to publish to. + * @param payload The payload to publish. + * @param qos The Quality of Service to publish with. Defaults to 0 + * @param retain Whether to retain the message. Defaults to false. + */ + bool publish(const std::string &topic, const std::string &payload, uint8_t qos = 0, bool retain = false); + + /** Publish an MQTT message with the given floating point number and number of decimals. + * + * Example: + * + * ```cpp + * void in_some_method() { + * publish("the/topic", 1.0); + * // with two digits after the decimal point + * publish("the/topic", 1.0, 2); + * } + * ``` + * + * @param topic The topic to publish to. + * @param payload The payload to publish. + * @param number_decimals The number of digits after the decimal point to round to, defaults to 3 digits. + */ + bool publish(const std::string &topic, float value, int8_t number_decimals = 3); + + /** Publish an MQTT message with the given integer as payload. + * + * Example: + * + * ```cpp + * void in_some_method() { + * publish("the/topic", 42); + * } + * ``` + * + * @param topic The topic to publish to. + * @param payload The payload to publish. + */ + bool publish(const std::string &topic, int value); + + /** Publish a JSON-encoded MQTT message with the given Quality of Service and retain settings. + * + * Example: + * + * ```cpp + * void in_some_method() { + * publish("the/topic", [=](JsonObject &root) { + * root["the_key"] = "Hello World!"; + * }, 0, false); + * } + * ``` + * + * @param topic The topic to publish to. + * @param payload The payload to publish. + * @param qos The Quality of Service to publish with. + * @param retain Whether to retain the message. + */ + bool publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, bool retain); + + /** Publish a JSON-encoded MQTT message. + * + * Example: + * + * ```cpp + * void in_some_method() { + * publish("the/topic", [=](JsonObject &root) { + * root["the_key"] = "Hello World!"; + * }); + * } + * ``` + * + * @param topic The topic to publish to. + * @param payload The payload to publish. + */ + bool publish_json(const std::string &topic, const json::json_build_t &f); + + /// Check whether the MQTT client is currently connected and messages can be published. + bool is_connected(); +}; + +template +void CustomMQTTDevice::subscribe(const std::string &topic, + void (T::*callback)(const std::string &, const std::string &), uint8_t qos) { + auto f = std::bind(callback, (T *) this, std::placeholders::_1, std::placeholders::_2); + global_mqtt_client->subscribe(topic, f, qos); +} +template +void CustomMQTTDevice::subscribe(const std::string &topic, void (T::*callback)(const std::string &), uint8_t qos) { + auto f = std::bind(callback, (T *) this, std::placeholders::_2); + global_mqtt_client->subscribe(topic, f, qos); +} +template void CustomMQTTDevice::subscribe(const std::string &topic, void (T::*callback)(), uint8_t qos) { + auto f = std::bind(callback, (T *) this); + global_mqtt_client->subscribe(topic, f, qos); +} +template +void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callback)(const std::string &, JsonObject &), + uint8_t qos) { + auto f = std::bind(callback, (T *) this, std::placeholders::_1, std::placeholders::_2); + global_mqtt_client->subscribe_json(topic, f, qos); +} +template +void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callback)(JsonObject &), uint8_t qos) { + auto f = std::bind(callback, (T *) this, std::placeholders::_2); + global_mqtt_client->subscribe_json(topic, f, qos); +} + +} // namespace mqtt +} // namespace esphome diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index e63d6649b3..edabcb398c 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -19,15 +19,19 @@ void MQTTBinarySensorComponent::dump_config() { LOG_MQTT_COMPONENT(true, false) } MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor *binary_sensor) - : MQTTComponent(), binary_sensor_(binary_sensor) {} + : MQTTComponent(), binary_sensor_(binary_sensor) { + if (this->binary_sensor_->is_status_binary_sensor()) { + this->set_custom_state_topic(mqtt::global_mqtt_client->get_availability().topic); + } +} std::string MQTTBinarySensorComponent::friendly_name() const { return this->binary_sensor_->get_name(); } void MQTTBinarySensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->binary_sensor_->get_device_class().empty()) root["device_class"] = this->binary_sensor_->get_device_class(); - if (this->is_status_) + if (this->binary_sensor_->is_status_binary_sensor()) root["payload_on"] = mqtt::global_mqtt_client->get_availability().payload_available; - if (this->is_status_) + if (this->binary_sensor_->is_status_binary_sensor()) root["payload_off"] = mqtt::global_mqtt_client->get_availability().payload_not_available; config.command_topic = false; } @@ -40,13 +44,12 @@ bool MQTTBinarySensorComponent::send_initial_state() { } bool MQTTBinarySensorComponent::is_internal() { return this->binary_sensor_->is_internal(); } bool MQTTBinarySensorComponent::publish_state(bool state) { - if (this->is_status_) + if (this->binary_sensor_->is_status_binary_sensor()) return true; const char *state_s = state ? "ON" : "OFF"; return this->publish(this->get_state_topic_(), state_s); } -void MQTTBinarySensorComponent::set_is_status(bool status) { this->is_status_ = status; } } // namespace mqtt } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_binary_sensor.h b/esphome/components/mqtt/mqtt_binary_sensor.h index 7793caec08..1ca82a947e 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.h +++ b/esphome/components/mqtt/mqtt_binary_sensor.h @@ -35,7 +35,6 @@ class MQTTBinarySensorComponent : public mqtt::MQTTComponent { std::string component_type() const override; binary_sensor::BinarySensor *binary_sensor_; - bool is_status_{false}; }; } // namespace mqtt diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 819dbd6119..e07204d559 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -15,7 +15,10 @@ namespace mqtt { static const char *TAG = "mqtt"; -MQTTClientComponent::MQTTClientComponent() { global_mqtt_client = this; } +MQTTClientComponent::MQTTClientComponent() { + global_mqtt_client = this; + this->credentials_.client_id = App.get_name() + "-" + get_mac_address(); +} // Connection void MQTTClientComponent::setup() { @@ -357,18 +360,19 @@ bool MQTTClientComponent::publish(const std::string &topic, const char *payload, } bool logging_topic = topic == this->log_message_.topic; uint16_t ret = this->mqtt_client_.publish(topic.c_str(), qos, retain, payload, payload_length); - yield(); + delay(0); if (ret == 0 && !logging_topic && this->is_connected()) { - delay(5); + delay(0); ret = this->mqtt_client_.publish(topic.c_str(), qos, retain, payload, payload_length); - yield(); + delay(0); } if (!logging_topic) { if (ret != 0) { ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", topic.c_str(), payload, retain); } else { - ESP_LOGW(TAG, "Publish failed for topic='%s' will retry later..", topic.c_str()); + ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", topic.c_str(), + payload_length); // NOLINT this->status_momentary_warning("publish", 1000); } } diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 8085fbf0f2..48b470cfb2 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -141,7 +141,21 @@ std::string MQTTClimateComponent::friendly_name() const { return this->device_-> bool MQTTClimateComponent::publish_state_() { auto traits = this->device_->get_traits(); // mode - const char *mode_s = climate_mode_to_string(this->device_->mode); + const char *mode_s = ""; + switch (this->device_->mode) { + case CLIMATE_MODE_OFF: + mode_s = "off"; + break; + case CLIMATE_MODE_AUTO: + mode_s = "auto"; + break; + case CLIMATE_MODE_COOL: + mode_s = "cool"; + break; + case CLIMATE_MODE_HEAT: + mode_s = "heat"; + break; + } bool success = true; if (!this->publish(this->get_mode_state_topic(), mode_s)) success = false; diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 13b48630fa..4201d41c44 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" +#include "esphome/core/version.h" namespace esphome { namespace mqtt { @@ -72,11 +73,13 @@ bool MQTTComponent::send_discovery_() { root["command_topic"] = this->get_command_topic_(); if (this->availability_ == nullptr) { - root["availability_topic"] = global_mqtt_client->get_availability().topic; - if (global_mqtt_client->get_availability().payload_available != "online") - root["payload_available"] = global_mqtt_client->get_availability().payload_available; - if (global_mqtt_client->get_availability().payload_not_available != "offline") - root["payload_not_available"] = global_mqtt_client->get_availability().payload_not_available; + if (!global_mqtt_client->get_availability().topic.empty()) { + root["availability_topic"] = global_mqtt_client->get_availability().topic; + if (global_mqtt_client->get_availability().payload_available != "online") + root["payload_available"] = global_mqtt_client->get_availability().payload_available; + if (global_mqtt_client->get_availability().payload_not_available != "offline") + root["payload_not_available"] = global_mqtt_client->get_availability().payload_not_available; + } } else if (!this->availability_->topic.empty()) { root["availability_topic"] = this->availability_->topic; if (this->availability_->payload_available != "online") @@ -146,9 +149,6 @@ void MQTTComponent::set_availability(std::string topic, std::string payload_avai } void MQTTComponent::disable_availability() { this->set_availability("", "", ""); } void MQTTComponent::call_setup() { - // Call component internal setup. - this->setup_internal_(); - if (this->is_internal()) return; @@ -170,8 +170,6 @@ void MQTTComponent::call_setup() { } void MQTTComponent::call_loop() { - this->loop_internal_(); - if (this->is_internal()) return; diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 56d18a3d22..a414c261f0 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -73,6 +73,9 @@ void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCon root["tilt_status_topic"] = this->get_tilt_state_topic(); root["tilt_command_topic"] = this->get_tilt_command_topic(); } + if (traits.get_supports_tilt() && !traits.get_supports_position()) { + config.command_topic = false; + } } std::string MQTTCoverComponent::component_type() const { return "cover"; } diff --git a/esphome/components/my9231/output.py b/esphome/components/my9231/output.py index 9acc8bdcd6..c69649fd5e 100644 --- a/esphome/components/my9231/output.py +++ b/esphome/components/my9231/output.py @@ -1,8 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import output -from esphome.components.my9231 import MY9231OutputComponent from esphome.const import CONF_CHANNEL, CONF_ID +from . import MY9231OutputComponent DEPENDENCIES = ['my9231'] diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index f07bdc80d7..49ea9d237a 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -7,7 +7,7 @@ from esphome.const import CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_METHOD, CONF_NUM_L from esphome.core import CORE neopixelbus_ns = cg.esphome_ns.namespace('neopixelbus') -NeoPixelBusLightOutputBase = neopixelbus_ns.class_('NeoPixelBusLightOutputBase', cg.Component, +NeoPixelBusLightOutputBase = neopixelbus_ns.class_('NeoPixelBusLightOutputBase', light.AddressableLight) NeoPixelRGBLightOutput = neopixelbus_ns.class_('NeoPixelRGBLightOutput', NeoPixelBusLightOutputBase) NeoPixelRGBWLightOutput = neopixelbus_ns.class_('NeoPixelRGBWLightOutput', @@ -101,6 +101,14 @@ ESP8266_METHODS = { ESP32_METHODS = { 'ESP32_I2S_0': 'NeoEsp32I2s0{}Method', 'ESP32_I2S_1': 'NeoEsp32I2s1{}Method', + 'ESP32_RMT_0': 'NeoEsp32Rmt0{}Method', + 'ESP32_RMT_1': 'NeoEsp32Rmt1{}Method', + 'ESP32_RMT_2': 'NeoEsp32Rmt2{}Method', + 'ESP32_RMT_3': 'NeoEsp32Rmt3{}Method', + 'ESP32_RMT_4': 'NeoEsp32Rmt4{}Method', + 'ESP32_RMT_5': 'NeoEsp32Rmt5{}Method', + 'ESP32_RMT_6': 'NeoEsp32Rmt6{}Method', + 'ESP32_RMT_7': 'NeoEsp32Rmt7{}Method', 'BIT_BANG': 'NeoEsp32BitBang{}Method', } @@ -160,4 +168,5 @@ def to_code(config): cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE]))) - cg.add_library('NeoPixelBus', '2.4.1') + # https://github.com/Makuna/NeoPixelBus/blob/master/library.json + cg.add_library('NeoPixelBus', '2.5.0') diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index 25e10d9bbd..5e8097187e 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -48,7 +48,7 @@ enum class ESPNeoPixelOrder { }; template -class NeoPixelBusLightOutputBase : public Component, public light::AddressableLight { +class NeoPixelBusLightOutputBase : public light::AddressableLight { public: NeoPixelBus *get_controller() const { return this->controller_; } diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index d8fe3762c9..92b41a88af 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -23,6 +23,13 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Set the text of a component to a static string. * @param component The component name. * @param text The static text to set. + * + * Example: + * ```cpp + * it.set_component_text("textview", "Hello World!"); + * ``` + * + * This will set the `txt` property of the component `textview` to `Hello World`. */ void set_component_text(const char *component, const char *text); /** @@ -30,18 +37,41 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param component The component name. * @param format The printf-style format string. * @param ... The arguments to the format. + * + * Example: + * ```cpp + * it.set_component_text_printf("textview", "The uptime is: %.0f", id(uptime_sensor).state); + * ``` + * + * This will change the text on the component named `textview` to `The uptime is:` Then the value of `uptime_sensor`. + * with zero decimals of accuracy (whole number). + * For example when `uptime_sensor` = 506, then, `The uptime is: 506` will be displayed. */ void set_component_text_printf(const char *component, const char *format, ...) __attribute__((format(printf, 3, 4))); /** * Set the integer value of a component * @param component The component name. * @param value The value to set. + * + * Example: + * ```cpp + * it.set_component_value("gauge", 50); + * ``` + * + * This will change the property `value` of the component `gauge` to 50. */ void set_component_value(const char *component, int value); /** * Set the picture of an image component. * @param component The component name. * @param value The picture name. + * + * Example: + * ```cpp + * it.set_component_picture("pic", "4"); + * ``` + * + * This will change the image of the component `pic` to the image with ID `4`. */ void set_component_picture(const char *component, const char *picture) { this->send_command_printf("%s.val=%s", component, picture); @@ -50,24 +80,61 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Set the background color of a component. * @param component The component name. * @param color The color (as a string). + * + * Example: + * ```cpp + * it.set_component_background_color("button", "17013"); + * ``` + * + * This will change the background color of the component `button` to blue. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. */ void set_component_background_color(const char *component, const char *color); /** * Set the pressed background color of a component. * @param component The component name. * @param color The color (as a string). + * + * Example: + * ```cpp + * it.set_component_pressed_background_color("button", "17013"); + * ``` + * + * This will change the pressed background color of the component `button` to blue. This is the background color that + * is shown when the component is pressed. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. */ void set_component_pressed_background_color(const char *component, const char *color); /** * Set the font color of a component. * @param component The component name. * @param color The color (as a string). + * + * Example: + * ```cpp + * it.set_component_font_color("textview", "17013"); + * ``` + * + * This will change the font color of the component `textview` to a blue color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. */ void set_component_font_color(const char *component, const char *color); /** * Set the pressed font color of a component. * @param component The component name. * @param color The color (as a string). + * + * Example: + * ```cpp + * it.set_component_pressed_font_color("button", "17013"); + * ``` + * + * This will change the pressed font color of the component `button` to a blue color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. */ void set_component_pressed_font_color(const char *component, const char *color); /** @@ -75,12 +142,26 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param component The component name. * @param x The x coordinate. * @param y The y coordinate. + * + * Example: + * ```cpp + * it.set_component_coordinates("pic", 55, 100); + * ``` + * + * This will move the position of the component `pic` to the x coordinate `55` and y coordinate `100`. */ void set_component_coordinates(const char *component, int x, int y); /** * Set the font id for a component. * @param component The component name. * @param font_id The ID of the font (number). + * + * Example: + * ```cpp + * it.set_component_font("textview", "3"); + * ``` + * + * Changes the font of the component named `textveiw`. Font IDs are set in the Nextion Editor. */ void set_component_font(const char *component, uint8_t font_id); #ifdef USE_TIME @@ -94,26 +175,61 @@ class Nextion : public PollingComponent, public uart::UARTDevice { /** * Show the page with a given name. * @param page The name of the page. + * + * Example: + * ```cpp + * it.goto_page("main"); + * ``` + * + * Switches to the page named `main`. Pages are named in the Nextion Editor. */ void goto_page(const char *page); /** * Hide a component. * @param component The component name. + * + * Example: + * ```cpp + * hide_component("button"); + * ``` + * + * Hides the component named `button`. */ void hide_component(const char *component); /** * Show a component. * @param component The component name. + * + * Example: + * ```cpp + * show_component("button"); + * ``` + * + * Shows the component named `button`. */ void show_component(const char *component); /** * Enable touch for a component. * @param component The component name. + * + * Example: + * ```cpp + * enable_component_touch("button"); + * ``` + * + * Enables touch for component named `button`. */ void enable_component_touch(const char *component); /** * Disable touch for a component. * @param component The component name. + * + * Example: + * ```cpp + * disable_component_touch("button"); + * ``` + * + * Disables touch for component named `button`. */ void disable_component_touch(const char *component); /** @@ -128,6 +244,13 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param picture_id The picture id. * @param x1 The x coordinate. * @param y1 The y coordniate. + * + * Example: + * ```cpp + * display_picture(2, 15, 25); + * ``` + * + * Displays the picture who has the id `2` at the x coordinates `15` and y coordinates `25`. */ void display_picture(int picture_id, int x_start, int y_start); /** @@ -137,6 +260,15 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param width The width to draw. * @param height The height to draw. * @param color The color to draw with (as a string). + * + * Example: + * ```cpp + * fill_area(50, 50, 100, 100, "17013"); + * ``` + * + * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with + * the color of blue. Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to + * convert color codes to Nextion HMI colors */ void fill_area(int x1, int y1, int width, int height, const char *color); /** @@ -146,6 +278,16 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param x2 The ending x coordinate. * @param y2 The ending y coordinate. * @param color The color to draw with (as a string). + * + * Example: + * ```cpp + * it.line(50, 50, 75, 75, "17013"); + * ``` + * + * Makes a line that starts at x coordinate `50` and y coordinate `50` and ends at x coordinate `75` and y coordinate + * `75` with the color of blue. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. */ void line(int x1, int y1, int x2, int y2, const char *color); /** @@ -155,6 +297,16 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param width The width of the rectangle. * @param height The height of the rectangle. * @param color The color to draw with (as a string). + * + * Example: + * ```cpp + * it.rectangle(25, 35, 40, 50, "17013"); + * ``` + * + * Makes a outline of a rectangle that starts at x coordinate `25` and y coordinate `35` and has a width of `40` and a + * length of `50` with color of blue. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. */ void rectangle(int x1, int y1, int width, int height, const char *color); /** @@ -171,17 +323,41 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param center_y The center y coordinate. * @param radius The circle radius. * @param color The color to draw with (as a string). + * + * Example: + * ```cpp + * it.filled_cricle(25, 25, 10, "17013"); + * ``` + * + * Makes a filled circle at the x cordinates `25` and y coordinate `25` with a radius of `10` with a color of blue. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. */ void filled_circle(int center_x, int center_y, int radius, const char *color); /** Set the brightness of the backlight. * * @param brightness The brightness, from 0 to 100. + * + * Example: + * ```cpp + * it.set_backlight_brightness(30); + * ``` + * + * Changes the brightness of the display to 30%. */ void set_backlight_brightness(uint8_t brightness); /** * Set the touch sleep timeout of the display. * @param timeout Timeout in seconds. + * + * Example: + * ```cpp + * it.set_touch_sleep_timeout(30); + * ``` + * + * After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up + * `thup`. */ void set_touch_sleep_timeout(uint16_t timeout); diff --git a/esphome/components/ntc/__init__.py b/esphome/components/ntc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/ntc/ntc.cpp b/esphome/components/ntc/ntc.cpp new file mode 100644 index 0000000000..1b5c5182c7 --- /dev/null +++ b/esphome/components/ntc/ntc.cpp @@ -0,0 +1,31 @@ +#include "ntc.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ntc { + +static const char *TAG = "ntc"; + +void NTC::setup() { + this->sensor_->add_on_state_callback([this](float value) { this->process_(value); }); + if (this->sensor_->has_state()) + this->process_(this->sensor_->state); +} +void NTC::dump_config() { LOG_SENSOR("", "NTC Sensor", this) } +float NTC::get_setup_priority() const { return setup_priority::DATA; } +void NTC::process_(float value) { + if (isnan(value)) { + this->publish_state(NAN); + return; + } + + float lr = logf(value); + float v = this->a_ + this->b_ * lr + this->c_ * lr * lr * lr; + float temp = 1 / v - 273.15f; + + ESP_LOGD(TAG, "'%s' - Temperature: %.1f°C", this->name_.c_str(), temp); + this->publish_state(temp); +} + +} // namespace ntc +} // namespace esphome diff --git a/esphome/components/ntc/ntc.h b/esphome/components/ntc/ntc.h new file mode 100644 index 0000000000..9d6b37412d --- /dev/null +++ b/esphome/components/ntc/ntc.h @@ -0,0 +1,29 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace ntc { + +class NTC : public Component, public sensor::Sensor { + public: + void set_sensor(Sensor *sensor) { sensor_ = sensor; } + void set_a(float a) { a_ = a; } + void set_b(float b) { b_ = b; } + void set_c(float c) { c_ = c; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + + protected: + void process_(float value); + + sensor::Sensor *sensor_; + float a_; + float b_; + float c_; +}; + +} // namespace ntc +} // namespace esphome diff --git a/esphome/components/ntc/sensor.py b/esphome/components/ntc/sensor.py new file mode 100644 index 0000000000..a528183ac8 --- /dev/null +++ b/esphome/components/ntc/sensor.py @@ -0,0 +1,120 @@ +# coding=utf-8 +from math import log + +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.components import sensor +from esphome.const import UNIT_CELSIUS, ICON_THERMOMETER, CONF_SENSOR, CONF_TEMPERATURE, \ + CONF_VALUE, CONF_CALIBRATION, CONF_ID + +ntc_ns = cg.esphome_ns.namespace('ntc') +NTC = ntc_ns.class_('NTC', cg.Component, sensor.Sensor) + +CONF_B_CONSTANT = 'b_constant' +CONF_REFERENCE_TEMPERATURE = 'reference_temperature' +CONF_REFERENCE_RESISTANCE = 'reference_resistance' +CONF_A = 'a' +CONF_B = 'b' +CONF_C = 'c' +ZERO_POINT = 273.15 + + +def validate_calibration_parameter(value): + if isinstance(value, dict): + return cv.Schema({ + cv.Required(CONF_TEMPERATURE): cv.float_, + cv.Required(CONF_VALUE): cv.float_, + })(value) + + value = cv.string(value) + parts = value.split('->') + if len(parts) != 2: + raise cv.Invalid(u"Calibration parameter must be of form 3000 -> 23°C") + voltage = cv.resistance(parts[0].strip()) + temperature = cv.temperature(parts[1].strip()) + return validate_calibration_parameter({ + CONF_TEMPERATURE: temperature, + CONF_VALUE: voltage, + }) + + +def calc_steinhart_hart(value): + r1 = value[0][CONF_VALUE] + r2 = value[1][CONF_VALUE] + r3 = value[2][CONF_VALUE] + t1 = value[0][CONF_TEMPERATURE] + ZERO_POINT + t2 = value[1][CONF_TEMPERATURE] + ZERO_POINT + t3 = value[2][CONF_TEMPERATURE] + ZERO_POINT + + l1 = log(r1) + l2 = log(r2) + l3 = log(r3) + + y1 = 1/t1 + y2 = 1/t2 + y3 = 1/t3 + + g2 = (y2-y1)/(l2-l1) + g3 = (y3-y1)/(l3-l1) + + c = (g3-g2)/(l3-l2) * 1/(l1+l2+l3) + b = g2 - c*(l1*l1 + l1*l2 + l2*l2) + a = y1 - (b + l1*l1*c) * l1 + return a, b, c + + +def calc_b(value): + beta = value[CONF_B_CONSTANT] + t0 = value[CONF_REFERENCE_TEMPERATURE] + ZERO_POINT + r0 = value[CONF_REFERENCE_RESISTANCE] + + a = (1/t0) - (1/beta) * log(r0) + b = 1/beta + c = 0 + + return a, b, c + + +def process_calibration(value): + if isinstance(value, dict): + value = cv.Schema({ + cv.Required(CONF_B_CONSTANT): cv.float_, + cv.Required(CONF_REFERENCE_TEMPERATURE): cv.temperature, + cv.Required(CONF_REFERENCE_RESISTANCE): cv.resistance, + })(value) + a, b, c = calc_b(value) + elif isinstance(value, list): + if len(value) != 3: + raise cv.Invalid("Steinhart–Hart Calibration must consist of exactly three values") + value = cv.Schema([validate_calibration_parameter])(value) + a, b, c = calc_steinhart_hart(value) + else: + raise cv.Invalid("Calibration parameter accepts either a list for steinhart-hart " + "calibration, or mapping for b-constant calibration, " + "not {}".format(type(value))) + + return { + CONF_A: a, + CONF_B: b, + CONF_C: c, + } + + +CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ + cv.GenerateID(): cv.declare_id(NTC), + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Required(CONF_CALIBRATION): process_calibration, +}).extend(cv.COMPONENT_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield sensor.register_sensor(var, config) + + sens = yield cg.get_variable(config[CONF_SENSOR]) + cg.add(var.set_sensor(sens)) + calib = config[CONF_CALIBRATION] + cg.add(var.set_a(calib[CONF_A])) + cg.add(var.set_b(calib[CONF_B])) + cg.add(var.set_c(calib[CONF_C])) diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index e290e57baf..869de777d6 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -22,6 +22,8 @@ def to_code(config): cg.add(var.set_port(config[CONF_PORT])) cg.add(var.set_auth_password(config[CONF_PASSWORD])) + yield cg.register_component(var, config) + if config[CONF_SAFE_MODE]: cg.add(var.start_safe_mode()) @@ -29,6 +31,3 @@ def to_code(config): cg.add_library('Update', None) elif CORE.is_esp32: cg.add_library('Hash', None) - - # Register at end for safe mode - yield cg.register_component(var, config) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index ee2d67c85e..7a00c5bb41 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -1,4 +1,4 @@ -#include "esphome/components/ota/ota_component.h" +#include "ota_component.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" @@ -358,7 +358,7 @@ void OTAComponent::start_safe_mode(uint8_t num_attempts, uint32_t enable_time) { this->safe_mode_start_time_ = millis(); this->safe_mode_enable_time_ = enable_time; this->safe_mode_num_attempts_ = num_attempts; - this->rtc_ = global_preferences.make_preference(233825507UL); + this->rtc_ = global_preferences.make_preference(233825507UL, false); this->safe_mode_rtc_value_ = this->read_rtc_(); ESP_LOGCONFIG(TAG, "There have been %u suspected unsuccessful boot attempts.", this->safe_mode_rtc_value_); @@ -369,19 +369,18 @@ void OTAComponent::start_safe_mode(uint8_t num_attempts, uint32_t enable_time) { ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode."); this->status_set_error(); - network_setup(); - this->call_setup(); + this->set_timeout(enable_time, []() { + ESP_LOGE(TAG, "No OTA attempt made, restarting."); + App.reboot(); + }); + + App.setup(); ESP_LOGI(TAG, "Waiting for OTA attempt."); - uint32_t begin = millis(); - while ((millis() - begin) < enable_time) { - this->call_loop(); - network_tick(); - App.feed_wdt(); - yield(); + + while (true) { + App.loop(); } - ESP_LOGE(TAG, "No OTA attempt made, restarting."); - App.reboot(); } else { // increment counter this->write_rtc_(this->safe_mode_rtc_value_ + 1); diff --git a/esphome/components/partition/light.py b/esphome/components/partition/light.py index cfc709854f..62434e7a19 100644 --- a/esphome/components/partition/light.py +++ b/esphome/components/partition/light.py @@ -33,4 +33,5 @@ def to_code(config): conf[CONF_TO] - conf[CONF_FROM] + 1)) var = cg.new_Pvariable(config[CONF_OUTPUT_ID], segments) + yield cg.register_component(var, config) yield light.register_light(var, config) diff --git a/esphome/components/partition/light_partition.h b/esphome/components/partition/light_partition.h index b68e3404f1..8085c43720 100644 --- a/esphome/components/partition/light_partition.h +++ b/esphome/components/partition/light_partition.h @@ -24,7 +24,7 @@ class AddressableSegment { int32_t dst_offset_; }; -class PartitionLightOutput : public light::AddressableLight, public Component { +class PartitionLightOutput : public light::AddressableLight { public: explicit PartitionLightOutput(std::vector segments) : segments_(segments) { int32_t off = 0; diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index 07a41444ce..93000a7421 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -335,7 +335,6 @@ bool PN532::wait_ready_() { return true; } -bool PN532::is_device_msb_first() { return false; } void PN532::dump_config() { ESP_LOGCONFIG(TAG, "PN532:"); switch (this->error_code_) { diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index d349c7a150..49d5878265 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -11,7 +11,9 @@ namespace pn532 { class PN532BinarySensor; class PN532Trigger; -class PN532 : public PollingComponent, public spi::SPIDevice { +class PN532 : public PollingComponent, + public spi::SPIDevice { public: void setup() override; @@ -26,8 +28,6 @@ class PN532 : public PollingComponent, public spi::SPIDevice { void register_trigger(PN532Trigger *trig) { this->triggers_.push_back(trig); } protected: - bool is_device_msb_first() override; - /// Write the full command given in data to the PN532 void pn532_write_command_(const std::vector &data); bool pn532_write_command_check_ack_(const std::vector &data); diff --git a/esphome/components/pulse_counter/sensor.py b/esphome/components/pulse_counter/sensor.py index 636147c138..e73bc36036 100644 --- a/esphome/components/pulse_counter/sensor.py +++ b/esphome/components/pulse_counter/sensor.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor from esphome.const import CONF_COUNT_MODE, CONF_FALLING_EDGE, CONF_ID, CONF_INTERNAL_FILTER, \ - CONF_PIN, CONF_RISING_EDGE, CONF_UPDATE_INTERVAL, CONF_NUMBER, \ + CONF_PIN, CONF_RISING_EDGE, CONF_NUMBER, \ ICON_PULSE, UNIT_PULSES_PER_MINUTE from esphome.core import CORE @@ -49,7 +49,6 @@ CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PULSES_PER_MINUTE, ICON_PULSE, 2).exte cv.Required(CONF_FALLING_EDGE): COUNT_MODE_SCHEMA, }), cv.Optional(CONF_INTERNAL_FILTER, default='13us'): validate_internal_filter, - cv.Optional(CONF_UPDATE_INTERVAL, default='60s'): cv.update_interval, }).extend(cv.polling_component_schema('60s')) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 8b6f5b1623..31d58c1cc0 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -2,7 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.components import binary_sensor -from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID, CONF_NBITS, CONF_ADDRESS, \ +from esphome.const import CONF_DATA, CONF_TRIGGER_ID, CONF_NBITS, CONF_ADDRESS, \ CONF_COMMAND, CONF_CODE, CONF_PULSE_LENGTH, CONF_SYNC, CONF_ZERO, CONF_ONE, CONF_INVERTED, \ CONF_PROTOCOL, CONF_GROUP, CONF_DEVICE, CONF_STATE, CONF_CHANNEL, CONF_FAMILY, CONF_REPEAT, \ CONF_WAIT_TIME, CONF_TIMES, CONF_TYPE_ID, CONF_CARRIER_FREQUENCY @@ -83,14 +83,20 @@ def register_dumper(name, type): return decorator +def validate_repeat(value): + if isinstance(value, dict): + return cv.Schema({ + cv.Required(CONF_TIMES): cv.templatable(cv.positive_int), + cv.Optional(CONF_WAIT_TIME, default='10ms'): + cv.templatable(cv.positive_time_period_microseconds), + })(value) + return validate_repeat({CONF_TIMES: value}) + + def register_action(name, type_, schema): validator = templatize(schema).extend({ cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterBase), - cv.Optional(CONF_REPEAT): cv.Schema({ - cv.Required(CONF_TIMES): cv.templatable(cv.positive_int), - cv.Optional(CONF_WAIT_TIME, default='10ms'): - cv.templatable(cv.positive_time_period_milliseconds), - }), + cv.Optional(CONF_REPEAT): validate_repeat, }) registerer = automation.register_action('remote_transmitter.transmit_{}'.format(name), type_, validator) @@ -344,7 +350,7 @@ RAW_SCHEMA = cv.Schema({ @register_binary_sensor('raw', RawBinarySensor, RAW_SCHEMA) def raw_binary_sensor(var, config): code_ = config[CONF_CODE] - arr = cg.progmem_array(config[CONF_ID], code_) + arr = cg.progmem_array(config[CONF_CODE_STORAGE_ID], code_) cg.add(var.set_data(arr)) cg.add(var.set_len(len(code_))) @@ -440,6 +446,22 @@ def validate_rc_switch_code(value): return value +def validate_rc_switch_raw_code(value): + if not isinstance(value, (str, text_type)): + raise cv.Invalid("All RCSwitch raw codes must be in quotes ('')") + for c in value: + if c not in ('0', '1', 'x'): + raise cv.Invalid( + "Invalid RCSwitch raw code character '{}'.Only '0', '1' and 'x' are allowed" + .format(c)) + if len(value) > 32: + raise cv.Invalid("Maximum length for RCSwitch raw codes is 32, code '{}' has length {}" + "".format(value, len(value))) + if not value: + raise cv.Invalid("RCSwitch raw code must not be empty") + return value + + def build_rc_switch_protocol(config): if isinstance(config, int): return rc_switch_protocols[config] @@ -451,7 +473,7 @@ def build_rc_switch_protocol(config): RC_SWITCH_RAW_SCHEMA = cv.Schema({ - cv.Required(CONF_CODE): validate_rc_switch_code, + cv.Required(CONF_CODE): validate_rc_switch_raw_code, cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, }) RC_SWITCH_TYPE_A_SCHEMA = cv.Schema({ @@ -484,7 +506,7 @@ RC_SWITCH_TRANSMITTER = cv.Schema({ cv.Optional(CONF_REPEAT, default={CONF_TIMES: 5}): cv.Schema({ cv.Required(CONF_TIMES): cv.templatable(cv.positive_int), cv.Optional(CONF_WAIT_TIME, default='10ms'): - cv.templatable(cv.positive_time_period_milliseconds), + cv.templatable(cv.positive_time_period_microseconds), }), }) diff --git a/esphome/components/remote_base/rc_switch_protocol.cpp b/esphome/components/remote_base/rc_switch_protocol.cpp index d983ffeb23..a04b13ecfd 100644 --- a/esphome/components/remote_base/rc_switch_protocol.cpp +++ b/esphome/components/remote_base/rc_switch_protocol.cpp @@ -113,7 +113,7 @@ bool RCSwitchBase::decode(RemoteReceiveData &src, uint32_t *out_data, uint8_t *o this->expect_sync(src); *out_data = 0; - for (*out_nbits = 1; *out_nbits < 32; *out_nbits += 1) { + for (*out_nbits = 0; *out_nbits < 32; *out_nbits += 1) { if (this->expect_zero(src)) { *out_data <<= 1; *out_data |= 0; @@ -121,7 +121,6 @@ bool RCSwitchBase::decode(RemoteReceiveData &src, uint32_t *out_data, uint8_t *o *out_data <<= 1; *out_data |= 1; } else { - *out_nbits -= 1; return *out_nbits >= 8; } } @@ -217,13 +216,22 @@ uint32_t decode_binary_string(const std::string &data) { return ret; } +uint32_t decode_binary_string_mask(const std::string &data) { + uint32_t ret = 0; + for (char c : data) { + ret <<= 1UL; + ret |= (c != 'x'); + } + return ret; +} + bool RCSwitchRawReceiver::matches(RemoteReceiveData src) { uint32_t decoded_code; uint8_t decoded_nbits; if (!this->protocol_.decode(src, &decoded_code, &decoded_nbits)) return false; - return decoded_nbits == this->nbits_ && decoded_code == this->code_; + return decoded_nbits == this->nbits_ && (decoded_code & this->mask_) == (this->code_ & this->mask_); } bool RCSwitchDumper::dump(RemoteReceiveData src) { for (uint8_t i = 1; i <= 7; i++) { @@ -232,7 +240,7 @@ bool RCSwitchDumper::dump(RemoteReceiveData src) { uint8_t out_nbits; RCSwitchBase *protocol = &rc_switch_protocols[i]; if (protocol->decode(src, &out_data, &out_nbits) && out_nbits >= 3) { - char buffer[32]; + char buffer[33]; for (uint8_t j = 0; j < out_nbits; j++) buffer[j] = (out_data & (1 << (out_nbits - j - 1))) ? '1' : '0'; diff --git a/esphome/components/remote_base/rc_switch_protocol.h b/esphome/components/remote_base/rc_switch_protocol.h index 728561c140..d937efbd25 100644 --- a/esphome/components/remote_base/rc_switch_protocol.h +++ b/esphome/components/remote_base/rc_switch_protocol.h @@ -55,6 +55,8 @@ extern RCSwitchBase rc_switch_protocols[8]; uint32_t decode_binary_string(const std::string &data); +uint32_t decode_binary_string_mask(const std::string &data); + template class RCSwitchRawAction : public RemoteTransmitterActionBase { public: TEMPLATABLE_VALUE(RCSwitchBase, protocol); @@ -167,6 +169,7 @@ class RCSwitchRawReceiver : public RemoteReceiverBinarySensorBase { void set_code(uint32_t code) { this->code_ = code; } void set_code(const std::string &code) { this->code_ = decode_binary_string(code); + this->mask_ = decode_binary_string_mask(code); this->nbits_ = code.size(); } void set_nbits(uint8_t nbits) { this->nbits_ = nbits; } @@ -192,6 +195,7 @@ class RCSwitchRawReceiver : public RemoteReceiverBinarySensorBase { RCSwitchBase protocol_; uint32_t code_; + uint32_t mask_{0xFFFFFFFF}; uint8_t nbits_; }; diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index d8eb1be356..6035e2fd57 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -293,7 +293,7 @@ template class RemoteReceiverBinarySensor : public Remot bool matches(RemoteReceiveData src) override { auto proto = T(); auto res = proto.decode(src); - return res.has_value(); + return res.has_value() && *res == this->data_; } public: diff --git a/esphome/components/remote_base/sony_protocol.cpp b/esphome/components/remote_base/sony_protocol.cpp index 2b50845f32..97318f6608 100644 --- a/esphome/components/remote_base/sony_protocol.cpp +++ b/esphome/components/remote_base/sony_protocol.cpp @@ -31,7 +31,7 @@ optional SonyProtocol::decode(RemoteReceiveData src) { .nbits = 0, }; if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) - return out; + return {}; for (; out.nbits < 20; out.nbits++) { uint32_t bit; diff --git a/esphome/components/resistance/__init__.py b/esphome/components/resistance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/resistance/resistance_sensor.cpp b/esphome/components/resistance/resistance_sensor.cpp new file mode 100644 index 0000000000..7c48bbbc08 --- /dev/null +++ b/esphome/components/resistance/resistance_sensor.cpp @@ -0,0 +1,42 @@ +#include "resistance_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace resistance { + +static const char *TAG = "resistance"; + +void ResistanceSensor::dump_config() { + LOG_SENSOR("", "Resistance Sensor", this); + ESP_LOGCONFIG(TAG, " Configuration: %s", this->configuration_ == UPSTREAM ? "UPSTREAM" : "DOWNSTREAM"); + ESP_LOGCONFIG(TAG, " Resistor: %.2fΩ", this->resistor_); + ESP_LOGCONFIG(TAG, " Reference Voltage: %.1fV", this->reference_voltage_); +} +void ResistanceSensor::process_(float value) { + if (isnan(value)) { + this->publish_state(NAN); + return; + } + float res = 0; + switch (this->configuration_) { + case UPSTREAM: + if (value == 0.0f) + res = NAN; + else + res = (this->reference_voltage_ - value) / value; + break; + case DOWNSTREAM: + if (value == this->reference_voltage_) + res = NAN; + else + res = value / (this->reference_voltage_ - value); + break; + } + + res *= this->resistor_; + ESP_LOGD(TAG, "'%s' - Resistance %.1fΩ", this->name_.c_str(), res); + this->publish_state(res); +} + +} // namespace resistance +} // namespace esphome diff --git a/esphome/components/resistance/resistance_sensor.h b/esphome/components/resistance/resistance_sensor.h new file mode 100644 index 0000000000..b57f90b59c --- /dev/null +++ b/esphome/components/resistance/resistance_sensor.h @@ -0,0 +1,38 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace resistance { + +enum ResistanceConfiguration { + UPSTREAM, + DOWNSTREAM, +}; + +class ResistanceSensor : public Component, public sensor::Sensor { + public: + void set_sensor(Sensor *sensor) { sensor_ = sensor; } + void set_configuration(ResistanceConfiguration configuration) { configuration_ = configuration; } + void set_resistor(float resistor) { resistor_ = resistor; } + void set_reference_voltage(float reference_voltage) { reference_voltage_ = reference_voltage; } + + void setup() override { + this->sensor_->add_on_state_callback([this](float value) { this->process_(value); }); + if (this->sensor_->has_state()) + this->process_(this->sensor_->state); + } + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void process_(float value); + sensor::Sensor *sensor_; + ResistanceConfiguration configuration_; + float resistor_; + float reference_voltage_; +}; + +} // namespace resistance +} // namespace esphome diff --git a/esphome/components/resistance/sensor.py b/esphome/components/resistance/sensor.py new file mode 100644 index 0000000000..fb245bcdf0 --- /dev/null +++ b/esphome/components/resistance/sensor.py @@ -0,0 +1,37 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import CONF_SENSOR, UNIT_OHM, ICON_FLASH, CONF_ID + +resistance_ns = cg.esphome_ns.namespace('resistance') +ResistanceSensor = resistance_ns.class_('ResistanceSensor', cg.Component, sensor.Sensor) + +CONF_REFERENCE_VOLTAGE = 'reference_voltage' +CONF_CONFIGURATION = 'configuration' +CONF_RESISTOR = 'resistor' + +ResistanceConfiguration = resistance_ns.enum('ResistanceConfiguration') +CONFIGURATIONS = { + 'DOWNSTREAM': ResistanceConfiguration.DOWNSTREAM, + 'UPSTREAM': ResistanceConfiguration.UPSTREAM, +} + +CONFIG_SCHEMA = sensor.sensor_schema(UNIT_OHM, ICON_FLASH, 1).extend({ + cv.GenerateID(): cv.declare_id(ResistanceSensor), + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Required(CONF_CONFIGURATION): cv.enum(CONFIGURATIONS, upper=True), + cv.Required(CONF_RESISTOR): cv.resistance, + cv.Optional(CONF_REFERENCE_VOLTAGE, default='3.3V'): cv.voltage, +}).extend(cv.COMPONENT_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield sensor.register_sensor(var, config) + + sens = yield cg.get_variable(config[CONF_SENSOR]) + cg.add(var.set_sensor(sens)) + cg.add(var.set_configuration(config[CONF_CONFIGURATION])) + cg.add(var.set_resistor(config[CONF_RESISTOR])) + cg.add(var.set_reference_voltage(config[CONF_REFERENCE_VOLTAGE])) diff --git a/esphome/components/rgbww/light.py b/esphome/components/rgbww/light.py index 143542f49c..c804eed942 100644 --- a/esphome/components/rgbww/light.py +++ b/esphome/components/rgbww/light.py @@ -37,4 +37,4 @@ def to_code(config): wwhite = yield cg.get_variable(config[CONF_WARM_WHITE]) cg.add(var.set_warm_white(wwhite)) - cg.add(var.set_warm_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE])) + cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE])) diff --git a/esphome/components/rgbww/rgbww_light_output.h b/esphome/components/rgbww/rgbww_light_output.h index dc1f09e09b..ef9e99a3eb 100644 --- a/esphome/components/rgbww/rgbww_light_output.h +++ b/esphome/components/rgbww/rgbww_light_output.h @@ -22,6 +22,8 @@ class RGBWWLightOutput : public light::LightOutput { traits.set_supports_rgb(true); traits.set_supports_rgb_white_value(true); traits.set_supports_color_temperature(true); + traits.set_min_mireds(this->cold_white_temperature_); + traits.set_max_mireds(this->warm_white_temperature_); return traits; } void write_state(light::LightState *state) override { diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index d88329d064..949a301661 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -101,15 +101,15 @@ void RotaryEncoderSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up Rotary Encoder '%s'...", this->name_.c_str()); this->pin_a_->setup(); this->store_.pin_a = this->pin_a_->to_isr(); - this->pin_a_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, CHANGE); - this->pin_b_->setup(); this->store_.pin_b = this->pin_b_->to_isr(); - this->pin_b_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, CHANGE); if (this->pin_i_ != nullptr) { this->pin_i_->setup(); } + + this->pin_a_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, CHANGE); + this->pin_b_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, CHANGE); } void RotaryEncoderSensor::dump_config() { LOG_SENSOR("", "Rotary Encoder", this); diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index 3eb2a5ca45..fb4881e18b 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -36,7 +36,7 @@ CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_STEPS, ICON_ROTATE_RIGHT, 0).ex pins.validate_has_interrupt), cv.Required(CONF_PIN_B): cv.All(pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt), - cv.Optional(CONF_PIN_RESET): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_PIN_RESET): pins.internal_gpio_output_pin_schema, cv.Optional(CONF_RESOLUTION, default=1): cv.enum(RESOLUTIONS, int=True), cv.Optional(CONF_MIN_VALUE): cv.int_, cv.Optional(CONF_MAX_VALUE): cv.int_, @@ -50,7 +50,7 @@ def to_code(config): pin_a = yield cg.gpio_pin_expression(config[CONF_PIN_A]) cg.add(var.set_pin_a(pin_a)) pin_b = yield cg.gpio_pin_expression(config[CONF_PIN_B]) - cg.add(var.set_pin_a(pin_b)) + cg.add(var.set_pin_b(pin_b)) if CONF_PIN_RESET in config: pin_i = yield cg.gpio_pin_expression(config[CONF_PIN_RESET]) diff --git a/esphome/components/script/__init__.py b/esphome/components/script/__init__.py index 962e2d56ca..e1983689a6 100644 --- a/esphome/components/script/__init__.py +++ b/esphome/components/script/__init__.py @@ -16,8 +16,13 @@ CONFIG_SCHEMA = automation.validate_automation({ def to_code(config): + # Register all variables first, so that scripts can use other scripts + triggers = [] for conf in config: trigger = cg.new_Pvariable(conf[CONF_ID]) + triggers.append((trigger, conf)) + + for trigger, conf in triggers: yield automation.build_automation(trigger, [], conf) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 43f0cefd56..c006dfad57 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -73,6 +73,7 @@ HeartbeatFilter = sensor_ns.class_('HeartbeatFilter', Filter, cg.Component) DeltaFilter = sensor_ns.class_('DeltaFilter', Filter) OrFilter = sensor_ns.class_('OrFilter', Filter) CalibrateLinearFilter = sensor_ns.class_('CalibrateLinearFilter', Filter) +CalibratePolynomialFilter = sensor_ns.class_('CalibratePolynomialFilter', Filter) SensorInRangeCondition = sensor_ns.class_('SensorInRangeCondition', Filter) unit_of_measurement = cv.string_strict @@ -194,6 +195,32 @@ def calibrate_linear_filter_to_code(config, filter_id): yield cg.new_Pvariable(filter_id, k, b) +CONF_DATAPOINTS = 'datapoints' +CONF_DEGREE = 'degree' + + +def validate_calibrate_polynomial(config): + if config[CONF_DEGREE] >= len(config[CONF_DATAPOINTS]): + raise cv.Invalid("Degree is too high! Maximum possible degree with given datapoints is " + "{}".format(len(config[CONF_DATAPOINTS]) - 1), [CONF_DEGREE]) + return config + + +@FILTER_REGISTRY.register('calibrate_polynomial', CalibratePolynomialFilter, cv.All(cv.Schema({ + cv.Required(CONF_DATAPOINTS): cv.All(cv.ensure_list(validate_datapoint), cv.Length(min=1)), + cv.Required(CONF_DEGREE): cv.positive_int, +}), validate_calibrate_polynomial)) +def calibrate_polynomial_filter_to_code(config, filter_id): + x = [conf[CONF_FROM] for conf in config[CONF_DATAPOINTS]] + y = [conf[CONF_TO] for conf in config[CONF_DATAPOINTS]] + degree = config[CONF_DEGREE] + a = [[1] + [x_**(i+1) for i in range(degree)] for x_ in x] + # Column vector + b = [[v] for v in y] + res = [v[0] for v in _lstsq(a, b)] + yield cg.new_Pvariable(filter_id, res) + + @coroutine def build_filters(config): yield cg.build_registry_list(FILTER_REGISTRY, config) @@ -303,6 +330,66 @@ def fit_linear(x, y): return k, b +def _mat_copy(m): + return [list(row) for row in m] + + +def _mat_transpose(m): + return _mat_copy(zip(*m)) + + +def _mat_identity(n): + return [[int(i == j) for j in range(n)] for i in range(n)] + + +def _mat_dot(a, b): + b_t = _mat_transpose(b) + return [[sum(x*y for x, y in zip(row_a, col_b)) for col_b in b_t] for row_a in a] + + +def _mat_inverse(m): + n = len(m) + m = _mat_copy(m) + id = _mat_identity(n) + + for diag in range(n): + # If diag element is 0, swap rows + if m[diag][diag] == 0: + for i in range(diag+1, n): + if m[i][diag] != 0: + break + else: + raise ValueError("Singular matrix, inverse cannot be calculated!") + + # Swap rows + m[diag], m[i] = m[i], m[diag] + id[diag], id[i] = id[i], id[diag] + + # Scale row to 1 in diagonal + scaler = 1.0 / m[diag][diag] + for j in range(n): + m[diag][j] *= scaler + id[diag][j] *= scaler + + # Subtract diag row + for i in range(n): + if i == diag: + continue + scaler = m[i][diag] + for j in range(n): + m[i][j] -= scaler * m[diag][j] + id[i][j] -= scaler * id[diag][j] + + return id + + +def _lstsq(a, b): + # min_x ||b - ax||^2_2 => x = (a^T a)^{-1} a^T b + a_t = _mat_transpose(a) + x = _mat_inverse(_mat_dot(a_t, a)) + return _mat_dot(_mat_dot(x, a_t), b) + + @coroutine_with_priority(40.0) def to_code(config): cg.add_define('USE_SENSOR') diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 4923a1c09b..79323bd8ab 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -1,5 +1,5 @@ -#include "esphome/components/sensor/filter.h" -#include "esphome/components/sensor/sensor.h" +#include "filter.h" +#include "sensor.h" #include "esphome/core/log.h" namespace esphome { @@ -228,5 +228,15 @@ float HeartbeatFilter::get_setup_priority() const { return setup_priority::HARDW optional CalibrateLinearFilter::new_value(float value) { return value * this->slope_ + this->bias_; } CalibrateLinearFilter::CalibrateLinearFilter(float slope, float bias) : slope_(slope), bias_(bias) {} +optional CalibratePolynomialFilter::new_value(float value) { + float res = 0.0f; + float x = 1.0f; + for (float coefficient : this->coefficients_) { + res += x * coefficient; + x *= value; + } + return res; +} + } // namespace sensor } // namespace esphome diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 6bd22be230..17a583e40e 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -243,5 +243,14 @@ class CalibrateLinearFilter : public Filter { float bias_; }; +class CalibratePolynomialFilter : public Filter { + public: + CalibratePolynomialFilter(const std::vector &coefficients) : coefficients_(coefficients) {} + optional new_value(float value) override; + + protected: + std::vector coefficients_; +}; + } // namespace sensor } // namespace esphome diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index ca6f4c23bb..e12e55e320 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -1,4 +1,4 @@ -#include "esphome/components/sensor/sensor.h" +#include "sensor.h" #include "esphome/core/log.h" namespace esphome { diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py new file mode 100644 index 0000000000..c64112570a --- /dev/null +++ b/esphome/components/sim800l/__init__.py @@ -0,0 +1,59 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.const import CONF_ID, CONF_TRIGGER_ID +from esphome.components import uart + +DEPENDENCIES = ['uart'] + +sim800l_ns = cg.esphome_ns.namespace('sim800l') +Sim800LComponent = sim800l_ns.class_('Sim800LComponent', cg.Component) + +Sim800LReceivedMessageTrigger = sim800l_ns.class_('Sim800LReceivedMessageTrigger', + automation.Trigger.template(cg.std_string, + cg.std_string)) + +# Actions +Sim800LSendSmsAction = sim800l_ns.class_('Sim800LSendSmsAction', automation.Action) + +MULTI_CONF = True + +CONF_ON_SMS_RECEIVED = 'on_sms_received' +CONF_RECIPIENT = 'recipient' +CONF_MESSAGE = 'message' + +CONFIG_SCHEMA = cv.All(cv.Schema({ + cv.GenerateID(): cv.declare_id(Sim800LComponent), + cv.Optional(CONF_ON_SMS_RECEIVED): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Sim800LReceivedMessageTrigger), + }), +}).extend(cv.polling_component_schema('5s')).extend(uart.UART_DEVICE_SCHEMA)) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield uart.register_uart_device(var, config) + + for conf in config.get(CONF_ON_SMS_RECEIVED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + yield automation.build_automation(trigger, [(cg.std_string, 'message'), + (cg.std_string, 'sender')], conf) + + +SIM800L_SEND_SMS_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.use_id(Sim800LComponent), + cv.Required(CONF_RECIPIENT): cv.templatable(cv.string_strict), + cv.Required(CONF_MESSAGE): cv.templatable(cv.string), +}) + + +@automation.register_action('sim800l.send_sms', Sim800LSendSmsAction, SIM800L_SEND_SMS_SCHEMA) +def sim800l_send_sms_to_code(config, action_id, template_arg, args): + paren = yield cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = yield cg.templatable(config[CONF_RECIPIENT], args, cg.std_string) + cg.add(var.set_recipient(template_)) + template_ = yield cg.templatable(config[CONF_MESSAGE], args, cg.std_string) + cg.add(var.set_message(template_)) + yield var diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp new file mode 100644 index 0000000000..646f20833f --- /dev/null +++ b/esphome/components/sim800l/sim800l.cpp @@ -0,0 +1,259 @@ +#include "sim800l.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace sim800l { + +static const char* TAG = "sim800l"; + +const char ASCII_CR = 0x0D; +const char ASCII_LF = 0x0A; + +void Sim800LComponent::update() { + if (this->watch_dog_++ == 2) { + this->state_ = STATE_INIT; + this->write(26); + } + + if (state_ == STATE_INIT) { + if (this->registered_ && this->send_pending_) { + this->send_cmd_("AT+CSCS=\"GSM\""); + this->state_ = STATE_SENDINGSMS1; + } else { + this->send_cmd_("AT"); + this->state_ = STATE_CHECK_AT; + } + this->expect_ack_ = true; + } + if (state_ == STATE_RECEIVEDSMS) { + // Serial Buffer should have flushed. + // Send cmd to delete received sms + char delete_cmd[20]; + sprintf(delete_cmd, "AT+CMGD=%d", this->parse_index_); + this->send_cmd_(delete_cmd); + this->state_ = STATE_CHECK_SMS; + this->expect_ack_ = true; + } +} + +void Sim800LComponent::send_cmd_(std::string message) { + ESP_LOGV(TAG, "S: %s - %d", message.c_str(), this->state_); + this->watch_dog_ = 0; + this->write_str(message.c_str()); + this->write_byte(ASCII_LF); +} + +void Sim800LComponent::parse_cmd_(std::string message) { + ESP_LOGV(TAG, "R: %s - %d", message.c_str(), this->state_); + + if (message.empty()) + return; + + if (this->expect_ack_) { + bool ok = message == "OK"; + this->expect_ack_ = false; + if (!ok) { + if (this->state_ == STATE_CHECK_AT && message == "AT") { + // Expected ack but AT echo received + this->state_ = STATE_DISABLE_ECHO; + this->expect_ack_ = true; + } else { + ESP_LOGW(TAG, "Not ack. %d %s", this->state_, message.c_str()); + this->state_ = STATE_IDLE; // Let it timeout + return; + } + } + } + + switch (this->state_) { + case STATE_INIT: + if (message.compare(0, 6, "+CMTI:") == 0) { + // While we were waiting for update to check for messages, this notifies a message + // is available. Grab it quickly + this->state_ = STATE_CHECK_SMS; + } + break; + case STATE_DISABLE_ECHO: + send_cmd_("ATE0"); + this->state_ = STATE_CHECK_AT; + this->expect_ack_ = true; + break; + case STATE_CHECK_AT: + send_cmd_("AT+CMGF=1"); + this->state_ = STATE_CREG; + this->expect_ack_ = true; + break; + case STATE_CREG: + send_cmd_("AT+CREG?"); + this->state_ = STATE_CREGWAIT; + break; + case STATE_CREGWAIT: { + // Response: "+CREG: 0,1" -- the one there means registered ok + // "+CREG: -,-" means not registered ok + bool registered = message.compare(0, 6, "+CREG:") == 0 && message[9] == '1'; + if (registered) { + if (!this->registered_) + ESP_LOGD(TAG, "Registered OK"); + send_cmd_("AT+CSQ"); + this->state_ = STATE_CSQ; + this->expect_ack_ = true; + } else { + ESP_LOGW(TAG, "Registration Fail"); + if (message[7] == '0') { // Network registration is disable, enable it + send_cmd_("AT+CREG=1"); + this->expect_ack_ = true; + this->state_ = STATE_CHECK_AT; + } else { + // Keep waiting registration + this->state_ = STATE_CREG; + } + } + this->registered_ = registered; + break; + } + case STATE_CSQ: + this->state_ = STATE_CSQ_RESPONSE; + break; + case STATE_CSQ_RESPONSE: + if (message.compare(0, 5, "+CSQ:") == 0) { + size_t comma = message.find(',', 6); + if (comma != 6) { + this->rssi_ = strtol(message.substr(6, comma - 6).c_str(), nullptr, 10); + ESP_LOGD(TAG, "RSSI: %d", this->rssi_); + } + } + this->state_ = STATE_CHECK_SMS; + break; + case STATE_PARSE_SMS: + this->state_ = STATE_PARSE_SMS_RESPONSE; + break; + case STATE_PARSE_SMS_RESPONSE: + if (message.compare(0, 6, "+CMGL:") == 0 && this->parse_index_ == 0) { + size_t start = 7; + size_t end = message.find(',', start); + uint8_t item = 0; + while (end != start) { + item++; + if (item == 1) { // Slot Index + this->parse_index_ = strtol(message.substr(start, end - start).c_str(), nullptr, 10); + } + // item 2 = STATUS, usually "REC UNERAD" + if (item == 3) { // recipient + // Add 1 and remove 2 from substring to get rid of "quotes" + this->sender_ = message.substr(start + 1, end - start - 2); + break; + } + // item 4 = "" + // item 5 = Received timestamp + start = end + 1; + end = message.find(',', start); + } + + if (item < 2) { + ESP_LOGD(TAG, "Invalid message %d %s", this->state_, message.c_str()); + return; + } + this->state_ = STATE_RECEIVESMS; + } + // Otherwise we receive another OK, we do nothing just wait polling to continuously check for SMS + if (message == "OK") + this->state_ = STATE_INIT; + break; + case STATE_RECEIVESMS: + /* Our recipient is set and the message body is in message + kick ESPHome callback now + */ + ESP_LOGD(TAG, "Received SMS from: %s", this->sender_.c_str()); + ESP_LOGD(TAG, "%s", message.c_str()); + this->callback_.call(message, this->sender_); + /* If the message is multiline, next lines will contain message data. + If there were other messages in the list, next line will be +CMGL: ... + At the end of the list the new line and the OK should be received. + To keep this simple just first line of message if considered, then + the next state will swallow all received data and in next poll event + this message index is marked for deletion. + */ + this->state_ = STATE_RECEIVEDSMS; + break; + case STATE_RECEIVEDSMS: + // Let the buffer flush. Next poll will request to delete the parsed index message. + break; + case STATE_SENDINGSMS1: + this->send_cmd_("AT+CMGS=\"" + this->recipient_ + "\""); + this->state_ = STATE_SENDINGSMS2; + break; + case STATE_SENDINGSMS2: + if (message == ">") { + // Send sms body + ESP_LOGD(TAG, "Sending message: '%s'", this->outgoing_message_.c_str()); + this->write_str(this->outgoing_message_.c_str()); + this->write(26); + this->state_ = STATE_SENDINGSMS3; + } else { + this->registered_ = false; + this->state_ = STATE_INIT; + this->send_cmd_("AT+CMEE=2"); + this->write(26); + } + break; + case STATE_SENDINGSMS3: + if (message.compare(0, 6, "+CMGS:") == 0) { + ESP_LOGD(TAG, "SMS Sent OK: %s", message.c_str()); + this->send_pending_ = false; + this->state_ = STATE_CHECK_SMS; + this->expect_ack_ = true; + } + break; + default: + ESP_LOGD(TAG, "Unhandled: %s - %d", message.c_str(), this->state_); + break; + } + if (this->state_ == STATE_CHECK_SMS) { + send_cmd_("AT+CMGL=\"ALL\""); + this->state_ = STATE_PARSE_SMS; + this->parse_index_ = 0; + this->expect_ack_ = true; + } +} + +void Sim800LComponent::loop() { + // Read message + while (this->available()) { + uint8_t byte; + this->read_byte(&byte); + + if (this->read_pos_ == SIM800L_READ_BUFFER_LENGTH) + this->read_pos_ = 0; + + ESP_LOGVV(TAG, "Buffer pos: %u %d", this->read_pos_, byte); // NOLINT + + if (byte == ASCII_CR) + continue; + if (byte >= 0x7F) + byte = '?'; // need to be valid utf8 string for log functions. + this->read_buffer_[this->read_pos_] = byte; + + if (this->state_ == STATE_SENDINGSMS2 && this->read_pos_ == 0 && byte == '>') + this->read_buffer_[++this->read_pos_] = ASCII_LF; + + if (this->read_buffer_[this->read_pos_] == ASCII_LF) { + this->read_buffer_[this->read_pos_] = 0; + this->read_pos_ = 0; + this->parse_cmd_(this->read_buffer_); + } else { + this->read_pos_++; + } + } +} + +void Sim800LComponent::send_sms(std::string recipient, std::string message) { + ESP_LOGD(TAG, "Sending to %s: %s", recipient.c_str(), message.c_str()); + this->recipient_ = recipient; + this->outgoing_message_ = message; + this->send_pending_ = true; + this->update(); +} + +} // namespace sim800l +} // namespace esphome diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h new file mode 100644 index 0000000000..17cd0111fe --- /dev/null +++ b/esphome/components/sim800l/sim800l.h @@ -0,0 +1,91 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" + +#define SIM800L_READ_BUFFER_LENGTH 255 + +namespace esphome { +namespace sim800l { + +enum State { + STATE_IDLE = 0, + STATE_INIT, + STATE_CHECK_AT, + STATE_CREG, + STATE_CREGWAIT, + STATE_CSQ, + STATE_CSQ_RESPONSE, + STATE_IDLEWAIT, + STATE_SENDINGSMS1, + STATE_SENDINGSMS2, + STATE_SENDINGSMS3, + STATE_CHECK_SMS, + STATE_PARSE_SMS, + STATE_PARSE_SMS_RESPONSE, + STATE_RECEIVESMS, + STATE_READSMS, + STATE_RECEIVEDSMS, + STATE_DELETEDSMS, + STATE_DISABLE_ECHO, + STATE_PARSE_SMS_OK +}; + +class Sim800LComponent : public uart::UARTDevice, public PollingComponent { + public: + /// Retrieve the latest sensor values. This operation takes approximately 16ms. + void update() override; + void loop() override; + void add_on_sms_received_callback(std::function callback) { + this->callback_.add(std::move(callback)); + } + void send_sms(std::string recipient, std::string message); + + protected: + void send_cmd_(std::string); + void parse_cmd_(std::string); + + std::string sender_; + char read_buffer_[SIM800L_READ_BUFFER_LENGTH]; + size_t read_pos_{0}; + uint8_t parse_index_{0}; + uint8_t watch_dog_{0}; + bool expect_ack_{false}; + sim800l::State state_{STATE_IDLE}; + bool registered_{false}; + int rssi_{0}; + + std::string recipient_; + std::string outgoing_message_; + bool send_pending_; + + CallbackManager callback_; +}; + +class Sim800LReceivedMessageTrigger : public Trigger { + public: + explicit Sim800LReceivedMessageTrigger(Sim800LComponent *parent) { + parent->add_on_sms_received_callback( + [this](std::string message, std::string sender) { this->trigger(message, sender); }); + } +}; + +template class Sim800LSendSmsAction : public Action { + public: + Sim800LSendSmsAction(Sim800LComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(std::string, recipient) + TEMPLATABLE_VALUE(std::string, message) + + void play(Ts... x) { + auto recipient = this->recipient_.value(x...); + auto message = this->message_.value(x...); + this->parent_->send_sms(recipient, message); + } + + protected: + Sim800LComponent *parent_; +}; + +} // namespace sim800l +} // namespace esphome diff --git a/esphome/components/sm16716/__init__.py b/esphome/components/sm16716/__init__.py new file mode 100644 index 0000000000..4e342588f9 --- /dev/null +++ b/esphome/components/sm16716/__init__.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.const import (CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_ID, + CONF_NUM_CHANNELS, CONF_NUM_CHIPS) + +AUTO_LOAD = ['output'] +sm16716_ns = cg.esphome_ns.namespace('sm16716') +SM16716 = sm16716_ns.class_('SM16716', cg.Component) + +MULTI_CONF = True +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(SM16716), + cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_NUM_CHANNELS, default=3): cv.int_range(min=3, max=255), + cv.Optional(CONF_NUM_CHIPS, default=1): cv.int_range(min=1, max=85), +}).extend(cv.COMPONENT_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + + data = yield cg.gpio_pin_expression(config[CONF_DATA_PIN]) + cg.add(var.set_data_pin(data)) + clock = yield cg.gpio_pin_expression(config[CONF_CLOCK_PIN]) + cg.add(var.set_clock_pin(clock)) + + cg.add(var.set_num_channels(config[CONF_NUM_CHANNELS])) + cg.add(var.set_num_chips(config[CONF_NUM_CHIPS])) diff --git a/esphome/components/sm16716/output.py b/esphome/components/sm16716/output.py new file mode 100644 index 0000000000..93c9ed4ce1 --- /dev/null +++ b/esphome/components/sm16716/output.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output +from esphome.const import CONF_CHANNEL, CONF_ID +from . import SM16716 + +DEPENDENCIES = ['sm16716'] + +Channel = SM16716.class_('Channel', output.FloatOutput) + +CONF_SM16716_ID = 'sm16716_id' +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ + cv.GenerateID(CONF_SM16716_ID): cv.use_id(SM16716), + cv.Required(CONF_ID): cv.declare_id(Channel), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=65535), +}).extend(cv.COMPONENT_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield output.register_output(var, config) + + parent = yield cg.get_variable(config[CONF_SM16716_ID]) + cg.add(var.set_parent(parent)) + cg.add(var.set_channel(config[CONF_CHANNEL])) diff --git a/esphome/components/sm16716/sm16716.cpp b/esphome/components/sm16716/sm16716.cpp new file mode 100644 index 0000000000..bc8e4fc1f4 --- /dev/null +++ b/esphome/components/sm16716/sm16716.cpp @@ -0,0 +1,52 @@ +#include "sm16716.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace sm16716 { + +static const char *TAG = "sm16716"; + +void SM16716::setup() { + ESP_LOGCONFIG(TAG, "Setting up SM16716OutputComponent..."); + this->data_pin_->setup(); + this->data_pin_->digital_write(false); + this->clock_pin_->setup(); + this->clock_pin_->digital_write(false); + this->pwm_amounts_.resize(this->num_channels_, 0); +} +void SM16716::dump_config() { + ESP_LOGCONFIG(TAG, "SM16716:"); + LOG_PIN(" Data Pin: ", this->data_pin_); + LOG_PIN(" Clock Pin: ", this->clock_pin_); + ESP_LOGCONFIG(TAG, " Total number of channels: %u", this->num_channels_); + ESP_LOGCONFIG(TAG, " Number of chips: %u", this->num_chips_); +} +void SM16716::loop() { + if (!this->update_) + return; + + for (uint8_t i = 0; i < 50; i++) { + this->write_bit_(false); + } + + // send 25 bits (1 start bit plus 24 data bits) for each chip + for (uint8_t index = 0; index < this->num_channels_; index++) { + // send a start bit initially and after every 3 channels + if (index % 3 == 0) { + this->write_bit_(true); + } + + this->write_byte_(this->pwm_amounts_[index]); + } + + // send a blank 25 bits to signal the end + this->write_bit_(false); + this->write_byte_(0); + this->write_byte_(0); + this->write_byte_(0); + + this->update_ = false; +} + +} // namespace sm16716 +} // namespace esphome diff --git a/esphome/components/sm16716/sm16716.h b/esphome/components/sm16716/sm16716.h new file mode 100644 index 0000000000..fe534d93fe --- /dev/null +++ b/esphome/components/sm16716/sm16716.h @@ -0,0 +1,70 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/output/float_output.h" + +namespace esphome { +namespace sm16716 { + +class SM16716 : public Component { + public: + class Channel; + + void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; } + void set_clock_pin(GPIOPin *clock_pin) { clock_pin_ = clock_pin; } + void set_num_channels(uint8_t num_channels) { num_channels_ = num_channels; } + void set_num_chips(uint8_t num_chips) { num_chips_ = num_chips; } + + void setup() override; + + void dump_config() override; + + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + /// Send new values if they were updated. + void loop() override; + + class Channel : public output::FloatOutput { + public: + void set_parent(SM16716 *parent) { parent_ = parent; } + void set_channel(uint8_t channel) { channel_ = channel; } + + protected: + void write_state(float state) override { + auto amount = uint8_t(state * 0xFF); + this->parent_->set_channel_value_(this->channel_, amount); + } + + SM16716 *parent_; + uint8_t channel_; + }; + + protected: + void set_channel_value_(uint8_t channel, uint8_t value) { + uint8_t index = this->num_channels_ - channel - 1; + if (this->pwm_amounts_[index] != value) { + this->update_ = true; + } + this->pwm_amounts_[index] = value; + } + void write_bit_(bool value) { + this->data_pin_->digital_write(value); + this->clock_pin_->digital_write(true); + this->clock_pin_->digital_write(false); + } + void write_byte_(uint8_t data) { + for (uint8_t mask = 0x80; mask; mask >>= 1) { + this->write_bit_(data & mask); + } + } + + GPIOPin *data_pin_; + GPIOPin *clock_pin_; + uint8_t num_channels_; + uint8_t num_chips_; + std::vector pwm_amounts_; + bool update_{true}; +}; + +} // namespace sm16716 +} // namespace esphome diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 4f931203fb..c10a3e5ac3 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" #ifdef ARDUINO_ARCH_ESP32 -#include "apps/sntp/sntp.h" +#include "lwip/apps/sntp.h" #endif #ifdef ARDUINO_ARCH_ESP8266 #include "sntp.h" diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index db4b71c29a..bf2a18955a 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -8,74 +8,10 @@ namespace spi { static const char *TAG = "spi"; -void ICACHE_RAM_ATTR HOT SPIComponent::write_byte(uint8_t data) { - uint8_t send_bits = data; - if (this->msb_first_) - send_bits = reverse_bits_8(data); - - this->clk_->digital_write(true); - if (!this->high_speed_) - delayMicroseconds(5); - - for (size_t i = 0; i < 8; i++) { - if (!this->high_speed_) - delayMicroseconds(5); - this->clk_->digital_write(false); - - // sampling on leading edge - this->mosi_->digital_write(send_bits & (1 << i)); - if (!this->high_speed_) - delayMicroseconds(5); - this->clk_->digital_write(true); - } - - ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data), data); -} - -uint8_t ICACHE_RAM_ATTR HOT SPIComponent::read_byte() { - this->clk_->digital_write(true); - - uint8_t data = 0; - for (size_t i = 0; i < 8; i++) { - if (!this->high_speed_) - delayMicroseconds(5); - data |= uint8_t(this->miso_->digital_read()) << i; - this->clk_->digital_write(false); - if (!this->high_speed_) - delayMicroseconds(5); - this->clk_->digital_write(true); - } - - if (this->msb_first_) { - data = reverse_bits_8(data); - } - - ESP_LOGVV(TAG, " Received 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data), data); - - return data; -} -void ICACHE_RAM_ATTR HOT SPIComponent::read_array(uint8_t *data, size_t length) { - for (size_t i = 0; i < length; i++) - data[i] = this->read_byte(); -} - -void ICACHE_RAM_ATTR HOT SPIComponent::write_array(uint8_t *data, size_t length) { - for (size_t i = 0; i < length; i++) { - App.feed_wdt(); - this->write_byte(data[i]); - } -} - -void ICACHE_RAM_ATTR HOT SPIComponent::enable(GPIOPin *cs, bool msb_first, bool high_speed) { - ESP_LOGVV(TAG, "Enabling SPI Chip on pin %u...", cs->get_pin()); - cs->digital_write(false); - - this->active_cs_ = cs; - this->msb_first_ = msb_first; - this->high_speed_ = high_speed; -} - void ICACHE_RAM_ATTR HOT SPIComponent::disable() { + if (this->hw_spi_ != nullptr) { + this->hw_spi_->endTransaction(); + } ESP_LOGVV(TAG, "Disabling SPI Chip on pin %u...", this->active_cs_->get_pin()); this->active_cs_->digital_write(true); this->active_cs_ = nullptr; @@ -84,6 +20,53 @@ void SPIComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up SPI bus..."); this->clk_->setup(); this->clk_->digital_write(true); + + bool use_hw_spi = true; + if (this->clk_->is_inverted()) + use_hw_spi = false; + const bool has_miso = this->miso_ != nullptr; + const bool has_mosi = this->mosi_ != nullptr; + if (has_miso && this->miso_->is_inverted()) + use_hw_spi = false; + if (has_mosi && this->mosi_->is_inverted()) + use_hw_spi = false; + int8_t clk_pin = this->clk_->get_pin(); + int8_t miso_pin = has_miso ? this->miso_->get_pin() : -1; + int8_t mosi_pin = has_mosi ? this->mosi_->get_pin() : -1; +#ifdef ARDUINO_ARCH_ESP8266 + if (clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) { + // pass + } else if (clk_pin == 14 && miso_pin == 12 && mosi_pin == 13) { + // pass + } else { + use_hw_spi = false; + } + + if (use_hw_spi) { + this->hw_spi_ = &SPI; + this->hw_spi_->pins(clk_pin, miso_pin, mosi_pin, 0); + this->hw_spi_->begin(); + return; + } +#endif +#ifdef ARDUINO_ARCH_ESP32 + static uint8_t spi_bus_num = 0; + if (spi_bus_num >= 2) { + use_hw_spi = false; + } + + if (use_hw_spi) { + if (spi_bus_num == 0) { + this->hw_spi_ = &SPI; + } else { + this->hw_spi_ = new SPIClass(VSPI); + } + spi_bus_num++; + this->hw_spi_->begin(clk_pin, miso_pin, mosi_pin); + return; + } +#endif + if (this->miso_ != nullptr) { this->miso_->setup(); } @@ -97,8 +80,154 @@ void SPIComponent::dump_config() { LOG_PIN(" CLK Pin: ", this->clk_); LOG_PIN(" MISO Pin: ", this->miso_); LOG_PIN(" MOSI Pin: ", this->mosi_); + ESP_LOGCONFIG(TAG, " Using HW SPI: %s", YESNO(this->hw_spi_ != nullptr)); } float SPIComponent::get_setup_priority() const { return setup_priority::BUS; } +void SPIComponent::debug_tx(uint8_t value) { + ESP_LOGVV(TAG, " TX 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(value), value); +} +void SPIComponent::debug_rx(uint8_t value) { + ESP_LOGVV(TAG, " RX 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(value), value); +} +void SPIComponent::debug_enable(uint8_t pin) { ESP_LOGVV(TAG, "Enabling SPI Chip on pin %u...", pin); } + +void SPIComponent::cycle_clock_(bool value) { + uint32_t start = ESP.getCycleCount(); + while (start - ESP.getCycleCount() < this->wait_cycle_) + ; + this->clk_->digital_write(value); + start += this->wait_cycle_; + while (start - ESP.getCycleCount() < this->wait_cycle_) + ; +} + +// NOLINTNEXTLINE +#pragma GCC optimize("unroll-loops") +// NOLINTNEXTLINE +#pragma GCC optimize("O2") + +template +uint8_t HOT SPIComponent::transfer_(uint8_t data) { + // Clock starts out at idle level + this->clk_->digital_write(CLOCK_POLARITY); + uint8_t out_data = 0; + + for (uint8_t i = 0; i < 8; i++) { + uint8_t shift; + if (BIT_ORDER == BIT_ORDER_MSB_FIRST) + shift = 7 - i; + else + shift = i; + + if (CLOCK_PHASE == CLOCK_PHASE_LEADING) { + // sampling on leading edge + if (WRITE) { + this->mosi_->digital_write(data & (1 << shift)); + } + + // SAMPLE! + this->cycle_clock_(!CLOCK_POLARITY); + + if (READ) { + out_data |= uint8_t(this->miso_->digital_read()) << shift; + } + + this->cycle_clock_(CLOCK_POLARITY); + } else { + // sampling on trailing edge + this->cycle_clock_(!CLOCK_POLARITY); + + if (WRITE) { + this->mosi_->digital_write(data & (1 << shift)); + } + + // SAMPLE! + this->cycle_clock_(CLOCK_POLARITY); + + if (READ) { + out_data |= uint8_t(this->miso_->digital_read()) << shift; + } + } + } + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + if (WRITE) { + SPIComponent::debug_tx(data); + } + if (READ) { + SPIComponent::debug_rx(out_data); + } +#endif + + App.feed_wdt(); + + return out_data; +} + +// Generate with (py3): +// +// from itertools import product +// bit_orders = ['BIT_ORDER_LSB_FIRST', 'BIT_ORDER_MSB_FIRST'] +// clock_pols = ['CLOCK_POLARITY_LOW', 'CLOCK_POLARITY_HIGH'] +// clock_phases = ['CLOCK_PHASE_LEADING', 'CLOCK_PHASE_TRAILING'] +// reads = [False, True] +// writes = [False, True] +// cpp_bool = {False: 'false', True: 'true'} +// for b, cpol, cph, r, w in product(bit_orders, clock_pols, clock_phases, reads, writes): +// if not r and not w: +// continue +// print(f"template uint8_t SPIComponent::transfer_<{b}, {cpol}, {cph}, {cpp_bool[r]}, {cpp_bool[w]}>(uint8_t +// data);") + +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); +template uint8_t SPIComponent::transfer_( + uint8_t data); + } // namespace spi } // namespace esphome diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 600e1a0cd2..ccef6192f3 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -2,10 +2,61 @@ #include "esphome/core/component.h" #include "esphome/core/esphal.h" +#include namespace esphome { namespace spi { +/// The bit-order for SPI devices. This defines how the data read from and written to the device is interpreted. +enum SPIBitOrder { + /// The least significant bit is transmitted/received first. + BIT_ORDER_LSB_FIRST, + /// The most significant bit is transmitted/received first. + BIT_ORDER_MSB_FIRST, +}; +/** The SPI clock signal polarity, + * + * This defines how the clock signal is used. Flipping this effectively inverts the clock signal. + */ +enum SPIClockPolarity { + /** The clock signal idles on LOW. (CPOL=0) + * + * A rising edge means a leading edge for the clock. + */ + CLOCK_POLARITY_LOW = false, + /** The clock signal idles on HIGH. (CPOL=1) + * + * A falling edge means a trailing edge for the clock. + */ + CLOCK_POLARITY_HIGH = true, +}; +/** The SPI clock signal phase. + * + * This defines when the data signals are sampled. Most SPI devices use the LEADING clock phase. + */ +enum SPIClockPhase { + /// The data is sampled on a leading clock edge. (CPHA=0) + CLOCK_PHASE_LEADING, + /// The data is sampled on a trailing clock edge. (CPHA=1) + CLOCK_PHASE_TRAILING, +}; +/** The SPI clock signal data rate. This defines for what duration the clock signal is HIGH/LOW. + * So effectively the rate of bytes can be calculated using + * + * effective_byte_rate = spi_data_rate / 16 + * + * Implementations can use the pre-defined constants here, or use an integer in the template definition + * to manually use a specific data rate. + */ +enum SPIDataRate : uint32_t { + DATA_RATE_1KHZ = 1000, + DATA_RATE_200KHZ = 200000, + DATA_RATE_1MHZ = 1000000, + DATA_RATE_2MHZ = 2000000, + DATA_RATE_4MHZ = 4000000, + DATA_RATE_8MHZ = 8000000, +}; + class SPIComponent : public Component { public: void set_clk(GPIOPin *clk) { clk_ = clk; } @@ -16,59 +67,156 @@ class SPIComponent : public Component { void dump_config() override; - uint8_t read_byte(); + template uint8_t read_byte() { + if (this->hw_spi_ != nullptr) { + return this->hw_spi_->transfer(0x00); + } + return this->transfer_(0x00); + } - void read_array(uint8_t *data, size_t length); + template + void read_array(uint8_t *data, size_t length) { + if (this->hw_spi_ != nullptr) { + this->hw_spi_->transfer(data, length); + return; + } + for (size_t i = 0; i < length; i++) { + data[i] = this->read_byte(); + } + } - void write_byte(uint8_t data); + template + void write_byte(uint8_t data) { + if (this->hw_spi_ != nullptr) { + this->hw_spi_->write(data); + return; + } + this->transfer_(data); + } - void write_array(uint8_t *data, size_t length); + template + void write_array(const uint8_t *data, size_t length) { + if (this->hw_spi_ != nullptr) { + auto *data_c = const_cast(data); + this->hw_spi_->writeBytes(data_c, length); + return; + } + for (size_t i = 0; i < length; i++) { + this->write_byte(data[i]); + } + } - void enable(GPIOPin *cs, bool msb_first, bool high_speed); + template + uint8_t transfer_byte(uint8_t data) { + if (this->hw_spi_ != nullptr) { + return this->hw_spi_->transfer(data); + } + return this->transfer_(data); + } + + template + void transfer_array(uint8_t *data, size_t length) { + if (this->hw_spi_ != nullptr) { + this->hw_spi_->transfer(data, length); + return; + } + for (size_t i = 0; i < length; i++) { + data[i] = this->transfer_byte(data[i]); + } + } + + template + void enable(GPIOPin *cs) { + SPIComponent::debug_enable(cs->get_pin()); + + if (this->hw_spi_ != nullptr) { + uint8_t data_mode = (uint8_t(CLOCK_POLARITY) << 1) | uint8_t(CLOCK_PHASE); + SPISettings settings(DATA_RATE, BIT_ORDER, data_mode); + this->hw_spi_->beginTransaction(settings); + } else { + this->clk_->digital_write(CLOCK_POLARITY); + this->wait_cycle_ = uint32_t(F_CPU) / DATA_RATE / 2ULL; + } + + this->active_cs_ = cs; + this->active_cs_->digital_write(false); + } void disable(); float get_setup_priority() const override; protected: + inline void cycle_clock_(bool value); + + static void debug_enable(uint8_t pin); + static void debug_tx(uint8_t value); + static void debug_rx(uint8_t value); + + template + uint8_t transfer_(uint8_t data); + GPIOPin *clk_; GPIOPin *miso_{nullptr}; GPIOPin *mosi_{nullptr}; GPIOPin *active_cs_{nullptr}; - bool msb_first_{true}; - bool high_speed_{false}; + SPIClass *hw_spi_{nullptr}; + uint32_t wait_cycle_; }; +template class SPIDevice { public: SPIDevice() = default; SPIDevice(SPIComponent *parent, GPIOPin *cs) : parent_(parent), cs_(cs) {} - void set_spi_parent(SPIComponent *parent) { this->parent_ = parent; } - void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; } + void set_spi_parent(SPIComponent *parent) { parent_ = parent; } + void set_cs_pin(GPIOPin *cs) { cs_ = cs; } void spi_setup() { this->cs_->setup(); this->cs_->digital_write(true); } - void enable() { this->parent_->enable(this->cs_, this->is_device_msb_first(), this->is_device_high_speed()); } + void enable() { this->parent_->template enable(this->cs_); } void disable() { this->parent_->disable(); } - uint8_t read_byte() { return this->parent_->read_byte(); } + uint8_t read_byte() { return this->parent_->template read_byte(); } - void read_array(uint8_t *data, size_t length) { return this->parent_->read_array(data, length); } + void read_array(uint8_t *data, size_t length) { + return this->parent_->template read_array(data, length); + } - void write_byte(uint8_t data) { return this->parent_->write_byte(data); } + template std::array read_array() { + std::array data; + this->read_array(data.data(), N); + return data; + } - void write_array(uint8_t *data, size_t length) { this->parent_->write_array(data, length); } + void write_byte(uint8_t data) { + return this->parent_->template write_byte(data); + } + + void write_array(const uint8_t *data, size_t length) { + this->parent_->template write_array(data, length); + } + + template void write_array(const std::array &data) { this->write_array(data.data(), N); } + + void write_array(const std::vector &data) { this->write_array(data.data(), data.size()); } + + uint8_t transfer_byte(uint8_t data) { + return this->parent_->template transfer_byte(data); + } + + void transfer_array(uint8_t *data, size_t length) { + this->parent_->template transfer_array(data, length); + } + + template void transfer_array(std::array &data) { this->transfer_array(data.data(), N); } protected: - virtual bool is_device_msb_first() = 0; - - virtual bool is_device_high_speed() { return false; } - SPIComponent *parent_{nullptr}; GPIOPin *cs_{nullptr}; }; diff --git a/esphome/components/ssd1306_spi/ssd1306_spi.cpp b/esphome/components/ssd1306_spi/ssd1306_spi.cpp index aeead612ff..d87f412f70 100644 --- a/esphome/components/ssd1306_spi/ssd1306_spi.cpp +++ b/esphome/components/ssd1306_spi/ssd1306_spi.cpp @@ -7,7 +7,6 @@ namespace ssd1306_spi { static const char *TAG = "ssd1306_spi"; -bool SPISSD1306::is_device_msb_first() { return true; } void SPISSD1306::setup() { ESP_LOGCONFIG(TAG, "Setting up SPI SSD1306..."); this->spi_setup(); @@ -52,7 +51,6 @@ void HOT SPISSD1306::write_display_data() { this->disable(); } } -bool SPISSD1306::is_device_high_speed() { return true; } } // namespace ssd1306_spi } // namespace esphome diff --git a/esphome/components/ssd1306_spi/ssd1306_spi.h b/esphome/components/ssd1306_spi/ssd1306_spi.h index 5d0640bd84..c58ebc800a 100644 --- a/esphome/components/ssd1306_spi/ssd1306_spi.h +++ b/esphome/components/ssd1306_spi/ssd1306_spi.h @@ -7,7 +7,9 @@ namespace esphome { namespace ssd1306_spi { -class SPISSD1306 : public ssd1306_base::SSD1306, public spi::SPIDevice { +class SPISSD1306 : public ssd1306_base::SSD1306, + public spi::SPIDevice { public: void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } @@ -19,8 +21,6 @@ class SPISSD1306 : public ssd1306_base::SSD1306, public spi::SPIDevice { void command(uint8_t value) override; void write_display_data() override; - bool is_device_msb_first() override; - bool is_device_high_speed() override; GPIOPin *dc_pin_; }; diff --git a/esphome/components/sun/__init__.py b/esphome/components/sun/__init__.py index 625e64dcc2..fef0902181 100644 --- a/esphome/components/sun/__init__.py +++ b/esphome/components/sun/__init__.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import time from esphome.const import CONF_TIME_ID, CONF_ID, CONF_TRIGGER_ID +from esphome.py_compat import string_types sun_ns = cg.esphome_ns.namespace('sun') @@ -17,6 +18,10 @@ CONF_ELEVATION = 'elevation' CONF_ON_SUNRISE = 'on_sunrise' CONF_ON_SUNSET = 'on_sunset' +# Default sun elevation is a bit below horizon because sunset +# means time when the entire sun disk is below the horizon +DEFAULT_ELEVATION = -0.883 + ELEVATION_MAP = { 'sunrise': 0.0, 'sunset': 0.0, @@ -27,9 +32,9 @@ ELEVATION_MAP = { def elevation(value): - if isinstance(value, str): + if isinstance(value, string_types): try: - value = ELEVATION_MAP[cv.one_of(*ELEVATION_MAP, lower=True, space='_')] + value = ELEVATION_MAP[cv.one_of(*ELEVATION_MAP, lower=True, space='_')(value)] except cv.Invalid: pass value = cv.angle(value) @@ -44,11 +49,11 @@ CONFIG_SCHEMA = cv.Schema({ cv.Optional(CONF_ON_SUNRISE): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SunTrigger), - cv.Optional(CONF_ELEVATION, default=0.0): elevation, + cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): elevation, }), cv.Optional(CONF_ON_SUNSET): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SunTrigger), - cv.Optional(CONF_ELEVATION, default=0.0): elevation, + cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): elevation, }), }) @@ -79,7 +84,7 @@ def to_code(config): @automation.register_condition('sun.is_above_horizon', SunCondition, cv.Schema({ cv.GenerateID(): cv.use_id(Sun), - cv.Optional(CONF_ELEVATION, default=0): cv.templatable(elevation), + cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): cv.templatable(elevation), })) def sun_above_horizon_to_code(config, condition_id, template_arg, args): var = cg.new_Pvariable(condition_id, template_arg) @@ -92,7 +97,7 @@ def sun_above_horizon_to_code(config, condition_id, template_arg, args): @automation.register_condition('sun.is_below_horizon', SunCondition, cv.Schema({ cv.GenerateID(): cv.use_id(Sun), - cv.Optional(CONF_ELEVATION, default=0): cv.templatable(elevation), + cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): cv.templatable(elevation), })) def sun_below_horizon_to_code(config, condition_id, template_arg, args): var = cg.new_Pvariable(condition_id, template_arg) diff --git a/esphome/components/sun/sun.h b/esphome/components/sun/sun.h index 2592c75c62..501d122da0 100644 --- a/esphome/components/sun/sun.h +++ b/esphome/components/sun/sun.h @@ -103,7 +103,7 @@ class SunTrigger : public Trigger<>, public PollingComponent, public Parentedlast_elevation_ >= this->elevation_ && this->elevation_ > current; } - if (crossed) { + if (crossed && !isnan(this->last_elevation_)) { this->trigger(); } this->last_elevation_ = current; @@ -111,7 +111,7 @@ class SunTrigger : public Trigger<>, public PollingComponent, public Parentedsensor_ != 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_two_point_target_temperature(false); + traits.set_supports_away(false); + traits.set_visual_min_temperature(TCL112_TEMP_MIN); + traits.set_visual_max_temperature(TCL112_TEMP_MAX); + traits.set_visual_temperature_step(.5f); + return traits; +} + +void Tcl112Climate::setup() { + if (this->sensor_) { + this->sensor_->add_on_state_callback([this](float state) { + this->current_temperature = state; + // current temperature changed, publish state + this->publish_state(); + }); + this->current_temperature = this->sensor_->state; + } else + this->current_temperature = NAN; + // restore set points + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(this); + } else { + // restore from defaults + this->mode = climate::CLIMATE_MODE_OFF; + this->target_temperature = 24; + } +} + +void Tcl112Climate::control(const climate::ClimateCall &call) { + if (call.get_mode().has_value()) + this->mode = *call.get_mode(); + if (call.get_target_temperature().has_value()) + this->target_temperature = *call.get_target_temperature(); + + this->transmit_state_(); + this->publish_state(); +} + +void Tcl112Climate::transmit_state_() { + uint8_t remote_state[TCL112_STATE_LENGTH] = {0}; + + // A known good state. (On, Cool, 24C) + remote_state[0] = 0x23; + remote_state[1] = 0xCB; + remote_state[2] = 0x26; + remote_state[3] = 0x01; + remote_state[5] = 0x24; + remote_state[6] = 0x03; + remote_state[7] = 0x07; + remote_state[8] = 0x40; + + // Set mode + switch (this->mode) { + case climate::CLIMATE_MODE_AUTO: + remote_state[6] &= 0xF0; + remote_state[6] |= TCL112_AUTO; + break; + case climate::CLIMATE_MODE_COOL: + remote_state[6] &= 0xF0; + remote_state[6] |= TCL112_COOL; + break; + case climate::CLIMATE_MODE_HEAT: + remote_state[6] &= 0xF0; + remote_state[6] |= TCL112_HEAT; + break; + case climate::CLIMATE_MODE_OFF: + default: + remote_state[5] &= ~TCL112_POWER_MASK; + break; + } + + // Set temperature + // Make sure we have desired temp in the correct range. + float safecelsius = std::max(this->target_temperature, TCL112_TEMP_MIN); + safecelsius = std::min(safecelsius, TCL112_TEMP_MAX); + // Convert to integer nr. of half degrees. + auto half_degrees = static_cast(safecelsius * 2); + if (half_degrees & 1) // Do we have a half degree celsius? + remote_state[12] |= TCL112_HALF_DEGREE; // Add 0.5 degrees + else + remote_state[12] &= ~TCL112_HALF_DEGREE; // Clear the half degree. + remote_state[7] &= 0xF0; // Clear temp bits. + remote_state[7] |= ((uint8_t) TCL112_TEMP_MAX - half_degrees / 2); + + // Calculate & set the checksum for the current internal state of the remote. + // Stored the checksum value in the last byte. + for (uint8_t checksum_byte = 0; checksum_byte < TCL112_STATE_LENGTH - 1; checksum_byte++) + remote_state[TCL112_STATE_LENGTH - 1] += remote_state[checksum_byte]; + + ESP_LOGV(TAG, "Sending tcl code: %u", remote_state[7]); + + auto transmit = this->transmitter_->transmit(); + auto data = transmit.get_data(); + + data->set_carrier_frequency(38000); + + // Header + data->mark(TCL112_HEADER_MARK); + data->space(TCL112_HEADER_SPACE); + // Data + for (uint8_t i : remote_state) + for (uint8_t j = 0; j < 8; j++) { + data->mark(TCL112_BIT_MARK); + bool bit = i & (1 << j); + data->space(bit ? TCL112_ONE_SPACE : TCL112_ZERO_SPACE); + } + // Footer + data->mark(TCL112_BIT_MARK); + data->space(TCL112_GAP); + + transmit.perform(); +} + +} // namespace tcl112 +} // namespace esphome diff --git a/esphome/components/tcl112/tcl112.h b/esphome/components/tcl112/tcl112.h new file mode 100644 index 0000000000..0b80dedbef --- /dev/null +++ b/esphome/components/tcl112/tcl112.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/climate/climate.h" +#include "esphome/components/remote_base/remote_base.h" +#include "esphome/components/remote_transmitter/remote_transmitter.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace tcl112 { + +class Tcl112Climate : public climate::Climate, public Component { + public: + void setup() override; + void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { + this->transmitter_ = transmitter; + } + void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } + void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } + void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } + + protected: + /// Override control to change settings of the climate device. + void control(const climate::ClimateCall &call) override; + /// Return the traits of this controller. + climate::ClimateTraits traits() override; + + /// Transmit via IR the state of this climate controller. + void transmit_state_(); + + bool supports_cool_{true}; + bool supports_heat_{true}; + + remote_transmitter::RemoteTransmitterComponent *transmitter_; + sensor::Sensor *sensor_{nullptr}; +}; + +} // namespace tcl112 +} // namespace esphome diff --git a/esphome/components/template/cover/__init__.py b/esphome/components/template/cover/__init__.py index 808318ac81..13370d749c 100644 --- a/esphome/components/template/cover/__init__.py +++ b/esphome/components/template/cover/__init__.py @@ -4,7 +4,7 @@ from esphome import automation from esphome.components import cover from esphome.const import CONF_ASSUMED_STATE, CONF_CLOSE_ACTION, CONF_CURRENT_OPERATION, CONF_ID, \ CONF_LAMBDA, CONF_OPEN_ACTION, CONF_OPTIMISTIC, CONF_POSITION, CONF_RESTORE_MODE, \ - CONF_STATE, CONF_STOP_ACTION + CONF_STATE, CONF_STOP_ACTION, CONF_TILT, CONF_TILT_ACTION, CONF_TILT_LAMBDA from .. import template_ns TemplateCover = template_ns.class_('TemplateCover', cover.Cover, cg.Component) @@ -24,6 +24,8 @@ CONFIG_SCHEMA = cover.COVER_SCHEMA.extend({ cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_CLOSE_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_STOP_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_TILT_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_TILT_LAMBDA): cv.returning_lambda, cv.Optional(CONF_RESTORE_MODE, default='RESTORE'): cv.enum(RESTORE_MODES, upper=True), }).extend(cv.COMPONENT_SCHEMA) @@ -42,6 +44,14 @@ def to_code(config): yield automation.build_automation(var.get_close_trigger(), [], config[CONF_CLOSE_ACTION]) if CONF_STOP_ACTION in config: yield automation.build_automation(var.get_stop_trigger(), [], config[CONF_STOP_ACTION]) + if CONF_TILT_ACTION in config: + yield automation.build_automation(var.get_tilt_trigger(), [(float, 'tilt')], + config[CONF_TILT_ACTION]) + cg.add(var.set_has_tilt(True)) + if CONF_TILT_LAMBDA in config: + tilt_template_ = yield cg.process_lambda(config[CONF_TILT_LAMBDA], [], + return_type=cg.optional.template(float)) + cg.add(var.set_tilt_lambda(tilt_template_)) cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) cg.add(var.set_assumed_state(config[CONF_ASSUMED_STATE])) @@ -53,6 +63,7 @@ def to_code(config): cv.Exclusive(CONF_STATE, 'pos'): cv.templatable(cover.validate_cover_state), cv.Exclusive(CONF_POSITION, 'pos'): cv.templatable(cv.zero_to_one_float), cv.Optional(CONF_CURRENT_OPERATION): cv.templatable(cover.validate_cover_operation), + cv.Optional(CONF_TILT): cv.templatable(cv.zero_to_one_float), })) def cover_template_publish_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) @@ -63,6 +74,9 @@ def cover_template_publish_to_code(config, action_id, template_arg, args): if CONF_POSITION in config: template_ = yield cg.templatable(config[CONF_POSITION], args, float) cg.add(var.set_position(template_)) + if CONF_TILT in config: + template_ = yield cg.templatable(config[CONF_TILT], args, float) + cg.add(var.set_tilt(template_)) if CONF_CURRENT_OPERATION in config: template_ = yield cg.templatable(config[CONF_CURRENT_OPERATION], args, cover.CoverOperation) cg.add(var.set_current_operation(template_)) diff --git a/esphome/components/template/cover/template_cover.cpp b/esphome/components/template/cover/template_cover.cpp index dd1a081aaa..381e6dd6cd 100644 --- a/esphome/components/template/cover/template_cover.cpp +++ b/esphome/components/template/cover/template_cover.cpp @@ -74,19 +74,12 @@ void TemplateCover::control(const CoverCall &call) { this->stop_prev_trigger_(); this->stop_trigger_->trigger(); this->prev_command_trigger_ = this->stop_trigger_; - this->current_operation = COVER_OPERATION_IDLE; this->publish_state(); } if (call.get_position().has_value()) { auto pos = *call.get_position(); this->stop_prev_trigger_(); - if (pos < this->position) { - this->current_operation = COVER_OPERATION_CLOSING; - } else if (pos > this->position) { - this->current_operation = COVER_OPERATION_OPENING; - } - if (pos == COVER_OPEN) { this->open_trigger_->trigger(); this->prev_command_trigger_ = this->open_trigger_; diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index a81255a254..634de26f00 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -52,6 +52,15 @@ def _tz_dst_str(dt): _tz_timedelta(td)) +def _non_dst_tz(tz, dt): + tzname = tz.tzname(dt) + utcoffset = tz.utcoffset(dt) + _LOGGER.info("Detected timezone '%s' with UTC offset %s", + tzname, _tz_timedelta(utcoffset)) + tzbase = '{}{}'.format(tzname, _tz_timedelta(-1 * utcoffset)) + return tzbase + + def convert_tz(pytz_obj): tz = pytz_obj @@ -59,23 +68,29 @@ def convert_tz(pytz_obj): first_january = datetime.datetime(year=now.year, month=1, day=1) if not isinstance(tz, pytz.tzinfo.DstTzInfo): - tzname = tz.tzname(first_january) - utcoffset = tz.utcoffset(first_january) - _LOGGER.info("Detected timezone '%s' with UTC offset %s", - tzname, _tz_timedelta(utcoffset)) - tzbase = '{}{}'.format(tzname, _tz_timedelta(-1 * utcoffset)) - return tzbase + return _non_dst_tz(tz, first_january) # pylint: disable=protected-access transition_times = tz._utc_transition_times transition_info = tz._transition_info idx = max(0, bisect.bisect_right(transition_times, now)) + if idx >= len(transition_times): + return _non_dst_tz(tz, now) + idx1, idx2 = idx, idx + 1 dstoffset1 = transition_info[idx1][1] if dstoffset1 == datetime.timedelta(seconds=0): # Normalize to 1 being DST on idx1, idx2 = idx + 1, idx + 2 + if idx2 >= len(transition_times): + return _non_dst_tz(tz, now) + + if transition_times[idx2].year > now.year + 1: + # Next transition is scheduled after this year + # Probably a scheduler timezone change. + return _non_dst_tz(tz, now) + utcoffset_on, _, tzname_on = transition_info[idx1] utcoffset_off, _, tzname_off = transition_info[idx2] dst_begins_utc = transition_times[idx1] @@ -244,10 +259,12 @@ def validate_tz(value): value = cv.string_strict(value) try: - return convert_tz(pytz.timezone(value)) - except Exception: # pylint: disable=broad-except + pytz_obj = pytz.timezone(value) + except pytz.UnknownTimeZoneError: # pylint: disable=broad-except return value + return convert_tz(pytz_obj) + TIME_SCHEMA = cv.Schema({ cv.Optional(CONF_TIMEZONE, default=detect_tz): validate_tz, diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 81524826be..96722229b1 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -12,7 +12,6 @@ static const char *TAG = "time"; RealTimeClock::RealTimeClock() = default; void RealTimeClock::call_setup() { - this->setup_internal_(); setenv("TZ", this->timezone_.c_str(), 1); tzset(); this->setup(); diff --git a/esphome/components/tsl2561/sensor.py b/esphome/components/tsl2561/sensor.py index e2e333cc81..d51ece09d5 100644 --- a/esphome/components/tsl2561/sensor.py +++ b/esphome/components/tsl2561/sensor.py @@ -32,7 +32,7 @@ TSL2561Sensor = tsl2561_ns.class_('TSL2561Sensor', sensor.Sensor, cg.PollingComp CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({ cv.GenerateID(): cv.declare_id(TSL2561Sensor), - cv.Optional(CONF_INTEGRATION_TIME, default=402): validate_integration_time, + cv.Optional(CONF_INTEGRATION_TIME, default='402ms'): validate_integration_time, cv.Optional(CONF_GAIN, default='1X'): cv.enum(GAINS, upper=True), cv.Optional(CONF_IS_CS_PACKAGE, default=False): cv.boolean, }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39)) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 06a0ed94df..5c983be002 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -1,15 +1,27 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome import pins -from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_RX_PIN, CONF_TX_PIN, CONF_UART_ID +from esphome import pins, automation +from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_RX_PIN, CONF_TX_PIN, CONF_UART_ID, CONF_DATA from esphome.core import CORE, coroutine +from esphome.py_compat import text_type, binary_type, char_to_byte uart_ns = cg.esphome_ns.namespace('uart') UARTComponent = uart_ns.class_('UARTComponent', cg.Component) UARTDevice = uart_ns.class_('UARTDevice') +UARTWriteAction = uart_ns.class_('UARTWriteAction', automation.Action) MULTI_CONF = True +def validate_raw_data(value): + if isinstance(value, text_type): + return value.encode('utf-8') + if isinstance(value, str): + return value + if isinstance(value, list): + return cv.Schema([cv.hex_uint8_t])(value) + raise cv.Invalid("data must either be a string wrapped in quotes or a list of bytes") + + def validate_rx_pin(value): value = pins.input_pin(value) if CORE.is_esp8266 and value >= 16: @@ -26,6 +38,7 @@ CONFIG_SCHEMA = cv.All(cv.Schema({ def to_code(config): + cg.add_global(uart_ns.using) var = cg.new_Pvariable(config[CONF_ID]) yield cg.register_component(var, config) @@ -51,3 +64,22 @@ def register_uart_device(var, config): """ parent = yield cg.get_variable(config[CONF_UART_ID]) cg.add(var.set_uart_parent(parent)) + + +@automation.register_action('uart.write', UARTWriteAction, cv.maybe_simple_value({ + cv.GenerateID(): cv.use_id(UARTComponent), + cv.Required(CONF_DATA): cv.templatable(validate_raw_data), +}, key=CONF_DATA)) +def uart_write_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + yield cg.register_parented(var, config[CONF_ID]) + data = config[CONF_DATA] + if isinstance(data, binary_type): + data = [char_to_byte(x) for x in data] + + if cg.is_template(data): + templ = yield cg.templatable(data, args, cg.std_vector.template(cg.uint8)) + cg.add(var.set_data_template(templ)) + else: + cg.add(var.set_data_static(data)) + yield var diff --git a/esphome/components/uart/automation.h b/esphome/components/uart/automation.h new file mode 100644 index 0000000000..9686f94413 --- /dev/null +++ b/esphome/components/uart/automation.h @@ -0,0 +1,36 @@ +#pragma once + +#include "uart.h" +#include "esphome/core/automation.h" + +namespace esphome { +namespace uart { + +template class UARTWriteAction : public Action, public Parented { + public: + void set_data_template(std::function(Ts...)> func) { + this->data_func_ = func; + this->static_ = false; + } + void set_data_static(const std::vector &data) { + this->data_static_ = data; + this->static_ = true; + } + + void play(Ts... x) override { + if (this->static_) { + this->parent_->write_array(this->data_static_); + } else { + auto val = this->data_func_(x...); + this->parent_->write_array(val); + } + } + + protected: + bool static_{false}; + std::function(Ts...)> data_func_{}; + std::vector data_static_{}; +}; + +} // namespace uart +} // namespace esphome diff --git a/esphome/components/uart/switch/__init__.py b/esphome/components/uart/switch/__init__.py index dae63a2add..b6f622604f 100644 --- a/esphome/components/uart/switch/__init__.py +++ b/esphome/components/uart/switch/__init__.py @@ -3,27 +3,17 @@ import esphome.config_validation as cv from esphome.components import switch, uart from esphome.const import CONF_DATA, CONF_ID, CONF_INVERTED from esphome.core import HexInt -from esphome.py_compat import text_type, binary_type, char_to_byte -from .. import uart_ns +from esphome.py_compat import binary_type, char_to_byte +from .. import uart_ns, validate_raw_data DEPENDENCIES = ['uart'] UARTSwitch = uart_ns.class_('UARTSwitch', switch.Switch, uart.UARTDevice, cg.Component) -def validate_data(value): - if isinstance(value, text_type): - return value.encode('utf-8') - if isinstance(value, str): - return value - if isinstance(value, list): - return cv.Schema([cv.hex_uint8_t])(value) - raise cv.Invalid("data must either be a string wrapped in quotes or a list of bytes") - - CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({ cv.GenerateID(): cv.declare_id(UARTSwitch), - cv.Required(CONF_DATA): validate_data, + cv.Required(CONF_DATA): validate_raw_data, cv.Optional(CONF_INVERTED): cv.invalid("UART switches do not support inverted mode!"), }).extend(uart.UART_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 005536de4a..f642d4ee81 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -30,7 +30,7 @@ class ESP8266SoftwareSerial { uint32_t bit_time_{0}; uint8_t *rx_buffer_{nullptr}; - size_t rx_buffer_size_{64}; + size_t rx_buffer_size_{512}; volatile size_t rx_in_pos_{0}; size_t rx_out_pos_{0}; ISRInternalGPIOPin *tx_pin_{nullptr}; @@ -49,6 +49,7 @@ class UARTComponent : public Component, public Stream { void write_byte(uint8_t data); void write_array(const uint8_t *data, size_t len); + void write_array(const std::vector &data) { this->write_array(&data[0], data.size()); } void write_str(const char *str); @@ -97,6 +98,7 @@ class UARTDevice : public Stream { void write_byte(uint8_t data) { this->parent_->write_byte(data); } void write_array(const uint8_t *data, size_t len) { this->parent_->write_array(data, len); } + void write_array(const std::vector &data) { this->parent_->write_array(data); } void write_str(const char *str) { this->parent_->write_str(str); } diff --git a/esphome/components/uptime/uptime_sensor.cpp b/esphome/components/uptime/uptime_sensor.cpp index a66ca0636f..f047724768 100644 --- a/esphome/components/uptime/uptime_sensor.cpp +++ b/esphome/components/uptime/uptime_sensor.cpp @@ -1,4 +1,4 @@ -#include "esphome/components/uptime/uptime_sensor.h" +#include "uptime_sensor.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" diff --git a/esphome/components/version/version_text_sensor.cpp b/esphome/components/version/version_text_sensor.cpp index 1e59deff96..6aedfdedcd 100644 --- a/esphome/components/version/version_text_sensor.cpp +++ b/esphome/components/version/version_text_sensor.cpp @@ -1,6 +1,7 @@ #include "version_text_sensor.h" #include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/version.h" namespace esphome { namespace version { diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 77a0bbc689..cb7de80918 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -24,7 +24,7 @@ MODELS = { '2.90in': ('a', WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN), '2.70in': ('b', WaveshareEPaper2P7In), '4.20in': ('b', WaveshareEPaper4P2In), - '7.50in': ('b', WaveshareEPaperTypeBModel), + '7.50in': ('b', WaveshareEPaper7P5In), } diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 392134f52b..c4d73c49f6 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -8,29 +8,6 @@ namespace waveshare_epaper { static const char *TAG = "waveshare_epaper"; -static const uint8_t WAVESHARE_EPAPER_COMMAND_DRIVER_OUTPUT_CONTROL = 0x01; -static const uint8_t WAVESHARE_EPAPER_COMMAND_BOOSTER_SOFT_START_CONTROL = 0x0C; -// static const uint8_t WAVESHARE_EPAPER_COMMAND_GATE_SCAN_START_POSITION = 0x0F; -// static const uint8_t WAVESHARE_EPAPER_COMMAND_DEEP_SLEEP_MODE = 0x10; -static const uint8_t WAVESHARE_EPAPER_COMMAND_DATA_ENTRY_MODE_SETTING = 0x11; -// static const uint8_t WAVESHARE_EPAPER_COMMAND_SW_RESET = 0x12; -// static const uint8_t WAVESHARE_EPAPER_COMMAND_TEMPERATURE_SENSOR_CONTROL = 0x1A; -static const uint8_t WAVESHARE_EPAPER_COMMAND_MASTER_ACTIVATION = 0x20; -// static const uint8_t WAVESHARE_EPAPER_COMMAND_DISPLAY_UPDATE_CONTROL_1 = 0x21; -static const uint8_t WAVESHARE_EPAPER_COMMAND_DISPLAY_UPDATE_CONTROL_2 = 0x22; -static const uint8_t WAVESHARE_EPAPER_COMMAND_WRITE_RAM = 0x24; -static const uint8_t WAVESHARE_EPAPER_COMMAND_WRITE_VCOM_REGISTER = 0x2C; -static const uint8_t WAVESHARE_EPAPER_COMMAND_WRITE_LUT_REGISTER = 0x32; -static const uint8_t WAVESHARE_EPAPER_COMMAND_SET_DUMMY_LINE_PERIOD = 0x3A; -static const uint8_t WAVESHARE_EPAPER_COMMAND_SET_GATE_TIME = 0x3B; -static const uint8_t WAVESHARE_EPAPER_COMMAND_BORDER_WAVEFORM_CONTROL = 0x3C; -static const uint8_t WAVESHARE_EPAPER_COMMAND_SET_RAM_X_ADDRESS_START_END_POSITION = 0x44; -static const uint8_t WAVESHARE_EPAPER_COMMAND_SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45; -static const uint8_t WAVESHARE_EPAPER_COMMAND_SET_RAM_X_ADDRESS_COUNTER = 0x4E; -static const uint8_t WAVESHARE_EPAPER_COMMAND_SET_RAM_Y_ADDRESS_COUNTER = 0x4F; -static const uint8_t WAVESHARE_EPAPER_COMMAND_TERMINATE_FRAME_READ_WRITE = 0xFF; - -// not in .text section since only 30 bytes static const uint8_t FULL_UPDATE_LUT[30] = {0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22, 0x66, 0x69, 0x69, 0x59, 0x58, 0x99, 0x99, 0x88, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xB4, 0x13, 0x51, 0x35, 0x51, 0x51, 0x19, 0x01, 0x00}; @@ -52,13 +29,7 @@ void WaveshareEPaper::setup_pins_() { } this->spi_setup(); - // Reset - if (this->reset_pin_ != nullptr) { - this->reset_pin_->digital_write(false); - delay(200); - this->reset_pin_->digital_write(true); - delay(200); - } + this->reset_(); } float WaveshareEPaper::get_setup_priority() const { return setup_priority::PROCESSOR; } void WaveshareEPaper::command(uint8_t value) { @@ -71,7 +42,6 @@ void WaveshareEPaper::data(uint8_t value) { this->write_byte(value); this->end_data_(); } -bool WaveshareEPaper::is_device_msb_first() { return true; } bool WaveshareEPaper::wait_until_idle_() { if (this->busy_pin_ == nullptr) { return true; @@ -110,7 +80,6 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, int color) this->buffer_[pos] &= ~(0x80 >> subpos); } uint32_t WaveshareEPaper::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal() / 8u; } -bool WaveshareEPaper::is_device_high_speed() { return true; } void WaveshareEPaper::start_command_() { this->dc_pin_->digital_write(false); this->enable(); @@ -121,34 +90,39 @@ void WaveshareEPaper::start_data_() { this->enable(); } void WaveshareEPaper::end_data_() { this->disable(); } +void WaveshareEPaper::on_safe_shutdown() { this->deep_sleep(); } // ======================================================== // Type A // ======================================================== -void WaveshareEPaperTypeA::setup() { - this->setup_pins_(); - - this->command(WAVESHARE_EPAPER_COMMAND_DRIVER_OUTPUT_CONTROL); +void WaveshareEPaperTypeA::initialize() { + // COMMAND DRIVER OUTPUT CONTROL + this->command(0x01); this->data(this->get_height_internal() - 1); this->data((this->get_height_internal() - 1) >> 8); this->data(0x00); // ? GD = 0, SM = 0, TB = 0 - this->command(WAVESHARE_EPAPER_COMMAND_BOOSTER_SOFT_START_CONTROL); // ? + // COMMAND BOOSTER SOFT START CONTROL + this->command(0x0C); this->data(0xD7); this->data(0xD6); this->data(0x9D); - this->command(WAVESHARE_EPAPER_COMMAND_WRITE_VCOM_REGISTER); // ? + // COMMAND WRITE VCOM REGISTER + this->command(0x2C); this->data(0xA8); - this->command(WAVESHARE_EPAPER_COMMAND_SET_DUMMY_LINE_PERIOD); // ? + // COMMAND SET DUMMY LINE PERIOD + this->command(0x3A); this->data(0x1A); - this->command(WAVESHARE_EPAPER_COMMAND_SET_GATE_TIME); // 2µs per row - this->data(0x08); + // COMMAND SET GATE TIME + this->command(0x3B); + this->data(0x08); // 2µs per row - this->command(WAVESHARE_EPAPER_COMMAND_DATA_ENTRY_MODE_SETTING); + // COMMAND DATA ENTRY MODE SETTING + this->command(0x11); this->data(0x03); // from top left to bottom right } void WaveshareEPaperTypeA::dump_config() { @@ -186,18 +160,22 @@ void HOT WaveshareEPaperTypeA::display() { } // Set x & y regions we want to write to (full) - this->command(WAVESHARE_EPAPER_COMMAND_SET_RAM_X_ADDRESS_START_END_POSITION); + // COMMAND SET RAM X ADDRESS START END POSITION + this->command(0x44); this->data(0x00); this->data((this->get_width_internal() - 1) >> 3); - this->command(WAVESHARE_EPAPER_COMMAND_SET_RAM_Y_ADDRESS_START_END_POSITION); + // COMMAND SET RAM Y ADDRESS START END POSITION + this->command(0x45); this->data(0x00); this->data(0x00); this->data(this->get_height_internal() - 1); this->data((this->get_height_internal() - 1) >> 8); - this->command(WAVESHARE_EPAPER_COMMAND_SET_RAM_X_ADDRESS_COUNTER); + // COMMAND SET RAM X ADDRESS COUNTER + this->command(0x4E); this->data(0x00); - this->command(WAVESHARE_EPAPER_COMMAND_SET_RAM_Y_ADDRESS_COUNTER); + // COMMAND SET RAM Y ADDRESS COUNTER + this->command(0x4F); this->data(0x00); this->data(0x00); @@ -206,15 +184,19 @@ void HOT WaveshareEPaperTypeA::display() { return; } - this->command(WAVESHARE_EPAPER_COMMAND_WRITE_RAM); + // COMMAND WRITE RAM + this->command(0x24); this->start_data_(); this->write_array(this->buffer_, this->get_buffer_length_()); this->end_data_(); - this->command(WAVESHARE_EPAPER_COMMAND_DISPLAY_UPDATE_CONTROL_2); + // COMMAND DISPLAY UPDATE CONTROL 2 + this->command(0x22); this->data(0xC4); - this->command(WAVESHARE_EPAPER_COMMAND_MASTER_ACTIVATION); - this->command(WAVESHARE_EPAPER_COMMAND_TERMINATE_FRAME_READ_WRITE); + // COMMAND MASTER ACTIVATION + this->command(0x20); + // COMMAND TERMINATE FRAME READ WRITE + this->command(0xFF); this->status_clear_warning(); } @@ -241,7 +223,8 @@ int WaveshareEPaperTypeA::get_height_internal() { return 0; } void WaveshareEPaperTypeA::write_lut_(const uint8_t *lut) { - this->command(WAVESHARE_EPAPER_COMMAND_WRITE_LUT_REGISTER); + // COMMAND WRITE LUT REGISTER + this->command(0x32); for (uint8_t i = 0; i < 30; i++) this->data(lut[i]); } @@ -253,47 +236,9 @@ void WaveshareEPaperTypeA::set_full_update_every(uint32_t full_update_every) { // ======================================================== // Type B // ======================================================== - -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_PANEL_SETTING = 0x00; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_POWER_SETTING = 0x01; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_POWER_OFF = 0x02; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_POWER_OFF_SEQUENCE_SETTING = 0x03; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_POWER_ON = 0x04; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_POWER_MEASURE = 0x05; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_BOOSTER_SOFT_START = 0x06; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_DEEP_SLEEP = 0x07; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_DATA_START_TRANSMISSION_1 = 0x10; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_DATA_STOP = 0x11; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_DISPLAY_REFRESH = 0x12; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_DATA_START_TRANSMISSION_2 = 0x13; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_PARTIAL_DATA_START_TRANSMISSION_1 = 0x14; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_PARTIAL_DATA_START_TRANSMISSION_2 = 0x15; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_PARTIAL_DISPLAY_REFRESH = 0x16; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_LUT_FOR_VCOM = 0x20; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_LUT_WHITE_TO_WHITE = 0x21; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_LUT_BLACK_TO_WHITE = 0x22; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_LUT_WHITE_TO_BLACK = 0x23; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_LUT_BLACK_TO_BLACK = 0x24; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_PLL_CONTROL = 0x30; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_TEMPERATURE_SENSOR_COMMAND = 0x40; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_TEMPERATURE_SENSOR_CALIBRATION = 0x41; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_TEMPERATURE_SENSOR_WRITE = 0x42; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_TEMPERATURE_SENSOR_READ = 0x43; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_VCOM_AND_DATA_INTERVAL_SETTING = 0x50; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_LOW_POWER_DETECTION = 0x51; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_TCON_SETTING = 0x60; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_RESOLUTION_SETTING = 0x61; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_GET_STATUS = 0x71; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_AUTO_MEASURE_VCOM = 0x80; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_VCOM_VALUE = 0x81; -static const uint8_t WAVESHARE_EPAPER_B_COMMAND_VCM_DC_SETTING_REGISTER = 0x82; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_PARTIAL_WINDOW = 0x90; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_PARTIAL_IN = 0x91; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_PARTIAL_OUT = 0x92; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_PROGRAM_MODE = 0xA0; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_ACTIVE_PROGRAM = 0xA1; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_READ_OTP_DATA = 0xA2; -// static const uint8_t WAVESHARE_EPAPER_B_COMMAND_POWER_SAVING = 0xE3; +// Datasheet: +// - https://www.waveshare.com/w/upload/7/7f/4.2inch-e-paper-b-specification.pdf +// - https://github.com/soonuse/epd-library-arduino/blob/master/4.2inch_e-paper/epd4in2/ static const uint8_t LUT_VCOM_DC_2_7[44] = { 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x05, 0x00, 0x32, 0x32, 0x00, 0x00, 0x02, 0x00, @@ -325,18 +270,17 @@ static const uint8_t LUT_BLACK_TO_BLACK_2_7[42] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; -void WaveshareEPaper2P7In::setup() { - this->setup_pins_(); - // this->buffer_.init(this->get_width_(), this->get_height_()); - - this->command(WAVESHARE_EPAPER_B_COMMAND_POWER_SETTING); +void WaveshareEPaper2P7In::initialize() { + // command power setting + this->command(0x01); this->data(0x03); // VDS_EN, VDG_EN this->data(0x00); // VCOM_HV, VGHL_LV[1], VGHL_LV[0] this->data(0x2B); // VDH this->data(0x2B); // VDL this->data(0x09); // VDHR - this->command(WAVESHARE_EPAPER_B_COMMAND_BOOSTER_SOFT_START); + // command booster soft start + this->command(0x06); this->data(0x07); this->data(0x07); this->data(0x17); @@ -364,51 +308,66 @@ void WaveshareEPaper2P7In::setup() { this->data(0x73); this->data(0x41); - this->command(WAVESHARE_EPAPER_B_COMMAND_PARTIAL_DISPLAY_REFRESH); + // command partial display refresh + this->command(0x16); this->data(0x00); - this->command(WAVESHARE_EPAPER_B_COMMAND_POWER_ON); + // command power on + this->command(0x04); this->wait_until_idle_(); delay(10); - this->command(WAVESHARE_EPAPER_B_COMMAND_PANEL_SETTING); + // Command panel setting + this->command(0x00); this->data(0xAF); // KW-BF KWR-AF BWROTP 0f - this->command(WAVESHARE_EPAPER_B_COMMAND_PLL_CONTROL); + // command pll control + this->command(0x30); this->data(0x3A); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ - this->command(WAVESHARE_EPAPER_B_COMMAND_VCM_DC_SETTING_REGISTER); + // COMMAND VCM DC SETTING + this->command(0x82); this->data(0x12); delay(2); - this->command(WAVESHARE_EPAPER_B_COMMAND_LUT_FOR_VCOM); + // COMMAND LUT FOR VCOM + this->command(0x20); for (uint8_t i : LUT_VCOM_DC_2_7) this->data(i); - this->command(WAVESHARE_EPAPER_B_COMMAND_LUT_WHITE_TO_WHITE); + + // COMMAND LUT WHITE TO WHITE + this->command(0x21); for (uint8_t i : LUT_WHITE_TO_WHITE_2_7) this->data(i); - this->command(WAVESHARE_EPAPER_B_COMMAND_LUT_BLACK_TO_WHITE); + // COMMAND LUT BLACK TO WHITE + this->command(0x22); for (uint8_t i : LUT_BLACK_TO_WHITE_2_7) this->data(i); - this->command(WAVESHARE_EPAPER_B_COMMAND_LUT_WHITE_TO_BLACK); + // COMMAND LUT WHITE TO BLACK + this->command(0x23); for (uint8_t i : LUT_WHITE_TO_BLACK_2_7) this->data(i); - this->command(WAVESHARE_EPAPER_B_COMMAND_LUT_BLACK_TO_BLACK); + // COMMAND LUT BLACK TO BLACK + this->command(0x24); for (uint8_t i : LUT_BLACK_TO_BLACK_2_7) this->data(i); } void HOT WaveshareEPaper2P7In::display() { - // TODO check active frame buffer to only transmit once / use partial transmits - this->command(WAVESHARE_EPAPER_B_COMMAND_DATA_START_TRANSMISSION_1); + // COMMAND DATA START TRANSMISSION 1 + this->command(0x10); delay(2); this->start_data_(); this->write_array(this->buffer_, this->get_buffer_length_()); this->end_data_(); delay(2); - this->command(WAVESHARE_EPAPER_B_COMMAND_DATA_START_TRANSMISSION_2); + + // COMMAND DATA START TRANSMISSION 2 + this->command(0x13); delay(2); this->start_data_(); this->write_array(this->buffer_, this->get_buffer_length_()); this->end_data_(); - this->command(WAVESHARE_EPAPER_B_COMMAND_DISPLAY_REFRESH); + + // COMMAND DISPLAY REFRESH + this->command(0x12); } int WaveshareEPaper2P7In::get_width_internal() { return 176; } int WaveshareEPaper2P7In::get_height_internal() { return 264; } @@ -449,77 +408,91 @@ static const uint8_t LUT_WHITE_TO_BLACK_4_2[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; -void WaveshareEPaper4P2In::setup() { - this->setup_pins_(); +void WaveshareEPaper4P2In::initialize() { + // https://www.waveshare.com/w/upload/7/7f/4.2inch-e-paper-b-specification.pdf - page 8 - this->command(WAVESHARE_EPAPER_B_COMMAND_POWER_SETTING); + // COMMAND POWER SETTING + this->command(0x01); this->data(0x03); // VDS_EN, VDG_EN this->data(0x00); // VCOM_HV, VGHL_LV[1], VGHL_LV[0] this->data(0x2B); // VDH this->data(0x2B); // VDL this->data(0xFF); // VDHR - this->command(WAVESHARE_EPAPER_B_COMMAND_BOOSTER_SOFT_START); - this->data(0x17); - this->data(0x17); - this->data(0x17); + // COMMAND BOOSTER SOFT START + this->command(0x06); + this->data(0x17); // PHA + this->data(0x17); // PHB + this->data(0x17); // PHC - this->command(WAVESHARE_EPAPER_B_COMMAND_POWER_ON); + // COMMAND POWER ON + this->command(0x04); this->wait_until_idle_(); delay(10); - this->command(WAVESHARE_EPAPER_B_COMMAND_PANEL_SETTING); + // COMMAND PANEL SETTING + this->command(0x00); this->data(0xBF); // KW-BF KWR-AF BWROTP 0f this->data(0x0B); - this->command(WAVESHARE_EPAPER_B_COMMAND_PLL_CONTROL); + // COMMAND PLL CONTROL + this->command(0x30); this->data(0x3C); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ delay(2); - this->command(WAVESHARE_EPAPER_B_COMMAND_LUT_FOR_VCOM); + // COMMAND LUT FOR VCOM + this->command(0x20); for (uint8_t i : LUT_VCOM_DC_4_2) this->data(i); - this->command(WAVESHARE_EPAPER_B_COMMAND_LUT_WHITE_TO_WHITE); + // COMMAND LUT WHITE TO WHITE + this->command(0x21); for (uint8_t i : LUT_WHITE_TO_WHITE_4_2) this->data(i); - this->command(WAVESHARE_EPAPER_B_COMMAND_LUT_BLACK_TO_WHITE); + // COMMAND LUT BLACK TO WHITE + this->command(0x22); for (uint8_t i : LUT_BLACK_TO_WHITE_4_2) this->data(i); - this->command(WAVESHARE_EPAPER_B_COMMAND_LUT_WHITE_TO_BLACK); + // COMMAND LUT WHITE TO BLACK + this->command(0x23); for (uint8_t i : LUT_WHITE_TO_BLACK_4_2) this->data(i); - this->command(WAVESHARE_EPAPER_B_COMMAND_LUT_BLACK_TO_BLACK); + // COMMAND LUT BLACK TO BLACK + this->command(0x24); for (uint8_t i : LUT_BLACK_TO_BLACK_4_2) this->data(i); } void HOT WaveshareEPaper4P2In::display() { - this->command(WAVESHARE_EPAPER_B_COMMAND_RESOLUTION_SETTING); + // COMMAND RESOLUTION SETTING + this->command(0x61); this->data(0x01); this->data(0x90); this->data(0x01); this->data(0x2C); - this->command(WAVESHARE_EPAPER_B_COMMAND_VCM_DC_SETTING_REGISTER); + // COMMAND VCM DC SETTING REGISTER + this->command(0x82); this->data(0x12); - this->command(WAVESHARE_EPAPER_B_COMMAND_VCOM_AND_DATA_INTERVAL_SETTING); + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); this->data(0x97); - // TODO check active frame buffer to only transmit once / use partial transmits - this->command(WAVESHARE_EPAPER_B_COMMAND_DATA_START_TRANSMISSION_1); + // COMMAND DATA START TRANSMISSION 1 + this->command(0x10); delay(2); this->start_data_(); this->write_array(this->buffer_, this->get_buffer_length_()); this->end_data_(); delay(2); - this->command(WAVESHARE_EPAPER_B_COMMAND_DATA_START_TRANSMISSION_2); + // COMMAND DATA START TRANSMISSION 2 + this->command(0x13); delay(2); this->start_data_(); this->write_array(this->buffer_, this->get_buffer_length_()); this->end_data_(); - this->command(WAVESHARE_EPAPER_B_COMMAND_DISPLAY_REFRESH); + // COMMAND DISPLAY REFRESH + this->command(0x12); } int WaveshareEPaper4P2In::get_width_internal() { return 400; } int WaveshareEPaper4P2In::get_height_internal() { return 300; } -bool WaveshareEPaper4P2In::is_device_high_speed() { return false; } void WaveshareEPaper4P2In::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); ESP_LOGCONFIG(TAG, " Model: 4.2in"); @@ -529,52 +502,61 @@ void WaveshareEPaper4P2In::dump_config() { LOG_UPDATE_INTERVAL(this); } -void WaveshareEPaper7P5In::setup() { - this->setup_pins_(); - - this->command(WAVESHARE_EPAPER_B_COMMAND_POWER_SETTING); +void WaveshareEPaper7P5In::initialize() { + // COMMAND POWER SETTING + this->command(0x01); this->data(0x37); this->data(0x00); - this->command(WAVESHARE_EPAPER_B_COMMAND_PANEL_SETTING); + // COMMAND PANEL SETTING + this->command(0x00); this->data(0xCF); this->data(0x0B); - this->command(WAVESHARE_EPAPER_B_COMMAND_BOOSTER_SOFT_START); + // COMMAND BOOSTER SOFT START + this->command(0x06); this->data(0xC7); this->data(0xCC); this->data(0x28); - this->command(WAVESHARE_EPAPER_B_COMMAND_POWER_ON); + // COMMAND POWER ON + this->command(0x04); this->wait_until_idle_(); delay(10); - this->command(WAVESHARE_EPAPER_B_COMMAND_PLL_CONTROL); + // COMMAND PLL CONTROL + this->command(0x30); this->data(0x3C); - this->command(WAVESHARE_EPAPER_B_COMMAND_TEMPERATURE_SENSOR_CALIBRATION); + // COMMAND TEMPERATURE SENSOR CALIBRATION + this->command(0x41); this->data(0x00); - this->command(WAVESHARE_EPAPER_B_COMMAND_VCOM_AND_DATA_INTERVAL_SETTING); + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); this->data(0x77); - this->command(WAVESHARE_EPAPER_B_COMMAND_TCON_SETTING); + // COMMAND TCON SETTING + this->command(0x60); this->data(0x22); - this->command(WAVESHARE_EPAPER_B_COMMAND_RESOLUTION_SETTING); + // COMMAND RESOLUTION SETTING + this->command(0x61); this->data(0x02); this->data(0x80); this->data(0x01); this->data(0x80); - this->command(WAVESHARE_EPAPER_B_COMMAND_VCM_DC_SETTING_REGISTER); + // COMMAND VCM DC SETTING REGISTER + this->command(0x82); this->data(0x1E); this->command(0xE5); this->data(0x03); } void HOT WaveshareEPaper7P5In::display() { - this->command(WAVESHARE_EPAPER_B_COMMAND_DATA_START_TRANSMISSION_1); + // COMMAND DATA START TRANSMISSION 1 + this->command(0x10); this->start_data_(); for (size_t i = 0; i < this->get_buffer_length_(); i++) { @@ -601,7 +583,8 @@ void HOT WaveshareEPaper7P5In::display() { } this->end_data_(); - this->command(WAVESHARE_EPAPER_B_COMMAND_DISPLAY_REFRESH); + // COMMAND DISPLAY REFRESH + this->command(0x12); } int WaveshareEPaper7P5In::get_width_internal() { return 640; } int WaveshareEPaper7P5In::get_height_internal() { return 384; } diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 49bb21bc7f..a8b85c93e9 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -7,23 +7,34 @@ namespace esphome { namespace waveshare_epaper { -class WaveshareEPaper : public PollingComponent, public spi::SPIDevice, public display::DisplayBuffer { +class WaveshareEPaper : public PollingComponent, + public display::DisplayBuffer, + public spi::SPIDevice { public: void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } float get_setup_priority() const override; void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; } - bool is_device_msb_first() override; void command(uint8_t value); void data(uint8_t value); virtual void display() = 0; + virtual void initialize() = 0; + virtual void deep_sleep() = 0; void update() override; void fill(int color) override; + void setup() override { + this->setup_pins_(); + this->initialize(); + } + + void on_safe_shutdown() override; + protected: void draw_absolute_pixel_internal(int x, int y, int color) override; @@ -31,9 +42,16 @@ class WaveshareEPaper : public PollingComponent, public spi::SPIDevice, public d void setup_pins_(); - uint32_t get_buffer_length_(); + void reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(200); + this->reset_pin_->digital_write(true); + delay(200); + } + } - bool is_device_high_speed() override; + uint32_t get_buffer_length_(); void start_command_(); void end_command_(); @@ -55,12 +73,18 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { public: WaveshareEPaperTypeA(WaveshareEPaperTypeAModel model); - void setup() override; + void initialize() override; void dump_config() override; void display() override; + void deep_sleep() override { + // COMMAND DEEP SLEEP MODE + this->command(0x10); + this->wait_until_idle_(); + } + void set_full_update_every(uint32_t full_update_every); protected: @@ -83,12 +107,18 @@ enum WaveshareEPaperTypeBModel { class WaveshareEPaper2P7In : public WaveshareEPaper { public: - void setup() override; + void initialize() override; void display() override; void dump_config() override; + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + protected: int get_width_internal() override; @@ -97,28 +127,64 @@ class WaveshareEPaper2P7In : public WaveshareEPaper { class WaveshareEPaper4P2In : public WaveshareEPaper { public: - void setup() override; + void initialize() override; void display() override; void dump_config() override; + void deep_sleep() override { + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x17); // border floating + + // COMMAND VCM DC SETTING + this->command(0x82); + // COMMAND PANEL SETTING + this->command(0x00); + + delay(100); + + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + delay(100); + + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + protected: int get_width_internal() override; int get_height_internal() override; - - bool is_device_high_speed() override; }; class WaveshareEPaper7P5In : public WaveshareEPaper { public: - void setup() override; + void initialize() override; void display() override; void dump_config() override; + void deep_sleep() override { + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + protected: int get_width_internal() override; diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 206fc2c733..ea7b179d1e 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -1,10 +1,11 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.components import web_server_base +from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.const import CONF_CSS_URL, CONF_ID, CONF_JS_URL, CONF_PORT -from esphome.core import CORE, coroutine_with_priority +from esphome.core import coroutine_with_priority -DEPENDENCIES = ['network'] -AUTO_LOAD = ['json'] +AUTO_LOAD = ['json', 'web_server_base'] web_server_ns = cg.esphome_ns.namespace('web_server') WebServer = web_server_ns.class_('WebServer', cg.Component, cg.Controller) @@ -14,18 +15,18 @@ CONFIG_SCHEMA = cv.Schema({ cv.Optional(CONF_PORT, default=80): cv.port, cv.Optional(CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css"): cv.string, cv.Optional(CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js"): cv.string, + + cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(web_server_base.WebServerBase), }).extend(cv.COMPONENT_SCHEMA) @coroutine_with_priority(40.0) def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + paren = yield cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) + + var = cg.new_Pvariable(config[CONF_ID], paren) yield cg.register_component(var, config) - cg.add(var.set_port(config[CONF_PORT])) + cg.add(paren.set_port(config[CONF_PORT])) cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) - - if CORE.is_esp32: - cg.add_library('FS', None) - cg.add_library('ESP Async WebServer', '1.1.1') diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 882af4b995..fe36d6c2ce 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -6,13 +6,6 @@ #include "StreamString.h" -#ifdef ARDUINO_ARCH_ESP32 -#include -#endif -#ifdef ARDUINO_ARCH_ESP8266 -#include -#endif - #include #include @@ -66,7 +59,7 @@ void WebServer::set_js_url(const char *js_url) { this->js_url_ = js_url; } void WebServer::setup() { ESP_LOGCONFIG(TAG, "Setting up web server..."); - this->server_ = new AsyncWebServer(this->port_); + this->base_->init(); this->events_.onConnect([this](AsyncEventSourceClient *client) { // Configure reconnect timeout @@ -114,91 +107,18 @@ void WebServer::setup() { logger::global_logger->add_on_log_callback( [this](int level, const char *tag, const char *message) { this->events_.send(message, "log", millis()); }); #endif - this->server_->addHandler(this); - this->server_->addHandler(&this->events_); - - this->server_->begin(); + this->base_->add_handler(&this->events_); + this->base_->add_handler(this); + this->base_->add_ota_handler(); this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); }); } void WebServer::dump_config() { ESP_LOGCONFIG(TAG, "Web Server:"); - ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_); + ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->base_->get_port()); } float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; } -void WebServer::handle_update_request(AsyncWebServerRequest *request) { - AsyncWebServerResponse *response; - if (!Update.hasError()) { - response = request->beginResponse(200, "text/plain", "Update Successful!"); - } else { - StreamString ss; - ss.print("Update Failed: "); - Update.printError(ss); - response = request->beginResponse(200, "text/plain", ss); - } - response->addHeader("Connection", "close"); - request->send(response); -} - -void report_ota_error() { - StreamString ss; - Update.printError(ss); - ESP_LOGW(TAG, "OTA Update failed! Error: %s", ss.c_str()); -} - -void WebServer::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, - size_t len, bool final) { - bool success; - if (index == 0) { - ESP_LOGI(TAG, "OTA Update Start: %s", filename.c_str()); - this->ota_read_length_ = 0; -#ifdef ARDUINO_ARCH_ESP8266 - Update.runAsync(true); - success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); -#endif -#ifdef ARDUINO_ARCH_ESP32 - if (Update.isRunning()) - Update.abort(); - success = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH); -#endif - if (!success) { - report_ota_error(); - return; - } - } else if (Update.hasError()) { - // don't spam logs with errors if something failed at start - return; - } - - success = Update.write(data, len) == len; - if (!success) { - report_ota_error(); - return; - } - this->ota_read_length_ += len; - - const uint32_t now = millis(); - if (now - this->last_ota_progress_ > 1000) { - if (request->contentLength() != 0) { - float percentage = (this->ota_read_length_ * 100.0f) / request->contentLength(); - ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage); - } else { - ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_); - } - this->last_ota_progress_ = now; - } - - if (final) { - if (Update.end(true)) { - ESP_LOGI(TAG, "OTA update successful!"); - this->set_timeout(100, []() { App.safe_reboot(); }); - } else { - report_ota_error(); - } - } -} - void WebServer::handle_index_request(AsyncWebServerRequest *request) { AsyncResponseStream *stream = request->beginResponseStream("text/html"); std::string title = App.get_name() + " Web Server"; @@ -248,7 +168,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream->print(F("

See ESPHome Web API for " "REST API documentation.

" - "

OTA Update

OTA Update
" "

Debug Log

"
                   "